async def _keep_beating(self): """ The beater task of kokoro. The control flow of tze method is the following: - If `.should_beat` is not `True`, break out. - Wait `.interval` time and set the waiting ``Future`` to `self.beat_waiter`. If kokoro is cancelled, or if it should beat now, it is cancelled and we repeat the loop. - If we did not get answer since last beating (what is triggered first from outside), then we stop the gateway. We also break out from the loop to terminate the beating state and we will wait for the websocket to connect again. - We beat one with starting `gateway._beat` as a ``Task`` and setting it to `.beat_task`. If the task is not completed before it's respective timeout, we stop the gateway here as well. We also break out from the loop to terminate the beating state and we will wait for the websocket to connect again. This task can also be cancelled. If cancellation occurs, we repeat the loop. - If the beating task is done, we update `.last_send` to the current `perf_counter` time. Repeat the loop. This method is a coroutine. """ self.last_answer = perf_counter() gateway = self.gateway self.should_beat = True while self.should_beat: waiter = sleep(self.interval, KOKORO) self.beat_waiter = waiter try: await waiter except CancelledError: self.last_send = perf_counter() continue finally: self.beat_waiter = None if (self.last_answer + self.interval + HEARTBEAT_TIMEOUT) - perf_counter() <= 0.0: self.should_beat = False Task(gateway.terminate(), KOKORO) break try: task = Task(gateway._beat(), KOKORO) future_or_timeout(task, HEARTBEAT_TIMEOUT) self.beat_task = task await task except TimeoutError: self.should_beat = False Task(gateway.terminate(), KOKORO) break except CancelledError: continue finally: self.beat_task = None self.last_send = perf_counter()
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)
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 write(self, data): """ Writes the given data to the buffer. Parameters ---------- data : `str` The data to write. Raises ------ TypeError Only string can be written to the output stream. ValueError I/O operation on closed or on a detached file. """ if self._closed: raise ValueError('I/O operation on closed or on a detached file.') if not isinstance(data, str): raise TypeError( f'Only `str` can be written into `{self.__class__.__name__}`, got ' f'{data.__class__.__name__}; {reprlib.repr(data)}.') if not data: return self._chunks.append(data) transfer_task = self._transfer_task if (transfer_task is None): self._transfer_task = Task(self._do_transfer(), KOKORO)
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
def cancel(self, exception=None): """ Cancels the pagination, if it is not cancelled yet. Parameters ---------- exception : `None`, ``BaseException`` = `None`, Optional Exception to cancel the pagination with. Defaults to `None` Returns ------- canceller_task : `None`, ``Task`` """ if self._task_flag in (GUI_STATE_READY, GUI_STATE_SWITCHING_PAGE, GUI_STATE_CANCELLING): self._task_flag = GUI_STATE_CANCELLED canceller = self._canceller if canceller is None: return self._canceller = None timeouter = self._timeouter if (timeouter is not None): timeouter.cancel() return Task(canceller(self, exception), KOKORO)
def delete_reaction_with(self, client): """ Removes the added reaction. Parameters ---------- client : ``Client`` The client, who will execute the action. Returns ------- result : `int` The identifier number of the action what will be executed. Can be one of the following: +-----------------------+-------+ | Respective name | Value | +=======================+=======+ | DELETE_REACTION_OK | 0 | +-----------------------+-------+ | DELETE_REACTION_PERM | 1 | +-----------------------+-------+ """ if self.message.channel.cached_permissions_for( client) & PERMISSION_MASK_MANAGE_MESSAGES: Task(_delete_reaction_with_task(self, client), KOKORO) result = self.DELETE_REACTION_OK else: result = self.DELETE_REACTION_PERM return result
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
def start_beating(self): """ Starts kokoro's beating. Handles all the cases around the board to do so. """ # case 1 : we are not running if not self.running: Task(self._start_beating(), KOKORO) return #case 2 : we wait for ws waiter = self.ws_waiter if (waiter is not None): self.ws_waiter = None waiter.set_result(None) return #case 3 : we wait for beat response waiter = self.beat_waiter if (waiter is not None): self.beat_waiter = None waiter.cancel() return #case 4 : we are beating task = self.beat_task if (task is not None): self.beat_waiter = None task.cancel() return
def _console_exit_callback(): """ Callback used by ``run_console_till_interruption`` to stop the running clients. """ WaitTillAll( [Task(client.disconnect(), KOKORO) for client in CLIENTS.values()], KOKORO).sync_wrap().wait()
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
async def _node_disconnected(self, node): """ Called when a node is disconnected from Lavalink. Parameters ---------- node : `SolarNode` The node that has just connected. """ players = node.players if players: best_node = self.find_ideal_node(node.region) if best_node is None: player_queue = self._player_queue if player_queue is None: player_queue = list(players) self._player_queue = player_queue else: player_queue.extend(players) else: tasks = [] for player in players: task = Task(player.change_node(best_node), KOKORO) tasks.append(task) await WaitTillAll(tasks, KOKORO)
def call_ready(self): """ Calls the ready event handler of the respective client. """ client = self.client_reference() if (client is not None): client.ready_state = None Task(client.events.ready(client), 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 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 parse_track_stuck(client, data): guild_id = int(data[LAVALINK_KEY_GUILD_ID]) try: player = client.solarlink.players[guild_id] except KeyError: return event = TrackStuckEvent(player, data) Task(client.solarlink._events.track_stuck(client, event), KOKORO)
def parse_player_websocket_closed(client, data): guild_id = int(data[LAVALINK_KEY_GUILD_ID]) try: player = client.solarlink.players[guild_id] except KeyError: return event = PlayerWebsocketClosedEvent(player, data) Task(client.solarlink._events.player_websocket_closed(client, event), KOKORO)
async def handle_voice_client_shutdown(client): """ Called when the client logs out. This function is a coroutine. """ tasks = [] for player in client.solarlink.players.values(): task = Task(player.disconnect(), KOKORO) tasks.append(task) task = None await WaitTillAll(tasks, KOKORO) tasks = [] for node in client.soalrlink.nodes: task = Task(node.close(), KOKORO) tasks.append(task) task = None await WaitTillAll(tasks, KOKORO)
def stop_clients(): """ Stops all the running clients. Can be called from any thread. """ for client in CLIENTS.values(): if client.running: Task(client.disconnect(), KOKORO) if (current_thread() is not KOKORO): KOKORO.wake_up()
async def close(self): """ Cancels the gateway sharder's gateways. This method is a coroutine. """ tasks = [] for gateway in self.gateways: task = Task(gateway.close(), KOKORO) tasks.append(task) await WaitTillAll(tasks, KOKORO)
def _trigger_auto_post(top_gg_client_reference): """ Triggers an auto post. Parameters ---------- top_gg_client_reference : ``WeakReferer`` to ``TopGGClient`` Weak reference to the top.gg client. """ top_gg_client = top_gg_client_reference() if (top_gg_client is not None): Task(_do_auto_post(top_gg_client, top_gg_client_reference), KOKORO)
async def restart(self): """ Restarts kokoro. This method is a coroutine. """ self.cancel() # skip 1 loop await skip_ready_cycle() self.task = Task(self._start(), KOKORO) # skip 1 loop await skip_ready_cycle()
def ensure(self, coroutine): """ Ensures the coroutine within the interaction response context This method is an awaitable generator. Parameters ---------- coroutine : ``CoroutineType`` """ self.interaction_event._async_task = Task(self._async(coroutine), KOKORO) yield # skip a ready cycle
def __init__(self, voice_client): """ Creates an ``AudioReader`` bound to the given voice client. Parameters ---------- voice_client : ``VoiceClient`` The parent voice client. """ self.voice_client = voice_client self.done = False self.audio_streams = {} self.task = Task(self.run(), KOKORO)
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
def trigger_voice_client_ghost_event(client, voice_state): """ Triggers `Client.events.voice_client_ghost` if set. Parameters ---------- client : ``Client`` The respective client instance. voice_state : ``VoiceState`` The client's ghost voice state. """ event_handler = client.events.voice_client_ghost if (event_handler is not DEFAULT_EVENT_HANDLER): Task(event_handler(client, voice_state), KOKORO)
def start_clients(): """ Starts up all the not running clients. Can be called from any thread. """ for client in CLIENTS.values(): if client.running: continue Task(client.connect(), KOKORO) if (current_thread() is not KOKORO): KOKORO.wake_up()
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)
async def start(self): """ Starts the gateways of the sharder gateway. This method is a coroutine. """ tasks = [] for gateway in self.gateways: task = Task(gateway.start(), KOKORO) tasks.append(task) await WaitTillExc(tasks, KOKORO) for task in tasks: task.cancel()
async def _start_beating(self): """ Internal method to start beating when kokoro is not running. This method is a coroutine. """ # starts kokoro, then beating self.task = Task(self._start(), KOKORO) # skip 1 loop await skip_ready_cycle() waiter = self.ws_waiter if (waiter is not None): self.ws_waiter = None waiter.set_result(None)