From 5523ae9a020a7bcd0fa1a39119ee13e181e2fc0b Mon Sep 17 00:00:00 2001 From: Tristiisch Date: Wed, 8 Nov 2023 03:51:39 +0100 Subject: [PATCH 1/7] discord: A first message is answered before being queued. --- src/pyramid/connector/discord/guild_cmd.py | 38 ++----- src/pyramid/tools/message_sender.py | 126 ++++++++++----------- src/pyramid/tools/message_sender_queued.py | 49 ++++++++ 3 files changed, 124 insertions(+), 89 deletions(-) create mode 100644 src/pyramid/tools/message_sender_queued.py diff --git a/src/pyramid/connector/discord/guild_cmd.py b/src/pyramid/connector/discord/guild_cmd.py index 2b1e422..965e549 100644 --- a/src/pyramid/connector/discord/guild_cmd.py +++ b/src/pyramid/connector/discord/guild_cmd.py @@ -24,8 +24,7 @@ def __init__( self.data = guild_data self.queue = guild_queue - async def play(self, ctx: Interaction, input: str, at_end=True) -> bool: - ms = MessageSender(ctx) + async def play(self, ms: MessageSender, ctx: Interaction, input: str, at_end=True) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel: return False @@ -39,8 +38,7 @@ async def play(self, ctx: Interaction, input: str, at_end=True) -> bool: return await self._execute_play(ms, voice_channel, track, at_end=at_end) - async def stop(self, ctx: Interaction) -> bool: - ms = MessageSender(ctx) + async def stop(self, ms: MessageSender, ctx: Interaction) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False @@ -53,8 +51,7 @@ async def stop(self, ctx: Interaction) -> bool: await ms.response_message(content="Music stop") return True - async def pause(self, ctx: Interaction) -> bool: - ms = MessageSender(ctx) + async def pause(self, ms: MessageSender, ctx: Interaction) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False @@ -66,8 +63,7 @@ async def pause(self, ctx: Interaction) -> bool: await ms.response_message(content="Music paused") return True - async def resume(self, ctx: Interaction) -> bool: - ms = MessageSender(ctx) + async def resume(self, ms: MessageSender, ctx: Interaction) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False @@ -79,8 +75,7 @@ async def resume(self, ctx: Interaction) -> bool: await ms.response_message(content="Music resume") return True - async def next(self, ctx: Interaction) -> bool: - ms = MessageSender(ctx) + async def next(self, ms: MessageSender, ctx: Interaction) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False @@ -101,8 +96,7 @@ async def next(self, ctx: Interaction) -> bool: await ms.response_message(content="Skip musique") return True - async def suffle(self, ctx: Interaction): - ms = MessageSender(ctx) + async def suffle(self, ms: MessageSender, ctx: Interaction): voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False @@ -114,8 +108,7 @@ async def suffle(self, ctx: Interaction): await ms.response_message(content="The queue has been shuffled.") return True - async def remove(self, ctx: Interaction, number_in_queue: int): - ms = MessageSender(ctx) + async def remove(self, ms: MessageSender, ctx: Interaction, number_in_queue: int): voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False @@ -144,8 +137,7 @@ async def remove(self, ctx: Interaction, number_in_queue: int): ) return True - async def goto(self, ctx: Interaction, number_in_queue: int): - ms = MessageSender(ctx) + async def goto(self, ms: MessageSender, ctx: Interaction, number_in_queue: int): voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False @@ -173,8 +165,7 @@ async def goto(self, ctx: Interaction, number_in_queue: int): await ms.response_message(content=f"f{tracks_removed + 1} tracks has been skipped") return True - async def queue_list(self, ctx: Interaction) -> bool: - ms = MessageSender(ctx) + async def queue_list(self, ms: MessageSender, ctx: Interaction) -> bool: queue: str | None = self.queue.queue_list() if queue is None: await ms.response_message(content="Queue is empty") @@ -183,9 +174,7 @@ async def queue_list(self, ctx: Interaction) -> bool: await ms.add_code_message(queue, prefix="Here's the music in the queue :") return True - async def search(self, ctx: Interaction, input: str, engine: str | None) -> bool: - ms = MessageSender(ctx) - + async def search(self, ms: MessageSender, ctx: Interaction, input: str, engine: str | None) -> bool: if engine is None: search_engine = self.data.search_engine else: @@ -206,8 +195,7 @@ async def search(self, ctx: Interaction, input: str, engine: str | None) -> bool await ms.add_code_message(hsa, prefix="Here are the results of your search :") return True - async def play_multiple(self, ctx: Interaction, input: str) -> bool: - ms = MessageSender(ctx) + async def play_multiple(self, ms: MessageSender, ctx: Interaction, input: str) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel: return False @@ -221,13 +209,11 @@ async def play_multiple(self, ctx: Interaction, input: str) -> bool: return await self._execute_play_multiple(ms, voice_channel, tracks) - async def play_url(self, ctx: Interaction, url: str, at_end=True) -> bool: - ms = MessageSender(ctx) + async def play_url(self, ms: MessageSender, ctx: Interaction, url: str, at_end=True) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel: return False - ms = MessageSender(ctx) await ms.response_message(content=f"Searching **{url}** ...") # ctx.client.loop diff --git a/src/pyramid/tools/message_sender.py b/src/pyramid/tools/message_sender.py index e092683..e8d4421 100644 --- a/src/pyramid/tools/message_sender.py +++ b/src/pyramid/tools/message_sender.py @@ -6,13 +6,12 @@ from discord import Interaction, Message, TextChannel, WebhookMessage from discord.errors import HTTPException from discord.utils import MISSING -from tools.queue import Queue, QueueItem MAX_MSG_LENGTH = 2000 -queue = Queue(1, "MessageSender") -queue.start() -queue.register_to_wait_on_exit() +# queue = Queue(1, "MessageSender") +# queue.start() +# queue.register_to_wait_on_exit() class MessageSender: @@ -37,7 +36,8 @@ async def add_message( self, content: str = MISSING, callback: Callable | None = None, - ) -> None: + # ) -> None: + ) -> Message: if content != MISSING and content != "": new_content, is_used = tools.substring_with_end_msg( content, MAX_MSG_LENGTH, "{} more characters..." @@ -46,28 +46,28 @@ async def add_message( content = new_content if not self.__ctx.response.is_done(): - # msg = await self.txt_channel.send(content) - queue.add( - QueueItem( - "Send reponse", self.txt_channel.send, self.loop, callback, content=content - ) - ) - else: - # msg = await self.__ctx.followup.send( - # content, - # wait=True, + msg = await self.txt_channel.send(content) + # queue.add( + # QueueItem( + # "Send reponse", self.txt_channel.send, self.loop, callback, content=content + # ) # ) - queue.add( - QueueItem( - "Send followup", - self.__ctx.followup.send, - self.loop, - callback, - content=content, - wait=True, - ) + else: + msg = await self.__ctx.followup.send( + content, + wait=True, ) - # return msg + # queue.add( + # QueueItem( + # "Send followup", + # self.__ctx.followup.send, + # self.loop, + # callback, + # content=content, + # wait=True, + # ) + # ) + return msg """ Send a message as a response. If the response has already been sent, it will be modified. @@ -92,17 +92,17 @@ async def response_message( elif self.__ctx.response.is_done(): try: - # await self.__ctx.edit_original_response( - # content=content, - # ) - queue.add( - QueueItem( - "Edit response", - self.__ctx.edit_original_response, - self.loop, - content=content, - ) + await self.__ctx.edit_original_response( + content=content, ) + # queue.add( + # QueueItem( + # "Edit response", + # self.__ctx.edit_original_response, + # self.loop, + # content=content, + # ) + # ) except HTTPException as err: if err.code == 50027: # 401 Unauthorized : Invalid Webhook Token logging.warning( @@ -113,17 +113,17 @@ async def response_message( else: raise err else: - # await self.__ctx.response.send_message( - # content=content, - # ) - queue.add( - QueueItem( - "Send followup as response", - self.__ctx.response.send_message, - self.loop, - content=content, - ) + await self.__ctx.response.send_message( + content=content, ) + # queue.add( + # QueueItem( + # "Send followup as response", + # self.__ctx.response.send_message, + # self.loop, + # content=content, + # ) + # ) """ Send a message with markdown code formatting. If the character limit is exceeded, send multiple messages. @@ -149,24 +149,24 @@ async def add_code_message(self, content: str, prefix=None, suffix=None): first_substring = next(substrings_generator, None) if first_substring is not None: first_substring_formatted = f"```{first_substring}```" - # await self.__ctx.response.send_message(content=first_substring_formatted) - queue.add( - QueueItem( - "Send code as response", - self.__ctx.response.send_message, - self.loop, - content=first_substring_formatted, - ) - ) + await self.__ctx.response.send_message(content=first_substring_formatted) + # queue.add( + # QueueItem( + # "Send code as response", + # self.__ctx.response.send_message, + # self.loop, + # content=first_substring_formatted, + # ) + # ) for substring in substrings_generator: substring_formatted = f"```{substring}```" - # await self.__ctx.followup.send(content=substring_formatted) - queue.add( - QueueItem( - "Send code as followup", - self.__ctx.followup.send, - self.loop, - content=substring_formatted, - ) - ) + await self.__ctx.followup.send(content=substring_formatted) + # queue.add( + # QueueItem( + # "Send code as followup", + # self.__ctx.followup.send, + # self.loop, + # content=substring_formatted, + # ) + # ) diff --git a/src/pyramid/tools/message_sender_queued.py b/src/pyramid/tools/message_sender_queued.py new file mode 100644 index 0000000..6f95987 --- /dev/null +++ b/src/pyramid/tools/message_sender_queued.py @@ -0,0 +1,49 @@ +from typing import Callable +from tools.message_sender import MessageSender + +from discord import Interaction +from discord.utils import MISSING +from tools.queue import Queue, QueueItem + +MAX_MSG_LENGTH = 2000 + +queue = Queue(1, "MessageSender") +queue.start() +queue.register_to_wait_on_exit() + + +class MessageSenderQueued(MessageSender): + def __init__(self, ctx: Interaction): + super().__init__(ctx) + + async def waiting(self): + await super().response_message("Waiting for result ...") + + async def add_message( + self, + content: str = MISSING, + callback: Callable | None = None, + ) -> None: + queue.add( + QueueItem( + "add_message", super().add_message, self.loop, content=content, callback=callback + ) + ) + + async def response_message( + self, + content: str = MISSING, + ): + queue.add( + QueueItem( + "response_message", super().response_message, self.loop, content=content + ) + ) + + async def add_code_message(self, content: str, prefix=None, suffix=None): + queue.add( + QueueItem( + "add_code_message", super().add_code_message, self.loop, content=content, prefix=prefix, suffix=suffix + ) + ) + From 4eac4bbfc11dd84e73144390ae9903586c0dce68 Mon Sep 17 00:00:00 2001 From: Tristiisch Date: Wed, 8 Nov 2023 03:51:52 +0100 Subject: [PATCH 2/7] remove progress bar in logs --- docker-compose.yml | 19 ++++- src/pyramid/connector/deezer/downloader.py | 10 ++- .../deezer/downloader_progress_bar.py | 73 +++++++++++++++++++ src/pyramid/connector/deezer/search.py | 10 ++- src/pyramid/connector/discord/bot_cmd.py | 64 +++++++++++----- src/pyramid/tools/logs_handler.py | 4 +- 6 files changed, 151 insertions(+), 29 deletions(-) create mode 100644 src/pyramid/connector/deezer/downloader_progress_bar.py diff --git a/docker-compose.yml b/docker-compose.yml index 769f61a..cafad61 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,26 @@ version: '3.7' +name: 'pyramid-dev' services: pyramid: image: tristiisch/pyramid:latest volumes: - - ./config.yml:/app/config.yml - - ./logs:/app/logs - - ./songs:/app/songs + - type: bind + source: ./config.yml + target: /app/config.yml + bind: + create_host_path: false + - type: bind + source: ./logs + target: /app/logs + bind: + create_host_path: true + - type: bind + source: ./songs + target: /app/songs + bind: + create_host_path: true restart: always networks: - pyramid_network diff --git a/src/pyramid/connector/deezer/downloader.py b/src/pyramid/connector/deezer/downloader.py index 9dcf247..7147e9e 100644 --- a/src/pyramid/connector/deezer/downloader.py +++ b/src/pyramid/connector/deezer/downloader.py @@ -2,6 +2,7 @@ import asyncio import logging import os +import time from typing import Callable import pydeezer.util @@ -10,6 +11,8 @@ from pydeezer.exceptions import LoginError from data.track import Track, TrackMinimal +from connector.deezer.downloader_progress_bar import DownloaderProgressBar +from urllib3.exceptions import MaxRetryError class DeezerDownloader: @@ -77,9 +80,14 @@ def __dl_track(self, func: Callable[..., None], track_info, file_name: str) -> b False, # lyrics ", ", # separator for multiple artists False, # show messages - None, # Custom progress bar + DownloaderProgressBar(), # Custom progress bar ) return True + except MaxRetryError: + track = Track(track_info["DATA"], None) + logging.warning("Downloader MaxRetryError %s", track) + time.sleep(5) + return self.__dl_track(func, track_info, file_name) except Exception: track = Track(track_info["DATA"], None) logging.warning("Unable to dl track %s", track, exc_info=True) diff --git a/src/pyramid/connector/deezer/downloader_progress_bar.py b/src/pyramid/connector/deezer/downloader_progress_bar.py new file mode 100644 index 0000000..43a7bc0 --- /dev/null +++ b/src/pyramid/connector/deezer/downloader_progress_bar.py @@ -0,0 +1,73 @@ +import logging +import time +from pydeezer.ProgressHandler import BaseProgressHandler + + +def convert_bytes_to_appropriate_unit(bytes_per_second): + units = ["bps", "Kbps", "Mbps", "Gbps", "Tbps"] + factor = 1000 + for unit in units: + if bytes_per_second < factor: + return f"{round(bytes_per_second)} {unit}" + bytes_per_second /= factor + + return f"{round(bytes_per_second)} {units[-1]}" + + +class DownloaderProgressBar(BaseProgressHandler): + def __init__(self): + pass + + def initialize(self, *args, **kwargs): + super().initialize(*args) + self.current_chunk_size: int + self.total_size: int + + self.start_time = time.time() + self.last_print = self.start_time + logging.info( + "Start download\t%s (%s)", + self.track_title, + self.track_quality + ) + + def update(self, *args, **kwargs): + super().update(**kwargs) + self.log_progress() + + def close(self, *args, **kwargs): + total_time = time.time() - self.start_time + average_speed = self.size_downloaded / total_time + logging.info( + "End download\t%s in %.2f seconds - %s", + self.track_title, + total_time, + convert_bytes_to_appropriate_unit(average_speed), + ) + + def log_progress(self): + current_time = time.time() + percentage = self.size_downloaded / self.total_size * 100 + duration_last_print = current_time - self.last_print + if duration_last_print < 1: + return + + duration = current_time - self.start_time + if duration == 0: + return + + current_speed = self.current_chunk_size / duration + remaining_size = self.total_size - self.size_downloaded + time_remaining = remaining_size / current_speed if current_speed > 0 else 0 + + if percentage == 100: + return + + logging.info( + "Downloading %s %.2f%% - %s - %.2f seconds remaining", + self.track_title, + percentage, + convert_bytes_to_appropriate_unit(current_speed), + time_remaining, + ) + self.last_print = current_time diff --git a/src/pyramid/connector/deezer/search.py b/src/pyramid/connector/deezer/search.py index 36e23d5..ca5f6bf 100644 --- a/src/pyramid/connector/deezer/search.py +++ b/src/pyramid/connector/deezer/search.py @@ -13,13 +13,14 @@ from data.track import TrackMinimalDeezer from data.a_search import ASearch +DEFAULT_LIMIT=100 class DeezerSearch(ASearch): def __init__(self): self.client = Client() self.tools = DeezerTools() self.strict = False - self.default_limit = 10 + self.default_limit = DEFAULT_LIMIT def search_track(self, search) -> TrackMinimalDeezer | None: search_results = self.client.search(query=search) @@ -34,7 +35,7 @@ def get_track_by_id(self, track_id: int) -> TrackMinimalDeezer | None: return None return TrackMinimalDeezer(track) - def search_tracks(self, search, limit=10) -> list[TrackMinimalDeezer] | None: + def search_tracks(self, search, limit=DEFAULT_LIMIT) -> list[TrackMinimalDeezer] | None: search_results = self.client.search(query=search, strict=self.strict) if not search_results or len(search_results) == 0: @@ -107,7 +108,7 @@ def get_album_tracks_by_id( return None return [TrackMinimalDeezer(element) for element in album.get_tracks()], [] - def get_top_artist(self, artist_name, limit=10) -> list[TrackMinimalDeezer] | None: + def get_top_artist(self, artist_name, limit=DEFAULT_LIMIT) -> list[TrackMinimalDeezer] | None: search_results = self.client.search_artists(query=artist_name, strict=self.strict) if not search_results or len(search_results) == 0: return None @@ -116,7 +117,7 @@ def get_top_artist(self, artist_name, limit=10) -> list[TrackMinimalDeezer] | No return [TrackMinimalDeezer(element) for element in top_tracks] def get_top_artist_by_id( - self, artist_id: int, limit=10 + self, artist_id: int, limit=DEFAULT_LIMIT ) -> tuple[list[TrackMinimalDeezer], list[TrackMinimalDeezer]] | None: artist = self.client.get_artist(artist_id) # TODO handle HTTP errors if not artist: @@ -176,6 +177,7 @@ def search_exact_track( i = err_json["code"] # type: ignore if int(i) == 4: time.sleep(5) + logging.warning("Search RateLimit %s - %s", artist_name, track_title) return self.search_exact_track(artist_name, album_title, track_title) else: raise err diff --git a/src/pyramid/connector/discord/bot_cmd.py b/src/pyramid/connector/discord/bot_cmd.py index c3d5cb4..21a7ef2 100644 --- a/src/pyramid/connector/discord/bot_cmd.py +++ b/src/pyramid/connector/discord/bot_cmd.py @@ -9,6 +9,7 @@ from connector.discord.guild_cmd import GuildCmd from data.environment import Environment from data.functional.application_info import ApplicationInfo +from tools.message_sender_queued import MessageSenderQueued from tools.message_sender import MessageSender @@ -114,91 +115,111 @@ async def cmd_help(ctx: Interaction): async def cmd_play(ctx: Interaction, input: str): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.play(ctx, input) + await guild_cmd.play(ms, ctx, input) @bot.tree.command(name="play_next", description="Play a single track next to the current") async def cmd_play_next(ctx: Interaction, input: str): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.play(ctx, input, at_end=False) + await guild_cmd.play(ms, ctx, input, at_end=False) @bot.tree.command(name="pause", description="Pause music") async def cmd_pause(ctx: Interaction): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.pause(ctx) + await guild_cmd.pause(ms, ctx) @bot.tree.command(name="resume", description="Resume music") async def cmd_resume(ctx: Interaction): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.resume(ctx) + await guild_cmd.resume(ms, ctx) @bot.tree.command(name="stop", description="Stop music and exit channel") async def cmd_stop(ctx: Interaction): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.stop(ctx) + await guild_cmd.stop(ms, ctx) @bot.tree.command(name="next", description="Next track") async def cmd_next(ctx: Interaction): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.next(ctx) + await guild_cmd.next(ms, ctx) @bot.tree.command(name="shuffle", description="Randomize the queue") async def cmd_shuffle(ctx: Interaction): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.suffle(ctx) + await guild_cmd.suffle(ms, ctx) @bot.tree.command(name="remove", description="Remove an element in the queue") async def cmd_remove(ctx: Interaction, number_in_queue: int): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.remove(ctx, number_in_queue) + await guild_cmd.remove(ms, ctx, number_in_queue) @bot.tree.command(name="goto", description="Go to an element in the queue") async def cmd_goto(ctx: Interaction, number_in_queue: int): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.goto(ctx, number_in_queue) + await guild_cmd.goto(ms, ctx, number_in_queue) @bot.tree.command(name="queue", description="List the track queue") async def cmd_queue(ctx: Interaction): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.queue_list(ctx) + await guild_cmd.queue_list(ms, ctx) @bot.tree.command( name="search", @@ -207,10 +228,12 @@ async def cmd_queue(ctx: Interaction): async def cmd_search(ctx: Interaction, input: str, engine: str | None): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.search(ctx, input, engine) + await guild_cmd.search(ms, ctx, input, engine) @bot.tree.command( name="play_multiple", description="Plays the first 10 songs of the search" @@ -218,41 +241,44 @@ async def cmd_search(ctx: Interaction, input: str, engine: str | None): async def cmd_play_multiple(ctx: Interaction, input: str): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.play_multiple(ctx, input) + await guild_cmd.play_multiple(ms, ctx, input) @bot.tree.command(name="play_url", description="Plays track, artist, album or playlist by URL") async def cmd_play_url(ctx: Interaction, url: str): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.play_url(ctx, url) + await guild_cmd.play_url(ms, ctx, url) @bot.tree.command(name="play_url_next", description="Plays track, artist, album or playlist by URL next to the current") async def cmd_play_url_next(ctx: Interaction, url: str): if (await self.__use_on_guild_only(ctx)) is False: return + ms = MessageSenderQueued(ctx) + await ms.waiting() guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.play_url(ctx, url, at_end=False) + await guild_cmd.play_url(ms, ctx, url, at_end=False) @bot.tree.command(name="spam", description="Test spam") async def cmd_spam(ctx: Interaction): - ms = MessageSender(ctx) + ms = MessageSenderQueued(ctx) + await ms.waiting() for i in range(100): await ms.add_message(f"Spam n°{i}") await ctx.response.send_message("Spam ended") - # @bot.command() - # async def ignore_none_slash_cmd(): - # pass - async def __use_on_guild_only(self, ctx: Interaction) -> bool: if ctx.guild is None: await ctx.response.send_message("You can use this command only on a guild") diff --git a/src/pyramid/tools/logs_handler.py b/src/pyramid/tools/logs_handler.py index c4ef59a..a27e09f 100644 --- a/src/pyramid/tools/logs_handler.py +++ b/src/pyramid/tools/logs_handler.py @@ -79,7 +79,7 @@ def __handle_unhandled_exception(self, exc_type, exc_value, exc_traceback): def set_log_level(self, mode: Environment): if mode == Environment.PRODUCTION: self.logger.setLevel("INFO") - coloredlogs.set_level("INFO") else: self.logger.setLevel("DEBUG") - coloredlogs.set_level("DEBUG") + # coloredlogs.set_level("DEBUG") + coloredlogs.set_level("INFO") From 03ce3122585829cc3bcb2602b4731bf88cc43a65 Mon Sep 17 00:00:00 2001 From: Tristiisch Date: Wed, 8 Nov 2023 03:52:05 +0100 Subject: [PATCH 3/7] discord on own thread, uptime in $$about --- src/pyramid/connector/discord/bot.py | 4 +++- src/pyramid/connector/discord/bot_cmd.py | 10 +++++++++- src/pyramid/data/functional/application_info.py | 2 +- src/pyramid/data/functional/main.py | 10 +++++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/pyramid/connector/discord/bot.py b/src/pyramid/connector/discord/bot.py index 946440f..91b0764 100644 --- a/src/pyramid/connector/discord/bot.py +++ b/src/pyramid/connector/discord/bot.py @@ -1,4 +1,5 @@ import logging +import time import traceback from logging import Logger from typing import Dict @@ -45,6 +46,7 @@ def __init__( "deezer": DeezerSearch(), } ) + self.__started = time.time() intents = discord.Intents.default() # intents.members = True @@ -59,7 +61,7 @@ def create(self): self.__logger.info("Discord bot creating with discord.py v%s ...", discord.__version__) self.listeners = BotListener(self.bot, self.__logger, self.__information) self.cmd = BotCmd( - self.bot, self.__get_guild_cmd, self.__logger, self.__information, self.__environment + self.bot, self.__get_guild_cmd, self.__logger, self.__information, self.__environment, self.__started ) @self.bot.event diff --git a/src/pyramid/connector/discord/bot_cmd.py b/src/pyramid/connector/discord/bot_cmd.py index 21a7ef2..ed0ec23 100644 --- a/src/pyramid/connector/discord/bot_cmd.py +++ b/src/pyramid/connector/discord/bot_cmd.py @@ -1,5 +1,6 @@ import math from logging import Logger +import time from typing import Callable, List from discord import AppInfo, ClientUser, Color, Embed, Guild, Interaction @@ -10,7 +11,7 @@ from data.environment import Environment from data.functional.application_info import ApplicationInfo from tools.message_sender_queued import MessageSenderQueued -from tools.message_sender import MessageSender +import tools.utils class BotCmd: @@ -21,12 +22,14 @@ def __init__( logger: Logger, info: ApplicationInfo, environment: Environment, + started: float, ): self.__bot = bot self.__get_guild_cmd = get_guild_cmd self.__logger = logger self.__info = info self.__environment = environment + self.__started = started def register(self): bot = self.__bot @@ -81,6 +84,11 @@ async def cmd_about(ctx: Interaction): value=self.__environment.name.capitalize(), inline=True, ) + embed.add_field( + name="Uptime", + value=tools.utils.time_to_duration(int(round(time.time() - self.__started))), + inline=True, + ) await ctx.response.send_message(embed=embed) diff --git a/src/pyramid/data/functional/application_info.py b/src/pyramid/data/functional/application_info.py index 77795d9..068416a 100644 --- a/src/pyramid/data/functional/application_info.py +++ b/src/pyramid/data/functional/application_info.py @@ -9,7 +9,7 @@ class ApplicationInfo: def __init__(self): self.name = "pyramid" self.os = get_os().lower() - self.version = "0.1.7" + self.version = "0.1.8" self.git_info = GitInfo() def load_git_info(self): diff --git a/src/pyramid/data/functional/main.py b/src/pyramid/data/functional/main.py index 1572284..ed8a78b 100644 --- a/src/pyramid/data/functional/main.py +++ b/src/pyramid/data/functional/main.py @@ -2,6 +2,7 @@ import logging import sys from datetime import datetime +from threading import Thread import tools.utils as tools from data.functional.application_info import ApplicationInfo @@ -73,8 +74,15 @@ def init(self): ) # Create bot discord_bot.create() + # Connect bot to Discord servers - discord_bot.start() + thread = Thread( + name="Discord", + target=discord_bot.start, + daemon=True, + ) + thread.start() + thread.join() def stop(self): Queue.wait_for_end(5) From 42a5f709b55b110c45f95a0d9c8a00683eb26fa9 Mon Sep 17 00:00:00 2001 From: Tristiisch Date: Fri, 10 Nov 2023 00:22:19 +0100 Subject: [PATCH 4/7] format --- src/pyramid/__main__.py | 1 - .../connector/deezer/downloader_progress_bar.py | 6 +----- src/pyramid/connector/deezer/search.py | 3 ++- src/pyramid/connector/discord/bot.py | 7 ++++++- src/pyramid/connector/discord/bot_cmd.py | 17 ++++++++++++----- src/pyramid/connector/discord/guild_cmd.py | 8 ++++++-- src/pyramid/test_queue.py | 2 ++ src/pyramid/tools/logs_handler.py | 4 ++-- src/pyramid/tools/message_sender.py | 2 +- src/pyramid/tools/message_sender_queued.py | 12 +++++++----- src/pyramid/tools/queue.py | 1 - src/pyramid/tools/utils.py | 1 + 12 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/pyramid/__main__.py b/src/pyramid/__main__.py index 2752d56..1b8e069 100644 --- a/src/pyramid/__main__.py +++ b/src/pyramid/__main__.py @@ -1,4 +1,3 @@ - from data.functional.main import Main from tools.test_dev import TestDev diff --git a/src/pyramid/connector/deezer/downloader_progress_bar.py b/src/pyramid/connector/deezer/downloader_progress_bar.py index 43a7bc0..0469c09 100644 --- a/src/pyramid/connector/deezer/downloader_progress_bar.py +++ b/src/pyramid/connector/deezer/downloader_progress_bar.py @@ -25,11 +25,7 @@ def initialize(self, *args, **kwargs): self.start_time = time.time() self.last_print = self.start_time - logging.info( - "Start download\t%s (%s)", - self.track_title, - self.track_quality - ) + logging.info("Start download\t%s (%s)", self.track_title, self.track_quality) def update(self, *args, **kwargs): super().update(**kwargs) diff --git a/src/pyramid/connector/deezer/search.py b/src/pyramid/connector/deezer/search.py index ca5f6bf..db58ad0 100644 --- a/src/pyramid/connector/deezer/search.py +++ b/src/pyramid/connector/deezer/search.py @@ -13,7 +13,8 @@ from data.track import TrackMinimalDeezer from data.a_search import ASearch -DEFAULT_LIMIT=100 +DEFAULT_LIMIT = 100 + class DeezerSearch(ASearch): def __init__(self): diff --git a/src/pyramid/connector/discord/bot.py b/src/pyramid/connector/discord/bot.py index 91b0764..9a3c4b1 100644 --- a/src/pyramid/connector/discord/bot.py +++ b/src/pyramid/connector/discord/bot.py @@ -61,7 +61,12 @@ def create(self): self.__logger.info("Discord bot creating with discord.py v%s ...", discord.__version__) self.listeners = BotListener(self.bot, self.__logger, self.__information) self.cmd = BotCmd( - self.bot, self.__get_guild_cmd, self.__logger, self.__information, self.__environment, self.__started + self.bot, + self.__get_guild_cmd, + self.__logger, + self.__information, + self.__environment, + self.__started, ) @self.bot.event diff --git a/src/pyramid/connector/discord/bot_cmd.py b/src/pyramid/connector/discord/bot_cmd.py index ed0ec23..b48f6a6 100644 --- a/src/pyramid/connector/discord/bot_cmd.py +++ b/src/pyramid/connector/discord/bot_cmd.py @@ -34,7 +34,9 @@ def __init__( def register(self): bot = self.__bot - @bot.tree.command(name="ping", description="Displays response time between bot and dioscord") + @bot.tree.command( + name="ping", description="Displays response time between bot and dioscord" + ) async def cmd_ping(ctx: Interaction): await ctx.response.send_message(f"Pong ! ({math.trunc(bot.latency * 1000)}ms)") @@ -94,7 +96,7 @@ async def cmd_about(ctx: Interaction): @bot.tree.command(name="help", description="List all commands") async def cmd_help(ctx: Interaction): - all_commands: List[Command] = bot.tree.get_commands() # type: ignore + all_commands: List[Command] = bot.tree.get_commands() # type: ignore commands_dict = {command.name: command.description for command in all_commands} embed_template = Embed(title="List of every commands available", color=Color.gold()) max_embed = 10 @@ -113,7 +115,7 @@ async def cmd_help(ctx: Interaction): # Sending the first embed as a response and subsequent follow-up embeds for i in range(0, len(embeds), max_embed): - embeds_chunk = embeds[i:i + max_embed] + embeds_chunk = embeds[i : i + max_embed] if i == 0: await ctx.response.send_message(embeds=embeds_chunk) else: @@ -256,7 +258,9 @@ async def cmd_play_multiple(ctx: Interaction, input: str): await guild_cmd.play_multiple(ms, ctx, input) - @bot.tree.command(name="play_url", description="Plays track, artist, album or playlist by URL") + @bot.tree.command( + name="play_url", description="Plays track, artist, album or playlist by URL" + ) async def cmd_play_url(ctx: Interaction, url: str): if (await self.__use_on_guild_only(ctx)) is False: return @@ -267,7 +271,10 @@ async def cmd_play_url(ctx: Interaction, url: str): await guild_cmd.play_url(ms, ctx, url) - @bot.tree.command(name="play_url_next", description="Plays track, artist, album or playlist by URL next to the current") + @bot.tree.command( + name="play_url_next", + description="Plays track, artist, album or playlist by URL next to the current", + ) async def cmd_play_url_next(ctx: Interaction, url: str): if (await self.__use_on_guild_only(ctx)) is False: return diff --git a/src/pyramid/connector/discord/guild_cmd.py b/src/pyramid/connector/discord/guild_cmd.py index 965e549..f6ee241 100644 --- a/src/pyramid/connector/discord/guild_cmd.py +++ b/src/pyramid/connector/discord/guild_cmd.py @@ -174,7 +174,9 @@ async def queue_list(self, ms: MessageSender, ctx: Interaction) -> bool: await ms.add_code_message(queue, prefix="Here's the music in the queue :") return True - async def search(self, ms: MessageSender, ctx: Interaction, input: str, engine: str | None) -> bool: + async def search( + self, ms: MessageSender, ctx: Interaction, input: str, engine: str | None + ) -> bool: if engine is None: search_engine = self.data.search_engine else: @@ -226,7 +228,9 @@ async def play_url(self, ms: MessageSender, ctx: Interaction, url: str, at_end=T if isinstance(res, tuple): tracks, tracks_unfindable = res - return await self._execute_play_multiple(ms, voice_channel, tracks, tracks_unfindable, at_end=at_end) + return await self._execute_play_multiple( + ms, voice_channel, tracks, tracks_unfindable, at_end=at_end + ) elif isinstance(res, TrackMinimal): tracks = res return await self._execute_play(ms, voice_channel, tracks, at_end=at_end) diff --git a/src/pyramid/test_queue.py b/src/pyramid/test_queue.py index d4ab822..27a24f8 100644 --- a/src/pyramid/test_queue.py +++ b/src/pyramid/test_queue.py @@ -57,6 +57,7 @@ def pi(n): exit_handler_executed = False + def exit_handler(): print("exit_handler") q.end() @@ -76,6 +77,7 @@ def exit_handler(): global exit_handler_executed exit_handler_executed = True + atexit.register(exit_handler) while not exit_handler_executed: diff --git a/src/pyramid/tools/logs_handler.py b/src/pyramid/tools/logs_handler.py index a27e09f..8cfbfe4 100644 --- a/src/pyramid/tools/logs_handler.py +++ b/src/pyramid/tools/logs_handler.py @@ -50,8 +50,8 @@ def log_to_file_exceptions(self): file_handler = logging.handlers.RotatingFileHandler( filename=log_filename, encoding="utf-8", - maxBytes=10 * 1024 * 1024, # 10 Mo - backupCount=10 + maxBytes=10 * 1024 * 1024, # 10 Mo + backupCount=10, ) formatter = logging.Formatter(self.__file_format, self.__date, style="{") diff --git a/src/pyramid/tools/message_sender.py b/src/pyramid/tools/message_sender.py index e8d4421..01c09d3 100644 --- a/src/pyramid/tools/message_sender.py +++ b/src/pyramid/tools/message_sender.py @@ -36,7 +36,7 @@ async def add_message( self, content: str = MISSING, callback: Callable | None = None, - # ) -> None: + # ) -> None: ) -> Message: if content != MISSING and content != "": new_content, is_used = tools.substring_with_end_msg( diff --git a/src/pyramid/tools/message_sender_queued.py b/src/pyramid/tools/message_sender_queued.py index 6f95987..02b3f38 100644 --- a/src/pyramid/tools/message_sender_queued.py +++ b/src/pyramid/tools/message_sender_queued.py @@ -35,15 +35,17 @@ async def response_message( content: str = MISSING, ): queue.add( - QueueItem( - "response_message", super().response_message, self.loop, content=content - ) + QueueItem("response_message", super().response_message, self.loop, content=content) ) async def add_code_message(self, content: str, prefix=None, suffix=None): queue.add( QueueItem( - "add_code_message", super().add_code_message, self.loop, content=content, prefix=prefix, suffix=suffix + "add_code_message", + super().add_code_message, + self.loop, + content=content, + prefix=prefix, + suffix=suffix, ) ) - diff --git a/src/pyramid/tools/queue.py b/src/pyramid/tools/queue.py index b1f3564..1b4bce4 100644 --- a/src/pyramid/tools/queue.py +++ b/src/pyramid/tools/queue.py @@ -72,7 +72,6 @@ def worker(q: Deque[QueueItem], thread_id: int, lock: Lock, event: Event): item.func.__module__, item.func.__qualname__, ) - continue except Exception as err: if item.func_error is not None: diff --git a/src/pyramid/tools/utils.py b/src/pyramid/tools/utils.py index 230fd6f..ab9978b 100644 --- a/src/pyramid/tools/utils.py +++ b/src/pyramid/tools/utils.py @@ -209,6 +209,7 @@ def time_to_duration(time_in_sec: int) -> str: return "now" return ", ".join(result) + def get_available_space(path: str = "."): total, used, free = shutil.disk_usage(path) return free From f3a5459503a528b5b45bee36bcfd403c0360c72a Mon Sep 17 00:00:00 2001 From: Tristiisch Date: Fri, 10 Nov 2023 00:25:32 +0100 Subject: [PATCH 5/7] remove downloader cli --- src/pyramid/connector/deezer/downloader.py | 41 ---------------------- 1 file changed, 41 deletions(-) diff --git a/src/pyramid/connector/deezer/downloader.py b/src/pyramid/connector/deezer/downloader.py index 7147e9e..5fc3d64 100644 --- a/src/pyramid/connector/deezer/downloader.py +++ b/src/pyramid/connector/deezer/downloader.py @@ -1,4 +1,3 @@ -import argparse import asyncio import logging import os @@ -52,21 +51,6 @@ async def dl_track_by_id(self, track_id) -> Track | None: track_downloaded = Track(track_info["DATA"], file_path) return track_downloaded - def get_track_by_name(self, name) -> TrackMinimal | None: - tracks_found = self.__deezer_dl_api.search_tracks(name) - if not tracks_found: - return None - track = TrackMinimal(tracks_found[0]) - return track - - async def dl_track_by_name(self, name) -> Track | None: - track: TrackMinimal | None = self.get_track_by_name(name) - if track is None: - return None - - track_downloaded = await self.dl_track_by_id(track.id) - return track_downloaded - def __dl_track(self, func: Callable[..., None], track_info, file_name: str) -> bool: try: func( @@ -92,28 +76,3 @@ def __dl_track(self, func: Callable[..., None], track_info, file_name: str) -> b track = Track(track_info["DATA"], None) logging.warning("Unable to dl track %s", track, exc_info=True) return False - - -async def cli(): - folder_path = "./songs/" - - parser = argparse.ArgumentParser() - parser.add_argument("-t", help="song title", metavar="title") - parser.add_argument("-id", help="id of the track", metavar="id") - parser.add_argument("-arl", help="arl deezer account", metavar="arl") - - args = parser.parse_args() - - if args.arl: - deezer_dl = DeezerDownloader(args.arl, folder_path) - else: - print("Missing Deezer ARL") - exit(1) - - if args.id: - print("Asked Track ", args.t) - await deezer_dl.dl_track_by_id(args.id) - elif args.t: - await deezer_dl.dl_track_by_name(args.t.replace("%20", " ")) - else: - print("Missing Args") From cb5b897a1fde3341db80d1074805d9ceaacd1efe Mon Sep 17 00:00:00 2001 From: Tristiisch Date: Fri, 10 Nov 2023 00:55:52 +0100 Subject: [PATCH 6/7] remove async for msg send in other thread --- src/pyramid/__main__.py | 2 +- src/pyramid/connector/deezer/downloader.py | 2 +- src/pyramid/connector/discord/bot_cmd.py | 8 +- src/pyramid/connector/discord/guild_cmd.py | 90 +++++++++---------- .../connector/discord/guild_cmd_tools.py | 44 ++++----- src/pyramid/connector/discord/guild_queue.py | 2 +- src/pyramid/data/functional/main.py | 7 +- .../functional/messages}/message_sender.py | 0 .../messages}/message_sender_queued.py | 8 +- 9 files changed, 82 insertions(+), 81 deletions(-) rename src/pyramid/{tools => data/functional/messages}/message_sender.py (100%) rename src/pyramid/{tools => data/functional/messages}/message_sender_queued.py (84%) diff --git a/src/pyramid/__main__.py b/src/pyramid/__main__.py index 1b8e069..15fb459 100644 --- a/src/pyramid/__main__.py +++ b/src/pyramid/__main__.py @@ -12,5 +12,5 @@ test_dev = TestDev(main._config, main.logger) # test_dev.test_deezer() -main.init() +main.start() main.stop() diff --git a/src/pyramid/connector/deezer/downloader.py b/src/pyramid/connector/deezer/downloader.py index 5fc3d64..a7cf479 100644 --- a/src/pyramid/connector/deezer/downloader.py +++ b/src/pyramid/connector/deezer/downloader.py @@ -9,7 +9,7 @@ from pydeezer.constants import track_formats from pydeezer.exceptions import LoginError -from data.track import Track, TrackMinimal +from data.track import Track from connector.deezer.downloader_progress_bar import DownloaderProgressBar from urllib3.exceptions import MaxRetryError diff --git a/src/pyramid/connector/discord/bot_cmd.py b/src/pyramid/connector/discord/bot_cmd.py index b48f6a6..5f2cf0e 100644 --- a/src/pyramid/connector/discord/bot_cmd.py +++ b/src/pyramid/connector/discord/bot_cmd.py @@ -10,7 +10,7 @@ from connector.discord.guild_cmd import GuildCmd from data.environment import Environment from data.functional.application_info import ApplicationInfo -from tools.message_sender_queued import MessageSenderQueued +from data.functional.messages.message_sender_queued import MessageSenderQueued import tools.utils @@ -229,7 +229,7 @@ async def cmd_queue(ctx: Interaction): guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.queue_list(ms, ctx) + guild_cmd.queue_list(ms, ctx) @bot.tree.command( name="search", @@ -243,7 +243,7 @@ async def cmd_search(ctx: Interaction, input: str, engine: str | None): guild: Guild = ctx.guild # type: ignore guild_cmd: GuildCmd = self.__get_guild_cmd(guild) - await guild_cmd.search(ms, ctx, input, engine) + guild_cmd.search(ms, ctx, input, engine) @bot.tree.command( name="play_multiple", description="Plays the first 10 songs of the search" @@ -291,7 +291,7 @@ async def cmd_spam(ctx: Interaction): await ms.waiting() for i in range(100): - await ms.add_message(f"Spam n°{i}") + ms.add_message(f"Spam n°{i}") await ctx.response.send_message("Spam ended") async def __use_on_guild_only(self, ctx: Interaction) -> bool: diff --git a/src/pyramid/connector/discord/guild_cmd.py b/src/pyramid/connector/discord/guild_cmd.py index f6ee241..b71376f 100644 --- a/src/pyramid/connector/discord/guild_cmd.py +++ b/src/pyramid/connector/discord/guild_cmd.py @@ -8,7 +8,7 @@ from connector.deezer.downloader import DeezerDownloader from connector.discord.guild_cmd_tools import GuildCmdTools from connector.discord.guild_queue import GuildQueue -from tools.message_sender import MessageSender +from data.functional.messages.message_sender_queued import MessageSenderQueued class GuildCmd(GuildCmdTools): @@ -24,165 +24,165 @@ def __init__( self.data = guild_data self.queue = guild_queue - async def play(self, ms: MessageSender, ctx: Interaction, input: str, at_end=True) -> bool: + async def play(self, ms: MessageSenderQueued, ctx: Interaction, input: str, at_end=True) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel: return False - await ms.response_message(content=f"Searching **{input}**") + ms.response_message(content=f"Searching **{input}**") track: TrackMinimal | None = self.data.search_engine.search_track(input) if not track: - await ms.response_message(content=f"**{input}** not found.") + ms.response_message(content=f"**{input}** not found.") return False return await self._execute_play(ms, voice_channel, track, at_end=at_end) - async def stop(self, ms: MessageSender, ctx: Interaction) -> bool: + async def stop(self, ms: MessageSenderQueued, ctx: Interaction) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False self.data.track_list.clear() if await self.queue.exit() is False: - await ms.response_message(content="The bot does not currently play music") + ms.response_message(content="The bot does not currently play music") return False - await ms.response_message(content="Music stop") + ms.response_message(content="Music stop") return True - async def pause(self, ms: MessageSender, ctx: Interaction) -> bool: + async def pause(self, ms: MessageSenderQueued, ctx: Interaction) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False if self.queue.pause() is False: - await ms.response_message(content="The bot does not currently play music") + ms.response_message(content="The bot does not currently play music") return False - await ms.response_message(content="Music paused") + ms.response_message(content="Music paused") return True - async def resume(self, ms: MessageSender, ctx: Interaction) -> bool: + async def resume(self, ms: MessageSenderQueued, ctx: Interaction) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False if self.queue.resume() is False: - await ms.response_message(content="The bot is not currently paused") + ms.response_message(content="The bot is not currently paused") return False - await ms.response_message(content="Music resume") + ms.response_message(content="Music resume") return True - async def next(self, ms: MessageSender, ctx: Interaction) -> bool: + async def next(self, ms: MessageSenderQueued, ctx: Interaction) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False if self.queue.has_next() is False: if self.queue.stop() is False: - await ms.response_message(content="The bot does not currently play music") + ms.response_message(content="The bot does not currently play music") return False else: - await ms.response_message(content="The bot didn't have next music") + ms.response_message(content="The bot didn't have next music") return True await self.queue.goto_channel(voice_channel) if self.queue.next() is False: - await ms.response_message(content="Unable to play next music") + ms.response_message(content="Unable to play next music") return False - await ms.response_message(content="Skip musique") + ms.response_message(content="Skip musique") return True - async def suffle(self, ms: MessageSender, ctx: Interaction): + async def suffle(self, ms: MessageSenderQueued, ctx: Interaction): voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False if not self.queue.shuffle(): - await ms.response_message(content="No need to shuffle the queue.") + ms.response_message(content="No need to shuffle the queue.") return False - await ms.response_message(content="The queue has been shuffled.") + ms.response_message(content="The queue has been shuffled.") return True - async def remove(self, ms: MessageSender, ctx: Interaction, number_in_queue: int): + async def remove(self, ms: MessageSenderQueued, ctx: Interaction, number_in_queue: int): voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False if number_in_queue <= 0: - await ms.response_message( + ms.response_message( content=f"Unable to remove element with the number {number_in_queue} in the queue" ) return False if number_in_queue == 1: - await ms.response_message( + ms.response_message( content="Unable to remove the current track from the queue. Use `next` instead" ) return False track_deleted = self.queue.remove(number_in_queue - 1) if track_deleted is None: - await ms.response_message( + ms.response_message( content=f"There is no element with the number {number_in_queue} in the queue" ) return False - await ms.response_message( + ms.response_message( content=f"**{track_deleted.get_full_name()}** has been removed from queue" ) return True - async def goto(self, ms: MessageSender, ctx: Interaction, number_in_queue: int): + async def goto(self, ms: MessageSenderQueued, ctx: Interaction, number_in_queue: int): voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel or not await self._verify_bot_channel(ms, voice_channel): return False if number_in_queue <= 0: - await ms.response_message( + ms.response_message( content=f"Unable to go to element with number {number_in_queue} in the queue" ) return False if number_in_queue == 1: - await ms.response_message( + ms.response_message( content="Unable to remove the current track from the queue. Use `next` instead" ) return False tracks_removed = self.queue.goto(number_in_queue - 1) if tracks_removed <= 0: - await ms.response_message( + ms.response_message( content=f"There is no element with the number {number_in_queue} in the queue" ) return False # +1 for current track - await ms.response_message(content=f"f{tracks_removed + 1} tracks has been skipped") + ms.response_message(content=f"f{tracks_removed + 1} tracks has been skipped") return True - async def queue_list(self, ms: MessageSender, ctx: Interaction) -> bool: + def queue_list(self, ms: MessageSenderQueued, ctx: Interaction) -> bool: queue: str | None = self.queue.queue_list() if queue is None: - await ms.response_message(content="Queue is empty") + ms.response_message(content="Queue is empty") return False - await ms.add_code_message(queue, prefix="Here's the music in the queue :") + ms.add_code_message(queue, prefix="Here's the music in the queue :") return True - async def search( - self, ms: MessageSender, ctx: Interaction, input: str, engine: str | None + def search( + self, ms: MessageSenderQueued, ctx: Interaction, input: str, engine: str | None ) -> bool: if engine is None: search_engine = self.data.search_engine else: test_value = self.data.search_engines.get(engine.lower()) if not test_value: - await ms.response_message(content=f"Search engine **{engine}** not found.") + ms.response_message(content=f"Search engine **{engine}** not found.") return False else: search_engine = test_value @@ -190,40 +190,40 @@ async def search( result: list[TrackMinimal] | None = search_engine.search_tracks(input) if not result: - await ms.response_message(content=f"**{input}** not found.") + ms.response_message(content=f"**{input}** not found.") return False hsa = utils_list_track.to_str.tracks(result) - await ms.add_code_message(hsa, prefix="Here are the results of your search :") + ms.add_code_message(hsa, prefix="Here are the results of your search :") return True - async def play_multiple(self, ms: MessageSender, ctx: Interaction, input: str) -> bool: + async def play_multiple(self, ms: MessageSenderQueued, ctx: Interaction, input: str) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel: return False - await ms.response_message(content=f"Searching **{input}** ...") + ms.response_message(content=f"Searching **{input}** ...") tracks: list[TrackMinimal] | None = self.data.search_engine.search_tracks(input) if not tracks: - await ms.response_message(content=f"**{input}** not found.") + ms.response_message(content=f"**{input}** not found.") return False return await self._execute_play_multiple(ms, voice_channel, tracks) - async def play_url(self, ms: MessageSender, ctx: Interaction, url: str, at_end=True) -> bool: + async def play_url(self, ms: MessageSenderQueued, ctx: Interaction, url: str, at_end=True) -> bool: voice_channel: VoiceChannel | None = await self._verify_voice_channel(ms, ctx.user) if not voice_channel: return False - await ms.response_message(content=f"Searching **{url}** ...") + ms.response_message(content=f"Searching **{url}** ...") # ctx.client.loop res: ( tuple[list[TrackMinimal], list[TrackMinimal]] | TrackMinimal | None ) = await self.data.search_engine.get_by_url(url) if not res: - await ms.response_message(content=f"**{url}** not found.") + ms.response_message(content=f"**{url}** not found.") return False if isinstance(res, tuple): diff --git a/src/pyramid/connector/discord/guild_cmd_tools.py b/src/pyramid/connector/discord/guild_cmd_tools.py index d750f0d..05c9d78 100644 --- a/src/pyramid/connector/discord/guild_cmd_tools.py +++ b/src/pyramid/connector/discord/guild_cmd_tools.py @@ -5,7 +5,7 @@ from data.tracklist import TrackList from connector.deezer.downloader import DeezerDownloader from connector.discord.guild_queue import GuildQueue -from tools.message_sender import MessageSender +from data.functional.messages.message_sender_queued import MessageSenderQueued class GuildCmdTools: @@ -20,7 +20,7 @@ def __init__( self.queue = guild_queue async def _verify_voice_channel( - self, ms: MessageSender, user: User | Member + self, ms: MessageSenderQueued, user: User | Member ) -> VoiceChannel | None: if not isinstance(user, Member): raise Exception("Can be only used by member (user in guild)") @@ -29,7 +29,7 @@ async def _verify_voice_channel( # only play music if user is in a voice channel if member.voice is None: - await ms.response_message(content="You're not in a channel.") + ms.response_message(content="You're not in a channel.") return None voice_state: VoiceState = member.voice @@ -42,25 +42,25 @@ async def _verify_voice_channel( bot: Member = self.data.guild.me permissions = voice_channel.permissions_for(bot) if not permissions.connect: - await ms.response_message(content=f"I can't go to {voice_channel.mention}") + ms.response_message(content=f"I can't go to {voice_channel.mention}") return None if not permissions.speak: - await ms.add_message(content=f"Warning ! I can't speak in {voice_channel.mention}") + ms.add_message(content=f"Warning ! I can't speak in {voice_channel.mention}") return voice_channel - async def _verify_bot_channel(self, ms: MessageSender, channel: VoiceChannel) -> bool: + async def _verify_bot_channel(self, ms: MessageSenderQueued, channel: VoiceChannel) -> bool: vc: VoiceClient = self.data.voice_client if vc.channel.id != channel.id: - await ms.response_message(content="You're not in the bot channel.") + ms.response_message(content="You're not in the bot channel.") return False return True async def _execute_play_multiple( self, - ms: MessageSender, + ms: MessageSenderQueued, voice_channel: VoiceChannel, tracks: list[TrackMinimal], tracks_unfindable: list[TrackMinimal] | None = None, @@ -79,21 +79,21 @@ async def _execute_play_multiple( if len(track_unvailable_names) != 0: out = "\n* ".join(track_unvailable_names) - await ms.add_message( + ms.add_message( content=f"These tracks are currently unavailable (restricted in certain regions or removed):\n* {out}" ) if len(tracks_unfindable_names) != 0: out = "\n* ".join(tracks_unfindable_names) - await ms.add_message(content=f"Can't find the audio for this track:\n* {out}") + ms.add_message(content=f"Can't find the audio for this track:\n* {out}") length = len(tracks) - await ms.response_message(content=f"Downloading ... 0/{length}") + ms.response_message(content=f"Downloading ... 0/{length}") cant_dl = 0 for i, track in enumerate(tracks): track_downloaded: Track | None = await self.deezer_dl.dl_track_by_id(track.id) if not track_downloaded: - await ms.add_message(content=f"**{track.get_full_name()}** can't be downloaded.") + ms.add_message(content=f"**{track.get_full_name()}** can't be downloaded.") cant_dl += 1 continue if ( @@ -101,12 +101,12 @@ async def _execute_play_multiple( and not tl.add_track(track_downloaded) or not tl.add_track_after(track_downloaded) ): - await ms.add_message( + ms.add_message( content=f"**{track.get_full_name()}** can't be add to the queue." ) cant_dl += 1 continue - await ms.response_message( + ms.response_message( content=f"Downloading ... {i + 1 - cant_dl}/{length - cant_dl}" ) if i == 0: @@ -114,24 +114,24 @@ async def _execute_play_multiple( await self.queue.play(ms) if length == cant_dl: - await ms.response_message(content="None of the music could be downloaded") + ms.response_message(content="None of the music could be downloaded") return False await self.queue.goto_channel(voice_channel) if await self.queue.play(ms) is False: - await ms.response_message(content=f"**{length}** tracks have been added to the queue") + ms.response_message(content=f"**{length}** tracks have been added to the queue") return True async def _execute_play( - self, ms: MessageSender, voice_channel: VoiceChannel, track: TrackMinimal, at_end=True + self, ms: MessageSenderQueued, voice_channel: VoiceChannel, track: TrackMinimal, at_end=True ) -> bool: tl: TrackList = self.data.track_list - await ms.response_message(content=f"**{track.get_full_name()}** found ! Downloading ...") + ms.response_message(content=f"**{track.get_full_name()}** found ! Downloading ...") track_downloaded: Track | None = await self.deezer_dl.dl_track_by_id(track.id) if not track_downloaded: - await ms.response_message(content=f"**{track.get_full_name()}** can't be downloaded.") + ms.response_message(content=f"**{track.get_full_name()}** can't be downloaded.") return False if ( @@ -139,12 +139,12 @@ async def _execute_play( and not tl.add_track(track_downloaded) or not tl.add_track_after(track_downloaded) ): - await ms.add_message(content=f"**{track.get_full_name()}** can't be add to the queue.") + ms.add_message(content=f"**{track.get_full_name()}** can't be add to the queue.") return False await self.queue.goto_channel(voice_channel) if await self.queue.play(ms) is False: - await ms.response_message(content=f"**{track.get_full_name()}** is added to the queue") + ms.response_message(content=f"**{track.get_full_name()}** is added to the queue") else: - await ms.response_message(content=f"Playing **{track.get_full_name()}**") + ms.response_message(content=f"Playing **{track.get_full_name()}**") return True diff --git a/src/pyramid/connector/discord/guild_queue.py b/src/pyramid/connector/discord/guild_queue.py index 72b54f4..d79b0fc 100644 --- a/src/pyramid/connector/discord/guild_queue.py +++ b/src/pyramid/connector/discord/guild_queue.py @@ -8,7 +8,7 @@ from data.tracklist import TrackList from data.guild_data import GuildData from connector.discord.music_player_interface import MusicPlayerInterface -from tools.message_sender import MessageSender +from data.functional.messages.message_sender import MessageSender class GuildQueue: diff --git a/src/pyramid/data/functional/main.py b/src/pyramid/data/functional/main.py index ed8a78b..20ebf5a 100644 --- a/src/pyramid/data/functional/main.py +++ b/src/pyramid/data/functional/main.py @@ -64,7 +64,7 @@ def clean_data(self): # Songs folder clear tools.clear_directory(self._config.deezer_folder) - def init(self): + def start(self): # Create Deezer player instance deezer_dl = DeezerDownloader(self._config.deezer_arl, self._config.deezer_folder) @@ -78,11 +78,12 @@ def init(self): # Connect bot to Discord servers thread = Thread( name="Discord", - target=discord_bot.start, - daemon=True, + target=discord_bot.start ) thread.start() thread.join() def stop(self): + logging.info("Wait for background tasks to stop") Queue.wait_for_end(5) + logging.info("Bye bye") diff --git a/src/pyramid/tools/message_sender.py b/src/pyramid/data/functional/messages/message_sender.py similarity index 100% rename from src/pyramid/tools/message_sender.py rename to src/pyramid/data/functional/messages/message_sender.py diff --git a/src/pyramid/tools/message_sender_queued.py b/src/pyramid/data/functional/messages/message_sender_queued.py similarity index 84% rename from src/pyramid/tools/message_sender_queued.py rename to src/pyramid/data/functional/messages/message_sender_queued.py index 02b3f38..00d535e 100644 --- a/src/pyramid/tools/message_sender_queued.py +++ b/src/pyramid/data/functional/messages/message_sender_queued.py @@ -1,8 +1,8 @@ from typing import Callable -from tools.message_sender import MessageSender from discord import Interaction from discord.utils import MISSING +from data.functional.messages.message_sender import MessageSender from tools.queue import Queue, QueueItem MAX_MSG_LENGTH = 2000 @@ -19,7 +19,7 @@ def __init__(self, ctx: Interaction): async def waiting(self): await super().response_message("Waiting for result ...") - async def add_message( + def add_message( self, content: str = MISSING, callback: Callable | None = None, @@ -30,7 +30,7 @@ async def add_message( ) ) - async def response_message( + def response_message( self, content: str = MISSING, ): @@ -38,7 +38,7 @@ async def response_message( QueueItem("response_message", super().response_message, self.loop, content=content) ) - async def add_code_message(self, content: str, prefix=None, suffix=None): + def add_code_message(self, content: str, prefix=None, suffix=None): queue.add( QueueItem( "add_code_message", From 51c1cb6d7dc0c62d719e49cfce10317aab16055c Mon Sep 17 00:00:00 2001 From: Tristiisch Date: Fri, 10 Nov 2023 01:30:46 +0100 Subject: [PATCH 7/7] fix duplicate addition in queue --- src/pyramid/connector/discord/guild_cmd_tools.py | 8 ++++---- src/pyramid/data/functional/application_info.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pyramid/connector/discord/guild_cmd_tools.py b/src/pyramid/connector/discord/guild_cmd_tools.py index 05c9d78..99ef77b 100644 --- a/src/pyramid/connector/discord/guild_cmd_tools.py +++ b/src/pyramid/connector/discord/guild_cmd_tools.py @@ -98,8 +98,8 @@ async def _execute_play_multiple( continue if ( at_end is True - and not tl.add_track(track_downloaded) - or not tl.add_track_after(track_downloaded) + and not (tl.add_track(track_downloaded) + or tl.add_track_after(track_downloaded)) ): ms.add_message( content=f"**{track.get_full_name()}** can't be add to the queue." @@ -136,8 +136,8 @@ async def _execute_play( if ( at_end is True - and not tl.add_track(track_downloaded) - or not tl.add_track_after(track_downloaded) + and not (tl.add_track(track_downloaded) + or tl.add_track_after(track_downloaded)) ): ms.add_message(content=f"**{track.get_full_name()}** can't be add to the queue.") return False diff --git a/src/pyramid/data/functional/application_info.py b/src/pyramid/data/functional/application_info.py index 068416a..3f19f5f 100644 --- a/src/pyramid/data/functional/application_info.py +++ b/src/pyramid/data/functional/application_info.py @@ -9,7 +9,7 @@ class ApplicationInfo: def __init__(self): self.name = "pyramid" self.os = get_os().lower() - self.version = "0.1.8" + self.version = "0.1.9" self.git_info = GitInfo() def load_git_info(self):