From 9fb091e9e1fc19a29e79ebdae6d72df302e727e1 Mon Sep 17 00:00:00 2001 From: profesaurus <6362518+profesaurus@users.noreply.github.com> Date: Wed, 26 May 2021 09:28:57 -0700 Subject: [PATCH 1/5] Added the ability to play a playlist with a new "playlist" command. Ex. ?playlist MyPlaylistName --- PlexBot/bot.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/PlexBot/bot.py b/PlexBot/bot.py index 9a47547..ebefde4 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -11,6 +11,7 @@ from discord import FFmpegPCMAudio from discord.ext import commands from discord.ext.commands import command from plexapi.exceptions import Unauthorized +from plexapi.exceptions import NotFound from plexapi.server import PlexServer from .exceptions import MediaNotFoundError @@ -236,6 +237,24 @@ class Plex(commands.Cog): return results[0] except IndexError: raise MediaNotFoundError("Album cannot be found") + + def _search_playlists(self, title: str): + """Search the Plex music db for playlist + + Args: + title: str title of playlist to search for + + Returns: + plexapi.playlist pointing to best matching title + + Raises: + MediaNotFoundError: Title of playlist can't be found in plex db + """ + + try: + return self.pms.playlist(title) + except NotFound: + raise MediaNotFoundError("Playlist cannot be found") async def _play(self): """Heavy lifting of playing songs @@ -400,6 +419,42 @@ class Plex(commands.Cog): bot_log.debug("Built embed for album - %s", album.title) return embed, art_file + + @staticmethod + def _build_embed_playlist(self, playlist): + """Creates a pretty embed card for playlists + + Builds a helpful status embed with the following info: + playlist art. All pertitent information + is grabbed dynamically from the Plex db. + + Args: + playlist: plexapi.playlist object of playlist + + Returns: + embed: discord.embed fully constructed payload. + thumb_art: io.BytesIO of playlist thumbnail img. + + Raises: + None + """ + # Grab the relevant thumbnail + img_stream = urlopen(self.pms.url(playlist.composite, True)) + img = io.BytesIO(img_stream.read()) + + # Attach to discord embed + art_file = discord.File(img, filename="image0.png") + title = "Added playlist to queue" + descrip = f"{playlist.title}" + + embed = discord.Embed( + title=title, description=descrip, colour=discord.Color.red() + ) + embed.set_author(name="Plex") + embed.set_thumbnail(url="attachment://image0.png") + bot_log.debug("Built embed for playlist - %s", playlist.title) + + return embed, art_file async def _validate(self, ctx): """Ensures user is in a vc @@ -506,6 +561,47 @@ class Plex(commands.Cog): for track in album.tracks(): await self.play_queue.put(track) + @command() + async def playlist(self, ctx, *args): + """User command to play playlist + + Searchs plex db and either, initiates playback, or + adds to queue. Handles invalid usage from the user. + + Args: + ctx: discord.ext.commands.Context message context from command + *args: Title of playlist to play + + Returns: + None + + Raises: + None + """ + # Save the context to use with async callbacks + self.ctx = ctx + title = " ".join(args) + + try: + playlist = self._search_playlists(title) + except MediaNotFoundError: + await ctx.send(f"Can't find playlist: {title}") + bot_log.debug("Failed to queue playlist, can't find - %s", title) + return + + try: + await self._validate(ctx) + except VoiceChannelError: + pass + + bot_log.debug("Added to queue - %s", title) + embed, img = self._build_embed_playlist(self, playlist) + await ctx.send(embed=embed, file=img) + + for item in playlist.items(): + if (item.TYPE == "track"): + await self.play_queue.put(item) + @command() async def stop(self, ctx): """User command to stop playback From 921dfc02b878f74ff6271233ac9351eeb17d7aa4 Mon Sep 17 00:00:00 2001 From: profesaurus <6362518+profesaurus@users.noreply.github.com> Date: Thu, 27 May 2021 08:11:02 -0700 Subject: [PATCH 2/5] Updated README.md to include the playlist command. Added the playlist command to bot.py comments. --- PlexBot/bot.py | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/PlexBot/bot.py b/PlexBot/bot.py index ebefde4..13a8256 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -30,6 +30,7 @@ General: Plex: play - Play a song from the plex server. album - Queue an entire album to play. + playlist - Queue an entire playlist to play. lyrics - Print the lyrics of the song (Requires Genius API) np - Print the current playing song. stop - Halt playback and leave vc. diff --git a/README.md b/README.md index fd502b5..b60501f 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ General: Plex: play - Play a song from the plex server. album - Queue an entire album to play. + playlist - Queue an entire playlist to play. lyrics - Print the lyrics of the song (Requires Genius API) np - Print the current playing song. stop - Halt playback and leave vc. From f4cd67550293cae586014d75cdffd5d6dc9b1787 Mon Sep 17 00:00:00 2001 From: profesaurus <6362518+profesaurus@users.noreply.github.com> Date: Thu, 27 May 2021 14:33:24 -0700 Subject: [PATCH 3/5] Attempting to fix the Codacy Static Code Analysis issues. --- PlexBot/bot.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/PlexBot/bot.py b/PlexBot/bot.py index 13a8256..7e238b1 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -3,6 +3,7 @@ import asyncio import io import logging from urllib.request import urlopen +from urllib.request import Request import discord import lyricsgenius @@ -238,9 +239,10 @@ class Plex(commands.Cog): return results[0] except IndexError: raise MediaNotFoundError("Album cannot be found") - + def _search_playlists(self, title: str): - """Search the Plex music db for playlist + """ + Search the Plex music db for playlist Args: title: str title of playlist to search for @@ -251,7 +253,6 @@ class Plex(commands.Cog): Raises: MediaNotFoundError: Title of playlist can't be found in plex db """ - try: return self.pms.playlist(title) except NotFound: @@ -339,7 +340,8 @@ class Plex(commands.Cog): @staticmethod def _build_embed_track(track, type_="play"): - """Creates a pretty embed card for tracks + """ + Creates a pretty embed card for tracks Builds a helpful status embed with the following info: Status, song title, album, artist and album art. All @@ -357,7 +359,8 @@ class Plex(commands.Cog): ValueError: Unsupported type of embed {type_} """ # Grab the relevant thumbnail - img_stream = urlopen(track.thumbUrl) + req = Request(track.thumbUrl) + img_stream = urlopen(req) img = io.BytesIO(img_stream.read()) # Attach to discord embed @@ -387,7 +390,8 @@ class Plex(commands.Cog): @staticmethod def _build_embed_album(album): - """Creates a pretty embed card for albums + """ + Creates a pretty embed card for albums Builds a helpful status embed with the following info: album, artist, and album art. All pertitent information @@ -404,7 +408,8 @@ class Plex(commands.Cog): None """ # Grab the relevant thumbnail - img_stream = urlopen(album.thumbUrl) + req = Request(album.thumbUrl) + img_stream = urlopen(req) img = io.BytesIO(img_stream.read()) # Attach to discord embed @@ -423,7 +428,8 @@ class Plex(commands.Cog): @staticmethod def _build_embed_playlist(self, playlist): - """Creates a pretty embed card for playlists + """ + Creates a pretty embed card for playlists Builds a helpful status embed with the following info: playlist art. All pertitent information @@ -440,7 +446,8 @@ class Plex(commands.Cog): None """ # Grab the relevant thumbnail - img_stream = urlopen(self.pms.url(playlist.composite, True)) + req = Request(self.pms.url(playlist.composite, True)) + img_stream = urlopen(req) img = io.BytesIO(img_stream.read()) # Attach to discord embed From 1ffa5a72297b37169744c3bc52506d1d70fcabb6 Mon Sep 17 00:00:00 2001 From: profesaurus <6362518+profesaurus@users.noreply.github.com> Date: Thu, 27 May 2021 15:35:34 -0700 Subject: [PATCH 4/5] Attempt to fix additional Codacy Static Code Analysis issues. --- PlexBot/bot.py | 77 +++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/PlexBot/bot.py b/PlexBot/bot.py index 7e238b1..00e0974 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -44,13 +44,15 @@ Plex: class General(commands.Cog): - """General commands + """ + General commands Manage general bot behavior """ def __init__(self, bot): - """Initialize commands + """ + Initialize commands Args: bot: discord.ext.command.Bot, bind for cogs @@ -65,7 +67,8 @@ class General(commands.Cog): @command() async def kill(self, ctx, *args): - """Kill the bot + """ + Kill the bot Args: ctx: discord.ext.commands.Context message context from command @@ -85,7 +88,8 @@ class General(commands.Cog): @command(name="help") async def help(self, ctx): - """Prints command help + """ + Prints command help Args: ctx: discord.ext.commands.Context message context from command @@ -101,7 +105,8 @@ class General(commands.Cog): @command() async def cleanup(self, ctx, limit=250): - """Delete old messages from bot + """ + Delete old messages from bot Args: ctx: discord.ext.commands.Context message context from command @@ -151,7 +156,8 @@ class Plex(commands.Cog): # within the bot. def __init__(self, bot, **kwargs): - """Initializes Plex resources + """ + Initializes Plex resources Connects to Plex library and sets up all asyncronous communications. @@ -205,7 +211,8 @@ class Plex(commands.Cog): self.bot.loop.create_task(self._audio_player_task()) def _search_tracks(self, title: str): - """Search the Plex music db for track + """ + Search the Plex music db for track Args: title: str title of song to search for @@ -223,7 +230,8 @@ class Plex(commands.Cog): raise MediaNotFoundError("Track cannot be found") def _search_albums(self, title: str): - """Search the Plex music db for album + """ + Search the Plex music db for album Args: title: str title of album to search for @@ -259,7 +267,8 @@ class Plex(commands.Cog): raise MediaNotFoundError("Playlist cannot be found") async def _play(self): - """Heavy lifting of playing songs + """ + Heavy lifting of playing songs Grabs the appropiate streaming URL, sends the `now playing` message, and initiates playback in the vc. @@ -287,7 +296,8 @@ class Plex(commands.Cog): self.np_message_id = await self.ctx.send(embed=embed, file=img) async def _audio_player_task(self): - """Coroutine to handle playback and queuing + """ + Coroutine to handle playback and queuing Always-running function awaiting new songs to be added. Auto disconnects from VC if idle for > 15 seconds. @@ -321,7 +331,8 @@ class Plex(commands.Cog): await self.np_message_id.delete() def _toggle_next(self, error=None): - """Callback for vc playback + """ + Callback for vc playback Clears current track, then activates _audio_player_task to play next in queue or disconnect. @@ -359,8 +370,7 @@ class Plex(commands.Cog): ValueError: Unsupported type of embed {type_} """ # Grab the relevant thumbnail - req = Request(track.thumbUrl) - img_stream = urlopen(req) + img_stream = requests.get(track.thumbUrl, stream=True).raw img = io.BytesIO(img_stream.read()) # Attach to discord embed @@ -408,8 +418,7 @@ class Plex(commands.Cog): None """ # Grab the relevant thumbnail - req = Request(album.thumbUrl) - img_stream = urlopen(req) + img_stream = requests.get(album.thumbUrl, stream=True).raw img = io.BytesIO(img_stream.read()) # Attach to discord embed @@ -425,7 +434,7 @@ class Plex(commands.Cog): bot_log.debug("Built embed for album - %s", album.title) return embed, art_file - + @staticmethod def _build_embed_playlist(self, playlist): """ @@ -446,8 +455,7 @@ class Plex(commands.Cog): None """ # Grab the relevant thumbnail - req = Request(self.pms.url(playlist.composite, True)) - img_stream = urlopen(req) + img_stream = requests.get(self.pms.url(playlist.composite, True), stream=True).raw img = io.BytesIO(img_stream.read()) # Attach to discord embed @@ -465,7 +473,8 @@ class Plex(commands.Cog): return embed, art_file async def _validate(self, ctx): - """Ensures user is in a vc + """ + Ensures user is in a vc Args: ctx: discord.ext.commands.Context message context from command @@ -489,7 +498,8 @@ class Plex(commands.Cog): @command() async def play(self, ctx, *args): - """User command to play song + """ + User command to play song Searchs plex db and either, initiates playback, or adds to queue. Handles invalid usage from the user. @@ -531,7 +541,8 @@ class Plex(commands.Cog): @command() async def album(self, ctx, *args): - """User command to play song + """ + User command to play song Searchs plex db and either, initiates playback, or adds to queue. Handles invalid usage from the user. @@ -571,7 +582,8 @@ class Plex(commands.Cog): @command() async def playlist(self, ctx, *args): - """User command to play playlist + """ + User command to play playlist Searchs plex db and either, initiates playback, or adds to queue. Handles invalid usage from the user. @@ -612,7 +624,8 @@ class Plex(commands.Cog): @command() async def stop(self, ctx): - """User command to stop playback + """ + User command to stop playback Stops playback and disconnects from vc. @@ -635,7 +648,8 @@ class Plex(commands.Cog): @command() async def pause(self, ctx): - """User command to pause playback + """ + User command to pause playback Pauses playback, but doesn't reset anything to allow playback resuming. @@ -656,7 +670,8 @@ class Plex(commands.Cog): @command() async def resume(self, ctx): - """User command to resume playback + """ + User command to resume playback Args: ctx: discord.ext.commands.Context message context from command @@ -674,7 +689,8 @@ class Plex(commands.Cog): @command() async def skip(self, ctx): - """User command to skip song in queue + """ + User command to skip song in queue Skips currently playing song. If no other songs in queue, stops playback, otherwise moves to next song. @@ -696,7 +712,8 @@ class Plex(commands.Cog): @command(name="np") async def now_playing(self, ctx): - """User command to get currently playing song. + """ + User command to get currently playing song. Deletes old `now playing` status message, Creates a new one with up to date information. @@ -722,7 +739,8 @@ class Plex(commands.Cog): @command() async def clear(self, ctx): - """User command to clear play queue. + """ + User command to clear play queue. Args: ctx: discord.ext.commands.Context message context from command @@ -739,7 +757,8 @@ class Plex(commands.Cog): @command() async def lyrics(self, ctx): - """User command to get lyrics of a song. + """ + User command to get lyrics of a song. Args: ctx: discord.ext.commands.Context message context from command From eeb430c01606f9cb806a254ba5c0e2436425ae7d Mon Sep 17 00:00:00 2001 From: profesaurus <6362518+profesaurus@users.noreply.github.com> Date: Thu, 27 May 2021 15:39:50 -0700 Subject: [PATCH 5/5] Fixed even more Codacy Static Code Analysis issues. --- PlexBot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexBot/bot.py b/PlexBot/bot.py index 00e0974..9dbb53c 100644 --- a/PlexBot/bot.py +++ b/PlexBot/bot.py @@ -3,7 +3,7 @@ import asyncio import io import logging from urllib.request import urlopen -from urllib.request import Request +import requests import discord import lyricsgenius