please make a ai chatbot that when anybody sends a message to this channel 1288185653494026292 it gets parsed into a ai with a certaint personality I can specify in a json. please make it then respond with text and with speech, depending on what mode is selected. make a speech only mode, make a text only mode and make the normal mode that does both. and for speech, make it turn its response into a speech using Coqui TTS with gpu enabled. and then playing it. make sure the ai gets the username that send the message. and also make it have chat history. use openhermes model for the ollama. make sure to output the entire code. here is what I have so far:
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, List
from enum import Enum
from discord.ui import View, Button
from math import ceil

# 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

# Playback modes
class PlaybackMode(Enum):
    QUEUE = 'queue'
    SYNC = 'sync'
    PARALLEL = 'parallel'

# 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
        self.guild_modes = {}      # Dictionary to hold playback modes per guild ('queue', 'sync', 'parallel')
        self.parallel_audio_sources = {}  # Dictionary to hold ParallelAudioSource 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")

    async def play_sound_via_button(self, interaction: discord.Interaction, sound_name: str):
        """
        Helper function to play sounds when triggered via soundboard buttons.
        Respects the current playback mode ('queue', 'sync', 'parallel').
        """
        # Check if the user is in a voice channel
        if not interaction.user.voice or not interaction.user.voice.channel:
            await interaction.response.send_message("❌ You need to be in a voice channel to use this button.", ephemeral=True)
            return

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

        # Locate the sound file
        if sound_name not in self.sounds:
            await interaction.response.send_message(f"❌ Sound `{sound_name}` not found.", ephemeral=True)
            return

        sound_file = self.sounds[sound_name]["file_path"]
        sound_duration = self.sounds[sound_name]["duration"]

        # Get the current mode
        mode = self.guild_modes.get(guild_id, 'queue')

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

        # Connect to the voice channel if not already connected
        voice_client = discord.utils.get(self.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.response.send_message(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.response.send_message(f"❌ Failed to move to voice channel: {e}", ephemeral=True)
                    return

        if mode == 'queue':
            # Enqueue the sound with default speed=1.0
            await self.queue[guild_id].put((sound_file, 1, sound_duration, 1.0))
            logging.info(f"Enqueued sound '{sound_name}' in guild {guild_id} (Mode: Queue).")

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

            # If not already playing, start playing
            if not voice_client.is_playing():
                await play_next(guild_id)

            await interaction.response.send_message(f"✅ Sound `{sound_name}` enqueued.", ephemeral=True)

        elif mode == 'sync':
            # Sync mode: Play immediately, interrupting current playback
            if voice_client.is_playing():
                voice_client.stop()
                logging.info(f"Stopped current playback in guild {guild_id} for sync mode.")

            # Clear the queue
            if guild_id in self.queue:
                try:
                    while not self.queue[guild_id].empty():
                        self.queue[guild_id].get_nowait()
                except:
                    pass  # Ignore if queue is already empty

            # Enqueue the sound with default speed=1.0
            await self.queue[guild_id].put((sound_file, 1, sound_duration, 1.0))
            logging.info(f"Queued sound '{sound_name}' in guild {guild_id} (Mode: Sync).")

            # Start playing immediately
            await play_next(guild_id)

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

            await interaction.response.send_message(f"✅ Sound `{sound_name}` played immediately (Sync Mode).", ephemeral=True)

        elif mode == 'parallel':
            # Parallel mode: Play sounds simultaneously
            # Prepare the audio source with default speed=1.0
            filters = generate_atempo_filter(1.0)
            before_options = f"-ss 0 -t {sound_duration}"
            options = f"-vn -filter:a {filters}"
            source = discord.FFmpegPCMAudio(sound_file, before_options=before_options, options=options)

            # Initialize ParallelAudioSource if not exists
            if guild_id not in self.parallel_audio_sources:
                self.parallel_audio_sources[guild_id] = ParallelAudioSource()

            # Add the source to ParallelAudioSource
            self.parallel_audio_sources[guild_id].add_source(source)

            # If not already playing, start playing
            if not voice_client.is_playing():
                def after_playing(error):
                    if error:
                        logging.error(f"Error in parallel playback: {error}")
                    asyncio.run_coroutine_threadsafe(handle_parallel_finish(guild_id), self.loop)

                voice_client.play(self.parallel_audio_sources[guild_id], after=after_playing)
                logging.info(f"Started parallel playback in guild {guild_id}.")

            await interaction.response.send_message(f"✅ Sound `{sound_name}` added to parallel playback.", ephemeral=True)

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.")

# Helper function to generate atempo filters based on desired speed
def generate_atempo_filter(speed: float) -> str:
    if speed <= 0:
        raise ValueError("Speed must be a positive number.")

    filters = []
    while speed > 2.0 or speed < 0.5:
        if speed > 2.0:
            filters.append("atempo=2.0")
            speed /= 2.0
        elif speed < 0.5:
            filters.append("atempo=0.5")
            speed /= 0.5
    filters.append(f"atempo={speed}")
    return ",".join(filters)

# 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, speed = 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, speed))
        return

    # Play the sound with specified duration and speed
    try:
        # Prepare FFmpeg options to limit playback duration and set speed
        filters = generate_atempo_filter(speed)
        before_options = f"-ss 0 -t {duration_play}"
        options = f"-vn -filter:a {filters}"
        source = discord.FFmpegPCMAudio(sound_file, before_options=before_options, options=options)
        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}. Speed: {speed}x")
    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)

