refactor!: migrate all modules to new module interface architecture

BREAKING CHANGE: All module routes changed from /{module} to /lite/{module}

- Implement IModuleLoaded and IModuleOnline interfaces across all modules
- Add HttpHydra support to all Invoke classes for HTTP request handling
- Replace ModuleInvoke.Conf() with ModuleInvoke.Init() in all ModInit classes
- Convert loadKit() from async to synchronous calls in all controllers
- Replace direct AppInit.conf.online.with_search.Add() with reflection-based
  RegisterWithSearch() method for decoupled module registration
- Simplify cacheTime() logic by removing mikrotik/multiaccess conditionals
- Add GlobalUsings.cs to all modules for shared namespace imports
- Update OnlineApi to use ModuleOnlineItem instead of value tuples
- Bump all module versions to new major versions
This commit is contained in:
Felix 2026-03-28 10:18:21 +02:00
parent 76fcbc8fbf
commit 6aece92fd0
61 changed files with 1386 additions and 502 deletions

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@ -23,13 +23,15 @@ namespace AnimeON
private IHybridCache _hybridCache;
private Action<string> _onLog;
private ProxyManager _proxyManager;
private readonly HttpHydra _httpHydra;
public AnimeONInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
public AnimeONInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
{
_init = init;
_hybridCache = hybridCache;
_onLog = onLog;
_proxyManager = proxyManager;
_httpHydra = httpHydra;
}
string AshdiRequestUrl(string url)
@ -61,7 +63,7 @@ namespace AnimeON
string searchUrl = $"{_init.host}/api/anime/search?text={System.Web.HttpUtility.UrlEncode(query)}";
_onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {searchUrl}");
string searchJson = await Http.Get(_init.cors(searchUrl), headers: headers, proxy: _proxyManager.Get());
string searchJson = await HttpGet(searchUrl, headers);
if (string.IsNullOrEmpty(searchJson))
return null;
@ -124,7 +126,7 @@ namespace AnimeON
string fundubsUrl = $"{_init.host}/api/player/{animeId}/translations";
_onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {fundubsUrl}");
string fundubsJson = await Http.Get(_init.cors(fundubsUrl), headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
string fundubsJson = await HttpGet(fundubsUrl, new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) });
if (string.IsNullOrEmpty(fundubsJson))
return null;
@ -150,7 +152,7 @@ namespace AnimeON
string episodesUrl = $"{_init.host}/api/player/{animeId}/episodes?take=100&skip=-1&playerId={playerId}&translationId={fundubId}";
_onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {episodesUrl}");
string episodesJson = await Http.Get(_init.cors(episodesUrl), headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
string episodesJson = await HttpGet(episodesUrl, new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) });
if (string.IsNullOrEmpty(episodesJson))
return null;
@ -169,7 +171,7 @@ namespace AnimeON
};
_onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {requestUrl}");
string html = await Http.Get(_init.cors(requestUrl), headers: headers, proxy: _proxyManager.Get());
string html = await HttpGet(requestUrl, headers);
if (string.IsNullOrEmpty(html))
return null;
@ -206,7 +208,7 @@ namespace AnimeON
string requestUrl = AshdiRequestUrl(WithAshdiMultivoice(url, enable: !disableAshdiMultivoiceForVod));
_onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {requestUrl}");
string html = await Http.Get(_init.cors(requestUrl), headers: headers, proxy: _proxyManager.Get());
string html = await HttpGet(requestUrl, headers);
if (string.IsNullOrEmpty(html))
return streams;
@ -263,7 +265,7 @@ namespace AnimeON
string url = $"{_init.host}/api/player/{episodeId}/episode";
_onLog($"AnimeON: using proxy {_proxyManager.CurrentProxyIp} for {url}");
string json = await Http.Get(_init.cors(url), headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
string json = await HttpGet(url, new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) });
if (string.IsNullOrEmpty(json))
return null;
@ -474,12 +476,21 @@ namespace AnimeON
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;
int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess;
if (ctime > multiaccess)
ctime = multiaccess;
return TimeSpan.FromMinutes(ctime);
}
private Task<string> HttpGet(string url, List<HeadersModel> headers)
{
if (_httpHydra != null)
return _httpHydra.Get(url, newheaders: headers);
return Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
}
public async Task<AnimeON.Models.AnimeONAggregatedStructure> AggregateSerialStructure(int animeId, int season)
{
string memKey = $"AnimeON:aggregated:{animeId}:{season}";

View File

@ -27,27 +27,27 @@ namespace AnimeON.Controllers
}
[HttpGet]
[Route("animeon")]
[Route("lite/animeon")]
async public 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 t, int s = -1, bool rjson = false, bool checksearch = false)
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.AnimeON);
var init = loadKit(ModInit.AnimeON);
if (!init.enable)
return Forbid();
var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
if (checksearch)
{
if (AppInit.conf?.online?.checkOnlineSearch != true)
return OnError("animeon", proxyManager);
if (!IsCheckOnlineSearchEnabled())
return OnError("animeon", refresh_proxy: true);
var checkSeasons = await invoke.Search(imdb_id, kinopoisk_id, title, original_title, year, serial);
if (checkSeasons != null && checkSeasons.Count > 0)
return Content("data-json=", "text/plain; charset=utf-8");
return OnError("animeon", proxyManager);
return OnError("animeon", refresh_proxy: true);
}
OnLog($"AnimeON Index: title={title}, original_title={original_title}, serial={serial}, s={s}, t={t}, year={year}, imdb_id={imdb_id}, kp={kinopoisk_id}");
@ -55,7 +55,7 @@ namespace AnimeON.Controllers
var seasons = await invoke.Search(imdb_id, kinopoisk_id, title, original_title, year, serial);
OnLog($"AnimeON: search results = {seasons?.Count ?? 0}");
if (seasons == null || seasons.Count == 0)
return OnError("animeon", proxyManager);
return OnError("animeon", refresh_proxy: true);
// [Refactoring] Використовується агрегована структура (AggregateSerialStructure) — попередній збір allOptions не потрібний
@ -81,7 +81,7 @@ namespace AnimeON.Controllers
foreach (var item in seasonItems)
{
string seasonName = item.SeasonNumber.ToString();
string link = $"{host}/animeon?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={item.SeasonNumber}";
string link = $"{host}/lite/animeon?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={item.SeasonNumber}";
season_tpl.Append(seasonName, link, seasonName);
}
OnLog($"AnimeON: return seasons count={seasonItems.Count}");
@ -106,13 +106,13 @@ namespace AnimeON.Controllers
selected = new { Anime = seasons[s], Index = s, SeasonNumber = seasons[s].Season > 0 ? seasons[s].Season : s + 1 };
if (selected == null)
return OnError("animeon", proxyManager);
return OnError("animeon", refresh_proxy: true);
var selectedAnime = selected.Anime;
int selectedSeasonNumber = selected.SeasonNumber;
var structure = await invoke.AggregateSerialStructure(selectedAnime.Id, selectedSeasonNumber);
if (structure == null || !structure.Voices.Any())
return OnError("animeon", proxyManager);
return OnError("animeon", refresh_proxy: true);
OnLog($"AnimeON: voices found = {structure.Voices.Count}");
var voiceItems = structure.Voices
@ -135,14 +135,14 @@ namespace AnimeON.Controllers
var voice_tpl = new VoiceTpl();
foreach (var voice in voiceItems)
{
string voiceLink = $"{host}/animeon?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&t={HttpUtility.UrlEncode(voice.Key)}";
string voiceLink = $"{host}/lite/animeon?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&t={HttpUtility.UrlEncode(voice.Key)}";
bool isActive = voice.Key == t;
voice_tpl.Append(voice.Display, isActive, voiceLink);
}
// Перевірка вибраної озвучки
if (!structure.Voices.ContainsKey(t))
return OnError("animeon", proxyManager);
return OnError("animeon", refresh_proxy: true);
var episode_tpl = new EpisodeTpl();
var selectedVoiceInfo = structure.Voices[t];
@ -179,7 +179,7 @@ namespace AnimeON.Controllers
if (string.IsNullOrEmpty(streamLink) && ep.EpisodeId > 0)
{
string callUrl = $"{host}/animeon/play?episode_id={ep.EpisodeId}&serial=1";
string callUrl = $"{host}/lite/animeon/play?episode_id={ep.EpisodeId}&serial=1";
episode_tpl.Append(episodeName, title ?? original_title, seasonStr, episodeStr, accsArgs(callUrl), "call");
continue;
}
@ -189,7 +189,7 @@ namespace AnimeON.Controllers
if (needsResolve || streamLink.Contains("moonanime.art") || streamLink.Contains("ashdi.vip/vod"))
{
string callUrl = $"{host}/animeon/play?url={HttpUtility.UrlEncode(streamLink)}&serial=1";
string callUrl = $"{host}/lite/animeon/play?url={HttpUtility.UrlEncode(streamLink)}&serial=1";
episode_tpl.Append(episodeName, title ?? original_title, seasonStr, episodeStr, accsArgs(callUrl), "call");
}
else
@ -212,12 +212,12 @@ namespace AnimeON.Controllers
{
var firstAnime = seasons.FirstOrDefault();
if (firstAnime == null)
return OnError("animeon", proxyManager);
return OnError("animeon", refresh_proxy: true);
var fundubs = await invoke.GetFundubs(firstAnime.Id);
OnLog($"AnimeON: movie fundubs count = {fundubs?.Count ?? 0}");
if (fundubs == null || fundubs.Count == 0)
return OnError("animeon", proxyManager);
return OnError("animeon", refresh_proxy: true);
var tpl = new MovieTpl(title, original_title);
@ -251,7 +251,7 @@ namespace AnimeON.Controllers
foreach (var ashdiStream in ashdiStreams)
{
string optionName = $"{translationName} {ashdiStream.title}";
string callUrl = $"{host}/animeon/play?url={HttpUtility.UrlEncode(ashdiStream.link)}";
string callUrl = $"{host}/lite/animeon/play?url={HttpUtility.UrlEncode(ashdiStream.link)}";
tpl.Append(optionName, accsArgs(callUrl), "call");
}
continue;
@ -260,7 +260,7 @@ namespace AnimeON.Controllers
if (needsResolve || streamLink.Contains("moonanime.art/iframe/") || streamLink.Contains("ashdi.vip/vod"))
{
string callUrl = $"{host}/animeon/play?url={HttpUtility.UrlEncode(streamLink)}";
string callUrl = $"{host}/lite/animeon/play?url={HttpUtility.UrlEncode(streamLink)}";
tpl.Append(translationName, accsArgs(callUrl), "call");
}
else
@ -272,7 +272,7 @@ namespace AnimeON.Controllers
// Якщо не зібрали жодної опції — повертаємо помилку
if (tpl.data == null || tpl.data.Count == 0)
return OnError("animeon", proxyManager);
return OnError("animeon", refresh_proxy: true);
OnLog("AnimeON: return movie options");
return rjson ? Content(tpl.ToJson(), "application/json; charset=utf-8") : Content(tpl.ToHtml(), "text/html; charset=utf-8");
@ -283,7 +283,7 @@ namespace AnimeON.Controllers
{
string fundubsUrl = $"{init.host}/api/player/{animeId}/translations";
string fundubsJson = await Http.Get(init.cors(fundubsUrl), headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
string fundubsJson = await httpHydra.Get(fundubsUrl, newheaders: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
if (string.IsNullOrEmpty(fundubsJson))
return null;
@ -308,7 +308,7 @@ namespace AnimeON.Controllers
{
string episodesUrl = $"{init.host}/api/player/{animeId}/episodes?take=100&skip=-1&playerId={playerId}&translationId={fundubId}";
string episodesJson = await Http.Get(init.cors(episodesUrl), headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
string episodesJson = await httpHydra.Get(episodesUrl, newheaders: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
if (string.IsNullOrEmpty(episodesJson))
return null;
@ -332,7 +332,7 @@ namespace AnimeON.Controllers
string searchUrl = $"{init.host}/api/anime/search?text={HttpUtility.UrlEncode(query)}";
string searchJson = await Http.Get(init.cors(searchUrl), headers: headers);
string searchJson = await httpHydra.Get(searchUrl, newheaders: headers);
if (string.IsNullOrEmpty(searchJson))
return null;
@ -373,16 +373,16 @@ namespace AnimeON.Controllers
return null;
}
[HttpGet("animeon/play")]
[HttpGet("lite/animeon/play")]
public async Task<ActionResult> Play(string url, int episode_id = 0, string title = null, int serial = 0)
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.AnimeON);
var init = loadKit(ModInit.AnimeON);
if (!init.enable)
return Forbid();
var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
bool disableAshdiMultivoiceForVod = serial == 1;
OnLog($"AnimeON Play: url={url}, episode_id={episode_id}, serial={serial}");
@ -398,13 +398,13 @@ namespace AnimeON.Controllers
else
{
OnLog("AnimeON Play: empty url");
return OnError("animeon", proxyManager);
return OnError("animeon", refresh_proxy: true);
}
if (string.IsNullOrEmpty(streamLink))
{
OnLog("AnimeON Play: cannot extract stream");
return OnError("animeon", proxyManager);
return OnError("animeon", refresh_proxy: true);
}
List<HeadersModel> streamHeaders = null;
@ -462,5 +462,38 @@ namespace AnimeON.Controllers
return HostStreamProxy(init, link, headers: headers, force_streamproxy: forceProxy);
}
private static bool IsCheckOnlineSearchEnabled()
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (checkProp?.GetValue(conf) is bool enabled)
return enabled;
}
catch
{
}
return true;
}
private static void OnLog(string message)
{
System.Console.WriteLine(message);
}
}
}

4
AnimeON/GlobalUsings.cs Normal file
View File

@ -0,0 +1,4 @@
global using Shared.Services;
global using Shared.Services.Hybrid;
global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;

View File

