Passed
Push — master ( 73c322...402528 )
by Darko
12:47
created

AgeVerificationManager   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 365
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 36
eloc 166
c 1
b 0
f 0
dl 0
loc 365
rs 9.52

14 Methods

Rating   Name   Duplication   Size   Complexity  
A setAgeVerificationCookies() 0 26 4
A makeRequest() 0 41 3
A extractDomain() 0 18 1
A hasCookies() 0 5 2
A getCookieDirectory() 0 3 1
A clearCookies() 0 12 2
A getCookieJar() 0 15 4
A __construct() 0 11 3
A isAgeVerificationPage() 0 16 4
A getCookieStats() 0 30 3
A refreshCookies() 0 20 3
A getCookieFilePath() 0 5 1
A clearAllCookies() 0 14 3
A getStoredDomains() 0 12 2
1
<?php
2
3
namespace App\Services\AdultProcessing;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\Cookie\CookieJar;
7
use GuzzleHttp\Cookie\FileCookieJar;
8
use GuzzleHttp\Cookie\SetCookie;
9
10
/**
11
 * Age Verification Cookie Manager
12
 *
13
 * Automatically handles age verification for adult sites by:
14
 * 1. Detecting age verification pages
15
 * 2. Simulating acceptance (setting appropriate cookies)
16
 * 3. Persisting cookies to disk for reuse
17
 * 4. Loading saved cookies automatically
18
 */
