diff --git a/royalnet/audio/playmodes.py b/royalnet/audio/playmodes.py index 185bf4c7..d93ad243 100644 --- a/royalnet/audio/playmodes.py +++ b/royalnet/audio/playmodes.py @@ -47,6 +47,18 @@ class PlayMode: """Delete all :py:class:`royalnet.audio.RoyalPCMAudio` contained inside this PlayMode.""" raise NotImplementedError() + def queue_preview(self) -> typing.List[RoyalPCMAudio]: + """Display all the videos in the PlayMode as a list, if possible. + + To be used with `queue` commands, for example. + + Raises: + NotImplementedError: If a preview can't be generated. + + Returns: + A list of videos contained in the queue.""" + raise NotImplementedError() + class Playlist(PlayMode): """A video list. :py:class:`royalnet.audio.RoyalPCMAudio` played are removed from the list.""" @@ -85,6 +97,9 @@ class Playlist(PlayMode): while self.list: self.list.pop(0).delete() + def queue_preview(self) -> typing.List[RoyalPCMAudio]: + return self.list + class Pool(PlayMode): """A :py:class:`royalnet.audio.RoyalPCMAudio` pool. :py:class:`royalnet.audio.RoyalPCMAudio` are selected in random order and are not repeated until every song has been played at least once.""" @@ -126,3 +141,8 @@ class Pool(PlayMode): item.delete() self.pool = None self._pool_copy = None + + def queue_preview(self) -> typing.List[RoyalPCMAudio]: + preview_pool = self.pool.copy() + random.shuffle(preview_pool) + return preview_pool \ No newline at end of file diff --git a/royalnet/commands/__init__.py b/royalnet/commands/__init__.py index 272a3de9..4dcb2dd3 100644 --- a/royalnet/commands/__init__.py +++ b/royalnet/commands/__init__.py @@ -26,9 +26,11 @@ from .videochannel import VideochannelCommand from .missing import MissingCommand from .cv import CvCommand from .pause import PauseCommand +from .queue import QueueCommand __all__ = ["NullCommand", "PingCommand", "ShipCommand", "SmecdsCommand", "CiaoruoziCommand", "ColorCommand", "SyncCommand", "DiarioCommand", "RageCommand", "DateparserCommand", "AuthorCommand", "ReminderCommand", "KvactiveCommand", "KvCommand", "KvrollCommand", "VideoinfoCommand", "SummonCommand", "PlayCommand", - "SkipCommand", "PlaymodeCommand", "VideochannelCommand", "MissingCommand", "CvCommand", "PauseCommand"] + "SkipCommand", "PlaymodeCommand", "VideochannelCommand", "MissingCommand", "CvCommand", "PauseCommand", + "QueueCommand"] diff --git a/royalnet/commands/pause.py b/royalnet/commands/pause.py index a27a778d..95437ff4 100644 --- a/royalnet/commands/pause.py +++ b/royalnet/commands/pause.py @@ -10,6 +10,7 @@ if typing.TYPE_CHECKING: class PauseNH(NetworkHandler): message_type = "music_pause" + # noinspection PyProtectedMember @classmethod async def discord(cls, bot: "DiscordBot", data: dict): # Find the matching guild @@ -23,7 +24,7 @@ class PauseNH(NetworkHandler): guild = list(bot.music_data)[0] # Set the currently playing source as ended voice_client: discord.VoiceClient = bot.client.find_voice_client_by_guild(guild) - if not voice_client.is_playing(): + if not (voice_client.is_playing() or voice_client.is_paused()): raise NoneFoundError("Nothing to pause") # Toggle pause resume = voice_client._player.is_paused() diff --git a/royalnet/commands/queue.py b/royalnet/commands/queue.py new file mode 100644 index 00000000..6ea7f982 --- /dev/null +++ b/royalnet/commands/queue.py @@ -0,0 +1,76 @@ +import typing +import pickle +from ..network import Request, ResponseSuccess +from ..utils import Command, Call, NetworkHandler, numberemojiformat +from ..error import TooManyFoundError, NoneFoundError +if typing.TYPE_CHECKING: + from ..bots import DiscordBot + + +class QueueNH(NetworkHandler): + message_type = "music_queue" + + @classmethod + async def discord(cls, bot: "DiscordBot", data: dict): + # Find the matching guild + if data["guild_name"]: + guild = bot.client.find_guild_by_name(data["guild_name"]) + else: + if len(bot.music_data) == 0: + raise NoneFoundError("No voice clients active") + if len(bot.music_data) > 1: + raise TooManyFoundError("Multiple guilds found") + guild = list(bot.music_data)[0] + # Check if the guild has a PlayMode + playmode = bot.music_data.get(guild) + if not playmode: + return ResponseSuccess({ + "type": None + }) + try: + queue = playmode.queue_preview() + except NotImplementedError: + return ResponseSuccess({ + "type": playmode.__class__.__name__ + }) + return ResponseSuccess({ + "type": playmode.__class__.__name__, + "queue": + { + "strings": [str(element.rpf.info) for element in queue], + "pickled_embeds": str(pickle.dumps([element.rpf.info.to_discord_embed() for element in queue])) + } + }) + + +class QueueCommand(Command): + + command_name = "queue" + command_description = "Visualizza un'anteprima della coda di riproduzione attuale." + command_syntax = "[ [guild] ]" + + network_handlers = [QueueNH] + + @classmethod + async def common(cls, call: Call): + guild, = call.args.match(r"(?:\[(.+)])?") + data = await call.net_request(Request("music_queue", {"guild_name": guild}), "discord") + if data["type"] is None: + await call.reply("ℹ️ Non c'è nessuna coda di riproduzione attiva al momento.") + return + elif "queue" not in data: + await call.reply(f"ℹ️ La coda di riproduzione attuale ([c]{data['type']}[/c]) non permette l'anteprima.") + return + if data["type"] == "Playlist": + message = f"ℹ️ Questa [c]Playlist[/c] contiene {len(data['queue'])} elementi, e i prossimi saranno:\n" + elif data["type"] == "Pool": + message = f"ℹ️ Questo [c]Pool[/c] contiene {len(data['queue'])} elementi, tra cui:\n" + else: + message = f"ℹ️ Il PlayMode attuale, [c]{data['type']}[/c], contiene {len(data['queue'])} elementi:\n" + if call.interface_name == "discord": + await call.reply(message) + for embed in pickle.loads(eval(data["queue"]["pickled_embeds"]))[:5]: + await call.channel.send(embed=embed) + else: + message += numberemojiformat(data["queue"]["strings"]) + await call.reply(message) diff --git a/royalnet/royalgames.py b/royalnet/royalgames.py index 46ebddfb..ba387c1c 100644 --- a/royalnet/royalgames.py +++ b/royalnet/royalgames.py @@ -20,7 +20,7 @@ log.setLevel(logging.WARNING) commands = [PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, DebugCreateCommand, SyncCommand, AuthorCommand, DiarioCommand, RageCommand, DateparserCommand, ReminderCommand, KvactiveCommand, KvCommand, KvrollCommand, VideoinfoCommand, SummonCommand, PlayCommand, SkipCommand, PlaymodeCommand, - VideochannelCommand, CvCommand, PauseCommand] + VideochannelCommand, CvCommand, PauseCommand, QueueCommand] address, port = "127.0.0.1", 1234 diff --git a/royalnet/utils/__init__.py b/royalnet/utils/__init__.py index cd1860b0..770b7196 100644 --- a/royalnet/utils/__init__.py +++ b/royalnet/utils/__init__.py @@ -9,7 +9,7 @@ from .safeformat import safeformat from .classdictjanitor import cdj from .sleepuntil import sleep_until from .networkhandler import NetworkHandler -from .formatters import andformat, plusformat, fileformat, ytdldateformat +from .formatters import andformat, plusformat, fileformat, ytdldateformat, numberemojiformat __all__ = ["asyncify", "Call", "Command", "safeformat", "cdj", "sleep_until", "plusformat", "CommandArgs", - "NetworkHandler", "andformat", "plusformat", "fileformat", "ytdldateformat"] + "NetworkHandler", "andformat", "plusformat", "fileformat", "ytdldateformat", "numberemojiformat"] diff --git a/royalnet/utils/formatters.py b/royalnet/utils/formatters.py index 7d52e872..1e4f92ee 100644 --- a/royalnet/utils/formatters.py +++ b/royalnet/utils/formatters.py @@ -58,3 +58,15 @@ def ytdldateformat(string: typing.Optional[str], separator: str = "-") -> str: if string is None: return "" return f"{string[0:4]}{separator}{string[4:6]}{separator}{string[6:8]}" + + +def numberemojiformat(l: typing.List[str]) -> str: + number_emojis = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"] + extra_emoji = "*️⃣" + result = "" + for index, element in enumerate(l): + try: + result += f"{number_emojis[index]} {element}\n" + except IndexError: + result += f"{extra_emoji} {element}\n" + return result