├── config.json ├── README.md ├── logo.txt ├── LICENSE ├── .gitignore ├── main.py └── pyhq.py /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "login_token": "" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hqwords-client 2 | 3 | hqwords-client is a client for HQ Words written in python. 4 | 5 | ### Usage 6 | 7 | This is provided for educational purposes only. Actual usage of this would violate HQ Trivia's terms of service. 8 | 9 | License 10 | ---- 11 | 12 | MIT 13 | -------------------------------------------------------------------------------- /logo.txt: -------------------------------------------------------------------------------- 1 | .:: .:: .:::: .:: .:: .:: 2 | .:: .:: .:: .:: .:: .:: .:: 3 | .:: .::.:: .:: .:: .: .:: .:: .: .::: .:: .:::: 4 | .:::::: .::.:: .:: .:: .:: .:: .:: .:: .:: .:: .::.:: 5 | .:: .::.:: .:: .:: .: .:: .::.:: .:: .:: .: .:: .::: 6 | .:: .:: .:: .: .:: .: .: .:::: .:: .:: .:: .: .:: .:: 7 | .:: .:: .:: :: .:: .:: .:: .::: .:: .::.:: .:: 8 | .: -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 SilliBird 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. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import websockets 3 | import json 4 | import pyhq 5 | import aioconsole 6 | from os import system, name 7 | from colorama import init, Fore, Style 8 | from datetime import datetime 9 | from random import choice, uniform 10 | 11 | debug = False 12 | 13 | init() 14 | 15 | def outputMessage(msg): 16 | out = (f'{str(datetime.now()).split()[1][:8]} {msg}') 17 | print(out) 18 | if 'DEBUG' in msg: 19 | with open('game.log', 'a+') as f: 20 | f.write(f'{out}\n') 21 | 22 | def loadJson(fn): 23 | try: 24 | with open(fn) as f: 25 | fr = json.load(f) 26 | except: fr = json.load("[]") 27 | return fr 28 | 29 | def writeJson(fn, nd): 30 | try: 31 | with open(fn, 'w') as f: 32 | json.dump(nd, f, indent=2) 33 | return True 34 | except: return False 35 | 36 | def clearOutput(): 37 | system('cls' if name == 'nt' else 'clear') 38 | 39 | def displayLogo(): 40 | randomColors = [Fore.RED, Fore.GREEN, Fore.YELLOW, Fore.BLUE, Fore.MAGENTA, Fore.CYAN, Fore.WHITE] 41 | color = choice(randomColors) 42 | with open('logo.txt', 'r') as f: 43 | print() 44 | for line in f: 45 | print (color + line, end='') 46 | print(Style.RESET_ALL + '\n') 47 | 48 | def displayMainMenu(): 49 | choice = None 50 | while True: 51 | clearOutput() 52 | displayLogo() 53 | print (f'{Fore.CYAN}[{Fore.WHITE}1{Fore.CYAN}] {Fore.WHITE}Play Game{Fore.CYAN} [{Fore.WHITE}2{Fore.CYAN}] {Fore.WHITE}Account Setup{Fore.CYAN} [{Fore.WHITE}3{Fore.CYAN}] {Fore.WHITE}Information') 54 | print(Style.RESET_ALL) 55 | choice = input("=> ") 56 | if not choice in ['1', '2', '3']: 57 | continue 58 | return choice 59 | 60 | def menuSwitch(): 61 | choice = int(displayMainMenu()) 62 | if choice == 3: 63 | print ("\nCreated with love by SilliBird") 64 | print ("HQ Trivia is a registered trademark of Intermedia Labs.") 65 | input (f"\n{Fore.CYAN}Hit enter to continue. {Style.RESET_ALL}") 66 | menuSwitch() 67 | elif choice == 2: 68 | loginToken = input (f"\nNew login token to use: ") 69 | config = loadJson('config.json') 70 | config['login_token'] = loginToken 71 | writeJson('config.json', config) 72 | input (f"\n{Fore.CYAN}Hit enter to continue. {Style.RESET_ALL}") 73 | menuSwitch() 74 | elif choice == 1: 75 | config = loadJson('config.json') 76 | if not config['login_token']: 77 | print ("\nYou need to set a login token in the Account Setup") 78 | input (f"\n{Fore.CYAN}Hit enter to continue. {Style.RESET_ALL}") 79 | menuSwitch() 80 | asyncio.get_event_loop().run_until_complete(playGame()) 81 | 82 | async def cooldown(): 83 | await asyncio.sleep(round(uniform(7.5, 8.5), 2)) 84 | outputMessage(f"[HQ Words] {Fore.GREEN}It's safe to answer!{Style.RESET_ALL}") 85 | 86 | async def playGame(): 87 | loginToken = loadJson('config.json')['login_token'] 88 | client = pyhq.HQClient(loginToken) 89 | outputMessage(f"[HQ Words] Connected as {Fore.YELLOW}{client.me().username}{Style.RESET_ALL} with an unclaimed balance of {Fore.YELLOW}{client.me().leaderboard.unclaimed}{Style.RESET_ALL}") 90 | schedule = client.schedule() 91 | if debug: outputMessage(f"[DEBUG] {schedule}") 92 | if not schedule['active'] == True or not schedule['showType'] == 'hq-words': 93 | outputMessage(f"[HQ Words] {Fore.YELLOW}Words isn't live.{Style.RESET_ALL}") 94 | input (f"\n{Fore.CYAN}Hit enter to continue. {Style.RESET_ALL}") 95 | menuSwitch() 96 | else: 97 | outputMessage(f"[HQ Words] {Fore.YELLOW}It's go time.{Style.RESET_ALL}") 98 | broadcastId = schedule['broadcast']['broadcastId'] 99 | websocketURL = client.socket_url() 100 | subscribed = False 101 | puzzleState = '' 102 | async with websockets.connect(websocketURL, extra_headers={'Authorization': 'Bearer ' + client.auth_token}) as websocket: 103 | async for msg in websocket: 104 | try: 105 | obj = json.loads(msg.encode("utf-8")) 106 | except: pass 107 | 108 | if obj.get('itemId') == 'chat': 109 | if debug: 110 | outputMessage(f'[DEBUG] Disabling chat.') 111 | await websocket.send(json.dumps({'type': 'chatVisibilityToggled', 'chatVisible': False})) 112 | elif debug and not obj.get('type') == 'broadcastStats' and not obj.get('type') == 'gameStatus': 113 | outputMessage(f'[DEBUG] {obj}') 114 | 115 | if not subscribed: 116 | subscribed = True 117 | await websocket.send(json.dumps({'type': 'subscribe', 'broadcastId': int(broadcastId), 'gameType': 'words'})) 118 | 119 | if obj.get('type') == 'showWheel': 120 | letter = obj.get('letters')[0] 121 | await asyncio.sleep(0.5) 122 | await websocket.send(json.dumps({'showId': int(obj.get('showId')), 'type': 'spin', 'nearbyIds': [], 'letter': letter})) 123 | outputMessage(f"[HQ Words] {Fore.YELLOW}Sent spun letter {letter}{Style.RESET_ALL}") 124 | 125 | if obj.get('type') == 'startRound': 126 | puzzleState = obj.get('puzzleState') 127 | outputMessage(f"[HQ Words] {Fore.YELLOW}{puzzleState}{Style.RESET_ALL}") 128 | asyncio.get_event_loop().create_task(cooldown()) 129 | solution = await aioconsole.ainput("=> ") 130 | for char in set(list(solution.upper().replace(' ',''))): 131 | for state in puzzleState: 132 | if char in state: 133 | continue 134 | await websocket.send(json.dumps({'roundId': int(obj.get('roundId')), 'type': 'guess', 'showId': int(obj.get('showId')), 'letter': char})) 135 | outputMessage(f"[HQ Words] {Fore.YELLOW}Guessed character {char}{Style.RESET_ALL}") 136 | outputMessage(f"[HQ Words] {Fore.YELLOW}Done solving!{Style.RESET_ALL}") 137 | 138 | if obj.get('type') == 'endRound': 139 | if obj.get('solved') == True: 140 | outputMessage(f"[HQ Words] {Fore.YELLOW}Solved in {round(int(obj.get('completionTime')) * .001, 3)} seconds!{Style.RESET_ALL}") 141 | else: 142 | outputMessage(f"[HQ Words] {Fore.YELLOW}Failed to solve :({Style.RESET_ALL}") 143 | 144 | 145 | if obj.get('type') == 'letterReveal': 146 | puzzleState = obj.get('puzzleState') 147 | outputMessage(f"[HQ Words] {Fore.YELLOW}{puzzleState}{Style.RESET_ALL}") 148 | 149 | await websocket.ping() 150 | 151 | menuSwitch() -------------------------------------------------------------------------------- /pyhq.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import re 4 | import datetime 5 | import os 6 | import time 7 | 8 | _first_re = re.compile("(.)([A-Z][a-z]+)") 9 | _cap_re = re.compile("([a-z0-9])([A-Z])") 10 | def _to_snake(name): 11 | s1 = _first_re.sub(r"\1_\2", name) 12 | return _cap_re.sub(r"\1_\2", s1).lower() 13 | 14 | 15 | class HQUserLeaderboard: 16 | def __init__(self, **kwargs): 17 | self.total_cents = kwargs.get("total_cents") 18 | self.total = kwargs.get("total") 19 | self.unclaimed = kwargs.get("unclaimed") 20 | for p in ("alltime", "weekly"): 21 | for v in ("wins", "total", "rank"): 22 | try: 23 | setattr(self, f"{p}_{v}", kwargs.get(p).get(v)) 24 | except: 25 | pass 26 | 27 | 28 | class HQUserInfo: 29 | def __init__(self, **kwargs): 30 | self.user_id = kwargs.get("user_id") 31 | self.username = kwargs.get("username") 32 | self.avatar_url = kwargs.get("avatar_url") 33 | self.created_timestamp = kwargs.get("created_timestamp") 34 | self.broadcasts = kwargs.get("broadcasts") # unused 35 | self.featured = kwargs.get("featured") # unused 36 | self.referral_url = kwargs.get("referral_url") # property? 37 | self.high_score = kwargs.get("high_score") 38 | self.games_played = kwargs.get("games_played") 39 | self.win_count = kwargs.get("win_count") # different than leaderboard.alltime_wins cuz i dont know, i think this is the accurate one 40 | self.blocked = kwargs.get("blocked") 41 | self.blocks_me = kwargs.get("blocks_me") 42 | try: 43 | x = kwargs.get("leaderboard") 44 | if isinstance(x, dict): 45 | kwargs2 = {} 46 | for k, v in x.items(): 47 | kwargs2[_to_snake(k)] = v 48 | self.leaderboard = HQUserLeaderboard(**kwargs2) 49 | elif isinstance(x, HQUserLeaderboard): 50 | self.leaderboard = x 51 | except Exception as e: 52 | raise e 53 | 54 | 55 | class HQMeInfo(HQUserInfo): 56 | def __init__(self, **kwargs): 57 | super().__init__(**kwargs) 58 | self.friend_ids = kwargs.get("friend_ids") 59 | self.stk = kwargs.get("stk") # unused? 60 | self.voip = kwargs.get("voip") 61 | self.device_tokens = kwargs.get("device_tokens") 62 | self.preferences = kwargs.get("preferences") 63 | self.lives = kwargs.get("lives") 64 | self.phone_number = kwargs.get("phone_number") 65 | self.referred = kwargs.get("referred") 66 | 67 | 68 | class HQClient: 69 | def __init__(self, login_token: str, client: str="iOS/1.3.28 b122", user_agent: str="HQ-iOS/122 CFNetwork/975.0.3 Darwin/18.2.0", caching=False, cache_time=15, no_ws_requests=False): 70 | self.login_token = login_token 71 | self.headers = { 72 | "x-hq-client": client, 73 | "user-agent": user_agent 74 | } 75 | self.auth_token = self.get_auth_token() 76 | self.ws = None 77 | self.ws_on_message = lambda x: None 78 | self.ws_on_error = lambda x: None 79 | self.ws_on_close = lambda x: None 80 | self.caching = caching # probably could just decorate but im too lazy 81 | self.cache_time = cache_time 82 | self._cache = {} 83 | self.no_ws_requests = no_ws_requests 84 | 85 | def get_auth_token(self) -> str: 86 | return requests.post("https://api-quiz.hype.space/tokens/", headers=self.headers, data={'token': self.login_token}).json()['authToken'] 87 | 88 | @property 89 | def default_headers(self) -> dict: 90 | return { 91 | "x-hq-client": self.headers["x-hq-client"], 92 | "authorization": "Bearer " + self.auth_token, 93 | "user-agent": self.headers["user-agent"] 94 | } 95 | 96 | def valid_auth(self) -> bool: 97 | return "active" in self.schedule() 98 | 99 | def make_it_rain(self) -> bool: 100 | return requests.post("https://api-quiz.hype.space/easter-eggs/makeItRain", headers=self.default_headers).status_code == 200 101 | 102 | def search_users(self, user: str) -> list: 103 | if self.caching: 104 | if "search_users" in self._cache: 105 | if user in self._cache["search_users"]: 106 | if (time.time() - self._cache["search_users"][user]["last_update"]) < self.cache_time: 107 | return self._cache["search_users"][user]["value"] 108 | response = requests.get("https://api-quiz.hype.space/users?q=" + user, headers=self.default_headers) 109 | ret = [] 110 | for x in response.json()["data"]: 111 | kwargs = {} 112 | for k, v in x.items(): 113 | kwargs[_to_snake(k)] = v 114 | ret.append(HQUserInfo(**kwargs)) 115 | if self.caching: 116 | if "search_users" not in self._cache: 117 | self._cache["search_users"] = {} 118 | self._cache["search_users"][user] = { 119 | "value": ret, 120 | "last_update": time.time() 121 | } 122 | return ret 123 | 124 | def user_info(self, something) -> HQUserInfo: 125 | if isinstance(something, str): 126 | search = self.search_users(something) 127 | if not search: 128 | raise Exception("User not found") 129 | else: 130 | user_id = search[0].user_id 131 | elif isinstance(something, int): 132 | user_id = something 133 | if self.caching: 134 | if "user_info" in self._cache: 135 | if user_id in self._cache["user_info"]: 136 | if (time.time() - self._cache["user_info"][user_id]["last_update"]) < self.cache_time: 137 | return self._cache["user_info"][user_id]["value"] 138 | response = requests.get("https://api-quiz.hype.space/users/me", headers=self.default_headers) 139 | kwargs = {} 140 | for k, v in response.json().items(): 141 | kwargs[_to_snake(k)] = v 142 | ret = HQUserInfo(**kwargs) 143 | if self.caching: 144 | if "user_info" not in self._cache: 145 | self._cache["user_info"] = {} 146 | self._cache["user_info"][user_id] = { 147 | "value": ret, 148 | "last_update": time.time() 149 | } 150 | return ret 151 | 152 | def me(self) -> HQMeInfo: 153 | response = requests.get("https://api-quiz.hype.space/users/me", headers=self.default_headers) 154 | kwargs = {} 155 | for k, v in response.json().items(): 156 | kwargs[_to_snake(k)] = v 157 | return HQMeInfo(**kwargs) 158 | 159 | def cashout(self, paypal: str) -> bool: 160 | return requests.post("https://api-quiz.hype.space/users/me/payouts", headers=self.default_headers, data={"email": paypal}).status_code == 200 161 | 162 | def unlink(self) -> bool: 163 | return requests.post("https://api-quiz.hype.space/users/me/payouts/unlink", headers=self.default_headers).status_code == 200 164 | 165 | def addRefferal(self, refferal: str) -> bool: 166 | return requests.patch("https://api-quiz.hype.space/users/me", headers=self.default_headers, data={"referringUsername": refferal}).status_code == 200 167 | 168 | def schedule(self) -> dict: 169 | if self.caching: 170 | if "schedule" in self._cache: 171 | if (time.time() - self._cache["schedule"]["last_update"]) < self.cache_time: 172 | return self._cache["schedule"]["value"] 173 | ret = requests.get("https://api-quiz.hype.space/shows/now?type=hq", headers=self.default_headers).json() 174 | if self.caching: 175 | if "schedule" not in self._cache: 176 | self._cache["schedule"] = {} 177 | self._cache["schedule"] = { 178 | "value": ret, 179 | "last_update": time.time() 180 | } 181 | return ret 182 | 183 | def aws_credentials(self) -> dict: 184 | return requests.get("https://api-quiz.hype.space/credentials/s3", headers=self.default_headers).json() 185 | 186 | def delete_avatar(self) -> str: 187 | return requests.delete("https://api-quiz.hype.space/users/me/avatarUrl", headers=self.default_headers).json()["avatarUrl"] 188 | 189 | def add_friend(self, something) -> dict: 190 | if isinstance(something, int): 191 | user_id = int(something) 192 | elif isinstance(something, str): 193 | search = self.search_users(something) 194 | if not search: 195 | raise Exception("user not found") 196 | user_id = search[0].user_id 197 | elif isinstance(something, HQUserInfo): 198 | user_id = something.user_id 199 | response = requests.post(f"https://api-quiz.hype.space/friends/{user_id}/requests", headers=self.default_headers).json() 200 | return { 201 | "requested_user": self.user_info(response["requestedUser"]["userId"]), 202 | "requesting_user": self.user_info(response["requestingUser"]["userId"]), 203 | "status": response["status"] 204 | } 205 | 206 | def friend_status(self, something): 207 | if isinstance(something, int): 208 | user_id = int(something) 209 | elif isinstance(something, str): 210 | search = self.search_users(something) 211 | if not search: 212 | raise Exception("user not found") 213 | user_id = search[0].user_id 214 | return requests.get(f"https://api-quiz.hype.space/friends/{user_id}/status", headers=self.default_headers).json()["status"] 215 | 216 | def accept_friend(self, something) -> dict: 217 | if isinstance(something, int): 218 | user_id = int(something) 219 | elif isinstance(something, str): 220 | search = self.search_users(something) 221 | if not search: 222 | raise Exception("user not found") 223 | user_id = search[0].user_id 224 | response = requests.put(f"https://api-quiz.hype.space/friends/{user_id}/status", headers=self.default_headers, data={ 225 | "status": "ACCEPTED" 226 | }).json() 227 | return { 228 | "requested_user": self.user_info(response["requestedUser"]["userId"]), 229 | "requesting_user": self.user_info(response["requestingUser"]["userId"]), 230 | "status": response["status"], 231 | "accepted_timestamp": response["created"] # milliseconds(?) 232 | } 233 | 234 | def remove_friend(self, something) -> bool: 235 | if isinstance(something, int): 236 | user_id = int(something) 237 | elif isinstance(something, str): 238 | search = self.search_users(something) 239 | if not search: 240 | raise Exception("user not found") 241 | user_id = search[0].user_id 242 | return requests.delete(f"https://api-quiz.hype.space/friends/{user_id}", headers=self.default_headers).json()["result"] 243 | 244 | def socket_url(self) -> str: 245 | if self.no_ws_requests: 246 | return "wss://hqecho.herokuapp.com/" # tbh just replace the line its one method no args 247 | x = self.schedule() 248 | if x["active"]: 249 | return x["broadcast"]["socketUrl"].replace("https", "wss") 250 | 251 | def generate_subscribe(self) -> str: 252 | if not self.no_ws_requests: 253 | x = self.schedule() 254 | broadcast_id = x["broadcast"]["broadcastId"] 255 | else: 256 | broadcast_id = "placeholder_broadcastid" 257 | return json.dumps({ 258 | "type": "subscribe", 259 | "broadcastId": broadcast_id 260 | }) 261 | 262 | def generate_answer(self, question_id: int, answer_id: int) -> str: 263 | if not self.no_ws_requests: 264 | x = self.schedule() 265 | broadcast_id = x["broadcast"]["broadcastId"] 266 | else: 267 | broadcast_id = "placeholder_broadcastid" 268 | 269 | return json.dumps({ 270 | "type": "answer", 271 | "questionId": question_id, 272 | "broadcastId": broadcast_id, 273 | "answerId": answer_id 274 | }) 275 | 276 | def generate_extra_life(self, question_id: int) -> str: 277 | if not self.no_ws_requests: 278 | x = self.schedule() 279 | broadcast_id = x["broadcast"]["broadcastId"] 280 | else: 281 | broadcast_id = "placeholder_broadcastid" 282 | 283 | return json.dumps({ 284 | "type": "useExtraLife", 285 | "broadcastId": broadcast_id, 286 | "questionId": question_id 287 | }) 288 | 289 | def verify(phone: str) -> str: 290 | try: 291 | return requests.post("https://api-quiz.hype.space/verifications", data={ 292 | "method": "sms", 293 | "phone": phone 294 | }, headers={"x-hq-client": "Android/1.6.2", "user-agent": "okhttp/3.8.0"}).json()["verificationId"] 295 | except KeyError: 296 | raise Exception("invalid phone number") 297 | 298 | 299 | def submit_code(verification_id: str, code: str) -> dict: 300 | return requests.post("https://api-quiz.hype.space/verifications/" + verification_id, data={"code": code}).json() 301 | 302 | 303 | def username_available(username: str) -> bool: 304 | return not bool(requests.post("https://api-quiz.hype.space/usernames/available", data={"username": username}).json()) 305 | 306 | 307 | def create_user(username: str, verification_id: str, region: str="US", language: str="en"): 308 | return requests.post("https://api-quiz.hype.space/users", data={ 309 | "country": region, 310 | "language": language, 311 | "username": username, 312 | "verificationId": verification_id 313 | }).json() --------------------------------------------------------------------------------