Skip to content

Commit 5be7772

Browse files
authored
Merge pull request #18 from Matthew17-21/py-improvements
1.4.1
2 parents b8fbf82 + 8adc1ea commit 5be7772

10 files changed

+211
-143
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,16 @@ def main():
114114
- **[2Captcha](https://www.2captcha.com/)**
115115
- **[Anticaptcha](https://www.anti-captcha.com/)**
116116
- **[Capsolver](https://www.capsolver.com/)**
117+
- **[CaptchaAI](https://captchaai.com/)**
117118

118119
### Site-Specific Support:
119120
| Site | Site ID |Captcha Types Supported | Task Types Supported|
120121
| :-------------: |:-------------:| :-----:| :-----:|
121122
| Capmonster | captchatools.CapmonsterSite| Image captchas,<br/> Recaptcha V2,<br />Recaptcha V3,<br />HCaptcha | ImageToTextTask,<br/>NoCaptchaTask,<br/> NoCaptchaTaskProxyless,<br/> RecaptchaV3TaskProxyless,<br />HCaptchaTaskProxyless |
122123
| Anticaptcha | captchatools.AnticaptchaSite| Image captchas,<br/> Recaptcha V2,<br />Recaptcha V3,<br />HCaptcha | ImageToTextTask,<br/> RecaptchaV2Task<br/> RecaptchaV2TaskProxyless,<br />RecaptchaV3TaskProxyless,<br />HCaptchaTaskProxyless |
123124
| 2Captcha | captchatools.TwoCaptchaSite| Image captchas,<br/> Recaptcha V2,<br />Recaptcha V3,<br />HCaptcha | - |
125+
| Capsolver | captchatools.CapsolverSite| Image captchas,<br/> Recaptcha V2,<br />Recaptcha V3,<br />HCaptcha | - |
126+
| CaptchaAI | captchatools.CaptchaAISite| Image captchas,<br/> Recaptcha V2,<br />Recaptcha V3,<br />HCaptcha | - |
124127

125128

126129

@@ -145,14 +148,19 @@ from captchatools import new_harvester, exceptions as captchaExceptions,
145148
def main():
146149
try:
147150
harvester = new_harvester()
148-
token harvester.get_token()
151+
token = harvester.get_token()
149152
except captchaExceptions.NoHarvesterException:
150153
print("I need to set my captcha harvester!")
151154
```
152155

153156

154157

155158
# Changelog
159+
### 1.4.1
160+
##### What's new
161+
1. Added CaptchaAI
162+
2. Removed internal redundant code
163+
3. Fix creating a new harvester if pass in the ID
156164
### 1.3.0
157165
##### What's new
158166
1. Get Balance Support

captchatools/__init__.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@
1515
1 = Capmonster
1616
2 = Anticaptcha
1717
3 = 2captcha
18+
4 = Capsolver
19+
5 = CaptchaAI
1820
'''
19-
__version__ = "2.0.0"
21+
__version__ = "1.4.1"
2022
__author__ = "Matthew17-21"
2123
__license__ = "MIT"
2224

2325
from abc import ABC, abstractmethod
2426
from typing import Optional
2527
from . import exceptions as captchaExceptions
28+
from warnings import warn
2629
class Harvester(ABC):
2730
'''
2831
Represents a captcha harvester.
@@ -72,8 +75,11 @@ def new_harvester(**kwargs) -> Harvester:
7275
from .anticaptcha import Anticaptcha
7376
from .capmonster import Capmonster
7477
from .capsolver import Capsolver
78+
from .captchaai import CaptchaAI
7579

76-
site = kwargs.get("solving_site","").lower()
80+
site = kwargs.get("solving_site","")
81+
if isinstance(site, str):
82+
site = site.lower()
7783
if site == 1 or site == "capmonster":
7884
return Capmonster(**kwargs)
7985
elif site == 2 or site == "anticaptcha":
@@ -82,9 +88,12 @@ def new_harvester(**kwargs) -> Harvester:
8288
return Twocap(**kwargs)
8389
elif site == 4 or site == "capsolver":
8490
return Capsolver(**kwargs)
91+
elif site == 5 or site == "captchaai":
92+
return CaptchaAI(**kwargs)
8593
raise captchaExceptions.NoHarvesterException("No solving site selected")
8694

8795

8896
# Just for backward compatibility
8997
def captcha_harvesters(**kwargs) -> Harvester:
98+
warn('This function is deprecated. Use the `new_harvester()` function', DeprecationWarning, stacklevel=2)
9099
return new_harvester(**kwargs)

captchatools/anticaptcha.py

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from . import Harvester
2-
from . import exceptions as captchaExceptions
2+
from .errors import ErrCodeToException
33
import requests
44
from typing import Optional
55
import time
@@ -13,7 +13,7 @@ def get_balance(self) -> float:
1313
try:
1414
resp = requests.post("https://api.anti-captcha.com/getBalance", json=payload ,timeout=20).json()
1515
if resp["errorId"] == 1: # Means there was an error
16-
self.check_error(resp["errorCode"])
16+
ErrCodeToException("Anticaptcha", resp["errorCode"])
1717
return float(resp["balance"])
1818
except requests.RequestException:
1919
pass
@@ -86,7 +86,7 @@ def __get_id(self,**kwargs):
8686
try:
8787
resp = requests.post(BASEURL + "/createTask " , json=payload, timeout=20).json()
8888
if resp["errorId"] != 0: # Means there was an error:
89-
self.check_error(resp["errorCode"])
89+
ErrCodeToException("Anticaptcha", resp["errorCode"])
9090
return resp["taskId"]
9191
except (requests.RequestException, KeyError):
9292
pass
@@ -97,7 +97,7 @@ def __get_answer(self,task_id:int):
9797
try:
9898
response = requests.post(BASEURL + "/getTaskResult",json=payload,timeout=20,).json()
9999
if response["errorId"] != 0: # Means there was an error
100-
self.check_error(response["errorId"])
100+
ErrCodeToException("Anticaptcha", response["errorId"])
101101
if response["status"] == "processing":
102102
time.sleep(3)
103103
continue
@@ -106,26 +106,4 @@ def __get_answer(self,task_id:int):
106106
else:
107107
return response["solution"]["gRecaptchaResponse"]
108108
except (requests.RequestException, KeyError):
109-
pass
110-
111-
@staticmethod
112-
def check_error(error_code):
113-
if error_code == "ERROR_ZERO_BALANCE":
114-
raise captchaExceptions.NoBalanceException()
115-
elif error_code == "ERROR_WRONG_GOOGLEKEY":
116-
raise captchaExceptions.WrongSitekeyException()
117-
elif error_code == "ERROR_WRONG_USER_KEY" or error_code == "ERROR_KEY_DOES_NOT_EXIST":
118-
raise captchaExceptions.WrongAPIKeyException()
119-
elif error_code == "ERROR_TOO_BIG_CAPTCHA_FILESIZE":
120-
raise captchaExceptions.CaptchaIMGTooBig()
121-
elif error_code == "ERROR_PAGEURL":
122-
raise captchaExceptions.TaskDetails(f"Error: {error_code}")
123-
elif error_code == "MAX_USER_TURN" or error_code == "ERROR_NO_SLOT_AVAILABLE":
124-
raise captchaExceptions.NoSlotAvailable("No slot available")
125-
elif error_code == "ERROR_IP_NOT_ALLOWED" or error_code == "IP_BANNED" or error_code == "ERROR_IP_BLOCKED":
126-
return captchaExceptions.Banned(error_code)
127-
elif error_code == "ERROR_ZERO_CAPTCHA_FILESIZE" or error_code == "ERROR_UPLOAD" or \
128-
error_code == "ERROR_CAPTCHAIMAGE_BLOCKED" or error_code == "ERROR_IMAGE_TYPE_NOT_SUPPORTED" or \
129-
error_code == "ERROR_WRONG_FILE_EXTENSION":
130-
raise captchaExceptions.CaptchaImageError(error_code)
131-
else: raise captchaExceptions.UnknownError(f"Error returned from 2captcha: {error_code}")
109+
pass

captchatools/capmonster.py

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from . import exceptions as captchaExceptions
1+
from .errors import ErrCodeToException
22
from . import Harvester
33
import time
44
import requests
@@ -18,7 +18,7 @@ def get_balance(self) -> float:
1818
try:
1919
resp = requests.post(BASEURL + "/getBalance", json=payload ,timeout=20).json()
2020
if resp["errorId"] == 1: # Means there was an error
21-
self.check_error(resp["errorCode"])
21+
ErrCodeToException("Capmonster", resp["errorCode"])
2222
return float(resp["balance"])
2323
except requests.RequestException:
2424
pass
@@ -91,7 +91,7 @@ def __get_id(self,**kwargs):
9191
try:
9292
resp = requests.post(BASEURL + "/createTask" , json=payload, timeout=20).json()
9393
if resp["errorId"] != 0: # Means there was an error:
94-
self.check_error(resp["errorCode"])
94+
ErrCodeToException("Capmonster", resp["errorCode"])
9595
return resp["taskId"]
9696
except (requests.RequestException, KeyError):
9797
pass
@@ -102,7 +102,7 @@ def __get_answer(self,task_id:int):
102102
try:
103103
response = requests.post(BASEURL + "/getTaskResult",json=payload,timeout=20,).json()
104104
if response["errorId"] != 0: # Means there was an error
105-
self.check_error(response["errorCode"])
105+
ErrCodeToException("Capmonster", response["errorCode"])
106106
if response["status"] == "processing":
107107
time.sleep(3)
108108
continue
@@ -111,26 +111,4 @@ def __get_answer(self,task_id:int):
111111
else:
112112
return response["solution"]["gRecaptchaResponse"]
113113
except (requests.RequestException, KeyError):
114-
pass
115-
116-
@staticmethod
117-
def check_error(error_code):
118-
if error_code == "ERROR_ZERO_BALANCE":
119-
raise captchaExceptions.NoBalanceException()
120-
elif error_code == "ERROR_WRONG_GOOGLEKEY":
121-
raise captchaExceptions.WrongSitekeyException()
122-
elif error_code == "ERROR_WRONG_USER_KEY" or error_code == "ERROR_KEY_DOES_NOT_EXIST":
123-
raise captchaExceptions.WrongAPIKeyException()
124-
elif error_code == "ERROR_TOO_BIG_CAPTCHA_FILESIZE":
125-
raise captchaExceptions.CaptchaIMGTooBig()
126-
elif error_code == "ERROR_PAGEURL":
127-
raise captchaExceptions.TaskDetails(f"Error: {error_code}")
128-
elif error_code == "MAX_USER_TURN" or error_code == "ERROR_NO_SLOT_AVAILABLE":
129-
raise captchaExceptions.NoSlotAvailable("No slot available")
130-
elif error_code == "ERROR_IP_NOT_ALLOWED" or error_code == "IP_BANNED" or error_code == "ERROR_IP_BLOCKED":
131-
return captchaExceptions.Banned(error_code)
132-
elif error_code == "ERROR_ZERO_CAPTCHA_FILESIZE" or error_code == "ERROR_UPLOAD" or \
133-
error_code == "ERROR_CAPTCHAIMAGE_BLOCKED" or error_code == "ERROR_IMAGE_TYPE_NOT_SUPPORTED" or \
134-
error_code == "ERROR_WRONG_FILE_EXTENSION":
135-
raise captchaExceptions.CaptchaImageError(error_code)
136-
else: raise captchaExceptions.UnknownError(f"Error returned from anticaptcha: {error_code}")
114+
pass

captchatools/capsolver.py

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from . import exceptions as captchaExceptions
1+
from .errors import ErrCodeToException
22
from . import Harvester
33
import time
44
import requests
@@ -18,7 +18,7 @@ def get_balance(self) -> float:
1818
try:
1919
resp = requests.post(BASEURL + "/getBalance", json=payload ,timeout=20).json()
2020
if resp["errorId"] == 1: # Means there was an error
21-
self.check_error(resp["errorCode"])
21+
ErrCodeToException("Capsolver", resp["errorCode"])
2222
return float(resp["balance"])
2323
except requests.RequestException:
2424
pass
@@ -103,7 +103,7 @@ def __get_id(self,**kwargs):
103103
try:
104104
resp = requests.post(BASEURL + "/createTask" , json=payload, timeout=20).json()
105105
if resp["errorId"] != 0: # Means there was an error:
106-
self.check_error(resp["errorCode"])
106+
ErrCodeToException("Capsolver", resp["errorCode"])
107107

108108
# Check if there is an answer already available
109109
if resp["status"] == "ready":
@@ -122,7 +122,7 @@ def __get_answer(self,task_id:int):
122122
try:
123123
response = requests.post(BASEURL + "/getTaskResult",json=payload,timeout=20,).json()
124124
if response["errorId"] != 0: # Means there was an error
125-
self.check_error(response["errorDescription"])
125+
ErrCodeToException("Capsolver", response["errorDescription"])
126126
if response["status"] == "processing":
127127
time.sleep(3)
128128
continue
@@ -131,26 +131,4 @@ def __get_answer(self,task_id:int):
131131
else:
132132
return response["solution"]["gRecaptchaResponse"]
133133
except (requests.RequestException, KeyError):
134-
pass
135-
136-
@staticmethod
137-
def check_error(error_code):
138-
if error_code == "ERROR_ZERO_BALANCE":
139-
raise captchaExceptions.NoBalanceException()
140-
elif error_code == "ERROR_WRONG_GOOGLEKEY":
141-
raise captchaExceptions.WrongSitekeyException()
142-
elif error_code == "ERROR_WRONG_USER_KEY" or error_code == "ERROR_KEY_DOES_NOT_EXIST":
143-
raise captchaExceptions.WrongAPIKeyException()
144-
elif error_code == "ERROR_TOO_BIG_CAPTCHA_FILESIZE":
145-
raise captchaExceptions.CaptchaIMGTooBig()
146-
elif error_code == "ERROR_REQUIRED_FIELDS":
147-
raise captchaExceptions.TaskDetails(f"Error: {error_code}")
148-
elif error_code == "ERROR_SERVICE_UNAVALIABLE" or error_code == "ERROR_THREADS_MAXIMUM":
149-
raise captchaExceptions.NoSlotAvailable("No slot available")
150-
elif error_code == "ERROR_IP_NOT_ALLOWED" or error_code == "IP_BANNED" or error_code == "ERROR_IP_BLOCKED":
151-
return captchaExceptions.Banned(error_code)
152-
elif error_code == "ERROR_ZERO_CAPTCHA_FILESIZE" or error_code == "ERROR_UPLOAD" or \
153-
error_code == "ERROR_CAPTCHAIMAGE_BLOCKED" or error_code == "ERROR_IMAGE_TYPE_NOT_SUPPORTED" or \
154-
error_code == "ERROR_WRONG_FILE_EXTENSION":
155-
raise captchaExceptions.CaptchaImageError(error_code)
156-
else: raise captchaExceptions.UnknownError(f"Error returned from Capsovler: {error_code}")
134+
pass

captchatools/captchaai.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from . import Harvester
2+
import requests
3+
from .errors import ErrCodeToException
4+
from typing import Optional
5+
import time
6+
7+
BASEURL = "https://ocr.captchaai.com/in.php"
8+
9+
class CaptchaAI(Harvester):
10+
def get_balance(self) -> float:
11+
url = f"https://ocr.captchaai.com/res.php?key={self.api_key}&action=getbalance&json=1"
12+
for _ in range(5):
13+
try:
14+
resp = requests.get(url, timeout=20).json()
15+
if resp["status"] == 0: # Means there was an error
16+
ErrCodeToException("CaptchaAI",resp["request"])
17+
return float(resp["request"])
18+
except requests.RequestException:
19+
pass
20+
21+
def get_token(self, b64_img: Optional[str] = None, user_agent: Optional[str] = None, proxy: Optional[str] = None, proxy_type: Optional[str] = None):
22+
# Get ID
23+
task_id = self.__get_id(
24+
b64_img=b64_img,
25+
user_agent=user_agent,
26+
proxy=proxy,
27+
proxy_type=proxy_type
28+
)
29+
30+
# Get Answer
31+
return self.__get_answer(task_id)
32+
33+
def __create_uri_parms(self, **kwargs):
34+
'''Creats the URI params to be used for submitting captcha info'''
35+
params = {
36+
"key": self.api_key,
37+
"json": 1,
38+
"pageurl": self.captcha_url,
39+
}
40+
if self.captcha_type == "image" or self.captcha_type == "normal":
41+
params["method"] = "base64"
42+
params["body"] = kwargs.get("b64_img", "")
43+
elif self.captcha_type == "v2":
44+
params["method"] = "userrecaptcha"
45+
params["googlekey"] = self.sitekey
46+
if self.invisible_captcha:
47+
params["invisible"] = 1
48+
elif self.captcha_type == "v3":
49+
params["method"] = "userrecaptcha"
50+
params["version"] = "v3"
51+
params["googlekey"] = self.sitekey
52+
params["pageurl"] = self.captcha_url
53+
if self.action != "":
54+
params["action"] = self.action
55+
if self.min_score is not None:
56+
params["min_score"] = self.min_score
57+
elif self.captcha_type == "hcap" or self.captcha_type == "hcaptcha":
58+
params["method"] = "hcaptcha"
59+
params["sitekey"] = self.sitekey
60+
61+
# Add Global Data
62+
if kwargs.get("proxy", None) is not None:
63+
params["proxy"] = kwargs.get("proxy")
64+
pxy_type = kwargs.get("proxy_type", "http")
65+
params["proxytype"] = pxy_type
66+
if kwargs.get("user_agent", None) is not None:
67+
params["userAgent"] = kwargs.get("user_agent")
68+
return params
69+
70+
def __get_id(self,**kwargs):
71+
# Create Payload
72+
params = self.__create_uri_parms(**kwargs)
73+
74+
# Get token & return it
75+
for _ in range(50):
76+
try:
77+
resp = requests.get(BASEURL, params=params ,timeout=20).json()
78+
if resp["status"] == 0: # Means there was an error:
79+
ErrCodeToException("CaptchaAI",resp["request"])
80+
return resp["request"]
81+
except (requests.RequestException, KeyError):
82+
pass
83+
84+
def __get_answer(self,task_id:int):
85+
for _ in range(100):
86+
try:
87+
response = requests.get(f"https://ocr.captchaai.com/res.php?key={self.api_key}&action=get&id={task_id}&json=1",timeout=20,).json()
88+
if response["status"] == 0 and response["request"] != "CAPCHA_NOT_READY": # Error checking
89+
ErrCodeToException("CaptchaAI",response["request"])
90+
if response["status"] == 0 and response["request"] == "CAPCHA_NOT_READY":
91+
time.sleep(4)
92+
continue
93+
return response["request"] # Return the captcha token
94+
except (requests.RequestException, KeyError):
95+
pass
96+

captchatools/errors.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from . import exceptions as captchaExceptions
2+
3+
def ErrCodeToException(site: str, error_code:str):
4+
'''
5+
Converts a given error code from the solving site and raises an appropriate exception
6+
'''
7+
if error_code == "ERROR_ZERO_BALANCE":
8+
raise captchaExceptions.NoBalanceException()
9+
elif error_code == "ERROR_WRONG_GOOGLEKEY":
10+
raise captchaExceptions.WrongSitekeyException()
11+
elif error_code == "ERROR_WRONG_USER_KEY" or error_code == "ERROR_KEY_DOES_NOT_EXIST":
12+
raise captchaExceptions.WrongAPIKeyException()
13+
elif error_code == "ERROR_TOO_BIG_CAPTCHA_FILESIZE":
14+
raise captchaExceptions.CaptchaIMGTooBig()
15+
elif error_code == "ERROR_PAGEURL":
16+
raise captchaExceptions.TaskDetails(f"Error: {error_code}")
17+
elif error_code == "MAX_USER_TURN" or error_code == "ERROR_NO_SLOT_AVAILABLE":
18+
raise captchaExceptions.NoSlotAvailable("No slot available")
19+
elif error_code == "ERROR_IP_NOT_ALLOWED" or error_code == "IP_BANNED":
20+
return captchaExceptions.Banned(error_code)
21+
elif error_code == "ERROR_ZERO_CAPTCHA_FILESIZE" or error_code == "ERROR_UPLOAD" or \
22+
error_code == "ERROR_CAPTCHAIMAGE_BLOCKED" or error_code == "ERROR_IMAGE_TYPE_NOT_SUPPORTED" or \
23+
error_code == "ERROR_WRONG_FILE_EXTENSION":
24+
raise captchaExceptions.CaptchaImageError(error_code)
25+
else: raise captchaExceptions.UnknownError(f"Error returned from {site}: {error_code}")

0 commit comments

Comments
 (0)