mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-04-16 17:32:20 +00:00
Merge pull request #5 from lampame/animeOn-refactoring
Anime on refactoring
This commit is contained in:
commit
3a35918336
@ -148,5 +148,73 @@ namespace AnimeON
|
|||||||
|
|
||||||
return TimeSpan.FromMinutes(ctime);
|
return TimeSpan.FromMinutes(ctime);
|
||||||
}
|
}
|
||||||
|
public async Task<AnimeON.Models.AnimeONAggregatedStructure> AggregateSerialStructure(int animeId, int season)
|
||||||
|
{
|
||||||
|
string memKey = $"AnimeON:aggregated:{animeId}:{season}";
|
||||||
|
if (_hybridCache.TryGetValue(memKey, out AnimeON.Models.AnimeONAggregatedStructure cached))
|
||||||
|
return cached;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var structure = new AnimeON.Models.AnimeONAggregatedStructure
|
||||||
|
{
|
||||||
|
AnimeId = animeId,
|
||||||
|
Season = season,
|
||||||
|
Voices = new Dictionary<string, AnimeON.Models.AnimeONVoiceInfo>()
|
||||||
|
};
|
||||||
|
|
||||||
|
var fundubs = await GetFundubs(animeId);
|
||||||
|
if (fundubs == null || fundubs.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
foreach (var fundub in fundubs)
|
||||||
|
{
|
||||||
|
if (fundub?.Fundub == null || fundub.Player == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var player in fundub.Player)
|
||||||
|
{
|
||||||
|
string display = $"[{player.Name}] {fundub.Fundub.Name}";
|
||||||
|
|
||||||
|
var episodesData = await GetEpisodes(animeId, player.Id, fundub.Fundub.Id);
|
||||||
|
if (episodesData?.Episodes == null || episodesData.Episodes.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var voiceInfo = new AnimeON.Models.AnimeONVoiceInfo
|
||||||
|
{
|
||||||
|
Name = fundub.Fundub.Name,
|
||||||
|
PlayerType = player.Name?.ToLower(),
|
||||||
|
DisplayName = display,
|
||||||
|
PlayerId = player.Id,
|
||||||
|
FundubId = fundub.Fundub.Id,
|
||||||
|
Episodes = episodesData.Episodes
|
||||||
|
.OrderBy(ep => ep.EpisodeNum)
|
||||||
|
.Select(ep => new AnimeON.Models.AnimeONEpisodeInfo
|
||||||
|
{
|
||||||
|
Number = ep.EpisodeNum,
|
||||||
|
Title = ep.Name,
|
||||||
|
Hls = ep.Hls,
|
||||||
|
VideoUrl = ep.VideoUrl,
|
||||||
|
EpisodeId = ep.Id
|
||||||
|
})
|
||||||
|
.ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
structure.Voices[display] = voiceInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!structure.Voices.Any())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
_hybridCache.Set(memKey, structure, cacheTime(20, init: _init));
|
||||||
|
return structure;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_onLog?.Invoke($"AnimeON AggregateSerialStructure error: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,114 +34,148 @@ namespace AnimeON.Controllers
|
|||||||
return Forbid();
|
return Forbid();
|
||||||
|
|
||||||
var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager);
|
var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager);
|
||||||
|
OnLog($"AnimeON Index: title={title}, original_title={original_title}, serial={serial}, s={s}, t={t}, year={year}, imdb_id={imdb_id}, kp={kinopoisk_id}");
|
||||||
|
|
||||||
var seasons = await invoke.Search(imdb_id, kinopoisk_id, title, original_title, year);
|
var seasons = await invoke.Search(imdb_id, kinopoisk_id, title, original_title, year);
|
||||||
|
OnLog($"AnimeON: search results = {seasons?.Count ?? 0}");
|
||||||
if (seasons == null || seasons.Count == 0)
|
if (seasons == null || seasons.Count == 0)
|
||||||
return Content("AnimeON", "text/html; charset=utf-8");
|
return OnError("animeon", proxyManager);
|
||||||
|
|
||||||
var allOptions = new List<(SearchModel season, FundubModel fundub, Player player)>();
|
// [Refactoring] Використовується агрегована структура (AggregateSerialStructure) — попередній збір allOptions не потрібний
|
||||||
foreach (var season in seasons)
|
|
||||||
{
|
|
||||||
var fundubs = await invoke.GetFundubs(season.Id);
|
|
||||||
if (fundubs != null)
|
|
||||||
{
|
|
||||||
foreach (var fundub in fundubs)
|
|
||||||
{
|
|
||||||
foreach (var player in fundub.Player)
|
|
||||||
{
|
|
||||||
allOptions.Add((season, fundub, player));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allOptions.Count == 0)
|
// [Refactoring] Перевірка allOptions видалена — використовується перевірка структури озвучок нижче
|
||||||
return Content("AnimeON", "text/html; charset=utf-8");
|
|
||||||
|
|
||||||
if (serial == 1)
|
if (serial == 1)
|
||||||
{
|
{
|
||||||
if (s == -1) // Выбор сезона/озвучки
|
if (s == -1) // Крок 1: Вибір аніме (як сезони)
|
||||||
{
|
{
|
||||||
var season_tpl = new SeasonTpl(allOptions.Count);
|
var season_tpl = new SeasonTpl(seasons.Count);
|
||||||
for (int i = 0; i < allOptions.Count; i++)
|
for (int i = 0; i < seasons.Count; i++)
|
||||||
{
|
{
|
||||||
var item = allOptions[i];
|
var anime = seasons[i];
|
||||||
string translationName = $"[{item.player.Name}|S{item.season.Season}] {item.fundub.Fundub.Name}";
|
string seasonName = anime.Season.ToString();
|
||||||
string link = $"{host}/animeon?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={i}";
|
string link = $"{host}/animeon?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={i}";
|
||||||
season_tpl.Append(translationName, link, $"{i}");
|
season_tpl.Append(seasonName, link, anime.Season.ToString());
|
||||||
}
|
}
|
||||||
|
OnLog($"AnimeON: return seasons count={seasons.Count}");
|
||||||
return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8");
|
return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8");
|
||||||
}
|
}
|
||||||
else // Вывод эпизодов
|
else // Крок 2/3: Вибір озвучки та епізодів
|
||||||
{
|
{
|
||||||
if (s >= allOptions.Count)
|
if (s >= seasons.Count)
|
||||||
return Content("AnimeON", "text/html; charset=utf-8");
|
return OnError("animeon", proxyManager);
|
||||||
|
|
||||||
var selected = allOptions[s];
|
var selectedAnime = seasons[s];
|
||||||
var episodesData = await invoke.GetEpisodes(selected.season.Id, selected.player.Id, selected.fundub.Fundub.Id);
|
var structure = await invoke.AggregateSerialStructure(selectedAnime.Id, selectedAnime.Season);
|
||||||
if (episodesData == null || episodesData.Episodes == null)
|
if (structure == null || !structure.Voices.Any())
|
||||||
return Content("AnimeON", "text/html; charset=utf-8");
|
return OnError("animeon", proxyManager);
|
||||||
|
|
||||||
var movie_tpl = new MovieTpl(title, original_title, episodesData.Episodes.Count);
|
OnLog($"AnimeON: voices found = {structure.Voices.Count}");
|
||||||
foreach (var ep in episodesData.Episodes.OrderBy(e => e.EpisodeNum))
|
// Автовибір першої озвучки якщо t не задано
|
||||||
|
if (string.IsNullOrEmpty(t))
|
||||||
|
t = structure.Voices.Keys.First();
|
||||||
|
|
||||||
|
// Формуємо список озвучок
|
||||||
|
var voice_tpl = new VoiceTpl();
|
||||||
|
foreach (var voice in structure.Voices)
|
||||||
{
|
{
|
||||||
var streamquality = new StreamQualityTpl();
|
string voiceLink = $"{host}/animeon?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)}";
|
||||||
|
bool isActive = voice.Key == t;
|
||||||
|
voice_tpl.Append(voice.Key, isActive, voiceLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перевірка вибраної озвучки
|
||||||
|
if (!structure.Voices.ContainsKey(t))
|
||||||
|
return OnError("animeon", proxyManager);
|
||||||
|
|
||||||
|
var episode_tpl = new EpisodeTpl();
|
||||||
|
var selectedVoiceInfo = structure.Voices[t];
|
||||||
|
|
||||||
|
// Формуємо епізоди для вибраної озвучки
|
||||||
|
foreach (var ep in selectedVoiceInfo.Episodes.OrderBy(e => e.Number))
|
||||||
|
{
|
||||||
|
string episodeName = !string.IsNullOrEmpty(ep.Title) ? ep.Title : $"Епізод {ep.Number}";
|
||||||
|
string seasonStr = selectedAnime.Season.ToString();
|
||||||
|
string episodeStr = ep.Number.ToString();
|
||||||
|
|
||||||
string streamLink = !string.IsNullOrEmpty(ep.Hls) ? ep.Hls : ep.VideoUrl;
|
string streamLink = !string.IsNullOrEmpty(ep.Hls) ? ep.Hls : ep.VideoUrl;
|
||||||
|
if (string.IsNullOrEmpty(streamLink))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (selected.player.Name.ToLower() == "moon" && !string.IsNullOrEmpty(streamLink) && streamLink.Contains("moonanime.art/iframe/"))
|
if (selectedVoiceInfo.PlayerType == "moon")
|
||||||
{
|
{
|
||||||
streamLink = $"{host}/animeon/play?url={HttpUtility.UrlEncode(streamLink)}";
|
// method=call з accsArgs(callUrl)
|
||||||
streamquality.Append(streamLink, "hls");
|
string callUrl = $"{host}/animeon/play?url={HttpUtility.UrlEncode(streamLink)}";
|
||||||
movie_tpl.Append(string.IsNullOrEmpty(ep.Name) ? $"Серія {ep.EpisodeNum}" : ep.Name, accsArgs(streamLink), streamquality: streamquality);
|
episode_tpl.Append(episodeName, title ?? original_title, seasonStr, episodeStr, accsArgs(callUrl), "call");
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(streamLink))
|
else
|
||||||
{
|
{
|
||||||
streamquality.Append(HostStreamProxy(init, accsArgs(streamLink)), "hls");
|
// Пряме відтворення через HostStreamProxy(init, accsArgs(streamLink))
|
||||||
movie_tpl.Append(string.IsNullOrEmpty(ep.Name) ? $"Серія {ep.EpisodeNum}" : ep.Name, accsArgs(streamquality.Firts().link), streamquality: streamquality);
|
string playUrl = HostStreamProxy(init, accsArgs(streamLink));
|
||||||
|
episode_tpl.Append(episodeName, title ?? original_title, seasonStr, episodeStr, playUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(episodesData.AnotherPlayer) && episodesData.AnotherPlayer.Contains("ashdi.vip"))
|
// Повертаємо озвучки + епізоди разом
|
||||||
{
|
OnLog($"AnimeON: return episodes count={selectedVoiceInfo.Episodes.Count} for voice='{t}' season={selectedAnime.Season}");
|
||||||
var match = Regex.Match(episodesData.AnotherPlayer, "/serial/([0-9]+)");
|
if (rjson)
|
||||||
if (match.Success)
|
return Content(episode_tpl.ToJson(voice_tpl), "application/json; charset=utf-8");
|
||||||
{
|
|
||||||
string ashdi_kp = match.Groups[1].Value;
|
|
||||||
string ashdi_link = $"/ashdi?kinopoisk_id={ashdi_kp}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}";
|
|
||||||
movie_tpl.Append("Плеєр Ashdi", ashdi_link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8");
|
return Content(voice_tpl.ToHtml() + episode_tpl.ToHtml(), "text/html; charset=utf-8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // Фильм
|
else // Фільм
|
||||||
{
|
{
|
||||||
var tpl = new MovieTpl(title, original_title, allOptions.Count);
|
var firstAnime = seasons.FirstOrDefault();
|
||||||
foreach (var item in allOptions)
|
if (firstAnime == null)
|
||||||
{
|
return OnError("animeon", proxyManager);
|
||||||
var episodesData = await invoke.GetEpisodes(item.season.Id, item.player.Id, item.fundub.Fundub.Id);
|
|
||||||
if (episodesData == null || episodesData.Episodes == null || episodesData.Episodes.Count == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
string translationName = $"[{item.player.Name}] {item.fundub.Fundub.Name}";
|
var fundubs = await invoke.GetFundubs(firstAnime.Id);
|
||||||
var streamquality = new StreamQualityTpl();
|
OnLog($"AnimeON: movie fundubs count = {fundubs?.Count ?? 0}");
|
||||||
var firstEp = episodesData.Episodes.FirstOrDefault();
|
if (fundubs == null || fundubs.Count == 0)
|
||||||
string streamLink = !string.IsNullOrEmpty(firstEp.Hls) ? firstEp.Hls : firstEp.VideoUrl;
|
return OnError("animeon", proxyManager);
|
||||||
|
|
||||||
if (item.player.Name.ToLower() == "moon" && !string.IsNullOrEmpty(streamLink) && streamLink.Contains("moonanime.art/iframe/"))
|
var tpl = new MovieTpl(title, original_title);
|
||||||
{
|
|
||||||
streamLink = $"{host}/animeon/play?url={HttpUtility.UrlEncode(streamLink)}";
|
foreach (var fundub in fundubs)
|
||||||
streamquality.Append(streamLink, "hls");
|
{
|
||||||
tpl.Append(translationName, accsArgs(streamLink), streamquality: streamquality);
|
if (fundub?.Fundub == null || fundub.Player == null || fundub.Player.Count == 0)
|
||||||
}
|
continue;
|
||||||
else if (!string.IsNullOrEmpty(streamLink))
|
|
||||||
{
|
foreach (var player in fundub.Player)
|
||||||
streamquality.Append(HostStreamProxy(init, accsArgs(streamLink)), "hls");
|
{
|
||||||
tpl.Append(translationName, accsArgs(streamquality.Firts().link), streamquality: streamquality);
|
var episodesData = await invoke.GetEpisodes(firstAnime.Id, player.Id, fundub.Fundub.Id);
|
||||||
}
|
if (episodesData == null || episodesData.Episodes == null || episodesData.Episodes.Count == 0)
|
||||||
}
|
continue;
|
||||||
return rjson ? Content(tpl.ToJson(), "application/json; charset=utf-8") : Content(tpl.ToHtml(), "text/html; charset=utf-8");
|
|
||||||
|
var firstEp = episodesData.Episodes.FirstOrDefault();
|
||||||
|
if (firstEp == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string streamLink = !string.IsNullOrEmpty(firstEp.Hls) ? firstEp.Hls : firstEp.VideoUrl;
|
||||||
|
if (string.IsNullOrEmpty(streamLink))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string translationName = $"[{player.Name}] {fundub.Fundub.Name}";
|
||||||
|
|
||||||
|
if (player.Name?.ToLower() == "moon" && streamLink.Contains("moonanime.art/iframe/"))
|
||||||
|
{
|
||||||
|
string callUrl = $"{host}/animeon/play?url={HttpUtility.UrlEncode(streamLink)}";
|
||||||
|
tpl.Append(translationName, accsArgs(callUrl), "call");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tpl.Append(translationName, HostStreamProxy(init, accsArgs(streamLink)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Якщо не зібрали жодної опції — повертаємо помилку
|
||||||
|
if (tpl.IsEmpty())
|
||||||
|
return OnError("animeon", proxyManager);
|
||||||
|
|
||||||
|
OnLog("AnimeON: return movie options");
|
||||||
|
return rjson ? Content(tpl.ToJson(), "application/json; charset=utf-8") : Content(tpl.ToHtml(), "text/html; charset=utf-8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,18 +261,26 @@ namespace AnimeON.Controllers
|
|||||||
public async Task<ActionResult> Play(string url)
|
public async Task<ActionResult> Play(string url)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(url))
|
if (string.IsNullOrEmpty(url))
|
||||||
return OnError("url is empty");
|
{
|
||||||
|
OnLog("AnimeON Play: empty url");
|
||||||
|
return OnError("animeon", proxyManager);
|
||||||
|
}
|
||||||
|
|
||||||
var init = await loadKit(ModInit.AnimeON);
|
var init = await loadKit(ModInit.AnimeON);
|
||||||
if (!init.enable)
|
if (!init.enable)
|
||||||
return Forbid();
|
return Forbid();
|
||||||
|
|
||||||
var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager);
|
var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager);
|
||||||
|
OnLog($"AnimeON Play: url={url}");
|
||||||
string streamLink = await invoke.ParseMoonAnimePage(url);
|
string streamLink = await invoke.ParseMoonAnimePage(url);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(streamLink))
|
if (string.IsNullOrEmpty(streamLink))
|
||||||
return Content("Не вдалося отримати посилання на відео", "text/html; charset=utf-8");
|
{
|
||||||
|
OnLog("AnimeON Play: cannot extract stream from iframe");
|
||||||
|
return OnError("animeon", proxyManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnLog("AnimeON Play: redirect to proxied stream");
|
||||||
return Redirect(HostStreamProxy(init, accsArgs(streamLink)));
|
return Redirect(HostStreamProxy(init, accsArgs(streamLink)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
AnimeON/Models/AnimeONAggregatedStructure.cs
Normal file
58
AnimeON/Models/AnimeONAggregatedStructure.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace AnimeON.Models
|
||||||
|
{
|
||||||
|
/// <summary> Aggregated structure for AnimeON serial content to match Lampac standard navigation.</summary>
|
||||||
|
public class AnimeONAggregatedStructure
|
||||||
|
{
|
||||||
|
/// <summary>Anime identifier from AnimeON API.</summary>
|
||||||
|
public int AnimeId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Season number.</summary>
|
||||||
|
public int Season { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Voices mapped by display key e.g. "[Moon] AniUA".</summary>
|
||||||
|
public Dictionary<string, AnimeONVoiceInfo> Voices { get; set; } = new Dictionary<string, AnimeONVoiceInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Voice information for a specific player/studio combination within a season.</summary>
|
||||||
|
public class AnimeONVoiceInfo
|
||||||
|
{
|
||||||
|
/// <summary>Studio/voice name (e.g., AniUA).</summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Player type ("moon" or "ashdi").</summary>
|
||||||
|
public string PlayerType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Display name (e.g., "[Moon] AniUA").</summary>
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Player identifier from API.</summary>
|
||||||
|
public int PlayerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Fundub identifier from API.</summary>
|
||||||
|
public int FundubId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Flat list of episodes for the selected season.</summary>
|
||||||
|
public List<AnimeONEpisodeInfo> Episodes { get; set; } = new List<AnimeONEpisodeInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Episode information within a voice.</summary>
|
||||||
|
public class AnimeONEpisodeInfo
|
||||||
|
{
|
||||||
|
/// <summary>Episode number.</summary>
|
||||||
|
public int Number { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Episode title.</summary>
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Primary HLS link if available.</summary>
|
||||||
|
public string Hls { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Fallback video URL (iframe or direct).</summary>
|
||||||
|
public string VideoUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Episode identifier from API.</summary>
|
||||||
|
public int EpisodeId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -72,7 +72,7 @@ namespace AnimeON.Models
|
|||||||
public List<Episode> Episodes { get; set; }
|
public List<Episode> Episodes { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("anotherPlayer")]
|
[JsonPropertyName("anotherPlayer")]
|
||||||
public string AnotherPlayer { get; set; }
|
public System.Text.Json.JsonElement AnotherPlayer { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Episode
|
public class Episode
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user