API Tutorials

Création d'un package PHP Composer pour CaptchaAI

PHP alimente une grande partie des backends d’automatisation Web. Un package Composer intègre l'API CaptchaAI dans une bibliothèque réutilisable - $client->solveRecaptchaV2($sitekey, $url) au lieu d'appels cURL bruts et d'analyse JSON manuelle dans chaque projet.

Structure du paquet

captchaai-php/
├── src/
│   ├── CaptchaAI.php        # Main client class
│   ├── Exception/
│   │   ├── CaptchaAIException.php
│   │   ├── SubmitException.php
│   │   ├── SolveException.php
│   │   └── TimeoutException.php
│   └── Enum/
│       └── Method.php
├── composer.json
└── README.md

Configuration du compositeur

{
    "name": "your-vendor/captchaai",
    "description": "PHP client library for CaptchaAI API",
    "type": "library",
    "license": "MIT",
    "require": {
        "php": ">=8.1",
        "guzzlehttp/guzzle": "^7.0"
    },
    "autoload": {
        "psr-4": {
            "CaptchaAI\\": "src/"
        }
    }
}

Classes d'exceptions

<?php
// src/Exception/CaptchaAIException.php
namespace CaptchaAI\Exception;

class CaptchaAIException extends \RuntimeException
{
    private ?string $errorCode;

    private const FATAL_CODES = [
        'ERROR_WRONG_USER_KEY',
        'ERROR_KEY_DOES_NOT_EXIST',
        'ERROR_ZERO_BALANCE',
        'ERROR_IP_NOT_ALLOWED',
    ];

    public function __construct(string $message, ?string $errorCode = null)
    {
        parent::__construct($message);
        $this->errorCode = $errorCode;
    }

    public function getErrorCode(): ?string
    {
        return $this->errorCode;
    }

    public function isFatal(): bool
    {
        return in_array($this->errorCode, self::FATAL_CODES, true);
    }
}
<?php
// src/Exception/SubmitException.php
namespace CaptchaAI\Exception;

class SubmitException extends CaptchaAIException
{
    public function __construct(string $code)
    {
        parent::__construct("Task submission failed: {$code}", $code);
    }
}
<?php
// src/Exception/SolveException.php
namespace CaptchaAI\Exception;

class SolveException extends CaptchaAIException
{
    public function __construct(string $code)
    {
        parent::__construct("Task solving failed: {$code}", $code);
    }
}
<?php
// src/Exception/TimeoutException.php
namespace CaptchaAI\Exception;

class TimeoutException extends CaptchaAIException
{
    private string $taskId;

    public function __construct(string $taskId, int $timeoutSeconds)
    {
        parent::__construct("Task {$taskId} timed out after {$timeoutSeconds}s");
        $this->taskId = $taskId;
    }

    public function getTaskId(): string
    {
        return $this->taskId;
    }
}

Client principal

<?php
// src/CaptchaAI.php
namespace CaptchaAI;

use GuzzleHttp\Client as HttpClient;
use CaptchaAI\Exception\SubmitException;
use CaptchaAI\Exception\SolveException;
use CaptchaAI\Exception\TimeoutException;

class CaptchaAI
{
    private const SUBMIT_URL = 'https://ocr.captchaai.com/in.php';
    private const RESULT_URL = 'https://ocr.captchaai.com/res.php';

    private string $apiKey;
    private HttpClient $http;
    private int $pollInterval;
    private int $timeout;

    public function __construct(
        string $apiKey,
        int $pollInterval = 5,
        int $timeout = 180,
        ?HttpClient $httpClient = null
    ) {
        $this->apiKey = $apiKey;
        $this->pollInterval = $pollInterval;
        $this->timeout = $timeout;
        $this->http = $httpClient ?? new HttpClient(['timeout' => 30]);
    }

    // --- Core methods ---

