using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Shared; using Shared.Engine; using Shared.Models; using Shared.Models.Online.Settings; using Makhno.Models; namespace Makhno { public class MakhnoInvoke { private const string WormholeHost = "http://wormhole.lampame.v6.rocks/"; private const string AshdiHost = "https://ashdi.vip"; private readonly OnlinesSettings _init; private readonly IHybridCache _hybridCache; private readonly Action _onLog; private readonly ProxyManager _proxyManager; public MakhnoInvoke(OnlinesSettings init, IHybridCache hybridCache, Action onLog, ProxyManager proxyManager) { _init = init; _hybridCache = hybridCache; _onLog = onLog; _proxyManager = proxyManager; } public async Task GetWormholePlay(string imdbId) { if (string.IsNullOrWhiteSpace(imdbId)) return null; string url = $"{WormholeHost}?imdb_id={imdbId}"; try { var headers = new List() { new HeadersModel("User-Agent", Http.UserAgent) }; string response = await Http.Get(url, timeoutSeconds: 4, headers: headers, proxy: _proxyManager.Get()); if (string.IsNullOrWhiteSpace(response)) return null; var payload = JsonConvert.DeserializeObject(response); return string.IsNullOrWhiteSpace(payload?.play) ? null : payload.play; } catch (Exception ex) { _onLog($"Makhno wormhole error: {ex.Message}"); return null; } } public async Task> SearchUaTUT(string query, string imdbId = null) { try { string searchUrl = $"{_init.apihost}/search.php"; 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($"Makhno UaTUT search error: {ex.Message}"); return new List(); } } private async Task> PerformSearch(string searchUrl, string query) { string url = $"{searchUrl}?q={WebUtility.UrlEncode(query)}"; _onLog($"Makhno UaTUT searching: {url}"); var headers = new List() { new HeadersModel("User-Agent", Http.UserAgent) }; var response = await Http.Get(url, headers: headers, proxy: _proxyManager.Get()); if (string.IsNullOrEmpty(response)) return null; try { var results = JsonConvert.DeserializeObject>(response); _onLog($"Makhno UaTUT found {results?.Count ?? 0} results for query: {query}"); return results; } catch (Exception ex) { _onLog($"Makhno UaTUT parse error: {ex.Message}"); return null; } } public async Task GetMoviePageContent(string movieId) { try { string url = $"{_init.apihost}/{movieId}"; _onLog($"Makhno UaTUT getting movie page: {url}"); var headers = new List() { new HeadersModel("User-Agent", Http.UserAgent) }; var response = await Http.Get(url, headers: headers, proxy: _proxyManager.Get()); return response; } catch (Exception ex) { _onLog($"Makhno UaTUT GetMoviePageContent error: {ex.Message}"); return null; } } public string GetPlayerUrl(string moviePageContent) { try { if (string.IsNullOrEmpty(moviePageContent)) return null; var match = Regex.Match(moviePageContent, @"]*id=[""']vip-player[""'][^>]*src=[""']([^""']+)", RegexOptions.IgnoreCase); if (match.Success) return NormalizePlayerUrl(match.Groups[1].Value); match = Regex.Match(moviePageContent, @"]*id=[""']alt-player[""'][^>]*src=[""']([^""']+)", RegexOptions.IgnoreCase); if (match.Success) return NormalizePlayerUrl(match.Groups[1].Value); var iframeMatches = Regex.Matches(moviePageContent, @"]*(?:id=[""']([^""']+)[""'])?[^>]*src=[""']([^""']+)[""']", RegexOptions.IgnoreCase); foreach (Match iframe in iframeMatches) { string iframeId = iframe.Groups[1].Value?.ToLowerInvariant(); string src = iframe.Groups[2].Value; if (string.IsNullOrEmpty(src)) continue; if (!string.IsNullOrEmpty(iframeId) && iframeId.Contains("player")) return NormalizePlayerUrl(src); if (src.Contains("ashdi.vip", StringComparison.OrdinalIgnoreCase) || src.Contains("zetvideo.net", StringComparison.OrdinalIgnoreCase) || src.Contains("player", StringComparison.OrdinalIgnoreCase)) return NormalizePlayerUrl(src); } var urlMatch = Regex.Match(moviePageContent, @"(https?://[^\"'\s>]+/(?:vod|serial)/\d+[^\"'\s>]*)", RegexOptions.IgnoreCase); if (urlMatch.Success) return NormalizePlayerUrl(urlMatch.Groups[1].Value); return null; } catch (Exception ex) { _onLog($"Makhno UaTUT GetPlayerUrl error: {ex.Message}"); return null; } } private string NormalizePlayerUrl(string src) { if (string.IsNullOrEmpty(src)) return null; if (src.StartsWith("//")) return $"https:{src}"; return src; } public async Task GetPlayerData(string playerUrl) { if (string.IsNullOrEmpty(playerUrl)) return null; try { string requestUrl = playerUrl; var headers = new List() { new HeadersModel("User-Agent", Http.UserAgent) }; if (playerUrl.Contains("ashdi.vip", StringComparison.OrdinalIgnoreCase)) { headers.Add(new HeadersModel("Referer", "https://ashdi.vip/")); } if (ApnHelper.IsAshdiUrl(playerUrl) && ApnHelper.IsEnabled(_init)) requestUrl = ApnHelper.WrapUrl(_init, playerUrl); _onLog($"Makhno getting player data from: {requestUrl}"); var response = await Http.Get(requestUrl, headers: headers, proxy: _proxyManager.Get()); if (string.IsNullOrEmpty(response)) return null; return ParsePlayerData(response); } catch (Exception ex) { _onLog($"Makhno GetPlayerData error: {ex.Message}"); return null; } } private PlayerData ParsePlayerData(string html) { try { if (string.IsNullOrEmpty(html)) return null; var fileMatch = Regex.Match(html, @"file:'([^']+)'", RegexOptions.IgnoreCase); if (!fileMatch.Success) fileMatch = Regex.Match(html, @"file:\s*\"([^\"]+)\"", RegexOptions.IgnoreCase); if (fileMatch.Success && !fileMatch.Groups[1].Value.StartsWith("[")) { var posterMatch = Regex.Match(html, @"poster:[\"']([^\"']+)[\"']", RegexOptions.IgnoreCase); return new PlayerData { File = fileMatch.Groups[1].Value, Poster = posterMatch.Success ? posterMatch.Groups[1].Value : null, Voices = new List() }; } var m3u8Match = Regex.Match(html, @"(https?://[^\"'\s>]+\.m3u8[^\"'\s>]*)", RegexOptions.IgnoreCase); if (m3u8Match.Success) { return new PlayerData { File = m3u8Match.Groups[1].Value, Poster = null, Voices = new List() }; } var sourceMatch = Regex.Match(html, @"]*src=[\"']([^\"']+)[\"']", RegexOptions.IgnoreCase); if (sourceMatch.Success) { return new PlayerData { File = sourceMatch.Groups[1].Value, Poster = null, Voices = new List() }; } var jsonMatch = Regex.Match(html, @"file:'(\[.*?\])'", RegexOptions.Singleline); if (jsonMatch.Success) { string jsonData = jsonMatch.Groups[1].Value .Replace("\\'", "'") .Replace("\\\"", "\""); return new PlayerData { File = null, Poster = null, Voices = ParseVoicesJson(jsonData) }; } return null; } catch (Exception ex) { _onLog($"Makhno ParsePlayerData error: {ex.Message}"); return null; } } private List ParseVoicesJson(string jsonData) { try { var voicesArray = JsonConvert.DeserializeObject>(jsonData); var voices = new List(); if (voicesArray == null) return voices; foreach (var voiceGroup in voicesArray) { var voice = new Voice { Name = voiceGroup["title"]?.ToString(), Seasons = new List() }; var seasons = voiceGroup["folder"] as JArray; if (seasons != null) { foreach (var seasonGroup in seasons) { string seasonTitle = seasonGroup["title"]?.ToString() ?? string.Empty; var episodes = new List(); var episodesArray = seasonGroup["folder"] as JArray; if (episodesArray != null) { foreach (var episode in episodesArray) { episodes.Add(new Episode { Id = episode["id"]?.ToString(), Title = episode["title"]?.ToString(), File = episode["file"]?.ToString(), Poster = episode["poster"]?.ToString(), Subtitle = episode["subtitle"]?.ToString() }); } } episodes = episodes .OrderBy(item => ExtractEpisodeNumber(item.Title) is null) .ThenBy(item => ExtractEpisodeNumber(item.Title) ?? 0) .ToList(); voice.Seasons.Add(new Season { Title = seasonTitle, Episodes = episodes }); } } voices.Add(voice); } return voices; } catch (Exception ex) { _onLog($"Makhno ParseVoicesJson error: {ex.Message}"); return new List(); } } private int? ExtractEpisodeNumber(string value) { if (string.IsNullOrEmpty(value)) return null; var match = Regex.Match(value, @"(\d+)"); if (!match.Success) return null; if (int.TryParse(match.Groups[1].Value, out int num)) return num; return null; } 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('/')}"; } public async Task GetAshdiPath(string movieId) { if (string.IsNullOrWhiteSpace(movieId)) return null; var page = await GetMoviePageContent(movieId); if (string.IsNullOrWhiteSpace(page)) return null; var playerUrl = GetPlayerUrl(page); var path = ExtractAshdiPath(playerUrl); if (!string.IsNullOrWhiteSpace(path)) return path; return ExtractAshdiPath(page); } public SearchResult SelectUaTUTItem(List items, string imdbId, int? year, string title, string titleEn) { if (items == null || items.Count == 0) return null; var candidates = items.Where(item => ImdbMatch(item, imdbId) && YearMatch(item, year)).ToList(); if (candidates.Count == 1) return candidates[0]; if (candidates.Count > 1) return null; candidates = items.Where(item => ImdbMatch(item, imdbId) && TitleMatch(item, title, titleEn)).ToList(); if (candidates.Count == 1) return candidates[0]; if (candidates.Count > 1) return null; candidates = items.Where(item => YearMatch(item, year) && TitleMatch(item, title, titleEn)).ToList(); if (candidates.Count == 1) return candidates[0]; return null; } private bool ImdbMatch(SearchResult item, string imdbId) { if (string.IsNullOrWhiteSpace(imdbId) || item == null) return false; return string.Equals(item.ImdbId?.Trim(), imdbId.Trim(), StringComparison.OrdinalIgnoreCase); } private bool YearMatch(SearchResult item, int? year) { if (year == null || item == null) return false; var itemYear = YearInt(item.Year); return itemYear.HasValue && itemYear.Value == year.Value; } private bool TitleMatch(SearchResult item, string title, string titleEn) { if (item == null) return false; string itemTitle = NormalizeTitle(item.Title); string itemTitleEn = NormalizeTitle(item.TitleEn); 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(); } private int? YearInt(string value) { if (string.IsNullOrWhiteSpace(value)) return null; if (int.TryParse(value.Trim(), out int result)) return result; return null; } public async Task<(JObject item, string mediaType)?> FetchTmdbByImdb(string imdbId, int? year) { 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; if (year.HasValue) { string yearText = year.Value.ToString(); foreach (var candidate in candidates) { 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 candidates[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(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; } } } }