def reconstruct_json_into(value, into, indent): """ Reconstructs a json payload extending the given `into` list. Parameters ---------- value : `str` Json payload data to reconstruct. into : `list` of `str` A list to extend it's content. indent : `int` The amount of indents to add. """ try: json_data = from_json(value) except JSONDecodeError: into.append(TYPE_NAME_STRING) into.append('(') into.append(MODIFIER_LENGTH) into.append('=') into.append(str(len(value))) into.append('): ') into.append(reprlib.repr(value)) else: reconstruct_value_into(json_data, into, indent, False)
async def _received_message(self, message): """ Processes the message sent by Discord. If the message is `DISPATCH`, ensures the specific parser for it and returns `False`. For every other operation code it calls ``._special_operation`` and returns that's return. This method is a coroutine. Parameters ---------- message : `bytes` The received message. Returns ------- should_reconnect : `bool` Raises ------ TimeoutError If the gateways's `.kokoro` is not beating, meanwhile it should. """ # return True if we should reconnect message = from_json(message) operation = message['op'] data = message.get('d', None) sequence = message.get('s', None) if sequence is not None: self.sequence = sequence if operation: return await self._special_operation(operation, data) # self.DISPATCH event = message['t'] client = self.client try: parser = PARSERS[event] except KeyError: call_unknown_dispatch_event_event_handler(client, event, data) return False if data is None: return try: if parser(client, data) is None: return False except BaseException as err: Task(client.events.error(client, event, err), KOKORO) return False if event == 'READY': self.session_id = data['session_id'] # elif event == 'RESUMED': # pass return False
async def _received_message(self, message): """ Processes a message received from a lavalink node. Parameters ---------- message : `str` the received message. Returns ------- should_reconnect : `bool` """ message = from_json(message) operation = message[LAVALINK_KEY_NODE_OPERATION] if operation == LAVALINK_KEY_GATEWAY_OPERATION_STATS: self.stats = Stats(message) return False if operation == LAVALINK_KEY_GATEWAY_OPERATION_PLAYER_UPDATE: guild_id = int(message[LAVALINK_KEY_GUILD_ID]) try: player = self.client.solarlink.players[guild_id] except KeyError: return False player._update_state(message[LAVALINK_KEY_PLAYER_STATE]) return False elif operation == LAVALINK_KEY_GATEWAY_OPERATION_EVENT: event = message[LAVALINK_KEY_EVENT_TYPE] client = self.client try: parser = PARSERS[event] except KeyError: Task( client.events.error( client, f'{self.__class__.__name__}._received_message', f'Unknown dispatch event {event}\nData: {message!r}', ), KOKORO, ) return False try: parser(self.client, message) except BaseException as err: Task(client.events.error(client, event, err), KOKORO) return False client = self.client Task( client.events.error( client, f'{self.__class__.__name__}._received_message', f'Unknown operation {operation}\nData: {message!r}', ), KOKORO, ) return False
def _from_path(cls, directory_path): """ Tries to create a project setting instance from the given directory. Parameters ---------- directory_path : `str` Path to the file. Returns ------- self : ``ProjectSettings`` Raises ------ RuntimeError - If `path` refers to not a file. - Unexpected file content or structure. """ file_path = join_paths(directory_path, SETTINGS_FILE_NAME) if not exists(file_path): return cls._create_empty(file_path) if not is_file(file_path): raise RuntimeError(f'Settings path is not a file: {file_path!r}.') with open(file_path, 'r') as file: file_content = file.read() if not file_content: return cls._create_empty(file_path) try: json = from_json(file_content) except BaseException as err: raise RuntimeError( 'Failed to decode settings file content.') from err if json is None: return cls._create_empty(file_path) if not isinstance(json, dict): raise RuntimeError( 'Settings file structure incorrect, expected dictionary as root object.' ) try: bot_directories = json[KEY_BOT_DIRECTORIES] except KeyError: bot_directories = None else: bot_directories = checkout_list_structure( bot_directories, 'bot_directories', True, ELEMENT_TYPE_IDENTIFIER_STRING, False, ) # Check bot directories to list only the existing ones. if (bot_directories is not None): bot_directories = set(bot_directories) directories = set() for name in list_directory(directory_path): path = join_paths(directory_path, name) if is_directory(path): directories.add(name) bot_directories.intersection_update(directories) if bot_directories: bot_directories = list(bot_directories) else: bot_directories = None self = object.__new__(cls) self.directory_path = directory_path self.bot_directories = bot_directories return self
async def _request(self, method, url, rate_limit_handler, data=None, query_parameters=None): """ Does a request towards top.gg API. This method is a coroutine. Parameters ---------- method : `str` Http method. url : `str` Endpoint to do request towards. rate_limit_handler : ``RateLimitHandlerBase` Rate limit handle to handle rate limit as. data : `None`, `Any` = `None`, Optional Json serializable data. query_parameters : `None`, `Any` = `None`, Optional Query parameters. Raises ------ ConnectionError No internet connection. TopGGGloballyRateLimited If the client got globally rate limited by top.gg and `raise_on_top_gg_global_rate_limit` was given as `True`. TopGGHttpException Any exception raised by top.gg api. """ headers = self._headers.copy() if (data is not None): headers[CONTENT_TYPE] = 'application/json' data = to_json(data) try_again = 2 while try_again > 0: global_rate_limit_expires_at = self._global_rate_limit_expires_at if global_rate_limit_expires_at > LOOP_TIME(): if self._raise_on_top_gg_global_rate_limit: raise TopGGGloballyRateLimited(None) future = Future(KOKORO) KOKORO.call_at(global_rate_limit_expires_at, Future.set_result_if_pending, future, None) await future async with rate_limit_handler.ctx(): try: async with RequestContextManager( self.http._request(method, url, headers, data, query_parameters)) as response: response_data = await response.text(encoding='utf-8') except OSError as err: if not try_again: raise ConnectionError( 'Invalid address or no connection with Top.gg.' ) from err await sleep(0.5 / try_again, KOKORO) try_again -= 1 continue response_headers = response.headers status = response.status content_type_headers = response_headers.get(CONTENT_TYPE, None) if (content_type_headers is not None ) and content_type_headers.startswith('application/json'): response_data = from_json(response_data) if 199 < status < 305: return response_data # Are we rate limited? if status == 429: try: retry_after = headers[RETRY_AFTER] except KeyError: retry_after = RATE_LIMIT_GLOBAL_DEFAULT_DURATION else: try: retry_after = float(retry_after) except ValueError: retry_after = RATE_LIMIT_GLOBAL_DEFAULT_DURATION self._global_rate_limit_expires_at = LOOP_TIME( ) + retry_after if self._raise_on_top_gg_global_rate_limit: raise TopGGGloballyRateLimited(None) await sleep(retry_after, KOKORO) continue # Python casts sets to frozensets if (status in {400, 401, 402, 404}): raise TopGGHttpException(response, response_data) if try_again and (status >= 500): await sleep(10.0 / try_again, KOKORO) try_again -= 1 continue raise TopGGHttpException(response, response_data)
async def _received_message(self, message): """ Processes the message sent by Discord. This method is a coroutine. Parameters ---------- message : `bytes` The received message. Raises ------ TimeoutError If the gateways's `.kokoro` is not beating, meanwhile it should. """ message = from_json(message) operation = message['op'] data = message.get('d', None) if operation == CLIENT_CONNECT: if data is None: return user_id = int(data['user_id']) try: audio_source = data['audio_ssrc'] except KeyError: pass else: self.client._update_audio_source(user_id, audio_source) try: video_source = data['video_ssrc'] except KeyError: pass else: self.client._update_video_source(user_id, video_source) return if operation == SPEAKING: if data is None: return user_id = int(data['user_id']) audio_source = data['ssrc'] self.client._update_audio_source(user_id, audio_source) return if operation == CLIENT_DISCONNECT: if data is None: return user_id = int(data['user_id']) self.client._remove_source(user_id) return kokoro = self.kokoro if kokoro is None: kokoro = await Kokoro(self) if operation == HELLO: # sowwy, but we need to ignore these or we will keep getting timeout # kokoro.interval=data['heartbeat_interval']/100. # send a heartbeat immediately await kokoro.beat_now() return if operation == HEARTBEAT_ACK: kokoro.answered() return if operation == SESSION_DESCRIPTION: if data is None: return # data['mode'] is same as our default every time? self.client._secret_box = SecretBox(bytes(data['secret_key'])) if kokoro.beater is None: # Discord order bug ? kokoro.start_beating() await self._beat() await self._set_speaking(self.client.speaking) return if kokoro.beater is None: raise TimeoutError if operation == READY: # need to ignore interval # kokoro.interval = data['heartbeat_interval']/100. await self._initial_connection(data) return if operation == INVALIDATE_SESSION: await self._identify() return