Completed
Push — master ( e32d59...b7ff0b )
by Dmitry
06:06
created

Client::recognizeKeyCaptcha()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 35
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 21
nc 4
nop 6
1
<?php
2
3
namespace Rucaptcha;
4
5
use GuzzleHttp\RequestOptions;
6
use Rucaptcha\Exception\ErrorResponseException;
7
use Rucaptcha\Exception\InvalidArgumentException;
8
use Rucaptcha\Exception\RuntimeException;
9
10
/**
11
 * Class Client
12
 *
13
 * @package Rucaptcha
14
 * @author Dmitry Gladyshev <[email protected]>
15
 */
16
class Client extends GenericClient
17
{
18
    /* json status codes */
19
    const STATUS_CODE_CAPCHA_NOT_READY = 0;
20
    const STATUS_CODE_OK = 1;
21
22
    /* status codes */
23
    const STATUS_OK_REPORT_RECORDED = 'OK_REPORT_RECORDED';
24
25
    /**
26
     * @var int
27
     */
28
    protected $recaptchaRTimeout = 15;
29
30
    /**
31
     * @var string
32
     */
33
    protected $serverBaseUri = 'http://rucaptcha.com';
34
35
    /**
36
     * Your application ID in Rucaptcha catalog.
37
     * The value `1013` is ID of this library. Set in false if you want to turn off sending any ID.
38
     *
39
     * @see https://rucaptcha.com/software/view/php-api-client
40
     * @var string
41
     */
42
    protected $softId = '1013';
43
44
    /**
45
     * @inheritdoc
46
     */
47
    public function sendCaptcha($content, array $extra = [])
48
    {
49
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
50
            $extra[Extra::SOFT_ID] = $this->softId;
51
        }
52
        return parent::sendCaptcha($content, $extra);
53
    }
54
55
    /**
56
     * Bulk captcha result.
57
     *
58
     * @param int[] $captchaIds         # Captcha task Ids array
59
     * @return string[]                 # Array $captchaId => $captchaText or false if is not ready
60
     * @throws ErrorResponseException
61
     */
62
    public function getCaptchaResultBulk(array $captchaIds)
63
    {
64
        $response = $this->getHttpClient()->request('GET', '/res.php?' . http_build_query([
65
                'key' => $this->apiKey,
66
                'action' => 'get',
67
                'ids' => join(',', $captchaIds)
68
            ]));
69
70
        $captchaTexts = $response->getBody()->__toString();
71
72
        $this->getLogger()->info("Got bulk response: `{$captchaTexts}`.");
73
74
        $captchaTexts = explode("|", $captchaTexts);
75
76
        $result = [];
77
78
        foreach ($captchaTexts as $index => $captchaText) {
79
            $captchaText = html_entity_decode(trim($captchaText));
80
            $result[$captchaIds[$index]] =
81
                ($captchaText == self::STATUS_CAPTCHA_NOT_READY) ? false : $captchaText;
82
        }
83
84
        return $result;
85
    }
86
87
    /**
88
     * Returns balance of account.
89
     *
90
     * @return string
91
     */
92
    public function getBalance()
93
    {
94
        $response = $this
95
            ->getHttpClient()
96
            ->request('GET', "/res.php?key={$this->apiKey}&action=getbalance");
97
98
        return $response->getBody()->__toString();
99
    }
100
101
    /**
102
     * Report of wrong recognition.
103
     *
104
     * @param string $captchaId
105
     * @return bool
106
     * @throws ErrorResponseException
107
     */
108
    public function badCaptcha($captchaId)
109
    {
110
        $response = $this
111
            ->getHttpClient()
112
            ->request('GET', "/res.php?key={$this->apiKey}&action=reportbad&id={$captchaId}");
113
114
        $responseText = $response->getBody()->__toString();
115
116
        if ($responseText === self::STATUS_OK_REPORT_RECORDED) {
117
            return true;
118
        }
119
120
        throw new ErrorResponseException(
121
            $this->getErrorMessage($responseText) ?: $responseText,
122
            $this->getErrorCode($responseText) ?: 0
123
        );
124
    }
