959 lines
39 KiB
C#
959 lines
39 KiB
C#
using Shared.Engine.RxEnumerate;
|
||
using Shared.Models;
|
||
using Shared.Models.Base;
|
||
using Shared.Models.Online;
|
||
using Shared.Models.Online.Rezka;
|
||
using Shared.Models.Online.Settings;
|
||
using Shared.Models.Templates;
|
||
using System.Collections.Concurrent;
|
||
using System.Text;
|
||
using System.Text.RegularExpressions;
|
||
using System.Web;
|
||
|
||
namespace Shared.Engine.Online
|
||
{
|
||
public class RezkaInvoke
|
||
{
|
||
#region RezkaInvoke
|
||
RezkaSettings init;
|
||
string host, scheme, route;
|
||
string apihost;
|
||
bool usehls, userprem, usereserve;
|
||
HttpHydra httpHydra;
|
||
List<HeadersModel> defaultHeaders;
|
||
Func<string, string> onstreamfile;
|
||
Action requesterror;
|
||
bool safety = false;
|
||
|
||
public string requestlog = string.Empty;
|
||
|
||
static readonly ConcurrentDictionary<long, string> basereferer = new();
|
||
|
||
public RezkaInvoke(string host, string route, RezkaSettings init, bool safety, List<HeadersModel> defaultHeaders, HttpHydra httpHydra, Func<string, string> onstreamfile, Action requesterror = null)
|
||
{
|
||
this.host = host != null ? $"{host}/" : null;
|
||
this.route = route;
|
||
this.init = init;
|
||
this.safety = safety;
|
||
apihost = init.corsHost();
|
||
scheme = init.scheme;
|
||
this.httpHydra = httpHydra;
|
||
this.defaultHeaders = defaultHeaders;
|
||
this.onstreamfile = onstreamfile;
|
||
usehls = init.hls;
|
||
usereserve = init.reserve;
|
||
userprem = init.premium;
|
||
this.requesterror = requesterror;
|
||
|
||
if (apihost.Contains("="))
|
||
{
|
||
char[] buffer = apihost.ToCharArray();
|
||
for (int i = 0; i < buffer.Length; i++)
|
||
{
|
||
char letter = buffer[i];
|
||
letter = (char)(letter - 3);
|
||
buffer[i] = letter;
|
||
}
|
||
|
||
apihost = new string(buffer);
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region Search
|
||
async public Task<SearchModel> Search(string title, string original_title, int clarification, int year)
|
||
{
|
||
var result = new SearchModel();
|
||
string reservedlink = null;
|
||
|
||
var base_headers = HeadersModel.Init(init.headers,
|
||
("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"),
|
||
("cache-control", "no-cache"),
|
||
("dnt", "1"),
|
||
("pragma", "no-cache"),
|
||
("sec-fetch-dest", "document"),
|
||
("sec-fetch-mode", "navigate"),
|
||
("sec-fetch-site", "same-origin"),
|
||
("sec-fetch-user", "?1"),
|
||
("upgrade-insecure-requests", "1"),
|
||
("referer", $"{apihost}/")
|
||
);
|
||
|
||
var newheaders = init.premium
|
||
? defaultHeaders
|
||
: HeadersModel.Join(base_headers, defaultHeaders);
|
||
|
||
result.search_uri = $"{apihost}/search/?do=search&subaction=search&q={HttpUtility.UrlEncode(clarification == 1 ? title : (original_title ?? title))}";
|
||
|
||
await httpHydra.GetSpan(result.search_uri, statusCodeOK: false, safety: safety, newheaders: newheaders, spanAction: search =>
|
||
{
|
||
if (search.Contains("class=\"error-code\"", StringComparison.OrdinalIgnoreCase) && search.Contains("ошибка доступа", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (search.Contains("(105)", StringComparison.Ordinal) ||
|
||
search.Contains(">105<", StringComparison.Ordinal) ||
|
||
search.Contains("(403)", StringComparison.Ordinal) ||
|
||
search.Contains(">403<", StringComparison.Ordinal))
|
||
{
|
||
result = new SearchModel() { IsEmpty = true, content = "Ошибка доступа (105)<br>IP-адрес заблокирован<br><br>" };
|
||
return;
|
||
}
|
||
|
||
if (search.Contains("(101)", StringComparison.Ordinal) ||
|
||
search.Contains(">101<", StringComparison.Ordinal))
|
||
{
|
||
result = new SearchModel() { IsEmpty = true, content = "Ошибка доступа (101)<br>Аккаунт заблокирован<br><br>" };
|
||
return;
|
||
}
|
||
|
||
var accessError = Rx.Groups(search, "Ошибка доступа[ \t]+\\(([0-9]+)\\)", RegexOptions.IgnoreCase);
|
||
result = new SearchModel() { IsEmpty = true, content = $"Ошибка доступа ({accessError[1].Value})" };
|
||
return;
|
||
}
|
||
|
||
string stitle = StringConvert.SearchName(title);
|
||
string sorigtitle = StringConvert.SearchName(original_title);
|
||
|
||
var rx = Rx.Split("\"b-content__inline_item\"", search, 1);
|
||
if (rx.Count == 0)
|
||
{
|
||
result.IsError = true;
|
||
return;
|
||
}
|
||
|
||
foreach (var row in rx.Rows())
|
||
{
|
||
var g = row.Groups("href=\"https?://[^/]+/([^\"]+)\">([^<]+)</a> ?<div>([0-9]{4})");
|
||
|
||
if (string.IsNullOrEmpty(g[1].Value))
|
||
continue;
|
||
|
||
string name = g[2].Value.Trim();
|
||
if (string.IsNullOrEmpty(name))
|
||
continue;
|
||
|
||
if (result.similar == null)
|
||
result.similar = new List<SimilarModel>(rx.Count);
|
||
|
||
string img = row.Match("<img src=\"([^\"]+)\"");
|
||
result.similar.Add(new SimilarModel(name, g[3].Value, g[1].Value, img));
|
||
|
||
string _sname = StringConvert.SearchName(name);
|
||
|
||
if ((stitle != null && (name.Contains(" / ") && _sname.Contains(stitle) || _sname == stitle)) ||
|
||
(sorigtitle != null && (name.Contains(" / ") && _sname.Contains(sorigtitle) || _sname == sorigtitle)))
|
||
{
|
||
reservedlink = g[1].Value;
|
||
|
||
if (string.IsNullOrEmpty(result.href) && year > 0 && g[3].Value == year.ToString())
|
||
result.href = reservedlink;
|
||
}
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(result.href))
|
||
{
|
||
if (string.IsNullOrEmpty(reservedlink))
|
||
{
|
||
if (result.similar.Count > 0)
|
||
return;
|
||
|
||
if (search.Contains("Результаты поиска", StringComparison.Ordinal))
|
||
{
|
||
result = new SearchModel() { IsEmpty = true };
|
||
return;
|
||
}
|
||
|
||
result.IsError = true;
|
||
return;
|
||
}
|
||
|
||
result.href = reservedlink;
|
||
}
|
||
});
|
||
|
||
if (result.IsError)
|
||
requesterror?.Invoke();
|
||
|
||
return result;
|
||
}
|
||
#endregion
|
||
|
||
#region Embed
|
||
async public Task<EmbedModel> Embed(string href, string search_uri)
|
||
{
|
||
if (!href.StartsWith("http"))
|
||
href = $"{apihost}/{href}";
|
||
|
||
var result = new EmbedModel();
|
||
|
||
var base_headers = HeadersModel.Init(init.headers,
|
||
("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"),
|
||
("cache-control", "no-cache"),
|
||
("dnt", "1"),
|
||
("pragma", "no-cache"),
|
||
("sec-fetch-dest", "document"),
|
||
("sec-fetch-mode", "navigate"),
|
||
("sec-fetch-site", "same-origin"),
|
||
("sec-fetch-user", "?1"),
|
||
("upgrade-insecure-requests", "1")
|
||
);
|
||
|
||
if (!string.IsNullOrEmpty(search_uri))
|
||
base_headers.Add(new HeadersModel("referer", search_uri));
|
||
|
||
var newheaders = init.premium
|
||
? defaultHeaders
|
||
: HeadersModel.Join(base_headers, defaultHeaders);
|
||
|
||
result.id = Regex.Match(href, "/([0-9]+)-[^/]+\\.html").Groups[1].Value;
|
||
if (long.TryParse(result.id, out long id) && id > 0)
|
||
basereferer.TryAdd(id, href);
|
||
|
||
bool IsTrailer = false;
|
||
|
||
await httpHydra.GetSpan(href, safety: safety, newheaders: newheaders, spanAction: html =>
|
||
{
|
||
IsTrailer = html.Contains("Ожидаем фильм в хорошем качестве", StringComparison.OrdinalIgnoreCase);
|
||
|
||
if (!html.Contains("data-season_id=", StringComparison.Ordinal))
|
||
{
|
||
var rx = Rx.Split("ctrl_token_id", html);
|
||
if (rx.Count > 0)
|
||
{
|
||
var row = Rx.Split("translators-list", rx[0].Span);
|
||
if (row.Count > 1)
|
||
result.content = row[1].ToString(); // фильм
|
||
}
|
||
}
|
||
else
|
||
{
|
||
string trs = Rx.Match(html, "\\.initCDNSeriesEvents\\([0-9]+, ([0-9]+),");
|
||
if (string.IsNullOrEmpty(trs))
|
||
return;
|
||
|
||
result.trs = trs;
|
||
|
||
var rx = Rx.Split("user-network-issues", html);
|
||
if (rx.Count > 0)
|
||
{
|
||
var row = Rx.Split("b-post__lastepisodeout", rx[0].Span);
|
||
if (row.Count > 1)
|
||
result.content = row[1].ToString(); // сериал
|
||
else if (!IsTrailer)
|
||
result.content = rx[0].Span.ToString(); // что то пошло не так
|
||
}
|
||
}
|
||
});
|
||
|
||
if (string.IsNullOrEmpty(result.content))
|
||
{
|
||
if (IsTrailer)
|
||
return new EmbedModel() { IsEmpty = true, content = "Ожидаем фильм в хорошем качестве..." };
|
||
|
||
requesterror?.Invoke();
|
||
return null;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
#endregion
|
||
|
||
#region EmbedID
|
||
//async public Task<EmbedModel> EmbedID(long kinopoisk_id, string imdb_id)
|
||
//{
|
||
// string search = await onpost($"{apihost}/engine/ajax/search.php", "q=%2B" + (!string.IsNullOrEmpty(imdb_id) ? imdb_id : kinopoisk_id.ToString()), null);
|
||
// if (search == null)
|
||
// {
|
||
// requesterror?.Invoke();
|
||
// return null;
|
||
// }
|
||
|
||
// string link = null;
|
||
// var result = new EmbedModel();
|
||
|
||
// var rx = Rx.Split("<li>", search, 1);
|
||
|
||
// foreach (var row in rx.Rows())
|
||
// {
|
||
// string href = row.Match("href=\"(https?://[^\"]+)\"");
|
||
// string name = row.Match("<span class=\"enty\">([^<]+)</span>");
|
||
// string year = row.Match(", ([0-9]{4})(\\)| -)");
|
||
|
||
// if (string.IsNullOrEmpty(href) || string.IsNullOrEmpty(name))
|
||
// continue;
|
||
|
||
// if (result.similar == null)
|
||
// result.similar = new List<SimilarModel>(rx.Count);
|
||
|
||
// result.similar.Add(new SimilarModel(name, year, href, null));
|
||
// link = href;
|
||
// }
|
||
|
||
// if (result?.similar != null && result.similar.Count > 1)
|
||
// return result;
|
||
|
||
// if (string.IsNullOrEmpty(link))
|
||
// {
|
||
// if (search.Contains("b-search__section_title"))
|
||
// return new EmbedModel() { IsEmpty = true };
|
||
|
||
// return null;
|
||
// }
|
||
|
||
// result!.id = Regex.Match(link, "/([0-9]+)-[^/]+\\.html").Groups[1].Value;
|
||
// result.content = await onget(link, null);
|
||
|
||
// if (result.content == null || string.IsNullOrEmpty(result.id))
|
||
// {
|
||
// if (result.content == null)
|
||
// requesterror?.Invoke();
|
||
|
||
// return null;
|
||
// }
|
||
|
||
// return result;
|
||
//}
|
||
#endregion
|
||
|
||
#region Tpl
|
||
public ITplResult Tpl(EmbedModel result, string args, string title, string original_title, int s, string href, bool showstream, bool rjson = false)
|
||
{
|
||
if (result == null || result.IsEmpty || result.content == null)
|
||
return default;
|
||
|
||
if (!string.IsNullOrEmpty(args))
|
||
args = $"&{args.Remove(0, 1)}";
|
||
|
||
string enc_title = HttpUtility.UrlEncode(title);
|
||
string enc_original_title = HttpUtility.UrlEncode(original_title);
|
||
string enc_href = HttpUtility.UrlEncode(href);
|
||
|
||
if (!result.content.Contains("data-season_id="))
|
||
{
|
||
#region Фильм
|
||
var match = new Regex("<[^>]+ data-translator_id=\"([0-9]+)\"([^>]+)?>(?<voice>[^<]+)(<img title=\"(?<imgname>[^\"]+)\" [^>]+/>)?").Match(result.content);
|
||
|
||
var mtpl = new MovieTpl(title, original_title, match.Length);
|
||
|
||
if (match.Success)
|
||
{
|
||
while (match.Success)
|
||
{
|
||
if (!string.IsNullOrEmpty(match.Groups[1].Value) && !string.IsNullOrEmpty(match.Groups["voice"].Value))
|
||
{
|
||
if (!userprem && match.Groups[0].Value.Contains("prem_translator"))
|
||
{
|
||
match = match.NextMatch();
|
||
continue;
|
||
}
|
||
|
||
string favs = Regex.Match(result.content, "id=\"ctrl_favs\" value=\"([^\"]+)\"").Groups[1].Value;
|
||
string link = host + $"{route}/movie?title={enc_title}&original_title={enc_original_title}&id={result.id}&t={match.Groups[1].Value}&favs={favs}";
|
||
|
||
string voice_href = Regex.Match(match.Groups[0].Value, "href=\"(https?://[^/]+)?/([^\"]+)\"").Groups[2].Value;
|
||
if (!string.IsNullOrEmpty(voice_href))
|
||
link += $"&voice={HttpUtility.UrlEncode(voice_href)}";
|
||
|
||
#region voice
|
||
string voice = match.Groups["voice"].Value.Trim();
|
||
|
||
if (!string.IsNullOrEmpty(match.Groups["imgname"].Value) && !voice.ToLower().Contains(match.Groups["imgname"].Value.ToLowerAndTrim()))
|
||
voice += $" ({match.Groups["imgname"].Value.Trim()})";
|
||
|
||
if (voice == "-" || string.IsNullOrEmpty(voice))
|
||
voice = "Оригинал";
|
||
#endregion
|
||
|
||
if (match.Groups[2].Value.Contains("data-director=\"1\""))
|
||
link += "&director=1";
|
||
|
||
string stream = null;
|
||
if (showstream)
|
||
{
|
||
stream = usehls ? $"{link.Replace("/movie", "/movie.m3u8")}&play=true" : $"{link}&play=true";
|
||
stream += args;
|
||
}
|
||
|
||
mtpl.Append(voice, link, "call", stream);
|
||
}
|
||
|
||
match = match.NextMatch();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var links = getStreamLink(Regex.Match(result.content, "\"id\":\"cdnplayer\",\"streams\":\"([^\"]+)\"").Groups[1].Value.Replace("\\", ""));
|
||
if (links.Count == 0)
|
||
return default;
|
||
|
||
var streamquality = new StreamQualityTpl(links.Select(l => (onstreamfile(l.stream_url!), l.title!)));
|
||
var first = streamquality.Firts();
|
||
|
||
mtpl.Append(first.quality, onstreamfile(first.link), streamquality: streamquality);
|
||
}
|
||
|
||
return mtpl;
|
||
#endregion
|
||
}
|
||
else
|
||
{
|
||
#region Перевод
|
||
var vtpl = new VoiceTpl();
|
||
|
||
if (result.content.Contains("data-translator_id="))
|
||
{
|
||
var match = new Regex("<[a-z]+ [^>]+ data-translator_id=\"(?<translator>[0-9]+)\"([^>]+)?>(?<name>[^<]+)(<img title=\"(?<imgname>[^\"]+)\" [^>]+/>)?").Match(result.content);
|
||
while (match.Success)
|
||
{
|
||
if (!userprem && match.Groups[0].Value.Contains("prem_translator"))
|
||
{
|
||
match = match.NextMatch();
|
||
continue;
|
||
}
|
||
|
||
string name = match.Groups["name"].Value.Trim();
|
||
if (!string.IsNullOrEmpty(match.Groups["imgname"].Value) && !name.ToLower().Contains(match.Groups["imgname"].Value.ToLowerAndTrim()))
|
||
name += $" ({match.Groups["imgname"].Value})";
|
||
|
||
string link = host + $"{route}/serial?rjson={rjson}&title={enc_title}&original_title={enc_original_title}&href={enc_href}&id={result.id}&t={match.Groups["translator"].Value}";
|
||
|
||
string voice_href = Regex.Match(match.Groups[0].Value, "href=\"(https?://[^/]+)?/([^\"]+)\"").Groups[2].Value;
|
||
if (!string.IsNullOrEmpty(voice_href) && init.ajax != null && init.ajax.Value == false)
|
||
{
|
||
string voice = HttpUtility.UrlEncode(voice_href);
|
||
link = host + $"{route}?rjson={rjson}&title={enc_title}&original_title={enc_original_title}&href={voice}&id={result.id}&t={match.Groups["translator"].Value}";
|
||
}
|
||
|
||
vtpl.Append(name, match.Groups["translator"].Value == result.trs, link);
|
||
|
||
match = match.NextMatch();
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region Сериал
|
||
var tpl = new SeasonTpl(vtpl);
|
||
var etpl = new EpisodeTpl(vtpl);
|
||
HashSet<string> eshash = new HashSet<string>();
|
||
|
||
string sArhc = s.ToString();
|
||
|
||
var m = Regex.Match(result.content, "data-cdn_url=\"(?<cdn>[^\"]+)\" [^>]+ data-season_id=\"(?<season>[0-9]+)\" data-episode_id=\"(?<episode>[0-9]+)\"([^>]+)?>(?<name>[^>]+)</[a-z]+>");
|
||
while (m.Success)
|
||
{
|
||
if (s == -1)
|
||
{
|
||
#region Сезоны
|
||
string sname = $"{m.Groups["season"].Value} сезон";
|
||
if (!string.IsNullOrEmpty(m.Groups["season"].Value) && !eshash.Contains(sname))
|
||
{
|
||
eshash.Add(sname);
|
||
string link = host + $"{route}?rjson={rjson}&title={enc_title}&original_title={enc_original_title}&href={enc_href}&t={result.trs}&s={m.Groups["season"].Value}";
|
||
|
||
tpl.Append(sname, link, m.Groups["season"].Value);
|
||
}
|
||
#endregion
|
||
}
|
||
else
|
||
{
|
||
#region Серии
|
||
if (m.Groups["season"].Value == sArhc && !eshash.Contains(m.Groups["name"].Value))
|
||
{
|
||
eshash.Add(m.Groups["name"].Value);
|
||
string link = host + $"{route}/movie?title={enc_title}&original_title={enc_original_title}&id={result.id}&t={result.trs}&s={s}&e={m.Groups["episode"].Value}";
|
||
|
||
string voice_href = Regex.Match(m.Groups[0].Value, "href=\"(https?://[^/]+)?/([^\"]+)\"").Groups[2].Value;
|
||
if (!string.IsNullOrEmpty(voice_href))
|
||
link += $"&voice={HttpUtility.UrlEncode(voice_href)}";
|
||
|
||
string stream = null;
|
||
if (showstream)
|
||
{
|
||
stream = usehls ? $"{link.Replace("/movie", "/movie.m3u8")}&play=true" : $"{link}&play=true";
|
||
stream += args;
|
||
}
|
||
|
||
etpl.Append(m.Groups["name"].Value, title ?? original_title, sArhc, m.Groups["episode"].Value, link, "call", streamlink: stream);
|
||
}
|
||
#endregion
|
||
}
|
||
|
||
m = m.NextMatch();
|
||
}
|
||
|
||
if (s == -1)
|
||
return tpl;
|
||
|
||
return etpl;
|
||
#endregion
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
|
||
#region Serial
|
||
async public Task<Episodes> SerialEmbed(long id, int t)
|
||
{
|
||
var base_headers = HeadersModel.Init(init.headers,
|
||
("accept", "application/json, text/javascript, */*; q=0.01"),
|
||
("cache-control", "no-cache"),
|
||
("dnt", "1"),
|
||
("origin", apihost),
|
||
("pragma", "no-cache"),
|
||
("sec-fetch-dest", "empty"),
|
||
("sec-fetch-mode", "cors"),
|
||
("sec-fetch-site", "same-origin"),
|
||
("x-requested-with", "XMLHttpRequest")
|
||
);
|
||
|
||
if (basereferer.TryGetValue(id, out string referer) && !string.IsNullOrEmpty(referer))
|
||
base_headers.Add(new HeadersModel("referer", referer));
|
||
|
||
var newheaders = init.premium
|
||
? defaultHeaders
|
||
: HeadersModel.Join(base_headers, defaultHeaders);
|
||
|
||
string uri = $"{apihost}/ajax/get_cdn_series/?t={((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds()}{Random.Shared.Next(101, 999)}";
|
||
string data = $"id={id}&translator_id={t}&action=get_episodes";
|
||
|
||
var root = await httpHydra.Post<Episodes>(uri, data, safety: safety, newheaders: newheaders);
|
||
if (root == null)
|
||
{
|
||
requesterror?.Invoke();
|
||
return null;
|
||
}
|
||
|
||
string episodes = root.episodes;
|
||
if (string.IsNullOrWhiteSpace(episodes) || episodes.Contains("false", StringComparison.OrdinalIgnoreCase))
|
||
return null;
|
||
|
||
return root;
|
||
}
|
||
|
||
public ITplResult Serial(Episodes root, EmbedModel result, string args, string title, string original_title, string href, long id, int t, int s, bool showstream, bool rjson = false)
|
||
{
|
||
if (root == null || result == null)
|
||
return default;
|
||
|
||
if (!string.IsNullOrEmpty(args))
|
||
args = $"&{args.Remove(0, 1)}";
|
||
|
||
string enc_title = HttpUtility.UrlEncode(title);
|
||
string enc_original_title = HttpUtility.UrlEncode(original_title);
|
||
string enc_href = HttpUtility.UrlEncode(href);
|
||
|
||
#region Перевод
|
||
var vtpl = new VoiceTpl();
|
||
|
||
{
|
||
if (string.IsNullOrWhiteSpace(href))
|
||
return default;
|
||
|
||
if (result?.content != null)
|
||
{
|
||
if (result.content.Contains("data-translator_id="))
|
||
{
|
||
var match = new Regex("<[a-z]+ [^>]+ data-translator_id=\"(?<translator>[0-9]+)\"([^>]+)?>(?<name>[^<]+)(<img title=\"(?<imgname>[^\"]+)\" [^>]+/>)?").Match(result.content);
|
||
while (match.Success)
|
||
{
|
||
if (!userprem && match.Groups[0].Value.Contains("prem_translator"))
|
||
{
|
||
match = match.NextMatch();
|
||
continue;
|
||
}
|
||
|
||
string name = match.Groups["name"].Value.Trim() + (string.IsNullOrWhiteSpace(match.Groups["imgname"].Value) ? "" : $" ({match.Groups["imgname"].Value})");
|
||
string link = host + $"{route}/serial?rjson={rjson}&title={enc_title}&original_title={enc_original_title}&href={enc_href}&id={id}&t={match.Groups["translator"].Value}";
|
||
|
||
vtpl.Append(name, match.Groups["translator"].Value == t.ToString(), link);
|
||
|
||
match = match.NextMatch();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
if (s == -1)
|
||
{
|
||
#region Сезоны
|
||
var tpl = new SeasonTpl(vtpl, root.seasons.Length);
|
||
|
||
var match = new Regex("data-tab_id=\"(?<season>[0-9]+)\"([^>]+)?>(?<name>[^<]+)</[a-z]+>").Match(root.seasons);
|
||
while (match.Success)
|
||
{
|
||
string link = host + $"{route}/serial?rjson={rjson}&title={enc_title}&original_title={enc_original_title}&href={enc_href}&id={id}&t={t}&s={match.Groups["season"].Value}";
|
||
|
||
tpl.Append($"{match.Groups["season"].Value} сезон", link, match.Groups["season"].Value);
|
||
|
||
match = match.NextMatch();
|
||
}
|
||
|
||
return tpl;
|
||
#endregion
|
||
}
|
||
else
|
||
{
|
||
#region Серии
|
||
var etpl = new EpisodeTpl(vtpl);
|
||
|
||
var m = new Regex($"data-season_id=\"{s}\" data-episode_id=\"(?<episode>[0-9]+)\"([^>]+)?>(?<name>[^<]+)</li>").Match(root.episodes);
|
||
while (m.Success)
|
||
{
|
||
if (!string.IsNullOrEmpty(m.Groups["episode"].Value) && !string.IsNullOrEmpty(m.Groups["name"].Value))
|
||
{
|
||
string link = host + $"{route}/movie?title={enc_title}&original_title={enc_original_title}&id={id}&t={t}&s={s}&e={m.Groups["episode"].Value}";
|
||
|
||
string voice_href = Regex.Match(m.Groups[0].Value, "href=\"(https?://[^/]+)?/([^\"]+)\"").Groups[2].Value;
|
||
if (!string.IsNullOrEmpty(voice_href))
|
||
link += $"&voice={HttpUtility.UrlEncode(voice_href)}";
|
||
|
||
string stream = usehls ? $"{link.Replace("/movie", "/movie.m3u8")}&play=true" : $"{link}&play=true";
|
||
|
||
etpl.Append(m.Groups["name"].Value, title ?? original_title, s.ToString(), m.Groups["episode"].Value, link, "call", streamlink: (showstream ? $"{stream}{args}" : null));
|
||
}
|
||
|
||
m = m.NextMatch();
|
||
}
|
||
|
||
return etpl;
|
||
#endregion
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region Movie
|
||
async public Task<MovieModel> Movie(long id, int t, int director, int s, int e, string favs)
|
||
{
|
||
string data = null;
|
||
string uri = $"{apihost}/ajax/get_cdn_series/?t={((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds()}{Random.Shared.Next(101, 999)}";
|
||
|
||
if (s == -1)
|
||
{
|
||
data = $"id={id}&translator_id={t}&is_camrip=0&is_ads=0&is_director={director}&favs={favs}&action=get_movie";
|
||
}
|
||
else
|
||
{
|
||
data = $"id={id}&translator_id={t}&season={s}&episode={e}&favs={favs}&action=get_stream";
|
||
}
|
||
|
||
var base_headers = HeadersModel.Init(init.headers,
|
||
("accept", "application/json, text/javascript, */*; q=0.01"),
|
||
("cache-control", "no-cache"),
|
||
("dnt", "1"),
|
||
("origin", apihost),
|
||
("pragma", "no-cache"),
|
||
("sec-fetch-dest", "empty"),
|
||
("sec-fetch-mode", "cors"),
|
||
("sec-fetch-site", "same-origin"),
|
||
("x-requested-with", "XMLHttpRequest")
|
||
);
|
||
|
||
if (basereferer.TryGetValue(id, out string referer) && !string.IsNullOrEmpty(referer))
|
||
base_headers.Add(new HeadersModel("referer", referer));
|
||
|
||
var newheaders = init.premium
|
||
? defaultHeaders
|
||
: HeadersModel.Join(base_headers, defaultHeaders);
|
||
|
||
var root = await httpHydra.Post<Dictionary<string, object>>(uri, data, safety: safety, newheaders: newheaders);
|
||
|
||
if (root == null)
|
||
{
|
||
requesterror?.Invoke();
|
||
return null;
|
||
}
|
||
|
||
string url = root.ContainsKey("url") ? root["url"]?.ToString() : null;
|
||
if (string.IsNullOrEmpty(url) || url.Contains("false", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
requesterror?.Invoke();
|
||
return null;
|
||
}
|
||
|
||
var links = getStreamLink(url);
|
||
if (links.Count == 0)
|
||
return null;
|
||
|
||
string subtitlehtml = null;
|
||
|
||
try
|
||
{
|
||
subtitlehtml = root?["subtitle"]?.ToString();
|
||
}
|
||
catch { }
|
||
|
||
return new MovieModel() { links = links, subtitlehtml = subtitlehtml };
|
||
}
|
||
|
||
async public Task<MovieModel> Movie(string href)
|
||
{
|
||
var base_headers = HeadersModel.Init(init.headers,
|
||
("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"),
|
||
("cache-control", "no-cache"),
|
||
("dnt", "1"),
|
||
("pragma", "no-cache"),
|
||
("sec-fetch-dest", "document"),
|
||
("sec-fetch-mode", "navigate"),
|
||
("sec-fetch-site", "same-origin"),
|
||
("sec-fetch-user", "?1"),
|
||
("upgrade-insecure-requests", "1")
|
||
);
|
||
|
||
var newheaders = init.premium
|
||
? defaultHeaders
|
||
: HeadersModel.Join(base_headers, defaultHeaders);
|
||
|
||
List<ApiModel> links = null;
|
||
string subtitlehtml = null;
|
||
|
||
await httpHydra.GetSpan($"{apihost}/{href}", safety: safety, newheaders: newheaders, spanAction: html =>
|
||
{
|
||
string url = Rx.Match(html, "\"streams\"\\s*:\\s*\"(.*?)\"\\s*,");
|
||
if (string.IsNullOrEmpty(url) || url.Contains("false", StringComparison.OrdinalIgnoreCase))
|
||
return;
|
||
|
||
links = getStreamLink(url.Replace("\\", ""));
|
||
|
||
if (links.Count > 0)
|
||
subtitlehtml = Rx.Match(html, "\"subtitle\":\"([^\"]+)\"");
|
||
});
|
||
|
||
if (links == null || links.Count == 0)
|
||
{
|
||
requesterror?.Invoke();
|
||
return null;
|
||
}
|
||
|
||
return new MovieModel()
|
||
{
|
||
links = links,
|
||
subtitlehtml = subtitlehtml
|
||
};
|
||
}
|
||
|
||
public string Movie(MovieModel md, string title, string original_title, bool play, VastConf vast = null)
|
||
{
|
||
if (play)
|
||
return onstreamfile(md.links[0].stream_url!);
|
||
|
||
#region subtitles
|
||
var subtitles = new SubtitleTpl();
|
||
|
||
try
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(md.subtitlehtml))
|
||
{
|
||
var m = Regex.Match(md.subtitlehtml, "\\[([^\\]]+)\\](https?://[^\n\r,']+\\.vtt)");
|
||
while (m.Success)
|
||
{
|
||
if (!string.IsNullOrEmpty(m.Groups[1].Value) && !string.IsNullOrEmpty(m.Groups[2].Value))
|
||
subtitles.Append(m.Groups[1].Value, onstreamfile(m.Groups[2].Value));
|
||
|
||
m = m.NextMatch();
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
#endregion
|
||
|
||
var streamquality = new StreamQualityTpl();
|
||
foreach (var l in md.links)
|
||
streamquality.Append(onstreamfile(l.stream_url!), l.title);
|
||
|
||
return VideoTpl.ToJson("play", onstreamfile(md.links[0].stream_url!), (title ?? original_title ?? "auto"),
|
||
streamquality: streamquality,
|
||
subtitles: subtitles,
|
||
vast: vast,
|
||
hls_manifest_timeout: (int)TimeSpan.FromSeconds(20).TotalMilliseconds
|
||
);
|
||
}
|
||
#endregion
|
||
|
||
|
||
#region decodeBase64
|
||
static string[] trashListBase = ["JCQhIUAkJEBeIUAjJCRA", "QEBAQEAhIyMhXl5e", "IyMjI14hISMjIUBA", "Xl5eIUAjIyEhIyM=", "JCQjISFAIyFAIyM="];
|
||
static string[] trashListOld = ["QEA=", "QCM=", "QCE=", "QF4=", "QCQ=", "I0A=", "IyM=", "IyE=", "I14=", "IyQ=", "IUA=", "ISM=", "ISE=", "IV4=", "ISQ=", "XkA=", "XiM=", "XiE=", "Xl4=", "XiQ=", "JEA=", "JCM=", "JCE=", "JF4=", "JCQ=", "QEBA", "QEAj", "QEAh", "QEBe", "QEAk", "QCNA", "QCMj", "QCMh", "QCNe", "QCMk", "QCFA", "QCEj", "QCEh", "QCFe", "QCEk", "QF5A", "QF4j", "QF4h", "QF5e", "QF4k", "QCRA", "QCQj", "QCQh", "QCRe", "QCQk", "I0BA", "I0Aj", "I0Ah", "I0Be", "I0Ak", "IyNA", "IyMj", "IyMh", "IyNe", "IyMk", "IyFA", "IyEj", "IyEh", "IyFe", "IyEk", "I15A", "I14j", "I14h", "I15e", "I14k", "IyRA", "IyQj", "IyQh", "IyRe", "IyQk", "IUBA", "IUAj", "IUAh", "IUBe", "IUAk", "ISNA", "ISMj", "ISMh", "ISNe", "ISMk", "ISFA", "ISEj", "ISEh", "ISFe", "ISEk", "IV5A", "IV4j", "IV4h", "IV5e", "IV4k", "ISRA", "ISQj", "ISQh", "ISRe", "ISQk", "XkBA", "XkAj", "XkAh", "XkBe", "XkAk", "XiNA", "XiMj", "XiMh", "XiNe", "XiMk", "XiFA", "XiEj", "XiEh", "XiFe", "XiEk", "Xl5A", "Xl4j", "Xl4h", "Xl5e", "Xl4k", "XiRA", "XiQj", "XiQh", "XiRe", "XiQk", "JEBA", "JEAj", "JEAh", "JEBe", "JEAk", "JCNA", "JCMj", "JCMh", "JCNe", "JCMk", "JCFA", "JCEj", "JCEh", "JCFe", "JCEk", "JF5A", "JF4j", "JF4h", "JF5e", "JF4k", "JCRA", "JCQj", "JCQh", "JCRe", "JCQk"];
|
||
|
||
static string decodeBase64(string data)
|
||
{
|
||
if (data.StartsWith("#"))
|
||
{
|
||
try
|
||
{
|
||
string _data = data.Remove(0, 2);
|
||
|
||
foreach (string trash in trashListBase)
|
||
{
|
||
if (_data.Contains($"//_//{trash}"))
|
||
_data = _data.Replace($"//_//{trash}", "");
|
||
}
|
||
|
||
try
|
||
{
|
||
return Encoding.UTF8.GetString(Convert.FromBase64String(_data));
|
||
}
|
||
catch
|
||
{
|
||
_data = Regex.Replace(_data, "//[^/]+_//", "").Replace("//_//", "");
|
||
_data = Encoding.UTF8.GetString(Convert.FromBase64String(_data));
|
||
|
||
return _data;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
string _data = data.Remove(0, 2).Replace("//_//", "");
|
||
|
||
foreach (string trash in trashListOld)
|
||
{
|
||
if (_data.Contains(trash))
|
||
_data = _data.Replace(trash, "");
|
||
}
|
||
|
||
_data = Regex.Replace(_data, "//[^/]+_//", "").Replace("//_//", "");
|
||
_data = Encoding.UTF8.GetString(Convert.FromBase64String(_data));
|
||
|
||
return _data;
|
||
}
|
||
}
|
||
|
||
return data;
|
||
}
|
||
#endregion
|
||
|
||
#region getStreamLink
|
||
List<ApiModel> getStreamLink(string _data)
|
||
{
|
||
string data = decodeBase64(_data);
|
||
var links = new List<ApiModel>(6);
|
||
|
||
#region getLink
|
||
string getLink(string _q)
|
||
{
|
||
string qline = Regex.Match(data, $"\\[({_q}|[^\\]]+{_q}[^\\]]+)\\]([^,\\[]+)").Groups[2].Value;
|
||
if (!qline.Contains(".mp4") && !qline.Contains(".m3u8"))
|
||
return null;
|
||
|
||
if (usereserve && qline.Contains(" or "))
|
||
{
|
||
return string.Join(" or ", qline.Split(" or ").Select(i =>
|
||
{
|
||
string l = Regex.Match(i, "(https?://[^\\[\n\r, ]+)").Groups[1].Value;
|
||
if (usehls)
|
||
{
|
||
if (l.EndsWith(".m3u8"))
|
||
return l;
|
||
|
||
return l + ":hls:manifest.m3u8";
|
||
}
|
||
|
||
return l.Replace(":hls:manifest.m3u8", "");
|
||
}));
|
||
}
|
||
else
|
||
{
|
||
string link = Regex.Match(qline, "(https?://[^\\[\n\r, ]+)").Groups[1].Value;
|
||
if (string.IsNullOrEmpty(link))
|
||
return null;
|
||
|
||
if (usehls)
|
||
{
|
||
if (link.EndsWith(".m3u8"))
|
||
return link;
|
||
|
||
return link + ":hls:manifest.m3u8";
|
||
}
|
||
|
||
return link.Replace(":hls:manifest.m3u8", "");
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region Максимально доступное
|
||
var qualities = new List<string> { "2160p", "1440p", "1080p", "720p", "480p" };
|
||
if (userprem)
|
||
qualities.InsertRange(2, new List<string> { "1080p Ultra" });
|
||
|
||
foreach (string q in qualities)
|
||
{
|
||
string link = null;
|
||
|
||
switch (q)
|
||
{
|
||
case "2160p":
|
||
link = getLink("4K") ?? getLink(q);
|
||
break;
|
||
case "1440p":
|
||
link = getLink("2K") ?? getLink(q);
|
||
break;
|
||
case "1080p":
|
||
link = userprem ? getLink(q) : (getLink(q) ?? getLink("1080p Ultra"));
|
||
break;
|
||
default:
|
||
link = getLink(q);
|
||
break;
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(link))
|
||
continue;
|
||
|
||
if (scheme == "http")
|
||
link = link.Replace("https:", "http:");
|
||
|
||
string realq = q;
|
||
|
||
switch (q)
|
||
{
|
||
case "1080p Ultra":
|
||
realq = "1080p";
|
||
break;
|
||
case "1080p":
|
||
realq = "720p";
|
||
break;
|
||
case "720p":
|
||
realq = "480p";
|
||
break;
|
||
case "480p":
|
||
realq = "360p";
|
||
break;
|
||
}
|
||
|
||
links.Add(new ApiModel()
|
||
{
|
||
title = realq,
|
||
stream_url = link
|
||
});
|
||
}
|
||
#endregion
|
||
|
||
return links;
|
||
}
|
||
#endregion
|
||
|
||
|
||
#region fixcdn
|
||
public static string fixcdn(string country, string uacdn, string link)
|
||
{
|
||
if (uacdn != null && country == "UA" && !link.Contains(".vtt"))
|
||
return Regex.Replace(link, "https?://[^/]+", uacdn);
|
||
|
||
return link;
|
||
}
|
||
#endregion
|
||
|
||
#region StreamProxyHeaders
|
||
public static List<HeadersModel> StreamProxyHeaders(RezkaSettings init) => HeadersModel.Init(init.headers,
|
||
("accept", "*/*"),
|
||
("cache-control", "no-cache"),
|
||
("dnt", "1"),
|
||
("origin", init.host),
|
||
("pragma", "no-cache"),
|
||
("referer", $"{init.host}/"),
|
||
("sec-fetch-dest", "empty"),
|
||
("sec-fetch-mode", "cors"),
|
||
("sec-fetch-site", "cross-site")
|
||
);
|
||
#endregion
|
||
}
|
||
}
|