From 8ee5d01bf6e0d61732b3ad40b12de6e1a1b98818 Mon Sep 17 00:00:00 2001 From: ErrorNoInternet Date: Tue, 7 Jan 2025 15:22:03 -0500 Subject: [PATCH] refactor(commands/voice): split into separate files --- commands/voice/__init__.py | 19 ++ commands/voice/channel.py | 19 ++ commands/voice/playback.py | 90 ++++++++++ commands/voice/playing.py | 75 ++++++++ commands/{voice.py => voice/queue.py} | 247 +------------------------- commands/voice/utils.py | 85 +++++++++ events.py | 4 +- 7 files changed, 293 insertions(+), 246 deletions(-) create mode 100644 commands/voice/__init__.py create mode 100644 commands/voice/channel.py create mode 100644 commands/voice/playback.py create mode 100644 commands/voice/playing.py rename commands/{voice.py => voice/queue.py} (51%) create mode 100644 commands/voice/utils.py diff --git a/commands/voice/__init__.py b/commands/voice/__init__.py new file mode 100644 index 0000000..58a0dba --- /dev/null +++ b/commands/voice/__init__.py @@ -0,0 +1,19 @@ +from .channel import join, leave +from .playback import fast_forward, pause, resume, volume +from .playing import playing +from .queue import queue_or_play, skip +from .utils import remove_queued + +__all__ = [ + "join", + "leave", + "fast_forward", + "playing", + "queue_or_play", + "skip", + "resume", + "pause", + "skip", + "remove_queued", + "volume", +] diff --git a/commands/voice/channel.py b/commands/voice/channel.py new file mode 100644 index 0000000..4ef6e6e --- /dev/null +++ b/commands/voice/channel.py @@ -0,0 +1,19 @@ +import utils + +from .utils import command_allowed + + +async def join(message): + if message.guild.voice_client: + return await message.guild.voice_client.move_to(message.channel) + + await message.channel.connect() + await utils.add_check_reaction(message) + + +async def leave(message): + if not command_allowed(message): + return + + await message.guild.voice_client.disconnect() + await utils.add_check_reaction(message) diff --git a/commands/voice/playback.py b/commands/voice/playback.py new file mode 100644 index 0000000..4ac108a --- /dev/null +++ b/commands/voice/playback.py @@ -0,0 +1,90 @@ +import arguments +import commands +import utils + +from .utils import command_allowed + + +async def resume(message): + if not command_allowed(message): + return + + if message.guild.voice_client.is_paused(): + message.guild.voice_client.resume() + await utils.add_check_reaction(message) + else: + await utils.reply( + message, + "nothing is paused!", + ) + + +async def pause(message): + if not command_allowed(message): + return + + if message.guild.voice_client.is_playing(): + message.guild.voice_client.pause() + await utils.add_check_reaction(message) + else: + await utils.reply( + message, + "nothing is playing!", + ) + + +async def fast_forward(message): + tokens = commands.tokenize(message.content) + parser = arguments.ArgumentParser(tokens[0], "fast forward audio playback") + parser.add_argument( + "seconds", + type=lambda v: arguments.range_type(v, min=0, max=300), + help="the amount of seconds to fast forward", + ) + if not (args := await parser.parse_args(message, tokens)): + return + + if not command_allowed(message): + return + + if not message.guild.voice_client.source: + await utils.reply(message, "nothing is playing!") + return + + message.guild.voice_client.pause() + message.guild.voice_client.source.original.fast_forward(args.seconds) + message.guild.voice_client.resume() + + await utils.add_check_reaction(message) + + +async def volume(message): + tokens = commands.tokenize(message.content) + parser = arguments.ArgumentParser(tokens[0], "get or set the current volume level") + parser.add_argument( + "volume", + nargs="?", + type=lambda v: arguments.range_type(v, min=0, max=150), + help="the volume level (0 - 150)", + ) + if not (args := await parser.parse_args(message, tokens)): + return + + if not command_allowed(message, immutable=True): + return + + if not message.guild.voice_client.source: + await utils.reply(message, "nothing is playing!") + return + + if args.volume is None: + await utils.reply( + message, + f"{int(message.guild.voice_client.source.volume * 100)}", + ) + else: + if not command_allowed(message): + return + + message.guild.voice_client.source.volume = float(args.volume) / 100.0 + await utils.add_check_reaction(message) diff --git a/commands/voice/playing.py b/commands/voice/playing.py new file mode 100644 index 0000000..f8a5ac0 --- /dev/null +++ b/commands/voice/playing.py @@ -0,0 +1,75 @@ +import disnake +import disnake_paginator + +import arguments +import commands +import constants +import utils +import youtubedl +from state import players + +from .utils import command_allowed + + +async def playing(message): + tokens = commands.tokenize(message.content) + parser = arguments.ArgumentParser( + tokens[0], "get information about the currently playing song" + ) + parser.add_argument( + "-d", + "--description", + action="store_true", + help="get the description", + ) + if not (args := await parser.parse_args(message, tokens)): + return + + if not command_allowed(message, immutable=True): + return + + if source := message.guild.voice_client.source: + if args.description: + if description := source.description: + paginator = disnake_paginator.ButtonPaginator( + invalid_user_function=utils.invalid_user_handler, + color=constants.EMBED_COLOR, + title=source.title, + segments=disnake_paginator.split(description), + ) + for embed in paginator.embeds: + embed.url = source.original_url + await paginator.start(utils.MessageInteractionWrapper(message)) + else: + await utils.reply( + message, + source.description or "no description found!", + ) + return + + progress = source.original.progress / source.duration + embed = disnake.Embed( + color=constants.EMBED_COLOR, + title=source.title, + url=source.original_url, + description=f"{'⏸️ ' if message.guild.voice_client.is_paused() else ''}" + f"`[{'#'*int(progress * constants.BAR_LENGTH)}{'-'*int((1 - progress) * constants.BAR_LENGTH)}]` " + f"**{youtubedl.format_duration(int(source.original.progress))}** / **{youtubedl.format_duration(source.duration)}** (**{round(progress * 100)}%**)", + ) + embed.add_field(name="Volume", value=f"{int(source.volume*100)}%") + embed.add_field(name="Views", value=f"{source.view_count:,}") + embed.add_field( + name="Queuer", + value=players[message.guild.id].current.trigger_message.author.mention, + ) + embed.set_image(source.thumbnail_url) + + await utils.reply( + message, + embed=embed, + ) + else: + await utils.reply( + message, + "nothing is playing!", + ) diff --git a/commands/voice.py b/commands/voice/queue.py similarity index 51% rename from commands/voice.py rename to commands/voice/queue.py index e395953..7251a8e 100644 --- a/commands/voice.py +++ b/commands/voice/queue.py @@ -10,6 +10,9 @@ import utils import youtubedl from state import client, players +from .playback import resume +from .utils import command_allowed, ensure_joined, play_next + async def queue_or_play(message, edited=False): if message.guild.id not in players: @@ -227,95 +230,6 @@ async def queue_or_play(message, edited=False): ) -async def playing(message): - tokens = commands.tokenize(message.content) - parser = arguments.ArgumentParser( - tokens[0], "get information about the currently playing song" - ) - parser.add_argument( - "-d", - "--description", - action="store_true", - help="get the description", - ) - if not (args := await parser.parse_args(message, tokens)): - return - - if not command_allowed(message, immutable=True): - return - - if source := message.guild.voice_client.source: - if args.description: - if description := source.description: - paginator = disnake_paginator.ButtonPaginator( - invalid_user_function=utils.invalid_user_handler, - color=constants.EMBED_COLOR, - title=source.title, - segments=disnake_paginator.split(description), - ) - for embed in paginator.embeds: - embed.url = source.original_url - await paginator.start(utils.MessageInteractionWrapper(message)) - else: - await utils.reply( - message, - source.description or "no description found!", - ) - return - - progress = source.original.progress / source.duration - embed = disnake.Embed( - color=constants.EMBED_COLOR, - title=source.title, - url=source.original_url, - description=f"{'⏸️ ' if message.guild.voice_client.is_paused() else ''}" - f"`[{'#'*int(progress * constants.BAR_LENGTH)}{'-'*int((1 - progress) * constants.BAR_LENGTH)}]` " - f"**{youtubedl.format_duration(int(source.original.progress))}** / **{youtubedl.format_duration(source.duration)}** (**{round(progress * 100)}%**)", - ) - embed.add_field(name="Volume", value=f"{int(source.volume*100)}%") - embed.add_field(name="Views", value=f"{source.view_count:,}") - embed.add_field( - name="Queuer", - value=players[message.guild.id].current.trigger_message.author.mention, - ) - embed.set_image(source.thumbnail_url) - - await utils.reply( - message, - embed=embed, - ) - else: - await utils.reply( - message, - "nothing is playing!", - ) - - -async def fast_forward(message): - tokens = commands.tokenize(message.content) - parser = arguments.ArgumentParser(tokens[0], "fast forward audio playback") - parser.add_argument( - "seconds", - type=lambda v: arguments.range_type(v, min=0, max=300), - help="the amount of seconds to fast forward", - ) - if not (args := await parser.parse_args(message, tokens)): - return - - if not command_allowed(message): - return - - if not message.guild.voice_client.source: - await utils.reply(message, "nothing is playing!") - return - - message.guild.voice_client.pause() - message.guild.voice_client.source.original.fast_forward(args.seconds) - message.guild.voice_client.resume() - - await utils.add_check_reaction(message) - - async def skip(message): tokens = commands.tokenize(message.content) parser = arguments.ArgumentParser(tokens[0], "skip the currently playing song") @@ -345,158 +259,3 @@ async def skip(message): await utils.add_check_reaction(message) if not message.guild.voice_client.source: play_next(message) - - -async def join(message): - if message.guild.voice_client: - return await message.guild.voice_client.move_to(message.channel) - - await message.channel.connect() - await utils.add_check_reaction(message) - - -async def leave(message): - if not command_allowed(message): - return - - await message.guild.voice_client.disconnect() - await utils.add_check_reaction(message) - - -async def resume(message): - if not command_allowed(message): - return - - if message.guild.voice_client.is_paused(): - message.guild.voice_client.resume() - await utils.add_check_reaction(message) - else: - await utils.reply( - message, - "nothing is paused!", - ) - - -async def pause(message): - if not command_allowed(message): - return - - if message.guild.voice_client.is_playing(): - message.guild.voice_client.pause() - await utils.add_check_reaction(message) - else: - await utils.reply( - message, - "nothing is playing!", - ) - - -async def volume(message): - tokens = commands.tokenize(message.content) - parser = arguments.ArgumentParser(tokens[0], "get or set the current volume level") - parser.add_argument( - "volume", - nargs="?", - type=lambda v: arguments.range_type(v, min=0, max=150), - help="the volume level (0 - 150)", - ) - if not (args := await parser.parse_args(message, tokens)): - return - - if not command_allowed(message, immutable=True): - return - - if not message.guild.voice_client.source: - await utils.reply(message, "nothing is playing!") - return - - if args.volume is None: - await utils.reply( - message, - f"{int(message.guild.voice_client.source.volume * 100)}", - ) - else: - if not command_allowed(message): - return - - message.guild.voice_client.source.volume = float(args.volume) / 100.0 - await utils.add_check_reaction(message) - - -def delete_queued(messages): - if messages[0].guild.id not in players: - return - - if len(players[messages[0].guild.id].queue) == 0: - return - - found = [] - for message in messages: - for queued in players[message.guild.id].queue: - if queued.trigger_message.id == message.id: - found.append(queued) - for queued in found: - players[messages[0].guild.id].queue.remove(queued) - - -def play_after_callback(e, message, once): - if e: - print(f"player error: {e}") - if not once: - play_next(message) - - -def play_next(message, once=False, first=False): - if not message.guild.voice_client: - return - - message.guild.voice_client.stop() - if message.guild.id in players and players[message.guild.id].queue: - queued = players[message.guild.id].queue_pop() - try: - message.guild.voice_client.play( - queued.player, after=lambda e: play_after_callback(e, message, once) - ) - except disnake.opus.OpusNotLoaded: - utils.load_opus() - message.guild.voice_client.play( - queued.player, after=lambda e: play_after_callback(e, message, once) - ) - - embed = disnake.Embed( - color=constants.EMBED_COLOR, - title=queued.player.title, - url=queued.player.original_url, - description=f"`[{'-'*constants.BAR_LENGTH}]` **{youtubedl.format_duration(0)}** / **{youtubedl.format_duration(queued.player.duration)}**", - ) - embed.add_field(name="Volume", value=f"{int(queued.player.volume*100)}%") - embed.add_field(name="Views", value=f"{queued.player.view_count:,}") - embed.add_field( - name="Queuer", - value=players[message.guild.id].current.trigger_message.author.mention, - ) - embed.set_image(queued.player.thumbnail_url) - - if first and len(players[message.guild.id].queue) == 0: - client.loop.create_task(utils.reply(message, embed=embed)) - else: - client.loop.create_task(utils.channel_send(message, embed=embed)) - - -async def ensure_joined(message): - if message.guild.voice_client is None: - if message.author.voice: - await message.author.voice.channel.connect() - else: - await utils.reply(message, "you are not connected to a voice channel!") - - -def command_allowed(message, immutable=False): - if not message.guild.voice_client: - return - if immutable: - return message.channel.id == message.guild.voice_client.channel.id - else: - if not message.author.voice: - return False - return message.author.voice.channel.id == message.guild.voice_client.channel.id diff --git a/commands/voice/utils.py b/commands/voice/utils.py new file mode 100644 index 0000000..336a910 --- /dev/null +++ b/commands/voice/utils.py @@ -0,0 +1,85 @@ +import constants +import disnake +import youtubedl +from state import client, players + +import utils + + +def play_after_callback(e, message, once): + if e: + print(f"player error: {e}") + if not once: + play_next(message) + + +def play_next(message, once=False, first=False): + if not message.guild.voice_client: + return + + message.guild.voice_client.stop() + if message.guild.id in players and players[message.guild.id].queue: + queued = players[message.guild.id].queue_pop() + try: + message.guild.voice_client.play( + queued.player, after=lambda e: play_after_callback(e, message, once) + ) + except disnake.opus.OpusNotLoaded: + utils.load_opus() + message.guild.voice_client.play( + queued.player, after=lambda e: play_after_callback(e, message, once) + ) + + embed = disnake.Embed( + color=constants.EMBED_COLOR, + title=queued.player.title, + url=queued.player.original_url, + description=f"`[{'-'*constants.BAR_LENGTH}]` **{youtubedl.format_duration(0)}** / **{youtubedl.format_duration(queued.player.duration)}**", + ) + embed.add_field(name="Volume", value=f"{int(queued.player.volume*100)}%") + embed.add_field(name="Views", value=f"{queued.player.view_count:,}") + embed.add_field( + name="Queuer", + value=players[message.guild.id].current.trigger_message.author.mention, + ) + embed.set_image(queued.player.thumbnail_url) + + if first and len(players[message.guild.id].queue) == 0: + client.loop.create_task(utils.reply(message, embed=embed)) + else: + client.loop.create_task(utils.channel_send(message, embed=embed)) + + +def remove_queued(messages): + if messages[0].guild.id not in players: + return + + if len(players[messages[0].guild.id].queue) == 0: + return + + found = [] + for message in messages: + for queued in players[message.guild.id].queue: + if queued.trigger_message.id == message.id: + found.append(queued) + for queued in found: + players[messages[0].guild.id].queue.remove(queued) + + +async def ensure_joined(message): + if message.guild.voice_client is None: + if message.author.voice: + await message.author.voice.channel.connect() + else: + await utils.reply(message, "you are not connected to a voice channel!") + + +def command_allowed(message, immutable=False): + if not message.guild.voice_client: + return + if immutable: + return message.channel.id == message.guild.voice_client.channel.id + else: + if not message.author.voice: + return False + return message.author.voice.channel.id == message.guild.voice_client.channel.id diff --git a/events.py b/events.py index d104520..f787d0f 100644 --- a/events.py +++ b/events.py @@ -20,7 +20,7 @@ def prepare(): async def on_bulk_message_delete(messages): - commands.voice.delete_queued(messages) + commands.voice.remove_queued(messages) async def on_message(message): @@ -28,7 +28,7 @@ async def on_message(message): async def on_message_delete(message): - commands.voice.delete_queued([message]) + commands.voice.remove_queued([message]) async def on_message_edit(before, after):