2024-06-21 13:08:45 -04:00
import logging
2024-06-20 22:12:52 -04:00
import discord
2024-06-22 08:55:26 -04:00
from discord import app_commands
2024-06-22 14:22:58 -04:00
import spotipy
2024-06-20 22:12:52 -04:00
from spotipy . oauth2 import SpotifyOAuth
import config
2024-06-21 13:08:45 -04:00
# Set up logging
logging . basicConfig ( level = logging . DEBUG )
logger = logging . getLogger ( __name__ )
2024-06-20 22:31:27 -04:00
class SpotifyModule :
2024-06-20 22:12:52 -04:00
def __init__ ( self , bot ) :
self . bot = bot
2024-06-22 14:22:58 -04:00
self . user_sessions = { } # To store user-specific Spotify sessions
self . auth_managers = { } # To store auth managers for each user
self . add_commands ( )
def get_spotify_session ( self , user_id ) :
return self . user_sessions . get ( user_id , None )
def add_commands ( self ) :
@app_commands.command ( name = " login_spotify " , description = " Login to Spotify " )
async def login_spotify ( interaction : discord . Interaction ) :
auth_manager = SpotifyOAuth (
2024-06-20 22:12:52 -04:00
client_id = config . SPOTIPY_CLIENT_ID ,
client_secret = config . SPOTIPY_CLIENT_SECRET ,
redirect_uri = config . SPOTIPY_REDIRECT_URI ,
scope = (
" user-library-read user-read-playback-state "
" user-modify-playback-state user-read-currently-playing "
2024-06-22 08:55:26 -04:00
) ,
2024-06-22 14:22:58 -04:00
cache_path = f " .cache- { interaction . user . id } " # Store tokens per user
)
auth_url = auth_manager . get_authorize_url ( )
self . auth_managers [ interaction . user . id ] = auth_manager
await interaction . response . send_message (
f " Please log in to Spotify: [Login]( { auth_url } ) \n "
" After logging in, please send the `/verify_spotify` command with the URL you were redirected to. "
2024-06-20 22:12:52 -04:00
)
2024-06-22 14:22:58 -04:00
@app_commands.command ( name = " verify_spotify " , description = " Verify Spotify login " )
async def verify_spotify ( interaction : discord . Interaction , callback_url : str ) :
user_id = interaction . user . id
if user_id not in self . auth_managers :
await interaction . response . send_message ( " Please initiate login first using /login_spotify. " )
return
auth_manager = self . auth_managers [ user_id ]
try :
code = auth_manager . parse_response_code ( callback_url )
token_info = auth_manager . get_access_token ( code )
if token_info :
sp = spotipy . Spotify ( auth_manager = auth_manager )
self . user_sessions [ user_id ] = sp
await interaction . response . send_message ( " Logged in to Spotify. You can now use Spotify commands. " )
del self . auth_managers [ user_id ] # Clean up the used auth manager
else :
await interaction . response . send_message ( " Failed to verify Spotify login. Please try again. " )
except Exception as e :
logger . error ( f " Error verifying Spotify login: { e } " , exc_info = True )
await interaction . response . send_message ( f " Failed to verify Spotify login: { e } " )
@app_commands.command ( name = " current_track " , description = " Get the currently playing song " )
2024-06-20 22:12:52 -04:00
async def current_track ( interaction : discord . Interaction ) :
2024-06-20 22:31:27 -04:00
await interaction . response . defer ( )
2024-06-22 14:22:58 -04:00
sp = self . get_spotify_session ( interaction . user . id )
if not sp :
await interaction . followup . send ( " Please log in to Spotify first using /login_spotify. " )
return
2024-06-20 22:31:27 -04:00
try :
2024-06-22 14:22:58 -04:00
current = sp . currently_playing ( )
2024-06-20 22:31:27 -04:00
if current is None or current [ " item " ] is None :
2024-06-20 23:00:20 -04:00
await interaction . followup . send (
2024-06-20 23:50:04 -04:00
embed = discord . Embed (
title = " Current Track " ,
description = " No song is currently playing " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . red ( ) ,
2024-06-20 23:50:04 -04:00
)
2024-06-20 23:00:20 -04:00
)
2024-06-21 13:08:45 -04:00
logger . info ( " No song is currently playing " )
2024-06-20 22:31:27 -04:00
else :
track = current [ " item " ]
2024-06-20 23:00:20 -04:00
artist = " , " . join ( [ a [ " name " ] for a in track [ " artists " ] ] )
2024-06-20 23:50:04 -04:00
embed = discord . Embed (
title = " Current Track " ,
description = f " { track [ ' name ' ] } by { artist } " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . green ( ) ,
2024-06-20 23:50:04 -04:00
)
2024-06-22 14:22:58 -04:00
embed . add_field ( name = " Album " , value = track [ " album " ] [ " name " ] , inline = False )
2024-06-22 08:55:26 -04:00
embed . set_thumbnail ( url = track [ " album " ] [ " images " ] [ 0 ] [ " url " ] )
2024-06-20 23:50:04 -04:00
await interaction . followup . send ( embed = embed )
2024-06-22 14:22:58 -04:00
logger . info ( f " Currently playing: { track [ ' name ' ] } by { artist } " )
2024-06-20 22:31:27 -04:00
except Exception as e :
await interaction . followup . send ( f " An error occurred: { e } " )
2024-06-21 13:08:45 -04:00
logger . error ( f " Error in current_track command: { e } " )
2024-06-20 22:12:52 -04:00
2024-06-22 14:22:58 -04:00
@app_commands.command ( name = " play_track " , description = " Play a track by searching for it " )
2024-06-20 22:12:52 -04:00
async def play_track ( interaction : discord . Interaction , query : str ) :
2024-06-20 22:31:27 -04:00
await interaction . response . defer ( )
2024-06-22 14:22:58 -04:00
sp = self . get_spotify_session ( interaction . user . id )
if not sp :
await interaction . followup . send ( " Please log in to Spotify first using /login_spotify. " )
return
2024-06-20 22:31:27 -04:00
try :
2024-06-22 14:22:58 -04:00
results = sp . search ( q = query , limit = 1 , type = " track " )
2024-06-20 22:31:27 -04:00
if not results [ " tracks " ] [ " items " ] :
2024-06-20 23:50:04 -04:00
await interaction . followup . send (
embed = discord . Embed (
title = " Play Track " ,
description = " No results found " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . red ( ) ,
2024-06-20 23:50:04 -04:00
)
)
2024-06-21 13:08:45 -04:00
logger . info ( f " No results found for query: { query } " )
2024-06-20 22:31:27 -04:00
return
2024-06-20 22:12:52 -04:00
2024-06-20 22:31:27 -04:00
track = results [ " tracks " ] [ " items " ] [ 0 ]
uri = track [ " uri " ]
2024-06-22 14:22:58 -04:00
devices = sp . devices ( )
2024-06-20 22:31:27 -04:00
if not devices [ " devices " ] :
2024-06-20 23:00:20 -04:00
await interaction . followup . send (
2024-06-20 23:50:04 -04:00
embed = discord . Embed (
title = " Play Track " ,
2024-06-22 14:22:58 -04:00
description = " No active devices found. Please open Spotify on a device. " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . red ( ) ,
2024-06-20 23:50:04 -04:00
)
2024-06-20 23:00:20 -04:00
)
2024-06-21 13:08:45 -04:00
logger . info ( " No active devices found for playback " )
2024-06-20 22:31:27 -04:00
return
2024-06-22 14:22:58 -04:00
sp . start_playback ( uris = [ uri ] )
2024-06-20 23:50:04 -04:00
embed = discord . Embed (
title = " Now Playing " ,
2024-06-22 14:22:58 -04:00
description = f " { track [ ' name ' ] } by { ' , ' . join ( [ a [ ' name ' ] for a in track [ ' artists ' ] ] ) } " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . green ( ) ,
2024-06-20 22:31:27 -04:00
)
2024-06-22 14:22:58 -04:00
embed . add_field ( name = " Album " , value = track [ " album " ] [ " name " ] , inline = False )
2024-06-22 08:55:26 -04:00
embed . set_thumbnail ( url = track [ " album " ] [ " images " ] [ 0 ] [ " url " ] )
2024-06-20 23:50:04 -04:00
await interaction . followup . send ( embed = embed )
2024-06-22 14:22:58 -04:00
logger . info ( f " Now playing: { track [ ' name ' ] } by { ' , ' . join ( [ a [ ' name ' ] for a in track [ ' artists ' ] ] ) } " )
except spotipy . SpotifyException as e :
if e . http_status == 403 :
await interaction . followup . send (
embed = discord . Embed (
title = " Play Track " ,
description = " Permission denied. Please check your Spotify account settings. " ,
color = discord . Color . red ( ) ,
)
)
logger . error ( f " Permission denied: { e } " )
else :
await interaction . followup . send ( f " An error occurred: { e } " )
logger . error ( f " Error in play_track command: { e } " )
2024-06-20 22:31:27 -04:00
except Exception as e :
await interaction . followup . send ( f " An error occurred: { e } " )
2024-06-21 13:08:45 -04:00
logger . error ( f " Error in play_track command: { e } " )
2024-06-20 22:31:27 -04:00
2024-06-22 14:22:58 -04:00
@app_commands.command ( name = " play_playlist " , description = " Play a playlist by searching for it or providing a link " )
2024-06-20 23:17:50 -04:00
async def play_playlist ( interaction : discord . Interaction , query : str ) :
await interaction . response . defer ( )
2024-06-22 14:22:58 -04:00
sp = self . get_spotify_session ( interaction . user . id )
if not sp :
await interaction . followup . send ( " Please log in to Spotify first using /login_spotify. " )
return
2024-06-20 23:17:50 -04:00
try :
2024-06-20 23:50:44 -04:00
if query . startswith ( " https://open.spotify.com/playlist/ " ) :
uri = query . split ( " / " ) [ - 1 ] . split ( " ? " ) [ 0 ]
uri = f " spotify:playlist: { uri } "
else :
2024-06-22 14:22:58 -04:00
results = sp . search ( q = query , limit = 1 , type = " playlist " )
2024-06-20 23:50:44 -04:00
if not results [ " playlists " ] [ " items " ] :
await interaction . followup . send (
embed = discord . Embed (
title = " Play Playlist " ,
description = " No results found " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . red ( ) ,
2024-06-20 23:50:44 -04:00
)
2024-06-20 23:50:04 -04:00
)
2024-06-21 13:08:45 -04:00
logger . info ( f " No results found for query: { query } " )
2024-06-20 23:50:44 -04:00
return
playlist = results [ " playlists " ] [ " items " ] [ 0 ]
uri = playlist [ " uri " ]
2024-06-20 23:17:50 -04:00
2024-06-22 14:22:58 -04:00
devices = sp . devices ( )
2024-06-20 23:17:50 -04:00
if not devices [ " devices " ] :
await interaction . followup . send (
2024-06-20 23:50:04 -04:00
embed = discord . Embed (
title = " Play Playlist " ,
2024-06-22 14:22:58 -04:00
description = " No active devices found. Please open Spotify on a device. " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . red ( ) ,
2024-06-20 23:50:04 -04:00
)
2024-06-20 23:17:50 -04:00
)
2024-06-21 13:08:45 -04:00
logger . info ( " No active devices found for playback " )
2024-06-20 23:17:50 -04:00
return
2024-06-22 14:22:58 -04:00
sp . start_playback ( context_uri = uri )
2024-06-20 23:50:04 -04:00
embed = discord . Embed (
title = " Now Playing Playlist " ,
2024-06-22 14:22:58 -04:00
description = f " { playlist [ ' name ' ] } by { playlist [ ' owner ' ] [ ' display_name ' ] } " if not query . startswith ( " https://open.spotify.com/playlist/ " ) else " Playing playlist " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . green ( ) ,
2024-06-20 23:17:50 -04:00
)
2024-06-20 23:50:44 -04:00
if not query . startswith ( " https://open.spotify.com/playlist/ " ) :
2024-06-22 08:55:26 -04:00
embed . set_thumbnail ( url = playlist [ " images " ] [ 0 ] [ " url " ] )
2024-06-20 23:50:04 -04:00
await interaction . followup . send ( embed = embed )
2024-06-22 14:22:58 -04:00
logger . info ( f " Now playing playlist: { playlist [ ' name ' ] } by { playlist [ ' owner ' ] [ ' display_name ' ] } " )
except spotipy . SpotifyException as e :
if e . http_status == 403 :
await interaction . followup . send (
embed = discord . Embed (
title = " Play Playlist " ,
description = " Permission denied. Please check your Spotify account settings. " ,
color = discord . Color . red ( ) ,
)
)
logger . error ( f " Permission denied: { e } " )
else :
await interaction . followup . send ( f " An error occurred: { e } " )
logger . error ( f " Error in play_playlist command: { e } " )
2024-06-20 23:17:50 -04:00
except Exception as e :
await interaction . followup . send ( f " An error occurred: { e } " )
2024-06-21 13:08:45 -04:00
logger . error ( f " Error in play_playlist command: { e } " )
2024-06-20 23:17:50 -04:00
2024-06-22 14:22:58 -04:00
@app_commands.command ( name = " pause " , description = " Pause the currently playing track " )
2024-06-20 22:31:27 -04:00
async def pause ( interaction : discord . Interaction ) :
await interaction . response . defer ( )
2024-06-22 14:22:58 -04:00
sp = self . get_spotify_session ( interaction . user . id )
if not sp :
await interaction . followup . send ( " Please log in to Spotify first using /login_spotify. " )
return
2024-06-20 22:31:27 -04:00
try :
2024-06-22 14:22:58 -04:00
sp . pause_playback ( )
2024-06-20 23:50:04 -04:00
await interaction . followup . send (
embed = discord . Embed (
title = " Pause " ,
description = " Playback paused. " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . green ( ) ,
2024-06-20 23:50:04 -04:00
)
)
2024-06-21 13:08:45 -04:00
logger . info ( " Playback paused " )
2024-06-22 14:22:58 -04:00
except spotipy . SpotifyException as e :
if e . http_status == 403 :
await interaction . followup . send (
embed = discord . Embed (
title = " Pause " ,
description = " Permission denied. Please check your Spotify account settings. " ,
color = discord . Color . red ( ) ,
)
)
logger . error ( f " Permission denied: { e } " )
else :
await interaction . followup . send ( f " An error occurred: { e } " )
logger . error ( f " Error in pause command: { e } " )
2024-06-20 22:31:27 -04:00
except Exception as e :
await interaction . followup . send ( f " An error occurred: { e } " )
2024-06-21 13:08:45 -04:00
logger . error ( f " Error in pause command: { e } " )
2024-06-20 22:31:27 -04:00
2024-06-22 14:22:58 -04:00
@app_commands.command ( name = " resume " , description = " Resume the currently playing track " )
2024-06-20 22:31:27 -04:00
async def resume ( interaction : discord . Interaction ) :
await interaction . response . defer ( )
2024-06-22 14:22:58 -04:00
sp = self . get_spotify_session ( interaction . user . id )
if not sp :
await interaction . followup . send ( " Please log in to Spotify first using /login_spotify. " )
return
2024-06-20 22:31:27 -04:00
try :
2024-06-22 14:22:58 -04:00
sp . start_playback ( )
2024-06-20 23:50:04 -04:00
await interaction . followup . send (
embed = discord . Embed (
title = " Resume " ,
description = " Playback resumed. " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . green ( ) ,
2024-06-20 23:50:04 -04:00
)
)
2024-06-21 13:08:45 -04:00
logger . info ( " Playback resumed " )
2024-06-22 14:22:58 -04:00
except spotipy . SpotifyException as e :
if e . http_status == 403 :
await interaction . followup . send (
embed = discord . Embed (
title = " Resume " ,
description = " Permission denied. Please check your Spotify account settings. " ,
color = discord . Color . red ( ) ,
)
)
logger . error ( f " Permission denied: { e } " )
else :
await interaction . followup . send ( f " An error occurred: { e } " )
logger . error ( f " Error in resume command: { e } " )
2024-06-20 22:31:27 -04:00
except Exception as e :
await interaction . followup . send ( f " An error occurred: { e } " )
2024-06-21 13:08:45 -04:00
logger . error ( f " Error in resume command: { e } " )
2024-06-20 22:31:27 -04:00
2024-06-22 08:55:26 -04:00
@app_commands.command ( name = " next " , description = " Skip to the next track " )
2024-06-20 22:31:27 -04:00
async def next_track ( interaction : discord . Interaction ) :
await interaction . response . defer ( )
2024-06-22 14:22:58 -04:00
sp = self . get_spotify_session ( interaction . user . id )
if not sp :
await interaction . followup . send ( " Please log in to Spotify first using /login_spotify. " )
return
2024-06-20 22:31:27 -04:00
try :
2024-06-22 14:22:58 -04:00
sp . next_track ( )
2024-06-20 23:50:04 -04:00
await interaction . followup . send (
embed = discord . Embed (
title = " Next Track " ,
description = " Skipped to the next track. " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . green ( ) ,
2024-06-20 23:50:04 -04:00
)
)
2024-06-21 13:08:45 -04:00
logger . info ( " Skipped to the next track " )
2024-06-22 14:22:58 -04:00
except spotipy . SpotifyException as e :
if e . http_status == 403 :
await interaction . followup . send (
embed = discord . Embed (
title = " Next Track " ,
description = " Permission denied. Please check your Spotify account settings. " ,
color = discord . Color . red ( ) ,
)
)
logger . error ( f " Permission denied: { e } " )
else :
await interaction . followup . send ( f " An error occurred: { e } " )
logger . error ( f " Error in next_track command: { e } " )
2024-06-20 22:31:27 -04:00
except Exception as e :
await interaction . followup . send ( f " An error occurred: { e } " )
2024-06-21 13:08:45 -04:00
logger . error ( f " Error in next_track command: { e } " )
2024-06-20 22:31:27 -04:00
2024-06-22 14:22:58 -04:00
@app_commands.command ( name = " previous " , description = " Go back to the previous track " )
2024-06-20 22:31:27 -04:00
async def previous_track ( interaction : discord . Interaction ) :
await interaction . response . defer ( )
2024-06-22 14:22:58 -04:00
sp = self . get_spotify_session ( interaction . user . id )
if not sp :
await interaction . followup . send ( " Please log in to Spotify first using /login_spotify. " )
return
2024-06-20 22:31:27 -04:00
try :
2024-06-22 14:22:58 -04:00
sp . previous_track ( )
2024-06-20 23:00:20 -04:00
await interaction . followup . send (
2024-06-20 23:50:04 -04:00
embed = discord . Embed (
title = " Previous Track " ,
description = " Returned to the previous track. " ,
2024-06-22 08:55:26 -04:00
color = discord . Color . green ( ) ,
2024-06-20 23:50:04 -04:00
)
2024-06-20 23:00:20 -04:00
)
2024-06-21 13:08:45 -04:00
logger . info ( " Returned to the previous track " )
2024-06-22 14:22:58 -04:00
except spotipy . SpotifyException as e :
if e . http_status == 403 :
await interaction . followup . send (
embed = discord . Embed (
title = " Previous Track " ,
description = " Permission denied. Please check your Spotify account settings. " ,
color = discord . Color . red ( ) ,
)
)
logger . error ( f " Permission denied: { e } " )
else :
await interaction . followup . send ( f " An error occurred: { e } " )
logger . error ( f " Error in previous_track command: { e } " )
2024-06-20 22:31:27 -04:00
except Exception as e :
await interaction . followup . send ( f " An error occurred: { e } " )
2024-06-21 13:08:45 -04:00
logger . error ( f " Error in previous_track command: { e } " )
2024-06-20 22:12:52 -04:00
2024-06-22 14:22:58 -04:00
self . bot . tree . add_command ( login_spotify )
self . bot . tree . add_command ( verify_spotify )
2024-06-20 22:12:52 -04:00
self . bot . tree . add_command ( current_track )
self . bot . tree . add_command ( play_track )
2024-06-20 23:17:50 -04:00
self . bot . tree . add_command ( play_playlist )
2024-06-20 22:31:27 -04:00
self . bot . tree . add_command ( pause )
self . bot . tree . add_command ( resume )
self . bot . tree . add_command ( next_track )
self . bot . tree . add_command ( previous_track )
2024-06-20 22:12:52 -04:00
async def setup ( bot ) :
2024-06-20 22:31:27 -04:00
SpotifyModule ( bot )