From 659fb65256dfe34b3ade598885ec52cab95d2685 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 9 Feb 2026 18:14:35 +0500 Subject: [PATCH] fix: lampac balancers (#10) * Fix Kinobase * Fix Videoseed * Fix Vibix --- Online/Controllers/Kinobase.cs | 2 +- Online/Controllers/Vibix.cs | 708 +++++++++++++++++++- Online/Controllers/Videoseed.cs | 358 +++++++++- Shared/Engine/Online/Kinobase.cs | 6 +- Shared/Models/Templates/StreamQualityTpl.cs | 17 +- 5 files changed, 1024 insertions(+), 67 deletions(-) diff --git a/Online/Controllers/Kinobase.cs b/Online/Controllers/Kinobase.cs index 3e05f54..f07a75a 100644 --- a/Online/Controllers/Kinobase.cs +++ b/Online/Controllers/Kinobase.cs @@ -139,7 +139,7 @@ namespace Online.Controllers }); PlaywrightBase.GotoAsync(page, uri); - await browser.WaitForAnySelectorAsync(page, "#playerjsfile", ".uppod-media", ".alert").ConfigureAwait(false); + await browser.WaitForAnySelectorAsync(page, "#playerjsfile", "[id^='playerjsfile']", ".uppod-media", ".alert").ConfigureAwait(false); string content = await page.ContentAsync().ConfigureAwait(false); diff --git a/Online/Controllers/Vibix.cs b/Online/Controllers/Vibix.cs index 14bfefd..a84f38a 100644 --- a/Online/Controllers/Vibix.cs +++ b/Online/Controllers/Vibix.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; using Shared.Models.Online.Vibix; +using Microsoft.Playwright; +using Shared.PlaywrightCore; namespace Online.Controllers { @@ -8,9 +10,28 @@ namespace Online.Controllers { public Vibix() : base(AppInit.conf.Vibix) { } + static readonly string[] qualityCandidates = new[] { "1080", "720", "480" }; + + StreamQualityTpl buildStreamQuality(string file) + { + var streams = new StreamQualityTpl(); + if (string.IsNullOrEmpty(file)) + return streams; + + foreach (string q in qualityCandidates) + { + var g = new Regex($"{q}p?\\](\\{{[^\\}}]+\\}})?(?https?://[^,\t\\[\\;\\{{ ]+)").Match(file).Groups; + + if (!string.IsNullOrEmpty(g["file"].Value)) + streams.Append(HostStreamProxy(g["file"].Value), $"{q}p"); + } + + return streams; + } + [HttpGet] [Route("lite/vibix")] - async public Task Index(string imdb_id, long kinopoisk_id, string title, string original_title, int s = -1, bool rjson = false) + async public Task Index(string imdb_id, long kinopoisk_id, string title, string original_title, int s = -1, bool rjson = false, int voiceover = 0) { if (await IsRequestBlocked(rch: true)) return badInitMsg; @@ -18,20 +39,142 @@ namespace Online.Controllers if (string.IsNullOrEmpty(init.token)) return OnError(); + string enc_title = HttpUtility.UrlEncode(title); + string enc_original_title = HttpUtility.UrlEncode(original_title); + + if (!hybridCache.TryGetValue($"vibix:v2:view:{kinopoisk_id}:{imdb_id}", out (Dictionary seasons, string iframe) v2Cache)) + { + v2Cache = await searchV2(imdb_id, kinopoisk_id); + if (v2Cache.seasons != null || !string.IsNullOrEmpty(v2Cache.iframe)) + hybridCache.Set($"vibix:v2:view:{kinopoisk_id}:{imdb_id}", v2Cache, cacheTime(40)); + } + + if (v2Cache.seasons != null) + { + if (PlaywrightBrowser.Status == PlaywrightStatus.disabled) + return OnError(); + + #region Сериал + if (s == -1) + { + var tpl = new SeasonTpl(v2Cache.seasons.Count); + + foreach (var season in v2Cache.seasons) + { + string link = $"{host}/lite/vibix?rjson={rjson}&kinopoisk_id={kinopoisk_id}&imdb_id={imdb_id}&title={enc_title}&original_title={enc_original_title}&s={season.Key}"; + tpl.Append($"{season.Key} сезон", link, season.Key); + } + return await ContentTpl(tpl); + } + else + { + string sArhc = s.ToString(); + var videos = v2Cache.seasons.First(i => i.Key == sArhc).Value["videos"].ToObject>(); + var etpl = new EpisodeTpl(videos.Count); + + foreach (var video in videos) + { + string iframe = addIframeArgs(video.Value.Value("iframe")); + etpl.Append($"{video.Key} серия", title ?? original_title, sArhc, video.Key, accsArgs($"{host}/lite/vibix/video/{AesTo.Encrypt(buildVideoPayload(iframe, null))}"), "call", vast: init.vast); + } + return await ContentTpl(etpl); + } + #endregion + } + var data = await search(imdb_id, kinopoisk_id); if (data == null) return OnError(); + if (data.type == "serial" && PlaywrightBrowser.Status != PlaywrightStatus.disabled && !string.IsNullOrEmpty(data.embed_code)) + { + var serials = await getSerials(imdb_id, kinopoisk_id); + if (serials?.seasons == null) + return OnError(); + + int defaultVoiceover = data.voiceovers?.FirstOrDefault()?.id ?? 0; + int activeVoiceover = voiceover > 0 ? voiceover : defaultVoiceover; + + VoiceTpl? vtpl = null; + if (data.voiceovers != null && data.voiceovers.Length > 0) + { + var voices = new VoiceTpl(data.voiceovers.Length); + foreach (var voice in data.voiceovers) + { + string link = $"{host}/lite/vibix?rjson={rjson}&kinopoisk_id={kinopoisk_id}&imdb_id={imdb_id}&title={enc_title}&original_title={enc_original_title}&s={s}&voiceover={voice.id}"; + voices.Append(voice.name, voice.id == activeVoiceover, link); + } + + vtpl = voices; + } + + if (s == -1) + { + var tpl = new SeasonTpl(serials.seasons.Length); + for (int i = 0; i < serials.seasons.Length; i++) + { + int seasonNumber = i + 1; + string link = $"{host}/lite/vibix?rjson={rjson}&kinopoisk_id={kinopoisk_id}&imdb_id={imdb_id}&title={enc_title}&original_title={enc_original_title}&s={seasonNumber}&voiceover={activeVoiceover}"; + tpl.Append($"{seasonNumber} сезон", link, seasonNumber); + } + + return await ContentTpl(tpl); + } + else + { + int seasonIndex = s - 1; + if (seasonIndex < 0 || seasonIndex >= serials.seasons.Length) + return OnError(); + + var season = serials.seasons[seasonIndex]; + var etpl = new EpisodeTpl(vtpl, season.series?.Length ?? 0); + + if (season.series != null) + { + foreach (var episode in season.series) + { + string embed = applyEmbedArgs(data.embed_code, s.ToString(), episode.id.ToString(), activeVoiceover); + etpl.Append(episode.name ?? $"{episode.id} серия", title ?? original_title, s.ToString(), episode.id.ToString(), accsArgs($"{host}/lite/vibix/video/{AesTo.Encrypt(buildVideoPayload(null, embed))}"), "call", vast: init.vast); + } + } + + return await ContentTpl(etpl); + } + } + + if (data.type == "movie" && PlaywrightBrowser.Status != PlaywrightStatus.disabled && !string.IsNullOrEmpty(data.iframe_url)) + { + var mtpl = new MovieTpl(title, original_title, 1); + string iframe = addIframeArgs(data.iframe_url); + if (data.voiceovers != null && data.voiceovers.Length > 0) + { + int defaultVoiceover = data.voiceovers.FirstOrDefault()?.id ?? 0; + int activeVoiceover = voiceover > 0 ? voiceover : defaultVoiceover; + + foreach (var voice in data.voiceovers) + { + string embed = applyEmbedArgs(data.embed_code, null, null, voice.id); + mtpl.Append(voice.name, accsArgs($"{host}/lite/vibix/video/{AesTo.Encrypt(buildVideoPayload(iframe, embed))}") + "#.m3u8", "call", voice_name: voice.name, vast: init.vast); + } + } + else + { + mtpl.Append("По-умолчанию", accsArgs($"{host}/lite/vibix/video/{AesTo.Encrypt(buildVideoPayload(iframe, data.embed_code))}") + "#.m3u8", "call", vast: init.vast); + } + return await ContentTpl(mtpl); + } rhubFallback: var cache = await InvokeCacheResult(ipkey($"vibix:iframe:{data.iframe_url}"), 20, async e => { - string api_url = data.iframe_url + + string domain = getFrontendDomain(); + string api_url = data.iframe_url .Replace("/embed/", "/api/v1/embed/") .Replace("/embed-serials/", "/api/v1/embed-serials/"); api_url += $"?iframe_url={HttpUtility.UrlEncode(data.iframe_url)}"; api_url += $"&kp={CrypTo.unic(6).ToLower()}"; - api_url += "&domain=cm.vibix.biz&parent_domain=cm.vibix.biz"; + api_url += $"&domain={domain}&parent_domain={domain}"; var api_headers = HeadersModel.Init( ("accept", "*/*"), @@ -56,21 +199,13 @@ namespace Online.Controllers if (data.type == "movie") { #region Фильм - return await ContentTpl(cache, () => + return await ContentTpl(cache, () => { var mtpl = new MovieTpl(title, original_title, 1); foreach (var movie in cache.Value.playlist) { - var streams = new StreamQualityTpl(); - - foreach (string q in new string[] { "1080", "720", "480" }) - { - var g = new Regex($"{q}p?\\](\\{{[^\\}}]+\\}})?(?https?://[^,\t\\[\\;\\{{ ]+)").Match(movie.file).Groups; - - if (!string.IsNullOrEmpty(g["file"].Value)) - streams.Append(HostStreamProxy(g["file"].Value), $"{q}p"); - } + var streams = buildStreamQuality(movie.file); mtpl.Append(movie.title, streams.Firts().link, streamquality: streams, vast: init.vast); } @@ -84,9 +219,6 @@ namespace Online.Controllers #region Сериал return await ContentTpl(cache, () => { - string enc_title = HttpUtility.UrlEncode(title); - string enc_original_title = HttpUtility.UrlEncode(original_title); - if (s == -1) { var tpl = new SeasonTpl(cache.Value.playlist.Length); @@ -121,14 +253,7 @@ namespace Online.Controllers if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(file)) continue; - var streams = new StreamQualityTpl(); - - foreach (string q in new string[] { "1080", "720", "480" }) - { - var g = new Regex($"{q}p?\\](\\{{[^\\}}]+\\}})?(?https?://[^,\t\\[\\;\\{{ ]+)").Match(file).Groups; - if (!string.IsNullOrEmpty(g["file"].Value)) - streams.Append(HostStreamProxy(g["file"].Value), $"{q}p"); - } + var streams = buildStreamQuality(file); etpl.Append(name, title ?? original_title, sArhc, Regex.Match(name, "([0-9]+)").Groups[1].Value, streams.Firts().link, streamquality: streams, vast: init.vast); } @@ -140,7 +265,440 @@ namespace Online.Controllers #endregion } } + string addIframeArgs(string iframe) + { + if (string.IsNullOrEmpty(iframe)) + return iframe; + if (iframe.Contains("domain=") || iframe.Contains("parent_domain=")) + return iframe; + + string delimiter = iframe.Contains("?") ? "&" : "?"; + string domain = getFrontendDomain(); + return $"{iframe}{delimiter}domain={domain}&parent_domain={domain}"; + } + + string buildVideoPayload(string iframe, string embed) + { + if (string.IsNullOrEmpty(embed)) + return iframe; + + return JObject.FromObject(new + { + iframe, + embed + }).ToString(); + } + + string buildEmbedHtml(string embedCode) + { + if (string.IsNullOrEmpty(embedCode)) + return null; + + embedCode = normalizeEmbedCode(embedCode); + + return $@" + + + + + + + {embedCode} + +"; + } + + string applyEmbedArgs(string embedCode, string season, string episodes, int voiceover) + { + if (string.IsNullOrEmpty(embedCode)) + return embedCode; + + embedCode = normalizeEmbedCode(embedCode); + var attrs = new List(); + + if (!string.IsNullOrEmpty(season) && !embedCode.Contains("data-season=")) + attrs.Add($"data-season=\"{season}\""); + + if (!string.IsNullOrEmpty(episodes) && !embedCode.Contains("data-episodes=")) + attrs.Add($"data-episodes=\"{episodes}\""); + + if (voiceover > 0 && !embedCode.Contains("data-voiceover=")) + attrs.Add($"data-voiceover=\"{voiceover}\""); + + if (voiceover > 0 && !embedCode.Contains("data-voiceover-only=")) + attrs.Add("data-voiceover-only=\"true\""); + + if (attrs.Count == 0) + return embedCode; + + string insert = $""; + } + + #region Video + [HttpGet] + [Route("lite/vibix/video/{*iframe}")] + async public ValueTask Video(string iframe) + { + if (PlaywrightBrowser.Status == PlaywrightStatus.disabled) + return OnError(); + + if (await IsRequestBlocked(rch: true)) + return badInitMsg; + + string decrypted = AesTo.Decrypt(iframe); + if (string.IsNullOrEmpty(decrypted)) + return OnError(); + + string embedCode = null; + string iframeUrl = decrypted; + + if (decrypted.StartsWith("{")) + { + try + { + var payload = JObject.Parse(decrypted); + iframeUrl = payload.Value("iframe"); + embedCode = payload.Value("embed"); + } + catch { } + } + + iframeUrl = addIframeArgs(iframeUrl); + if (string.IsNullOrEmpty(iframeUrl) && string.IsNullOrEmpty(embedCode)) + return OnError(); + + string cacheKey = iframeUrl ?? CrypTo.md5(embedCode ?? string.Empty); + return await InvkSemaphore($"vibix:video:{cacheKey}:{proxyManager?.CurrentProxyIp}", async key => + { + if (!hybridCache.TryGetValue(key, out (string location, StreamQualityTpl streamquality) cache)) + { + int bestQuality = 0; + var streamquality = new StreamQualityTpl(); + string referer = Regex.Match(iframeUrl ?? string.Empty, "(^https?://[^/]+)").Groups[1].Value; + if (string.IsNullOrEmpty(referer)) + referer = $"https://{getFrontendDomain()}"; + var headers = httpHeaders(init, HeadersModel.Init + ( + ("referer", referer) + )); + + TimeSpan? cacheTtl = null; + try + { + using (var browser = new PlaywrightBrowser(init.priorityBrowser)) + { + var page = await browser.NewPageAsync(init.plugin, proxy: proxy_data, headers: headers?.ToDictionary()).ConfigureAwait(false); + if (page == null) + return OnError(); + + await page.AddInitScriptAsync(@"() => { + try { + localStorage.setItem('pljsquality', '1080p'); + localStorage.setItem('ksquality', '1080p'); + } catch (e) {} + }").ConfigureAwait(false); + + await page.RouteAsync("**/api/v1/embed/**", async route => + { + try + { + if (route.Request.Method != "GET") + { + await route.ContinueAsync(); + return; + } + + await route.ContinueAsync(); + } + catch + { + await route.ContinueAsync(); + } + }); + + await page.RouteAsync("**/*", async route => + { + try + { + if (!string.IsNullOrEmpty(cache.location)) + { + await route.AbortAsync(); + return; + } + + var reqUrl = route.Request.Url; + + if (reqUrl.Contains("/hls/", StringComparison.OrdinalIgnoreCase) && + reqUrl.Contains("/seg-", StringComparison.OrdinalIgnoreCase) && + reqUrl.Contains(".ts", StringComparison.OrdinalIgnoreCase)) + { + var playlist = Regex.Replace(reqUrl, @"/seg-[^/?]+\.ts", "/index.m3u8", RegexOptions.IgnoreCase); + updateCacheTtl(ref cacheTtl, reqUrl); + updateLocation(ref cache.location, ref bestQuality, playlist, streamquality); + } + + if (reqUrl.IndexOf(".m3u8", StringComparison.OrdinalIgnoreCase) >= 0 || + (reqUrl.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 && + reqUrl.IndexOf(".ts", StringComparison.OrdinalIgnoreCase) < 0)) + { + updateCacheTtl(ref cacheTtl, reqUrl); + updateLocation(ref cache.location, ref bestQuality, reqUrl, streamquality); + } + + if (await PlaywrightBase.AbortOrCache(page, route, abortMedia: true, fullCacheJS: true)) + return; + + await route.ContinueAsync(); + } + catch { } + }); + + var options = new PageGotoOptions() + { + Timeout = 15_000, + WaitUntil = WaitUntilState.NetworkIdle + }; + + IResponse result = null; + + try + { + await page.GotoAsync("https://cm.vibix.biz", new PageGotoOptions() + { + Timeout = 10_000, + WaitUntil = WaitUntilState.DOMContentLoaded + }).ConfigureAwait(false); + } + catch { } + + if (!string.IsNullOrEmpty(embedCode)) + { + try + { + await page.SetContentAsync(buildEmbedHtml(embedCode), new PageSetContentOptions() + { + Timeout = 15_000, + WaitUntil = WaitUntilState.NetworkIdle + }).ConfigureAwait(false); + } + catch { } + } + else if (!string.IsNullOrEmpty(iframeUrl)) + { + try + { + await page.SetContentAsync(PlaywrightBase.IframeHtml(iframeUrl), new PageSetContentOptions() + { + Timeout = 15_000, + WaitUntil = WaitUntilState.NetworkIdle + }).ConfigureAwait(false); + } + catch { } + } + + if (string.IsNullOrEmpty(cache.location) && !string.IsNullOrEmpty(iframeUrl)) + result = await page.GotoAsync(iframeUrl, options).ConfigureAwait(false); + if (result != null) + { + try + { + await page.EvaluateAsync(@"() => { + const video = document.querySelector('video'); + if (video) { + video.muted = true; + video.play().catch(() => {}); + } + const btn = document.querySelector('.vjs-big-play-button, .plyr__control--overlaid, .jw-icon-playback, .jw-icon-play'); + if (btn) btn.click(); + }").ConfigureAwait(false); + } + catch { } + } + + if (string.IsNullOrEmpty(cache.location)) + await page.WaitForTimeoutAsync(3000).ConfigureAwait(false); + + if (result != null && string.IsNullOrEmpty(cache.location)) + { + string html = await page.ContentAsync().ConfigureAwait(false); + cache.location = Regex.Match(html, "