make a mode switch. where you can toggle between queue system and sync system. make it easy switch via buttons. also make a /soundboard command that makes multiple buttons with the sounds for easy use. but dont remove any existing features.
Code:
import discord
from discord import app_commands
from discord.ext import commands
import os
from dotenv import load_dotenv
import asyncio
from pydub import AudioSegment
import requests
import json
import logging
from typing import Optional

# Configure logging with timestamps
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s:%(levelname)s:%(name)s: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# Load environment variables from .env
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

# Define intents
intents = discord.Intents.default()
intents.message_content = True
intents.guilds = True
intents.voice_states = True

# Initialize the bot
class SoundboardBot(commands.Bot):
    def __init__(self):
        super().__init__(
            command_prefix="!",  # Prefix is not used for slash commands but required
            intents=intents,
        )
        self.queue = {}            # Dictionary to hold queues for each guild
        self.sounds = {}           # Dictionary to hold sound metadata
        self.disconnect_tasks = {} # Dictionary to hold disconnect tasks for each guild
        self.guild_durations = {}  # Dictionary to hold disconnect durations per guild

    async def setup_hook(self):
        # Sync the command tree with Discord
        await self.tree.sync()
        # Load sounds from JSON
        self.load_sounds()

    def load_sounds(self):
        sounds_file = os.path.join("data", "sounds.json")
        if os.path.exists(sounds_file):
            with open(sounds_file, "r") as f:
                self.sounds = json.load(f)
            logging.info("Loaded sounds from sounds.json")
        else:
            self.sounds = {}
            os.makedirs(os.path.dirname(sounds_file), exist_ok=True)
            with open(sounds_file, "w") as f:
                json.dump(self.sounds, f, indent=4)
            logging.info("Created new sounds.json")

    def save_sounds(self):
        sounds_file = os.path.join("data", "sounds.json")
        with open(sounds_file, "w") as f:
            json.dump(self.sounds, f, indent=4)
        logging.info("Saved sounds to sounds.json")


bot = SoundboardBot()

# Directory to store sound files
SOUND_DIR = "sounds"

# Ensure the sounds and data directories exist
for directory in [SOUND_DIR, "data"]:
    if not os.path.exists(directory):
        os.makedirs(directory)
        logging.info(f"Created directory: {directory}")

# Allowed audio formats
ALLOWED_EXTENSIONS = ('.mp3', '.wav', '.ogg', '.flac')
# Maximum file size (in bytes) for uploads via `/add` command
MAX_FILE_SIZE = 8 * 1024 * 1024  # 8MB

# Helper function to extract duration of audio file in seconds
def get_audio_duration(file_path):
    try:
        audio = AudioSegment.from_file(file_path)
        duration = len(audio) / 1000  # pydub works in milliseconds
        return round(duration, 2)
    except Exception as e:
        logging.error(f"Error getting duration of {file_path}: {e}")
        return None

# Helper function to download a file from a URL
async def download_file(url, dest):
    loop = asyncio.get_event_loop()
    try:
        response = requests.get(url, stream=True, timeout=120)
        response.raise_for_status()
        # Download in chunks to handle large files efficiently
        with open(dest, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
        return True, ""
    except requests.exceptions.RequestException as e:
        return False, str(e)

# Helper function to handle disconnect after duration
async def monitor_queue(guild_id: int):
    duration = bot.guild_durations.get(guild_id, 60)  # Default to 60 seconds
    logging.info(f"Monitoring guild {guild_id} for disconnect in {duration} seconds.")
    await asyncio.sleep(duration)
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)
    if voice_client and voice_client.is_connected():
        await voice_client.disconnect()
        logging.info(f"Disconnected from guild {guild_id} after {duration} seconds of inactivity.")

