mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-04-16 17:32:20 +00:00
Add Anihub module with search and embed functionality
This commit is contained in:
parent
b150cabfb0
commit
de63674809
15
Anihub/Anihub.csproj
Normal file
15
Anihub/Anihub.csproj
Normal file
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>library</OutputType>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Shared">
|
||||
<HintPath>..\..\Shared.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
244
Anihub/AnihubInvoke.cs
Normal file
244
Anihub/AnihubInvoke.cs
Normal file
@ -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<string> _onLog;
|
||||
private ProxyManager _proxyManager;
|
||||
|
||||
public AnihubInvoke(OnlinesSettings init, HybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
|
||||
{
|
||||
_init = init;
|
||||
_hybridCache = hybridCache;
|
||||
_onLog = onLog;
|
||||
_proxyManager = proxyManager;
|
||||
}
|
||||
|
||||
public async ValueTask<AnihubSearchResponse?> 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<AnihubSearchResponse>(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<AnihubEpisodeSourcesResponse?> 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<AnihubEpisodeSourcesResponse>(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<string?> Auth()
|
||||
{
|
||||
// Видаляємо цей метод, оскільки OnlinesSettings не має login/passwd полів
|
||||
// або можна додати перевірку на token з _init
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<List<MovieTpl>> GetMovies(AnihubResult anime)
|
||||
{
|
||||
var movies = new List<MovieTpl>();
|
||||
|
||||
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<List<VoiceTpl>> GetVoices(AnihubEpisodeSourcesResponse sources)
|
||||
{
|
||||
var voices = new List<VoiceTpl>();
|
||||
|
||||
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<List<SeasonTpl>> GetSeasons(AnihubEpisodeSourcesResponse sources)
|
||||
{
|
||||
var seasons = new List<SeasonTpl>();
|
||||
|
||||
try
|
||||
{
|
||||
var seasonNumbers = new HashSet<int>();
|
||||
|
||||
// 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<List<EpisodeTpl>> GetEpisodes(AnihubEpisodeSourcesResponse sources, int seasonId)
|
||||
{
|
||||
var episodes = new List<EpisodeTpl>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
353
Anihub/Controller.cs
Normal file
353
Anihub/Controller.cs
Normal file
@ -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<ActionResult> 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<ActionResult> 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<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Anihub/ModInit.cs
Normal file
38
Anihub/ModInit.cs
Normal file
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// модуль загружен
|
||||
/// </summary>
|
||||
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<OnlinesSettings>();
|
||||
|
||||
// Виводити "уточнити пошук"
|
||||
AppInit.conf.online.with_search.Add("anihub");
|
||||
}
|
||||
}
|
||||
}
|
||||
331
Anihub/Models/AnihubModels.cs
Normal file
331
Anihub/Models/AnihubModels.cs
Normal file
@ -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<AnihubResult> Results { get; set; } = new();
|
||||
|
||||
public bool IsEmpty => Results == null || Results.Count == 0;
|
||||
public List<AnihubResult> content => Results ?? new List<AnihubResult>();
|
||||
}
|
||||
|
||||
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<AnihubGenre> 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<MoonanimeSource> Moonanime { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("ashdi")]
|
||||
public List<AshdiSource> 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<MoonanimeEpisode> 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<AshdiEpisodeData> EpisodesData { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("episode_urls")]
|
||||
public List<AshdiEpisodeUrl> 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<string> UniqueNames { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("details")]
|
||||
public List<MoonanimeDetail> 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<string> UniqueNames { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("details")]
|
||||
public List<AshdiDetail> 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; }
|
||||
}
|
||||
}
|
||||
25
Anihub/OnlineApi.cs
Normal file
25
Anihub/OnlineApi.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Anihub/manifest.json
Normal file
6
Anihub/manifest.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"enable": true,
|
||||
"version": 2,
|
||||
"initspace": "Anihub.ModInit",
|
||||
"online": "Anihub.OnlineApi"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user