diff --git a/.docker/php/Dockerfile b/.docker/php/Dockerfile
index 80fead06..bed3d88b 100644
--- a/.docker/php/Dockerfile
+++ b/.docker/php/Dockerfile
@@ -71,6 +71,9 @@ RUN sed -i \
# 配置 PHP 错误日志输出到 stderr
RUN echo "error_log = /dev/stderr" >> /usr/local/etc/php/conf.d/error-logging.ini
+# 上传配置
+RUN echo "upload_max_filesize = 10M" >> /usr/local/etc/php/conf.d/upload.ini
+RUN echo "post_max_size = 10M" >> /usr/local/etc/php/conf.d/upload.ini
# 安装 Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
diff --git a/app/Console/Commands/HitAndRunUpdateStatus.php b/app/Console/Commands/HitAndRunUpdateStatus.php
index 7ca934c0..4c4162c0 100644
--- a/app/Console/Commands/HitAndRunUpdateStatus.php
+++ b/app/Console/Commands/HitAndRunUpdateStatus.php
@@ -42,10 +42,10 @@ class HitAndRunUpdateStatus extends Command
$torrentId = $this->option('torrent_id');
$ignoreTime = $this->option('ignore_time');
$rep = new HitAndRunRepository();
- $result = $rep->cronjobUpdateStatus($uid, $torrentId, $ignoreTime);
+ $rep->cronjobUpdateStatus($uid, $torrentId, $ignoreTime);
$log = sprintf(
- '[%s], %s, uid: %s, torrentId: %s, ignoreTime: %s, result: %s',
- nexus()->getRequestId(), __METHOD__, $uid, $torrentId, $ignoreTime, var_export($result, true)
+ '[%s], %s, uid: %s, torrentId: %s, ignoreTime: %s',
+ nexus()->getRequestId(), __METHOD__, $uid, $torrentId, $ignoreTime
);
$this->info($log);
do_log($log);
diff --git a/app/Console/Commands/Test.php b/app/Console/Commands/Test.php
index 4be7874f..ce65cece 100644
--- a/app/Console/Commands/Test.php
+++ b/app/Console/Commands/Test.php
@@ -14,6 +14,7 @@ use App\Models\TorrentExtra;
use App\Models\User;
use App\Repositories\ClaimRepository;
use App\Repositories\ExamRepository;
+use App\Repositories\RequireSeedTorrentRepository;
use App\Repositories\SeedBoxRepository;
use App\Repositories\TokenRepository;
use App\Repositories\UploadRepository;
@@ -30,6 +31,8 @@ use NexusPlugin\StickyPromotion\Models\StickyPromotion;
use NexusPlugin\StickyPromotion\Models\StickyPromotionParticipator;
use NexusPlugin\Work\Models\RoleWork;
use NexusPlugin\Work\WorkRepository;
+use Rhilip\Bencode\Bencode;
+use Rhilip\Bencode\TorrentFile;
use Stichoza\GoogleTranslate\GoogleTranslate;
class Test extends Command
@@ -65,22 +68,9 @@ class Test extends Command
*/
public function handle()
{
-// $failedJob = DB::table('failed_jobs')->find(569);
-//
-// $payload = json_decode($failedJob->payload, true);
-// dd($payload);
-//
-// $base64 = $payload['data']['command'];
-// $job = unserialize($base64);
-//
-// dd($job);
-
-// UpdateUserDownloadPrivilege::dispatch(1, "yes", "test_key");
-// $res = unserialize("O:36:\"App\\Jobs\\UpdateUserDownloadPrivilege\":3:{s:6:\"userId\";i:1;s:6:\"status\";s:3:\"yes\";s:9:\"reasonKey\";s:8:\"test_key\";}");
-// $res = unserialize("O:36:\"App\\Jobs\\UpdateUserDownloadPrivilege\":3:{s:6:\"userId\";i:1;s:6:\"status\";s:3:\"yes\";s:9:\"reasonKey\";s:8:\"test_key\";}");
-// dd($res);
- $r = TokenRepository::listUserTokenPermissionAllowed();
- dd($r);
+ $rep = new RequireSeedTorrentRepository();
+// $rep->doRemove(Torrent::query()->whereIn('id', [58])->get());
+ $rep->autoAddToListCronjob();
}
}
diff --git a/app/Filament/Resources/System/PluginStoreResource.php b/app/Filament/Resources/System/PluginStoreResource.php
index 011284e8..18ccaa59 100644
--- a/app/Filament/Resources/System/PluginStoreResource.php
+++ b/app/Filament/Resources/System/PluginStoreResource.php
@@ -137,7 +137,7 @@ class PluginStoreResource extends Resource
{
$result = [];
$result[] = nexus_trans("plugin.labels.config_plugin_address");
- $result[] = sprintf("composer config repositories.%s git %s", $record->plugin_id, $record->remote_url);
+ $result[] = sprintf("composer config repositories.%s vcs %s", $record->plugin_id, $record->remote_url);
$result[] = "
" . nexus_trans("plugin.labels.download_specific_version");
$result[] = sprintf("composer require %s:%s", $record->package_name, $record->version);
$result[] = "
" . nexus_trans("plugin.labels.execute_install");
diff --git a/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php b/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php
index 29c887a6..374e778e 100644
--- a/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php
+++ b/app/Filament/Resources/System/SettingResource/Pages/EditSetting.php
@@ -8,6 +8,8 @@ use App\Filament\Resources\System\SettingResource;
use App\Models\HitAndRun;
use App\Models\SearchBox;
use App\Models\Setting;
+use App\Models\Tag;
+use App\Models\Torrent;
use App\Models\User;
use App\Repositories\TokenRepository;
use App\Repositories\ToolRepository;
@@ -112,6 +114,11 @@ class EditSetting extends Page implements Forms\Contracts\HasForms
->schema($this->getHitAndRunSchema())
->columns(2)
;
+// $tabs[] = Forms\Components\Tabs\Tab::make(__('label.setting.require_seed_section.tab_header'))
+// ->id('require_seed_section')
+// ->schema($this->getRequireSeedSectionSchema())
+// ->columns(2)
+// ;
$tabs[] = Forms\Components\Tabs\Tab::make(__('label.setting.backup.tab_header'))
->id('backup')
@@ -220,6 +227,56 @@ class EditSetting extends Page implements Forms\Contracts\HasForms
return apply_filter("hit_and_run_setting_schema", $default);
}
+ private function getRequireSeedSectionSchema(): array
+ {
+ return [
+ Forms\Components\Radio::make('require_seed_section.enabled')->options(self::$yesOrNo)->label(__('label.enabled'))->helperText(__('label.setting.require_seed_section.enabled_help')),
+ Forms\Components\TextInput::make('require_seed_section.menu_title')->label(__('label.setting.require_seed_section.menu_title'))->helperText(__('label.setting.require_seed_section.menu_title_help')),
+ Forms\Components\TextInput::make('require_seed_section.seeder_lte')->label(__('label.setting.require_seed_section.seeder_lte'))->helperText(__('label.setting.require_seed_section.seeder_lte_help'))->integer(),
+ Forms\Components\TextInput::make('require_seed_section.seeder_gte')->label(__('label.setting.require_seed_section.seeder_gte'))->helperText(__('label.setting.require_seed_section.seeder_gte_help'))->integer(),
+ Forms\Components\CheckboxList::make('require_seed_section.require_tags')->label(__('label.setting.require_seed_section.require_tags'))->helperText(__('label.setting.require_seed_section.require_tags_help'))->options(Tag::query()->pluck('name', 'id'))->columns(4),
+ Forms\Components\Select::make('require_seed_section.promotion_state')->label(__('label.setting.require_seed_section.promotion_state'))->helperText(__('label.setting.require_seed_section.promotion_state_help'))->options(Torrent::listPromotionTypes(true)),
+ Forms\Components\TextInput::make('require_seed_section.daily_seed_time_min')->label(__('label.setting.require_seed_section.daily_seed_time_min'))->helperText(__('label.setting.require_seed_section.daily_seed_time_min_help'))->integer(),
+ Forms\Components\TextInput::make('require_seed_section.torrent_count_max')->label(__('label.setting.require_seed_section.torrent_count_max'))->helperText(__('label.setting.require_seed_section.torrent_count_max_help'))->integer(),
+ Forms\Components\Repeater::make('require_seed_section.bonus_reward')
+ ->label(__('label.setting.require_seed_section.bonus_reward'))
+ ->helperText(__('label.setting.require_seed_section.bonus_reward_help'))
+ ->schema([
+ Forms\Components\TextInput::make('seeders')
+ ->label(__('label.setting.require_seed_section.seeders'))
+ ->required()
+ ->integer()
+ ->columnSpan(2)
+ ,
+ Forms\Components\Repeater::make('seed_time_reward')
+ ->label(__('label.setting.require_seed_section.seed_time_reward'))
+ ->schema([
+ Forms\Components\TextInput::make('begin')->label(__('label.setting.require_seed_section.seed_time_reward_begin'))->helperText(__('label.setting.require_seed_section.seed_time_reward_begin_help')),
+ Forms\Components\TextInput::make('end')->label(__('label.setting.require_seed_section.seed_time_reward_end'))->helperText(__('label.setting.require_seed_section.seed_time_reward_end_help')),
+ Forms\Components\TextInput::make('window')->label(__('label.setting.require_seed_section.seed_time_reward_window'))->helperText(__('label.setting.require_seed_section.seed_time_reward_window_help')),
+ Forms\Components\TextInput::make('reward')->label(__('label.setting.require_seed_section.seed_time_reward_reward'))->helperText(__('label.setting.require_seed_section.seed_time_reward_reward_help')),
+ ])
+ ->columns(4)
+ ->columnSpan(5)
+ ,
+ Forms\Components\Repeater::make('data_traffic_reward')
+ ->label(__('label.setting.require_seed_section.data_traffic_reward'))
+ ->schema([
+ Forms\Components\TextInput::make('begin')->label(__('label.setting.require_seed_section.data_traffic_reward_begin'))->helperText(__('label.setting.require_seed_section.data_traffic_reward_begin_help')),
+ Forms\Components\TextInput::make('end')->label(__('label.setting.require_seed_section.data_traffic_reward_end'))->helperText(__('label.setting.require_seed_section.data_traffic_reward_end_help')),
+ Forms\Components\TextInput::make('window')->label(__('label.setting.require_seed_section.data_traffic_reward_window'))->helperText(__('label.setting.require_seed_section.data_traffic_reward_window_help')),
+ Forms\Components\TextInput::make('reward')->label(__('label.setting.require_seed_section.data_traffic_reward_reward'))->helperText(__('label.setting.require_seed_section.data_traffic_reward_reward_help')),
+ ])
+ ->columns(4)
+ ->columnSpan(5)
+ ])
+ ->columns(12)
+ ->columnSpanFull()
+ ->defaultItems(3)
+ ->reorderable(false)
+ ];
+ }
+
private function getTabMeilisearchSchema($id): array
{
$schema = [];
diff --git a/app/Filament/Resources/Torrent/AnnounceLogResource.php b/app/Filament/Resources/Torrent/AnnounceLogResource.php
index d1f55352..7acb3716 100644
--- a/app/Filament/Resources/Torrent/AnnounceLogResource.php
+++ b/app/Filament/Resources/Torrent/AnnounceLogResource.php
@@ -169,6 +169,7 @@ class AnnounceLogResource extends Resource
Forms\Components\TextInput::make('user_id')
->label(__('announce-log.user_id'))
->numeric()
+ ->minValue(1)
,
])
,
@@ -177,6 +178,7 @@ class AnnounceLogResource extends Resource
Forms\Components\TextInput::make('torrent_id')
->label(__('announce-log.torrent_id'))
->numeric()
+ ->minValue(1)
,
])
,
diff --git a/app/Filament/Resources/User/BonusLogResource.php b/app/Filament/Resources/User/BonusLogResource.php
index 20517460..05f13b51 100644
--- a/app/Filament/Resources/User/BonusLogResource.php
+++ b/app/Filament/Resources/User/BonusLogResource.php
@@ -55,7 +55,7 @@ class BonusLogResource extends Resource
,
Tables\Columns\TextColumn::make('old_total_value')
->label(__('bonus-log.fields.old_total_value'))
- ->formatStateUsing(fn ($state) => number_format($state))
+ ->formatStateUsing(fn ($state) => $state >= 0 ? number_format($state) : '-')
,
Tables\Columns\TextColumn::make('value')
->formatStateUsing(fn ($record) => $record->old_total_value > $record->new_total_value ? "-" . number_format($record->value) : "+" . number_format($record->value))
@@ -63,7 +63,7 @@ class BonusLogResource extends Resource
,
Tables\Columns\TextColumn::make('new_total_value')
->label(__('bonus-log.fields.new_total_value'))
- ->formatStateUsing(fn ($state) => number_format($state))
+ ->formatStateUsing(fn ($state) => $state >= 0 ? number_format($state) : '-')
,
Tables\Columns\TextColumn::make('comment')
->label(__('label.comment'))
diff --git a/app/Jobs/CalculateUserSeedBonus.php b/app/Jobs/CalculateUserSeedBonus.php
index 99602f58..992135d6 100644
--- a/app/Jobs/CalculateUserSeedBonus.php
+++ b/app/Jobs/CalculateUserSeedBonus.php
@@ -2,6 +2,7 @@
namespace App\Jobs;
+use App\Models\BonusLogs;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Bus\Queueable;
@@ -86,22 +87,33 @@ class CalculateUserSeedBonus implements ShouldQueue
}
$sql = sprintf("select %s from users where id in (%s)", implode(',', User::$commonFields), $idStr);
$results = NexusDB::select($sql);
+ if (empty($results)) {
+ do_log("$logPrefix, no data from idStr: $idStr", "error");
+ return;
+ }
$logFile = getLogFile("seed-bonus-points");
do_log("$logPrefix, [GET_UID_REAL], count: " . count($results) . ", logFile: $logFile");
$fd = fopen($logFile, 'a');
$seedPointsUpdates = $seedPointsPerHourUpdates = $seedBonusUpdates = [];
$seedingTorrentCountUpdates = $seedingTorrentSizeUpdates = [];
$logStr = "";
+ $bonusLogInsert = [];
foreach ($results as $userInfo)
{
$uid = $userInfo['id'];
+ $donorAddition = $officialAddition = $haremAddition = $medalAddition = 0;
$isDonor = is_donor($userInfo);
$seedBonusResult = calculate_seed_bonus($uid);
$bonusLog = "[CLEANUP_CLI_CALCULATE_SEED_BONUS_HANDLE_USER], user: $uid, seedBonusResult: " . nexus_json_encode($seedBonusResult);
- $all_bonus = $seedBonusResult['seed_bonus'];
+ $all_bonus = $basicBonus = $seedBonusResult['seed_bonus'];
$bonusLog .= ", all_bonus: $all_bonus";
+ if ($all_bonus == 0) {
+ do_log("$bonusLog, all_bonus is zero, skip");
+ continue;
+ }
if ($isDonor && $donortimes_bonus != 0) {
- $all_bonus = $all_bonus * $donortimes_bonus;
+ $donorAddition = $basicBonus * $donortimes_bonus;
+ $all_bonus += $donorAddition;
$bonusLog .= ", isDonor, donortimes_bonus: $donortimes_bonus, all_bonus: $all_bonus";
}
if ($officialAdditionFactor > 0) {
@@ -133,6 +145,14 @@ class CalculateUserSeedBonus implements ShouldQueue
$seedingTorrentCountUpdates[] = sprintf("when %d then %f", $uid, $seedBonusResult['count']);
$seedingTorrentSizeUpdates[] = sprintf("when %d then %f", $uid, $seedBonusResult['size']);
$seedBonusUpdates[] = sprintf("when %d then seedbonus + %f", $uid, $all_bonus);
+ //here before/after not correct, don't record it, fill with -1
+ $this->appendBonusLogInsert($bonusLogInsert, $uid, [
+ BonusLogs::BUSINESS_TYPE_SEEDING_BASIC => $basicBonus,
+ BonusLogs::BUSINESS_TYPE_SEEDING_DONOR_ADDITION => $donorAddition,
+ BonusLogs::BUSINESS_TYPE_SEEDING_OFFICIAL_ADDITION => $officialAddition,
+ BonusLogs::BUSINESS_TYPE_SEEDING_HAREM_ADDITION => $haremAddition,
+ BonusLogs::BUSINESS_TYPE_SEEDING_MEDAL_ADDITION => $medalAddition,
+ ]);
if ($fd) {
$log = sprintf(
'%s|%s|%s|%s|%s|%s|%s|%s',
@@ -155,6 +175,9 @@ class CalculateUserSeedBonus implements ShouldQueue
if ($delIdRedisKey) {
NexusDB::cache_del($this->idRedisKey);
}
+ if (!empty($bonusLogInsert)) {
+ BonusLogs::query()->insert($bonusLogInsert);
+ }
fwrite($fd, $logStr);
$costTime = time() - $beginTimestamp;
do_log(sprintf(
@@ -174,4 +197,22 @@ class CalculateUserSeedBonus implements ShouldQueue
{
do_log("failed: " . $exception->getMessage() . $exception->getTraceAsString(), 'error');
}
+
+ private function appendBonusLogInsert(array &$bonusLogInsert, int $userId, array $typeValues): void
+ {
+ foreach ($typeValues as $type => $value) {
+ if ($value > 0) {
+ $bonusLogInsert[] = [
+ 'business_type' => $type,
+ 'uid' => $userId,
+ 'old_total_value' => -1,
+ 'value' => $value,
+ 'new_total_value' => -1,
+ 'comment' => BonusLogs::$businessTypes[$type]['text'],
+ 'created_at' => now()->toDateTimeString(),
+ 'updated_at' => now()->toDateTimeString(),
+ ];
+ }
+ }
+ }
}
diff --git a/app/Jobs/FireEvent.php b/app/Jobs/FireEvent.php
index 2503abc2..85e7599e 100644
--- a/app/Jobs/FireEvent.php
+++ b/app/Jobs/FireEvent.php
@@ -47,7 +47,7 @@ class FireEvent implements ShouldQueue
}
$result = call_user_func_array([$eventName, "dispatch"], $params);
$log .= ", success call dispatch, result: " . var_export($result, true);
- publish_model_event($name, $model->id);
+ publish_model_event($name, $model->id, $model->toJson());
} else {
$log .= ", no event match this name";
}
diff --git a/app/Jobs/UpdateTorrentSeedersEtc.php b/app/Jobs/UpdateTorrentSeedersEtc.php
index a080a78d..8cabe42d 100644
--- a/app/Jobs/UpdateTorrentSeedersEtc.php
+++ b/app/Jobs/UpdateTorrentSeedersEtc.php
@@ -89,6 +89,10 @@ class UpdateTorrentSeedersEtc implements ShouldQueue
->whereRaw("torrent in ($idStr)")
->groupBy(['torrent', 'seeder'])
->get();
+ if ($res->isEmpty()) {
+ do_log("$logPrefix, no data from idStr: $idStr", "error");
+ return;
+ }
foreach ($res as $row) {
if ($row->seeder == "yes")
$key = "seeders";
diff --git a/app/Jobs/UpdateUserSeedingLeechingTime.php b/app/Jobs/UpdateUserSeedingLeechingTime.php
index c9c90094..f53dfd01 100644
--- a/app/Jobs/UpdateUserSeedingLeechingTime.php
+++ b/app/Jobs/UpdateUserSeedingLeechingTime.php
@@ -87,6 +87,10 @@ class UpdateUserSeedingLeechingTime implements ShouldQueue
->whereRaw("userid in ($idStr)")
->groupBy("userid")
->get();
+ if ($res->isEmpty()) {
+ do_log("$logPrefix, no data from idStr: $idStr", "error");
+ return;
+ }
$seedtimeUpdates = $leechTimeUpdates = [];
$nowStr = now()->toDateTimeString();
$count = 0;
diff --git a/app/Models/BonusLogs.php b/app/Models/BonusLogs.php
index 149e05cf..4150e138 100644
--- a/app/Models/BonusLogs.php
+++ b/app/Models/BonusLogs.php
@@ -47,6 +47,12 @@ class BonusLogs extends NexusModel
const BUSINESS_TYPE_RECEIVE_GIFT = 1003;
const BUSINESS_TYPE_UPLOAD_TORRENT = 1004;
+ const BUSINESS_TYPE_SEEDING_BASIC = 10000;
+ const BUSINESS_TYPE_SEEDING_DONOR_ADDITION = 10001;
+ const BUSINESS_TYPE_SEEDING_OFFICIAL_ADDITION = 10002;
+ const BUSINESS_TYPE_SEEDING_HAREM_ADDITION = 10003;
+ const BUSINESS_TYPE_SEEDING_MEDAL_ADDITION = 10004;
+
public static array $businessTypes = [
self::BUSINESS_TYPE_CANCEL_HIT_AND_RUN => ['text' => 'Cancel H&R'],
self::BUSINESS_TYPE_BUY_MEDAL => ['text' => 'Buy medal'],
@@ -75,6 +81,11 @@ class BonusLogs extends NexusModel
self::BUSINESS_TYPE_RECEIVE_REWARD => ['text' => 'Receive reward'],
self::BUSINESS_TYPE_RECEIVE_GIFT => ['text' => 'Receive gift'],
self::BUSINESS_TYPE_UPLOAD_TORRENT => ['text' => 'Upload torrent'],
+ self::BUSINESS_TYPE_SEEDING_BASIC => ['text' => 'Seeding basic'],
+ self::BUSINESS_TYPE_SEEDING_DONOR_ADDITION => ['text' => 'Seeding donor addition'],
+ self::BUSINESS_TYPE_SEEDING_OFFICIAL_ADDITION => ['text' => 'Seeding official addition'],
+ self::BUSINESS_TYPE_SEEDING_HAREM_ADDITION => ['text' => 'Seeding harem addition'],
+ self::BUSINESS_TYPE_SEEDING_MEDAL_ADDITION => ['text' => 'Seeding medal addition'],
];
public function getBusinessTypeTextAttribute()
diff --git a/app/Models/RequireSeedTorrent.php b/app/Models/RequireSeedTorrent.php
new file mode 100644
index 00000000..b4e7b2cc
--- /dev/null
+++ b/app/Models/RequireSeedTorrent.php
@@ -0,0 +1,12 @@
+ ['text' => 'DOS-vy'],
diff --git a/app/Models/TrackerUrl.php b/app/Models/TrackerUrl.php
index cfc41e2d..3edc6eec 100644
--- a/app/Models/TrackerUrl.php
+++ b/app/Models/TrackerUrl.php
@@ -19,22 +19,7 @@ class TrackerUrl extends NexusModel
if ($model->is_default == 1) {
self::query()->where("id", "!=", $model->id)->update(["is_default" => 0]);
}
- //添加 id 与 URL 映射
- $redis = NexusDB::redis();
- $redis->del(self::TRACKER_URL_CACHE_KEY);
- $list = self::listAll();
- $first = $list->first();
- $hasDefault = false;
- foreach ($list as $item) {
- $redis->hset(self::TRACKER_URL_CACHE_KEY, $item->id, $item->url);
- if ($item->is_default == 1) {
- $hasDefault = true;
- $redis->set(self::TRACKER_URL_DEFAULT_CACHE_KEY, $item->url);
- }
- }
- if (!$hasDefault && $first) {
- $redis->set(self::TRACKER_URL_DEFAULT_CACHE_KEY, $first->url);
- }
+ self::saveUrlCache();
});
static::saving(function (TrackerUrl $model) {
if ($model->is_default == 1) {
@@ -43,6 +28,26 @@ class TrackerUrl extends NexusModel
});
}
+ public static function saveUrlCache(): void
+ {
+ //添加 id 与 URL 映射
+ $redis = NexusDB::redis();
+ $redis->del(self::TRACKER_URL_CACHE_KEY);
+ $list = self::listAll();
+ $first = $list->first();
+ $hasDefault = false;
+ foreach ($list as $item) {
+ $redis->hset(self::TRACKER_URL_CACHE_KEY, $item->id, $item->url);
+ if ($item->is_default == 1) {
+ $hasDefault = true;
+ $redis->set(self::TRACKER_URL_DEFAULT_CACHE_KEY, $item->url);
+ }
+ }
+ if (!$hasDefault && $first) {
+ $redis->set(self::TRACKER_URL_DEFAULT_CACHE_KEY, $first->url);
+ }
+ }
+
public static function listAll()
{
return self::query()
diff --git a/app/Models/User.php b/app/Models/User.php
index ec648303..f311115e 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -66,7 +66,7 @@ class User extends Authenticatable implements FilamentUser, HasName
self::CLASS_EXTREME_USER => ['text' => 'Extreme User', 'min_seed_points' => 600000],
self::CLASS_ULTIMATE_USER => ['text' => 'Ultimate User', 'min_seed_points' => 800000],
self::CLASS_NEXUS_MASTER => ['text' => 'Nexus Master', 'min_seed_points' => 1000000],
- self::CLASS_VIP => ['text' => 'Vip'],
+ self::CLASS_VIP => ['text' => 'VIP'],
self::CLASS_RETIREE => ['text' => 'Retiree'],
self::CLASS_UPLOADER => ['text' => 'Uploader'],
self::CLASS_MODERATOR => ['text' => 'Moderator'],
@@ -105,6 +105,13 @@ class User extends Authenticatable implements FilamentUser, HasName
public static array $notificationOptions = ['topic_reply', 'hr_reached'];
+ private const USER_ENABLE_LATELY = "user_enable_lately:%s";
+
+ public static function getUserEnableLatelyCacheKey(int $userId): string
+ {
+ return sprintf(self::USER_ENABLE_LATELY, $userId);
+ }
+
public function getClassTextAttribute(): string
{
return self::getClassText($this->class);
@@ -115,12 +122,12 @@ class User extends Authenticatable implements FilamentUser, HasName
if (!is_numeric($class)|| !isset(self::$classes[$class])) {
return '';
}
+ $classText = self::$classes[$class]['text'];
if ($class >= self::CLASS_VIP) {
- $classText = nexus_trans('user.class_names.' . $class);
+ $alias = nexus_trans('user.class_names.' . $class);
} else {
- $classText = self::$classes[$class]['text'];
+ $alias = Setting::get("account.{$class}_alias");
}
- $alias = Setting::get("account.{$class}_alias");
if (!empty($alias)) {
$classText .= "({$alias})";
}
diff --git a/app/Models/UserRequireSeedTorrent.php b/app/Models/UserRequireSeedTorrent.php
new file mode 100644
index 00000000..ab921b9f
--- /dev/null
+++ b/app/Models/UserRequireSeedTorrent.php
@@ -0,0 +1,10 @@
+ 0) {
-// $targetLeechTime = $row->snatch->leechtime;
- $targetLeechTime = $row->leech_time_no_seeder;//使用自身记录的值
+ //use diff, other index should do also, update later @todo
+ $targetLeechTime = $row->snatch->leech_time_no_seeder - $row->leech_time_no_seeder_begin;
$requireLeechTime = bcmul($setting['leech_time_minimum'], 3600);
do_log("$currentLog, targetLeechTime: $targetLeechTime, requireLeechTime: $requireLeechTime");
if ($targetLeechTime >= $requireLeechTime) {
@@ -258,12 +258,13 @@ class HitAndRunRepository extends BaseRepository
private function geReachedMessage(HitAndRun $hitAndRun): array
{
+ $snatched = $hitAndRun->snatch;
return [
'receiver' => $hitAndRun->uid,
'added' => Carbon::now()->toDateTimeString(),
'subject' => nexus_trans('hr.reached_message_subject', ['hit_and_run_id' => $hitAndRun->id], $hitAndRun->user->locale),
'msg' => nexus_trans('hr.reached_message_content', [
- 'completed_at' => format_datetime($hitAndRun->snatch->completedat),
+ 'completed_at' => format_datetime($snatched->completedat ?: $snatched->startdat),
'torrent_id' => $hitAndRun->torrent_id,
'torrent_name' => $hitAndRun->torrent->name,
], $hitAndRun->user->locale),
@@ -305,7 +306,7 @@ class HitAndRunRepository extends BaseRepository
do_log(__METHOD__);
$comment = nexus_trans('hr.reached_by_leech_time_comment', [
'now' => Carbon::now()->toDateTimeString(),
- 'leech_time' => bcdiv($hitAndRun->snatch->leechtime, 3600, 1),
+ 'leech_time' => bcdiv($hitAndRun->snatch->leech_time_no_seeder - $hitAndRun->leech_time_no_seeder_begin, 3600, 1),
'leech_time_minimum' => $setting['leech_time_minimum'],
], $hitAndRun->user->locale);
$update = [
diff --git a/app/Repositories/RequireSeedTorrentRepository.php b/app/Repositories/RequireSeedTorrentRepository.php
new file mode 100644
index 00000000..f51539eb
--- /dev/null
+++ b/app/Repositories/RequireSeedTorrentRepository.php
@@ -0,0 +1,169 @@
+count();
+ if ($countNow >= $countMaxAllowed) {
+ do_log("$logPrefix, max allowed $countMaxAllowed reached");
+ return;
+ }
+ $count = $countMaxAllowed - $countNow;
+ $seederMax = Setting::getRequireSeedSectionSeederLte();
+ $seederMin = Setting::getRequireSeedSectionSeederGte();
+ $logPrefix .= ", countMaxAllowed: $countMaxAllowed, countNow: $countNow, count: $count, seederMax: $seederMax, seederMin: $seederMin";
+ $query = Torrent::query()->where('banned', 'no')
+ ->where('seeders', '<=', $seederMax)
+ ->where('seeders', '>=', $seederMin)
+ ;
+ $tags = Setting::getRequireSeedSectionTags();
+ if (!empty($tags)) {
+ $logPrefix .= ", tags: " . implode(',', $tags);
+ $query->whereHas('torrent_tags', function ($query) use ($tags) {
+ $query->whereIn('tag_id', $tags);
+ });
+ }
+ $list = $query->leftJoin("require_seed_torrents", "torrents.id", "=", "require_seed_torrents.torrent_id")
+ ->whereNull("require_seed_torrents.id")
+ ->orderBy('torrents.seeders', 'asc')
+ ->orderBy('torrents.times_completed', 'desc')
+ ->orderBy('torrents.hits', 'desc')
+ ->limit($count)
+ ->get(['torrents.id'])
+ ;
+ $data = [];
+ $nowStr = now()->toDateTimeString();
+ $redis = NexusDB::redis();
+ $cacheKey = self::getTorrentCacheKey();
+ foreach ($list as $item) {
+ $data[] = [
+ 'torrent_id' => $item->id,
+ 'created_at' => $nowStr,
+ 'updated_at' => $nowStr,
+ ];
+ $redis->hset($cacheKey, $item->id, $nowStr);
+ }
+ RequireSeedTorrent::query()->insert($data);
+ do_log("$logPrefix, success inserted: " . count($data));
+ }
+
+ public function autoRemoveFromListCronjob(): void
+ {
+ $idArr = RequireSeedTorrent::query()->pluck('torrent_id')->toArray();
+ if (empty($idArr)) {
+ do_log("no data to remove");
+ return;
+ }
+ $seederMax = Setting::getRequireSeedSectionSeederLte();
+ $seederMin = Setting::getRequireSeedSectionSeederGte();
+ $torrents = Torrent::query()->whereIn('id', $idArr)
+ ->where('seeders', '<', $seederMin)
+ ->get(['id'])
+ ;
+ if (!empty($torrents)) {
+ $this->doRemove($torrents);
+ do_log(sprintf("remove %s seeders < %s", count($torrents), $seederMin));
+ }
+ $torrents = Torrent::query()->whereIn('id', $idArr)
+ ->where('seeders', '>', $seederMax)
+ ->get(['id'])
+ ;
+ if (!empty($torrents)) {
+ $this->doRemove($torrents);
+ do_log(sprintf("remove %s seeders > %s", count($torrents), $seederMax));
+ }
+ }
+
+ public function doRemove(Collection $torrents): void
+ {
+ $idArr = [];
+ $redis = NexusDB::redis();
+ $promotionState = Setting::getRequireSeedSectionPromotionState();
+ $ttlInSeconds = 24 * 3600;
+ foreach ($torrents as $torrent) {
+ $idArr[] = $torrent->id;
+ $promotionStateCacheKey = sprintf("%s:%s", Torrent::REQUIRE_SEED_SECTION_PROMOTION_STATE_CACHE_KEY, $torrent->id);
+ $redis->setex($promotionStateCacheKey, $ttlInSeconds, $promotionState);
+ //remove torrent from list
+ $redis->hDel(self::getTorrentCacheKey(), $torrent->id);
+ //remove all users under torrent
+ $redis->del(self::getTorrentUserCacheKey($torrent->id));
+ }
+ RequireSeedTorrent::query()->whereIn('torrent_id', $idArr)->delete();
+ UserRequireSeedTorrent::query()->whereIn('torrent_id', $idArr)->delete();
+ do_log("success removed " . count($idArr));
+ }
+
+ private static function getTorrentCacheKey(): string
+ {
+ return Torrent::REQUIRE_SEED_SECTION_TORRENT_ON_LIST_CACHE_KEY;
+ }
+
+ private static function getTorrentUserCacheKey($torrentId): string
+ {
+ return sprintf("%s:%s", Torrent::REQUIRE_SEED_SECTION_TORRENT_USER_CACHE_KEY, $torrentId);
+ }
+
+ public static function shouldRecordUser(\Redis $redis, $userId, $torrentId): bool
+ {
+ $logPrefix = "userId: $userId, torrentId: $torrentId";
+ //check enabled or not
+ if (!Setting::getIsRequireSeedSectionEnabled()) {
+ do_log("$logPrefix, not enabled", 'debug');
+ return false;
+ }
+ //first, torrent on list
+ $onListCacheKey = self::getTorrentCacheKey();
+ if (!$redis->hExists($onListCacheKey, $torrentId)) {
+ do_log("$logPrefix, torrent not on list: $onListCacheKey", 'debug');
+ return false;
+ }
+ //second, torrent user not exists
+ $torrentUserCacheKey = self::getTorrentUserCacheKey($torrentId);
+ if ($redis->hExists($torrentUserCacheKey, $userId)) {
+ do_log("$logPrefix, user already exists: $torrentUserCacheKey", 'debug');
+ return false;
+ }
+ return true;
+ }
+
+ public static function recordUser(\Redis $redis, $userId, $torrentId, array $snatchedInfo): void
+ {
+ $torrentUserCacheKey = self::getTorrentUserCacheKey($torrentId);
+ $nowStr = now()->toDateTimeString();
+ $values = [
+ 'user_id' => $userId,
+ 'torrent_id' => $torrentId,
+ 'seed_time_begin' => $snatchedInfo['seedtime'],
+ 'uploaded_begin' => $snatchedInfo['uploaded'],
+ 'created_at' => $nowStr,
+ ];
+ $uniqueBy = ['user_id', 'torrent_id'];
+ $update = ['updated_at'];
+ UserRequireSeedTorrent::query()->upsert($values, $uniqueBy, $update);
+ $redis->hset($torrentUserCacheKey, $userId, $nowStr);
+ do_log("success insert user: $userId, torrent: $torrentId");
+ }
+
+ public function autoSettlementCronjob(): void
+ {
+
+ }
+}
diff --git a/app/Repositories/SeedBoxRepository.php b/app/Repositories/SeedBoxRepository.php
index 04ea9dfc..1a6d6e71 100644
--- a/app/Repositories/SeedBoxRepository.php
+++ b/app/Repositories/SeedBoxRepository.php
@@ -123,9 +123,17 @@ class SeedBoxRepository extends BaseRepository
public function delete($id, $uid)
{
+ $baseQuery = SeedBoxRecord::query()->whereIn('id', Arr::wrap($id))->where('uid', $uid);
+ $list = $baseQuery->clone()->get();
+ if ($list->isEmpty()) {
+ return false;
+ }
+ $baseQuery->delete();
$this->clearApprovalCountCache();
- publish_model_event("seed_box_record_deleted", $id);
- return SeedBoxRecord::query()->whereIn('id', Arr::wrap($id))->where('uid', $uid)->delete();
+ foreach ($list as $record) {
+ publish_model_event("seed_box_record_deleted", $record->id, $record->toJson());
+ }
+ return true;
}
public function updateStatus(SeedBoxRecord $seedBoxRecord, $status, $reason = '')
@@ -321,8 +329,13 @@ class SeedBoxRepository extends BaseRepository
if (is_null($reader)) {
return 0;
}
- $asnObj = $reader->asn($ip);
- return $asnObj->autonomousSystemNumber ?? 0;
+ try {
+ $asnObj = $reader->asn($ip);
+ return $asnObj->autonomousSystemNumber ?? 0;
+ } catch (\Exception $e) {
+ do_log("ip: $ip, error: " . $e->getMessage(), 'error');
+ return 0;
+ }
});
}
diff --git a/app/Repositories/UploadRepository.php b/app/Repositories/UploadRepository.php
index e39c8391..fd86eaaf 100644
--- a/app/Repositories/UploadRepository.php
+++ b/app/Repositories/UploadRepository.php
@@ -169,6 +169,7 @@ class UploadRepository extends BaseRepository
throw new NexusException(nexus_trans('upload.missing_torrent_file'));
}
if (!$file->isValid()) {
+ do_log("torrent file is invalid: " . nexus_json_encode($_FILES), 'error');
throw new NexusException("upload torrent file error");
}
$size = $file->getSize();
diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php
index 1b7d3819..ee9fb77c 100644
--- a/app/Repositories/UserRepository.php
+++ b/app/Repositories/UserRepository.php
@@ -2,6 +2,7 @@
namespace App\Repositories;
use App\Enums\ModelEventEnum;
+use App\Enums\RedisKeysEnum;
use App\Exceptions\InsufficientPermissionException;
use App\Exceptions\NexusException;
use App\Http\Resources\ExamUserResource;
@@ -252,9 +253,15 @@ class UserRepository extends BaseRepository
do_log("user: $uid, $modCommentText, update: " . nexus_json_encode($update));
$this->clearCache($targetUser);
fire_event("user_enabled", $targetUser);
+ $this->setEnableLatelyCache($targetUser->id);
return true;
}
+ private function setEnableLatelyCache(int $userId): void
+ {
+ NexusDB::cache_put(User::getUserEnableLatelyCacheKey($userId), now()->toDateTimeString());
+ }
+
public function getInviteInfo($id)
{
$user = User::query()->findOrFail($id, ['id']);
diff --git a/database/migrations/2025_07_22_001027_create_require_seed_torrents_table.php b/database/migrations/2025_07_22_001027_create_require_seed_torrents_table.php
new file mode 100644
index 00000000..acce2912
--- /dev/null
+++ b/database/migrations/2025_07_22_001027_create_require_seed_torrents_table.php
@@ -0,0 +1,28 @@
+id();
+ $table->integer('torrent_id')->unique();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('require_seed_torrents');
+ }
+};
diff --git a/database/migrations/2025_07_22_034710_create_user_require_seed_torrents_table.php b/database/migrations/2025_07_22_034710_create_user_require_seed_torrents_table.php
new file mode 100644
index 00000000..337ae21e
--- /dev/null
+++ b/database/migrations/2025_07_22_034710_create_user_require_seed_torrents_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->integer('user_id');
+ $table->integer('torrent_id')->index();
+ $table->bigInteger('seed_time_begin');
+ $table->bigInteger('uploaded_begin');
+ $table->dateTime('last_settlement_at')->nullable()->index();
+ $table->timestamps();
+ $table->unique(['user_id', 'torrent_id']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('user_require_seed_torrents');
+ }
+};
diff --git a/database/migrations/2025_08_15_132400_add_leech_time_no_seeder_to_snatched_table.php b/database/migrations/2025_08_15_132400_add_leech_time_no_seeder_to_snatched_table.php
new file mode 100644
index 00000000..ff1a4668
--- /dev/null
+++ b/database/migrations/2025_08_15_132400_add_leech_time_no_seeder_to_snatched_table.php
@@ -0,0 +1,28 @@
+bigInteger('leech_time_no_seeder')->default(0);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('snatched', function (Blueprint $table) {
+ $table->dropColumn('leech_time_no_seeder');
+ });
+ }
+};
diff --git a/database/migrations/2025_07_19_023351_add_leech_time_no_seeder_to_hit_and_runs_table.php b/database/migrations/2025_08_15_132620_add_leech_time_no_seeder_begin_to_hit_and_runs_table.php
similarity index 79%
rename from database/migrations/2025_07_19_023351_add_leech_time_no_seeder_to_hit_and_runs_table.php
rename to database/migrations/2025_08_15_132620_add_leech_time_no_seeder_begin_to_hit_and_runs_table.php
index 08040b47..d5e9fbc4 100644
--- a/database/migrations/2025_07_19_023351_add_leech_time_no_seeder_to_hit_and_runs_table.php
+++ b/database/migrations/2025_08_15_132620_add_leech_time_no_seeder_begin_to_hit_and_runs_table.php
@@ -12,7 +12,7 @@ return new class extends Migration
public function up(): void
{
Schema::table('hit_and_runs', function (Blueprint $table) {
- $table->bigInteger('leech_time_no_seeder')->default(0);
+ $table->bigInteger('leech_time_no_seeder_begin')->default(0);
});
}
@@ -22,7 +22,7 @@ return new class extends Migration
public function down(): void
{
Schema::table('hit_and_runs', function (Blueprint $table) {
- $table->dropColumn('leech_time_no_seeder');
+ $table->dropColumn('leech_time_no_seeder_begin');
});
}
};
diff --git a/database/migrations/2025_09_07_034041_add_index_to_added_field_on_torrents_table.php b/database/migrations/2025_09_07_034041_add_index_to_added_field_on_torrents_table.php
new file mode 100644
index 00000000..ace14482
--- /dev/null
+++ b/database/migrations/2025_09_07_034041_add_index_to_added_field_on_torrents_table.php
@@ -0,0 +1,29 @@
+index('added');
+ $table->index(['promotion_until', 'promotion_time_type']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('torrents', function (Blueprint $table) {
+ //
+ });
+ }
+};
diff --git a/include/cleanup.php b/include/cleanup.php
index b52b5c8c..5de5c51c 100644
--- a/include/cleanup.php
+++ b/include/cleanup.php
@@ -237,6 +237,11 @@ function disable_user(\Illuminate\Database\Eloquent\Builder $query, $reasonKey)
$userModifyLogs = [];
foreach ($results as $user) {
$uid = $user->id;
+ $enableCacheResult = \Nexus\Database\NexusDB::cache_get(\App\Models\User::getUserEnableLatelyCacheKey($uid));
+ if ($enableCacheResult) {
+ do_log(sprintf("user: %s just enable at: %s, skip", $uid, $enableCacheResult));
+ continue;
+ }
$uidArr[] = $uid;
$reason = nexus_trans($reasonKey, [], $user->locale);
$userBanLogData[] = [
@@ -251,6 +256,9 @@ function disable_user(\Illuminate\Database\Eloquent\Builder $query, $reasonKey)
'updated_at' => date("Y-m-d H:i:s"),
];
}
+ if (empty($uidArr)) {
+ return [];
+ }
$sql = sprintf(
"update users set enabled = '%s' where id in (%s)",
\App\Models\User::ENABLED_NO, implode(', ', $uidArr)
@@ -275,9 +283,7 @@ function docleanup($forceAll = 0, $printProgress = false) {
global $Cache;
global $rootpath;
$requestId = nexus()->getRequestId();
-
// require_once($rootpath . '/lang/_target/lang_cleanup.php');
-
set_time_limit(0);
ignore_user_abort(1);
$now = time();
diff --git a/include/constants.php b/include/constants.php
index a371a315..b5ef7f07 100644
--- a/include/constants.php
+++ b/include/constants.php
@@ -1,6 +1,6 @@
cache_value($cacheKey, $toApprovalCounts, 60);
}
if ($toApprovalCounts) {
- msgalert('torrents.php?approval_status=0', sprintf($lang_functions['text_torrent_to_approval'], is_or_are($toApprovalCounts), $toApprovalCounts, add_s($toApprovalCounts)), 'darkred');
+ msgalert('torrents.php?approval_status=0&incldead=0', sprintf($lang_functions['text_torrent_to_approval'], is_or_are($toApprovalCounts), $toApprovalCounts, add_s($toApprovalCounts)), 'darkred');
}
}
@@ -3007,17 +3007,7 @@ function get_protocol_prefix()
if (isHttps()) {
return "https://";
}
- if ($securelogin == "yes") {
- return "https://";
- } elseif ($securelogin == "no") {
- return "http://";
- } else {
- if (!isset($_COOKIE["c_secure_ssl"])) {
- return "http://";
- } else {
- return base64_decode($_COOKIE["c_secure_ssl"]) == "yeah" ? "https://" : "http://";
- }
- }
+ return 'http://';
}
function get_langid_from_langcookie($lang = '')
@@ -5818,7 +5808,7 @@ function can_access_torrent($torrent, $uid)
function get_ip_location_from_geoip($ip): bool|array
{
- $locationInfo = \Nexus\Database\NexusDB::remember("locations_{$ip}", 3600, function () use ($ip) {
+ $locationInfo = \Nexus\Database\NexusDB::remember("locations_{$ip}", 864000, function () use ($ip) {
$lang = get_langfolder_cookie();
$langMap = [
'chs' => 'zh-CN',
@@ -5861,7 +5851,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() . $exception->getTraceAsString(), 'error');
+ do_log($exception->getMessage());
}
return $info;
});
@@ -5898,7 +5888,7 @@ function msgalert($url, $text, $bgcolor = "red")
function build_medal_image(\Illuminate\Support\Collection $medals, $maxHeight = 200, $withActions = false): string
{
$medalImages = [];
- $wrapBefore = '