Redid the code completely for Selena's music
This commit is contained in:
parent
ae3a610b33
commit
1b5587c958
22
config.py
22
config.py
@ -1,22 +0,0 @@
|
|||||||
from dotenv import load_dotenv
|
|
||||||
import os
|
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
config = {
|
|
||||||
'DISCORD_TOKEN': os.getenv('DISCORD_TOKEN'),
|
|
||||||
'GUILD_ID_1': int(os.getenv('DISCORD_GUILD_ID')),
|
|
||||||
'GUILD_ID_2': int(os.getenv('DISCORD_GUILD_ID_2')),
|
|
||||||
'DISCORD_CHANNEL_ID': int(os.getenv('DISCORD_CHANNEL_ID')),
|
|
||||||
'YOUTUBE_API_KEY': os.getenv('YOUTUBE_API_KEY'),
|
|
||||||
'TWITCH_CLIENT_ID': os.getenv('TWITCH_CLIENT_ID'),
|
|
||||||
'TWITCH_CLIENT_SECRET': os.getenv('TWITCH_CLIENT_SECRET'),
|
|
||||||
'BUNGIE_API_KEY': os.getenv('BUNGIE_API_KEY'),
|
|
||||||
'OAUTH_URL': os.getenv('OAUTH_URL'),
|
|
||||||
'OAUTH_CLIENT_ID': os.getenv('OAUTH_CLIENT_ID'),
|
|
||||||
'modules': {
|
|
||||||
'music': {'enabled': True},
|
|
||||||
'terms_privacy': {'enabled': True},
|
|
||||||
'data_privacy': {'enabled': True}
|
|
||||||
}
|
|
||||||
}
|
|
63
main.py
63
main.py
@ -1,59 +1,34 @@
|
|||||||
import discord
|
import discord
|
||||||
from config import config
|
from discord.ext import commands
|
||||||
import logging
|
import asyncio
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
# Load environment variables
|
||||||
|
load_dotenv()
|
||||||
|
TOKEN = os.getenv('DISCORD_TOKEN')
|
||||||
|
|
||||||
TOKEN = config['DISCORD_TOKEN']
|
# Import the Music module
|
||||||
GUILD_ID_1 = config['GUILD_ID_1']
|
from modules.music import Music
|
||||||
GUILD_ID_2 = config['GUILD_ID_2']
|
|
||||||
|
|
||||||
intents = discord.Intents.default()
|
intents = discord.Intents.default()
|
||||||
intents.message_content = True
|
intents.message_content = True # Required for accessing message content
|
||||||
|
|
||||||
|
|
||||||
class Selena(discord.Client):
|
class Selena(discord.Client):
|
||||||
def __init__(self):
|
def __init__(self, *, intents):
|
||||||
super().__init__(intents=intents)
|
super().__init__(intents=intents)
|
||||||
self.tree = discord.app_commands.CommandTree(self)
|
self.tree = discord.app_commands.CommandTree(self)
|
||||||
self.xp_module = None # Initialize as None
|
|
||||||
self.load_modules()
|
# Initialize modules
|
||||||
|
self.music = Music(self)
|
||||||
|
|
||||||
async def setup_hook(self):
|
async def setup_hook(self):
|
||||||
logging.info("Setting up modules...")
|
# Sync the app commands with Discord
|
||||||
await self.tree.sync(guild=discord.Object(id=GUILD_ID_1))
|
await self.tree.sync()
|
||||||
logging.info(f"Modules setup and commands synchronized to {GUILD_ID_1}")
|
|
||||||
await self.tree.sync(guild=discord.Object(id=GUILD_ID_2))
|
|
||||||
logging.info(f"Modules setup and commands synchronized to {GUILD_ID_2}")
|
|
||||||
# Call setup_hook for xp_module here
|
|
||||||
if self.xp_module:
|
|
||||||
await self.xp_module.setup_hook()
|
|
||||||
|
|
||||||
def load_modules(self):
|
|
||||||
if config['modules']['music']['enabled']:
|
|
||||||
from modules.music.music import Music
|
|
||||||
music = Music(self)
|
|
||||||
music.setup(self.tree)
|
|
||||||
logging.info("Music module loaded")
|
|
||||||
|
|
||||||
if config['modules']['terms_privacy']['enabled']:
|
|
||||||
from modules.admin.terms_privacy import TermsPrivacy
|
|
||||||
terms_privacy = TermsPrivacy(self)
|
|
||||||
terms_privacy.setup(self.tree)
|
|
||||||
logging.info("Terms and Privacy module loaded")
|
|
||||||
|
|
||||||
if config['modules']['data_privacy']['enabled']:
|
|
||||||
from modules.admin.data_privacy import DataPrivacy
|
|
||||||
data_privacy = DataPrivacy(self)
|
|
||||||
data_privacy.setup(self.tree)
|
|
||||||
logging.info("Data Privacy module loaded")
|
|
||||||
|
|
||||||
|
|
||||||
bot = Selena()
|
client = Selena(intents=intents)
|
||||||
|
|
||||||
|
# Run the bot
|
||||||
@bot.event
|
client.run(TOKEN)
|
||||||
async def on_ready():
|
|
||||||
logging.info(f'{bot.user.name} has connected to Discord!')
|
|
||||||
|
|
||||||
bot.run(TOKEN)
|
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord import app_commands
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
|
|
||||||
class DataPrivacy:
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
self.db_path = 'data/selena.db'
|
|
||||||
|
|
||||||
async def fetch_user_data(self, user_id):
|
|
||||||
conn = sqlite3.connect(self.db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("SELECT * FROM user_data WHERE user_id = ?", (user_id,))
|
|
||||||
data = cursor.fetchall()
|
|
||||||
conn.close()
|
|
||||||
return data
|
|
||||||
|
|
||||||
async def delete_user_data(self, user_id):
|
|
||||||
conn = sqlite3.connect(self.db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("DELETE FROM user_data WHERE user_id = ?", (user_id,))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
def setup(self, tree: app_commands.CommandTree):
|
|
||||||
@tree.command(name="request_data", description="Request your stored data")
|
|
||||||
async def request_data_command(interaction: discord.Interaction):
|
|
||||||
user_id = interaction.user.id
|
|
||||||
data = await self.fetch_user_data(user_id)
|
|
||||||
if data:
|
|
||||||
await interaction.response.send_message(f"Your data: {data}", ephemeral=True)
|
|
||||||
else:
|
|
||||||
await interaction.response.send_message("No data found for your user.", ephemeral=True)
|
|
||||||
|
|
||||||
@tree.command(name="delete_data", description="Request deletion of your stored data")
|
|
||||||
async def delete_data_command(interaction: discord.Interaction):
|
|
||||||
user_id = interaction.user.id
|
|
||||||
await self.delete_user_data(user_id)
|
|
||||||
await interaction.response.send_message("Your data has been deleted.", ephemeral=True)
|
|
||||||
|
|
||||||
if not tree.get_command("request_data"):
|
|
||||||
tree.add_command(request_data_command)
|
|
||||||
|
|
||||||
if not tree.get_command("delete_data"):
|
|
||||||
tree.add_command(delete_data_command)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
data_privacy = DataPrivacy(bot)
|
|
||||||
data_privacy.setup(bot.tree)
|
|
@ -1,73 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord import app_commands
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
|
|
||||||
class TermsPrivacy:
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
self.db_path = 'data/selena.db'
|
|
||||||
self.privacy_policy_url = "https://advtech92.github.io/selena-website/privacy_policy.html"
|
|
||||||
self.terms_of_service_url = "https://advtech92.github.io/selena-website/terms_of_service.html"
|
|
||||||
|
|
||||||
async def user_opt_out(self, user_id):
|
|
||||||
conn = sqlite3.connect(self.db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("INSERT INTO opt_out_users (user_id) VALUES (?)", (user_id,))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
async def user_opt_in(self, user_id):
|
|
||||||
conn = sqlite3.connect(self.db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("DELETE FROM opt_out_users WHERE user_id = ?", (user_id,))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
async def is_user_opted_out(self, user_id):
|
|
||||||
conn = sqlite3.connect(self.db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("SELECT 1 FROM opt_out_users WHERE user_id = ?", (user_id,))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
conn.close()
|
|
||||||
return result is not None
|
|
||||||
|
|
||||||
def setup(self, tree: app_commands.CommandTree):
|
|
||||||
@tree.command(name="privacy_policy", description="Show the privacy policy")
|
|
||||||
async def privacy_policy_command(interaction: discord.Interaction):
|
|
||||||
embed = discord.Embed(title="Privacy Policy", url=self.privacy_policy_url, description="Read our privacy policy.", color=discord.Color.blue())
|
|
||||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
||||||
|
|
||||||
@tree.command(name="terms_of_service", description="Show the terms of service")
|
|
||||||
async def terms_of_service_command(interaction: discord.Interaction):
|
|
||||||
embed = discord.Embed(title="Terms of Service", url=self.terms_of_service_url, description="Read our terms of service.", color=discord.Color.blue())
|
|
||||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
||||||
|
|
||||||
@tree.command(name="opt_out", description="Opt out of using the bot")
|
|
||||||
async def opt_out_command(interaction: discord.Interaction):
|
|
||||||
user_id = interaction.user.id
|
|
||||||
await self.user_opt_out(user_id)
|
|
||||||
await interaction.response.send_message("You have opted out of using the bot.", ephemeral=True)
|
|
||||||
|
|
||||||
@tree.command(name="opt_in", description="Opt back in to using the bot")
|
|
||||||
async def opt_in_command(interaction: discord.Interaction):
|
|
||||||
user_id = interaction.user.id
|
|
||||||
await self.user_opt_in(user_id)
|
|
||||||
await interaction.response.send_message("You have opted back in to using the bot.", ephemeral=True)
|
|
||||||
|
|
||||||
if not tree.get_command("privacy_policy"):
|
|
||||||
tree.add_command(privacy_policy_command)
|
|
||||||
|
|
||||||
if not tree.get_command("terms_of_service"):
|
|
||||||
tree.add_command(terms_of_service_command)
|
|
||||||
|
|
||||||
if not tree.get_command("opt_out"):
|
|
||||||
tree.add_command(opt_out_command)
|
|
||||||
|
|
||||||
if not tree.get_command("opt_in"):
|
|
||||||
tree.add_command(opt_in_command)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
terms_privacy = TermsPrivacy(bot)
|
|
||||||
terms_privacy.setup(bot.tree)
|
|
256
modules/music.py
Normal file
256
modules/music.py
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
import yt_dlp
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
class Music:
|
||||||
|
def __init__(self, client):
|
||||||
|
self.client = client
|
||||||
|
self.voice_clients = {}
|
||||||
|
self.music_queues = {}
|
||||||
|
self.current_tracks = {}
|
||||||
|
self.save_file = 'music_state.json'
|
||||||
|
self.volumes = {} # Store volume levels per guild
|
||||||
|
self.default_volume = 0.5 # Default volume level (50%)
|
||||||
|
|
||||||
|
# Load saved state for auto-resume
|
||||||
|
self.load_music_state()
|
||||||
|
|
||||||
|
# Register app commands
|
||||||
|
self.register_commands()
|
||||||
|
|
||||||
|
def register_commands(self):
|
||||||
|
@app_commands.command(name='play', description='Play a song by title and artist')
|
||||||
|
async def play(interaction: discord.Interaction, *, query: str):
|
||||||
|
await self.play(interaction, query)
|
||||||
|
|
||||||
|
@app_commands.command(name='pause', description='Pause the current song')
|
||||||
|
async def pause(interaction: discord.Interaction):
|
||||||
|
await self.pause(interaction)
|
||||||
|
|
||||||
|
@app_commands.command(name='resume', description='Resume the paused song')
|
||||||
|
async def resume(interaction: discord.Interaction):
|
||||||
|
await self.resume(interaction)
|
||||||
|
|
||||||
|
@app_commands.command(name='skip', description='Skip the current song')
|
||||||
|
async def skip(interaction: discord.Interaction):
|
||||||
|
await self.skip(interaction)
|
||||||
|
|
||||||
|
@app_commands.command(name='stop', description='Stop playback and clear the queue')
|
||||||
|
async def stop(interaction: discord.Interaction):
|
||||||
|
await self.stop(interaction)
|
||||||
|
|
||||||
|
@app_commands.command(name='volume', description='Set the playback volume')
|
||||||
|
@app_commands.describe(level='Volume level between 0 and 100')
|
||||||
|
async def volume(interaction: discord.Interaction, level: int):
|
||||||
|
await self.set_volume(interaction, level)
|
||||||
|
|
||||||
|
# Add commands to the client's tree
|
||||||
|
self.client.tree.add_command(play)
|
||||||
|
self.client.tree.add_command(pause)
|
||||||
|
self.client.tree.add_command(resume)
|
||||||
|
self.client.tree.add_command(skip)
|
||||||
|
self.client.tree.add_command(stop)
|
||||||
|
self.client.tree.add_command(volume)
|
||||||
|
|
||||||
|
async def play(self, interaction: discord.Interaction, query: str):
|
||||||
|
await interaction.response.defer()
|
||||||
|
|
||||||
|
guild_id = interaction.guild_id
|
||||||
|
|
||||||
|
# Check if the user is in a voice channel
|
||||||
|
if interaction.user.voice is None:
|
||||||
|
await interaction.followup.send("You must be connected to a voice channel to use this command.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Connect to the voice channel if not already connected
|
||||||
|
if guild_id not in self.voice_clients or not self.voice_clients[guild_id].is_connected():
|
||||||
|
channel = interaction.user.voice.channel
|
||||||
|
self.voice_clients[guild_id] = await channel.connect()
|
||||||
|
|
||||||
|
# Ensure volume is set
|
||||||
|
if guild_id not in self.volumes:
|
||||||
|
self.volumes[guild_id] = self.default_volume
|
||||||
|
|
||||||
|
await interaction.followup.send(f"🔍 **Searching for:** {query}")
|
||||||
|
|
||||||
|
# Search YouTube for the song
|
||||||
|
search_url = await self.search_youtube(query)
|
||||||
|
if not search_url:
|
||||||
|
await interaction.followup.send("❌ **Could not find the song on YouTube.**")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add URL to the music queue
|
||||||
|
if guild_id not in self.music_queues:
|
||||||
|
self.music_queues[guild_id] = []
|
||||||
|
self.music_queues[guild_id].append(search_url)
|
||||||
|
|
||||||
|
await interaction.followup.send(f"✅ **Added to queue:** {query}")
|
||||||
|
|
||||||
|
# If nothing is playing, start playing
|
||||||
|
if not self.voice_clients[guild_id].is_playing():
|
||||||
|
await self.play_next(guild_id)
|
||||||
|
|
||||||
|
async def search_youtube(self, query):
|
||||||
|
"""Searches YouTube for the query and returns the URL of the first result."""
|
||||||
|
ytdl_opts = {
|
||||||
|
'format': 'bestaudio/best',
|
||||||
|
'noplaylist': True,
|
||||||
|
'default_search': 'ytsearch',
|
||||||
|
'quiet': True,
|
||||||
|
'no_warnings': True,
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
with yt_dlp.YoutubeDL(ytdl_opts) as ytdl:
|
||||||
|
try:
|
||||||
|
info = await loop.run_in_executor(None, lambda: ytdl.extract_info(query, download=False))
|
||||||
|
if 'entries' in info:
|
||||||
|
# Take the first item from the search results
|
||||||
|
video = info['entries'][0]
|
||||||
|
else:
|
||||||
|
video = info
|
||||||
|
video_url = f"https://www.youtube.com/watch?v={video['id']}"
|
||||||
|
return video_url
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error searching YouTube: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def play_next(self, guild_id):
|
||||||
|
if guild_id not in self.music_queues or not self.music_queues[guild_id]:
|
||||||
|
await self.voice_clients[guild_id].disconnect()
|
||||||
|
return
|
||||||
|
|
||||||
|
url = self.music_queues[guild_id].pop(0)
|
||||||
|
self.current_tracks[guild_id] = url
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use yt_dlp to get audio source
|
||||||
|
ytdl_opts = {'format': 'bestaudio/best'}
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
with yt_dlp.YoutubeDL(ytdl_opts) as ytdl:
|
||||||
|
info = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=False))
|
||||||
|
audio_url = info['url']
|
||||||
|
title = info.get('title', 'Unknown Title')
|
||||||
|
webpage_url = info.get('webpage_url', url)
|
||||||
|
thumbnail = info.get('thumbnail')
|
||||||
|
|
||||||
|
# Prepare FFmpeg options
|
||||||
|
ffmpeg_opts = {
|
||||||
|
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
|
||||||
|
'options': '-vn',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create audio source using FFmpegPCMAudio
|
||||||
|
source = discord.FFmpegPCMAudio(audio_url, **ffmpeg_opts)
|
||||||
|
volume = self.volumes.get(guild_id, self.default_volume)
|
||||||
|
source = discord.PCMVolumeTransformer(source, volume=volume)
|
||||||
|
|
||||||
|
# Play audio
|
||||||
|
self.voice_clients[guild_id].play(source, after=lambda e: self.after_song(e, guild_id))
|
||||||
|
|
||||||
|
# Send an embedded message indicating the song is now playing
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Now Playing 🎵",
|
||||||
|
description=f"[{title}]({webpage_url})",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
if thumbnail:
|
||||||
|
embed.set_thumbnail(url=thumbnail)
|
||||||
|
|
||||||
|
channel = self.voice_clients[guild_id].channel
|
||||||
|
text_channel = channel.guild.system_channel or channel.guild.text_channels[0]
|
||||||
|
await text_channel.send(embed=embed)
|
||||||
|
|
||||||
|
# Save state for auto-resume
|
||||||
|
self.save_music_state()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error during playback: {e}")
|
||||||
|
await self.voice_clients[guild_id].disconnect()
|
||||||
|
|
||||||
|
def after_song(self, error, guild_id):
|
||||||
|
if error:
|
||||||
|
print(f"Error: {error}")
|
||||||
|
coro = self.play_next(guild_id)
|
||||||
|
asyncio.run_coroutine_threadsafe(coro, self.client.loop)
|
||||||
|
|
||||||
|
async def pause(self, interaction: discord.Interaction):
|
||||||
|
guild_id = interaction.guild_id
|
||||||
|
if guild_id in self.voice_clients and self.voice_clients[guild_id].is_playing():
|
||||||
|
self.voice_clients[guild_id].pause()
|
||||||
|
await interaction.response.send_message("⏸️ **Music paused.**")
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("❌ **No music is playing.**")
|
||||||
|
|
||||||
|
async def resume(self, interaction: discord.Interaction):
|
||||||
|
guild_id = interaction.guild_id
|
||||||
|
if guild_id in self.voice_clients and self.voice_clients[guild_id].is_paused():
|
||||||
|
self.voice_clients[guild_id].resume()
|
||||||
|
await interaction.response.send_message("▶️ **Music resumed.**")
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("❌ **No music is paused.**")
|
||||||
|
|
||||||
|
async def skip(self, interaction: discord.Interaction):
|
||||||
|
guild_id = interaction.guild_id
|
||||||
|
if guild_id in self.voice_clients and self.voice_clients[guild_id].is_playing():
|
||||||
|
self.voice_clients[guild_id].stop()
|
||||||
|
await interaction.response.send_message("⏭️ **Skipped current song.**")
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("❌ **No music is playing.**")
|
||||||
|
|
||||||
|
async def stop(self, interaction: discord.Interaction):
|
||||||
|
guild_id = interaction.guild_id
|
||||||
|
if guild_id in self.voice_clients:
|
||||||
|
self.voice_clients[guild_id].stop()
|
||||||
|
self.music_queues[guild_id] = []
|
||||||
|
await self.voice_clients[guild_id].disconnect()
|
||||||
|
await interaction.response.send_message("🛑 **Playback stopped and queue cleared.**")
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message("❌ **No music is playing.**")
|
||||||
|
|
||||||
|
async def set_volume(self, interaction: discord.Interaction, level: int):
|
||||||
|
guild_id = interaction.guild_id
|
||||||
|
|
||||||
|
# Validate volume level
|
||||||
|
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 # Convert to a 0.0 - 1.0 scale
|
||||||
|
self.volumes[guild_id] = volume
|
||||||
|
|
||||||
|
# Adjust volume if something is playing
|
||||||
|
if guild_id in self.voice_clients and self.voice_clients[guild_id].is_playing():
|
||||||
|
current_source = self.voice_clients[guild_id].source
|
||||||
|
if isinstance(current_source, discord.PCMVolumeTransformer):
|
||||||
|
current_source.volume = volume
|
||||||
|
else:
|
||||||
|
# Wrap the existing source with PCMVolumeTransformer
|
||||||
|
self.voice_clients[guild_id].source = discord.PCMVolumeTransformer(current_source, volume=volume)
|
||||||
|
|
||||||
|
await interaction.response.send_message(f"🔊 **Volume set to {level}%.**")
|
||||||
|
|
||||||
|
def save_music_state(self):
|
||||||
|
state = {
|
||||||
|
'current_tracks': self.current_tracks,
|
||||||
|
'music_queues': self.music_queues,
|
||||||
|
'volumes': self.volumes
|
||||||
|
}
|
||||||
|
with open(self.save_file, 'w') as f:
|
||||||
|
json.dump(state, f)
|
||||||
|
|
||||||
|
def load_music_state(self):
|
||||||
|
if os.path.exists(self.save_file):
|
||||||
|
with open(self.save_file, 'r') as f:
|
||||||
|
state = json.load(f)
|
||||||
|
self.current_tracks = state.get('current_tracks', {})
|
||||||
|
self.music_queues = state.get('music_queues', {})
|
||||||
|
self.volumes = state.get('volumes', {})
|
||||||
|
else:
|
||||||
|
self.current_tracks = {}
|
||||||
|
self.music_queues = {}
|
||||||
|
self.volumes = {}
|
@ -1,183 +0,0 @@
|
|||||||
import discord
|
|
||||||
from discord.ext import commands
|
|
||||||
from discord import app_commands
|
|
||||||
from yt_dlp import YoutubeDL
|
|
||||||
import os
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
YTDL_OPTIONS = {
|
|
||||||
'format': 'bestaudio',
|
|
||||||
'noplaylist': 'True',
|
|
||||||
}
|
|
||||||
|
|
||||||
FFMPEG_OPTIONS = {
|
|
||||||
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
|
|
||||||
'options': '-vn'
|
|
||||||
}
|
|
||||||
|
|
||||||
ytdl = YoutubeDL(YTDL_OPTIONS)
|
|
||||||
|
|
||||||
|
|
||||||
class Music(commands.Cog):
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.bot = bot
|
|
||||||
self.queue = []
|
|
||||||
self.is_playing = False
|
|
||||||
self.volume = 0.3
|
|
||||||
self.loop = False # Initialize loop state
|
|
||||||
self.current_song = None # Track the current song
|
|
||||||
|
|
||||||
async def join(self, interaction: discord.Interaction):
|
|
||||||
channel = interaction.user.voice.channel
|
|
||||||
if interaction.guild.voice_client is None:
|
|
||||||
await channel.connect(self_deaf=True) # Join the channel deafened
|
|
||||||
await interaction.followup.send("Joined the voice channel.")
|
|
||||||
else:
|
|
||||||
await interaction.followup.send("Already in a voice channel.")
|
|
||||||
|
|
||||||
async def leave(self, interaction: discord.Interaction):
|
|
||||||
if interaction.guild.voice_client:
|
|
||||||
await interaction.guild.voice_client.disconnect()
|
|
||||||
await interaction.followup.send("Left the voice channel.")
|
|
||||||
else:
|
|
||||||
await interaction.followup.send("Not connected to a voice channel.")
|
|
||||||
|
|
||||||
async def play(self, interaction: discord.Interaction, search: str):
|
|
||||||
if not interaction.guild.voice_client:
|
|
||||||
await self.join(interaction)
|
|
||||||
|
|
||||||
info = ytdl.extract_info(f"ytsearch:{search}", download=False)['entries'][0]
|
|
||||||
url = info['url']
|
|
||||||
|
|
||||||
if interaction.guild.voice_client.is_playing():
|
|
||||||
self.queue.append((url, info['title']))
|
|
||||||
await interaction.followup.send(f'Queued: {info["title"]}')
|
|
||||||
else:
|
|
||||||
self.current_song = (url, info['title'])
|
|
||||||
source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(url, **FFMPEG_OPTIONS), volume=self.volume)
|
|
||||||
interaction.guild.voice_client.play(source, after=lambda e: self.bot.loop.create_task(self.play_next(interaction)))
|
|
||||||
self.is_playing = True
|
|
||||||
await interaction.followup.send(f'Playing: {info["title"]}')
|
|
||||||
|
|
||||||
async def play_next(self, interaction: discord.Interaction):
|
|
||||||
if self.loop and self.current_song: # If loop is active, repeat the current song
|
|
||||||
url, title = self.current_song
|
|
||||||
source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(url, **FFMPEG_OPTIONS), volume=self.volume)
|
|
||||||
interaction.guild.voice_client.play(source, after=lambda e: self.bot.loop.create_task(self.play_next(interaction)))
|
|
||||||
await interaction.followup.send(f'Repeating: {title}')
|
|
||||||
elif self.queue:
|
|
||||||
url, title = self.queue.pop(0)
|
|
||||||
self.current_song = (url, title)
|
|
||||||
source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(url, **FFMPEG_OPTIONS), volume=self.volume)
|
|
||||||
interaction.guild.voice_client.play(source, after=lambda e: self.bot.loop.create_task(self.play_next(interaction)))
|
|
||||||
await interaction.followup.send(f'Playing next: {title}')
|
|
||||||
else:
|
|
||||||
self.is_playing = False
|
|
||||||
|
|
||||||
async def pause(self, interaction: discord.Interaction):
|
|
||||||
if interaction.guild.voice_client.is_playing():
|
|
||||||
interaction.guild.voice_client.pause()
|
|
||||||
await interaction.followup.send("Paused the song.")
|
|
||||||
else:
|
|
||||||
await interaction.followup.send("No song is currently playing.")
|
|
||||||
|
|
||||||
async def resume(self, interaction: discord.Interaction):
|
|
||||||
if interaction.guild.voice_client.is_paused():
|
|
||||||
interaction.guild.voice_client.resume()
|
|
||||||
await interaction.followup.send("Resumed the song.")
|
|
||||||
else:
|
|
||||||
await interaction.followup.send("The song is not paused.")
|
|
||||||
|
|
||||||
async def stop(self, interaction: discord.Interaction):
|
|
||||||
if interaction.guild.voice_client.is_playing():
|
|
||||||
interaction.guild.voice_client.stop()
|
|
||||||
self.queue = []
|
|
||||||
self.current_song = None
|
|
||||||
await interaction.followup.send("Stopped the song.")
|
|
||||||
else:
|
|
||||||
await interaction.followup.send("No song is currently playing.")
|
|
||||||
|
|
||||||
async def set_volume(self, interaction: discord.Interaction, volume: float):
|
|
||||||
self.volume = volume
|
|
||||||
if interaction.guild.voice_client and interaction.guild.voice_client.source:
|
|
||||||
interaction.guild.voice_client.source.volume = self.volume
|
|
||||||
await interaction.followup.send(f"Volume set to {volume * 100}%.")
|
|
||||||
else:
|
|
||||||
await interaction.followup.send("No audio source found.")
|
|
||||||
|
|
||||||
async def toggle_loop(self, interaction: discord.Interaction):
|
|
||||||
self.loop = not self.loop # Toggle the loop state
|
|
||||||
state = "enabled" if self.loop else "disabled"
|
|
||||||
await interaction.followup.send(f"Loop has been {state}.")
|
|
||||||
|
|
||||||
def setup(self, tree: discord.app_commands.CommandTree):
|
|
||||||
@tree.command(name="join", description="Join the voice channel")
|
|
||||||
async def join_command(interaction: discord.Interaction):
|
|
||||||
await interaction.response.defer()
|
|
||||||
await self.join(interaction)
|
|
||||||
|
|
||||||
@tree.command(name="leave", description="Leave the voice channel")
|
|
||||||
async def leave_command(interaction: discord.Interaction):
|
|
||||||
await interaction.response.defer()
|
|
||||||
await self.leave(interaction)
|
|
||||||
|
|
||||||
@tree.command(name="play", description="Play a song from YouTube")
|
|
||||||
async def play_command(interaction: discord.Interaction, search: str):
|
|
||||||
await interaction.response.defer()
|
|
||||||
await self.play(interaction, search)
|
|
||||||
|
|
||||||
@tree.command(name="pause", description="Pause the current song")
|
|
||||||
async def pause_command(interaction: discord.Interaction):
|
|
||||||
await interaction.response.defer()
|
|
||||||
await self.pause(interaction)
|
|
||||||
|
|
||||||
@tree.command(name="resume", description="Resume the paused song")
|
|
||||||
async def resume_command(interaction: discord.Interaction):
|
|
||||||
await interaction.response.defer()
|
|
||||||
await self.resume(interaction)
|
|
||||||
|
|
||||||
@tree.command(name="stop", description="Stop the current song")
|
|
||||||
async def stop_command(interaction: discord.Interaction):
|
|
||||||
await interaction.response.defer()
|
|
||||||
await self.stop(interaction)
|
|
||||||
|
|
||||||
@tree.command(name="volume", description="Set the volume (0 to 1)")
|
|
||||||
async def volume_command(interaction: discord.Interaction, volume: float):
|
|
||||||
await interaction.response.defer()
|
|
||||||
await self.set_volume(interaction, volume)
|
|
||||||
|
|
||||||
@tree.command(name="loop", description="Toggle loop for the current song")
|
|
||||||
async def loop_command(interaction: discord.Interaction):
|
|
||||||
await interaction.response.defer()
|
|
||||||
await self.toggle_loop(interaction)
|
|
||||||
|
|
||||||
if not tree.get_command("play"):
|
|
||||||
tree.add_command(play_command)
|
|
||||||
|
|
||||||
if not tree.get_command("pause"):
|
|
||||||
tree.add_command(pause_command)
|
|
||||||
|
|
||||||
if not tree.get_command("resume"):
|
|
||||||
tree.add_command(resume_command)
|
|
||||||
|
|
||||||
if not tree.get_command("stop"):
|
|
||||||
tree.add_command(stop_command)
|
|
||||||
|
|
||||||
if not tree.get_command("volume"):
|
|
||||||
tree.add_command(volume_command)
|
|
||||||
|
|
||||||
if not tree.get_command("loop"):
|
|
||||||
tree.add_command(loop_command)
|
|
||||||
|
|
||||||
if not tree.get_command("join"):
|
|
||||||
tree.add_command(join_command)
|
|
||||||
|
|
||||||
if not tree.get_command("leave"):
|
|
||||||
tree.add_command(leave_command)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
|
||||||
music = Music(bot)
|
|
||||||
music.setup(bot.tree)
|
|
1
music_state.json
Normal file
1
music_state.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"current_tracks": {"1161168803888107550": "https://www.youtube.com/watch?v=fnlJw9H0xAM", "1142517462529757184": "https://www.youtube.com/watch?v=fnlJw9H0xAM"}, "music_queues": {"1161168803888107550": [], "1142517462529757184": []}, "volumes": {"1161168803888107550": 0.5, "1142517462529757184": 0.5}}
|
Loading…
x
Reference in New Issue
Block a user