├── LICENSE ├── README.md ├── hyper_sdk ├── __init__.py ├── akamai │ ├── __init__.py │ ├── pixel.py │ ├── script_path.py │ ├── sec_cpt.py │ └── stop_signal.py ├── akamai_input.py ├── datadome │ ├── __init__.py │ └── parse.py ├── datadome_input.py ├── incapsula │ ├── __init__.py │ ├── dynamic.py │ └── utmvc.py ├── incapsula_input.py ├── kasada │ ├── __init__.py │ └── parse.py ├── kasada_input.py └── session.py └── pyproject.toml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Hyper Solutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyper Solutions SDK for Python 2 | 3 | ## Installation 4 | 5 | To use the Hyper Solutions SDK in your Python project, you need to install it using the following command: 6 | 7 | ``` 8 | pip install hyper-sdk 9 | ``` 10 | 11 | ## Documentation 12 | 13 | For detailed documentation on how to use the SDK, including examples and API reference, please visit our documentation website: 14 | 15 | [https://docs.justhyped.dev/](https://docs.justhyped.dev/) 16 | 17 | ## Contributing 18 | 19 | If you find any issues or have suggestions for improvement, please open an issue or submit a pull request. 20 | 21 | ## License 22 | 23 | This SDK is licensed under the [MIT License](LICENSE). -------------------------------------------------------------------------------- /hyper_sdk/__init__.py: -------------------------------------------------------------------------------- 1 | from .akamai.pixel import * 2 | from .akamai.script_path import * 3 | from .akamai.sec_cpt import * 4 | from .akamai.stop_signal import * 5 | from .incapsula.utmvc import * 6 | from .incapsula.dynamic import * 7 | from .session import * 8 | from .kasada.parse import * 9 | from .datadome.parse import * 10 | -------------------------------------------------------------------------------- /hyper_sdk/akamai/__init__.py: -------------------------------------------------------------------------------- 1 | from .pixel import * 2 | from .script_path import * 3 | from .sec_cpt import * 4 | from .stop_signal import * 5 | -------------------------------------------------------------------------------- /hyper_sdk/akamai/pixel.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | pixel_html_expr = re.compile(r'bazadebezolkohpepadr="(\d+)"') 4 | pixel_script_url_expr = re.compile(r'src="(https?://.+/akam/\d+/\w+)"') 5 | pixel_script_var_expr = re.compile(r'g=_\[(\d+)]') 6 | pixel_script_string_array_expr = re.compile(r'var _=\[(.+?)];') 7 | pixel_script_strings_expr = re.compile(r'("[^",]*")') 8 | 9 | 10 | def parse_pixel_html_var(src: str) -> int: 11 | """ 12 | ParsePixelHtmlVar gets the required pixel challenge variable from the given HTML code src. 13 | 14 | Args: 15 | src (str): HTML source code as a string. 16 | 17 | Returns: 18 | int: The parsed pixel HTML variable. 19 | 20 | Raises: 21 | Exception: If the pixel HTML var is not found in the source. 22 | """ 23 | match = pixel_html_expr.search(src) 24 | if match: 25 | return int(match.group(1)) 26 | else: 27 | raise Exception("hyper-sdk: pixel HTML var not found") 28 | 29 | 30 | def parse_pixel_script_url(src: str) -> tuple[str, str]: 31 | """ 32 | ParsePixelScriptURL gets the script URL of the pixel challenge script and the URL 33 | to post a generated payload to from the given HTML code src. 34 | 35 | Args: 36 | src (str): HTML source code as a string. 37 | 38 | Returns: 39 | tuple[str, str]: A tuple containing the script URL and the post URL. 40 | 41 | Raises: 42 | Exception: If the script URL is not found in the source. 43 | """ 44 | match = pixel_script_url_expr.search(src) 45 | if match: 46 | script_url = match.group(1) 47 | # Create post_url 48 | parts = script_url.split("/") 49 | parts[-1] = "pixel_" + parts[-1] 50 | post_url = "/".join(parts) 51 | return script_url, post_url 52 | else: 53 | raise Exception("hyper-sdk: script URL not found") 54 | 55 | 56 | def parse_pixel_script_var(src: str) -> str: 57 | """ 58 | Gets the dynamic value from the pixel script. 59 | 60 | Args: 61 | src (str): HTML source code as a string. 62 | 63 | Returns: 64 | str: The dynamic value extracted from the pixel script. 65 | 66 | Raises: 67 | Exception: If the script variable is not found or if there are issues extracting it. 68 | """ 69 | index_match = pixel_script_var_expr.search(src) 70 | if not index_match: 71 | raise Exception("hyper-sdk: script var not found") 72 | string_index = int(index_match.group(1)) 73 | 74 | array_declaration_match = pixel_script_string_array_expr.search(src) 75 | if not array_declaration_match: 76 | raise Exception("hyper-sdk: script var not found") 77 | 78 | raw_strings = pixel_script_strings_expr.findall(array_declaration_match.group(1)) 79 | if string_index >= len(raw_strings): 80 | raise Exception("hyper-sdk: script var not found") 81 | 82 | string_value = raw_strings[string_index].strip('"') 83 | return string_value 84 | -------------------------------------------------------------------------------- /hyper_sdk/akamai/script_path.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # Precompiled regular expressions 4 | script_path_expr = re.compile(r'', 5 | re.IGNORECASE) 6 | 7 | 8 | def parse_script_path(src: str) -> str: 9 | """ 10 | Gets the Akamai Bot Manager web SDK path from the given HTML code src. 11 | 12 | This function searches the provided HTML source code for the path of a JavaScript script tag that matches the 13 | specified regular expression pattern. 14 | 15 | Args: 16 | src (str): The HTML source code as a string. 17 | 18 | Returns: 19 | str: The path of the script extracted from the script tag. 20 | 21 | Raises: 22 | Exception: If the script path is not found in the source. 23 | """ 24 | match = script_path_expr.search(src) 25 | if match: 26 | return match.group(1) 27 | else: 28 | raise "hyper-sdk: script path not found" 29 | -------------------------------------------------------------------------------- /hyper_sdk/akamai/sec_cpt.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import hashlib 3 | import json 4 | import os 5 | import re 6 | import time 7 | from collections import OrderedDict 8 | from typing import List 9 | 10 | sec_duration_expr = re.compile(r'data-duration=(\d+)') 11 | sec_challenge_expr = re.compile(r'challenge="(.*?)"') 12 | sec_page_expr = re.compile(r'data-duration=\d+\s+src="([^"]+)"') 13 | 14 | 15 | class SecCptChallengeData: 16 | def __init__(self, token: str, timestamp: int, nonce: str, difficulty: int, count: int): 17 | self.token = token 18 | self.timestamp = timestamp 19 | self.nonce = nonce 20 | self.difficulty = difficulty 21 | self.count = count 22 | 23 | 24 | class SecCptChallenge: 25 | def __init__(self, duration: int, challenge_path: str, challenge_data: SecCptChallengeData): 26 | self.duration = duration 27 | self.challenge_path = challenge_path 28 | self.challenge_data = challenge_data 29 | 30 | @staticmethod 31 | def parse(html: str) -> 'SecCptChallenge': 32 | challenge_data = SecCptChallenge._parse_challenge_data(html) 33 | duration = SecCptChallenge._parse_duration(html) 34 | challenge_path = SecCptChallenge._parse_challenge_path(html) 35 | 36 | return SecCptChallenge(duration, challenge_path, challenge_data) 37 | 38 | @staticmethod 39 | def parse_from_json(json_payload: str) -> 'SecCptChallenge': 40 | api_response = json.loads(json_payload) 41 | 42 | challenge_data = SecCptChallengeData( 43 | api_response.get('token', ''), 44 | api_response.get('timestamp', 0), 45 | api_response.get('nonce', ''), 46 | api_response.get('difficulty', 0) 47 | ) 48 | 49 | duration = api_response.get('chlg_duration', 0) 50 | challenge_path = api_response.get('branding_url_content', '') 51 | 52 | return SecCptChallenge(duration, challenge_path, challenge_data) 53 | 54 | @staticmethod 55 | def _parse_challenge_data(src: str) -> SecCptChallengeData: 56 | challenge_match = sec_challenge_expr.search(src) 57 | if not challenge_match: 58 | raise Exception("hyper-sdk: Challenge data not found.") 59 | 60 | decoded_challenge = base64.b64decode(challenge_match.group(1)) 61 | challenge_data = json.loads(decoded_challenge) 62 | 63 | return SecCptChallengeData( 64 | challenge_data.get('token', ''), 65 | challenge_data.get('timestamp', 0), 66 | challenge_data.get('nonce', ''), 67 | challenge_data.get('difficulty', 0), 68 | challenge_data.get('count', 0) 69 | ) 70 | 71 | @staticmethod 72 | def _parse_duration(src: str) -> int: 73 | duration_match = sec_duration_expr.search(src) 74 | if not duration_match: 75 | raise Exception("hyper-sdk: Duration not found.") 76 | 77 | try: 78 | duration = int(duration_match.group(1)) 79 | except ValueError as e: 80 | raise Exception(f"hyper-sdk: Invalid duration value: {e}") 81 | 82 | return duration 83 | 84 | @staticmethod 85 | def _parse_challenge_path(src: str) -> str: 86 | page_match = sec_page_expr.search(src) 87 | if not page_match: 88 | raise Exception("hyper-sdk: Challenge path not found.") 89 | 90 | return page_match.group(1) 91 | 92 | def generate_sec_cpt_payload(self, sec_cpt_cookie: str) -> str: 93 | sec, _, _ = sec_cpt_cookie.partition("~") 94 | if sec == sec_cpt_cookie: 95 | raise Exception("hyper-sdk: Malformed sec_cpt cookie.") 96 | 97 | answers = self._generate_sec_cpt_answers(sec) 98 | 99 | payload = OrderedDict([ 100 | ("token", self.challenge_data.token), 101 | ("answers", answers) 102 | ]) 103 | 104 | return json.dumps(payload) 105 | 106 | def sleep(self): 107 | time.sleep(self.duration) 108 | 109 | def _generate_sec_cpt_answers(self, sec: str) -> List[str]: 110 | answers = [] 111 | difficulty = self.challenge_data.difficulty 112 | 113 | while True: 114 | answer = f"0.{os.urandom(8).hex()}" 115 | hash_input = f"{sec}{self.challenge_data.timestamp}{self.challenge_data.nonce}{difficulty}{answer}" 116 | 117 | output = 0 118 | for byte in hashlib.sha256(hash_input.encode('ascii')).digest(): 119 | output = (output << 8) | byte 120 | output %= difficulty 121 | 122 | if output == 0: 123 | difficulty += 1 124 | answers.append(answer) 125 | 126 | if len(answers) == self.challenge_data.count: 127 | break 128 | continue 129 | 130 | return answers -------------------------------------------------------------------------------- /hyper_sdk/akamai/stop_signal.py: -------------------------------------------------------------------------------- 1 | def is_cookie_valid(cookie: str, request_count: int) -> bool: 2 | """ 3 | Determines if the provided _abck cookie value is valid, based on Akamai Bot Manager's 4 | client-side stop signal mechanism using the given request count. If the result is true, the client is ADVISED 5 | to halt further sensor data submissions. Submitting further would still produce a valid cookie but is unnecessary. 6 | 7 | The stop signal mechanism in the Akamai Bot Manager's client-side script informs a client that the cookie received is 8 | valid and that any additional submissions are superfluous. 9 | 10 | Args: 11 | cookie (str): The _abck cookie value. 12 | request_count (int): The number of requests made. 13 | 14 | Returns: 15 | bool: True if the cookie is valid, False otherwise. 16 | """ 17 | parts = cookie.split("~") 18 | if len(parts) < 2: 19 | return False 20 | 21 | try: 22 | request_threshold = int(parts[1]) 23 | except ValueError: 24 | request_threshold = -1 25 | 26 | return request_threshold != -1 and request_count >= request_threshold 27 | 28 | 29 | def is_cookie_invalidated(cookie: str) -> bool: 30 | """ 31 | Determines if the current session requires more sensors to be sent. 32 | 33 | Protected endpoints can invalidate a session by setting a new _abck cookie that ends in '~0~-1~-1' or similar. 34 | This function returns if such an invalidated cookie is present, if it is present you should be able to make the 35 | cookie valid again with only 1 sensor post. 36 | 37 | Args: 38 | cookie (str): The _abck cookie value. 39 | 40 | Returns: 41 | bool: True if the cookie has been invalidated, False otherwise. 42 | """ 43 | parts = cookie.split("~") 44 | if len(parts) < 4: 45 | return False 46 | 47 | try: 48 | signal = int(parts[3]) 49 | except ValueError: 50 | signal = -1 51 | 52 | return signal > -1 53 | -------------------------------------------------------------------------------- /hyper_sdk/akamai_input.py: -------------------------------------------------------------------------------- 1 | class SensorInput: 2 | def __init__(self, abck: str, bmsz: str, version: str, page_url: str, user_agent: str, ip: str, acceptLanguage: str, context: str, script_hash="", dynamic_values=""): 3 | self.abck = abck 4 | self.bmsz = bmsz 5 | self.version = version 6 | self.page_url = page_url 7 | self.user_agent = user_agent 8 | self.script_hash = script_hash 9 | self.dynamic_values = dynamic_values 10 | self.context = context 11 | self.ip = ip 12 | self.acceptLanguage = acceptLanguage 13 | 14 | 15 | class PixelInput: 16 | def __init__(self, user_agent: str, html_var: str, script_var: str, acceptLanguage: str, ip: str): 17 | self.user_agent = user_agent 18 | self.html_var = html_var 19 | self.script_var = script_var 20 | self.acceptLanguage = acceptLanguage 21 | self.ip = ip 22 | 23 | 24 | class DynamicInput: 25 | def __init__(self, script: str): 26 | self.script = script 27 | 28 | 29 | class SbsdInput: 30 | def __init__(self, user_agent: str, uuid: str, page_url: str, o_cookie: str, script: str, acceptLanguage: str, ip: str): 31 | self.user_agent = user_agent 32 | self.uuid = uuid 33 | self.page_url = page_url 34 | self.o_cookie = o_cookie 35 | self.script = script 36 | self.acceptLanguage = acceptLanguage 37 | self.ip = ip 38 | -------------------------------------------------------------------------------- /hyper_sdk/datadome/__init__.py: -------------------------------------------------------------------------------- 1 | from .parse import * 2 | -------------------------------------------------------------------------------- /hyper_sdk/datadome/parse.py: -------------------------------------------------------------------------------- 1 | import json 2 | from urllib.parse import urlencode 3 | 4 | 5 | def parse_slider_device_check_link(src: str, datadome_cookie: str, referer: str) -> str: 6 | """ 7 | Parse the device check URL for DataDome slider captcha from a blocked response body. 8 | 9 | This function extracts the necessary parameters from the DataDome JavaScript object 10 | embedded in the HTML source and constructs the URL for the slider captcha challenge. 11 | 12 | Args: 13 | src (str): The HTML source of the blocked page containing the DataDome JavaScript object. 14 | datadome_cookie (str): The current value of the 'datadome' cookie. 15 | referer (str): The referer URL to be included in the device check link. 16 | 17 | Returns: 18 | str: The constructed device check URL for the slider captcha. 19 | 20 | Raises: 21 | RuntimeError: If the dd object cannot be extracted or parsed, 22 | or if the proxy is blocked (indicated by 't' == 'bv'). 23 | """ 24 | try: 25 | dd_object = src.split("var dd=")[1].split("")[0] 26 | dd_object = dd_object.replace("'", '"') 27 | dd_object_parsed = json.loads(dd_object) 28 | except Exception as _: 29 | raise RuntimeError("Failed to parse dd object.") 30 | 31 | if dd_object_parsed.get("t") == "bv": 32 | raise RuntimeError("proxy blocked") 33 | 34 | params = { 35 | "initialCid": dd_object_parsed.get("cid"), 36 | "hash": dd_object_parsed.get("hsh"), 37 | "cid": datadome_cookie, 38 | "t": dd_object_parsed.get("t"), 39 | "referer": referer, 40 | "s": str(dd_object_parsed.get("s")), 41 | "e": dd_object_parsed.get("e"), 42 | "dm": "cd", 43 | } 44 | 45 | return f"https://geo.captcha-delivery.com/captcha/?{urlencode(params)}" 46 | 47 | 48 | def parse_interstitial_device_check_link(src: str, datadome_cookie: str, referer: str) -> str: 49 | """ 50 | Parse the device check URL for DataDome interstitial challenge from a blocked response body. 51 | 52 | This function extracts the necessary parameters from the DataDome JavaScript object 53 | embedded in the HTML source and constructs the URL for the interstitial challenge. 54 | 55 | Args: 56 | src (str): The HTML source of the blocked page containing the DataDome JavaScript object. 57 | datadome_cookie (str): The current value of the 'datadome' cookie. 58 | referer (str): The referer URL to be included in the device check link. 59 | 60 | Returns: 61 | str: The constructed device check URL for the interstitial challenge. 62 | 63 | Raises: 64 | RuntimeError: If the DataDome dd object cannot be extracted or parsed. 65 | """ 66 | try: 67 | dd_object = src.split("var dd=")[1].split("")[0] 68 | dd_object = dd_object.replace("'", '"') 69 | dd_object_parsed = json.loads(dd_object) 70 | except Exception as _: 71 | raise RuntimeError("Failed to parse dd object.") 72 | 73 | params = { 74 | "initialCid": dd_object_parsed.get("cid"), 75 | "hash": dd_object_parsed.get("hsh"), 76 | "cid": datadome_cookie, 77 | "referer": referer, 78 | "s": str(dd_object_parsed.get("s")), 79 | "b": str(dd_object_parsed.get("b")), 80 | "dm": "cd", 81 | } 82 | 83 | return f"https://geo.captcha-delivery.com/interstitial/?{urlencode(params)}" 84 | -------------------------------------------------------------------------------- /hyper_sdk/datadome_input.py: -------------------------------------------------------------------------------- 1 | 2 | class DataDomeSliderInput: 3 | def __init__(self, user_agent: str, device_link: str, html: str, puzzle: str, piece: str, parent_url: str, acceptLanguage: str, ip: str): 4 | # UserAgent must be a Chrome Windows User-Agent. 5 | self.user_agent = user_agent 6 | 7 | # DeviceLink is the URL that contains the script and starts like this: 8 | # https://geo.captcha-delivery.com/captcha/?initialCid 9 | self.device_link = device_link 10 | 11 | # Html is the response body of the GET request to the DeviceLink 12 | self.html = html 13 | 14 | # Puzzle is the captcha puzzle image bytes, base64 encoded. 15 | # The URL that returns the puzzle looks like this: 16 | # https://dd.prod.captcha-delivery.com/image/2024-xx-xx/hash.jpg 17 | self.puzzle = puzzle 18 | 19 | # Piece is the captcha puzzle piece image bytes, base64 encoded. 20 | # The URL that returns the puzzle looks like this: 21 | # https://dd.prod.captcha-delivery.com/image/2024-xx-xx/hash.frag.png 22 | self.piece = piece 23 | 24 | self.parent_url = parent_url 25 | self.acceptLanguage = acceptLanguage 26 | self.ip = ip 27 | 28 | def to_dict(self): 29 | return { 30 | "userAgent": self.user_agent, 31 | "deviceLink": self.device_link, 32 | "html": self.html, 33 | "puzzle": self.puzzle, 34 | "piece": self.piece, 35 | "parentUrl": self.parent_url, 36 | "acceptLanguage": self.acceptLanguage, 37 | "ip": self.ip, 38 | } 39 | 40 | 41 | class DataDomeInterstitialInput: 42 | def __init__(self, user_agent: str, device_link: str, html: str, acceptLanguage: str, ip: str): 43 | # UserAgent must be a Chrome Windows User-Agent. 44 | self.user_agent = user_agent 45 | 46 | # DeviceLink is the URL that contains the script and starts like this: 47 | # https://geo.captcha-delivery.com/captcha/?initialCid 48 | self.device_link = device_link 49 | 50 | # Html is the response body of the GET request to the DeviceLink 51 | self.html = html 52 | 53 | self.acceptLanguage = acceptLanguage 54 | self.ip = ip 55 | 56 | def to_dict(self): 57 | return { 58 | "userAgent": self.user_agent, 59 | "deviceLink": self.device_link, 60 | "html": self.html, 61 | "acceptLanguage": self.acceptLanguage, 62 | "ip": self.ip, 63 | } 64 | 65 | 66 | class DataDomeTagsInput: 67 | def __init__(self, user_agent: str, cid: str, ddk: str, referer: str, tags_type: str, version: str, acceptLanguage: str, ip: str): 68 | # UserAgent must be a Chrome Windows User-Agent. 69 | self.user_agent = user_agent 70 | self.cid = cid 71 | self.ddk = ddk 72 | self.referer = referer 73 | self.tags_type = tags_type 74 | self.version = version 75 | self.acceptLanguage = acceptLanguage 76 | self.ip = ip 77 | 78 | def to_dict(self): 79 | return { 80 | "userAgent": self.user_agent, 81 | "cid": self.cid, 82 | "ddk": self.ddk, 83 | "referer": self.referer, 84 | "type": self.tags_type, 85 | "acceptLanguage": self.acceptLanguage, 86 | "ip": self.ip, 87 | "version": self.version, 88 | } -------------------------------------------------------------------------------- /hyper_sdk/incapsula/__init__.py: -------------------------------------------------------------------------------- 1 | from .utmvc import * 2 | from .dynamic import * 3 | -------------------------------------------------------------------------------- /hyper_sdk/incapsula/dynamic.py: -------------------------------------------------------------------------------- 1 | import re 2 | from urllib.parse import urlparse 3 | 4 | # Precompiled regular expressions 5 | reese_script_regex = re.compile(r'src\s*=\s*"((/[^/]+/\d+)(?:\?.*)?)"') 6 | 7 | 8 | def parse_dynamic_reese_script(html_content: str, url_str: str) -> tuple[str, str]: 9 | """ 10 | Parses the sensor path and script path from the given HTML content. 11 | 12 | This function searches the provided HTML for a script element containing a specific pattern 13 | and extracts both the sensor path (shortened path) and script path (the full path). 14 | It requires that the HTML contains "Pardon Our Interruption" to confirm it's the correct page type. 15 | It also takes a URL string, extracts the hostname, and appends it to the sensor path. 16 | 17 | Args: 18 | html_content (str): The HTML content to parse. 19 | url_str (str): The URL string to extract the hostname from. 20 | 21 | Returns: 22 | tuple[str, str]: A tuple containing the sensor path (with hostname) and script path. 23 | 24 | Raises: 25 | ValueError: If the URL is invalid. 26 | Exception: If the page is not an interruption page or if the Reese script is not found. 27 | """ 28 | # Parse the URL to extract hostname 29 | try: 30 | parsed_url = urlparse(url_str) 31 | hostname = parsed_url.netloc 32 | except Exception: 33 | raise ValueError("hyper-sdk: invalid URL") 34 | 35 | # Verify this is an interruption page 36 | if "Pardon Our Interruption" not in html_content: 37 | raise Exception("hyper-sdk: not an interruption page") 38 | 39 | # Find the Reese script 40 | match = reese_script_regex.search(html_content) 41 | if not match or len(match.groups()) < 2: 42 | raise Exception("hyper-sdk: reese script not found") 43 | 44 | script_path = match.group(1) 45 | sensor_path = match.group(2) 46 | 47 | # Append the hostname to the sensor path 48 | return f"{sensor_path}?d={hostname}", script_path -------------------------------------------------------------------------------- /hyper_sdk/incapsula/utmvc.py: -------------------------------------------------------------------------------- 1 | import re 2 | import random 3 | 4 | # Precompiled regular expressions 5 | script_regex = re.compile(r'src="(/_Incapsula_Resource\?[^"]*)"') 6 | 7 | 8 | def parse_utmvc_script_path(script_content: str) -> str: 9 | """ 10 | Parses the UTMVC script path from the given script content. 11 | 12 | This function searches the provided script content for a specific pattern matching the UTMVC script path 13 | using a precompiled regular expression. It extracts and returns the first match if found. 14 | 15 | Args: 16 | script_content (str): The content of the script from which the UTMVC script path is to be extracted. 17 | 18 | Returns: 19 | str: The extracted UTMVC script path. 20 | 21 | Raises: 22 | Exception: If the UTMVC script path is not found in the script content. 23 | """ 24 | match = script_regex.search(script_content) 25 | if match: 26 | return match.group(1) 27 | else: 28 | raise Exception("hyper-sdk: utmvc script not found") 29 | 30 | 31 | def get_utmvc_submit_path() -> str: 32 | """ 33 | Generates a UTMVC submit path with a unique random query parameter. 34 | 35 | This function constructs a submit path for the UTMVC script by appending a random floating-point number as a query 36 | parameter. The random number is used to ensure uniqueness of the request. 37 | 38 | Returns: 39 | str: A unique UTMVC submit path. 40 | """ 41 | random_float = random.random() 42 | return f"/_Incapsula_Resource?SWKMTFSR=1&e={random_float:g}" 43 | -------------------------------------------------------------------------------- /hyper_sdk/incapsula_input.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | class UtmvcInput: 5 | def __init__(self, user_agent: str, session_ids: List[str], script: str): 6 | self.user_agent = user_agent 7 | self.session_ids = session_ids 8 | self.script = script 9 | 10 | class ReeseInput: 11 | def __init__(self, user_agent: str, acceptLanguage: str, ip: str, pageUrl: str, scriptUrl: str = ""): 12 | self.user_agent = user_agent 13 | self.acceptLanguage = acceptLanguage 14 | self.ip = ip 15 | self.scriptUrl = scriptUrl 16 | self.pageUrl = pageUrl 17 | -------------------------------------------------------------------------------- /hyper_sdk/kasada/__init__.py: -------------------------------------------------------------------------------- 1 | from .parse import * 2 | -------------------------------------------------------------------------------- /hyper_sdk/kasada/parse.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # Precompiled regular expression 4 | script_path_expr = re.compile(r' str: 8 | """ 9 | Gets the Akamai Bot Manager web SDK path from the given HTML code src. 10 | 11 | This function searches the provided HTML source code for the path of a JavaScript script tag that matches the 12 | specified regular expression pattern. 13 | 14 | Args: 15 | src (str): The HTML source code as a string. 16 | 17 | Returns: 18 | str: The path of the script extracted from the script tag. 19 | 20 | Raises: 21 | Exception: If the script path is not found in the source. 22 | """ 23 | match = script_path_expr.search(src) 24 | if match: 25 | return re.sub(r'&', '&', match.group(1)) 26 | else: 27 | raise Exception("hyper-sdk: script path not found") 28 | -------------------------------------------------------------------------------- /hyper_sdk/kasada_input.py: -------------------------------------------------------------------------------- 1 | class KasadaPowInput: 2 | def __init__(self, st: int, ct: str, work_time: int = None): 3 | # St is the x-kpsdk-st value returned by the /tl POST request 4 | self.st = st 5 | # Ct is the x-kpsdk-ct value returned by the /tl POST request 6 | self.ct = ct 7 | # WorkTime can be used to pre-generate POW strings 8 | self.work_time = work_time 9 | 10 | def to_dict(self): 11 | result = {"st": self.st, "ct": self.ct} 12 | if self.work_time is not None: 13 | result["workTime"] = self.work_time 14 | return result 15 | 16 | 17 | class KasadaPayloadInput: 18 | def __init__(self, user_agent: str, ips_link: str, script: str, acceptLanguage: str, ip: str): 19 | # UserAgent must be a Chrome Windows User-Agent. 20 | self.user_agent = user_agent 21 | 22 | # IpsLink is the ips.js script link, parsed from the block page (429 status code) 23 | self.ips_link = ips_link 24 | 25 | # Script is the ips.js script retrieved using the IpsLink url 26 | self.script = script 27 | 28 | # Your accept-language header 29 | self.acceptLanguage = acceptLanguage 30 | self.ip = ip 31 | 32 | def to_dict(self): 33 | result = { 34 | "userAgent": self.user_agent, 35 | "ipsLink": self.ips_link, 36 | "script": self.script, 37 | "acceptLanguage": self.acceptLanguage, 38 | "ip": self.ip, 39 | } 40 | return result 41 | -------------------------------------------------------------------------------- /hyper_sdk/session.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Dict, Any 2 | from urllib.parse import quote 3 | 4 | import requests 5 | import jwt 6 | from datetime import datetime, timedelta, timezone 7 | 8 | from .akamai_input import SensorInput, PixelInput, DynamicInput, SbsdInput 9 | from .kasada_input import KasadaPowInput, KasadaPayloadInput 10 | from .datadome_input import DataDomeSliderInput, DataDomeInterstitialInput, DataDomeTagsInput 11 | from .incapsula_input import UtmvcInput, ReeseInput 12 | 13 | 14 | class Session: 15 | def __init__(self, api_key: str, jwt_key: Optional[str] = None, client: Optional[requests.Session] = None) -> None: 16 | self.api_key = api_key 17 | self.jwt_key = jwt_key 18 | self.client = requests.Session() if client is None else client 19 | 20 | def generate_sensor_data(self, input_data: SensorInput) -> tuple[str, str]: 21 | """ 22 | Returns the sensor data required to generate valid akamai cookies using the Hyper Solutions API. 23 | 24 | Args: 25 | input_data (SensorInput): An instance of SensorInput containing the necessary data for generating the sensor data. 26 | 27 | Returns: 28 | str: Sensor data as a string. 29 | str: Context data as a string. 30 | """ 31 | sensor_endpoint = "https://akm.hypersolutions.co/v2/sensor" 32 | if not self.api_key: 33 | raise ValueError("Missing API key") 34 | 35 | headers = { 36 | 'Content-Type': 'application/json', 37 | 'Accept-Encoding': 'gzip', 38 | 'X-Api-Key': self.api_key 39 | } 40 | 41 | if self.jwt_key: 42 | signature = self.generate_signature() 43 | headers['X-Signature'] = signature 44 | 45 | response = self.client.post(sensor_endpoint, headers=headers, json={ 46 | 'userAgent': input_data.user_agent, 47 | 'abck': input_data.abck, 48 | 'bmsz': input_data.bmsz, 49 | 'version': input_data.version, 50 | 'pageUrl': input_data.page_url, 51 | 'scriptHash': input_data.script_hash, 52 | 'dynamicValues': input_data.dynamic_values, 53 | 'context': input_data.context, 54 | 'ip': input_data.ip, 55 | 'acceptLanguage': input_data.acceptLanguage, 56 | }) 57 | 58 | response_data = response.json() 59 | 60 | if "error" in response_data and response_data["error"]: 61 | raise Exception(f"API returned with error: {response_data['error']}") 62 | 63 | if response.status_code != 200: 64 | raise Exception(f"API returned with status code: {response.status_code}") 65 | 66 | return response_data["payload"], response_data["context"] 67 | 68 | def generate_sbsd_data(self, input_data: SbsdInput) -> str: 69 | """ 70 | Returns the sbsd data required to solve SBSD using the Hyper Solutions API. 71 | 72 | Args: 73 | input_data (SbsdInput): An instance of SbsdInput containing the necessary data for generating the sbsd data. 74 | 75 | Returns: 76 | str: Sensor data as a string. 77 | """ 78 | sensor_endpoint = "https://akm.hypersolutions.co/sbsd" 79 | return self._send_request(sensor_endpoint, { 80 | 'userAgent': input_data.user_agent, 81 | 'uuid': input_data.uuid, 82 | 'pageUrl': input_data.page_url, 83 | 'o': input_data.o_cookie, 84 | 'script': input_data.script, 85 | 'acceptLanguage': input_data.acceptLanguage, 86 | 'ip': input_data.ip, 87 | }) 88 | 89 | def parse_v3_dynamic(self, input_data: DynamicInput) -> str: 90 | """ 91 | Returns the dynamic values required to generate sensor data for V3 dynamic with Hyper Solutions API. 92 | 93 | Args: 94 | input_data (DynamicInput): An instance of DynamicInput containing the necessary data for parsing the script. 95 | 96 | Returns: 97 | str: Dynamic values as a string. 98 | """ 99 | sensor_endpoint = "https://akm.hypersolutions.co/v3dynamic" 100 | return self._send_request(sensor_endpoint, { 101 | 'script': input_data.script, 102 | }) 103 | 104 | def generate_pixel_data(self, input_data: PixelInput) -> str: 105 | """ 106 | Returns the pixel data using the Hyper Solutions API. 107 | 108 | Args: 109 | session (Session): An instance of Session to handle the network request. 110 | input_data (PixelInput): An instance of PixelInput containing the necessary data for generating the pixel data. 111 | 112 | Returns: 113 | str: Pixel data as a string. 114 | """ 115 | pixel_endpoint = "https://akm.hypersolutions.co/pixel" 116 | return self._send_request(pixel_endpoint, { 117 | 'userAgent': input_data.user_agent, 118 | 'htmlVar': input_data.html_var, 119 | 'scriptVar': input_data.script_var, 120 | 'ip': input_data.ip, 121 | 'acceptLanguage': input_data.acceptLanguage, 122 | }) 123 | 124 | def generate_reese84_sensor(self, site: str, input_data: ReeseInput) -> str: 125 | """ 126 | Returns the sensor data required to generate valid reese84 cookies using the Hyper Solutions API. 127 | 128 | This function sends a request to the specified sensor endpoint with the necessary data to generate the reese84 sensor data. 129 | 130 | Args: 131 | site (str): The name of the site that will be used to generate the sensor data. 132 | input_data (ReeseInput): The input data. 133 | 134 | Returns: 135 | str: Sensor data as a string. 136 | 137 | Raises: 138 | ValueError: If the script attribute in input_data is empty. 139 | """ 140 | return self._send_request("https://incapsula.hypersolutions.co/reese84/" + quote(site), 141 | { 142 | 'userAgent': input_data.user_agent, 143 | 'acceptLanguage': input_data.acceptLanguage, 144 | 'ip': input_data.ip, 145 | 'scriptUrl': input_data.scriptUrl, 146 | 'pageUrl': input_data.pageUrl, 147 | }) 148 | 149 | def generate_utmvc_cookie(self, input_data: UtmvcInput) -> str: 150 | """ 151 | Returns the utmvc cookie using the Hyper Solutions API. 152 | 153 | This function sends a request to the utmvc sensor endpoint with the necessary data to generate the utmvc cookie. 154 | The input data must include a non-empty script and session IDs. 155 | 156 | Args: 157 | session (Session): An instance of Session to handle the network request. 158 | input_data (Input): An instance of Input containing the user agent, session IDs, and script. 159 | 160 | Returns: 161 | str: The utmvc cookie as a string. 162 | str: The swhanedl parameter. 163 | 164 | Raises: 165 | ValueError: If the script attribute or session IDs in input_data are empty. 166 | """ 167 | if not self.api_key: 168 | raise ValueError("Missing API key") 169 | 170 | headers = { 171 | 'Content-Type': 'application/json', 172 | 'Accept-Encoding': 'gzip', 173 | 'X-Api-Key': self.api_key 174 | } 175 | 176 | if self.jwt_key: 177 | signature = self.generate_signature() 178 | headers['X-Signature'] = signature 179 | 180 | response = self.client.post("https://incapsula.hypersolutions.co/utmvc", headers=headers, json={ 181 | 'userAgent': input_data.user_agent, 182 | 'sessionIds': input_data.session_ids, 183 | 'script': input_data.script, 184 | }) 185 | 186 | response_data = response.json() 187 | 188 | if "error" in response_data and response_data["error"]: 189 | raise Exception(f"API returned with error: {response_data['error']}") 190 | 191 | if response.status_code != 200: 192 | raise Exception(f"API returned with status code: {response.status_code}") 193 | 194 | return response_data["payload"], response_data["swhanedl"] 195 | 196 | 197 | def generate_kasada_pow(self, input_data: KasadaPowInput) -> str: 198 | """ 199 | Returns the x-kpsdk-cd value using the Hyper Solutions API. 200 | 201 | Args: 202 | session (Session): An instance of Session to handle the network request. 203 | input_data (Input): An instance of Input containing the st and optionally workTime. 204 | 205 | Returns: 206 | str: The x-kpsdk-cd value as a string. 207 | """ 208 | return self._send_request("https://kasada.hypersolutions.co/cd", input_data.to_dict()) 209 | 210 | def generate_kasada_payload(self, input_data: KasadaPayloadInput) -> tuple[str, dict]: 211 | """ 212 | Returns a base64 encoded payload and headers using the Hyper Solutions API. 213 | 214 | Args: input_data (KasadaPayloadInput): An instance of KasadaPayloadInput containing the userAgent, 215 | ipsLink and script. 216 | 217 | Returns: tuple[str, dict]: A tuple containing the base64 encoded payload (to POST to /tl) as a string and a 218 | dictionary of headers. 219 | """ 220 | if not self.api_key: 221 | raise ValueError("Missing API key") 222 | 223 | headers = { 224 | 'Content-Type': 'application/json', 225 | 'Accept-Encoding': 'gzip', 226 | 'X-Api-Key': self.api_key 227 | } 228 | 229 | if self.jwt_key: 230 | signature = self.generate_signature() 231 | headers['X-Signature'] = signature 232 | 233 | response = self.client.post("https://kasada.hypersolutions.co/payload", headers=headers, json=input_data.to_dict()) 234 | 235 | response_data = response.json() 236 | 237 | if "error" in response_data and response_data["error"]: 238 | raise Exception(f"API returned with error: {response_data['error']}") 239 | 240 | if response.status_code != 200: 241 | raise Exception(f"API returned with status code: {response.status_code}") 242 | 243 | return response_data["payload"], response_data["headers"] 244 | 245 | def generate_interstitial_payload(self, input_data: DataDomeInterstitialInput) -> Dict[str, Any]: 246 | """ 247 | Returns the DataDome interstitial payload value and response headers using the Hyper Solutions API. 248 | 249 | Args: 250 | input_data (DataDomeInterstitialInput): An instance of DataDomeInterstitialInput. 251 | 252 | Returns: 253 | Dict[str, Any]: A dictionary containing: 254 | - payload (str): The payload to post to /interstitial/ 255 | - headers (Dict[str, str]): The response headers 256 | """ 257 | return self._send_request_with_headers("https://datadome.hypersolutions.co/interstitial", input_data.to_dict()) 258 | 259 | def generate_slider_payload(self, input_data: DataDomeSliderInput) -> Dict[str, Any]: 260 | """ 261 | Returns the DataDome Slider URL value and response headers using the Hyper Solutions API. 262 | 263 | Args: 264 | input_data (DataDomeSliderInput): An instance of DataDomeSliderInput. 265 | 266 | Returns: 267 | Dict[str, Any]: A dictionary containing: 268 | - payload (str): The URL to make a GET request to for a solved datadome cookie 269 | - headers (Dict[str, str]): The response headers 270 | """ 271 | return self._send_request_with_headers("https://datadome.hypersolutions.co/slider", input_data.to_dict()) 272 | 273 | def generate_tags_payload(self, input_data: DataDomeTagsInput) -> str: 274 | """ 275 | Returns the DataDome Tags payload using the Hyper Solutions API. 276 | 277 | Args: 278 | session (Session): An instance of Session to handle the network request. 279 | input_data (DataDomeTagsInput): An instance of DataDomeTagsInput. 280 | 281 | Returns: 282 | str: The tags payload. 283 | """ 284 | return self._send_request("https://datadome.hypersolutions.co/tags", input_data.to_dict()) 285 | 286 | def generate_signature(self) -> str: 287 | claims = { 288 | "key": self.api_key, 289 | "exp": datetime.now(timezone.utc) + timedelta(seconds=60) 290 | } 291 | token = jwt.encode(claims, self.jwt_key, algorithm='HS256') 292 | return token.decode('utf-8') 293 | 294 | def _send_request(self, url: str, input_data: Dict[str, Any]) -> str: 295 | if not self.api_key: 296 | raise ValueError("Missing API key") 297 | 298 | headers = { 299 | 'Content-Type': 'application/json', 300 | 'Accept-Encoding': 'gzip', 301 | 'X-Api-Key': self.api_key 302 | } 303 | 304 | if self.jwt_key: 305 | signature = self.generate_signature() 306 | headers['X-Signature'] = signature 307 | 308 | response = self.client.post(url, headers=headers, json=input_data) 309 | 310 | response_data = response.json() 311 | 312 | if "error" in response_data and response_data["error"]: 313 | raise Exception(f"API returned with error: {response_data['error']}") 314 | 315 | if response.status_code != 200: 316 | raise Exception(f"API returned with status code: {response.status_code}") 317 | 318 | return response_data["payload"] 319 | 320 | def _send_request_with_headers(self, url: str, input_data: Dict[str, Any]) -> Dict[str, Any]: 321 | if not self.api_key: 322 | raise ValueError("Missing API key") 323 | 324 | headers = { 325 | 'Content-Type': 'application/json', 326 | 'Accept-Encoding': 'gzip', 327 | 'X-Api-Key': self.api_key 328 | } 329 | 330 | if self.jwt_key: 331 | signature = self.generate_signature() 332 | headers['X-Signature'] = signature 333 | 334 | response = self.client.post(url, headers=headers, json=input_data) 335 | 336 | response_data = response.json() 337 | 338 | if "error" in response_data and response_data["error"]: 339 | raise Exception(f"API returned with error: {response_data['error']}") 340 | 341 | if response.status_code != 200: 342 | raise Exception(f"API returned with status code: {response.status_code}") 343 | 344 | return { 345 | "payload": response_data["payload"], 346 | "headers": response_data["headers"] 347 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "hyper_sdk" 7 | version = "2.1.0" 8 | description = "Hyper Solutions Python SDK" 9 | readme = "README.md" 10 | requires-python = ">=3.6" 11 | classifiers = [ 12 | "Intended Audience :: Developers", 13 | "License :: OSI Approved :: MIT License", 14 | "Programming Language :: Python :: 3", 15 | ] 16 | dependencies = [ 17 | "certifi>=2024.2.2", 18 | "charset-normalizer>=3.3.2", 19 | "idna>=3.6", 20 | "jsonpickle>=3.0.3", 21 | "PyJWT>=2.8.0", 22 | "requests>=2.31.0", 23 | "urllib3>=2.2.1", 24 | ] 25 | license = {file = "LICENSE"} 26 | 27 | [project.urls] 28 | homepage = "https://github.com/Hyper-Solutions/hyper-sdk-py" 29 | 30 | [tool.setuptools] 31 | packages = ["hyper_sdk", "hyper_sdk.akamai", "hyper_sdk.incapsula", "hyper_sdk.kasada", "hyper_sdk.datadome"] --------------------------------------------------------------------------------