    private function submit(array $params): string
    {
        $params['key'] = $this->apiKey;
        $params['json'] = 1;

        $response = $this->http->post(self::SUBMIT_URL, [
            'form_params' => $params,
        ]);

        $result = json_decode($response->getBody()->getContents(), true);

        if (($result['status'] ?? 0) !== 1) {
            throw new SubmitException($result['request'] ?? 'unknown');
        }

        return $result['request']; // task ID
    }

    private function poll(string $taskId): string
    {
        $startTime = time();

        while (time() - $startTime < $this->timeout) {
            sleep($this->pollInterval);

            $response = $this->http->get(self::RESULT_URL, [
                'query' => [
                    'key' => $this->apiKey,
                    'action' => 'get',
                    'id' => $taskId,
                    'json' => 1,
                ],
            ]);

            $result = json_decode($response->getBody()->getContents(), true);

            if (($result['request'] ?? '') === 'CAPCHA_NOT_READY') {
                continue;
            }

            if (($result['status'] ?? 0) === 1) {
                return $result['request'];
            }

            throw new SolveException($result['request'] ?? 'unknown');
        }

        throw new TimeoutException($taskId, $this->timeout);
    }

    private function solve(array $params): string
    {
        $taskId = $this->submit($params);
        return $this->poll($taskId);
    }

    // --- Solver methods ---

    /**

     * Solve reCAPTCHA v2
     */
    public function solveRecaptchaV2(
        string $sitekey,
        string $pageurl,
        bool $invisible = false,
        ?string $cookies = null
    ): string {
        $params = [
            'method' => 'userrecaptcha',
            'googlekey' => $sitekey,
            'pageurl' => $pageurl,
        ];
        if ($invisible) $params['invisible'] = 1;
        if ($cookies) $params['cookies'] = $cookies;

        return $this->solve($params);
    }

    /**

     * Solve reCAPTCHA v3
     */
    public function solveRecaptchaV3(
        string $sitekey,
        string $pageurl,
        string $action = 'verify',
    ): string {
        return $this->solve([
            'method' => 'userrecaptcha',
            'version' => 'v3',
            'googlekey' => $sitekey,
            'pageurl' => $pageurl,
            'action' => $action,
        ]);
    }

    /**

     * Solve Cloudflare Turnstile
     */
    public function solveTurnstile(
        string $sitekey,
        string $pageurl,
        ?string $action = null,
        ?string $cdata = null
    ): string {
        $params = [
            'method' => 'turnstile',
            'sitekey' => $sitekey,
            'pageurl' => $pageurl,
        ];
        if ($action) $params['action'] = $action;
        if ($cdata) $params['data'] = $cdata;

        return $this->solve($params);
    }

    /**

     * Solve hCaptcha
     */
    public function solveHCaptcha(string $sitekey, string $pageurl): string
    {
        return $this->solve([
            'method' => 'hcaptcha',
            'sitekey' => $sitekey,
            'pageurl' => $pageurl,
        ]);
    }

    /**

     * Solve image/text CAPTCHA from base64
     */
    public function solveImage(
        string $base64Image,
        bool $caseSensitive = false,
        ?int $minLength = null,
        ?int $maxLength = null
    ): string {
        $params = [
            'method' => 'base64',
            'body' => $base64Image,
        ];
        if ($caseSensitive) $params['regsense'] = 1;
        if ($minLength !== null) $params['min_len'] = $minLength;
        if ($maxLength !== null) $params['max_len'] = $maxLength;

        return $this->solve($params);
    }

    /**

     * Solve GeeTest v3
     */
    public function solveGeeTestV3(
        string $gt,
        string $challenge,
        string $pageurl
    ): string {
        return $this->solve([
            'method' => 'geetest',
            'gt' => $gt,
            'challenge' => $challenge,
            'pageurl' => $pageurl,
        ]);
    }

    // --- Utility methods ---

