Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
64b27154b3 | ||
|
d69f1099ff | ||
|
807c0afb5e | ||
|
cb55fa8ce4 | ||
|
fb9c3d68de | ||
|
bd5e814306 | ||
|
82e7387379 | ||
|
24bf918b9e | ||
|
f53f6a1173 | ||
|
4f38a1a89e | ||
|
d53887dd1a | ||
|
5a0cf3855d | ||
|
215fad0471 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -169,3 +169,4 @@ cython_debug/
|
|||||||
|
|
||||||
# PyPI configuration file
|
# PyPI configuration file
|
||||||
.pypirc
|
.pypirc
|
||||||
|
/user_data.json
|
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Run Quartessa",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "E:\\Development\\AI Development\\Quartessa\\main.py",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
440
audio.py
Normal file
440
audio.py
Normal file
@ -0,0 +1,440 @@
|
|||||||
|
import discord
|
||||||
|
import yt_dlp
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
voice_clients = {} # Track active voice connections
|
||||||
|
music_queues = {} # Per-guild song queue
|
||||||
|
current_tracks = {} # Currently playing tracks
|
||||||
|
volumes = {} # Volume levels per guild
|
||||||
|
repeat_modes = {} # Tracks repeat mode per guild: "one", "all", or "off"
|
||||||
|
now_playing_messages = {} # Tracks the "Now Playing" embed per guild
|
||||||
|
progress_tasks = {} # Progress bar tasks per guild
|
||||||
|
_247_mode = {} # Tracks 24/7 mode per guild
|
||||||
|
default_volume = 0.05 # Default volume (5%)
|
||||||
|
USER_DATA_FILE = 'user_data.json'
|
||||||
|
YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY") # Replace with your API key
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- Utility Functions ----------
|
||||||
|
def load_user_data():
|
||||||
|
if not os.path.exists(USER_DATA_FILE):
|
||||||
|
return {}
|
||||||
|
with open(USER_DATA_FILE, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def save_user_data(data):
|
||||||
|
with open(USER_DATA_FILE, 'w') as f:
|
||||||
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
def update_user_history(user_id, song_title, artist):
|
||||||
|
user_data = load_user_data()
|
||||||
|
if str(user_id) not in user_data:
|
||||||
|
user_data[str(user_id)] = {"history": []}
|
||||||
|
|
||||||
|
# Add the song to the user's history
|
||||||
|
user_data[str(user_id)]["history"].append({"title": song_title, "artist": artist})
|
||||||
|
save_user_data(user_data)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- Playback Functions ----------
|
||||||
|
async def join_voice(interaction: discord.Interaction):
|
||||||
|
"""Joins the user's voice channel."""
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
channel = interaction.user.voice.channel
|
||||||
|
|
||||||
|
if guild_id in voice_clients and voice_clients[guild_id].channel == channel:
|
||||||
|
# Already in the same channel
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
voice_client = await channel.connect()
|
||||||
|
voice_clients[guild_id] = voice_client
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
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):
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
|
# Immediate acknowledgment to prevent "Amber is thinking"
|
||||||
|
if not interaction.response.is_done():
|
||||||
|
await interaction.response.defer()
|
||||||
|
|
||||||
|
# Ensure Amber is connected to a voice channel
|
||||||
|
if guild_id not in voice_clients or not voice_clients[guild_id].is_connected():
|
||||||
|
joined = await join_voice(interaction)
|
||||||
|
if not joined:
|
||||||
|
await interaction.followup.send("❌ **Error:** Could not join the voice channel.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Start a background task to fetch and queue the song
|
||||||
|
asyncio.create_task(fetch_and_queue_song(interaction, query))
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_and_queue_song(interaction: discord.Interaction, query: str):
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
|
# Search for the song on YouTube
|
||||||
|
ydl_opts = {
|
||||||
|
'format': 'bestaudio/best',
|
||||||
|
'quiet': True,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
info = ydl.extract_info(f"ytsearch:{query}", download=False)['entries'][0]
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.followup.send(f"❌ **Error:** Could not find or play the requested audio. {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
song_url = info['url']
|
||||||
|
title = info.get('title', 'Unknown Title')
|
||||||
|
artist = info.get('uploader', 'Unknown Artist')
|
||||||
|
duration = info.get('duration', 0)
|
||||||
|
|
||||||
|
# Log the song in the user's history
|
||||||
|
update_user_history(interaction.user.id, title, artist)
|
||||||
|
|
||||||
|
# Add the song to the queue
|
||||||
|
if guild_id not in music_queues:
|
||||||
|
music_queues[guild_id] = []
|
||||||
|
|
||||||
|
music_queues[guild_id].append((song_url, title, duration))
|
||||||
|
await interaction.followup.send(f"✅ **Added to Queue:** {title}")
|
||||||
|
|
||||||
|
# Start playback if nothing is playing
|
||||||
|
if not voice_clients[guild_id].is_playing():
|
||||||
|
await play_next(interaction)
|
||||||
|
|
||||||
|
|
||||||
|
async def play_next(interaction: discord.Interaction):
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
|
# If no songs are left in the queue
|
||||||
|
if guild_id not in music_queues or not music_queues[guild_id]:
|
||||||
|
if _247_mode.get(guild_id, True):
|
||||||
|
# Update the embed to indicate idle state
|
||||||
|
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(
|
||||||
|
title="🎵 Queue Empty",
|
||||||
|
description="Amber is in 24/7 mode. Add more songs to the queue to resume playback.",
|
||||||
|
color=discord.Color.orange()
|
||||||
|
)
|
||||||
|
await message.edit(embed=embed, view=None) # Remove playback controls
|
||||||
|
except discord.NotFound:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Disconnect if 24/7 mode is disabled
|
||||||
|
if guild_id in voice_clients and voice_clients[guild_id].is_connected():
|
||||||
|
await voice_clients[guild_id].disconnect()
|
||||||
|
del voice_clients[guild_id]
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the next song from the queue
|
||||||
|
song_url, title, duration = music_queues[guild_id].pop(0)
|
||||||
|
current_tracks[guild_id] = (song_url, title)
|
||||||
|
|
||||||
|
# Prepare FFmpeg options
|
||||||
|
ffmpeg_options = {
|
||||||
|
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
|
||||||
|
'options': '-vn',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
voice_client = voice_clients[guild_id]
|
||||||
|
source = discord.FFmpegPCMAudio(song_url, **ffmpeg_options)
|
||||||
|
volume = volumes.get(guild_id, default_volume)
|
||||||
|
source = discord.PCMVolumeTransformer(source, volume=volume)
|
||||||
|
|
||||||
|
# Play the audio
|
||||||
|
voice_client.play(
|
||||||
|
source,
|
||||||
|
after=lambda e: asyncio.run_coroutine_threadsafe(
|
||||||
|
play_next(interaction), interaction.client.loop
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the "Now Playing" embed
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🎵 Now Playing",
|
||||||
|
description=f"**{title}**\n\n`00:00 / {time.strftime('%M:%S', time.gmtime(duration))}`",
|
||||||
|
color=discord.Color.green()
|
||||||
|
)
|
||||||
|
embed.set_thumbnail(url="https://img.youtube.com/vi/{}/0.jpg".format(song_url.split("?v=")[-1]))
|
||||||
|
embed.add_field(name="Artist", value=artist, inline=True)
|
||||||
|
embed.set_footer(text="React with 👍 or 👎 to rate this song!")
|
||||||
|
view = PlaybackControls(interaction, guild_id)
|
||||||
|
|
||||||
|
# Update or send the embed
|
||||||
|
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, view=view)
|
||||||
|
except discord.NotFound:
|
||||||
|
message = await interaction.followup.send(embed=embed, view=view)
|
||||||
|
now_playing_messages[guild_id] = message.id
|
||||||
|
else:
|
||||||
|
message = await interaction.followup.send(embed=embed, view=view)
|
||||||
|
now_playing_messages[guild_id] = message.id
|
||||||
|
|
||||||
|
# Start updating the progress bar
|
||||||
|
start_time = time.time()
|
||||||
|
asyncio.create_task(update_progress_bar(interaction, duration, start_time))
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.followup.send(f"❌ **Error:** Could not play the next song. {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
async def update_progress_bar(interaction: discord.Interaction, duration: int, start_time: float):
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
|
try:
|
||||||
|
while guild_id in voice_clients and voice_clients[guild_id].is_playing():
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
progress = min(elapsed_time / duration, 1.0)
|
||||||
|
progress_blocks = int(progress * 20)
|
||||||
|
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()
|
||||||
|
)
|
||||||
|
|
||||||
|
if guild_id in now_playing_messages and now_playing_messages[guild_id]:
|
||||||
|
message_id = now_playing_messages[guild_id]
|
||||||
|
try:
|
||||||
|
message = await interaction.channel.fetch_message(message_id)
|
||||||
|
await message.edit(embed=embed)
|
||||||
|
except discord.NotFound:
|
||||||
|
break
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def stop_audio(interaction: discord.Interaction):
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
|
if guild_id in voice_clients:
|
||||||
|
voice_client = voice_clients[guild_id]
|
||||||
|
if voice_client.is_playing():
|
||||||
|
voice_client.stop()
|
||||||
|
await interaction.response.send_message("⏹️ **Playback stopped.**")
|
||||||
|
else:
|
||||||
|
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):
|
||||||
|
"""Pauses the currently playing song."""
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
|
if guild_id in voice_clients and voice_clients[guild_id].is_playing():
|
||||||
|
voice_clients[guild_id].pause() # Pause playback
|
||||||
|
await interaction.response.send_message("⏸️ **Music paused.**")
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("❌ **No music is currently playing.**")
|
||||||
|
|
||||||
|
|
||||||
|
async def resume_audio(interaction: discord.Interaction):
|
||||||
|
"""Resumes playback of a paused song."""
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
|
if guild_id in voice_clients and voice_clients[guild_id].is_paused():
|
||||||
|
voice_clients[guild_id].resume() # Resume playback
|
||||||
|
await interaction.response.send_message("▶️ **Music resumed.**")
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("❌ **No paused music to resume.**")
|
||||||
|
|
||||||
|
|
||||||
|
async def set_volume(interaction: discord.Interaction, level: int):
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
|
if level < 0 or level > 100:
|
||||||
|
await interaction.response.send_message("❌ **Volume must be between 0 and 100.**")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set the volume
|
||||||
|
volume = level / 100
|
||||||
|
volumes[guild_id] = volume
|
||||||
|
|
||||||
|
# Adjust volume for the current source if playing
|
||||||
|
if guild_id in voice_clients and voice_clients[guild_id].is_playing():
|
||||||
|
source = voice_clients[guild_id].source
|
||||||
|
if isinstance(source, discord.PCMVolumeTransformer):
|
||||||
|
source.volume = volume
|
||||||
|
|
||||||
|
await interaction.response.send_message(f"🔊 **Volume set to {level}%**.")
|
||||||
|
|
||||||
|
|
||||||
|
async def leave_voice(interaction: discord.Interaction):
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
|
||||||
|
if guild_id in voice_clients:
|
||||||
|
voice_client = voice_clients[guild_id]
|
||||||
|
if voice_client.is_connected():
|
||||||
|
await voice_client.disconnect()
|
||||||
|
voice_clients.pop(guild_id, None)
|
||||||
|
await interaction.response.send_message("👋 **Left the voice channel.**")
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("❌ **I am not connected to any voice channel.**")
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("❌ **I am not connected to any voice channel.**")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- Discord UI ----------
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybackControls(discord.ui.View):
|
||||||
|
def __init__(self, interaction: discord.Interaction, guild_id: int):
|
||||||
|
super().__init__(timeout=None)
|
||||||
|
self.interaction = interaction
|
||||||
|
self.guild_id = guild_id
|
||||||
|
|
||||||
|
@discord.ui.button(label="Pause", style=discord.ButtonStyle.primary)
|
||||||
|
async def pause(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
if self.guild_id in voice_clients and voice_clients[self.guild_id].is_playing():
|
||||||
|
voice_clients[self.guild_id].pause()
|
||||||
|
await interaction.response.send_message("⏸️ **Playback paused.**", ephemeral=True)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("⚠️ **No audio is playing.**", ephemeral=True)
|
||||||
|
|
||||||
|
@discord.ui.button(label="Resume", style=discord.ButtonStyle.green)
|
||||||
|
async def resume(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
if self.guild_id in voice_clients and voice_clients[self.guild_id].is_paused():
|
||||||
|
voice_clients[self.guild_id].resume()
|
||||||
|
await interaction.response.send_message("▶️ **Playback resumed.**", ephemeral=True)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("⚠️ **No audio is paused.**", ephemeral=True)
|
||||||
|
|
||||||
|
@discord.ui.button(label="Stop", style=discord.ButtonStyle.danger)
|
||||||
|
async def stop(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
if self.guild_id in voice_clients:
|
||||||
|
voice_clients[self.guild_id].stop()
|
||||||
|
music_queues[self.guild_id] = [] # Clear the queue
|
||||||
|
await interaction.response.send_message("⏹️ **Playback stopped and queue cleared.**", ephemeral=True)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("⚠️ **No audio is playing.**", ephemeral=True)
|
||||||
|
|
||||||
|
@discord.ui.button(label="Skip", style=discord.ButtonStyle.secondary)
|
||||||
|
async def skip(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
if self.guild_id in voice_clients and voice_clients[self.guild_id].is_playing():
|
||||||
|
voice_clients[self.guild_id].stop() # Trigger play_next
|
||||||
|
await interaction.response.send_message("⏭️ **Skipped to the next track.**", ephemeral=True)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("⚠️ **No audio is playing.**", ephemeral=True)
|
||||||
|
|
||||||
|
@discord.ui.button(label="Repeat: Off", style=discord.ButtonStyle.success)
|
||||||
|
async def repeat(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
current_mode = repeat_modes.get(self.guild_id, "off")
|
||||||
|
new_mode = "one" if current_mode == "off" else "all" if current_mode == "one" else "off"
|
||||||
|
repeat_modes[self.guild_id] = new_mode
|
||||||
|
button.label = f"Repeat: {'One' if new_mode == 'one' else 'All' if new_mode == 'all' else 'Off'}"
|
||||||
|
await interaction.response.edit_message(view=self)
|
||||||
|
|
||||||
|
@discord.ui.button(label="Volume Up", style=discord.ButtonStyle.primary)
|
||||||
|
async def volume_up(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
if self.guild_id in voice_clients:
|
||||||
|
volume = volumes.get(self.guild_id, default_volume)
|
||||||
|
new_volume = min(volume + 0.1, 2.0) # Max volume 200%
|
||||||
|
volumes[self.guild_id] = new_volume
|
||||||
|
voice_clients[self.guild_id].source.volume = new_volume
|
||||||
|
await interaction.response.send_message(f"🔊 **Volume increased to {int(new_volume * 100)}%.**", ephemeral=True)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("⚠️ **No audio is playing.**", ephemeral=True)
|
||||||
|
|
||||||
|
@discord.ui.button(label="Volume Down", style=discord.ButtonStyle.primary)
|
||||||
|
async def volume_down(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
if self.guild_id in voice_clients:
|
||||||
|
volume = volumes.get(self.guild_id, default_volume)
|
||||||
|
new_volume = max(volume - 0.1, 0.1) # Min volume 10%
|
||||||
|
volumes[self.guild_id] = new_volume
|
||||||
|
voice_clients[self.guild_id].source.volume = new_volume
|
||||||
|
await interaction.response.send_message(f"🔉 **Volume decreased to {int(new_volume * 100)}%.**", ephemeral=True)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("⚠️ **No audio is playing.**", ephemeral=True)
|
||||||
|
|
||||||
|
@discord.ui.button(label="Mute/Unmute", style=discord.ButtonStyle.danger)
|
||||||
|
async def mute(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
if self.guild_id in voice_clients:
|
||||||
|
muted = volumes.get(self.guild_id) == 0.0
|
||||||
|
new_volume = default_volume if muted else 0.0
|
||||||
|
volumes[self.guild_id] = new_volume
|
||||||
|
voice_clients[self.guild_id].source.volume = new_volume
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"🔇 **Muted.**" if new_volume == 0.0 else "🔈 **Unmuted.**",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("⚠️ **No audio is playing.**", ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- Recommendations ----------
|
||||||
|
def fetch_related_songs(song_title, artist):
|
||||||
|
"""
|
||||||
|
Fetch related songs using the YouTube Data API v3.
|
||||||
|
Returns a list of recommendations in the format:
|
||||||
|
[ "Title - URL" ]
|
||||||
|
"""
|
||||||
|
query = f"{song_title} {artist} related songs"
|
||||||
|
url = "https://www.googleapis.com/youtube/v3/search"
|
||||||
|
params = {
|
||||||
|
"q": query,
|
||||||
|
"key": YOUTUBE_API_KEY,
|
||||||
|
"type": "video",
|
||||||
|
"part": "snippet",
|
||||||
|
"maxResults": 5
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, params=params)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
return [
|
||||||
|
f'{item["snippet"]["title"]} - https://www.youtube.com/watch?v={item["id"]["videoId"]}'
|
||||||
|
for item in data.get("items", [])
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
print(f"Error fetching related songs: {response.status_code} {response.text}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def generate_recommendations(user_id):
|
||||||
|
"""
|
||||||
|
Generate recommendations using the last song the user listened to.
|
||||||
|
"""
|
||||||
|
user_data = load_user_data()
|
||||||
|
user_history = user_data.get(str(user_id), {}).get("history", [])
|
||||||
|
|
||||||
|
if not user_history:
|
||||||
|
return ["No recommendations yet. Start playing some songs!"]
|
||||||
|
|
||||||
|
# Use the last played song to fetch recommendations
|
||||||
|
last_song = user_history[-1]
|
||||||
|
title = last_song["title"]
|
||||||
|
artist = last_song["artist"]
|
||||||
|
recommendations = fetch_related_songs(title, artist)
|
||||||
|
|
||||||
|
if recommendations:
|
||||||
|
return recommendations
|
||||||
|
else:
|
||||||
|
return ["No recommendations could be fetched at this time."]
|
84
commands.py
Normal file
84
commands.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
from audio import (
|
||||||
|
play_audio,
|
||||||
|
stop_audio,
|
||||||
|
set_volume,
|
||||||
|
join_voice,
|
||||||
|
leave_voice,
|
||||||
|
pause_audio,
|
||||||
|
resume_audio,
|
||||||
|
repeat_modes,
|
||||||
|
_247_mode,
|
||||||
|
generate_recommendations
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_commands(client, guild_id=None):
|
||||||
|
@client.tree.command(name="join", description="Join the user's current voice channel.")
|
||||||
|
async def join(interaction: discord.Interaction):
|
||||||
|
await join_voice(interaction)
|
||||||
|
|
||||||
|
@client.tree.command(name="leave", description="Leave the current voice channel.")
|
||||||
|
async def leave(interaction: discord.Interaction):
|
||||||
|
await leave_voice(interaction)
|
||||||
|
|
||||||
|
@client.tree.command(name="play", description="Play a song by title or artist.")
|
||||||
|
async def play(interaction: discord.Interaction, query: str):
|
||||||
|
await play_audio(interaction, query)
|
||||||
|
|
||||||
|
@client.tree.command(name="stop", description="Stop playback and clear the queue.")
|
||||||
|
async def stop(interaction: discord.Interaction):
|
||||||
|
await stop_audio(interaction)
|
||||||
|
|
||||||
|
@client.tree.command(name="pause", description="Pause the current song.")
|
||||||
|
async def pause(interaction: discord.Interaction):
|
||||||
|
await pause_audio(interaction)
|
||||||
|
|
||||||
|
@client.tree.command(name="resume", description="Resume the paused song.")
|
||||||
|
async def resume(interaction: discord.Interaction):
|
||||||
|
await resume_audio(interaction)
|
||||||
|
|
||||||
|
@client.tree.command(name="repeat", description="Set the repeat mode for playback.")
|
||||||
|
@app_commands.describe(mode="Repeat mode: 'one', 'all', or 'off'")
|
||||||
|
async def repeat(interaction: discord.Interaction, mode: str):
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
valid_modes = ["one", "all", "off"]
|
||||||
|
|
||||||
|
if mode not in valid_modes:
|
||||||
|
await interaction.response.send_message(f"❌ **Invalid mode. Use one of: {', '.join(valid_modes)}.**")
|
||||||
|
return
|
||||||
|
|
||||||
|
repeat_modes[guild_id] = mode
|
||||||
|
await interaction.response.send_message(f"🔄 **Repeat mode set to:** {mode.capitalize()}.")
|
||||||
|
|
||||||
|
@client.tree.command(name="volume", description="Set playback volume.")
|
||||||
|
async def volume(interaction: discord.Interaction, level: int):
|
||||||
|
await set_volume(interaction, level)
|
||||||
|
|
||||||
|
@client.tree.command(name="247", description="Enable or disable 24/7 mode.")
|
||||||
|
@app_commands.describe(mode="on/off")
|
||||||
|
async def _247(interaction: discord.Interaction, mode: str):
|
||||||
|
guild_id = interaction.guild.id
|
||||||
|
if mode.lower() == "on":
|
||||||
|
_247_mode[guild_id] = True
|
||||||
|
await interaction.response.send_message("✅ **24/7 mode enabled. Amber will stay in the voice channel even when idle.**")
|
||||||
|
elif mode.lower() == "off":
|
||||||
|
_247_mode[guild_id] = False
|
||||||
|
await interaction.response.send_message("✅ **24/7 mode disabled. Amber will leave the voice channel when idle.**")
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("❌ **Invalid mode. Use 'on' or 'off'.**")
|
||||||
|
|
||||||
|
@client.tree.command(name="recommend", description="Get song recommendations based on your listening history.")
|
||||||
|
async def recommend(interaction: discord.Interaction):
|
||||||
|
user_id = interaction.user.id
|
||||||
|
recommendations = generate_recommendations(user_id)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🎵 Recommended Songs",
|
||||||
|
description="\n".join(recommendations),
|
||||||
|
color=discord.Color.green()
|
||||||
|
)
|
||||||
|
await interaction.response.send_message(embed=embed)
|
||||||
|
|
||||||
|
await client.tree.sync()
|
54
main.py
Normal file
54
main.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import discord
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from commands import setup_commands
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
TOKEN = os.getenv("DISCORD_TOKEN")
|
||||||
|
GUILD_ID = os.getenv("GUILD_ID")
|
||||||
|
|
||||||
|
# Validate Guild ID
|
||||||
|
if GUILD_ID:
|
||||||
|
try:
|
||||||
|
GUILD_ID = int(GUILD_ID)
|
||||||
|
except ValueError:
|
||||||
|
logging.error("Invalid GUILD_ID in .env file. It must be a numeric value.")
|
||||||
|
GUILD_ID = None
|
||||||
|
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
intents.messages = True
|
||||||
|
intents.message_content = True
|
||||||
|
intents.guilds = True
|
||||||
|
intents.voice_states = True
|
||||||
|
|
||||||
|
|
||||||
|
class AmberClient(discord.Client):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(intents=intents)
|
||||||
|
self.tree = discord.app_commands.CommandTree(self)
|
||||||
|
|
||||||
|
async def on_ready(self):
|
||||||
|
logging.info(f"Amber is online as {self.user}")
|
||||||
|
|
||||||
|
# Sync commands after the bot is fully ready
|
||||||
|
if GUILD_ID:
|
||||||
|
logging.info(f"Setting up commands for guild: {GUILD_ID}")
|
||||||
|
else:
|
||||||
|
logging.info("Setting up global commands (no guild ID specified).")
|
||||||
|
|
||||||
|
try:
|
||||||
|
await setup_commands(self, guild_id=GUILD_ID)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to setup commands: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
client = AmberClient()
|
||||||
|
|
||||||
|
if TOKEN:
|
||||||
|
client.run(TOKEN)
|
||||||
|
else:
|
||||||
|
logging.error("Bot token not found. Check your .env file.")
|
21
requirements.txt
Normal file
21
requirements.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
aiohappyeyeballs==2.4.4
|
||||||
|
aiohttp==3.11.11
|
||||||
|
aiosignal==1.3.2
|
||||||
|
async-timeout==5.0.1
|
||||||
|
attrs==25.1.0
|
||||||
|
certifi==2024.12.14
|
||||||
|
cffi==1.17.1
|
||||||
|
charset-normalizer==3.4.1
|
||||||
|
discord.py==2.4.0
|
||||||
|
frozenlist==1.5.0
|
||||||
|
idna==3.10
|
||||||
|
multidict==6.1.0
|
||||||
|
propcache==0.2.1
|
||||||
|
pycparser==2.22
|
||||||
|
PyNaCl==1.5.0
|
||||||
|
python-dotenv==1.0.1
|
||||||
|
requests==2.32.3
|
||||||
|
typing_extensions==4.12.2
|
||||||
|
urllib3==2.3.0
|
||||||
|
yarl==1.18.3
|
||||||
|
yt-dlp==2025.1.15
|
Loading…
x
Reference in New Issue
Block a user