Client::recognizeRecaptchaV2()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 9.52
c 0
b 0
f 0
cc 4
nc 4
nop 3
1
<?php
2
3
namespace Rucaptcha;
4
5
use GuzzleHttp\Exception\GuzzleException;
6
use GuzzleHttp\Psr7\Request;
7
use Rucaptcha\Exception\ErrorResponseException;
8
use Rucaptcha\Exception\InvalidArgumentException;
9
use Rucaptcha\Exception\RuntimeException;
10
use Throwable;
11
12
/**
13
 * Class Client
14
 *
15
 * @package Rucaptcha
16
 * @author Dmitry Gladyshev <[email protected]>
17
 */
18
class Client extends GenericClient
19
{
20
    /* json status codes */
21
    const STATUS_CODE_CAPCHA_NOT_READY = 0;
22
    const STATUS_CODE_OK = 1;
23
24
    /* status codes */
25
    const STATUS_OK_REPORT_RECORDED = 'OK_REPORT_RECORDED';
26
27
    /**
28
     * @var int
29
     */
30
    protected $recaptchaRTimeout = 15;
31
32
    /**
33
     * @var string
34
     */
35
    protected $serverBaseUri = 'http://rucaptcha.com';
36
37
    /**
38
     * Your application ID in Rucaptcha catalog.
39
     * The value `1013` is ID of this library. Set in false if you want to turn off sending any ID.
40
     *
41
     * @see https://rucaptcha.com/software/view/php-api-client
42
     * @var string
43
     */
44
    protected $softId = '1013';
45
46
    /**
47
     * @inheritdoc
48
     */
49
    public function sendCaptcha($content, array $extra = [])
50
    {
51
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
52
            $extra[Extra::SOFT_ID] = $this->softId;
53
        }
54
        return parent::sendCaptcha($content, $extra);
55
    }
56
57
    /**
58
     * Bulk captcha result.
59
     *
60
     * @param int[] $captchaIds # Captcha task Ids array
61
     * @return string[]                 # Array $captchaId => $captchaText or false if is not ready
62
     * @throws GuzzleException
63
     */
64
    public function getCaptchaResultBulk(array $captchaIds)
65
    {
66
        $request = new Request('GET', '/res.php?' . http_build_query([
67
            'key' => $this->apiKey,
68
            'action' => 'get',
69
            'ids' => implode(',', $captchaIds)
70
        ]));
71
72
        $response = $this
73
            ->getHttpClient()
74
            ->sendRequest($request);
75
76
        $captchaTexts = $response->getBody()->getContents();
77
78
        $this->getLogger()->info("Got bulk response: `{$captchaTexts}`.");
79
80
        $captchaTexts = explode("|", $captchaTexts);
81
82
        $result = [];
83
84
        foreach ($captchaTexts as $index => $captchaText) {
85
            $captchaText = html_entity_decode(trim($captchaText));
86
            $result[$captchaIds[$index]] = ($captchaText == self::STATUS_CAPTCHA_NOT_READY) ? false : $captchaText;
87
        }
88
89
        return $result;
90
    }
91
92
    /**
93
     * Returns balance of account.
94
     *
95
     * @return string
96
     * @throws Throwable
97
     */
98
    public function getBalance()
99
    {
100
        $request = new Request('GET', "/res.php?key={$this->apiKey}&action=getbalance");
101
102
        $response = $this
103
            ->getHttpClient()
104
            ->sendRequest($request);
105
106
        return $response->getBody()->getContents();
107
    }
108
109
    /**
110
     * Alias of $this->reportBad();
111
     *
112
     * @param string $captchaId
113
     * @return bool
114
     * @throws Throwable
115
     * @deprecated
116
     */
117
    public function badCaptcha($captchaId)
118
    {
119
        return $this->reportBad($captchaId);
120
    }
121
122
    /**
123
     * Alias of $this->reportGood();
124
     *
125
     * @param string $captchaId
126
     * @return bool
127
     * @throws ErrorResponseException
128
     * @throws Throwable
129
     * @deprecated
130
     */
131
    public function goodCaptcha($captchaId)
132
    {
133
        return $this->reportGood($captchaId);
134
    }
135
136
    /**
137
     * Report of wrong recognition.
138
     *
139
     * @param string $captchaId
140
     * @return bool
141
     * @throws ErrorResponseException
142
     * @throws Throwable
143
     */