@ -3,6 +3,7 @@ using Newtonsoft.Json.Linq;
using Shared;
using Shared.Engine;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using Shared.Models.Online.Settings;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
@ -23,9 +24,9 @@ using System.Threading.Tasks;
namespace AnimeON
{
public class ModInit
public class ModInit : IModuleLoaded
{
public static double Version => 3.7;
public static double Version => 4.0;
public static OnlinesSettings AnimeON;
public static bool ApnHostProvided;
@ -39,7 +40,7 @@ namespace AnimeON
/// <summary>
/// модуль загружен
/// </summary>
public static void loaded(InitspaceModel initspace)
public void Loaded(InitspaceModel initspace)
{
@ -55,7 +56,7 @@ namespace AnimeON
list = new string[] { "socks5://ip:port" }
}
};
var conf = ModuleInvoke.Conf("AnimeON", AnimeON);
var conf = ModuleInvoke.Init("AnimeON", JObject.FromObject(AnimeON));
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
conf.Remove("apn");
conf.Remove("apn_host");
@ -74,7 +75,45 @@ namespace AnimeON
}
// Виводити "уточнити пошук"
AppInit.conf.online.with_search.Add("animeon");
RegisterWithSearch("animeon");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}

View File

@ -1,47 +1,46 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared.Models;
using Shared.Models.Base;
using Shared.Models.Module;
using System.Collections.Generic;
using Shared.Models.Module.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace AnimeON
{
public class OnlineApi
public class OnlineApi : IModuleOnline
{
public static List<(string name, string url, string plugin, int index)> Invoke(
HttpContext httpContext,
IMemoryCache memoryCache,
RequestModel requestInfo,
string host,
OnlineEventsModel args)
public List<ModuleOnlineItem> 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)
public Task<List<ModuleOnlineItem>> InvokeAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineEventsModel args)
=> Task.FromResult(default(List<ModuleOnlineItem>));
public List<ModuleOnlineSpiderItem> Spider(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> null;
public Task<List<ModuleOnlineSpiderItem>> SpiderAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> Task.FromResult(default(List<ModuleOnlineSpiderItem>));
private static List<ModuleOnlineItem> 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 online = new List<ModuleOnlineItem>();
var init = ModInit.AnimeON;
// Визначаємо isAnime згідно стандарту Lampac (Deepwiki):
// isanime = true якщо original_language == "ja" або "zh"
bool hasLang = !string.IsNullOrEmpty(original_language);
bool isanime = hasLang && (original_language == "ja" || original_language == "zh");
// AnimeON — аніме-провайдер. Додаємо його:
// - при загальному пошуку (serial == -1), або
// - якщо контент визначений як аніме (isanime), або
// - якщо мова невідома (відсутній original_language)
if (init.enable && !init.rip && (serial == -1 || isanime || !hasLang))
{
string url = init.overridehost;
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
url = $"{host}/animeon";
url = $"{host}/lite/animeon";
online.Add((init.displayname, url, "animeon", init.displayindex));
online.Add(new ModuleOnlineItem(init.displayname, url, "animeon", init.displayindex));
}
return online;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@ -20,13 +20,15 @@ namespace Bamboo
private readonly IHybridCache _hybridCache;
private readonly Action<string> _onLog;
private readonly ProxyManager _proxyManager;
private readonly HttpHydra _httpHydra;
public BambooInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
public BambooInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
{
_init = init;
_hybridCache = hybridCache;
_onLog = onLog;
_proxyManager = proxyManager;
_httpHydra = httpHydra;
}
public async Task<List<SearchResult>> Search(string title, string original_title)
@ -50,7 +52,7 @@ namespace Bamboo
};
_onLog?.Invoke($"Bamboo search: {searchUrl}");
string html = await Http.Get(_init.cors(searchUrl), headers: headers, proxy: _proxyManager.Get());
string html = await HttpGet(searchUrl, headers);
if (string.IsNullOrEmpty(html))
return null;
@ -109,7 +111,7 @@ namespace Bamboo
};
_onLog?.Invoke($"Bamboo series page: {href}");
string html = await Http.Get(_init.cors(href), headers: headers, proxy: _proxyManager.Get());
string html = await HttpGet(href, headers);
if (string.IsNullOrEmpty(html))
return null;
@ -183,7 +185,7 @@ namespace Bamboo
};
_onLog?.Invoke($"Bamboo movie page: {href}");
string html = await Http.Get(_init.cors(href), headers: headers, proxy: _proxyManager.Get());
string html = await HttpGet(href, headers);
if (string.IsNullOrEmpty(html))
return null;
@ -311,12 +313,20 @@ namespace Bamboo
return HtmlEntity.DeEntitize(value).Trim();
}
private Task<string> HttpGet(string url, List<HeadersModel> headers)
{
if (_httpHydra != null)
return _httpHydra.Get(url, newheaders: headers);
return Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
}
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;
int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess;
if (ctime > multiaccess)
ctime = multiaccess;

View File

@ -23,27 +23,27 @@ namespace Bamboo.Controllers
}
[HttpGet]
[Route("bamboo")]
[Route("lite/bamboo")]
async public 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 t, int s = -1, bool rjson = false, string href = null, bool checksearch = false)
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.Bamboo);
var init = loadKit(ModInit.Bamboo);
if (!init.enable)
return Forbid();
var invoke = new BambooInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new BambooInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
if (checksearch)
{
if (AppInit.conf?.online?.checkOnlineSearch != true)
return OnError("bamboo", proxyManager);
if (!IsCheckOnlineSearchEnabled())
return OnError("bamboo", refresh_proxy: true);
var searchResults = await invoke.Search(title, original_title);
if (searchResults != null && searchResults.Count > 0)
return Content("data-json=", "text/plain; charset=utf-8");
return OnError("bamboo", proxyManager);
return OnError("bamboo", refresh_proxy: true);
}
string itemUrl = href;
@ -51,14 +51,14 @@ namespace Bamboo.Controllers
{
var searchResults = await invoke.Search(title, original_title);
if (searchResults == null || searchResults.Count == 0)
return OnError("bamboo", proxyManager);
return OnError("bamboo", refresh_proxy: true);
if (searchResults.Count > 1)
{
var similar_tpl = new SimilarTpl(searchResults.Count);
foreach (var res in searchResults)
{
string link = $"{host}/bamboo?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(res.Url)}";
string link = $"{host}/lite/bamboo?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(res.Url)}";
similar_tpl.Append(res.Title, string.Empty, string.Empty, link, res.Poster);
}
@ -72,7 +72,7 @@ namespace Bamboo.Controllers
{
var series = await invoke.GetSeriesEpisodes(itemUrl);
if (series == null || (series.Sub.Count == 0 && series.Dub.Count == 0))
return OnError("bamboo", proxyManager);
return OnError("bamboo", refresh_proxy: true);
var voice_tpl = new VoiceTpl();
var episode_tpl = new EpisodeTpl();
@ -88,13 +88,13 @@ namespace Bamboo.Controllers
foreach (var voice in availableVoices)
{
string voiceLink = $"{host}/bamboo?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&t={voice.key}&href={HttpUtility.UrlEncode(itemUrl)}";
string voiceLink = $"{host}/lite/bamboo?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&t={voice.key}&href={HttpUtility.UrlEncode(itemUrl)}";
voice_tpl.Append(voice.name, voice.key == t, voiceLink);
}
var selected = availableVoices.FirstOrDefault(v => v.key == t);
if (selected.episodes == null || selected.episodes.Count == 0)
return OnError("bamboo", proxyManager);
return OnError("bamboo", refresh_proxy: true);
int index = 1;
foreach (var ep in selected.episodes.OrderBy(e => e.Episode ?? int.MaxValue))
@ -116,7 +116,7 @@ namespace Bamboo.Controllers
{
var streams = await invoke.GetMovieStreams(itemUrl);
if (streams == null || streams.Count == 0)
return OnError("bamboo", proxyManager);
return OnError("bamboo", refresh_proxy: true);
var movie_tpl = new MovieTpl(title, original_title);
for (int i = 0; i < streams.Count; i++)
@ -166,5 +166,38 @@ namespace Bamboo.Controllers
cleaned = cleaned.Replace("?&", "?").Replace("&&", "&").TrimEnd('?', '&');
return cleaned;
}
private static bool IsCheckOnlineSearchEnabled()
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (checkProp?.GetValue(conf) is bool enabled)
return enabled;
}
catch
{
}
return true;
}
private static void OnLog(string message)
{
System.Console.WriteLine(message);
}
}
}

4
Bamboo/GlobalUsings.cs Normal file
View File

@ -0,0 +1,4 @@
global using Shared.Services;
global using Shared.Services.Hybrid;
global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;

View File

@ -3,6 +3,7 @@ using Shared;
using Shared.Engine;
using Shared.Models.Online.Settings;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.Extensions.Caching.Memory;
@ -22,9 +23,9 @@ using System.Threading.Tasks;
namespace Bamboo
{
public class ModInit
public class ModInit : IModuleLoaded
{
public static double Version => 3.6;
public static double Version => 4.0;
public static OnlinesSettings Bamboo;
public static bool ApnHostProvided;
@ -38,7 +39,7 @@ namespace Bamboo
/// <summary>
/// модуль загружен
/// </summary>
public static void loaded(InitspaceModel initspace)
public void Loaded(InitspaceModel initspace)
{
@ -54,7 +55,7 @@ namespace Bamboo
list = new string[] { "socks5://ip:port" }
}
};
var conf = ModuleInvoke.Conf("Bamboo", Bamboo);
var conf = ModuleInvoke.Init("Bamboo", JObject.FromObject(Bamboo));
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
conf.Remove("apn");
conf.Remove("apn_host");
@ -73,7 +74,45 @@ namespace Bamboo
}
// Виводити "уточнити пошук"
AppInit.conf.online.with_search.Add("bamboo");
RegisterWithSearch("bamboo");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}

View File

@ -1,28 +1,33 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared.Models;
using Shared.Models.Base;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Bamboo
{
public class OnlineApi
public class OnlineApi : IModuleOnline
{
public static List<(string name, string url, string plugin, int index)> Invoke(
HttpContext httpContext,
IMemoryCache memoryCache,
RequestModel requestInfo,
string host,
OnlineEventsModel args)
public List<ModuleOnlineItem> 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)
public Task<List<ModuleOnlineItem>> InvokeAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineEventsModel args)
=> Task.FromResult(default(List<ModuleOnlineItem>));
public List<ModuleOnlineSpiderItem> Spider(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> null;
public Task<List<ModuleOnlineSpiderItem>> SpiderAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> Task.FromResult(default(List<ModuleOnlineSpiderItem>));
private static List<ModuleOnlineItem> 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 online = new List<ModuleOnlineItem>();
var init = ModInit.Bamboo;
if (init.enable && !init.rip)
@ -36,9 +41,9 @@ namespace Bamboo
string url = init.overridehost;
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
url = $"{host}/bamboo";
url = $"{host}/lite/bamboo";
online.Add((init.displayname, url, "bamboo", init.displayindex));
online.Add(new ModuleOnlineItem(init.displayname, url, "bamboo", init.displayindex));
}
return online;

View File