125
126
    /**
127
     * Returns server health data.
128
     *
129
     * @param string|string[] $paramsList   # List of metrics to be returned
130
     * @return array                        # Array of load metrics $metric => $value formatted
131
     */
132
    public function getLoad($paramsList = ['waiting', 'load', 'minbid', 'averageRecognitionTime'])
133
    {
134
        $parser = $this->getLoadXml();
135
136
        if (is_string($paramsList)) {
137
            return $parser->$paramsList->__toString();
138
        }
139
140
        $statusData = [];
141
142
        foreach ($paramsList as $item) {
143
            $statusData[$item] = $parser->$item->__toString();
144
        }
145
146
        return $statusData;
147
    }
148
149
    /**
150
     * Returns load data as XML.
151
     *
152
     * @return \SimpleXMLElement
153
     */
154
    public function getLoadXml()
155
    {
156
        $response = $this
157
            ->getHttpClient()
158
            ->request('GET', "/load.php");
159
160
        return new \SimpleXMLElement($response->getBody()->__toString());
161
    }
162
163
    /**
164
     * @param string $captchaId     # Captcha task ID
165
     * @return array | false        # Solved captcha and cost array or false if captcha is not ready
166
     * @throws ErrorResponseException
167
     */
168
    public function getCaptchaResultWithCost($captchaId)
169
    {
170
        $response = $this
171
            ->getHttpClient()
172
            ->request('GET', "/res.php?key={$this->apiKey}&action=get2&id={$captchaId}");
173
174
        $responseText = $response->getBody()->__toString();
175
176
        if ($responseText === self::STATUS_CAPTCHA_NOT_READY) {
177
            return false;
178
        }
179
180
        if (strpos($responseText, 'OK|') !== false) {
181
            $this->getLogger()->info("Got OK response: `{$responseText}`.");
182
            $data = explode('|', $responseText);
183
            return [
184
                'captcha' => html_entity_decode(trim($data[1])),
185
                'cost' => html_entity_decode(trim($data[2])),
186
            ];
187
        }
188
189
        throw new ErrorResponseException(
190
            $this->getErrorMessage($responseText) ?: $responseText,
191
            $this->getErrorCode($responseText) ?: 0
192
        );
193
    }
194
195
    /**
196
     * Add pingback url to rucaptcha whitelist.
197
     *
198
     * @param string $url
199
     * @return bool                     # true if added and exception if fail
200
     * @throws ErrorResponseException
201
     */
202
    public function addPingback($url)
203
    {
204
        $response = $this
205
            ->getHttpClient()
206
            ->request('GET', "/res.php?key={$this->apiKey}&action=add_pingback&addr={$url}");
207
208
        $responseText = $response->getBody()->__toString();
209
210
        if ($responseText === self::STATUS_OK) {
211
            return true;
212
        }
213
214
        throw new ErrorResponseException(
215
            $this->getErrorMessage($responseText) ?: $responseText,
216
            $this->getErrorCode($responseText) ?: 0
217
        );
218
    }
219
220
    /**
221
     * Returns pingback whitelist items.
222
     *
223
     * @return string[]                 # List of urls
224
     * @throws ErrorResponseException
225
     */
226
    public function getPingbacks()
227
    {
228
        $response = $this
229
            ->getHttpClient()
230
            ->request('GET', "/res.php?key={$this->apiKey}&action=get_pingback");
231
232
        $responseText = $response->getBody()->__toString();
233
234
        if (strpos($responseText, 'OK|') !== false) {
235
            $data = explode('|', $responseText);
236
            unset($data[0]);
237
            return empty($data[1]) ? [] : array_values($data);
238
        }
239
240
        throw new ErrorResponseException(
241
            $this->getErrorMessage($responseText) ?: $responseText,
242
            $this->getErrorCode($responseText) ?: 0
243
        );
244
    }
245
246
    /**
247
     * Remove pingback url from whitelist.
248
     *
249
     * @param string $uri
250
     * @return bool
251
     * @throws ErrorResponseException
252
     */
253
    public function deletePingback($uri)
