diff --git a/AnimeON/AnimeON.csproj b/AnimeON/AnimeON.csproj
new file mode 100644
index 0000000..c26a806
--- /dev/null
+++ b/AnimeON/AnimeON.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net9.0
+ library
+ true
+
+
+
+
+ ..\..\Shared.dll
+
+
+
+
\ No newline at end of file
diff --git a/AnimeON/Controller.cs b/AnimeON/Controller.cs
new file mode 100644
index 0000000..635a7bf
--- /dev/null
+++ b/AnimeON/Controller.cs
@@ -0,0 +1,192 @@
+using System.Text.Json;
+using Shared.Engine;
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Web;
+using System.Linq;
+using Shared;
+using Shared.Models.Templates;
+using AnimeON.Models;
+using System.Text.RegularExpressions;
+using Shared.Models.Online.Settings;
+using Shared.Models;
+using HtmlAgilityPack;
+
+namespace AnimeON.Controllers
+{
+ public class Controller : BaseOnlineController
+ {
+ ProxyManager proxyManager;
+
+ public Controller()
+ {
+ proxyManager = new ProxyManager(ModInit.AnimeON);
+ }
+
+ [HttpGet]
+ [Route("animeon")]
+ async public Task 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, bool rjson = false)
+ {
+ var init = await loadKit(ModInit.AnimeON);
+ if (!init.enable)
+ return Forbid();
+
+ var seasons = await search(init, 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);
+ if (fundubs != null)
+ {
+ foreach (var fundub in fundubs)
+ {
+ foreach (var player in fundub.Player)
+ {
+ allOptions.Add((season, fundub, player));
+ }
+ }
+ }
+ }
+
+ if (allOptions.Count == 0)
+ return Content("AnimeON", "text/html; charset=utf-8");
+
+ if (serial == 1)
+ {
+ if (s == -1) // Выбор сезона/озвучки
+ {
+ var season_tpl = new SeasonTpl(allOptions.Count);
+ for (int i = 0; i < allOptions.Count; i++)
+ {
+ var item = allOptions[i];
+ string translationName = $"[{item.player.Name}|S{item.season.Season}] {item.fundub.Fundub.Name}";
+ string link = $"{host}/animeon?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={i}";
+ season_tpl.Append(translationName, link, $"{i}");
+ }
+ return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8");
+ }
+ else // Вывод эпизодов
+ {
+ if (s >= allOptions.Count)
+ 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);
+ if (episodesData == null || episodesData.Episodes == null)
+ return Content("AnimeON", "text/html; charset=utf-8");
+
+ var movie_tpl = new MovieTpl(title, original_title, episodesData.Episodes.Count);
+ foreach (var ep in episodesData.Episodes.OrderBy(e => e.EpisodeNum))
+ {
+ 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);
+ }
+ return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8");
+ }
+ }
+ else // Фильм
+ {
+ 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);
+ 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();
+ string streamLink = !string.IsNullOrEmpty(firstEp.Hls) ? firstEp.Hls : firstEp.VideoUrl;
+ 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");
+ }
+ }
+
+ async Task> GetFundubs(OnlinesSettings init, int animeId)
+ {
+ string fundubsUrl = $"{init.host}/api/player/fundubs/{animeId}";
+ string fundubsJson = await Http.Get(fundubsUrl, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
+ if (string.IsNullOrEmpty(fundubsJson))
+ return null;
+
+ var fundubsResponse = JsonSerializer.Deserialize(fundubsJson);
+ return fundubsResponse?.FunDubs;
+ }
+
+ async Task GetEpisodes(OnlinesSettings init, 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() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
+ if (string.IsNullOrEmpty(episodesJson))
+ return null;
+
+ return JsonSerializer.Deserialize(episodesJson);
+ }
+
+ async ValueTask> search(OnlinesSettings init, 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 res))
+ return res;
+
+ try
+ {
+ var headers = new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) };
+
+ async Task> FindAnime(string query)
+ {
+ if (string.IsNullOrEmpty(query))
+ return null;
+
+ string searchUrl = $"{init.host}/api/anime/search?text={HttpUtility.UrlEncode(query)}";
+ string searchJson = await Http.Get(searchUrl, headers: headers);
+ if (string.IsNullOrEmpty(searchJson))
+ return null;
+
+ var searchResponse = JsonSerializer.Deserialize(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 { firstResult };
+ hybridCache.Set(memKey, list, cacheTime(5));
+ return list;
+ }
+
+ return null;
+ }
+ catch (Exception ex)
+ {
+ OnLog($"AnimeON error: {ex.Message}");
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/AnimeON/ModInit.cs b/AnimeON/ModInit.cs
new file mode 100644
index 0000000..c8e9d3a
--- /dev/null
+++ b/AnimeON/ModInit.cs
@@ -0,0 +1,24 @@
+using Shared;
+using Shared.Models.Online.Settings;
+
+namespace AnimeON
+{
+ public class ModInit
+ {
+ public static OnlinesSettings AnimeON;
+
+ ///
+ /// модуль загружен
+ ///
+ public static void loaded()
+ {
+ AnimeON = new OnlinesSettings("AnimeON", "https://animeon.club", streamproxy: false)
+ {
+ displayname = "🇯🇵 AnimeON"
+ };
+
+ // Виводити "уточнити пошук"
+ AppInit.conf.online.with_search.Add("animeon");
+ }
+ }
+}
\ No newline at end of file
diff --git a/AnimeON/Models/Models.cs b/AnimeON/Models/Models.cs
new file mode 100644
index 0000000..c45874e
--- /dev/null
+++ b/AnimeON/Models/Models.cs
@@ -0,0 +1,106 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace AnimeON.Models
+{
+ public class SearchResponseModel
+ {
+ [JsonPropertyName("result")]
+ public List Result { get; set; }
+
+ [JsonPropertyName("count")]
+ public int Count { get; set; }
+ }
+
+ public class SearchModel
+ {
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ [JsonPropertyName("titleUa")]
+ public string TitleUa { get; set; }
+
+ [JsonPropertyName("titleEn")]
+ public string TitleEn { get; set; }
+
+ [JsonPropertyName("releaseDate")]
+ public string Year { get; set; }
+
+ [JsonPropertyName("imdbId")]
+ public string ImdbId { get; set; }
+
+ [JsonPropertyName("season")]
+ public int Season { get; set; }
+ }
+
+ public class FundubsResponseModel
+ {
+ [JsonPropertyName("funDubs")]
+ public List FunDubs { get; set; }
+ }
+
+ public class FundubModel
+ {
+ [JsonPropertyName("fundub")]
+ public Fundub Fundub { get; set; }
+
+ [JsonPropertyName("player")]
+ public List Player { get; set; }
+ }
+
+ public class Fundub
+ {
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ }
+
+ public class Player
+ {
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+ }
+
+ public class EpisodeModel
+ {
+ [JsonPropertyName("episodes")]
+ public List Episodes { get; set; }
+ }
+
+ public class Episode
+ {
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ [JsonPropertyName("episode")]
+ public int EpisodeNum { get; set; }
+
+ [JsonPropertyName("fileUrl")]
+ public string Hls { get; set; }
+
+ [JsonPropertyName("videoUrl")]
+ public string VideoUrl { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ }
+
+ public class Movie
+ {
+ public string translation { get; set; }
+ public List<(string link, string quality)> links { get; set; }
+ public Shared.Models.Templates.SubtitleTpl? subtitles { get; set; }
+ public int season { get; set; }
+ public int episode { get; set; }
+ }
+
+ public class Result
+ {
+ public List movie { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/AnimeON/OnlineApi.cs b/AnimeON/OnlineApi.cs
new file mode 100644
index 0000000..2d9d0cc
--- /dev/null
+++ b/AnimeON/OnlineApi.cs
@@ -0,0 +1,25 @@
+using Shared.Models.Base;
+using System.Collections.Generic;
+
+namespace AnimeON
+{
+ public class OnlineApi
+ {
+ public static List<(string name, string url, string plugin, int index)> Events(string host, long id, string imdb_id, long kinopoisk_id, string title, string original_title, string original_language, int year, string source, int serial, string account_email)
+ {
+ var online = new List<(string name, string url, string plugin, int index)>();
+
+ var init = ModInit.AnimeON;
+ if (init.enable && !init.rip)
+ {
+ string url = init.overridehost;
+ if (string.IsNullOrEmpty(url))
+ url = $"{host}/animeon";
+
+ online.Add((init.displayname, url, "animeon", init.displayindex > 0 ? init.displayindex : online.Count));
+ }
+
+ return online;
+ }
+ }
+}
diff --git a/AnimeON/manifest.json b/AnimeON/manifest.json
new file mode 100644
index 0000000..0c653cb
--- /dev/null
+++ b/AnimeON/manifest.json
@@ -0,0 +1,6 @@
+{
+ "enable": true,
+ "version": 1,
+ "initspace": "AnimeON.ModInit",
+ "online": "AnimeON.OnlineApi"
+}
\ No newline at end of file
diff --git a/Uaflix/Controller.cs b/Uaflix/Controller.cs
index 2f9df56..6ec5108 100644
--- a/Uaflix/Controller.cs
+++ b/Uaflix/Controller.cs
@@ -5,18 +5,32 @@ using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Web;
using System.Linq;
-using System.Net.Http;
using HtmlAgilityPack;
using Shared;
using Shared.Models.Templates;
-using Uaflix.Models;
using System.Text.RegularExpressions;
using Shared.Models.Online.Settings;
-using Shared.Engine;
using Shared.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
{
ProxyManager proxyManager;
@@ -28,441 +42,238 @@ namespace Uaflix.Controllers
[HttpGet]
[Route("uaflix")]
- async public Task 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, bool rjson = false)
+ async public Task Index(long id, string imdb_id, long kinopoisk_id, string title, string original_title, string original_language, int year, string source, int serial, string account_email, string t, int s = -1, int e = -1, bool play = false, bool rjson = false)
{
var init = await loadKit(ModInit.UaFlix);
if (!init.enable)
return Forbid();
- var result = await search(init, imdb_id, kinopoisk_id, title, original_title, year, serial, s);
+ var episodesInfo = await search(init, imdb_id, kinopoisk_id, title, original_title, year, serial == 0);
+ if (episodesInfo == null)
+ return Content("Uaflix", "text/html; charset=utf-8");
- if (result == null || result.movie == null)
+ if (play)
{
- proxyManager.Refresh();
+ var episode = episodesInfo.FirstOrDefault(ep => ep.season == s && ep.episode == e);
+ if (serial == 0) // для фильма берем первый
+ episode = episodesInfo.FirstOrDefault();
+
+ if (episode == null)
+ return Content("Uaflix", "text/html; charset=utf-8");
+
+ var playResult = await ParseEpisode(init, episode.url);
+
+ if (!string.IsNullOrEmpty(playResult.ashdi_url))
+ {
+ string ashdi_kp = Regex.Match(playResult.ashdi_url, "/serial/([0-9]+)").Groups[1].Value;
+ if (!string.IsNullOrEmpty(ashdi_kp))
+ return Redirect($"/ashdi?kinopoisk_id={ashdi_kp}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&s={s}&e={e}");
+ }
+
+ if (playResult.streams != null && playResult.streams.Count > 0)
+ return Redirect(HostStreamProxy(init, playResult.streams.First().link));
+
return Content("Uaflix", "text/html; charset=utf-8");
}
if (serial == 1)
{
- var seasons = result.movie.GroupBy(e => e.season).ToDictionary(k => k.Key, v => v.ToList());
- OnLog($"Знайдено сезонів: {seasons.Count}");
- foreach (var season in seasons)
- {
- OnLog($"Сезон {season.Key}: {season.Value.Count} епізодів");
- }
-
- if (s == -1)
+ if (s == -1) // Выбор сезона
{
+ var seasons = episodesInfo.GroupBy(ep => ep.season).ToDictionary(k => k.Key, v => v.ToList());
var season_tpl = new SeasonTpl(seasons.Count);
foreach (var season in seasons.OrderBy(i => i.Key))
{
string link = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={season.Key}";
- season_tpl.Append(season.Key.ToString(), link, $"{season.Key}");
+ season_tpl.Append($"Сезон {season.Key}", link, $"{season.Key}");
}
-
return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8");
}
-
- var episodes = seasons.GetValueOrDefault>(s, null);
- OnLog($"Вибраний сезон: {s}, кількість епізодів: {episodes?.Count ?? 0}");
- if (episodes == null)
- return Content("Uaflix", "text/html; charset=utf-8");
-
+
+ // Выбор эпизода
+ var episodes = episodesInfo.Where(ep => ep.season == s).OrderBy(ep => ep.episode).ToList();
var movie_tpl = new MovieTpl(title, original_title, episodes.Count);
-
- foreach (var episode in episodes.OrderBy(e => e.episode))
+ foreach(var ep in episodes)
{
- var streamquality = new StreamQualityTpl();
- if (episode.links != null)
- {
- foreach (var item in episode.links)
- streamquality.Append(HostStreamProxy(init, item.link), item.quality);
- }
-
- var firstStream = streamquality.Firts();
- string videoLink = firstStream.link;
-
- string episodeName = episode.translation ?? $"Серія {episode.episode}";
- if (episode.translation?.StartsWith("Вийде:") == true)
- {
- episodeName = episode.translation;
- }
-
- movie_tpl.Append(episodeName, videoLink, streamquality: streamquality, subtitles: episode.subtitles);
+ string link = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&s={s}&e={ep.episode}&play=true";
+ movie_tpl.Append(ep.title, link);
}
-
return rjson ? Content(movie_tpl.ToJson(), "application/json; charset=utf-8") : Content(movie_tpl.ToHtml(), "text/html; charset=utf-8");
}
-
- if (result.movie != null)
+ else // Фильм
{
- var tpl = new MovieTpl(title, original_title, result.movie.Count);
-
- foreach (var movie in result.movie)
- {
- var streamquality = new StreamQualityTpl();
- foreach (var item in movie.links)
- streamquality.Append(HostStreamProxy(ModInit.UaFlix, item.link), item.quality);
-
- var firstStream = streamquality.Firts();
- if (string.IsNullOrEmpty(firstStream.link))
- continue;
-
- tpl.Append(
- movie.translation,
- firstStream.link,
- streamquality: streamquality,
- subtitles: movie.subtitles
- );
- }
-
- return rjson
- ? Content(tpl.ToJson(), "application/json; charset=utf-8")
- : Content(tpl.ToHtml(), "text/html; charset=utf-8");
+ string link = $"{host}/uaflix?imdb_id={imdb_id}&kinopoisk_id={kinopoisk_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&play=true";
+ var tpl = new MovieTpl(title, original_title, 1);
+ tpl.Append(title, link);
+ return rjson ? Content(tpl.ToJson(), "application/json; charset=utf-8") : Content(tpl.ToHtml(), "text/html; charset=utf-8");
}
-
- return Content("Uaflix", "text/html; charset=utf-8");
}
- async ValueTask search(OnlinesSettings init, string imdb_id, long kinopoisk_id, string title, string original_title, int year, int serial, int s = -1)
+ async ValueTask> search(OnlinesSettings init, string imdb_id, long kinopoisk_id, string title, string original_title, int year, bool isfilm = false)
{
- string memKey = $"UaFlix:view:{kinopoisk_id}:{imdb_id}:{s}";
- if (!hybridCache.TryGetValue(memKey, out Result res))
- {
- try
- {
- var proxy = proxyManager.Get();
+ string memKey = $"UaFlix:search:{kinopoisk_id}:{imdb_id}";
+ if (hybridCache.TryGetValue(memKey, out List res))
+ return res;
+ try
+ {
string filmTitle = !string.IsNullOrEmpty(title) ? title : original_title;
string searchUrl = $"{init.host}/index.php?do=search&subaction=search&story={HttpUtility.UrlEncode(filmTitle)}";
+ var headers = new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) };
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", init.host)
- };
-
- var searchHtml = await Http.Get(searchUrl, proxy: proxy, headers: headers);
+ var searchHtml = await Http.Get(searchUrl, headers: headers);
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;
-
- if (serial == 1)
+ foreach (var filmNode in filmNodes)
{
- var filmNode = doc.DocumentNode.SelectSingleNode("//a[contains(@class, 'sres-wrap')]");
- if (filmNode == null)
+ 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()))
{
- OnLog("filmNode is null");
- return null;
+ filmUrl = filmNode.GetAttributeValue("href", "");
+ break;
}
- filmUrl = filmNode.GetAttributeValue("href", "");
}
- else
- {
- var filmNodes = doc.DocumentNode.SelectNodes("//a[contains(@class, 'sres-wrap')]");
- if (filmNodes == null)
- {
- OnLog("No search results found");
- return null;
- }
- // First try to find with year
- string selectedFilmUrl = null;
- HtmlNode selectedFilmNode = null;
- foreach (var filmNode in filmNodes)
- {
- var h2Node = filmNode.SelectSingleNode(".//h2");
- if (h2Node == null) continue;
-
- string nodeTitle = h2Node.InnerText.Trim().ToLower();
- if (!nodeTitle.Contains(filmTitle.ToLower())) continue;
-
- var descNode = filmNode.SelectSingleNode(".//div[contains(@class, 'sres-desc')]");
- string desc = (descNode?.InnerText ?? "") + " " + nodeTitle;
- if (year > 0 && desc.Contains(year.ToString()))
- {
- selectedFilmUrl = filmNode.GetAttributeValue("href", "");
- selectedFilmNode = filmNode;
- OnLog($"Selected film URL with year in description: {selectedFilmUrl} for title '{filmTitle}' year {year}");
- break;
- }
- }
-
- // If no match with year in description, check year on film page or pick first title match
- if (string.IsNullOrEmpty(selectedFilmUrl))
- {
- foreach (var filmNode in filmNodes)
- {
- var h2Node = filmNode.SelectSingleNode(".//h2");
- if (h2Node == null) continue;
-
- string nodeTitle = h2Node.InnerText.Trim().ToLower();
- if (!nodeTitle.Contains(filmTitle.ToLower())) continue;
-
- string href = filmNode.GetAttributeValue("href", "");
- if (!href.StartsWith("http"))
- href = init.host + href;
-
- // Get film page and check year
- try
- {
- var filmPageHtml = await Http.Get(href, proxy: proxy, headers: headers);
- var filmDoc = new HtmlDocument();
- filmDoc.LoadHtml(filmPageHtml);
-
- var yearNode = filmDoc.DocumentNode.SelectSingleNode("//span[@itemprop='dateCreated' and @class='year']");
- int filmYear = 0;
- if (yearNode != null)
- {
- if (int.TryParse(yearNode.InnerText, out int parsedYear))
- {
- filmYear = parsedYear;
- }
- }
-
- if (year == 0 || filmYear == 0 || filmYear == year)
- {
- selectedFilmUrl = href;
- selectedFilmNode = filmNode;
- OnLog($"Selected film URL with year from page: {selectedFilmUrl} for title '{filmTitle}' year {year} (page year: {filmYear})");
- break;
- }
- else
- {
- OnLog($"Film year mismatch: requested {year}, page {filmYear}. Skipping film.");
- }
- }
- catch (Exception ex)
- {
- OnLog($"Error fetching film page {href}: {ex.Message}. Trying next film.");
- // If error fetching page, try next film
- continue;
- }
- }
-
- // If still no match, pick first title match
- if (string.IsNullOrEmpty(selectedFilmUrl))
- {
- foreach (var filmNode in filmNodes)
- {
- var h2Node = filmNode.SelectSingleNode(".//h2");
- if (h2Node == null) continue;
-
- string nodeTitle = h2Node.InnerText.Trim().ToLower();
- if (nodeTitle.Contains(filmTitle.ToLower()))
- {
- selectedFilmUrl = filmNode.GetAttributeValue("href", "");
- selectedFilmNode = filmNode;
- OnLog($"Selected first matching film URL: {selectedFilmUrl} for title '{filmTitle}' (no year match)");
- break;
- }
- }
- }
- }
-
- if (string.IsNullOrEmpty(selectedFilmUrl))
- {
- OnLog($"No matching film found for '{filmTitle}'");
- return null;
- }
-
- filmUrl = selectedFilmUrl;
- }
+ if (filmUrl == null)
+ filmUrl = filmNodes.First().GetAttributeValue("href", "");
if (!filmUrl.StartsWith("http"))
filmUrl = init.host + filmUrl;
- var filmHtml = await Http.Get(filmUrl, proxy: proxy, headers: headers);
+ if (isfilm)
+ {
+ res = new List() { new EpisodeLinkInfo() { url = filmUrl } };
+ hybridCache.Set(memKey, res, cacheTime(20));
+ return res;
+ }
+
+ var filmHtml = await Http.Get(filmUrl, headers: headers);
doc.LoadHtml(filmHtml);
- var movies = new List();
-
- if (serial == 1)
+ res = new List();
+ var episodeNodes = doc.DocumentNode.SelectNodes("//div[contains(@class, 'frels2')]//a[contains(@class, 'vi-img')]");
+ if (episodeNodes != null)
{
- var episodeNodes = doc.DocumentNode.SelectNodes("//div[contains(@class, 'frels2')]//a[contains(@class, 'vi-img')]");
- if (episodeNodes != null)
+ foreach (var episodeNode in episodeNodes.Reverse())
{
- OnLog($"Знайдено {episodeNodes.Count} епізодів");
- var uniqueEpisodes = new HashSet();
- foreach (var episodeNode in episodeNodes.Reverse())
+ 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)
{
- string episodeUrl = episodeNode.GetAttributeValue("href", "");
- if (!episodeUrl.StartsWith("http"))
- episodeUrl = init.host + episodeUrl;
-
- if (uniqueEpisodes.Add(episodeUrl))
+ res.Add(new EpisodeLinkInfo
{
- string episodeTitle = episodeNode.SelectSingleNode(".//div[@class='vi-rate']")?.InnerText.Trim();
-
- var match = System.Text.RegularExpressions.Regex.Match(episodeUrl, @"season-(\d+).*?episode-(\d+)");
- if (match.Success && match.Groups.Count > 2)
- {
- if (int.TryParse(match.Groups[1].Value, out int seasonNumber) && int.TryParse(match.Groups[2].Value, out int episodeNumber))
- {
- var episodeMovies = await ParseEpisode(init, episodeUrl, filmTitle, episodeTitle, seasonNumber, episodeNumber);
- if (episodeMovies != null)
- {
- movies.AddRange(episodeMovies);
- }
- }
- }
- }
- }
- }
- }
- else
- {
- var episodeMovies = await ParseEpisode(init, filmUrl, filmTitle, null, 1, 1);
- if (episodeMovies != null)
- movies.AddRange(episodeMovies);
- }
-
- if (movies.Count > 0)
- {
- res = new Result()
- {
- movie = movies
- };
- hybridCache.Set(memKey, res, cacheTime(5));
- proxyManager.Success();
- }
- }
- catch (Exception ex)
- {
- OnLog($"UaFlix error: {ex.Message}");
- }
- }
- return res;
- }
-
- async Task> ParseEpisode(OnlinesSettings init, string url, string filmTitle, string episodeTitle = null, int seasonNumber = 0, int episodeNumber = 0)
- {
- var movies = new List();
- try
- {
- var proxy = proxyManager.Get();
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", init.host)
- };
-
- string html = await Http.Get(url, proxy: proxy, headers: headers);
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- string cleanTranslation = episodeTitle ?? filmTitle;
- if (!string.IsNullOrEmpty(episodeTitle))
- {
- cleanTranslation = Regex.Replace(episodeTitle, @"^\d+\s+", "").Trim();
- }
-
- var movie = new Movie()
- {
- translation = cleanTranslation,
- links = new List<(string, string)>(),
- subtitles = null,
- season = seasonNumber,
- episode = episodeNumber
- };
-
- var iframe = doc.DocumentNode.SelectSingleNode("//div[contains(@class, 'video-box')]//iframe");
- if (iframe != null)
- {
- string iframeUrl = iframe.GetAttributeValue("src", "");
- if (!string.IsNullOrEmpty(iframeUrl))
- {
- if (iframeUrl.Contains("zetvideo.net"))
- {
- movie.links = await ParseAllZetvideoSources(iframeUrl);
- }
- else if (iframeUrl.Contains("ashdi.vip"))
- {
- movie.links = await ParseAllAshdiSources(iframeUrl);
- string? ashdiId = null;
- var idMatch = Regex.Match(iframeUrl, @"_(\d+)");
- if (idMatch.Success)
- ashdiId = idMatch.Groups[1].Value;
- else
- {
- idMatch = Regex.Match(iframeUrl, @"vod/(\d+)");
- if (idMatch.Success)
- ashdiId = idMatch.Groups[1].Value;
- }
-
- if (!string.IsNullOrEmpty(ashdiId))
- movie.subtitles = await GetAshdiSubtitles(ashdiId);
+ 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 (movie.links.Count == 0)
+ if (res.Count == 0)
{
- var soonNode = doc.DocumentNode.SelectSingleNode("//div[@class='soon-day']");
- if (soonNode != null)
- {
- movie.translation = $"Вийде: {soonNode.InnerText.Trim()}";
- }
+ 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 });
+ }
}
- movies.Add(movie);
+ if (res.Count > 0)
+ hybridCache.Set(memKey, res, cacheTime(20));
+
+ return res;
+ }
+ catch (Exception ex)
+ {
+ OnLog($"UaFlix search error: {ex.Message}");
+ }
+ return null;
+ }
+
+ async Task ParseEpisode(OnlinesSettings init, string url)
+ {
+ var result = new PlayResult() { streams = new List<(string, string)>() };
+ try
+ {
+ string html = await Http.Get(url, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) });
+ 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 movies;
+ return result;
}
+ #region Parsers
async Task> ParseAllZetvideoSources(string iframeUrl)
{
var result = new List<(string link, string quality)>();
- try
+ var html = await Http.Get(iframeUrl, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://zetvideo.net/") });
+ 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 proxy = proxyManager.Get();
- var headers = new List()
+ var match = Regex.Match(script.InnerText, @"file:\s*""([^""]+\.m3u8)");
+ if (match.Success)
{
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", "https://zetvideo.net/")
- };
- var html = await Http.Get(iframeUrl, proxy: proxy, headers: headers);
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- var sourceNodes = doc.DocumentNode.SelectNodes("//source[contains(@src, '.m3u8')]");
- if (sourceNodes != null)
- {
- foreach (var node in sourceNodes)
- {
- var url = node.GetAttributeValue("src", null);
- var label = node.GetAttributeValue("label", null) ?? node.GetAttributeValue("res", null) ?? "1080p";
- if (!string.IsNullOrEmpty(url))
- result.Add((url, label));
- }
- }
-
- if (result.Count == 0)
- {
- var scriptNodes = doc.DocumentNode.SelectNodes("//script");
- if (scriptNodes != null)
- {
- foreach (var script in scriptNodes)
- {
- var text = script.InnerText;
- var urls = Regex.Matches(text, @"https?:\/\/[^\s'""]+\.m3u8")
- .Cast()
- .Select(m => m.Value)
- .Distinct();
- foreach (var url in urls)
- result.Add((url, "1080p"));
- }
- }
+ result.Add((match.Groups[1].Value, "1080p"));
+ return result;
}
}
- catch (Exception ex)
+
+ var sourceNodes = doc.DocumentNode.SelectNodes("//source[contains(@src, '.m3u8')]");
+ if (sourceNodes != null)
{
- OnLog($"Zetvideo parse error: {ex.Message}");
+ foreach (var node in sourceNodes)
+ {
+ result.Add((node.GetAttributeValue("src", null), node.GetAttributeValue("label", null) ?? node.GetAttributeValue("res", null) ?? "1080p"));
+ }
}
return result;
}
@@ -470,104 +281,41 @@ namespace Uaflix.Controllers
async Task> ParseAllAshdiSources(string iframeUrl)
{
var result = new List<(string link, string quality)>();
- try
- {
- var proxy = proxyManager.Get();
- var headers = new List()
- {
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", "https://ashdi.vip/")
- };
-
- var html = await Http.Get(iframeUrl, proxy: proxy, headers: headers);
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
+ var html = await Http.Get(iframeUrl, headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") });
+ 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)
- {
- var url = node.GetAttributeValue("src", null);
- var label = node.GetAttributeValue("label", null) ?? node.GetAttributeValue("res", null) ?? "1080p";
- if (!string.IsNullOrEmpty(url))
- result.Add((url, label));
- }
- }
-
- if (result.Count == 0)
- {
- var scriptNodes = doc.DocumentNode.SelectNodes("//script");
- if (scriptNodes != null)
- {
- foreach (var script in scriptNodes)
- {
- var text = script.InnerText;
- var urls = Regex.Matches(text, @"https?:\/\/[^\s'""]+\.m3u8")
- .Cast()
- .Select(m => m.Value)
- .Distinct();
- foreach (var url in urls)
- result.Add((url, "1080p"));
- }
- }
- }
- }
- catch (Exception ex)
+ var sourceNodes = doc.DocumentNode.SelectNodes("//source[contains(@src, '.m3u8')]");
+ if (sourceNodes != null)
{
- OnLog($"Ashdi parse error: {ex.Message}");
+ foreach (var node in sourceNodes)
+ {
+ result.Add((node.GetAttributeValue("src", null), node.GetAttributeValue("label", null) ?? node.GetAttributeValue("res", null) ?? "1080p"));
+ }
}
return result;
}
async Task GetAshdiSubtitles(string id)
{
- try
+ var html = await Http.Get($"https://ashdi.vip/vod/{id}", headers: new List() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") });
+ string subtitle = new Regex("subtitle(\")?:\"([^\"]+)\"").Match(html).Groups[2].Value;
+ if (!string.IsNullOrEmpty(subtitle))
{
- var proxy = proxyManager.Get();
- string url = $"https://ashdi.vip/vod/{id}";
-
- var headers = new List()
+ var match = new Regex("\\[([^\\]]+)\\](https?://[^\\,]+)").Match(subtitle);
+ var st = new SubtitleTpl();
+ while (match.Success)
{
- new HeadersModel("User-Agent", "Mozilla/5.0"),
- new HeadersModel("Referer", "https://ashdi.vip/")
- };
-
- var html = await Http.Get(url, proxy: proxy, headers: headers);
-
- string subtitle = new Regex("subtitle(\")?:\"([^\"]+)\"").Match(html).Groups[2].Value;
- if (!string.IsNullOrEmpty(subtitle))
- {
- var match = new Regex("\\[([^\\]]+)\\](https?://[^\\,]+)").Match(subtitle);
- var st = new SubtitleTpl();
- while (match.Success)
- {
- st.Append(match.Groups[1].Value, match.Groups[2].Value);
- match = match.NextMatch();
- }
- if (!st.IsEmpty())
- return st;
+ st.Append(match.Groups[1].Value, match.Groups[2].Value);
+ match = match.NextMatch();
}
- }
- catch (Exception ex)
- {
- OnLog("Ashdi subtitle parse error: " + ex.Message);
+ if (!st.IsEmpty())
+ return st;
}
return null;
}
-
- public class Movie
- {
- public string translation { get; set; }
- public List<(string link, string quality)> links { get; set; }
- public SubtitleTpl? subtitles { get; set; }
- public int season { get; set; }
- public int episode { get; set; }
- }
-
- public class Result
- {
- public List movie { get; set; }
- }
+ #endregion
}
}