# Handle finishing of parallel playback
async def handle_parallel_finish(guild_id: int):
    if guild_id in bot.parallel_audio_sources:
        if bot.parallel_audio_sources[guild_id].finished:
            # 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
            del bot.parallel_audio_sources[guild_id]
            logging.info(f"Parallel playback finished in guild {guild_id}.")

# Class for mixing audio sources for parallel playback
class ParallelAudioSource(discord.AudioSource):
    def __init__(self):
        self.sources: List[discord.AudioSource] = []
        self.finished = False

    def add_source(self, source: discord.AudioSource):
        self.sources.append(source)

    def is_opus(self):
        return False

    def read(self):
        if not self.sources:
            self.finished = True
            return b''

        frames = []
        for source in self.sources[:]:
            frame = source.read()
            if frame:
                frames.append(frame)
            else:
                # EOF for this source
                self.sources.remove(source)

        if not frames:
            self.finished = True
            return b''

        # Assume all frames are the same size
        frame_size = len(frames[0])
        mixed_frame = bytearray(frame_size)

        # Mixing frames
        for i in range(0, frame_size, 2):
            sample_sum = 0
            for frame in frames:
                # Get the sample from the frame
                sample = int.from_bytes(frame[i:i+2], 'little', signed=True)
                sample_sum += sample
            # Prevent clipping
            sample_sum = max(min(sample_sum, 32767), -32768)
            # Write back to mixed_frame
            mixed_frame[i:i+2] = int(sample_sum).to_bytes(2, 'little', signed=True)
        return bytes(mixed_frame)

# 🔹 New Feature: Soundboard Command with Pagination
# Utilizing discord.ui for interactive components

class SoundboardView(View):
    def __init__(self, bot: SoundboardBot, guild_id: int, current_page: int = 1, sounds_per_page: int = 20):
        super().__init__(timeout=None)
        self.bot = bot
        self.guild_id = guild_id
        self.current_page = current_page
        self.sounds_per_page = sounds_per_page
        self.sounds_sorted = sorted(bot.sounds.keys(), key=lambda x: x.lower())
        self.total_sounds = len(self.sounds_sorted)
        self.total_pages = ceil(self.total_sounds / self.sounds_per_page) if self.sounds_per_page else 1

        self.update_buttons()

    def update_buttons(self):
        # Clear existing buttons to update the view
        self.clear_items()

        start_index = (self.current_page - 1) * self.sounds_per_page
        end_index = start_index + self.sounds_per_page
        sounds_on_page = self.sounds_sorted[start_index:end_index]

        # Add sound buttons
        for sound_name in sounds_on_page:
            self.add_item(SoundButton(sound_name, self.bot, self.guild_id))

        # Add navigation buttons if necessary
        if self.total_pages > 1:
            if self.current_page > 1:
                self.add_item(PreviousPageButton())
            if self.current_page < self.total_pages:
                self.add_item(NextPageButton())

class PreviousPageButton(Button):
    def __init__(self):
        super().__init__(style=discord.ButtonStyle.secondary, label="⬅️ Previous", custom_id="previous_page")

    async def callback(self, interaction: discord.Interaction):
        view: SoundboardView = self.view
        if view.current_page > 1:
            view.current_page -= 1
            view.update_buttons()
            # Retrieve the existing embed
            if interaction.message.embeds:
                embed = interaction.message.embeds[0]
            else:
                embed = discord.Embed(
                    title="🎵 Soundboard 🎵",
                    description="Click the buttons below to play sounds.",
                    color=discord.Color.blue()
                )
            # Update the footer
            embed.set_footer(text=f"Page {view.current_page} of {view.total_pages}")
            await interaction.response.edit_message(embed=embed, view=view)
        else:
            await interaction.response.defer()

