diff --git a/LME.AnimeON/AnimeON.csproj b/LME.AnimeON/AnimeON.csproj
index 9512ffc..245e522 100644
--- a/LME.AnimeON/AnimeON.csproj
+++ b/LME.AnimeON/AnimeON.csproj
@@ -10,6 +10,7 @@
..\..\Shared.dll
+
\ No newline at end of file
diff --git a/LME.AnimeON/AnimeONInvoke.cs b/LME.AnimeON/AnimeONInvoke.cs
index eb002a3..a9e637c 100644
--- a/LME.AnimeON/AnimeONInvoke.cs
+++ b/LME.AnimeON/AnimeONInvoke.cs
@@ -6,11 +6,14 @@ using Shared.Models.Online.Settings;
using Shared.Models;
using Shared.Models.Templates;
using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Linq;
using System.Text;
using System.Net;
using System.Text.RegularExpressions;
using LME.AnimeON.Models;
+using LME.Shared;
+using LME.Shared.Models;
using Shared.Engine;
namespace LME.AnimeON
@@ -183,11 +186,12 @@ namespace LME.AnimeON
if (string.IsNullOrEmpty(html))
return null;
- var match = System.Text.RegularExpressions.Regex.Match(html, @"file:\s*""([^""]+\.m3u8)""");
- if (match.Success)
- {
- return match.Groups[1].Value;
- }
+ var payload = PlayerJsDecoder.ExtractPlayerPayload(html);
+ if (payload?.FilePayload == null)
+ return null;
+
+ var streamUrls = ExtractStreamUrls(payload.FilePayload);
+ return streamUrls?.FirstOrDefault();
}
catch (Exception ex)
{
@@ -197,6 +201,61 @@ namespace LME.AnimeON
return null;
}
+ private List ExtractStreamUrls(object filePayload)
+ {
+ var urls = new List();
+ if (filePayload == null)
+ return urls;
+
+ // Обробка string значення
+ if (filePayload is string strPayload)
+ {
+ urls.Add(strPayload);
+ return urls;
+ }
+
+ // Обробка JsonValue
+ if (filePayload is JsonValue jsonValue && jsonValue.TryGetValue(out string strValue))
+ {
+ urls.Add(strValue);
+ return urls;
+ }
+
+ // Обробка JsonObject — витягти 'file' поле
+ if (filePayload is JsonObject objPayload)
+ {
+ if (objPayload.TryGetPropertyValue("file", out JsonNode fileNode))
+ {
+ string fileStr = fileNode?.ToString();
+ if (!string.IsNullOrEmpty(fileStr))
+ urls.Add(fileStr);
+ }
+ return urls;
+ }
+
+ // Обробка JsonArray
+ if (filePayload is JsonArray arrayPayload)
+ {
+ foreach (var item in arrayPayload)
+ {
+ if (item is JsonObject itemObj && itemObj.TryGetPropertyValue("file", out JsonNode fileProp))
+ {
+ string fileStr = fileProp?.ToString();
+ if (!string.IsNullOrEmpty(fileStr))
+ urls.Add(fileStr);
+ }
+ else if (item is JsonValue itemValue && itemValue.TryGetValue(out string itemStr))
+ {
+ if (!string.IsNullOrEmpty(itemStr))
+ urls.Add(itemStr);
+ }
+ }
+ return urls;
+ }
+
+ return urls;
+ }
+
public async Task ParseAshdiPage(string url, bool disableAshdiMultivoiceForVod = false)
{
var streams = await ParseAshdiPageStreams(url, disableAshdiMultivoiceForVod);
diff --git a/LME.Mikai/Mikai.csproj b/LME.Mikai/Mikai.csproj
index c280999..049c9e2 100644
--- a/LME.Mikai/Mikai.csproj
+++ b/LME.Mikai/Mikai.csproj
@@ -10,6 +10,7 @@
..\..\Shared.dll
+
diff --git a/LME.Mikai/MikaiInvoke.cs b/LME.Mikai/MikaiInvoke.cs
index ea44c6f..6f5fd3d 100644
--- a/LME.Mikai/MikaiInvoke.cs
+++ b/LME.Mikai/MikaiInvoke.cs
@@ -2,11 +2,14 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Web;
using System.Net;
using System.Text.RegularExpressions;
using LME.Mikai.Models;
+using LME.Shared;
+using LME.Shared.Models;
using Shared;
using Shared.Engine;
using Shared.Models;
@@ -158,16 +161,73 @@ namespace LME.Mikai
if (string.IsNullOrEmpty(html))
return null;
- var match = System.Text.RegularExpressions.Regex.Match(html, @"file:\s*""([^""]+\.m3u8)""");
- if (match.Success)
- return match.Groups[1].Value;
+ var payload = PlayerJsDecoder.ExtractPlayerPayload(html);
+ if (payload?.FilePayload == null)
+ return null;
+
+ var streamUrls = ExtractStreamUrls(payload.FilePayload);
+ return streamUrls?.FirstOrDefault();
}
catch (Exception ex)
{
_onLog($"Mikai ParseMoonAnimePage error: {ex.Message}");
+ return null;
+ }
+ }
+
+ private List ExtractStreamUrls(object filePayload)
+ {
+ var urls = new List();
+ if (filePayload == null)
+ return urls;
+
+ // Обробка string значення
+ if (filePayload is string strPayload)
+ {
+ urls.Add(strPayload);
+ return urls;
}
- return null;
+ // Обробка JsonValue
+ if (filePayload is JsonValue jsonValue && jsonValue.TryGetValue(out string strValue))
+ {
+ urls.Add(strValue);
+ return urls;
+ }
+
+ // Обробка JsonObject — витягти 'file' поле
+ if (filePayload is JsonObject objPayload)
+ {
+ if (objPayload.TryGetPropertyValue("file", out JsonNode fileNode))
+ {
+ string fileStr = fileNode?.ToString();
+ if (!string.IsNullOrEmpty(fileStr))
+ urls.Add(fileStr);
+ }
+ return urls;
+ }
+
+ // Обробка JsonArray
+ if (filePayload is JsonArray arrayPayload)
+ {
+ foreach (var item in arrayPayload)
+ {
+ if (item is JsonObject itemObj && itemObj.TryGetPropertyValue("file", out JsonNode fileProp))
+ {
+ string fileStr = fileProp?.ToString();
+ if (!string.IsNullOrEmpty(fileStr))
+ urls.Add(fileStr);
+ }
+ else if (item is JsonValue itemValue && itemValue.TryGetValue(out string itemStr))
+ {
+ if (!string.IsNullOrEmpty(itemStr))
+ urls.Add(itemStr);
+ }
+ }
+ return urls;
+ }
+
+ return urls;
}
string AshdiRequestUrl(string url)
diff --git a/LME.NMoonAnime/Controller.cs b/LME.NMoonAnime/Controller.cs
index 8631584..c2841f5 100644
--- a/LME.NMoonAnime/Controller.cs
+++ b/LME.NMoonAnime/Controller.cs
@@ -109,7 +109,7 @@ namespace LME.NMoonAnime.Controllers
if (!streamQuality.Any())
return OnError("lme_nmoonanime", refresh_proxy: true);
- var first = streamQuality.Firts();
+ var first = streamQuality.First();
string json = VideoTpl.ToJson("play", first.link, title ?? string.Empty, streamquality: streamQuality);
return UpdateService.Validate(Content(json, "application/json; charset=utf-8"));
}
diff --git a/LME.NMoonAnime/NMoonAnime.csproj b/LME.NMoonAnime/NMoonAnime.csproj
index c280999..049c9e2 100644
--- a/LME.NMoonAnime/NMoonAnime.csproj
+++ b/LME.NMoonAnime/NMoonAnime.csproj
@@ -10,6 +10,7 @@
..\..\Shared.dll
+
diff --git a/LME.NMoonAnime/NMoonAnimeInvoke.cs b/LME.NMoonAnime/NMoonAnimeInvoke.cs
index ded524c..ebb7325 100644
--- a/LME.NMoonAnime/NMoonAnimeInvoke.cs
+++ b/LME.NMoonAnime/NMoonAnimeInvoke.cs
@@ -1,4 +1,6 @@
using LME.NMoonAnime.Models;
+using LME.Shared;
+using LME.Shared.Models;
using Shared;
using Shared.Engine;
using Shared.Models;
@@ -29,12 +31,6 @@ namespace LME.NMoonAnime
};
private static readonly Regex _reSeason = new Regex(@"(?:season|сезон)\s*(\d+)|(\d+)\s*(?:season|сезон)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex _reEpisode = new Regex(@"(?:episode|серія|серия|епізод|ep)\s*(\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
- private static readonly Regex _reTrailingComma = new Regex(@",\s*([}\]])", RegexOptions.Compiled);
- private static readonly Regex _reAtobLiteral = new Regex(@"atob\(\s*(['""])(?[A-Za-z0-9+/=]+)\1\s*\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
- private static readonly Regex _reJsonParseHelper = new Regex(@"JSON\.parse\(\s*(?[A-Za-z_$][\w$]*)\(\s*(?['""])(?.*?)(\k)\s*\)\s*\)", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
- private static readonly Regex _reHelperCall = new Regex(@"^\s*(?[A-Za-z_$][\w$]*)\(\s*(?['""])(?.*?)(\k)\s*\)\s*$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
- private static readonly UTF8Encoding _utf8Strict = new UTF8Encoding(false, true);
- private static readonly Encoding _latin1 = Encoding.GetEncoding("ISO-8859-1");
public NMoonAnimeInvoke(OnlinesSettings init, IHybridCache hybridCache, Action onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
{
@@ -241,7 +237,7 @@ namespace LME.NMoonAnime
IsSeries = false
};
- var payload = ExtractPlayerPayload(html);
+ var payload = PlayerJsDecoder.ExtractPlayerPayload(html);
if (payload == null)
return content;
@@ -495,437 +491,14 @@ namespace LME.NMoonAnime
return entries;
}
- private PlayerPayload ExtractPlayerPayload(string htmlText)
- {
- string cleanHtml = WebUtility.HtmlDecode(htmlText ?? string.Empty);
- if (string.IsNullOrWhiteSpace(cleanHtml))
- return null;
-
- var candidates = new List { cleanHtml };
- string decodedScript = DecodeOuterPlayerScript(cleanHtml);
- if (!string.IsNullOrWhiteSpace(decodedScript))
- candidates.Insert(0, decodedScript);
-
- foreach (string sourceText in candidates)
- {
- string objectText = ExtractObjectByBraces(sourceText, "new Playerjs");
- if (string.IsNullOrWhiteSpace(objectText))
- objectText = ExtractObjectByBraces(sourceText, "Playerjs({");
-
- string searchText = string.IsNullOrWhiteSpace(objectText) ? sourceText : objectText;
-
- string fileValue = ExtractJsValue(searchText, "file");
- if (fileValue == null && !string.IsNullOrWhiteSpace(objectText))
- fileValue = ExtractJsValue(sourceText, "file");
- if (fileValue == null)
- continue;
-
- string titleValue = ExtractJsValue(searchText, "title");
- object parsedFile = ParsePlayerFileValue(fileValue, sourceText);
-
- return new PlayerPayload
- {
- Title = Nullish(titleValue),
- FilePayload = parsedFile
- };
- }
-
- return null;
- }
-
- private object ParsePlayerFileValue(string rawValue, string contextText)
- {
- string text = rawValue?.Trim();
- if (string.IsNullOrWhiteSpace(text))
- return rawValue;
-
- if (text.StartsWith("[") || text.StartsWith("{"))
- {
- JsonNode loaded = LoadJsonLoose(text);
- if (loaded != null)
- return loaded;
- }
-
- var parseMatch = _reJsonParseHelper.Match(text);
- if (parseMatch.Success)
- {
- string decoded = DecodeHelperPayload(parseMatch.Groups["fn"].Value, parseMatch.Groups["payload"].Value, contextText);
- if (!string.IsNullOrWhiteSpace(decoded))
- {
- JsonNode loaded = LoadJsonLoose(decoded);
- if (loaded != null)
- return loaded;
- }
- }
-
- var helperMatch = _reHelperCall.Match(text);
- if (helperMatch.Success)
- {
- string decoded = DecodeHelperPayload(helperMatch.Groups["fn"].Value, helperMatch.Groups["payload"].Value, contextText);
- if (!string.IsNullOrWhiteSpace(decoded))
- {
- JsonNode loaded = LoadJsonLoose(decoded);
- if (loaded != null)
- return loaded;
-
- return decoded;
- }
- }
-
- return rawValue;
- }
-
- private string DecodeHelperPayload(string helperName, string payload, string contextText)
- {
- if (string.IsNullOrWhiteSpace(helperName))
- return null;
-
- if (helperName.Equals("atob", StringComparison.OrdinalIgnoreCase))
- {
- byte[] rawBytes = SafeBase64Decode(payload);
- return rawBytes == null ? null : DecodeBytes(rawBytes);
- }
-
- string helperKey = ExtractHelperKey(contextText, helperName);
- if (string.IsNullOrWhiteSpace(helperKey))
- return null;
-
- byte[] keyBytes = Encoding.UTF8.GetBytes(helperKey);
- if (keyBytes.Length == 0)
- return null;
-
- byte[] payloadBytes = SafeBase64Decode(payload);
- if (payloadBytes == null)
- return null;
-
- var decoded = new byte[payloadBytes.Length];
- for (int index = 0; index < payloadBytes.Length; index++)
- decoded[index] = (byte)(payloadBytes[index] ^ keyBytes[index % keyBytes.Length]);
-
- return DecodeBytes(decoded);
- }
-
- private static string ExtractHelperKey(string contextText, string helperName)
- {
- if (string.IsNullOrWhiteSpace(contextText) || string.IsNullOrWhiteSpace(helperName))
- return null;
-
- string pattern = $@"function\s+{Regex.Escape(helperName)}\s*\([^)]*\)\s*\{{[\s\S]*?var\s+k\s*=\s*(['""])(?.*?)\1";
- var match = Regex.Match(contextText, pattern, RegexOptions.IgnoreCase);
- if (!match.Success)
- return null;
-
- return Nullish(match.Groups["key"].Value);
- }
-
- private static string DecodeOuterPlayerScript(string text)
- {
- if (string.IsNullOrWhiteSpace(text))
- return null;
-
- var match = _reAtobLiteral.Match(text);
- if (!match.Success)
- return null;
-
- byte[] rawData = SafeBase64Decode(match.Groups["payload"].Value);
- if (rawData == null || rawData.Length <= 32)
- return null;
-
- byte[] key = rawData.Take(32).ToArray();
- byte[] encryptedData = rawData.Skip(32).ToArray();
- var decoded = new byte[encryptedData.Length];
-
- for (int index = 0; index < encryptedData.Length; index++)
- decoded[index] = (byte)(encryptedData[index] ^ key[index % key.Length]);
-
- return DecodeBytes(decoded);
- }
-
- private static byte[] SafeBase64Decode(string value)
- {
- string text = value?.Trim();
- if (string.IsNullOrWhiteSpace(text))
- return null;
-
- int remainder = text.Length % 4;
- if (remainder != 0)
- text += new string('=', 4 - remainder);
-
- try
- {
- return Convert.FromBase64String(text);
- }
- catch
- {
- return null;
- }
- }
-
- private static string DecodeBytes(byte[] data)
- {
- if (data == null || data.Length == 0)
- return null;
-
- try
- {
- return _utf8Strict.GetString(data);
- }
- catch
- {
- return _latin1.GetString(data);
- }
- }
-
- private static string ExtractObjectByBraces(string text, string anchor)
- {
- if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(anchor))
- return null;
-
- int anchorIndex = text.IndexOf(anchor, StringComparison.OrdinalIgnoreCase);
- if (anchorIndex < 0)
- return null;
-
- int braceIndex = text.IndexOf('{', anchorIndex);
- if (braceIndex < 0)
- return null;
-
- int depth = 0;
- bool escaped = false;
- char? inString = null;
-
- for (int index = braceIndex; index < text.Length; index++)
- {
- char current = text[index];
-
- if (inString.HasValue)
- {
- if (escaped)
- {
- escaped = false;
- continue;
- }
-
- if (current == '\\')
- {
- escaped = true;
- continue;
- }
-
- if (current == inString.Value)
- inString = null;
-
- continue;
- }
-
- if (current == '"' || current == '\'')
- {
- inString = current;
- continue;
- }
-
- if (current == '{')
- {
- depth++;
- continue;
- }
-
- if (current == '}')
- {
- depth--;
- if (depth == 0)
- return text.Substring(braceIndex + 1, index - braceIndex - 1);
- }
- }
-
- return null;
- }
-
- private static string ExtractJsValue(string text, string key)
- {
- if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(key))
- return null;
-
- var match = Regex.Match(text, $@"\b{Regex.Escape(key)}\b\s*:\s*", RegexOptions.IgnoreCase);
- if (!match.Success)
- return null;
-
- int index = match.Index + match.Length;
- while (index < text.Length && char.IsWhiteSpace(text[index]))
- index++;
-
- if (index >= text.Length)
- return null;
-
- char token = text[index];
- if (token == '"' || token == '\'')
- {
- var (value, _) = ReadJsString(text, index);
- return value;
- }
-
- if (token == '[')
- {
- int endIndex = FindMatchingBracket(text, index, '[', ']');
- return endIndex >= index ? text.Substring(index, endIndex - index + 1) : null;
- }
-
- if (token == '{')
- {
- int endIndex = FindMatchingBracket(text, index, '{', '}');
- return endIndex >= index ? text.Substring(index, endIndex - index + 1) : null;
- }
-
- int stopIndex = index;
- while (stopIndex < text.Length && text[stopIndex] != ',' && text[stopIndex] != '}' && text[stopIndex] != '\n' && text[stopIndex] != '\r')
- stopIndex++;
-
- return text.Substring(index, stopIndex - index).Trim();
- }
-
- private static (string value, int nextIndex) ReadJsString(string text, int startIndex)
- {
- if (string.IsNullOrWhiteSpace(text) || startIndex < 0 || startIndex >= text.Length)
- return (null, -1);
-
- char quote = text[startIndex];
- if (quote != '"' && quote != '\'')
- return (null, -1);
-
- var buffer = new StringBuilder();
- bool escaped = false;
-
- for (int index = startIndex + 1; index < text.Length; index++)
- {
- char current = text[index];
-
- if (escaped)
- {
- buffer.Append(current);
- escaped = false;
- continue;
- }
-
- if (current == '\\')
- {
- escaped = true;
- continue;
- }
-
- if (current == quote)
- return (buffer.ToString(), index + 1);
-
- buffer.Append(current);
- }
-
- return (null, -1);
- }
-
- private static int FindMatchingBracket(string text, int startIndex, char openChar, char closeChar)
- {
- if (string.IsNullOrWhiteSpace(text) || startIndex < 0 || startIndex >= text.Length || text[startIndex] != openChar)
- return -1;
-
- int depth = 0;
- bool escaped = false;
- char? inString = null;
-
- for (int index = startIndex; index < text.Length; index++)
- {
- char current = text[index];
-
- if (inString.HasValue)
- {
- if (escaped)
- {
- escaped = false;
- continue;
- }
-
- if (current == '\\')
- {
- escaped = true;
- continue;
- }
-
- if (current == inString.Value)
- inString = null;
-
- continue;
- }
-
- if (current == '"' || current == '\'')
- {
- inString = current;
- continue;
- }
-
- if (current == openChar)
- {
- depth++;
- continue;
- }
-
- if (current == closeChar)
- {
- depth--;
- if (depth == 0)
- return index;
- }
- }
-
- return -1;
- }
-
private static JsonNode LoadJsonLoose(string value)
{
- string text = value?.Trim();
- if (string.IsNullOrWhiteSpace(text))
- return null;
-
- string normalized = WebUtility.HtmlDecode(text).Replace("\\/", "/");
- string unescapedQuotes = normalized.Replace("\\'", "'").Replace("\\\"", "\"");
- var candidates = new[]
- {
- normalized,
- unescapedQuotes,
- RemoveTrailingCommas(normalized),
- RemoveTrailingCommas(unescapedQuotes)
- };
-
- foreach (string candidate in candidates.Distinct(StringComparer.Ordinal))
- {
- if (string.IsNullOrWhiteSpace(candidate))
- continue;
-
- try
- {
- return JsonNode.Parse(candidate);
- }
- catch
- {
- }
- }
-
- return null;
- }
-
- private static string RemoveTrailingCommas(string value)
- {
- return string.IsNullOrWhiteSpace(value) ? value : _reTrailingComma.Replace(value, "$1");
+ return PlayerJsDecoder.LoadJsonLoose(value);
}
private static string Nullish(string value)
{
- string text = value?.Trim();
- if (string.IsNullOrWhiteSpace(text))
- return null;
-
- if (text.Equals("null", StringComparison.OrdinalIgnoreCase) ||
- text.Equals("none", StringComparison.OrdinalIgnoreCase) ||
- text.Equals("undefined", StringComparison.OrdinalIgnoreCase))
- return null;
-
- return text;
+ return PlayerJsDecoder.Nullish(value);
}
private static bool TryGetArray(JsonObject obj, string key, out JsonArray array)
@@ -1120,13 +693,6 @@ namespace LME.NMoonAnime
return TimeSpan.FromMinutes(ctime);
}
- private sealed class PlayerPayload
- {
- public string Title { get; set; }
-
- public object FilePayload { get; set; }
- }
-
private sealed class NMoonAnimeMovieEntry
{
public string Title { get; set; }
diff --git a/LME.Shared/GlobalUsings.cs b/LME.Shared/GlobalUsings.cs
index c3d3cbf..3a1f874 100644
--- a/LME.Shared/GlobalUsings.cs
+++ b/LME.Shared/GlobalUsings.cs
@@ -1,6 +1,3 @@
-global using Shared.Services;
-global using Shared.Services.Hybrid;
-global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;
global using LME.Common.Online;
global using LME.Common.Update;
diff --git a/LME.Shared/LME.Shared.csproj b/LME.Shared/LME.Shared.csproj
new file mode 100644
index 0000000..393ea31
--- /dev/null
+++ b/LME.Shared/LME.Shared.csproj
@@ -0,0 +1,12 @@
+
+
+ net10.0
+ library
+ true
+
+
+
+ ..\..\Shared.dll
+
+
+
diff --git a/LME.Shared/Models/PlayerPayload.cs b/LME.Shared/Models/PlayerPayload.cs
new file mode 100644
index 0000000..c771668
--- /dev/null
+++ b/LME.Shared/Models/PlayerPayload.cs
@@ -0,0 +1,9 @@
+namespace LME.Shared.Models
+{
+ public sealed class PlayerPayload
+ {
+ public string Title { get; set; }
+
+ public object FilePayload { get; set; }
+ }
+}
diff --git a/LME.Shared/PlayerJsDecoder.cs b/LME.Shared/PlayerJsDecoder.cs
new file mode 100644
index 0000000..dcaec37
--- /dev/null
+++ b/LME.Shared/PlayerJsDecoder.cs
@@ -0,0 +1,455 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Text.RegularExpressions;
+using LME.Shared.Models;
+
+namespace LME.Shared
+{
+ public static class PlayerJsDecoder
+ {
+ private static readonly Regex _reAtobLiteral = new Regex(@"atob\(\s*(['""])(?[A-Za-z0-9+/=]+)\1\s*\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
+ private static readonly Regex _reJsonParseHelper = new Regex(@"JSON\.parse\(\s*(?[A-Za-z_$][\w$]*)\(\s*(?['""])(?.*?)(\k)\s*\)\s*\)", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
+ private static readonly Regex _reHelperCall = new Regex(@"^\s*(?[A-Za-z_$][\w$]*)\(\s*(?['""])(?.*?)(\k)\s*\)\s*$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
+ private static readonly Regex _reTrailingComma = new Regex(@",\s*([}\]])", RegexOptions.Compiled);
+ private static readonly UTF8Encoding _utf8Strict = new UTF8Encoding(false, true);
+ private static readonly Encoding _latin1 = Encoding.GetEncoding("ISO-8859-1");
+
+ public static PlayerPayload ExtractPlayerPayload(string htmlText)
+ {
+ string cleanHtml = WebUtility.HtmlDecode(htmlText ?? string.Empty);
+ if (string.IsNullOrWhiteSpace(cleanHtml))
+ return null;
+
+ var candidates = new List { cleanHtml };
+ string decodedScript = DecodeOuterPlayerScript(cleanHtml);
+ if (!string.IsNullOrWhiteSpace(decodedScript))
+ candidates.Insert(0, decodedScript);
+
+ foreach (string sourceText in candidates)
+ {
+ string objectText = ExtractObjectByBraces(sourceText, "new Playerjs");
+ if (string.IsNullOrWhiteSpace(objectText))
+ objectText = ExtractObjectByBraces(sourceText, "Playerjs({");
+
+ string searchText = string.IsNullOrWhiteSpace(objectText) ? sourceText : objectText;
+
+ string fileValue = ExtractJsValue(searchText, "file");
+ if (fileValue == null && !string.IsNullOrWhiteSpace(objectText))
+ fileValue = ExtractJsValue(sourceText, "file");
+ if (fileValue == null)
+ continue;
+
+ string titleValue = ExtractJsValue(searchText, "title");
+ object parsedFile = ParsePlayerFileValue(fileValue, sourceText);
+
+ return new PlayerPayload
+ {
+ Title = Nullish(titleValue),
+ FilePayload = parsedFile
+ };
+ }
+
+ return null;
+ }
+
+ private static object ParsePlayerFileValue(string rawValue, string contextText)
+ {
+ string text = rawValue?.Trim();
+ if (string.IsNullOrWhiteSpace(text))
+ return rawValue;
+
+ if (text.StartsWith("[") || text.StartsWith("{"))
+ {
+ JsonNode loaded = LoadJsonLoose(text);
+ if (loaded != null)
+ return loaded;
+ }
+
+ var parseMatch = _reJsonParseHelper.Match(text);
+ if (parseMatch.Success)
+ {
+ string decoded = DecodeHelperPayload(parseMatch.Groups["fn"].Value, parseMatch.Groups["payload"].Value, contextText);
+ if (!string.IsNullOrWhiteSpace(decoded))
+ {
+ JsonNode loaded = LoadJsonLoose(decoded);
+ if (loaded != null)
+ return loaded;
+ }
+ }
+
+ var helperMatch = _reHelperCall.Match(text);
+ if (helperMatch.Success)
+ {
+ string decoded = DecodeHelperPayload(helperMatch.Groups["fn"].Value, helperMatch.Groups["payload"].Value, contextText);
+ if (!string.IsNullOrWhiteSpace(decoded))
+ {
+ JsonNode loaded = LoadJsonLoose(decoded);
+ if (loaded != null)
+ return loaded;
+
+ return decoded;
+ }
+ }
+
+ return rawValue;
+ }
+
+ private static string DecodeHelperPayload(string helperName, string payload, string contextText)
+ {
+ if (string.IsNullOrWhiteSpace(helperName))
+ return null;
+
+ if (helperName.Equals("atob", StringComparison.OrdinalIgnoreCase))
+ {
+ byte[] rawBytes = SafeBase64Decode(payload);
+ return rawBytes == null ? null : DecodeBytes(rawBytes);
+ }
+
+ string helperKey = ExtractHelperKey(contextText, helperName);
+ if (string.IsNullOrWhiteSpace(helperKey))
+ return null;
+
+ byte[] keyBytes = Encoding.UTF8.GetBytes(helperKey);
+ if (keyBytes.Length == 0)
+ return null;
+
+ byte[] payloadBytes = SafeBase64Decode(payload);
+ if (payloadBytes == null)
+ return null;
+
+ var decoded = new byte[payloadBytes.Length];
+ for (int index = 0; index < payloadBytes.Length; index++)
+ decoded[index] = (byte)(payloadBytes[index] ^ keyBytes[index % keyBytes.Length]);
+
+ return DecodeBytes(decoded);
+ }
+
+ private static string ExtractHelperKey(string contextText, string helperName)
+ {
+ if (string.IsNullOrWhiteSpace(contextText) || string.IsNullOrWhiteSpace(helperName))
+ return null;
+
+ string pattern = $@"function\s+{Regex.Escape(helperName)}\s*\([^)]*\)\s*\{{[\s\S]*?var\s+k\s*=\s*(['""])(?.*?)\1";
+ var match = Regex.Match(contextText, pattern, RegexOptions.IgnoreCase);
+ if (!match.Success)
+ return null;
+
+ return Nullish(match.Groups["key"].Value);
+ }
+
+ private static string DecodeOuterPlayerScript(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text))
+ return null;
+
+ var match = _reAtobLiteral.Match(text);
+ if (!match.Success)
+ return null;
+
+ byte[] rawData = SafeBase64Decode(match.Groups["payload"].Value);
+ if (rawData == null || rawData.Length <= 32)
+ return null;
+
+ byte[] key = rawData.Take(32).ToArray();
+ byte[] encryptedData = rawData.Skip(32).ToArray();
+ var decoded = new byte[encryptedData.Length];
+
+ for (int index = 0; index < encryptedData.Length; index++)
+ decoded[index] = (byte)(encryptedData[index] ^ key[index % key.Length]);
+
+ return DecodeBytes(decoded);
+ }
+
+ private static byte[] SafeBase64Decode(string value)
+ {
+ string text = value?.Trim();
+ if (string.IsNullOrWhiteSpace(text))
+ return null;
+
+ int remainder = text.Length % 4;
+ if (remainder != 0)
+ text += new string('=', 4 - remainder);
+
+ try
+ {
+ return Convert.FromBase64String(text);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private static string DecodeBytes(byte[] data)
+ {
+ if (data == null || data.Length == 0)
+ return null;
+
+ try
+ {
+ return _utf8Strict.GetString(data);
+ }
+ catch
+ {
+ return _latin1.GetString(data);
+ }
+ }
+
+ private static string ExtractObjectByBraces(string text, string anchor)
+ {
+ if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(anchor))
+ return null;
+
+ int anchorIndex = text.IndexOf(anchor, StringComparison.OrdinalIgnoreCase);
+ if (anchorIndex < 0)
+ return null;
+
+ int braceIndex = text.IndexOf('{', anchorIndex);
+ if (braceIndex < 0)
+ return null;
+
+ int depth = 0;
+ bool escaped = false;
+ char? inString = null;
+
+ for (int index = braceIndex; index < text.Length; index++)
+ {
+ char current = text[index];
+
+ if (inString.HasValue)
+ {
+ if (escaped)
+ {
+ escaped = false;
+ continue;
+ }
+
+ if (current == '\\')
+ {
+ escaped = true;
+ continue;
+ }
+
+ if (current == inString.Value)
+ inString = null;
+
+ continue;
+ }
+
+ if (current == '"' || current == '\'')
+ {
+ inString = current;
+ continue;
+ }
+
+ if (current == '{')
+ {
+ depth++;
+ continue;
+ }
+
+ if (current == '}')
+ {
+ depth--;
+ if (depth == 0)
+ return text.Substring(braceIndex + 1, index - braceIndex - 1);
+ }
+ }
+
+ return null;
+ }
+
+ private static string ExtractJsValue(string text, string key)
+ {
+ if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(key))
+ return null;
+
+ var match = Regex.Match(text, $@"\b{Regex.Escape(key)}\b\s*:\s*", RegexOptions.IgnoreCase);
+ if (!match.Success)
+ return null;
+
+ int index = match.Index + match.Length;
+ while (index < text.Length && char.IsWhiteSpace(text[index]))
+ index++;
+
+ if (index >= text.Length)
+ return null;
+
+ char token = text[index];
+ if (token == '"' || token == '\'')
+ {
+ var (value, _) = ReadJsString(text, index);
+ return value;
+ }
+
+ if (token == '[')
+ {
+ int endIndex = FindMatchingBracket(text, index, '[', ']');
+ return endIndex >= index ? text.Substring(index, endIndex - index + 1) : null;
+ }
+
+ if (token == '{')
+ {
+ int endIndex = FindMatchingBracket(text, index, '{', '}');
+ return endIndex >= index ? text.Substring(index, endIndex - index + 1) : null;
+ }
+
+ int stopIndex = index;
+ while (stopIndex < text.Length && text[stopIndex] != ',' && text[stopIndex] != '}' && text[stopIndex] != '\n' && text[stopIndex] != '\r')
+ stopIndex++;
+
+ return text.Substring(index, stopIndex - index).Trim();
+ }
+
+ private static (string value, int nextIndex) ReadJsString(string text, int startIndex)
+ {
+ if (string.IsNullOrWhiteSpace(text) || startIndex < 0 || startIndex >= text.Length)
+ return (null, -1);
+
+ char quote = text[startIndex];
+ if (quote != '"' && quote != '\'')
+ return (null, -1);
+
+ var buffer = new StringBuilder();
+ bool escaped = false;
+
+ for (int index = startIndex + 1; index < text.Length; index++)
+ {
+ char current = text[index];
+
+ if (escaped)
+ {
+ buffer.Append(current);
+ escaped = false;
+ continue;
+ }
+
+ if (current == '\\')
+ {
+ escaped = true;
+ continue;
+ }
+
+ if (current == quote)
+ return (buffer.ToString(), index + 1);
+
+ buffer.Append(current);
+ }
+
+ return (null, -1);
+ }
+
+ private static int FindMatchingBracket(string text, int startIndex, char openChar, char closeChar)
+ {
+ if (string.IsNullOrWhiteSpace(text) || startIndex < 0 || startIndex >= text.Length || text[startIndex] != openChar)
+ return -1;
+
+ int depth = 0;
+ bool escaped = false;
+ char? inString = null;
+
+ for (int index = startIndex; index < text.Length; index++)
+ {
+ char current = text[index];
+
+ if (inString.HasValue)
+ {
+ if (escaped)
+ {
+ escaped = false;
+ continue;
+ }
+
+ if (current == '\\')
+ {
+ escaped = true;
+ continue;
+ }
+
+ if (current == inString.Value)
+ inString = null;
+
+ continue;
+ }
+
+ if (current == '"' || current == '\'')
+ {
+ inString = current;
+ continue;
+ }
+
+ if (current == openChar)
+ {
+ depth++;
+ continue;
+ }
+
+ if (current == closeChar)
+ {
+ depth--;
+ if (depth == 0)
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ public static JsonNode LoadJsonLoose(string value)
+ {
+ string text = value?.Trim();
+ if (string.IsNullOrWhiteSpace(text))
+ return null;
+
+ string normalized = WebUtility.HtmlDecode(text).Replace("\\/", "/");
+ string unescapedQuotes = normalized.Replace("\\'", "'").Replace("\\\"", "\"");
+ var candidates = new[]
+ {
+ normalized,
+ unescapedQuotes,
+ RemoveTrailingCommas(normalized),
+ RemoveTrailingCommas(unescapedQuotes)
+ };
+
+ foreach (string candidate in candidates.Distinct(StringComparer.Ordinal))
+ {
+ if (string.IsNullOrWhiteSpace(candidate))
+ continue;
+
+ try
+ {
+ return JsonNode.Parse(candidate);
+ }
+ catch
+ {
+ }
+ }
+
+ return null;
+ }
+
+ private static string RemoveTrailingCommas(string value)
+ {
+ return string.IsNullOrWhiteSpace(value) ? value : _reTrailingComma.Replace(value, "$1");
+ }
+
+ public static string Nullish(string value)
+ {
+ string text = value?.Trim();
+ if (string.IsNullOrWhiteSpace(text))
+ return null;
+
+ if (text.Equals("null", StringComparison.OrdinalIgnoreCase) ||
+ text.Equals("none", StringComparison.OrdinalIgnoreCase) ||
+ text.Equals("undefined", StringComparison.OrdinalIgnoreCase))
+ return null;
+
+ return text;
+ }
+ }
+}