From 0c6ce9974ae731accf106f5214b8167d44346106 Mon Sep 17 00:00:00 2001 From: DariusIII Date: Wed, 7 Jan 2026 14:51:28 +0100 Subject: [PATCH] CS fixes --- .../Commands/CreateManticoreIndexes.php | 1 - app/Console/Commands/CreateMediaIndexes.php | 30 +-- .../Commands/DisableExpiredPromotions.php | 2 +- app/Console/Commands/GetArticleRange.php | 2 +- app/Console/Commands/ManageRoleStacking.php | 39 ++-- app/Console/Commands/MigrateAnimeCovers.php | 13 +- app/Console/Commands/NntmuxOffsetPopulate.php | 1 - app/Console/Commands/NntmuxOffsetWorker.php | 2 +- app/Console/Commands/NntmuxPopulateAniDB.php | 1 + .../Commands/NntmuxPopulateSearchIndexes.php | 1 - app/Console/Commands/PartRepair.php | 2 +- app/Console/Commands/PostProcessGuid.php | 8 +- app/Console/Commands/ProcessAdultMovies.php | 10 +- app/Console/Commands/RecategorizeReleases.php | 2 +- app/Console/Commands/RedisMonitor.php | 25 +-- app/Console/Commands/RefreshAnimeData.php | 34 ++-- .../Commands/ReleasesFixNamesGroup.php | 11 +- .../Commands/ReprocessMissingEpisodes.php | 21 +- app/Console/Commands/ReprocessTvRelease.php | 16 +- .../Commands/ReprocessUnmatchedTvReleases.php | 22 +-- app/Console/Commands/SteamLookupGame.php | 25 +-- app/Console/Commands/SteamSearchGames.php | 12 +- app/Console/Commands/TestCategorization.php | 11 +- app/Console/Commands/TmuxMonitor.php | 2 +- app/Console/Commands/UpdateBinaries.php | 22 +-- app/Console/Commands/UpdateGroupHeaders.php | 2 +- app/Console/Commands/UpdatePerGroup.php | 10 +- app/Console/Commands/UpdatePostProcess.php | 6 +- .../Commands/UpdateReleasesIndexSchema.php | 54 +++-- .../Commands/UpdateReleasesIndexSchemaES.php | 78 ++++---- app/Enums/BlacklistConstants.php | 1 - app/Enums/FileCompletionStatus.php | 1 - app/Enums/QueueType.php | 1 - app/Enums/SignupError.php | 1 - app/Enums/UserRole.php | 1 - app/Extensions/helper/helpers.php | 2 +- app/Facades/Categorization.php | 1 - app/Facades/TvProcessing.php | 1 - app/Facades/Yenc.php | 1 - .../Controllers/Admin/AdminAjaxController.php | 2 +- .../Admin/AdminContentController.php | 26 +-- .../Admin/AdminMusicController.php | 2 +- .../Admin/AdminPromotionController.php | 15 +- .../Controllers/Admin/AdminUserController.php | 3 +- .../Admin/AdminUserRoleHistoryController.php | 23 ++- app/Http/Controllers/Api/ApiController.php | 3 +- .../Controllers/Api/ApiInformController.php | 2 +- app/Http/Controllers/Api/ApiV2Controller.php | 5 +- .../Controllers/Auth/RegisterController.php | 2 +- app/Http/Controllers/ContentController.php | 1 + app/Http/Controllers/CoverController.php | 6 +- app/Http/Controllers/DetailsController.php | 12 +- app/Http/Controllers/MusicController.php | 2 +- app/Http/Controllers/ProfileController.php | 2 +- app/Http/Controllers/SearchController.php | 2 + .../Controllers/SearchSuggestController.php | 1 - app/Http/Resources/AnidbCollection.php | 1 - app/Http/Resources/AnidbResource.php | 1 - app/Http/Resources/BookCollection.php | 1 - app/Http/Resources/BookResource.php | 1 - app/Http/Resources/MovieCollection.php | 1 - app/Http/Resources/MovieResource.php | 1 - app/Models/Category.php | 8 +- app/Models/Content.php | 4 + app/Models/GamesInfo.php | 11 +- app/Models/Genre.php | 10 +- app/Models/Release.php | 1 - app/Models/RolePromotion.php | 5 +- app/Models/RolePromotionStat.php | 1 - app/Models/UsenetGroup.php | 4 +- app/Models/User.php | 9 +- app/Models/UserRoleHistory.php | 1 - app/Observers/MovieInfoObserver.php | 1 - app/Observers/ReleaseObserver.php | 1 - app/Observers/RolePromotionObserver.php | 1 - app/Observers/VideoObserver.php | 1 - .../AdditionalProcessingServiceProvider.php | 27 +-- .../AdultProcessingServiceProvider.php | 15 +- .../CategorizationServiceProvider.php | 3 - app/Providers/ProcessingServiceProvider.php | 5 +- app/Providers/SearchServiceProvider.php | 3 +- app/Providers/TvProcessingServiceProvider.php | 1 - app/Rules/ValidEmailDomain.php | 9 +- .../AdditionalProcessingOrchestrator.php | 15 +- .../ArchiveExtractionService.php | 23 ++- .../Config/ProcessingConfiguration.php | 44 +++++ .../ConsoleOutputService.php | 148 +++++++++++--- .../DTO/ReleaseProcessingContext.php | 40 +++- .../MediaExtractionService.php | 24 ++- .../AdditionalProcessing/NzbContentParser.php | 5 +- .../ReleaseFileManager.php | 22 ++- .../UsenetDownloadService.php | 23 ++- .../AdultProcessingPassable.php | 5 +- .../AdultProcessingPipeline.php | 156 ++++++++------- .../AdultProcessing/AdultProcessingResult.php | 5 +- .../AdultProcessing/AdultReleaseContext.php | 1 - .../AgeVerificationManager.php | 3 +- .../Pipes/AbstractAdultProviderPipe.php | 84 ++++---- .../AdultProcessing/Pipes/AdePipe.php | 37 ++-- .../AdultProcessing/Pipes/AdmPipe.php | 24 ++- .../AdultProcessing/Pipes/AebnPipe.php | 88 +++++---- .../AdultProcessing/Pipes/Data18Pipe.php | 79 ++++---- .../AdultProcessing/Pipes/HotmoviesPipe.php | 28 +-- .../AdultProcessing/Pipes/IafdPipe.php | 78 ++++---- .../AdultProcessing/Pipes/PoppornPipe.php | 50 +++-- app/Services/AnidbService.php | 2 - .../Archives/SevenZipPartialParser.php | 1 - app/Services/Backfill/BackfillConfig.php | 1 - app/Services/Backfill/BackfillService.php | 1 - app/Services/Binaries/BinariesConfig.php | 1 - app/Services/Binaries/BinariesService.php | 1 - app/Services/Binaries/BinaryHandler.php | 1 - app/Services/Binaries/CollectionHandler.php | 1 - app/Services/Binaries/HeaderParser.php | 2 - .../Binaries/HeaderStorageService.php | 3 - .../Binaries/HeaderStorageTransaction.php | 1 - app/Services/Binaries/MissedPartHandler.php | 1 - app/Services/Binaries/PartHandler.php | 1 - app/Services/BookService.php | 3 +- .../Categorization/CategorizationPipeline.php | 30 +-- .../Categorization/CategorizationResult.php | 9 +- .../Categorization/CategorizationService.php | 12 +- .../Categorizers/AbstractCategorizer.php | 22 ++- .../Categorizers/BookCategorizer.php | 95 +++++++-- .../Categorizers/ConsoleCategorizer.php | 130 ++++++++++--- .../Categorizers/GroupNameCategorizer.php | 43 +++- .../Categorizers/MiscCategorizer.php | 18 +- .../Categorizers/MovieCategorizer.php | 7 +- .../Categorizers/MusicCategorizer.php | 23 ++- .../Categorizers/PcCategorizer.php | 14 +- .../Categorizers/TvCategorizer.php | 23 ++- .../Categorizers/XxxCategorizer.php | 65 ++++--- .../Contracts/CategorizerInterface.php | 12 +- .../Pipes/AbstractCategorizationPipe.php | 1 - .../Categorization/Pipes/BookPipe.php | 3 +- .../Pipes/CategorizationPassable.php | 4 +- .../Categorization/Pipes/ConsolePipe.php | 4 +- .../Categorization/Pipes/GroupNamePipe.php | 4 +- .../Categorization/Pipes/MiscPipe.php | 4 +- .../Categorization/Pipes/MoviePipe.php | 4 +- .../Categorization/Pipes/MusicPipe.php | 4 +- app/Services/Categorization/Pipes/PcPipe.php | 4 +- app/Services/Categorization/Pipes/TvPipe.php | 4 +- app/Services/Categorization/Pipes/XxxPipe.php | 4 +- .../Categorization/ReleaseContext.php | 1 - app/Services/CollectionCleanupService.php | 3 +- app/Services/CollectionsCleaningService.php | 10 +- app/Services/ConsoleService.php | 54 +++-- app/Services/FanartTvService.php | 1 - app/Services/ForkingService.php | 15 +- app/Services/GamesService.php | 108 +++++----- app/Services/GamesTitleParser.php | 8 +- app/Services/GenreService.php | 1 - app/Services/IGDBService.php | 77 +++++--- app/Services/IRCClient.php | 11 +- app/Services/IRCScraper.php | 2 - app/Services/ItunesService.php | 2 +- app/Services/MovieBrowseService.php | 1 - app/Services/MovieService.php | 3 +- app/Services/MusicService.php | 7 - app/Services/NNTP/NNTPService.php | 1 - .../Checkers/AbstractNameChecker.php | 2 +- .../NameFixing/Checkers/AppNameChecker.php | 2 +- .../NameFixing/Checkers/GameNameChecker.php | 2 +- .../NameFixing/Checkers/MovieNameChecker.php | 3 +- .../NameFixing/Checkers/TvNameChecker.php | 2 +- .../Contracts/NameCheckerInterface.php | 5 +- .../Contracts/NameSourceFixerInterface.php | 17 +- app/Services/NameFixing/DTO/NameFixResult.php | 1 - .../Extractors/FileNameExtractor.php | 19 +- .../Extractors/NfoNameExtractor.php | 35 ++-- app/Services/NameFixing/FileNameCleaner.php | 26 +-- app/Services/NameFixing/FilePrioritizer.php | 21 +- .../NameFixing/NameCheckerService.php | 31 +-- app/Services/NameFixing/NameFixingService.php | 180 ++++++++++------- .../NameFixing/Patterns/AppPatterns.php | 1 - .../NameFixing/Patterns/GamePatterns.php | 1 - .../NameFixing/Patterns/MoviePatterns.php | 1 - .../NameFixing/Patterns/TvPatterns.php | 1 - .../NameFixing/ReleaseUpdateService.php | 79 +++++--- app/Services/NfoService.php | 2 +- app/Services/Nzb/NzbContentsService.php | 5 +- app/Services/Nzb/NzbImportService.php | 4 +- app/Services/Nzb/NzbParserService.php | 23 ++- app/Services/Nzb/NzbService.php | 3 +- app/Services/PopulateAniListService.php | 5 +- app/Services/PostProcessService.php | 65 ++++--- app/Services/RedisMonitorService.php | 184 ++++++++++-------- app/Services/RegexService.php | 1 - app/Services/ReleaseCleaningService.php | 1 - app/Services/ReleaseCreationService.php | 4 +- app/Services/ReleaseExtraService.php | 3 - app/Services/ReleaseImageService.php | 1 - app/Services/ReleaseProcessingService.php | 124 +++++++----- app/Services/ReleaseRemoverService.php | 29 ++- .../Releases/ReleaseBrowseService.php | 32 +-- .../Releases/ReleaseManagementService.php | 7 +- .../Releases/ReleaseSearchService.php | 15 +- app/Services/Runners/BackfillRunner.php | 1 - app/Services/Runners/BaseRunner.php | 24 ++- app/Services/Runners/BinariesRunner.php | 3 +- app/Services/Runners/PostProcessRunner.php | 1 - app/Services/Runners/ReleasesRunner.php | 1 - .../Contracts/SearchDriverInterface.php | 1 - .../Contracts/SearchServiceInterface.php | 4 +- .../Search/Drivers/ElasticSearchDriver.php | 8 +- .../Search/Drivers/ManticoreSearchDriver.php | 6 +- app/Services/Search/MediaSearchService.php | 2 - app/Services/Search/SearchService.php | 3 - app/Services/SteamService.php | 95 +++++---- app/Services/TmdbClient.php | 6 +- .../Tmux/Scripts/groupfixrelnames.php | 1 - app/Services/Tmux/Scripts/monitor.php | 1 - app/Services/Tmux/Scripts/postprocess_pre.php | 1 - app/Services/Tmux/Scripts/update_groups.php | 1 - app/Services/Tmux/Tmux.php | 1 - app/Services/Tmux/TmuxLayoutBuilder.php | 1 + app/Services/Tmux/TmuxOutput.php | 1 - app/Services/Tmux/TmuxTaskRunner.php | 1 + app/Services/TraktService.php | 3 +- .../Pipes/AbstractTvProviderPipe.php | 18 +- .../TvProcessing/Pipes/LocalDbPipe.php | 7 +- .../TvProcessing/Pipes/ParseInfoPipe.php | 6 +- app/Services/TvProcessing/Pipes/TmdbPipe.php | 14 +- app/Services/TvProcessing/Pipes/TraktPipe.php | 14 +- .../TvProcessing/Pipes/TvMazePipe.php | 14 +- app/Services/TvProcessing/Pipes/TvdbPipe.php | 16 +- .../Providers/AbstractTvProvider.php | 1 - .../Providers/BaseVideoProvider.php | 1 - .../TvProcessing/Providers/TraktProvider.php | 6 +- .../TvProcessing/Providers/TvMazeProvider.php | 2 +- .../TvProcessing/Providers/TvdbProvider.php | 4 +- .../TvProcessing/TvProcessingPassable.php | 5 +- .../TvProcessing/TvProcessingPipeline.php | 29 ++- .../TvProcessing/TvProcessingResult.php | 5 +- .../TvProcessing/TvReleaseContext.php | 1 - app/Services/XXXProcessor.php | 6 +- app/Services/XxxBrowseService.php | 2 - app/Services/YencService.php | 1 - app/Support/DTOs/ProcessReleasesSettings.php | 8 +- app/Support/DTOs/ReleaseCreationResult.php | 3 +- app/Support/DTOs/ReleaseDeleteStats.php | 3 +- app/Support/DTOs/SteamGameData.php | 92 ++++----- app/Support/DTOs/SteamPriceData.php | 1 - bootstrap/providers.php | 2 - config/search.php | 1 - database/factories/UserFactory.php | 1 - ...6_add_uuid_column_to_failed_jobs_table.php | 6 +- ...28_000000_create_role_promotions_table.php | 1 - ...0000_create_role_promotion_stats_table.php | 1 - ...dd_role_stacking_fields_to_users_table.php | 1 - ..._120001_create_user_role_history_table.php | 1 - ...00000_add_redis_args_to_settings_table.php | 1 - ...2_05_000000_replace_anidb_with_anilist.php | 7 +- ...ories_postdate_index_to_releases_table.php | 1 - ...00001_add_covering_index_for_tv_search.php | 1 - ..._performance_indexes_to_releases_table.php | 1 - ...omposite_indexes_for_admin_performance.php | 1 - routes/web.php | 1 - tests/Feature/AdultProcessingPipelineTest.php | 24 +-- tests/Feature/FanarttvApiTest.php | 1 - tests/Feature/OmdbApiTest.php | 1 - tests/Feature/RoleUpgradeTest.php | 20 +- tests/Feature/SteamServiceTest.php | 3 +- tests/Feature/TmdbApiTest.php | 1 - tests/Feature/TvdbApiTest.php | 1 - tests/Feature/TvmazeApiTest.php | 1 - .../ItunesServiceIntegrationTest.php | 13 +- tests/Support/DatabaseTestCase.php | 4 - tests/Support/TestBinariesHarness.php | 1 - tests/Unit/Rules/ValidEmailDomainTest.php | 21 +- tests/Unit/SteamDTOsTest.php | 1 - tests/Unit/SteamServiceTest.php | 5 +- tests/Unit/YencServiceTest.php | 1 - 274 files changed, 2370 insertions(+), 1678 deletions(-) diff --git a/app/Console/Commands/CreateManticoreIndexes.php b/app/Console/Commands/CreateManticoreIndexes.php index aa381f834..efd09ce40 100644 --- a/app/Console/Commands/CreateManticoreIndexes.php +++ b/app/Console/Commands/CreateManticoreIndexes.php @@ -5,7 +5,6 @@ namespace App\Console\Commands; use Illuminate\Console\Command; use Manticoresearch\Client; use Manticoresearch\Exceptions\ResponseException; -use Manticoresearch\Table; class CreateManticoreIndexes extends Command { diff --git a/app/Console/Commands/CreateMediaIndexes.php b/app/Console/Commands/CreateMediaIndexes.php index 725a7d236..ecbc309ba 100644 --- a/app/Console/Commands/CreateMediaIndexes.php +++ b/app/Console/Commands/CreateMediaIndexes.php @@ -2,7 +2,6 @@ namespace App\Console\Commands; -use App\Facades\Search; use Illuminate\Console\Command; use Manticoresearch\Client; use Manticoresearch\Exceptions\ResponseException; @@ -39,6 +38,7 @@ class CreateMediaIndexes extends Command return $this->createElasticsearchIndexes(); } else { $this->error("Unsupported search driver: {$driver}"); + return Command::FAILURE; } } @@ -61,7 +61,8 @@ class CreateMediaIndexes extends Command $client->nodes()->status(); $this->info('Connected to ManticoreSearch successfully.'); } catch (\Exception $e) { - $this->error('Failed to connect to ManticoreSearch: ' . $e->getMessage()); + $this->error('Failed to connect to ManticoreSearch: '.$e->getMessage()); + return Command::FAILURE; } @@ -109,13 +110,14 @@ class CreateMediaIndexes extends Command $hasErrors = false; foreach ($indexes as $indexName => $schema) { - if (!$this->createManticoreIndex($client, $indexName, $schema, $dropExisting)) { + if (! $this->createManticoreIndex($client, $indexName, $schema, $dropExisting)) { $hasErrors = true; } } if ($hasErrors) { $this->error('Some errors occurred during index creation.'); + return Command::FAILURE; } @@ -158,6 +160,7 @@ class CreateMediaIndexes extends Command $client->tables()->drop(['index' => $indexName, 'body' => ['silent' => true]]); } else { $this->info("Index {$indexName} already exists. Use --drop to recreate."); + return true; } } @@ -172,30 +175,35 @@ class CreateMediaIndexes extends Command ]); $this->info("Index {$indexName} created successfully."); - $this->line('Response: ' . json_encode($response, JSON_PRETTY_PRINT)); + $this->line('Response: '.json_encode($response, JSON_PRETTY_PRINT)); + return true; } catch (ResponseException $e) { // Check if the error is because the index already exists if (str_contains($e->getMessage(), 'already exists')) { $this->warn("Index {$indexName} already exists. Use --drop to recreate."); + return true; } // Check for data_dir configuration error if (str_contains($e->getMessage(), 'data_dir')) { - $this->error("Failed to create index {$indexName}: " . $e->getMessage()); + $this->error("Failed to create index {$indexName}: ".$e->getMessage()); $this->newLine(); $this->warn('ManticoreSearch requires data_dir to be set in its configuration file.'); $this->info('To fix this, edit your ManticoreSearch config file (usually /etc/manticoresearch/manticore.conf):'); $this->line(' 1. Add or uncomment: data_dir = /var/lib/manticore'); $this->line(' 2. Ensure the directory exists and is writable by the manticore user'); $this->line(' 3. Restart ManticoreSearch: sudo systemctl restart manticore'); + return false; } - $this->error("Failed to create index {$indexName}: " . $e->getMessage()); + $this->error("Failed to create index {$indexName}: ".$e->getMessage()); + return false; } catch (\Throwable $e) { - $this->error("Unexpected error creating index {$indexName}: " . $e->getMessage()); + $this->error("Unexpected error creating index {$indexName}: ".$e->getMessage()); + return false; } } @@ -266,7 +274,7 @@ class CreateMediaIndexes extends Command $esClient->indices()->delete(['index' => $moviesIndex]); } - if (!$esClient->indices()->exists(['index' => $moviesIndex])) { + if (! $esClient->indices()->exists(['index' => $moviesIndex])) { $this->info("Creating index: {$moviesIndex}"); $esClient->indices()->create([ 'index' => $moviesIndex, @@ -284,7 +292,7 @@ class CreateMediaIndexes extends Command $esClient->indices()->delete(['index' => $tvshowsIndex]); } - if (!$esClient->indices()->exists(['index' => $tvshowsIndex])) { + if (! $esClient->indices()->exists(['index' => $tvshowsIndex])) { $this->info("Creating index: {$tvshowsIndex}"); $esClient->indices()->create([ 'index' => $tvshowsIndex, @@ -313,9 +321,9 @@ class CreateMediaIndexes extends Command return Command::SUCCESS; } catch (\Throwable $e) { - $this->error('Failed to create Elasticsearch indexes: ' . $e->getMessage()); + $this->error('Failed to create Elasticsearch indexes: '.$e->getMessage()); + return Command::FAILURE; } } } - diff --git a/app/Console/Commands/DisableExpiredPromotions.php b/app/Console/Commands/DisableExpiredPromotions.php index efd17f39d..4a79ee19f 100644 --- a/app/Console/Commands/DisableExpiredPromotions.php +++ b/app/Console/Commands/DisableExpiredPromotions.php @@ -37,6 +37,7 @@ class DisableExpiredPromotions extends Command if ($expiredPromotions->isEmpty()) { $this->info('No expired promotions found.'); + return Command::SUCCESS; } @@ -52,4 +53,3 @@ class DisableExpiredPromotions extends Command return Command::SUCCESS; } } - diff --git a/app/Console/Commands/GetArticleRange.php b/app/Console/Commands/GetArticleRange.php index bcb0d2646..80d446ad3 100644 --- a/app/Console/Commands/GetArticleRange.php +++ b/app/Console/Commands/GetArticleRange.php @@ -61,7 +61,7 @@ class GetArticleRange extends Command return self::FAILURE; } - $binaries = new BinariesService(); + $binaries = new BinariesService; $binaries->setNntp($nntp); $return = $binaries->scan( $groupMySQL, diff --git a/app/Console/Commands/ManageRoleStacking.php b/app/Console/Commands/ManageRoleStacking.php index 2badd1cbf..973f46173 100644 --- a/app/Console/Commands/ManageRoleStacking.php +++ b/app/Console/Commands/ManageRoleStacking.php @@ -34,7 +34,7 @@ class ManageRoleStacking extends Command { $action = $this->argument('action'); - return match($action) { + return match ($action) { 'list-pending' => $this->listPendingRoles(), 'cancel-pending' => $this->cancelPendingRole(), 'history' => $this->showHistory(), @@ -49,6 +49,7 @@ class ManageRoleStacking extends Command protected function handleUnknownAction(string $action): int { $this->error("Unknown action: {$action}"); + return 1; } @@ -63,6 +64,7 @@ class ManageRoleStacking extends Command if ($users->isEmpty()) { $this->info('No users with pending roles found.'); + return 0; } @@ -99,8 +101,9 @@ class ManageRoleStacking extends Command protected function cancelPendingRole(): int { if ($this->option('all')) { - if (!$this->confirm('Are you sure you want to cancel ALL pending roles?')) { + if (! $this->confirm('Are you sure you want to cancel ALL pending roles?')) { $this->info('Operation cancelled.'); + return 0; } @@ -115,37 +118,43 @@ class ManageRoleStacking extends Command } $this->info("Cancelled pending roles for {$count} users."); + return 0; } $userId = $this->argument('user'); - if (!$userId) { + if (! $userId) { $this->error('User ID or username required. Use --all to cancel for all users.'); + return 1; } $user = $this->findUser($userId); - if (!$user) { + if (! $user) { return 1; } - if (!$user->hasPendingRole()) { + if (! $user->hasPendingRole()) { $this->info("User {$user->username} has no pending role."); + return 0; } $pendingRole = $user->getPendingRole(); - if (!$this->confirm("Cancel pending role '{$pendingRole->name}' for user '{$user->username}'?")) { + if (! $this->confirm("Cancel pending role '{$pendingRole->name}' for user '{$user->username}'?")) { $this->info('Operation cancelled.'); + return 0; } if ($user->cancelPendingRole()) { $this->info("Successfully cancelled pending role for user: {$user->username}"); + return 0; } - $this->error("Failed to cancel pending role."); + $this->error('Failed to cancel pending role.'); + return 1; } @@ -155,13 +164,14 @@ class ManageRoleStacking extends Command protected function showHistory(): int { $userId = $this->argument('user'); - if (!$userId) { + if (! $userId) { $this->error('User ID or username required.'); + return 1; } $user = $this->findUser($userId); - if (!$user) { + if (! $user) { return 1; } @@ -169,6 +179,7 @@ class ManageRoleStacking extends Command if ($history->isEmpty()) { $this->info("No role change history found for user: {$user->username}"); + return 0; } @@ -215,8 +226,9 @@ class ManageRoleStacking extends Command $this->warn('Normally, this is done automatically by the scheduled task.'); $this->newLine(); - if (!$this->confirm('Do you want to continue?')) { + if (! $this->confirm('Do you want to continue?')) { $this->info('Operation cancelled.'); + return 0; } @@ -228,6 +240,7 @@ class ManageRoleStacking extends Command if ($users->isEmpty()) { $this->info('No pending roles ready for activation.'); + return 0; } @@ -285,7 +298,7 @@ class ManageRoleStacking extends Command } $this->newLine(); - $this->info("Activation complete:"); + $this->info('Activation complete:'); $this->info(" Successful: {$activated}"); if ($failed > 0) { $this->error(" Failed: {$failed}"); @@ -303,12 +316,12 @@ class ManageRoleStacking extends Command ? User::find($identifier) : User::where('username', $identifier)->first(); - if (!$user) { + if (! $user) { $this->error("User not found: {$identifier}"); + return null; } return $user; } } - diff --git a/app/Console/Commands/MigrateAnimeCovers.php b/app/Console/Commands/MigrateAnimeCovers.php index 8f08459e8..bdb97e1b8 100644 --- a/app/Console/Commands/MigrateAnimeCovers.php +++ b/app/Console/Commands/MigrateAnimeCovers.php @@ -29,30 +29,32 @@ class MigrateAnimeCovers extends Command $animeCoverPath = storage_path('covers/anime/'); $dryRun = $this->option('dry-run'); - if (!is_dir($animeCoverPath)) { + if (! is_dir($animeCoverPath)) { $this->error("Anime covers directory does not exist: {$animeCoverPath}"); + return 1; } $this->info("Scanning anime covers directory: {$animeCoverPath}"); - + $files = File::files($animeCoverPath); $renamed = 0; $skipped = 0; foreach ($files as $file) { $filename = $file->getFilename(); - + // Check if file matches old format: {id}.jpg (where id is numeric) if (preg_match('/^(\d+)\.jpg$/', $filename, $matches)) { $anidbid = $matches[1]; $newFilename = "{$anidbid}-cover.jpg"; - $newPath = $animeCoverPath . $newFilename; + $newPath = $animeCoverPath.$newFilename; // Skip if new format already exists if (file_exists($newPath)) { $this->warn("Skipping {$filename} - {$newFilename} already exists"); $skipped++; + continue; } @@ -72,7 +74,7 @@ class MigrateAnimeCovers extends Command if ($dryRun) { $this->info("\nDry run complete. Would rename {$renamed} file(s), skipped {$skipped}."); - $this->info("Run without --dry-run to perform the migration."); + $this->info('Run without --dry-run to perform the migration.'); } else { $this->info("\nMigration complete. Renamed {$renamed} file(s), skipped {$skipped}."); } @@ -80,4 +82,3 @@ class MigrateAnimeCovers extends Command return 0; } } - diff --git a/app/Console/Commands/NntmuxOffsetPopulate.php b/app/Console/Commands/NntmuxOffsetPopulate.php index d0ab8f274..d9a1252ed 100644 --- a/app/Console/Commands/NntmuxOffsetPopulate.php +++ b/app/Console/Commands/NntmuxOffsetPopulate.php @@ -7,7 +7,6 @@ use App\Models\Predb; use App\Models\Release; use Exception; use Illuminate\Console\Command; -use Illuminate\Support\Arr; use Illuminate\Support\Facades\Process; class NntmuxOffsetPopulate extends Command diff --git a/app/Console/Commands/NntmuxOffsetWorker.php b/app/Console/Commands/NntmuxOffsetWorker.php index 6c15f238a..ed5494778 100644 --- a/app/Console/Commands/NntmuxOffsetWorker.php +++ b/app/Console/Commands/NntmuxOffsetWorker.php @@ -94,7 +94,7 @@ class NntmuxOffsetWorker extends Command if ($engine === 'manticore') { $batchData = []; - $query->chunk($batchSize, function ($items) use ($indexName, $transformer, &$processed, &$errors, &$batchData, $batchSize, $workerId) { + $query->chunk($batchSize, function ($items) use ($transformer, &$processed, &$errors, &$batchData, $batchSize, $workerId) { $this->info("Worker {$workerId}: Processing chunk of {$items->count()} items"); foreach ($items as $item) { diff --git a/app/Console/Commands/NntmuxPopulateAniDB.php b/app/Console/Commands/NntmuxPopulateAniDB.php index 63f3067b8..ad75ffaf6 100644 --- a/app/Console/Commands/NntmuxPopulateAniDB.php +++ b/app/Console/Commands/NntmuxPopulateAniDB.php @@ -36,6 +36,7 @@ class NntmuxPopulateAniDB extends Command $palist->populateTable('info'); } else { $this->error('Please specify --info option'); + return; } diff --git a/app/Console/Commands/NntmuxPopulateSearchIndexes.php b/app/Console/Commands/NntmuxPopulateSearchIndexes.php index ea60768c8..90773d77c 100644 --- a/app/Console/Commands/NntmuxPopulateSearchIndexes.php +++ b/app/Console/Commands/NntmuxPopulateSearchIndexes.php @@ -9,7 +9,6 @@ use App\Models\Release; use App\Models\Video; use Exception; use Illuminate\Console\Command; -use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; class NntmuxPopulateSearchIndexes extends Command diff --git a/app/Console/Commands/PartRepair.php b/app/Console/Commands/PartRepair.php index b2daf2118..bb60d96d1 100644 --- a/app/Console/Commands/PartRepair.php +++ b/app/Console/Commands/PartRepair.php @@ -48,7 +48,7 @@ class PartRepair extends Command return self::FAILURE; } - $binaries = new BinariesService(); + $binaries = new BinariesService; $binaries->setNntp($nntp); $binaries->partRepair($groupMySQL); diff --git a/app/Console/Commands/PostProcessGuid.php b/app/Console/Commands/PostProcessGuid.php index 1e721a808..1407bb051 100644 --- a/app/Console/Commands/PostProcessGuid.php +++ b/app/Console/Commands/PostProcessGuid.php @@ -7,8 +7,8 @@ namespace App\Console\Commands; use App\Models\Settings; use App\Services\AdditionalProcessing\AdditionalProcessingOrchestrator; use App\Services\NfoService; -use App\Services\PostProcessService; use App\Services\NNTP\NNTPService; +use App\Services\PostProcessService; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; @@ -47,7 +47,7 @@ class PostProcessGuid extends Command $guid = $this->argument('guid'); $renamed = $this->argument('renamed') ?? ''; - if (!$this->isValidChar($guid)) { + if (! $this->isValidChar($guid)) { $this->error('GUID character must be a-f or 0-9.'); return self::FAILURE; @@ -89,7 +89,7 @@ class PostProcessGuid extends Command private function processNfo(string $guid): void { $nntp = $this->getNntp(); - (new NfoService())->processNfoFiles( + (new NfoService)->processNfoFiles( $nntp, '', $guid, @@ -115,7 +115,7 @@ class PostProcessGuid extends Command */ private function getNntp(): NNTPService { - $nntp = new NNTPService(); + $nntp = new NNTPService; $connectResult = config('nntmux_nntp.use_alternate_nntp_server') === true ? $nntp->doConnect(false, true) diff --git a/app/Console/Commands/ProcessAdultMovies.php b/app/Console/Commands/ProcessAdultMovies.php index fa9b32958..169da9c76 100644 --- a/app/Console/Commands/ProcessAdultMovies.php +++ b/app/Console/Commands/ProcessAdultMovies.php @@ -32,6 +32,7 @@ class ProcessAdultMovies extends Command { if ((int) Settings::settingValue('lookupxxx') !== 1) { $this->error('Adult movie lookup is disabled in settings. Enable "lookupxxx" to use this command.'); + return Command::FAILURE; } @@ -54,12 +55,12 @@ class ProcessAdultMovies extends Command $this->line("Title: {$title_display}"); $this->line("Synopsis: {$synopsis_display}..."); - if (!empty($result['movieData']['boxcover'])) { + if (! empty($result['movieData']['boxcover'])) { $cover_url = $result['movieData']['boxcover']; $this->line("Cover: {$cover_url}"); } - if ($debug && !empty($result['debug'])) { + if ($debug && ! empty($result['debug'])) { $this->newLine(); $this->line('Debug Info:'); $this->line(json_encode($result['debug'], JSON_PRETTY_PRINT)); @@ -67,7 +68,7 @@ class ProcessAdultMovies extends Command } else { $this->warn("No match found for: {$title}"); - if ($debug && !empty($result['debug'])) { + if ($debug && ! empty($result['debug'])) { $this->newLine(); $this->line('Debug Info:'); $this->line(json_encode($result['debug'], JSON_PRETTY_PRINT)); @@ -97,7 +98,7 @@ class ProcessAdultMovies extends Command ] ); - if (!empty($stats['providers'])) { + if (! empty($stats['providers'])) { $this->newLine(); $this->info('Provider Statistics:'); $providerData = []; @@ -111,4 +112,3 @@ class ProcessAdultMovies extends Command return Command::SUCCESS; } } - diff --git a/app/Console/Commands/RecategorizeReleases.php b/app/Console/Commands/RecategorizeReleases.php index dea243243..6f5345026 100644 --- a/app/Console/Commands/RecategorizeReleases.php +++ b/app/Console/Commands/RecategorizeReleases.php @@ -66,7 +66,7 @@ class RecategorizeReleases extends Command $count = $countQuery->count(); - $cat = new CategorizationService(); + $cat = new CategorizationService; $results = $countQuery->select(['id', 'searchname', 'fromname', 'groups_id', 'categories_id'])->get(); $bar = $this->output->createProgressBar($count); $bar->start(); diff --git a/app/Console/Commands/RedisMonitor.php b/app/Console/Commands/RedisMonitor.php index cf1023493..3b2137efb 100644 --- a/app/Console/Commands/RedisMonitor.php +++ b/app/Console/Commands/RedisMonitor.php @@ -39,12 +39,14 @@ class RedisMonitor extends Command if ($once) { $stats = $service->getStats(); - if (!$stats['connected']) { + if (! $stats['connected']) { $this->error("Cannot connect to Redis at {$stats['host']}:{$stats['port']}"); + return Command::FAILURE; } $this->displayStats($stats); + return Command::SUCCESS; } @@ -54,7 +56,8 @@ class RedisMonitor extends Command return Command::SUCCESS; } catch (\Exception $e) { - $this->error('Redis Monitor failed: ' . $e->getMessage()); + $this->error('Redis Monitor failed: '.$e->getMessage()); + return Command::FAILURE; } } @@ -107,9 +110,9 @@ class RedisMonitor extends Command ['Metric', 'Value'], [ ['Commands Processed', number_format($stats['total_commands'])], - ['Operations/sec', number_format($stats['ops_per_sec']) . ' ops/s'], - ['Network Input', $stats['input_kbps'] . ' KB/s'], - ['Network Output', $stats['output_kbps'] . ' KB/s'], + ['Operations/sec', number_format($stats['ops_per_sec']).' ops/s'], + ['Network Input', $stats['input_kbps'].' KB/s'], + ['Network Output', $stats['output_kbps'].' KB/s'], ] ); @@ -121,7 +124,7 @@ class RedisMonitor extends Command ['Total Keys (db0)', number_format($stats['db_keys'])], ['Keyspace Hits', number_format($stats['keyspace_hits'])], ['Keyspace Misses', number_format($stats['keyspace_misses'])], - ['Hit Rate', $stats['hit_rate'] . '%'], + ['Hit Rate', $stats['hit_rate'].'%'], ['Expired Keys', number_format($stats['expired_keys'])], ['Evicted Keys', number_format($stats['evicted_keys'])], ] @@ -134,13 +137,13 @@ class RedisMonitor extends Command protected function formatBytes(int $bytes): string { if ($bytes >= 1073741824) { - return sprintf("%.2f GB", $bytes / 1073741824); + return sprintf('%.2f GB', $bytes / 1073741824); } elseif ($bytes >= 1048576) { - return sprintf("%.2f MB", $bytes / 1048576); + return sprintf('%.2f MB', $bytes / 1048576); } elseif ($bytes >= 1024) { - return sprintf("%.2f KB", $bytes / 1024); + return sprintf('%.2f KB', $bytes / 1024); } - return $bytes . " B"; + + return $bytes.' B'; } } - diff --git a/app/Console/Commands/RefreshAnimeData.php b/app/Console/Commands/RefreshAnimeData.php index 17253ad75..2733fdd3d 100644 --- a/app/Console/Commands/RefreshAnimeData.php +++ b/app/Console/Commands/RefreshAnimeData.php @@ -89,6 +89,7 @@ class RefreshAnimeData extends Command if ($totalCount === 0) { $this->warn('No anime releases found to process.'); + return self::SUCCESS; } @@ -120,7 +121,7 @@ class RefreshAnimeData extends Command foreach ($chunks as $chunk) { foreach ($chunk as $release) { $searchname = $release->searchname ?? ''; - $progressBar->setMessage("Processing: " . substr($searchname, 0, 50) . "..."); + $progressBar->setMessage('Processing: '.substr($searchname, 0, 50).'...'); try { // Extract clean title from searchname @@ -139,6 +140,7 @@ class RefreshAnimeData extends Command } $processed++; $progressBar->advance(); + continue; } @@ -160,6 +162,7 @@ class RefreshAnimeData extends Command $skipped++; $processed++; $progressBar->advance(); + continue; } } @@ -187,12 +190,13 @@ class RefreshAnimeData extends Command ]; if ($this->getOutput()->isVerbose()) { $this->newLine(); - $this->warn("No AniList match found for:"); + $this->warn('No AniList match found for:'); $this->line(" Searchname: {$searchname}"); $this->line(" Cleaned title: {$cleanTitle}"); } $processed++; $progressBar->advance(); + continue; } @@ -208,12 +212,13 @@ class RefreshAnimeData extends Command ]; if ($this->getOutput()->isVerbose()) { $this->newLine(); - $this->warn("AniList search returned result but no ID for:"); + $this->warn('AniList search returned result but no ID for:'); $this->line(" Searchname: {$searchname}"); $this->line(" Cleaned title: {$cleanTitle}"); } $processed++; $progressBar->advance(); + continue; } @@ -261,17 +266,17 @@ class RefreshAnimeData extends Command ); // Show failed searchnames if any - if (!empty($failedSearchnames)) { + if (! empty($failedSearchnames)) { $this->newLine(); - $this->warn("Failed searchnames (before rate limit error):"); - $this->line("Showing up to 10 examples:"); + $this->warn('Failed searchnames (before rate limit error):'); + $this->line('Showing up to 10 examples:'); $examples = array_slice($failedSearchnames, 0, 10); foreach ($examples as $item) { $cleanedTitle = $item['cleaned_title'] ?? '(extraction failed)'; $this->line(" - {$item['searchname']} -> {$cleanedTitle} ({$item['reason']})"); } if (count($failedSearchnames) > 10) { - $this->line(" ... and " . (count($failedSearchnames) - 10) . " more."); + $this->line(' ... and '.(count($failedSearchnames) - 10).' more.'); } } @@ -281,7 +286,7 @@ class RefreshAnimeData extends Command $failed++; if ($this->getOutput()->isVerbose()) { $this->newLine(); - $this->error("Error processing release ID {$release->id}: " . $e->getMessage()); + $this->error("Error processing release ID {$release->id}: ".$e->getMessage()); } } @@ -308,7 +313,7 @@ class RefreshAnimeData extends Command ); // Show failed searchnames if any - if (!empty($failedSearchnames) && $notFound > 0) { + if (! empty($failedSearchnames) && $notFound > 0) { $this->newLine(); $this->warn("Failed to fetch data for {$notFound} release(s):"); $this->newLine(); @@ -319,8 +324,8 @@ class RefreshAnimeData extends Command foreach ($examples as $item) { $cleanedTitle = $item['cleaned_title'] ?? '(extraction failed)'; $rows[] = [ - substr($item['searchname'], 0, 60) . (strlen($item['searchname']) > 60 ? '...' : ''), - substr($cleanedTitle, 0, 40) . (strlen($cleanedTitle) > 40 ? '...' : ''), + substr($item['searchname'], 0, 60).(strlen($item['searchname']) > 60 ? '...' : ''), + substr($cleanedTitle, 0, 40).(strlen($cleanedTitle) > 40 ? '...' : ''), $item['reason'], ]; } @@ -331,7 +336,7 @@ class RefreshAnimeData extends Command ); if (count($failedSearchnames) > 20) { - $this->line("... and " . (count($failedSearchnames) - 20) . " more. Use --verbose to see all."); + $this->line('... and '.(count($failedSearchnames) - 20).' more. Use --verbose to see all.'); } } @@ -428,7 +433,7 @@ class RefreshAnimeData extends Command if (preg_match('/Ã[^\s]/u', $text)) { // Try ISO-8859-1 -> UTF-8 conversion (common double-encoding fix) $converted = @mb_convert_encoding($text, 'UTF-8', 'ISO-8859-1'); - if ($converted !== false && !preg_match('/Ã[^\s]/u', $converted)) { + if ($converted !== false && ! preg_match('/Ã[^\s]/u', $converted)) { $text = $converted; } } @@ -530,7 +535,7 @@ class RefreshAnimeData extends Command if ($waitTime > 0 && $waitTime <= 60) { if ($this->getOutput()->isVerbose()) { $this->newLine(); - $this->warn("Rate limit reached ({$requestCount}/" . self::RATE_LIMIT_PER_MINUTE . "). Waiting {$waitTime} seconds..."); + $this->warn("Rate limit reached ({$requestCount}/".self::RATE_LIMIT_PER_MINUTE."). Waiting {$waitTime} seconds..."); } sleep($waitTime); @@ -565,4 +570,3 @@ class RefreshAnimeData extends Command $this->requestTimestamps[] = $now; } } - diff --git a/app/Console/Commands/ReleasesFixNamesGroup.php b/app/Console/Commands/ReleasesFixNamesGroup.php index 3911d6412..16a80c669 100644 --- a/app/Console/Commands/ReleasesFixNamesGroup.php +++ b/app/Console/Commands/ReleasesFixNamesGroup.php @@ -9,9 +9,8 @@ use App\Models\Predb; use App\Models\Release; use App\Services\NameFixing\NameFixingService; use App\Services\NfoService; -use App\Services\Nzb\NzbContentsService; -use App\Services\PostProcessService; use App\Services\NNTP\NNTPService; +use App\Services\Nzb\NzbContentsService; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; @@ -110,7 +109,7 @@ class ReleasesFixNamesGroup extends Command $this->nameFixingService->checkName($release, true, 'UID, ', true, true); } - if (!$this->nameFixingService->getUpdateService()->matched && ! empty($release->mediainfo)) { + if (! $this->nameFixingService->getUpdateService()->matched && ! empty($release->mediainfo)) { $this->nameFixingService->checkName($release, true, 'Mediainfo, ', true, true); } } @@ -191,7 +190,7 @@ class ReleasesFixNamesGroup extends Command if (is_array($fileNames)) { $releaseFile = $release; foreach ($fileNames as $fileName) { - if (!$this->nameFixingService->getUpdateService()->matched) { + if (! $this->nameFixingService->getUpdateService()->matched) { $releaseFile->textstring = $fileName; $this->nameFixingService->checkName($releaseFile, true, 'Filenames, ', true, true); } @@ -211,7 +210,7 @@ class ReleasesFixNamesGroup extends Command if ((int) $release->proc_par2 === NameFixingService::PROC_PAR2_NONE) { // Initialize NZB contents if needed if (! isset($nzbcontents)) { - $nntp = new NNTPService(); + $nntp = new NNTPService; $compressedHeaders = config('nntmux_nntp.compressed_headers'); $connectResult = config('nntmux_nntp.use_alternate_nntp_server') === true @@ -225,7 +224,7 @@ class ReleasesFixNamesGroup extends Command } $this->warn($errorMessage); } else { - $Nfo = new NfoService(); + $Nfo = new NfoService; $nzbcontents = app(NzbContentsService::class); $nzbcontents->setNntp($nntp); $nzbcontents->setNfo($Nfo); diff --git a/app/Console/Commands/ReprocessMissingEpisodes.php b/app/Console/Commands/ReprocessMissingEpisodes.php index 5a32f2bdf..e7d2aca65 100644 --- a/app/Console/Commands/ReprocessMissingEpisodes.php +++ b/app/Console/Commands/ReprocessMissingEpisodes.php @@ -67,6 +67,7 @@ class ReprocessMissingEpisodes extends Command if ($totalCount === 0) { $this->info('No TV releases with missing episode matches found.'); + return self::SUCCESS; } @@ -91,7 +92,7 @@ class ReprocessMissingEpisodes extends Command $rows = $sample->map(fn ($release) => [ $release->id, $release->videos_id, - mb_substr($release->searchname, 0, 55) . (strlen($release->searchname) > 55 ? '...' : ''), + mb_substr($release->searchname, 0, 55).(strlen($release->searchname) > 55 ? '...' : ''), ])->toArray(); $this->table(['ID', 'Video ID', 'Search Name'], $rows); } @@ -134,9 +135,9 @@ class ReprocessMissingEpisodes extends Command if ($debug) { $this->newLine(); cli()->primary("Episode matched: {$release->searchname}"); - $this->info(' Provider: ' . ($result['provider'] ?? 'Unknown')); - $this->info(' Video ID: ' . ($result['video_id'] ?? 'N/A')); - $this->info(' Episode ID: ' . ($result['episode_id'] ?? 'N/A')); + $this->info(' Provider: '.($result['provider'] ?? 'Unknown')); + $this->info(' Video ID: '.($result['video_id'] ?? 'N/A')); + $this->info(' Episode ID: '.($result['episode_id'] ?? 'N/A')); } } else { $failed++; @@ -147,10 +148,10 @@ class ReprocessMissingEpisodes extends Command } } catch (\Throwable $e) { $failed++; - Log::error("Error processing release {$release->guid}: " . $e->getMessage()); + Log::error("Error processing release {$release->guid}: ".$e->getMessage()); if ($debug) { $this->newLine(); - $this->error("Error processing {$release->searchname}: " . $e->getMessage()); + $this->error("Error processing {$release->searchname}: ".$e->getMessage()); } } @@ -167,8 +168,9 @@ class ReprocessMissingEpisodes extends Command } catch (\Throwable $e) { $bar->finish(); $this->newLine(); - $this->error('Fatal error during processing: ' . $e->getMessage()); - Log::error('Fatal error in tv:reprocess-missing-episodes: ' . $e->getMessage() . "\n" . $e->getTraceAsString()); + $this->error('Fatal error during processing: '.$e->getMessage()); + Log::error('Fatal error in tv:reprocess-missing-episodes: '.$e->getMessage()."\n".$e->getTraceAsString()); + return self::FAILURE; } @@ -182,11 +184,10 @@ class ReprocessMissingEpisodes extends Command number_format($processed), number_format($matched), number_format($failed), - $processed > 0 ? round(($matched / $processed) * 100, 2) . '%' : '0%', + $processed > 0 ? round(($matched / $processed) * 100, 2).'%' : '0%', ]] ); return self::SUCCESS; } } - diff --git a/app/Console/Commands/ReprocessTvRelease.php b/app/Console/Commands/ReprocessTvRelease.php index da6f6a759..8d380be30 100644 --- a/app/Console/Commands/ReprocessTvRelease.php +++ b/app/Console/Commands/ReprocessTvRelease.php @@ -45,6 +45,7 @@ class ReprocessTvRelease extends Command if ($release === null) { $this->error("Release with GUID '{$guid}' not found."); + return self::FAILURE; } @@ -54,7 +55,7 @@ class ReprocessTvRelease extends Command [[ $release->id, $release->guid, - mb_substr($release->searchname, 0, 60) . (strlen($release->searchname) > 60 ? '...' : ''), + mb_substr($release->searchname, 0, 60).(strlen($release->searchname) > 60 ? '...' : ''), $release->videos_id, $release->tv_episodes_id, $release->categories_id, @@ -81,14 +82,14 @@ class ReprocessTvRelease extends Command if ($result['matched']) { cli()->primary('Successfully matched!'); - $this->info('Provider: ' . ($result['provider'] ?? 'Unknown')); - $this->info('Video ID: ' . ($result['video_id'] ?? 'N/A')); - $this->info('Episode ID: ' . ($result['episode_id'] ?? 'N/A')); + $this->info('Provider: '.($result['provider'] ?? 'Unknown')); + $this->info('Video ID: '.($result['video_id'] ?? 'N/A')); + $this->info('Episode ID: '.($result['episode_id'] ?? 'N/A')); } else { cli()->warning('No match found.'); - $this->info('Status: ' . ($result['status'] ?? 'Unknown')); + $this->info('Status: '.($result['status'] ?? 'Unknown')); if (isset($result['provider'])) { - $this->info('Last provider tried: ' . $result['provider']); + $this->info('Last provider tried: '.$result['provider']); } } @@ -110,10 +111,11 @@ class ReprocessTvRelease extends Command return self::SUCCESS; } catch (\Throwable $e) { Log::error($e->getTraceAsString()); - $this->error('Error processing release: ' . $e->getMessage()); + $this->error('Error processing release: '.$e->getMessage()); if ($debug) { $this->error($e->getTraceAsString()); } + return self::FAILURE; } } diff --git a/app/Console/Commands/ReprocessUnmatchedTvReleases.php b/app/Console/Commands/ReprocessUnmatchedTvReleases.php index ab7461244..29c0b75fa 100644 --- a/app/Console/Commands/ReprocessUnmatchedTvReleases.php +++ b/app/Console/Commands/ReprocessUnmatchedTvReleases.php @@ -55,6 +55,7 @@ class ReprocessUnmatchedTvReleases extends Command if ($totalCount === 0) { $this->info('No unmatched TV releases found.'); + return self::SUCCESS; } @@ -79,7 +80,7 @@ class ReprocessUnmatchedTvReleases extends Command $rows = $sample->map(fn ($release) => [ $release->id, $release->guid, - mb_substr($release->searchname, 0, 60) . (strlen($release->searchname) > 60 ? '...' : ''), + mb_substr($release->searchname, 0, 60).(strlen($release->searchname) > 60 ? '...' : ''), $release->categories_id, ])->toArray(); $this->table(['ID', 'GUID', 'Search Name', 'Category'], $rows); @@ -120,9 +121,9 @@ class ReprocessUnmatchedTvReleases extends Command if ($debug) { $this->newLine(); cli()->primary("Matched: {$release->searchname}"); - $this->info(' Provider: ' . ($result['provider'] ?? 'Unknown')); - $this->info(' Video ID: ' . ($result['video_id'] ?? 'N/A')); - $this->info(' Episode ID: ' . ($result['episode_id'] ?? 'N/A')); + $this->info(' Provider: '.($result['provider'] ?? 'Unknown')); + $this->info(' Video ID: '.($result['video_id'] ?? 'N/A')); + $this->info(' Episode ID: '.($result['episode_id'] ?? 'N/A')); } } else { $failed++; @@ -133,10 +134,10 @@ class ReprocessUnmatchedTvReleases extends Command } } catch (\Throwable $e) { $failed++; - Log::error("Error processing release {$release->guid}: " . $e->getMessage()); + Log::error("Error processing release {$release->guid}: ".$e->getMessage()); if ($debug) { $this->newLine(); - $this->error("Error processing {$release->searchname}: " . $e->getMessage()); + $this->error("Error processing {$release->searchname}: ".$e->getMessage()); } } @@ -150,12 +151,12 @@ class ReprocessUnmatchedTvReleases extends Command } } - } catch (\Throwable $e) { $bar->finish(); $this->newLine(); - $this->error('Fatal error during processing: ' . $e->getMessage()); - Log::error('Fatal error in tv:reprocess-unmatched: ' . $e->getMessage() . "\n" . $e->getTraceAsString()); + $this->error('Fatal error during processing: '.$e->getMessage()); + Log::error('Fatal error in tv:reprocess-unmatched: '.$e->getMessage()."\n".$e->getTraceAsString()); + return self::FAILURE; } @@ -169,11 +170,10 @@ class ReprocessUnmatchedTvReleases extends Command number_format($processed), number_format($matched), number_format($failed), - $processed > 0 ? round(($matched / $processed) * 100, 2) . '%' : '0%', + $processed > 0 ? round(($matched / $processed) * 100, 2).'%' : '0%', ]] ); return self::SUCCESS; } } - diff --git a/app/Console/Commands/SteamLookupGame.php b/app/Console/Commands/SteamLookupGame.php index d0264aad6..a9600dfea 100644 --- a/app/Console/Commands/SteamLookupGame.php +++ b/app/Console/Commands/SteamLookupGame.php @@ -43,6 +43,7 @@ class SteamLookupGame extends Command if ($appId === null) { $this->error('No matching game found.'); + return Command::FAILURE; } @@ -54,6 +55,7 @@ class SteamLookupGame extends Command if ($details === false) { $this->error('Could not retrieve game details.'); + return Command::FAILURE; } @@ -82,7 +84,7 @@ class SteamLookupGame extends Command } if ($playerCount !== null) { - $this->info("Current Players: " . number_format($playerCount)); + $this->info('Current Players: '.number_format($playerCount)); } } @@ -112,13 +114,13 @@ class SteamLookupGame extends Command $this->table(['Property', 'Value'], $basicInfo); // Scores - if (!empty($details['metacritic_score'])) { + if (! empty($details['metacritic_score'])) { $this->newLine(); $this->info("Metacritic Score: {$details['metacritic_score']}"); } // Price - if (!empty($details['price'])) { + if (! empty($details['price'])) { $price = $details['price']; $this->newLine(); $this->info('Pricing:'); @@ -137,7 +139,7 @@ class SteamLookupGame extends Command } // Description - if (!empty($details['description'])) { + if (! empty($details['description'])) { $this->newLine(); $this->info('Description:'); $this->line(wordwrap($details['description'], 80)); @@ -148,34 +150,33 @@ class SteamLookupGame extends Command $this->info('Links:'); $this->line("Store: {$details['directurl']}"); - if (!empty($details['website'])) { + if (! empty($details['website'])) { $this->line("Website: {$details['website']}"); } - if (!empty($details['metacritic_url'])) { + if (! empty($details['metacritic_url'])) { $this->line("Metacritic: {$details['metacritic_url']}"); } // Stats - if (!empty($details['achievements']) || !empty($details['recommendations'])) { + if (! empty($details['achievements']) || ! empty($details['recommendations'])) { $this->newLine(); $this->info('Stats:'); - if (!empty($details['achievements'])) { + if (! empty($details['achievements'])) { $this->line("Achievements: {$details['achievements']}"); } - if (!empty($details['recommendations'])) { - $this->line("Recommendations: " . number_format($details['recommendations'])); + if (! empty($details['recommendations'])) { + $this->line('Recommendations: '.number_format($details['recommendations'])); } } // Categories (multiplayer, etc.) - if (!empty($details['categories'])) { + if (! empty($details['categories'])) { $this->newLine(); $this->info('Features:'); $this->line(implode(', ', $details['categories'])); } } } - diff --git a/app/Console/Commands/SteamSearchGames.php b/app/Console/Commands/SteamSearchGames.php index ac629bf27..51e37d0d7 100644 --- a/app/Console/Commands/SteamSearchGames.php +++ b/app/Console/Commands/SteamSearchGames.php @@ -4,14 +4,12 @@ declare(strict_types=1); namespace App\Console\Commands; -use App\Models\SteamApp; use App\Services\SteamService; use Illuminate\Console\Command; -use function Laravel\Prompts\progress; use function Laravel\Prompts\info; -use function Laravel\Prompts\warning; use function Laravel\Prompts\table; +use function Laravel\Prompts\warning; class SteamSearchGames extends Command { @@ -45,16 +43,17 @@ class SteamSearchGames extends Command if ($results->isEmpty()) { warning('No games found matching your query.'); + return Command::SUCCESS; } info("Found {$results->count()} result(s):"); - $tableData = $results->map(fn($r) => [ + $tableData = $results->map(fn ($r) => [ $r['appid'], substr($r['name'], 0, 60), - number_format($r['score'], 1) . '%', - 'https://store.steampowered.com/app/' . $r['appid'], + number_format($r['score'], 1).'%', + 'https://store.steampowered.com/app/'.$r['appid'], ])->toArray(); table( @@ -65,4 +64,3 @@ class SteamSearchGames extends Command return Command::SUCCESS; } } - diff --git a/app/Console/Commands/TestCategorization.php b/app/Console/Commands/TestCategorization.php index f24c183a5..d51dedba9 100644 --- a/app/Console/Commands/TestCategorization.php +++ b/app/Console/Commands/TestCategorization.php @@ -17,18 +17,20 @@ class TestCategorization extends Command public function handle(): int { - $service = new CategorizationService(); + $service = new CategorizationService; if ($this->option('list-categorizers')) { $this->listCategorizers($service); + return 0; } $releaseName = $this->option('release'); - if (!$releaseName) { + if (! $releaseName) { // Test with sample releases $this->testSampleReleases($service); + return 0; } @@ -65,7 +67,7 @@ class TestCategorization extends Command if (isset($result['debug'])) { $this->info("Matched By: {$result['debug']['matched_by']}"); - $this->info("Confidence: " . ($result['debug']['final_confidence'] ?? 'N/A')); + $this->info('Confidence: '.($result['debug']['final_confidence'] ?? 'N/A')); } } @@ -126,7 +128,7 @@ class TestCategorization extends Command foreach ($samples as $sample) { $comparison = $service->compare(0, $sample); $results[] = [ - substr($sample, 0, 50) . (strlen($sample) > 50 ? '...' : ''), + substr($sample, 0, 50).(strlen($sample) > 50 ? '...' : ''), $comparison['pipeline']['category_name'], $comparison['legacy']['category_name'], $comparison['match'] ? '✓' : '✗', @@ -139,4 +141,3 @@ class TestCategorization extends Command ); } } - diff --git a/app/Console/Commands/TmuxMonitor.php b/app/Console/Commands/TmuxMonitor.php index 320baa665..4250bbc6d 100644 --- a/app/Console/Commands/TmuxMonitor.php +++ b/app/Console/Commands/TmuxMonitor.php @@ -5,9 +5,9 @@ namespace App\Console\Commands; use App\Models\Collection; use App\Models\Settings; use App\Services\Tmux\TmuxMonitorService; +use App\Services\Tmux\TmuxOutput; use App\Services\Tmux\TmuxSessionManager; use App\Services\Tmux\TmuxTaskRunner; -use App\Services\Tmux\TmuxOutput; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; diff --git a/app/Console/Commands/UpdateBinaries.php b/app/Console/Commands/UpdateBinaries.php index 6d3704c91..fdf91faa2 100644 --- a/app/Console/Commands/UpdateBinaries.php +++ b/app/Console/Commands/UpdateBinaries.php @@ -51,17 +51,17 @@ class UpdateBinaries extends Command $this->outputBanner(); $nntp = $this->getNntp(); - $binaries = new BinariesService(); + $binaries = new BinariesService; $binaries->setNntp($nntp); - if ($groupName && !is_numeric($groupName)) { + if ($groupName && ! is_numeric($groupName)) { $this->outputHeader('Updating Single Group'); $this->outputInfo("Group: {$groupName}"); - $this->outputInfo('Max headers: ' . number_format($maxHeaders)); + $this->outputInfo('Max headers: '.number_format($maxHeaders)); $this->updateSingleGroup($binaries, $groupName, $maxHeaders); } else { $this->outputHeader('Updating All Groups'); - $this->outputInfo('Max headers: ' . number_format($maxHeaders)); + $this->outputInfo('Max headers: '.number_format($maxHeaders)); $binaries->updateAllGroups($maxHeaders); } @@ -70,7 +70,7 @@ class UpdateBinaries extends Command return self::SUCCESS; } catch (\Throwable $e) { Log::error($e->getMessage()); - cli()->error('Error: ' . $e->getMessage()); + cli()->error('Error: '.$e->getMessage()); return self::FAILURE; } @@ -95,7 +95,7 @@ class UpdateBinaries extends Command */ private function getNntp(): NNTPService { - $nntp = new NNTPService(); + $nntp = new NNTPService; $connectResult = $nntp->doConnect(); @@ -115,13 +115,13 @@ class UpdateBinaries extends Command */ private function outputBanner(): void { - if (!$this->echoCLI) { + if (! $this->echoCLI) { return; } echo PHP_EOL; cli()->header('NNTmux Binary Update'); - cli()->info('Started: ' . now()->format('Y-m-d H:i:s')); + cli()->info('Started: '.now()->format('Y-m-d H:i:s')); } /** @@ -129,7 +129,7 @@ class UpdateBinaries extends Command */ private function outputHeader(string $title): void { - if (!$this->echoCLI) { + if (! $this->echoCLI) { return; } @@ -143,7 +143,7 @@ class UpdateBinaries extends Command */ private function outputInfo(string $message): void { - if (!$this->echoCLI) { + if (! $this->echoCLI) { return; } @@ -155,7 +155,7 @@ class UpdateBinaries extends Command */ private function outputSummary(\DateTimeInterface $startTime): void { - if (!$this->echoCLI) { + if (! $this->echoCLI) { return; } diff --git a/app/Console/Commands/UpdateGroupHeaders.php b/app/Console/Commands/UpdateGroupHeaders.php index 00af930da..7cba2b7c7 100644 --- a/app/Console/Commands/UpdateGroupHeaders.php +++ b/app/Console/Commands/UpdateGroupHeaders.php @@ -41,7 +41,7 @@ class UpdateGroupHeaders extends Command return self::FAILURE; } - $binaries = new BinariesService(); + $binaries = new BinariesService; $binaries->setNntp($nntp); $binaries->updateGroup($groupMySQL); diff --git a/app/Console/Commands/UpdatePerGroup.php b/app/Console/Commands/UpdatePerGroup.php index 72e7f3311..d9510a208 100644 --- a/app/Console/Commands/UpdatePerGroup.php +++ b/app/Console/Commands/UpdatePerGroup.php @@ -10,8 +10,8 @@ use App\Services\AdditionalProcessing\AdditionalProcessingOrchestrator; use App\Services\Backfill\BackfillService; use App\Services\Binaries\BinariesService; use App\Services\NfoService; -use App\Services\ReleaseProcessingService; use App\Services\NNTP\NNTPService; +use App\Services\ReleaseProcessingService; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; @@ -44,7 +44,7 @@ class UpdatePerGroup extends Command { $groupId = $this->argument('groupId'); - if (!is_numeric($groupId)) { + if (! is_numeric($groupId)) { $this->error('Group ID must be numeric.'); return self::FAILURE; @@ -65,7 +65,7 @@ class UpdatePerGroup extends Command // Update the group for new binaries $this->info("Updating binaries for group: {$groupMySQL['name']}"); - (new BinariesService())->updateGroup($groupMySQL); + (new BinariesService)->updateGroup($groupMySQL); // BackFill the group with 20k articles $this->info("Backfilling group: {$groupMySQL['name']}"); @@ -80,7 +80,7 @@ class UpdatePerGroup extends Command app(AdditionalProcessingOrchestrator::class)->start($groupId); $this->info("Processing NFO files for group: {$groupMySQL['name']}"); - (new NfoService())->processNfoFiles( + (new NfoService)->processNfoFiles( $nntp, $groupId, '', @@ -127,7 +127,7 @@ class UpdatePerGroup extends Command */ private function getNntp(): NNTPService { - $nntp = new NNTPService(); + $nntp = new NNTPService; $useAlternate = config('nntmux_nntp.use_alternate_nntp_server') === true; $connectResult = $useAlternate diff --git a/app/Console/Commands/UpdatePostProcess.php b/app/Console/Commands/UpdatePostProcess.php index 1459737fd..3190e7a7f 100644 --- a/app/Console/Commands/UpdatePostProcess.php +++ b/app/Console/Commands/UpdatePostProcess.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace App\Console\Commands; -use App\Services\PostProcessService; use App\Services\NNTP\NNTPService; +use App\Services\PostProcessService; use Illuminate\Console\Command; class UpdatePostProcess extends Command @@ -59,7 +59,7 @@ class UpdatePostProcess extends Command { $type = $this->argument('type'); - if (!array_key_exists($type, self::VALID_TYPES)) { + if (! array_key_exists($type, self::VALID_TYPES)) { $this->error("Invalid type: {$type}"); $this->showHelp(); @@ -131,7 +131,7 @@ class UpdatePostProcess extends Command */ private function getNntp(): NNTPService { - $nntp = new NNTPService(); + $nntp = new NNTPService; $connectResult = config('nntmux_nntp.use_alternate_nntp_server') === true ? $nntp->doConnect(false, true) diff --git a/app/Console/Commands/UpdateReleasesIndexSchema.php b/app/Console/Commands/UpdateReleasesIndexSchema.php index 4abcad1be..5763feb97 100644 --- a/app/Console/Commands/UpdateReleasesIndexSchema.php +++ b/app/Console/Commands/UpdateReleasesIndexSchema.php @@ -64,10 +64,11 @@ class UpdateReleasesIndexSchema extends Command if ($driver !== 'manticore') { $this->error("This command currently only supports ManticoreSearch. Current driver: {$driver}"); + return Command::FAILURE; } - $this->info("Releases index schema update utility"); + $this->info('Releases index schema update utility'); $this->newLine(); // Initialize ManticoreSearch client @@ -84,7 +85,8 @@ class UpdateReleasesIndexSchema extends Command $this->client->nodes()->status(); $this->info('Connected to ManticoreSearch successfully.'); } catch (\Exception $e) { - $this->error('Failed to connect to ManticoreSearch: ' . $e->getMessage()); + $this->error('Failed to connect to ManticoreSearch: '.$e->getMessage()); + return Command::FAILURE; } @@ -104,7 +106,7 @@ class UpdateReleasesIndexSchema extends Command } // If no options specified, show current schema info - if (!$this->option('add-fields') && !$this->option('update-media-ids')) { + if (! $this->option('add-fields') && ! $this->option('update-media-ids')) { $this->showSchemaInfo(); } @@ -126,6 +128,7 @@ class UpdateReleasesIndexSchema extends Command if (empty($columns)) { $this->warn('Index releases_rt does not exist or has no columns.'); $this->info('Run `php artisan manticore:create-indexes` to create the index first.'); + return; } @@ -135,7 +138,7 @@ class UpdateReleasesIndexSchema extends Command foreach ($columns as $field => $props) { $type = $props['Type'] ?? 'unknown'; - $properties = isset($props['Properties']) ? implode(', ', (array)$props['Properties']) : ''; + $properties = isset($props['Properties']) ? implode(', ', (array) $props['Properties']) : ''; $rows[] = [$field, $type, $properties]; $existingFields[$field] = strtolower($type); } @@ -146,12 +149,12 @@ class UpdateReleasesIndexSchema extends Command // Check for missing fields $missingFields = []; foreach ($this->expectedFields as $field => $config) { - if (!isset($existingFields[$field]) && $field !== 'id') { + if (! isset($existingFields[$field]) && $field !== 'id') { $missingFields[] = $field; } } - if (!empty($missingFields)) { + if (! empty($missingFields)) { $this->warn('Missing fields that should be added:'); foreach ($missingFields as $field) { $this->line(" - {$field} ({$this->expectedFields[$field]['type']})"); @@ -173,7 +176,7 @@ class UpdateReleasesIndexSchema extends Command $this->line(' --batch-size=N Set batch size for updates (default: 5000)'); } catch (\Throwable $e) { - $this->error('Failed to describe index: ' . $e->getMessage()); + $this->error('Failed to describe index: '.$e->getMessage()); $this->info('The index may not exist. Run `php artisan manticore:create-indexes` first.'); } } @@ -193,6 +196,7 @@ class UpdateReleasesIndexSchema extends Command if (empty($columns)) { $this->error('Index releases_rt does not exist. Create it first with: php artisan manticore:create-indexes'); + return Command::FAILURE; } @@ -215,13 +219,14 @@ class UpdateReleasesIndexSchema extends Command $fieldsToAdd = []; foreach ($mediaFields as $field => $type) { - if (!isset($existingFields[$field]) || $this->option('force')) { + if (! isset($existingFields[$field]) || $this->option('force')) { $fieldsToAdd[$field] = $type; } } if (empty($fieldsToAdd)) { $this->info('All media fields already exist in the index.'); + return Command::SUCCESS; } @@ -230,8 +235,9 @@ class UpdateReleasesIndexSchema extends Command $this->line(" - {$field} ({$type})"); } - if (!$this->confirm('Do you want to proceed with adding these fields?', true)) { + if (! $this->confirm('Do you want to proceed with adding these fields?', true)) { $this->info('Operation cancelled.'); + return Command::SUCCESS; } @@ -244,7 +250,8 @@ class UpdateReleasesIndexSchema extends Command if (str_contains($e->getMessage(), 'already exists')) { $this->warn("Field {$field} already exists, skipping."); } else { - $this->error("Failed to add field {$field}: " . $e->getMessage()); + $this->error("Failed to add field {$field}: ".$e->getMessage()); + return Command::FAILURE; } } @@ -258,7 +265,8 @@ class UpdateReleasesIndexSchema extends Command return Command::SUCCESS; } catch (\Throwable $e) { - $this->error('Failed to update schema: ' . $e->getMessage()); + $this->error('Failed to update schema: '.$e->getMessage()); + return Command::FAILURE; } } @@ -303,21 +311,21 @@ class UpdateReleasesIndexSchema extends Command // Apply filters based on options if ($moviesOnly) { $query->whereNotNull('releases.movieinfo_id') - ->where('releases.movieinfo_id', '>', 0); + ->where('releases.movieinfo_id', '>', 0); $this->info('Filtering: Movies only (releases with movieinfo_id)'); } elseif ($tvOnly) { $query->whereNotNull('releases.videos_id') - ->where('releases.videos_id', '>', 0); + ->where('releases.videos_id', '>', 0); $this->info('Filtering: TV shows only (releases with videos_id)'); } else { // Get releases that have either movie or TV info $query->where(function ($q) { $q->where(function ($subq) { $subq->whereNotNull('releases.movieinfo_id') - ->where('releases.movieinfo_id', '>', 0); + ->where('releases.movieinfo_id', '>', 0); })->orWhere(function ($subq) { $subq->whereNotNull('releases.videos_id') - ->where('releases.videos_id', '>', 0); + ->where('releases.videos_id', '>', 0); }); }); $this->info('Filtering: Releases with either movie or TV info'); @@ -327,13 +335,15 @@ class UpdateReleasesIndexSchema extends Command if ($total === 0) { $this->warn('No releases found matching the criteria.'); + return Command::SUCCESS; } $this->info("Found {$total} releases to update."); - if (!$this->confirm('Do you want to proceed with the update?', true)) { + if (! $this->confirm('Do you want to proceed with the update?', true)) { $this->info('Operation cancelled.'); + return Command::SUCCESS; } @@ -355,15 +365,17 @@ class UpdateReleasesIndexSchema extends Command $mediaData = $this->prepareMediaData($release); // Skip if all media IDs are zero and we're not forcing update - $hasMediaIds = array_filter($mediaData, fn($v) => $v > 0); + $hasMediaIds = array_filter($mediaData, fn ($v) => $v > 0); if (empty($hasMediaIds)) { $bar->advance(); + continue; } // If missing-only, check if the document already has media IDs if ($missingOnly && $this->documentHasMediaIds($indexName, $release->id)) { $bar->advance(); + continue; } @@ -384,7 +396,7 @@ class UpdateReleasesIndexSchema extends Command } // Process remaining batch - if (!empty($batch)) { + if (! empty($batch)) { $result = $this->processBatch($indexName, $batch); $updated += $result['updated']; $errors += $result['errors']; @@ -394,7 +406,7 @@ class UpdateReleasesIndexSchema extends Command $bar->finish(); $this->newLine(2); - $this->info("Update completed!"); + $this->info('Update completed!'); $this->line(" - Updated: {$updated}"); if ($errors > 0) { $this->warn(" - Errors: {$errors}"); @@ -428,7 +440,7 @@ class UpdateReleasesIndexSchema extends Command try { $doc = $this->client->table($indexName)->getDocumentById($id); - if (!$doc) { + if (! $doc) { return false; } @@ -470,7 +482,7 @@ class UpdateReleasesIndexSchema extends Command $errors++; if ($errors <= 5) { // Log first few errors - Log::warning("Failed to update release {$item['id']} in search index: " . $e2->getMessage()); + Log::warning("Failed to update release {$item['id']} in search index: ".$e2->getMessage()); } } } diff --git a/app/Console/Commands/UpdateReleasesIndexSchemaES.php b/app/Console/Commands/UpdateReleasesIndexSchemaES.php index fe7b0e101..72744de82 100644 --- a/app/Console/Commands/UpdateReleasesIndexSchemaES.php +++ b/app/Console/Commands/UpdateReleasesIndexSchemaES.php @@ -2,7 +2,6 @@ namespace App\Console\Commands; -use App\Facades\Search; use App\Models\Release; use Elasticsearch; use Elasticsearch\Common\Exceptions\Missing404Exception; @@ -57,12 +56,12 @@ class UpdateReleasesIndexSchemaES extends Command if ($driver !== 'elasticsearch') { $this->warn("Current search driver is '{$driver}'. This command is for ElasticSearch."); - if (!$this->confirm('Do you want to continue anyway?', false)) { + if (! $this->confirm('Do you want to continue anyway?', false)) { return Command::SUCCESS; } } - $this->info("ElasticSearch releases index schema update utility"); + $this->info('ElasticSearch releases index schema update utility'); $this->newLine(); // Test connection @@ -70,7 +69,8 @@ class UpdateReleasesIndexSchemaES extends Command $health = Elasticsearch::cluster()->health(); $this->info("Connected to ElasticSearch. Cluster status: {$health['status']}"); } catch (\Exception $e) { - $this->error('Failed to connect to ElasticSearch: ' . $e->getMessage()); + $this->error('Failed to connect to ElasticSearch: '.$e->getMessage()); + return Command::FAILURE; } @@ -98,7 +98,7 @@ class UpdateReleasesIndexSchemaES extends Command } // If no options specified, show current schema info - if (!$this->option('add-fields') && !$this->option('update-media-ids') && !$this->option('recreate-index')) { + if (! $this->option('add-fields') && ! $this->option('update-media-ids') && ! $this->option('recreate-index')) { $this->showSchemaInfo(); } @@ -116,9 +116,10 @@ class UpdateReleasesIndexSchemaES extends Command try { $indexName = config('search.drivers.elasticsearch.indexes.releases', 'releases'); - if (!Elasticsearch::indices()->exists(['index' => $indexName])) { + if (! Elasticsearch::indices()->exists(['index' => $indexName])) { $this->warn("Index '{$indexName}' does not exist."); $this->info('Run `php artisan nntmux:create-es-indexes` to create the index first.'); + return; } @@ -127,6 +128,7 @@ class UpdateReleasesIndexSchemaES extends Command if (empty($properties)) { $this->warn('Index has no mapped properties.'); + return; } @@ -140,7 +142,7 @@ class UpdateReleasesIndexSchemaES extends Command if (isset($config['analyzer'])) { $props[] = "analyzer: {$config['analyzer']}"; } - if (isset($config['index']) && !$config['index']) { + if (isset($config['index']) && ! $config['index']) { $props[] = 'not indexed'; } if (isset($config['fields'])) { @@ -156,12 +158,12 @@ class UpdateReleasesIndexSchemaES extends Command // Check for missing media fields $missingFields = []; foreach ($this->mediaFields as $field => $config) { - if (!isset($existingFields[$field])) { + if (! isset($existingFields[$field])) { $missingFields[] = $field; } } - if (!empty($missingFields)) { + if (! empty($missingFields)) { $this->warn('Missing media fields that should be added:'); foreach ($missingFields as $field) { $this->line(" - {$field} ({$this->mediaFields[$field]['type']})"); @@ -184,7 +186,7 @@ class UpdateReleasesIndexSchemaES extends Command $this->line(' --batch-size=N Set batch size for updates (default: 1000)'); } catch (\Throwable $e) { - $this->error('Failed to get index mapping: ' . $e->getMessage()); + $this->error('Failed to get index mapping: '.$e->getMessage()); $this->info('The index may not exist. Run `php artisan nntmux:create-es-indexes` first.'); } } @@ -201,8 +203,9 @@ class UpdateReleasesIndexSchemaES extends Command try { $indexName = config('search.drivers.elasticsearch.indexes.releases', 'releases'); - if (!Elasticsearch::indices()->exists(['index' => $indexName])) { + if (! Elasticsearch::indices()->exists(['index' => $indexName])) { $this->error("Index '{$indexName}' does not exist. Create it first with: php artisan nntmux:create-es-indexes"); + return Command::FAILURE; } @@ -213,13 +216,14 @@ class UpdateReleasesIndexSchemaES extends Command // Find fields to add $fieldsToAdd = []; foreach ($this->mediaFields as $field => $config) { - if (!isset($existingProperties[$field]) || $this->option('force')) { + if (! isset($existingProperties[$field]) || $this->option('force')) { $fieldsToAdd[$field] = $config; } } if (empty($fieldsToAdd)) { $this->info('All media fields already exist in the index.'); + return Command::SUCCESS; } @@ -228,8 +232,9 @@ class UpdateReleasesIndexSchemaES extends Command $this->line(" - {$field} ({$config['type']})"); } - if (!$this->confirm('Do you want to proceed with adding these fields?', true)) { + if (! $this->confirm('Do you want to proceed with adding these fields?', true)) { $this->info('Operation cancelled.'); + return Command::SUCCESS; } @@ -242,8 +247,8 @@ class UpdateReleasesIndexSchemaES extends Command Elasticsearch::indices()->putMapping([ 'index' => $indexName, 'body' => [ - 'properties' => $newProperties - ] + 'properties' => $newProperties, + ], ]); $this->info('Schema update completed successfully!'); @@ -254,7 +259,8 @@ class UpdateReleasesIndexSchemaES extends Command return Command::SUCCESS; } catch (\Throwable $e) { - $this->error('Failed to update schema: ' . $e->getMessage()); + $this->error('Failed to update schema: '.$e->getMessage()); + return Command::FAILURE; } } @@ -268,10 +274,11 @@ class UpdateReleasesIndexSchemaES extends Command $indexName = config('search.drivers.elasticsearch.indexes.releases', 'releases'); $this->warn("WARNING: This will DELETE all data in the '{$indexName}' index and recreate it with the new schema!"); - $this->warn("You will need to re-populate the index after this operation."); + $this->warn('You will need to re-populate the index after this operation.'); - if (!$this->confirm('Are you sure you want to proceed?', false)) { + if (! $this->confirm('Are you sure you want to proceed?', false)) { $this->info('Operation cancelled.'); + return Command::SUCCESS; } @@ -380,7 +387,8 @@ class UpdateReleasesIndexSchemaES extends Command return Command::SUCCESS; } catch (\Throwable $e) { - $this->error('Failed to recreate index: ' . $e->getMessage()); + $this->error('Failed to recreate index: '.$e->getMessage()); + return Command::FAILURE; } } @@ -400,8 +408,9 @@ class UpdateReleasesIndexSchemaES extends Command $indexName = config('search.drivers.elasticsearch.indexes.releases', 'releases'); // Check if index exists - if (!Elasticsearch::indices()->exists(['index' => $indexName])) { + if (! Elasticsearch::indices()->exists(['index' => $indexName])) { $this->error("Index '{$indexName}' does not exist. Create it first."); + return Command::FAILURE; } @@ -429,21 +438,21 @@ class UpdateReleasesIndexSchemaES extends Command // Apply filters based on options if ($moviesOnly) { $query->whereNotNull('releases.movieinfo_id') - ->where('releases.movieinfo_id', '>', 0); + ->where('releases.movieinfo_id', '>', 0); $this->info('Filtering: Movies only (releases with movieinfo_id)'); } elseif ($tvOnly) { $query->whereNotNull('releases.videos_id') - ->where('releases.videos_id', '>', 0); + ->where('releases.videos_id', '>', 0); $this->info('Filtering: TV shows only (releases with videos_id)'); } else { // Get releases that have either movie or TV info $query->where(function ($q) { $q->where(function ($subq) { $subq->whereNotNull('releases.movieinfo_id') - ->where('releases.movieinfo_id', '>', 0); + ->where('releases.movieinfo_id', '>', 0); })->orWhere(function ($subq) { $subq->whereNotNull('releases.videos_id') - ->where('releases.videos_id', '>', 0); + ->where('releases.videos_id', '>', 0); }); }); $this->info('Filtering: Releases with either movie or TV info'); @@ -453,13 +462,15 @@ class UpdateReleasesIndexSchemaES extends Command if ($total === 0) { $this->warn('No releases found matching the criteria.'); + return Command::SUCCESS; } $this->info("Found {$total} releases to update."); - if (!$this->confirm('Do you want to proceed with the update?', true)) { + if (! $this->confirm('Do you want to proceed with the update?', true)) { $this->info('Operation cancelled.'); + return Command::SUCCESS; } @@ -481,10 +492,11 @@ class UpdateReleasesIndexSchemaES extends Command $mediaData = $this->prepareMediaData($release); // Skip if all media IDs are zero - $hasMediaIds = array_filter($mediaData, fn($v) => $v > 0); + $hasMediaIds = array_filter($mediaData, fn ($v) => $v > 0); if (empty($hasMediaIds)) { $skipped++; $bar->advance(); + continue; } @@ -492,6 +504,7 @@ class UpdateReleasesIndexSchemaES extends Command if ($missingOnly && $this->documentHasMediaIds($indexName, $release->id)) { $skipped++; $bar->advance(); + continue; } @@ -500,7 +513,7 @@ class UpdateReleasesIndexSchemaES extends Command 'update' => [ '_index' => $indexName, '_id' => $release->id, - ] + ], ]; $bulkParams['body'][] = [ 'doc' => $mediaData, @@ -511,7 +524,7 @@ class UpdateReleasesIndexSchemaES extends Command } // Execute bulk update - if (!empty($bulkParams['body'])) { + if (! empty($bulkParams['body'])) { try { $response = Elasticsearch::bulk($bulkParams); @@ -520,7 +533,7 @@ class UpdateReleasesIndexSchemaES extends Command if (isset($item['update']['error'])) { $errors++; if ($errors <= 5) { - Log::warning("Failed to update release in ES: " . json_encode($item['update']['error'])); + Log::warning('Failed to update release in ES: '.json_encode($item['update']['error'])); } } else { $updated++; @@ -532,7 +545,7 @@ class UpdateReleasesIndexSchemaES extends Command } catch (\Throwable $e) { $errors += count($bulkParams['body']) / 2; if ($errors <= 5) { - Log::error("Bulk update failed: " . $e->getMessage()); + Log::error('Bulk update failed: '.$e->getMessage()); } } } @@ -541,7 +554,7 @@ class UpdateReleasesIndexSchemaES extends Command $bar->finish(); $this->newLine(2); - $this->info("Update completed!"); + $this->info('Update completed!'); $this->line(" - Updated: {$updated}"); $this->line(" - Skipped: {$skipped}"); if ($errors > 0) { @@ -582,7 +595,7 @@ class UpdateReleasesIndexSchemaES extends Command $doc = Elasticsearch::get([ 'index' => $indexName, 'id' => $id, - '_source' => ['imdbid', 'tmdbid', 'traktid', 'tvdb', 'tvmaze', 'tvrage'] + '_source' => ['imdbid', 'tmdbid', 'traktid', 'tvdb', 'tvmaze', 'tvrage'], ]); $source = $doc['_source'] ?? []; @@ -604,4 +617,3 @@ class UpdateReleasesIndexSchemaES extends Command } } } - diff --git a/app/Enums/BlacklistConstants.php b/app/Enums/BlacklistConstants.php index 699379152..e9b0f196e 100644 --- a/app/Enums/BlacklistConstants.php +++ b/app/Enums/BlacklistConstants.php @@ -25,4 +25,3 @@ final class BlacklistConstants public const BLACKLIST_FIELD_MESSAGEID = 3; } - diff --git a/app/Enums/FileCompletionStatus.php b/app/Enums/FileCompletionStatus.php index 850593f37..5f8ec2374 100644 --- a/app/Enums/FileCompletionStatus.php +++ b/app/Enums/FileCompletionStatus.php @@ -34,4 +34,3 @@ enum FileCompletionStatus: int return $this === self::Complete; } } - diff --git a/app/Enums/QueueType.php b/app/Enums/QueueType.php index b4c96e545..cc51352a6 100644 --- a/app/Enums/QueueType.php +++ b/app/Enums/QueueType.php @@ -19,4 +19,3 @@ enum QueueType: int }; } } - diff --git a/app/Enums/SignupError.php b/app/Enums/SignupError.php index 934ce6207..b7b481513 100644 --- a/app/Enums/SignupError.php +++ b/app/Enums/SignupError.php @@ -37,4 +37,3 @@ enum SignupError: int return $this === self::SUCCESS; } } - diff --git a/app/Enums/UserRole.php b/app/Enums/UserRole.php index 729cbd045..e0983d850 100644 --- a/app/Enums/UserRole.php +++ b/app/Enums/UserRole.php @@ -41,4 +41,3 @@ enum UserRole: int return in_array($this, [self::ADMIN, self::MODERATOR], true); } } - diff --git a/app/Extensions/helper/helpers.php b/app/Extensions/helper/helpers.php index 379ded3b4..2c399ac0a 100644 --- a/app/Extensions/helper/helpers.php +++ b/app/Extensions/helper/helpers.php @@ -972,6 +972,7 @@ if (! function_exists('isValidNewznabNzb')) { if ($xml === false) { libxml_clear_errors(); + return false; } @@ -1271,4 +1272,3 @@ if (! function_exists('cli')) { return $instance; } } - diff --git a/app/Facades/Categorization.php b/app/Facades/Categorization.php index cb1fa1ad6..2922653ad 100644 --- a/app/Facades/Categorization.php +++ b/app/Facades/Categorization.php @@ -19,4 +19,3 @@ class Categorization extends Facade return CategorizationPipeline::class; } } - diff --git a/app/Facades/TvProcessing.php b/app/Facades/TvProcessing.php index 6f2901da8..d347fab6e 100644 --- a/app/Facades/TvProcessing.php +++ b/app/Facades/TvProcessing.php @@ -24,4 +24,3 @@ class TvProcessing extends Facade return TvProcessingPipeline::class; } } - diff --git a/app/Facades/Yenc.php b/app/Facades/Yenc.php index b9f0b1c25..9fd3ac754 100644 --- a/app/Facades/Yenc.php +++ b/app/Facades/Yenc.php @@ -26,4 +26,3 @@ class Yenc extends Facade return \App\Services\YencService::class; } } - diff --git a/app/Http/Controllers/Admin/AdminAjaxController.php b/app/Http/Controllers/Admin/AdminAjaxController.php index 379250461..a52e7541b 100644 --- a/app/Http/Controllers/Admin/AdminAjaxController.php +++ b/app/Http/Controllers/Admin/AdminAjaxController.php @@ -23,7 +23,7 @@ class AdminAjaxController extends BasePageController switch ($request->input('action')) { case 'binary_blacklist_delete': $id = (int) $request->input('row_id'); - (new BlacklistService())->deleteBlacklist($id); + (new BlacklistService)->deleteBlacklist($id); echo "Blacklist $id deleted."; break; diff --git a/app/Http/Controllers/Admin/AdminContentController.php b/app/Http/Controllers/Admin/AdminContentController.php index 63b899619..b7dae178f 100644 --- a/app/Http/Controllers/Admin/AdminContentController.php +++ b/app/Http/Controllers/Admin/AdminContentController.php @@ -4,8 +4,6 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\BasePageController; use App\Models\Content; -use App\Models\User; -use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; class AdminContentController extends BasePageController @@ -36,6 +34,7 @@ class AdminContentController extends BasePageController * Show form to create or edit content. * * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View + * * @throws \Exception */ public function create(Request $request) @@ -90,13 +89,13 @@ class AdminContentController extends BasePageController $contenttypelist = [ Content::TYPE_USEFUL => 'Useful Link', - Content::TYPE_INDEX => 'Homepage' + Content::TYPE_INDEX => 'Homepage', ]; $rolelist = [ Content::ROLE_EVERYONE => 'Everyone', Content::ROLE_LOGGED_IN => 'Logged in Users', - Content::ROLE_ADMIN => 'Admins' + Content::ROLE_ADMIN => 'Admins', ]; $this->viewData = array_merge($this->viewData, [ @@ -132,14 +131,14 @@ class AdminContentController extends BasePageController return response()->json([ 'success' => true, 'status' => $newStatus, - 'message' => $newStatus === Content::STATUS_ENABLED ? 'Content enabled' : 'Content disabled' + 'message' => $newStatus === Content::STATUS_ENABLED ? 'Content enabled' : 'Content disabled', ]); } } return response()->json([ 'success' => false, - 'message' => 'Content not found' + 'message' => 'Content not found', ], 404); } @@ -158,7 +157,7 @@ class AdminContentController extends BasePageController if ($request->wantsJson() || $request->ajax()) { return response()->json([ 'success' => true, - 'message' => 'Content deleted successfully' + 'message' => 'Content deleted successfully', ]); } @@ -169,12 +168,13 @@ class AdminContentController extends BasePageController if ($request->wantsJson() || $request->ajax()) { return response()->json([ 'success' => false, - 'message' => 'Content not found' + 'message' => 'Content not found', ], 404); } } $referrer = $request->server('HTTP_REFERER'); + return redirect()->to($referrer)->with('error', 'Invalid request'); } @@ -248,17 +248,17 @@ class AdminContentController extends BasePageController // Check if URL is external (has protocol or is a domain pattern) $hasProtocol = str_starts_with($url, 'http://') || str_starts_with($url, 'https://'); - $isDomain = !str_starts_with($url, '/') && preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z]{2,}/', $url); + $isDomain = ! str_starts_with($url, '/') && preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z]{2,}/', $url); // Only normalize internal URLs (those starting with / or root) - if (!$hasProtocol && !$isDomain) { + if (! $hasProtocol && ! $isDomain) { // Ensure internal URL starts with / - if ($url !== '/' && !str_starts_with($url, '/')) { - $data['url'] = '/' . $url; + if ($url !== '/' && ! str_starts_with($url, '/')) { + $data['url'] = '/'.$url; } // Ensure internal URL ends with / - if (!str_ends_with($data['url'], '/')) { + if (! str_ends_with($data['url'], '/')) { $data['url'] .= '/'; } } diff --git a/app/Http/Controllers/Admin/AdminMusicController.php b/app/Http/Controllers/Admin/AdminMusicController.php index abf9d76d4..340bb955e 100644 --- a/app/Http/Controllers/Admin/AdminMusicController.php +++ b/app/Http/Controllers/Admin/AdminMusicController.php @@ -3,8 +3,8 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\BasePageController; -use App\Services\MusicService; use App\Services\GenreService; +use App\Services\MusicService; use Illuminate\Http\Request; use Illuminate\Support\Carbon; diff --git a/app/Http/Controllers/Admin/AdminPromotionController.php b/app/Http/Controllers/Admin/AdminPromotionController.php index b53ddaeef..502b236c3 100644 --- a/app/Http/Controllers/Admin/AdminPromotionController.php +++ b/app/Http/Controllers/Admin/AdminPromotionController.php @@ -146,7 +146,7 @@ class AdminPromotionController extends BasePageController public function toggle(int $id): RedirectResponse { $promotion = RolePromotion::findOrFail($id); - $promotion->update(['is_active' => !$promotion->is_active]); + $promotion->update(['is_active' => ! $promotion->is_active]); $status = $promotion->is_active ? 'activated' : 'deactivated'; @@ -206,11 +206,11 @@ class AdminPromotionController extends BasePageController 'active_promotions' => $promotions->where('is_active', true)->count(), 'total_applications' => $promotions->sum('statistics_count'), 'unique_users' => RolePromotionStat::query() - ->when($startDate, fn($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) + ->when($startDate, fn ($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) ->distinct('user_id') ->count('user_id'), 'total_days_added' => RolePromotionStat::query() - ->when($startDate, fn($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) + ->when($startDate, fn ($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) ->sum('days_added'), ]; @@ -219,7 +219,7 @@ class AdminPromotionController extends BasePageController // Get recent activity $recentActivity = RolePromotionStat::with(['user', 'promotion', 'role']) - ->when($startDate, fn($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) + ->when($startDate, fn ($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) ->latest('applied_at') ->limit(10) ->get(); @@ -227,7 +227,7 @@ class AdminPromotionController extends BasePageController // Get statistics by role $statsByRole = RolePromotionStat::query() ->selectRaw('role_id, COUNT(*) as total_upgrades, SUM(days_added) as total_days, COUNT(DISTINCT user_id) as unique_users') - ->when($startDate, fn($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) + ->when($startDate, fn ($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) ->groupBy('role_id') ->with('role') ->get(); @@ -284,14 +284,14 @@ class AdminPromotionController extends BasePageController // Get applications with users $applications = RolePromotionStat::forPromotion($id) - ->when($startDate, fn($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) + ->when($startDate, fn ($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) ->with(['user', 'role']) ->latest('applied_at') ->paginate(20); // Get daily statistics for chart $dailyStats = RolePromotionStat::forPromotion($id) - ->when($startDate, fn($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) + ->when($startDate, fn ($q) => $q->whereBetween('applied_at', [$startDate, $endDate])) ->selectRaw('DATE(applied_at) as date, COUNT(*) as count, SUM(days_added) as days') ->groupBy('date') ->orderBy('date') @@ -309,4 +309,3 @@ class AdminPromotionController extends BasePageController ]); } } - diff --git a/app/Http/Controllers/Admin/AdminUserController.php b/app/Http/Controllers/Admin/AdminUserController.php index a8205a140..0f5799ace 100644 --- a/app/Http/Controllers/Admin/AdminUserController.php +++ b/app/Http/Controllers/Admin/AdminUserController.php @@ -11,7 +11,6 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Jrean\UserVerification\Facades\UserVerification; use Spatie\Permission\Models\Role; -use Stevebauman\Location\Facades\Location; class AdminUserController extends BasePageController { @@ -212,7 +211,7 @@ class AdminUserController extends BasePageController User::updateUserRole( $editedUser->id, (int) $request->input('role'), // Cast to integer - !$adminManuallySetExpiry, // Only apply promotions if admin didn't set custom expiry + ! $adminManuallySetExpiry, // Only apply promotions if admin didn't set custom expiry $stackRole, // Stack role if requested $changedBy, $originalRoleChangeDate, // Pass original expiry for history diff --git a/app/Http/Controllers/Admin/AdminUserRoleHistoryController.php b/app/Http/Controllers/Admin/AdminUserRoleHistoryController.php index 2175414eb..4ab0a9784 100644 --- a/app/Http/Controllers/Admin/AdminUserRoleHistoryController.php +++ b/app/Http/Controllers/Admin/AdminUserRoleHistoryController.php @@ -27,33 +27,33 @@ class AdminUserRoleHistoryController extends BasePageController ->orderBy('created_at', 'desc'); // Apply filters - if ($request->has('user_id') && !empty($request->input('user_id'))) { + if ($request->has('user_id') && ! empty($request->input('user_id'))) { $query->where('user_id', $request->input('user_id')); } - if ($request->has('username') && !empty($request->input('username'))) { + if ($request->has('username') && ! empty($request->input('username'))) { $query->whereHas('user', function ($q) use ($request) { - $q->where('username', 'like', '%' . $request->input('username') . '%'); + $q->where('username', 'like', '%'.$request->input('username').'%'); }); } - if ($request->has('role_id') && !empty($request->input('role_id'))) { + if ($request->has('role_id') && ! empty($request->input('role_id'))) { $query->where(function ($q) use ($request) { $q->where('old_role_id', $request->input('role_id')) ->orWhere('new_role_id', $request->input('role_id')); }); } - if ($request->has('change_reason') && !empty($request->input('change_reason'))) { - $query->where('change_reason', 'like', '%' . $request->input('change_reason') . '%'); + if ($request->has('change_reason') && ! empty($request->input('change_reason'))) { + $query->where('change_reason', 'like', '%'.$request->input('change_reason').'%'); } - if ($request->has('date_from') && !empty($request->input('date_from'))) { - $query->where('created_at', '>=', $request->input('date_from') . ' 00:00:00'); + if ($request->has('date_from') && ! empty($request->input('date_from'))) { + $query->where('created_at', '>=', $request->input('date_from').' 00:00:00'); } - if ($request->has('date_to') && !empty($request->input('date_to'))) { - $query->where('created_at', '<=', $request->input('date_to') . ' 23:59:59'); + if ($request->has('date_to') && ! empty($request->input('date_to'))) { + $query->where('created_at', '<=', $request->input('date_to').' 23:59:59'); } // Pagination @@ -88,7 +88,7 @@ class AdminUserRoleHistoryController extends BasePageController $this->setAdminPrefs(); $user = User::findOrFail($userId); - $meta_title = $title = 'Role History for ' . $user->username; + $meta_title = $title = 'Role History for '.$user->username; $history = UserRoleHistory::with(['oldRole', 'newRole', 'changedByUser']) ->where('user_id', $userId) @@ -105,4 +105,3 @@ class AdminUserRoleHistoryController extends BasePageController return view('admin.user-role-history.show', $this->viewData); } } - diff --git a/app/Http/Controllers/Api/ApiController.php b/app/Http/Controllers/Api/ApiController.php index 205c717da..05da191c6 100644 --- a/app/Http/Controllers/Api/ApiController.php +++ b/app/Http/Controllers/Api/ApiController.php @@ -27,7 +27,9 @@ use Symfony\Component\HttpFoundation\StreamedResponse; class ApiController extends BasePageController { private string $type; + private ReleaseSearchService $releaseSearchService; + private ReleaseBrowseService $releaseBrowseService; public function __construct( @@ -130,7 +132,6 @@ class ApiController extends BasePageController } } - // Set Query Parameters based on Request objects $outputXML = ! ($request->has('o') && $request->input('o') === 'json'); $minSize = $request->has('minsize') && $request->input('minsize') > 0 ? $request->input('minsize') : 0; diff --git a/app/Http/Controllers/Api/ApiInformController.php b/app/Http/Controllers/Api/ApiInformController.php index 205916466..8fb3a7143 100644 --- a/app/Http/Controllers/Api/ApiInformController.php +++ b/app/Http/Controllers/Api/ApiInformController.php @@ -31,7 +31,7 @@ class ApiInformController extends Controller ReleaseInform::insertOrIgnore(['relOName' => $releaseObName, 'relPName' => $releasePrName, 'api_token' => $apiToken, 'created_at' => now(), 'updated_at' => now()]); $release = Release::whereSearchname($releaseObName)->first(); if (! empty($release)) { - (new ReleaseUpdateService())->updateRelease($release, $releasePrName, 'Release Inform API', true, 'Filenames, ', true, true); + (new ReleaseUpdateService)->updateRelease($release, $releasePrName, 'Release Inform API', true, 'Filenames, ', true, true); } return response()->json(['message' => 'Release Information Added!'], 200); diff --git a/app/Http/Controllers/Api/ApiV2Controller.php b/app/Http/Controllers/Api/ApiV2Controller.php index d6e0a0be0..314610040 100644 --- a/app/Http/Controllers/Api/ApiV2Controller.php +++ b/app/Http/Controllers/Api/ApiV2Controller.php @@ -25,7 +25,9 @@ use Illuminate\Support\Facades\DB; class ApiV2Controller extends BasePageController { private ApiController $api; + private ReleaseSearchService $releaseSearchService; + private ReleaseBrowseService $releaseBrowseService; public function __construct( @@ -113,7 +115,7 @@ class ApiV2Controller extends BasePageController // Create cache key for movie search results $searchCacheKey = 'api_movie_search:'.md5(serialize([ $imdbId, $tmdbId, $traktId, $offset, $limit, $searchName, - $categoryID, $maxAge, $minSize, $catExclusions + $categoryID, $maxAge, $minSize, $catExclusions, ])); // Cache search results for 10 minutes @@ -139,6 +141,7 @@ class ApiV2Controller extends BasePageController $userStatsCacheKey = 'api_user_stats:'.$user->id; $userStats = Cache::remember($userStatsCacheKey, 60, function () use ($user) { $oneDayAgo = now()->subDay()->toDateTimeString(); + return DB::selectOne(' SELECT (SELECT COUNT(*) FROM user_requests WHERE users_id = ? AND timestamp > ?) as api_count, diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 3d0b2340f..90866a824 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -128,7 +128,7 @@ class RegisterController extends Controller $validator = Validator::make($request->all(), [ 'username' => ['required', 'string', 'min:5', 'max:255', 'unique:users'], - 'email' => ['required', 'string', 'email', 'max:255', 'unique:users', new ValidEmailDomain()], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users', new ValidEmailDomain], 'password' => ['required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols()->uncompromised()], ], [ 'password.min' => 'The password must be at least 8 characters.', diff --git a/app/Http/Controllers/ContentController.php b/app/Http/Controllers/ContentController.php index 55847514f..7d7f9996d 100644 --- a/app/Http/Controllers/ContentController.php +++ b/app/Http/Controllers/ContentController.php @@ -12,6 +12,7 @@ class ContentController extends BasePageController * Display content page(s). * * @return \Illuminate\Http\JsonResponse|\Illuminate\View\View + * * @throws \Exception */ public function show(Request $request) diff --git a/app/Http/Controllers/CoverController.php b/app/Http/Controllers/CoverController.php index 17ac6a1c3..b187a21f9 100644 --- a/app/Http/Controllers/CoverController.php +++ b/app/Http/Controllers/CoverController.php @@ -51,12 +51,12 @@ class CoverController extends Controller abort(404); } } - + // For anime, try the requested filename first, then fall back to old format (without -cover) $filePath = storage_path("covers/{$type}/{$filename}"); - + // If file doesn't exist and filename ends with -cover.jpg, try old format - if (!file_exists($filePath) && preg_match('/^(\d+)-cover\.jpg$/', $filename, $matches)) { + if (! file_exists($filePath) && preg_match('/^(\d+)-cover\.jpg$/', $filename, $matches)) { $oldFormatPath = storage_path("covers/{$type}/{$matches[1]}.jpg"); if (file_exists($oldFormatPath)) { $filePath = $oldFormatPath; diff --git a/app/Http/Controllers/DetailsController.php b/app/Http/Controllers/DetailsController.php index 517b60ecd..2b4c51251 100644 --- a/app/Http/Controllers/DetailsController.php +++ b/app/Http/Controllers/DetailsController.php @@ -13,15 +13,15 @@ use App\Models\Settings; use App\Models\UserDownload; use App\Models\Video; use App\Models\XxxInfo; -use App\Services\MovieService; -use App\Services\Releases\ReleaseSearchService; -use App\Services\BookService; use App\Services\AnidbService; -use App\Services\XxxBrowseService; -use App\Services\GamesService; +use App\Services\BookService; use App\Services\ConsoleService; +use App\Services\GamesService; +use App\Services\MovieService; use App\Services\MusicService; use App\Services\ReleaseExtraService; +use App\Services\Releases\ReleaseSearchService; +use App\Services\XxxBrowseService; use Illuminate\Http\Request; class DetailsController extends BasePageController @@ -141,7 +141,7 @@ class DetailsController extends BasePageController $AniDBAPIArray = (new AnidbService)->getAnimeInfo($data['anidbid']); // If we have anilist_id but missing details, fetch from AniList - if ($AniDBAPIArray && !empty($AniDBAPIArray->anilist_id)) { + if ($AniDBAPIArray && ! empty($AniDBAPIArray->anilist_id)) { $anilistId = is_object($AniDBAPIArray) ? $AniDBAPIArray->anilist_id : ($AniDBAPIArray['anilist_id'] ?? null); if ($anilistId && (empty($AniDBAPIArray->country) && empty($AniDBAPIArray->media_type))) { // Fetch fresh data from AniList if country/media_type is missing diff --git a/app/Http/Controllers/MusicController.php b/app/Http/Controllers/MusicController.php index d50a7d276..e1a4c083c 100644 --- a/app/Http/Controllers/MusicController.php +++ b/app/Http/Controllers/MusicController.php @@ -3,8 +3,8 @@ namespace App\Http\Controllers; use App\Models\Category; -use App\Services\MusicService; use App\Services\GenreService; +use App\Services\MusicService; use Illuminate\Http\Request; use Illuminate\Support\Arr; diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 0d6c1983d..9917a3b66 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -132,7 +132,7 @@ class ProfileController extends BasePageController return redirect()->to('profileedit'); case 'submit': $validator = Validator::make($request->all(), [ - 'email' => ['nullable', 'string', 'email', 'max:255', 'unique:users,email,'.$userid, new ValidEmailDomain()], + 'email' => ['nullable', 'string', 'email', 'max:255', 'unique:users,email,'.$userid, new ValidEmailDomain], 'password' => ['nullable', 'string', 'min:8', 'confirmed', 'regex:/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/'], ]); diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index fb9b634f3..f97e3e04f 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -12,7 +12,9 @@ use Illuminate\Http\Request; class SearchController extends BasePageController { private SearchServiceInterface $searchService; + private ReleaseSearchService $releaseSearchService; + private ReleaseBrowseService $releaseBrowseService; public function __construct( diff --git a/app/Http/Controllers/SearchSuggestController.php b/app/Http/Controllers/SearchSuggestController.php index cf1e6c51a..787e79471 100644 --- a/app/Http/Controllers/SearchSuggestController.php +++ b/app/Http/Controllers/SearchSuggestController.php @@ -170,4 +170,3 @@ class SearchSuggestController extends Controller return response()->json($response); } } - diff --git a/app/Http/Resources/AnidbCollection.php b/app/Http/Resources/AnidbCollection.php index cbcbfd3d7..9190c232b 100644 --- a/app/Http/Resources/AnidbCollection.php +++ b/app/Http/Resources/AnidbCollection.php @@ -29,4 +29,3 @@ class AnidbCollection extends ResourceCollection ]; } } - diff --git a/app/Http/Resources/AnidbResource.php b/app/Http/Resources/AnidbResource.php index 426038bd3..0ff03cd6a 100644 --- a/app/Http/Resources/AnidbResource.php +++ b/app/Http/Resources/AnidbResource.php @@ -71,4 +71,3 @@ class AnidbResource extends JsonResource return null; } } - diff --git a/app/Http/Resources/BookCollection.php b/app/Http/Resources/BookCollection.php index 930b345a5..703934453 100644 --- a/app/Http/Resources/BookCollection.php +++ b/app/Http/Resources/BookCollection.php @@ -29,4 +29,3 @@ class BookCollection extends ResourceCollection ]; } } - diff --git a/app/Http/Resources/BookResource.php b/app/Http/Resources/BookResource.php index 8e95f475d..7df0e52e2 100644 --- a/app/Http/Resources/BookResource.php +++ b/app/Http/Resources/BookResource.php @@ -55,4 +55,3 @@ class BookResource extends JsonResource return null; } } - diff --git a/app/Http/Resources/MovieCollection.php b/app/Http/Resources/MovieCollection.php index 14b0515aa..0f1df8a35 100644 --- a/app/Http/Resources/MovieCollection.php +++ b/app/Http/Resources/MovieCollection.php @@ -125,4 +125,3 @@ class MovieCollection extends ResourceCollection return null; } } - diff --git a/app/Http/Resources/MovieResource.php b/app/Http/Resources/MovieResource.php index f6beb0273..8724c77ad 100644 --- a/app/Http/Resources/MovieResource.php +++ b/app/Http/Resources/MovieResource.php @@ -68,4 +68,3 @@ class MovieResource extends JsonResource return url('/covers/movies/'.$this->imdbid.'-backdrop.jpg'); } } - diff --git a/app/Models/Category.php b/app/Models/Category.php index 9a58cd4a6..e31479902 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -309,7 +309,7 @@ class Category extends Model public static function getCategorySearch(array $cat = [], ?string $searchType = null, $builder = false): string|array|null { // Generate a cache key based on the input parameters - $cacheKey = 'cat_search_' . md5(serialize($cat) . $searchType . ($builder ? '1' : '0')); + $cacheKey = 'cat_search_'.md5(serialize($cat).$searchType.($builder ? '1' : '0')); $cached = Cache::get($cacheKey); if ($cached !== null) { return $cached; @@ -342,7 +342,7 @@ class Category extends Model $categoryInt = (int) $category; if (is_numeric($category) && $categoryInt !== -1 && self::isParent($categoryInt)) { // Cache child category IDs for parent categories - $childCacheKey = 'cat_children_' . $categoryInt; + $childCacheKey = 'cat_children_'.$categoryInt; $children = Cache::get($childCacheKey); if ($children === null) { $children = RootCategory::find($categoryInt)->categories->pluck('id')->toArray(); @@ -363,6 +363,7 @@ class Category extends Model default => $categories, }; Cache::put($cacheKey, $result, now()->addHours(24)); + return $result; } @@ -373,6 +374,7 @@ class Category extends Model }; Cache::put($cacheKey, $result, now()->addHours(24)); + return $result; } @@ -401,7 +403,7 @@ class Category extends Model public static function isParent($cid): bool { // Cache the parent category check to avoid repeated DB queries - $cacheKey = 'cat_is_parent_' . $cid; + $cacheKey = 'cat_is_parent_'.$cid; $cached = Cache::get($cacheKey); if ($cached !== null) { return $cached; diff --git a/app/Models/Content.php b/app/Models/Content.php index 374e541e4..934d4120b 100644 --- a/app/Models/Content.php +++ b/app/Models/Content.php @@ -44,15 +44,19 @@ class Content extends Model { // Content type constants public const TYPE_USEFUL = 1; + public const TYPE_INDEX = 3; // Status constants public const STATUS_ENABLED = 1; + public const STATUS_DISABLED = 0; // Role constants (aligned with User roles) public const ROLE_EVERYONE = 0; + public const ROLE_LOGGED_IN = 1; + public const ROLE_ADMIN = 2; /** diff --git a/app/Models/GamesInfo.php b/app/Models/GamesInfo.php index de494e579..a30b76902 100644 --- a/app/Models/GamesInfo.php +++ b/app/Models/GamesInfo.php @@ -25,7 +25,6 @@ use Laravel\Scout\Searchable; * @property string $classused * @property \Carbon\Carbon|null $created_at * @property \Carbon\Carbon|null $updated_at - * * @property-read Genre|null $genre * @property-read \Illuminate\Database\Eloquent\Collection|Release[] $releases * @@ -113,11 +112,12 @@ class GamesInfo extends Model */ public function getCoverPath(): ?string { - if (!$this->cover) { + if (! $this->cover) { return null; } - $path = config('nntmux_settings.covers_path') . '/games/' . $this->id . '.jpg'; + $path = config('nntmux_settings.covers_path').'/games/'.$this->id.'.jpg'; + return file_exists($path) ? $path : null; } @@ -126,11 +126,12 @@ class GamesInfo extends Model */ public function getBackdropPath(): ?string { - if (!$this->backdrop) { + if (! $this->backdrop) { return null; } - $path = config('nntmux_settings.covers_path') . '/games/' . $this->id . '-backdrop.jpg'; + $path = config('nntmux_settings.covers_path').'/games/'.$this->id.'-backdrop.jpg'; + return file_exists($path) ? $path : null; } } diff --git a/app/Models/Genre.php b/app/Models/Genre.php index 434e0727e..1a9ff505e 100644 --- a/app/Models/Genre.php +++ b/app/Models/Genre.php @@ -135,18 +135,18 @@ class Genre extends Model $typeSql = ! empty($type) ? ' AND g.type = '.intval($type) : ''; $sql = sprintf( - "SELECT g.* + 'SELECT g.* FROM genres g - INNER JOIN (SELECT DISTINCT genres_id FROM musicinfo) x ON x.genres_id = g.id WHERE 1=1 %1\$s + INNER JOIN (SELECT DISTINCT genres_id FROM musicinfo) x ON x.genres_id = g.id WHERE 1=1 %1$s UNION SELECT g.* FROM genres g - INNER JOIN (SELECT DISTINCT genres_id FROM consoleinfo) x ON x.genres_id = g.id WHERE 1=1 %1\$s + INNER JOIN (SELECT DISTINCT genres_id FROM consoleinfo) x ON x.genres_id = g.id WHERE 1=1 %1$s UNION SELECT g.* FROM genres g - INNER JOIN (SELECT DISTINCT genres_id FROM gamesinfo) x ON x.genres_id = g.id WHERE 1=1 %1\$s - ORDER BY title", + INNER JOIN (SELECT DISTINCT genres_id FROM gamesinfo) x ON x.genres_id = g.id WHERE 1=1 %1$s + ORDER BY title', $typeSql ); diff --git a/app/Models/Release.php b/app/Models/Release.php index 6ec4f3f51..b514c1eec 100644 --- a/app/Models/Release.php +++ b/app/Models/Release.php @@ -9,7 +9,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; -use Illuminate\Support\Arr; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; diff --git a/app/Models/RolePromotion.php b/app/Models/RolePromotion.php index 1de8bebf4..ec3bcee76 100644 --- a/app/Models/RolePromotion.php +++ b/app/Models/RolePromotion.php @@ -68,6 +68,7 @@ class RolePromotion extends Model public function scopeValid($query) { $now = Carbon::now(); + return $query->where('is_active', true) ->where(function ($q) use ($now) { $q->whereNull('end_date') @@ -85,6 +86,7 @@ class RolePromotion extends Model public static function getCustomRoles() { $systemRoles = ['Admin', 'User', 'Moderator', 'Disabled', 'Friend']; + return Role::whereNotIn('name', $systemRoles)->get(); } @@ -93,7 +95,7 @@ class RolePromotion extends Model */ public function isCurrentlyActive(): bool { - if (!$this->is_active) { + if (! $this->is_active) { return false; } @@ -243,4 +245,3 @@ class RolePromotion extends Model ]; } } - diff --git a/app/Models/RolePromotionStat.php b/app/Models/RolePromotionStat.php index 582950a79..d7e368b78 100644 --- a/app/Models/RolePromotionStat.php +++ b/app/Models/RolePromotionStat.php @@ -157,4 +157,3 @@ class RolePromotionStat extends Model ]); } } - diff --git a/app/Models/UsenetGroup.php b/app/Models/UsenetGroup.php index c1b3e1544..27747c7bc 100644 --- a/app/Models/UsenetGroup.php +++ b/app/Models/UsenetGroup.php @@ -2,9 +2,9 @@ namespace App\Models; +use App\Services\NNTP\NNTPService; use App\Services\Nzb\NzbService; use App\Services\ReleaseImageService; -use App\Services\NNTP\NNTPService; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -425,7 +425,7 @@ class UsenetGroup extends Model if (preg_match('/^\s*$/m', $groupList)) { $ret = 'No group list provided.'; } else { - $nntp = new NNTPService(); + $nntp = new NNTPService; if ($nntp->doConnect() !== true) { return 'Problem connecting to usenet.'; } diff --git a/app/Models/User.php b/app/Models/User.php index 9db08c4d9..414560a2e 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -452,6 +452,7 @@ final class User extends Authenticatable return $stackedHistory->map(function ($history) { $role = Role::find($history->new_role_id); + return [ 'role' => $role, 'role_name' => $role?->name ?? 'Unknown Role', @@ -504,7 +505,7 @@ final class User extends Authenticatable ?string $createdFrom = '', ?string $createdTo = '', ): int { - return static::query() + return self::query() ->withTrashed() ->excludeSharing() ->when($role, fn (Builder $q) => $q->where('roles_id', $role)) @@ -521,7 +522,7 @@ final class User extends Authenticatable */ public static function findByUsername(string $username): ?static { - return static::whereUsername($username)->first(); + return self::whereUsername($username)->first(); } /** @@ -1074,6 +1075,7 @@ final class User extends Authenticatable * Get paginated user list with filters. * * @return Collection + * * @throws \Throwable */ public static function getRange( @@ -1343,8 +1345,6 @@ final class User extends Authenticatable /** * Create a new user. - * - * @return int|false */ public static function add( string $userName, @@ -1380,6 +1380,7 @@ final class User extends Authenticatable * Get excluded category IDs for a user. * * @return array + * * @throws \Exception */ public static function getCategoryExclusionById(int $userId): array diff --git a/app/Models/UserRoleHistory.php b/app/Models/UserRoleHistory.php index 9324cac87..757a7d1a7 100644 --- a/app/Models/UserRoleHistory.php +++ b/app/Models/UserRoleHistory.php @@ -127,4 +127,3 @@ class UserRoleHistory extends Model ->get(); } } - diff --git a/app/Observers/MovieInfoObserver.php b/app/Observers/MovieInfoObserver.php index f24aafd2f..4893b9d64 100644 --- a/app/Observers/MovieInfoObserver.php +++ b/app/Observers/MovieInfoObserver.php @@ -66,4 +66,3 @@ class MovieInfoObserver } } } - diff --git a/app/Observers/ReleaseObserver.php b/app/Observers/ReleaseObserver.php index 8d82e724d..693fb1fa1 100644 --- a/app/Observers/ReleaseObserver.php +++ b/app/Observers/ReleaseObserver.php @@ -130,4 +130,3 @@ class ReleaseObserver } } } - diff --git a/app/Observers/RolePromotionObserver.php b/app/Observers/RolePromotionObserver.php index 34373eebc..ca7ca1efe 100644 --- a/app/Observers/RolePromotionObserver.php +++ b/app/Observers/RolePromotionObserver.php @@ -34,4 +34,3 @@ class RolePromotionObserver } } } - diff --git a/app/Observers/VideoObserver.php b/app/Observers/VideoObserver.php index c6d79ed33..006b1df30 100644 --- a/app/Observers/VideoObserver.php +++ b/app/Observers/VideoObserver.php @@ -65,4 +65,3 @@ class VideoObserver } } } - diff --git a/app/Providers/AdditionalProcessingServiceProvider.php b/app/Providers/AdditionalProcessingServiceProvider.php index 4586f8c93..b76f32b02 100644 --- a/app/Providers/AdditionalProcessingServiceProvider.php +++ b/app/Providers/AdditionalProcessingServiceProvider.php @@ -11,13 +11,13 @@ use App\Services\AdditionalProcessing\NzbContentParser; use App\Services\AdditionalProcessing\ReleaseFileManager; use App\Services\AdditionalProcessing\UsenetDownloadService; use App\Services\Categorization\CategorizationService; -use App\Services\Nzb\NzbParserService; -use App\Services\Nzb\NzbService; -use App\Services\ReleaseImageService; -use App\Services\TempWorkspaceService; use App\Services\NameFixing\NameFixingService; use App\Services\NfoService; +use App\Services\Nzb\NzbParserService; +use App\Services\Nzb\NzbService; use App\Services\ReleaseExtraService; +use App\Services\ReleaseImageService; +use App\Services\TempWorkspaceService; use Illuminate\Support\ServiceProvider; /** @@ -33,23 +33,25 @@ class AdditionalProcessingServiceProvider extends ServiceProvider { // Configuration is a singleton since it loads settings once $this->app->singleton(ProcessingConfiguration::class, function () { - return new ProcessingConfiguration(); + return new ProcessingConfiguration; }); // Release extra service for video/audio/subtitle data $this->app->singleton(ReleaseExtraService::class, function () { - return new ReleaseExtraService(); + return new ReleaseExtraService; }); // Console output service $this->app->singleton(ConsoleOutputService::class, function ($app) { $config = $app->make(ProcessingConfiguration::class); + return new ConsoleOutputService($config->echoCLI); }); // NZB content parser $this->app->singleton(NzbContentParser::class, function ($app) { $config = $app->make(ProcessingConfiguration::class); + return new NzbContentParser( $app->make(NzbService::class), $app->make(NzbParserService::class), @@ -77,10 +79,10 @@ class AdditionalProcessingServiceProvider extends ServiceProvider return new ReleaseFileManager( $app->make(ProcessingConfiguration::class), $app->make(ReleaseExtraService::class), - new ReleaseImageService(), - new NfoService(), + new ReleaseImageService, + new NfoService, $app->make(NzbService::class), - new NameFixingService() + new NameFixingService ); }); @@ -88,15 +90,15 @@ class AdditionalProcessingServiceProvider extends ServiceProvider $this->app->singleton(MediaExtractionService::class, function ($app) { return new MediaExtractionService( $app->make(ProcessingConfiguration::class), - new ReleaseImageService(), + new ReleaseImageService, $app->make(ReleaseExtraService::class), - new CategorizationService() + new CategorizationService ); }); // Temp workspace service (might already be registered elsewhere) $this->app->singleton(TempWorkspaceService::class, function () { - return new TempWorkspaceService(); + return new TempWorkspaceService; }); // Main orchestrator @@ -122,4 +124,3 @@ class AdditionalProcessingServiceProvider extends ServiceProvider // } } - diff --git a/app/Providers/AdultProcessingServiceProvider.php b/app/Providers/AdultProcessingServiceProvider.php index 9ceb5901c..d1fa09aca 100644 --- a/app/Providers/AdultProcessingServiceProvider.php +++ b/app/Providers/AdultProcessingServiceProvider.php @@ -20,13 +20,13 @@ class AdultProcessingServiceProvider extends ServiceProvider public function register(): void { // Register individual pipes as singletons - $this->app->singleton(AebnPipe::class, fn () => new AebnPipe()); - $this->app->singleton(IafdPipe::class, fn () => new IafdPipe()); - $this->app->singleton(Data18Pipe::class, fn () => new Data18Pipe()); - $this->app->singleton(PoppornPipe::class, fn () => new PoppornPipe()); - $this->app->singleton(AdmPipe::class, fn () => new AdmPipe()); - $this->app->singleton(AdePipe::class, fn () => new AdePipe()); - $this->app->singleton(HotmoviesPipe::class, fn () => new HotmoviesPipe()); + $this->app->singleton(AebnPipe::class, fn () => new AebnPipe); + $this->app->singleton(IafdPipe::class, fn () => new IafdPipe); + $this->app->singleton(Data18Pipe::class, fn () => new Data18Pipe); + $this->app->singleton(PoppornPipe::class, fn () => new PoppornPipe); + $this->app->singleton(AdmPipe::class, fn () => new AdmPipe); + $this->app->singleton(AdePipe::class, fn () => new AdePipe); + $this->app->singleton(HotmoviesPipe::class, fn () => new HotmoviesPipe); // Register the pipeline with all default pipes $this->app->singleton(AdultProcessingPipeline::class, function ($app) { @@ -50,4 +50,3 @@ class AdultProcessingServiceProvider extends ServiceProvider // } } - diff --git a/app/Providers/CategorizationServiceProvider.php b/app/Providers/CategorizationServiceProvider.php index 3dfaf0ba8..e5b477452 100644 --- a/app/Providers/CategorizationServiceProvider.php +++ b/app/Providers/CategorizationServiceProvider.php @@ -3,8 +3,6 @@ namespace App\Providers; use App\Services\Categorization\CategorizationPipeline; -use App\Services\Categorization\Categorizers; -use App\Services\Categorization\Contracts\CategorizerInterface; use Illuminate\Support\ServiceProvider; class CategorizationServiceProvider extends ServiceProvider @@ -31,4 +29,3 @@ class CategorizationServiceProvider extends ServiceProvider // } } - diff --git a/app/Providers/ProcessingServiceProvider.php b/app/Providers/ProcessingServiceProvider.php index 8c702b80c..9e1fc73ae 100644 --- a/app/Providers/ProcessingServiceProvider.php +++ b/app/Providers/ProcessingServiceProvider.php @@ -16,11 +16,11 @@ class ProcessingServiceProvider extends ServiceProvider public function register(): void { $this->app->singleton(PostProcessService::class, function ($app) { - return new PostProcessService(); + return new PostProcessService; }); $this->app->singleton(ReleaseProcessingService::class, function ($app) { - return new ReleaseProcessingService(); + return new ReleaseProcessingService; }); } @@ -32,4 +32,3 @@ class ProcessingServiceProvider extends ServiceProvider // } } - diff --git a/app/Providers/SearchServiceProvider.php b/app/Providers/SearchServiceProvider.php index caf753250..519061125 100644 --- a/app/Providers/SearchServiceProvider.php +++ b/app/Providers/SearchServiceProvider.php @@ -41,7 +41,7 @@ class SearchServiceProvider extends ServiceProvider // Register the MediaSearchService for optimized movie/TV searches $this->app->singleton(MediaSearchService::class, function (Application $app) { - return new MediaSearchService(); + return new MediaSearchService; }); // Register individual drivers as singletons for direct access @@ -72,4 +72,3 @@ class SearchServiceProvider extends ServiceProvider // } } - diff --git a/app/Providers/TvProcessingServiceProvider.php b/app/Providers/TvProcessingServiceProvider.php index 759e60432..8cfd22570 100644 --- a/app/Providers/TvProcessingServiceProvider.php +++ b/app/Providers/TvProcessingServiceProvider.php @@ -27,4 +27,3 @@ class TvProcessingServiceProvider extends ServiceProvider // } } - diff --git a/app/Rules/ValidEmailDomain.php b/app/Rules/ValidEmailDomain.php index 6ac28fad2..1a3f20809 100644 --- a/app/Rules/ValidEmailDomain.php +++ b/app/Rules/ValidEmailDomain.php @@ -89,6 +89,7 @@ class ValidEmailDomain implements ValidationRule { if (empty($value) || ! is_string($value)) { $fail('The :attribute must be a valid email address.'); + return; } @@ -96,6 +97,7 @@ class ValidEmailDomain implements ValidationRule $parts = explode('@', $value); if (count($parts) !== 2) { $fail('The :attribute must be a valid email address.'); + return; } @@ -105,12 +107,14 @@ class ValidEmailDomain implements ValidationRule // Validate local part is not empty if (empty($localPart)) { $fail('The :attribute must be a valid email address.'); + return; } // Validate domain is not empty if (empty($domain)) { $fail('The :attribute must be a valid email address.'); + return; } @@ -121,6 +125,7 @@ class ValidEmailDomain implements ValidationRule 'domain' => $domain, ]); $fail('Temporary or disposable email addresses are not allowed.'); + return; } @@ -131,6 +136,7 @@ class ValidEmailDomain implements ValidationRule 'domain' => $domain, ]); $fail('Temporary or disposable email addresses are not allowed.'); + return; } @@ -143,6 +149,7 @@ class ValidEmailDomain implements ValidationRule 'pattern' => $pattern, ]); $fail('Temporary or disposable email addresses are not allowed.'); + return; } } @@ -154,6 +161,7 @@ class ValidEmailDomain implements ValidationRule 'domain' => $domain, ]); $fail('The email domain does not appear to be valid or reachable.'); + return; } @@ -206,4 +214,3 @@ class ValidEmailDomain implements ValidationRule return false; } } - diff --git a/app/Services/AdditionalProcessing/AdditionalProcessingOrchestrator.php b/app/Services/AdditionalProcessing/AdditionalProcessingOrchestrator.php index c35608f27..e2ce02313 100644 --- a/app/Services/AdditionalProcessing/AdditionalProcessingOrchestrator.php +++ b/app/Services/AdditionalProcessing/AdditionalProcessingOrchestrator.php @@ -24,8 +24,11 @@ class AdditionalProcessingOrchestrator public const int MAX_COMPRESSED_FILES_TO_CHECK = 10; private Collection $releases; + private int $totalReleases = 0; + private string $mainTmpPath = ''; + private array $triedCompressedMids = []; public function __construct( @@ -64,6 +67,7 @@ class AdditionalProcessingOrchestrator $release = Release::where('guid', $guid)->first(); if ($release === null) { $this->output->warning('Release not found for GUID: '.$guid); + return false; } @@ -79,6 +83,7 @@ class AdditionalProcessingOrchestrator if ($this->config->debugMode) { Log::error('processSingleGuid failed: '.$e->getMessage()); } + return false; } } @@ -162,6 +167,7 @@ class AdditionalProcessingOrchestrator $context->tmpPath = $this->tempWorkspace->createReleaseTempFolder($this->mainTmpPath, $release->guid); } catch (\Throwable $e) { $this->output->warning('Unable to create directory: '.$e->getMessage()); + continue; } @@ -170,6 +176,7 @@ class AdditionalProcessingOrchestrator if ($nzbResult['error'] !== null) { $this->output->warning($nzbResult['error']); $this->releaseManager->deleteRelease($release); + continue; } $context->nzbContents = $nzbResult['contents']; @@ -493,6 +500,7 @@ class AdditionalProcessingOrchestrator if ($result['hasPassword']) { $context->releaseHasPassword = true; $context->passwordStatus = $result['passwordStatus']; + return false; } @@ -622,6 +630,7 @@ class AdditionalProcessingOrchestrator $context, $this->archiveService->getPar2Info() ); + continue; } @@ -632,6 +641,7 @@ class AdditionalProcessingOrchestrator if ($this->releaseManager->processNfoFile($filePath, $context, $this->downloadService->getNNTP())) { $this->output->echoNfoFound(); } + continue; } // Alternative NFO filenames (file_id.diz, readme.txt, etc.) @@ -639,6 +649,7 @@ class AdditionalProcessingOrchestrator if ($this->releaseManager->processNfoFile($filePath, $context, $this->downloadService->getNNTP())) { $this->output->echoNfoFound(); } + continue; } } @@ -662,6 +673,7 @@ class AdditionalProcessingOrchestrator $this->output->echoAudioSampleCreated(); } File::delete($audioPath); + continue; } @@ -672,6 +684,7 @@ class AdditionalProcessingOrchestrator $this->output->echoJpgSaved(); } File::delete($filePath); + continue; } @@ -680,6 +693,7 @@ class AdditionalProcessingOrchestrator && preg_match('/(.*)'.$this->config->videoFileRegex.'$/i', $filePath) ) { $this->mediaService->processVideoFile($filePath, $context, $context->tmpPath); + continue; } @@ -738,4 +752,3 @@ class AdditionalProcessingOrchestrator } } } - diff --git a/app/Services/AdditionalProcessing/ArchiveExtractionService.php b/app/Services/AdditionalProcessing/ArchiveExtractionService.php index 3ac85369d..23135b21d 100644 --- a/app/Services/AdditionalProcessing/ArchiveExtractionService.php +++ b/app/Services/AdditionalProcessing/ArchiveExtractionService.php @@ -17,13 +17,14 @@ use Illuminate\Support\Facades\Log; class ArchiveExtractionService { private ArchiveInfo $archiveInfo; + private Par2Info $par2Info; public function __construct( private readonly ProcessingConfiguration $config ) { - $this->archiveInfo = new ArchiveInfo(); - $this->par2Info = new Par2Info(); + $this->archiveInfo = new ArchiveInfo; + $this->par2Info = new Par2Info; // Configure external clients for ArchiveInfo if ($this->config->unrarPath) { @@ -84,6 +85,7 @@ class ArchiveExtractionService 'standaloneVideoData' => $compressedData, ]; } + return $result; } @@ -91,6 +93,7 @@ class ArchiveExtractionService if ($this->config->debugMode) { Log::debug('ArchiveInfo Error: '.$this->archiveInfo->error); } + return $result; } @@ -100,6 +103,7 @@ class ArchiveExtractionService if ($this->config->debugMode) { Log::warning($e->getTraceAsString()); } + return $result; } @@ -110,6 +114,7 @@ class ArchiveExtractionService if ($this->config->debugMode) { Log::debug('ArchiveInfo: Compressed file has a password.'); } + return [ 'success' => false, 'files' => [], @@ -189,6 +194,7 @@ class ArchiveExtractionService if ($crc === "\x00\x00\x00\x00" || $crc === "\xFF\xFF\xFF\xFF") { return false; } + return true; } @@ -238,7 +244,7 @@ class ArchiveExtractionService // Fallback: scan for filenames in raw data $scannedNames = $this->scanSevenZipFilenames($compressedData); if (! empty($scannedNames)) { - $files = array_map(fn($name) => [ + $files = array_map(fn ($name) => [ 'name' => $name, 'size' => 0, 'date' => time(), @@ -284,15 +290,18 @@ class ArchiveExtractionService // Try plain listing fallback $plainResult = $this->listSevenZipPlain($tmpFile); File::delete($tmpFile); + return $plainResult; } File::delete($tmpFile); + return $this->parseSevenZipStructuredOutput($stdout); } catch (\Throwable $e) { if ($this->config->debugMode) { Log::debug('Exception listing 7z: '.$e->getMessage()); } + return []; } } @@ -595,7 +604,7 @@ class ArchiveExtractionService /** * Check if a file is an NFO or info file. * - * @param string $filename The filename to check. + * @param string $filename The filename to check. * @return bool True if it's an NFO-like file. */ public function isNfoFile(string $filename): bool @@ -630,7 +639,7 @@ class ArchiveExtractionService /** * Sort files to prioritize NFO files for processing. * - * @param array $files Array of file info arrays. + * @param array $files Array of file info arrays. * @return array Sorted array with NFO files first. */ public function sortFilesWithNfoPriority(array $files): array @@ -694,6 +703,7 @@ class ArchiveExtractionService runCmd($killString.$this->config->unrarPath.'" e -ai -ep -c- -id -inul -kb -or -p- -r -y "'.$fileName.'" "'.$tmpPath.'unrar/"'); File::delete($fileName); } + return 'r'; case ArchiveInfo::TYPE_ZIP: @@ -703,6 +713,7 @@ class ArchiveExtractionService runCmd($this->config->unzipPath.' -o "'.$fileName.'" -d "'.$tmpPath.'unzip/"'); File::delete($fileName); } + return 'z'; } @@ -785,6 +796,7 @@ class ArchiveExtractionService $process = @proc_open($cmd, $descriptorSpec, $pipes, null, null, ['bypass_shell' => true]); if (! is_resource($process)) { $exitCode = -1; + return false; } @@ -797,4 +809,3 @@ class ArchiveExtractionService return $exitCode === 0; } } - diff --git a/app/Services/AdditionalProcessing/Config/ProcessingConfiguration.php b/app/Services/AdditionalProcessing/Config/ProcessingConfiguration.php index 1222f7dc1..838724d3d 100644 --- a/app/Services/AdditionalProcessing/Config/ProcessingConfiguration.php +++ b/app/Services/AdditionalProcessing/Config/ProcessingConfiguration.php @@ -1,6 +1,9 @@ echoCLI = (bool) config('nntmux.echocli'); @@ -100,6 +142,7 @@ final readonly class ProcessingConfiguration $this->supportFileRegex = '\\.(?:vol\\d{1,3}\\+\\d{1,3}|par2|srs|sfv|nzb)'; $this->videoFileRegex = '\\.(AVI|F4V|IFO|M1V|M2V|M4V|MKV|MOV|MP4|MPEG|MPG|MPGV|MPV|OGV|QT|RM|RMVB|TS|VOB|WMV)'; } + /** * Build the kill string for timeout command wrapper. */ @@ -108,6 +151,7 @@ final readonly class ProcessingConfiguration if ($this->timeoutPath && $this->timeoutSeconds > 0) { return '"'.$this->timeoutPath.'" --foreground --signal=KILL '.$this->timeoutSeconds.' "'; } + return '"'; } } diff --git a/app/Services/AdditionalProcessing/ConsoleOutputService.php b/app/Services/AdditionalProcessing/ConsoleOutputService.php index f736cd85f..1d37ed314 100644 --- a/app/Services/AdditionalProcessing/ConsoleOutputService.php +++ b/app/Services/AdditionalProcessing/ConsoleOutputService.php @@ -1,4 +1,5 @@ echoCLI) { cli()->$type($message); } } + public function debug(string $message): void { if ($this->echoCLI && config('app.env') === 'local' && config('app.debug') === true) { cli()->debug('DEBUG: '.$message); } } + public function echoDescription(int $totalReleases): void { if ($totalReleases > 1 && $this->echoCLI) { @@ -45,39 +48,134 @@ class ConsoleOutputService ); } } + public function echoReleaseStart(int $releaseId, int $size): void { $this->echo(PHP_EOL.'['.$releaseId.']['.human_filesize($size, 1).']', 'primaryOver'); } - public function echoCompressedDownload(): void { $this->echo('(cB)', 'primaryOver'); } - public function echoCompressedFailure(int $failCount): void { $this->echo('fC'.$failCount, 'warningOver'); } - public function echoGroupUnavailable(): void { $this->echo('G', 'warningOver'); } - public function echoSampleDownload(): void { $this->echo('(sB)', 'primaryOver'); } - public function echoSampleFailure(): void { $this->echo('fS', 'warningOver'); } - public function echoMediaInfoDownload(): void { $this->echo('(mB)', 'primaryOver'); } - public function echoMediaInfoFailure(): void { $this->echo('fM', 'warningOver'); } - public function echoAudioDownload(): void { $this->echo('(aB)', 'primaryOver'); } - public function echoAudioFailure(): void { $this->echo('fA', 'warningOver'); } - public function echoJpgDownload(): void { $this->echo('(jB)', 'primaryOver'); } - public function echoJpgFailure(): void { $this->echo('fJ', 'warningOver'); } - public function echoArchiveMarker(string $marker): void { $this->echo($marker, 'primaryOver'); } - public function echoFileInfoAdded(): void { $this->echo('^', 'primaryOver'); } - public function echoSampleCreated(): void { $this->echo('s', 'primaryOver'); } - public function echoJpgSaved(): void { $this->echo('j', 'primaryOver'); } - public function echoAudioSampleCreated(): void { $this->echo('A', 'primaryOver'); } - public function echoAudioInfoAdded(): void { $this->echo('a', 'primaryOver'); } - public function echoVideoCreated(): void { $this->echo('v', 'primaryOver'); } - public function echoMediaInfoAdded(): void { $this->echo('m', 'primaryOver'); } - public function echoNfoFound(): void { $this->echo('n', 'primaryOver'); } - public function echoInlineVideo(): void { $this->echo('(vRAW)', 'primaryOver'); } - public function warning(string $message): void { $this->echo($message, 'warning'); } - public function echoReleaseDeleted(int $releaseId): void { $this->echo('Deleted broken release ID '.$releaseId, 'warningOver'); } + + public function echoCompressedDownload(): void + { + $this->echo('(cB)', 'primaryOver'); + } + + public function echoCompressedFailure(int $failCount): void + { + $this->echo('fC'.$failCount, 'warningOver'); + } + + public function echoGroupUnavailable(): void + { + $this->echo('G', 'warningOver'); + } + + public function echoSampleDownload(): void + { + $this->echo('(sB)', 'primaryOver'); + } + + public function echoSampleFailure(): void + { + $this->echo('fS', 'warningOver'); + } + + public function echoMediaInfoDownload(): void + { + $this->echo('(mB)', 'primaryOver'); + } + + public function echoMediaInfoFailure(): void + { + $this->echo('fM', 'warningOver'); + } + + public function echoAudioDownload(): void + { + $this->echo('(aB)', 'primaryOver'); + } + + public function echoAudioFailure(): void + { + $this->echo('fA', 'warningOver'); + } + + public function echoJpgDownload(): void + { + $this->echo('(jB)', 'primaryOver'); + } + + public function echoJpgFailure(): void + { + $this->echo('fJ', 'warningOver'); + } + + public function echoArchiveMarker(string $marker): void + { + $this->echo($marker, 'primaryOver'); + } + + public function echoFileInfoAdded(): void + { + $this->echo('^', 'primaryOver'); + } + + public function echoSampleCreated(): void + { + $this->echo('s', 'primaryOver'); + } + + public function echoJpgSaved(): void + { + $this->echo('j', 'primaryOver'); + } + + public function echoAudioSampleCreated(): void + { + $this->echo('A', 'primaryOver'); + } + + public function echoAudioInfoAdded(): void + { + $this->echo('a', 'primaryOver'); + } + + public function echoVideoCreated(): void + { + $this->echo('v', 'primaryOver'); + } + + public function echoMediaInfoAdded(): void + { + $this->echo('m', 'primaryOver'); + } + + public function echoNfoFound(): void + { + $this->echo('n', 'primaryOver'); + } + + public function echoInlineVideo(): void + { + $this->echo('(vRAW)', 'primaryOver'); + } + + public function warning(string $message): void + { + $this->echo($message, 'warning'); + } + + public function echoReleaseDeleted(int $releaseId): void + { + $this->echo('Deleted broken release ID '.$releaseId, 'warningOver'); + } + public function endOutput(): void { if ($this->echoCLI) { echo PHP_EOL; } } + public function isEnabled(): bool { return $this->echoCLI; diff --git a/app/Services/AdditionalProcessing/DTO/ReleaseProcessingContext.php b/app/Services/AdditionalProcessing/DTO/ReleaseProcessingContext.php index 8d1910baa..f13cc4be4 100644 --- a/app/Services/AdditionalProcessing/DTO/ReleaseProcessingContext.php +++ b/app/Services/AdditionalProcessing/DTO/ReleaseProcessingContext.php @@ -1,6 +1,9 @@ release = $release; } + /** * Initialize processing state based on configuration flags. * Sets "found" flags to true if processing is disabled (to skip those steps). @@ -65,6 +94,7 @@ class ReleaseProcessingContext $this->foundSample = ! $processThumbnails; $this->foundPAR2Info = false; } + /** * Reset message ID arrays for a fresh processing run. */ @@ -77,6 +107,7 @@ class ReleaseProcessingContext $this->rarFileMessageIDs = []; $this->audioInfoExtension = ''; } + /** * Reset file counters. */ @@ -86,6 +117,7 @@ class ReleaseProcessingContext $this->totalFileInfo = 0; $this->compressedFilesChecked = 0; } + /** * Full reset for processing a new release. */ @@ -98,16 +130,18 @@ class ReleaseProcessingContext $this->resetMessageIDs(); $this->resetCounters(); } + /** * Check if more media processing is needed. */ public function needsMediaProcessing(): bool { - return ! $this->foundVideo - || ! $this->foundMediaInfo - || ! $this->foundAudioInfo + return ! $this->foundVideo + || ! $this->foundMediaInfo + || ! $this->foundAudioInfo || ! $this->foundAudioSample; } + /** * Check if any sample is still needed. */ diff --git a/app/Services/AdditionalProcessing/MediaExtractionService.php b/app/Services/AdditionalProcessing/MediaExtractionService.php index a7685dabe..e03b37622 100644 --- a/app/Services/AdditionalProcessing/MediaExtractionService.php +++ b/app/Services/AdditionalProcessing/MediaExtractionService.php @@ -8,8 +8,8 @@ use App\Models\Release; use App\Services\AdditionalProcessing\Config\ProcessingConfiguration; use App\Services\AdditionalProcessing\DTO\ReleaseProcessingContext; use App\Services\Categorization\CategorizationService; -use App\Services\ReleaseImageService; use App\Services\ReleaseExtraService; +use App\Services\ReleaseImageService; use FFMpeg\Coordinate\Dimension; use FFMpeg\Coordinate\TimeCode; use FFMpeg\FFMpeg; @@ -28,7 +28,9 @@ use Mhor\MediaInfo\MediaInfo; class MediaExtractionService { private ?FFMpeg $ffmpeg = null; + private ?FFProbe $ffprobe = null; + private ?MediaInfo $mediaInfo = null; public function __construct( @@ -52,6 +54,7 @@ class MediaExtractionService if ($this->config->debugMode) { Log::debug($e->getMessage()); } + return ''; } @@ -91,6 +94,7 @@ class MediaExtractionService if ($this->config->debugMode) { Log::error($e->getTraceAsString()); } + return false; } @@ -147,7 +151,7 @@ class MediaExtractionService TimeCode::fromString($lowestLength), TimeCode::fromSeconds($this->config->ffmpegDuration) ); - $format = new Ogg(); + $format = new Ogg; $format->setAudioCodec('libvorbis'); $clip->filters()->resize(new Dimension(320, -1), ResizeFilter::RESIZEMODE_SCALE_HEIGHT); $clip->save($format, $fileName); @@ -169,7 +173,7 @@ class MediaExtractionService TimeCode::fromSeconds(0), TimeCode::fromSeconds($this->config->ffmpegDuration) ); - $format = new Ogg(); + $format = new Ogg; $format->setAudioCodec('libvorbis'); $clip->filters()->resize(new Dimension(320, -1), ResizeFilter::RESIZEMODE_SCALE_HEIGHT); $clip->save($format, $fileName); @@ -214,9 +218,11 @@ class MediaExtractionService $xmlArray = $this->mediaInfo()->getInfo($fileLocation, true); \App\Models\MediaInfo::addData($releaseId, $xmlArray); $this->releaseExtra->addFromXml($releaseId, $xmlArray); + return true; } catch (\Throwable $e) { Log::debug($e->getMessage()); + return false; } } @@ -236,6 +242,7 @@ class MediaExtractionService if ($saved === 1) { Release::query()->where('guid', $guid)->update(['jpgstatus' => 1]); + return true; } @@ -354,7 +361,7 @@ class MediaExtractionService try { if ($this->ffprobe()->isValid($fileLocation)) { $audioSample = $this->ffmpeg()->open($fileLocation); - $format = new Vorbis(); + $format = new Vorbis; $audioSample->clip(TimeCode::fromSeconds(30), TimeCode::fromSeconds(30)); $audioSample->save($format, $tmpPath.$audioFileName); } @@ -431,6 +438,7 @@ class MediaExtractionService if (! File::isFile($filePath)) { return false; } + return exif_imagetype($filePath) === IMAGETYPE_JPEG; } @@ -440,6 +448,7 @@ class MediaExtractionService $timeout = $this->config->timeoutSeconds > 0 ? $this->config->timeoutSeconds : 60; $this->ffmpeg = FFMpeg::create(['timeout' => $timeout]); } + return $this->ffmpeg; } @@ -448,18 +457,20 @@ class MediaExtractionService if ($this->ffprobe === null) { $this->ffprobe = FFProbe::create(); } + return $this->ffprobe; } private function mediaInfo(): MediaInfo { if ($this->mediaInfo === null) { - $this->mediaInfo = new MediaInfo(); + $this->mediaInfo = new MediaInfo; $this->mediaInfo->setConfig('use_oldxml_mediainfo_output_format', true); if ($this->config->mediaInfoPath) { $this->mediaInfo->setConfig('command', $this->config->mediaInfoPath); } } + return $this->mediaInfo; } @@ -468,6 +479,7 @@ class MediaExtractionService if ($this->manticore === null) { $this->manticore = app(ManticoreSearchService::class); } + return $this->manticore; } @@ -476,7 +488,7 @@ class MediaExtractionService if ($this->elasticsearch === null) { $this->elasticsearch = app(ElasticSearchService::class); } + return $this->elasticsearch; } } - diff --git a/app/Services/AdditionalProcessing/NzbContentParser.php b/app/Services/AdditionalProcessing/NzbContentParser.php index 528b9ce65..384754833 100644 --- a/app/Services/AdditionalProcessing/NzbContentParser.php +++ b/app/Services/AdditionalProcessing/NzbContentParser.php @@ -23,7 +23,7 @@ class NzbContentParser /** * Parse an NZB file and return its contents as an array of files. * - * @param string $guid The release GUID to find the NZB for + * @param string $guid The release GUID to find the NZB for * @return array{contents: array, error: string|null} */ public function parseNzb(string $guid): array @@ -119,6 +119,7 @@ class NzbContentParser $guidInfo = $guid ? ' for GUID: '.$guid : ''; echo 'NZB repair failed'.$guidInfo.' ('.count($errors).' XML errors)'.PHP_EOL; } + return null; } @@ -251,6 +252,7 @@ class NzbContentParser } $ids[] = (string) $segments[$i]; } + return $ids; } @@ -262,4 +264,3 @@ class NzbContentParser return $this->nzb->nzbPath($guid); } } - diff --git a/app/Services/AdditionalProcessing/ReleaseFileManager.php b/app/Services/AdditionalProcessing/ReleaseFileManager.php index 1650bc502..f7d6aa0be 100644 --- a/app/Services/AdditionalProcessing/ReleaseFileManager.php +++ b/app/Services/AdditionalProcessing/ReleaseFileManager.php @@ -6,16 +6,16 @@ use App\Models\MediaInfo as MediaInfoModel; use App\Models\Predb; use App\Models\Release; use App\Models\ReleaseFile; -use App\Services\Nzb\NzbService; -use App\Services\ReleaseImageService; -use App\Services\Releases\ReleaseBrowseService; use App\Services\AdditionalProcessing\Config\ProcessingConfiguration; use App\Services\AdditionalProcessing\DTO\ReleaseProcessingContext; use App\Services\NameFixing\NameFixingService; use App\Services\NameFixing\ReleaseUpdateService; use App\Services\NfoService; use App\Services\NNTP\NNTPService; +use App\Services\Nzb\NzbService; use App\Services\ReleaseExtraService; +use App\Services\ReleaseImageService; +use App\Services\Releases\ReleaseBrowseService; use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\File; @@ -27,7 +27,6 @@ use Illuminate\Support\Facades\Log; */ class ReleaseFileManager { - public function __construct( private readonly ProcessingConfiguration $config, private readonly ReleaseExtraService $releaseExtra, @@ -39,6 +38,7 @@ class ReleaseFileManager /** * Add file information to the database. + * * @throws \Exception */ public function addFileInfo( @@ -50,6 +50,7 @@ class ReleaseFileManager if ($this->config->debugMode) { Log::debug("Error: {$file['error']} (in: {$file['source']})"); } + return false; } @@ -61,6 +62,7 @@ class ReleaseFileManager if (isset($file['pass']) && $file['pass'] === true) { $context->releaseHasPassword = true; $context->passwordStatus = ReleaseBrowseService::PASSWD_RAR; + return false; } @@ -70,6 +72,7 @@ class ReleaseFileManager ) { $context->releaseHasPassword = true; $context->passwordStatus = ReleaseBrowseService::PASSWD_RAR; + return false; } @@ -357,6 +360,7 @@ class ReleaseFileManager && $this->nfo->addAlternateNfo($data, $context->release, $nntp) ) { $context->releaseHasNoNFO = false; + return true; } } catch (FileNotFoundException $e) { @@ -369,7 +373,7 @@ class ReleaseFileManager /** * Check if a filename looks like an NFO file. * - * @param string $filename The filename to check. + * @param string $filename The filename to check. * @return bool True if the filename matches NFO patterns. */ public function isNfoFilename(string $filename): bool @@ -404,7 +408,7 @@ class ReleaseFileManager * NFO files often use CP437 (DOS) encoding for ASCII art. * This method attempts to detect and convert various encodings. * - * @param string $data Raw NFO data. + * @param string $data Raw NFO data. * @return string UTF-8 encoded NFO data. */ protected function normalizeNfoEncoding(string $data): string @@ -470,7 +474,7 @@ class ReleaseFileManager $candidate = $this->normalizeCandidateTitle($candidate); if ($this->isPlausibleReleaseTitle($candidate)) { - (new ReleaseUpdateService())->updateRelease( + (new ReleaseUpdateService)->updateRelease( $context->release, $candidate, 'RarInfo FileName Match', @@ -496,7 +500,7 @@ class ReleaseFileManager $candidate = $this->normalizeCandidateTitle($candidate); if ($this->isPlausibleReleaseTitle($candidate)) { - (new ReleaseUpdateService())->updateRelease( + (new ReleaseUpdateService)->updateRelease( $context->release, $candidate, 'RarInfo FileName Match', @@ -544,6 +548,7 @@ class ReleaseFileManager $t = preg_replace('/\.(par2?|nfo|sfv|nzb|rar|zip|r\d{2,3}|pkg|exe|msi|jpe?g|png|gif|bmp)$/i', '', $t) ?? $t; $t = preg_replace('/[.\-_ ](?:part|vol|r)\d+(?:\+\d+)?$/i', '', $t) ?? $t; $t = preg_replace('/[\s_]+/', ' ', $t) ?? $t; + return trim($t, " .-_\t\r\n"); } @@ -579,4 +584,3 @@ class ReleaseFileManager return $hasGroupSuffix || ($hasTV && $hasQuality) || ($hasYear && ($hasQuality || $hasTV)) || $hasXXX; } } - diff --git a/app/Services/AdditionalProcessing/UsenetDownloadService.php b/app/Services/AdditionalProcessing/UsenetDownloadService.php index 178dc9ac3..8f88a24bb 100644 --- a/app/Services/AdditionalProcessing/UsenetDownloadService.php +++ b/app/Services/AdditionalProcessing/UsenetDownloadService.php @@ -18,16 +18,17 @@ class UsenetDownloadService public function __construct( private readonly ProcessingConfiguration $config ) { - $this->nntp = new NNTPService(); + $this->nntp = new NNTPService; } /** * Download binary content from usenet using message IDs. * - * @param array|string $messageIDs Single or array of message IDs - * @param string $groupName Group name for logging - * @param int|null $releaseId Release ID for logging + * @param array|string $messageIDs Single or array of message IDs + * @param string $groupName Group name for logging + * @param int|null $releaseId Release ID for logging * @return array{success: bool, data: string|null, groupUnavailable: bool, error: string|null} + * * @throws Exception */ public function downloadByMessageIDs( @@ -44,6 +45,7 @@ class UsenetDownloadService if (empty($messageIDs)) { $result['error'] = 'No message IDs provided'; + return $result; } @@ -75,6 +77,7 @@ class UsenetDownloadService ) { $result['groupUnavailable'] = true; $result['error'] = 'Group unavailable: '.$errorMessage; + return $result; } } @@ -92,11 +95,13 @@ class UsenetDownloadService } $result['error'] = $errorMessage ?? 'Download failed'; + return $result; } $result['success'] = true; $result['data'] = $binary; + return $result; } @@ -147,11 +152,12 @@ class UsenetDownloadService /** * Download compressed file content (RAR, ZIP, etc.). * - * @param array $messageIDs Message IDs to download - * @param string $groupName Group name for logging - * @param int|null $releaseId Release ID for logging - * @param string|null $fileTitle File title for logging + * @param array $messageIDs Message IDs to download + * @param string $groupName Group name for logging + * @param int|null $releaseId Release ID for logging + * @param string|null $fileTitle File title for logging * @return array{success: bool, data: string|null, groupUnavailable: bool, error: string|null} + * * @throws Exception */ public function downloadCompressedFile( @@ -200,4 +206,3 @@ class UsenetDownloadService return strlen($data) > $minimumBytes; } } - diff --git a/app/Services/AdultProcessing/AdultProcessingPassable.php b/app/Services/AdultProcessing/AdultProcessingPassable.php index 482321b25..71b7f28b5 100644 --- a/app/Services/AdultProcessing/AdultProcessingPassable.php +++ b/app/Services/AdultProcessing/AdultProcessingPassable.php @@ -8,9 +8,13 @@ namespace App\Services\AdultProcessing; class AdultProcessingPassable { public AdultReleaseContext $context; + public AdultProcessingResult $result; + public bool $debug; + public array $providerResults = []; + public ?string $cookie = null; public function __construct(AdultReleaseContext $context, bool $debug = false, ?string $cookie = null) @@ -88,4 +92,3 @@ class AdultProcessingPassable return $returnValue; } } - diff --git a/app/Services/AdultProcessing/AdultProcessingPipeline.php b/app/Services/AdultProcessing/AdultProcessingPipeline.php index e449cc707..62e2c7fa7 100644 --- a/app/Services/AdultProcessing/AdultProcessingPipeline.php +++ b/app/Services/AdultProcessing/AdultProcessingPipeline.php @@ -37,10 +37,15 @@ class AdultProcessingPipeline protected Collection $pipes; protected int $movieQty; + protected bool $echoOutput; + protected ReleaseImageService $releaseImage; + protected string $imgSavePath; + protected string $cookie; + protected string $showPasswords; /** @@ -56,7 +61,7 @@ class AdultProcessingPipeline ]; /** - * @param iterable $pipes + * @param iterable $pipes */ public function __construct(iterable $pipes = [], bool $echoOutput = true) { @@ -79,7 +84,7 @@ class AdultProcessingPipeline } $this->echoOutput = $echoOutput; - $this->releaseImage = new ReleaseImageService(); + $this->releaseImage = new ReleaseImageService; $this->imgSavePath = storage_path('covers/xxx/'); $this->cookie = resource_path('tmp/xxx.cookie'); } @@ -93,6 +98,7 @@ class AdultProcessingPipeline $instance = new self([], $echoOutput); $instance->cookie = $cookie; $instance->imgSavePath = $imgSavePath; + return $instance; } @@ -102,13 +108,13 @@ class AdultProcessingPipeline protected function getDefaultPipes(): Collection { return collect([ - new AebnPipe(), - new IafdPipe(), - new Data18Pipe(), - new PoppornPipe(), - new AdmPipe(), - new AdePipe(), - new HotmoviesPipe(), + new AebnPipe, + new IafdPipe, + new Data18Pipe, + new PoppornPipe, + new AdmPipe, + new AdePipe, + new HotmoviesPipe, ]); } @@ -126,8 +132,8 @@ class AdultProcessingPipeline /** * Process a single movie title through the pipeline. * - * @param string $movie Movie title to search for - * @param bool $debug Whether to include debug information + * @param string $movie Movie title to search for + * @param bool $debug Whether to include debug information * @return array Processing result */ public function processMovie(string $movie, bool $debug = false): array @@ -152,9 +158,9 @@ class AdultProcessingPipeline /** * Process a single release through the pipeline. * - * @param array|object $release Release data - * @param string $cleanTitle Cleaned movie title - * @param bool $debug Whether to include debug information + * @param array|object $release Release data + * @param string $cleanTitle Cleaned movie title + * @param bool $debug Whether to include debug information * @return array Processing result */ public function processRelease(array|object $release, string $cleanTitle, bool $debug = false): array @@ -194,11 +200,12 @@ class AdultProcessingPipeline if ($this->echoOutput) { cli()->header('No XXX releases to process.'); } + return; } if ($this->echoOutput) { - cli()->header('Processing ' . $releaseCount . ' XXX releases using pipeline.'); + cli()->header('Processing '.$releaseCount.' XXX releases using pipeline.'); } // Process releases in batches for parallel processing @@ -207,12 +214,12 @@ class AdultProcessingPipeline foreach ($batches as $batchIndex => $batch) { if ($this->echoOutput) { - cli()->info('Processing batch ' . ($batchIndex + 1) . ' of ' . count($batches)); + cli()->info('Processing batch '.($batchIndex + 1).' of '.count($batches)); } $this->processBatch($batch); } } catch (\Throwable $e) { - Log::error('processXXXReleases failed: ' . $e->getMessage(), [ + Log::error('processXXXReleases failed: '.$e->getMessage(), [ 'exception' => get_class($e), 'file' => $e->getFile(), 'line' => $e->getLine(), @@ -220,7 +227,7 @@ class AdultProcessingPipeline ]); if ($this->echoOutput) { - cli()->error('Error during processing: ' . $e->getMessage()); + cli()->error('Error during processing: '.$e->getMessage()); } } @@ -245,14 +252,15 @@ class AdultProcessingPipeline // TODO: Re-enable async when database connection pooling is properly configured $useAsync = $this->canUseAsync() && config('nntmux.adult_processing_async', false); - if (!$useAsync) { + if (! $useAsync) { // Fall back to sequential processing (more reliable) foreach ($batch as $release) { $releaseId = (int) ($release['id'] ?? 0); if ($releaseId <= 0) { - Log::warning('Invalid release ID in batch: ' . json_encode($release)); + Log::warning('Invalid release ID in batch: '.json_encode($release)); $this->stats['failed']++; + continue; } @@ -260,17 +268,18 @@ class AdultProcessingPipeline $this->processReleaseItem($release); } catch (\Throwable $e) { // processReleaseItem should handle its own exceptions, but this is a fallback - Log::error('Unexpected error in processBatch for release ' . $releaseId . ': ' . $e->getMessage()); + Log::error('Unexpected error in processBatch for release '.$releaseId.': '.$e->getMessage()); $this->stats['failed']++; // Ensure the release is marked as processed to avoid infinite loop try { $this->updateReleaseXxxId($releaseId, -2); } catch (\Throwable $updateError) { - Log::error('Failed to update release ' . $releaseId . ' with error code: ' . $updateError->getMessage()); + Log::error('Failed to update release '.$releaseId.' with error code: '.$updateError->getMessage()); } } } + return; } @@ -308,7 +317,7 @@ class AdultProcessingPipeline $this->stats['processed']++; } } catch (\Throwable $e) { - Log::error('Async batch processing failed: ' . $e->getMessage()); + Log::error('Async batch processing failed: '.$e->getMessage()); // Mark all releases in batch as processed with error to avoid infinite loop foreach ($batch as $release) { @@ -316,7 +325,7 @@ class AdultProcessingPipeline try { Release::query()->where('id', $release['id'])->update(['xxxinfo_id' => -2]); } catch (\Throwable $updateError) { - Log::error('Failed to mark release ' . $release['id'] . ' as processed: ' . $updateError->getMessage()); + Log::error('Failed to mark release '.$release['id'].' as processed: '.$updateError->getMessage()); } } } @@ -348,6 +357,7 @@ class AdultProcessingPipeline if ($cleanTitle === false) { Release::query()->where('id', $releaseData['id'])->update(['xxxinfo_id' => -2]); + return $result; } @@ -389,6 +399,7 @@ class AdultProcessingPipeline if ($releaseId <= 0) { Log::warning('Invalid release ID in processReleaseItem'); $this->stats['failed']++; + return $idCheck; } @@ -401,6 +412,7 @@ class AdultProcessingPipeline } $this->updateReleaseXxxId($releaseId, $idCheck); $this->stats['skipped']++; + return $idCheck; } @@ -409,12 +421,12 @@ class AdultProcessingPipeline if ($existingInfo !== null) { if ($this->echoOutput) { - cli()->info('Local match found for XXX Movie: ' . $cleanTitle); + cli()->info('Local match found for XXX Movie: '.$cleanTitle); } $idCheck = (int) $existingInfo['id']; } else { if ($this->echoOutput) { - cli()->info('Looking up: ' . $cleanTitle); + cli()->info('Looking up: '.$cleanTitle); cli()->info('Local match not found, checking web!'); } @@ -433,7 +445,7 @@ class AdultProcessingPipeline $this->stats['matched']++; } } catch (\Throwable $e) { - Log::error('Processing failed for release ' . $releaseId . ': ' . $e->getMessage(), [ + Log::error('Processing failed for release '.$releaseId.': '.$e->getMessage(), [ 'exception' => get_class($e), 'file' => $e->getFile(), 'line' => $e->getLine(), @@ -444,7 +456,7 @@ class AdultProcessingPipeline try { $this->updateReleaseXxxId($releaseId, -2); } catch (\Throwable $updateError) { - Log::error('Failed to update release ' . $releaseId . ' after processing error: ' . $updateError->getMessage()); + Log::error('Failed to update release '.$releaseId.' after processing error: '.$updateError->getMessage()); } $this->stats['failed']++; } @@ -477,29 +489,29 @@ class AdultProcessingPipeline 'hotm' => 'HotMovies', default => $providerName, }; - cli()->primary('Fetching XXX info from: ' . $fromStr); + cli()->primary('Fetching XXX info from: '.$fromStr); } // Track provider usage $this->stats['providers'][$providerName] = ($this->stats['providers'][$providerName] ?? 0) + 1; // Prepare the movie data for database storage - $cast = !empty($movieData['cast']) ? implode(',', (array) $movieData['cast']) : ''; - $genres = !empty($movieData['genres']) ? $this->getGenreID($movieData['genres']) : ''; + $cast = ! empty($movieData['cast']) ? implode(',', (array) $movieData['cast']) : ''; + $genres = ! empty($movieData['genres']) ? $this->getGenreID($movieData['genres']) : ''; $mov = [ - 'trailers' => !empty($movieData['trailers']) ? serialize($movieData['trailers']) : '', - 'extras' => !empty($movieData['extras']) ? serialize($movieData['extras']) : '', - 'productinfo' => !empty($movieData['productinfo']) ? serialize($movieData['productinfo']) : '', - 'backdrop' => !empty($movieData['backcover']) ? $movieData['backcover'] : 0, - 'cover' => !empty($movieData['boxcover']) ? $movieData['boxcover'] : 0, - 'title' => !empty($movieData['title']) ? html_entity_decode($movieData['title'], ENT_QUOTES, 'UTF-8') : '', - 'plot' => !empty($movieData['synopsis']) ? html_entity_decode($movieData['synopsis'], ENT_QUOTES, 'UTF-8') : '', - 'tagline' => !empty($movieData['tagline']) ? html_entity_decode($movieData['tagline'], ENT_QUOTES, 'UTF-8') : '', - 'genre' => !empty($genres) ? html_entity_decode($genres, ENT_QUOTES, 'UTF-8') : '', - 'director' => !empty($movieData['director']) ? html_entity_decode($movieData['director'], ENT_QUOTES, 'UTF-8') : '', - 'actors' => !empty($cast) ? html_entity_decode($cast, ENT_QUOTES, 'UTF-8') : '', - 'directurl' => !empty($movieData['directurl']) ? html_entity_decode($movieData['directurl'], ENT_QUOTES, 'UTF-8') : '', + 'trailers' => ! empty($movieData['trailers']) ? serialize($movieData['trailers']) : '', + 'extras' => ! empty($movieData['extras']) ? serialize($movieData['extras']) : '', + 'productinfo' => ! empty($movieData['productinfo']) ? serialize($movieData['productinfo']) : '', + 'backdrop' => ! empty($movieData['backcover']) ? $movieData['backcover'] : 0, + 'cover' => ! empty($movieData['boxcover']) ? $movieData['boxcover'] : 0, + 'title' => ! empty($movieData['title']) ? html_entity_decode($movieData['title'], ENT_QUOTES, 'UTF-8') : '', + 'plot' => ! empty($movieData['synopsis']) ? html_entity_decode($movieData['synopsis'], ENT_QUOTES, 'UTF-8') : '', + 'tagline' => ! empty($movieData['tagline']) ? html_entity_decode($movieData['tagline'], ENT_QUOTES, 'UTF-8') : '', + 'genre' => ! empty($genres) ? html_entity_decode($genres, ENT_QUOTES, 'UTF-8') : '', + 'director' => ! empty($movieData['director']) ? html_entity_decode($movieData['director'], ENT_QUOTES, 'UTF-8') : '', + 'actors' => ! empty($cast) ? html_entity_decode($cast, ENT_QUOTES, 'UTF-8') : '', + 'directurl' => ! empty($movieData['directurl']) ? html_entity_decode($movieData['directurl'], ENT_QUOTES, 'UTF-8') : '', 'classused' => $providerName, ]; @@ -514,20 +526,20 @@ class AdultProcessingPipeline $xxxID = $check['id']; // Update BoxCover - if (!empty($mov['cover'])) { - $cover = $this->releaseImage->saveImage($xxxID . '-cover', $mov['cover'], $this->imgSavePath); + if (! empty($mov['cover'])) { + $cover = $this->releaseImage->saveImage($xxxID.'-cover', $mov['cover'], $this->imgSavePath); } // BackCover - if (!empty($mov['backdrop'])) { - $backdrop = $this->releaseImage->saveImage($xxxID . '-backdrop', $mov['backdrop'], $this->imgSavePath, 1920, 1024); + if (! empty($mov['backdrop'])) { + $backdrop = $this->releaseImage->saveImage($xxxID.'-backdrop', $mov['backdrop'], $this->imgSavePath, 1920, 1024); } // Update existing record XxxInfo::query()->where('id', $check['id'])->update([ 'title' => $mov['title'], 'tagline' => $mov['tagline'], - 'plot' => "\x1f\x8b\x08\x00" . gzcompress($mov['plot']), + 'plot' => "\x1f\x8b\x08\x00".gzcompress($mov['plot']), 'genre' => substr($mov['genre'], 0, 64), 'director' => $mov['director'], 'actors' => $mov['actors'], @@ -544,7 +556,7 @@ class AdultProcessingPipeline $xxxID = XxxInfo::query()->insertGetId([ 'title' => $mov['title'], 'tagline' => $mov['tagline'], - 'plot' => "\x1f\x8b\x08\x00" . gzcompress($mov['plot']), + 'plot' => "\x1f\x8b\x08\x00".gzcompress($mov['plot']), 'genre' => substr($mov['genre'], 0, 64), 'director' => $mov['director'], 'actors' => $mov['actors'], @@ -558,13 +570,13 @@ class AdultProcessingPipeline ]); // Update BoxCover - if (!empty($mov['cover'])) { - $cover = $this->releaseImage->saveImage($xxxID . '-cover', $mov['cover'], $this->imgSavePath); + if (! empty($mov['cover'])) { + $cover = $this->releaseImage->saveImage($xxxID.'-cover', $mov['cover'], $this->imgSavePath); } // BackCover - if (!empty($mov['backdrop'])) { - $backdrop = $this->releaseImage->saveImage($xxxID . '-backdrop', $mov['backdrop'], $this->imgSavePath, 1920, 1024); + if (! empty($mov['backdrop'])) { + $backdrop = $this->releaseImage->saveImage($xxxID.'-backdrop', $mov['backdrop'], $this->imgSavePath, 1920, 1024); } XxxInfo::whereId($xxxID)->update(['cover' => $cover, 'backdrop' => $backdrop]); @@ -572,7 +584,7 @@ class AdultProcessingPipeline if ($this->echoOutput) { cli()->primary( - ($xxxID !== false ? 'Added/updated XXX movie: ' . $mov['title'] : 'Nothing to update for XXX movie: ' . $mov['title']), + ($xxxID !== false ? 'Added/updated XXX movie: '.$mov['title'] : 'Nothing to update for XXX movie: '.$mov['title']), true ); } @@ -610,7 +622,7 @@ class AdultProcessingPipeline { return class_exists('Illuminate\Support\Facades\Concurrency') && function_exists('pcntl_fork') && - !defined('HHVM_VERSION'); + ! defined('HHVM_VERSION'); } /** @@ -618,7 +630,8 @@ class AdultProcessingPipeline */ protected function checkXXXInfoExists(string $releaseName): ?array { - $result = XxxInfo::query()->where('title', 'like', '%' . $releaseName . '%')->first(['id', 'title']); + $result = XxxInfo::query()->where('title', 'like', '%'.$releaseName.'%')->first(['id', 'title']); + return $result ? $result->toArray() : null; } @@ -637,20 +650,21 @@ class AdultProcessingPipeline { $ret = null; - if (!is_array($arr)) { + if (! is_array($arr)) { $res = Genre::query()->where('title', $arr)->first(['id']); if ($res !== null) { return (string) $res['id']; } + return ''; } foreach ($arr as $value) { $res = Genre::query()->where('title', $value)->first(['id']); if ($res !== null) { - $ret .= ',' . $res['id']; + $ret .= ','.$res['id']; } else { - $ret .= ',' . $this->insertGenre($value); + $ret .= ','.$this->insertGenre($value); } } @@ -669,6 +683,7 @@ class AdultProcessingPipeline 'disabled' => 0, ]); } + return ''; } @@ -682,22 +697,22 @@ class AdultProcessingPipeline $name = ''; $followingList = '[^\w]((2160|1080|480|720)(p|i)|AC3D|Directors([^\w]CUT)?|DD5\.1|(DVD|BD|BR)(Rip)?|BluRay|divx|HDTV|iNTERNAL|LiMiTED|(Real\.)?Proper|RE(pack|Rip)|Sub\.?(fix|pack)|Unrated|WEB-DL|(x|H)[ ._-]?264|xvid|[Dd][Ii][Ss][Cc](\d+|\s*\d+|\.\d+)|XXX|BTS|DirFix|Trailer|WEBRiP|NFO|(19|20)\d\d)[^\w]'; - if (preg_match('/([^\w]{2,})?(?P[\w .-]+?)' . $followingList . '/i', $releaseName, $hits)) { + if (preg_match('/([^\w]{2,})?(?P[\w .-]+?)'.$followingList.'/i', $releaseName, $hits)) { $name = $hits['name']; } if ($name !== '') { - $name = preg_replace('/' . $followingList . '/i', ' ', $name); + $name = preg_replace('/'.$followingList.'/i', ' ', $name); $name = preg_replace('/\(.*?\)|[._-]/i', ' ', $name); $name = trim(preg_replace('/\s{2,}/', ' ', $name)); $name = trim(preg_replace('/^Private\s(Specials|Blockbusters|Blockbuster|Sports|Gold|Lesbian|Movies|Classics|Castings|Fetish|Stars|Pictures|XXX|Private|Black\sLabel|Black)\s\d+/i', '', $name)); $name = trim(preg_replace('/(brazilian|chinese|croatian|danish|deutsch|dutch|estonian|flemish|finnish|french|german|greek|hebrew|icelandic|italian|latin|nordic|norwegian|polish|portuguese|japenese|japanese|russian|serbian|slovenian|spanish|spanisch|swedish|thai|turkish)$/i', '', $name)); if (strlen($name) > 5 && - !preg_match('/^\d+$/', $name) && - !preg_match('/( File \d+ of \d+|\d+.\d+.\d+)/', $name) && - !preg_match('/(E\d+)/', $name) && - !preg_match('/\d\d\.\d\d.\d\d/', $name) + ! preg_match('/^\d+$/', $name) && + ! preg_match('/( File \d+ of \d+|\d+.\d+.\d+)/', $name) && + ! preg_match('/(E\d+)/', $name) && + ! preg_match('/\d\d\.\d\d.\d\d/', $name) ) { return $name; } @@ -727,13 +742,13 @@ class AdultProcessingPipeline protected function outputStats(): void { cli()->header("\n=== Adult Processing Statistics ==="); - cli()->primary('Processed: ' . $this->stats['processed']); - cli()->primary('Matched: ' . $this->stats['matched']); - cli()->primary('Failed: ' . $this->stats['failed']); - cli()->primary('Skipped: ' . $this->stats['skipped']); + cli()->primary('Processed: '.$this->stats['processed']); + cli()->primary('Matched: '.$this->stats['matched']); + cli()->primary('Failed: '.$this->stats['failed']); + cli()->primary('Skipped: '.$this->stats['skipped']); cli()->primary(sprintf('Duration: %.2f seconds', $this->stats['duration'])); - if (!empty($this->stats['providers'])) { + if (! empty($this->stats['providers'])) { cli()->header("\nProvider Statistics:"); foreach ($this->stats['providers'] as $provider => $count) { cli()->primary(" {$provider}: {$count} matches"); @@ -757,4 +772,3 @@ class AdultProcessingPipeline return $this->pipes; } } - diff --git a/app/Services/AdultProcessing/AdultProcessingResult.php b/app/Services/AdultProcessing/AdultProcessingResult.php index 6820ffd28..a4b3748bc 100644 --- a/app/Services/AdultProcessing/AdultProcessingResult.php +++ b/app/Services/AdultProcessing/AdultProcessingResult.php @@ -8,9 +8,13 @@ namespace App\Services\AdultProcessing; class AdultProcessingResult { public const STATUS_MATCHED = 'matched'; + public const STATUS_NOT_FOUND = 'not_found'; + public const STATUS_FAILED = 'failed'; + public const STATUS_SKIPPED = 'skipped'; + public const STATUS_PENDING = 'pending'; public function __construct( @@ -131,4 +135,3 @@ class AdultProcessingResult return $this->movieData['directurl'] ?? null; } } - diff --git a/app/Services/AdultProcessing/AdultReleaseContext.php b/app/Services/AdultProcessing/AdultReleaseContext.php index b1601d133..f6fec48cb 100644 --- a/app/Services/AdultProcessing/AdultReleaseContext.php +++ b/app/Services/AdultProcessing/AdultReleaseContext.php @@ -42,4 +42,3 @@ class AdultReleaseContext ); } } - diff --git a/app/Services/AdultProcessing/AgeVerificationManager.php b/app/Services/AdultProcessing/AgeVerificationManager.php index 7d14916e8..db363c35b 100644 --- a/app/Services/AdultProcessing/AgeVerificationManager.php +++ b/app/Services/AdultProcessing/AgeVerificationManager.php @@ -111,7 +111,7 @@ class AgeVerificationManager $this->cookieJars[$domain] = new FileCookieJar($cookieFile, true); // If cookie file is new or empty, set age verification cookies - if (!file_exists($cookieFile) || filesize($cookieFile) < 10) { + if (! file_exists($cookieFile) || filesize($cookieFile) < 10) { $this->setAgeVerificationCookies($domain, $this->cookieJars[$domain]); } } @@ -383,4 +383,3 @@ class AgeVerificationManager return $this->cookieDir; } } - diff --git a/app/Services/AdultProcessing/Pipes/AbstractAdultProviderPipe.php b/app/Services/AdultProcessing/Pipes/AbstractAdultProviderPipe.php index 3677bd2f1..6c79b126b 100644 --- a/app/Services/AdultProcessing/Pipes/AbstractAdultProviderPipe.php +++ b/app/Services/AdultProcessing/Pipes/AbstractAdultProviderPipe.php @@ -9,10 +9,8 @@ use Closure; use GuzzleHttp\Client; use GuzzleHttp\Cookie\CookieJar; use GuzzleHttp\Cookie\FileCookieJar; -use GuzzleHttp\Cookie\SetCookie; use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\HandlerStack; use GuzzleHttp\Middleware; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; @@ -31,8 +29,11 @@ use voku\helper\HtmlDomParser; abstract class AbstractAdultProviderPipe { protected int $priority = 50; + protected bool $echoOutput = true; + protected ?HtmlDomParser $html = null; + protected ?string $cookie = null; /** @@ -96,8 +97,9 @@ abstract class AbstractAdultProviderPipe protected function getHtmlParser(): HtmlDomParser { if ($this->html === null) { - $this->html = new HtmlDomParser(); + $this->html = new HtmlDomParser; } + return $this->html; } @@ -120,6 +122,7 @@ abstract class AbstractAdultProviderPipe AdultProcessingResult::skipped('Provider skipped', $this->getName()), $this->getName() ); + return $next($passable); } @@ -164,7 +167,7 @@ abstract class AbstractAdultProviderPipe if (isset(self::$lastRequestTime[$providerName])) { $elapsed = $now - self::$lastRequestTime[$providerName]; if ($elapsed < $this->rateLimitDelay) { - usleep((int)(($this->rateLimitDelay - $elapsed) * 1000)); + usleep((int) (($this->rateLimitDelay - $elapsed) * 1000)); } } @@ -225,6 +228,7 @@ abstract class AbstractAdultProviderPipe public function setEchoOutput(bool $echo): self { $this->echoOutput = $echo; + return $this; } @@ -233,17 +237,18 @@ abstract class AbstractAdultProviderPipe */ protected function getCachedSearch(string $movie): array|false|null { - if (!$this->useCache) { + if (! $this->useCache) { return null; } - $cacheKey = 'adult_search_' . $this->getName() . '_' . md5(strtolower($movie)); + $cacheKey = 'adult_search_'.$this->getName().'_'.md5(strtolower($movie)); $cached = Cache::get($cacheKey); if ($cached !== null) { if ($this->echoOutput) { - cli()->info('Using cached result for: ' . $movie); + cli()->info('Using cached result for: '.$movie); } + return $cached; } @@ -255,11 +260,11 @@ abstract class AbstractAdultProviderPipe */ protected function cacheSearchResult(string $movie, array|false $result): void { - if (!$this->useCache) { + if (! $this->useCache) { return; } - $cacheKey = 'adult_search_' . $this->getName() . '_' . md5(strtolower($movie)); + $cacheKey = 'adult_search_'.$this->getName().'_'.md5(strtolower($movie)); Cache::put($cacheKey, $result, now()->addMinutes($this->cacheDuration)); } @@ -301,25 +306,27 @@ abstract class AbstractAdultProviderPipe if (empty($finalUrl)) { // Use the effective URI if available $effectiveUri = $response->getHeader('X-Guzzle-Redirect-History'); - if (!empty($effectiveUri)) { + if (! empty($effectiveUri)) { $finalUrl = end($effectiveUri); } } // Check for common error pages if ($this->isErrorPage($body)) { - Log::warning('Received error page from ' . $this->getName() . ': ' . $url); + Log::warning('Received error page from '.$this->getName().': '.$url); if ($attempt < $this->maxRetries) { usleep($this->retryDelay * 1000); + continue; } + return false; } // Check for age verification requirement if ($this->requiresAgeVerification($body)) { // If we haven't tried age verification yet, refresh cookies and retry - if (!$ageVerificationAttempted) { + if (! $ageVerificationAttempted) { $ageVerificationAttempted = true; // Refresh cookies using the manager @@ -329,7 +336,8 @@ abstract class AbstractAdultProviderPipe $this->httpClient = null; $this->cookieJar = null; - Log::info('Refreshed age verification cookies for ' . $this->getName() . ', retrying...'); + Log::info('Refreshed age verification cookies for '.$this->getName().', retrying...'); + continue; } @@ -343,7 +351,7 @@ abstract class AbstractAdultProviderPipe } catch (ConnectException $e) { $lastException = $e; - Log::warning('Connection failed for ' . $this->getName() . ' (attempt ' . $attempt . '): ' . $e->getMessage()); + Log::warning('Connection failed for '.$this->getName().' (attempt '.$attempt.'): '.$e->getMessage()); if ($attempt < $this->maxRetries) { usleep($this->retryDelay * 1000 * $attempt); // Exponential backoff @@ -354,11 +362,12 @@ abstract class AbstractAdultProviderPipe // Don't retry on 4xx client errors (except 429 rate limit) if ($statusCode >= 400 && $statusCode < 500 && $statusCode !== 429) { - Log::error('HTTP ' . $statusCode . ' for ' . $this->getName() . ': ' . $url); + Log::error('HTTP '.$statusCode.' for '.$this->getName().': '.$url); + return false; } - Log::warning('Request failed for ' . $this->getName() . ' (attempt ' . $attempt . '): ' . $e->getMessage()); + Log::warning('Request failed for '.$this->getName().' (attempt '.$attempt.'): '.$e->getMessage()); if ($attempt < $this->maxRetries) { // Longer delay for rate limit errors @@ -367,7 +376,7 @@ abstract class AbstractAdultProviderPipe } } catch (\Exception $e) { $lastException = $e; - Log::error('Unexpected error for ' . $this->getName() . ': ' . $e->getMessage()); + Log::error('Unexpected error for '.$this->getName().': '.$e->getMessage()); if ($attempt < $this->maxRetries) { usleep($this->retryDelay * 1000); @@ -376,7 +385,7 @@ abstract class AbstractAdultProviderPipe } if ($lastException) { - Log::error('All retry attempts failed for ' . $this->getName() . ': ' . $lastException->getMessage()); + Log::error('All retry attempts failed for '.$this->getName().': '.$lastException->getMessage()); } return false; @@ -462,7 +471,7 @@ abstract class AbstractAdultProviderPipe foreach ($contentIndicators as $pattern) { // Note: Using # as delimiter to avoid issues with / in patterns like - if (preg_match('#' . $pattern . '#is', $html)) { + if (preg_match('#'.$pattern.'#is', $html)) { return false; // This is a content page, not an age verification page } } @@ -521,7 +530,7 @@ abstract class AbstractAdultProviderPipe // Re-initialize cookies from the manager and retry if ($this->cookieJar) { // The manager already handles setting cookies, but let's ensure they're fresh - Log::info('Attempting to handle age verification for ' . $this->getName() . ' with domain: ' . $domain); + Log::info('Attempting to handle age verification for '.$this->getName().' with domain: '.$domain); } // Try to find and submit age verification form @@ -541,10 +550,10 @@ abstract class AbstractAdultProviderPipe // Try to submit the form with age confirmation $postData = $this->extractAgeVerificationFormData($form); - if (!empty($postData)) { + if (! empty($postData)) { $submitUrl = $action; - if (!str_starts_with($submitUrl, 'http')) { - $submitUrl = $this->getBaseUrl() . '/' . ltrim($submitUrl, '/'); + if (! str_starts_with($submitUrl, 'http')) { + $submitUrl = $this->getBaseUrl().'/'.ltrim($submitUrl, '/'); } // Submit the age verification @@ -557,11 +566,11 @@ abstract class AbstractAdultProviderPipe $body = $response->getBody()->getContents(); // Check if we still get age verification after submit - if (!$this->requiresAgeVerification($body)) { + if (! $this->requiresAgeVerification($body)) { return $body; } } catch (\Exception $e) { - Log::warning('Age verification submission failed for ' . $this->getName() . ': ' . $e->getMessage()); + Log::warning('Age verification submission failed for '.$this->getName().': '.$e->getMessage()); } } } @@ -571,10 +580,10 @@ abstract class AbstractAdultProviderPipe if (preg_match('/onclick\s*=\s*["\'].*?(enter|agree|confirm|over18|adult).*?["\']/i', $html) || preg_match('/]*href\s*=\s*["\']([^"\']*)["\'][^>]*>(Enter|I am over 18|Agree|Enter Site|I Agree)/i', $html, $matches)) { // Try to follow the link or simulate the click - if (!empty($matches[1])) { + if (! empty($matches[1])) { $enterUrl = $matches[1]; - if (!str_starts_with($enterUrl, 'http')) { - $enterUrl = $this->getBaseUrl() . '/' . ltrim($enterUrl, '/'); + if (! str_starts_with($enterUrl, 'http')) { + $enterUrl = $this->getBaseUrl().'/'.ltrim($enterUrl, '/'); } try { @@ -583,11 +592,11 @@ abstract class AbstractAdultProviderPipe ]); $body = $response->getBody()->getContents(); - if (!$this->requiresAgeVerification($body)) { + if (! $this->requiresAgeVerification($body)) { return $body; } } catch (\Exception $e) { - Log::warning('Age verification link follow failed for ' . $this->getName() . ': ' . $e->getMessage()); + Log::warning('Age verification link follow failed for '.$this->getName().': '.$e->getMessage()); } } } @@ -600,15 +609,16 @@ abstract class AbstractAdultProviderPipe ]); $body = $response->getBody()->getContents(); - if (!$this->requiresAgeVerification($body)) { + if (! $this->requiresAgeVerification($body)) { return $body; } } catch (\Exception $e) { - Log::warning('Age verification retry failed for ' . $this->getName() . ': ' . $e->getMessage()); + Log::warning('Age verification retry failed for '.$this->getName().': '.$e->getMessage()); } // If we couldn't handle age verification, log and return false - Log::warning('Could not handle age verification for ' . $this->getName() . ': ' . $url); + Log::warning('Could not handle age verification for '.$this->getName().': '.$url); + return false; } @@ -642,7 +652,7 @@ abstract class AbstractAdultProviderPipe break; case 'submit': // Include submit button value if it has a name - if (!empty($value)) { + if (! empty($value)) { $data[$name] = $value; } break; @@ -679,8 +689,9 @@ abstract class AbstractAdultProviderPipe protected function getAgeVerificationManager(): AgeVerificationManager { if ($this->ageVerificationManager === null) { - $this->ageVerificationManager = new AgeVerificationManager(); + $this->ageVerificationManager = new AgeVerificationManager; } + return $this->ageVerificationManager; } @@ -856,7 +867,7 @@ abstract class AbstractAdultProviderPipe ]; foreach ($metaTags as $property => $key) { - $meta = $this->getHtmlParser()->findOne('meta[property="' . $property . '"]'); + $meta = $this->getHtmlParser()->findOne('meta[property="'.$property.'"]'); if ($meta && isset($meta->content)) { $og[$key] = trim($meta->content); } @@ -865,4 +876,3 @@ abstract class AbstractAdultProviderPipe return $og; } } - diff --git a/app/Services/AdultProcessing/Pipes/AdePipe.php b/app/Services/AdultProcessing/Pipes/AdePipe.php index 91e058bb5..a92b74306 100644 --- a/app/Services/AdultProcessing/Pipes/AdePipe.php +++ b/app/Services/AdultProcessing/Pipes/AdePipe.php @@ -15,10 +15,13 @@ class AdePipe extends AbstractAdultProviderPipe protected int $priority = 40; private const BASE_URL = 'https://www.adultdvdempire.com'; + private const SEARCH_URL = '/dvd/search?q='; protected string $directUrl = ''; + protected string $title = ''; + protected string $response = ''; public function getName(): string @@ -44,6 +47,7 @@ class AdePipe extends AbstractAdultProviderPipe if ($searchResult === false) { $this->outputNotFound(); + return AdultProcessingResult::notFound($this->getName()); } @@ -83,7 +87,7 @@ class AdePipe extends AbstractAdultProviderPipe // Initialize session with age verification cookies $this->initializeSession(); - $searchUrl = self::BASE_URL . self::SEARCH_URL . rawurlencode($movie); + $searchUrl = self::BASE_URL.self::SEARCH_URL.rawurlencode($movie); $response = $this->fetchHtml($searchUrl, $this->cookie); if ($response === false) { @@ -104,7 +108,7 @@ class AdePipe extends AbstractAdultProviderPipe foreach ($resultSelectors as $selector) { $res = $this->getHtmlParser()->find($selector); - if (!empty($res)) { + if (! empty($res)) { foreach ($res as $ret) { $title = $ret->title ?? $ret->getAttribute('title') ?? trim($ret->plaintext); $url = trim($ret->href ?? ''); @@ -119,7 +123,7 @@ class AdePipe extends AbstractAdultProviderPipe $highestSimilarity = $similarity; $bestMatch = [ 'title' => trim($title), - 'url' => str_starts_with($url, 'http') ? $url : self::BASE_URL . $url, + 'url' => str_starts_with($url, 'http') ? $url : self::BASE_URL.$url, ]; } } @@ -142,8 +146,8 @@ class AdePipe extends AbstractAdultProviderPipe { $results = []; - if (!empty($this->directUrl)) { - if (!empty($this->title)) { + if (! empty($this->directUrl)) { + if (! empty($this->title)) { $results['title'] = $this->title; } $results['directurl'] = $this->directUrl; @@ -196,7 +200,7 @@ class AdePipe extends AbstractAdultProviderPipe if ($trailersResponse !== false) { if (preg_match("/([\"|'])(?P[^\"']+.swf)([\"|'])/i", $trailersResponse, $hits)) { - $res['trailers']['url'] = self::BASE_URL . trim(trim($hits['swf']), '"'); + $res['trailers']['url'] = self::BASE_URL.trim(trim($hits['swf']), '"'); if (preg_match('#(?:streamID:\s\")(?P[0-9A-Z]+)(?:\")#', $trailersResponse, $hits)) { $res['trailers']['streamid'] = trim($hits['streamid']); @@ -251,7 +255,7 @@ class AdePipe extends AbstractAdultProviderPipe foreach ($selectors as $selector => $property) { $meta = $this->getHtmlParser()->findOne($selector); - if ($meta && isset($meta->$property) && $meta->$property !== false && !empty(trim($meta->$property))) { + if ($meta && isset($meta->$property) && $meta->$property !== false && ! empty(trim($meta->$property))) { $res['synopsis'] = trim($meta->$property); return $res; @@ -275,14 +279,14 @@ class AdePipe extends AbstractAdultProviderPipe foreach ($selectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $a) { - if ($a->plaintext !== false && !empty(trim($a->plaintext))) { + if ($a->plaintext !== false && ! empty(trim($a->plaintext))) { $cast[] = trim($a->plaintext); } } - if (!empty($cast)) { + if (! empty($cast)) { break; } } @@ -308,14 +312,14 @@ class AdePipe extends AbstractAdultProviderPipe foreach ($selectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $a) { - if ($a->plaintext !== false && !empty(trim($a->plaintext))) { + if ($a->plaintext !== false && ! empty(trim($a->plaintext))) { $genres[] = trim($a->plaintext); } } - if (!empty($genres)) { + if (! empty($genres)) { break; } } @@ -332,7 +336,7 @@ class AdePipe extends AbstractAdultProviderPipe $dofeature = null; $tmpResponse = str_ireplace('Section ProductInfo', 'spdinfo', $this->response); - $tmpHtml = new \voku\helper\HtmlDomParser(); + $tmpHtml = new \voku\helper\HtmlDomParser; $tmpHtml->loadHtml($tmpResponse); if ($ret = $tmpHtml->findOne('div[class=spdinfo]')) { @@ -361,7 +365,7 @@ class AdePipe extends AbstractAdultProviderPipe array_shift($productinfo); $res['productinfo'] = array_chunk($productinfo, 2, false); - if (!empty($extrasData)) { + if (! empty($extrasData)) { $res['extras'] = $extrasData; } } @@ -387,8 +391,7 @@ class AdePipe extends AbstractAdultProviderPipe usleep(300000); // 300ms delay } catch (\Exception $e) { - \Illuminate\Support\Facades\Log::debug('ADE session initialization: ' . $e->getMessage()); + \Illuminate\Support\Facades\Log::debug('ADE session initialization: '.$e->getMessage()); } } } - diff --git a/app/Services/AdultProcessing/Pipes/AdmPipe.php b/app/Services/AdultProcessing/Pipes/AdmPipe.php index 74c04cdac..abb00f1c5 100644 --- a/app/Services/AdultProcessing/Pipes/AdmPipe.php +++ b/app/Services/AdultProcessing/Pipes/AdmPipe.php @@ -15,10 +15,13 @@ class AdmPipe extends AbstractAdultProviderPipe protected int $priority = 30; private const BASE_URL = 'https://www.adultdvdmarketplace.com'; + private const SEARCH_URL = '/xcart/adult_dvd/advanced_search.php?sort_by=relev&title='; protected string $directUrl = ''; + protected string $title = ''; + protected string $response = ''; public function getName(): string @@ -44,6 +47,7 @@ class AdmPipe extends AbstractAdultProviderPipe if ($searchResult === false) { $this->outputNotFound(); + return AdultProcessingResult::notFound($this->getName()); } @@ -80,7 +84,7 @@ class AdmPipe extends AbstractAdultProviderPipe return false; } - $searchUrl = self::BASE_URL . self::SEARCH_URL . urlencode($movie); + $searchUrl = self::BASE_URL.self::SEARCH_URL.urlencode($movie); $response = $this->fetchHtml($searchUrl, $this->cookie); if ($response === false) { @@ -98,7 +102,7 @@ class AdmPipe extends AbstractAdultProviderPipe $highestSimilarity = 0; foreach ($check as $ret) { - if (!isset($ret->alt) || !isset($ret->src)) { + if (! isset($ret->alt) || ! isset($ret->src)) { continue; } @@ -119,7 +123,7 @@ class AdmPipe extends AbstractAdultProviderPipe if ($bestMatch !== null && $highestSimilarity >= $this->minimumSimilarity) { return [ 'title' => $bestMatch['title'], - 'url' => self::BASE_URL . '/dvd_view_' . $bestMatch['sku'] . '.html', + 'url' => self::BASE_URL.'/dvd_view_'.$bestMatch['sku'].'.html', ]; } @@ -130,8 +134,8 @@ class AdmPipe extends AbstractAdultProviderPipe { $results = []; - if (!empty($this->directUrl)) { - if (!empty($this->title)) { + if (! empty($this->directUrl)) { + if (! empty($this->title)) { $results['title'] = $this->title; } $results['directurl'] = $this->directUrl; @@ -180,7 +184,7 @@ class AdmPipe extends AbstractAdultProviderPipe if (isset($ret->href) && preg_match('/images\/.*[\d]+\.jpg$/i', $ret->href, $hits)) { $res['boxcover'] = str_starts_with($hits[0], 'http') ? $hits[0] - : $baseUrl . $hits[0]; + : $baseUrl.$hits[0]; $res['backcover'] = str_ireplace('/front/', '/back/', $res['boxcover']); return $res; @@ -192,7 +196,7 @@ class AdmPipe extends AbstractAdultProviderPipe if (isset($ret->src) && preg_match('/images\/.*[\d]+\.jpg$/i', $ret->src, $hits)) { $res['boxcover'] = str_starts_with($hits[0], 'http') ? $hits[0] - : $baseUrl . $hits[0]; + : $baseUrl.$hits[0]; return $res; } @@ -210,8 +214,9 @@ class AdmPipe extends AbstractAdultProviderPipe foreach ($this->getHtmlParser()->find('h3') as $heading) { if (trim($heading->plaintext) === 'Description') { $nextElement = $heading->next_sibling(); - if ($nextElement && !empty(trim($nextElement->plaintext))) { + if ($nextElement && ! empty(trim($nextElement->plaintext))) { $res['synopsis'] = trim($nextElement->plaintext); + return $res; } } @@ -219,7 +224,7 @@ class AdmPipe extends AbstractAdultProviderPipe // Fallback: Try meta description $meta = $this->getHtmlParser()->findOne('meta[name="description"]'); - if ($meta && isset($meta->content) && !empty(trim($meta->content))) { + if ($meta && isset($meta->content) && ! empty(trim($meta->content))) { $res['synopsis'] = trim($meta->content); } @@ -290,4 +295,3 @@ class AdmPipe extends AbstractAdultProviderPipe return $res; } } - diff --git a/app/Services/AdultProcessing/Pipes/AebnPipe.php b/app/Services/AdultProcessing/Pipes/AebnPipe.php index 2e7171499..ef8f4e409 100644 --- a/app/Services/AdultProcessing/Pipes/AebnPipe.php +++ b/app/Services/AdultProcessing/Pipes/AebnPipe.php @@ -16,13 +16,19 @@ class AebnPipe extends AbstractAdultProviderPipe protected int $priority = 10; private const BASE_URL = 'https://straight.theater.aebn.net'; + private const SEARCH_URL = '/dispatcher/fts?theaterId=13992&genreId=101&locale=en&count=30&imageType=Large&targetSearchMode=basic&isAdvancedSearch=false&isFlushAdvancedSearchCriteria=false&sortType=Relevance&userQuery=title%3A+%2B'; + private const TRAILER_URL = '/dispatcher/previewPlayer?locale=en&theaterId=13992&genreId=101&movieId='; protected string $directUrl = ''; + protected string $title = ''; + protected array $res = []; + protected string $response = ''; + protected ?array $jsonLdData = null; public function getName(): string @@ -50,6 +56,7 @@ class AebnPipe extends AbstractAdultProviderPipe if ($cached === false) { return AdultProcessingResult::notFound($this->getName()); } + return AdultProcessingResult::matched( $cached['title'] ?? $movie, $this->getName(), @@ -62,6 +69,7 @@ class AebnPipe extends AbstractAdultProviderPipe if ($searchResult === false) { $this->cacheSearchResult($movie, false); $this->outputNotFound(); + return AdultProcessingResult::notFound($this->getName()); } @@ -84,6 +92,7 @@ class AebnPipe extends AbstractAdultProviderPipe if ($movieInfo === false) { $this->cacheSearchResult($movie, false); + return AdultProcessingResult::notFound($this->getName()); } @@ -105,7 +114,7 @@ class AebnPipe extends AbstractAdultProviderPipe return false; } - $searchUrl = self::BASE_URL . self::SEARCH_URL . urlencode($movie); + $searchUrl = self::BASE_URL.self::SEARCH_URL.urlencode($movie); $response = $this->fetchHtml($searchUrl, $this->cookie); if ($response === false) { @@ -129,7 +138,7 @@ class AebnPipe extends AbstractAdultProviderPipe $movies = []; foreach ($containerSelectors as $containerSelector) { $movies = $this->getHtmlParser()->find($containerSelector); - if (!empty($movies) && count($movies) > 0) { + if (! empty($movies) && count($movies) > 0) { break; } } @@ -137,7 +146,7 @@ class AebnPipe extends AbstractAdultProviderPipe foreach ($movies as $mov) { // Try multiple selector patterns for the title link $selectors = [ - 'a#FTSMovieSearch_link_title_detail_' . $i, + 'a#FTSMovieSearch_link_title_detail_'.$i, 'a.title-link', 'a[href*="/movie/"]', 'h3 a', @@ -155,14 +164,14 @@ class AebnPipe extends AbstractAdultProviderPipe if ($ret && isset($ret->href)) { $title = $ret->title ?? trim($ret->plaintext ?? ''); - if (!empty($title)) { + if (! empty($title)) { $similarity = $this->calculateSimilarity($movie, $title); if ($similarity > $highestSimilarity) { $highestSimilarity = $similarity; $bestMatch = [ 'title' => trim($title), - 'url' => self::BASE_URL . html_entity_decode($ret->href), + 'url' => self::BASE_URL.html_entity_decode($ret->href), 'similarity' => $similarity, ]; } @@ -182,8 +191,8 @@ class AebnPipe extends AbstractAdultProviderPipe { $results = []; - if (!empty($this->directUrl)) { - if (!empty($this->title)) { + if (! empty($this->directUrl)) { + if (! empty($this->title)) { $results['title'] = $this->title; } $results['directurl'] = $this->directUrl; @@ -196,32 +205,32 @@ class AebnPipe extends AbstractAdultProviderPipe // Get all the movie data (HTML fallback) $synopsis = $this->extractSynopsis(); - if (is_array($synopsis) && !empty($synopsis)) { + if (is_array($synopsis) && ! empty($synopsis)) { $results = array_merge($results, $synopsis); } $productInfo = $this->extractProductInfo(true); - if (is_array($productInfo) && !empty($productInfo)) { + if (is_array($productInfo) && ! empty($productInfo)) { $results = array_merge($results, $productInfo); } $cast = $this->extractCast(); - if (is_array($cast) && !empty($cast)) { + if (is_array($cast) && ! empty($cast)) { $results = array_merge($results, $cast); } $genres = $this->extractGenres(); - if (is_array($genres) && !empty($genres)) { + if (is_array($genres) && ! empty($genres)) { $results = array_merge($results, $genres); } $covers = $this->extractCovers(); - if (is_array($covers) && !empty($covers)) { + if (is_array($covers) && ! empty($covers)) { $results = array_merge($results, $covers); } $trailers = $this->extractTrailers(); - if (is_array($trailers) && !empty($trailers)) { + if (is_array($trailers) && ! empty($trailers)) { $results = array_merge($results, $trailers); } @@ -244,31 +253,31 @@ class AebnPipe extends AbstractAdultProviderPipe } // Title - if (!empty($this->jsonLdData['name'])) { + if (! empty($this->jsonLdData['name'])) { $results['title'] = $this->jsonLdData['name']; } // Synopsis/Description - if (!empty($this->jsonLdData['description'])) { + if (! empty($this->jsonLdData['description'])) { $results['synopsis'] = $this->jsonLdData['description']; } // Image/Cover - if (!empty($this->jsonLdData['image'])) { + if (! empty($this->jsonLdData['image'])) { $image = is_array($this->jsonLdData['image']) ? ($this->jsonLdData['image'][0] ?? '') : $this->jsonLdData['image']; - if (!empty($image)) { + if (! empty($image)) { $results['boxcover'] = $image; $results['backcover'] = str_ireplace(['xlf.jpg', 'front'], ['xlb.jpg', 'back'], $image); } } // Duration - if (!empty($this->jsonLdData['duration'])) { + if (! empty($this->jsonLdData['duration'])) { $results['duration'] = $this->jsonLdData['duration']; } // Director - if (!empty($this->jsonLdData['director'])) { + if (! empty($this->jsonLdData['director'])) { $director = $this->jsonLdData['director']; if (is_array($director)) { $results['director'] = $director['name'] ?? ($director[0]['name'] ?? ''); @@ -278,25 +287,25 @@ class AebnPipe extends AbstractAdultProviderPipe } // Actors - if (!empty($this->jsonLdData['actor'])) { + if (! empty($this->jsonLdData['actor'])) { $actors = $this->jsonLdData['actor']; $cast = []; if (is_array($actors)) { foreach ($actors as $actor) { - if (is_array($actor) && !empty($actor['name'])) { + if (is_array($actor) && ! empty($actor['name'])) { $cast[] = $actor['name']; } elseif (is_string($actor)) { $cast[] = $actor; } } } - if (!empty($cast)) { + if (! empty($cast)) { $results['cast'] = $cast; } } // Genre - if (!empty($this->jsonLdData['genre'])) { + if (! empty($this->jsonLdData['genre'])) { $genres = $this->jsonLdData['genre']; if (is_array($genres)) { $results['genres'] = $genres; @@ -323,7 +332,8 @@ class AebnPipe extends AbstractAdultProviderPipe $ret = $this->getHtmlParser()->find($selector, 0); if ($ret && isset($ret->href) && preg_match('/movieId=(?\d+)/', trim($ret->href), $hits)) { $movieid = $hits['movieid']; - $res['trailers']['url'] = self::BASE_URL . self::TRAILER_URL . $movieid; + $res['trailers']['url'] = self::BASE_URL.self::TRAILER_URL.$movieid; + return $res; } } @@ -351,10 +361,10 @@ class AebnPipe extends AbstractAdultProviderPipe if ($ret) { $coverUrl = $ret->src ?? $ret->content ?? null; - if (!empty($coverUrl)) { + if (! empty($coverUrl)) { // Ensure URL has protocol if (str_starts_with($coverUrl, '//')) { - $coverUrl = 'https:' . $coverUrl; + $coverUrl = 'https:'.$coverUrl; } // Get high-resolution versions @@ -385,20 +395,20 @@ class AebnPipe extends AbstractAdultProviderPipe foreach ($selectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $genre) { $text = trim($genre->plaintext ?? ''); - if (!empty($text)) { + if (! empty($text)) { $genres[] = $text; } } - if (!empty($genres)) { + if (! empty($genres)) { break; } } } - if (!empty($genres)) { + if (! empty($genres)) { $res['genres'] = array_unique($genres); } @@ -421,14 +431,14 @@ class AebnPipe extends AbstractAdultProviderPipe foreach ($selectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $star) { $text = trim($star->plaintext ?? ''); - if (!empty($text) && strlen($text) > 2) { + if (! empty($text) && strlen($text) > 2) { $cast[] = $text; } } - if (!empty($cast)) { + if (! empty($cast)) { break; } } @@ -437,7 +447,7 @@ class AebnPipe extends AbstractAdultProviderPipe // Fallback: try detailsLink div if (empty($cast)) { $ret = $this->getHtmlParser()->findOne('div.detailsLink'); - if ($ret && !($ret instanceof \voku\helper\SimpleHtmlDomNodeBlank)) { + if ($ret && ! ($ret instanceof \voku\helper\SimpleHtmlDomNodeBlank)) { foreach ($ret->find('span') as $star) { $text = $star->plaintext ?? ''; if (str_contains($text, '/More/') && str_contains($text, '/Stars/')) { @@ -447,7 +457,7 @@ class AebnPipe extends AbstractAdultProviderPipe } } - if (!empty($cast)) { + if (! empty($cast)) { $res['cast'] = array_unique($cast); } @@ -474,13 +484,13 @@ class AebnPipe extends AbstractAdultProviderPipe $text = rawurldecode($span->plaintext ?? ''); $text = preg_replace('/ /', '', $text); $text = trim($text); - if (!empty($text)) { + if (! empty($text)) { $productinfo[] = $text; } } } - if (!empty($productinfo)) { + if (! empty($productinfo)) { if (false !== $key = array_search('Running Time:', $productinfo, false)) { unset($productinfo[$key + 2]); } @@ -519,10 +529,11 @@ class AebnPipe extends AbstractAdultProviderPipe $ret = $this->getHtmlParser()->findOne($selector); if ($ret) { $text = $ret->plaintext ?? $ret->content ?? ''; - if (!empty(trim($text))) { + if (! empty(trim($text))) { $text = trim($text); $text = preg_replace('/^Description:\s*/i', '', $text); $res['synopsis'] = $text; + return $res; } } @@ -531,4 +542,3 @@ class AebnPipe extends AbstractAdultProviderPipe return $res; } } - diff --git a/app/Services/AdultProcessing/Pipes/Data18Pipe.php b/app/Services/AdultProcessing/Pipes/Data18Pipe.php index 8693cd7de..f57c4e4d8 100644 --- a/app/Services/AdultProcessing/Pipes/Data18Pipe.php +++ b/app/Services/AdultProcessing/Pipes/Data18Pipe.php @@ -16,11 +16,15 @@ class Data18Pipe extends AbstractAdultProviderPipe protected int $priority = 15; // Second priority after AEBN private const BASE_URL = 'https://www.data18.com'; + private const SEARCH_URL = '/search/?k='; protected string $directUrl = ''; + protected string $title = ''; + protected string $response = ''; + protected ?array $jsonLdData = null; public function getName(): string @@ -48,6 +52,7 @@ class Data18Pipe extends AbstractAdultProviderPipe if ($cached === false) { return AdultProcessingResult::notFound($this->getName()); } + return AdultProcessingResult::matched( $cached['title'] ?? $movie, $this->getName(), @@ -60,6 +65,7 @@ class Data18Pipe extends AbstractAdultProviderPipe if ($searchResult === false) { $this->cacheSearchResult($movie, false); $this->outputNotFound(); + return AdultProcessingResult::notFound($this->getName()); } @@ -82,6 +88,7 @@ class Data18Pipe extends AbstractAdultProviderPipe if ($movieInfo === false) { $this->cacheSearchResult($movie, false); + return AdultProcessingResult::notFound($this->getName()); } @@ -103,7 +110,7 @@ class Data18Pipe extends AbstractAdultProviderPipe return false; } - $searchUrl = self::BASE_URL . self::SEARCH_URL . urlencode($movie); + $searchUrl = self::BASE_URL.self::SEARCH_URL.urlencode($movie); $response = $this->fetchHtml($searchUrl, $this->cookie); if ($response === false) { @@ -126,7 +133,7 @@ class Data18Pipe extends AbstractAdultProviderPipe foreach ($containerSelectors as $containerSelector) { $results = $this->getHtmlParser()->find($containerSelector); - if (!empty($results)) { + if (! empty($results)) { foreach ($results as $result) { $link = null; $title = ''; @@ -143,14 +150,14 @@ class Data18Pipe extends AbstractAdultProviderPipe } } - if ($link && isset($link->href) && !empty($title)) { + if ($link && isset($link->href) && ! empty($title)) { $similarity = $this->calculateSimilarity($movie, $title); if ($similarity > $highestSimilarity) { $highestSimilarity = $similarity; $url = $link->href; - if (!str_starts_with($url, 'http')) { - $url = self::BASE_URL . $url; + if (! str_starts_with($url, 'http')) { + $url = self::BASE_URL.$url; } $bestMatch = [ 'title' => trim($title), @@ -178,8 +185,8 @@ class Data18Pipe extends AbstractAdultProviderPipe { $results = []; - if (!empty($this->directUrl)) { - if (!empty($this->title)) { + if (! empty($this->directUrl)) { + if (! empty($this->title)) { $results['title'] = $this->title; } $results['directurl'] = $this->directUrl; @@ -192,27 +199,27 @@ class Data18Pipe extends AbstractAdultProviderPipe // Get all the movie data (HTML fallback) $synopsis = $this->extractSynopsis(); - if (is_array($synopsis) && !empty($synopsis)) { + if (is_array($synopsis) && ! empty($synopsis)) { $results = array_merge($results, $synopsis); } $productInfo = $this->extractProductInfo(true); - if (is_array($productInfo) && !empty($productInfo)) { + if (is_array($productInfo) && ! empty($productInfo)) { $results = array_merge($results, $productInfo); } $cast = $this->extractCast(); - if (is_array($cast) && !empty($cast)) { + if (is_array($cast) && ! empty($cast)) { $results = array_merge($results, $cast); } $genres = $this->extractGenres(); - if (is_array($genres) && !empty($genres)) { + if (is_array($genres) && ! empty($genres)) { $results = array_merge($results, $genres); } $covers = $this->extractCovers(); - if (is_array($covers) && !empty($covers)) { + if (is_array($covers) && ! empty($covers)) { $results = array_merge($results, $covers); } @@ -235,30 +242,30 @@ class Data18Pipe extends AbstractAdultProviderPipe } // Title - if (!empty($this->jsonLdData['name'])) { + if (! empty($this->jsonLdData['name'])) { $results['title'] = $this->jsonLdData['name']; } // Synopsis/Description - if (!empty($this->jsonLdData['description'])) { + if (! empty($this->jsonLdData['description'])) { $results['synopsis'] = $this->jsonLdData['description']; } // Image/Cover - if (!empty($this->jsonLdData['image'])) { + if (! empty($this->jsonLdData['image'])) { $image = is_array($this->jsonLdData['image']) ? ($this->jsonLdData['image'][0] ?? '') : $this->jsonLdData['image']; - if (!empty($image)) { + if (! empty($image)) { $results['boxcover'] = $image; } } // Duration - if (!empty($this->jsonLdData['duration'])) { + if (! empty($this->jsonLdData['duration'])) { $results['duration'] = $this->jsonLdData['duration']; } // Director - if (!empty($this->jsonLdData['director'])) { + if (! empty($this->jsonLdData['director'])) { $director = $this->jsonLdData['director']; if (is_array($director)) { $results['director'] = $director['name'] ?? ($director[0]['name'] ?? ''); @@ -268,25 +275,25 @@ class Data18Pipe extends AbstractAdultProviderPipe } // Actors - if (!empty($this->jsonLdData['actor'])) { + if (! empty($this->jsonLdData['actor'])) { $actors = $this->jsonLdData['actor']; $cast = []; if (is_array($actors)) { foreach ($actors as $actor) { - if (is_array($actor) && !empty($actor['name'])) { + if (is_array($actor) && ! empty($actor['name'])) { $cast[] = $actor['name']; } elseif (is_string($actor)) { $cast[] = $actor; } } } - if (!empty($cast)) { + if (! empty($cast)) { $results['cast'] = $cast; } } // Genre - if (!empty($this->jsonLdData['genre'])) { + if (! empty($this->jsonLdData['genre'])) { $genres = $this->jsonLdData['genre']; if (is_array($genres)) { $results['genres'] = $genres; @@ -317,11 +324,11 @@ class Data18Pipe extends AbstractAdultProviderPipe if ($ret) { $coverUrl = $ret->src ?? $ret->content ?? null; - if (!empty($coverUrl)) { + if (! empty($coverUrl)) { if (str_starts_with($coverUrl, '//')) { - $coverUrl = 'https:' . $coverUrl; - } elseif (!str_starts_with($coverUrl, 'http')) { - $coverUrl = self::BASE_URL . '/' . ltrim($coverUrl, '/'); + $coverUrl = 'https:'.$coverUrl; + } elseif (! str_starts_with($coverUrl, 'http')) { + $coverUrl = self::BASE_URL.'/'.ltrim($coverUrl, '/'); } $res['boxcover'] = $coverUrl; @@ -358,8 +365,9 @@ class Data18Pipe extends AbstractAdultProviderPipe $ret = $this->getHtmlParser()->findOne($selector); if ($ret) { $text = $ret->plaintext ?? $ret->content ?? ''; - if (!empty(trim($text))) { + if (! empty(trim($text))) { $res['synopsis'] = trim($text); + return $res; } } @@ -383,20 +391,20 @@ class Data18Pipe extends AbstractAdultProviderPipe foreach ($selectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $element) { $name = trim($element->plaintext ?? ''); - if (!empty($name) && strlen($name) > 2) { + if (! empty($name) && strlen($name) > 2) { $cast[] = $name; } } - if (!empty($cast)) { + if (! empty($cast)) { break; } } } - if (!empty($cast)) { + if (! empty($cast)) { $res['cast'] = array_unique($cast); } @@ -418,20 +426,20 @@ class Data18Pipe extends AbstractAdultProviderPipe foreach ($selectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $element) { $text = trim($element->plaintext ?? ''); - if (!empty($text) && strlen($text) > 1) { + if (! empty($text) && strlen($text) > 1) { $genres[] = $text; } } - if (!empty($genres)) { + if (! empty($genres)) { break; } } } - if (!empty($genres)) { + if (! empty($genres)) { $res['genres'] = array_unique($genres); } @@ -489,4 +497,3 @@ class Data18Pipe extends AbstractAdultProviderPipe return $res; } } - diff --git a/app/Services/AdultProcessing/Pipes/HotmoviesPipe.php b/app/Services/AdultProcessing/Pipes/HotmoviesPipe.php index 8eb0fc091..0bd35821e 100644 --- a/app/Services/AdultProcessing/Pipes/HotmoviesPipe.php +++ b/app/Services/AdultProcessing/Pipes/HotmoviesPipe.php @@ -15,11 +15,15 @@ class HotmoviesPipe extends AbstractAdultProviderPipe protected int $priority = 50; private const BASE_URL = 'https://www.hotmovies.com'; + private const SEARCH_URL = '/search.php?words='; + private const EXTRA_SEARCH = '&complete=on&search_in=video_title'; protected string $directUrl = ''; + protected string $title = ''; + protected string $response = ''; public function getName(): string @@ -45,6 +49,7 @@ class HotmoviesPipe extends AbstractAdultProviderPipe if ($searchResult === false) { $this->outputNotFound(); + return AdultProcessingResult::notFound($this->getName()); } @@ -84,7 +89,7 @@ class HotmoviesPipe extends AbstractAdultProviderPipe // Initialize session with age verification $this->initializeSession(); - $searchUrl = self::BASE_URL . self::SEARCH_URL . urlencode($movie) . self::EXTRA_SEARCH; + $searchUrl = self::BASE_URL.self::SEARCH_URL.urlencode($movie).self::EXTRA_SEARCH; $response = $this->fetchHtml($searchUrl, $this->cookie); if ($response === false) { @@ -105,7 +110,7 @@ class HotmoviesPipe extends AbstractAdultProviderPipe foreach ($resultSelectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $ret) { $title = $ret->title ?? $ret->plaintext ?? ''; $url = $ret->href ?? ''; @@ -120,7 +125,7 @@ class HotmoviesPipe extends AbstractAdultProviderPipe $highestSimilarity = $similarity; $bestMatch = [ 'title' => trim($title), - 'url' => str_starts_with($url, 'http') ? $url : self::BASE_URL . $url, + 'url' => str_starts_with($url, 'http') ? $url : self::BASE_URL.$url, ]; } } @@ -143,8 +148,8 @@ class HotmoviesPipe extends AbstractAdultProviderPipe { $results = []; - if (!empty($this->directUrl)) { - if (!empty($this->title)) { + if (! empty($this->directUrl)) { + if (! empty($this->title)) { $results['title'] = $this->title; } $results['directurl'] = $this->directUrl; @@ -200,7 +205,7 @@ class HotmoviesPipe extends AbstractAdultProviderPipe $ret = $this->getHtmlParser()->findOne($selector); if ($ret) { $text = $ret->innerText ?? $ret->plaintext ?? $ret->content ?? ''; - if (!empty(trim($text))) { + if (! empty(trim($text))) { $res['synopsis'] = trim($text); return $res; @@ -217,7 +222,7 @@ class HotmoviesPipe extends AbstractAdultProviderPipe $studio = false; $director = false; - if (($ret = $this->getHtmlParser()->find('div.page_video_info')) && !empty($ret->find('text'))) { + if (($ret = $this->getHtmlParser()->find('div.page_video_info')) && ! empty($ret->find('text'))) { $productinfo = []; foreach ($ret->find('text') as $e) { @@ -236,12 +241,12 @@ class HotmoviesPipe extends AbstractAdultProviderPipe if ($studio === true) { if ((stripos($e, 'Custodian of Records') === false) && stripos($e, 'Description') === false) { - if ($director === true && !empty($e)) { + if ($director === true && ! empty($e)) { $res['director'] = $e; $e = null; $director = false; } - if (!empty($e)) { + if (! empty($e)) { $productinfo[] = $e; } } else { @@ -285,7 +290,7 @@ class HotmoviesPipe extends AbstractAdultProviderPipe } } - if (!empty($cast)) { + if (! empty($cast)) { $res['cast'] = array_values(array_unique($cast)); } @@ -353,8 +358,7 @@ class HotmoviesPipe extends AbstractAdultProviderPipe usleep(300000); // 300ms delay } catch (\Exception $e) { - \Illuminate\Support\Facades\Log::debug('HotMovies session initialization: ' . $e->getMessage()); + \Illuminate\Support\Facades\Log::debug('HotMovies session initialization: '.$e->getMessage()); } } } - diff --git a/app/Services/AdultProcessing/Pipes/IafdPipe.php b/app/Services/AdultProcessing/Pipes/IafdPipe.php index 991657f19..b9a066395 100644 --- a/app/Services/AdultProcessing/Pipes/IafdPipe.php +++ b/app/Services/AdultProcessing/Pipes/IafdPipe.php @@ -16,11 +16,15 @@ class IafdPipe extends AbstractAdultProviderPipe protected int $priority = 12; // High priority - good database private const BASE_URL = 'https://www.iafd.com'; + private const SEARCH_URL = '/results.asp?searchtype=comprehensive&searchstring='; protected string $directUrl = ''; + protected string $title = ''; + protected string $response = ''; + protected ?array $jsonLdData = null; public function getName(): string @@ -48,6 +52,7 @@ class IafdPipe extends AbstractAdultProviderPipe if ($cached === false) { return AdultProcessingResult::notFound($this->getName()); } + return AdultProcessingResult::matched( $cached['title'] ?? $movie, $this->getName(), @@ -60,6 +65,7 @@ class IafdPipe extends AbstractAdultProviderPipe if ($searchResult === false) { $this->cacheSearchResult($movie, false); $this->outputNotFound(); + return AdultProcessingResult::notFound($this->getName()); } @@ -82,6 +88,7 @@ class IafdPipe extends AbstractAdultProviderPipe if ($movieInfo === false) { $this->cacheSearchResult($movie, false); + return AdultProcessingResult::notFound($this->getName()); } @@ -103,7 +110,7 @@ class IafdPipe extends AbstractAdultProviderPipe return false; } - $searchUrl = self::BASE_URL . self::SEARCH_URL . urlencode($movie); + $searchUrl = self::BASE_URL.self::SEARCH_URL.urlencode($movie); $response = $this->fetchHtml($searchUrl, $this->cookie); if ($response === false) { @@ -139,14 +146,14 @@ class IafdPipe extends AbstractAdultProviderPipe $title = $link->title ?? trim($link->plaintext ?? ''); } - if ($link && isset($link->href) && !empty($title)) { + if ($link && isset($link->href) && ! empty($title)) { $similarity = $this->calculateSimilarity($movie, $title); if ($similarity > $highestSimilarity) { $highestSimilarity = $similarity; $url = $link->href; - if (!str_starts_with($url, 'http')) { - $url = self::BASE_URL . '/' . ltrim($url, '/'); + if (! str_starts_with($url, 'http')) { + $url = self::BASE_URL.'/'.ltrim($url, '/'); } $bestMatch = [ 'title' => trim($title), @@ -168,8 +175,8 @@ class IafdPipe extends AbstractAdultProviderPipe { $results = []; - if (!empty($this->directUrl)) { - if (!empty($this->title)) { + if (! empty($this->directUrl)) { + if (! empty($this->title)) { $results['title'] = $this->title; } $results['directurl'] = $this->directUrl; @@ -182,27 +189,27 @@ class IafdPipe extends AbstractAdultProviderPipe // Get all the movie data (HTML fallback) $synopsis = $this->extractSynopsis(); - if (is_array($synopsis) && !empty($synopsis)) { + if (is_array($synopsis) && ! empty($synopsis)) { $results = array_merge($results, $synopsis); } $productInfo = $this->extractProductInfo(true); - if (is_array($productInfo) && !empty($productInfo)) { + if (is_array($productInfo) && ! empty($productInfo)) { $results = array_merge($results, $productInfo); } $cast = $this->extractCast(); - if (is_array($cast) && !empty($cast)) { + if (is_array($cast) && ! empty($cast)) { $results = array_merge($results, $cast); } $genres = $this->extractGenres(); - if (is_array($genres) && !empty($genres)) { + if (is_array($genres) && ! empty($genres)) { $results = array_merge($results, $genres); } $covers = $this->extractCovers(); - if (is_array($covers) && !empty($covers)) { + if (is_array($covers) && ! empty($covers)) { $results = array_merge($results, $covers); } @@ -225,22 +232,22 @@ class IafdPipe extends AbstractAdultProviderPipe } // Standard JSON-LD extraction - if (!empty($this->jsonLdData['name'])) { + if (! empty($this->jsonLdData['name'])) { $results['title'] = $this->jsonLdData['name']; } - if (!empty($this->jsonLdData['description'])) { + if (! empty($this->jsonLdData['description'])) { $results['synopsis'] = $this->jsonLdData['description']; } - if (!empty($this->jsonLdData['image'])) { + if (! empty($this->jsonLdData['image'])) { $image = is_array($this->jsonLdData['image']) ? ($this->jsonLdData['image'][0] ?? '') : $this->jsonLdData['image']; - if (!empty($image)) { + if (! empty($image)) { $results['boxcover'] = $image; } } - if (!empty($this->jsonLdData['director'])) { + if (! empty($this->jsonLdData['director'])) { $director = $this->jsonLdData['director']; if (is_array($director)) { $results['director'] = $director['name'] ?? ($director[0]['name'] ?? ''); @@ -249,24 +256,24 @@ class IafdPipe extends AbstractAdultProviderPipe } } - if (!empty($this->jsonLdData['actor'])) { + if (! empty($this->jsonLdData['actor'])) { $actors = $this->jsonLdData['actor']; $cast = []; if (is_array($actors)) { foreach ($actors as $actor) { - if (is_array($actor) && !empty($actor['name'])) { + if (is_array($actor) && ! empty($actor['name'])) { $cast[] = $actor['name']; } elseif (is_string($actor)) { $cast[] = $actor; } } } - if (!empty($cast)) { + if (! empty($cast)) { $results['cast'] = $cast; } } - if (!empty($this->jsonLdData['genre'])) { + if (! empty($this->jsonLdData['genre'])) { $genres = $this->jsonLdData['genre']; if (is_array($genres)) { $results['genres'] = $genres; @@ -296,14 +303,15 @@ class IafdPipe extends AbstractAdultProviderPipe if ($ret) { $coverUrl = $ret->src ?? $ret->content ?? null; - if (!empty($coverUrl)) { + if (! empty($coverUrl)) { if (str_starts_with($coverUrl, '//')) { - $coverUrl = 'https:' . $coverUrl; - } elseif (!str_starts_with($coverUrl, 'http')) { - $coverUrl = self::BASE_URL . '/' . ltrim($coverUrl, '/'); + $coverUrl = 'https:'.$coverUrl; + } elseif (! str_starts_with($coverUrl, 'http')) { + $coverUrl = self::BASE_URL.'/'.ltrim($coverUrl, '/'); } $res['boxcover'] = $coverUrl; + return $res; } } @@ -328,8 +336,9 @@ class IafdPipe extends AbstractAdultProviderPipe $ret = $this->getHtmlParser()->findOne($selector); if ($ret) { $text = $ret->plaintext ?? $ret->content ?? ''; - if (!empty(trim($text))) { + if (! empty(trim($text))) { $res['synopsis'] = trim($text); + return $res; } } @@ -350,7 +359,7 @@ class IafdPipe extends AbstractAdultProviderPipe $performers = $castTable->find('a[href*="/person.rme"]'); foreach ($performers as $performer) { $name = trim($performer->plaintext ?? ''); - if (!empty($name) && strlen($name) > 2) { + if (! empty($name) && strlen($name) > 2) { $cast[] = $name; } } @@ -365,21 +374,21 @@ class IafdPipe extends AbstractAdultProviderPipe foreach ($selectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $element) { $name = trim($element->plaintext ?? ''); - if (!empty($name) && strlen($name) > 2 && !str_contains(strtolower($name), 'director')) { + if (! empty($name) && strlen($name) > 2 && ! str_contains(strtolower($name), 'director')) { $cast[] = $name; } } - if (!empty($cast)) { + if (! empty($cast)) { break; } } } } - if (!empty($cast)) { + if (! empty($cast)) { $res['cast'] = array_unique($cast); } @@ -399,20 +408,20 @@ class IafdPipe extends AbstractAdultProviderPipe foreach ($selectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $element) { $text = trim($element->plaintext ?? ''); - if (!empty($text) && strlen($text) > 1) { + if (! empty($text) && strlen($text) > 1) { $genres[] = $text; } } - if (!empty($genres)) { + if (! empty($genres)) { break; } } } - if (!empty($genres)) { + if (! empty($genres)) { $res['genres'] = array_unique($genres); } @@ -481,4 +490,3 @@ class IafdPipe extends AbstractAdultProviderPipe return $res; } } - diff --git a/app/Services/AdultProcessing/Pipes/PoppornPipe.php b/app/Services/AdultProcessing/Pipes/PoppornPipe.php index 99b74ba55..82c506dc2 100644 --- a/app/Services/AdultProcessing/Pipes/PoppornPipe.php +++ b/app/Services/AdultProcessing/Pipes/PoppornPipe.php @@ -15,10 +15,13 @@ class PoppornPipe extends AbstractAdultProviderPipe protected int $priority = 20; private const BASE_URL = 'https://www.popporn.com'; + private const SEARCH_ENDPOINT = '/search?q='; protected string $directUrl = ''; + protected string $title = ''; + protected string $response = ''; public function getName(): string @@ -44,6 +47,7 @@ class PoppornPipe extends AbstractAdultProviderPipe if ($searchResult === false) { $this->outputNotFound(); + return AdultProcessingResult::notFound($this->getName()); } @@ -83,7 +87,7 @@ class PoppornPipe extends AbstractAdultProviderPipe // First, establish a session by visiting the AgeConfirmation endpoint to set cookies $this->acceptAgeVerification(); - $searchUrl = self::BASE_URL . self::SEARCH_ENDPOINT . urlencode($movie); + $searchUrl = self::BASE_URL.self::SEARCH_ENDPOINT.urlencode($movie); $response = $this->fetchHtml($searchUrl, $this->cookie); if (empty($response)) { @@ -105,19 +109,19 @@ class PoppornPipe extends AbstractAdultProviderPipe foreach ($resultSelectors as $selector) { $results = $this->getHtmlParser()->find($selector); - if (!empty($results)) { + if (! empty($results)) { foreach ($results as $result) { $title = $result->title ?? $result->plaintext; $url = $result->href; - if (!empty($title)) { + if (! empty($title)) { $similarity = $this->calculateSimilarity($movie, $title); if ($similarity > $highestSimilarity) { $highestSimilarity = $similarity; $bestMatch = [ 'title' => trim($title), - 'url' => str_starts_with($url, 'http') ? $url : self::BASE_URL . $url, + 'url' => str_starts_with($url, 'http') ? $url : self::BASE_URL.$url, ]; } } @@ -138,8 +142,8 @@ class PoppornPipe extends AbstractAdultProviderPipe { $results = []; - if (!empty($this->directUrl)) { - if (!empty($this->title)) { + if (! empty($this->directUrl)) { + if (! empty($this->title)) { $results['title'] = $this->title; } $results['directurl'] = $this->directUrl; @@ -233,6 +237,7 @@ class PoppornPipe extends AbstractAdultProviderPipe // Method 1: Try structured data if (preg_match('/"description":\s*"(.*?)"/is', $this->response, $match)) { $res['synopsis'] = trim(html_entity_decode(str_replace('\\u', '\\u', $match[1]))); + return $res; } @@ -255,8 +260,9 @@ class PoppornPipe extends AbstractAdultProviderPipe } } - if (!empty($text)) { + if (! empty($text)) { $res['synopsis'] = trim($text); + return $res; } } @@ -272,8 +278,9 @@ class PoppornPipe extends AbstractAdultProviderPipe // Method 1: Try structured data if (preg_match('/"contentUrl":\s*"(.*?)"/is', $this->response, $match)) { $url = trim($match[1]); - if (!empty($url)) { + if (! empty($url)) { $res['trailers']['url'] = $url; + return $res; } } @@ -287,8 +294,9 @@ class PoppornPipe extends AbstractAdultProviderPipe foreach ($videoSelectors as $selector) { $ret = $this->getHtmlParser()->findOne($selector); - if ($ret && isset($ret->src) && !empty(trim($ret->src))) { + if ($ret && isset($ret->src) && ! empty(trim($ret->src))) { $res['trailers']['url'] = trim($ret->src); + return $res; } } @@ -330,7 +338,7 @@ class PoppornPipe extends AbstractAdultProviderPipe if ($country === true) { if (stripos($e, 'addthis_config') === false) { - if (!empty($e)) { + if (! empty($e)) { $rawInfo[] = $e; } } else { @@ -339,7 +347,7 @@ class PoppornPipe extends AbstractAdultProviderPipe } } - if (!empty($rawInfo)) { + if (! empty($rawInfo)) { $productInfo = array_chunk($rawInfo, 2, false); break; } @@ -366,15 +374,16 @@ class PoppornPipe extends AbstractAdultProviderPipe $text = trim($e->plaintext); if ($text === 'Features:') { $features = true; + continue; } - if ($features === true && !empty($text)) { + if ($features === true && ! empty($text)) { $extrasData[] = $text; } } - if (!empty($extrasData)) { + if (! empty($extrasData)) { $res['extras'] = $extrasData; break; } @@ -412,7 +421,7 @@ class PoppornPipe extends AbstractAdultProviderPipe foreach ($castSelectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $element) { $cast[] = trim($element->plaintext); } @@ -450,7 +459,7 @@ class PoppornPipe extends AbstractAdultProviderPipe foreach ($selectors as $selector) { $elements = $this->getHtmlParser()->find($selector); - if (!empty($elements)) { + if (! empty($elements)) { foreach ($elements as $e) { $genres[] = trim($e->plaintext); } @@ -480,7 +489,7 @@ class PoppornPipe extends AbstractAdultProviderPipe // First, make a request that disables redirects to see where we're being sent try { - $response = $client->get(self::BASE_URL . '/', [ + $response = $client->get(self::BASE_URL.'/', [ 'headers' => $this->getDefaultHeaders(), 'allow_redirects' => false, 'http_errors' => false, @@ -495,8 +504,8 @@ class PoppornPipe extends AbstractAdultProviderPipe if (stripos($location, 'AgeConfirmation') !== false) { // The redirect URL includes ?url2= parameter, we need to visit it $ageConfirmUrl = $location; - if (!str_starts_with($ageConfirmUrl, 'http')) { - $ageConfirmUrl = self::BASE_URL . $ageConfirmUrl; + if (! str_starts_with($ageConfirmUrl, 'http')) { + $ageConfirmUrl = self::BASE_URL.$ageConfirmUrl; } // Visit the age confirmation page @@ -506,7 +515,7 @@ class PoppornPipe extends AbstractAdultProviderPipe 'http_errors' => false, ]); - \Illuminate\Support\Facades\Log::debug('PopPorn age confirmation visited: ' . $ageConfirmUrl); + \Illuminate\Support\Facades\Log::debug('PopPorn age confirmation visited: '.$ageConfirmUrl); } } } catch (\Exception $e) { @@ -518,8 +527,7 @@ class PoppornPipe extends AbstractAdultProviderPipe } catch (\Exception $e) { // Log but don't fail - we'll try the search anyway - \Illuminate\Support\Facades\Log::debug('PopPorn age verification setup: ' . $e->getMessage()); + \Illuminate\Support\Facades\Log::debug('PopPorn age verification setup: '.$e->getMessage()); } } } - diff --git a/app/Services/AnidbService.php b/app/Services/AnidbService.php index a3ec9c5f0..c8cded549 100644 --- a/app/Services/AnidbService.php +++ b/app/Services/AnidbService.php @@ -2,7 +2,6 @@ namespace App\Services; -use App\Models\AnidbInfo; use App\Models\AnidbTitle; use App\Models\Category; use Illuminate\Contracts\Pagination\LengthAwarePaginator; @@ -242,4 +241,3 @@ class AnidbService return $result; } } - diff --git a/app/Services/Archives/SevenZipPartialParser.php b/app/Services/Archives/SevenZipPartialParser.php index 6b2b9b617..66d5f63a0 100644 --- a/app/Services/Archives/SevenZipPartialParser.php +++ b/app/Services/Archives/SevenZipPartialParser.php @@ -1722,4 +1722,3 @@ class SevenZipPartialParser return $largest; } } - diff --git a/app/Services/Backfill/BackfillConfig.php b/app/Services/Backfill/BackfillConfig.php index c7fae12bd..8b15eba43 100644 --- a/app/Services/Backfill/BackfillConfig.php +++ b/app/Services/Backfill/BackfillConfig.php @@ -48,4 +48,3 @@ final readonly class BackfillConfig return $value !== '' ? (int) $value : $default; } } - diff --git a/app/Services/Backfill/BackfillService.php b/app/Services/Backfill/BackfillService.php index bac1b6dc6..053a10db5 100644 --- a/app/Services/Backfill/BackfillService.php +++ b/app/Services/Backfill/BackfillService.php @@ -371,4 +371,3 @@ final class BackfillService $this->log(PHP_EOL."Group {$shortGroupName} processed in {$duration} seconds."); } } - diff --git a/app/Services/Binaries/BinariesConfig.php b/app/Services/Binaries/BinariesConfig.php index c1cc273bc..a1c3f136e 100644 --- a/app/Services/Binaries/BinariesConfig.php +++ b/app/Services/Binaries/BinariesConfig.php @@ -53,4 +53,3 @@ final readonly class BinariesConfig return $value !== '' ? (int) $value : $default; } } - diff --git a/app/Services/Binaries/BinariesService.php b/app/Services/Binaries/BinariesService.php index caa64674b..7ea19a614 100644 --- a/app/Services/Binaries/BinariesService.php +++ b/app/Services/Binaries/BinariesService.php @@ -901,4 +901,3 @@ class BinariesService } } } - diff --git a/app/Services/Binaries/BinaryHandler.php b/app/Services/Binaries/BinaryHandler.php index 39e4d6c38..d256d9943 100644 --- a/app/Services/Binaries/BinaryHandler.php +++ b/app/Services/Binaries/BinaryHandler.php @@ -300,4 +300,3 @@ final class BinaryHandler return $rows; } } - diff --git a/app/Services/Binaries/CollectionHandler.php b/app/Services/Binaries/CollectionHandler.php index 28beffe06..0c5ae7cef 100644 --- a/app/Services/Binaries/CollectionHandler.php +++ b/app/Services/Binaries/CollectionHandler.php @@ -263,4 +263,3 @@ final class CollectionHandler return array_keys($this->batchCollectionHashes); } } - diff --git a/app/Services/Binaries/HeaderParser.php b/app/Services/Binaries/HeaderParser.php index f41ffcc0b..e8dad0898 100644 --- a/app/Services/Binaries/HeaderParser.php +++ b/app/Services/Binaries/HeaderParser.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace App\Services\Binaries; use App\Services\BlacklistService; -use Illuminate\Support\Facades\Log; /** * Parses and filters raw NNTP headers. @@ -164,4 +163,3 @@ final class HeaderParser return $result; } } - diff --git a/app/Services/Binaries/HeaderStorageService.php b/app/Services/Binaries/HeaderStorageService.php index c427d5fa6..ac255d292 100644 --- a/app/Services/Binaries/HeaderStorageService.php +++ b/app/Services/Binaries/HeaderStorageService.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace App\Services\Binaries; -use Illuminate\Support\Facades\Log; - /** * Orchestrates the header storage process. * @@ -171,4 +169,3 @@ final class HeaderStorageService return $fileCount; } } - diff --git a/app/Services/Binaries/HeaderStorageTransaction.php b/app/Services/Binaries/HeaderStorageTransaction.php index 56ba7e51f..6358c677c 100644 --- a/app/Services/Binaries/HeaderStorageTransaction.php +++ b/app/Services/Binaries/HeaderStorageTransaction.php @@ -191,4 +191,3 @@ final class HeaderStorageTransaction } } } - diff --git a/app/Services/Binaries/MissedPartHandler.php b/app/Services/Binaries/MissedPartHandler.php index 3c3327012..9e2e9d46a 100644 --- a/app/Services/Binaries/MissedPartHandler.php +++ b/app/Services/Binaries/MissedPartHandler.php @@ -178,4 +178,3 @@ final class MissedPartHandler }, 10); } } - diff --git a/app/Services/Binaries/PartHandler.php b/app/Services/Binaries/PartHandler.php index 9d95abaf7..2019f9172 100644 --- a/app/Services/Binaries/PartHandler.php +++ b/app/Services/Binaries/PartHandler.php @@ -154,4 +154,3 @@ final class PartHandler return ! empty($this->parts); } } - diff --git a/app/Services/BookService.php b/app/Services/BookService.php index 2b21f4721..b6dd3359c 100644 --- a/app/Services/BookService.php +++ b/app/Services/BookService.php @@ -531,7 +531,7 @@ class BookService */ public function fetchItunesBookProperties(string $bookInfo) { - $itunes = new ItunesService(); + $itunes = new ItunesService; $iTunesBook = $itunes->findEbook($bookInfo); if ($iTunesBook === null) { @@ -567,4 +567,3 @@ class BookService return $book; } } - diff --git a/app/Services/Categorization/CategorizationPipeline.php b/app/Services/Categorization/CategorizationPipeline.php index c70213bec..d74e9dbf3 100644 --- a/app/Services/Categorization/CategorizationPipeline.php +++ b/app/Services/Categorization/CategorizationPipeline.php @@ -25,10 +25,11 @@ class CategorizationPipeline protected Collection $pipes; protected bool $categorizeForeign; + protected bool $catWebDL; /** - * @param iterable $pipes + * @param iterable $pipes */ public function __construct(iterable $pipes = []) { @@ -53,10 +54,10 @@ class CategorizationPipeline /** * Determine the category for a release using Laravel Pipeline. * - * @param int|string $groupId The usenet group ID - * @param string $releaseName The name of the release - * @param string|null $poster The poster name - * @param bool $debug Whether to include debug information + * @param int|string $groupId The usenet group ID + * @param string $releaseName The name of the release + * @param string|null $poster The poster name + * @param bool $debug Whether to include debug information * @return array The categorization result */ public function categorize( @@ -103,16 +104,15 @@ class CategorizationPipeline public static function createDefault(): self { return new self([ - new Pipes\GroupNamePipe(), - new Pipes\XxxPipe(), - new Pipes\TvPipe(), - new Pipes\MoviePipe(), - new Pipes\BookPipe(), - new Pipes\MusicPipe(), - new Pipes\PcPipe(), - new Pipes\ConsolePipe(), - new Pipes\MiscPipe(), + new Pipes\GroupNamePipe, + new Pipes\XxxPipe, + new Pipes\TvPipe, + new Pipes\MoviePipe, + new Pipes\BookPipe, + new Pipes\MusicPipe, + new Pipes\PcPipe, + new Pipes\ConsolePipe, + new Pipes\MiscPipe, ]); } } - diff --git a/app/Services/Categorization/CategorizationResult.php b/app/Services/Categorization/CategorizationResult.php index 146fb3ec4..0e9b90566 100644 --- a/app/Services/Categorization/CategorizationResult.php +++ b/app/Services/Categorization/CategorizationResult.php @@ -10,10 +10,10 @@ use App\Models\Category; class CategorizationResult { /** - * @param int $categoryId The determined category ID - * @param float $confidence Confidence level (0.0 to 1.0) - * @param string $matchedBy Description of what matched - * @param array $debug Additional debug information + * @param int $categoryId The determined category ID + * @param float $confidence Confidence level (0.0 to 1.0) + * @param string $matchedBy Description of what matched + * @param array $debug Additional debug information */ public function __construct( public readonly int $categoryId = Category::OTHER_MISC, @@ -69,4 +69,3 @@ class CategorizationResult ); } } - diff --git a/app/Services/Categorization/CategorizationService.php b/app/Services/Categorization/CategorizationService.php index f9b247373..1863ecd0c 100644 --- a/app/Services/Categorization/CategorizationService.php +++ b/app/Services/Categorization/CategorizationService.php @@ -22,10 +22,10 @@ class CategorizationService /** * Determine category for a release. * - * @param int|string $groupId The usenet group ID - * @param string $releaseName The name of the release - * @param string|null $poster The poster name - * @param bool $debug Whether to include debug information + * @param int|string $groupId The usenet group ID + * @param string $releaseName The name of the release + * @param string|null $poster The poster name + * @param bool $debug Whether to include debug information * @return array The categorization result with category ID and optional debug info */ public function determineCategory( @@ -40,7 +40,7 @@ class CategorizationService /** * Batch categorize multiple releases. * - * @param array $releases Array of ['group_id' => x, 'name' => y, 'poster' => z] + * @param array $releases Array of ['group_id' => x, 'name' => y, 'poster' => z] * @return array Array of categorization results */ public function batchCategorize(array $releases): array @@ -75,6 +75,7 @@ class CategorizationService public function addCategorizer(AbstractCategorizationPipe $pipe): self { $this->pipeline->addCategorizer($pipe); + return $this; } @@ -94,4 +95,3 @@ class CategorizationService })->toArray(); } } - diff --git a/app/Services/Categorization/Categorizers/AbstractCategorizer.php b/app/Services/Categorization/Categorizers/AbstractCategorizer.php index a62e745ee..f26ac5e06 100644 --- a/app/Services/Categorization/Categorizers/AbstractCategorizer.php +++ b/app/Services/Categorization/Categorizers/AbstractCategorizer.php @@ -1,16 +1,32 @@ priority; } - public function shouldSkip(ReleaseContext $context): bool { return false; } + + public function getPriority(): int + { + return $this->priority; + } + + public function shouldSkip(ReleaseContext $context): bool + { + return false; + } + protected function matched(int $categoryId, float $confidence, string $matchedBy, array $debug = []): CategorizationResult { return new CategorizationResult($categoryId, $confidence, $matchedBy, $debug); } - protected function noMatch(): CategorizationResult { return CategorizationResult::noMatch(); } + + protected function noMatch(): CategorizationResult + { + return CategorizationResult::noMatch(); + } } diff --git a/app/Services/Categorization/Categorizers/BookCategorizer.php b/app/Services/Categorization/Categorizers/BookCategorizer.php index 7bae184c7..00bd10095 100644 --- a/app/Services/Categorization/Categorizers/BookCategorizer.php +++ b/app/Services/Categorization/Categorizers/BookCategorizer.php @@ -1,63 +1,118 @@ hasAdultMarkers()) return true; - if (preg_match('/\.PS4-[A-Z0-9]+$/i', $context->releaseName)) return true; - if (preg_match('/\b(?:PS[1-5]|PlayStation|Xbox|Switch|Nintendo|Wii|3DS|GameCube)\b/i', $context->releaseName)) return true; + if ($context->hasAdultMarkers()) { + return true; + } + if (preg_match('/\.PS4-[A-Z0-9]+$/i', $context->releaseName)) { + return true; + } + if (preg_match('/\b(?:PS[1-5]|PlayStation|Xbox|Switch|Nintendo|Wii|3DS|GameCube)\b/i', $context->releaseName)) { + return true; + } // Skip TV shows (season patterns) - if (preg_match('/[._ -]S\d{1,3}[._ -]?(E\d|Complete|Full|1080|720|480|2160|WEB|HDTV|BluRay)/i', $context->releaseName)) return true; + if (preg_match('/[._ -]S\d{1,3}[._ -]?(E\d|Complete|Full|1080|720|480|2160|WEB|HDTV|BluRay)/i', $context->releaseName)) { + return true; + } // Skip movies (year + quality patterns) - if (preg_match('/\b(19|20)\d{2}\b.*\b(1080p|720p|2160p|BluRay|WEB-DL|BDRip|DVDRip)\b/i', $context->releaseName)) return true; + if (preg_match('/\b(19|20)\d{2}\b.*\b(1080p|720p|2160p|BluRay|WEB-DL|BDRip|DVDRip)\b/i', $context->releaseName)) { + return true; + } + return false; } + public function categorize(ReleaseContext $context): CategorizationResult { $name = $context->releaseName; - if ($result = $this->checkComic($name)) return $result; - if ($result = $this->checkTechnical($name)) return $result; - if ($result = $this->checkMagazine($name)) return $result; - if ($result = $this->checkEbook($name)) return $result; + if ($result = $this->checkComic($name)) { + return $result; + } + if ($result = $this->checkTechnical($name)) { + return $result; + } + if ($result = $this->checkMagazine($name)) { + return $result; + } + if ($result = $this->checkEbook($name)) { + return $result; + } + return $this->noMatch(); } + protected function checkComic(string $name): ?CategorizationResult { - if (preg_match('/\b(?:CBR|CBZ|C2C)\b|\.(?:cbr|cbz)$/i', $name)) return $this->matched(Category::BOOKS_COMICS, 0.9, 'comic_format'); + if (preg_match('/\b(?:CBR|CBZ|C2C)\b|\.(?:cbr|cbz)$/i', $name)) { + return $this->matched(Category::BOOKS_COMICS, 0.9, 'comic_format'); + } if (preg_match('/\b(?:Marvel|DC[._ -]Comics|Image[._ -]Comics|Dark[._ -]Horse|IDW)\b/i', $name) && - preg_match('/\b(?:Comics?|Annual|Issue|Vol|TPB)\b/i', $name)) return $this->matched(Category::BOOKS_COMICS, 0.85, 'comic_publisher'); - if (preg_match('/\b(?:Manga|Manhwa|Manhua|Webtoon)\b/i', $name)) return $this->matched(Category::BOOKS_COMICS, 0.85, 'manga'); + preg_match('/\b(?:Comics?|Annual|Issue|Vol|TPB)\b/i', $name)) { + return $this->matched(Category::BOOKS_COMICS, 0.85, 'comic_publisher'); + } + if (preg_match('/\b(?:Manga|Manhwa|Manhua|Webtoon)\b/i', $name)) { + return $this->matched(Category::BOOKS_COMICS, 0.85, 'manga'); + } + return null; } + protected function checkTechnical(string $name): ?CategorizationResult { $publishers = 'Apress|Addison[._ -]Wesley|Manning|No[._ -]Starch|OReilly|Packt|Pragmatic|Wiley|Wrox'; - if (preg_match('/\b(' . $publishers . ')\b/i', $name)) return $this->matched(Category::BOOKS_TECHNICAL, 0.9, 'technical_publisher'); + if (preg_match('/\b('.$publishers.')\b/i', $name)) { + return $this->matched(Category::BOOKS_TECHNICAL, 0.9, 'technical_publisher'); + } $subjects = 'Programming|Python|JavaScript|Java|Database|Linux|DevOps|Machine[._ -]Learning|Data[._ -]Science'; - if (preg_match('/\b(' . $subjects . ')\b/i', $name) && preg_match('/\b(Book|Guide|Tutorial|Learn)\b/i', $name)) { + if (preg_match('/\b('.$subjects.')\b/i', $name) && preg_match('/\b(Book|Guide|Tutorial|Learn)\b/i', $name)) { return $this->matched(Category::BOOKS_TECHNICAL, 0.85, 'technical_subject'); } + return null; } + protected function checkMagazine(string $name): ?CategorizationResult { - if (preg_match('/[._ -](Monthly|Weekly|Annual|Quarterly|Issue)[._ -]/i', $name)) return $this->matched(Category::BOOKS_MAGAZINES, 0.9, 'magazine_frequency'); + if (preg_match('/[._ -](Monthly|Weekly|Annual|Quarterly|Issue)[._ -]/i', $name)) { + return $this->matched(Category::BOOKS_MAGAZINES, 0.9, 'magazine_frequency'); + } $magazines = 'Forbes|Fortune|GQ|National[._ -]Geographic|Newsweek|Time|Vogue|Wired|PC[._ -]Gamer'; - if (preg_match('/\b(' . $magazines . ')\b/i', $name)) return $this->matched(Category::BOOKS_MAGAZINES, 0.85, 'magazine_title'); + if (preg_match('/\b('.$magazines.')\b/i', $name)) { + return $this->matched(Category::BOOKS_MAGAZINES, 0.85, 'magazine_title'); + } + return null; } + protected function checkEbook(string $name): ?CategorizationResult { $formats = 'EPUB|MOBI|AZW\d?|PDF|FB2|DJVU|LIT'; - if (preg_match('/\.(' . $formats . ')$/i', $name)) return $this->matched(Category::BOOKS_EBOOK, 0.9, 'ebook_format'); - if (preg_match('/\b(' . $formats . ')\b/i', $name)) return $this->matched(Category::BOOKS_EBOOK, 0.85, 'ebook_indicator'); - if (preg_match('/\b(E-?book|Kindle|Kobo|Nook)\b/i', $name)) return $this->matched(Category::BOOKS_EBOOK, 0.8, 'ebook_platform'); + if (preg_match('/\.('.$formats.')$/i', $name)) { + return $this->matched(Category::BOOKS_EBOOK, 0.9, 'ebook_format'); + } + if (preg_match('/\b('.$formats.')\b/i', $name)) { + return $this->matched(Category::BOOKS_EBOOK, 0.85, 'ebook_indicator'); + } + if (preg_match('/\b(E-?book|Kindle|Kobo|Nook)\b/i', $name)) { + return $this->matched(Category::BOOKS_EBOOK, 0.8, 'ebook_platform'); + } + return null; } } diff --git a/app/Services/Categorization/Categorizers/ConsoleCategorizer.php b/app/Services/Categorization/Categorizers/ConsoleCategorizer.php index 899d180ae..a1411767d 100644 --- a/app/Services/Categorization/Categorizers/ConsoleCategorizer.php +++ b/app/Services/Categorization/Categorizers/ConsoleCategorizer.php @@ -1,103 +1,183 @@ hasAdultMarkers()) return true; + + public function getName(): string + { + return 'Console'; + } + + public function shouldSkip(ReleaseContext $context): bool + { + if ($context->hasAdultMarkers()) { + return true; + } // Skip TV shows (season patterns) - if (preg_match('/[._ -]S\d{1,3}[._ -]?(E\d|Complete|Full|1080|720|480|2160|WEB|HDTV|BluRay)/i', $context->releaseName)) return true; + if (preg_match('/[._ -]S\d{1,3}[._ -]?(E\d|Complete|Full|1080|720|480|2160|WEB|HDTV|BluRay)/i', $context->releaseName)) { + return true; + } + return false; } + public function categorize(ReleaseContext $context): CategorizationResult { $name = $context->releaseName; - if ($result = $this->checkPS4($name)) return $result; - if ($result = $this->checkPS3($name)) return $result; - if ($result = $this->checkPSVita($name)) return $result; - if ($result = $this->checkPSP($name)) return $result; - if ($result = $this->checkXboxOne($name)) return $result; - if ($result = $this->checkXbox360($name)) return $result; - if ($result = $this->checkXbox($name)) return $result; - if ($result = $this->checkWiiU($name)) return $result; - if ($result = $this->checkWii($name)) return $result; - if ($result = $this->check3DS($name)) return $result; - if ($result = $this->checkNDS($name)) return $result; - if ($result = $this->checkOther($name)) return $result; + if ($result = $this->checkPS4($name)) { + return $result; + } + if ($result = $this->checkPS3($name)) { + return $result; + } + if ($result = $this->checkPSVita($name)) { + return $result; + } + if ($result = $this->checkPSP($name)) { + return $result; + } + if ($result = $this->checkXboxOne($name)) { + return $result; + } + if ($result = $this->checkXbox360($name)) { + return $result; + } + if ($result = $this->checkXbox($name)) { + return $result; + } + if ($result = $this->checkWiiU($name)) { + return $result; + } + if ($result = $this->checkWii($name)) { + return $result; + } + if ($result = $this->check3DS($name)) { + return $result; + } + if ($result = $this->checkNDS($name)) { + return $result; + } + if ($result = $this->checkOther($name)) { + return $result; + } + return $this->noMatch(); } + protected function checkPS4(string $name): ?CategorizationResult { if (preg_match('/^PS4[_\.\-]/i', $name) || preg_match('/CUSA\d{5}/i', $name) || preg_match('/\.PS4-DUPLEX$/i', $name) || preg_match('/\bPS4\b|PlayStation\s*4/i', $name)) { return $this->matched(Category::GAME_PS4, 0.9, 'ps4'); } + return null; } + protected function checkPS3(string $name): ?CategorizationResult { - if (preg_match('/\bPS3\b|PlayStation\s*3/i', $name)) return $this->matched(Category::GAME_PS3, 0.9, 'ps3'); + if (preg_match('/\bPS3\b|PlayStation\s*3/i', $name)) { + return $this->matched(Category::GAME_PS3, 0.9, 'ps3'); + } + return null; } + protected function checkPSVita(string $name): ?CategorizationResult { - if (preg_match('/\bPS\s?Vita\b|PSV(ita)?\b/i', $name)) return $this->matched(Category::GAME_PSVITA, 0.9, 'psvita'); + if (preg_match('/\bPS\s?Vita\b|PSV(ita)?\b/i', $name)) { + return $this->matched(Category::GAME_PSVITA, 0.9, 'psvita'); + } + return null; } + protected function checkPSP(string $name): ?CategorizationResult { - if (preg_match('/\bPSP\b|PlayStation\s*Portable/i', $name)) return $this->matched(Category::GAME_PSP, 0.9, 'psp'); + if (preg_match('/\bPSP\b|PlayStation\s*Portable/i', $name)) { + return $this->matched(Category::GAME_PSP, 0.9, 'psp'); + } + return null; } + protected function checkXboxOne(string $name): ?CategorizationResult { if (preg_match('/\b(XboxOne|XBOX\s*One|XBONE|XB1|Xbox\s*Series[._ -]?[SX]|XSX|XSS)\b/i', $name)) { return $this->matched(Category::GAME_XBOXONE, 0.9, 'xboxone'); } + return null; } + protected function checkXbox360(string $name): ?CategorizationResult { - if (preg_match('/\b(Xbox360|XBOX360|X360)\b/i', $name)) return $this->matched(Category::GAME_XBOX360, 0.9, 'xbox360'); + if (preg_match('/\b(Xbox360|XBOX360|X360)\b/i', $name)) { + return $this->matched(Category::GAME_XBOX360, 0.9, 'xbox360'); + } + return null; } + protected function checkXbox(string $name): ?CategorizationResult { - if (preg_match('/\bXBOX\b/i', $name) && !preg_match('/\b(XBOX\s?360|XBOX\s?ONE|Series)\b/i', $name)) { + if (preg_match('/\bXBOX\b/i', $name) && ! preg_match('/\b(XBOX\s?360|XBOX\s?ONE|Series)\b/i', $name)) { return $this->matched(Category::GAME_XBOX, 0.85, 'xbox'); } + return null; } + protected function checkWiiU(string $name): ?CategorizationResult { - if (preg_match('/\bWii\s*U\b|WiiU/i', $name)) return $this->matched(Category::GAME_WIIU, 0.9, 'wiiu'); + if (preg_match('/\bWii\s*U\b|WiiU/i', $name)) { + return $this->matched(Category::GAME_WIIU, 0.9, 'wiiu'); + } + return null; } + protected function checkWii(string $name): ?CategorizationResult { - if (preg_match('/\bWii\b/i', $name) && !preg_match('/WiiU/i', $name)) return $this->matched(Category::GAME_WII, 0.85, 'wii'); + if (preg_match('/\bWii\b/i', $name) && ! preg_match('/WiiU/i', $name)) { + return $this->matched(Category::GAME_WII, 0.85, 'wii'); + } + return null; } + protected function check3DS(string $name): ?CategorizationResult { - if (preg_match('/\b3DS\b|Nintendo\s*3DS/i', $name)) return $this->matched(Category::GAME_3DS, 0.9, '3ds'); + if (preg_match('/\b3DS\b|Nintendo\s*3DS/i', $name)) { + return $this->matched(Category::GAME_3DS, 0.9, '3ds'); + } + return null; } + protected function checkNDS(string $name): ?CategorizationResult { - if (preg_match('/\bNDS\b|Nintendo\s*DS/i', $name)) return $this->matched(Category::GAME_NDS, 0.9, 'nds'); + if (preg_match('/\bNDS\b|Nintendo\s*DS/i', $name)) { + return $this->matched(Category::GAME_NDS, 0.9, 'nds'); + } + return null; } + protected function checkOther(string $name): ?CategorizationResult { if (preg_match('/\b(PS[12X]|PS2|SNES|NES|SEGA|GB[AC]?|Dreamcast|Saturn|Atari|N64)\b/i', $name) && preg_match('/\b(EUR|JP|JPN|NTSC|PAL|USA|ROM)\b/i', $name)) { return $this->matched(Category::GAME_OTHER, 0.8, 'retro_console'); } + return null; } } diff --git a/app/Services/Categorization/Categorizers/GroupNameCategorizer.php b/app/Services/Categorization/Categorizers/GroupNameCategorizer.php index dd172db98..8e697c6bd 100644 --- a/app/Services/Categorization/Categorizers/GroupNameCategorizer.php +++ b/app/Services/Categorization/Categorizers/GroupNameCategorizer.php @@ -1,23 +1,48 @@ groupName; - if (empty($groupName)) return $this->noMatch(); - if (preg_match('/alt\.binaries\..*?(tv|hdtv|tvseries)/i', $groupName)) return $this->matched(Category::TV_OTHER, 0.6, 'group_tv'); - if (preg_match('/alt\.binaries\..*?(movies?|dvd|bluray|x264)/i', $groupName)) return $this->matched(Category::MOVIE_OTHER, 0.6, 'group_movie'); - if (preg_match('/alt\.binaries\..*?(erotica|pictures\.erotica|xxx)/i', $groupName)) return $this->matched(Category::XXX_OTHER, 0.7, 'group_xxx'); - if (preg_match('/alt\.binaries\..*?(sounds?|mp3|music|lossless)/i', $groupName)) return $this->matched(Category::MUSIC_OTHER, 0.6, 'group_music'); - if (preg_match('/alt\.binaries\..*?(games?|console|psx|nintendo)/i', $groupName)) return $this->matched(Category::GAME_OTHER, 0.6, 'group_game'); - if (preg_match('/alt\.binaries\..*?(warez|0day|apps?|software)/i', $groupName)) return $this->matched(Category::PC_0DAY, 0.6, 'group_pc'); - if (preg_match('/alt\.binaries\..*?(e-?book|ebook|comics?)/i', $groupName)) return $this->matched(Category::BOOKS_EBOOK, 0.6, 'group_book'); + if (empty($groupName)) { + return $this->noMatch(); + } + if (preg_match('/alt\.binaries\..*?(tv|hdtv|tvseries)/i', $groupName)) { + return $this->matched(Category::TV_OTHER, 0.6, 'group_tv'); + } + if (preg_match('/alt\.binaries\..*?(movies?|dvd|bluray|x264)/i', $groupName)) { + return $this->matched(Category::MOVIE_OTHER, 0.6, 'group_movie'); + } + if (preg_match('/alt\.binaries\..*?(erotica|pictures\.erotica|xxx)/i', $groupName)) { + return $this->matched(Category::XXX_OTHER, 0.7, 'group_xxx'); + } + if (preg_match('/alt\.binaries\..*?(sounds?|mp3|music|lossless)/i', $groupName)) { + return $this->matched(Category::MUSIC_OTHER, 0.6, 'group_music'); + } + if (preg_match('/alt\.binaries\..*?(games?|console|psx|nintendo)/i', $groupName)) { + return $this->matched(Category::GAME_OTHER, 0.6, 'group_game'); + } + if (preg_match('/alt\.binaries\..*?(warez|0day|apps?|software)/i', $groupName)) { + return $this->matched(Category::PC_0DAY, 0.6, 'group_pc'); + } + if (preg_match('/alt\.binaries\..*?(e-?book|ebook|comics?)/i', $groupName)) { + return $this->matched(Category::BOOKS_EBOOK, 0.6, 'group_book'); + } + return $this->noMatch(); } } diff --git a/app/Services/Categorization/Categorizers/MiscCategorizer.php b/app/Services/Categorization/Categorizers/MiscCategorizer.php index f6eb2ba70..2a4a9f292 100644 --- a/app/Services/Categorization/Categorizers/MiscCategorizer.php +++ b/app/Services/Categorization/Categorizers/MiscCategorizer.php @@ -85,14 +85,14 @@ class MiscCategorizer extends AbstractCategorizer { // Dataset/dump patterns that aren't media if (preg_match('/\b(sql|csv|dump|backup|dataset|collection)\b/i', $name) && - !preg_match('/\b(movie|tv|show|audio|video|book|game)\b/i', $name)) { + ! preg_match('/\b(movie|tv|show|audio|video|book|game)\b/i', $name)) { return $this->matched(Category::OTHER_MISC, 0.6, 'dataset'); } // Data leaks/dumps (be careful with these) if (preg_match('/\b(leak|breach|data|database)\b/i', $name) && preg_match('/\b(dump|export|backup)\b/i', $name) && - !preg_match('/\b(movie|tv|show|audio|video|book|game)\b/i', $name)) { + ! preg_match('/\b(movie|tv|show|audio|video|book|game)\b/i', $name)) { return $this->matched(Category::OTHER_MISC, 0.6, 'data_dump'); } @@ -109,8 +109,8 @@ class MiscCategorizer extends AbstractCategorizer // Mixed-case alphanumeric strings without separators (common obfuscation pattern) // These look like random strings: e.g., "AA7Jl2toE8Q53yNZmQ5R6G" if (preg_match('/^[a-zA-Z0-9]{15,}$/', $name) && - !preg_match('/\b(19|20)\d{2}\b/', $name) && - !preg_match('/^[A-Z][a-z]+([A-Z][a-z]+)+$/', $name)) { // Exclude CamelCase words + ! preg_match('/\b(19|20)\d{2}\b/', $name) && + ! preg_match('/^[A-Z][a-z]+([A-Z][a-z]+)+$/', $name)) { // Exclude CamelCase words return $this->matched(Category::OTHER_HASHED, 0.7, 'obfuscated_mixed_alphanumeric'); } @@ -121,8 +121,8 @@ class MiscCategorizer extends AbstractCategorizer $filename = $matches[1]; // Ensure the filename looks random (not a real title) // Real titles would have words/structure, obfuscated ones are random chars - if (!preg_match('/[._ -]/', $filename) && // No separators - !preg_match('/\b(19|20)\d{2}\b/', $filename) && // No year + if (! preg_match('/[._ -]/', $filename) && // No separators + ! preg_match('/\b(19|20)\d{2}\b/', $filename) && // No year $this->looksLikeRandomString($filename)) { // Additional entropy check return $this->matched(Category::OTHER_HASHED, 0.85, 'obfuscated_usenet_filename'); } @@ -130,7 +130,7 @@ class MiscCategorizer extends AbstractCategorizer // Only punctuation and numbers with no clear structure if (preg_match('/^[^a-zA-Z]*[A-Z0-9\._\-]{5,}[^a-zA-Z]*$/', $name) && - !preg_match('/\.(mkv|avi|mp4|mp3|flac|pdf|epub|exe|iso)$/i', $name)) { + ! preg_match('/\.(mkv|avi|mp4|mp3|flac|pdf|epub|exe|iso)$/i', $name)) { return $this->matched(Category::OTHER_MISC, 0.5, 'obfuscated_pattern'); } @@ -158,7 +158,7 @@ class MiscCategorizer extends AbstractCategorizer $prevIsDigit = ctype_digit($str[$i - 1]); $currIsDigit = ctype_digit($str[$i]); - if (($prevIsUpper !== $currIsUpper && !$prevIsDigit && !$currIsDigit) || + if (($prevIsUpper !== $currIsUpper && ! $prevIsDigit && ! $currIsDigit) || ($prevIsDigit !== $currIsDigit)) { $transitions++; } @@ -176,7 +176,7 @@ class MiscCategorizer extends AbstractCategorizer $hasWordPattern = preg_match('/[bcdfghjklmnpqrstvwxyz]{1,2}[aeiou][bcdfghjklmnpqrstvwxyz]{1,2}[aeiou]/i', $str); // If transition ratio is high and no word patterns, likely random - if ($transitionRatio > 0.3 && !$hasWordPattern) { + if ($transitionRatio > 0.3 && ! $hasWordPattern) { return true; } diff --git a/app/Services/Categorization/Categorizers/MovieCategorizer.php b/app/Services/Categorization/Categorizers/MovieCategorizer.php index c4502a5b9..eefd37ed2 100644 --- a/app/Services/Categorization/Categorizers/MovieCategorizer.php +++ b/app/Services/Categorization/Categorizers/MovieCategorizer.php @@ -48,7 +48,7 @@ class MovieCategorizer extends AbstractCategorizer $name = $context->releaseName; // Check if it looks like movie content - if (!$this->looksLikeMovie($name)) { + if (! $this->looksLikeMovie($name)) { return $this->noMatch(); } @@ -162,7 +162,7 @@ class MovieCategorizer extends AbstractCategorizer protected function checkBluRay(string $name): ?CategorizationResult { if (preg_match('/bluray-|[._ -]bd?[._ -]?(25|50)|blu-ray|Bluray\s-\sUntouched|[._ -]untouched[._ -]/i', $name) && - !preg_match('/SecretUsenet\.com$/i', $name)) { + ! preg_match('/SecretUsenet\.com$/i', $name)) { return $this->matched(Category::MOVIE_BLURAY, 0.9, 'bluray'); } @@ -193,7 +193,7 @@ class MovieCategorizer extends AbstractCategorizer return $this->matched(Category::MOVIE_HD, 0.85, 'hd'); } - if (!$catWebDL && preg_match('/web[._ -]dl|web-?rip/i', $name)) { + if (! $catWebDL && preg_match('/web[._ -]dl|web-?rip/i', $name)) { return $this->matched(Category::MOVIE_HD, 0.8, 'hd_webdl_fallback'); } @@ -218,4 +218,3 @@ class MovieCategorizer extends AbstractCategorizer return null; } } - diff --git a/app/Services/Categorization/Categorizers/MusicCategorizer.php b/app/Services/Categorization/Categorizers/MusicCategorizer.php index 2ed7e179f..87dd4c973 100644 --- a/app/Services/Categorization/Categorizers/MusicCategorizer.php +++ b/app/Services/Categorization/Categorizers/MusicCategorizer.php @@ -25,9 +25,14 @@ class MusicCategorizer extends AbstractCategorizer public function shouldSkip(ReleaseContext $context): bool { - if ($context->hasAdultMarkers()) return true; + if ($context->hasAdultMarkers()) { + return true; + } // Skip TV shows (season patterns) - if (preg_match('/[._ -]S\d{1,3}[._ -]?(E\d|Complete|Full|1080|720|480|2160|WEB|HDTV|BluRay)/i', $context->releaseName)) return true; + if (preg_match('/[._ -]S\d{1,3}[._ -]?(E\d|Complete|Full|1080|720|480|2160|WEB|HDTV|BluRay)/i', $context->releaseName)) { + return true; + } + return false; } @@ -65,7 +70,7 @@ class MusicCategorizer extends AbstractCategorizer protected function checkForeign(string $name): bool { - return (bool) preg_match('/(?:^|[\s\.\-_])(?:' . self::FOREIGN_LANGUAGES . '|' . self::LANGUAGE_CODES . ')(?:$|[\s\.\-_])/i', $name); + return (bool) preg_match('/(?:^|[\s\.\-_])(?:'.self::FOREIGN_LANGUAGES.'|'.self::LANGUAGE_CODES.')(?:$|[\s\.\-_])/i', $name); } protected function checkAudiobook(string $name): ?CategorizationResult @@ -124,6 +129,7 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.85, 'music_video_foreign'); } + return $this->matched(Category::MUSIC_VIDEO, 0.9, 'music_video'); } } @@ -133,6 +139,7 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.8, 'music_video_foreign'); } + return $this->matched(Category::MUSIC_VIDEO, 0.8, 'music_video_artist'); } @@ -149,6 +156,7 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.9, 'lossless_foreign'); } + return $this->matched(Category::MUSIC_LOSSLESS, 0.9, 'lossless'); } } @@ -160,6 +168,7 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.85, 'flac_foreign'); } + return $this->matched(Category::MUSIC_LOSSLESS, 0.85, 'flac'); } @@ -168,6 +177,7 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.85, 'lossless_format_foreign'); } + return $this->matched(Category::MUSIC_LOSSLESS, 0.85, 'lossless_format'); } @@ -185,6 +195,7 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.85, 'mp3_foreign'); } + return $this->matched(Category::MUSIC_MP3, 0.85, 'mp3'); } } @@ -194,6 +205,7 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.75, 'mp3_scene_foreign'); } + return $this->matched(Category::MUSIC_MP3, 0.75, 'mp3_scene'); } @@ -202,6 +214,7 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.8, 'mp3_bitrate_foreign'); } + return $this->matched(Category::MUSIC_MP3, 0.8, 'mp3_bitrate'); } @@ -215,6 +228,7 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.8, 'music_other_foreign'); } + return $this->matched(Category::MUSIC_OTHER, 0.8, 'music_other'); } @@ -224,6 +238,7 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.75, 'music_album_foreign'); } + return $this->matched(Category::MUSIC_OTHER, 0.75, 'music_album'); } @@ -233,10 +248,10 @@ class MusicCategorizer extends AbstractCategorizer if ($categorizeForeign && $this->checkForeign($name)) { return $this->matched(Category::MUSIC_FOREIGN, 0.75, 'music_dj_foreign'); } + return $this->matched(Category::MUSIC_OTHER, 0.75, 'music_dj'); } return null; } } - diff --git a/app/Services/Categorization/Categorizers/PcCategorizer.php b/app/Services/Categorization/Categorizers/PcCategorizer.php index d9853a90a..26a28ca23 100644 --- a/app/Services/Categorization/Categorizers/PcCategorizer.php +++ b/app/Services/Categorization/Categorizers/PcCategorizer.php @@ -26,9 +26,14 @@ class PcCategorizer extends AbstractCategorizer public function shouldSkip(ReleaseContext $context): bool { - if ($context->hasAdultMarkers()) return true; + if ($context->hasAdultMarkers()) { + return true; + } // Skip TV shows (season patterns) - if (preg_match('/[._ -]S\d{1,3}[._ -]?(E\d|Complete|Full|1080|720|480|2160|WEB|HDTV|BluRay)/i', $context->releaseName)) return true; + if (preg_match('/[._ -]S\d{1,3}[._ -]?(E\d|Complete|Full|1080|720|480|2160|WEB|HDTV|BluRay)/i', $context->releaseName)) { + return true; + } + return false; } @@ -110,8 +115,8 @@ class PcCategorizer extends AbstractCategorizer // Check for PC game patterns // PC_GROUPS must be at the END of the release name (scene naming: Game.Name-GROUP) // PC_KEYWORDS can appear anywhere - $groupPattern = '/[-.](' . self::PC_GROUPS . ')$/i'; - $keywordPattern = '/' . self::PC_KEYWORDS . '/i'; + $groupPattern = '/[-.]('.self::PC_GROUPS.')$/i'; + $keywordPattern = '/'.self::PC_KEYWORDS.'/i'; if (preg_match($groupPattern, $name) || preg_match($keywordPattern, $name)) { return $this->matched(Category::PC_GAMES, 0.9, 'pc_game'); @@ -159,4 +164,3 @@ class PcCategorizer extends AbstractCategorizer return null; } } - diff --git a/app/Services/Categorization/Categorizers/TvCategorizer.php b/app/Services/Categorization/Categorizers/TvCategorizer.php index 0bc486479..e8a25326e 100644 --- a/app/Services/Categorization/Categorizers/TvCategorizer.php +++ b/app/Services/Categorization/Categorizers/TvCategorizer.php @@ -27,7 +27,7 @@ class TvCategorizer extends AbstractCategorizer { $name = $context->releaseName; - if (!$this->looksLikeTV($name)) { + if (! $this->looksLikeTV($name)) { return $this->noMatch(); } @@ -99,6 +99,7 @@ class TvCategorizer extends AbstractCategorizer if (preg_match('/(?:^|[.\-_ \[])(URANiME|ANiHLS|HaiKU|ANiURL|SkyAnime|Erai-raws|LostYears|Vodes|SubsPlease|Judas|Ember|EMBER|YuiSubs|ASW|Tsundere-Raws|Anime-Raws|Kekkan)(?:[.\-_ \]]|$)/i', $name)) { return true; } + return false; } @@ -123,22 +124,23 @@ class TvCategorizer extends AbstractCategorizer // Combined with episode-only pattern (E05 without season prefix) or roman numeral season if (preg_match('/[._ ]no[._ ]/i', $name) && (preg_match('/[._ ](I{1,3}|IV|V|VI{0,3}|IX|X)[._ ]?E\d{1,4}[._ ]/i', $name) || - (preg_match('/[._ ]E\d{1,4}[._ ]/i', $name) && !preg_match('/[._ ]S\d{1,3}[._ ]?E\d/i', $name)))) { + (preg_match('/[._ ]E\d{1,4}[._ ]/i', $name) && ! preg_match('/[._ ]S\d{1,3}[._ ]?E\d/i', $name)))) { return $this->matched(Category::TV_ANIME, 0.9, 'anime_japanese_title'); } // Episode pattern with known anime indicators if (preg_match('/[._ -]E\d{1,4}[._ -]/i', $name) && preg_match('/\b(BluRay|BD|BDRip)\b/i', $name) && - !preg_match('/\bS\d{1,3}\b/i', $name)) { + ! preg_match('/\bS\d{1,3}\b/i', $name)) { // Episode-only pattern with BluRay but no season - likely anime return $this->matched(Category::TV_ANIME, 0.8, 'anime_episode_bluray'); } // Roman numeral season with episode-only pattern (common in anime) // e.g., Title.III.E05, Title.II.E12 - typically anime naming convention if (preg_match('/[._ ](I{1,3}|IV|V|VI{0,3}|IX|X)[._ ]E\d{1,4}[._ ]/i', $name) && - !preg_match('/[._ ]S\d{1,3}[._ ]?E\d/i', $name)) { + ! preg_match('/[._ ]S\d{1,3}[._ ]?E\d/i', $name)) { return $this->matched(Category::TV_ANIME, 0.85, 'anime_roman_numeral_season'); } + return null; } @@ -148,6 +150,7 @@ class TvCategorizer extends AbstractCategorizer preg_match('/\d{4}|\b(Season|Week|Round|Match|Game)\b/i', $name)) { return $this->matched(Category::TV_SPORT, 0.85, 'sport'); } + return null; } @@ -156,17 +159,19 @@ class TvCategorizer extends AbstractCategorizer if (preg_match('/\b(Documentary|Docu[._ -]?Series|DOCU)\b/i', $name)) { return $this->matched(Category::TV_DOCU, 0.85, 'documentary'); } + return null; } protected function checkForeign(ReleaseContext $context): ?CategorizationResult { - if (!$context->categorizeForeign) { + if (! $context->categorizeForeign) { return null; } if (preg_match('/(danish|flemish|Deutsch|dutch|french|german|hebrew|nl[._ -]?sub|dub(bed|s)?|\.NL|norwegian|swedish|swesub|spanish|Staffel)[._ -]|\(german\)|Multisub/i', $context->releaseName)) { return $this->matched(Category::TV_FOREIGN, 0.8, 'foreign_language'); } + return null; } @@ -175,6 +180,7 @@ class TvCategorizer extends AbstractCategorizer if (preg_match('/(S\d+).*(x265).*(rmteam|MeGusta|HETeam|PSA|ONLY|H4S5S|TrollHD|ImE)/i', $name)) { return $this->matched(Category::TV_X265, 0.9, 'x265_group'); } + return null; } @@ -183,6 +189,7 @@ class TvCategorizer extends AbstractCategorizer if (preg_match('/web[._ -]dl|web-?rip/i', $name)) { return $this->matched(Category::TV_WEBDL, 0.85, 'webdl'); } + return null; } @@ -200,6 +207,7 @@ class TvCategorizer extends AbstractCategorizer if (preg_match('/(S\d+).*(2160p).*(Netflix|Amazon|NF|AMZN).*(TrollUHD|NTb|VLAD|DEFLATE|POFUDUK|CMRG)/i', $name)) { return $this->matched(Category::TV_UHD, 0.9, 'uhd_streaming'); } + return null; } @@ -208,9 +216,10 @@ class TvCategorizer extends AbstractCategorizer if (preg_match('/1080([ip])|720p|bluray/i', $name)) { return $this->matched(Category::TV_HD, 0.85, 'hd_resolution'); } - if (!$catWebDL && preg_match('/web[._ -]dl|web-?rip/i', $name)) { + if (! $catWebDL && preg_match('/web[._ -]dl|web-?rip/i', $name)) { return $this->matched(Category::TV_HD, 0.8, 'hd_webdl_fallback'); } + return null; } @@ -222,6 +231,7 @@ class TvCategorizer extends AbstractCategorizer if (preg_match('/(([HP])D[._ -]?TV|DSR|WebRip)[._ -]x264/i', $name)) { return $this->matched(Category::TV_SD, 0.8, 'sd_codec'); } + return null; } @@ -235,6 +245,7 @@ class TvCategorizer extends AbstractCategorizer if (preg_match('/[._ -]S\d{1,3}[._ -]/i', $name)) { return $this->matched(Category::TV_OTHER, 0.6, 'tv_season_pack'); } + return null; } } diff --git a/app/Services/Categorization/Categorizers/XxxCategorizer.php b/app/Services/Categorization/Categorizers/XxxCategorizer.php index 53a8f8df9..2524a1c5f 100644 --- a/app/Services/Categorization/Categorizers/XxxCategorizer.php +++ b/app/Services/Categorization/Categorizers/XxxCategorizer.php @@ -32,7 +32,7 @@ class XxxCategorizer extends AbstractCategorizer $name = $context->releaseName; // Check if it looks like adult content - if (!$this->looksLikeXxx($name)) { + if (! $this->looksLikeXxx($name)) { return $this->noMatch(); } @@ -107,24 +107,24 @@ class XxxCategorizer extends AbstractCategorizer } // Check for known studios/sites - if (preg_match('/\b(' . self::KNOWN_STUDIOS . ')\b/i', $name, $matches)) { + if (preg_match('/\b('.self::KNOWN_STUDIOS.')\b/i', $name, $matches)) { $matchedStudio = $matches[1]; // If the studio name appears at the start, check if it's actually a movie release pattern // Movie pattern: Title.Year.Resolution.Source (e.g., Wicked.2024.1080p.WEB-DL) // Adult pattern: Studio.Date.Performer (e.g., Wicked.24.01.15.Performer.Name) - if (preg_match('/^' . preg_quote($matchedStudio, '/') . '[.\-_ ]/i', $name)) { + if (preg_match('/^'.preg_quote($matchedStudio, '/').'[.\-_ ]/i', $name)) { // Check if this looks like a movie release: Studio.Year.Resolution or Studio.Title.Year // Movie releases typically have: 4-digit year followed by resolution/source markers - if (preg_match('/^' . preg_quote($matchedStudio, '/') . '[.\-_ ](?:[A-Za-z]+[.\-_ ])*(?:19|20)\d{2}[.\-_ ](?:720p|1080p|2160p|4K|UHD)/i', $name) && + if (preg_match('/^'.preg_quote($matchedStudio, '/').'[.\-_ ](?:[A-Za-z]+[.\-_ ])*(?:19|20)\d{2}[.\-_ ](?:720p|1080p|2160p|4K|UHD)/i', $name) && preg_match('/\b(WEB-?DL|WEBRip|BluRay|BDRip|HDRip|HDTV|DVDRip|Remux|PROPER|REPACK|HC|KORSUB)\b/i', $name) && - !preg_match('/\b(' . self::ADULT_KEYWORDS . ')\b/i', $name)) { + ! preg_match('/\b('.self::ADULT_KEYWORDS.')\b/i', $name)) { // This looks like a movie release (Title.Year.Resolution.Source), not adult content return false; } // Adult studio releases typically use date patterns: Studio.YY.MM.DD or Studio.YYYY.MM.DD - if (preg_match('/^' . preg_quote($matchedStudio, '/') . '[.\-_ ](19|20)?\d{2}[.\-_ ]\d{2}[.\-_ ]\d{2}[.\-_ ]/i', $name)) { + if (preg_match('/^'.preg_quote($matchedStudio, '/').'[.\-_ ](19|20)?\d{2}[.\-_ ]\d{2}[.\-_ ]\d{2}[.\-_ ]/i', $name)) { return true; } } @@ -133,18 +133,18 @@ class XxxCategorizer extends AbstractCategorizer } // Check for known VR sites - if (preg_match('/\b(' . self::VR_SITES . ')\b/i', $name)) { + if (preg_match('/\b('.self::VR_SITES.')\b/i', $name)) { return true; } // Check for adult content indicators combined with video markers - if (preg_match('/\b(' . self::ADULT_KEYWORDS . ')\b/i', $name) && + if (preg_match('/\b('.self::ADULT_KEYWORDS.')\b/i', $name) && preg_match('/\b(720p|1080p|2160p|4k|mp4|mkv|avi|wmv)\b/i', $name)) { return true; } // Check for JAV/AV marker (common in Japanese adult releases) - if (preg_match('/\b(AV|JAV)\b/', $name) && preg_match('/\b(' . self::KNOWN_STUDIOS . ')\b/i', $name)) { + if (preg_match('/\b(AV|JAV)\b/', $name) && preg_match('/\b('.self::KNOWN_STUDIOS.')\b/i', $name)) { return true; } @@ -152,14 +152,14 @@ class XxxCategorizer extends AbstractCategorizer // This pattern is very common for adult sites but rare for regular content if (preg_match('/^[A-Za-z]+[.\-_ ](19|20)?\d{2}[.\-_ ]\d{2}[.\-_ ]\d{2}[.\-_ ][A-Za-z]/i', $name)) { // Check it's not a TV daily show by checking for adult keywords or specific patterns - if (preg_match('/\b(' . self::ADULT_KEYWORDS . ')\b/i', $name)) { + if (preg_match('/\b('.self::ADULT_KEYWORDS.')\b/i', $name)) { return true; } // Check for performer name patterns (firstname.lastname) after the date if (preg_match('/\d{2}[.\-_ ]([a-z]+)[.\-_ ]([a-z]+)[.\-_ ]/i', $name)) { // Has a "firstname.lastname" pattern after date - likely adult // But exclude obvious TV patterns - if (!preg_match('/\b(S\d{1,2}E\d{1,2}|Episode|Season|HDTV|PDTV)\b/i', $name)) { + if (! preg_match('/\b(S\d{1,2}E\d{1,2}|Episode|Season|HDTV|PDTV)\b/i', $name)) { return true; } } @@ -172,7 +172,7 @@ class XxxCategorizer extends AbstractCategorizer { // Skip photo packs unless there's a video hint if (preg_match('/\b(photo(set)?|image(set)?|pics?|wallpapers?|collection|pack)\b/i', $name) && - !preg_match('/\b(mp4|mkv|mov|wmv|avi|webm|h\.?264|x264|h\.?265|x265)\b/i', $name)) { + ! preg_match('/\b(mp4|mkv|mov|wmv|avi|webm|h\.?264|x264|h\.?265|x265)\b/i', $name)) { return null; } @@ -190,17 +190,17 @@ class XxxCategorizer extends AbstractCategorizer } // Check for known VR site - $hasVRSite = preg_match('/\b(' . self::VR_SITES . ')\b/i', $name); + $hasVRSite = preg_match('/\b('.self::VR_SITES.')\b/i', $name); // Require either a VR site token, explicit VR180/VR360, or VR device - if (!preg_match('/\bVR(?:180|360)\b/i', $name) && - !$hasVRSite && - !preg_match('/\b(?:GearVR|Oculus|Quest[123]?|PSVR|Vive|Index|Pimax)\b/i', $name)) { + if (! preg_match('/\bVR(?:180|360)\b/i', $name) && + ! $hasVRSite && + ! preg_match('/\b(?:GearVR|Oculus|Quest[123]?|PSVR|Vive|Index|Pimax)\b/i', $name)) { return null; } // VR pattern matching - includes VR devices - $vrPattern = '/\b(' . self::VR_SITES . ')\b|\bVR(?:180|360)\b|\b(?:5K|6K|7K|8K)\b.*\bVR\b|\b(?:GearVR|Oculus|Quest[123]?|PSVR|Vive|Index|Pimax)\b/i'; + $vrPattern = '/\b('.self::VR_SITES.')\b|\bVR(?:180|360)\b|\b(?:5K|6K|7K|8K)\b.*\bVR\b|\b(?:GearVR|Oculus|Quest[123]?|PSVR|Vive|Index|Pimax)\b/i'; if (preg_match($vrPattern, $name)) { // VR sites are definitively adult content @@ -208,7 +208,7 @@ class XxxCategorizer extends AbstractCategorizer return $this->matched(Category::XXX_VR, 0.95, 'vr_site'); } // VR device with adult keywords - if (preg_match('/\bXXX\b/i', $name) || preg_match('/\b(' . self::ADULT_KEYWORDS . ')\b/i', $name)) { + if (preg_match('/\bXXX\b/i', $name) || preg_match('/\b('.self::ADULT_KEYWORDS.')\b/i', $name)) { return $this->matched(Category::XXX_VR, 0.9, 'vr_device'); } } @@ -218,16 +218,16 @@ class XxxCategorizer extends AbstractCategorizer protected function checkUHD(string $name): ?CategorizationResult { - if (!preg_match('/\b(2160p|4k|UHD|Ultra[._ -]?HD)\b/i', $name)) { + if (! preg_match('/\b(2160p|4k|UHD|Ultra[._ -]?HD)\b/i', $name)) { return null; } // Check for adult markers $hasAdultMarker = preg_match('/\bXXX\b/i', $name) || - preg_match('/\b(' . self::KNOWN_STUDIOS . ')\b/i', strtolower($name)) || + preg_match('/\b('.self::KNOWN_STUDIOS.')\b/i', strtolower($name)) || preg_match('/\b(Hardcore|Porn|Sex|Anal|Creampie|MILF|Lesbian|Teen|Interracial)\b/i', $name); - if (!$hasAdultMarker) { + if (! $hasAdultMarker) { return null; } @@ -255,44 +255,46 @@ class XxxCategorizer extends AbstractCategorizer $hasHD = preg_match('/\b(720p|1080p|2160p|HD|4K)\b/i', $name); // Studio + performer + HD resolution - if (preg_match('/^(' . self::KNOWN_STUDIOS . ')\.([A-Z][a-z]+).*?(720p|1080p|2160p|HD|4K)/i', $name)) { + if (preg_match('/^('.self::KNOWN_STUDIOS.')\.([A-Z][a-z]+).*?(720p|1080p|2160p|HD|4K)/i', $name)) { return $this->matched(Category::XXX_CLIPHD, 0.9, 'clip_hd_studio'); } // Known studio with date pattern: site.YYYY.MM.DD or site.YY.MM.DD - if (preg_match('/^(' . self::KNOWN_STUDIOS . ')[.\-_ ](19|20)?\d{2}[.\-_ ]\d{2}[.\-_ ]\d{2}/i', $name)) { + if (preg_match('/^('.self::KNOWN_STUDIOS.')[.\-_ ](19|20)?\d{2}[.\-_ ]\d{2}[.\-_ ]\d{2}/i', $name)) { if ($hasHD) { return $this->matched(Category::XXX_CLIPHD, 0.95, 'clip_hd_studio_date'); } + // Even without HD marker, if it's a known studio with date pattern, likely XXX return $this->matched(Category::XXX_X264, 0.85, 'studio_date'); } // Date pattern with 4-digit year: site.YYYY.MM.DD.performer.title.1080p if (preg_match('/^([A-Z][a-zA-Z0-9]+)[.\-_ ](19|20)\d{2}[.\-_ ]\d{2}[.\-_ ]\d{2}[.\-_ ]/i', $name) && - !preg_match('/\b(S\d{2}E\d{2}|Documentary|Series)\b/i', $name)) { + ! preg_match('/\b(S\d{2}E\d{2}|Documentary|Series)\b/i', $name)) { // Check if it has adult keywords or HD resolution - if ($hasHD || preg_match('/\b(' . self::ADULT_KEYWORDS . ')\b/i', $name)) { + if ($hasHD || preg_match('/\b('.self::ADULT_KEYWORDS.')\b/i', $name)) { return $this->matched(Category::XXX_CLIPHD, 0.85, 'clip_hd_date_4digit'); } } // Date pattern with 2-digit year: site.YY.MM.DD.performer.title.1080p if (preg_match('/^([A-Z][a-zA-Z0-9]+)\.(\d{2})\.(\d{2})\.(\d{2})\..*?(720p|1080p|2160p|HD|4K)/i', $name) && - !preg_match('/\b(S\d{2}E\d{2}|Documentary|Series)\b/i', $name)) { + ! preg_match('/\b(S\d{2}E\d{2}|Documentary|Series)\b/i', $name)) { return $this->matched(Category::XXX_CLIPHD, 0.85, 'clip_hd_date'); } // JAV compact date pattern: site.YYMMDD (e.g., 10musume.121025) - if (preg_match('/^(' . self::KNOWN_STUDIOS . ')[.\-_ ](\d{6})/i', $name)) { + if (preg_match('/^('.self::KNOWN_STUDIOS.')[.\-_ ](\d{6})/i', $name)) { if ($hasHD) { return $this->matched(Category::XXX_CLIPHD, 0.9, 'clip_hd_jav_date'); } + return $this->matched(Category::XXX_X264, 0.85, 'jav_date'); } // Known studio with XXX marker and HD resolution - if (preg_match('/^(' . self::KNOWN_STUDIOS . ')[.\-_ ].*\bXXX\b.*?(720p|1080p|2160p|HD|4K)/i', $name)) { + if (preg_match('/^('.self::KNOWN_STUDIOS.')[.\-_ ].*\bXXX\b.*?(720p|1080p|2160p|HD|4K)/i', $name)) { return $this->matched(Category::XXX_CLIPHD, 0.9, 'clip_hd_studio_xxx'); } @@ -344,8 +346,8 @@ class XxxCategorizer extends AbstractCategorizer } if (preg_match('/web[._ -]dl|web-?rip/i', $name) && - (preg_match('/\b(' . self::ADULT_KEYWORDS . ')\b/i', $name) || - preg_match('/\b(' . self::KNOWN_STUDIOS . ')\b/i', $name) || + (preg_match('/\b('.self::ADULT_KEYWORDS.')\b/i', $name) || + preg_match('/\b('.self::KNOWN_STUDIOS.')\b/i', $name) || preg_match('/\b(XXX|Porn|Adult|JAV|Hentai)\b/i', $name))) { return $this->matched(Category::XXX_WEBDL, 0.85, 'webdl'); } @@ -361,7 +363,7 @@ class XxxCategorizer extends AbstractCategorizer } // Require H.264/x264/AVC - if (!preg_match('/\b((x|h)[\.\-_ ]?264|AVC)\b/i', $name)) { + if (! preg_match('/\b((x|h)[\.\-_ ]?264|AVC)\b/i', $name)) { return null; } @@ -430,4 +432,3 @@ class XxxCategorizer extends AbstractCategorizer return null; } } - diff --git a/app/Services/Categorization/Contracts/CategorizerInterface.php b/app/Services/Categorization/Contracts/CategorizerInterface.php index f1c2ab402..a36275cbd 100644 --- a/app/Services/Categorization/Contracts/CategorizerInterface.php +++ b/app/Services/Categorization/Contracts/CategorizerInterface.php @@ -1,10 +1,13 @@ categorizer = new BookCategorizer(); + $this->categorizer = new BookCategorizer; } public function getName(): string diff --git a/app/Services/Categorization/Pipes/CategorizationPassable.php b/app/Services/Categorization/Pipes/CategorizationPassable.php index bbb1fe335..516cb2b7e 100644 --- a/app/Services/Categorization/Pipes/CategorizationPassable.php +++ b/app/Services/Categorization/Pipes/CategorizationPassable.php @@ -11,8 +11,11 @@ use App\Services\Categorization\ReleaseContext; class CategorizationPassable { public ReleaseContext $context; + public CategorizationResult $bestResult; + public bool $debug; + public array $allResults = []; public function __construct(ReleaseContext $context, bool $debug = false) @@ -70,4 +73,3 @@ class CategorizationPassable return $returnValue; } } - diff --git a/app/Services/Categorization/Pipes/ConsolePipe.php b/app/Services/Categorization/Pipes/ConsolePipe.php index ce4534d92..74087acd2 100644 --- a/app/Services/Categorization/Pipes/ConsolePipe.php +++ b/app/Services/Categorization/Pipes/ConsolePipe.php @@ -12,11 +12,12 @@ use App\Services\Categorization\ReleaseContext; class ConsolePipe extends AbstractCategorizationPipe { protected int $priority = 35; + private ConsoleCategorizer $categorizer; public function __construct() { - $this->categorizer = new ConsoleCategorizer(); + $this->categorizer = new ConsoleCategorizer; } public function getName(): string @@ -34,4 +35,3 @@ class ConsolePipe extends AbstractCategorizationPipe return $this->categorizer->categorize($context); } } - diff --git a/app/Services/Categorization/Pipes/GroupNamePipe.php b/app/Services/Categorization/Pipes/GroupNamePipe.php index 3f16f4912..e5295c639 100644 --- a/app/Services/Categorization/Pipes/GroupNamePipe.php +++ b/app/Services/Categorization/Pipes/GroupNamePipe.php @@ -12,11 +12,12 @@ use App\Services\Categorization\ReleaseContext; class GroupNamePipe extends AbstractCategorizationPipe { protected int $priority = 5; + private GroupNameCategorizer $categorizer; public function __construct() { - $this->categorizer = new GroupNameCategorizer(); + $this->categorizer = new GroupNameCategorizer; } public function getName(): string @@ -29,4 +30,3 @@ class GroupNamePipe extends AbstractCategorizationPipe return $this->categorizer->categorize($context); } } - diff --git a/app/Services/Categorization/Pipes/MiscPipe.php b/app/Services/Categorization/Pipes/MiscPipe.php index da56a48eb..f1748db0c 100644 --- a/app/Services/Categorization/Pipes/MiscPipe.php +++ b/app/Services/Categorization/Pipes/MiscPipe.php @@ -14,11 +14,12 @@ use App\Services\Categorization\ReleaseContext; class MiscPipe extends AbstractCategorizationPipe { protected int $priority = 1; // Run first to catch hashes early + private MiscCategorizer $categorizer; public function __construct() { - $this->categorizer = new MiscCategorizer(); + $this->categorizer = new MiscCategorizer; } public function getName(): string @@ -31,4 +32,3 @@ class MiscPipe extends AbstractCategorizationPipe return $this->categorizer->categorize($context); } } - diff --git a/app/Services/Categorization/Pipes/MoviePipe.php b/app/Services/Categorization/Pipes/MoviePipe.php index 4c01726ed..23c55e34c 100644 --- a/app/Services/Categorization/Pipes/MoviePipe.php +++ b/app/Services/Categorization/Pipes/MoviePipe.php @@ -12,11 +12,12 @@ use App\Services\Categorization\ReleaseContext; class MoviePipe extends AbstractCategorizationPipe { protected int $priority = 25; + private MovieCategorizer $categorizer; public function __construct() { - $this->categorizer = new MovieCategorizer(); + $this->categorizer = new MovieCategorizer; } public function getName(): string @@ -34,4 +35,3 @@ class MoviePipe extends AbstractCategorizationPipe return $this->categorizer->categorize($context); } } - diff --git a/app/Services/Categorization/Pipes/MusicPipe.php b/app/Services/Categorization/Pipes/MusicPipe.php index fa127aeda..c7bcdf651 100644 --- a/app/Services/Categorization/Pipes/MusicPipe.php +++ b/app/Services/Categorization/Pipes/MusicPipe.php @@ -12,11 +12,12 @@ use App\Services\Categorization\ReleaseContext; class MusicPipe extends AbstractCategorizationPipe { protected int $priority = 40; + private MusicCategorizer $categorizer; public function __construct() { - $this->categorizer = new MusicCategorizer(); + $this->categorizer = new MusicCategorizer; } public function getName(): string @@ -34,4 +35,3 @@ class MusicPipe extends AbstractCategorizationPipe return $this->categorizer->categorize($context); } } - diff --git a/app/Services/Categorization/Pipes/PcPipe.php b/app/Services/Categorization/Pipes/PcPipe.php index 8fa28c3f4..8490505e8 100644 --- a/app/Services/Categorization/Pipes/PcPipe.php +++ b/app/Services/Categorization/Pipes/PcPipe.php @@ -12,11 +12,12 @@ use App\Services\Categorization\ReleaseContext; class PcPipe extends AbstractCategorizationPipe { protected int $priority = 30; + private PcCategorizer $categorizer; public function __construct() { - $this->categorizer = new PcCategorizer(); + $this->categorizer = new PcCategorizer; } public function getName(): string @@ -34,4 +35,3 @@ class PcPipe extends AbstractCategorizationPipe return $this->categorizer->categorize($context); } } - diff --git a/app/Services/Categorization/Pipes/TvPipe.php b/app/Services/Categorization/Pipes/TvPipe.php index 213412609..e710df50b 100644 --- a/app/Services/Categorization/Pipes/TvPipe.php +++ b/app/Services/Categorization/Pipes/TvPipe.php @@ -12,11 +12,12 @@ use App\Services\Categorization\ReleaseContext; class TvPipe extends AbstractCategorizationPipe { protected int $priority = 20; + private TvCategorizer $categorizer; public function __construct() { - $this->categorizer = new TvCategorizer(); + $this->categorizer = new TvCategorizer; } public function getName(): string @@ -34,4 +35,3 @@ class TvPipe extends AbstractCategorizationPipe return $this->categorizer->categorize($context); } } - diff --git a/app/Services/Categorization/Pipes/XxxPipe.php b/app/Services/Categorization/Pipes/XxxPipe.php index dd9afe168..cf0dfcc14 100644 --- a/app/Services/Categorization/Pipes/XxxPipe.php +++ b/app/Services/Categorization/Pipes/XxxPipe.php @@ -12,11 +12,12 @@ use App\Services\Categorization\ReleaseContext; class XxxPipe extends AbstractCategorizationPipe { protected int $priority = 10; + private XxxCategorizer $categorizer; public function __construct() { - $this->categorizer = new XxxCategorizer(); + $this->categorizer = new XxxCategorizer; } public function getName(): string @@ -29,4 +30,3 @@ class XxxPipe extends AbstractCategorizationPipe return $this->categorizer->categorize($context); } } - diff --git a/app/Services/Categorization/ReleaseContext.php b/app/Services/Categorization/ReleaseContext.php index ea2f1b868..2007b1f15 100644 --- a/app/Services/Categorization/ReleaseContext.php +++ b/app/Services/Categorization/ReleaseContext.php @@ -70,4 +70,3 @@ class ReleaseContext return false; } } - diff --git a/app/Services/CollectionCleanupService.php b/app/Services/CollectionCleanupService.php index 6e00ea29a..ea2aae910 100644 --- a/app/Services/CollectionCleanupService.php +++ b/app/Services/CollectionCleanupService.php @@ -9,8 +9,7 @@ use Illuminate\Support\Str; class CollectionCleanupService { - public function __construct() - {} + public function __construct() {} /** * Deletes finished/old collections, cleans orphans, and removes collections missed after NZB creation. diff --git a/app/Services/CollectionsCleaningService.php b/app/Services/CollectionsCleaningService.php index cf331795c..b34f19ef1 100644 --- a/app/Services/CollectionsCleaningService.php +++ b/app/Services/CollectionsCleaningService.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Services; - /** * Cleans names for collections/imports/namefixer. * @@ -87,9 +86,10 @@ class CollectionsCleaningService * Attempts to extract a clean collection name from a Usenet subject line. * First tries database regexes, then falls back to generic cleaning patterns. * - * @param string $subject The raw subject line to clean - * @param string $groupName The newsgroup name (optional, used for context-specific cleaning) + * @param string $subject The raw subject line to clean + * @param string $groupName The newsgroup name (optional, used for context-specific cleaning) * @return array Returns ['id' => regex_id, 'name' => cleaned_name] + * * @throws \Exception */ public function collectionsCleaner(string $subject, string $groupName = ''): array @@ -204,7 +204,7 @@ class CollectionsCleaningService /** * Enhance short or generic subjects by extracting additional information. * - * @param string $cleanSubject The cleaned subject that is too short + * @param string $cleanSubject The cleaned subject that is too short * @return string Enhanced subject string */ protected function enhanceShortSubject(string $cleanSubject): string @@ -251,7 +251,7 @@ class CollectionsCleaningService /** * Normalize a string by collapsing multiple spaces and encoding to UTF-8. * - * @param string $subject The subject to normalize + * @param string $subject The subject to normalize * @return string Normalized and UTF-8 encoded string */ protected function normalizeString(string $subject): string diff --git a/app/Services/ConsoleService.php b/app/Services/ConsoleService.php index a18596be7..1c4f051e7 100644 --- a/app/Services/ConsoleService.php +++ b/app/Services/ConsoleService.php @@ -67,7 +67,7 @@ class ConsoleService $this->asstag = Settings::settingValue('amazonassociatetag'); $this->gameQty = (Settings::settingValue('maxgamesprocessed') !== '') ? (int) Settings::settingValue('maxgamesprocessed') : 150; $this->sleepTime = (Settings::settingValue('amazonsleep') !== '') ? (int) Settings::settingValue('amazonsleep') : 1000; - $this->imgSavePath = config('nntmux_settings.covers_path') . '/console/'; + $this->imgSavePath = config('nntmux_settings.covers_path').'/console/'; $this->renamed = (int) Settings::settingValue('lookupgames') === 2; $this->failCache = []; @@ -103,11 +103,11 @@ class ConsoleService foreach (explode(' ', $title) as $word) { $word = trim(rtrim(trim($word), '-')); if ($word !== '' && $word !== '-') { - $word = '+' . $word; + $word = '+'.$word; $searchWords .= sprintf('%s ', $word); } } - $searchWords = trim($searchWords . '+' . $platform); + $searchWords = trim($searchWords.'+'.$platform); return ConsoleInfo::search($searchWords)->first() ?? false; } @@ -133,7 +133,7 @@ class ConsoleService } $exccatlist = ''; if (\count($excludedCats) > 0) { - $exccatlist = ' AND r.categories_id NOT IN (' . implode(',', $excludedCats) . ')'; + $exccatlist = ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')'; } $order = $this->getConsoleOrder($orderBy); $calcSql = sprintf( @@ -155,17 +155,17 @@ class ConsoleService $exccatlist, $order[0], $order[1], - ($start === false ? '' : ' LIMIT ' . $num . ' OFFSET ' . $start) + ($start === false ? '' : ' LIMIT '.$num.' OFFSET '.$start) ); - $cached = Cache::get(md5($calcSql . $page)); + $cached = Cache::get(md5($calcSql.$page)); if ($cached !== null) { $consoles = $cached; } else { $data = DB::select($calcSql); $consoles = ['total' => DB::select('SELECT FOUND_ROWS() AS total'), 'result' => $data]; $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium')); - Cache::put(md5($calcSql . $page), $consoles, $expiresAt); + Cache::put(md5($calcSql.$page), $consoles, $expiresAt); } $consoleIDs = $releaseIDs = false; @@ -203,7 +203,7 @@ class ConsoleService $order[1] ); - $return = Cache::get(md5($sql . $page)); + $return = Cache::get(md5($sql.$page)); if ($return !== null) { return $return; } @@ -214,7 +214,7 @@ class ConsoleService } $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_long')); - Cache::put(md5($sql . $page), $return, $expiresAt); + Cache::put(md5($sql.$page), $return, $expiresAt); return $return; } @@ -277,7 +277,7 @@ class ConsoleService foreach ($this->getBrowseByOptions() as $bbk => $bbv) { if (! empty($_REQUEST[$bbk])) { $bbs = stripslashes($_REQUEST[$bbk]); - $browseBy .= ' AND con.' . $bbv . ' LIKE ' . escapeString('%' . $bbs . '%'); + $browseBy .= ' AND con.'.$bbv.' LIKE '.escapeString('%'.$bbs.'%'); } } @@ -351,12 +351,12 @@ class ConsoleService $consoleId = $this->updateConsoleTable($igdb); if ($this->echoOutput && $consoleId !== -2) { - cli()->header('Added/updated game: ') . - cli()->alternateOver(' Title: ') . - cli()->primary($igdb['title']) . - cli()->alternateOver(' Platform: ') . - cli()->primary($igdb['platform']) . - cli()->alternateOver(' Genre: ') . + cli()->header('Added/updated game: '). + cli()->alternateOver(' Title: '). + cli()->primary($igdb['title']). + cli()->alternateOver(' Platform: '). + cli()->primary($igdb['platform']). + cli()->alternateOver(' Genre: '). cli()->primary($igdb['consolegenre']); } } @@ -435,9 +435,9 @@ class ConsoleService 'title' => $game->name, 'asin' => $game->id, 'review' => $game->summary ?? '', - 'coverurl' => ! empty($game->cover->url) ? 'https:' . $game->cover->url : '', + 'coverurl' => ! empty($game->cover->url) ? 'https:'.$game->cover->url : '', 'releasedate' => ! empty($game->first_release_date) ? $game->first_release_date->format('Y-m-d') : now()->format('Y-m-d'), - 'esrb' => ! empty($game->aggregated_rating) ? round($game->aggregated_rating) . '%' : 'Not Rated', + 'esrb' => ! empty($game->aggregated_rating) ? round($game->aggregated_rating).'%' : 'Not Rated', 'url' => $game->url ?? '', 'publisher' => ! empty($publishers) ? implode(',', $publishers) : 'Unknown', 'platform' => $platform ?? '', @@ -460,7 +460,7 @@ class ConsoleService $this->igdbSleep = now()->endOfMonth(); } } catch (\Exception $e) { - cli()->error('Error fetching IGDB properties: ' . $e->getMessage()); + cli()->error('Error fetching IGDB properties: '.$e->getMessage()); return false; } @@ -494,7 +494,7 @@ class ConsoleService $releaseCount = $res->count(); if ($res instanceof \Traversable && $releaseCount > 0) { if ($this->echoOutput) { - cli()->header('Processing ' . $releaseCount . ' console release(s).'); + cli()->header('Processing '.$releaseCount.' console release(s).'); } foreach ($res as $arr) { @@ -505,13 +505,13 @@ class ConsoleService if ($gameInfo !== false) { if ($this->echoOutput) { - cli()->info('Looking up: ' . $gameInfo['title'] . ' (' . $gameInfo['platform'] . ')'); + cli()->info('Looking up: '.$gameInfo['title'].' ('.$gameInfo['platform'].')'); } // Check for existing console entry. $gameCheck = $this->getConsoleInfoByName($gameInfo['title'], $gameInfo['platform']); - if ($gameCheck === false && \in_array($gameInfo['title'] . $gameInfo['platform'], $this->failCache, false)) { + if ($gameCheck === false && \in_array($gameInfo['title'].$gameInfo['platform'], $this->failCache, false)) { // Lookup recently failed, no point trying again if ($this->echoOutput) { cli()->info('Cached previous failure. Skipping.'); @@ -522,11 +522,11 @@ class ConsoleService $usedAmazon = true; if ($gameId === null) { $gameId = -2; - $this->failCache[] = $gameInfo['title'] . $gameInfo['platform']; + $this->failCache[] = $gameInfo['title'].$gameInfo['platform']; } } else { if ($this->echoOutput) { - cli()->headerOver('Found Local: ') . + cli()->headerOver('Found Local: '). cli()->primary("{$gameInfo['title']} - {$gameInfo['platform']}"); } $gameId = $gameCheck['id'] ?? -2; @@ -555,8 +555,6 @@ class ConsoleService /** * Parse release title for game info. - * - * @return array|false */ public function parseTitle(string $releaseName): array|false { @@ -681,8 +679,6 @@ class ConsoleService /** * Match browse node to genre name. - * - * @return bool|string */ public function matchBrowseNode(string $nodeName): bool|string { @@ -770,7 +766,6 @@ class ConsoleService /** * Get or create genre key. * - * @return false|int|string * * @throws \Exception */ @@ -799,4 +794,3 @@ class ConsoleService return $gen->loadGenres((string) GenreService::CONSOLE_TYPE); } } - diff --git a/app/Services/FanartTvService.php b/app/Services/FanartTvService.php index 49a3f13b3..c74ddad55 100644 --- a/app/Services/FanartTvService.php +++ b/app/Services/FanartTvService.php @@ -322,4 +322,3 @@ class FanartTvService return Cache::forget('fanarttv_tv_'.$tvdbId); } } - diff --git a/app/Services/ForkingService.php b/app/Services/ForkingService.php index 2111dafdb..015d89e8c 100644 --- a/app/Services/ForkingService.php +++ b/app/Services/ForkingService.php @@ -43,10 +43,10 @@ class ForkingService $this->maxRetries = max($this->maxRetries, -8); // Initialize runners - $this->backfillRunner = new BackfillRunner(); - $this->binariesRunner = new BinariesRunner(); - $this->releasesRunner = new ReleasesRunner(); - $this->postProcessRunner = new PostProcessRunner(); + $this->backfillRunner = new BackfillRunner; + $this->binariesRunner = new BinariesRunner; + $this->releasesRunner = new ReleasesRunner; + $this->postProcessRunner = new PostProcessRunner; } /** @@ -215,8 +215,8 @@ class ForkingService if (config('nntmux.echocli')) { cli()->header( - "Multi-processing for {$taskName} finished in " . (now()->timestamp - $startTime) . - ' seconds at ' . now()->toRfc2822String() . '.' . PHP_EOL + "Multi-processing for {$taskName} finished in ".(now()->timestamp - $startTime). + ' seconds at '.now()->toRfc2822String().'.'.PHP_EOL ); } } @@ -279,8 +279,7 @@ class ForkingService protected function log(string $message): void { if (config('nntmux.echocli')) { - echo $message . PHP_EOL; + echo $message.PHP_EOL; } } } - diff --git a/app/Services/GamesService.php b/app/Services/GamesService.php index 503f23abb..444c6207c 100644 --- a/app/Services/GamesService.php +++ b/app/Services/GamesService.php @@ -30,29 +30,44 @@ use Illuminate\Support\Facades\Log; class GamesService { protected const int GAME_MATCH_PERCENTAGE = 85; + protected const int GAME_CACHE_TTL = 86400; // 24 hours + protected const int FAILED_LOOKUP_CACHE_TTL = 3600; // 1 hour public bool $echoOutput; + public string|int|null $gameQty; + public string $imgSavePath; + public int $matchPercentage; + public bool $maxHitRequest; + public string $renamed; + public string $catWhere; protected SteamService $steamService; + protected IGDBService $igdbService; + protected GamesTitleParser $titleParser; + protected ReleaseImageService $imageService; // Processing stats protected int $processedCount = 0; + protected int $matchedCount = 0; + protected int $failedCount = 0; + protected int $cachedCount = 0; protected string $_classUsed = ''; + protected $igdbSleep; /** @@ -71,11 +86,11 @@ class GamesService $this->imageService = $imageService ?? new ReleaseImageService; $this->gameQty = Settings::settingValue('maxgamesprocessed') !== '' ? (int) Settings::settingValue('maxgamesprocessed') : 150; - $this->imgSavePath = config('nntmux_settings.covers_path') . '/games/'; + $this->imgSavePath = config('nntmux_settings.covers_path').'/games/'; $this->renamed = (int) Settings::settingValue('lookupgames') === 2 ? 'AND isrenamed = 1' : ''; $this->matchPercentage = 60; $this->maxHitRequest = false; - $this->catWhere = 'AND categories_id = ' . Category::PC_GAMES . ' '; + $this->catWhere = 'AND categories_id = '.Category::PC_GAMES.' '; } // ======================================== @@ -142,8 +157,8 @@ class GamesService ->from('gamesinfo as gi') ->join('genres as g', 'gi.genres_id', '=', 'g.id'); - if (!empty($search)) { - $query->where('gi.title', 'like', '%' . $search . '%'); + if (! empty($search)) { + $query->where('gi.title', 'like', '%'.$search.'%'); } return $query->orderByDesc('created_at') @@ -178,27 +193,27 @@ class GamesService } $exccatlist = ''; if (count($excludedCats) > 0) { - $exccatlist = ' AND r.categories_id NOT IN (' . implode(',', $excludedCats) . ')'; + $exccatlist = ' AND r.categories_id NOT IN ('.implode(',', $excludedCats).')'; } $order = $this->getGamesOrder($orderBy); $gamesSql = "SELECT SQL_CALC_FOUND_ROWS gi.id, GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id FROM gamesinfo gi LEFT JOIN releases r ON gi.id = r.gamesinfo_id WHERE gi.title != '' AND gi.cover = 1 AND r.passwordstatus " - . app(\App\Services\Releases\ReleaseBrowseService::class)->showPasswords() . - $browseBy . - $catsrch . - $maxAge . - $exccatlist . - ' GROUP BY gi.id ORDER BY ' . ($order[0]) . ' ' . ($order[1]) . - ($start === false ? '' : ' LIMIT ' . $num . ' OFFSET ' . $start); + .app(\App\Services\Releases\ReleaseBrowseService::class)->showPasswords(). + $browseBy. + $catsrch. + $maxAge. + $exccatlist. + ' GROUP BY gi.id ORDER BY '.($order[0]).' '.($order[1]). + ($start === false ? '' : ' LIMIT '.$num.' OFFSET '.$start); $expiresAt = now()->addMinutes(config('nntmux.cache_expiry_medium')); - $gamesCache = Cache::get(md5($gamesSql . $page)); + $gamesCache = Cache::get(md5($gamesSql.$page)); if ($gamesCache !== null) { $games = $gamesCache; } else { $data = DB::select($gamesSql); $games = ['total' => DB::select('SELECT FOUND_ROWS() AS total'), 'result' => $data]; - Cache::put(md5($gamesSql . $page), $games, $expiresAt); + Cache::put(md5($gamesSql.$page), $games, $expiresAt); } $gameIDs = $releaseIDs = false; @@ -210,9 +225,9 @@ class GamesService } $returnSql = - 'SELECT r.id, r.rarinnerfilecount, r.grabs, r.comments, r.totalpart, r.size, r.postdate, r.searchname, r.haspreview, r.passwordstatus, r.guid, g.name AS group_name, df.failed AS failed, gi.*, YEAR (gi.releasedate) as year, r.gamesinfo_id, rn.releases_id AS nfoid FROM releases r LEFT OUTER JOIN usenet_groups g ON g.id = r.groups_id LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id INNER JOIN gamesinfo gi ON gi.id = r.gamesinfo_id WHERE gi.id IN (' . (is_array($gameIDs) ? implode(',', $gameIDs) : -1) . ') AND r.id IN (' . (is_array($releaseIDs) ? implode(',', $releaseIDs) : -1) . ')' . $catsrch . ' GROUP BY gi.id ORDER BY ' . ($order[0]) . ' ' . ($order[1]); + 'SELECT r.id, r.rarinnerfilecount, r.grabs, r.comments, r.totalpart, r.size, r.postdate, r.searchname, r.haspreview, r.passwordstatus, r.guid, g.name AS group_name, df.failed AS failed, gi.*, YEAR (gi.releasedate) as year, r.gamesinfo_id, rn.releases_id AS nfoid FROM releases r LEFT OUTER JOIN usenet_groups g ON g.id = r.groups_id LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id INNER JOIN gamesinfo gi ON gi.id = r.gamesinfo_id WHERE gi.id IN ('.(is_array($gameIDs) ? implode(',', $gameIDs) : -1).') AND r.id IN ('.(is_array($releaseIDs) ? implode(',', $releaseIDs) : -1).')'.$catsrch.' GROUP BY gi.id ORDER BY '.($order[0]).' '.($order[1]); - $return = Cache::get(md5($returnSql . $page)); + $return = Cache::get(md5($returnSql.$page)); if ($return !== null) { return $return; } @@ -220,7 +235,7 @@ class GamesService if (count($return) > 0) { $return[0]->_totalcount = $games['total'][0]->total ?? 0; } - Cache::put(md5($returnSql . $page), $return, $expiresAt); + Cache::put(md5($returnSql.$page), $return, $expiresAt); return $return; } @@ -273,12 +288,12 @@ class GamesService { $browseBy = ' '; foreach ($this->getBrowseByOptions() as $bbk => $bbv) { - if (!empty($_REQUEST[$bbk])) { + if (! empty($_REQUEST[$bbk])) { $bbs = stripslashes($_REQUEST[$bbk]); if ($bbk === 'year') { - $browseBy .= ' AND YEAR (gi.releasedate) ' . 'LIKE ' . escapeString('%' . $bbs . '%'); + $browseBy .= ' AND YEAR (gi.releasedate) '.'LIKE '.escapeString('%'.$bbs.'%'); } else { - $browseBy .= ' AND gi.' . $bbv . ' ' . 'LIKE ' . escapeString('%' . $bbs . '%'); + $browseBy .= ' AND gi.'.$bbv.' '.'LIKE '.escapeString('%'.$bbs.'%'); } } } @@ -334,6 +349,7 @@ class GamesService if (Cache::has("game_lookup_failed:{$titleKey}")) { Log::debug('GamesService: Skipping previously failed lookup', ['title' => $gameInfo['title']]); $this->cachedCount++; + return false; } @@ -342,6 +358,7 @@ class GamesService if ($cachedResult !== null) { Log::debug('GamesService: Using cached lookup result', ['title' => $gameInfo['title']]); $this->cachedCount++; + return $this->saveGameInfoFromCache($cachedResult, $gen, $gameInfo); } @@ -373,6 +390,7 @@ class GamesService $game = $this->igdbService->buildGameData($igdbGame, $genreName); } else { Cache::put("game_lookup_failed:{$titleKey}", true, self::FAILED_LOOKUP_CACHE_TTL); + return false; } } @@ -386,6 +404,7 @@ class GamesService if (empty($game)) { Cache::put("game_lookup_failed:{$titleKey}", true, self::FAILED_LOOKUP_CACHE_TTL); + return false; } @@ -400,32 +419,32 @@ class GamesService { $game = []; - if (!empty($steamResults['cover'])) { + if (! empty($steamResults['cover'])) { $game['coverurl'] = (string) $steamResults['cover']; } - if (!empty($steamResults['backdrop'])) { + if (! empty($steamResults['backdrop'])) { $game['backdropurl'] = (string) $steamResults['backdrop']; } $game['title'] = (string) $steamResults['title']; $game['asin'] = $steamResults['steamid']; $game['url'] = (string) $steamResults['directurl']; - $game['publisher'] = !empty($steamResults['publisher']) ? (string) $steamResults['publisher'] : 'Unknown'; - $game['esrb'] = !empty($steamResults['rating']) ? (string) $steamResults['rating'] : 'Not Rated'; + $game['publisher'] = ! empty($steamResults['publisher']) ? (string) $steamResults['publisher'] : 'Unknown'; + $game['esrb'] = ! empty($steamResults['rating']) ? (string) $steamResults['rating'] : 'Not Rated'; - if (!empty($steamResults['releasedate'])) { + if (! empty($steamResults['releasedate'])) { $dateReleased = strtotime($steamResults['releasedate']) === false ? '' : $steamResults['releasedate']; $game['releasedate'] = ($steamResults['releasedate'] === '' || strtotime($steamResults['releasedate']) === false) ? null : Carbon::createFromFormat('M j, Y', Carbon::parse($dateReleased)->toFormattedDateString())->format('Y-m-d'); } - if (!empty($steamResults['description'])) { + if (! empty($steamResults['description'])) { $game['review'] = (string) $steamResults['description']; } - if (!empty($steamResults['genres'])) { + if (! empty($steamResults['genres'])) { $genreName = $this->igdbService->matchGenre($steamResults['genres']); } @@ -443,16 +462,16 @@ class GamesService // Prepare database values $game['cover'] = isset($game['coverurl']) ? 1 : 0; $game['backdrop'] = isset($game['backdropurl']) ? 1 : 0; - if (!isset($game['trailer'])) { + if (! isset($game['trailer'])) { $game['trailer'] = 0; } if (empty($game['title'])) { $game['title'] = $gameInfo['title']; } - if (!isset($game['releasedate'])) { + if (! isset($game['releasedate'])) { $game['releasedate'] = ''; } - if (!isset($game['review'])) { + if (! isset($game['review'])) { $game['review'] = 'No Review'; } $game['classused'] = $this->_classUsed; @@ -472,7 +491,7 @@ class GamesService $gamesId = false; - if (!empty($game['asin'])) { + if (! empty($game['asin'])) { try { DB::beginTransaction(); @@ -529,18 +548,19 @@ class GamesService DB::rollBack(); Log::error('GamesService: Database error saving game', [ 'title' => $game['title'], - 'error' => $e->getMessage() + 'error' => $e->getMessage(), ]); + return false; } } - if (!empty($gamesId)) { + if (! empty($gamesId)) { if ($this->echoOutput) { - cli()->header('Added/updated game: ') . - cli()->alternateOver(' Title: ') . - cli()->primary($game['title']) . - cli()->alternateOver(' Source: ') . + cli()->header('Added/updated game: '). + cli()->alternateOver(' Title: '). + cli()->primary($game['title']). + cli()->alternateOver(' Source: '). cli()->primary($this->_classUsed); } @@ -551,11 +571,11 @@ class GamesService // Save backdrop image if ($game['backdrop'] === 1 && isset($game['backdropurl'])) { - $game['backdrop'] = $this->imageService->saveImage($gamesId . '-backdrop', $game['backdropurl'], $this->imgSavePath, 1920, 1024); + $game['backdrop'] = $this->imageService->saveImage($gamesId.'-backdrop', $game['backdropurl'], $this->imgSavePath, 1920, 1024); } } elseif ($this->echoOutput) { - cli()->headerOver('Nothing to update: ') . - cli()->primary($game['title'] . ' (PC)'); + cli()->headerOver('Nothing to update: '). + cli()->primary($game['title'].' (PC)'); } return $gamesId !== false ? $gamesId : false; @@ -577,7 +597,7 @@ class GamesService $game['gamesgenreID'] = $genreKey; - if (!empty($game['asin'])) { + if (! empty($game['asin'])) { $check = GamesInfo::query()->where('asin', $game['asin'])->first(); if ($check !== null) { return $check['id']; @@ -622,7 +642,7 @@ class GamesService if ($res->count() > 0) { if ($this->echoOutput) { - cli()->header('Processing ' . $res->count() . ' games release(s).'); + cli()->header('Processing '.$res->count().' games release(s).'); } Log::info('GamesService: Starting processing', ['count' => $res->count()]); @@ -637,7 +657,7 @@ class GamesService if ($gameInfo !== false) { if ($this->echoOutput) { - cli()->info('Looking up: ' . $gameInfo['title'] . ' (PC)'); + cli()->info('Looking up: '.$gameInfo['title'].' (PC)'); } // Check for existing games entry @@ -743,7 +763,7 @@ class GamesService } catch (\Exception $e) { Log::error('GamesService: Failed to update release', [ 'release_id' => $releaseId, - 'error' => $e->getMessage() + 'error' => $e->getMessage(), ]); } } diff --git a/app/Services/GamesTitleParser.php b/app/Services/GamesTitleParser.php index b750f7aa8..8f5cb4d43 100644 --- a/app/Services/GamesTitleParser.php +++ b/app/Services/GamesTitleParser.php @@ -88,7 +88,7 @@ class GamesTitleParser // Remove scene group suffix $groupAlternation = implode('|', array_map('preg_quote', self::SCENE_GROUPS)); - $name = (string) preg_replace('/\s*-\s*(?:' . $groupAlternation . '|[A-Z0-9]{2,})\s*$/i', '', $name); + $name = (string) preg_replace('/\s*-\s*(?:'.$groupAlternation.'|[A-Z0-9]{2,})\s*$/i', '', $name); // Replace separators with spaces $name = (string) preg_replace('/[._+]+/', ' ', $name); @@ -148,14 +148,16 @@ class GamesTitleParser $j++; } $startsWithV = preg_match('/^v\d+$/i', $tok) === 1; - if (($startsWithV && $numRun >= 1) || (!$startsWithV && $numRun >= 2)) { + if (($startsWithV && $numRun >= 1) || (! $startsWithV && $numRun >= 2)) { // Skip this version run $i = $j; + continue; } // Not a version run: keep the single number token $filtered[] = $tok; $i++; + continue; } $filtered[] = $tok; @@ -201,7 +203,7 @@ class GamesTitleParser // Strip scene groups at end $groupAlternation = implode('|', array_map('preg_quote', self::SCENE_GROUPS)); - $t = (string) preg_replace('/\s*-\s*(?:' . $groupAlternation . '|[A-Z0-9]{2,})\s*$/i', '', $t); + $t = (string) preg_replace('/\s*-\s*(?:'.$groupAlternation.'|[A-Z0-9]{2,})\s*$/i', '', $t); // Remove edition tokens and common noise $t = (string) preg_replace('/\b(game of the year|goty|definitive edition|deluxe edition|ultimate edition|complete edition|remastered|hd remaster|directors? cut|anniversary edition|update|patch|hotfix|incl(?:uding)? dlcs?|dlcs?|repack|rip|iso|crack(?:fix)?|beta|alpha)\b/i', ' ', $t); diff --git a/app/Services/GenreService.php b/app/Services/GenreService.php index eb15bc90a..19178e8cb 100644 --- a/app/Services/GenreService.php +++ b/app/Services/GenreService.php @@ -100,4 +100,3 @@ class GenreService // Clear other genre caches by pattern if needed } } - diff --git a/app/Services/IGDBService.php b/app/Services/IGDBService.php index d5872c95b..2d6e0286f 100644 --- a/app/Services/IGDBService.php +++ b/app/Services/IGDBService.php @@ -24,11 +24,14 @@ class IGDBService { // Rate limiting constants protected const string RATE_LIMIT_KEY = 'igdb_api_rate_limit'; + protected const int REQUESTS_PER_MINUTE = 4; + protected const int DECAY_SECONDS = 60; // Cache TTLs protected const int GAME_CACHE_TTL = 86400; // 24 hours + protected const int FAILED_LOOKUP_CACHE_TTL = 3600; // 1 hour // Matching configuration @@ -51,15 +54,16 @@ class IGDBService */ public function search(string $title): ?Game { - if (!$this->isConfigured() || empty($title)) { + if (! $this->isConfigured() || empty($title)) { return null; } - $cacheKey = 'igdb_search:' . md5(mb_strtolower($title)); + $cacheKey = 'igdb_search:'.md5(mb_strtolower($title)); // Check failed lookup cache if (Cache::has("igdb_search_failed:{$cacheKey}")) { Log::debug('IGDBService: Skipping previously failed search', ['title' => $title]); + return null; } @@ -67,6 +71,7 @@ class IGDBService $cached = Cache::get($cacheKey); if ($cached !== null) { Log::debug('IGDBService: Using cached search result', ['title' => $title]); + return $cached instanceof Game ? $cached : null; } @@ -121,6 +126,7 @@ class IGDBService $game = $this->searchExact($title); if ($game !== null) { Log::debug('IGDBService: Exact match found', ['title' => $title, 'matched' => $game->name]); + return $game; } @@ -128,6 +134,7 @@ class IGDBService $game = $this->searchFuzzy($title); if ($game !== null) { Log::debug('IGDBService: Fuzzy match found', ['title' => $title, 'matched' => $game->name]); + return $game; } @@ -137,6 +144,7 @@ class IGDBService $game = $this->searchFuzzy($cleanTitle); if ($game !== null) { Log::debug('IGDBService: Clean title match found', ['title' => $title, 'matched' => $game->name]); + return $game; } } @@ -147,6 +155,7 @@ class IGDBService $game = $this->searchFuzzy($baseTitle); if ($game !== null) { Log::debug('IGDBService: Base title match found', ['title' => $title, 'matched' => $game->name]); + return $game; } } @@ -176,6 +185,7 @@ class IGDBService return $result instanceof Game ? $result : null; } catch (\Exception $e) { Log::warning('IGDBService: Exact search error', ['error' => $e->getMessage()]); + return null; } } @@ -208,6 +218,7 @@ class IGDBService return $this->findBestMatch($results, $title); } catch (\Exception $e) { Log::warning('IGDBService: Fuzzy search error', ['error' => $e->getMessage()]); + return null; } } @@ -277,7 +288,7 @@ class IGDBService Log::debug('IGDBService: Best match selected', [ 'query' => $title, 'matched' => $bestMatch->name, - 'score' => $bestScore + 'score' => $bestScore, ]); } @@ -292,7 +303,7 @@ class IGDBService // Extract publishers and developers $publishers = []; $developers = []; - if (!empty($game->involved_companies)) { + if (! empty($game->involved_companies)) { $involvedCompanies = $game->involved_companies; if ($involvedCompanies instanceof \Illuminate\Support\Collection) { $involvedCompanies = $involvedCompanies->toArray(); @@ -335,7 +346,7 @@ class IGDBService $releaseDate = $this->getReleaseDate($game); // Get game URL - $gameUrl = $game->url ?? ('https://www.igdb.com/games/' . ($game->slug ?? $game->id)); + $gameUrl = $game->url ?? ('https://www.igdb.com/games/'.($game->slug ?? $game->id)); // Build review text $review = $this->buildReview($game, $developers); @@ -343,14 +354,14 @@ class IGDBService Log::info('IGDBService: Game data built', [ 'title' => $game->name, 'id' => $game->id, - 'has_cover' => !empty($coverUrl), - 'has_backdrop' => !empty($backdropUrl), + 'has_cover' => ! empty($coverUrl), + 'has_backdrop' => ! empty($backdropUrl), 'genres' => $genres, ]); return [ 'title' => $game->name, - 'asin' => 'igdb-' . $game->id, + 'asin' => 'igdb-'.$game->id, 'review' => $review, 'coverurl' => $coverUrl, 'releasedate' => $releaseDate, @@ -358,8 +369,8 @@ class IGDBService 'url' => $gameUrl, 'backdropurl' => $backdropUrl, 'trailer' => $trailerUrl, - 'publisher' => !empty($publishers) ? implode(', ', array_slice($publishers, 0, 3)) : 'Unknown', - 'developer' => !empty($developers) ? implode(', ', array_slice($developers, 0, 3)) : '', + 'publisher' => ! empty($publishers) ? implode(', ', array_slice($publishers, 0, 3)) : 'Unknown', + 'developer' => ! empty($developers) ? implode(', ', array_slice($developers, 0, 3)) : '', 'genres' => $genres, ]; } @@ -370,7 +381,7 @@ class IGDBService protected function extractGenres(Game $game): array { $genres = []; - if (!empty($game->genres)) { + if (! empty($game->genres)) { $gameGenres = $game->genres; if ($gameGenres instanceof \Illuminate\Support\Collection) { $gameGenres = $gameGenres->toArray(); @@ -381,7 +392,7 @@ class IGDBService } // Fall back to themes if no genres - if (empty($genres) && !empty($game->themes)) { + if (empty($genres) && ! empty($game->themes)) { $gameThemes = $game->themes; if ($gameThemes instanceof \Illuminate\Support\Collection) { $gameThemes = $gameThemes->toArray(); @@ -412,16 +423,17 @@ class IGDBService ]; } - if (!empty($imageData['image_id'])) { - return 'https://images.igdb.com/igdb/image/upload/t_' . $size . '/' . $imageData['image_id'] . '.jpg'; + if (! empty($imageData['image_id'])) { + return 'https://images.igdb.com/igdb/image/upload/t_'.$size.'/'.$imageData['image_id'].'.jpg'; } - if (!empty($imageData['url'])) { + if (! empty($imageData['url'])) { $url = $imageData['url']; if (strpos($url, '//') === 0) { - $url = 'https:' . $url; + $url = 'https:'.$url; } - return preg_replace('/t_[a-z0-9_]+/', 't_' . $size, $url); + + return preg_replace('/t_[a-z0-9_]+/', 't_'.$size, $url); } return ''; @@ -432,18 +444,19 @@ class IGDBService */ protected function getBackdropUrl(Game $game): string { - if (!empty($game->artworks)) { + if (! empty($game->artworks)) { $artworks = $game->artworks; $firstArtwork = ($artworks instanceof \Illuminate\Support\Collection) ? $artworks->first() : ($artworks[0] ?? null); $url = $this->getImageUrl($firstArtwork, '1080p'); - if (!empty($url)) { + if (! empty($url)) { return $url; } } - if (!empty($game->screenshots)) { + if (! empty($game->screenshots)) { $screenshots = $game->screenshots; $firstScreenshot = ($screenshots instanceof \Illuminate\Support\Collection) ? $screenshots->first() : ($screenshots[0] ?? null); + return $this->getImageUrl($firstScreenshot, '1080p'); } @@ -455,7 +468,7 @@ class IGDBService */ protected function getTrailerUrl(Game $game): string { - if (!empty($game->videos)) { + if (! empty($game->videos)) { $videos = $game->videos; if ($videos instanceof \Illuminate\Support\Collection) { $videos = $videos->toArray(); @@ -463,7 +476,7 @@ class IGDBService foreach ($videos as $video) { $videoId = is_array($video) ? ($video['video_id'] ?? null) : ($video->video_id ?? null); if ($videoId) { - return 'https://www.youtube.com/watch?v=' . $videoId; + return 'https://www.youtube.com/watch?v='.$videoId; } } } @@ -527,7 +540,7 @@ class IGDBService */ protected function getReleaseDate(Game $game): string { - if (!empty($game->release_dates)) { + if (! empty($game->release_dates)) { foreach ($game->release_dates as $release) { if (isset($release['platform']) && $release['platform'] === 6 && isset($release['date'])) { return Carbon::createFromTimestamp($release['date'])->format('Y-m-d'); @@ -561,20 +574,20 @@ class IGDBService } $additionalInfo = []; - if (!empty($developers)) { - $additionalInfo[] = 'Developer: ' . implode(', ', array_slice($developers, 0, 3)); + if (! empty($developers)) { + $additionalInfo[] = 'Developer: '.implode(', ', array_slice($developers, 0, 3)); } - if (!empty($game->game_modes)) { + if (! empty($game->game_modes)) { $gameModes = $game->game_modes; if ($gameModes instanceof \Illuminate\Support\Collection) { - $modes = $gameModes->map(fn($m) => is_array($m) ? ($m['name'] ?? '') : ($m->name ?? ''))->toArray(); + $modes = $gameModes->map(fn ($m) => is_array($m) ? ($m['name'] ?? '') : ($m->name ?? ''))->toArray(); } else { - $modes = array_map(fn($m) => $m['name'] ?? '', $gameModes); + $modes = array_map(fn ($m) => $m['name'] ?? '', $gameModes); } - $additionalInfo[] = 'Modes: ' . implode(', ', array_filter($modes)); + $additionalInfo[] = 'Modes: '.implode(', ', array_filter($modes)); } - if (!empty($additionalInfo) && !empty($review)) { - $review .= "\n\n" . implode("\n", $additionalInfo); + if (! empty($additionalInfo) && ! empty($review)) { + $review .= "\n\n".implode("\n", $additionalInfo); } return $review; @@ -653,7 +666,7 @@ class IGDBService break; } } - if (empty($genreName) && !empty($tmpGenre[0])) { + if (empty($genreName) && ! empty($tmpGenre[0])) { $genreName = trim($tmpGenre[0]); } } else { diff --git a/app/Services/IRCClient.php b/app/Services/IRCClient.php index 6417d877a..183445c8e 100644 --- a/app/Services/IRCClient.php +++ b/app/Services/IRCClient.php @@ -2,7 +2,6 @@ namespace App\Services; - /** * Basic IRC client for fetching IRCScraper. * @@ -229,11 +228,10 @@ class IRCClient /** * Log in to a IRC server. * - * @param string $nickName The nick name - visible in the channel. - * @param string $userName The user name - visible in the host name. - * @param string $realName The real name - visible in the WhoIs. - * @param string|null $password The password - some servers require a password. - * @return bool + * @param string $nickName The nick name - visible in the channel. + * @param string $userName The user name - visible in the host name. + * @param string $realName The real name - visible in the WhoIs. + * @param string|null $password The password - some servers require a password. */ public function login(string $nickName, string $userName, string $realName, ?string $password = null): bool { @@ -623,4 +621,3 @@ class IRCClient return is_string($result) ? $result : $text; } } - diff --git a/app/Services/IRCScraper.php b/app/Services/IRCScraper.php index 5e6ab2845..9851dc32d 100644 --- a/app/Services/IRCScraper.php +++ b/app/Services/IRCScraper.php @@ -50,7 +50,6 @@ class IRCScraper extends IRCClient */ protected string|false $_titleIgnoreRegex = false; - /** * Construct. * @@ -104,7 +103,6 @@ class IRCScraper extends IRCClient $this->_titleIgnoreRegex = (string) config('irc_settings.scrape_irc_title_ignore'); } - $this->_groupList = []; $this->_silent = $silent; $this->_debug = $debug; diff --git a/app/Services/ItunesService.php b/app/Services/ItunesService.php index c9091ddfc..739ce10dd 100644 --- a/app/Services/ItunesService.php +++ b/app/Services/ItunesService.php @@ -17,6 +17,7 @@ use Illuminate\Support\Facades\Log; class ItunesService { protected const API_URL = 'https://itunes.apple.com/search'; + protected const LOOKUP_URL = 'https://itunes.apple.com/lookup'; protected Client $client; @@ -347,4 +348,3 @@ class ItunesService } } } - diff --git a/app/Services/MovieBrowseService.php b/app/Services/MovieBrowseService.php index 66243766f..4f64ed756 100644 --- a/app/Services/MovieBrowseService.php +++ b/app/Services/MovieBrowseService.php @@ -181,4 +181,3 @@ class MovieBrowseService ]; } } - diff --git a/app/Services/MovieService.php b/app/Services/MovieService.php index a76cf5166..d24e9c9a8 100644 --- a/app/Services/MovieService.php +++ b/app/Services/MovieService.php @@ -69,7 +69,7 @@ class MovieService $this->releaseImage = new ReleaseImageService; $this->traktcheck = config('nntmux_api.trakttv_api_key'); if ($this->traktcheck !== null) { - $this->traktTv = new TraktProvider(); + $this->traktTv = new TraktProvider; } $this->client = new Client; $this->fanartapikey = config('nntmux_api.fanarttv_api_key'); @@ -1385,4 +1385,3 @@ class MovieService return false; } } - diff --git a/app/Services/MusicService.php b/app/Services/MusicService.php index 229137504..dcf139c91 100644 --- a/app/Services/MusicService.php +++ b/app/Services/MusicService.php @@ -468,8 +468,6 @@ class MusicService /** * Parse artist and album name from release name. - * - * @return array|false */ public function parseArtist(string $releaseName): array|false { @@ -501,8 +499,6 @@ class MusicService /** * Match browse node ID to genre name. - * - * @return string|false */ public function matchBrowseNode(string $nodeId): string|false { @@ -608,8 +604,6 @@ class MusicService /** * Fetch music properties from iTunes. - * - * @return array|false */ protected function fetchItunesMusicProperties(string $title): array|false { @@ -675,4 +669,3 @@ class MusicService ]; } } - diff --git a/app/Services/NNTP/NNTPService.php b/app/Services/NNTP/NNTPService.php index 6c523410a..328aad19b 100644 --- a/app/Services/NNTP/NNTPService.php +++ b/app/Services/NNTP/NNTPService.php @@ -1390,4 +1390,3 @@ class NNTPService extends \Net_NNTP_Client return $this->_compressionSupported; } } - diff --git a/app/Services/NameFixing/Checkers/AbstractNameChecker.php b/app/Services/NameFixing/Checkers/AbstractNameChecker.php index baab3aade..92cc2e1d9 100644 --- a/app/Services/NameFixing/Checkers/AbstractNameChecker.php +++ b/app/Services/NameFixing/Checkers/AbstractNameChecker.php @@ -15,6 +15,7 @@ use App\Services\NameFixing\DTO\NameFixResult; abstract class AbstractNameChecker implements NameCheckerInterface { protected int $priority = 50; + protected string $name = 'Abstract'; /** @@ -71,4 +72,3 @@ abstract class AbstractNameChecker implements NameCheckerInterface return str_replace('_', ' ', strtolower($patternName)); } } - diff --git a/app/Services/NameFixing/Checkers/AppNameChecker.php b/app/Services/NameFixing/Checkers/AppNameChecker.php index d50001ac0..d237a7d69 100644 --- a/app/Services/NameFixing/Checkers/AppNameChecker.php +++ b/app/Services/NameFixing/Checkers/AppNameChecker.php @@ -16,6 +16,7 @@ use App\Services\NameFixing\Patterns\AppPatterns; class AppNameChecker extends AbstractNameChecker { protected int $priority = 40; + protected string $name = 'App'; /** @@ -96,4 +97,3 @@ class AppNameChecker extends AbstractNameChecker return null; } } - diff --git a/app/Services/NameFixing/Checkers/GameNameChecker.php b/app/Services/NameFixing/Checkers/GameNameChecker.php index 9907f63ac..365074d70 100644 --- a/app/Services/NameFixing/Checkers/GameNameChecker.php +++ b/app/Services/NameFixing/Checkers/GameNameChecker.php @@ -16,6 +16,7 @@ use App\Services\NameFixing\Patterns\GamePatterns; class GameNameChecker extends AbstractNameChecker { protected int $priority = 30; + protected string $name = 'Game'; /** @@ -92,4 +93,3 @@ class GameNameChecker extends AbstractNameChecker return null; } } - diff --git a/app/Services/NameFixing/Checkers/MovieNameChecker.php b/app/Services/NameFixing/Checkers/MovieNameChecker.php index fe33cbaf9..575d13529 100644 --- a/app/Services/NameFixing/Checkers/MovieNameChecker.php +++ b/app/Services/NameFixing/Checkers/MovieNameChecker.php @@ -16,6 +16,7 @@ use App\Services\NameFixing\Patterns\MoviePatterns; class MovieNameChecker extends AbstractNameChecker { protected int $priority = 20; + protected string $name = 'Movie'; /** @@ -82,6 +83,7 @@ class MovieNameChecker extends AbstractNameChecker foreach ($standardPatterns as $patternName => $pattern) { if (preg_match($pattern, $textstring, $matches)) { $confidence = $patternName === 'GENERIC' ? 0.70 : 0.85; + return NameFixResult::fromMatch( newName: $matches[0], method: $this->formatMethod($patternName), @@ -94,4 +96,3 @@ class MovieNameChecker extends AbstractNameChecker return null; } } - diff --git a/app/Services/NameFixing/Checkers/TvNameChecker.php b/app/Services/NameFixing/Checkers/TvNameChecker.php index c8c7f1386..d44a77faf 100644 --- a/app/Services/NameFixing/Checkers/TvNameChecker.php +++ b/app/Services/NameFixing/Checkers/TvNameChecker.php @@ -16,6 +16,7 @@ use App\Services\NameFixing\Patterns\TvPatterns; class TvNameChecker extends AbstractNameChecker { protected int $priority = 10; + protected string $name = 'TV'; /** @@ -102,4 +103,3 @@ class TvNameChecker extends AbstractNameChecker return null; } } - diff --git a/app/Services/NameFixing/Contracts/NameCheckerInterface.php b/app/Services/NameFixing/Contracts/NameCheckerInterface.php index d12c8861e..543dc2a47 100644 --- a/app/Services/NameFixing/Contracts/NameCheckerInterface.php +++ b/app/Services/NameFixing/Contracts/NameCheckerInterface.php @@ -17,8 +17,8 @@ interface NameCheckerInterface /** * Check a release for name patterns. * - * @param object $release The release object to check - * @param string $textstring The text content to analyze + * @param object $release The release object to check + * @param string $textstring The text content to analyze * @return NameFixResult|null Returns result if a match is found, null otherwise */ public function check(object $release, string $textstring): ?NameFixResult; @@ -33,4 +33,3 @@ interface NameCheckerInterface */ public function getName(): string; } - diff --git a/app/Services/NameFixing/Contracts/NameSourceFixerInterface.php b/app/Services/NameFixing/Contracts/NameSourceFixerInterface.php index 28968799c..e95117331 100644 --- a/app/Services/NameFixing/Contracts/NameSourceFixerInterface.php +++ b/app/Services/NameFixing/Contracts/NameSourceFixerInterface.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace App\Services\NameFixing\Contracts; -use Illuminate\Database\Eloquent\Collection; - /** * Interface for source-specific name fixers. * @@ -27,8 +25,8 @@ interface NameSourceFixerInterface /** * Build the query for fetching releases to process. * - * @param int $cats Category filter (2=misc/hashed, 3=predb) - * @param bool $preId Whether processing for PreDB + * @param int $cats Category filter (2=misc/hashed, 3=predb) + * @param bool $preId Whether processing for PreDB * @return string SQL query string */ public function buildQuery(int $cats, bool &$preId): string; @@ -36,11 +34,11 @@ interface NameSourceFixerInterface /** * Process a single release. * - * @param object $release The release to process - * @param bool $echo Whether to actually update the database - * @param bool $nameStatus Whether to update status columns - * @param bool $show Whether to show output - * @param bool $preId Whether processing for PreDB + * @param object $release The release to process + * @param bool $echo Whether to actually update the database + * @param bool $nameStatus Whether to update status columns + * @param bool $show Whether to show output + * @param bool $preId Whether processing for PreDB * @return bool True if a name was found */ public function processRelease(object $release, bool $echo, bool $nameStatus, bool $show, bool $preId): bool; @@ -55,4 +53,3 @@ interface NameSourceFixerInterface */ public function getProcessingDoneValue(): int; } - diff --git a/app/Services/NameFixing/DTO/NameFixResult.php b/app/Services/NameFixing/DTO/NameFixResult.php index cb3408add..b5e5728b8 100644 --- a/app/Services/NameFixing/DTO/NameFixResult.php +++ b/app/Services/NameFixing/DTO/NameFixResult.php @@ -75,4 +75,3 @@ final class NameFixResult return "{$this->checkerName}: {$this->method}"; } } - diff --git a/app/Services/NameFixing/Extractors/FileNameExtractor.php b/app/Services/NameFixing/Extractors/FileNameExtractor.php index a762faf8d..0336ce663 100644 --- a/app/Services/NameFixing/Extractors/FileNameExtractor.php +++ b/app/Services/NameFixing/Extractors/FileNameExtractor.php @@ -6,8 +6,6 @@ namespace App\Services\NameFixing\Extractors; use App\Services\NameFixing\DTO\NameFixResult; use App\Services\NameFixing\FileNameCleaner; -use App\Services\NameFixing\Patterns\TvPatterns; -use App\Services\NameFixing\Patterns\MoviePatterns; /** * Extracts release names from file names and paths. @@ -26,7 +24,7 @@ class FileNameExtractor public function __construct(?FileNameCleaner $cleaner = null) { - $this->cleaner = $cleaner ?? new FileNameCleaner(); + $this->cleaner = $cleaner ?? new FileNameCleaner; } /** @@ -100,18 +98,21 @@ class FileNameExtractor // Movie with year in parentheses - AVI format if (preg_match('/.+\\\\(.+\((19|20)\d\d\)\.avi)$/i', $filename, $result)) { $newName = str_replace('.avi', ' DVDRip XVID NoGroup', $result[1]); + return NameFixResult::fromMatch($newName, 'Movie (year) avi', 'File'); } // Movie with year in parentheses - ISO format if (preg_match('/.+\\\\(.+\((19|20)\d\d\)\.iso)$/i', $filename, $result)) { $newName = str_replace('.iso', ' DVD NoGroup', $result[1]); + return NameFixResult::fromMatch($newName, 'Movie (year) iso', 'File'); } // Movie with year in parentheses - MKV format if (preg_match('/.+\\\\(.+\((19|20)\d\d\)\.(mkv|mp4|m4v))$/i', $filename, $result)) { $newName = preg_replace('/\.(mkv|mp4|m4v)$/i', ' BDRip x264 NoGroup', $result[1]); + return NameFixResult::fromMatch($newName, 'Movie (year) mkv/mp4', 'File'); } @@ -132,7 +133,7 @@ class FileNameExtractor // VIDEOOT releases if (preg_match('/^VIDEOOT-[A-Z0-9]+\\\\([\w!.,& ()\[\]\'\`-]{8,}?\b.?)([\-_](proof|sample|thumbs?))*(\.part\d*(\.rar)?|\.rar|\.7z)?(\d{1,3}\.rev|\.vol.+?|\.mp4)/', $filename, $result)) { - return NameFixResult::fromMatch($result[1] . ' XXX DVDRIP XviD-VIDEOOT', 'XXX XviD VIDEOOT', 'File'); + return NameFixResult::fromMatch($result[1].' XXX DVDRIP XviD-VIDEOOT', 'XXX XviD VIDEOOT', 'File'); } // XXX SDPORN @@ -143,6 +144,7 @@ class FileNameExtractor // R&C releases if (preg_match('/\w[\-\w.\',;& ]+1080i[._ -]DD5[._ -]1[._ -]MPEG2-R&C(?=\.ts)$/i', $filename, $result)) { $newResult = str_replace('MPEG2', 'MPEG2.HDTV', $result[0]); + return NameFixResult::fromMatch($newResult, 'R&C', 'File'); } @@ -156,6 +158,7 @@ class FileNameExtractor $newResult = str_replace('720p', '720p.HDTV.X264', $result[0]); $newResult = str_replace('1080p', '1080p.Bluray.X264', $newResult); $newResult = str_replace('xvid', 'XVID.DVDrip', $newResult); + return NameFixResult::fromMatch($newResult, 'tvp', 'File'); } @@ -181,12 +184,12 @@ class FileNameExtractor // Nintendo 3DS if (preg_match('/3DS_\d{4}.+\d{4} - (.+?)\.3ds/i', $filename, $result)) { - return NameFixResult::fromMatch('3DS ' . $result[1], '.3ds Nintendo 3DS', 'File'); + return NameFixResult::fromMatch('3DS '.$result[1], '.3ds Nintendo 3DS', 'File'); } // Nintendo Switch if (preg_match('/^(.+?)\[[\w]+\]\.(?:nsp|xci|nsz)$/i', $filename, $result)) { - return NameFixResult::fromMatch(trim($result[1]) . ' Switch', 'Nintendo Switch', 'File'); + return NameFixResult::fromMatch(trim($result[1]).' Switch', 'Nintendo Switch', 'File'); } // PlayStation/Xbox game releases @@ -196,7 +199,8 @@ class FileNameExtractor // EBooks if (preg_match('/\w.+?\.(epub|mobi|azw3?|opf|fb2|prc|djvu|cb[rz])/i', $filename, $result)) { - $newResult = str_replace('.' . $result[1], ' (' . $result[1] . ')', $result[0]); + $newResult = str_replace('.'.$result[1], ' ('.$result[1].')', $result[0]); + return NameFixResult::fromMatch($newResult, 'EBook', 'File'); } @@ -218,4 +222,3 @@ class FileNameExtractor return null; } } - diff --git a/app/Services/NameFixing/Extractors/NfoNameExtractor.php b/app/Services/NameFixing/Extractors/NfoNameExtractor.php index 68b69f116..3493ceb84 100644 --- a/app/Services/NameFixing/Extractors/NfoNameExtractor.php +++ b/app/Services/NameFixing/Extractors/NfoNameExtractor.php @@ -5,8 +5,6 @@ declare(strict_types=1); namespace App\Services\NameFixing\Extractors; use App\Services\NameFixing\DTO\NameFixResult; -use App\Services\NameFixing\Patterns\TvPatterns; -use App\Services\NameFixing\Patterns\MoviePatterns; /** * Extracts release names from NFO content. @@ -70,6 +68,7 @@ class NfoNameExtractor // FM Radio pattern if (preg_match('/(?:\s{2,})(.+?-FM-\d{2}-\d{2})/i', $nfoContent, $result)) { $newName = str_replace('-FM-', '-FM-Radio-MP3-', $result[1]); + return NameFixResult::fromMatch($newName, 'Music FM RADIO', 'NFO'); } @@ -91,7 +90,7 @@ class NfoNameExtractor } // Look for Title (Year) pattern - if (!preg_match('/(\w[\-\w`~!@#$%^&*()_+={}|"<>?\[\]\\;\',.\/ ]+\s?\((19|20)\d\d\))/i', $nfoContent, $result)) { + if (! preg_match('/(\w[\-\w`~!@#$%^&*()_+={}|"<>?\[\]\\;\',.\/ ]+\s?\((19|20)\d\d\))/i', $nfoContent, $result)) { return null; } @@ -125,19 +124,21 @@ class NfoNameExtractor $result = []; // Check for known game groups - if (!preg_match('/ALiAS|BAT-TEAM|FAiRLiGHT|Game Type|Glamoury|HI2U|iTWINS|JAGUAR|(LARGE|MEDIUM)ISO|MAZE|nERv|PROPHET|PROFiT|PROCYON|RELOADED|REVOLVER|ROGUE|ViTALiTY/i', $nfoContent)) { + if (! preg_match('/ALiAS|BAT-TEAM|FAiRLiGHT|Game Type|Glamoury|HI2U|iTWINS|JAGUAR|(LARGE|MEDIUM)ISO|MAZE|nERv|PROPHET|PROFiT|PROCYON|RELOADED|REVOLVER|ROGUE|ViTALiTY/i', $nfoContent)) { return null; } // (c) pattern if (preg_match('/\w[\w.+&*\/\()\',;: -]+\(c\)[\-\w.\',;& ]+\w/i', $nfoContent, $result)) { $releaseName = str_replace(['(c)', '(C)'], '(GAMES) (c)', $result[0]); + return NameFixResult::fromMatch($releaseName, 'PC Games (c)', 'NFO'); } // *ISO* pattern if (preg_match('/\w[\w.+&*\/()\',;: -]+\*ISO\*/i', $nfoContent, $result)) { $releaseName = str_replace('*ISO*', '*ISO* (PC GAMES)', $result[0]); + return NameFixResult::fromMatch($releaseName, 'PC Games *ISO*', 'NFO'); } @@ -149,7 +150,7 @@ class NfoNameExtractor */ public function checkForIguana(string $nfoContent): ?NameFixResult { - if (!preg_match('/Supplier.+?IGUANA/i', $nfoContent)) { + if (! preg_match('/Supplier.+?IGUANA/i', $nfoContent)) { return null; } @@ -161,15 +162,15 @@ class NfoNameExtractor } if (preg_match('/\s\[\*\] (English|Dutch|French|German|Spanish)\b/i', $nfoContent, $result)) { - $releaseName .= '.' . $result[1]; + $releaseName .= '.'.$result[1]; } if (preg_match('/\s\[\*\] (DT?S [2567][._ -][0-2]( MONO)?)\b/i', $nfoContent, $result)) { - $releaseName .= '.' . $result[2]; + $releaseName .= '.'.$result[2]; } if (preg_match('/Format.+(DVD([59R])?|[HX][._ -]?264)\b/i', $nfoContent, $result)) { - $releaseName .= '.' . $result[1]; + $releaseName .= '.'.$result[1]; } if (preg_match('/\[(640x.+|1280x.+|1920x.+)\] Resolution\b/i', $nfoContent, $result)) { @@ -179,11 +180,11 @@ class NfoNameExtractor $result[1] === '1920x.+' => '1080p', default => $result[1], }; - $releaseName .= '.' . $res; + $releaseName .= '.'.$res; } if ($releaseName !== '') { - return NameFixResult::fromMatch($releaseName . '.IGUANA', 'IGUANA', 'NFO'); + return NameFixResult::fromMatch($releaseName.'.IGUANA', 'IGUANA', 'NFO'); } return null; @@ -203,8 +204,9 @@ class NfoNameExtractor 'ES' => 'SPANISH', default => $result['lang'], }; - $releaseName .= '.' . $lang; + $releaseName .= '.'.$lang; } + return $releaseName; } @@ -223,7 +225,7 @@ class NfoNameExtractor '2160' => '2160p', default => $result['res'], }; - $releaseName .= '.' . $res; + $releaseName .= '.'.$res; } elseif (preg_match('/(largeur|width).*?(?P(\(?640|688|704|720|1280( \@)?|1 ?920))/i', $nfoContent, $result)) { $res = match ($result['res']) { '640', '(640', '688', '704', '720' => '480p', @@ -232,7 +234,7 @@ class NfoNameExtractor '2160' => '2160p', default => $result['res'], }; - $releaseName .= '.' . $res; + $releaseName .= '.'.$res; } return $releaseName; @@ -255,7 +257,7 @@ class NfoNameExtractor 'Ripped ' => 'DVDRIP', default => $result['source'], }; - $releaseName .= '.' . $source; + $releaseName .= '.'.$source; } elseif (preg_match('/(codec( (name|code))?|(original )?format|res(olution)|video( (codec|format|res))?|tv system|type|writing library).*?\b(?P