class NextPageButton(Button):
    def __init__(self):
        super().__init__(style=discord.ButtonStyle.secondary, label="Next ➡️", custom_id="next_page")

    async def callback(self, interaction: discord.Interaction):
        view: SoundboardView = self.view
        if view.current_page < view.total_pages:
            view.current_page += 1
            view.update_buttons()
            # Retrieve the existing embed
            if interaction.message.embeds:
                embed = interaction.message.embeds[0]
            else:
                embed = discord.Embed(
                    title="🎵 Soundboard 🎵",
                    description="Click the buttons below to play sounds.",
                    color=discord.Color.blue()
                )
            # Update the footer
            embed.set_footer(text=f"Page {view.current_page} of {view.total_pages}")
            await interaction.response.edit_message(embed=embed, view=view)
        else:
            await interaction.response.defer()

class SoundButton(Button):
    def __init__(self, sound_name: str, bot: SoundboardBot, guild_id: int):
        super().__init__(
            style=discord.ButtonStyle.primary,
            label=sound_name,
            custom_id=f'soundboard_sound_{sound_name}'
        )
        self.sound_name = sound_name
        self.bot = bot
        self.guild_id = guild_id

    async def callback(self, interaction: discord.Interaction):
        await self.bot.play_sound_via_button(interaction, self.sound_name)

# 🔹 Slash Command: Soundboard
@bot.tree.command(name="soundboard", description="Display the soundboard with clickable buttons for sounds.")
async def soundboard(interaction: discord.Interaction):
    await interaction.response.defer()

    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

    # Create the view with sound buttons and pagination
    view = SoundboardView(bot, interaction.guild.id)

    embed = discord.Embed(
        title="🎵 Soundboard 🎵",
        description="Click the buttons below to play sounds.",
        color=discord.Color.blue()
    )

    # Add page information
    embed.set_footer(text=f"Page {view.current_page} of {view.total_pages}")

    await interaction.followup.send(embed=embed, view=view)

# 🔹 Slash Command: Set Playback Mode
@bot.tree.command(name="set_mode", description="Set the playback mode for the bot.")
@app_commands.describe(
    mode='Playback mode to set: "queue", "sync", or "parallel"'
)
async def set_mode(interaction: discord.Interaction, mode: PlaybackMode):
    await interaction.response.defer(ephemeral=True)
    guild_id = interaction.guild.id
    bot.guild_modes[guild_id] = mode.value
    logging.info(f"Guild {guild_id} mode changed to {mode.value.capitalize()}.")
    await interaction.followup.send(f"✅ Playback mode set to '{mode.value}'.", ephemeral=True)

# 🔹 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 speed
@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)",
    speed="Playback speed (e.g., 1.0 is normal, 2.0 is 2x speed) (optional)"
)
async def play_sound(interaction: discord.Interaction, name: str, repeat: int = 1, play_duration: Optional[float] = None, speed: Optional[float] = 1.0):
    await interaction.response.defer()
    
    # Validate repeat parameter
    if repeat < 1:
        await interaction.followup.send("❌ Repeat count must be at least 1.", ephemeral=True)
        return

    # Validate speed parameter
    if speed <= 0:
        await interaction.followup.send("❌ Speed must be a positive number.", 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

    # Get the current mode
    mode = bot.guild_modes.get(guild_id, 'queue')

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

    # 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 mode == 'queue':
        # Enqueue the sound with specified speed
        for _ in range(repeat):
            await bot.queue[guild_id].put((sound_file, 1, play_duration, speed))  # Each enqueue has repeat=1
        logging.info(f"Enqueued sound '{name}' {repeat} time(s) in guild {guild_id} (Mode: Queue).")

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

        # 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="Speed", value=f"{speed}x", 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)

    elif mode == 'sync':
        # Sync mode: Play immediately, interrupting current playback
        if voice_client.is_playing():
            voice_client.stop()
            logging.info(f"Stopped current playback in guild {guild_id} for sync mode.")

        # Clear the queue
        if guild_id in bot.queue:
            try:
                while not bot.queue[guild_id].empty():
                    bot.queue[guild_id].get_nowait()
            except:
                pass  # Ignore if queue is already empty

        # Enqueue the sound with specified speed
        for _ in range(repeat):
            await bot.queue[guild_id].put((sound_file, 1, play_duration, speed))  # Each enqueue has repeat=1
        logging.info(f"Enqueued sound '{name}' {repeat} time(s) in guild {guild_id} (Mode: Sync).")

        # Start playing immediately
        await play_next(guild_id)

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

        # Send confirmation with embed
        embed = discord.Embed(
            title="🎶 Sound Played 🎶",
            description=f"Sound `{name}` has been played immediately (Sync Mode).",
            color=discord.Color.orange()
        )
        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="Speed", value=f"{speed}x", inline=False)
        await interaction.followup.send(embed=embed)

    elif mode == 'parallel':
        # Parallel mode: Play sounds simultaneously
        # Prepare the audio sources with specified speed
        for _ in range(repeat):
            filters = generate_atempo_filter(speed)
            before_options = f"-ss 0 -t {play_duration}"
            options = f"-vn -filter:a {filters}"
            source = discord.FFmpegPCMAudio(sound_file, before_options=before_options, options=options)

            # Initialize ParallelAudioSource if not exists
            if guild_id not in bot.parallel_audio_sources:
                bot.parallel_audio_sources[guild_id] = ParallelAudioSource()

            # Add the source to ParallelAudioSource
            bot.parallel_audio_sources[guild_id].add_source(source)
            logging.info(f"Added sound '{name}' at speed {speed}x to parallel playback in guild {guild_id}.")

        # If not already playing, start playing
        if not voice_client.is_playing():
            def after_playing(error):
                if error:
                    logging.error(f"Error in parallel playback: {error}")
                asyncio.run_coroutine_threadsafe(handle_parallel_finish(guild_id), bot.loop)

            voice_client.play(bot.parallel_audio_sources[guild_id], after=after_playing)
            logging.info(f"Started parallel playback in guild {guild_id}.")

        # Send confirmation with embed
        embed = discord.Embed(
            title="🎶 Sound Added to Parallel Playback 🎶",
            description=f"Sound `{name}` has been added to parallel playback.",
            color=discord.Color.green()
        )
        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="Speed", value=f"{speed}x", inline=False)
        await interaction.followup.send(embed=embed)