# Function to play the next sound in the queue
async def play_next(guild_id: int):
    if guild_id not in bot.queue:
        return

    if bot.queue[guild_id].empty():
        # Start a disconnect timer
        if guild_id in bot.disconnect_tasks:
            bot.disconnect_tasks[guild_id].cancel()
            del bot.disconnect_tasks[guild_id]
        disconnect_task = asyncio.create_task(monitor_queue(guild_id))
        bot.disconnect_tasks[guild_id] = disconnect_task
        return

    sound_file, repeat, duration_play = await bot.queue[guild_id].get()
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if not voice_client or not voice_client.is_connected():
        logging.warning(f"No active voice client in guild {guild_id}.")
        return

    if voice_client.is_playing():
        # Re-enqueue the sound if it's still playing
        await bot.queue[guild_id].put((sound_file, repeat, duration_play))
        return

    # Play the sound with specified duration
    try:
        # Prepare FFmpeg options to limit playback duration
        before_options = f"-ss 0 -t {duration_play}"
        source = discord.FFmpegPCMAudio(sound_file, before_options=before_options, options="-vn")
        def after_playing(error):
            if error:
                logging.error(f"Error playing sound: {error}")
            asyncio.run_coroutine_threadsafe(play_next(guild_id), bot.loop)
        voice_client.play(source, after=after_playing)
        sound_name = os.path.splitext(os.path.basename(sound_file))[0]
        logging.info(f"Playing sound '{sound_name}' in guild {guild_id}. Repeat left: {repeat}")
    except Exception as e:
        logging.error(f"Error playing sound {sound_file} in guild {guild_id}: {e}")
        # Proceed to next sound
        await play_next(guild_id)

# 🔹 Slash Command: Add a sound via file upload
@bot.tree.command(name="add", description="Add a new sound to the soundboard.")
@app_commands.describe(
    name="Name for the sound",
    file="Audio file to upload (MP3, WAV, OGG, FLAC)"
)
async def add_sound(interaction: discord.Interaction, name: str, file: discord.Attachment):
    await interaction.response.defer(ephemeral=True)

    # Validate file extension
    if not any(file.filename.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS):
        await interaction.followup.send(
            f"❌ Unsupported file format. Please upload a file with one of the following extensions: {', '.join(ALLOWED_EXTENSIONS)}.",
            ephemeral=True
        )
        return

    # Validate file size
    if file.size > MAX_FILE_SIZE:
        await interaction.followup.send(
            f"❌ File size exceeds the 8MB limit.",
            ephemeral=True
        )
        return

    # Sanitize the sound name to prevent filesystem issues
    safe_name = "".join([c if c.isalnum() or c in (' ', '_') else '_' for c in name]).strip()
    if not safe_name:
        await interaction.followup.send(
            "❌ Invalid sound name. Please use alphanumeric characters, spaces, or underscores.",
            ephemeral=True
        )
        return

    file_extension = os.path.splitext(file.filename)[1]
    file_path = os.path.join(SOUND_DIR, f"{safe_name}{file_extension}")

    # Check for name conflicts
    if any(safe_name.lower() == existing.lower() for existing in bot.sounds.keys()):
        await interaction.followup.send(
            f"❌ A sound with the name `{safe_name}` already exists.",
            ephemeral=True
        )
        return

    try:
        # Save the uploaded file
        await file.save(file_path)

        # Extract duration
        duration = get_audio_duration(file_path)
        if duration is None:
            os.remove(file_path)
            await interaction.followup.send(
                "❌ Failed to extract duration from the audio file. Ensure it's a valid audio file.",
                ephemeral=True
            )
            return

        # Add sound metadata
        bot.sounds[safe_name] = {
            "file_path": file_path,
            "duration": duration
        }
        bot.save_sounds()

        # Confirm addition with an embed
        embed = discord.Embed(
            title="✅ Sound Added ✅",
            description=f"Sound `{safe_name}` has been added successfully.",
            color=discord.Color.green()
        )
        if interaction.user.avatar:
            embed.set_thumbnail(url=interaction.user.avatar.url)
        embed.add_field(name="File", value=f"[{file.filename}]({file.url})", inline=False)
        embed.add_field(name="Duration", value=f"{duration} seconds", inline=False)
        await interaction.followup.send(embed=embed, ephemeral=True)
    except Exception as e:
        logging.error(f"Failed to add sound: {e}")
        await interaction.followup.send(f"❌ Failed to add sound: {e}", ephemeral=True)