@ -1,10 +1,9 @@
using JackTor.Models;
using Microsoft.AspNetCore.Mvc;
using Shared;
using Shared.Engine;
using Shared.Models;
using Shared.Models.Online.PiTor;
using Shared.Models.Online.Settings;
using Shared.Services.Utilities;
using Shared.Models.Templates;
using System;
using System.Collections.Generic;
@ -27,7 +26,7 @@ namespace JackTor.Controllers
}
[HttpGet]
[Route("jacktor")]
[Route("lite/jacktor")]
async public Task<ActionResult> Index(
long id,
string imdb_id,
@ -46,7 +45,7 @@ namespace JackTor.Controllers
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.Settings);
var init = loadKit(ModInit.Settings);
if (!init.enable)
return Forbid();
@ -57,14 +56,14 @@ namespace JackTor.Controllers
if (checksearch)
{
if (AppInit.conf?.online?.checkOnlineSearch != true)
return OnError("jacktor", proxyManager);
if (!IsCheckOnlineSearchEnabled())
return OnError("jacktor", refresh_proxy: true);
var check = await invoke.Search(title, original_title, year, serial, original_language);
if (check.Count > 0)
return Content("data-json=", "text/plain; charset=utf-8");
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
}
var torrents = await invoke.Search(title, original_title, year, serial, original_language);
@ -98,7 +97,7 @@ namespace JackTor.Controllers
{
seasonTpl.Append(
$"{season} сезон",
$"{host}/jacktor?rjson={rjson}&title={enTitle}&original_title={enOriginal}&year={year}&original_language={original_language}&serial=1&s={season}",
$"{host}/lite/jacktor?rjson={rjson}&title={enTitle}&original_title={enOriginal}&year={year}&original_language={original_language}&serial=1&s={season}",
season);
}
@ -127,7 +126,7 @@ namespace JackTor.Controllers
: $"{seasonLabel} • {torrent.Voice}";
string qualityInfo = $"{torrent.Tracker} / {torrent.QualityLabel} / {torrent.MediaInfo} / ↑{torrent.Seeders}";
string releaseLink = accsArgs($"{host}/jacktor/serial/{torrent.Rid}?rjson={rjson}&title={enTitle}&original_title={enOriginal}&s={targetSeason}");
string releaseLink = accsArgs($"{host}/lite/jacktor/serial/{torrent.Rid}?rjson={rjson}&title={enTitle}&original_title={enOriginal}&s={targetSeason}");
similarTpl.Append(releaseName, null, qualityInfo, releaseLink);
}
@ -147,7 +146,7 @@ namespace JackTor.Controllers
: torrent.Voice;
string voiceName = $"{torrent.QualityLabel} / {torrent.MediaInfo} / ↑{torrent.Seeders}";
string streamLink = accsArgs($"{host}/jacktor/s{torrent.Rid}");
string streamLink = accsArgs($"{host}/lite/jacktor/s{torrent.Rid}");
movieTpl.Append(
voice,
@ -163,10 +162,10 @@ namespace JackTor.Controllers
}
[HttpGet]
[Route("jacktor/serial/{rid}")]
[Route("lite/jacktor/serial/{rid}")]
async public ValueTask<ActionResult> Serial(string rid, string account_email, string title, string original_title, int s = 1, bool rjson = false)
{
var init = await loadKit(ModInit.Settings);
var init = loadKit(ModInit.Settings);
if (!init.enable)
return Forbid();
@ -175,7 +174,7 @@ namespace JackTor.Controllers
var invoke = new JackTorInvoke(init, hybridCache, OnLog, proxyManager);
if (!invoke.TryGetSource(rid, out JackTorSourceCache source))
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
string memKey = $"jacktor:serial:{rid}";
@ -185,49 +184,49 @@ namespace JackTor.Controllers
{
var ts = ResolveProbeTorrentServer(init, account_email);
if (string.IsNullOrWhiteSpace(ts.host))
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
string hashResponse = await Http.Post(
string hashResponse = await httpHydra.Post(
$"{ts.host}/torrents",
BuildAddPayload(source.SourceUri),
timeoutSeconds: 8,
headers: ts.headers);
statusCodeOK: false,
newheaders: ts.headers);
string hash = ExtractHash(hashResponse);
if (string.IsNullOrWhiteSpace(hash))
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
Stat stat = null;
DateTime deadline = DateTime.Now.AddSeconds(20);
while (true)
{
stat = await Http.Post<Stat>(
stat = await httpHydra.Post<Stat>(
$"{ts.host}/torrents",
BuildGetPayload(hash),
timeoutSeconds: 3,
headers: ts.headers);
statusCodeOK: false,
newheaders: ts.headers);
if (stat?.file_stats != null && stat.file_stats.Length > 0)
break;
if (DateTime.Now > deadline)
{
_ = Http.Post($"{ts.host}/torrents", BuildRemovePayload(hash), headers: ts.headers);
return OnError("jacktor", proxyManager);
_ = httpHydra.Post($"{ts.host}/torrents", BuildRemovePayload(hash), statusCodeOK: false, newheaders: ts.headers);
return OnError("jacktor", refresh_proxy: true);
}
await Task.Delay(250);
}
_ = Http.Post($"{ts.host}/torrents", BuildRemovePayload(hash), headers: ts.headers);
_ = httpHydra.Post($"{ts.host}/torrents", BuildRemovePayload(hash), statusCodeOK: false, newheaders: ts.headers);
fileStats = stat.file_stats;
hybridCache.Set(memKey, fileStats, DateTime.Now.AddHours(36));
}
if (fileStats == null || fileStats.Length == 0)
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
var episodeTpl = new EpisodeTpl();
int appended = 0;
@ -242,13 +241,13 @@ namespace JackTor.Controllers
title ?? original_title,
s.ToString(),
file.Id.ToString(),
accsArgs($"{host}/jacktor/s{rid}?tsid={file.Id}"));
accsArgs($"{host}/lite/jacktor/s{rid}?tsid={file.Id}"));
appended++;
}
if (appended == 0)
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
return rjson
? Content(episodeTpl.ToJson(), "application/json; charset=utf-8")
@ -257,10 +256,10 @@ namespace JackTor.Controllers
}
[HttpGet]
[Route("jacktor/s{rid}")]
[Route("lite/jacktor/s{rid}")]
async public ValueTask<ActionResult> Stream(string rid, int tsid = -1, string account_email = null)
{
var init = await loadKit(ModInit.Settings);
var init = loadKit(ModInit.Settings);
if (!init.enable)
return Forbid();
@ -269,7 +268,7 @@ namespace JackTor.Controllers
var invoke = new JackTorInvoke(init, hybridCache, OnLog, proxyManager);
if (!invoke.TryGetSource(rid, out JackTorSourceCache source))
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
int index = tsid != -1 ? tsid : 1;
string country = requestInfo.Country;
@ -284,15 +283,15 @@ namespace JackTor.Controllers
var headers = HeadersModel.Init("Authorization", $"Basic {CrypTo.Base64($"{login}:{passwd}")}");
headers = HeadersModel.Join(headers, addheaders);
string response = await Http.Post(
string response = await httpHydra.Post(
$"{tsHost}/torrents",
BuildAddPayload(source.SourceUri),
timeoutSeconds: 5,
headers: headers);
statusCodeOK: false,
newheaders: headers);
hash = ExtractHash(response);
if (string.IsNullOrWhiteSpace(hash))
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
hybridCache.Set(memKey, hash, DateTime.Now.AddMinutes(1));
}
@ -329,7 +328,7 @@ namespace JackTor.Controllers
}
if (servers.Count == 0)
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
ts = servers[Random.Shared.Next(0, servers.Count)];
hybridCache.Set(tsKey, ts, DateTime.Now.AddHours(4));
@ -342,7 +341,7 @@ namespace JackTor.Controllers
if (init.base_auth != null && init.base_auth.enable)
{
if (init.torrs == null || init.torrs.Length == 0)
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
string tsKey = $"jacktor:ts3:{rid}:{requestInfo.IP}";
if (!hybridCache.TryGetValue(tsKey, out string tsHost))
@ -355,7 +354,7 @@ namespace JackTor.Controllers
}
if (init.torrs == null || init.torrs.Length == 0)
return OnError("jacktor", proxyManager);
return OnError("jacktor", refresh_proxy: true);
string key = $"jacktor:ts4:{rid}:{requestInfo.IP}";
if (!hybridCache.TryGetValue(key, out string torrentHost))
@ -474,5 +473,38 @@ namespace JackTor.Controllers
return Regex.Replace(url, "(apikey=)[^&]+", "$1***", RegexOptions.IgnoreCase);
}
private static bool IsCheckOnlineSearchEnabled()
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (checkProp?.GetValue(conf) is bool enabled)
return enabled;
}
catch
{
}
return true;
}
private static void OnLog(string message)
{
System.Console.WriteLine(message);
}
}
}

4
JackTor/GlobalUsings.cs Normal file
View File

@ -0,0 +1,4 @@
global using Shared.Services;
global using Shared.Services.Hybrid;
global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@ -1,5 +1,4 @@
using JackTor.Models;
using Shared.Engine;
using Shared.Models;
using System;
using System.Collections.Generic;

View File

@ -2,8 +2,8 @@ using JackTor.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Shared;
using Shared.Engine;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using System;
using System.Net.Http;
using System.Net.Mime;
@ -15,9 +15,9 @@ using System.Threading.Tasks;
namespace JackTor
{
public class ModInit
public class ModInit : IModuleLoaded
{
public static double Version => 1.0;
public static double Version => 2.0;
public static JackTorSettings JackTor;
@ -30,7 +30,7 @@ namespace JackTor
/// <summary>
/// Модуль завантажено.
/// </summary>
public static void loaded(InitspaceModel initspace)
public void Loaded(InitspaceModel initspace)
{
JackTor = new JackTorSettings("JackTor", "http://127.0.0.1:9117", streamproxy: false, useproxy: false)
{
@ -66,7 +66,7 @@ namespace JackTor
}
};
var conf = ModuleInvoke.Conf("JackTor", JackTor) ?? JObject.FromObject(JackTor);
var conf = ModuleInvoke.Init("JackTor", JObject.FromObject(JackTor)) ?? JObject.FromObject(JackTor);
JackTor = conf.ToObject<JackTorSettings>();
if (string.IsNullOrWhiteSpace(JackTor.jackett))
@ -76,7 +76,45 @@ namespace JackTor
JackTor.host = JackTor.jackett;
// Показувати «уточнити пошук».
AppInit.conf.online.with_search.Add("jacktor");
RegisterWithSearch("jacktor");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}

View File

@ -197,4 +197,18 @@ namespace JackTor.Models
public int[] Seasons { get; set; }
}
public class FileStat
{
public int Id { get; set; }
public string Path { get; set; }
public long Length { get; set; }
}
public class Stat
{
public FileStat[] file_stats { get; set; }
}
}

View File

@ -2,46 +2,41 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared.Models;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace JackTor
{
public class OnlineApi
public class OnlineApi : IModuleOnline
{
public static List<(string name, string url, string plugin, int index)> Invoke(
HttpContext httpContext,
IMemoryCache memoryCache,
RequestModel requestInfo,
string host,
OnlineEventsModel args)
public List<ModuleOnlineItem> 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)
public Task<List<ModuleOnlineItem>> InvokeAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineEventsModel args)
=> Task.FromResult(default(List<ModuleOnlineItem>));
public List<ModuleOnlineSpiderItem> Spider(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> null;
public Task<List<ModuleOnlineSpiderItem>> SpiderAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> Task.FromResult(default(List<ModuleOnlineSpiderItem>));
private static List<ModuleOnlineItem> 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 online = new List<ModuleOnlineItem>();
var init = ModInit.JackTor;
if (init.enable && !init.rip)
{
string url = init.overridehost;
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
url = $"{host}/jacktor";
url = $"{host}/lite/jacktor";
online.Add((init.displayname, url, "jacktor", init.displayindex));
online.Add(new ModuleOnlineItem(init.displayname, url, "jacktor", init.displayindex));
}
return online;

View File

@ -22,27 +22,27 @@ namespace KlonFUN.Controllers
}
[HttpGet]
[Route("klonfun")]
[Route("lite/klonfun")]
async public 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 t, int s = -1, bool rjson = false, string href = null, bool checksearch = false)
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.KlonFUN);
var init = loadKit(ModInit.KlonFUN);
if (!init.enable)
return Forbid();
var invoke = new KlonFUNInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new KlonFUNInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
if (checksearch)
{
if (AppInit.conf?.online?.checkOnlineSearch != true)
return OnError("klonfun", proxyManager);
if (!IsCheckOnlineSearchEnabled())
return OnError("klonfun", refresh_proxy: true);
var checkResults = await invoke.Search(imdb_id, title, original_title);
if (checkResults != null && checkResults.Count > 0)
return Content("data-json=", "text/plain; charset=utf-8");
return OnError("klonfun", proxyManager);
return OnError("klonfun", refresh_proxy: true);
}
string itemUrl = href;
@ -50,14 +50,14 @@ namespace KlonFUN.Controllers
{
var searchResults = await invoke.Search(imdb_id, title, original_title);
if (searchResults == null || searchResults.Count == 0)
return OnError("klonfun", proxyManager);
return OnError("klonfun", refresh_proxy: true);
if (searchResults.Count > 1)
{
var similarTpl = new SimilarTpl(searchResults.Count);
foreach (SearchResult result in searchResults)
{
string link = $"{host}/klonfun?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(result.Url)}";
string link = $"{host}/lite/klonfun?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(result.Url)}";
similarTpl.Append(result.Title, result.Year > 0 ? result.Year.ToString() : string.Empty, string.Empty, link, result.Poster);
}
@ -73,7 +73,7 @@ namespace KlonFUN.Controllers
if (item == null || string.IsNullOrWhiteSpace(item.PlayerUrl))
{
OnLog($"KlonFUN: не знайдено iframe-плеєр для {itemUrl}");
return OnError("klonfun", proxyManager);
return OnError("klonfun", refresh_proxy: true);
}
string contentTitle = !string.IsNullOrWhiteSpace(title) ? title : item.Title;
@ -87,7 +87,7 @@ namespace KlonFUN.Controllers
{
var serialStructure = await invoke.GetSerialStructure(item.PlayerUrl);
if (serialStructure == null || serialStructure.Voices.Count == 0)
return OnError("klonfun", proxyManager);
return OnError("klonfun", refresh_proxy: true);
if (s == -1)
{
@ -118,12 +118,12 @@ namespace KlonFUN.Controllers
}
if (seasons.Count == 0)
return OnError("klonfun", proxyManager);
return OnError("klonfun", refresh_proxy: true);
var seasonTpl = new SeasonTpl(seasons.Count);
foreach (int seasonNumber in seasons)
{
string link = $"{host}/klonfun?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={seasonNumber}&href={HttpUtility.UrlEncode(itemUrl)}";
string link = $"{host}/lite/klonfun?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={seasonNumber}&href={HttpUtility.UrlEncode(itemUrl)}";
if (!string.IsNullOrWhiteSpace(t))
link += $"&t={HttpUtility.UrlEncode(t)}";
@ -140,7 +140,7 @@ namespace KlonFUN.Controllers
.ToList();
if (voicesForSeason.Count == 0)
return OnError("klonfun", proxyManager);
return OnError("klonfun", refresh_proxy: true);
var selectedVoiceForSeason = voicesForSeason
.FirstOrDefault(v => !string.IsNullOrWhiteSpace(t) && v.Key.Equals(t, StringComparison.OrdinalIgnoreCase))
@ -149,12 +149,12 @@ namespace KlonFUN.Controllers
var voiceTpl = new VoiceTpl(voicesForSeason.Count);
foreach (var voice in voicesForSeason)
{
string voiceLink = $"{host}/klonfun?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&t={HttpUtility.UrlEncode(voice.Key)}&href={HttpUtility.UrlEncode(itemUrl)}";
string voiceLink = $"{host}/lite/klonfun?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&t={HttpUtility.UrlEncode(voice.Key)}&href={HttpUtility.UrlEncode(itemUrl)}";
voiceTpl.Append(voice.DisplayName, voice.Key.Equals(selectedVoiceForSeason.Key, StringComparison.OrdinalIgnoreCase), voiceLink);
}
if (!selectedVoiceForSeason.Seasons.TryGetValue(s, out List<SerialEpisode> episodes) || episodes.Count == 0)
return OnError("klonfun", proxyManager);
return OnError("klonfun", refresh_proxy: true);
var episodeTpl = new EpisodeTpl(episodes.Count);
foreach (SerialEpisode episode in episodes.OrderBy(e => e.Number))
@ -177,7 +177,7 @@ namespace KlonFUN.Controllers
{
var streams = await invoke.GetMovieStreams(item.PlayerUrl);
if (streams == null || streams.Count == 0)
return OnError("klonfun", proxyManager);
return OnError("klonfun", refresh_proxy: true);
var movieTpl = new MovieTpl(contentTitle, contentOriginalTitle, streams.Count);
for (int i = 0; i < streams.Count; i++)
@ -232,5 +232,38 @@ namespace KlonFUN.Controllers
cleaned = cleaned.Replace("?&", "?").Replace("&&", "&").TrimEnd('?', '&');
return cleaned;
}
private static bool IsCheckOnlineSearchEnabled()
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (checkProp?.GetValue(conf) is bool enabled)
return enabled;
}
catch
{
}
return true;
}
private static void OnLog(string message)
{
System.Console.WriteLine(message);
}
}
}

4
KlonFUN/GlobalUsings.cs Normal file
View File

@ -0,0 +1,4 @@
global using Shared.Services;
global using Shared.Services.Hybrid;
global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@ -28,13 +28,15 @@ namespace KlonFUN
private readonly IHybridCache _hybridCache;
private readonly Action<string> _onLog;
private readonly ProxyManager _proxyManager;
private readonly HttpHydra _httpHydra;
public KlonFUNInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
public KlonFUNInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
{
_init = init;
_hybridCache = hybridCache;
_onLog = onLog;
_proxyManager = proxyManager;
_httpHydra = httpHydra;
}
public async Task<List<SearchResult>> Search(string imdbId, string title, string originalTitle)
@ -108,7 +110,7 @@ namespace KlonFUN
try
{
var headers = DefaultHeaders();
string html = await Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
string html = await HttpGet(url, headers);
if (string.IsNullOrWhiteSpace(html))
return null;
@ -362,7 +364,7 @@ namespace KlonFUN
var headers = DefaultHeaders();
string form = $"do=search&subaction=search&story={HttpUtility.UrlEncode(query)}";
string html = await Http.Post(_init.cors(_init.host), form, headers: headers, proxy: _proxyManager.Get());
string html = await HttpPost(_init.host, form, headers);
if (string.IsNullOrWhiteSpace(html))
return null;
@ -465,7 +467,7 @@ namespace KlonFUN
requestUrl = ApnHelper.WrapUrl(_init, playerUrl);
var headers = DefaultHeaders();
return await Http.Get(_init.cors(requestUrl), headers: headers, proxy: _proxyManager.Get());
return await HttpGet(requestUrl, headers);
}
private static JArray ParsePlayerArray(string html)
@ -699,16 +701,28 @@ namespace KlonFUN
return null;
}
private Task<string> HttpGet(string url, List<HeadersModel> headers)
{
if (_httpHydra != null)
return _httpHydra.Get(url, newheaders: headers);
return Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
}
private Task<string> HttpPost(string url, string data, List<HeadersModel> headers)
{
if (_httpHydra != null)
return _httpHydra.Post(url, data, newheaders: headers);
return Http.Post(_init.cors(url), data, headers: headers, proxy: _proxyManager.Get());
}
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;
int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess;
if (ctime > multiaccess)
ctime = multiaccess;

View File

@ -3,6 +3,7 @@ using Shared;
using Shared.Engine;
using Shared.Models.Online.Settings;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
@ -16,9 +17,9 @@ using System.Threading.Tasks;
namespace KlonFUN
{
public class ModInit
public class ModInit : IModuleLoaded
{
public static double Version => 1.1;
public static double Version => 2.0;
public static OnlinesSettings KlonFUN;
public static bool ApnHostProvided;
@ -32,7 +33,7 @@ namespace KlonFUN
/// <summary>
/// Модуль завантажено.
/// </summary>
public static void loaded(InitspaceModel initspace)
public void Loaded(InitspaceModel initspace)
{
KlonFUN = new OnlinesSettings("KlonFUN", "https://klon.fun", streamproxy: false, useproxy: false)
{
@ -47,7 +48,7 @@ namespace KlonFUN
}
};
var conf = ModuleInvoke.Conf("KlonFUN", KlonFUN);
var conf = ModuleInvoke.Init("KlonFUN", JObject.FromObject(KlonFUN));
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
conf.Remove("apn");
conf.Remove("apn_host");
@ -67,7 +68,45 @@ namespace KlonFUN
}
// Додаємо підтримку "уточнити пошук".
AppInit.conf.online.with_search.Add("klonfun");
RegisterWithSearch("klonfun");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}

View File

@ -1,37 +1,42 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared.Models;
using Shared.Models.Base;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace KlonFUN
{
public class OnlineApi
public class OnlineApi : IModuleOnline
{
public static List<(string name, string url, string plugin, int index)> Invoke(
HttpContext httpContext,
IMemoryCache memoryCache,
RequestModel requestInfo,
string host,
OnlineEventsModel args)
public List<ModuleOnlineItem> 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)
public Task<List<ModuleOnlineItem>> InvokeAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineEventsModel args)
=> Task.FromResult(default(List<ModuleOnlineItem>));
public List<ModuleOnlineSpiderItem> Spider(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> null;
public Task<List<ModuleOnlineSpiderItem>> SpiderAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> Task.FromResult(default(List<ModuleOnlineSpiderItem>));
private static List<ModuleOnlineItem> 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 online = new List<ModuleOnlineItem>();
var init = ModInit.KlonFUN;
if (init.enable && !init.rip)
{
string url = init.overridehost;
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
url = $"{host}/klonfun";
url = $"{host}/lite/klonfun";
online.Add((init.displayname, url, "klonfun", init.displayindex));
online.Add(new ModuleOnlineItem(init.displayname, url, "klonfun", init.displayindex));
}
return online;

View File

@ -13,7 +13,7 @@ using Makhno.Models;
namespace Makhno
{
[Route("makhno")]
[Route("lite/makhno")]
public class MakhnoController : BaseOnlineController
{
private readonly ProxyManager proxyManager;
@ -28,7 +28,7 @@ namespace Makhno
{
if (checksearch)
{
if (AppInit.conf?.online?.checkOnlineSearch != true)
if (!IsCheckOnlineSearchEnabled())
return OnError();
return Content("data-json=", "text/plain; charset=utf-8");
@ -36,14 +36,14 @@ namespace Makhno
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.Makhno);
var init = loadKit(ModInit.Makhno);
if (!init.enable)
return OnError();
Initialization(init);
OnLog($"Makhno: {title} (serial={serial}, s={s}, season={season}, t={t})");
var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
var resolved = await ResolvePlaySource(imdb_id, serial, invoke);
if (resolved == null || string.IsNullOrEmpty(resolved.PlayUrl))
@ -61,14 +61,14 @@ namespace Makhno
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.Makhno);
var init = loadKit(ModInit.Makhno);
if (!init.enable)
return OnError();
Initialization(init);
OnLog($"Makhno Play: {title} (s={s}, season={season}, t={t}, episodeId={episodeId}) play={play}");
var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
var resolved = await ResolvePlaySource(imdb_id, serial: 1, invoke);
if (resolved == null || string.IsNullOrEmpty(resolved.PlayUrl))
return OnError();
@ -119,14 +119,14 @@ namespace Makhno
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.Makhno);
var init = loadKit(ModInit.Makhno);
if (!init.enable)
return OnError();
Initialization(init);
OnLog($"Makhno PlayMovie: {title} ({year}) play={play}");
var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
var resolved = await ResolvePlaySource(imdb_id, serial: 0, invoke);
if (resolved == null || string.IsNullOrEmpty(resolved.PlayUrl))
return OnError();
@ -267,7 +267,7 @@ namespace Makhno
string voiceParam = seasonVoiceIndex.HasValue ? $"&t={seasonVoiceIndex.Value}" : string.Empty;
string seasonName = seasonItem.HasValue ? seasonItem.Value.Season?.Title ?? $"Сезон {seasonNumber}" : $"Сезон {seasonNumber}";
string link = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={seasonNumber}{voiceParam}";
string link = $"{host}/lite/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={seasonNumber}{voiceParam}";
season_tpl.Append(seasonName, link, seasonNumber.ToString());
}
@ -337,7 +337,7 @@ namespace Makhno
string voiceParam = seasonVoiceIndexForTpl.HasValue ? $"&t={seasonVoiceIndexForTpl.Value}" : string.Empty;
string seasonName = seasonItem.HasValue ? seasonItem.Value.Season?.Title ?? $"Сезон {seasonNumber}" : $"Сезон {seasonNumber}";
string link = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={seasonNumber}{voiceParam}";
string link = $"{host}/lite/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={seasonNumber}{voiceParam}";
seasonTplForVoice.Append(seasonName, link, seasonNumber.ToString());
}
@ -354,11 +354,11 @@ namespace Makhno
bool sameSeasonSet = seasonsForVoice.Select(s => s.Number).ToHashSet().SetEquals(selectedVoiceSeasonSet);
if (hasRequestedSeason && sameSeasonSet)
{
voiceLink = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={requestedSeason}&t={i}";
voiceLink = $"{host}/lite/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={requestedSeason}&t={i}";
}
else
{
voiceLink = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season=-1&t={i}";
voiceLink = $"{host}/lite/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season=-1&t={i}";
}
bool isActive = selectedVoice == i.ToString();
@ -374,7 +374,7 @@ namespace Makhno
bool hasRequestedSeason = seasonsForVoice.Any(s => s.Number == requestedSeason);
if (!hasRequestedSeason)
{
string redirectUrl = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season=-1&t={voiceIndex}";
string redirectUrl = $"{host}/lite/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season=-1&t={voiceIndex}";
return UpdateService.Validate(Redirect(redirectUrl));
}
@ -517,5 +517,38 @@ namespace Makhno
public string PlayUrl { get; set; }
public bool IsSerial { get; set; }
}
private static bool IsCheckOnlineSearchEnabled()
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (checkProp?.GetValue(conf) is bool enabled)
return enabled;
}
catch
{
}
return true;
}
private static void OnLog(string message)
{
System.Console.WriteLine(message);
}
}
}

4
Makhno/GlobalUsings.cs Normal file
View File

