mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Improve pc game categorization
This commit is contained in:
@@ -695,7 +695,22 @@ class Categorize
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match('/[^a-z0-9](0x0007|ALiAS|ElAmigos|BACKLASH|BAT|CLONECD|CPY|FAS(DOX|iSO)|FLT([._ -]|COGENT)|FLT(DOX)?|PC GAMES?|\(?(Game([sz])|GAME([SZ]))\)? ?(\(([Cc])\))|GENESIS|-GOG|-HATRED|HI2U|INLAWS|JAGUAR|MAZE|MONEY|OUTLAWS|PPTCLASSiCS|PC Game|PROPHET|RAiN|Razor1911|RELOADED|DEViANCE|PLAZA|RiTUELYPOGEiOS|[rR][iI][pP]-[uU][nN][lL][eE][aA][sS][hH][eE][dD]|Steam(\b)?Rip|SKIDROW|TiNYiSO|CODEX|SiMPLEX)[^a-z0-9]?/', $this->releaseName)) {
|
||||
// Guard: avoid misclassifying console or Mac releases as PC games.
|
||||
$consoleOrMac = '/\b(PS5|PS4|PS3|PlayStation|PS(Vita|V)\b|Xbox\s?(Series|One|360)|XBOX(ONE|360|SERIES|SX|SS)?|XSX|XSS|XBSX|NSW|Switch|WiiU|Wii|3DS|NDS|PSP|PSV(ita)?|GameCube|NGC|CUSA\d{5}|XCI|NSP|PKG)\b/i';
|
||||
if (preg_match($consoleOrMac, $this->releaseName) || preg_match('/\b(Mac\s?OS\s?X|macOS)\b/i', $this->releaseName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Expanded PC game markers: common scene/p2p groups and tags seen in 2020-2025.
|
||||
$pcGroups = '(?:0x0007|ALiAS|ANOMALY|BACKLASH|BAT|CODEX|CPY|DARKS(?:iDERS|IDERS)|DEViANCE|DOGE|DODI|ELAMIGOS|EMPRESS|FITGIRL|FAS(?:DOX|iSO)|FLT(?:[._ -]|COGENT|DOX)?|GOG(?:-GAMES)?|GOLDBERG|HI2U|HOODLUM|INLAWS|JAGUAR|MAZE|MONEY|OUTLAWS|PLAZA|PROPHET|RAZOR1911|RAiN|RELOADED|RUNE|SiMPLEX|SKIDROW|TENOKE|TiNYiSO|UNLEASHED|P2P)';
|
||||
|
||||
// Additional PC-only keywords.
|
||||
$pcKeywords = '(?:PC[ _.-]?GAMES?|\[(?:PC)\]|\(PC\)|Steam(?:[ ._-]?Rip|\b)|GOG(?:\b|[ ._-])|Retail\s*PC|DRM-?Free|Win(All|32|64)\b|Windows(?:\s?10|\s?11)?\b|Repack)';
|
||||
|
||||
// Build a combined pattern ensuring start/end or non-word boundaries to avoid partial matches.
|
||||
$pattern = '/(?:(?:^|[\s\._-])(?:'.$pcGroups.')(?:$|[\s\._-])|'.$pcKeywords.')/i';
|
||||
|
||||
if (preg_match($pattern, $this->releaseName)) {
|
||||
$this->tmpCat = Category::PC_GAMES;
|
||||
|
||||
return true;
|
||||
@@ -1435,8 +1450,8 @@ class Categorize
|
||||
// First check if the release name suggests Nintendo Wii content
|
||||
if (preg_match('/(?:^|[^a-zA-Z0-9])(?:Wii|Nintendo\s+Wii)|\b(?:Wii)\b|[\._-]Wii[\._-]|Nintendo[\._-]Wii/i', $this->releaseName)) {
|
||||
// Verify with region codes, game-specific markers, or known Wii release groups
|
||||
if (preg_match('/\b(?:ANTiDOTE|APATHY|ALMoST|AMBITION|Allstars|BAHAMUT|BiOSHOCK|Caravan|CLiiCHE|DMZ|DNi|DRYB|EUR?|GAME|GCN|GCP|HaZMaT|iCON|JAP|JPN|KOR|LaKiTu|LoCAL|LOADER|MARVEL|MULTi|NAGGERS|OneUp|NTSC|PAL|PLAYME|PONS|PROMiNENT|ProCiSiON|PROPER|QwiiF|RANT|REV0|Scrubbed|SUNSHiNE|SUSHi|TMD|USA?|VORTEX|WBFS|WiiERD|ZARD|ZER0)\b/i', $this->releaseName) ||
|
||||
preg_match('/\b(?:ISO|WBFS|CSO|NKit|RVZ|NAND|WAD|IOS\d+|cIOS|MODCHIP|Homebrew|DOLPHIN|vWii)\b/i', $this->releaseName) ||
|
||||
if (preg_match('/\b(?:ANTiDOTE|APATHY|ALMoST|AMBITION|Allstars|BAHAMUT|BiOSHOCK|Caravan|CLiiCHE|DMZ|DNi|DRYB|EUR?|GAME|GC|GCP|HaZMaT|iCON|JAP|JPN|KOR|LaKiTu|LoCAL|LOADER|MARVEL|MULTi|NAGGERS|OneUp|NTSC|PAL|PLAYME|PONS|PROMiNENT|ProCiSiON|PROPER|QwiiF|RANT|REV0|Scrubbed|SUNSHiNE|SUSHi|TMD|USA?|VORTEX|WBFS|WIIERD|ZARD|ZER0)\b/i', $this->releaseName) ||
|
||||
preg_match('/\b(?:ISO|WBFS|CSO|NKit|RVZ|NAND|WAD|IOS\d+|cIOS|MODCHIP|Homebrew|DOLPHIN|vWii|Virtual[._ -]Console)\b/i', $this->releaseName) ||
|
||||
preg_match('/\[Wii\]|\(Wii\)|Nintendo\.Wii|Wii\.Game|RVZ-[A-Z0-9]+|WII-\w+|READNFO|WiiGamerZ|Wii-Backup/i', $this->releaseName)) {
|
||||
$this->tmpCat = Category::GAME_WII;
|
||||
|
||||
@@ -1589,9 +1604,9 @@ class Categorize
|
||||
|
||||
// First check if the release name suggests retro/other console content
|
||||
if (preg_match('/(?:^|[^a-zA-Z0-9])(?:PS[1X]|PS2|SNES|NES|SEGA(?:\s+(?:Genesis|CD|Saturn|32X|Master\s+System))?|GB[AC]?|GameBoy(?:\s+(?:Advance|Color))?|Game\s*Boy(?:\s+(?:Advance|Color))?|Dreamcast|Saturn|Atari(?:\s+(?:Jaguar|2600|5200|7800|Lynx))?|3DO|Neo\s*Geo|N64|Nintendo\s*64|PCEngine|TurboGrafx|Intellivision|Colecovision)|\b(?:PS[1X]|PS2|SNES|NES|MAME|N64)\b|[\._-](?:PS[1X]|PS2|SNES|NES|N64)[\._-]/i', $this->releaseName)) {
|
||||
// Verify with region codes, game-specific markers, or known retro release patterns
|
||||
if (preg_match('/\b(?:EUR?|FR|GAME|HOL|ISO|JP|JPN|NL|NTSC|PAL|KS|USA?|ROMS?(et)?|ROM\s+Collection|RIP|Full\s+Set|Redump|No\s+Intro|TOSEC|GoodSet|EverDrive|Collection|Classics|Anthology|Trilogy|Compilation|Complete|Rev\s+[A-Z])\b/i', $this->releaseName) ||
|
||||
preg_match('/\b(?:BIOS|Beetle|RetroArch|Emulator|MultiDisc|Arcade|OpenEmu|RetroPie|Recalbox|Lakka|Batocera|Hyperspin|LaunchBox|MAME|Collection|Goodset|Trurip|Verified|ReDump|PROPER|iNTERNAL|Venom|Caravan|WRG)\b/i', $this->releaseName) ||
|
||||
// Verify with region codes, version indicators, or other game-specific markers
|
||||
if (preg_match('/\b(?:EUR?|FR|GAME|HOL|ISO|JP|JPN|NL|NTSC|PAL|KS|USA?)[\)\_]/i', $this->releaseName) ||
|
||||
preg_match('/\b(?:ROMs?(et)?|ROM\s+Collection|RIP|Full\s+Set|Redump|No\s+Intro|TOSEC|GoodSet|EverDrive|Collection|Classics|Anthology|Trilogy|Compilation|Complete|Rev\s+[A-Z])\b/i', $this->releaseName) ||
|
||||
preg_match('/\(([CP]|\d{2,})\)|\.(bin|chd|cue|gcm|gdi|iso|img|mdf|nrg|z64|v64|n64|md|smc|smd|fig|gb|gbc|gba|nes|sfc|gen)$/i', $this->releaseName)) {
|
||||
$this->tmpCat = Category::GAME_OTHER;
|
||||
|
||||
@@ -1740,7 +1755,7 @@ class Categorize
|
||||
return true;
|
||||
}
|
||||
|
||||
// Legacy detection for backward compatibility
|
||||
// Legacy pattern for backward compatibility
|
||||
if (preg_match('/(720P|x264)\-(19|20)\d\d\-[a-z0-9]{1,12}/i', $this->releaseName) ||
|
||||
preg_match('/[a-z0-9]{1,12}-(19|20)\d\d-(720P|x264)/i', $this->releaseName)) {
|
||||
if ($this->isMusicForeign()) {
|
||||
@@ -2038,7 +2053,7 @@ class Categorize
|
||||
$fullLanguages = 'arabic|brazilian|bulgarian|cantonese|chinese|croatian|czech|danish|deutsch|dutch|estonian|'.
|
||||
'finnish|flemish|french|german|greek|hebrew|hungarian|icelandic|italian|japanese|korean|'.
|
||||
'latin|mandarin|nordic|norwegian|polish|portuguese|romanian|russian|serbian|slovenian|'.
|
||||
'spanish|swedish|thai|turkish|ukrainian|vietnamese';
|
||||
'spanish|spanisch|swedish|thai|turkish|ukrainian|vietnamese';
|
||||
|
||||
// Common language codes and abbreviations
|
||||
$langCodes = 'ar|bg|cn|cs|cz|da|de|dk|el|es|et|fi|fr|ger|gr|heb|hr|hu|hun|is|it|ita|jp|kr|ko|lt|lv|'.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
use Blacklight\Categorize;
|
||||
|
||||
$c = (new ReflectionClass(Categorize::class))->newInstanceWithoutConstructor();
|
||||
$c->poster = '';
|
||||
|
||||
$samples = [
|
||||
'Starfield-RUNE',
|
||||
'Baldurs.Gate.3.TENOKE',
|
||||
'ELDEN.RING-EMPRESS',
|
||||
'Horizon.Zero.Dawn-CODEX',
|
||||
'Cyberpunk.2077.GOG',
|
||||
'Forza.Horizon.5.ElAmigos',
|
||||
'The.Witcher.3.Wild.Hunt.PLaza',
|
||||
'Resident.Evil.4.Remake-FITGIRL',
|
||||
'Red.Dead.Redemption.2.DODI-Repack',
|
||||
'Some.Game.SKiDROW',
|
||||
'Awesome.Game.SteamRip',
|
||||
'Great.Game.Repack-FitGirl',
|
||||
'Indie.Title.DRM-Free.GOG',
|
||||
'Cool.Game.PC.Game.2024',
|
||||
'Windows.10.Title.Repack',
|
||||
'Title-[PC]-DRMFree',
|
||||
];
|
||||
|
||||
foreach ($samples as $name) {
|
||||
$c->releaseName = $name;
|
||||
$res = $c->isPCGame();
|
||||
echo ($res ? 'MATCH' : 'NO-MATCH')."\t$name\n";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
$rc = new ReflectionClass(\Blacklight\Games::class);
|
||||
$games = $rc->newInstanceWithoutConstructor();
|
||||
|
||||
$inputs = [
|
||||
'[FitGirl] Red.Dead.Redemption.2.v1.0.1436.28.Repack',
|
||||
'Baldurs_Gate_3_v1.0.2_MULTI12-EMPRESS',
|
||||
'Starfield.Update.1.7.29.Patch-FLT',
|
||||
];
|
||||
foreach ($inputs as $in) {
|
||||
$res = $games->parseTitle($in);
|
||||
echo $in, ' => ', json_encode($res, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), "\n";
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Models\Category;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
|
||||
class CategorizePcGameTest extends TestCase
|
||||
{
|
||||
private function makeCategorizeNoCtor(): object
|
||||
{
|
||||
$rc = new ReflectionClass(\Blacklight\Categorize::class);
|
||||
|
||||
return $rc->newInstanceWithoutConstructor();
|
||||
}
|
||||
|
||||
public function test_pc_game_detects_common_scene_groups(): void
|
||||
{
|
||||
$samples = [
|
||||
'Starfield-RUNE',
|
||||
'Baldurs.Gate.3.TENOKE',
|
||||
'ELDEN.RING-EMPRESS',
|
||||
'Horizon.Zero.Dawn-CODEX',
|
||||
'Cyberpunk.2077.GOG',
|
||||
'Forza.Horizon.5.ElAmigos',
|
||||
'The.Witcher.3.Wild.Hunt.PLaza',
|
||||
'Resident.Evil.4.Remake-FITGIRL',
|
||||
'Red.Dead.Redemption.2.DODI-Repack',
|
||||
'Some.Game.SKiDROW',
|
||||
];
|
||||
|
||||
foreach ($samples as $name) {
|
||||
$c = $this->makeCategorizeNoCtor();
|
||||
$c->releaseName = $name;
|
||||
$c->poster = '';
|
||||
$this->assertTrue($c->isPCGame(), "Expected PC game match for: $name");
|
||||
$this->assertSame(Category::PC_GAMES, $this->readTmpCat($c), "Expected PC_GAMES category for: $name");
|
||||
}
|
||||
}
|
||||
|
||||
public function test_pc_game_detects_keywords_and_platform_cues(): void
|
||||
{
|
||||
$samples = [
|
||||
'Awesome.Game.SteamRip',
|
||||
'Great.Game.Repack-FitGirl',
|
||||
'Indie.Title.DRM-Free.GOG',
|
||||
'Cool.Game.PC.Game.2024',
|
||||
'Windows.10.Title.Repack',
|
||||
'Title-[PC]-DRMFree',
|
||||
];
|
||||
|
||||
foreach ($samples as $name) {
|
||||
$c = $this->makeCategorizeNoCtor();
|
||||
$c->releaseName = $name;
|
||||
$c->poster = '';
|
||||
$this->assertTrue($c->isPCGame(), "Expected PC game keyword match for: $name");
|
||||
$this->assertSame(Category::PC_GAMES, $this->readTmpCat($c), "Expected PC_GAMES category for: $name");
|
||||
}
|
||||
}
|
||||
|
||||
public function test_console_and_mac_not_misclassified_as_pc_game(): void
|
||||
{
|
||||
$negatives = [
|
||||
'The.Last.of.Us.Part.I.PS5',
|
||||
'Gran.Turismo.7.PS4.CUSA12345',
|
||||
'Mario.Kart.8.Deluxe.NSW.NSP',
|
||||
'Zelda.Breath.of.the.Wild.Switch.XCI',
|
||||
'Halo.Infinite.Xbox.Series.X',
|
||||
'Gears.5.XBOXONE',
|
||||
'Some.Game.macOS.13',
|
||||
];
|
||||
|
||||
foreach ($negatives as $name) {
|
||||
$c = $this->makeCategorizeNoCtor();
|
||||
$c->releaseName = $name;
|
||||
$c->poster = '';
|
||||
$this->assertFalse($c->isPCGame(), "Did not expect PC game match for console/Mac: $name");
|
||||
}
|
||||
}
|
||||
|
||||
private function readTmpCat(object $instance): int
|
||||
{
|
||||
$rp = (new ReflectionClass($instance))->getProperty('tmpCat');
|
||||
$rp->setAccessible(true);
|
||||
|
||||
return (int) $rp->getValue($instance);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user