def __init__(self):
     """
     Creates a new mass user chunker.
     """
     self.waiter = Future(KOKORO)
     self.last = now = LOOP_TIME()
     self.timer = KOKORO.call_at(now + USER_CHUNK_TIMEOUT, type(self)._cancel, self)
Example #2
0
 def __new__(cls, client, message, check, timeout):
     """
     Creates a new ``ComponentInteractionWaiter`` with the given parameters.
     
     Parameters
     ----------
     client : ``Client``
         The client who will wait for component interaction.
     message : ``Message``
         The waited interaction component's message.
     check : `None`, `callable`
         The check to call to validate whether the response is sufficient.
     timeout : `None`, `float`
         The timeout till the waiting is done. If expires, `TimeoutError` is raised to ``._future``.
     """
     self = object.__new__(cls)
     self._finished = False
     self._future = Future(KOKORO)
     self._message = message
     self._check = check
     self._timeouter = None
     
     if (timeout is not None):
         self._timeouter = Timeouter(self, timeout)
     
     client.slasher.add_component_interaction_waiter(message, self)
     
     return self
Example #3
0
 def __iter__(self):
     """
     Awaits the rate limit handler.
     
     This method is a generator. Should be used with `await` expression.
     
     Returns
     -------
     cancelled : `bool`
         Whether the respective gateway was closed.
     """
     now = LOOP_TIME()
     if now >= self.resets_at:
         self.resets_at = now + GATEWAY_RATE_LIMIT_RESET
         remaining = GATEWAY_RATE_LIMIT_LIMIT
     else:
         remaining = self.remaining
     
     if remaining:
         self.remaining = remaining - 1
         return False
     
     if self.wake_upper is None:
         self.wake_upper = KOKORO.call_at(self.resets_at, type(self).wake_up, self)
     
     future = Future(KOKORO)
     self.queue.append(future)
     return (yield from future)
Example #4
0
 def __await__(self):
     """Awaits the iterator's next result."""
     future = self._future
     if (future is None):
         # As it should be
         queue = self._queue
         if queue is None:
             # Check finished here :KoishiWink:
             if self._finished:
                 exception = self._exception
                 if (exception is None):
                     return None
                 else:
                     raise exception
             
             future = self._future = Future(KOKORO)
         else:
             result = queue.popleft()
             if not queue:
                 self._queue = None
             
             return result
     
     try:
         return (yield from future)
     finally:
         self._future = None
Example #5
0
    def __new__(cls, node, guild_id, channel_id):
        """
        Creates a new solar player instance.
        
        Parameters
        ----------
        node : ``SolarNode``
            The node of the player.
        guild_id : `int`
            The guild's identifier, where the node will connect to.
        channel_id : `int`
            The channel's identifier, where the node will connect to.
        
        Returns
        -------
        self : ``SolarPlayerBase``
            The player.
        waiter : ``Future``
            A future, which can be awaited to wait till the player is connected.
        """
        self = object.__new__(cls)

        self._filters = {}
        self._position = 0.0
        self._position_update = 0.0
        self._forward_data = None

        self.guild_id = guild_id
        self.channel_id = channel_id
        self.node = node

        waiter = Future(KOKORO)
        Task(self._connect(waiter=waiter), KOKORO)

        return self, waiter
Example #6
0
    async def wait_for_close(self, timeout=None):
        """
        Waits till the stream is closed.
        
        Parameters
        ----------
        timeout : `None`, `float` = `None`, Optional
            Maximal timeout to wait.
        
        Raises
        ------
        TimeoutError
            Timeout occurred.
        """
        if self._closed:
            return

        close_waiter = self._close_waiter
        if (close_waiter is None):
            close_waiter = self._close_waiter = Future(KOKORO)

        waiter = shield(close_waiter, KOKORO)

        if (timeout is not None):
            future_or_timeout(waiter, timeout)

        return waiter
Example #7
0
 async def enter(self):
     """
     Waits till a respective request can be started.
     
     Should be called before the rate limit handler is used inside of it's context manager.
     
     This method is a coroutine.
     """
     size = self.parent.size
     if size < 1:
         if size == UNLIMITED_SIZE_VALUE:
             return
         
         if size == 0:
             size = 1
         else:
             size = -size
     
     queue = self.queue
     if queue is None:
         self.queue = queue = deque()
     
     active = self.active
     left = size - active
     
     if left <= 0:
         future = Future(KOKORO)
         queue.append(future)
         await future
         
         self.active += 1
         return
     
     left -= self.count_drops()
     if left > 0:
         self.active = active + 1
         return
     
     future = Future(KOKORO)
     queue.append(future)
     await future
     
     self.active += 1
Example #8
0
    def _set_payload_reader(self, payload_reader):
        """
        Sets payload reader to the stream.
        
        Parameters
        ----------
        payload_reader : `GeneratorType`
            A generator, what gets control, every time a chunk is received, till it returns or raises.
        
        Returns
        -------
        payload_waiter : ``Future``
            Waiter, to what the result of the `payload_reader` is set.
        
        Raises
        ------
        ValueError
            I/O operation on closed or on a detached file.
        """
        assert self._payload_reader is None, 'Payload reader already set!'

        if self._closed and (not self._chunks):
            raise ValueError('I/O operation on closed or on a detached file.')

        payload_waiter = Future(KOKORO)

        try:
            payload_reader.send(None)
        except StopIteration as err:
            args = err.args
            if not args:
                result = None
            elif len(args) == 1:
                result = args[0]
            else:
                result = args

            payload_waiter.set_result_if_pending(result)
        except GeneratorExit as err:
            exception = CancelledError()
            exception.__cause__ = err
            payload_waiter.set_exception_if_pending(exception)
        except BaseException as err:
            payload_waiter.set_exception_if_pending(err)

        else:
            self._payload_waiter = payload_waiter
            self._payload_reader = payload_reader

        return payload_waiter
Example #9
0
 async def start(self):
     """
     Starts the lavalink node.
     
     This method is a coroutine.
     
     Raises
     ------
     BaseException
         Any exception raised when trying to connect.
     """
     waiter = Future(KOKORO)
     Task(self.run(waiter=waiter), KOKORO)
     return await waiter
Example #10
0
 async def wait_for_response_message(self, *, timeout=None):
     """
     Waits for response message. Applicable for application command interactions.
     
     This method is a coroutine.
     
     Parameters
     ----------
     timeout : `None`, `float` = `None`, Optional (Keyword only)
         The maximal time to wait for message before `TimeoutError` is raised.
     
     Returns
     -------
     message : ``Message``
         The received message.
     
     Raises
     ------
     RuntimeError
         The interaction was acknowledged with `show_for_invoking_user_only=True` (as ephemeral). Response
         `message` cannot be detected.
     TimeoutError
         Message was not received before timeout.
     """
     message = self.message
     if (message is not None):
         return message
     
     if self._response_flag & RESPONSE_FLAG_EPHEMERAL:
         raise RuntimeError(
             f'The interaction was acknowledged with `show_for_invoking_user_only=True` '
             f'(as ephemeral). Response `message` cannot be detected.'
         )
     
     try:
         waiter = INTERACTION_EVENT_MESSAGE_WAITERS[self]
     except KeyError:
         waiter = Future(KOKORO)
         INTERACTION_EVENT_MESSAGE_WAITERS[self] = waiter
     
     waiter = shield(waiter, KOKORO)
     
     if (timeout is not None):
         future_or_timeout(waiter, timeout)
     
     await waiter
     return self.message
