mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-06-17 12:08:54 +00:00
Compare commits
10 Commits
0f6b048545
...
adfa97e810
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adfa97e810 | ||
|
|
5829c65426 | ||
|
|
5e551b2746 | ||
|
|
cfdf0f2d76 | ||
|
|
020f331729 | ||
|
|
fd01af1e2c | ||
|
|
b253a21cdf | ||
|
|
60867dabae | ||
|
|
0f89b08ee1 | ||
|
|
d36f29b7be |
@ -6,11 +6,13 @@ using Shared.Models.Online.Settings;
|
|||||||
using Shared.Models;
|
using Shared.Models;
|
||||||
using Shared.Models.Templates;
|
using Shared.Models.Templates;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using LME.AnimeON.Models;
|
using LME.AnimeON.Models;
|
||||||
|
using LME.Common.Playerjs;
|
||||||
using Shared.Engine;
|
using Shared.Engine;
|
||||||
|
|
||||||
namespace LME.AnimeON
|
namespace LME.AnimeON
|
||||||
@ -183,11 +185,12 @@ namespace LME.AnimeON
|
|||||||
if (string.IsNullOrEmpty(html))
|
if (string.IsNullOrEmpty(html))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var match = System.Text.RegularExpressions.Regex.Match(html, @"file:\s*""([^""]+\.m3u8)""");
|
var payload = PlayerJsDecoder.ExtractPlayerPayload(html);
|
||||||
if (match.Success)
|
if (payload?.FilePayload == null)
|
||||||
{
|
return null;
|
||||||
return match.Groups[1].Value;
|
|
||||||
}
|
var streamUrls = ExtractStreamUrls(payload.FilePayload);
|
||||||
|
return streamUrls?.FirstOrDefault();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -197,6 +200,61 @@ namespace LME.AnimeON
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<string> ExtractStreamUrls(object filePayload)
|
||||||
|
{
|
||||||
|
var urls = new List<string>();
|
||||||
|
if (filePayload == null)
|
||||||
|
return urls;
|
||||||
|
|
||||||
|
// Обробка string значення
|
||||||
|
if (filePayload is string strPayload)
|
||||||
|
{
|
||||||
|
urls.Add(strPayload);
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обробка JsonValue
|
||||||
|
if (filePayload is JsonValue jsonValue && jsonValue.TryGetValue<string>(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<string>(out string itemStr))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(itemStr))
|
||||||
|
urls.Add(itemStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@ -26,7 +26,7 @@ namespace LME.AnimeON
|
|||||||
{
|
{
|
||||||
public class ModInit : IModuleLoaded
|
public class ModInit : IModuleLoaded
|
||||||
{
|
{
|
||||||
public static double Version => 4.1;
|
public static double Version => 4.2;
|
||||||
|
|
||||||
public static OnlinesSettings AnimeON;
|
public static OnlinesSettings AnimeON;
|
||||||
public static bool ApnHostProvided;
|
public static bool ApnHostProvided;
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
"../LME.Shared/GlobalUsings.cs",
|
"../LME.Shared/GlobalUsings.cs",
|
||||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||||
"../LME.Shared/Apn/ApnHelper.cs"
|
"../LME.Shared/Apn/ApnHelper.cs",
|
||||||
|
"../LME.Shared/Playerjs/PlayerJsDecoder.cs"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,9 +95,15 @@ namespace LME.JackTor.Controllers
|
|||||||
var seasonTpl = new SeasonTpl(quality: quality);
|
var seasonTpl = new SeasonTpl(quality: quality);
|
||||||
foreach (int season in seasons)
|
foreach (int season in seasons)
|
||||||
{
|
{
|
||||||
|
int seasonYear = torrents
|
||||||
|
.Where(i => i.Seasons != null && i.Seasons.Contains(season) && i.ExtractedYear > 1900)
|
||||||
|
.Select(i => i.ExtractedYear)
|
||||||
|
.DefaultIfEmpty(year)
|
||||||
|
.Min();
|
||||||
|
|
||||||
seasonTpl.Append(
|
seasonTpl.Append(
|
||||||
$"{season} сезон",
|
$"{season} сезон",
|
||||||
$"{host}/lite/lme_jacktor?rjson={rjson}&title={enTitle}&original_title={enOriginal}&year={year}&original_language={original_language}&serial=1&s={season}",
|
$"{host}/lite/lme_jacktor?rjson={rjson}&title={enTitle}&original_title={enOriginal}&year={seasonYear}&original_language={original_language}&serial=1&s={season}",
|
||||||
season);
|
season);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -335,7 +335,7 @@ namespace LME.JackTor
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
int extractedYear = ExtractYear(searchable);
|
int extractedYear = ExtractYear(searchable);
|
||||||
if (year > 1900 && extractedYear > 1900 && Math.Abs(extractedYear - year) > yearTolerance)
|
if (serial != 1 && year > 1900 && extractedYear > 1900 && Math.Abs(extractedYear - year) > yearTolerance)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int[] seasons = ParseSeasons(searchable);
|
int[] seasons = ParseSeasons(searchable);
|
||||||
|
|||||||
@ -18,7 +18,7 @@ namespace LME.JackTor
|
|||||||
{
|
{
|
||||||
public class ModInit : IModuleLoaded
|
public class ModInit : IModuleLoaded
|
||||||
{
|
{
|
||||||
public static double Version => 2.1;
|
public static double Version => 2.2;
|
||||||
|
|
||||||
public static JackTorSettings JackTor;
|
public static JackTorSettings JackTor;
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,13 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using LME.Mikai.Models;
|
using LME.Mikai.Models;
|
||||||
|
using LME.Common.Playerjs;
|
||||||
using Shared;
|
using Shared;
|
||||||
using Shared.Engine;
|
using Shared.Engine;
|
||||||
using Shared.Models;
|
using Shared.Models;
|
||||||
@ -158,16 +160,73 @@ namespace LME.Mikai
|
|||||||
if (string.IsNullOrEmpty(html))
|
if (string.IsNullOrEmpty(html))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var match = System.Text.RegularExpressions.Regex.Match(html, @"file:\s*""([^""]+\.m3u8)""");
|
var payload = PlayerJsDecoder.ExtractPlayerPayload(html);
|
||||||
if (match.Success)
|
if (payload?.FilePayload == null)
|
||||||
return match.Groups[1].Value;
|
return null;
|
||||||
|
|
||||||
|
var streamUrls = ExtractStreamUrls(payload.FilePayload);
|
||||||
|
return streamUrls?.FirstOrDefault();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_onLog($"Mikai ParseMoonAnimePage error: {ex.Message}");
|
_onLog($"Mikai ParseMoonAnimePage error: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> ExtractStreamUrls(object filePayload)
|
||||||
|
{
|
||||||
|
var urls = new List<string>();
|
||||||
|
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<string>(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<string>(out string itemStr))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(itemStr))
|
||||||
|
urls.Add(itemStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls;
|
||||||
}
|
}
|
||||||
|
|
||||||
string AshdiRequestUrl(string url)
|
string AshdiRequestUrl(string url)
|
||||||
|
|||||||
@ -25,7 +25,7 @@ namespace LME.Mikai
|
|||||||
{
|
{
|
||||||
public class ModInit : IModuleLoaded
|
public class ModInit : IModuleLoaded
|
||||||
{
|
{
|
||||||
public static double Version => 4.1;
|
public static double Version => 4.2;
|
||||||
|
|
||||||
public static OnlinesSettings Mikai;
|
public static OnlinesSettings Mikai;
|
||||||
public static bool ApnHostProvided;
|
public static bool ApnHostProvided;
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
"../LME.Shared/GlobalUsings.cs",
|
"../LME.Shared/GlobalUsings.cs",
|
||||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||||
"../LME.Shared/Apn/ApnHelper.cs"
|
"../LME.Shared/Apn/ApnHelper.cs",
|
||||||
|
"../LME.Shared/Playerjs/PlayerJsDecoder.cs"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using LME.NMoonAnime.Models;
|
using LME.NMoonAnime.Models;
|
||||||
|
using LME.Common.Playerjs;
|
||||||
using Shared;
|
using Shared;
|
||||||
using Shared.Engine;
|
using Shared.Engine;
|
||||||
using Shared.Models;
|
using Shared.Models;
|
||||||
@ -29,12 +30,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 _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 _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*(['""])(?<payload>[A-Za-z0-9+/=]+)\1\s*\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
|
||||||
private static readonly Regex _reJsonParseHelper = new Regex(@"JSON\.parse\(\s*(?<fn>[A-Za-z_$][\w$]*)\(\s*(?<quote>['""])(?<payload>.*?)(\k<quote>)\s*\)\s*\)", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
|
|
||||||
private static readonly Regex _reHelperCall = new Regex(@"^\s*(?<fn>[A-Za-z_$][\w$]*)\(\s*(?<quote>['""])(?<payload>.*?)(\k<quote>)\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<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
|
public NMoonAnimeInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
|
||||||
{
|
{
|
||||||
@ -241,7 +236,7 @@ namespace LME.NMoonAnime
|
|||||||
IsSeries = false
|
IsSeries = false
|
||||||
};
|
};
|
||||||
|
|
||||||
var payload = ExtractPlayerPayload(html);
|
var payload = PlayerJsDecoder.ExtractPlayerPayload(html);
|
||||||
if (payload == null)
|
if (payload == null)
|
||||||
return content;
|
return content;
|
||||||
|
|
||||||
@ -495,437 +490,14 @@ namespace LME.NMoonAnime
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlayerPayload ExtractPlayerPayload(string htmlText)
|
|
||||||
{
|
|
||||||
string cleanHtml = WebUtility.HtmlDecode(htmlText ?? string.Empty);
|
|
||||||
if (string.IsNullOrWhiteSpace(cleanHtml))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var candidates = new List<string> { 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*(['""])(?<key>.*?)\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)
|
private static JsonNode LoadJsonLoose(string value)
|
||||||
{
|
{
|
||||||
string text = value?.Trim();
|
return PlayerJsDecoder.LoadJsonLoose(value);
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Nullish(string value)
|
private static string Nullish(string value)
|
||||||
{
|
{
|
||||||
string text = value?.Trim();
|
return PlayerJsDecoder.Nullish(value);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryGetArray(JsonObject obj, string key, out JsonArray array)
|
private static bool TryGetArray(JsonObject obj, string key, out JsonArray array)
|
||||||
@ -1120,13 +692,6 @@ namespace LME.NMoonAnime
|
|||||||
return TimeSpan.FromMinutes(ctime);
|
return TimeSpan.FromMinutes(ctime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class PlayerPayload
|
|
||||||
{
|
|
||||||
public string Title { get; set; }
|
|
||||||
|
|
||||||
public object FilePayload { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class NMoonAnimeMovieEntry
|
private sealed class NMoonAnimeMovieEntry
|
||||||
{
|
{
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
"../LME.Shared/GlobalUsings.cs",
|
"../LME.Shared/GlobalUsings.cs",
|
||||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||||
"../LME.Shared/Apn/ApnHelper.cs"
|
"../LME.Shared/Apn/ApnHelper.cs",
|
||||||
|
"../LME.Shared/Playerjs/PlayerJsDecoder.cs"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
12
LME.Shared/LME.Shared.csproj
Normal file
12
LME.Shared/LME.Shared.csproj
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<OutputType>library</OutputType>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Shared">
|
||||||
|
<HintPath>..\..\Shared.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
461
LME.Shared/Playerjs/PlayerJsDecoder.cs
Normal file
461
LME.Shared/Playerjs/PlayerJsDecoder.cs
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
namespace LME.Common.Playerjs
|
||||||
|
{
|
||||||
|
public static class PlayerJsDecoder
|
||||||
|
{
|
||||||
|
private static readonly Regex _reAtobLiteral = new Regex(@"atob\(\s*(['""])(?<payload>[A-Za-z0-9+/=]+)\1\s*\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
private static readonly Regex _reJsonParseHelper = new Regex(@"JSON\.parse\(\s*(?<fn>[A-Za-z_$][\w$]*)\(\s*(?<quote>['""])(?<payload>.*?)(\k<quote>)\s*\)\s*\)", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
|
||||||
|
private static readonly Regex _reHelperCall = new Regex(@"^\s*(?<fn>[A-Za-z_$][\w$]*)\(\s*(?<quote>['""])(?<payload>.*?)(\k<quote>)\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<string> { 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*(['""])(?<key>.*?)\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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlayerPayload
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
public object FilePayload { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user