Compare commits

..

3 Commits

Author SHA1 Message Date
Felix
0f6b048545 fix(controller): adjust continue statement placement in stream loop
Move the continue statement outside the conditional block to ensure
correct loop control flow. The previous nesting could cause unintended
skipping or processing of stream iterations based on condition evaluation,
potentially leading to logic errors in stream handling.
2026-05-02 16:11:21 +03:00
Felix
b00795c464 refactor(controller): restructure Ashdi stream handling logic
Move the foreach loop inside the null/empty check for ashdiStreams to ensure
proper iteration only when streams are available. This improves code readability
and prevents potential issues with iterating over null or empty collections.
2026-05-02 16:08:27 +03:00
Felix
04bb7d48b5 Усі модулі тепер коректно передають об'єкт SubtitleTpl у шаблони Lampac, що дозволяє відображати субтитри в інтерфейсі плеєра. 2026-05-02 15:54:56 +03:00
15 changed files with 186 additions and 62 deletions

1
.gitignore vendored
View File

@ -17,3 +17,4 @@ obj
.vscode/settings.json .vscode/settings.json
.qwen .qwen
log log
.kilo

View File

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Shared; using Shared;
using Shared.Models.Online.Settings; using Shared.Models.Online.Settings;
using Shared.Models; using Shared.Models;
using Shared.Models.Templates;
using System.Text.Json; using System.Text.Json;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -14,6 +15,13 @@ using Shared.Engine;
namespace LME.AnimeON namespace LME.AnimeON
{ {
public class AshdiStream
{
public string Title { get; set; }
public string Link { get; set; }
public SubtitleTpl Subtitles { get; set; }
}
public class AnimeONInvoke public class AnimeONInvoke
{ {
private static readonly Regex Quality4kRegex = new Regex(@"(^|[^0-9])(2160p?)([^0-9]|$)|\b4k\b|\buhd\b", RegexOptions.IgnoreCase); private static readonly Regex Quality4kRegex = new Regex(@"(^|[^0-9])(2160p?)([^0-9]|$)|\b4k\b|\buhd\b", RegexOptions.IgnoreCase);
@ -192,12 +200,12 @@ namespace LME.AnimeON
public async Task<string> ParseAshdiPage(string url, bool disableAshdiMultivoiceForVod = false) public async Task<string> ParseAshdiPage(string url, bool disableAshdiMultivoiceForVod = false)
{ {
var streams = await ParseAshdiPageStreams(url, disableAshdiMultivoiceForVod); var streams = await ParseAshdiPageStreams(url, disableAshdiMultivoiceForVod);
return streams?.FirstOrDefault().link; return streams?.FirstOrDefault()?.Link;
} }
public async Task<List<(string title, string link)>> ParseAshdiPageStreams(string url, bool disableAshdiMultivoiceForVod = false) public async Task<List<AshdiStream>> ParseAshdiPageStreams(string url, bool disableAshdiMultivoiceForVod = false)
{ {
var streams = new List<(string title, string link)>(); var streams = new List<AshdiStream>();
try try
{ {
var headers = new List<HeadersModel>() var headers = new List<HeadersModel>()
@ -234,7 +242,12 @@ namespace LME.AnimeON
continue; continue;
string rawTitle = item.TryGetProperty("title", out var titleProp) ? titleProp.GetString() : null; string rawTitle = item.TryGetProperty("title", out var titleProp) ? titleProp.GetString() : null;
streams.Add((BuildDisplayTitle(rawTitle, file, index), file)); streams.Add(new AshdiStream
{
Title = BuildDisplayTitle(rawTitle, file, index),
Link = file,
Subtitles = ApnHelper.ParseSubtitles(item.TryGetProperty("subtitle", out var subtitleProp) ? subtitleProp.GetString() : null)
});
index++; index++;
} }
@ -247,7 +260,12 @@ namespace LME.AnimeON
if (match.Success) if (match.Success)
{ {
string file = match.Groups[1].Value; string file = match.Groups[1].Value;
streams.Add((BuildDisplayTitle("Основне джерело", file, 1), file)); streams.Add(new AshdiStream
{
Title = BuildDisplayTitle("Основне джерело", file, 1),
Link = file,
Subtitles = ApnHelper.ParseSubtitles(ApnHelper.ExtractPlayerSubtitle(html))
});
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -251,12 +251,14 @@ namespace LME.AnimeON.Controllers
{ {
foreach (var ashdiStream in ashdiStreams) foreach (var ashdiStream in ashdiStreams)
{ {
string optionName = $"{translationName} {ashdiStream.title}"; string optionName = $"{translationName} {ashdiStream.Title}";
string callUrl = $"{host}/lite/lme_animeon/play?url={HttpUtility.UrlEncode(ashdiStream.link)}"; string subtitlesParam = ashdiStream.Subtitles != null ? $"&subtitles={HttpUtility.UrlEncode(JsonSerializer.Serialize(ashdiStream.Subtitles.ToObject()))}" : string.Empty;
string callUrl = $"{host}/lite/lme_animeon/play?url={HttpUtility.UrlEncode(ashdiStream.Link)}{subtitlesParam}";
tpl.Append(optionName, accsArgs(callUrl), "call"); tpl.Append(optionName, accsArgs(callUrl), "call");
} }
continue;
} }
continue;
} }
if (needsResolve || streamLink.Contains("moonanime.art/iframe/") || streamLink.Contains("ashdi.vip/vod")) if (needsResolve || streamLink.Contains("moonanime.art/iframe/") || streamLink.Contains("ashdi.vip/vod"))
@ -375,7 +377,7 @@ namespace LME.AnimeON.Controllers
} }
[HttpGet("lite/lme_animeon/play")] [HttpGet("lite/lme_animeon/play")]
public async Task<ActionResult> Play(string url, int episode_id = 0, string title = null, int serial = 0) public async Task<ActionResult> Play(string url, int episode_id = 0, string title = null, int serial = 0, string subtitles = null)
{ {
await UpdateService.ConnectAsync(host); await UpdateService.ConnectAsync(host);
@ -421,10 +423,25 @@ namespace LME.AnimeON.Controllers
forceProxy = true; forceProxy = true;
} }
SubtitleTpl subtitleTpl = null;
if (!string.IsNullOrEmpty(subtitles))
{
try
{
var subtitleDtos = JsonSerializer.Deserialize<List<SubtitleDto>>(subtitles);
if (subtitleDtos != null && subtitleDtos.Count > 0)
{
subtitleTpl = new SubtitleTpl(subtitleDtos.Count);
foreach (var sub in subtitleDtos)
subtitleTpl.Append(sub.label, sub.url);
}
}
catch { }
}
string streamUrl = BuildStreamUrl(init, streamLink, streamHeaders, forceProxy); string streamUrl = BuildStreamUrl(init, streamLink, streamHeaders, forceProxy);
string jsonResult = $"{{\"method\":\"play\",\"url\":\"{streamUrl}\",\"title\":\"{title ?? string.Empty}\"}}";
OnLog("AnimeON Play: return call JSON"); OnLog("AnimeON Play: return call JSON");
return UpdateService.Validate(Content(jsonResult, "application/json; charset=utf-8")); return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title ?? string.Empty, subtitles: subtitleTpl), "application/json; charset=utf-8"));
} }
private static string StripLampacArgs(string url) private static string StripLampacArgs(string url)