Example #11
0
    async def _start(self):
        """
        The main keep alive task of kokoro.
        
        The control flow of the method is the following:
            - Set `.ws_waiter` and wait till it is done. If it is done, means that kokoro's gateway is connected.
                If it was cancelled meanwhile, means the beat task should stop.
            - Sets `.beater` to ``._keep_beating`` and awaits it. This is the main beater of kokoro and runs meanwhile
                it's gateway is connected. This task can be cancelled, but we ignore that case.
            - If `.running` is still `True`, repeat.
        
        This method is a coroutine.
        """
        try:
            self.running = True
            while True:
                #wait for start
                try:
                    waiter = Future(KOKORO)
                    self.ws_waiter = waiter
                    await waiter
                except CancelledError:
                    #kokoro cancelled, client shuts down
                    break
                finally:
                    self.ws_waiter = None

                #keep beating
                try:
                    beater = Task(self._keep_beating(), KOKORO)
                    self.beater = beater
                    await beater
                except CancelledError:
                    #connection cancelled, lets wait for it
                    pass
                finally:
                    #make sure
                    self.beater = None

                if self.running:
                    continue

                break
        finally:
            if self.task is KOKORO.current_task:
                self.task = None
Example #12
0
 async def _runner(self):
     """
     Runner task of the ready state waiting for all guild users to be requested.
     
     This method is a coroutine.
     """
     try:
         shard_count = self.shard_count
         if not shard_count:
             shard_count = 1
         
         while True:
             tasks = None
             done_tasks = 0
             
             for shard_user_requester in self.shard_user_requesters.values():
                 if shard_user_requester.state == USER_REQUEST_STATE_NONE:
                     if tasks is None:
                         tasks = []
                     
                     tasks.append(shard_user_requester.task)
                 else:
                     done_tasks += 1
             
             if (tasks is not None):
                 await WaitTillAll(tasks, KOKORO)
                 continue
             
             if done_tasks == shard_count:
                 break
             
             shard_ready_waiter = Future(KOKORO)
             future_or_timeout(shard_ready_waiter, SHARD_CONNECT_TIMEOUT)
             self.shard_ready_waiter = None
             
             try:
                 await shard_ready_waiter
             except TimeoutError:
                 return
             finally:
                 self.shard_ready_waiter = None
     
     finally:
         self.task = None
         self.call_ready()
Example #13
0
def wait_for_reaction(client, message, check, timeout):
    """
    Executes waiting for reaction on a message with a ``Future``.
    
    Parameters
    ----------
    client : ``Client``
        The client who's `reaction_add` event will be used.
    message : ``Message``
        The target message on what new reactions will be checked.
    check : `callable`
        The check what is called with the received parameters whenever an event is received.
    timeout : `float`
        The timeout after `TimeoutError` will be raised to the waiter future.
    
    Returns
    -------
    future : ``Future``
        The waiter future, what should be awaited.
    """
    future = Future(KOKORO)
    WaitAndContinue(future, check, message, client.events.reaction_add,
                    timeout)
    return future
Example #14
0
 async def wait_till_limits_expire(self):
     """
     Waits till the represented rate limits expire.
     
     This method is a coroutine.
     
     Raises
     ------
     RuntimeError
         If the method is called meanwhile `keep_alive` is `True`.
     
     Notes
     -----
     The waiting is implemented with weakreference callback, so the coroutine returns when the source callback is
     garbage collected. This also means waiting on the exact same limit multiple times causes misbehaviour.
     """
     handler = self._handler
     
     while True:
         if (handler is not None):
             handler = handler()
             if (handler is not None):
                 break
         
         handler = self.client.http.handlers.get(self._key)
         if handler is None:
             return
         
         break
     
     if handler is self._key:
         raise RuntimeError('Cannot use `.wait_till_limits_expire` meanwhile `keep_alive` is `True`.')
     
     future = Future(current_thread())
     self._handler = WeakReferer(handler, self._wait_till_limits_expire_callback(future))
     await future
Example #15
0
def wait_for_message(client, channel, check, timeout):
    """
    Executes waiting for messages at a channel with a ``Future``.
    
    Parameters
    ----------
    client : ``Client``
        The client who's `message_create` event will be used.
    channel : ``ChannelBase``
        The target channel where the new messages will be checked.
    check : `callable`
        The check what is called with the received parameters whenever an event is received.
    timeout : `float`
        The timeout after `TimeoutError` will be raised to the waiter future.
    
    Returns
    -------
    future : ``Future``
        The waiter future, what should be awaited.
    """
    future = Future(KOKORO)
    WaitAndContinue(future, check, channel, client.events.message_create,
                    timeout)
    return future
Example #16
0
    def __new__(cls, client, guild_id, channel_id):
        """
        Creates a ``VoiceClient``. If any of the required libraries are not present, raises `RuntimeError`.
        
        If the voice client was successfully created, returns a ``Future``, what is a waiter for it's ``._connect``
        method. If connecting failed, then the future will raise `TimeoutError`.
        
        Parameters
        ----------
        client : ``Client``
            The parent client.
        guild_id : `int`
            the guild's identifier, where the the client will connect to.
        channel_id : `int`
            The channel's identifier where the client will connect to.
        
        Returns
        -------
        waiter : ``Future``
        
        Raises
        ------
        RuntimeError
            If `PyNaCl` is not loaded.
            If `Opus` is not loaded.
        """
        # raise error at __new__
        if SecretBox is None:
            raise RuntimeError('PyNaCl is not loaded.')

        if OpusEncoder is None:
            raise RuntimeError('Opus is not loaded.')

        region = try_get_voice_region(guild_id, channel_id)

        self = object.__new__(cls)

        self.guild_id = guild_id
        self.channel_id = channel_id
        self.region = region
        self.gateway = DiscordGatewayVoice(self)
        self._socket = None
        self._protocol = None
        self._transport = None
        self.client = client
        self.connected = Event(KOKORO)
        self.queue = []
        self.player = None
        self.call_after = cls._play_next
        self.speaking = 0
        self.lock = Lock(KOKORO)
        self.reader = None

        self._handshake_complete = Future(KOKORO)
        self._encoder = OpusEncoder()
        self._sequence = 0
        self._timestamp = 0
        self._audio_source = 0
        self._video_source = 0
        self._pref_volume = 1.0
        self._set_speaking_task = None
        self._endpoint = None
        self._port = None
        self._endpoint_ip = None
        self._secret_box = None
        self._audio_port = None
        self._ip = None
        self._audio_sources = {}
        self._video_sources = {}
        self._audio_streams = None
        self._reconnecting = True

        client.voice_clients[guild_id] = self
        waiter = Future(KOKORO)
        Task(self._connect(waiter=waiter), KOKORO)
        return waiter
Example #17
0
class ComponentInteractionWaiter:
    """
    Waiter class for button press.
    
    Parameters
    ----------
    _check : `None`, `callable`
        The check to call to validate whether the response is sufficient.
    _finished : `bool`
        Whether the waiter finished.
    _future : ``Future``
        The waiter future.
    _message : ``Message``
        The waited interaction component's message.
    _timeouter : `None`, ``Timeouter``
        Executes the timeout feature on the waiter.
    """
    __slots__ = ('_check', '_finished', '_future', '_message', '_timeouter')
    
    def __new__(cls, client, message, check, timeout):
        """
        Creates a new ``ComponentInteractionWaiter`` with the given parameters.
        
        Parameters
        ----------
        client : ``Client``
            The client who will wait for component interaction.
        message : ``Message``
            The waited interaction component's message.
        check : `None`, `callable`
            The check to call to validate whether the response is sufficient.
        timeout : `None`, `float`
            The timeout till the waiting is done. If expires, `TimeoutError` is raised to ``._future``.
        """
        self = object.__new__(cls)
        self._finished = False
        self._future = Future(KOKORO)
        self._message = message
        self._check = check
        self._timeouter = None
        
        if (timeout is not None):
            self._timeouter = Timeouter(self, timeout)
        
        client.slasher.add_component_interaction_waiter(message, self)
        
        return self
    
    
    async def __call__(self, interaction_event):
        """
        Calls the component interaction waiter checking whether the respective event is sufficient setting the waiter's
        result.
        
        This method is a coroutine.
        
        Parameters
        ----------
        interaction_event : ``InteractionEvent``
            The received interaction event
        """
        check = self._check
        if check is None:
            self._future.set_result_if_pending(interaction_event)
            should_acknowledge = False
        else:
            try:
                result = check(interaction_event)
            except BaseException as err:
                self._future.set_exception_if_pending(err)
                should_acknowledge = False
            else:
                if isinstance(result, bool):
                    if result:
                        self._future.set_result_if_pending(interaction_event)
                        should_acknowledge = False
                    else:
                        should_acknowledge = True
                
                else:
                    self._future.set_result_if_pending((interaction_event, result))
                    should_acknowledge = False
        
        if should_acknowledge:
            await acknowledge_component_interaction(interaction_event)
        else:
            self.cancel()
    
    
    def __await__(self):
        """Awaits the waiter's result."""
        return (yield from self._future)
    
    
    def cancel(self, exception=None):
        """
        Cancels the component waiter.
        
        Parameters
        ----------
        exception : `None`, ``BaseException`` = `None`, Optional
            The exception to cancel the waiter with.
        """
        if self._finished:
            return
        
        self._finished = True
        
        timeouter = self._timeouter
        if (timeouter is not None):
            self._timeouter = None
            timeouter.cancel()
        
        message = self._message
        client = get_client_from_message(message)
        client.slasher.remove_component_interaction_waiter(message, self)
        
        future = self._future
        if exception is None:
            future.set_result_if_pending(None)
        else:
            future.set_exception_if_pending(exception)
