Passed
Push — master ( 0f8cd6...fe0902 )
by Darko
11:16
created

PcCategorizer::checkPCGame()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 30
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 13
c 2
b 0
f 0
dl 0
loc 30
rs 8.8333
cc 7
nc 5
nop 2
1
<?php
2
3
namespace App\Services\Categorization\Categorizers;
4
5
use App\Models\Category;
6
use App\Services\Categorization\CategorizationResult;
7
use App\Services\Categorization\ReleaseContext;
8
9
/**
10
 * Categorizer for PC content (Games, Software, ISO, 0day, Mac, Phone apps).
11
 */
12
class PcCategorizer extends AbstractCategorizer
13
{
14
    protected int $priority = 30;
15
16
    // Common PC game release groups (matched at end of release name only)
17
    protected const PC_GROUPS = '0x0007|ALiAS|ANOMALY|BACKLASH|BAT|BLASTCiTY|CHRONOS|CODEX|CONSPIRACY|CPY|CROSSFiRE|DARKS(?:iDERS|IDERS)|DELiGHT|DEViANCE|DINOByTES|DOGE|DODI|DVN|ELAMIGOS|EMPRESS|ENiGMA|FANiSO|FAS(?:DOX|iSO)|FCKDRM|FITGIRL|FLT|FLTDOX|GGS|GOG(?:-?GAMES)?|GOLDBERG|HATRED|HI2U|HOODLUM|I_KnoW|iNLAWS|iNTERNAL|JAGUAR|KACZKRULL|KAOS|KaOsKrew|LAZARUS|LiGHTFORCE|MEPHISTO|OUTLAWS|PARADOX|PHOENIX|PLAZA|POSTMORTEM|PROPHET|PROVOKED|RADIUM|RAZOR(?:1911|DOX)|RELOADED|REVOLUTiON|RUNE|SAFARi|SCRUBS|SiLENTGATE|SiMPLEX|SKIDROW|SPLiNTERCELL|STEAMPUNKS|SUXXORS|TENOKE|TiNYiSO|UBERMENCH|UNLEASHED|VENGEANCE|ViTALiTY|VENOM|VACE|ZEKE|P2P';
18
19
    // PC-only keywords (can appear anywhere)
20
    protected const PC_KEYWORDS = 'PC[ _.-]?GAMES?|\[(?:PC)\]|\(PC\)|Steam(?:[ ._-]?Rip|\b)|Retail\s*PC|DRM-?Free|Win(All|32|64)\b|Repack';
21
22
    public function getName(): string
23
    {
24
        return 'PC';
25
    }
26
27
    public function shouldSkip(ReleaseContext $context): bool
28
    {
29
        if ($context->hasAdultMarkers()) return true;
30
        // Skip TV shows (season patterns)
31
        if (preg_match('/[._ -]S\d{1,3}[._ -]?(E\d|Complete|Full|1080|720|480|2160|WEB|HDTV|BluRay)/i', $context->releaseName)) return true;
32
        return false;
33
    }
34
35
    public function categorize(ReleaseContext $context): CategorizationResult
36
    {
37
        $name = $context->releaseName;
38
39
        // Try each PC category
40
        if ($result = $this->checkPhone($name)) {
41
            return $result;
42
        }
43
44
        if ($result = $this->checkMac($name)) {
45
            return $result;
46
        }
47
48
        if ($result = $this->checkPCGame($name, $context->poster)) {
49
            return $result;
50
        }
51
52
        if ($result = $this->checkISO($name)) {
53
            return $result;
54
        }
55
56
        if ($result = $this->check0day($name)) {
57
            return $result;
58
        }
59
60
        return $this->noMatch();
61
    }
62
63
    protected function checkPhone(string $name): ?CategorizationResult
64
    {
65
        // iOS
66
        if (preg_match('/[^a-z0-9](IPHONE|ITOUCH|IPAD)[._ -]/i', $name)) {
67
            return $this->matched(Category::PC_PHONE_IOS, 0.9, 'ios');
68
        }
69
70
        // Android
71
        if (preg_match('/[._ -]?(ANDROID)[._ -]/i', $name)) {
72
            return $this->matched(Category::PC_PHONE_ANDROID, 0.9, 'android');
73
        }
74
75
        // Other mobile platforms
76
        if (preg_match('/[^a-z0-9](symbian|xscale|wm5|wm6)[._ -]/i', $name)) {
77
            return $this->matched(Category::PC_PHONE_OTHER, 0.85, 'phone_other');
78
        }
79
80
        return null;
81
    }
82
83
    protected function checkMac(string $name): ?CategorizationResult
84
    {
85
        if (preg_match('/(\b|[._ -])mac([\.\s])?osx(\b|[\-_. ])/i', $name)) {
86
            return $this->matched(Category::PC_MAC, 0.9, 'mac');
87
        }
88
89
        if (preg_match('/\b(Mac\s?OS\s?X|macOS)\b/i', $name)) {
90
            return $this->matched(Category::PC_MAC, 0.9, 'macos');
91
        }
92
93
        return null;
94
    }
95
96
    protected function checkPCGame(string $name, string $poster): ?CategorizationResult
97
    {
98
        // Exclude console releases
99
        $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';
100
        if (preg_match($consoleOrMac, $name) || preg_match('/\b(Mac\s?OS\s?X|macOS)\b/i', $name)) {
101
            return null;
102
        }
103
104
        // Exclude TV shows
105
        $tvPatterns = '/\b(S\d{1,4}[._ -]?E\d{1,4}|S\d{1,4}[._ -]?D\d{1,4}|\d{1,2}x\d{2,3}|Season[._ -]?\d{1,3}|Episode[._ -]?\d{1,4}|HDTV|PDTV|DSR|WEB[._ -]?DL|WEB[._ -]?RIP|TVRip)\b/i';
106
        if (preg_match($tvPatterns, $name)) {
107
            return null;
108
        }
109
110
        // Check for PC game patterns
111
        // PC_GROUPS must be at the END of the release name (scene naming: Game.Name-GROUP)
112
        // PC_KEYWORDS can appear anywhere
113
        $groupPattern = '/[-.](' . self::PC_GROUPS . ')$/i';
114
        $keywordPattern = '/' . self::PC_KEYWORDS . '/i';
115
116
        if (preg_match($groupPattern, $name) || preg_match($keywordPattern, $name)) {
117
            return $this->matched(Category::PC_GAMES, 0.9, 'pc_game');
118
        }
119
120
        // Check poster
121
        if (preg_match('/<PC@MASTER\.RACE>/i', $poster)) {
122
            return $this->matched(Category::PC_GAMES, 0.85, 'pc_game_poster');
123
        }
124
125
        return null;
126
    }
127
128
    protected function checkISO(string $name): ?CategorizationResult
129
    {
130
        if (preg_match('/[._ -]([a-zA-Z]{2,10})?iso[ _.-]|[\-. ]([a-z]{2,10})?iso$/i', $name)) {
131
            return $this->matched(Category::PC_ISO, 0.85, 'iso');
132
        }
133
134
        // Training/Tutorial ISOs
135
        if (preg_match('/[._ -](DYNAMiCS|INFINITESKILLS|UDEMY|kEISO|PLURALSIGHT|DIGITALTUTORS|TUTSPLUS|OSTraining|PRODEV|CBT\.Nuggets|COMPRISED)/i', $name)) {
136
            return $this->matched(Category::PC_ISO, 0.9, 'training_iso');
137
        }
138
139
        return null;
140
    }
141
142
    protected function check0day(string $name): ?CategorizationResult
143
    {
144
        // Explicit 0day indicators
145
        if (preg_match('/[._ -]exe$|[._ -](utorrent|Virtualbox)[._ -]|\b0DAY\b|incl.+crack| DRM$|>DRM</i', $name)) {
146
            return $this->matched(Category::PC_0DAY, 0.9, '0day_explicit');
147
        }
148
149
        // System/architecture indicators
150
        if (preg_match('/[._ -]((32|64)bit|converter|i\d86|key(gen|maker)|freebsd|GAMEGUiDE|hpux|irix|linux|multilingual|Patch|Pro v\d{1,3}|portable|regged|software|solaris|template|unix|win2kxp2k3|win64|win(2k|32|64|all|dows|nt(2k)?(xp)?|xp)|win9x(me|nt)?|x(32|64|86))[._ -]/i', $name)) {
151
            return $this->matched(Category::PC_0DAY, 0.85, '0day_system');
152
        }
153
154
        // Software vendors and patterns
155
        if (preg_match('/\b(Adobe|auto(cad|desk)|-BEAN|Cracked|Cucusoft|CYGNUS|Divx[._ -]Plus|\.(deb|exe)|DIGERATI|FOSI|-FONT|Key(filemaker|gen|maker)|Lynda\.com|lz0|MULTiLANGUAGE|Microsoft\s*(Office|Windows|Server)|MultiOS|-(iNViSiBLE|SPYRAL|SUNiSO|UNION|TE)|v\d{1,3}.*?Pro|[._ -]v\d{1,3}[._ -]|\(x(64|86)\)|Xilisoft)\b/i', $name)) {
156
            return $this->matched(Category::PC_0DAY, 0.85, '0day_software');
157
        }
158
159
        return null;
160
    }
161
}
162
163