Add album playback features

Queue a whole album with one single command.
This commit is contained in:
Joshua Arulsamy 2020-08-13 02:25:15 -06:00
parent 56fd4aa5ab
commit f95d5c1fd2
2 changed files with 141 additions and 31 deletions

View File

@ -12,7 +12,8 @@ from discord.ext.commands import command
from plexapi.exceptions import Unauthorized
from plexapi.server import PlexServer
from .exceptions import TrackNotFoundError
from .exceptions import MediaNotFoundError
from .exceptions import VoiceChannelError
root_log = logging.getLogger()
plex_log = logging.getLogger("Plex")
@ -159,16 +160,34 @@ class Plex(commands.Cog):
title: str title of song to search for
Returns:
plexapi.audio.Track pointing to closest matching title
plexapi.audio.Track pointing to best matching title
Raises:
TrackNotFoundError: Title of track can't be found in plex db.
MediaNotFoundError: Title of track can't be found in plex db
"""
results = self.music.searchTracks(title=title, maxresults=1)
try:
return results[0]
except IndexError:
raise TrackNotFoundError
raise MediaNotFoundError("Track cannot be found")
def _search_albums(self, title: str):
"""Search the Plex music db for album
Args:
title: str title of album to search for
Returns:
plexapi.audio.Album pointing to best matching title
Raises:
MediaNotFoundError: Title of album can't be found in plex db
"""
results = self.music.searchAlbums(title=title, maxresults=1)
try:
return results[0]
except IndexError:
raise MediaNotFoundError("Album cannot be found")
async def _play(self):
"""Heavy lifting of playing songs
@ -195,7 +214,7 @@ class Plex(commands.Cog):
plex_log.debug("%s - URL: %s", self.current_track, track_url)
embed, img = self._build_embed(self.current_track)
embed, img = self._build_embed_track(self.current_track)
self.np_message_id = await self.ctx.send(embed=embed, file=img)
async def _audio_player_task(self):
@ -250,11 +269,11 @@ class Plex(commands.Cog):
self.current_track = None
self.bot.loop.call_soon_threadsafe(self.play_next_event.set)
def _build_embed(self, track, type_="play"):
"""Creates a pretty embed card.
def _build_embed_track(self, track, type_="play"):
"""Creates a pretty embed card for tracks
Builds a helpful status embed with the following info:
Status, song title, album, artistm and album art. All
Status, song title, album, artist and album art. All
pertitent information is grabbed dynamically from the Plex db.
Args:
@ -262,7 +281,7 @@ class Plex(commands.Cog):
type_: Type of card to make (play, queue).
Returns:
embed: discord.embed fully constructed img payload.
embed: discord.embed fully constructed payload.
thumb_art: io.BytesIO of album thumbnail img.
Raises:
@ -293,13 +312,71 @@ class Plex(commands.Cog):
# Point to file attached with ctx object.
embed.set_thumbnail(url="attachment://image0.png")
bot_log.debug("Built embed for %s", track.title)
bot_log.debug("Built embed for track - %s", track.title)
return embed, art_file
def _build_embed_album(self, album):
"""Creates a pretty embed card for albums
Builds a helpful status embed with the following info:
album, artist, and album art. All pertitent information
is grabbed dynamically from the Plex db.
Args:
album: plexapi.audio.Album object of album
Returns:
embed: discord.embed fully constructed payload.
thumb_art: io.BytesIO of album thumbnail img.
Raises:
None
"""
# Grab the relevant thumbnail
img_stream = urlopen(album.thumbUrl)
img = io.BytesIO(img_stream.read())
# Attach to discord embed
art_file = discord.File(img, filename="image0.png")
title = "Added album to queue"
descrip = f"{album.title} - {album.artist().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 album - %s", album.title)
return embed, art_file
async def _validate(self, ctx):
"""Ensures user is in a vc
Args:
ctx: discord.ext.commands.Context message context from command
Returns:
None
Raises:
VoiceChannelError: Author not in voice channel
"""
# Fail if user not in vc
if not ctx.author.voice:
await ctx.send("Join a voice channel first!")
bot_log.debug("Failed to play, requester not in voice channel")
raise VoiceChannelError
# 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.")
@command()
async def play(self, ctx, *args):
"""User command to start playback
"""User command to play song
Searchs plex db and either, initiates playback, or
adds to queue. Handles invalid usage from the user.
@ -316,40 +393,69 @@ class Plex(commands.Cog):
"""
# Save the context to use with async callbacks
self.ctx = ctx
if not args:
await ctx.send(f"Usage: {self.bot_prefix}play TITLE_OF_SONG")
bot_log.debug("Failed to play, invalid usage")
return
title = " ".join(args)
try:
track = self._search_tracks(title)
except TrackNotFoundError:
except MediaNotFoundError:
await ctx.send(f"Can't find song: {title}")
bot_log.debug("Failed to play, can't find song - %s", title)
return
# Fail if user not in vc
if not ctx.author.voice:
await ctx.send("Join a voice channel first!")
bot_log.debug("Failed to play, requester not in voice channel")
return
# 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:
await self._validate(ctx)
except VoiceChannelError:
pass
# Specific add to queue message
if self.voice_channel.is_playing():
bot_log.debug("Added to queue - %s", title)
embed, img = self._build_embed(track, type_="queue")
embed, img = self._build_embed_track(track, type_="queue")
await ctx.send(embed=embed, file=img)
# Add the song to the async queue
await self.play_queue.put(track)
@command()
async def album(self, ctx, *args):
"""User command to play song
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 song to play
Returns:
None
Raises:
None
"""
# Save the context to use with async callbacks
self.ctx = ctx
title = " ".join(args)
try:
album = self._search_albums(title)
except MediaNotFoundError:
await ctx.send(f"Can't find album: {title}")
bot_log.debug("Failed to queue album, 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_album(album)
await ctx.send(embed=embed, file=img)
for track in album.tracks():
await self.play_queue.put(track)
@command()
async def stop(self, ctx):
"""User command to stop playback
@ -451,7 +557,7 @@ class Plex(commands.Cog):
None
"""
if self.current_track:
embed, img = self._build_embed(self.current_track)
embed, img = self._build_embed_track(self.current_track)
bot_log.debug("Now playing")
if self.np_message_id:
await self.np_message_id.delete()

View File

@ -1,2 +1,6 @@
class TrackNotFoundError(Exception):
class MediaNotFoundError(Exception):
pass
class VoiceChannelError(Exception):
pass