hyper-sdk 2.12.4__tar.gz → 2.13.0__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.
Files changed (31) hide show
  1. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/PKG-INFO +29 -1
  2. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/README.md +28 -0
  3. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/datadome/parse.py +56 -0
  4. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/datadome_input.py +29 -4
  5. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/incapsula/dynamic.py +1 -1
  6. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk.egg-info/PKG-INFO +29 -1
  7. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/pyproject.toml +37 -37
  8. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/LICENSE +0 -0
  9. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/__init__.py +0 -0
  10. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/akamai/__init__.py +0 -0
  11. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/akamai/pixel.py +0 -0
  12. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/akamai/script_path.py +0 -0
  13. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/akamai/sec_cpt.py +0 -0
  14. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/akamai/stop_signal.py +0 -0
  15. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/akamai_input.py +0 -0
  16. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/datadome/__init__.py +0 -0
  17. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/incapsula/__init__.py +0 -0
  18. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/incapsula/utmvc.py +0 -0
  19. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/incapsula_input.py +0 -0
  20. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/kasada/__init__.py +0 -0
  21. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/kasada/parse.py +0 -0
  22. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/kasada_input.py +0 -0
  23. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/session.py +0 -0
  24. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/session_async.py +0 -0
  25. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/shared.py +0 -0
  26. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk/trustdecision_input.py +0 -0
  27. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk.egg-info/SOURCES.txt +0 -0
  28. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk.egg-info/dependency_links.txt +0 -0
  29. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk.egg-info/requires.txt +0 -0
  30. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/hyper_sdk.egg-info/top_level.txt +0 -0
  31. {hyper_sdk-2.12.4 → hyper_sdk-2.13.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyper_sdk
3
- Version: 2.12.4
3
+ Version: 2.13.0
4
4
  Summary: Hyper Solutions Python SDK
5
5
  License: MIT License
6
6
 
@@ -386,6 +386,34 @@ device_link = parse_slider_device_check_link(
386
386
  )
387
387
  ```
388
388
 
389
+ ### Challenge Script
390
+
391
+ DataDome sometimes serves a challenge page with the **challenge script in its own file** instead of inlining it in the page:
392
+
393
+ ```html
394
+ <script defer src="https://ct.captcha-delivery.com/interstitial.1.33.0.202609141.js"></script>
395
+ ```
396
+
397
+ It switches between the two forms per request, so check every challenge page. When the page uses a separate file, fetch it with your own client so the request keeps your proxy, TLS fingerprint and headers, then pass the body as `script`:
398
+
399
+ ```python
400
+ from hyper_sdk import DataDomeInterstitialInput
401
+ from hyper_sdk.datadome import parse_challenge_script_url
402
+
403
+ script = ""
404
+ script_url = parse_challenge_script_url(html_content)
405
+ if script_url is not None:
406
+ script = your_client.get(script_url).text
407
+
408
+ result = session.generate_interstitial_payload(DataDomeInterstitialInput(
409
+ # other interstitial input fields
410
+ html=html_content,
411
+ script=script, # empty when the page inlines the script
412
+ ))
413
+ ```
414
+
415
+ The same applies to `DataDomeSliderInput`. One helper covers both challenge types, since the captcha page uses the same tag. `script` is optional and leaving it empty keeps working.
416
+
389
417
  ## 📖 Documentation
390
418
 
391
419
  For detailed documentation on how to use the SDK, including examples and API reference, please visit our documentation website:
@@ -343,6 +343,34 @@ device_link = parse_slider_device_check_link(
343
343
  )
344
344
  ```
345
345
 
346
+ ### Challenge Script
347
+
348
+ DataDome sometimes serves a challenge page with the **challenge script in its own file** instead of inlining it in the page:
349
+
350
+ ```html
351
+ <script defer src="https://ct.captcha-delivery.com/interstitial.1.33.0.202609141.js"></script>
352
+ ```
353
+
354
+ It switches between the two forms per request, so check every challenge page. When the page uses a separate file, fetch it with your own client so the request keeps your proxy, TLS fingerprint and headers, then pass the body as `script`:
355
+
356
+ ```python
357
+ from hyper_sdk import DataDomeInterstitialInput
358
+ from hyper_sdk.datadome import parse_challenge_script_url
359
+
360
+ script = ""
361
+ script_url = parse_challenge_script_url(html_content)
362
+ if script_url is not None:
363
+ script = your_client.get(script_url).text
364
+
365
+ result = session.generate_interstitial_payload(DataDomeInterstitialInput(
366
+ # other interstitial input fields
367
+ html=html_content,
368
+ script=script, # empty when the page inlines the script
369
+ ))
370
+ ```
371
+
372
+ The same applies to `DataDomeSliderInput`. One helper covers both challenge types, since the captcha page uses the same tag. `script` is optional and leaving it empty keeps working.
373
+
346
374
  ## 📖 Documentation
347
375
 
348
376
  For detailed documentation on how to use the SDK, including examples and API reference, please visit our documentation website:
@@ -1,6 +1,16 @@
1
1
  import json
2
+ import re
3
+ from typing import Optional
2
4
  from urllib.parse import urlencode
3
5
 
6
+ # Matches the tag a challenge page uses to load the bundle from
7
+ # ct.captcha-delivery.com, capturing the URL. The name in front of the version
8
+ # is left open so it keeps matching whatever DataDome calls the bundle, e.g.
9
+ # interstitial.1.33.0.202609141.js or captcha.1.34.0.202609141.js.
10
+ _CHALLENGE_SCRIPT_REGEX = re.compile(
11
+ r"""<script[^>]+src\s*=\s*["']([^"']*/[a-z_-]+\.\d+\.\d+\.\d+\.[^"']*\.js)["']"""
12
+ )
13
+
4
14
 
5
15
  def parse_slider_device_check_link(src: str, datadome_cookie: str, referer: str) -> str:
6
16
  """
@@ -82,3 +92,49 @@ def parse_interstitial_device_check_link(src: str, datadome_cookie: str, referer
82
92
  }
83
93
 
84
94
  return f"https://geo.captcha-delivery.com/interstitial/?{urlencode(params)}"
95
+
96
+
97
+ def parse_challenge_script_url(html: str) -> Optional[str]:
98
+ """
99
+ Return the challenge bundle URL a device check or captcha page loads with a
100
+ <script defer src="..."> tag, or None when the page has no such tag.
101
+
102
+ DataDome serves some challenge pages with the bundle inlined in the HTML and
103
+ others with it in its own file, switching between the two per request, so this
104
+ has to be checked on every challenge rather than configured once.
105
+
106
+ When it returns a URL, GET that URL with the same client, proxy and headers you
107
+ used for the challenge page, and pass the response body as the `script` field of
108
+ DataDomeInterstitialInput or DataDomeSliderInput. When it returns None the
109
+ bundle is already in the HTML and `script` stays empty.
110
+
111
+ Args:
112
+ html (str): The response body of the GET request to the device check link.
113
+
114
+ Returns:
115
+ Optional[str]: The challenge bundle URL, or None when the page inlines it.
116
+
117
+ Example:
118
+ device_link = parse_interstitial_device_check_link(body, cookie, referer)
119
+ # ... GET device_link with your own client, read the body into html ...
120
+
121
+ script = ""
122
+ script_url = parse_challenge_script_url(html)
123
+ if script_url is not None:
124
+ # fetch script_url with your own client, read the body into script
125
+ script = your_client.get(script_url).text
126
+
127
+ result = session.generate_interstitial_payload(DataDomeInterstitialInput(
128
+ user_agent=user_agent,
129
+ device_link=device_link,
130
+ html=html,
131
+ accept_language=accept_language,
132
+ ip=ip,
133
+ script=script,
134
+ ))
135
+ """
136
+ match = _CHALLENGE_SCRIPT_REGEX.search(html)
137
+ if match is None:
138
+ return None
139
+
140
+ return match.group(1)
@@ -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, accept_language: str, ip: 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, script: str = ""):
4
4
  # UserAgent must be a Chrome Windows User-Agent.
5
5
  self.user_agent = user_agent
6
6
 
@@ -22,11 +22,21 @@ class DataDomeSliderInput:
22
22
  self.piece = piece
23
23
 
24
24
  self.parent_url = parent_url
25
+
26
+ # Script is the DataDome challenge bundle. It is only needed when the
27
+ # challenge page loads the bundle with a <script defer src="..."> tag
28
+ # instead of inlining it, which DataDome does per request. Use
29
+ # hyper_sdk.datadome.parse_challenge_script_url on the html to find out,
30
+ # then GET that URL with the same client and pass the response body here.
31
+ #
32
+ # Leave it empty when the page inlines the bundle.
33
+ self.script = script
34
+
25
35
  self.accept_language = accept_language
26
36
  self.ip = ip
27
37
 
28
38
  def to_dict(self):
29
- return {
39
+ data = {
30
40
  "userAgent": self.user_agent,
31
41
  "deviceLink": self.device_link,
32
42
  "html": self.html,
@@ -36,10 +46,13 @@ class DataDomeSliderInput:
36
46
  "acceptLanguage": self.accept_language,
37
47
  "ip": self.ip,
38
48
  }
49
+ if self.script: # Only include script if it's not empty
50
+ data["script"] = self.script
51
+ return data
39
52
 
40
53
 
41
54
  class DataDomeInterstitialInput:
42
- def __init__(self, user_agent: str, device_link: str, html: str, accept_language: str, ip: str):
55
+ def __init__(self, user_agent: str, device_link: str, html: str, accept_language: str, ip: str, script: str = ""):
43
56
  # UserAgent must be a Chrome Windows User-Agent.
44
57
  self.user_agent = user_agent
45
58
 
@@ -50,17 +63,29 @@ class DataDomeInterstitialInput:
50
63
  # Html is the response body of the GET request to the DeviceLink
51
64
  self.html = html
52
65
 
66
+ # Script is the DataDome challenge bundle. It is only needed when the
67
+ # challenge page loads the bundle with a <script defer src="..."> tag
68
+ # instead of inlining it, which DataDome does per request. Use
69
+ # hyper_sdk.datadome.parse_challenge_script_url on the html to find out,
70
+ # then GET that URL with the same client and pass the response body here.
71
+ #
72
+ # Leave it empty when the page inlines the bundle.
73
+ self.script = script
74
+
53
75
  self.accept_language = accept_language
54
76
  self.ip = ip
55
77
 
56
78
  def to_dict(self):
57
- return {
79
+ data = {
58
80
  "userAgent": self.user_agent,
59
81
  "deviceLink": self.device_link,
60
82
  "html": self.html,
61
83
  "acceptLanguage": self.accept_language,
62
84
  "ip": self.ip,
63
85
  }
86
+ if self.script: # Only include script if it's not empty
87
+ data["script"] = self.script
88
+ return data
64
89
 
65
90
 
66
91
  class DataDomeTagsInput:
@@ -2,7 +2,7 @@ import re
2
2
  from urllib.parse import urlparse
3
3
 
4
4
  # Precompiled regular expressions
5
- reese_script_regex = re.compile(r'src\s*=\s*"((/[^/]+/\d+)(?:\?.*)?)"')
5
+ reese_script_regex = re.compile(r'src\s*=\s*"((/[^/]+/[^/?"]+)\?s=[^"]*)"')
6
6
 
7
7
 
8
8
  def parse_dynamic_reese_script(html_content: str, url_str: str) -> tuple[str, str]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyper_sdk
3
- Version: 2.12.4
3
+ Version: 2.13.0
4
4
  Summary: Hyper Solutions Python SDK
5
5
  License: MIT License
6
6
 
@@ -386,6 +386,34 @@ device_link = parse_slider_device_check_link(
386
386
  )
387
387
  ```
388
388
 
389
+ ### Challenge Script
390
+
391
+ DataDome sometimes serves a challenge page with the **challenge script in its own file** instead of inlining it in the page:
392
+
393
+ ```html
394
+ <script defer src="https://ct.captcha-delivery.com/interstitial.1.33.0.202609141.js"></script>
395
+ ```
396
+
397
+ It switches between the two forms per request, so check every challenge page. When the page uses a separate file, fetch it with your own client so the request keeps your proxy, TLS fingerprint and headers, then pass the body as `script`:
398
+
399
+ ```python
400
+ from hyper_sdk import DataDomeInterstitialInput
401
+ from hyper_sdk.datadome import parse_challenge_script_url
402
+
403
+ script = ""
404
+ script_url = parse_challenge_script_url(html_content)
405
+ if script_url is not None:
406
+ script = your_client.get(script_url).text
407
+
408
+ result = session.generate_interstitial_payload(DataDomeInterstitialInput(
409
+ # other interstitial input fields
410
+ html=html_content,
411
+ script=script, # empty when the page inlines the script
412
+ ))
413
+ ```
414
+
415
+ The same applies to `DataDomeSliderInput`. One helper covers both challenge types, since the captcha page uses the same tag. `script` is optional and leaving it empty keeps working.
416
+
389
417
  ## 📖 Documentation
390
418
 
391
419
  For detailed documentation on how to use the SDK, including examples and API reference, please visit our documentation website:
@@ -1,38 +1,38 @@
1
- [build-system]
2
- requires = ["setuptools>=42", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "hyper_sdk"
7
- version = "2.12.4"
8
- description = "Hyper Solutions Python SDK"
9
- readme = "README.md"
10
- requires-python = ">=3.7"
11
- classifiers = [
12
- "Intended Audience :: Developers",
13
- "License :: OSI Approved :: MIT License",
14
- "Programming Language :: Python :: 3",
15
- "Framework :: AsyncIO",
16
- ]
17
- dependencies = [
18
- "httpx[http2]>=0.24.0",
19
- "certifi>=2024.2.2",
20
- "charset-normalizer>=3.3.2",
21
- "idna>=3.6",
22
- "jsonpickle>=3.0.3",
23
- "PyJWT>=2.8.0",
24
- "urllib3>=2.2.1"
25
- ]
26
- license = {file = "LICENSE"}
27
-
28
- [project.urls]
29
- homepage = "https://github.com/Hyper-Solutions/hyper-sdk-py"
30
-
31
- [tool.setuptools]
32
- packages = [
33
- "hyper_sdk",
34
- "hyper_sdk.akamai",
35
- "hyper_sdk.incapsula",
36
- "hyper_sdk.kasada",
37
- "hyper_sdk.datadome",
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hyper_sdk"
7
+ version = "2.13.0"
8
+ description = "Hyper Solutions Python SDK"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ classifiers = [
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python :: 3",
15
+ "Framework :: AsyncIO",
16
+ ]
17
+ dependencies = [
18
+ "httpx[http2]>=0.24.0",
19
+ "certifi>=2024.2.2",
20
+ "charset-normalizer>=3.3.2",
21
+ "idna>=3.6",
22
+ "jsonpickle>=3.0.3",
23
+ "PyJWT>=2.8.0",
24
+ "urllib3>=2.2.1"
25
+ ]
26
+ license = {file = "LICENSE"}
27
+
28
+ [project.urls]
29
+ homepage = "https://github.com/Hyper-Solutions/hyper-sdk-py"
30
+
31
+ [tool.setuptools]
32
+ packages = [
33
+ "hyper_sdk",
34
+ "hyper_sdk.akamai",
35
+ "hyper_sdk.incapsula",
36
+ "hyper_sdk.kasada",
37
+ "hyper_sdk.datadome",
38
38
  ]
File without changes
File without changes