From c5d698b01aab8869a5785797dafec5cbac236c05 Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 18 Oct 2025 10:15:09 +0300 Subject: [PATCH] Add Voice logic --- Uaflix/Controller.cs | 51 +++++--- Uaflix/Models/EpisodeLinkInfo.cs | 2 +- Uaflix/Models/SerialAggregatedStructure.cs | 2 +- Uaflix/Models/VoiceInfo.cs | 2 +- Uaflix/UaflixInvoke.cs | 128 +++++++++++++++++++-- 5 files changed, 154 insertions(+), 31 deletions(-) diff --git a/Uaflix/Controller.cs b/Uaflix/Controller.cs index 7b55763..8068705 100644 --- a/Uaflix/Controller.cs +++ b/Uaflix/Controller.cs @@ -79,7 +79,10 @@ namespace Uaflix.Controllers if (play) { - var playResult = await invoke.ParseEpisode(t); + // Визначаємо URL для парсингу - або з параметра t, або з episode_url + string urlToParse = !string.IsNullOrEmpty(t) ? t : Request.Query["episode_url"]; + + var playResult = await invoke.ParseEpisode(urlToParse); if (playResult.streams != null && playResult.streams.Count > 0) { OnLog("=== RETURN: play redirect ==="); @@ -89,6 +92,24 @@ namespace Uaflix.Controllers OnLog("=== RETURN: play no streams ==="); return Content("Uaflix", "text/html; charset=utf-8"); } + + // Якщо є episode_url але немає play=true, це виклик для отримання інформації про стрім (для method: 'call') + string episodeUrl = Request.Query["episode_url"]; + if (!string.IsNullOrEmpty(episodeUrl)) + { + var playResult = await invoke.ParseEpisode(episodeUrl); + if (playResult.streams != null && playResult.streams.Count > 0) + { + // Повертаємо JSON з інформацією про стрім для методу 'play' + string streamUrl = HostStreamProxy(init, accsArgs(playResult.streams.First().link)); + string jsonResult = $"{{\"method\":\"play\",\"url\":\"{streamUrl}\",\"title\":\"{title ?? original_title}\"}}"; + OnLog($"=== RETURN: call method JSON for episode_url ==="); + return Content(jsonResult, "application/json; charset=utf-8"); + } + + OnLog("=== RETURN: call method no streams ==="); + return Content("Uaflix", "text/html; charset=utf-8"); + } string filmUrl = href; @@ -251,23 +272,19 @@ namespace Uaflix.Controllers // Для ashdi/zetvideo-serial повертаємо готове посилання з play var voice = structure.Voices[t]; - if (voice.PlayerType == "zetvideo-vod") + if (voice.PlayerType == "zetvideo-vod" || voice.PlayerType == "ashdi-vod") { - // Знайти URL епізоду зі структури AllEpisodes - var episodeInfo = structure.AllEpisodes - .FirstOrDefault(e => e.season == s && e.episode == ep.Number); - - if (episodeInfo != null) - { - string callUrl = $"{host}/uaflix?t={HttpUtility.UrlEncode(episodeInfo.url)}&play=true"; - episode_tpl.Append( - name: ep.Title, - title: title, - s: s.ToString(), - e: ep.Number.ToString(), - link: accsArgs(callUrl) - ); - } + // Для zetvideo-vod та ashdi-vod використовуємо URL епізоду для виклику + // Потрібно передати URL епізоду в інший параметр, щоб не плутати з play=true + string callUrl = $"{host}/uaflix?episode_url={HttpUtility.UrlEncode(ep.File)}&imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&s={s}&e={ep.Number}"; + episode_tpl.Append( + name: ep.Title, + title: title, + s: s.ToString(), + e: ep.Number.ToString(), + link: accsArgs(callUrl), + method: "call" + ); } else { diff --git a/Uaflix/Models/EpisodeLinkInfo.cs b/Uaflix/Models/EpisodeLinkInfo.cs index 9b4adb0..2180106 100644 --- a/Uaflix/Models/EpisodeLinkInfo.cs +++ b/Uaflix/Models/EpisodeLinkInfo.cs @@ -10,7 +10,7 @@ namespace Uaflix.Models public int episode { get; set; } // Нові поля для підтримки змішаних плеєрів - public string playerType { get; set; } // "ashdi-serial", "zetvideo-serial", "zetvideo-vod" + public string playerType { get; set; } // "ashdi-serial", "zetvideo-serial", "zetvideo-vod", "ashdi-vod" public string iframeUrl { get; set; } // URL iframe для цього епізоду } } \ No newline at end of file diff --git a/Uaflix/Models/SerialAggregatedStructure.cs b/Uaflix/Models/SerialAggregatedStructure.cs index ef2d770..d0deede 100644 --- a/Uaflix/Models/SerialAggregatedStructure.cs +++ b/Uaflix/Models/SerialAggregatedStructure.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Uaflix.Models { /// - /// Агрегована структура серіалу з озвучками з усіх джерел (ashdi, zetvideo-serial, zetvideo-vod) + /// Агрегована структура серіалу з озвучками з усіх джерел (ashdi, zetvideo-serial, zetvideo-vod, ashdi-vod) /// public class SerialAggregatedStructure { diff --git a/Uaflix/Models/VoiceInfo.cs b/Uaflix/Models/VoiceInfo.cs index 5a52735..2e6a474 100644 --- a/Uaflix/Models/VoiceInfo.cs +++ b/Uaflix/Models/VoiceInfo.cs @@ -13,7 +13,7 @@ namespace Uaflix.Models public string Name { get; set; } /// - /// Тип плеєра: "ashdi-serial", "zetvideo-serial", "zetvideo-vod" + /// Тип плеєра: "ashdi-serial", "zetvideo-serial", "zetvideo-vod", "ashdi-vod" /// public string PlayerType { get; set; } diff --git a/Uaflix/UaflixInvoke.cs b/Uaflix/UaflixInvoke.cs index 9df46ad..7c25c98 100644 --- a/Uaflix/UaflixInvoke.cs +++ b/Uaflix/UaflixInvoke.cs @@ -45,6 +45,8 @@ namespace Uaflix // Перевіряємо на підтримувані типи плеєрів if (iframeUrl.Contains("ashdi.vip/serial/")) return "ashdi-serial"; + else if (iframeUrl.Contains("ashdi.vip/vod/")) + return "ashdi-vod"; else if (iframeUrl.Contains("zetvideo.net/serial/")) return "zetvideo-serial"; else if (iframeUrl.Contains("zetvideo.net/vod/")) @@ -204,6 +206,46 @@ namespace Uaflix } } + /// + /// Парсинг одного епізоду з ashdi-vod (новий метод для обробки окремих епізодів з ashdi.vip/vod/) + /// + private async Task<(string file, string voiceName)> ParseAshdiVodEpisode(string iframeUrl) + { + var headers = new List() + { + new HeadersModel("User-Agent", "Mozilla/5.0"), + new HeadersModel("Referer", "https://uafix.net/") + }; + + try + { + string html = await Http.Get(iframeUrl, headers: headers, proxy: _proxyManager.Get()); + + // Шукаємо Playerjs конфігурацію з file параметром + var match = Regex.Match(html, @"file:\s*'?([^'""\s,}]+\.m3u8)'?"); + if (!match.Success) + { + // Якщо не знайдено, шукаємо в іншому форматі + match = Regex.Match(html, @"file['""]?\s*:\s*['""]([^'""}]+\.m3u8)['""]"); + } + + if (!match.Success) + return (null, null); + + string fileUrl = match.Groups[1].Value; + + // Визначити озвучку з URL + string voiceName = ExtractVoiceFromUrl(fileUrl); + + return (fileUrl, voiceName); + } + catch (Exception ex) + { + _onLog($"ParseAshdiVodEpisode error: {ex.Message}"); + return (null, null); + } + } + /// /// Витягнути назву озвучки з URL файлу /// @@ -363,9 +405,8 @@ namespace Uaflix { _onLog($"AggregateSerialStructure: Processing zetvideo-vod for season {season} with {seasonGroup.Value.Count} episodes"); - // Для zetvideo-vod достатньо визначити тип плеєра, озвучки будуть визначатися через call - // Створюємо умовну озвучку для цього сезону - string displayName = "[Uaflix] Uaflix"; + // Для zetvideo-vod створюємо озвучку з реальними епізодами + string displayName = "Uaflix #2"; if (!structure.Voices.ContainsKey(displayName)) { @@ -378,10 +419,61 @@ namespace Uaflix }; } - // Створюємо пустий список епізодів для цього сезону - вони будуть заповнені через call - structure.Voices[displayName].Seasons[season] = new List(); + // Створюємо епізоди для цього сезону з посиланнями на сторінки епізодів + var episodes = new List(); + foreach (var episodeInfo in seasonGroup.Value) + { + episodes.Add(new EpisodeInfo + { + Number = episodeInfo.episode, + Title = episodeInfo.title, + File = episodeInfo.url, // URL сторінки епізоду для використання в call + Id = episodeInfo.url, + Poster = null, + Subtitle = null + }); + } - _onLog($"AggregateSerialStructure: Created placeholder voice for season {season} in zetvideo-vod"); + structure.Voices[displayName].Seasons[season] = episodes; + + _onLog($"AggregateSerialStructure: Created voice with {episodes.Count} episodes for season {season} in zetvideo-vod"); + } + else if (playerType == "ashdi-vod") + { + _onLog($"AggregateSerialStructure: Processing ashdi-vod for season {season} with {seasonGroup.Value.Count} episodes"); + + // Для ashdi-vod створюємо озвучку з реальними епізодами + string displayName = "Uaflix #3"; + + if (!structure.Voices.ContainsKey(displayName)) + { + structure.Voices[displayName] = new VoiceInfo + { + Name = "Uaflix", + PlayerType = "ashdi-vod", + DisplayName = displayName, + Seasons = new Dictionary>() + }; + } + + // Створюємо епізоди для цього сезону з посиланнями на сторінки епізодів + var episodes = new List(); + foreach (var episodeInfo in seasonGroup.Value) + { + episodes.Add(new EpisodeInfo + { + Number = episodeInfo.episode, + Title = episodeInfo.title, + File = episodeInfo.url, // URL сторінки епізоду для використання в call + Id = episodeInfo.url, + Poster = null, + Subtitle = null + }); + } + + structure.Voices[displayName].Seasons[season] = episodes; + + _onLog($"AggregateSerialStructure: Created voice with {episodes.Count} episodes for season {season} in ashdi-vod"); } } @@ -748,12 +840,26 @@ namespace Uaflix result.streams = await ParseAllZetvideoSources(iframeUrl); else if (iframeUrl.Contains("ashdi.vip")) { - result.streams = await ParseAllAshdiSources(iframeUrl); - var idMatch = Regex.Match(iframeUrl, @"_(\d+)|vod/(\d+)"); - if (idMatch.Success) + // Перевіряємо, чи це ashdi-vod (окремий епізод) або ashdi-serial (багатосерійний плеєр) + if (iframeUrl.Contains("/vod/")) { - string ashdiId = idMatch.Groups[1].Success ? idMatch.Groups[1].Value : idMatch.Groups[2].Value; - result.subtitles = await GetAshdiSubtitles(ashdiId); + // Це окремий епізод на ashdi.vip/vod/, обробляємо як ashdi-vod + var (file, voiceName) = await ParseAshdiVodEpisode(iframeUrl); + if (!string.IsNullOrEmpty(file)) + { + result.streams.Add((file, "1080p")); + } + } + else + { + // Це багатосерійний плеєр, обробляємо як і раніше + result.streams = await ParseAllAshdiSources(iframeUrl); + var idMatch = Regex.Match(iframeUrl, @"_(\d+)|vod/(\d+)"); + if (idMatch.Success) + { + string ashdiId = idMatch.Groups[1].Success ? idMatch.Groups[1].Value : idMatch.Groups[2].Value; + result.subtitles = await GetAshdiSubtitles(ashdiId); + } } } }