144
    public function reportBad($captchaId)
145
    {
146
        $request = new Request('GET', "/res.php?key={$this->apiKey}&action=reportbad&id={$captchaId}");
147
148
        $response = $this
149
            ->getHttpClient()
150
            ->sendRequest($request);
151
152
        $responseText = $response->getBody()->getContents();
153
154
        if ($responseText === self::STATUS_OK_REPORT_RECORDED) {
155
            return true;
156
        }
157
158
        throw new ErrorResponseException(
159
            $this->getErrorMessage($responseText) ?: $responseText,
160
            $this->getErrorCode($responseText) ?: 0
161
        );
162
    }
163
164
165
    /**
166
     * Reports rucaptcha for good recognition.
167
     *
168
     * @param $captchaId
169
     * @return bool
170
     * @throws ErrorResponseException
171
     * @throws Throwable
172
     */
173
    public function reportGood($captchaId)
174
    {
175
        $request = new Request('GET', "/res.php?key={$this->apiKey}&action=reportgood&id={$captchaId}");
176
177
        $response = $this
178
            ->getHttpClient()
179
            ->sendRequest($request);
180
181
        $responseText = $response->getBody()->getContents();
182
183
        if ($responseText === self::STATUS_OK_REPORT_RECORDED) {
184
            return true;
185
        }
186
187
        throw new ErrorResponseException(
188
            $this->getErrorMessage($responseText) ?: $responseText,
189
            $this->getErrorCode($responseText) ?: 0
190
        );
191
    }
192
193
    /**
194
     * @param string $captchaId     # Captcha task ID
195
     * @return array | false        # Solved captcha and cost array or false if captcha is not ready
196
     * @throws ErrorResponseException
197
     * @throws Throwable
198
     */
199
    public function getCaptchaResultWithCost($captchaId)
200
    {
201
        $request = new Request('GET', "/res.php?key={$this->apiKey}&action=get2&id={$captchaId}");
202
203
        $response = $this
204
            ->getHttpClient()
205
            ->sendRequest($request);
206
207
        $responseText = $response->getBody()->getContents();
208
209
        if ($responseText === self::STATUS_CAPTCHA_NOT_READY) {
210
            return false;
211
        }
212
213
        if (strpos($responseText, 'OK|') !== false) {
214
            $this->getLogger()->info("Got OK response: `{$responseText}`.");
215
            $data = explode('|', $responseText);
216
            return [
217
                'captcha' => html_entity_decode(trim($data[1])),
218
                'cost' => html_entity_decode(trim($data[2])),
219
            ];
220
        }
221
222
        throw new ErrorResponseException(
223
            $this->getErrorMessage($responseText) ?: $responseText,
224
            $this->getErrorCode($responseText) ?: 0
225
        );
226
    }
227
228
    /**
229
     * Add pingback url to rucaptcha whitelist.
230
     *
231
     * @param string $url
232
     * @return bool                     # true if added and exception if fail
233
     * @throws ErrorResponseException
234
     * @throws Throwable
235
     */
236
    public function addPingback($url)
237
    {
238
        $request = new Request('GET', "/res.php?key={$this->apiKey}&action=add_pingback&addr={$url}");
239
240
        $response = $this
241
            ->getHttpClient()
242
            ->sendRequest($request);
243
244
        $responseText = $response->getBody()->getContents();
245
246
        if ($responseText === self::STATUS_OK) {
247
            return true;
248
        }
249
250
        throw new ErrorResponseException(
251
            $this->getErrorMessage($responseText) ?: $responseText,
252
            $this->getErrorCode($responseText) ?: 0
253
        );
254
    }
255
256
    /**
257
     * Returns pingback whitelist items.
258
     *
259
     * @return string[]                 # List of urls
260
     * @throws ErrorResponseException
261
     * @throws Throwable
262
     */
263
    public function getPingbacks()
