Beispiel #1
0
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)
Beispiel #2
0
 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
Beispiel #3
0
    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
Beispiel #4
0
    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
Beispiel #5
0
    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)
Beispiel #6
0
    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