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

474 lines
19 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 Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Shared.Models.Base;
using Shared.Models.Online.Filmix;
using Shared.Models.Online.Settings;
using Shared.Models.Templates;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using System.Web;
namespace Shared.Engine.Online
{
public class FilmixInvoke
{
static ConcurrentDictionary<string, string> user_dev_ids = new ConcurrentDictionary<string, string>();
static readonly JsonSerializerSettings jsonSettings = new JsonSerializerSettings { Error = (se, ev) => { ev.ErrorContext.Handled = true; } };
#region FilmixInvoke
FilmixSettings init;
public bool disableSphinxSearch, reserve;
public string token;
string host, args, route;
string apihost;
Func<string, string> onstreamfile;
Func<string, string> onlog;
Action requesterror;
bool rjson;
HttpHydra httpHydra;
public FilmixInvoke(FilmixSettings init, string host, string token, string route, HttpHydra httpHydra, Func<string, string> onstreamfile, Func<string, string> onlog = null, Action requesterror = null, bool rjson = false)
{
this.init = init;
apihost = init.corsHost();
reserve = init.reserve;
this.token = token;
this.route = route;
this.host = host != null ? $"{host}/" : null;
this.onstreamfile = onstreamfile;
this.onlog = onlog;
this.requesterror = requesterror;
this.rjson = rjson;
this.httpHydra = httpHydra;
string user_dev_id = user_dev_ids.GetOrAdd(token ?? string.Empty, (k) => UnicTo.Code(16));
args = $"app_lang=ru_RU&user_dev_apk=2.2.10.0&user_dev_id={user_dev_id}&user_dev_name=Xiaomi+24069PC21G&user_dev_os=14&user_dev_token={token}&user_dev_vendor=Xiaomi";
}
#endregion
#region Search
async public Task<SearchResult> Search(string title, string original_title, int clarification, int year, bool similar)
{
if (string.IsNullOrWhiteSpace(title ?? original_title))
return null;
string uri = $"{apihost}/api/v2/search?story={HttpUtility.UrlEncode(clarification == 1 ? title : (original_title ?? title))}&{args}";
onlog?.Invoke(uri);
var root = await httpHydra.Get<List<SearchModel>>(uri, useDefaultHeaders: false, safety: !string.IsNullOrEmpty(token));
if (root == null || root.Count == 0)
return await Search2(title, original_title, clarification, year);
var ids = new List<int>(root.Count);
var stpl = new SimilarTpl(root.Count);
string enc_title = HttpUtility.UrlEncode(title);
string enc_original_title = HttpUtility.UrlEncode(original_title);
string stitle = StringConvert.SearchName(title);
string sorigtitle = StringConvert.SearchName(original_title);
foreach (var item in root)
{
if (item == null)
continue;
string name = !string.IsNullOrEmpty(item.title) && !string.IsNullOrEmpty(item.original_title) ? $"{item.title} / {item.original_title}" : (item.title ?? item.original_title);
stpl.Append(name, item.year.ToString(), string.Empty, host + $"{route}?postid={item.id}&title={enc_title}&original_title={enc_original_title}", PosterApi.Size(item.poster));
if ((!string.IsNullOrEmpty(stitle) && StringConvert.SearchName(item.title) == stitle) ||
(!string.IsNullOrEmpty(sorigtitle) && StringConvert.SearchName(item.original_title) == sorigtitle))
{
if (item.year == year)
ids.Add(item.id);
}
}
onlog?.Invoke("ids: " + ids.Count);
if (ids.Count == 1 && !similar)
return new SearchResult() { id = ids[0] };
return new SearchResult() { similars = stpl };
}
#endregion
#region Search2
async Task<SearchResult> Search2(string title, string original_title, int clarification, int year)
{
async Task<List<SearchModel>> gosearch(string story)
{
if (string.IsNullOrEmpty(story))
return null;
string uri = $"https://api.filmix.tv/api-fx/list?search={HttpUtility.UrlEncode(story)}&limit=48";
onlog?.Invoke(uri);
List<SearchModel> items = null;
try
{
var root = await httpHydra.Get<JObject>(uri, useDefaultHeaders: false, safety: !string.IsNullOrEmpty(token));
items = root["items"]?.ToObject<List<SearchModel>>();
}
catch { }
if (items == null || items.Count == 0)
return null;
return items;
}
var result = await gosearch(clarification == 1 ? original_title : title);
if (result == null)
result = await gosearch(clarification == 1 ? title : original_title);
if (result == null)
return await Search3(title, original_title, clarification, year);
var ids = new List<int>(result.Count);
var stpl = new SimilarTpl(result.Count);
string enc_title = HttpUtility.UrlEncode(title);
string enc_original_title = HttpUtility.UrlEncode(original_title);
string stitle = StringConvert.SearchName(title);
string sorigtitle = StringConvert.SearchName(original_title);
foreach (var item in result)
{
if (item == null)
continue;
string name = !string.IsNullOrEmpty(item.title) && !string.IsNullOrEmpty(item.original_name) ? $"{item.title} / {item.original_name}" : (item.title ?? item.original_name);
stpl.Append(name, item.year.ToString(), string.Empty, host + $"{route}?postid={item.id}&title={enc_title}&original_title={enc_original_title}");
if ((!string.IsNullOrEmpty(stitle) && StringConvert.SearchName(item.title) == stitle) ||
(!string.IsNullOrEmpty(sorigtitle) && StringConvert.SearchName(item.original_name) == sorigtitle))
{
if (item.year == year)
ids.Add(item.id);
}
}
onlog?.Invoke("ids: " + ids.Count);
if (ids.Count == 1)
return new SearchResult() { id = ids[0] };
return new SearchResult() { similars = stpl };
}
#endregion
#region Search3
async Task<SearchResult> Search3(string title, string original_title, int clarification, int year)
{
return null;
//if (disableSphinxSearch)
//{
// requesterror?.Invoke();
// return null;
//}
//onlog?.Invoke("Search3");
//string html = await onpost.Invoke("https://filmix.my/engine/ajax/sphinx_search.php", $"scf=fx&story={HttpUtility.UrlEncode(clarification == 1 ? title : (original_title ?? title))}&search_start=0&do=search&subaction=search&years_ot=1902&years_do={DateTime.Today.Year}&kpi_ot=1&kpi_do=10&imdb_ot=1&imdb_do=10&sort_name=&undefined=asc&sort_date=&sort_favorite=&simple=1", HeadersModel.Init(
// ("Origin", "https://filmix.my"),
// ("Referer", "https://filmix.my/search/"),
// ("X-Requested-With", "XMLHttpRequest"),
// ("Sec-Fetch-Site", "same-origin"),
// ("Sec-Fetch-Mode", "cors"),
// ("Sec-Fetch-Dest", "empty"),
// ("Cookie", "x-a-key=sinatra; FILMIXNET=2g5orcue70hmbkugbr7vi431l0; _ga_GYLWSWSZ3C=GS1.1.1703578122.1.0.1703578122.0.0.0; _ga=GA1.1.1855910641.1703578123")
//));
//if (html == null)
//{
// requesterror?.Invoke();
// return null;
//}
//var rows = html.Split("</article>");
//var ids = new List<int>(rows.Length);
//var stpl = new SimilarTpl(rows.Length);
//string enc_title = HttpUtility.UrlEncode(title);
//string enc_original_title = HttpUtility.UrlEncode(original_title);
//string stitle = title?.ToLower();
//string sorigtitle = original_title?.ToLower();
//foreach (string row in rows)
//{
// string ftitle = Regex.Match(row, "itemprop=\"name\" content=\"([^\"]+)\"").Groups[1].Value;
// string ftitle_orig = Regex.Match(row, "itemprop=\"alternativeHeadline\" content=\"([^\"]+)\"").Groups[1].Value;
// string fyear = Regex.Match(row, "itemprop=\"copyrightYear\"[^>]+>([0-9]{4})").Groups[1].Value;
// string fid = Regex.Match(row, "data-id=\"([0-9]+)\"").Groups[1].Value;
// if (int.TryParse(fid, out int id) && id > 0)
// {
// string name = !string.IsNullOrEmpty(ftitle) && !string.IsNullOrEmpty(ftitle_orig) ? $"{ftitle} / {ftitle_orig}" : (ftitle ?? ftitle_orig);
// stpl.Append(name, fyear, string.Empty, host + $"{route}?postid={id}&title={enc_title}&original_title={enc_original_title}");
// if ((!string.IsNullOrEmpty(stitle) && ftitle.ToLower() == stitle) ||
// (!string.IsNullOrEmpty(sorigtitle) && ftitle_orig.ToLower() == sorigtitle))
// {
// if (fyear == year.ToString())
// ids.Add(id);
// }
// }
//}
//onlog?.Invoke("ids: " + ids.Count);
//if (ids.Count == 1)
// return new SearchResult() { id = ids[0] };
//return new SearchResult() { similars = stpl };
}
#endregion
#region Post
async public Task<RootObject> Post(int postid)
{
string uri = $"{apihost}/api/v2/post/{postid}?{args}";
onlog?.Invoke(uri);
string json = await httpHydra.Get(uri, useDefaultHeaders: false, safety: !string.IsNullOrEmpty(token));
if (json == null)
{
requesterror?.Invoke();
return null;
}
try
{
string jsonFix = json.Contains("\"playlist\":[],")
? json.Replace("\"playlist\":[],", "\"playlist\":null,")
: json;
var root = JsonConvert.DeserializeObject<RootObject>(jsonFix, jsonSettings);
if (root?.player_links == null)
return null;
return root;
}
catch { return null; }
}
#endregion
#region Html
public ITplResult Tpl(RootObject root, bool pro, int postid, string title, string original_title, int t, int? s, VastConf vast = null)
{
var player_links = root.player_links;
if (player_links.movie == null && player_links.playlist == null)
return default;
int filmixservtime = DateTime.UtcNow.AddHours(2).Hour;
bool hidefree720 = string.IsNullOrEmpty(token) /*&& filmixservtime >= 19 && filmixservtime <= 23*/;
if (player_links.movie != null && player_links.movie.Length > 0)
{
#region Фильм
if (player_links.movie.Length == 1 && player_links.movie[0].translation.ToLower().StartsWith("заблокировано "))
return default;
var cdns = reserve ? player_links.movie
.Select(e => Regex.Match(e.link, "^(https?://[^/]+)").Groups[1].Value)
.ToHashSet() : null;
var mtpl = new MovieTpl(title, original_title, player_links.movie.Length);
foreach (var v in player_links.movie)
{
var streamquality = new StreamQualityTpl();
foreach (int q in new int[] { 2160, 1440, 1080, 720, 480 })
{
if (!pro)
{
if (hidefree720 && q > 480)
continue;
if (q > 720)
continue;
}
if (!v.link.Contains($"{q},"))
continue;
string l = Regex.Replace(v.link, "_\\[[0-9,]+\\]\\.mp4", $"_{q}.mp4");
if (init.hls && !Regex.IsMatch(l, "/(HDR10p?|HEVC)/"))
{
var m = Regex.Match(l, "^(https?://[^/]+)/s/([^/]+)/(.*)");
if (m.Success)
l = $"{m.Groups[1].Value}/hls/{m.Groups[3].Value}/index.m3u8?hash={m.Groups[2].Value}";
}
if (reserve)
{
foreach (string cdn in cdns)
{
if (!l.Contains(cdn))
{
l += " or " + Regex.Replace(l, "^https?://[^/]+", cdn);
break;
}
}
}
streamquality.Append(onstreamfile.Invoke(l), $"{q}p");
}
mtpl.Append(v.translation, streamquality.Firts().link, streamquality: streamquality, vast: vast);
}
return mtpl;
#endregion
}
else
{
#region Сериал
if (player_links.playlist == null || player_links.playlist.Count == 0)
return default;
string enc_title = HttpUtility.UrlEncode(title);
string enc_original_title = HttpUtility.UrlEncode(original_title);
if (s == null)
{
#region Сезоны
var tpl = new SeasonTpl(!string.IsNullOrEmpty(root?.quality) ? $"{root.quality.Replace("+", "")}p" : null, player_links.playlist.Count);
foreach (var season in player_links.playlist)
{
string link = host + $"{route}?rjson={rjson}&postid={postid}&title={enc_title}&original_title={enc_original_title}&s={season.Key}";
tpl.Append($"{season.Key.Replace("-1", "1")} сезон", link, season.Key);
}
return tpl;
#endregion
}
else
{
string sArch = s?.ToString();
if (sArch == null)
return default;
#region Перевод
var voices = player_links.playlist[sArch];
int indexTranslate = 0;
var vtpl = new VoiceTpl(voices.Count);
foreach (var translation in voices)
{
string link = host + $"{route}?rjson={rjson}&postid={postid}&title={enc_title}&original_title={enc_original_title}&s={s}&t={indexTranslate}";
bool active = t == indexTranslate;
indexTranslate++;
vtpl.Append(translation.Key, active, link);
}
#endregion
#region Deserialize
Dictionary<string, Movie> episodes = null;
try
{
episodes = player_links.playlist[sArch].ElementAt(t).Value.ToObject<Dictionary<string, Movie>>();
}
catch
{
try
{
int episod_id = 0;
episodes = new Dictionary<string, Movie>();
foreach (var item in player_links.playlist[sArch].ElementAt(t).Value.ToObject<List<Movie>>())
{
episod_id++;
episodes.Add(episod_id.ToString(), item);
}
}
catch { }
}
if (episodes == null || episodes.Count == 0)
return default;
#endregion
var cdns = reserve ? episodes
.Select(e => Regex.Match(e.Value.link, "^(https?://[^/]+)").Groups[1].Value)
.ToHashSet() : null;
#region Серии
var etpl = new EpisodeTpl(vtpl, episodes.Count);
foreach (var episode in episodes)
{
var streamquality = new StreamQualityTpl();
foreach (var lq in episode.Value.qualities.OrderByDescending(i => i))
{
if (!pro)
{
if (hidefree720 && lq > 480)
continue;
if (lq > 720)
continue;
}
string l = episode.Value.link.Replace("_%s.mp4", $"_{lq}.mp4");
if (init.hls && !Regex.IsMatch(l, "/(HDR10p?|HEVC)/"))
{
var m = Regex.Match(l, "^(https?://[^/]+)/s/([^/]+)/(.*)");
if (m.Success)
l = $"{m.Groups[1].Value}/hls/{m.Groups[3].Value}/index.m3u8?hash={m.Groups[2].Value}";
}
if (reserve)
{
foreach (string cdn in cdns)
{
if (!l.Contains(cdn))
{
l += " or " + Regex.Replace(l, "^https?://[^/]+", cdn);
break;
}
}
}
streamquality.Append(onstreamfile.Invoke(l), $"{lq}p");
}
int fis = s == -1 ? 1 : (s ?? 1);
etpl.Append($"{episode.Key} серия", title ?? original_title, fis.ToString(), episode.Key, streamquality.Firts().link, streamquality: streamquality, vast: vast);
}
#endregion
return etpl;
}
#endregion
}
}
#endregion
}
}