From ae39beb8b606113ba89a2c8bfb50eceee90cd6e5 Mon Sep 17 00:00:00 2001 From: Felix Date: Fri, 27 Feb 2026 11:43:07 +0200 Subject: [PATCH] refactor: Remove KlonFUN integration and Wormhole enrichment, simplifying play source resolution to only use Wormhole. --- Makhno/Controller.cs | 122 ++-------- Makhno/MakhnoInvoke.cs | 434 ---------------------------------- Makhno/Models/MakhnoModels.cs | 36 --- 3 files changed, 14 insertions(+), 578 deletions(-) diff --git a/Makhno/Controller.cs b/Makhno/Controller.cs index a3239f9..68cf71c 100644 --- a/Makhno/Controller.cs +++ b/Makhno/Controller.cs @@ -45,25 +45,10 @@ namespace Makhno var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager); - var resolved = await ResolvePlaySource(imdb_id, title, original_title, year, serial, invoke); + var resolved = await ResolvePlaySource(imdb_id, serial, invoke); if (resolved == null || string.IsNullOrEmpty(resolved.PlayUrl)) return OnError(); - if (resolved.ShouldEnrich) - { - _ = Task.Run(async () => - { - try - { - await EnrichWormhole(imdb_id, year, resolved, invoke); - } - catch (Exception ex) - { - OnLog($"Makhno wormhole enrich failed: {ex.Message}"); - } - }); - } - if (resolved.IsSerial) return await HandleSerial(resolved.PlayUrl, imdb_id, title, original_title, year, t, season, rjson, invoke); @@ -84,7 +69,7 @@ namespace Makhno OnLog($"Makhno Play: {title} (s={s}, season={season}, t={t}, episodeId={episodeId}) play={play}"); var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager); - var resolved = await ResolvePlaySource(imdb_id, title, original_title, year, serial: 1, invoke); + var resolved = await ResolvePlaySource(imdb_id, serial: 1, invoke); if (resolved == null || string.IsNullOrEmpty(resolved.PlayUrl)) return OnError(); @@ -142,7 +127,7 @@ namespace Makhno OnLog($"Makhno PlayMovie: {title} ({year}) play={play}"); var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager); - var resolved = await ResolvePlaySource(imdb_id, title, original_title, year, serial: 0, invoke); + var resolved = await ResolvePlaySource(imdb_id, serial: 0, invoke); if (resolved == null || string.IsNullOrEmpty(resolved.PlayUrl)) return OnError(); @@ -455,68 +440,24 @@ namespace Makhno return result; } - private async Task ResolvePlaySource(string imdbId, string title, string originalTitle, int year, int serial, MakhnoInvoke invoke) + private async Task ResolvePlaySource(string imdbId, int serial, MakhnoInvoke invoke) { - string playUrl = null; + if (string.IsNullOrWhiteSpace(imdbId)) + return null; - if (!string.IsNullOrEmpty(imdbId)) + string cacheKey = $"makhno:wormhole:{imdbId}"; + string playUrl = await InvokeCache(cacheKey, TimeSpan.FromMinutes(5), async () => { - string cacheKey = $"makhno:wormhole:{imdbId}"; - playUrl = await InvokeCache(cacheKey, TimeSpan.FromMinutes(5), async () => - { - return await invoke.GetWormholePlay(imdbId); - }); - - if (!string.IsNullOrEmpty(playUrl)) - { - return new ResolveResult - { - PlayUrl = playUrl, - IsSerial = IsSerialByUrl(playUrl, serial), - ShouldEnrich = false - }; - } - } - - string searchQuery = originalTitle ?? title; - string klonSearchCacheKey = $"makhno:klonfun:search:{imdbId ?? searchQuery}"; - var klonSearchResults = await InvokeCache>(klonSearchCacheKey, TimeSpan.FromMinutes(10), async () => - { - return await invoke.SearchKlonFUN(imdbId, title, originalTitle); + return await invoke.GetWormholePlay(imdbId); }); - if (klonSearchResults != null && klonSearchResults.Count > 0) + if (!string.IsNullOrEmpty(playUrl)) { - var klonSelected = invoke.SelectKlonFUNItem(klonSearchResults, year > 0 ? year : null, title, originalTitle); - if (klonSelected != null && !string.IsNullOrWhiteSpace(klonSelected.Url)) + return new ResolveResult { - string klonPlayerCacheKey = $"makhno:klonfun:player:{klonSelected.Url}"; - string klonPlayerUrl = await InvokeCache(klonPlayerCacheKey, TimeSpan.FromMinutes(10), async () => - { - return await invoke.GetKlonFUNPlayerUrl(klonSelected.Url); - }); - - if (!string.IsNullOrWhiteSpace(klonPlayerUrl)) - { - string klonAshdiPath = invoke.ExtractAshdiPath(klonPlayerUrl); - - return new ResolveResult - { - PlayUrl = klonPlayerUrl, - AshdiPath = klonAshdiPath, - Selected = new SearchResult - { - ImdbId = imdbId, - Title = klonSelected.Title, - TitleEn = originalTitle, - Year = klonSelected.Year > 0 ? klonSelected.Year.ToString() : null, - Category = invoke.IsSerialPlayerUrl(klonPlayerUrl) ? "Серіал" : "Фільм" - }, - IsSerial = serial == 1 || invoke.IsSerialPlayerUrl(klonPlayerUrl) || IsSerialByUrl(klonPlayerUrl, serial), - ShouldEnrich = !string.IsNullOrWhiteSpace(klonAshdiPath) - }; - } - } + PlayUrl = playUrl, + IsSerial = IsSerialByUrl(playUrl, serial) + }; } return null; @@ -533,38 +474,6 @@ namespace Makhno return url.Contains("/serial/", StringComparison.OrdinalIgnoreCase); } - private async Task EnrichWormhole(string imdbId, int year, ResolveResult resolved, MakhnoInvoke invoke) - { - if (string.IsNullOrWhiteSpace(imdbId) || resolved == null || string.IsNullOrWhiteSpace(resolved.AshdiPath)) - return; - - int? yearValue = year > 0 ? year : null; - if (!yearValue.HasValue && int.TryParse(resolved.Selected?.Year, out int parsedYear)) - yearValue = parsedYear; - - var tmdbResult = await invoke.FetchTmdbByImdb(imdbId, yearValue, resolved.IsSerial); - if (tmdbResult == null) - return; - - var (item, _) = tmdbResult.Value; - var tmdbId = item.Value("id"); - if (!tmdbId.HasValue) - return; - - string mediaType = resolved.IsSerial ? "tv" : "movie"; - int serialValue = resolved.IsSerial ? 1 : 0; - - var payload = new - { - imdb_id = imdbId, - _id = $"{mediaType}:{tmdbId.Value}", - serial = serialValue, - ashdi = resolved.AshdiPath - }; - - await invoke.PostWormholeAsync(payload); - } - private static string StripLampacArgs(string url) { if (string.IsNullOrEmpty(url)) @@ -606,10 +515,7 @@ namespace Makhno private class ResolveResult { public string PlayUrl { get; set; } - public string AshdiPath { get; set; } - public SearchResult Selected { get; set; } public bool IsSerial { get; set; } - public bool ShouldEnrich { get; set; } } } } diff --git a/Makhno/MakhnoInvoke.cs b/Makhno/MakhnoInvoke.cs index a808e5a..91f114d 100644 --- a/Makhno/MakhnoInvoke.cs +++ b/Makhno/MakhnoInvoke.cs @@ -2,10 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -using HtmlAgilityPack; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Shared; @@ -19,11 +17,8 @@ namespace Makhno public class MakhnoInvoke { private const string WormholeHost = "https://wh.lme.isroot.in/"; - private const string AshdiHost = "https://ashdi.vip"; - private const string KlonHost = "https://klon.fun"; private static readonly Regex Quality4kRegex = new Regex(@"(^|[^0-9])(2160p?)([^0-9]|$)|\b4k\b|\buhd\b", RegexOptions.IgnoreCase); private static readonly Regex QualityFhdRegex = new Regex(@"(^|[^0-9])(1080p?)([^0-9]|$)|\bfhd\b", RegexOptions.IgnoreCase); - private static readonly Regex YearRegex = new Regex(@"(19|20)\d{2}", RegexOptions.IgnoreCase); private readonly OnlinesSettings _init; private readonly IHybridCache _hybridCache; @@ -65,284 +60,6 @@ namespace Makhno } } - public async Task> SearchKlonFUN(string imdbId, string title, string originalTitle) - { - try - { - if (!string.IsNullOrWhiteSpace(imdbId)) - { - var byImdb = await SearchKlonFUNByQuery(imdbId); - if (byImdb?.Count > 0) - { - _onLog($"Makhno KlonFUN: знайдено {byImdb.Count} результат(ів) за imdb_id={imdbId}"); - return byImdb; - } - } - - var queries = new[] { originalTitle, title } - .Where(q => !string.IsNullOrWhiteSpace(q)) - .Select(q => q.Trim()) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - var results = new List(); - var seen = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (string query in queries) - { - var partial = await SearchKlonFUNByQuery(query); - if (partial == null || partial.Count == 0) - continue; - - foreach (var item in partial) - { - if (!string.IsNullOrWhiteSpace(item?.Url) && seen.Add(item.Url)) - results.Add(item); - } - - if (results.Count > 0) - break; - } - - if (results.Count > 0) - { - _onLog($"Makhno KlonFUN: знайдено {results.Count} результат(ів) за назвою"); - return results; - } - } - catch (Exception ex) - { - _onLog($"Makhno KlonFUN: помилка пошуку - {ex.Message}"); - } - - return new List(); - } - - public KlonSearchResult SelectKlonFUNItem(List items, int? year, string title, string titleEn) - { - if (items == null || items.Count == 0) - return null; - - if (items.Count == 1) - return items[0]; - - var byYearAndTitle = items - .Where(item => YearMatch(item, year) && TitleMatch(item?.Title, title, titleEn)) - .ToList(); - - if (byYearAndTitle.Count == 1) - return byYearAndTitle[0]; - if (byYearAndTitle.Count > 1) - return null; - - var byTitle = items - .Where(item => TitleMatch(item?.Title, title, titleEn)) - .ToList(); - - if (byTitle.Count == 1) - return byTitle[0]; - if (byTitle.Count > 1) - return null; - - var byYear = items - .Where(item => YearMatch(item, year)) - .ToList(); - - if (byYear.Count == 1) - return byYear[0]; - - return null; - } - - public async Task GetKlonFUNPlayerUrl(string itemUrl) - { - if (string.IsNullOrWhiteSpace(itemUrl)) - return null; - - try - { - var headers = new List() - { - new HeadersModel("User-Agent", Http.UserAgent), - new HeadersModel("Referer", KlonHost) - }; - - string html = await Http.Get(_init.cors(itemUrl), headers: headers, proxy: _proxyManager.Get()); - if (string.IsNullOrWhiteSpace(html)) - return null; - - var doc = new HtmlDocument(); - doc.LoadHtml(html); - - string playerUrl = doc.DocumentNode - .SelectSingleNode("//div[contains(@class,'film-player')]//iframe") - ?.GetAttributeValue("data-src", null); - - if (string.IsNullOrWhiteSpace(playerUrl)) - { - playerUrl = doc.DocumentNode - .SelectSingleNode("//div[contains(@class,'film-player')]//iframe") - ?.GetAttributeValue("src", null); - } - - if (string.IsNullOrWhiteSpace(playerUrl)) - { - playerUrl = doc.DocumentNode - .SelectSingleNode("//iframe[contains(@src,'ashdi.vip') or contains(@src,'zetvideo.net') or contains(@src,'/vod/') or contains(@src,'/serial/')]") - ?.GetAttributeValue("src", null); - } - - return NormalizeUrl(KlonHost, playerUrl); - } - catch (Exception ex) - { - _onLog($"Makhno KlonFUN: помилка отримання плеєра - {ex.Message}"); - return null; - } - } - - public bool IsSerialPlayerUrl(string playerUrl) - { - return !string.IsNullOrWhiteSpace(playerUrl) - && playerUrl.IndexOf("/serial/", StringComparison.OrdinalIgnoreCase) >= 0; - } - - private async Task> SearchKlonFUNByQuery(string query) - { - if (string.IsNullOrWhiteSpace(query)) - return null; - - try - { - var headers = new List() - { - new HeadersModel("User-Agent", Http.UserAgent), - new HeadersModel("Referer", KlonHost) - }; - - string form = $"do=search&subaction=search&story={WebUtility.UrlEncode(query)}"; - string html = await Http.Post(_init.cors(KlonHost), form, headers: headers, proxy: _proxyManager.Get()); - if (string.IsNullOrWhiteSpace(html)) - return null; - - var doc = new HtmlDocument(); - doc.LoadHtml(html); - - var results = new List(); - var seen = new HashSet(StringComparer.OrdinalIgnoreCase); - - var nodes = doc.DocumentNode.SelectNodes("//div[contains(@class,'short-news__slide-item')]"); - if (nodes != null) - { - foreach (var node in nodes) - { - string href = node.SelectSingleNode(".//a[contains(@class,'short-news__small-card__link')]") - ?.GetAttributeValue("href", null) - ?? node.SelectSingleNode(".//a[contains(@class,'card-link__style')]") - ?.GetAttributeValue("href", null); - - href = NormalizeUrl(KlonHost, href); - if (string.IsNullOrWhiteSpace(href) || !seen.Add(href)) - continue; - - string itemTitle = CleanText(node.SelectSingleNode(".//div[contains(@class,'card-link__text')]")?.InnerText); - if (string.IsNullOrWhiteSpace(itemTitle)) - { - itemTitle = CleanText(node.SelectSingleNode(".//a[contains(@class,'card-link__style')]")?.InnerText); - } - - string poster = node.SelectSingleNode(".//img[contains(@class,'card-poster__img')]") - ?.GetAttributeValue("data-src", null); - if (string.IsNullOrWhiteSpace(poster)) - { - poster = node.SelectSingleNode(".//img[contains(@class,'card-poster__img')]") - ?.GetAttributeValue("src", null); - } - - string meta = CleanText(node.SelectSingleNode(".//div[contains(@class,'subscribe-label-module')]")?.InnerText); - int itemYear = 0; - if (!string.IsNullOrWhiteSpace(meta)) - { - var yearMatch = YearRegex.Match(meta); - if (yearMatch.Success) - int.TryParse(yearMatch.Value, out itemYear); - } - - if (string.IsNullOrWhiteSpace(itemTitle)) - continue; - - results.Add(new KlonSearchResult - { - Title = itemTitle, - Url = href, - Poster = NormalizeUrl(KlonHost, poster), - Year = itemYear - }); - } - } - - if (results.Count == 0) - { - var anchors = doc.DocumentNode.SelectNodes("//a[.//span[contains(@class,'searchheading')]]"); - if (anchors != null) - { - foreach (var anchor in anchors) - { - string href = NormalizeUrl(KlonHost, anchor.GetAttributeValue("href", null)); - if (string.IsNullOrWhiteSpace(href) || !seen.Add(href)) - continue; - - string itemTitle = CleanText(anchor.SelectSingleNode(".//span[contains(@class,'searchheading')]")?.InnerText); - if (string.IsNullOrWhiteSpace(itemTitle)) - continue; - - results.Add(new KlonSearchResult - { - Title = itemTitle, - Url = href, - Poster = string.Empty, - Year = 0 - }); - } - } - } - - return results; - } - catch (Exception ex) - { - _onLog($"Makhno KlonFUN: помилка запиту '{query}' - {ex.Message}"); - return null; - } - } - - private static string NormalizeUrl(string host, string url) - { - if (string.IsNullOrWhiteSpace(url)) - return string.Empty; - - string value = WebUtility.HtmlDecode(url.Trim()); - - if (value.StartsWith("//")) - return $"https:{value}"; - - if (value.StartsWith("/")) - return host.TrimEnd('/') + value; - - if (!value.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - return host.TrimEnd('/') + "/" + value.TrimStart('/'); - - return value; - } - - private static string CleanText(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return string.Empty; - - return HtmlEntity.DeEntitize(value).Trim(); - } - public async Task GetPlayerData(string playerUrl) { if (string.IsNullOrEmpty(playerUrl)) @@ -809,157 +526,6 @@ namespace Makhno return normalized; } - public string ExtractAshdiPath(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return null; - - var match = Regex.Match(value, @"https?://(?:www\.)?ashdi\.vip/((?:vod|serial)/\d+)", RegexOptions.IgnoreCase); - if (match.Success) - return match.Groups[1].Value; - - match = Regex.Match(value, @"\b((?:vod|serial)/\d+)\b", RegexOptions.IgnoreCase); - if (match.Success) - return match.Groups[1].Value; - - return null; - } - - public string BuildAshdiUrl(string path) - { - if (string.IsNullOrWhiteSpace(path)) - return null; - - if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - return path; - - return $"{AshdiHost}/{path.TrimStart('/')}"; - } - - private bool YearMatch(KlonSearchResult item, int? year) - { - if (year == null || item == null || item.Year <= 0) - return false; - - return item.Year == year.Value; - } - - private bool TitleMatch(string itemTitle, string title, string titleEn) - { - return TitleMatch(itemTitle, null, title, titleEn); - } - - private bool TitleMatch(string itemTitleRaw, string itemTitleEnRaw, string title, string titleEn) - { - string itemTitle = NormalizeTitle(itemTitleRaw); - string itemTitleEn = NormalizeTitle(itemTitleEnRaw); - string targetTitle = NormalizeTitle(title); - string targetTitleEn = NormalizeTitle(titleEn); - - return (itemTitle.Length > 0 && targetTitle.Length > 0 && itemTitle == targetTitle) - || (itemTitle.Length > 0 && targetTitleEn.Length > 0 && itemTitle == targetTitleEn) - || (itemTitleEn.Length > 0 && targetTitle.Length > 0 && itemTitleEn == targetTitle) - || (itemTitleEn.Length > 0 && targetTitleEn.Length > 0 && itemTitleEn == targetTitleEn); - } - - private string NormalizeTitle(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return string.Empty; - - string text = value.ToLowerInvariant(); - text = Regex.Replace(text, @"[^\w\s]+", " "); - text = Regex.Replace(text, @"\b(season|сезон|частина|part|ova|special|movie|film)\b", " "); - text = Regex.Replace(text, @"\b(\d+)(st|nd|rd|th)\b", "$1"); - text = Regex.Replace(text, @"\b\d+\b", " "); - text = Regex.Replace(text, @"\s+", " "); - return text.Trim(); - } - - public async Task<(JObject item, string mediaType)?> FetchTmdbByImdb(string imdbId, int? year, bool isSerial) - { - if (string.IsNullOrWhiteSpace(imdbId)) - return null; - - try - { - string apiKey = AppInit.conf?.tmdb?.api_key; - if (string.IsNullOrWhiteSpace(apiKey)) - return null; - - string tmdbUrl = $"http://{AppInit.conf.listen.localhost}:{AppInit.conf.listen.port}/tmdb/api/3/find/{imdbId}?external_source=imdb_id&api_key={apiKey}&language=en-US"; - - var headers = new List() - { - new HeadersModel("User-Agent", Http.UserAgent) - }; - - JObject payload = await Http.Get(tmdbUrl, timeoutSeconds: 6, headers: headers); - if (payload == null) - return null; - - var movieResults = payload["movie_results"] as JArray ?? new JArray(); - var tvResults = payload["tv_results"] as JArray ?? new JArray(); - - var candidates = new List<(JObject item, string mediaType)>(); - foreach (var item in movieResults.OfType()) - candidates.Add((item, "movie")); - foreach (var item in tvResults.OfType()) - candidates.Add((item, "tv")); - - if (candidates.Count == 0) - return null; - - string preferredMediaType = isSerial ? "tv" : "movie"; - var orderedCandidates = candidates - .Where(c => c.mediaType == preferredMediaType) - .Concat(candidates.Where(c => c.mediaType != preferredMediaType)) - .ToList(); - - if (year.HasValue) - { - string yearText = year.Value.ToString(); - foreach (var candidate in orderedCandidates) - { - string dateValue = candidate.mediaType == "movie" - ? candidate.item.Value("release_date") - : candidate.item.Value("first_air_date"); - - if (!string.IsNullOrWhiteSpace(dateValue) && dateValue.StartsWith(yearText, StringComparison.Ordinal)) - return candidate; - } - } - - return orderedCandidates[0]; - } - catch (Exception ex) - { - _onLog($"Makhno TMDB fetch failed: {ex.Message}"); - return null; - } - } - - public async Task PostWormholeAsync(object payload) - { - try - { - var headers = new List() - { - new HeadersModel("Content-Type", "application/json"), - new HeadersModel("User-Agent", Http.UserAgent) - }; - - string json = JsonConvert.SerializeObject(payload, Formatting.None); - await Http.Post(_init.cors(WormholeHost), json, timeoutSeconds: 6, headers: headers, proxy: _proxyManager.Get()); - return true; - } - catch (Exception ex) - { - _onLog($"Makhno wormhole insert failed: {ex.Message}"); - return false; - } - } - private class WormholeResponse { public string play { get; set; } diff --git a/Makhno/Models/MakhnoModels.cs b/Makhno/Models/MakhnoModels.cs index 60466a9..c2e3273 100644 --- a/Makhno/Models/MakhnoModels.cs +++ b/Makhno/Models/MakhnoModels.cs @@ -1,43 +1,7 @@ -using Newtonsoft.Json; using System.Collections.Generic; namespace Makhno.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 KlonSearchResult - { - public string Title { get; set; } - public string Url { get; set; } - public string Poster { get; set; } - public int Year { get; set; } - } - public class PlayerData { public string File { get; set; }