From f2b70fa95e48cd3bd6b0f14663c169a0d4acddce Mon Sep 17 00:00:00 2001 From: baliasnyifeliks Date: Wed, 4 Feb 2026 21:52:22 +0200 Subject: [PATCH] feat(mikai,uaflix): implement voice-specific season filtering and redirect handling Add support for restricting season lists by specific voice selection, implement proper redirect handling when selected season is unavailable for chosen voice, and add season set validation to ensure consistent navigation between voices with different season availability --- Mikai/Controller.cs | 43 ++++++++++++++++++++++----- Mikai/ModInit.cs | 2 +- Uaflix/Controller.cs | 71 +++++++++++++++++++++++++++++++++++++++----- Uaflix/ModInit.cs | 2 +- 4 files changed, 102 insertions(+), 16 deletions(-) diff --git a/Mikai/Controller.cs b/Mikai/Controller.cs index c8cb05a..5ec5ddf 100644 --- a/Mikai/Controller.cs +++ b/Mikai/Controller.cs @@ -57,11 +57,14 @@ namespace Mikai.Controllers if (isSerial) { - var seasonNumbers = voices.Values - .SelectMany(v => v.Seasons.Keys) - .Distinct() - .OrderBy(n => n) - .ToList(); + bool restrictByVoice = !string.IsNullOrEmpty(t) && voices.TryGetValue(t, out var voiceForSeasons); + var seasonNumbers = restrictByVoice + ? GetSeasonSet(voiceForSeasons).OrderBy(n => n).ToList() + : voices.Values + .SelectMany(v => GetSeasonSet(v)) + .Distinct() + .OrderBy(n => n) + .ToList(); if (seasonNumbers.Count == 0) return OnError("mikai", _proxyManager); @@ -72,6 +75,8 @@ namespace Mikai.Controllers foreach (var seasonNumber in seasonNumbers) { string link = $"{host}/mikai?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={seasonNumber}"; + if (restrictByVoice) + link += $"&t={HttpUtility.UrlEncode(t)}"; seasonTpl.Append($"{seasonNumber}", link, seasonNumber.ToString()); } @@ -89,16 +94,29 @@ namespace Mikai.Controllers if (string.IsNullOrEmpty(t)) t = voicesForSeason[0].Key; + else if (!voices.ContainsKey(t)) + t = voicesForSeason[0].Key; var voiceTpl = new VoiceTpl(); + var selectedVoiceInfo = voices[t]; + var selectedSeasonSet = GetSeasonSet(selectedVoiceInfo); foreach (var voice in voicesForSeason) { - string voiceLink = $"{host}/mikai?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&t={HttpUtility.UrlEncode(voice.Key)}"; + var targetSeasonSet = GetSeasonSet(voice.Value); + bool sameSeasonSet = targetSeasonSet.SetEquals(selectedSeasonSet); + string voiceLink = $"{host}/mikai?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1"; + if (sameSeasonSet) + voiceLink += $"&s={s}&t={HttpUtility.UrlEncode(voice.Key)}"; + else + voiceLink += $"&s=-1&t={HttpUtility.UrlEncode(voice.Key)}"; voiceTpl.Append(voice.Key, voice.Key == t, voiceLink); } if (!voices.ContainsKey(t) || !voices[t].Seasons.ContainsKey(s)) - return OnError("mikai", _proxyManager); + { + string redirectUrl = $"{host}/mikai?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s=-1&t={HttpUtility.UrlEncode(t)}"; + return Redirect(redirectUrl); + } var episodeTpl = new EpisodeTpl(); foreach (var ep in voices[t].Seasons[s].OrderBy(e => e.Number)) @@ -367,6 +385,17 @@ namespace Mikai.Controllers streamLink.Contains("moonanime.art", StringComparison.OrdinalIgnoreCase); } + private static HashSet GetSeasonSet(MikaiVoiceInfo voice) + { + if (voice?.Seasons == null || voice.Seasons.Count == 0) + return new HashSet(); + + return voice.Seasons + .Where(kv => kv.Value != null && kv.Value.Any(ep => !string.IsNullOrEmpty(ep.Url))) + .Select(kv => kv.Key) + .ToHashSet(); + } + private static string StripLampacArgs(string url) { if (string.IsNullOrEmpty(url)) diff --git a/Mikai/ModInit.cs b/Mikai/ModInit.cs index eb2a898..c9ef993 100644 --- a/Mikai/ModInit.cs +++ b/Mikai/ModInit.cs @@ -24,7 +24,7 @@ namespace Mikai { public class ModInit { - public static double Version => 3.3; + public static double Version => 3.4; public static OnlinesSettings Mikai; public static bool ApnHostProvided; diff --git a/Uaflix/Controller.cs b/Uaflix/Controller.cs index be1ed92..bf70816 100644 --- a/Uaflix/Controller.cs +++ b/Uaflix/Controller.cs @@ -167,11 +167,21 @@ namespace Uaflix.Controllers // s == -1: Вибір сезону if (s == -1) { - var allSeasons = structure.Voices - .SelectMany(v => v.Value.Seasons.Keys) - .Distinct() - .OrderBy(sn => sn) - .ToList(); + List allSeasons; + bool restrictByVoice = !string.IsNullOrEmpty(t) && structure.Voices.TryGetValue(t, out var tVoice) && IsAshdiVoice(tVoice); + if (restrictByVoice) + { + allSeasons = GetSeasonSet(tVoice).OrderBy(sn => sn).ToList(); + OnLog($"Ashdi voice selected (t='{t}'), seasons count={allSeasons.Count}"); + } + else + { + allSeasons = structure.Voices + .SelectMany(v => GetSeasonSet(v.Value)) + .Distinct() + .OrderBy(sn => sn) + .ToList(); + } OnLog($"Found {allSeasons.Count} seasons in structure: {string.Join(", ", allSeasons)}"); @@ -205,6 +215,8 @@ namespace Uaflix.Controllers foreach (var season in seasonsWithValidEpisodes) { string link = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={season}&href={HttpUtility.UrlEncode(filmUrl)}"; + if (restrictByVoice) + link += $"&t={HttpUtility.UrlEncode(t)}"; season_tpl.Append($"{season}", link, season.ToString()); OnLog($"Added season {season} to template"); } @@ -239,12 +251,31 @@ namespace Uaflix.Controllers t = voicesForSeason[0].DisplayName; OnLog($"Auto-selected first voice: {t}"); } - + else if (!structure.Voices.ContainsKey(t)) + { + t = voicesForSeason[0].DisplayName; + OnLog($"Voice '{t}' not found, fallback to first voice: {t}"); + } + // Створюємо VoiceTpl з усіма озвучками var voice_tpl = new VoiceTpl(); + var selectedVoiceInfo = structure.Voices[t]; + var selectedSeasonSet = GetSeasonSet(selectedVoiceInfo); + bool selectedIsAshdi = IsAshdiVoice(selectedVoiceInfo); + foreach (var voice in voicesForSeason) { - string voiceLink = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&t={HttpUtility.UrlEncode(voice.DisplayName)}&href={HttpUtility.UrlEncode(filmUrl)}"; + bool targetIsAshdi = IsAshdiVoice(voice.Info); + var targetSeasonSet = GetSeasonSet(voice.Info); + bool sameSeasonSet = targetSeasonSet.SetEquals(selectedSeasonSet); + bool needSeasonReset = (selectedIsAshdi || targetIsAshdi) && !sameSeasonSet; + + string voiceLink = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&href={HttpUtility.UrlEncode(filmUrl)}"; + if (needSeasonReset) + voiceLink += $"&s=-1&t={HttpUtility.UrlEncode(voice.DisplayName)}"; + else + voiceLink += $"&s={s}&t={HttpUtility.UrlEncode(voice.DisplayName)}"; + bool isActive = voice.DisplayName == t; voice_tpl.Append(voice.DisplayName, isActive, voiceLink); } @@ -261,6 +292,13 @@ namespace Uaflix.Controllers if (!structure.Voices[t].Seasons.ContainsKey(s)) { OnLog($"Season {s} not found for voice '{t}'"); + if (IsAshdiVoice(structure.Voices[t])) + { + string redirectUrl = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s=-1&t={HttpUtility.UrlEncode(t)}&href={HttpUtility.UrlEncode(filmUrl)}"; + OnLog($"Ashdi voice missing season, redirect to season selector: {redirectUrl}"); + return Redirect(redirectUrl); + } + OnLog("=== RETURN: season not found for voice OnError ==="); return OnError("uaflix", proxyManager); } @@ -369,5 +407,24 @@ namespace Uaflix.Controllers cleaned = cleaned.Replace("?&", "?").Replace("&&", "&").TrimEnd('?', '&'); return cleaned; } + + private static bool IsAshdiVoice(VoiceInfo voice) + { + if (voice == null || string.IsNullOrEmpty(voice.PlayerType)) + return false; + + return voice.PlayerType == "ashdi-serial" || voice.PlayerType == "ashdi-vod"; + } + + private static HashSet GetSeasonSet(VoiceInfo voice) + { + if (voice?.Seasons == null || voice.Seasons.Count == 0) + return new HashSet(); + + return voice.Seasons + .Where(kv => kv.Value != null && kv.Value.Any(ep => !string.IsNullOrEmpty(ep.File))) + .Select(kv => kv.Key) + .ToHashSet(); + } } } diff --git a/Uaflix/ModInit.cs b/Uaflix/ModInit.cs index 50dbf56..370547b 100644 --- a/Uaflix/ModInit.cs +++ b/Uaflix/ModInit.cs @@ -25,7 +25,7 @@ namespace Uaflix { public class ModInit { - public static double Version => 3.3; + public static double Version => 3.4; public static OnlinesSettings UaFlix; public static bool ApnHostProvided;