diff --git a/commands/voice.py b/commands/voice.py index 8d2fcf5..602c2da 100644 --- a/commands/voice.py +++ b/commands/voice.py @@ -4,7 +4,7 @@ import arguments import commands import utils import youtubedl -from state import client, player_current, player_queue +from state import client, players async def queue_or_play(message): @@ -12,8 +12,8 @@ async def queue_or_play(message): if not command_allowed(message): return - if message.guild.id not in player_queue: - player_queue[message.guild.id] = [] + if message.guild.id not in players: + players[message.guild.id] = youtubedl.QueuedPlayer() tokens = commands.tokenize(message.content) parser = arguments.ArgumentParser( @@ -62,30 +62,31 @@ async def queue_or_play(message): return if args.clear: - player_queue[message.guild.id] = [] + players[message.guild.id].queue.clear() await utils.add_check_reaction(message) return elif i := args.remove_index: try: - queued = player_queue[message.guild.id][i - 1] - del player_queue[message.guild.id][i - 1] - await utils.reply(message, f"**x** `{queued['player'].title}`") + queued = players[message.guild.id].queue[i - 1] + del players[message.guild.id].queue[i - 1] + await utils.reply(message, f"**x** {queued.format()}") except: await utils.reply(message, "invalid index!") elif args.remove_title or args.remove_queuer: targets = [] - for queued in player_queue[message.guild.id]: + for queued in players[message.guild.id].queue: if t := args.remove_title: - if t in queued["player"].title: + if t in queued.player.title: targets.append(queued) + continue if q := args.remove_queuer: - if q == queued["queuer"]: + if q == queued.queuer: targets.append(queued) - if not args.remove_multiple: + if args.remove_multiple: targets = targets[:1] + for target in targets: - if target in player_queue[message.guild.id]: - player_queue[message.guild.id].remove(target) + players[message.guild.id].queue.remove(target) await utils.reply( message, f"removed **{len(targets)}** queued {'song' if len(targets) == 1 else 'songs'}", @@ -104,67 +105,64 @@ async def queue_or_play(message): ) return - player_queue[message.guild.id].insert( - 0, {"player": player, "queuer": message.author.id} - ) + queued = youtubedl.QueuedSong(player, message.author.id) + players[message.guild.id].queue_add(queued) if ( not message.guild.voice_client.is_playing() and not message.guild.voice_client.is_paused() ): - await utils.reply(message, f"**0.** `{player.title}`") + await utils.reply(message, f"**0.** {queued.format()}") play_next(message) else: await utils.reply( message, - f"**+** `{player.title}`", + f"**+** {queued.format()}", ) else: - if message.guild.voice_client: - if tokens[0].lower() == "play": - message.guild.voice_client.resume() - await utils.reply( - message, - "resumed!", - ) - else: - generate_currently_playing = ( - lambda: f"**0.** {'(paused) ' if message.guild.voice_client.is_paused() else ''}`{message.guild.voice_client.source.title}` (<@{player_current[message.guild.id]['queuer']}>)" - ) - if ( - not player_queue[message.guild.id] - and not message.guild.voice_client.source - ): - await utils.reply( - message, - "nothing is playing or queued!", - ) - elif not player_queue[message.guild.id]: - await utils.reply(message, generate_currently_playing()) - elif not message.guild.voice_client.source: - await utils.reply( - message, - generate_queue_list(player_queue[message.guild.id]), - ) - else: - await utils.reply( - message, - generate_currently_playing() - + "\n" - + generate_queue_list(player_queue[message.guild.id]), - ) - else: + if tokens[0].lower() == "play": + message.guild.voice_client.resume() await utils.reply( message, - "nothing is currently queued!", + "resumed!", ) + else: + currently_playing = ( + lambda: f"**0.** {'(paused) ' if message.guild.voice_client.is_paused() else ''} {players[message.guild.id].current.format(with_queuer=True)}" + ) + queue_list = lambda: "\n".join( + [ + f"**{i + 1}.** {queued.format(with_queuer=True, hide_preview=True)}" + for i, queued in enumerate(players[message.guild.id].queue) + ] + ) + if ( + not players[message.guild.id].queue + and not message.guild.voice_client.source + ): + await utils.reply( + message, + "nothing is playing or queued!", + ) + elif not players[message.guild.id].queue: + await utils.reply(message, currently_playing()) + elif not message.guild.voice_client.source: + await utils.reply( + message, + queue_list(), + ) + else: + await utils.reply( + message, + currently_playing() + "\n" + queue_list(), + ) async def skip(message): if not command_allowed(message): return - if not player_queue[message.guild.id]: + if not players[message.guild.id].queue: message.guild.voice_client.stop() await utils.reply( message, @@ -193,22 +191,28 @@ async def resume(message): if not command_allowed(message): return - message.guild.voice_client.resume() - await utils.reply( - message, - "resumed!", - ) + 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 - message.guild.voice_client.pause() - await utils.reply( - message, - "paused!", - ) + 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): @@ -233,7 +237,7 @@ async def volume(message): if not message.guild.voice_client.source: await utils.reply( message, - f"there is no player currently active!", + f"nothing is playing!", ) return @@ -247,14 +251,19 @@ async def volume(message): await utils.add_check_reaction(message) +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): message.guild.voice_client.stop() - if player_queue[message.guild.id]: - queued = player_queue[message.guild.id][0] - del player_queue[message.guild.id][0] - player_current[message.guild.id] = queued + if players[message.guild.id].queue: + queued = players[message.guild.id].queue_pop() message.guild.voice_client.play( - queued["player"], after=lambda _: play_next(message) if not once else None + queued.player, after=lambda e: play_after_callback(e, message, once) ) @@ -270,12 +279,3 @@ def command_allowed(message): if not message.guild.voice_client: return False return message.author.voice.channel.id == message.guild.voice_client.channel.id - - -def generate_queue_list(queue: list): - return "\n".join( - [ - f"**{i + 1}.** `{queued['player'].title}` (<@{queued['queuer']}>)" - for i, queued in enumerate(queue) - ] - ) diff --git a/constants.py b/constants.py index 52c4a3e..2b07a3b 100644 --- a/constants.py +++ b/constants.py @@ -13,6 +13,7 @@ RELOADABLE_MODULES = [ "constants", "core", "events", + "player", "utils", "voice", "youtubedl", diff --git a/core.py b/core.py index ab805ae..2c100ac 100644 --- a/core.py +++ b/core.py @@ -30,6 +30,9 @@ async def on_message(message): ) return + if message.guild.id not in command_locks: + command_locks[message.guild.id] = asyncio.Lock() + C = commands.Command try: match matched[0]: @@ -95,8 +98,6 @@ async def on_message(message): case C.LEAVE: await commands.voice.leave(message) case C.QUEUE | C.PLAY: - if message.guild.id not in command_locks: - command_locks[message.guild.id] = asyncio.Lock() async with command_locks[message.guild.id]: await commands.voice.queue_or_play(message) case C.SKIP: diff --git a/state.py b/state.py index 9ab6bd9..562673a 100644 --- a/state.py +++ b/state.py @@ -2,8 +2,7 @@ import time import disnake -player_queue = {} -player_current = {} +players = {} command_locks = {} intents = disnake.Intents.default() diff --git a/youtubedl.py b/youtubedl.py index ea2833f..297efff 100644 --- a/youtubedl.py +++ b/youtubedl.py @@ -14,7 +14,9 @@ class YTDLSource(disnake.PCMVolumeTransformer): self, source: disnake.AudioSource, *, data: dict[str, Any], volume: float = 0.5 ): super().__init__(source, volume) + print(data) self.title = data.get("title") + self.original_url = data.get("original_url") @classmethod async def from_url( @@ -41,6 +43,33 @@ class YTDLSource(disnake.PCMVolumeTransformer): ) +class QueuedPlayer: + def __init__(self): + self.queue = [] + self.current = None + + def queue_pop(self): + popped = self.queue[0] + del self.queue[0] + self.current = popped + return popped + + def queue_add(self, item): + self.queue.append(item) + + +class QueuedSong: + def __init__(self, player, queuer): + self.player = player + self.queuer = queuer + + def format(self, with_queuer=False, hide_preview=False): + return ( + f"[`{self.player.title}`]({'<' if hide_preview else ''}{self.player.original_url}{'>' if hide_preview else ''})" + + (f" (<@{self.queuer}>)" if with_queuer else "") + ) + + def __reload_module__(): global ytdl ytdl = yt_dlp.YoutubeDL(constants.YTDL_OPTIONS)