264
    {
265
        $request = new Request('GET', "/res.php?key={$this->apiKey}&action=get_pingback");
266
267
        $response = $this
268
            ->getHttpClient()
269
            ->sendRequest($request);
270
271
        $responseText = $response->getBody()->getContents();
272
273
        if (strpos($responseText, 'OK|') !== false) {
274
            $data = explode('|', $responseText);
275
            unset($data[0]);
276
            return empty($data[1]) ? [] : array_values($data);
277
        }
278
279
        throw new ErrorResponseException(
280
            $this->getErrorMessage($responseText) ?: $responseText,
281
            $this->getErrorCode($responseText) ?: 0
282
        );
283
    }
284
285
    /**
286
     * Remove pingback url from whitelist.
287
     *
288
     * @param string $uri
289
     * @return bool
290
     * @throws ErrorResponseException
291
     * @throws Throwable
292
     */
293
    public function deletePingback($uri)
294
    {
295
        $request = new Request('GET', "/res.php?key={$this->apiKey}&action=del_pingback&addr={$uri}");
296
297
        $response = $this
298
            ->getHttpClient()
299
            ->sendRequest($request);
300
301
        $responseText = $response->getBody()->getContents();
302
303
        if ($responseText === self::STATUS_OK) {
304
            return true;
305
        }
306
307
        throw new ErrorResponseException(
308
            $this->getErrorMessage($responseText) ?: $responseText,
309
            $this->getErrorCode($responseText) ?: 0
310
        );
311
    }
312
313
    /**
314
     * Truncate pingback whitelist.
315
     *
316
     * @return bool
317
     * @throws ErrorResponseException
318
     * @throws Throwable
319
     */
320
    public function deleteAllPingbacks()
321
    {
322
        return $this->deletePingback('all');
323
    }
324
325
    /* Recaptcha v2 */
326
327
    /**
328
     * Sent recaptcha v2
329
     *
330
     * @param string $googleKey
331
     * @param string $pageUrl
332
     * @param array $extra
333
     *
334
     * @return string
335
     *
336
     * @throws ErrorResponseException
337
     * @throws Throwable
338
     */
339
    public function sendRecaptchaV2($googleKey, $pageUrl, $extra = [])
340
    {
341
        $this->getLogger()->info("Try send google key (recaptcha)  on {$this->serverBaseUri}/in.php");
342
343
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
344
            $extra[Extra::SOFT_ID] = $this->softId;
345
        }
346
347
        $params = array_merge($extra, [
348
            'method' => 'userrecaptcha',
349
            'key' => $this->apiKey,
350
            'googlekey' => $googleKey,
351
            'pageurl' => $pageUrl
352
        ]);
353
354
        $request = new Request('POST', "/in.php?" . http_build_query($params));
355
356
        $response = $this
357
            ->getHttpClient()
358
            ->sendRequest($request);
359
360
        $responseText = $response->getBody()->getContents();
361
362
        if (strpos($responseText, 'OK|') !== false) {
363
            $this->lastCaptchaId = explode("|", $responseText)[1];
364
            $this->getLogger()->info("Sending success. Got captcha id `{$this->lastCaptchaId}`.");
365
            return $this->lastCaptchaId;
366
        }
367
368
        throw new ErrorResponseException($this->getErrorMessage($responseText) ?: "Unknown error: `{$responseText}`.");
369
    }
370
371
    /**
372
     * Recaptcha V2 recognition.
373
     *
374
     * @param string $googleKey
375
     * @param string $pageUrl
376
     * @param array $extra              # Captcha options
377
     *
378
     * @return string                   # Code to place in hidden form
379
     *
380
     * @throws ErrorResponseException
381
     * @throws InvalidArgumentException
382
     * @throws RuntimeException
383
     * @throws Throwable
384
     */
385
    public function recognizeRecaptchaV2($googleKey, $pageUrl, $extra = [])
386
    {
387
        $captchaId = $this->sendRecaptchaV2($googleKey, $pageUrl, $extra);
388
        $startTime = time();
389
390
        while (true) {
391
            $this->getLogger()->info("Waiting {$this->recaptchaRTimeout} sec.");
392
393
            sleep($this->recaptchaRTimeout);
394
395
            if (time() - $startTime >= $this->mTimeout) {
396
                throw new RuntimeException("Captcha waiting timeout.");
397
            }
398
399
            $result = $this->getCaptchaResult($captchaId);
400
401
            if ($result === false) {
402
                continue;
403
            }
404
405
            $this->getLogger()->info("Elapsed " . (time()-$startTime) . " second(s).");
406
407
            return $result;
408
        }
409
    }