# 🔹 Slash Command: Add a sound from a URL (no file upload limit)
@bot.tree.command(name="add_from_url", description="Add a new sound to the soundboard by downloading from a URL.")
@app_commands.describe(
    name="Name for the sound",
    url="Direct URL to the audio file (MP3, WAV, OGG, FLAC)"
)
async def add_from_url(interaction: discord.Interaction, name: str, url: str):
    await interaction.response.defer(ephemeral=True)

    # Validate file extension
    if not any(url.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS):
        await interaction.followup.send(
            f"❌ Unsupported file format. Please provide a URL ending with one of the following extensions: {', '.join(ALLOWED_EXTENSIONS)}.",
            ephemeral=True
        )
        return

    # Sanitize the sound name
    safe_name = "".join([c if c.isalnum() or c in (' ', '_') else '_' for c in name]).strip()
    if not safe_name:
        await interaction.followup.send(
            "❌ Invalid sound name. Please use alphanumeric characters, spaces, or underscores.",
            ephemeral=True
        )
        return

    file_extension = os.path.splitext(url)[1]
    file_path = os.path.join(SOUND_DIR, f"{safe_name}{file_extension}")

    # Check for name conflicts
    if any(safe_name.lower() == existing.lower() for existing in bot.sounds.keys()):
        await interaction.followup.send(
            f"❌ A sound with the name `{safe_name}` already exists.",
            ephemeral=True
        )
        return

    try:
        # Download the file
        success, error = await download_file(url, file_path)
        if not success:
            await interaction.followup.send(
                f"❌ Failed to download the file: {error}",
                ephemeral=True
            )
            return

        # Extract duration
        duration = get_audio_duration(file_path)
        if duration is None:
            os.remove(file_path)
            await interaction.followup.send(
                "❌ Failed to extract duration from the audio file. Ensure it's a valid audio file.",
                ephemeral=True
            )
            return

        # Add sound metadata
        bot.sounds[safe_name] = {
            "file_path": file_path,
            "duration": duration
        }
        bot.save_sounds()

        # Confirm addition with an embed
        embed = discord.Embed(
            title="✅ Sound Added from URL ✅",
            description=f"Sound `{safe_name}` has been added successfully from the provided URL.",
            color=discord.Color.green()
        )
        embed.add_field(name="URL", value=f"[Download Here]({url})", inline=False)
        embed.add_field(name="Duration", value=f"{duration} seconds", inline=False)
        await interaction.followup.send(embed=embed, ephemeral=True)
    except Exception as e:
        logging.error(f"Failed to add sound from URL: {e}")
        # Clean up if download failed
        if os.path.exists(file_path):
            os.remove(file_path)
        await interaction.followup.send(f"❌ Failed to add sound from URL: {e}", ephemeral=True)

# 🔹 Slash Command: Play a sound with optional repeat and play duration
@bot.tree.command(name="play", description="Play a sound in your current voice channel.")
@app_commands.describe(
    name="Name of the sound to play",
    repeat="Number of times to play the sound (default: 1)",
    play_duration="Time in seconds to play the sound (default: full duration)"
)
async def play_sound(interaction: discord.Interaction, name: str, repeat: int = 1, play_duration: Optional[float] = None):
    await interaction.response.defer()
    
    # Validate repeat parameter
    if repeat < 1:
        await interaction.followup.send("❌ Repeat count must be at least 1.", ephemeral=True)
        return

    # Check if the user is in a voice channel
    if not interaction.user.voice or not interaction.user.voice.channel:
        await interaction.followup.send("❌ You need to be in a voice channel to use this command.", ephemeral=True)
        return

    voice_channel = interaction.user.voice.channel
    guild_id = interaction.guild.id

    # Locate the sound file
    if name not in bot.sounds:
        await interaction.followup.send(f"❌ Sound `{name}` not found.", ephemeral=True)
        return

    sound_file = bot.sounds[name]["file_path"]
    sound_duration = bot.sounds[name]["duration"]

    # Determine play duration
    if play_duration is None:
        play_duration = sound_duration
    elif play_duration > sound_duration:
        play_duration = sound_duration
    elif play_duration <= 0:
        await interaction.followup.send("❌ Play duration must be a positive number.", ephemeral=True)
        return
    play_duration = round(play_duration, 2)  # Ensure it's rounded to 2 decimal places

    # Initialize the queue if not present
    if guild_id not in bot.queue:
        bot.queue[guild_id] = asyncio.Queue()

    # Enqueue the sound with repeat count and play duration
    for _ in range(repeat):
        await bot.queue[guild_id].put((sound_file, 1, play_duration))  # Each enqueue has repeat=1

    # Cancel any existing disconnect task
    if guild_id in bot.disconnect_tasks:
        bot.disconnect_tasks[guild_id].cancel()
        del bot.disconnect_tasks[guild_id]

    # Connect to the voice channel if not already connected
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if not voice_client:
        try:
            voice_client = await voice_channel.connect()
            logging.info(f"Connected to voice channel {voice_channel} in guild {guild_id}.")
        except Exception as e:
            logging.error(f"Failed to connect to voice channel: {e}")
            await interaction.followup.send(f"❌ Failed to connect to voice channel: {e}", ephemeral=True)
            return
    else:
        if voice_client.channel != voice_channel:
            try:
                await voice_client.move_to(voice_channel)
                logging.info(f"Moved to voice channel {voice_channel} in guild {guild_id}.")
            except Exception as e:
                logging.error(f"Failed to move to voice channel: {e}")
                await interaction.followup.send(f"❌ Failed to move to voice channel: {e}", ephemeral=True)
                return

    # If the voice client is not playing, start playing the next sound
    if not voice_client.is_playing():
        await play_next(guild_id)

    # Send confirmation with embed
    queue_length = bot.queue[guild_id].qsize()
    embed = discord.Embed(
        title="🎶 Sound Enqueued 🎶",
        description=f"Sound `{name}` has been added to the queue.",
        color=discord.Color.blue()
    )
    if interaction.user.avatar:
        embed.set_thumbnail(url=interaction.user.avatar.url)
    embed.add_field(name="Repeat Count", value=f"{repeat}", inline=False)
    embed.add_field(name="Play Duration", value=f"{play_duration} seconds", inline=False)
    embed.add_field(name="Position in Queue", value=f"{queue_length}", inline=False)
    embed.set_footer(text=f"Total in Queue: {queue_length}")
    await interaction.followup.send(embed=embed)

