music-bot / app.py
not-lain's picture
Implement voice keepalive feature and enhance command validations
c0a28a9
raw
history blame
7.04 kB
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
# Load environment variables
load_dotenv()
if os.path.exists("assets") is False:
os.makedirs("assets", exist_ok=True)
hf_hub_download(
"not-lain/assets", "lofi.mp3", repo_type="dataset", local_dir="assets"
)
song = "assets/lofi.mp3"
# Bot configuration
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)
class MusicBot:
def __init__(self):
self.is_playing = False
self.voice_client = None
self.keepalive_task = None
self.last_context = None
async def voice_keepalive(self, voice_client):
"""Keeps the voice connection alive by periodically playing audio"""
print("Starting voice keepalive task")
while True:
if voice_client.is_connected() and not self.is_playing:
await self.play_next(self.last_context)
await asyncio.sleep(15)
else:
await asyncio.sleep(1)
async def join_voice(self, ctx):
if ctx.author.voice:
channel = ctx.author.voice.channel
if self.voice_client is None:
self.voice_client = await channel.connect()
self.last_context = ctx
if self.keepalive_task:
self.keepalive_task.cancel()
self.keepalive_task = asyncio.create_task(
self.voice_keepalive(self.voice_client)
)
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):
if not self.is_playing:
self.is_playing = True
try:
audio_source = discord.FFmpegPCMAudio(song)
def after_playing(e):
self.is_playing = False
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 stop_playing(self, ctx):
try:
if self.keepalive_task:
self.keepalive_task.cancel()
self.keepalive_task = None
if self.voice_client:
if self.voice_client.is_playing():
self.voice_client.stop()
self.is_playing = False
self.last_context = None
if self.voice_client.is_connected():
await self.voice_client.disconnect(force=False)
self.voice_client = None
return True
return False
except Exception as e:
print(f"Error during cleanup: {e}")
self.is_playing = False
self.voice_client = None
self.last_context = None
self.keepalive_task = None
return False
music_bot = MusicBot()
@bot.event
async def on_ready():
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 the sample music")
async def play(interaction: discord.Interaction):
await interaction.response.defer()
ctx = await bot.get_context(interaction)
await music_bot.join_voice(ctx)
if not music_bot.is_playing:
await music_bot.play_next(ctx)
await interaction.followup.send("Playing sample music!")
else:
await interaction.followup.send("Already playing!")
@bot.tree.command(name="skip", description="Skip the current song")
async def skip(interaction: discord.Interaction):
# Check if user is in a voice channel
if not interaction.user.voice:
await interaction.response.send_message(
"You must be in a voice channel to use this command!"
)
return
# Check if bot is in a voice channel
if not music_bot.voice_client:
await interaction.response.send_message("No song is currently playing!")
return
# Check if user is in the same channel as the bot
if interaction.user.voice.channel != music_bot.voice_client.channel:
await interaction.response.send_message(
"You must be in the same voice channel as the bot!"
)
return
if music_bot.voice_client and music_bot.is_playing:
music_bot.is_playing = False # Reset playing state
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):
# Check if user is in a voice channel
if not interaction.user.voice:
await interaction.response.send_message(
"You must be in a voice channel to use this command!"
)
return
# Check if bot is in a voice channel
if not music_bot.voice_client:
await interaction.response.send_message("I'm not in any voice channel!")
return
# Check if user is in the same channel as the bot
if interaction.user.voice.channel != music_bot.voice_client.channel:
await interaction.response.send_message(
"You must be in the same voice channel as the bot!"
)
return
await interaction.response.defer()
ctx = await bot.get_context(interaction)
try:
success = await music_bot.stop_playing(ctx)
if success:
await interaction.followup.send("Successfully disconnected! 👋")
else:
await interaction.followup.send(
"Failed to disconnect properly. Please try again."
)
except Exception as e:
print(f"Error during leave command: {e}")
await interaction.followup.send("An error occurred while trying to disconnect.")
def run_discord_bot():
bot.run(os.getenv("DISCORD_TOKEN"))
# Create the Gradio interface
with gr.Blocks() as iface:
gr.Markdown("# Discord Music Bot Control Panel")
gr.Markdown("Bot is running in background")
if __name__ == "__main__":
# Start the Discord bot in a separate thread
bot_thread = threading.Thread(target=run_discord_bot, daemon=True)
bot_thread.start()
# Run Gradio interface in the main thread
iface.launch(debug=True)