diff --git a/poetry.lock b/poetry.lock index 230884e0..2bfe0733 100644 --- a/poetry.lock +++ b/poetry.lock @@ -268,6 +268,17 @@ version = "2.8.1" [package.dependencies] six = ">=1.5" +[[package]] +category = "main" +description = "A streaming multipart parser for Python" +name = "python-multipart" +optional = false +python-versions = "*" +version = "0.0.5" + +[package.dependencies] +six = ">=1.4.0" + [[package]] category = "main" description = "We have made you a wrapper you can't refuse" @@ -366,6 +377,10 @@ version = "^2.8.4" optional = true version = "^1.3.0" +[package.dependencies.python-multipart] +optional = true +version = "^0.0.5" + [package.dependencies.python_telegram_bot] optional = true version = "^12.2.0" @@ -399,14 +414,14 @@ alchemy_easy = ["sqlalchemy (^1.3.10)", "psycopg2_binary (^2.8.4)"] alchemy_hard = ["sqlalchemy (^1.3.10)", "psycopg2 (^2.8.4)"] bard = ["ffmpeg_python (~0.2.0)", "youtube-dl"] coloredlogs = ["coloredlogs (^10.0)"] -constellation = ["starlette (^0.12.13)", "uvicorn (^0.10.7)"] +constellation = ["starlette (^0.12.13)", "uvicorn (^0.10.7)", "python-multipart (^0.0.5)"] discord = ["discord.py", "pynacl (^1.3.0)"] herald = ["websockets (^8.1)"] sentry = ["sentry_sdk (~0.13.2)"] telegram = ["python_telegram_bot (^12.2.0)"] [package.source] -reference = "f1803f2ffd5507827be1a430d2b09996decfc098" +reference = "8ff0731aa5bd167a59f342472bfe2d7e74daf1ca" type = "git" url = "https://github.com/Steffo99/royalnet/" @@ -787,6 +802,9 @@ python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, ] +python-multipart = [ + {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, +] python-telegram-bot = [ {file = "python-telegram-bot-12.2.0.tar.gz", hash = "sha256:346d42771c2b23384c59f5f41e05bd7e801a0ce118d8dcb95209bb73d5f694c5"}, {file = "python_telegram_bot-12.2.0-py2.py3-none-any.whl", hash = "sha256:3beee89cba3bc3217566c96199f04776dd25f541ac8992da27fd247b2d208a14"}, diff --git a/royalpack/commands/__init__.py b/royalpack/commands/__init__.py index 2bbbcda1..2f41196d 100644 --- a/royalpack/commands/__init__.py +++ b/royalpack/commands/__init__.py @@ -11,7 +11,7 @@ from .videochannel import VideochannelCommand # from .trivia import TriviaCommand # from .matchmaking import MatchmakingCommand # from .pause import PauseCommand -# from .play import PlayCommand +from .play import PlayCommand # from .playmode import PlaymodeCommand # from .queue import QueueCommand # from .skip import SkipCommand @@ -39,7 +39,7 @@ available_commands = [ # TriviaCommand, # MatchmakingCommand, # PauseCommand, - # PlayCommand, + PlayCommand, # PlaymodeCommand, # QueueCommand, # SkipCommand, diff --git a/royalpack/commands/play.py b/royalpack/commands/play.py index 44e7823d..5889d4d8 100644 --- a/royalpack/commands/play.py +++ b/royalpack/commands/play.py @@ -1,11 +1,9 @@ -import typing import pickle -import datetime +import base64 import discord +from typing import * from royalnet.commands import * -from royalnet.utils import asyncify -from royalnet.audio import YtdlDiscord -from royalnet.bots import DiscordBot +from royalnet.utils import * class PlayCommand(Command): @@ -15,65 +13,41 @@ class PlayCommand(Command): description: str = "Aggiunge un url alla coda della chat vocale." - syntax = "[ [guild] ] {url}" - - @staticmethod - async def _legacy_play_handler(bot: "DiscordBot", guild_name: typing.Optional[str], url: str): - """Handle a play Royalnet request. That is, add audio to a PlayMode.""" - # Find the matching guild - if guild_name: - guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(guild_name) - else: - guilds = bot.client.guilds - if len(guilds) == 0: - raise CommandError("Server non trovato.") - if len(guilds) > 1: - raise CommandError("Il nome del server è ambiguo.") - guild = list(bot.client.guilds)[0] - # Ensure the guild has a PlayMode before adding the file to it - if not bot.music_data.get(guild): - raise CommandError("Il bot non è in nessun canale vocale.") - # Create url - ytdl_args = { - "format": "bestaudio/best", - "outtmpl": f"./downloads/{datetime.datetime.now().timestamp()}_%(title)s.%(ext)s" - } - # Start downloading - dfiles: typing.List[YtdlDiscord] = await asyncify(YtdlDiscord.create_from_url, url, **ytdl_args) - await bot.add_to_music_data(dfiles, guild) - # Create response dictionary - response = { - "videos": [{ - "title": dfile.info.title, - "discord_embed_pickle": str(pickle.dumps(dfile.info.to_discord_embed())) - } for dfile in dfiles] - } - return response - - _event_name = "_legacy_play" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_play_handler) + syntax = "{url}" async def run(self, args: CommandArgs, data: CommandData) -> None: - guild_name, url = args.match(r"(?:\[(.+)])?\s*?") - if not (url.startswith("http://") or url.startswith("https://")): - raise CommandError(f"Il comando [c]{self.interface.prefix}play[/c] funziona solo per riprodurre file da" - f" un URL.\n" - f"Se vuoi cercare un video, usa [c]{self.interface.prefix}youtube[/c] o" - f" [c]{self.interface.prefix}soundcloud[/c]!") - response: dict = await self.interface.call_herald_action("discord", self._event_name, { - "guild_name": guild_name, - "url": url - }) - if len(response["videos"]) == 0: - raise CommandError(f"Nessun file trovato.") - for video in response["videos"]: + url = args.joined() + # if not (url.startswith("http://") or url.startswith("https://")): + # raise CommandError(f"Il comando [c]{self.interface.prefix}play[/c] funziona solo per riprodurre file da" + # f" un URL.\n" + # f"Se vuoi cercare un video, come misura temporanea puoi usare " + # f"[c]ytsearch:nomevideo[/c] o [c]scsearch:nomevideo[/c] come url.") + if self.interface.name == "discord": + message: discord.Message = data.message + guild: discord.Guild = message.guild + guild_id: Optional[int] = guild.id + else: + guild_id = None + response: Dict[str, Any] = await self.interface.call_herald_event("discord", "discord_play", + url=url, guild_id=guild_id) + + too_long: List[Dict[str, Any]] = response["too_long"] + if len(too_long) > 0: + await data.reply(f"⚠ {len(too_long)} file non {'è' if len(too_long) == 1 else 'sono'}" + f" stat{'o' if len(too_long) == 1 else 'i'} scaricat{'o' if len(too_long) == 1 else 'i'}" + f" perchè durava{'' if len(too_long) == 1 else 'no'}" + f" più di [c]{self.config['Play']['max_song_duration']}[/c] secondi.") + + added: List[Dict[str, Any]] = response["added"] + if len(added) > 0: + reply = f"▶️ Aggiunt{'o' if len(added) == 1 else 'i'} {len(added)} file alla coda:\n" if self.interface.name == "discord": - # This is one of the unsafest things ever - embed = pickle.loads(eval(video["discord_embed_pickle"])) - await data.message.channel.send(content="▶️ Aggiunto alla coda:", embed=embed) + await data.reply(reply) + for item in added: + embed = pickle.loads(base64.b64decode(bytes(item["stringified_base64_pickled_discord_embed"], + encoding="ascii"))) + # noinspection PyUnboundLocalVariable + await message.channel.send(embed=embed) else: - await data.reply(f"▶️ Aggiunto alla coda: [i]{video['title']}[/i]") + reply += numberemojiformat([a["title"] for a in added]) + await data.reply(reply) diff --git a/royalpack/commands/smecds.py b/royalpack/commands/smecds.py index e5a54efa..7789cdf1 100644 --- a/royalpack/commands/smecds.py +++ b/royalpack/commands/smecds.py @@ -1,7 +1,6 @@ import typing import random from royalnet.commands import * -from royalnet.utils import safeformat class SmecdsCommand(Command): @@ -62,8 +61,6 @@ class SmecdsCommand(Command): "dello Slime God", "del salassato", "della salsa", "di Senjougahara", "di Sugar", "della Stampa", "della Stampante"] - _SMECDS = "🤔 Secondo me, è colpa {ds}." - async def run(self, args: CommandArgs, data: CommandData) -> None: ds = random.sample(self._DS_LIST, 1)[0] - await data.reply(safeformat(self._SMECDS, ds=ds)) + await data.reply(f"🤔 Secondo me, è colpa {ds}.") diff --git a/royalpack/events/__init__.py b/royalpack/events/__init__.py index a86c2a2e..8299a307 100644 --- a/royalpack/events/__init__.py +++ b/royalpack/events/__init__.py @@ -1,11 +1,13 @@ # Imports go here! from .discord_cv import DiscordCvEvent from .discord_summon import DiscordSummonEvent +from .discord_play import DiscordPlayEvent # Enter the commands of your Pack here! available_events = [ DiscordCvEvent, DiscordSummonEvent, + DiscordPlayEvent, ] # noinspection PyUnreachableCode diff --git a/royalpack/events/discord_play.py b/royalpack/events/discord_play.py new file mode 100644 index 00000000..5f6575d2 --- /dev/null +++ b/royalpack/events/discord_play.py @@ -0,0 +1,61 @@ +import discord +import pickle +import base64 +import datetime +from typing import * +from royalnet.commands import * +from royalnet.serf.discord import * +from royalnet.bard import * + + +class DiscordPlayEvent(Event): + name = "discord_play" + + async def run(self, + url: str, + guild_id: Optional[int] = None, + **kwargs) -> dict: + if not isinstance(self.serf, DiscordSerf): + raise UnsupportedError() + client: discord.Client = self.serf.client + if len(self.serf.voice_players) == 1: + voice_player: VoicePlayer = self.serf.voice_players[0] + else: + if guild_id is None: + # TODO: trovare un modo per riprodurre canzoni su più server da Telegram + raise InvalidInputError("Non so in che Server riprodurre questo file...\n" + "Invia il comando su Discord, per favore!") + guild: discord.Guild = client.get_guild(guild_id) + if guild is None: + raise InvalidInputError("Impossibile trovare il Server specificato.") + voice_player: VoicePlayer = self.serf.find_voice_player(guild) + if voice_player is None: + raise UserError("Il bot non è in nessun canale vocale.\n" + "Evocalo prima con [c]summon[/c]!") + ytds = await YtdlDiscord.from_url(url) + added: List[YtdlDiscord] = [] + too_long: List[YtdlDiscord] = [] + if isinstance(voice_player.playing, PlayableYTDQueue): + for ytd in ytds: + if ytd.info.duration >= datetime.timedelta(seconds=self.config["Play"]["max_song_duration"]): + too_long.append(ytd) + continue + await ytd.convert_to_pcm() + added.append(ytd) + voice_player.playing.contents.append(ytd) + if not voice_player.voice_client.is_playing(): + await voice_player.start() + else: + raise CommandError(f"Non so come aggiungere musica a [c]{voice_player.playing.__class__.__qualname__}[/c]!") + return { + "added": [{ + "title": ytd.info.title, + "stringified_base64_pickled_discord_embed": str(base64.b64encode(pickle.dumps(ytd.embed())), + encoding="ascii") + } for ytd in added], + "too_long": [{ + "title": ytd.info.title, + "stringified_base64_pickled_discord_embed": str(base64.b64encode(pickle.dumps(ytd.embed())), + encoding="ascii") + } for ytd in too_long] + } diff --git a/royalpack/events/discord_summon.py b/royalpack/events/discord_summon.py index 0400331c..646613c4 100644 --- a/royalpack/events/discord_summon.py +++ b/royalpack/events/discord_summon.py @@ -9,7 +9,7 @@ class DiscordSummonEvent(Event): name = "discord_summon" async def run(self, *, - channel_name: Optional[str] = None, + channel_name: str = "", channel_id: Optional[int] = None, guild_id: Optional[int] = None, user_id: Optional[int] = None, @@ -27,11 +27,11 @@ class DiscordSummonEvent(Event): else: member = None # From channel id - if channel_id: + if channel_id is not None: client: discord.Client = self.serf.client channel = client.get_channel(channel_id) # Find channel - elif channel_name is not None: + elif channel_name != "": # Find accessible_to accessible_to = [self.serf.client.user] if member is not None: