mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-06-17 12:08:54 +00:00
feat(moon_decoder): add moon anime decryption and multi-quality streams
This commit is contained in:
parent
6a398317a4
commit
41ba6dc878
@ -169,15 +169,82 @@ namespace LME.AnimeON
|
||||
return JsonSerializer.Deserialize<EpisodeModel>(episodesJson);
|
||||
}
|
||||
|
||||
#region MoonAnime Decryption
|
||||
private static string CleanMoonUrl(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return url;
|
||||
|
||||
string cleaned = Regex.Replace(url, @"([?&])player=[^&]*", "$1", RegexOptions.IgnoreCase);
|
||||
cleaned = cleaned.Replace("?&", "?").Replace("&&", "&").TrimEnd('?', '&');
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
public static string MoonDecode(string base64Input)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] raw = Convert.FromBase64String(base64Input);
|
||||
const int KeySize = 32;
|
||||
const int HeaderSize = 1 + KeySize;
|
||||
|
||||
if (raw.Length < HeaderSize)
|
||||
return null;
|
||||
|
||||
byte state = raw[0];
|
||||
byte[] key = new byte[KeySize];
|
||||
Array.Copy(raw, 1, key, 0, KeySize);
|
||||
|
||||
int payloadLen = raw.Length - HeaderSize;
|
||||
byte[] payload = new byte[payloadLen];
|
||||
Array.Copy(raw, HeaderSize, payload, 0, payloadLen);
|
||||
|
||||
for (int i = 0; i < payload.Length; i++)
|
||||
{
|
||||
byte encrypted = payload[i];
|
||||
byte keyByte = key[i % KeySize];
|
||||
|
||||
payload[i] = (byte)(encrypted ^ keyByte ^ state);
|
||||
state = (byte)((encrypted + keyByte) & 0xFF);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(payload);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string MoonXorDecrypt(string file, string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes(key);
|
||||
byte[] data = Convert.FromBase64String(file);
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = (byte)(data[i] ^ keyBytes[i % keyBytes.Length]);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<string> ParseMoonAnimePage(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
string requestUrl = $"{url}?player=animeon.club";
|
||||
string requestUrl = CleanMoonUrl(url);
|
||||
var headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
||||
new HeadersModel("Referer", "https://animeon.club/")
|
||||
new HeadersModel("User-Agent", "Mozilla/5.0")
|
||||
};
|
||||
|
||||
_onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {requestUrl}");
|
||||
@ -185,13 +252,57 @@ namespace LME.AnimeON
|
||||
if (string.IsNullOrEmpty(html))
|
||||
return null;
|
||||
|
||||
var payload = PlayerJsDecoder.ExtractPlayerPayload(html);
|
||||
if (payload?.FilePayload == null)
|
||||
return null;
|
||||
var atobMatch = Regex.Match(html, @"=atob\(""([^""]+)""\)");
|
||||
if (!atobMatch.Success)
|
||||
{
|
||||
atobMatch = Regex.Match(html, @"=atob\('([^']+)'\)");
|
||||
}
|
||||
|
||||
if (atobMatch.Success)
|
||||
{
|
||||
string blob = atobMatch.Groups[1].Value;
|
||||
string decryptedJs = MoonDecode(blob);
|
||||
if (!string.IsNullOrEmpty(decryptedJs))
|
||||
{
|
||||
var keyMatch = Regex.Match(decryptedJs, @"var k=""([^""]+)""");
|
||||
if (!keyMatch.Success)
|
||||
keyMatch = Regex.Match(decryptedJs, @"var k='([^']+)'");
|
||||
|
||||
var fileMatch = Regex.Match(decryptedJs, @"file\s*:\s*_0xd\(""([^""]+)""\)");
|
||||
if (!fileMatch.Success)
|
||||
fileMatch = Regex.Match(decryptedJs, @"file\s*:\s*_0xd\('([^']+)'\)");
|
||||
|
||||
if (keyMatch.Success && fileMatch.Success)
|
||||
{
|
||||
string key = keyMatch.Groups[1].Value;
|
||||
string fileEncrypted = fileMatch.Groups[1].Value;
|
||||
string streams = MoonXorDecrypt(fileEncrypted, key);
|
||||
if (!string.IsNullOrEmpty(streams))
|
||||
{
|
||||
return streams;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var directFileMatch = Regex.Match(decryptedJs, @"file\s*:\s*""([^""]+)""");
|
||||
if (!directFileMatch.Success)
|
||||
directFileMatch = Regex.Match(decryptedJs, @"file\s*:\s*'([^']+)'");
|
||||
|
||||
if (directFileMatch.Success)
|
||||
{
|
||||
return directFileMatch.Groups[1].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var payload = PlayerJsDecoder.ExtractPlayerPayload(html);
|
||||
if (payload?.FilePayload != null)
|
||||
{
|
||||
var streamUrls = ExtractStreamUrls(payload.FilePayload);
|
||||
return streamUrls?.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"AnimeON ParseMoonAnimePage error: {ex.Message}");
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using Shared.Engine;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
@ -439,8 +439,59 @@ namespace LME.AnimeON.Controllers
|
||||
catch { }
|
||||
}
|
||||
|
||||
string streamUrl = BuildStreamUrl(init, streamLink, streamHeaders, forceProxy);
|
||||
var streamQuality = new StreamQualityTpl();
|
||||
string streamUrl = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(streamLink) && (streamLink.StartsWith("[") || streamLink.Contains("]http")))
|
||||
{
|
||||
var bracketMatches = Regex.Matches(streamLink, @"\[(?<quality>[^\]]+)\](?<url>https?://[^,\[]+)", RegexOptions.IgnoreCase);
|
||||
foreach (Match match in bracketMatches)
|
||||
{
|
||||
string quality = match.Groups["quality"].Value;
|
||||
string urlVal = match.Groups["url"].Value?.Trim()?.TrimEnd(',');
|
||||
if (string.IsNullOrWhiteSpace(urlVal))
|
||||
continue;
|
||||
|
||||
var headers = streamHeaders;
|
||||
if (urlVal.Contains("moonanime.art") || urlVal.Contains("mooncdn.space"))
|
||||
{
|
||||
headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
||||
new HeadersModel("Referer", "https://moonanime.art/")
|
||||
};
|
||||
}
|
||||
|
||||
string qualityStreamUrl = BuildStreamUrl(init, urlVal, headers, forceProxy);
|
||||
streamQuality.Append(qualityStreamUrl, quality);
|
||||
}
|
||||
|
||||
if (streamQuality.Any())
|
||||
{
|
||||
var first = streamQuality.Firts();
|
||||
streamUrl = first.link;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(streamUrl))
|
||||
{
|
||||
var headers = streamHeaders;
|
||||
if (!string.IsNullOrEmpty(streamLink) && (streamLink.Contains("moonanime.art") || streamLink.Contains("mooncdn.space")))
|
||||
{
|
||||
headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
||||
new HeadersModel("Referer", "https://moonanime.art/")
|
||||
};
|
||||
}
|
||||
streamUrl = BuildStreamUrl(init, streamLink, headers, forceProxy);
|
||||
}
|
||||
|
||||
OnLog("AnimeON Play: return call JSON");
|
||||
if (streamQuality.Any())
|
||||
{
|
||||
return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title ?? string.Empty, subtitles: subtitleTpl, streamquality: streamQuality), "application/json; charset=utf-8"));
|
||||
}
|
||||
return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title ?? string.Empty, subtitles: subtitleTpl), "application/json; charset=utf-8"));
|
||||
}
|
||||
|
||||
|
||||
@ -254,7 +254,58 @@ namespace LME.Mikai.Controllers
|
||||
catch { }
|
||||
}
|
||||
|
||||
string streamUrl = BuildStreamUrl(init, streamLink, streamHeaders, forceProxy);
|
||||
var streamQuality = new StreamQualityTpl();
|
||||
string streamUrl = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(streamLink) && (streamLink.StartsWith("[") || streamLink.Contains("]http")))
|
||||
{
|
||||
var bracketMatches = Regex.Matches(streamLink, @"\[(?<quality>[^\]]+)\](?<url>https?://[^,\[]+)", RegexOptions.IgnoreCase);
|
||||
foreach (Match match in bracketMatches)
|
||||
{
|
||||
string quality = match.Groups["quality"].Value;
|
||||
string urlVal = match.Groups["url"].Value?.Trim()?.TrimEnd(',');
|
||||
if (string.IsNullOrWhiteSpace(urlVal))
|
||||
continue;
|
||||
|
||||
var headers = streamHeaders;
|
||||
if (urlVal.Contains("moonanime.art") || urlVal.Contains("mooncdn.space"))
|
||||
{
|
||||
headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
||||
new HeadersModel("Referer", "https://moonanime.art/")
|
||||
};
|
||||
}
|
||||
|
||||
string qualityStreamUrl = BuildStreamUrl(init, urlVal, headers, forceProxy);
|
||||
streamQuality.Append(qualityStreamUrl, quality);
|
||||
}
|
||||
|
||||
if (streamQuality.Any())
|
||||
{
|
||||
var first = streamQuality.Firts();
|
||||
streamUrl = first.link;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(streamUrl))
|
||||
{
|
||||
var headers = streamHeaders;
|
||||
if (!string.IsNullOrEmpty(streamLink) && (streamLink.Contains("moonanime.art") || streamLink.Contains("mooncdn.space")))
|
||||
{
|
||||
headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
||||
new HeadersModel("Referer", "https://moonanime.art/")
|
||||
};
|
||||
}
|
||||
streamUrl = BuildStreamUrl(init, streamLink, headers, forceProxy);
|
||||
}
|
||||
|
||||
if (streamQuality.Any())
|
||||
{
|
||||
return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title ?? string.Empty, subtitles: subtitleTpl, streamquality: streamQuality), "application/json; charset=utf-8"));
|
||||
}
|
||||
return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title ?? string.Empty, subtitles: subtitleTpl), "application/json; charset=utf-8"));
|
||||
}
|
||||
|
||||
|
||||
@ -137,22 +137,82 @@ namespace LME.Mikai
|
||||
return url;
|
||||
}
|
||||
|
||||
#region MoonAnime Decryption
|
||||
private static string CleanMoonUrl(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return url;
|
||||
|
||||
string cleaned = Regex.Replace(url, @"([?&])player=[^&]*", "$1", RegexOptions.IgnoreCase);
|
||||
cleaned = cleaned.Replace("?&", "?").Replace("&&", "&").TrimEnd('?', '&');
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
public static string MoonDecode(string base64Input)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] raw = Convert.FromBase64String(base64Input);
|
||||
const int KeySize = 32;
|
||||
const int HeaderSize = 1 + KeySize;
|
||||
|
||||
if (raw.Length < HeaderSize)
|
||||
return null;
|
||||
|
||||
byte state = raw[0];
|
||||
byte[] key = new byte[KeySize];
|
||||
Array.Copy(raw, 1, key, 0, KeySize);
|
||||
|
||||
int payloadLen = raw.Length - HeaderSize;
|
||||
byte[] payload = new byte[payloadLen];
|
||||
Array.Copy(raw, HeaderSize, payload, 0, payloadLen);
|
||||
|
||||
for (int i = 0; i < payload.Length; i++)
|
||||
{
|
||||
byte encrypted = payload[i];
|
||||
byte keyByte = key[i % KeySize];
|
||||
|
||||
payload[i] = (byte)(encrypted ^ keyByte ^ state);
|
||||
state = (byte)((encrypted + keyByte) & 0xFF);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(payload);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string MoonXorDecrypt(string file, string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes(key);
|
||||
byte[] data = Convert.FromBase64String(file);
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = (byte)(data[i] ^ keyBytes[i % keyBytes.Length]);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<string> ParseMoonAnimePage(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
string requestUrl = url;
|
||||
if (!requestUrl.Contains("player=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
requestUrl = requestUrl.Contains("?")
|
||||
? $"{requestUrl}&player=mikai.me"
|
||||
: $"{requestUrl}?player=mikai.me";
|
||||
}
|
||||
|
||||
string requestUrl = CleanMoonUrl(url);
|
||||
var headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
||||
new HeadersModel("Referer", _init.host)
|
||||
new HeadersModel("User-Agent", "Mozilla/5.0")
|
||||
};
|
||||
|
||||
_onLog($"Mikai: using proxy {_proxyManager.CurrentProxyIp} for {requestUrl}");
|
||||
@ -160,18 +220,63 @@ namespace LME.Mikai
|
||||
if (string.IsNullOrEmpty(html))
|
||||
return null;
|
||||
|
||||
var payload = PlayerJsDecoder.ExtractPlayerPayload(html);
|
||||
if (payload?.FilePayload == null)
|
||||
return null;
|
||||
var atobMatch = Regex.Match(html, @"=atob\(""([^""]+)""\)");
|
||||
if (!atobMatch.Success)
|
||||
{
|
||||
atobMatch = Regex.Match(html, @"=atob\('([^']+)'\)");
|
||||
}
|
||||
|
||||
if (atobMatch.Success)
|
||||
{
|
||||
string blob = atobMatch.Groups[1].Value;
|
||||
string decryptedJs = MoonDecode(blob);
|
||||
if (!string.IsNullOrEmpty(decryptedJs))
|
||||
{
|
||||
var keyMatch = Regex.Match(decryptedJs, @"var k=""([^""]+)""");
|
||||
if (!keyMatch.Success)
|
||||
keyMatch = Regex.Match(decryptedJs, @"var k='([^']+)'");
|
||||
|
||||
var fileMatch = Regex.Match(decryptedJs, @"file\s*:\s*_0xd\(""([^""]+)""\)");
|
||||
if (!fileMatch.Success)
|
||||
fileMatch = Regex.Match(decryptedJs, @"file\s*:\s*_0xd\('([^']+)'\)");
|
||||
|
||||
if (keyMatch.Success && fileMatch.Success)
|
||||
{
|
||||
string key = keyMatch.Groups[1].Value;
|
||||
string fileEncrypted = fileMatch.Groups[1].Value;
|
||||
string streams = MoonXorDecrypt(fileEncrypted, key);
|
||||
if (!string.IsNullOrEmpty(streams))
|
||||
{
|
||||
return streams;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var directFileMatch = Regex.Match(decryptedJs, @"file\s*:\s*""([^""]+)""");
|
||||
if (!directFileMatch.Success)
|
||||
directFileMatch = Regex.Match(decryptedJs, @"file\s*:\s*'([^']+)'");
|
||||
|
||||
if (directFileMatch.Success)
|
||||
{
|
||||
return directFileMatch.Groups[1].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var payload = PlayerJsDecoder.ExtractPlayerPayload(html);
|
||||
if (payload?.FilePayload != null)
|
||||
{
|
||||
var streamUrls = ExtractStreamUrls(payload.FilePayload);
|
||||
return streamUrls?.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Mikai ParseMoonAnimePage error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<string> ExtractStreamUrls(object filePayload)
|
||||
|
||||
@ -11,7 +11,7 @@ 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 _reAtobLiteral = new Regex(@"atob\(\s*(['""])(?<payload>[^'""]+)\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);
|
||||
@ -150,7 +150,10 @@ namespace LME.Common.Playerjs
|
||||
if (!match.Success)
|
||||
return null;
|
||||
|
||||
byte[] rawData = SafeBase64Decode(match.Groups["payload"].Value);
|
||||
string rawBase64 = match.Groups["payload"].Value;
|
||||
rawBase64 = Regex.Replace(rawBase64, @"\s+", "");
|
||||
|
||||
byte[] rawData = SafeBase64Decode(rawBase64);
|
||||
if (rawData == null || rawData.Length <= 32)
|
||||
return null;
|
||||
|
||||
@ -161,7 +164,39 @@ namespace LME.Common.Playerjs
|
||||
for (int index = 0; index < encryptedData.Length; index++)
|
||||
decoded[index] = (byte)(encryptedData[index] ^ key[index % key.Length]);
|
||||
|
||||
return DecodeBytes(decoded);
|
||||
string decodedStr = DecodeBytes(decoded);
|
||||
if (decodedStr != null && (decodedStr.Contains("Playerjs") || decodedStr.Contains("file:")))
|
||||
return decodedStr;
|
||||
|
||||
try
|
||||
{
|
||||
if (rawData.Length > 33)
|
||||
{
|
||||
byte state = rawData[0];
|
||||
byte[] moonKey = new byte[32];
|
||||
Array.Copy(rawData, 1, moonKey, 0, 32);
|
||||
|
||||
int payloadLen = rawData.Length - 33;
|
||||
byte[] moonPayload = new byte[payloadLen];
|
||||
Array.Copy(rawData, 33, moonPayload, 0, payloadLen);
|
||||
|
||||
for (int i = 0; i < moonPayload.Length; i++)
|
||||
{
|
||||
byte encrypted = moonPayload[i];
|
||||
byte keyByte = moonKey[i % 32];
|
||||
|
||||
moonPayload[i] = (byte)(encrypted ^ keyByte ^ state);
|
||||
state = (byte)((encrypted + keyByte) & 0xFF);
|
||||
}
|
||||
|
||||
string moonDecoded = DecodeBytes(moonPayload);
|
||||
if (moonDecoded != null && (moonDecoded.Contains("Playerjs") || moonDecoded.Contains("file:")))
|
||||
return moonDecoded;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return decodedStr;
|
||||
}
|
||||
|
||||
private static byte[] SafeBase64Decode(string value)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user