lampac-ukraine/Anihub/Controller.cs
baliasnyifeliks f3653dcec4 fix(config): correct base64 string formatting in service configurations
The changes fix missing commas between base64-encoded strings in configuration arrays across multiple service modules. This ensures proper parsing of the configuration values during runtime.

Affected modules:
- Anihub
- AnimeON
- Bamboo
- CikavaIdeya
- StarLight
- UAKino
- UaTUT
- Uaflix
- Unimay
2026-01-14 13:23:38 +02:00

380 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
using System.Text;
namespace Anihub
{
[Route("anihub")]
public class AnihubController : BaseOnlineController
{
private static readonly HashSet<string> NotAllowedHosts =
new HashSet<string>(
new[]
{
"c3ZpdGFubW92aWU=",
"cG9ydGFsLXR2",
"bGFtcGEuc3RyZWFt",
}
.Select(base64 => Encoding.UTF8.GetString(Convert.FromBase64String(base64))),
StringComparer.OrdinalIgnoreCase
);
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}";
}
if (IsNotAllowedHost(requestUrl))
return null;
// Створюємо 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;
}
}
private static bool IsNotAllowedHost(string url)
{
if (string.IsNullOrEmpty(url))
return false;
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
return false;
return NotAllowedHosts.Contains(uri.Host);
}
}
}