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 user_dev_ids = new ConcurrentDictionary(); 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 onstreamfile; Func onlog; Action requesterror; bool rjson; HttpHydra httpHydra; public FilmixInvoke(FilmixSettings init, string host, string token, string route, HttpHydra httpHydra, Func onstreamfile, Func 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 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>(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(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 Search2(string title, string original_title, int clarification, int year) { async Task> 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 items = null; try { var root = await httpHydra.Get(uri, useDefaultHeaders: false, safety: !string.IsNullOrEmpty(token)); items = root["items"]?.ToObject>(); } 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(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 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(""); //var ids = new List(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 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(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 episodes = null; try { episodes = player_links.playlist[sArch].ElementAt(t).Value.ToObject>(); } catch { try { int episod_id = 0; episodes = new Dictionary(); foreach (var item in player_links.playlist[sArch].ElementAt(t).Value.ToObject>()) { 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 } }