GenericClient   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 66.67%

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 13
dl 0
loc 244
rs 10
c 0
b 0
f 0
ccs 54
cts 81
cp 0.6667

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getLastCaptchaId() 0 4 1
A setHttpClient() 0 6 1
A setVerbose() 0 7 1
A recognizeFile() 0 16 3
A recognize() 0 25 4
A sendCaptcha() 0 28 3
A getCaptchaResult() 0 21 4
A getErrorMessage() 0 6 2
A getHttpClient() 0 9 2
A getLogger() 0 10 3
1
<?php
2
/**
3
 * @author Dmitry Gladyshev <[email protected]>
4
 */
5
6
namespace Rucaptcha;
7
8
use GuzzleHttp\Psr7\Request;
9
use Psr\Http\Client\ClientInterface;
10
use GuzzleHttp\RequestOptions;
11
use Psr\Log\LoggerAwareInterface;
12
use Psr\Log\LoggerAwareTrait;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\NullLogger;
15
use Rucaptcha\Exception\ErrorResponseException;
16
use Rucaptcha\Exception\InvalidArgumentException;
17
use Rucaptcha\Exception\RuntimeException;
18
use SplFileObject;
19
use Throwable;
20
21
class GenericClient implements LoggerAwareInterface
22
{
23
    use LoggerAwareTrait;
24
    use ConfigurableTrait;
25
26
    /* Statuses */
27
    const STATUS_OK = 'OK';
28
    const STATUS_CAPTCHA_NOT_READY = 'CAPCHA_NOT_READY';
29
30
    /**
31
     * @var string
32
     */
33
    protected $lastCaptchaId = '';
34
35
    /**
36
     * @var string
37
     */
38
    protected $serverBaseUri = '';
39
40
    /**
41
     * @var bool
42
     */
43
    protected $verbose = false;
44
45
    /**
46
     * @var string
47
     */
48
    protected $apiKey = '';
49
50
    /**
51
     * @var int
52
     */
53
    protected $rTimeout = 5;
54
55
    /**
56
     * @var int
57
     */
58
    protected $mTimeout = 120;
59
60
    /**
61
     * @var ClientInterface
62
     */
63
    private $httpClient = null;
64
65
    /**
66
     * @param array $options
67
     * @param string $apiKey
68
     * @throws InvalidArgumentException
69
     */
70 25
    public function __construct($apiKey, array $options = [])
71
    {
72 25
        $this->apiKey = $apiKey;
73 25
        $this->setOptions($options);
74 25
    }
75
76
    /**
77
     * @return string   # Last successfully sent captcha task ID
78
     */
79 2
    public function getLastCaptchaId()
80
    {
81 2
        return $this->lastCaptchaId;
82
    }
83
84
    /**
85
     * @param ClientInterface $client
86
     * @return $this
87
     */
88 22
    public function setHttpClient(ClientInterface $client)
89
    {
90 22
        $this->httpClient = $client;
91
92 22
        return $this;
93
    }
94
95
    /**
96
     * @param bool $verbose
97
     * @return $this
98
     */
99
    public function setVerbose($verbose)
100
    {
101
        $this->logger = null;
102
        $this->verbose = $verbose;
103
104
        return $this;
105
    }
106
107
    /**
108
     * @param string $path
109
     * @param array $extra
110
     * @return string
111
     * @throws InvalidArgumentException
112
     * @throws RuntimeException
113
     * @throws \GuzzleHttp\Exception\GuzzleException
114
     */
115
    public function recognizeFile($path, array $extra = [])
116
    {
117
        if (!file_exists($path)) {
118
            throw new InvalidArgumentException("Captcha file `$path` not found.");
119
        }
120
121
        $file = new SplFileObject($path, 'r');
122
123
        $content = '';
124
125
        while (!$file->eof()) {
126
            $content .= $file->fgets();
127
        }
128
129
        return $this->recognize($content, $extra);
130
    }
131
132
    /**
133
     * @param string $content
134
     * @param array $extra
135
     * @return string
136
     * @throws RuntimeException
137
     * @throws \GuzzleHttp\Exception\GuzzleException
138
     */
139
    public function recognize($content, array $extra = [])
140
    {
141
        $captchaId = $this->sendCaptcha($content, $extra);
142
        $startTime = time();
143
144
        while (true) {
145
            $this->getLogger()->info("Waiting {$this->rTimeout} sec.");
146
147
            sleep($this->rTimeout);
148
149
            if (time() - $startTime >= $this->mTimeout) {
150
                throw new RuntimeException("Captcha waiting timeout.");
151
            }
152
153
            $result = $this->getCaptchaResult($captchaId);
154
155
            if ($result === false) {
156
                continue;
157
            }
158
159
            $this->getLogger()->info("Elapsed " . (time()-$startTime) . " second(s).");
160
161
            return $result;
162
        }
163
    }
164
165
    /**
166
     * @param string $content       # Captcha image content
167
     * @param array $extra          # Array of recognition options
168
     * @return string               # Captcha task ID
169
     * @throws Throwable
170
     */
171
    public function sendCaptcha($content, array $extra = [])
172
    {
173
        $this->getLogger()->info("Try send captcha image on {$this->serverBaseUri}/in.php");
174 13
175
        $headers = [
176 13
            'Content-Type' => 'application/x-www-form-urlencoded'
177
        ];
178 13
179 13
        $body = http_build_query(array_merge($extra, [
180
            'method' => 'base64',
181 13
            'key' => $this->apiKey,
182 13
            'body' => base64_encode($content)
183 13
        ]));
184 13
185 13
        $request = new Request('POST', '/in.php', $headers, $body);
186 13
187 13
        $response = $this->getHttpClient()->sendRequest($request);
188
189 13
        $responseText = $response->getBody()->getContents();
190
191 13
        if (mb_strpos($responseText, 'OK|') !== false) {
192 1
            $this->lastCaptchaId = explode("|", $responseText)[1];
193 1
            $this->getLogger()->info("Sending success. Got captcha id `{$this->lastCaptchaId}`.");
194 1
            return $this->lastCaptchaId;
195
        }
196
197 12
        throw new ErrorResponseException($this->getErrorMessage($responseText) ?: "Unknown error: `{$responseText}`.");
198
    }
199
200
    /**
201
     * @param string $captchaId     # Captcha task ID
202
     * @return string|false         # Solved captcha text or false if captcha is not ready
203
     * @throws Throwable
204
     */
205
    public function getCaptchaResult($captchaId)
206 9
    {
207
        $request = new \GuzzleHttp\Psr7\Request('GET', "/res.php?key={$this->apiKey}&action=get&id={$captchaId}");
208 9
209
        $response = $this
210 9
            ->getHttpClient()
211
            ->sendRequest($request);
212 9
213 1
        $responseText = $response->getBody()->getContents();
214
215
        if ($responseText === self::STATUS_CAPTCHA_NOT_READY) {
216 8
            return false;
217 1
        }
218 1
219
        if (mb_strpos($responseText, 'OK|') !== false) {
220
            $this->getLogger()->info("Got OK response: `{$responseText}`.");
221 7
            return html_entity_decode(trim(explode('|', $responseText)[1]));
222
        }
223
224
        throw new ErrorResponseException($this->getErrorMessage($responseText) ?: $responseText);
225
    }
226
227
    /**
228 19
     * @param string $responseText  # Server response text usually begin with `ERROR_` prefix
229
     * @return false|string         # Error message text or false if associated message in not found
230 19
     */
231 19
    protected function getErrorMessage($responseText)
232 19
    {
233
        return isset(Error::$messages[$responseText])
234
            ? Error::$messages[$responseText]
235
            : false;
236
    }
237
238 23
    /**
239
     * @return ClientInterface
240 23
     */
241 1
    protected function getHttpClient()
242 1
    {
243 1
        if ($this->httpClient === null) {
244 1
            $this->httpClient = new \GuzzleHttp\Client([
245 23
                'base_uri' => $this->serverBaseUri
246
            ]);
247
        }
248
        return $this->httpClient;
249
    }
250
251 15
    /**
252
     * @return LoggerInterface
253 15
     */
254 15
    public function getLogger()
255 15
    {
256 15
        if ($this->logger === null) {
257 15
            $defaultLogger = $this->verbose
258 15
                ? new Logger
259 15
                : new NullLogger;
260
            $this->setLogger($defaultLogger);
261
        }
262
        return $this->logger;
263
    }
264
}
265