@ -0,0 +1,4 @@
global using Shared.Services;
global using Shared.Services.Hybrid;
global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@ -24,13 +24,15 @@ namespace Makhno
private readonly IHybridCache _hybridCache;
private readonly Action<string> _onLog;
private readonly ProxyManager _proxyManager;
private readonly HttpHydra _httpHydra;
public MakhnoInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
public MakhnoInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
{
_init = init;
_hybridCache = hybridCache;
_onLog = onLog;
_proxyManager = proxyManager;
_httpHydra = httpHydra;
}
public async Task<string> GetWormholePlay(string imdbId)
@ -46,7 +48,7 @@ namespace Makhno
new HeadersModel("User-Agent", Http.UserAgent)
};
string response = await Http.Get(_init.cors(url), timeoutSeconds: 4, headers: headers, proxy: _proxyManager.Get());
string response = await HttpGet(url, headers, timeoutSeconds: 4);
if (string.IsNullOrWhiteSpace(response))
return null;
@ -84,7 +86,7 @@ namespace Makhno
_onLog($"Makhno getting player data from: {requestUrl}");
var response = await Http.Get(_init.cors(requestUrl), headers: headers, proxy: _proxyManager.Get());
var response = await HttpGet(requestUrl, headers);
if (string.IsNullOrEmpty(response))
return null;
@ -526,6 +528,14 @@ namespace Makhno
return normalized;
}
private Task<string> HttpGet(string url, List<HeadersModel> headers, int timeoutSeconds = 15)
{
if (_httpHydra != null)
return _httpHydra.Get(url, newheaders: headers);
return Http.Get(_init.cors(url), timeoutSeconds: timeoutSeconds, headers: headers, proxy: _proxyManager.Get());
}
private class WormholeResponse
{
public string play { get; set; }

View File

@ -4,6 +4,7 @@ using Shared.Engine;
using Newtonsoft.Json.Linq;
using Shared.Models.Online.Settings;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.Extensions.Caching.Memory;
@ -21,9 +22,9 @@ using System.Threading.Tasks;
namespace Makhno
{
public class ModInit
public class ModInit : IModuleLoaded
{
public static double Version => 2.1;
public static double Version => 3.0;
public static OnlinesSettings Makhno;
public static bool ApnHostProvided;
@ -37,7 +38,7 @@ namespace Makhno
/// <summary>
/// модуль загружен
/// </summary>
public static void loaded(InitspaceModel initspace)
public void Loaded(InitspaceModel initspace)
{
Makhno = new OnlinesSettings("Makhno", "https://wh.lme.isroot.in", streamproxy: false, useproxy: false)
{
@ -51,7 +52,7 @@ namespace Makhno
list = new string[] { "socks5://ip:port" }
}
};
var conf = ModuleInvoke.Conf("Makhno", Makhno);
var conf = ModuleInvoke.Init("Makhno", JObject.FromObject(Makhno));
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
if (hasApn)
{
@ -73,7 +74,45 @@ namespace Makhno
}
// Виводити "уточнити пошук"
AppInit.conf.online.with_search.Add("makhno");
RegisterWithSearch("makhno");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}

View File

@ -1,37 +1,42 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared.Models;
using Shared.Models.Base;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Makhno
{
public class OnlineApi
public class OnlineApi : IModuleOnline
{
public static List<(string name, string url, string plugin, int index)> Invoke(
HttpContext httpContext,
IMemoryCache memoryCache,
RequestModel requestInfo,
string host,
OnlineEventsModel args)
public List<ModuleOnlineItem> 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)
public Task<List<ModuleOnlineItem>> InvokeAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineEventsModel args)
=> Task.FromResult(default(List<ModuleOnlineItem>));
public List<ModuleOnlineSpiderItem> Spider(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> null;
public Task<List<ModuleOnlineSpiderItem>> SpiderAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> Task.FromResult(default(List<ModuleOnlineSpiderItem>));
private static List<ModuleOnlineItem> 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 online = new List<ModuleOnlineItem>();
var init = ModInit.Makhno;
if (init.enable && !init.rip)
{
string url = init.overridehost;
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
url = $"{host}/makhno";
url = $"{host}/lite/makhno";
online.Add((init.displayname, url, "makhno", init.displayindex));
online.Add(new ModuleOnlineItem(init.displayname, url, "makhno", init.displayindex));
}
return online;

View File

@ -23,48 +23,48 @@ namespace Mikai.Controllers
}
[HttpGet]
[Route("mikai")]
[Route("lite/mikai")]
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 t, int s = -1, bool rjson = false, bool checksearch = false)
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.Mikai);
var init = loadKit(ModInit.Mikai);
if (!init.enable)
return Forbid();
var invoke = new MikaiInvoke(init, hybridCache, OnLog, _proxyManager);
var invoke = new MikaiInvoke(init, hybridCache, OnLog, _proxyManager, httpHydra);
if (checksearch)
{
if (AppInit.conf?.online?.checkOnlineSearch != true)
return OnError("mikai", _proxyManager);
if (!IsCheckOnlineSearchEnabled())
return OnError("mikai", refresh_proxy: true);
var checkResults = await invoke.Search(title, original_title, year);
if (checkResults != null && checkResults.Count > 0)
return Content("data-json=", "text/plain; charset=utf-8");
return OnError("mikai", _proxyManager);
return OnError("mikai", refresh_proxy: true);
}
OnLog($"Mikai Index: title={title}, original_title={original_title}, serial={serial}, s={s}, t={t}, year={year}");
var searchResults = await invoke.Search(title, original_title, year);
if (searchResults == null || searchResults.Count == 0)
return OnError("mikai", _proxyManager);
return OnError("mikai", refresh_proxy: true);
var selected = searchResults.FirstOrDefault();
if (selected == null)
return OnError("mikai", _proxyManager);
return OnError("mikai", refresh_proxy: true);
var details = await invoke.GetDetails(selected.Id);
if (details == null || details.Players == null || details.Players.Count == 0)
return OnError("mikai", _proxyManager);
return OnError("mikai", refresh_proxy: true);
bool isSerial = serial == 1 || (serial == -1 && !string.Equals(details.Format, "movie", StringComparison.OrdinalIgnoreCase));
var seasonDetails = await CollectSeasonDetails(details, invoke);
var voices = BuildVoices(seasonDetails);
if (voices.Count == 0)
return OnError("mikai", _proxyManager);
return OnError("mikai", refresh_proxy: true);
string displayTitle = title ?? details.Details?.Names?.Name ?? original_title;
@ -81,14 +81,14 @@ namespace Mikai.Controllers
.ToList();
if (seasonNumbers.Count == 0)
return OnError("mikai", _proxyManager);
return OnError("mikai", refresh_proxy: true);
if (s == -1)
{
var seasonTpl = new SeasonTpl(seasonNumbers.Count);
foreach (var seasonNumber in seasonNumbers)
{
string link = $"{host}/mikai?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={seasonNumber}";
string link = $"{host}/lite/mikai?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={seasonNumber}";
if (restrictByVoice)
link += $"&t={HttpUtility.UrlEncode(t)}";
seasonTpl.Append($"{seasonNumber}", link, seasonNumber.ToString());
@ -104,7 +104,7 @@ namespace Mikai.Controllers
.ToList();
if (!voicesForSeason.Any())
return OnError("mikai", _proxyManager);
return OnError("mikai", refresh_proxy: true);
if (string.IsNullOrEmpty(t))
t = voicesForSeason[0].Key;
@ -118,7 +118,7 @@ namespace Mikai.Controllers
{
var targetSeasonSet = GetSeasonSet(voice.Value);
bool sameSeasonSet = targetSeasonSet.SetEquals(selectedSeasonSet);
string voiceLink = $"{host}/mikai?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1";
string voiceLink = $"{host}/lite/mikai?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1";
if (sameSeasonSet)
voiceLink += $"&s={s}&t={HttpUtility.UrlEncode(voice.Key)}";
else
@ -128,7 +128,7 @@ namespace Mikai.Controllers
if (!voices.ContainsKey(t) || !voices[t].Seasons.ContainsKey(s))
{
string redirectUrl = $"{host}/mikai?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s=-1&t={HttpUtility.UrlEncode(t)}";
string redirectUrl = $"{host}/lite/mikai?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s=-1&t={HttpUtility.UrlEncode(t)}";
return Redirect(redirectUrl);
}
@ -143,7 +143,7 @@ namespace Mikai.Controllers
if (NeedsResolve(voices[t].ProviderName, streamLink))
{
string callUrl = $"{host}/mikai/play?url={HttpUtility.UrlEncode(streamLink)}&title={HttpUtility.UrlEncode(displayTitle)}&serial=1";
string callUrl = $"{host}/lite/mikai/play?url={HttpUtility.UrlEncode(streamLink)}&title={HttpUtility.UrlEncode(displayTitle)}&serial=1";
episodeTpl.Append(episodeName, displayTitle, s.ToString(), ep.Number.ToString(), accsArgs(callUrl), "call");
}
else
@ -177,14 +177,14 @@ namespace Mikai.Controllers
foreach (var ashdiStream in ashdiStreams)
{
string optionName = $"{voice.DisplayName} {ashdiStream.title}";
string ashdiCallUrl = $"{host}/mikai/play?url={HttpUtility.UrlEncode(ashdiStream.link)}&title={HttpUtility.UrlEncode(displayTitle)}";
string ashdiCallUrl = $"{host}/lite/mikai/play?url={HttpUtility.UrlEncode(ashdiStream.link)}&title={HttpUtility.UrlEncode(displayTitle)}";
movieTpl.Append(optionName, accsArgs(ashdiCallUrl), "call");
}
continue;
}
}
string callUrl = $"{host}/mikai/play?url={HttpUtility.UrlEncode(episode.Url)}&title={HttpUtility.UrlEncode(displayTitle)}";
string callUrl = $"{host}/lite/mikai/play?url={HttpUtility.UrlEncode(episode.Url)}&title={HttpUtility.UrlEncode(displayTitle)}";
movieTpl.Append(voice.DisplayName, accsArgs(callUrl), "call");
}
else
@ -195,31 +195,31 @@ namespace Mikai.Controllers
}
if (movieTpl.data == null || movieTpl.data.Count == 0)
return OnError("mikai", _proxyManager);
return OnError("mikai", refresh_proxy: true);
return rjson
? Content(movieTpl.ToJson(), "application/json; charset=utf-8")
: Content(movieTpl.ToHtml(), "text/html; charset=utf-8");
}
[HttpGet("mikai/play")]
[HttpGet("lite/mikai/play")]
public async Task<ActionResult> Play(string url, string title = null, int serial = 0)
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.Mikai);
var init = loadKit(ModInit.Mikai);
if (!init.enable)
return Forbid();
if (string.IsNullOrEmpty(url))
return OnError("mikai", _proxyManager);
return OnError("mikai", refresh_proxy: true);
var invoke = new MikaiInvoke(init, hybridCache, OnLog, _proxyManager);
var invoke = new MikaiInvoke(init, hybridCache, OnLog, _proxyManager, httpHydra);
OnLog($"Mikai Play: url={url}, serial={serial}");
string streamLink = await invoke.ResolveVideoUrl(url, serial == 1);
if (string.IsNullOrEmpty(streamLink))
return OnError("mikai", _proxyManager);
return OnError("mikai", refresh_proxy: true);
List<HeadersModel> streamHeaders = null;
bool forceProxy = false;
@ -462,5 +462,38 @@ namespace Mikai.Controllers
return HostStreamProxy(init, link, headers: headers, force_streamproxy: forceProxy);
}
private static bool IsCheckOnlineSearchEnabled()
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (checkProp?.GetValue(conf) is bool enabled)
return enabled;
}
catch
{
}
return true;
}
private static void OnLog(string message)
{
System.Console.WriteLine(message);
}
}
}

4
Mikai/GlobalUsings.cs Normal file
View File

@ -0,0 +1,4 @@
global using Shared.Services;
global using Shared.Services.Hybrid;
global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@ -23,13 +23,15 @@ namespace Mikai
private readonly IHybridCache _hybridCache;
private readonly Action<string> _onLog;
private readonly ProxyManager _proxyManager;
private readonly HttpHydra _httpHydra;
public MikaiInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
public MikaiInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
{
_init = init;
_hybridCache = hybridCache;
_onLog = onLog;
_proxyManager = proxyManager;
_httpHydra = httpHydra;
}
public async Task<List<MikaiAnime>> Search(string title, string original_title, int year)
@ -49,7 +51,7 @@ namespace Mikai
var headers = DefaultHeaders();
_onLog($"Mikai: using proxy {_proxyManager.CurrentProxyIp} for {searchUrl}");
string json = await Http.Get(_init.cors(searchUrl), headers: headers, proxy: _proxyManager.Get());
string json = await HttpGet(searchUrl, headers);
if (string.IsNullOrEmpty(json))
return null;
@ -93,7 +95,7 @@ namespace Mikai
var headers = DefaultHeaders();
_onLog($"Mikai: using proxy {_proxyManager.CurrentProxyIp} for {url}");
string json = await Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
string json = await HttpGet(url, headers);
if (string.IsNullOrEmpty(json))
return null;
@ -144,7 +146,7 @@ namespace Mikai
};
_onLog($"Mikai: using proxy {_proxyManager.CurrentProxyIp} for {requestUrl}");
string html = await Http.Get(_init.cors(requestUrl), headers: headers, proxy: _proxyManager.Get());
string html = await HttpGet(requestUrl, headers);
if (string.IsNullOrEmpty(html))
return null;
@ -190,7 +192,7 @@ namespace Mikai
string requestUrl = AshdiRequestUrl(WithAshdiMultivoice(url, enable: !disableAshdiMultivoiceForVod));
_onLog($"Mikai: using proxy {_proxyManager.CurrentProxyIp} for {requestUrl}");
string html = await Http.Get(_init.cors(requestUrl), headers: headers, proxy: _proxyManager.Get());
string html = await HttpGet(requestUrl, headers);
if (string.IsNullOrEmpty(html))
return streams;
@ -415,12 +417,20 @@ namespace Mikai
return null;
}
private Task<string> HttpGet(string url, List<HeadersModel> headers)
{
if (_httpHydra != null)
return _httpHydra.Get(url, newheaders: headers);
return Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
}
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;
int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess;
if (ctime > multiaccess)
ctime = multiaccess;

View File

@ -3,6 +3,7 @@ using Shared;
using Shared.Engine;
using Shared.Models.Online.Settings;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.Extensions.Caching.Memory;
@ -22,9 +23,9 @@ using System.Threading.Tasks;
namespace Mikai
{
public class ModInit
public class ModInit : IModuleLoaded
{
public static double Version => 3.8;
public static double Version => 4.0;
public static OnlinesSettings Mikai;
public static bool ApnHostProvided;
@ -38,7 +39,7 @@ namespace Mikai
/// <summary>
/// модуль загружен
/// </summary>
public static void loaded(InitspaceModel initspace)
public void Loaded(InitspaceModel initspace)
{
@ -56,7 +57,7 @@ namespace Mikai
}
};
var conf = ModuleInvoke.Conf("Mikai", Mikai);
var conf = ModuleInvoke.Init("Mikai", JObject.FromObject(Mikai));
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
conf.Remove("apn");
conf.Remove("apn_host");
@ -75,7 +76,45 @@ namespace Mikai
}
// Виводити "уточнити пошук"
AppInit.conf.online.with_search.Add("mikai");
RegisterWithSearch("mikai");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}

View File

