fix(uakino): add HTML page fallback when playlist API returns no data

Handle cases where UAKino playlist requests return empty results by
resolving stream URLs directly from the content page HTML.

Add a fallback parser that extracts video sources from `link[itemprop=video]`
or `iframe#pre`, and use it to return playable movie or single-episode
responses instead of failing immediately.

Ensure Ashdi links consistently include `multivoice` for movie playback,
including episode file URLs, to improve stream compatibility.
This commit is contained in:
Felix 2026-05-15 19:06:29 +03:00
parent a54bc0e435
commit 80f0869401
2 changed files with 89 additions and 1 deletions

View File

@ -99,7 +99,37 @@ namespace LME.UAKino.Controllers
var voices = await invoke.GetPlaylist(newsId);
if (voices == null || voices.Count == 0)
{
// Fallback: playlist API повернув ERR_NOT_DATA — пробуємо зі сторінки
string fallbackUrl = await invoke.GetPageFallbackUrl(itemUrl);
if (!string.IsNullOrEmpty(fallbackUrl))
{
if (serial == 1)
{
var voice_tpl = new VoiceTpl();
var episode_tpl = new EpisodeTpl();
string streamUrl = BuildStreamUrl(init, fallbackUrl);
voice_tpl.Append("Озвучення", true, null);
episode_tpl.Append("Епізод 1", title ?? original_title, s >= 0 ? s.ToString() : "1", "01", streamUrl);
episode_tpl.Append(voice_tpl);
return rjson
? Content(episode_tpl.ToJson(), "application/json; charset=utf-8")
: Content(episode_tpl.ToHtml(), "text/html; charset=utf-8");
}
else
{
if (ApnHelper.IsAshdiUrl(fallbackUrl) && !fallbackUrl.Contains("multivoice"))
fallbackUrl += (fallbackUrl.Contains("?") ? "&" : "?") + "multivoice";
string streamUrl = BuildStreamUrl(init, fallbackUrl);
var movie_tpl = new MovieTpl(title, original_title);
movie_tpl.Append("Фільм", streamUrl);
return rjson
? Content(movie_tpl.ToJson(), "application/json; charset=utf-8")
: Content(movie_tpl.ToHtml(), "text/html; charset=utf-8");
}
}
return OnError("lme_uakino", refresh_proxy: true);
}
if (serial == 1)
{
@ -190,7 +220,11 @@ namespace LME.UAKino.Controllers
if (voices.Count == 1 && voice.Episodes.Count > 1)
label = ep.Title;
string streamUrl = BuildStreamUrl(init, ep.FileUrl);
string fileUrl = ep.FileUrl;
if (ApnHelper.IsAshdiUrl(fileUrl) && !fileUrl.Contains("multivoice"))
fileUrl += (fileUrl.Contains("?") ? "&" : "?") + "multivoice";
string streamUrl = BuildStreamUrl(init, fileUrl);
movie_tpl.Append(label, streamUrl);
}
}

View File

@ -139,6 +139,60 @@ namespace LME.UAKino
}
}
/// <summary>
/// Fallback: отримати стрім з HTML сторінки фільму коли playlist API недоступний
/// Парсить &lt;link itemprop="video" value="..."&gt; або &lt;iframe id="pre" src="..."&gt;
/// </summary>
public async Task<string> GetPageFallbackUrl(string pageUrl)
{
if (string.IsNullOrEmpty(pageUrl))
return null;
try
{
_onLog?.Invoke($"UAKino page fallback: {pageUrl}");
var headers = new List<HeadersModel>()
{
new HeadersModel("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.3 Safari/605.1.15"),
new HeadersModel("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
new HeadersModel("Referer", _init.host)
};
string html = await HttpGet(pageUrl, headers);
if (string.IsNullOrEmpty(html))
return null;
var doc = new HtmlDocument();
doc.LoadHtml(html);
// Спершу пробуємо <link itemprop="video" value="...">
var linkTag = doc.DocumentNode.SelectSingleNode("//link[@itemprop='video']");
if (linkTag != null)
{
string value = linkTag.GetAttributeValue("value", "");
if (!string.IsNullOrEmpty(value))
return NormalizeUrl(value);
}
// Fallback до <iframe id="pre" src="...">
var iframeTag = doc.DocumentNode.SelectSingleNode("//iframe[@id='pre']");
if (iframeTag != null)
{
string src = iframeTag.GetAttributeValue("src", "");
if (!string.IsNullOrEmpty(src))
return NormalizeUrl(src);
}
return null;
}
catch (Exception ex)
{
_onLog?.Invoke($"UAKino page fallback error: {ex.Message}");
return null;
}
}
/// <summary>
/// Витягнути news_id з URL контенту
/// </summary>