music-bot / app.py
not-lain's picture
add spotify
c8aea3c
raw
history blame
7.37 kB
# Import necessary libraries for Discord bot, HuggingFace integration, and UI
import discord
from discord.ext import commands
from huggingface_hub import hf_hub_download
import gradio as gr
from dotenv import load_dotenv
import os
import threading
import asyncio
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
import yt_dlp
# Load environment variables from .env file
load_dotenv()
# Create assets directory and download sample music if not exists
if os.path.exists('assets') is False:
os.makedirs('assets', exist_ok=True)
hf_hub_download("not-lain/assets", "sample.mp3", repo_type="dataset",local_dir="assets")
# Set up Discord bot with necessary intents
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='!', intents=intents)
# Initialize Spotify client
spotify = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials(
client_id=os.getenv('SPOTIFY_CLIENT_ID'),
client_secret=os.getenv('SPOTIFY_CLIENT_SECRET')
))
# Class to handle music playback functionality
class MusicBot:
def __init__(self):
# Initialize bot state variables
self.is_playing = False
self.voice_client = None
self.ydl_opts = {
'format': 'bestaudio/best',
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
}
async def join_voice(self, ctx):
# Method to join voice channel or move to user's channel
if ctx.author.voice:
channel = ctx.author.voice.channel
if self.voice_client is None:
self.voice_client = await channel.connect()
else:
await self.voice_client.move_to(channel)
else:
await ctx.send("You need to be in a voice channel!")
async def play_next(self, ctx):
# Method to play audio and handle playback completion
if not self.is_playing:
self.is_playing = True
try:
# Create audio source from local file
audio_source = discord.FFmpegPCMAudio("assets/sample.mp3")
def after_playing(e):
# Callback function when song ends
self.is_playing = False
if e:
print(f"Playback error: {e}")
# Test loop by default
asyncio.run_coroutine_threadsafe(self.play_next(ctx), bot.loop)
self.voice_client.play(audio_source, after=after_playing)
except Exception as e:
print(f"Error playing file: {e}")
await ctx.send("Error playing the song.")
self.is_playing = False
async def play_spotify(self, ctx, track_url):
if not self.is_playing:
self.is_playing = True
try:
# Extract Spotify track ID
track_id = track_url.split('/')[-1].split('?')[0]
track_info = spotify.track(track_id)
search_query = f"{track_info['name']} {track_info['artists'][0]['name']}"
# Use yt-dlp to find and download the audio
with yt_dlp.YoutubeDL(self.ydl_opts) as ydl:
# Search YouTube for the song
info = ydl.extract_info(f"ytsearch:{search_query}", download=False)
url = info['entries'][0]['url']
# Play the audio
FFMPEG_OPTIONS = {
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
'options': '-vn',
}
audio_source = discord.FFmpegPCMAudio(url, **FFMPEG_OPTIONS)
def after_playing(e):
self.is_playing = False
if e:
print(f"Playback error: {e}")
self.voice_client.play(audio_source, after=after_playing)
return track_info['name']
except Exception as e:
print(f"Error playing Spotify track: {e}")
await ctx.send("Error playing the song.")
self.is_playing = False
return None
# Create instance of MusicBot
music_bot = MusicBot()
@bot.event
async def on_ready():
# Event handler for when bot is ready and connected
print(f'Bot is ready! Logged in as {bot.user}')
print("Syncing commands...")
try:
await bot.tree.sync(guild=None) # Set to None for global sync
print("Successfully synced commands globally!")
except discord.app_commands.errors.CommandSyncFailure as e:
print(f"Failed to sync commands: {e}")
except Exception as e:
print(f"An error occurred while syncing commands: {e}")
@bot.tree.command(name="play", description="Play a song from Spotify")
async def play(interaction: discord.Interaction, url: str):
# Command to start playing music
await interaction.response.defer()
ctx = await commands.Context.from_interaction(interaction)
if not url.startswith('https://open.spotify.com/track/'):
await interaction.followup.send('Please provide a valid Spotify track URL!')
return
await music_bot.join_voice(ctx)
if not music_bot.is_playing:
song_name = await music_bot.play_spotify(ctx, url)
if song_name:
await interaction.followup.send(f'Playing {song_name} from Spotify!')
else:
await interaction.followup.send('Failed to play the song!')
else:
await interaction.followup.send('Already playing!')
@bot.tree.command(name="skip", description="Skip the current song")
async def skip(interaction: discord.Interaction):
# Command to skip current playing song
if music_bot.voice_client:
music_bot.voice_client.stop()
await interaction.response.send_message('Skipped current song!')
else:
await interaction.response.send_message('No song is currently playing!')
@bot.tree.command(name="leave", description="Disconnect bot from voice channel")
async def leave(interaction: discord.Interaction):
# Command to disconnect bot from voice channel
if music_bot.voice_client:
await music_bot.voice_client.disconnect()
music_bot.voice_client = None
music_bot.queue = []
music_bot.is_playing = False
await interaction.response.send_message('Bot disconnected!')
else:
await interaction.response.send_message('Bot is not in a voice channel!')
def run_discord_bot():
# Function to start the Discord bot
bot.run(os.getenv('DISCORD_TOKEN'))
# Create Gradio interface for web control
with gr.Blocks() as iface:
# Set up simple web interface
gr.Markdown("# Discord Music Bot Control Panel")
gr.Markdown("Bot is running in background")
if __name__ == "__main__":
# Main entry point: start bot in background thread and launch web interface
bot_thread = threading.Thread(target=run_discord_bot, daemon=True)
bot_thread.start()
# Launch Gradio interface in main thread
iface.launch(debug=True)