diff --git a/UaTUT/ApnHelper.cs b/UaTUT/ApnHelper.cs deleted file mode 100644 index 394a5bc..0000000 --- a/UaTUT/ApnHelper.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Newtonsoft.Json.Linq; -using Shared.Models.Base; -using System; -using System.Web; - -namespace Shared.Engine -{ - public static class ApnHelper - { - public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}"; - - public static bool TryGetInitConf(JObject conf, out bool enabled, out string host) - { - enabled = false; - host = null; - - if (conf == null) - return false; - - if (!conf.TryGetValue("apn", out var apnToken) || apnToken?.Type != JTokenType.Boolean) - return false; - - enabled = apnToken.Value(); - host = conf.Value("apn_host"); - return true; - } - - public static void ApplyInitConf(bool enabled, string host, BaseSettings init) - { - if (init == null) - return; - - if (!enabled) - { - init.apnstream = false; - init.apn = null; - return; - } - - if (string.IsNullOrWhiteSpace(host)) - host = DefaultHost; - - if (init.apn == null) - init.apn = new ApnConf(); - - init.apn.host = host; - init.apnstream = true; - } - - public static bool IsEnabled(BaseSettings init) - { - return init?.apnstream == true && !string.IsNullOrWhiteSpace(init?.apn?.host); - } - - public static bool IsAshdiUrl(string url) - { - return !string.IsNullOrEmpty(url) && - url.IndexOf("ashdi.vip", StringComparison.OrdinalIgnoreCase) >= 0; - } - - public static string WrapUrl(BaseSettings init, string url) - { - if (!IsEnabled(init)) - return url; - - return BuildUrl(init.apn.host, url); - } - - public static string BuildUrl(string host, string url) - { - if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(url)) - return url; - - if (host.Contains("{encodeurl}")) - return host.Replace("{encodeurl}", HttpUtility.UrlEncode(url)); - - if (host.Contains("{encode_uri}")) - return host.Replace("{encode_uri}", HttpUtility.UrlEncode(url)); - - if (host.Contains("{uri}")) - return host.Replace("{uri}", url); - - return $"{host.TrimEnd('/')}/{url}"; - } - } -} diff --git a/UaTUT/Controller.cs b/UaTUT/Controller.cs deleted file mode 100644 index 303cd14..0000000 --- a/UaTUT/Controller.cs +++ /dev/null @@ -1,550 +0,0 @@ -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 UaTUT.Models; -using System.Text.RegularExpressions; -using Shared.Models.Online.Settings; -using Shared.Models; - -namespace UaTUT -{ - [Route("uatut")] - public class UaTUTController : BaseOnlineController - { - ProxyManager proxyManager; - - public UaTUTController() : base(ModInit.Settings) - { - proxyManager = new ProxyManager(ModInit.UaTUT); - } - - [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, int season = -1, bool rjson = false, bool checksearch = false) - { - await UpdateService.ConnectAsync(host); - - var init = await loadKit(ModInit.UaTUT); - if (!init.enable) - return OnError(); - Initialization(init); - - OnLog($"UaTUT: {title} (serial={serial}, s={s}, season={season}, t={t})"); - - var invoke = new UaTUTInvoke(init, hybridCache, OnLog, proxyManager); - - // Використовуємо кеш для пошуку, щоб уникнути дублювання запитів - string searchCacheKey = $"uatut:search:{imdb_id ?? original_title ?? title}"; - var searchResults = await InvokeCache>(searchCacheKey, TimeSpan.FromMinutes(10), async () => - { - return await invoke.Search(original_title ?? title, imdb_id); - }); - - if (checksearch) - { - if (AppInit.conf?.online?.checkOnlineSearch != true) - return OnError(); - - if (searchResults != null && searchResults.Any()) - return Content("data-json=", "text/plain; charset=utf-8"); - - return OnError(); - } - - if (searchResults == null || !searchResults.Any()) - { - OnLog("UaTUT: No search results found"); - return OnError(); - } - - if (serial == 1) - { - return await HandleSeries(searchResults, imdb_id, kinopoisk_id, title, original_title, year, s, season, t, rjson, invoke, preferSeries: true); - } - else - { - return await HandleMovie(searchResults, rjson, invoke, preferSeries: false); - } - } - - private async Task HandleSeries(List searchResults, string imdb_id, long kinopoisk_id, string title, string original_title, int year, int s, int season, string t, bool rjson, UaTUTInvoke invoke, bool preferSeries) - { - var init = ModInit.UaTUT; - - // Фільтруємо тільки серіали та аніме - var seriesResults = searchResults.Where(r => IsSeriesCategory(r.Category, preferSeries)).ToList(); - - if (!seriesResults.Any()) - { - OnLog("UaTUT: No series found in search results"); - return OnError(); - } - - if (s == -1) // Крок 1: Відображення списку серіалів - { - var season_tpl = new SeasonTpl(); - for (int i = 0; i < seriesResults.Count; i++) - { - var series = seriesResults[i]; - string seasonName = $"{series.Title} ({series.Year})"; - string link = $"{host}/uatut?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($"UaTUT: generated {seriesResults.Count} series options"); - return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8"); - } - else if (season == -1) // Крок 2: Відображення сезонів для вибраного серіалу - { - if (s >= seriesResults.Count) - return OnError(); - - var selectedSeries = seriesResults[s]; - - // Використовуємо кеш для уникнення повторних запитів - string cacheKey = $"uatut:player_data:{selectedSeries.Id}"; - var playerData = await InvokeCache(cacheKey, TimeSpan.FromMinutes(10), async () => - { - return await GetPlayerDataCached(selectedSeries, invoke); - }); - - if (playerData?.Voices == null || !playerData.Voices.Any()) - return OnError(); - - // Використовуємо першу озвучку для отримання списку сезонів - var firstVoice = playerData.Voices.First(); - - var season_tpl = new SeasonTpl(); - for (int i = 0; i < firstVoice.Seasons.Count; i++) - { - var seasonItem = firstVoice.Seasons[i]; - string seasonName = seasonItem.Title ?? $"Сезон {i + 1}"; - int seasonNumber = i + 1; - string link = $"{host}/uatut?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&season={seasonNumber}"; - season_tpl.Append(seasonName, link, seasonNumber.ToString()); - } - - OnLog($"UaTUT: found {firstVoice.Seasons.Count} seasons"); - return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8"); - } - else // Крок 3: Відображення озвучок та епізодів для вибраного сезону - { - if (s >= seriesResults.Count) - return OnError(); - - var selectedSeries = seriesResults[s]; - - // Використовуємо той самий кеш - string cacheKey = $"uatut:player_data:{selectedSeries.Id}"; - var playerData = await InvokeCache(cacheKey, TimeSpan.FromMinutes(10), async () => - { - return await GetPlayerDataCached(selectedSeries, invoke); - }); - - if (playerData?.Voices == null || !playerData.Voices.Any()) - return OnError(); - - int seasonIndex = season > 0 ? season - 1 : season; - - // Перевіряємо чи існує вибраний сезон - if (seasonIndex >= playerData.Voices.First().Seasons.Count || seasonIndex < 0) - return OnError(); - - var voice_tpl = new VoiceTpl(); - var episode_tpl = new EpisodeTpl(); - - // Автоматично вибираємо першу озвучку якщо не вибрана - string selectedVoice = t; - if (string.IsNullOrEmpty(selectedVoice) && playerData.Voices.Any()) - { - selectedVoice = "0"; // Перша озвучка - } - - // Додаємо всі озвучки - for (int i = 0; i < playerData.Voices.Count; i++) - { - var voice = playerData.Voices[i]; - string voiceName = voice.Name ?? $"Озвучка {i + 1}"; - int seasonNumber = seasonIndex + 1; - string voiceLink = $"{host}/uatut?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&season={seasonNumber}&t={i}"; - bool isActive = selectedVoice == i.ToString(); - voice_tpl.Append(voiceName, isActive, voiceLink); - } - - // Додаємо епізоди тільки для вибраного сезону та озвучки - if (!string.IsNullOrEmpty(selectedVoice) && int.TryParse(selectedVoice, out int voiceIndex) && voiceIndex < playerData.Voices.Count) - { - var selectedVoiceData = playerData.Voices[voiceIndex]; - - if (seasonIndex < selectedVoiceData.Seasons.Count) - { - var selectedSeason = selectedVoiceData.Seasons[seasonIndex]; - - // Сортуємо епізоди та додаємо правильну нумерацію - var sortedEpisodes = selectedSeason.Episodes.OrderBy(e => ExtractEpisodeNumber(e.Title)).ToList(); - - for (int i = 0; i < sortedEpisodes.Count; i++) - { - var episode = sortedEpisodes[i]; - string episodeName = episode.Title; - string episodeFile = episode.File; - - if (!string.IsNullOrEmpty(episodeFile)) - { - string streamUrl = BuildStreamUrl(init, episodeFile); - int seasonNumber = seasonIndex + 1; - episode_tpl.Append( - episodeName, - title ?? original_title, - seasonNumber.ToString(), - (i + 1).ToString("D2"), - streamUrl - ); - } - } - } - } - - int voiceCount = playerData.Voices.Count; - int episodeCount = 0; - if (!string.IsNullOrEmpty(selectedVoice) && int.TryParse(selectedVoice, out int vIndex) && vIndex < playerData.Voices.Count) - { - var selectedVoiceData = playerData.Voices[vIndex]; - if (season < selectedVoiceData.Seasons.Count) - { - episodeCount = selectedVoiceData.Seasons[season].Episodes.Count; - } - } - - OnLog($"UaTUT: generated {voiceCount} voices, {episodeCount} episodes"); - - episode_tpl.Append(voice_tpl); - if (rjson) - return Content(episode_tpl.ToJson(), "application/json; charset=utf-8"); - - return Content(episode_tpl.ToHtml(), "text/html; charset=utf-8"); - } - } - - // Допоміжний метод для кешованого отримання даних плеєра - private async Task GetPlayerDataCached(SearchResult selectedSeries, UaTUTInvoke invoke) - { - var pageContent = await invoke.GetMoviePageContent(selectedSeries.Id); - if (string.IsNullOrEmpty(pageContent)) - return null; - - var playerUrl = await invoke.GetPlayerUrl(pageContent); - if (string.IsNullOrEmpty(playerUrl)) - return null; - - return await invoke.GetPlayerData(playerUrl); - } - - // Допоміжний метод для витягування номера епізоду з назви - private int ExtractEpisodeNumber(string title) - { - if (string.IsNullOrEmpty(title)) - return 0; - - var match = Regex.Match(title, @"(\d+)"); - return match.Success ? int.Parse(match.Groups[1].Value) : 0; - } - - private async Task HandleMovie(List searchResults, bool rjson, UaTUTInvoke invoke, bool preferSeries) - { - var init = ModInit.UaTUT; - - // Фільтруємо тільки фільми - var movieResults = searchResults.Where(r => IsMovieCategory(r.Category, preferSeries)).ToList(); - - if (!movieResults.Any()) - { - OnLog("UaTUT: No movies found in search results"); - return OnError(); - } - - var movie_tpl = new MovieTpl(title: "UaTUT Movies", original_title: "UaTUT Movies"); - - foreach (var movie in movieResults) - { - var pageContent = await invoke.GetMoviePageContent(movie.Id); - if (string.IsNullOrEmpty(pageContent)) - continue; - - var playerUrl = await invoke.GetPlayerUrl(pageContent); - if (string.IsNullOrEmpty(playerUrl)) - continue; - - var playerData = await invoke.GetPlayerData(playerUrl); - var movieStreams = playerData?.Movies? - .Where(m => m != null && !string.IsNullOrEmpty(m.File)) - .ToList() ?? new List(); - - if (movieStreams.Count == 0 && !string.IsNullOrEmpty(playerData?.File)) - { - movieStreams.Add(new MovieVariant - { - File = playerData.File, - Title = "Основне джерело", - Quality = "auto" - }); - } - - if (movieStreams.Count == 0) - continue; - - foreach (var variant in movieStreams) - { - string label = !string.IsNullOrWhiteSpace(variant.Title) - ? variant.Title - : "Варіант"; - - movie_tpl.Append(label, BuildStreamUrl(init, variant.File)); - } - } - - if (movie_tpl.data == null || movie_tpl.data.Count == 0) - { - OnLog("UaTUT: No playable movies found"); - return OnError(); - } - - OnLog($"UaTUT: found {movieResults.Count} movies"); - return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8"); - } - - [HttpGet] - [Route("play/movie")] - async public Task PlayMovie(long imdb_id, string title, int year, string stream = null, bool play = false, bool rjson = false) - { - await UpdateService.ConnectAsync(host); - - var init = await loadKit(ModInit.UaTUT); - if (!init.enable) - return OnError(); - Initialization(init); - - OnLog($"UaTUT PlayMovie: {title} ({year}) play={play}"); - - var invoke = new UaTUTInvoke(init, hybridCache, OnLog, proxyManager); - - // Використовуємо кеш для пошуку - string searchCacheKey = $"uatut:search:{title}"; - var searchResults = await InvokeCache>(searchCacheKey, TimeSpan.FromMinutes(10), async () => - { - return await invoke.Search(title, null); - }); - - if (searchResults == null || !searchResults.Any()) - { - OnLog("UaTUT PlayMovie: No search results found"); - return OnError(); - } - - // Шукаємо фільм за ID - var movie = searchResults.FirstOrDefault(r => r.Id == imdb_id.ToString() && r.Category == "Фільм"); - if (movie == null) - { - OnLog("UaTUT PlayMovie: Movie not found"); - return OnError(); - } - - var pageContent = await invoke.GetMoviePageContent(movie.Id); - if (string.IsNullOrEmpty(pageContent)) - return OnError(); - - var playerUrl = await invoke.GetPlayerUrl(pageContent); - if (string.IsNullOrEmpty(playerUrl)) - return OnError(); - - var playerData = await invoke.GetPlayerData(playerUrl); - string selectedFile = HttpUtility.UrlDecode(stream); - if (string.IsNullOrWhiteSpace(selectedFile)) - selectedFile = playerData?.Movies?.FirstOrDefault(m => !string.IsNullOrWhiteSpace(m.File))?.File ?? playerData?.File; - - if (string.IsNullOrWhiteSpace(selectedFile)) - return OnError(); - - OnLog($"UaTUT PlayMovie: обрано потік {selectedFile}"); - - string streamUrl = BuildStreamUrl(init, selectedFile); - - // Якщо play=true, робимо Redirect, інакше повертаємо JSON - if (play) - return UpdateService.Validate(Redirect(streamUrl)); - else - return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title), "application/json; charset=utf-8")); - } - - [HttpGet] - [Route("play")] - async public Task Play(long id, string imdb_id, long kinopoisk_id, string title, string original_title, int year, int s, int season, string t, string episodeId, bool play = false, bool rjson = false) - { - await UpdateService.ConnectAsync(host); - - var init = await loadKit(ModInit.UaTUT); - if (!init.enable) - return OnError(); - Initialization(init); - - OnLog($"UaTUT Play: {title} (s={s}, season={season}, t={t}, episodeId={episodeId}) play={play}"); - - var invoke = new UaTUTInvoke(init, hybridCache, OnLog, proxyManager); - - // Використовуємо кеш для пошуку - string searchCacheKey = $"uatut:search:{imdb_id ?? original_title ?? title}"; - var searchResults = await InvokeCache>(searchCacheKey, TimeSpan.FromMinutes(10), async () => - { - return await invoke.Search(original_title ?? title, imdb_id); - }); - - if (searchResults == null || !searchResults.Any()) - { - OnLog("UaTUT Play: No search results found"); - return OnError(); - } - - // Фільтруємо тільки серіали та аніме - var seriesResults = searchResults.Where(r => r.Category == "Серіал" || r.Category == "Аніме").ToList(); - - if (!seriesResults.Any() || s >= seriesResults.Count) - { - OnLog("UaTUT Play: No series found or invalid series index"); - return OnError(); - } - - var selectedSeries = seriesResults[s]; - - // Використовуємо той самий кеш як і в HandleSeries - string cacheKey = $"uatut:player_data:{selectedSeries.Id}"; - var playerData = await InvokeCache(cacheKey, TimeSpan.FromMinutes(10), async () => - { - return await GetPlayerDataCached(selectedSeries, invoke); - }); - - if (playerData?.Voices == null || !playerData.Voices.Any()) - { - OnLog("UaTUT Play: No player data or voices found"); - return OnError(); - } - - // Знаходимо потрібний епізод в конкретному сезоні та озвучці - if (int.TryParse(t, out int voiceIndex) && voiceIndex < playerData.Voices.Count) - { - var selectedVoice = playerData.Voices[voiceIndex]; - - int seasonIndex = season > 0 ? season - 1 : season; - if (seasonIndex >= 0 && seasonIndex < selectedVoice.Seasons.Count) - { - var selectedSeasonData = selectedVoice.Seasons[seasonIndex]; - - foreach (var episode in selectedSeasonData.Episodes) - { - if (episode.Id == episodeId && !string.IsNullOrEmpty(episode.File)) - { - OnLog($"UaTUT Play: Found episode {episode.Title}, stream: {episode.File}"); - - string streamUrl = BuildStreamUrl(init, episode.File); - string episodeTitle = $"{title ?? original_title} - {episode.Title}"; - - // Якщо play=true, робимо Redirect, інакше повертаємо JSON - if (play) - return UpdateService.Validate(Redirect(streamUrl)); - else - return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, episodeTitle), "application/json; charset=utf-8")); - } - } - } - else - { - OnLog($"UaTUT Play: Invalid season {season}, available seasons: {selectedVoice.Seasons.Count}"); - } - } - else - { - OnLog($"UaTUT Play: Invalid voice index {t}, available voices: {playerData.Voices.Count}"); - } - - OnLog("UaTUT Play: Episode not found"); - return OnError(); - } - - private static string StripLampacArgs(string url) - { - if (string.IsNullOrEmpty(url)) - return url; - - string cleaned = System.Text.RegularExpressions.Regex.Replace( - url, - @"([?&])(account_email|uid|nws_id)=[^&]*", - "$1", - System.Text.RegularExpressions.RegexOptions.IgnoreCase - ); - - cleaned = cleaned.Replace("?&", "?").Replace("&&", "&").TrimEnd('?', '&'); - return cleaned; - } - - private static bool IsMovieCategory(string category, bool preferSeries) - { - if (string.IsNullOrWhiteSpace(category)) - return false; - - var value = category.Trim().ToLowerInvariant(); - if (IsAnimeCategory(value)) - return !preferSeries; - - return value == "фільм" || value == "фильм" || value == "мультфільм" || value == "мультфильм" || value == "movie"; - } - - private static bool IsSeriesCategory(string category, bool preferSeries) - { - if (string.IsNullOrWhiteSpace(category)) - return false; - - var value = category.Trim().ToLowerInvariant(); - if (IsAnimeCategory(value)) - return preferSeries; - - return value == "серіал" || value == "сериал" - || value == "аніме" || value == "аниме" - || value == "мультсеріал" || value == "мультсериал" - || value == "tv"; - } - - private static bool IsAnimeCategory(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return false; - - return value == "аніме" || value == "аниме"; - } - - string BuildStreamUrl(OnlinesSettings init, string streamLink) - { - string link = streamLink?.Trim(); - if (string.IsNullOrEmpty(link)) - return link; - - link = StripLampacArgs(link); - - if (ApnHelper.IsEnabled(init)) - { - if (ModInit.ApnHostProvided || ApnHelper.IsAshdiUrl(link)) - return ApnHelper.WrapUrl(init, link); - - var noApn = (OnlinesSettings)init.Clone(); - noApn.apnstream = false; - noApn.apn = null; - return HostStreamProxy(noApn, link); - } - - return HostStreamProxy(init, link); - } - } -} diff --git a/UaTUT/ModInit.cs b/UaTUT/ModInit.cs deleted file mode 100644 index ef957c3..0000000 --- a/UaTUT/ModInit.cs +++ /dev/null @@ -1,199 +0,0 @@ -using Newtonsoft.Json; -using Shared; -using Shared.Engine; -using Newtonsoft.Json.Linq; -using Shared.Models.Online.Settings; -using Shared.Models.Module; -using Microsoft.AspNetCore.Mvc; -using Microsoft.CodeAnalysis.Scripting; -using Microsoft.Extensions.Caching.Memory; -using Shared.Models; -using Shared.Models.Events; -using System; -using System.Net.Http; -using System.Net.Mime; -using System.Net.Security; -using System.Security.Authentication; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - - -namespace UaTUT -{ - public class ModInit - { - public static double Version => 3.7; - - public static OnlinesSettings UaTUT; - public static bool ApnHostProvided; - - public static OnlinesSettings Settings - { - get => UaTUT; - set => UaTUT = value; - } - - /// - /// модуль загружен - /// - public static void loaded(InitspaceModel initspace) - { - - - UaTUT = new OnlinesSettings("UaTUT", "https://uk.uatut.fun", streamproxy: false, useproxy: false) - { - displayname = "🇺🇦 UaTUT", - displayindex = 0, - apihost = "https://uk.uatut.fun/watch", - proxy = new Shared.Models.Base.ProxySettings() - { - useAuth = true, - username = "a", - password = "a", - list = new string[] { "socks5://IP:PORT" } - } - }; - var conf = ModuleInvoke.Conf("UaTUT", UaTUT); - bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost); - conf.Remove("apn"); - conf.Remove("apn_host"); - UaTUT = conf.ToObject(); - if (hasApn) - ApnHelper.ApplyInitConf(apnEnabled, apnHost, UaTUT); - ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost); - if (hasApn && apnEnabled) - { - UaTUT.streamproxy = false; - } - else if (UaTUT.streamproxy) - { - UaTUT.apnstream = false; - UaTUT.apn = null; - } - - // Виводити "уточнити пошук" - AppInit.conf.online.with_search.Add("uatut"); - } - } - - public static class UpdateService - { - private static readonly string _connectUrl = "https://lmcuk.lampame.v6.rocks/stats"; - - private static ConnectResponse? Connect = null; - private static DateTime? _connectTime = null; - private static DateTime? _disconnectTime = null; - - private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4); - private static Timer? _resetTimer = null; - - private static readonly object _lock = new(); - - public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default) - { - if (_connectTime is not null || Connect?.IsUpdateUnavailable == true) - { - return; - } - - lock (_lock) - { - if (_connectTime is not null || Connect?.IsUpdateUnavailable == true) - { - return; - } - - _connectTime = DateTime.UtcNow; - } - - try - { - using var handler = new SocketsHttpHandler - { - SslOptions = new SslClientAuthenticationOptions - { - RemoteCertificateValidationCallback = (_, _, _, _) => true, - EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13 - } - }; - - using var client = new HttpClient(handler); - client.Timeout = TimeSpan.FromSeconds(15); - - var request = new - { - Host = host, - Module = ModInit.Settings.plugin, - Version = ModInit.Version, - }; - - var requestJson = JsonConvert.SerializeObject(request, Formatting.None); - var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json); - - var response = await client - .PostAsync(_connectUrl, requestContent, cancellationToken) - .ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - if (response.Content.Headers.ContentLength > 0) - { - var responseText = await response.Content - .ReadAsStringAsync(cancellationToken) - .ConfigureAwait(false); - - Connect = JsonConvert.DeserializeObject(responseText); - } - - lock (_lock) - { - _resetTimer?.Dispose(); - _resetTimer = null; - - if (Connect?.IsUpdateUnavailable != true) - { - _resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan); - } - else - { - _disconnectTime = Connect?.IsNoiseEnabled == true - ? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4)) - : DateTime.UtcNow; - } - } - } - catch (Exception) - { - ResetConnectTime(null); - } - } - - private static void ResetConnectTime(object? state) - { - lock (_lock) - { - _connectTime = null; - Connect = null; - - _resetTimer?.Dispose(); - _resetTimer = null; - } - } - public static bool IsDisconnected() - { - return _disconnectTime is not null - && DateTime.UtcNow >= _disconnectTime; - } - - public static ActionResult Validate(ActionResult result) - { - return IsDisconnected() - ? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}") - : result; - } - } - - public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled); -} \ No newline at end of file diff --git a/UaTUT/Models/UaTUTModels.cs b/UaTUT/Models/UaTUTModels.cs deleted file mode 100644 index 714f274..0000000 --- a/UaTUT/Models/UaTUTModels.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace UaTUT.Models -{ - public class SearchResult - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("imdb_id")] - public string ImdbId { get; set; } - - [JsonProperty("title")] - public string Title { get; set; } - - [JsonProperty("title_alt")] - public string TitleAlt { get; set; } - - [JsonProperty("title_en")] - public string TitleEn { get; set; } - - [JsonProperty("title_ru")] - public string TitleRu { get; set; } - - [JsonProperty("year")] - public string Year { get; set; } - - [JsonProperty("category")] - public string Category { get; set; } - } - - public class PlayerData - { - public string File { get; set; } - public string Poster { get; set; } - public List Voices { get; set; } - public List Seasons { get; set; } // Залишаємо для зворотної сумісності - public List Movies { get; set; } - } - - public class Voice - { - public string Name { get; set; } - public List Seasons { get; set; } - } - - public class Season - { - public string Title { get; set; } - public List Episodes { get; set; } - } - - public class Episode - { - public string Title { get; set; } - public string File { get; set; } - public string Id { get; set; } - public string Poster { get; set; } - public string Subtitle { get; set; } - } - - public class MovieVariant - { - public string Title { get; set; } - public string File { get; set; } - public string Quality { get; set; } - } -} diff --git a/UaTUT/OnlineApi.cs b/UaTUT/OnlineApi.cs deleted file mode 100644 index f00f93b..0000000 --- a/UaTUT/OnlineApi.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Caching.Memory; -using Shared.Models; -using Shared.Models.Base; -using Shared.Models.Module; -using System.Collections.Generic; - -namespace UaTUT -{ - public class OnlineApi - { - public static List<(string name, string url, string plugin, int index)> Invoke( - HttpContext httpContext, - IMemoryCache memoryCache, - RequestModel requestInfo, - string host, - OnlineEventsModel args) - { - long.TryParse(args.id, out long tmdbid); - return Events(host, tmdbid, args.imdb_id, args.kinopoisk_id, args.title, args.original_title, args.original_language, args.year, args.source, args.serial, args.account_email); - } - - 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.UaTUT; - // UaTUT: змішаний контент (аніме + не-аніме) — завжди включати при enable && !rip - if (init.enable && !init.rip) - { - string url = init.overridehost; - if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected()) - url = $"{host}/uatut"; - - online.Add((init.displayname, url, "uatut", init.displayindex)); - } - - return online; - } - } -} diff --git a/UaTUT/UaTUT.csproj b/UaTUT/UaTUT.csproj deleted file mode 100644 index 1fbe365..0000000 --- a/UaTUT/UaTUT.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net9.0 - library - true - - - - - ..\..\Shared.dll - - - - diff --git a/UaTUT/UaTUTInvoke.cs b/UaTUT/UaTUTInvoke.cs deleted file mode 100644 index 1c3c561..0000000 --- a/UaTUT/UaTUTInvoke.cs +++ /dev/null @@ -1,478 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Web; -using Newtonsoft.Json; -using Shared.Engine; -using Shared.Models.Online.Settings; -using Shared.Models; -using UaTUT.Models; - -namespace UaTUT -{ - public class UaTUTInvoke - { - private static readonly Regex Quality4kRegex = new Regex(@"(^|[^0-9])(2160p?)([^0-9]|$)|\b4k\b|\buhd\b", RegexOptions.IgnoreCase); - private static readonly Regex QualityFhdRegex = new Regex(@"(^|[^0-9])(1080p?)([^0-9]|$)|\bfhd\b", RegexOptions.IgnoreCase); - - private OnlinesSettings _init; - private IHybridCache _hybridCache; - private Action _onLog; - private ProxyManager _proxyManager; - - public UaTUTInvoke(OnlinesSettings init, IHybridCache hybridCache, Action onLog, ProxyManager proxyManager) - { - _init = init; - _hybridCache = hybridCache; - _onLog = onLog; - _proxyManager = proxyManager; - } - - public async Task> Search(string query, string imdbId = null) - { - try - { - string searchUrl = $"{_init.apihost}/search.php"; - - // Поступовий пошук: спочатку по imdbId, потім по назві - if (!string.IsNullOrEmpty(imdbId)) - { - var imdbResults = await PerformSearch(searchUrl, imdbId); - if (imdbResults?.Any() == true) - return imdbResults; - } - - // Пошук по назві - if (!string.IsNullOrEmpty(query)) - { - var titleResults = await PerformSearch(searchUrl, query); - return titleResults ?? new List(); - } - - return new List(); - } - catch (Exception ex) - { - _onLog($"UaTUT Search error: {ex.Message}"); - return new List(); - } - } - - private async Task> PerformSearch(string searchUrl, string query) - { - string url = $"{searchUrl}?q={HttpUtility.UrlEncode(query)}"; - _onLog($"UaTUT searching: {url}"); - - var headers = new List() { new HeadersModel("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") }; - var response = await Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get()); - - if (string.IsNullOrEmpty(response)) - return null; - - try - { - var results = JsonConvert.DeserializeObject>(response); - _onLog($"UaTUT found {results?.Count ?? 0} results for query: {query}"); - return results; - } - catch (Exception ex) - { - _onLog($"UaTUT parse error: {ex.Message}"); - return null; - } - } - - public async Task GetMoviePageContent(string movieId) - { - try - { - string url = $"{_init.apihost}/{movieId}"; - _onLog($"UaTUT getting movie page: {url}"); - - var headers = new List() { new HeadersModel("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") }; - var response = await Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get()); - - return response; - } - catch (Exception ex) - { - _onLog($"UaTUT GetMoviePageContent error: {ex.Message}"); - return null; - } - } - - public async Task GetPlayerUrl(string moviePageContent) - { - try - { - // Шукаємо iframe з id="vip-player" та class="tab-content" - var match = Regex.Match(moviePageContent, @"]*id=[""']vip-player[""'][^>]*src=[""']([^""']+)[""']", RegexOptions.IgnoreCase); - if (match.Success) - { - string playerUrl = match.Groups[1].Value; - _onLog($"UaTUT found player URL: {playerUrl}"); - return playerUrl; - } - - _onLog("UaTUT: vip-player iframe not found"); - return null; - } - catch (Exception ex) - { - _onLog($"UaTUT GetPlayerUrl error: {ex.Message}"); - return null; - } - } - - public async Task GetPlayerData(string playerUrl) - { - try - { - string sourceUrl = WithAshdiMultivoice(playerUrl); - string requestUrl = sourceUrl; - if (ApnHelper.IsAshdiUrl(sourceUrl) && ApnHelper.IsEnabled(_init) && string.IsNullOrWhiteSpace(_init.webcorshost)) - requestUrl = ApnHelper.WrapUrl(_init, sourceUrl); - - _onLog($"UaTUT getting player data from: {requestUrl}"); - - var headers = new List() - { - new HeadersModel("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"), - new HeadersModel("Referer", sourceUrl.Contains("ashdi.vip", StringComparison.OrdinalIgnoreCase) ? "https://ashdi.vip/" : _init.apihost) - }; - var response = await Http.Get(_init.cors(requestUrl), headers: headers, proxy: _proxyManager.Get()); - - if (string.IsNullOrEmpty(response)) - return null; - - return ParsePlayerData(response); - } - catch (Exception ex) - { - _onLog($"UaTUT GetPlayerData error: {ex.Message}"); - return null; - } - } - - private PlayerData ParsePlayerData(string playerHtml) - { - try - { - var playerData = new PlayerData(); - - // Для фільмів шукаємо прямий file - var fileMatch = Regex.Match(playerHtml, @"file:'([^']+)'", RegexOptions.IgnoreCase); - if (fileMatch.Success && !fileMatch.Groups[1].Value.StartsWith("[")) - { - playerData.File = fileMatch.Groups[1].Value; - playerData.Movies = new List() - { - new MovieVariant - { - File = playerData.File, - Quality = DetectQualityTag(playerData.File) ?? "auto", - Title = BuildMovieTitle("Основне джерело", playerData.File, 1) - } - }; - _onLog($"UaTUT found direct file: {playerData.File}"); - - // Шукаємо poster - var posterMatch = Regex.Match(playerHtml, @"poster:[""']([^""']+)[""']", RegexOptions.IgnoreCase); - if (posterMatch.Success) - playerData.Poster = posterMatch.Groups[1].Value; - - return playerData; - } - - // Для серіалів шукаємо JSON структуру з сезонами та озвучками - string jsonData = ExtractPlayerFileArray(playerHtml); - if (!string.IsNullOrWhiteSpace(jsonData)) - { - string normalizedJson = WebUtility.HtmlDecode(jsonData) - .Replace("\\/", "/") - .Replace("\\'", "'") - .Replace("\\\"", "\""); - - _onLog($"UaTUT found JSON data for series"); - - playerData.Movies = ParseMovieVariantsJson(normalizedJson); - playerData.File = playerData.Movies?.FirstOrDefault()?.File; - playerData.Voices = ParseVoicesJson(normalizedJson); - return playerData; - } - - _onLog("UaTUT: No player data found"); - return null; - } - catch (Exception ex) - { - _onLog($"UaTUT ParsePlayerData error: {ex.Message}"); - return null; - } - } - - private List ParseVoicesJson(string jsonData) - { - try - { - // Декодуємо JSON структуру озвучок - dynamic voicesData = JsonConvert.DeserializeObject(jsonData); - var voices = new List(); - - if (voicesData != null) - { - foreach (var voiceGroup in voicesData) - { - var voice = new Voice - { - Name = voiceGroup.title?.ToString(), - Seasons = new List() - }; - - if (voiceGroup.folder != null) - { - foreach (var seasonData in voiceGroup.folder) - { - var season = new Season - { - Title = seasonData.title?.ToString(), - Episodes = new List() - }; - - if (seasonData.folder != null) - { - foreach (var episodeData in seasonData.folder) - { - var episode = new Episode - { - Title = episodeData.title?.ToString(), - File = episodeData.file?.ToString(), - Id = episodeData.id?.ToString(), - Poster = episodeData.poster?.ToString(), - Subtitle = episodeData.subtitle?.ToString() - }; - season.Episodes.Add(episode); - } - } - - voice.Seasons.Add(season); - } - } - - voices.Add(voice); - } - } - - _onLog($"UaTUT parsed {voices.Count} voices"); - return voices; - } - catch (Exception ex) - { - _onLog($"UaTUT ParseVoicesJson error: {ex.Message}"); - return new List(); - } - } - - private List ParseMovieVariantsJson(string jsonData) - { - try - { - var data = JsonConvert.DeserializeObject>(jsonData); - var movies = new List(); - if (data == null || data.Count == 0) - return movies; - - int index = 1; - foreach (var item in data) - { - string file = item?.file?.ToString(); - if (string.IsNullOrWhiteSpace(file)) - continue; - - string rawTitle = item?.title?.ToString(); - movies.Add(new MovieVariant - { - File = file, - Quality = DetectQualityTag($"{rawTitle} {file}") ?? "auto", - Title = BuildMovieTitle(rawTitle, file, index) - }); - index++; - } - - return movies; - } - catch (Exception ex) - { - _onLog($"UaTUT ParseMovieVariantsJson error: {ex.Message}"); - return new List(); - } - } - - private static string WithAshdiMultivoice(string url) - { - if (string.IsNullOrWhiteSpace(url)) - return url; - - if (url.IndexOf("ashdi.vip/vod/", StringComparison.OrdinalIgnoreCase) < 0) - return url; - - if (url.IndexOf("multivoice", StringComparison.OrdinalIgnoreCase) >= 0) - return url; - - return url.Contains("?") ? $"{url}&multivoice" : $"{url}?multivoice"; - } - - private static string BuildMovieTitle(string rawTitle, string file, int index) - { - string title = string.IsNullOrWhiteSpace(rawTitle) ? $"Варіант {index}" : StripMoviePrefix(WebUtility.HtmlDecode(rawTitle).Trim()); - string qualityTag = DetectQualityTag($"{title} {file}"); - if (string.IsNullOrWhiteSpace(qualityTag)) - return title; - - if (title.StartsWith("[4K]", StringComparison.OrdinalIgnoreCase) || title.StartsWith("[FHD]", StringComparison.OrdinalIgnoreCase)) - return title; - - return $"{qualityTag} {title}"; - } - - private static string DetectQualityTag(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return null; - - if (Quality4kRegex.IsMatch(value)) - return "[4K]"; - - if (QualityFhdRegex.IsMatch(value)) - return "[FHD]"; - - return null; - } - - private static string StripMoviePrefix(string title) - { - if (string.IsNullOrWhiteSpace(title)) - return title; - - string normalized = Regex.Replace(title, @"\s+", " ").Trim(); - int sepIndex = normalized.LastIndexOf(" - ", StringComparison.Ordinal); - if (sepIndex <= 0 || sepIndex >= normalized.Length - 3) - return normalized; - - string prefix = normalized.Substring(0, sepIndex).Trim(); - string suffix = normalized.Substring(sepIndex + 3).Trim(); - if (string.IsNullOrWhiteSpace(suffix)) - return normalized; - - if (Regex.IsMatch(prefix, @"(19|20)\d{2}")) - return suffix; - - return normalized; - } - - private static string ExtractPlayerFileArray(string html) - { - if (string.IsNullOrWhiteSpace(html)) - return null; - - int searchIndex = 0; - while (searchIndex >= 0 && searchIndex < html.Length) - { - int fileIndex = html.IndexOf("file", searchIndex, StringComparison.OrdinalIgnoreCase); - if (fileIndex < 0) - return null; - - int colonIndex = html.IndexOf(':', fileIndex); - if (colonIndex < 0) - return null; - - int startIndex = colonIndex + 1; - while (startIndex < html.Length && char.IsWhiteSpace(html[startIndex])) - startIndex++; - - if (startIndex < html.Length && (html[startIndex] == '\'' || html[startIndex] == '"')) - { - startIndex++; - while (startIndex < html.Length && char.IsWhiteSpace(html[startIndex])) - startIndex++; - } - - if (startIndex >= html.Length || html[startIndex] != '[') - { - searchIndex = fileIndex + 4; - continue; - } - - return ExtractBracketArray(html, startIndex); - } - - return null; - } - - private static string ExtractBracketArray(string text, int startIndex) - { - if (startIndex < 0 || startIndex >= text.Length || text[startIndex] != '[') - return null; - - int depth = 0; - bool inString = false; - bool escaped = false; - char quoteChar = '\0'; - - for (int i = startIndex; i < text.Length; i++) - { - char ch = text[i]; - - if (inString) - { - if (escaped) - { - escaped = false; - continue; - } - - if (ch == '\\') - { - escaped = true; - continue; - } - - if (ch == quoteChar) - { - inString = false; - quoteChar = '\0'; - } - - continue; - } - - if (ch == '"' || ch == '\'') - { - inString = true; - quoteChar = ch; - continue; - } - - if (ch == '[') - { - depth++; - continue; - } - - if (ch == ']') - { - depth--; - if (depth == 0) - return text.Substring(startIndex, i - startIndex + 1); - } - } - - return null; - } - } -} diff --git a/UaTUT/manifest.json b/UaTUT/manifest.json deleted file mode 100644 index e320417..0000000 --- a/UaTUT/manifest.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "enable": true, - "version": 3, - "initspace": "UaTUT.ModInit", - "online": "UaTUT.OnlineApi" -} \ No newline at end of file