1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 19:44:20 +00:00

Merge branch 'unity' into unity-docs

This commit is contained in:
Steffo 2019-04-23 16:46:12 +02:00
commit e300926f4c
8 changed files with 92 additions and 16 deletions

View file

@ -10,10 +10,15 @@ class RoyalPCMAudio(AudioSource):
self._file = open(self.rpf.audio_filename, "rb") self._file = open(self.rpf.audio_filename, "rb")
@staticmethod @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) rpf_list = RoyalPCMFile.create_from_url(url)
return [RoyalPCMAudio(rpf) for rpf in rpf_list] 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): def is_opus(self):
return False return False

View file

@ -48,7 +48,14 @@ class RoyalPCMFile(YtdlFile):
@staticmethod @staticmethod
def create_from_url(url, **ytdl_args) -> typing.List["RoyalPCMFile"]: def create_from_url(url, **ytdl_args) -> typing.List["RoyalPCMFile"]:
info_list = YtdlInfo.create_from_url(url) 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 @property
def _ytdl_filename(self): def _ytdl_filename(self):

View file

@ -53,7 +53,9 @@ class DiscordBot(GenericBot):
.replace("[u]", "__") \ .replace("[u]", "__") \
.replace("[/u]", "__") \ .replace("[/u]", "__") \
.replace("[c]", "`") \ .replace("[c]", "`") \
.replace("[/c]", "`") .replace("[/c]", "`") \
.replace("[p]", "```") \
.replace("[/p]", "```")
await call.channel.send(escaped_text) await call.channel.send(escaped_text)
async def net_request(call, message: Message, destination: str): async def net_request(call, message: Message, destination: str):
@ -203,11 +205,9 @@ class DiscordBot(GenericBot):
await self.client.connect() await self.client.connect()
# TODO: how to stop? # 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.""" """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] guild_music_data = self.music_data[guild]
audio_sources = await asyncify(RoyalPCMAudio.create_from_url, url)
for audio_source in audio_sources: for audio_source in audio_sources:
log.debug(f"Adding {audio_source} to music_data") log.debug(f"Adding {audio_source} to music_data")
guild_music_data.add(audio_source) guild_music_data.add(audio_source)

View file

@ -48,7 +48,9 @@ class TelegramBot(GenericBot):
.replace("[u]", "<b>") \ .replace("[u]", "<b>") \
.replace("[/u]", "</b>") \ .replace("[/u]", "</b>") \
.replace("[c]", "<code>") \ .replace("[c]", "<code>") \
.replace("[/c]", "</code>") .replace("[/c]", "</code>") \
.replace("[p]", "<pre>") \
.replace("[/p]", "</pre>")
await asyncify(call.channel.send_message, escaped_text, parse_mode="HTML") await asyncify(call.channel.send_message, escaped_text, parse_mode="HTML")
async def net_request(call, message: Message, destination: str): async def net_request(call, message: Message, destination: str):

View file

@ -42,10 +42,10 @@ class ErrorHandlerCommand(Command):
await call.reply("⚠️ Il bot non è stato configurato correttamente, quindi questo comando non può essere eseguito. L'errore è stato segnalato all'amministratore.") await call.reply("⚠️ Il bot non è stato configurato correttamente, quindi questo comando non può essere eseguito. L'errore è stato segnalato all'amministratore.")
return return
if isinstance(exception, RoyalnetError): if isinstance(exception, RoyalnetError):
await call.reply(f"⚠️ La richiesta a Royalnet ha restituito un errore: {exception.exc}") await call.reply(f"⚠️ La richiesta a Royalnet ha restituito un errore: [p]{exception.exc}[/p]")
return return
if isinstance(exception, ExternalError): if isinstance(exception, ExternalError):
await call.reply("⚠️ Una risorsa esterna necessaria per l'esecuzione del comando non ha funzionato correttamente, quindi il comando è stato annullato.") await call.reply("⚠️ Una risorsa esterna necessaria per l'esecuzione del comando non ha funzionato correttamente, quindi il comando è stato annullato.")
return return
await call.reply(f"❌ Eccezione non gestita durante l'esecuzione del comando:\n[b]{exception.__class__.__name__}[/b]\n{exception}") await call.reply(f"❌ Eccezione non gestita durante l'esecuzione del comando:\n[b]{exception.__class__.__name__}[/b]\n[p]{exception}[/p]")
log.error(f"Unhandled exception - {exception.__class__.__name__}: {exception}") log.error(f"Unhandled exception - {exception.__class__.__name__}: {exception}")

View file

