Skip to content

Commit 276bf97

Browse files
committed
Add Altcha captcha support
- Add AltchaTask and AltchaTaskProxyless to CaptchaTypeEnm - Add Altcha captcha handler class (altcha.py) - Add test suite for Altcha (test_altcha.py)
1 parent c74210f commit 276bf97

File tree

3 files changed

+260
-0
lines changed

3 files changed

+260
-0
lines changed

src/python3_anticaptcha/altcha.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
from typing import Union, Optional
2+
3+
from .core.base import CaptchaParams
4+
from .core.enum import ProxyTypeEnm, CaptchaTypeEnm
5+
6+
__all__ = ("Altcha",)
7+
8+
9+
class Altcha(CaptchaParams):
10+
def __init__(
11+
self,
12+
api_key: str,
13+
captcha_type: Union[CaptchaTypeEnm, str],
14+
websiteURL: str,
15+
challengeURL: Optional[str] = None,
16+
challengeJSON: Optional[str] = None,
17+
proxyType: Optional[Union[ProxyTypeEnm, str]] = None,
18+
proxyAddress: Optional[str] = None,
19+
proxyPort: Optional[int] = None,
20+
proxyLogin: Optional[str] = None,
21+
proxyPassword: Optional[str] = None,
22+
userAgent: Optional[str] = None,
23+
sleep_time: Optional[int] = 10,
24+
):
25+
"""
26+
The class is used to work with Altcha.
27+
28+
Args:
29+
api_key: Capsolver API key
30+
captcha_type: Captcha type
31+
websiteURL: Address of the webpage
32+
challengeURL: Full URL to the challenge page (use either challengeURL or challengeJSON)
33+
challengeJSON: JSON-encoded challenge data string (use either challengeURL or challengeJSON)
34+
proxyType: Type of the proxy
35+
proxyAddress: Proxy IP address IPv4/IPv6. Not allowed to use:
36+
host names instead of IPs,
37+
transparent proxies (where client IP is visible),
38+
proxies from local networks (192.., 10.., 127...)
39+
proxyPort: Proxy port.
40+
proxyLogin: Proxy login.
41+
proxyPassword: Proxy password.
42+
userAgent: Browser UserAgent.
43+
sleep_time: The waiting time between requests to get the result of the Captcha
44+
45+
Examples:
46+
>>> Altcha(api_key="99d7d111a0111dc11184111c8bb111da",
47+
... captcha_type="AltchaTaskProxyless",
48+
... websiteURL="https://example.com/",
49+
... challengeURL="/api/challenge"
50+
... ).captcha_handler()
51+
{
52+
"errorId": 0,
53+
"errorCode": None,
54+
"errorDescription": None,
55+
"status":"ready",
56+
"solution":{
57+
"token":"0.Qz0.....f1",
58+
"userAgent":"Mozilla/.....36"
59+
},
60+
"cost": 0.002,
61+
"ip": "46.53.249.230",
62+
"createTime": 1679004358,
63+
"endTime": 1679004368,
64+
"solveCount": 0,
65+
"taskId": 396687629
66+
}
67+
68+
>>> await Altcha(api_key="99d7d111a0111dc11184111c8bb111da",
69+
... captcha_type="AltchaTaskProxyless",
70+
... websiteURL="https://example.com/",
71+
... challengeJSON='{"algorithm":"SHA-256","challenge":"..."}'
72+
... ).aio_captcha_handler()
73+
{
74+
"errorId": 0,
75+
"errorCode": None,
76+
"errorDescription": None,
77+
"status":"ready",
78+
"solution":{
79+
"token":"0.Qz0.....f1",
80+
"userAgent":"Mozilla/.....36"
81+
},
82+
"cost": 0.002,
83+
"ip": "46.53.249.230",
84+
"createTime": 1679004358,
85+
"endTime": 1679004368,
86+
"solveCount": 0,
87+
"taskId": 396687629
88+
}
89+
90+
>>> Altcha(api_key="99d7d111a0111dc11184111c8bb111da",
91+
... captcha_type="AltchaTask",
92+
... websiteURL="https://example.com/",
93+
... challengeURL="/api/challenge",
94+
... proxyType="http",
95+
... proxyAddress="1.2.3.4",
96+
... proxyPort=8080
97+
... ).captcha_handler()
98+
{
99+
"errorId": 0,
100+
"errorCode": None,
101+
"errorDescription": None,
102+
"status":"ready",
103+
"solution":{
104+
"token":"0.Qz0.....f1",
105+
"userAgent":"Mozilla/.....36"
106+
},
107+
"cost": 0.002,
108+
"ip": "46.53.249.230",
109+
"createTime": 1679004358,
110+
"endTime": 1679004368,
111+
"solveCount": 0,
112+
"taskId": 396687629
113+
}
114+
115+
Notes:
116+
https://anti-captcha.com/apidoc/task-types/AltchaTask
117+
118+
https://anti-captcha.com/apidoc/task-types/AltchaTaskProxyless
119+
"""
120+
121+
super().__init__(api_key=api_key, sleep_time=sleep_time)
122+
123+
# validation of the received parameters
124+
if captcha_type == CaptchaTypeEnm.AltchaTask:
125+
self.task_params = dict(
126+
type=captcha_type,
127+
websiteURL=websiteURL,
128+
challengeURL=challengeURL,
129+
challengeJSON=challengeJSON,
130+
proxyType=proxyType,
131+
proxyAddress=proxyAddress,
132+
proxyPort=proxyPort,
133+
proxyLogin=proxyLogin,
134+
proxyPassword=proxyPassword,
135+
userAgent=userAgent,
136+
)
137+
elif captcha_type == CaptchaTypeEnm.AltchaTaskProxyless:
138+
self.task_params = dict(
139+
type=captcha_type,
140+
websiteURL=websiteURL,
141+
challengeURL=challengeURL,
142+
challengeJSON=challengeJSON,
143+
)
144+
else:
145+
raise ValueError(
146+
f"Invalid `captcha_type` parameter set for `{self.__class__.__name__}`, \
147+
available - {CaptchaTypeEnm.AltchaTaskProxyless.value, CaptchaTypeEnm.AltchaTask.value}"
148+
)

