diff --git a/LME.UAKino/Controller.cs b/LME.UAKino/Controller.cs index 41b9ed7..ef65682 100644 --- a/LME.UAKino/Controller.cs +++ b/LME.UAKino/Controller.cs @@ -118,9 +118,8 @@ namespace LME.UAKino.Controllers } else { - if (ApnHelper.IsAshdiUrl(fallbackUrl) && !fallbackUrl.Contains("multivoice")) - fallbackUrl += (fallbackUrl.Contains("?") ? "&" : "?") + "multivoice"; - string streamUrl = BuildStreamUrl(init, fallbackUrl); + string resolvedUrl = await invoke.ResolveAshdiVod(fallbackUrl); + string streamUrl = BuildStreamUrl(init, resolvedUrl); var movie_tpl = new MovieTpl(title, original_title); movie_tpl.Append("Фільм", streamUrl); return rjson @@ -137,7 +136,7 @@ namespace LME.UAKino.Controllers } else { - return HandleMovie(init, voices, title, original_title, rjson); + return await HandleMovie(init, voices, title, original_title, rjson, invoke); } } @@ -192,12 +191,13 @@ namespace LME.UAKino.Controllers if (selected == null || selected.Episodes.Count == 0) return OnError("lme_uakino", refresh_proxy: true); + string seasonStr = s >= 0 ? s.ToString() : "1"; foreach (var ep in selected.Episodes.OrderBy(e => e.EpisodeNumber ?? int.MaxValue)) { int epNum = ep.EpisodeNumber ?? 1; string epName = string.IsNullOrEmpty(ep.Title) ? $"Епізод {epNum}" : ep.Title; string streamUrl = BuildStreamUrl(init, ep.FileUrl); - episode_tpl.Append(epName, title ?? original_title, "1", epNum.ToString("D2"), streamUrl); + episode_tpl.Append(epName, title ?? original_title, seasonStr, epNum.ToString("D2"), streamUrl); } episode_tpl.Append(voice_tpl); @@ -208,8 +208,9 @@ namespace LME.UAKino.Controllers } /// Фільм: список стрімів - private ActionResult HandleMovie(OnlinesSettings init, List voices, string title, string original_title, bool rjson) + private async Task HandleMovie(OnlinesSettings init, List voices, string title, string original_title, bool rjson, UAKinoInvoke invoke) { + var processed = new HashSet(); var movie_tpl = new MovieTpl(title, original_title); foreach (var voice in voices) @@ -221,10 +222,13 @@ namespace LME.UAKino.Controllers label = ep.Title; string fileUrl = ep.FileUrl; - if (ApnHelper.IsAshdiUrl(fileUrl) && !fileUrl.Contains("multivoice")) - fileUrl += (fileUrl.Contains("?") ? "&" : "?") + "multivoice"; + // Резолвимо Ashdi VOD — отримуємо реальний .m3u8 стрім + string resolvedUrl = await invoke.ResolveAshdiVod(fileUrl); + // Дедуплікація: якщо той самий стрім — пропускаємо + if (!processed.Add(resolvedUrl)) + continue; - string streamUrl = BuildStreamUrl(init, fileUrl); + string streamUrl = BuildStreamUrl(init, resolvedUrl); movie_tpl.Append(label, streamUrl); } } diff --git a/LME.UAKino/UAKinoInvoke.cs b/LME.UAKino/UAKinoInvoke.cs index e8cb8da..22a553e 100644 --- a/LME.UAKino/UAKinoInvoke.cs +++ b/LME.UAKino/UAKinoInvoke.cs @@ -193,6 +193,71 @@ namespace LME.UAKino } } + /// + /// Резолв Ashdi VOD сторінки: отримати реальний .m3u8 стрім з Playerjs file:'...' + /// + public async Task ResolveAshdiVod(string vodUrl) + { + if (string.IsNullOrEmpty(vodUrl) || !ApnHelper.IsAshdiUrl(vodUrl)) + return vodUrl; + + try + { + string fetchUrl = vodUrl; + if (!fetchUrl.Contains("multivoice")) + fetchUrl += (fetchUrl.Contains("?") ? "&" : "?") + "multivoice"; + + _onLog?.Invoke($"UAKino resolve Ashdi: {fetchUrl}"); + + var headers = new List() + { + new HeadersModel("User-Agent", Http.UserAgent), + new HeadersModel("Referer", "https://ashdi.vip/") + }; + + if (ApnHelper.IsEnabled(_init) && string.IsNullOrWhiteSpace(_init.webcorshost)) + fetchUrl = ApnHelper.WrapUrl(_init, fetchUrl); + + string html = await HttpGet(fetchUrl, headers); + if (string.IsNullOrEmpty(html)) + return vodUrl; + + // Спершу простий pattern file:'url' + var fileMatch = Regex.Match(html, @"file:\s*'([^']+)'", RegexOptions.IgnoreCase); + if (!fileMatch.Success) + fileMatch = Regex.Match(html, @"file:\s*""([^""]+)""", RegexOptions.IgnoreCase); + + if (fileMatch.Success) + { + string resolvedUrl = fileMatch.Groups[1].Value; + if (!string.IsNullOrEmpty(resolvedUrl) && !resolvedUrl.StartsWith("[")) + { + _onLog?.Invoke($"UAKino resolved Ashdi: {resolvedUrl}"); + return resolvedUrl; + } + } + + // Складний плеєр — пробуємо PlayerJsDecoder + try + { + var playerPayload = LME.Common.Playerjs.PlayerJsDecoder.ExtractPlayerPayload(html); + if (playerPayload?.FilePayload is string strUrl && !string.IsNullOrEmpty(strUrl)) + { + _onLog?.Invoke($"UAKino resolved Ashdi (PlayerJsDecoder): {strUrl}"); + return strUrl; + } + } + catch { } + + return vodUrl; + } + catch (Exception ex) + { + _onLog?.Invoke($"UAKino resolve Ashdi error: {ex.Message}"); + return vodUrl; + } + } + /// /// Витягнути news_id з URL контенту ///