@ -1,47 +1,46 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared.Models;
using Shared.Models.Base;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Mikai
{
public class OnlineApi
public class OnlineApi : IModuleOnline
{
public static List<(string name, string url, string plugin, int index)> Invoke(
HttpContext httpContext,
IMemoryCache memoryCache,
RequestModel requestInfo,
string host,
OnlineEventsModel args)
public List<ModuleOnlineItem> 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)
public Task<List<ModuleOnlineItem>> InvokeAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineEventsModel args)
=> Task.FromResult(default(List<ModuleOnlineItem>));
public List<ModuleOnlineSpiderItem> Spider(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> null;
public Task<List<ModuleOnlineSpiderItem>> SpiderAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> Task.FromResult(default(List<ModuleOnlineSpiderItem>));
private static List<ModuleOnlineItem> 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 online = new List<ModuleOnlineItem>();
var init = ModInit.Mikai;
// Визначаємо isAnime згідно стандарту Lampac (Deepwiki):
// isanime = true якщо original_language == "ja" або "zh"
bool hasLang = !string.IsNullOrEmpty(original_language);
bool isanime = hasLang && (original_language == "ja" || original_language == "zh");
// Mikai — аніме-провайдер. Додаємо його:
// - при загальному пошуку (serial == -1), або
// - якщо контент визначений як аніме (isanime), або
// - якщо мова невідома (відсутній original_language)
if (init.enable && !init.rip && (serial == -1 || isanime || !hasLang))
{
string url = init.overridehost;
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
url = $"{host}/mikai";
url = $"{host}/lite/mikai";
online.Add((init.displayname, url, "mikai", init.displayindex));
online.Add(new ModuleOnlineItem(init.displayname, url, "mikai", init.displayindex));
}
return online;

View File

@ -25,35 +25,35 @@ namespace NMoonAnime.Controllers
}
[HttpGet]
[Route("nmoonanime")]
[Route("lite/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);
var init = loadKit(ModInit.NMoonAnime);
if (!init.enable)
return Forbid();
var invoke = new NMoonAnimeInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new NMoonAnimeInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
string effectiveMalId = ResolveMalId(mal_id, kinopoisk_id, source);
if (checksearch)
{
if (AppInit.conf?.online?.checkOnlineSearch != true)
return OnError("nmoonanime", proxyManager);
if (!IsCheckOnlineSearchEnabled())
return OnError("nmoonanime", refresh_proxy: true);
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);
return OnError("nmoonanime", refresh_proxy: true);
}
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);
return OnError("nmoonanime", refresh_proxy: true);
bool isSeries = serial == 1;
NMoonAnimeSeasonContent firstSeasonData = null;
@ -62,7 +62,7 @@ namespace NMoonAnime.Controllers
{
firstSeasonData = await invoke.GetSeasonContent(seasons[0]);
if (firstSeasonData == null || firstSeasonData.Voices.Count == 0)
return OnError("nmoonanime", proxyManager);
return OnError("nmoonanime", refresh_proxy: true);
isSeries = firstSeasonData.IsSeries;
}
@ -75,22 +75,22 @@ namespace NMoonAnime.Controllers
return await RenderMovie(invoke, seasons, title, original_title, firstSeasonData, rjson);
}
[HttpGet("nmoonanime/play")]
[HttpGet("lite/nmoonanime/play")]
public async Task<ActionResult> Play(string file, string title = null)
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.NMoonAnime);
var init = loadKit(ModInit.NMoonAnime);
if (!init.enable)
return Forbid();
if (string.IsNullOrWhiteSpace(file))
return OnError("nmoonanime", proxyManager);
return OnError("nmoonanime", refresh_proxy: true);
var invoke = new NMoonAnimeInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new NMoonAnimeInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
var streams = invoke.ParseStreams(file);
if (streams == null || streams.Count == 0)
return OnError("nmoonanime", proxyManager);
return OnError("nmoonanime", refresh_proxy: true);
if (streams.Count == 1)
{
@ -107,7 +107,7 @@ namespace NMoonAnime.Controllers
}
if (!streamQuality.Any())
return OnError("nmoonanime", proxyManager);
return OnError("nmoonanime", refresh_proxy: true);
var first = streamQuality.Firts();
string json = VideoTpl.ToJson("play", first.link, title ?? string.Empty, streamquality: streamQuality);
@ -133,7 +133,7 @@ namespace NMoonAnime.Controllers
.ToList();
if (orderedSeasons.Count == 0)
return OnError("nmoonanime", proxyManager);
return OnError("nmoonanime", refresh_proxy: true);
if (selectedSeason == -1)
{
@ -154,14 +154,14 @@ namespace NMoonAnime.Controllers
var currentSeason = orderedSeasons.FirstOrDefault(s => s.SeasonNumber == selectedSeason) ?? orderedSeasons[0];
var seasonData = await invoke.GetSeasonContent(currentSeason);
if (seasonData == null)
return OnError("nmoonanime", proxyManager);
return OnError("nmoonanime", refresh_proxy: true);
var voices = seasonData.Voices
.Where(v => v != null && v.Episodes != null && v.Episodes.Count > 0)
.ToList();
if (voices.Count == 0)
return OnError("nmoonanime", proxyManager);
return OnError("nmoonanime", refresh_proxy: true);
int activeVoiceIndex = ParseVoiceIndex(selectedVoice, voices.Count);
var voiceTpl = new VoiceTpl(voices.Count);
@ -180,7 +180,7 @@ namespace NMoonAnime.Controllers
.ToList();
if (episodes.Count == 0)
return OnError("nmoonanime", proxyManager);
return OnError("nmoonanime", refresh_proxy: true);
string displayTitle = !string.IsNullOrWhiteSpace(title)
? title
@ -193,7 +193,7 @@ namespace NMoonAnime.Controllers
{
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)}";
string callUrl = $"{host}/lite/nmoonanime/play?file={HttpUtility.UrlEncode(episode.File)}&title={HttpUtility.UrlEncode(displayTitle)}";
episodeTpl.Append(episodeName, displayTitle, currentSeason.SeasonNumber.ToString(), episodeNumber.ToString(), accsArgs(callUrl), "call");
}
@ -218,14 +218,14 @@ namespace NMoonAnime.Controllers
.FirstOrDefault();
if (currentSeason == null)
return OnError("nmoonanime", proxyManager);
return OnError("nmoonanime", refresh_proxy: true);
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);
return OnError("nmoonanime", refresh_proxy: true);
string displayTitle = !string.IsNullOrWhiteSpace(title)
? title
@ -249,13 +249,13 @@ namespace NMoonAnime.Controllers
continue;
string voiceName = string.IsNullOrWhiteSpace(voice.Name) ? $"Озвучка {fallbackIndex}" : voice.Name;
string callUrl = $"{host}/nmoonanime/play?file={HttpUtility.UrlEncode(file)}&title={HttpUtility.UrlEncode(displayTitle)}";
string callUrl = $"{host}/lite/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 OnError("nmoonanime", refresh_proxy: true);
return rjson
? Content(movieTpl.ToJson(), "application/json; charset=utf-8")
@ -265,7 +265,7 @@ namespace NMoonAnime.Controllers
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($"{host}/lite/nmoonanime?imdb_id={HttpUtility.UrlEncode(imdbId)}");
url.Append($"&kinopoisk_id={kinopoiskId}");
url.Append($"&title={HttpUtility.UrlEncode(title)}");
url.Append($"&original_title={HttpUtility.UrlEncode(originalTitle)}");
@ -353,5 +353,38 @@ namespace NMoonAnime.Controllers
cleaned = cleaned.Replace("?&", "?").Replace("&&", "&").TrimEnd('?', '&');
return cleaned;
}
private static bool IsCheckOnlineSearchEnabled()
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (checkProp?.GetValue(conf) is bool enabled)
return enabled;
}
catch
{
}
return true;
}
private static void OnLog(string message)
{
System.Console.WriteLine(message);
}
}
}

View File

@ -0,0 +1,4 @@
global using Shared.Services;
global using Shared.Services.Hybrid;
global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;

View File

@ -4,6 +4,7 @@ using Newtonsoft.Json.Linq;
using Shared;
using Shared.Engine;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using Shared.Models.Online.Settings;
using System;
using System.Net.Http;
@ -16,9 +17,9 @@ using System.Threading.Tasks;
namespace NMoonAnime
{
public class ModInit
public class ModInit : IModuleLoaded
{
public static double Version => 1.0;
public static double Version => 2.0;
public static OnlinesSettings NMoonAnime;
@ -33,7 +34,7 @@ namespace NMoonAnime
/// <summary>
/// Модуль завантажено.
/// </summary>
public static void loaded(InitspaceModel initspace)
public void Loaded(InitspaceModel initspace)
{
NMoonAnime = new OnlinesSettings("NMoonAnime", "https://moonanime.art", "https://apx.lme.isroot.in", streamproxy: false, useproxy: false)
{
@ -48,7 +49,7 @@ namespace NMoonAnime
}
};
var conf = ModuleInvoke.Conf("NMoonAnime", NMoonAnime) ?? JObject.FromObject(NMoonAnime);
var conf = ModuleInvoke.Init("NMoonAnime", JObject.FromObject(NMoonAnime)) ?? JObject.FromObject(NMoonAnime);
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
conf.Remove("apn");
conf.Remove("apn_host");
@ -68,7 +69,45 @@ namespace NMoonAnime
NMoonAnime.apn = null;
}
AppInit.conf.online.with_search.Add("nmoonanime");
RegisterWithSearch("nmoonanime");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@ -22,6 +22,7 @@ namespace NMoonAnime
private readonly IHybridCache _hybridCache;
private readonly Action<string> _onLog;
private readonly ProxyManager _proxyManager;
private readonly HttpHydra _httpHydra;
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
@ -35,12 +36,13 @@ namespace NMoonAnime
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)
public NMoonAnimeInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
{
_init = init;
_hybridCache = hybridCache;
_onLog = onLog;
_proxyManager = proxyManager;
_httpHydra = httpHydra;
}
public async Task<List<NMoonAnimeSeasonRef>> Search(string imdbId, string malId, string title, int year)
@ -64,7 +66,7 @@ namespace NMoonAnime
continue;
_onLog($"NMoonAnime: пошук через {searchUrl}");
string json = await Http.Get(_init.cors(searchUrl), headers: DefaultHeaders(), proxy: _proxyManager.Get());
string json = await HttpGet(searchUrl, DefaultHeaders());
if (string.IsNullOrWhiteSpace(json))
continue;
@ -108,7 +110,7 @@ namespace NMoonAnime
try
{
_onLog($"NMoonAnime: завантаження сезону {season.Url}");
string html = await Http.Get(_init.cors(season.Url), headers: DefaultHeaders(), proxy: _proxyManager.Get());
string html = await HttpGet(season.Url, DefaultHeaders());
if (string.IsNullOrWhiteSpace(html))
return null;
@ -1098,12 +1100,20 @@ namespace NMoonAnime
};
}
private Task<string> HttpGet(string url, List<HeadersModel> headers)
{
if (_httpHydra != null)
return _httpHydra.Get(url, newheaders: headers);
return Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
}
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;
int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess;
if (ctime > multiaccess)
ctime = multiaccess;

View File

@ -1,28 +1,33 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared.Models;
using Shared.Models.Base;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NMoonAnime
{
public class OnlineApi
public class OnlineApi : IModuleOnline
{
public static List<(string name, string url, string plugin, int index)> Invoke(
HttpContext httpContext,
IMemoryCache memoryCache,
RequestModel requestInfo,
string host,
OnlineEventsModel args)
public List<ModuleOnlineItem> 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)
public Task<List<ModuleOnlineItem>> InvokeAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineEventsModel args)
=> Task.FromResult(default(List<ModuleOnlineItem>));
public List<ModuleOnlineSpiderItem> Spider(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> null;
public Task<List<ModuleOnlineSpiderItem>> SpiderAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> Task.FromResult(default(List<ModuleOnlineSpiderItem>));
private static List<ModuleOnlineItem> 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 online = new List<ModuleOnlineItem>();
var init = ModInit.NMoonAnime;
@ -33,9 +38,9 @@ namespace NMoonAnime
{
string url = init.overridehost;
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
url = $"{host}/nmoonanime";
url = $"{host}/lite/nmoonanime";
online.Add((init.displayname, url, "nmoonanime", init.displayindex));
online.Add(new ModuleOnlineItem(init.displayname, url, "nmoonanime", init.displayindex));
}
return online;

View File

@ -24,27 +24,27 @@ namespace StarLight.Controllers
}
[HttpGet]
[Route("starlight")]
[Route("lite/starlight")]
async public 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, int s = -1, bool rjson = false, string href = null, bool checksearch = false)
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.StarLight);
var init = loadKit(ModInit.StarLight);
if (!init.enable)
return Forbid();
var invoke = new StarLightInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new StarLightInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
if (checksearch)
{
if (AppInit.conf?.online?.checkOnlineSearch != true)
return OnError("starlight", proxyManager);
if (!IsCheckOnlineSearchEnabled())
return OnError("starlight", refresh_proxy: true);
var searchResults = await invoke.Search(title, original_title);
if (searchResults != null && searchResults.Count > 0)
return Content("data-json=", "text/plain; charset=utf-8");
return OnError("starlight", proxyManager);
return OnError("starlight", refresh_proxy: true);
}
string itemUrl = href;
@ -52,14 +52,14 @@ namespace StarLight.Controllers
{
var searchResults = await invoke.Search(title, original_title);
if (searchResults == null || searchResults.Count == 0)
return OnError("starlight", proxyManager);
return OnError("starlight", refresh_proxy: true);
if (searchResults.Count > 1)
{
var similar_tpl = new SimilarTpl(searchResults.Count);
foreach (var res in searchResults)
{
string link = $"{host}/starlight?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(res.Href)}";
string link = $"{host}/lite/starlight?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(res.Href)}";
similar_tpl.Append(res.Title, string.Empty, string.Empty, link, string.Empty);
}
@ -71,7 +71,7 @@ namespace StarLight.Controllers
var project = await invoke.GetProject(itemUrl);
if (project == null)
return OnError("starlight", proxyManager);
return OnError("starlight", refresh_proxy: true);
if (serial == 1 && project.Seasons.Count > 0)
{
@ -82,7 +82,7 @@ namespace StarLight.Controllers
{
var seasonInfo = project.Seasons[i];
string seasonName = string.IsNullOrEmpty(seasonInfo.Title) ? $"Сезон {i + 1}" : seasonInfo.Title;
string link = $"{host}/starlight?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={i}&href={HttpUtility.UrlEncode(itemUrl)}";
string link = $"{host}/lite/starlight?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={i}&href={HttpUtility.UrlEncode(itemUrl)}";
season_tpl.Append(seasonName, link, i.ToString());
}
@ -90,13 +90,13 @@ namespace StarLight.Controllers
}
if (s < 0 || s >= project.Seasons.Count)
return OnError("starlight", proxyManager);
return OnError("starlight", refresh_proxy: true);
var season = project.Seasons[s];
string seasonSlug = season.Slug;
var episodes = invoke.GetEpisodes(project, seasonSlug);
if (episodes == null || episodes.Count == 0)
return OnError("starlight", proxyManager);
return OnError("starlight", refresh_proxy: true);
var episode_tpl = new EpisodeTpl();
int index = 1;
@ -114,7 +114,7 @@ namespace StarLight.Controllers
continue;
string episodeName = string.IsNullOrEmpty(ep.Title) ? $"Епізод {index}" : ep.Title;
string callUrl = $"{host}/starlight/play?hash={HttpUtility.UrlEncode(ep.Hash)}&title={HttpUtility.UrlEncode(title ?? original_title)}";
string callUrl = $"{host}/lite/starlight/play?hash={HttpUtility.UrlEncode(ep.Hash)}&title={HttpUtility.UrlEncode(title ?? original_title)}";
episode_tpl.Append(episodeName, title ?? original_title, seasonNumber, index.ToString("D2"), accsArgs(callUrl), "call");
index++;
}
@ -128,9 +128,9 @@ namespace StarLight.Controllers
hash = project.Episodes.FirstOrDefault(e => !string.IsNullOrEmpty(e.Hash))?.Hash;
if (string.IsNullOrEmpty(hash))
return OnError("starlight", proxyManager);
return OnError("starlight", refresh_proxy: true);
string callUrl = $"{host}/starlight/play?hash={HttpUtility.UrlEncode(hash)}&title={HttpUtility.UrlEncode(title ?? original_title)}";
string callUrl = $"{host}/lite/starlight/play?hash={HttpUtility.UrlEncode(hash)}&title={HttpUtility.UrlEncode(title ?? original_title)}";
var movie_tpl = new MovieTpl(title, original_title, 1);
movie_tpl.Append(string.IsNullOrEmpty(title) ? "StarLight" : title, accsArgs(callUrl), "call");
@ -139,22 +139,22 @@ namespace StarLight.Controllers
}
[HttpGet]
[Route("starlight/play")]
[Route("lite/starlight/play")]
async public Task<ActionResult> Play(string hash, string title)
{
await UpdateService.ConnectAsync(host);
if (string.IsNullOrEmpty(hash))
return OnError("starlight", proxyManager);
return OnError("starlight", refresh_proxy: true);
var init = await loadKit(ModInit.StarLight);
var init = loadKit(ModInit.StarLight);
if (!init.enable)
return Forbid();
var invoke = new StarLightInvoke(init, hybridCache, OnLog, proxyManager);
var invoke = new StarLightInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
var result = await invoke.ResolveStream(hash);
if (result == null || string.IsNullOrEmpty(result.Stream))
return OnError("starlight", proxyManager);
return OnError("starlight", refresh_proxy: true);
string videoTitle = title ?? result.Name ?? "";
@ -266,5 +266,38 @@ namespace StarLight.Controllers
return DateTime.TryParse(episode.Date, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt) ? dt : null;
}
private static bool IsCheckOnlineSearchEnabled()
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (checkProp?.GetValue(conf) is bool enabled)
return enabled;
}
catch
{
}
return true;
}
private static void OnLog(string message)
{
System.Console.WriteLine(message);
}
}
}