View File

@ -166,7 +166,7 @@ namespace LME.KlonFUN.Controllers
: $"Серія {episode.Number}"; : $"Серія {episode.Number}";
string streamUrl = BuildStreamUrl(init, episode.Link); string streamUrl = BuildStreamUrl(init, episode.Link);
episodeTpl.Append(episodeTitle, contentTitle, s.ToString(), episode.Number.ToString("D2"), streamUrl); episodeTpl.Append(episodeTitle, contentTitle, s.ToString(), episode.Number.ToString("D2"), streamUrl, subtitles: episode.Subtitles);
} }
episodeTpl.Append(voiceTpl); episodeTpl.Append(voiceTpl);
@ -190,7 +190,7 @@ namespace LME.KlonFUN.Controllers
: $"Варіант {i + 1}"; : $"Варіант {i + 1}";
string streamUrl = BuildStreamUrl(init, stream.Link); string streamUrl = BuildStreamUrl(init, stream.Link);
movieTpl.Append(label, streamUrl); movieTpl.Append(label, streamUrl, subtitles: stream.Subtitles);
} }
return rjson return rjson

View File

@ -206,7 +206,8 @@ namespace LME.KlonFUN
streams.Add(new MovieStream streams.Add(new MovieStream
{ {
Title = voiceTitle, Title = voiceTitle,
Link = link Link = link,
Subtitles = ApnHelper.ParseSubtitles(item.Value<string>("subtitle"))
}); });
index++; index++;
@ -221,7 +222,8 @@ namespace LME.KlonFUN
streams.Add(new MovieStream streams.Add(new MovieStream
{ {
Title = FormatMovieTitle("Основне джерело", directMatch.Groups["url"].Value, 1), Title = FormatMovieTitle("Основне джерело", directMatch.Groups["url"].Value, 1),
Link = directMatch.Groups["url"].Value Link = directMatch.Groups["url"].Value,
Subtitles = ApnHelper.ParseSubtitles(ApnHelper.ExtractPlayerSubtitle(playerHtml))
}); });
} }
} }
@ -310,7 +312,8 @@ namespace LME.KlonFUN
{ {
Number = episodeNumber, Number = episodeNumber,
Title = string.IsNullOrWhiteSpace(episodeTitle) ? $"Серія {episodeNumber}" : episodeTitle, Title = string.IsNullOrWhiteSpace(episodeTitle) ? $"Серія {episodeNumber}" : episodeTitle,
Link = link Link = link,
Subtitles = ApnHelper.ParseSubtitles(episodeObj.Value<string>("subtitle"))
}); });
episodeFallback++; episodeFallback++;

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Shared.Models.Templates;
namespace LME.KlonFUN.Models namespace LME.KlonFUN.Models
{ {
@ -47,6 +48,7 @@ namespace LME.KlonFUN.Models
{ {
public string Title { get; set; } public string Title { get; set; }
public string Link { get; set; } public string Link { get; set; }
public SubtitleTpl Subtitles { get; set; }
} }
public class SerialEpisode public class SerialEpisode
@ -54,6 +56,7 @@ namespace LME.KlonFUN.Models
public int Number { get; set; } public int Number { get; set; }
public string Title { get; set; } public string Title { get; set; }
public string Link { get; set; } public string Link { get; set; }
public SubtitleTpl Subtitles { get; set; }
} }
public class SerialVoice public class SerialVoice

