mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-04-16 09:22:21 +00:00
475 lines
19 KiB
C#
475 lines
19 KiB
C#
using JackTor.Models;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Shared;
|
|
using Shared.Engine;
|
|
using Shared.Models;
|
|
using Shared.Models.Online.PiTor;
|
|
using Shared.Models.Online.Settings;
|
|
using Shared.Models.Templates;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using System.Web;
|
|
|
|
namespace JackTor.Controllers
|
|
{
|
|
public class Controller : BaseOnlineController<JackTorSettings>
|
|
{
|
|
ProxyManager proxyManager;
|
|
|
|
public Controller() : base(ModInit.Settings)
|
|
{
|
|
proxyManager = new ProxyManager(ModInit.Settings);
|
|
}
|
|
|
|
[HttpGet]
|
|
[Route("jacktor")]
|
|
async public Task<ActionResult> Index(
|
|
long id,
|
|
string imdb_id,
|
|
long kinopoisk_id,
|
|
string title,
|
|
string original_title,
|
|
string original_language,
|
|
int year,
|
|
string source,
|
|
int serial,
|
|
string account_email,
|
|
string t = null,
|
|
int s = -1,
|
|
bool rjson = false,
|
|
bool checksearch = false)
|
|
{
|
|
await UpdateService.ConnectAsync(host);
|
|
|
|
var init = await loadKit(ModInit.Settings);
|
|
if (!init.enable)
|
|
return Forbid();
|
|
|
|
if (NoAccessGroup(init, out string error_msg))
|
|
return Json(new { accsdb = true, error_msg });
|
|
|
|
var invoke = new JackTorInvoke(init, hybridCache, OnLog, proxyManager);
|
|
|
|
if (checksearch)
|
|
{
|
|
if (AppInit.conf?.online?.checkOnlineSearch != true)
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
var check = await invoke.Search(title, original_title, year, serial, original_language);
|
|
if (check.Count > 0)
|
|
return Content("data-json=", "text/plain; charset=utf-8");
|
|
|
|
return OnError("jacktor", proxyManager);
|
|
}
|
|
|
|
var torrents = await invoke.Search(title, original_title, year, serial, original_language);
|
|
if (torrents == null || torrents.Count == 0)
|
|
{
|
|
string debugInfo = $"title={title}\noriginal_title={original_title}\nyear={year}\nserial={serial}\njackett={MaskSensitiveUrl(init.jackett)}\nmin_sid={init.min_sid}\nmin_peers={init.min_peers}";
|
|
return OnError("jacktor", refresh_proxy: true, weblog: debugInfo);
|
|
}
|
|
|
|
if (serial == 1)
|
|
{
|
|
var seasons = torrents
|
|
.Where(i => i.Seasons != null && i.Seasons.Length > 0)
|
|
.SelectMany(i => i.Seasons)
|
|
.Distinct()
|
|
.OrderBy(i => i)
|
|
.ToList();
|
|
|
|
string enTitle = HttpUtility.UrlEncode(title);
|
|
string enOriginal = HttpUtility.UrlEncode(original_title);
|
|
|
|
if (s == -1 && seasons.Count > 0)
|
|
{
|
|
string quality = torrents.FirstOrDefault(i => i.Quality >= 2160)?.QualityLabel
|
|
?? torrents.FirstOrDefault(i => i.Quality >= 1080)?.QualityLabel
|
|
?? torrents.FirstOrDefault(i => i.Quality >= 720)?.QualityLabel
|
|
?? "720p";
|
|
|
|
var seasonTpl = new SeasonTpl(quality: quality);
|
|
foreach (int season in seasons)
|
|
{
|
|
seasonTpl.Append(
|
|
$"{season} сезон",
|
|
$"{host}/jacktor?rjson={rjson}&title={enTitle}&original_title={enOriginal}&year={year}&original_language={original_language}&serial=1&s={season}",
|
|
season);
|
|
}
|
|
|
|
return rjson
|
|
? Content(seasonTpl.ToJson(), "application/json; charset=utf-8")
|
|
: Content(seasonTpl.ToHtml(), "text/html; charset=utf-8");
|
|
}
|
|
|
|
int targetSeason = s == -1 ? 1 : s;
|
|
var releases = torrents
|
|
.Where(i => i.Seasons == null || i.Seasons.Length == 0 || i.Seasons.Contains(targetSeason))
|
|
.ToList();
|
|
|
|
if (releases.Count == 0)
|
|
releases = torrents;
|
|
|
|
var similarTpl = new SimilarTpl();
|
|
foreach (var torrent in releases)
|
|
{
|
|
string releaseName = string.IsNullOrWhiteSpace(torrent.Voice)
|
|
? (torrent.Tracker ?? "Без назви")
|
|
: torrent.Voice;
|
|
|
|
string qualityInfo = $"{torrent.QualityLabel} / {torrent.MediaInfo} / ↑{torrent.Seeders}";
|
|
string releaseLink = accsArgs($"{host}/jacktor/serial/{torrent.Rid}?rjson={rjson}&title={enTitle}&original_title={enOriginal}&s={targetSeason}");
|
|
|
|
similarTpl.Append(releaseName, null, qualityInfo, releaseLink);
|
|
}
|
|
|
|
return rjson
|
|
? Content(similarTpl.ToJson(), "application/json; charset=utf-8")
|
|
: Content(similarTpl.ToHtml(), "text/html; charset=utf-8");
|
|
}
|
|
else
|
|
{
|
|
var movieTpl = new MovieTpl(title, original_title);
|
|
|
|
foreach (var torrent in torrents)
|
|
{
|
|
string voice = string.IsNullOrWhiteSpace(torrent.Voice)
|
|
? (torrent.Tracker ?? "Торрент")
|
|
: torrent.Voice;
|
|
|
|
string voiceName = $"{torrent.QualityLabel} / {torrent.MediaInfo} / ↑{torrent.Seeders}";
|
|
string streamLink = accsArgs($"{host}/jacktor/s{torrent.Rid}");
|
|
|
|
movieTpl.Append(
|
|
voice,
|
|
streamLink,
|
|
voice_name: voiceName,
|
|
quality: torrent.Quality > 0 ? torrent.Quality.ToString() : null);
|
|
}
|
|
|
|
return rjson
|
|
? Content(movieTpl.ToJson(), "application/json; charset=utf-8")
|
|
: Content(movieTpl.ToHtml(), "text/html; charset=utf-8");
|
|
}
|
|
}
|
|
|
|
[HttpGet]
|
|
[Route("jacktor/serial/{rid}")]
|
|
async public ValueTask<ActionResult> Serial(string rid, string account_email, string title, string original_title, int s = 1, bool rjson = false)
|
|
{
|
|
var init = await loadKit(ModInit.Settings);
|
|
if (!init.enable)
|
|
return Forbid();
|
|
|
|
if (NoAccessGroup(init, out string error_msg))
|
|
return Json(new { accsdb = true, error_msg });
|
|
|
|
var invoke = new JackTorInvoke(init, hybridCache, OnLog, proxyManager);
|
|
if (!invoke.TryGetSource(rid, out JackTorSourceCache source))
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
string memKey = $"jacktor:serial:{rid}";
|
|
|
|
return await InvkSemaphore(memKey, null, async () =>
|
|
{
|
|
if (!hybridCache.TryGetValue(memKey, out FileStat[] fileStats))
|
|
{
|
|
var ts = ResolveProbeTorrentServer(init, account_email);
|
|
if (string.IsNullOrWhiteSpace(ts.host))
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
string hashResponse = await Http.Post(
|
|
$"{ts.host}/torrents",
|
|
BuildAddPayload(source.SourceUri),
|
|
timeoutSeconds: 8,
|
|
headers: ts.headers);
|
|
|
|
string hash = ExtractHash(hashResponse);
|
|
if (string.IsNullOrWhiteSpace(hash))
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
Stat stat = null;
|
|
DateTime deadline = DateTime.Now.AddSeconds(20);
|
|
|
|
while (true)
|
|
{
|
|
stat = await Http.Post<Stat>(
|
|
$"{ts.host}/torrents",
|
|
BuildGetPayload(hash),
|
|
timeoutSeconds: 3,
|
|
headers: ts.headers);
|
|
|
|
if (stat?.file_stats != null && stat.file_stats.Length > 0)
|
|
break;
|
|
|
|
if (DateTime.Now > deadline)
|
|
{
|
|
_ = Http.Post($"{ts.host}/torrents", BuildRemovePayload(hash), headers: ts.headers);
|
|
return OnError("jacktor", proxyManager);
|
|
}
|
|
|
|
await Task.Delay(250);
|
|
}
|
|
|
|
_ = Http.Post($"{ts.host}/torrents", BuildRemovePayload(hash), headers: ts.headers);
|
|
|
|
fileStats = stat.file_stats;
|
|
hybridCache.Set(memKey, fileStats, DateTime.Now.AddHours(36));
|
|
}
|
|
|
|
if (fileStats == null || fileStats.Length == 0)
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
var episodeTpl = new EpisodeTpl();
|
|
int appended = 0;
|
|
|
|
foreach (var file in fileStats.OrderBy(i => i.Id))
|
|
{
|
|
if (!IsVideoFile(file.Path))
|
|
continue;
|
|
|
|
episodeTpl.Append(
|
|
Path.GetFileName(file.Path),
|
|
title ?? original_title,
|
|
s.ToString(),
|
|
file.Id.ToString(),
|
|
accsArgs($"{host}/jacktor/s{rid}?tsid={file.Id}"));
|
|
|
|
appended++;
|
|
}
|
|
|
|
if (appended == 0)
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
return rjson
|
|
? Content(episodeTpl.ToJson(), "application/json; charset=utf-8")
|
|
: Content(episodeTpl.ToHtml(), "text/html; charset=utf-8");
|
|
});
|
|
}
|
|
|
|
[HttpGet]
|
|
[Route("jacktor/s{rid}")]
|
|
async public ValueTask<ActionResult> Stream(string rid, int tsid = -1, string account_email = null)
|
|
{
|
|
var init = await loadKit(ModInit.Settings);
|
|
if (!init.enable)
|
|
return Forbid();
|
|
|
|
if (NoAccessGroup(init, out string error_msg))
|
|
return Json(new { accsdb = true, error_msg });
|
|
|
|
var invoke = new JackTorInvoke(init, hybridCache, OnLog, proxyManager);
|
|
if (!invoke.TryGetSource(rid, out JackTorSourceCache source))
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
int index = tsid != -1 ? tsid : 1;
|
|
string country = requestInfo.Country;
|
|
|
|
async ValueTask<ActionResult> AuthStream(string tsHost, string login, string passwd, string uhost = null, Dictionary<string, string> addheaders = null)
|
|
{
|
|
string memKey = $"jacktor:auth_stream:{rid}:{uhost ?? tsHost}";
|
|
if (!hybridCache.TryGetValue(memKey, out string hash))
|
|
{
|
|
login = (login ?? string.Empty).Replace("{account_email}", account_email ?? string.Empty);
|
|
|
|
var headers = HeadersModel.Init("Authorization", $"Basic {CrypTo.Base64($"{login}:{passwd}")}");
|
|
headers = HeadersModel.Join(headers, addheaders);
|
|
|
|
string response = await Http.Post(
|
|
$"{tsHost}/torrents",
|
|
BuildAddPayload(source.SourceUri),
|
|
timeoutSeconds: 5,
|
|
headers: headers);
|
|
|
|
hash = ExtractHash(response);
|
|
if (string.IsNullOrWhiteSpace(hash))
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
hybridCache.Set(memKey, hash, DateTime.Now.AddMinutes(1));
|
|
}
|
|
|
|
return Redirect($"{uhost ?? tsHost}/stream?link={hash}&index={index}&play");
|
|
}
|
|
|
|
if ((init.torrs == null || init.torrs.Length == 0) && (init.auth_torrs == null || init.auth_torrs.Count == 0))
|
|
{
|
|
if (TryReadLocalTorrServerPassword(out string localPassword))
|
|
{
|
|
return await AuthStream(
|
|
$"http://{AppInit.conf.listen.localhost}:9080",
|
|
"ts",
|
|
localPassword,
|
|
uhost: $"{host}/ts");
|
|
}
|
|
|
|
return Redirect($"{host}/ts/stream?link={HttpUtility.UrlEncode(source.SourceUri)}&index={index}&play");
|
|
}
|
|
|
|
if (init.auth_torrs != null && init.auth_torrs.Count > 0)
|
|
{
|
|
string tsKey = $"jacktor:ts2:{rid}:{requestInfo.IP}";
|
|
if (!hybridCache.TryGetValue(tsKey, out PidTorAuthTS ts))
|
|
{
|
|
var servers = init.auth_torrs.Where(i => i.enable).ToList();
|
|
if (country != null)
|
|
{
|
|
servers = servers
|
|
.Where(i => i.country == null || i.country.Contains(country))
|
|
.Where(i => i.no_country == null || !i.no_country.Contains(country))
|
|
.ToList();
|
|
}
|
|
|
|
if (servers.Count == 0)
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
ts = servers[Random.Shared.Next(0, servers.Count)];
|
|
hybridCache.Set(tsKey, ts, DateTime.Now.AddHours(4));
|
|
}
|
|
|
|
return await AuthStream(ts.host, ts.login, ts.passwd, addheaders: ts.headers);
|
|
}
|
|
else
|
|
{
|
|
if (init.base_auth != null && init.base_auth.enable)
|
|
{
|
|
if (init.torrs == null || init.torrs.Length == 0)
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
string tsKey = $"jacktor:ts3:{rid}:{requestInfo.IP}";
|
|
if (!hybridCache.TryGetValue(tsKey, out string tsHost))
|
|
{
|
|
tsHost = init.torrs[Random.Shared.Next(0, init.torrs.Length)];
|
|
hybridCache.Set(tsKey, tsHost, DateTime.Now.AddHours(4));
|
|
}
|
|
|
|
return await AuthStream(tsHost, init.base_auth.login, init.base_auth.passwd, addheaders: init.base_auth.headers);
|
|
}
|
|
|
|
if (init.torrs == null || init.torrs.Length == 0)
|
|
return OnError("jacktor", proxyManager);
|
|
|
|
string key = $"jacktor:ts4:{rid}:{requestInfo.IP}";
|
|
if (!hybridCache.TryGetValue(key, out string torrentHost))
|
|
{
|
|
torrentHost = init.torrs[Random.Shared.Next(0, init.torrs.Length)];
|
|
hybridCache.Set(key, torrentHost, DateTime.Now.AddHours(4));
|
|
}
|
|
|
|
return Redirect($"{torrentHost}/stream?link={HttpUtility.UrlEncode(source.SourceUri)}&index={index}&play");
|
|
}
|
|
}
|
|
|
|
private (List<HeadersModel> headers, string host) ResolveProbeTorrentServer(JackTorSettings init, string account_email)
|
|
{
|
|
if ((init.torrs == null || init.torrs.Length == 0) && (init.auth_torrs == null || init.auth_torrs.Count == 0))
|
|
{
|
|
if (TryReadLocalTorrServerPassword(out string localPassword))
|
|
{
|
|
var headers = HeadersModel.Init("Authorization", $"Basic {CrypTo.Base64($"ts:{localPassword}")}");
|
|
return (headers, $"http://{AppInit.conf.listen.localhost}:9080");
|
|
}
|
|
|
|
return (null, $"http://{AppInit.conf.listen.localhost}:9080");
|
|
}
|
|
|
|
if (init.auth_torrs != null && init.auth_torrs.Count > 0)
|
|
{
|
|
var ts = init.auth_torrs.FirstOrDefault(i => i.enable) ?? init.auth_torrs.First();
|
|
string login = (ts.login ?? string.Empty).Replace("{account_email}", account_email ?? string.Empty);
|
|
var auth = HeadersModel.Init("Authorization", $"Basic {CrypTo.Base64($"{login}:{ts.passwd}")}");
|
|
|
|
return (httpHeaders(ts.host, HeadersModel.Join(auth, ts.headers)), ts.host);
|
|
}
|
|
|
|
if (init.base_auth != null && init.base_auth.enable)
|
|
{
|
|
string tsHost = init.torrs?.FirstOrDefault();
|
|
if (string.IsNullOrWhiteSpace(tsHost))
|
|
return (null, null);
|
|
|
|
string login = (init.base_auth.login ?? string.Empty).Replace("{account_email}", account_email ?? string.Empty);
|
|
var auth = HeadersModel.Init("Authorization", $"Basic {CrypTo.Base64($"{login}:{init.base_auth.passwd}")}");
|
|
|
|
return (httpHeaders(tsHost, HeadersModel.Join(auth, init.base_auth.headers)), tsHost);
|
|
}
|
|
|
|
return (null, init.torrs?.FirstOrDefault());
|
|
}
|
|
|
|
private bool TryReadLocalTorrServerPassword(out string password)
|
|
{
|
|
password = null;
|
|
|
|
if (!System.IO.File.Exists("torrserver/accs.db"))
|
|
return false;
|
|
|
|
string accs = System.IO.File.ReadAllText("torrserver/accs.db");
|
|
password = Regex.Match(accs, "\"ts\":\"([^\"]+)\"").Groups[1].Value;
|
|
return !string.IsNullOrWhiteSpace(password);
|
|
}
|
|
|
|
private static string BuildAddPayload(string sourceUri)
|
|
{
|
|
return JsonSerializer.Serialize(new
|
|
{
|
|
action = "add",
|
|
link = sourceUri,
|
|
title = string.Empty,
|
|
poster = string.Empty,
|
|
save_to_db = false
|
|
});
|
|
}
|
|
|
|
private static string BuildGetPayload(string hash)
|
|
{
|
|
return JsonSerializer.Serialize(new
|
|
{
|
|
action = "get",
|
|
hash
|
|
});
|
|
}
|
|
|
|
private static string BuildRemovePayload(string hash)
|
|
{
|
|
return JsonSerializer.Serialize(new
|
|
{
|
|
action = "rem",
|
|
hash
|
|
});
|
|
}
|
|
|
|
private static string ExtractHash(string response)
|
|
{
|
|
return Regex.Match(response ?? string.Empty, "\"hash\":\"([^\"]+)\"").Groups[1].Value;
|
|
}
|
|
|
|
private static bool IsVideoFile(string path)
|
|
{
|
|
string ext = (Path.GetExtension(path) ?? string.Empty).ToLowerInvariant();
|
|
return ext switch
|
|
{
|
|
".srt" => false,
|
|
".txt" => false,
|
|
".jpg" => false,
|
|
".jpeg" => false,
|
|
".png" => false,
|
|
".nfo" => false,
|
|
_ => true,
|
|
};
|
|
}
|
|
|
|
private static string MaskSensitiveUrl(string url)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(url))
|
|
return string.Empty;
|
|
|
|
return Regex.Replace(url, "(apikey=)[^&]+", "$1***", RegexOptions.IgnoreCase);
|
|
}
|
|
}
|
|
}
|