View File

@ -0,0 +1,4 @@
global using Shared.Services;
global using Shared.Services.Hybrid;
global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;

View File

@ -2,6 +2,7 @@ using Newtonsoft.Json.Linq;
using Shared;
using Shared.Engine;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using Shared.Models.Online.Settings;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
@ -22,9 +23,9 @@ using System.Threading.Tasks;
namespace StarLight
{
public class ModInit
public class ModInit : IModuleLoaded
{
public static double Version => 3.3;
public static double Version => 4.0;
public static OnlinesSettings StarLight;
public static bool ApnHostProvided;
@ -38,7 +39,7 @@ namespace StarLight
/// <summary>
/// модуль загружен
/// </summary>
public static void loaded(InitspaceModel initspace)
public void Loaded(InitspaceModel initspace)
{
@ -54,7 +55,7 @@ namespace StarLight
list = new string[] { "socks5://ip:port" }
}
};
var conf = ModuleInvoke.Conf("StarLight", StarLight);
var conf = ModuleInvoke.Init("StarLight", JObject.FromObject(StarLight));
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
conf.Remove("apn");
conf.Remove("apn_host");
@ -73,7 +74,45 @@ namespace StarLight
}
// Виводити "уточнити пошук"
AppInit.conf.online.with_search.Add("starlight");
RegisterWithSearch("starlight");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}

View File