    /**

     * Get current account balance
     */
    public function getBalance(): float
    {
        $response = $this->http->get(self::RESULT_URL, [
            'query' => [
                'key' => $this->apiKey,
                'action' => 'getbalance',
                'json' => 1,
            ],
        ]);

        $result = json_decode($response->getBody()->getContents(), true);
        return (float) ($result['request'] ?? 0);
    }

    /**

     * Report a bad solution
     */
    public function reportBad(string $taskId): bool
    {
        $response = $this->http->get(self::RESULT_URL, [
            'query' => [
                'key' => $this->apiKey,
                'action' => 'reportbad',
                'id' => $taskId,
                'json' => 1,
            ],
        ]);

        $result = json_decode($response->getBody()->getContents(), true);
        return ($result['status'] ?? 0) === 1;
    }
}

Exemples d'utilisation

<?php
require_once 'vendor/autoload.php';

use CaptchaAI\CaptchaAI;
use CaptchaAI\Exception\SubmitException;
use CaptchaAI\Exception\TimeoutException;

$client = new CaptchaAI(
    apiKey: 'YOUR_API_KEY',
    pollInterval: 5,
    timeout: 120
);

// Check balance
$balance = $client->getBalance();
echo "Balance: \${$balance}\n";

// Solve reCAPTCHA v2
try {
    $token = $client->solveRecaptchaV2(
        sitekey: '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
        pageurl: 'https://example.com/login'
    );
    echo "Token: " . substr($token, 0, 40) . "...\n";
} catch (TimeoutException $e) {
    echo "Timed out: {$e->getMessage()}\n";
} catch (SubmitException $e) {
    if ($e->isFatal()) {
        echo "Fatal: {$e->getErrorCode()}\n";
        exit(1);
    }
    echo "Retryable: {$e->getErrorCode()}\n";
}

// Solve Turnstile
$turnstileToken = $client->solveTurnstile(
    sitekey: '0x4AAAAAAADnPIDROrmt1Wwj',
    pageurl: 'https://example.com/checkout'
);

// Solve image CAPTCHA
$imageBase64 = base64_encode(file_get_contents('captcha.png'));
$text = $client->solveImage($imageBase64, caseSensitive: true);
echo "Text: {$text}\n";

Dépannage

Problème Parce que Corriger
SubmitException: ERROR_WRONG_USER_KEY Clé API invalide Vérifier la clé depuis le tableau de bord
TimeoutException fréquemment Délai d'attente trop court Augmentez $timeout à 180+
Class not found Chargeur automatique non configuré Exécutez composer dump-autoload
Erreur de connexion Guzzle Problème de réseau ou pare-feu Le serveur de vérification peut atteindre ocr.captchaai.com
json_decode renvoie null Corps de réponse invalide Vérifiez l'URL de l'API ; enregistrer la réponse brute pour le débogage

FAQ

Pourquoi Guzzle au lieu de cURL natif ?

Guzzle fournit des interfaces de messages PSR-7, une gestion automatique de JSON, un regroupement de connexions et une prise en charge des middlewares. Pour les projets utilisant déjà Guzzle, le SDK s'intègre sans ajouter de dépendances. Vous pouvez remplacer n'importe quel client compatible PSR-18.

Comment puis-je l’utiliser dans Laravel ?

Enregistrez le client en tant que singleton chez un fournisseur de services. Liez CaptchaAI::class avec la clé API de config/services.php. Injectez-le via une injection de constructeur dans des contrôleurs ou des tâches.

Dois-je publier sur Packagist ?

Pour un usage interne, référencez via repositories dans composer.json pointant vers votre dépôt Git. Pour une distribution publique, soumettez-le à Packagist avec la version appropriée et un README.

Articles connexes

Prochaines étapes

Créez votre package PHP CAPTCHA -récupérez votre clé API CaptchaAIet créez une bibliothèque Composer.

Guides associés :

Les commentaires sont désactivés pour cet article.