mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-04-16 17:32:20 +00:00
Refactor episode link construction to use a common BuildStreamUrl method in Makhno and UaTUT controllers, and add streamlink support to UAKino controller. This change standardizes how streaming URLs are generated and passed to the episode template, reducing code duplication and improving maintainability.
425 lines
17 KiB
C#
425 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 streamUrl = BuildStreamUrl(init, episode.File);
|
|
episode_tpl.Append(
|
|
episode.Title,
|
|
title ?? original_title,
|
|
season.ToString(),
|
|
(i + 1).ToString("D2"),
|
|
streamUrl
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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; }
|
|
}
|
|
}
|
|
}
|