diff --git a/LME.UAKino/Controller.cs b/LME.UAKino/Controller.cs new file mode 100644 index 0000000..0374701 --- /dev/null +++ b/LME.UAKino/Controller.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Web; +using LME.UAKino.Models; +using Microsoft.AspNetCore.Mvc; +using Shared; +using Shared.Engine; +using Shared.Models; +using Shared.Models.Online.Settings; +using Shared.Models.Templates; + +namespace LME.UAKino.Controllers +{ + public class Controller : BaseOnlineController + { + ProxyManager proxyManager; + + public Controller() : base(ModInit.Settings) + { + proxyManager = new ProxyManager(ModInit.UAKino); + } + + [HttpGet] + [Route("lite/lme_uakino")] + async public Task Index(long id, string imdb_id, long kinopoisk_id, string title, string original_title, string original_language, int year, string source, int serial, string account_email, string t, int s = -1, bool rjson = false, string href = null, bool checksearch = false) + { + await UpdateService.ConnectAsync(host); + + var init = loadKit(ModInit.UAKino); + if (!init.enable) + return Forbid(); + + var invoke = new UAKinoInvoke(init, hybridCache, OnLog, proxyManager, httpHydra); + + if (checksearch) + { + if (!IsCheckOnlineSearchEnabled()) + return OnError("lme_uakino", refresh_proxy: true); + + var searchResults = await invoke.Search(title, original_title, year, imdb_id); + if (searchResults != null && searchResults.Count > 0) + return Content("data-json=", "text/plain; charset=utf-8"); + + return OnError("lme_uakino", refresh_proxy: true); + } + + string newsId = null; + string itemUrl = href; + + if (string.IsNullOrEmpty(itemUrl)) + { + var searchResults = await invoke.Search(title, original_title, year, imdb_id); + if (searchResults == null || searchResults.Count == 0) + return OnError("lme_uakino", refresh_proxy: true); + + // Якщо кілька результатів — дозволяємо обрати + if (searchResults.Count > 1) + { + var similar_tpl = new SimilarTpl(searchResults.Count); + foreach (var res in searchResults) + { + string link = $"{host}/lite/lme_uakino?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial={serial}&href={HttpUtility.UrlEncode(res.Url)}"; + similar_tpl.Append(res.Title, res.Year?.ToString() ?? "", res.OriginalTitle ?? "", link, res.Poster); + } + + return rjson + ? Content(similar_tpl.ToJson(), "application/json; charset=utf-8") + : Content(similar_tpl.ToHtml(), "text/html; charset=utf-8"); + } + + itemUrl = searchResults[0].Url; + newsId = searchResults[0].NewsId; + } + else + { + newsId = UAKinoInvoke.ExtractNewsId(itemUrl); + } + + if (string.IsNullOrEmpty(newsId)) + return OnError("lme_uakino", refresh_proxy: true); + + var voices = await invoke.GetPlaylist(newsId); + if (voices == null || voices.Count == 0) + return OnError("lme_uakino", refresh_proxy: true); + + if (serial == 1) + { + return HandleSerial(init, voices, title, original_title, year, imdb_id, kinopoisk_id, itemUrl, t, rjson); + } + else + { + return HandleMovie(init, voices, title, original_title, rjson); + } + } + + private ActionResult HandleSerial(OnlinesSettings init, List voices, string title, string original_title, int year, string imdb_id, long kinopoisk_id, string itemUrl, string t, bool rjson) + { + var voice_tpl = new VoiceTpl(); + var episode_tpl = new EpisodeTpl(); + + if (string.IsNullOrEmpty(t)) + t = voices.First().DataId; + + foreach (var voice in voices) + { + string voiceLink = $"{host}/lite/lme_uakino?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&t={voice.DataId}&href={HttpUtility.UrlEncode(itemUrl)}"; + voice_tpl.Append(voice.Name, voice.DataId == t, voiceLink); + } + + var selected = voices.FirstOrDefault(v => v.DataId == t); + if (selected == null || selected.Episodes.Count == 0) + return OnError("lme_uakino", refresh_proxy: true); + + foreach (var ep in selected.Episodes.OrderBy(e => e.EpisodeNumber ?? int.MaxValue)) + { + int epNum = ep.EpisodeNumber ?? 1; + string epName = string.IsNullOrEmpty(ep.Title) ? $"Епізод {epNum}" : ep.Title; + string streamUrl = BuildStreamUrl(init, ep.FileUrl); + episode_tpl.Append(epName, title ?? original_title, "1", epNum.ToString("D2"), streamUrl); + } + + episode_tpl.Append(voice_tpl); + + return rjson + ? Content(episode_tpl.ToJson(), "application/json; charset=utf-8") + : Content(episode_tpl.ToHtml(), "text/html; charset=utf-8"); + } + + private ActionResult HandleMovie(OnlinesSettings init, List voices, string title, string original_title, bool rjson) + { + var movie_tpl = new MovieTpl(title, original_title); + + foreach (var voice in voices) + { + foreach (var ep in voice.Episodes) + { + string label = voice.Name; + if (voices.Count == 1 && voice.Episodes.Count > 1) + label = ep.Title; + + string streamUrl = BuildStreamUrl(init, ep.FileUrl); + movie_tpl.Append(label, streamUrl); + } + } + + return rjson + ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") + : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8"); + } + + string BuildStreamUrl(OnlinesSettings init, string streamLink) + { + string link = StripLampacArgs(streamLink?.Trim()); + if (string.IsNullOrEmpty(link)) + return link; + + if (ApnHelper.IsEnabled(init)) + { + if (ModInit.ApnHostProvided || ApnHelper.IsAshdiUrl(link)) + return ApnHelper.WrapUrl(init, link); + + var noApn = (OnlinesSettings)init.Clone(); + noApn.apnstream = false; + noApn.apn = null; + return HostStreamProxy(noApn, link); + } + + return HostStreamProxy(init, link); + } + + private static string StripLampacArgs(string url) + { + if (string.IsNullOrEmpty(url)) + return url; + + string cleaned = System.Text.RegularExpressions.Regex.Replace( + url, + @"([?&])(account_email|uid|nws_id)=[^&]*", + "$1", + System.Text.RegularExpressions.RegexOptions.IgnoreCase + ); + + cleaned = cleaned.Replace("?&", "?").Replace("&&", "&").TrimEnd('?', '&'); + return cleaned; + } + + private static bool IsCheckOnlineSearchEnabled() + { + try + { + var onlineType = Type.GetType("Online.ModInit"); + if (onlineType == null) + { + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + onlineType = asm.GetType("Online.ModInit"); + if (onlineType != null) + break; + } + } + var confField = onlineType?.GetField("conf", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); + var conf = confField?.GetValue(null); + var checkProp = conf?.GetType().GetProperty("checkOnlineSearch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); + + if (checkProp?.GetValue(conf) is bool enabled) + return enabled; + } + catch + { + } + + return true; + } + + private static void OnLog(string message) + { + System.Console.WriteLine(message); + } + } +} diff --git a/LME.UAKino/ModInit.cs b/LME.UAKino/ModInit.cs new file mode 100644 index 0000000..2bd0d2d --- /dev/null +++ b/LME.UAKino/ModInit.cs @@ -0,0 +1,99 @@ +using Newtonsoft.Json.Linq; +using Shared; +using Shared.Engine; +using Shared.Models.Online.Settings; +using Shared.Models.Module; +using Shared.Models.Module.Interfaces; +using Microsoft.AspNetCore.Mvc; +using Microsoft.CodeAnalysis.Scripting; +using Microsoft.Extensions.Caching.Memory; +using Newtonsoft.Json; +using Shared.Models; +using Shared.Models.Events; +using System; +using System.Net.Http; +using System.Net.Mime; +using System.Net.Security; +using System.Security.Authentication; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace LME.UAKino +{ + public class ModInit : IModuleLoaded + { + public static double Version => 1.0; + + public static OnlinesSettings UAKino; + public static bool ApnHostProvided; + + public static OnlinesSettings Settings + { + get => UAKino; + set => UAKino = value; + } + + /// + /// модуль загружен + /// + public void Loaded(InitspaceModel initspace) + { + UAKino = new OnlinesSettings("LME.UAKino", "https://uakino.top", streamproxy: false, useproxy: false) + { + displayname = "UAKino", + displayindex = 0, + proxy = new Shared.Models.Base.ProxySettings() + { + useAuth = true, + username = "", + password = "", + list = new string[] { "socks5://ip:port" } + } + }; + var defaults = JObject.FromObject(UAKino); + defaults["enabled"] = true; + var conf = ModuleInvoke.Init("LME.UAKino", defaults); + bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost); + conf.Remove("apn"); + conf.Remove("apn_host"); + UAKino = conf.ToObject(); + if (hasApn) + ApnHelper.ApplyInitConf(apnEnabled, apnHost, UAKino, useDefaultHostWhenEmpty: true); + ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost); + if (hasApn && apnEnabled) + { + UAKino.streamproxy = false; + } + else if (UAKino.streamproxy) + { + UAKino.apnstream = false; + UAKino.apn = null; + } + + // Виводити "уточнити пошук" + OnlineRegistry.RegisterWithSearch("lme_uakino"); + } + + public void Dispose() + { + } + } + + public static class UpdateService + { + private static readonly ModuleUpdateService _service = new( + () => ModInit.Settings?.plugin, + () => ModInit.Version); + + public static Task ConnectAsync(string host, CancellationToken cancellationToken = default) + => _service.ConnectAsync(host, cancellationToken); + + public static bool IsDisconnected() + => _service.IsDisconnected(); + + public static ActionResult Validate(ActionResult result) + => _service.Validate(result); + } +} diff --git a/LME.UAKino/Models/UAKinoModels.cs b/LME.UAKino/Models/UAKinoModels.cs new file mode 100644 index 0000000..bb5943a --- /dev/null +++ b/LME.UAKino/Models/UAKinoModels.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace LME.UAKino.Models +{ + public class SearchResult + { + public string Title { get; set; } + public string OriginalTitle { get; set; } + public string Url { get; set; } + public string Poster { get; set; } + public int? Year { get; set; } + public string NewsId { get; set; } + } + + public class VoiceGroup + { + public string Name { get; set; } + public string DataId { get; set; } + public List Episodes { get; set; } = new(); + } + + public class EpisodeItem + { + public string Title { get; set; } + public string FileUrl { get; set; } + public int? EpisodeNumber { get; set; } + } +} diff --git a/LME.UAKino/OnlineApi.cs b/LME.UAKino/OnlineApi.cs new file mode 100644 index 0000000..857a997 --- /dev/null +++ b/LME.UAKino/OnlineApi.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Memory; +using Shared.Models; +using Shared.Models.Module; +using Shared.Models.Module.Interfaces; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LME.UAKino +{ + public class OnlineApi : IModuleOnline + { + public List Invoke(HttpContext httpContext, RequestModel requestInfo, string host, OnlineEventsModel args) + { + long.TryParse(args.id, out long tmdbid); + return Events(host, tmdbid, args.imdb_id, args.kinopoisk_id, args.title, args.original_title, args.original_language, args.year, args.source, args.serial, args.account_email); + } + + private static List Events(string host, long id, string imdb_id, long kinopoisk_id, string title, string original_title, string original_language, int year, string source, int serial, string account_email) + { + var online = new List(); + + var init = ModInit.UAKino; + if (init.enable && !init.rip) + { + if (UpdateService.IsDisconnected()) + init.overridehost = null; + + online.Add(new ModuleOnlineItem(init, "lme_uakino")); + } + + return online; + } + } +} diff --git a/LME.UAKino/UAKino.csproj b/LME.UAKino/UAKino.csproj new file mode 100644 index 0000000..c280999 --- /dev/null +++ b/LME.UAKino/UAKino.csproj @@ -0,0 +1,15 @@ + + + + net10.0 + library + true + + + + + ..\..\Shared.dll + + + + diff --git a/LME.UAKino/UAKinoInvoke.cs b/LME.UAKino/UAKinoInvoke.cs new file mode 100644 index 0000000..cf3c2de --- /dev/null +++ b/LME.UAKino/UAKinoInvoke.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; +using LME.UAKino.Models; +using HtmlAgilityPack; +using Shared; +using Shared.Engine; +using Shared.Models; +using Shared.Models.Online.Settings; + +namespace LME.UAKino +{ + public class UAKinoInvoke + { + private readonly OnlinesSettings _init; + private readonly IHybridCache _hybridCache; + private readonly Action _onLog; + private readonly ProxyManager _proxyManager; + private readonly HttpHydra _httpHydra; + + public UAKinoInvoke(OnlinesSettings init, IHybridCache hybridCache, Action onLog, ProxyManager proxyManager, HttpHydra httpHydra = null) + { + _init = init; + _hybridCache = hybridCache; + _onLog = onLog; + _proxyManager = proxyManager; + _httpHydra = httpHydra; + } + + public async Task> Search(string title, string original_title, int year, string imdb_id) + { + string query = BuildSearchQuery(title, original_title, imdb_id); + if (string.IsNullOrEmpty(query)) + return null; + + string memKey = $"UAKino:search:{query}"; + if (_hybridCache.TryGetValue(memKey, out List cached)) + return FilterByYear(cached, year); + + try + { + _onLog?.Invoke($"UAKino search: {query}"); + + string url = $"{_init.host}/engine/lazydev/dle_search/ajax.php"; + string body = $"story={HttpUtility.UrlEncode(query)}&thisUrl=/ua/"; + + var headers = new List() + { + new HeadersModel("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.3 Safari/605.1.15"), + new HeadersModel("Referer", $"{_init.host}/ua/"), + new HeadersModel("X-Requested-With", "XMLHttpRequest"), + new HeadersModel("Origin", _init.host), + new HeadersModel("Accept", "*/*"), + new HeadersModel("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + }; + + string json = await Http.Post(_init.cors(url), body, headers: headers, proxy: _proxyManager.Get()); + if (string.IsNullOrEmpty(json)) + return null; + + using var jsonDoc = JsonDocument.Parse(json); + if (!jsonDoc.RootElement.TryGetProperty("content", out JsonElement contentElem)) + return null; + + string html = contentElem.GetString(); + if (string.IsNullOrEmpty(html)) + return null; + + var htmlDoc = new HtmlDocument(); + htmlDoc.LoadHtml(html); + + var results = ParseSearchResults(htmlDoc); + if (results.Count > 0) + _hybridCache.Set(memKey, results, cacheTime(20)); + + return FilterByYear(results, year); + } + catch (Exception ex) + { + _onLog?.Invoke($"UAKino search error: {ex.Message}"); + return null; + } + } + + /// + /// Отримати плейлист (озвучки + епізоди) за news_id + /// + public async Task> GetPlaylist(string newsId) + { + if (string.IsNullOrEmpty(newsId)) + return null; + + string memKey = $"UAKino:playlist:{newsId}"; + if (_hybridCache.TryGetValue(memKey, out List cached)) + return cached; + + try + { + _onLog?.Invoke($"UAKino playlist: {newsId}"); + + string url = $"{_init.host}/engine/ajax/playlists.php?news_id={newsId}&xfield=playlist"; + + var headers = new List() + { + new HeadersModel("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.3 Safari/605.1.15"), + new HeadersModel("Referer", $"{_init.host}/{newsId}-"), + new HeadersModel("X-Requested-With", "XMLHttpRequest"), + new HeadersModel("Accept", "application/json, text/javascript, */*; q=0.01") + }; + + string json = await HttpGet(url, headers); + if (string.IsNullOrEmpty(json)) + return null; + + using var jsonDoc = JsonDocument.Parse(json); + if (!jsonDoc.RootElement.TryGetProperty("response", out JsonElement responseElem)) + return null; + + string html = responseElem.GetString(); + if (string.IsNullOrEmpty(html)) + return null; + + var voices = ParsePlaylistHtml(html); + if (voices.Count > 0) + _hybridCache.Set(memKey, voices, cacheTime(30)); + + return voices; + } + catch (Exception ex) + { + _onLog?.Invoke($"UAKino playlist error: {ex.Message}"); + return null; + } + } + + /// + /// Витягнути news_id з URL контенту + /// + public static string ExtractNewsId(string url) + { + if (string.IsNullOrEmpty(url)) + return null; + + var match = Regex.Match(url, @"[?/](\d+)-[^/]*\.html"); + if (match.Success) + return match.Groups[1].Value; + + return null; + } + + private List ParseSearchResults(HtmlDocument doc) + { + var results = new List(); + var nodes = doc.DocumentNode.SelectNodes("//a[@class='search-result-link']"); + if (nodes == null) + return results; + + foreach (var node in nodes) + { + try + { + string href = node.GetAttributeValue("href", ""); + if (string.IsNullOrEmpty(href)) + continue; + + var imgNode = node.SelectSingleNode(".//img[@class='search-poster']"); + string poster = imgNode?.GetAttributeValue("src", "") ?? ""; + + var titleNode = node.SelectSingleNode(".//span[@class='searchheading']"); + string title = CleanText(titleNode?.InnerText); + + var origTitleNode = node.SelectSingleNode(".//span[@class='search-orig-title']"); + string origTitle = CleanText(origTitleNode?.InnerText); + + var infoNode = node.SelectSingleNode(".//div[@class='search-extend-info']"); + int? year = null; + if (infoNode != null) + { + var yearSpan = infoNode.SelectSingleNode("./span[1]"); + string yearText = CleanText(yearSpan?.InnerText); + if (!string.IsNullOrEmpty(yearText) && int.TryParse(yearText.Trim(), out int parsedYear)) + year = parsedYear; + } + + string newsId = ExtractNewsId(href); + + results.Add(new SearchResult + { + Title = title, + OriginalTitle = origTitle, + Url = NormalizeUrl(href), + Poster = NormalizeUrl(poster), + Year = year, + NewsId = newsId + }); + } + catch + { + continue; + } + } + + return results; + } + + private List ParsePlaylistHtml(string html) + { + var voices = new List(); + + var doc = new HtmlDocument(); + doc.LoadHtml(html); + + var playerDiv = doc.DocumentNode.SelectSingleNode("//div[@class='playlists-player']"); + if (playerDiv == null) + { + // спроба знайти епізоди без обгортки playlists-player + return ParseEpisodesFlat(doc.DocumentNode); + } + + // Парсимо голоси (озвучки) з вкладки playlists-lists + var voiceItems = playerDiv.SelectNodes(".//div[@class='playlists-lists']//ul/li"); + if (voiceItems != null) + { + foreach (var li in voiceItems) + { + string dataId = li.GetAttributeValue("data-id", ""); + string text = CleanText(li.InnerText); + // Прибираємо "(X-Y)" з кінця назви озвучки + string voiceName = Regex.Replace(text, @"\s*\(\d+[\d,\s-]*\)\s*$", "").Trim(); + if (string.IsNullOrEmpty(voiceName)) + voiceName = text; + + if (!string.IsNullOrEmpty(dataId)) + { + voices.Add(new VoiceGroup + { + Name = voiceName, + DataId = dataId, + Episodes = new List() + }); + } + } + } + + // Парсимо епізоди з playlists-videos + var episodeItems = playerDiv.SelectNodes(".//div[@class='playlists-videos']//ul/li[@data-file]"); + if (episodeItems != null) + { + foreach (var li in episodeItems) + { + string fileUrl = li.GetAttributeValue("data-file", ""); + string dataId = li.GetAttributeValue("data-id", ""); + string voiceAttr = li.GetAttributeValue("data-voice", ""); + string text = CleanText(li.InnerText); + + // Визначаємо до якого voice групи належить + VoiceGroup targetVoice = null; + + // Спершу за data-id + if (!string.IsNullOrEmpty(dataId)) + targetVoice = voices.FirstOrDefault(v => v.DataId == dataId); + + // Якщо не знайшли, то за data-voice (назвою) + if (targetVoice == null && !string.IsNullOrEmpty(voiceAttr)) + targetVoice = voices.FirstOrDefault(v => + v.Name.Equals(voiceAttr, StringComparison.OrdinalIgnoreCase)); + + // Якщо досі не знайшли, беремо перший голос + targetVoice ??= voices.FirstOrDefault(); + + int? epNum = ExtractEpisodeNumber(text); + + var episode = new EpisodeItem + { + Title = string.IsNullOrEmpty(text) ? $"Епізод {epNum ?? 1}" : text, + FileUrl = NormalizeUrl(fileUrl), + EpisodeNumber = epNum + }; + + if (targetVoice != null) + targetVoice.Episodes.Add(episode); + } + } + + return voices; + } + + /// + /// Парсинг коли немає структури playlists-player (наприклад для фільмів) + /// + private List ParseEpisodesFlat(HtmlNode scope) + { + var voices = new List(); + var items = scope.SelectNodes("//li[@data-file]"); + if (items == null) + return voices; + + var defaultVoice = new VoiceGroup + { + Name = "Озвучення", + DataId = "0_0", + Episodes = new List() + }; + + foreach (var li in items) + { + string fileUrl = li.GetAttributeValue("data-file", ""); + string voiceAttr = li.GetAttributeValue("data-voice", ""); + string text = CleanText(li.InnerText); + int? epNum = ExtractEpisodeNumber(text); + + defaultVoice.Episodes.Add(new EpisodeItem + { + Title = string.IsNullOrEmpty(text) ? "Фільм" : text, + FileUrl = NormalizeUrl(fileUrl), + EpisodeNumber = epNum + }); + } + + if (defaultVoice.Episodes.Count > 0) + voices.Add(defaultVoice); + + return voices; + } + + private static string BuildSearchQuery(string title, string original_title, string imdb_id) + { + if (!string.IsNullOrEmpty(imdb_id) && imdb_id.StartsWith("tt")) + return imdb_id; + + if (!string.IsNullOrEmpty(title)) + return title; + + if (!string.IsNullOrEmpty(original_title)) + return original_title; + + return null; + } + + private static List FilterByYear(List results, int year) + { + if (results == null || results.Count <= 1 || year <= 0) + return results; + + var yearMatch = results.Where(r => r.Year == year).ToList(); + if (yearMatch.Count > 0) + return yearMatch; + + return results; + } + + private string NormalizeUrl(string url) + { + if (string.IsNullOrEmpty(url)) + return string.Empty; + + if (url.StartsWith("//")) + return $"https:{url}"; + + if (url.StartsWith("/")) + return $"{_init.host}{url}"; + + return url; + } + + private static int? ExtractEpisodeNumber(string title) + { + if (string.IsNullOrEmpty(title)) + return null; + + var match = Regex.Match(title, @"(\d+)"); + if (match.Success && int.TryParse(match.Groups[1].Value, out int value)) + return value; + + return null; + } + + private static string CleanText(string value) + { + if (string.IsNullOrEmpty(value)) + return string.Empty; + + return HtmlEntity.DeEntitize(value).Trim(); + } + + private Task HttpGet(string url, List headers) + { + if (_httpHydra != null) + return _httpHydra.Get(url, newheaders: headers); + + return Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get()); + } + + public static TimeSpan cacheTime(int multiaccess, OnlinesSettings init = null) + { + int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess; + if (ctime > multiaccess) + ctime = multiaccess; + + return TimeSpan.FromMinutes(ctime); + } + } +} diff --git a/LME.UAKino/manifest.json b/LME.UAKino/manifest.json new file mode 100644 index 0000000..9ed0240 --- /dev/null +++ b/LME.UAKino/manifest.json @@ -0,0 +1,12 @@ +{ + "enable": true, + "version": 3, + "initspace": "LME.UAKino.ModInit", + "online": "LME.UAKino.OnlineApi", + "syntaxPaths": [ + "../LME.Shared/GlobalUsings.cs", + "../LME.Shared/Online/OnlineRegistry.cs", + "../LME.Shared/Update/ModuleUpdateService.cs", + "../LME.Shared/Apn/ApnHelper.cs" + ] +}