Skip to content

Commit b8fbf82

Browse files
authored
Merge pull request #17 from Matthew17-21/go-captchaai
[+] Added CaptchaAI Support [+] Image Captcha Support [+] Recaptcha V2 & V3 Support [+] HCaptcha Support
2 parents de822ea + 38b7110 commit b8fbf82

File tree

7 files changed

+389
-11
lines changed

7 files changed

+389
-11
lines changed

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ CAPSOLVER_KEY=""
88
ANTICAPTCHA_KEY=""
99

1010
# API key for capmonster
11-
CAPMONSTER_KEY=""
11+
CAPMONSTER_KEY=""
12+
13+
# API key for captchaai
14+
CAPTCHAAI_KEY=""

captchatools-go/README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -261,18 +261,19 @@ func addtional_data() {
261261
- **[2Captcha](https://www.2captcha.com/)**
262262
- **[Anticaptcha](https://www.anti-captcha.com/)**
263263
- **[Capsolver](https://capsolver.com/)**
264+
- **[CaptchaAI](https://captchaai.com/)**
264265

265266
### Site-Specific Support:
266-
| Captcha Type |2Captcha | Anticaptcha | Capmonster| Capsolver |
267-
| :-------------: |:-------------:| :-----:| :-----:| :-----:|
268-
| Recaptcha V2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
269-
| Recaptcha V3 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
270-
| Hcaptcha | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
271-
| Image Captcha | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
272-
| Cloudflare Turnstile | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
273-
| Funcaptcha |:x: | :x: | :x: | :x: |
274-
| GeeTest |:x: | :x: | :x: | :x: |
275-
| Amazon WAF |:x: | :x: | :x: | :x: |
267+
| Captcha Type |2Captcha | Anticaptcha | Capmonster| Capsolver | CaptchaAI|
268+
| :-------------: |:-------------:| :-----:| :-----:| :-----:| :-----:|
269+
| Recaptcha V2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
270+
| Recaptcha V3 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
271+
| Hcaptcha | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
272+
| Image Captcha | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
273+
| Cloudflare Turnstile | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: |
274+
| Funcaptcha |:x: | :x: | :x: | :x: | :x: |
275+
| GeeTest |:x: | :x: | :x: | :x: | :x: |
276+
| Amazon WAF |:x: | :x: | :x: | :x: | :x: |
276277

277278

278279

captchatools-go/captchaai.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package captchatoolsgo
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"net/url"
11+
"strconv"
12+
"time"
13+
)
14+
15+
type CaptchaAi struct {
16+
*Config
17+
}
18+
19+
func (t CaptchaAi) GetToken(additional ...*AdditionalData) (*CaptchaAnswer, error) {
20+
return t.getCaptchaAnswer(context.Background(), additional...)
21+
}
22+
23+
func (t CaptchaAi) GetTokenWithContext(ctx context.Context, additional ...*AdditionalData) (*CaptchaAnswer, error) {
24+
return t.getCaptchaAnswer(ctx, additional...)
25+
}
26+
27+
func (t CaptchaAi) GetBalance() (float32, error) {
28+
return t.getBalance()
29+
}
30+
31+
// Method to get Queue ID from the API.
32+
func (t CaptchaAi) getID(data *AdditionalData) (string, error) {
33+
// Get Payload
34+
uri, err := t.createUrl(data)
35+
if err != nil {
36+
return "", err
37+
}
38+
39+
// Make request to get answer
40+
response := &struct {
41+
Status int `json:"status"`
42+
Request int `json:"request"`
43+
}{}
44+
for i := 0; i < 100; i++ {
45+
resp, err := http.Get(uri)
46+
if err != nil {
47+
time.Sleep(3 * time.Second)
48+
continue
49+
}
50+
body, _ := io.ReadAll(resp.Body)
51+
resp.Body.Close()
52+
json.Unmarshal(body, response)
53+
54+
// Parse the response
55+
if response.Status != 1 { // Means there was an error
56+
// Have to read the error into an interface
57+
temp := make(map[string]string)
58+
json.Unmarshal(body, &temp)
59+
return "", errCodeToError(temp["request"])
60+
}
61+
return strconv.Itoa(response.Request), nil
62+
}
63+
return "", ErrMaxAttempts
64+
}
65+
66+
// This method gets the captcha token from the Capmonster API
67+
func (t CaptchaAi) getCaptchaAnswer(ctx context.Context, additional ...*AdditionalData) (*CaptchaAnswer, error) {
68+
var data *AdditionalData = nil
69+
if len(additional) > 0 {
70+
data = additional[0]
71+
}
72+
73+
// Get Queue ID
74+
queueID, err := t.getID(data)
75+
if err != nil {
76+
return nil, err
77+
}
78+
79+
// Get Captcha Answer
80+
response := &twocaptchaResponse{}
81+
urlToAnswer := fmt.Sprintf(
82+
"https://ocr.captchaai.com/res.php?key=%v&action=get&id=%v&json=1",
83+
t.Api_key,
84+
queueID,
85+
)
86+
for i := 0; i < 100; i++ {
87+
req, _ := http.NewRequestWithContext(ctx, "GET", urlToAnswer, nil)
88+
resp, err := makeRequest(req)
89+
if err != nil {
90+
if errors.Is(err, context.DeadlineExceeded) {
91+
return nil, fmt.Errorf("getCaptchaAnswer error: %w", err)
92+
}
93+
time.Sleep(3 * time.Second)
94+
continue
95+
}
96+
97+
// Parse Response
98+
body, _ := io.ReadAll(resp.Body)
99+
resp.Body.Close()
100+
json.Unmarshal(body, response)
101+
102+
// Check for any errors
103+
if response.Status == 0 && response.Request != "CAPCHA_NOT_READY" {
104+
return nil, errCodeToError(response.Request)
105+
}
106+
107+
// Check if captcha is ready
108+
if response.Request == "CAPCHA_NOT_READY" {
109+
time.Sleep(3 * time.Second)
110+
continue
111+
}
112+
return newCaptchaAnswer(
113+
queueID,
114+
response.Request,
115+
t.Api_key,
116+
t.CaptchaType,
117+
CaptchaAiSite,
118+
"",
119+
), nil
120+
}
121+
return nil, ErrMaxAttempts
122+
}
123+
124+
func (t CaptchaAi) getBalance() (float32, error) {
125+
// Attempt to get the balance from the API
126+
// Max attempts is 5
127+
url := fmt.Sprintf("https://ocr.captchaai.com/res.php?key=%v&action=getbalance&json=1", t.Api_key)
128+
response := &twocaptchaResponse{}
129+
for i := 0; i < 5; i++ {
130+
resp, err := http.Get(url)
131+
if err != nil {
132+
time.Sleep(1 * time.Second)
133+
continue
134+
}
135+
136+
// Parse Response
137+
body, _ := io.ReadAll(resp.Body)
138+
resp.Body.Close()
139+
json.Unmarshal(body, response)
140+
if response.Status == 0 {
141+
return 0, errCodeToError(response.Request)
142+
}
143+
144+
// Convert to float32
145+
var balance float32
146+
value, err := strconv.ParseFloat(response.Request, 32)
147+
if err != nil {
148+
return 0, errors.New("unable to convert balance")
149+
}
150+
balance = float32(value)
151+
return balance, nil
152+
}
153+
return 0, ErrMaxAttempts
154+
}
155+
156+
/*
157+
createUrl creates the Uri needed to submit data to CaptchaAi
158+
159+
Possible errors that can be returned:
160+
1) ErrIncorrectCapType
161+
*/
162+
func (t CaptchaAi) createUrl(data *AdditionalData) (string, error) {
163+
164+
// Create base uri
165+
u, err := url.Parse("https://ocr.captchaai.com/in.php")
166+
if err != nil {
167+
return "", fmt.Errorf("createUrl error: %w", err)
168+
}
169+
170+
// Dynamically add queries
171+
query := u.Query()
172+
query.Add("key", t.Api_key)
173+
query.Add("json", "1")
174+
query.Add("pageurl", t.CaptchaURL)
175+
switch t.CaptchaType {
176+
case ImageCaptcha:
177+
query.Add("method", "base64")
178+
if data != nil && data.B64Img != "" {
179+
query.Add("body", data.B64Img)
180+
}
181+
case V2Captcha:
182+
query.Add("method", "userrecaptcha")
183+
query.Add("googlekey", t.Sitekey)
184+
if t.IsInvisibleCaptcha {
185+
query.Add("invisible", "1")
186+
}
187+
case V3Captcha:
188+
query.Add("method", "userrecaptcha")
189+
query.Add("version", "v3")
190+
query.Add("googlekey", t.Sitekey)
191+
if t.Action != "" {
192+
query.Add("action", t.Action)
193+
}
194+
if t.MinScore > 0 {
195+
query.Add("min_score", fmt.Sprintf("%v", t.MinScore))
196+
}
197+
case HCaptcha:
198+
query.Add("method", "hcaptcha")
199+
query.Add("sitekey", t.Sitekey)
200+
201+
case CFTurnstile:
202+
return "", ErrNotSupported
203+
default:
204+
return "", ErrIncorrectCapType
205+
}
206+
if data != nil && t.CaptchaType != ImageCaptcha {
207+
if data.UserAgent != "" {
208+
query.Add("userAgent", data.UserAgent)
209+
}
210+
if data.Proxy != nil {
211+
query.Add("proxy", data.Proxy.StringFormatted())
212+
}
213+
if data.ProxyType != "" {
214+
query.Add("proxytype", data.ProxyType)
215+
}
216+
}
217+
218+
u.RawQuery = query.Encode()
219+
return u.String(), nil
220+
}

0 commit comments

Comments
 (0)