# 🔹 Slash Command: Play All Sounds
@bot.tree.command(name="play_all", description="Play all sounds at once in your current voice channel.")
@app_commands.describe(
    repeat="Number of times to play each sound (default: 1)",
    play_duration="Time in seconds to play each sound (default: full duration)",
    speed="Playback speed for all sounds (e.g., 1.0 is normal, 2.0 is 2x speed) (optional)"
)
async def play_all_sounds(interaction: discord.Interaction, repeat: int = 1, play_duration: Optional[float] = None, speed: Optional[float] = 1.0):
    await interaction.response.defer()

    # Validate repeat parameter
    if repeat < 1:
        await interaction.followup.send("❌ Repeat count must be at least 1.", ephemeral=True)
        return

    # Validate speed parameter
    if speed <= 0:
        await interaction.followup.send("❌ Speed must be a positive number.", ephemeral=True)
        return

    # Check if there are sounds available
    if not bot.sounds:
        await interaction.followup.send("❌ No sounds available to play.", 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

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

    # 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

    # Determine play duration
    # Assuming all sounds play the same duration
    first_sound = next(iter(bot.sounds.values()), None)
    if first_sound:
        default_play_duration = first_sound["duration"]
        if play_duration is None:
            play_duration = default_play_duration
        elif play_duration > default_play_duration:
            play_duration = default_play_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)

    # Enqueue all sounds
    for sound_name, meta in bot.sounds.items():
        sound_file = meta["file_path"]
        sound_duration = meta["duration"]

        # Determine individual play duration
        individual_play_duration = play_duration if play_duration else sound_duration

        # Adjust if play_duration exceeds actual duration
        if individual_play_duration > sound_duration:
            individual_play_duration = sound_duration

        if individual_play_duration <= 0:
            individual_play_duration = sound_duration

        for _ in range(repeat):
            await bot.queue[guild_id].put((sound_file, 1, individual_play_duration, speed))
            logging.info(f"Enqueued sound '{sound_name}' {repeat} time(s) for Play All in guild {guild_id}.")

    # Update playback mode to parallel temporarily
    original_mode = bot.guild_modes.get(guild_id, 'queue')
    bot.guild_modes[guild_id] = 'parallel'

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

    # If not already playing, start playing
    if not voice_client.is_playing():
        await play_next(guild_id)

    # Restore original playback mode after enqueueing
    bot.guild_modes[guild_id] = original_mode

    # Send confirmation with embed
    queue_length = bot.queue[guild_id].qsize()
    embed = discord.Embed(
        title="🎶 All Sounds Enqueued 🎶",
        description=f"All sounds have been enqueued for playback.",
        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" if play_duration else "Full duration", inline=False)
    embed.add_field(name="Speed", value=f"{speed}x", inline=False)
    embed.add_field(name="Total Sounds Enqueued", value=f"{len(bot.sounds) * repeat}", 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 guild_id in bot.parallel_audio_sources:
        del bot.parallel_audio_sources[guild_id]

    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, speed) 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\nSpeed: {speed}x",
            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)