19
class AgeVerificationManager
20
{
21
    /**
22
     * Cookie storage directory
23
     */
24
    private string $cookieDir;
25
26
    /**
27
     * Cookie jar instances per site
28
     */
29
    private array $cookieJars = [];
30
31
    /**
32
     * Site-specific age verification configurations
33
     */
34
    private array $siteConfigs = [
35
        'adultdvdempire.com' => [
36
            'cookies' => [
37
                ['name' => 'age_verified', 'value' => '1'],
38
                ['name' => 'age_gate_passed', 'value' => 'true'],
39
                ['name' => 'over18', 'value' => '1'],
40
                ['name' => 'ageConfirmed', 'value' => 'true'],
41
                ['name' => 'isAdult', 'value' => 'true'],
42
            ],
43
            'detection' => ['ageConfirmationButton', 'age-confirmation', 'confirm you are over'],
44
        ],
45
        'adultdvdmarketplace.com' => [
46
            'cookies' => [
47
                ['name' => 'age_verified', 'value' => '1'],
48
                ['name' => 'over18', 'value' => 'yes'],
49
                ['name' => 'ageConfirmed', 'value' => 'true'],
50
            ],
51
            'detection' => ['age verification', 'are you 18'],
52
        ],
53
        'straight.theater.aebn.net' => [
54
            'cookies' => [
55
                ['name' => 'age_verified', 'value' => '1'],
56
                ['name' => 'aebn_age_check', 'value' => 'passed'],
57
                ['name' => 'over18', 'value' => 'true'],
58
            ],
59
            'detection' => ['age verification', 'over 18'],
60
        ],
61
        'hotmovies.com' => [
62
            'cookies' => [
63
                ['name' => 'age_verified', 'value' => '1'],
64
                ['name' => 'over18', 'value' => 'true'],
65
                ['name' => 'ageConfirmed', 'value' => 'true'],
66
                ['name' => 'hmAgeVerified', 'value' => '1'],
67
            ],
68
            'detection' => ['age verification', 'enter'],
69
        ],
70
        'popporn.com' => [
71
            'cookies' => [
72
                ['name' => 'age_verified', 'value' => '1'],
73
                ['name' => 'over_18', 'value' => 'yes'],
74
                ['name' => 'ageVerified', 'value' => 'true'],
75
                ['name' => 'popAgeConfirmed', 'value' => '1'],
76
                ['name' => 'adultConsent', 'value' => 'true'],
77
                // Note: etoken is dynamically set by the server, we can't pre-set it
78
            ],
79
            'detection' => ['age verification', 'over 18', 'AgeConfirmation'],
80
            'redirectBypass' => true, // Indicates this site uses redirect-based verification
81
        ],
82
    ];
83
84
    /**
85
     * Constructor
86
     */
87
    public function __construct(?string $cookieDir = null)
88
    {
89
        if ($cookieDir !== null) {
90
            $this->cookieDir = $cookieDir;
91
        } else {
92
            $this->cookieDir = storage_path('app/cookies/adult_sites');
93
        }
94
95
        // Create cookie directory if it doesn't exist
96
        if (! is_dir($this->cookieDir)) {
97
            mkdir($this->cookieDir, 0755, true);
98
        }
99
    }
100
101
    /**
102
     * Get cookie jar for a specific site
103
     * Loads existing cookies or creates new jar
104
     */
105
    public function getCookieJar(string $url): FileCookieJar
106
    {
107
        $domain = $this->extractDomain($url);
108
109
        if (! isset($this->cookieJars[$domain])) {
110
            $cookieFile = $this->getCookieFilePath($domain);
111
            $this->cookieJars[$domain] = new FileCookieJar($cookieFile, true);
112
113
            // If cookie file is new or empty, set age verification cookies
114
            if (!file_exists($cookieFile) || filesize($cookieFile) < 10) {
115
                $this->setAgeVerificationCookies($domain, $this->cookieJars[$domain]);
116
            }
117
        }
118
119
        return $this->cookieJars[$domain];
120
    }
121
122
    /**
123
     * Make HTTP request with automatic age verification handling
124
     */
125
    public function makeRequest(string $url, array $options = []): string|false
126
    {
127
        $domain = $this->extractDomain($url);
128
        $cookieJar = $this->getCookieJar($url);
129
130
        $client = new Client([
131
            'cookies' => $cookieJar,
132
            'headers' => [
133
                'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
134
                'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
135
                'Accept-Language' => 'en-US,en;q=0.5',
136
                'Accept-Encoding' => 'gzip, deflate, br',
137
                'DNT' => '1',
138
                'Connection' => 'keep-alive',
139
                'Upgrade-Insecure-Requests' => '1',
140
            ],
141
            'allow_redirects' => true,
142
            'verify' => false, // For development; set to true in production
143
        ]);
144
145
        try {
146
            $response = $client->get($url, $options);
147
            $html = $response->getBody()->getContents();
148
149
            // Check if we got an age verification page
150
            if ($this->isAgeVerificationPage($html, $domain)) {
151
                // Set age verification cookies and retry
152
                $this->setAgeVerificationCookies($domain, $cookieJar);
153
                $cookieJar->save($this->getCookieFilePath($domain));
154
155
                // Retry request with new cookies
156
                $response = $client->get($url, $options);
157
                $html = $response->getBody()->getContents();
158
            }
159
160
            return $html;
161
162
        } catch (\Exception $e) {
163
            error_log("Age Verification Manager: Request failed for {$url}: ".$e->getMessage());
164
165
            return false;
166
        }
167
    }
168
169
    /**
170
     * Check if response is an age verification page
171
     */
172
    private function isAgeVerificationPage(string $html, string $domain): bool
173
    {
174
        if (! isset($this->siteConfigs[$domain])) {
175
            return false;
176
        }
177
178
        $detection = $this->siteConfigs[$domain]['detection'];
179
        $htmlLower = strtolower($html);
180
181
        foreach ($detection as $keyword) {
182
            if (stripos($htmlLower, strtolower($keyword)) !== false) {
183
                return true;
184
            }
185
        }
186
187
        return false;
188
    }
189
190
    /**
191
     * Set age verification cookies for a domain
192
     */
193
    private function setAgeVerificationCookies(string $domain, CookieJar $cookieJar): void
194
    {
195
        if (! isset($this->siteConfigs[$domain])) {
196
            return;
197
        }
198
199
        $cookies = $this->siteConfigs[$domain]['cookies'];
200
        $expiry = time() + (365 * 24 * 60 * 60); // 1 year
201
202
        foreach ($cookies as $cookieData) {
203
            $cookie = new SetCookie([
204
                'Name' => $cookieData['name'],
205
                'Value' => $cookieData['value'],
206
                'Domain' => '.'.$domain,
207
                'Path' => '/',
208
                'Expires' => $expiry,
209
                'Secure' => true,
210
                'HttpOnly' => false,
211
            ]);
212
213
            $cookieJar->setCookie($cookie);
214
        }
215
216
        // Save cookies to file
217
        if ($cookieJar instanceof FileCookieJar) {
218
            $cookieJar->save($this->getCookieFilePath($domain));
219
        }
220
    }
221
222
    /**
223
     * Extract domain from URL
224
     */
225
    private function extractDomain(string $url): string
226
    {
227
        $parsed = parse_url($url);
228
        $host = $parsed['host'] ?? '';
229
230
        // Remove www. prefix if present
231
        $host = preg_replace('/^www\./', '', $host);
232
233
        // Map known subdomains to main domains
234
        $domainMap = [
235
            'straight.theater.aebn.net' => 'straight.theater.aebn.net',
236
            'adultdvdempire.com' => 'adultdvdempire.com',
237
            'adultdvdmarketplace.com' => 'adultdvdmarketplace.com',
238
            'hotmovies.com' => 'hotmovies.com',
239
            'popporn.com' => 'popporn.com',
240
        ];
241
242
        return $domainMap[$host] ?? $host;
243
    }
244
245
    /**
246
     * Force refresh cookies for a domain.
247
     */
248
    public function refreshCookies(string $url): FileCookieJar
249
    {
250
        $domain = $this->extractDomain($url);
251
        $cookieFile = $this->getCookieFilePath($domain);
252
253
        // Clear existing cookie jar
254
        if (isset($this->cookieJars[$domain])) {
255
            unset($this->cookieJars[$domain]);
256
        }
257
258
        // Delete old cookie file if exists
259
        if (file_exists($cookieFile)) {
260
            unlink($cookieFile);
261
        }
262
263
        // Create new jar with fresh cookies
264
        $this->cookieJars[$domain] = new FileCookieJar($cookieFile, true);
265
        $this->setAgeVerificationCookies($domain, $this->cookieJars[$domain]);
266
267
        return $this->cookieJars[$domain];
268
    }
269
270
    /**
271
     * Get cookie file path for a domain
272
     */
273
    private function getCookieFilePath(string $domain): string
274
    {
275
        $safeName = preg_replace('/[^a-z0-9_-]/', '_', strtolower($domain));
276
277
        return $this->cookieDir.'/'.$safeName.'_cookies.json';
278
    }
279
280
    /**
281
     * Clear cookies for a specific domain
282
     */
283
    public function clearCookies(string $domain): bool
284
    {
285
        $cookieFile = $this->getCookieFilePath($domain);
286
287
        if (file_exists($cookieFile)) {
288
            unlink($cookieFile);
289
            unset($this->cookieJars[$domain]);
290
291
            return true;
292
        }
293
294
        return false;
295
    }
296
297
    /**
298
     * Clear all stored cookies
299
     */
300
    public function clearAllCookies(): int
301
    {
302
        $cleared = 0;
303
        $files = glob($this->cookieDir.'/*_cookies.json');
304
305
        foreach ($files as $file) {
306
            if (unlink($file)) {
307
                $cleared++;
308
            }
309
        }
310
311
        $this->cookieJars = [];
312
313
        return $cleared;
314
    }
315
316
    /**
317
     * Get list of domains with stored cookies
318
     */
319
    public function getStoredDomains(): array
320
    {
321
        $domains = [];
322
        $files = glob($this->cookieDir.'/*_cookies.json');
323
324
        foreach ($files as $file) {
325
            $basename = basename($file, '_cookies.json');
326
            $domain = str_replace('_', '.', $basename);
327
            $domains[] = $domain;
328
        }
329
330
        return $domains;
331
    }
332
333
    /**
334
     * Check if cookies exist for a domain
335
     */
336
    public function hasCookies(string $domain): bool
337
    {
338
        $cookieFile = $this->getCookieFilePath($domain);
339
340
        return file_exists($cookieFile) && filesize($cookieFile) > 10;
341
    }
342
343
    /**
344
     * Get cookie statistics
345
     */
346
    public function getCookieStats(): array
347
    {
348
        $stats = [
349
            'total_domains' => 0,
350
            'total_cookies' => 0,
351
            'domains' => [],
352
        ];
353
354
        foreach ($this->siteConfigs as $domain => $config) {
355
            if ($this->hasCookies($domain)) {
356
                $cookieJar = $this->getCookieJar('https://'.$domain);
357
                $cookieCount = count($cookieJar);
358
359
                $stats['domains'][$domain] = [
360
                    'has_cookies' => true,
361
                    'cookie_count' => $cookieCount,
362
                    'file' => $this->getCookieFilePath($domain),
363
                ];
364
365
                $stats['total_domains']++;
366
                $stats['total_cookies'] += $cookieCount;
367
            } else {
368
                $stats['domains'][$domain] = [
369
                    'has_cookies' => false,
370
                    'cookie_count' => 0,
371
                ];
372
            }
373
        }
374
375
        return $stats;
376
    }
377
378
    /**
379
     * Get the cookie directory path
380
     */
381
    public function getCookieDirectory(): string
382
    {
383
        return $this->cookieDir;
384
    }
385
}
386
387