mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-04-16 17:32:20 +00:00
Compare commits
No commits in common. "5bb04bc4eb241567ba2b5fabe6fda15fd2ffdef8" and "a7dbdbce2ee05c469d03dcf97a21967b3cde17f4" have entirely different histories.
5bb04bc4eb
...
a7dbdbce2e
@ -45,7 +45,7 @@ namespace AnimeON
|
|||||||
|
|
||||||
AnimeON = new OnlinesSettings("AnimeON", "https://animeon.club", streamproxy: false, useproxy: false)
|
AnimeON = new OnlinesSettings("AnimeON", "https://animeon.club", streamproxy: false, useproxy: false)
|
||||||
{
|
{
|
||||||
displayname = "AnimeON",
|
displayname = "🇯🇵 AnimeON",
|
||||||
displayindex = 0,
|
displayindex = 0,
|
||||||
proxy = new Shared.Models.Base.ProxySettings()
|
proxy = new Shared.Models.Base.ProxySettings()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Shared.Models.Base;
|
|
||||||
using System;
|
|
||||||
using System.Web;
|
|
||||||
|
|
||||||
namespace Shared.Engine
|
|
||||||
{
|
|
||||||
public static class ApnHelper
|
|
||||||
{
|
|
||||||
public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}";
|
|
||||||
|
|
||||||
public static bool TryGetInitConf(JObject conf, out bool enabled, out string host)
|
|
||||||
{
|
|
||||||
enabled = false;
|
|
||||||
host = null;
|
|
||||||
|
|
||||||
if (conf == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!conf.TryGetValue("apn", out var apnToken) || apnToken?.Type != JTokenType.Boolean)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
enabled = apnToken.Value<bool>();
|
|
||||||
host = conf.Value<string>("apn_host");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init)
|
|
||||||
{
|
|
||||||
if (init == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!enabled)
|
|
||||||
{
|
|
||||||
init.apnstream = false;
|
|
||||||
init.apn = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(host))
|
|
||||||
host = DefaultHost;
|
|
||||||
|
|
||||||
if (init.apn == null)
|
|
||||||
init.apn = new ApnConf();
|
|
||||||
|
|
||||||
init.apn.host = host;
|
|
||||||
init.apnstream = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsEnabled(BaseSettings init)
|
|
||||||
{
|
|
||||||
return init?.apnstream == true && !string.IsNullOrWhiteSpace(init?.apn?.host);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsAshdiUrl(string url)
|
|
||||||
{
|
|
||||||
return !string.IsNullOrEmpty(url) &&
|
|
||||||
url.IndexOf("ashdi.vip", StringComparison.OrdinalIgnoreCase) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string WrapUrl(BaseSettings init, string url)
|
|
||||||
{
|
|
||||||
if (!IsEnabled(init))
|
|
||||||
return url;
|
|
||||||
|
|
||||||
return BuildUrl(init.apn.host, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string BuildUrl(string host, string url)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(url))
|
|
||||||
return url;
|
|
||||||
|
|
||||||
if (host.Contains("{encodeurl}"))
|
|
||||||
return host.Replace("{encodeurl}", HttpUtility.UrlEncode(url));
|
|
||||||
|
|
||||||
if (host.Contains("{encode_uri}"))
|
|
||||||
return host.Replace("{encode_uri}", HttpUtility.UrlEncode(url));
|
|
||||||
|
|
||||||
if (host.Contains("{uri}"))
|
|
||||||
return host.Replace("{uri}", url);
|
|
||||||
|
|
||||||
return $"{host.TrimEnd('/')}/{url}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,357 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using NMoonAnime.Models;
|
|
||||||
using Shared;
|
|
||||||
using Shared.Engine;
|
|
||||||
using Shared.Models;
|
|
||||||
using Shared.Models.Online.Settings;
|
|
||||||
using Shared.Models.Templates;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Web;
|
|
||||||
|
|
||||||
namespace NMoonAnime.Controllers
|
|
||||||
{
|
|
||||||
public class Controller : BaseOnlineController
|
|
||||||
{
|
|
||||||
private readonly ProxyManager proxyManager;
|
|
||||||
|
|
||||||
public Controller() : base(ModInit.Settings)
|
|
||||||
{
|
|
||||||
proxyManager = new ProxyManager(ModInit.NMoonAnime);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route("nmoonanime")]
|
|
||||||
public async Task<ActionResult> Index(long id, string imdb_id, long kinopoisk_id, string title, string original_title, string original_language, int year, string source, int serial, string account_email, string mal_id, string t, int s = -1, bool rjson = false, bool checksearch = false)
|
|
||||||
{
|
|
||||||
await UpdateService.ConnectAsync(host);
|
|
||||||
|
|
||||||
var init = await loadKit(ModInit.NMoonAnime);
|
|
||||||
if (!init.enable)
|
|
||||||
return Forbid();
|
|
||||||
|
|
||||||
var invoke = new NMoonAnimeInvoke(init, hybridCache, OnLog, proxyManager);
|
|
||||||
string effectiveMalId = ResolveMalId(mal_id, kinopoisk_id, source);
|
|
||||||
|
|
||||||
if (checksearch)
|
|
||||||
{
|
|
||||||
if (AppInit.conf?.online?.checkOnlineSearch != true)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
var checkResults = await invoke.Search(imdb_id, effectiveMalId, title, year);
|
|
||||||
if (checkResults != null && checkResults.Count > 0)
|
|
||||||
return Content("data-json=", "text/plain; charset=utf-8");
|
|
||||||
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnLog($"NMoonAnime: назва={title}, source={source}, imdb={imdb_id}, kinopoisk_id(як mal_id)={kinopoisk_id}, mal_id_ефективний={effectiveMalId}, рік={year}, серіал={serial}, сезон={s}, озвучка={t}");
|
|
||||||
|
|
||||||
var seasons = await invoke.Search(imdb_id, effectiveMalId, title, year);
|
|
||||||
if (seasons == null || seasons.Count == 0)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
bool isSeries = serial == 1;
|
|
||||||
NMoonAnimeSeasonContent firstSeasonData = null;
|
|
||||||
|
|
||||||
if (serial == -1)
|
|
||||||
{
|
|
||||||
firstSeasonData = await invoke.GetSeasonContent(seasons[0]);
|
|
||||||
if (firstSeasonData == null || firstSeasonData.Voices.Count == 0)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
isSeries = firstSeasonData.IsSeries;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSeries)
|
|
||||||
{
|
|
||||||
return await RenderSerial(invoke, seasons, imdb_id, kinopoisk_id, title, original_title, year, effectiveMalId, s, t, rjson);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await RenderMovie(invoke, seasons, title, original_title, firstSeasonData, rjson);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("nmoonanime/play")]
|
|
||||||
public async Task<ActionResult> Play(string file, string title = null)
|
|
||||||
{
|
|
||||||
await UpdateService.ConnectAsync(host);
|
|
||||||
|
|
||||||
var init = await loadKit(ModInit.NMoonAnime);
|
|
||||||
if (!init.enable)
|
|
||||||
return Forbid();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(file))
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
var invoke = new NMoonAnimeInvoke(init, hybridCache, OnLog, proxyManager);
|
|
||||||
var streams = invoke.ParseStreams(file);
|
|
||||||
if (streams == null || streams.Count == 0)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
if (streams.Count == 1)
|
|
||||||
{
|
|
||||||
string singleUrl = BuildStreamUrl(init, streams[0].Url);
|
|
||||||
string singleJson = VideoTpl.ToJson("play", singleUrl, title ?? string.Empty, quality: streams[0].Quality ?? "auto");
|
|
||||||
return UpdateService.Validate(Content(singleJson, "application/json; charset=utf-8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
var streamQuality = new StreamQualityTpl();
|
|
||||||
foreach (var stream in streams)
|
|
||||||
{
|
|
||||||
string streamUrl = BuildStreamUrl(init, stream.Url);
|
|
||||||
streamQuality.Append(streamUrl, stream.Quality);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!streamQuality.Any())
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
var first = streamQuality.Firts();
|
|
||||||
string json = VideoTpl.ToJson("play", first.link, title ?? string.Empty, streamquality: streamQuality);
|
|
||||||
return UpdateService.Validate(Content(json, "application/json; charset=utf-8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ActionResult> RenderSerial(
|
|
||||||
NMoonAnimeInvoke invoke,
|
|
||||||
List<NMoonAnimeSeasonRef> seasons,
|
|
||||||
string imdbId,
|
|
||||||
long kinopoiskId,
|
|
||||||
string title,
|
|
||||||
string originalTitle,
|
|
||||||
int year,
|
|
||||||
string malId,
|
|
||||||
int selectedSeason,
|
|
||||||
string selectedVoice,
|
|
||||||
bool rjson)
|
|
||||||
{
|
|
||||||
var orderedSeasons = seasons
|
|
||||||
.Where(s => s != null && !string.IsNullOrWhiteSpace(s.Url))
|
|
||||||
.OrderBy(s => s.SeasonNumber)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (orderedSeasons.Count == 0)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
if (selectedSeason == -1)
|
|
||||||
{
|
|
||||||
var seasonTpl = new SeasonTpl(orderedSeasons.Count);
|
|
||||||
foreach (var season in orderedSeasons)
|
|
||||||
{
|
|
||||||
int seasonNumber = season.SeasonNumber <= 0 ? 1 : season.SeasonNumber;
|
|
||||||
string seasonName = $"Сезон {seasonNumber}";
|
|
||||||
string seasonLink = BuildIndexUrl(imdbId, kinopoiskId, title, originalTitle, year, 1, malId, seasonNumber, selectedVoice);
|
|
||||||
seasonTpl.Append(seasonName, seasonLink, seasonNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rjson
|
|
||||||
? Content(seasonTpl.ToJson(), "application/json; charset=utf-8")
|
|
||||||
: Content(seasonTpl.ToHtml(), "text/html; charset=utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentSeason = orderedSeasons.FirstOrDefault(s => s.SeasonNumber == selectedSeason) ?? orderedSeasons[0];
|
|
||||||
var seasonData = await invoke.GetSeasonContent(currentSeason);
|
|
||||||
if (seasonData == null)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
var voices = seasonData.Voices
|
|
||||||
.Where(v => v != null && v.Episodes != null && v.Episodes.Count > 0)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (voices.Count == 0)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
int activeVoiceIndex = ParseVoiceIndex(selectedVoice, voices.Count);
|
|
||||||
var voiceTpl = new VoiceTpl(voices.Count);
|
|
||||||
for (int i = 0; i < voices.Count; i++)
|
|
||||||
{
|
|
||||||
string voiceName = string.IsNullOrWhiteSpace(voices[i].Name) ? $"Озвучка {i + 1}" : voices[i].Name;
|
|
||||||
string voiceLink = BuildIndexUrl(imdbId, kinopoiskId, title, originalTitle, year, 1, malId, currentSeason.SeasonNumber, i.ToString());
|
|
||||||
voiceTpl.Append(voiceName, i == activeVoiceIndex, voiceLink);
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedVoiceData = voices[activeVoiceIndex];
|
|
||||||
var episodes = selectedVoiceData.Episodes
|
|
||||||
.Where(e => e != null && !string.IsNullOrWhiteSpace(e.File))
|
|
||||||
.OrderBy(e => e.Number <= 0 ? int.MaxValue : e.Number)
|
|
||||||
.ThenBy(e => e.Name)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (episodes.Count == 0)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
string displayTitle = !string.IsNullOrWhiteSpace(title)
|
|
||||||
? title
|
|
||||||
: !string.IsNullOrWhiteSpace(originalTitle)
|
|
||||||
? originalTitle
|
|
||||||
: "NMoonAnime";
|
|
||||||
|
|
||||||
var episodeTpl = new EpisodeTpl(episodes.Count);
|
|
||||||
foreach (var episode in episodes)
|
|
||||||
{
|
|
||||||
int episodeNumber = episode.Number <= 0 ? 1 : episode.Number;
|
|
||||||
string episodeName = string.IsNullOrWhiteSpace(episode.Name) ? $"Епізод {episodeNumber}" : episode.Name;
|
|
||||||
string callUrl = $"{host}/nmoonanime/play?file={HttpUtility.UrlEncode(episode.File)}&title={HttpUtility.UrlEncode(displayTitle)}";
|
|
||||||
episodeTpl.Append(episodeName, displayTitle, currentSeason.SeasonNumber.ToString(), episodeNumber.ToString(), accsArgs(callUrl), "call");
|
|
||||||
}
|
|
||||||
|
|
||||||
episodeTpl.Append(voiceTpl);
|
|
||||||
|
|
||||||
return rjson
|
|
||||||
? Content(episodeTpl.ToJson(), "application/json; charset=utf-8")
|
|
||||||
: Content(episodeTpl.ToHtml(), "text/html; charset=utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ActionResult> RenderMovie(
|
|
||||||
NMoonAnimeInvoke invoke,
|
|
||||||
List<NMoonAnimeSeasonRef> seasons,
|
|
||||||
string title,
|
|
||||||
string originalTitle,
|
|
||||||
NMoonAnimeSeasonContent firstSeasonData,
|
|
||||||
bool rjson)
|
|
||||||
{
|
|
||||||
var currentSeason = seasons
|
|
||||||
.Where(s => s != null && !string.IsNullOrWhiteSpace(s.Url))
|
|
||||||
.OrderBy(s => s.SeasonNumber)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (currentSeason == null)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
NMoonAnimeSeasonContent seasonData = firstSeasonData;
|
|
||||||
if (seasonData == null || !string.Equals(seasonData.Url, currentSeason.Url, StringComparison.OrdinalIgnoreCase))
|
|
||||||
seasonData = await invoke.GetSeasonContent(currentSeason);
|
|
||||||
|
|
||||||
if (seasonData == null || seasonData.Voices.Count == 0)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
string displayTitle = !string.IsNullOrWhiteSpace(title)
|
|
||||||
? title
|
|
||||||
: !string.IsNullOrWhiteSpace(originalTitle)
|
|
||||||
? originalTitle
|
|
||||||
: "NMoonAnime";
|
|
||||||
|
|
||||||
var movieTpl = new MovieTpl(displayTitle, originalTitle);
|
|
||||||
int fallbackIndex = 1;
|
|
||||||
|
|
||||||
foreach (var voice in seasonData.Voices)
|
|
||||||
{
|
|
||||||
if (voice == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
string file = !string.IsNullOrWhiteSpace(voice.MovieFile)
|
|
||||||
? voice.MovieFile
|
|
||||||
: voice.Episodes?.FirstOrDefault(e => !string.IsNullOrWhiteSpace(e.File))?.File;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(file))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
string voiceName = string.IsNullOrWhiteSpace(voice.Name) ? $"Озвучка {fallbackIndex}" : voice.Name;
|
|
||||||
string callUrl = $"{host}/nmoonanime/play?file={HttpUtility.UrlEncode(file)}&title={HttpUtility.UrlEncode(displayTitle)}";
|
|
||||||
movieTpl.Append(voiceName, accsArgs(callUrl), "call");
|
|
||||||
fallbackIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (movieTpl.IsEmpty)
|
|
||||||
return OnError("nmoonanime", proxyManager);
|
|
||||||
|
|
||||||
return rjson
|
|
||||||
? Content(movieTpl.ToJson(), "application/json; charset=utf-8")
|
|
||||||
: Content(movieTpl.ToHtml(), "text/html; charset=utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string BuildIndexUrl(string imdbId, long kinopoiskId, string title, string originalTitle, int year, int serial, string malId, int season, string voice)
|
|
||||||
{
|
|
||||||
var url = new StringBuilder();
|
|
||||||
url.Append($"{host}/nmoonanime?imdb_id={HttpUtility.UrlEncode(imdbId)}");
|
|
||||||
url.Append($"&kinopoisk_id={kinopoiskId}");
|
|
||||||
url.Append($"&title={HttpUtility.UrlEncode(title)}");
|
|
||||||
url.Append($"&original_title={HttpUtility.UrlEncode(originalTitle)}");
|
|
||||||
url.Append($"&year={year}");
|
|
||||||
url.Append($"&serial={serial}");
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(malId))
|
|
||||||
url.Append($"&mal_id={HttpUtility.UrlEncode(malId)}");
|
|
||||||
|
|
||||||
if (season > 0)
|
|
||||||
url.Append($"&s={season}");
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(voice))
|
|
||||||
url.Append($"&t={HttpUtility.UrlEncode(voice)}");
|
|
||||||
|
|
||||||
return url.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int ParseVoiceIndex(string voiceValue, int totalVoices)
|
|
||||||
{
|
|
||||||
if (totalVoices <= 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (!int.TryParse(voiceValue, out int index))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (index < 0 || index >= totalVoices)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ResolveMalId(string malId, long kinopoiskId, string source)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(source) && source.Equals("tmdb", StringComparison.OrdinalIgnoreCase))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(malId))
|
|
||||||
return malId.Trim();
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(source) && source.Equals("hikka", StringComparison.OrdinalIgnoreCase) && kinopoiskId > 0)
|
|
||||||
return kinopoiskId.ToString();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string BuildStreamUrl(OnlinesSettings init, string streamLink)
|
|
||||||
{
|
|
||||||
string link = StripLampacArgs(streamLink?.Trim());
|
|
||||||
if (string.IsNullOrEmpty(link))
|
|
||||||
return link;
|
|
||||||
|
|
||||||
var headers = new List<HeadersModel>
|
|
||||||
{
|
|
||||||
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
|
||||||
new HeadersModel("Referer", "https://moonanime.art/")
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ApnHelper.IsEnabled(init))
|
|
||||||
{
|
|
||||||
if (ModInit.ApnHostProvided)
|
|
||||||
return ApnHelper.WrapUrl(init, link);
|
|
||||||
|
|
||||||
var noApn = (OnlinesSettings)init.Clone();
|
|
||||||
noApn.apnstream = false;
|
|
||||||
noApn.apn = null;
|
|
||||||
return HostStreamProxy(noApn, link, headers: headers, proxy: proxyManager.Get());
|
|
||||||
}
|
|
||||||
|
|
||||||
return HostStreamProxy(init, link, headers: headers, proxy: proxyManager.Get());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string StripLampacArgs(string url)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(url))
|
|
||||||
return url;
|
|
||||||
|
|
||||||
string cleaned = Regex.Replace(
|
|
||||||
url,
|
|
||||||
@"([?&])(account_email|uid|nws_id)=[^&]*",
|
|
||||||
"$1",
|
|
||||||
RegexOptions.IgnoreCase
|
|
||||||
);
|
|
||||||
|
|
||||||
cleaned = cleaned.Replace("?&", "?").Replace("&&", "&").TrimEnd('?', '&');
|
|
||||||
return cleaned;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,194 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Shared;
|
|
||||||
using Shared.Engine;
|
|
||||||
using Shared.Models.Module;
|
|
||||||
using Shared.Models.Online.Settings;
|
|
||||||
using System;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Mime;
|
|
||||||
using System.Net.Security;
|
|
||||||
using System.Security.Authentication;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace NMoonAnime
|
|
||||||
{
|
|
||||||
public class ModInit
|
|
||||||
{
|
|
||||||
public static double Version => 1.0;
|
|
||||||
|
|
||||||
public static OnlinesSettings NMoonAnime;
|
|
||||||
|
|
||||||
public static bool ApnHostProvided;
|
|
||||||
|
|
||||||
public static OnlinesSettings Settings
|
|
||||||
{
|
|
||||||
get => NMoonAnime;
|
|
||||||
set => NMoonAnime = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Модуль завантажено.
|
|
||||||
/// </summary>
|
|
||||||
public static void loaded(InitspaceModel initspace)
|
|
||||||
{
|
|
||||||
NMoonAnime = new OnlinesSettings("NMoonAnime", "https://moonanime.art", "https://apx.lme.isroot.in", streamproxy: false, useproxy: false)
|
|
||||||
{
|
|
||||||
displayname = "New MoonAnime",
|
|
||||||
displayindex = 0,
|
|
||||||
proxy = new Shared.Models.Base.ProxySettings()
|
|
||||||
{
|
|
||||||
useAuth = true,
|
|
||||||
username = "",
|
|
||||||
password = "",
|
|
||||||
list = new string[] { "socks5://ip:port" }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var conf = ModuleInvoke.Conf("NMoonAnime", NMoonAnime) ?? JObject.FromObject(NMoonAnime);
|
|
||||||
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
|
|
||||||
conf.Remove("apn");
|
|
||||||
conf.Remove("apn_host");
|
|
||||||
NMoonAnime = conf.ToObject<OnlinesSettings>();
|
|
||||||
|
|
||||||
if (hasApn)
|
|
||||||
ApnHelper.ApplyInitConf(apnEnabled, apnHost, NMoonAnime);
|
|
||||||
|
|
||||||
ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost);
|
|
||||||
if (hasApn && apnEnabled)
|
|
||||||
{
|
|
||||||
NMoonAnime.streamproxy = false;
|
|
||||||
}
|
|
||||||
else if (NMoonAnime.streamproxy)
|
|
||||||
{
|
|
||||||
NMoonAnime.apnstream = false;
|
|
||||||
NMoonAnime.apn = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppInit.conf.online.with_search.Add("nmoonanime");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class UpdateService
|
|
||||||
{
|
|
||||||
private static readonly string _connectUrl = "https://lmcuk.lampame.v6.rocks/stats";
|
|
||||||
|
|
||||||
private static ConnectResponse? Connect = null;
|
|
||||||
private static DateTime? _connectTime = null;
|
|
||||||
private static DateTime? _disconnectTime = null;
|
|
||||||
|
|
||||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
|
||||||
private static Timer? _resetTimer = null;
|
|
||||||
|
|
||||||
private static readonly object _lock = new();
|
|
||||||
|
|
||||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_connectTime = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var handler = new SocketsHttpHandler
|
|
||||||
{
|
|
||||||
SslOptions = new SslClientAuthenticationOptions
|
|
||||||
{
|
|
||||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
|
||||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using var client = new HttpClient(handler);
|
|
||||||
client.Timeout = TimeSpan.FromSeconds(15);
|
|
||||||
|
|
||||||
var request = new
|
|
||||||
{
|
|
||||||
Host = host,
|
|
||||||
Module = ModInit.Settings.plugin,
|
|
||||||
Version = ModInit.Version,
|
|
||||||
};
|
|
||||||
|
|
||||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
|
||||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
|
||||||
|
|
||||||
var response = await client
|
|
||||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
if (response.Content.Headers.ContentLength > 0)
|
|
||||||
{
|
|
||||||
var responseText = await response.Content
|
|
||||||
.ReadAsStringAsync(cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_resetTimer?.Dispose();
|
|
||||||
_resetTimer = null;
|
|
||||||
|
|
||||||
if (Connect?.IsUpdateUnavailable != true)
|
|
||||||
{
|
|
||||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
|
||||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
|
||||||
: DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
ResetConnectTime(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ResetConnectTime(object? state)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_connectTime = null;
|
|
||||||
Connect = null;
|
|
||||||
|
|
||||||
_resetTimer?.Dispose();
|
|
||||||
_resetTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisconnected()
|
|
||||||
{
|
|
||||||
return _disconnectTime is not null
|
|
||||||
&& DateTime.UtcNow >= _disconnectTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ActionResult Validate(ActionResult result)
|
|
||||||
{
|
|
||||||
return IsDisconnected()
|
|
||||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
|
||||||
: result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace NMoonAnime.Models
|
|
||||||
{
|
|
||||||
public class NMoonAnimeSearchResponse
|
|
||||||
{
|
|
||||||
[JsonPropertyName("seasons")]
|
|
||||||
public List<NMoonAnimeSeasonRef> Seasons { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NMoonAnimeSeasonRef
|
|
||||||
{
|
|
||||||
[JsonPropertyName("season_number")]
|
|
||||||
public int SeasonNumber { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("url")]
|
|
||||||
public string Url { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NMoonAnimeSeasonContent
|
|
||||||
{
|
|
||||||
public int SeasonNumber { get; set; }
|
|
||||||
|
|
||||||
public string Url { get; set; }
|
|
||||||
|
|
||||||
public bool IsSeries { get; set; }
|
|
||||||
|
|
||||||
public List<NMoonAnimeVoiceContent> Voices { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NMoonAnimeVoiceContent
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string MovieFile { get; set; }
|
|
||||||
|
|
||||||
public List<NMoonAnimeEpisodeContent> Episodes { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NMoonAnimeEpisodeContent
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public int Number { get; set; }
|
|
||||||
|
|
||||||
public string File { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NMoonAnimeStreamVariant
|
|
||||||
{
|
|
||||||
public string Url { get; set; }
|
|
||||||
|
|
||||||
public string Quality { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<OutputType>library</OutputType>
|
|
||||||
<IsPackable>true</IsPackable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Shared">
|
|
||||||
<HintPath>..\..\Shared.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@ -1,459 +0,0 @@
|
|||||||
using NMoonAnime.Models;
|
|
||||||
using Shared;
|
|
||||||
using Shared.Engine;
|
|
||||||
using Shared.Models;
|
|
||||||
using Shared.Models.Online.Settings;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Web;
|
|
||||||
|
|
||||||
namespace NMoonAnime
|
|
||||||
{
|
|
||||||
public class NMoonAnimeInvoke
|
|
||||||
{
|
|
||||||
private readonly OnlinesSettings _init;
|
|
||||||
private readonly IHybridCache _hybridCache;
|
|
||||||
private readonly Action<string> _onLog;
|
|
||||||
private readonly ProxyManager _proxyManager;
|
|
||||||
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
PropertyNameCaseInsensitive = true
|
|
||||||
};
|
|
||||||
|
|
||||||
public NMoonAnimeInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
|
|
||||||
{
|
|
||||||
_init = init;
|
|
||||||
_hybridCache = hybridCache;
|
|
||||||
_onLog = onLog;
|
|
||||||
_proxyManager = proxyManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<NMoonAnimeSeasonRef>> Search(string imdbId, string malId, string title, int year)
|
|
||||||
{
|
|
||||||
string memKey = $"NMoonAnime:search:{imdbId}:{malId}:{title}:{year}";
|
|
||||||
if (_hybridCache.TryGetValue(memKey, out List<NMoonAnimeSeasonRef> cached))
|
|
||||||
return cached;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var endpoints = new[]
|
|
||||||
{
|
|
||||||
"/moonanime/search",
|
|
||||||
"/moonanime"
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var endpoint in endpoints)
|
|
||||||
{
|
|
||||||
string searchUrl = BuildSearchUrl(endpoint, imdbId, malId, title, year);
|
|
||||||
if (string.IsNullOrWhiteSpace(searchUrl))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
_onLog($"NMoonAnime: пошук через {searchUrl}");
|
|
||||||
string json = await Http.Get(_init.cors(searchUrl), headers: DefaultHeaders(), proxy: _proxyManager.Get());
|
|
||||||
if (string.IsNullOrWhiteSpace(json))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var response = JsonSerializer.Deserialize<NMoonAnimeSearchResponse>(json, _jsonOptions);
|
|
||||||
var seasons = response?.Seasons?
|
|
||||||
.Where(s => s != null && !string.IsNullOrWhiteSpace(s.Url))
|
|
||||||
.Select(s => new NMoonAnimeSeasonRef
|
|
||||||
{
|
|
||||||
SeasonNumber = s.SeasonNumber <= 0 ? 1 : s.SeasonNumber,
|
|
||||||
Url = s.Url.Trim()
|
|
||||||
})
|
|
||||||
.GroupBy(s => s.Url, StringComparer.OrdinalIgnoreCase)
|
|
||||||
.Select(g => g.First())
|
|
||||||
.OrderBy(s => s.SeasonNumber)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (seasons != null && seasons.Count > 0)
|
|
||||||
{
|
|
||||||
_hybridCache.Set(memKey, seasons, cacheTime(10, init: _init));
|
|
||||||
return seasons;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_onLog($"NMoonAnime: помилка пошуку - {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new List<NMoonAnimeSeasonRef>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<NMoonAnimeSeasonContent> GetSeasonContent(NMoonAnimeSeasonRef season)
|
|
||||||
{
|
|
||||||
if (season == null || string.IsNullOrWhiteSpace(season.Url))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
string memKey = $"NMoonAnime:season:{season.Url}";
|
|
||||||
if (_hybridCache.TryGetValue(memKey, out NMoonAnimeSeasonContent cached))
|
|
||||||
return cached;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_onLog($"NMoonAnime: завантаження сезону {season.Url}");
|
|
||||||
string html = await Http.Get(_init.cors(season.Url), headers: DefaultHeaders(), proxy: _proxyManager.Get());
|
|
||||||
if (string.IsNullOrWhiteSpace(html))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var content = ParseSeasonPage(html, season.SeasonNumber, season.Url);
|
|
||||||
if (content != null)
|
|
||||||
_hybridCache.Set(memKey, content, cacheTime(20, init: _init));
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_onLog($"NMoonAnime: помилка читання сезону - {ex.Message}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<NMoonAnimeStreamVariant> ParseStreams(string rawFile)
|
|
||||||
{
|
|
||||||
var streams = new List<NMoonAnimeStreamVariant>();
|
|
||||||
if (string.IsNullOrWhiteSpace(rawFile))
|
|
||||||
return streams;
|
|
||||||
|
|
||||||
string value = WebUtility.HtmlDecode(rawFile).Trim();
|
|
||||||
|
|
||||||
var bracketMatches = Regex.Matches(value, @"\[(?<quality>[^\]]+)\](?<url>https?://[^,\[]+)", RegexOptions.IgnoreCase);
|
|
||||||
foreach (Match match in bracketMatches)
|
|
||||||
{
|
|
||||||
string quality = NormalizeQuality(match.Groups["quality"].Value);
|
|
||||||
string url = match.Groups["url"].Value?.Trim();
|
|
||||||
if (string.IsNullOrWhiteSpace(url))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
streams.Add(new NMoonAnimeStreamVariant
|
|
||||||
{
|
|
||||||
Url = url,
|
|
||||||
Quality = quality
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streams.Count == 0)
|
|
||||||
{
|
|
||||||
var taggedMatches = Regex.Matches(value, @"(?<quality>\d{3,4}p?)\s*[:|]\s*(?<url>https?://[^,\s]+)", RegexOptions.IgnoreCase);
|
|
||||||
foreach (Match match in taggedMatches)
|
|
||||||
{
|
|
||||||
string quality = NormalizeQuality(match.Groups["quality"].Value);
|
|
||||||
string url = match.Groups["url"].Value?.Trim();
|
|
||||||
if (string.IsNullOrWhiteSpace(url))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
streams.Add(new NMoonAnimeStreamVariant
|
|
||||||
{
|
|
||||||
Url = url,
|
|
||||||
Quality = quality
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streams.Count == 0)
|
|
||||||
{
|
|
||||||
var plainLinks = value
|
|
||||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
|
||||||
.Where(part => part.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (plainLinks.Count > 1)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < plainLinks.Count; i++)
|
|
||||||
{
|
|
||||||
streams.Add(new NMoonAnimeStreamVariant
|
|
||||||
{
|
|
||||||
Url = plainLinks[i],
|
|
||||||
Quality = $"auto-{i + 1}"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streams.Count == 0 && value.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
streams.Add(new NMoonAnimeStreamVariant
|
|
||||||
{
|
|
||||||
Url = value,
|
|
||||||
Quality = "auto"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return streams
|
|
||||||
.Where(s => s != null && !string.IsNullOrWhiteSpace(s.Url))
|
|
||||||
.Select(s => new NMoonAnimeStreamVariant
|
|
||||||
{
|
|
||||||
Url = s.Url.Trim(),
|
|
||||||
Quality = NormalizeQuality(s.Quality)
|
|
||||||
})
|
|
||||||
.GroupBy(s => s.Url, StringComparer.OrdinalIgnoreCase)
|
|
||||||
.Select(g => g.First())
|
|
||||||
.OrderByDescending(s => QualityWeight(s.Quality))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string BuildSearchUrl(string endpoint, string imdbId, string malId, string title, int year)
|
|
||||||
{
|
|
||||||
var query = HttpUtility.ParseQueryString(string.Empty);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(malId))
|
|
||||||
query["mal_id"] = malId;
|
|
||||||
else if (!string.IsNullOrWhiteSpace(imdbId))
|
|
||||||
query["imdb_id"] = imdbId;
|
|
||||||
else if (!string.IsNullOrWhiteSpace(title))
|
|
||||||
query["title"] = title;
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (year > 0)
|
|
||||||
query["year"] = year.ToString();
|
|
||||||
|
|
||||||
if (query.Count == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return $"{_init.apihost.TrimEnd('/')}{endpoint}?{query}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private NMoonAnimeSeasonContent ParseSeasonPage(string html, int seasonNumber, string seasonUrl)
|
|
||||||
{
|
|
||||||
var content = new NMoonAnimeSeasonContent
|
|
||||||
{
|
|
||||||
SeasonNumber = seasonNumber <= 0 ? 1 : seasonNumber,
|
|
||||||
Url = seasonUrl,
|
|
||||||
IsSeries = false
|
|
||||||
};
|
|
||||||
|
|
||||||
string fileArrayJson = ExtractFileArrayJson(html);
|
|
||||||
if (string.IsNullOrWhiteSpace(fileArrayJson))
|
|
||||||
return content;
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(fileArrayJson);
|
|
||||||
if (doc.RootElement.ValueKind != JsonValueKind.Array)
|
|
||||||
return content;
|
|
||||||
|
|
||||||
int voiceIndex = 1;
|
|
||||||
foreach (var entry in doc.RootElement.EnumerateArray())
|
|
||||||
{
|
|
||||||
if (entry.ValueKind != JsonValueKind.Object)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var voice = new NMoonAnimeVoiceContent
|
|
||||||
{
|
|
||||||
Name = NormalizeVoiceName(GetStringProperty(entry, "title"), voiceIndex)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (entry.TryGetProperty("folder", out var folder) && folder.ValueKind == JsonValueKind.Array)
|
|
||||||
{
|
|
||||||
int episodeIndex = 1;
|
|
||||||
foreach (var episodeEntry in folder.EnumerateArray())
|
|
||||||
{
|
|
||||||
if (episodeEntry.ValueKind != JsonValueKind.Object)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
string file = GetStringProperty(episodeEntry, "file");
|
|
||||||
if (string.IsNullOrWhiteSpace(file))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
string episodeTitle = GetStringProperty(episodeEntry, "title");
|
|
||||||
int episodeNumber = ParseEpisodeNumber(episodeTitle, episodeIndex);
|
|
||||||
|
|
||||||
voice.Episodes.Add(new NMoonAnimeEpisodeContent
|
|
||||||
{
|
|
||||||
Name = string.IsNullOrWhiteSpace(episodeTitle) ? $"Епізод {episodeNumber}" : WebUtility.HtmlDecode(episodeTitle),
|
|
||||||
Number = episodeNumber,
|
|
||||||
File = file
|
|
||||||
});
|
|
||||||
|
|
||||||
episodeIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (voice.Episodes.Count > 0)
|
|
||||||
{
|
|
||||||
content.IsSeries = true;
|
|
||||||
voice.Episodes = voice.Episodes
|
|
||||||
.OrderBy(e => e.Number <= 0 ? int.MaxValue : e.Number)
|
|
||||||
.ThenBy(e => e.Name)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
voice.MovieFile = GetStringProperty(entry, "file");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(voice.MovieFile) || voice.Episodes.Count > 0)
|
|
||||||
content.Voices.Add(voice);
|
|
||||||
|
|
||||||
voiceIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeVoiceName(string source, int fallbackIndex)
|
|
||||||
{
|
|
||||||
string voice = WebUtility.HtmlDecode(source ?? string.Empty).Trim();
|
|
||||||
return string.IsNullOrWhiteSpace(voice) ? $"Озвучка {fallbackIndex}" : voice;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int ParseEpisodeNumber(string title, int fallback)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(title))
|
|
||||||
return fallback;
|
|
||||||
|
|
||||||
var match = Regex.Match(title, @"\d+");
|
|
||||||
if (match.Success && int.TryParse(match.Value, out int number))
|
|
||||||
return number;
|
|
||||||
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeQuality(string quality)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(quality))
|
|
||||||
return "auto";
|
|
||||||
|
|
||||||
string value = quality.Trim().Trim('[', ']');
|
|
||||||
if (value.Equals("auto", StringComparison.OrdinalIgnoreCase))
|
|
||||||
return "auto";
|
|
||||||
|
|
||||||
var match = Regex.Match(value, @"(?<q>\d{3,4})");
|
|
||||||
if (match.Success)
|
|
||||||
return $"{match.Groups["q"].Value}p";
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int QualityWeight(string quality)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(quality))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var match = Regex.Match(quality, @"\d{3,4}");
|
|
||||||
if (match.Success && int.TryParse(match.Value, out int q))
|
|
||||||
return q;
|
|
||||||
|
|
||||||
return quality.Equals("auto", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetStringProperty(JsonElement element, string name)
|
|
||||||
{
|
|
||||||
return element.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.String
|
|
||||||
? value.GetString()
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ExtractFileArrayJson(string html)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(html))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var match = Regex.Match(html, @"file\s*:\s*(\[[\s\S]*?\])\s*,\s*skip\s*:", RegexOptions.IgnoreCase);
|
|
||||||
if (match.Success)
|
|
||||||
return match.Groups[1].Value;
|
|
||||||
|
|
||||||
int fileIndex = html.IndexOf("file", StringComparison.OrdinalIgnoreCase);
|
|
||||||
if (fileIndex < 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
int colonIndex = html.IndexOf(':', fileIndex);
|
|
||||||
if (colonIndex < 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
int arrayIndex = html.IndexOf('[', colonIndex);
|
|
||||||
if (arrayIndex < 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return ExtractBracketArray(html, arrayIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ExtractBracketArray(string source, int startIndex)
|
|
||||||
{
|
|
||||||
bool inString = false;
|
|
||||||
bool escaped = false;
|
|
||||||
char stringChar = '\0';
|
|
||||||
int depth = 0;
|
|
||||||
int begin = -1;
|
|
||||||
|
|
||||||
for (int i = startIndex; i < source.Length; i++)
|
|
||||||
{
|
|
||||||
char c = source[i];
|
|
||||||
|
|
||||||
if (inString)
|
|
||||||
{
|
|
||||||
if (escaped)
|
|
||||||
{
|
|
||||||
escaped = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == '\\')
|
|
||||||
{
|
|
||||||
escaped = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == stringChar)
|
|
||||||
{
|
|
||||||
inString = false;
|
|
||||||
stringChar = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == '"' || c == '\'')
|
|
||||||
{
|
|
||||||
inString = true;
|
|
||||||
stringChar = c;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == '[')
|
|
||||||
{
|
|
||||||
if (depth == 0)
|
|
||||||
begin = i;
|
|
||||||
|
|
||||||
depth++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == ']')
|
|
||||||
{
|
|
||||||
depth--;
|
|
||||||
if (depth == 0 && begin >= 0)
|
|
||||||
return source.Substring(begin, i - begin + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<HeadersModel> DefaultHeaders()
|
|
||||||
{
|
|
||||||
return new List<HeadersModel>
|
|
||||||
{
|
|
||||||
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
|
||||||
new HeadersModel("Referer", _init.host)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TimeSpan cacheTime(int multiaccess, int home = 5, int mikrotik = 2, OnlinesSettings init = null, int rhub = -1)
|
|
||||||
{
|
|
||||||
if (init != null && init.rhub && rhub != -1)
|
|
||||||
return TimeSpan.FromMinutes(rhub);
|
|
||||||
|
|
||||||
int ctime = AppInit.conf.mikrotik ? mikrotik : AppInit.conf.multiaccess ? init != null && init.cache_time > 0 ? init.cache_time : multiaccess : home;
|
|
||||||
if (ctime > multiaccess)
|
|
||||||
ctime = multiaccess;
|
|
||||||
|
|
||||||
return TimeSpan.FromMinutes(ctime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using Shared.Models;
|
|
||||||
using Shared.Models.Base;
|
|
||||||
using Shared.Models.Module;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace NMoonAnime
|
|
||||||
{
|
|
||||||
public class OnlineApi
|
|
||||||
{
|
|
||||||
public static List<(string name, string url, string plugin, int index)> Invoke(
|
|
||||||
HttpContext httpContext,
|
|
||||||
IMemoryCache memoryCache,
|
|
||||||
RequestModel requestInfo,
|
|
||||||
string host,
|
|
||||||
OnlineEventsModel args)
|
|
||||||
{
|
|
||||||
long.TryParse(args.id, out long tmdbid);
|
|
||||||
return Events(host, tmdbid, args.imdb_id, args.kinopoisk_id, args.title, args.original_title, args.original_language, args.year, args.source, args.serial, args.account_email);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<(string name, string url, string plugin, int index)> Events(string host, long id, string imdb_id, long kinopoisk_id, string title, string original_title, string original_language, int year, string source, int serial, string account_email)
|
|
||||||
{
|
|
||||||
var online = new List<(string name, string url, string plugin, int index)>();
|
|
||||||
|
|
||||||
var init = ModInit.NMoonAnime;
|
|
||||||
|
|
||||||
bool hasLang = !string.IsNullOrEmpty(original_language);
|
|
||||||
bool isAnime = hasLang && (original_language == "ja" || original_language == "zh");
|
|
||||||
|
|
||||||
if (init.enable && !init.rip && (serial == -1 || isAnime || !hasLang))
|
|
||||||
{
|
|
||||||
string url = init.overridehost;
|
|
||||||
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
|
|
||||||
url = $"{host}/nmoonanime";
|
|
||||||
|
|
||||||
online.Add((init.displayname, url, "nmoonanime", init.displayindex));
|
|
||||||
}
|
|
||||||
|
|
||||||
return online;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"enable": true,
|
|
||||||
"version": 3,
|
|
||||||
"initspace": "NMoonAnime.ModInit",
|
|
||||||
"online": "NMoonAnime.OnlineApi"
|
|
||||||
}
|
|
||||||
@ -13,7 +13,6 @@
|
|||||||
- [x] BambooUA
|
- [x] BambooUA
|
||||||
- [x] Unimay
|
- [x] Unimay
|
||||||
- [x] Mikai
|
- [x] Mikai
|
||||||
- [x] NMoonAnime
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -42,7 +41,6 @@ Create or update the module/repository.yaml file
|
|||||||
- AnimeON
|
- AnimeON
|
||||||
- Unimay
|
- Unimay
|
||||||
- Mikai
|
- Mikai
|
||||||
- NMoonAnime
|
|
||||||
- Uaflix
|
- Uaflix
|
||||||
- Bamboo
|
- Bamboo
|
||||||
- Makhno
|
- Makhno
|
||||||
@ -163,7 +161,6 @@ Sources with APN support:
|
|||||||
- Mikai
|
- Mikai
|
||||||
- Makhno
|
- Makhno
|
||||||
- KlonFUN
|
- KlonFUN
|
||||||
- NMoonAnime
|
|
||||||
|
|
||||||
## Source/player availability check script
|
## Source/player availability check script
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user