mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-04-16 09:22:21 +00:00
428 lines
17 KiB
C#
428 lines
17 KiB
C#
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, bool checksearch = false)
|
|
{
|
|
if (checksearch)
|
|
return Content("data-json=");
|
|
|
|
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())
|
|
{
|
|
OnLog("Makhno Play: no voices parsed");
|
|
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)
|
|
{
|
|
OnLog("Makhno PlayMovie: no file parsed");
|
|
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)
|
|
{
|
|
OnLog("Makhno HandleMovie: no file parsed");
|
|
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())
|
|
{
|
|
OnLog("Makhno HandleSerial: no voices parsed");
|
|
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}";
|
|
string streamLink = $"{episodeLink}&play=true";
|
|
episode_tpl.Append(
|
|
episode.Title,
|
|
title ?? original_title,
|
|
season.ToString(),
|
|
(i + 1).ToString("D2"),
|
|
accsArgs(episodeLink),
|
|
"call",
|
|
streamlink: accsArgs(streamLink)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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; }
|
|
}
|
|
}
|
|
}
|