mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-04-16 09:22:21 +00:00
feat(makhno): add Makhno online streaming module
Integrate Makhno video streaming service with support for movies and serials. The module provides search functionality, player data retrieval, and streaming capabilities through multiple external APIs including Wormhole, Ashdi, and UaTUT. Features include: - HTTP controller for handling playback requests - Support for multiple voice translations and seasons - Proxy management and caching - TMDB integration for metadata enrichment - Online API integration for event handling
This commit is contained in:
parent
02398f3ea5
commit
5f40e1781f
403
Makhno/Controller.cs
Normal file
403
Makhno/Controller.cs
Normal file
@ -0,0 +1,403 @@
|
||||
using Shared.Engine;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Shared;
|
||||
using Shared.Models.Templates;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models;
|
||||
using Makhno.Models;
|
||||
|
||||
namespace Makhno
|
||||
{
|
||||
[Route("makhno")]
|
||||
public class MakhnoController : BaseOnlineController
|
||||
{
|
||||
private readonly ProxyManager proxyManager;
|
||||
|
||||
public MakhnoController() : base(ModInit.Settings)
|
||||
{
|
||||
proxyManager = new ProxyManager(ModInit.Makhno);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async 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 season = -1, bool rjson = false)
|
||||
{
|
||||
await UpdateService.ConnectAsync(host);
|
||||
|
||||
var init = await loadKit(ModInit.Makhno);
|
||||
if (!init.enable)
|
||||
return OnError();
|
||||
Initialization(init);
|
||||
|
||||
OnLog($"Makhno: {title} (serial={serial}, s={s}, season={season}, t={t})");
|
||||
|
||||
var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager);
|
||||
|
||||
var resolved = await ResolvePlaySource(imdb_id, title, original_title, year, serial, invoke);
|
||||
if (resolved == null || string.IsNullOrEmpty(resolved.PlayUrl))
|
||||
return OnError();
|
||||
|
||||
if (resolved.ShouldEnrich)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await EnrichWormhole(imdb_id, title, original_title, year, resolved, invoke);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnLog($"Makhno wormhole enrich failed: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (resolved.IsSerial)
|
||||
return await HandleSerial(resolved.PlayUrl, imdb_id, title, original_title, year, t, season, rjson, invoke);
|
||||
|
||||
return await HandleMovie(resolved.PlayUrl, imdb_id, title, original_title, year, rjson, invoke);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("play")]
|
||||
public async Task<ActionResult> Play(long id, string imdb_id, long kinopoisk_id, string title, string original_title, int year, int s, int season, string t, string episodeId, bool play = false, bool rjson = false)
|
||||
{
|
||||
await UpdateService.ConnectAsync(host);
|
||||
|
||||
var init = await loadKit(ModInit.Makhno);
|
||||
if (!init.enable)
|
||||
return OnError();
|
||||
Initialization(init);
|
||||
|
||||
OnLog($"Makhno Play: {title} (s={s}, season={season}, t={t}, episodeId={episodeId}) play={play}");
|
||||
|
||||
var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager);
|
||||
var resolved = await ResolvePlaySource(imdb_id, title, original_title, year, serial: 1, invoke);
|
||||
if (resolved == null || string.IsNullOrEmpty(resolved.PlayUrl))
|
||||
return OnError();
|
||||
|
||||
var playerData = await InvokeCache<PlayerData>($"makhno:player:{resolved.PlayUrl}", TimeSpan.FromMinutes(10), async () =>
|
||||
{
|
||||
return await invoke.GetPlayerData(resolved.PlayUrl);
|
||||
});
|
||||
|
||||
if (playerData?.Voices == null || !playerData.Voices.Any())
|
||||
return OnError();
|
||||
|
||||
if (string.IsNullOrEmpty(t) || !int.TryParse(t, out int voiceIndex) || voiceIndex >= playerData.Voices.Count)
|
||||
return OnError();
|
||||
|
||||
var selectedVoice = playerData.Voices[voiceIndex];
|
||||
if (season < 0 || season >= selectedVoice.Seasons.Count)
|
||||
return OnError();
|
||||
|
||||
var selectedSeason = selectedVoice.Seasons[season];
|
||||
foreach (var episode in selectedSeason.Episodes)
|
||||
{
|
||||
if (episode.Id == episodeId && !string.IsNullOrEmpty(episode.File))
|
||||
{
|
||||
OnLog($"Makhno Play: Found episode {episode.Title}, stream: {episode.File}");
|
||||
|
||||
string streamUrl = BuildStreamUrl(init, episode.File);
|
||||
string episodeTitle = $"{title ?? original_title} - {episode.Title}";
|
||||
|
||||
if (play)
|
||||
return UpdateService.Validate(Redirect(streamUrl));
|
||||
|
||||
return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, episodeTitle), "application/json; charset=utf-8"));
|
||||
}
|
||||
}
|
||||
|
||||
OnLog("Makhno Play: Episode not found");
|
||||
return OnError();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("play/movie")]
|
||||
public async Task<ActionResult> PlayMovie(long id, string imdb_id, string title, string original_title, int year, bool play = false, bool rjson = false)
|
||||
{
|
||||
await UpdateService.ConnectAsync(host);
|
||||
|
||||
var init = await loadKit(ModInit.Makhno);
|
||||
if (!init.enable)
|
||||
return OnError();
|
||||
Initialization(init);
|
||||
|
||||
OnLog($"Makhno PlayMovie: {title} ({year}) play={play}");
|
||||
|
||||
var invoke = new MakhnoInvoke(init, hybridCache, OnLog, proxyManager);
|
||||
var resolved = await ResolvePlaySource(imdb_id, title, original_title, year, serial: 0, invoke);
|
||||
if (resolved == null || string.IsNullOrEmpty(resolved.PlayUrl))
|
||||
return OnError();
|
||||
|
||||
var playerData = await InvokeCache<PlayerData>($"makhno:player:{resolved.PlayUrl}", TimeSpan.FromMinutes(10), async () =>
|
||||
{
|
||||
return await invoke.GetPlayerData(resolved.PlayUrl);
|
||||
});
|
||||
|
||||
if (playerData?.File == null)
|
||||
return OnError();
|
||||
|
||||
string streamUrl = BuildStreamUrl(init, playerData.File);
|
||||
|
||||
if (play)
|
||||
return UpdateService.Validate(Redirect(streamUrl));
|
||||
|
||||
return UpdateService.Validate(Content(VideoTpl.ToJson("play", streamUrl, title ?? original_title), "application/json; charset=utf-8"));
|
||||
}
|
||||
|
||||
private async Task<ActionResult> HandleMovie(string playUrl, string imdb_id, string title, string original_title, int year, bool rjson, MakhnoInvoke invoke)
|
||||
{
|
||||
var init = ModInit.Makhno;
|
||||
var playerData = await InvokeCache<PlayerData>($"makhno:player:{playUrl}", TimeSpan.FromMinutes(10), async () =>
|
||||
{
|
||||
return await invoke.GetPlayerData(playUrl);
|
||||
});
|
||||
|
||||
if (playerData?.File == null)
|
||||
return OnError();
|
||||
|
||||
string movieLink = $"{host}/makhno/play/movie?imdb_id={HttpUtility.UrlEncode(imdb_id)}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&play=true";
|
||||
var tpl = new MovieTpl(title ?? original_title, original_title, 1);
|
||||
tpl.Append(title ?? original_title, accsArgs(movieLink), method: "play");
|
||||
|
||||
return rjson ? Content(tpl.ToJson(), "application/json; charset=utf-8") : Content(tpl.ToHtml(), "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
private async Task<ActionResult> HandleSerial(string playUrl, string imdb_id, string title, string original_title, int year, string t, int season, bool rjson, MakhnoInvoke invoke)
|
||||
{
|
||||
var init = ModInit.Makhno;
|
||||
|
||||
var playerData = await InvokeCache<PlayerData>($"makhno:player:{playUrl}", TimeSpan.FromMinutes(10), async () =>
|
||||
{
|
||||
return await invoke.GetPlayerData(playUrl);
|
||||
});
|
||||
|
||||
if (playerData?.Voices == null || !playerData.Voices.Any())
|
||||
return OnError();
|
||||
|
||||
if (season == -1)
|
||||
{
|
||||
var firstVoice = playerData.Voices.First();
|
||||
var season_tpl = new SeasonTpl();
|
||||
for (int i = 0; i < firstVoice.Seasons.Count; i++)
|
||||
{
|
||||
var seasonItem = firstVoice.Seasons[i];
|
||||
string seasonName = seasonItem.Title ?? $"Сезон {i + 1}";
|
||||
string link = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={i}";
|
||||
season_tpl.Append(seasonName, link, i.ToString());
|
||||
}
|
||||
|
||||
return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
if (season < 0 || season >= playerData.Voices.First().Seasons.Count)
|
||||
return OnError();
|
||||
|
||||
var voice_tpl = new VoiceTpl();
|
||||
var episode_tpl = new EpisodeTpl();
|
||||
|
||||
string selectedVoice = t;
|
||||
if (string.IsNullOrEmpty(selectedVoice) && playerData.Voices.Any())
|
||||
{
|
||||
selectedVoice = "0";
|
||||
}
|
||||
|
||||
for (int i = 0; i < playerData.Voices.Count; i++)
|
||||
{
|
||||
var voice = playerData.Voices[i];
|
||||
string voiceName = voice.Name ?? $"Озвучка {i + 1}";
|
||||
string voiceLink = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={season}&t={i}";
|
||||
bool isActive = selectedVoice == i.ToString();
|
||||
voice_tpl.Append(voiceName, isActive, voiceLink);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(selectedVoice) && int.TryParse(selectedVoice, out int voiceIndex) && voiceIndex < playerData.Voices.Count)
|
||||
{
|
||||
var selectedVoiceData = playerData.Voices[voiceIndex];
|
||||
if (season < selectedVoiceData.Seasons.Count)
|
||||
{
|
||||
var selectedSeason = selectedVoiceData.Seasons[season];
|
||||
var sortedEpisodes = selectedSeason.Episodes.OrderBy(e => ExtractEpisodeNumber(e.Title)).ToList();
|
||||
|
||||
for (int i = 0; i < sortedEpisodes.Count; i++)
|
||||
{
|
||||
var episode = sortedEpisodes[i];
|
||||
if (!string.IsNullOrEmpty(episode.File))
|
||||
{
|
||||
string episodeLink = $"{host}/makhno/play?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&season={season}&t={selectedVoice}&episodeId={episode.Id}";
|
||||
episode_tpl.Append(episode.Title, title ?? original_title, season.ToString(), (i + 1).ToString("D2"), episodeLink, "call");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
episode_tpl.Append(voice_tpl);
|
||||
if (rjson)
|
||||
return Content(episode_tpl.ToJson(), "application/json; charset=utf-8");
|
||||
|
||||
return Content(episode_tpl.ToHtml(), "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
private int ExtractEpisodeNumber(string title)
|
||||
{
|
||||
if (string.IsNullOrEmpty(title))
|
||||
return 0;
|
||||
|
||||
var match = System.Text.RegularExpressions.Regex.Match(title, @"(\d+)");
|
||||
return match.Success ? int.Parse(match.Groups[1].Value) : 0;
|
||||
}
|
||||
|
||||
private async Task<ResolveResult> ResolvePlaySource(string imdbId, string title, string originalTitle, int year, int serial, MakhnoInvoke invoke)
|
||||
{
|
||||
string playUrl = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
string cacheKey = $"makhno:wormhole:{imdbId}";
|
||||
playUrl = await InvokeCache<string>(cacheKey, TimeSpan.FromMinutes(5), async () =>
|
||||
{
|
||||
return await invoke.GetWormholePlay(imdbId);
|
||||
});
|
||||
|
||||
if (!string.IsNullOrEmpty(playUrl))
|
||||
{
|
||||
return new ResolveResult
|
||||
{
|
||||
PlayUrl = playUrl,
|
||||
IsSerial = IsSerialByUrl(playUrl, serial),
|
||||
ShouldEnrich = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
string searchQuery = originalTitle ?? title;
|
||||
string searchCacheKey = $"makhno:uatut:search:{imdbId ?? searchQuery}";
|
||||
|
||||
var searchResults = await InvokeCache<List<SearchResult>>(searchCacheKey, TimeSpan.FromMinutes(10), async () =>
|
||||
{
|
||||
return await invoke.SearchUaTUT(searchQuery, imdbId);
|
||||
});
|
||||
|
||||
if (searchResults == null || searchResults.Count == 0)
|
||||
return null;
|
||||
|
||||
var selected = invoke.SelectUaTUTItem(searchResults, imdbId, year > 0 ? year : null, title, originalTitle);
|
||||
if (selected == null)
|
||||
return null;
|
||||
|
||||
var ashdiPath = await InvokeCache<string>($"makhno:ashdi:{selected.Id}", TimeSpan.FromMinutes(10), async () =>
|
||||
{
|
||||
return await invoke.GetAshdiPath(selected.Id);
|
||||
});
|
||||
|
||||
if (string.IsNullOrEmpty(ashdiPath))
|
||||
return null;
|
||||
|
||||
playUrl = invoke.BuildAshdiUrl(ashdiPath);
|
||||
|
||||
bool isSerial = serial == 1 || IsSerialByCategory(selected.Category) || IsSerialByUrl(playUrl, serial);
|
||||
|
||||
return new ResolveResult
|
||||
{
|
||||
PlayUrl = playUrl,
|
||||
AshdiPath = ashdiPath,
|
||||
Selected = selected,
|
||||
IsSerial = isSerial,
|
||||
ShouldEnrich = true
|
||||
};
|
||||
}
|
||||
|
||||
private bool IsSerialByCategory(string category)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(category))
|
||||
return false;
|
||||
|
||||
return category.Equals("Серіал", StringComparison.OrdinalIgnoreCase)
|
||||
|| category.Equals("Аніме", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private bool IsSerialByUrl(string url, int serial)
|
||||
{
|
||||
if (serial == 1)
|
||||
return true;
|
||||
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return false;
|
||||
|
||||
return url.Contains("/serial/", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private async Task EnrichWormhole(string imdbId, string title, string originalTitle, int year, ResolveResult resolved, MakhnoInvoke invoke)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(imdbId) || resolved?.Selected == null || string.IsNullOrWhiteSpace(resolved.AshdiPath))
|
||||
return;
|
||||
|
||||
int? yearValue = year > 0 ? year : null;
|
||||
if (!yearValue.HasValue && int.TryParse(resolved.Selected.Year, out int parsedYear))
|
||||
yearValue = parsedYear;
|
||||
|
||||
var tmdbResult = await invoke.FetchTmdbByImdb(imdbId, yearValue);
|
||||
if (tmdbResult == null)
|
||||
return;
|
||||
|
||||
var (item, mediaType) = tmdbResult.Value;
|
||||
var tmdbId = item.Value<long?>("id");
|
||||
if (!tmdbId.HasValue)
|
||||
return;
|
||||
|
||||
string original = item.Value<string>("original_title")
|
||||
?? item.Value<string>("original_name")
|
||||
?? resolved.Selected.TitleEn
|
||||
?? originalTitle
|
||||
?? title;
|
||||
|
||||
string resultTitle = resolved.Selected.Title
|
||||
?? item.Value<string>("title")
|
||||
?? item.Value<string>("name");
|
||||
|
||||
var payload = new
|
||||
{
|
||||
imdb_id = imdbId,
|
||||
_id = $"{mediaType}:{tmdbId.Value}",
|
||||
original_title = original,
|
||||
title = resultTitle,
|
||||
serial = mediaType == "tv" ? 1 : 0,
|
||||
ashdi = resolved.AshdiPath,
|
||||
year = (resolved.Selected.Year ?? yearValue?.ToString())
|
||||
};
|
||||
|
||||
await invoke.PostWormholeAsync(payload);
|
||||
}
|
||||
|
||||
private string BuildStreamUrl(OnlinesSettings init, string streamLink)
|
||||
{
|
||||
string link = accsArgs(streamLink);
|
||||
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 class ResolveResult
|
||||
{
|
||||
public string PlayUrl { get; set; }
|
||||
public string AshdiPath { get; set; }
|
||||
public SearchResult Selected { get; set; }
|
||||
public bool IsSerial { get; set; }
|
||||
public bool ShouldEnrich { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Makhno/Makhno.csproj
Normal file
15
Makhno/Makhno.csproj
Normal file
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>library</OutputType>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Shared">
|
||||
<HintPath>..\..\Shared.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
592
Makhno/MakhnoInvoke.cs
Normal file
592
Makhno/MakhnoInvoke.cs
Normal file
@ -0,0 +1,592 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared;
|
||||
using Shared.Engine;
|
||||
using Shared.Models;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Makhno.Models;
|
||||
|
||||
namespace Makhno
|
||||
{
|
||||
public class MakhnoInvoke
|
||||
{
|
||||
private const string WormholeHost = "http://wormhole.lampame.v6.rocks/";
|
||||
private const string AshdiHost = "https://ashdi.vip";
|
||||
|
||||
private readonly OnlinesSettings _init;
|
||||
private readonly IHybridCache _hybridCache;
|
||||
private readonly Action<string> _onLog;
|
||||
private readonly ProxyManager _proxyManager;
|
||||
|
||||
public MakhnoInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
|
||||
{
|
||||
_init = init;
|
||||
_hybridCache = hybridCache;
|
||||
_onLog = onLog;
|
||||
_proxyManager = proxyManager;
|
||||
}
|
||||
|
||||
public async Task<string> GetWormholePlay(string imdbId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(imdbId))
|
||||
return null;
|
||||
|
||||
string url = $"{WormholeHost}?imdb_id={imdbId}";
|
||||
try
|
||||
{
|
||||
var headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", Http.UserAgent)
|
||||
};
|
||||
|
||||
string response = await Http.Get(url, timeoutSeconds: 4, headers: headers, proxy: _proxyManager.Get());
|
||||
if (string.IsNullOrWhiteSpace(response))
|
||||
return null;
|
||||
|
||||
var payload = JsonConvert.DeserializeObject<WormholeResponse>(response);
|
||||
return string.IsNullOrWhiteSpace(payload?.play) ? null : payload.play;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Makhno wormhole error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SearchResult>> SearchUaTUT(string query, string imdbId = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
string searchUrl = $"{_init.apihost}/search.php";
|
||||
|
||||
if (!string.IsNullOrEmpty(imdbId))
|
||||
{
|
||||
var imdbResults = await PerformSearch(searchUrl, imdbId);
|
||||
if (imdbResults?.Any() == true)
|
||||
return imdbResults;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
{
|
||||
var titleResults = await PerformSearch(searchUrl, query);
|
||||
return titleResults ?? new List<SearchResult>();
|
||||
}
|
||||
|
||||
return new List<SearchResult>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Makhno UaTUT search error: {ex.Message}");
|
||||
return new List<SearchResult>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<SearchResult>> PerformSearch(string searchUrl, string query)
|
||||
{
|
||||
string url = $"{searchUrl}?q={WebUtility.UrlEncode(query)}";
|
||||
_onLog($"Makhno UaTUT searching: {url}");
|
||||
|
||||
var headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", Http.UserAgent)
|
||||
};
|
||||
|
||||
var response = await Http.Get(url, headers: headers, proxy: _proxyManager.Get());
|
||||
|
||||
if (string.IsNullOrEmpty(response))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var results = JsonConvert.DeserializeObject<List<SearchResult>>(response);
|
||||
_onLog($"Makhno UaTUT found {results?.Count ?? 0} results for query: {query}");
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Makhno UaTUT parse error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetMoviePageContent(string movieId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = $"{_init.apihost}/{movieId}";
|
||||
_onLog($"Makhno UaTUT getting movie page: {url}");
|
||||
|
||||
var headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", Http.UserAgent)
|
||||
};
|
||||
var response = await Http.Get(url, headers: headers, proxy: _proxyManager.Get());
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Makhno UaTUT GetMoviePageContent error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetPlayerUrl(string moviePageContent)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(moviePageContent))
|
||||
return null;
|
||||
|
||||
var match = Regex.Match(moviePageContent, @"<iframe[^>]*id=[""']vip-player[""'][^>]*src=[""']([^""']+)", RegexOptions.IgnoreCase);
|
||||
if (match.Success)
|
||||
return NormalizePlayerUrl(match.Groups[1].Value);
|
||||
|
||||
match = Regex.Match(moviePageContent, @"<iframe[^>]*id=[""']alt-player[""'][^>]*src=[""']([^""']+)", RegexOptions.IgnoreCase);
|
||||
if (match.Success)
|
||||
return NormalizePlayerUrl(match.Groups[1].Value);
|
||||
|
||||
var iframeMatches = Regex.Matches(moviePageContent, @"<iframe[^>]*(?:id=[""']([^""']+)[""'])?[^>]*src=[""']([^""']+)[""']", RegexOptions.IgnoreCase);
|
||||
foreach (Match iframe in iframeMatches)
|
||||
{
|
||||
string iframeId = iframe.Groups[1].Value?.ToLowerInvariant();
|
||||
string src = iframe.Groups[2].Value;
|
||||
if (string.IsNullOrEmpty(src))
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(iframeId) && iframeId.Contains("player"))
|
||||
return NormalizePlayerUrl(src);
|
||||
|
||||
if (src.Contains("ashdi.vip", StringComparison.OrdinalIgnoreCase) ||
|
||||
src.Contains("zetvideo.net", StringComparison.OrdinalIgnoreCase) ||
|
||||
src.Contains("player", StringComparison.OrdinalIgnoreCase))
|
||||
return NormalizePlayerUrl(src);
|
||||
}
|
||||
|
||||
var urlMatch = Regex.Match(moviePageContent, @"(https?://[^\"'\s>]+/(?:vod|serial)/\d+[^\"'\s>]*)", RegexOptions.IgnoreCase);
|
||||
if (urlMatch.Success)
|
||||
return NormalizePlayerUrl(urlMatch.Groups[1].Value);
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Makhno UaTUT GetPlayerUrl error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string NormalizePlayerUrl(string src)
|
||||
{
|
||||
if (string.IsNullOrEmpty(src))
|
||||
return null;
|
||||
|
||||
if (src.StartsWith("//"))
|
||||
return $"https:{src}";
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
public async Task<PlayerData> GetPlayerData(string playerUrl)
|
||||
{
|
||||
if (string.IsNullOrEmpty(playerUrl))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
string requestUrl = playerUrl;
|
||||
var headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", Http.UserAgent)
|
||||
};
|
||||
|
||||
if (playerUrl.Contains("ashdi.vip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
headers.Add(new HeadersModel("Referer", "https://ashdi.vip/"));
|
||||
}
|
||||
|
||||
if (ApnHelper.IsAshdiUrl(playerUrl) && ApnHelper.IsEnabled(_init))
|
||||
requestUrl = ApnHelper.WrapUrl(_init, playerUrl);
|
||||
|
||||
_onLog($"Makhno getting player data from: {requestUrl}");
|
||||
|
||||
var response = await Http.Get(requestUrl, headers: headers, proxy: _proxyManager.Get());
|
||||
if (string.IsNullOrEmpty(response))
|
||||
return null;
|
||||
|
||||
return ParsePlayerData(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Makhno GetPlayerData error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private PlayerData ParsePlayerData(string html)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(html))
|
||||
return null;
|
||||
|
||||
var fileMatch = Regex.Match(html, @"file:'([^']+)'", RegexOptions.IgnoreCase);
|
||||
if (!fileMatch.Success)
|
||||
fileMatch = Regex.Match(html, @"file:\s*\"([^\"]+)\"", RegexOptions.IgnoreCase);
|
||||
|
||||
if (fileMatch.Success && !fileMatch.Groups[1].Value.StartsWith("["))
|
||||
{
|
||||
var posterMatch = Regex.Match(html, @"poster:[\"']([^\"']+)[\"']", RegexOptions.IgnoreCase);
|
||||
return new PlayerData
|
||||
{
|
||||
File = fileMatch.Groups[1].Value,
|
||||
Poster = posterMatch.Success ? posterMatch.Groups[1].Value : null,
|
||||
Voices = new List<Voice>()
|
||||
};
|
||||
}
|
||||
|
||||
var m3u8Match = Regex.Match(html, @"(https?://[^\"'\s>]+\.m3u8[^\"'\s>]*)", RegexOptions.IgnoreCase);
|
||||
if (m3u8Match.Success)
|
||||
{
|
||||
return new PlayerData
|
||||
{
|
||||
File = m3u8Match.Groups[1].Value,
|
||||
Poster = null,
|
||||
Voices = new List<Voice>()
|
||||
};
|
||||
}
|
||||
|
||||
var sourceMatch = Regex.Match(html, @"<source[^>]*src=[\"']([^\"']+)[\"']", RegexOptions.IgnoreCase);
|
||||
if (sourceMatch.Success)
|
||||
{
|
||||
return new PlayerData
|
||||
{
|
||||
File = sourceMatch.Groups[1].Value,
|
||||
Poster = null,
|
||||
Voices = new List<Voice>()
|
||||
};
|
||||
}
|
||||
|
||||
var jsonMatch = Regex.Match(html, @"file:'(\[.*?\])'", RegexOptions.Singleline);
|
||||
if (jsonMatch.Success)
|
||||
{
|
||||
string jsonData = jsonMatch.Groups[1].Value
|
||||
.Replace("\\'", "'")
|
||||
.Replace("\\\"", "\"");
|
||||
|
||||
return new PlayerData
|
||||
{
|
||||
File = null,
|
||||
Poster = null,
|
||||
Voices = ParseVoicesJson(jsonData)
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Makhno ParsePlayerData error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Voice> ParseVoicesJson(string jsonData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var voicesArray = JsonConvert.DeserializeObject<List<JObject>>(jsonData);
|
||||
var voices = new List<Voice>();
|
||||
|
||||
if (voicesArray == null)
|
||||
return voices;
|
||||
|
||||
foreach (var voiceGroup in voicesArray)
|
||||
{
|
||||
var voice = new Voice
|
||||
{
|
||||
Name = voiceGroup["title"]?.ToString(),
|
||||
Seasons = new List<Season>()
|
||||
};
|
||||
|
||||
var seasons = voiceGroup["folder"] as JArray;
|
||||
if (seasons != null)
|
||||
{
|
||||
foreach (var seasonGroup in seasons)
|
||||
{
|
||||
string seasonTitle = seasonGroup["title"]?.ToString() ?? string.Empty;
|
||||
var episodes = new List<Episode>();
|
||||
|
||||
var episodesArray = seasonGroup["folder"] as JArray;
|
||||
if (episodesArray != null)
|
||||
{
|
||||
foreach (var episode in episodesArray)
|
||||
{
|
||||
episodes.Add(new Episode
|
||||
{
|
||||
Id = episode["id"]?.ToString(),
|
||||
Title = episode["title"]?.ToString(),
|
||||
File = episode["file"]?.ToString(),
|
||||
Poster = episode["poster"]?.ToString(),
|
||||
Subtitle = episode["subtitle"]?.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
episodes = episodes
|
||||
.OrderBy(item => ExtractEpisodeNumber(item.Title) is null)
|
||||
.ThenBy(item => ExtractEpisodeNumber(item.Title) ?? 0)
|
||||
.ToList();
|
||||
|
||||
voice.Seasons.Add(new Season
|
||||
{
|
||||
Title = seasonTitle,
|
||||
Episodes = episodes
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
voices.Add(voice);
|
||||
}
|
||||
|
||||
return voices;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Makhno ParseVoicesJson error: {ex.Message}");
|
||||
return new List<Voice>();
|
||||
}
|
||||
}
|
||||
|
||||
private int? ExtractEpisodeNumber(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return null;
|
||||
|
||||
var match = Regex.Match(value, @"(\d+)");
|
||||
if (!match.Success)
|
||||
return null;
|
||||
|
||||
if (int.TryParse(match.Groups[1].Value, out int num))
|
||||
return num;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string ExtractAshdiPath(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return null;
|
||||
|
||||
var match = Regex.Match(value, @"https?://(?:www\.)?ashdi\.vip/((?:vod|serial)/\d+)", RegexOptions.IgnoreCase);
|
||||
if (match.Success)
|
||||
return match.Groups[1].Value;
|
||||
|
||||
match = Regex.Match(value, @"\b((?:vod|serial)/\d+)\b", RegexOptions.IgnoreCase);
|
||||
if (match.Success)
|
||||
return match.Groups[1].Value;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string BuildAshdiUrl(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return null;
|
||||
|
||||
if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
return path;
|
||||
|
||||
return $"{AshdiHost}/{path.TrimStart('/')}";
|
||||
}
|
||||
|
||||
public async Task<string> GetAshdiPath(string movieId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(movieId))
|
||||
return null;
|
||||
|
||||
var page = await GetMoviePageContent(movieId);
|
||||
if (string.IsNullOrWhiteSpace(page))
|
||||
return null;
|
||||
|
||||
var playerUrl = GetPlayerUrl(page);
|
||||
var path = ExtractAshdiPath(playerUrl);
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
return path;
|
||||
|
||||
return ExtractAshdiPath(page);
|
||||
}
|
||||
|
||||
public SearchResult SelectUaTUTItem(List<SearchResult> items, string imdbId, int? year, string title, string titleEn)
|
||||
{
|
||||
if (items == null || items.Count == 0)
|
||||
return null;
|
||||
|
||||
var candidates = items.Where(item => ImdbMatch(item, imdbId) && YearMatch(item, year)).ToList();
|
||||
if (candidates.Count == 1)
|
||||
return candidates[0];
|
||||
if (candidates.Count > 1)
|
||||
return null;
|
||||
|
||||
candidates = items.Where(item => ImdbMatch(item, imdbId) && TitleMatch(item, title, titleEn)).ToList();
|
||||
if (candidates.Count == 1)
|
||||
return candidates[0];
|
||||
if (candidates.Count > 1)
|
||||
return null;
|
||||
|
||||
candidates = items.Where(item => YearMatch(item, year) && TitleMatch(item, title, titleEn)).ToList();
|
||||
if (candidates.Count == 1)
|
||||
return candidates[0];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool ImdbMatch(SearchResult item, string imdbId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(imdbId) || item == null)
|
||||
return false;
|
||||
|
||||
return string.Equals(item.ImdbId?.Trim(), imdbId.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private bool YearMatch(SearchResult item, int? year)
|
||||
{
|
||||
if (year == null || item == null)
|
||||
return false;
|
||||
|
||||
var itemYear = YearInt(item.Year);
|
||||
return itemYear.HasValue && itemYear.Value == year.Value;
|
||||
}
|
||||
|
||||
private bool TitleMatch(SearchResult item, string title, string titleEn)
|
||||
{
|
||||
if (item == null)
|
||||
return false;
|
||||
|
||||
string itemTitle = NormalizeTitle(item.Title);
|
||||
string itemTitleEn = NormalizeTitle(item.TitleEn);
|
||||
string targetTitle = NormalizeTitle(title);
|
||||
string targetTitleEn = NormalizeTitle(titleEn);
|
||||
|
||||
return (itemTitle.Length > 0 && targetTitle.Length > 0 && itemTitle == targetTitle)
|
||||
|| (itemTitle.Length > 0 && targetTitleEn.Length > 0 && itemTitle == targetTitleEn)
|
||||
|| (itemTitleEn.Length > 0 && targetTitle.Length > 0 && itemTitleEn == targetTitle)
|
||||
|| (itemTitleEn.Length > 0 && targetTitleEn.Length > 0 && itemTitleEn == targetTitleEn);
|
||||
}
|
||||
|
||||
private string NormalizeTitle(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return string.Empty;
|
||||
|
||||
string text = value.ToLowerInvariant();
|
||||
text = Regex.Replace(text, @"[^\w\s]+", " ");
|
||||
text = Regex.Replace(text, @"\b(season|сезон|частина|part|ova|special|movie|film)\b", " ");
|
||||
text = Regex.Replace(text, @"\b(\d+)(st|nd|rd|th)\b", "$1");
|
||||
text = Regex.Replace(text, @"\b\d+\b", " ");
|
||||
text = Regex.Replace(text, @"\s+", " ");
|
||||
return text.Trim();
|
||||
}
|
||||
|
||||
private int? YearInt(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return null;
|
||||
|
||||
if (int.TryParse(value.Trim(), out int result))
|
||||
return result;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<(JObject item, string mediaType)?> FetchTmdbByImdb(string imdbId, int? year)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(imdbId))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
string apiKey = AppInit.conf?.tmdb?.api_key;
|
||||
if (string.IsNullOrWhiteSpace(apiKey))
|
||||
return null;
|
||||
|
||||
string tmdbUrl = $"http://{AppInit.conf.listen.localhost}:{AppInit.conf.listen.port}/tmdb/api/3/find/{imdbId}?external_source=imdb_id&api_key={apiKey}&language=en-US";
|
||||
|
||||
var headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", Http.UserAgent)
|
||||
};
|
||||
|
||||
JObject payload = await Http.Get<JObject>(tmdbUrl, timeoutSeconds: 6, headers: headers);
|
||||
if (payload == null)
|
||||
return null;
|
||||
|
||||
var movieResults = payload["movie_results"] as JArray ?? new JArray();
|
||||
var tvResults = payload["tv_results"] as JArray ?? new JArray();
|
||||
|
||||
var candidates = new List<(JObject item, string mediaType)>();
|
||||
foreach (var item in movieResults.OfType<JObject>())
|
||||
candidates.Add((item, "movie"));
|
||||
foreach (var item in tvResults.OfType<JObject>())
|
||||
candidates.Add((item, "tv"));
|
||||
|
||||
if (candidates.Count == 0)
|
||||
return null;
|
||||
|
||||
if (year.HasValue)
|
||||
{
|
||||
string yearText = year.Value.ToString();
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
string dateValue = candidate.mediaType == "movie"
|
||||
? candidate.item.Value<string>("release_date")
|
||||
: candidate.item.Value<string>("first_air_date");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dateValue) && dateValue.StartsWith(yearText, StringComparison.Ordinal))
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return candidates[0];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Makhno TMDB fetch failed: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> PostWormholeAsync(object payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
var headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("Content-Type", "application/json"),
|
||||
new HeadersModel("User-Agent", Http.UserAgent)
|
||||
};
|
||||
|
||||
string json = JsonConvert.SerializeObject(payload, Formatting.None);
|
||||
await Http.Post(WormholeHost, json, timeoutSeconds: 6, headers: headers, proxy: _proxyManager.Get());
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Makhno wormhole insert failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class WormholeResponse
|
||||
{
|
||||
public string play { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
196
Makhno/ModInit.cs
Normal file
196
Makhno/ModInit.cs
Normal file
@ -0,0 +1,196 @@
|
||||
using Newtonsoft.Json;
|
||||
using Shared;
|
||||
using Shared.Engine;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models.Module;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
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 Makhno
|
||||
{
|
||||
public class ModInit
|
||||
{
|
||||
public static double Version => 1.0;
|
||||
|
||||
public static OnlinesSettings Makhno;
|
||||
public static bool ApnHostProvided;
|
||||
|
||||
public static OnlinesSettings Settings
|
||||
{
|
||||
get => Makhno;
|
||||
set => Makhno = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// модуль загружен
|
||||
/// </summary>
|
||||
public static void loaded(InitspaceModel initspace)
|
||||
{
|
||||
Makhno = new OnlinesSettings("Makhno", "https://wormhole.lampame.v6.rocks", streamproxy: false, useproxy: false)
|
||||
{
|
||||
displayname = "Махно",
|
||||
displayindex = 0,
|
||||
apihost = "https://uk.uatut.fun/watch",
|
||||
proxy = new Shared.Models.Base.ProxySettings()
|
||||
{
|
||||
useAuth = true,
|
||||
username = "",
|
||||
password = "",
|
||||
list = new string[] { "socks5://ip:port" }
|
||||
}
|
||||
};
|
||||
var conf = ModuleInvoke.Conf("Makhno", Makhno);
|
||||
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
|
||||
conf.Remove("apn");
|
||||
conf.Remove("apn_host");
|
||||
Makhno = conf.ToObject<OnlinesSettings>();
|
||||
if (hasApn)
|
||||
ApnHelper.ApplyInitConf(apnEnabled, apnHost, Makhno);
|
||||
ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost);
|
||||
if (hasApn && apnEnabled)
|
||||
{
|
||||
Makhno.streamproxy = false;
|
||||
}
|
||||
else if (Makhno.streamproxy)
|
||||
{
|
||||
Makhno.apnstream = false;
|
||||
Makhno.apn = null;
|
||||
}
|
||||
|
||||
// Виводити "уточнити пошук"
|
||||
AppInit.conf.online.with_search.Add("makhno");
|
||||
}
|
||||
}
|
||||
|
||||
public static class UpdateService
|
||||
{
|
||||
private static readonly string _connectUrl = "https://lmcuk.lampame.v6.rocks/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, 16))
|
||||
: 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;
|
||||
}
|
||||
|
||||
public static ActionResult Validate(ActionResult result)
|
||||
{
|
||||
return IsDisconnected()
|
||||
? throw new JsonReaderException($"Disconnect error: {Guid.CreateVersion7()}")
|
||||
: result;
|
||||
}
|
||||
}
|
||||
|
||||
public record ConnectResponse(bool IsUpdateUnavailable, bool IsNoiseEnabled);
|
||||
}
|
||||
61
Makhno/Models/MakhnoModels.cs
Normal file
61
Makhno/Models/MakhnoModels.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Makhno.Models
|
||||
{
|
||||
public class SearchResult
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty("imdb_id")]
|
||||
public string ImdbId { get; set; }
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonProperty("title_alt")]
|
||||
public string TitleAlt { get; set; }
|
||||
|
||||
[JsonProperty("title_en")]
|
||||
public string TitleEn { get; set; }
|
||||
|
||||
[JsonProperty("title_ru")]
|
||||
public string TitleRu { get; set; }
|
||||
|
||||
[JsonProperty("year")]
|
||||
public string Year { get; set; }
|
||||
|
||||
[JsonProperty("category")]
|
||||
public string Category { get; set; }
|
||||
}
|
||||
|
||||
public class PlayerData
|
||||
{
|
||||
public string File { get; set; }
|
||||
public string Poster { get; set; }
|
||||
public List<Voice> Voices { get; set; }
|
||||
public List<Season> Seasons { get; set; }
|
||||
}
|
||||
|
||||
public class Voice
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public List<Season> Seasons { get; set; }
|
||||
}
|
||||
|
||||
public class Season
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public List<Episode> Episodes { get; set; }
|
||||
}
|
||||
|
||||
public class Episode
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string File { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Poster { get; set; }
|
||||
public string Subtitle { get; set; }
|
||||
}
|
||||
}
|
||||
40
Makhno/OnlineApi.cs
Normal file
40
Makhno/OnlineApi.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Shared.Models;
|
||||
using Shared.Models.Base;
|
||||
using Shared.Models.Module;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Makhno
|
||||
{
|
||||
public class OnlineApi
|
||||
{
|
||||
public static List<(string name, string url, string plugin, int index)> Invoke(
|
||||
HttpContext httpContext,
|
||||
IMemoryCache memoryCache,
|
||||
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);
|
||||
}
|
||||
|
||||
public static List<(string name, string url, string plugin, int index)> 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<(string name, string url, string plugin, int index)>();
|
||||
|
||||
var init = ModInit.Makhno;
|
||||
if (init.enable && !init.rip)
|
||||
{
|
||||
string url = init.overridehost;
|
||||
if (string.IsNullOrEmpty(url) || UpdateService.IsDisconnected())
|
||||
url = $"{host}/makhno";
|
||||
|
||||
online.Add((init.displayname, url, "makhno", init.displayindex));
|
||||
}
|
||||
|
||||
return online;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Makhno/manifest.json
Normal file
6
Makhno/manifest.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"enable": true,
|
||||
"version": 1,
|
||||
"initspace": "Makhno.ModInit",
|
||||
"online": "Makhno.OnlineApi"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user