diff --git a/LME.StreamData/Controller.cs b/LME.StreamData/Controller.cs
deleted file mode 100644
index 27970d7..0000000
--- a/LME.StreamData/Controller.cs
+++ /dev/null
@@ -1,306 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Web;
-using LME.StreamData.Models;
-using Microsoft.AspNetCore.Mvc;
-using Shared;
-using Shared.Engine;
-using Shared.Models;
-using Shared.Models.Online.Settings;
-using Shared.Models.Templates;
-
-namespace LME.StreamData.Controllers
-{
- public class Controller : BaseOnlineController
- {
- ProxyManager proxyManager;
-
- public Controller() : base(ModInit.Settings)
- {
- proxyManager = new ProxyManager(ModInit.StreamDataSettings);
- }
-
- ///
- /// Головний ендпоінт модуля StreamData
- /// Працює виключно через TMDB ID (параметр id)
- ///
- [HttpGet]
- [Route("lite/lme_streamdata")]
- 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, int e = -1, bool play = false, bool rjson = false, string href = null, bool checksearch = false)
- {
- await UpdateService.ConnectAsync(host);
-
- var init = loadKit(ModInit.StreamDataSettings);
- if (!init.enable)
- return Forbid();
-
- var invoke = new StreamDataInvoke(init, hybridCache, OnLog, proxyManager, httpHydra);
-
- // checksearch — перевірка доступності
- if (checksearch)
- {
- if (!IsCheckOnlineSearchEnabled())
- return OnError("lme_streamdata", refresh_proxy: true);
-
- if (id > 0)
- return Content("data-json=", "text/plain; charset=utf-8");
-
- return OnError("lme_streamdata", refresh_proxy: true);
- }
-
- // play — повернути стрім для конкретного епізоду (call метод)
- if (play)
- {
- return await HandlePlay(invoke, init, id, s, e, title, original_title, t);
- }
-
- // Фільм
- if (serial != 1)
- {
- return await HandleMovie(invoke, init, id, title, original_title, rjson);
- }
-
- // Серіал
- return await HandleSerial(invoke, init, id, title, original_title, s, e, t, rjson);
- }
-
- ///
- /// Обробка фільму: отримуємо всі stream_urls та показуємо їх
- ///
- private async Task HandleMovie(StreamDataInvoke invoke, OnlinesSettings init, long tmdbId, string title, string originalTitle, bool rjson)
- {
- var response = await invoke.GetMovie(tmdbId);
- if (response?.data?.stream_urls == null || response.data.stream_urls.Count == 0)
- return OnError("lme_streamdata", refresh_proxy: true);
-
- var streamUrls = response.data.stream_urls;
- var subs = CollectSubtitles(response);
-
- var displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title);
- var tpl = new MovieTpl(displayTitle, originalTitle);
-
- int index = 1;
- foreach (var streamUrl in streamUrls)
- {
- if (string.IsNullOrWhiteSpace(streamUrl))
- continue;
-
- string label = $"Джерело #{index}";
- string processedUrl = BuildStreamUrl(init, streamUrl);
- tpl.Append(label, processedUrl, subtitles: subs);
- index++;
- }
-
- if (tpl.data == null || tpl.data.Count == 0)
- return OnError("lme_streamdata", refresh_proxy: true);
-
- return Content(
- rjson ? tpl.ToJson() : tpl.ToHtml(),
- rjson ? "application/json; charset=utf-8" : "text/html; charset=utf-8"
- );
- }
-
- ///
- /// Обробка серіалу: eps → сезони → епізоди з voice-вкладками (джерела)
- ///
- private async Task HandleSerial(StreamDataInvoke invoke, OnlinesSettings init, long tmdbId, string title, string originalTitle, int s, int e, string t, bool rjson)
- {
- var response = await invoke.GetTvSeries(tmdbId);
- if (response?.data?.eps == null || response.data.eps.Count == 0)
- return OnError("lme_streamdata", refresh_proxy: true);
-
- var eps = response.data.eps;
- var seasons = eps.Keys
- .Select(k => int.TryParse(k, out int sn) ? sn : 0)
- .Where(sn => sn > 0)
- .OrderBy(sn => sn)
- .ToList();
-
- if (seasons.Count == 0)
- return OnError("lme_streamdata", refresh_proxy: true);
-
- // Кількість джерел (CDN) з першого запиту
- var sources = response.data?.stream_urls?.Where(u => !string.IsNullOrWhiteSpace(u)).ToList() ?? new List();
- int sourceCount = Math.Max(1, sources.Count);
-
- var displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title);
-
- // Список сезонів
- if (s <= 0)
- {
- var seasonTpl = new SeasonTpl(seasons.Count);
- foreach (var season in seasons)
- {
- string seasonLink = $"{host}/lite/lme_streamdata?id={tmdbId}&serial=1&s={season}&title={HttpUtility.UrlEncode(displayTitle)}&original_title={HttpUtility.UrlEncode(originalTitle)}";
- seasonTpl.Append($"Сезон {season}", seasonLink, season.ToString());
- }
-
- return Content(
- rjson ? seasonTpl.ToJson() : seasonTpl.ToHtml(),
- rjson ? "application/json; charset=utf-8" : "text/html; charset=utf-8"
- );
- }
-
- // Список епізодів з voice-вкладками для вибору джерела
- string seasonKey = s.ToString();
- if (!eps.ContainsKey(seasonKey) || eps[seasonKey] == null || eps[seasonKey].Count == 0)
- return OnError("lme_streamdata", refresh_proxy: true);
-
- var episodeNumbers = eps[seasonKey]
- .Select(ep => int.TryParse(ep, out int en) ? en : 0)
- .Where(en => en > 0)
- .OrderBy(en => en)
- .ToList();
-
- if (episodeNumbers.Count == 0)
- return OnError("lme_streamdata", refresh_proxy: true);
-
- // Voice-вкладки: кожне джерело як окрема "озвучка"
- string selectedSource = string.IsNullOrEmpty(t) ? "1" : t;
- int selectedIndex = int.TryParse(selectedSource, out int si) && si >= 1 && si <= sourceCount ? si : 1;
-
- var voiceTpl = new VoiceTpl();
- for (int i = 1; i <= sourceCount; i++)
- {
- string voiceLink = $"{host}/lite/lme_streamdata?id={tmdbId}&serial=1&s={s}&t={i}&title={HttpUtility.UrlEncode(displayTitle)}&original_title={HttpUtility.UrlEncode(originalTitle)}";
- voiceTpl.Append($"Джерело #{i}", i == selectedIndex, voiceLink);
- }
-
- // Епізоди з посиланнями на вибране джерело
- var episodeTpl = new EpisodeTpl(episodeNumbers.Count);
- foreach (var epNum in episodeNumbers)
- {
- string episodeName = $"Епізод {epNum}";
- string callUrl = $"{host}/lite/lme_streamdata?id={tmdbId}&serial=1&s={s}&e={epNum}&play=true&t={selectedSource}&title={HttpUtility.UrlEncode(displayTitle)}&original_title={HttpUtility.UrlEncode(originalTitle)}";
- episodeTpl.Append(episodeName, displayTitle, s.ToString(), epNum.ToString("D2"), accsArgs(callUrl), "call");
- }
-
- episodeTpl.Append(voiceTpl);
-
- return Content(
- rjson ? episodeTpl.ToJson() : episodeTpl.ToHtml(),
- rjson ? "application/json; charset=utf-8" : "text/html; charset=utf-8"
- );
- }
-
- ///
- /// Обробка play-запиту: API з season/episode → JSON з вибраним джерелом
- ///
- private async Task HandlePlay(StreamDataInvoke invoke, OnlinesSettings init, long tmdbId, int season, int episode, string title, string originalTitle, string t)
- {
- if (tmdbId <= 0 || season <= 0 || episode <= 0)
- return OnError("lme_streamdata", refresh_proxy: true);
-
- var response = await invoke.GetEpisode(tmdbId, season, episode);
- if (response?.data?.stream_urls == null || response.data.stream_urls.Count == 0)
- return OnError("lme_streamdata", refresh_proxy: true);
-
- var streamUrls = response.data.stream_urls.Where(u => !string.IsNullOrWhiteSpace(u)).ToList();
- if (streamUrls.Count == 0)
- return OnError("lme_streamdata", refresh_proxy: true);
-
- // Вибираємо джерело за індексом з voice-вкладки t (1-based)
- int sourceIndex = int.TryParse(t, out int si) && si >= 1 && si <= streamUrls.Count ? si - 1 : 0;
- string streamUrl = BuildStreamUrl(init, streamUrls[sourceIndex]);
-
- var subs = CollectSubtitles(response);
- string displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title);
-
- return UpdateService.Validate(Content(
- VideoTpl.ToJson("play", streamUrl, displayTitle, subtitles: subs),
- "application/json; charset=utf-8"
- ));
- }
-
- ///
- /// Зібрати субтитри з відповіді API
- ///
- private SubtitleTpl CollectSubtitles(StreamDataResponse response)
- {
- if (response?.default_subs == null || response.default_subs.Count == 0)
- return null;
-
- var tpl = new SubtitleTpl(response.default_subs.Count);
- foreach (var sub in response.default_subs)
- {
- if (!string.IsNullOrWhiteSpace(sub?.url) && !string.IsNullOrWhiteSpace(sub?.lang))
- {
- tpl.Append(sub.lang, sub.url);
- }
- }
-
- return tpl;
- }
-
- string BuildStreamUrl(OnlinesSettings init, string streamLink)
- {
- string link = StripLampacArgs(streamLink?.Trim());
- if (string.IsNullOrEmpty(link))
- return link;
-
- if (ApnHelper.IsEnabled(init))
- {
- if (ModInit.ApnHostProvided)
- return ApnHelper.WrapUrl(init, link);
-
- var noApn = (OnlinesSettings)init.Clone();
- noApn.apnstream = false;
- noApn.apn = null;
- return HostStreamProxy(noApn, link);
- }
-
- 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.StreamData/ModInit.cs b/LME.StreamData/ModInit.cs
deleted file mode 100644
index 8d731b5..0000000
--- a/LME.StreamData/ModInit.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using Newtonsoft.Json.Linq;
-using Shared;
-using Shared.Engine;
-using Shared.Models.Module;
-using Shared.Models.Module.Interfaces;
-using Microsoft.AspNetCore.Mvc;
-using Shared.Models;
-using Shared.Models.Events;
-using Shared.Models.Online.Settings;
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace LME.StreamData
-{
- public class ModInit : IModuleLoaded
- {
- public static double Version => 1.5;
-
- public static OnlinesSettings StreamDataSettings;
- public static bool ApnHostProvided;
-
- public static OnlinesSettings Settings
- {
- get => StreamDataSettings;
- set => StreamDataSettings = value;
- }
-
- ///
- /// Модуль завантажено
- ///
- public void Loaded(InitspaceModel initspace)
- {
- StreamDataSettings = new OnlinesSettings("LME.StreamData", "https://streamdata.vaplayer.ru", streamproxy: false, useproxy: false)
- {
- displayname = "StreamData",
- displayindex = 0,
- proxy = new Shared.Models.Base.ProxySettings()
- {
- useAuth = true,
- username = "",
- password = "",
- list = new string[] { "socks5://ip:port" }
- }
- };
-
- var defaults = JObject.FromObject(StreamDataSettings);
- defaults["enabled"] = true;
- var conf = ModuleInvoke.Init("LME.StreamData", defaults);
- bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
- conf.Remove("apn");
- conf.Remove("apn_host");
- StreamDataSettings = conf.ToObject();
- if (hasApn)
- ApnHelper.ApplyInitConf(apnEnabled, apnHost, StreamDataSettings, useDefaultHostWhenEmpty: true);
- ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost);
- if (hasApn && apnEnabled)
- {
- StreamDataSettings.streamproxy = false;
- }
- else if (StreamDataSettings.streamproxy)
- {
- StreamDataSettings.apnstream = false;
- StreamDataSettings.apn = null;
- }
-
- // Реєструємо плагін без пошуку — працюємо тільки через TMDB ID
- OnlineRegistry.RegisterWithSearch("lme_streamdata");
- }
-
- 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.StreamData/Models/StreamDataModels.cs b/LME.StreamData/Models/StreamDataModels.cs
deleted file mode 100644
index 8dcbd05..0000000
--- a/LME.StreamData/Models/StreamDataModels.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System.Collections.Generic;
-
-namespace LME.StreamData.Models
-{
- public class SubtitleItem
- {
- public string lang { get; set; }
- public string code { get; set; }
- public string url { get; set; }
- }
-
- public class StreamDataResponse
- {
- public string status_code { get; set; }
- public StreamDataInfo data { get; set; }
- public List default_subs { get; set; }
- }
-
- public class StreamDataInfo
- {
- public string title { get; set; }
- public string imdb_id { get; set; }
- public int season { get; set; }
- public string episode { get; set; }
- public Dictionary> eps { get; set; }
- public string file_name { get; set; }
- public string backdrop { get; set; }
- public List stream_urls { get; set; }
- public string thumbnails_url { get; set; }
- }
-}
diff --git a/LME.StreamData/OnlineApi.cs b/LME.StreamData/OnlineApi.cs
deleted file mode 100644
index eb4c2d8..0000000
--- a/LME.StreamData/OnlineApi.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Microsoft.AspNetCore.Http;
-using Shared.Models;
-using Shared.Models.Module;
-using Shared.Models.Module.Interfaces;
-using System.Collections.Generic;
-
-namespace LME.StreamData
-{
- public class OnlineApi : IModuleOnline
- {
- public List Invoke(HttpContext httpContext, RequestModel requestInfo, string host, OnlineEventsModel args)
- {
- var online = new List();
-
- var init = ModInit.StreamDataSettings;
- if (init.enable && !init.rip)
- {
- if (UpdateService.IsDisconnected())
- init.overridehost = null;
-
- // StreamData працює з TMDB ID — показуємо для всього контенту
- online.Add(new ModuleOnlineItem(init, "lme_streamdata"));
- }
-
- return online;
- }
- }
-}
diff --git a/LME.StreamData/StreamData.csproj b/LME.StreamData/StreamData.csproj
deleted file mode 100644
index c280999..0000000
--- a/LME.StreamData/StreamData.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- net10.0
- library
- true
-
-
-
-
- ..\..\Shared.dll
-
-
-
-
diff --git a/LME.StreamData/StreamDataInvoke.cs b/LME.StreamData/StreamDataInvoke.cs
deleted file mode 100644
index 432b96e..0000000
--- a/LME.StreamData/StreamDataInvoke.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using LME.StreamData.Models;
-using Newtonsoft.Json;
-using Shared;
-using Shared.Engine;
-using Shared.Models;
-using Shared.Models.Online.Settings;
-
-namespace LME.StreamData
-{
- public class StreamDataInvoke
- {
- private readonly OnlinesSettings _init;
- private readonly IHybridCache _hybridCache;
- private readonly Action _onLog;
- private readonly ProxyManager _proxyManager;
- private readonly HttpHydra _httpHydra;
-
- private const string API_BASE = "https://streamdata.vaplayer.ru/api.php";
-
- public StreamDataInvoke(OnlinesSettings init, IHybridCache hybridCache, Action onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
- {
- _init = init;
- _hybridCache = hybridCache;
- _onLog = onLog;
- _proxyManager = proxyManager;
- _httpHydra = httpHydra;
- }
-
- ///
- /// Отримати дані для фільму за TMDB ID
- ///
- public async Task GetMovie(long tmdbId)
- {
- string memKey = $"StreamData:movie:{tmdbId}";
- if (_hybridCache.TryGetValue(memKey, out StreamDataResponse cached))
- return cached;
-
- try
- {
- string url = $"{API_BASE}?tmdb={tmdbId}&type=movie";
- _onLog?.Invoke($"StreamData movie: {url}");
-
- string json = await ApiGet(url);
- if (string.IsNullOrEmpty(json))
- return null;
-
- var response = JsonConvert.DeserializeObject(json);
- if (response?.status_code != "200" || response?.data?.stream_urls == null || response.data.stream_urls.Count == 0)
- return null;
-
- _hybridCache.Set(memKey, response, cacheTime(30, init: _init));
- return response;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"StreamData movie error: {ex.Message}");
- return null;
- }
- }
-
- ///
- /// Отримати дані для серіалу (без season/episode - отримуємо eps структуру + S01E01)
- ///
- public async Task GetTvSeries(long tmdbId)
- {
- string memKey = $"StreamData:tv:{tmdbId}";
- if (_hybridCache.TryGetValue(memKey, out StreamDataResponse cached))
- return cached;
-
- try
- {
- string url = $"{API_BASE}?tmdb={tmdbId}&type=tv";
- _onLog?.Invoke($"StreamData tv: {url}");
-
- string json = await ApiGet(url);
- if (string.IsNullOrEmpty(json))
- return null;
-
- var response = JsonConvert.DeserializeObject(json);
- if (response?.status_code != "200" || response?.data?.eps == null || response.data.eps.Count == 0)
- return null;
-
- _hybridCache.Set(memKey, response, cacheTime(30, init: _init));
- return response;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"StreamData tv error: {ex.Message}");
- return null;
- }
- }
-
- ///
- /// Отримати стріми для конкретного епізоду
- ///
- public async Task GetEpisode(long tmdbId, int season, int episode)
- {
- string memKey = $"StreamData:ep:{tmdbId}:s{season}e{episode}";
- if (_hybridCache.TryGetValue(memKey, out StreamDataResponse cached))
- return cached;
-
- try
- {
- string url = $"{API_BASE}?tmdb={tmdbId}&type=tv&season={season}&episode={episode}";
- _onLog?.Invoke($"StreamData episode: {url}");
-
- string json = await ApiGet(url);
- if (string.IsNullOrEmpty(json))
- return null;
-
- var response = JsonConvert.DeserializeObject(json);
- if (response?.status_code != "200" || response?.data?.stream_urls == null || response.data.stream_urls.Count == 0)
- return null;
-
- _hybridCache.Set(memKey, response, cacheTime(30, init: _init));
- return response;
- }
- catch (Exception ex)
- {
- _onLog?.Invoke($"StreamData episode error: {ex.Message}");
- return null;
- }
- }
-
- private Task ApiGet(string url)
- {
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", "https://brightpathsignals.com/"),
- new HeadersModel("X-Requested-With", "XMLHttpRequest")
- };
-
- if (_httpHydra != null)
- return _httpHydra.Get(url, newheaders: headers);
-
- return Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
- }
-
- public static TimeSpan cacheTime(int multiaccess, int home = 5, int mikrotik = 2, OnlinesSettings init = null, int rhub = -1)
- {
- if (init != null && init.rhub && rhub != -1)
- return TimeSpan.FromMinutes(rhub);
-
- int ctime = init != null && init.cache_time > 0 ? init.cache_time : multiaccess;
- if (ctime > multiaccess)
- ctime = multiaccess;
-
- return TimeSpan.FromMinutes(ctime);
- }
- }
-}
diff --git a/LME.StreamData/manifest.json b/LME.StreamData/manifest.json
deleted file mode 100644
index 7b2fd84..0000000
--- a/LME.StreamData/manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "enable": true,
- "version": 3,
- "initspace": "LME.StreamData.ModInit",
- "online": "LME.StreamData.OnlineApi",
- "syntaxPaths": [
- "../LME.Shared/GlobalUsings.cs",
- "../LME.Shared/Online/OnlineRegistry.cs",
- "../LME.Shared/Update/ModuleUpdateService.cs",
- "../LME.Shared/Apn/ApnHelper.cs"
- ]
-}