Compare commits

...

23 Commits

Author SHA1 Message Date
xiaomlove 695d5f071f merge 1.9 2026-01-23 17:01:55 +07:00
xiaomlove dd8df70f7e update version to 1.9.14 2026-01-23 15:38:56 +07:00
xiaomlove 5e163da05e fix bbcode_attach_to_img driver local 2026-01-23 13:10:24 +07:00
xiaomlove 3ced82fc0a bbcode_attach_to_img 2026-01-23 13:01:16 +07:00
xiaomlove 7fc12723ec Merge pull request #425 from ex-hentai/filament-v5
update to filament v5
2026-01-19 14:17:21 +07:00
NekoCH 4f32c10d9f update to filament v5 2026-01-18 19:18:45 +08:00
xiaomlove 7494041005 fix get_ip_location_from_geoip() missing continent_en 2026-01-15 23:24:28 +07:00
xiaomlove af2b7c77e0 Merge pull request #423 from ex-hentai/torrent-file
use TorrentFile from Rhilip\Bencode
2026-01-14 17:25:03 +07:00
NekoCH 4035f4084f use TorrentFile from Rhilip\Bencode 2026-01-14 16:18:16 +08:00
xiaomlove 0a568c0d19 add more index to hit and runs table 2026-01-14 11:37:36 +07:00
xiaomlove b2f21010dc Merge pull request #424 from specialpointcentral/php8
Fix Filament UserPasskeyResource compatibility
2026-01-11 16:34:43 +07:00
Qi HU ef01571b9a Fix Filament UserPasskeyResource compatibility
Signed-off-by: Qi HU <github@spcsky.com>
2026-01-10 11:48:37 +09:00
xiaomlove c91cf060b1 delete torrent extra when delete torrent 2026-01-07 16:00:14 +07:00
xiaomlove 4d96d40a29 Merge remote-tracking branch 'origin/php8' into php8 2026-01-07 01:34:59 +07:00
xiaomlove 81400a34cc Merge branch '1.9' into php8 2026-01-07 01:31:11 +07:00
xiaomlove f20771353e new bonus type serchable 2026-01-07 01:30:21 +07:00
xiaomlove da4a609ef7 claim settle add bonus log 2026-01-07 01:20:44 +07:00
xiaomlove 0b8d4bee52 Merge pull request #421 from ex-hentai/rss-cache-1
filter get params in torrent rss
2026-01-04 21:57:56 +07:00
NekoCH 2cad4e1a83 filter get params in torrent rss 2026-01-04 21:32:48 +08:00
xiaomlove 8d98a7dd8b ptgen fill small title add field: chinese_title 2025-12-31 02:39:07 +07:00
xiaomlove 0351f92bf4 [API] comments list 2025-12-30 17:49:56 +07:00
xiaomlove 5f37d5c59a usercp add seedbox cancel ip help text 2025-12-30 15:39:15 +07:00
xiaomlove 791c617bc0 token permission cache load from db when not exists 2025-12-30 12:56:56 +07:00
61 changed files with 43808 additions and 3982 deletions
@@ -94,6 +94,7 @@ class BonusLogResource extends Resource
SelectFilter::make('business_type')
->options(BonusLogs::listStaticProps(Arr::except(BonusLogs::$businessTypes, BonusLogs::$businessTypeBonus), 'bonus-log.business_types', true))
->label(__('bonus-log.fields.business_type'))
->searchable(true)
,
// Tables\Filters\Filter::make('exclude_seeding_bonus')
// ->toggle()
@@ -4,9 +4,11 @@ namespace App\Filament\Resources\User;
use App\Filament\Resources\User\UserPasskeyResource\Pages;
use App\Models\Passkey;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
@@ -15,9 +17,9 @@ class UserPasskeyResource extends Resource
{
protected static ?string $model = Passkey::class;
protected static ?string $navigationIcon = 'heroicon-o-key';
protected static string | \BackedEnum | null $navigationIcon = 'heroicon-o-key';
protected static ?string $navigationGroup = 'User';
protected static string | \UnitEnum | null $navigationGroup = 'User';
protected static ?int $navigationSort = 12;
@@ -31,10 +33,10 @@ class UserPasskeyResource extends Resource
return self::getNavigationLabel();
}
public static function form(Form $form): Form
public static function form(Schema $schema): Schema
{
return $form
->schema([
return $schema
->components([
//
]);
}
@@ -66,7 +68,7 @@ class UserPasskeyResource extends Resource
->filters([
Tables\Filters\Filter::make('user_id')
->form([
Forms\Components\TextInput::make('uid')
TextInput::make('uid')
->label(__('label.username'))
->placeholder('UID')
,
@@ -76,7 +78,7 @@ class UserPasskeyResource extends Resource
,
Tables\Filters\Filter::make('credential_id')
->form([
Forms\Components\TextInput::make('credential_id')
TextInput::make('credential_id')
->label(__('passkey.fields.credential_id'))
->placeholder('Credential ID')
,
@@ -85,11 +87,11 @@ class UserPasskeyResource extends Resource
})
,
])
->actions([
Tables\Actions\DeleteAction::make(),
->recordActions([
DeleteAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
->toolbarActions([
DeleteBulkAction::make(),
]);
}
+1 -10
View File
@@ -25,17 +25,8 @@ class CommentController extends Controller
*/
public function index(Request $request)
{
$torrentId = $request->torrent_id;
$with = ['create_user', 'update_user'];
$comments = Comment::query()
->with($with)
->where('torrent', $torrentId)
->paginate();
$comments = $this->repository->getList($request, Auth::user());
$resource = CommentResource::collection($comments);
// $resource->additional([
// 'page_title' => nexus_trans('comment.index.page_title'),
// ]);
return $this->success($resource);
}
+4 -6
View File
@@ -15,15 +15,13 @@ class CommentResource extends JsonResource
*/
public function toArray($request)
{
$descriptionArr = format_description($this->text);
return [
'id' => $this->id,
'description' => $descriptionArr,
'images' => get_image_from_description($descriptionArr),
'updated_at_human' => format_datetime($this->editdate),
'created_at_human' => $this->added->format('Y-m-d H:i'),
'text' => bbcode_attach_to_img($this->text),
'updated_at' => format_datetime($this->editdate),
'created_at' => format_datetime($this->added),
'create_user' => new UserResource($this->whenLoaded('create_user')),
'update_user' => new UserResource($this->whenLoaded('update_user')),
'update_user' => $this->when($this->editedby > 0, new UserResource($this->whenLoaded('update_user'))),
];
}
}
+4
View File
@@ -42,6 +42,7 @@ class BonusLogs extends NexusModel
const BUSINESS_TYPE_TASK_NOT_PASS_DEDUCT = 20;
const BUSINESS_TYPE_TASK_PASS_REWARD = 21;
const BUSINESS_TYPE_REWARD_TORRENT = 22;
const BUSINESS_TYPE_CLAIMED_UNREACHED = 23;
//获得类,普通获得,1000 起步
const BUSINESS_TYPE_ROLE_WORK_SALARY = 1000;
@@ -50,6 +51,7 @@ class BonusLogs extends NexusModel
const BUSINESS_TYPE_RECEIVE_GIFT = 1003;
const BUSINESS_TYPE_UPLOAD_TORRENT = 1004;
const BUSINESS_TYPE_TORRENT_BE_REWARD = 1005;
const BUSINESS_TYPE_CLAIMED_REACHED = 1006;
//获得类,做种获得,10000 起
const BUSINESS_TYPE_SEEDING_BASIC = 10000;
@@ -81,6 +83,7 @@ class BonusLogs extends NexusModel
self::BUSINESS_TYPE_TASK_NOT_PASS_DEDUCT => ['text' => 'Task failure penalty'],
self::BUSINESS_TYPE_TASK_PASS_REWARD => ['text' => 'Task success reward'],
self::BUSINESS_TYPE_REWARD_TORRENT => ['text' => 'Reward torrent'],
self::BUSINESS_TYPE_CLAIMED_UNREACHED => ['text' => 'Claimed torrent unreached'],
self::BUSINESS_TYPE_ROLE_WORK_SALARY => ['text' => 'Role work salary'],
self::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => ['text' => 'Torrent be downloaded'],
@@ -88,6 +91,7 @@ class BonusLogs extends NexusModel
self::BUSINESS_TYPE_RECEIVE_GIFT => ['text' => 'Receive gift'],
self::BUSINESS_TYPE_UPLOAD_TORRENT => ['text' => 'Upload torrent'],
self::BUSINESS_TYPE_TORRENT_BE_REWARD => ['text' => 'Torrent be reward'],
self::BUSINESS_TYPE_CLAIMED_REACHED => ['text' => 'Claimed torrent reached'],
self::BUSINESS_TYPE_SEEDING_BASIC => ['text' => 'Seeding basic'],
self::BUSINESS_TYPE_SEEDING_DONOR_ADDITION => ['text' => 'Seeding donor addition'],
+16 -6
View File
@@ -85,13 +85,23 @@ class TrackerUrl extends NexusModel
if ($redis->exists($notFoundFlagKey)) {
return false;
}
self::saveUrlCache();
$result = call_user_func_array([$redis, $command], $params);
if ($result !== false) {
return $result;
$lockKey = "$notFoundFlagKey:lock";
if (!$redis->set($lockKey, 1, ["nx", "ex" => 5])) {
return false;
}
try {
self::saveUrlCache();
$result = call_user_func_array([$redis, $command], $params);
if ($result !== false) {
return $result;
}
//只从 db 拉取一次,仍然没有即标记不存在, 有效期 15 分钟
$redis->setex($notFoundFlagKey, 900, date("Y-m-d H:i:s"));
} catch (\Throwable $throwable) {
do_log($throwable->getMessage(), 'error');
} finally {
$redis->del($lockKey);
}
//只从 db 拉取一次,仍然没有即标记不存在, 有效期 15 分钟
$redis->setex($notFoundFlagKey, 900, date("Y-m-d H:i:s"));
do_log(sprintf("redis command %s with args %s no result", $command, json_encode($params)), 'error');
return false;
}
+25 -2
View File
@@ -6,6 +6,7 @@ use App\Exceptions\NexusException;
use App\Http\Middleware\Locale;
use App\Models\Traits\NexusActivityLogTrait;
use App\Repositories\ExamRepository;
use App\Repositories\TokenRepository;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
@@ -635,10 +636,32 @@ class User extends Authenticatable implements FilamentUser, HasName
return is_null($this->original['notifs']) || str_contains($this->notifs, "[{$name}]");
}
public function tokenCan(string $ability)
public function tokenCan(string $ability): bool
{
$redis = NexusDB::redis();
return $redis->sismember(Setting::USER_TOKEN_PERMISSION_ALLOWED_CACHE_KRY, $ability)
$cacheKey = Setting::USER_TOKEN_PERMISSION_ALLOWED_CACHE_KRY;
if (!$redis->exists($cacheKey)) {
$lockKey = "$cacheKey:lock";
if ($redis->set($lockKey, 1, ['nx', 'ex' => 5])) {
try {
if (!$redis->exists($cacheKey)) {
$abilities = TokenRepository::listUserTokenPermissions(false);
do_log("load user token permissions: " . json_encode($abilities), 'alert');
if (!empty($abilities)) {
$redis->sadd($cacheKey, ...$abilities);
} else {
$redis->sadd($cacheKey, "__NO_USER_TOKEN_PERMISSION__");
$redis->expire($cacheKey, 900);
}
}
} catch (\Throwable $throwable) {
do_log($throwable->getMessage(), 'error');
} finally {
$redis->del($lockKey);
}
}
}
return $redis->sismember($cacheKey, $ability)
&& $this->accessToken && $this->accessToken->can($ability);
}
+13 -4
View File
@@ -2,6 +2,7 @@
namespace App\Repositories;
use App\Jobs\SettleClaim;
use App\Models\BonusLogs;
use App\Models\Claim;
use App\Models\Message;
use App\Models\Snatch;
@@ -260,16 +261,24 @@ class ClaimRepository extends BaseRepository
//Wrap with transaction
DB::transaction(function () use ($uid, $unReachedIdArr, $toUpdateIdArr, $bonusFinal, $totalDeduct, $uploadedCaseWhen, $seedTimeCaseWhen, $message, $now) {
//get latest
$oldBonus = User::query()->find($uid, ['seedbonus'])->seedbonus;
$delta = 0;
//Increase user bonus
User::query()->where('id', $uid)->increment('seedbonus', $bonusFinal);
do_log("Increase user bonus: $bonusFinal", 'alert');
$delta += $bonusFinal;
do_log("Increase user: $uid bonus: $bonusFinal", 'alert');
BonusLogs::add($uid, $oldBonus, $bonusFinal, $oldBonus + $bonusFinal, "", BonusLogs::BUSINESS_TYPE_CLAIMED_REACHED);
$oldBonus += $bonusFinal;
//Handle unreached
if (!empty($unReachedIdArr)) {
Claim::query()->whereIn('id', $unReachedIdArr)->delete();
User::query()->where('id', $uid)->decrement('seedbonus', $totalDeduct);
do_log("Deduct user bonus: $totalDeduct", 'alert');
$delta -= $totalDeduct;
do_log("Deduct user: $uid bonus: $totalDeduct", 'alert');
BonusLogs::add($uid, $oldBonus, $totalDeduct, $oldBonus - $totalDeduct, "", BonusLogs::BUSINESS_TYPE_CLAIMED_UNREACHED);
$oldBonus -= $totalDeduct;
}
User::query()->where('id', $uid)->increment('seedbonus', $delta);
//Update claim `last_settle_at` and init `seed_time_begin` & `uploaded_begin`
if (!empty($toUpdateIdArr)) {
+11 -10
View File
@@ -9,26 +9,27 @@ use App\Models\Torrent;
use App\Models\User;
use Carbon\Carbon;
use Hamcrest\Core\Set;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Nexus\Database\NexusDB;
class CommentRepository extends BaseRepository
{
public function getList(array $params)
public function getList(Request $request, Authenticatable $user)
{
$query = Comment::query()->with(['create_user', 'update_user']);
if (!empty($params['torrent_id'])) {
$query->where('torrent', $params['torrent_id']);
if (!empty($request->torrent_id)) {
$query->where('torrent', $request->torrent_id);
}
if (!empty($params['offer_id'])) {
$query->where('offer', $params['offer_id']);
if (!empty($request->offer_id)) {
$query->where('offer', $request->offer_id);
}
if (!empty($params['request_id'])) {
$query->where('request', $params['request_id']);
if (!empty($request->request_id)) {
$query->where('request', $request->request_id);
}
list($sortField, $sortType) = $this->getSortFieldAndType($params);
$query->orderBy($sortField, $sortType);
return $query->paginate();
$query->orderBy('id', 'asc');
return $query->paginate($this->getPerPageFromRequest($request));
}
public function store(array $params, User $user)
+2 -2
View File
@@ -38,7 +38,7 @@
"calebporzio/sushi": "^2.5",
"cybercog/laravel-clickhouse": "dev-master",
"elasticsearch/elasticsearch": "^7.16",
"filament/filament": "~4.0",
"filament/filament": "~5.0",
"flowframe/laravel-trend": "^0.4",
"geoip2/geoip2": "~2.0",
"google/auth": "1.44.0",
@@ -54,7 +54,7 @@
"meilisearch/meilisearch-php": "^1.0",
"orangehill/iseed": "^3.0",
"phpgangsta/googleauthenticator": "dev-master",
"rhilip/bencode": "^2.0",
"rhilip/bencode": "^2.5.0",
"rlanvin/php-ip": "^3.0",
"spatie/laravel-activitylog": "^4.10",
"stichoza/google-translate-php": "^5.2"
Generated
+341 -302
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -29,7 +29,7 @@ return [
|
*/
'expiration' => 129600,
'expiration' => 5256000,
/*
|--------------------------------------------------------------------------
@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('hit_and_runs', function (Blueprint $table) {
$table->index('status');
$table->index('created_at');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('hit_and_runs', function (Blueprint $table) {
//
});
}
};
+2 -2
View File
@@ -1,6 +1,6 @@
<?php
defined('VERSION_NUMBER') || define('VERSION_NUMBER', '1.9.13');
defined('RELEASE_DATE') || define('RELEASE_DATE', '2025-12-28');
defined('VERSION_NUMBER') || define('VERSION_NUMBER', '1.9.14');
defined('RELEASE_DATE') || define('RELEASE_DATE', '2026-01-23');
defined('IN_TRACKER') || define('IN_TRACKER', false);
defined('PROJECTNAME') || define("PROJECTNAME","NexusPHP");
defined('NEXUSPHPURL') || define("NEXUSPHPURL","https://nexusphp.org");
+44 -5
View File
@@ -3195,6 +3195,7 @@ function deletetorrent($id, $notify = false) {
$idStr = implode(', ', $idArr ?: [0]);
$torrent_dir = get_setting('main.torrent_dir');
\Nexus\Database\NexusDB::statement("DELETE FROM torrents WHERE id in ($idStr)");
\Nexus\Database\NexusDB::statement("DELETE FROM torrent_extras WHERE torrent_id in ($idStr)");
//delete by torrent, make sure user is deleted
\Nexus\Database\NexusDB::statement("DELETE FROM snatched WHERE torrentid in ($idStr) and not exists (select 1 from users where id = snatched.userid)");
foreach(array("peers", "files", "comments") as $x) {
@@ -3214,13 +3215,11 @@ function deletetorrent($id, $notify = false) {
'action_type' => \App\Models\TorrentOperationLog::ACTION_TYPE_DELETE,
'comment' => '',
], $notify);
do_action("torrent_delete", $_id);
fire_event("torrent_deleted", $torrentInfo->get($_id));
}
$meiliSearchRep = new \App\Repositories\MeiliSearchRepository();
$meiliSearchRep->deleteDocuments($idArr);
if (is_int($id)) {
do_action("torrent_delete", $id);
fire_event("torrent_deleted", $torrentInfo->get($id));
}
}
function pager($rpp, $count, $href, $opts = array(), $pagename = "page") {
@@ -5003,6 +5002,9 @@ function get_searchbox_value($mode = 1, $item = 'showsubcat'){
function get_ratio($userid, $html = true){
$row = get_user_row($userid);
if (empty($row)) {
return "---";
}
$uped = $row['uploaded'];
$downed = $row['downloaded'];
if ($html == true){
@@ -5940,6 +5942,7 @@ function get_ip_location_from_geoip($ip): bool|array
'city' => '',
'country_en' => '',
'city_en' => '',
'continent_en' => '',
];
try {
$database = nexus_env('GEOIP2_DATABASE');
@@ -5968,7 +5971,7 @@ function get_ip_location_from_geoip($ip): bool|array
$info['continent'] = $continentName;
$info['continent_en'] = $record->continent->names['en'] ?? '';
} catch (\Exception $exception) {
do_log($exception->getMessage());
do_log($exception->getMessage() . ", trace: " . $exception->getTraceAsString(), 'error');
}
return $info;
});
@@ -6612,4 +6615,40 @@ function hide_text($text) {
return '<span class="hidden-text">' . $text . '</span>';
}
function make_content_disposition(string $filename, string $disposition = 'attachment'): string {
$filenameFallback = str_replace('%', '', Str::ascii($filename));
return \Symfony\Component\HttpFoundation\HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback);
}
function bbcode_attach_to_img(string $text) {
$pattern = "/\[attach\]([0-9a-zA-z][0-9a-zA-z]*)\[\/attach\]/is";
return preg_replace_callback($pattern, function ($matches) {
$dlkey = $matches[1];
$httpdirectory_attachment = get_setting('attachment.httpdirectory');
$row = \Nexus\Database\NexusDB::remember('attachment_'.$dlkey.'_content', 86400, function() use ($dlkey) {
$record = \App\Models\Attachment::query()->where("dlkey", $dlkey)->first();
if ($record) {
return $record->toArray();
}
return [];
});
if (empty($row) || $row['isimage'] != 1) {
do_log(sprintf("dlkey: %s get attachment %s not exists or not image", $dlkey, json_encode($row)));
return $matches[0];
}
$driver = $row['driver'] ?? 'local';
if ($driver == "local") {
if ($row['thumb'] == 1){
$url = $httpdirectory_attachment."/".$row['location'].".thumb.jpg";
} else {
$url = $httpdirectory_attachment."/".$row['location'];
}
$url = sprintf("%s/%s", getSchemeAndHttpHost(true), trim($url, "/"));
} else {
$url = \Nexus\Attachment\Storage::getDriver($driver)->getImageUrl($row['location']);
}
return "[img]" . $url . "[/img]";
}, $text, 20);
}
?>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
.fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3}
+13 -27
View File
@@ -1,4 +1,7 @@
<?php
use Rhilip\Bencode\TorrentFile;
require_once("../include/bittorrent.php");
dbconn();
require_once ROOT_PATH . get_langfile_path("functions.php");
@@ -89,7 +92,7 @@ if ($CURUSER['downloadpos']=="no") {
//$ssl_torrent = $trackerSchemaAndHost['ssl_torrent'];
//$base_announce_url = $trackerSchemaAndHost['base_announce_url'];
$res = sql_query("SELECT torrents.name, torrents.filename, torrents.save_as, torrents.size, torrents.owner, torrents.banned, torrents.approval_status, torrents.price, categories.mode as search_box_id FROM torrents left join categories on torrents.category = categories.id WHERE torrents.id = ".sqlesc($id)) or sqlerr(__FILE__, __LINE__);
$res = sql_query("SELECT torrents.name, torrents.filename, torrents.save_as, torrents.size, torrents.owner, torrents.banned, torrents.approval_status, torrents.price, torrents.added, categories.mode as search_box_id FROM torrents left join categories on torrents.category = categories.id WHERE torrents.id = ".sqlesc($id)) or sqlerr(__FILE__, __LINE__);
$row = mysql_fetch_assoc($res);
if (!$row) {
do_log("[TORRENT_NOT_EXISTS_IN_DATABASE] $id");
@@ -140,10 +143,13 @@ if (strlen($CURUSER['passkey']) != 32) {
$CURUSER['passkey'] = md5($CURUSER['username'].date("Y-m-d H:i:s").$CURUSER['passhash']);
sql_query("UPDATE users SET passkey=".sqlesc($CURUSER['passkey'])." WHERE id=".sqlesc($CURUSER['id']));
}
$dict = \Rhilip\Bencode\Bencode::load($fn);
$dict['announce'] = get_tracker_schema_and_host($CURUSER['tracker_url_id'], true) . "?passkey=" . $CURUSER['passkey'];
$dict['comment'] = getSchemeAndHttpHost(true) . "/details.php?id=" . $id;
do_log(sprintf("[ANNOUNCE_URL], user: %s, torrent: %s, url: %s", $CURUSER['id'] ?? '', $id, $dict['announce']));
$dict = TorrentFile::load($fn);
$dict->cleanRootFields();
$dict->setAnnounce(get_tracker_schema_and_host($CURUSER['tracker_url_id'], true) . "?passkey=" . $CURUSER['passkey']);
$dict->setComment(getSchemeAndHttpHost(true) . "/details.php?id=" . $id);
$dict->setCreatedBy($SITENAME);
$dict->setCreationDate(strtotime($row['added']));
do_log(sprintf("[ANNOUNCE_URL], user: %s, torrent: %s, url: %s", $CURUSER['id'] ?? '', $id, $dict->getAnnounce()));
/**
* does not support multi-tracker
*
@@ -197,30 +203,10 @@ header ("Content-Transfer-Encoding: binary");
*/
header("Content-Type: application/x-bittorrent");
if ( str_replace("Gecko", "", $_SERVER['HTTP_USER_AGENT']) != $_SERVER['HTTP_USER_AGENT'])
{
header ("Content-Disposition: attachment; filename=\"$torrentnameprefix.".$row["save_as"].".torrent\" ; charset=utf-8");
}
else if ( str_replace("Firefox", "", $_SERVER['HTTP_USER_AGENT']) != $_SERVER['HTTP_USER_AGENT'] )
{
header ("Content-Disposition: attachment; filename=\"$torrentnameprefix.".$row["save_as"].".torrent\" ; charset=utf-8");
}
else if ( str_replace("Opera", "", $_SERVER['HTTP_USER_AGENT']) != $_SERVER['HTTP_USER_AGENT'] )
{
header ("Content-Disposition: attachment; filename=\"$torrentnameprefix.".$row["save_as"].".torrent\" ; charset=utf-8");
}
else if ( str_replace("IE", "", $_SERVER['HTTP_USER_AGENT']) != $_SERVER['HTTP_USER_AGENT'] )
{
header ("Content-Disposition: attachment; filename=".str_replace("+", "%20", rawurlencode("$torrentnameprefix." . $row["save_as"] .".torrent")));
}
else
{
header ("Content-Disposition: attachment; filename=".str_replace("+", "%20", rawurlencode("$torrentnameprefix." . $row["save_as"] .".torrent")));
}
header("Content-Disposition: " . make_content_disposition($torrentnameprefix . $row["save_as"] . '.torrent'));
//header ("Content-Disposition: attachment; filename=".$row["filename"]."");
//ob_implicit_flush(true);
//print(benc($dict));
echo \Rhilip\Bencode\Bencode::encode($dict);
echo $dict->dumpToString();
?>
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),Livewire.hook("commit",({component:e,commit:t,succeed:i,fail:o,respond:h})=>{i(({snapshot:r,effect:l})=>{this.$nextTick(()=>{e.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))}}}export{c as default};
function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),Livewire.interceptMessage(({message:e,onSuccess:i})=>{i(()=>{this.$nextTick(()=>{e.component.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(i=>{let t=i.querySelector("input[type=checkbox]");t.disabled||t.checked!==e&&(t.checked=e,t.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))}}}export{c as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default};
function n({initialHeight:e,shouldAutosize:i,state:h}){return{state:h,wrapperEl:null,init(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=e+"rem")},resize(){if(this.$el.scrollHeight<=0)return;let t=this.$el.style.height;this.$el.style.height="0px";let r=this.$el.scrollHeight;this.$el.style.height=t;let l=parseFloat(e)*parseFloat(getComputedStyle(document.documentElement).fontSize),s=Math.max(r,l)+"px";this.wrapperEl.style.height!==s&&(this.wrapperEl.style.height=s)},setUpResizeObserver(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{n as default};
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
var i=()=>({isSticky:!1,enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1}});export{i as default};
var i=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let e=this.$el.parentElement;e&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(e),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let e=this.$el.parentElement;if(!e)return;let t=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=e.offsetWidth+parseInt(t.marginInlineStart,10)*-1+parseInt(t.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});export{i as default};
+1 -1
View File
@@ -1 +1 @@
function u({activeTab:a,isTabPersistedInQueryString:e,livewireId:h,tab:o,tabQueryStringKey:s}){return{tab:o,init(){let t=this.getTabs(),i=new URLSearchParams(window.location.search);e&&i.has(s)&&t.includes(i.get(s))&&(this.tab=i.get(s)),this.$watch("tab",()=>this.updateQueryString()),(!this.tab||!t.includes(this.tab))&&(this.tab=t[a-1]),Livewire.hook("commit",({component:r,commit:f,succeed:c,fail:l,respond:b})=>{c(({snapshot:d,effect:m})=>{this.$nextTick(()=>{if(r.id!==h)return;let n=this.getTabs();n.includes(this.tab)||(this.tab=n[a-1]??this.tab)})})})},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!e)return;let t=new URL(window.location.href);t.searchParams.set(s,this.tab),history.replaceState(null,document.title,t.toString())}}}export{u as default};
function I({activeTab:w,isScrollable:f,isTabPersistedInQueryString:g,livewireId:m,tab:T,tabQueryStringKey:c}){return{boundResizeHandler:null,isScrollable:f,resizeDebounceTimer:null,tab:T,withinDropdownIndex:null,withinDropdownMounted:!1,init(){let t=this.getTabs(),e=new URLSearchParams(window.location.search);g&&e.has(c)&&t.includes(e.get(c))&&(this.tab=e.get(c)),this.$watch("tab",()=>this.updateQueryString()),(!this.tab||!t.includes(this.tab))&&(this.tab=t[w-1]),Livewire.interceptMessage(({message:n,onSuccess:o})=>{o(()=>{this.$nextTick(()=>{if(n.component.id!==m)return;let l=this.getTabs();l.includes(this.tab)||(this.tab=l[w-1]??this.tab)})})}),f||(this.boundResizeHandler=this.debouncedUpdateTabsWithinDropdown.bind(this),window.addEventListener("resize",this.boundResizeHandler),this.updateTabsWithinDropdown())},calculateAvailableWidth(t){let e=window.getComputedStyle(t);return Math.floor(t.clientWidth)-Math.ceil(parseFloat(e.paddingLeft))*2},calculateContainerGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap))},calculateDropdownIconWidth(t){let e=t.querySelector(".fi-icon");return Math.ceil(e.clientWidth)},calculateTabItemGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap)||8)},calculateTabItemPadding(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.paddingLeft))+Math.ceil(parseFloat(e.paddingRight))},findOverflowIndex(t,e,n,o,l,h){let u=t.map(i=>Math.ceil(i.clientWidth)),p=t.map(i=>{let d=i.querySelector(".fi-tabs-item-label"),a=i.querySelector(".fi-badge"),s=Math.ceil(d.clientWidth),r=a?Math.ceil(a.clientWidth):0;return{label:s,badge:r,total:s+(r>0?o+r:0)}});for(let i=0;i<t.length;i++){let d=u.slice(0,i+1).reduce((b,y)=>b+y,0),a=i*n,s=p.slice(i+1),r=s.length>0,W=r?Math.max(...s.map(b=>b.total)):0,D=r?l+W+o+h+n:0;if(d+a+D>e)return i}return-1},get isDropdownButtonVisible(){return this.withinDropdownMounted?this.withinDropdownIndex===null?!1:this.getTabs().findIndex(e=>e===this.tab)<this.withinDropdownIndex:!0},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!g)return;let t=new URL(window.location.href);t.searchParams.set(c,this.tab),history.replaceState(null,document.title,t.toString())},debouncedUpdateTabsWithinDropdown(){clearTimeout(this.resizeDebounceTimer),this.resizeDebounceTimer=setTimeout(()=>this.updateTabsWithinDropdown(),150)},async updateTabsWithinDropdown(){this.withinDropdownIndex=null,this.withinDropdownMounted=!1,await this.$nextTick();let t=this.$el.querySelector(".fi-tabs"),e=t.querySelector(".fi-tabs-item:last-child"),n=Array.from(t.children).slice(0,-1),o=n.map(a=>a.style.display);n.forEach(a=>a.style.display=""),t.offsetHeight;let l=this.calculateAvailableWidth(t),h=this.calculateContainerGap(t),u=this.calculateDropdownIconWidth(e),p=this.calculateTabItemGap(n[0]),i=this.calculateTabItemPadding(n[0]),d=this.findOverflowIndex(n,l,h,p,i,u);n.forEach((a,s)=>a.style.display=o[s]),d!==-1&&(this.withinDropdownIndex=d),this.withinDropdownMounted=!0},destroy(){this.boundResizeHandler&&window.removeEventListener("resize",this.boundResizeHandler),clearTimeout(this.resizeDebounceTimer)}}}export{I as default};
+1 -1
View File
@@ -1 +1 @@
(()=>{var d=()=>({isSticky:!1,enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1}});var m=function(n,e,i){let t=n;if(e.startsWith("/")&&(i=!0,e=e.slice(1)),i)return e;for(;e.startsWith("../");)t=t.includes(".")?t.slice(0,t.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(t)?e:["",null,void 0].includes(e)?t:`${t}.${e}`},u=n=>{let e=Alpine.findClosest(n,i=>i.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:n})=>({handleFormValidationError(e){e.detail.livewireId===n&&this.$nextTick(()=>{let i=this.$el.querySelector("[data-validation-error]");if(!i)return;let t=i;for(;t;)t.dispatchEvent(new CustomEvent("expand")),t=t.parentNode;setTimeout(()=>i.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})}})),window.Alpine.data("filamentSchemaComponent",({path:n,containerPath:e,isLive:i,$wire:t})=>({$statePath:n,$get:(r,l)=>t.$get(m(e,r,l)),$set:(r,l,a,o=null)=>(o??(o=i),t.$set(m(e,r,a),l,o)),get $state(){return t.$get(n)}})),window.Alpine.data("filamentActionsSchemaComponent",d),Livewire.hook("commit",({component:n,commit:e,respond:i,succeed:t,fail:r})=>{t(({snapshot:l,effects:a})=>{a.dispatches?.forEach(o=>{if(!o.params?.awaitSchemaComponent)return;let s=Array.from(n.el.querySelectorAll(`[wire\\:partial="schema-component::${o.params.awaitSchemaComponent}"]`)).filter(c=>u(c)===n);if(s.length!==1){if(s.length>1)throw`Multiple schema components found with key [${o.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${n.id}-${o.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(o.name,{detail:o.params}))},{once:!0})}})})})});})();
(()=>{var o=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let i=this.$el.parentElement;i&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(i),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let i=this.$el.parentElement;if(!i)return;let e=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=i.offsetWidth+parseInt(e.marginInlineStart,10)*-1+parseInt(e.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});var a=function(i,e,n){let t=i;if(e.startsWith("/")&&(n=!0,e=e.slice(1)),n)return e;for(;e.startsWith("../");)t=t.includes(".")?t.slice(0,t.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(t)?e:["",null,void 0].includes(e)?t:`${t}.${e}`},d=i=>{let e=Alpine.findClosest(i,n=>n.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:i})=>({handleFormValidationError(e){e.detail.livewireId===i&&this.$nextTick(()=>{let n=this.$el.querySelector("[data-validation-error]");if(!n)return;let t=n;for(;t;)t.dispatchEvent(new CustomEvent("expand")),t=t.parentNode;setTimeout(()=>n.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},isStateChanged(e,n){if(e===void 0)return!1;try{return JSON.stringify(e)!==JSON.stringify(n)}catch{return e!==n}}})),window.Alpine.data("filamentSchemaComponent",({path:i,containerPath:e,$wire:n})=>({$statePath:i,$get:(t,r)=>n.$get(a(e,t,r)),$set:(t,r,s,l=!1)=>n.$set(a(e,t,s),r,l),get $state(){return n.$get(i)}})),window.Alpine.data("filamentActionsSchemaComponent",o),Livewire.interceptMessage(({message:i,onSuccess:e})=>{e(({payload:n})=>{n.effects?.dispatches?.forEach(t=>{if(!t.params?.awaitSchemaComponent)return;let r=Array.from(i.component.el.querySelectorAll(`[wire\\:partial="schema-component::${t.params.awaitSchemaComponent}"]`)).filter(s=>d(s)===i.component);if(r.length!==1){if(r.length>1)throw`Multiple schema components found with key [${t.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${component.id}-${t.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(t.name,{detail:t.params}))},{once:!0})}})})})});})();
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default};
function a({name:i,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,init(){Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let r=this.getServerState();r===void 0||Alpine.raw(this.state)===r||(this.state=r)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(i,s,this.state);this.error=t?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{a as default};
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:d,respond:u})=>{n(({snapshot:f,effect:h})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||this.getNormalizedState()===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.getNormalizedState()),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[null,void 0].includes(this.$refs.serverState.value)?"":this.$refs.serverState.value.replaceAll('\\"','"')},getNormalizedState(){let e=Alpine.raw(this.state);return[null,void 0].includes(e)?"":e}}}export{o as default};
function a({name:i,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,init(){Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let r=this.getServerState();r===void 0||this.getNormalizedState()===r||(this.state=r)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(i,s,this.state);this.error=t?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.getNormalizedState()),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[null,void 0].includes(this.$refs.serverState.value)?"":this.$refs.serverState.value.replaceAll('\\"','"')},getNormalizedState(){let e=Alpine.raw(this.state);return[null,void 0].includes(e)?"":e}}}export{a as default};
+1 -1
View File
@@ -1 +1 @@
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default};
function a({name:i,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,init(){Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let r=this.getServerState();r===void 0||Alpine.raw(this.state)===r||(this.state=r)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(i,s,this.state);this.error=t?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{a as default};
-1
View File
@@ -1 +0,0 @@
function n(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let o=s.indexOf(this.lastChecked),r=s.indexOf(t),l=[o,r].sort((i,d)=>i-d),c=[];for(let i=l[0];i<=l[1];i++)s[i].checked=t.checked,c.push(s[i].value);t.checked?this.selectRecords(c):this.deselectRecords(c)}this.lastChecked=t}}}export{n as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -20,7 +20,11 @@ jQuery('.btn-get-pt-gen').on('click', function () {
}
doInsert(response.data.format, '', false)
if (response.data.aka && response.data.site === 'douban') {
form.find("input[name=small_descr]").val(response.data.aka.join("/"))
let aka = response.data.aka
if (response.data.chinese_title) {
aka.unshift(response.data.chinese_title)
}
form.find("input[name=small_descr]").val(aka.join("/"))
}
if (response.data.imdb_link) {
form.find("input[data-pt-gen=url]").val(response.data.imdb_link)
+32 -74
View File
@@ -1,5 +1,8 @@
<?php
//require_once("../include/benc.php");
use Rhilip\Bencode\ParseException;
use Rhilip\Bencode\TorrentFile;
require_once("../include/bittorrent.php");
ini_set("upload_max_filesize",$max_torrent_size);
@@ -83,13 +86,11 @@ $audiocodecid = intval($_POST["audiocodec_sel"][$catmod] ?? 0);
if (!is_valid_id($catid))
bark($lang_takeupload['std_category_unselected']);
if (!validfilename($fname))
bark($lang_takeupload['std_invalid_filename']);
if (!preg_match('/^(.+)\.torrent$/si', $fname, $matches))
bark($lang_takeupload['std_filename_not_torrent']);
$shortfname = $torrent = $matches[1];
if (!empty($_POST["name"]))
$torrent = unesc($_POST["name"]);
$torrent = trim(unesc($_POST["name"]));
if ($f['size'] > $max_torrent_size)
bark($lang_takeupload['std_torrent_file_too_big'].number_format($max_torrent_size).$lang_takeupload['std_remake_torrent_note']);
$tmpname = $f["tmp_name"];
@@ -108,79 +109,36 @@ if ($maxPrice > 0 && isset($_POST['price']) && $_POST['price'] > $maxPrice && $p
}
try {
$dict = \Rhilip\Bencode\Bencode::load($tmpname);
} catch (\Rhilip\Bencode\ParseErrorException $e) {
bark($lang_takeupload['std_not_bencoded_file']);
$dict = TorrentFile::load($tmpname);
$dict->unhybridizedTo();
$dict->parse();
} catch (ParseException $e) {
bark($e->getMessage());
}
function checkTorrentDict($dict, $key, $type = null)
{
global $lang_takeupload;
if (!is_array($dict)) bark($lang_takeupload['std_not_a_dictionary']);
$value = $dict[$key];
if (!isset($value)) bark($lang_takeupload['std_dictionary_is_missing_key']);
if (!is_null($type)) {
$isFunction = 'is_' . $type;
if (function_exists($isFunction) && !$isFunction($value)) {
bark($lang_takeupload['std_invalid_entry_in_dictionary']);
}
}
return $value;
}
$info = checkTorrentDict($dict, 'info');
if (isset($dict['piece layers']) || isset($info['files tree']) || (isset($info['meta version']) && $info['meta version'] == 2)) {
bark('Torrent files created with Bittorrent Protocol v2, or hybrid torrents are not supported.');
}
$plen = checkTorrentDict($info, 'piece length', 'integer'); // Only Check without use
$dname = checkTorrentDict($info, 'name', 'string');
$pieces = checkTorrentDict($info, 'pieces', 'string');
if (strlen($pieces) % 20 != 0)
bark($lang_takeupload['std_invalid_pieces']);
$filelist = array();
$totallen = $info['length'] ?? null;
if (isset($totallen)) {
$filelist[] = array($dname, $totallen);
$type = "single";
}
else {
$flist = checkTorrentDict($info, 'files', 'array');
if (!isset($flist)) bark($lang_takeupload['std_missing_length_and_files']);
if (!count($flist)) bark("no files");
$totallen = 0;
foreach ($flist as $fn) {
$ll = checkTorrentDict($fn, 'length', 'integer');
$path_key = isset($fn['path.utf-8']) ? 'path.utf-8' : 'path';
$ff = checkTorrentDict($fn, $path_key, 'list');
$totallen += $ll;
$ffa = array();
foreach ($ff as $ffe) {
if (!is_string($ffe)) bark($lang_takeupload['std_filename_errors']);
$ffa[] = $ffe;
}
if (!count($ffa)) bark($lang_takeupload['std_filename_errors']);
$ffe = implode("/", $ffa);
$filelist[] = array($ffe, $ll);
}
$type = "multi";
}
$dict['announce'] = get_protocol_prefix() . $announce_urls[0]; // change announce url to local
$dict['info']['private'] = 1;
//The following line requires uploader to re-download torrents after uploading
//even the torrent is set as private and with uploader's passkey in it.
$dict['info']['source'] = "[$BASEURL] $SITENAME";
unset ($dict['announce-list']); // remove multi-tracker capability
unset ($dict['nodes']); // remove cached peers (Bitcomet & Azareus)
$dict->cleanRootFields()
->setComment(getSchemeAndHttpHost())
->setCreationDate(time())
->setCreatedBy($SITENAME)
->setAnnounce(get_protocol_prefix() . $announce_urls[0]) // change announce url to local
->setPrivate(true)
->setSource("[$BASEURL] $SITENAME");
$infohash = pack("H*", sha1(\Rhilip\Bencode\Bencode::encode($dict['info']))); // double up on the becoding solves the occassional misgenerated infohash
$filelist = $dict->getFileList();
$dname = $dict->getName();
$type = $dict->getFileMode();
$totallen = $dict->getSize();
$pieces = $dict->getInfoField('pieces');
$piecesCount = strlen($pieces) / 20;
$maxPieceCount = 24576;
$idealPiecesCount = $totallen / (8 * 1024 ** 2);
if ($piecesCount > $maxPieceCount && $idealPiecesCount < $maxPieceCount) {
bark('Too many pieces');
}
$infohash = $dict->getInfoHashV1ForAnnounce();
$exists = \App\Models\Torrent::query()->where('info_hash', $infohash)->first(['id']);
if ($exists) {
// bark($lang_takeupload['std_torrent_existed']);
@@ -351,7 +309,7 @@ $insert = [
// 'pt_gen' => $_POST['pt_gen'] ?? '',
// 'technical_info' => $_POST['technical_info'] ?? '',
'cover' => $cover,
'pieces_hash' => sha1($info['pieces']),
'pieces_hash' => sha1($pieces),
'cache_stamp' => time(),
];
/**
@@ -412,7 +370,7 @@ $id = \Nexus\Database\NexusDB::insert('torrents', $insert);
//$id = mysql_insert_id();
$torrentFilePath = "$torrentSavePath/$id.torrent";
$saveResult = \Rhilip\Bencode\Bencode::dump($torrentFilePath, $dict);
$saveResult = $dict->dump($torrentFilePath);
if ($saveResult === false) {
sql_query("delete from torrents where id = $id limit 1");
bark("save torrent to $torrentFilePath fail.");
@@ -442,7 +400,7 @@ if (!empty($tagIdArr)) {
@sql_query("DELETE FROM files WHERE torrent = $id");
foreach ($filelist as $file) {
@sql_query("INSERT INTO files (torrent, filename, size) VALUES ($id, ".sqlesc($file[0]).",".$file[1].")");
@sql_query("INSERT INTO files (torrent, filename, size) VALUES ($id, ".sqlesc($file['path']).",".$file['size'].")");
}
$extra['torrent_id'] = $id;
\App\Models\TorrentExtra::query()->create($extra);
+12 -5
View File
@@ -4,6 +4,17 @@ $passkey = $_GET['passkey'] ?? $CURUSER['passkey'] ?? '';
if (!$passkey) {
die("require passkey");
}
$exactParams = ['inclbookmarked', 'paid', 'rows', 'icat', 'ismalldescr', 'isize', 'iuplder', 'search', 'search_mode', 'sticky', 'linktype'];
$prefixedParams = ['cat', 'sou', 'med', 'cod', 'sta', 'pro', 'tea', 'aud'];
foreach ($_GET as $key => $value) {
if (in_array($key, $exactParams, true)) {
continue;
}
if (preg_match('/^(cat|sou|med|cod|sta|pro|tea|aud)\d+$/', $key)) {
continue;
}
unset($_GET[$key]);
}
$cacheKey = "nexus_rss:$passkey:" . md5(http_build_query($_GET));
$cacheData = \Nexus\Database\NexusDB::cache_get($cacheKey);
if ($cacheData && nexus_env('APP_ENV') != 'local') {
@@ -79,10 +90,6 @@ if (isset($searchstr)){
$where .= ($where ? " AND " : "") . implode(" AND ", $wherea);
}
$limit = "";
$startindex = intval($_GET['startindex'] ?? 0);
if ($startindex) {
$limit .= $startindex.", ";
}
$showrows = intval($_GET['rows'] ?? 0);
if($showrows < 1 || $showrows > 50) {
$showrows = 50;
@@ -184,7 +191,7 @@ if (!$noNormalResults) {
return \Nexus\Database\NexusDB::select($query);
});
}
if (!empty($prependIdArr) && $startindex == 0) {
if (!empty($prependIdArr)) {
$prependIdStr = implode(',', $prependIdArr);
$query = "SELECT $fieldStr FROM torrents LEFT JOIN categories ON torrents.category = categories.id left join torrent_extras on torrent_extras.torrent_id = torrents.id where torrents.id in ($prependIdStr) and $where ORDER BY field(torrents.id, $prependIdStr)";
$prependRows = \Nexus\Database\NexusDB::remember(sprintf("nexus_rss:prepend:%s", md5($query)), 300, function () use ($query) {
+1 -1
View File
@@ -1062,7 +1062,7 @@ if (get_setting('seed_box.enabled') == 'yes') {
</div>
<div class="form-control-row">
<div class="label">{$columnIP}</div>
<div class="field"><input type="text" name="params[ip]"><div><small>{$columnIPHelp}</small></div></div>
<div class="field"><input type="text" name="params[ip]"></div>
</div>
<div class="form-control-row">
<div class="label">{$columnComment}</div>
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5087 -1726
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+6202 -1621
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
{"/livewire.js":"df3a17f2"}
{"/livewire.js":"8b575521"}
+2
View File
@@ -22,6 +22,7 @@ return [
\App\Models\BonusLogs::BUSINESS_TYPE_GIFT_MEDAL => 'Gift medal',
\App\Models\BonusLogs::BUSINESS_TYPE_BUY_TORRENT => 'Buy torrent',
\App\Models\BonusLogs::BUSINESS_TYPE_REWARD_TORRENT => 'Reward torrent',
\App\Models\BonusLogs::BUSINESS_TYPE_CLAIMED_UNREACHED => 'Claimed torrent unreached',
\App\Models\BonusLogs::BUSINESS_TYPE_ROLE_WORK_SALARY => 'Role work salary',
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => 'Torrent be downloaded',
@@ -29,6 +30,7 @@ return [
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => 'Receive gift',
\App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => 'Upload torrent',
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_REWARD => 'Torrent receive reward',
\App\Models\BonusLogs::BUSINESS_TYPE_CLAIMED_REACHED => 'Claimed torrent reached reward',
\App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_BASIC => 'Seeding basic',
\App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_DONOR_ADDITION => 'Seeding donor addition',
+2
View File
@@ -24,6 +24,7 @@ return [
\App\Models\BonusLogs::BUSINESS_TYPE_TASK_PASS_REWARD => '任务完成奖励',
\App\Models\BonusLogs::BUSINESS_TYPE_TASK_NOT_PASS_DEDUCT => '任务未完成扣除',
\App\Models\BonusLogs::BUSINESS_TYPE_REWARD_TORRENT => '奖励种子',
\App\Models\BonusLogs::BUSINESS_TYPE_CLAIMED_UNREACHED => '认领种子未达标扣除',
\App\Models\BonusLogs::BUSINESS_TYPE_ROLE_WORK_SALARY => '工作组工资',
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => '种子被下载',
@@ -31,6 +32,7 @@ return [
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => '收到礼物',
\App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => '发布种子',
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_REWARD => '种子收到奖励',
\App\Models\BonusLogs::BUSINESS_TYPE_CLAIMED_REACHED => '认领种子达标奖励',
\App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_BASIC => '做种基础魔力',
\App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_DONOR_ADDITION => '做种捐赠加成',
+2
View File
@@ -22,6 +22,7 @@ return [
\App\Models\BonusLogs::BUSINESS_TYPE_GIFT_MEDAL => '贈送勛章',
\App\Models\BonusLogs::BUSINESS_TYPE_BUY_TORRENT => '購買種子',
\App\Models\BonusLogs::BUSINESS_TYPE_REWARD_TORRENT => '獎勵種子',
\App\Models\BonusLogs::BUSINESS_TYPE_CLAIMED_UNREACHED => '認領種子未達標扣除',
\App\Models\BonusLogs::BUSINESS_TYPE_ROLE_WORK_SALARY => '工作組工資',
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => '種子被下載',
@@ -29,6 +30,7 @@ return [
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => '收到禮物',
\App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => '發布種子',
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_REWARD => '種子收到獎勵',
\App\Models\BonusLogs::BUSINESS_TYPE_CLAIMED_REACHED => '認領種子達標獎勵',
\App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_BASIC => '做種基礎魔力',
\App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_DONOR_ADDITION => '做種捐贈加成',
+1 -2
View File
@@ -29,7 +29,6 @@ Route::group(['middleware' => ['auth:sanctum']], function () {
// Route::resource('messages', \App\Http\Controllers\MessageController::class);
// Route::get('messages-unread', [\App\Http\Controllers\MessageController::class, 'listUnread']);
// Route::resource('torrents', \App\Http\Controllers\TorrentController::class);
// Route::resource('comments', \App\Http\Controllers\CommentController::class);
// Route::resource('peers', \App\Http\Controllers\PeerController::class);
// Route::resource('files', \App\Http\Controllers\FileController::class);
// Route::resource('thanks', \App\Http\Controllers\ThankController::class);
@@ -56,7 +55,7 @@ Route::group(['middleware' => ['auth:sanctum']], function () {
Route::post('bookmarks', [\App\Http\Controllers\BookmarkController::class, 'store'])->middleware(ability(RoutePermissionEnum::BOOKMARK_STORE));
Route::post('bookmarks/delete', [\App\Http\Controllers\BookmarkController::class, 'destroy'])->middleware(ability(RoutePermissionEnum::BOOKMARK_DELETE));
Route::get('comments', [\App\Http\Controllers\CommentController::class, 'index'])->middleware(ability(RoutePermissionEnum::TORRENT_VIEW));
});