mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-06-17 12:08:54 +00:00
feat(streamdata): add new StreamData online provider module
Introduce a full LME.StreamData module with initialization, manifest, and online registration to expose StreamData as a new content source. Implement TMDB-based movie, series, and episode retrieval flows with controller endpoints, API invoke client, proxy-aware requests, caching, and subtitle/stream mapping for playback templates.
This commit is contained in:
parent
79f75ef168
commit
3d47e802f1
294
LME.StreamData/Controller.cs
Normal file
294
LME.StreamData/Controller.cs
Normal file
@ -0,0 +1,294 @@
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Головний ендпоінт модуля StreamData
|
||||
/// Працює виключно через TMDB ID (параметр id)
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[Route("lite/lme_streamdata")]
|
||||
async public Task<ActionResult> Index(long id, string imdb_id, long kinopoisk_id, string title, string original_title, string original_language, int year, string source, int serial, string account_email, string t, int s = -1, int e = -1, bool play = false, bool rjson = false, string href = null, bool checksearch = false)
|
||||
{
|
||||
await UpdateService.ConnectAsync(host);
|
||||
|
||||
var init = 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);
|
||||
}
|
||||
|
||||
// Фільм
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обробка фільму: отримуємо всі stream_urls та показуємо їх
|
||||
/// </summary>
|
||||
private async Task<ActionResult> 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 hostname = StreamDataInvoke.ExtractHostname(streamUrl);
|
||||
string label = streamUrls.Count > 1 ? $"Джерело {index} ({hostname})" : hostname;
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обробка серіалу: eps структура → сезони → епізоди → play
|
||||
/// </summary>
|
||||
private async Task<ActionResult> 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);
|
||||
|
||||
// Якщо сезон не вибрано — показуємо список сезонів
|
||||
if (s <= 0)
|
||||
{
|
||||
var displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title);
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
// Якщо вибрано сезон але не вибрано епізод — показуємо епізоди
|
||||
string seasonKey = s.ToString();
|
||||
if (!eps.ContainsKey(seasonKey) || eps[seasonKey] == null || eps[seasonKey].Count == 0)
|
||||
return OnError("lme_streamdata", refresh_proxy: true);
|
||||
|
||||
if (e <= 0)
|
||||
{
|
||||
var episodes = eps[seasonKey]
|
||||
.Select(ep => int.TryParse(ep, out int en) ? en : 0)
|
||||
.Where(en => en > 0)
|
||||
.OrderBy(en => en)
|
||||
.ToList();
|
||||
|
||||
if (episodes.Count == 0)
|
||||
return OnError("lme_streamdata", refresh_proxy: true);
|
||||
|
||||
var displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title);
|
||||
var episodeTpl = new EpisodeTpl(episodes.Count);
|
||||
|
||||
foreach (var episode in episodes)
|
||||
{
|
||||
string episodeName = $"Епізод {episode}";
|
||||
string callUrl = $"{host}/lite/lme_streamdata?id={tmdbId}&serial=1&s={s}&e={episode}&play=true&title={HttpUtility.UrlEncode(displayTitle)}&original_title={HttpUtility.UrlEncode(originalTitle)}";
|
||||
episodeTpl.Append(episodeName, displayTitle, s.ToString(), episode.ToString("D2"), accsArgs(callUrl), "call");
|
||||
}
|
||||
|
||||
return Content(
|
||||
rjson ? episodeTpl.ToJson() : episodeTpl.ToHtml(),
|
||||
rjson ? "application/json; charset=utf-8" : "text/html; charset=utf-8"
|
||||
);
|
||||
}
|
||||
|
||||
// Якщо є play=true з s та e — це вже оброблено вище в play-гілці
|
||||
// Сюди не дійдемо, але на всяк випадок:
|
||||
return OnError("lme_streamdata", refresh_proxy: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обробка play-запиту: API з season/episode → JSON зі стрімом
|
||||
/// </summary>
|
||||
private async Task<ActionResult> HandlePlay(StreamDataInvoke invoke, OnlinesSettings init, long tmdbId, int season, int episode, string title, string originalTitle)
|
||||
{
|
||||
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 firstUrl = response.data.stream_urls.FirstOrDefault(u => !string.IsNullOrWhiteSpace(u));
|
||||
if (string.IsNullOrEmpty(firstUrl))
|
||||
return OnError("lme_streamdata", refresh_proxy: true);
|
||||
|
||||
var subs = CollectSubtitles(response);
|
||||
string displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title);
|
||||
string streamUrl = BuildStreamUrl(init, firstUrl);
|
||||
|
||||
return UpdateService.Validate(Content(
|
||||
VideoTpl.ToJson("play", streamUrl, displayTitle, subtitles: subs),
|
||||
"application/json; charset=utf-8"
|
||||
));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Зібрати субтитри з відповіді API
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
LME.StreamData/ModInit.cs
Normal file
90
LME.StreamData/ModInit.cs
Normal file
@ -0,0 +1,90 @@
|
||||
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.0;
|
||||
|
||||
public static OnlinesSettings StreamDataSettings;
|
||||
|
||||
public static OnlinesSettings Settings
|
||||
{
|
||||
get => StreamDataSettings;
|
||||
set => StreamDataSettings = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Модуль завантажено
|
||||
/// </summary>
|
||||
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<OnlinesSettings>();
|
||||
if (hasApn)
|
||||
ApnHelper.ApplyInitConf(apnEnabled, apnHost, StreamDataSettings, useDefaultHostWhenEmpty: true);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
31
LME.StreamData/Models/StreamDataModels.cs
Normal file
31
LME.StreamData/Models/StreamDataModels.cs
Normal file
@ -0,0 +1,31 @@
|
||||
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<SubtitleItem> 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<string, List<string>> eps { get; set; }
|
||||
public string file_name { get; set; }
|
||||
public string backdrop { get; set; }
|
||||
public List<string> stream_urls { get; set; }
|
||||
public string thumbnails_url { get; set; }
|
||||
}
|
||||
}
|
||||
28
LME.StreamData/OnlineApi.cs
Normal file
28
LME.StreamData/OnlineApi.cs
Normal file
@ -0,0 +1,28 @@
|
||||
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<ModuleOnlineItem> Invoke(HttpContext httpContext, RequestModel requestInfo, string host, OnlineEventsModel args)
|
||||
{
|
||||
var online = new List<ModuleOnlineItem>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
LME.StreamData/StreamData.csproj
Normal file
15
LME.StreamData/StreamData.csproj
Normal file
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<OutputType>library</OutputType>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Shared">
|
||||
<HintPath>..\..\Shared.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
175
LME.StreamData/StreamDataInvoke.cs
Normal file
175
LME.StreamData/StreamDataInvoke.cs
Normal file
@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
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<string> _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<string> onLog, ProxyManager proxyManager, HttpHydra httpHydra = null)
|
||||
{
|
||||
_init = init;
|
||||
_hybridCache = hybridCache;
|
||||
_onLog = onLog;
|
||||
_proxyManager = proxyManager;
|
||||
_httpHydra = httpHydra;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отримати дані для фільму за TMDB ID
|
||||
/// </summary>
|
||||
public async Task<StreamDataResponse> 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<StreamDataResponse>(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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отримати дані для серіалу (без season/episode - отримуємо eps структуру + S01E01)
|
||||
/// </summary>
|
||||
public async Task<StreamDataResponse> 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<StreamDataResponse>(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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отримати стріми для конкретного епізоду
|
||||
/// </summary>
|
||||
public async Task<StreamDataResponse> 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<StreamDataResponse>(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<string> ApiGet(string url)
|
||||
{
|
||||
var headers = new List<HeadersModel>()
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Витягнути назву хоста з URL для відображення джерела
|
||||
/// </summary>
|
||||
public static string ExtractHostname(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return "невідомо";
|
||||
|
||||
try
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
return uri.Host;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "невідомо";
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
LME.StreamData/manifest.json
Normal file
12
LME.StreamData/manifest.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user