diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 8f4dd64..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-/.idea/
-/AIDocumentation/
-/LampaC/
-/BanderaBackend/
-/Kinovezha/
-/.clinerules/moduls.md
-/.clinerules/uaflix-optimization.md
-/.clinerules/
-/.qodo/
\ No newline at end of file
diff --git a/AnimeON/AnimeON.csproj b/AnimeON/AnimeON.csproj
deleted file mode 100644
index c26a806..0000000
--- a/AnimeON/AnimeON.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- net9.0
- library
- true
-
-
-
-
- ..\..\Shared.dll
-
-
-
-
\ No newline at end of file
diff --git a/AnimeON/AnimeONInvoke.cs b/AnimeON/AnimeONInvoke.cs
deleted file mode 100644
index 42c0789..0000000
--- a/AnimeON/AnimeONInvoke.cs
+++ /dev/null
@@ -1,352 +0,0 @@
-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 System.Text;
-using AnimeON.Models;
-using Shared.Engine;
-
-namespace AnimeON
-{
- public class AnimeONInvoke
- {
- private static readonly HashSet NotAllowedHosts =
- new HashSet(
- new[]
- {
- "c3ZpdGFubW92aWU=",
- "cG9ydGFsLXR2",
- }
- .Select(base64 => Encoding.UTF8.GetString(Convert.FromBase64String(base64))),
- StringComparer.OrdinalIgnoreCase
- );
- private OnlinesSettings _init;
- private HybridCache _hybridCache;
- private Action _onLog;
- private ProxyManager _proxyManager;
-
- public AnimeONInvoke(OnlinesSettings init, HybridCache hybridCache, Action onLog, ProxyManager proxyManager)
- {
- _init = init;
- _hybridCache = hybridCache;
- _onLog = onLog;
- _proxyManager = proxyManager;
- }
-
- public async Task> Search(string imdb_id, long kinopoisk_id, string title, string original_title, int year)
- {
- string memKey = $"AnimeON:search:{kinopoisk_id}:{imdb_id}";
- if (_hybridCache.TryGetValue(memKey, out List res))
- return res;
-
- try
- {
- var headers = new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) };
-
- async Task> FindAnime(string query)
- {
- if (string.IsNullOrEmpty(query))
- return null;
-
- string searchUrl = $"{_init.host}/api/anime/search?text={System.Web.HttpUtility.UrlEncode(query)}";
- if (IsNotAllowedHost(searchUrl))
- return null;
-
- _onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {searchUrl}");
- string searchJson = await Http.Get(searchUrl, headers: headers, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(searchJson))
- return null;
-
- var searchResponse = JsonSerializer.Deserialize(searchJson);
- return searchResponse?.Result;
- }
-
- var searchResults = await FindAnime(title) ?? await FindAnime(original_title);
- if (searchResults == null)
- return null;
-
- if (!string.IsNullOrEmpty(imdb_id))
- {
- var seasons = searchResults.Where(a => a.ImdbId == imdb_id).ToList();
- if (seasons.Count > 0)
- {
- _hybridCache.Set(memKey, seasons, cacheTime(5));
- return seasons;
- }
- }
-
- // Fallback to first result if no imdb match
- var firstResult = searchResults.FirstOrDefault();
- if (firstResult != null)
- {
- var list = new List { firstResult };
- _hybridCache.Set(memKey, list, cacheTime(5));
- return list;
- }
-
- return null;
- }
- catch (Exception ex)
- {
- _onLog($"AnimeON error: {ex.Message}");
- }
-
- return null;
- }
-
- public async Task> GetFundubs(int animeId)
- {
- string fundubsUrl = $"{_init.host}/api/player/{animeId}/translations";
- if (IsNotAllowedHost(fundubsUrl))
- return null;
-
- _onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {fundubsUrl}");
- string fundubsJson = await Http.Get(fundubsUrl, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(fundubsJson))
- return null;
-
- var fundubsResponse = JsonSerializer.Deserialize(fundubsJson);
- if (fundubsResponse?.Translations == null || fundubsResponse.Translations.Count == 0)
- return null;
-
- var fundubs = new List();
- foreach (var translation in fundubsResponse.Translations)
- {
- var fundubModel = new FundubModel
- {
- Fundub = translation.Translation,
- Player = translation.Player
- };
- fundubs.Add(fundubModel);
- }
- return fundubs;
- }
-
- public async Task GetEpisodes(int animeId, int playerId, int fundubId)
- {
- string episodesUrl = $"{_init.host}/api/player/{animeId}/episodes?take=100&skip=-1&playerId={playerId}&translationId={fundubId}";
- if (IsNotAllowedHost(episodesUrl))
- return null;
-
- _onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {episodesUrl}");
- string episodesJson = await Http.Get(episodesUrl, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(episodesJson))
- return null;
-
- return JsonSerializer.Deserialize(episodesJson);
- }
-
- public async Task ParseMoonAnimePage(string url)
- {
- try
- {
- string requestUrl = $"{url}?player=animeon.club";
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", "https://animeon.club/")
- };
-
- if (IsNotAllowedHost(requestUrl))
- return null;
-
- _onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {requestUrl}");
- string html = await Http.Get(requestUrl, headers: headers, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(html))
- return null;
-
- var match = System.Text.RegularExpressions.Regex.Match(html, @"file:\s*""([^""]+\.m3u8)""");
- if (match.Success)
- {
- return match.Groups[1].Value;
- }
- }
- catch (Exception ex)
- {
- _onLog($"AnimeON ParseMoonAnimePage error: {ex.Message}");
- }
-
- return null;
- }
-
- public async Task ParseAshdiPage(string url)
- {
- try
- {
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", "https://ashdi.vip/")
- };
-
- if (IsNotAllowedHost(url))
- return null;
-
- _onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {url}");
- string html = await Http.Get(url, headers: headers, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(html))
- return null;
-
- var match = System.Text.RegularExpressions.Regex.Match(html, @"file\s*:\s*['""]([^'""]+)['""]");
- if (match.Success)
- {
- return match.Groups[1].Value;
- }
- }
- catch (Exception ex)
- {
- _onLog($"AnimeON ParseAshdiPage error: {ex.Message}");
- }
-
- return null;
- }
-
- public async Task ResolveEpisodeStream(int episodeId)
- {
- try
- {
- string url = $"{_init.host}/api/player/{episodeId}/episode";
- if (IsNotAllowedHost(url))
- return null;
-
- _onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {url}");
- string json = await Http.Get(url, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(json))
- return null;
-
- using var doc = JsonDocument.Parse(json);
- var root = doc.RootElement;
- if (root.TryGetProperty("fileUrl", out var fileProp))
- {
- string fileUrl = fileProp.GetString();
- if (!string.IsNullOrEmpty(fileUrl))
- return fileUrl;
- }
-
- if (root.TryGetProperty("videoUrl", out var videoProp))
- {
- string videoUrl = videoProp.GetString();
- return await ResolveVideoUrl(videoUrl);
- }
- }
- catch (Exception ex)
- {
- _onLog($"AnimeON ResolveEpisodeStream error: {ex.Message}");
- }
-
- return null;
- }
-
- public async Task ResolveVideoUrl(string url)
- {
- if (string.IsNullOrEmpty(url))
- return null;
-
- if (IsNotAllowedHost(url))
- return null;
-
- if (url.Contains("moonanime.art"))
- return await ParseMoonAnimePage(url);
-
- if (url.Contains("ashdi.vip/vod"))
- return await ParseAshdiPage(url);
-
- return url;
- }
-
- 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);
- }
-
- public static TimeSpan cacheTime(int multiaccess, int home = 5, int mikrotik = 2, OnlinesSettings init = null, int rhub = -1)
- {
- if (init != null && init.rhub && rhub != -1)
- return TimeSpan.FromMinutes(rhub);
-
- int ctime = AppInit.conf.mikrotik ? mikrotik : AppInit.conf.multiaccess ? init != null && init.cache_time > 0 ? init.cache_time : multiaccess : home;
- if (ctime > multiaccess)
- ctime = multiaccess;
-
- return TimeSpan.FromMinutes(ctime);
- }
- public async Task AggregateSerialStructure(int animeId, int season)
- {
- string memKey = $"AnimeON:aggregated:{animeId}:{season}";
- if (_hybridCache.TryGetValue(memKey, out AnimeON.Models.AnimeONAggregatedStructure cached))
- return cached;
-
- try
- {
- var structure = new AnimeON.Models.AnimeONAggregatedStructure
- {
- AnimeId = animeId,
- Season = season,
- Voices = new Dictionary()
- };
-
- var fundubs = await GetFundubs(animeId);
- if (fundubs == null || fundubs.Count == 0)
- return null;
-
- foreach (var fundub in fundubs)
- {
- if (fundub?.Fundub == null || fundub.Player == null)
- continue;
-
- foreach (var player in fundub.Player)
- {
- string display = $"[{player.Name}] {fundub.Fundub.Name}";
-
- var episodesData = await GetEpisodes(animeId, player.Id, fundub.Fundub.Id);
- if (episodesData?.Episodes == null || episodesData.Episodes.Count == 0)
- continue;
-
- var voiceInfo = new AnimeON.Models.AnimeONVoiceInfo
- {
- Name = fundub.Fundub.Name,
- PlayerType = player.Name?.ToLower(),
- DisplayName = display,
- PlayerId = player.Id,
- FundubId = fundub.Fundub.Id,
- Episodes = episodesData.Episodes
- .OrderBy(ep => ep.EpisodeNum)
- .Select(ep => new AnimeON.Models.AnimeONEpisodeInfo
- {
- Number = ep.EpisodeNum,
- Title = ep.Name,
- Hls = ep.Hls,
- VideoUrl = ep.VideoUrl,
- EpisodeId = ep.Id
- })
- .ToList()
- };
-
- structure.Voices[display] = voiceInfo;
- }
- }
-
- if (!structure.Voices.Any())
- return null;
-
- _hybridCache.Set(memKey, structure, cacheTime(20, init: _init));
- return structure;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"AnimeON AggregateSerialStructure error: {ex.Message}");
- return null;
- }
- }
- }
-}
diff --git a/AnimeON/Controller.cs b/AnimeON/Controller.cs
deleted file mode 100644
index dc0e1c7..0000000
--- a/AnimeON/Controller.cs
+++ /dev/null
@@ -1,361 +0,0 @@
-using System.Text.Json;
-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 AnimeON.Models;
-using System.Text.RegularExpressions;
-using System.Text;
-using Shared.Models.Online.Settings;
-using Shared.Models;
-using HtmlAgilityPack;
-
-namespace AnimeON.Controllers
-{
- public class Controller : BaseOnlineController
- {
- private static readonly HashSet NotAllowedHosts =
- new HashSet(
- new[]
- {
- "c3ZpdGFubW92aWU=",
- "cG9ydGFsLXR2",
- }
- .Select(base64 => Encoding.UTF8.GetString(Convert.FromBase64String(base64))),
- StringComparer.OrdinalIgnoreCase
- );
- ProxyManager proxyManager;
-
- public Controller()
- {
- proxyManager = new ProxyManager(ModInit.AnimeON);
- }
-
- [HttpGet]
- [Route("animeon")]
- 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.AnimeON);
- if (!init.enable)
- return Forbid();
-
- var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager);
- OnLog($"AnimeON Index: title={title}, original_title={original_title}, serial={serial}, s={s}, t={t}, year={year}, imdb_id={imdb_id}, kp={kinopoisk_id}");
-
- var seasons = await invoke.Search(imdb_id, kinopoisk_id, title, original_title, year);
- OnLog($"AnimeON: search results = {seasons?.Count ?? 0}");
- if (seasons == null || seasons.Count == 0)
- return OnError("animeon", proxyManager);
-
- // [Refactoring] Використовується агрегована структура (AggregateSerialStructure) — попередній збір allOptions не потрібний
-
- // [Refactoring] Перевірка allOptions видалена — використовується перевірка структури озвучок нижче
-
- if (serial == 1)
- {
- if (s == -1) // Крок 1: Вибір аніме (як сезони)
- {
- var season_tpl = new SeasonTpl(seasons.Count);
- for (int i = 0; i < seasons.Count; i++)
- {
- var anime = seasons[i];
- string seasonName = anime.Season.ToString();
- string link = $"{host}/animeon?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={i}";
- season_tpl.Append(seasonName, link, anime.Season.ToString());
- }
- OnLog($"AnimeON: return seasons count={seasons.Count}");
- return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8");
- }
- else // Крок 2/3: Вибір озвучки та епізодів
- {
- if (s >= seasons.Count)
- return OnError("animeon", proxyManager);
-
- var selectedAnime = seasons[s];
- var structure = await invoke.AggregateSerialStructure(selectedAnime.Id, selectedAnime.Season);
- if (structure == null || !structure.Voices.Any())
- return OnError("animeon", proxyManager);
-
- OnLog($"AnimeON: voices found = {structure.Voices.Count}");
- // Автовибір першої озвучки якщо t не задано
- if (string.IsNullOrEmpty(t))
- t = structure.Voices.Keys.First();
-
- // Формуємо список озвучок
- var voice_tpl = new VoiceTpl();
- foreach (var voice in structure.Voices)
- {
- string voiceLink = $"{host}/animeon?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&t={HttpUtility.UrlEncode(voice.Key)}";
- bool isActive = voice.Key == t;
- voice_tpl.Append(voice.Key, isActive, voiceLink);
- }
-
- // Перевірка вибраної озвучки
- if (!structure.Voices.ContainsKey(t))
- return OnError("animeon", proxyManager);
-
- var episode_tpl = new EpisodeTpl();
- var selectedVoiceInfo = structure.Voices[t];
-
- // Формуємо епізоди для вибраної озвучки
- foreach (var ep in selectedVoiceInfo.Episodes.OrderBy(e => e.Number))
- {
- string episodeName = !string.IsNullOrEmpty(ep.Title) ? ep.Title : $"Епізод {ep.Number}";
- string seasonStr = selectedAnime.Season.ToString();
- string episodeStr = ep.Number.ToString();
-
- string streamLink = !string.IsNullOrEmpty(ep.Hls) ? ep.Hls : ep.VideoUrl;
- bool needsResolve = selectedVoiceInfo.PlayerType == "moon" || selectedVoiceInfo.PlayerType == "ashdi";
-
- if (string.IsNullOrEmpty(streamLink) && ep.EpisodeId > 0)
- {
- string callUrl = $"{host}/animeon/play?episode_id={ep.EpisodeId}";
- episode_tpl.Append(episodeName, title ?? original_title, seasonStr, episodeStr, accsArgs(callUrl), "call");
- continue;
- }
-
- if (string.IsNullOrEmpty(streamLink))
- continue;
-
- if (needsResolve || streamLink.Contains("moonanime.art") || streamLink.Contains("ashdi.vip/vod"))
- {
- string callUrl = $"{host}/animeon/play?url={HttpUtility.UrlEncode(streamLink)}";
- episode_tpl.Append(episodeName, title ?? original_title, seasonStr, episodeStr, accsArgs(callUrl), "call");
- }
- else
- {
- string playUrl = HostStreamProxy(init, accsArgs(streamLink));
- episode_tpl.Append(episodeName, title ?? original_title, seasonStr, episodeStr, playUrl);
- }
- }
-
- // Повертаємо озвучки + епізоди разом
- OnLog($"AnimeON: return episodes count={selectedVoiceInfo.Episodes.Count} for voice='{t}' season={selectedAnime.Season}");
- 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 = seasons.FirstOrDefault();
- if (firstAnime == null)
- return OnError("animeon", proxyManager);
-
- var fundubs = await invoke.GetFundubs(firstAnime.Id);
- OnLog($"AnimeON: movie fundubs count = {fundubs?.Count ?? 0}");
- if (fundubs == null || fundubs.Count == 0)
- return OnError("animeon", proxyManager);
-
- var tpl = new MovieTpl(title, original_title);
-
- foreach (var fundub in fundubs)
- {
- if (fundub?.Fundub == null || fundub.Player == null || fundub.Player.Count == 0)
- continue;
-
- foreach (var player in fundub.Player)
- {
- var episodesData = await invoke.GetEpisodes(firstAnime.Id, player.Id, fundub.Fundub.Id);
- if (episodesData == null || episodesData.Episodes == null || episodesData.Episodes.Count == 0)
- continue;
-
- var firstEp = episodesData.Episodes.FirstOrDefault();
- if (firstEp == null)
- continue;
-
- string streamLink = !string.IsNullOrEmpty(firstEp.Hls) ? firstEp.Hls : firstEp.VideoUrl;
- if (string.IsNullOrEmpty(streamLink))
- continue;
-
- string translationName = $"[{player.Name}] {fundub.Fundub.Name}";
-
- bool needsResolve = player.Name?.ToLower() == "moon" || player.Name?.ToLower() == "ashdi";
- if (needsResolve || streamLink.Contains("moonanime.art/iframe/") || streamLink.Contains("ashdi.vip/vod"))
- {
- string callUrl = $"{host}/animeon/play?url={HttpUtility.UrlEncode(streamLink)}";
- tpl.Append(translationName, accsArgs(callUrl), "call");
- }
- else
- {
- tpl.Append(translationName, HostStreamProxy(init, accsArgs(streamLink)));
- }
- }
- }
-
- // Якщо не зібрали жодної опції — повертаємо помилку
- if (tpl.IsEmpty())
- return OnError("animeon", proxyManager);
-
- OnLog("AnimeON: return movie options");
- return rjson ? Content(tpl.ToJson(), "application/json; charset=utf-8") : Content(tpl.ToHtml(), "text/html; charset=utf-8");
- }
- }
-
- async Task> GetFundubs(OnlinesSettings init, int animeId)
- {
- string fundubsUrl = $"{init.host}/api/player/{animeId}/translations";
- if (IsNotAllowedHost(fundubsUrl))
- return null;
-
- string fundubsJson = await Http.Get(fundubsUrl, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
- if (string.IsNullOrEmpty(fundubsJson))
- return null;
-
- var fundubsResponse = JsonSerializer.Deserialize(fundubsJson);
- if (fundubsResponse?.Translations == null || fundubsResponse.Translations.Count == 0)
- return null;
-
- var fundubs = new List();
- foreach (var translation in fundubsResponse.Translations)
- {
- var fundubModel = new FundubModel
- {
- Fundub = translation.Translation,
- Player = translation.Player
- };
- fundubs.Add(fundubModel);
- }
- return fundubs;
- }
-
- async Task GetEpisodes(OnlinesSettings init, int animeId, int playerId, int fundubId)
- {
- string episodesUrl = $"{init.host}/api/player/{animeId}/episodes?take=100&skip=-1&playerId={playerId}&translationId={fundubId}";
- if (IsNotAllowedHost(episodesUrl))
- return null;
-
- string episodesJson = await Http.Get(episodesUrl, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
- if (string.IsNullOrEmpty(episodesJson))
- return null;
-
- return JsonSerializer.Deserialize(episodesJson);
- }
-
- async ValueTask> search(OnlinesSettings init, string imdb_id, long kinopoisk_id, string title, string original_title, int year)
- {
- string memKey = $"AnimeON:search:{kinopoisk_id}:{imdb_id}";
- if (hybridCache.TryGetValue(memKey, out List res))
- return res;
-
- try
- {
- var headers = new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) };
-
- async Task> FindAnime(string query)
- {
- if (string.IsNullOrEmpty(query))
- return null;
-
- string searchUrl = $"{init.host}/api/anime/search?text={HttpUtility.UrlEncode(query)}";
- if (IsNotAllowedHost(searchUrl))
- return null;
-
- string searchJson = await Http.Get(searchUrl, headers: headers);
- if (string.IsNullOrEmpty(searchJson))
- return null;
-
- var searchResponse = JsonSerializer.Deserialize(searchJson);
- return searchResponse?.Result;
- }
-
- var searchResults = await FindAnime(title) ?? await FindAnime(original_title);
- if (searchResults == null)
- return null;
-
- if (!string.IsNullOrEmpty(imdb_id))
- {
- var seasons = searchResults.Where(a => a.ImdbId == imdb_id).ToList();
- if (seasons.Count > 0)
- {
- hybridCache.Set(memKey, seasons, cacheTime(5));
- return seasons;
- }
- }
-
- // Fallback to first result if no imdb match
- var firstResult = searchResults.FirstOrDefault();
- if (firstResult != null)
- {
- var list = new List { firstResult };
- hybridCache.Set(memKey, list, cacheTime(5));
- return list;
- }
-
- return null;
- }
- catch (Exception ex)
- {
- OnLog($"AnimeON error: {ex.Message}");
- }
-
- return null;
- }
-
- [HttpGet("animeon/play")]
- public async Task Play(string url, int episode_id = 0, string title = null)
- {
- var init = await loadKit(ModInit.AnimeON);
- if (!init.enable)
- return Forbid();
-
- var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager);
- OnLog($"AnimeON Play: url={url}, episode_id={episode_id}");
-
- string streamLink = null;
- if (episode_id > 0)
- {
- streamLink = await invoke.ResolveEpisodeStream(episode_id);
- }
- else if (!string.IsNullOrEmpty(url))
- {
- streamLink = await invoke.ResolveVideoUrl(url);
- }
- else
- {
- OnLog("AnimeON Play: empty url");
- return OnError("animeon", proxyManager);
- }
-
- if (string.IsNullOrEmpty(streamLink))
- {
- OnLog("AnimeON Play: cannot extract stream");
- return OnError("animeon", proxyManager);
- }
-
- List streamHeaders = null;
- bool forceProxy = false;
- if (streamLink.Contains("ashdi.vip", StringComparison.OrdinalIgnoreCase))
- {
- streamHeaders = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", "https://ashdi.vip/")
- };
- forceProxy = true;
- }
-
- string streamUrl = HostStreamProxy(init, accsArgs(streamLink), headers: streamHeaders, force_streamproxy: forceProxy);
- string jsonResult = $"{{\"method\":\"play\",\"url\":\"{streamUrl}\",\"title\":\"{title ?? string.Empty}\"}}";
- OnLog("AnimeON Play: return call JSON");
- return Content(jsonResult, "application/json; charset=utf-8");
- }
-
- 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);
- }
- }
-}
diff --git a/AnimeON/ModInit.cs b/AnimeON/ModInit.cs
deleted file mode 100644
index aefa2e5..0000000
--- a/AnimeON/ModInit.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using Newtonsoft.Json;
-using Shared;
-using Shared.Engine;
-using Newtonsoft.Json.Linq;
-using Shared;
-using Shared.Models.Online.Settings;
-using Shared.Models.Module;
-
-using Newtonsoft.Json;
-using Shared;
-using Shared.Engine;
-using Newtonsoft.Json.Linq;
-
-namespace AnimeON
-{
- public class ModInit
- {
- public static OnlinesSettings AnimeON;
-
- ///
- /// модуль загружен
- ///
- public static void loaded(InitspaceModel initspace)
- {
- AnimeON = new OnlinesSettings("AnimeON", "https://animeon.club", streamproxy: false, useproxy: false)
- {
- displayname = "🇯🇵 AnimeON",
- displayindex = 0,
- proxy = new Shared.Models.Base.ProxySettings()
- {
- useAuth = true,
- username = "",
- password = "",
- list = new string[] { "socks5://ip:port" }
- }
- };
- AnimeON = ModuleInvoke.Conf("AnimeON", AnimeON).ToObject();
-
- // Виводити "уточнити пошук"
- AppInit.conf.online.with_search.Add("animeon");
- }
- }
-}
\ No newline at end of file
diff --git a/AnimeON/Models/AnimeONAggregatedStructure.cs b/AnimeON/Models/AnimeONAggregatedStructure.cs
deleted file mode 100644
index d3f3f32..0000000
--- a/AnimeON/Models/AnimeONAggregatedStructure.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System.Collections.Generic;
-
-namespace AnimeON.Models
-{
- /// Aggregated structure for AnimeON serial content to match Lampac standard navigation.
- public class AnimeONAggregatedStructure
- {
- /// Anime identifier from AnimeON API.
- public int AnimeId { get; set; }
-
- /// Season number.
- public int Season { get; set; }
-
- /// Voices mapped by display key e.g. "[Moon] AniUA".
- public Dictionary Voices { get; set; } = new Dictionary();
- }
-
- /// Voice information for a specific player/studio combination within a season.
- public class AnimeONVoiceInfo
- {
- /// Studio/voice name (e.g., AniUA).
- public string Name { get; set; }
-
- /// Player type ("moon" or "ashdi").
- public string PlayerType { get; set; }
-
- /// Display name (e.g., "[Moon] AniUA").
- public string DisplayName { get; set; }
-
- /// Player identifier from API.
- public int PlayerId { get; set; }
-
- /// Fundub identifier from API.
- public int FundubId { get; set; }
-
- /// Flat list of episodes for the selected season.
- public List Episodes { get; set; } = new List();
- }
-
- /// Episode information within a voice.
- public class AnimeONEpisodeInfo
- {
- /// Episode number.
- public int Number { get; set; }
-
- /// Episode title.
- public string Title { get; set; }
-
- /// Primary HLS link if available.
- public string Hls { get; set; }
-
- /// Fallback video URL (iframe or direct).
- public string VideoUrl { get; set; }
-
- /// Episode identifier from API.
- public int EpisodeId { get; set; }
- }
-}
\ No newline at end of file
diff --git a/AnimeON/Models/EmbedModel.cs b/AnimeON/Models/EmbedModel.cs
deleted file mode 100644
index afd9d3c..0000000
--- a/AnimeON/Models/EmbedModel.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-
-namespace AnimeON.Models
-{
- public class EmbedModel
- {
- [JsonPropertyName("translation")]
- public string Translation { get; set; }
-
- [JsonPropertyName("links")]
- public List<(string link, string quality)> Links { get; set; }
-
- [JsonPropertyName("subtitles")]
- public Shared.Models.Templates.SubtitleTpl? Subtitles { get; set; }
-
- [JsonPropertyName("season")]
- public int Season { get; set; }
-
- [JsonPropertyName("episode")]
- public int Episode { get; set; }
- }
-}
\ No newline at end of file
diff --git a/AnimeON/Models/Models.cs b/AnimeON/Models/Models.cs
deleted file mode 100644
index a498121..0000000
--- a/AnimeON/Models/Models.cs
+++ /dev/null
@@ -1,169 +0,0 @@
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-
-namespace AnimeON.Models
-{
- public class SearchResponseModel
- {
- [JsonPropertyName("result")]
- public List Result { get; set; }
-
- [JsonPropertyName("count")]
- public int Count { get; set; }
- }
-
- public class SearchModel
- {
- [JsonPropertyName("id")]
- public int Id { get; set; }
-
- [JsonPropertyName("titleUa")]
- public string TitleUa { get; set; }
-
- [JsonPropertyName("titleEn")]
- public string TitleEn { get; set; }
-
- [JsonPropertyName("releaseDate")]
- public string Year { get; set; }
-
- [JsonPropertyName("imdbId")]
- public string ImdbId { get; set; }
-
- [JsonPropertyName("season")]
- public int Season { get; set; }
- }
-
- public class FundubsResponseModel
- {
- [JsonPropertyName("translations")]
- public List Translations { get; set; }
- }
-
- public class TranslationModel
- {
- [JsonPropertyName("translation")]
- public Fundub Translation { get; set; }
-
- [JsonPropertyName("player")]
- public List Player { get; set; }
- }
-
- public class Fundub
- {
- [JsonPropertyName("id")]
- public int Id { get; set; }
-
- [JsonPropertyName("name")]
- public string Name { get; set; }
-
- [JsonPropertyName("isSub")]
- public bool IsSub { get; set; }
-
- [JsonPropertyName("synonyms")]
- public List Synonyms { get; set; }
-
- [JsonPropertyName("studios")]
- public List Studios { get; set; }
- }
-
- public class Studio
- {
- [JsonPropertyName("slug")]
- public string Slug { get; set; }
-
- [JsonPropertyName("name")]
- public string Name { get; set; }
-
- [JsonPropertyName("description")]
- public string Description { get; set; }
-
- [JsonPropertyName("team")]
- public object Team { get; set; }
-
- [JsonPropertyName("telegram")]
- public string Telegram { get; set; }
-
- [JsonPropertyName("youtube")]
- public string Youtube { get; set; }
-
- [JsonPropertyName("patreon")]
- public string Patreon { get; set; }
-
- [JsonPropertyName("buymeacoffee")]
- public string BuyMeACoffee { get; set; }
-
- [JsonPropertyName("avatar")]
- public Avatar Avatar { get; set; }
- }
-
- public class Avatar
- {
- [JsonPropertyName("id")]
- public int Id { get; set; }
-
- [JsonPropertyName("original")]
- public string Original { get; set; }
-
- [JsonPropertyName("preview")]
- public string Preview { get; set; }
- }
-
- public class Player
- {
- [JsonPropertyName("name")]
- public string Name { get; set; }
-
- [JsonPropertyName("id")]
- public int Id { get; set; }
-
- [JsonPropertyName("episodesCount")]
- public int EpisodesCount { get; set; }
- }
-
- public class FundubModel
- {
- public Fundub Fundub { get; set; }
- public List Player { get; set; }
- }
-
- public class EpisodeModel
- {
- [JsonPropertyName("episodes")]
- public List Episodes { get; set; }
-
- [JsonPropertyName("anotherPlayer")]
- public System.Text.Json.JsonElement AnotherPlayer { get; set; }
- }
-
- public class Episode
- {
- [JsonPropertyName("id")]
- public int Id { get; set; }
-
- [JsonPropertyName("episode")]
- public int EpisodeNum { get; set; }
-
- [JsonPropertyName("fileUrl")]
- public string Hls { get; set; }
-
- [JsonPropertyName("videoUrl")]
- public string VideoUrl { get; set; }
-
- [JsonPropertyName("name")]
- public string Name { get; set; }
- }
-
- public class Movie
- {
- public string translation { get; set; }
- public List<(string link, string quality)> links { get; set; }
- public Shared.Models.Templates.SubtitleTpl? subtitles { get; set; }
- public int season { get; set; }
- public int episode { get; set; }
- }
-
- public class Result
- {
- public List movie { get; set; }
- }
-}
\ No newline at end of file
diff --git a/AnimeON/Models/Serial.cs b/AnimeON/Models/Serial.cs
deleted file mode 100644
index ed2392a..0000000
--- a/AnimeON/Models/Serial.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-
-namespace AnimeON.Models
-{
- public class Serial
- {
- [JsonPropertyName("id")]
- public int Id { get; set; }
-
- [JsonPropertyName("title_ua")]
- public string TitleUa { get; set; }
-
- [JsonPropertyName("title_en")]
- public string TitleEn { get; set; }
-
- [JsonPropertyName("year")]
- public string Year { get; set; }
-
- [JsonPropertyName("imdb_id")]
- public string ImdbId { get; set; }
-
- [JsonPropertyName("season")]
- public int Season { get; set; }
-
- [JsonPropertyName("voices")]
- public List Voices { get; set; }
- }
-}
\ No newline at end of file
diff --git a/AnimeON/Models/Voice.cs b/AnimeON/Models/Voice.cs
deleted file mode 100644
index 4c25ce7..0000000
--- a/AnimeON/Models/Voice.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-
-namespace AnimeON.Models
-{
- public class Voice
- {
- [JsonPropertyName("id")]
- public int Id { get; set; }
-
- [JsonPropertyName("name")]
- public string Name { get; set; }
-
- [JsonPropertyName("players")]
- public List Players { get; set; }
- }
-
- public class VoicePlayer
- {
- [JsonPropertyName("id")]
- public int Id { get; set; }
-
- [JsonPropertyName("name")]
- public string Name { get; set; }
- }
-}
\ No newline at end of file
diff --git a/AnimeON/OnlineApi.cs b/AnimeON/OnlineApi.cs
deleted file mode 100644
index 4e164c4..0000000
--- a/AnimeON/OnlineApi.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Shared.Models.Base;
-using System.Collections.Generic;
-
-namespace AnimeON
-{
- 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.AnimeON;
-
- // Визначаємо isAnime згідно стандарту Lampac (Deepwiki):
- // isanime = true якщо original_language == "ja" або "zh"
- bool hasLang = !string.IsNullOrEmpty(original_language);
- bool isanime = hasLang && (original_language == "ja" || original_language == "zh");
-
- // AnimeON — аніме-провайдер. Додаємо його:
- // - при загальному пошуку (serial == -1), або
- // - якщо контент визначений як аніме (isanime), або
- // - якщо мова невідома (відсутній original_language)
- if (init.enable && !init.rip && (serial == -1 || isanime || !hasLang))
- {
- string url = init.overridehost;
- if (string.IsNullOrEmpty(url))
- url = $"{host}/animeon";
-
- online.Add((init.displayname, url, "animeon", init.displayindex));
- }
-
- return online;
- }
- }
-}
diff --git a/AnimeON/manifest.json b/AnimeON/manifest.json
deleted file mode 100644
index 86348fc..0000000
--- a/AnimeON/manifest.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "enable": true,
- "version": 2,
- "initspace": "AnimeON.ModInit",
- "online": "AnimeON.OnlineApi"
-}
\ No newline at end of file
diff --git a/Bamboo/Bamboo.csproj b/Bamboo/Bamboo.csproj
deleted file mode 100644
index 1fbe365..0000000
--- a/Bamboo/Bamboo.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- net9.0
- library
- true
-
-
-
-
- ..\..\Shared.dll
-
-
-
-
diff --git a/Bamboo/BambooInvoke.cs b/Bamboo/BambooInvoke.cs
deleted file mode 100644
index 8818135..0000000
--- a/Bamboo/BambooInvoke.cs
+++ /dev/null
@@ -1,355 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using System.Web;
-using Bamboo.Models;
-using HtmlAgilityPack;
-using Shared;
-using Shared.Engine;
-using Shared.Models;
-using Shared.Models.Online.Settings;
-
-namespace Bamboo
-{
- public class BambooInvoke
- {
- private static readonly HashSet NotAllowedHosts =
- new HashSet(
- new[]
- {
- "c3ZpdGFubW92aWU=",
- "cG9ydGFsLXR2",
- }
- .Select(base64 => Encoding.UTF8.GetString(Convert.FromBase64String(base64))),
- StringComparer.OrdinalIgnoreCase
- );
- private readonly OnlinesSettings _init;
- private readonly HybridCache _hybridCache;
- private readonly Action _onLog;
- private readonly ProxyManager _proxyManager;
-
- public BambooInvoke(OnlinesSettings init, HybridCache hybridCache, Action onLog, ProxyManager proxyManager)
- {
- _init = init;
- _hybridCache = hybridCache;
- _onLog = onLog;
- _proxyManager = proxyManager;
- }
-
- public async Task> Search(string title, string original_title)
- {
- string query = !string.IsNullOrEmpty(title) ? title : original_title;
- if (string.IsNullOrEmpty(query))
- return null;
-
- string memKey = $"Bamboo:search:{query}";
- if (_hybridCache.TryGetValue(memKey, out List cached))
- return cached;
-
- try
- {
- string searchUrl = $"{_init.host}/index.php?do=search&subaction=search&story={HttpUtility.UrlEncode(query)}";
- if (IsNotAllowedHost(searchUrl))
- return null;
-
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", _init.host)
- };
-
- _onLog?.Invoke($"Bamboo search: {searchUrl}");
- string html = await Http.Get(searchUrl, headers: headers, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(html))
- return null;
-
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- var results = new List();
- var nodes = doc.DocumentNode.SelectNodes("//li[contains(@class,'slide-item')]");
- if (nodes != null)
- {
- foreach (var node in nodes)
- {
- string itemTitle = CleanText(node.SelectSingleNode(".//h6")?.InnerText);
- string href = ExtractHref(node);
- string poster = ExtractPoster(node);
-
- if (string.IsNullOrEmpty(itemTitle) || string.IsNullOrEmpty(href))
- continue;
-
- results.Add(new SearchResult
- {
- Title = itemTitle,
- Url = href,
- Poster = poster
- });
- }
- }
-
- if (results.Count > 0)
- _hybridCache.Set(memKey, results, cacheTime(20, init: _init));
-
- return results;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"Bamboo search error: {ex.Message}");
- return null;
- }
- }
-
- public async Task GetSeriesEpisodes(string href)
- {
- if (string.IsNullOrEmpty(href))
- return null;
-
- string memKey = $"Bamboo:series:{href}";
- if (_hybridCache.TryGetValue(memKey, out SeriesEpisodes cached))
- return cached;
-
- try
- {
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", _init.host)
- };
-
- if (IsNotAllowedHost(href))
- return null;
-
- _onLog?.Invoke($"Bamboo series page: {href}");
- string html = await Http.Get(href, headers: headers, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(html))
- return null;
-
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- var result = new SeriesEpisodes();
- bool foundBlocks = false;
-
- var blocks = doc.DocumentNode.SelectNodes("//div[contains(@class,'mt-4')]");
- if (blocks != null)
- {
- foreach (var block in blocks)
- {
- string header = CleanText(block.SelectSingleNode(".//h3[contains(@class,'my-4')]")?.InnerText);
- var episodes = ParseEpisodeSpans(block);
- if (episodes.Count == 0)
- continue;
-
- foundBlocks = true;
- if (!string.IsNullOrEmpty(header) && header.Contains("Субтитри", StringComparison.OrdinalIgnoreCase))
- {
- result.Sub.AddRange(episodes);
- }
- else if (!string.IsNullOrEmpty(header) && header.Contains("Озвучення", StringComparison.OrdinalIgnoreCase))
- {
- result.Dub.AddRange(episodes);
- }
- }
- }
-
- if (!foundBlocks || (result.Sub.Count == 0 && result.Dub.Count == 0))
- {
- var fallback = ParseEpisodeSpans(doc.DocumentNode);
- if (fallback.Count > 0)
- result.Dub.AddRange(fallback);
- }
-
- if (result.Sub.Count == 0)
- {
- var fallback = ParseEpisodeSpans(doc.DocumentNode);
- if (fallback.Count > 0)
- result.Sub.AddRange(fallback);
- }
-
- _hybridCache.Set(memKey, result, cacheTime(30, init: _init));
- return result;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"Bamboo series error: {ex.Message}");
- return null;
- }
- }
-
- public async Task> GetMovieStreams(string href)
- {
- if (string.IsNullOrEmpty(href))
- return null;
-
- string memKey = $"Bamboo:movie:{href}";
- if (_hybridCache.TryGetValue(memKey, out List cached))
- return cached;
-
- try
- {
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", _init.host)
- };
-
- if (IsNotAllowedHost(href))
- return null;
-
- _onLog?.Invoke($"Bamboo movie page: {href}");
- string html = await Http.Get(href, headers: headers, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(html))
- return null;
-
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- var streams = new List();
- var nodes = doc.DocumentNode.SelectNodes("//span[contains(@class,'mr-3') and @data-file]");
- if (nodes != null)
- {
- foreach (var node in nodes)
- {
- string dataFile = node.GetAttributeValue("data-file", "");
- if (string.IsNullOrEmpty(dataFile))
- continue;
-
- string title = node.GetAttributeValue("data-title", "");
- title = string.IsNullOrEmpty(title) ? CleanText(node.InnerText) : title;
-
- streams.Add(new StreamInfo
- {
- Title = title,
- Url = NormalizeUrl(dataFile)
- });
- }
- }
-
- if (streams.Count > 0)
- _hybridCache.Set(memKey, streams, cacheTime(30, init: _init));
-
- return streams;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"Bamboo movie error: {ex.Message}");
- return null;
- }
- }
-
- private List ParseEpisodeSpans(HtmlNode scope)
- {
- var episodes = new List();
- var nodes = scope.SelectNodes(".//span[@data-file]");
- if (nodes == null)
- return episodes;
-
- foreach (var node in nodes)
- {
- string dataFile = node.GetAttributeValue("data-file", "");
- if (string.IsNullOrEmpty(dataFile))
- continue;
-
- string title = node.GetAttributeValue("data-title", "");
- if (string.IsNullOrEmpty(title))
- title = CleanText(node.InnerText);
-
- int? episodeNum = ExtractEpisodeNumber(title);
-
- episodes.Add(new EpisodeInfo
- {
- Title = string.IsNullOrEmpty(title) ? "Episode" : title,
- Url = NormalizeUrl(dataFile),
- Episode = episodeNum
- });
- }
-
- return episodes;
- }
-
- private string ExtractHref(HtmlNode node)
- {
- var link = node.SelectSingleNode(".//a[contains(@class,'hover-buttons')]")
- ?? node.SelectSingleNode(".//a[@href]");
- if (link == null)
- return string.Empty;
-
- string href = link.GetAttributeValue("href", "");
- return NormalizeUrl(href);
- }
-
- private string ExtractPoster(HtmlNode node)
- {
- var img = node.SelectSingleNode(".//img");
- if (img == null)
- return string.Empty;
-
- string src = img.GetAttributeValue("src", "");
- if (string.IsNullOrEmpty(src))
- src = img.GetAttributeValue("data-src", "");
-
- return NormalizeUrl(src);
- }
-
- private string NormalizeUrl(string url)
- {
- if (string.IsNullOrEmpty(url))
- return string.Empty;
-
- if (url.StartsWith("//"))
- return IsNotAllowedHost($"https:{url}") ? string.Empty : $"https:{url}";
-
- if (url.StartsWith("/"))
- return IsNotAllowedHost(_init.host) ? string.Empty : $"{_init.host}{url}";
-
- return IsNotAllowedHost(url) ? string.Empty : url;
- }
-
- 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);
- }
-
- private static int? ExtractEpisodeNumber(string title)
- {
- if (string.IsNullOrEmpty(title))
- return null;
-
- var match = Regex.Match(title, @"(\d+)");
- if (match.Success && int.TryParse(match.Groups[1].Value, out int value))
- return value;
-
- return null;
- }
-
- private static string CleanText(string value)
- {
- if (string.IsNullOrEmpty(value))
- return string.Empty;
-
- return HtmlEntity.DeEntitize(value).Trim();
- }
-
- public static TimeSpan cacheTime(int multiaccess, int home = 5, int mikrotik = 2, OnlinesSettings init = null, int rhub = -1)
- {
- if (init != null && init.rhub && rhub != -1)
- return TimeSpan.FromMinutes(rhub);
-
- int ctime = AppInit.conf.mikrotik ? mikrotik : AppInit.conf.multiaccess ? init != null && init.cache_time > 0 ? init.cache_time : multiaccess : home;
- if (ctime > multiaccess)
- ctime = multiaccess;
-
- return TimeSpan.FromMinutes(ctime);
- }
- }
-}
diff --git a/Bamboo/Controller.cs b/Bamboo/Controller.cs
deleted file mode 100644
index 34365af..0000000
--- a/Bamboo/Controller.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Web;
-using Bamboo.Models;
-using Microsoft.AspNetCore.Mvc;
-using Shared;
-using Shared.Engine;
-using Shared.Models;
-using Shared.Models.Online.Settings;
-using Shared.Models.Templates;
-
-namespace Bamboo.Controllers
-{
- public class Controller : BaseOnlineController
- {
- ProxyManager proxyManager;
-
- public Controller()
- {
- proxyManager = new ProxyManager(ModInit.Bamboo);
- }
-
- [HttpGet]
- [Route("bamboo")]
- 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, string href = null)
- {
- var init = await loadKit(ModInit.Bamboo);
- if (!init.enable)
- return Forbid();
-
- var invoke = new BambooInvoke(init, hybridCache, OnLog, proxyManager);
-
- string itemUrl = href;
- if (string.IsNullOrEmpty(itemUrl))
- {
- var searchResults = await invoke.Search(title, original_title);
- if (searchResults == null || searchResults.Count == 0)
- return OnError("bamboo", proxyManager);
-
- if (searchResults.Count > 1)
- {
- var similar_tpl = new SimilarTpl(searchResults.Count);
- foreach (var res in searchResults)
- {
- string link = $"{host}/bamboo?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(res.Url)}";
- similar_tpl.Append(res.Title, string.Empty, string.Empty, link, res.Poster);
- }
-
- return rjson ? Content(similar_tpl.ToJson(), "application/json; charset=utf-8") : Content(similar_tpl.ToHtml(), "text/html; charset=utf-8");
- }
-
- itemUrl = searchResults[0].Url;
- }
-
- if (serial == 1)
- {
- var series = await invoke.GetSeriesEpisodes(itemUrl);
- if (series == null || (series.Sub.Count == 0 && series.Dub.Count == 0))
- return OnError("bamboo", proxyManager);
-
- var voice_tpl = new VoiceTpl();
- var episode_tpl = new EpisodeTpl();
-
- var availableVoices = new List<(string key, string name, List episodes)>();
- if (series.Sub.Count > 0)
- availableVoices.Add(("sub", "Субтитри", series.Sub));
- if (series.Dub.Count > 0)
- availableVoices.Add(("dub", "Озвучення", series.Dub));
-
- if (string.IsNullOrEmpty(t))
- t = availableVoices.First().key;
-
- foreach (var voice in availableVoices)
- {
- string voiceLink = $"{host}/bamboo?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&t={voice.key}&href={HttpUtility.UrlEncode(itemUrl)}";
- voice_tpl.Append(voice.name, voice.key == t, voiceLink);
- }
-
- var selected = availableVoices.FirstOrDefault(v => v.key == t);
- if (selected.episodes == null || selected.episodes.Count == 0)
- return OnError("bamboo", proxyManager);
-
- int index = 1;
- foreach (var ep in selected.episodes.OrderBy(e => e.Episode ?? int.MaxValue))
- {
- int episodeNumber = ep.Episode ?? index;
- string episodeName = string.IsNullOrEmpty(ep.Title) ? $"Епізод {episodeNumber}" : ep.Title;
- string streamUrl = HostStreamProxy(init, accsArgs(ep.Url));
- episode_tpl.Append(episodeName, title ?? original_title, "1", episodeNumber.ToString("D2"), streamUrl);
- index++;
- }
-
- 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 streams = await invoke.GetMovieStreams(itemUrl);
- if (streams == null || streams.Count == 0)
- return OnError("bamboo", proxyManager);
-
- var movie_tpl = new MovieTpl(title, original_title);
- for (int i = 0; i < streams.Count; i++)
- {
- var stream = streams[i];
- string label = !string.IsNullOrEmpty(stream.Title) ? stream.Title : $"Варіант {i + 1}";
- string streamUrl = HostStreamProxy(init, accsArgs(stream.Url));
- movie_tpl.Append(label, streamUrl);
- }
-
- return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8");
- }
- }
- }
-}
diff --git a/Bamboo/ModInit.cs b/Bamboo/ModInit.cs
deleted file mode 100644
index d7994d5..0000000
--- a/Bamboo/ModInit.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Shared;
-using Shared.Engine;
-using Shared.Models.Online.Settings;
-using Shared.Models.Module;
-
-namespace Bamboo
-{
- public class ModInit
- {
- public static OnlinesSettings Bamboo;
-
- ///
- /// модуль загружен
- ///
- public static void loaded(InitspaceModel initspace)
- {
- Bamboo = new OnlinesSettings("Bamboo", "https://bambooua.com", streamproxy: false, useproxy: false)
- {
- displayname = "BambooUA",
- displayindex = 0,
- proxy = new Shared.Models.Base.ProxySettings()
- {
- useAuth = true,
- username = "",
- password = "",
- list = new string[] { "socks5://ip:port" }
- }
- };
- Bamboo = ModuleInvoke.Conf("Bamboo", Bamboo).ToObject();
-
- // Виводити "уточнити пошук"
- AppInit.conf.online.with_search.Add("bamboo");
- }
- }
-}
diff --git a/Bamboo/Models/BambooModels.cs b/Bamboo/Models/BambooModels.cs
deleted file mode 100644
index af99cb7..0000000
--- a/Bamboo/Models/BambooModels.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System.Collections.Generic;
-
-namespace Bamboo.Models
-{
- public class SearchResult
- {
- public string Title { get; set; }
- public string Url { get; set; }
- public string Poster { get; set; }
- }
-
- public class EpisodeInfo
- {
- public string Title { get; set; }
- public string Url { get; set; }
- public int? Episode { get; set; }
- }
-
- public class StreamInfo
- {
- public string Title { get; set; }
- public string Url { get; set; }
- }
-
- public class SeriesEpisodes
- {
- public List Sub { get; set; } = new();
- public List Dub { get; set; } = new();
- }
-}
diff --git a/Bamboo/OnlineApi.cs b/Bamboo/OnlineApi.cs
deleted file mode 100644
index 5becaac..0000000
--- a/Bamboo/OnlineApi.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Shared.Models.Base;
-using System.Collections.Generic;
-
-namespace Bamboo
-{
- 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.Bamboo;
- if (init.enable && !init.rip)
- {
- string url = init.overridehost;
- if (string.IsNullOrEmpty(url))
- url = $"{host}/bamboo";
-
- online.Add((init.displayname, url, "bamboo", init.displayindex));
- }
-
- return online;
- }
- }
-}
diff --git a/Bamboo/manifest.json b/Bamboo/manifest.json
deleted file mode 100644
index 933863e..0000000
--- a/Bamboo/manifest.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "enable": true,
- "version": 2,
- "initspace": "Bamboo.ModInit",
- "online": "Bamboo.OnlineApi"
-}
diff --git a/CikavaIdeya/CikavaIdeya.csproj b/CikavaIdeya/CikavaIdeya.csproj
deleted file mode 100644
index 7befc43..0000000
--- a/CikavaIdeya/CikavaIdeya.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- net9.0
- library
- true
-
-
-
-
- ..\..\Shared.dll
-
-
-
-
\ No newline at end of file
diff --git a/CikavaIdeya/CikavaIdeyaInvoke.cs b/CikavaIdeya/CikavaIdeyaInvoke.cs
deleted file mode 100644
index 1fcb9c6..0000000
--- a/CikavaIdeya/CikavaIdeyaInvoke.cs
+++ /dev/null
@@ -1,396 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using Shared;
-using Shared.Models.Online.Settings;
-using Shared.Models;
-using System.Text.RegularExpressions;
-using System.Text;
-using HtmlAgilityPack;
-using CikavaIdeya.Models;
-using Shared.Engine;
-using System.Linq;
-
-namespace CikavaIdeya
-{
- public class CikavaIdeyaInvoke
- {
- private static readonly HashSet NotAllowedHosts =
- new HashSet(
- new[]
- {
- "c3ZpdGFubW92aWU=",
- "cG9ydGFsLXR2",
- }
- .Select(base64 => Encoding.UTF8.GetString(Convert.FromBase64String(base64))),
- StringComparer.OrdinalIgnoreCase
- );
- private OnlinesSettings _init;
- private HybridCache _hybridCache;
- private Action _onLog;
- private ProxyManager _proxyManager;
-
- public CikavaIdeyaInvoke(OnlinesSettings init, HybridCache hybridCache, Action onLog, ProxyManager proxyManager)
- {
- _init = init;
- _hybridCache = hybridCache;
- _onLog = onLog;
- _proxyManager = proxyManager;
- }
-
- public async Task> Search(string imdb_id, long kinopoisk_id, string title, string original_title, int year, bool isfilm = false)
- {
- string filmTitle = !string.IsNullOrEmpty(title) ? title : original_title;
- string memKey = $"CikavaIdeya:search:{filmTitle}:{year}:{isfilm}";
- if (_hybridCache.TryGetValue(memKey, out List res))
- return res;
-
- try
- {
- // Спочатку шукаємо по title
- res = await PerformSearch(title, year);
-
- // Якщо нічого не знайдено і є original_title, шукаємо по ньому
- if ((res == null || res.Count == 0) && !string.IsNullOrEmpty(original_title) && original_title != title)
- {
- _onLog($"No results for '{title}', trying search by original title '{original_title}'");
- res = await PerformSearch(original_title, year);
- // Оновлюємо ключ кешу для original_title
- if (res != null && res.Count > 0)
- {
- memKey = $"CikavaIdeya:search:{original_title}:{year}:{isfilm}";
- }
- }
-
- if (res != null && res.Count > 0)
- {
- _hybridCache.Set(memKey, res, cacheTime(20));
- return res;
- }
- }
- catch (Exception ex)
- {
- _onLog($"CikavaIdeya search error: {ex.Message}");
- }
- return null;
- }
-
- async Task> PerformSearch(string searchTitle, int year)
- {
- try
- {
- string searchUrl = $"{_init.host}/index.php?do=search&subaction=search&story={System.Web.HttpUtility.UrlEncode(searchTitle)}";
- var headers = new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) };
-
- if (IsNotAllowedHost(searchUrl))
- return null;
-
- var searchHtml = await Http.Get(searchUrl, headers: headers, proxy: _proxyManager.Get());
- // Перевіряємо, чи є результати пошуку
- if (searchHtml.Contains("На жаль, пошук на сайті не дав жодних результатів"))
- {
- _onLog($"No search results for '{searchTitle}'");
- return new List();
- }
-
- var doc = new HtmlDocument();
- doc.LoadHtml(searchHtml);
-
- var filmNodes = doc.DocumentNode.SelectNodes("//div[@class='th-item']");
- if (filmNodes == null)
- {
- _onLog($"No film nodes found for '{searchTitle}'");
- return new List();
- }
-
- string filmUrl = null;
- foreach (var filmNode in filmNodes)
- {
- var titleNode = filmNode.SelectSingleNode(".//div[@class='th-title']");
- if (titleNode == null || !titleNode.InnerText.Trim().ToLower().Contains(searchTitle.ToLower())) continue;
-
- var descNode = filmNode.SelectSingleNode(".//div[@class='th-subtitle']");
- if (year > 0 && (descNode?.InnerText ?? "").Contains(year.ToString()))
- {
- var linkNode = filmNode.SelectSingleNode(".//a[@class='th-in']");
- if (linkNode != null)
- {
- filmUrl = linkNode.GetAttributeValue("href", "");
- break;
- }
- }
- }
-
- if (filmUrl == null)
- {
- var firstNode = filmNodes.FirstOrDefault()?.SelectSingleNode(".//a[@class='th-in']");
- if (firstNode != null)
- filmUrl = firstNode.GetAttributeValue("href", "");
- }
-
- if (filmUrl == null)
- {
- _onLog($"No film URL found for '{searchTitle}'");
- return new List();
- }
-
- if (!filmUrl.StartsWith("http"))
- filmUrl = _init.host + filmUrl;
-
- // Отримуємо список епізодів (для фільмів - один епізод, для серіалів - всі епізоди)
- if (IsNotAllowedHost(filmUrl))
- return null;
-
- var filmHtml = await Http.Get(filmUrl, headers: headers, proxy: _proxyManager.Get());
- // Перевіряємо, чи не видалено контент
- if (filmHtml.Contains("Видалено на прохання правовласника"))
- {
- _onLog($"Content removed on copyright holder request: {filmUrl}");
- return new List();
- }
-
- doc.LoadHtml(filmHtml);
-
- // Знаходимо JavaScript з даними про епізоди
- var scriptNodes = doc.DocumentNode.SelectNodes("//script");
- if (scriptNodes != null)
- {
- foreach (var scriptNode in scriptNodes)
- {
- var scriptContent = scriptNode.InnerText;
- if (scriptContent.Contains("switches = Object"))
- {
- _onLog($"Found switches script: {scriptContent}");
- // Парсимо структуру switches
- var match = Regex.Match(scriptContent, @"switches = Object\((\{.*\})\);", RegexOptions.Singleline);
- if (match.Success)
- {
- string switchesJson = match.Groups[1].Value;
- _onLog($"Parsed switches JSON: {switchesJson}");
- // Спрощений парсинг JSON-подібної структури
- var res = ParseSwitchesJson(switchesJson, _init.host, filmUrl);
- _onLog($"Parsed episodes count: {res.Count}");
- foreach (var ep in res)
- {
- _onLog($"Episode: season={ep.season}, episode={ep.episode}, title={ep.title}, url={ep.url}");
- }
- return res;
- }
- }
- }
- }
- }
- catch (Exception ex)
- {
- _onLog($"PerformSearch error for '{searchTitle}': {ex.Message}");
- }
- return new List();
- }
-
- List ParseSwitchesJson(string json, string host, string baseUrl)
- {
- var result = new List();
-
- try
- {
- _onLog($"Parsing switches JSON: {json}");
- // Спрощений парсинг JSON-подібної структури
- // Приклад для серіалу: {"Player1":{"1 сезон":{"1 серія":"https://ashdi.vip/vod/57364",...},"2 сезон":{"1 серія":"https://ashdi.vip/vod/118170",...}}}
- // Приклад для фільму: {"Player1":"https://ashdi.vip/vod/162246"}
-
- // Знаходимо плеєр Player1
- // Спочатку спробуємо знайти об'єкт Player1
- var playerObjectMatch = Regex.Match(json, @"""Player1""\s*:\s*(\{(?:[^{}]|(?\{)|(?<-open>\}))+(?(open)(?!)))", RegexOptions.Singleline);
- if (playerObjectMatch.Success)
- {
- string playerContent = playerObjectMatch.Groups[1].Value;
- _onLog($"Player1 object content: {playerContent}");
-
- // Це серіал, парсимо сезони
- var seasonMatches = Regex.Matches(playerContent, @"""([^""]+?сезон[^""]*?)""\s*:\s*\{((?:[^{}]|(?\{)|(?<-open>\}))+(?(open)(?!)))\}", RegexOptions.Singleline);
- _onLog($"Found {seasonMatches.Count} seasons");
- foreach (Match seasonMatch in seasonMatches)
- {
- string seasonName = seasonMatch.Groups[1].Value;
- string seasonContent = seasonMatch.Groups[2].Value;
- _onLog($"Season: {seasonName}, Content: {seasonContent}");
-
- // Витягуємо номер сезону
- var seasonNumMatch = Regex.Match(seasonName, @"(\d+)");
- int seasonNum = seasonNumMatch.Success ? int.Parse(seasonNumMatch.Groups[1].Value) : 1;
- _onLog($"Season number: {seasonNum}");
-
- // Парсимо епізоди
- var episodeMatches = Regex.Matches(seasonContent, @"""([^""]+?)""\s*:\s*""([^""]+?)""", RegexOptions.Singleline);
- _onLog($"Found {episodeMatches.Count} episodes in season {seasonNum}");
- foreach (Match episodeMatch in episodeMatches)
- {
- string episodeName = episodeMatch.Groups[1].Value;
- string episodeUrl = episodeMatch.Groups[2].Value;
- _onLog($"Episode: {episodeName}, URL: {episodeUrl}");
-
- // Витягуємо номер епізоду
- var episodeNumMatch = Regex.Match(episodeName, @"(\d+)");
- int episodeNum = episodeNumMatch.Success ? int.Parse(episodeNumMatch.Groups[1].Value) : 1;
-
- result.Add(new CikavaIdeya.Models.EpisodeLinkInfo
- {
- url = episodeUrl,
- title = episodeName,
- season = seasonNum,
- episode = episodeNum
- });
- }
- }
- }
- else
- {
- // Якщо не знайшли об'єкт, спробуємо знайти просте значення
- var playerStringMatch = Regex.Match(json, @"""Player1""\s*:\s*(""([^""]+)"")", RegexOptions.Singleline);
- if (playerStringMatch.Success)
- {
- string playerContent = playerStringMatch.Groups[1].Value;
- _onLog($"Player1 string content: {playerContent}");
-
- // Якщо це фільм (просте значення)
- if (playerContent.StartsWith("\"") && playerContent.EndsWith("\""))
- {
- string filmUrl = playerContent.Trim('"');
- result.Add(new CikavaIdeya.Models.EpisodeLinkInfo
- {
- url = filmUrl,
- title = "Фільм",
- season = 1,
- episode = 1
- });
- }
- }
- else
- {
- _onLog("Player1 not found");
- }
- }
- }
- catch (Exception ex)
- {
- _onLog($"ParseSwitchesJson error: {ex.Message}");
- }
-
- return result;
- }
-
- public async Task ParseEpisode(string url)
- {
- var result = new CikavaIdeya.Models.PlayResult() { streams = new List<(string, string)>() };
- try
- {
- // Якщо це вже iframe URL (наприклад, з switches), повертаємо його
- if (url.Contains("ashdi.vip"))
- {
- _onLog($"ParseEpisode: URL contains ashdi.vip, calling GetStreamUrlFromAshdi");
- string streamUrl = await GetStreamUrlFromAshdi(url);
- _onLog($"ParseEpisode: GetStreamUrlFromAshdi returned {streamUrl}");
- if (!string.IsNullOrEmpty(streamUrl))
- {
- result.streams.Add((streamUrl, "hls"));
- _onLog($"ParseEpisode: added stream URL to result.streams");
- return result;
- }
- // Якщо не вдалося отримати посилання на поток, повертаємо iframe URL
- _onLog($"ParseEpisode: stream URL is null or empty, setting iframe_url");
- result.iframe_url = url;
- return result;
- }
-
- // Інакше парсимо сторінку
- if (IsNotAllowedHost(url))
- return result;
-
- string html = await Http.Get(url, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- var iframe = doc.DocumentNode.SelectSingleNode("//div[@class='video-box']//iframe");
- if (iframe != null)
- {
- string iframeUrl = iframe.GetAttributeValue("src", "").Replace("&", "&");
- if (iframeUrl.StartsWith("//"))
- iframeUrl = "https:" + iframeUrl;
-
- result.iframe_url = iframeUrl;
- return result;
- }
- }
- catch (Exception ex)
- {
- _onLog($"ParseEpisode error: {ex.Message}");
- }
- return result;
- }
- public async Task GetStreamUrlFromAshdi(string url)
- {
- try
- {
- _onLog($"GetStreamUrlFromAshdi: trying to get stream URL from {url}");
- var headers = new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") };
- if (IsNotAllowedHost(url))
- return null;
-
- string html = await Http.Get(url, headers: headers, proxy: _proxyManager.Get());
- _onLog($"GetStreamUrlFromAshdi: received HTML, length={html.Length}");
-
- // Знаходимо JavaScript код з об'єктом player
- var match = Regex.Match(html, @"var\s+player\s*=\s*new\s+Playerjs[\s\S]*?\(\s*({[\s\S]*?})\s*\)", RegexOptions.Multiline | RegexOptions.IgnoreCase);
- if (match.Success)
- {
- _onLog($"GetStreamUrlFromAshdi: found player object");
- string playerJson = match.Groups[1].Value;
- _onLog($"GetStreamUrlFromAshdi: playerJson={playerJson}");
- // Знаходимо поле file
- var fileMatch = Regex.Match(playerJson, @"file\s*:\s*['""]([^'""]+)['""]", RegexOptions.Multiline | RegexOptions.IgnoreCase);
- if (fileMatch.Success)
- {
- _onLog($"GetStreamUrlFromAshdi: found file URL: {fileMatch.Groups[1].Value}");
- return fileMatch.Groups[1].Value;
- }
- else
- {
- _onLog($"GetStreamUrlFromAshdi: file URL not found in playerJson");
- }
- }
- else
- {
- _onLog($"GetStreamUrlFromAshdi: player object not found in HTML");
- }
- }
- catch (Exception ex)
- {
- _onLog($"GetStreamUrlFromAshdi 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);
- }
-
- public static TimeSpan cacheTime(int multiaccess, int home = 5, int mikrotik = 2, OnlinesSettings init = null, int rhub = -1)
- {
- if (init != null && init.rhub && rhub != -1)
- return TimeSpan.FromMinutes(rhub);
-
- int ctime = AppInit.conf.mikrotik ? mikrotik : AppInit.conf.multiaccess ? init != null && init.cache_time > 0 ? init.cache_time : multiaccess : home;
- if (ctime > multiaccess)
- ctime = multiaccess;
-
- return TimeSpan.FromMinutes(ctime);
- }
- }
-}
diff --git a/CikavaIdeya/Controller.cs b/CikavaIdeya/Controller.cs
deleted file mode 100644
index cd1ca0c..0000000
--- a/CikavaIdeya/Controller.cs
+++ /dev/null
@@ -1,417 +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 HtmlAgilityPack;
-using Shared;
-using Shared.Models.Templates;
-using System.Text.RegularExpressions;
-using System.Text;
-using Shared.Models.Online.Settings;
-using Shared.Models;
-using CikavaIdeya.Models;
-
-namespace CikavaIdeya.Controllers
-{
- public class Controller : BaseOnlineController
- {
- private static readonly HashSet NotAllowedHosts =
- new HashSet(
- new[]
- {
- "c3ZpdGFubW92aWU=",
- "cG9ydGFsLXR2",
- }
- .Select(base64 => Encoding.UTF8.GetString(Convert.FromBase64String(base64))),
- StringComparer.OrdinalIgnoreCase
- );
- ProxyManager proxyManager;
-
- public Controller()
- {
- proxyManager = new ProxyManager(ModInit.CikavaIdeya);
- }
-
- [HttpGet]
- [Route("cikavaideya")]
- 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 e = -1, bool play = false, bool rjson = false)
- {
- var init = await loadKit(ModInit.CikavaIdeya);
- if (!init.enable)
- return Forbid();
-
- var invoke = new CikavaIdeyaInvoke(init, hybridCache, OnLog, proxyManager);
-
- var episodesInfo = await invoke.Search(imdb_id, kinopoisk_id, title, original_title, year, serial == 0);
- if (episodesInfo == null)
- return Content("CikavaIdeya", "text/html; charset=utf-8");
-
- if (play)
- {
- var episode = episodesInfo.FirstOrDefault(ep => ep.season == s && ep.episode == e);
- if (serial == 0) // для фильма берем первый
- episode = episodesInfo.FirstOrDefault();
-
- if (episode == null)
- return Content("CikavaIdeya", "text/html; charset=utf-8");
-
- OnLog($"Controller: calling invoke.ParseEpisode with URL: {episode.url}");
- var playResult = await invoke.ParseEpisode(episode.url);
- OnLog($"Controller: invoke.ParseEpisode returned playResult with streams.Count={playResult.streams?.Count ?? 0}, iframe_url={playResult.iframe_url}");
-
- if (playResult.streams != null && playResult.streams.Count > 0)
- {
- string streamLink = playResult.streams.First().link;
- string streamUrl = HostStreamProxy(init, accsArgs(streamLink));
- OnLog($"Controller: redirecting to stream URL: {streamUrl}");
- return Redirect(streamUrl);
- }
-
- if (!string.IsNullOrEmpty(playResult.iframe_url))
- {
- OnLog($"Controller: redirecting to iframe URL: {playResult.iframe_url}");
- // Для CikavaIdeya ми просто повертаємо iframe URL
- return Redirect(playResult.iframe_url);
- }
-
- if (playResult.streams != null && playResult.streams.Count > 0)
- return Redirect(HostStreamProxy(init, accsArgs(playResult.streams.First().link)));
-
- return Content("CikavaIdeya", "text/html; charset=utf-8");
- }
-
- if (serial == 1)
- {
- if (s == -1) // Выбор сезона
- {
- var seasons = episodesInfo.GroupBy(ep => ep.season).ToDictionary(k => k.Key, v => v.ToList());
- OnLog($"Grouped seasons count: {seasons.Count}");
- foreach (var season in seasons)
- {
- OnLog($"Season {season.Key}: {season.Value.Count} episodes");
- }
- var season_tpl = new SeasonTpl(seasons.Count);
- foreach (var season in seasons.OrderBy(i => i.Key))
- {
- string link = $"{host}/cikavaideya?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={season.Key}";
- season_tpl.Append($"Сезон {season.Key}", link, $"{season.Key}");
- }
- OnLog("Before generating season template HTML");
- string htmlContent = season_tpl.ToHtml();
- OnLog($"Season template HTML: {htmlContent}");
- return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(htmlContent, "text/html; charset=utf-8");
- }
-
- // Выбор эпизода
- var episodes = episodesInfo.Where(ep => ep.season == s).OrderBy(ep => ep.episode).ToList();
- var movie_tpl = new MovieTpl(title, original_title, episodes.Count);
- foreach(var ep in episodes)
- {
- string link = $"{host}/cikavaideya?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&e={ep.episode}&play=true";
- movie_tpl.Append(ep.title, accsArgs(link), method: "play");
- }
- return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8");
- }
- else // Фильм
- {
- string link = $"{host}/cikavaideya?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&play=true";
- var tpl = new MovieTpl(title, original_title, 1);
- tpl.Append(title, accsArgs(link), method: "play");
- return rjson ? Content(tpl.ToJson(), "application/json; charset=utf-8") : Content(tpl.ToHtml(), "text/html; charset=utf-8");
- }
- }
-
- async ValueTask> search(OnlinesSettings init, string imdb_id, long kinopoisk_id, string title, string original_title, int year, bool isfilm = false)
- {
- string filmTitle = !string.IsNullOrEmpty(title) ? title : original_title;
- string memKey = $"CikavaIdeya:search:{filmTitle}:{year}:{isfilm}";
- if (hybridCache.TryGetValue(memKey, out List res))
- return res;
-
- try
- {
- // Спочатку шукаємо по title
- res = await PerformSearch(init, title, year);
-
- // Якщо нічого не знайдено і є original_title, шукаємо по ньому
- if ((res == null || res.Count == 0) && !string.IsNullOrEmpty(original_title) && original_title != title)
- {
- OnLog($"No results for '{title}', trying search by original title '{original_title}'");
- res = await PerformSearch(init, original_title, year);
- // Оновлюємо ключ кешу для original_title
- if (res != null && res.Count > 0)
- {
- memKey = $"CikavaIdeya:search:{original_title}:{year}:{isfilm}";
- }
- }
-
- if (res != null && res.Count > 0)
- {
- hybridCache.Set(memKey, res, cacheTime(20));
- return res;
- }
- }
- catch (Exception ex)
- {
- OnLog($"CikavaIdeya search error: {ex.Message}");
- }
- return null;
- }
-
- async Task> PerformSearch(OnlinesSettings init, string searchTitle, int year)
- {
- try
- {
- string searchUrl = $"{init.host}/index.php?do=search&subaction=search&story={HttpUtility.UrlEncode(searchTitle)}";
- var headers = new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) };
-
- if (IsNotAllowedHost(searchUrl))
- return null;
-
- var searchHtml = await Http.Get(searchUrl, headers: headers);
- // Перевіряємо, чи є результати пошуку
- if (searchHtml.Contains("На жаль, пошук на сайті не дав жодних результатів"))
- {
- OnLog($"No search results for '{searchTitle}'");
- return new List();
- }
-
- var doc = new HtmlDocument();
- doc.LoadHtml(searchHtml);
-
- var filmNodes = doc.DocumentNode.SelectNodes("//div[@class='th-item']");
- if (filmNodes == null)
- {
- OnLog($"No film nodes found for '{searchTitle}'");
- return new List();
- }
-
- string filmUrl = null;
- foreach (var filmNode in filmNodes)
- {
- var titleNode = filmNode.SelectSingleNode(".//div[@class='th-title']");
- if (titleNode == null || !titleNode.InnerText.Trim().ToLower().Contains(searchTitle.ToLower())) continue;
-
- var descNode = filmNode.SelectSingleNode(".//div[@class='th-subtitle']");
- if (year > 0 && (descNode?.InnerText ?? "").Contains(year.ToString()))
- {
- var linkNode = filmNode.SelectSingleNode(".//a[@class='th-in']");
- if (linkNode != null)
- {
- filmUrl = linkNode.GetAttributeValue("href", "");
- break;
- }
- }
- }
-
- if (filmUrl == null)
- {
- var firstNode = filmNodes.FirstOrDefault()?.SelectSingleNode(".//a[@class='th-in']");
- if (firstNode != null)
- filmUrl = firstNode.GetAttributeValue("href", "");
- }
-
- if (filmUrl == null)
- {
- OnLog($"No film URL found for '{searchTitle}'");
- return new List();
- }
-
- if (!filmUrl.StartsWith("http"))
- filmUrl = init.host + filmUrl;
-
- // Отримуємо список епізодів (для фільмів - один епізод, для серіалів - всі епізоди)
- if (IsNotAllowedHost(filmUrl))
- return null;
-
- var filmHtml = await Http.Get(filmUrl, headers: headers);
- // Перевіряємо, чи не видалено контент
- if (filmHtml.Contains("Видалено на прохання правовласника"))
- {
- OnLog($"Content removed on copyright holder request: {filmUrl}");
- return new List();
- }
-
- doc.LoadHtml(filmHtml);
-
- // Знаходимо JavaScript з даними про епізоди
- var scriptNodes = doc.DocumentNode.SelectNodes("//script");
- if (scriptNodes != null)
- {
- foreach (var scriptNode in scriptNodes)
- {
- var scriptContent = scriptNode.InnerText;
- if (scriptContent.Contains("switches = Object"))
- {
- OnLog($"Found switches script: {scriptContent}");
- // Парсимо структуру switches
- var match = Regex.Match(scriptContent, @"switches = Object\((\{.*\})\);", RegexOptions.Singleline);
- if (match.Success)
- {
- string switchesJson = match.Groups[1].Value;
- OnLog($"Parsed switches JSON: {switchesJson}");
- // Спрощений парсинг JSON-подібної структури
- var res = ParseSwitchesJson(switchesJson, init.host, filmUrl);
- OnLog($"Parsed episodes count: {res.Count}");
- foreach (var ep in res)
- {
- OnLog($"Episode: season={ep.season}, episode={ep.episode}, title={ep.title}, url={ep.url}");
- }
- return res;
- }
- }
- }
- }
- }
- catch (Exception ex)
- {
- OnLog($"PerformSearch error for '{searchTitle}': {ex.Message}");
- }
- return new List();
- }
-
- List ParseSwitchesJson(string json, string host, string baseUrl)
- {
- var result = new List();
-
- try
- {
- OnLog($"Parsing switches JSON: {json}");
- // Спрощений парсинг JSON-подібної структури
- // Приклад для серіалу: {"Player1":{"1 сезон":{"1 серія":"https://ashdi.vip/vod/57364",...},"2 сезон":{"1 серія":"https://ashdi.vip/vod/118170",...}}}
- // Приклад для фільму: {"Player1":"https://ashdi.vip/vod/162246"}
-
- // Знаходимо плеєр Player1
- // Спочатку спробуємо знайти об'єкт Player1
- var playerObjectMatch = Regex.Match(json, @"""Player1""\s*:\s*(\{(?:[^{}]|(?\{)|(?<-open>\}))+(?(open)(?!)))", RegexOptions.Singleline);
- if (playerObjectMatch.Success)
- {
- string playerContent = playerObjectMatch.Groups[1].Value;
- OnLog($"Player1 object content: {playerContent}");
-
- // Це серіал, парсимо сезони
- var seasonMatches = Regex.Matches(playerContent, @"""([^""]+?сезон[^""]*?)""\s*:\s*\{((?:[^{}]|(?\{)|(?<-open>\}))+(?(open)(?!)))\}", RegexOptions.Singleline);
- OnLog($"Found {seasonMatches.Count} seasons");
- foreach (Match seasonMatch in seasonMatches)
- {
- string seasonName = seasonMatch.Groups[1].Value;
- string seasonContent = seasonMatch.Groups[2].Value;
- OnLog($"Season: {seasonName}, Content: {seasonContent}");
-
- // Витягуємо номер сезону
- var seasonNumMatch = Regex.Match(seasonName, @"(\d+)");
- int seasonNum = seasonNumMatch.Success ? int.Parse(seasonNumMatch.Groups[1].Value) : 1;
- OnLog($"Season number: {seasonNum}");
-
- // Парсимо епізоди
- var episodeMatches = Regex.Matches(seasonContent, @"""([^""]+?)""\s*:\s*""([^""]+?)""", RegexOptions.Singleline);
- OnLog($"Found {episodeMatches.Count} episodes in season {seasonNum}");
- foreach (Match episodeMatch in episodeMatches)
- {
- string episodeName = episodeMatch.Groups[1].Value;
- string episodeUrl = episodeMatch.Groups[2].Value;
- OnLog($"Episode: {episodeName}, URL: {episodeUrl}");
-
- // Витягуємо номер епізоду
- var episodeNumMatch = Regex.Match(episodeName, @"(\d+)");
- int episodeNum = episodeNumMatch.Success ? int.Parse(episodeNumMatch.Groups[1].Value) : 1;
-
- result.Add(new EpisodeLinkInfo
- {
- url = episodeUrl,
- title = episodeName,
- season = seasonNum,
- episode = episodeNum
- });
- }
- }
- }
- else
- {
- // Якщо не знайшли об'єкт, спробуємо знайти просте значення
- var playerStringMatch = Regex.Match(json, @"""Player1""\s*:\s*(""([^""]+)"")", RegexOptions.Singleline);
- if (playerStringMatch.Success)
- {
- string playerContent = playerStringMatch.Groups[1].Value;
- OnLog($"Player1 string content: {playerContent}");
-
- // Якщо це фільм (просте значення)
- if (playerContent.StartsWith("\"") && playerContent.EndsWith("\""))
- {
- string filmUrl = playerContent.Trim('"');
- result.Add(new EpisodeLinkInfo
- {
- url = filmUrl,
- title = "Фільм",
- season = 1,
- episode = 1
- });
- }
- }
- else
- {
- OnLog("Player1 not found");
- }
- }
- }
- catch (Exception ex)
- {
- OnLog($"ParseSwitchesJson error: {ex.Message}");
- }
-
- return result;
- }
-
- async Task ParseEpisode(OnlinesSettings init, string url)
- {
- var result = new PlayResult() { streams = new List<(string, string)>() };
- try
- {
- // Якщо це вже iframe URL (наприклад, з switches), повертаємо його
- if (url.Contains("ashdi.vip"))
- {
- result.iframe_url = url;
- return result;
- }
-
- // Інакше парсимо сторінку
- if (IsNotAllowedHost(url))
- return result;
-
- string html = await Http.Get(url, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- var iframe = doc.DocumentNode.SelectSingleNode("//div[@class='video-box']//iframe");
- if (iframe != null)
- {
- string iframeUrl = iframe.GetAttributeValue("src", "").Replace("&", "&");
- if (iframeUrl.StartsWith("//"))
- iframeUrl = "https:" + iframeUrl;
-
- result.iframe_url = iframeUrl;
- return result;
- }
- }
- catch (Exception ex)
- {
- OnLog($"ParseEpisode error: {ex.Message}");
- }
- return result;
- }
-
- 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);
- }
- }
-}
diff --git a/CikavaIdeya/ModInit.cs b/CikavaIdeya/ModInit.cs
deleted file mode 100644
index 678cb77..0000000
--- a/CikavaIdeya/ModInit.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using Newtonsoft.Json;
-using Shared;
-using Shared.Engine;
-using Newtonsoft.Json.Linq;
-using Shared;
-using Shared.Models.Online.Settings;
-using Shared.Models.Module;
-
-using Newtonsoft.Json;
-using Shared;
-using Shared.Engine;
-using Newtonsoft.Json.Linq;
-
-namespace CikavaIdeya
-{
- public class ModInit
- {
- public static OnlinesSettings CikavaIdeya;
-
- ///
- /// модуль загружен
- ///
- public static void loaded(InitspaceModel initspace)
- {
- CikavaIdeya = new OnlinesSettings("CikavaIdeya", "https://cikava-ideya.top", streamproxy: false, useproxy: false)
- {
- displayname = "ЦікаваІдея",
- displayindex = 0,
- proxy = new Shared.Models.Base.ProxySettings()
- {
- useAuth = true,
- username = "a",
- password = "a",
- list = new string[] { "socks5://IP:PORT" }
- }
- };
- CikavaIdeya = ModuleInvoke.Conf("CikavaIdeya", CikavaIdeya).ToObject();
-
- // Виводити "уточнити пошук"
- AppInit.conf.online.with_search.Add("cikavaideya");
- }
- }
-}
\ No newline at end of file
diff --git a/CikavaIdeya/Models/EpisodeModel.cs b/CikavaIdeya/Models/EpisodeModel.cs
deleted file mode 100644
index 46d0a11..0000000
--- a/CikavaIdeya/Models/EpisodeModel.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace CikavaIdeya.Models
-{
- public class EpisodeModel
- {
- [JsonPropertyName("episode_number")]
- public int EpisodeNumber { get; set; }
-
- [JsonPropertyName("title")]
- public string Title { get; set; }
-
- [JsonPropertyName("url")]
- public string Url { get; set; }
- }
-}
\ No newline at end of file
diff --git a/CikavaIdeya/Models/Models.cs b/CikavaIdeya/Models/Models.cs
deleted file mode 100644
index 188475f..0000000
--- a/CikavaIdeya/Models/Models.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace CikavaIdeya.Models
-{
- public class EpisodeLinkInfo
- {
- public string url { get; set; }
- public string title { get; set; }
- public int season { get; set; }
- public int episode { get; set; }
- }
-
- public class PlayResult
- {
- public string iframe_url { get; set; }
- public List<(string link, string quality)> streams { get; set; }
- }
-}
\ No newline at end of file
diff --git a/CikavaIdeya/Models/PlayerModel.cs b/CikavaIdeya/Models/PlayerModel.cs
deleted file mode 100644
index 3e871f9..0000000
--- a/CikavaIdeya/Models/PlayerModel.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-
-namespace CikavaIdeya.Models
-{
- public class CikavaIdeyaPlayerModel
- {
- [JsonPropertyName("name")]
- public string Name { get; set; }
-
- [JsonPropertyName("qualities")]
- public List<(string link, string quality)> Qualities { get; set; }
-
- [JsonPropertyName("subtitles")]
- public Shared.Models.Templates.SubtitleTpl? Subtitles { get; set; }
- }
-}
\ No newline at end of file
diff --git a/CikavaIdeya/Models/SeasonModel.cs b/CikavaIdeya/Models/SeasonModel.cs
deleted file mode 100644
index 570c7e0..0000000
--- a/CikavaIdeya/Models/SeasonModel.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-
-namespace CikavaIdeya.Models
-{
- public class SeasonModel
- {
- [JsonPropertyName("season_number")]
- public int SeasonNumber { get; set; }
-
- [JsonPropertyName("episodes")]
- public List Episodes { get; set; }
- }
-}
\ No newline at end of file
diff --git a/CikavaIdeya/OnlineApi.cs b/CikavaIdeya/OnlineApi.cs
deleted file mode 100644
index 474fe1a..0000000
--- a/CikavaIdeya/OnlineApi.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Shared.Models.Base;
-using System.Collections.Generic;
-
-namespace CikavaIdeya
-{
- 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.CikavaIdeya;
-
- // Визначення isAnime згідно Lampac (Deepwiki): original_language == "ja" або "zh"
- bool hasLang = !string.IsNullOrEmpty(original_language);
- bool isanime = hasLang && (original_language == "ja" || original_language == "zh");
-
- // CikavaIdeya — не-аніме провайдер. Додаємо якщо:
- // - загальний пошук (serial == -1), або
- // - контент НЕ аніме (!isanime), або
- // - мова невідома (немає original_language)
- if (init.enable && !init.rip && (serial == -1 || !isanime || !hasLang))
- {
- string url = init.overridehost;
- if (string.IsNullOrEmpty(url))
- url = $"{host}/cikavaideya";
-
- online.Add((init.displayname, url, "cikavaideya", init.displayindex));
- }
-
- return online;
- }
- }
-}
\ No newline at end of file
diff --git a/CikavaIdeya/manifest.json b/CikavaIdeya/manifest.json
deleted file mode 100644
index f03df58..0000000
--- a/CikavaIdeya/manifest.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "enable": true,
- "version": 2,
- "initspace": "CikavaIdeya.ModInit",
- "online": "CikavaIdeya.OnlineApi"
-}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 261eeb9..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/README.md b/README.md
deleted file mode 100644
index 3df6fdf..0000000
--- a/README.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Ukraine online source for Lampac
-
-- [x] AnimeON
-- [x] [RIP] AniHUB
-- [x] BambooUA
-- [x] CikavaIdeya
-- [x] StarLight
-- [x] UAKino
-- [x] UAFlix
-- [x] UATuTFun
-- [x] Unimay
-
-
-## Installation
-
-1. Clone the repository:
- ```bash
- git clone https://github.com/lampac-ukraine/lampac-ukraine.git .
- ```
-
-2. Move the modules to the correct directory:
- - If Lampac is installed system-wide, move the modules to the `module` directory.
- - If Lampac is running in Docker, mount the volume:
- ```bash
- -v /path/to/your/cloned/repo/Uaflix:/home/module/Uaflix
- ```
-
-## Auto installation
-
-If Lampac version 148.1 and newer
-
-Create or update the module/repository.yaml file
-
-```YAML
-- repository: https://github.com/lampame/lampac-ukraine
- branch: main
- modules:
- - AnimeON
- - Anihub
- - Unimay
- - CikavaIdeya
- - Uaflix
- - UaTUT
- - Bamboo
- - UAKino
- - StarLight
-```
-
-branch - optional, default main
-
-modules - optional, if not specified, all modules from the repository will be installed
-
-## Init support
-
-```json
-"Uaflix": {
- "enable": true,
- "domain": "https://uaflix.net",
- "displayname": "Uaflix",
- "streamproxy": false,
- "useproxy": false,
- "proxy": {
- "useAuth": true,
- "username": "FooBAR",
- "password": "Strong_password",
- "list": [
- "socks5://adress:port"
- ]
- },
- "displayindex": 1
- }
-```
-
-## Donate
-
-Support the author: https://lampame.donatik.me
diff --git a/StarLight/Controller.cs b/StarLight/Controller.cs
deleted file mode 100644
index 63dc423..0000000
--- a/StarLight/Controller.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Web;
-using Microsoft.AspNetCore.Mvc;
-using Shared;
-using Shared.Engine;
-using Shared.Models.Online.Settings;
-using Shared.Models.Templates;
-using StarLight.Models;
-
-namespace StarLight.Controllers
-{
- public class Controller : BaseOnlineController
- {
- ProxyManager proxyManager;
-
- public Controller()
- {
- proxyManager = new ProxyManager(ModInit.StarLight);
- }
-
- [HttpGet]
- [Route("starlight")]
- 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, int s = -1, bool rjson = false, string href = null)
- {
- var init = await loadKit(ModInit.StarLight);
- if (!init.enable)
- return Forbid();
-
- var invoke = new StarLightInvoke(init, hybridCache, OnLog, proxyManager);
-
- string itemUrl = href;
- if (string.IsNullOrEmpty(itemUrl))
- {
- var searchResults = await invoke.Search(title, original_title);
- if (searchResults == null || searchResults.Count == 0)
- return OnError("starlight", proxyManager);
-
- if (searchResults.Count > 1)
- {
- var similar_tpl = new SimilarTpl(searchResults.Count);
- foreach (var res in searchResults)
- {
- string link = $"{host}/starlight?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(res.Href)}";
- similar_tpl.Append(res.Title, string.Empty, string.Empty, link, string.Empty);
- }
-
- return rjson ? Content(similar_tpl.ToJson(), "application/json; charset=utf-8") : Content(similar_tpl.ToHtml(), "text/html; charset=utf-8");
- }
-
- itemUrl = searchResults[0].Href;
- }
-
- var project = await invoke.GetProject(itemUrl);
- if (project == null)
- return OnError("starlight", proxyManager);
-
- if (serial == 1 && project.Seasons.Count > 0)
- {
- if (s == -1)
- {
- var season_tpl = new SeasonTpl(project.Seasons.Count);
- for (int i = 0; i < project.Seasons.Count; i++)
- {
- var seasonInfo = project.Seasons[i];
- string seasonName = string.IsNullOrEmpty(seasonInfo.Title) ? $"Сезон {i + 1}" : seasonInfo.Title;
- string link = $"{host}/starlight?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={i}&href={HttpUtility.UrlEncode(itemUrl)}";
- season_tpl.Append(seasonName, link, i.ToString());
- }
-
- return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8");
- }
-
- if (s < 0 || s >= project.Seasons.Count)
- return OnError("starlight", proxyManager);
-
- var season = project.Seasons[s];
- string seasonSlug = season.Slug;
- var episodes = invoke.GetEpisodes(project, seasonSlug);
- if (episodes == null || episodes.Count == 0)
- return OnError("starlight", proxyManager);
-
- var episode_tpl = new EpisodeTpl();
- int index = 1;
- string seasonNumber = GetSeasonNumber(season, s);
- foreach (var ep in episodes)
- {
- if (string.IsNullOrEmpty(ep.Hash))
- continue;
-
- string episodeName = string.IsNullOrEmpty(ep.Title) ? $"Епізод {index}" : ep.Title;
- string callUrl = $"{host}/starlight/play?hash={HttpUtility.UrlEncode(ep.Hash)}&title={HttpUtility.UrlEncode(title ?? original_title)}";
- episode_tpl.Append(episodeName, title ?? original_title, seasonNumber, index.ToString("D2"), accsArgs(callUrl), "call");
- index++;
- }
-
- return rjson ? Content(episode_tpl.ToJson(), "application/json; charset=utf-8") : Content(episode_tpl.ToHtml(), "text/html; charset=utf-8");
- }
- else
- {
- string hash = project.Hash;
- if (string.IsNullOrEmpty(hash) && project.Episodes.Count > 0)
- hash = project.Episodes.FirstOrDefault(e => !string.IsNullOrEmpty(e.Hash))?.Hash;
-
- if (string.IsNullOrEmpty(hash))
- return OnError("starlight", proxyManager);
-
- string callUrl = $"{host}/starlight/play?hash={HttpUtility.UrlEncode(hash)}&title={HttpUtility.UrlEncode(title ?? original_title)}";
- var movie_tpl = new MovieTpl(title, original_title, 1);
- movie_tpl.Append(string.IsNullOrEmpty(title) ? "StarLight" : title, accsArgs(callUrl), "call");
-
- return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8");
- }
- }
-
- [HttpGet]
- [Route("starlight/play")]
- async public Task Play(string hash, string title)
- {
- if (string.IsNullOrEmpty(hash))
- return OnError("starlight", proxyManager);
-
- var init = await loadKit(ModInit.StarLight);
- if (!init.enable)
- return Forbid();
-
- var invoke = new StarLightInvoke(init, hybridCache, OnLog, proxyManager);
- var result = await invoke.ResolveStream(hash);
- if (result == null || string.IsNullOrEmpty(result.Stream))
- return OnError("starlight", proxyManager);
-
- string streamUrl = HostStreamProxy(init, accsArgs(result.Stream), proxy: proxyManager.Get());
- string jsonResult = $"{{\"method\":\"play\",\"url\":\"{streamUrl}\",\"title\":\"{title ?? result.Name ?? ""}\"}}";
- return Content(jsonResult, "application/json; charset=utf-8");
- }
-
- private static string GetSeasonNumber(SeasonInfo season, int fallbackIndex)
- {
- if (season?.Title == null)
- return (fallbackIndex + 1).ToString();
-
- var digits = new string(season.Title.Where(char.IsDigit).ToArray());
- return string.IsNullOrEmpty(digits) ? (fallbackIndex + 1).ToString() : digits;
- }
- }
-}
diff --git a/StarLight/ModInit.cs b/StarLight/ModInit.cs
deleted file mode 100644
index ac0df4b..0000000
--- a/StarLight/ModInit.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Shared;
-using Shared.Engine;
-using Shared.Models.Online.Settings;
-using Shared.Models.Module;
-
-namespace StarLight
-{
- public class ModInit
- {
- public static OnlinesSettings StarLight;
-
- ///
- /// модуль загружен
- ///
- public static void loaded(InitspaceModel initspace)
- {
- StarLight = new OnlinesSettings("StarLight", "https://tp-back.starlight.digital", streamproxy: false, useproxy: false)
- {
- displayname = "StarLight",
- displayindex = 0,
- proxy = new Shared.Models.Base.ProxySettings()
- {
- useAuth = true,
- username = "",
- password = "",
- list = new string[] { "socks5://ip:port" }
- }
- };
- StarLight = ModuleInvoke.Conf("StarLight", StarLight).ToObject();
-
- // Виводити "уточнити пошук"
- AppInit.conf.online.with_search.Add("starlight");
- }
- }
-}
diff --git a/StarLight/Models/StarLightModels.cs b/StarLight/Models/StarLightModels.cs
deleted file mode 100644
index 90142aa..0000000
--- a/StarLight/Models/StarLightModels.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System.Collections.Generic;
-
-namespace StarLight.Models
-{
- public class SearchResult
- {
- public string Title { get; set; }
- public string Type { get; set; }
- public string Href { get; set; }
- public string Channel { get; set; }
- public string Project { get; set; }
- }
-
- public class SeasonInfo
- {
- public string Title { get; set; }
- public string Slug { get; set; }
- }
-
- public class EpisodeInfo
- {
- public string Title { get; set; }
- public string Hash { get; set; }
- public string VideoSlug { get; set; }
- public string Date { get; set; }
- public string SeasonSlug { get; set; }
- }
-
- public class ProjectInfo
- {
- public string Title { get; set; }
- public string Description { get; set; }
- public string Poster { get; set; }
- public string Hash { get; set; }
- public string Type { get; set; }
- public string Channel { get; set; }
- public List Seasons { get; set; } = new();
- public List Episodes { get; set; } = new();
- }
-
- public class StreamResult
- {
- public string Stream { get; set; }
- public string Poster { get; set; }
- public string Name { get; set; }
- }
-}
diff --git a/StarLight/OnlineApi.cs b/StarLight/OnlineApi.cs
deleted file mode 100644
index 20bb35b..0000000
--- a/StarLight/OnlineApi.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Shared.Models.Base;
-using System;
-using System.Collections.Generic;
-
-namespace StarLight
-{
- 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)>();
-
- if (!string.Equals(original_language, "uk", StringComparison.OrdinalIgnoreCase))
- return online;
-
- var init = ModInit.StarLight;
- if (init.enable && !init.rip)
- {
- string url = init.overridehost;
- if (string.IsNullOrEmpty(url))
- url = $"{host}/starlight";
-
- online.Add((init.displayname, url, "starlight", init.displayindex));
- }
-
- return online;
- }
- }
-}
diff --git a/StarLight/StarLight.csproj b/StarLight/StarLight.csproj
deleted file mode 100644
index 1fbe365..0000000
--- a/StarLight/StarLight.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- net9.0
- library
- true
-
-
-
-
- ..\..\Shared.dll
-
-
-
-
diff --git a/StarLight/StarLightInvoke.cs b/StarLight/StarLightInvoke.cs
deleted file mode 100644
index 76c78ea..0000000
--- a/StarLight/StarLightInvoke.cs
+++ /dev/null
@@ -1,361 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.Json;
-using System.Threading.Tasks;
-using System.Web;
-using Shared;
-using Shared.Engine;
-using Shared.Models;
-using Shared.Models.Online.Settings;
-using StarLight.Models;
-
-namespace StarLight
-{
- public class StarLightInvoke
- {
- private const string PlayerApi = "https://vcms-api2.starlight.digital/player-api";
- private const string PlayerReferer = "https://teleportal.ua/";
- private const string Language = "ua";
- private static readonly HashSet NotAllowedHosts =
- new HashSet(
- new[]
- {
- "c3ZpdGFubW92aWU=",
- "cG9ydGFsLXR2",
- }
- .Select(base64 => Encoding.UTF8.GetString(Convert.FromBase64String(base64))),
- StringComparer.OrdinalIgnoreCase
- );
- private readonly OnlinesSettings _init;
- private readonly HybridCache _hybridCache;
- private readonly Action _onLog;
- private readonly ProxyManager _proxyManager;
-
- public StarLightInvoke(OnlinesSettings init, HybridCache hybridCache, Action onLog, ProxyManager proxyManager)
- {
- _init = init;
- _hybridCache = hybridCache;
- _onLog = onLog;
- _proxyManager = proxyManager;
- }
-
- public async Task> Search(string title, string original_title)
- {
- string query = !string.IsNullOrEmpty(title) ? title : original_title;
- if (string.IsNullOrEmpty(query))
- return null;
-
- string memKey = $"StarLight:search:{query}";
- if (_hybridCache.TryGetValue(memKey, out List cached))
- return cached;
-
- string url = $"{_init.host}/{Language}/live-search?q={HttpUtility.UrlEncode(query)}";
- if (IsNotAllowedHost(url))
- return null;
-
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", _init.host)
- };
-
- try
- {
- _onLog?.Invoke($"StarLight search: {url}");
- string payload = await Http.Get(url, headers: headers, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(payload))
- return null;
-
- var results = new List();
- using var document = JsonDocument.Parse(payload);
- if (document.RootElement.ValueKind == JsonValueKind.Array)
- {
- foreach (var item in document.RootElement.EnumerateArray())
- {
- string typeSlug = item.TryGetProperty("typeSlug", out var typeProp) ? typeProp.GetString() : null;
- string channelSlug = item.TryGetProperty("channelSlug", out var channelProp) ? channelProp.GetString() : null;
- string projectSlug = item.TryGetProperty("projectSlug", out var projectProp) ? projectProp.GetString() : null;
- if (string.IsNullOrEmpty(typeSlug) || string.IsNullOrEmpty(channelSlug) || string.IsNullOrEmpty(projectSlug))
- continue;
-
- string href = $"{_init.host}/{Language}/{typeSlug}/{channelSlug}/{projectSlug}";
- results.Add(new SearchResult
- {
- Title = item.TryGetProperty("title", out var titleProp) ? titleProp.GetString() : null,
- Type = typeSlug,
- Href = href,
- Channel = channelSlug,
- Project = projectSlug
- });
- }
- }
-
- if (results.Count > 0)
- _hybridCache.Set(memKey, results, cacheTime(15, init: _init));
-
- return results;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"StarLight search error: {ex.Message}");
- return null;
- }
- }
-
- public async Task GetProject(string href)
- {
- if (string.IsNullOrEmpty(href))
- return null;
-
- string memKey = $"StarLight:project:{href}";
- if (_hybridCache.TryGetValue(memKey, out ProjectInfo cached))
- return cached;
-
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", _init.host)
- };
-
- try
- {
- if (IsNotAllowedHost(href))
- return null;
-
- _onLog?.Invoke($"StarLight project: {href}");
- string payload = await Http.Get(href, headers: headers, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(payload))
- return null;
-
- using var document = JsonDocument.Parse(payload);
- var root = document.RootElement;
-
- var project = new ProjectInfo
- {
- Title = root.TryGetProperty("title", out var titleProp) ? titleProp.GetString() : null,
- Description = root.TryGetProperty("description", out var descProp) ? descProp.GetString() : null,
- Poster = NormalizeImage(root.TryGetProperty("image", out var imageProp) ? imageProp.GetString() : null),
- Hash = root.TryGetProperty("hash", out var hashProp) ? hashProp.GetString() : null,
- Type = root.TryGetProperty("typeSlug", out var typeProp) ? typeProp.GetString() : null,
- Channel = root.TryGetProperty("channelTitle", out var channelProp) ? channelProp.GetString() : null
- };
-
- if (root.TryGetProperty("seasons", out var seasonsListProp) && seasonsListProp.ValueKind == JsonValueKind.Array)
- {
- foreach (var seasonItem in seasonsListProp.EnumerateArray())
- {
- string seasonTitle = seasonItem.TryGetProperty("title", out var sTitle) ? sTitle.GetString() : null;
- string seasonSlug = seasonItem.TryGetProperty("seasonSlug", out var sSlug) ? sSlug.GetString() : null;
- AddSeason(project, seasonTitle, seasonSlug);
- }
- }
-
- if (root.TryGetProperty("seasonsGallery", out var seasonsProp) && seasonsProp.ValueKind == JsonValueKind.Array)
- {
- foreach (var seasonItem in seasonsProp.EnumerateArray())
- {
- string seasonTitle = seasonItem.TryGetProperty("title", out var sTitle) ? sTitle.GetString() : null;
- string seasonSlug = seasonItem.TryGetProperty("seasonSlug", out var sSlug) ? sSlug.GetString() : null;
- AddSeason(project, seasonTitle, seasonSlug);
-
- if (seasonItem.TryGetProperty("items", out var itemsProp) && itemsProp.ValueKind == JsonValueKind.Array)
- {
- foreach (var item in itemsProp.EnumerateArray())
- {
- project.Episodes.Add(new EpisodeInfo
- {
- Title = item.TryGetProperty("title", out var eTitle) ? eTitle.GetString() : null,
- Hash = item.TryGetProperty("hash", out var eHash) ? eHash.GetString() : null,
- VideoSlug = item.TryGetProperty("videoSlug", out var eSlug) ? eSlug.GetString() : null,
- Date = item.TryGetProperty("dateOfBroadcast", out var eDate) ? eDate.GetString() : (item.TryGetProperty("timeUploadVideo", out var eDate2) ? eDate2.GetString() : null),
- SeasonSlug = seasonSlug
- });
- }
- }
- }
- }
-
- await LoadMissingSeasonEpisodes(project, href, headers);
-
- _hybridCache.Set(memKey, project, cacheTime(10, init: _init));
- return project;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"StarLight project error: {ex.Message}");
- return null;
- }
- }
-
- private async Task LoadMissingSeasonEpisodes(ProjectInfo project, string href, List headers)
- {
- if (project == null || string.IsNullOrEmpty(href))
- return;
-
- var missing = project.Seasons
- .Where(s => !string.IsNullOrEmpty(s.Slug))
- .Where(s => !project.Episodes.Any(e => e.SeasonSlug == s.Slug))
- .ToList();
-
- foreach (var seasonInfo in missing)
- {
- string seasonUrl = $"{href}/{seasonInfo.Slug}";
- try
- {
- _onLog?.Invoke($"StarLight season: {seasonUrl}");
- string payload = await Http.Get(seasonUrl, headers: headers, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(payload))
- continue;
-
- using var document = JsonDocument.Parse(payload);
- var root = document.RootElement;
-
- if (root.TryGetProperty("items", out var itemsProp) && itemsProp.ValueKind == JsonValueKind.Array)
- {
- foreach (var item in itemsProp.EnumerateArray())
- {
- string hash = item.TryGetProperty("hash", out var eHash) ? eHash.GetString() : null;
- if (string.IsNullOrEmpty(hash))
- continue;
-
- if (project.Episodes.Any(e => e.SeasonSlug == seasonInfo.Slug && e.Hash == hash))
- continue;
-
- project.Episodes.Add(new EpisodeInfo
- {
- Title = item.TryGetProperty("title", out var eTitle) ? eTitle.GetString() : null,
- Hash = hash,
- VideoSlug = item.TryGetProperty("videoSlug", out var eSlug) ? eSlug.GetString() : null,
- Date = item.TryGetProperty("dateOfBroadcast", out var eDate) ? eDate.GetString() : (item.TryGetProperty("timeUploadVideo", out var eDate2) ? eDate2.GetString() : null),
- SeasonSlug = seasonInfo.Slug
- });
- }
- }
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"StarLight season error: {ex.Message}");
- }
- }
- }
-
- private static void AddSeason(ProjectInfo project, string title, string slug)
- {
- if (project == null || string.IsNullOrEmpty(slug))
- return;
-
- if (project.Seasons.Any(s => s.Slug == slug))
- return;
-
- project.Seasons.Add(new SeasonInfo { Title = title, Slug = slug });
- }
-
- public List GetEpisodes(ProjectInfo project, string seasonSlug)
- {
- if (project == null || project.Seasons.Count == 0)
- return new List();
-
- if (string.IsNullOrEmpty(seasonSlug))
- return project.Episodes;
-
- return project.Episodes.Where(e => e.SeasonSlug == seasonSlug).ToList();
- }
-
- public async Task ResolveStream(string hash)
- {
- if (string.IsNullOrEmpty(hash))
- return null;
-
- string url = $"{PlayerApi}/{hash}?referer={HttpUtility.UrlEncode(PlayerReferer)}&lang={Language}";
- if (IsNotAllowedHost(url))
- return null;
-
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", PlayerReferer)
- };
-
- try
- {
- _onLog?.Invoke($"StarLight stream: {url}");
- string payload = await Http.Get(url, headers: headers, proxy: _proxyManager.Get());
- if (string.IsNullOrEmpty(payload))
- return null;
-
- using var document = JsonDocument.Parse(payload);
- var root = document.RootElement;
-
- string stream = null;
- if (root.TryGetProperty("video", out var videoProp) && videoProp.ValueKind == JsonValueKind.Array)
- {
- var video = videoProp.EnumerateArray().FirstOrDefault();
- if (video.ValueKind != JsonValueKind.Undefined)
- {
- if (video.TryGetProperty("mediaHlsNoAdv", out var hlsNoAdv))
- stream = hlsNoAdv.GetString();
- if (string.IsNullOrEmpty(stream) && video.TryGetProperty("mediaHls", out var hls))
- stream = hls.GetString();
- if (string.IsNullOrEmpty(stream) && video.TryGetProperty("media", out var mediaProp) && mediaProp.ValueKind == JsonValueKind.Array)
- {
- var media = mediaProp.EnumerateArray().FirstOrDefault();
- if (media.TryGetProperty("url", out var mediaUrl))
- stream = mediaUrl.GetString();
- }
- }
- }
-
- if (string.IsNullOrEmpty(stream))
- return null;
-
- return new StreamResult
- {
- Stream = stream,
- Poster = root.TryGetProperty("poster", out var posterProp) ? posterProp.GetString() : null,
- Name = root.TryGetProperty("name", out var nameProp) ? nameProp.GetString() : null
- };
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"StarLight stream error: {ex.Message}");
- return null;
- }
- }
-
- private string NormalizeImage(string path)
- {
- if (string.IsNullOrEmpty(path))
- return string.Empty;
-
- if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- return IsNotAllowedHost(path) ? string.Empty : path;
-
- return IsNotAllowedHost(_init.host) ? string.Empty : $"{_init.host}{path}";
- }
-
- 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);
- }
-
- public static TimeSpan cacheTime(int multiaccess, int home = 5, int mikrotik = 2, OnlinesSettings init = null, int rhub = -1)
- {
- if (init != null && init.rhub && rhub != -1)
- return TimeSpan.FromMinutes(rhub);
-
- int ctime = AppInit.conf.mikrotik ? mikrotik : AppInit.conf.multiaccess ? init != null && init.cache_time > 0 ? init.cache_time : multiaccess : home;
- if (ctime > multiaccess)
- ctime = multiaccess;
-
- return TimeSpan.FromMinutes(ctime);
- }
- }
-}
diff --git a/StarLight/manifest.json b/StarLight/manifest.json
deleted file mode 100644
index 1b214ae..0000000
--- a/StarLight/manifest.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "enable": true,
- "version": 2,
- "initspace": "StarLight.ModInit",
- "online": "StarLight.OnlineApi"
-}
diff --git a/UAKino/Controller.cs b/UAKino/Controller.cs
deleted file mode 100644
index 811c04b..0000000
--- a/UAKino/Controller.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Web;
-using Microsoft.AspNetCore.Mvc;
-using Shared;
-using Shared.Engine;
-using Shared.Models.Online.Settings;
-using Shared.Models.Templates;
-using UAKino.Models;
-
-namespace UAKino.Controllers
-{
- public class Controller : BaseOnlineController
- {
- ProxyManager proxyManager;
-
- public Controller()
- {
- proxyManager = new ProxyManager(ModInit.UAKino);
- }
-
- [HttpGet]
- [Route("uakino")]
- 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, bool rjson = false, string href = null)
- {
- var init = await loadKit(ModInit.UAKino);
- if (!init.enable)
- return Forbid();
-
- var invoke = new UAKinoInvoke(init, hybridCache, OnLog, proxyManager);
-
- string itemUrl = href;
- if (string.IsNullOrEmpty(itemUrl))
- {
- var searchResults = await invoke.Search(title, original_title, serial);
- if (searchResults == null || searchResults.Count == 0)
- return OnError("uakino", proxyManager);
-
- if (searchResults.Count > 1)
- {
- var similar_tpl = new SimilarTpl(searchResults.Count);
- foreach (var res in searchResults)
- {
- string link = $"{host}/uakino?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(res.Url)}";
- similar_tpl.Append(res.Title, string.Empty, string.Empty, link, res.Poster);
- }
-
- return rjson ? Content(similar_tpl.ToJson(), "application/json; charset=utf-8") : Content(similar_tpl.ToHtml(), "text/html; charset=utf-8");
- }
-
- itemUrl = searchResults[0].Url;
- }
-
- if (serial == 1)
- {
- var playlist = await invoke.GetPlaylist(itemUrl);
- if (playlist == null || playlist.Count == 0)
- return OnError("uakino", proxyManager);
-
- var voiceGroups = playlist
- .GroupBy(p => string.IsNullOrEmpty(p.Voice) ? "Основне" : p.Voice)
- .Select(g => (key: g.Key, episodes: g.ToList()))
- .ToList();
-
- if (voiceGroups.Count == 0)
- return OnError("uakino", proxyManager);
-
- if (string.IsNullOrEmpty(t))
- t = voiceGroups.First().key;
-
- var voice_tpl = new VoiceTpl();
- foreach (var voice in voiceGroups)
- {
- string voiceLink = $"{host}/uakino?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&t={HttpUtility.UrlEncode(voice.key)}&href={HttpUtility.UrlEncode(itemUrl)}";
- voice_tpl.Append(voice.key, voice.key == t, voiceLink);
- }
-
- var selected = voiceGroups.FirstOrDefault(v => v.key == t);
- if (selected.episodes == null || selected.episodes.Count == 0)
- return OnError("uakino", proxyManager);
-
- var episode_tpl = new EpisodeTpl();
- int index = 1;
- foreach (var ep in selected.episodes.OrderBy(e => UAKinoInvoke.TryParseEpisodeNumber(e.Title) ?? int.MaxValue))
- {
- int episodeNumber = UAKinoInvoke.TryParseEpisodeNumber(ep.Title) ?? index;
- string episodeName = string.IsNullOrEmpty(ep.Title) ? $"Епізод {episodeNumber}" : ep.Title;
- string callUrl = $"{host}/uakino/play?url={HttpUtility.UrlEncode(ep.Url)}&title={HttpUtility.UrlEncode(title ?? original_title)}";
- episode_tpl.Append(episodeName, title ?? original_title, "1", episodeNumber.ToString("D2"), accsArgs(callUrl), "call");
- index++;
- }
-
- 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
- {
- string playerUrl = await invoke.GetPlayerUrl(itemUrl);
- if (string.IsNullOrEmpty(playerUrl))
- {
- var playlist = await invoke.GetPlaylist(itemUrl);
- playerUrl = playlist?.FirstOrDefault()?.Url;
- }
-
- if (string.IsNullOrEmpty(playerUrl))
- return OnError("uakino", proxyManager);
-
- var movie_tpl = new MovieTpl(title, original_title);
- string callUrl = $"{host}/uakino/play?url={HttpUtility.UrlEncode(playerUrl)}&title={HttpUtility.UrlEncode(title ?? original_title)}";
- movie_tpl.Append(string.IsNullOrEmpty(title) ? "UAKino" : title, accsArgs(callUrl), "call");
-
- return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8");
- }
- }
-
- [HttpGet]
- [Route("uakino/play")]
- async public Task Play(string url, string title)
- {
- if (string.IsNullOrEmpty(url))
- return OnError("uakino", proxyManager);
-
- var init = await loadKit(ModInit.UAKino);
- if (!init.enable)
- return Forbid();
-
- var invoke = new UAKinoInvoke(init, hybridCache, OnLog, proxyManager);
- var result = await invoke.ParsePlayer(url);
- if (result == null || string.IsNullOrEmpty(result.File))
- return OnError("uakino", proxyManager);
-
- string streamUrl = HostStreamProxy(init, accsArgs(result.File), proxy: proxyManager.Get());
- string jsonResult = $"{{\"method\":\"play\",\"url\":\"{streamUrl}\",\"title\":\"{title ?? ""}\"}}";
- return Content(jsonResult, "application/json; charset=utf-8");
- }
- }
-}
diff --git a/UAKino/ModInit.cs b/UAKino/ModInit.cs
deleted file mode 100644
index 3df537f..0000000
--- a/UAKino/ModInit.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Shared;
-using Shared.Engine;
-using Shared.Models.Online.Settings;
-using Shared.Models.Module;
-
-namespace UAKino
-{
- public class ModInit
- {
- public static OnlinesSettings UAKino;
-
- ///
- /// модуль загружен
- ///
- public static void loaded(InitspaceModel initspace)
- {
- UAKino = new OnlinesSettings("UAKino", "https://uakino.best", streamproxy: false, useproxy: false)
- {
- displayname = "UAKino",
- displayindex = 0,
- proxy = new Shared.Models.Base.ProxySettings()
- {
- useAuth = true,
- username = "",
- password = "",
- list = new string[] { "socks5://ip:port" }
- }
- };
- UAKino = ModuleInvoke.Conf("UAKino", UAKino).ToObject();
-
- // Виводити "уточнити пошук"
- AppInit.conf.online.with_search.Add("uakino");
- }
- }
-}
diff --git a/UAKino/Models/UAKinoModels.cs b/UAKino/Models/UAKinoModels.cs
deleted file mode 100644
index 4b2f6ea..0000000
--- a/UAKino/Models/UAKinoModels.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System.Collections.Generic;
-
-namespace UAKino.Models
-{
- public class SearchResult
- {
- public string Title { get; set; }
- public string Url { get; set; }
- public string Poster { get; set; }
- public string Season { get; set; }
- }
-
- public class PlaylistItem
- {
- public string Title { get; set; }
- public string Url { get; set; }
- public string Voice { get; set; }
- }
-
- public class SubtitleInfo
- {
- public string Lang { get; set; }
- public string Url { get; set; }
- }
-
- public class PlayerResult
- {
- public string File { get; set; }
- public List Subtitles { get; set; } = new();
- }
-}
diff --git a/UAKino/OnlineApi.cs b/UAKino/OnlineApi.cs
deleted file mode 100644
index 7404676..0000000
--- a/UAKino/OnlineApi.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Shared.Models.Base;
-using System.Collections.Generic;
-
-namespace UAKino
-{
- 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.UAKino;
- if (init.enable && !init.rip)
- {
- string url = init.overridehost;
- if (string.IsNullOrEmpty(url))
- url = $"{host}/uakino";
-
- online.Add((init.displayname, url, "uakino", init.displayindex));
- }
-
- return online;
- }
- }
-}
diff --git a/UAKino/UAKino.csproj b/UAKino/UAKino.csproj
deleted file mode 100644
index 1fbe365..0000000
--- a/UAKino/UAKino.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- net9.0
- library
- true
-
-
-
-
- ..\..\Shared.dll
-
-
-
-
diff --git a/UAKino/UAKinoInvoke.cs b/UAKino/UAKinoInvoke.cs
deleted file mode 100644
index 2725d5e..0000000
--- a/UAKino/UAKinoInvoke.cs
+++ /dev/null
@@ -1,509 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Net.Security;
-using System.Security.Authentication;
-using System.Text;
-using System.Text.Json;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Web;
-using HtmlAgilityPack;
-using Shared;
-using Shared.Engine;
-using Shared.Models;
-using Shared.Models.Online.Settings;
-using UAKino.Models;
-
-namespace UAKino
-{
- public class UAKinoInvoke
- {
- private const string PlaylistPath = "/engine/ajax/playlists.php";
- private const string PlaylistField = "playlist";
- private const string BlacklistRegex = "(/news/)|(/franchise/)";
- private static readonly HashSet NotAllowedHosts =
- new HashSet(
- new[]
- {
- "c3ZpdGFubW92aWU=",
- "cG9ydGFsLXR2",
- }
- .Select(base64 => Encoding.UTF8.GetString(Convert.FromBase64String(base64))),
- StringComparer.OrdinalIgnoreCase
- );
- private readonly OnlinesSettings _init;
- private readonly HybridCache _hybridCache;
- private readonly Action _onLog;
- private readonly ProxyManager _proxyManager;
-
- public UAKinoInvoke(OnlinesSettings init, HybridCache hybridCache, Action onLog, ProxyManager proxyManager)
- {
- _init = init;
- _hybridCache = hybridCache;
- _onLog = onLog;
- _proxyManager = proxyManager;
- }
-
- public async Task> Search(string title, string original_title, int serial)
- {
- var queries = new List();
- if (!string.IsNullOrEmpty(title))
- queries.Add(title);
- if (!string.IsNullOrEmpty(original_title) && !queries.Contains(original_title))
- queries.Add(original_title);
-
- if (queries.Count == 0)
- return null;
-
- string memKey = $"UAKino:search:{string.Join("|", queries)}:{serial}";
- if (_hybridCache.TryGetValue(memKey, out List cached))
- return cached;
-
- var results = new List();
- var seen = new HashSet(StringComparer.OrdinalIgnoreCase);
-
- foreach (var query in queries)
- {
- try
- {
- string searchUrl = $"{_init.host}/index.php?do=search&subaction=search&story={HttpUtility.UrlEncode(query)}";
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", _init.host)
- };
-
- _onLog?.Invoke($"UAKino search: {searchUrl}");
- string html = await GetString(searchUrl, headers);
- if (string.IsNullOrEmpty(html))
- continue;
-
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- var nodes = doc.DocumentNode.SelectNodes("//div[contains(@class,'movie-item') and contains(@class,'short-item')]");
- if (nodes == null)
- continue;
-
- foreach (var node in nodes)
- {
- var titleNode = node.SelectSingleNode(".//a[contains(@class,'movie-title')] | .//a[contains(@class,'full-movie')]");
- string itemTitle = CleanText(titleNode?.InnerText);
- string href = NormalizeUrl(titleNode?.GetAttributeValue("href", ""));
- if (string.IsNullOrEmpty(itemTitle))
- {
- var altTitle = node.SelectSingleNode(".//div[contains(@class,'full-movie-title')]");
- itemTitle = CleanText(altTitle?.InnerText);
- }
-
- if (string.IsNullOrEmpty(itemTitle) || string.IsNullOrEmpty(href) || IsBlacklisted(href))
- continue;
-
- if (serial == 1 && !IsSeriesUrl(href))
- continue;
- if (serial == 0 && !IsMovieUrl(href))
- continue;
-
- string seasonText = CleanText(node.SelectSingleNode(".//div[contains(@class,'full-season')]")?.InnerText);
- if (!string.IsNullOrEmpty(seasonText) && !itemTitle.Contains(seasonText, StringComparison.OrdinalIgnoreCase))
- itemTitle = $"{itemTitle} ({seasonText})";
-
- string poster = ExtractPoster(node);
-
- if (seen.Contains(href))
- continue;
- seen.Add(href);
-
- results.Add(new SearchResult
- {
- Title = itemTitle,
- Url = href,
- Poster = poster,
- Season = seasonText
- });
- }
-
- if (results.Count > 0)
- break;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"UAKino search error: {ex.Message}");
- }
- }
-
- if (results.Count > 0)
- _hybridCache.Set(memKey, results, cacheTime(20, init: _init));
-
- return results;
- }
-
- public async Task> GetPlaylist(string href)
- {
- string newsId = ExtractNewsId(href);
- if (string.IsNullOrEmpty(newsId))
- return null;
-
- string memKey = $"UAKino:playlist:{newsId}";
- if (_hybridCache.TryGetValue(memKey, out List cached))
- return cached;
-
- string url = BuildPlaylistUrl(newsId);
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", href ?? _init.host),
- new HeadersModel("X-Requested-With", "XMLHttpRequest")
- };
-
- try
- {
- _onLog?.Invoke($"UAKino playlist: {url}");
- string payload = await GetString(url, headers);
- if (string.IsNullOrEmpty(payload))
- return null;
-
- using var document = JsonDocument.Parse(payload);
- if (!document.RootElement.TryGetProperty("success", out var successProp) || !successProp.GetBoolean())
- return null;
-
- if (!document.RootElement.TryGetProperty("response", out var responseProp))
- return null;
-
- string html = responseProp.GetString();
- if (string.IsNullOrEmpty(html))
- return null;
-
- var items = ParsePlaylistHtml(html);
- if (items.Count > 0)
- _hybridCache.Set(memKey, items, cacheTime(10, init: _init));
-
- return items;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"UAKino playlist error: {ex.Message}");
- return null;
- }
- }
-
- public async Task GetPlayerUrl(string href)
- {
- if (string.IsNullOrEmpty(href))
- return null;
-
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", _init.host)
- };
-
- try
- {
- _onLog?.Invoke($"UAKino movie page: {href}");
- string html = await GetString(href, headers);
- if (string.IsNullOrEmpty(html))
- return null;
-
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- var iframe = doc.DocumentNode.SelectSingleNode("//iframe[@id='pre']");
- if (iframe == null)
- return null;
-
- string src = iframe.GetAttributeValue("src", "");
- if (string.IsNullOrEmpty(src))
- src = iframe.GetAttributeValue("data-src", "");
-
- return NormalizeUrl(src);
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"UAKino player url error: {ex.Message}");
- return null;
- }
- }
-
- public async Task ParsePlayer(string url)
- {
- if (string.IsNullOrEmpty(url))
- return null;
-
- if (LooksLikeDirectStream(url))
- {
- return new PlayerResult { File = url };
- }
-
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", _init.host)
- };
-
- try
- {
- _onLog?.Invoke($"UAKino parse player: {url}");
- string html = await GetString(url, headers);
- if (string.IsNullOrEmpty(html))
- return null;
-
- string file = ExtractPlayerFile(html);
- if (string.IsNullOrEmpty(file))
- return null;
-
- return new PlayerResult
- {
- File = NormalizeUrl(file),
- Subtitles = ExtractSubtitles(html)
- };
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"UAKino parse player error: {ex.Message}");
- return null;
- }
- }
-
- private async Task GetString(string url, List headers, int timeoutSeconds = 15)
- {
- if (IsNotAllowedHost(url))
- return null;
-
- var handler = new SocketsHttpHandler
- {
- AllowAutoRedirect = true,
- AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip | DecompressionMethods.Deflate,
- SslOptions = new SslClientAuthenticationOptions
- {
- RemoteCertificateValidationCallback = (_, _, _, _) => true,
- EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
- }
- };
-
- var proxy = _proxyManager.Get();
- if (proxy != null)
- {
- handler.UseProxy = true;
- handler.Proxy = proxy;
- }
- else
- {
- handler.UseProxy = false;
- }
-
- using var client = new HttpClient(handler);
- using var req = new HttpRequestMessage(HttpMethod.Get, url);
-
- if (headers != null)
- {
- foreach (var h in headers)
- req.Headers.TryAddWithoutValidation(h.name, h.val);
- }
-
- using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds)));
- using var response = await client.SendAsync(req, cts.Token).ConfigureAwait(false);
- if (!response.IsSuccessStatusCode)
- return null;
-
- return await response.Content.ReadAsStringAsync(cts.Token).ConfigureAwait(false);
- }
-
- private List ParsePlaylistHtml(string html)
- {
- var items = new List();
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- var nodes = doc.DocumentNode.SelectNodes("//li[@data-file]");
- if (nodes == null)
- return items;
-
- foreach (var node in nodes)
- {
- string dataFile = node.GetAttributeValue("data-file", "");
- if (string.IsNullOrEmpty(dataFile))
- continue;
-
- string title = CleanText(node.InnerText);
- string voice = node.GetAttributeValue("data-voice", "");
-
- items.Add(new PlaylistItem
- {
- Title = string.IsNullOrEmpty(title) ? "Episode" : title,
- Url = NormalizeUrl(dataFile),
- Voice = voice
- });
- }
-
- return items;
- }
-
- private string BuildPlaylistUrl(string newsId)
- {
- long ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
- return $"{_init.host}{PlaylistPath}?news_id={newsId}&xfield={PlaylistField}&time={ts}";
- }
-
- private static string ExtractNewsId(string href)
- {
- if (string.IsNullOrEmpty(href))
- return null;
-
- string tail = href.TrimEnd('/').Split('/').LastOrDefault();
- if (string.IsNullOrEmpty(tail))
- return null;
-
- string newsId = tail.Split('-')[0];
- return string.IsNullOrEmpty(newsId) ? null : newsId;
- }
-
- private static string ExtractPlayerFile(string html)
- {
- var match = Regex.Match(html, "file\\s*:\\s*['\"]([^'\"]+)['\"]", RegexOptions.IgnoreCase);
- if (match.Success)
- {
- string value = match.Groups[1].Value.Trim();
- if (!value.StartsWith("[", StringComparison.Ordinal))
- return value;
- }
-
- var sourceMatch = Regex.Match(html, "]+src=['\"]([^'\"]+)['\"]", RegexOptions.IgnoreCase);
- if (sourceMatch.Success)
- return sourceMatch.Groups[1].Value;
-
- var m3u8Match = Regex.Match(html, "(https?://[^\"'\\s>]+\\.m3u8[^\"'\\s>]*)", RegexOptions.IgnoreCase);
- if (m3u8Match.Success)
- return m3u8Match.Groups[1].Value;
-
- return null;
- }
-
- private List ExtractSubtitles(string html)
- {
- var subtitles = new List();
- var match = Regex.Match(html, "subtitle\\s*:\\s*['\"]([^'\"]+)['\"]", RegexOptions.IgnoreCase);
- if (!match.Success)
- return subtitles;
-
- string value = match.Groups[1].Value.Trim();
- if (string.IsNullOrEmpty(value))
- return subtitles;
-
- if (value.StartsWith("[", StringComparison.Ordinal) && value.Contains(']'))
- {
- int endIdx = value.LastIndexOf(']');
- string label = value.Substring(1, endIdx - 1).Trim();
- string url = value[(endIdx + 1)..].Trim();
- url = NormalizeUrl(url);
- if (!string.IsNullOrEmpty(url))
- subtitles.Add(new SubtitleInfo { Lang = string.IsNullOrEmpty(label) ? "unknown" : label, Url = url });
- }
- else if (value.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- subtitles.Add(new SubtitleInfo { Lang = "unknown", Url = value });
- }
-
- return subtitles;
- }
-
- private string NormalizeUrl(string url)
- {
- if (string.IsNullOrEmpty(url))
- return string.Empty;
-
- if (url.StartsWith("//"))
- return IsNotAllowedHost($"https:{url}") ? string.Empty : $"https:{url}";
-
- if (url.StartsWith("/"))
- return IsNotAllowedHost(_init.host) ? string.Empty : $"{_init.host}{url}";
-
- return IsNotAllowedHost(url) ? string.Empty : url;
- }
-
- private static bool LooksLikeDirectStream(string url)
- {
- return url.Contains(".m3u8", StringComparison.OrdinalIgnoreCase) || url.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase);
- }
-
- private static bool IsBlacklisted(string url)
- {
- return Regex.IsMatch(url ?? string.Empty, BlacklistRegex, RegexOptions.IgnoreCase);
- }
-
- 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);
- }
-
- private static bool IsSeriesUrl(string url)
- {
- return url.Contains("/seriesss/") || url.Contains("/anime-series/") || url.Contains("/cartoonseries/");
- }
-
- private static bool IsMovieUrl(string url)
- {
- return url.Contains("/filmy/") || url.Contains("/anime-solo/") || url.Contains("/features/");
- }
-
- private string ExtractPoster(HtmlNode node)
- {
- var img = node.SelectSingleNode(".//img");
- if (img == null)
- return string.Empty;
-
- string src = img.GetAttributeValue("src", "");
- if (string.IsNullOrEmpty(src))
- src = img.GetAttributeValue("data-src", "");
-
- return NormalizeUrl(src);
- }
-
- private static string CleanText(string value)
- {
- if (string.IsNullOrEmpty(value))
- return string.Empty;
-
- return HtmlEntity.DeEntitize(value).Trim();
- }
-
- private static int? ExtractEpisodeNumber(string title)
- {
- if (string.IsNullOrEmpty(title))
- return null;
-
- var match = Regex.Match(title, @"(\d+)");
- if (match.Success && int.TryParse(match.Groups[1].Value, out int number))
- return number;
-
- return null;
- }
-
- public static TimeSpan cacheTime(int multiaccess, int home = 5, int mikrotik = 2, OnlinesSettings init = null, int rhub = -1)
- {
- if (init != null && init.rhub && rhub != -1)
- return TimeSpan.FromMinutes(rhub);
-
- int ctime = AppInit.conf.mikrotik ? mikrotik : AppInit.conf.multiaccess ? init != null && init.cache_time > 0 ? init.cache_time : multiaccess : home;
- if (ctime > multiaccess)
- ctime = multiaccess;
-
- return TimeSpan.FromMinutes(ctime);
- }
-
- public static int? TryParseEpisodeNumber(string title)
- {
- return ExtractEpisodeNumber(title);
- }
- }
-}
diff --git a/UAKino/manifest.json b/UAKino/manifest.json
deleted file mode 100644
index d4cb417..0000000
--- a/UAKino/manifest.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "enable": true,
- "version": 2,
- "initspace": "UAKino.ModInit",
- "online": "UAKino.OnlineApi"
-}
diff --git a/UaTUT/Controller.cs b/UaTUT/Controller.cs
deleted file mode 100644
index 4aac662..0000000
--- a/UaTUT/Controller.cs
+++ /dev/null
@@ -1,424 +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()
- {
- 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)
- {
- var init = await loadKit(ModInit.UaTUT);
- if (!init.enable)
- return OnError();
-
- 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 (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);
- }
- else
- {
- return await HandleMovie(searchResults, rjson, invoke);
- }
- }
-
- 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)
- {
- var init = ModInit.UaTUT;
-
- // Фільтруємо тільки серіали та аніме
- var seriesResults = searchResults.Where(r => r.Category == "Серіал" || r.Category == "Аніме").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}";
- 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={i}";
- season_tpl.Append(seasonName, link, i.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();
-
- // Перевіряємо чи існує вибраний сезон
- if (season >= playerData.Voices.First().Seasons.Count)
- 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}";
- 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={season}&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 (season < selectedVoiceData.Seasons.Count)
- {
- var selectedSeason = selectedVoiceData.Seasons[season];
-
- // Сортуємо епізоди та додаємо правильну нумерацію
- 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))
- {
- // Створюємо прямий лінк на епізод через play action
- string episodeLink = $"{host}/uatut/play?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&s={s}&season={season}&t={selectedVoice}&episodeId={episode.Id}";
-
- // Використовуємо правильний синтаксис EpisodeTpl.Append без poster параметра
- episode_tpl.Append(episodeName, title ?? original_title, season.ToString(), (i + 1).ToString("D2"), episodeLink, "call");
- }
- }
- }
- }
-
- 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");
-
- 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");
- }
- }
-
- // Допоміжний метод для кешованого отримання даних плеєра
- 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)
- {
- var init = ModInit.UaTUT;
-
- // Фільтруємо тільки фільми
- var movieResults = searchResults.Where(r => r.Category == "Фільм").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);
- if (playerData?.File == null)
- continue;
-
- string movieName = $"{movie.Title} ({movie.Year})";
- string movieLink = $"{host}/uatut/play/movie?imdb_id={movie.Id}&title={HttpUtility.UrlEncode(movie.Title)}&year={movie.Year}";
- movie_tpl.Append(movieName, movieLink, "call");
- }
-
- if (movie_tpl.IsEmpty())
- {
- 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, bool play = false, bool rjson = false)
- {
- var init = await loadKit(ModInit.UaTUT);
- if (!init.enable)
- return OnError();
-
- 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);
- if (playerData?.File == null)
- return OnError();
-
- OnLog($"UaTUT PlayMovie: Found direct file: {playerData.File}");
-
- string streamUrl = HostStreamProxy(init, playerData.File);
-
- // Якщо play=true, робимо Redirect, інакше повертаємо JSON
- if (play)
- return Redirect(streamUrl);
- else
- return 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)
- {
- var init = await loadKit(ModInit.UaTUT);
- if (!init.enable)
- return OnError();
-
- 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];
-
- if (season >= 0 && season < selectedVoice.Seasons.Count)
- {
- var selectedSeasonData = selectedVoice.Seasons[season];
-
- 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 = HostStreamProxy(init, episode.File);
- string episodeTitle = $"{title ?? original_title} - {episode.Title}";
-
- // Якщо play=true, робимо Redirect, інакше повертаємо JSON
- if (play)
- return Redirect(streamUrl);
- else
- return 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();
- }
- }
-}
diff --git a/UaTUT/ModInit.cs b/UaTUT/ModInit.cs
deleted file mode 100644
index 25a6e13..0000000
--- a/UaTUT/ModInit.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using Newtonsoft.Json;
-using Shared;
-using Shared.Engine;
-using Newtonsoft.Json.Linq;
-using Shared.Models.Online.Settings;
-using Shared.Models.Module;
-
-namespace UaTUT
-{
- public class ModInit
- {
- public static OnlinesSettings UaTUT;
-
- ///
- /// модуль загружен
- ///
- 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" }
- }
- };
- UaTUT = ModuleInvoke.Conf("UaTUT", UaTUT).ToObject();
-
- // Виводити "уточнити пошук"
- AppInit.conf.online.with_search.Add("uatut");
- }
- }
-}
diff --git a/UaTUT/Models/UaTUTModels.cs b/UaTUT/Models/UaTUTModels.cs
deleted file mode 100644
index c9ea23d..0000000
--- a/UaTUT/Models/UaTUTModels.cs
+++ /dev/null
@@ -1,61 +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 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; }
- }
-}
diff --git a/UaTUT/OnlineApi.cs b/UaTUT/OnlineApi.cs
deleted file mode 100644
index ea4ed11..0000000
--- a/UaTUT/OnlineApi.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Shared.Models.Base;
-using System.Collections.Generic;
-
-namespace UaTUT
-{
- 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.UaTUT;
- // UaTUT: змішаний контент (аніме + не-аніме) — завжди включати при enable && !rip
- if (init.enable && !init.rip)
- {
- string url = init.overridehost;
- if (string.IsNullOrEmpty(url))
- 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 aa2613c..0000000
--- a/UaTUT/UaTUTInvoke.cs
+++ /dev/null
@@ -1,283 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-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 HashSet NotAllowedHosts =
- new HashSet(
- new[]
- {
- "c3ZpdGFubW92aWU=",
- "cG9ydGFsLXR2",
- }
- .Select(base64 => Encoding.UTF8.GetString(Convert.FromBase64String(base64))),
- StringComparer.OrdinalIgnoreCase
- );
- private OnlinesSettings _init;
- private HybridCache _hybridCache;
- private Action _onLog;
- private ProxyManager _proxyManager;
-
- public UaTUTInvoke(OnlinesSettings init, HybridCache 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}");
-
- if (IsNotAllowedHost(url))
- return null;
-
- 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(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}");
-
- if (IsNotAllowedHost(url))
- return null;
-
- 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(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, @"