hyper-sdk 2.7.0__tar.gz → 2.12.1__tar.gz
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.
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/PKG-INFO +26 -1
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/README.md +25 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/akamai_input.py +10 -13
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/datadome/parse.py +1 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/datadome_input.py +14 -13
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/incapsula_input.py +4 -4
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/kasada_input.py +30 -4
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/session.py +38 -47
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/session_async.py +38 -47
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk.egg-info/PKG-INFO +26 -1
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/pyproject.toml +2 -2
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/LICENSE +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/__init__.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/akamai/__init__.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/akamai/pixel.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/akamai/script_path.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/akamai/sec_cpt.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/akamai/stop_signal.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/datadome/__init__.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/incapsula/__init__.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/incapsula/dynamic.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/incapsula/utmvc.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/kasada/__init__.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/kasada/parse.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/shared.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk/trustdecision_input.py +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk.egg-info/SOURCES.txt +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk.egg-info/dependency_links.txt +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk.egg-info/requires.txt +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/hyper_sdk.egg-info/top_level.txt +0 -0
- {hyper_sdk-2.7.0 → hyper_sdk-2.12.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyper_sdk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.12.1
|
|
4
4
|
Summary: Hyper Solutions Python SDK
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -280,6 +280,31 @@ pow_payload = session.generate_kasada_pow(KasadaPowInput(
|
|
|
280
280
|
))
|
|
281
281
|
```
|
|
282
282
|
|
|
283
|
+
## 🤖 Vercel BotID
|
|
284
|
+
|
|
285
|
+
Bypass **Vercel BotID** protection by generating the required `x-is-human` header.
|
|
286
|
+
|
|
287
|
+
### Generating the x-is-human Header
|
|
288
|
+
|
|
289
|
+
Create the **x-is-human header** for Vercel BotID bypass:
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from hyper_sdk import BotIDHeaderInput
|
|
293
|
+
|
|
294
|
+
header = session.generate_botid_header(BotIDHeaderInput(
|
|
295
|
+
script=script_body, # The c.js script content
|
|
296
|
+
user_agent="your-user-agent",
|
|
297
|
+
ip="your-proxy-ip",
|
|
298
|
+
accept_language="en-US,en;q=0.9",
|
|
299
|
+
))
|
|
300
|
+
|
|
301
|
+
# Use the header in your requests
|
|
302
|
+
headers = {
|
|
303
|
+
"x-is-human": header,
|
|
304
|
+
# ... other headers
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
283
308
|
### Script Path Extraction
|
|
284
309
|
|
|
285
310
|
Extract **Kasada script paths** from blocked pages (HTTP 429):
|
|
@@ -237,6 +237,31 @@ pow_payload = session.generate_kasada_pow(KasadaPowInput(
|
|
|
237
237
|
))
|
|
238
238
|
```
|
|
239
239
|
|
|
240
|
+
## 🤖 Vercel BotID
|
|
241
|
+
|
|
242
|
+
Bypass **Vercel BotID** protection by generating the required `x-is-human` header.
|
|
243
|
+
|
|
244
|
+
### Generating the x-is-human Header
|
|
245
|
+
|
|
246
|
+
Create the **x-is-human header** for Vercel BotID bypass:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from hyper_sdk import BotIDHeaderInput
|
|
250
|
+
|
|
251
|
+
header = session.generate_botid_header(BotIDHeaderInput(
|
|
252
|
+
script=script_body, # The c.js script content
|
|
253
|
+
user_agent="your-user-agent",
|
|
254
|
+
ip="your-proxy-ip",
|
|
255
|
+
accept_language="en-US,en;q=0.9",
|
|
256
|
+
))
|
|
257
|
+
|
|
258
|
+
# Use the header in your requests
|
|
259
|
+
headers = {
|
|
260
|
+
"x-is-human": header,
|
|
261
|
+
# ... other headers
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
240
265
|
### Script Path Extraction
|
|
241
266
|
|
|
242
267
|
Extract **Kasada script paths** from blocked pages (HTTP 429):
|
|
@@ -1,38 +1,35 @@
|
|
|
1
1
|
class SensorInput:
|
|
2
|
-
def __init__(self, abck: str, bmsz: str, version: str, page_url: str, user_agent: str, ip: str,
|
|
2
|
+
def __init__(self, abck: str, bmsz: str, version: str, page_url: str, user_agent: str, ip: str, accept_language: str,
|
|
3
|
+
context: str, script: str, script_url: str):
|
|
3
4
|
self.abck = abck
|
|
4
5
|
self.bmsz = bmsz
|
|
5
6
|
self.version = version
|
|
6
7
|
self.page_url = page_url
|
|
7
8
|
self.user_agent = user_agent
|
|
8
|
-
self.
|
|
9
|
-
self.
|
|
9
|
+
self.script = script
|
|
10
|
+
self.script_url = script_url
|
|
10
11
|
self.context = context
|
|
11
12
|
self.ip = ip
|
|
12
|
-
self.
|
|
13
|
+
self.accept_language = accept_language
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class PixelInput:
|
|
16
|
-
def __init__(self, user_agent: str, html_var: str, script_var: str,
|
|
17
|
+
def __init__(self, user_agent: str, html_var: str, script_var: str, accept_language: str, ip: str):
|
|
17
18
|
self.user_agent = user_agent
|
|
18
19
|
self.html_var = html_var
|
|
19
20
|
self.script_var = script_var
|
|
20
|
-
self.
|
|
21
|
+
self.accept_language = accept_language
|
|
21
22
|
self.ip = ip
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
class DynamicInput:
|
|
25
|
-
def __init__(self, script: str):
|
|
26
|
-
self.script = script
|
|
27
|
-
|
|
28
|
-
|
|
29
25
|
class SbsdInput:
|
|
30
|
-
def __init__(self, index: int, user_agent: str, uuid: str, page_url: str, o_cookie: str, script: str,
|
|
26
|
+
def __init__(self, index: int, user_agent: str, uuid: str, page_url: str, o_cookie: str, script: str,
|
|
27
|
+
accept_language: str, ip: str):
|
|
31
28
|
self.index = index
|
|
32
29
|
self.user_agent = user_agent
|
|
33
30
|
self.uuid = uuid
|
|
34
31
|
self.page_url = page_url
|
|
35
32
|
self.o_cookie = o_cookie
|
|
36
33
|
self.script = script
|
|
37
|
-
self.
|
|
34
|
+
self.accept_language = accept_language
|
|
38
35
|
self.ip = ip
|
|
@@ -76,6 +76,7 @@ def parse_interstitial_device_check_link(src: str, datadome_cookie: str, referer
|
|
|
76
76
|
"cid": datadome_cookie,
|
|
77
77
|
"referer": referer,
|
|
78
78
|
"s": str(dd_object_parsed.get("s")),
|
|
79
|
+
"e": str(dd_object_parsed.get("e")),
|
|
79
80
|
"b": str(dd_object_parsed.get("b")),
|
|
80
81
|
"dm": "cd",
|
|
81
82
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
class DataDomeSliderInput:
|
|
3
|
-
def __init__(self, user_agent: str, device_link: str, html: str, puzzle: str, piece: str, parent_url: str,
|
|
3
|
+
def __init__(self, user_agent: str, device_link: str, html: str, puzzle: str, piece: str, parent_url: str, accept_language: str, ip: str):
|
|
4
4
|
# UserAgent must be a Chrome Windows User-Agent.
|
|
5
5
|
self.user_agent = user_agent
|
|
6
6
|
|
|
@@ -22,7 +22,7 @@ class DataDomeSliderInput:
|
|
|
22
22
|
self.piece = piece
|
|
23
23
|
|
|
24
24
|
self.parent_url = parent_url
|
|
25
|
-
self.
|
|
25
|
+
self.accept_language = accept_language
|
|
26
26
|
self.ip = ip
|
|
27
27
|
|
|
28
28
|
def to_dict(self):
|
|
@@ -33,13 +33,13 @@ class DataDomeSliderInput:
|
|
|
33
33
|
"puzzle": self.puzzle,
|
|
34
34
|
"piece": self.piece,
|
|
35
35
|
"parentUrl": self.parent_url,
|
|
36
|
-
"acceptLanguage": self.
|
|
36
|
+
"acceptLanguage": self.accept_language,
|
|
37
37
|
"ip": self.ip,
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class DataDomeInterstitialInput:
|
|
42
|
-
def __init__(self, user_agent: str, device_link: str, html: str,
|
|
42
|
+
def __init__(self, user_agent: str, device_link: str, html: str, accept_language: str, ip: str):
|
|
43
43
|
# UserAgent must be a Chrome Windows User-Agent.
|
|
44
44
|
self.user_agent = user_agent
|
|
45
45
|
|
|
@@ -50,7 +50,7 @@ class DataDomeInterstitialInput:
|
|
|
50
50
|
# Html is the response body of the GET request to the DeviceLink
|
|
51
51
|
self.html = html
|
|
52
52
|
|
|
53
|
-
self.
|
|
53
|
+
self.accept_language = accept_language
|
|
54
54
|
self.ip = ip
|
|
55
55
|
|
|
56
56
|
def to_dict(self):
|
|
@@ -58,31 +58,32 @@ class DataDomeInterstitialInput:
|
|
|
58
58
|
"userAgent": self.user_agent,
|
|
59
59
|
"deviceLink": self.device_link,
|
|
60
60
|
"html": self.html,
|
|
61
|
-
"acceptLanguage": self.
|
|
61
|
+
"acceptLanguage": self.accept_language,
|
|
62
62
|
"ip": self.ip,
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
class DataDomeTagsInput:
|
|
67
|
-
def __init__(self, user_agent: str,
|
|
68
|
-
# UserAgent must be a Chrome Windows User-Agent.
|
|
67
|
+
def __init__(self, user_agent: str, ddk: str, referer: str, tags_type: str, version: str, accept_language: str, ip: str, cid: str = ""):
|
|
69
68
|
self.user_agent = user_agent
|
|
70
69
|
self.cid = cid
|
|
71
70
|
self.ddk = ddk
|
|
72
71
|
self.referer = referer
|
|
73
72
|
self.tags_type = tags_type
|
|
74
73
|
self.version = version
|
|
75
|
-
self.
|
|
74
|
+
self.accept_language = accept_language
|
|
76
75
|
self.ip = ip
|
|
77
76
|
|
|
78
77
|
def to_dict(self):
|
|
79
|
-
|
|
78
|
+
data = {
|
|
80
79
|
"userAgent": self.user_agent,
|
|
81
|
-
"cid": self.cid,
|
|
82
80
|
"ddk": self.ddk,
|
|
83
81
|
"referer": self.referer,
|
|
84
82
|
"type": self.tags_type,
|
|
85
|
-
"acceptLanguage": self.
|
|
83
|
+
"acceptLanguage": self.accept_language,
|
|
86
84
|
"ip": self.ip,
|
|
87
85
|
"version": self.version,
|
|
88
|
-
}
|
|
86
|
+
}
|
|
87
|
+
if self.cid: # Only include cid if it's not empty
|
|
88
|
+
data["cid"] = self.cid
|
|
89
|
+
return data
|
|
@@ -8,11 +8,11 @@ class UtmvcInput:
|
|
|
8
8
|
self.script = script
|
|
9
9
|
|
|
10
10
|
class ReeseInput:
|
|
11
|
-
def __init__(self, user_agent: str,
|
|
11
|
+
def __init__(self, user_agent: str, accept_language: str, ip: str, pageUrl: str, script: str, script_url: str, pow: str = ""):
|
|
12
12
|
self.user_agent = user_agent
|
|
13
|
-
self.
|
|
13
|
+
self.accept_language = accept_language
|
|
14
14
|
self.ip = ip
|
|
15
|
-
self.
|
|
15
|
+
self.script_url = script_url
|
|
16
16
|
self.pow = pow
|
|
17
17
|
self.script = script
|
|
18
|
-
self.pageUrl = pageUrl
|
|
18
|
+
self.pageUrl = pageUrl
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
class KasadaPowInput:
|
|
2
|
-
def __init__(self, st: int, ct: str, domain: str, work_time: int = None):
|
|
2
|
+
def __init__(self, st: int, ct: str, domain: str, fc: str = "", work_time: int = None):
|
|
3
3
|
# St is the x-kpsdk-st value returned by the /tl POST request
|
|
4
4
|
self.st = st
|
|
5
5
|
# Ct is the x-kpsdk-ct value returned by the /tl POST request
|
|
6
6
|
self.ct = ct
|
|
7
|
+
# fc is the x-kpsdk-fc value returned by the /mfc GET request, if used by the site
|
|
8
|
+
self.fc = fc
|
|
7
9
|
# WorkTime can be used to pre-generate POW strings
|
|
8
10
|
self.work_time = work_time
|
|
9
11
|
self.domain = domain
|
|
10
12
|
|
|
11
13
|
def to_dict(self):
|
|
12
14
|
result = {"st": self.st, "ct": self.ct, "domain": self.domain}
|
|
15
|
+
if self.fc: # Only include fc if it's not empty
|
|
16
|
+
result["fc"] = self.fc
|
|
13
17
|
if self.work_time is not None:
|
|
14
18
|
result["workTime"] = self.work_time
|
|
15
19
|
return result
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
class KasadaPayloadInput:
|
|
19
|
-
def __init__(self, user_agent: str, ips_link: str, script: str,
|
|
23
|
+
def __init__(self, user_agent: str, ips_link: str, script: str, accept_language: str, ip: str):
|
|
20
24
|
# UserAgent must be a Chrome Windows User-Agent.
|
|
21
25
|
self.user_agent = user_agent
|
|
22
26
|
|
|
@@ -27,7 +31,7 @@ class KasadaPayloadInput:
|
|
|
27
31
|
self.script = script
|
|
28
32
|
|
|
29
33
|
# Your accept-language header
|
|
30
|
-
self.
|
|
34
|
+
self.accept_language = accept_language
|
|
31
35
|
self.ip = ip
|
|
32
36
|
|
|
33
37
|
def to_dict(self):
|
|
@@ -35,7 +39,29 @@ class KasadaPayloadInput:
|
|
|
35
39
|
"userAgent": self.user_agent,
|
|
36
40
|
"ipsLink": self.ips_link,
|
|
37
41
|
"script": self.script,
|
|
38
|
-
"acceptLanguage": self.
|
|
42
|
+
"acceptLanguage": self.accept_language,
|
|
39
43
|
"ip": self.ip,
|
|
40
44
|
}
|
|
41
45
|
return result
|
|
46
|
+
|
|
47
|
+
class BotIDHeaderInput:
|
|
48
|
+
def __init__(self, script: str, user_agent: str, ip: str, accept_language: str):
|
|
49
|
+
# Script is the c.js script retrieved from the BotID script endpoint
|
|
50
|
+
self.script = script
|
|
51
|
+
|
|
52
|
+
# UserAgent must be a Chrome Windows User-Agent.
|
|
53
|
+
self.user_agent = user_agent
|
|
54
|
+
|
|
55
|
+
# IP is the IPV4 address of your network or proxy
|
|
56
|
+
self.ip = ip
|
|
57
|
+
|
|
58
|
+
# Your accept-language header
|
|
59
|
+
self.accept_language = accept_language
|
|
60
|
+
|
|
61
|
+
def to_dict(self):
|
|
62
|
+
return {
|
|
63
|
+
"script": self.script,
|
|
64
|
+
"userAgent": self.user_agent,
|
|
65
|
+
"ip": self.ip,
|
|
66
|
+
"acceptLanguage": self.accept_language,
|
|
67
|
+
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"""Session class for Hyper Solutions API."""
|
|
2
2
|
|
|
3
3
|
from typing import Optional, Dict, Any, Tuple
|
|
4
|
-
from urllib.parse import quote
|
|
5
4
|
import httpx
|
|
6
5
|
import json
|
|
7
|
-
import
|
|
6
|
+
import gzip
|
|
8
7
|
|
|
9
8
|
from .shared import generate_signature, build_headers, validate_response
|
|
10
|
-
from .akamai_input import SensorInput, PixelInput,
|
|
11
|
-
from .kasada_input import KasadaPowInput, KasadaPayloadInput
|
|
9
|
+
from .akamai_input import SensorInput, PixelInput, SbsdInput
|
|
10
|
+
from .kasada_input import KasadaPowInput, KasadaPayloadInput, BotIDHeaderInput
|
|
12
11
|
from .datadome_input import DataDomeSliderInput, DataDomeInterstitialInput, DataDomeTagsInput
|
|
13
12
|
from .incapsula_input import UtmvcInput, ReeseInput
|
|
14
13
|
from .trustdecision_input import PayloadInput, DecodeInput, SignatureInput
|
|
@@ -24,12 +23,7 @@ class Session:
|
|
|
24
23
|
self.app_secret = app_secret
|
|
25
24
|
self.client = httpx.Client() if client is None else client
|
|
26
25
|
self._owns_client = client is None
|
|
27
|
-
self.compression = compression
|
|
28
|
-
|
|
29
|
-
# Initialize zstd compressor and decompressor if available
|
|
30
|
-
if self.compression:
|
|
31
|
-
self._compressor = zstd.ZstdCompressor(level=3)
|
|
32
|
-
self._decompressor = zstd.ZstdDecompressor()
|
|
26
|
+
self.compression = compression
|
|
33
27
|
|
|
34
28
|
def __enter__(self):
|
|
35
29
|
return self
|
|
@@ -63,18 +57,18 @@ class Session:
|
|
|
63
57
|
'bmsz': input_data.bmsz,
|
|
64
58
|
'version': input_data.version,
|
|
65
59
|
'pageUrl': input_data.page_url,
|
|
66
|
-
'
|
|
67
|
-
'
|
|
60
|
+
'script': input_data.script,
|
|
61
|
+
'scriptUrl': input_data.script_url,
|
|
68
62
|
'context': input_data.context,
|
|
69
63
|
'ip': input_data.ip,
|
|
70
|
-
'acceptLanguage': input_data.
|
|
64
|
+
'acceptLanguage': input_data.accept_language,
|
|
71
65
|
}
|
|
72
66
|
payload = json.dumps(payload_data).encode('utf-8')
|
|
73
67
|
|
|
74
68
|
# Compress payload if large enough
|
|
75
69
|
payload, use_compression = self._compress_payload(payload)
|
|
76
70
|
if use_compression:
|
|
77
|
-
headers["content-encoding"] = "
|
|
71
|
+
headers["content-encoding"] = "gzip"
|
|
78
72
|
|
|
79
73
|
response = self.client.post(sensor_endpoint, headers=headers, content=payload)
|
|
80
74
|
|
|
@@ -102,26 +96,11 @@ class Session:
|
|
|
102
96
|
'pageUrl': input_data.page_url,
|
|
103
97
|
'o': input_data.o_cookie,
|
|
104
98
|
'script': input_data.script,
|
|
105
|
-
'acceptLanguage': input_data.
|
|
99
|
+
'acceptLanguage': input_data.accept_language,
|
|
106
100
|
'ip': input_data.ip,
|
|
107
101
|
'index': input_data.index,
|
|
108
102
|
})
|
|
109
103
|
|
|
110
|
-
def parse_v3_dynamic(self, input_data: DynamicInput) -> str:
|
|
111
|
-
"""
|
|
112
|
-
Returns the dynamic values required to generate sensor data for V3 dynamic with Hyper Solutions API.
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
input_data (DynamicInput): An instance of DynamicInput containing the necessary data for parsing the script.
|
|
116
|
-
|
|
117
|
-
Returns:
|
|
118
|
-
str: Dynamic values as a string.
|
|
119
|
-
"""
|
|
120
|
-
sensor_endpoint = "https://akm.hypersolutions.co/v3dynamic"
|
|
121
|
-
return self._send_request(sensor_endpoint, {
|
|
122
|
-
'script': input_data.script,
|
|
123
|
-
})
|
|
124
|
-
|
|
125
104
|
def generate_pixel_data(self, input_data: PixelInput) -> str:
|
|
126
105
|
"""
|
|
127
106
|
Returns the pixel data using the Hyper Solutions API.
|
|
@@ -138,17 +117,16 @@ class Session:
|
|
|
138
117
|
'htmlVar': input_data.html_var,
|
|
139
118
|
'scriptVar': input_data.script_var,
|
|
140
119
|
'ip': input_data.ip,
|
|
141
|
-
'acceptLanguage': input_data.
|
|
120
|
+
'acceptLanguage': input_data.accept_language,
|
|
142
121
|
})
|
|
143
122
|
|
|
144
|
-
def generate_reese84_sensor(self,
|
|
123
|
+
def generate_reese84_sensor(self, input_data: ReeseInput) -> str:
|
|
145
124
|
"""
|
|
146
125
|
Returns the sensor data required to generate valid reese84 cookies using the Hyper Solutions API.
|
|
147
126
|
|
|
148
127
|
This function sends a request to the specified sensor endpoint with the necessary data to generate the reese84 sensor data.
|
|
149
128
|
|
|
150
129
|
Args:
|
|
151
|
-
site (str): The name of the site that will be used to generate the sensor data.
|
|
152
130
|
input_data (ReeseInput): The input data.
|
|
153
131
|
|
|
154
132
|
Returns:
|
|
@@ -157,11 +135,11 @@ class Session:
|
|
|
157
135
|
Raises:
|
|
158
136
|
ValueError: If the script attribute in input_data is empty.
|
|
159
137
|
"""
|
|
160
|
-
return self._send_request("https://incapsula.hypersolutions.co/reese84
|
|
138
|
+
return self._send_request("https://incapsula.hypersolutions.co/reese84", {
|
|
161
139
|
'userAgent': input_data.user_agent,
|
|
162
|
-
'acceptLanguage': input_data.
|
|
140
|
+
'acceptLanguage': input_data.accept_language,
|
|
163
141
|
'ip': input_data.ip,
|
|
164
|
-
'scriptUrl': input_data.
|
|
142
|
+
'scriptUrl': input_data.script_url,
|
|
165
143
|
'pageUrl': input_data.pageUrl,
|
|
166
144
|
'pow': input_data.pow,
|
|
167
145
|
'script': input_data.script,
|
|
@@ -195,7 +173,7 @@ class Session:
|
|
|
195
173
|
# Compress payload if large enough
|
|
196
174
|
payload, use_compression = self._compress_payload(payload)
|
|
197
175
|
if use_compression:
|
|
198
|
-
headers["content-encoding"] = "
|
|
176
|
+
headers["content-encoding"] = "gzip"
|
|
199
177
|
|
|
200
178
|
response = self.client.post("https://incapsula.hypersolutions.co/utmvc", headers=headers, content=payload)
|
|
201
179
|
|
|
@@ -237,7 +215,7 @@ class Session:
|
|
|
237
215
|
# Compress payload if large enough
|
|
238
216
|
payload, use_compression = self._compress_payload(payload)
|
|
239
217
|
if use_compression:
|
|
240
|
-
headers["content-encoding"] = "
|
|
218
|
+
headers["content-encoding"] = "gzip"
|
|
241
219
|
|
|
242
220
|
response = self.client.post("https://kasada.hypersolutions.co/payload", headers=headers, content=payload)
|
|
243
221
|
|
|
@@ -248,6 +226,19 @@ class Session:
|
|
|
248
226
|
|
|
249
227
|
return response_data["payload"], response_data["headers"]
|
|
250
228
|
|
|
229
|
+
def generate_botid_header(self, input_data: BotIDHeaderInput) -> str:
|
|
230
|
+
"""
|
|
231
|
+
Returns the x-is-human header value for Vercel BotID using the Hyper Solutions API.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
input_data (BotIDHeaderInput): An instance of BotIDHeaderInput containing the script,
|
|
235
|
+
user agent, IP, and accept language.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
str: The x-is-human header value as a string.
|
|
239
|
+
"""
|
|
240
|
+
return self._send_request("https://kasada.hypersolutions.co/botid", input_data.to_dict())
|
|
241
|
+
|
|
251
242
|
def generate_interstitial_payload(self, input_data: DataDomeInterstitialInput) -> Dict[str, Any]:
|
|
252
243
|
"""
|
|
253
244
|
Returns the DataDome interstitial payload value and response headers using the Hyper Solutions API.
|
|
@@ -316,7 +307,7 @@ class Session:
|
|
|
316
307
|
# Compress payload if large enough
|
|
317
308
|
payload, use_compression = self._compress_payload(payload)
|
|
318
309
|
if use_compression:
|
|
319
|
-
headers["content-encoding"] = "
|
|
310
|
+
headers["content-encoding"] = "gzip"
|
|
320
311
|
|
|
321
312
|
response = self.client.post("https://trustdecision.hypersolutions.co/payload", headers=headers, content=payload)
|
|
322
313
|
|
|
@@ -382,12 +373,12 @@ class Session:
|
|
|
382
373
|
headers = build_headers(self.api_key, self.jwt_key, self.app_key, self.app_secret)
|
|
383
374
|
# Add compression headers
|
|
384
375
|
if self.compression:
|
|
385
|
-
headers["accept-encoding"] = "
|
|
376
|
+
headers["accept-encoding"] = "gzip"
|
|
386
377
|
return headers
|
|
387
378
|
|
|
388
379
|
def _compress_payload(self, payload: bytes) -> Tuple[bytes, bool]:
|
|
389
380
|
"""
|
|
390
|
-
Compresses the payload using
|
|
381
|
+
Compresses the payload using gzip if enabled and payload is large enough.
|
|
391
382
|
|
|
392
383
|
Args:
|
|
393
384
|
payload (bytes): The payload to potentially compress
|
|
@@ -399,7 +390,7 @@ class Session:
|
|
|
399
390
|
return payload, False
|
|
400
391
|
|
|
401
392
|
try:
|
|
402
|
-
compressed =
|
|
393
|
+
compressed = gzip.compress(payload, compresslevel=6)
|
|
403
394
|
return compressed, True
|
|
404
395
|
except Exception:
|
|
405
396
|
# Fall back to uncompressed if compression fails
|
|
@@ -407,7 +398,7 @@ class Session:
|
|
|
407
398
|
|
|
408
399
|
def _decompress_response(self, response: httpx.Response) -> bytes:
|
|
409
400
|
"""
|
|
410
|
-
Decompresses the response body if it's compressed with
|
|
401
|
+
Decompresses the response body if it's compressed with gzip.
|
|
411
402
|
|
|
412
403
|
Args:
|
|
413
404
|
response (httpx.Response): The HTTP response
|
|
@@ -418,9 +409,9 @@ class Session:
|
|
|
418
409
|
content = response.content
|
|
419
410
|
content_encoding = response.headers.get("content-encoding", "").lower()
|
|
420
411
|
|
|
421
|
-
if content_encoding == "
|
|
412
|
+
if content_encoding == "gzip" and self.compression:
|
|
422
413
|
try:
|
|
423
|
-
return
|
|
414
|
+
return gzip.decompress(content)
|
|
424
415
|
except Exception:
|
|
425
416
|
# Fall back to original content if decompression fails
|
|
426
417
|
pass
|
|
@@ -444,7 +435,7 @@ class Session:
|
|
|
444
435
|
# Compress payload if large enough
|
|
445
436
|
payload, use_compression = self._compress_payload(payload)
|
|
446
437
|
if use_compression:
|
|
447
|
-
headers["content-encoding"] = "
|
|
438
|
+
headers["content-encoding"] = "gzip"
|
|
448
439
|
|
|
449
440
|
response = self.client.post(url, headers=headers, content=payload)
|
|
450
441
|
|
|
@@ -471,7 +462,7 @@ class Session:
|
|
|
471
462
|
# Compress payload if large enough
|
|
472
463
|
payload, use_compression = self._compress_payload(payload)
|
|
473
464
|
if use_compression:
|
|
474
|
-
headers["content-encoding"] = "
|
|
465
|
+
headers["content-encoding"] = "gzip"
|
|
475
466
|
|
|
476
467
|
response = self.client.post(url, headers=headers, content=payload)
|
|
477
468
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"""Async version of the Session class for Hyper Solutions API."""
|
|
2
2
|
|
|
3
3
|
from typing import Optional, Dict, Any, Tuple
|
|
4
|
-
from urllib.parse import quote
|
|
5
4
|
import httpx
|
|
6
5
|
import json
|
|
7
|
-
import
|
|
6
|
+
import gzip
|
|
8
7
|
|
|
9
8
|
from .shared import generate_signature, build_headers, validate_response
|
|
10
|
-
from .akamai_input import SensorInput, PixelInput,
|
|
11
|
-
from .kasada_input import KasadaPowInput, KasadaPayloadInput
|
|
9
|
+
from .akamai_input import SensorInput, PixelInput, SbsdInput
|
|
10
|
+
from .kasada_input import KasadaPowInput, KasadaPayloadInput, BotIDHeaderInput
|
|
12
11
|
from .datadome_input import DataDomeSliderInput, DataDomeInterstitialInput, DataDomeTagsInput
|
|
13
12
|
from .incapsula_input import UtmvcInput, ReeseInput
|
|
14
13
|
from .trustdecision_input import PayloadInput, DecodeInput, SignatureInput
|
|
@@ -24,12 +23,7 @@ class SessionAsync:
|
|
|
24
23
|
self.app_secret = app_secret
|
|
25
24
|
self.client = client
|
|
26
25
|
self._owns_client = client is None
|
|
27
|
-
self.compression = compression
|
|
28
|
-
|
|
29
|
-
# Initialize zstd compressor and decompressor if available
|
|
30
|
-
if self.compression:
|
|
31
|
-
self._compressor = zstd.ZstdCompressor(level=3)
|
|
32
|
-
self._decompressor = zstd.ZstdDecompressor()
|
|
26
|
+
self.compression = compression
|
|
33
27
|
|
|
34
28
|
async def __aenter__(self):
|
|
35
29
|
if self._owns_client:
|
|
@@ -67,18 +61,18 @@ class SessionAsync:
|
|
|
67
61
|
'bmsz': input_data.bmsz,
|
|
68
62
|
'version': input_data.version,
|
|
69
63
|
'pageUrl': input_data.page_url,
|
|
70
|
-
'
|
|
71
|
-
'
|
|
64
|
+
'script': input_data.script,
|
|
65
|
+
'scriptUrl': input_data.script_url,
|
|
72
66
|
'context': input_data.context,
|
|
73
67
|
'ip': input_data.ip,
|
|
74
|
-
'acceptLanguage': input_data.
|
|
68
|
+
'acceptLanguage': input_data.accept_language,
|
|
75
69
|
}
|
|
76
70
|
payload = json.dumps(payload_data).encode('utf-8')
|
|
77
71
|
|
|
78
72
|
# Compress payload if large enough
|
|
79
73
|
payload, use_compression = self._compress_payload(payload)
|
|
80
74
|
if use_compression:
|
|
81
|
-
headers["content-encoding"] = "
|
|
75
|
+
headers["content-encoding"] = "gzip"
|
|
82
76
|
|
|
83
77
|
response = await self.client.post(sensor_endpoint, headers=headers, content=payload)
|
|
84
78
|
|
|
@@ -106,26 +100,11 @@ class SessionAsync:
|
|
|
106
100
|
'pageUrl': input_data.page_url,
|
|
107
101
|
'o': input_data.o_cookie,
|
|
108
102
|
'script': input_data.script,
|
|
109
|
-
'acceptLanguage': input_data.
|
|
103
|
+
'acceptLanguage': input_data.accept_language,
|
|
110
104
|
'ip': input_data.ip,
|
|
111
105
|
'index': input_data.index,
|
|
112
106
|
})
|
|
113
107
|
|
|
114
|
-
async def parse_v3_dynamic(self, input_data: DynamicInput) -> str:
|
|
115
|
-
"""
|
|
116
|
-
Returns the dynamic values required to generate sensor data for V3 dynamic with Hyper Solutions API.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
input_data (DynamicInput): An instance of DynamicInput containing the necessary data for parsing the script.
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
str: Dynamic values as a string.
|
|
123
|
-
"""
|
|
124
|
-
sensor_endpoint = "https://akm.hypersolutions.co/v3dynamic"
|
|
125
|
-
return await self._send_request(sensor_endpoint, {
|
|
126
|
-
'script': input_data.script,
|
|
127
|
-
})
|
|
128
|
-
|
|
129
108
|
async def generate_pixel_data(self, input_data: PixelInput) -> str:
|
|
130
109
|
"""
|
|
131
110
|
Returns the pixel data using the Hyper Solutions API.
|
|
@@ -142,17 +121,16 @@ class SessionAsync:
|
|
|
142
121
|
'htmlVar': input_data.html_var,
|
|
143
122
|
'scriptVar': input_data.script_var,
|
|
144
123
|
'ip': input_data.ip,
|
|
145
|
-
'acceptLanguage': input_data.
|
|
124
|
+
'acceptLanguage': input_data.accept_language,
|
|
146
125
|
})
|
|
147
126
|
|
|
148
|
-
async def generate_reese84_sensor(self,
|
|
127
|
+
async def generate_reese84_sensor(self, input_data: ReeseInput) -> str:
|
|
149
128
|
"""
|
|
150
129
|
Returns the sensor data required to generate valid reese84 cookies using the Hyper Solutions API.
|
|
151
130
|
|
|
152
131
|
This function sends a request to the specified sensor endpoint with the necessary data to generate the reese84 sensor data.
|
|
153
132
|
|
|
154
133
|
Args:
|
|
155
|
-
site (str): The name of the site that will be used to generate the sensor data.
|
|
156
134
|
input_data (ReeseInput): The input data.
|
|
157
135
|
|
|
158
136
|
Returns:
|
|
@@ -161,11 +139,11 @@ class SessionAsync:
|
|
|
161
139
|
Raises:
|
|
162
140
|
ValueError: If the script attribute in input_data is empty.
|
|
163
141
|
"""
|
|
164
|
-
return await self._send_request("https://incapsula.hypersolutions.co/reese84
|
|
142
|
+
return await self._send_request("https://incapsula.hypersolutions.co/reese84", {
|
|
165
143
|
'userAgent': input_data.user_agent,
|
|
166
|
-
'acceptLanguage': input_data.
|
|
144
|
+
'acceptLanguage': input_data.accept_language,
|
|
167
145
|
'ip': input_data.ip,
|
|
168
|
-
'scriptUrl': input_data.
|
|
146
|
+
'scriptUrl': input_data.script_url,
|
|
169
147
|
'pageUrl': input_data.pageUrl,
|
|
170
148
|
'pow': input_data.pow,
|
|
171
149
|
'script': input_data.script,
|
|
@@ -200,7 +178,7 @@ class SessionAsync:
|
|
|
200
178
|
# Compress payload if large enough
|
|
201
179
|
payload, use_compression = self._compress_payload(payload)
|
|
202
180
|
if use_compression:
|
|
203
|
-
headers["content-encoding"] = "
|
|
181
|
+
headers["content-encoding"] = "gzip"
|
|
204
182
|
|
|
205
183
|
response = await self.client.post("https://incapsula.hypersolutions.co/utmvc", headers=headers, content=payload)
|
|
206
184
|
|
|
@@ -243,7 +221,7 @@ class SessionAsync:
|
|
|
243
221
|
# Compress payload if large enough
|
|
244
222
|
payload, use_compression = self._compress_payload(payload)
|
|
245
223
|
if use_compression:
|
|
246
|
-
headers["content-encoding"] = "
|
|
224
|
+
headers["content-encoding"] = "gzip"
|
|
247
225
|
|
|
248
226
|
response = await self.client.post("https://kasada.hypersolutions.co/payload", headers=headers, content=payload)
|
|
249
227
|
|
|
@@ -254,6 +232,19 @@ class SessionAsync:
|
|
|
254
232
|
|
|
255
233
|
return response_data["payload"], response_data["headers"]
|
|
256
234
|
|
|
235
|
+
async def generate_botid_header(self, input_data: BotIDHeaderInput) -> str:
|
|
236
|
+
"""
|
|
237
|
+
Returns the x-is-human header value for Vercel BotID using the Hyper Solutions API.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
input_data (BotIDHeaderInput): An instance of BotIDHeaderInput containing the script,
|
|
241
|
+
user agent, IP, and accept language.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
str: The x-is-human header value as a string.
|
|
245
|
+
"""
|
|
246
|
+
return await self._send_request("https://kasada.hypersolutions.co/botid", input_data.to_dict())
|
|
247
|
+
|
|
257
248
|
async def generate_interstitial_payload(self, input_data: DataDomeInterstitialInput) -> Dict[str, Any]:
|
|
258
249
|
"""
|
|
259
250
|
Returns the DataDome interstitial payload value and response headers using the Hyper Solutions API.
|
|
@@ -325,7 +316,7 @@ class SessionAsync:
|
|
|
325
316
|
# Compress payload if large enough
|
|
326
317
|
payload, use_compression = self._compress_payload(payload)
|
|
327
318
|
if use_compression:
|
|
328
|
-
headers["content-encoding"] = "
|
|
319
|
+
headers["content-encoding"] = "gzip"
|
|
329
320
|
|
|
330
321
|
response = await self.client.post("https://trustdecision.hypersolutions.co/payload", headers=headers, content=payload)
|
|
331
322
|
|
|
@@ -391,12 +382,12 @@ class SessionAsync:
|
|
|
391
382
|
headers = build_headers(self.api_key, self.jwt_key, self.app_key, self.app_secret)
|
|
392
383
|
# Add compression headers
|
|
393
384
|
if self.compression:
|
|
394
|
-
headers["accept-encoding"] = "
|
|
385
|
+
headers["accept-encoding"] = "gzip"
|
|
395
386
|
return headers
|
|
396
387
|
|
|
397
388
|
def _compress_payload(self, payload: bytes) -> Tuple[bytes, bool]:
|
|
398
389
|
"""
|
|
399
|
-
Compresses the payload using
|
|
390
|
+
Compresses the payload using gzip if enabled and payload is large enough.
|
|
400
391
|
|
|
401
392
|
Args:
|
|
402
393
|
payload (bytes): The payload to potentially compress
|
|
@@ -408,7 +399,7 @@ class SessionAsync:
|
|
|
408
399
|
return payload, False
|
|
409
400
|
|
|
410
401
|
try:
|
|
411
|
-
compressed =
|
|
402
|
+
compressed = gzip.compress(payload, compresslevel=6)
|
|
412
403
|
return compressed, True
|
|
413
404
|
except Exception:
|
|
414
405
|
# Fall back to uncompressed if compression fails
|
|
@@ -416,7 +407,7 @@ class SessionAsync:
|
|
|
416
407
|
|
|
417
408
|
def _decompress_response(self, response: httpx.Response) -> bytes:
|
|
418
409
|
"""
|
|
419
|
-
Decompresses the response body if it's compressed with
|
|
410
|
+
Decompresses the response body if it's compressed with gzip.
|
|
420
411
|
|
|
421
412
|
Args:
|
|
422
413
|
response (httpx.Response): The HTTP response
|
|
@@ -427,9 +418,9 @@ class SessionAsync:
|
|
|
427
418
|
content = response.content
|
|
428
419
|
content_encoding = response.headers.get("content-encoding", "").lower()
|
|
429
420
|
|
|
430
|
-
if content_encoding == "
|
|
421
|
+
if content_encoding == "gzip" and self.compression:
|
|
431
422
|
try:
|
|
432
|
-
return
|
|
423
|
+
return gzip.decompress(content)
|
|
433
424
|
except Exception:
|
|
434
425
|
# Fall back to original content if decompression fails
|
|
435
426
|
pass
|
|
@@ -454,7 +445,7 @@ class SessionAsync:
|
|
|
454
445
|
# Compress payload if large enough
|
|
455
446
|
payload, use_compression = self._compress_payload(payload)
|
|
456
447
|
if use_compression:
|
|
457
|
-
headers["content-encoding"] = "
|
|
448
|
+
headers["content-encoding"] = "gzip"
|
|
458
449
|
|
|
459
450
|
response = await self.client.post(url, headers=headers, content=payload)
|
|
460
451
|
|
|
@@ -482,7 +473,7 @@ class SessionAsync:
|
|
|
482
473
|
# Compress payload if large enough
|
|
483
474
|
payload, use_compression = self._compress_payload(payload)
|
|
484
475
|
if use_compression:
|
|
485
|
-
headers["content-encoding"] = "
|
|
476
|
+
headers["content-encoding"] = "gzip"
|
|
486
477
|
|
|
487
478
|
response = await self.client.post(url, headers=headers, content=payload)
|
|
488
479
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyper_sdk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.12.1
|
|
4
4
|
Summary: Hyper Solutions Python SDK
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -280,6 +280,31 @@ pow_payload = session.generate_kasada_pow(KasadaPowInput(
|
|
|
280
280
|
))
|
|
281
281
|
```
|
|
282
282
|
|
|
283
|
+
## 🤖 Vercel BotID
|
|
284
|
+
|
|
285
|
+
Bypass **Vercel BotID** protection by generating the required `x-is-human` header.
|
|
286
|
+
|
|
287
|
+
### Generating the x-is-human Header
|
|
288
|
+
|
|
289
|
+
Create the **x-is-human header** for Vercel BotID bypass:
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from hyper_sdk import BotIDHeaderInput
|
|
293
|
+
|
|
294
|
+
header = session.generate_botid_header(BotIDHeaderInput(
|
|
295
|
+
script=script_body, # The c.js script content
|
|
296
|
+
user_agent="your-user-agent",
|
|
297
|
+
ip="your-proxy-ip",
|
|
298
|
+
accept_language="en-US,en;q=0.9",
|
|
299
|
+
))
|
|
300
|
+
|
|
301
|
+
# Use the header in your requests
|
|
302
|
+
headers = {
|
|
303
|
+
"x-is-human": header,
|
|
304
|
+
# ... other headers
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
283
308
|
### Script Path Extraction
|
|
284
309
|
|
|
285
310
|
Extract **Kasada script paths** from blocked pages (HTTP 429):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hyper_sdk"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.12.1"
|
|
8
8
|
description = "Hyper Solutions Python SDK"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.7"
|
|
@@ -21,7 +21,7 @@ dependencies = [
|
|
|
21
21
|
"idna>=3.6",
|
|
22
22
|
"jsonpickle>=3.0.3",
|
|
23
23
|
"PyJWT>=2.8.0",
|
|
24
|
-
"urllib3>=2.2.1"
|
|
24
|
+
"urllib3>=2.2.1"
|
|
25
25
|
]
|
|
26
26
|
license = {file = "LICENSE"}
|
|
27
27
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|