View File

@ -107,7 +107,7 @@ namespace LME.Makhno
if (play) if (play)
return UpdateService.Validate(Redirect(streamUrl)); return UpdateService.Validate(Redirect(streamUrl));
return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, episodeTitle), "application/json; charset=utf-8")); return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, episodeTitle, subtitles: episode.Subtitles), "application/json; charset=utf-8"));
} }
} }
@ -150,7 +150,7 @@ namespace LME.Makhno
if (play) if (play)
return UpdateService.Validate(Redirect(streamUrl)); return UpdateService.Validate(Redirect(streamUrl));
return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title ?? original_title), "application/json; charset=utf-8")); return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title ?? original_title, subtitles: playerData.Subtitles ?? playerData.Movies?.FirstOrDefault(m => m.File == playerData.File)?.Subtitles), "application/json; charset=utf-8"));
} }
private async Task<ActionResult> HandleMovie(string playUrl, string imdb_id, string title, string original_title, int year, bool rjson, MakhnoInvoke invoke) private async Task<ActionResult> HandleMovie(string playUrl, string imdb_id, string title, string original_title, int year, bool rjson, MakhnoInvoke invoke)
@ -171,7 +171,8 @@ namespace LME.Makhno
{ {
File = playerData.File, File = playerData.File,
Title = "Основне джерело", Title = "Основне джерело",
Quality = "auto" Quality = "auto",
Subtitles = playerData.Subtitles
}); });
} }
@ -189,7 +190,7 @@ namespace LME.Makhno
? stream.Title ? stream.Title
: $"Варіант {index}"; : $"Варіант {index}";
tpl.Append(label, BuildStreamUrl(init, stream.File)); tpl.Append(label, BuildStreamUrl(init, stream.File), subtitles: stream.Subtitles);
index++; index++;
} }
@ -395,8 +396,10 @@ namespace LME.Makhno
title ?? original_title, title ?? original_title,
requestedSeason.ToString(), requestedSeason.ToString(),
(i + 1).ToString("D2"), (i + 1).ToString("D2"),
streamUrl streamUrl,
subtitles: episode.Subtitles
); );
} }
} }
} }

View File

