lampac/SISI/Controllers/NextHUB/ListController.cs
lampac-talks f843f04fd4 chore: initial commit 154.3
Signed-off-by: lampac-talks <lampac-talks@users.noreply.github.com>
2026-01-30 16:23:09 +03:00

630 lines
27 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using HtmlAgilityPack;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.Playwright;
using Shared.Models.CSharpGlobals;
using Shared.Models.SISI.NextHUB;
using Shared.PlaywrightCore;
using System.Text;
using System.Web;
namespace SISI.Controllers.NextHUB
{
public class ListController : BaseSisiController<NxtSettings>
{
public ListController() : base(default) { }
[HttpGet]
[Route("nexthub")]
async public Task<ActionResult> Index(string plugin, string search, string sort, string cat, string model, int pg = 1)
{
if (!AppInit.conf.sisi.NextHUB)
return OnError("disabled", rcache: false);
var _nxtInit = Root.goInit(plugin);
if (_nxtInit == null)
return OnError("init not found", rcache: false);
if (!string.IsNullOrEmpty(search) && string.IsNullOrEmpty(_nxtInit.search?.uri))
return OnError("search disable", rcache: false);
if (await IsRequestBlocked(_nxtInit, rch: _nxtInit.rch_access != null))
return badInitMsg;
string semaphoreKey = $"nexthub:{plugin}:{search}:{sort}:{cat}:{model}:{pg}";
if (init.menu?.customs != null)
{
foreach (var item in init.menu.customs)
semaphoreKey += $":{HttpContext.Request.Query[item.arg]}";
}
rhubFallback:
var cache = await InvokeCacheResult<List<PlaylistItem>>(semaphoreKey, init.cache_time, async e =>
{
#region contentParse
var contentParse = init.list.contentParse ?? init.contentParse;
if (!string.IsNullOrEmpty(search) && init.search?.contentParse != null)
contentParse = init.search.contentParse;
if (!string.IsNullOrEmpty(model) && init.model?.contentParse != null)
contentParse = init.model.contentParse;
#endregion
string html = await HttpRequest(init, plugin, pg, search, sort, cat, model);
var playlists = goPlaylist(requestInfo, host, contentParse, init, html, plugin);
if (playlists == null || playlists.Count == 0)
return e.Fail("playlists", refresh_proxy: string.IsNullOrEmpty(search));
return e.Success(playlists);
});
if (IsRhubFallback(cache))
goto rhubFallback;
var menu = new List<MenuItem>(3);
bool usedRoute = init.menu?.route != null || init.route?.eval != null;
#region search
if (string.IsNullOrEmpty(model) && init.search?.uri != null)
{
menu.Add(new MenuItem()
{
title = "Поиск",
search_on = "search_on",
playlist_url = $"{host}/nexthub?plugin={plugin}",
});
}
#endregion
#region sort
if (string.IsNullOrEmpty(search) && init.menu?.sort != null)
{
var msort = new MenuItem()
{
title = $"Сортировка: {init.menu.sort.FirstOrDefault(i => i.Value.Equals(sort, StringComparison.OrdinalIgnoreCase)).Key ?? init.menu.sort.First().Key}",
playlist_url = "submenu",
submenu = new List<MenuItem>()
};
string arg = usedRoute && init.menu.bind ? $"&cat={HttpUtility.UrlEncode(cat)}&model={HttpUtility.UrlEncode(model)}" : string.Empty;
foreach (var s in init.menu.sort)
{
msort.submenu.Add(new MenuItem()
{
title = s.Key,
playlist_url = $"{host}/nexthub?plugin={plugin}&sort={HttpUtility.UrlEncode(s.Value.Trim())}" + arg,
});
}
if (msort.submenu.Count > 0)
menu.Add(msort);
}
#endregion
#region categories
if (string.IsNullOrEmpty(search) && string.IsNullOrEmpty(model) && init.menu?.categories != null)
{
var categories = init.menu.categories.Where(i => i.Key != "format");
var mcat = new MenuItem()
{
title = $"Категории: {categories.FirstOrDefault(i => i.Value.Equals(cat, StringComparison.OrdinalIgnoreCase)).Key ?? "Выбрать"}",
playlist_url = "submenu",
submenu = new List<MenuItem>()
};
string arg = usedRoute && init.menu.bind ? $"&sort={HttpUtility.UrlEncode(sort)}" : string.Empty;
foreach (var s in categories)
{
mcat.submenu.Add(new MenuItem()
{
title = s.Key,
playlist_url = $"{host}/nexthub?plugin={plugin}&cat={HttpUtility.UrlEncode(s.Value.Trim())}" + arg,
});
}
if (mcat.submenu.Count > 0)
menu.Add(mcat);
}
#endregion
#region custom categories
if (string.IsNullOrEmpty(search) && string.IsNullOrEmpty(model) && init.menu?.customs != null)
{
foreach (var custom in init.menu.customs)
{
string argvalue = HttpContext.Request.Query[custom.arg];
var mcat = new MenuItem()
{
title = $"{custom.name}: {custom.submenu.FirstOrDefault(i => i.Value.Equals(argvalue, StringComparison.OrdinalIgnoreCase)).Key ?? "Выбрать"}",
playlist_url = "submenu",
submenu = new List<MenuItem>()
};
foreach (var s in custom.submenu)
{
mcat.submenu.Add(new MenuItem()
{
title = s.Key,
playlist_url = $"{host}/nexthub?plugin={plugin}&{custom.arg}={HttpUtility.UrlEncode(s.Value.Trim())}",
});
}
if (mcat.submenu.Count > 0)
menu.Add(mcat);
}
}
#endregion
#region total_pages
int total_pages = init.list.total_pages;
if (search != null && init.search != null)
total_pages = init.search.total_pages;
if (model != null && init.model != null)
total_pages = init.model.total_pages;
#endregion
return await PlaylistResult(cache,
menu.Count == 0 ? null : menu,
total_pages: total_pages
);
}
#region goPlaylist
public static List<PlaylistItem> goPlaylist(RequestModel requestInfo, string host, ContentParseSettings parse, NxtSettings init, string html, string plugin)
{
if (parse == null || string.IsNullOrEmpty(html))
return null;
if (init.debug)
Console.WriteLine(html);
var doc = new HtmlDocument();
doc.LoadHtml(html);
string eval = parse.eval;
if (!string.IsNullOrEmpty(eval) && eval.EndsWith(".cs"))
eval = FileCache.ReadAllText($"NextHUB/sites/{eval}");
if (string.IsNullOrEmpty(parse.nodes))
{
if (string.IsNullOrEmpty(eval))
return null;
var options = ScriptOptions.Default
.AddReferences(CSharpEval.ReferenceFromFile("Shared.dll"))
.AddImports("Shared.Models.SISI.Base")
.AddImports("Shared.Models.SISI")
.AddReferences(CSharpEval.ReferenceFromFile("HtmlAgilityPack.dll"))
.AddImports("HtmlAgilityPack");
return CSharpEval.Execute<List<PlaylistItem>>(eval, new NxtPlaylist(init, plugin, host, html, doc, new List<PlaylistItem>()), options);
}
var nodes = doc.DocumentNode.SelectNodes(parse.nodes);
if (nodes == null || nodes.Count == 0)
return null;
var playlists = new List<PlaylistItem>(nodes.Count);
foreach (var row in nodes)
{
#region nodeValue
string nodeValue(SingleNodeSettings nd)
{
string value = null;
if (nd != null)
{
if (string.IsNullOrEmpty(nd.node) && (!string.IsNullOrEmpty(nd.attribute) || nd.attributes != null))
{
if (nd.attributes != null)
{
foreach (var attr in nd.attributes)
{
var attrValue = row.GetAttributeValue(attr, null);
if (!string.IsNullOrEmpty(attrValue))
{
value = attrValue;
break;
}
}
}
else
{
value = row.GetAttributeValue(nd.attribute, null);
}
}
else
{
var inNode = row.SelectSingleNode(nd.node);
if (inNode != null)
{
if (nd.attributes != null)
{
foreach (var attr in nd.attributes)
{
var attrValue = inNode.GetAttributeValue(attr, null);
if (!string.IsNullOrEmpty(attrValue))
{
value = attrValue;
break;
}
}
}
else
{
value = (!string.IsNullOrEmpty(nd.attribute) ? inNode.GetAttributeValue(nd.attribute, null) : inNode.InnerText)?.Trim();
}
}
}
}
if (string.IsNullOrEmpty(value))
return null;
if (nd.format != null)
return CSharpEval.BaseExecute<string>($"return $\"{nd.format}\";", new NxtNodeValue(value, host));
return value;
}
#endregion
string name = nodeValue(parse.name);
string href = nodeValue(parse.href);
string img = nodeValue(parse.img);
string duration = nodeValue(parse.duration);
string quality = nodeValue(parse.quality);
string preview = nodeValue(parse.preview);
#region model
ModelItem? model = null;
if (parse.model != null)
{
string mname = nodeValue(parse.model.name);
string mhref = nodeValue(parse.model.href);
if (!string.IsNullOrEmpty(mname) && !string.IsNullOrEmpty(mhref))
{
model = new ModelItem()
{
name = mname,
uri = $"nexthub?plugin={plugin}&model={HttpUtility.UrlEncode(mhref)}"
};
}
}
#endregion
#region args
string args = string.Empty;
if (parse.args != null)
{
foreach (var a in parse.args)
{
string arg = nodeValue(a);
if (!string.IsNullOrEmpty(arg))
args += $"&{a.name}={HttpUtility.UrlEncode(arg)}";
}
}
#endregion
if (init.debug)
Console.WriteLine($"\n\nname: {name}\nhref: {href}\nimg: {img}\nduration: {duration}\nquality: {quality}\nmyarg: {args}\n\n{row.OuterHtml}");
if (!string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(href))
{
#region href
if (href.StartsWith("../"))
href = $"{init.host}/{href.Replace("../", "")}";
else if (href.StartsWith("//"))
href = $"https:{href}";
else if (href.StartsWith("/"))
href = init.host + href;
else if (!href.StartsWith("http"))
href = $"{init.host}/{href}";
#endregion
#region img
if (img != null)
{
img = img.Replace("&amp;", "&").Replace("\\", "");
if (img.StartsWith("../"))
img = $"{init.host}/{img.Replace("../", "")}";
else if (img.StartsWith("//"))
img = $"https:{img}";
else if (img.StartsWith("/"))
img = init.host + img;
else if (!img.StartsWith("http"))
img = $"{init.host}/{img}";
}
#endregion
if (!init.ignore_no_picture && string.IsNullOrEmpty(img))
continue;
#region preview
if (preview != null)
{
preview = preview.Replace("&amp;", "&").Replace("\\", "");
if (preview.StartsWith("../"))
preview = $"{init.host}/{preview.Replace("../", "")}";
else if (preview.StartsWith("//"))
preview = $"https:{preview}";
else if (preview.StartsWith("/"))
preview = init.host + preview;
else if (!preview.StartsWith("http"))
preview = $"{init.host}/{preview}";
if (init.streamproxy_preview)
preview = $"{host}/proxy/{ProxyLink.Encrypt(preview, string.Empty, verifyip: false, ex: DateTime.Now.AddHours(1))}";
}
#endregion
string clearText(string text)
{
if (string.IsNullOrEmpty(text))
return text;
text = text.Replace("&nbsp;", "");
return Regex.Replace(text, "<[^>]+>", "");
}
var pl = new PlaylistItem()
{
name = clearText(name),
video = $"nexthub/vidosik?uri={HttpUtility.UrlEncode($"{plugin}_-:-_{href}")}" + args,
preview = preview,
picture = img,
time = clearText(duration),
quality = clearText(quality),
myarg = args,
json = parse.json,
related = init.view != null ? init.view.related : false,
model = model,
bookmark = new Bookmark()
{
site = "nexthub",
href = $"{plugin}_-:-_{href}",
image = img
}
};
if (eval != null)
{
var options = ScriptOptions.Default
.AddReferences(CSharpEval.ReferenceFromFile("Shared.dll"))
.AddImports("Shared.Models.SISI.Base")
.AddImports("Shared.Models.SISI")
.AddReferences(CSharpEval.ReferenceFromFile("HtmlAgilityPack.dll"))
.AddImports("HtmlAgilityPack");
pl = CSharpEval.Execute<PlaylistItem>(eval, new NxtChangePlaylis(init, plugin, host, html, nodes, pl, row), options);
}
if (pl.json == false && (init.streamproxy || (init.geostreamproxy != null && init.geostreamproxy.Contains(requestInfo.Country))))
pl.video = $"{host}/proxy/{ProxyLink.Encrypt(pl.video, requestInfo.IP, HeadersModel.Init(init.headers_stream))}";
if (pl != null)
playlists.Add(pl);
}
}
return playlists;
}
#endregion
#region ContentAsync
async Task<string> ContentAsync(NxtSettings init, string url, List<HeadersModel> headers, (string ip, string username, string password) proxy, string search, string sort, string cat, string model, int pg)
{
try
{
var conf = string.IsNullOrEmpty(search) ? init.list : init.search;
using (var browser = new PlaywrightBrowser(init.priorityBrowser))
{
var page = await browser.NewPageAsync(init.plugin, headers?.ToDictionary(), proxy: proxy, keepopen: init.keepopen).ConfigureAwait(false);
if (page == default)
return null;
if (init.cookies != null)
await page.Context.AddCookiesAsync(init.cookies).ConfigureAwait(false);
string routeEval = conf.routeEval;
if (!string.IsNullOrEmpty(routeEval) && routeEval.EndsWith(".cs"))
routeEval = FileCache.ReadAllText($"NextHUB/sites/{routeEval}");
await page.RouteAsync("**/*", async route =>
{
try
{
#region routeEval
if (routeEval != null)
{
var options = ScriptOptions.Default
.AddReferences(CSharpEval.ReferenceFromFile("Microsoft.Playwright.dll"))
.AddImports("Microsoft.Playwright");
bool _next = await CSharpEval.ExecuteAsync<bool>(routeEval, new NxtRoute(route, HttpContext.Request.Query, url, search, sort, cat, model, pg), options);
if (!_next)
return;
}
#endregion
if (conf.patternAbort != null && Regex.IsMatch(route.Request.Url, conf.patternAbort, RegexOptions.IgnoreCase))
{
PlaywrightBase.ConsoleLog(() => $"Playwright: Abort {route.Request.Url}");
await route.AbortAsync();
return;
}
if (init.abortMedia || init.fullCacheJS)
{
if (await PlaywrightBase.AbortOrCache(page, route, abortMedia: init.abortMedia, fullCacheJS: init.fullCacheJS))
return;
}
else
{
PlaywrightBase.ConsoleLog(() => $"Playwright: {route.Request.Method} {route.Request.Url}");
}
await browser.ClearContinueAsync(route, page);
}
catch (Exception ex) { PlaywrightBase.ConsoleLog(() => ex.Message); }
});
string content = null;
PlaywrightBase.GotoAsync(page, url);
if (!string.IsNullOrEmpty(conf.waitForSelector))
{
try
{
await page.WaitForSelectorAsync(conf.waitForSelector, new PageWaitForSelectorOptions
{
Timeout = conf.waitForSelector_timeout
}).ConfigureAwait(false);
}
catch { }
content = await page.ContentAsync().ConfigureAwait(false);
}
else
{
await page.WaitForLoadStateAsync(LoadState.NetworkIdle, new PageWaitForLoadStateOptions() { Timeout = 20_000 }).ConfigureAwait(false);
content = await page.ContentAsync().ConfigureAwait(false);
}
PlaywrightBase.WebLog("GET", url, content, proxy);
return content;
}
}
catch
{
return null;
}
}
#endregion
#region HttpRequest
async Task<string> HttpRequest(
NxtSettings init, string plugin, int pg, string search, string sort, string cat, string model
)
{
string data = !string.IsNullOrEmpty(search) ? (init.search?.data ?? init.list.data) : init.list.data;
#region encoding
Encoding encodingRequest = default, encodingResponse = default;
if (!string.IsNullOrEmpty(search))
{
if (init.search?.encodingRequest != null)
encodingRequest = Encoding.GetEncoding(init.search.encodingRequest);
if (init.search?.encodingResponse != null)
encodingResponse = Encoding.GetEncoding(init.search.encodingResponse);
}
if (encodingRequest == default && init.list?.encodingRequest != null)
encodingRequest = Encoding.GetEncoding(init.list.encodingRequest);
if (encodingResponse == default && init.list?.encodingResponse != null)
encodingResponse = Encoding.GetEncoding(init.list.encodingResponse);
#endregion
#region формируем url
string url = $"{init.corsHost()}/{(pg == 1 && init.list.firstpage != null ? init.list.firstpage : init.list.uri)}";
if (!string.IsNullOrEmpty(search))
{
string uri = pg == 1 && init.search?.firstpage != null ? init.search.firstpage : init.search?.uri;
string _s = encodingRequest != default ? HttpUtility.UrlEncode(search, encodingRequest) : HttpUtility.UrlEncode(search);
url = $"{init.corsHost()}/{uri}".Replace("{search}", _s);
}
else
{
if (!string.IsNullOrEmpty(sort))
url = $"{init.corsHost()}/{sort}";
else if (!string.IsNullOrEmpty(cat))
url = $"{init.corsHost()}/{init.menu.formatcat(cat)}";
else if (!string.IsNullOrEmpty(model))
{
url = $"{init.corsHost()}/{model}";
if (init.model?.uri != null)
url = init.model.uri.Replace("{host}", init.corsHost()).Replace("{model}", model);
else if (init.model?.format != null)
{
string eval = $"return $\"{init.model.format}\";";
url = CSharpEval.BaseExecute<string>(eval, new NxtMenuRoute(init.corsHost(), plugin, url, search, cat, sort, model, HttpContext.Request.Query, pg));
}
}
else if (init.menu?.customs != null)
{
foreach (var c in init.menu.customs)
{
if (HttpContext.Request.Query.ContainsKey(c.arg))
url = $"{init.corsHost()}/{c.format.Replace("{value}", HttpContext.Request.Query[c.arg])}";
}
}
if (init.menu?.route != null)
{
string goroute(string name)
{
if (init.menu.route.TryGetValue(name, out string value))
return value;
if (init.menu.route.TryGetValue("-", out value))
return value;
return string.Empty;
}
string eval = $"return (cat != null && sort != null) ? $\"{goroute("catsort")}\" : (model != null && sort != null) ? $\"{goroute("modelsort")}\" : model != null ? $\"{goroute("model")}\" : cat != null ? $\"{goroute("cat")}\" : sort != null ? $\"{goroute("sort")}\" : \"{url}\";";
url = CSharpEval.BaseExecute<string>(eval, new NxtMenuRoute(init.corsHost(), plugin, url, search, cat, sort, model, HttpContext.Request.Query, pg));
}
}
if (init.route?.eval != null)
url = CSharpEval.Execute<string>(init.route.eval, new NxtMenuRoute(init.corsHost(), plugin, url, search, cat, sort, model, HttpContext.Request.Query, pg));
#endregion
if (!string.IsNullOrEmpty(data))
{
if (!string.IsNullOrEmpty(search))
{
string _s = encodingRequest != default ? HttpUtility.UrlEncode(search, encodingRequest) : HttpUtility.UrlEncode(search);
data = data.Replace("{search}", _s);
}
data = data.Replace("{page}", pg.ToString());
return rch?.enable == true
? await rch.Post(url.Replace("{page}", pg.ToString()), data, httpHeaders(init))
: await Http.Post(url.Replace("{page}", pg.ToString()), data, encoding: encodingResponse, headers: httpHeaders(init), proxy: proxy, timeoutSeconds: init.timeout, httpversion: init.httpversion);
}
else
{
return rch?.enable == true
? await rch.Get(url.Replace("{page}", pg.ToString()), httpHeaders(init))
: init.priorityBrowser == "http" ? await Http.Get(url.Replace("{page}", pg.ToString()), encoding: encodingResponse, headers: httpHeaders(init), proxy: proxy, timeoutSeconds: init.timeout, httpversion: init.httpversion)
: init.list.viewsource ? await PlaywrightBrowser.Get(init, url.Replace("{page}", pg.ToString()), httpHeaders(init), proxy_data, cookies: init.cookies)
: await ContentAsync(init, url.Replace("{page}", pg.ToString()), httpHeaders(init), proxy_data, search, sort, cat, model, pg);
}
}
#endregion
}
}