254
    {
255
        $response = $this
256
            ->getHttpClient()
257
            ->request('GET', "/res.php?key={$this->apiKey}&action=del_pingback&addr={$uri}");
258
259
        $responseText = $response->getBody()->__toString();
260
261
        if ($responseText === self::STATUS_OK) {
262
            return true;
263
        }
264
        throw new ErrorResponseException(
265
            $this->getErrorMessage($responseText) ?: $responseText,
266
            $this->getErrorCode($responseText) ?: 0
267
        );
268
    }
269
270
    /**
271
     * Truncate pingback whitelist.
272
     *
273
     * @return bool
274
     * @throws ErrorResponseException
275
     */
276
    public function deleteAllPingbacks()
277
    {
278
        return $this->deletePingback('all');
279
    }
280
281
    /* Recaptcha v2 */
282
283
    public function sendRecapthaV2($googleKey, $pageUrl, $extra = [])
284
    {
285
        $this->getLogger()->info("Try send google key (recaptcha)  on {$this->serverBaseUri}/in.php");
286
287
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
288
            $extra[Extra::SOFT_ID] = $this->softId;
289
        }
290
291
        $response = $this->getHttpClient()->request('POST', "/in.php", [
292
            RequestOptions::QUERY => array_merge($extra, [
293
                'method' => 'userrecaptcha',
294
                'key' => $this->apiKey,
295
                'googlekey' => $googleKey,
296
                'pageurl' => $pageUrl
297
            ])
298
        ]);
299
300
        $responseText = $response->getBody()->__toString();
301
302
        if (strpos($responseText, 'OK|') !== false) {
303
            $this->lastCaptchaId = explode("|", $responseText)[1];
304
            $this->getLogger()->info("Sending success. Got captcha id `{$this->lastCaptchaId}`.");
305
            return $this->lastCaptchaId;
306
        }
307
308
        throw new ErrorResponseException($this->getErrorMessage($responseText) ?: "Unknown error: `{$responseText}`.");
309
    }
310
311
    /**
312
     * Recaptcha V2 recognition.
313
     *
314
     * @param string $googleKey
315
     * @param string $pageUrl
316
     * @param array $extra      # Captcha options
317
     * @return string           # Code to place in hidden form
318
     * @throws RuntimeException
319
     */
320
    public function recognizeRecaptchaV2($googleKey, $pageUrl, $extra = [])
321
    {
322
        $captchaId = $this->sendRecapthaV2($googleKey, $pageUrl, $extra);
323
        $startTime = time();
324
325
        while (true) {
326
            $this->getLogger()->info("Waiting {$this->rTimeout} sec.");
327
328
            sleep($this->recaptchaRTimeout);
329
330
            if (time() - $startTime >= $this->mTimeout) {
331
                throw new RuntimeException("Captcha waiting timeout.");
332
            }
333
334
            $result = $this->getCaptchaResult($captchaId);
335
336
            if ($result === false) {
337
                continue;
338
            }
339
340
            $this->getLogger()->info("Elapsed " . (time()-$startTime) . " second(s).");
341
342
            return $result;
343
        }
344
345
        throw new RuntimeException('Unknown recognition logic error.');
346
    }
347
348
    /**
349
     * Keycaptcha recognition.
350
     *
351
     * @param string $SSCUserId
352
     * @param string $SSCSessionId
353
     * @param string $SSCWebServerSign
354
     * @param string $SSCWebServerSign2
355
     * @param string $pageUrl
356
     * @param array $extra
357
     * @return string                       # Captcha ID
358
     * @throws ErrorResponseException
359
     */
