using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; using Shared.Models.Online.Vibix; using Microsoft.Playwright; using Shared.PlaywrightCore; namespace Online.Controllers { public class Vibix : BaseOnlineController { 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, int voiceover = 0) { if (await IsRequestBlocked(rch: true)) return badInitMsg; 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 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={domain}&parent_domain={domain}"; var api_headers = HeadersModel.Init( ("accept", "*/*"), ("accept-language", "ru-RU,ru;q=0.9,uk-UA;q=0.8,uk;q=0.7,en-US;q=0.6,en;q=0.5"), ("sec-fetch-dest", "empty"), ("sec-fetch-mode", "cors"), ("sec-fetch-site", "same-origin"), ("referer", data.iframe_url) ); var root = await httpHydra.Get(api_url, addheaders: api_headers); if (root == null || !root.ContainsKey("data") || root["data"]?["playlist"] == null) return e.Fail("root", refresh_proxy: true); return e.Success(new EmbedModel() { playlist = root["data"]["playlist"].ToObject() }); }); if (IsRhubFallback(cache)) goto rhubFallback; if (data.type == "movie") { #region Фильм return await ContentTpl(cache, () => { var mtpl = new MovieTpl(title, original_title, 1); foreach (var movie in cache.Value.playlist) { var streams = buildStreamQuality(movie.file); mtpl.Append(movie.title, streams.Firts().link, streamquality: streams, vast: init.vast); } return mtpl; }); #endregion } else { #region Сериал return await ContentTpl(cache, () => { if (s == -1) { var tpl = new SeasonTpl(cache.Value.playlist.Length); foreach (var season in cache.Value.playlist) { string name = season.title; if (int.TryParse(Regex.Match(name, "([0-9]+)$").Groups[1].Value, out int _s) && _s > 0) { 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}"; tpl.Append($"{_s} сезон", link, _s); } } return tpl; } else { var etpl = new EpisodeTpl(); string sArhc = s.ToString(); foreach (var season in cache.Value.playlist) { if (!season.title.EndsWith($" {s}")) continue; foreach (var episode in season.folder) { string name = episode.title; string file = episode.folder?.First().file ?? episode.file; if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(file)) continue; 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); } } return etpl; } }); #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, "