pynocaptcha 3.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pynocaptcha/__init__.py +63 -0
- pynocaptcha/akamai.py +45 -0
- pynocaptcha/aws.py +35 -0
- pynocaptcha/base.py +262 -0
- pynocaptcha/cloudflare.py +42 -0
- pynocaptcha/datadome.py +93 -0
- pynocaptcha/discord.py +33 -0
- pynocaptcha/hcaptcha.py +80 -0
- pynocaptcha/incapsula.py +86 -0
- pynocaptcha/kasada.py +102 -0
- pynocaptcha/perimeterx.py +154 -0
- pynocaptcha/recaptcha.py +124 -0
- pynocaptcha/shape.py +118 -0
- pynocaptcha/tls.py +49 -0
- pynocaptcha/unlocker.py +1751 -0
- pynocaptcha-3.0.7.dist-info/METADATA +39 -0
- pynocaptcha-3.0.7.dist-info/RECORD +20 -0
- pynocaptcha-3.0.7.dist-info/WHEEL +5 -0
- pynocaptcha-3.0.7.dist-info/licenses/LICENSE +21 -0
- pynocaptcha-3.0.7.dist-info/top_level.txt +1 -0
pynocaptcha/__init__.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from .akamai import AkamaiV2Cracker
|
|
5
|
+
from .aws import AwsUniversalCracker
|
|
6
|
+
from .cloudflare import CloudFlareCracker
|
|
7
|
+
from .discord import DiscordCracker
|
|
8
|
+
from .hcaptcha import HcaptchaCracker
|
|
9
|
+
from .incapsula import (IncapsulaRbzidCracker,
|
|
10
|
+
IncapsulaReese84Cracker,
|
|
11
|
+
IncapsulaUtmvcCracker)
|
|
12
|
+
from .recaptcha import (ReCaptchaAppCracker,
|
|
13
|
+
ReCaptchaEnterpriseCracker,
|
|
14
|
+
ReCaptchaSteamCracker,
|
|
15
|
+
ReCaptchaUniversalCracker)
|
|
16
|
+
from .datadome import crack_datadome, async_crack_datadome
|
|
17
|
+
from .kasada import KasadaCdCracker, crack_kasada, async_crack_kasada
|
|
18
|
+
from .perimeterx import PerimeterxCracker, crack_perimeterx, async_crack_perimeterx
|
|
19
|
+
from .shape import crack_shape_v1, async_crack_shape_v1, crack_shape_v2, async_crack_shape_v2
|
|
20
|
+
from .tls import TlsV1Cracker
|
|
21
|
+
|
|
22
|
+
from .unlocker import Unlocker, AsyncUnlocker, run_unlocker, async_run_unlocker
|
|
23
|
+
from .unlocker import KohlCheckBalanceParams, LululemonCheckBalanceParams, WbiParams, DicksCheckBalanceParams
|
|
24
|
+
from .unlocker import FinishlineCheckBalanceParams, FootlockerCheckBalanceParams, ReiCheckBalanceParams
|
|
25
|
+
from .unlocker import One4AllCheckBalanceParams, SaksCheckBalanceParams, CoachCheckBalanceParams
|
|
26
|
+
from .unlocker import FandangoCheckBalanceParams, ColumbiaCheckBalanceParams, HomeDepotCheckBalanceParams
|
|
27
|
+
from .unlocker import EtsyRegisterParams, PrizepicksLoginParams, FifaLoginParams
|
|
28
|
+
from .unlocker import AuspostTrackParams, NorseSearchFlightsParams, ScootBookingParams
|
|
29
|
+
from .unlocker import MyPrepaidValidateParams, PBandaiCartParams, MicrosoftCreateParams
|
|
30
|
+
from .unlocker import GodaddyLoginParams, UltimateDiningLoginParams, FlightInfo, PassengerInfo
|
|
31
|
+
from .unlocker import AjetAvailabilityParams, AttCheckParams, EasyjetFindParams, AjetPnrLoginParams
|
|
32
|
+
from .unlocker import EurowingsBookingParams, FastPeopleSearchParams, HiltonTokenParams, AjetGetBookingDetailParams
|
|
33
|
+
from .unlocker import HiltonCustomerParams, KoreanAirLoginParams, MaerskTokenParams, MarriottLoginParams
|
|
34
|
+
from .unlocker import MarriottEnrollParams, PokemonLoginParams, PokemonLotteryListParams, PokemonApplyLotteryParams
|
|
35
|
+
from .unlocker import SephoraCheckBalanceParams, TextnowLoginParams, WalmartCheckBalanceParams, WalmartCaCheckBalanceParams
|
|
36
|
+
from .unlocker import WalmartWalletParams, FlightSegment, WizzairSearchParams, HmCheckBalanceParams
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
'pynocaptcha', 'magneto',
|
|
41
|
+
'CloudFlareCracker', 'IncapsulaReese84Cracker', 'IncapsulaUtmvcCracker', 'IncapsulaRbzidCracker', 'HcaptchaCracker',
|
|
42
|
+
'AkamaiV2Cracker', 'ReCaptchaUniversalCracker', 'ReCaptchaEnterpriseCracker', 'ReCaptchaSteamCracker',
|
|
43
|
+
'TlsV1Cracker', 'DiscordCracker', 'ReCaptchaAppCracker', 'AwsUniversalCracker', 'PerimeterxCracker', 'KasadaCdCracker',
|
|
44
|
+
'crack_datadome', 'async_crack_datadome',
|
|
45
|
+
'crack_kasada', 'async_crack_kasada', 'crack_perimeterx', 'async_crack_perimeterx',
|
|
46
|
+
'crack_shape_v1', 'async_crack_shape_v1', 'crack_shape_v2', 'async_crack_shape_v2',
|
|
47
|
+
|
|
48
|
+
'Unlocker', 'AsyncUnlocker', 'run_unlocker', 'async_run_unlocker',
|
|
49
|
+
'KohlCheckBalanceParams', 'LululemonCheckBalanceParams', 'WbiParams', 'DicksCheckBalanceParams',
|
|
50
|
+
'FinishlineCheckBalanceParams', 'FootlockerCheckBalanceParams', 'ReiCheckBalanceParams',
|
|
51
|
+
'One4AllCheckBalanceParams', 'SaksCheckBalanceParams', 'CoachCheckBalanceParams',
|
|
52
|
+
'FandangoCheckBalanceParams', 'ColumbiaCheckBalanceParams', 'HomeDepotCheckBalanceParams',
|
|
53
|
+
'EtsyRegisterParams', 'PrizepicksLoginParams', 'FifaLoginParams', 'AuspostTrackParams',
|
|
54
|
+
'NorseSearchFlightsParams', 'ScootBookingParams', 'MyPrepaidValidateParams', 'PBandaiCartParams',
|
|
55
|
+
'MicrosoftCreateParams', 'GodaddyLoginParams', 'UltimateDiningLoginParams', 'FlightInfo',
|
|
56
|
+
'PassengerInfo', 'AjetAvailabilityParams', 'AttCheckParams', 'EasyjetFindParams',
|
|
57
|
+
'EurowingsBookingParams', 'FastPeopleSearchParams', 'HiltonTokenParams', 'HiltonCustomerParams',
|
|
58
|
+
'KoreanAirLoginParams', 'MaerskTokenParams', 'MarriottLoginParams', 'MarriottEnrollParams',
|
|
59
|
+
'PokemonLoginParams', 'PokemonLotteryListParams', 'PokemonApplyLotteryParams', 'AjetPnrLoginParams',
|
|
60
|
+
'SephoraCheckBalanceParams', 'TextnowLoginParams', 'WalmartCheckBalanceParams', 'AjetGetBookingDetailParams',
|
|
61
|
+
'WalmartCaCheckBalanceParams', 'WalmartWalletParams', 'FlightSegment', 'WizzairSearchParams',
|
|
62
|
+
'HmCheckBalanceParams'
|
|
63
|
+
]
|
pynocaptcha/akamai.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
from .base import BaseCracker, get_ipinfo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AkamaiV2Cracker(BaseCracker):
|
|
7
|
+
|
|
8
|
+
cracker_name = "akamai"
|
|
9
|
+
cracker_version = "v2"
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
akamai v2 cracker
|
|
13
|
+
:param href: 触发验证的页面地址
|
|
14
|
+
:param proxy: 业务代理, 服务使用该代理完成验证
|
|
15
|
+
:param user_agent: 自定义 user_agent
|
|
16
|
+
:param country/ip/timezone/geolocation: 出口属地信息, 不传时自动请求 ipinfo.io/json
|
|
17
|
+
调用示例:
|
|
18
|
+
cookies = AkamaiV2Cracker(
|
|
19
|
+
user_token="xxx",
|
|
20
|
+
href="xxx",
|
|
21
|
+
proxy="user:pass@ip:port",
|
|
22
|
+
).crack()
|
|
23
|
+
# cookies = {"_abck": "...", "bm_sz": "...", ...}
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# 必传参数
|
|
27
|
+
must_check_params = ["href"]
|
|
28
|
+
# 默认可选参数
|
|
29
|
+
option_params = {
|
|
30
|
+
"branch": "Master",
|
|
31
|
+
"proxy": None,
|
|
32
|
+
"country": None,
|
|
33
|
+
"ip": None,
|
|
34
|
+
"timezone": None,
|
|
35
|
+
"geolocation": None,
|
|
36
|
+
"user_agent": None,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def request(self):
|
|
40
|
+
# 未显式传入的属地信息, 走 proxy 请求 ipinfo.io/json 补齐后再提交
|
|
41
|
+
for k, v in get_ipinfo(self.proxy).items():
|
|
42
|
+
self.wanda_args.setdefault(k, v)
|
|
43
|
+
|
|
44
|
+
def response(self, result):
|
|
45
|
+
return result or {}
|
pynocaptcha/aws.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from .base import BaseCracker
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AwsUniversalCracker(BaseCracker):
|
|
8
|
+
cracker_name = "aws"
|
|
9
|
+
cracker_version = "universal"
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
aws universal cracker
|
|
13
|
+
:param href: 触发验证的页面地址
|
|
14
|
+
:param user_agent: 请求头
|
|
15
|
+
调用示例:
|
|
16
|
+
cracker = AwsUniversalCracker(
|
|
17
|
+
user_token="xxx",
|
|
18
|
+
href="xxx",
|
|
19
|
+
|
|
20
|
+
# debug=True,
|
|
21
|
+
# proxy=proxy,
|
|
22
|
+
)
|
|
23
|
+
ret = cracker.crack()
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# 必传参数
|
|
27
|
+
must_check_params = ["href"]
|
|
28
|
+
# 默认可选参数
|
|
29
|
+
option_params = {
|
|
30
|
+
"user_agent": "",
|
|
31
|
+
"only_sense" : False,
|
|
32
|
+
"challenge_url": "",
|
|
33
|
+
"api_key": "",
|
|
34
|
+
"html": ""
|
|
35
|
+
}
|
pynocaptcha/base.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from curl_cffi import requests
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
IPINFO_URL = "https://ipinfo.io/json"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _proxy_url(proxy):
|
|
13
|
+
if proxy and isinstance(proxy, str) and "://" not in proxy:
|
|
14
|
+
return "http://" + proxy
|
|
15
|
+
return proxy
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _parse_ipinfo(resp: dict) -> dict:
|
|
19
|
+
return {
|
|
20
|
+
k: v for k, v in {
|
|
21
|
+
"country": (resp.get("country") or "").lower() or None,
|
|
22
|
+
"ip": resp.get("ip"),
|
|
23
|
+
"timezone": resp.get("timezone"),
|
|
24
|
+
"geolocation": resp.get("loc"),
|
|
25
|
+
}.items() if v
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_ipinfo(proxy: str = None, timeout: int = 5) -> dict:
|
|
30
|
+
"""请求 ipinfo.io/json 获取出口 ip 的 country/ip/timezone/geolocation。
|
|
31
|
+
|
|
32
|
+
走业务代理请求, 保证属地信息与实际出口一致; 失败返回空 dict, 不阻断验证。
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
resp = requests.get(
|
|
36
|
+
IPINFO_URL, proxy=_proxy_url(proxy), timeout=timeout,
|
|
37
|
+
).json()
|
|
38
|
+
return _parse_ipinfo(resp)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.debug(f"获取 ipinfo 失败: {e}")
|
|
41
|
+
return {}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def async_get_ipinfo(proxy: str = None, timeout: int = 5) -> dict:
|
|
45
|
+
"""get_ipinfo 的异步版本。"""
|
|
46
|
+
try:
|
|
47
|
+
async with requests.AsyncSession() as session:
|
|
48
|
+
resp = (await session.get(
|
|
49
|
+
IPINFO_URL, proxy=_proxy_url(proxy), timeout=timeout,
|
|
50
|
+
)).json()
|
|
51
|
+
return _parse_ipinfo(resp)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logger.debug(f"获取 ipinfo 失败: {e}")
|
|
54
|
+
return {}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def resolve_ipinfo(proxy: str = None, country: str = None, ip: str = None,
|
|
58
|
+
timezone: str = None, geolocation: str = None) -> dict:
|
|
59
|
+
"""显式传入的属地信息优先, 缺项时请求 ipinfo.io/json 补齐。"""
|
|
60
|
+
explicit = {
|
|
61
|
+
k: v for k, v in {
|
|
62
|
+
"country": country,
|
|
63
|
+
"ip": ip,
|
|
64
|
+
"timezone": timezone,
|
|
65
|
+
"geolocation": geolocation,
|
|
66
|
+
}.items() if v
|
|
67
|
+
}
|
|
68
|
+
if len(explicit) == 4:
|
|
69
|
+
return explicit
|
|
70
|
+
info = get_ipinfo(proxy)
|
|
71
|
+
info.update(explicit)
|
|
72
|
+
return info
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def async_resolve_ipinfo(proxy: str = None, country: str = None, ip: str = None,
|
|
76
|
+
timezone: str = None, geolocation: str = None) -> dict:
|
|
77
|
+
"""resolve_ipinfo 的异步版本。"""
|
|
78
|
+
explicit = {
|
|
79
|
+
k: v for k, v in {
|
|
80
|
+
"country": country,
|
|
81
|
+
"ip": ip,
|
|
82
|
+
"timezone": timezone,
|
|
83
|
+
"geolocation": geolocation,
|
|
84
|
+
}.items() if v
|
|
85
|
+
}
|
|
86
|
+
if len(explicit) == 4:
|
|
87
|
+
return explicit
|
|
88
|
+
info = await async_get_ipinfo(proxy)
|
|
89
|
+
info.update(explicit)
|
|
90
|
+
return info
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class BaseCracker:
|
|
95
|
+
|
|
96
|
+
# 破解器
|
|
97
|
+
cracker_name = "base"
|
|
98
|
+
|
|
99
|
+
# 破解版本
|
|
100
|
+
cracker_version = "universal"
|
|
101
|
+
|
|
102
|
+
# 必须参数列表
|
|
103
|
+
must_check_params = []
|
|
104
|
+
|
|
105
|
+
# 可选参数
|
|
106
|
+
option_params = {}
|
|
107
|
+
|
|
108
|
+
# 需要删除的多余参数
|
|
109
|
+
delete_params = []
|
|
110
|
+
|
|
111
|
+
_extra_data = None
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
show_ad=True,
|
|
116
|
+
user_token: str = None,
|
|
117
|
+
developer_id: str = None,
|
|
118
|
+
internal_host: bool = False,
|
|
119
|
+
# user_agent: str = None,
|
|
120
|
+
# cookies: dict = {},
|
|
121
|
+
# proxy: str = None,
|
|
122
|
+
# timeout: int = 30,
|
|
123
|
+
# debug: bool = False,
|
|
124
|
+
# check_useful: bool = False,
|
|
125
|
+
# max_retry_times: int = 3,
|
|
126
|
+
# internal=True,
|
|
127
|
+
# auth=False,
|
|
128
|
+
**kwargs
|
|
129
|
+
) -> None:
|
|
130
|
+
"""
|
|
131
|
+
:param user_token: nocaptcha.io 用户 token
|
|
132
|
+
:param developer_id: nocaptcha.io 用户上级代理 token
|
|
133
|
+
:param user_agent: 请求流程使用 ua
|
|
134
|
+
:param proxy: 请求流程代理, 不传默认使用系统代理, 某些强制要求代理一致或者特定区域的站点请传代理, 支持协议 http/https/socks5, 代理格式: {protocol}://{ip}:{port}(如有账号验证:{protocol}://{user}:{password}@{ip}:{port})
|
|
135
|
+
:param timeout: 破解接口超时时间(秒)
|
|
136
|
+
:param debug: 是否开启 debug 模式
|
|
137
|
+
:param check_useful: 检查破解是否成功
|
|
138
|
+
:param max_retry_times: 最大重试次数
|
|
139
|
+
:param internal: 是否使用国内代理
|
|
140
|
+
:param auth: 是否大户通道验证模式
|
|
141
|
+
:param internal_host: 是否使用国内域名 api.nocaptcha.cn, 默认 api.nocaptcha.io
|
|
142
|
+
"""
|
|
143
|
+
if show_ad:
|
|
144
|
+
logger.debug("感谢选择 nocaptcha, 我们只做别人做不到的(手动狗头)~")
|
|
145
|
+
logger.debug("欢迎推荐注册, 官网地址: https://www.nocaptcha.io/")
|
|
146
|
+
self.user_token = user_token
|
|
147
|
+
if not self.user_token:
|
|
148
|
+
raise Exception("缺少用户凭证")
|
|
149
|
+
self.developer_id = developer_id
|
|
150
|
+
self.cookies = kwargs.get("cookies") or {}
|
|
151
|
+
self.user_agent = kwargs.get("user_agent")
|
|
152
|
+
self.proxy = kwargs.get("proxy")
|
|
153
|
+
self.timeout = kwargs.get("timeout") or 30
|
|
154
|
+
self.debug = kwargs.get("debug", False)
|
|
155
|
+
self.check_useful = kwargs.get("check_useful", False)
|
|
156
|
+
self.max_retry_times = kwargs.get("max_retry_times", 3)
|
|
157
|
+
self.api_host = 'api.nocaptcha.cn' if internal_host else 'api.nocaptcha.io'
|
|
158
|
+
self.wanda_args = {
|
|
159
|
+
"internal": kwargs.get('internal', True),
|
|
160
|
+
"is_auth": kwargs.get('auth', False),
|
|
161
|
+
"timeout": self.timeout
|
|
162
|
+
}
|
|
163
|
+
for k in self.must_check_params:
|
|
164
|
+
_v = kwargs.get(k)
|
|
165
|
+
if not hasattr(self, k) or getattr(self, k) is None:
|
|
166
|
+
setattr(self, k, _v)
|
|
167
|
+
if _v is not None:
|
|
168
|
+
self.wanda_args.update({ k: _v })
|
|
169
|
+
for k, v in self.option_params.items():
|
|
170
|
+
_v = kwargs.get(k, v)
|
|
171
|
+
if not hasattr(self, k) or getattr(self, k) is None:
|
|
172
|
+
setattr(self, k, _v)
|
|
173
|
+
if _v is not None:
|
|
174
|
+
self.wanda_args.update({ k: _v })
|
|
175
|
+
|
|
176
|
+
for k in self.delete_params:
|
|
177
|
+
if k in self.wanda_args:
|
|
178
|
+
del self.wanda_args[k]
|
|
179
|
+
|
|
180
|
+
for k in self.must_check_params:
|
|
181
|
+
if getattr(self, k) is None:
|
|
182
|
+
raise AttributeError(f"缺少参数 {k}, 请检查")
|
|
183
|
+
|
|
184
|
+
if self.user_agent:
|
|
185
|
+
self.wanda_args["user_agent"] = self.user_agent
|
|
186
|
+
if self.cookies:
|
|
187
|
+
self.wanda_args["cookies"] = self.cookies
|
|
188
|
+
if self.proxy:
|
|
189
|
+
self.wanda_args["proxy"] = self.proxy
|
|
190
|
+
|
|
191
|
+
self.request()
|
|
192
|
+
|
|
193
|
+
def request(self):
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
def response(self, result: Any):
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
def check(self, ret):
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
def extra(self):
|
|
203
|
+
return self._extra_data
|
|
204
|
+
|
|
205
|
+
def fix_headers(self, headers: dict):
|
|
206
|
+
for k, _ in headers.items():
|
|
207
|
+
k = k.lower()
|
|
208
|
+
if k in [
|
|
209
|
+
"sec-ch-ua",
|
|
210
|
+
"accept-language",
|
|
211
|
+
'sec-ch-ua-arch',
|
|
212
|
+
'sec-ch-ua-bitness',
|
|
213
|
+
'sec-ch-ua-full-version',
|
|
214
|
+
'sec-ch-ua-full-version-list',
|
|
215
|
+
'sec-ch-ua-mobile',
|
|
216
|
+
'sec-ch-ua-model',
|
|
217
|
+
'sec-ch-ua-platform',
|
|
218
|
+
'sec-ch-ua-platform-version',
|
|
219
|
+
"user-agent",
|
|
220
|
+
] and self._extra_data.get(k):
|
|
221
|
+
headers[k] = self._extra_data[k]
|
|
222
|
+
|
|
223
|
+
def crack(self):
|
|
224
|
+
headers = {
|
|
225
|
+
"User-Token": self.user_token
|
|
226
|
+
}
|
|
227
|
+
if self.developer_id:
|
|
228
|
+
headers["Developer-Id"] = self.developer_id
|
|
229
|
+
|
|
230
|
+
retry_times = 0
|
|
231
|
+
resp = {}
|
|
232
|
+
while retry_times < self.max_retry_times:
|
|
233
|
+
try:
|
|
234
|
+
resp = requests.post(
|
|
235
|
+
f"http://{self.api_host}/api/wanda/{self.cracker_name}/{self.cracker_version}",
|
|
236
|
+
headers=headers, json=self.wanda_args, timeout=self.timeout
|
|
237
|
+
).json()
|
|
238
|
+
if self.debug:
|
|
239
|
+
logger.info(resp)
|
|
240
|
+
break
|
|
241
|
+
except Exception as e:
|
|
242
|
+
if self.debug:
|
|
243
|
+
logger.error(e)
|
|
244
|
+
retry_times += 1
|
|
245
|
+
wanda_ret = resp.get("data")
|
|
246
|
+
self._extra_data = resp.get("extra")
|
|
247
|
+
if not wanda_ret:
|
|
248
|
+
if self.debug:
|
|
249
|
+
logger.error(resp.get("msg"))
|
|
250
|
+
return
|
|
251
|
+
ret = self.response(wanda_ret)
|
|
252
|
+
if self.check_useful:
|
|
253
|
+
if self.check(wanda_ret):
|
|
254
|
+
if self.debug:
|
|
255
|
+
logger.success("crack success")
|
|
256
|
+
else:
|
|
257
|
+
retry_times += 1
|
|
258
|
+
if retry_times < self.max_retry_times:
|
|
259
|
+
return self.crack()
|
|
260
|
+
else:
|
|
261
|
+
logger.error("crack fail")
|
|
262
|
+
return ret
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
from .base import BaseCracker
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CloudFlareCracker(BaseCracker):
|
|
7
|
+
|
|
8
|
+
cracker_name = "cloudflare"
|
|
9
|
+
cracker_version = "universal"
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
cloudflare cracker
|
|
13
|
+
:param href: 触发 cloudfalre 验证的首页地址
|
|
14
|
+
:param user_agent: 请求流程使用 ua, 不传
|
|
15
|
+
:param headers: 触发验证必须的 headers, 默认 {}
|
|
16
|
+
:param cookies: 触发验证必须的 cookies, 默认 {}
|
|
17
|
+
:param html: 是否需要返回 html
|
|
18
|
+
:param alpha: 是否 alpha 模式, true 会更快
|
|
19
|
+
:param timeout: 最大破解超时时间
|
|
20
|
+
调用示例:
|
|
21
|
+
cracker = CloudFlareCracker(
|
|
22
|
+
href=href,
|
|
23
|
+
user_token="xxx",
|
|
24
|
+
)
|
|
25
|
+
ret = cracker.crack()
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# 必传参数
|
|
29
|
+
must_check_params = ["href"]
|
|
30
|
+
# 默认可选参数
|
|
31
|
+
option_params = {
|
|
32
|
+
"branch": "Master",
|
|
33
|
+
"user_agent": None,
|
|
34
|
+
"headers": {},
|
|
35
|
+
"cookies": {},
|
|
36
|
+
"html": False,
|
|
37
|
+
"sitekey": "",
|
|
38
|
+
"alpha": False,
|
|
39
|
+
"action": None,
|
|
40
|
+
"cdata": None,
|
|
41
|
+
"timeout": 30
|
|
42
|
+
}
|
pynocaptcha/datadome.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
from loguru import logger
|
|
4
|
+
from curl_cffi import requests
|
|
5
|
+
|
|
6
|
+
from .base import async_resolve_ipinfo, resolve_ipinfo
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _dd_payload(href, proxy=None, user_agent=None, interstitial=False,
|
|
10
|
+
cookies=None, ipinfo=None):
|
|
11
|
+
payload = {"href": href}
|
|
12
|
+
if proxy:
|
|
13
|
+
payload["proxy"] = proxy
|
|
14
|
+
if user_agent:
|
|
15
|
+
payload["user_agent"] = user_agent if isinstance(user_agent, str) else str(user_agent)
|
|
16
|
+
if interstitial:
|
|
17
|
+
payload["interstitial"] = True
|
|
18
|
+
if cookies:
|
|
19
|
+
payload["cookies"] = cookies
|
|
20
|
+
if ipinfo:
|
|
21
|
+
payload.update(ipinfo)
|
|
22
|
+
return payload
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _dd_request(user_token, payload, internal_host, developer_id, timeout):
|
|
26
|
+
host = "api.nocaptcha.cn" if internal_host else "api.nocaptcha.io"
|
|
27
|
+
headers = {"User-Token": user_token}
|
|
28
|
+
if developer_id:
|
|
29
|
+
headers["Developer-Id"] = developer_id
|
|
30
|
+
return requests.post(
|
|
31
|
+
f"http://{host}/api/wanda/datadome/universal",
|
|
32
|
+
headers=headers,
|
|
33
|
+
json=payload,
|
|
34
|
+
timeout=timeout,
|
|
35
|
+
).json()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _dd_result(resp, debug):
|
|
39
|
+
if debug:
|
|
40
|
+
logger.info(resp)
|
|
41
|
+
if not resp.get("status"):
|
|
42
|
+
raise Warning(resp.get("msg") or "验证失败")
|
|
43
|
+
return resp.get("data") or {}, resp.get("extra")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def crack_datadome(
|
|
47
|
+
user_token: str, href: str, proxy: str = None,
|
|
48
|
+
user_agent: str = None, interstitial: bool = False, cookies: dict = None,
|
|
49
|
+
internal_host: bool = True, developer_id: str = None,
|
|
50
|
+
timeout: int = 120, debug: bool = False,
|
|
51
|
+
country: str = None, ip: str = None, timezone: str = None, geolocation: str = None,
|
|
52
|
+
):
|
|
53
|
+
"""datadome 薄封装: 只上传 href/proxy 和出口属地信息, 计算全部在服务端完成。
|
|
54
|
+
|
|
55
|
+
country/ip/timezone/geolocation 未传时, 会走 proxy 请求 https://ipinfo.io/json 自动补齐。
|
|
56
|
+
:return: (cookies, extra), cookies 含可用的 datadome cookie
|
|
57
|
+
"""
|
|
58
|
+
resp = _dd_request(
|
|
59
|
+
user_token,
|
|
60
|
+
_dd_payload(
|
|
61
|
+
href, proxy=proxy, user_agent=user_agent, interstitial=interstitial,
|
|
62
|
+
cookies=cookies,
|
|
63
|
+
ipinfo=resolve_ipinfo(proxy, country, ip, timezone, geolocation),
|
|
64
|
+
),
|
|
65
|
+
internal_host, developer_id, timeout,
|
|
66
|
+
)
|
|
67
|
+
return _dd_result(resp, debug)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def async_crack_datadome(
|
|
71
|
+
user_token: str, href: str, proxy: str = None,
|
|
72
|
+
user_agent: str = None, interstitial: bool = False, cookies: dict = None,
|
|
73
|
+
internal_host: bool = True, developer_id: str = None,
|
|
74
|
+
timeout: int = 120, debug: bool = False,
|
|
75
|
+
country: str = None, ip: str = None, timezone: str = None, geolocation: str = None,
|
|
76
|
+
):
|
|
77
|
+
"""crack_datadome 的异步版本, 返回值与同步版本一致。"""
|
|
78
|
+
ipinfo = await async_resolve_ipinfo(proxy, country, ip, timezone, geolocation)
|
|
79
|
+
host = "api.nocaptcha.cn" if internal_host else "api.nocaptcha.io"
|
|
80
|
+
headers = {"User-Token": user_token}
|
|
81
|
+
if developer_id:
|
|
82
|
+
headers["Developer-Id"] = developer_id
|
|
83
|
+
async with requests.AsyncSession() as session:
|
|
84
|
+
resp = (await session.post(
|
|
85
|
+
f"http://{host}/api/wanda/datadome/universal",
|
|
86
|
+
headers=headers,
|
|
87
|
+
json=_dd_payload(
|
|
88
|
+
href, proxy=proxy, user_agent=user_agent, interstitial=interstitial,
|
|
89
|
+
cookies=cookies, ipinfo=ipinfo,
|
|
90
|
+
),
|
|
91
|
+
timeout=timeout,
|
|
92
|
+
)).json()
|
|
93
|
+
return _dd_result(resp, debug)
|
pynocaptcha/discord.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
from .base import BaseCracker
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DiscordCracker(BaseCracker):
|
|
7
|
+
|
|
8
|
+
cracker_name = "discord"
|
|
9
|
+
cracker_version = "guild"
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
discord cracker
|
|
13
|
+
:param authorization: discord 账号登录凭证
|
|
14
|
+
:param guild_id: 群 id
|
|
15
|
+
:param cookies:
|
|
16
|
+
调用示例:
|
|
17
|
+
cracker = DiscordCracker(
|
|
18
|
+
user_token="xxx",
|
|
19
|
+
authorization="MTExNzI1NDQ3NzA2NjU0MzE5NQ.xxx",
|
|
20
|
+
guild_id='645607528297922560',
|
|
21
|
+
# guild_name='fusionlist'
|
|
22
|
+
# debug=True,
|
|
23
|
+
)
|
|
24
|
+
ret = cracker.crack()
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# 必传参数
|
|
28
|
+
must_check_params = ["authorization"]
|
|
29
|
+
# 可选参数
|
|
30
|
+
option_params = {
|
|
31
|
+
"guild_id": "",
|
|
32
|
+
"guild_name": ""
|
|
33
|
+
}
|
pynocaptcha/hcaptcha.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from .base import BaseCracker
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HcaptchaCracker(BaseCracker):
|
|
8
|
+
|
|
9
|
+
cracker_name = "hcaptcha"
|
|
10
|
+
cracker_version = "universal"
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
hcaptcha cracker
|
|
14
|
+
:param sitekey: hcaptcha 对接网站的 key
|
|
15
|
+
:param rqtoken: 验证码配置接口有返回 captcha_rqdata、captcha_rqtoken 的请携带该值(如 discord 加频道)
|
|
16
|
+
:param user_agent: 请求流程使用 ua, 必须使用 MacOS Firefox User-Agent, 否则可能破解失败
|
|
17
|
+
:param referer: 触发 hcaptcha 验证的页面地址
|
|
18
|
+
:param mode: 验证模式, 默认 picture 图片验证模式, 可选 question 题库模式
|
|
19
|
+
调用示例:
|
|
20
|
+
cracker = HcaptchaCracker(
|
|
21
|
+
user_token="xxx",
|
|
22
|
+
sitekey='f5561ba9-8f1e-40ca-9b5b-a0b3f719ef34',
|
|
23
|
+
rqdata="",
|
|
24
|
+
referer="https://discord.com/login",
|
|
25
|
+
|
|
26
|
+
# debug=True,
|
|
27
|
+
)
|
|
28
|
+
ret = cracker.crack()
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# 必传参数
|
|
32
|
+
must_check_params = ["sitekey", "referer"]
|
|
33
|
+
# 默认可选参数
|
|
34
|
+
option_params = {
|
|
35
|
+
"mode": "picture", # 验证模式, 默认图片验证, 可选 question 题库验证
|
|
36
|
+
"rqdata": "",
|
|
37
|
+
"need_ekey": False,
|
|
38
|
+
"region": None,
|
|
39
|
+
"domain": "hcaptcha.com",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class HcaptchaV2Cracker(BaseCracker):
|
|
44
|
+
|
|
45
|
+
cracker_name = "hcaptcha"
|
|
46
|
+
cracker_version = "v2"
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
hcaptcha cracker
|
|
50
|
+
:param sitekey: hcaptcha 对接网站的 key
|
|
51
|
+
:param rqtoken: 验证码配置接口有返回 captcha_rqdata、captcha_rqtoken 的请携带该值(如 discord 加频道)
|
|
52
|
+
:param user_agent: 请求流程使用 ua, 必须使用 MacOS Firefox User-Agent, 否则可能破解失败
|
|
53
|
+
:param referer: 触发 hcaptcha 验证的页面地址
|
|
54
|
+
:param mode: 验证模式, 默认 picture 图片验证模式, 可选 question 题库模式
|
|
55
|
+
调用示例:
|
|
56
|
+
cracker = HcaptchaCracker(
|
|
57
|
+
user_token="xxx",
|
|
58
|
+
sitekey='f5561ba9-8f1e-40ca-9b5b-a0b3f719ef34',
|
|
59
|
+
rqdata="",
|
|
60
|
+
referer="https://discord.com/login",
|
|
61
|
+
|
|
62
|
+
# debug=True,
|
|
63
|
+
)
|
|
64
|
+
ret = cracker.crack()
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# 必传参数
|
|
68
|
+
must_check_params = ["sitekey", "referer"]
|
|
69
|
+
# 默认可选参数
|
|
70
|
+
option_params = {
|
|
71
|
+
"action": None,
|
|
72
|
+
"rqdata": "",
|
|
73
|
+
"need_ekey": False,
|
|
74
|
+
"title": None,
|
|
75
|
+
"extra": {},
|
|
76
|
+
"region": None,
|
|
77
|
+
"ip": None,
|
|
78
|
+
"timezone": None,
|
|
79
|
+
"geolocation": None,
|
|
80
|
+
}
|