410
411
    /* Recaptcha v3 */
412
413
    /**
414
     * @param string $googleKey
415
     * @param string $pageUrl
416
     * @param string $action
417
     * @param string $minScore
418
     * @param array $extra
419
     *
420
     * @return string
421
     *
422
     * @throws ErrorResponseException
423
     * @throws Throwable
424
     *
425
     * @see https://rucaptcha.com/blog/for_webmaster/recaptcha-v3-obhod
426
     */
427
    public function sendRecaptchaV3($googleKey, $pageUrl, $action, $minScore = '0.3', $extra = [])
428
    {
429
        $this->getLogger()->info("Try send google key (recaptcha v3)  on {$this->serverBaseUri}/in.php");
430
431
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
432
            $extra[Extra::SOFT_ID] = $this->softId;
433
        }
434
435
        $request = new Request('POST', "/in.php?". http_build_query(array_merge($extra, [
436
            'method' => 'userrecaptcha',
437
            'version' => 'v3',
438
            'key' => $this->apiKey,
439
            'googlekey' => $googleKey,
440
            'pageurl' => $pageUrl,
441
            'action' => $action,
442
            'min_score' => $minScore
443
        ])));
444
445
        $response = $this
446
            ->getHttpClient()
447
            ->sendRequest($request);
448
449
        $responseText = $response->getBody()->getContents();
450
451
        if (strpos($responseText, 'OK|') !== false) {
452
            $this->lastCaptchaId = explode("|", $responseText)[1];
453
            $this->getLogger()->info("Sending success. Got captcha id `{$this->lastCaptchaId}`.");
454
            return $this->lastCaptchaId;
455
        }
456
457
        throw new ErrorResponseException($this->getErrorMessage($responseText) ?: "Unknown error: `{$responseText}`.");
458
    }
459
460
    /**
461
     * @param string $googleKey
462
     * @param string $pageUrl
463
     * @param string $action
464
     * @param string $minScore
465
     * @param array $extra
466
     *
467
     * @return false|string
468
     *
469
     * @throws ErrorResponseException
470
     * @throws InvalidArgumentException
471
     * @throws RuntimeException
472
     * @throws Throwable
473
     */
474
    public function recognizeRecaptchaV3($googleKey, $pageUrl, $action, $minScore = '0.3', $extra = [])
475
    {
476
        $captchaId = $this->sendRecaptchaV3($googleKey, $pageUrl, $action, $minScore, $extra);
477
        $startTime = time();
478
479
        while (true) {
480
            $this->getLogger()->info("Waiting {$this->recaptchaRTimeout} sec.");
481
482
            sleep($this->recaptchaRTimeout);
483
484
            if (time() - $startTime >= $this->mTimeout) {
485
                throw new RuntimeException("Captcha waiting timeout.");
486
            }
487
488
            $result = $this->getCaptchaResult($captchaId);
489
490
            if ($result === false) {
491
                continue;
492
            }
493
494
            $this->getLogger()->info("Elapsed " . (time()-$startTime) . " second(s).");
495
496
            return $result;
497
        }
498
    }
499
500
    /**
501
     * Keycaptcha recognition.
502
     *
503
     * @param string $SSCUserId
504
     * @param string $SSCSessionId
505
     * @param string $SSCWebServerSign
506
     * @param string $SSCWebServerSign2
507
     * @param string $pageUrl
508
     * @param array $extra
509
     *
510
     * @return string # Captcha ID
511
     *
512
     * @throws ErrorResponseException
513
     * @throws Throwable
514
     */
515
    public function sendKeyCaptcha(
516
        $SSCUserId,
517
        $SSCSessionId,
518
        $SSCWebServerSign,
519
        $SSCWebServerSign2,
520
        $pageUrl,
521
        $extra = []
522
    ) {
523
        $this->getLogger()->info("Try send google key (recaptcha)  on {$this->serverBaseUri}/in.php");
524
525
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
526
            $extra[Extra::SOFT_ID] = $this->softId;
527
        }
528
529
        $request = new Request('POST', "/in.php?" . http_build_query(array_merge($extra, [
530
            'method' => 'keycaptcha',
531
            'key' => $this->apiKey,
532
            's_s_c_user_id' => $SSCUserId,
533
            's_s_c_session_id' => $SSCSessionId,
534
            's_s_c_web_server_sign' => $SSCWebServerSign,
535
            's_s_c_web_server_sign2' => $SSCWebServerSign2,
536
            'pageurl' => $pageUrl
537
        ])));
