|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Solve GeeTest v4 using the CaptchaAI API. |
| 3 | +# |
| 4 | +# Usage: |
| 5 | +# chmod +x solve.sh |
| 6 | +# bash solve.sh |
| 7 | + |
| 8 | +set -euo pipefail |
| 9 | + |
| 10 | +SUBMIT_URL="https://ocr.captchaai.com/in.php" |
| 11 | +RESULT_URL="https://ocr.captchaai.com/res.php" |
| 12 | + |
| 13 | +# Load .env |
| 14 | +ENV_FILE="$(dirname "$0")/../.env" |
| 15 | +if [[ -f "$ENV_FILE" ]]; then |
| 16 | + set -a |
| 17 | + # shellcheck disable=SC1090 |
| 18 | + source "$ENV_FILE" |
| 19 | + set +a |
| 20 | +fi |
| 21 | + |
| 22 | +API_KEY="${CAPTCHAAI_API_KEY:-}" |
| 23 | +GT="${CAPTCHA_GT:-}" |
| 24 | +PAGEURL="${CAPTCHA_PAGEURL:-}" |
| 25 | +POLL_INTERVAL="${POLL_INTERVAL:-5}" |
| 26 | +MAX_TIMEOUT="${MAX_TIMEOUT:-120}" |
| 27 | + |
| 28 | +# Validate |
| 29 | +if [[ -z "$API_KEY" || "$API_KEY" == "YOUR_API_KEY" ]]; then |
| 30 | + echo "[!] ERROR: CAPTCHAAI_API_KEY is not set." |
| 31 | + echo " Copy .env.example to .env and add your real API key." |
| 32 | + exit 1 |
| 33 | +fi |
| 34 | +if [[ -z "$GT" || "$GT" == "YOUR_CAPTCHA_ID" ]]; then |
| 35 | + echo "[!] ERROR: CAPTCHA_GT is not set." |
| 36 | + echo " Find the captcha_id in the page source or GeeTest v4 initialization call." |
| 37 | + exit 1 |
| 38 | +fi |
| 39 | +if [[ -z "$PAGEURL" || "$PAGEURL" == "https://example.com" ]]; then |
| 40 | + echo "[!] WARNING: CAPTCHA_PAGEURL may not be set correctly." |
| 41 | + echo " Make sure it points to the actual target page." |
| 42 | +fi |
| 43 | + |
| 44 | +# JSON helpers |
| 45 | +json_str() { |
| 46 | + local json="$1" key="$2" |
| 47 | + echo "$json" | grep -oP "\"${key}\":\s*\"\\K[^\"]*" || echo "" |
| 48 | +} |
| 49 | +json_int() { |
| 50 | + local json="$1" key="$2" |
| 51 | + echo "$json" | grep -oP "\"${key}\":\s*\\K[0-9]+" || echo "0" |
| 52 | +} |
| 53 | + |
| 54 | +# Error handler |
| 55 | +handle_error() { |
| 56 | + local error="$1" |
| 57 | + case "$error" in |
| 58 | + ERROR_WRONG_USER_KEY|ERROR_KEY_DOES_NOT_EXIST|IP_BANNED) |
| 59 | + echo "[!] Authentication error: $error" |
| 60 | + echo " Check your API key at https://captchaai.com/dashboard" |
| 61 | + ;; |
| 62 | + ERROR_ZERO_BALANCE) |
| 63 | + echo "[!] Balance error: $error" |
| 64 | + echo " Top up your account at https://captchaai.com" |
| 65 | + ;; |
| 66 | + ERROR_PAGEURL|ERROR_WRONG_GOOGLEKEY|ERROR_BAD_PARAMETERS|ERROR_BAD_TOKEN_OR_PAGEURL) |
| 67 | + echo "[!] Input error: $error" |
| 68 | + echo " Verify your gt (captcha_id) and page URL are correct." |
| 69 | + ;; |
| 70 | + ERROR_BAD_PROXY|ERROR_PROXY_CONNECTION_FAILED) |
| 71 | + echo "[!] Proxy error: $error" |
| 72 | + echo " Check your proxy configuration or try a different proxy." |
| 73 | + ;; |
| 74 | + *) |
| 75 | + echo "[!] Submission failed: $error" |
| 76 | + ;; |
| 77 | + esac |
| 78 | +} |
| 79 | + |
| 80 | +# Submit task |
| 81 | +echo "[*] Submitting GeeTest v4 task..." |
| 82 | +ENCODED_KEY=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$API_KEY'))" 2>/dev/null || echo "$API_KEY") |
| 83 | +ENCODED_GT=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$GT'))" 2>/dev/null || echo "$GT") |
| 84 | +ENCODED_PAGEURL=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAGEURL'))" 2>/dev/null || echo "$PAGEURL") |
| 85 | + |
| 86 | +SUBMIT_QUERY="key=${ENCODED_KEY}&method=geetest>=${ENCODED_GT}&pageurl=${ENCODED_PAGEURL}&version=4&json=1" |
| 87 | + |
| 88 | +SUBMIT_RESP=$(curl -sS --max-time 30 "${SUBMIT_URL}?${SUBMIT_QUERY}") |
| 89 | + |
| 90 | +if [[ -z "$SUBMIT_RESP" ]]; then |
| 91 | + echo "[!] Network error during submission." |
| 92 | + exit 1 |
| 93 | +fi |
| 94 | + |
| 95 | +SUBMIT_STATUS=$(json_int "$SUBMIT_RESP" "status") |
| 96 | +SUBMIT_REQUEST=$(json_str "$SUBMIT_RESP" "request") |
| 97 | + |
| 98 | +if [[ "$SUBMIT_STATUS" -ne 1 ]]; then |
| 99 | + handle_error "$SUBMIT_REQUEST" |
| 100 | + exit 1 |
| 101 | +fi |
| 102 | + |
| 103 | +TASK_ID="$SUBMIT_REQUEST" |
| 104 | +echo "[+] Task submitted. ID: $TASK_ID" |
| 105 | + |
| 106 | +# Poll for result |
| 107 | +echo "[*] Waiting 15s before first poll..." |
| 108 | +sleep 15 |
| 109 | + |
| 110 | +ELAPSED=15 |
| 111 | +ATTEMPT=0 |
| 112 | +BACKOFF="$POLL_INTERVAL" |
| 113 | + |
| 114 | +while [[ "$ELAPSED" -lt "$MAX_TIMEOUT" ]]; do |
| 115 | + ATTEMPT=$((ATTEMPT + 1)) |
| 116 | + echo "[*] Polling for result (attempt $ATTEMPT)..." |
| 117 | + |
| 118 | + POLL_RESP=$(curl -sS --max-time 30 \ |
| 119 | + "${RESULT_URL}?key=${ENCODED_KEY}&action=get&id=${TASK_ID}&json=1" 2>/dev/null || echo "") |
| 120 | + |
| 121 | + if [[ -z "$POLL_RESP" ]]; then |
| 122 | + echo "[!] Network error during polling." |
| 123 | + sleep "$BACKOFF" |
| 124 | + ELAPSED=$((ELAPSED + BACKOFF)) |
| 125 | + BACKOFF=$((BACKOFF * 2)) |
| 126 | + [[ "$BACKOFF" -gt 30 ]] && BACKOFF=30 |
| 127 | + continue |
| 128 | + fi |
| 129 | + |
| 130 | + STATUS=$(json_int "$POLL_RESP" "status") |
| 131 | + |
| 132 | + if [[ "$STATUS" -eq 1 ]]; then |
| 133 | + # Extract the 5 GeeTest v4 values from the response |
| 134 | + CAPTCHA_ID_VAL=$(echo "$POLL_RESP" | grep -oP '"captcha_id"\s*:\s*"\\K[^"]*' || echo "") |
| 135 | + LOT_NUMBER=$(echo "$POLL_RESP" | grep -oP '"lot_number"\s*:\s*"\\K[^"]*' || echo "") |
| 136 | + PASS_TOKEN=$(echo "$POLL_RESP" | grep -oP '"pass_token"\s*:\s*"\\K[^"]*' || echo "") |
| 137 | + GEN_TIME=$(echo "$POLL_RESP" | grep -oP '"gen_time"\s*:\s*"\\K[^"]*' || echo "") |
| 138 | + CAPTCHA_OUTPUT=$(echo "$POLL_RESP" | grep -oP '"captcha_output"\s*:\s*"\\K[^"]*' || echo "") |
| 139 | + |
| 140 | + # If values are empty, the request might be a JSON string — try extracting from unescaped content |
| 141 | + if [[ -z "$CAPTCHA_ID_VAL" ]]; then |
| 142 | + REQUEST_VALUE=$(json_str "$POLL_RESP" "request") |
| 143 | + # Unescape the JSON string and extract values |
| 144 | + UNESCAPED=$(echo "$REQUEST_VALUE" | sed 's/\\"/"/g; s/\\\\/\\/g') |
| 145 | + CAPTCHA_ID_VAL=$(echo "$UNESCAPED" | grep -oP '"captcha_id"\s*:\s*"\\K[^"]*' || echo "") |
| 146 | + LOT_NUMBER=$(echo "$UNESCAPED" | grep -oP '"lot_number"\s*:\s*"\\K[^"]*' || echo "") |
| 147 | + PASS_TOKEN=$(echo "$UNESCAPED" | grep -oP '"pass_token"\s*:\s*"\\K[^"]*' || echo "") |
| 148 | + GEN_TIME=$(echo "$UNESCAPED" | grep -oP '"gen_time"\s*:\s*"\\K[^"]*' || echo "") |
| 149 | + CAPTCHA_OUTPUT=$(echo "$UNESCAPED" | grep -oP '"captcha_output"\s*:\s*"\\K[^"]*' || echo "") |
| 150 | + fi |
| 151 | + |
| 152 | + echo "[+] Solved!" |
| 153 | + echo "[+] captcha_id: $CAPTCHA_ID_VAL" |
| 154 | + echo "[+] lot_number: $LOT_NUMBER" |
| 155 | + echo "[+] pass_token: $PASS_TOKEN" |
| 156 | + echo "[+] gen_time: $GEN_TIME" |
| 157 | + echo "[+] captcha_output: $CAPTCHA_OUTPUT" |
| 158 | + echo |
| 159 | + echo "Next step: submit these five values to the site's" |
| 160 | + echo "GeeTest v4 verification endpoint." |
| 161 | + exit 0 |
| 162 | + fi |
| 163 | + |
| 164 | + REQUEST=$(json_str "$POLL_RESP" "request") |
| 165 | + |
| 166 | + if [[ "$REQUEST" == "CAPCHA_NOT_READY" ]]; then |
| 167 | + echo "[*] Not ready yet, waiting ${POLL_INTERVAL}s..." |
| 168 | + sleep "$POLL_INTERVAL" |
| 169 | + ELAPSED=$((ELAPSED + POLL_INTERVAL)) |
| 170 | + BACKOFF="$POLL_INTERVAL" |
| 171 | + continue |
| 172 | + fi |
| 173 | + |
| 174 | + case "$REQUEST" in |
| 175 | + ERROR_SERVER_ERROR|ERROR_INTERNAL_SERVER_ERROR) |
| 176 | + echo "[!] Transient error: $REQUEST, retrying in ${BACKOFF}s..." |
| 177 | + sleep "$BACKOFF" |
| 178 | + ELAPSED=$((ELAPSED + BACKOFF)) |
| 179 | + BACKOFF=$((BACKOFF * 2)) |
| 180 | + [[ "$BACKOFF" -gt 30 ]] && BACKOFF=30 |
| 181 | + continue |
| 182 | + ;; |
| 183 | + ERROR_CAPTCHA_UNSOLVABLE) |
| 184 | + echo "[!] Solve error: $REQUEST" |
| 185 | + echo " The CAPTCHA could not be solved. Verify parameters and retry." |
| 186 | + exit 1 |
| 187 | + ;; |
| 188 | + esac |
| 189 | + |
| 190 | + echo "[!] Unexpected error: $REQUEST" |
| 191 | + exit 1 |
| 192 | +done |
| 193 | + |
| 194 | +echo "[!] Timeout: no solution received within ${MAX_TIMEOUT} seconds." |
| 195 | +exit 1 |
0 commit comments