diff --git a/royalnet/audio/royalpcmaudio.py b/royalnet/audio/royalpcmaudio.py
index c8381535..ae883095 100644
--- a/royalnet/audio/royalpcmaudio.py
+++ b/royalnet/audio/royalpcmaudio.py
@@ -10,10 +10,15 @@ class RoyalPCMAudio(AudioSource):
self._file = open(self.rpf.audio_filename, "rb")
@staticmethod
- def create_from_url(url) -> typing.List["RoyalPCMAudio"]:
+ def create_from_url(url: str) -> typing.List["RoyalPCMAudio"]:
rpf_list = RoyalPCMFile.create_from_url(url)
return [RoyalPCMAudio(rpf) for rpf in rpf_list]
+ @staticmethod
+ def create_from_ytsearch(search: str, amount: int = 1) -> typing.List["RoyalPCMAudio"]:
+ rpf_list = RoyalPCMFile.create_from_ytsearch(search, amount)
+ return [RoyalPCMAudio(rpf) for rpf in rpf_list]
+
def is_opus(self):
return False
diff --git a/royalnet/audio/royalpcmfile.py b/royalnet/audio/royalpcmfile.py
index 2450b846..3c87998f 100644
--- a/royalnet/audio/royalpcmfile.py
+++ b/royalnet/audio/royalpcmfile.py
@@ -48,7 +48,14 @@ class RoyalPCMFile(YtdlFile):
@staticmethod
def create_from_url(url, **ytdl_args) -> typing.List["RoyalPCMFile"]:
info_list = YtdlInfo.create_from_url(url)
- return [RoyalPCMFile(info) for info in info_list]
+ return [RoyalPCMFile(info, **ytdl_args) for info in info_list]
+
+ @staticmethod
+ def create_from_ytsearch(search: str, amount: int = 1, **ytdl_args) -> typing.List["RoyalPCMFile"]:
+ """Search a string on YouTube and download the first amount videos found."""
+ url = f"ytsearch{amount}:{search}"
+ info_list = YtdlInfo.create_from_url(url)
+ return [RoyalPCMFile(info, **ytdl_args) for info in info_list]
@property
def _ytdl_filename(self):
diff --git a/royalnet/bots/discord.py b/royalnet/bots/discord.py
index 318a5ba8..ab2c1834 100644
--- a/royalnet/bots/discord.py
+++ b/royalnet/bots/discord.py
@@ -53,7 +53,9 @@ class DiscordBot(GenericBot):
.replace("[u]", "__") \
.replace("[/u]", "__") \
.replace("[c]", "`") \
- .replace("[/c]", "`")
+ .replace("[/c]", "`") \
+ .replace("[p]", "```") \
+ .replace("[/p]", "```")
await call.channel.send(escaped_text)
async def net_request(call, message: Message, destination: str):
@@ -203,11 +205,9 @@ class DiscordBot(GenericBot):
await self.client.connect()
# TODO: how to stop?
- async def add_to_music_data(self, url: str, guild: discord.Guild):
+ async def add_to_music_data(self, audio_sources: typing.List[discord.AudioSource], guild: discord.Guild):
"""Add a file to the corresponding music_data object."""
- log.debug(f"Adding {url} to music_data of {guild}")
guild_music_data = self.music_data[guild]
- audio_sources = await asyncify(RoyalPCMAudio.create_from_url, url)
for audio_source in audio_sources:
log.debug(f"Adding {audio_source} to music_data")
guild_music_data.add(audio_source)
diff --git a/royalnet/bots/telegram.py b/royalnet/bots/telegram.py
index f7238cdd..3bea21c0 100644
--- a/royalnet/bots/telegram.py
+++ b/royalnet/bots/telegram.py
@@ -48,7 +48,9 @@ class TelegramBot(GenericBot):
.replace("[u]", "") \
.replace("[/u]", "") \
.replace("[c]", "") \
- .replace("[/c]", "
")
+ .replace("[/c]", "") \
+ .replace("[p]", "
") \ + .replace("[/p]", "") await asyncify(call.channel.send_message, escaped_text, parse_mode="HTML") async def net_request(call, message: Message, destination: str): diff --git a/royalnet/commands/play.py b/royalnet/commands/play.py index e44c127c..8144164f 100644 --- a/royalnet/commands/play.py +++ b/royalnet/commands/play.py @@ -1,8 +1,11 @@ import typing import asyncio -from ..utils import Command, Call, NetworkHandler -from ..network import Message, RequestSuccessful +import youtube_dl +import ffmpeg +from ..utils import Command, Call, NetworkHandler, asyncify +from ..network import Message, RequestSuccessful, RequestError from ..error import TooManyFoundError, NoneFoundError +from ..audio import RoyalPCMAudio, YtdlInfo if typing.TYPE_CHECKING: from ..bots import DiscordBot @@ -16,6 +19,11 @@ class PlayMessage(Message): self.guild_name: typing.Optional[str] = guild_name +class PlaySuccessful(RequestSuccessful): + def __init__(self, info_list: typing.List[YtdlInfo]): + self.info_list: typing.List[YtdlInfo] = info_list + + class PlayNH(NetworkHandler): message_type = PlayMessage @@ -36,9 +44,12 @@ class PlayNH(NetworkHandler): # TODO: change Exception raise Exception("No music_data for this guild") # Start downloading - # noinspection PyAsyncCall - loop.create_task(bot.add_to_music_data(message.url, guild)) - return RequestSuccessful() + try: + audio_sources: typing.List[RoyalPCMAudio] = await asyncify(RoyalPCMAudio.create_from_url, message.url) + except youtube_dl.utils.DownloadError: + audio_sources = await asyncify(RoyalPCMAudio.create_from_ytsearch, message.url) + await bot.add_to_music_data(audio_sources, guild) + return PlaySuccessful(info_list=[source.rpf.info for source in audio_sources]) class PlayCommand(Command): @@ -51,5 +62,40 @@ class PlayCommand(Command): @classmethod async def common(cls, call: Call): guild, url = call.args.match(r"(?:\[(.+)])?\s*(.+)") - await call.net_request(PlayMessage(url, guild), "discord") - await call.reply(f"✅ Richiesta la riproduzione di [c]{url}[/c].") + response: typing.Union[RequestError, PlaySuccessful] = await call.net_request(PlayMessage(url, guild), "discord") + if isinstance(response, RequestError): + # RoyalPCMFile errors + if isinstance(response.exc, FileExistsError): + await call.reply(f"❌ Scaricare [c]{url}[/c] significherebbe sovrascrivere un file già esistente.\nQuesto è un bug, e non dovrebbe mai succedere. Se è appena successo, segnalate il problema a https://github.com/Steffo99/royalnet/issues.\n[p]{response.exc}[/p]") + return + # ffmpeg errors + if isinstance(response.exc, ffmpeg.Error): + await call.reply(f"⚠️ Errore durante la conversione a PCM di [c]{url}[/c]:\n[p]{response.exc}[/p]") + return + # youtube_dl errors + if isinstance(response.exc, youtube_dl.utils.ContentTooShortError): + await call.reply(f"⚠️ Mentre era in corso il download di [c]{url}[/c], la connessione è stata interrotta, quindi la riproduzione è stata annullata.\n[p]{response.exc}[/p]") + return + if isinstance(response.exc, youtube_dl.utils.UnavailableVideoError): + await call.reply(f"⚠️ Non è disponibile nessun audio su [c]{url}[/c].\n[p]{response.exc}[/p]") + return + if isinstance(response.exc, youtube_dl.utils.SameFileError): + await call.reply(f"❌ Scaricare [c]{url}[/c] significherebbe scaricare due file diversi sullo stesso nome.\nQuesto è un bug, e non dovrebbe mai succedere. Se è appena successo, segnalate il problema a https://github.com/Steffo99/royalnet/issues.\n[p]{response.exc}[/p]") + return + if isinstance(response.exc, youtube_dl.utils.GeoRestrictedError): + await call.reply(f"⚠️ [c]{url}[/c] non può essere visualizzato nel paese in cui si trova il bot e non può essere scaricato.\n[p]{response.exc}[/p]") + return + if isinstance(response.exc, youtube_dl.utils.UnsupportedError): + await call.reply(f"⚠️ [c]{url}[/c] non è supportato da YoutubeDL e non può essere scaricato.\n[p]{response.exc}[/p]") + return + if isinstance(response.exc, youtube_dl.utils.ExtractorError): + await call.reply(f"⚠️ Errore nella ricerca di info per [c]{url}[/c]:\n[p]{response.exc}[/p]") + return + if isinstance(response.exc, youtube_dl.utils.DownloadError): + await call.reply(f"⚠️ Errore nel download di [c]{url}[/c]:\n[p]{response.exc}[/p]") + return + if isinstance(response.exc, youtube_dl.utils.YoutubeDLError): + await call.reply(f"⚠️ Errore di youtube_dl per [c]{url}[/c]:\n[p]{response.exc}[/p]") + return + for info in response.info_list: + await call.reply(f"⬇️ Download di [i]{info.title}[/i] completato.")