src/python3_anticaptcha/core/enum.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class CaptchaTypeEnm(str, MyEnum):
6464
# AmazonWAF
6565
AmazonTask = "AmazonTask"
6666
AmazonTaskProxyless = "AmazonTaskProxyless"
67+
# Altcha
68+
AltchaTask = "AltchaTask"
69+
AltchaTaskProxyless = "AltchaTaskProxyless"
6770
# Custom
6871
AntiGateTask = "AntiGateTask"
6972

tests/test_altcha.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import pytest
2+
3+
from tests.conftest import BaseTest
4+
from python3_anticaptcha.core.enum import ProxyTypeEnm, CaptchaTypeEnm
5+
from python3_anticaptcha.altcha import Altcha
6+
from python3_anticaptcha.core.serializer import GetTaskResultResponseSer
7+
from python3_anticaptcha.core.context_instr import AIOContextManager, SIOContextManager
8+
9+
10+
class TestAltcha(BaseTest):
11+
websiteURL = "https://example.com/"
12+
challengeURL = "/api/challenge"
13+
14+
def test_sio_success(self):
15+
instance = Altcha(
16+
api_key=self.API_KEY,
17+
websiteURL=self.websiteURL,
18+
challengeURL=self.challengeURL,
19+
captcha_type=CaptchaTypeEnm.AltchaTaskProxyless,
20+
)
21+
result = instance.captcha_handler()
22+
23+
assert isinstance(result, dict)
24+
ser_result = GetTaskResultResponseSer(**result)
25+
assert ser_result.errorId == 0
26+
assert ser_result.taskId is not None
27+
assert ser_result.cost != 0.0
28+
29+
async def test_aio_success(self):
30+
instance = Altcha(
31+
api_key=self.API_KEY,
32+
websiteURL=self.websiteURL,
33+
challengeURL=self.challengeURL,
34+
captcha_type=CaptchaTypeEnm.AltchaTaskProxyless,
35+
)
36+
result = await instance.aio_captcha_handler()
37+
38+
assert isinstance(result, dict)
39+
ser_result = GetTaskResultResponseSer(**result)
40+
assert ser_result.errorId == 0
41+
assert ser_result.taskId is not None
42+
assert ser_result.cost != 0.0
43+
44+
def test_err_captcha_type(self):
45+
with pytest.raises(ValueError):
46+
Altcha(
47+
api_key=self.API_KEY,
48+
websiteURL=self.websiteURL,
49+
challengeURL=self.challengeURL,
50+
captcha_type=self.get_random_string(length=10),
51+
)
52+
53+
@pytest.mark.parametrize("proxyType", ProxyTypeEnm)
54+
def test_proxy_args(self, proxyType: ProxyTypeEnm):
55+
proxy_args = self.get_proxy_args()
56+
proxy_args.update({"proxyType": proxyType})
57+
instance = Altcha(
58+
api_key=self.API_KEY,
59+
websiteURL=self.websiteURL,
60+
challengeURL=self.challengeURL,
61+
captcha_type=CaptchaTypeEnm.AltchaTask,
62+
**proxy_args,
63+
)
64+
for key, value in proxy_args.items():
65+
assert instance.task_params[key] == value
66+
67+
def test_context(self, mocker):
68+
context_enter_spy = mocker.spy(SIOContextManager, "__enter__")
69+
context_exit_spy = mocker.spy(SIOContextManager, "__exit__")
70+
with Altcha(
71+
api_key=self.API_KEY,
72+
websiteURL=self.websiteURL,
73+
challengeURL=self.challengeURL,
74+
captcha_type=CaptchaTypeEnm.AltchaTaskProxyless,
75+
) as instance:
76+
assert context_enter_spy.call_count == 1
77+
assert context_exit_spy.call_count == 1
78+
79+
async def test_aio_context(self, mocker):
80+
context_enter_spy = mocker.spy(AIOContextManager, "__aenter__")
81+
context_exit_spy = mocker.spy(AIOContextManager, "__aexit__")
82+
async with Altcha(
83+
api_key=self.API_KEY,
84+
websiteURL=self.websiteURL,
85+
challengeURL=self.challengeURL,
86+
captcha_type=CaptchaTypeEnm.AltchaTaskProxyless,
87+
) as instance:
88+
assert context_enter_spy.call_count == 1
89+
assert context_exit_spy.call_count == 1
90+
91+
def test_err_context(self):
92+
with pytest.raises(ValueError):
93+
with Altcha(
94+
api_key=self.API_KEY,
95+
websiteURL=self.websiteURL,
96+
challengeURL=self.challengeURL,
97+
captcha_type=CaptchaTypeEnm.AltchaTaskProxyless,
98+
) as instance:
99+
raise ValueError("Test error")
100+
101+
async def test_err_aio_context(self):
102+
with pytest.raises(ValueError):
103+
async with Altcha(
104+
api_key=self.API_KEY,
105+
websiteURL=self.websiteURL,
106+
challengeURL=self.challengeURL,
107+
captcha_type=CaptchaTypeEnm.AltchaTaskProxyless,
108+
) as instance:
109+
raise ValueError("Test error")

0 commit comments

Comments
 (0)