From 04da5dfd42ca6f44f4ffb5070e1a3c795e098694 Mon Sep 17 00:00:00 2001 From: Carsten Burgard Date: Fri, 15 Apr 2022 11:53:43 +0200 Subject: [PATCH 1/4] added functionality to inspect current queue and skip multiple tracks at once --- PlexBot/bot.py | 65 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/PlexBot/bot.py b/PlexBot/bot.py index d077981..7c88eaa 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -34,10 +34,11 @@ show_playlists - Query for playlists with a name matching any of the arguments. lyrics - Print the lyrics of the song (Requires Genius API) np - Print the current playing song. + q - Print the current queue (This can take very long!) stop - Halt playback and leave vc. pause - Pause playback. resume - Resume playback. - skip - Skip the current song. + skip - Skip the current song. Give a number as argument to skip more than 1. clear - Clear play queue. [] - Optional args. @@ -203,6 +204,7 @@ def __init__(self, bot, **kwargs): self.voice_channel = None self.current_track = None self.np_message_id = None + self.show_queue_message_ids = [] self.ctx = None # Initialize events @@ -296,8 +298,8 @@ async def _play(self): track_url = self.current_track.getStreamURL() audio_stream = FFmpegPCMAudio(track_url) - while self.voice_channel.is_playing(): - asyncio.sleep(2) + while self.voice_channel and self.voice_channel.is_playing(): + await asyncio.sleep(2) self.voice_channel.play(audio_stream, after=self._toggle_next) @@ -339,7 +341,8 @@ async def _audio_player_task(self): await self._play() await self.play_next_event.wait() - await self.np_message_id.delete() + if self.np_message_id: + await self.np_message_id.delete() def _toggle_next(self, error=None): """ @@ -391,6 +394,8 @@ def _build_embed_track(track, type_="play"): title = f"Now Playing - {track.title}" elif type_ == "queue": title = f"Added to queue - {track.title}" + elif type_ == "queued": + title = f"Next in line - {track.title}" else: raise ValueError(f"Unsupported type of embed {type_}") @@ -739,7 +744,7 @@ async def resume(self, ctx): await ctx.send(":play_pause: Resumed") @command() - async def skip(self, ctx): + async def skip(self, ctx, *args): """ User command to skip song in queue @@ -755,10 +760,16 @@ async def skip(self, ctx): Raises: None """ - bot_log.debug("Skip") + n = 1 + if args: + n = int(args[0]) + bot_log.debug("Skipping "+str(n)) if self.voice_channel: self.voice_channel.stop() bot_log.debug("Skipped") + if n>1: + for i in range(n-1): + await self.play_queue.get() self._toggle_next() @command(name="np") @@ -779,15 +790,51 @@ async def now_playing(self, ctx): None """ if self.current_track: - embed, img = self._build_embed_track(self.current_track) + embed, img = self._build_embed_track(self.current_track,type_="play") bot_log.debug("Now playing") if self.np_message_id: - await self.np_message_id.delete() - bot_log.debug("Deleted old np status") + try: + await self.np_message_id.delete() + bot_log.debug("Deleted old np status") + except discord.errors.NotFound: + pass bot_log.debug("Created np status") self.np_message_id = await ctx.send(embed=embed, file=img) + @command(name="q") + async def show_queue(self, ctx): + """ + User command to print the current queue + + Deletes old `now playing` status message, + Creates a new one with up to date information. + + Args: + ctx: discord.ext.commands.Context message context from command + + Returns: + None + + Raises: + None + """ + bot_log.debug("Deleted old queue messages") + for msg in self.show_queue_message_ids: + await msg.delete() + + # need to do this in order to avoid errors when queue is modified during inspection + elems = [] + for track in self.play_queue._queue: + elems.append(track) + + for track in elems: + embed, img = self._build_embed_track(track,type_="queued") + bot_log.debug("Show queue") + + bot_log.debug("Created queue message") + self.show_queue_message_ids.append(await ctx.send(embed=embed, file=img)) + @command() async def clear(self, ctx): """ From 15b7e0f50e1dc3782b9029815cc53f76e6b23c28 Mon Sep 17 00:00:00 2001 From: Carsten Burgard Date: Fri, 15 Apr 2022 12:42:49 +0200 Subject: [PATCH 2/4] catching a few exceptions and handling them accordingly --- PlexBot/bot.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/PlexBot/bot.py b/PlexBot/bot.py index 7c88eaa..fb11547 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -299,15 +299,25 @@ async def _play(self): audio_stream = FFmpegPCMAudio(track_url) while self.voice_channel and self.voice_channel.is_playing(): + bot_log.debug("waiting for track to finish") await asyncio.sleep(2) + bot_log.debug("track finished") - self.voice_channel.play(audio_stream, after=self._toggle_next) - - plex_log.debug("%s - URL: %s", self.current_track, track_url) - - embed, img = self._build_embed_track(self.current_track) - self.np_message_id = await self.ctx.send(embed=embed, file=img) + if self.voice_channel: + self.voice_channel.play(audio_stream, after=self._toggle_next) + + plex_log.debug("%s - URL: %s", self.current_track, track_url) + + embed, img = self._build_embed_track(self.current_track) + self.np_message_id = await self.ctx.send(embed=embed, file=img) + + async def _play_next(self): + try: + self.current_track = await self.play_queue.get() + except asyncio.exceptions.CancelledError: + bot_log.debug("failed to pop queue") + async def _audio_player_task(self): """ Coroutine to handle playback and queuing @@ -331,13 +341,14 @@ async def _audio_player_task(self): try: # Disconnect after 15 seconds idle async with timeout(15): - self.current_track = await self.play_queue.get() + await self._play_next() except asyncio.TimeoutError: + bot_log("timeout - disconnecting") await self.voice_channel.disconnect() self.voice_channel = None if not self.current_track: - self.current_track = await self.play_queue.get() + await self._play_next() await self._play() await self.play_next_event.wait() @@ -510,8 +521,11 @@ async def _validate(self, ctx): # Connect to voice if not already if not self.voice_channel: - self.voice_channel = await ctx.author.voice.channel.connect() - bot_log.debug("Connected to vc.") + try: + self.voice_channel = await ctx.author.voice.channel.connect() + bot_log.debug("Connected to vc.") + except asyncio.exceptions.TimeoutError: + bot_log.debug("Cannot connect to vc - timeout") @command() async def play(self, ctx, *args): @@ -548,7 +562,7 @@ async def play(self, ctx, *args): pass # Specific add to queue message - if self.voice_channel.is_playing(): + if self.voice_channel and self.voice_channel.is_playing(): bot_log.debug("Added to queue - %s", title) embed, img = self._build_embed_track(track, type_="queue") await ctx.send(embed=embed, file=img) From 069d88fd1e41e56e6a018acf11d9514fcfda887d Mon Sep 17 00:00:00 2001 From: Carsten Burgard Date: Fri, 15 Apr 2022 12:43:55 +0200 Subject: [PATCH 3/4] implemented looping of tracks --- PlexBot/bot.py | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/PlexBot/bot.py b/PlexBot/bot.py index fb11547..94056ee 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -203,6 +203,7 @@ def __init__(self, bot, **kwargs): # Initialize necessary vars self.voice_channel = None self.current_track = None + self.is_looping = False self.np_message_id = None self.show_queue_message_ids = [] self.ctx = None @@ -312,10 +313,13 @@ async def _play(self): self.np_message_id = await self.ctx.send(embed=embed, file=img) async def _play_next(self): - try: - self.current_track = await self.play_queue.get() - except asyncio.exceptions.CancelledError: - bot_log.debug("failed to pop queue") + if self.is_looping: + self.current_track = self.is_looping + else: + try: + self.current_track = await self.play_queue.get() + except asyncio.exceptions.CancelledError: + bot_log.debug("failed to pop queue") async def _audio_player_task(self): @@ -716,6 +720,41 @@ async def stop(self, ctx): bot_log.debug("Stopped") await ctx.send(":stop_button: Stopped") + @command() + async def loop(self, ctx): + """ + User command to activate looping the current track + + Args: + ctx: discord.ext.commands.Context message context from command + + Returns: + None + + Raises: + None + """ + bot_log.debug("Looping "+str(self.current_track)) + self.is_looping = self.current_track + + @command() + async def unloop(self, ctx): + """ + User command to deactivate looping the current track + + Args: + ctx: discord.ext.commands.Context message context from command + + Returns: + None + + Raises: + None + """ + bot_log.debug("Unlooping") + self.is_looping = False + + @command() async def pause(self, ctx): """ From ec03209b2d04e2b943292ff3984f41de538e02c3 Mon Sep 17 00:00:00 2001 From: Carsten Burgard Date: Fri, 15 Apr 2022 15:06:30 +0200 Subject: [PATCH 4/4] added documentation --- PlexBot/bot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PlexBot/bot.py b/PlexBot/bot.py index 94056ee..9ce42c2 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -36,6 +36,8 @@ np - Print the current playing song. q - Print the current queue (This can take very long!) stop - Halt playback and leave vc. + loop - Loop the current song. + unloop - Disable looping. pause - Pause playback. resume - Resume playback. skip - Skip the current song. Give a number as argument to skip more than 1.