@ -114,18 +114,22 @@ namespace LME.Makhno
{ {
string file = fileMatch.Groups[1].Value; string file = fileMatch.Groups[1].Value;
var posterMatch = Regex.Match(html, @"poster:[""']([^""']+)[""']", RegexOptions.IgnoreCase); var posterMatch = Regex.Match(html, @"poster:[""']([^""']+)[""']", RegexOptions.IgnoreCase);
var subtitles = ApnHelper.ParseSubtitles(ApnHelper.ExtractPlayerSubtitle(html));
return new PlayerData return new PlayerData
{ {
File = file, File = file,
Poster = posterMatch.Success ? posterMatch.Groups[1].Value : null, Poster = posterMatch.Success ? posterMatch.Groups[1].Value : null,
Voices = new List<Voice>(), Voices = new List<Voice>(),
Subtitles = subtitles,
Movies = new List<MovieVariant>() Movies = new List<MovieVariant>()
{ {
new MovieVariant new MovieVariant
{ {
File = file, File = file,
Quality = DetectQualityTag(file) ?? "auto", Quality = DetectQualityTag(file) ?? "auto",
Title = BuildMovieTitle("Основне джерело", file, 1) Title = BuildMovieTitle("Основне джерело", file, 1),
Subtitles = subtitles
} }
} }
}; };
@ -238,7 +242,8 @@ namespace LME.Makhno
Title = episode["title"]?.ToString(), Title = episode["title"]?.ToString(),
File = episode["file"]?.ToString(), File = episode["file"]?.ToString(),
Poster = episode["poster"]?.ToString(), Poster = episode["poster"]?.ToString(),
Subtitle = episode["subtitle"]?.ToString() Subtitle = episode["subtitle"]?.ToString(),
Subtitles = ApnHelper.ParseSubtitles(episode["subtitle"]?.ToString())
}); });
} }
} }
@ -289,7 +294,8 @@ namespace LME.Makhno
{ {
File = file, File = file,
Quality = DetectQualityTag($"{rawTitle} {file}") ?? "auto", Quality = DetectQualityTag($"{rawTitle} {file}") ?? "auto",
Title = BuildMovieTitle(rawTitle, file, index) Title = BuildMovieTitle(rawTitle, file, index),
Subtitles = ApnHelper.ParseSubtitles(item["subtitle"]?.ToString())
}); });
index++; index++;
} }

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Shared.Models.Templates;
namespace LME.Makhno.Models namespace LME.Makhno.Models
{ {
@ -9,6 +10,7 @@ namespace LME.Makhno.Models
public List<Voice> Voices { get; set; } public List<Voice> Voices { get; set; }
public List<Season> Seasons { get; set; } public List<Season> Seasons { get; set; }
public List<MovieVariant> Movies { get; set; } public List<MovieVariant> Movies { get; set; }
public SubtitleTpl Subtitles { get; set; }
} }
public class Voice public class Voice
@ -30,6 +32,7 @@ namespace LME.Makhno.Models
public string Id { get; set; } public string Id { get; set; }
public string Poster { get; set; } public string Poster { get; set; }
public string Subtitle { get; set; } public string Subtitle { get; set; }
public SubtitleTpl Subtitles { get; set; }
} }
public class MovieVariant public class MovieVariant
@ -37,5 +40,6 @@ namespace LME.Makhno.Models
public string Title { get; set; } public string Title { get; set; }
public string File { get; set; } public string File { get; set; }
public string Quality { get; set; } public string Quality { get; set; }
public SubtitleTpl Subtitles { get; set; }
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -177,12 +178,14 @@ namespace LME.Mikai.Controllers
{ {
foreach (var ashdiStream in ashdiStreams) foreach (var ashdiStream in ashdiStreams)
{ {
string optionName = $"{voice.DisplayName} {ashdiStream.title}"; string optionName = $"{voice.DisplayName} {ashdiStream.Title}";
string ashdiCallUrl = $"{host}/lite/lme_mikai/play?url={HttpUtility.UrlEncode(ashdiStream.link)}&title={HttpUtility.UrlEncode(displayTitle)}"; string subtitlesParam = ashdiStream.Subtitles != null ? $"&subtitles={HttpUtility.UrlEncode(JsonSerializer.Serialize(ashdiStream.Subtitles.ToObject()))}" : string.Empty;
string ashdiCallUrl = $"{host}/lite/lme_mikai/play?url={HttpUtility.UrlEncode(ashdiStream.Link)}&title={HttpUtility.UrlEncode(displayTitle)}{subtitlesParam}";
movieTpl.Append(optionName, accsArgs(ashdiCallUrl), "call"); movieTpl.Append(optionName, accsArgs(ashdiCallUrl), "call");
} }
continue;
} }
continue;
} }
string callUrl = $"{host}/lite/lme_mikai/play?url={HttpUtility.UrlEncode(episode.Url)}&title={HttpUtility.UrlEncode(displayTitle)}"; string callUrl = $"{host}/lite/lme_mikai/play?url={HttpUtility.UrlEncode(episode.Url)}&title={HttpUtility.UrlEncode(displayTitle)}";
@ -204,7 +207,7 @@ namespace LME.Mikai.Controllers
} }
[HttpGet("lite/lme_mikai/play")] [HttpGet("lite/lme_mikai/play")]
public async Task<ActionResult> Play(string url, string title = null, int serial = 0) public async Task<ActionResult> Play(string url, string title = null, int serial = 0, string subtitles = null)
{ {
await UpdateService.ConnectAsync(host); await UpdateService.ConnectAsync(host);
@ -235,9 +238,24 @@ namespace LME.Mikai.Controllers
forceProxy = true; forceProxy = true;
} }
SubtitleTpl subtitleTpl = null;
if (!string.IsNullOrEmpty(subtitles))
{
try
{
var subtitleDtos = JsonSerializer.Deserialize<List<SubtitleDto>>(subtitles);
if (subtitleDtos != null && subtitleDtos.Count > 0)
{
subtitleTpl = new SubtitleTpl(subtitleDtos.Count);
foreach (var sub in subtitleDtos)
subtitleTpl.Append(sub.label, sub.url);
}
}
catch { }
}
string streamUrl = BuildStreamUrl(init, streamLink, streamHeaders, forceProxy); string streamUrl = BuildStreamUrl(init, streamLink, streamHeaders, forceProxy);
string jsonResult = $"{{\"method\":\"play\",\"url\":\"{streamUrl}\",\"title\":\"{title ?? string.Empty}\"}}"; return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title ?? string.Empty, subtitles: subtitleTpl), "application/json; charset=utf-8"));
return UpdateService.Validate(Content(jsonResult, "application/json; charset=utf-8"));
} }
private async Task<List<MikaiAnime>> CollectSeasonDetails(MikaiAnime details, MikaiInvoke invoke) private async Task<List<MikaiAnime>> CollectSeasonDetails(MikaiAnime details, MikaiInvoke invoke)

