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 = '
'; + $wrapBefore = '
'; $wrapAfter = '
'; foreach ($medals as $medal) { $html = sprintf('
', $medal->image_large, $medal->name, $maxHeight, $maxHeight); @@ -6003,6 +5993,7 @@ function calculate_seed_bonus($uid, $torrentIdArr = null): array $nzero_bonus = $settingBonus['nzero']; $bzero_bonus = $settingBonus['bzero']; $l_bonus = $settingBonus['l']; + $minSize = $settingBonus['min_size'] ?? 0; $sqrtof2 = sqrt(2); $logofpointone = log(0.1); @@ -6022,9 +6013,9 @@ function calculate_seed_bonus($uid, $torrentIdArr = null): array $torrentIdArr = [-1]; } $idStr = implode(',', \Illuminate\Support\Arr::wrap($torrentIdArr)); - $sql = "select torrents.id, torrents.added, torrents.size, torrents.seeders, 'NO_PEER_ID' as peerID, '' as last_action from torrents WHERE id in ($idStr)"; + $sql = "select torrents.id, torrents.added, torrents.size, torrents.seeders, 'NO_PEER_ID' as peerID, '' as last_action from torrents WHERE id in ($idStr) and size >= $minSize"; } else { - $sql = "select torrents.id, torrents.added, torrents.size, torrents.seeders, peers.id as peerID, peers.last_action from torrents LEFT JOIN peers ON peers.torrent = torrents.id WHERE peers.userid = $uid AND peers.seeder ='yes' group by peers.torrent, peers.peer_id"; + $sql = "select torrents.id, torrents.added, torrents.size, torrents.seeders, peers.id as peerID, peers.last_action from torrents LEFT JOIN peers ON peers.torrent = torrents.id WHERE peers.userid = $uid AND peers.seeder ='yes' and torrents.size > $minSize group by peers.torrent, peers.peer_id"; } $tagGrouped = []; $torrentResult = \Nexus\Database\NexusDB::select($sql); diff --git a/include/globalfunctions.php b/include/globalfunctions.php index cfa90114..8d8d97ee 100644 --- a/include/globalfunctions.php +++ b/include/globalfunctions.php @@ -763,7 +763,13 @@ function get_user_row($id) if (isset($userRows[$id])) return $userRows[$id]; $cacheKey = 'user_'.$id.'_content'; $row = \Nexus\Database\NexusDB::remember($cacheKey, 3600, function () use ($id, $neededColumns) { - $user = \App\Models\User::query()->with(['wearing_medals'])->find($id, $neededColumns); + $user = \App\Models\User::query()->with([ + 'wearing_medals' => function ($query) { + $query->orderBy('user_medals.priority', 'desc') + ->orderBy('user_medals.id', 'desc') + ->limit(get_setting('system.maximum_number_of_medals_can_be_worn', 3)); + } + ])->find($id, $neededColumns); if (!$user) { return null; } @@ -1392,7 +1398,7 @@ function fire_event(string $name, \Illuminate\Database\Eloquent\Model $model, ?\ } } call_user_func_array([$eventClass, "dispatch"], $params); - publish_model_event($name, $model->id); + publish_model_event($name, $model->id, $model->toJson()); do_log("success fire_event in laravel, name: $name, id: $model->id, oldId: " . ($oldModel ? $oldModel->id : "")); } } @@ -1400,11 +1406,11 @@ function fire_event(string $name, \Illuminate\Database\Eloquent\Model $model, ?\ /** * 仅仅是往 redis 发布事件, php 端无监听者仅在其他平台有需要的触发这个即可, 较轻量 */ -function publish_model_event(string $event, int $id): void +function publish_model_event(string $event, int $id, string $json = ""): void { $channel = nexus_env("CHANNEL_NAME_MODEL_EVENT"); if (!empty($channel)) { - \Nexus\Database\NexusDB::redis()->publish($channel, json_encode(["event" => $event, "id" => $id])); + \Nexus\Database\NexusDB::redis()->publish($channel, json_encode(["event" => $event, "id" => $id, "json" => $json])); } else { do_log("event: $event, id: $id, channel: $channel, channel is empty!", "error"); } diff --git a/lang/chs/lang_settings.php b/lang/chs/lang_settings.php index 0d7e74f5..96c70283 100644 --- a/lang/chs/lang_settings.php +++ b/lang/chs/lang_settings.php @@ -818,6 +818,9 @@ $lang_settings = array 'row_complain_enabled_note' => '默认: "yes"', 'row_record_announce_logs' => '记录汇报日志', 'text_record_announce_logs_note' => '要启用,请先安装并启动 ClickHouse,并在 .env 文件中添加配置', + 'row_min_size' => '起步体积', + 'text_bonus_mini_size' => '要求种子的体积最小为', + 'text_bonus_mini_size_help' => '。小于此体积的种子不参与魔力计算。单位:字节(Byte),如 1 Byte = 1024 KiB。', ); ?> diff --git a/lang/cht/lang_settings.php b/lang/cht/lang_settings.php index bcb45a25..4b03c77d 100644 --- a/lang/cht/lang_settings.php +++ b/lang/cht/lang_settings.php @@ -816,6 +816,9 @@ $lang_settings = array 'text_use_challenge_response_authentication_note' => '如果啟用,登錄時將不傳輸明文密碼,建議啟用。未來版本會刪除此配置且啟用此功能。', 'row_complain_enabled' => '啟用申訴', 'row_complain_enabled_note' => '默認: "yes"', + 'row_min_size' => '起步體積', + 'text_bonus_mini_size' => '要求種子的體積最小爲', + 'text_bonus_mini_size_help' => '。小於此體積的種子不參與魔力計算。單位:字節(Byte),如 1 Byte = 1024 KiB。', ); ?> diff --git a/lang/en/lang_settings.php b/lang/en/lang_settings.php index 765d0beb..dfc1d612 100644 --- a/lang/en/lang_settings.php +++ b/lang/en/lang_settings.php @@ -816,6 +816,9 @@ $lang_settings = array 'text_use_challenge_response_authentication_note' => 'If enabled, no plaintext passwords will be transmitted at login, recommended. Future releases will remove this configuration and enable this feature.' , 'row_complain_enabled' => 'Whether to enable complaints', 'row_complain_enabled_note' => 'default: "yes"', + 'row_min_size' => 'Minimum volume', + 'text_bonus_mini_size'=> 'The minimum volume required for torrent is', + 'text_bonus_mini_size_help' => '. Torrent size smaller than this volume are not included in the bonus calculation. Unit: bytes (Byte), e.g., 1 Byte = 1024 KiB.', ); ?> diff --git a/nexus/Imdb/Imdb.php b/nexus/Imdb/Imdb.php index 4e9e95c2..4c5d8cd3 100644 --- a/nexus/Imdb/Imdb.php +++ b/nexus/Imdb/Imdb.php @@ -47,17 +47,16 @@ class Imdb private function checkDir($dir, $langKeyPrefix) { - global $lang_functions; if (!is_dir($dir)) { $mkdirResult = mkdir($dir, 0777, true); if ($mkdirResult !== true) { - $msg = $lang_functions["{$langKeyPrefix}_can_not_create"]; + $msg = nexus_trans("torrent.{$langKeyPrefix}_can_not_create"); do_log("$msg, dir: $dir"); throw new ImdbException($msg); } } if (!is_writable($dir)) { - $msg = $lang_functions["{$langKeyPrefix}_is_not_writeable"]; + $msg = nexus_trans("torrent.{$langKeyPrefix}_is_not_writeable"); do_log("$msg, dir: $dir"); throw new ImdbException($msg); } diff --git a/nexus/Install/Update.php b/nexus/Install/Update.php index 4b996fc6..50a92f87 100644 --- a/nexus/Install/Update.php +++ b/nexus/Install/Update.php @@ -376,6 +376,7 @@ class Update extends Install "enabled" => 1, "is_default" => 1, ]); + TrackerUrl::saveUrlCache(); NexusDB::cache_del("nexus_plugin_store_all"); } /** diff --git a/public/announce.php b/public/announce.php index 47299313..3e6fb038 100644 --- a/public/announce.php +++ b/public/announce.php @@ -401,6 +401,8 @@ if ( } } +$leechTimeNoSeeder = ""; + // current peer_id, or you could say session with tracker not found in table peers if (!isset($self)) { @@ -473,13 +475,18 @@ else // continue an existing session } do_log("upthis: $upthis, downthis: $downthis, announcetime: $announcetime, is_cheater: $is_cheater"); - $snatchInfo = get_snatch_info($torrentid, $userid); + if (!isset($snatchInfo)) { + $snatchInfo = get_snatch_info($torrentid, $userid); + } if (!$is_cheater && ($trueupthis > 0 || $truedownthis > 0)) { $dataTraffic = getDataTraffic($torrent, $_GET, $az, $self, $snatchInfo, apply_filter('torrent_promotion', $torrent)); $USERUPDATESET[] = "uploaded = uploaded + " . $dataTraffic['uploaded_increment_for_user']; $USERUPDATESET[] = "downloaded = downloaded + " . $dataTraffic['downloaded_increment_for_user']; } + if ($torrent['seeders'] <= 0 && $seeder == 'no' && $self['announcetime'] > 0) { + $leechTimeNoSeeder = ", leech_time_no_seeder = leech_time_no_seeder + {$self['announcetime']}"; + } } $dt = sqlesc(date("Y-m-d H:i:s")); @@ -495,7 +502,7 @@ if (isset($self) && $event == "stopped") { $updateset[] = ($self["seeder"] == "yes" ? "seeders = seeders - 1" : "leechers = leechers - 1"); $hasChangeSeederLeecher = true; - sql_query("UPDATE snatched SET uploaded = uploaded + $trueupthis, downloaded = downloaded + $truedownthis, to_go = $left, $announcetime, last_action = ".$dt." WHERE id = {$snatchInfo['id']}") or err("SL Err 1"); + sql_query("UPDATE snatched SET uploaded = uploaded + $trueupthis, downloaded = downloaded + $truedownthis, to_go = $left, $announcetime $leechTimeNoSeeder, last_action = ".$dt." WHERE id = {$snatchInfo['id']}") or err("SL Err 1"); } } elseif(isset($self)) @@ -518,7 +525,7 @@ elseif(isset($self)) $hasChangeSeederLeecher = true; } if (!empty($snatchInfo)) { - sql_query("UPDATE snatched SET uploaded = uploaded + $trueupthis, downloaded = downloaded + $truedownthis, to_go = $left, $announcetime, last_action = ".$dt." $finished_snatched WHERE id = {$snatchInfo['id']}") or err("SL Err 2"); + sql_query("UPDATE snatched SET uploaded = uploaded + $trueupthis, downloaded = downloaded + $truedownthis, to_go = $left, $announcetime, last_action = ".$dt." $finished_snatched $leechTimeNoSeeder WHERE id = {$snatchInfo['id']}") or err("SL Err 2"); do_action('snatched_saved', $torrent, $snatchInfo); } } @@ -571,14 +578,16 @@ if (($left > 0 || $event == "completed") && $az['class'] < \App\Models\HitAndRun $hrLog .= ", hrExists: $hrExists"; if (!$hrExists) { //last check include rate - $includeRate = \App\Models\HitAndRun::getConfig('include_rate', $torrent['mode']); - if ($includeRate === "" || $includeRate === null) { - //not set yet - $includeRate = 1; - } + $includeRate = (float)\App\Models\HitAndRun::getConfig('include_rate', $torrent['mode']); +// if ($includeRate === "" || $includeRate === null) { +// //not set yet +// $includeRate = 1; +// } $hrLog .= ", includeRate: $includeRate"; //get newest snatch info - $snatchInfo = get_snatch_info($torrentid, $userid); + if (!isset($snatchInfo)) { + $snatchInfo = get_snatch_info($torrentid, $userid); + } $requiredDownloaded = $torrent['size'] * $includeRate; if ($snatchInfo['downloaded'] >= $requiredDownloaded) { $nowStr = date('Y-m-d H:i:s'); @@ -598,28 +607,13 @@ if (($left > 0 || $event == "completed") && $az['class'] < \App\Models\HitAndRun do_log("$hrLog, total downloaded: {$snatchInfo['downloaded']} < required: $requiredDownloaded", "debug"); } } else { - $hrLog .= ", already exists"; - if (isset($self) && $torrent['seeders'] <= 0) { - $hrLeechTimeMin = \App\Models\HitAndRun::getConfig('leech_time_minimum', $torrent['mode']); - if ($hrLeechTimeMin > 0) { - $hrLog .= ", enable hrLeechTimeMin: $hrLeechTimeMin"; - $hrInfo = json_decode($hrExists, true); - if ($hrInfo['status'] == \App\Models\HitAndRun::STATUS_INSPECTING) { - sql_query("update hit_and_runs set leech_time_no_seeder = leech_time_no_seeder + {$self['announcetime']} where id = {$hrInfo['id']} limit 1"); - } else { - do_log("$hrLog, hr status != STATUS_INSPECTING", "debug"); - } - } else { - do_log("$hrLog, not enable hrLeechTimeMin", "debug"); - } - } else { - do_log("$hrLog, no self or seeders({$torrent['seeders']}) > 0", "debug"); - } + do_log("$hrLog, already exists", 'debug'); } } else { do_log("$hrLog, not match", "debug"); } } + // revert to only increment/decrement //if (isset($event) && !empty($event)) { // $updateset[] = 'seeders = ' . get_row_count("peers", "where torrent = $torrentid and to_go = 0"); @@ -660,6 +654,12 @@ $lockKey = sprintf("record_batch_lock:%s:%s", $userid, $torrentid); if ($redis->set($lockKey, TIMENOW, ['nx', 'ex' => $autoclean_interval_one])) { \App\Repositories\CleanupRepository::recordBatch($redis, $userid, $torrentid); } +if (\App\Repositories\RequireSeedTorrentRepository::shouldRecordUser($redis, $userid, $torrentid)) { + if (!isset($snatchInfo)) { + $snatchInfo = get_snatch_info($torrentid, $userid); + } + \App\Repositories\RequireSeedTorrentRepository::recordUser($redis, $userid, $torrentid, $snatchInfo); +} do_action('announced', $torrent, $az, $_REQUEST); benc_resp($rep_dict); ?> diff --git a/public/getrss.php b/public/getrss.php index e0ab69dd..49401979 100644 --- a/public/getrss.php +++ b/public/getrss.php @@ -39,7 +39,7 @@ if ($showaudiocodec) $audiocodecs = searchbox_item_list("audiocodecs", $brsectio } stdhead($lang_getrss['head_rss_feeds']); $query = []; -$allowed_showrows=array('10','50','100','200'); +$allowed_showrows=array('10','50'); $stickyTypes = [ 0 => nexus_trans('torrent.pos_state_normal'), 1 => nexus_trans('torrent.pos_state_sticky'), @@ -365,17 +365,6 @@ if (get_setting('main.spsct') == 'yes') { } ?> - - - - - -
- - diff --git a/public/modtask.php b/public/modtask.php index ccd0d6dd..b21e1ac6 100644 --- a/public/modtask.php +++ b/public/modtask.php @@ -111,7 +111,9 @@ if ($action == "edituser") if ($arr['email'] != $email){ $updateset[] = "email = " . sqlesc($email); // $modcomment = date("Y-m-d") . " - Email changed from $arr[email] to $email by {$CURUSER['username']}.\n". $modcomment; - $userModifyLogs[] = "Email changed from $arr[email] to $email by {$CURUSER['username']}."; + $modifyLog = "Email changed from $arr[email] to $email by {$CURUSER['username']}."; + do_log($modifyLog, "alert"); + $userModifyLogs[] = $modifyLog; $locale = get_user_locale($userid); $subject = sqlesc(nexus_trans("user.msg_email_change", [], $locale)); $msg = sqlesc(nexus_trans("user.msg_your_email_changed_from", [], $locale).$arr['email'].nexus_trans("user.msg_to_new", [], $locale) . $email .nexus_trans("user.msg_by", [], $locale).$CURUSER['username']); diff --git a/public/recover.php b/public/recover.php index a0c5e219..128c23f9 100644 --- a/public/recover.php +++ b/public/recover.php @@ -51,6 +51,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") $title = $SITENAME.$lang_recover['mail_title']; $mailOne = sprintf($lang_recover['mail_one'], $siteName); $mailFour = sprintf($lang_recover['mail_four'], $siteName); + \Nexus\Database\NexusDB::cache_put("recover:$hash", now()->toDateTimeString()); $body = <<"); print("".$lang_settings['text_bonus_by_seeding'].""); + tr($lang_settings['row_min_size'], $lang_settings['text_bonus_mini_size']."".$lang_settings['text_bonus_mini_size_help'],1); tr($lang_settings['row_donor_gets_double'], $lang_settings['text_donor_gets']."".$lang_settings['text_times_as_many'],1); tr($lang_settings['row_basic_seeding_bonus'], $lang_settings['text_user_would_get']."".$lang_settings['text_bonus_points']."".$lang_settings['text_torrents_default'], 1); diff --git a/public/takeflush.php b/public/takeflush.php index 8216a4f6..75622f75 100644 --- a/public/takeflush.php +++ b/public/takeflush.php @@ -15,9 +15,10 @@ $id = intval($_GET['id'] ?? 0); int_check($id,true); if (get_user_class() >= UC_MODERATOR || $CURUSER['id'] == "$id") -{ +{ $deadtime = deadtime(); - sql_query("DELETE FROM peers WHERE last_action < FROM_UNIXTIME($deadtime) AND userid=" . sqlesc($id)); + $lastAction = date("Y-m-d H:i:s", $deadtime); + sql_query("DELETE FROM peers WHERE last_action < '$lastAction' AND userid=" . sqlesc($id)); $effected = mysql_affected_rows(); stderr($lang_takeflush['std_success'], "$effected ".$lang_takeflush['std_ghost_torrents_cleaned']); diff --git a/public/takeupload.php b/public/takeupload.php index bb77228a..3e74dbe2 100644 --- a/public/takeupload.php +++ b/public/takeupload.php @@ -93,8 +93,10 @@ $torrent = unesc($_POST["name"]); if ($f['size'] > $max_torrent_size) bark($lang_takeupload['std_torrent_file_too_big'].number_format($max_torrent_size).$lang_takeupload['std_remake_torrent_note']); $tmpname = $f["tmp_name"]; -if (!is_uploaded_file($tmpname)) -bark("eek"); +if (!is_uploaded_file($tmpname)) { + do_log("eek, FILE: " . nexus_json_encode($f), 'error'); + bark("eek"); +} if (!filesize($tmpname)) bark($lang_takeupload['std_empty_file']); diff --git a/public/torrentrss.php b/public/torrentrss.php index 61684422..c8ccee2c 100644 --- a/public/torrentrss.php +++ b/public/torrentrss.php @@ -37,7 +37,8 @@ if ($passkey){ } } } -$searchstr = mysql_real_escape_string(trim($_GET["search"] ?? '')); +//$searchstr = mysql_real_escape_string(trim($_GET["search"] ?? '')); +$searchstr = null;//don't support search, use client self filter instead if (empty($searchstr)) unset($searchstr); if (isset($searchstr)){ @@ -81,8 +82,8 @@ if ($startindex) { $limit .= $startindex.", "; } $showrows = intval($_GET['rows'] ?? 0); -if($showrows < 1 || $showrows > 200) { - $showrows = 10; +if($showrows < 1 || $showrows > 50) { + $showrows = 50; } $limit .= $showrows; diff --git a/public/torrents.php b/public/torrents.php index e02986d4..162c0218 100644 --- a/public/torrents.php +++ b/public/torrents.php @@ -738,7 +738,7 @@ if (isset($searchstr)) { $searchstr_element = trim($searchstr_element); // furthur trim to ensure that multi space seperated words still work $searchstr_exploded_count++; - if ($searchstr_exploded_count > 10) // maximum 10 keywords + if ($searchstr_exploded_count > 3) // maximum 3 keywords break; $like_expression_array[] = " LIKE '%" . $searchstr_element. "%'"; } diff --git a/resources/lang/en/bonus-log.php b/resources/lang/en/bonus-log.php index 6df1efa5..281847c3 100644 --- a/resources/lang/en/bonus-log.php +++ b/resources/lang/en/bonus-log.php @@ -27,6 +27,11 @@ return [ \App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => 'Receive reward', \App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => 'Receive gift', \App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => 'Upload torrent', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_BASIC => 'Seeding basic', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_DONOR_ADDITION => 'Seeding donor addition', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_OFFICIAL_ADDITION => 'Seeding official addition', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_HAREM_ADDITION => 'Seeding harem addition', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_MEDAL_ADDITION => 'Seeding medal addition', ], 'fields' => [ 'business_type' => 'Business type', diff --git a/resources/lang/en/torrent.php b/resources/lang/en/torrent.php index 21aa5d28..d3a3ff3c 100644 --- a/resources/lang/en/torrent.php +++ b/resources/lang/en/torrent.php @@ -107,4 +107,9 @@ return [ 'msg_here' => " [b]here[/b]", 'msg_offer' => "Offer ", 'msg_blank' => ".", + 'require_seed_section_menu_title' => 'Require Seed', + 'imdb_cache_dir_can_not_create' => 'imdb cache dir can not create', + 'imdb_cache_dir_is_not_writeable' => 'imdb cache dir is not writeable', + 'imdb_photo_dir_can_not_create' => 'imdb photo dir can not create', + 'imdb_photo_dir_is_not_writeable' => 'imdb photo dir is not writeable', ]; diff --git a/resources/lang/zh_CN/bonus-log.php b/resources/lang/zh_CN/bonus-log.php index a26f7156..76c87d30 100644 --- a/resources/lang/zh_CN/bonus-log.php +++ b/resources/lang/zh_CN/bonus-log.php @@ -29,6 +29,11 @@ return [ \App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => '收到奖励', \App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => '收到礼物', \App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => '发布种子', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_BASIC => '做种基础魔力', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_DONOR_ADDITION => '做种捐赠加成', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_OFFICIAL_ADDITION => '做种官种加成', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_HAREM_ADDITION => '做种后宫加成', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_MEDAL_ADDITION => '做种勋章加成', ], 'fields' => [ 'business_type' => '业务类型', diff --git a/resources/lang/zh_CN/label.php b/resources/lang/zh_CN/label.php index e5c3284d..295fa449 100644 --- a/resources/lang/zh_CN/label.php +++ b/resources/lang/zh_CN/label.php @@ -91,6 +91,47 @@ return [ 'include_rate' => '计入完成率', 'include_rate_help' => '当下载完成率(0 ~ 1 之间的小数)达到此值时才计入 H&R。默认:1' ], + 'require_seed_section' => [ + 'tab_header' => '急需保种列表', + 'menu_title' => '菜单名称', + 'enabled_help' => '注意:此列表的种子不受盒子规则限制', + 'menu_title_help' => '若留空,使用默认值:急需保种', + 'seeder_lte' => '做种数小于等于', + 'seeder_lte_help' => '满足此条件的种子才进入此列表。默认 0', + 'seeder_gte' => '做种数大于等于', + 'seeder_gte_help' => '满足此条件的种子才进入此列表。默认 0', + 'promotion_state' => '促销状态', + 'promotion_state_help' => '移出列表后有效性保留24小时。默认 Free', + 'bonus_reward' => '魔力奖励', + 'bonus_reward_help' => '按做种数由小到大配置。窗口都是采用去尾法,即不足一个窗口的舍弃掉。时间奖励窗口单位是小时,上传量窗口单位是GiB', + 'require_tags' => '包含标签', + 'require_tags_help' => '至少包含选中标签中的一个才进入此列表。留空则不限制。', + 'seeders' => '做种数小于等于', + 'daily_seed_time_min' => '每天最小做种时间', + 'daily_seed_time_min_help' => '单位:小时。每天 0 点结算,统计前一天 24 小时内做种时间,不小于此小时数的种子才有奖励', + 'torrent_count_max' => '数量上限', + 'torrent_count_max_help' => '列表允许的最大的种子数量。必须严格控制数量,过多失则去了意义。默认:100', + + 'seed_time_reward' => '做种时间奖励', + 'seed_time_reward_begin' => '开始值', + 'seed_time_reward_begin_help' => '', + 'seed_time_reward_end' => '结束值', + 'seed_time_reward_end_help' => '', + 'seed_time_reward_window' => '窗口', + 'seed_time_reward_window_help' => '', + 'seed_time_reward_reward' => '奖励', + 'seed_time_reward_reward_help' => '', + + 'data_traffic_reward' => '上传量奖励', + 'data_traffic_reward_begin' => '开始值', + 'data_traffic_reward_begin_help' => '', + 'data_traffic_reward_end' => '结束值', + 'data_traffic_reward_end_help' => '', + 'data_traffic_reward_window' => '窗口', + 'data_traffic_reward_window_help' => '', + 'data_traffic_reward_reward' => '奖励', + 'data_traffic_reward_reward_help' => '', + ], 'seed_box' => [ 'tab_header' => 'SeedBox', 'enabled_help' => '是否启用 SeedBox 规则', diff --git a/resources/lang/zh_CN/torrent.php b/resources/lang/zh_CN/torrent.php index db58b5c5..4c438736 100644 --- a/resources/lang/zh_CN/torrent.php +++ b/resources/lang/zh_CN/torrent.php @@ -106,4 +106,9 @@ return [ 'msg_here' => "[b]这里[/b]", 'msg_offer' => "候选 ", 'msg_blank' => "刪除。", + 'require_seed_section_menu_title' => '急需保种', + 'imdb_cache_dir_can_not_create' => 'imdb 缓存目录无法创建', + 'imdb_cache_dir_is_not_writeable' => 'imdb 缓存目录不可写', + 'imdb_photo_dir_can_not_create' => 'imdb 图片目录无法创建', + 'imdb_photo_dir_is_not_writeable' => 'imdb 图片目录不可写', ]; diff --git a/resources/lang/zh_TW/bonus-log.php b/resources/lang/zh_TW/bonus-log.php index 1fd5ead2..faa9797e 100644 --- a/resources/lang/zh_TW/bonus-log.php +++ b/resources/lang/zh_TW/bonus-log.php @@ -27,6 +27,11 @@ return [ \App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => '收到獎勵', \App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => '收到禮物', \App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => '發布種子', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_BASIC => '做種基礎魔力', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_DONOR_ADDITION => '做種捐贈加成', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_OFFICIAL_ADDITION => '做種官種加成', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_HAREM_ADDITION => '做種後宮加成', + \App\Models\BonusLogs::BUSINESS_TYPE_SEEDING_MEDAL_ADDITION => '做種勳章加成', ], 'fields' => [ 'business_type' => '業務類型', diff --git a/resources/lang/zh_TW/torrent.php b/resources/lang/zh_TW/torrent.php index 568b1b80..8591d31b 100644 --- a/resources/lang/zh_TW/torrent.php +++ b/resources/lang/zh_TW/torrent.php @@ -105,4 +105,9 @@ return [ 'msg_you_can_download' => "上傳。\n下載請到", 'msg_here' => "[b]這裏[/b]", 'msg_offer' => "候選 ", + 'require_seed_section_menu_title' => '急需保種', + 'imdb_cache_dir_can_not_create' => 'imdb 緩存目錄無法創建', + 'imdb_cache_dir_is_not_writeable' => 'imdb 緩存目錄不可寫', + 'imdb_photo_dir_can_not_create' => 'imdb 圖片目錄無法創建', + 'imdb_photo_dir_is_not_writeable' => 'imdb 圖片目錄不可寫', ];