@ -1,8 +1,11 @@
import typing import typing
import asyncio import asyncio
from ..utils import Command, Call, NetworkHandler import youtube_dl
import ffmpeg
from ..utils import Command, Call, NetworkHandler, asyncify
from ..network import Message, RequestSuccessful from ..network import Message, RequestSuccessful
from ..error import TooManyFoundError, NoneFoundError from ..error import TooManyFoundError, NoneFoundError
from ..audio import RoyalPCMAudio, YtdlInfo
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from ..bots import DiscordBot from ..bots import DiscordBot
@ -16,6 +19,11 @@ class PlayMessage(Message):
self.guild_name: typing.Optional[str] = guild_name 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): class PlayNH(NetworkHandler):
message_type = PlayMessage message_type = PlayMessage
@ -36,9 +44,21 @@ class PlayNH(NetworkHandler):
# TODO: change Exception # TODO: change Exception
raise Exception("No music_data for this guild") raise Exception("No music_data for this guild")
# Start downloading # Start downloading
# noinspection PyAsyncCall try:
loop.create_task(bot.add_to_music_data(message.url, guild)) audio_sources: typing.List[RoyalPCMAudio] = await asyncify(RoyalPCMAudio.create_from_url, message.url)
return RequestSuccessful() 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])
async def notify_on_timeout(call: Call, url: str, time: float, repeat: bool = False):
"""Send a message after a while to let the user know that the bot is still downloading the files and hasn't crashed."""
while True:
await asyncio.sleep(time)
await call.reply(f" Il download di [c]{url}[/c] sta richiedendo più tempo del solito, ma è ancora in corso!")
if not repeat:
break
class PlayCommand(Command): class PlayCommand(Command):
@ -51,5 +71,46 @@ class PlayCommand(Command):
@classmethod @classmethod
async def common(cls, call: Call): async def common(cls, call: Call):
guild, url = call.args.match(r"(?:\[(.+)])?\s*(.+)") guild, url = call.args.match(r"(?:\[(.+)])?\s*(.+)")
await call.net_request(PlayMessage(url, guild), "discord") download_task = loop.create_task(call.net_request(PlayMessage(url, guild), "discord"))
await call.reply(f"✅ Richiesta la riproduzione di [c]{url}[/c].") notify_task = loop.create_task(notify_on_timeout(call, url, time=20, repeat=True))
try:
response: PlaySuccessful = await download_task
except Exception as exc:
# RoyalPCMFile errors
if isinstance(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]{exc}[/p]")
return
# ffmpeg errors
if isinstance(exc, ffmpeg.Error):
await call.reply(f"⚠️ Errore durante la conversione a PCM di [c]{url}[/c]:\n[p]{exc}[/p]")
return
# youtube_dl errors
if isinstance(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]{exc}[/p]")
return
if isinstance(exc, youtube_dl.utils.UnavailableVideoError):
await call.reply(f"⚠️ Non è disponibile nessun audio su [c]{url}[/c].\n[p]{exc}[/p]")
return
if isinstance(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]{exc}[/p]")
return
if isinstance(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]{exc}[/p]")
return
if isinstance(exc, youtube_dl.utils.UnsupportedError):
await call.reply(f"⚠️ [c]{url}[/c] non è supportato da YoutubeDL e non può essere scaricato.\n[p]{exc}[/p]")
return
if isinstance(exc, youtube_dl.utils.ExtractorError):
await call.reply(f"⚠️ Errore nella ricerca di info per [c]{url}[/c]:\n[p]{exc}[/p]")
return
if isinstance(exc, youtube_dl.utils.DownloadError):
await call.reply(f"⚠️ Errore nel download di [c]{url}[/c]:\n[p]{exc}[/p]")
return
if isinstance(exc, youtube_dl.utils.YoutubeDLError):
await call.reply(f"⚠️ Errore di youtube_dl per [c]{url}[/c]:\n[p]{exc}[/p]")
return
raise
finally:
notify_task.cancel()
for info in response.info_list:
await call.reply(f"⬇️ Download di [i]{info.title}[/i] completato.")

View file

@ -22,7 +22,7 @@ class PlaymodeNH(NetworkHandler):
@classmethod @classmethod
async def discord(cls, bot: "DiscordBot", message: PlaymodeMessage): async def discord(cls, bot: "DiscordBot", message: PlaymodeMessage):
"""Handle a play Royalnet request. That is, add audio to a PlayMode.""" """Handle a playmode Royalnet request. That is, change current PlayMode."""
# Find the matching guild # Find the matching guild
if message.guild_name: if message.guild_name:
guild = bot.client.find_guild(message.guild_name) guild = bot.client.find_guild(message.guild_name)

View file

@ -4,5 +4,6 @@ import typing
async def asyncify(function: typing.Callable, *args, **kwargs): async def asyncify(function: typing.Callable, *args, **kwargs):
# TODO: make cancellable somehow
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, functools.partial(function, *args, **kwargs)) return await loop.run_in_executor(None, functools.partial(function, *args, **kwargs))