538
539
        $response = $this
540
            ->getHttpClient()
541
            ->sendRequest($request);
542
543
        $responseText = $response->getBody()->getContents();
544
545
        if (strpos($responseText, 'OK|') !== false) {
546
            $this->lastCaptchaId = explode("|", $responseText)[1];
547
            $this->getLogger()->info("Sending success. Got captcha id `{$this->lastCaptchaId}`.");
548
            return $this->lastCaptchaId;
549
        }
550
551
        throw new ErrorResponseException($this->getErrorMessage($responseText) ?: "Unknown error: `{$responseText}`.");
552
    }
553
554
    /**
555
     * Keycaptcha recognition.
556
     *
557
     * @param string $SSCUserId
558
     * @param string $SSCSessionId
559
     * @param string $SSCWebServerSign
560
     * @param string $SSCWebServerSign2
561
     * @param string $pageUrl
562
     * @param array $extra
563
     * @return string                       # Code to place into id="capcode" input value
564
     * @throws ErrorResponseException
565
     * @throws InvalidArgumentException
566
     * @throws RuntimeException
567
     * @throws Throwable
568
     */
569
    public function recognizeKeyCaptcha(
570
        $SSCUserId,
571
        $SSCSessionId,
572
        $SSCWebServerSign,
573
        $SSCWebServerSign2,
574
        $pageUrl,
575
        $extra = []
576
    ) {
577
        $captchaId = $this
578
            ->sendKeyCaptcha($SSCUserId, $SSCSessionId, $SSCWebServerSign, $SSCWebServerSign2, $pageUrl, $extra);
579
580
        $startTime = time();
581
582
        while (true) {
583
            $this->getLogger()->info("Waiting {$this->recaptchaRTimeout} sec.");
584
585
            sleep($this->recaptchaRTimeout);
586
587
            if (time() - $startTime >= $this->mTimeout) {
588
                throw new RuntimeException("Captcha waiting timeout.");
589
            }
590
591
            $result = $this->getCaptchaResult($captchaId);
592
593
            if ($result === false) {
594
                continue;
595
            }
596
597
            $this->getLogger()->info("Elapsed " . (time()-$startTime) . " second(s).");
598
599
            return $result;
600
        }
601
    }
602
603
    /**
604
     * Override generic method for using json response.
605
     *
606
     * @param string $captchaId # Captcha task ID
607
     * @return false|string             # Solved captcha text or false if captcha is not ready
608
     * @throws ErrorResponseException
609
     * @throws InvalidArgumentException
610
     * @throws Throwable
611
     */
612
    public function getCaptchaResult($captchaId)
613
    {
614
        $request = new Request('GET', "/res.php?key={$this->apiKey}&action=get&id={$captchaId}&json=1");
615
616
        $response = $this
617
            ->getHttpClient()
618
            ->sendRequest($request);
619
620
        $responseData = json_decode($response->getBody()->getContents(), true);
621
622
        if (JSON_ERROR_NONE !== json_last_error()) {
623
            throw new InvalidArgumentException(
624
                'json_decode error: ' . json_last_error_msg()
625
            );
626
        }
627
628
        if ($responseData['status'] === self::STATUS_CODE_CAPCHA_NOT_READY) {
629
            return false;
630
        }
631
632
        if ($responseData['status'] === self::STATUS_CODE_OK) {
633
            $this->getLogger()->info("Got OK response: `{$responseData['request']}`.");
634
            return $responseData['request'];
635
        }
636
637
        throw new ErrorResponseException(
638
            $this->getErrorMessage($responseData['request']) ?: $responseData['request'],
639
            $responseData['status']
640
        );
641
    }
642
643
    /**
644
     * Match error code by response.
645
     *
646
     * @param string $responseText
647
     * @return int
648
     */
649
    private function getErrorCode($responseText)
650
    {
651
        if (preg_match('/ERROR:\s*(\d{0,4})/ui', $responseText, $matches)) {
652
            return intval($matches[1]);
653
        }
654
        return 0;
655
    }
656
}
657