Example #18
0
 async def run(self):
     """
     Runs the gateway sharder's gateways. If any of them returns, stops the rest as well. And if any of them
     returned `True`, then returns `True`, else `False`.
     
     This method is a coroutine.
     
     Raises
     ------
     DiscordGatewayException
         The client tries to connect with bad or not acceptable intent or shard value.
     DiscordException
         Any exception raised by the discord API when connecting.
     """
     max_concurrency = self.client._gateway_max_concurrency
     gateways = self.gateways
     
     index = 0
     limit = len(gateways)
     
     # At every step we add up to max_concurrency gateways to launch up. When a gateway is launched up, the waiter
     # yields a ``Future`` and if the same amount of ``Future`` is yielded as gateway started up, then we do the next
     # loop. An exception is, when the waiter yielded a ``Task``, because t–en 1 of our gateway stopped with no
     # internet stop, or it was stopped by the client, so we abort all the launching and return.
     waiter = WaitContinuously(None, KOKORO)
     while True:
         if index == limit:
             break
         
         left_from_batch = 0
         while True:
             future = Future(KOKORO)
             waiter.add(future)
             
             task = Task(gateways[index].run(future), KOKORO)
             waiter.add(task)
             
             index += 1
             left_from_batch += 1
             if index == limit:
                 break
             
             if left_from_batch == max_concurrency:
                 break
             
             continue
         
         while True:
             try:
                 result = await waiter
             except:
                 waiter.cancel()
                 raise
             
             waiter.reset()
             
             if type(result) is Future:
                 left_from_batch -= 1
                 
                 if left_from_batch:
                     continue
                 
                 break
             
             waiter.cancel()
             result.result()
         
         # We could time gateway connect rate limit more precisely, but this is already fine. We don't need to rush
         # it, there is many gateway to connect and sync with.
         await sleep(5.0, KOKORO)
         
         continue
     
     try:
         result = await waiter
     finally:
         waiter.cancel()
     
     result.result()
Example #19
0
 async def execute(self, client, parameter):
     """
     Executes the request and returns it's result or raises.
     
     This method is a coroutine.
     
     Parameters
     ----------
     client : ``Client``
         The client, who's `.discovery_validate_term` method was called.
     parameter : `str`
         The discovery term.
     
     Returns
     -------
     result : `Any`
     
     Raises
     ------
     ConnectionError
         If there is no internet connection, or there is no available cached result.
     TypeError
         The given `parameter` was not passed as `str`.
     DiscordException
         If any exception was received from the Discord API.
     """
     # First check parameter
     parameter_type = parameter.__class__
     if parameter_type is str:
         pass
     elif issubclass(parameter_type, str):
         parameter = str(parameter)
     else:
         raise TypeError(
             f'`parameter` can be `str`, got {parameter_type.__class__}; {parameter!r}.'
         )
     
     # First check cache
     try:
         unit = self.cached[parameter]
     except KeyError:
         unit = None
     else:
         now = LOOP_TIME()
         if self.timeout + unit.creation_time > now:
             unit.last_usage_time = now
             return unit.result
     
     # Second check actual request
     try:
         waiter = self._waiters[parameter]
     except KeyError:
         pass
     else:
         if waiter is None:
             self._waiters[parameter] = waiter = Future(KOKORO)
         
         return await waiter
     
     # No actual request is being done, so mark that we are doing a request.
     self._waiters[parameter] = None
     
     # Search client with free rate limits.
     free_count = RateLimitProxy(client, *self._rate_limit_proxy_parameters).free_count
     if not free_count:
         requester = client
         for client_ in CLIENTS.values():
             if client_ is client:
                 continue
             
             free_count = RateLimitProxy(client_, *self._rate_limit_proxy_parameters).free_count
             if free_count:
                 requester = client_
                 break
             
             continue
         
         # If there is no client with free count do not care about the reset times, because probably only 1 client
         # forces requests anyways, so that's rate limits will reset first as well.
         client = requester
     
     # Do the request
     try:
         result = await self.func(client, parameter)
     except ConnectionError as err:
         if (unit is None):
             waiter = self._waiters.pop(parameter)
             if (waiter is not None):
                 waiter.set_exception(err)
             
             raise
         
         unit.last_usage_time = LOOP_TIME()
         result = unit.result
     
     except BaseException as err:
         waiter = self._waiters.pop(parameter, None)
         if (waiter is not None):
             waiter.set_exception(err)
         
         raise
     
     else:
         if unit is None:
             self.cached[parameter] = unit = TimedCacheUnit()
         
         now = LOOP_TIME()
         unit.last_usage_time = now
         unit.creation_time = now
         unit.result = result
     
     finally:
         # Do cleanup if needed
         now = LOOP_TIME()
         if self._last_cleanup + self._minimal_cleanup_interval < now:
             self._last_cleanup = now
             
             cleanup_till = now - self.timeout
             collected = []
             
             cached = self.cached
             for cached_parameter, cached_unit in cached.items():
                 if cached_unit.last_usage_time < cleanup_till:
                     collected.append(cached_parameter)
             
             for cached_parameter in collected:
                 del cached[cached_parameter]
     
     waiter = self._waiters.pop(parameter)
     if (waiter is not None):
         waiter.set_result(result)
     
     return result
Example #20
0
 def __init__(self, ):
     self.waiter = Future(KOKORO)
     self.timer = KOKORO.call_at(LOOP_TIME() + USER_CHUNK_TIMEOUT, type(self)._cancel, self)
Example #21
0
 async def execute(self, client):
     """
     Executes the request and returns it's result or raises.
     
     This method is a coroutine.
     
     Returns
     -------
     result : `Any`
     
     Raises
     ------
     ConnectionError
         If there is no internet connection, or there is no available cached result.
     DiscordException
         If any exception was received from the Discord API.
     """
     if (LOOP_TIME() - self.timeout) < self._last_update:
         if self._active_request:
             waiter = self._waiter
             if waiter is None:
                 waiter = self._waiter = Future(KOKORO)
             
             result = await waiter
         else:
             result = self.cached
         
         return result
     
     self._active_request = True
     try:
         result = await self.func(client)
     except ConnectionError as err:
         result = self.cached
         if (result is ...):
             waiter = self._waiter
             if (waiter is not None):
                 self._waiter = None
                 waiter.set_exception(err)
             
             raise
     
     except BaseException as err:
         waiter = self._waiter
         if (waiter is not None):
             self._waiter = None
             waiter.set_exception(err)
         
         raise
     
     else:
         self._last_update = LOOP_TIME()
     
     finally:
         self._active_request = False
     
     waiter = self._waiter
     if (waiter is not None):
         self._waiter = None
         waiter.set_result(result)
     
     return result
