Felix b1a7ce510d feat(uafilmme): add UafilmME streaming plugin with APN support
Integrate a new online streaming source for UafilmME, including API invocation, search, and playback functionality. Adds APN proxy helper for Ashdi streams, module initialization, and related models and controllers to extend the existing online framework.
2026-04-04 12:09:18 +03:00

229 lines
7.4 KiB
C#

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Shared;
using Shared.Engine;
using Shared.Models.Module;
using Shared.Models.Module.Interfaces;
using Shared.Models.Online.Settings;
using Microsoft.AspNetCore.Mvc;
using 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 UafilmME
{
public class ModInit : IModuleLoaded
{
public static double Version => 1.0;
public static OnlinesSettings UafilmME;
public static bool ApnHostProvided;
public static OnlinesSettings Settings
{
get => UafilmME;
set => UafilmME = value;
}
/// <summary>
/// Модуль завантажено.
/// </summary>
public void Loaded(InitspaceModel initspace)
{
UafilmME = new OnlinesSettings("UafilmME", "https://uafilm.me", streamproxy: false, useproxy: false)
{
displayname = "UAFilmME",
displayindex = 0,
proxy = new Shared.Models.Base.ProxySettings()
{
useAuth = true,
username = "",
password = "",
list = new string[] { "socks5://ip:port" }
}
};
var conf = ModuleInvoke.Init("UafilmME", JObject.FromObject(UafilmME));
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
conf.Remove("apn");
conf.Remove("apn_host");
UafilmME = conf.ToObject<OnlinesSettings>();
if (hasApn)
ApnHelper.ApplyInitConf(apnEnabled, apnHost, UafilmME);
ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost);
if (hasApn && apnEnabled)
{
UafilmME.streamproxy = false;
}
else if (UafilmME.streamproxy)
{
UafilmME.apnstream = false;
UafilmME.apn = null;
}
RegisterWithSearch("uafilmme");
}
private static void RegisterWithSearch(string plugin)
{
try
{
var onlineType = Type.GetType("Online.ModInit");
if (onlineType == null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
onlineType = asm.GetType("Online.ModInit");
if (onlineType != null)
break;
}
}
var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var conf = confField?.GetValue(null);
var withSearchProp = conf?.GetType().GetProperty("with_search", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (withSearchProp?.GetValue(conf) is System.Collections.IList list)
{
foreach (var item in list)
{
if (string.Equals(item?.ToString(), plugin, StringComparison.OrdinalIgnoreCase))
return;
}
list.Add(plugin);
}
}
catch
{
}
}
public void Dispose()
{
}
}
public static class UpdateService
{
private static readonly string _connectUrl = "https://lmcuk.lme.isroot.in/stats";
private static ConnectResponse? Connect = null;
private static DateTime? _connectTime = null;
private static DateTime? _disconnectTime = null;
private static readonly TimeSpan _resetInterval = TimeSpan.FromHours(4);
private static Timer? _resetTimer = null;
private static readonly object _lock = new();
public static async Task ConnectAsync(string host, CancellationToken cancellationToken = default)
{
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
return;
lock (_lock)
{
if (_connectTime is not null || Connect?.IsUpdateUnavailable == true)
return;
_connectTime = DateTime.UtcNow;
}
try
{
using var handler = new SocketsHttpHandler
{
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = (_, _, _, _) => true,
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
}
};
using var client = new HttpClient(handler);
client.Timeout = TimeSpan.FromSeconds(15);
var request = new
{
Host = host,
Module = ModInit.Settings.plugin,
Version = ModInit.Version,
};
var requestJson = JsonConvert.SerializeObject(request, Formatting.None);
var requestContent = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json);
var response = await client
.PostAsync(_connectUrl, requestContent, cancellationToken)
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
if (response.Content.Headers.ContentLength > 0)
{
var responseText = await response.Content
.ReadAsStringAsync(cancellationToken)
.ConfigureAwait(false);
Connect = JsonConvert.DeserializeObject<ConnectResponse>(responseText);
}
lock (_lock)
{
_resetTimer?.Dispose();
_resetTimer = null;
if (Connect?.IsUpdateUnavailable != true)
{
_resetTimer = new Timer(ResetConnectTime, null, _resetInterval, Timeout.InfiniteTimeSpan);
}
else
{
_disconnectTime = Connect?.IsNoiseEnabled == true
? DateTime.UtcNow.AddHours(Random.Shared.Next(1, 4))
: DateTime.UtcNow;
}
}
}
catch
{
ResetConnectTime(null);
}
}
private static void ResetConnectTime(object? state)
{
lock (_lock)
{
_connectTime = null;
Connect = null;
_resetTimer?.Dispose();
_resetTimer = null;
}
}
public static bool IsDisconnected()
{
return _disconnectTime is not null
&& DateTime.UtcNow >= _disconnectTime;
}
public static ActionResult Validate(ActionResult result)
{
return IsDisconnected()
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
: result;
}
}
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
}