From b6a884d6e3b8c0b9a14d5d8e5d8e3c643a33f0bd Mon Sep 17 00:00:00 2001 From: Felix Date: Fri, 15 May 2026 20:03:34 +0300 Subject: [PATCH] feat(uakino): return all Ashdi VOD streams with per-track labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a dedicated resolver that parses the Ashdi multivoice JSON array and returns every available stream entry instead of a single resolved URL. Update controller fallback handling to build MovieTpl output from the full stream list, preserving optional item titles as display labels and defaulting to "Фільм" when absent. This exposes separate playable variants in results and avoids collapsing multivoice entries into one stream link. --- LME.UAKino/Controller.cs | 12 ++++--- LME.UAKino/UAKinoInvoke.cs | 72 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/LME.UAKino/Controller.cs b/LME.UAKino/Controller.cs index ef65682..0315d5d 100644 --- a/LME.UAKino/Controller.cs +++ b/LME.UAKino/Controller.cs @@ -118,10 +118,14 @@ namespace LME.UAKino.Controllers } else { - string resolvedUrl = await invoke.ResolveAshdiVod(fallbackUrl); - string streamUrl = BuildStreamUrl(init, resolvedUrl); - var movie_tpl = new MovieTpl(title, original_title); - movie_tpl.Append("Фільм", streamUrl); + var resolvedStreams = await invoke.ResolveAshdiVodAll(fallbackUrl); + var movie_tpl = new MovieTpl(title, original_title, resolvedStreams.Count); + foreach (var (file, label) in resolvedStreams) + { + string displayLabel = !string.IsNullOrEmpty(label) ? label : "Фільм"; + string streamUrl = BuildStreamUrl(init, file); + movie_tpl.Append(displayLabel, streamUrl); + } return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8"); diff --git a/LME.UAKino/UAKinoInvoke.cs b/LME.UAKino/UAKinoInvoke.cs index 24ce2c6..f334078 100644 --- a/LME.UAKino/UAKinoInvoke.cs +++ b/LME.UAKino/UAKinoInvoke.cs @@ -270,6 +270,78 @@ namespace LME.UAKino } } + /// + /// Резолв Ashdi VOD з ?multivoice: повертає ВСІ стріми з JSON масиву + /// + public async Task> ResolveAshdiVodAll(string vodUrl) + { + var result = new List<(string file, string title)>(); + + if (string.IsNullOrEmpty(vodUrl) || !ApnHelper.IsAshdiUrl(vodUrl)) + { + if (!string.IsNullOrEmpty(vodUrl)) + result.Add((vodUrl, null)); + return result; + } + + try + { + _onLog?.Invoke($"UAKino resolve Ashdi all: {vodUrl}"); + + var headers = new List() + { + new HeadersModel("User-Agent", Http.UserAgent), + new HeadersModel("Referer", "https://ashdi.vip/") + }; + + string fetchUrl = vodUrl; + if (ApnHelper.IsEnabled(_init) && string.IsNullOrWhiteSpace(_init.webcorshost)) + fetchUrl = ApnHelper.WrapUrl(_init, fetchUrl); + + string html = await HttpGet(fetchUrl, headers); + if (string.IsNullOrEmpty(html)) + { + result.Add((vodUrl, null)); + return result; + } + + int arrayStart = FindAshdiJsonArray(html); + if (arrayStart >= 0) + { + string jsonArray = ExtractBalancedBrackets(html, arrayStart); + if (!string.IsNullOrEmpty(jsonArray)) + { + try + { + using var arr = JsonDocument.Parse(jsonArray); + if (arr.RootElement.ValueKind == JsonValueKind.Array) + { + foreach (var item in arr.RootElement.EnumerateArray()) + { + string file = item.GetProperty("file").GetString(); + string title = item.TryGetProperty("title", out var t) ? t.GetString() : null; + if (!string.IsNullOrEmpty(file)) + result.Add((file, title?.Trim())); + } + } + } + catch { } + } + } + + if (result.Count == 0) + result.Add((vodUrl, null)); + + return result; + } + catch (Exception ex) + { + _onLog?.Invoke($"UAKino resolve Ashdi all error: {ex.Message}"); + result.Add((vodUrl, null)); + return result; + } + } + /// /// Знайти позицію JSON масиву `[{...}]` після `file:'` ///