# 🔹 Slash Command: Set Disconnect Duration After Queue Empty (Admin Only)
@bot.tree.command(name="set_disconnect_duration", description="Set how long the bot stays connected after the queue is empty. (Admin only)")
@app_commands.describe(
    duration="Time in seconds to stay connected after queue is empty (minimum: 10)"
)
@app_commands.default_permissions(administrator=True)
async def set_disconnect_duration(interaction: discord.Interaction, duration: int):
    await interaction.response.defer(ephemeral=True)

    # Validate duration parameter
    if duration < 10:
        await interaction.followup.send("❌ Duration must be at least 10 seconds.", ephemeral=True)
        return

    guild_id = interaction.guild.id
    bot.guild_durations[guild_id] = duration

    # If there's an existing disconnect task, cancel it
    if guild_id in bot.disconnect_tasks:
        bot.disconnect_tasks[guild_id].cancel()
        del bot.disconnect_tasks[guild_id]

    # Confirm setting with an embed
    embed = discord.Embed(
        title="⏰ Disconnect Duration Set ⏰",
        description=f"The bot will stay connected for {duration} seconds after the queue is empty.",
        color=discord.Color.blue()
    )
    if interaction.user.avatar:
        embed.set_thumbnail(url=interaction.user.avatar.url)
    await interaction.followup.send(embed=embed, ephemeral=True)

# 🔹 Slash Command: Skip the Currently Playing Sound
@bot.tree.command(name="skip", description="Skip the currently playing sound.")
async def skip_sound(interaction: discord.Interaction):
    await interaction.response.defer()
    guild_id = interaction.guild.id
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if voice_client and voice_client.is_playing():
        voice_client.stop()
        logging.info(f"Skipped sound in guild {guild_id}.")
        await interaction.followup.send("✅ Skipped the current sound.")
    else:
        await interaction.followup.send("❌ No sound is currently playing.", ephemeral=True)

