diff options
author | Parker <contact@pkrm.dev> | 2024-11-28 00:04:48 -0600 |
---|---|---|
committer | Parker <contact@pkrm.dev> | 2024-11-28 00:04:48 -0600 |
commit | 382c42262954767acc3f5ee3e008e03acbfc4f26 (patch) | |
tree | 1617f4cf15e349d34408f71b07032ea7a6ba6594 /code/utils | |
parent | 5eb665c99b1fcfaf9500daf7811f835d002e078e (diff) |
Overhaul \`play\` command
- Split custom sources into helper functions
- Add proper logging and handling
- Fix LoadError embed messsage
Diffstat (limited to 'code/utils')
-rw-r--r-- | code/utils/command_tree.py | 22 | ||||
-rw-r--r-- | code/utils/source_helpers/apple/album.py | 79 | ||||
-rw-r--r-- | code/utils/source_helpers/apple/playlist.py | 90 | ||||
-rw-r--r-- | code/utils/source_helpers/apple/song.py | 74 | ||||
-rw-r--r-- | code/utils/source_helpers/parse.py | 80 | ||||
-rw-r--r-- | code/utils/source_helpers/spotify/album.py | 73 | ||||
-rw-r--r-- | code/utils/source_helpers/spotify/playlist.py | 73 | ||||
-rw-r--r-- | code/utils/source_helpers/spotify/song.py | 68 |
8 files changed, 544 insertions, 15 deletions
diff --git a/code/utils/command_tree.py b/code/utils/command_tree.py index 3d9214d..7ccb8b1 100644 --- a/code/utils/command_tree.py +++ b/code/utils/command_tree.py @@ -84,31 +84,23 @@ class Tree(app_commands.CommandTree): except discord.errors.InteractionResponded: await interaction.followup.send(embed=embed, ephemeral=True) - # If a Spotify song is linked but cannot be found on a provider (e.g. YouTube) - elif isinstance(error, LoadError): + elif (error, LoadError): embed = discord.Embed( - title="Nothing Found", + title="Load Error", description=( - "Spotify does not allow direct play, meaning songs have to" - " be found on a supported provider. In this case, the song" - " couldn't be found. Please try again with a different" - " song, or try searching for just the name and artist" - " manually rather than sending a link." + "Apple Music and Spotify do not allow direct playing from" + " their websites, and I was unable to load a track on a" + " valid source. Please try again." ), color=BOT_COLOR, ) - embed.set_footer( - text=datetime.datetime.now(datetime.timezone.utc).strftime( - "%Y-%m-%d %H:%M:%S" - ) - + " UTC" - ) + # Only send the error if the interaction is still valid try: await interaction.response.send_message( embed=embed, ephemeral=True ) except discord.errors.InteractionResponded: - await interaction.followup.send(embed=embed, ephemeral=True) + pass else: raise error diff --git a/code/utils/source_helpers/apple/album.py b/code/utils/source_helpers/apple/album.py new file mode 100644 index 0000000..3195e2d --- /dev/null +++ b/code/utils/source_helpers/apple/album.py @@ -0,0 +1,79 @@ +import datetime +import discord +import requests +from typing import Tuple, Optional +from requests.exceptions import JSONDecodeError + +from utils.config import BOT_COLOR, LOG + + +async def load( + headers: dict, + query: str, + user: discord.User, +) -> Tuple[Optional[dict], Optional[discord.Embed]]: + """ + Get the album info from the Apple Music API + """ + album_id = query.split("/album/")[1].split("/")[1] + + try: + # Get the album info + response = requests.get( + f"https://api.music.apple.com/v1/catalog/us/albums/{album_id}", + headers=headers, + ) + + if response.status_code == 404: + embed = discord.Embed( + title="Album Not Found", + description=( + "The album could not be found as the provided link is" + " invalid. Please try again." + ), + color=BOT_COLOR, + ) + return None, embed + + if response.status_code == 401: + LOG.error( + "Could not authorize with Apple Music API. Likely need to" + " restart the bot." + ) + return None, None + + response.raise_for_status() + # Unpack the album info + album = response.json() + name = album["data"][0]["attributes"]["name"] + artist = album["data"][0]["attributes"]["artistName"] + num_tracks = len(album["data"][0]["relationships"]["tracks"]["data"]) + except IndexError: + LOG.error("Failed unpacking Apple Music album info") + return None, None + except (JSONDecodeError, requests.HTTPError): + LOG.error("Failed making request to Apple Music API") + return None, None + + # Extract artwork URL, if available + artwork_url = ( + album["data"][0]["attributes"].get("artwork", {}).get("url", None) + ) + if artwork_url: + artwork_url = artwork_url.replace("{w}x{h}", "300x300") + + embed = discord.Embed( + title="Album Queued", + description=( + f"**{name}** by **{artist}**\n" + f"` {num_tracks} ` tracks\n\n" + f"Queued by: {user.mention}" + ), + color=BOT_COLOR, + ) + embed.set_thumbnail(url=artwork_url) + embed.set_footer( + text=datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + " UTC" + ) + + return album, embed diff --git a/code/utils/source_helpers/apple/playlist.py b/code/utils/source_helpers/apple/playlist.py new file mode 100644 index 0000000..8a27315 --- /dev/null +++ b/code/utils/source_helpers/apple/playlist.py @@ -0,0 +1,90 @@ +import datetime +import discord +import requests +from typing import Tuple, Optional +from requests.exceptions import JSONDecodeError + +from utils.config import BOT_COLOR, LOG + + +async def load( + headers: dict, + query: str, + user: discord.User, +) -> Tuple[Optional[dict], Optional[discord.Embed]]: + """ + Get the playlist info from the Apple Music API + """ + playlist_id = query.split("/playlist/")[1].split("/")[1] + try: + # Get all of the tracks in the playlist (limit at 100) + response = requests.get( + f"https://api.music.apple.com/v1/catalog/us/playlists/{playlist_id}/tracks?limit=100", + headers=headers, + ) + + if response.status_code == 404: + embed = discord.Embed( + title="Playlist Not Found", + description=( + "The playlist could not be found as the provided link is" + " invalid. Please try again." + ), + color=BOT_COLOR, + ) + return None, embed + + if response.status_code == 401: + LOG.error( + "Could not authorize with Apple Music API. Likely need to" + " restart the bot." + ) + return None, None + + response.raise_for_status() + playlist = response.json() + + # Get the general playlist info (name, artwork) + response = requests.get( + f"https://api.music.apple.com/v1/catalog/us/playlists/{playlist_id}", + headers=headers, + ) + + response.raise_for_status() + # Unpack the playlist info + playlist_info = response.json() + name = playlist_info["data"][0]["attributes"]["name"] + num_tracks = len(playlist["data"]) + except IndexError: + LOG.error("Failed unpacking Apple Music playlist info") + return None, None + except (JSONDecodeError, requests.HTTPError): + LOG.error("Failed making request to Apple Music API") + return None, None + + # Extract artwork URL, if available + artwork_url = ( + playlist_info["data"][0]["attributes"] + .get("artwork", {}) + .get("url", None) + ) + if artwork_url: + artwork_url = artwork_url.replace("{w}x{h}", "300x300") + + embed = discord.Embed( + title="Playlist Queued", + description=( + f"**{name}**\n` {num_tracks} ` tracks\n\nQueued by: {user.mention}" + ), + color=BOT_COLOR, + ) + + # Add small alert if the playlist is the max size + if len(playlist["data"]) == 100: + embed.description += ( + "\n\n*This playlist is longer than the 100 song" + " maximum. Only the first 100 songs will be" + " queued.*" + ) + + return playlist, embed diff --git a/code/utils/source_helpers/apple/song.py b/code/utils/source_helpers/apple/song.py new file mode 100644 index 0000000..55db003 --- /dev/null +++ b/code/utils/source_helpers/apple/song.py @@ -0,0 +1,74 @@ +import datetime +import discord +import requests +from typing import Tuple, Optional +from requests.exceptions import JSONDecodeError + +from utils.config import BOT_COLOR, LOG + + +async def load( + headers: dict, + query: str, + user: discord.User, +) -> Tuple[Optional[dict], Optional[discord.Embed]]: + """ + Get the song info from the Apple Music API + """ + song_id = query.split("/album/")[1].split("?i=")[1] + + try: + # Get the song info + response = requests.get( + f"https://api.music.apple.com/v1/catalog/us/songs/{song_id}", + headers=headers, + ) + + if response.status_code == 404: + embed = discord.Embed( + title="Song Not Found", + description=( + "The song could not be found as the provided link is" + " invalid. Please try again." + ), + color=BOT_COLOR, + ) + return None, embed + + if response.status_code == 401: + LOG.error( + "Could not authorize with Apple Music API. Likely need to" + " restart the bot." + ) + return None, None + + response.raise_for_status() + # Unpack the song info + song = response.json() + name = song["data"][0]["attributes"]["name"] + artist = song["data"][0]["attributes"]["artistName"] + except IndexError: + LOG.error("Failed unpacking Apple Music song info") + return None, None + except (JSONDecodeError, requests.HTTPError): + LOG.error("Failed making request to Apple Music API") + return None, None + + # Extract artwork URL, if available + artwork_url = ( + song["data"][0]["attributes"].get("artwork", {}).get("url", None) + ) + if artwork_url: + artwork_url = artwork_url.replace("{w}x{h}", "300x300") + + embed = discord.Embed( + title="Song Queued", + description=f"**{name}** by **{artist}**\n\nQueued by: {user.mention}", + color=BOT_COLOR, + ) + embed.set_thumbnail(url=artwork_url) + embed.set_footer( + text=datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + " UTC" + ) + + return song, embed diff --git a/code/utils/source_helpers/parse.py b/code/utils/source_helpers/parse.py new file mode 100644 index 0000000..8489515 --- /dev/null +++ b/code/utils/source_helpers/parse.py @@ -0,0 +1,80 @@ +import discord + +from utils.source_helpers.apple import ( + album as apple_album, + playlist as apple_playlist, + song as apple_song, +) +from utils.source_helpers.spotify import ( + album as spotify_album, + playlist as spotify_playlist, + song as spotify_song, +) +from utils.custom_sources import AppleSource, SpotifySource + + +async def parse_custom_source( + self, provider: str, query: str, user: discord.User +): + """ + Parse the query and run the appropriate functions to get the results/info + + Return the results and an embed or None, None + """ + load_funcs = { + "apple": { + "album": apple_album.load, + "playlist": apple_playlist.load, + "song": apple_song.load, + }, + "spotify": { + "album": spotify_album.load, + "playlist": spotify_playlist.load, + "song": spotify_song.load, + }, + } + + headers = { + "apple": self.bot.apple_headers, + "spotify": self.bot.spotify_headers, + } + + sources = { + "apple": AppleSource, + "spotify": SpotifySource, + } + + # Catch all songs + if "?i=" in query or "/track/" in query: + song, embed = await load_funcs[provider]["song"]( + headers[provider], query, user + ) + + if song: + results = await sources[provider].load_item(self, user, song) + else: + return None, embed + # Catch all playlists + elif "/playlist/" in query: + playlist, embed = await load_funcs[provider]["playlist"]( + headers[provider], query, user + ) + + if playlist: + results = await sources[provider].load_playlist( + self, user, playlist + ) + else: + return None, embed + # Catch all albums + elif "/album/" in query: + album, embed = await load_funcs[provider]["album"]( + headers[provider], query, user + ) + + if album: + results = await sources[provider].load_album(self, user, album) + else: + return None, embed + + return results, embed diff --git a/code/utils/source_helpers/spotify/album.py b/code/utils/source_helpers/spotify/album.py new file mode 100644 index 0000000..cf527ed --- /dev/null +++ b/code/utils/source_helpers/spotify/album.py @@ -0,0 +1,73 @@ +import datetime +import discord +import requests +from typing import Tuple, Optional +from requests.exceptions import JSONDecodeError + +from utils.config import BOT_COLOR, LOG + + +async def load( + headers: dict, + query: str, + user: discord.User, +) -> Tuple[Optional[dict], Optional[discord.Embed]]: + """ + Get the album info from the Spotify API + """ + album_id = query.split("/album/")[1] + + try: + # Get the album info + response = requests.get( + f"https://api.spotify.com/v1/albums/{album_id}", + headers=headers, + ) + + if response.status_code == 404: + embed = discord.Embed( + title="Album Not Found", + description=( + "The album could not be found as the provided link is" + " invalid. Please try again." + ), + color=BOT_COLOR, + ) + return None, embed + + if response.status_code == 401: + LOG.error( + "Could not authorize with Spotify API. Likely need to" + " restart the bot." + ) + return None, None + + response.raise_for_status() + # Unpack the album info + album = response.json() + name = album["name"] + artist = album["artists"][0]["name"] + num_tracks = len(album["tracks"]["items"]) + artwork_url = album["images"][0]["url"] + except IndexError: + LOG.error("Failed unpacking Spotify album info") + return None, None + except (JSONDecodeError, requests.HTTPError): + LOG.error("Failed making request to Spotify API") + return None, None + + embed = discord.Embed( + title="Album Queued", + description=( + f"**{name}** by **{artist}**\n" + f"` {num_tracks} ` tracks\n\n" + f"Queued by: {user.mention}" + ), + color=BOT_COLOR, + ) + embed.set_thumbnail(url=artwork_url) + embed.set_footer( + text=datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + " UTC" + ) + + return album, embed diff --git a/code/utils/source_helpers/spotify/playlist.py b/code/utils/source_helpers/spotify/playlist.py new file mode 100644 index 0000000..c806e39 --- /dev/null +++ b/code/utils/source_helpers/spotify/playlist.py @@ -0,0 +1,73 @@ +import datetime +import discord +import requests +from typing import Tuple, Optional +from requests.exceptions import JSONDecodeError + +from utils.config import BOT_COLOR, LOG + + +async def load( + headers: dict, + query: str, + user: discord.User, +) -> Tuple[Optional[dict], Optional[discord.Embed]]: + """ + Get the playlist info from the Spotify API + """ + playlist_id = query.split("/playlist/")[1].split("?si=")[0] + + try: + # Get the playlist info + response = requests.get( + f"https://api.spotify.com/v1/playlists/{playlist_id}", + headers=headers, + ) + + if response.status_code == 404: + embed = discord.Embed( + title="Playlist Not Found", + description=( + "The playlist could not be found as the provided link is" + " invalid. Please try again." + ), + color=BOT_COLOR, + ) + return None, embed + + if response.status_code == 401: + LOG.error( + "Could not authorize with Spotify API. Likely need to" + " restart the bot." + ) + return None, None + + response.raise_for_status() + # Unpack the playlist info + playlist = response.json() + name = playlist["name"] + owner = playlist["owner"]["display_name"] + num_tracks = len(playlist["tracks"]["items"]) + artwork_url = playlist["images"][0]["url"] + except IndexError: + LOG.error("Failed unpacking Spotify playlist info") + return None, None + except (JSONDecodeError, requests.HTTPError): + LOG.error("Failed making request to Spotify API") + return None, None + + embed = discord.Embed( + title="Playlist Queued", + description=( + f"**{name}** from **{owner}**\n" + f"` {num_tracks} ` tracks\n\n" + f"Queued by {user.mention}" + ), + color=BOT_COLOR, + ) + embed.set_thumbnail(url=artwork_url) + embed.set_footer( + text=datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + " UTC" + ) + + return playlist, embed diff --git a/code/utils/source_helpers/spotify/song.py b/code/utils/source_helpers/spotify/song.py new file mode 100644 index 0000000..258b652 --- /dev/null +++ b/code/utils/source_helpers/spotify/song.py @@ -0,0 +1,68 @@ +import datetime +import discord +import requests +from typing import Tuple, Optional +from requests.exceptions import JSONDecodeError + +from utils.config import BOT_COLOR, LOG + + +async def load( + headers: dict, + query: str, + user: discord.User, +) -> Tuple[Optional[dict], Optional[discord.Embed]]: + """ + Get the song info from the Spotify API + """ + song_id = query.split("/track/")[1] + + try: + # Get the song info + response = requests.get( + f"https://api.spotify.com/v1/tracks/{song_id}", + headers=headers, + ) + + if response.status_code == 404: + embed = discord.Embed( + title="Song Not Found", + description=( + "The song could not be found as the provided link is" + " invalid. Please try again." + ), + color=BOT_COLOR, + ) + return None, embed + + if response.status_code == 401: + LOG.error( + "Could not authorize with Spotify API. Likely need to" + " restart the bot." + ) + return None, None + + response.raise_for_status() + # Unpack the song info + song = response.json() + name = song["name"] + artist = song["artists"][0]["name"] + artwork_url = song["album"]["images"][0]["url"] + except IndexError: + LOG.error("Failed unpacking Spotify song info") + return None, None + except (JSONDecodeError, requests.HTTPError): + LOG.error("Failed making request to Spotify API") + return None, None + + embed = discord.Embed( + title="Song Queued", + description=f"**{name}** by **{artist}**\n\nQueued by {user.mention}", + color=BOT_COLOR, + ) + embed.set_thumbnail(url=artwork_url) + embed.set_footer( + text=datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + " UTC" + ) + + return song, embed |