using Microsoft.AspNetCore.Mvc; using Microsoft.Playwright; using Newtonsoft.Json.Linq; using Shared.Engine.Utilities; using Shared.Models.Online.Alloha; using Shared.Models.Online.Settings; using Shared.PlaywrightCore; namespace Online.Controllers { public class Alloha : BaseOnlineController { public Alloha() : base(AppInit.conf.Alloha) { loadKitInitialization = (j, i, c) => { if (j.ContainsKey("m4s")) i.m4s = c.m4s; if (j.ContainsKey("linkhost")) i.linkhost = c.linkhost; if (j.ContainsKey("reserve")) i.reserve = c.reserve; i.secret_token = c.secret_token; i.token = c.token; return i; }; } [HttpGet] [Route("lite/alloha")] async public Task Index(string orid, string imdb_id, long kinopoisk_id, string title, string original_title, int serial, string original_language, int year, string t, int s = -1, bool origsource = false, bool rjson = false, bool similar = false) { if (similar) return await RouteToSpiderSearch(title, rjson); if (await IsRequestBlocked(rch: !string.IsNullOrEmpty(init.secret_token))) return badInitMsg; var result = await search(orid, imdb_id, kinopoisk_id, title, serial, original_language, year); if (result.category_id == 0) return OnError("data", refresh_proxy: result.refresh_proxy); if (result.data == null) return Ok(); if (origsource) return ContentTo(JsonConvertPool.SerializeObject(result.data)); JToken data = result.data; string defaultargs = $"&orid={orid}&imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&serial={serial}&year={year}&original_language={original_language}"; if (result.category_id is 1 or 3) { #region Фильм var mtpl = new MovieTpl(title, original_title); bool directors_cut = data.Value("available_directors_cut"); foreach (var translation in data.Value("translation_iframe").ToObject>>()) { string link = $"{host}/lite/alloha/video?t={translation.Key}&token_movie={result.data.Value("token_movie")}" + defaultargs; string streamlink = accsArgs($"{link.Replace("/video", "/video.m3u8")}&play=true"); bool uhd = false; if (translation.Value.TryGetValue("uhd", out object _uhd)) uhd = _uhd.ToString().ToLower() == "true" && init.m4s; if (directors_cut && translation.Key == "66") mtpl.Append("Режиссерская версия", $"{link}&directors_cut=true", "call", $"{streamlink}&directors_cut=true", voice_name: uhd ? "2160p" : translation.Value["quality"].ToString(), quality: uhd ? "2160p" : ""); mtpl.Append(translation.Value["name"].ToString(), link, "call", streamlink, voice_name: uhd ? "2160p" : translation.Value["quality"].ToString(), quality: uhd ? "2160p" : ""); } return await ContentTpl(mtpl); #endregion } else { #region Сериал if (s == -1) { var tpl = new SeasonTpl(result.data.Value("uhd") && init.m4s ? "2160p" : null); foreach (var season in data.Value("seasons").ToObject>().Reverse()) tpl.Append($"{season.Key} сезон", $"{host}/lite/alloha?rjson={rjson}&s={season.Key}{defaultargs}", season.Key); return await ContentTpl(tpl); } else { #region Перевод var vtpl = new VoiceTpl(); var temp_translation = new HashSet(); string activTranslate = t; foreach (var episodes in data.Value("seasons").GetValue(s.ToString()).Value("episodes").ToObject>().Select(i => i.Value.translation)) { foreach (var translation in episodes) { if (temp_translation.Contains(translation.Value.translation) || translation.Value.translation.ToLower().Contains("субтитры")) continue; temp_translation.Add(translation.Value.translation); if (string.IsNullOrWhiteSpace(activTranslate)) activTranslate = translation.Key; vtpl.Append(translation.Value.translation, activTranslate == translation.Key, $"{host}/lite/alloha?rjson={rjson}&s={s}&t={translation.Key}{defaultargs}"); } } #endregion var etpl = new EpisodeTpl(vtpl); string sArhc = s.ToString(); foreach (var episode in data.Value("seasons").GetValue(sArhc).Value("episodes").ToObject>().Reverse()) { if (!string.IsNullOrWhiteSpace(activTranslate) && !episode.Value.translation.ContainsKey(activTranslate)) continue; string link = $"{host}/lite/alloha/video?t={activTranslate}&s={s}&e={episode.Key}&token_movie={result.data.Value("token_movie")}" + defaultargs; string streamlink = accsArgs($"{link.Replace("/video", "/video.m3u8")}&play=true"); etpl.Append($"{episode.Key} серия", title ?? original_title, sArhc, episode.Key, link, "call", streamlink: streamlink); } return await ContentTpl(etpl); } #endregion } } #region Video [HttpGet] [Route("lite/alloha/video")] [Route("lite/alloha/video.m3u8")] async public ValueTask Video(string token_movie, string title, string original_title, string t, int s, int e, bool play, bool directors_cut) { if (await IsRequestBlocked(rch: !string.IsNullOrEmpty(init.secret_token), rch_check: !play)) return badInitMsg; return await InvkSemaphore($"alloha:view:stream:{init.secret_token}:{token_movie}:{t}:{s}:{e}:{init.m4s}:{directors_cut}", async key => { if (!string.IsNullOrEmpty(init.secret_token)) { #region Прямые ссылки string userIp = requestInfo.IP; if (init.localip || init.streamproxy) { userIp = await mylocalip(); if (userIp == null) return OnError("userIp", gbcache: false); } if (!hybridCache.TryGetValue(key, out JToken data)) { #region url запроса string uri = $"{init.linkhost}/direct?secret_token={init.secret_token}&token_movie={token_movie}"; uri += $"&ip={userIp}&translation={t}"; if (s > 0) uri += $"&season={s}"; if (e > 0) uri += $"&episode={e}"; if (init.m4s) uri += "&av1=true"; if (directors_cut) uri += "&directors_cut"; #endregion var root = await httpHydra.Get(uri, safety: true); if (root == null) return OnError("json", refresh_proxy: true); if (!root.ContainsKey("data")) return OnError("data"); proxyManager?.Success(); data = root["data"]; hybridCache.Set(key, data, cacheTime(10)); } #region subtitle var subtitles = new SubtitleTpl(); try { foreach (var sub in data["file"]["tracks"]) subtitles.Append(sub.Value("label"), sub.Value("src")); } catch { } #endregion List streams = null; foreach (var hlsSource in data["file"]["hlsSource"]) { // first or default if (streams == null || hlsSource.Value("default")) { streams = new List(6); foreach (var q in hlsSource["quality"].ToObject>()) { string file = q.Value; if (init.reserve) file += " or " + hlsSource["reserve"][q.Key].ToString(); streams.Add(new StreamQualityDto(HostStreamProxy(file), $"{q.Key}p")); } } } if (streams == null || streams.Count == 0) return OnError("streams"); var streamquality = new StreamQualityTpl(streams); if (play) return RedirectToPlay(streamquality.Firts().link); #region segments var segments = new SegmentTpl(); var dfile = data["file"]; string skipTime = dfile.Value("skipTime"); string removeTime = dfile.Value("removeTime"); if (skipTime != null && skipTime.Contains("-")) { foreach (string skp in skipTime.Split(",")) { var t = skp.Trim().Split('-'); if (t.Length >= 2 && int.TryParse(t[0].Trim(), out int start) && int.TryParse(t[1].Trim(), out int end)) segments.skip(start, end); } } if (removeTime != null && removeTime.Contains("-")) { foreach (string skp in removeTime.Split(",")) { var t = skp.Trim().Split('-'); if (t.Length >= 2 && int.TryParse(t[0].Trim(), out int start) && int.TryParse(t[1].Trim(), out int end)) segments.ad(start, end); } } #endregion return ContentTo(VideoTpl.ToJson("play", streamquality.Firts().link, (title ?? original_title), streamquality: streamquality, vast: init.vast, subtitles: subtitles, segments: segments, hls_manifest_timeout: (int)TimeSpan.FromSeconds(20).TotalMilliseconds )); #endregion } else { #region Playwright init.streamproxy = true; // force streamproxy string memKey = $"alloha:black_magic:{proxy_data.ip}:{token_movie}:{t}:{s}:{e}"; if (!hybridCache.TryGetValue(memKey, out (string hls, List headers) cache)) { if (PlaywrightBrowser.Status == PlaywrightStatus.disabled) return OnError(); using (var browser = new PlaywrightBrowser(init.priorityBrowser)) { string targetHost = "https://alloha.tv"; string targetUrl = $"{init.linkhost}/?token_movie={token_movie}&translation={t}&token={init.token}"; if (s > 0) targetUrl += $"&season={s}&episode={e}"; var page = await browser.NewPageAsync(init.plugin, proxy: proxy_data, headers: Http.defaultFullHeaders).ConfigureAwait(false); if (page == null) return null; string q = init.m4s ? "2160" : "1080"; await page.AddInitScriptAsync($"localStorage.setItem('allplay', '{{\"captionParam\":{{\"fontSize\":\"100%\",\"colorText\":\"Белый\",\"colorBackground\":\"Черный\",\"opacityText\":\"100%\",\"opacityBackground\":\"75%\",\"styleText\":\"Без контура\",\"weightText\":\"Обычный текст\"}},\"quality\":{q},\"volume\":0.5,\"muted\":false}}');"); await page.RouteAsync("**/*", async route => { try { if (browser.IsCompleted || route.Request.Url.Contains("blank.mp4") || route.Request.Url.Contains("googleapis.com")) { PlaywrightBase.ConsoleLog(() => $"Playwright: Abort {route.Request.Url}"); await route.AbortAsync(); return; } if (route.Request.Url.StartsWith(targetHost)) { await route.FulfillAsync(new RouteFulfillOptions { Body = PlaywrightBase.IframeHtml(targetUrl) }); } else { //if (route.Request.Url.Contains("/m/")) //{ // await route.ContinueAsync(); // var response = await page.WaitForResponseAsync(route.Request.Url); // if (response != null && response.Headers.ContainsKey("location")) // { // response = await page.WaitForResponseAsync(response.Headers["location"]); // if (response != null) // { // cache.headers = HeadersModel.Init(Http.defaultFullHeaders, // ("sec-fetch-dest", "empty"), // ("sec-fetch-mode", "cors"), // ("sec-fetch-site", "cross-site") // ); // foreach (var item in response.Request.Headers) // { // if (item.Key.ToLower() is "host" or "accept-encoding" or "connection" or "range") // continue; // if (!Http.defaultFullHeaders.ContainsKey(item.Key.ToLower())) // cache.headers.Add(new HeadersModel(item.Key, item.Value.ToString())); // } // PlaywrightBase.ConsoleLog(() => ($"Playwright: SET {response.Request.Url}", cache.headers)); // browser.SetPageResult(response.Request.Url); // } // } //} if (route.Request.Url.Contains("/master.m3u8")) { await route.AbortAsync(); cache.headers = HeadersModel.Init(Http.defaultFullHeaders, ("sec-fetch-dest", "empty"), ("sec-fetch-mode", "cors"), ("sec-fetch-site", "cross-site") ); foreach (var item in route.Request.Headers) { if (item.Key.ToLower() is "host" or "accept-encoding" or "connection" or "range") continue; if (!Http.defaultFullHeaders.ContainsKey(item.Key.ToLower())) cache.headers.Add(new HeadersModel(item.Key, item.Value.ToString())); } PlaywrightBase.ConsoleLog(() => ($"Playwright: SET {route.Request.Url}", cache.headers)); browser.SetPageResult(route.Request.Url); } else { if (await PlaywrightBase.AbortOrCache(page, route, abortMedia: true, fullCacheJS: true)) return; await route.ContinueAsync(); } } } catch { } }); PlaywrightBase.GotoAsync(page, targetHost); cache.hls = await browser.WaitPageResult(); } if (string.IsNullOrEmpty(cache.hls)) return OnError(); hybridCache.Set(memKey, cache, cacheTime(20)); } var streamquality = new StreamQualityTpl(); streamquality.Append(HostStreamProxy(cache.hls, headers: cache.headers), "auto"); if (play) return RedirectToPlay(streamquality.Firts().link); return ContentTo(VideoTpl.ToJson("play", streamquality.Firts().link, title ?? original_title, streamquality: streamquality, vast: init.vast, headers: cache.headers )); #endregion } }); } #endregion #region RouteToSpiderSearch [HttpGet] [Route("lite/alloha-search")] async public Task RouteToSpiderSearch(string title, bool rjson = false) { if (string.IsNullOrWhiteSpace(title)) return OnError("title", gbcache: false); if (await IsRequestBlocked(rch: !string.IsNullOrEmpty(init.token))) return badInitMsg; var cache = await InvokeCacheResult($"alloha:search:{title}", 40, async e => { var root = await httpHydra.Get($"{init.apihost}/?token={init.token}&name={HttpUtility.UrlEncode(title)}&list", safety: true); if (root == null || !root.ContainsKey("data")) return e.Fail("data", refresh_proxy: true); return e.Success(root["data"].ToObject()); }); return await ContentTpl(cache, () => { var stpl = new SimilarTpl(cache.Value.Count); foreach (var j in cache.Value) { string uri = $"{host}/lite/alloha?orid={j.Value("token_movie")}"; stpl.Append(j.Value("name") ?? j.Value("original_name"), j.Value("year").ToString(), string.Empty, uri, PosterApi.Size(j.Value("poster"))); } return stpl; }); } #endregion #region search async ValueTask<(bool refresh_proxy, int category_id, JToken data)> search(string token_movie, string imdb_id, long kinopoisk_id, string title, int serial, string original_language, int year) { string memKey = $"alloha:view:{kinopoisk_id}:{imdb_id}"; if (0 >= kinopoisk_id && string.IsNullOrEmpty(imdb_id)) memKey = $"alloha:viewsearch:{title}:{serial}:{original_language}:{year}"; if (!string.IsNullOrEmpty(token_movie)) memKey = $"alloha:view:{token_movie}"; JObject root = null; if (!hybridCache.TryGetValue(memKey, out (int category_id, JToken data) res)) { if (memKey.Contains(":viewsearch:")) { if (string.IsNullOrWhiteSpace(title) || year == 0) return default; root = await httpHydra.Get($"{init.apihost}/?token={init.token}&name={HttpUtility.UrlEncode(title)}&list={(serial == 1 ? "serial" : "movie")}", safety: true); if (root == null) return (true, 0, null); if (root.ContainsKey("data")) { string stitle = title.ToLowerAndTrim(); foreach (var item in root["data"]) { if (item.Value("name")?.ToLowerAndTrim() == stitle) { int y = item.Value("year"); if (y > 0 && (y == year || y == (year - 1) || y == (year + 1))) { if (original_language == "ru" && item.Value("country")?.ToLowerAndTrim() != "россия") continue; res.data = item; res.category_id = item.Value("category_id"); break; } } } } } else { if (!string.IsNullOrEmpty(imdb_id)) root = await httpHydra.Get($"{init.apihost}/?token={init.token}&imdb={imdb_id}&token_movie={token_movie}", safety: true); if ((root == null || !root.ContainsKey("data")) && kinopoisk_id > 0) root = await httpHydra.Get($"{init.apihost}/?token={init.token}&kp={kinopoisk_id}&token_movie={token_movie}", safety: true); if (root == null) return (true, 0, null); if (root.ContainsKey("data")) { res.data = root.GetValue("data"); res.category_id = res.data.Value("category"); } } if (res.data != null) proxyManager?.Success(); if (res.data != null || (root.ContainsKey("error_info") && root.Value("error_info") == "not movie")) hybridCache.Set(memKey, res, cacheTime(res.category_id is 1 or 3 ? 120 : 40)); else hybridCache.Set(memKey, res, cacheTime(2)); } return (false, res.category_id, res.data); } #endregion } }