chore: initial commit 154.3

Signed-off-by: lampac-talks <lampac-talks@users.noreply.github.com>
This commit is contained in:
lampac-talks 2026-01-30 16:22:36 +03:00
commit f843f04fd4
623 changed files with 78461 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

343
.gitignore vendored Normal file
View File

@ -0,0 +1,343 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
Properties/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
!Build

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,724 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Shared;
using Shared.Engine;
using Shared.Models.Module;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IO = System.IO;
namespace Lampac.Controllers
{
public class AdminController : BaseController
{
#region TryAuthorizeAdmin
bool TryAuthorizeAdmin(string passwd, out ActionResult result)
{
result = null;
if (AppInit.rootPasswd == "termux")
{
HttpContext.Response.Cookies.Append("passwd", "termux");
return true;
}
if (string.IsNullOrWhiteSpace(passwd))
HttpContext.Request.Cookies.TryGetValue("passwd", out passwd);
if (string.IsNullOrWhiteSpace(passwd))
{
result = Redirect("/admin/auth");
return false;
}
string ipKey = $"Accsdb:auth:IP:{requestInfo.IP}";
if (!memoryCache.TryGetValue(ipKey, out ConcurrentDictionary<string, byte> passwds))
{
passwds = new ConcurrentDictionary<string, byte>();
memoryCache.Set(ipKey, passwds, DateTime.Today.AddDays(1));
}
passwds.TryAdd(passwd, 0);
if (passwds.Count > 10)
{
result = Content("Too many attempts, try again tomorrow.");
return false;
}
if (AppInit.rootPasswd == passwd)
return true;
HttpContext.Response.Cookies.Delete("passwd");
result = Redirect("/admin/auth");
return false;
}
#endregion
#region admin / auth
[HttpGet]
[HttpPost]
[Route("/admin")]
[Route("/admin/auth")]
public ActionResult Authorization([FromForm]string parol)
{
string passwd = parol?.Trim();
if (string.IsNullOrWhiteSpace(passwd))
HttpContext.Request.Cookies.TryGetValue("passwd", out passwd);
if (string.IsNullOrWhiteSpace(passwd))
{
string html = @"
<!DOCTYPE html>
<html>
<head>
<title>Authorization</title>
</head>
<body>
<style type=""text/css"">
* {
box-sizing: border-box;
outline: none;
}
body{
padding: 40px;
font-family: sans-serif;
}
label{
display: block;
font-weight: 700;
margin-bottom: 8px;
}
input,
textarea,
select{
width: 340px;
padding: 8px;
}
button{
padding: 10px;
}
form > * + *{
margin-top: 20px;
}
</style>
<form method=""post"" action=""/admin/auth"" id=""form"">
<div>
<input type=""text"" name=""parol"" placeholder=""пароль из файла passwd""></input>
</div>
<button type=""submit"">войти</button>
</form>
<div style=""margin-top: 4em;""><b style=""color: cadetblue;"">Выполните одну из команд через ssh</b><br><br>
cat /home/lampac/passwd<br><br>
docker exec -it lampac cat passwd
</div>
</body>
</html>
";
return Content(html, "text/html; charset=utf-8");
}
else
{
if (!TryAuthorizeAdmin(passwd, out ActionResult badresult))
return badresult;
HttpContext.Response.Cookies.Append("passwd", passwd);
return renderAdmin();
}
}
ActionResult renderAdmin()
{
string adminHtml = IO.File.Exists("wwwroot/mycontrol/index.html") ? IO.File.ReadAllText("wwwroot/mycontrol/index.html") : IO.File.ReadAllText("wwwroot/control/index.html");
return Content(adminHtml, contentType: "text/html; charset=utf-8");
}
#endregion
#region init
[HttpPost]
[Route("/admin/init/save")]
public ActionResult InitSave([FromForm]string json)
{
if (!TryAuthorizeAdmin(null, out ActionResult badresult))
return badresult;
try
{
JsonConvert.DeserializeObject<AppInit>(json);
}
catch (Exception ex) { return Json(new { error = true, ex = ex.Message }); }
var jo = JsonConvert.DeserializeObject<JObject>(json);
JToken users = null;
var accsdbNode = jo["accsdb"] as JObject;
if (accsdbNode != null)
{
var usersNode = accsdbNode["users"];
if (usersNode != null)
{
users = usersNode.DeepClone();
accsdbNode.Remove("users");
IO.File.WriteAllText("users.json", JsonConvert.SerializeObject(users, Formatting.Indented));
}
}
IO.File.WriteAllText("init.conf", JsonConvert.SerializeObject(jo, Formatting.Indented));
return Json(new { success = true });
}
[HttpGet]
[Route("/admin/init/custom")]
public ActionResult InitCustom()
{
if (!TryAuthorizeAdmin(null, out ActionResult badresult))
return badresult;
string json = IO.File.Exists("init.conf") ? IO.File.ReadAllText("init.conf") : null;
if (json != null && !json.Trim().StartsWith("{"))
json = "{" + json + "}";
var ob = json != null ? JsonConvert.DeserializeObject<JObject>(json) : new JObject { };
return ContentTo(JsonConvert.SerializeObject(ob));
}
[HttpGet]
[Route("/admin/init/current")]
public ActionResult InitCurrent()
{
if (!TryAuthorizeAdmin(null, out ActionResult badresult))
return badresult;
return Content(JsonConvert.SerializeObject(AppInit.conf), contentType: "application/json; charset=utf-8");
}
[HttpGet]
[Route("/admin/init/default")]
public ActionResult InitDefault()
{
if (!TryAuthorizeAdmin(null, out ActionResult badresult))
return badresult;
return Content(JsonConvert.SerializeObject(new AppInit()), contentType: "application/json; charset=utf-8");
}
[HttpGet]
[Route("/admin/init/example")]
public ActionResult InitExample()
{
if (!TryAuthorizeAdmin(null, out ActionResult badresult))
return badresult;
return Content(IO.File.Exists("example.conf") ? IO.File.ReadAllText("example.conf") : string.Empty);
}
#endregion
#region sync/init
[HttpGet]
[Route("/admin/sync/init")]
public ActionResult Synchtml()
{
if (!TryAuthorizeAdmin(null, out ActionResult badresult))
return badresult;
string html = @"
<!DOCTYPE html>
<html>
<head>
<title>Редактор sync.conf</title>
</head>
<body>
<style type=""text/css"">
* {
box-sizing: border-box;
outline: none;
}
body{
padding: 40px;
font-family: sans-serif;
}
label{
display: block;
font-weight: 700;
margin-bottom: 8px;
}
input,
textarea,
select{
width: 100%;
padding: 10px;
}
button{
padding: 10px;
}
form > * + *{
margin-top: 30px;
}
</style>
<form method=""post"" action="""" id=""form"">
<div>
<label>Ваш sync.conf
<textarea id=""value"" name=""value"" rows=""30"">{conf}</textarea>
</div>
<button type=""submit"">Сохранить</button>
</form>
<script type=""text/javascript"">
document.getElementById('form').addEventListener(""submit"", (e) => {
let json = document.getElementById('value').value
e.preventDefault()
try{
let formData = new FormData()
formData.append('json', json)
fetch('/admin/sync/init/save',{
method: ""POST"",
body: formData
})
.then((response)=>{
if (!response.ok) {
return response.json().then(err => {
throw new Error(err.ex || 'Не удалось сохранить настройки');
});
}
return response.json();
})
.then((data)=>{
if (data.success) {
alert('Сохранено');
} else if (data.error) {
throw new Error(data.ex);
} else {
throw new Error('Не удалось сохранить настройки');
}
})
.catch((e)=>{
alert(e.message)
})
}
catch(e){
alert('Ошибка: ' + e.message)
}
})
</script>
</body>
</html>
";
string conf = IO.File.Exists("sync.conf") ? IO.File.ReadAllText("sync.conf") : string.Empty;
return Content(html.Replace("{conf}", conf), contentType: "text/html; charset=utf-8");
}
[HttpPost]
[Route("/admin/sync/init/save")]
public ActionResult SyncSave([FromForm] string json)
{
if (!TryAuthorizeAdmin(null, out ActionResult badresult))
return badresult;
try
{
string testjson = json.Trim();
if (!testjson.StartsWith("{"))
testjson = "{" + testjson + "}";
JsonConvert.DeserializeObject<AppInit>(testjson);
}
catch (Exception ex) { return Json(new { error = true, ex = ex.Message }); }
IO.File.WriteAllText("sync.conf", json);
return Json(new { success = true });
}
#endregion
#region manifest
[HttpGet]
[HttpPost]
[Route("/admin/manifest/install")]
public Task ManifestInstallHtml(string online, string sisi, string jac, string dlna, string tracks, string ts, string catalog, string merch, string eng)
{
if (IO.File.Exists("module/manifest.json"))
{
if (!TryAuthorizeAdmin(null, out ActionResult badresult))
{
HttpContext.Response.Redirect("/admin/auth");
return Task.CompletedTask;
}
}
HttpContext.Response.ContentType = "text/html; charset=utf-8";
if (AppInit.rootPasswd == "termux")
return HttpContext.Response.WriteAsync("В termux операция недоступна");
bool isEditManifest = false;
if (IO.File.Exists("module/manifest.json"))
{
if (HttpContext.Request.Cookies.TryGetValue("passwd", out string passwd) && passwd == AppInit.rootPasswd)
{
isEditManifest = true;
}
else
{
HttpContext.Response.Redirect("/admin");
return Task.CompletedTask;
}
}
if (HttpContext.Request.Method == "POST")
{
var modules = new List<string>(10);
if (online == "on")
modules.Add("{\"enable\":true,\"dll\":\"Online.dll\"}");
if (sisi == "on")
modules.Add("{\"enable\":true,\"dll\":\"SISI.dll\"}");
if (!string.IsNullOrEmpty(jac))
{
modules.Add("{\"enable\":true,\"initspace\":\"Jackett.ModInit\",\"dll\":\"JacRed.dll\"}");
#region JacRed.conf
if (jac == "fdb")
{
var jacPath = "module/JacRed.conf";
JObject jj;
if (IO.File.Exists(jacPath))
{
string txt = IO.File.ReadAllText(jacPath).Trim();
if (string.IsNullOrEmpty(txt))
jj = new JObject();
else
{
if (!txt.StartsWith("{"))
txt = "{" + txt + "}";
try
{
jj = JsonConvert.DeserializeObject<JObject>(txt) ?? new JObject();
}
catch
{
jj = new JObject();
}
}
}
else
{
jj = new JObject();
}
jj["typesearch"] = "red";
IO.File.WriteAllText(jacPath, JsonConvert.SerializeObject(jj, Formatting.Indented));
}
#endregion
}
if (dlna == "on")
modules.Add("{\"enable\":true,\"dll\":\"DLNA.dll\"}");
if (tracks == "on")
modules.Add("{\"enable\":true,\"initspace\":\"Tracks.ModInit\",\"dll\":\"Tracks.dll\"}");
if (ts == "on")
modules.Add("{\"enable\":true,\"initspace\":\"TorrServer.ModInit\",\"dll\":\"TorrServer.dll\"}");
if (catalog == "on")
modules.Add("{\"enable\":true,\"initspace\":\"Catalog.ModInit\",\"dll\":\"Catalog.dll\"}");
if (merch == "on")
modules.Add("{\"enable\":false,\"dll\":\"Merchant.dll\"}");
IO.File.WriteAllText("module/manifest.json", $"[{string.Join(",", modules)}]");
if (eng != "on")
UpdateInitConf(j => j["disableEng"] = true);
if (isEditManifest)
{
return HttpContext.Response.WriteAsync("Перезагрузите lampac для изменения настроек");
}
else
{
#region frontend cloudflare
if (HttpContext.Request.Headers.TryGetValue("CF-Connecting-IP", out var xip) && !string.IsNullOrEmpty(xip))
{
UpdateInitConf(j =>
{
var listen = j["listen"] as JObject;
if (listen == null)
{
listen = new JObject();
j["listen"] = listen;
}
listen["frontend"] = "cloudflare";
});
}
#endregion
#region htmlSuccess
#region shared_passwd
string shared_passwd = CrypTo.unic(8).ToLower();
UpdateInitConf(j =>
{
var accsdb = j["accsdb"] as JObject;
if (accsdb == null)
{
accsdb = new JObject();
j["accsdb"] = accsdb;
}
accsdb["enable"] = true;
accsdb["shared_passwd"] = shared_passwd;
});
string sharedBlock = $@"<div class=""block""><b>Авторизация в Lampa</b><br /><br />
Пароль: {shared_passwd}
</div><hr />";
#endregion
string htmlSuccesds = $@"<!DOCTYPE html>
<html>
<head>
<meta charset=""utf-8"" />
<title>Настройка завершена</title>
</head>
<body>
<style type=""text/css"">
* {{ box-sizing: border-box; outline: none; }}
body {{ padding: 40px; font-family: sans-serif; }}
h1 {{ color: #2b7a78; margin-bottom: 1em; text-align: center; }}
hr {{ margin-top: 1em; margin-bottom: 2em; }}
.block {{ margin-top: 20px; }}
pre {{ background: #f5f5f5; padding: 12px; border-radius: 6px; white-space: pre-wrap; word-break: break-all; }}
</style>
<h1>Настройка завершена</h1>
{sharedBlock}
<div class=""block"">
<b>Админ панель</b><br /><br />
рес: {host}/admin<br />
Пароль: {IO.File.ReadAllText("passwd")}
</div>
<hr />
<div class=""block"">
<div style=""margin-top:10px"">
<b>Media Station X</b><br /><br />
Settings -> Start Parameter -> Setup<br />
Enter current ip address and port: {HttpContext.Request.Host.Value}<br /><br />
Убрать/Добавить адреса можно в /home/lampac/msx.json
</div>
</div>
<hr />
<div class=""block"">
<b>Виджет для Samsung</b><br /><br />
{host}/samsung.wgt
</div>
<hr />
<div class=""block"">
<b>Для android apk</b><br /><br />
Зажмите кнопку назад и введите новый адрес: {host}
</div>
<hr />
<div class=""block"">
<b>Плагины для Lampa</b><br /><br />
Заходим в настройки - расширения, жмем на кнопку ""добавить плагин"". В окне ввода вписываем адрес плагина {host}/on.js и перезагружаем виджет удерживая кнопку ""назад"" пока виджет не закроется.
</div>
<hr />
<div class=""block"">
<b>TorrServer (если установлен)</b><br /><br />
{host}/ts
</div>
</body>
</html>";
return HttpContext.Response.WriteAsync(htmlSuccesds).ContinueWith(t => Shared.Startup.appReload.Reload());
#endregion
}
}
#region renderHtml
string renderHtml()
{
var modules = IO.File.Exists("module/manifest.json") ? JsonConvert.DeserializeObject<List<RootModule>>(IO.File.ReadAllText("module/manifest.json")) : null;
string IsChecked(string name, string def)
{
if (modules == null)
return def;
bool res = modules.FirstOrDefault(m => m.dll == name)?.enable ?? false;
return res ? "checked" : string.Empty;
}
return $@"
<!DOCTYPE html>
<html>
<head>
<meta charset=""utf-8"" />
<title>Модули</title>
</head>
<body>
<style type='text/css'>
* {{
box-sizing: border-box;
outline: none;
}}
body{{
padding: 40px;
font-family: sans-serif;
}}
label{{
display: block;
font-weight: 700;
margin-bottom: 8px;
}}
input,
select{{
margin: 10px;
margin-left: 0px;
}}
button{{
padding: 10px;
}}
form > * + *{{
margin-top: 30px;
}}
.flex{{
display: flex;
align-items: center;
}}
</style>
<form method='post' action='/admin/manifest/install' id='form'>
<div>
<label>Установка модулей</label>
<div class='flex'>
<input name='online' type='checkbox' {IsChecked("Online.dll", "checked")} /> Онлайн балансеры Rezka, Filmix, etc
</div>
<div class='flex'>
&nbsp; &nbsp; &nbsp; <input name='eng' type='checkbox' checked /> ENG балансеры
</div>
<div class='flex'>
<input name='sisi' type='checkbox' {IsChecked("SISI.dll", "checked")} /> Клубничка 18+, PornHub, Xhamster, etc
</div>
<div class='flex'>
<input name='catalog' type='checkbox' {IsChecked("Catalog.dll", "checked")} /> Альтернативные источники каталога cub и tmdb
</div>
<div class='flex'>
<input name='dlna' type='checkbox' {IsChecked("DLNA.dll", "checked")} /> DLNA - Загрузка торрентов и просмотр медиа файлов с локального устройства
</div>
<div class='flex'>
<input name='ts' type='checkbox' {IsChecked("TorrServer.dll", "checked")} /> TorrServer - возможность просматривать торренты в онлайн
</div>
<div class='flex'>
<input name='tracks' type='checkbox' {IsChecked("Tracks.dll", "checked")} /> Tracks - транскодинг видео и замена названий аудиодорожек с rus1, rus2 на читаемые LostFilm, HDRezka, etc
</div>
<div class='flex'>
<input name='merch' type='checkbox' {IsChecked("Merchant.dll", "")} /> Автоматизация оплаты FreeKassa, Streampay, Litecoin, CryptoCloud
</div>
<br><br>
<label>Поиск торрентов</label>
<div class='flex'>
<input name='jac' type='radio' value='webapi' checked /> Быстрый поиск по внешним базам JacRed, Rutor, Kinozal, NNM-Club, Rutracker, etc
</div>
<div class='flex'>
<input name='jac' type='radio' value='fdb' /> Локальный jacred.xyz (не рекомендуется ставить на домашние устройства) - 2GB HDD
</div>
</div>
<button type='submit'>{(isEditManifest ? "Изменить настройки" : "Завершить настройку")}</button></form></body></html>";
}
#endregion
return HttpContext.Response.WriteAsync(renderHtml());
}
#endregion
#region UpdateInitConf
void UpdateInitConf(Action<JObject> modify)
{
JObject jo;
if (IO.File.Exists("init.conf"))
{
string initconf = IO.File.ReadAllText("init.conf").Trim();
if (string.IsNullOrEmpty(initconf))
jo = new JObject();
else
{
if (!initconf.StartsWith("{"))
initconf = "{" + initconf + "}";
try
{
jo = JsonConvert.DeserializeObject<JObject>(initconf) ?? new JObject();
}
catch
{
jo = new JObject();
}
}
}
else
{
jo = new JObject();
}
modify?.Invoke(jo);
IO.File.WriteAllText("init.conf", JsonConvert.SerializeObject(jo, Formatting.Indented));
}
#endregion
}
}

View File

@ -0,0 +1,798 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Shared;
using Shared.Engine;
using Shared.Engine.Utilities;
using Shared.Models;
using Shared.Models.SQL;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
namespace Lampac.Controllers
{
public class BookmarkController : BaseController
{
#region bookmark.js
[HttpGet]
[AllowAnonymous]
[Route("bookmark.js")]
[Route("bookmark/js/{token}")]
public ActionResult BookmarkJS(string token)
{
if (!AppInit.conf.sync_user.enable)
return Content(string.Empty, "application/javascript; charset=utf-8");
var sb = new StringBuilder(FileCache.ReadAllText("plugins/bookmark.js"));
sb.Replace("{localhost}", host)
.Replace("{token}", HttpUtility.UrlEncode(token));
return Content(sb.ToString(), "application/javascript; charset=utf-8");
}
#endregion
static readonly string[] BookmarkCategories = {
"history",
"like",
"watch",
"wath",
"book",
"look",
"viewed",
"scheduled",
"continued",
"thrown"
};
#region List
[HttpGet]
[Route("/bookmark/list")]
public async Task<ActionResult> List(string filed)
{
if (!AppInit.conf.sync_user.enable)
return ContentTo("{}");
string userUid = getUserid(requestInfo, HttpContext);
#region migration storage to sql
if (AppInit.conf.sync_user.version != 1 && !string.IsNullOrEmpty(requestInfo.user_uid))
{
string profile_id = getProfileid(requestInfo, HttpContext);
string id = requestInfo.user_uid + profile_id;
string md5key = AppInit.conf.storage.md5name ? CrypTo.md5(id) : Regex.Replace(id, "[^a-z0-9\\-]", "");
string storageFile = $"database/storage/sync_favorite/{md5key.Substring(0, 2)}/{md5key.Substring(2)}";
if (System.IO.File.Exists(storageFile) && !System.IO.File.Exists($"{storageFile}.migration"))
{
try
{
await SyncUserContext.semaphore.WaitAsync(TimeSpan.FromSeconds(40));
if (System.IO.File.Exists(storageFile) && !System.IO.File.Exists($"{storageFile}.migration"))
{
var content = System.IO.File.ReadAllText(storageFile);
if (!string.IsNullOrWhiteSpace(content))
{
var root = JsonConvert.DeserializeObject<JObject>(content);
var favorite = (JObject)root["favorite"];
using (var sqlDb = SyncUserContext.Factory != null
? SyncUserContext.Factory.CreateDbContext()
: new SyncUserContext())
{
var (entity, loaded) = LoadBookmarks(sqlDb, userUid, createIfMissing: true);
bool changed = false;
EnsureDefaultArrays(loaded);
#region migrate card objects
if (favorite["card"] is JArray srcCards)
{
foreach (var c in srcCards.Children<JObject>())
{
changed |= EnsureCard(loaded, c, c?["id"]?.ToString(), insert: false);
}
}
#endregion
#region migrate categories
foreach (var prop in favorite.Properties())
{
var name = prop.Name.ToLowerAndTrim();
if (string.Equals(name, "card", StringComparison.OrdinalIgnoreCase))
continue;
var srcValue = prop.Value;
if (BookmarkCategories.Contains(name))
{
if (srcValue is JArray srcArray)
{
var dest = GetCategoryArray(loaded, name);
foreach (var t in srcArray)
{
var idStr = t?.ToString();
if (string.IsNullOrWhiteSpace(idStr))
continue;
if (dest.Any(dt => dt.ToString() == idStr) == false)
{
if (long.TryParse(idStr, out long _id) && _id > 0)
dest.Add(_id);
else
dest.Add(idStr);
changed = true;
}
}
}
}
else
{
var existing = loaded[name];
if (existing == null || !JToken.DeepEquals(existing, srcValue))
{
loaded[name] = srcValue;
changed = true;
}
}
}
#endregion
if (changed)
Save(sqlDb, entity, loaded);
}
System.IO.File.Create($"{storageFile}.migration");
}
}
}
catch { }
finally
{
SyncUserContext.semaphore.Release();
}
}
}
#endregion
using (var sqlDb = SyncUserContext.Factory != null
? SyncUserContext.Factory.CreateDbContext()
: new SyncUserContext())
{
bool IsDbInitialization = sqlDb.bookmarks.AsNoTracking().FirstOrDefault(i => i.user == userUid) != null;
if (!IsDbInitialization)
return Json(new { dbInNotInitialization = true });
var data = GetBookmarksForResponse(sqlDb);
if (!string.IsNullOrEmpty(filed))
return ContentTo(data[filed].ToString(Formatting.None));
return ContentTo(data.ToString(Formatting.None));
}
}
#endregion
#region Set
[HttpPost]
[Route("/bookmark/set")]
public async Task<ActionResult> Set(string connectionId)
{
if (string.IsNullOrEmpty(requestInfo.user_uid) || !AppInit.conf.sync_user.enable)
return JsonFailure();
using (var reader = new StreamReader(Request.Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: PoolInvk.bufferSize, leaveOpen: true))
{
string body = await reader.ReadToEndAsync();
if (string.IsNullOrWhiteSpace(body))
return JsonFailure();
var token = JsonConvert.DeserializeObject<JToken>(body);
if (token == null)
return JsonFailure();
var jobs = new List<JObject>();
if (token.Type == JTokenType.Array)
{
foreach (var obj in token.Children<JObject>())
jobs.Add(obj);
}
else if (token is JObject singleJob)
{
jobs.Add(singleJob);
}
bool IsDbInitialization = false;
try
{
await SyncUserContext.semaphore.WaitAsync(TimeSpan.FromSeconds(30));
using (var sqlDb = SyncUserContext.Factory != null
? SyncUserContext.Factory.CreateDbContext()
: new SyncUserContext())
{
string userUid = getUserid(requestInfo, HttpContext);
IsDbInitialization = sqlDb.bookmarks.AsNoTracking().FirstOrDefault(i => i.user == userUid) != null;
var (entity, data) = LoadBookmarks(sqlDb, userUid, createIfMissing: true);
foreach (var job in jobs)
{
string where = job.Value<string>("where")?.ToLowerAndTrim();
if (string.IsNullOrWhiteSpace(where))
return JsonFailure();
if (IsDbInitialization && AppInit.conf.sync_user.fullset == false)
{
if (where == "card" || BookmarkCategories.Contains(where))
return JsonFailure("enable sync_user.fullset in init.conf");
}
if (!job.TryGetValue("data", out var dataValue))
return JsonFailure();
data[where] = dataValue;
}
EnsureDefaultArrays(data);
Save(sqlDb, entity, data);
}
}
catch
{
return JsonFailure();
}
finally
{
SyncUserContext.semaphore.Release();
}
if (IsDbInitialization)
{
_ = Shared.Startup.Nws.EventsAsync(connectionId, requestInfo.user_uid, "bookmark", JsonConvertPool.SerializeObject(new
{
type = "set",
data = token,
profile_id = getProfileid(requestInfo, HttpContext)
})).ConfigureAwait(false);
}
return JsonSuccess();
}
}
#endregion
#region Add/Added
[HttpPost]
[Route("/bookmark/add")]
[Route("/bookmark/added")]
public async Task<ActionResult> Add(string connectionId)
{
if (string.IsNullOrEmpty(requestInfo.user_uid) || !AppInit.conf.sync_user.enable)
return JsonFailure();
var readBody = await ReadPayloadAsync();
if (readBody.payloads.Count == 0)
return JsonFailure();
bool isAddedRequest = HttpContext?.Request?.Path.Value?.StartsWith("/bookmark/added", StringComparison.OrdinalIgnoreCase) == true;
try
{
await SyncUserContext.semaphore.WaitAsync(TimeSpan.FromSeconds(30));
using (var sqlDb = SyncUserContext.Factory != null
? SyncUserContext.Factory.CreateDbContext()
: new SyncUserContext())
{
var (entity, data) = LoadBookmarks(sqlDb, getUserid(requestInfo, HttpContext), createIfMissing: true);
bool changed = false;
foreach (var payload in readBody.payloads)
{
var cardId = payload.ResolveCardId();
if (cardId == null)
continue;
changed |= EnsureCard(data, payload.Card, cardId);
if (payload.Where != null)
changed |= AddToCategory(data, payload.Where, cardId);
if (isAddedRequest)
changed |= MoveIdToFrontInAllCategories(data, cardId);
}
if (changed)
{
Save(sqlDb, entity, data);
if (readBody.token != null)
{
string edata = JsonConvertPool.SerializeObject(new
{
type = isAddedRequest ? "added" : "add",
profile_id = getProfileid(requestInfo, HttpContext),
data = readBody.token
});
_ = Shared.Startup.Nws.EventsAsync(connectionId, requestInfo.user_uid, "bookmark", edata).ConfigureAwait(false);
}
}
}
return JsonSuccess();
}
catch
{
return JsonFailure();
}
finally
{
SyncUserContext.semaphore.Release();
}
}
#endregion
#region Remove
[HttpPost]
[Route("/bookmark/remove")]
public async Task<ActionResult> Remove(string connectionId)
{
if (string.IsNullOrEmpty(requestInfo.user_uid) || !AppInit.conf.sync_user.enable)
return JsonFailure();
var readBody = await ReadPayloadAsync();
if (readBody.payloads.Count == 0)
return JsonFailure();
try
{
await SyncUserContext.semaphore.WaitAsync(TimeSpan.FromSeconds(30));
using (var sqlDb = SyncUserContext.Factory != null
? SyncUserContext.Factory.CreateDbContext()
: new SyncUserContext())
{
var (entity, data) = LoadBookmarks(sqlDb, getUserid(requestInfo, HttpContext), createIfMissing: false);
if (entity == null)
return JsonSuccess();
bool changed = false;
foreach (var payload in readBody.payloads)
{
var cardId = payload.ResolveCardId();
if (cardId == null)
continue;
if (payload.Where != null)
changed |= RemoveFromCategory(data, payload.Where, cardId);
if (payload.Method == "card")
{
changed |= RemoveIdFromAllCategories(data, cardId);
changed |= RemoveCard(data, cardId);
}
}
if (changed)
{
Save(sqlDb, entity, data);
if (readBody.token != null)
{
string edata = JsonConvertPool.SerializeObject(new
{
type = "remove",
profile_id = getProfileid(requestInfo, HttpContext),
data = readBody.token
});
_ = Shared.Startup.Nws.EventsAsync(connectionId, requestInfo.user_uid, "bookmark", edata).ConfigureAwait(false);
}
}
}
return JsonSuccess();
}
catch
{
return JsonFailure();
}
finally
{
SyncUserContext.semaphore.Release();
}
}
#endregion
#region static
static string getUserid(RequestModel requestInfo, HttpContext httpContext)
{
string user_id = requestInfo.user_uid;
string profile_id = getProfileid(requestInfo, httpContext);
if (!string.IsNullOrEmpty(profile_id))
return $"{user_id}_{profile_id}";
return user_id;
}
static string getProfileid(RequestModel requestInfo, HttpContext httpContext)
{
if (httpContext.Request.Query.TryGetValue("profile_id", out var profile_id) && !string.IsNullOrEmpty(profile_id) && profile_id != "0")
return profile_id;
return string.Empty;
}
JObject GetBookmarksForResponse(SyncUserContext sqlDb)
{
if (string.IsNullOrEmpty(requestInfo.user_uid))
return CreateDefaultBookmarks();
string user_id = getUserid(requestInfo, HttpContext);
var entity = sqlDb.bookmarks.AsNoTracking().FirstOrDefault(i => i.user == user_id);
var data = entity != null ? DeserializeBookmarks(entity.data) : CreateDefaultBookmarks();
EnsureDefaultArrays(data);
return data;
}
static (SyncUserBookmarkSqlModel entity, JObject data) LoadBookmarks(SyncUserContext sqlDb, string userUid, bool createIfMissing)
{
JObject data = CreateDefaultBookmarks();
SyncUserBookmarkSqlModel entity = null;
if (!string.IsNullOrEmpty(userUid))
{
entity = sqlDb.bookmarks.FirstOrDefault(i => i.user == userUid);
if (entity != null && !string.IsNullOrEmpty(entity.data))
data = DeserializeBookmarks(entity.data);
}
EnsureDefaultArrays(data);
if (entity == null && createIfMissing && !string.IsNullOrEmpty(userUid))
entity = new SyncUserBookmarkSqlModel { user = userUid };
return (entity, data);
}
static JObject DeserializeBookmarks(string json)
{
if (string.IsNullOrWhiteSpace(json))
return CreateDefaultBookmarks();
try
{
var job = JsonConvert.DeserializeObject<JObject>(json) ?? new JObject();
EnsureDefaultArrays(job);
return job;
}
catch
{
return CreateDefaultBookmarks();
}
}
static JObject CreateDefaultBookmarks()
{
var obj = new JObject
{
["card"] = new JArray()
};
foreach (var category in BookmarkCategories)
obj[category] = new JArray();
return obj;
}
static void EnsureDefaultArrays(JObject root)
{
if (root == null)
return;
if (root["card"] is not JArray)
root["card"] = new JArray();
foreach (var category in BookmarkCategories)
{
if (root[category] is not JArray)
root[category] = new JArray();
}
}
static bool EnsureCard(JObject data, JObject card, string idStr, bool insert = true)
{
if (data == null || card == null || string.IsNullOrWhiteSpace(idStr))
return false;
var cardArray = GetCardArray(data);
var newCard = (JObject)card.DeepClone();
foreach (var existing in cardArray.Children<JObject>().ToList())
{
var token = existing["id"];
if (token != null && token.ToString() == idStr)
{
if (!JToken.DeepEquals(existing, newCard))
{
existing.Replace(newCard);
return true;
}
return false;
}
}
if (insert)
cardArray.Insert(0, newCard);
else
cardArray.Add(newCard);
return true;
}
static bool AddToCategory(JObject data, string category, string idStr)
{
var array = GetCategoryArray(data, category);
foreach (var token in array)
{
if (token.ToString() == idStr)
return false;
}
if (long.TryParse(idStr, out long _id) && _id > 0)
array.Insert(0, _id);
else
array.Insert(0, idStr);
return true;
}
static bool MoveIdToFrontInAllCategories(JObject data, string idStr)
{
bool changed = false;
foreach (var prop in data.Properties())
{
if (string.Equals(prop.Name, "card", StringComparison.OrdinalIgnoreCase))
continue;
if (prop.Value is JArray array)
changed |= MoveIdToFront(array, idStr);
}
return changed;
}
static bool MoveIdToFront(JArray array, string idStr)
{
if (array == null)
return false;
for (int i = 0; i < array.Count; i++)
{
var token = array[i];
if (token?.ToString() == idStr)
{
if (i == 0)
return false;
token.Remove();
array.Insert(0, token);
return true;
}
}
return false;
}
static bool RemoveFromCategory(JObject data, string category, string idStr)
{
if (data[category] is not JArray array)
return false;
return RemoveFromArray(array, idStr);
}
static bool RemoveIdFromAllCategories(JObject data, string idStr)
{
bool changed = false;
foreach (var property in data.Properties().ToList())
{
if (property.Name == "card")
continue;
if (property.Value is JArray array && RemoveFromArray(array, idStr))
changed = true;
}
return changed;
}
static bool RemoveCard(JObject data, string idStr)
{
if (data["card"] is JArray cardArray)
{
foreach (var card in cardArray.Children<JObject>().ToList())
{
var token = card["id"];
if (token != null && token.ToString() == idStr)
{
card.Remove();
return true;
}
}
}
return false;
}
static JArray GetCardArray(JObject data)
{
if (data["card"] is JArray array)
return array;
array = new JArray();
data["card"] = array;
return array;
}
static JArray GetCategoryArray(JObject data, string category)
{
if (data[category] is JArray array)
return array;
array = new JArray();
data[category] = array;
return array;
}
static bool RemoveFromArray(JArray array, string idStr)
{
foreach (var token in array.ToList())
{
if (token.ToString() == idStr)
{
token.Remove();
return true;
}
}
return false;
}
static void Save(SyncUserContext sqlDb, SyncUserBookmarkSqlModel entity, JObject data)
{
if (entity == null)
return;
entity.data = data.ToString(Formatting.None);
entity.updated = DateTime.UtcNow;
if (entity.Id == 0)
sqlDb.bookmarks.Add(entity);
else
sqlDb.bookmarks.Update(entity);
sqlDb.SaveChanges();
}
JsonResult JsonSuccess() => Json(new { success = true });
ActionResult JsonFailure(string message = null) => ContentTo(JsonConvertPool.SerializeObject(new { success = false, message }));
async Task<(IReadOnlyList<BookmarkEventPayload> payloads, JToken token)> ReadPayloadAsync()
{
JToken token = null;
var payloads = new List<BookmarkEventPayload>();
using (var reader = new StreamReader(Request.Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: PoolInvk.bufferSize, leaveOpen: true))
{
try
{
string json = await reader.ReadToEndAsync();
if (string.IsNullOrWhiteSpace(json))
return (payloads, token);
token = JsonConvert.DeserializeObject<JToken>(json);
if (token == null)
return (payloads, token);
if (token.Type == JTokenType.Array)
{
foreach (var obj in token.Children<JObject>())
payloads.Add(ParsePayload(obj));
}
else if (token is JObject job)
{
payloads.Add(ParsePayload(job));
}
}
catch { }
}
return (payloads, token);
}
static BookmarkEventPayload ParsePayload(JObject job)
{
var payload = new BookmarkEventPayload
{
Method = job.Value<string>("method"),
CardIdRaw = job.Value<string>("id") ?? job.Value<string>("card_id")
};
payload.Where = (job.Value<string>("where") ?? job.Value<string>("list"))?.ToLowerAndTrim();
if (string.IsNullOrEmpty(payload.Where) || payload.Where == "card")
payload.Where = null;
if (job.TryGetValue("card", out var cardToken) && cardToken is JObject cardObj)
payload.Card = cardObj;
return payload;
}
#endregion
#region BookmarkEventPayload
sealed class BookmarkEventPayload
{
public string Method { get; set; }
public string Where { get; set; }
public JObject Card { get; set; }
public string CardIdRaw { get; set; }
public string ResolveCardId()
{
if (!string.IsNullOrWhiteSpace(CardIdRaw))
return CardIdRaw.ToLowerAndTrim();
var token = Card?["id"];
if (token != null)
{
if (token.Type == JTokenType.Integer)
return token.Value<long>().ToString();
string _id = token.ToString();
if (string.IsNullOrWhiteSpace(_id))
return null;
return _id.ToLowerAndTrim();
}
return null;
}
}
#endregion
}
}

View File

@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Shared;
namespace Lampac.Controllers
{
public class ChromiumController : BaseController
{
[HttpGet]
[AllowAnonymous]
[Route("/api/chromium/ping")]
public string Ping() => "pong";
[HttpGet]
[AllowAnonymous]
[Route("/api/chromium/iframe")]
public ActionResult RenderIframe(string src)
{
return ContentTo($@"<html lang=""ru"">
<head>
<meta charset=""UTF-8"">
<meta http-equiv=""X-UA-Compatible"" content=""IE=edge"">
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"">
<title>chromium iframe</title>
</head>
<body>
<iframe width=""560"" height=""400"" src=""{src}"" frameborder=""0"" allow=""*"" allowfullscreen></iframe>
</body>
</html>");
}
}
}

View File

@ -0,0 +1,57 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
using Shared;
using Shared.Engine;
using Shared.Models.CSharpGlobals;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Lampac.Controllers
{
public class CmdController : BaseController
{
[HttpGet]
[Route("cmd/{key}/{*comand}")]
async public Task CMD(string key, string comand)
{
if (!AppInit.conf.cmd.TryGetValue(key, out var cmd))
return;
if (!string.IsNullOrEmpty(cmd.eval))
{
var options = ScriptOptions.Default
.AddReferences(typeof(HttpRequest).Assembly).AddImports("Microsoft.AspNetCore.Http")
.AddReferences(typeof(Task).Assembly).AddImports("System.Threading.Tasks")
.AddReferences(CSharpEval.ReferenceFromFile("Newtonsoft.Json.dll")).AddImports("Newtonsoft.Json").AddImports("Newtonsoft.Json.Linq")
.AddReferences(CSharpEval.ReferenceFromFile("Shared.dll")).AddImports("Shared.Engine").AddImports("Shared.Models")
.AddReferences(typeof(System.IO.File).Assembly).AddImports("System.IO")
.AddReferences(typeof(Process).Assembly).AddImports("System.Diagnostics");
var model = new CmdEvalModel(key, comand, requestInfo, HttpContext.Request, hybridCache, memoryCache);
await CSharpEval.ExecuteAsync(cmd.eval, model, options);
}
else
{
if (cmd.arguments.Length == 0)
return;
var _info = new ProcessStartInfo()
{
FileName = cmd.path
};
foreach (string a in cmd.arguments)
{
_info.ArgumentList.Add(a.Contains("{value}")
? a.Replace("{value}", comand + HttpContext.Request.QueryString.Value)
: a
);
}
Process.Start(_info);
}
}
}
}

View File

@ -0,0 +1,453 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Playwright;
using Newtonsoft.Json;
using Shared;
using Shared.Engine;
using Shared.Models;
using Shared.Models.Base;
using Shared.PlaywrightCore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Lampac.Controllers
{
public class CorseuController : BaseController
{
#region Routes
[HttpGet]
[AllowAnonymous]
[Route("/corseu/{token}/{*url}")]
public Task<IActionResult> Get(string token, string url)
{
return ExecuteAsync(new CorseuRequest
{
url = url + HttpContext.Request.QueryString.Value,
auth_token = token
});
}
[HttpGet]
[AllowAnonymous]
[Route("/corseu")]
public Task<IActionResult> Get(string auth_token, string method, string url, string data, string headers, string browser, int? httpversion, int? timeout, string encoding, bool? defaultHeaders, bool? autoredirect, string proxy, string proxy_name, bool? headersOnly)
{
return ExecuteAsync(new CorseuRequest
{
url = url,
method = method,
data = data,
browser = browser,
httpversion = httpversion,
timeout = timeout,
encoding = encoding,
defaultHeaders = defaultHeaders,
autoredirect = autoredirect,
proxy = proxy,
proxy_name = proxy_name,
headersOnly = headersOnly,
auth_token = auth_token,
headers = ParseHeaders(headers)
});
}
[HttpPost]
[AllowAnonymous]
[Route("/corseu")]
async public Task<IActionResult> Post()
{
try
{
using (var reader = new StreamReader(HttpContext.Request.Body, Encoding.UTF8, leaveOpen: true))
{
string body = await reader.ReadToEndAsync().ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(body))
return BadRequest("Empty body");
var model = JsonConvert.DeserializeObject<CorseuRequest>(body);
if (model == null)
return BadRequest("Invalid body");
return await ExecuteAsync(model);
}
}
catch (JsonException)
{
return BadRequest("Invalid JSON");
}
}
#endregion
#region Execute
async Task<IActionResult> ExecuteAsync(CorseuRequest model)
{
var init = AppInit.conf.corseu;
if (init?.tokens == null || init.tokens.Length == 0)
return StatusCode((int)HttpStatusCode.Forbidden);
if (string.IsNullOrEmpty(model?.auth_token) || !init.tokens.Contains(model.auth_token))
return StatusCode((int)HttpStatusCode.Forbidden);
if (string.IsNullOrWhiteSpace(model?.url))
return BadRequest("url is empty");
InvkEvent.CorseuRequest(model);
string method = string.IsNullOrWhiteSpace(model.method) ? "GET" : model.method.ToUpperInvariant();
string browser = string.IsNullOrWhiteSpace(model.browser) ? "http" : model.browser.ToLowerInvariant();
var headers = model.headers != null
? new Dictionary<string, string>(model.headers, StringComparer.OrdinalIgnoreCase)
: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
bool useDefaultHeaders = model.defaultHeaders ?? true;
bool autoRedirect = model.autoredirect ?? true;
bool headersOnly = model.headersOnly ?? false;
int timeout = model.timeout.HasValue && model.timeout.Value > 5 ? model.timeout.Value : 15;
int httpVersion = model.httpversion ?? 1;
#region rules
if (init?.rules != null)
{
foreach (var rule in init.rules)
{
if (rule?.headers == null || rule.headers.Count == 0)
continue;
if (string.IsNullOrEmpty(rule.method) || string.IsNullOrEmpty(rule.url))
continue;
if (!string.Equals(rule.method, method, StringComparison.OrdinalIgnoreCase))
continue;
if (!Regex.IsMatch(model.url, rule.url, RegexOptions.IgnoreCase))
continue;
var ruleHeaders = new Dictionary<string, string>(rule.headers, StringComparer.OrdinalIgnoreCase);
if (rule.replace)
{
headers = ruleHeaders;
}
else
{
foreach (var pair in ruleHeaders)
headers[pair.Key] = pair.Value;
}
}
}
#endregion
string contentType = null;
if (headers.TryGetValue("content-type", out string ct))
{
contentType = ct;
headers.Remove("content-type");
}
if (headers.ContainsKey("content-length"))
headers.Remove("content-length");
if (browser is "chromium" or "playwright")
return await SendWithChromiumAsync(method, model.url, model.data, headers, contentType, timeout, autoRedirect, headersOnly, model.proxy, model.proxy_name);
return await SendWithHttpClientAsync(method, model.url, model.data, headers, contentType, timeout, httpVersion, useDefaultHeaders, autoRedirect, headersOnly, model.proxy, model.proxy_name, model.encoding);
}
#endregion
#region HttpClient
async Task<IActionResult> SendWithHttpClientAsync(
string method, string url, string data, Dictionary<string, string> headers,
string contentType, int timeout, int httpVersion, bool useDefaultHeaders, bool autoRedirect, bool headersOnly, string encodingName,
string proxyValue, string proxyName)
{
var proxyManager = CreateProxy(url, proxyValue, proxyName);
try
{
var handler = Http.Handler(url, proxyManager.Get());
handler.AllowAutoRedirect = autoRedirect;
var client = FrendlyHttp.MessageClient(httpVersion == 2 ? "http2" : "base", handler);
using (var request = new HttpRequestMessage(new HttpMethod(method), url))
{
request.Version = httpVersion == 2 ? HttpVersion.Version20 : HttpVersion.Version11;
if (!string.IsNullOrEmpty(data))
{
var encoding = string.IsNullOrEmpty(encodingName)
? Encoding.UTF8
: Encoding.GetEncoding(encodingName);
var content = new StringContent(data, encoding);
if (!string.IsNullOrEmpty(contentType))
content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
request.Content = content;
}
var headersModel = headers.Count > 0 ? HeadersModel.Init(headers) : null;
Http.DefaultRequestHeaders(url, request, null, null, headersModel, useDefaultHeaders);
if (InvkEvent.IsCorseuHttpRequest())
InvkEvent.CorseuHttpRequest(method, url, request);
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(HttpContext.RequestAborted))
{
cts.CancelAfter(TimeSpan.FromSeconds(Math.Max(5, timeout)));
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token).ConfigureAwait(false))
{
proxyManager.Success();
await CopyResponseAsync(response, headersOnly).ConfigureAwait(false);
return new EmptyResult();
}
}
}
}
catch (OperationCanceledException)
{
proxyManager.Refresh();
return StatusCode((int)HttpStatusCode.RequestTimeout);
}
catch (Exception ex)
{
proxyManager.Refresh();
return StatusCode((int)HttpStatusCode.BadGateway, ex.Message);
}
}
#endregion
#region Chromium
async Task<IActionResult> SendWithChromiumAsync(
string method, string url, string data, Dictionary<string, string> headers,
string contentType, int timeout, bool autoRedirect, bool headersOnly,
string proxyValue, string proxyName)
{
var proxyManager = CreateProxy(url, proxyValue, proxyName);
var proxy = proxyManager.BaseGet();
try
{
if (PlaywrightBrowser.Status == PlaywrightStatus.disabled)
return StatusCode((int)HttpStatusCode.BadGateway, "PlaywrightStatus disabled");
var contextHeaders = new Dictionary<string, string>(headers, StringComparer.OrdinalIgnoreCase);
var requestHeaders = new Dictionary<string, string>(headers, StringComparer.OrdinalIgnoreCase);
if (!string.IsNullOrEmpty(contentType))
{
if (!requestHeaders.ContainsKey("content-type"))
{
requestHeaders["content-type"] = contentType;
contextHeaders["content-type"] = contentType;
}
}
var contextOptions = new APIRequestNewContextOptions
{
IgnoreHTTPSErrors = true,
ExtraHTTPHeaders = Http.NormalizeHeaders(contextHeaders),
Timeout = timeout * 1000
};
if (requestHeaders.TryGetValue("user-agent", out string _useragent))
contextOptions.UserAgent = _useragent;
var requestOptions = new APIRequestContextOptions
{
Method = method,
Headers = requestHeaders,
Timeout = timeout * 1000
};
if (!string.IsNullOrEmpty(data))
requestOptions.DataString = data;
if (!autoRedirect)
requestOptions.MaxRedirects = 0;
if (proxy.proxy != null)
{
contextOptions.Proxy = new Proxy
{
Server = proxy.data.ip,
Username = proxy.data.username,
Password = proxy.data.password
};
}
if (InvkEvent.IsCorseuPlaywrightRequest())
InvkEvent.CorseuPlaywrightRequest(method, url, contextOptions, requestOptions);
await using (var requestContext = await Chromium.playwright.APIRequest.NewContextAsync(contextOptions).ConfigureAwait(false))
{
var response = await requestContext.FetchAsync(url, requestOptions).ConfigureAwait(false);
try
{
HttpContext.Response.StatusCode = response.Status;
foreach (var header in response.HeadersArray)
{
var headerName = header.Name.ToLowerInvariant();
if (ShouldSkipHeader(headerName))
continue;
if (headerName == "content-type")
HttpContext.Response.ContentType = header.Value;
HttpContext.Response.Headers[header.Name] = header.Value;
}
if (headersOnly)
{
proxyManager.Success();
await HttpContext.Response.CompleteAsync().ConfigureAwait(false);
return new EmptyResult();
}
var body = await response.BodyAsync().ConfigureAwait(false);
if (body?.Length > 0)
await HttpContext.Response.Body.WriteAsync(body, 0, body.Length, HttpContext.RequestAborted).ConfigureAwait(false);
proxyManager.Success();
return new EmptyResult();
}
finally
{
await response.DisposeAsync().ConfigureAwait(false);
}
}
}
catch (OperationCanceledException)
{
proxyManager.Refresh();
return StatusCode((int)HttpStatusCode.RequestTimeout);
}
catch (Exception ex)
{
proxyManager.Refresh();
return StatusCode((int)HttpStatusCode.BadGateway, ex.Message);
}
}
#endregion
#region Helpers
Dictionary<string, string> ParseHeaders(string headers)
{
try
{
if (!string.IsNullOrEmpty(headers))
return JsonConvert.DeserializeObject<Dictionary<string, string>>(headers);
}
catch { }
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
ProxyManager CreateProxy(string url, string proxyValue, string proxyName)
{
var model = new BaseSettings()
{
plugin = $"corseu:{Regex.Match(url, "https?://([^/]+)")}"
};
if (!string.IsNullOrEmpty(proxyValue))
{
model.proxy = new ProxySettings();
model.proxy.list = [proxyValue];
}
else if (!string.IsNullOrEmpty(proxyName))
{
if (AppInit.conf.globalproxy != null)
{
var settings = AppInit.conf.globalproxy.FirstOrDefault(i => i.name == proxyName);
if (settings?.list != null && settings.list.Length > 0)
model.proxy = settings;
}
}
if (model.proxy != null)
model.useproxy = true;
return new ProxyManager("corseu", model);
}
async Task CopyResponseAsync(HttpResponseMessage response, bool headersOnly)
{
var httpResponse = HttpContext.Response;
httpResponse.StatusCode = (int)response.StatusCode;
foreach (var header in response.Headers)
{
if (ShouldSkipHeader(header.Key))
continue;
httpResponse.Headers[header.Key] = string.Join(", ", header.Value);
}
foreach (var header in response.Content.Headers)
{
if (string.Equals(header.Key, "Content-Type", StringComparison.OrdinalIgnoreCase))
{
httpResponse.ContentType = response.Content.Headers.ContentType?.ToString();
continue;
}
if (ShouldSkipHeader(header.Key))
continue;
httpResponse.Headers[header.Key] = string.Join(", ", header.Value);
}
if (headersOnly)
{
await httpResponse.CompleteAsync().ConfigureAwait(false);
return;
}
using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
await responseStream.CopyToAsync(httpResponse.Body, HttpContext.RequestAborted).ConfigureAwait(false);
}
bool ShouldSkipHeader(string header)
{
string key = header.ToLowerInvariant();
return key switch
{
"content-length" => true,
"transfer-encoding" => true,
"connection" => true,
"keep-alive" => true,
"content-disposition" => true,
"content-encoding" => true,
"content-security-policy" => true,
"vary" => true,
"alt-svc" => true,
_ when key.StartsWith("access-control") => true,
_ when key.StartsWith("x-") => true,
_ => false
};
}
#endregion
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Shared;
using Shared.Engine;
using System.Web;
namespace Lampac.Controllers
{
public class CubController : BaseController
{
[HttpGet]
[AllowAnonymous]
[Route("cubproxy.js")]
[Route("cubproxy/js/{token}")]
public ActionResult CubProxy(string token)
{
if (!AppInit.conf.cub.enabled(requestInfo.Country))
return Content(string.Empty, contentType: "application/javascript; charset=utf-8");
string file = FileCache.ReadAllText("plugins/cubproxy.js").Replace("{localhost}", host);
file = file.Replace("{token}", HttpUtility.UrlEncode(token));
return Content(file, contentType: "application/javascript; charset=utf-8");
}
}
}

View File

@ -0,0 +1,109 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Shared;
using Shared.Engine;
namespace Lampac.Controllers
{
public class ErrorDocController : BaseController
{
[HttpGet]
[AllowAnonymous]
[Route("/e/acb")]
public ActionResult Accsdb()
{
string shared_passwd = CrypTo.unic(8).ToLowerInvariant();
string pw1 = CrypTo.unic(6).ToLowerInvariant();
string pw2 = CrypTo.unic(8).ToLowerInvariant();
return ContentTo($@"<!DOCTYPE html>
<html lang='ru'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Настройка AccsDB</title>
<link href='/control/npm/bootstrap.min.css' rel='stylesheet'>
</head>
<body>
<div class='container mt-5'>
<div class='card mt-4'>
<div class='card-body'>
<p class='card-text'>Добавьте в init.conf заменив email/unic_id на свои:</p>
<pre style='background: #e9ecef; padding: 1em;'><code>""accsdb"": {{
""accounts"": {{
""{pw1}@mail.ru"": ""2040-10-17T00:00:00"", // email cub.red
""{pw2}"": ""2040-10-17T00:00:00"", // unic_id
}}
}}</code></pre>
<br>
<p class='card-text'>Или через <a href='/admin' target='_blank'>{host}/admin</a> > Пользователи > Добавить пользователя > В ID указать email/unic_id</p>
</div>
</div>
</div>
<div class='container mt-5'>
<div class='card mt-4'>
<div class='card-body'>
<p class='card-text'>Если нужно разрешить внешний доступ без добавления каждого устройства, создайте пароль доступа:</p>
<pre style='background: #e9ecef; padding: 1em;'><code>""accsdb"": {{
""shared_passwd"": ""{shared_passwd}""
}}</code></pre>
<br>
<p class='card-text'>Так все кому вы сообщили пароль <b>{shared_passwd}</b> огут самостоятельно авторизоваться</p>
</div>
</div>
</div>
<div class='container mt-5' style='margin-bottom: 2em;'>
<div class='card mt-4'>
<div class='card-body'>
<p class='card-text'>Персональные пароли для плагинов, пример пароля <b>kitty</b>:</p>
<pre style='background: #e9ecef; padding: 1em;'><code>""accsdb"": {{
""accounts"": {{
""kitty"": ""2040-10-17T00:00:00"",
}}
}}</code></pre>
<br>
<p class='card-text'>Или через <a href='/admin' target='_blank'>{host}/admin</a> > Пользователи > Добавить пользователя > В ID указать <b>kitty</b></p>
<br>
<p class='card-text'>Все плагины сразу
<br>
http://IP:9118/on/js/kitty
</p>
<p class='card-text'>Онлайн
<br>
http://IP:9118/online/js/kitty
</p>
<p class='card-text'>Клубничка
<br>
http://IP:9118/sisi/js/kitty
</p>
<p class='card-text'>DLNA
<br>
http://IP:9118/dlna/js/kitty
</p>
<p class='card-text'>Таймкоды
<br>
http://IP:9118/timecode/js/kitty
</p>
<p class='card-text'>Tracks и Транскодинг
<br>
http://IP:9118/tracks/js/kitty
</p>
<p class='card-text'>TorrServer
<br>
http://IP:9118/ts/js/kitty
</p>
</div>
</div>
</div>
</body>
</html>");
}
}
}

View File

@ -0,0 +1,626 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Shared;
using Shared.Engine;
using Shared.Models.Events;
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using IO = System.IO;
namespace Lampac.Controllers
{
public class LampaWebController : BaseController
{
[HttpGet]
[AllowAnonymous]
[Route("/personal.lampa")]
[Route("/lampa-main/personal.lampa")]
[Route("/{myfolder}/personal.lampa")]
public ActionResult PersonalLampa(string myfolder) => StatusCode(200);
#region Index
[HttpGet]
[AllowAnonymous]
[Route("/")]
public ActionResult Index()
{
if (string.IsNullOrWhiteSpace(AppInit.conf.LampaWeb.index))
return Content("api work", contentType: "text/plain; charset=utf-8");
if (AppInit.conf.LampaWeb.basetag && Regex.IsMatch(AppInit.conf.LampaWeb.index, "/[^\\./]+\\.html$"))
{
if (!memoryCache.TryGetValue($"LampaWeb.index:{AppInit.conf.LampaWeb.index}", out string html))
{
html = IO.File.ReadAllText($"wwwroot/{AppInit.conf.LampaWeb.index}");
html = html.Replace("<head>", $"<head><base href=\"/{Regex.Match(AppInit.conf.LampaWeb.index, "^([^/]+)/").Groups[1].Value}/\" />");
memoryCache.Set($"LampaWeb.index:{AppInit.conf.LampaWeb.index}", html, DateTime.Now.AddMinutes(1));
}
return Content(html, contentType: "text/html; charset=utf-8");
}
return LocalRedirect($"/{AppInit.conf.LampaWeb.index}");
}
#endregion
#region Extensions
[HttpGet]
[AllowAnonymous]
[Route("/extensions")]
public ActionResult Extensions()
{
return ContentTo(FileCache.ReadAllText("plugins/extensions.json").Replace("{localhost}", host).Replace("\n", "").Replace("\r", ""));
}
#endregion
#region testaccsdb
[HttpGet]
[HttpPost]
[Route("/testaccsdb")]
public ActionResult TestAccsdb(string account_email, string uid)
{
if (!string.IsNullOrEmpty(AppInit.conf.accsdb.shared_passwd) && uid == AppInit.conf.accsdb.shared_passwd)
return ContentTo("{\"accsdb\": true, \"newuid\": true}");
if (!string.IsNullOrEmpty(uid) && !string.IsNullOrEmpty(account_email) && account_email == AppInit.conf.accsdb.shared_passwd)
{
try
{
string file = "users.json";
JArray arr = new JArray();
if (IO.File.Exists(file))
{
var txt = IO.File.ReadAllText(file);
if (!string.IsNullOrWhiteSpace(txt))
try { arr = JArray.Parse(txt); } catch { arr = new JArray(); }
}
bool exists = arr.Children<JObject>().Any(o =>
(o.Value<string>("id") != null && o.Value<string>("id").Equals(uid, StringComparison.OrdinalIgnoreCase)) ||
(o["ids"] != null && o["ids"].Any(t => t.ToString().Equals(uid, StringComparison.OrdinalIgnoreCase)))
);
if (exists)
return ContentTo("{\"accsdb\": false}");
var obj = new JObject();
obj["id"] = uid;
obj["expires"] = DateTime.Now.AddDays(Math.Max(1, AppInit.conf.accsdb.shared_daytime));
arr.Add(obj);
IO.File.WriteAllText(file, arr.ToString(Formatting.Indented));
return ContentTo("{\"accsdb\": false, \"success\": true, \"uid\": \"" + uid + "\"}");
}
catch { return ContentTo("{\"accsdb\": true}"); }
}
return ContentTo("{\"accsdb\": false, \"success\": true}");
}
#endregion
#region app.min.js
[HttpGet]
[AllowAnonymous]
[Route("/app.min.js")]
[Route("{type}/app.min.js")]
public ContentResult LampaApp(string type)
{
if (string.IsNullOrEmpty(type))
{
if (AppInit.conf.LampaWeb.path != null)
{
type = AppInit.conf.LampaWeb.path;
}
else
{
if (AppInit.conf.LampaWeb.index == null || !AppInit.conf.LampaWeb.index.Contains("/"))
return Content(string.Empty, "application/javascript; charset=utf-8");
type = AppInit.conf.LampaWeb.index.Split("/")[0];
}
}
else
{
type = Regex.Replace(type, "[^a-z0-9\\-]", "", RegexOptions.IgnoreCase);
}
bool usecubproxy = AppInit.conf.cub.enabled(requestInfo.Country);
var apr = AppInit.conf.LampaWeb.appReplace ?? InvkEvent.conf?.Controller?.AppReplace?.appjs?.regex;
string memKey = $"ApiController:{type}:{host}:{usecubproxy}:{apr?.Count ?? 0}:app.min.js";
if (!memoryCache.TryGetValue(memKey, out string file))
{
file = IO.File.ReadAllText($"wwwroot/{type}/app.min.js");
#region appReplace
if (apr != null)
{
foreach (var r in apr)
{
string val = r.Value;
if (val.StartsWith("file:"))
val = IO.File.ReadAllText(val.Substring(5));
val = val.Replace("{localhost}", host).Replace("{host}", Regex.Replace(host, "^https?://", ""));
file = Regex.Replace(file, r.Key, val, RegexOptions.IgnoreCase);
}
}
if (InvkEvent.conf?.Controller?.AppReplace?.appjs?.list != null)
{
foreach (var r in InvkEvent.conf.Controller.AppReplace.appjs.list)
{
string val = r.Value;
if (val.StartsWith("file:"))
val = IO.File.ReadAllText(val.Substring(5));
val = val.Replace("{localhost}", host).Replace("{host}", Regex.Replace(host, "^https?://", ""));
file = file.Replace(r.Key, val);
}
}
#endregion
string playerinner = FileCache.ReadAllText("plugins/player-inner.js", saveCache: false)
.Replace("{useplayer}", (!string.IsNullOrEmpty(AppInit.conf.playerInner)).ToString().ToLower())
.Replace("{notUseTranscoding}", (AppInit.conf.transcoding.enable == false).ToString().ToLower());
var bulder = new StringBuilder(file);
bulder = bulder.Replace("Player.play(element);", playerinner);
if (usecubproxy)
{
bulder = bulder.Replace("protocol + mirror + '/api/checker'", $"'{host}/cub/api/checker'");
bulder = bulder.Replace("Utils$1.protocol() + 'tmdb.' + object$2.cub_domain + '/' + u,", $"'{host}/cub/tmdb./' + u,");
bulder = bulder.Replace("Utils$2.protocol() + 'tmdb.' + object$2.cub_domain + '/' + u,", $"'{host}/cub/tmdb./' + u,");
bulder = bulder.Replace("Utils$1.protocol() + object$2.cub_domain", $"'{host}/cub/red'");
bulder = bulder.Replace("Utils$2.protocol() + object$2.cub_domain", $"'{host}/cub/red'");
bulder = bulder.Replace("object$2.cub_domain", $"'{AppInit.conf.cub.mirror}'");
}
bulder = bulder.Replace("http://lite.lampa.mx", $"{host}/{type}");
bulder = bulder.Replace("https://yumata.github.io/lampa-lite", $"{host}/{type}");
bulder = bulder.Replace("http://lampa.mx", $"{host}/{type}");
bulder = bulder.Replace("https://yumata.github.io/lampa", $"{host}/{type}");
bulder = bulder.Replace("window.lampa_settings.dcma = dcma;", "window.lampa_settings.fixdcma = true;");
bulder = bulder.Replace("Storage.get('vpn_checked_ready', 'false')", "true");
bulder = bulder.Replace("status$1 = false;", "status$1 = true;"); // local apk to personal.lampa
bulder = bulder.Replace("return status$1;", "return true;"); // отключение рекламы
bulder = bulder.Replace("if (!Storage.get('metric_uid', ''))", "return;"); // metric
bulder = bulder.Replace("function log(data) {", "function log(data) { return;");
bulder = bulder.Replace("function stat$1(method, name) {", "function stat$1(method, name) { return;");
bulder = bulder.Replace("if (domain) {", "if (false) {");
bulder = bulder.Replace("{localhost}", host);
file = bulder.ToString();
if (AppInit.conf.mikrotik == false)
memoryCache.Set(memKey, file, DateTime.Now.AddMinutes(1));
}
if (InvkEvent.conf?.Controller?.AppReplace?.appjs?.eval != null)
file = InvkEvent.AppReplace("appjs", new EventAppReplace(file, null, type, host, requestInfo, HttpContext.Request, hybridCache));
return Content(file, "application/javascript; charset=utf-8");
}
#endregion
#region app.css
[HttpGet]
[AllowAnonymous]
[Route("/css/app.css")]
[Route("{type}/css/app.css")]
public ContentResult LampaAppCss(string type)
{
if (string.IsNullOrEmpty(type))
{
if (AppInit.conf.LampaWeb.path != null)
{
type = AppInit.conf.LampaWeb.path;
}
else
{
if (AppInit.conf.LampaWeb.index == null || !AppInit.conf.LampaWeb.index.Contains("/"))
return Content(string.Empty, "application/javascript; charset=utf-8");
type = AppInit.conf.LampaWeb.index.Split("/")[0];
}
}
else
{
type = Regex.Replace(type, "[^a-z0-9\\-]", "", RegexOptions.IgnoreCase);
}
var apr = AppInit.conf.LampaWeb.cssReplace ?? InvkEvent.conf?.Controller?.AppReplace?.appcss?.regex;
string memKey = $"ApiController:css/app.css:{type}:{host}:{apr?.Count ?? 0}";
if (!memoryCache.TryGetValue(memKey, out string css))
{
css = IO.File.ReadAllText($"wwwroot/{type}/css/app.css");
#region appReplace
if (apr != null)
{
foreach (var r in apr)
{
string val = r.Value;
if (val.StartsWith("file:"))
val = IO.File.ReadAllText(val.Substring(5));
val = val.Replace("{localhost}", host).Replace("{host}", Regex.Replace(host, "^https?://", ""));
css = Regex.Replace(css, r.Key, val, RegexOptions.IgnoreCase);
}
}
if (InvkEvent.conf?.Controller?.AppReplace?.appcss?.list != null)
{
foreach (var r in InvkEvent.conf.Controller.AppReplace.appcss.list)
{
string val = r.Value;
if (val.StartsWith("file:"))
val = IO.File.ReadAllText(val.Substring(5));
val = val.Replace("{localhost}", host).Replace("{host}", Regex.Replace(host, "^https?://", ""));
css = css.Replace(r.Key, val);
}
}
#endregion
memoryCache.Set(memKey, css, DateTime.Now.AddMinutes(AppInit.conf.multiaccess ? 5 : 1));
}
if (InvkEvent.conf?.Controller?.AppReplace?.appcss?.eval != null)
css = InvkEvent.AppReplace("appcss", new EventAppReplace(css, null, type, host, requestInfo, HttpContext.Request, hybridCache));
return Content(css, "text/css; charset=utf-8");
}
#endregion
#region samsung.wgt
[HttpGet]
[AllowAnonymous]
[Route("samsung.wgt")]
public ActionResult SamsWgt(string overwritehost)
{
string folder = "data/widgets";
if (!IO.File.Exists($"{folder}/samsung/loader.js"))
return Content(string.Empty);
string wgt = $"{folder}/{CrypTo.md5(overwritehost ?? host + "v3")}.wgt";
if (IO.File.Exists(wgt))
return File(IO.File.OpenRead(wgt), "application/octet-stream");
string index = IO.File.ReadAllText($"{folder}/samsung/index.html");
IO.File.WriteAllText($"{folder}/samsung/publish/index.html", index.Replace("{localhost}", overwritehost ?? host));
string loader = IO.File.ReadAllText($"{folder}/samsung/loader.js");
IO.File.WriteAllText($"{folder}/samsung/publish/loader.js", loader.Replace("{localhost}", overwritehost ?? host));
string app = IO.File.ReadAllText($"{folder}/samsung/app.js");
IO.File.WriteAllText($"{folder}/samsung/publish/app.js", app.Replace("{localhost}", overwritehost ?? host));
IO.File.Copy($"{folder}/samsung/icon.png", $"{folder}/samsung/publish/icon.png", overwrite: true);
IO.File.Copy($"{folder}/samsung/logo_appname_fg.png", $"{folder}/samsung/publish/logo_appname_fg.png", overwrite: true);
IO.File.Copy($"{folder}/samsung/config.xml", $"{folder}/samsung/publish/config.xml", overwrite: true);
string gethash(string file)
{
using (SHA512 sha = SHA512.Create())
{
return Convert.ToBase64String(sha.ComputeHash(IO.File.ReadAllBytes(file)));
//digestValue = hash.Remove(76) + "\n" + hash.Remove(0, 76);
}
}
string indexhashsha512 = gethash($"{folder}/samsung/publish/index.html");
string loaderhashsha512 = gethash($"{folder}/samsung/publish/loader.js");
string apphashsha512 = gethash($"{folder}/samsung/publish/app.js");
string confighashsha512 = gethash($"{folder}/samsung/publish/config.xml");
string iconhashsha512 = gethash($"{folder}/samsung/publish/icon.png");
string logohashsha512 = gethash($"{folder}/samsung/publish/logo_appname_fg.png");
string author_sigxml = IO.File.ReadAllText($"{folder}/samsung/author-signature.xml");
author_sigxml = author_sigxml.Replace("loaderhashsha512", loaderhashsha512).Replace("apphashsha512", apphashsha512)
.Replace("iconhashsha512", iconhashsha512).Replace("logohashsha512", logohashsha512)
.Replace("confighashsha512", confighashsha512)
.Replace("indexhashsha512", indexhashsha512);
IO.File.WriteAllText($"{folder}/samsung/publish/author-signature.xml", author_sigxml);
string authorsignaturehashsha512 = gethash($"{folder}/samsung/publish/author-signature.xml");
string sigxml1 = IO.File.ReadAllText($"{folder}/samsung/signature1.xml");
sigxml1 = sigxml1.Replace("loaderhashsha512", loaderhashsha512).Replace("apphashsha512", apphashsha512)
.Replace("confighashsha512", confighashsha512).Replace("authorsignaturehashsha512", authorsignaturehashsha512)
.Replace("iconhashsha512", iconhashsha512).Replace("logohashsha512", logohashsha512).Replace("indexhashsha512", indexhashsha512);
IO.File.WriteAllText($"{folder}/samsung/publish/signature1.xml", sigxml1);
ZipFile.CreateFromDirectory($"{folder}/samsung/publish/", wgt);
return File(IO.File.OpenRead(wgt), "application/octet-stream");
}
#endregion
#region MSX
[HttpGet]
[AllowAnonymous]
[Route("msx/start.json")]
public ActionResult MSX()
{
return Content(FileCache.ReadAllText("msx.json").Replace("{localhost}", host), "application/json; charset=utf-8");
}
#endregion
#region startpage.js
[HttpGet]
[AllowAnonymous]
[Route("startpage.js")]
public ActionResult StartPage()
{
return Content(FileCache.ReadAllText("plugins/startpage.js").Replace("{localhost}", host), "application/javascript; charset=utf-8");
}
#endregion
#region lampainit.js
[HttpGet]
[AllowAnonymous]
[Route("lampainit.js")]
public ActionResult LamInit(bool lite)
{
string initiale = string.Empty;
var sb = new StringBuilder(FileCache.ReadAllText($"plugins/{(lite ? "liteinit" : "lampainit")}.js"));
if (AppInit.modules != null)
{
if (lite)
{
if (AppInit.conf.LampaWeb.initPlugins.online && AppInit.modules.FirstOrDefault(i => i.dll == "Online.dll" && i.enable) != null)
initiale += "\"{localhost}/lite.js\",";
if (AppInit.conf.LampaWeb.initPlugins.sisi && AppInit.modules.FirstOrDefault(i => i.dll == "SISI.dll" && i.enable) != null)
initiale += "\"{localhost}/sisi.js?lite=true\",";
if (AppInit.conf.LampaWeb.initPlugins.sync)
initiale += "\"{localhost}/sync.js?lite=true\",";
}
else
{
if (AppInit.conf.LampaWeb.initPlugins.dlna && AppInit.modules.FirstOrDefault(i => i.dll == "DLNA.dll" && i.enable) != null)
initiale += "{\"url\": \"{localhost}/dlna.js\",\"status\": 1,\"name\": \"DLNA\",\"author\": \"lampac\"},";
if (AppInit.modules.FirstOrDefault(i => i.dll == "Tracks.dll" && i.enable) != null)
{
if (AppInit.conf.LampaWeb.initPlugins.tracks)
initiale += "{\"url\": \"{localhost}/tracks.js\",\"status\": 1,\"name\": \"Tracks.js\",\"author\": \"lampac\"},";
if (AppInit.conf.LampaWeb.initPlugins.transcoding && AppInit.conf.transcoding.enable)
initiale += "{\"url\": \"{localhost}/transcoding.js\",\"status\": 1,\"name\": \"Transcoding video\",\"author\": \"lampac\"},";
}
if (AppInit.conf.LampaWeb.initPlugins.tmdbProxy)
initiale += "{\"url\": \"{localhost}/tmdbproxy.js\",\"status\": 1,\"name\": \"TMDB Proxy\",\"author\": \"lampac\"},";
if (AppInit.conf.LampaWeb.initPlugins.online && AppInit.modules.FirstOrDefault(i => i.dll == "Online.dll" && i.enable) != null)
initiale += "{\"url\": \"{localhost}/online.js\",\"status\": 1,\"name\": \"Онлайн\",\"author\": \"lampac\"},";
if (AppInit.conf.LampaWeb.initPlugins.catalog && AppInit.modules.FirstOrDefault(i => i.dll == "Catalog.dll" && i.enable) != null)
initiale += "{\"url\": \"{localhost}/catalog.js\",\"status\": 1,\"name\": \"Альтернативные источники каталога\",\"author\": \"lampac\"},";
if (AppInit.conf.LampaWeb.initPlugins.sisi && AppInit.modules.FirstOrDefault(i => i.dll == "SISI.dll" && i.enable) != null)
{
initiale += "{\"url\": \"{localhost}/sisi.js\",\"status\": 1,\"name\": \"Клубничка\",\"author\": \"lampac\"},";
initiale += "{\"url\": \"{localhost}/startpage.js\",\"status\": 1,\"name\": \"Стартовая страница\",\"author\": \"lampac\"},";
}
if (AppInit.conf.LampaWeb.initPlugins.sync)
initiale += "{\"url\": \"{localhost}/sync.js\",\"status\": 1,\"name\": \"Синхронизация\",\"author\": \"lampac\"},";
if (AppInit.conf.LampaWeb.initPlugins.timecode)
initiale += "{\"url\": \"{localhost}/timecode.js\",\"status\": 1,\"name\": \"Синхронизация тайм-кодов\",\"author\": \"lampac\"},";
if (AppInit.conf.LampaWeb.initPlugins.bookmark)
initiale += "{\"url\": \"{localhost}/bookmark.js\",\"status\": 1,\"name\": \"Синхронизация закладок\",\"author\": \"lampac\"},";
if (AppInit.conf.LampaWeb.initPlugins.torrserver && AppInit.modules.FirstOrDefault(i => i.dll == "TorrServer.dll" && i.enable) != null)
initiale += "{\"url\": \"{localhost}/ts.js\",\"status\": 1,\"name\": \"TorrServer\",\"author\": \"lampac\"},";
if (AppInit.conf.LampaWeb.initPlugins.backup)
initiale += "{\"url\": \"{localhost}/backup.js\",\"status\": 1,\"name\": \"Backup\",\"author\": \"lampac\"},";
if (AppInit.conf.pirate_store)
sb = sb.Replace("{pirate_store}", FileCache.ReadAllText("plugins/pirate_store.js"));
if (AppInit.conf.accsdb.enable || (!requestInfo.IsLocalIp && !AppInit.conf.WAF.allowExternalIpAccess))
sb = sb.Replace("{deny}", FileCache.ReadAllText("plugins/deny.js").Replace("{cubMesage}", AppInit.conf.accsdb.authMesage));
}
}
sb = sb.Replace("{lampainit-invc}", FileCache.ReadAllText("plugins/lampainit-invc.js"));
sb = sb.Replace("{initiale}", Regex.Replace(initiale, ",$", ""));
sb = sb.Replace("{country}", requestInfo.Country);
sb = sb.Replace("{localhost}", host);
sb = sb.Replace("{deny}", string.Empty);
sb = sb.Replace("{pirate_store}", string.Empty);
sb = sb.Replace("{ major: 0, minor: 0 }", $"{{major: {appversion}, minor: {minorversion}}}");
if (AppInit.modules != null && AppInit.modules.FirstOrDefault(i => i.dll == "JacRed.dll" && i.enable) != null)
sb = sb.Replace("{jachost}", Regex.Replace(host, "^https?://", ""));
else
sb = sb.Replace("{jachost}", "redapi.apn.monster");
#region full_btn_priority_hash
string online_version = Regex.Match(FileCache.ReadAllText("plugins/online.js"), "version: '([^']+)'").Groups[1].Value;
string LampaUtilshash(string input)
{
if (!AppInit.conf.online.version)
input = input.Replace($"v{online_version}", "");
string str = (input ?? string.Empty);
int hash = 0;
if (str.Length == 0) return hash.ToString();
for (int i = 0; i < str.Length; i++)
{
int _char = str[i];
hash = (hash << 5) - hash + _char;
hash = hash & hash; // Преобразование в 32-битное целое число
}
return Math.Abs(hash).ToString();
}
string full_btn_priority_hash = LampaUtilshash($"<div class=\"full-start__button selector view--online lampac--button\" data-subtitle=\"{AppInit.conf.online.name} v{online_version}\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 392.697 392.697\" xml:space=\"preserve\">\n <path d=\"M21.837,83.419l36.496,16.678L227.72,19.886c1.229-0.592,2.002-1.846,1.98-3.209c-0.021-1.365-0.834-2.592-2.082-3.145\n L197.766,0.3c-0.903-0.4-1.933-0.4-2.837,0L21.873,77.036c-1.259,0.559-2.073,1.803-2.081,3.18\n C19.784,81.593,20.584,82.847,21.837,83.419z\" fill=\"currentColor\"></path>\n <path d=\"M185.689,177.261l-64.988-30.01v91.617c0,0.856-0.44,1.655-1.167,2.114c-0.406,0.257-0.869,0.386-1.333,0.386\n c-0.368,0-0.736-0.082-1.079-0.244l-68.874-32.625c-0.869-0.416-1.421-1.293-1.421-2.256v-92.229L6.804,95.5\n c-1.083-0.496-2.344-0.406-3.347,0.238c-1.002,0.645-1.608,1.754-1.608,2.944v208.744c0,1.371,0.799,2.615,2.045,3.185\n l178.886,81.768c0.464,0.211,0.96,0.315,1.455,0.315c0.661,0,1.318-0.188,1.892-0.555c1.002-0.645,1.608-1.754,1.608-2.945\n V180.445C187.735,179.076,186.936,177.831,185.689,177.261z\" fill=\"currentColor\"></path>\n <path d=\"M389.24,95.74c-1.002-0.644-2.264-0.732-3.347-0.238l-178.876,81.76c-1.246,0.57-2.045,1.814-2.045,3.185v208.751\n c0,1.191,0.606,2.302,1.608,2.945c0.572,0.367,1.23,0.555,1.892,0.555c0.495,0,0.991-0.104,1.455-0.315l178.876-81.768\n c1.246-0.568,2.045-1.813,2.045-3.185V98.685C390.849,97.494,390.242,96.384,389.24,95.74z\" fill=\"currentColor\"></path>\n <path d=\"M372.915,80.216c-0.009-1.377-0.823-2.621-2.082-3.18l-60.182-26.681c-0.938-0.418-2.013-0.399-2.938,0.045\n l-173.755,82.992l60.933,29.117c0.462,0.211,0.958,0.316,1.455,0.316s0.993-0.105,1.455-0.316l173.066-79.092\n C372.122,82.847,372.923,81.593,372.915,80.216z\" fill=\"currentColor\"></path>\n </svg>\n\n <span>Онлайн</span>\n </div>");
sb = sb.Replace("{full_btn_priority_hash}", full_btn_priority_hash)
.Replace("{btn_priority_forced}", AppInit.conf.online.btn_priority_forced.ToString().ToLower());
#endregion
#region domain token
if (!string.IsNullOrEmpty(AppInit.conf.accsdb.domainId_pattern))
{
string token = Regex.Match(HttpContext.Request.Host.Host, AppInit.conf.accsdb.domainId_pattern).Groups[1].Value;
sb = sb.Replace("{token}", token);
}
else { sb = sb.Replace("{token}", string.Empty); }
#endregion
return Content(sb.ToString(), "application/javascript; charset=utf-8");
}
#endregion
#region on.js
[HttpGet]
[AllowAnonymous]
[Route("on.js")]
[Route("on/js/{token}")]
[Route("on/h/{token}")]
[Route("on/{token}")]
public ActionResult LamOnInit(string token, bool adult = true)
{
if (adult && HttpContext.Request.Path.Value.StartsWith("/on/h/"))
adult = false;
var plugins = new List<string>(10);
var sb = new StringBuilder(FileCache.ReadAllText("plugins/on.js"));
if (AppInit.modules != null)
{
void send(string name, bool worktoken)
{
if (worktoken && !string.IsNullOrEmpty(token))
{
plugins.Add($"\"{{localhost}}/{name}/js/{HttpUtility.UrlEncode(token)}\"");
}
else
{
plugins.Add($"\"{{localhost}}/{name}.js\"");
}
}
if (AppInit.conf.LampaWeb.initPlugins.dlna && AppInit.modules.FirstOrDefault(i => i.dll == "DLNA.dll" && i.enable) != null)
send("dlna", true);
if (AppInit.modules.FirstOrDefault(i => i.dll == "Tracks.dll" && i.enable) != null)
{
if (AppInit.conf.LampaWeb.initPlugins.tracks)
send("tracks", true);
if (AppInit.conf.LampaWeb.initPlugins.transcoding && AppInit.conf.transcoding.enable)
send("transcoding", true);
}
if (AppInit.conf.LampaWeb.initPlugins.tmdbProxy)
send("tmdbproxy", true);
if (AppInit.conf.LampaWeb.initPlugins.online && AppInit.modules.FirstOrDefault(i => i.dll == "Online.dll" && i.enable) != null)
send("online", true);
if (adult)
{
if (AppInit.conf.LampaWeb.initPlugins.sisi && AppInit.modules.FirstOrDefault(i => i.dll == "SISI.dll" && i.enable) != null)
{
send("sisi", true);
send("startpage", false);
}
}
if (AppInit.conf.LampaWeb.initPlugins.sync)
send("sync", true);
if (AppInit.conf.LampaWeb.initPlugins.timecode)
send("timecode", true);
if (AppInit.conf.LampaWeb.initPlugins.bookmark)
send("bookmark", true);
if (AppInit.conf.LampaWeb.initPlugins.torrserver && AppInit.modules.FirstOrDefault(i => i.dll == "TorrServer.dll" && i.enable) != null)
send("ts", true);
if (AppInit.conf.LampaWeb.initPlugins.backup)
send("backup", true);
}
if (plugins.Count == 0)
sb = sb.Replace("{plugins}", string.Empty);
else
{
sb = sb.Replace("{plugins}", string.Join(",", plugins));
}
sb = sb.Replace("{country}", requestInfo.Country)
.Replace("{localhost}", host);
return Content(sb.ToString(), "application/javascript; charset=utf-8");
}
#endregion
#region privateinit.js
[HttpGet]
[Route("privateinit.js")]
public ActionResult PrivateInit()
{
var user = requestInfo.user;
if (user == null || user.ban || DateTime.UtcNow > user.expires)
return Content(string.Empty, "application/javascript; charset=utf-8");
var sb = new StringBuilder(FileCache.ReadAllText("plugins/privateinit.js"));
sb = sb.Replace("{country}", requestInfo.Country)
.Replace("{localhost}", host);
if (AppInit.modules != null && AppInit.modules.FirstOrDefault(i => i.dll == "JacRed.dll" && i.enable) != null)
sb = sb.Replace("{jachost}", Regex.Replace(host, "^https?://", ""));
else
sb = sb.Replace("{jachost}", "redapi.apn.monster");
return Content(sb.ToString(), "application/javascript; charset=utf-8");
}
#endregion
}
}

View File

@ -0,0 +1,203 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Shared;
using Shared.Engine;
using Shared.Engine.Utilities;
using Shared.Models;
using Shared.Models.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace Lampac.Controllers
{
public class MediaController : BaseController
{
#region Routes
[HttpGet]
[AllowAnonymous]
[Route("/media/rsize/{token}/{width}/{height}/{*url}")]
public ActionResult Get(string token, int width, int height, string url)
{
return GetLocation(url + HttpContext.Request.QueryString.Value, new MediaRequestBase
{
type = "img",
auth_token = token,
width = width,
height = height
}, null, null);
}
[HttpGet]
[AllowAnonymous]
[Route("/media/{type}/{token}/{*url}")]
public ActionResult Get(string type, string token, string url)
{
return GetLocation(url + HttpContext.Request.QueryString.Value, new MediaRequestBase
{
auth_token = token,
type = type
}, null, null);
}
[HttpGet]
[AllowAnonymous]
[Route("/media")]
public ActionResult Get(string url, string headers, [FromQuery] MediaRequestBase request)
{
var webProxy = CreateProxy(request?.proxy, request?.proxy_name);
var headerList = HeadersModel.Init(ParseHeaders(headers));
return GetLocation(url, request, headerList, webProxy);
}
[HttpPost]
[AllowAnonymous]
[Route("/media")]
public ActionResult Post([FromBody] MediaRequest request)
{
if (!TryValidateBase(request, out ActionResult errorResult))
return errorResult;
if (request.urls == null || request.urls.Count == 0)
return JsonError("invalid urls", 400);
var webProxy = CreateProxy(request.proxy, request.proxy_name);
var headerList = HeadersModel.Init(request.headers);
var streamSettings = CreateStreamSettings(request);
var result = new List<string>(request.urls.Count);
foreach (string source in request.urls)
{
string proxied = request.type == "img"
? CreateImageProxy(source, request.width, request.height, headerList, webProxy)
: HostStreamProxy(streamSettings, source, headerList, webProxy);
result.Add(proxied);
}
return Json(new
{
success = true,
urls = result
});
}
#endregion
#region Helpers
ActionResult GetLocation(string url, MediaRequestBase request, List<HeadersModel> headers, WebProxy proxy)
{
if (string.IsNullOrEmpty(url))
return JsonError("invalid url", 400);
if (!TryValidateBase(request, out ActionResult errorResult))
return errorResult;
string location = request.type == "img"
? CreateImageProxy(url, request.width, request.height, headers, proxy)
: HostStreamProxy(CreateStreamSettings(request), url, headers, proxy);
return Redirect(location);
}
BaseSettings CreateStreamSettings(MediaRequestBase request)
{
return new BaseSettings
{
plugin = "media",
streamproxy = true,
apnstream = request.apnstream,
useproxystream = request.useproxystream
};
}
bool TryValidateBase(MediaRequestBase request, out ActionResult errorResult)
{
errorResult = null;
var init = AppInit.conf.media;
if (request == null)
{
errorResult = JsonError("invalid request", 400);
return false;
}
if (string.IsNullOrEmpty(request.auth_token) || init?.tokens == null || !init.tokens.Any(t => t == request.auth_token))
{
errorResult = JsonError("unauthorized", 401);
return false;
}
return true;
}
Dictionary<string, string> ParseHeaders(string headers)
{
try
{
if (!string.IsNullOrEmpty(headers))
return JsonConvert.DeserializeObject<Dictionary<string, string>>(headers);
}
catch { }
return null;
}
WebProxy CreateProxy(string proxyValue, string proxyName)
{
ProxySettings proxySettings = null;
if (!string.IsNullOrEmpty(proxyValue))
{
proxySettings = new ProxySettings
{
list = [proxyValue]
};
}
else if (!string.IsNullOrEmpty(proxyName) && AppInit.conf.globalproxy != null)
{
var settings = AppInit.conf.globalproxy.FirstOrDefault(i => i.name == proxyName);
if (settings?.list != null && settings.list.Length > 0)
proxySettings = settings;
}
if (proxySettings == null)
return null;
return ProxyManager.ConfigureWebProxy(proxySettings, proxySettings.list.First()).proxy;
}
string CreateImageProxy(string url, int? width, int? height, List<HeadersModel> headers, WebProxy proxy)
{
if (!AppInit.conf.serverproxy.enable)
return url;
string encrypted = ProxyLink.Encrypt(url, requestInfo.IP, headers, proxy, "posterapi", verifyip: false, IsProxyImg: true);
if (AppInit.conf.accsdb.enable && !AppInit.conf.serverproxy.encrypt)
encrypted = AccsDbInvk.Args(encrypted, HttpContext);
int normalizedWidth = Math.Max(0, width ?? 0);
int normalizedHeight = Math.Max(0, height ?? 0);
if (normalizedWidth > 0 || normalizedHeight > 0)
return $"{host}/proxyimg:{normalizedWidth}:{normalizedHeight}/{encrypted}";
return $"{host}/proxyimg/{encrypted}";
}
ActionResult JsonError(string message, int statusCode)
{
HttpContext.Response.StatusCode = statusCode;
return ContentTo(JsonConvertPool.SerializeObject(new
{
success = false,
error = message
}));
}
#endregion
}
}

View File

@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Shared;
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace Lampac.Controllers
{
public class PlayerInnerController : BaseController
{
[HttpGet]
[Route("player-inner/{*uri}")]
public void PlayerInner(string uri)
{
if (string.IsNullOrEmpty(AppInit.conf.playerInner))
return;
// убираем мусор в ссылке
uri = Regex.Replace(uri, "[^a-z0-9_:\\-\\/\\.\\=\\?\\&\\%\\@]+", "", RegexOptions.IgnoreCase);
uri = uri + HttpContext.Request.QueryString.Value;
if (!Uri.TryCreate(uri, UriKind.Absolute, out var stream) ||
(stream.Scheme != Uri.UriSchemeHttp && stream.Scheme != Uri.UriSchemeHttps))
return;
var _info = new ProcessStartInfo()
{
FileName = AppInit.conf.playerInner
};
_info.ArgumentList.Add(stream.AbsoluteUri);
Process.Start(_info);
}
}
}

View File

@ -0,0 +1,85 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Shared;
using Shared.Engine;
using System.IO.Compression;
using System.Threading.Tasks;
namespace Lampac.Controllers
{
public class RchBaseApi : BaseController
{
[HttpGet]
[Route("rch/check/connected")]
public ActionResult СheckСonnected()
{
var rch = new RchClient(HttpContext, host, new Shared.Models.Base.BaseSettings() { rhub = true }, requestInfo);
if (rch.IsNotConnected())
return ContentTo(rch.connectionMsg);
var info = rch.InfoConnected() ?? new RchClientInfo();
return Json(new { info.version, info.apkVersion, info.rchtype });
}
}
public class RchApi : Controller
{
[HttpPost]
[AllowAnonymous]
[Route("rch/result")]
async public Task<ActionResult> WriteResult([FromQuery] string id)
{
if (string.IsNullOrEmpty(id))
return BadRequest(401);
if (!RchClient.rchIds.TryGetValue(id, out var rchHub))
return BadRequest(400);
try
{
await Request.Body.CopyToAsync(rchHub.ms, PoolInvk.bufferSize, HttpContext.RequestAborted);
rchHub.ms.Position = 0;
rchHub.tcs.TrySetResult(null);
}
catch
{
rchHub.tcs.TrySetResult(null);
return BadRequest(400);
}
return Ok();
}
[HttpPost]
[AllowAnonymous]
[Route("rch/gzresult")]
async public Task<ActionResult> WriteZipResult([FromQuery] string id)
{
if (string.IsNullOrEmpty(id))
return BadRequest(401);
if (!RchClient.rchIds.TryGetValue(id, out var rchHub))
return BadRequest(400);
try
{
using (var gzip = new GZipStream(Request.Body, CompressionMode.Decompress, leaveOpen: true))
{
await gzip.CopyToAsync(rchHub.ms, PoolInvk.bufferSize, HttpContext.RequestAborted);
rchHub.ms.Position = 0;
rchHub.tcs.TrySetResult(null);
}
}
catch
{
rchHub.tcs.TrySetResult(null);
return BadRequest(400);
}
return Ok();
}
}
}

View File

@ -0,0 +1,356 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Shared;
using Shared.Engine;
using Shared.Engine.Utilities;
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using IO = System.IO;
namespace Lampac.Controllers
{
public class StorageController : BaseController
{
#region StorageController
static StorageController()
{
Directory.CreateDirectory("database/storage");
Directory.CreateDirectory("database/storage/temp");
}
#endregion
#region backup.js
[HttpGet]
[AllowAnonymous]
[Route("backup.js")]
[Route("backup/js/{token}")]
public ActionResult Backup(string token)
{
if (!AppInit.conf.storage.enable)
return Content(string.Empty, "application/javascript; charset=utf-8");
var sb = new StringBuilder(FileCache.ReadAllText("plugins/backup.js"));
sb.Replace("{localhost}", host)
.Replace("{token}", HttpUtility.UrlEncode(token));
return Content(sb.ToString(), "application/javascript; charset=utf-8");
}
#endregion
#region sync.js
[HttpGet]
[AllowAnonymous]
[Route("sync.js")]
[Route("sync/js/{token}")]
public ActionResult SyncJS(string token, bool lite)
{
if (!AppInit.conf.storage.enable)
return Content(string.Empty, "application/javascript; charset=utf-8");
StringBuilder sb;
if (lite || AppInit.conf.sync_user.version == 1)
{
sb = new StringBuilder(FileCache.ReadAllText($"plugins/{(lite ? "sync_lite" : "sync")}.js"));
}
else
{
sb = new StringBuilder(FileCache.ReadAllText("plugins/sync_v2/sync.js"));
}
sb.Replace("{sync-invc}", FileCache.ReadAllText("plugins/sync-invc.js"))
.Replace("{localhost}", host)
.Replace("{token}", HttpUtility.UrlEncode(token));
return Content(sb.ToString(), "application/javascript; charset=utf-8");
}
#endregion
#region Get
[HttpGet]
[Route("/storage/get")]
async public Task<ActionResult> Get(string path, string pathfile, bool responseInfo)
{
if (!AppInit.conf.storage.enable)
return ContentTo("{\"success\": false, \"msg\": \"disabled\"}");
string outFile = getFilePath(path, pathfile, false);
if (outFile == null || !IO.File.Exists(outFile))
return ContentTo("{\"success\": false, \"msg\": \"outFile\"}");
var file = new FileInfo(outFile);
var fileInfo = new { file.Name, path = outFile, file.Length, changeTime = new DateTimeOffset(file.LastWriteTimeUtc).ToUnixTimeMilliseconds() };
if (responseInfo)
return Json(new { success = true, uid = requestInfo.user_uid, fileInfo });
string data;
if (AppInit.conf.storage.brotli)
{
data = await BrotliTo.DecompressAsync(outFile);
}
else
{
var semaphore = new SemaphorManager(outFile, TimeSpan.FromSeconds(20));
try
{
await semaphore.WaitAsync();
data = await IO.File.ReadAllTextAsync(outFile);
}
catch
{
HttpContext.Response.StatusCode = 503;
return ContentTo("{\"success\": false, \"msg\": \"fileLock\"}");
}
finally
{
semaphore.Release();
}
}
return Json(new { success = true, uid = requestInfo.user_uid, fileInfo, data });
}
#endregion
#region Set
[HttpPost]
[Route("/storage/set")]
async public Task<ActionResult> Set([FromQuery]string path, [FromQuery]string pathfile, [FromQuery]string connectionId, [FromQuery]string events)
{
if (!AppInit.conf.storage.enable)
return ContentTo("{\"success\": false, \"msg\": \"disabled\"}");
if (HttpContext.Request.ContentLength > AppInit.conf.storage.max_size)
return ContentTo("{\"success\": false, \"msg\": \"max_size\"}");
string outFile = getFilePath(path, pathfile, true);
if (outFile == null)
return ContentTo("{\"success\": false, \"msg\": \"outFile\"}");
using (var memoryStream = PoolInvk.msm.GetStream())
{
try
{
await HttpContext.Request.Body.CopyToAsync(memoryStream);
memoryStream.Position = 0;
}
catch
{
HttpContext.Response.StatusCode = 503;
return ContentTo("{\"success\": false, \"msg\": \"Request.Body.CopyToAsync\"}");
}
if (AppInit.conf.storage.brotli)
{
await BrotliTo.CompressAsync(outFile, memoryStream);
}
else
{
var semaphore = new SemaphorManager(outFile, TimeSpan.FromSeconds(20));
try
{
await semaphore.WaitAsync();
using (var fileStream = new FileStream(outFile, FileMode.Create, FileAccess.Write, FileShare.None, PoolInvk.bufferSize))
await memoryStream.CopyToAsync(fileStream, PoolInvk.bufferSize);
}
catch
{
HttpContext.Response.StatusCode = 503;
return ContentTo("{\"success\": false, \"msg\": \"fileLock\"}");
}
finally
{
semaphore.Release();
}
}
}
#region events
if (!string.IsNullOrEmpty(events))
{
try
{
var json = JsonConvert.DeserializeObject<JObject>(CrypTo.DecodeBase64(events));
_ = Shared.Startup.WS.EventsAsync(json.Value<string>("connectionId"), requestInfo.user_uid, json.Value<string>("name"), json.Value<string>("data")).ConfigureAwait(false);
_ = Shared.Startup.Nws.EventsAsync(json.Value<string>("connectionId"), requestInfo.user_uid, json.Value<string>("name"), json.Value<string>("data")).ConfigureAwait(false);
}
catch { }
}
else
{
string edata = JsonConvertPool.SerializeObject(new { path, pathfile });
_ = Shared.Startup.Nws.EventsAsync(connectionId, requestInfo.user_uid, "storage", edata).ConfigureAwait(false);
}
#endregion
var inf = new FileInfo(outFile);
return Json(new
{
success = true,
uid = requestInfo.user_uid,
fileInfo = new { inf.Name, path = outFile, inf.Length, changeTime = new DateTimeOffset(inf.LastWriteTimeUtc).ToUnixTimeMilliseconds() }
});
}
#endregion
#region TempGet
[HttpGet]
[Route("/storage/temp/{key}")]
async public Task<ActionResult> TempGet(string key, bool responseInfo)
{
if (!AppInit.conf.storage.enable)
return ContentTo("{\"success\": false, \"msg\": \"disabled\"}");
string outFile = getFilePath("temp", null, false, user_uid: key);
if (outFile == null || !IO.File.Exists(outFile))
return ContentTo("{\"success\": false, \"msg\": \"outFile\"}");
var file = new FileInfo(outFile);
var fileInfo = new { file.Name, path = outFile, file.Length, changeTime = new DateTimeOffset(file.LastWriteTimeUtc).ToUnixTimeMilliseconds() };
if (responseInfo)
return Json(new { success = true, uid = requestInfo.user_uid, fileInfo });
string data;
if (AppInit.conf.storage.brotli)
{
data = await BrotliTo.DecompressAsync(outFile);
}
else
{
var semaphore = new SemaphorManager(outFile, TimeSpan.FromSeconds(20));
try
{
await semaphore.WaitAsync();
data = await IO.File.ReadAllTextAsync(outFile);
}
catch
{
HttpContext.Response.StatusCode = 503;
return ContentTo("{\"success\": false, \"msg\": \"fileLock\"}");
}
finally
{
semaphore.Release();
}
}
return Json(new { success = true, uid = requestInfo.user_uid, fileInfo, data });
}
#endregion
#region TempSet
[HttpPost]
[Route("/storage/temp/{key}")]
async public Task<ActionResult> TempSet(string key)
{
if (!AppInit.conf.storage.enable)
return ContentTo("{\"success\": false, \"msg\": \"disabled\"}");
if (HttpContext.Request.ContentLength > AppInit.conf.storage.max_size)
return ContentTo("{\"success\": false, \"msg\": \"max_size\"}");
string outFile = getFilePath("temp", null, true, user_uid: key);
if (outFile == null)
return ContentTo("{\"success\": false, \"msg\": \"outFile\"}");
using (var memoryStream = PoolInvk.msm.GetStream())
{
try
{
await HttpContext.Request.Body.CopyToAsync(memoryStream);
memoryStream.Position = 0;
}
catch
{
HttpContext.Response.StatusCode = 503;
return ContentTo("{\"success\": false, \"msg\": \"Request.Body.CopyToAsync\"}");
}
if (AppInit.conf.storage.brotli)
{
await BrotliTo.CompressAsync(outFile, memoryStream);
}
else
{
var semaphore = new SemaphorManager(outFile, TimeSpan.FromSeconds(20));
try
{
await semaphore.WaitAsync();
using (var fileStream = new FileStream(outFile, FileMode.Create, FileAccess.Write, FileShare.None, PoolInvk.bufferSize))
await memoryStream.CopyToAsync(fileStream, PoolInvk.bufferSize);
}
catch
{
HttpContext.Response.StatusCode = 503;
return ContentTo("{\"success\": false, \"msg\": \"fileLock\"}");
}
finally
{
semaphore.Release();
}
}
}
var inf = new FileInfo(outFile);
return Json(new
{
success = true,
uid = requestInfo.user_uid,
fileInfo = new { inf.Name, path = outFile, inf.Length, changeTime = new DateTimeOffset(inf.LastWriteTimeUtc).ToUnixTimeMilliseconds() }
});
}
#endregion
#region getFilePath
string getFilePath(string path, string pathfile, bool createDirectory, string user_uid = null)
{
if (path == "temp" && string.IsNullOrEmpty(user_uid))
return null;
path = Regex.Replace(path, "[^a-z0-9\\-]", "", RegexOptions.IgnoreCase);
string id = user_uid ?? requestInfo.user_uid;
if (string.IsNullOrEmpty(id))
return null;
id += pathfile;
string md5key = AppInit.conf.storage.md5name ? CrypTo.md5(id) : Regex.Replace(id, "[^a-z0-9\\-]", "");
if (path == "temp")
{
return $"database/storage/{path}/{md5key}";
}
else
{
if (createDirectory)
Directory.CreateDirectory($"database/storage/{path}/{md5key.Substring(0, 2)}");
return $"database/storage/{path}/{md5key.Substring(0, 2)}/{md5key.Substring(2)}";
}
}
#endregion
}
}

View File

@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Shared;
using IO = System.IO;
namespace Lampac.Controllers
{
public class SyncApiController : BaseController
{
[HttpGet]
[Route("/api/sync")]
public ActionResult Sync()
{
var sync = AppInit.conf.sync;
if (!requestInfo.IsLocalRequest || !sync.enable || sync.type != "master")
return Content("error");
if (sync.initconf == "current")
return Content(JsonConvert.SerializeObject(AppInit.conf), "application/json; charset=utf-8");
var init = new AppInit();
string confile = "sync.conf";
if (sync.override_conf != null && sync.override_conf.TryGetValue(requestInfo.IP, out string _conf))
confile = _conf;
if (IO.File.Exists(confile))
init = JsonConvert.DeserializeObject<AppInit>(IO.File.ReadAllText(confile));
init.accsdb.users = AppInit.conf.accsdb.users;
string json = JsonConvert.SerializeObject(init);
json = json.Replace("{server_ip}", requestInfo.IP);
return Content(json, "application/json; charset=utf-8");
}
}
}

View File

@ -0,0 +1,118 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Shared;
using Shared.Engine;
using Shared.Models;
using Shared.Models.SQL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace Lampac.Controllers
{
public class TimecodeController : BaseController
{
#region timecode.js
[HttpGet]
[AllowAnonymous]
[Route("timecode.js")]
[Route("timecode/js/{token}")]
public ActionResult timecode(string token)
{
string file = FileCache.ReadAllText("plugins/timecode.js").Replace("{localhost}", host);
file = file.Replace("{token}", HttpUtility.UrlEncode(token));
return Content(file, contentType: "application/javascript; charset=utf-8");
}
#endregion
[HttpGet]
[Route("/timecode/all")]
async public Task<ActionResult> Get(string card_id)
{
if (string.IsNullOrEmpty(card_id))
return Json(new { });
string userId = getUserid(requestInfo, HttpContext);
Dictionary<string, string> timecodes = null;
using (var sqlDb = SyncUserContext.Factory != null
? SyncUserContext.Factory.CreateDbContext()
: new SyncUserContext())
{
timecodes = await sqlDb.timecodes
.AsNoTracking()
.Where(i => i.user == userId && i.card == card_id)
.ToDictionaryAsync(i => i.item, i => i.data);
}
if (timecodes == null || timecodes.Count == 0)
return Json(new { });
return Json(timecodes);
}
[HttpPost]
[Route("/timecode/add")]
async public Task<ActionResult> Set([FromQuery] string card_id, [FromForm] string id, [FromForm] string data)
{
if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(data))
return ContentTo("{\"success\": false}");
if (string.IsNullOrEmpty(card_id))
return ContentTo("{\"success\": false}");
string userId = getUserid(requestInfo, HttpContext);
bool success = false;
try
{
await SyncUserContext.semaphore.WaitAsync(TimeSpan.FromSeconds(30));
using (var sqlDb = SyncUserContext.Factory != null
? SyncUserContext.Factory.CreateDbContext()
: new SyncUserContext())
{
sqlDb.timecodes
.Where(i => i.user == userId && i.card == card_id && i.item == id)
.ExecuteDelete();
sqlDb.timecodes.Add(new SyncUserTimecodeSqlModel
{
user = userId,
card = card_id,
item = id,
data = data,
updated = DateTime.UtcNow
});
success = await sqlDb.SaveChangesAsync() > 0;
}
}
catch { }
finally
{
SyncUserContext.semaphore.Release();
}
return ContentTo($"{{\"success\": {success.ToString().ToLower()}}}");
}
static string getUserid(RequestModel requestInfo, HttpContext httpContext)
{
string user_id = requestInfo.user_uid;
if (httpContext.Request.Query.TryGetValue("profile_id", out var profile_id) && !string.IsNullOrEmpty(profile_id) && profile_id != "0")
return $"{user_id}_{profile_id}";
return user_id;
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Shared;
using Shared.Engine;
using System.Web;
namespace Lampac.Controllers
{
public class TmdbController : BaseController
{
[HttpGet]
[AllowAnonymous]
[Route("tmdbproxy.js")]
[Route("tmdbproxy/js/{token}")]
public ActionResult TmdbProxy(string token)
{
string file = FileCache.ReadAllText("plugins/tmdbproxy.js").Replace("{localhost}", host);
file = file.Replace("{token}", HttpUtility.UrlEncode(token));
return Content(file, contentType: "application/javascript; charset=utf-8");
}
}
}

View File

@ -0,0 +1,204 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Shared;
namespace Lampac.Controllers
{
public class WebLogController : BaseController
{
[HttpGet]
[AllowAnonymous]
[Route("weblog")]
public ActionResult WebLog(string token, string pattern, string receive = "http")
{
if (!AppInit.conf.weblog.enable)
return Content("Включите weblog в init.conf\n\n\"weblog\": {\n \"enable\": true\n}", contentType: "text/plain; charset=utf-8");
if (!string.IsNullOrEmpty(AppInit.conf.weblog.token) && token != AppInit.conf.weblog.token)
return Content("Используйте /weblog?token=my_key\n\n\"weblog\": {\n \"enable\": true,\n \"token\": \"my_key\"\n}", contentType: "text/plain; charset=utf-8");
string html = $@"<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>weblog</title>
<style>
details summary {{
font-weight: bold;
font-size: 1.2em;
color: #2772d2;
cursor: pointer;
list-style: none;
user-select: none;
position: relative;
}}
details summary:hover {{
color: #1a4bb8;
}}
</style>
</head>
<body style='margin: 0px;'>
<div id='controls' style='margin-bottom: 1em; background: #f0f0f0; padding: 10px; border-bottom: 1px solid #ccc;'>
<label style='margin-right: 20px;'>Запросы:
<select id='receiveSelect' style='padding: 0px 5px 0px 0px;'>
<option value='http' {(receive == "http" ? "selected" : "")}>Исходящие</option>
<option value='request' {(receive == "request" ? "selected" : "")}>Входящие</option>
</select>
</label>
<label for='patternInput'>Фильтр: </label>
<input type='text' id='patternInput' placeholder='rezka.ag' value='{pattern ?? ""}' style='margin-right: 20px;' />
</div>
<div id='log'></div>
<script src='/js/nws-client-es5.js'></script>
<script src='/signalr-6.0.25_es5.js'></script>
<script>
let pattern = document.getElementById('patternInput').value.trim();
let receive = document.getElementById('receiveSelect').value;
document.getElementById('patternInput').addEventListener('input', e => {{
pattern = e.target.value.trim();
}});
document.getElementById('receiveSelect').addEventListener('change', e => {{
receive = e.target.value;
}});
function send(message) {{
if (pattern && message.indexOf(pattern) === -1) return;
var messageHtml = message.replace(/</g, '&lt;').replace(/>/g, '&gt;');
const markers = [
{{ text: 'CurrentUrl: ', caseSensitive: true }},
{{ text: 'StatusCode: ', caseSensitive: true }},
{{ text: '&lt;!doctype html&gt;', caseSensitive: false }}
];
for (const marker of markers) {{
let searchText = marker.text;
let messageText = messageHtml;
if (!marker.caseSensitive)
messageText = messageHtml.toLowerCase();
const index = messageText.indexOf(searchText);
if (index !== -1) {{
messageHtml = messageHtml.slice(0, index)
+ '<details><summary>Показать содержимое</summary>'
+ messageHtml.slice(index)
+ '</details>';
break;
}}
}}
var par = document.getElementById('log');
let messageElement = document.createElement('hr');
messageElement.style.cssText = ' margin-bottom: 2.5em; margin-top: 2.5em;';
par.insertBefore(messageElement, par.children[0]);
messageElement = document.createElement('pre');
messageElement.style.cssText = 'padding: 10px; background: cornsilk; white-space: pre-wrap; word-wrap: break-word;';
if (messageHtml.indexOf('<details>') !== -1)
messageElement.innerHTML = messageHtml;
else
messageElement.innerText = message;
par.insertBefore(messageElement, par.children[0]);
}}
let outageReported = false;
function reportOutageOnce(message) {{
if (!outageReported) {{
send(message);
outageReported = true;
}}
}}
{(AppInit.conf.WebSocket.type == "signalr" ? signalCode(token) : nwsCode(token))}
</script>
</body>
</html>";
return Content(html, "text/html; charset=utf-8");
}
static string nwsCode(string token) => $@"
const client = new NativeWsClient(""/nws"", {{
autoReconnect: true,
reconnectDelay: 2000,
onOpen: function () {{
send('WebSocket connected');
outageReported = false;
client.invoke('RegistryWebLog', '{token}');
}},
onClose: function () {{
reportOutageOnce('Connection closed');
}},
onError: function (err) {{
reportOutageOnce('Connection error: ' + (err && err.message ? err.message : String(err)));
}}
}});
client.on('Receive', function (message, e) {{
if (receive === e) send(message);
}});
client.connect();
";
static string signalCode(string token) => $@"
const hubConnection = new signalR.HubConnectionBuilder()
.withUrl('/ws')
.build();
let reconnectAttempts = 0;
const maxReconnectAttempts = 150; // 5 minutes
const reconnectDelay = 2000; // 2 seconds
function startConnection() {{
hubConnection.start()
.then(function () {{
if (reconnectAttempts != 0)
send('WebSocket connected');
reconnectAttempts = 0; // Reset counter on successful connection
hubConnection.invoke('RegistryWebLog', '{token}');
}})
.catch(function (err) {{
console.log(`${{err.toString()}}\n\nAttempting to reconnect (${{reconnectAttempts}}/${{maxReconnectAttempts}})...`);
attemptReconnect();
}});
}}
function attemptReconnect() {{
if (reconnectAttempts < maxReconnectAttempts) {{
reconnectAttempts++;
setTimeout(function() {{
startConnection();
}}, reconnectDelay);
}} else {{
send('Max reconnection attempts reached. Please refresh the page.');
}}
}}
hubConnection.on('Receive', function(message, e) {{
if(receive === e) send(message);
}});
hubConnection.onclose(function(err) {{
if (err) {{
send('Connection closed due to error: ' + err.toString());
}} else {{
send('Connection closed');
}}
attemptReconnect();
}});
startConnection();
";
}
}

34
Build/Docker/amd64 Normal file
View File

@ -0,0 +1,34 @@
FROM debian:12.5-slim
EXPOSE 9118
WORKDIR /home
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl unzip sed chromium xvfb libnspr4 fontconfig \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN curl -fSL -k -o dotnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.12/aspnetcore-runtime-9.0.12-linux-x64.tar.gz \
&& mkdir -p /usr/share/dotnet \
&& tar -oxzf dotnet.tar.gz -C /usr/share/dotnet \
&& rm dotnet.tar.gz
RUN curl -L -k -o publish.zip https://github.com/immisterio/Lampac/releases/latest/download/publish.zip \
&& unzip -o publish.zip && rm -f publish.zip && rm -rf merchant \
&& rm -rf runtimes/os* && rm -rf runtimes/win* && rm -rf runtimes/linux-arm runtimes/linux-arm64 runtimes/linux-musl-arm64 runtimes/linux-musl-x64 \
&& touch isdocker
RUN curl -k -s https://raw.githubusercontent.com/immisterio/Lampac/main/Build/Docker/update.sh | bash
RUN mkdir -p torrserver && curl -L -k -o torrserver/TorrServer-linux https://github.com/YouROK/TorrServer/releases/latest/download/TorrServer-linux-amd64 \
&& chmod +x torrserver/TorrServer-linux
RUN mkdir -p .playwright/node/linux-x64 && curl -L -k -o .playwright/node/linux-x64/node https://github.com/immisterio/playwright/releases/download/chrome/node-linux-x64 \
&& chmod +x .playwright/node/linux-x64/node && touch .playwright/node/linux-x64/node.ok
RUN curl -L -k -o ffmpeg.zip https://github.com/immisterio/ffmpeg/releases/download/ffmpeg2/ffmpeg-master-latest-linux64-gpl.zip \
&& unzip -o ffmpeg.zip && rm -f ffmpeg.zip \
&& mv ffprobe data/ffprobe && chmod +x data/ffprobe \
&& mv ffmpeg data/ffmpeg && chmod +x data/ffmpeg
RUN echo '{"chromium":{"executablePath":"/usr/bin/chromium"}}' > init.conf
ENTRYPOINT ["/usr/share/dotnet/dotnet", "Lampac.dll"]

35
Build/Docker/arm32 Normal file
View File

@ -0,0 +1,35 @@
FROM arm32v7/debian:12.5-slim
EXPOSE 9118
WORKDIR /home
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl unzip sed chromium xvfb libnspr4 \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN curl -L -k -o ffprobe.zip https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v6.1/ffprobe-6.1-linux-armhf-32.zip \
&& unzip -o ffprobe.zip && rm -f ffprobe.zip \
&& mv ffprobe /usr/bin/ffprobe && chmod +x /usr/bin/ffprobe
RUN curl -fSL -k -o dotnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.12/aspnetcore-runtime-9.0.12-linux-arm.tar.gz \
&& mkdir -p /usr/share/dotnet \
&& tar -oxzf dotnet.tar.gz -C /usr/share/dotnet \
&& rm dotnet.tar.gz
RUN curl -L -k -o publish.zip https://github.com/immisterio/Lampac/releases/latest/download/publish.zip \
&& unzip -o publish.zip && rm -f publish.zip && rm -rf merchant \
&& rm -rf runtimes/os* && rm -rf runtimes/win* && rm -rf runtimes/linux-arm64 runtimes/linux-musl-arm64 runtimes/linux-musl-x64 runtimes/linux-x64 \
&& touch isdocker
RUN curl -k -s https://raw.githubusercontent.com/immisterio/Lampac/main/Build/Docker/update.sh | bash
RUN mkdir -p torrserver && curl -L -k -o torrserver/TorrServer-linux https://github.com/YouROK/TorrServer/releases/latest/download/TorrServer-linux-arm7 \
&& chmod +x torrserver/TorrServer-linux
RUN mkdir -p .playwright/node/linux-arm && curl -L -k -o .playwright/node/linux-arm/node https://github.com/immisterio/playwright/releases/download/chrome/node-linux-armv7l \
&& chmod +x .playwright/node/linux-arm/node && touch .playwright/node/linux-arm/node.ok
RUN echo '{"chromium":{"executablePath":"/usr/bin/chromium"},"typecache":"mem","isarm":true,"mikrotik":true,"GC":{"enable":true,"Concurrent":false,"ConserveMemory":9,"HighMemoryPercent":1,"RetainVM":false},"WAF":{"enable":false,"bypassLocalIP":true,"allowExternalIpAccess":true,"bruteForceProtection":false},"serverproxy":{"verifyip":false,"image":{"cache": false,"cache_rsize":false}}}' > init.conf
RUN echo '{"runtimeOptions":{"tfm":"net9.0","frameworks":[{"name":"Microsoft.NETCore.App","version":"9.0.0"},{"name":"Microsoft.AspNetCore.App","version":"9.0.0"}],"configProperties":{"System.GC.Server":false,"System.Reflection.Metadata.MetadataUpdater.IsSupported":false,"System.Reflection.NullabilityInfoContext.IsSupported":true,"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization":false}}}' > Lampac.runtimeconfig.json
ENTRYPOINT ["/usr/share/dotnet/dotnet", "Lampac.dll"]

36
Build/Docker/arm64 Normal file
View File

@ -0,0 +1,36 @@
FROM arm64v8/debian:12.5-slim
EXPOSE 9118
WORKDIR /home
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl unzip sed chromium xvfb libnspr4 fontconfig \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN curl -fSL -k -o dotnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.12/aspnetcore-runtime-9.0.12-linux-arm64.tar.gz \
&& mkdir -p /usr/share/dotnet \
&& tar -oxzf dotnet.tar.gz -C /usr/share/dotnet \
&& rm dotnet.tar.gz
RUN curl -L -k -o publish.zip https://github.com/immisterio/Lampac/releases/latest/download/publish.zip \
&& unzip -o publish.zip && rm -f publish.zip && rm -rf merchant \
&& rm -rf runtimes/os* && rm -rf runtimes/win* && rm -rf runtimes/linux-arm runtimes/linux-musl-arm64 runtimes/linux-musl-x64 runtimes/linux-x64 \
&& touch isdocker
RUN curl -k -s https://raw.githubusercontent.com/immisterio/Lampac/main/Build/Docker/update.sh | bash
RUN mkdir -p torrserver && curl -L -k -o torrserver/TorrServer-linux https://github.com/YouROK/TorrServer/releases/latest/download/TorrServer-linux-arm64 \
&& chmod +x torrserver/TorrServer-linux
RUN mkdir -p .playwright/node/linux-arm64 && curl -L -k -o .playwright/node/linux-arm64/node https://github.com/immisterio/playwright/releases/download/chrome/node-linux-arm64 \
&& chmod +x .playwright/node/linux-arm64/node && touch .playwright/node/linux-arm64/node.ok
RUN curl -L -k -o ffmpeg.zip https://github.com/immisterio/ffmpeg/releases/download/ffmpeg2/ffmpeg-master-latest-linuxarm64-gpl.zip \
&& unzip -o ffmpeg.zip && rm -f ffmpeg.zip \
&& mv ffprobe data/ffprobe && chmod +x data/ffprobe \
&& mv ffmpeg data/ffmpeg && chmod +x data/ffmpeg
RUN echo '{"chromium":{"executablePath":"/usr/bin/chromium"},"typecache":"mem","isarm":true,"mikrotik":true,"GC":{"enable":true,"Concurrent":false,"ConserveMemory":9,"HighMemoryPercent":1,"RetainVM":false},"WAF":{"enable":false,"bypassLocalIP":true,"allowExternalIpAccess":true,"bruteForceProtection":false},"serverproxy":{"verifyip":false,"image":{"cache": false,"cache_rsize":false}}}' > init.conf
RUN echo '{"runtimeOptions":{"tfm":"net9.0","frameworks":[{"name":"Microsoft.NETCore.App","version":"9.0.0"},{"name":"Microsoft.AspNetCore.App","version":"9.0.0"}],"configProperties":{"System.GC.Server":false,"System.Reflection.Metadata.MetadataUpdater.IsSupported":false,"System.Reflection.NullabilityInfoContext.IsSupported":true,"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization":false}}}' > Lampac.runtimeconfig.json
ENTRYPOINT ["/usr/share/dotnet/dotnet", "Lampac.dll"]

30
Build/Docker/koyeb Normal file
View File

@ -0,0 +1,30 @@
FROM debian:12.5-slim
EXPOSE 8000
WORKDIR /home
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl unzip libicu-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN curl -fSL -k -o dotnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.12/aspnetcore-runtime-9.0.12-linux-x64.tar.gz \
&& mkdir -p /usr/share/dotnet \
&& tar -oxzf dotnet.tar.gz -C /usr/share/dotnet \
&& rm dotnet.tar.gz
RUN curl -L -k -o publish.zip https://github.com/immisterio/Lampac/releases/latest/download/publish.zip \
&& unzip -o publish.zip && rm -f publish.zip && rm -rf merchant \
&& rm -rf runtimes/os* && rm -rf runtimes/win* && rm -rf runtimes/linux-arm runtimes/linux-arm64 runtimes/linux-musl-arm64 runtimes/linux-musl-x64 \
&& touch isdocker
RUN curl -k -s https://raw.githubusercontent.com/immisterio/Lampac/main/Build/Docker/update.sh | bash
RUN echo '{"listen":{"port":8000,"scheme":"https","frontend":"cloudflare"},"KnownProxies":[{"ip":"0.0.0.0","prefixLength":0}],"mikrotik":true,"typecache":"mem","GC":{"enable":true,"Concurrent":false,"ConserveMemory":9,"HighMemoryPercent":1,"RetainVM":false},"WAF":{"enable":false,"bypassLocalIP":true,"allowExternalIpAccess":true,"bruteForceProtection":false},"watcherInit":"cron","pirate_store":false,"rch":{"keepalive":900},"weblog":{"enable":true},"chromium":{"enable":false},"firefox":{"enable":false},"LampaWeb":{"autoupdate":false,"initPlugins":{"timecode":false,"backup":false,"sync":false}},"cub":{"enable":true,"geo":["RU"]},"tmdb":{"enable":true},"serverproxy":{"verifyip":false,"buffering":{"enable":false},"image":{"cache":false,"cache_rsize":false}},"online":{"checkOnlineSearch":false},"sisi":{"push_all":false,"rsize_disable":["BongaCams","Chaturbate","Runetki","PornHub","Eporner","HQporner","Spankbang","Porntrex","Xnxx","Xvideos","Xhamster","Tizam"],"proxyimg_disable":["Ebalovo"]},"Mirage":{"displayindex":1},"Ashdi":{"rhub":true},"Kinoukr":{"rhub":true},"VDBmovies":{"rhub":true},"VideoDB":{"rhub":true},"FanCDN":{"rhub":true},"Rezka":{"rhub":true,"scheme":"https"},"Kinotochka":{"rhub":true,"rhub_streamproxy":true,"streamproxy":false,"geostreamproxy":null,"rhub_geo_disable":["RU"]},"Videoseed":{"streamproxy":false,"geostreamproxy":null},"Vibix":{"streamproxy":false,"geostreamproxy":null},"iRemux":{"streamproxy":false,"geostreamproxy":null},"Rgshows":{"streamproxy":false,"geostreamproxy":null},"Autoembed":{"enable":false},"Animevost":{"rhub":true},"AnilibriaOnline":{"rhub":true},"Ebalovo":{"rhub":true},"Spankbang":{"rhub":true,"rhub_geo_disable":["RU"]},"BongaCams":{"rhub":true},"Chaturbate":{"rhub":true,"rhub_geo_disable":["RU"]},"Runetki":{"rhub":true},"HQporner":{"rhub":true,"streamproxy":false,"geostreamproxy":null,"qualitys_proxy":false,"geo_hide":["RU"]},"Eporner":{"streamproxy":false,"geostreamproxy":null,"qualitys_proxy":false,"rhub_geo_disable":["RU"]},"Porntrex":{"rhub":true,"streamproxy":false,"geostreamproxy":null,"qualitys_proxy":false,"rhub_geo_disable":["RU"]},"Xhamster":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true,"rhub_geo_disable":["RU"]},"Xnxx":{"rhub":true,"rhub_fallback":true,"rhub_streamproxy":true,"rhub_geo_disable":["RU"]},"Tizam":{"rhub":true,"rhub_fallback":true,"streamproxy":false,"geostreamproxy":null,"qualitys_proxy":false},"Xvideos":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true,"rhub_geo_disable":["RU"]},"PornHub":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"RutubeMovie":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true,"rhub_geo_disable":["UA"]},"VkMovie":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"Plvideo":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true,"rhub_geo_disable":["UA"]},"CDNvideohub":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"Redheadsound":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"CDNmovies":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"AniMedia":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"Animebesst":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true}}' > /home/init.conf
RUN echo '"typesearch":"webapi","merge":null' > /home/module/JacRed.conf
RUN echo '[{"enable":true,"dll":"SISI.dll"},{"enable":true,"dll":"Online.dll"},{"enable":true,"initspace":"Catalog.ModInit","dll":"Catalog.dll"},{"enable":true,"initspace":"TorrServer.ModInit","dll":"TorrServer.dll"},{"enable":true,"initspace":"Jackett.ModInit","dll":"JacRed.dll"}]' > /home/module/manifest.json
RUN mkdir -p torrserver && curl -L -k -o torrserver/TorrServer-linux https://github.com/YouROK/TorrServer/releases/latest/download/TorrServer-linux-amd64 \
&& chmod +x torrserver/TorrServer-linux
ENTRYPOINT ["/usr/share/dotnet/dotnet", "Lampac.dll"]

25
Build/Docker/mircloud Normal file
View File

@ -0,0 +1,25 @@
FROM debian:12.5-slim
EXPOSE 80
WORKDIR /home
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl sed unzip \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN curl -fSL -k -o dotnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.12/aspnetcore-runtime-9.0.12-linux-x64.tar.gz \
&& mkdir -p /usr/share/dotnet \
&& tar -oxzf dotnet.tar.gz -C /usr/share/dotnet \
&& rm dotnet.tar.gz
RUN curl -L -k -o publish.zip https://github.com/immisterio/Lampac/releases/latest/download/publish.zip \
&& unzip -o publish.zip && rm -f publish.zip && rm -rf merchant \
&& rm -rf runtimes/os* && rm -rf runtimes/win* && rm -rf runtimes/linux-arm runtimes/linux-arm64 runtimes/linux-musl-arm64 runtimes/linux-musl-x64 \
&& touch isdocker
RUN curl -k -s https://raw.githubusercontent.com/immisterio/Lampac/main/Build/Docker/update.sh | bash
RUN echo '{"listen":{"port":80,"scheme":"https"},"KnownProxies":[{"ip":"0.0.0.0","prefixLength":0}],"rch":{"enable":true},"typecache":"mem","GC":{"enable":true,"Concurrent":false,"ConserveMemory":9,"HighMemoryPercent":1,"RetainVM":false},"mikrotik":true,"serverproxy":{"verifyip":false,"showOrigUri":true,"buffering":{"enable":false}},"pirate_store": false,"dlna":{"enable":false},"chromium":{"enable":false},"online":{"checkOnlineSearch":true},"Rezka":{"host":"https://hdrezka.me","corseu":true,"xrealip":true,"uacdn":"https://prx-ams.ukrtelcdn.net","hls":false},"Zetflix":{"enable":false},"VDBmovies":{"enable":false},"iRemux":{"streamproxy":false,"geostreamproxy":["UA"]},"Kinobase":{"rhub":true},"Eneyida":{"rhub":true},"Kinoukr":{"rhub":true},"Kodik":{"enable":false},"AnimeGo":{"enable": false},"Animebesst":{"enable": false},"Eporner":{"streamproxy":false},"PornHub":{"enable":false},"Ebalovo":{"enable":false}}' > /home/init.conf
RUN echo '[{"enable":true,"dll":"SISI.dll"},{"enable":true,"dll":"Online.dll"},{"enable":true,"initspace":"Jackett.ModInit","dll":"JacRed.dll"}]' > /home/module/manifest.json
ENTRYPOINT ["/usr/share/dotnet/dotnet", "Lampac.dll"]

27
Build/Docker/northflank Normal file
View File

@ -0,0 +1,27 @@
FROM debian:12.5-slim
EXPOSE 80
WORKDIR /home
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl unzip libicu-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN curl -fSL -k -o dotnet.tar.gz https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.12/aspnetcore-runtime-9.0.12-linux-x64.tar.gz \
&& mkdir -p /usr/share/dotnet \
&& tar -oxzf dotnet.tar.gz -C /usr/share/dotnet \
&& rm dotnet.tar.gz
RUN curl -L -k -o publish.zip https://github.com/immisterio/Lampac/releases/latest/download/publish.zip \
&& unzip -o publish.zip && rm -f publish.zip && rm -rf merchant \
&& rm -rf runtimes/os* && rm -rf runtimes/win* && rm -rf runtimes/linux-arm runtimes/linux-arm64 runtimes/linux-musl-arm64 runtimes/linux-musl-x64 \
&& touch isdocker
RUN curl -k -s https://raw.githubusercontent.com/immisterio/Lampac/main/Build/Docker/update.sh | bash
RUN echo '{"listen":{"port":80,"scheme":"https"},"KnownProxies":[{"ip":"0.0.0.0","prefixLength":0}],"mikrotik":true,"typecache":"mem","GC":{"enable":true,"Concurrent":false,"ConserveMemory":9,"HighMemoryPercent":1,"RetainVM":false},"watcherInit":"cron","pirate_store":false,"rch":{"keepalive":900},"weblog":{"enable":true},"chromium":{"enable":false},"firefox":{"enable":false},"LampaWeb":{"autoupdate":false,"initPlugins":{"timecode":false,"backup":false,"sync":false}},"cub":{"enable":true,"geo":["RU"]},"tmdb":{"enable":true},"serverproxy":{"verifyip":false,"buffering":{"enable":false},"image":{"cache":false,"cache_rsize":false}},"online":{"checkOnlineSearch":false},"sisi":{"push_all":false,"rsize_disable":["BongaCams","Chaturbate","Runetki","PornHub","Eporner","HQporner","Spankbang","Porntrex","Xnxx","Xvideos","Xhamster","Tizam"],"proxyimg_disable":["Ebalovo"]},"Mirage":{"displayindex":1},"Ashdi":{"rhub":true},"Kinoukr":{"rhub":true},"VDBmovies":{"rhub":true},"VideoDB":{"rhub":true},"FanCDN":{"rhub":true},"Rezka":{"rhub":true,"scheme":"https"},"Kinotochka":{"rhub":true,"rhub_fallback":true,"rhub_streamproxy":true,"rhub_geo_disable":["RU"]},"Autoembed":{"enable":false},"Kodik":{"overridehost":"https://rc.bwa.to/lite/kodik"},"Animevost":{"rhub":true,"rhub_fallback":true,"rhub_streamproxy":true},"AnilibriaOnline":{"rhub":true},"Ebalovo":{"rhub":true},"Spankbang":{"rhub":true,"rhub_streamproxy":true,"rhub_geo_disable":["RU"]},"BongaCams":{"rhub":true,"rhub_streamproxy":true},"Chaturbate":{"rhub":true,"rhub_streamproxy":true,"rhub_geo_disable":["RU"]},"Runetki":{"rhub":true,"rhub_streamproxy":true},"Xhamster":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true,"rhub_geo_disable":["RU"]},"Xnxx":{"rhub":true,"rhub_fallback":true,"rhub_streamproxy":true,"rhub_geo_disable":["RU"]},"Tizam":{"rhub":true,"rhub_fallback":true,"rhub_streamproxy":true},"Xvideos":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true,"rhub_geo_disable":["RU"]},"PornHub":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"RutubeMovie":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true,"rhub_geo_disable":["UA"]},"VkMovie":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"Plvideo":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true,"rhub_geo_disable":["UA"]},"CDNvideohub":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"Redheadsound":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"CDNmovies":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"AniMedia":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"Animebesst":{"rhub":true,"rhub_streamproxy":true,"rhub_fallback":true},"AnimeGo":{"enable":false}}' > /home/init.conf
RUN echo '"typesearch":"webapi","merge":null' > /home/module/JacRed.conf
RUN echo '[{"enable":true,"dll":"SISI.dll"},{"enable":true,"dll":"Online.dll"},{"enable":true,"initspace":"Catalog.ModInit","dll":"Catalog.dll"},{"enable":true,"initspace":"Jackett.ModInit","dll":"JacRed.dll"}]' > /home/module/manifest.json
ENTRYPOINT ["/usr/share/dotnet/dotnet", "Lampac.dll"]

10
Build/Docker/update.sh Normal file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
ver=$(curl -k -s https://api.github.com/repos/immisterio/Lampac/releases/latest | grep tag_name | sed s/[^0-9]//g)
upver=$(curl -k -s http://noah.lampac.sh/update/$ver.txt)
if [[ ${#upver} -eq 8 ]]; then
curl -L -k -o update.zip http://noah.lampac.sh/update/$upver.zip
unzip -o update.zip
rm -f update.zip
fi

View File

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<UserSecretsId>7a9d4585-3e95-4564-a350-5fe756d1351f</UserSecretsId>
<AssemblyName>Lampac</AssemblyName>
<RootNamespace>Lampac</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Data\**" />
<Content Remove="Data\**" />
<EmbeddedResource Remove="Data\**" />
<None Remove="Data\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<Target Name="MoveDependenciesAfterPublish" AfterTargets="Publish">
<PropertyGroup>
<PublishDir>$(OutputPath)publish\</PublishDir>
</PropertyGroup>
<ItemGroup>
<DependencyDlls Include="$(PublishDir)*.dll" Exclude="$(PublishDir)$(AssemblyName).dll" />
</ItemGroup>
<!-- Создаем папку references -->
<MakeDir Directories="$(PublishDir)runtimes\references" Condition="!Exists('$(PublishDir)references')" />
<!-- Перемещаем зависимости -->
<Move SourceFiles="@(DependencyDlls)" DestinationFiles="@(DependencyDlls->'$(PublishDir)runtimes\references\%(Filename)%(Extension)')" Condition="'@(DependencyDlls)' != ''" />
<!-- Перемещаем только Shared.pdb -->
<Move SourceFiles="$(PublishDir)Shared.pdb" DestinationFiles="$(PublishDir)runtimes\references\Shared.pdb" Condition="Exists('$(PublishDir)Shared.pdb')" />
</Target>
</Project>

View File

@ -0,0 +1,6 @@
curl -sSL https://dot.net/v1/dotnet-install.sh > dotnet-install.sh
chmod +x dotnet-install.sh
./dotnet-install.sh --version 9.0.310 -InstallDir ./dotnet
chmod +x Build/cloudflare/nightlies.sh
./Build/cloudflare/nightlies.sh

View File

@ -0,0 +1,72 @@
mkdir -p lpc/
cat Build/cloudflare/Lampac.csproj > Lampac/Lampac.csproj
# Публикация проекта
./dotnet/dotnet publish Lampac -c Release
# Целевая директория
publish_dir="Lampac/bin/Release/net9.0/publish"
# Удаляем все папки в runtimes кроме references
for dir in "$publish_dir/runtimes"/*/; do
dirname=$(basename "$dir")
if [ "$dirname" != "references" ]; then
rm -rf "$dir"
fi
done
# Перемещаем языковые папки в runtimes/references/
for lang in cs de es fr it ja ko pl pt-BR ru tr zh-Hans zh-Hant; do
if [ -d "$publish_dir/$lang" ]; then
mv "$publish_dir/$lang" "$publish_dir/runtimes/references/"
fi
done
# Копируем всё в lpc/
cp -R "$publish_dir"/* lpc/
# Сборка модулей
mkdir -p lpc/module
./dotnet/dotnet publish DLNA -c Release
cp DLNA/bin/Release/net9.0/publish/DLNA.dll lpc/module/
./dotnet/dotnet publish JacRed -c Release
cp JacRed/bin/Release/net9.0/publish/JacRed.dll lpc/module/
./dotnet/dotnet publish Merchant -c Release
cp Merchant/bin/Release/net9.0/publish/Merchant.dll lpc/module/
./dotnet/dotnet publish Online -c Release
cp Online/bin/Release/net9.0/publish/Online.dll lpc/module/
./dotnet/dotnet publish Catalog -c Release
cp Catalog/bin/Release/net9.0/publish/Catalog.dll lpc/module/
./dotnet/dotnet publish SISI -c Release
cp SISI/bin/Release/net9.0/publish/SISI.dll lpc/module/
./dotnet/dotnet publish TorrServer -c Release
cp TorrServer/bin/Release/net9.0/publish/TorrServer.dll lpc/module/
./dotnet/dotnet publish Tracks -c Release
cp Tracks/bin/Release/net9.0/publish/Tracks.dll lpc/module/
mkdir -p lpc/basemod
cp -R BaseModule/Controllers lpc/basemod/
cd lpc/
rm -f Lampac.runtimeconfig.json
curl -L -k -o cloudflare.zip "https://lampac.sh/update/cloudflare.zip?v=$(date +%s)"
unzip -o cloudflare.zip
rm -f cloudflare.zip
python -m zipfile -c update.zip *
cd ../
mkdir out/
cat Build/cloudflare/nightlies_update.sh > lpc/update.sh
cat lpc/update.sh > out/ver.sh
mv lpc out/

View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
VERSION=$1
CUSTOM_DIR=$2
if [ -n "$CUSTOM_DIR" ]; then
WORK_DIR="$CUSTOM_DIR"
else
WORK_DIR="/home/lampac"
fi
if [ -n "$VERSION" ]; then
UPDATEURI="https://${VERSION}.bwa.pages.dev/lpc/update.zip"
else
UPDATEURI="https://bwa.pages.dev/lpc/update.zip"
fi
cd "$WORK_DIR" || { echo "Failed to change directory to $WORK_DIR. Exiting."; exit 1; }
rm -f update.zip
echo -e "Download $UPDATEURI \n"
if ! curl -L -k -o update.zip "$UPDATEURI"; then
echo -e "\nFailed to download update.zip. Exiting."
exit 1
fi
if ! unzip -t update.zip; then
echo -e "\nFailed to test update.zip. Exiting."
exit 1
fi
systemctl stop lampac
unzip -o update.zip
rm -f update.zip
systemctl start lampac
echo -e "\n\nUpdate completed successfully in directory: $WORK_DIR"

214
Catalog/ApiController.cs Normal file
View File

@ -0,0 +1,214 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Catalog.Controllers
{
public class ApiController : BaseController
{
#region catalog.js
[HttpGet]
[AllowAnonymous]
[Route("catalog.js")]
[Route("catalog/js/{token}")]
public ActionResult CatalogJS(string token)
{
var sb = new StringBuilder(FileCache.ReadAllText("plugins/catalog.js"));
sb.Replace("{localhost}", host)
.Replace("{token}", HttpUtility.UrlEncode(token))
.Replace("catalogs:{}", $"catalogs:{jsonCatalogs()}");
return Content(sb.ToString(), "application/javascript; charset=utf-8");
}
#endregion
[HttpGet]
[Route("catalog")]
public ActionResult Index()
{
return ContentTo(jsonCatalogs());
}
string jsonCatalogs()
{
var result = new JObject();
string dir = Path.Combine(AppContext.BaseDirectory, "catalog", "sites");
if (!Directory.Exists(dir))
return result.ToString(Formatting.None);
#region sites
var sites = new List<(string key, JObject obj, int index)>();
foreach (var file in Directory.GetFiles(dir, "*.yaml"))
{
try
{
var site = Path.GetFileNameWithoutExtension(file);
if (string.IsNullOrEmpty(site))
continue;
var init = ModInit.goInit(site);
if (init == null || !init.enable || init.menu == null || init.hide)
continue;
var siteObj = new JObject();
foreach (var menuItem in init.menu)
{
if (menuItem?.categories == null || menuItem.categories.Count == 0)
continue;
foreach (var cat in menuItem.categories)
{
string catName = cat.Key;
string catCode = cat.Value;
if (!(siteObj[catName] is JObject catObj))
{
catObj = new JObject();
if (init.search != null)
siteObj["search"] = $"/catalog/list?plugin={HttpUtility.UrlEncode(menuItem.catalog ?? site)}";
siteObj["search_lazy"] = init.search_lazy;
if (!string.IsNullOrEmpty(init.catalog_key))
siteObj["catalog_key"] = init.catalog_key;
if (!string.IsNullOrEmpty(menuItem.defaultName))
siteObj["defaultName"] = menuItem.defaultName;
siteObj[catName] = catObj;
}
string baseUrl = $"/catalog/list?plugin={HttpUtility.UrlEncode(menuItem.catalog ?? site)}&cat={HttpUtility.UrlEncode(catCode)}";
bool addBaseEntry = true;
if (menuItem.format != null)
{
if (!menuItem.format.ContainsKey("-"))
addBaseEntry = false;
}
if (addBaseEntry)
{
if (catObj[catName] == null)
catObj[catName] = baseUrl;
}
if (menuItem.sort != null)
{
foreach (var s in menuItem.sort)
{
string sortName = s.Key;
string sortCode = s.Value;
if (string.IsNullOrEmpty(sortName) || string.IsNullOrEmpty(sortCode))
continue;
string sortUrl = baseUrl + "&sort=" + HttpUtility.UrlEncode(sortCode);
if (catObj[sortName] == null)
catObj[sortName] = sortUrl;
}
}
}
}
string siteKey = !string.IsNullOrEmpty(init.plugin) ? init.plugin : init.displayname ?? site;
int idx = init.displayindex;
if (idx == 0)
idx = int.MaxValue - sites.Count;
sites.Add((siteKey, siteObj, idx));
}
catch { }
}
#endregion
#region result
foreach (var s in sites.OrderBy(x => x.index))
{
result[s.key] = new JObject();
if (s.obj.ContainsKey("search"))
result[s.key]["search"] = s.obj["search"];
result[s.key]["search_lazy"] = s.obj["search_lazy"];
string catalog_key = s.obj.ContainsKey("catalog_key") ? s.obj["catalog_key"]?.ToString() : null;
string defaultName = s.obj.ContainsKey("defaultName") ? s.obj["defaultName"]?.ToString() : null;
var menu = new JObject();
var main = new JObject();
foreach (var prop in s.obj.Properties())
{
if (!(prop.Value is JObject catObj))
continue;
foreach (var inner in catObj.Properties())
{
string pname = prop.Name;
if (pname.StartsWith("["))
pname = prop.Name.Split(']')[1].Trim();
if (pname != inner.Name)
main[$"{pname} • {inner.Name.ToLower()}"] = inner.Value;
else
main[pname] = inner.Value;
if (!menu.ContainsKey(pname) || (catalog_key != null && catalog_key == inner.Name))
menu[pname] = inner.Value;
}
var categoryMap = new Dictionary<string, string>
{
{ "Фильмы", "movie" },
{ "Сериалы", "tv" },
{ "Мультфильмы", "cartoons" },
{ "Аниме", "anime" },
{ "Релизы", "relise" }
};
string targetCat, targetName = null;
if (categoryMap.TryGetValue(prop.Name, out targetCat))
targetName = prop.Name;
if (prop.Name.StartsWith("["))
{
targetCat = prop.Name.Split(']')[0].Trim('[');
targetName = prop.Name.Split(']')[1];
}
if (!string.IsNullOrEmpty(targetName) && !string.IsNullOrEmpty(targetCat))
{
var targetObj = new JObject();
foreach (var inner in catObj.Properties())
{
if (targetName.Trim() != inner.Name)
targetObj[inner.Name] = inner.Value;
else
targetObj[defaultName ?? inner.Name] = inner.Value;
}
if (targetObj.HasValues)
result[s.key][targetCat.Trim()] = targetObj;
}
}
if (menu.HasValues)
result[s.key]["menu"] = menu;
if (main.HasValues)
result[s.key]["main"] = main;
}
#endregion
return result.ToString(Formatting.None);
}
}
}

397
Catalog/CardController.cs Normal file
View File

@ -0,0 +1,397 @@
using Microsoft.AspNetCore.Mvc;
using Shared.Engine.Utilities;
using Shared.PlaywrightCore;
using System.Net.Http;
namespace Catalog.Controllers
{
public class CardController : BaseController
{
[HttpGet]
[Route("catalog/card")]
public async Task<ActionResult> Index(string plugin, string uri, string type)
{
var init = ModInit.goInit(plugin)?.Clone();
if (init == null || !init.enable)
return BadRequest("init not found");
var rch = new RchClient(HttpContext, host, init, requestInfo);
if (rch.IsNotConnected())
rch.Disabled();
var proxyManager = new ProxyManager(init, rch);
var proxy = proxyManager.BaseGet();
string memKey = $"catalog:card:{plugin}:{uri}";
return await InvkSemaphore(memKey, rch, async () =>
{
if (!hybridCache.TryGetValue(memKey, out JObject jo, inmemory: false))
{
string url = $"{init.host}/{uri}";
var headers = httpHeaders(init);
if (init.args != null)
url = url.Contains("?") ? $"{url}&{init.args}" : $"{url}?{init.args}";
if (init.card_parse.initUrl != null)
url = CSharpEval.Execute<string>(init.card_parse.initUrl, new CatalogInitUrlCard(init.host, init.args, uri, HttpContext.Request.Query, type));
if (init.card_parse.initHeader != null)
headers = CSharpEval.Execute<List<HeadersModel>>(init.card_parse.initHeader, new CatalogInitHeader(url, headers));
reset:
string html = null;
if (!string.IsNullOrEmpty(init.card_parse.postData))
{
string mediaType = init.card_parse.postData.StartsWith("{") || init.card_parse.postData.StartsWith("[") ? "application/json" : "application/x-www-form-urlencoded";
var httpdata = new StringContent(init.card_parse.postData, Encoding.UTF8, mediaType);
html = rch.enable
? await rch.Post(url, init.card_parse.postData, headers, useDefaultHeaders: init.useDefaultHeaders)
: await Http.Post(url, httpdata, headers: headers, proxy: proxy.proxy, timeoutSeconds: init.timeout, httpversion: init.httpversion, useDefaultHeaders: init.useDefaultHeaders);
}
else
{
html = rch.enable
? await rch.Get(url, headers, useDefaultHeaders: init.useDefaultHeaders)
: init.priorityBrowser == "playwright" ? await PlaywrightBrowser.Get(init, url, headers, proxy.data, cookies: init.cookies)
: await Http.Get(url, headers: headers, proxy: proxy.proxy, timeoutSeconds: init.timeout, httpversion: init.httpversion, useDefaultHeaders: init.useDefaultHeaders);
}
if (html == null)
{
if (ModInit.IsRhubFallback(init))
goto reset;
proxyManager.Refresh();
return BadRequest("html");
}
proxyManager.Success();
var parse = init.card_parse;
bool? jsonPath = parse.jsonPath;
if (jsonPath == null)
jsonPath = init.jsonPath;
#region parse doc/json
HtmlNode node = null;
JToken json = null;
if (jsonPath == true)
{
try
{
json = JToken.Parse(html);
if (!string.IsNullOrEmpty(parse.node))
{
json = json.SelectToken(parse.node);
if (json == null)
return BadRequest("parse.node");
}
}
catch
{
json = null;
return BadRequest("json");
}
}
else
{
var doc = new HtmlDocument();
doc.LoadHtml(html);
node = doc.DocumentNode;
}
#endregion
#region name / original_name / year
string name;
string original_name;
string year;
if (jsonPath == true)
{
name = ModInit.nodeValue(json, parse.name, host)?.ToString();
original_name = ModInit.nodeValue(json, parse.original_name, host)?.ToString();
year = ModInit.nodeValue(json, parse.year, host)?.ToString();
}
else
{
name = ModInit.nodeValue(node, parse.name, host)?.ToString();
original_name = ModInit.nodeValue(node, parse.original_name, host)?.ToString();
year = ModInit.nodeValue(node, parse.year, host)?.ToString();
}
#endregion
#region img
string img = jsonPath == true
? ModInit.nodeValue(json, parse.image, host)?.ToString()
: ModInit.nodeValue(node, parse.image, host)?.ToString();
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
jo = new JObject()
{
["id"] = uri.Trim(),
["img"] = PosterApi.Size(host, img),
["vote_average"] = 0,
["genres"] = new JArray(),
["production_countries"] = new JArray(),
["production_companies"] = new JArray()
};
string overview = jsonPath == true
? ModInit.nodeValue(json, parse.description, host)?.ToString()
: ModInit.nodeValue(node, parse.description, host)?.ToString();
if (!string.IsNullOrEmpty(overview))
jo["overview"] = overview;
if (type == "tv")
{
jo["first_air_date"] = year;
jo["name"] = name;
if (!string.IsNullOrEmpty(original_name))
jo["original_name"] = original_name;
}
else
{
jo["release_date"] = year;
jo["title"] = name;
if (!string.IsNullOrEmpty(original_name))
jo["original_title"] = original_name;
}
#region card_args
if (init.card_args != null)
{
foreach (var arg in init.card_args)
{
object val = jsonPath == true
? ModInit.nodeValue(json, arg, host)
: ModInit.nodeValue(node, arg, host);
ModInit.setArgsValue(arg, val, jo);
}
}
#endregion
if (init.tmdb_injects != null && init.tmdb_injects.Length > 0)
await Injects(year, jo, init.tmdb_injects);
if (!jo.ContainsKey("tagline") && !string.IsNullOrEmpty(original_name))
jo["tagline"] = original_name;
hybridCache.Set(memKey, jo, cacheTimeBase(init.cache_time, init: init), inmemory: false);
}
return ContentTo(JsonConvertPool.SerializeObject(jo));
});
}
#region TMDB Injects
static readonly string[] defaultInjectskeys =
[
"imdb_id",
"external_ids",
"backdrop_path",
"created_by",
"genres",
"production_companies",
"production_countries",
"content_ratings",
"episode_run_time",
"languages",
"number_of_episodes",
"number_of_seasons",
"last_episode_to_air",
"origin_country",
"original_language",
"status",
"networks",
"seasons",
"type",
"budget",
"spoken_languages",
"alternative_titles",
"keywords",
// &append_to_response=
"videos",
"credits",
"recommendations",
"similar",
];
static readonly string[] addEmptykeys =
[
"tagline",
"overview",
"first_air_date",
"last_air_date",
"release_date",
"runtime"
];
async Task Injects(string year, JObject jo, string[] keys)
{
if (!jo.ContainsKey("imdb_id") && !jo.ContainsKey("original_title") && !jo.ContainsKey("original_name"))
return;
if (keys.Length == 1 && keys[0] == "default")
keys = defaultInjectskeys;
#region Поиск карточки в TMDB
string imdbId = null;
if (jo.ContainsKey("imdb_id"))
imdbId = jo["imdb_id"]?.ToString();
var header = HeadersModel.Init(("localrequest", AppInit.rootPasswd));
long id = 0;
string cat = string.Empty;
if (!string.IsNullOrWhiteSpace(imdbId) && imdbId.StartsWith("tt"))
{
var find = await Http.Get<JObject>($"http://{AppInit.conf.listen.localhost}:{AppInit.conf.listen.port}/tmdb/api/3/find/{imdbId}?external_source=imdb_id&api_key={AppInit.conf.tmdb.api_key}", timeoutSeconds: 5, headers: header);
if (find != null)
{
foreach (string key in new string[] { "movie_results", "tv_results" })
{
if (find.ContainsKey(key))
{
var movies = find[key] as JArray;
if (movies != null && movies.Count > 0)
{
id = movies[0].Value<long>("id");
cat = key == "movie_results" ? "movie" : "tv";
break;
}
}
}
}
}
else if (jo.ContainsKey("original_title") || jo.ContainsKey("original_name"))
{
string type = jo.ContainsKey("original_title") ? "movie" : "tv";
string originalTitle = jo.Value<string>(type == "movie" ? "original_title" : "original_name");
if (!string.IsNullOrEmpty(originalTitle) && int.TryParse(year.Split("-")[0], out int _year) && _year > 0)
{
var searchMovie = await Http.Get<JObject>($"http://{AppInit.conf.listen.localhost}:{AppInit.conf.listen.port}/tmdb/api/3/search/{type}?query={HttpUtility.UrlEncode(originalTitle)}&api_key={AppInit.conf.tmdb.api_key}", timeoutSeconds: 5, headers: header);
if (searchMovie != null && searchMovie.ContainsKey("results"))
{
var results = searchMovie["results"] as JArray;
if (results != null && results.Count > 0)
{
long foundId = 0;
for (int i = 0; i < results.Count; i++)
{
var item = results[i] as JObject;
if (item == null)
continue;
string date = item.Value<string>("release_date") ?? item.Value<string>("first_air_date");
if (string.IsNullOrEmpty(date))
continue;
// date is usually in format YYYY-MM-DD, take first 4 chars
string yearStr = date.Length >= 4 ? date.Substring(0, 4) : date;
if (int.TryParse(yearStr, out int itemYear) && itemYear == _year)
{
string _s1 = StringConvert.SearchName(originalTitle);
string _s2 = StringConvert.SearchName(item.Value<string>(type == "movie" ? "original_title" : "original_name"));
if (!string.IsNullOrEmpty(_s1) && !string.IsNullOrEmpty(_s2) && _s1 == _s2)
{
foundId = item.Value<long>("id");
break;
}
}
}
if (foundId != 0)
{
id = foundId;
cat = type;
}
}
}
}
}
#endregion
if (id == 0)
return;
string append = "content_ratings,release_dates,external_ids,keywords,alternative_titles,videos,credits,recommendations,similar";
var result = await Http.Get<JObject>($"http://{AppInit.conf.listen.localhost}:{AppInit.conf.listen.port}/tmdb/api/3/{cat}/{id}?api_key={AppInit.conf.tmdb.api_key}&append_to_response={append}&language=ru", timeoutSeconds: 5, headers: header);
if (result == null)
return;
foreach (string key in keys)
{
if (key is "videos" or "recommendations" or "similar")
{
if (result.ContainsKey(key) && result[key] is JObject _jo && _jo.ContainsKey("results"))
jo[key] = _jo["results"];
}
else if (result.ContainsKey(key))
{
jo[key] = result[key];
}
}
if (result.ContainsKey("id"))
jo["tmdb_id"] = result["id"];
if (!jo.ContainsKey("imdb_id") && result.ContainsKey("external_ids") && result["external_ids"] is JObject extIds && extIds.ContainsKey("imdb_id"))
jo["imdb_id"] = extIds["imdb_id"];
foreach (string key in addEmptykeys)
{
if (!jo.ContainsKey(key) && result.ContainsKey(key))
{
var tok = result[key];
if (tok == null)
continue;
if (tok.Type == JTokenType.String)
{
var str = tok.Value<string>();
if (!string.IsNullOrWhiteSpace(str))
jo[key] = str;
}
else
{
jo[key] = tok;
}
}
}
}
#endregion
}
}

13
Catalog/Catalog.csproj Normal file
View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

18
Catalog/GlobalUsings.cs Normal file
View File

@ -0,0 +1,18 @@
global using System;
global using System.Threading.Tasks;
global using System.Collections.Generic;
global using System.Text.RegularExpressions;
global using System.IO;
global using System.Linq;
global using Shared;
global using Shared.Models;
global using Shared.Engine;
global using Shared.Models.Base;
global using Shared.Models.Catalog;
global using Shared.Models.CSharpGlobals;
global using HtmlAgilityPack;
global using Newtonsoft.Json;
global using Newtonsoft.Json.Linq;
global using Microsoft.CodeAnalysis.Scripting;
global using System.Text;
global using System.Web;

547
Catalog/ListController.cs Normal file
View File

@ -0,0 +1,547 @@
using Microsoft.AspNetCore.Mvc;
using Shared.PlaywrightCore;
using System.Net.Http;
namespace Catalog.Controllers
{
public class ListController : BaseController
{
[HttpGet]
[Route("catalog/list")]
async public ValueTask<ActionResult> Index(string query, string plugin, string cat, string sort, int page = 1)
{
var init = ModInit.goInit(plugin)?.Clone();
if (init == null || !init.enable)
return BadRequest("init not found");
if (!string.IsNullOrEmpty(query) && string.IsNullOrEmpty(init.search?.uri))
return BadRequest("search disable");
var rch = new RchClient(HttpContext, host, init, requestInfo);
if (rch.IsNotConnected())
rch.Disabled();
var proxyManager = new ProxyManager(init, rch);
var proxy = proxyManager.BaseGet();
string search = query;
string memKey = $"catalog:{plugin}:{search}:{sort}:{cat}:{page}";
return await InvkSemaphore(memKey, rch, async () =>
{
if (!hybridCache.TryGetValue(memKey, out (List<PlaylistItem> playlists, int total_pages) cache, inmemory: false))
{
#region contentParse
var contentParse = init.list?.contentParse ?? init.content;
if (!string.IsNullOrEmpty(search) && init.search?.contentParse != null)
contentParse = init.search.contentParse;
#endregion
#region html
var headers = httpHeaders(init);
var parse = init.list;
string url = $"{init.host}/{(page == 1 && init.list?.firstpage != null ? init.list?.firstpage : init.list?.uri)}";
string data = init.list?.postData;
if (!string.IsNullOrEmpty(search))
{
string uri = page == 1 && init.search?.firstpage != null ? init.search.firstpage : init.search?.uri;
url = $"{init.host}/{uri}".Replace("{search}", HttpUtility.UrlEncode(search));
data = init.search?.postData?.Replace("{search}", HttpUtility.UrlEncode(search));
parse = init.search;
}
else if (!string.IsNullOrEmpty(cat))
{
var menu = init.menu.FirstOrDefault(i => i.categories.Values.Contains(cat));
if (menu == null)
return BadRequest("menu");
string getFormat(string key)
{
if (menu.format.TryGetValue(key, out string _f))
return _f;
return string.Empty;
}
string eval = (cat != null && sort != null) ? getFormat("sort") : getFormat("-");
if (!string.IsNullOrEmpty(eval))
{
if (!eval.Contains("$\"") && eval.Contains("{") && eval.Contains("}"))
eval = $"return $\"{eval}\";";
url = CSharpEval.BaseExecute<string>(eval, new CatalogGlobalsMenuRoute(init.host, plugin, init.args, url, search, cat, sort, HttpContext.Request.Query, page));
}
if (!url.StartsWith("http"))
url = $"{init.host}/{url}";
}
if (init.args != null)
url = url.Contains("?") ? $"{url}&{init.args}" : $"{url}?{init.args}";
if (parse?.initUrl != null)
url = CSharpEval.Execute<string>(parse.initUrl, new CatalogGlobalsMenuRoute(init.host, plugin, init.args, url, search, cat, sort, HttpContext.Request.Query, page));
if (parse?.initHeader != null)
headers = CSharpEval.Execute<List<HeadersModel>>(parse.initHeader, new CatalogInitHeader(url, headers));
reset:
string html = null;
if (!string.IsNullOrEmpty(data))
{
string mediaType = data.StartsWith("{") || data.StartsWith("[") ? "application/json" : "application/x-www-form-urlencoded";
var httpdata = new StringContent(data, Encoding.UTF8, mediaType);
html = rch.enable
? await rch.Post(url.Replace("{page}", page.ToString()), data, headers, useDefaultHeaders: init.useDefaultHeaders)
: await Http.Post(url.Replace("{page}", page.ToString()), httpdata, headers: headers, proxy: proxy.proxy, timeoutSeconds: init.timeout, httpversion: init.httpversion, useDefaultHeaders: init.useDefaultHeaders);
}
else
{
html = rch.enable
? await rch.Get(url.Replace("{page}", page.ToString()), headers, useDefaultHeaders: init.useDefaultHeaders)
: init.priorityBrowser == "playwright" ? await PlaywrightBrowser.Get(init, url.Replace("{page}", page.ToString()), headers, proxy.data, cookies: init.cookies)
: await Http.Get(url.Replace("{page}", page.ToString()), headers: headers, proxy: proxy.proxy, timeoutSeconds: init.timeout, httpversion: init.httpversion, useDefaultHeaders: init.useDefaultHeaders);
}
#endregion
bool? jsonPath = contentParse.jsonPath;
if (jsonPath == null)
jsonPath = init.jsonPath;
#region parse doc/json
HtmlDocument doc = null;
JToken json = null;
if (jsonPath == true)
{
try
{
json = JToken.Parse(html);
}
catch
{
json = null;
}
}
else
{
doc = new HtmlDocument();
if (html != null)
doc.LoadHtml(html);
}
#endregion
cache.playlists = jsonPath == true
? goPlaylistJson(cat, json, requestInfo, host, contentParse, init, html, plugin)
: goPlaylist(cat, doc, requestInfo, host, contentParse, init, html, plugin);
if (cache.playlists == null || cache.playlists.Count == 0)
{
if (ModInit.IsRhubFallback(init))
goto reset;
proxyManager.Refresh();
return BadRequest("playlists");
}
if (contentParse.total_pages != null)
{
string _p = jsonPath == true
? ModInit.nodeValue(json, contentParse.total_pages, host)?.ToString() ?? ""
: ModInit.nodeValue(doc.DocumentNode, contentParse.total_pages, host)?.ToString() ?? "";
if (int.TryParse(_p, out int _pages) && _pages > 0)
cache.total_pages = _pages;
}
proxyManager.Success();
hybridCache.Set(memKey, cache, cacheTimeBase(init.cache_time, init: init), inmemory: false);
}
#region total_pages
int? total_pages = init.list?.total_pages ?? 0;
if (search != null && init.search != null)
total_pages = init.search.total_pages;
if (total_pages == 0)
total_pages = cache.total_pages;
#endregion
#region next_page
bool? next_page = null;
if (search != null)
{
if (init.search != null && init.search.count_page > 0 && cache.playlists.Count >= init.search.count_page)
next_page = true;
}
else
{
if (init.list != null && init.list.count_page > 0 && cache.playlists.Count >= init.list.count_page)
next_page = true;
}
if (next_page == true && total_pages == 0)
total_pages = null;
#endregion
#region results
var results = new JArray();
foreach (var pl in cache.playlists)
{
var jo = new JObject()
{
["id"] = pl.id,
["img"] = pl.img
};
if (pl.is_serial)
{
jo["first_air_date"] = pl.year;
jo["name"] = pl.title;
jo["original_name"] = string.IsNullOrWhiteSpace(pl.original_title) ? pl.title : pl.original_title;
}
else
{
jo["release_date"] = pl.year;
jo["title"] = pl.title;
jo["original_title"] = string.IsNullOrWhiteSpace(pl.original_title) ? pl.title : pl.original_title;
}
if (pl.args != null)
{
foreach (var a in pl.args)
jo[a.Key] = JToken.FromObject(a.Value);
}
results.Add(jo);
}
#endregion
return ContentTo(JsonConvert.SerializeObject(new
{
page,
results,
total_pages,
next_page
}, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore
}));
});
}
#region goPlaylistJson
static List<PlaylistItem> goPlaylistJson(string cat, JToken json, RequestModel requestInfo, string host, ContentParseSettings parse, CatalogSettings init, string html, string plugin)
{
if (parse == null || json == null)
return null;
if (init.debug)
Console.WriteLine(html);
string eval = parse.eval;
if (!string.IsNullOrEmpty(eval) && eval.EndsWith(".cs"))
eval = FileCache.ReadAllText($"catalog/sites/{eval}");
if (string.IsNullOrEmpty(parse.nodes))
{
if (string.IsNullOrEmpty(eval))
return null;
var options = ScriptOptions.Default
.AddReferences(CSharpEval.ReferenceFromFile("Shared.dll"))
.AddImports("Shared")
.AddImports("Shared.Models")
.AddImports("Shared.Engine")
.AddReferences(CSharpEval.ReferenceFromFile("Newtonsoft.Json.dll"))
.AddImports("Newtonsoft.Json")
.AddImports("Newtonsoft.Json.Linq");
return CSharpEval.Execute<List<PlaylistItem>>(eval, new CatalogPlaylistJson(init, plugin, host, html, json, new List<PlaylistItem>()), options);
}
var nodes = json.SelectTokens(parse.nodes)?.ToList();
if (nodes == null || nodes.Count == 0)
return null;
var playlists = new List<PlaylistItem>(nodes.Count);
foreach (var node in nodes)
{
string name = ModInit.nodeValue(node, parse.name, host)?.ToString();
string original_name = ModInit.nodeValue(node, parse.original_name, host)?.ToString();
string href = ModInit.nodeValue(node, parse.href, host)?.ToString();
string img = ModInit.nodeValue(node, parse.image, host)?.ToString();
string year = ModInit.nodeValue(node, parse.year, host)?.ToString();
if (init.debug)
Console.WriteLine($"\n\nname: {name}\noriginal_name: {original_name}\nhref: {href}\nimg: {img}\nyear: {year}\n\n{node.ToString(Formatting.None)}");
if (!string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(href))
{
#region href
if (href.StartsWith("../"))
href = href.Replace("../", "");
else if (href.StartsWith("//"))
href = Regex.Replace(href, "//[^/]+/", "");
else if (href.StartsWith("http"))
href = Regex.Replace(href, "https?://[^/]+/", "");
else if (href.StartsWith("/"))
href = href.Substring(1);
#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;
string clearText(string text)
{
if (string.IsNullOrEmpty(text))
return text;
text = text.Replace("&nbsp;", "");
text = Regex.Replace(text, "<[^>]+>", "");
text = HttpUtility.HtmlDecode(text);
return text.Trim();
}
#region is_serial
bool? is_serial = null;
if (cat != null)
{
if (init.movie_cats != null && init.movie_cats.Contains(cat))
is_serial = false;
else if (init.serial_cats != null && init.serial_cats.Contains(cat))
is_serial = true;
}
if (is_serial == null && parse.serial_regex != null)
is_serial = Regex.IsMatch(node.ToString(Formatting.None), parse.serial_regex, RegexOptions.IgnoreCase);
if (is_serial == null && parse.serial_key != null)
{
if (ModInit.nodeValue(node, parse.serial_key, host) != null)
is_serial = true;
}
#endregion
var pl = new PlaylistItem()
{
id = href,
title = clearText(name),
original_title = clearText(original_name),
img = PosterApi.Size(host, img),
year = clearText(year),
is_serial = is_serial == true
};
if (parse.args != null)
{
foreach (var arg in parse.args)
{
if (pl.args == null)
pl.args = new JObject();
object val = ModInit.nodeValue(node, arg, host);
ModInit.setArgsValue(arg, val, pl.args);
}
}
if (eval != null)
{
var options = ScriptOptions.Default
.AddReferences(CSharpEval.ReferenceFromFile("Shared.dll"))
.AddImports("Shared")
.AddImports("Shared.Models")
.AddImports("Shared.Engine")
.AddReferences(CSharpEval.ReferenceFromFile("Newtonsoft.Json.dll"))
.AddImports("Newtonsoft.Json")
.AddImports("Newtonsoft.Json.Linq");
pl = CSharpEval.Execute<PlaylistItem>(eval, new CatalogChangePlaylisJson(init, plugin, host, html, nodes, pl, node), options);
}
if (pl != null)
playlists.Add(pl);
}
}
return playlists;
}
#endregion
#region goPlaylist
static List<PlaylistItem> goPlaylist(string cat, HtmlDocument doc, RequestModel requestInfo, string host, ContentParseSettings parse, CatalogSettings init, string html, string plugin)
{
if (parse == null || string.IsNullOrEmpty(html))
return null;
if (init.debug)
Console.WriteLine(html);
string eval = parse.eval;
if (!string.IsNullOrEmpty(eval) && eval.EndsWith(".cs"))
eval = FileCache.ReadAllText($"catalog/sites/{eval}");
if (string.IsNullOrEmpty(parse.nodes))
{
if (string.IsNullOrEmpty(eval))
return null;
var options = ScriptOptions.Default
.AddReferences(CSharpEval.ReferenceFromFile("Shared.dll"))
.AddImports("Shared")
.AddImports("Shared.Models")
.AddImports("Shared.Engine")
.AddReferences(CSharpEval.ReferenceFromFile("HtmlAgilityPack.dll"))
.AddImports("HtmlAgilityPack");
return CSharpEval.Execute<List<PlaylistItem>>(eval, new CatalogPlaylist(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 node in nodes)
{
string name = ModInit.nodeValue(node, parse.name, host)?.ToString();
string original_name = ModInit.nodeValue(node, parse.original_name, host)?.ToString();
string href = ModInit.nodeValue(node, parse.href, host)?.ToString();
string img = ModInit.nodeValue(node, parse.image, host)?.ToString();
string year = ModInit.nodeValue(node, parse.year, host)?.ToString();
if (init.debug)
Console.WriteLine($"\n\nname: {name}\noriginal_name: {original_name}\nhref: {href}\nimg: {img}\nyear: {year}\n\n{node.OuterHtml}");
if (!string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(href))
{
#region href
if (href.StartsWith("../"))
href = href.Replace("../", "");
else if (href.StartsWith("//"))
href = Regex.Replace(href, "//[^/]+/", "");
else if (href.StartsWith("http"))
href = Regex.Replace(href, "https?://[^/]+/", "");
else if (href.StartsWith("/"))
href = href.Substring(1);
#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 is_serial
bool? is_serial = null;
if (cat != null)
{
if (init.movie_cats != null && init.movie_cats.Contains(cat))
is_serial = false;
else if (init.serial_cats != null && init.serial_cats.Contains(cat))
is_serial = true;
}
if (is_serial == null && parse.serial_regex != null)
is_serial = Regex.IsMatch(node.OuterHtml, parse.serial_regex, RegexOptions.IgnoreCase);
if (is_serial == null && parse.serial_key != null)
{
if (ModInit.nodeValue(node, parse.serial_key, host) != null)
is_serial = true;
}
#endregion
var pl = new PlaylistItem()
{
id = href,
title = ModInit.clearText(name),
original_title = ModInit.clearText(original_name),
img = PosterApi.Size(host, img),
year = ModInit.clearText(year),
is_serial = is_serial == true
};
if (parse.args != null)
{
foreach (var arg in parse.args)
{
if (pl.args == null)
pl.args = new JObject();
object val = ModInit.nodeValue(node, arg, host);
ModInit.setArgsValue(arg, val, pl.args);
}
}
if (eval != null)
{
var options = ScriptOptions.Default
.AddReferences(CSharpEval.ReferenceFromFile("Shared.dll"))
.AddImports("Shared")
.AddImports("Shared.Models")
.AddImports("Shared.Engine")
.AddReferences(CSharpEval.ReferenceFromFile("HtmlAgilityPack.dll"))
.AddImports("HtmlAgilityPack");
pl = CSharpEval.Execute<PlaylistItem>(eval, new CatalogChangePlaylis(init, plugin, host, html, nodes, pl, node), options);
}
if (pl != null)
playlists.Add(pl);
}
}
return playlists;
}
#endregion
}
}

401
Catalog/ModInit.cs Normal file
View File

@ -0,0 +1,401 @@
using System.Globalization;
using YamlDotNet.Serialization;
namespace Catalog
{
public class ModInit
{
public static void loaded()
{
}
#region goInit
public static CatalogSettings goInit(string site)
{
if (string.IsNullOrEmpty(site))
return null;
site = site.ToLowerAndTrim();
site = Regex.Replace(site, "[^a-z0-9\\-]", "", RegexOptions.IgnoreCase);
var hybridCache = IHybridCache.Get(null);
string memKey = $"catalog:goInit:{site}";
if (!hybridCache.TryGetValue(memKey, out CatalogSettings init))
{
// Если файл не найден по имени, пробуем найти по displayname в *.yaml
if (!File.Exists($"catalog/sites/{site}.yaml"))
{
string found = FindSiteByDisplayName(site);
if (string.IsNullOrEmpty(found))
return null;
site = found;
}
var deserializer = new DeserializerBuilder().Build();
// Чтение основного YAML-файла
string yaml = File.ReadAllText($"catalog/sites/{site}.yaml");
var target = deserializer.Deserialize<Dictionary<object, object>>(yaml);
foreach (string y in new string[] { "_", site })
{
if (File.Exists($"catalog/override/{y}.yaml"))
{
// Чтение пользовательского YAML-файла
string myYaml = File.ReadAllText($"catalog/override/{y}.yaml");
var mySource = deserializer.Deserialize<Dictionary<object, object>>(myYaml);
// Объединение словарей
foreach (var property in mySource)
{
if (!target.ContainsKey(property.Key))
{
target[property.Key] = property.Value;
continue;
}
if (property.Value is IDictionary<object, object> sourceDict &&
target[property.Key] is IDictionary<object, object> targetDict)
{
// Рекурсивное объединение вложенных словарей
foreach (var item in sourceDict)
targetDict[item.Key] = item.Value;
}
else
{
target[property.Key] = property.Value;
}
}
}
}
// Преобразование словаря в объект CatalogSettings
var serializer = new SerializerBuilder().Build();
var yamlResult = serializer.Serialize(target);
init = deserializer.Deserialize<CatalogSettings>(yamlResult);
if (string.IsNullOrEmpty(init.plugin))
init.plugin = init.displayname;
if (!init.debug || !AppInit.conf.multiaccess)
hybridCache.Set(memKey, init, DateTime.Now.AddMinutes(1), inmemory: true);
}
return init;
}
#endregion
#region FindSiteByDisplayName
static string FindSiteByDisplayName(string site)
{
var deserializer = new DeserializerBuilder().Build();
foreach (var folder in new[] { "catalog/sites", "catalog/override" })
{
if (!Directory.Exists(folder))
continue;
foreach (var file in Directory.EnumerateFiles(folder, "*.yaml"))
{
try
{
var yaml = File.ReadAllText(file);
var dict = deserializer.Deserialize<Dictionary<object, object>>(yaml);
if (dict != null && dict.TryGetValue("displayname", out var dnObj) && dnObj != null)
{
var dn = dnObj.ToString().ToLowerAndTrim();
if (dn == site)
return Path.GetFileNameWithoutExtension(file);
}
}
catch { }
}
}
return null;
}
#endregion
#region IsRhubFallback
public static bool IsRhubFallback(BaseSettings init)
{
if (init.rhub && init.rhub_fallback)
{
init.rhub = false;
return true;
}
return false;
}
#endregion
#region nodeValue - HtmlNode
public static object nodeValue(HtmlNode node, SingleNodeSettings nd, string host)
{
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 = node.GetAttributeValue(attr, null);
if (!string.IsNullOrEmpty(attrValue))
{
value = attrValue;
break;
}
}
}
else
{
if ("innerhtml".Equals(nd.attribute, StringComparison.OrdinalIgnoreCase))
value = node.InnerHtml;
else if ("outerhtml".Equals(nd.attribute, StringComparison.OrdinalIgnoreCase))
value = node.OuterHtml;
else
value = node.GetAttributeValue(nd.attribute, null);
}
}
else
{
var inNode = node.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
{
if (!string.IsNullOrEmpty(nd.attribute))
{
if ("innerhtml".Equals(nd.attribute, StringComparison.OrdinalIgnoreCase))
value = inNode.InnerHtml;
else if ("outerhtml".Equals(nd.attribute, StringComparison.OrdinalIgnoreCase))
value = inNode.OuterHtml;
else
value = inNode.GetAttributeValue(nd.attribute, null)?.Trim();
}
else
{
value = inNode.InnerText?.Trim();
}
}
}
}
}
if (string.IsNullOrEmpty(value))
return null;
if (nd.format != null)
{
var options = ScriptOptions.Default
.AddReferences(CSharpEval.ReferenceFromFile("Shared.dll"))
.AddImports("Shared")
.AddImports("Shared.Engine")
.AddImports("Shared.Models")
.AddReferences(CSharpEval.ReferenceFromFile("Newtonsoft.Json.dll"))
.AddImports("Newtonsoft.Json")
.AddImports("Newtonsoft.Json.Linq");
return CSharpEval.Execute<object>(nd.format, new CatalogNodeValue(value, host), options);
}
return value?.Trim();
}
#endregion
#region nodeValue - JToken
public static object nodeValue(JToken node, SingleNodeSettings nd, string host)
{
if (node == null || nd == null)
return null;
var current = node is JProperty property ? property.Value : node;
JToken valueToken = null;
if (!string.IsNullOrEmpty(nd.node))
{
current = current.SelectToken(nd.node);
if (current == null)
return null;
}
if (nd.attributes != null)
{
foreach (var attr in nd.attributes)
{
valueToken = current[attr];
if (valueToken != null)
break;
}
}
if (valueToken == null && !string.IsNullOrEmpty(nd.attribute))
valueToken = current[nd.attribute];
if (valueToken == null)
return null;
string value = valueToken switch
{
JValue jValue => jValue.Value?.ToString(),
JProperty jProp => jProp.Value?.ToString(),
_ => valueToken.ToString(Formatting.None)
};
if (string.IsNullOrEmpty(value))
return null;
if (nd.format != null)
{
var options = ScriptOptions.Default
.AddReferences(CSharpEval.ReferenceFromFile("Shared.dll"))
.AddImports("Shared")
.AddImports("Shared.Engine")
.AddImports("Shared.Models")
.AddReferences(CSharpEval.ReferenceFromFile("Newtonsoft.Json.dll"))
.AddImports("Newtonsoft.Json")
.AddImports("Newtonsoft.Json.Linq");
return CSharpEval.Execute<object>(nd.format, new CatalogNodeValue(value, host), options);
}
if (valueToken is JValue)
return value?.Trim();
return valueToken;
}
#endregion
#region setArgsValue
public static void setArgsValue(SingleNodeSettings arg, object val, JObject jo)
{
if (val != null)
{
if (arg.name_arg is "kp_rating" or "imdb_rating")
{
string rating = val?.ToString()?.Trim();
if (!string.IsNullOrEmpty(rating) && rating != "0" && rating != "0.0" && double.TryParse(rating, NumberStyles.Any, CultureInfo.InvariantCulture, out _))
{
rating = rating.Length > 3 ? rating.Substring(0, 3) : rating;
if (rating.Length == 1)
rating = $"{rating}.0";
jo[arg.name_arg] = JToken.FromObject(rating.Replace(",", "."));
}
}
else if (arg.name_arg is "vote_average")
{
string value = val?.ToString()?.Trim();
if (!string.IsNullOrEmpty(value) && double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out double _v) && _v > 0)
jo[arg.name_arg] = JToken.FromObject(_v);
}
else if (arg.name_arg is "runtime" or "PG")
{
string value = val?.ToString()?.Trim();
if (!string.IsNullOrEmpty(value) && long.TryParse(value, out long _v) && _v > 0)
jo[arg.name_arg] = JToken.FromObject(_v);
}
else if (arg.name_arg is "genres" or "created_by" or "production_countries" or "production_companies" or "networks" or "spoken_languages")
{
if (val is string)
{
string arrayStr = val?.ToString();
var array = new JArray();
if (!string.IsNullOrEmpty(arrayStr))
{
foreach (string str in arrayStr.Split(","))
{
if (string.IsNullOrWhiteSpace(str))
continue;
array.Add(new JObject() { ["name"] = clearText(str) });
}
jo[arg.name_arg] = array;
}
}
else if (IsStringList(val as JToken))
{
var array = new JArray();
foreach (var item in (JArray)val)
array.Add(new JObject() { ["name"] = clearText(item.ToString()) });
jo[arg.name_arg] = array;
}
else if (val is JToken token && token.Type == JTokenType.Array)
{
jo[arg.name_arg] = token;
}
}
else if (val is string && (arg.name_arg is "origin_country" or "languages"))
{
string arrayStr = val?.ToString();
var array = new JArray();
if (!string.IsNullOrEmpty(arrayStr))
{
foreach (string str in arrayStr.Split(","))
{
if (!string.IsNullOrWhiteSpace(str))
array.Add(str.Trim());
}
if (array.Count > 0)
jo[arg.name_arg] = array;
}
}
else
{
jo[arg.name_arg] = JToken.FromObject(val);
}
}
}
#endregion
#region IsStringList
static bool IsStringList(JToken token)
{
if (token?.Type != JTokenType.Array)
return false;
var array = token as JArray;
return array?.All(item => item.Type == JTokenType.String) == true;
}
#endregion
#region clearText
public static string clearText(string text)
{
if (string.IsNullOrEmpty(text))
return text;
text = text.Replace("&nbsp;", "");
text = Regex.Replace(text, "<[^>]+>", "");
text = HttpUtility.HtmlDecode(text);
return text.Trim();
}
#endregion
}
}

1181
DLNA/ApiController.cs Normal file

File diff suppressed because it is too large Load Diff

13
DLNA/DLNA.csproj Normal file
View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

188
DLNA/ModInit.cs Normal file
View File

@ -0,0 +1,188 @@
using DLNA.Controllers;
using Shared;
using Shared.Engine;
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace DLNA
{
public class ModInit
{
public static void loaded()
{
DLNAController.Initialization();
var init = AppInit.conf.dlna;
var cover = init.cover;
Directory.CreateDirectory($"{init.path}/temp/");
ThreadPool.QueueUserWorkItem(async _ =>
{
bool? ffmpegInit = null;
while (true)
{
if (cover.timeout == -666)
await Task.Delay(TimeSpan.FromSeconds(5));
else
await Task.Delay(TimeSpan.FromMinutes(cover.timeout > 0 ? cover.timeout : 1));
if (!init.enable || !cover.enable)
continue;
if (ffmpegInit == null)
{
ffmpegInit = await FFmpeg.InitializationAsync();
if (ffmpegInit == false)
break;
}
try
{
#region path files
foreach (string file in Directory.GetFiles(init.path))
{
if (!Regex.IsMatch(Path.GetExtension(file), cover.extension))
continue;
string name = Path.GetFileName(file);
var fileinfo = new FileInfo(file);
if (fileinfo.Length == 0)
continue;
var time = fileinfo.CreationTime > fileinfo.LastWriteTime ? fileinfo.CreationTime : fileinfo.LastWriteTime;
if (time.AddMinutes(cover.skipModificationTime) > DateTime.Now)
{
log("skip time: " + file);
continue;
}
string thumb = Path.Combine(init.path, "thumbs", $"{CrypTo.md5(name)}.jpg");
if (File.Exists(thumb))
{
log("thumb ok: " + file);
continue;
}
string lockfile = Path.Combine(init.path, "temp", $"{CrypTo.md5(name)}-ffmpeg.lock");
if (File.Exists(lockfile))
{
log("lock: " + file);
continue;
}
File.Create(lockfile);
string coverComand = cover.coverComand.Replace("{file}", file).Replace("{thumb}", thumb);
log("\ncoverComand: " + coverComand);
var ffmpegLog = await FFmpeg.RunAsync(coverComand, priorityClass: cover.priorityClass);
log(ffmpegLog.outputData);
log(ffmpegLog.errorData);
if (cover.preview)
{
string preview = Path.Combine(init.path, "temp", $"{CrypTo.md5(name)}.mp4");
string previewComand = cover.previewComand.Replace("{file}", file).Replace("{preview}", preview);
log("\npreviewComand: " + previewComand);
ffmpegLog = await FFmpeg.RunAsync(previewComand, priorityClass: cover.priorityClass);
log(ffmpegLog.outputData);
log(ffmpegLog.errorData);
}
}
#endregion
#region path directories
foreach (string folder in Directory.GetDirectories(init.path))
{
if (folder.Contains("thumbs") || folder.Contains("tmdb") || folder.Contains("temp"))
continue;
string folder_name = Path.GetFileName(folder);
string folder_thumb = Path.Combine(init.path, "thumbs", $"{CrypTo.md5(folder_name)}.jpg");
if (File.Exists(folder_thumb))
{
log("thumb ok: " + folder);
continue;
}
var files = Directory.GetFiles(folder);
if (files.Length == 0)
continue;
var folderinfo = new DirectoryInfo(folder);
var time = folderinfo.CreationTime > folderinfo.LastWriteTime ? folderinfo.CreationTime : folderinfo.LastWriteTime;
if (time.AddMinutes(cover.skipModificationTime) > DateTime.Now)
{
log("skip time: " + folder);
continue;
}
string lockfile = Path.Combine(init.path, "temp", $"{CrypTo.md5(folder_name)}-ffmpeg.lock");
if (File.Exists(lockfile))
{
log("lock: " + folder);
continue;
}
File.Create(lockfile);
#region постер с превью на папку
{
string coverComand = cover.coverComand.Replace("{file}", files[0]).Replace("{thumb}", folder_thumb);
log("\ncoverComand: " + coverComand);
var ffmpegLog = await FFmpeg.RunAsync(coverComand, priorityClass: cover.priorityClass);
log(ffmpegLog.outputData);
log(ffmpegLog.errorData);
if (cover.preview)
{
string preview = Path.Combine(init.path, "temp", $"{CrypTo.md5(folder_name)}.mp4");
string previewComand = cover.previewComand.Replace("{file}", files[0]).Replace("{preview}", preview);
log("\npreviewComand: " + previewComand);
ffmpegLog = await FFmpeg.RunAsync(previewComand, priorityClass: cover.priorityClass);
log(ffmpegLog.outputData);
log(ffmpegLog.errorData);
}
}
#endregion
#region постеры на файлы внутри папки
foreach (string file in files)
{
string name = $"{Path.GetFileName(folder)}/{Path.GetFileName(file)}";
string thumb = Path.Combine(init.path, "thumbs", $"{CrypTo.md5(name)}.jpg");
string coverComand = cover.coverComand.Replace("{file}", file).Replace("{thumb}", thumb);
log("\ncoverComand: " + coverComand);
var ffmpegLog = await FFmpeg.RunAsync(coverComand, priorityClass: cover.priorityClass);
log(ffmpegLog.outputData);
log(ffmpegLog.errorData);
}
#endregion
}
#endregion
}
catch { }
}
});
}
public static void log(string value)
{
if (AppInit.conf.dlna.cover.consoleLog && !string.IsNullOrEmpty(value))
Console.WriteLine("\nFFmpeg: " + value);
}
}
}

35
DLNA/Models/DlnaModel.cs Normal file
View File

@ -0,0 +1,35 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
namespace DLNA.Models
{
public class DlnaModel
{
public string name { get; set; }
public string uri { get; set; }
public string img { get; set; }
public string preview { get; set; }
public List<Subtitle> subtitles { get; set; }
public string path { get; set; }
public string type { get; set; }
public long length { get; set; }
public DateTime creationTime { get; set; }
public int s { get; set; }
public int e { get; set; }
public JObject tmdb { get; set; }
public JObject episode { get; set; }
}
}

9
DLNA/Models/Subtitle.cs Normal file
View File

@ -0,0 +1,9 @@
namespace DLNA.Models
{
public class Subtitle
{
public string label { get; set; }
public string url { get; set; }
}
}

375
JacRed/ApiController.cs Normal file
View File

@ -0,0 +1,375 @@
using Jackett;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Shared.Engine.Utilities;
namespace JacRed.Controllers
{
public class ApiController : JacBaseController
{
#region Conf
[HttpGet]
[Route("api/v1.0/conf")]
public JsonResult JacConf(string apikey)
{
return Json(new
{
apikey = string.IsNullOrWhiteSpace(AppInit.conf.apikey) || apikey == AppInit.conf.apikey
});
}
#endregion
#region Indexers
[HttpGet]
[Route("/api/v2.0/indexers/{status}/results")]
async public Task<ActionResult> Indexers(string apikey, string query, string title, string title_original, int year, Dictionary<string, string> category, int is_serial = -1)
{
if (string.IsNullOrEmpty(ModInit.conf.typesearch))
return Content("typesearch == null");
#region Запрос с NUM
bool rqnum = !HttpContext.Request.QueryString.Value.Contains("&is_serial=") && HttpContext.Request.Headers.UserAgent.ToString() == "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36";
if (rqnum && query != null)
{
var mNum = Regex.Match(query, "^([^a-z-A-Z]+) ([^а-я-А-Я]+) ([0-9]{4})$");
if (mNum.Success)
{
if (Regex.IsMatch(mNum.Groups[2].Value, "[a-zA-Z0-9]{2}"))
{
var g = mNum.Groups;
title = g[1].Value;
title_original = g[2].Value;
year = int.Parse(g[3].Value);
}
}
else
{
if (Regex.IsMatch(query, "^([^a-z-A-Z]+) ((19|20)[0-9]{2})$"))
return Content(JsonConvertPool.SerializeObject(new { Results = new List<object>(), jacred = ModInit.conf.typesearch == "red" }), "application/json; charset=utf-8");
mNum = Regex.Match(query, "^([^a-z-A-Z]+) ([^а-я-А-Я]+)$");
if (mNum.Success)
{
if (Regex.IsMatch(mNum.Groups[2].Value, "[a-zA-Z0-9]{2}"))
{
var g = mNum.Groups;
title = g[1].Value;
title_original = g[2].Value;
}
}
}
}
#endregion
if (!HttpContext.Request.QueryString.Value.ToLower().Contains("&category[]="))
category = null;
IEnumerable<TorrentDetails> torrents = null;
if (ModInit.conf.typesearch == "red")
{
#region red
string memoryKey = $"{ModInit.conf.typesearch}:{query}:{rqnum}:{title}:{title_original}:{year}:{is_serial}";
if (!hybridCache.TryGetValue(memoryKey, out List<TorrentDetails> _redCache, inmemory: false))
{
var res = RedApi.Indexers(rqnum, apikey, query, title, title_original, year, is_serial, category);
_redCache = res.torrents.ToList();
if (res.setcache && !red.evercache.enable)
hybridCache.Set(memoryKey, _redCache, DateTime.Now.AddMinutes(5), inmemory: false);
}
if (ModInit.conf.merge == "jackett")
{
torrents = mergeTorrents
(
_redCache,
await JackettApi.Indexers(host, query, title, title_original, year, is_serial, category)
);
}
else
{
torrents = _redCache;
}
#endregion
}
else if (ModInit.conf.typesearch == "webapi")
{
#region webapi
if (ModInit.conf.merge == "jackett")
{
var t1 = WebApi.Indexers(query, title, title_original, year, is_serial, category);
var t2 = JackettApi.Indexers(host, query, title, title_original, year, is_serial, category);
await Task.WhenAll(t1, t2);
torrents = mergeTorrents(t1.Result, t2.Result);
}
else
{
torrents = await WebApi.Indexers(query, title, title_original, year, is_serial, category);
}
#endregion
}
else if (ModInit.conf.typesearch == "jackett")
{
torrents = await JackettApi.Indexers(host, query, title, title_original, year, is_serial, category);
}
return Content(JsonConvert.SerializeObject(new
{
Results = torrents.OrderByDescending(i => i.createTime).Take(2_000).Select(i => new
{
Tracker = i.trackerName,
Details = i.url != null && i.url.StartsWith("http") ? i.url : null,
Title = i.title,
Size = (long)(0 >= i.size ? getSizeInfo(i.sizeName) : i.size),
PublishDate = i.createTime,
Category = getCategoryIds(i, out string categoryDesc),
CategoryDesc = categoryDesc,
Seeders = i.sid,
Peers = i.pir,
MagnetUri = i.magnet,
Link = i.parselink != null ? $"{i.parselink}&apikey={apikey}" : null,
Info = ModInit.conf.typesearch != "red" || rqnum ? null : new
{
i.name,
i.originalname,
i.relased,
i.quality,
i.videotype,
i.sizeName,
i.voices,
seasons = i.seasons != null && i.seasons.Count > 0 ? i.seasons : null,
i.types
},
languages = !rqnum && i.languages != null && i.languages.Count > 0 ? i.languages : null,
ffprobe = rqnum ? null : i.ffprobe
}),
jacred = ModInit.conf.typesearch == "red"
}, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }), "application/json; charset=utf-8");
}
#endregion
#region Api
[HttpGet]
[Route("/api/v1.0/torrents")]
async public Task<ActionResult> Api(string apikey, string search, string altname, bool exact, string type, string sort, string tracker, string voice, string videotype, long relased, long quality, long season)
{
if (string.IsNullOrEmpty(ModInit.conf.typesearch))
return Content("typesearch == null");
#region search kp/imdb
if (!string.IsNullOrWhiteSpace(search) && Regex.IsMatch(search.Trim(), "^(tt|kp)[0-9]+$"))
{
string memkey = $"api/v1.0/torrents:{search}";
if (!hybridCache.TryGetValue(memkey, out (string original_name, string name) cache, inmemory: false))
{
search = search.Trim();
string uri = $"&imdb={search}";
if (search.StartsWith("kp"))
uri = $"&kp={search.Remove(0, 2)}";
var root = await Http.Get<JObject>("https://api.alloha.tv/?token=04941a9a3ca3ac16e2b4327347bbc1" + uri, timeoutSeconds: 10);
cache.original_name = root?.Value<JObject>("data")?.Value<string>("original_name");
cache.name = root?.Value<JObject>("data")?.Value<string>("name");
hybridCache.Set(memkey, cache, DateTime.Now.AddDays(1), inmemory: false);
}
if (!string.IsNullOrWhiteSpace(cache.name) && !string.IsNullOrWhiteSpace(cache.original_name))
{
search = cache.original_name;
altname = cache.name;
}
else
{
search = cache.original_name ?? cache.name;
}
}
#endregion
IEnumerable<TorrentDetails> torrents = null;
if (ModInit.conf.typesearch == "red")
{
#region red
torrents = RedApi.Api(search, altname, exact, type, sort, tracker, voice, videotype, relased, quality, season);
if (ModInit.conf.merge == "jackett")
{
torrents = mergeTorrents
(
torrents,
await JackettApi.Api(host, search)
);
}
#endregion
}
else if (ModInit.conf.typesearch == "webapi")
{
#region webapi
if (ModInit.conf.merge == "jackett")
{
var t1 = WebApi.Api(search);
var t2 = JackettApi.Api(host, search);
await Task.WhenAll(t1, t2);
torrents = mergeTorrents(t1.Result, t2.Result);
}
else
{
torrents = await WebApi.Api(search);
}
#endregion
}
else if (ModInit.conf.typesearch == "jackett")
{
torrents = await JackettApi.Api(host, search);
}
return Content(JsonConvert.SerializeObject(torrents.Take(2_000).Select(i => new
{
tracker = i.trackerName,
url = i.url != null && i.url.StartsWith("http") ? i.url : null,
i.title,
size = 0 > i.size ? getSizeInfo(i.sizeName) : i.size,
i.sizeName,
i.createTime,
i.sid,
i.pir,
magnet = i.magnet ?? $"{i.parselink}&apikey={apikey}",
i.name,
i.originalname,
i.relased,
i.videotype,
i.quality,
i.voices,
i.seasons,
i.types
}), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }), "application/json; charset=utf-8");
}
#endregion
#region getSizeInfo
long getSizeInfo(string sizeName)
{
if (string.IsNullOrWhiteSpace(sizeName))
return 0;
try
{
double size = 0.1;
var gsize = Regex.Match(sizeName, "([0-9\\.,]+) (Mb|МБ|GB|ГБ|TB|ТБ)", RegexOptions.IgnoreCase).Groups;
if (!string.IsNullOrWhiteSpace(gsize[2].Value))
{
if (double.TryParse(gsize[1].Value, NumberStyles.Any, CultureInfo.InvariantCulture, out size) && size != 0)
{
if (gsize[2].Value.ToLower() is "gb" or "гб")
size *= 1024;
if (gsize[2].Value.ToLower() is "tb" or "тб")
size *= 1048576;
return (long)(size * 1048576);
}
}
}
catch { }
return 0;
}
#endregion
#region getCategoryIds
HashSet<int> getCategoryIds(TorrentDetails t, out string categoryDesc)
{
categoryDesc = null;
HashSet<int> categoryIds = new HashSet<int>();
if (t.types == null)
return categoryIds;
foreach (string type in t.types)
{
switch (type)
{
case "movie":
categoryDesc = "Movies";
categoryIds.Add(2000);
break;
case "serial":
categoryDesc = "TV";
categoryIds.Add(5000);
break;
case "documovie":
case "docuserial":
categoryDesc = "TV/Documentary";
categoryIds.Add(5080);
break;
case "tvshow":
categoryDesc = "TV/Foreign";
categoryIds.Add(5020);
categoryIds.Add(2010);
break;
case "anime":
categoryDesc = "TV/Anime";
categoryIds.Add(5070);
break;
}
}
return categoryIds;
}
#endregion
#region mergeTorrents
static IEnumerable<TorrentDetails> mergeTorrents(IEnumerable<TorrentDetails> red, IEnumerable<TorrentDetails> jac)
{
if (red == null && jac == null)
return new List<TorrentDetails>();
if (red == null || !red.Any())
return jac;
if (jac == null || !jac.Any())
return red;
var torrents = new Dictionary<string, TorrentDetails>();
foreach (var i in red.Concat(jac))
{
if (string.IsNullOrEmpty(i.url) || !i.url.StartsWith("http"))
continue;
void add(string url) { torrents.TryAdd(Regex.Replace(url, "^https?://[^/]+/", ""), (TorrentDetails)i.Clone()); }
if (i.urls != null && i.urls.Count > 0)
{
foreach (string u in i.urls)
add(u);
}
else
{
add(i.url);
}
}
return torrents.Values;
}
#endregion
}
}

View File

@ -0,0 +1,74 @@
using Microsoft.AspNetCore.Mvc;
using JacRed.Models.AniLibria;
namespace JacRed.Controllers
{
[Route("anilibria/[action]")]
public class AniLibriaController : JacBaseController
{
#region parseMagnet
async public Task<ActionResult> parseMagnet(string url, string code)
{
if (!jackett.Anilibria.enable || jackett.Anilibria.showdown)
return Content("disable");
var proxyManager = new ProxyManager("anilibria", jackett.Anilibria);
byte[] _t = await Http.Download($"{jackett.Anilibria.host}/{url}", referer: $"{jackett.Anilibria.host}/release/{code}.html", proxy: proxyManager.Get());
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
proxyManager.Refresh();
return Content("error");
}
#endregion
#region parsePage
async public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query)
{
if (!jackett.Anilibria.enable)
return false;
var proxyManager = new ProxyManager("anilibria", jackett.Anilibria);
var roots = await Http.Get<List<RootObject>>("https://api.anilibria.tv/v2/searchTitles?search=" + HttpUtility.UrlEncode(query), timeoutSeconds: jackett.timeoutSeconds, proxy: proxyManager.Get(), IgnoreDeserializeObject: true);
if (roots == null || roots.Count == 0)
{
consoleErrorLog("anilibria");
proxyManager.Refresh();
return false;
}
foreach (var root in roots)
{
DateTime createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(root.last_change > root.updated ? root.last_change : root.updated);
foreach (var torrent in root.torrents.list)
{
if (string.IsNullOrWhiteSpace(root.code) || 480 >= torrent.quality.resolution && string.IsNullOrWhiteSpace(torrent.quality.encoder) && string.IsNullOrWhiteSpace(torrent.url))
continue;
torrents.Add(new TorrentDetails()
{
trackerName = "anilibria.tv",
types = new string[] { "anime" },
url = $"{jackett.Anilibria.host}/release/{root.code}.html",
title = $"{root.names.ru} / {root.names.en} {root.season.year} (s{root.season.code}, e{torrent.series.@string}) [{torrent.quality.@string}]",
sid = torrent.seeders,
pir = torrent.leechers,
createTime = createTime,
parselink = $"{host}/anilibria/parsemagnet?url={HttpUtility.UrlEncode(torrent.url)}&code={root.code}",
sizeName = tParse.BytesToString(torrent.total_size),
name = root.names.ru,
originalname = root.names.en,
relased = root.season.year
});
}
}
return true;
}
#endregion
}
}

View File

@ -0,0 +1,149 @@
using Microsoft.AspNetCore.Mvc;
namespace JacRed.Controllers
{
[Route("anifilm/[action]")]
public class AnifilmController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query)
{
if (!jackett.Anifilm.enable || jackett.Anifilm.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string url)
{
if (!jackett.Anifilm.enable)
return Content("disable");
var proxyManager = new ProxyManager("anifilm", jackett.Anifilm);
var fullNews = await Http.Get($"{jackett.Anifilm.host}/{url}", proxy: proxyManager.Get());
if (fullNews == null)
return Content("error");
string tid = null;
string[] releasetorrents = fullNews.Split("<li class=\"release__torrents-item\">");
string _rnews = releasetorrents.FirstOrDefault(i => i.Contains("href=\"/releases/download-torrent/") && i.Contains(" 1080p "));
if (!string.IsNullOrWhiteSpace(_rnews))
tid = Regex.Match(_rnews, "href=\"/(releases/download-torrent/[0-9]+)\">скачать</a>").Groups[1].Value;
if (string.IsNullOrWhiteSpace(tid))
tid = Regex.Match(fullNews, "href=\"/(releases/download-torrent/[0-9]+)\">скачать</a>").Groups[1].Value;
if (!string.IsNullOrWhiteSpace(tid))
{
var _t = await Http.Download($"{jackett.Anifilm.host}/{tid}", referer: $"{jackett.Anifilm.host}/", proxy: proxyManager.Get());
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
}
proxyManager.Refresh();
return Content("error");
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query)
{
#region html
var proxyManager = new ProxyManager("anifilm", jackett.Anifilm);
string html = await Http.Get($"{jackett.Anifilm.host}/releases?title={HttpUtility.UrlEncode(query)}", timeoutSeconds: jackett.timeoutSeconds, proxy: proxyManager.Get());
if (html == null || !html.Contains("id=\"ui-components\""))
{
consoleErrorLog("anifilm");
proxyManager.Refresh();
return null;
}
#endregion
var torrents = new List<TorrentDetails>();
if (html.Contains("class=\"releases__item\""))
{
foreach (string row in html.Split("class=\"releases__item\"").Skip(1))
{
if (string.IsNullOrWhiteSpace(row))
continue;
#region Локальный метод - Match
string Match(string pattern, int index = 1)
{
string res = HttpUtility.HtmlDecode(new Regex(pattern, RegexOptions.IgnoreCase).Match(row).Groups[index].Value.Trim());
res = Regex.Replace(res, "[\n\r\t ]+", " ");
return res.Trim();
}
#endregion
#region Данные раздачи
string url = Match("<a href=\"/(releases/[^\"]+)\"");
string name = Match("<a class=\"releases__title-russian\" [^>]+>([^<]+)</a>");
string originalname = Match("<span class=\"releases__title-original\">([^<]+)</span>");
string episodes = Match("([0-9]+(-[0-9]+)?) из [0-9]+ эп.,");
if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(originalname))
continue;
int.TryParse(Match("<a href=\"/releases/releases/[^\"]+\">([0-9]{4})</a> г\\."), out int relased);
string title = $"{name} / {originalname}";
if (!string.IsNullOrWhiteSpace(episodes))
title += $" ({episodes})";
var createTime = DateTime.Now.AddYears(-1);
if (relased > 0)
{
title += $" [{relased}]";
createTime = tParse.ParseCreateTime($"01.01.{relased}", "dd.MM.yyyy");
}
#endregion
torrents.Add(new TorrentDetails()
{
types = new string[] { "anime" },
url = $"{jackett.Anifilm.host}/{url}",
title = title,
sid = 1,
createTime = createTime,
parselink = $"{host}/anifilm/parsemagnet?url={HttpUtility.UrlEncode(url)}",
name = name,
originalname = originalname,
relased = relased
});
}
}
else
{
string url = Regex.Match(html, "property=\"og:url\" content=\"https?://[^/]+/([^\"]+)\"").Groups[1].Value;
string name = Regex.Match(html, "itemprop=\"name\">([^<]+)").Groups[1].Value;
string alternative = Regex.Match(html, "itemprop=\"alternativeHeadline\">([^<]+)").Groups[1].Value;
if (!string.IsNullOrEmpty(name))
{
torrents.Add(new TorrentDetails()
{
types = new string[] { "anime" },
url = $"{jackett.Anifilm.host}/{url}",
title = name + (!string.IsNullOrEmpty(alternative) ? $" / {alternative}" : ""),
sid = 1,
parselink = $"{host}/anifilm/parsemagnet?url={HttpUtility.UrlEncode(url)}"
});
}
}
return torrents;
}
#endregion
}
}

View File

@ -0,0 +1,219 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
namespace JacRed.Controllers
{
[Route("animelayer/[action]")]
public class AnimeLayerController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query)
{
if (!jackett.Animelayer.enable || string.IsNullOrEmpty(jackett.Animelayer.cookie ?? jackett.Animelayer.login.u) || jackett.Animelayer.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string url)
{
if (!jackett.Animelayer.enable)
return Content("disable");
string cookie = await getCookie();
if (string.IsNullOrEmpty(cookie))
return Content("cookie == null");
var proxyManager = new ProxyManager("animelayer", jackett.Animelayer);
byte[] _t = await Http.Download($"{url}download/", proxy: proxyManager.Get(), cookie: cookie, referer: jackett.Animelayer.host);
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
return Content("error");
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query)
{
#region Авторизация
string cookie = await getCookie();
if (string.IsNullOrEmpty(cookie))
{
consoleErrorLog("animelayer");
return null;
}
#endregion
var torrents = new List<TorrentDetails>();
var proxyManager = new ProxyManager("animelayer", jackett.Animelayer);
#region html
string html = await Http.Get($"{jackett.Animelayer.host}/torrents/anime/?q={HttpUtility.UrlEncode(query)}", proxy: proxyManager.Get(), cookie: cookie, timeoutSeconds: jackett.timeoutSeconds);
if (html != null && html.Contains("id=\"wrapper\""))
{
if (!html.Contains($">{jackett.Animelayer.login.u}<"))
{
consoleErrorLog("animelayer");
return null;
}
}
else if (html == null)
{
consoleErrorLog("animelayer");
return null;
}
#endregion
foreach (string row in html.Split("class=\"torrent-item torrent-item-medium panel\"").Skip(1))
{
if (string.IsNullOrWhiteSpace(row))
continue;
#region Локальный метод - Match
string Match(string pattern, int index = 1)
{
string res = new Regex(pattern, RegexOptions.IgnoreCase).Match(row).Groups[index].Value.Trim();
res = Regex.Replace(res, "[\n\r\t ]+", " ");
return res.Trim();
}
#endregion
#region Дата создания
DateTime createTime = default;
if (Regex.IsMatch(row, "(Добавл|Обновл)[^<]+</span>(&nbsp;)?[0-9]+ [^ ]+ [0-9]{4}"))
{
createTime = tParse.ParseCreateTime(Match(">(Добавл|Обновл)[^<]+</span>(&nbsp;)?([0-9]+ [^ ]+ [0-9]{4})", 3), "dd.MM.yyyy");
}
else
{
string date = Match("(Добавл|Обновл)[^<]+</span>([^\n]+) в", 2);
if (!string.IsNullOrWhiteSpace(date))
createTime = tParse.ParseCreateTime($"{date} {DateTime.Today.Year}", "dd.MM.yyyy");
}
#endregion
#region Данные раздачи
var gurl = Regex.Match(row, "<a href=\"/(torrent/[a-z0-9]+)/?\">([^<]+)</a>").Groups;
string url = gurl[1].Value;
string title = gurl[2].Value;
string _sid = Match("class=\"icon s-icons-upload\"></i>(&nbsp;)?([0-9]+)", 2);
string _pir = Match("class=\"icon s-icons-download\"></i>(&nbsp;)?([0-9]+)", 2);
string sizeName = Match("<i class=\"icon s-icons-download\"></i>[^<]+<span class=\"gray\">[^<]+</span>[\n\r\t ]+([^\n\r<]+)").Trim();
if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(title))
continue;
if (Regex.IsMatch(row, "Разрешение: ?</strong>1920x1080"))
title += " [1080p]";
else if (Regex.IsMatch(row, "Разрешение: ?</strong>1280x720"))
title += " [720p]";
#endregion
int.TryParse(_sid, out int sid);
int.TryParse(_pir, out int pir);
torrents.Add(new TorrentDetails()
{
types = new string[] { "anime" },
url = $"{jackett.Animelayer.host}/{url}/",
title = title,
sid = sid,
pir = pir,
sizeName = sizeName,
createTime = createTime,
parselink = $"{host}/animelayer/parsemagnet?url={HttpUtility.UrlEncode(url)}"
});
}
return torrents;
}
#endregion
#region getCookie
async static ValueTask<string> getCookie()
{
if (!string.IsNullOrEmpty(jackett.Animelayer.cookie))
return jackett.Animelayer.cookie;
string authKey = "Animelayer:TakeLogin()";
if (Startup.memoryCache.TryGetValue(authKey, out string _cookie))
return _cookie;
if (Startup.memoryCache.TryGetValue($"{authKey}:error", out _))
return null;
Startup.memoryCache.Set($"{authKey}:error", 0, TimeSpan.FromSeconds(20));
try
{
using (var clientHandler = new System.Net.Http.HttpClientHandler()
{
AllowAutoRedirect = false
})
{
clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new System.Net.Http.HttpClient(clientHandler))
{
client.Timeout = TimeSpan.FromSeconds(jackett.timeoutSeconds);
client.MaxResponseContentBufferSize = 2000000; // 2MB
foreach (var h in Http.defaultFullHeaders)
client.DefaultRequestHeaders.TryAddWithoutValidation(h.Key, h.Value);
var postParams = new Dictionary<string, string>
{
{ "login", jackett.Animelayer.login.u },
{ "password", jackett.Animelayer.login.p }
};
using (var postContent = new System.Net.Http.FormUrlEncodedContent(postParams))
{
using (var response = await client.PostAsync($"{jackett.Animelayer.host}/auth/login/", postContent))
{
if (response.Headers.TryGetValues("Set-Cookie", out var cook))
{
string layer_id = null, layer_hash = null, PHPSESSID = null;
foreach (string line in cook)
{
if (string.IsNullOrWhiteSpace(line))
continue;
if (line.Contains("layer_id="))
layer_id = new Regex("layer_id=([^;]+)(;|$)").Match(line).Groups[1].Value;
if (line.Contains("layer_hash="))
layer_hash = new Regex("layer_hash=([^;]+)(;|$)").Match(line).Groups[1].Value;
if (line.Contains("PHPSESSID="))
PHPSESSID = new Regex("PHPSESSID=([^;]+)(;|$)").Match(line).Groups[1].Value;
}
if (!string.IsNullOrWhiteSpace(layer_id) && !string.IsNullOrWhiteSpace(layer_hash) && !string.IsNullOrWhiteSpace(PHPSESSID))
{
string cookie = $"layer_id={layer_id}; layer_hash={layer_hash}; PHPSESSID={PHPSESSID};";
Startup.memoryCache.Set(authKey, cookie, DateTime.Today.AddDays(1));
return cookie;
}
}
}
}
}
}
}
catch { }
return null;
}
#endregion
}
}

View File

@ -0,0 +1,153 @@
using Microsoft.AspNetCore.Mvc;
namespace JacRed.Controllers
{
[Route("bigfangroup/[action]")]
public class BigFanGroup : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query, string[] cats)
{
if (!jackett.BigFanGroup.enable || jackett.BigFanGroup.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query, cats));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string id)
{
if (!jackett.BigFanGroup.enable)
return Content("disable");
var proxyManager = new ProxyManager("bigfangroup", jackett.BigFanGroup);
var _t = await Http.Download($"{jackett.BigFanGroup.host}/download.php?id={id}", proxy: proxyManager.Get(), referer: jackett.BigFanGroup.host);
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
return Content("error");
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query, string[] cats)
{
var torrents = new List<TorrentDetails>();
var proxyManager = new ProxyManager("bigfangroup", jackett.BigFanGroup);
#region Кеш html
string html = await Http.Get($"{jackett.BigFanGroup.host}/browse.php?search=" + HttpUtility.UrlEncode(query), proxy: proxyManager.Get(), timeoutSeconds: jackett.timeoutSeconds);
if (html == null || !html.Contains("id=\"searchinput\""))
{
consoleErrorLog("bigfangroup");
return null;
}
#endregion
var doc = new HtmlDocument();
doc.LoadHtml(html.Replace("&nbsp;", " "));
var nodes = doc.DocumentNode.SelectNodes("//tbody//tr");
if (nodes == null || nodes.Count == 0)
return null;
foreach (var row in nodes)
{
var hc = new HtmlCommon(row);
#region Данные раздачи
string title = hc.NodeValue(".//a//b");
DateTime createTime = tParse.ParseCreateTime(hc.NodeValue(".//img[@src='pic/time.png']", "title").Split(" в")[0], "dd.MM.yyyy");
string viewtopic = hc.Match("href=\"details.php\\?id=([0-9]+)");
string tracker = hc.Match("href=\"browse.php\\?cat=([0-9]+)");
string sid = hc.NodeValue(".//font[@color='#000000']");
string pir = hc.Match("todlers=[0-9]+\">([0-9]+)</a>");
string sizeName = hc.NodeValue(".//td[contains(text(), 'GB') or contains(text(), 'MB')]");
if (string.IsNullOrEmpty(viewtopic) || string.IsNullOrEmpty(tracker) || string.IsNullOrEmpty(title) || title.Contains(" | КПК"))
continue;
#endregion
#region types
string[] types = null;
switch (tracker)
{
case "13":
case "52":
case "33":
case "48":
case "21":
case "39":
case "18":
case "24":
case "36":
case "53":
case "19":
case "31":
case "29":
case "27":
case "22":
case "26":
case "23":
case "30":
types = new string[] { "movie" };
break;
case "12":
case "20":
case "47":
types = new string[] { "multfilm" };
types = new string[] { "multserial" };
break;
case "11":
types = new string[] { "serial" };
break;
case "49":
case "32":
case "28":
types = new string[] { "docuserial", "documovie" };
break;
case "25":
types = new string[] { "tvshow" };
break;
}
if (cats != null)
{
if (types == null)
continue;
bool isok = false;
foreach (string cat in cats)
{
if (types.Contains(cat))
isok = true;
}
if (!isok)
continue;
}
#endregion
torrents.Add(new TorrentDetails()
{
types = types,
url = $"{jackett.BigFanGroup.host}/forum/viewtopic.php?t={viewtopic}",
title = title,
sid = HtmlCommon.Integer(sid),
pir = HtmlCommon.Integer(pir),
sizeName = sizeName,
createTime = createTime,
parselink = $"{host}/bigfangroup/parsemagnet?id={viewtopic}"
});
}
return torrents;
}
#endregion
}
}

View File

@ -0,0 +1,151 @@
using Microsoft.AspNetCore.Mvc;
namespace JacRed.Controllers
{
[Route("bitru/[action]")]
public class BitruController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query, string[] cats)
{
if (!jackett.Bitru.enable || jackett.Bitru.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query, cats));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string id, bool usecache)
{
if (!jackett.Bitru.enable)
return Content("disable");
var proxyManager = new ProxyManager("bitru", jackett.Bitru);
byte[] _t = await Http.Download($"{jackett.Bitru.host}/download.php?id={id}", referer: $"{jackett.Bitru}/details.php?id={id}", proxy: proxyManager.Get());
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
proxyManager.Refresh();
return Content("error");
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query, string[] cats)
{
#region html
var proxyManager = new ProxyManager("bitru", jackett.Bitru);
string html = await Http.Get($"{jackett.Bitru.host}/browse.php?s={HttpUtility.HtmlEncode(query)}&sort=&tmp=&cat=&subcat=&year=&country=&sound=&soundtrack=&subtitles=#content", proxy: proxyManager.Get(), timeoutSeconds: jackett.timeoutSeconds);
if (html == null || !html.Contains("id=\"logo\""))
{
consoleErrorLog("bitru");
proxyManager.Refresh();
return null;
}
#endregion
var torrents = new List<TorrentDetails>();
foreach (string row in html.Split("<div class=\"b-title\"").Skip(1))
{
if (string.IsNullOrWhiteSpace(row) || row.Contains(">Аниме</a>"))
continue;
#region Локальный метод - Match
string Match(string pattern, int index = 1)
{
string res = HttpUtility.HtmlDecode(new Regex(pattern, RegexOptions.IgnoreCase).Match(row).Groups[index].Value.Trim());
res = Regex.Replace(res, "[\n\r\t ]+", " ");
return res.Trim();
}
#endregion
#region Дата создания
DateTime createTime = default;
if (row.Contains("<span>Сегодня"))
{
createTime = DateTime.Today;
}
else if (row.Contains("<span>Вчера"))
{
createTime = DateTime.Today.AddDays(-1);
}
else
{
createTime = tParse.ParseCreateTime(Match("<div class=\"ellips\">(<i [^>]+></i>)?<span>([0-9]{2} [^ ]+ [0-9]{4}) в [0-9]{2}:[0-9]{2} от <a", 2), "dd.MM.yyyy");
}
#endregion
#region Данные раздачи
string url = Match("href=\"(details.php\\?id=[0-9]+)\"");
string newsid = Match("href=\"details.php\\?id=([0-9]+)\"");
string cat = Match("<a href=\"browse.php\\?tmp=(movie|serial)&");
string title = Match("<div class=\"it-title\">([^<]+)</div>");
string _sid = Match("<span class=\"b-seeders\">([0-9]+)");
string _pir = Match("<span class=\"b-leechers\">([0-9]+)");
string sizeName = Match("title=\"Размер\">([^<]+)</td>");
if (string.IsNullOrWhiteSpace(cat) || string.IsNullOrWhiteSpace(newsid) || string.IsNullOrWhiteSpace(title))
continue;
if (!title.ToLower().Contains(query.ToLower()))
continue;
#endregion
#region types
string[] types = null;
switch (cat)
{
case "movie":
types = new string[] { "movie" };
break;
case "serial":
types = new string[] { "serial" };
break;
}
if (cats != null)
{
if (types == null)
continue;
bool isok = false;
foreach (string c in cats)
{
if (types.Contains(c))
isok = true;
}
if (!isok)
continue;
}
#endregion
int.TryParse(_sid, out int sid);
int.TryParse(_pir, out int pir);
torrents.Add(new TorrentDetails()
{
types = types,
url = $"{jackett.Bitru.host}/{url}",
title = title,
sid = sid,
pir = pir,
sizeName = sizeName,
createTime = createTime,
parselink = $"{host}/bitru/parsemagnet?id={newsid}"
});
}
return torrents;
}
#endregion
}
}

View File

@ -0,0 +1,269 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
namespace JacRed.Controllers
{
[Route("kinozal/[action]")]
public class KinozalController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query, string[] cats)
{
if (!jackett.Kinozal.enable || jackett.Kinozal.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query, cats));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string id)
{
if (!jackett.Kinozal.enable)
return Content("disable");
var proxyManager = new ProxyManager("kinozal", jackett.Kinozal);
#region Download
if (jackett.Kinozal.cookie != null || Cookie != null)
{
var _t = await Http.Download("http://dl.kinozal.tv/download.php?id=" + id, proxy: proxyManager.Get(), cookie: jackett.Kinozal.cookie ?? Cookie, referer: jackett.Kinozal.host, timeoutSeconds: 10);
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
}
#endregion
string srv_details = await Http.Post($"{jackett.Kinozal.host}/get_srv_details.php?id={id}&action=2", $"id={id}&action=2", "__cfduid=d476ac2d9b5e18f2b67707b47ebd9b8cd1560164391; uid=20520283; pass=ouV5FJdFCd;", proxy: proxyManager.Get(), timeoutSeconds: 10);
if (srv_details != null)
{
string torrentHash = new Regex("<ul><li>Инфо хеш: +([^<]+)</li>").Match(srv_details).Groups[1].Value;
if (!string.IsNullOrEmpty(torrentHash))
return Redirect($"magnet:?xt=urn:btih:{torrentHash}");
}
proxyManager.Refresh();
return Content("error");
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query, string[] cats)
{
var torrents = new List<TorrentDetails>();
var proxyManager = new ProxyManager("kinozal", jackett.Kinozal);
string html = await Http.Get($"{jackett.Kinozal.host}/browse.php?s={HttpUtility.UrlEncode(query)}&g=0&c=0&v=0&d=0&w=0&t=0&f=0", proxy: proxyManager.Get(), timeoutSeconds: jackett.timeoutSeconds);
if (html != null && html.Contains("Кинозал.ТВ</title>"))
{
if (!html.Contains(">Выход</a>") && !string.IsNullOrWhiteSpace(jackett.Kinozal.login.u) && !string.IsNullOrWhiteSpace(jackett.Kinozal.login.p))
TakeLogin();
}
else if (html == null)
{
consoleErrorLog("kinozal");
proxyManager.Refresh();
return null;
}
foreach (string row in Regex.Split(html, "<tr class=('first bg'|bg)>").Skip(1))
{
if (string.IsNullOrWhiteSpace(row))
continue;
#region Локальный метод - Match
string Match(string pattern, int index = 1)
{
string res = HttpUtility.HtmlDecode(new Regex(pattern, RegexOptions.IgnoreCase).Match(row).Groups[index].Value.Trim());
res = Regex.Replace(res, "[\n\r\t ]+", " ");
return res.Trim();
}
#endregion
#region Дата создания
DateTime createTime = default;
if (row.Contains("<td class='s'>сегодня"))
{
createTime = DateTime.Today;
}
else if (row.Contains("<td class='s'>вчера"))
{
createTime = DateTime.Today.AddDays(-1);
}
else
{
createTime = tParse.ParseCreateTime(Match("<td class='s'>([0-9]{2}.[0-9]{2}.[0-9]{4}) в [0-9]{2}:[0-9]{2}"), "dd.MM.yyyy");
}
#endregion
#region Данные раздачи
string url = Match("href=\"/(details.php\\?id=[0-9]+)\"");
string tracker = Match("src=\"/pic/cat/([0-9]+)\\.gif\"");
string title = Match("class=\"r[0-9]+\">([^<]+)");
string _sid = Match("<td class='sl_s'>([0-9]+)");
string _pir = Match("<td class='sl_p'>([0-9]+)");
string sizeName = Match("<td class='s'>([0-9\\.,]+ (МБ|ГБ))");
if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(tracker))
continue;
#endregion
// Id новости
string id = Match("href=\"/details.php\\?id=([0-9]+)\"");
if (string.IsNullOrEmpty(id))
continue;
#region types
string[] types = new string[] { };
switch (tracker)
{
case "1002":
case "8":
case "6":
case "15":
case "17":
case "35":
case "39":
case "13":
case "14":
case "24":
case "11":
case "10":
case "9":
case "47":
case "18":
case "37":
case "12":
case "7":
case "16":
types = new string[] { "movie" };
break;
case "45":
case "46":
types = new string[] { "serial" };
break;
case "21":
case "22":
types = new string[] { "multfilm", "multserial" };
break;
case "20":
types = new string[] { "anime" };
break;
case "1006":
case "48":
case "49":
case "50":
case "38":
types = new string[] { "tvshow" };
break;
}
if (cats != null)
{
if (types == null)
continue;
bool isok = false;
foreach (string cat in cats)
{
if (types.Contains(cat))
isok = true;
}
if (!isok)
continue;
}
#endregion
int.TryParse(_sid, out int sid);
int.TryParse(_pir, out int pir);
torrents.Add(new TorrentDetails()
{
types = types,
url = $"{jackett.Kinozal.host}/{url}",
title = title,
sid = sid,
pir = pir,
sizeName = sizeName,
createTime = createTime,
parselink = $"{host}/kinozal/parsemagnet?id={id}"
});
}
return torrents;
}
#endregion
#region Cookie / TakeLogin
static string Cookie;
async static void TakeLogin()
{
string authKey = "kinozal:TakeLogin()";
if (Startup.memoryCache.TryGetValue(authKey, out _))
return;
Startup.memoryCache.Set(authKey, 0, AppInit.conf.multiaccess ? TimeSpan.FromMinutes(2) : TimeSpan.FromSeconds(20));
try
{
using (var clientHandler = new System.Net.Http.HttpClientHandler()
{
AllowAutoRedirect = false
})
{
clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new System.Net.Http.HttpClient(clientHandler))
{
client.Timeout = TimeSpan.FromSeconds(jackett.timeoutSeconds);
client.MaxResponseContentBufferSize = 2000000; // 2MB
client.DefaultRequestHeaders.Add("origin", jackett.Kinozal.host);
client.DefaultRequestHeaders.Add("referer", $"{jackett.Kinozal.host}/");
client.DefaultRequestHeaders.Add("upgrade-insecure-requests", "1");
foreach (var h in Http.defaultFullHeaders)
client.DefaultRequestHeaders.TryAddWithoutValidation(h.Key, h.Value);
var postParams = new Dictionary<string, string>
{
{ "username", jackett.Kinozal.login.u },
{ "password", jackett.Kinozal.login.p },
{ "returnto", "" }
};
using (var postContent = new System.Net.Http.FormUrlEncodedContent(postParams))
{
using (var response = await client.PostAsync($"{jackett.Kinozal.host}/takelogin.php", postContent))
{
if (response.Headers.TryGetValues("Set-Cookie", out var cook))
{
string uid = null, pass = null;
foreach (string line in cook)
{
if (string.IsNullOrWhiteSpace(line))
continue;
if (line.Contains("uid="))
uid = new Regex("uid=([0-9]+)").Match(line).Groups[1].Value;
if (line.Contains("pass="))
pass = new Regex("pass=([^;]+)(;|$)").Match(line).Groups[1].Value;
}
if (!string.IsNullOrWhiteSpace(uid) && !string.IsNullOrWhiteSpace(pass))
Cookie = $"uid={uid}; pass={pass};";
}
}
}
}
}
}
catch { }
}
#endregion
}
}

View File

@ -0,0 +1,235 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
namespace JacRed.Controllers
{
[Route("lostfilm/[action]")]
public class LostfilmController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query)
{
if (!jackett.Lostfilm.enable || string.IsNullOrEmpty(jackett.Lostfilm.cookie ?? jackett.Lostfilm.login.u) || jackett.Lostfilm.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string episodeid)
{
if (!jackett.Lostfilm.enable)
return Content("disable");
var _t = await getTorrent(episodeid);
if (_t != null)
return File(_t, "application/x-bittorrent");
return Content("error");
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query)
{
var proxyManager = new ProxyManager("lostfilm", jackett.Lostfilm);
#region html
bool validrq = false;
string html = await Http.Get($"{jackett.Lostfilm.host}/search/?q={HttpUtility.UrlEncode(query)}", timeoutSeconds: jackett.timeoutSeconds, proxy: proxyManager.Get());
if (html != null && html.Contains("onClick=\"FollowSerial("))
{
string serie = Regex.Match(html, "href=\"/series/([^\"]+)\" class=\"no-decoration\"").Groups[1].Value;
if (!string.IsNullOrWhiteSpace(serie))
{
html = await Http.Get($"{jackett.Lostfilm.host}/series/{serie}/seasons/", timeoutSeconds: jackett.timeoutSeconds);
if (html != null && html.Contains("LostFilm.TV"))
validrq = true;
}
}
if (!validrq)
{
consoleErrorLog("lostfilm");
return null;
}
#endregion
var torrents = new List<TorrentDetails>();
foreach (string row in html.Split("<tr>").Skip(1))
{
if (string.IsNullOrWhiteSpace(row))
continue;
#region Локальный метод - Match
string Match(string val, string pattern, int index = 1)
{
string res = HttpUtility.HtmlDecode(new Regex(pattern, RegexOptions.IgnoreCase).Match(val).Groups[index].Value.Trim());
res = Regex.Replace(res, "[\n\r\t ]+", " ");
return res.Trim();
}
#endregion
#region Данные раздачи
DateTime createTime = tParse.ParseCreateTime(Match(row, "data-released=\"([0-9]{2}\\.[0-9]{2}\\.[0-9]{4})\">([^<]+)</span>"), "dd.MM.yyyy");
string url = Match(html, "href=\"/(series/[^/]+/seasons)\" class=\"item active\">Гид по сериям</a>");
string sinfo = Match(row, "title=\"Перейти к серии\">([^<]+)</td>");
string name = Match(html, "<h1 class=\"title-ru\" itemprop=\"name\">([^<]+)</h1>");
string originalname = Match(html, "<h2 class=\"title-en\" itemprop=\"alternativeHeadline\">([^<]+)</h2>");
if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(originalname) || string.IsNullOrWhiteSpace(sinfo))
continue;
#endregion
string episodeid = Match(row, "onclick=\"PlayEpisode\\('([0-9]+)'\\)\"");
if (string.IsNullOrWhiteSpace(episodeid))
continue;
torrents.Add(new TorrentDetails()
{
types = new string[] { "serial" },
url = $"{jackett.Lostfilm.host}/{url}",
title = $"{name} / {originalname} / {sinfo} [{createTime.Year}, 1080p]",
sid = 1,
createTime = createTime,
parselink = $"{host}/lostfilm/parsemagnet?episodeid={episodeid}",
name = name,
originalname = originalname,
relased = createTime.Year
});
}
return torrents;
}
#endregion
#region getTorrent
async Task<byte[]> getTorrent(string episodeid)
{
try
{
string cookie = await getCookie();
if (string.IsNullOrEmpty(cookie))
return null;
var proxyManager = new ProxyManager("lostfilm", jackett.Lostfilm);
var proxy = proxyManager.Get();
// Получаем ссылку на поиск
string v_search = await Http.Get($"{jackett.Lostfilm.host}/v_search.php?a={episodeid}", proxy: proxy, cookie: cookie);
string retreSearchUrl = new Regex("url=(\")?(https?://[^/]+/[^\"]+)").Match(v_search ?? "").Groups[2].Value.Trim();
if (!string.IsNullOrWhiteSpace(retreSearchUrl))
{
// Загружаем HTML поиска
string shtml = await Http.Get(retreSearchUrl, proxy: proxy, cookie: cookie);
if (!string.IsNullOrWhiteSpace(shtml))
{
var match = new Regex("<div class=\"inner-box--link main\"><a href=\"([^\"]+)\">([^<]+)</a></div>").Match(Regex.Replace(shtml, "[\n\r\t]+", ""));
while (match.Success)
{
if (Regex.IsMatch(match.Groups[2].Value, "(2160p|2060p|1440p|1080p|720p)", RegexOptions.IgnoreCase))
{
string torrentFile = match.Groups[1].Value;
string quality = Regex.Match(match.Groups[2].Value, "(2160p|2060p|1440p|1080p|720p)").Groups[1].Value;
if (!string.IsNullOrWhiteSpace(torrentFile) && !string.IsNullOrWhiteSpace(quality))
{
byte[] torrent = await Http.Download(torrentFile, referer: $"{jackett.Lostfilm.host}/", proxy: proxy, cookie: cookie);
if (BencodeTo.Magnet(torrent) != null)
return torrent;
}
}
match = match.NextMatch();
}
}
}
}
catch { }
return null;
}
#endregion
#region getCookie
async static ValueTask<string> getCookie()
{
if (!string.IsNullOrEmpty(jackett.Lostfilm.cookie))
return jackett.Lostfilm.cookie;
string authKey = "Lostfilm:TakeLogin()";
if (Startup.memoryCache.TryGetValue(authKey, out string _cookie))
return _cookie;
if (Startup.memoryCache.TryGetValue($"{authKey}:error", out _))
return null;
Startup.memoryCache.Set($"{authKey}:error", 0, TimeSpan.FromSeconds(20));
try
{
using (var clientHandler = new System.Net.Http.HttpClientHandler()
{
AllowAutoRedirect = false
})
{
clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new System.Net.Http.HttpClient(clientHandler))
{
client.Timeout = TimeSpan.FromSeconds(jackett.timeoutSeconds);
client.MaxResponseContentBufferSize = 2000000; // 2MB
foreach (var h in Http.defaultFullHeaders)
client.DefaultRequestHeaders.TryAddWithoutValidation(h.Key, h.Value);
var postParams = new Dictionary<string, string>
{
{ "act", "users" },
{ "type", "login" },
{ "mail", jackett.Lostfilm.login.u },
{ "pass", jackett.Lostfilm.login.p },
{ "need_captcha", "" },
{ "captcha", "" },
{ "rem", "1" }
};
using (var postContent = new System.Net.Http.FormUrlEncodedContent(postParams))
{
using (var response = await client.PostAsync($"{jackett.Lostfilm.host}/ajaxik.users.php", postContent))
{
if (response.Headers.TryGetValues("Set-Cookie", out var cook))
{
string cookie = string.Empty;
foreach (string line in cook)
{
if (string.IsNullOrWhiteSpace(line))
continue;
cookie += " " + line;
}
if (cookie.Contains("lf_session=") && cookie.Contains("lnk_uid="))
{
cookie = Regex.Replace(cookie.Trim(), ";$", "");
Startup.memoryCache.Set(authKey, cookie, DateTime.Today.AddDays(1));
return cookie;
}
}
}
}
}
}
}
catch { }
return null;
}
#endregion
}
}

View File

@ -0,0 +1,102 @@
using Microsoft.AspNetCore.Mvc;
using System.Text;
namespace JacRed.Controllers
{
[Route("megapeer/[action]")]
public class MegapeerController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query, string cat)
{
if (!jackett.Megapeer.enable || jackett.Megapeer.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query, cat));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string id)
{
if (!jackett.Megapeer.enable)
return Content("disable");
var proxyManager = new ProxyManager("megapeer", jackett.Megapeer);
byte[] _t = await Http.Download($"{jackett.Megapeer.host}/download/{id}", referer: jackett.Megapeer.host, proxy: proxyManager.Get());
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
proxyManager.Refresh();
return Content("error");
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query, string cat)
{
#region html
var proxyManager = new ProxyManager("megapeer", jackett.Megapeer);
string html = await Http.Get($"{jackett.Megapeer.host}/browse.php?search={HttpUtility.UrlEncode(query, Encoding.GetEncoding(1251))}&cat={cat}", encoding: Encoding.GetEncoding(1251), proxy: proxyManager.Get(), timeoutSeconds: jackett.timeoutSeconds, headers: HeadersModel.Init(
("dnt", "1"),
("pragma", "no-cache"),
("referer", $"{jackett.Megapeer.host}"),
("sec-fetch-dest", "document"),
("sec-fetch-mode", "navigate"),
("sec-fetch-site", "same-origin"),
("sec-fetch-user", "?1"),
("upgrade-insecure-requests", "1")
));
if (html == null || !html.Contains("id=\"logo\"") || html.Contains("<H1>Раздачи за последние"))
{
consoleErrorLog("megapeer");
proxyManager.Refresh();
return null;
}
#endregion
var doc = new HtmlDocument();
doc.LoadHtml(html.Replace("&nbsp;", " "));
var nodes = doc.DocumentNode.SelectNodes("//tr[@class='table_fon']");
if (nodes == null || nodes.Count == 0)
return null;
var torrents = new List<TorrentDetails>();
foreach (var row in nodes)
{
var hc = new HtmlCommon(row);
string url = hc.Match("href=\"/(torrent/[^\"]+)\"");
string title = hc.NodeValue(".//a[@class='url']");
title = Regex.Replace(title, "<[^>]+>", "");
string sizeName = hc.NodeValue(".//td[@align='right' and contains(text(), 'GB') or contains(text(), 'MB')]");
string downloadid = hc.Match("href=\"/?download/([0-9]+)\"");
string createTime = hc.NodeValue(".//td");
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(downloadid))
continue;
torrents.Add(new TorrentDetails()
{
url = $"{jackett.Megapeer.host}/{url}",
title = title,
sid = HtmlCommon.Integer(hc.NodeValue(".//font[@color='#008000']")),
pir = HtmlCommon.Integer(hc.NodeValue(".//font[@color='#8b0000']")),
sizeName = sizeName,
parselink = $"{host}/megapeer/parsemagnet?id={downloadid}",
createTime = tParse.ParseCreateTime(createTime, "dd.MM.yy")
});
}
return torrents;
}
#endregion
}
}

View File

@ -0,0 +1,279 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using System.Text;
namespace JacRed.Controllers
{
[Route("nnmclub/[action]")]
public class NNMClubController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query, string[] cats)
{
if (!jackett.NNMClub.enable || jackett.NNMClub.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query, cats));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string id)
{
if (!jackett.NNMClub.enable)
return Content("disable");
var proxyManager = new ProxyManager("nnmclub", jackett.NNMClub);
#region html
string html = await Http.Get($"{jackett.NNMClub.host}/forum/viewtopic.php?t=" + id, proxy: proxyManager.Get());
string magnet = new Regex("href=\"(magnet:[^\"]+)\" title=\"Примагнититься\"").Match(html ?? string.Empty).Groups[1].Value;
if (html == null)
{
proxyManager.Refresh();
return Content("error");
}
#endregion
#region download torrent
if (jackett.NNMClub.cookie != null || Cookie != null)
{
string downloadid = new Regex("href=\"download\\.php\\?id=([0-9]+)\"").Match(html).Groups[1].Value;
if (!string.IsNullOrWhiteSpace(downloadid))
{
byte[] _t = await Http.Download($"{jackett.NNMClub.host}/forum/download.php?id={downloadid}", proxy: proxyManager.Get(), cookie: jackett.NNMClub.cookie ?? Cookie, referer: jackett.NNMClub.host);
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
}
}
#endregion
if (string.IsNullOrEmpty(magnet))
{
proxyManager.Refresh();
return Content("error");
}
return Redirect(magnet);
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query, string[] cats)
{
var torrents = new List<TorrentDetails>();
var proxyManager = new ProxyManager("nnmclub", jackett.NNMClub);
#region html
string data = $"prev_sd=0&prev_a=0&prev_my=0&prev_n=0&prev_shc=0&prev_shf=1&prev_sha=1&prev_shs=0&prev_shr=0&prev_sht=0&o=1&s=2&tm=-1&shf=1&sha=1&ta=-1&sns=-1&sds=-1&nm={HttpUtility.UrlEncode(query, Encoding.GetEncoding(1251))}&pn=&submit=%CF%EE%E8%F1%EA";
string html = await Http.Post($"{jackett.NNMClub.host}/forum/tracker.php", new System.Net.Http.StringContent(data, Encoding.UTF8, "application/x-www-form-urlencoded"), encoding: Encoding.GetEncoding(1251), proxy: proxyManager.Get(), timeoutSeconds: jackett.timeoutSeconds);
if (html != null && html.Contains("NNM-Club</title>"))
{
if (!html.Contains(">Выход") && !string.IsNullOrWhiteSpace(jackett.NNMClub.login.u) && !string.IsNullOrWhiteSpace(jackett.NNMClub.login.p))
TakeLogin();
}
else if (html == null)
{
consoleErrorLog("nnmclub");
proxyManager.Refresh();
return null;
}
#endregion
foreach (string row in html.Split("</tr>"))
{
#region Локальный метод - Match
string Match(string pattern, int index = 1)
{
string res = HttpUtility.HtmlDecode(new Regex(pattern, RegexOptions.IgnoreCase).Match(row).Groups[index].Value.Trim());
res = Regex.Replace(res, "[\n\r\t ]+", " ");
return res.Trim();
}
#endregion
#region Данные раздачи
string url = Match("href=\"(viewtopic.php\\?t=[0-9]+)\"");
string viewtopic = Match("href=\"viewtopic.php\\?t=([0-9]+)\"");
string tracker = Match("class=\"gen\" href=\"tracker.php\\?f=([0-9]+)");
string title = Match("class=\"genmed topictitle\" [^>]+><b>([^<]+)</b>");
string _sid = Match("class=\"seedmed\"><b>([0-9]+)</b><");
string _pir = Match("class=\"leechmed\"><b>([0-9]+)</b></td>");
string sizeName = Match("class=\"gensmall\"><u>[^<]+</u> ([^<]+)</td>");
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(viewtopic) || string.IsNullOrWhiteSpace(tracker))
continue;
if (tracker == "913" && !title.Contains("UKR"))
continue;
#endregion
#region types
string[] types = null;
switch (tracker)
{
case "270":
case "221":
case "882":
case "225":
case "227":
case "913":
case "218":
case "954":
case "1293":
case "1296":
case "1299":
case "682":
case "884":
case "693":
types = new string[] { "movie" };
break;
case "769":
case "768":
types = new string[] { "serial" };
break;
case "713":
case "576":
case "610":
types = new string[] { "docuserial", "documovie" };
break;
case "731":
case "733":
case "1329":
case "1330":
case "1331":
case "1332":
case "1336":
case "1337":
case "1338":
case "1339":
types = new string[] { "multfilm" };
break;
case "658":
case "232":
types = new string[] { "multserial" };
break;
case "623":
case "622":
case "621":
case "632":
case "627":
case "626":
case "625":
case "644":
types = new string[] { "anime" };
break;
}
if (cats != null)
{
if (types == null)
continue;
bool isok = false;
foreach (string cat in cats)
{
if (types.Contains(cat))
isok = true;
}
if (!isok)
continue;
}
#endregion
int.TryParse(_sid, out int sid);
int.TryParse(_pir, out int pir);
torrents.Add(new TorrentDetails()
{
types = types,
url = $"{jackett.NNMClub.host}/{url}",
title = title,
sid = sid,
pir = pir,
sizeName = sizeName,
parselink = $"{host}/nnmclub/parsemagnet?id={viewtopic}",
createTime = tParse.ParseCreateTime(Match("title=\"Добавлено\" class=\"gensmall\"><u>[0-9]+</u> ([0-9]{2}-[0-9]{2}-[0-9]{4}<br>[^<]+)</td>").Replace("<br>", " "), "dd-MM-yyyy HH:mm")
});
}
return torrents;
}
#endregion
#region Cookie / TakeLogin
static string Cookie;
async static void TakeLogin()
{
string authKey = "nnmclub:TakeLogin()";
if (Startup.memoryCache.TryGetValue(authKey, out _))
return;
Startup.memoryCache.Set(authKey, 0, AppInit.conf.multiaccess ? TimeSpan.FromMinutes(2) : TimeSpan.FromSeconds(20));
try
{
using (var clientHandler = new System.Net.Http.HttpClientHandler()
{
AllowAutoRedirect = false
})
{
clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new System.Net.Http.HttpClient(clientHandler))
{
client.Timeout = TimeSpan.FromSeconds(jackett.timeoutSeconds);
client.MaxResponseContentBufferSize = 2000000; // 2MB
client.DefaultRequestHeaders.Add("origin", jackett.NNMClub.host);
client.DefaultRequestHeaders.Add("referer", $"{jackett.NNMClub.host}/");
client.DefaultRequestHeaders.Add("upgrade-insecure-requests", "1");
foreach (var h in Http.defaultFullHeaders)
client.DefaultRequestHeaders.TryAddWithoutValidation(h.Key, h.Value);
var postParams = new Dictionary<string, string>
{
{ "redirect", "%2F" },
{ "username", jackett.NNMClub.login.u },
{ "password", jackett.NNMClub.login.p },
{ "autologin", "on" },
{ "login", "%C2%F5%EE%E4" }
};
using (var postContent = new System.Net.Http.FormUrlEncodedContent(postParams))
{
using (var response = await client.PostAsync($"{jackett.NNMClub.host}/forum/login.php", postContent))
{
if (response.Headers.TryGetValues("Set-Cookie", out var cook))
{
string data = null, sid = null;
foreach (string line in cook)
{
if (string.IsNullOrWhiteSpace(line))
continue;
if (line.Contains("phpbb2mysql_4_data="))
data = new Regex("phpbb2mysql_4_data=([^;]+)(;|$)").Match(line).Groups[1].Value;
if (line.Contains("phpbb2mysql_4_sid="))
sid = new Regex("phpbb2mysql_4_sid=([^;]+)(;|$)").Match(line).Groups[1].Value;
}
if (!string.IsNullOrWhiteSpace(data) && !string.IsNullOrWhiteSpace(sid))
Cookie = $"phpbb2mysql_4_data={data}; phpbb2mysql_4_sid={sid};";
}
}
}
}
}
}
catch { }
}
#endregion
}
}

View File

@ -0,0 +1,103 @@
using Microsoft.AspNetCore.Mvc;
namespace JacRed.Controllers
{
[Route("rutor/[action]")]
public class RutorController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query, string cat, bool isua = false, string parsecat = null)
{
if (!jackett.Rutor.enable || jackett.Rutor.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query, cat, isua, parsecat));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(int id, string magnet)
{
if (!jackett.Rutor.enable || jackett.Rutor.priority != "torrent")
return Content("disable");
var proxyManager = new ProxyManager("rutor", jackett.Rutor);
byte[] _t = await Http.Download($"{Regex.Replace(jackett.Rutor.host, "^(https?:)//", "$1//d.")}/download/{id}", referer: jackett.Rutor.host, proxy: proxyManager.Get());
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
proxyManager.Refresh();
if (string.IsNullOrEmpty(magnet))
return Content("empty");
return Redirect(magnet);
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query, string cat, bool isua, string parsecat)
{
// fix search
query = query.Replace("\"", " ").Replace("'", " ").Replace("?", " ").Replace("&", " ");
var proxyManager = new ProxyManager("rutor", jackett.Rutor);
string html = await Http.Get($"{jackett.Rutor.host}/search" + (cat == "0" ? $"/{HttpUtility.UrlEncode(query)}" : $"/0/{cat}/000/0/{HttpUtility.UrlEncode(query)}"), proxy: proxyManager.Get(), timeoutSeconds: jackett.timeoutSeconds);
if (html == null || !html.Contains("id=\"logo\""))
{
consoleErrorLog("rutor");
proxyManager.Refresh();
return null;
}
var doc = new HtmlDocument();
doc.LoadHtml(html.Replace("&nbsp;", " ").Replace(" ", " ")); // Меняем непонятный символ похожий на проблел, на обычный проблел
var nodes = doc.DocumentNode.SelectNodes("//tr[@class='gai' or @class='tum']");
if (nodes == null || nodes.Count == 0)
return null;
var torrents = new List<TorrentDetails>();
foreach (var row in nodes)
{
var hc = new HtmlCommon(row);
string url = hc.Match("href=\"/(torrent/[^\"]+)\"");
string viewtopic = Regex.Match(url, "torrent/([0-9]+)").Groups[1].Value;
string title = hc.NodeValue(".//a[contains(@href, '/torrent/')]");
string sid = hc.NodeValue(".//span[@class='green']", removeChild: ".//img");
string pir = hc.NodeValue(".//span[@class='red']");
string sizeName = hc.NodeValue(".//td[@align='right' and contains(text(), 'GB') or contains(text(), 'MB')]");
string createTime = hc.NodeValue(".//td");
string magnet = hc.Match("href=\"(magnet:\\?xt=[^\"]+)\"");
if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(magnet) || title.ToLower().Contains("трейлер"))
continue;
if (isua && !title.Contains(" UKR"))
continue;
torrents.Add(new TorrentDetails()
{
url = $"{jackett.Rutor.host}/{url}",
title = title,
sid = HtmlCommon.Integer(sid),
pir = HtmlCommon.Integer(pir),
sizeName = sizeName,
magnet = jackett.Rutor.priority == "torrent" ? null : magnet,
parselink = jackett.Rutor.priority == "torrent" ? $"{host}/rutor/parsemagnet?id={viewtopic}&magnet={HttpUtility.UrlEncode(magnet)}" : null,
createTime = tParse.ParseCreateTime(createTime, "dd.MM.yy")
});
}
return torrents;
}
#endregion
}
}

View File

@ -0,0 +1,450 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
namespace JacRed.Controllers
{
[Route("rutracker/[action]")]
public class RutrackerController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query, string[] cats)
{
if (!jackett.Rutracker.enable || string.IsNullOrEmpty(jackett.Rutracker.cookie ?? jackett.Rutracker.login.u) || jackett.Rutracker.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query, cats));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string id)
{
if (!jackett.Rutracker.enable)
return Content("disable");
string cookie = await getCookie();
if (string.IsNullOrEmpty(cookie))
return Content("cookie == null");
var proxyManager = new ProxyManager("rutracker", jackett.Rutracker);
#region Download
if (jackett.Rutracker.priority == "torrent")
{
var _t = await Http.Download($"{jackett.Rutracker.host}/forum/dl.php?t={id}", proxy: proxyManager.Get(), cookie: cookie, referer: jackett.Rutracker.host);
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
}
#endregion
#region Magnet
var fullNews = await Http.Get($"{jackett.Rutracker.host}/forum/viewtopic.php?t=" + id, proxy: proxyManager.Get(), cookie: cookie);
if (fullNews != null)
{
string magnet = Regex.Match(fullNews, "href=\"(magnet:[^\"]+)\" class=\"(med )?med magnet-link\"").Groups[1].Value;
if (!string.IsNullOrWhiteSpace(magnet))
return Redirect(magnet);
}
#endregion
return Content("error");
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query, string[] cats)
{
var torrents = new List<TorrentDetails>();
var proxyManager = new ProxyManager("rutracker", jackett.Rutracker);
#region Авторизация
string cookie = await getCookie();
if (string.IsNullOrEmpty(cookie))
{
consoleErrorLog("rutracker");
return null;
}
#endregion
#region Кеш html
string html = await Http.Get($"{jackett.Rutracker.host}/forum/tracker.php?nm=" + HttpUtility.UrlEncode(query), proxy: proxyManager.Get(), cookie: cookie, timeoutSeconds: jackett.timeoutSeconds);
if (html != null)
{
if (!html.Contains("id=\"logged-in-username\""))
{
consoleErrorLog("rutracker");
return null;
}
}
#endregion
foreach (string row in html.Split("class=\"tCenter hl-tr\"").Skip(1))
{
if (string.IsNullOrWhiteSpace(row))
continue;
#region Локальный метод - Match
string Match(string pattern, int index = 1)
{
string res = HttpUtility.HtmlDecode(new Regex(pattern, RegexOptions.IgnoreCase).Match(row).Groups[index].Value.Trim());
res = Regex.Replace(res, "[\n\r\t ]+", " ");
return res.Trim();
}
#endregion
#region Данные раздачи
string title = Match("href=\"viewtopic.php\\?t=[0-9]+\">([^\n\r]+)</a>");
title = Regex.Replace(title, "<[^>]+>", "");
DateTime createTime = tParse.ParseCreateTime(Match("<p>([0-9]{2}-[^-<]+-[0-9]{2})</p>").Replace("-", " "), "dd.MM.yy");
string viewtopic = Match("href=\"viewtopic.php\\?t=([0-9]+)\"");
string tracker = Match("href=\"tracker.php\\?f=([0-9]+)");
string _sid = Match("class=\"seedmed\">([0-9]+)");
string _pir = Match("title=\"Личи\">([0-9]+)");
string sizeName = Match("href=\"dl.php\\?t=[0-9]+\">([^<]+) &#8595;</a>").Replace("&nbsp;", " ");
if (string.IsNullOrWhiteSpace(viewtopic) || string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(tracker))
continue;
#endregion
#region types
string[] types = null;
switch (tracker)
{
case "22":
case "1666":
case "941":
case "1950":
case "2090":
case "2221":
case "2091":
case "2092":
case "2093":
case "2200":
case "2540":
case "934":
case "505":
case "124":
case "1457":
case "2199":
case "313":
case "312":
case "1247":
case "2201":
case "2339":
case "140":
case "252":
case "2198":
types = new string[] { "movie" };
break;
case "2343":
case "930":
case "2365":
case "208":
case "539":
case "209":
types = new string[] { "multfilm" };
break;
case "921":
case "815":
case "1460":
types = new string[] { "multserial" };
break;
case "842":
case "235":
case "242":
case "819":
case "1531":
case "721":
case "1102":
case "1120":
case "1214":
case "489":
case "387":
case "9":
case "81":
case "119":
case "1803":
case "266":
case "193":
case "1690":
case "1459":
case "825":
case "1248":
case "1288":
case "325":
case "534":
case "694":
case "704":
case "915":
case "1939":
types = new string[] { "serial" };
break;
case "1105":
case "2491":
case "1389":
types = new string[] { "anime" };
break;
case "709":
types = new string[] { "documovie" };
break;
case "46":
case "671":
case "2177":
case "2538":
case "251":
case "98":
case "97":
case "851":
case "2178":
case "821":
case "2076":
case "56":
case "2123":
case "876":
case "2139":
case "1467":
case "1469":
case "249":
case "552":
case "500":
case "2112":
case "1327":
case "1468":
case "2168":
case "2160":
case "314":
case "1281":
case "2110":
case "979":
case "2169":
case "2164":
case "2166":
case "2163":
types = new string[] { "docuserial", "documovie" };
break;
case "24":
case "1959":
case "939":
case "1481":
case "113":
case "115":
case "882":
case "1482":
case "393":
case "2537":
case "532":
case "827":
types = new string[] { "tvshow" };
break;
case "2103":
case "2522":
case "2485":
case "2486":
case "2479":
case "2089":
case "1794":
case "845":
case "2312":
case "343":
case "2111":
case "1527":
case "2069":
case "1323":
case "2009":
case "2000":
case "2010":
case "2006":
case "2007":
case "2005":
case "259":
case "2004":
case "1999":
case "2001":
case "2002":
case "283":
case "1997":
case "2003":
case "1608":
case "1609":
case "2294":
case "1229":
case "1693":
case "2532":
case "136":
case "592":
case "2533":
case "1952":
case "1621":
case "2075":
case "1668":
case "1613":
case "1614":
case "1623":
case "1615":
case "1630":
case "2425":
case "2514":
case "1616":
case "2014":
case "1442":
case "1491":
case "1987":
case "1617":
case "1620":
case "1998":
case "1343":
case "751":
case "1697":
case "255":
case "260":
case "261":
case "256":
case "1986":
case "660":
case "1551":
case "626":
case "262":
case "1326":
case "978":
case "1287":
case "1188":
case "1667":
case "1675":
case "257":
case "875":
case "263":
case "2073":
case "550":
case "2124":
case "1470":
case "528":
case "486":
case "854":
case "2079":
case "1336":
case "2171":
case "1339":
case "2455":
case "1434":
case "2350":
case "1472":
case "2068":
case "2016":
types = new string[] { "sport" };
break;
}
if (cats != null)
{
if (types == null)
continue;
bool isok = false;
foreach (string cat in cats)
{
if (types.Contains(cat))
isok = true;
}
if (!isok)
continue;
}
#endregion
int.TryParse(_sid, out int sid);
int.TryParse(_pir, out int pir);
torrents.Add(new TorrentDetails()
{
types = types,
url = $"{jackett.Rutracker.host}/forum/viewtopic.php?t={viewtopic}",
title = title,
sid = sid,
pir = pir,
sizeName = sizeName,
createTime = createTime,
parselink = $"{host}/rutracker/parsemagnet?id={viewtopic}"
});
}
return torrents;
}
#endregion
#region getCookie
async static ValueTask<string> getCookie()
{
if (!string.IsNullOrEmpty(jackett.Rutracker.cookie))
return jackett.Rutracker.cookie;
string authKey = "Rutracker:TakeLogin()";
if (Startup.memoryCache.TryGetValue(authKey, out string _cookie))
return _cookie;
if (Startup.memoryCache.TryGetValue($"{authKey}:error", out _))
return null;
Startup.memoryCache.Set($"{authKey}:error", 0, TimeSpan.FromSeconds(20));
try
{
using (var clientHandler = new System.Net.Http.HttpClientHandler()
{
AllowAutoRedirect = false
})
{
clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new System.Net.Http.HttpClient(clientHandler))
{
client.Timeout = TimeSpan.FromSeconds(jackett.timeoutSeconds);
client.MaxResponseContentBufferSize = 2000000; // 2MB
foreach (var h in Http.defaultFullHeaders)
client.DefaultRequestHeaders.TryAddWithoutValidation(h.Key, h.Value);
var postParams = new Dictionary<string, string>
{
{ "login_username", jackett.Rutracker.login.u },
{ "login_password", jackett.Rutracker.login.p },
{ "login", "Вход" }
};
using (var postContent = new System.Net.Http.FormUrlEncodedContent(postParams))
{
using (var response = await client.PostAsync($"{jackett.Rutracker.host}/forum/login.php", postContent))
{
if (response.Headers.TryGetValues("Set-Cookie", out var cook))
{
string session = null;
foreach (string line in cook)
{
if (string.IsNullOrWhiteSpace(line))
continue;
if (line.Contains("bb_session="))
session = new Regex("bb_session=([^;]+)(;|$)").Match(line).Groups[1].Value;
}
if (!string.IsNullOrWhiteSpace(session))
{
string cookie = $"bb_ssl=1; bb_session={session};";
Startup.memoryCache.Set(authKey, cookie, DateTime.Today.AddDays(1));
return cookie;
}
}
}
}
}
}
}
catch { }
return null;
}
#endregion
}
}

View File

@ -0,0 +1,215 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
namespace JacRed.Controllers
{
[Route("selezen/[action]")]
public class SelezenController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query)
{
if (!jackett.Selezen.enable || string.IsNullOrEmpty(jackett.Selezen.cookie ?? jackett.Selezen.login.u) || jackett.Selezen.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string url)
{
if (!jackett.Selezen.enable)
return Content("disable");
string cookie = await getCookie();
if (string.IsNullOrEmpty(cookie))
return Content("cookie == null");
var proxyManager = new ProxyManager("selezen", jackett.Selezen);
string html = await Http.Get(url, cookie: cookie, proxy: proxyManager.Get());
string magnet = new Regex("href=\"(magnet:[^\"]+)\"").Match(html ?? string.Empty).Groups[1].Value;
if (html == null)
return Content("error");
#region Download
if (jackett.Selezen.priority == "torrent")
{
string id = new Regex("href=\"/index.php\\?do=download&id=([0-9]+)").Match(html).Groups[1].Value;
if (!string.IsNullOrWhiteSpace(id))
{
var _t = await Http.Download($"{jackett.Selezen.host}/index.php?do=download&id={id}", cookie: cookie, referer: jackett.Selezen.host, timeoutSeconds: 10);
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
}
}
#endregion
if (string.IsNullOrWhiteSpace(magnet))
return Content("error");
return Redirect(magnet);
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query)
{
#region Авторизация
string cookie = await getCookie();
if (string.IsNullOrEmpty(cookie))
{
consoleErrorLog("selezen");
return null;
}
#endregion
#region html
var proxyManager = new ProxyManager("selezen", jackett.Selezen);
string html = await Http.Post($"{jackett.Selezen.host}/index.php?do=search", $"do=search&subaction=search&search_start=0&full_search=0&result_from=1&story={HttpUtility.UrlEncode(query)}&titleonly=0&searchuser=&replyless=0&replylimit=0&searchdate=0&beforeafter=after&sortby=date&resorder=desc&showposts=0&catlist%5B%5D=9", proxy: proxyManager.Get(), cookie: cookie, timeoutSeconds: jackett.timeoutSeconds);
if (html != null && html.Contains("dle_root"))
{
if (!html.Contains($">{jackett.Selezen.login.u}<"))
{
consoleErrorLog("selezen");
return null;
}
}
#endregion
var torrents = new List<TorrentDetails>();
foreach (string row in html.Split("class=\"card radius-10 overflow-hidden\"").Skip(1))
{
if (string.IsNullOrWhiteSpace(row) || row.Contains(">Аниме</a>") || row.Contains(" [S0"))
continue;
#region Локальный метод - Match
string Match(string pattern, int index = 1)
{
string res = HttpUtility.HtmlDecode(new Regex(pattern, RegexOptions.IgnoreCase).Match(row).Groups[index].Value.Trim());
res = Regex.Replace(res, "[\n\r\t ]+", " ");
return res.Trim();
}
#endregion
#region Данные раздачи
var g = Regex.Match(row, "<a href=\"(https?://[^<]+)\"><h4 class=\"card-title\">([^<]+)</h4>").Groups;
string url = g[1].Value;
string title = g[2].Value;
string _sid = Match("<i class=\"bx bx-chevrons-up\"></i>([0-9 ]+)").Trim();
string _pir = Match("<i class=\"bx bx-chevrons-down\"></i>([0-9 ]+)").Trim();
string sizeName = Match("<span class=\"bx bx-download\"></span>([^<]+)</a>").Trim();
DateTime createTime = tParse.ParseCreateTime(Match("class=\"bx bx-calendar\"></span> ?([0-9]{2}\\.[0-9]{2}\\.[0-9]{4} [0-9]{2}:[0-9]{2})</a>"), "dd.MM.yyyy HH:mm");
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(url))
continue;
#endregion
#region types
string[] types = new string[] { "movie" };
if (row.Contains(">Мульт") || row.Contains(">мульт"))
types = new string[] { "multfilm" };
#endregion
int.TryParse(_sid, out int sid);
int.TryParse(_pir, out int pir);
torrents.Add(new TorrentDetails()
{
types = types,
url = url,
title = title,
sid = sid,
pir = pir,
sizeName = sizeName,
createTime = createTime,
parselink = $"{host}/selezen/parsemagnet?url={HttpUtility.UrlEncode(url)}"
});
}
return torrents;
}
#endregion
#region getCookie
async static ValueTask<string> getCookie()
{
if (!string.IsNullOrEmpty(jackett.Selezen.cookie))
return jackett.Selezen.cookie;
string authKey = "selezen:TakeLogin()";
if (Startup.memoryCache.TryGetValue(authKey, out string _cookie))
return _cookie;
if (Startup.memoryCache.TryGetValue($"{authKey}:error", out _))
return null;
Startup.memoryCache.Set($"{authKey}:error", 0, TimeSpan.FromSeconds(20));
try
{
using (var clientHandler = new System.Net.Http.HttpClientHandler()
{
AllowAutoRedirect = false
})
{
clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new System.Net.Http.HttpClient(clientHandler))
{
client.Timeout = TimeSpan.FromSeconds(jackett.timeoutSeconds);
client.MaxResponseContentBufferSize = 2000000; // 2MB
foreach (var h in Http.defaultFullHeaders)
client.DefaultRequestHeaders.TryAddWithoutValidation(h.Key, h.Value);
var postParams = new Dictionary<string, string>
{
{ "login_name", jackett.Selezen.login.u },
{ "login_password", jackett.Selezen.login.p },
{ "login_not_save", "1" },
{ "login", "submit" }
};
using (var postContent = new System.Net.Http.FormUrlEncodedContent(postParams))
{
using (var response = await client.PostAsync(jackett.Selezen.host, postContent))
{
if (response.Headers.TryGetValues("Set-Cookie", out var cook))
{
string PHPSESSID = null;
foreach (string line in cook)
{
if (string.IsNullOrWhiteSpace(line))
continue;
if (line.Contains("PHPSESSID="))
PHPSESSID = new Regex("PHPSESSID=([^;]+)(;|$)").Match(line).Groups[1].Value;
}
if (!string.IsNullOrWhiteSpace(PHPSESSID))
{
string cookie = $"PHPSESSID={PHPSESSID}; _ym_isad=2;";
Startup.memoryCache.Set(authKey, cookie, DateTime.Today.AddDays(1));
return cookie;
}
}
}
}
}
}
}
catch { }
return null;
}
#endregion
}
}

View File

@ -0,0 +1,399 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
namespace JacRed.Controllers
{
[Route("toloka/[action]")]
public class TolokaController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query, string[] cats)
{
if (!jackett.Toloka.enable || string.IsNullOrEmpty(jackett.Toloka.cookie ?? jackett.Toloka.login.u) || jackett.Toloka.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query, cats));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(string id)
{
if (!jackett.Toloka.enable)
return Content("disable");
string cookie = await getCookie();
if (string.IsNullOrEmpty(cookie))
return Content("cookie == null");
var proxyManager = new ProxyManager("toloka", jackett.Toloka);
byte[] _t = await Http.Download($"{jackett.Toloka.host}/download.php?id={id}", proxy: proxyManager.Get(), cookie: cookie, referer: jackett.Toloka.host);
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
return Content("error");
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query, string[] cats)
{
#region Авторизация
string cookie = await getCookie();
if (string.IsNullOrEmpty(cookie))
{
consoleErrorLog("toloka");
return null;
}
#endregion
#region html
var proxyManager = new ProxyManager("toloka", jackett.Toloka);
string html = await Http.Get($"{jackett.Toloka.host}/tracker.php?prev_sd=0&prev_a=0&prev_my=0&prev_n=0&prev_shc=0&prev_shf=1&prev_sha=1&prev_cg=0&prev_ct=0&prev_at=0&prev_nt=0&prev_de=0&prev_nd=0&prev_tcs=1&prev_shs=0&f%5B%5D=-1&o=1&s=2&tm=-1&shf=1&sha=1&tcs=1&sns=-1&sds=-1&nm={HttpUtility.UrlEncode(query)}&pn=&send=%D0%9F%D0%BE%D1%88%D1%83%D0%BA", proxy: proxyManager.Get(), cookie: cookie, timeoutSeconds: jackett.timeoutSeconds);
if (html != null && html.Contains("<html lang=\"uk\""))
{
if (!html.Contains(">Вихід"))
{
consoleErrorLog("toloka");
return null;
}
}
#endregion
var torrents = new List<TorrentDetails>();
foreach (string row in html.Split("</tr>"))
{
if (string.IsNullOrWhiteSpace(row) || Regex.IsMatch(row, "Збір коштів", RegexOptions.IgnoreCase))
continue;
#region Локальный метод - Match
string Match(string pattern, int index = 1)
{
string res = HttpUtility.HtmlDecode(new Regex(pattern, RegexOptions.IgnoreCase).Match(row).Groups[index].Value.Trim());
res = Regex.Replace(res, "[\n\r\t ]+", " ");
return res.Trim();
}
#endregion
#region Дата создания
string _createTime = Match("class=\"gensmall\">([0-9]{4}-[0-9]{2}-[0-9]{2})").Replace("-", ".");
DateTime.TryParse(_createTime, out DateTime createTime);
#endregion
#region Данные раздачи
string url = Match("class=\"topictitle genmed\"><a class=\"[^\"]+\" href=\"(t[0-9]+)\"");
string title = Match("class=\"topictitle genmed\"><a [^>]+><b>([^<]+)</b></a>");
string downloadid = Match("href=\"download.php\\?id=([0-9]+)\"");
string tracker = Match("class=\"gen\" href=\"tracker.php\\?f=([0-9]+)");
string _sid = Match("class=\"seedmed\"><b>([0-9]+)");
string _pir = Match("class=\"leechmed\"><b>([0-9]+)");
string sizeName = Match("class=\"gensmall\">([0-9\\.]+ (MB|GB))</td>");
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(downloadid) || string.IsNullOrWhiteSpace(tracker) || sizeName == "0 B")
continue;
#endregion
#region Парсим раздачи
int relased = 0;
string name = null, originalname = null;
if (tracker is "16" or "96" or "19" or "139" or "12" or "131" or "84" or "42")
{
#region Фильмы
// Незворотність / Irréversible / Irreversible (2002) AVC Ukr/Fre | Sub Eng
var g = Regex.Match(title, "^([^/\\(\\[]+)/[^/\\(\\[]+/([^/\\(\\[]+) \\(([0-9]{4})(\\)|-)").Groups;
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[2].Value) && !string.IsNullOrWhiteSpace(g[3].Value))
{
name = g[1].Value.Trim();
originalname = g[2].Value.Trim();
if (int.TryParse(g[3].Value, out int _yer))
relased = _yer;
}
else
{
// Мій рік у Нью-Йорку / My Salinger Year (2020) Ukr/Eng
g = Regex.Match(title, "^([^/\\(\\[]+)/([^/\\(\\[]+) \\(([0-9]{4})(\\)|-)").Groups;
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[2].Value) && !string.IsNullOrWhiteSpace(g[3].Value))
{
name = g[1].Value.Trim();
originalname = g[2].Value.Trim();
if (int.TryParse(g[3].Value, out int _yer))
relased = _yer;
}
else
{
// Хроніка надій та ілюзій. Дзеркало історії. (83 серії) (2001-2003) PDTVRip
g = Regex.Match(title, "^([^/\\(\\[]+) \\([^\\)]+\\) \\(([0-9]{4})(\\)|-)").Groups;
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[2].Value))
{
name = g[1].Value;
if (int.TryParse(g[2].Value, out int _yer))
relased = _yer;
}
else
{
// Берестечко. Битва за Україну (2015-2016) DVDRip-AVC
g = Regex.Match(title, "^([^/\\(\\[]+) \\(([0-9]{4})(\\)|-)").Groups;
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[2].Value))
{
name = g[1].Value;
if (int.TryParse(g[2].Value, out int _yer))
relased = _yer;
}
}
}
}
#endregion
}
else if (tracker is "32" or "173" or "174" or "44" or "230" or "226" or "227" or "228" or "229" or "127" or "124" or "125" or "132")
{
#region Сериалы
// Атака титанів (Attack on Titan) (Сезон 1) / Shingeki no Kyojin (Season 1) (2013) BDRip 720р
var g = Regex.Match(title, "^([^/\\(\\[]+) \\([^\\)]+\\) \\([^\\)]+\\) ?/([^/\\(\\[]+) \\([^\\)]+\\) \\(([0-9]{4})(\\)|-)").Groups;
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[2].Value) && !string.IsNullOrWhiteSpace(g[3].Value))
{
name = g[1].Value.Trim();
originalname = g[2].Value.Trim();
if (int.TryParse(g[3].Value, out int _yer))
relased = _yer;
}
else
{
// Дім з прислугою (Сезон 2, серії 1-8) / Servant (Season 2, episodes 1-8) (2021) WEB-DLRip-AVC Ukr/Eng
g = Regex.Match(title, "^([^/\\(\\[]+) \\([^\\)]+\\) ?/([^/\\(\\[]+) \\([^\\)]+\\) \\(([0-9]{4})(\\)|-)").Groups;
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[2].Value) && !string.IsNullOrWhiteSpace(g[3].Value))
{
name = g[1].Value.Trim();
originalname = g[2].Value.Trim();
if (int.TryParse(g[3].Value, out int _yer))
relased = _yer;
}
else
{
// Детективне агентство прекрасних хлопчиків (08 з 12) / Bishounen Tanteidan (2021) BDRip 1080p Ukr/Jap | Ukr Sub
g = Regex.Match(title, "^([^/\\(\\[]+) (\\(|\\[)[^\\)\\]]+(\\)|\\]) ?/([^/\\(\\[]+) \\(([0-9]{4})(\\)|-)").Groups;
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[4].Value) && !string.IsNullOrWhiteSpace(g[5].Value))
{
name = g[1].Value.Trim();
originalname = g[4].Value.Trim();
if (int.TryParse(g[5].Value, out int _yer))
relased = _yer;
}
else
{
// Яйця Дракона / Dragon Ball (01-31 з 153) (1986-1989) BDRip 1080p H.265
// Томо — дівчина! / Tomo-chan wa Onnanoko! (Сезон 1, серії 01-02 з 13) (2023) WEBDL 1080p H.265 Ukr/Jap | sub Ukr
g = Regex.Match(title, "^([^/\\(\\[]+)/([^/\\(\\[]+) \\([^\\)]+\\) \\(([0-9]{4})(\\)|-)").Groups;
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[2].Value) && !string.IsNullOrWhiteSpace(g[3].Value))
{
name = g[1].Value.Trim();
originalname = g[2].Value.Trim();
if (int.TryParse(g[3].Value, out int _yer))
relased = _yer;
}
else
{
// Людина-бензопила / チェンソーマン /Chainsaw Man (сезон 1, серії 8 з 12) (2022) WEBRip 1080p
g = Regex.Match(title, "^([^/\\(\\[]+)/[^/\\(\\[]+/([^/\\(\\[]+) \\([^\\)]+\\) \\(([0-9]{4})(\\)|-)").Groups;
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[2].Value) && !string.IsNullOrWhiteSpace(g[3].Value))
{
name = g[1].Value.Trim();
originalname = g[2].Value.Trim();
if (int.TryParse(g[3].Value, out int _yer))
relased = _yer;
}
else
{
// МастерШеф. 10 сезон (1-18 епізоди) (2020) IPTVRip 400p
g = Regex.Match(title, "^([^/\\(\\[]+) \\([^\\)]+\\) \\(([0-9]{4})(\\)|-)").Groups;
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[2].Value))
{
name = g[1].Value.Trim();
if (int.TryParse(g[2].Value, out int _yer))
relased = _yer;
}
}
}
}
}
}
#endregion
}
#endregion
#region types
string[] types = null;
switch (tracker)
{
case "16":
case "96":
case "42":
types = new string[] { "movie" };
break;
case "19":
case "139":
case "84":
types = new string[] { "multfilm" };
break;
case "32":
case "173":
case "124":
types = new string[] { "serial" };
break;
case "174":
case "44":
case "125":
types = new string[] { "multserial" };
break;
case "226":
case "227":
case "228":
case "229":
case "230":
case "12":
case "131":
types = new string[] { "docuserial", "documovie" };
break;
case "127":
types = new string[] { "anime" };
break;
case "132":
types = new string[] { "tvshow" };
break;
}
if (cats != null)
{
if (types == null)
continue;
bool isok = false;
foreach (string cat in cats)
{
if (types.Contains(cat))
isok = true;
}
if (!isok)
continue;
}
#endregion
int.TryParse(_sid, out int sid);
int.TryParse(_pir, out int pir);
torrents.Add(new TorrentDetails()
{
types = types,
url = $"{jackett.Toloka.host}/{url}",
title = title,
sid = sid,
pir = pir,
sizeName = sizeName,
createTime = createTime,
parselink = $"{host}/toloka/parsemagnet?id={downloadid}",
name = name,
originalname = originalname,
relased = relased
});
}
return torrents;
}
#endregion
#region getCookie
async static ValueTask<string> getCookie()
{
if (!string.IsNullOrEmpty(jackett.Toloka.cookie))
return jackett.Toloka.cookie;
string authKey = "Toloka:TakeLogin()";
if (Startup.memoryCache.TryGetValue(authKey, out string _cookie))
return _cookie;
if (Startup.memoryCache.TryGetValue($"{authKey}:error", out _))
return null;
Startup.memoryCache.Set($"{authKey}:error", 0, TimeSpan.FromSeconds(20));
try
{
using (var clientHandler = new System.Net.Http.HttpClientHandler()
{
AllowAutoRedirect = false
})
{
clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new System.Net.Http.HttpClient(clientHandler))
{
client.Timeout = TimeSpan.FromSeconds(jackett.timeoutSeconds);
client.MaxResponseContentBufferSize = 2000000; // 2MB
foreach (var h in Http.defaultFullHeaders)
client.DefaultRequestHeaders.TryAddWithoutValidation(h.Key, h.Value);
var postParams = new Dictionary<string, string>
{
{ "username", jackett.Toloka.login.u },
{ "password", jackett.Toloka.login.p },
{ "autologin", "on" },
{ "ssl", "on" },
{ "redirect", "index.php?" },
{ "login", "Вхід" }
};
using (var postContent = new System.Net.Http.FormUrlEncodedContent(postParams))
{
using (var response = await client.PostAsync($"{jackett.Toloka.host}/login.php", postContent))
{
if (response.Headers.TryGetValues("Set-Cookie", out var cook))
{
string toloka_sid = null, toloka_data = null;
foreach (string line in cook)
{
if (string.IsNullOrWhiteSpace(line))
continue;
if (line.Contains("toloka_sid="))
toloka_sid = new Regex("toloka_sid=([^;]+)(;|$)").Match(line).Groups[1].Value;
if (line.Contains("toloka_data="))
toloka_data = new Regex("toloka_data=([^;]+)(;|$)").Match(line).Groups[1].Value;
}
if (!string.IsNullOrWhiteSpace(toloka_sid) && !string.IsNullOrWhiteSpace(toloka_data))
{
string cookie = $"toloka_sid={toloka_sid}; toloka_ssl=1; toloka_data={toloka_data};";
Startup.memoryCache.Set(authKey, cookie, DateTime.Today.AddDays(1));
return cookie;
}
}
}
}
}
}
}
catch { }
return null;
}
#endregion
}
}

View File

@ -0,0 +1,115 @@
using Microsoft.AspNetCore.Mvc;
namespace JacRed.Controllers
{
[Route("torrentby/[action]")]
public class TorrentByController : JacBaseController
{
#region search
public static Task<bool> search(string host, ConcurrentBag<TorrentDetails> torrents, string query, string cat)
{
if (!jackett.TorrentBy.enable || jackett.TorrentBy.showdown)
return Task.FromResult(false);
return Joinparse(torrents, () => parsePage(host, query, cat));
}
#endregion
#region parseMagnet
async public Task<ActionResult> parseMagnet(int id, string magnet)
{
if (!jackett.TorrentBy.enable || jackett.TorrentBy.priority != "torrent")
return Content("disable");
var proxyManager = new ProxyManager("torrentby", jackett.TorrentBy);
var _t = await Http.Download($"{jackett.TorrentBy.host}/d.php?id={id}", referer: jackett.TorrentBy.host, proxy: proxyManager.Get());
if (_t != null && BencodeTo.Magnet(_t) != null)
return File(_t, "application/x-bittorrent");
proxyManager.Refresh();
if (string.IsNullOrEmpty(magnet))
return Content("empty");
return Redirect(magnet);
}
#endregion
#region parsePage
async static ValueTask<List<TorrentDetails>> parsePage(string host, string query, string cat)
{
#region html
var proxyManager = new ProxyManager("torrentby", jackett.TorrentBy);
string html = await Http.Get($"{jackett.TorrentBy.host}/search/?search={HttpUtility.UrlEncode(query)}&category={cat}", proxy: proxyManager.Get(), timeoutSeconds: jackett.timeoutSeconds);
if (html == null || !html.Contains("id=\"find\""))
{
consoleErrorLog("torrentby");
proxyManager.Refresh();
return null;
}
#endregion
var doc = new HtmlDocument();
doc.LoadHtml(html.Replace("&nbsp;", " "));
var nodes = doc.DocumentNode.SelectNodes("//tr[contains(@class, 'ttable_col')]");
if (nodes == null || nodes.Count == 0)
return null;
var torrents = new List<TorrentDetails>();
foreach (var row in nodes)
{
var hc = new HtmlCommon(row);
#region Дата создания
DateTime createTime = default;
if (row.InnerHtml.Contains("Сегодня"))
{
createTime = DateTime.Today;
}
else if (row.InnerHtml.Contains("Вчера"))
{
createTime = DateTime.Today.AddDays(-1);
}
else
{
string _createTime = hc.Match(">([0-9]{4}-[0-9]{2}-[0-9]{2})</td>").Replace("-", " ");
DateTime.TryParseExact(_createTime, "yyyy MM dd", new CultureInfo("ru-RU"), DateTimeStyles.None, out createTime);
}
#endregion
string url = hc.NodeValue(".//a[@name='search_select']", "href");
string viewtopic = Regex.Match(url, "^/([0-9]+)").Groups[1].Value;
string title = hc.NodeValue(".//a[@name='search_select']");
title = Regex.Replace(title, "<[^>]+>", "");
string magnet = hc.Match("href=\"(magnet:\\?xt=[^\"]+)\"");
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(magnet))
continue;
torrents.Add(new TorrentDetails()
{
url = $"{jackett.TorrentBy.host}/{url.Remove(0, 1)}",
title = title,
sid = HtmlCommon.Integer(hc.NodeValue(".//font[@color='green']")),
pir = HtmlCommon.Integer(hc.NodeValue(".//font[@color='red']")),
sizeName = hc.NodeValue(".//td[contains(text(), 'GB') or contains(text(), 'MB')]"),
magnet = jackett.TorrentBy.priority == "torrent" ? null : magnet,
parselink = jackett.TorrentBy.priority == "torrent" ? $"{host}/torrentby/parsemagnet?id={viewtopic}&magnet={HttpUtility.UrlEncode(magnet)}" : null,
createTime = createTime
});
}
return torrents;
}
#endregion
}
}

View File

@ -0,0 +1,39 @@
using Jackett;
using JacRed.Engine.CORE;
using JacRed.Models;
namespace JacRed.Engine
{
public partial class FileDB : IDisposable
{
string fdbkey;
public ConcurrentDictionary<string, TorrentDetails> Database = new ConcurrentDictionary<string, TorrentDetails>();
FileDB(string key, bool empty = false)
{
fdbkey = key;
string fdbpath = pathDb(key);
if (!empty && File.Exists(fdbpath))
Database = JsonStream.Read<ConcurrentDictionary<string, TorrentDetails>>(fdbpath) ?? new ConcurrentDictionary<string, TorrentDetails>();
}
public void Dispose()
{
if (Database.Count > 0)
JsonStream.Write(pathDb(fdbkey), Database);
if (openWriteTask.TryGetValue(fdbkey, out WriteTaskModel val))
{
val.openconnection -= 1;
if (0 >= val.openconnection)
{
if (!ModInit.conf.Red.evercache.enable || (ModInit.conf.Red.evercache.enable && ModInit.conf.Red.evercache.validHour > 0))
openWriteTask.TryRemove(fdbkey, out _);
}
}
}
}
}

View File

@ -0,0 +1,146 @@
using Jackett;
using JacRed.Engine.CORE;
using JacRed.Models;
namespace JacRed.Engine
{
public partial class FileDB : IDisposable
{
#region FileDB
/// <summary>
/// $"{search_name}:{search_originalname}"
/// Верхнее время изменения
/// </summary>
public static ConcurrentDictionary<string, DateTime> masterDb = new ConcurrentDictionary<string, DateTime>();
static ConcurrentDictionary<string, WriteTaskModel> openWriteTask = new ConcurrentDictionary<string, WriteTaskModel>();
static FileDB()
{
if (File.Exists("cache/jacred/masterDb.bz"))
masterDb = JsonStream.Read<ConcurrentDictionary<string, DateTime>>("cache/jacred/masterDb.bz");
if (masterDb == null)
{
if (File.Exists($"cache/jacred/masterDb_{DateTime.Today:dd-MM-yyyy}.bz"))
masterDb = JsonStream.Read<ConcurrentDictionary<string, DateTime>>($"cache/jacred/masterDb_{DateTime.Today:dd-MM-yyyy}.bz");
if (masterDb == null && File.Exists($"cache/jacred/masterDb_{DateTime.Today.AddDays(-1):dd-MM-yyyy}.bz"))
masterDb = JsonStream.Read<ConcurrentDictionary<string, DateTime>>($"cache/jacred/masterDb_{DateTime.Today.AddDays(-1):dd-MM-yyyy}.bz");
if (masterDb == null)
masterDb = new ConcurrentDictionary<string, DateTime>();
if (File.Exists("cache/jacred/lastsync.txt"))
File.Delete("cache/jacred/lastsync.txt");
}
}
#endregion
#region pathDb
static string pathDb(string key)
{
string md5key = CrypTo.md5(key);
Directory.CreateDirectory($"cache/jacred/fdb/{md5key.Substring(0, 2)}");
return $"cache/jacred/fdb/{md5key.Substring(0, 2)}/{md5key.Substring(2)}";
}
#endregion
#region Open
public static FileDB Open(string key, bool empty = false)
{
if (empty)
{
openWriteTask.TryRemove(key, out _);
return new FileDB(key, empty: empty);
}
if (openWriteTask.TryGetValue(key, out WriteTaskModel val))
{
val.countread++;
val.openconnection += 1;
val.lastread = DateTime.UtcNow;
return val.db;
}
else
{
var fdb = new FileDB(key);
openWriteTask.TryAdd(key, new WriteTaskModel() { db = fdb, openconnection = 1, countread = 1, lastread = DateTime.UtcNow });
return fdb;
}
}
#endregion
#region SaveChangesToFile
public static void SaveChangesToFile()
{
try
{
JsonStream.Write("cache/jacred/masterDb.bz", masterDb);
if (!File.Exists($"cache/jacred/masterDb_{DateTime.Today:dd-MM-yyyy}.bz"))
File.Copy("cache/jacred/masterDb.bz", $"cache/jacred/masterDb_{DateTime.Today:dd-MM-yyyy}.bz");
if (File.Exists($"cache/jacred/masterDb_{DateTime.Today.AddDays(-2):dd-MM-yyyy}.bz"))
File.Delete($"cache/jacred/masterDb_{DateTime.Today.AddDays(-2):dd-MM-yyyy}.bz");
}
catch { }
}
#endregion
#region Cron
async public static Task Cron()
{
while (true)
{
await Task.Delay(TimeSpan.FromMinutes(10));
if (!ModInit.conf.Red.evercache.enable || 0 >= ModInit.conf.Red.evercache.validHour)
continue;
try
{
var deleteKeys = openWriteTask
.Where(i => DateTime.UtcNow > i.Value.lastread.AddHours(ModInit.conf.Red.evercache.validHour))
.Select(i => i.Key)
.ToArray();
foreach (string key in deleteKeys)
openWriteTask.TryRemove(key, out _);
}
catch { }
}
}
async public static Task CronFast()
{
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(20));
if (!ModInit.conf.Red.evercache.enable || 0 >= ModInit.conf.Red.evercache.validHour)
continue;
try
{
if (openWriteTask.Count > ModInit.conf.Red.evercache.maxOpenWriteTask)
{
var deleteKeys = openWriteTask
.Where(i => DateTime.Now > i.Value.create.AddMinutes(10))
.OrderBy(i => i.Value.countread).ThenBy(i => i.Value.lastread)
.Take(ModInit.conf.Red.evercache.dropCacheTake)
.Select(i => i.Key)
.ToArray();
foreach (string key in deleteKeys)
openWriteTask.TryRemove(key, out _);
}
}
catch { }
}
}
#endregion
}
}

View File

@ -0,0 +1,33 @@
using Jackett;
using JacRed.Models.AppConf;
namespace JacRed.Engine
{
public class JacBaseController : BaseController
{
public static RedConf red => ModInit.conf.Red;
public static JacConf jackett => ModInit.conf.Jackett;
async public static Task<bool> Joinparse(ConcurrentBag<TorrentDetails> torrents, Func<ValueTask<List<TorrentDetails>>> parse)
{
var result = await parse();
if (result != null && result.Count > 0)
{
foreach (TorrentDetails torrent in result)
torrents.Add(torrent);
return true;
}
return false;
}
public static void consoleErrorLog(string plugin)
{
Console.WriteLine($"JacRed: InternalServerError - {plugin}");
}
}
}

286
JacRed/Engine/JackettApi.cs Normal file
View File

@ -0,0 +1,286 @@
using Jackett;
using JacRed.Controllers;
using JacRed.Models.AppConf;
using System.Reflection;
namespace JacRed.Engine
{
public static class JackettApi
{
static JacConf jackett => ModInit.conf.Jackett;
#region Indexers
async public static Task<List<TorrentDetails>> Indexers(string host, string query, string title, string title_original, int year, int is_serial, Dictionary<string, string> category)
{
var hybridCache = IHybridCache.Get(null);
string mkey = $"JackettApi:{query}:{title}:{year}:{is_serial}";
if (hybridCache.TryGetValue(mkey, out List<TorrentDetails> cache, inmemory: false))
return cache;
var torrents = new ConcurrentBag<TorrentDetails>();
#region search
string search = jackett.search_lang == "query" ? query : jackett.search_lang == "title" ? title : title_original;
if (string.IsNullOrWhiteSpace(search))
{
search = query ?? title ?? title_original;
if (string.IsNullOrWhiteSpace(search))
return torrents.ToList();
}
#endregion
#region category
if (category != null)
{
string cat = category.FirstOrDefault().Value;
if (cat != null)
{
if (cat.Contains("5020") || cat.Contains("2010"))
is_serial = 3; // tvshow
else if (cat.Contains("5080"))
is_serial = 4; // док
else if (cat.Contains("5070"))
is_serial = 5; // аниме
else if (is_serial == 0)
{
if (cat.StartsWith("20"))
is_serial = 1; // фильм
else if (cat.StartsWith("50"))
is_serial = 2; // сериал
}
}
}
#endregion
#region modpars
void modpars(List<Task> tasks, string cat)
{
if (AppInit.modules != null && AppInit.modules.Count > 0)
{
foreach (var item in AppInit.modules)
{
foreach (var mod in item.jac)
{
if (mod.enable)
{
try
{
if (item.assembly.GetType(mod.@namespace) is Type t && t.GetMethod("parsePage") is MethodInfo m)
{
var task = (Task)m.Invoke(null, new object[] { host, torrents, search, cat });
if (task != null)
tasks.Add(task);
}
}
catch { }
}
}
}
}
}
#endregion
#region Парсим торренты
if (is_serial == 1)
{
#region Фильм
var tasks = new List<Task>
{
RutorController.search(host, torrents, search, "1"), // movie
RutorController.search(host, torrents, search, "5"), // movie
RutorController.search(host, torrents, search, "7"), // multfilm
RutorController.search(host, torrents, search, "12"), // documovie
RutorController.search(host, torrents, search, "17", true, "1"), // UKR
MegapeerController.search(host, torrents, search, "79"), // Наши фильмы
MegapeerController.search(host, torrents, search, "80"), // Зарубежные фильмы
MegapeerController.search(host, torrents, search, "76"), // Мультипликация
TorrentByController.search(host, torrents, search, "1"), // movie
TorrentByController.search(host, torrents, search, "2"), // movie
TorrentByController.search(host, torrents, search, "5"), // multfilm
KinozalController.search(host, torrents, search, new string[] { "movie", "multfilm", "tvshow" }),
NNMClubController.search(host, torrents, search, new string[] { "movie", "multfilm", "documovie" }),
TolokaController.search(host, torrents, search, new string[] { "movie", "multfilm", "documovie" }),
RutrackerController.search(host, torrents, search, new string[] { "movie", "multfilm", "documovie" }),
BitruController.search(host, torrents, search, new string[] { "movie" }),
SelezenController.search(host, torrents, search),
BigFanGroup.search(host, torrents, search, new string[] { "movie", "multfilm", "documovie" })
};
modpars(tasks, "movie");
await Task.WhenAll(tasks);
#endregion
}
else if (is_serial == 2)
{
#region Сериал
var tasks = new List<Task>
{
RutorController.search(host, torrents, search, "4"), // serial
RutorController.search(host, torrents, search, "16"), // serial
RutorController.search(host, torrents, search, "7"), // multserial
RutorController.search(host, torrents, search, "12"), // docuserial
RutorController.search(host, torrents, search, "6"), // tvshow
RutorController.search(host, torrents, search, "17", true, "4"), // UKR
MegapeerController.search(host, torrents, search, "5"), // serial
MegapeerController.search(host, torrents, search, "6"), // serial
MegapeerController.search(host, torrents, search, "55"), // docuserial
MegapeerController.search(host, torrents, search, "57"), // tvshow
MegapeerController.search(host, torrents, search, "76"), // multserial
TorrentByController.search(host, torrents, search, "3"), // serial
TorrentByController.search(host, torrents, search, "5"), // multserial
TorrentByController.search(host, torrents, search, "4"), // tvshow
TorrentByController.search(host, torrents, search, "12"), // tvshow
KinozalController.search(host, torrents, search, new string[] { "serial", "multserial", "tvshow" }),
NNMClubController.search(host, torrents, search, new string[] { "serial", "multserial", "docuserial" }),
TolokaController.search(host, torrents, search, new string[] { "serial", "multserial", "docuserial" }),
RutrackerController.search(host, torrents, search, new string[] { "serial", "multserial", "docuserial" }),
BitruController.search(host, torrents, search, new string[] { "serial" }),
LostfilmController.search(host, torrents, search),
BigFanGroup.search(host, torrents, search, new string[] { "serial", "multserial", "docuserial", "tvshow" })
};
modpars(tasks, "serial");
await Task.WhenAll(tasks);
#endregion
}
else if (is_serial == 3)
{
#region tvshow
var tasks = new List<Task>
{
RutorController.search(host, torrents, search, "6"),
MegapeerController.search(host, torrents, search, "57"),
TorrentByController.search(host, torrents, search, "4"),
TorrentByController.search(host, torrents, search, "12"),
KinozalController.search(host, torrents, search, new string[] { "tvshow" }),
NNMClubController.search(host, torrents, search, new string[] { "docuserial", "documovie" }),
TolokaController.search(host, torrents, search, new string[] { "docuserial", "documovie" }),
RutrackerController.search(host, torrents, search, new string[] { "tvshow" }),
BigFanGroup.search(host, torrents, search, new string[] { "tvshow" })
};
modpars(tasks, "tvshow");
await Task.WhenAll(tasks);
#endregion
}
else if (is_serial == 4)
{
#region docuserial / documovie
var tasks = new List<Task>
{
RutorController.search(host, torrents, search, "12"),
MegapeerController.search(host, torrents, search, "55"),
NNMClubController.search(host, torrents, search, new string[] { "docuserial", "documovie" }),
TolokaController.search(host, torrents, search, new string[] { "docuserial", "documovie" }),
RutrackerController.search(host, torrents, search, new string[] { "docuserial", "documovie" }),
BigFanGroup.search(host, torrents, search, new string[] { "docuserial", "documovie" })
};
modpars(tasks, "documental");
await Task.WhenAll(tasks);
#endregion
}
else if (is_serial == 5)
{
#region anime
string animesearch = title ?? query;
var tasks = new List<Task>
{
RutorController.search(host, torrents, animesearch, "10"),
TorrentByController.search(host, torrents, animesearch, "6"),
KinozalController.search(host, torrents, animesearch, new string[] { "anime" }),
NNMClubController.search(host, torrents, animesearch, new string[] { "anime" }),
RutrackerController.search(host, torrents, animesearch, new string[] { "anime" }),
TolokaController.search(host, torrents, search, new string[] { "anime" }),
AniLibriaController.search(host, torrents, animesearch),
AnimeLayerController.search(host, torrents, animesearch),
AnifilmController.search(host, torrents, animesearch)
};
modpars(tasks, "anime");
await Task.WhenAll(tasks);
#endregion
}
else
{
#region Неизвестно
var tasks = new List<Task>
{
RutorController.search(host, torrents, search, "0"),
MegapeerController.search(host, torrents, search, "0"),
TorrentByController.search(host, torrents, search, "0"),
KinozalController.search(host, torrents, search, null),
NNMClubController.search(host, torrents, search, null),
BitruController.search(host, torrents, search, null),
RutrackerController.search(host, torrents, search, null),
TolokaController.search(host, torrents, search, null),
AniLibriaController.search(host, torrents, search),
AnimeLayerController.search(host, torrents, search),
AnifilmController.search(host, torrents, search),
SelezenController.search(host, torrents, search),
LostfilmController.search(host, torrents, search),
BigFanGroup.search(host, torrents, search, null)
};
modpars(tasks, "search");
await Task.WhenAll(tasks);
#endregion
}
#endregion
var hash = new HashSet<string>();
var finaly = new List<TorrentDetails>(torrents.Count);
foreach (var t in torrents)
{
if (t.trackerName == null)
t.trackerName = Regex.Match(t.url, "https?://([^/]+)").Groups[1].Value;
if (!string.IsNullOrEmpty(ModInit.conf.filter) && !Regex.IsMatch(t.title, ModInit.conf.filter, RegexOptions.IgnoreCase))
continue;
if (!string.IsNullOrEmpty(ModInit.conf.filter_ignore) && Regex.IsMatch(t.title, ModInit.conf.filter_ignore, RegexOptions.IgnoreCase))
continue;
if (!hash.Contains(t.url))
{
hash.Add(t.url);
finaly.Add(t);
}
}
var result = finaly.AsEnumerable();
if (is_serial == 1 && year > 0)
result = result.Where(i => i.title.Contains(year.ToString()) || i.title.Contains($"{year+1}") || i.title.Contains($"{year-1}"));
if (ModInit.conf.Jackett.cacheToMinutes > 0)
hybridCache.Set(mkey, result.ToList(), DateTime.Now.AddMinutes(ModInit.conf.Jackett.cacheToMinutes), inmemory: false);
return result.ToList();
}
#endregion
#region Api
public static Task<List<TorrentDetails>> Api(string host, string search)
{
return Indexers(host, search, null, null, 0, 0, null);
}
#endregion
}
}

View File

@ -0,0 +1,59 @@
using Newtonsoft.Json;
using System.IO.Compression;
namespace JacRed.Engine.CORE
{
public static class JsonStream
{
#region Read
public static T Read<T>(string path)
{
try
{
var settings = new JsonSerializerSettings
{
Error = (se, ev) => { ev.ErrorContext.Handled = true; }
};
var serializer = JsonSerializer.Create(settings);
using (Stream file = new GZipStream(File.OpenRead(path), CompressionMode.Decompress))
{
using (var sr = new StreamReader(file))
{
using (var jsonTextReader = new JsonTextReader(sr))
{
return serializer.Deserialize<T>(jsonTextReader);
}
}
}
}
catch { return default; }
}
#endregion
#region Write
public static void Write(string path, object db)
{
try
{
//var settings = new JsonSerializerSettings()
//{
// Formatting = Formatting.Indented
//};
var serializer = JsonSerializer.Create(); // settings
using (var sw = new StreamWriter(new GZipStream(File.OpenWrite(path), CompressionMode.Compress)))
{
using (var jsonTextWriter = new JsonTextWriter(sw))
{
serializer.Serialize(jsonTextWriter, db);
}
}
}
catch { }
}
#endregion
}
}

569
JacRed/Engine/RedApi.cs Normal file
View File

@ -0,0 +1,569 @@
using MonoTorrent;
using JacRed.Models.AppConf;
using Jackett;
namespace JacRed.Engine
{
public static class RedApi
{
static RedConf red => ModInit.conf.Red;
#region Indexers
public static (IEnumerable<TorrentDetails> torrents, bool setcache) Indexers(bool rqnum, string apikey, string query, string title, string title_original, int year, int is_serial, Dictionary<string, string> category)
{
bool setcache = false;
var torrents = new Dictionary<string, TorrentDetails>();
#region category
if (is_serial == 0 && category != null)
{
string cat = category.FirstOrDefault().Value;
if (cat != null)
{
if (cat.Contains("5020") || cat.Contains("2010"))
is_serial = 3; // tvshow
else if (cat.Contains("5080"))
is_serial = 4; // док
else if (cat.Contains("5070"))
is_serial = 5; // аниме
else if (is_serial == 0)
{
if (cat.StartsWith("20"))
is_serial = 1; // фильм
else if (cat.StartsWith("50"))
is_serial = 2; // сериал
}
}
}
#endregion
#region AddTorrents
void AddTorrents(TorrentDetails t)
{
if (t.url == null)
return;
if (!string.IsNullOrEmpty(ModInit.conf.filter) && !Regex.IsMatch(t.title, ModInit.conf.filter, RegexOptions.IgnoreCase))
return;
if (!string.IsNullOrEmpty(ModInit.conf.filter_ignore) && Regex.IsMatch(t.title, ModInit.conf.filter_ignore, RegexOptions.IgnoreCase))
return;
if (InvkEvent.conf.RedApi?.AddTorrents != null)
{
if (!InvkEvent.RedApi("addtorrent", t))
return;
}
else
{
EventListener.RedApiAddTorrents?.Invoke(t);
}
if (torrents.TryGetValue(t.url, out TorrentDetails val))
{
if (t.updateTime > val.updateTime)
torrents[t.url] = t;
}
else
{
torrents.TryAdd(t.url, t);
}
}
#endregion
if (!string.IsNullOrWhiteSpace(title) || !string.IsNullOrWhiteSpace(title_original))
{
#region Точный поиск
setcache = true;
string _n = StringConvert.SearchName(title);
string _o = StringConvert.SearchName(title_original);
// Быстрая выборка по совпадению ключа в имени
var mdb = FileDB.masterDb.Where(i => _n != null && i.Key.StartsWith($"{_n}:") || _o != null && i.Key.EndsWith($":{_o}"));
if (!red.evercache.enable || red.evercache.validHour > 0)
mdb = mdb.Take(red.maxreadfile);
foreach (var val in mdb)
{
using (var fdb = FileDB.Open(val.Key))
{
foreach (var t in fdb.Database.Values)
{
if (t.types == null || t.title.Contains(" КПК"))
continue;
string name = StringConvert.SearchName(t.name);
string originalname = StringConvert.SearchName(t.originalname);
// Точная выборка по name или originalname
if (_n != null && _n == name || _o != null && _o == originalname)
{
if (is_serial == 1)
{
#region Фильм
if (t.types.Contains("movie") || t.types.Contains("multfilm") || t.types.Contains("anime") || t.types.Contains("documovie"))
{
if (Regex.IsMatch(t.title, " (сезон|сери(и|я|й))", RegexOptions.IgnoreCase))
continue;
if (year > 0)
{
if (t.relased == year || t.relased == year - 1 || t.relased == year + 1)
AddTorrents(t);
}
else
{
AddTorrents(t);
}
}
#endregion
}
else if (is_serial == 2)
{
#region Сериал
if (t.types.Contains("serial") || t.types.Contains("multserial") || t.types.Contains("anime") || t.types.Contains("docuserial") || t.types.Contains("tvshow"))
{
if (year > 0)
{
if (t.relased >= year - 1)
AddTorrents(t);
}
else
{
AddTorrents(t);
}
}
#endregion
}
else if (is_serial == 3)
{
#region tvshow
if (t.types.Contains("tvshow"))
{
if (year > 0)
{
if (t.relased >= year - 1)
AddTorrents(t);
}
else
{
AddTorrents(t);
}
}
#endregion
}
else if (is_serial == 4)
{
#region docuserial / documovie
if (t.types.Contains("docuserial") || t.types.Contains("documovie"))
{
if (year > 0)
{
if (t.relased >= year - 1)
AddTorrents(t);
}
else
{
AddTorrents(t);
}
}
#endregion
}
else if (is_serial == 5)
{
#region anime
if (t.types.Contains("anime"))
{
if (year > 0)
{
if (t.relased >= year - 1)
AddTorrents(t);
}
else
{
AddTorrents(t);
}
}
#endregion
}
else
{
#region Неизвестно
if (year > 0)
{
if (t.types.Contains("movie") || t.types.Contains("multfilm") || t.types.Contains("documovie"))
{
if (t.relased == year || t.relased == year - 1 || t.relased == year + 1)
AddTorrents(t);
}
else
{
if (t.relased >= year - 1)
AddTorrents(t);
}
}
else
{
AddTorrents(t);
}
#endregion
}
}
}
}
}
#endregion
}
else if (!string.IsNullOrWhiteSpace(query) && query.Length > 1)
{
#region Обычный поиск
string _s = StringConvert.SearchName(query);
#region torrentsSearch
void torrentsSearch(bool exact)
{
var mdb = FileDB.masterDb.Where(i => i.Key.Contains(_s));
if (!red.evercache.enable || red.evercache.validHour > 0)
mdb = mdb.Take(red.maxreadfile);
foreach (var val in mdb)
{
using (var fdb = FileDB.Open(val.Key))
{
foreach (var t in fdb.Database.Values)
{
if (exact)
{
if (StringConvert.SearchName(t.name) != _s && StringConvert.SearchName(t.originalname) != _s)
continue;
}
if (t.types == null || t.title.Contains(" КПК"))
continue;
if (is_serial == 1)
{
if (t.types.Contains("movie") || t.types.Contains("multfilm") || t.types.Contains("anime") || t.types.Contains("documovie"))
AddTorrents(t);
}
else if (is_serial == 2)
{
if (t.types.Contains("serial") || t.types.Contains("multserial") || t.types.Contains("anime") || t.types.Contains("docuserial") || t.types.Contains("tvshow"))
AddTorrents(t);
}
else if (is_serial == 3)
{
if (t.types.Contains("tvshow"))
AddTorrents(t);
}
else if (is_serial == 4)
{
if (t.types.Contains("docuserial") || t.types.Contains("documovie"))
AddTorrents(t);
}
else if (is_serial == 5)
{
if (t.types.Contains("anime"))
AddTorrents(t);
}
else
{
AddTorrents(t);
}
}
}
}
}
#endregion
if (is_serial == -1)
torrentsSearch(exact: false);
else
{
torrentsSearch(exact: true);
if (torrents.Count == 0)
torrentsSearch(exact: false);
}
#endregion
}
#region Объединить дубликаты
IEnumerable<TorrentDetails> tsort = null;
if (ModInit.conf.typesearch == "red" && ((!rqnum && red.mergeduplicates) || (rqnum && red.mergenumduplicates)))
{
var temp = new Dictionary<string, (TorrentDetails torrent, string title, string Name, List<string> AnnounceUrls)>();
foreach (var torrent in torrents.Values
.Where(i => red.trackers == null || red.trackers.Contains(i.trackerName))
.OrderByDescending(i => i.createTime)
.ThenBy(i => i.trackerName == "selezen").ToList())
{
if (torrent.magnet == null)
continue;
var magnetLink = MagnetLink.Parse(torrent.magnet);
string hex = magnetLink.InfoHashes.V1.ToHex();
if (!temp.TryGetValue(hex, out _))
{
temp.TryAdd(hex, ((TorrentDetails)torrent.Clone(), torrent.trackerName == "kinozal" ? torrent.title : null, magnetLink.Name, magnetLink.AnnounceUrls?.ToList() ?? new List<string>()));
}
else
{
var t = temp[hex];
t.torrent.trackerName += $", {torrent.trackerName}";
#region urls
if (t.torrent.urls == null)
t.torrent.urls = new HashSet<string> { t.torrent.url };
t.torrent.urls.Add(torrent.url);
#endregion
#region UpdateMagnet
void UpdateMagnet()
{
string magnet = $"magnet:?xt=urn:btih:{hex.ToLower()}";
if (!string.IsNullOrWhiteSpace(t.Name))
magnet += $"&dn={HttpUtility.UrlEncode(t.Name)}";
if (t.AnnounceUrls != null && t.AnnounceUrls.Count > 0)
{
foreach (string announce in t.AnnounceUrls)
{
string tr = announce.Contains("/") || announce.Contains(":") ? HttpUtility.UrlEncode(announce) : announce;
if (!magnet.Contains(tr))
magnet += $"&tr={tr}";
}
}
t.torrent.magnet = magnet;
}
#endregion
if (string.IsNullOrWhiteSpace(t.Name) && !string.IsNullOrWhiteSpace(magnetLink.Name))
{
t.Name = magnetLink.Name;
temp[hex] = t;
UpdateMagnet();
}
if (magnetLink.AnnounceUrls != null && magnetLink.AnnounceUrls.Count > 0)
{
t.AnnounceUrls.AddRange(magnetLink.AnnounceUrls);
UpdateMagnet();
}
#region UpdateTitle
void UpdateTitle()
{
if (string.IsNullOrWhiteSpace(t.title))
return;
string title = t.title;
if (t.torrent.voices != null && t.torrent.voices.Count > 0)
title += $" | {string.Join(" | ", t.torrent.voices)}";
t.torrent.title = title;
}
if (torrent.trackerName == "kinozal")
{
t.title = torrent.title;
temp[hex] = t;
UpdateTitle();
}
if (torrent.voices != null && torrent.voices.Count > 0)
{
if (t.torrent.voices == null)
{
t.torrent.voices = torrent.voices;
}
else
{
foreach (var v in torrent.voices)
t.torrent.voices.Add(v);
}
UpdateTitle();
}
#endregion
if (torrent.trackerName != "selezen")
{
if (torrent.sid > t.torrent.sid)
t.torrent.sid = torrent.sid;
if (torrent.pir > t.torrent.pir)
t.torrent.pir = torrent.pir;
}
if (torrent.createTime > t.torrent.createTime)
t.torrent.createTime = torrent.createTime;
if (torrent.voices != null && torrent.voices.Count > 0)
{
if (t.torrent.voices == null)
t.torrent.voices = new HashSet<string>();
foreach (var v in torrent.voices)
t.torrent.voices.Add(v);
}
if (torrent.languages != null && torrent.languages.Count > 0)
{
if (t.torrent.languages == null)
t.torrent.languages = new HashSet<string>();
foreach (var v in torrent.languages)
t.torrent.languages.Add(v);
}
if (t.torrent.ffprobe == null)
t.torrent.ffprobe = torrent.ffprobe;
}
}
tsort = temp.Select(i => i.Value.torrent);
}
else
{
tsort = torrents.Values.Where(i => red.trackers == null || red.trackers.Contains(i.trackerName));
}
#endregion
if (apikey == "rus")
return (tsort.Where(i => i.languages != null && i.languages.Contains("rus") || i.types != null && (i.types.Contains("sport") || i.types.Contains("tvshow") || i.types.Contains("docuserial"))), setcache);
return (tsort, setcache);
}
#endregion
#region Api
public static IEnumerable<TorrentDetails> Api(string search, string altname, bool exact, string type, string sort, string tracker, string voice, string videotype, long relased, long quality, long season)
{
var torrents = new Dictionary<string, TorrentDetails>();
#region AddTorrents
void AddTorrents(TorrentDetails t)
{
if (torrents.TryGetValue(t.url, out TorrentDetails val))
{
if (t.updateTime > val.updateTime)
torrents[t.url] = t;
}
else
{
torrents.TryAdd(t.url, t);
}
}
#endregion
if (string.IsNullOrWhiteSpace(search) || search.Length == 1)
return new List<TorrentDetails>();
string _s = StringConvert.SearchName(search);
string _altsearch = StringConvert.SearchName(altname);
if (exact)
{
#region Точный поиск
foreach (var mdb in FileDB.masterDb.Where(i => i.Key.StartsWith($"{_s}:") || i.Key.EndsWith($":{_s}") || _altsearch != null && i.Key.Contains(_altsearch)))
{
using (var fdb = FileDB.Open(mdb.Key))
{
foreach (var t in fdb.Database.Values)
{
if (t.types == null)
continue;
if (string.IsNullOrWhiteSpace(type) || t.types.Contains(type))
{
string _n = StringConvert.SearchName(t.name);
string _o = StringConvert.SearchName(t.originalname);
if (_n == _s || _o == _s || _altsearch != null && (_n == _altsearch || _o == _altsearch))
AddTorrents(t);
}
}
}
}
#endregion
}
else
{
#region Поиск по совпадению ключа в имени
var mdb = FileDB.masterDb.Where(i => i.Key.Contains(_s) || _altsearch != null && i.Key.Contains(_altsearch));
if (!red.evercache.enable || red.evercache.validHour > 0)
mdb = mdb.Take(red.maxreadfile);
foreach (var val in mdb)
{
using (var fdb = FileDB.Open(val.Key))
{
foreach (var t in fdb.Database.Values)
{
if (t.types == null)
continue;
if (string.IsNullOrWhiteSpace(type) || t.types.Contains(type))
AddTorrents(t);
}
}
}
#endregion
}
if (torrents.Count == 0)
return new List<TorrentDetails>();
IEnumerable<TorrentDetails> query = torrents.Values;
#region sort
switch (sort ?? string.Empty)
{
case "sid":
query = query.OrderByDescending(i => i.sid);
break;
case "pir":
query = query.OrderByDescending(i => i.pir);
break;
case "size":
query = query.OrderByDescending(i => i.size);
break;
default:
query = query.OrderByDescending(i => i.createTime);
break;
}
#endregion
if (!string.IsNullOrWhiteSpace(tracker))
query = query.Where(i => i.trackerName == tracker);
if (relased > 0)
query = query.Where(i => i.relased == relased);
if (quality > 0)
query = query.Where(i => i.quality == quality);
if (!string.IsNullOrWhiteSpace(videotype))
query = query.Where(i => i.videotype == videotype);
if (!string.IsNullOrWhiteSpace(voice))
query = query.Where(i => i.voices.Contains(voice));
if (season > 0)
query = query.Where(i => i.seasons.Contains((int)season));
return query.Where(i => red.trackers == null || red.trackers.Contains(i.trackerName));
}
#endregion
}
}

117
JacRed/Engine/SyncCron.cs Normal file
View File

@ -0,0 +1,117 @@
using Jackett;
using JacRed.Models.Sync;
namespace JacRed.Engine
{
public static class SyncCron
{
static long lastsync = -1;
async public static Task Run()
{
bool reset = true;
await Task.Delay(TimeSpan.FromMinutes(2));
DateTime lastSave = DateTime.Now;
while (true)
{
try
{
if (File.Exists(@"C:\ProgramData\lampac\disablesync"))
break;
if (ModInit.conf.typesearch == "red" && !string.IsNullOrWhiteSpace(ModInit.conf.Red.syncapi))
{
if (lastsync == -1 && File.Exists("cache/jacred/lastsync.txt"))
lastsync = long.Parse(File.ReadAllText("cache/jacred/lastsync.txt"));
var root = await Http.Get<RootObject>($"{ModInit.conf.Red.syncapi}/sync/fdb/torrents?time={lastsync}", timeoutSeconds: 300, MaxResponseContentBufferSize: 100_000_000, weblog: false);
if (root?.collections == null)
{
if (reset)
{
reset = false;
await Task.Delay(TimeSpan.FromMinutes(1));
continue;
}
}
else if (root.collections.Count > 0)
{
reset = true;
foreach (var collection in root.collections)
{
bool updateMasterDb = false;
using (var fdb = FileDB.Open(collection.Key, empty: true))
{
foreach (var torrent in collection.Value.torrents)
{
if (torrent.Value.types == null || torrent.Value.types.Contains("sport"))
continue;
fdb.Database.AddOrUpdate(torrent.Key, torrent.Value, (k, v) => torrent.Value);
updateMasterDb = true;
}
}
if (updateMasterDb)
{
if (FileDB.masterDb.ContainsKey(collection.Key))
{
FileDB.masterDb[collection.Key] = collection.Value.time;
}
else
{
FileDB.masterDb.TryAdd(collection.Key, collection.Value.time);
}
}
}
lastsync = root.collections.Last().Value.fileTime;
if (root.nextread)
{
if (DateTime.Now > lastSave.AddMinutes(5))
{
lastSave = DateTime.Now;
FileDB.SaveChangesToFile();
File.WriteAllText("cache/jacred/lastsync.txt", lastsync.ToString());
}
continue;
}
}
FileDB.SaveChangesToFile();
File.WriteAllText("cache/jacred/lastsync.txt", lastsync.ToString());
}
else
{
await Task.Delay(TimeSpan.FromMinutes(1));
continue;
}
}
catch
{
try
{
if (lastsync > 0)
{
FileDB.SaveChangesToFile();
File.WriteAllText("cache/jacred/lastsync.txt", lastsync.ToString());
}
}
catch { }
}
await Task.Delay(1000 * Random.Shared.Next(60, 300));
await Task.Delay(1000 * 60 * (20 > ModInit.conf.Red.syntime ? 20 : ModInit.conf.Red.syntime));
reset = true;
lastSave = DateTime.Now;
}
}
}
}

99
JacRed/Engine/WebApi.cs Normal file
View File

@ -0,0 +1,99 @@
using Jackett;
using Newtonsoft.Json.Linq;
using System.Text;
using Shared.Models.JacRed.Tracks;
namespace JacRed.Engine
{
public static class WebApi
{
#region Indexers
async public static Task<List<TorrentDetails>> Indexers(string query, string title, string title_original, int year, int is_serial, Dictionary<string, string> category)
{
var queryString = new StringBuilder();
if (!string.IsNullOrEmpty(title))
queryString.Append($"&title={HttpUtility.UrlEncode(title)}");
if (!string.IsNullOrEmpty(title_original))
queryString.Append($"&title_original={HttpUtility.UrlEncode(title_original)}");
if (year > 0)
queryString.Append($"&year={year}");
if (is_serial > 0)
queryString.Append($"&is_serial={is_serial}");
if (category != null && category.Count > 0)
queryString.Append($"&category[]={category.First().Value}");
var root = await Http.Get<JObject>($"{ModInit.conf.webApiHost}/api/v2.0/indexers/all/results?query={HttpUtility.UrlEncode(query)}" + queryString.ToString(), timeoutSeconds: 8);
if (root == null)
return new List<TorrentDetails>();
var results = root.GetValue("Results")?.ToObject<JArray>();
if (results == null || results.Count == 0)
return new List<TorrentDetails>();
var torrents = new List<TorrentDetails>(results.Count);
foreach (var torrent in results)
{
try
{
string name = torrent.Value<string>("Title");
string tracker = torrent.Value<string>("Tracker");
if (ModInit.conf.Red.trackers != null)
{
if (!tracker.Contains(","))
{
if (!ModInit.conf.Red.trackers.Contains(tracker))
continue;
}
else
{
/*
* Этот код фильтрует результаты поиска торрентов по списку разрешённых трекеров, который хранится в ModInit.conf.Red.trackers.
* Если у торрента в поле Tracker указано несколько трекеров через запятую, то он будет допущен только в том случае, если хотя бы один из этих трекеров есть в разрешённом списке.
*/
var trackers = tracker.Split(',');
if (!ModInit.conf.Red.trackers.Any(t => trackers.Contains(t)))
continue;
}
}
if (!string.IsNullOrEmpty(ModInit.conf.filter) && !Regex.IsMatch(name, ModInit.conf.filter, RegexOptions.IgnoreCase))
continue;
if (!string.IsNullOrEmpty(ModInit.conf.filter_ignore) && Regex.IsMatch(name, ModInit.conf.filter_ignore, RegexOptions.IgnoreCase))
continue;
torrents.Add(new TorrentDetails()
{
trackerName = tracker,
url = torrent.Value<string>("Details"),
title = name,
sid = torrent.Value<int>("Seeders"),
pir = torrent.Value<int>("Peers"),
size = torrent.Value<double>("Size"),
magnet = torrent.Value<string>("MagnetUri"),
createTime = torrent.Value<DateTime>("PublishDate"),
ffprobe = torrent["ffprobe"]?.ToObject<List<ffStream>>()
});
}
catch { }
}
return torrents;
}
#endregion
#region Api
public static Task<List<TorrentDetails>> Api(string search)
{
return Indexers(search, null, null, 0, 0, null);
}
#endregion
}
}

16
JacRed/GlobalUsings.cs Normal file
View File

@ -0,0 +1,16 @@
global using System;
global using System.Web;
global using System.Threading.Tasks;
global using System.Collections.Generic;
global using System.Text.RegularExpressions;
global using System.Collections.Concurrent;
global using System.Globalization;
global using System.IO;
global using System.Linq;
global using HtmlAgilityPack;
global using Shared;
global using Shared.Models;
global using Shared.Engine;
global using JacRed.Engine;
global using Shared.Engine.JacRed;
global using Shared.Models.JacRed;

13
JacRed/JacRed.csproj Normal file
View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

117
JacRed/ModInit.cs Normal file
View File

@ -0,0 +1,117 @@
using JacRed.Models.AppConf;
using Newtonsoft.Json;
using System.Threading;
namespace Jackett
{
public class ModInit
{
#region ModInit
static (ModInit, DateTime) cacheconf = default;
public static ModInit conf
{
get
{
if (cacheconf.Item1 == null)
{
if (!File.Exists("module/JacRed.conf"))
return new ModInit();
}
var lastWriteTime = File.GetLastWriteTime("module/JacRed.conf");
if (cacheconf.Item2 != lastWriteTime)
{
var jss = new JsonSerializerSettings { Error = (se, ev) =>
{
ev.ErrorContext.Handled = true;
Console.WriteLine("module/JacRed.conf - " + ev.ErrorContext.Error + "\n\n");
}};
string json = File.ReadAllText("module/JacRed.conf");
if (!json.TrimStart().StartsWith("{"))
json = "{"+json+"}";
cacheconf.Item1 = JsonConvert.DeserializeObject<ModInit>(json, jss);
cacheconf.Item2 = lastWriteTime;
}
return cacheconf.Item1;
}
}
#endregion
public static void loaded()
{
Directory.CreateDirectory("cache/jacred");
File.WriteAllText("module/JacRed.current.conf", JsonConvert.SerializeObject(conf, Formatting.Indented));
ThreadPool.QueueUserWorkItem(async _ => await SyncCron.Run());
ThreadPool.QueueUserWorkItem(async _ => await FileDB.Cron());
ThreadPool.QueueUserWorkItem(async _ => await FileDB.CronFast());
ThreadPool.QueueUserWorkItem(async _ =>
{
while (true)
{
await Task.Delay(TimeSpan.FromMinutes(5));
try
{
if (conf.typesearch == "jackett" || conf.merge == "jackett")
{
async ValueTask<bool> showdown(string name, TrackerSettings settings)
{
if (!settings.monitor_showdown)
return false;
var proxyManager = new ProxyManager(name, settings);
string html = await Http.Get($"{settings.host}", timeoutSeconds: conf.Jackett.timeoutSeconds, proxy: proxyManager.Get(), weblog: false);
return html == null;
}
conf.Jackett.Rutor.showdown = await showdown("rutor", conf.Jackett.Rutor);
conf.Jackett.Megapeer.showdown = await showdown("megapeer", conf.Jackett.Megapeer);
conf.Jackett.TorrentBy.showdown = await showdown("torrentby", conf.Jackett.TorrentBy);
conf.Jackett.Kinozal.showdown = await showdown("kinozal", conf.Jackett.Kinozal);
conf.Jackett.NNMClub.showdown = await showdown("nnmclub", conf.Jackett.NNMClub);
conf.Jackett.Bitru.showdown = await showdown("bitru", conf.Jackett.Bitru);
conf.Jackett.Toloka.showdown = await showdown("toloka", conf.Jackett.Toloka);
conf.Jackett.Rutracker.showdown = await showdown("rutracker", conf.Jackett.Rutracker);
conf.Jackett.BigFanGroup.showdown = await showdown("bigfangroup", conf.Jackett.BigFanGroup);
conf.Jackett.Selezen.showdown = await showdown("selezen", conf.Jackett.Selezen);
conf.Jackett.Lostfilm.showdown = await showdown("lostfilm", conf.Jackett.Lostfilm);
conf.Jackett.Anilibria.showdown = await showdown("anilibria", conf.Jackett.Anilibria);
conf.Jackett.Animelayer.showdown = await showdown("animelayer", conf.Jackett.Animelayer);
conf.Jackett.Anifilm.showdown = await showdown("anifilm", conf.Jackett.Anifilm);
}
}
catch { }
}
});
}
/// <summary>
/// red
/// jackett
/// webapi
/// </summary>
public string typesearch = "webapi";
public string merge = "jackett";
public string webApiHost = "http://redapi.cfhttp.top";
public string filter { get; set; }
public string filter_ignore { get; set; }
public RedConf Red = new RedConf();
public JacConf Jackett = new JacConf();
}
}

View File

@ -0,0 +1,9 @@
namespace JacRed.Models.AniLibria
{
public class Names
{
public string ru { get; set; }
public string en { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace JacRed.Models.AniLibria
{
public class Quality
{
public string @string { get; set; }
public int resolution { get; set; }
public string encoder { get; set; }
}
}

View File

@ -0,0 +1,17 @@
namespace JacRed.Models.AniLibria
{
public class RootObject
{
public Names names { get; set; }
public string code { get; set; }
public Torrents torrents { get; set; }
public Season season { get; set; }
public long updated { get; set; }
public long last_change { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace JacRed.Models.AniLibria
{
public class Season
{
public int year { get; set; }
public int code { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace JacRed.Models.AniLibria
{
public class Series
{
public string @string { get; set; }
}
}

View File

@ -0,0 +1,17 @@
namespace JacRed.Models.AniLibria
{
public class Torrent
{
public Series series { get; set; }
public Quality quality { get; set; }
public int leechers { get; set; }
public int seeders { get; set; }
public string url { get; set; }
public long total_size { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace JacRed.Models.AniLibria
{
public class Torrents
{
public List<Torrent> list { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace JacRed.Models.AppConf
{
public class Evercache
{
public bool enable = false;
public int validHour = 1;
public int maxOpenWriteTask { get; set; } = 1000;
public int dropCacheTake { get; set; } = 100;
}
}

View File

@ -0,0 +1,40 @@
namespace JacRed.Models.AppConf
{
public class JacConf
{
public int cacheToMinutes = 5;
public string search_lang = "query";
public int timeoutSeconds = 8;
public TrackerSettings Rutor = new TrackerSettings("https://rutor.info"/*, priority: "torrent"*/);
public TrackerSettings Megapeer = new TrackerSettings("https://megapeer.vip", enable: false);
public TrackerSettings TorrentBy = new TrackerSettings("https://torrent.by"/*, priority: "torrent"*/);
public TrackerSettings Kinozal = new TrackerSettings("https://kinozal.tv");
public TrackerSettings NNMClub = new TrackerSettings("https://nnmclub.to");
public TrackerSettings Bitru = new TrackerSettings("https://bitru.org");
public TrackerSettings Toloka = new TrackerSettings("https://toloka.to");
public TrackerSettings Rutracker = new TrackerSettings("https://rutracker.org"/*, priority: "torrent"*/);
public TrackerSettings BigFanGroup = new TrackerSettings("https://bigfangroup.org");
public TrackerSettings Selezen = new TrackerSettings("https://open.selezen.org"/*, priority: "torrent"*/);
public TrackerSettings Lostfilm = new TrackerSettings("https://www.lostfilm.tv");
public TrackerSettings Anilibria = new TrackerSettings("https://www.anilibria.tv");
public TrackerSettings Animelayer = new TrackerSettings("http://animelayer.ru");
public TrackerSettings Anifilm = new TrackerSettings("https://anifilm.pro");
}
}

View File

@ -0,0 +1,9 @@
namespace JacRed.Models.AppConf
{
public class LoginSettings
{
public string u { get; set; }
public string p { get; set; }
}
}

View File

@ -0,0 +1,19 @@
namespace JacRed.Models.AppConf
{
public class RedConf
{
public string syncapi = "http://redapi.cfhttp.top";
public int syntime = 60;
public string[] trackers = new string[] { "rutracker", "rutor", "kinozal", "nnmclub", "megapeer", "bitru", "toloka", "lostfilm", "baibako", "torrentby", "hdrezka", "selezen", "animelayer", "anilibria", "anifilm" };
public int maxreadfile = 300;
public bool mergeduplicates = true;
public bool mergenumduplicates = true;
public Evercache evercache = new Evercache();
}
}

View File

@ -0,0 +1,43 @@
using Shared.Models.Base;
namespace JacRed.Models.AppConf
{
public class TrackerSettings : Iproxy
{
public TrackerSettings(string host, bool enable = true, bool useproxy = false, LoginSettings login = null, string priority = null)
{
this.host = host;
this.enable = enable;
this.useproxy = useproxy;
if (login != null)
this.login = login;
this.priority = priority;
}
public string host { get; set; }
public bool enable { get; set; }
public bool showdown { get; set; }
public bool monitor_showdown { get; set; } = true;
public string priority { get; set; }
public LoginSettings login { get; set; } = new LoginSettings();
public string cookie { get; set; }
public bool useproxy { get; set; }
public bool useproxystream { get; set; }
public string globalnameproxy { get; set; }
public ProxySettings proxy { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace JacRed.Models.Sync
{
public class Collection
{
public string Key { get; set; }
public Value Value { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace JacRed.Models.Sync
{
public class RootObject
{
public bool nextread { get; set; }
public List<Collection> collections { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace JacRed.Models.Sync
{
public class Value
{
public DateTime time { get; set; }
public long fileTime { get; set; }
public Dictionary<string, TorrentDetails> torrents { get; set; }
}
}

View File

@ -0,0 +1,15 @@
namespace JacRed.Models
{
public class WriteTaskModel
{
public FileDB db { get; set; }
public int openconnection { get; set; }
public int countread { get; set; }
public DateTime lastread { get; set; }
public DateTime create { get; set; } = DateTime.Now;
}
}

98
Lampac.sln Normal file
View File

@ -0,0 +1,98 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.2.11415.280 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lampac", "Lampac\Lampac.csproj", "{6EC456A8-9B61-4F88-992B-972BE6E66C96}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{7A19617E-61AF-4741-B901-CB24D1565CA4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Catalog", "Catalog\Catalog.csproj", "{68BA3E1D-69D1-47D3-9E84-65189FF1AE90}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Online", "Online\Online.csproj", "{DD693EB2-48C4-49D0-BFEE-B25B534CE6B4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SISI", "SISI\SISI.csproj", "{DF3D53D6-647F-42A3-9764-BE9B4ACA3235}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Merchant", "Merchant\Merchant.csproj", "{0C2B4AF5-F6CA-4465-8D53-BB8B245DE4A6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DLNA", "DLNA\DLNA.csproj", "{A874EBE4-88D3-4C02-9FDA-6A6631380CE9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tracks", "Tracks\Tracks.csproj", "{E0036073-200D-4E60-8F6A-9C428E49442E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JacRed", "JacRed\JacRed.csproj", "{DB36397B-76E7-44EF-9F2E-21DDCBF6A836}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TorrServer", "TorrServer\TorrServer.csproj", "{A9FB8118-B1A4-436B-8C9C-1EACD4154F37}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseModule", "BaseModule\BaseModule.csproj", "{A9FB8118-B1A4-436B-8C9C-1EACD4154F39}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{1F4A0DC9-498D-49A9-91E4-F8007335BFD0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6EC456A8-9B61-4F88-992B-972BE6E66C96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6EC456A8-9B61-4F88-992B-972BE6E66C96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6EC456A8-9B61-4F88-992B-972BE6E66C96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6EC456A8-9B61-4F88-992B-972BE6E66C96}.Release|Any CPU.Build.0 = Release|Any CPU
{7A19617E-61AF-4741-B901-CB24D1565CA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A19617E-61AF-4741-B901-CB24D1565CA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A19617E-61AF-4741-B901-CB24D1565CA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A19617E-61AF-4741-B901-CB24D1565CA4}.Release|Any CPU.Build.0 = Release|Any CPU
{68BA3E1D-69D1-47D3-9E84-65189FF1AE90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68BA3E1D-69D1-47D3-9E84-65189FF1AE90}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68BA3E1D-69D1-47D3-9E84-65189FF1AE90}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68BA3E1D-69D1-47D3-9E84-65189FF1AE90}.Release|Any CPU.Build.0 = Release|Any CPU
{DD693EB2-48C4-49D0-BFEE-B25B534CE6B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD693EB2-48C4-49D0-BFEE-B25B534CE6B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD693EB2-48C4-49D0-BFEE-B25B534CE6B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD693EB2-48C4-49D0-BFEE-B25B534CE6B4}.Release|Any CPU.Build.0 = Release|Any CPU
{DF3D53D6-647F-42A3-9764-BE9B4ACA3235}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF3D53D6-647F-42A3-9764-BE9B4ACA3235}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF3D53D6-647F-42A3-9764-BE9B4ACA3235}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF3D53D6-647F-42A3-9764-BE9B4ACA3235}.Release|Any CPU.Build.0 = Release|Any CPU
{0C2B4AF5-F6CA-4465-8D53-BB8B245DE4A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C2B4AF5-F6CA-4465-8D53-BB8B245DE4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C2B4AF5-F6CA-4465-8D53-BB8B245DE4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C2B4AF5-F6CA-4465-8D53-BB8B245DE4A6}.Release|Any CPU.Build.0 = Release|Any CPU
{A874EBE4-88D3-4C02-9FDA-6A6631380CE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A874EBE4-88D3-4C02-9FDA-6A6631380CE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A874EBE4-88D3-4C02-9FDA-6A6631380CE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A874EBE4-88D3-4C02-9FDA-6A6631380CE9}.Release|Any CPU.Build.0 = Release|Any CPU
{E0036073-200D-4E60-8F6A-9C428E49442E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0036073-200D-4E60-8F6A-9C428E49442E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0036073-200D-4E60-8F6A-9C428E49442E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0036073-200D-4E60-8F6A-9C428E49442E}.Release|Any CPU.Build.0 = Release|Any CPU
{DB36397B-76E7-44EF-9F2E-21DDCBF6A836}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DB36397B-76E7-44EF-9F2E-21DDCBF6A836}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB36397B-76E7-44EF-9F2E-21DDCBF6A836}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DB36397B-76E7-44EF-9F2E-21DDCBF6A836}.Release|Any CPU.Build.0 = Release|Any CPU
{A9FB8118-B1A4-436B-8C9C-1EACD4154F37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9FB8118-B1A4-436B-8C9C-1EACD4154F37}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9FB8118-B1A4-436B-8C9C-1EACD4154F37}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9FB8118-B1A4-436B-8C9C-1EACD4154F37}.Release|Any CPU.Build.0 = Release|Any CPU
{A9FB8118-B1A4-436B-8C9C-1EACD4154F39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9FB8118-B1A4-436B-8C9C-1EACD4154F39}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9FB8118-B1A4-436B-8C9C-1EACD4154F39}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9FB8118-B1A4-436B-8C9C-1EACD4154F39}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{68BA3E1D-69D1-47D3-9E84-65189FF1AE90} = {1F4A0DC9-498D-49A9-91E4-F8007335BFD0}
{DD693EB2-48C4-49D0-BFEE-B25B534CE6B4} = {1F4A0DC9-498D-49A9-91E4-F8007335BFD0}
{DF3D53D6-647F-42A3-9764-BE9B4ACA3235} = {1F4A0DC9-498D-49A9-91E4-F8007335BFD0}
{0C2B4AF5-F6CA-4465-8D53-BB8B245DE4A6} = {1F4A0DC9-498D-49A9-91E4-F8007335BFD0}
{A874EBE4-88D3-4C02-9FDA-6A6631380CE9} = {1F4A0DC9-498D-49A9-91E4-F8007335BFD0}
{E0036073-200D-4E60-8F6A-9C428E49442E} = {1F4A0DC9-498D-49A9-91E4-F8007335BFD0}
{DB36397B-76E7-44EF-9F2E-21DDCBF6A836} = {1F4A0DC9-498D-49A9-91E4-F8007335BFD0}
{A9FB8118-B1A4-436B-8C9C-1EACD4154F37} = {1F4A0DC9-498D-49A9-91E4-F8007335BFD0}
{A9FB8118-B1A4-436B-8C9C-1EACD4154F39} = {1F4A0DC9-498D-49A9-91E4-F8007335BFD0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F05D572A-75BC-4642-98F8-F65F5CCF6A0B}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,162 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Shared;
using Shared.Engine;
using System;
using System.Linq;
using System.Text;
using System.Web;
using IO = System.IO;
namespace Lampac.Controllers
{
public class ApiController : BaseController
{
#region Version / Headers / geo / myip / reqinfo
[HttpGet]
[AllowAnonymous]
[Route("/version")]
public ActionResult Version() => Content($"{appversion}.{minorversion}");
[HttpGet]
[AllowAnonymous]
[Route("/ping")]
public ActionResult PingPong() => Content("pong");
[HttpGet]
[AllowAnonymous]
[Route("/headers")]
public ActionResult Headers(string type)
{
if (type == "text")
{
return Content(string.Join(
Environment.NewLine,
HttpContext.Request.Headers.Select(h => $"{h.Key}: {h.Value}")
));
}
return Json(HttpContext.Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()));
}
[HttpGet]
[AllowAnonymous]
[Route("/geo")]
public ActionResult Geo(string select, string ip)
{
if (select == "ip")
return Content(ip ?? requestInfo.IP);
string country = requestInfo.Country;
if (ip != null)
country = GeoIP2.Country(ip);
if (select == "country")
return Content(country);
return Json(new
{
ip = ip ?? requestInfo.IP,
country
});
}
[HttpGet]
[AllowAnonymous]
[Route("/myip")]
public ActionResult MyIP() => Content(requestInfo.IP);
[HttpGet]
[Route("/reqinfo")]
public ActionResult Reqinfo() => ContentTo(JsonConvert.SerializeObject(requestInfo, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore
}));
#endregion
#region invc-ws.js
[HttpGet]
[AllowAnonymous]
[Route("invc-ws.js")]
[Route("invc-ws/js/{token}")]
public ActionResult InvcSyncJS(string token)
{
StringBuilder sb;
if (AppInit.conf.sync_user.version == 1)
{
sb = new StringBuilder(FileCache.ReadAllText("plugins/invc-ws.js"));
}
else
{
sb = new StringBuilder(FileCache.ReadAllText("plugins/sync_v2/invc-ws.js"));
}
sb.Replace("{invc-rch}", FileCache.ReadAllText("plugins/invc-rch.js"))
.Replace("{invc-rch_nws}", FileCache.ReadAllText("plugins/invc-rch_nws.js"))
.Replace("{localhost}", host)
.Replace("{token}", HttpUtility.UrlEncode(token));
return Content(sb.ToString(), "application/javascript; charset=utf-8");
}
#endregion
#region invc-rch.js
[HttpGet]
[AllowAnonymous]
[Route("invc-rch.js")]
public ActionResult InvcRchJS()
{
string source = FileCache.ReadAllText("plugins/invc-rch.js").Replace("{localhost}", host);
source = $"(function(){{'use strict'; {source} }})();";
return Content(source, "application/javascript; charset=utf-8");
}
#endregion
#region nws-client-es5.js
[HttpGet]
[AllowAnonymous]
[Route("nws-client-es5.js")]
[Route("js/nws-client-es5.js")]
public ActionResult NwsClient()
{
string memKey = "ApiController:nws-client-es5.js";
if (!memoryCache.TryGetValue(memKey, out string source))
{
source = IO.File.ReadAllText("plugins/nws-client-es5.js");
memoryCache.Set(memKey, source);
}
if (source.Contains("{localhost}"))
source = source.Replace("{localhost}", host);
return Content(source, "application/javascript; charset=utf-8");
}
#endregion
#region signalr-6.0.25_es5.js
[HttpGet]
[AllowAnonymous]
[Route("signalr-6.0.25_es5.js")]
public ActionResult SignalrJs()
{
string memKey = "ApiController:signalr-6.0.25_es5.js";
if (!memoryCache.TryGetValue(memKey, out string source))
{
source = IO.File.ReadAllText("plugins/signalr-6.0.25_es5.js");
memoryCache.Set(memKey, source);
}
return Content(source, "application/javascript; charset=utf-8");
}
#endregion
}
}

View File

@ -0,0 +1,196 @@
using Lampac.Engine;
using Lampac.Engine.Middlewares;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Shared;
using Shared.Engine;
using Shared.Engine.Pools;
using Shared.Models.AppConf;
using Shared.PlaywrightCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
namespace Lampac.Controllers
{
public class OpenStatController : BaseController
{
public OpenStatConf openstat => AppInit.conf.openstat;
public bool IsDeny(out string ermsg)
{
ermsg = "Включите openstat в init.conf\n\n\"openstat\": {\n \"enable\": true\n}";
if (!openstat.enable || (!string.IsNullOrEmpty(openstat.token) && openstat.token != HttpContext.Request.Query["token"].ToString()))
return true;
return false;
}
#region browser/context
[HttpGet]
[AllowAnonymous]
[Route("/stats/browser/context")]
public ActionResult BrowserContext()
{
if (IsDeny(out string ermsg))
return Content(ermsg, "text/plain; charset=utf-8");
return Json(new
{
Chromium = new
{
open = Chromium.ContextsCount,
req_keepopen = Chromium.stats_keepopen,
req_newcontext = Chromium.stats_newcontext,
ping = new
{
Chromium.stats_ping.status,
Chromium.stats_ping.time,
Chromium.stats_ping.ex
}
},
Firefox = new
{
open = Firefox.ContextsCount,
req_keepopen = Firefox.stats_keepopen,
req_newcontext = Firefox.stats_newcontext
}
});
}
#endregion
#region request
[HttpGet]
[AllowAnonymous]
[Route("/stats/request")]
public ActionResult Requests()
{
if (IsDeny(out string ermsg))
return Content(ermsg, "text/plain; charset=utf-8");
var now = DateTime.UtcNow;
long req_min = 0;
if (memoryCache.TryGetValue($"stats:request:{now.Hour}:{now.AddMinutes(-1).Minute}", out CounterRequestInfo _counter))
req_min = _counter.Value;
long req_hour = req_min;
for (int i = 1; i < 60; i++)
{
var cutoff = now.AddMinutes(-i);
if (memoryCache.TryGetValue($"stats:request:{cutoff.Hour}:{cutoff.Minute}", out CounterRequestInfo _r))
req_hour += _r.Value;
}
var responseStats = RequestStatisticsTracker.GetResponseTimeStatsLastMinute();
var httpResponseMs = new Dictionary<string, object>
{
["avg"] = Math.Round(responseStats.Average, 2)
};
foreach (var percentile in responseStats.PercentileAverages.OrderBy(x => x.Key))
httpResponseMs.Add(percentile.Key.ToString(), Math.Round(percentile.Value, 2));
return Json(new
{
req_min,
req_hour,
tcpConnections = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections().Length,
nws_online = NativeWebSocket.CountConnection,
soks_online = soks.connections,
http_active = RequestStatisticsTracker.ActiveHttpRequests,
http_response_ms = httpResponseMs
});
}
#endregion
#region rch
[HttpGet]
[AllowAnonymous]
[Route("/stats/rch")]
public ActionResult Rhc()
{
if (IsDeny(out string ermsg))
return Content(ermsg, "text/plain; charset=utf-8");
var now = DateTime.UtcNow;
int receive = 0, send = 0;
if (memoryCache.TryGetValue("stats:nws", out CounterNws _c))
{
receive = _c.receive;
send = _c.send;
}
return Json(new
{
clients = RchClient.clients.Count,
counter = new
{
receive,
send
},
rchIds = RchClient.rchIds.Count
});
}
#endregion
#region TempDb
[HttpGet]
[AllowAnonymous]
[Route("/stats/tempdb")]
public ActionResult TempDb()
{
if (IsDeny(out string ermsg))
return Content(ermsg, "text/plain; charset=utf-8");
return Json(new
{
HybridCache = HybridCache.Stat_ContTempDb,
HybridFileCache = HybridFileCache.Stat_ContTempDb,
ProxyLink = ProxyLink.Stat_ContLinks,
ProxyAPI = ProxyAPI.Stat_ContCacheFiles,
ProxyTmdb = ProxyTmdb.Stat_ContCacheFiles,
ProxyImg = ProxyImg.Stat_ContCacheFiles,
ProxyCub = ProxyCub.Stat_ContCacheFiles,
SemaphorManager = SemaphorManager.Stat_ContSemaphoreLocks,
rch = new
{
clients = RchClient.clients.Count,
Ids = RchClient.rchIds.Count
},
pool = new
{
msm = new
{
PoolInvk.msm.SmallPoolInUseSize,
PoolInvk.msm.LargePoolInUseSize,
PoolInvk.msm.SmallBlocksFree,
PoolInvk.msm.SmallPoolFreeSize,
PoolInvk.msm.LargeBuffersFree,
PoolInvk.msm.LargePoolFreeSize
},
StringBuilder = new
{
Rent = StringBuilderPool.RentNew,
Free = StringBuilderPool.FreeCont,
StringBuilderPool.GC
},
MemoryStream = MemoryStreamPool.Count == 0 ? null : new
{
MemoryStreamPool.Count,
MemoryStreamPool.GC
}
},
memoryCache = memoryCache.GetCurrentStatistics()
});
}
#endregion
}
}

View File

@ -0,0 +1,116 @@
using Shared;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
namespace Lampac.Engine.CRON
{
public static class CacheCron
{
public static void Run()
{
_cronTimer = new Timer(cron, null, TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(5));
}
static Timer _cronTimer;
static int _updatingDb = 0;
static void cron(object state)
{
if (Interlocked.Exchange(ref _updatingDb, 1) == 1)
return;
try
{
var files = new Dictionary<string, FileInfo>();
long freeDiskSpace = getFreeDiskSpace();
foreach (var conf in new List<(string path, int minute)> {
("tmdb", AppInit.conf.tmdb.cache_img),
("cub", AppInit.conf.cub.cache_img),
("img", AppInit.conf.serverproxy.image.cache_time),
("torrent", AppInit.conf.fileCacheInactive.torrent),
("html", AppInit.conf.fileCacheInactive.html),
("hls", AppInit.conf.fileCacheInactive.hls),
("storage/temp", 10)
})
{
try
{
string path = Path.Combine("cache", conf.path);
if (conf.minute == -1 || !Directory.Exists(path))
continue;
var ex = DateTime.UtcNow.AddMinutes(-conf.minute);
foreach (string infile in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories))
{
try
{
if (conf.minute == 0)
File.Delete(infile);
else
{
var lastWriteTime = File.GetLastWriteTimeUtc(infile);
if (ex > lastWriteTime)
File.Delete(infile);
else if (freeDiskSpace != -1 && AppInit.conf.fileCacheInactive.freeDiskSpace > freeDiskSpace)
files.TryAdd(infile, new FileInfo(infile));
}
}
catch { }
}
}
catch { }
}
if (files.Count > 0)
{
long removeGb = 0;
foreach (var item in files.OrderBy(i => i.Value.LastWriteTime))
{
try
{
if (File.Exists(item.Key))
{
File.Delete(item.Key);
removeGb += item.Value.Length;
// 2Gb
if (removeGb > 2147483648)
break;
}
}
catch { }
}
}
}
catch { }
finally
{
Volatile.Write(ref _updatingDb, 0);
}
}
static long getFreeDiskSpace()
{
try
{
var directory = new DirectoryInfo("cache");
var drive = DriveInfo.GetDrives()
.FirstOrDefault(d => d.IsReady && directory.FullName.StartsWith(d.RootDirectory.FullName, StringComparison.OrdinalIgnoreCase));
return drive?.AvailableFreeSpace ?? -1;
}
catch
{
return -1;
}
}
}
}

View File

@ -0,0 +1,59 @@
using Shared;
using Shared.Engine;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Lampac.Engine.CRON
{
public static class KurwaCron
{
public static void Run()
{
_cronTimer = new Timer(cron, null, TimeSpan.FromMinutes(20), TimeSpan.FromHours(5));
}
static Timer _cronTimer;
static bool _cronWork = false;
async static void cron(object state)
{
if (_cronWork)
return;
_cronWork = true;
try
{
await DownloadBigJson("externalids");
await DownloadBigJson("cdnmovies");
await DownloadBigJson("lumex");
await DownloadBigJson("veoveo");
await DownloadBigJson("kodik");
}
finally
{
_cronWork = false;
}
}
async static Task DownloadBigJson(string path)
{
try
{
using (var ms = PoolInvk.msm.GetStream())
{
bool success = await Http.DownloadToStream(ms, $"http://194.246.82.144/{path}.json");
if (success)
{
using (var fileStream = new FileStream($"data/{path}.json", FileMode.Create, FileAccess.Write, FileShare.None, PoolInvk.bufferSize))
await ms.CopyToAsync(fileStream, PoolInvk.bufferSize);
}
}
}
catch { }
}
}
}

View File

@ -0,0 +1,122 @@
using Shared;
using Shared.Engine;
using System;
using System.IO;
using System.IO.Compression;
using System.Threading;
using System.Threading.Tasks;
namespace Lampac.Engine.CRON
{
public static class LampaCron
{
static string currentapp;
public static void Run()
{
var init = AppInit.conf.LampaWeb;
_cronTimer = new Timer(cron, null, TimeSpan.FromSeconds(20), TimeSpan.FromMinutes(Math.Max(init.intervalupdate, 5)));
}
static Timer _cronTimer;
static int _updatingDb = 0;
async static void cron(object state)
{
if (Interlocked.Exchange(ref _updatingDb, 1) == 1)
return;
try
{
var init = AppInit.conf.LampaWeb;
bool istree = !string.IsNullOrEmpty(init.tree);
async ValueTask<bool> update()
{
if (!init.autoupdate)
return false;
if (!File.Exists("wwwroot/lampa-main/app.min.js"))
return true;
if (istree && File.Exists("wwwroot/lampa-main/tree") && init.tree == File.ReadAllText("wwwroot/lampa-main/tree"))
return false;
bool changeversion = false;
await Http.GetSpan(gitapp =>
{
if (!gitapp.Contains("author: 'Yumata'", StringComparison.Ordinal))
return;
if (currentapp == null)
currentapp = CrypTo.md5File("wwwroot/lampa-main/app.min.js");
if (!string.IsNullOrEmpty(currentapp) && CrypTo.md5(gitapp) != currentapp)
changeversion = true;
}, $"https://raw.githubusercontent.com/{init.git}/{(istree ? init.tree : "main")}/app.min.js", weblog: false);
if (istree)
File.WriteAllText("wwwroot/lampa-main/tree", init.tree);
return changeversion;
}
if (await update())
{
string uri = istree ?
$"https://github.com/{init.git}/archive/{init.tree}.zip" :
$"https://github.com/{init.git}/archive/refs/heads/main.zip";
byte[] array = await Http.Download(uri);
if (array != null)
{
currentapp = null;
await File.WriteAllBytesAsync("wwwroot/lampa.zip", array);
ZipFile.ExtractToDirectory("wwwroot/lampa.zip", "wwwroot/", overwriteFiles: true);
if (istree)
{
foreach (string infilePath in Directory.GetFiles($"wwwroot/lampa-{init.tree}", "*", SearchOption.AllDirectories))
{
string outfile = infilePath.Replace($"lampa-{init.tree}", "lampa-main");
Directory.CreateDirectory(Path.GetDirectoryName(outfile));
File.Copy(infilePath, outfile, true);
}
File.WriteAllText("wwwroot/lampa-main/tree", init.tree);
}
string html = File.ReadAllText("wwwroot/lampa-main/index.html");
html = html.Replace("</body>", "<script src=\"/lampainit.js\"></script></body>");
File.WriteAllText("wwwroot/lampa-main/index.html", html);
File.CreateText("wwwroot/lampa-main/personal.lampa");
if (!File.Exists("wwwroot/lampa-main/plugins_black_list.json"))
File.WriteAllText("wwwroot/lampa-main/plugins_black_list.json", "[]");
if (!File.Exists("wwwroot/lampa-main/plugins/modification.js"))
{
Directory.CreateDirectory("wwwroot/lampa-main/plugins");
File.WriteAllText("wwwroot/lampa-main/plugins/modification.js", string.Empty);
}
File.Delete("wwwroot/lampa.zip");
if (istree)
Directory.Delete($"wwwroot/lampa-{init.tree}", true);
}
}
}
catch { }
finally
{
Volatile.Write(ref _updatingDb, 0);
}
}
}
}

View File

@ -0,0 +1,81 @@
using Shared;
using Shared.Engine;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Lampac.Engine.CRON
{
public static class PluginsCron
{
public static void Run()
{
_cronTimer = new Timer(cron, null, TimeSpan.FromMinutes(2), TimeSpan.FromHours(1));
}
static Timer _cronTimer;
static bool _cronWork = false;
async static void cron(object state)
{
if (_cronWork)
return;
_cronWork = true;
try
{
if (!AppInit.conf.pirate_store)
return;
await update("https://immisterio.github.io/bwa/fx.js");
await update("https://adultjs.onrender.com", path: "adult.js");
await update("https://nb557.github.io/plugins/online_mod.js");
await update("http://github.freebie.tom.ru/want.js");
await update("https://nb557.github.io/plugins/reset_subs.js");
await update("http://193.233.134.21/plugins/mult.js");
await update("https://nemiroff.github.io/lampa/select_weapon.js");
await update("https://nb557.github.io/plugins/not_mobile.js");
await update("http://cub.red/plugin/etor", path: "etor.js");
await update("http://193.233.134.21/plugins/checker.js");
await update("https://plugin.rootu.top/ts-preload.js");
await update("https://lampame.github.io/main/pubtorr/pubtorr.js");
await update("https://lampame.github.io/main/nc/nc.js");
await update("https://nb557.github.io/plugins/rating.js");
await update("https://github.freebie.tom.ru/torrents.js");
await update("https://nnmdd.github.io/lampa_hotkeys/hotkeys.js");
await update("https://bazzzilius.github.io/scripts/gold_theme.js");
await update("https://bdvburik.github.io/rezkacomment.js");
await update("https://lampame.github.io/main/Shikimori/Shikimori.js");
}
catch { }
finally
{
_cronWork = false;
}
}
async static Task update(string url, string checkcode = "Lampa.", string path = null)
{
try
{
await Http.GetSpan(js =>
{
if (js.Contains(checkcode, StringComparison.Ordinal))
{
if (path == null)
path = Path.GetFileName(url);
File.WriteAllText($"wwwroot/plugins/{path}", js, Encoding.UTF8);
}
}, url, Encoding.UTF8, weblog: false);
}
catch { }
}
}
}

View File

@ -0,0 +1,53 @@
using Shared;
using Shared.Engine;
using Shared.Models;
using System;
using System.Threading;
namespace Lampac.Engine.CRON
{
public static class SyncCron
{
public static void Run()
{
_cronTimer = new Timer(cron, null, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(1));
}
static Timer _cronTimer;
static int _updatingDb = 0;
async static void cron(object state)
{
if (Interlocked.Exchange(ref _updatingDb, 1) == 1)
return;
try
{
var sync = AppInit.conf?.sync;
if (sync == null || !sync.enable || sync.type != "slave" || string.IsNullOrEmpty(sync.api_host) || string.IsNullOrEmpty(sync.api_passwd))
return;
var init = await Http.Get<AppInit>(sync.api_host + "/api/sync", timeoutSeconds: 5, headers: HeadersModel.Init("localrequest", sync.api_passwd), weblog: false);
if (init != null)
{
if (sync.sync_full)
{
init.sync = sync;
AppInit.conf = init;
}
else
{
AppInit.conf.accsdb.users = init.accsdb.users;
}
}
}
catch { }
finally
{
Volatile.Write(ref _updatingDb, 0);
}
}
}
}

View File

@ -0,0 +1,127 @@
using Shared;
using Shared.Engine;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Lampac.Engine.CRON
{
public static class TrackersCron
{
public static void Run()
{
_cronTimer = new Timer(cron, null, TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(AppInit.conf.dlna.intervalUpdateTrackers));
}
static Timer _cronTimer;
static int _updatingDb = 0;
async static void cron(object state)
{
if (Interlocked.Exchange(ref _updatingDb, 1) == 1)
return;
try
{
if (AppInit.modules == null || AppInit.modules.FirstOrDefault(i => i.dll == "DLNA.dll" && i.enable) == null)
return;
if (AppInit.conf.dlna.enable && AppInit.conf.dlna.autoupdatetrackers)
{
var trackers = new HashSet<string>();
var trackers_bad = new HashSet<string>();
var temp = new HashSet<string>();
foreach (string uri in new string[]
{
"http://redapi.cfhttp.top/trackers.txt",
"https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all_ip.txt",
"https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/all.txt",
"https://newtrackon.com/api/all"
})
{
string plain = await Http.Get(uri, weblog: false);
if (plain == null)
continue;
foreach (string line in plain.Replace("\r", "").Replace("\t", "").Split("\n"))
if (!string.IsNullOrEmpty(line))
temp.Add(line.Trim());
}
foreach (string url in temp)
{
if (await ckeck(url))
trackers.Add(url);
else
trackers_bad.Add(url);
}
File.WriteAllLines("cache/trackers_bad.txt", trackers_bad);
File.WriteAllLines("cache/trackers.txt", trackers.OrderByDescending(i => Regex.IsMatch(i, "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+")).ThenByDescending(i => i.StartsWith("http")));
}
}
catch { }
finally
{
Volatile.Write(ref _updatingDb, 0);
}
}
async static Task<bool> ckeck(string tracker)
{
if (string.IsNullOrWhiteSpace(tracker) || tracker.Contains("["))
return false;
if (tracker.StartsWith("http"))
{
try
{
using (var handler = new System.Net.Http.HttpClientHandler())
{
handler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new System.Net.Http.HttpClient(handler))
{
client.Timeout = TimeSpan.FromSeconds(7);
await client.GetAsync(tracker, System.Net.Http.HttpCompletionOption.ResponseHeadersRead);
return true;
}
}
}
catch { }
}
else if (tracker.StartsWith("udp:"))
{
try
{
tracker = tracker.Replace("udp://", "");
string host = tracker.Split(':')[0].Split('/')[0];
int port = tracker.Contains(":") ? int.Parse(tracker.Split(':')[1].Split('/')[0]) : 6969;
using (UdpClient client = new UdpClient(host, port))
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(7000);
string uri = Regex.Match(tracker, "^[^/]/(.*)").Groups[1].Value;
await client.SendAsync(Encoding.UTF8.GetBytes($"GET /{uri} HTTP/1.1\r\nHost: {host}\r\n\r\n"), cts.Token);
return true;
}
}
catch { }
}
return false;
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Primitives;
using System.Threading;
namespace Lampac.Engine
{
public class DynamicActionDescriptorChangeProvider : IActionDescriptorChangeProvider
{
public static DynamicActionDescriptorChangeProvider Instance { get; } = new DynamicActionDescriptorChangeProvider();
private CancellationTokenSource tokenSource = new CancellationTokenSource();
public CancellationTokenSource TokenSource => tokenSource;
public IChangeToken GetChangeToken() => new CancellationChangeToken(tokenSource.Token);
public void NotifyChanges()
{
var previous = Interlocked.Exchange(ref tokenSource, new CancellationTokenSource());
previous.Cancel();
}
}
}

View File

@ -0,0 +1,335 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared;
using Shared.Engine;
using Shared.Models;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Lampac.Engine.Middlewares
{
public class Accsdb
{
static readonly Regex rexJac = new Regex("^/(api/v2.0/indexers|api/v1.0/|toloka|rutracker|rutor|torrentby|nnmclub|kinozal|bitru|selezen|megapeer|animelayer|anilibria|anifilm|toloka|lostfilm|bigfangroup|mazepa)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static readonly Regex rexStaticAssets = new Regex("\\.(js|css|ico|png|svg|jpe?g|woff|webmanifest)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static readonly Regex rexProxyPath = new Regex("/(proxy|proxyimg([^/]+)?)/", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static readonly Regex rexTmdbPath = new Regex("^/tmdb/[^/]+/", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static readonly Regex rexLockBypass = new Regex("^/(testaccsdb|proxy/|proxyimg|lifeevents|externalids|sisi/(bookmarks|historys)|(ts|transcoding|dlna|storage|bookmark|tmdb|cub)/|timecode)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Accsdb()
{
Directory.CreateDirectory("cache/logs/accsdb");
}
private readonly RequestDelegate _next;
static bool manifestInitial = false;
IMemoryCache memoryCache;
public Accsdb(RequestDelegate next, IMemoryCache mem)
{
_next = next;
memoryCache = mem;
}
public Task Invoke(HttpContext httpContext)
{
var requestInfo = httpContext.Features.Get<RequestModel>();
#region manifest/install
if (!manifestInitial)
{
if (!File.Exists("module/manifest.json"))
{
if (httpContext.Request.Path.Value.StartsWith("/admin/manifest/install", StringComparison.OrdinalIgnoreCase))
return _next(httpContext);
httpContext.Response.Redirect("/admin/manifest/install");
return Task.CompletedTask;
}
else { manifestInitial = true; }
}
#endregion
#region admin
if (httpContext.Request.Path.Value.StartsWith("/admin", StringComparison.OrdinalIgnoreCase))
{
if (httpContext.Request.Cookies.TryGetValue("passwd", out string passwd))
{
string ipKey = $"Accsdb:auth:IP:{requestInfo.IP}";
if (!memoryCache.TryGetValue(ipKey, out ConcurrentDictionary<string, byte> passwds))
{
passwds = new ConcurrentDictionary<string, byte>();
memoryCache.Set(ipKey, passwds, DateTime.Today.AddDays(1));
}
passwds.TryAdd(passwd, 0);
if (passwds.Count > 10)
return httpContext.Response.WriteAsync("Too many attempts, try again tomorrow.", httpContext.RequestAborted);
if (passwd == AppInit.rootPasswd)
return _next(httpContext);
}
if (httpContext.Request.Path.Value.StartsWith("/admin/auth", StringComparison.OrdinalIgnoreCase))
return _next(httpContext);
httpContext.Response.Redirect("/admin/auth");
return Task.CompletedTask;
}
#endregion
if (requestInfo.IsLocalRequest || requestInfo.IsAnonymousRequest)
return _next(httpContext);
#region jacred
if (rexJac.IsMatch(httpContext.Request.Path.Value))
{
if (!string.IsNullOrEmpty(AppInit.conf.apikey))
{
if (AppInit.conf.apikey != httpContext.Request.Query["apikey"])
return Task.CompletedTask;
}
return _next(httpContext);
}
#endregion
if (AppInit.conf.accsdb.enable || (!requestInfo.IsLocalIp && !AppInit.conf.WAF.allowExternalIpAccess))
{
var accsdb = AppInit.conf.accsdb;
if (httpContext.Request.Path.Value.StartsWith("/testaccsdb", StringComparison.OrdinalIgnoreCase) && accsdb.shared_passwd != null && requestInfo.user_uid == accsdb.shared_passwd)
{
requestInfo.IsLocalRequest = true;
return _next(httpContext);
}
if (!string.IsNullOrEmpty(accsdb.premium_pattern) && !Regex.IsMatch(httpContext.Request.Path.Value, accsdb.premium_pattern, RegexOptions.IgnoreCase))
return _next(httpContext);
if (!string.IsNullOrEmpty(accsdb.whitepattern) && Regex.IsMatch(httpContext.Request.Path.Value, accsdb.whitepattern, RegexOptions.IgnoreCase))
{
requestInfo.IsAnonymousRequest = true;
return _next(httpContext);
}
bool limitip = false;
var user = requestInfo.user;
if (requestInfo.user_uid != null && accsdb.white_uids != null && accsdb.white_uids.Contains(requestInfo.user_uid))
return _next(httpContext);
string uri = httpContext.Request.Path.Value + httpContext.Request.QueryString.Value;
if (IsLockHostOrUser(memoryCache, requestInfo.user_uid, requestInfo.IP, uri, out limitip)
|| user == null
|| user.ban
|| DateTime.UtcNow > user.expires)
{
if (httpContext.Request.Path.Value.StartsWith("/proxy/", StringComparison.OrdinalIgnoreCase) ||
httpContext.Request.Path.Value.StartsWith("/proxyimg", StringComparison.OrdinalIgnoreCase))
{
string hash = rexProxyPath.Replace(httpContext.Request.Path.Value, "");
if (AppInit.conf.serverproxy.encrypt || ProxyLink.Decrypt(hash, requestInfo.IP)?.uri != null)
return _next(httpContext);
}
if (uri.StartsWith("/tmdb/api.themoviedb.org/", StringComparison.OrdinalIgnoreCase) ||
uri.StartsWith("/tmdb/api/", StringComparison.OrdinalIgnoreCase))
{
httpContext.Response.Redirect("https://api.themoviedb.org/" + rexTmdbPath.Replace(httpContext.Request.Path.Value, ""));
return Task.CompletedTask;
}
if (rexStaticAssets.IsMatch(httpContext.Request.Path.Value))
{
if (uri.StartsWith("/tmdb/image.tmdb.org/", StringComparison.OrdinalIgnoreCase) ||
uri.StartsWith("/tmdb/img/", StringComparison.OrdinalIgnoreCase))
{
httpContext.Response.Redirect("https://image.tmdb.org/" + rexTmdbPath.Replace(httpContext.Request.Path.Value, ""));
return Task.CompletedTask;
}
httpContext.Response.StatusCode = 404;
httpContext.Response.ContentType = "application/octet-stream";
return Task.CompletedTask;
}
#region msg
string msg = limitip ? $"Превышено допустимое количество ip/запросов на аккаунт."
: string.IsNullOrEmpty(requestInfo.user_uid) ? accsdb.authMesage
: accsdb.denyMesage.Replace("{account_email}", requestInfo.user_uid).Replace("{user_uid}", requestInfo.user_uid).Replace("{host}", httpContext.Request.Host.Value);
if (user != null)
{
if (user.ban)
msg = user.ban_msg ?? "Вы заблокированы";
else if (DateTime.UtcNow > user.expires)
{
msg = accsdb.expiresMesage
.Replace("{account_email}", requestInfo.user_uid)
.Replace("{user_uid}", requestInfo.user_uid)
.Replace("{expires}", user.expires.ToString("dd.MM.yyyy"));
}
}
#endregion
#region denymsg
string denymsg = limitip ? $"Превышено допустимое количество ip/запросов на аккаунт." : null;
if (user != null)
{
if (user.ban)
denymsg = user.ban_msg ?? "Вы заблокированы";
else if (DateTime.UtcNow > user.expires)
{
denymsg = accsdb.expiresMesage
.Replace("{account_email}", requestInfo.user_uid)
.Replace("{user_uid}", requestInfo.user_uid)
.Replace("{expires}", user.expires.ToString("dd.MM.yyyy"));
}
}
#endregion
return httpContext.Response.WriteAsJsonAsync(new { accsdb = true, msg, denymsg, user }, httpContext.RequestAborted);
}
}
return _next(httpContext);
}
#region IsLock
static bool IsLockHostOrUser(IMemoryCache memoryCache, string account_email, string userip, string uri, out bool islock)
{
if (string.IsNullOrEmpty(account_email))
{
islock = false;
return islock;
}
if (rexLockBypass.IsMatch(uri))
{
islock = false;
return islock;
}
if (IsLockIpHour(memoryCache, account_email, userip, out islock, out ConcurrentDictionary<string, byte> ips) |
IsLockReqHour(memoryCache, account_email, uri, out islock, out ConcurrentDictionary<string, byte> urls))
{
setLogs("lock_hour", account_email);
countlock_day(memoryCache, true, account_email);
File.WriteAllLines($"cache/logs/accsdb/{CrypTo.md5(account_email)}.ips.log", ips.Keys);
File.WriteAllLines($"cache/logs/accsdb/{CrypTo.md5(account_email)}.urls.log", urls.Keys);
return islock;
}
if (countlock_day(memoryCache, false, account_email) > AppInit.conf.accsdb.maxlock_day)
{
if (AppInit.conf.accsdb.blocked_hour != -1)
memoryCache.Set($"Accsdb:blocked_hour:{account_email}", 0, DateTime.Now.AddHours(AppInit.conf.accsdb.blocked_hour));
setLogs("lock_day", account_email);
islock = true;
return islock;
}
if (memoryCache.TryGetValue($"Accsdb:blocked_hour:{account_email}", out _))
{
setLogs("blocked", account_email);
islock = true;
return islock;
}
islock = false;
return islock;
}
static bool IsLockIpHour(IMemoryCache memoryCache, string account_email, string userip, out bool islock, out ConcurrentDictionary<string, byte> ips)
{
ips = memoryCache.GetOrCreate($"Accsdb:IsLockIpHour:{account_email}:{DateTime.Now.Hour}", entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
return new ConcurrentDictionary<string, byte>();
});
ips.TryAdd(userip, 0);
if (ips.Count > AppInit.conf.accsdb.maxip_hour)
{
islock = true;
return islock;
}
islock = false;
return islock;
}
static bool IsLockReqHour(IMemoryCache memoryCache, string account_email, string uri, out bool islock, out ConcurrentDictionary<string, byte> urls)
{
urls = memoryCache.GetOrCreate($"Accsdb:IsLockReqHour:{account_email}:{DateTime.Now.Hour}", entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
return new ConcurrentDictionary<string, byte>();
});
urls.TryAdd(uri, 0);
if (urls.Count > AppInit.conf.accsdb.maxrequest_hour)
{
islock = true;
return islock;
}
islock = false;
return islock;
}
#endregion
#region setLogs
static string logsLock = string.Empty;
static void setLogs(string name, string account_email)
{
string logFile = $"cache/logs/accsdb/{DateTime.Now:dd-MM-yyyy}.lock.txt";
if (logsLock != string.Empty && !File.Exists(logFile))
logsLock = string.Empty;
string line = $"{name} / {account_email} / {CrypTo.md5(account_email)}.*.log";
if (!logsLock.Contains(line))
{
logsLock += $"{DateTime.Now}: {line}\n";
File.WriteAllText(logFile, logsLock);
}
}
#endregion
#region countlock_day
static int countlock_day(IMemoryCache memoryCache, bool update, string account_email)
{
var lockhour = memoryCache.GetOrCreate($"Accsdb:lock_day:{account_email}:{DateTime.Now.Day}", entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1);
return new ConcurrentDictionary<int, byte>();
});
if (update)
lockhour.TryAdd(DateTime.Now.Hour, 0);
return lockhour.Count;
}
#endregion
}
}

View File

@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.WebUtilities;
using Shared;
using System;
using System.Threading.Tasks;
namespace Lampac.Engine.Middlewares
{
public class AlwaysRjson
{
private readonly RequestDelegate _next;
public AlwaysRjson(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
if (!AppInit.conf.always_rjson)
return _next(context);
var builder = new QueryBuilder();
foreach (var kv in QueryHelpers.ParseQuery(context.Request.QueryString.HasValue ? context.Request.QueryString.Value : string.Empty))
{
if (string.Equals(kv.Key, "rjson", StringComparison.OrdinalIgnoreCase))
continue;
foreach (var value in kv.Value)
builder.Add(kv.Key, value);
}
builder.Add("rjson", "true");
context.Request.QueryString = builder.ToQueryString();
return _next(context);
}
}
}

View File

@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Shared.Models;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Lampac.Engine.Middlewares
{
public class AnonymousRequest
{
private readonly RequestDelegate _next;
public AnonymousRequest(RequestDelegate next)
{
_next = next;
}
static readonly Regex rexProxy = new Regex("^/(proxy-dash|cub|ts|kit|bind)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static readonly Regex rexJs = new Regex("^/[a-zA-Z\\-]+\\.js", RegexOptions.Compiled);
public Task Invoke(HttpContext httpContext)
{
var requestInfo = httpContext.Features.Get<RequestModel>();
var endpoint = httpContext.GetEndpoint();
if (endpoint != null && endpoint.Metadata.GetMetadata<IAllowAnonymous>() != null)
requestInfo.IsAnonymousRequest = true;
if (httpContext.Request.Path.Value == "/" || httpContext.Request.Path.Value == "/favicon.ico")
requestInfo.IsAnonymousRequest = true;
if (httpContext.Request.Path.Value == "/.well-known/appspecific/com.chrome.devtools.json")
requestInfo.IsAnonymousRequest = true;
if (rexProxy.IsMatch(httpContext.Request.Path.Value))
requestInfo.IsAnonymousRequest = true;
if (rexJs.IsMatch(httpContext.Request.Path.Value))
requestInfo.IsAnonymousRequest = true;
return _next(httpContext);
}
}
}

View File

@ -0,0 +1,166 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Lampac.Engine.Middlewares
{
public class BaseMod
{
private readonly RequestDelegate _next;
public BaseMod(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
if (!HttpMethods.IsGet(context.Request.Method) &&
!HttpMethods.IsPost(context.Request.Method) &&
!HttpMethods.IsOptions(context.Request.Method))
return Task.CompletedTask;
if (!IsValidPath(context.Request.Path.Value))
{
context.Response.StatusCode = 400;
return context.Response.WriteAsync("400 Bad Request", context.RequestAborted);
}
if (Program.RuntimeCve2025_55315)
{
if (Regex.IsMatch(context.Request.Path.Value, "^/(ffprobe|transcoding|dlna|admin)", RegexOptions.IgnoreCase))
{
string ip = context.Connection.RemoteIpAddress.ToString();
if (!Shared.Engine.Utilities.IPNetwork.IsLocalIp(ip))
{
context.Response.StatusCode = 400;
return context.Response.WriteAsync("Please update dotnet\nhttps://github.com/dotnet/core/blob/main/release-notes/9.0/9.0.12/9.0.113.md", context.RequestAborted);
}
}
}
var builder = new QueryBuilder();
var dict = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
var sbQuery = new StringBuilder(32);
foreach (var q in context.Request.Query)
{
if (IsValidQueryName(q.Key))
{
string val = ValidQueryValue(sbQuery, q.Key, q.Value);
if (dict.TryAdd(q.Key, val))
builder.Add(q.Key, val);
}
}
context.Request.QueryString = builder.ToQueryString();
context.Request.Query = new QueryCollection(dict);
return _next(context);
}
#region IsValid
static bool IsValidPath(ReadOnlySpan<char> path)
{
if (path.IsEmpty)
return false;
foreach (char ch in path)
{
if (
ch == '/' || ch == '-' || ch == '.' || ch == '_' ||
ch == ':' || ch == '+' || ch == '=' ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9')
)
{
continue;
}
return false;
}
return true;
}
static bool IsValidQueryName(ReadOnlySpan<char> path)
{
if (path.IsEmpty)
return false;
foreach (char ch in path)
{
if (
ch == '-' || ch == '_' ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9') ||
ch == '.' // tmdb
)
{
continue;
}
return false;
}
return true;
}
static string ValidQueryValue(StringBuilder sb, string name, StringValues values)
{
if (values.Count == 0)
return string.Empty;
string value = values[0];
if (string.IsNullOrEmpty(value))
return string.Empty;
sb.Clear();
foreach (char ch in value)
{
if (
ch == '/' || ch == ':' || ch == '?' || ch == '&' || ch == '=' || ch == '.' || // ссылки
ch == '-' || ch == '_' || ch == ' ' || ch == ',' || // base
(ch >= '0' && ch <= '9') ||
ch == '@' || // email
ch == '+' || // aes
ch == '*' || // merchant
ch == '|' || // tmdb
char.IsLetter(ch) // ← любые буквы Unicode
)
{
sb.Append(ch);
continue;
}
if (name is "search" or "query" or "title" or "original_title" or "t")
{
if (
char.IsDigit(ch) || // ← символ цифрой Unicode
ch == '\'' || ch == '!' || ch == ',' || ch == '+' || ch == '~' || ch == '"' || ch == ';' ||
ch == '(' || ch == ')' || ch == '[' || ch == ']' || ch == '{' || ch == '}' || ch == '«' || ch == '»' || ch == '“' || ch == '”' ||
ch == '$' || ch == '%' || ch == '^' || ch == '#' || ch == '×'
)
{
sb.Append(ch);
continue;
}
}
}
return sb.ToString();
}
#endregion
}
}

View File

@ -0,0 +1,82 @@
using Microsoft.AspNetCore.Builder;
namespace Lampac.Engine.Middlewares
{
public static class Extensions
{
public static IApplicationBuilder UseBaseMod(this IApplicationBuilder builder)
{
return builder.UseMiddleware<BaseMod>();
}
public static IApplicationBuilder UseWAF(this IApplicationBuilder builder)
{
return builder.UseMiddleware<WAF>();
}
public static IApplicationBuilder UseModHeaders(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ModHeaders>();
}
public static IApplicationBuilder UseRequestStatistics(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestStatistics>();
}
public static IApplicationBuilder UseOverrideResponse(this IApplicationBuilder builder, bool first)
{
return builder.UseMiddleware<OverrideResponse>(first);
}
public static IApplicationBuilder UseRequestInfo(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestInfo>();
}
public static IApplicationBuilder UseAnonymousRequest(this IApplicationBuilder builder)
{
return builder.UseMiddleware<AnonymousRequest>();
}
public static IApplicationBuilder UseAlwaysRjson(this IApplicationBuilder builder)
{
return builder.UseMiddleware<AlwaysRjson>();
}
public static IApplicationBuilder UseAccsdb(this IApplicationBuilder builder)
{
return builder.UseMiddleware<Accsdb>();
}
public static IApplicationBuilder UseProxyAPI(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ProxyAPI>();
}
public static IApplicationBuilder UseProxyIMG(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ProxyImg>();
}
public static IApplicationBuilder UseProxyCub(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ProxyCub>();
}
public static IApplicationBuilder UseProxyTmdb(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ProxyTmdb>();
}
public static IApplicationBuilder UseModule(this IApplicationBuilder builder, bool first)
{
return builder.UseMiddleware<Module>(first);
}
public static IApplicationBuilder UseStaticache(this IApplicationBuilder builder)
{
return builder.UseMiddleware<Staticache>();
}
}
}

View File

@ -0,0 +1,98 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Lampac.Engine.Middlewares
{
public class ModHeaders
{
private readonly RequestDelegate _next;
public ModHeaders(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext httpContext)
{
if (httpContext.Request.Path.Value.StartsWith("/cors/check", StringComparison.OrdinalIgnoreCase))
return Task.CompletedTask;
httpContext.Response.Headers["Access-Control-Allow-Credentials"] = "true";
httpContext.Response.Headers["Access-Control-Allow-Private-Network"] = "true";
httpContext.Response.Headers["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS";
if (GetAllowHeaders(httpContext, out HashSet<string> allowHeadersSet))
httpContext.Response.Headers["Access-Control-Allow-Headers"] = string.Join(", ", allowHeadersSet);
else
httpContext.Response.Headers["Access-Control-Allow-Headers"] = stringAllowHeaders;
if (httpContext.Request.Headers.TryGetValue("origin", out var origin))
httpContext.Response.Headers["Access-Control-Allow-Origin"] = GetOrigin(origin);
else if (httpContext.Request.Headers.TryGetValue("referer", out var referer))
httpContext.Response.Headers["Access-Control-Allow-Origin"] = GetOrigin(referer);
else
httpContext.Response.Headers["Access-Control-Allow-Origin"] = "*";
if (Regex.IsMatch(httpContext.Request.Path.Value, "^/(lampainit|sisi|lite|online|tmdbproxy|cubproxy|tracks|transcoding|dlna|timecode|bookmark|catalog|sync|backup|ts|invc-ws)\\.js", RegexOptions.IgnoreCase) ||
Regex.IsMatch(httpContext.Request.Path.Value, "^/(on/|(lite|online|sisi|timecode|bookmark|sync|tmdbproxy|dlna|ts|tracks|transcoding|backup|catalog|invc-ws)/js/)", RegexOptions.IgnoreCase))
{
httpContext.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate"; // HTTP 1.1.
httpContext.Response.Headers["Pragma"] = "no-cache"; // HTTP 1.0.
httpContext.Response.Headers["Expires"] = "0"; // Proxies.
}
if (HttpMethods.IsOptions(httpContext.Request.Method))
return Task.CompletedTask;
return _next(httpContext);
}
static readonly string stringAllowHeaders = "Authorization, Token, Profile, X-Kit-AesGcm, Content-Type, X-Signalr-User-Agent, X-Requested-With";
static readonly HashSet<string> hashAllowHeaders = new HashSet<string>(
[
"Authorization", "Token", "Profile", "X-Kit-AesGcm",
"Content-Type", "X-Signalr-User-Agent", "X-Requested-With"
], StringComparer.OrdinalIgnoreCase);
static bool GetAllowHeaders(HttpContext httpContext, out HashSet<string> headersSet)
{
if (httpContext.Request.Headers.TryGetValue("Access-Control-Request-Headers", out var requestedHeaders))
{
headersSet = [.. hashAllowHeaders];
foreach (string header in requestedHeaders.ToString().Split(',', StringSplitOptions.RemoveEmptyEntries))
{
if (!string.IsNullOrWhiteSpace(header))
headersSet.Add(header.Trim());
}
return true;
}
headersSet = null;
return false;
}
static string GetOrigin(string url)
{
if (string.IsNullOrEmpty(url))
return string.Empty;
int scheme = url.IndexOf("://", StringComparison.Ordinal);
if (scheme <= 0)
return url;
int start = scheme + 3;
int slash = url.IndexOf('/', start);
if (slash < 0)
return url; // уже origin
return url.Substring(0, slash);
}
}
}

View File

@ -0,0 +1,90 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Shared;
using Shared.Engine;
using Shared.Models;
using Shared.Models.Events;
using Shared.Models.Module.Entrys;
using System.Threading.Tasks;
namespace Lampac.Engine.Middlewares
{
public class Module
{
private readonly RequestDelegate _next;
IMemoryCache memoryCache;
private readonly bool first;
public Module(RequestDelegate next, IMemoryCache mem, bool first)
{
_next = next;
memoryCache = mem;
this.first = first;
}
async public Task InvokeAsync(HttpContext httpContext)
{
#region modules
MiddlewaresModuleEntry.EnsureCache();
if (MiddlewaresModuleEntry.middlewareModulesCache != null && MiddlewaresModuleEntry.middlewareModulesCache.Count > 0)
{
foreach (var entry in MiddlewaresModuleEntry.middlewareModulesCache)
{
var mod = entry.mod;
try
{
if (first && (mod.version == 0 || mod.version == 1))
continue;
if (mod.version >= 2)
{
if (entry.Invoke != null)
{
bool next = entry.Invoke(first, httpContext, memoryCache);
if (!next)
return;
}
if (entry.InvokeAsync != null)
{
bool next = await entry.InvokeAsync(first, httpContext, memoryCache);
if (!next)
return;
}
}
else
{
if (entry.InvokeV1 != null)
{
bool next = entry.InvokeV1(httpContext, memoryCache);
if (!next)
return;
}
if (entry.InvokeAsyncV1 != null)
{
bool next = await entry.InvokeAsyncV1(httpContext, memoryCache);
if (!next)
return;
}
}
}
catch { }
}
}
#endregion
if (InvkEvent.IsMiddleware(first))
{
var rqinfo = httpContext.Features.Get<RequestModel>();
bool next = await InvkEvent.Middleware(first, new EventMiddleware(rqinfo, httpContext.Request, httpContext, IHybridCache.Get(rqinfo), memoryCache));
if (!next)
return;
}
await _next(httpContext);
}
}
}

Some files were not shown because too many files have changed in this diff Show More