View File

@ -11,9 +11,17 @@ using Shared;
using Shared.Engine; using Shared.Engine;
using Shared.Models; using Shared.Models;
using Shared.Models.Online.Settings; using Shared.Models.Online.Settings;
using Shared.Models.Templates;
namespace LME.Mikai namespace LME.Mikai
{ {
public class AshdiStream
{
public string Title { get; set; }
public string Link { get; set; }
public SubtitleTpl Subtitles { get; set; }
}
public class MikaiInvoke public class MikaiInvoke
{ {
private static readonly Regex Quality4kRegex = new Regex(@"(^|[^0-9])(2160p?)([^0-9]|$)|\b4k\b|\buhd\b", RegexOptions.IgnoreCase); private static readonly Regex Quality4kRegex = new Regex(@"(^|[^0-9])(2160p?)([^0-9]|$)|\b4k\b|\buhd\b", RegexOptions.IgnoreCase);
@ -176,12 +184,12 @@ namespace LME.Mikai
public async Task<string> ParseAshdiPage(string url, bool disableAshdiMultivoiceForVod = false) public async Task<string> ParseAshdiPage(string url, bool disableAshdiMultivoiceForVod = false)
{ {
var streams = await ParseAshdiPageStreams(url, disableAshdiMultivoiceForVod); var streams = await ParseAshdiPageStreams(url, disableAshdiMultivoiceForVod);
return streams?.FirstOrDefault().link; return streams?.FirstOrDefault()?.Link;
} }
public async Task<List<(string title, string link)>> ParseAshdiPageStreams(string url, bool disableAshdiMultivoiceForVod = false) public async Task<List<AshdiStream>> ParseAshdiPageStreams(string url, bool disableAshdiMultivoiceForVod = false)
{ {
var streams = new List<(string title, string link)>(); var streams = new List<AshdiStream>();
try try
{ {
var headers = new List<HeadersModel>() var headers = new List<HeadersModel>()
@ -218,7 +226,12 @@ namespace LME.Mikai
continue; continue;
string rawTitle = item.TryGetProperty("title", out var titleProp) ? titleProp.GetString() : null; string rawTitle = item.TryGetProperty("title", out var titleProp) ? titleProp.GetString() : null;
streams.Add((BuildDisplayTitle(rawTitle, file, index), file)); streams.Add(new AshdiStream
{
Title = BuildDisplayTitle(rawTitle, file, index),
Link = file,
Subtitles = ApnHelper.ParseSubtitles(item.TryGetProperty("subtitle", out var subtitleProp) ? subtitleProp.GetString() : null)
});
index++; index++;
} }
@ -231,7 +244,12 @@ namespace LME.Mikai
if (match.Success) if (match.Success)
{ {
string file = match.Groups[1].Value; string file = match.Groups[1].Value;
streams.Add((BuildDisplayTitle("Основне джерело", file, 1), file)); streams.Add(new AshdiStream
{
Title = BuildDisplayTitle("Основне джерело", file, 1),
Link = file,
Subtitles = ApnHelper.ParseSubtitles(ApnHelper.ExtractPlayerSubtitle(html))
});
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -1,6 +1,9 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Shared.Models.Base; using Shared.Models.Base;
using Shared.Models.Templates;
using System; using System;
using System.Net;
using System.Text.RegularExpressions;
using System.Web; using System.Web;
namespace Shared.Engine namespace Shared.Engine
@ -9,6 +12,8 @@ namespace Shared.Engine
{ {
public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}"; public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}";
private static readonly Regex SubtitleLineRegex = new Regex(@"\[([^\]]+)\]([^,]+)", RegexOptions.Compiled);
public static bool TryGetInitConf(JObject conf, out bool enabled, out string host) public static bool TryGetInitConf(JObject conf, out bool enabled, out string host)
{ {
enabled = false; enabled = false;
@ -120,6 +125,40 @@ namespace Shared.Engine
return $"{host.TrimEnd('/')}/{url}"; return $"{host.TrimEnd('/')}/{url}";
} }
public static SubtitleTpl ParseSubtitles(string subtitleValue)
{
if (string.IsNullOrWhiteSpace(subtitleValue))
return null;
var subtitles = new SubtitleTpl();
string normalized = WebUtility.HtmlDecode(subtitleValue)
.Replace("\\/", "/")
.Replace("\\'", "'")
.Replace("\\\"", "\"");
foreach (Match match in SubtitleLineRegex.Matches(normalized))
{
string label = WebUtility.HtmlDecode(match.Groups[1].Value).Trim();
string url = WebUtility.HtmlDecode(match.Groups[2].Value).Trim();
if (!string.IsNullOrWhiteSpace(label) && !string.IsNullOrWhiteSpace(url))
subtitles.Append(label, url);
}
return subtitles.IsEmpty ? null : subtitles;
}
public static string ExtractPlayerSubtitle(string html)
{
if (string.IsNullOrWhiteSpace(html))
return null;
var match = Regex.Match(html, @"subtitle\s*:\s*['""]([^'""']+)['""]", RegexOptions.IgnoreCase);
if (!match.Success)
match = Regex.Match(html, @"subtitle['""]?\s*:\s*['""]([^'""']+)['""]", RegexOptions.IgnoreCase);
return match.Success ? match.Groups[1].Value : null;
}
private static string NormalizeHost(string host) private static string NormalizeHost(string host)
{ {
if (string.IsNullOrWhiteSpace(host)) if (string.IsNullOrWhiteSpace(host))

View File

@ -104,9 +104,10 @@ namespace LME.Uaflix.Controllers
{ {
// Повертаємо JSON з інформацією про стрім для методу 'play' // Повертаємо JSON з інформацією про стрім для методу 'play'
string streamUrl = BuildStreamUrl(init, playResult.streams.First().link); string streamUrl = BuildStreamUrl(init, playResult.streams.First().link);
string jsonResult = $"{{\"method\":\"play\",\"url\":\"{streamUrl}\",\"title\":\"{title ?? original_title}\"}}"; var subtitles = playResult.subtitles ?? playResult.streams.FirstOrDefault(s => s.subtitles != null)?.subtitles;
OnLog($"=== RETURN: call method JSON for episode_url ==="); OnLog($"=== RETURN: call method JSON for episode_url ===");
return UpdateService.Validate(Content(jsonResult, "application/json; charset=utf-8")); return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title ?? original_title, subtitles: subtitles), "application/json; charset=utf-8"));
} }
OnLog("=== RETURN: call method no streams ==="); OnLog("=== RETURN: call method no streams ===");
@ -284,7 +285,8 @@ namespace LME.Uaflix.Controllers
e: ep.Number.ToString(), e: ep.Number.ToString(),
link: accsArgs(callUrl), link: accsArgs(callUrl),
method: "call", method: "call",
streamlink: accsArgs($"{callUrl}&play=true") streamlink: accsArgs($"{callUrl}&play=true"),
subtitles: ApnHelper.ParseSubtitles(ep.Subtitle)
); );
} }
else else
@ -296,7 +298,8 @@ namespace LME.Uaflix.Controllers
title: title, title: title,
s: s.ToString(), s: s.ToString(),
e: ep.Number.ToString(), e: ep.Number.ToString(),
link: playUrl link: playUrl,
subtitles: ApnHelper.ParseSubtitles(ep.Subtitle)
); );
} }
@ -351,7 +354,7 @@ namespace LME.Uaflix.Controllers
? stream.title ? stream.title
: $"Варіант {index}"; : $"Варіант {index}";
tpl.Append(label, BuildStreamUrl(init, stream.link)); tpl.Append(label, BuildStreamUrl(init, stream.link), subtitles: stream.subtitles ?? playResult.subtitles);
index++; index++;
} }