360
    public function sendKeyCaptcha(
361
        $SSCUserId,
362
        $SSCSessionId,
363
        $SSCWebServerSign,
364
        $SSCWebServerSign2,
365
        $pageUrl,
366
        $extra = []
367
    ) {
368
    
369
        $this->getLogger()->info("Try send google key (recaptcha)  on {$this->serverBaseUri}/in.php");
370
371
        if ($this->softId && !isset($extra[Extra::SOFT_ID])) {
372
            $extra[Extra::SOFT_ID] = $this->softId;
373
        }
374
375
        $response = $this->getHttpClient()->request('POST', "/in.php", [
376
            RequestOptions::QUERY => array_merge($extra, [
377
                'method' => 'keycaptcha',
378
                'key' => $this->apiKey,
379
                's_s_c_user_id' => $SSCUserId,
380
                's_s_c_session_id' => $SSCSessionId,
381
                's_s_c_web_server_sign' => $SSCWebServerSign,
382
                's_s_c_web_server_sign2' => $SSCWebServerSign2,
383
                'pageurl' => $pageUrl
384
            ])
385
        ]);
386
387
        $responseText = $response->getBody()->__toString();
388
389
        if (strpos($responseText, 'OK|') !== false) {
390
            $this->lastCaptchaId = explode("|", $responseText)[1];
391
            $this->getLogger()->info("Sending success. Got captcha id `{$this->lastCaptchaId}`.");
392
            return $this->lastCaptchaId;
393
        }
394
395
        throw new ErrorResponseException($this->getErrorMessage($responseText) ?: "Unknown error: `{$responseText}`.");
396
    }
397
398
    /**
399
     * Keycaptcha recognition.
400
     *
401
     * @param string $SSCUserId
402
     * @param string $SSCSessionId
403
     * @param string $SSCWebServerSign
404
     * @param string $SSCWebServerSign2
405
     * @param string $pageUrl
406
     * @param array $extra
407
     * @return string                       # Code to place into id="capcode" input value
408
     * @throws RuntimeException
409
     */
410
    public function recognizeKeyCaptcha(
411
        $SSCUserId,
412
        $SSCSessionId,
413
        $SSCWebServerSign,
414
        $SSCWebServerSign2,
415
        $pageUrl,
416
        $extra = []
417
    ) {
418
        $captchaId = $this
419
            ->sendKeyCaptcha($SSCUserId, $SSCSessionId, $SSCWebServerSign, $SSCWebServerSign2, $pageUrl, $extra);
420
421
        $startTime = time();
422
423
        while (true) {
424
            $this->getLogger()->info("Waiting {$this->rTimeout} sec.");
425
426
            sleep($this->recaptchaRTimeout);
427
428
            if (time() - $startTime >= $this->mTimeout) {
429
                throw new RuntimeException("Captcha waiting timeout.");
430
            }
431
432
            $result = $this->getCaptchaResult($captchaId);
433
434
            if ($result === false) {
435
                continue;
436
            }
437
438
            $this->getLogger()->info("Elapsed " . (time()-$startTime) . " second(s).");
439
440
            return $result;
441
        }
442
443
        throw new RuntimeException('Unknown recognition logic error.');
444
    }
445
446
    /**
447
     * Override generic method for using json response.
448
     *
449
     * @param string $captchaId         # Captcha task ID
450
     * @return false|string             # Solved captcha text or false if captcha is not ready
451
     * @throws ErrorResponseException
452
     * @throws InvalidArgumentException
453
     */
454
    public function getCaptchaResult($captchaId)
455
    {
456
        $response = $this
457
            ->getHttpClient()
458
            ->request('GET', "/res.php?key={$this->apiKey}&action=get&id={$captchaId}&json=1");
459
460
        $responseData = json_decode($response->getBody()->__toString(), true);
461
462
        if (JSON_ERROR_NONE !== json_last_error()) {
463
            throw new InvalidArgumentException(
464
                'json_decode error: ' . json_last_error_msg()
465
            );
466
        }
467
468
        if ($responseData['status'] === self::STATUS_CODE_CAPCHA_NOT_READY) {
469
            return false;
470
        }
471
472
        if ($responseData['status'] === self::STATUS_CODE_OK) {
473
            $this->getLogger()->info("Got OK response: `{$responseData['request']}`.");
474
            return $responseData['request'];
475
        }
476
477
        throw new ErrorResponseException(
478
            $this->getErrorMessage(
479
                $responseData['request']
480
            ) ?: $responseData['request'],
481
            $responseData['status']
482
        );
483
    }
484
485
    /**
486
     * Match error code by response.
487
     *
488
     * @param string $responseText
489
     * @return int
490
     */
491
    private function getErrorCode($responseText)
492
    {
493
        if (preg_match('/ERROR:\s*(\d{0,4})/ui', $responseText, $matches)) {
494
            return intval($matches[1]);
495
        }
496
        return 0;
497
    }
498
}
499