status bar in queue and cleaned up embeds, slash command on help
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands.context import Context
|
||||
from discord import app_commands
|
||||
import discord
|
||||
|
||||
import cogs.music.util as util
|
||||
import cogs.music.queue as queue
|
||||
@@ -74,31 +76,42 @@ class music(commands.Cog):
|
||||
await util.leave_vc(ctx)
|
||||
await ctx.message.add_reaction('👍')
|
||||
|
||||
@commands.command(
|
||||
help="Queues a song into the bot",
|
||||
aliases=['p'])
|
||||
async def play(self, ctx: Context, *, url=None):
|
||||
if url is None:
|
||||
raise commands.CommandError("Must provide a link or search query")
|
||||
elif ctx.guild is None:
|
||||
raise commands.CommandError("Command must be issued in a server")
|
||||
|
||||
# HYBRID COMMAND - works as both =play and /play
|
||||
@commands.hybrid_command(
|
||||
name="play",
|
||||
description="Queue a song to play")
|
||||
@app_commands.describe(query="YouTube URL, Spotify link, or search query")
|
||||
async def play(self, ctx: Context, *, query: str):
|
||||
"""Queues a song into the bot"""
|
||||
if ctx.guild is None:
|
||||
await ctx.send("❌ This command must be used in a server!", ephemeral=True)
|
||||
return
|
||||
|
||||
server = ctx.guild.id
|
||||
|
||||
# For slash commands, defer the response since fetching takes time
|
||||
if ctx.interaction:
|
||||
await ctx.defer()
|
||||
|
||||
await util.join_vc(ctx)
|
||||
await ctx.message.add_reaction('👍')
|
||||
|
||||
# Different responses for slash vs prefix
|
||||
if not ctx.interaction:
|
||||
await ctx.message.add_reaction('👍')
|
||||
|
||||
msg = await ctx.send("Fetching song(s)...")
|
||||
async with ctx.typing():
|
||||
#TODO potentially save requests before getting stream link
|
||||
# Grab video details such as title thumbnail duration
|
||||
audio = await translate.main(url, self.sp)
|
||||
|
||||
#TODO potentially save requests before getting stream link
|
||||
# Grab video details such as title thumbnail duration
|
||||
audio = await translate.main(query, self.sp)
|
||||
|
||||
await msg.delete()
|
||||
|
||||
if len(audio) == 0:
|
||||
await ctx.message.add_reaction('🚫')
|
||||
await ctx.send("Failed to find song!")
|
||||
if not ctx.interaction:
|
||||
await ctx.message.add_reaction('🚫')
|
||||
await ctx.send("❌ Failed to find song!")
|
||||
return
|
||||
|
||||
|
||||
@@ -128,16 +141,61 @@ class music(commands.Cog):
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Display the current music queue",
|
||||
aliases=['q', 'songs'])
|
||||
async def queue(self, ctx: Context):
|
||||
help="Queue a song to play next (top of queue)",
|
||||
aliases=['pt', 'pn', 'playnext'])
|
||||
async def playtop(self, ctx: Context, *, url=None):
|
||||
"""Queue a song at the top of the queue (plays next)"""
|
||||
if url is None:
|
||||
raise commands.CommandError("Must provide a link or search query")
|
||||
elif ctx.guild is None:
|
||||
raise commands.CommandError("Command must be issued in a server")
|
||||
|
||||
server = ctx.guild.id
|
||||
|
||||
await util.join_vc(ctx)
|
||||
await ctx.message.add_reaction('⏭️')
|
||||
|
||||
msg = await ctx.send("Fetching song(s) to play next...")
|
||||
async with ctx.typing():
|
||||
audio = await translate.main(url, self.sp)
|
||||
|
||||
await msg.delete()
|
||||
|
||||
if len(audio) == 0:
|
||||
await ctx.message.add_reaction('🚫')
|
||||
await ctx.send("Failed to find song!")
|
||||
return
|
||||
|
||||
# Add songs to TOP of queue (position 0, then 1, then 2...)
|
||||
for i, song in enumerate(audio):
|
||||
await queue.add_song(
|
||||
server,
|
||||
song,
|
||||
ctx.author.display_name,
|
||||
position=i) # Insert at top
|
||||
|
||||
# Show first song that was added
|
||||
audio[0]['position'] = 0
|
||||
await util.queue_message(ctx, audio[0])
|
||||
|
||||
if await queue.is_server_playing(server):
|
||||
return
|
||||
|
||||
await queue.update_server(server, True)
|
||||
await queue.play(ctx)
|
||||
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="queue",
|
||||
description="Display the current music queue")
|
||||
async def queue_cmd(self, ctx: Context):
|
||||
"""Display the current music queue"""
|
||||
server = ctx.guild
|
||||
|
||||
# Perform usual checks
|
||||
if server is None:
|
||||
raise commands.CommandError("Command must be issued in a server")
|
||||
|
||||
await ctx.send("❌ This command must be used in a server!", ephemeral=True)
|
||||
return
|
||||
|
||||
# Grab all songs from this server
|
||||
n, songs = await queue.grab_songs(server.id)
|
||||
@@ -151,42 +209,62 @@ class music(commands.Cog):
|
||||
await util.display_server_queue(ctx, songs, n)
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Skips the current song that is playing, can include number to skip more songs",
|
||||
aliases=['s'])
|
||||
async def skip(self, ctx: Context, n='1'):
|
||||
@commands.hybrid_command(
|
||||
name="skip",
|
||||
description="Skip the current song")
|
||||
@app_commands.describe(count="Number of songs to skip (default: 1)")
|
||||
async def skip(self, ctx: Context, count: int = 1):
|
||||
"""Skips the current song that is playing"""
|
||||
server = ctx.guild
|
||||
|
||||
if server is None:
|
||||
raise commands.CommandError("Command must be issued in a server")
|
||||
await ctx.send("❌ This command must be used in a server!", ephemeral=True)
|
||||
return
|
||||
|
||||
if ctx.voice_client is None:
|
||||
raise commands.CommandError("I'm not in a voice channel")
|
||||
await ctx.send("❌ I'm not in a voice channel!", ephemeral=True)
|
||||
return
|
||||
|
||||
if not n.isdigit():
|
||||
raise commands.CommandError("Please enter a number to skip")
|
||||
n = int(n)
|
||||
if count <= 0:
|
||||
await ctx.send("❌ Please enter a positive number!", ephemeral=True)
|
||||
return
|
||||
|
||||
if n <= 0:
|
||||
raise commands.CommandError("Please enter a positive number")
|
||||
|
||||
# Skip specificed number of songs
|
||||
for _ in range(n-1):
|
||||
await queue.pop(server.id, True)
|
||||
|
||||
# Safe to ignore error for now
|
||||
ctx.voice_client.stop()
|
||||
# Check loop mode
|
||||
loop_mode = await queue.get_loop_mode(server.id)
|
||||
|
||||
if loop_mode == 'song' and count == 1:
|
||||
# When looping song and skipping once, just restart it
|
||||
ctx.voice_client.stop()
|
||||
await ctx.send(f"⏭️ Restarting song (loop mode active)")
|
||||
else:
|
||||
# Skip specified number of songs
|
||||
for _ in range(count-1):
|
||||
await queue.pop(server.id, True, skip_mode=True)
|
||||
|
||||
# Last song gets skip_mode=True to handle loop properly
|
||||
if loop_mode != 'song':
|
||||
await queue.pop(server.id, True, skip_mode=True)
|
||||
|
||||
ctx.voice_client.stop()
|
||||
await ctx.send(f"⏭️ Skipped {count} song(s)")
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Toggle loop mode: off -> song -> queue -> off",
|
||||
aliases=['l', 'repeat'])
|
||||
@commands.hybrid_command(
|
||||
name="loop",
|
||||
description="Toggle loop mode")
|
||||
@app_commands.describe(mode="Loop mode: off, song, or queue")
|
||||
@app_commands.choices(mode=[
|
||||
app_commands.Choice(name="Off", value="off"),
|
||||
app_commands.Choice(name="Song", value="song"),
|
||||
app_commands.Choice(name="Queue", value="queue")
|
||||
])
|
||||
async def loop(self, ctx: Context, mode: str = None):
|
||||
"""Toggle between loop modes or set a specific mode"""
|
||||
server = ctx.guild
|
||||
|
||||
if server is None:
|
||||
raise commands.CommandError("Command must be issued in a server")
|
||||
await ctx.send("❌ This command must be used in a server!", ephemeral=True)
|
||||
return
|
||||
|
||||
current_mode = await queue.get_loop_mode(server.id)
|
||||
|
||||
@@ -202,7 +280,8 @@ class music(commands.Cog):
|
||||
# Set specific mode
|
||||
mode = mode.lower()
|
||||
if mode not in ['off', 'song', 'queue']:
|
||||
raise commands.CommandError("Loop mode must be: off, song, or queue")
|
||||
await ctx.send("❌ Loop mode must be: off, song, or queue", ephemeral=True)
|
||||
return
|
||||
new_mode = mode
|
||||
|
||||
await queue.set_loop_mode(server.id, new_mode)
|
||||
@@ -211,7 +290,7 @@ class music(commands.Cog):
|
||||
emojis = {'off': '⏹️', 'song': '🔂', 'queue': '🔁'}
|
||||
messages = {
|
||||
'off': 'Loop disabled',
|
||||
'song': 'Looping current song 🔂',
|
||||
'song': 'Looping current song 🔂 (skip to restart)',
|
||||
'queue': 'Looping entire queue 🔁'
|
||||
}
|
||||
|
||||
@@ -237,31 +316,30 @@ class music(commands.Cog):
|
||||
await ctx.send("🔀 Queue shuffled!")
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Set playback volume (0-200%)",
|
||||
aliases=['vol', 'v'])
|
||||
async def volume(self, ctx: Context, vol: str = None):
|
||||
@commands.hybrid_command(
|
||||
name="volume",
|
||||
description="Set playback volume")
|
||||
@app_commands.describe(level="Volume level (0-200%, default shows current)")
|
||||
async def volume(self, ctx: Context, level: int = None):
|
||||
"""Set or display the current volume"""
|
||||
server = ctx.guild
|
||||
|
||||
if server is None:
|
||||
raise commands.CommandError("Command must be issued in a server")
|
||||
await ctx.send("❌ This command must be used in a server!", ephemeral=True)
|
||||
return
|
||||
|
||||
if vol is None:
|
||||
if level is None:
|
||||
# Display current volume
|
||||
current_vol = await queue.get_volume(server.id)
|
||||
await ctx.send(f"🔊 Current volume: {current_vol}%")
|
||||
return
|
||||
|
||||
# Set volume
|
||||
if not vol.isdigit():
|
||||
raise commands.CommandError("Volume must be a number (0-200)")
|
||||
if level < 0 or level > 200:
|
||||
await ctx.send("❌ Volume must be between 0 and 200!", ephemeral=True)
|
||||
return
|
||||
|
||||
vol = int(vol)
|
||||
if vol < 0 or vol > 200:
|
||||
raise commands.CommandError("Volume must be between 0 and 200")
|
||||
|
||||
new_vol = await queue.set_volume(server.id, vol)
|
||||
new_vol = await queue.set_volume(server.id, level)
|
||||
|
||||
# Update the current playing song's volume if something is playing
|
||||
if ctx.voice_client and ctx.voice_client.source:
|
||||
@@ -280,15 +358,17 @@ class music(commands.Cog):
|
||||
await ctx.send(f"{emoji} Volume set to {new_vol}%")
|
||||
|
||||
|
||||
@commands.command(
|
||||
help="Apply audio effects to playback",
|
||||
aliases=['fx', 'filter'])
|
||||
@commands.hybrid_command(
|
||||
name="effect",
|
||||
description="Apply audio effects to playback")
|
||||
@app_commands.describe(effect_name="The audio effect to apply (leave empty to see list)")
|
||||
async def effect(self, ctx: Context, effect_name: str = None):
|
||||
"""Apply or list audio effects"""
|
||||
server = ctx.guild
|
||||
|
||||
if server is None:
|
||||
raise commands.CommandError("Command must be issued in a server")
|
||||
await ctx.send("❌ This command must be used in a server!", ephemeral=True)
|
||||
return
|
||||
|
||||
# If no effect specified, show current effect and list options
|
||||
if effect_name is None:
|
||||
@@ -298,18 +378,18 @@ class music(commands.Cog):
|
||||
|
||||
effects_list = '\n'.join([
|
||||
f"`{e}` - {queue.get_effect_description(e)}"
|
||||
for e in queue.list_all_effects()[:7] # Show first 7
|
||||
for e in queue.list_all_effects()[:9] # Show first 9
|
||||
])
|
||||
more_effects = '\n'.join([
|
||||
f"`{e}` - {queue.get_effect_description(e)}"
|
||||
for e in queue.list_all_effects()[7:] # Show rest
|
||||
for e in queue.list_all_effects()[9:] # Show rest
|
||||
])
|
||||
|
||||
await ctx.send(
|
||||
f"{emoji} **Current effect:** {current} - {desc}\n\n"
|
||||
f"**Available effects:**\n{effects_list}\n\n"
|
||||
f"**More effects:**\n{more_effects}\n\n"
|
||||
f"Use `=effect <name>` to apply an effect!"
|
||||
f"Use `=effect <name>` or `/effect <name>` to apply!"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -317,21 +397,24 @@ class music(commands.Cog):
|
||||
effect_name = effect_name.lower()
|
||||
|
||||
if effect_name not in queue.list_all_effects():
|
||||
raise commands.CommandError(
|
||||
f"Unknown effect! Use `=effect` to see available effects."
|
||||
await ctx.send(
|
||||
f"❌ Unknown effect! Use `/effect` to see available effects.",
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
await queue.set_effect(server.id, effect_name)
|
||||
|
||||
emoji = queue.get_effect_emoji(effect_name)
|
||||
desc = queue.get_effect_description(effect_name)
|
||||
|
||||
# Special warning for earrape
|
||||
if effect_name == 'earrape':
|
||||
# Special warning for earrape/deepfry
|
||||
if effect_name in ['earrape', 'deepfry']:
|
||||
await ctx.send(
|
||||
f"⚠️ **EARRAPE MODE ACTIVATED** ⚠️\n"
|
||||
f"RIP your eardrums. Effect will apply to next song.\n"
|
||||
f"Use `=effect none` to disable."
|
||||
f"⚠️ **{effect_name.upper()} MODE ACTIVATED** ⚠️\n"
|
||||
f"{desc}\n"
|
||||
f"Effect will apply to next song.\n"
|
||||
f"Use `/effect none` to disable."
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
@@ -342,4 +425,4 @@ class music(commands.Cog):
|
||||
|
||||
# If something is currently playing, notify about skip
|
||||
if ctx.voice_client and ctx.voice_client.is_playing():
|
||||
await ctx.send("💡 Tip: Use `=skip` to apply effect to current song immediately!")
|
||||
await ctx.send("💡 Tip: Use `/skip` to apply effect immediately!")
|
||||
|
||||
Reference in New Issue
Block a user