View File

@ -15,5 +15,6 @@ namespace LME.Uaflix.Models
public string link { get; set; } public string link { get; set; }
public string quality { get; set; } public string quality { get; set; }
public string title { get; set; } public string title { get; set; }
public SubtitleTpl? subtitles { get; set; }
} }
} }

View File

@ -582,7 +582,8 @@ namespace LME.Uaflix
{ {
link = fileUrl, link = fileUrl,
quality = DetectQualityTag($"{rawTitle} {fileUrl}") ?? "auto", quality = DetectQualityTag($"{rawTitle} {fileUrl}") ?? "auto",
title = BuildDisplayTitle(rawTitle, fileUrl, index) title = BuildDisplayTitle(rawTitle, fileUrl, index),
subtitles = ApnHelper.ParseSubtitles(item?["subtitle"]?.ToString())
}); });
index++; index++;
} }
@ -605,7 +606,8 @@ namespace LME.Uaflix
{ {
link = fallbackFile, link = fallbackFile,
quality = DetectQualityTag(fallbackFile) ?? "auto", quality = DetectQualityTag(fallbackFile) ?? "auto",
title = BuildDisplayTitle(ExtractVoiceFromUrl(fallbackFile), fallbackFile, 1) title = BuildDisplayTitle(ExtractVoiceFromUrl(fallbackFile), fallbackFile, 1),
subtitles = ApnHelper.ParseSubtitles(ApnHelper.ExtractPlayerSubtitle(html))
}); });
return result; return result;
@ -2050,19 +2052,7 @@ namespace LME.Uaflix
string url = $"https://ashdi.vip/vod/{id}"; string url = $"https://ashdi.vip/vod/{id}";
var html = await GetHtml(AshdiRequestUrl(url), new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") }); var html = await GetHtml(AshdiRequestUrl(url), new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") });
string subtitle = new Regex("subtitle(\")?:\"([^\"]+)\"").Match(html).Groups[2].Value; string subtitle = new Regex("subtitle(\")?:\"([^\"]+)\"").Match(html).Groups[2].Value;
if (!string.IsNullOrEmpty(subtitle)) return ApnHelper.ParseSubtitles(subtitle);
{
var match = new Regex("\\[([^\\]]+)\\](https?://[^\\,]+)").Match(subtitle);
var st = new Shared.Models.Templates.SubtitleTpl();
while (match.Success)
{
st.Append(match.Groups[1].Value, match.Groups[2].Value);
match = match.NextMatch();
}
if (st.data != null && st.data.Count > 0)
return st;
}
return null;
} }
private static string WithAshdiMultivoice(string url) private static string WithAshdiMultivoice(string url)