From de6367480972c184b73f00884f1b65320266006e Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Oct 2025 14:42:31 +0300 Subject: [PATCH] Add Anihub module with search and embed functionality --- Anihub/Anihub.csproj | 15 ++ Anihub/AnihubInvoke.cs | 244 +++++++++++++++++++++++ Anihub/Controller.cs | 353 ++++++++++++++++++++++++++++++++++ Anihub/ModInit.cs | 38 ++++ Anihub/Models/AnihubModels.cs | 331 +++++++++++++++++++++++++++++++ Anihub/OnlineApi.cs | 25 +++ Anihub/manifest.json | 6 + 7 files changed, 1012 insertions(+) create mode 100644 Anihub/Anihub.csproj create mode 100644 Anihub/AnihubInvoke.cs create mode 100644 Anihub/Controller.cs create mode 100644 Anihub/ModInit.cs create mode 100644 Anihub/Models/AnihubModels.cs create mode 100644 Anihub/OnlineApi.cs create mode 100644 Anihub/manifest.json diff --git a/Anihub/Anihub.csproj b/Anihub/Anihub.csproj new file mode 100644 index 0000000..11a5084 --- /dev/null +++ b/Anihub/Anihub.csproj @@ -0,0 +1,15 @@ + + + + net9.0 + library + true + + + + + ..\..\Shared.dll + + + + diff --git a/Anihub/AnihubInvoke.cs b/Anihub/AnihubInvoke.cs new file mode 100644 index 0000000..91548ec --- /dev/null +++ b/Anihub/AnihubInvoke.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Shared; +using Shared.Models.Online.Settings; +using Shared.Models; +using System.Text.Json; +using System.Linq; +using Anihub.Models; +using Shared.Engine; +using System.Net; +using System.Web; +using Microsoft.Extensions.Caching.Memory; +using Shared.Models.Templates; +using System.Net.Http; +using System.Text; + +namespace Anihub +{ + public class AnihubInvoke + { + private OnlinesSettings _init; + private HybridCache _hybridCache; + private Action _onLog; + private ProxyManager _proxyManager; + + public AnihubInvoke(OnlinesSettings init, HybridCache hybridCache, Action onLog, ProxyManager proxyManager) + { + _init = init; + _hybridCache = hybridCache; + _onLog = onLog; + _proxyManager = proxyManager; + } + + public async ValueTask Search(string title, string original_title, string year, string t) + { + var headers = HeadersModel.Init( + ("Referer", _init.host) + ); + + string searchQuery = string.IsNullOrEmpty(title) ? original_title : title; + string searchUrl = $"{_init.apihost}/anime/?search={HttpUtility.UrlEncode(searchQuery)}"; + + string response = await Http.Get(searchUrl, headers: headers, proxy: _proxyManager.Get()); + + if (string.IsNullOrEmpty(response)) + { + _onLog?.Invoke($"Anihub search failed for: {searchQuery}"); + return null; + } + + try + { + var searchResponse = JsonSerializer.Deserialize(response); + _onLog?.Invoke($"Anihub search: {searchQuery} -> {searchResponse?.Count ?? 0} results"); + return searchResponse; + } + catch (Exception ex) + { + _onLog?.Invoke($"Anihub search parse error: {searchQuery} - {ex.Message}"); + return null; + } + } + + public async ValueTask Embed(string animeId, string? searchUri = null) + { + if (!int.TryParse(animeId, out int parsedAnimeId)) + { + _onLog?.Invoke($"Anihub embed: invalid animeId {animeId}"); + return null; + } + + var headers = HeadersModel.Init( + ("Referer", _init.host) + ); + + string sourcesUrl = $"{_init.apihost}/episode-sources/{parsedAnimeId}"; + string response = await Http.Get(sourcesUrl, headers: headers, proxy: _proxyManager.Get()); + + if (string.IsNullOrEmpty(response)) + { + _onLog?.Invoke($"Anihub sources failed for animeId: {parsedAnimeId}"); + return null; + } + + try + { + var sourcesResponse = JsonSerializer.Deserialize(response); + int moonCount = sourcesResponse?.Moonanime?.Count ?? 0; + int ashdiCount = sourcesResponse?.Ashdi?.Count ?? 0; + _onLog?.Invoke($"Anihub sources: animeId {parsedAnimeId} -> {moonCount} Moon, {ashdiCount} Ashdi"); + return sourcesResponse; + } + catch (Exception ex) + { + _onLog?.Invoke($"Anihub sources parse error: animeId {parsedAnimeId} - {ex.Message}"); + return null; + } + } + + public async Task Auth() + { + // Видаляємо цей метод, оскільки OnlinesSettings не має login/passwd полів + // або можна додати перевірку на token з _init + return null; + } + + public async Task> GetMovies(AnihubResult anime) + { + var movies = new List(); + + try + { + var movie = new MovieTpl(anime.TitleUkrainian, anime.TitleEnglish, 1); + movies.Add(movie); + } + catch (Exception ex) + { + _onLog?.Invoke($"Anihub GetMovies error: {ex.Message}"); + } + + return movies; + } + + public async Task> GetVoices(AnihubEpisodeSourcesResponse sources) + { + var voices = new List(); + + try + { + var voice_tpl = new VoiceTpl(); + + // Add Moonanime sources + foreach (var source in sources.Moonanime) + { + string voiceName = $"[Moon] {source.StudioName}"; + string voiceToken = $"moonanime:{source.Id}"; + voice_tpl.Append(voiceName, false, voiceToken); + } + + // Add Ashdi sources + foreach (var source in sources.Ashdi) + { + string voiceName = $"[Ashdi] {source.StudioName}"; + string voiceToken = $"ashdi:{source.Id}"; + voice_tpl.Append(voiceName, false, voiceToken); + } + + voices.Add(voice_tpl); + } + catch (Exception ex) + { + _onLog?.Invoke($"Anihub GetVoices error: {ex.Message}"); + } + + return voices; + } + + public async Task> GetSeasons(AnihubEpisodeSourcesResponse sources) + { + var seasons = new List(); + + try + { + var seasonNumbers = new HashSet(); + + // Collect unique season numbers from Moonanime sources + foreach (var source in sources.Moonanime) + { + seasonNumbers.Add(source.SeasonNumber); + } + + // Collect unique season numbers from Ashdi sources + foreach (var source in sources.Ashdi) + { + seasonNumbers.Add(source.SeasonNumber); + } + + // Create season templates + var season_tpl = new SeasonTpl(); + foreach (var seasonNum in seasonNumbers.OrderBy(x => x)) + { + string seasonName = $"Сезон {seasonNum}"; + string seasonToken = seasonNum.ToString(); + season_tpl.Append(seasonName, seasonToken, seasonToken); + } + + seasons.Add(season_tpl); + } + catch (Exception ex) + { + _onLog?.Invoke($"Anihub GetSeasons error: {ex.Message}"); + } + + return seasons; + } + + public async Task> GetEpisodes(AnihubEpisodeSourcesResponse sources, int seasonId) + { + var episodes = new List(); + + try + { + var episode_tpl = new EpisodeTpl(); + + // Get episodes from Moonanime sources + var moonanimeSource = sources.Moonanime.FirstOrDefault(s => s.SeasonNumber == seasonId); + if (moonanimeSource != null) + { + foreach (var ep in moonanimeSource.Episodes.OrderBy(e => e.EpisodeNumber)) + { + string episodeName = ep.Title ?? $"Епізод {ep.EpisodeNumber}"; + string seasonStr = seasonId.ToString(); + string episodeStr = ep.EpisodeNumber.ToString(); + string link = $"{_init.host}/embed/{ep.Id}"; + episode_tpl.Append(episodeName, "Anihub", seasonStr, episodeStr, link, "call"); + } + } + + // Get episodes from Ashdi sources + var ashdiSource = sources.Ashdi.FirstOrDefault(s => s.SeasonNumber == seasonId); + if (ashdiSource != null) + { + foreach (var ep in ashdiSource.EpisodesData.OrderBy(e => e.EpisodeNumber)) + { + string episodeName = ep.Title ?? $"Епізод {ep.EpisodeNumber} (Ashdi)"; + string seasonStr = seasonId.ToString(); + string episodeStr = ep.EpisodeNumber.ToString(); + string link = ep.Url ?? $"{_init.host}/embed/{ep.Id}"; + episode_tpl.Append(episodeName, "Anihub", seasonStr, episodeStr, link, "call"); + } + } + + episodes.Add(episode_tpl); + } + catch (Exception ex) + { + _onLog?.Invoke($"Anihub GetEpisodes error: {ex.Message}"); + } + + return episodes; + } + } +} diff --git a/Anihub/Controller.cs b/Anihub/Controller.cs new file mode 100644 index 0000000..bb3eef2 --- /dev/null +++ b/Anihub/Controller.cs @@ -0,0 +1,353 @@ +using Shared.Engine; +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Web; +using System.Linq; +using Shared; +using Shared.Models.Templates; +using Anihub.Models; +using System.Text.RegularExpressions; +using Shared.Models.Online.Settings; +using Shared.Models; +using System.Net; +using System.Net.Http; + +namespace Anihub +{ + [Route("anihub")] + public class AnihubController : BaseOnlineController + { + ProxyManager proxyManager; + + public AnihubController() + { + proxyManager = new ProxyManager(ModInit.Anihub); + } + + [HttpGet] + async public Task Index(long id, string imdb_id, long kinopoisk_id, string title, string original_title, string original_language, int year, string source, int serial, string account_email, string t, int s = -1, bool rjson = false) + { + var init = await loadKit(ModInit.Anihub); + if (!init.enable) + return OnError(); + + OnLog($"Anihub: {title} (serial={serial}, s={s}, t={t})"); + + var invoke = new AnihubInvoke(init, hybridCache, OnLog, proxyManager); + + var searchResponse = await invoke.Search(title, original_title, "0", year.ToString()); + if (searchResponse == null || searchResponse.IsEmpty) + return OnError(); + + if (serial == 1) + { + if (s == -1) // Відображення списку аніме як "сезонів" + { + var season_tpl = new SeasonTpl(); + for (int i = 0; i < searchResponse.content.Count; i++) + { + var anime = searchResponse.content[i]; + string seasonName = anime.TitleUkrainian ?? anime.TitleEnglish ?? anime.TitleOriginal; + string link = $"{host}/anihub?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(seasonName, link, i.ToString()); + } + + OnLog($"Anihub: generated {searchResponse.content.Count} seasons"); + return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8"); + } + else // Відображення озвучок та епізодів для вибраного аніме + { + if (s >= searchResponse.content.Count) + return OnError(); + + var selectedAnime = searchResponse.content[s]; + var episodesResponse = await invoke.Embed(selectedAnime.Id.ToString()); + if (episodesResponse == null) + return OnError(); + + var voice_tpl = new VoiceTpl(); + var episode_tpl = new EpisodeTpl(); + + // Автоматично вибираємо першу озвучку якщо не вибрана + string selectedVoice = t; + if (string.IsNullOrEmpty(selectedVoice)) + { + if (episodesResponse.Moonanime.Count > 0) + selectedVoice = $"moon_{episodesResponse.Moonanime.First().Id}"; + else if (episodesResponse.Ashdi.Count > 0) + selectedVoice = $"ashdi_{episodesResponse.Ashdi.First().Id}"; + } + + // Додаємо озвучки з Moonanime + foreach (var moonSource in episodesResponse.Moonanime) + { + string voiceName = $"[Moon] {moonSource.StudioName}"; + string voiceLink = $"{host}/anihub?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=moon_{moonSource.Id}"; + bool isActive = selectedVoice != null && selectedVoice.StartsWith("moon_") && selectedVoice.Split('_')[1] == moonSource.Id.ToString(); + voice_tpl.Append(voiceName, isActive, voiceLink); + } + + // Додаємо озвучки з Ashdi + foreach (var ashdiSource in episodesResponse.Ashdi) + { + string voiceName = $"[Ashdi] {ashdiSource.StudioName}"; + string voiceLink = $"{host}/anihub?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=ashdi_{ashdiSource.Id}"; + bool isActive = selectedVoice != null && selectedVoice.StartsWith("ashdi_") && selectedVoice.Split('_')[1] == ashdiSource.Id.ToString(); + voice_tpl.Append(voiceName, isActive, voiceLink); + } + + // Завжди додаємо епізоди для вибраної озвучки (або автоматично вибраної) + if (!string.IsNullOrEmpty(selectedVoice)) + { + if (selectedVoice.StartsWith("moon_")) + { + var parts = selectedVoice.Split('_'); + if (parts.Length >= 2 && int.TryParse(parts[1], out int sourceId)) + { + var selectedSource = episodesResponse.Moonanime.FirstOrDefault(m => m.Id == sourceId); + if (selectedSource != null) + { + foreach (var episode in selectedSource.Episodes.OrderBy(e => e.EpisodeNumber)) + { + string episodeName = !string.IsNullOrEmpty(episode.Title) ? episode.Title : $"Епізод {episode.EpisodeNumber}"; + string episodeLink = $"{host}/anihub/play?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&s={s}&e={episode.EpisodeNumber}&t={selectedVoice}_{episode.Id}"; + episode_tpl.Append(episodeName, title ?? original_title, s.ToString(), episode.EpisodeNumber.ToString("D2"), episodeLink, "call"); + } + } + } + } + else if (selectedVoice.StartsWith("ashdi_")) + { + var parts = selectedVoice.Split('_'); + if (parts.Length >= 2 && int.TryParse(parts[1], out int sourceId)) + { + var selectedSource = episodesResponse.Ashdi.FirstOrDefault(a => a.Id == sourceId); + if (selectedSource != null) + { + foreach (var episodeData in selectedSource.EpisodesData.OrderBy(e => e.EpisodeNumber)) + { + string episodeName = !string.IsNullOrEmpty(episodeData.Title) ? episodeData.Title : $"Епізод {episodeData.EpisodeNumber}"; + string episodeLink = $"{host}/anihub/play?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&s={s}&e={episodeData.EpisodeNumber}&t={selectedVoice}_{episodeData.Id}"; + episode_tpl.Append(episodeName, title ?? original_title, s.ToString(), episodeData.EpisodeNumber.ToString("D2"), episodeLink, "call"); + } + } + } + } + } + + int voiceCount = episodesResponse.Moonanime.Count + episodesResponse.Ashdi.Count; + int episodeCount = 0; + if (!string.IsNullOrEmpty(selectedVoice)) + { + if (selectedVoice.StartsWith("moon_")) + { + var parts = selectedVoice.Split('_'); + if (parts.Length >= 2 && int.TryParse(parts[1], out int sourceId)) + { + var selectedSource = episodesResponse.Moonanime.FirstOrDefault(m => m.Id == sourceId); + episodeCount = selectedSource?.Episodes?.Count ?? 0; + } + } + else if (selectedVoice.StartsWith("ashdi_")) + { + var parts = selectedVoice.Split('_'); + if (parts.Length >= 2 && int.TryParse(parts[1], out int sourceId)) + { + var selectedSource = episodesResponse.Ashdi.FirstOrDefault(a => a.Id == sourceId); + episodeCount = selectedSource?.EpisodesData?.Count ?? 0; + } + } + } + + OnLog($"Anihub: generated {voiceCount} voices, {episodeCount} episodes"); + + if (rjson) + return Content(episode_tpl.ToJson(voice_tpl), "application/json; charset=utf-8"); + + return Content(voice_tpl.ToHtml() + episode_tpl.ToHtml(), "text/html; charset=utf-8"); + } + } + else // Фільм + { + var firstAnime = searchResponse.content.FirstOrDefault(); + if (firstAnime == null) + return OnError(); + + var episodesResponse = await invoke.Embed(firstAnime.Id.ToString()); + if (episodesResponse == null) + return OnError(); + + var movie_tpl = new MovieTpl(title, original_title); + + // Обробляємо джерела Moonanime + foreach (var moonSource in episodesResponse.Moonanime) + { + foreach (var episode in moonSource.Episodes) + { + string voiceName = $"[Moon] {moonSource.StudioName}"; + string link = $"{host}/anihub/play?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&s=0&e={episode.EpisodeNumber}&t=moon_{moonSource.Id}_{episode.Id}"; + movie_tpl.Append(voiceName, link, "call"); + } + } + + // Обробляємо джерела Ashdi + foreach (var ashdiSource in episodesResponse.Ashdi) + { + foreach (var episodeData in ashdiSource.EpisodesData) + { + string voiceName = $"[Ashdi] {ashdiSource.StudioName}"; + string link = $"{host}/anihub/play?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&s=0&e={episodeData.EpisodeNumber}&t=ashdi_{ashdiSource.Id}_{episodeData.Id}"; + movie_tpl.Append(voiceName, link, "call"); + } + } + + int totalOptions = 0; + foreach (var moonSource in episodesResponse.Moonanime) + totalOptions += moonSource.Episodes.Count; + foreach (var ashdiSource in episodesResponse.Ashdi) + totalOptions += ashdiSource.EpisodesData.Count; + + OnLog($"Anihub: generated {totalOptions} movie options"); + return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8"); + } + } + + [HttpGet] + [Route("play")] + async public Task Play(string imdb_id, long kinopoisk_id, string title, string original_title, int year, int s, int e, string t, bool play = false) + { + var init = await loadKit(ModInit.Anihub); + if (!init.enable) + return OnError(); + + OnLog($"Anihub play: {title} s={s} e={e} ({t})"); + + var invoke = new AnihubInvoke(init, hybridCache, OnLog, proxyManager); + + // Парсимо токен озвучки/джерела + if (string.IsNullOrEmpty(t)) + return OnError(); + + var parts = t.Split('_'); + if (parts.Length < 3) + return OnError(); + + string sourceType = parts[0]; + if (!int.TryParse(parts[1], out int sourceId) || !int.TryParse(parts[2], out int episodeId)) + return OnError(); + + // Знаходимо аніме та отримуємо джерела епізодів + var searchResponse = await invoke.Search(title, original_title, "0", year.ToString()); + if (searchResponse == null || searchResponse.IsEmpty || s >= searchResponse.content.Count) + return OnError(); + + var selectedAnime = searchResponse.content[s]; + var episodesResponse = await invoke.Embed(selectedAnime.Id.ToString()); + if (episodesResponse == null) + return OnError(); + + string iframeUrl = null; + + if (sourceType == "moon") + { + var moonSource = episodesResponse.Moonanime.FirstOrDefault(m => m.Id == sourceId); + var episode = moonSource?.Episodes.FirstOrDefault(ep => ep.Id == episodeId); + iframeUrl = episode?.IframeLink; + } + else if (sourceType == "ashdi") + { + var ashdiSource = episodesResponse.Ashdi.FirstOrDefault(a => a.Id == sourceId); + var episodeData = ashdiSource?.EpisodesData.FirstOrDefault(ep => ep.Id == episodeId.ToString()); + iframeUrl = episodeData?.VodUrl; + } + + if (string.IsNullOrEmpty(iframeUrl)) + { + OnLog($"Anihub play: iframe URL not found for {sourceType}"); + return OnError(); + } + + // Отримуємо пряме посилання на потік + string streamUrl = await ExtractStreamUrl(iframeUrl, sourceType); + + if (string.IsNullOrEmpty(streamUrl)) + { + OnLog($"Anihub play: stream extraction failed for {sourceType}"); + return OnError(); + } + + OnLog($"Anihub play: extracted {sourceType} stream"); + + // Якщо play=true, робимо Redirect, інакше повертаємо JSON + if (play) + return Redirect(streamUrl); + else + return Content(VideoTpl.ToJson("play", streamUrl, title ?? original_title), "application/json; charset=utf-8"); + } + + private async Task ExtractStreamUrl(string iframeUrl, string sourceType) + { + try + { + string requestUrl = iframeUrl; + + // Додаємо параметр player тільки для Moon + if (sourceType == "moon") + { + requestUrl = iframeUrl + (iframeUrl.Contains("?") ? "&" : "?") + $"player={host}"; + } + + // Створюємо HTTP клієнт з правильними заголовками + using var httpClient = new HttpClient(); + + if (sourceType == "moon") + { + httpClient.DefaultRequestHeaders.Add("Referer", "https://moonanime.art/"); + httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); + httpClient.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"); + httpClient.DefaultRequestHeaders.Add("Accept-Language", "uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7"); + } + else + { + httpClient.DefaultRequestHeaders.Add("Referer", host); + httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0.1 Safari/605.1.15"); + } + + // Робимо запит до плеєра + OnLog($"Anihub player: requesting {sourceType} iframe"); + var response = await httpClient.GetStringAsync(requestUrl); + + if (string.IsNullOrEmpty(response)) + return null; + + // Парсимо відповідь для отримання file URL + string streamUrl = null; + + if (sourceType == "ashdi") + { + var match = Regex.Match(response, @"file:'([^']+)'"); + if (match.Success) + streamUrl = match.Groups[1].Value; + } + else if (sourceType == "moon") + { + var match = Regex.Match(response, @"file:\s*""([^""]+)"""); + if (match.Success) + streamUrl = match.Groups[1].Value; + } + + if (!string.IsNullOrEmpty(streamUrl)) + OnLog($"Anihub player: extracted {sourceType} stream"); + + return streamUrl; + } + catch (Exception ex) + { + OnLog($"Anihub player: {sourceType} error - {ex.Message}"); + return null; + } + } + } +} diff --git a/Anihub/ModInit.cs b/Anihub/ModInit.cs new file mode 100644 index 0000000..4bf98de --- /dev/null +++ b/Anihub/ModInit.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; +using Shared; +using Shared.Engine; +using Newtonsoft.Json.Linq; +using Shared.Models.Online.Settings; +using Shared.Models.Module; + +namespace Anihub +{ + public class ModInit + { + public static OnlinesSettings Anihub; + + /// + /// модуль загружен + /// + public static void loaded(InitspaceModel initspace) + { + Anihub = new OnlinesSettings("Anihub", "https://anihub.in.ua", streamproxy: false, useproxy: false) + { + displayname = "🇺🇦 Anihub", + displayindex = 0, + apihost = "https://anihub.in.ua/api", + proxy = new Shared.Models.Base.ProxySettings() + { + useAuth = true, + username = "a", + password = "a", + list = new string[] { "socks5://IP:PORT" } + } + }; + Anihub = ModuleInvoke.Conf("Anihub", Anihub).ToObject(); + + // Виводити "уточнити пошук" + AppInit.conf.online.with_search.Add("anihub"); + } + } +} diff --git a/Anihub/Models/AnihubModels.cs b/Anihub/Models/AnihubModels.cs new file mode 100644 index 0000000..cac25ea --- /dev/null +++ b/Anihub/Models/AnihubModels.cs @@ -0,0 +1,331 @@ +using System; +using System.Text.Json.Serialization; +using System.Collections.Generic; +using System.Linq; + +namespace Anihub.Models +{ + public class AnihubSearchResponse + { + [JsonPropertyName("count")] + public int Count { get; set; } + + [JsonPropertyName("next")] + public object? Next { get; set; } + + [JsonPropertyName("previous")] + public object? Previous { get; set; } + + [JsonPropertyName("results")] + public List Results { get; set; } = new(); + + public bool IsEmpty => Results == null || Results.Count == 0; + public List content => Results ?? new List(); + } + + public class AnihubResult + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("slug")] + public string Slug { get; set; } = string.Empty; + + [JsonPropertyName("title_ukrainian")] + public string TitleUkrainian { get; set; } = string.Empty; + + [JsonPropertyName("title_english")] + public string TitleEnglish { get; set; } = string.Empty; + + [JsonPropertyName("title_original")] + public string TitleOriginal { get; set; } = string.Empty; + + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + [JsonPropertyName("poster_url")] + public string PosterUrl { get; set; } = string.Empty; + + [JsonPropertyName("banner_url")] + public string BannerUrl { get; set; } = string.Empty; + + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; + + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; + + [JsonPropertyName("year")] + public int Year { get; set; } + + [JsonPropertyName("season_number")] + public int SeasonNumber { get; set; } + + [JsonPropertyName("episodes_count")] + public int EpisodesCount { get; set; } + + [JsonPropertyName("rating")] + public double Rating { get; set; } + + [JsonPropertyName("genres")] + public List Genres { get; set; } = new(); + + [JsonPropertyName("is_recommended_by_community")] + public bool IsRecommendedByCommunity { get; set; } + + [JsonPropertyName("is_nsfw")] + public bool IsNsfw { get; set; } + + [JsonPropertyName("has_ukrainian_dub")] + public bool HasUkrainianDub { get; set; } + } + + public class AnihubGenre + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("name_ukrainian")] + public string NameUkrainian { get; set; } = string.Empty; + } + + public class AnihubEpisodeSourcesResponse + { + [JsonPropertyName("moonanime")] + public List Moonanime { get; set; } = new(); + + [JsonPropertyName("ashdi")] + public List Ashdi { get; set; } = new(); + + [JsonPropertyName("statistics")] + public AnihubStatistics Statistics { get; set; } = new(); + + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; + } + + public class MoonanimeSource + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("studio_name")] + public string StudioName { get; set; } = string.Empty; + + [JsonPropertyName("season_number")] + public int SeasonNumber { get; set; } + + [JsonPropertyName("episodes")] + public List Episodes { get; set; } = new(); + + [JsonPropertyName("episodes_count")] + public int EpisodesCount { get; set; } + + [JsonPropertyName("created_at")] + public string CreatedAt { get; set; } = string.Empty; + + [JsonPropertyName("updated_at")] + public string UpdatedAt { get; set; } = string.Empty; + } + + public class MoonanimeEpisode + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("episode_number")] + public int EpisodeNumber { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + [JsonPropertyName("title_en")] + public string TitleEn { get; set; } = string.Empty; + + [JsonPropertyName("title_jp")] + public string TitleJp { get; set; } = string.Empty; + + [JsonPropertyName("iframe_link")] + public string IframeLink { get; set; } = string.Empty; + + [JsonPropertyName("vod_link")] + public string VodLink { get; set; } = string.Empty; + + [JsonPropertyName("poster_url")] + public string PosterUrl { get; set; } = string.Empty; + + [JsonPropertyName("episode_type")] + public string EpisodeType { get; set; } = string.Empty; + + [JsonPropertyName("release_date")] + public string ReleaseDate { get; set; } = string.Empty; + + [JsonPropertyName("embed_url")] + public EmbedUrl EmbedUrl { get; set; } = new(); + + [JsonPropertyName("created_at")] + public string CreatedAt { get; set; } = string.Empty; + + [JsonPropertyName("updated_at")] + public string UpdatedAt { get; set; } = string.Empty; + } + + public class EmbedUrl + { + [JsonPropertyName("iframe_url")] + public string IframeUrl { get; set; } = string.Empty; + + [JsonPropertyName("iframe_code")] + public string IframeCode { get; set; } = string.Empty; + + [JsonPropertyName("width")] + public int Width { get; set; } + + [JsonPropertyName("height")] + public int Height { get; set; } + } + + public class AshdiSource + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("studio_name")] + public string StudioName { get; set; } = string.Empty; + + [JsonPropertyName("season_number")] + public int SeasonNumber { get; set; } + + [JsonPropertyName("episodes_data")] + public List EpisodesData { get; set; } = new(); + + [JsonPropertyName("episode_urls")] + public List EpisodeUrls { get; set; } = new(); + + [JsonPropertyName("episodes_count")] + public int EpisodesCount { get; set; } + + [JsonPropertyName("created_at")] + public string CreatedAt { get; set; } = string.Empty; + + [JsonPropertyName("updated_at")] + public string UpdatedAt { get; set; } = string.Empty; + } + + public class AshdiEpisodeData + { + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("url")] + public string Url { get; set; } = string.Empty; + + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + [JsonPropertyName("vod_url")] + public string VodUrl { get; set; } = string.Empty; + + [JsonPropertyName("episode_number")] + public int EpisodeNumber { get; set; } + } + + public class AshdiEpisodeUrl + { + [JsonPropertyName("episode_number")] + public int EpisodeNumber { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + [JsonPropertyName("url")] + public string Url { get; set; } = string.Empty; + + [JsonPropertyName("ashdi_episode_id")] + public string AshdiEpisodeId { get; set; } = string.Empty; + } + + public class AnihubStatistics + { + [JsonPropertyName("anime_title")] + public string AnimeTitle { get; set; } = string.Empty; + + [JsonPropertyName("season_number")] + public int SeasonNumber { get; set; } + + [JsonPropertyName("moonanime")] + public MoonanimeStats Moonanime { get; set; } = new(); + + [JsonPropertyName("ashdi")] + public AshdiStats Ashdi { get; set; } = new(); + + [JsonPropertyName("total_unique_studios")] + public int TotalUniqueStudios { get; set; } + } + + public class MoonanimeStats + { + [JsonPropertyName("total_records")] + public int TotalRecords { get; set; } + + [JsonPropertyName("unique_count")] + public int UniqueCount { get; set; } + + [JsonPropertyName("unique_names")] + public List UniqueNames { get; set; } = new(); + + [JsonPropertyName("details")] + public List Details { get; set; } = new(); + } + + public class MoonanimeDetail + { + [JsonPropertyName("original_name")] + public string OriginalName { get; set; } = string.Empty; + + [JsonPropertyName("normalized_name")] + public string NormalizedName { get; set; } = string.Empty; + + [JsonPropertyName("season")] + public int Season { get; set; } + + [JsonPropertyName("moon_id")] + public int MoonId { get; set; } + + [JsonPropertyName("episodes_count")] + public int EpisodesCount { get; set; } + } + + public class AshdiStats + { + [JsonPropertyName("total_records")] + public int TotalRecords { get; set; } + + [JsonPropertyName("unique_count")] + public int UniqueCount { get; set; } + + [JsonPropertyName("unique_names")] + public List UniqueNames { get; set; } = new(); + + [JsonPropertyName("details")] + public List Details { get; set; } = new(); + } + + public class AshdiDetail + { + [JsonPropertyName("original_name")] + public string OriginalName { get; set; } = string.Empty; + + [JsonPropertyName("normalized_name")] + public string NormalizedName { get; set; } = string.Empty; + + [JsonPropertyName("season")] + public int Season { get; set; } + + [JsonPropertyName("episodes_count")] + public int EpisodesCount { get; set; } + } +} diff --git a/Anihub/OnlineApi.cs b/Anihub/OnlineApi.cs new file mode 100644 index 0000000..5d78bf6 --- /dev/null +++ b/Anihub/OnlineApi.cs @@ -0,0 +1,25 @@ +using Shared.Models.Base; +using System.Collections.Generic; + +namespace Anihub +{ + public class OnlineApi + { + public static List<(string name, string url, string plugin, int index)> Events(string host, long id, string imdb_id, long kinopoisk_id, string title, string original_title, string original_language, int year, string source, int serial, string account_email) + { + var online = new List<(string name, string url, string plugin, int index)>(); + + var init = ModInit.Anihub; + if (init.enable && !init.rip) + { + string url = init.overridehost; + if (string.IsNullOrEmpty(url)) + url = $"{host}/anihub"; + + online.Add((init.displayname, url, "anihub", init.displayindex)); + } + + return online; + } + } +} diff --git a/Anihub/manifest.json b/Anihub/manifest.json new file mode 100644 index 0000000..3cc2b60 --- /dev/null +++ b/Anihub/manifest.json @@ -0,0 +1,6 @@ +{ + "enable": true, + "version": 2, + "initspace": "Anihub.ModInit", + "online": "Anihub.OnlineApi" +}