lampac/Shared/Engine/Online/VideoCDN.cs
lampac-talks f843f04fd4 chore: initial commit 154.3
Signed-off-by: lampac-talks <lampac-talks@users.noreply.github.com>
2026-01-30 16:23:09 +03:00

379 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Shared.Models.Online.Settings;
using Shared.Models.Online.VideoCDN;
using Shared.Models.Templates;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Web;
namespace Shared.Engine.Online
{
public struct VideoCDNInvoke
{
#region VideoCDNInvoke
string host, scheme;
string iframeapihost;
string apihost;
string token;
bool usehls;
Func<string, string, Task<string>> onget;
Func<string, string> onstreamfile;
Action requesterror;
public string onstream(string stream)
{
if (onstreamfile == null)
return stream;
return onstreamfile.Invoke(stream);
}
public VideoCDNInvoke(OnlinesSettings init, Func<string, string, Task<string>> onget, Func<string, string> onstreamfile, string host = null, Action requesterror = null)
{
this.host = host != null ? $"{host}/" : null;
this.scheme = init.scheme;
this.iframeapihost = init.corsHost();
this.apihost = init.cors(init.apihost);
this.token = init!.token;
this.onget = onget;
this.onstreamfile = onstreamfile;
usehls = init.hls;
this.requesterror = requesterror;
}
#endregion
#region Search
public async Task<SimilarTpl> Search(string title, string original_title, int serial)
{
if (string.IsNullOrWhiteSpace(title ?? original_title))
return null;
string uri = $"{apihost}/api/short?api_token={token}&title={HttpUtility.UrlEncode(original_title ?? title)}";
string json = await onget.Invoke(uri, apihost);
if (json == null)
{
requesterror?.Invoke();
return null;
}
SearchRoot root = null;
try
{
root = JsonSerializer.Deserialize<SearchRoot>(json);
if (root?.data == null || root.data.Length == 0)
return null;
}
catch { return null; }
var stpl = new SimilarTpl(root.data.Length);
string enc_title = HttpUtility.UrlEncode(title);
string enc_original_title = HttpUtility.UrlEncode(original_title);
foreach (var item in root.data)
{
if (item.kp_id == 0 && string.IsNullOrEmpty(item.imdb_id))
continue;
if (serial != -1)
{
if ((serial == 0 && item.content_type != "movie") || (serial == 1 && item.content_type == "movie"))
continue;
}
bool isok = title != null && title.Length > 3 && item.title != null && item.title.ToLower().Contains(title.ToLower());
isok = isok ? true : original_title != null && original_title.Length > 3 && item.orig_title != null && item.orig_title.ToLower().Contains(original_title.ToLower());
if (!isok)
continue;
string year = item.add?.Split("-")?[0] ?? string.Empty;
string name = !string.IsNullOrEmpty(item.title) && !string.IsNullOrEmpty(item.orig_title) ? $"{item.title} / {item.orig_title}" : (item.title ?? item.orig_title);
string details = $"imdb: {item.imdb_id} {SimilarTpl.OnlineSplit} kinopoisk: {item.kp_id}";
stpl.Append(name, year, details, host + $"lite/vcdn?title={enc_title}&original_title={enc_original_title}&kinopoisk_id={item.kp_id}&imdb_id={item.imdb_id}");
}
return stpl;
}
#endregion
#region Embed
public async Task<EmbedModel> Embed(long kinopoisk_id, string imdb_id)
{
string args = kinopoisk_id > 0 ? $"kp_id={kinopoisk_id}&imdb_id={imdb_id}" : $"imdb_id={imdb_id}";
string content = await onget.Invoke($"{iframeapihost}?{args}", "https://kinogo.ec/113447-venom-3-poslednij-tanec.html");
if (content == null)
{
requesterror?.Invoke();
return null;
}
var result = new EmbedModel();
result.type = Regex.Match(content, "id=\"videoType\" value=\"([^\"]+)\"").Groups[1].Value;
result.voices = new Dictionary<string, string>();
if (content.Contains("</option>"))
{
result.voices.TryAdd("0", "По умолчанию");
var match = new Regex("<option +value=\"([0-9]+)\"[^>]+>([^<]+)</option>").Match(Regex.Replace(content, "[\n\r\t]+", ""));
while (match.Success)
{
string translation_id = match.Groups[1].Value;
string translation = match.Groups[2].Value.Trim();
if (!string.IsNullOrEmpty(translation_id) && !string.IsNullOrEmpty(translation))
result.voices.TryAdd(translation_id, translation);
match = match.NextMatch();
}
}
string Decode(string pass, string src)
{
try
{
int passLen = pass.Length;
int srcLen = src.Length;
byte[] passArr = new byte[passLen];
for (int i = 0; i < passLen; i++)
{
passArr[i] = (byte)pass[i];
}
StringBuilder res = new StringBuilder(PoolInvk.rentChunk);
for (int i = 0; i < srcLen; i += 2)
{
string hex = src.Substring(i, 2);
int code = Convert.ToInt32(hex, 16);
byte secret = (byte)(passArr[(i / 2) % passLen] % 255);
res.Append((char)(code ^ secret));
}
return res.ToString();
}
catch { return null; }
}
string files = null;
string client_id = Regex.Match(content, "id=\"client_id\" value=\"([^\"]+)\"").Groups[1].Value;
var m = Regex.Match(content, "<input type=\"hidden\" id=\"[^\"]+\" value=('|\")([^\"']+)");
while (m.Success)
{
string sentry_id = m.Groups[2].Value;
if (200 > sentry_id.Length || sentry_id.StartsWith("{"))
{
m = m.NextMatch();
continue;
}
files = Decode(client_id, sentry_id);
if (!string.IsNullOrEmpty(files))
break;
m = m.NextMatch();
}
if (string.IsNullOrEmpty(files))
{
files = Regex.Match(content, "value='(\\{\"[0-9]+\"[^\']+)'").Groups[1].Value;
if (string.IsNullOrEmpty(files))
return null;
}
result.quality = files.Contains("1080p") ? "1080p" : files.Contains("720p") ? "720p" : "480p";
try
{
if (result.type is "movie" or "anime")
{
result.movie = JsonSerializer.Deserialize<Dictionary<string, string>>(files);
if (result.movie == null)
return null;
}
else
{
result.serial = JsonSerializer.Deserialize<Dictionary<string, List<Season>>>(files);
if (result.serial == null)
return null;
#region voiceSeasons
result.voiceSeasons = new Dictionary<string, HashSet<int>>();
foreach (var voice in result.serial.OrderByDescending(k => k.Key == "0"))
{
if (result.voices.TryGetValue(voice.Key, out string name) && name != null)
{
foreach (var season in voice.Value)
{
if (result.voiceSeasons.TryGetValue(voice.Key, out HashSet<int> _s))
{
_s.Add(season.id);
}
else
{
result.voiceSeasons.TryAdd(voice.Key, new HashSet<int>() { season.id });
}
}
}
}
#endregion
}
}
catch { return null; }
return result;
}
#endregion
#region Tpl
public ITplResult Tpl(EmbedModel result, string imdb_id, long kinopoisk_id, string title, string original_title, string t, int s, bool rjson = false)
{
if (result == null)
return default;
if (result.type is "movie" or "anime")
{
#region Фильм
if (result.movie == null || result.movie.Count == 0)
return default;
var mtpl = new MovieTpl(title, original_title, result.movie.Count);
foreach (var voice in result.movie)
{
result.voices.TryGetValue(voice.Key, out string name);
if (string.IsNullOrEmpty(name))
{
if (result.movie.Count > 1)
continue;
name = "По умолчанию";
}
var streamquality = new StreamQualityTpl();
foreach (Match m in Regex.Matches(voice.Value, $"\\[(1080|720|480|360)p?\\]([^\\[\\|,\n\r\t ]+\\.(mp4|m3u8))"))
{
string link = m.Groups[2].Value;
if (string.IsNullOrEmpty(link))
continue;
if (usehls && !link.Contains(".m3u"))
link += ":hls:manifest.m3u8";
else if (!usehls && link.Contains(".m3u"))
link = link.Replace(":hls:manifest.m3u8", "");
streamquality.Insert(onstream($"{scheme}:{link}"), $"{m.Groups[1].Value}p");
}
mtpl.Append(name, streamquality.Firts().link, streamquality: streamquality);
}
return mtpl;
#endregion
}
else
{
#region Сериал
string enc_title = HttpUtility.UrlEncode(title);
string enc_original_title = HttpUtility.UrlEncode(original_title);
try
{
if (result.serial == null || result.serial.Count == 0)
return default;
if (s == -1)
{
var seasons = new HashSet<int>(10);
foreach (var voice in result.serial)
{
foreach (var season in voice.Value)
seasons.Add(season.id);
}
var tpl = new SeasonTpl(result.quality, seasons.Count);
foreach (int id in seasons.OrderBy(s => s))
{
string link = host + $"lite/vcdn?kinopoisk_id={kinopoisk_id}&imdb_id={imdb_id}&rjson={rjson}&title={enc_title}&original_title={enc_original_title}&s={id}";
tpl.Append($"{id} сезон", link, id);
}
return tpl;
}
else
{
#region Перевод
var vtpl = new VoiceTpl();
foreach (var voice in result.voiceSeasons)
{
if (!voice.Value.Contains(s))
continue;
if (result.voices.TryGetValue(voice.Key, out string name) && name != null)
{
if (string.IsNullOrEmpty(t))
t = voice.Key;
vtpl.Append(name, t == voice.Key, host + $"lite/vcdn?kinopoisk_id={kinopoisk_id}&imdb_id={imdb_id}&rjson={rjson}&title={enc_title}&original_title={enc_original_title}&s={s}&t={voice.Key}");
}
}
#endregion
if (string.IsNullOrEmpty(t))
t = "0";
var season = result.serial[t].First(i => i.id == s);
if (season.folder == null)
return default;
var etpl = new EpisodeTpl(vtpl, season.folder.Length);
foreach (var episode in season.folder)
{
var streamquality = new StreamQualityTpl();
foreach (Match m in Regex.Matches(episode.file ?? "", $"\\[(1080|720|480|360)p?\\]([^\\[\\|,\n\r\t ]+\\.(mp4|m3u8))"))
{
string link = m.Groups[2].Value;
if (string.IsNullOrEmpty(link))
continue;
if (usehls && !link.Contains(".m3u"))
link += ":hls:manifest.m3u8";
else if (!usehls && link.Contains(".m3u"))
link = link.Replace(":hls:manifest.m3u8", "");
streamquality.Insert(onstream($"{scheme}:{link}"), $"{m.Groups[1].Value}p");
}
string e = episode.id.Split("_")[1];
etpl.Append($"{e} серия", title ?? original_title, s.ToString(), e, streamquality.Firts().link, streamquality: streamquality);
}
return etpl;
}
}
catch
{
return default;
}
#endregion
}
}
#endregion
}
}