chore: initial commit 154.3
Signed-off-by: lampac-talks <lampac-talks@users.noreply.github.com>
This commit is contained in:
commit
f843f04fd4
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
343
.gitignore
vendored
Normal file
343
.gitignore
vendored
Normal 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
|
||||||
13
BaseModule/BaseModule.csproj
Normal file
13
BaseModule/BaseModule.csproj
Normal 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>
|
||||||
724
BaseModule/Controllers/AdminController.cs
Normal file
724
BaseModule/Controllers/AdminController.cs
Normal 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 />
|
||||||
|
Aдрес: {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'>
|
||||||
|
<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
|
||||||
|
}
|
||||||
|
}
|
||||||
798
BaseModule/Controllers/BookmarkController.cs
Normal file
798
BaseModule/Controllers/BookmarkController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
33
BaseModule/Controllers/ChromiumController.cs
Normal file
33
BaseModule/Controllers/ChromiumController.cs
Normal 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>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
BaseModule/Controllers/CmdController.cs
Normal file
57
BaseModule/Controllers/CmdController.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
453
BaseModule/Controllers/CorseuController.cs
Normal file
453
BaseModule/Controllers/CorseuController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
26
BaseModule/Controllers/CubController.cs
Normal file
26
BaseModule/Controllers/CubController.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
BaseModule/Controllers/ErrorDocController.cs
Normal file
109
BaseModule/Controllers/ErrorDocController.cs
Normal 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> cмогут самостоятельно авторизоваться</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>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
626
BaseModule/Controllers/LampaWebController.cs
Normal file
626
BaseModule/Controllers/LampaWebController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
203
BaseModule/Controllers/MediaController.cs
Normal file
203
BaseModule/Controllers/MediaController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
37
BaseModule/Controllers/PlayerInnerController.cs
Normal file
37
BaseModule/Controllers/PlayerInnerController.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
BaseModule/Controllers/RchApiController.cs
Normal file
85
BaseModule/Controllers/RchApiController.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
356
BaseModule/Controllers/StorageController.cs
Normal file
356
BaseModule/Controllers/StorageController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
38
BaseModule/Controllers/SyncApiController.cs
Normal file
38
BaseModule/Controllers/SyncApiController.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
118
BaseModule/Controllers/TimecodeController.cs
Normal file
118
BaseModule/Controllers/TimecodeController.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
BaseModule/Controllers/TmdbController.cs
Normal file
23
BaseModule/Controllers/TmdbController.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
204
BaseModule/Controllers/WebLogController.cs
Normal file
204
BaseModule/Controllers/WebLogController.cs
Normal 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, '<').replace(/>/g, '>');
|
||||||
|
|
||||||
|
const markers = [
|
||||||
|
{{ text: 'CurrentUrl: ', caseSensitive: true }},
|
||||||
|
{{ text: 'StatusCode: ', caseSensitive: true }},
|
||||||
|
{{ text: '<!doctype html>', 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
34
Build/Docker/amd64
Normal 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
35
Build/Docker/arm32
Normal 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
36
Build/Docker/arm64
Normal 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
30
Build/Docker/koyeb
Normal 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
25
Build/Docker/mircloud
Normal 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
27
Build/Docker/northflank
Normal 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
10
Build/Docker/update.sh
Normal 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
|
||||||
40
Build/cloudflare/Lampac.csproj
Normal file
40
Build/cloudflare/Lampac.csproj
Normal 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>
|
||||||
6
Build/cloudflare/deploy.sh
Normal file
6
Build/cloudflare/deploy.sh
Normal 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
|
||||||
72
Build/cloudflare/nightlies.sh
Normal file
72
Build/cloudflare/nightlies.sh
Normal 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/
|
||||||
38
Build/cloudflare/nightlies_update.sh
Normal file
38
Build/cloudflare/nightlies_update.sh
Normal 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
214
Catalog/ApiController.cs
Normal 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
397
Catalog/CardController.cs
Normal 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("&", "&").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
13
Catalog/Catalog.csproj
Normal 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
18
Catalog/GlobalUsings.cs
Normal 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
547
Catalog/ListController.cs
Normal 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("&", "&").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(" ", "");
|
||||||
|
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("&", "&").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
401
Catalog/ModInit.cs
Normal 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(" ", "");
|
||||||
|
text = Regex.Replace(text, "<[^>]+>", "");
|
||||||
|
text = HttpUtility.HtmlDecode(text);
|
||||||
|
return text.Trim();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
1181
DLNA/ApiController.cs
Normal file
1181
DLNA/ApiController.cs
Normal file
File diff suppressed because it is too large
Load Diff
13
DLNA/DLNA.csproj
Normal file
13
DLNA/DLNA.csproj
Normal 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
188
DLNA/ModInit.cs
Normal 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
35
DLNA/Models/DlnaModel.cs
Normal 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
9
DLNA/Models/Subtitle.cs
Normal 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
375
JacRed/ApiController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
74
JacRed/Controllers/AniLibriaController.cs
Normal file
74
JacRed/Controllers/AniLibriaController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
149
JacRed/Controllers/AnifilmController.cs
Normal file
149
JacRed/Controllers/AnifilmController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
219
JacRed/Controllers/AnimeLayerController.cs
Normal file
219
JacRed/Controllers/AnimeLayerController.cs
Normal 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>( )?[0-9]+ [^ ]+ [0-9]{4}"))
|
||||||
|
{
|
||||||
|
createTime = tParse.ParseCreateTime(Match(">(Добавл|Обновл)[^<]+</span>( )?([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>( )?([0-9]+)", 2);
|
||||||
|
string _pir = Match("class=\"icon s-icons-download\"></i>( )?([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
|
||||||
|
}
|
||||||
|
}
|
||||||
153
JacRed/Controllers/BigFanGroup.cs
Normal file
153
JacRed/Controllers/BigFanGroup.cs
Normal 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(" ", " "));
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
151
JacRed/Controllers/BitruController.cs
Normal file
151
JacRed/Controllers/BitruController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
269
JacRed/Controllers/KinozalController.cs
Normal file
269
JacRed/Controllers/KinozalController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
235
JacRed/Controllers/LostfilmController.cs
Normal file
235
JacRed/Controllers/LostfilmController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
102
JacRed/Controllers/MegapeerController.cs
Normal file
102
JacRed/Controllers/MegapeerController.cs
Normal 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(" ", " "));
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
279
JacRed/Controllers/NNMClubController.cs
Normal file
279
JacRed/Controllers/NNMClubController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
103
JacRed/Controllers/RutorController.cs
Normal file
103
JacRed/Controllers/RutorController.cs
Normal 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(" ", " ").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
|
||||||
|
}
|
||||||
|
}
|
||||||
450
JacRed/Controllers/RutrackerController.cs
Normal file
450
JacRed/Controllers/RutrackerController.cs
Normal 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]+\">([^<]+) ↓</a>").Replace(" ", " ");
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
215
JacRed/Controllers/SelezenController.cs
Normal file
215
JacRed/Controllers/SelezenController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
399
JacRed/Controllers/TolokaController.cs
Normal file
399
JacRed/Controllers/TolokaController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
115
JacRed/Controllers/TorrentByController.cs
Normal file
115
JacRed/Controllers/TorrentByController.cs
Normal 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(" ", " "));
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
39
JacRed/Engine/FileDB/FileDB.cs
Normal file
39
JacRed/Engine/FileDB/FileDB.cs
Normal 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 _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
146
JacRed/Engine/FileDB/staticDB.cs
Normal file
146
JacRed/Engine/FileDB/staticDB.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
33
JacRed/Engine/JacBaseController.cs
Normal file
33
JacRed/Engine/JacBaseController.cs
Normal 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
286
JacRed/Engine/JackettApi.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
59
JacRed/Engine/JsonStream.cs
Normal file
59
JacRed/Engine/JsonStream.cs
Normal 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
569
JacRed/Engine/RedApi.cs
Normal 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
117
JacRed/Engine/SyncCron.cs
Normal 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
99
JacRed/Engine/WebApi.cs
Normal 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
16
JacRed/GlobalUsings.cs
Normal 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
13
JacRed/JacRed.csproj
Normal 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
117
JacRed/ModInit.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
9
JacRed/Models/AniLibria/Names.cs
Normal file
9
JacRed/Models/AniLibria/Names.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace JacRed.Models.AniLibria
|
||||||
|
{
|
||||||
|
public class Names
|
||||||
|
{
|
||||||
|
public string ru { get; set; }
|
||||||
|
|
||||||
|
public string en { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
11
JacRed/Models/AniLibria/Quality.cs
Normal file
11
JacRed/Models/AniLibria/Quality.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
JacRed/Models/AniLibria/RootObject.cs
Normal file
17
JacRed/Models/AniLibria/RootObject.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
9
JacRed/Models/AniLibria/Season.cs
Normal file
9
JacRed/Models/AniLibria/Season.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace JacRed.Models.AniLibria
|
||||||
|
{
|
||||||
|
public class Season
|
||||||
|
{
|
||||||
|
public int year { get; set; }
|
||||||
|
|
||||||
|
public int code { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
7
JacRed/Models/AniLibria/Series.cs
Normal file
7
JacRed/Models/AniLibria/Series.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace JacRed.Models.AniLibria
|
||||||
|
{
|
||||||
|
public class Series
|
||||||
|
{
|
||||||
|
public string @string { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
JacRed/Models/AniLibria/Torrent.cs
Normal file
17
JacRed/Models/AniLibria/Torrent.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
7
JacRed/Models/AniLibria/Torrents.cs
Normal file
7
JacRed/Models/AniLibria/Torrents.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace JacRed.Models.AniLibria
|
||||||
|
{
|
||||||
|
public class Torrents
|
||||||
|
{
|
||||||
|
public List<Torrent> list { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
13
JacRed/Models/AppConf/Evercache.cs
Normal file
13
JacRed/Models/AppConf/Evercache.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
JacRed/Models/AppConf/JacConf.cs
Normal file
40
JacRed/Models/AppConf/JacConf.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
9
JacRed/Models/AppConf/LoginSettings.cs
Normal file
9
JacRed/Models/AppConf/LoginSettings.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace JacRed.Models.AppConf
|
||||||
|
{
|
||||||
|
public class LoginSettings
|
||||||
|
{
|
||||||
|
public string u { get; set; }
|
||||||
|
|
||||||
|
public string p { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
19
JacRed/Models/AppConf/RedConf.cs
Normal file
19
JacRed/Models/AppConf/RedConf.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
JacRed/Models/AppConf/TrackerSettings.cs
Normal file
43
JacRed/Models/AppConf/TrackerSettings.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
9
JacRed/Models/Sync/Collection.cs
Normal file
9
JacRed/Models/Sync/Collection.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace JacRed.Models.Sync
|
||||||
|
{
|
||||||
|
public class Collection
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
public Value Value { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
9
JacRed/Models/Sync/RootObject.cs
Normal file
9
JacRed/Models/Sync/RootObject.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace JacRed.Models.Sync
|
||||||
|
{
|
||||||
|
public class RootObject
|
||||||
|
{
|
||||||
|
public bool nextread { get; set; }
|
||||||
|
|
||||||
|
public List<Collection> collections { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
11
JacRed/Models/Sync/Value.cs
Normal file
11
JacRed/Models/Sync/Value.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
15
JacRed/Models/WriteTaskModel.cs
Normal file
15
JacRed/Models/WriteTaskModel.cs
Normal 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
98
Lampac.sln
Normal 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
|
||||||
162
Lampac/Controllers/ApiController.cs
Normal file
162
Lampac/Controllers/ApiController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
196
Lampac/Controllers/OpenStatController.cs
Normal file
196
Lampac/Controllers/OpenStatController.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
116
Lampac/Engine/CRON/CacheCron.cs
Normal file
116
Lampac/Engine/CRON/CacheCron.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
Lampac/Engine/CRON/KurwaCron.cs
Normal file
59
Lampac/Engine/CRON/KurwaCron.cs
Normal 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 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Lampac/Engine/CRON/LampaCron.cs
Normal file
122
Lampac/Engine/CRON/LampaCron.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Lampac/Engine/CRON/PluginsCron.cs
Normal file
81
Lampac/Engine/CRON/PluginsCron.cs
Normal 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 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Lampac/Engine/CRON/SyncCron.cs
Normal file
53
Lampac/Engine/CRON/SyncCron.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
127
Lampac/Engine/CRON/TrackersCron.cs
Normal file
127
Lampac/Engine/CRON/TrackersCron.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Lampac/Engine/DynamicActionDescriptorChangeProvider.cs
Normal file
22
Lampac/Engine/DynamicActionDescriptorChangeProvider.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
335
Lampac/Engine/Middlewares/Accsdb.cs
Normal file
335
Lampac/Engine/Middlewares/Accsdb.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Lampac/Engine/Middlewares/AlwaysRjson.cs
Normal file
42
Lampac/Engine/Middlewares/AlwaysRjson.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Lampac/Engine/Middlewares/AnonymousRequest.cs
Normal file
43
Lampac/Engine/Middlewares/AnonymousRequest.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
166
Lampac/Engine/Middlewares/BaseMod.cs
Normal file
166
Lampac/Engine/Middlewares/BaseMod.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
82
Lampac/Engine/Middlewares/Extensions.cs
Normal file
82
Lampac/Engine/Middlewares/Extensions.cs
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
Lampac/Engine/Middlewares/ModHeaders.cs
Normal file
98
Lampac/Engine/Middlewares/ModHeaders.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
90
Lampac/Engine/Middlewares/Module.cs
Normal file
90
Lampac/Engine/Middlewares/Module.cs
Normal 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
Loading…
x
Reference in New Issue
Block a user