# 🔹 Slash Command: Stop Playback and Clear Queue
@bot.tree.command(name="stop", description="Stop playback and clear the queue.")
async def stop_sound(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id
    voice_client = discord.utils.get(bot.voice_clients, guild__id=guild_id)

    if guild_id in bot.queue:
        while not bot.queue[guild_id].empty():
            bot.queue[guild_id].get_nowait()

    if voice_client and voice_client.is_connected():
        voice_client.stop()
        await voice_client.disconnect()
        logging.info(f"Stopped playback and disconnected from guild {guild_id}.")
        await interaction.followup.send("✅ Stopped playback and cleared the queue.")
    else:
        await interaction.followup.send("❌ No active voice connection found.", ephemeral=True)

# 🔹 Slash Command: Clear the Entire Queue
@bot.tree.command(name="clear", description="Clear the entire sound queue.")
async def clear_queue(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id

    if guild_id in bot.queue and not bot.queue[guild_id].empty():
        while not bot.queue[guild_id].empty():
            bot.queue[guild_id].get_nowait()
        # Cancel any disconnect tasks as the queue is now empty
        if guild_id in bot.disconnect_tasks:
            bot.disconnect_tasks[guild_id].cancel()
            del bot.disconnect_tasks[guild_id]
        await interaction.followup.send("✅ The queue has been cleared.")
        logging.info(f"Cleared the queue in guild {guild_id}.")
    else:
        await interaction.followup.send("❌ The queue is already empty.", ephemeral=True)

# 🔹 Slash Command: List All Sounds with Durations
@bot.tree.command(name="list", description="List all available sounds.")
async def list_sounds(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)

    if not bot.sounds:
        embed = discord.Embed(
            title="❌ No Sounds Available ❌",
            description="Add some sounds using the `/add` or `/add_from_url` commands.",
            color=discord.Color.red()
        )
        await interaction.followup.send(embed=embed)
        return

    embed = discord.Embed(
        title="🎶 Available Sounds 🎶",
        description="Here are the sounds you can play:",
        color=discord.Color.green()
    )

    # Discord embeds have a limit of 25 fields; handle accordingly
    sounds_sorted = sorted(bot.sounds.items(), key=lambda item: item[0].lower())
    for name, meta in sounds_sorted:
        embed.add_field(
            name=name,
            value=f"Duration: {meta['duration']} seconds",
            inline=False
        )
        if len(embed.fields) == 25:
            break  # Prevent exceeding Discord's embed field limit

    await interaction.followup.send(embed=embed)

# 🔹 Slash Command: Display the Current Sound Queue
@bot.tree.command(name="queue", description="Display the current sound queue.")
async def display_queue(interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id

    if guild_id not in bot.queue or bot.queue[guild_id].empty():
        embed = discord.Embed(
            title="🔄 Sound Queue 🔄",
            description="The queue is currently empty.",
            color=discord.Color.orange()
        )
        await interaction.followup.send(embed=embed)
        return

    # Peek into the queue without removing items
    temp_list = list(bot.queue[guild_id].queue)

    embed = discord.Embed(
        title="🔄 Sound Queue 🔄",
        description="Here's the current queue:",
        color=discord.Color.purple()
    )

    for idx, (sound, repeat, duration_play) in enumerate(temp_list, start=1):
        sound_name = os.path.splitext(os.path.basename(sound))[0]
        embed.add_field(
            name=f"{idx}. {sound_name}",
            value=f"Repeat: {repeat} time(s)\nPlay Duration: {duration_play} seconds",
            inline=False
        )
        if len(embed.fields) == 25:
            break  # Prevent exceeding Discord's embed field limit

    await interaction.followup.send(embed=embed)

# 🔹 Slash Command: Delete a Sound (Admin Only)
@bot.tree.command(name="delete", description="Delete a sound from the soundboard. (Admin only)")
@app_commands.describe(name="Name of the sound to delete")
@app_commands.default_permissions(administrator=True)
async def delete_sound(interaction: discord.Interaction, name: str):
    await interaction.response.defer(ephemeral=True)

    # Locate the sound file
    if name not in bot.sounds:
        await interaction.followup.send(f"❌ Sound `{name}` not found.", ephemeral=True)
        return

    sound_file = bot.sounds[name]["file_path"]

    try:
        os.remove(sound_file)
        del bot.sounds[name]
        bot.save_sounds()

        # Also remove the sound from all guild queues if present
        for guild_id, queue in bot.queue.items():
            temp_queue = asyncio.Queue()
            while not queue.empty():
                item = await queue.get()
                if item[0] != sound_file:
                    await temp_queue.put(item)
            bot.queue[guild_id] = temp_queue

        # Confirm deletion with an embed
        embed = discord.Embed(
            title="🗑️ Sound Deleted 🗑️",
            description=f"Sound `{name}` has been deleted successfully.",
            color=discord.Color.dark_red()
        )
        if interaction.user.avatar:
            embed.set_thumbnail(url=interaction.user.avatar.url)
        await interaction.followup.send(embed=embed, ephemeral=True)
    except Exception as e:
        logging.error(f"Failed to delete sound: {e}")
        await interaction.followup.send(f"❌ Failed to delete sound: {e}", ephemeral=True)

# 🔹 Error handling for delete command permissions
@delete_sound.error
async def delete_sound_error(interaction: discord.Interaction, error):
    if isinstance(error, app_commands.errors.MissingPermissions):
        await interaction.response.send_message("🔒 You don't have permission to delete sounds.", ephemeral=True)
    else:
        await interaction.response.send_message("⚠️ An error occurred while trying to delete the sound.", ephemeral=True)

# 🔹 Event: Bot Ready
@bot.event
async def on_ready():
    print(f'Bot connected as {bot.user}')  # Removed Unicode emojis to prevent encoding errors
    logging.info(f'✅ Bot connected as {bot.user} ✅')

# 🔹 Run the bot
bot.run(TOKEN)