@ -1,29 +1,34 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared.Models;
using Shared.Models.Base;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace StarLight
{
public class OnlineApi
public class OnlineApi : IModuleOnline
{
public static List<(string name, string url, string plugin, int index)> Invoke(
HttpContext httpContext,
IMemoryCache memoryCache,
RequestModel requestInfo,
string host,
OnlineEventsModel args)
public List<ModuleOnlineItem> 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)
public Task<List<ModuleOnlineItem>> InvokeAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineEventsModel args)
=> Task.FromResult(default(List<ModuleOnlineItem>));
public List<ModuleOnlineSpiderItem> Spider(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> null;
public Task<List<ModuleOnlineSpiderItem>> SpiderAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> Task.FromResult(default(List<ModuleOnlineSpiderItem>));
private static List<ModuleOnlineItem> 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 online = new List<ModuleOnlineItem>();
if (!string.Equals(original_language, "uk", StringComparison.OrdinalIgnoreCase))
return online;
@ -33,9 +38,9 @@ namespace StarLight
{
string url = init.overridehost;
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
url = $"{host}/starlight";
url = $"{host}/lite/starlight";
online.Add((init.displayname, url, "starlight", init.displayindex));
online.Add(new ModuleOnlineItem(init.displayname, url, "starlight", init.displayindex));
}
return online;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@ -24,13 +24,15 @@ namespace StarLight
private readonly IHybridCache _hybridCache;
private readonly Action<string> _onLog;
private readonly ProxyManager _proxyManager;
private readonly HttpHydra _httpHydra;
public StarLightInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
public StarLightInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
{
_init = init;
_hybridCache = hybridCache;
_onLog = onLog;
_proxyManager = proxyManager;
_httpHydra = httpHydra;
}
public async Task<List<SearchResult>> Search(string title, string original_title)
@ -54,7 +56,7 @@ namespace StarLight
try
{
_onLog?.Invoke($"StarLight search: {url}");
string payload = await Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
string payload = await HttpGet(url, headers);
if (string.IsNullOrEmpty(payload))
return null;
@ -112,7 +114,7 @@ namespace StarLight
try
{
_onLog?.Invoke($"StarLight project: {href}");
string payload = await Http.Get(_init.cors(href), headers: headers, proxy: _proxyManager.Get());
string payload = await HttpGet(href, headers);
if (string.IsNullOrEmpty(payload))
return null;
@ -193,7 +195,7 @@ namespace StarLight
try
{
_onLog?.Invoke($"StarLight season: {seasonUrl}");
string payload = await Http.Get(_init.cors(seasonUrl), headers: headers, proxy: _proxyManager.Get());
string payload = await HttpGet(seasonUrl, headers);
if (string.IsNullOrEmpty(payload))
continue;
@ -279,7 +281,7 @@ namespace StarLight
try
{
_onLog?.Invoke($"StarLight stream: {url}");
string payload = await Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
string payload = await HttpGet(url, headers);
if (string.IsNullOrEmpty(payload))
return null;
@ -338,12 +340,20 @@ namespace StarLight
return $"{_init.host}{path}";
}
private Task<string> HttpGet(string url, List<HeadersModel> headers)
{
if (_httpHydra != null)
return _httpHydra.Get(url, newheaders: headers);
return Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
}
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;
int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess;
if (ctime > multiaccess)
ctime = multiaccess;

View File

@ -28,28 +28,28 @@ namespace Uaflix.Controllers
}
[HttpGet]
[Route("uaflix")]
[Route("lite/uaflix")]
async public 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 t, int s = -1, int e = -1, bool play = false, bool rjson = false, string href = null, bool checksearch = false)
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.UaFlix);
if (await IsBadInitialization(init))
return Forbid();
if (await IsRequestBlocked(rch: false))
return badInitMsg;
var init = this.init;
OnLog($"=== UAFLIX INDEX START ===");
OnLog($"Uaflix Index: title={title}, serial={serial}, s={s}, play={play}, href={href}, checksearch={checksearch}");
OnLog($"Uaflix Index: kinopoisk_id={kinopoisk_id}, imdb_id={imdb_id}, id={id}");
OnLog($"Uaflix Index: year={year}, source={source}, t={t}, e={e}, rjson={rjson}");
var auth = new UaflixAuth(init, memoryCache, OnLog, proxyManager);
var invoke = new UaflixInvoke(init, hybridCache, OnLog, proxyManager, auth);
var invoke = new UaflixInvoke(init, hybridCache, OnLog, proxyManager, auth, httpHydra);
// Обробка параметра checksearch - повертаємо спеціальну відповідь для валідації
if (checksearch)
{
if (AppInit.conf?.online?.checkOnlineSearch != true)
return OnError("uaflix", proxyManager);
if (!IsCheckOnlineSearchEnabled())
return OnError("uaflix", refresh_proxy: true);
try
{
@ -63,13 +63,13 @@ namespace Uaflix.Controllers
OnLog("checksearch: Контент не знайдено");
OnLog("=== RETURN: checksearch OnError ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
catch (Exception ex)
{
OnLog($"checksearch: помилка - {ex.Message}");
OnLog("=== RETURN: checksearch exception OnError ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
}
@ -80,7 +80,7 @@ namespace Uaflix.Controllers
if (string.IsNullOrWhiteSpace(urlToParse))
{
OnLog("=== RETURN: play missing url OnError ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
var playResult = await invoke.ParseEpisode(urlToParse);
@ -91,7 +91,7 @@ namespace Uaflix.Controllers
}
OnLog("=== RETURN: play no streams ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
// Якщо є episode_url але немає play=true, це виклик для отримання інформації про стрім (для method: 'call')
@ -109,7 +109,7 @@ namespace Uaflix.Controllers
}
OnLog("=== RETURN: call method no streams ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
string filmUrl = href;
@ -121,7 +121,7 @@ namespace Uaflix.Controllers
{
OnLog("No search results found");
OnLog("=== RETURN: no search results OnError ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
var selectedResult = invoke.SelectBestSearchResult(searchResults, title, original_title, year);
@ -142,7 +142,7 @@ namespace Uaflix.Controllers
var similar_tpl = new SimilarTpl(orderedResults.Count);
foreach (var res in orderedResults)
{
string link = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(res.Url)}";
string link = $"{host}/lite/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(res.Url)}";
string y = res.Year > 0 ? res.Year.ToString() : string.Empty;
string details = res.Category switch
{
@ -168,7 +168,7 @@ namespace Uaflix.Controllers
{
OnLog("No voices found in aggregated structure");
OnLog("=== RETURN: no voices OnError ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
OnLog($"Structure aggregated successfully: {structure.Voices.Count} voices, URL: {filmUrl}");
@ -226,13 +226,13 @@ namespace Uaflix.Controllers
{
OnLog("No seasons with valid episodes found in structure");
OnLog("=== RETURN: no valid seasons OnError ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
var season_tpl = new SeasonTpl(seasonsWithValidEpisodes.Count);
foreach (var season in seasonsWithValidEpisodes)
{
string link = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={season}&href={HttpUtility.UrlEncode(filmUrl)}";
string link = $"{host}/lite/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={season}&href={HttpUtility.UrlEncode(filmUrl)}";
if (restrictByVoice)
link += $"&t={HttpUtility.UrlEncode(t)}";
season_tpl.Append($"{season}", link, season.ToString());
@ -260,7 +260,7 @@ namespace Uaflix.Controllers
{
OnLog($"No voices found for season {s}");
OnLog("=== RETURN: no voices for season OnError ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
// Автоматично вибираємо першу озвучку якщо не вказана
@ -288,7 +288,7 @@ namespace Uaflix.Controllers
bool sameSeasonSet = targetSeasonSet.SetEquals(selectedSeasonSet);
bool needSeasonReset = (selectedIsAshdi || targetIsAshdi) && !sameSeasonSet;
string voiceLink = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&href={HttpUtility.UrlEncode(filmUrl)}";
string voiceLink = $"{host}/lite/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&href={HttpUtility.UrlEncode(filmUrl)}";
if (needSeasonReset)
voiceLink += $"&s=-1&t={HttpUtility.UrlEncode(voice.DisplayName)}";
else
@ -304,7 +304,7 @@ namespace Uaflix.Controllers
{
OnLog($"Voice '{t}' not found in structure");
OnLog("=== RETURN: voice not found OnError ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
if (!structure.Voices[t].Seasons.ContainsKey(s))
@ -312,13 +312,13 @@ namespace Uaflix.Controllers
OnLog($"Season {s} not found for voice '{t}'");
if (IsAshdiVoice(structure.Voices[t]))
{
string redirectUrl = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s=-1&t={HttpUtility.UrlEncode(t)}&href={HttpUtility.UrlEncode(filmUrl)}";
string redirectUrl = $"{host}/lite/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s=-1&t={HttpUtility.UrlEncode(t)}&href={HttpUtility.UrlEncode(filmUrl)}";
OnLog($"Ashdi voice missing season, redirect to season selector: {redirectUrl}");
return Redirect(redirectUrl);
}
OnLog("=== RETURN: season not found for voice OnError ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
var episodes = structure.Voices[t].Seasons[s];
@ -334,7 +334,7 @@ namespace Uaflix.Controllers
{
// Для zetvideo-vod та ashdi-vod використовуємо URL епізоду для виклику
// Потрібно передати URL епізоду в інший параметр, щоб не плутати з play=true
string callUrl = $"{host}/uaflix?episode_url={HttpUtility.UrlEncode(ep.File)}&imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&s={s}&e={ep.Number}";
string callUrl = $"{host}/lite/uaflix?episode_url={HttpUtility.UrlEncode(ep.File)}&imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&s={s}&e={ep.Number}";
episode_tpl.Append(
name: ep.Title,
title: title,
@ -378,7 +378,7 @@ namespace Uaflix.Controllers
// Fallback: якщо жоден з умов не виконався
OnLog($"Fallback: s={s}, t={t}");
OnLog("=== RETURN: fallback OnError ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
else // Фільм
{
@ -386,7 +386,7 @@ namespace Uaflix.Controllers
if (playResult?.streams == null || playResult.streams.Count == 0)
{
OnLog("=== RETURN: movie no streams ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
var tpl = new MovieTpl(title, original_title, playResult.streams.Count);
@ -407,7 +407,7 @@ namespace Uaflix.Controllers
if (tpl.data == null || tpl.data.Count == 0)
{
OnLog("=== RETURN: movie template empty ===");
return OnError("uaflix", proxyManager);
return OnError("uaflix", refresh_proxy: true);
}
OnLog("=== RETURN: movie template ===");
@ -469,5 +469,38 @@ namespace Uaflix.Controllers
.Select(kv => kv.Key)
.ToHashSet();
}
private static bool IsCheckOnlineSearchEnabled()
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (checkProp?.GetValue(conf) is bool enabled)
return enabled;
}
catch
{
}
return true;
}
private static void OnLog(string message)
{
System.Console.WriteLine(message);
}
}
}

4
Uaflix/GlobalUsings.cs Normal file
View File

@ -0,0 +1,4 @@
global using Shared.Services;
global using Shared.Services.Hybrid;
global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;

View File

@ -4,6 +4,7 @@ using Newtonsoft.Json.Linq;
using Shared;
using Shared.Engine;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using System;
using System.Net.Http;
using System.Net.Mime;
@ -16,9 +17,9 @@ using Uaflix.Models;
namespace Uaflix
{
public class ModInit
public class ModInit : IModuleLoaded
{
public static double Version => 4.0;
public static double Version => 5.0;
public static UaflixSettings UaFlix;
@ -33,7 +34,7 @@ namespace Uaflix
/// <summary>
/// Модуль завантажено.
/// </summary>
public static void loaded(InitspaceModel initspace)
public void Loaded(InitspaceModel initspace)
{
UaFlix = new UaflixSettings("Uaflix", "https://uafix.net", streamproxy: false, useproxy: false)
{
@ -53,7 +54,7 @@ namespace Uaflix
}
};
var conf = ModuleInvoke.Conf("Uaflix", UaFlix) ?? JObject.FromObject(UaFlix);
var conf = ModuleInvoke.Init("Uaflix", JObject.FromObject(UaFlix)) ?? JObject.FromObject(UaFlix);
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
conf.Remove("apn");
conf.Remove("apn_host");
@ -74,7 +75,45 @@ namespace Uaflix
}
// Показувати «уточнити пошук».
AppInit.conf.online.with_search.Add("uaflix");
RegisterWithSearch("uaflix");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}

View File

@ -1,37 +1,42 @@
using Microsoft.AspNetCore.Http;
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 Uaflix
{
public class OnlineApi
using Shared.Models.Module.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Uaflix
{
public class OnlineApi : IModuleOnline
{
public static List<(string name, string url, string plugin, int index)> Invoke(
HttpContext httpContext,
IMemoryCache memoryCache,
RequestModel requestInfo,
string host,
OnlineEventsModel args)
public List<ModuleOnlineItem> 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)
public Task<List<ModuleOnlineItem>> InvokeAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineEventsModel args)
=> Task.FromResult(default(List<ModuleOnlineItem>));
public List<ModuleOnlineSpiderItem> Spider(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> null;
public Task<List<ModuleOnlineSpiderItem>> SpiderAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> Task.FromResult(default(List<ModuleOnlineSpiderItem>));
private static List<ModuleOnlineItem> 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 online = new List<ModuleOnlineItem>();
var init = ModInit.UaFlix;
if (init.enable && !init.rip)
{
string url = init.overridehost;
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
url = $"{host}/uaflix";
url = $"{host}/lite/uaflix";
online.Add((init.displayname, url, "uaflix", init.displayindex));
online.Add(new ModuleOnlineItem(init.displayname, url, "uaflix", init.displayindex));
}
return online;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@ -28,14 +28,16 @@ namespace Uaflix
private readonly Action<string> _onLog;
private readonly ProxyManager _proxyManager;
private readonly UaflixAuth _auth;
private readonly HttpHydra _httpHydra;
public UaflixInvoke(UaflixSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, UaflixAuth auth)
public UaflixInvoke(UaflixSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, UaflixAuth auth, HttpHydra httpHydra = null)
{
_init = init;
_hybridCache = hybridCache;
_onLog = onLog;
_proxyManager = proxyManager;
_auth = auth;
_httpHydra = httpHydra;
}
string AshdiRequestUrl(string url)
@ -76,7 +78,6 @@ namespace Uaflix
if (string.IsNullOrWhiteSpace(url))
return null;
string requestUrl = _init.cors(url);
bool withAuth = ShouldUseAuth(url);
var requestHeaders = headers != null ? new List<HeadersModel>(headers) : new List<HeadersModel>();
@ -86,6 +87,26 @@ namespace Uaflix
_auth.ApplyCookieHeader(requestHeaders, cookie);
}
if (_httpHydra != null)
{
string content = await _httpHydra.Get(url, newheaders: requestHeaders, statusCodeOK: false);
if (string.IsNullOrWhiteSpace(content)
&& retryOnUnauthorized
&& withAuth
&& _auth != null
&& _auth.CanUseCredentials)
{
_onLog($"UaflixAuth: порожня відповідь для {url}, виконую повторну авторизацію");
string refreshedCookie = await _auth.GetCookieHeaderAsync(forceRefresh: true);
_auth.ApplyCookieHeader(requestHeaders, refreshedCookie);
content = await _httpHydra.Get(url, newheaders: requestHeaders, statusCodeOK: false);
}
return string.IsNullOrWhiteSpace(content) ? null : content;
}
string requestUrl = _init.cors(url);
var response = await Http.BaseGet(requestUrl,
headers: requestHeaders,
timeoutSeconds: timeoutSeconds,
@ -1790,7 +1811,7 @@ namespace Uaflix
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;
int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess;
if (ctime > multiaccess)
ctime = multiaccess;
@ -1805,9 +1826,9 @@ namespace Uaflix
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 (init != null && ctime > init.cache_time && init.cache_time > 0)
ctime = init.cache_time;
int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess;
if (ctime > multiaccess)
ctime = multiaccess;
return TimeSpan.FromMinutes(ctime);
}

View File

@ -1,4 +1,3 @@
using Shared.Engine;
using System;
using System.Threading.Tasks;
using System.Linq;
@ -22,20 +21,20 @@ namespace Unimay.Controllers
}
[HttpGet]
[Route("unimay")]
[Route("lite/unimay")]
async public ValueTask<ActionResult> Index(string title, string original_title, string code, int serial = -1, int s = -1, int e = -1, bool play = false, bool rjson = false, bool checksearch = false)
{
await UpdateService.ConnectAsync(host);
var init = await loadKit(ModInit.Unimay);
if (await IsBadInitialization(init, rch: false))
if (await IsRequestBlocked(rch: false))
return badInitMsg;
var invoke = new UnimayInvoke(init, hybridCache, OnLog, proxyManager);
var init = this.init;
var invoke = new UnimayInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
if (checksearch)
{
if (AppInit.conf?.online?.checkOnlineSearch != true)
if (!IsCheckOnlineSearchEnabled())
return OnError("unimay");
var searchResults = await invoke.Search(title, original_title, serial);
@ -169,5 +168,38 @@ namespace Unimay.Controllers
cleaned = cleaned.Replace("?&", "?").Replace("&&", "&").TrimEnd('?', '&');
return cleaned;
}
private static bool IsCheckOnlineSearchEnabled()
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (checkProp?.GetValue(conf) is bool enabled)
return enabled;
}
catch
{
}
return true;
}
private static void OnLog(string message)
{
System.Console.WriteLine(message);
}
}
}

4
Unimay/GlobalUsings.cs Normal file
View File

@ -0,0 +1,4 @@
global using Shared.Services;
global using Shared.Services.Hybrid;
global using Shared.Models.Base;
global using AppInit = Shared.CoreInit;

View File

@ -1,20 +1,10 @@
using Newtonsoft.Json;
using Shared;
using Shared.Engine;
using Newtonsoft.Json.Linq;
using Shared;
using Shared.Models.Online.Settings;
using Shared.Models.Module;
using Newtonsoft.Json;
using Shared;
using Shared.Engine;
using Newtonsoft.Json.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.Extensions.Caching.Memory;
using Shared.Models;
using Shared.Models.Events;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Shared;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using Shared.Models.Online.Settings;
using System;
using System.Net.Http;
using System.Net.Mime;
@ -28,9 +18,9 @@ using System.Threading.Tasks;
namespace Unimay
{
public class ModInit
public class ModInit : IModuleLoaded
{
public static double Version => 3.4;
public static double Version => 4.0;
public static OnlinesSettings Unimay;
@ -43,7 +33,7 @@ namespace Unimay
/// <summary>
/// модуль загружен
/// </summary>
public static void loaded(InitspaceModel initspace)
public void Loaded(InitspaceModel initspace)
{
@ -59,10 +49,48 @@ namespace Unimay
list = new string[] { "socks5://IP:PORT" }
}
};
Unimay = ModuleInvoke.Conf("Unimay", Unimay).ToObject<OnlinesSettings>();
Unimay = ModuleInvoke.Init("Unimay", JObject.FromObject(Unimay)).ToObject<OnlinesSettings>();
// Виводити "уточнити пошук"
AppInit.conf.online.with_search.Add("unimay");
RegisterWithSearch("unimay");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}
@ -184,4 +212,4 @@ namespace Unimay
}
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
}
}

View File

@ -1,47 +1,46 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared.Models;
using Shared.Models.Base;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Unimay
{
public class OnlineApi
public class OnlineApi : IModuleOnline
{
public static List<(string name, string url, string plugin, int index)> Invoke(
HttpContext httpContext,
IMemoryCache memoryCache,
RequestModel requestInfo,
string host,
OnlineEventsModel args)
public List<ModuleOnlineItem> 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)
public Task<List<ModuleOnlineItem>> InvokeAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineEventsModel args)
=> Task.FromResult(default(List<ModuleOnlineItem>));
public List<ModuleOnlineSpiderItem> Spider(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> null;
public Task<List<ModuleOnlineSpiderItem>> SpiderAsync(HttpContext httpContext, IMemoryCache memoryCache, RequestModel requestInfo, string host, OnlineSpiderModel args)
=> Task.FromResult(default(List<ModuleOnlineSpiderItem>));
private static List<ModuleOnlineItem> 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 online = new List<ModuleOnlineItem>();
var init = ModInit.Unimay;
// Визначення isAnime згідно стандарту Lampac (Deepwiki):
// isanime = true якщо original_language == "ja" або "zh"
bool hasLang = !string.IsNullOrEmpty(original_language);
bool isanime = hasLang && (original_language == "ja" || original_language == "zh");
// Unimay — аніме-провайдер. Додаємо якщо:
// - загальний пошук (serial == -1), або
// - контент є аніме (isanime), або
// - мова невідома (немає original_language)
if (init.enable && !init.rip && (serial == -1 || isanime || !hasLang))
{
string url = init.overridehost;
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
url = $"{host}/unimay";
url = $"{host}/lite/unimay";
online.Add((init.displayname, url, "unimay", init.displayindex));
online.Add(new ModuleOnlineItem(init.displayname, url, "unimay", init.displayindex));
}
return online;

View File

@ -1,11 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<LangVersion>latest</LangVersion>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
@ -14,4 +12,4 @@
</Reference>
</ItemGroup>
</Project>
</Project>

View File

@ -6,7 +6,6 @@ using Shared.Models.Online.Settings;
using Shared.Models;
using System.Linq;
using Unimay.Models;
using Shared.Engine;
using System.Net;
using System.Text;
@ -18,13 +17,15 @@ namespace Unimay
private ProxyManager _proxyManager;
private IHybridCache _hybridCache;
private Action<string> _onLog;
private readonly HttpHydra _httpHydra;
public UnimayInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
public UnimayInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
{
_init = init;
_hybridCache = hybridCache;
_onLog = onLog;
_proxyManager = proxyManager;
_httpHydra = httpHydra;
}
public async Task<SearchResponse> Search(string title, string original_title, int serial)
@ -39,7 +40,7 @@ namespace Unimay
string searchUrl = $"{_init.host}/release/search?page=0&page_size=10&title={searchQuery}";
var headers = httpHeaders(_init);
SearchResponse root = await Http.Get<SearchResponse>(_init.cors(searchUrl), timeoutSeconds: 8, proxy: _proxyManager.Get(), headers: headers);
SearchResponse root = await HttpGet<SearchResponse>(searchUrl, headers, timeoutSeconds: 8);
if (root == null || root.Content == null || root.Content.Count == 0)
{
@ -69,7 +70,7 @@ namespace Unimay
string releaseUrl = $"{_init.host}/release?code={code}";
var headers = httpHeaders(_init);
ReleaseResponse root = await Http.Get<ReleaseResponse>(_init.cors(releaseUrl), timeoutSeconds: 8, proxy: _proxyManager.Get(), headers: headers);
ReleaseResponse root = await HttpGet<ReleaseResponse>(releaseUrl, headers, timeoutSeconds: 8);
if (root == null)
{
@ -103,7 +104,7 @@ namespace Unimay
}
string itemTitle = item.Names?.Ukr ?? item.Names?.Eng ?? item.Title;
string releaseUrl = $"{host}/unimay?code={item.Code}&title={System.Web.HttpUtility.UrlEncode(itemTitle)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial={serial}";
string releaseUrl = $"{host}/lite/unimay?code={item.Code}&title={System.Web.HttpUtility.UrlEncode(itemTitle)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial={serial}";
results.Add((itemTitle, item.Year, item.Type, releaseUrl));
}
@ -116,7 +117,7 @@ namespace Unimay
return (null, null);
var movieEpisode = releaseDetail.Playlist[0];
string movieLink = $"{host}/unimay?code={releaseDetail.Code}&title={System.Web.HttpUtility.UrlEncode(title)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial=0&play=true";
string movieLink = $"{host}/lite/unimay?code={releaseDetail.Code}&title={System.Web.HttpUtility.UrlEncode(title)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial=0&play=true";
string movieTitle = movieEpisode.Title ?? title;
return (movieTitle, movieLink);
@ -124,7 +125,7 @@ namespace Unimay
public (string seasonName, string seasonUrl, string seasonId) GetSeasonInfo(string host, string code, string title, string original_title)
{
string seasonUrl = $"{host}/unimay?code={code}&title={System.Web.HttpUtility.UrlEncode(title)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial=1&s=1";
string seasonUrl = $"{host}/lite/unimay?code={code}&title={System.Web.HttpUtility.UrlEncode(title)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial=1&s=1";
return ("Сезон 1", seasonUrl, "1");
}
@ -138,7 +139,7 @@ namespace Unimay
foreach (var ep in releaseDetail.Playlist.Where(ep => ep.Number >= 1 && ep.Number <= 24).OrderBy(ep => ep.Number))
{
string epTitle = ep.Title ?? $"Епізод {ep.Number}";
string epLink = $"{host}/unimay?code={releaseDetail.Code}&title={System.Web.HttpUtility.UrlEncode(title)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial=1&s=1&e={ep.Number}&play=true";
string epLink = $"{host}/lite/unimay?code={releaseDetail.Code}&title={System.Web.HttpUtility.UrlEncode(title)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial=1&s=1&e={ep.Number}&play=true";
episodes.Add((epTitle, epLink));
}
@ -160,12 +161,20 @@ namespace Unimay
};
}
private Task<T> HttpGet<T>(string url, List<HeadersModel> headers, int timeoutSeconds = 15)
{
if (_httpHydra != null)
return _httpHydra.Get<T>(url, newheaders: headers);
return Http.Get<T>(_init.cors(url), timeoutSeconds: timeoutSeconds, proxy: _proxyManager.Get(), headers: headers);
}
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;
int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess;
if (ctime > multiaccess)
ctime = multiaccess;