import discord from discord import app_commands from discord.ext import commands import asyncio import os from dotenv import load_dotenv from ytdl import YTDLSource # Load environment variables load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') GUILD_ID = os.getenv('GUILD_ID') class MusicBot(commands.Cog): def __init__(self, bot): self.bot = bot self.queue = [] self.current = None self.volume = 0.5 self.loop_current = False self.last_channel = None def load_saved_channel_id(self): if os.path.exists("config.json"): try: import json with open("config.json", "r") as f: data = json.load(f) return data.get("text_channel_id") except Exception as e: print(f"Error loading config: {e}") return None def save_channel_id(self, channel_id): try: import json data = {} if os.path.exists("config.json"): with open("config.json", "r") as f: data = json.load(f) data["text_channel_id"] = channel_id with open("config.json", "w") as f: json.dump(data, f, indent=4) except Exception as e: print(f"Error saving config: {e}") def get_notification_channel(self): if self.last_channel: return self.last_channel saved_id = self.load_saved_channel_id() if saved_id: channel = self.bot.get_channel(int(saved_id)) if channel: self.last_channel = channel return channel voice_client = self.bot.voice_clients[0] if self.bot.voice_clients else None if voice_client and voice_client.channel: for tc in voice_client.channel.guild.text_channels: if tc.permissions_for(voice_client.channel.guild.me).send_messages: self.last_channel = tc return tc return None async def ensure_voice(self, interaction): if not interaction.user.voice: await interaction.response.send_message( "You are not connected to a voice channel", ephemeral=True ) return False if interaction.guild.voice_client is None: await interaction.user.voice.channel.connect() elif interaction.guild.voice_client.channel != interaction.user.voice.channel: await interaction.response.send_message( "I'm already in another voice channel", ephemeral=True ) return False return True @app_commands.command(name="play", description="Play a song from YouTube") @app_commands.describe(query="YouTube URL or search query") async def play(self, interaction: discord.Interaction, query: str): """Play a song from YouTube""" if not await self.ensure_voice(interaction): return await interaction.response.defer() self.last_channel = interaction.channel try: player = await YTDLSource.from_url(query, loop=self.bot.loop, stream=True) if interaction.guild.voice_client.is_playing(): self.queue.append(player) await interaction.followup.send( f"Added to queue: {player.title}", ephemeral=False ) else: self.current = player interaction.guild.voice_client.play( player, after=lambda e: asyncio.run_coroutine_threadsafe( self.play_next(), self.bot.loop ) ) await interaction.followup.send( f"Now playing: {player.title}", ephemeral=False ) except Exception as e: await interaction.followup.send( f"An error occurred: {e}", ephemeral=True ) async def play_next(self): voice_client = self.bot.voice_clients[0] if self.bot.voice_clients else None if not voice_client: self.current = None return if self.loop_current and self.current: try: url = self.current.data.get('webpage_url') or self.current.url player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True) self.current = player voice_client.play( self.current, after=lambda e: asyncio.run_coroutine_threadsafe( self.play_next(), self.bot.loop ) ) await self.send_to_last_channel(f"Now playing (looped): {self.current.title}") return except Exception as e: print(f"Error repeating song: {e}") if len(self.queue) > 0: self.current = self.queue.pop(0) voice_client.play( self.current, after=lambda e: asyncio.run_coroutine_threadsafe( self.play_next(), self.bot.loop ) ) await self.send_to_last_channel(f"Now playing: {self.current.title}") else: self.current = None await self.send_to_last_channel("Queue is empty") async def send_to_last_channel(self, text): channel = self.get_notification_channel() if channel: try: await channel.send(text) except Exception as e: print(f"Failed to send message: {e}") @app_commands.command(name="queue", description="Show the current queue") async def show_queue(self, interaction: discord.Interaction): """Show the current queue""" if not self.queue: await interaction.response.send_message( "The queue is empty", ephemeral=True ) return queue_list = "\n".join([f"{i + 1}. {item.title}" for i, item in enumerate(self.queue)]) await interaction.response.send_message( f"Current queue:\n{queue_list}", ephemeral=False ) @app_commands.command(name="skip", description="Skip the current song") async def skip(self, interaction: discord.Interaction): """Skip the current song""" if interaction.guild.voice_client.is_playing(): interaction.guild.voice_client.stop() await interaction.response.send_message( "Skipped current song", ephemeral=False ) else: await interaction.response.send_message( "Not playing anything", ephemeral=True ) @app_commands.command(name="pause", description="Pause the current song") async def pause(self, interaction: discord.Interaction): """Pause the current song""" if interaction.guild.voice_client.is_playing(): interaction.guild.voice_client.pause() await interaction.response.send_message( "Paused", ephemeral=False ) else: await interaction.response.send_message( "Not playing anything", ephemeral=True ) @app_commands.command(name="resume", description="Resume the current song") async def resume(self, interaction: discord.Interaction): """Resume the current song""" if interaction.guild.voice_client.is_paused(): interaction.guild.voice_client.resume() await interaction.response.send_message( "Resumed", ephemeral=False ) else: await interaction.response.send_message( "Not paused", ephemeral=True ) @app_commands.command(name="stop", description="Stop playback and clear queue") async def stop(self, interaction: discord.Interaction): """Stop playback and clear queue""" if interaction.guild.voice_client.is_playing(): interaction.guild.voice_client.stop() self.queue.clear() self.current = None await interaction.response.send_message( "Stopped and cleared queue", ephemeral=False ) @app_commands.command(name="volume", description="Change the player volume") @app_commands.describe(volume="The volume level (1-100)") async def volume(self, interaction: discord.Interaction, volume: int): """Change the player volume""" if interaction.guild.voice_client is None: return await interaction.response.send_message( "Not connected to a voice channel", ephemeral=True ) if 0 < volume <= 100: self.volume = volume / 100 interaction.guild.voice_client.source.volume = self.volume await interaction.response.send_message( f"Changed volume to {volume}%", ephemeral=False ) return None else: await interaction.response.send_message( "Please enter a value between 1 and 100", ephemeral=True ) return None @app_commands.command(name="nowplaying", description="Show the currently playing song") async def now_playing(self, interaction: discord.Interaction): """Show the currently playing song""" if self.current: await interaction.response.send_message( f"Now playing: {self.current.title}", ephemeral=False ) else: await interaction.response.send_message( "Nothing is currently playing", ephemeral=True ) @app_commands.command(name="leave", description="Leave the voice channel") async def leave(self, interaction: discord.Interaction): """Leave the voice channel""" if interaction.guild.voice_client is not None: await interaction.guild.voice_client.disconnect() self.queue.clear() self.current = None await interaction.response.send_message( "Left the voice channel", ephemeral=False ) else: await interaction.response.send_message( "I'm not in a voice channel", ephemeral=True ) @app_commands.command(name="loop", description="Toggle loop for the current song") async def loop_command(self, interaction: discord.Interaction): """Toggle loop for the current song""" self.loop_current = not self.loop_current state = "enabled" if self.loop_current else "disabled" await interaction.response.send_message( f"Looping for the current song is now {state}.", ephemeral=False ) class MyBot(commands.Bot): def __init__(self): intents = discord.Intents.default() intents.message_content = True intents.voice_states = True super().__init__(command_prefix="!", intents=intents) async def setup_hook(self): await self.add_cog(MusicBot(self)) if GUILD_ID: guild = discord.Object(id=int(GUILD_ID)) self.tree.copy_global_to(guild=guild) await self.tree.sync(guild=guild) print(f"Synced commands to guild {GUILD_ID}") else: await self.tree.sync() print("Synced commands globally") async def on_ready(self): print(f'Logged in as {self.user.name} ({self.user.id})') print('------') bot = MyBot()