Your IP : 216.73.217.78


Current Path : /home/seto/indexator.pm/src/Search/
Upload File :
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());
        }
    }
}