mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-06-17 12:08:54 +00:00
Merge pull request #25 from lampame/Shared
## Що зроблено Цей PR рефакторить спільний повторюваний код у `LME.*` модулях і виносить його в окрему спільну теку `LME.Shared`, яка підключається через `syntaxPaths` у `manifest.json`. ### Основні зміни 1. Додано спільні файли: - `LME.Shared/GlobalUsings.cs` - `LME.Shared/Apn/ApnHelper.cs` - `LME.Shared/Online/OnlineRegistry.cs` - `LME.Shared/Update/ModuleUpdateService.cs` 2. Оновлено всі `LME.* /manifest.json`: - додано `syntaxPaths` для підключення спільних `.cs` файлів із `LME.Shared`. 3. Спрощено `ModInit.cs` у всіх `LME.*`: - прибрано дубльований `RegisterWithSearch(...)` і замінено на `OnlineRegistry.RegisterWithSearch(...)`. - прибрано дубльований `UpdateService/ConnectResponse` і замінено на wrapper над `ModuleUpdateService`. 4. Видалено дублікати з модулів: - `LME.*/GlobalUsings.cs` - `LME.*/ApnHelper.cs` (де були) 5. Виправлено namespace-конфлікт: - `LME.Shared.Online` -> `LME.Common.Online` - `LME.Shared.Update` -> `LME.Common.Update` - оновлено `global using` відповідно. 6. Виправлено збірку `LME.JackTor`: - додано `using Microsoft.AspNetCore.Mvc;` у `LME.JackTor/ModInit.cs` (для `ActionResult` у `UpdateService.Validate`). 7. Оновлено `README.md`: - зроблено читабельну структуру. - додано повноцінні UA + EN секції. - додано навігацію на початку. - явно зазначено, що при вибірковому `modules:` у `repository.yaml` потрібно додавати `LME.Shared`. ## Чому це потрібно - Менше дублювання коду між модулями. - Простіше супроводжувати й оновлювати спільну логіку. - Менше ризику розсинхрону поведінки між модулями. ## Важливо для користувачів При вибірковому встановленні через `repository.yaml` треба включати `LME.Shared`: ```yaml modules: - LME.Shared - LME.AnimeON - LME.Unimay ``` Інакше модулі з `syntaxPaths` не знайдуть спільні файли під час компіляції.
This commit is contained in:
commit
77f712feef
@ -1,116 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared.Models.Base;
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Shared.Engine
|
||||
{
|
||||
public static class ApnHelper
|
||||
{
|
||||
public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}";
|
||||
|
||||
public static bool TryGetInitConf(JObject conf, out bool enabled, out string host)
|
||||
{
|
||||
enabled = false;
|
||||
host = null;
|
||||
|
||||
if (conf == null)
|
||||
return false;
|
||||
|
||||
if (!conf.TryGetValue("apn", out var apnToken) || apnToken?.Type != JTokenType.Boolean)
|
||||
return false;
|
||||
|
||||
enabled = apnToken.Value<bool>();
|
||||
host = conf.Value<string>("apn_host");
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string TryGetMagicAshdiHost(JObject conf)
|
||||
{
|
||||
if (conf == null || !conf.TryGetValue("magic_apn", out var magicToken) || magicToken == null)
|
||||
return null;
|
||||
|
||||
if (magicToken.Type == JTokenType.Boolean)
|
||||
return magicToken.Value<bool>() ? DefaultHost : null;
|
||||
|
||||
if (magicToken.Type == JTokenType.String)
|
||||
return NormalizeHost(magicToken.Value<string>());
|
||||
|
||||
if (magicToken.Type != JTokenType.Object)
|
||||
return null;
|
||||
|
||||
return NormalizeHost(((JObject)magicToken).Value<string>("ashdi"));
|
||||
}
|
||||
|
||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init)
|
||||
{
|
||||
if (init == null)
|
||||
return;
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
host = NormalizeHost(host);
|
||||
if (host == null)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (init.apn == null)
|
||||
init.apn = new ApnConf();
|
||||
|
||||
init.apn.host = host;
|
||||
init.apnstream = true;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(BaseSettings init)
|
||||
{
|
||||
return init?.apnstream == true && !string.IsNullOrWhiteSpace(init?.apn?.host);
|
||||
}
|
||||
|
||||
public static bool IsAshdiUrl(string url)
|
||||
{
|
||||
return !string.IsNullOrEmpty(url) &&
|
||||
url.IndexOf("ashdi.vip", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
public static string WrapUrl(BaseSettings init, string url)
|
||||
{
|
||||
if (!IsEnabled(init))
|
||||
return url;
|
||||
|
||||
return BuildUrl(init.apn.host, url);
|
||||
}
|
||||
|
||||
public static string BuildUrl(string host, string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(url))
|
||||
return url;
|
||||
|
||||
if (host.Contains("{encodeurl}"))
|
||||
return host.Replace("{encodeurl}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{encode_uri}"))
|
||||
return host.Replace("{encode_uri}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{uri}"))
|
||||
return host.Replace("{uri}", url);
|
||||
|
||||
return $"{host.TrimEnd('/')}/{url}";
|
||||
}
|
||||
|
||||
private static string NormalizeHost(string host)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
return null;
|
||||
|
||||
return host.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
@ -85,27 +85,7 @@ namespace LME.AnimeON
|
||||
}
|
||||
|
||||
// Виводити "уточнити пошук"
|
||||
RegisterWithSearch("lme_animeon");
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
OnlineRegistry.RegisterWithSearch("lme_animeon");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -115,120 +95,17 @@ namespace LME.AnimeON
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse? Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer? _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
|
||||
@ -2,5 +2,11 @@
|
||||
"enable": true,
|
||||
"version": 3,
|
||||
"initspace": "LME.AnimeON.ModInit",
|
||||
"online": "LME.AnimeON.OnlineApi"
|
||||
"online": "LME.AnimeON.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared.Models.Base;
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Shared.Engine
|
||||
{
|
||||
public static class ApnHelper
|
||||
{
|
||||
public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}";
|
||||
|
||||
public static bool TryGetInitConf(JObject conf, out bool enabled, out string host)
|
||||
{
|
||||
enabled = false;
|
||||
host = null;
|
||||
|
||||
if (conf == null)
|
||||
return false;
|
||||
|
||||
if (!conf.TryGetValue("apn", out var apnToken) || apnToken?.Type != JTokenType.Boolean)
|
||||
return false;
|
||||
|
||||
enabled = apnToken.Value<bool>();
|
||||
host = conf.Value<string>("apn_host");
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init)
|
||||
{
|
||||
if (init == null)
|
||||
return;
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
host = DefaultHost;
|
||||
|
||||
if (init.apn == null)
|
||||
init.apn = new ApnConf();
|
||||
|
||||
init.apn.host = host;
|
||||
init.apnstream = true;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(BaseSettings init)
|
||||
{
|
||||
return init?.apnstream == true && !string.IsNullOrWhiteSpace(init?.apn?.host);
|
||||
}
|
||||
|
||||
public static bool IsAshdiUrl(string url)
|
||||
{
|
||||
return !string.IsNullOrEmpty(url) &&
|
||||
url.IndexOf("ashdi.vip", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
public static string WrapUrl(BaseSettings init, string url)
|
||||
{
|
||||
if (!IsEnabled(init))
|
||||
return url;
|
||||
|
||||
return BuildUrl(init.apn.host, url);
|
||||
}
|
||||
|
||||
public static string BuildUrl(string host, string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(url))
|
||||
return url;
|
||||
|
||||
if (host.Contains("{encodeurl}"))
|
||||
return host.Replace("{encodeurl}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{encode_uri}"))
|
||||
return host.Replace("{encode_uri}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{uri}"))
|
||||
return host.Replace("{uri}", url);
|
||||
|
||||
return $"{host.TrimEnd('/')}/{url}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
@ -63,7 +63,7 @@ namespace LME.Bamboo
|
||||
conf.Remove("apn_host");
|
||||
Bamboo = conf.ToObject<OnlinesSettings>();
|
||||
if (hasApn)
|
||||
ApnHelper.ApplyInitConf(apnEnabled, apnHost, Bamboo);
|
||||
ApnHelper.ApplyInitConf(apnEnabled, apnHost, Bamboo, useDefaultHostWhenEmpty: true);
|
||||
ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost);
|
||||
if (hasApn && apnEnabled)
|
||||
{
|
||||
@ -76,27 +76,7 @@ namespace LME.Bamboo
|
||||
}
|
||||
|
||||
// Виводити "уточнити пошук"
|
||||
RegisterWithSearch("lme_bamboo");
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
OnlineRegistry.RegisterWithSearch("lme_bamboo");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -106,120 +86,17 @@ namespace LME.Bamboo
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse? Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer? _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
@ -2,5 +2,11 @@
|
||||
"enable": true,
|
||||
"version": 3,
|
||||
"initspace": "LME.Bamboo.ModInit",
|
||||
"online": "LME.Bamboo.OnlineApi"
|
||||
"online": "LME.Bamboo.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using LME.JackTor.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared;
|
||||
@ -78,27 +79,7 @@ namespace LME.JackTor
|
||||
JackTor.host = JackTor.jackett;
|
||||
|
||||
// Показувати «уточнити пошук».
|
||||
RegisterWithSearch("lme_jacktor");
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
OnlineRegistry.RegisterWithSearch("lme_jacktor");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -108,110 +89,17 @@ namespace LME.JackTor
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
return;
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,5 +2,11 @@
|
||||
"enable": true,
|
||||
"version": 3,
|
||||
"initspace": "LME.JackTor.ModInit",
|
||||
"online": "LME.JackTor.OnlineApi"
|
||||
"online": "LME.JackTor.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared.Models.Base;
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Shared.Engine
|
||||
{
|
||||
public static class ApnHelper
|
||||
{
|
||||
public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}";
|
||||
|
||||
public static bool TryGetInitConf(JObject conf, out bool enabled, out string host)
|
||||
{
|
||||
enabled = false;
|
||||
host = null;
|
||||
|
||||
if (conf == null)
|
||||
return false;
|
||||
|
||||
if (!conf.TryGetValue("apn", out var apnToken) || apnToken?.Type != JTokenType.Boolean)
|
||||
return false;
|
||||
|
||||
enabled = apnToken.Value<bool>();
|
||||
host = conf.Value<string>("apn_host");
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string TryGetMagicAshdiHost(JObject conf)
|
||||
{
|
||||
if (conf == null || !conf.TryGetValue("magic_apn", out var magicToken) || magicToken == null)
|
||||
return null;
|
||||
|
||||
if (magicToken.Type == JTokenType.Boolean)
|
||||
return magicToken.Value<bool>() ? DefaultHost : null;
|
||||
|
||||
if (magicToken.Type == JTokenType.String)
|
||||
return NormalizeHost(magicToken.Value<string>());
|
||||
|
||||
if (magicToken.Type != JTokenType.Object)
|
||||
return null;
|
||||
|
||||
return NormalizeHost(((JObject)magicToken).Value<string>("ashdi"));
|
||||
}
|
||||
|
||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init)
|
||||
{
|
||||
if (init == null)
|
||||
return;
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
host = NormalizeHost(host);
|
||||
if (host == null)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (init.apn == null)
|
||||
init.apn = new ApnConf();
|
||||
|
||||
init.apn.host = host;
|
||||
init.apnstream = true;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(BaseSettings init)
|
||||
{
|
||||
return init?.apnstream == true && !string.IsNullOrWhiteSpace(init?.apn?.host);
|
||||
}
|
||||
|
||||
public static bool IsAshdiUrl(string url)
|
||||
{
|
||||
return !string.IsNullOrEmpty(url)
|
||||
&& url.IndexOf("ashdi.vip", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
public static string WrapUrl(BaseSettings init, string url)
|
||||
{
|
||||
if (!IsEnabled(init))
|
||||
return url;
|
||||
|
||||
return BuildUrl(init.apn.host, url);
|
||||
}
|
||||
|
||||
public static string BuildUrl(string host, string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(url))
|
||||
return url;
|
||||
|
||||
if (host.Contains("{encodeurl}"))
|
||||
return host.Replace("{encodeurl}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{encode_uri}"))
|
||||
return host.Replace("{encode_uri}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{uri}"))
|
||||
return host.Replace("{uri}", url);
|
||||
|
||||
return $"{host.TrimEnd('/')}/{url}";
|
||||
}
|
||||
|
||||
private static string NormalizeHost(string host)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
return null;
|
||||
|
||||
return host.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
@ -41,7 +41,7 @@ namespace LME.KlonFUN
|
||||
EventListener.UpdateInitFile += UpdateConfig;
|
||||
|
||||
// Додаємо підтримку "уточнити пошук".
|
||||
RegisterWithSearch("lme_klonfun");
|
||||
OnlineRegistry.RegisterWithSearch("lme_klonfun");
|
||||
}
|
||||
|
||||
private void UpdateConfig()
|
||||
@ -88,26 +88,6 @@ namespace LME.KlonFUN
|
||||
}
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EventListener.UpdateInitFile -= UpdateConfig;
|
||||
@ -116,121 +96,17 @@ namespace LME.KlonFUN
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse? Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer? _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
{
|
||||
"enable": true,
|
||||
"initspace": "LME.KlonFUN.ModInit",
|
||||
"online": "LME.KlonFUN.OnlineApi"
|
||||
"online": "LME.KlonFUN.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
@ -84,27 +84,7 @@ namespace LME.Makhno
|
||||
}
|
||||
|
||||
// Виводити "уточнити пошук"
|
||||
RegisterWithSearch("lme_makhno");
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
OnlineRegistry.RegisterWithSearch("lme_makhno");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -114,120 +94,17 @@ namespace LME.Makhno
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse? Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer? _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
{
|
||||
"enable": true,
|
||||
"initspace": "LME.Makhno.ModInit",
|
||||
"online": "LME.Makhno.OnlineApi"
|
||||
"online": "LME.Makhno.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared.Models.Base;
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Shared.Engine
|
||||
{
|
||||
public static class ApnHelper
|
||||
{
|
||||
public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}";
|
||||
|
||||
public static bool TryGetInitConf(JObject conf, out bool enabled, out string host)
|
||||
{
|
||||
enabled = false;
|
||||
host = null;
|
||||
|
||||
if (conf == null)
|
||||
return false;
|
||||
|
||||
if (!conf.TryGetValue("apn", out var apnToken) || apnToken?.Type != JTokenType.Boolean)
|
||||
return false;
|
||||
|
||||
enabled = apnToken.Value<bool>();
|
||||
host = conf.Value<string>("apn_host");
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string TryGetMagicAshdiHost(JObject conf)
|
||||
{
|
||||
if (conf == null || !conf.TryGetValue("magic_apn", out var magicToken) || magicToken == null)
|
||||
return null;
|
||||
|
||||
if (magicToken.Type == JTokenType.Boolean)
|
||||
return magicToken.Value<bool>() ? DefaultHost : null;
|
||||
|
||||
if (magicToken.Type == JTokenType.String)
|
||||
return NormalizeHost(magicToken.Value<string>());
|
||||
|
||||
if (magicToken.Type != JTokenType.Object)
|
||||
return null;
|
||||
|
||||
return NormalizeHost(((JObject)magicToken).Value<string>("ashdi"));
|
||||
}
|
||||
|
||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init)
|
||||
{
|
||||
if (init == null)
|
||||
return;
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
host = NormalizeHost(host);
|
||||
if (host == null)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (init.apn == null)
|
||||
init.apn = new ApnConf();
|
||||
|
||||
init.apn.host = host;
|
||||
init.apnstream = true;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(BaseSettings init)
|
||||
{
|
||||
return init?.apnstream == true && !string.IsNullOrWhiteSpace(init?.apn?.host);
|
||||
}
|
||||
|
||||
public static bool IsAshdiUrl(string url)
|
||||
{
|
||||
return !string.IsNullOrEmpty(url) &&
|
||||
url.IndexOf("ashdi.vip", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
public static string WrapUrl(BaseSettings init, string url)
|
||||
{
|
||||
if (!IsEnabled(init))
|
||||
return url;
|
||||
|
||||
return BuildUrl(init.apn.host, url);
|
||||
}
|
||||
|
||||
public static string BuildUrl(string host, string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(url))
|
||||
return url;
|
||||
|
||||
if (host.Contains("{encodeurl}"))
|
||||
return host.Replace("{encodeurl}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{encode_uri}"))
|
||||
return host.Replace("{encode_uri}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{uri}"))
|
||||
return host.Replace("{uri}", url);
|
||||
|
||||
return $"{host.TrimEnd('/')}/{url}";
|
||||
}
|
||||
|
||||
private static string NormalizeHost(string host)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
return null;
|
||||
|
||||
return host.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
@ -86,27 +86,7 @@ namespace LME.Mikai
|
||||
}
|
||||
|
||||
// Виводити "уточнити пошук"
|
||||
RegisterWithSearch("lme_mikai");
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
OnlineRegistry.RegisterWithSearch("lme_mikai");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -116,120 +96,17 @@ namespace LME.Mikai
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse? Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer? _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
|
||||
@ -2,5 +2,11 @@
|
||||
"enable": true,
|
||||
"version": 3,
|
||||
"initspace": "LME.Mikai.ModInit",
|
||||
"online": "LME.Mikai.OnlineApi"
|
||||
"online": "LME.Mikai.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared.Models.Base;
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Shared.Engine
|
||||
{
|
||||
public static class ApnHelper
|
||||
{
|
||||
public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}";
|
||||
|
||||
public static bool TryGetInitConf(JObject conf, out bool enabled, out string host)
|
||||
{
|
||||
enabled = false;
|
||||
host = null;
|
||||
|
||||
if (conf == null)
|
||||
return false;
|
||||
|
||||
if (!conf.TryGetValue("apn", out var apnToken) || apnToken?.Type != JTokenType.Boolean)
|
||||
return false;
|
||||
|
||||
enabled = apnToken.Value<bool>();
|
||||
host = conf.Value<string>("apn_host");
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init)
|
||||
{
|
||||
if (init == null)
|
||||
return;
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
host = DefaultHost;
|
||||
|
||||
if (init.apn == null)
|
||||
init.apn = new ApnConf();
|
||||
|
||||
init.apn.host = host;
|
||||
init.apnstream = true;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(BaseSettings init)
|
||||
{
|
||||
return init?.apnstream == true && !string.IsNullOrWhiteSpace(init?.apn?.host);
|
||||
}
|
||||
|
||||
public static bool IsAshdiUrl(string url)
|
||||
{
|
||||
return !string.IsNullOrEmpty(url) &&
|
||||
url.IndexOf("ashdi.vip", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
public static string WrapUrl(BaseSettings init, string url)
|
||||
{
|
||||
if (!IsEnabled(init))
|
||||
return url;
|
||||
|
||||
return BuildUrl(init.apn.host, url);
|
||||
}
|
||||
|
||||
public static string BuildUrl(string host, string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(url))
|
||||
return url;
|
||||
|
||||
if (host.Contains("{encodeurl}"))
|
||||
return host.Replace("{encodeurl}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{encode_uri}"))
|
||||
return host.Replace("{encode_uri}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{uri}"))
|
||||
return host.Replace("{uri}", url);
|
||||
|
||||
return $"{host.TrimEnd('/')}/{url}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
@ -58,7 +58,7 @@ namespace LME.NMoonAnime
|
||||
NMoonAnime = conf.ToObject<OnlinesSettings>();
|
||||
|
||||
if (hasApn)
|
||||
ApnHelper.ApplyInitConf(apnEnabled, apnHost, NMoonAnime);
|
||||
ApnHelper.ApplyInitConf(apnEnabled, apnHost, NMoonAnime, useDefaultHostWhenEmpty: true);
|
||||
|
||||
ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost);
|
||||
if (hasApn && apnEnabled)
|
||||
@ -71,27 +71,7 @@ namespace LME.NMoonAnime
|
||||
NMoonAnime.apn = null;
|
||||
}
|
||||
|
||||
RegisterWithSearch("lme_nmoonanime");
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
OnlineRegistry.RegisterWithSearch("lme_nmoonanime");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -101,121 +81,17 @@ namespace LME.NMoonAnime
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse? Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer? _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
|
||||
@ -2,5 +2,11 @@
|
||||
"enable": true,
|
||||
"version": 3,
|
||||
"initspace": "LME.NMoonAnime.ModInit",
|
||||
"online": "LME.NMoonAnime.OnlineApi"
|
||||
"online": "LME.NMoonAnime.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
@ -54,7 +54,7 @@ namespace Shared.Engine
|
||||
return NormalizeHost(((JObject)magicToken).Value<string>("ashdi"));
|
||||
}
|
||||
|
||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init)
|
||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init, bool useDefaultHostWhenEmpty = false)
|
||||
{
|
||||
if (init == null)
|
||||
return;
|
||||
@ -67,6 +67,9 @@ namespace Shared.Engine
|
||||
}
|
||||
|
||||
host = NormalizeHost(host);
|
||||
if (host == null && useDefaultHostWhenEmpty)
|
||||
host = DefaultHost;
|
||||
|
||||
if (host == null)
|
||||
{
|
||||
init.apnstream = false;
|
||||
@ -2,3 +2,5 @@ global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
global using LME.Common.Online;
|
||||
global using LME.Common.Update;
|
||||
28
LME.Shared/Online/OnlineRegistry.cs
Normal file
28
LME.Shared/Online/OnlineRegistry.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Shared;
|
||||
using System;
|
||||
|
||||
namespace LME.Common.Online
|
||||
{
|
||||
public static class OnlineRegistry
|
||||
{
|
||||
public static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
LME.Shared/Update/ModuleUpdateService.cs
Normal file
139
LME.Shared/Update/ModuleUpdateService.cs
Normal file
@ -0,0 +1,139 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LME.Common.Update
|
||||
{
|
||||
public sealed class ModuleUpdateService
|
||||
{
|
||||
private const string ConnectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
|
||||
private readonly Func<string> _pluginResolver;
|
||||
private readonly Func<double> _versionResolver;
|
||||
|
||||
private ConnectResponse? _connect;
|
||||
private DateTime? _connectTime;
|
||||
private DateTime? _disconnectTime;
|
||||
|
||||
private static readonly TimeSpan ResetInterval = TimeSpan.FromHours(4);
|
||||
private Timer? _resetTimer;
|
||||
|
||||
private readonly object _lock = new();
|
||||
|
||||
public ModuleUpdateService(Func<string> pluginResolver, Func<double> versionResolver)
|
||||
{
|
||||
_pluginResolver = pluginResolver;
|
||||
_versionResolver = versionResolver;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || _connect?.IsUpdateUnavailable == true)
|
||||
return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || _connect?.IsUpdateUnavailable == true)
|
||||
return;
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = _pluginResolver(),
|
||||
Version = _versionResolver(),
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(ConnectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
_connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (_connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, ResetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = _connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
|
||||
public ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
}
|
||||
|
||||
private void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
_connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared.Models.Base;
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Shared.Engine
|
||||
{
|
||||
public static class ApnHelper
|
||||
{
|
||||
public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}";
|
||||
|
||||
public static bool TryGetInitConf(JObject conf, out bool enabled, out string host)
|
||||
{
|
||||
enabled = false;
|
||||
host = null;
|
||||
|
||||
if (conf == null)
|
||||
return false;
|
||||
|
||||
if (!conf.TryGetValue("apn", out var apnToken) || apnToken?.Type != JTokenType.Boolean)
|
||||
return false;
|
||||
|
||||
enabled = apnToken.Value<bool>();
|
||||
host = conf.Value<string>("apn_host");
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init)
|
||||
{
|
||||
if (init == null)
|
||||
return;
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
host = DefaultHost;
|
||||
|
||||
if (init.apn == null)
|
||||
init.apn = new ApnConf();
|
||||
|
||||
init.apn.host = host;
|
||||
init.apnstream = true;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(BaseSettings init)
|
||||
{
|
||||
return init?.apnstream == true && !string.IsNullOrWhiteSpace(init?.apn?.host);
|
||||
}
|
||||
|
||||
public static bool IsAshdiUrl(string url)
|
||||
{
|
||||
return !string.IsNullOrEmpty(url) &&
|
||||
url.IndexOf("ashdi.vip", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
public static string WrapUrl(BaseSettings init, string url)
|
||||
{
|
||||
if (!IsEnabled(init))
|
||||
return url;
|
||||
|
||||
return BuildUrl(init.apn.host, url);
|
||||
}
|
||||
|
||||
public static string BuildUrl(string host, string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(url))
|
||||
return url;
|
||||
|
||||
if (host.Contains("{encodeurl}"))
|
||||
return host.Replace("{encodeurl}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{encode_uri}"))
|
||||
return host.Replace("{encode_uri}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{uri}"))
|
||||
return host.Replace("{uri}", url);
|
||||
|
||||
return $"{host.TrimEnd('/')}/{url}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
@ -63,7 +63,7 @@ namespace LME.StarLight
|
||||
conf.Remove("apn_host");
|
||||
StarLight = conf.ToObject<OnlinesSettings>();
|
||||
if (hasApn)
|
||||
ApnHelper.ApplyInitConf(apnEnabled, apnHost, StarLight);
|
||||
ApnHelper.ApplyInitConf(apnEnabled, apnHost, StarLight, useDefaultHostWhenEmpty: true);
|
||||
ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost);
|
||||
if (hasApn && apnEnabled)
|
||||
{
|
||||
@ -76,27 +76,7 @@ namespace LME.StarLight
|
||||
}
|
||||
|
||||
// Виводити "уточнити пошук"
|
||||
RegisterWithSearch("lme_starlight");
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
OnlineRegistry.RegisterWithSearch("lme_starlight");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -106,120 +86,17 @@ namespace LME.StarLight
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse? Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer? _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
@ -2,5 +2,11 @@
|
||||
"enable": true,
|
||||
"version": 3,
|
||||
"initspace": "LME.StarLight.ModInit",
|
||||
"online": "LME.StarLight.OnlineApi"
|
||||
"online": "LME.StarLight.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared.Models.Base;
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Shared.Engine
|
||||
{
|
||||
public static class ApnHelper
|
||||
{
|
||||
public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}";
|
||||
|
||||
public static bool TryGetInitConf(JObject conf, out bool enabled, out string host)
|
||||
{
|
||||
enabled = false;
|
||||
host = null;
|
||||
|
||||
if (conf == null)
|
||||
return false;
|
||||
|
||||
if (!conf.TryGetValue("apn", out var apnToken) || apnToken?.Type != JTokenType.Boolean)
|
||||
return false;
|
||||
|
||||
enabled = apnToken.Value<bool>();
|
||||
host = conf.Value<string>("apn_host");
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init)
|
||||
{
|
||||
if (init == null)
|
||||
return;
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
host = NormalizeHost(host);
|
||||
if (host == null)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (init.apn == null)
|
||||
init.apn = new ApnConf();
|
||||
|
||||
init.apn.host = host;
|
||||
init.apnstream = true;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(BaseSettings init)
|
||||
{
|
||||
return init?.apnstream == true && !string.IsNullOrWhiteSpace(init?.apn?.host);
|
||||
}
|
||||
|
||||
public static bool IsAshdiUrl(string url)
|
||||
{
|
||||
return !string.IsNullOrEmpty(url) &&
|
||||
url.IndexOf("ashdi.vip", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
public static string WrapUrl(BaseSettings init, string url)
|
||||
{
|
||||
if (!IsEnabled(init))
|
||||
return url;
|
||||
|
||||
return BuildUrl(init.apn.host, url);
|
||||
}
|
||||
|
||||
public static string BuildUrl(string host, string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(url))
|
||||
return url;
|
||||
|
||||
if (host.Contains("{encodeurl}"))
|
||||
return host.Replace("{encodeurl}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{encode_uri}"))
|
||||
return host.Replace("{encode_uri}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{uri}"))
|
||||
return host.Replace("{uri}", url);
|
||||
|
||||
return $"{host.TrimEnd('/')}/{url}";
|
||||
}
|
||||
|
||||
private static string NormalizeHost(string host)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
return null;
|
||||
|
||||
return host.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
@ -69,27 +69,7 @@ namespace LME.UafilmME
|
||||
UafilmME.apn = null;
|
||||
}
|
||||
|
||||
RegisterWithSearch("lme_uafilmme");
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
OnlineRegistry.RegisterWithSearch("lme_uafilmme");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -99,117 +79,17 @@ namespace LME.UafilmME
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse? Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer? _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
return;
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
|
||||
@ -2,5 +2,11 @@
|
||||
"enable": true,
|
||||
"version": 3,
|
||||
"initspace": "LME.UafilmME.ModInit",
|
||||
"online": "LME.UafilmME.OnlineApi"
|
||||
"online": "LME.UafilmME.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared.Models.Base;
|
||||
using System;
|
||||
using System.Web;
|
||||
|
||||
namespace Shared.Engine
|
||||
{
|
||||
public static class ApnHelper
|
||||
{
|
||||
public const string DefaultHost = "https://tut.im/proxy.php?url={encodeurl}";
|
||||
|
||||
public static bool TryGetInitConf(JObject conf, out bool enabled, out string host)
|
||||
{
|
||||
enabled = false;
|
||||
host = null;
|
||||
|
||||
if (conf == null)
|
||||
return false;
|
||||
|
||||
if (!conf.TryGetValue("apn", out var apnToken) || apnToken?.Type != JTokenType.Boolean)
|
||||
return false;
|
||||
|
||||
enabled = apnToken.Value<bool>();
|
||||
host = conf.Value<string>("apn_host");
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string TryGetMagicAshdiHost(JObject conf)
|
||||
{
|
||||
if (conf == null || !conf.TryGetValue("magic_apn", out var magicToken) || magicToken == null)
|
||||
return null;
|
||||
|
||||
if (magicToken.Type == JTokenType.Boolean)
|
||||
return magicToken.Value<bool>() ? DefaultHost : null;
|
||||
|
||||
if (magicToken.Type == JTokenType.String)
|
||||
return NormalizeHost(magicToken.Value<string>());
|
||||
|
||||
if (magicToken.Type != JTokenType.Object)
|
||||
return null;
|
||||
|
||||
return NormalizeHost(((JObject)magicToken).Value<string>("ashdi"));
|
||||
}
|
||||
|
||||
public static void ApplyInitConf(bool enabled, string host, BaseSettings init)
|
||||
{
|
||||
if (init == null)
|
||||
return;
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
host = NormalizeHost(host);
|
||||
if (host == null)
|
||||
{
|
||||
init.apnstream = false;
|
||||
init.apn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (init.apn == null)
|
||||
init.apn = new ApnConf();
|
||||
|
||||
init.apn.host = host;
|
||||
init.apnstream = true;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(BaseSettings init)
|
||||
{
|
||||
return init?.apnstream == true && !string.IsNullOrWhiteSpace(init?.apn?.host);
|
||||
}
|
||||
|
||||
public static bool IsAshdiUrl(string url)
|
||||
{
|
||||
return !string.IsNullOrEmpty(url) &&
|
||||
url.IndexOf("ashdi.vip", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
public static string WrapUrl(BaseSettings init, string url)
|
||||
{
|
||||
if (!IsEnabled(init))
|
||||
return url;
|
||||
|
||||
return BuildUrl(init.apn.host, url);
|
||||
}
|
||||
|
||||
public static string BuildUrl(string host, string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(url))
|
||||
return url;
|
||||
|
||||
if (host.Contains("{encodeurl}"))
|
||||
return host.Replace("{encodeurl}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{encode_uri}"))
|
||||
return host.Replace("{encode_uri}", HttpUtility.UrlEncode(url));
|
||||
|
||||
if (host.Contains("{uri}"))
|
||||
return host.Replace("{uri}", url);
|
||||
|
||||
return $"{host.TrimEnd('/')}/{url}";
|
||||
}
|
||||
|
||||
private static string NormalizeHost(string host)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
return null;
|
||||
|
||||
return host.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
@ -85,27 +85,7 @@ namespace LME.Uaflix
|
||||
}
|
||||
|
||||
// Показувати «уточнити пошук».
|
||||
RegisterWithSearch("lme_uaflix");
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
OnlineRegistry.RegisterWithSearch("lme_uaflix");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -115,121 +95,17 @@ namespace LME.Uaflix
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse? Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer? _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
|
||||
@ -2,5 +2,11 @@
|
||||
"enable": true,
|
||||
"version": 3,
|
||||
"initspace": "LME.Uaflix.ModInit",
|
||||
"online": "LME.Uaflix.OnlineApi"
|
||||
"online": "LME.Uaflix.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
global using Shared.Services;
|
||||
global using Shared.Services.Hybrid;
|
||||
global using Shared.Models.Base;
|
||||
global using AppInit = Shared.CoreInit;
|
||||
@ -54,27 +54,7 @@ namespace LME.Unimay
|
||||
Unimay = ModuleInvoke.Init("LME.Unimay", defaults).ToObject<OnlinesSettings>();
|
||||
|
||||
// Виводити "уточнити пошук"
|
||||
RegisterWithSearch("lme_unimay");
|
||||
}
|
||||
|
||||
private static void RegisterWithSearch(string plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CoreInit.conf.online.with_search == null)
|
||||
return;
|
||||
|
||||
foreach (var item in CoreInit.conf.online.with_search)
|
||||
{
|
||||
if (string.Equals(item, plugin, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
CoreInit.conf.online.with_search.Add(plugin);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
OnlineRegistry.RegisterWithSearch("lme_unimay");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -84,120 +64,17 @@ namespace LME.Unimay
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
|
||||
private static readonly ModuleUpdateService _service = new(
|
||||
() => ModInit.Settings?.plugin,
|
||||
() => ModInit.Version);
|
||||
|
||||
private static ConnectResponse? Connect = null;
|
||||
private static DateTime? _connectTime = null;
|
||||
private static DateTime? _disconnectTime = null;
|
||||
public static Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
=> _service.ConnectAsync(host, cancellationToken);
|
||||
|
||||
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
|
||||
private static Timer? _resetTimer = null;
|
||||
|
||||
private static readonly object _lock = new();
|
||||
|
||||
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connectTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var handler = new SocketsHttpHandler
|
||||
{
|
||||
SslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
RemoteCertificateValidationCallback = (_, _, _, _) => true,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
}
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
var request = new
|
||||
{
|
||||
Host = host,
|
||||
Module = ModInit.Settings.plugin,
|
||||
Version = ModInit.Version,
|
||||
};
|
||||
|
||||
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
|
||||
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
var response = await client
|
||||
.PostAsync(_connectUrl, requestContent, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength > 0)
|
||||
{
|
||||
var responseText = await response.Content
|
||||
.ReadAsStringAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
|
||||
if (Connect?.IsUpdateUnavailable != true)
|
||||
{
|
||||
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disconnectTime = Connect?.IsNoiseEnabled == true
|
||||
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ResetConnectTime(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetConnectTime(object? state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_connectTime = null;
|
||||
Connect = null;
|
||||
|
||||
_resetTimer?.Dispose();
|
||||
_resetTimer = null;
|
||||
}
|
||||
}
|
||||
public static bool IsDisconnected()
|
||||
{
|
||||
return _disconnectTime is not null
|
||||
&& DateTime.UtcNow >= _disconnectTime;
|
||||
}
|
||||
=> _service.IsDisconnected();
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
=> _service.Validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
|
||||
@ -2,5 +2,11 @@
|
||||
"enable": true,
|
||||
"version": 3,
|
||||
"initspace": "LME.Unimay.ModInit",
|
||||
"online": "LME.Unimay.OnlineApi"
|
||||
"online": "LME.Unimay.OnlineApi",
|
||||
"syntaxPaths": [
|
||||
"../LME.Shared/GlobalUsings.cs",
|
||||
"../LME.Shared/Online/OnlineRegistry.cs",
|
||||
"../LME.Shared/Update/ModuleUpdateService.cs",
|
||||
"../LME.Shared/Apn/ApnHelper.cs"
|
||||
]
|
||||
}
|
||||
192
README.md
192
README.md
@ -1,48 +1,57 @@
|
||||
# Ukraine online source for Lampac NextGen
|
||||
# Lampac Ukraine Modules (`LME.*`)
|
||||
|
||||
> **Important:** All modules use the prefix `LME.` (Lampac Modules Extended) to avoid conflicts with Lampac's built-in modules.
|
||||
> Text names, namespaces, keys in `init.conf`, and routes all use the prefix `LME.`.
|
||||
Набір українських онлайн-модулів для Lampac NextGen.
|
||||
Усі модулі використовують префікс `LME.` (Lampac Modules Extended), щоб уникати конфліктів із вбудованими модулями Lampac.
|
||||
|
||||
## Sources
|
||||
### TVShows and Movies
|
||||
## Навігація
|
||||
|
||||
- [x] LME.Uaflix
|
||||
- [x] LME.Makhno
|
||||
- [x] LME.StarLight
|
||||
- [x] LME.KlonFUN
|
||||
- [x] LME.UafilmME
|
||||
- [Українська](#ua)
|
||||
- [English](#en)
|
||||
|
||||
### Anime and Dorama
|
||||
- [x] LME.AnimeON
|
||||
- [x] LME.Bamboo
|
||||
- [x] LME.Unimay
|
||||
- [x] LME.Mikai
|
||||
- [x] LME.NMoonAnime
|
||||
## <a id="ua"></a>Українська
|
||||
|
||||
## Installation
|
||||
### Доступні модулі
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/lampame/lampac-ukraine.git .
|
||||
```
|
||||
**Фільми та серіали**
|
||||
- `LME.Uaflix`
|
||||
- `LME.Makhno`
|
||||
- `LME.StarLight`
|
||||
- `LME.KlonFUN`
|
||||
- `LME.UafilmME`
|
||||
- `LME.JackTor`
|
||||
|
||||
2. Move the modules to the correct directory:
|
||||
- If Lampac is installed system-wide, move the modules to the `module` directory.
|
||||
- If Lampac is running in Docker, mount the volume:
|
||||
```bash
|
||||
-v /path/to/your/cloned/repo/LME.Uaflix:/lampac/module/LME.Uaflix
|
||||
```
|
||||
**Аніме та дорами**
|
||||
- `LME.AnimeON`
|
||||
- `LME.Bamboo`
|
||||
- `LME.Unimay`
|
||||
- `LME.Mikai`
|
||||
- `LME.NMoonAnime`
|
||||
|
||||
## Auto installation
|
||||
### Ручне встановлення
|
||||
|
||||
If Lampac version 148.1 and newer
|
||||
1. Клонуйте репозиторій:
|
||||
```bash
|
||||
git clone https://github.com/lampame/lampac-ukraine.git .
|
||||
```
|
||||
|
||||
Create or update the module/repository.yaml file
|
||||
2. Скопіюйте потрібні теки модулів у директорію `module` вашого Lampac.
|
||||
|
||||
```YAML
|
||||
3. Для Docker приклад монтування:
|
||||
```bash
|
||||
-v /path/to/lampac-ukraine/LME.Uaflix:/lampac/module/LME.Uaflix
|
||||
```
|
||||
|
||||
### Автовстановлення через `repository.yaml`
|
||||
|
||||
Працює у Lampac `148.1+`.
|
||||
|
||||
Створіть або оновіть `module/repository.yaml`:
|
||||
|
||||
```yaml
|
||||
- repository: https://github.com/lampame/lampac-ukraine
|
||||
branch: main
|
||||
modules:
|
||||
- LME.Shared
|
||||
- LME.AnimeON
|
||||
- LME.Unimay
|
||||
- LME.Mikai
|
||||
@ -56,14 +65,16 @@ Create or update the module/repository.yaml file
|
||||
- LME.JackTor
|
||||
```
|
||||
|
||||
branch - optional, default main
|
||||
Важливо:
|
||||
- `branch` — необов'язково, за замовчуванням `main`.
|
||||
- `modules` — необов'язково; якщо не вказано, встановляться всі модулі з репозиторію.
|
||||
- Якщо ви вказуєте конкретний список `modules`, додавайте `LME.Shared`, бо інші модулі підключають спільні файли через `syntaxPaths`.
|
||||
|
||||
modules - optional, if not specified, all modules from the repository will be installed
|
||||
### Налаштування в `init.conf`
|
||||
|
||||
## Init support
|
||||
Ключ має збігатися з назвою модуля (`LME.XXX`), а не з назвою провайдера.
|
||||
|
||||
> **Note:** The key in `init.conf` must match the module name (`LME.XXX`), **not** the provider name.
|
||||
> For example, for Uaflix, use `“LME.Uaflix”`, not `“Uaflix”`.
|
||||
Приклад для `LME.Uaflix`:
|
||||
|
||||
```json
|
||||
"LME.Uaflix": {
|
||||
@ -88,16 +99,16 @@ modules - optional, if not specified, all modules from the repository will be in
|
||||
"magic_apn": {
|
||||
"ashdi": "https://tut.im/proxy.php?url={encodeurl}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Parameter compatibility:
|
||||
- `webcorshost` + `useproxy`: work together (parsing via CORS host, and network output can go through a proxy with `useproxy`).
|
||||
- `webcorshost` does not conflict with `streamproxy`: CORS is used for parsing, `streamproxy` is used for streaming.
|
||||
- `magic_apn.ashdi` is used only for Ashdi links and only when the value is not empty.
|
||||
- `webcorshost` does not conflict with `magic_apn`: CORS is used for parsing, while `magic_apn` is used for Ashdi streaming.
|
||||
Сумісність параметрів:
|
||||
- `webcorshost` + `useproxy`: працюють разом (парсинг через CORS-хост, мережевий вихід може йти через проксі).
|
||||
- `webcorshost` + `streamproxy`: не конфліктують (CORS для парсингу, `streamproxy` для потоків).
|
||||
- `magic_apn.ashdi` використовується лише для Ashdi-посилань і лише коли значення не порожнє.
|
||||
- `webcorshost` + `magic_apn`: не конфліктують.
|
||||
|
||||
## JackTor config example (`init.conf`)
|
||||
### Приклад конфігурації `LME.JackTor`
|
||||
|
||||
```json
|
||||
"LME.JackTor": {
|
||||
@ -159,19 +170,98 @@ Parameter compatibility:
|
||||
}
|
||||
```
|
||||
|
||||
Key parameters at a glance:
|
||||
- `jackett` + `apikey`: your Jackett host and API key.
|
||||
- `min_sid` / `min_peers` / `max_size` / `max_serial_size`: base torrent filters.
|
||||
- `quality_allow`, `hdr_mode`, `codec_allow`, `audio_pref`: quality/codec/language prioritization.
|
||||
- `torrs`, `auth_torrs`, `base_auth`: TorrServer nodes used for playback.
|
||||
- `filter` / `filter_ignore`: regex filters for release title and voice labels.
|
||||
Ключові параметри:
|
||||
- `jackett` + `apikey`: хост Jackett та API-ключ.
|
||||
- `min_sid` / `min_peers` / `max_size` / `max_serial_size`: базові фільтри торрентів.
|
||||
- `quality_allow`, `hdr_mode`, `codec_allow`, `audio_pref`: пріоритезація якості, кодека та мов.
|
||||
- `torrs`, `auth_torrs`, `base_auth`: вузли TorrServer для відтворення.
|
||||
- `filter` / `filter_ignore`: regex-фільтри для релізів та озвучок.
|
||||
|
||||
## Source/player availability check script
|
||||
### Скрипт перевірки доступності джерел
|
||||
|
||||
```bash
|
||||
wget -O check.sh https://raw.githubusercontent.com/lampame/lampac-ukraine/main/check.sh && sh check.sh
|
||||
```
|
||||
|
||||
## Donate
|
||||
### Підтримка
|
||||
|
||||
Підтримати автора: https://lampame.donatik.me
|
||||
|
||||
---
|
||||
|
||||
## <a id="en"></a>English
|
||||
|
||||
### Available modules
|
||||
|
||||
**TV shows and movies**
|
||||
- `LME.Uaflix`
|
||||
- `LME.Makhno`
|
||||
- `LME.StarLight`
|
||||
- `LME.KlonFUN`
|
||||
- `LME.UafilmME`
|
||||
- `LME.JackTor`
|
||||
|
||||
**Anime and dorama**
|
||||
- `LME.AnimeON`
|
||||
- `LME.Bamboo`
|
||||
- `LME.Unimay`
|
||||
- `LME.Mikai`
|
||||
- `LME.NMoonAnime`
|
||||
|
||||
### Manual installation
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/lampame/lampac-ukraine.git .
|
||||
```
|
||||
|
||||
2. Copy required module folders into Lampac `module` directory.
|
||||
|
||||
3. Docker mount example:
|
||||
```bash
|
||||
-v /path/to/lampac-ukraine/LME.Uaflix:/lampac/module/LME.Uaflix
|
||||
```
|
||||
|
||||
### Auto installation via `repository.yaml`
|
||||
|
||||
Requires Lampac `148.1+`.
|
||||
|
||||
Create or update `module/repository.yaml`:
|
||||
|
||||
```yaml
|
||||
- repository: https://github.com/lampame/lampac-ukraine
|
||||
branch: main
|
||||
modules:
|
||||
- LME.Shared
|
||||
- LME.AnimeON
|
||||
- LME.Unimay
|
||||
- LME.Mikai
|
||||
- LME.NMoonAnime
|
||||
- LME.Uaflix
|
||||
- LME.Bamboo
|
||||
- LME.Makhno
|
||||
- LME.StarLight
|
||||
- LME.KlonFUN
|
||||
- LME.UafilmME
|
||||
- LME.JackTor
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `branch` is optional, default is `main`.
|
||||
- `modules` is optional; if omitted, all repository modules are installed.
|
||||
- If you specify an explicit module list, include `LME.Shared` because other modules use shared files through `syntaxPaths`.
|
||||
|
||||
### `init.conf` key rule
|
||||
|
||||
Use module name (`LME.XXX`) as a key, not provider name.
|
||||
Example: `LME.Uaflix` instead of `Uaflix`.
|
||||
|
||||
### Source/player availability check script
|
||||
|
||||
```bash
|
||||
wget -O check.sh https://raw.githubusercontent.com/lampame/lampac-ukraine/main/check.sh && sh check.sh
|
||||
```
|
||||
|
||||
### Support
|
||||
|
||||
Support the author: https://lampame.donatik.me
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user