mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-04-16 17:32:20 +00:00
Refactor
This commit is contained in:
parent
3e1cab4531
commit
7a211c838b
148
AnimeON/AnimeONInvoke.cs
Normal file
148
AnimeON/AnimeONInvoke.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models;
|
||||
using System.Text.Json;
|
||||
using System.Linq;
|
||||
using AnimeON.Models;
|
||||
using Shared.Engine;
|
||||
|
||||
namespace AnimeON
|
||||
{
|
||||
public class AnimeONInvoke
|
||||
{
|
||||
private OnlinesSettings _init;
|
||||
private HybridCache _hybridCache;
|
||||
private Action<string> _onLog;
|
||||
private ProxyManager _proxyManager;
|
||||
|
||||
public AnimeONInvoke(OnlinesSettings init, HybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
|
||||
{
|
||||
_init = init;
|
||||
_hybridCache = hybridCache;
|
||||
_onLog = onLog;
|
||||
_proxyManager = proxyManager;
|
||||
}
|
||||
|
||||
public async Task<List<SearchModel>> Search(string imdb_id, long kinopoisk_id, string title, string original_title, int year)
|
||||
{
|
||||
string memKey = $"AnimeON:search:{kinopoisk_id}:{imdb_id}";
|
||||
if (_hybridCache.TryGetValue(memKey, out List<SearchModel> res))
|
||||
return res;
|
||||
|
||||
try
|
||||
{
|
||||
var headers = new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) };
|
||||
|
||||
async Task<List<SearchModel>> FindAnime(string query)
|
||||
{
|
||||
if (string.IsNullOrEmpty(query))
|
||||
return null;
|
||||
|
||||
string searchUrl = $"{_init.host}/api/anime/search?text={System.Web.HttpUtility.UrlEncode(query)}";
|
||||
string searchJson = await Http.Get(searchUrl, headers: headers, proxy: _proxyManager.Get());
|
||||
if (string.IsNullOrEmpty(searchJson))
|
||||
return null;
|
||||
|
||||
var searchResponse = JsonSerializer.Deserialize<SearchResponseModel>(searchJson);
|
||||
return searchResponse?.Result;
|
||||
}
|
||||
|
||||
var searchResults = await FindAnime(title) ?? await FindAnime(original_title);
|
||||
if (searchResults == null)
|
||||
return null;
|
||||
|
||||
if (!string.IsNullOrEmpty(imdb_id))
|
||||
{
|
||||
var seasons = searchResults.Where(a => a.ImdbId == imdb_id).ToList();
|
||||
if (seasons.Count > 0)
|
||||
{
|
||||
_hybridCache.Set(memKey, seasons, cacheTime(5));
|
||||
return seasons;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to first result if no imdb match
|
||||
var firstResult = searchResults.FirstOrDefault();
|
||||
if (firstResult != null)
|
||||
{
|
||||
var list = new List<SearchModel> { firstResult };
|
||||
_hybridCache.Set(memKey, list, cacheTime(5));
|
||||
return list;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"AnimeON error: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<List<FundubModel>> GetFundubs(int animeId)
|
||||
{
|
||||
string fundubsUrl = $"{_init.host}/api/player/fundubs/{animeId}";
|
||||
string fundubsJson = await Http.Get(fundubsUrl, headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
|
||||
if (string.IsNullOrEmpty(fundubsJson))
|
||||
return null;
|
||||
|
||||
var fundubsResponse = JsonSerializer.Deserialize<FundubsResponseModel>(fundubsJson);
|
||||
return fundubsResponse?.FunDubs;
|
||||
}
|
||||
|
||||
public async Task<EpisodeModel> GetEpisodes(int animeId, int playerId, int fundubId)
|
||||
{
|
||||
string episodesUrl = $"{_init.host}/api/player/episodes/{animeId}?take=100&skip=-1&playerId={playerId}&fundubId={fundubId}";
|
||||
string episodesJson = await Http.Get(episodesUrl, headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
|
||||
if (string.IsNullOrEmpty(episodesJson))
|
||||
return null;
|
||||
|
||||
return JsonSerializer.Deserialize<EpisodeModel>(episodesJson);
|
||||
}
|
||||
|
||||
public async Task<string> ParseMoonAnimePage(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
string requestUrl = $"{url}?player=animeon.club";
|
||||
var headers = new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
||||
new HeadersModel("Referer", "https://animeon.club/")
|
||||
};
|
||||
|
||||
string html = await Http.Get(requestUrl, headers: headers, proxy: _proxyManager.Get());
|
||||
if (string.IsNullOrEmpty(html))
|
||||
return null;
|
||||
|
||||
var match = System.Text.RegularExpressions.Regex.Match(html, @"file:\s*""([^""]+\.m3u8)""");
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"AnimeON ParseMoonAnimePage error: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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 = AppInit.conf.mikrotik ? mikrotik : AppInit.conf.multiaccess ? init != null && init.cache_time > 0 ? init.cache_time : multiaccess : home;
|
||||
if (ctime > multiaccess)
|
||||
ctime = multiaccess;
|
||||
|
||||
return TimeSpan.FromMinutes(ctime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -33,14 +33,16 @@ namespace AnimeON.Controllers
|
||||
if (!init.enable)
|
||||
return Forbid();
|
||||
|
||||
var seasons = await search(init, imdb_id, kinopoisk_id, title, original_title, year);
|
||||
var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager);
|
||||
|
||||
var seasons = await invoke.Search(imdb_id, kinopoisk_id, title, original_title, year);
|
||||
if (seasons == null || seasons.Count == 0)
|
||||
return Content("AnimeON", "text/html; charset=utf-8");
|
||||
|
||||
var allOptions = new List<(SearchModel season, FundubModel fundub, Player player)>();
|
||||
foreach (var season in seasons)
|
||||
{
|
||||
var fundubs = await GetFundubs(init, season.Id);
|
||||
var fundubs = await invoke.GetFundubs(season.Id);
|
||||
if (fundubs != null)
|
||||
{
|
||||
foreach (var fundub in fundubs)
|
||||
@ -76,7 +78,7 @@ namespace AnimeON.Controllers
|
||||
return Content("AnimeON", "text/html; charset=utf-8");
|
||||
|
||||
var selected = allOptions[s];
|
||||
var episodesData = await GetEpisodes(init, selected.season.Id, selected.player.Id, selected.fundub.Fundub.Id);
|
||||
var episodesData = await invoke.GetEpisodes(selected.season.Id, selected.player.Id, selected.fundub.Fundub.Id);
|
||||
if (episodesData == null || episodesData.Episodes == null)
|
||||
return Content("AnimeON", "text/html; charset=utf-8");
|
||||
|
||||
@ -85,9 +87,31 @@ namespace AnimeON.Controllers
|
||||
{
|
||||
var streamquality = new StreamQualityTpl();
|
||||
string streamLink = !string.IsNullOrEmpty(ep.Hls) ? ep.Hls : ep.VideoUrl;
|
||||
streamquality.Append(HostStreamProxy(init, streamLink), "hls");
|
||||
movie_tpl.Append(string.IsNullOrEmpty(ep.Name) ? $"Серія {ep.EpisodeNum}" : ep.Name, streamquality.Firts().link, streamquality: streamquality);
|
||||
|
||||
if (selected.player.Name.ToLower() == "moon" && !string.IsNullOrEmpty(streamLink) && streamLink.Contains("moonanime.art/iframe/"))
|
||||
{
|
||||
streamLink = $"{host}/animeon/play?url={HttpUtility.UrlEncode(streamLink)}";
|
||||
streamquality.Append(streamLink, "hls");
|
||||
movie_tpl.Append(string.IsNullOrEmpty(ep.Name) ? $"Серія {ep.EpisodeNum}" : ep.Name, streamLink, streamquality: streamquality);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(streamLink))
|
||||
{
|
||||
streamquality.Append(HostStreamProxy(init, streamLink), "hls");
|
||||
movie_tpl.Append(string.IsNullOrEmpty(ep.Name) ? $"Серія {ep.EpisodeNum}" : ep.Name, streamquality.Firts().link, streamquality: streamquality);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(episodesData.AnotherPlayer) && episodesData.AnotherPlayer.Contains("ashdi.vip"))
|
||||
{
|
||||
var match = Regex.Match(episodesData.AnotherPlayer, "/serial/([0-9]+)");
|
||||
if (match.Success)
|
||||
{
|
||||
string ashdi_kp = match.Groups[1].Value;
|
||||
string ashdi_link = $"/ashdi?kinopoisk_id={ashdi_kp}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}";
|
||||
movie_tpl.Append("Плеєр Ashdi", ashdi_link);
|
||||
}
|
||||
}
|
||||
|
||||
return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8");
|
||||
}
|
||||
}
|
||||
@ -96,16 +120,26 @@ namespace AnimeON.Controllers
|
||||
var tpl = new MovieTpl(title, original_title, allOptions.Count);
|
||||
foreach (var item in allOptions)
|
||||
{
|
||||
var episodesData = await GetEpisodes(init, item.season.Id, item.player.Id, item.fundub.Fundub.Id);
|
||||
var episodesData = await invoke.GetEpisodes(item.season.Id, item.player.Id, item.fundub.Fundub.Id);
|
||||
if (episodesData == null || episodesData.Episodes == null || episodesData.Episodes.Count == 0)
|
||||
continue;
|
||||
|
||||
|
||||
string translationName = $"[{item.player.Name}] {item.fundub.Fundub.Name}";
|
||||
var streamquality = new StreamQualityTpl();
|
||||
var firstEp = episodesData.Episodes.First();
|
||||
var firstEp = episodesData.Episodes.FirstOrDefault();
|
||||
string streamLink = !string.IsNullOrEmpty(firstEp.Hls) ? firstEp.Hls : firstEp.VideoUrl;
|
||||
streamquality.Append(HostStreamProxy(init, streamLink), "hls");
|
||||
tpl.Append(translationName, streamquality.Firts().link, streamquality: streamquality);
|
||||
|
||||
if (item.player.Name.ToLower() == "moon" && !string.IsNullOrEmpty(streamLink) && streamLink.Contains("moonanime.art/iframe/"))
|
||||
{
|
||||
streamLink = $"{host}/animeon/play?url={HttpUtility.UrlEncode(streamLink)}";
|
||||
streamquality.Append(streamLink, "hls");
|
||||
tpl.Append(translationName, streamLink, streamquality: streamquality);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(streamLink))
|
||||
{
|
||||
streamquality.Append(HostStreamProxy(init, streamLink), "hls");
|
||||
tpl.Append(translationName, streamquality.Firts().link, streamquality: streamquality);
|
||||
}
|
||||
}
|
||||
return rjson ? Content(tpl.ToJson(), "application/json; charset=utf-8") : Content(tpl.ToHtml(), "text/html; charset=utf-8");
|
||||
}
|
||||
@ -188,5 +222,24 @@ namespace AnimeON.Controllers
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[HttpGet("animeon/play")]
|
||||
public async Task<ActionResult> Play(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return OnError("url is empty");
|
||||
|
||||
var init = await loadKit(ModInit.AnimeON);
|
||||
if (!init.enable)
|
||||
return Forbid();
|
||||
|
||||
var invoke = new AnimeONInvoke(init, hybridCache, OnLog, proxyManager);
|
||||
string streamLink = await invoke.ParseMoonAnimePage(url);
|
||||
|
||||
if (string.IsNullOrEmpty(streamLink))
|
||||
return Content("Не вдалося отримати посилання на відео", "text/html; charset=utf-8");
|
||||
|
||||
return Redirect(HostStreamProxy(init, streamLink));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models.Module;
|
||||
|
||||
namespace AnimeON
|
||||
{
|
||||
public class ModInit
|
||||
@ -10,7 +11,7 @@ namespace AnimeON
|
||||
/// <summary>
|
||||
/// модуль загружен
|
||||
/// </summary>
|
||||
public static void loaded()
|
||||
public static void loaded(InitspaceModel initspace)
|
||||
{
|
||||
AnimeON = new OnlinesSettings("AnimeON", "https://animeon.club", streamproxy: false)
|
||||
{
|
||||
|
||||
23
AnimeON/Models/EmbedModel.cs
Normal file
23
AnimeON/Models/EmbedModel.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AnimeON.Models
|
||||
{
|
||||
public class EmbedModel
|
||||
{
|
||||
[JsonPropertyName("translation")]
|
||||
public string Translation { get; set; }
|
||||
|
||||
[JsonPropertyName("links")]
|
||||
public List<(string link, string quality)> Links { get; set; }
|
||||
|
||||
[JsonPropertyName("subtitles")]
|
||||
public Shared.Models.Templates.SubtitleTpl? Subtitles { get; set; }
|
||||
|
||||
[JsonPropertyName("season")]
|
||||
public int Season { get; set; }
|
||||
|
||||
[JsonPropertyName("episode")]
|
||||
public int Episode { get; set; }
|
||||
}
|
||||
}
|
||||
@ -70,6 +70,9 @@ namespace AnimeON.Models
|
||||
{
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<Episode> Episodes { get; set; }
|
||||
|
||||
[JsonPropertyName("anotherPlayer")]
|
||||
public string AnotherPlayer { get; set; }
|
||||
}
|
||||
|
||||
public class Episode
|
||||
|
||||
29
AnimeON/Models/Serial.cs
Normal file
29
AnimeON/Models/Serial.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AnimeON.Models
|
||||
{
|
||||
public class Serial
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonPropertyName("title_ua")]
|
||||
public string TitleUa { get; set; }
|
||||
|
||||
[JsonPropertyName("title_en")]
|
||||
public string TitleEn { get; set; }
|
||||
|
||||
[JsonPropertyName("year")]
|
||||
public string Year { get; set; }
|
||||
|
||||
[JsonPropertyName("imdb_id")]
|
||||
public string ImdbId { get; set; }
|
||||
|
||||
[JsonPropertyName("season")]
|
||||
public int Season { get; set; }
|
||||
|
||||
[JsonPropertyName("voices")]
|
||||
public List<Voice> Voices { get; set; }
|
||||
}
|
||||
}
|
||||
26
AnimeON/Models/Voice.cs
Normal file
26
AnimeON/Models/Voice.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AnimeON.Models
|
||||
{
|
||||
public class Voice
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("players")]
|
||||
public List<VoicePlayer> Players { get; set; }
|
||||
}
|
||||
|
||||
public class VoicePlayer
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"enable": true,
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"initspace": "AnimeON.ModInit",
|
||||
"online": "AnimeON.OnlineApi"
|
||||
}
|
||||
312
CikavaIdeya/CikavaIdeyaInvoke.cs
Normal file
312
CikavaIdeya/CikavaIdeyaInvoke.cs
Normal file
@ -0,0 +1,312 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
using HtmlAgilityPack;
|
||||
using CikavaIdeya.Models;
|
||||
using Shared.Engine;
|
||||
using System.Linq;
|
||||
|
||||
namespace CikavaIdeya
|
||||
{
|
||||
public class CikavaIdeyaInvoke
|
||||
{
|
||||
private OnlinesSettings _init;
|
||||
private HybridCache _hybridCache;
|
||||
private Action<string> _onLog;
|
||||
private ProxyManager _proxyManager;
|
||||
|
||||
public CikavaIdeyaInvoke(OnlinesSettings init, HybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
|
||||
{
|
||||
_init = init;
|
||||
_hybridCache = hybridCache;
|
||||
_onLog = onLog;
|
||||
_proxyManager = proxyManager;
|
||||
}
|
||||
|
||||
public async Task<List<CikavaIdeya.Models.EpisodeLinkInfo>> Search(string imdb_id, long kinopoisk_id, string title, string original_title, int year, bool isfilm = false)
|
||||
{
|
||||
string filmTitle = !string.IsNullOrEmpty(title) ? title : original_title;
|
||||
string memKey = $"CikavaIdeya:search:{filmTitle}:{year}:{isfilm}";
|
||||
if (_hybridCache.TryGetValue(memKey, out List<CikavaIdeya.Models.EpisodeLinkInfo> res))
|
||||
return res;
|
||||
|
||||
try
|
||||
{
|
||||
// Спочатку шукаємо по title
|
||||
res = await PerformSearch(title, year);
|
||||
|
||||
// Якщо нічого не знайдено і є original_title, шукаємо по ньому
|
||||
if ((res == null || res.Count == 0) && !string.IsNullOrEmpty(original_title) && original_title != title)
|
||||
{
|
||||
_onLog($"No results for '{title}', trying search by original title '{original_title}'");
|
||||
res = await PerformSearch(original_title, year);
|
||||
// Оновлюємо ключ кешу для original_title
|
||||
if (res != null && res.Count > 0)
|
||||
{
|
||||
memKey = $"CikavaIdeya:search:{original_title}:{year}:{isfilm}";
|
||||
}
|
||||
}
|
||||
|
||||
if (res != null && res.Count > 0)
|
||||
{
|
||||
_hybridCache.Set(memKey, res, cacheTime(20));
|
||||
return res;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"CikavaIdeya search error: {ex.Message}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<List<EpisodeLinkInfo>> PerformSearch(string searchTitle, int year)
|
||||
{
|
||||
try
|
||||
{
|
||||
string searchUrl = $"{_init.host}/index.php?do=search&subaction=search&story={System.Web.HttpUtility.UrlEncode(searchTitle)}";
|
||||
var headers = new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) };
|
||||
|
||||
var searchHtml = await Http.Get(searchUrl, headers: headers, proxy: _proxyManager.Get());
|
||||
// Перевіряємо, чи є результати пошуку
|
||||
if (searchHtml.Contains("На жаль, пошук на сайті не дав жодних результатів"))
|
||||
{
|
||||
_onLog($"No search results for '{searchTitle}'");
|
||||
return new List<CikavaIdeya.Models.EpisodeLinkInfo>();
|
||||
}
|
||||
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(searchHtml);
|
||||
|
||||
var filmNodes = doc.DocumentNode.SelectNodes("//div[@class='th-item']");
|
||||
if (filmNodes == null)
|
||||
{
|
||||
_onLog($"No film nodes found for '{searchTitle}'");
|
||||
return new List<CikavaIdeya.Models.EpisodeLinkInfo>();
|
||||
}
|
||||
|
||||
string filmUrl = null;
|
||||
foreach (var filmNode in filmNodes)
|
||||
{
|
||||
var titleNode = filmNode.SelectSingleNode(".//div[@class='th-title']");
|
||||
if (titleNode == null || !titleNode.InnerText.Trim().ToLower().Contains(searchTitle.ToLower())) continue;
|
||||
|
||||
var descNode = filmNode.SelectSingleNode(".//div[@class='th-subtitle']");
|
||||
if (year > 0 && (descNode?.InnerText ?? "").Contains(year.ToString()))
|
||||
{
|
||||
var linkNode = filmNode.SelectSingleNode(".//a[@class='th-in']");
|
||||
if (linkNode != null)
|
||||
{
|
||||
filmUrl = linkNode.GetAttributeValue("href", "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filmUrl == null)
|
||||
{
|
||||
var firstNode = filmNodes.FirstOrDefault()?.SelectSingleNode(".//a[@class='th-in']");
|
||||
if (firstNode != null)
|
||||
filmUrl = firstNode.GetAttributeValue("href", "");
|
||||
}
|
||||
|
||||
if (filmUrl == null)
|
||||
{
|
||||
_onLog($"No film URL found for '{searchTitle}'");
|
||||
return new List<CikavaIdeya.Models.EpisodeLinkInfo>();
|
||||
}
|
||||
|
||||
if (!filmUrl.StartsWith("http"))
|
||||
filmUrl = _init.host + filmUrl;
|
||||
|
||||
// Отримуємо список епізодів (для фільмів - один епізод, для серіалів - всі епізоди)
|
||||
var filmHtml = await Http.Get(filmUrl, headers: headers, proxy: _proxyManager.Get());
|
||||
// Перевіряємо, чи не видалено контент
|
||||
if (filmHtml.Contains("Видалено на прохання правовласника"))
|
||||
{
|
||||
_onLog($"Content removed on copyright holder request: {filmUrl}");
|
||||
return new List<CikavaIdeya.Models.EpisodeLinkInfo>();
|
||||
}
|
||||
|
||||
doc.LoadHtml(filmHtml);
|
||||
|
||||
// Знаходимо JavaScript з даними про епізоди
|
||||
var scriptNodes = doc.DocumentNode.SelectNodes("//script");
|
||||
if (scriptNodes != null)
|
||||
{
|
||||
foreach (var scriptNode in scriptNodes)
|
||||
{
|
||||
var scriptContent = scriptNode.InnerText;
|
||||
if (scriptContent.Contains("switches = Object"))
|
||||
{
|
||||
_onLog($"Found switches script: {scriptContent}");
|
||||
// Парсимо структуру switches
|
||||
var match = Regex.Match(scriptContent, @"switches = Object\((\{.*\})\);", RegexOptions.Singleline);
|
||||
if (match.Success)
|
||||
{
|
||||
string switchesJson = match.Groups[1].Value;
|
||||
_onLog($"Parsed switches JSON: {switchesJson}");
|
||||
// Спрощений парсинг JSON-подібної структури
|
||||
var res = ParseSwitchesJson(switchesJson, _init.host, filmUrl);
|
||||
_onLog($"Parsed episodes count: {res.Count}");
|
||||
foreach (var ep in res)
|
||||
{
|
||||
_onLog($"Episode: season={ep.season}, episode={ep.episode}, title={ep.title}, url={ep.url}");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"PerformSearch error for '{searchTitle}': {ex.Message}");
|
||||
}
|
||||
return new List<EpisodeLinkInfo>();
|
||||
}
|
||||
|
||||
List<CikavaIdeya.Models.EpisodeLinkInfo> ParseSwitchesJson(string json, string host, string baseUrl)
|
||||
{
|
||||
var result = new List<CikavaIdeya.Models.EpisodeLinkInfo>();
|
||||
|
||||
try
|
||||
{
|
||||
_onLog($"Parsing switches JSON: {json}");
|
||||
// Спрощений парсинг JSON-подібної структури
|
||||
// Приклад для серіалу: {"Player1":{"1 сезон":{"1 серія":"https://ashdi.vip/vod/57364",...},"2 сезон":{"1 серія":"https://ashdi.vip/vod/118170",...}}}
|
||||
// Приклад для фільму: {"Player1":"https://ashdi.vip/vod/162246"}
|
||||
|
||||
// Знаходимо плеєр Player1
|
||||
// Спочатку спробуємо знайти об'єкт Player1
|
||||
var playerObjectMatch = Regex.Match(json, @"""Player1""\s*:\s*(\{(?:[^{}]|(?<open>\{)|(?<-open>\}))+(?(open)(?!)))", RegexOptions.Singleline);
|
||||
if (playerObjectMatch.Success)
|
||||
{
|
||||
string playerContent = playerObjectMatch.Groups[1].Value;
|
||||
_onLog($"Player1 object content: {playerContent}");
|
||||
|
||||
// Це серіал, парсимо сезони
|
||||
var seasonMatches = Regex.Matches(playerContent, @"""([^""]+?сезон[^""]*?)""\s*:\s*\{((?:[^{}]|(?<open>\{)|(?<-open>\}))+(?(open)(?!)))\}", RegexOptions.Singleline);
|
||||
_onLog($"Found {seasonMatches.Count} seasons");
|
||||
foreach (Match seasonMatch in seasonMatches)
|
||||
{
|
||||
string seasonName = seasonMatch.Groups[1].Value;
|
||||
string seasonContent = seasonMatch.Groups[2].Value;
|
||||
_onLog($"Season: {seasonName}, Content: {seasonContent}");
|
||||
|
||||
// Витягуємо номер сезону
|
||||
var seasonNumMatch = Regex.Match(seasonName, @"(\d+)");
|
||||
int seasonNum = seasonNumMatch.Success ? int.Parse(seasonNumMatch.Groups[1].Value) : 1;
|
||||
_onLog($"Season number: {seasonNum}");
|
||||
|
||||
// Парсимо епізоди
|
||||
var episodeMatches = Regex.Matches(seasonContent, @"""([^""]+?)""\s*:\s*""([^""]+?)""", RegexOptions.Singleline);
|
||||
_onLog($"Found {episodeMatches.Count} episodes in season {seasonNum}");
|
||||
foreach (Match episodeMatch in episodeMatches)
|
||||
{
|
||||
string episodeName = episodeMatch.Groups[1].Value;
|
||||
string episodeUrl = episodeMatch.Groups[2].Value;
|
||||
_onLog($"Episode: {episodeName}, URL: {episodeUrl}");
|
||||
|
||||
// Витягуємо номер епізоду
|
||||
var episodeNumMatch = Regex.Match(episodeName, @"(\d+)");
|
||||
int episodeNum = episodeNumMatch.Success ? int.Parse(episodeNumMatch.Groups[1].Value) : 1;
|
||||
|
||||
result.Add(new CikavaIdeya.Models.EpisodeLinkInfo
|
||||
{
|
||||
url = episodeUrl,
|
||||
title = episodeName,
|
||||
season = seasonNum,
|
||||
episode = episodeNum
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Якщо не знайшли об'єкт, спробуємо знайти просте значення
|
||||
var playerStringMatch = Regex.Match(json, @"""Player1""\s*:\s*(""([^""]+)"")", RegexOptions.Singleline);
|
||||
if (playerStringMatch.Success)
|
||||
{
|
||||
string playerContent = playerStringMatch.Groups[1].Value;
|
||||
_onLog($"Player1 string content: {playerContent}");
|
||||
|
||||
// Якщо це фільм (просте значення)
|
||||
if (playerContent.StartsWith("\"") && playerContent.EndsWith("\""))
|
||||
{
|
||||
string filmUrl = playerContent.Trim('"');
|
||||
result.Add(new CikavaIdeya.Models.EpisodeLinkInfo
|
||||
{
|
||||
url = filmUrl,
|
||||
title = "Фільм",
|
||||
season = 1,
|
||||
episode = 1
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_onLog("Player1 not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"ParseSwitchesJson error: {ex.Message}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<CikavaIdeya.Models.PlayResult> ParseEpisode(string url)
|
||||
{
|
||||
var result = new CikavaIdeya.Models.PlayResult() { streams = new List<(string, string)>() };
|
||||
try
|
||||
{
|
||||
// Якщо це вже iframe URL (наприклад, з switches), повертаємо його
|
||||
if (url.Contains("ashdi.vip"))
|
||||
{
|
||||
result.iframe_url = url;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Інакше парсимо сторінку
|
||||
string html = await Http.Get(url, headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(html);
|
||||
|
||||
var iframe = doc.DocumentNode.SelectSingleNode("//div[@class='video-box']//iframe");
|
||||
if (iframe != null)
|
||||
{
|
||||
string iframeUrl = iframe.GetAttributeValue("src", "").Replace("&", "&");
|
||||
if (iframeUrl.StartsWith("//"))
|
||||
iframeUrl = "https:" + iframeUrl;
|
||||
|
||||
result.iframe_url = iframeUrl;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"ParseEpisode error: {ex.Message}");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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 = AppInit.conf.mikrotik ? mikrotik : AppInit.conf.multiaccess ? init != null && init.cache_time > 0 ? init.cache_time : multiaccess : home;
|
||||
if (ctime > multiaccess)
|
||||
ctime = multiaccess;
|
||||
|
||||
return TimeSpan.FromMinutes(ctime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,9 @@ namespace CikavaIdeya.Controllers
|
||||
if (!init.enable)
|
||||
return Forbid();
|
||||
|
||||
var episodesInfo = await search(init, imdb_id, kinopoisk_id, title, original_title, year, serial == 0);
|
||||
var invoke = new CikavaIdeyaInvoke(init, hybridCache, OnLog, proxyManager);
|
||||
|
||||
var episodesInfo = await invoke.Search(imdb_id, kinopoisk_id, title, original_title, year, serial == 0);
|
||||
if (episodesInfo == null)
|
||||
return Content("CikavaIdeya", "text/html; charset=utf-8");
|
||||
|
||||
@ -45,7 +47,7 @@ namespace CikavaIdeya.Controllers
|
||||
if (episode == null)
|
||||
return Content("CikavaIdeya", "text/html; charset=utf-8");
|
||||
|
||||
var playResult = await ParseEpisode(init, episode.url);
|
||||
var playResult = await invoke.ParseEpisode(episode.url);
|
||||
|
||||
if (!string.IsNullOrEmpty(playResult.iframe_url))
|
||||
{
|
||||
@ -182,7 +184,7 @@ namespace CikavaIdeya.Controllers
|
||||
|
||||
if (filmUrl == null)
|
||||
{
|
||||
var firstNode = filmNodes.First().SelectSingleNode(".//a[@class='th-in']");
|
||||
var firstNode = filmNodes.FirstOrDefault()?.SelectSingleNode(".//a[@class='th-in']");
|
||||
if (firstNode != null)
|
||||
filmUrl = firstNode.GetAttributeValue("href", "");
|
||||
}
|
||||
|
||||
@ -1,24 +1,25 @@
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
|
||||
namespace CikavaIdeya
|
||||
{
|
||||
public class ModInit
|
||||
{
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models.Module;
|
||||
|
||||
namespace CikavaIdeya
|
||||
{
|
||||
public class ModInit
|
||||
{
|
||||
public static OnlinesSettings CikavaIdeya;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// модуль загружен
|
||||
/// </summary>
|
||||
public static void loaded()
|
||||
public static void loaded(InitspaceModel initspace)
|
||||
{
|
||||
CikavaIdeya = new OnlinesSettings("CikavaIdeya", "https://cikava-ideya.top", streamproxy: false)
|
||||
{
|
||||
displayname = "ЦікаваІдея"
|
||||
};
|
||||
|
||||
|
||||
// Виводити "уточнити пошук"
|
||||
AppInit.conf.online.with_search.Add("cikavaideya");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
CikavaIdeya/Models/EpisodeModel.cs
Normal file
16
CikavaIdeya/Models/EpisodeModel.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CikavaIdeya.Models
|
||||
{
|
||||
public class EpisodeModel
|
||||
{
|
||||
[JsonPropertyName("episode_number")]
|
||||
public int EpisodeNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
||||
17
CikavaIdeya/Models/PlayerModel.cs
Normal file
17
CikavaIdeya/Models/PlayerModel.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CikavaIdeya.Models
|
||||
{
|
||||
public class CikavaIdeyaPlayerModel
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("qualities")]
|
||||
public List<(string link, string quality)> Qualities { get; set; }
|
||||
|
||||
[JsonPropertyName("subtitles")]
|
||||
public Shared.Models.Templates.SubtitleTpl? Subtitles { get; set; }
|
||||
}
|
||||
}
|
||||
14
CikavaIdeya/Models/SeasonModel.cs
Normal file
14
CikavaIdeya/Models/SeasonModel.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CikavaIdeya.Models
|
||||
{
|
||||
public class SeasonModel
|
||||
{
|
||||
[JsonPropertyName("season_number")]
|
||||
public int SeasonNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("episodes")]
|
||||
public List<EpisodeModel> Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"enable": true,
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"initspace": "CikavaIdeya.ModInit",
|
||||
"online": "CikavaIdeya.OnlineApi"
|
||||
}
|
||||
@ -11,25 +11,10 @@ using Shared.Models.Templates;
|
||||
using System.Text.RegularExpressions;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models;
|
||||
using Uaflix.Models;
|
||||
|
||||
namespace Uaflix.Controllers
|
||||
{
|
||||
#region Models
|
||||
public class EpisodeLinkInfo
|
||||
{
|
||||
public string url { get; set; }
|
||||
public string title { get; set; }
|
||||
public int season { get; set; }
|
||||
public int episode { get; set; }
|
||||
}
|
||||
|
||||
public class PlayResult
|
||||
{
|
||||
public string ashdi_url { get; set; }
|
||||
public List<(string link, string quality)> streams { get; set; }
|
||||
public SubtitleTpl? subtitles { get; set; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
public class Controller : BaseOnlineController
|
||||
{
|
||||
@ -48,7 +33,9 @@ namespace Uaflix.Controllers
|
||||
if (!init.enable)
|
||||
return Forbid();
|
||||
|
||||
var episodesInfo = await search(init, imdb_id, kinopoisk_id, title, original_title, year, serial == 0);
|
||||
var invoke = new UaflixInvoke(init, hybridCache, OnLog, proxyManager);
|
||||
|
||||
var episodesInfo = await invoke.Search(imdb_id, kinopoisk_id, title, original_title, year, serial == 0);
|
||||
if (episodesInfo == null)
|
||||
return Content("Uaflix", "text/html; charset=utf-8");
|
||||
|
||||
@ -61,7 +48,7 @@ namespace Uaflix.Controllers
|
||||
if (episode == null)
|
||||
return Content("Uaflix", "text/html; charset=utf-8");
|
||||
|
||||
var playResult = await ParseEpisode(init, episode.url);
|
||||
var playResult = await invoke.ParseEpisode(episode.url);
|
||||
|
||||
if (!string.IsNullOrEmpty(playResult.ashdi_url))
|
||||
{
|
||||
@ -109,10 +96,10 @@ namespace Uaflix.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
async ValueTask<List<EpisodeLinkInfo>> search(OnlinesSettings init, string imdb_id, long kinopoisk_id, string title, string original_title, int year, bool isfilm = false)
|
||||
async ValueTask<List<Uaflix.Models.EpisodeLinkInfo>> search(OnlinesSettings init, string imdb_id, long kinopoisk_id, string title, string original_title, int year, bool isfilm = false)
|
||||
{
|
||||
string memKey = $"UaFlix:search:{kinopoisk_id}:{imdb_id}";
|
||||
if (hybridCache.TryGetValue(memKey, out List<EpisodeLinkInfo> res))
|
||||
if (hybridCache.TryGetValue(memKey, out List<Uaflix.Models.EpisodeLinkInfo> res))
|
||||
return res;
|
||||
|
||||
try
|
||||
@ -143,14 +130,14 @@ namespace Uaflix.Controllers
|
||||
}
|
||||
|
||||
if (filmUrl == null)
|
||||
filmUrl = filmNodes.First().GetAttributeValue("href", "");
|
||||
filmUrl = filmNodes.FirstOrDefault()?.GetAttributeValue("href", "");
|
||||
|
||||
if (!filmUrl.StartsWith("http"))
|
||||
filmUrl = init.host + filmUrl;
|
||||
|
||||
if (isfilm)
|
||||
{
|
||||
res = new List<EpisodeLinkInfo>() { new EpisodeLinkInfo() { url = filmUrl } };
|
||||
res = new List<Uaflix.Models.EpisodeLinkInfo>() { new Uaflix.Models.EpisodeLinkInfo() { url = filmUrl } };
|
||||
hybridCache.Set(memKey, res, cacheTime(20));
|
||||
return res;
|
||||
}
|
||||
@ -158,11 +145,11 @@ namespace Uaflix.Controllers
|
||||
var filmHtml = await Http.Get(filmUrl, headers: headers);
|
||||
doc.LoadHtml(filmHtml);
|
||||
|
||||
res = new List<EpisodeLinkInfo>();
|
||||
res = new List<Uaflix.Models.EpisodeLinkInfo>();
|
||||
var episodeNodes = doc.DocumentNode.SelectNodes("//div[contains(@class, 'frels2')]//a[contains(@class, 'vi-img')]");
|
||||
if (episodeNodes != null)
|
||||
{
|
||||
foreach (var episodeNode in episodeNodes.Reverse())
|
||||
foreach (var episodeNode in episodeNodes.Reverse().ToList())
|
||||
{
|
||||
string episodeUrl = episodeNode.GetAttributeValue("href", "");
|
||||
if (!episodeUrl.StartsWith("http"))
|
||||
@ -171,7 +158,7 @@ namespace Uaflix.Controllers
|
||||
var match = Regex.Match(episodeUrl, @"season-(\d+).*?episode-(\d+)");
|
||||
if (match.Success)
|
||||
{
|
||||
res.Add(new EpisodeLinkInfo
|
||||
res.Add(new Uaflix.Models.EpisodeLinkInfo
|
||||
{
|
||||
url = episodeUrl,
|
||||
title = episodeNode.SelectSingleNode(".//div[@class='vi-rate']")?.InnerText.Trim() ?? $"Епізод {match.Groups[2].Value}",
|
||||
@ -187,7 +174,7 @@ namespace Uaflix.Controllers
|
||||
var iframe = doc.DocumentNode.SelectSingleNode("//div[contains(@class, 'video-box')]//iframe[contains(@src, 'ashdi.vip/serial/')]");
|
||||
if (iframe != null)
|
||||
{
|
||||
res.Add(new EpisodeLinkInfo() { url = filmUrl, season = 1, episode = 1 });
|
||||
res.Add(new Uaflix.Models.EpisodeLinkInfo() { url = filmUrl, season = 1, episode = 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,9 +190,9 @@ namespace Uaflix.Controllers
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<PlayResult> ParseEpisode(OnlinesSettings init, string url)
|
||||
async Task<Uaflix.Models.PlayResult> ParseEpisode(OnlinesSettings init, string url)
|
||||
{
|
||||
var result = new PlayResult() { streams = new List<(string, string)>() };
|
||||
var result = new Uaflix.Models.PlayResult() { streams = new List<(string, string)>() };
|
||||
try
|
||||
{
|
||||
string html = await Http.Get(url, headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
|
||||
@ -305,7 +292,7 @@ namespace Uaflix.Controllers
|
||||
if (!string.IsNullOrEmpty(subtitle))
|
||||
{
|
||||
var match = new Regex("\\[([^\\]]+)\\](https?://[^\\,]+)").Match(subtitle);
|
||||
var st = new SubtitleTpl();
|
||||
var st = new Shared.Models.Templates.SubtitleTpl();
|
||||
while (match.Success)
|
||||
{
|
||||
st.Append(match.Groups[1].Value, match.Groups[2].Value);
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
|
||||
namespace Uaflix
|
||||
{
|
||||
public class ModInit
|
||||
{
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models.Module;
|
||||
|
||||
namespace Uaflix
|
||||
{
|
||||
public class ModInit
|
||||
{
|
||||
public static OnlinesSettings UaFlix;
|
||||
|
||||
/// <summary>
|
||||
/// модуль загружен
|
||||
/// </summary>
|
||||
public static void loaded()
|
||||
public static void loaded(InitspaceModel initspace)
|
||||
{
|
||||
UaFlix = new OnlinesSettings("Uaflix", "https://uafix.net", streamproxy: false)
|
||||
{
|
||||
@ -20,5 +21,5 @@ namespace Uaflix
|
||||
// Виводити "уточнити пошук"
|
||||
AppInit.conf.online.with_search.Add("uaflix");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Uaflix/Models/EpisodeLinkInfo.cs
Normal file
12
Uaflix/Models/EpisodeLinkInfo.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Uaflix.Models
|
||||
{
|
||||
public class EpisodeLinkInfo
|
||||
{
|
||||
public string url { get; set; }
|
||||
public string title { get; set; }
|
||||
public int season { get; set; }
|
||||
public int episode { get; set; }
|
||||
}
|
||||
}
|
||||
12
Uaflix/Models/PlayResult.cs
Normal file
12
Uaflix/Models/PlayResult.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using Shared.Models.Templates;
|
||||
|
||||
namespace Uaflix.Models
|
||||
{
|
||||
public class PlayResult
|
||||
{
|
||||
public string ashdi_url { get; set; }
|
||||
public List<(string link, string quality)> streams { get; set; }
|
||||
public SubtitleTpl? subtitles { get; set; }
|
||||
}
|
||||
}
|
||||
250
Uaflix/UaflixInvoke.cs
Normal file
250
Uaflix/UaflixInvoke.cs
Normal file
@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
using HtmlAgilityPack;
|
||||
using Uaflix.Controllers;
|
||||
using Shared.Engine;
|
||||
using Uaflix.Models;
|
||||
using System.Linq;
|
||||
using Shared.Models.Templates;
|
||||
|
||||
namespace Uaflix
|
||||
{
|
||||
public class UaflixInvoke
|
||||
{
|
||||
private OnlinesSettings _init;
|
||||
private HybridCache _hybridCache;
|
||||
private Action<string> _onLog;
|
||||
private ProxyManager _proxyManager;
|
||||
|
||||
public UaflixInvoke(OnlinesSettings init, HybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
|
||||
{
|
||||
_init = init;
|
||||
_hybridCache = hybridCache;
|
||||
_onLog = onLog;
|
||||
_proxyManager = proxyManager;
|
||||
}
|
||||
|
||||
public async Task<List<Uaflix.Models.EpisodeLinkInfo>> Search(string imdb_id, long kinopoisk_id, string title, string original_title, int year, bool isfilm = false)
|
||||
{
|
||||
string memKey = $"UaFlix:search:{kinopoisk_id}:{imdb_id}";
|
||||
if (_hybridCache.TryGetValue(memKey, out List<Uaflix.Models.EpisodeLinkInfo> res))
|
||||
return res;
|
||||
|
||||
try
|
||||
{
|
||||
string filmTitle = !string.IsNullOrEmpty(title) ? title : original_title;
|
||||
string searchUrl = $"{_init.host}/index.php?do=search&subaction=search&story={System.Web.HttpUtility.UrlEncode(filmTitle)}";
|
||||
var headers = new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) };
|
||||
|
||||
var searchHtml = await Http.Get(searchUrl, headers: headers, proxy: _proxyManager.Get());
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(searchHtml);
|
||||
|
||||
var filmNodes = doc.DocumentNode.SelectNodes("//a[contains(@class, 'sres-wrap')]");
|
||||
if (filmNodes == null) return null;
|
||||
|
||||
string filmUrl = null;
|
||||
foreach (var filmNode in filmNodes)
|
||||
{
|
||||
var h2Node = filmNode.SelectSingleNode(".//h2");
|
||||
if (h2Node == null || !h2Node.InnerText.Trim().ToLower().Contains(filmTitle.ToLower())) continue;
|
||||
|
||||
var descNode = filmNode.SelectSingleNode(".//div[contains(@class, 'sres-desc')]");
|
||||
if (year > 0 && (descNode?.InnerText ?? "").Contains(year.ToString()))
|
||||
{
|
||||
filmUrl = filmNode.GetAttributeValue("href", "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (filmUrl == null)
|
||||
filmUrl = filmNodes.FirstOrDefault()?.GetAttributeValue("href", "");
|
||||
|
||||
if (!filmUrl.StartsWith("http"))
|
||||
filmUrl = _init.host + filmUrl;
|
||||
|
||||
if (isfilm)
|
||||
{
|
||||
res = new List<Uaflix.Models.EpisodeLinkInfo>() { new Uaflix.Models.EpisodeLinkInfo() { url = filmUrl } };
|
||||
_hybridCache.Set(memKey, res, cacheTime(20));
|
||||
return res;
|
||||
}
|
||||
|
||||
var filmHtml = await Http.Get(filmUrl, headers: headers, proxy: _proxyManager.Get());
|
||||
doc.LoadHtml(filmHtml);
|
||||
|
||||
res = new List<Uaflix.Models.EpisodeLinkInfo>();
|
||||
var episodeNodes = doc.DocumentNode.SelectNodes("//div[contains(@class, 'frels2')]//a[contains(@class, 'vi-img')]");
|
||||
if (episodeNodes != null)
|
||||
{
|
||||
foreach (var episodeNode in episodeNodes.Reverse().ToList())
|
||||
{
|
||||
string episodeUrl = episodeNode.GetAttributeValue("href", "");
|
||||
if (!episodeUrl.StartsWith("http"))
|
||||
episodeUrl = _init.host + episodeUrl;
|
||||
|
||||
var match = Regex.Match(episodeUrl, @"season-(\d+).*?episode-(\d+)");
|
||||
if (match.Success)
|
||||
{
|
||||
res.Add(new Uaflix.Models.EpisodeLinkInfo
|
||||
{
|
||||
url = episodeUrl,
|
||||
title = episodeNode.SelectSingleNode(".//div[@class='vi-rate']")?.InnerText.Trim() ?? $"Епізод {match.Groups[2].Value}",
|
||||
season = int.Parse(match.Groups[1].Value),
|
||||
episode = int.Parse(match.Groups[2].Value)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res.Count == 0)
|
||||
{
|
||||
var iframe = doc.DocumentNode.SelectSingleNode("//div[contains(@class, 'video-box')]//iframe[contains(@src, 'ashdi.vip/serial/')]");
|
||||
if (iframe != null)
|
||||
{
|
||||
res.Add(new Uaflix.Models.EpisodeLinkInfo() { url = filmUrl, season = 1, episode = 1 });
|
||||
}
|
||||
}
|
||||
|
||||
if (res.Count > 0)
|
||||
_hybridCache.Set(memKey, res, cacheTime(20));
|
||||
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"UaFlix search error: {ex.Message}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<Uaflix.Models.PlayResult> ParseEpisode(string url)
|
||||
{
|
||||
var result = new Uaflix.Models.PlayResult() { streams = new List<(string, string)>() };
|
||||
try
|
||||
{
|
||||
string html = await Http.Get(url, headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(html);
|
||||
|
||||
var iframe = doc.DocumentNode.SelectSingleNode("//div[contains(@class, 'video-box')]//iframe");
|
||||
if (iframe != null)
|
||||
{
|
||||
string iframeUrl = iframe.GetAttributeValue("src", "").Replace("&", "&");
|
||||
if (iframeUrl.StartsWith("//"))
|
||||
iframeUrl = "https:" + iframeUrl;
|
||||
|
||||
if (iframeUrl.Contains("ashdi.vip/serial/"))
|
||||
{
|
||||
result.ashdi_url = iframeUrl;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (iframeUrl.Contains("zetvideo.net"))
|
||||
result.streams = await ParseAllZetvideoSources(iframeUrl);
|
||||
else if (iframeUrl.Contains("ashdi.vip"))
|
||||
{
|
||||
result.streams = await ParseAllAshdiSources(iframeUrl);
|
||||
var idMatch = Regex.Match(iframeUrl, @"_(\d+)|vod/(\d+)");
|
||||
if (idMatch.Success)
|
||||
{
|
||||
string ashdiId = idMatch.Groups[1].Success ? idMatch.Groups[1].Value : idMatch.Groups[2].Value;
|
||||
result.subtitles = await GetAshdiSubtitles(ashdiId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"ParseEpisode error: {ex.Message}");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async Task<List<(string link, string quality)>> ParseAllZetvideoSources(string iframeUrl)
|
||||
{
|
||||
var result = new List<(string link, string quality)>();
|
||||
var html = await Http.Get(iframeUrl, headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://zetvideo.net/") }, proxy: _proxyManager.Get());
|
||||
if (string.IsNullOrEmpty(html)) return result;
|
||||
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(html);
|
||||
|
||||
var script = doc.DocumentNode.SelectSingleNode("//script[contains(text(), 'file:')]");
|
||||
if (script != null)
|
||||
{
|
||||
var match = Regex.Match(script.InnerText, @"file:\s*""([^""]+\.m3u8)");
|
||||
if (match.Success)
|
||||
{
|
||||
result.Add((match.Groups[1].Value, "1080p"));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
var sourceNodes = doc.DocumentNode.SelectNodes("//source[contains(@src, '.m3u8')]");
|
||||
if (sourceNodes != null)
|
||||
{
|
||||
foreach (var node in sourceNodes)
|
||||
{
|
||||
result.Add((node.GetAttributeValue("src", null), node.GetAttributeValue("label", null) ?? node.GetAttributeValue("res", null) ?? "1080p"));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async Task<List<(string link, string quality)>> ParseAllAshdiSources(string iframeUrl)
|
||||
{
|
||||
var result = new List<(string link, string quality)>();
|
||||
var html = await Http.Get(iframeUrl, headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") }, proxy: _proxyManager.Get());
|
||||
if (string.IsNullOrEmpty(html)) return result;
|
||||
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(html);
|
||||
|
||||
var sourceNodes = doc.DocumentNode.SelectNodes("//source[contains(@src, '.m3u8')]");
|
||||
if (sourceNodes != null)
|
||||
{
|
||||
foreach (var node in sourceNodes)
|
||||
{
|
||||
result.Add((node.GetAttributeValue("src", null), node.GetAttributeValue("label", null) ?? node.GetAttributeValue("res", null) ?? "1080p"));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async Task<SubtitleTpl?> GetAshdiSubtitles(string id)
|
||||
{
|
||||
var html = await Http.Get($"https://ashdi.vip/vod/{id}", headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") }, proxy: _proxyManager.Get());
|
||||
string subtitle = new Regex("subtitle(\")?:\"([^\"]+)\"").Match(html).Groups[2].Value;
|
||||
if (!string.IsNullOrEmpty(subtitle))
|
||||
{
|
||||
var match = new Regex("\\[([^\\]]+)\\](https?://[^\\,]+)").Match(subtitle);
|
||||
var st = new Shared.Models.Templates.SubtitleTpl();
|
||||
while (match.Success)
|
||||
{
|
||||
st.Append(match.Groups[1].Value, match.Groups[2].Value);
|
||||
match = match.NextMatch();
|
||||
}
|
||||
if (!st.IsEmpty())
|
||||
return st;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
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 = AppInit.conf.mikrotik ? mikrotik : AppInit.conf.multiaccess ? init != null && init.cache_time > 0 ? init.cache_time : multiaccess : home;
|
||||
if (ctime > multiaccess)
|
||||
ctime = multiaccess;
|
||||
|
||||
return TimeSpan.FromMinutes(ctime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"enable": true,
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"initspace": "Uaflix.ModInit",
|
||||
"online": "Uaflix.OnlineApi"
|
||||
}
|
||||
@ -29,65 +29,35 @@ namespace Unimay.Controllers
|
||||
if (await IsBadInitialization(init, rch: false))
|
||||
return badInitMsg;
|
||||
|
||||
var proxy = proxyManager.Get();
|
||||
var invoke = new UnimayInvoke(init, hybridCache, OnLog, proxyManager);
|
||||
|
||||
if (!string.IsNullOrEmpty(code))
|
||||
{
|
||||
// Fetch release details
|
||||
return await Release(init, proxy, code, title, original_title, serial, s, e, play, rjson);
|
||||
return await Release(invoke, init, code, title, original_title, serial, s, e, play, rjson);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Search
|
||||
return await Search(init, proxy, title, original_title, serial, rjson);
|
||||
return await Search(invoke, init, title, original_title, serial, rjson);
|
||||
}
|
||||
}
|
||||
|
||||
async ValueTask<ActionResult> Search(OnlinesSettings init, System.Net.WebProxy proxy, string title, string original_title, int serial, bool rjson)
|
||||
async ValueTask<ActionResult> Search(UnimayInvoke invoke, OnlinesSettings init, string title, string original_title, int serial, bool rjson)
|
||||
{
|
||||
string memKey = $"unimay:search:{title}:{original_title}:{serial}";
|
||||
|
||||
return await InvkSemaphore(init, memKey, async () =>
|
||||
{
|
||||
if (!hybridCache.TryGetValue(memKey, out JArray searchResults))
|
||||
{
|
||||
string searchQuery = HttpUtility.UrlEncode(title ?? original_title ?? "");
|
||||
string searchUrl = $"{init.host}/release/search?page=0&page_size=10&title={searchQuery}";
|
||||
|
||||
var headers = httpHeaders(init);
|
||||
JObject root = await Http.Get<JObject>(searchUrl, timeoutSeconds: 8, proxy: proxy, headers: headers);
|
||||
|
||||
if (root == null || !root.ContainsKey("content") || ((JArray)root["content"]).Count == 0)
|
||||
{
|
||||
proxyManager.Refresh();
|
||||
return OnError("search failed");
|
||||
}
|
||||
|
||||
searchResults = (JArray)root["content"];
|
||||
hybridCache.Set(memKey, searchResults, cacheTime(30, init: init));
|
||||
}
|
||||
|
||||
if (searchResults == null || searchResults.Count == 0)
|
||||
var searchResults = await invoke.Search(title, original_title, serial);
|
||||
if (searchResults == null || searchResults.Content.Count == 0)
|
||||
return OnError("no results");
|
||||
|
||||
var stpl = new SimilarTpl(searchResults.Count);
|
||||
var stpl = new SimilarTpl(searchResults.Content.Count);
|
||||
var results = invoke.GetSearchResults(host, searchResults, title, original_title, serial);
|
||||
|
||||
foreach (JObject item in searchResults)
|
||||
foreach (var (itemTitle, itemYear, itemType, releaseUrl) in results)
|
||||
{
|
||||
string itemCode = item.Value<string>("code");
|
||||
string itemTitle = item["names"]?["ukr"]?.Value<string>() ?? item.Value<string>("title");
|
||||
string itemYear = item.Value<string>("year");
|
||||
string itemType = item.Value<string>("type"); // "Телесеріал" or "Фільм"
|
||||
|
||||
// Filter by serial if specified (0: movie "Фільм", 1: serial "Телесеріал")
|
||||
if (serial != -1)
|
||||
{
|
||||
bool isMovie = itemType == "Фільм";
|
||||
if ((serial == 0 && !isMovie) || (serial == 1 && isMovie))
|
||||
continue;
|
||||
}
|
||||
|
||||
string releaseUrl = $"{host}/unimay?code={itemCode}&title={HttpUtility.UrlEncode(itemTitle)}&original_title={HttpUtility.UrlEncode(original_title ?? "")}&serial={serial}";
|
||||
stpl.Append(itemTitle, itemYear, itemType, releaseUrl);
|
||||
}
|
||||
|
||||
@ -95,34 +65,18 @@ namespace Unimay.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
async ValueTask<ActionResult> Release(OnlinesSettings init, System.Net.WebProxy proxy, string code, string title, string original_title, int serial, int s, int e, bool play, bool rjson)
|
||||
async ValueTask<ActionResult> Release(UnimayInvoke invoke, OnlinesSettings init, string code, string title, string original_title, int serial, int s, int e, bool play, bool rjson)
|
||||
{
|
||||
string memKey = $"unimay:release:{code}";
|
||||
|
||||
return await InvkSemaphore(init, memKey, async () =>
|
||||
{
|
||||
if (!hybridCache.TryGetValue(memKey, out JObject releaseDetail))
|
||||
{
|
||||
string releaseUrl = $"{init.host}/release?code={code}";
|
||||
|
||||
var headers = httpHeaders(init);
|
||||
JObject root = await Http.Get<JObject>(releaseUrl, timeoutSeconds: 8, proxy: proxy, headers: headers);
|
||||
|
||||
if (root == null)
|
||||
{
|
||||
proxyManager.Refresh();
|
||||
return OnError("release failed");
|
||||
}
|
||||
|
||||
releaseDetail = root;
|
||||
hybridCache.Set(memKey, releaseDetail, cacheTime(60, init: init));
|
||||
}
|
||||
|
||||
var releaseDetail = await invoke.Release(code);
|
||||
if (releaseDetail == null)
|
||||
return OnError("no release detail");
|
||||
|
||||
string itemType = releaseDetail.Value<string>("type");
|
||||
JArray playlist = (JArray)releaseDetail["playlist"];
|
||||
string itemType = releaseDetail.Type;
|
||||
var playlist = releaseDetail.Playlist;
|
||||
|
||||
if (playlist == null || playlist.Count == 0)
|
||||
return OnError("no playlist");
|
||||
@ -130,33 +84,32 @@ namespace Unimay.Controllers
|
||||
if (play)
|
||||
{
|
||||
// Get specific episode
|
||||
JObject episode = null;
|
||||
Unimay.Models.Episode episode = null;
|
||||
if (itemType == "Телесеріал")
|
||||
{
|
||||
if (s <= 0 || e <= 0) return OnError("invalid episode");
|
||||
episode = playlist.FirstOrDefault(ep => (int?)ep["number"] == e) as JObject;
|
||||
episode = playlist.FirstOrDefault(ep => ep.Number == e);
|
||||
}
|
||||
else // Movie
|
||||
{
|
||||
episode = playlist[0] as JObject;
|
||||
episode = playlist[0];
|
||||
}
|
||||
|
||||
if (episode == null)
|
||||
return OnError("episode not found");
|
||||
|
||||
string masterUrl = episode["hls"]?["master"]?.Value<string>();
|
||||
string masterUrl = invoke.GetStreamUrl(episode);
|
||||
if (string.IsNullOrEmpty(masterUrl))
|
||||
return OnError("no stream");
|
||||
|
||||
return Redirect(HostStreamProxy(init, masterUrl, proxy: proxy));
|
||||
return Redirect(HostStreamProxy(init, masterUrl, proxy: proxyManager.Get()));
|
||||
}
|
||||
|
||||
if (itemType == "Фільм")
|
||||
{
|
||||
JObject movieEpisode = playlist[0] as JObject;
|
||||
string movieLink = $"{host}/unimay?code={code}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&serial=0&play=true";
|
||||
var (movieTitle, movieLink) = invoke.GetMovieResult(host, releaseDetail, title, original_title);
|
||||
var mtpl = new MovieTpl(title, original_title, 1);
|
||||
mtpl.Append(movieEpisode["title"]?.Value<string>() ?? title, movieLink);
|
||||
mtpl.Append(movieTitle, movieLink);
|
||||
return ContentTo(rjson ? mtpl.ToJson() : mtpl.ToHtml());
|
||||
}
|
||||
else if (itemType == "Телесеріал")
|
||||
@ -164,27 +117,18 @@ namespace Unimay.Controllers
|
||||
if (s == -1)
|
||||
{
|
||||
// Assume single season
|
||||
var (seasonName, seasonUrl, seasonId) = invoke.GetSeasonInfo(host, code, title, original_title);
|
||||
var stpl = new SeasonTpl();
|
||||
stpl.Append("Сезон 1", $"{host}/unimay?code={code}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&serial=1&s=1", "1");
|
||||
stpl.Append(seasonName, seasonUrl, seasonId);
|
||||
return ContentTo(rjson ? stpl.ToJson() : stpl.ToHtml());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Episodes for season 1
|
||||
var episodes = new List<JObject>();
|
||||
foreach (JObject ep in playlist)
|
||||
{
|
||||
int epNum = (int)ep["number"];
|
||||
if (epNum >= 1 && epNum <= 24) // Assume season 1
|
||||
episodes.Add(ep);
|
||||
}
|
||||
|
||||
var episodes = invoke.GetEpisodesForSeason(host, releaseDetail, title, original_title);
|
||||
var mtpl = new MovieTpl(title, original_title, episodes.Count);
|
||||
foreach (JObject ep in episodes.OrderBy(ep => (int)ep["number"]))
|
||||
foreach (var (epTitle, epLink) in episodes)
|
||||
{
|
||||
int epNum = (int)ep["number"];
|
||||
string epTitle = ep["title"]?.Value<string>() ?? $"Епізод {epNum}";
|
||||
string epLink = $"{host}/unimay?code={code}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&serial=1&s=1&e={epNum}&play=true";
|
||||
mtpl.Append(epTitle, epLink);
|
||||
}
|
||||
return ContentTo(rjson ? mtpl.ToJson() : mtpl.ToHtml());
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models.Module;
|
||||
|
||||
namespace Unimay
|
||||
{
|
||||
@ -10,9 +11,9 @@ namespace Unimay
|
||||
/// <summary>
|
||||
/// модуль загружен
|
||||
/// </summary>
|
||||
public static void loaded()
|
||||
public static void loaded(InitspaceModel initspace)
|
||||
{
|
||||
Unimay = new OnlinesSettings("Unimay", "https://api.unimay.media/v1", streamproxy: true)
|
||||
Unimay = new OnlinesSettings("Unimay", "https://api.unimay.media/v1", streamproxy: false)
|
||||
{
|
||||
displayname = "Unimay"
|
||||
};
|
||||
|
||||
22
Unimay/Models/Episode.cs
Normal file
22
Unimay/Models/Episode.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Unimay.Models
|
||||
{
|
||||
public class Episode
|
||||
{
|
||||
[JsonPropertyName("number")]
|
||||
public int Number { get; set; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("hls")]
|
||||
public Hls Hls { get; set; }
|
||||
}
|
||||
|
||||
public class Hls
|
||||
{
|
||||
[JsonPropertyName("master")]
|
||||
public string Master { get; set; }
|
||||
}
|
||||
}
|
||||
23
Unimay/Models/ReleaseResponse.cs
Normal file
23
Unimay/Models/ReleaseResponse.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Unimay.Models
|
||||
{
|
||||
public class ReleaseResponse
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public string Code { get; set; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("year")]
|
||||
public string Year { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } // "Фільм" або "Телесеріал"
|
||||
|
||||
[JsonPropertyName("playlist")]
|
||||
public List<Episode> Playlist { get; set; }
|
||||
}
|
||||
}
|
||||
41
Unimay/Models/SearchResponse.cs
Normal file
41
Unimay/Models/SearchResponse.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Unimay.Models
|
||||
{
|
||||
public class SearchResponse
|
||||
{
|
||||
[JsonPropertyName("content")]
|
||||
public List<ReleaseInfo> Content { get; set; }
|
||||
|
||||
[JsonPropertyName("totalElements")]
|
||||
public int TotalElements { get; set; }
|
||||
}
|
||||
|
||||
public class ReleaseInfo
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public string Code { get; set; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("year")]
|
||||
public string Year { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } // "Фільм" або "Телесеріал"
|
||||
|
||||
[JsonPropertyName("names")]
|
||||
public Names Names { get; set; }
|
||||
}
|
||||
|
||||
public class Names
|
||||
{
|
||||
[JsonPropertyName("ukr")]
|
||||
public string Ukr { get; set; }
|
||||
|
||||
[JsonPropertyName("eng")]
|
||||
public string Eng { get; set; }
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lampac.csproj" />
|
||||
<Reference Include="Shared">
|
||||
<HintPath>..\..\Shared.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
173
Unimay/UnimayInvoke.cs
Normal file
173
Unimay/UnimayInvoke.cs
Normal file
@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Shared;
|
||||
using Shared.Models.Online.Settings;
|
||||
using Shared.Models;
|
||||
using System.Linq;
|
||||
using Unimay.Models;
|
||||
using Shared.Engine;
|
||||
using System.Net;
|
||||
|
||||
namespace Unimay
|
||||
{
|
||||
public class UnimayInvoke
|
||||
{
|
||||
private OnlinesSettings _init;
|
||||
private ProxyManager _proxyManager;
|
||||
private HybridCache _hybridCache;
|
||||
private Action<string> _onLog;
|
||||
|
||||
public UnimayInvoke(OnlinesSettings init, HybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
|
||||
{
|
||||
_init = init;
|
||||
_hybridCache = hybridCache;
|
||||
_onLog = onLog;
|
||||
_proxyManager = proxyManager;
|
||||
}
|
||||
|
||||
public async Task<SearchResponse> Search(string title, string original_title, int serial)
|
||||
{
|
||||
string memKey = $"unimay:search:{title}:{original_title}:{serial}";
|
||||
if (_hybridCache.TryGetValue(memKey, out SearchResponse searchResults))
|
||||
return searchResults;
|
||||
|
||||
try
|
||||
{
|
||||
string searchQuery = System.Web.HttpUtility.UrlEncode(title ?? original_title ?? "");
|
||||
string searchUrl = $"{_init.host}/release/search?page=0&page_size=10&title={searchQuery}";
|
||||
|
||||
var headers = httpHeaders(_init);
|
||||
SearchResponse root = await Http.Get<SearchResponse>(searchUrl, timeoutSeconds: 8, proxy: _proxyManager.Get(), headers: headers);
|
||||
|
||||
if (root == null || root.Content == null || root.Content.Count == 0)
|
||||
{
|
||||
// Refresh proxy on failure
|
||||
_proxyManager.Refresh();
|
||||
return null;
|
||||
}
|
||||
|
||||
_hybridCache.Set(memKey, root, cacheTime(30, init: _init));
|
||||
return root;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Unimay search error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReleaseResponse> Release(string code)
|
||||
{
|
||||
string memKey = $"unimay:release:{code}";
|
||||
if (_hybridCache.TryGetValue(memKey, out ReleaseResponse releaseDetail))
|
||||
return releaseDetail;
|
||||
|
||||
try
|
||||
{
|
||||
string releaseUrl = $"{_init.host}/release?code={code}";
|
||||
|
||||
var headers = httpHeaders(_init);
|
||||
ReleaseResponse root = await Http.Get<ReleaseResponse>(releaseUrl, timeoutSeconds: 8, proxy: _proxyManager.Get(), headers: headers);
|
||||
|
||||
if (root == null)
|
||||
{
|
||||
// Refresh proxy on failure
|
||||
_proxyManager.Refresh();
|
||||
return null;
|
||||
}
|
||||
|
||||
_hybridCache.Set(memKey, root, cacheTime(60, init: _init));
|
||||
return root;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_onLog($"Unimay release error: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<(string title, string year, string type, string url)> GetSearchResults(string host, SearchResponse searchResults, string title, string original_title, int serial)
|
||||
{
|
||||
var results = new List<(string title, string year, string type, string url)>();
|
||||
|
||||
foreach (var item in searchResults.Content)
|
||||
{
|
||||
// Filter by serial if specified (0: movie "Фільм", 1: serial "Телесеріал")
|
||||
if (serial != -1)
|
||||
{
|
||||
bool isMovie = item.Type == "Фільм";
|
||||
if ((serial == 0 && !isMovie) || (serial == 1 && isMovie))
|
||||
continue;
|
||||
}
|
||||
|
||||
string itemTitle = item.Names?.Ukr ?? item.Names?.Eng ?? item.Title;
|
||||
string releaseUrl = $"{host}/unimay?code={item.Code}&title={System.Web.HttpUtility.UrlEncode(itemTitle)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial={serial}";
|
||||
results.Add((itemTitle, item.Year, item.Type, releaseUrl));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public (string title, string link) GetMovieResult(string host, ReleaseResponse releaseDetail, string title, string original_title)
|
||||
{
|
||||
if (releaseDetail.Playlist == null || releaseDetail.Playlist.Count == 0)
|
||||
return (null, null);
|
||||
|
||||
var movieEpisode = releaseDetail.Playlist[0];
|
||||
string movieLink = $"{host}/unimay?code={releaseDetail.Code}&title={System.Web.HttpUtility.UrlEncode(title)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial=0&play=true";
|
||||
string movieTitle = movieEpisode.Title ?? title;
|
||||
|
||||
return (movieTitle, movieLink);
|
||||
}
|
||||
|
||||
public (string seasonName, string seasonUrl, string seasonId) GetSeasonInfo(string host, string code, string title, string original_title)
|
||||
{
|
||||
string seasonUrl = $"{host}/unimay?code={code}&title={System.Web.HttpUtility.UrlEncode(title)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial=1&s=1";
|
||||
return ("Сезон 1", seasonUrl, "1");
|
||||
}
|
||||
|
||||
public List<(string episodeTitle, string episodeUrl)> GetEpisodesForSeason(string host, ReleaseResponse releaseDetail, string title, string original_title)
|
||||
{
|
||||
var episodes = new List<(string episodeTitle, string episodeUrl)>();
|
||||
|
||||
if (releaseDetail.Playlist == null)
|
||||
return episodes;
|
||||
|
||||
foreach (var ep in releaseDetail.Playlist.Where(ep => ep.Number >= 1 && ep.Number <= 24).OrderBy(ep => ep.Number))
|
||||
{
|
||||
string epTitle = ep.Title ?? $"Епізод {ep.Number}";
|
||||
string epLink = $"{host}/unimay?code={releaseDetail.Code}&title={System.Web.HttpUtility.UrlEncode(title)}&original_title={System.Web.HttpUtility.UrlEncode(original_title ?? "")}&serial=1&s=1&e={ep.Number}&play=true";
|
||||
episodes.Add((epTitle, epLink));
|
||||
}
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
public string GetStreamUrl(Episode episode)
|
||||
{
|
||||
return episode.Hls?.Master;
|
||||
}
|
||||
|
||||
private List<HeadersModel> httpHeaders(OnlinesSettings init)
|
||||
{
|
||||
return new List<HeadersModel>()
|
||||
{
|
||||
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
||||
new HeadersModel("Referer", init.host),
|
||||
new HeadersModel("Accept", "application/json")
|
||||
};
|
||||
}
|
||||
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 = AppInit.conf.mikrotik ? mikrotik : AppInit.conf.multiaccess ? init != null && init.cache_time > 0 ? init.cache_time : multiaccess : home;
|
||||
if (ctime > multiaccess)
|
||||
ctime = multiaccess;
|
||||
|
||||
return TimeSpan.FromMinutes(ctime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"enable": true,
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"initspace": "Unimay.ModInit",
|
||||
"online": "Unimay.OnlineApi"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user