Fixed up the embeds so it's a single embed now, added a progress bar that tracks the song progress down to two seconds.
This commit is contained in:
parent
24bf918b9e
commit
82e7387379
192
audio.py
192
audio.py
@ -5,6 +5,7 @@ import os
|
|||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import requests
|
import requests
|
||||||
|
import time
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
@ -14,6 +15,7 @@ music_queues = {} # Per-guild song queue
|
|||||||
current_tracks = {} # Currently playing tracks
|
current_tracks = {} # Currently playing tracks
|
||||||
volumes = {} # Volume levels per guild
|
volumes = {} # Volume levels per guild
|
||||||
repeat_modes = {} # Tracks repeat mode per guild: "one", "all", or "off"
|
repeat_modes = {} # Tracks repeat mode per guild: "one", "all", or "off"
|
||||||
|
now_playing_messages = {} # Tracks the "Now Playing" embed per guild
|
||||||
default_volume = 0.05 # Default volume (5%)
|
default_volume = 0.05 # Default volume (5%)
|
||||||
USER_DATA_FILE = 'user_data.json'
|
USER_DATA_FILE = 'user_data.json'
|
||||||
YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY") # Replace with your API key
|
YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY") # Replace with your API key
|
||||||
@ -44,46 +46,44 @@ def update_user_history(user_id, song_title, artist):
|
|||||||
|
|
||||||
# ---------- Playback Functions ----------
|
# ---------- Playback Functions ----------
|
||||||
async def join_voice(interaction: discord.Interaction):
|
async def join_voice(interaction: discord.Interaction):
|
||||||
if interaction.user.voice is None or interaction.user.voice.channel is None:
|
"""Joins the user's voice channel."""
|
||||||
await interaction.response.send_message("You need to be in a voice channel for me to join.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
channel = interaction.user.voice.channel
|
|
||||||
guild_id = interaction.guild.id
|
guild_id = interaction.guild.id
|
||||||
|
channel = interaction.user.voice.channel
|
||||||
|
|
||||||
if guild_id not in voice_clients:
|
if guild_id in voice_clients and voice_clients[guild_id].channel == channel:
|
||||||
voice_clients[guild_id] = await channel.connect()
|
# Already in the same channel
|
||||||
|
return True
|
||||||
|
|
||||||
# Ensure the response is completed
|
try:
|
||||||
if interaction.response.is_done():
|
voice_client = await channel.connect()
|
||||||
await interaction.followup.send(f"✅ **Joined {channel.name}.**")
|
voice_clients[guild_id] = voice_client
|
||||||
else:
|
return True
|
||||||
await interaction.response.send_message(f"✅ **Joined {channel.name}.**")
|
except Exception as e:
|
||||||
return voice_clients[guild_id]
|
await interaction.followup.send(f"❌ **Error:** Could not join the voice channel. {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def play_audio(interaction: discord.Interaction, query: str):
|
async def play_audio(interaction: discord.Interaction, query: str):
|
||||||
guild_id = interaction.guild.id
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
# Defer the response if not already done
|
# Defer the interaction if not already responded
|
||||||
if not interaction.response.is_done():
|
if not interaction.response.is_done():
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
|
|
||||||
# Ensure Amber is connected to a voice channel
|
# Ensure Amber is connected to a voice channel
|
||||||
if guild_id not in voice_clients or not voice_clients[guild_id].is_connected():
|
if guild_id not in voice_clients or not voice_clients[guild_id].is_connected():
|
||||||
voice_client = await join_voice(interaction)
|
joined = await join_voice(interaction)
|
||||||
if voice_client is None: # If join failed
|
if not joined:
|
||||||
|
await interaction.followup.send("❌ **Error:** Could not join the voice channel.")
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
voice_client = voice_clients[guild_id]
|
|
||||||
|
|
||||||
# Search for the song on YouTube
|
# Search for the song on YouTube
|
||||||
ydl_opts = {
|
ydl_opts = {
|
||||||
'format': 'bestaudio/best',
|
'format': 'bestaudio/best',
|
||||||
'quiet': True,
|
'quiet': True,
|
||||||
}
|
}
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
||||||
try:
|
try:
|
||||||
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
info = ydl.extract_info(f"ytsearch:{query}", download=False)['entries'][0]
|
info = ydl.extract_info(f"ytsearch:{query}", download=False)['entries'][0]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await interaction.followup.send(f"❌ **Error:** Could not find or play the requested audio. {str(e)}")
|
await interaction.followup.send(f"❌ **Error:** Could not find or play the requested audio. {str(e)}")
|
||||||
@ -92,6 +92,7 @@ async def play_audio(interaction: discord.Interaction, query: str):
|
|||||||
song_url = info['url']
|
song_url = info['url']
|
||||||
title = info.get('title', 'Unknown Title')
|
title = info.get('title', 'Unknown Title')
|
||||||
artist = info.get('uploader', 'Unknown Artist')
|
artist = info.get('uploader', 'Unknown Artist')
|
||||||
|
duration = info.get('duration', 0) # Duration in seconds
|
||||||
|
|
||||||
# Log the song in the user's history
|
# Log the song in the user's history
|
||||||
update_user_history(interaction.user.id, title, artist)
|
update_user_history(interaction.user.id, title, artist)
|
||||||
@ -100,47 +101,64 @@ async def play_audio(interaction: discord.Interaction, query: str):
|
|||||||
if guild_id not in music_queues:
|
if guild_id not in music_queues:
|
||||||
music_queues[guild_id] = []
|
music_queues[guild_id] = []
|
||||||
|
|
||||||
music_queues[guild_id].append((song_url, title))
|
music_queues[guild_id].append((song_url, title, duration))
|
||||||
await interaction.followup.send(f"✅ **Added to queue:** {title}")
|
|
||||||
|
|
||||||
# If nothing is playing, start playback
|
# Start playback if nothing is playing
|
||||||
|
voice_client = voice_clients[guild_id]
|
||||||
if not voice_client.is_playing():
|
if not voice_client.is_playing():
|
||||||
await play_next(interaction)
|
await play_next(interaction)
|
||||||
|
|
||||||
# Send "Now Playing" embed with controls
|
# Use the existing embed or create a new one
|
||||||
|
if guild_id in now_playing_messages and now_playing_messages[guild_id]:
|
||||||
|
try:
|
||||||
|
message = await interaction.channel.fetch_message(now_playing_messages[guild_id])
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="🎵 Now Playing",
|
title="🎵 Now Playing",
|
||||||
description=f"**{title}** by {artist}",
|
description=f"**{title}** by {artist}\n\n`00:00 / {time.strftime('%M:%S', time.gmtime(duration))}`",
|
||||||
color=discord.Color.blue()
|
color=discord.Color.blue()
|
||||||
)
|
)
|
||||||
view = PlaybackControls(interaction, guild_id)
|
view = PlaybackControls(interaction, guild_id)
|
||||||
await interaction.followup.send(embed=embed, view=view)
|
await message.edit(embed=embed, view=view)
|
||||||
|
return
|
||||||
|
except discord.NotFound:
|
||||||
|
pass # If the original embed is deleted, send a new one
|
||||||
|
|
||||||
|
# Send the "Now Playing" embed
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🎵 Now Playing",
|
||||||
|
description=f"**{title}** by {artist}\n\n`00:00 / {time.strftime('%M:%S', time.gmtime(duration))}`",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
view = PlaybackControls(interaction, guild_id)
|
||||||
|
message = await interaction.followup.send(embed=embed, view=view)
|
||||||
|
now_playing_messages[guild_id] = message.id # Track the message ID
|
||||||
|
|
||||||
|
# Start updating the progress bar
|
||||||
|
start_time = time.time()
|
||||||
|
asyncio.create_task(update_progress_bar(interaction, duration, start_time))
|
||||||
|
|
||||||
|
|
||||||
async def play_next(interaction: discord.Interaction):
|
async def play_next(interaction: discord.Interaction):
|
||||||
guild_id = interaction.guild.id
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
if guild_id not in music_queues:
|
if guild_id not in music_queues or not music_queues[guild_id]:
|
||||||
music_queues[guild_id] = []
|
# If no songs are left in the queue, update the embed to reflect this
|
||||||
|
if guild_id in now_playing_messages and now_playing_messages[guild_id]:
|
||||||
# Handle Repeat One
|
try:
|
||||||
if repeat_modes.get(guild_id) == "one" and current_tracks.get(guild_id):
|
message = await interaction.channel.fetch_message(now_playing_messages[guild_id])
|
||||||
song_url, title = current_tracks[guild_id]
|
embed = discord.Embed(
|
||||||
music_queues[guild_id].insert(0, (song_url, title)) # Re-add the current song to the front of the queue
|
title="🎵 Queue Finished",
|
||||||
# Skip sending an alert for the repeated song
|
description="Amber is now idle. Add more songs to the queue to keep the music going!",
|
||||||
voice_client = voice_clients[guild_id]
|
color=discord.Color.red()
|
||||||
elif repeat_modes.get(guild_id) == "all" and not music_queues[guild_id]:
|
)
|
||||||
# Recycle the current track into the queue
|
await message.edit(embed=embed, view=None) # Remove playback controls
|
||||||
if current_tracks.get(guild_id):
|
except discord.NotFound:
|
||||||
music_queues[guild_id].append(current_tracks[guild_id])
|
# If the message was deleted, do nothing
|
||||||
|
pass
|
||||||
# If no songs are left in the queue
|
|
||||||
if not music_queues[guild_id]:
|
|
||||||
await interaction.followup.send("❌ **No more songs in the queue.**")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the next song from the queue
|
# Get the next song from the queue
|
||||||
song_url, title = music_queues[guild_id].pop(0)
|
song_url, title, duration = music_queues[guild_id].pop(0)
|
||||||
current_tracks[guild_id] = (song_url, title)
|
current_tracks[guild_id] = (song_url, title)
|
||||||
|
|
||||||
# Prepare FFmpeg options
|
# Prepare FFmpeg options
|
||||||
@ -163,16 +181,32 @@ async def play_next(interaction: discord.Interaction):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only alert if the song is new
|
# Update or create the "Now Playing" embed
|
||||||
if repeat_modes.get(guild_id) != "one":
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="🎵 Now Playing",
|
title="🎵 Now Playing",
|
||||||
description=f"**{title}**",
|
description=f"**{title}**\n\n`00:00 / {time.strftime('%M:%S', time.gmtime(duration))}`",
|
||||||
color=discord.Color.green()
|
color=discord.Color.green()
|
||||||
)
|
)
|
||||||
await interaction.followup.send(embed=embed)
|
|
||||||
|
# Check if the "Now Playing" message already exists
|
||||||
|
if guild_id in now_playing_messages and now_playing_messages[guild_id]:
|
||||||
|
try:
|
||||||
|
message = await interaction.channel.fetch_message(now_playing_messages[guild_id])
|
||||||
|
await message.edit(embed=embed)
|
||||||
|
except discord.NotFound:
|
||||||
|
# If the message was deleted, send a new one
|
||||||
|
message = await interaction.followup.send(embed=embed)
|
||||||
|
now_playing_messages[guild_id] = message.id
|
||||||
|
else:
|
||||||
|
# Send a new "Now Playing" embed
|
||||||
|
message = await interaction.followup.send(embed=embed)
|
||||||
|
now_playing_messages[guild_id] = message.id
|
||||||
|
|
||||||
|
# Start updating the progress bar with the song duration
|
||||||
|
start_time = time.time()
|
||||||
|
asyncio.create_task(update_progress_bar(interaction, duration, start_time))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await interaction.followup.send(f"❌ **Error:** Failed to play the next song. {str(e)}")
|
await interaction.followup.send(f"❌ **Error:** Could not play the next song. {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
async def stop_audio(interaction: discord.Interaction):
|
async def stop_audio(interaction: discord.Interaction):
|
||||||
@ -182,11 +216,15 @@ async def stop_audio(interaction: discord.Interaction):
|
|||||||
voice_client = voice_clients[guild_id]
|
voice_client = voice_clients[guild_id]
|
||||||
if voice_client.is_playing():
|
if voice_client.is_playing():
|
||||||
voice_client.stop()
|
voice_client.stop()
|
||||||
music_queues[guild_id] = [] # Clear the queue
|
await interaction.response.send_message("⏹️ **Playback stopped.**")
|
||||||
current_tracks.pop(guild_id, None) # Remove the current track
|
|
||||||
await interaction.response.send_message("🛑 **Playback stopped and queue cleared.**")
|
|
||||||
else:
|
else:
|
||||||
await interaction.response.send_message("❌ **No music is playing.**")
|
await interaction.response.send_message("⚠️ **Nothing is playing.**")
|
||||||
|
|
||||||
|
# Clear the queue but do not disconnect
|
||||||
|
music_queues[guild_id] = []
|
||||||
|
await interaction.followup.send("🗑️ **Queue cleared. Amber will remain idle in the voice channel.**")
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("⚠️ **Amber is not connected to a voice channel.**")
|
||||||
|
|
||||||
|
|
||||||
async def pause_audio(interaction: discord.Interaction):
|
async def pause_audio(interaction: discord.Interaction):
|
||||||
@ -341,6 +379,56 @@ class PlaybackControls(discord.ui.View):
|
|||||||
await interaction.response.edit_message(content=f"🔇 **Volume {status}.**", view=self)
|
await interaction.response.edit_message(content=f"🔇 **Volume {status}.**", view=self)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_progress_bar(interaction: discord.Interaction, duration: int, start_time: float):
|
||||||
|
"""Updates the progress bar in the 'Now Playing' embed."""
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
|
while guild_id in voice_clients and voice_clients[guild_id].is_playing():
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
|
||||||
|
# Calculate progress
|
||||||
|
progress = min(elapsed_time / duration, 1.0)
|
||||||
|
progress_blocks = int(progress * 20) # 20 blocks for the bar
|
||||||
|
progress_bar = "▬" * progress_blocks + "🔘" + "▬" * (20 - progress_blocks)
|
||||||
|
|
||||||
|
elapsed_time_str = time.strftime("%M:%S", time.gmtime(elapsed_time))
|
||||||
|
duration_str = time.strftime("%M:%S", time.gmtime(duration))
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🎵 Now Playing",
|
||||||
|
description=f"**{current_tracks[guild_id][1]}**\n\n{progress_bar} `{elapsed_time_str} / {duration_str}`",
|
||||||
|
color=discord.Color.green()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update the original "Now Playing" message
|
||||||
|
try:
|
||||||
|
message_id = now_playing_messages.get(guild_id)
|
||||||
|
if message_id:
|
||||||
|
message = await interaction.channel.fetch_message(message_id)
|
||||||
|
await message.edit(embed=embed)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
except discord.NotFound:
|
||||||
|
break # Stop if the message is deleted or invalid
|
||||||
|
|
||||||
|
await asyncio.sleep(2) # Update every 2 seconds
|
||||||
|
|
||||||
|
# When playback ends, clear the progress bar
|
||||||
|
if not voice_clients[guild_id].is_playing():
|
||||||
|
try:
|
||||||
|
message_id = now_playing_messages.pop(guild_id, None)
|
||||||
|
if message_id:
|
||||||
|
message = await interaction.channel.fetch_message(message_id)
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🎵 Queue Finished",
|
||||||
|
description="Amber is now idle. Add more songs to the queue to keep the music going!",
|
||||||
|
color=discord.Color.red()
|
||||||
|
)
|
||||||
|
await message.edit(embed=embed, view=None) # Remove playback controls
|
||||||
|
except discord.NotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# ---------- Recommendations ----------
|
# ---------- Recommendations ----------
|
||||||
def fetch_related_songs(song_title, artist):
|
def fetch_related_songs(song_title, artist):
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user