mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-23 11:27:24 +08:00
plugin management + user tables and torrents table text column migrate
This commit is contained in:
+1
-1
@@ -69,7 +69,7 @@ ELASTICSEARCH_PORT=
|
||||
ELASTICSEARCH_SCHEME=
|
||||
ELASTICSEARCH_USER=
|
||||
ELASTICSEARCH_PASS=
|
||||
ELASTICSEARCH_SSL_VERIFICATION
|
||||
ELASTICSEARCH_SSL_VERIFICATION=
|
||||
ELASTICSEARCH_ENABLED=
|
||||
|
||||
SFTP_HOST=
|
||||
|
||||
@@ -16,6 +16,7 @@ use App\Models\Invite;
|
||||
use App\Models\LoginLog;
|
||||
use App\Models\Medal;
|
||||
use App\Models\Peer;
|
||||
use App\Models\Post;
|
||||
use App\Models\SearchBox;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Snatch;
|
||||
@@ -101,11 +102,7 @@ class Test extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$str = "1.abc.de";
|
||||
$ext = "png";
|
||||
$str = "202404/20240403215909f58f38ddd968a0e8a4bdd30690a9e92e.png";
|
||||
$ext = pathinfo($str, PATHINFO_EXTENSION);
|
||||
dd(basename($str), $ext);
|
||||
$this->info("haha");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Upgrade;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class MigrateTorrentsTableTextColumn extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'upgrade:migrate_torrents_table_text_column';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Migrate torrents table text column';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$columns = ["ori_descr", "descr", "nfo", "technical_info", "pt_gen"];
|
||||
$sql = "alter table torrents ";
|
||||
$drops = [];
|
||||
foreach ($columns as $column) {
|
||||
if (Schema::hasColumn("torrents", $column)) {
|
||||
$drops[] = "drop column $column";
|
||||
}
|
||||
}
|
||||
if (!empty($drops)) {
|
||||
$sql .= implode(",", $drops);
|
||||
NexusDB::statement($sql);
|
||||
}
|
||||
if (Schema::hasTable("torrent_extras")) {
|
||||
NexusDB::statement("insert torrent_extras (torrent_id, descr, media_info, nfo, created_at) select id, descr, technical_info, nfo, now() from torrents on duplicate key update torrent_id = values(torrent_id)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Upgrade;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class MigrateUsersTableCommentRelatedColumn extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'upgrade:migrate_users_table_comment_related_column';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Migrate users table comment related column';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('users', 'modcomment')) {
|
||||
$table->dropColumn('modcomment');
|
||||
}
|
||||
if (Schema::hasColumn('users', 'bonuscomment')) {
|
||||
$table->dropColumn('bonuscomment');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,9 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
if (app()->runningInConsole()) {
|
||||
return;
|
||||
}
|
||||
$this->reportable(function (InsufficientPermissionException $e) {
|
||||
if (request()->expectsJson()) {
|
||||
return response()->json(fail($e->getMessage(), request()->all()), 403);
|
||||
@@ -55,9 +58,9 @@ class Handler extends ExceptionHandler
|
||||
});
|
||||
|
||||
//Other Only handle in json request
|
||||
// if (!request()->expectsJson()) {
|
||||
// return;
|
||||
// }
|
||||
if (!request()->expectsJson()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->renderable(function (AuthenticationException $e) {
|
||||
return response()->json(fail($e->getMessage(), ['guards' => $e->guards()]), 401);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Clusters;
|
||||
|
||||
use Filament\Clusters\Cluster;
|
||||
|
||||
class Plugin extends Cluster
|
||||
{
|
||||
protected static ?string $navigationIcon = 'heroicon-o-squares-2x2';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use Filament\Pages\Page;
|
||||
|
||||
class RunCommand extends Page
|
||||
{
|
||||
protected static ?string $navigationIcon = 'heroicon-o-document-text';
|
||||
|
||||
protected static string $view = 'filament.pages.run-command';
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class PluginResource extends Resource
|
||||
return self::getNavigationLabel();
|
||||
}
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
protected static bool $shouldRegisterNavigation = true;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\System;
|
||||
|
||||
use App\Filament\Resources\System\PluginStoreResource\Pages;
|
||||
use App\Filament\Resources\System\PluginStoreResource\RelationManagers;
|
||||
use App\Livewire\InstallPluginModal;
|
||||
use App\Models\Plugin;
|
||||
use App\Models\PluginStore;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Infolists\Components;
|
||||
use Filament\Infolists;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Filament\Actions\Action;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class PluginStoreResource extends Resource
|
||||
{
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
protected static ?string $navigationGroup = 'System';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\Layout\Stack::make([
|
||||
Tables\Columns\Layout\Stack::make([
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
->weight(FontWeight::Bold)
|
||||
,
|
||||
Tables\Columns\TextColumn::make('description'),
|
||||
]),
|
||||
Tables\Columns\Layout\Stack::make([
|
||||
Tables\Columns\TextColumn::make('version')
|
||||
->formatStateUsing(fn (PluginStore $record) => sprintf("版本: %s | 更新时间: %s", $record->version, $record->release_date))
|
||||
->color('gray')
|
||||
,
|
||||
])
|
||||
])->space(3),
|
||||
])
|
||||
->contentGrid([
|
||||
'md' => 2,
|
||||
'xl' => 3,
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\ViewAction::make()
|
||||
->modalHeading("详细介绍")
|
||||
->modalContent(fn (PluginStore $record) => $record->getFullDescription())
|
||||
->extraModalFooterActions([
|
||||
Action::make("viewOnBlog")
|
||||
->url(fn (PluginStore $record) => $record->getBlogPostUrl())
|
||||
->extraAttributes(['target' => '_blank'])
|
||||
,
|
||||
])
|
||||
,
|
||||
Tables\Actions\Action::make("install")
|
||||
->label("安装")
|
||||
->modalHeading(fn (PluginStore $record) => sprintf("安装插件: %s", $record->title))
|
||||
->modalContent(function (PluginStore $record) {
|
||||
$infolist = new Infolist();
|
||||
$infolist->record = $record;
|
||||
$infolist->schema([
|
||||
Infolists\Components\TextEntry::make('plugin_id')
|
||||
->label(fn () => sprintf("进入目录: %s, 以 root 用户的身份依次执行以下命令进行安装: ", base_path()))
|
||||
->html(true)
|
||||
->formatStateUsing(function (PluginStore $record) {
|
||||
$user = executeCommand("whoami");
|
||||
$commands = [
|
||||
sprintf("sudo -u %s composer config repositories.%s %s", $user, $record->plugin_id, $record->remote_url),
|
||||
sprintf("sudo -u %s composer require %s:%s", $user, $record->package_name, $record->version),
|
||||
sprintf("sudo -u %s php artisan plugin install %s", $user, $record->package_name),
|
||||
];
|
||||
return implode("<br/>", $commands);
|
||||
})
|
||||
,
|
||||
]);
|
||||
return $infolist;
|
||||
})
|
||||
->modalFooterActions(fn () => [])
|
||||
,
|
||||
])
|
||||
->recordAction(null)
|
||||
->paginated(false)
|
||||
;
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPluginStores::route('/'),
|
||||
// 'create' => Pages\CreatePluginStore::route('/create'),
|
||||
// 'edit' => Pages\EditPluginStore::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\System\PluginStoreResource\Pages;
|
||||
|
||||
use App\Filament\Resources\System\PluginStoreResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePluginStore extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PluginStoreResource::class;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\System\PluginStoreResource\Pages;
|
||||
|
||||
use App\Filament\Resources\System\PluginStoreResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPluginStore extends EditRecord
|
||||
{
|
||||
protected static string $resource = PluginStoreResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\System\PluginStoreResource\Pages;
|
||||
|
||||
use App\Filament\Resources\System\PluginStoreResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPluginStores extends ListRecords
|
||||
{
|
||||
protected static string $resource = PluginStoreResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -130,8 +130,10 @@ class UserResource extends Resource
|
||||
,
|
||||
Infolists\Components\TextEntry::make('email')
|
||||
->label(__("label.email"))
|
||||
->copyable()
|
||||
->placeholder("点击复制")
|
||||
,
|
||||
Infolists\Components\TextEntry::make('passkey'),
|
||||
Infolists\Components\TextEntry::make('passkey')->limit(10)->copyable(),
|
||||
Infolists\Components\TextEntry::make('added')->label(__("label.added")),
|
||||
Infolists\Components\TextEntry::make('last_access')->label(__("label.last_access")),
|
||||
Infolists\Components\TextEntry::make('inviter.username')->label(__("label.user.invite_by")),
|
||||
@@ -141,7 +143,12 @@ class UserResource extends Resource
|
||||
Infolists\Components\TextEntry::make('uploadedText')->label(__("label.uploaded")),
|
||||
Infolists\Components\TextEntry::make('downloadedText')->label(__("label.downloaded")),
|
||||
Infolists\Components\TextEntry::make('seedbonus')->label(__("label.user.seedbonus")),
|
||||
])->columns(2),
|
||||
Infolists\Components\TextEntry::make('seed_points')->label(__("label.user.seed_points")),
|
||||
])
|
||||
->columns(6)
|
||||
->columnSpan(4)
|
||||
,
|
||||
|
||||
Components\Group::make([
|
||||
Infolists\Components\TextEntry::make('status')
|
||||
->label(__('label.user.status'))
|
||||
@@ -174,7 +181,8 @@ class UserResource extends Resource
|
||||
->hintAction(self::buildActionCancelTwoStepAuthentication())
|
||||
,
|
||||
])
|
||||
]),
|
||||
->columnSpan(1)
|
||||
])->columns(5),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Repositories\PluginRepository;
|
||||
use App\Repositories\ToolRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Telegram\Bot\Api;
|
||||
use Telegram\Bot\Commands\HelpCommand;
|
||||
|
||||
class PluginController extends Controller
|
||||
{
|
||||
private $repository;
|
||||
|
||||
public function __construct(PluginRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
|
||||
public function runCommandSSE(Request $request)
|
||||
{
|
||||
$commands = [
|
||||
['pwd'], // Example command 1
|
||||
['ls', '-la'], // Example command 2
|
||||
['composer', '--version'], // Example command 3
|
||||
['composer', 'why', 'ext-zip']
|
||||
];
|
||||
foreach ($commands as $command) {
|
||||
$process = new Process($command);
|
||||
$process->setTimeout(0); // Set timeout for each process
|
||||
|
||||
try {
|
||||
$process->mustRun(function ($type, $buffer) {
|
||||
if (Process::OUT === $type) {
|
||||
echo "Output: " . $buffer; // 实时输出
|
||||
} else { // Process::ERR === $type
|
||||
echo "Error: " . $buffer;
|
||||
}
|
||||
});
|
||||
} catch (\Symfony\Component\Process\Exception\ProcessFailedException $e) {
|
||||
echo "Command failed: " . implode(' ', $command) . "\n";
|
||||
echo $e->getMessage();
|
||||
break; // Stop executing further commands
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\PluginStore;
|
||||
use App\Repositories\ToolRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class ServerSendEventController extends Controller
|
||||
{
|
||||
public function sse(Request $request)
|
||||
{
|
||||
$this->initSSE();
|
||||
$response = response()->stream(function () use ($request) {
|
||||
$action = $request->input('action');
|
||||
$this->sendData("action: $action");
|
||||
try {
|
||||
if ($action == "install_plugin") {
|
||||
$this->checkComposer();
|
||||
$pluginId = $request->input('plugin_id');
|
||||
$this->sendData("pluginId: $pluginId");
|
||||
$this->doActionInstallPlugin($pluginId);
|
||||
} else {
|
||||
throw new \InvalidArgumentException("Invalid action: $action");
|
||||
}
|
||||
}catch (\Throwable $throwable) {
|
||||
$this->sendData("error: " . $throwable->getMessage());
|
||||
$this->sendData("close");
|
||||
}
|
||||
});
|
||||
$response->headers->set('Content-Type', 'text/event-stream');
|
||||
$response->headers->set('Cache-Control', 'no-cache');
|
||||
$response->headers->set('Connection', 'keep-alive');
|
||||
$response->headers->set('X-Accel-Buffering', 'no');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function initSSE(): void
|
||||
{
|
||||
@set_time_limit(0); // Disable time limit
|
||||
|
||||
// Prevent buffering
|
||||
if(function_exists('apache_setenv')){
|
||||
@apache_setenv('no-gzip', 1);
|
||||
}
|
||||
|
||||
@ini_set('zlib.output_compression', 0);
|
||||
@ini_set('implicit_flush', 1);
|
||||
|
||||
while (ob_get_level() != 0) {
|
||||
ob_end_flush();
|
||||
}
|
||||
ob_implicit_flush(1);
|
||||
}
|
||||
|
||||
private function sendData(string $data): void
|
||||
{
|
||||
echo sprintf("data: %s\n\n", $data);
|
||||
@ob_flush();
|
||||
@flush();
|
||||
}
|
||||
|
||||
private function runCommand(string $command): void
|
||||
{
|
||||
$this->sendData("running $command, this may take a while...");
|
||||
$process = Process::fromShellCommandline($command);
|
||||
$process->setWorkingDirectory(base_path());
|
||||
$process->setTimeout(null);
|
||||
$process->mustRun(function ($type, $buffer) {
|
||||
$this->sendData($buffer);
|
||||
});
|
||||
}
|
||||
|
||||
private function doActionInstallPlugin(string $pluginId): void
|
||||
{
|
||||
$pluginInfo = PluginStore::getInfo($pluginId);
|
||||
if (!$pluginInfo) {
|
||||
throw new \InvalidArgumentException("invalid plugin: $pluginId");
|
||||
}
|
||||
$this->sendData(sprintf("going to install plugin: %s, version: %s", $pluginInfo['title'], $pluginInfo['version']));
|
||||
$commands = [];
|
||||
$commands[] = sprintf("composer config repositories.%s git %s", $pluginInfo['plugin_id'], $pluginInfo['remote_url']);
|
||||
$commands[] = sprintf("composer require %s:%s", $pluginInfo['package_name'], $pluginInfo['version']);
|
||||
$commands[] = sprintf("php artisan plugin install %s", $pluginInfo['package_name']);
|
||||
foreach ($commands as $command) {
|
||||
$this->runCommand($command);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkComposer(): void
|
||||
{
|
||||
$this->sendData("checking composer ...");
|
||||
$composerCacheDir = executeCommand("composer config cache-vcs-dir -f " . base_path("composer.json"));
|
||||
$this->sendData("composer cache-vcs-dir: $composerCacheDir");
|
||||
if (!is_dir($composerCacheDir)) {
|
||||
$this->sendData("going to mkdir: $composerCacheDir");
|
||||
try {
|
||||
mkdir($composerCacheDir, 0755, true);
|
||||
$this->sendData("success create composer cache-vcs-dir: $composerCacheDir");
|
||||
} catch (\Throwable $e) {
|
||||
$this->sendData("fail to mkdir: " . $e->getMessage());
|
||||
$this->sendData("Please execute the following command as root user:");
|
||||
$this->sendData(sprintf("mkdir -p %s", $composerCacheDir));
|
||||
throw new \RuntimeException("stop due to composer cache-vcs-dir: $composerCacheDir not exists");
|
||||
}
|
||||
}
|
||||
if (!is_writable($composerCacheDir)) {
|
||||
$this->sendData("cache directory: $composerCacheDir is not writable");
|
||||
$this->sendData("Please execute the following command as root user:");
|
||||
$user = executeCommand("whoami");
|
||||
$this->sendData(sprintf("chown -R %s:%s %s", $user, $user, $composerCacheDir));
|
||||
throw new \RuntimeException("stop due to composer cache-vcs-dir: $composerCacheDir not writable");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\PluginStore;
|
||||
use App\Repositories\ToolRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Telegram\Bot\Api;
|
||||
use Telegram\Bot\Commands\HelpCommand;
|
||||
|
||||
|
||||
+3
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use App\Http\Middleware\Filament;
|
||||
use App\Http\Middleware\Locale;
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
@@ -49,7 +50,8 @@ class Kernel extends HttpKernel
|
||||
],
|
||||
'filament' => [
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Filament\Http\Middleware\Authenticate::class,
|
||||
// \Filament\Http\Middleware\Authenticate::class,
|
||||
Filament::class,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
||||
use App\Models\User;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\UnauthorizedException;
|
||||
|
||||
class Admin
|
||||
{
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\PluginStore;
|
||||
use Livewire\Component;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class InstallPluginModal extends Component
|
||||
{
|
||||
// public PluginStore $record;
|
||||
|
||||
public $output = ''; // 存储命令输出
|
||||
|
||||
// public function mount(PluginStore $record)
|
||||
// {
|
||||
// $this->recored = $record;
|
||||
// $this->output = sprintf("点击按钮以开始安装 %s ...", $record->title);
|
||||
// }
|
||||
|
||||
public function executeCommand($command)
|
||||
{
|
||||
$this->output = ''; // 清空之前的输出
|
||||
// $command = "whereis composer";
|
||||
// $process = new Process([$command]);
|
||||
// $process->setTimeout(60); // 设置超时时间(秒)
|
||||
//
|
||||
// $process->start(); // 启动异步进程
|
||||
//
|
||||
// foreach ($process as $type => $data) {
|
||||
// if ($type === Process::OUT) {
|
||||
// $this->output .= $data; // 实时追加标准输出
|
||||
// } elseif ($type === Process::ERR) {
|
||||
// $this->output .= "[ERROR]: $data"; // 实时追加错误输出
|
||||
// }
|
||||
//
|
||||
//// $this->dispatch('updateTextarea', $this->output); // 通知前端更新
|
||||
// }
|
||||
$process = new Process(['/usr/local/bin/composer', 'info']);
|
||||
$process->setTimeout(3600); // 可选,设置超时时间
|
||||
$basePath = base_path();
|
||||
do_log("base path: $basePath");
|
||||
$process->setWorkingDirectory($basePath);
|
||||
try {
|
||||
$process->mustRun(function ($type, $buffer) {
|
||||
if (Process::OUT === $type) {
|
||||
$this->output = $buffer;
|
||||
$this->dispatch('updateTextarea', $this->output);
|
||||
} else { // Process::ERR === $type
|
||||
do_log("executeCommand, ERR: " . $buffer, 'error');
|
||||
}
|
||||
});
|
||||
} catch (\Exception $e) {
|
||||
do_log($e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.install-plugin-modal');
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
class BonusLogs extends NexusModel
|
||||
{
|
||||
protected $table = 'bonus_logs';
|
||||
@@ -41,6 +43,7 @@ class BonusLogs extends NexusModel
|
||||
|
||||
const BUSINESS_TYPE_ROLE_WORK_SALARY = 1000;
|
||||
const BUSINESS_TYPE_TORRENT_BE_DOWNLOADED = 1001;
|
||||
const BUSINESS_TYPE_RECEIVE_REWARD = 1002;
|
||||
|
||||
public static array $businessTypes = [
|
||||
self::BUSINESS_TYPE_CANCEL_HIT_AND_RUN => ['text' => 'Cancel H&R'],
|
||||
@@ -65,6 +68,7 @@ class BonusLogs extends NexusModel
|
||||
|
||||
self::BUSINESS_TYPE_ROLE_WORK_SALARY => ['text' => 'Role work salary'],
|
||||
self::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => ['text' => 'Torrent be downloaded'],
|
||||
self::BUSINESS_TYPE_RECEIVE_REWARD => ['text' => 'Receive reward'],
|
||||
];
|
||||
|
||||
public function getBusinessTypeTextAttribute()
|
||||
@@ -102,5 +106,23 @@ class BonusLogs extends NexusModel
|
||||
return $result ?? self::DEFAULT_BONUS_BUY_CHANGE_USERNAME_CARD;
|
||||
}
|
||||
|
||||
public static function add(int $userId, float $old, float $delta, float $new, string $comment, int $businessType)
|
||||
{
|
||||
if (!isset(self::$businessTypes[$businessType])) {
|
||||
throw new \InvalidArgumentException("Invalid business type: $businessType");
|
||||
}
|
||||
$nowStr = Carbon::now()->toDateTimeString();
|
||||
return self::query()->create([
|
||||
'business_type' => $businessType,
|
||||
'uid' => $userId,
|
||||
'old_total_value' => $old,
|
||||
'value' => $delta,
|
||||
'new_total_value' => $new,
|
||||
'comment' => sprintf("[%s] %s", self::$businessTypes[$businessType]['text'], $comment),
|
||||
'created_at' => $nowStr,
|
||||
'updated_at' => $nowStr,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Sushi\Sushi;
|
||||
|
||||
class PluginStore extends Model
|
||||
{
|
||||
use Sushi;
|
||||
|
||||
const PLUGIN_LIST_API = "https://nppl.nexusphp.workers.dev";
|
||||
const BLOG_POST_INFO_API = "https://nexusphp.org/wp-json/wp/v2/posts/%d";
|
||||
const BLOG_POST_URL = "https://nexusphp.org/?p=%d";
|
||||
|
||||
public function getRows()
|
||||
{
|
||||
return Http::get(self::PLUGIN_LIST_API)->json();
|
||||
}
|
||||
|
||||
public function getBlogPostUrl(): string
|
||||
{
|
||||
return sprintf(self::BLOG_POST_URL, $this->post_id);
|
||||
}
|
||||
|
||||
public function getFullDescription(): Htmlable
|
||||
{
|
||||
$url = $this->getBlogPostInfoUrl($this->post_id);
|
||||
$logPrefix = sprintf("post_id: %s, url: %s", $this->post_id, $url);
|
||||
$defaultContent = "无法获取详细信息 ...";
|
||||
try {
|
||||
$result = Http::get($url)->json();
|
||||
do_log("$logPrefix, result: " . json_encode($result));
|
||||
$content = $result['content']['rendered'] ?? $result['message'] ?? $defaultContent;
|
||||
} catch (\Exception $e) {
|
||||
do_log(sprintf(
|
||||
"%s, error: %s",
|
||||
$logPrefix, $e->getMessage() . $e->getTraceAsString()
|
||||
), 'error');
|
||||
$content = $defaultContent;
|
||||
}
|
||||
return new HtmlString($content);
|
||||
}
|
||||
|
||||
private function getBlogPostInfoUrl(int $postId): string
|
||||
{
|
||||
return sprintf(self::BLOG_POST_INFO_API, $postId);
|
||||
}
|
||||
|
||||
public static function getInfo(string $id)
|
||||
{
|
||||
return Http::get(self::PLUGIN_LIST_API . "/plugin/$id")->json();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@ namespace App\Models;
|
||||
|
||||
class Post extends NexusModel
|
||||
{
|
||||
public function user()
|
||||
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'userid');
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
class Torrent extends NexusModel
|
||||
{
|
||||
protected $fillable = [
|
||||
'name', 'filename', 'save_as', 'descr', 'small_descr', 'ori_descr',
|
||||
'name', 'filename', 'save_as', 'small_descr',
|
||||
'category', 'source', 'medium', 'codec', 'standard', 'processing', 'team', 'audiocodec',
|
||||
'size', 'added', 'type', 'numfiles', 'owner', 'nfo', 'sp_state', 'promotion_time_type',
|
||||
'promotion_until', 'anonymous', 'url', 'pos_state', 'cache_stamp', 'picktype', 'picktime',
|
||||
'last_reseed', 'pt_gen', 'technical_info', 'leechers', 'seeders', 'cover', 'last_action',
|
||||
'last_reseed', 'leechers', 'seeders', 'cover', 'last_action',
|
||||
'times_completed', 'approval_status', 'banned', 'visible', 'pos_state_until', 'price',
|
||||
];
|
||||
|
||||
@@ -25,7 +25,6 @@ class Torrent extends NexusModel
|
||||
|
||||
protected $casts = [
|
||||
'added' => 'datetime',
|
||||
'pt_gen' => 'array',
|
||||
'promotion_until' => 'datetime',
|
||||
'pos_state_until' => 'datetime',
|
||||
];
|
||||
@@ -241,7 +240,7 @@ class Torrent extends NexusModel
|
||||
|
||||
public static function getFieldsForList($appendTableName = false): array|bool
|
||||
{
|
||||
$fields = 'id, sp_state, promotion_time_type, promotion_until, banned, picktype, pos_state, category, source, medium, codec, standard, processing, team, audiocodec, leechers, seeders, name, small_descr, times_completed, size, added, comments,anonymous,owner,url,cache_stamp, pt_gen, hr, approval_status, cover, price';
|
||||
$fields = 'id, sp_state, promotion_time_type, promotion_until, banned, picktype, pos_state, category, source, medium, codec, standard, processing, team, audiocodec, leechers, seeders, name, small_descr, times_completed, size, added, comments,anonymous,owner,url,cache_stamp, hr, approval_status, cover, price';
|
||||
$fields = preg_split('/[,\s]+/', $fields);
|
||||
if ($appendTableName) {
|
||||
foreach ($fields as &$value) {
|
||||
@@ -513,4 +512,9 @@ class Torrent extends NexusModel
|
||||
{
|
||||
return $this->hasMany(TorrentOperationLog::class, 'torrent_id');
|
||||
}
|
||||
|
||||
public function extra(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(TorrentExtra::class, 'torrent_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class TorrentExtra extends NexusModel
|
||||
{
|
||||
public $timestamps = true;
|
||||
|
||||
protected $fillable = ['torrent_id', 'descr', 'ori_descr', 'media_info'];
|
||||
|
||||
public function torrent()
|
||||
{
|
||||
return $this->belongsTo(Torrent::class, 'torrent_id');
|
||||
}
|
||||
|
||||
}
|
||||
+19
-7
@@ -182,9 +182,9 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'username', 'email', 'passhash', 'secret', 'stylesheet', 'editsecret', 'added', 'modcomment', 'enabled', 'status',
|
||||
'username', 'email', 'passhash', 'secret', 'stylesheet', 'editsecret', 'added', 'enabled', 'status',
|
||||
'leechwarn', 'leechwarnuntil', 'page', 'class', 'uploaded', 'downloaded', 'clientselect', 'showclienterror', 'last_home',
|
||||
'seedbonus', 'bonuscomment', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
|
||||
'seedbonus', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
|
||||
'seed_points_per_hour', 'passkey',
|
||||
];
|
||||
|
||||
@@ -229,7 +229,7 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
'uploaded', 'downloaded', 'seedbonus', 'seedtime', 'leechtime',
|
||||
'invited_by', 'enabled', 'seed_points', 'last_access', 'invites',
|
||||
'lang', 'attendance_card', 'privacy', 'noad', 'downloadpos', 'donoruntil', 'donor',
|
||||
'bonuscomment', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
|
||||
'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
|
||||
'seed_points_per_hour'
|
||||
];
|
||||
|
||||
@@ -535,6 +535,11 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
return $this->examAndTasks()->wherePivot("status", ExamUser::STATUS_NORMAL);
|
||||
}
|
||||
|
||||
public function modifyLogs(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(UserModifyLog::class, "user_id");
|
||||
}
|
||||
|
||||
public function getAvatarAttribute($value)
|
||||
{
|
||||
if ($value) {
|
||||
@@ -560,10 +565,17 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
throw new \RuntimeException('This method only works when user exists !');
|
||||
}
|
||||
//@todo how to do prepare bindings here ?
|
||||
$comment = addslashes($comment);
|
||||
do_log("update: " . json_encode($update) . ", $commentField: $comment", 'notice');
|
||||
$update[$commentField] = NexusDB::raw("if($commentField = '', '$comment', concat_ws('\n', '$comment', $commentField))");
|
||||
return $this->update($update);
|
||||
// $comment = addslashes($comment);
|
||||
// do_log("update: " . json_encode($update) . ", $commentField: $comment", 'notice');
|
||||
// $update[$commentField] = NexusDB::raw("if($commentField = '', '$comment', concat_ws('\n', '$comment', $commentField))");
|
||||
|
||||
if ($commentField != "modcomment") {
|
||||
throw new \RuntimeException("unsupported commentField: $commentField !");
|
||||
}
|
||||
return NexusDB::transaction(function () use ($update, $comment) {
|
||||
$this->modifyLogs()->create(['content' => $comment]);
|
||||
return $this->update($update);
|
||||
});
|
||||
}
|
||||
|
||||
public function canAccessAdmin(): bool
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class UserModifyLog extends NexusModel
|
||||
{
|
||||
protected $fillable = ['user_id', 'content', ];
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, "user_id");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,8 @@ use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use Filament\Tables\Enums\FiltersLayout;
|
||||
use Filament\Tables\Table;
|
||||
use Livewire\Livewire;
|
||||
use NexusPlugin\TelegramBot\Filament\TelegramBotBindsResource;
|
||||
use NexusPlugin\TelegramBot\Filament\TelegramBotResource;
|
||||
|
||||
class AppPanelProvider extends PanelProvider
|
||||
{
|
||||
@@ -29,7 +31,7 @@ class AppPanelProvider extends PanelProvider
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('app')
|
||||
->id('admin')
|
||||
->homeUrl("/")
|
||||
->sidebarWidth("15rem")
|
||||
->topbar(true)
|
||||
@@ -40,6 +42,10 @@ class AppPanelProvider extends PanelProvider
|
||||
->colors([
|
||||
'primary' => Color::Amber,
|
||||
])
|
||||
->resources([
|
||||
// TelegramBotResource::class,
|
||||
// TelegramBotBindsResource::class
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([
|
||||
@@ -51,6 +57,7 @@ class AppPanelProvider extends PanelProvider
|
||||
// Widgets\AccountWidget::class,
|
||||
// Widgets\FilamentInfoWidget::class,
|
||||
])
|
||||
->discoverClusters(app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters')
|
||||
->middleware([
|
||||
// EncryptCookies::class,
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Http\Middleware\Filament;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -47,14 +48,15 @@ class RouteServiceProvider extends ServiceProvider
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web.php'));
|
||||
|
||||
Route::prefix('api')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/tracker.php'));
|
||||
|
||||
Route::prefix('api')
|
||||
->namespace($this->namespace)
|
||||
->middleware('throttle:third-party')
|
||||
->group(base_path('routes/third-party.php'));
|
||||
|
||||
Route::prefix('admin')
|
||||
->namespace($this->namespace)
|
||||
->middleware([Filament::class])
|
||||
->group(base_path('routes/admin.php'));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -381,6 +381,14 @@ class TorrentRepository extends BaseRepository
|
||||
return md5($passkey . date('Ymd') . $user['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param $id
|
||||
* @param $uid
|
||||
* @param $initializeIfNotExists
|
||||
* @return string
|
||||
* @throws NexusException
|
||||
*/
|
||||
public function getTrackerReportAuthKey($id, $uid, $initializeIfNotExists = false): string
|
||||
{
|
||||
$key = $this->getTrackerReportAuthKeySecret($id, $uid, $initializeIfNotExists);
|
||||
@@ -389,6 +397,8 @@ class TorrentRepository extends BaseRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* check tracker report authkey
|
||||
* if valid, the result will be the date the key generate, else if will be empty string
|
||||
*
|
||||
|
||||
@@ -227,6 +227,13 @@ class TrackerRepository extends BaseRepository
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @param $authkey
|
||||
* @return array
|
||||
* @throws TrackerException
|
||||
*/
|
||||
protected function checkAuthkey($authkey)
|
||||
{
|
||||
$arr = explode('|', $authkey);
|
||||
|
||||
@@ -9,6 +9,7 @@ if (!RUNNING_IN_OCTANE) {
|
||||
}
|
||||
$GLOBALS['hook'] = $hook = new \Nexus\Plugin\Hook();
|
||||
$GLOBALS['plugin'] = $plugin = new \Nexus\Plugin\Plugin();
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Create The Application
|
||||
|
||||
+5
-3
@@ -33,6 +33,7 @@
|
||||
"ext-xml": "*",
|
||||
"ext-zend-opcache": "*",
|
||||
"ext-zip": "*",
|
||||
"calebporzio/sushi": "^2.5",
|
||||
"elasticsearch/elasticsearch": "^7.16",
|
||||
"filament/filament": "^3.2",
|
||||
"flowframe/laravel-trend": "^0.3",
|
||||
@@ -45,7 +46,6 @@
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.5",
|
||||
"league/flysystem-sftp-v3": "^3.0",
|
||||
"masbug/flysystem-google-drive-ext": "^2.0",
|
||||
"meilisearch/meilisearch-php": "^1.0",
|
||||
"orangehill/iseed": "^3.0",
|
||||
"phpgangsta/googleauthenticator": "dev-master",
|
||||
@@ -74,7 +74,8 @@
|
||||
"@php artisan filament:upgrade"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
|
||||
"@php artisan passport:keys"
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi"
|
||||
@@ -95,7 +96,8 @@
|
||||
"secure-http": false,
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"process-timeout": 900
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
|
||||
Generated
+280
-597
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
<?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::create('torrent_extras', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('torrent_id')->unique();
|
||||
$table->mediumText('descr');
|
||||
$table->text('media_info')->nullable();
|
||||
$table->binary('nfo')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('torrent_extras');
|
||||
}
|
||||
};
|
||||
@@ -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::create('user_modify_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('user_id')->index();
|
||||
$table->text('content');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('user_modify_logs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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('users', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('users', 'modcomment')) {
|
||||
$table->dropColumn('modcomment');
|
||||
}
|
||||
if (Schema::hasColumn('users', 'bonuscomment')) {
|
||||
$table->dropColumn("bonuscomment");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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('torrents', function (Blueprint $table) {
|
||||
foreach (["ori_descr", "descr", "nfo", "technical_info", "pt_gen"] as $field) {
|
||||
if (Schema::hasColumn('torrents', $field)) {
|
||||
$table->dropColumn($field);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
defined('VERSION_NUMBER') || define('VERSION_NUMBER', '1.9.0');
|
||||
defined('RELEASE_DATE') || define('RELEASE_DATE', '2024-12-29');
|
||||
defined('RELEASE_DATE') || define('RELEASE_DATE', '2025-01-19');
|
||||
defined('IN_TRACKER') || define('IN_TRACKER', false);
|
||||
defined('PROJECTNAME') || define("PROJECTNAME","NexusPHP");
|
||||
defined('NEXUSPHPURL') || define("NEXUSPHPURL","https://nexusphp.org");
|
||||
|
||||
+11
-10
@@ -3363,16 +3363,17 @@ function linkcolor($num) {
|
||||
}
|
||||
|
||||
function writecomment($userid, $comment, $oldModcomment = null) {
|
||||
if (is_null($oldModcomment)) {
|
||||
$res = sql_query("SELECT modcomment FROM users WHERE id = '$userid'") or sqlerr(__FILE__, __LINE__);
|
||||
$arr = mysql_fetch_assoc($res);
|
||||
$modcomment = date("Y-m-d") . " - " . $comment . "" . ($arr['modcomment'] != "" ? "\n" : "") . $arr['modcomment'];
|
||||
} else {
|
||||
$modcomment = date("Y-m-d") . " - " . $comment . "" . ($oldModcomment != "" ? "\n" : "") .$oldModcomment;
|
||||
}
|
||||
$modcom = sqlesc($modcomment);
|
||||
do_log("update user: $userid prepend modcomment: $comment, with oldModcomment: $oldModcomment");
|
||||
return sql_query("UPDATE users SET modcomment = $modcom WHERE id = '$userid'") or sqlerr(__FILE__, __LINE__);
|
||||
\App\Models\UserModifyLog::query()->create(['user_id' => $userid, 'content' => date("Y-m-d") . " - " . $comment]);
|
||||
// if (is_null($oldModcomment)) {
|
||||
// $res = sql_query("SELECT modcomment FROM users WHERE id = '$userid'") or sqlerr(__FILE__, __LINE__);
|
||||
// $arr = mysql_fetch_assoc($res);
|
||||
// $modcomment = date("Y-m-d") . " - " . $comment . "" . ($arr['modcomment'] != "" ? "\n" : "") . $arr['modcomment'];
|
||||
// } else {
|
||||
// $modcomment = date("Y-m-d") . " - " . $comment . "" . ($oldModcomment != "" ? "\n" : "") .$oldModcomment;
|
||||
// }
|
||||
// $modcom = sqlesc($modcomment);
|
||||
// do_log("update user: $userid prepend modcomment: $comment, with oldModcomment: $oldModcomment");
|
||||
// return sql_query("UPDATE users SET modcomment = $modcom WHERE id = '$userid'") or sqlerr(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
function return_torrent_bookmark_array($userid)
|
||||
|
||||
@@ -264,6 +264,9 @@ function getLogFile($append = '')
|
||||
if ($append) {
|
||||
$name .= "-$append";
|
||||
}
|
||||
if (isRunningInConsole()) {
|
||||
$name .= sprintf("-cli-%s-%s", get_current_user(), getmyuid());
|
||||
}
|
||||
$logFile = sprintf('%s-%s%s', $name, date('Y-m-d'), $suffix);
|
||||
return $logFiles[$append] = $logFile;
|
||||
|
||||
@@ -1221,6 +1224,7 @@ function is_donor(array $userInfo): bool
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param $authkey
|
||||
* @return false|int|mixed|string|null
|
||||
* @throws \App\Exceptions\NexusException
|
||||
|
||||
@@ -26,6 +26,8 @@ use App\Repositories\TorrentRepository;
|
||||
use Carbon\Carbon;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
@@ -332,6 +334,18 @@ class Update extends Install
|
||||
["value" => User::query()->where("class", User::CLASS_STAFF_LEADER)->first(["id"])->id]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.9.0
|
||||
*/
|
||||
if (!Schema::hasTable("torrent_extras")) {
|
||||
$this->runMigrate("database/migrations/2025_01_08_133552_create_torrent_extra_table.php");
|
||||
$this->runMigrate("database/migrations/2025_01_08_133847_create_user_modify_logs_table.php");
|
||||
$this->runMigrate("database/migrations/2025_01_18_235747_drop_users_table_text_column.php");
|
||||
$this->runMigrate("database/migrations/2025_01_18_235757_drop_torrents_table_text_column.php");
|
||||
Artisan::call("upgrade:upgrade:migrate_torrents_table_text_column");
|
||||
Artisan::call("upgrade:migrate_users_table_comment_related_column");
|
||||
}
|
||||
}
|
||||
|
||||
public function runExtraMigrate()
|
||||
|
||||
@@ -157,6 +157,28 @@ class AjaxInterface{
|
||||
$rep = new \App\Repositories\ExamRepository();
|
||||
return $rep->assignToUser($CURUSER['id'], $params['exam_id']);
|
||||
}
|
||||
|
||||
public static function addToken($params)
|
||||
{
|
||||
global $CURUSER;
|
||||
if (empty($params['name'])) {
|
||||
throw new \InvalidArgumentException("Name is required");
|
||||
}
|
||||
$user = \App\Models\User::query()->findOrFail($CURUSER['id'], \App\Models\User::$commonFields);
|
||||
$user->createToken($params['name']);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function removeToken($params)
|
||||
{
|
||||
global $CURUSER;
|
||||
if (empty($params['id'])) {
|
||||
throw new \InvalidArgumentException("id is required");
|
||||
}
|
||||
$user = \App\Models\User::query()->findOrFail($CURUSER['id'], \App\Models\User::$commonFields);
|
||||
$user->tokens()->where('id', $params['id'])->delete();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$class = 'AjaxInterface';
|
||||
@@ -171,5 +193,6 @@ try {
|
||||
throw new \RuntimeException("Invalid action: $action");
|
||||
}
|
||||
}catch(\Throwable $exception){
|
||||
do_log($exception->getMessage() . $exception->getTraceAsString(), "error");
|
||||
exit(json_encode(fail($exception->getMessage(), $_POST)));
|
||||
}
|
||||
|
||||
+2
-53
@@ -26,45 +26,13 @@ if (isset($passkey) && strlen($passkey) != 32) warn("Invalid passkey (" . strlen
|
||||
|
||||
$redis = $Cache->getRedis();
|
||||
$torrentNotExistsKey = "torrent_not_exists";
|
||||
$authKeyInvalidKey = "authkey_invalid";
|
||||
$passkeyInvalidKey = "passkey_invalid";
|
||||
$isReAnnounce = false;
|
||||
$reAnnounceInterval = 5;
|
||||
$frequencyInterval = 30;
|
||||
$isStoppedOrCompleted = !empty($GLOBALS['event']) && in_array($GLOBALS['event'], array('completed', 'stopped'));
|
||||
$userAuthenticateKey = "";
|
||||
if (!empty($_GET['authkey'])) {
|
||||
$authkey = $_GET['authkey'];
|
||||
$parts = explode("|", $authkey);
|
||||
if (count($parts) != 3) {
|
||||
warn("authkey format error");
|
||||
}
|
||||
$authKeyTid = $parts[0];
|
||||
$authKeyUid = $userAuthenticateKey = $parts[1];
|
||||
$subAuthkey = sprintf("%s|%s", $authKeyTid, $authKeyUid);
|
||||
//check ReAnnounce
|
||||
$lockParams = [
|
||||
'user' => $authKeyUid,
|
||||
'info_hash' => $info_hash
|
||||
];
|
||||
|
||||
$lockString = http_build_query($lockParams);
|
||||
$lockKey = "isReAnnounce:" . md5($lockString);
|
||||
if (!$redis->set($lockKey, TIMENOW, ['nx', 'ex' => $reAnnounceInterval])) {
|
||||
$isReAnnounce = true;
|
||||
}
|
||||
$reAnnounceCheckByAuthKey = "reAnnounceCheckByAuthKey:$subAuthkey";
|
||||
if (!$isStoppedOrCompleted && !$isReAnnounce && !$redis->set($reAnnounceCheckByAuthKey, TIMENOW, ['nx', 'ex' => $frequencyInterval])) {
|
||||
$msg = "Request too frequent(a)";
|
||||
do_log(sprintf("[ANNOUNCE] %s key: %s already exists, value: %s", $msg, $reAnnounceCheckByAuthKey, TIMENOW));
|
||||
warn($msg, 300);
|
||||
}
|
||||
if ($redis->get("$authKeyInvalidKey:$authkey")) {
|
||||
$msg = "Invalid authkey";
|
||||
do_log("[ANNOUNCE] $msg");
|
||||
warn($msg);
|
||||
}
|
||||
} elseif (!empty($_GET['passkey'])) {
|
||||
if (!empty($_GET['passkey'])) {
|
||||
$passkey = $userAuthenticateKey = $_GET['passkey'];
|
||||
if ($redis->get("$passkeyInvalidKey:$passkey")) {
|
||||
$msg = "Passkey invalid";
|
||||
@@ -81,7 +49,7 @@ if (!empty($_GET['authkey'])) {
|
||||
$isReAnnounce = true;
|
||||
}
|
||||
} else {
|
||||
warn("Require passkey or authkey");
|
||||
warn("Require passkey");
|
||||
}
|
||||
|
||||
if ($redis->get("$torrentNotExistsKey:$info_hash")) {
|
||||
@@ -95,20 +63,7 @@ if (!$isStoppedOrCompleted && !$isReAnnounce && !$redis->set($torrentReAnnounceK
|
||||
do_log(sprintf("[ANNOUNCE] %s key: %s already exists, value: %s", $msg, $torrentReAnnounceKey, TIMENOW));
|
||||
warn($msg, 300);
|
||||
}
|
||||
|
||||
|
||||
dbconn_announce();
|
||||
//check authkey
|
||||
if (!empty($_REQUEST['authkey'])) {
|
||||
try {
|
||||
$GLOBALS['passkey'] = $_GET['passkey'] = get_passkey_by_authkey($_REQUEST['authkey']);
|
||||
} catch (\Exception $exception) {
|
||||
$redis->set("$authKeyInvalidKey:".$_REQUEST['authkey'], TIMENOW, ['ex' => 3600*24]);
|
||||
warn($exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//4. GET IP AND CHECK PORT
|
||||
$ip = getip(); // avoid to get the spoof ip from some agent
|
||||
@@ -229,12 +184,6 @@ if (!$torrent) {
|
||||
}
|
||||
$GLOBALS['torrent'] = $torrent;
|
||||
$torrentid = $torrent["id"];
|
||||
if (isset($authKeyTid) && $authKeyTid != $torrentid) {
|
||||
$redis->set("$authKeyInvalidKey:$authkey", TIMENOW, ['ex' => 3600*24]);
|
||||
$msg = "Invalid authkey: $authkey 2";
|
||||
do_log("[ANNOUNCE] $msg");
|
||||
warn($msg);
|
||||
}
|
||||
if ($torrent['banned'] == 'yes') {
|
||||
if (!user_can('seebanned', false, $az['id'])) {
|
||||
err("torrent banned");
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -13,7 +13,7 @@ cropperjs/dist/cropper.min.css:
|
||||
|
||||
filepond/dist/filepond.min.css:
|
||||
(*!
|
||||
* FilePond 4.32.5
|
||||
* FilePond 4.32.6
|
||||
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
||||
* Please visit https://pqina.nl/filepond/ for details.
|
||||
*)
|
||||
|
||||
+4
-7
@@ -11,8 +11,9 @@ if (!isset($id) || !$id)
|
||||
die();
|
||||
|
||||
$taxonomyFields = "sources.name AS source_name, media.name AS medium_name, codecs.name AS codec_name, standards.name AS standard_name, processings.name AS processing_name, teams.name AS team_name, audiocodecs.name AS audiocodec_name";
|
||||
$res = sql_query("SELECT torrents.cache_stamp, torrents.sp_state, torrents.url, torrents.small_descr, torrents.seeders, torrents.banned, torrents.leechers, torrents.info_hash, torrents.filename, nfo, LENGTH(torrents.nfo) AS nfosz, torrents.last_action, torrents.name, torrents.owner, torrents.save_as, torrents.descr, torrents.visible, torrents.size, torrents.added, torrents.views, torrents.hits, torrents.times_completed, torrents.id, torrents.type, torrents.numfiles, torrents.anonymous, torrents.pt_gen, torrents.technical_info, torrents.hr, torrents.promotion_until, torrents.promotion_time_type, torrents.approval_status, torrents.price,
|
||||
categories.name AS cat_name, categories.mode as search_box_id, $taxonomyFields
|
||||
$extraFields = "torrent_extras.descr, torrent_extras.nfo, LENGTH(torrent_extras.nfo) AS nfosz, torrent_extras.media_info as technical_info";
|
||||
$res = sql_query("SELECT torrents.cache_stamp, torrents.sp_state, torrents.url, torrents.small_descr, torrents.seeders, torrents.banned, torrents.leechers, torrents.info_hash, torrents.filename, torrents.last_action, torrents.name, torrents.owner, torrents.save_as, torrents.visible, torrents.size, torrents.added, torrents.views, torrents.hits, torrents.times_completed, torrents.id, torrents.type, torrents.numfiles, torrents.anonymous, torrents.hr, torrents.promotion_until, torrents.promotion_time_type, torrents.approval_status, torrents.price,
|
||||
categories.name AS cat_name, categories.mode as search_box_id, $taxonomyFields, $extraFields
|
||||
FROM torrents LEFT JOIN categories ON torrents.category = categories.id
|
||||
LEFT JOIN sources ON torrents.source = sources.id
|
||||
LEFT JOIN media ON torrents.medium = media.id
|
||||
@@ -21,6 +22,7 @@ FROM torrents LEFT JOIN categories ON torrents.category = categories.id
|
||||
LEFT JOIN processings ON torrents.processing = processings.id
|
||||
LEFT JOIN teams ON torrents.team = teams.id
|
||||
LEFT JOIN audiocodecs ON torrents.audiocodec = audiocodecs.id
|
||||
LEFT JOIN torrent_extras ON torrents.id = torrent_extras.torrent_id
|
||||
WHERE torrents.id = $id LIMIT 1")
|
||||
or sqlerr();
|
||||
$row = mysql_fetch_array($res);
|
||||
@@ -398,11 +400,6 @@ JS;
|
||||
echo $Cache->next_row();
|
||||
}
|
||||
}
|
||||
|
||||
if (get_setting('main.enable_pt_gen_system') == 'yes' && !empty($row['pt_gen'])) {
|
||||
$ptGen = new \Nexus\PTGen\PTGen();
|
||||
$ptGen->updateTorrentPtGen($row);
|
||||
}
|
||||
if (!empty($otherCopiesIdArr))
|
||||
{
|
||||
// $where_area = " url = " . sqlesc((int)$imdb_id) ." AND torrents.id != ".sqlesc($id);
|
||||
|
||||
@@ -139,7 +139,6 @@ 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']));
|
||||
}
|
||||
$trackerReportAuthKey = $torrentRep->getTrackerReportAuthKey($id, $CURUSER['id'], true);
|
||||
$dict = \Rhilip\Bencode\Bencode::load($fn);
|
||||
$dict['announce'] = $ssl_torrent . $base_announce_url . "?passkey=" . $CURUSER['passkey'];
|
||||
do_log(sprintf("[ANNOUNCE_URL], user: %s, torrent: %s, url: %s", $CURUSER['id'] ?? '', $id, $dict['announce']));
|
||||
@@ -221,6 +220,5 @@ else
|
||||
//header ("Content-Disposition: attachment; filename=".$row["filename"]."");
|
||||
//ob_implicit_flush(true);
|
||||
//print(benc($dict));
|
||||
\Nexus\Database\NexusDB::cache_put("authkey2passkey:$trackerReportAuthKey", $CURUSER['passkey'], 3600*24);
|
||||
echo \Rhilip\Bencode\Bencode::encode($dict);
|
||||
?>
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+9
-9
File diff suppressed because one or more lines are too long
+27
-27
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+27
-16
@@ -25,7 +25,7 @@ if (!$id)
|
||||
die();
|
||||
|
||||
|
||||
$res = sql_query("SELECT id, category, owner, filename, save_as, anonymous, picktype, picktime, added, pt_gen, banned FROM torrents WHERE id = ".mysql_real_escape_string($id));
|
||||
$res = sql_query("SELECT id, category, owner, filename, save_as, anonymous, picktype, picktime, added, banned FROM torrents WHERE id = ".mysql_real_escape_string($id));
|
||||
$row = mysql_fetch_array($res);
|
||||
$torrentAddedTimeString = $row['added'];
|
||||
if (!$row)
|
||||
@@ -35,6 +35,7 @@ if ($CURUSER["id"] != $row["owner"] && !user_can('torrentmanage'))
|
||||
bark($lang_takeedit['std_not_owner']);
|
||||
$oldcatmode = get_single_value("categories","mode","WHERE id=".sqlesc($row['category']));
|
||||
$updateset = array();
|
||||
$extraUpdate = [];
|
||||
|
||||
//$fname = $row["filename"];
|
||||
//preg_match('/^(.+)\.torrent$/si', $fname, $matches);
|
||||
@@ -45,19 +46,23 @@ $url = parse_imdb_id($_POST['url'] ?? '');
|
||||
/**
|
||||
* add PT-Gen
|
||||
* @since 1.6
|
||||
*
|
||||
* @deprecated
|
||||
* @since 1.9
|
||||
*/
|
||||
if (!empty($_POST['pt_gen'])) {
|
||||
$postPtGen = $_POST['pt_gen'];
|
||||
$existsPtGenInfo = json_decode($row['pt_gen'], true) ?? [];
|
||||
$ptGen = new \Nexus\PTGen\PTGen();
|
||||
if ($postPtGen != $ptGen->getLink($existsPtGenInfo)) {
|
||||
$updateset[] = "pt_gen = " . sqlesc($postPtGen);
|
||||
}
|
||||
} else {
|
||||
$updateset[] = "pt_gen = ''";
|
||||
}
|
||||
//if (!empty($_POST['pt_gen'])) {
|
||||
// $postPtGen = $_POST['pt_gen'];
|
||||
// $existsPtGenInfo = json_decode($row['pt_gen'], true) ?? [];
|
||||
// $ptGen = new \Nexus\PTGen\PTGen();
|
||||
// if ($postPtGen != $ptGen->getLink($existsPtGenInfo)) {
|
||||
// $updateset[] = "pt_gen = " . sqlesc($postPtGen);
|
||||
// }
|
||||
//} else {
|
||||
// $updateset[] = "pt_gen = ''";
|
||||
//}
|
||||
|
||||
$updateset[] = "technical_info = " . sqlesc($_POST['technical_info'] ?? '');
|
||||
//$updateset[] = "technical_info = " . sqlesc($_POST['technical_info'] ?? '');
|
||||
$extraUpdate["media_info"] = $_POST['technical_info'] ?? '';
|
||||
$torrentOperationLog = [];
|
||||
|
||||
|
||||
@@ -70,8 +75,11 @@ if ($nfoaction == "update")
|
||||
if ($nfofile['size'] > 65535)
|
||||
bark($lang_takeedit['std_nfo_too_big']);
|
||||
$nfofilename = $nfofile['tmp_name'];
|
||||
if (@is_uploaded_file($nfofilename) && @filesize($nfofilename) > 0)
|
||||
$updateset[] = "nfo = " . sqlesc(str_replace("\x0d\x0d\x0a", "\x0d\x0a", file_get_contents($nfofilename)));
|
||||
if (@is_uploaded_file($nfofilename) && @filesize($nfofilename) > 0) {
|
||||
// $updateset[] = "nfo = " . sqlesc(str_replace("\x0d\x0d\x0a", "\x0d\x0a", file_get_contents($nfofilename)));
|
||||
$extraUpdate["nfo"] = str_replace("\x0d\x0d\x0a", "\x0d\x0a", file_get_contents($nfofilename));
|
||||
}
|
||||
|
||||
$Cache->delete_value('nfo_block_torrent_id_'.$id);
|
||||
}
|
||||
elseif ($nfoaction == "remove"){
|
||||
@@ -93,7 +101,8 @@ if ($oldcatmode != $newcatmode && !$allowmove)
|
||||
bark($lang_takeedit['std_cannot_move_torrent']);
|
||||
$updateset[] = "anonymous = '" . (!empty($_POST["anonymous"]) ? "yes" : "no") . "'";
|
||||
$updateset[] = "name = " . sqlesc($name);
|
||||
$updateset[] = "descr = " . sqlesc($descr);
|
||||
//$updateset[] = "descr = " . sqlesc($descr);
|
||||
$extraUpdate["descr"] = $descr;
|
||||
$updateset[] = "url = " . sqlesc($url);
|
||||
$updateset[] = "small_descr = " . sqlesc($_POST["small_descr"]);
|
||||
//$updateset[] = "ori_descr = " . sqlesc($descr);
|
||||
@@ -232,7 +241,9 @@ if (user_can('torrent-set-price') && $paidTorrentEnabled) {
|
||||
$sql = "UPDATE torrents SET " . join(",", $updateset) . " WHERE id = $id";
|
||||
do_log("[UPDATE_TORRENT]: $sql");
|
||||
$affectedRows = sql_query($sql) or sqlerr(__FILE__, __LINE__);
|
||||
fire_event("torrent_updated", \App\Models\Torrent::query()->find($id), $torrentOld);
|
||||
$torrentInfo = \App\Models\Torrent::query()->find($id);
|
||||
$torrentInfo->extra()->update($extraUpdate);
|
||||
fire_event("torrent_updated", $torrentInfo, $torrentOld);
|
||||
$dateTimeStringNow = date("Y-m-d H:i:s");
|
||||
|
||||
/**
|
||||
|
||||
+14
-4
@@ -330,8 +330,8 @@ $insert = [
|
||||
'type' => $type,
|
||||
'url' => $url,
|
||||
'small_descr' => $small_descr,
|
||||
'descr' => $descr,
|
||||
'ori_descr' => $descr,
|
||||
// 'descr' => $descr,
|
||||
// 'ori_descr' => $descr,
|
||||
'category' => $catid,
|
||||
'source' => $sourceid,
|
||||
'medium' => $mediumid,
|
||||
@@ -346,12 +346,21 @@ $insert = [
|
||||
'last_action' => $dateTimeStringNow,
|
||||
'nfo' => $nfo,
|
||||
'info_hash' => $infohash,
|
||||
'pt_gen' => $_POST['pt_gen'] ?? '',
|
||||
'technical_info' => $_POST['technical_info'] ?? '',
|
||||
// 'pt_gen' => $_POST['pt_gen'] ?? '',
|
||||
// 'technical_info' => $_POST['technical_info'] ?? '',
|
||||
'cover' => $cover,
|
||||
'pieces_hash' => sha1($info['pieces']),
|
||||
'cache_stamp' => time(),
|
||||
];
|
||||
/**
|
||||
* migrate to extra table and remove pt_gen field
|
||||
* @since 1.9
|
||||
*/
|
||||
$extra = [
|
||||
'descr' => $descr,
|
||||
'media_info' => $_POST['technical_info'] ?? '',
|
||||
'nfo' => $nfo,
|
||||
];
|
||||
if (isset($_POST['hr'][$catmod]) && isset(\App\Models\Torrent::$hrStatus[$_POST['hr'][$catmod]]) && user_can('torrent_hr')) {
|
||||
$insert['hr'] = $_POST['hr'][$catmod];
|
||||
}
|
||||
@@ -432,6 +441,7 @@ if (!empty($tagIdArr)) {
|
||||
foreach ($filelist as $file) {
|
||||
@sql_query("INSERT INTO files (torrent, filename, size) VALUES ($id, ".sqlesc($file[0]).",".$file[1].")");
|
||||
}
|
||||
\App\Models\TorrentExtra::query()->create($extra);
|
||||
|
||||
//===add karma
|
||||
KPS("+",$uploadtorrent_bonus,$CURUSER["id"]);
|
||||
|
||||
+90
-14
@@ -844,17 +844,16 @@ EOD;
|
||||
if ($CURUSER['privacy'] != $privacy) $privacyupdated = 1;
|
||||
|
||||
$user = $CURUSER["id"];
|
||||
$query = sprintf("UPDATE users SET " . implode(",", $updateset) . " WHERE id ='%s'",
|
||||
mysql_real_escape_string($user));
|
||||
$result = sql_query($query);
|
||||
if (!$result)
|
||||
sqlerr(__FILE__,__LINE__);
|
||||
|
||||
if (!empty($_REQUEST['resetauthkey']) && $_REQUEST['resetauthkey'] == 1) {
|
||||
//reset authkey
|
||||
$torrentRep = new \App\Repositories\TorrentRepository();
|
||||
$torrentRep->resetTrackerReportAuthKeySecret($user);
|
||||
}
|
||||
\Nexus\Database\NexusDB::transaction(function () use ($user, $updateset) {
|
||||
$query = sprintf("UPDATE users SET " . implode(",", $updateset) . " WHERE id ='%s'", mysql_real_escape_string($user));
|
||||
sql_query($query);
|
||||
if (!empty($_REQUEST['resetauthkey']) && $_REQUEST['resetauthkey'] == 1) {
|
||||
//reset authkey
|
||||
$torrentRep = new \App\Repositories\TorrentRepository();
|
||||
$torrentRep->resetTrackerReportAuthKeySecret($user);
|
||||
}
|
||||
do_action("usercp_security_update", $_POST);
|
||||
});
|
||||
$to = "usercp.php?action=security&type=saved";
|
||||
if ($changedemail == 1)
|
||||
$to .= "&mail=1";
|
||||
@@ -891,7 +890,8 @@ EOD;
|
||||
print("<input type=\"hidden\" name=\"two_step_secret\" value=\"$two_step_secret\">");
|
||||
print("<input type=\"hidden\" name=\"two_step_code\" value=\"$two_step_code\">");
|
||||
Print("<tr><td class=\"rowhead nowrap\" valign=\"top\" align=\"right\" width=1%>".$lang_usercp['row_security_check']."</td><td valign=\"top\" align=\"left\" width=\"99%\"><input type=password name=oldpassword style=\"width: 200px\"><br /><font class=small>".$lang_usercp['text_security_check_note']."</font></td></tr>\n");
|
||||
submit();
|
||||
do_action("usercp_security_update_confirm", $_POST);
|
||||
submit();
|
||||
print("</table>");
|
||||
stdfoot();
|
||||
die;
|
||||
@@ -900,7 +900,7 @@ EOD;
|
||||
print("<tr><td colspan=2 class=\"heading\" valign=\"top\" align=\"center\"><font color=red>".$lang_usercp['text_saved'].($_GET["mail"] == "1" ? $lang_usercp['std_confirmation_email_sent'] : "")." ".($_GET["passkey"] == "1" ? $lang_usercp['std_passkey_reset'] : "")." ".($_GET["password"] == "1" ? $lang_usercp['std_password_changed'] : "")." ".($_GET["privacy"] == "1" ? $lang_usercp['std_privacy_level_updated'] : "")."</font></td></tr>\n");
|
||||
form ("security");
|
||||
tr_small($lang_usercp['row_reset_passkey'],"<input type=checkbox name=resetpasskey value=1 />".$lang_usercp['checkbox_reset_my_passkey']."<br /><font class=small>".$lang_usercp['text_reset_passkey_note']."</font>", 1);
|
||||
tr_small($lang_usercp['row_reset_authkey'],"<input type=checkbox name=resetauthkey value=1 />".$lang_usercp['checkbox_reset_my_authkey']."<br /><font class=small>".$lang_usercp['text_reset_authkey_note']."</font>", 1);
|
||||
// tr_small($lang_usercp['row_reset_authkey'],"<input type=checkbox name=resetauthkey value=1 />".$lang_usercp['checkbox_reset_my_authkey']."<br /><font class=small>".$lang_usercp['text_reset_authkey_note']."</font>", 1);
|
||||
|
||||
//two step authentication
|
||||
if (!empty($CURUSER['two_step_secret'])) {
|
||||
@@ -926,7 +926,8 @@ EOD;
|
||||
|
||||
if ($disableemailchange != 'no' && $smtptype != 'none') //system-wide setting
|
||||
tr_small($lang_usercp['row_email_address'], "<input type=\"text\" name=\"email\" style=\"width: 200px\" value=\"" . htmlspecialchars($CURUSER["email"]) . "\" /> <br /><font class=small>".$lang_usercp['text_email_address_note']."</font>", 1);
|
||||
tr_small($lang_usercp['row_change_password'], "<input type=\"password\" name=\"chpassword\" style=\"width: 200px\" />", 1);
|
||||
do_action("usercp_security_setting_form");
|
||||
tr_small($lang_usercp['row_change_password'], "<input type=\"password\" name=\"chpassword\" style=\"width: 200px\" />", 1);
|
||||
tr_small($lang_usercp['row_type_password_again'], "<input type=\"password\" name=\"passagain\" style=\"width: 200px\" />", 1);
|
||||
tr_small($lang_usercp['row_privacy_level'], priv("normal", $lang_usercp['radio_normal']) . " " . priv("low", $lang_usercp['radio_low']) . " " . priv("strong", $lang_usercp['radio_strong']), 1);
|
||||
submit();
|
||||
@@ -1119,6 +1120,81 @@ JS;
|
||||
}
|
||||
//end seed box
|
||||
|
||||
//token start
|
||||
$token = '';
|
||||
$tokenLabel = nexus_trans("token.label");
|
||||
$columnName = nexus_trans('label.name');
|
||||
$columnCreatedAt = nexus_trans('label.created_at');
|
||||
$actionCreate = nexus_trans('label.create');
|
||||
//$res = \App\Models\SeedBoxRecord::query()->where('uid', $CURUSER['id'])->where('type', \App\Models\SeedBoxRecord::TYPE_USER)->get();
|
||||
//if ($res->count() > 0)
|
||||
//{
|
||||
// $seedBox .= "<table border='1' cellspacing='0' cellpadding='5' id='seed-box-table'><tr><td class='colhead'>ID</td><td class='colhead'>{$columnOperator}</td><td class='colhead'>{$columnBandwidth}</td><td class='colhead'>{$columnIP}</td><td class='colhead'>{$columnComment}</td><td class='colhead'>{$columnStatus}</td><td class='colhead'></td></tr>";
|
||||
// foreach ($res as $seedBoxRecord)
|
||||
// {
|
||||
// $seedBox .= "<tr>";
|
||||
// $seedBox .= sprintf('<td>%s</td>', $seedBoxRecord->id);
|
||||
// $seedBox .= sprintf('<td>%s</td>', $seedBoxRecord->operator);
|
||||
// $seedBox .= sprintf('<td>%s</td>', $seedBoxRecord->bandwidth ?: '');
|
||||
// $seedBox .= sprintf('<td>%s</td>', $seedBoxRecord->ip ?: sprintf('%s ~ %s', $seedBoxRecord->ip_begin, $seedBoxRecord->ip_end));
|
||||
// $seedBox .= sprintf('<td>%s</td>', $seedBoxRecord->comment);
|
||||
// $seedBox .= sprintf('<td>%s</td>', $seedBoxRecord->statusText);
|
||||
// $seedBox .= sprintf('<td><img style="cursor: pointer" class="staff_delete remove-seed-box-btn" src="pic/trans.gif" alt="D" title="%s" data-id="%s"></td>', $lang_functions['text_delete'], $seedBoxRecord->id);
|
||||
// $seedBox .= "</tr>";
|
||||
// }
|
||||
// $seedBox .= '</table>';
|
||||
//}
|
||||
$token .= sprintf('<div><input type="button" id="add-token-box-btn" value="%s"/></div>', $actionCreate);
|
||||
tr_small($tokenLabel, $token, 1);
|
||||
$tokenFoxForm = <<<FORM
|
||||
<div class="form-box">
|
||||
<form id="token-box-form">
|
||||
<div class="form-control-row">
|
||||
<div class="label">{$columnName}</div>
|
||||
<div class="field"><input type="text" name="params[name]"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
FORM;
|
||||
$tokenBoxJs = <<<JS
|
||||
jQuery('#add-token-box-btn').on('click', function () {
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: "{$tokenLabel} {$actionCreate}",
|
||||
content: `$tokenFoxForm`,
|
||||
btn: ['OK'],
|
||||
btnAlign: 'c',
|
||||
yes: function () {
|
||||
let params = jQuery('#token-box-form').serialize()
|
||||
jQuery.post('ajax.php', params + "&action=addToken", function (response) {
|
||||
console.log(response)
|
||||
if (response.ret != 0) {
|
||||
layer.alert(response.msg)
|
||||
return
|
||||
}
|
||||
window.location.reload()
|
||||
}, 'json')
|
||||
}
|
||||
})
|
||||
});
|
||||
jQuery('#token-box-table').on('click', '.remove-token-box-btn', function () {
|
||||
let params = {action: "removeToken", params: {id: jQuery(this).attr("data-id")}}
|
||||
layer.confirm("{$lang_functions['std_confirm_remove']}", {btnAlign: 'c'}, function (index) {
|
||||
jQuery.post('ajax.php', params, function (response) {
|
||||
console.log(response)
|
||||
if (response.ret != 0) {
|
||||
layer.alert(response.msg)
|
||||
return
|
||||
}
|
||||
window.location.reload()
|
||||
}, 'json')
|
||||
})
|
||||
});
|
||||
JS;
|
||||
\Nexus\Nexus::js($tokenBoxJs, 'footer', false);
|
||||
|
||||
//token end
|
||||
|
||||
if ($forumposts)
|
||||
tr($lang_usercp['row_forum_posts'], $forumposts." [<a href=\"userhistory.php?action=viewposts&id=".$CURUSER['id']."\" title=\"".$lang_usercp['link_view_posts']."\">".$lang_usercp['text_view']."</a>] (".$dayposts.$lang_usercp['text_posts_per_day']."; ".$percentages.$lang_usercp['text_of_total_posts'].")", 1);
|
||||
?>
|
||||
|
||||
+15
-2
@@ -496,9 +496,22 @@ if (user_can('prfmanage') && $user["class"] < get_user_class())
|
||||
|
||||
if (user_can('cruprfmanage'))
|
||||
{
|
||||
$modcomment = htmlspecialchars($user["modcomment"]);
|
||||
$modcomment = \App\Models\UserModifyLog::query()
|
||||
->where("user_id", $user["id"])
|
||||
->orderBy("id", "desc")
|
||||
->limit(20)
|
||||
->get()
|
||||
->implode("content", "\n")
|
||||
;
|
||||
tr($lang_userdetails['row_comment'], "<textarea cols=\"60\" rows=\"6\" name=\"modcomment\">".$modcomment."</textarea>", 1);
|
||||
$bonuscomment = htmlspecialchars($user["bonuscomment"]);
|
||||
$bonuscomment = \App\Models\BonusLogs::query()
|
||||
->where("uid", $user["id"])
|
||||
->orderBy("id", "desc")
|
||||
->limit(20)
|
||||
->get()
|
||||
->map(fn ($item) => sprintf("%s - %s", $item->created_at->format("Y-m-d"), $item->comment))
|
||||
->implode("\n")
|
||||
;
|
||||
tr($lang_userdetails['row_seeding_karma'], "<textarea cols=\"60\" rows=\"6\" name=\"bonuscomment\" readonly=\"readonly\">".$bonuscomment."</textarea>", 1);
|
||||
}
|
||||
$warned = $user["warned"] == "yes";
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ $id = intval($_GET["id"] ?? 0);
|
||||
if (!user_can('viewnfo') || !is_valid_id($id) || $enablenfo_main != 'yes')
|
||||
permissiondenied();
|
||||
|
||||
$r = sql_query("SELECT name,nfo FROM torrents WHERE id=$id") or sqlerr();
|
||||
$r = sql_query("SELECT torrents.name, torrent_extras.nfo FROM torrents left join torrent_extras on torrents.id=torrent_extras.torrent_id WHERE torrents.id=$id") or sqlerr();
|
||||
$a = mysql_fetch_assoc($r) or die($lang_viewnfo['std_puke']);
|
||||
|
||||
//error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
|
||||
|
||||
@@ -24,6 +24,7 @@ return [
|
||||
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_ROLE_WORK_SALARY => 'Role work salary',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => 'Torrent be downloaded',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => 'Receive reward',
|
||||
],
|
||||
'fields' => [
|
||||
'business_type' => 'Business type',
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_ROLE_WORK_SALARY => '工作组工资',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => '种子被下载',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => '收到奖励',
|
||||
],
|
||||
'fields' => [
|
||||
'business_type' => '业务类型',
|
||||
|
||||
@@ -41,6 +41,7 @@ return [
|
||||
'client' => '客户端',
|
||||
'reason' => '原因',
|
||||
'change' => '修改',
|
||||
'create' => '创建',
|
||||
'setting' => [
|
||||
'nav_text' => '设置',
|
||||
'backup' => [
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"label" => "访问令牌",
|
||||
];
|
||||
@@ -24,6 +24,7 @@ return [
|
||||
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_ROLE_WORK_SALARY => '工作組工資',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => '種子被下載',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => '收到獎勵',
|
||||
],
|
||||
'fields' => [
|
||||
'business_type' => '業務類型',
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<x-filament-panels::page>
|
||||
@livewire('install-plugin-modal')
|
||||
</x-filament-panels::page>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<div>
|
||||
<h3 class="text-danger-700 pb-6">注意: 执行此操作会先将站点启用维护模式, 执行完毕后会关闭维护模式!</h3>
|
||||
<div class="code">
|
||||
<textarea readonly style="width: 100%;min-height: 200px"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,39 @@
|
||||
<div>
|
||||
<h3 class="text-danger-700 pb-6">注意: 执行此操作会先将站点启用维护模式, 执行完毕后会关闭维护模式!</h3>
|
||||
<textarea id="output" rows="10" class="w-full border rounded p-2" readonly></textarea>
|
||||
<div class="mt-4">
|
||||
<button id="btn-run" class="fi-btn">点击执行</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('btn-run').addEventListener('click', () => {
|
||||
document.getElementById('output').textContent = ''; // 清空之前的输出
|
||||
const es = new EventSource('/admin/sse?plugin_id=tgbot&action=install_plugin');
|
||||
const textarea = document.getElementById("output")
|
||||
es.onmessage = (event) => {
|
||||
console.log("onmessage: ", event.data)
|
||||
if (event.data === "close") {
|
||||
console.log("close es ...")
|
||||
es.close();
|
||||
return
|
||||
}
|
||||
textarea.textContent += event.data + "\n"
|
||||
textarea.scrollTop = textarea.scrollHeight
|
||||
}
|
||||
es.onerror = (event) => {
|
||||
console.log("onerror: ", event)
|
||||
switch (es.readyState) {
|
||||
case EventSource.CONNECTING:
|
||||
console.log("Reconnecting...");
|
||||
break;
|
||||
case EventSource.CLOSED:
|
||||
console.log("Connection closed.");
|
||||
break;
|
||||
}
|
||||
es.close()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get("/sse", [\App\Http\Controllers\ServerSendEventController::class, "sse"]);
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('announce', [\App\Http\Controllers\TrackerController::class, 'announce']);
|
||||
Route::get('scrape', [\App\Http\Controllers\TrackerController::class, 'scrape']);
|
||||
Reference in New Issue
Block a user