| Current Path : /home/seto/indexator.pm/src/Search/ |
| Current File : /home/seto/indexator.pm/src/Search/DataForSeoProvider.php |
<?php
namespace App\Search;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
class DataForSeoProvider implements SearchProviderInterface
{
private Client $http;
public function __construct(private string $login, private string $password)
{
$this->http = new Client([
'base_uri' => 'https://api.dataforseo.com',
'timeout' => 45,
'connect_timeout' => 10,
'auth' => [$login, $password],
'headers' => ['Content-Type' => 'application/json'],
]);
}
public function getName(): string
{
return 'dataforseo';
}
public function buildCurlOptions(string $query): array
{
$body = json_encode([[
'keyword' => $query,
'location_code' => 2840,
'language_code' => 'en',
'device' => 'desktop',
'depth' => 10,
]]);
return [
CURLOPT_URL => 'https://api.dataforseo.com/v3/serp/google/organic/live/advanced',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Basic ' . base64_encode($this->login . ':' . $this->password),
],
CURLOPT_TIMEOUT => 45,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_RETURNTRANSFER => true,
];
}
public function parseResponse(string $body, int $httpCode): SearchResult
{
$rawExcerpt = substr($body, 0, 2000);
if ($httpCode === 429) {
return new SearchResult('rate_limit', 0, [], $rawExcerpt, 'Rate limit (HTTP 429)');
}
if ($httpCode < 200 || $httpCode >= 300) {
return new SearchResult('error', 0, [], $rawExcerpt, 'HTTP ' . $httpCode);
}
$data = json_decode($body, true);
if (!is_array($data)) {
return new SearchResult('error', 0, [], $rawExcerpt, 'Invalid JSON from DataForSEO');
}
$statusCode = $data['status_code'] ?? 0;
if ($statusCode !== 20000) {
$msg = $data['status_message'] ?? 'DataForSEO error';
if ($statusCode === 40201 || str_contains(strtolower($msg), 'limit')) {
return new SearchResult('rate_limit', 0, [], $rawExcerpt, $msg);
}
return new SearchResult('error', 0, [], $rawExcerpt, $msg);
}
$tasks = $data['tasks'] ?? [];
$organicUrls = [];
foreach ($tasks as $task) {
$taskStatus = $task['status_code'] ?? 0;
if (in_array($taskStatus, [40501, 40102], true)) {
return new SearchResult('ok', 0, [], $rawExcerpt);
}
if ($taskStatus !== 20000) {
$msg = $task['status_message'] ?? 'Task error';
return new SearchResult('error', 0, [], $rawExcerpt, $msg);
}
foreach ($task['result'] ?? [] as $result) {
foreach ($result['items'] ?? [] as $item) {
if (($item['type'] ?? '') === 'organic' && isset($item['url'])) {
$organicUrls[] = (string) $item['url'];
}
}
}
}
return new SearchResult('ok', count($organicUrls), $organicUrls, $rawExcerpt);
}
public function search(string $query): SearchResult
{
try {
$response = $this->http->post('/v3/serp/google/organic/live/advanced', [
'json' => [[
'keyword' => $query,
'location_code' => 2840,
'language_code' => 'en',
'device' => 'desktop',
'depth' => 10,
]],
]);
$body = (string) $response->getBody();
return $this->parseResponse($body, $response->getStatusCode());
} catch (ClientException $e) {
$body = (string) $e->getResponse()->getBody();
return $this->parseResponse($body, $e->getResponse()->getStatusCode());
} catch (GuzzleException $e) {
return new SearchResult('error', 0, [], '', 'Request failed: ' . $e->getMessage());
}
}
}