using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; using Shared.Models.Online.Filmix; using Shared.Models.Online.Settings; using System.Security.Cryptography; using System.Text; namespace Online.Controllers { public class FilmixPartner : BaseOnlineController { public FilmixPartner() : base(AppInit.conf.FilmixPartner) { } [HttpGet] [Route("lite/fxapi")] async public Task Index(long kinopoisk_id, bool checksearch, string title, string original_title, int year, int postid, int t = -1, int s = -1, bool rjson = false, bool similar = false, string source = null, string id = null) { if (postid == 0 && !string.IsNullOrEmpty(source) && !string.IsNullOrEmpty(id)) { if (source.Contains("filmix", StringComparison.OrdinalIgnoreCase) || source.Contains("filmixapp", StringComparison.OrdinalIgnoreCase)) { if (!int.TryParse(id, out postid)) int.TryParse(Regex.Match(id, "/([0-9]+)-").Groups[1].Value, out postid); } } if (await IsRequestBlocked(rch: false)) return badInitMsg; if (postid == 0) { var res = await InvokeCache($"fxapi:search:{title}:{original_title}:{similar}", 40, () => Search(title, original_title, year, similar) ); if (similar) return await ContentTpl(res.similars); if (res != null) postid = res.id; // платный поиск if (!checksearch && postid == 0 && kinopoisk_id > 0) postid = await searchKp(kinopoisk_id); if (postid == 0 && res?.similars != null) return await ContentTpl(res.similars); } if (postid == 0) return OnError(); if (checksearch) return Content("data-json="); string hashKey = $"fxapi:hashfimix:{requestInfo.IP}"; hybridCache.TryGetValue(hashKey, out string hashfimix); string videoKey = $"fxapi:{postid}:{(string.IsNullOrEmpty(hashfimix) ? requestInfo.IP : "")}"; return await InvkSemaphore(videoKey, async () => { #region video_links if (!hybridCache.TryGetValue(videoKey, out JArray root)) { string XFXTOKEN = await getXFXTOKEN(requestInfo.user_uid); if (string.IsNullOrWhiteSpace(XFXTOKEN)) return OnError(); root = await Http.Get($"{init.corsHost()}/video_links/{postid}", headers: httpHeaders(init, HeadersModel.Init("X-FX-TOKEN", XFXTOKEN))); if (root == null || root.Count == 0) return OnError(); var first = root.First.ToObject(); if (!first.ContainsKey("files") && !first.ContainsKey("seasons")) return OnError(); if (string.IsNullOrEmpty(hashfimix)) { hashfimix = Regex.Match(root.First.ToString().Replace("\\", ""), "/s/([^/]+)/").Groups[1].Value; if (!string.IsNullOrEmpty(hashfimix)) { videoKey = $"fxapi:{postid}"; hybridCache.Set(hashKey, hashfimix, DateTime.Now.AddHours(1)); } } hybridCache.Set(videoKey, root, DateTime.Now.AddHours(2)); } #endregion if (root.First.ToObject().ContainsKey("files")) { #region Фильм var mtpl = new MovieTpl(title, original_title, root.Count); foreach (var movie in root) { var streamquality = new StreamQualityTpl(); foreach (var file in movie.Value("files").OrderByDescending(i => i.Value("quality"))) { int q = file.Value("quality"); string url = file.Value("url"); if (!string.IsNullOrEmpty(hashfimix)) url = Regex.Replace(url, "/s/[^/]+/", $"/s/{hashfimix}/"); streamquality.Append(HostStreamProxy(url), $"{q}p"); } mtpl.Append(movie.Value("name"), streamquality.Firts().link, streamquality: streamquality, vast: init.vast); } return await ContentTpl(mtpl); #endregion } else { #region Сериал if (s == -1) { #region Сезоны var tpl = new SeasonTpl(root.Count); var temp_season = new HashSet(); foreach (var translation in root) { foreach (var season in translation.Value("seasons")) { int sid = season.Value("season"); string sname = $"{sid} сезон"; if (!temp_season.Contains(sid)) { temp_season.Add(sid); string link = $"{host}/lite/fxapi?rjson={rjson}&postid={postid}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&s={sid}"; tpl.Append(sname, link, sid); } } } return await ContentTpl(tpl); #endregion } else { #region Перевод int indexTranslate = 0; var vtpl = new VoiceTpl(); foreach (var translation in root) { foreach (var season in translation.Value("seasons")) { if (season.Value("season") == s) { string link = $"{host}/lite/fxapi?rjson={rjson}&postid={postid}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&s={s}&t={indexTranslate}"; bool active = t == indexTranslate; if (t == -1) t = indexTranslate; vtpl.Append(translation.Value("name"), active, link); break; } } indexTranslate++; } #endregion #region Серии var etpl = new EpisodeTpl(vtpl); foreach (var episode in root[t].Value("seasons").FirstOrDefault(i => i.Value("season") == s).Value("episodes").ToObject>().Values) { var streamquality = new StreamQualityTpl(); foreach (var file in episode.Value("files").OrderByDescending(i => i.Value("quality"))) { int q = file.Value("quality"); string url = file.Value("url"); if (!string.IsNullOrEmpty(hashfimix)) url = Regex.Replace(url, "/s/[^/]+/", $"/s/{hashfimix}/"); string l = HostStreamProxy(url); streamquality.Append(l, $"{q}p"); } int e = episode.Value("episode"); etpl.Append($"{e} серия", title ?? original_title, s.ToString(), e.ToString(), streamquality.Firts().link, streamquality: streamquality, vast: init.vast); } #endregion return await ContentTpl(etpl); } #endregion } }); } [HttpGet] [AllowAnonymous] [Route("lite/fxapi/lowlevel/{*uri}")] async public Task LowlevelApi(string uri) { var init = AppInit.conf.FilmixPartner; if (!init.enable) return OnError("disable", gbcache: false); if (!HttpContext.Request.Headers.TryGetValue("low_passw", out var low_passw) || low_passw.ToString() != init.lowlevel_api_passw) return OnError("lowlevel_api", gbcache: false); string XFXTOKEN = await getXFXTOKEN(); if (string.IsNullOrWhiteSpace(XFXTOKEN)) return OnError("XFXTOKEN", gbcache: false); string json = await Http.Get($"{init.corsHost()}/{uri}", headers: httpHeaders(init, HeadersModel.Init("X-FX-TOKEN", XFXTOKEN))); return Content(json, "application/json; charset=utf-8"); } #region search async ValueTask searchKp(long kinopoisk_id) { if (kinopoisk_id == 0) return 0; string memKey = $"fxapi:search:{kinopoisk_id}"; if (!hybridCache.TryGetValue(memKey, out int postid)) { string XFXTOKEN = await getXFXTOKEN(); if (string.IsNullOrWhiteSpace(XFXTOKEN)) return 0; var root = await Http.Get($"{init.corsHost()}/film/by-kp/{kinopoisk_id}", proxy: proxy, headers: httpHeaders(init, HeadersModel.Init("X-FX-TOKEN", XFXTOKEN))); if (root == null || !root.ContainsKey("id")) return 0; postid = root.Value("id"); if (postid > 0) hybridCache.Set(memKey, postid, DateTime.Now.AddDays(20)); else hybridCache.Set(memKey, postid, DateTime.Now.AddDays(1)); } return postid; } async Task Search(string title, string original_title, int year, bool similar) { if (string.IsNullOrWhiteSpace(title ?? original_title)) return null; string uri = $"{AppInit.conf.Filmix.corsHost()}/api/v2/search?story={HttpUtility.UrlEncode(title)}&user_dev_apk=2.0.1&user_dev_id=&user_dev_name=Xiaomi&user_dev_os=11&user_dev_token={init.token}&user_dev_vendor=Xiaomi"; var root = await Http.Get>(init.cors(uri), timeoutSeconds: 7, proxy: proxy, useDefaultHeaders: false, headers: HeadersModel.Init( ("Accept-Encoding", "gzip") )); if (root == null || root.Count == 0) { if (root == null) proxyManager?.Refresh(); return await Search2(title, original_title, year); } var ids = new List(); 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 + $"lite/fxapi?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); } } if (ids.Count == 1 &&!similar) return new SearchResult() { id = ids[0] }; return new SearchResult() { similars = stpl }; } async Task Search2(string title, string original_title, int year) { async Task> gosearch(string story) { if (string.IsNullOrEmpty(story)) return null; string uri = $"{AppInit.conf.FilmixTV.corsHost()}/api-fx/list?search={HttpUtility.UrlEncode(story)}&limit=48"; var root = await Http.Get(uri, proxy: proxy, timeoutSeconds: 5); if (root == null || !root.ContainsKey("items")) return null; return root["items"].ToObject>(); } var result = await gosearch(original_title); if (result == null) result = await gosearch(title); if (result == null) return default; var ids = new List(); 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_title) ? $"{item.title} / {item.original_title}" : (item.title ?? item.original_title); stpl.Append(name, item.year.ToString(), string.Empty, host + $"lite/filmix?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); } } if (ids.Count == 1) return new SearchResult() { id = ids[0] }; return new SearchResult() { similars = stpl }; } #endregion #region getXFXTOKEN static long userid = 1; static string serverip = null; async ValueTask getXFXTOKEN(string uid = null) { var init = AppInit.conf.FilmixPartner; if (serverip == null) { var myip = await Http.Get($"{init.host}/my_ip", headers: httpHeaders(init)); if (myip == null || string.IsNullOrWhiteSpace(myip.Value("ip"))) return null; serverip = myip.Value("ip"); } if (userid > 20_000) userid = 1; userid++; if (uid != null) { using (SHA256 sha256Hash = SHA256.Create()) { // Преобразуем строку в байты и вычисляем хэш byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(uid)); // Преобразуем первые 8 байт хэша в число long result = BitConverter.ToInt64(bytes, 0); userid = Math.Abs(result); // Возвращаем положительное значение } } string XNICK = ReverseString(DateTime.Now.ToString("HHmm")) + DateTime.Now.ToString("yyyyMMdd"); string XSAM = ReverseString(serverip.Replace(".", "")) + DateTime.Now.ToString("HHmm"); var salt = await Http.Post($"{init.host}/request-salt", $"key={init.APIKEY}", headers: httpHeaders(init, HeadersModel.Init( ("X-NICK", SHA1(XNICK)), ("X-SAM", SHA1(XSAM)) ))); if (salt == null || string.IsNullOrWhiteSpace(salt.Value("salt"))) return null; string token = SHA1(init.APISECRET + init.APIKEY + CrypTo.md5(array_sum(serverip) + salt.Value("salt"))); var xtk = await Http.Post($"{init.host}/request-token", $"user_name={init.user_name}&user_passw={init.user_passw}&key={init.APIKEY}&token={token}", proxy: proxy, headers: httpHeaders(init, HeadersModel.Init("User-Id", userid.ToString()))); if (xtk != null && !string.IsNullOrWhiteSpace(xtk.Value("token"))) return xtk.Value("token"); return null; } #endregion #region ReverseString / array_sum / SHA1 static string ReverseString(string s) { char[] charArray = s.ToCharArray(); Array.Reverse(charArray); return new string(charArray); } static int array_sum(string s) { List mass = new List(); foreach (var num in s.Split(".")) mass.Add(int.Parse(num)); return mass.Sum(); } static string SHA1(string IntText) { using (var sha1 = new System.Security.Cryptography.SHA1Managed()) { var result = sha1.ComputeHash(Encoding.UTF8.GetBytes(IntText)); return BitConverter.ToString(result).Replace("-", "").ToLower(); } } #endregion } }