Example #22
0
class SingleUserChunker:
    """
    A user chunk waiter, which yields after the first received chunk. Used at ``Client.request_members``.
    
    Attributes
    ----------
    timer : `Handle`, `None`
        The time-outer of the chunker, what will cancel if the timeout occurs.
    waiter : ``Future``
        The waiter future what will yield, when we receive the response, or when the timeout occurs.
    """
    __slots__ = ('timer', 'waiter',)
    
    def __init__(self, ):
        self.waiter = Future(KOKORO)
        self.timer = KOKORO.call_at(LOOP_TIME() + USER_CHUNK_TIMEOUT, type(self)._cancel, self)
    
    def __call__(self, event):
        """
        Called when a chunk is received with it's respective nonce.
        
        Parameters
        ----------
        event : ``GuildUserChunkEvent``
            The received guild user chunk's event.
        
        Returns
        -------
        is_last : `bool`
            ``SingleUserChunker`` returns always `True`, because it waits only for one event.
        """
        self.waiter.set_result_if_pending(event.users)
        timer = self.timer
        if (timer is not None):
            self.timer = None
            timer.cancel()
        
        return True
    
    def _cancel(self):
        """
        The chunker's timer calls this method.
        
        Cancels ``.waiter`` and ``.timer``. After this method was called, the waiting coroutine will remove it's
        reference from the event handler.
        """
        self.waiter.cancel()
        
        timer = self.timer
        if (timer is not None):
            self.timer = None
            timer.cancel()
    
    def cancel(self):
        """
        Cancels the chunker.
        
        This method should be called when when the chunker is canceller from outside. Before this method is called,
        it's references should be removed as well from the event handler.
        """
        self.waiter.set_result_if_pending([])
        
        timer = self.timer
        if (timer is not None):
            self.timer = None
            timer.cancel()
    
    def __await__(self):
        """
        Awaits the chunker's waiter and returns that's result.
        
        Returns
        -------
        users : `list` of ``ClientUserBase`` objects
            The received users. Can be an empty list.
        
        Raises
        ------
        CancelledError
            If timeout occurred.
        """
        return self.waiter.__await__()
Example #23
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)
Example #24
0
class MassUserChunker:
    """
    A user chunk waiter, which yields after the chunks of certain amount of guilds are received. Used at
    ``Client._request_members`` and at ``Client._request_members``.
    
    Attributes
    ----------
    last : `float`
        The timestamp of the last received chunk.
    timer : `Handle`, `None`
        The time-outer of the chunker, what will cancel if the timeout occurs.
    waiter : ``Future``
        The waiter future what will yield, when we receive the response, or when the timeout occurs.
    """
    __slots__ = ('last', 'timer', 'waiter',)
    
    def __init__(self):
        """
        Creates a new mass user chunker.
        """
        self.waiter = Future(KOKORO)
        self.last = now = LOOP_TIME()
        self.timer = KOKORO.call_at(now + USER_CHUNK_TIMEOUT, type(self)._cancel, self)
    
    
    def __call__(self, event):
        """
        Called when a chunk is received with it's respective nonce.
        
        Updates the chunker's last received chunk's time to push out the current timeout.
        
        Parameters
        ----------
        event : ``GuildUserChunkEvent``
            The received guild user chunk's event.
        
        Returns
        -------
        is_last : `bool`
            Whether the last chunk was received.
        """
        self.last = LOOP_TIME()
        if event.index + 1 != event.count:
            return False
        
        self.waiter.set_result_if_pending(None)
        timer = self.timer
        if (timer is not None):
            self.timer = None
            timer.cancel()
        
        return True
    
    
    def _cancel(self):
        """
        The chunker's timer calls this method. If the chunker received any chunks since it's ``.timer`` was started,
        pushes out the timeout.
        
        Cancels ``.waiter`` and ``.timer``. After this method was called, the waiting coroutine will remove it's
        reference from the event handler.
        """
        now = LOOP_TIME()
        next_ = self.last + USER_CHUNK_TIMEOUT
        if next_ > now:
            self.timer = KOKORO.call_at(next_, type(self)._cancel, self)
        else:
            self.timer = None
            self.waiter.cancel()
    
    
    def cancel(self):
        """
        Cancels the chunker.
        
        This method should be called when when the chunker is canceller from outside. Before this method is called,
        it's references should be removed as well from the event handler.
        """
        self.waiter.set_result_if_pending(None)
        
        timer = self.timer
        if (timer is not None):
            self.timer = None
            timer.cancel()
    
    
    def __await__(self):
        """
        Awaits the chunker's waiter.
        
        Raises
        ------
        CancelledError
            If timeout occurred.
        """
        return self.waiter.__await__()
Example #25
0
 async def _runner(self):
     """
     Requests the users of the represented shard's guilds.
     
     This method is a coroutine.
     """
     try:
         guild_ids = self.guild_ids
         received_guild_ids = self.received_guild_ids
         can_request_users = self.can_request_users
         
         if can_request_users:
             sub_data = {
                 'guild_id': 0,
                 'query': '',
                 'limit': 0,
                 'presences': CACHE_PRESENCE,
             }
             
             data = {
                 'op': GATEWAY_OPERATION_CODE_REQUEST_MEMBERS,
                 'd': sub_data
             }
         
         while guild_ids:
             
             if not received_guild_ids:
                 guild_create_waiter = Future(KOKORO)
                 future_or_timeout(guild_create_waiter, GUILD_RECEIVE_TIMEOUT)
                 self.guild_create_waiter = guild_create_waiter
                 
                 try:
                     await guild_create_waiter
                 except TimeoutError:
                     self.state = USER_REQUEST_STATE_TIMEOUT
                     return
                 finally:
                     self.guild_create_waiter = None
             
             guild_id, should_request_users = received_guild_ids.popleft()
             guild_ids.discard(guild_id)
             
             if not can_request_users:
                 continue
             
             try:
                 READY_STATE_TO_DO_GUILD_IDS.remove(guild_id)
             except KeyError:
                 continue
             
             if not should_request_users:
                 continue
             
             sub_data['guild_id'] = guild_id
             await self.gateway.send_as_json(data)
             await sleep(0.6, KOKORO)
     
     except (CancelledError, GeneratorExit):
         self.state = USER_REQUEST_STATE_CANCELLED
         raise
     
     except:
         self.state = USER_REQUEST_STATE_ERROR
         raise
     
     else:
         self.state = USER_REQUEST_STATE_DONE
     
     finally:
         self.task = None
Example #26
0
class VoiceClient:
    """
    Represents a client what is joined to a voice channel.
    
    Attributes
    ----------
    _audio_port : `int`
        The port, where the voice client should send the audio data.
    _audio_source : `int`
        An identifier sent by Discord what should be sent back with every voice packet.
    _audio_sources : `dict` of (`int`, `int`) items
        `user_id` - `audio_source` mapping used by ``AudioStream``-s.
    _audio_streams : `None`, `dict` of (`int`, (``AudioStream`` or (`list` of ``AudioStream``)) items
        `user_id` - ``AudioStream``(s) mapping for linking ``AudioStream`` to their respective user.
    _encoder : ``OpusEncoder``
        Encode not opus encoded audio data.
    _endpoint : `None`, `str`
        The endpoint, where the voice client sends the audio data.
    _endpoint_ip : `None`, `tuple` of `int`
        The ip version of the `._endpoint` attribute.
    _handshake_complete : ``Future``
        Used for awaiting the connecting handshake with Discord.
    _ip : `None`, `tuple` of `int`
        The ip to what the voice client's gateway connects.
    _port : `None`, `int`
        The port to what the voice client's gateway connects.
    _pref_volume : `float`
        The preferred volume of the voice client. can be between `0.0` and `2.0`.
    _protocol : `None`, ``DatagramMergerReadProtocol``
        Asynchronous protocol of the voice client to communicate with it's socket.
    _reconnecting : `bool`
        Whether the voice client plans to reconnect and it's reader and player should not be stopped.
    _secret_box : `None`, `nacl.secret.SecretBox`
        Data encoder of the voice client.
    _sequence : `int`
        Counter to define the sent data's sequence for Discord.
    _session_id : `None`, `str`
        The session id of the voice client's owner client's shard.
    _set_speaking_task : `None`, ``Task``
        Synchronization task for the `.set_speaking` coroutine.
    _socket : `None`, `socket.socket`
        The socket through what the ``VoiceClient`` sends the voice data to Discord. Created by the ``._create_socket``
        method, when the client's gateway receives response after connecting. If the client leaves the voice channel,
        then the socket is closed and set back to `None`.
    _timestamp : `int`
        A timestamp identifier to tell Discord how much frames we sent to it.
    _token : `str`
        Token received by the voice client's owner client's gateway. Used to authorize the voice client.
    _transport : `None`, ``_SelectorDatagramTransport``
        Asynchronous transport of the voice client to communicate with it's socket.
    _video_source : `int`
        An identifier sent by Discord what should be sent back with every video packet.
    _video_sources : `dict` of (`int`, `int`) items
        `user_id` - `video_source` mapping. Not used for now.
    call_after : `callable` (`awaitable`)
        A coroutine function what is awaited, when the voice clients's current audio finishes playing. By default
        this attribute is set to the ``._play_next`` function of the voice client (plays the next audio at the voice
        clients's ``.queue`` as expected.
        
        This attribute of the client can be modified freely. To it `2` parameters are passed:
         +------------------+---------------------------+
         | Respective name  | Type                      |
         +==================+===========================+
         | client           | ``VoiceClient``           |
         +------------------+---------------------------+
         | last_source      | `None`, ``AudioSource``   |
         +------------------+---------------------------+
         
         The ``VoiceClient`` also includes some other predefined function for setting as `call_after`:
         - ``._play_next``
         - ``._loop_actual``
         - ``._loop_queue``
     
    channel_id : `int`
        The channel's identifier where the voice client currently is.
    client : ``Client``
        The voice client's owner client.
    connected : ``Event``
        Used to communicate with the ``AudioPlayer`` thread.
    gateway : ``DiscordGatewayVoice``
        The gateway through what the voice client communicates with Discord.
    guild_id : `int``
        The guild's identifier where the voice client is.
    lock : `Lock`
        A lock used meanwhile changing the currently playing audio to not modifying it parallelly.
    player : ``AudioPlayer``
        The actual player of the ``VoiceClient``. If the voice client is not playing nor paused, then set as `None`.
    queue : `list` of ``AudioSource``
        A list of the scheduled audios.
    reader : `None`, ``AudioReader``
        Meanwhile the received audio is collected, this attribute is set to a running ``AudioReader``.
    region : ``VoiceRegion``
        The actual voice region of the voice client.
    speaking : `int`
        Whether the client is showed by Discord as `speaking`, then this attribute should is set as `1`. Can be
        modified, with the ``.set_speaking``, however it is always adjusted to the voice client's current playing state.
    """
    __slots__ = ('_audio_port', '_audio_source', '_audio_sources',
                 '_audio_streams', '_encoder', '_endpoint', '_endpoint_ip',
                 '_handshake_complete', '_ip', '_port', '_pref_volume',
                 '_protocol', '_reconnecting', '_secret_box', '_sequence',
                 '_session_id', '_set_speaking_task', '_socket', '_timestamp',
                 '_token', '_transport', '_video_source', '_video_sources',
                 'call_after', 'channel_id', 'client', 'connected', 'gateway',
                 'guild_id', 'lock', 'player', 'queue', 'reader', 'region',
                 'speaking')

    def __new__(cls, client, guild_id, channel_id):
        """
        Creates a ``VoiceClient``. If any of the required libraries are not present, raises `RuntimeError`.
        
        If the voice client was successfully created, returns a ``Future``, what is a waiter for it's ``._connect``
        method. If connecting failed, then the future will raise `TimeoutError`.
        
        Parameters
        ----------
        client : ``Client``
            The parent client.
        guild_id : `int`
            the guild's identifier, where the the client will connect to.
        channel_id : `int`
            The channel's identifier where the client will connect to.
        
        Returns
        -------
        waiter : ``Future``
        
        Raises
        ------
        RuntimeError
            If `PyNaCl` is not loaded.
            If `Opus` is not loaded.
        """
        # raise error at __new__
        if SecretBox is None:
            raise RuntimeError('PyNaCl is not loaded.')

        if OpusEncoder is None:
            raise RuntimeError('Opus is not loaded.')

        region = try_get_voice_region(guild_id, channel_id)

        self = object.__new__(cls)

        self.guild_id = guild_id
        self.channel_id = channel_id
        self.region = region
        self.gateway = DiscordGatewayVoice(self)
        self._socket = None
        self._protocol = None
        self._transport = None
        self.client = client
        self.connected = Event(KOKORO)
        self.queue = []
        self.player = None
        self.call_after = cls._play_next
        self.speaking = 0
        self.lock = Lock(KOKORO)
        self.reader = None

        self._handshake_complete = Future(KOKORO)
        self._encoder = OpusEncoder()
        self._sequence = 0
        self._timestamp = 0
        self._audio_source = 0
        self._video_source = 0
        self._pref_volume = 1.0
        self._set_speaking_task = None
        self._endpoint = None
        self._port = None
        self._endpoint_ip = None
        self._secret_box = None
        self._audio_port = None
        self._ip = None
        self._audio_sources = {}
        self._video_sources = {}
        self._audio_streams = None
        self._reconnecting = True

        client.voice_clients[guild_id] = self
        waiter = Future(KOKORO)
        Task(self._connect(waiter=waiter), KOKORO)
        return waiter

    # properties
    def _get_volume(self):
        return self._pref_volume

    def _set_volume(self, value):
        if value < 0.:
            value = 0.
        elif value > 2.:
            value = 2.

        self._pref_volume = value

    volume = property(_get_volume, _set_volume)
    del _get_volume, _set_volume
    if DOCS_ENABLED:
        volume.__doc__ = ("""
        Get-set property for accessing the voice client's volume.
        
        Can be between `0.0` and `2.0`.
        """)

    @property
    def source(self):
        """
        Returns the voice client's player's source if applicable.
        
        Returns
        -------
        source : `None`, ``AudioSource``
        """
        player = self.player
        if player is None:
            return

        return player.source

    # methods
    async def set_speaking(self, value):
        """
        A coroutine, what is used when changing the ``.speaking`` state of the voice client. By default when audio is
        played, the speaking state is changed to `True` and meanwhile not, then to `False`.
        
        This method is a coroutine.
        
        Parameters
        ----------
        value : `int` (`0`, `1`)
        
        Notes
        -----
        Tinkering with this method is not recommended.
        """
        task = self._set_speaking_task
        if (task is not None):
            await task

        if self.speaking == value:
            return

        self.speaking = value

        task = Task(self.gateway._set_speaking(value), KOKORO)
        self._set_speaking_task = task

        try:
            await task
        finally:
            self._set_speaking_task = None

    def listen_to(self, user, **kwargs):
        """
        Creates an audio stream for the given user.
        
        Parameters
        ----------
        user : ``UserBase``
            The user, who's voice will be captured.
        **kwargs : Keyword parameters
            Additional keyword parameters.
        
        Other Parameters
        ----------------
        auto_decode : `bool`
            Whether the received packets should be auto decoded.
        yield_decoded : `bool`
            Whether the audio stream should yield encoded data.
        
        Returns
        -------
        audio_stream : ``AudioStream``
        """
        stream = AudioStream(self, user, **kwargs)
        self._link_audio_stream(stream)
        return stream

    def _link_audio_stream(self, stream):
        """
        Links the given ``AudioStream`` to self causing to start receiving audio.
        
        Parameters
        ----------
        stream : ``AudioStream``
        """
        voice_client_audio_streams = self._audio_streams
        if voice_client_audio_streams is None:
            voice_client_audio_streams = self._audio_streams = {}

        user_id = stream.user.id
        try:
            voice_client_actual_stream = voice_client_audio_streams[user_id]
        except KeyError:
            voice_client_audio_streams[user_id] = stream
        else:
            if type(voice_client_actual_stream) is list:
                voice_client_actual_stream.append(stream)
            else:
                voice_client_audio_streams[user_id] = [
                    voice_client_actual_stream, stream
                ]

        source = stream.source
        if (source is not None):
            reader = self.reader
            if reader is None:
                reader = self.reader = AudioReader(self)

            reader_audio_streams = reader.audio_streams
            try:
                reader_actual_stream = reader_audio_streams[source]
            except KeyError:
                reader_audio_streams[source] = stream
            else:
                if type(reader_actual_stream) is list:
                    reader_actual_stream.append(stream)
                else:
                    reader_audio_streams[source] = [
                        reader_actual_stream, stream
                    ]

    def _unlink_audio_stream(self, audio_stream):
        """
        Un-links the given audio stream from the voice client causing it to stop receiving audio.
        
        Parameters
        ----------
        audio_stream : ``AudioStream``
        """
        voice_client_audio_streams = self._audio_streams
        if (voice_client_audio_streams is not None):
            user_id = audio_stream.user.id
            try:
                voice_client_actual_stream = voice_client_audio_streams[
                    user_id]
            except KeyError:
                pass
            else:
                if type(voice_client_actual_stream) is list:
                    try:
                        voice_client_actual_stream.remove(audio_stream)
                    except ValueError:
                        pass
                    else:
                        if len(voice_client_actual_stream) == 1:
                            voice_client_audio_streams[
                                user_id] = voice_client_actual_stream[0]
                else:
                    if voice_client_actual_stream is audio_stream:
                        del voice_client_audio_streams[user_id]

        reader = self.reader
        if (reader is not None):
            source = audio_stream.source
            if (source is not None):
                reader_audio_streams = reader.audio_streams
                try:
                    reader_actual_stream = reader_audio_streams[source]
                except KeyError:
                    pass
                else:
                    if type(reader_actual_stream) is list:
                        try:
                            reader_actual_stream.remove(audio_stream)
                        except ValueError:
                            pass
                        else:
                            if len(reader_actual_stream) == 1:
                                reader_audio_streams[
                                    source] = reader_actual_stream[0]
                    else:
                        if reader_actual_stream is audio_stream:
                            del reader_audio_streams[source]

    def _remove_source(self, user_id):
        """
        Un-links the audio and video streams's source listening to the given user (id), causing the affected audio
        str-eam(s) to stop receiving audio data at the meanwhile.
        
        Parameters
        ----------
        user_id : `int`
            The respective user's id.
        """
        voice_sources = self._audio_sources
        try:
            voice_source = voice_sources.pop(user_id)
        except KeyError:
            pass
        else:
            audio_streams = self._audio_streams
            if (audio_streams is not None):
                try:
                    audio_streams[user_id]
                except KeyError:
                    pass
                else:
                    reader = self.reader
                    if (reader is not None):
                        try:
                            del reader.audio_streams[voice_source]
                        except KeyError:
                            pass

        try:
            del self._video_sources[user_id]
        except KeyError:
            pass

    def _update_audio_source(self, user_id, audio_source):
        """
        Updates (or adds) an `user-id` - `audio-source` relation to the voice client causing the affected audio
        streams to listen to their new source.
        
        Parameters
        ----------
        user_id : `int`
            The respective user's id.
        audio_source : `int`
            Audio source identifier of the user.
        """
        voice_sources = self._audio_sources
        try:
            old_audio_source = voice_sources.pop(user_id)
        except KeyError:
            # Should not happen if it is an update, only if it is an add
            pass
        else:
            # Should happen if it is an update
            if audio_source == old_audio_source:
                # Should double happen if it is an update
                return

            reader = self.reader
            if (reader is not None):
                reader_audio_streams = reader.audio_streams
                try:
                    del reader_audio_streams[old_audio_source]
                except KeyError:
                    pass

        voice_sources[user_id] = audio_source

        streams = self._audio_streams
        if streams is None:
            return

        try:
            voice_client_actual_stream = streams[user_id]
        except KeyError:
            return

        # Link source
        if type(voice_client_actual_stream) is list:
            for stream in voice_client_actual_stream:
                stream.source = audio_source
        else:
            voice_client_actual_stream.source = audio_source

        # Add the sources to reader
        reader = self.reader
        if reader is None:
            reader = self.reader = AudioReader(self)

        reader_audio_streams = reader.audio_streams
        try:
            reader_actual_stream = reader_audio_streams[audio_source]
        except KeyError:
            # This should happen
            if type(voice_client_actual_stream) is list:
                reader_new_stream = voice_client_actual_stream.copy()
            else:
                reader_new_stream = voice_client_actual_stream
            reader_audio_streams[audio_source] = reader_new_stream
        else:
            # Should not happen
            if type(reader_actual_stream) is list:
                if type(voice_client_actual_stream) is list:
                    reader_actual_stream.extend(voice_client_actual_stream)
                else:
                    reader_actual_stream.append(voice_client_actual_stream)
            else:
                reader_new_stream = [reader_actual_stream]
                if type(voice_client_actual_stream) is list:
                    reader_new_stream.extend(voice_client_actual_stream)
                else:
                    reader_new_stream.append(voice_client_actual_stream)

                reader_audio_streams[audio_source] = reader_new_stream

    def _update_video_source(self, user_id, video_source):
        """
        Updates (or adds) an `user-id` - `video-source` relation to the voice client.
        
        Parameters
        ----------
        user_id : `int`
            The respective user's id.
        video_source : `int`
            Video source identifier of the user.
        """
        self._video_sources[user_id] = video_source

    def get_audio_streams(self):
        """
        Returns the audio streams of the voice client within a `list`.
        
        Returns
        -------
        streams : `list` of `tuple` (``ClientUserBase``, ``AudioStream``)
            Audio streams as a `list` of `tuples` of their respective listened `user` and `stream`.
        """
        streams = []
        voice_client_audio_streams = self._audio_streams
        if (voice_client_audio_streams is not None):
            for user_id, stream in voice_client_audio_streams.items():
                user = User.precreate(user_id)
                if type(stream) is list:
                    for stream in stream:
                        streams.append((user, stream))
                else:
                    streams.append((user, stream))

        return streams

    @property
    def voice_state(self):
        """
        Returns the voice state of the client.
        
        Returns
        -------
        voice_state : `None`, ``VoiceState``
        """
        try:
            guild = GUILDS[self.guild_id]
        except KeyError:
            pass
        else:
            return guild.voice_states.get(self.client.id, None)

    async def move_to(self, channel):
        """
        Move the voice client to an another voice channel.
        
        This method is a coroutine.
        
        Parameters
        ---------
        channel : ``ChannelVoiceBase``, `int`
            The channel where the voice client will move to.
        
        Returns
        -------
        moved : `bool`
            Returns `False` if the voice client is already in the channel.
        
        Raises
        ------
        TypeError
            If  `channel` was not given as ``ChannelVoiceBase`` not `int`.
        """
        if isinstance(channel, ChannelVoiceBase):
            channel_id = channel.id
        else:
            channel_id = maybe_snowflake(channel)
            if channel_id is None:
                raise TypeError(
                    f'`channel` can be `{ChannelVoiceBase.__name__}`, `int`, got '
                    f'{channel.__class__.__name__}; {channel!r}.')

        if self.channel_id == channel_id:
            return False

        gateway = self.client.gateway_for(self.guild_id)
        await gateway.change_voice_state(self.guild_id, channel_id)
        return True

    async def join_speakers(self, *, request=False):
        """
        Requests to speak at the voice client's voice channel. Only applicable for stage channels.
        
        This method is a coroutine.
        
        Parameters
        ----------
        request : `bool` = `False`, Optional (Keyword only)
            Whether the client should only request to speak.
        
        Raises
        ------
        ConnectionError
            No internet connection.
        DiscordException
            If any exception was received from the Discord API.
        """
        guild_id = self.guild_id
        try:
            guild = GUILDS[guild_id]
        except KeyError:
            pass
        else:
            try:
                voice_state = guild.voice_states[self.client.id]
            except KeyError:
                return

            if voice_state.is_speaker:
                return

        if request:
            timestamp = datetime_to_timestamp(datetime.utcnow())
        else:
            timestamp = None

        data = {
            'suppress': False,
            'request_to_speak_timestamp': timestamp,
            'channel_id': self.channel_id
        }

        await self.client.http.voice_state_client_edit(guild_id, data)

    async def join_audience(self):
        """
        Joins the audience in the voice client's voice channel. Only applicable for stage channels.
        
        This method is a coroutine.
        
        Raises
        ------
        ConnectionError
            No internet connection.
        DiscordException
            If any exception was received from the Discord API.
        """
        guild_id = self.guild_id
        try:
            guild = GUILDS[guild_id]
        except KeyError:
            pass
        else:
            try:
                voice_state = guild.voice_states[self.client.id]
            except KeyError:
                return

            if not voice_state.is_speaker:
                return

        data = {'suppress': True, 'channel_id': self.channel_id}

        await self.client.http.voice_state_client_edit(guild_id, data)

    def append(self, source):
        """
        Starts playing the given audio source. If the voice client is already playing, puts it on it's queue instead.
        
        Parameters
        ---------
        source : ``AudioSource``
            The audio source to put on the queue.
        
        Returns
        -------
        started_playing : `bool`
            Whether the source is started playing and not put on queue.
        """
        if not isinstance(source, AudioSource):
            raise TypeError(
                f'Expected `{AudioSource.__name__}`, got {source.__class__.__name__}; {source!r}.'
            )

        player = self.player
        if player is None:
            self.player = AudioPlayer(
                self,
                source,
            )
            Task(self.set_speaking(1), KOKORO)
            return True

        queue = self.queue
        if queue or (player.source is not None):
            queue.append(source)
            return False

        player.set_source(source)
        Task(self.set_speaking(1), KOKORO)
        return True

    def skip(self, index=0):
        """
        Skips the currently played audio at the given index and returns it.
        
        Skipping nothing yields to returning `None`.
        
        Parameters
        ----------
        index : `int` = `0`, Optional
            The index of the audio to skip. Defaults to `0`, what causes the currently playing source to skipped.
        
        Returns
        -------
        source : `None`, ``AudioSource``
        """
        if index == 0:
            player = self.player
            if player is None:
                source = None
            else:
                source = player.source

            # Try playing next even if player is not `None`.
            Task(self.play_next(), KOKORO)

        elif index < 0:
            source = None
        else:
            queue = self.queue
            if index > len(queue):
                source = None
            else:
                source = queue.pop(index - 1)

        return source

    def pause(self):
        """
        Pauses the currently played audio if applicable.
        """
        player = self.player
        if (player is not None):
            player.pause()
            Task(self.set_speaking(0), KOKORO)

    def resume(self):
        """
        Resumes the currently stopped audio if applicable.
        """
        player = self.player
        if (player is not None):
            player.resume()
            Task(self.set_speaking(1), KOKORO)

    def stop(self):
        """
        Stops the currently playing audio and clears the audio queue.
        """
        self.queue.clear()

        player = self.player
        if (player is not None):
            self.player = None
            player.stop()

    def is_connected(self):
        """
        Returns whether the voice client is connected to a ``ChannelVoice``.
        
        Returns
        -------
        is_connected : `bool`
        """
        return self.connected.is_set()

    def is_playing(self):
        """
        Returns whether the voice client is currently playing audio.
        
        Returns
        -------
        is_playing : `bool`
        """
        player = self.player
        if player is None:
            return False

        if player.done:
            return False

        if not player.resumed_waiter.is_set():
            return False

        return True

    def is_paused(self):
        """
        Returns whether the voice client is currently paused (or not playing).
        
        Returns
        -------
        is_paused : `bool`
        """
        player = self.player
        if player is None:
            return True

        if player.done:
            return True

        if not player.resumed_waiter.is_set():
            return True

        return False

    # connection related

    async def _connect(self, waiter=None):
        """
        Connects the voice client to Discord and keeps receiving the gateway events.
        
        This method is a coroutine.
        
        Parameters
        ----------
        waiter : `None`, ``Future`` = `None`, Optional
            A Waiter what's result is set (or is raised to), when the voice client connects (or failed to connect).
        """
        try:
            await self.gateway.start()
            tries = 0
            while True:
                if tries == 5:
                    if (waiter is not None):
                        waiter.set_exception(TimeoutError())
                    return

                self._secret_box = None

                try:
                    await self._start_handshake()
                except TimeoutError:
                    tries += 1
                    continue
                except:
                    await self._disconnect(force=True)
                    raise

                try:
                    task = Task(self.gateway.connect(), KOKORO)
                    future_or_timeout(
                        task,
                        30.,
                    )
                    await task

                    self.connected.clear()
                    while True:
                        task = Task(self.gateway._poll_event(), KOKORO)
                        future_or_timeout(task, 60.)
                        await task

                        if self._secret_box is not None:
                            break

                    self.connected.set()

                except (OSError, TimeoutError, ConnectionError,
                        ConnectionClosed, WebSocketProtocolError,
                        InvalidHandshake, ValueError) as err:
                    self._maybe_close_socket()

                    if isinstance(err, ConnectionClosed) and (
                            err.code == VOICE_CLIENT_DISCONNECT_CLOSE_CODE):
                        # If we are getting disconnected and voice region changed, then Discord disconnects us, not
                        # user nor us, so reconnect.
                        if not self._maybe_change_voice_region():
                            self._reconnecting = False
                            await self._disconnect(force=False)
                            return

                    if not (isinstance(err, ConnectionClosed) and
                            (err.code == VOICE_CLIENT_RECONNECT_CLOSE_CODE)):
                        await sleep(1 + (tries << 1), KOKORO)

                    self._maybe_change_voice_region()

                    tries += 1
                    await self._terminate_handshake()
                    continue

                except:
                    await self._disconnect(force=True)
                    raise

                if (waiter is not None):
                    waiter.set_result(self)
                    waiter = None

                tries = 0
                while True:
                    try:
                        task = Task(self.gateway._poll_event(), KOKORO)
                        future_or_timeout(task, 60.)
                        await task
                    except (
                            OSError,
                            TimeoutError,
                            ConnectionClosed,
                            WebSocketProtocolError,
                    ) as err:
                        self._maybe_close_socket()

                        if isinstance(err, ConnectionClosed):
                            # If we are getting disconnected and voice region changed, then Discord disconnects us, not
                            # user nor us, so reconnect.
                            code = err.code
                            if code == 1000 or (
                                (code == VOICE_CLIENT_DISCONNECT_CLOSE_CODE)
                                    and
                                (not self._maybe_change_voice_region())):
                                self._reconnecting = False
                                await self._disconnect(force=False)
                                return

                        self.connected.clear()

                        if not (isinstance(err, ConnectionClosed) and
                                (err.code
                                 == VOICE_CLIENT_RECONNECT_CLOSE_CODE)):
                            await sleep(5., KOKORO)

                        self._maybe_change_voice_region()

                        await self._terminate_handshake()
                        break

                    except:
                        self._reconnecting = False
                        await self._disconnect(force=True)
                        raise
        finally:
            self._reconnecting = False

            try:
                del self.client.voice_clients[self.guild_id]
            except KeyError:
                pass

    async def disconnect(self):
        """
        Disconnects the voice client.
        
        This method is a coroutine.
        """
        await self._disconnect()

    async def _disconnect(self, force=False, terminate=True):
        """
        Disconnects the voice client.
        
        If you want to disconnect a voice client, then you should use ``.disconnect``. Passing bad parameters to this
        method the can cause misbehaviour.
        
        This method is a coroutine.
        
        Parameters
        ----------
        force : `bool` = `False`, Optional
            Whether the voice client should disconnect only if it is not connected (for example when it is connecting).
        terminate : `bool` = `True`, Optional
            Whether it is an internal disconnect. If the Disconnect comes from Discord's side, then `terminate` is
            `False`, what means, we do not need to terminate the gateway handshake.
        """
        if not (force or self.connected.is_set()):
            return

        self.queue.clear()

        if not self._reconnecting:
            player = self.player
            if (player is not None):
                self.player = None
                player.stop()

                # skip 1 full loop
                await skip_poll_cycle(KOKORO)

            reader = self.reader
            if (reader is not None):
                self.reader = None
                reader.stop()

        self.connected.clear()

        try:
            await self.gateway.close()
            if terminate:
                await self._terminate_handshake()
        finally:
            self._maybe_close_socket()

    @classmethod
    async def _kill_ghost(cls, client, voice_state):
        """
        When a client is restarted, it might happen that it will be in still in some voice channels. At this
        case this function is ensured to kill the ghost connection.
        
        This method is a coroutine.
        
        Parameters
        ----------
        client : ``Client``
            The owner client of the ghost connection.
        voice_state : ``VoiceState``
            The ghost voice client's voice state.
        """
        try:
            voice_client = await cls(client, voice_state.guild_id,
                                     voice_state.channel_id)
        except (RuntimeError, TimeoutError):
            return

        await voice_client._disconnect(force=True)

    async def play_next(self):
        """
        Skips the currently playing audio.
        
        Familiar to `.skip`, but it return when the operation id done.
        
        This method is a coroutine.
        """
        async with self.lock:
            await self._play_next(self, None)

    @staticmethod
    async def _play_next(self, last_source):
        """
        Starts to play the next audio object on ``.queue`` and cancels the actual one if applicable.
        Should be used inside of ``.lock`` to ensure that the voice client is not modified parallelly.
        
        This function is a coroutine.
        
        Parameters
        ----------
        self : ``VoiceClient``
            The respective voice client.
        last_source : `None`, ``AudioSource``
            The audio what was played.
        """
        player = self.player
        queue = self.queue
        if player is None:
            if not queue:
                return

            source = queue.pop(0)
            self.player = AudioPlayer(self, source)
            if self.connected.is_set():
                Task(self.set_speaking(1), KOKORO)

            return

        if not queue:
            player.set_source(None)
            if self.connected.is_set():
                Task(self.set_speaking(0), KOKORO)
            return

        source = queue.pop(0)
        player.set_source(source)
        if self.connected.is_set():
            Task(self.set_speaking(1), KOKORO)

    @staticmethod
    async def _loop_actual(self, last_source):
        """
        Repeats the last played audio if applicable.
        
        Should be used inside of ``.lock``to ensure that the voice client is not modified parallelly.
        
        This function is a coroutine.
        
        Parameters
        ----------
        self : ``VoiceClient``
            The respective voice client.
        last_source : `None`, ``AudioSource``
            The audio what was played.
        """
        if (last_source is None) or (not last_source.REPEATABLE):
            await self._play_next(self, None)
            return

        player = self.player
        if player is None:
            # Should not happen, lol
            self.player = AudioPlayer(self, last_source)
        else:
            # The audio was over.
            player.set_source(last_source)

        if self.connected.is_set():
            Task(self.set_speaking(1), KOKORO)

    @staticmethod
    async def _loop_queue(self, last_source):
        """
        Puts the last played audio back on the voice client's queue.
        
        Should be used inside of ``.lock``to ensure that the voice client is not modified parallelly.
        
        This function is a coroutine.
        
        Parameters
        ----------
        self : ``VoiceClient``
            The respective voice client.
        last_source : `None`, ``AudioSource``
            The audio what was played.
        """
        if (last_source is not None) and last_source.REPEATABLE:
            # The last source was not skipped an we can repeat it.
            self.queue.append(last_source)

        await self._play_next(self, None)

    async def _start_handshake(self):
        """
        Requests a gateway handshake from Discord. If we get answer on it, means, we can open the socket to send audio
        data.
        
        This method is a coroutine.
        
        Raises
        ------
        TimeoutError
            We did not receive answer in time.
        """
        client = self.client

        gateway = client.gateway_for(self.guild_id)

        # request joining
        await gateway.change_voice_state(self.guild_id, self.channel_id)
        future_or_timeout(self._handshake_complete, 60.0)

        try:
            await self._handshake_complete
        except TimeoutError:
            await self._terminate_handshake()
            raise

    async def _terminate_handshake(self):
        """
        Called when connecting to Discord fails. Ensures, that everything is aborted correctly.
        
        This method is a coroutine.
        """
        self._handshake_complete.clear()

        gateway = self.client.gateway_for(self.guild_id)

        try:
            await gateway.change_voice_state(self.guild_id, 0, self_mute=True)
        except ConnectionClosed:
            pass

        kokoro = self.gateway.kokoro
        if (kokoro is not None):
            kokoro.terminate()

    async def _create_socket(self, event):
        """
        Called when voice server update data is received from Discord.
        
        If full data was received, closes the actual socket if exists and creates a new one connected to the received
        address.
        
        If the voice client is already connected reconnects it, if not then marks it as connected.
        
        This method is a coroutine.
        
        Parameters
        ----------
        event : ``VoiceServerUpdateEvent``
            Voice server update event.
        """
        self.connected.clear()

        gateway = self.client.gateway_for(self.guild_id)

        self._session_id = gateway.session_id
        token = event.token
        self._token = token
        endpoint = event.endpoint

        if (endpoint is None) or (token is None):
            return

        self._endpoint = endpoint.replace(':80', '').replace(':443', '')

        self._maybe_close_socket()

        socket = module_socket.socket(module_socket.AF_INET,
                                      module_socket.SOCK_DGRAM)

        protocol = await KOKORO.create_datagram_connection_with(partial_func(
            DatagramMergerReadProtocol, KOKORO),
                                                                socket=socket)
        self._transport = protocol.get_transport()
        self._protocol = protocol
        self._socket = socket

        if self.reader is None:
            self.reader = AudioReader(self)

        handshake_complete = self._handshake_complete
        if handshake_complete.is_done():
            # terminate the websocket and handle the reconnect loop if necessary.
            handshake_complete.clear()
            await self.gateway.terminate()
        else:
            handshake_complete.set_result(None)

    def send_packet(self, packet):
        """
        Sends the given packet to Discord with the voice client's socket.
        
        Parameters
        ----------
        packet : `bytes-like`
            The packet to send.
        """
        transport = self._transport
        if (transport is not None):
            transport.send_to(packet, (self._endpoint_ip, self._audio_port))

    def __del__(self):
        """Stops and unallocates the resources by the voice client, if was not done already."""
        self.stop()
        self.connected.set()

        player = self.player
        if (player is not None):
            self.player = None
            player.stop()

        reader = self.reader
        if (reader is not None):
            self.reader = None
            reader.stop()

        self._maybe_close_socket()

    def _maybe_close_socket(self):
        """
        Closes the voice client's socket and transport if they are set.
        """
        protocol = self._protocol
        if (protocol is not None):
            self._protocol = None
            self._transport = None
            protocol.close()

        socket = self._socket
        if socket is not None:
            self._socket = None
            socket.close()

    def _maybe_change_voice_region(self):
        """
        Resets the voice region of the voice client.
        
        Returns
        -------
        changed: `bool`
            Whether voice region changed.
        """
        region = try_get_voice_region(self.guild_id, self.channel_id)

        if region is self.region:
            changed = False
        else:
            self.region = region
            changed = True

        return changed

    def __repr__(self):
        """Returns the voice client's representation."""
        repr_parts = [
            '<',
            self.__class__.__name__,
            ' client=',
            repr(self.client.full_name),
            ', channel_id=',
            repr(self.channel_id),
            ', guild_id=',
            repr(self.guild_id),
        ]

        repr_parts.append('>')

        return ''.join(repr_parts)

    @property
    def channel(self):
        """
        Returns the voice client's channel.
        
        Returns
        -------
        channel : `None`, ``ChannelVoiceBase``
        """
        return CHANNELS.get(self.channel_id, None)

    @property
    def guild(self):
        """
        Returns the voice client's guild.
        
        Returns
        -------
        guild : `None`, ``Guild``
        """
        return GUILDS.get(self.guild_id, None)