mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-27 13:34:28 +00:00
I'm amazed this actually works. Closes #53!
This commit is contained in:
parent
e0e1290ecd
commit
a9e90f5554
16 changed files with 193 additions and 133 deletions
2
docs/html/_static/jquery-3.2.1.js
vendored
2
docs/html/_static/jquery-3.2.1.js
vendored
|
@ -3857,7 +3857,7 @@ jQuery.Deferred.exceptionHook = function( error, stack ) {
|
|||
// Support: IE 8 - 9 only
|
||||
// Console exists when dev tools are open, which can happen at any time
|
||||
if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
|
||||
window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
|
||||
window.console.warn( "jQuery.Deferred exception: " + error.data, error.stack, stack );
|
||||
}
|
||||
};
|
||||
|
||||
|
|
2
docs/html/_static/jquery.js
vendored
2
docs/html/_static/jquery.js
vendored
File diff suppressed because one or more lines are too long
|
@ -5,8 +5,8 @@ import logging as _logging
|
|||
from .generic import GenericBot
|
||||
from ..commands import NullCommand
|
||||
from ..utils import asyncify, Call, Command
|
||||
from ..error import UnregisteredError, NoneFoundError, TooManyFoundError, InvalidConfigError
|
||||
from ..network import Message, Reply, RoyalnetConfig
|
||||
from ..error import UnregisteredError, NoneFoundError, TooManyFoundError, InvalidConfigError, RoyalnetResponseError
|
||||
from ..network import RoyalnetConfig, Request, Response, ResponseSuccess, ResponseError
|
||||
from ..database import DatabaseConfig
|
||||
from ..audio import PlayMode, Playlist, RoyalPCMAudio
|
||||
|
||||
|
@ -62,12 +62,20 @@ class DiscordBot(GenericBot):
|
|||
.replace("[/p]", "```")
|
||||
await call.channel.send(escaped_text)
|
||||
|
||||
async def net_request(call, message: Message, destination: str):
|
||||
async def net_request(call, request: Request, destination: str) -> dict:
|
||||
if self.network is None:
|
||||
raise InvalidConfigError("Royalnet is not enabled on this bot")
|
||||
response: Reply = await self.network.request(message, destination)
|
||||
response_dict: dict = await self.network.request(request.to_dict(), destination)
|
||||
if "type" not in response_dict:
|
||||
raise RoyalnetResponseError("Response is missing a type")
|
||||
elif response_dict["type"] == "ResponseSuccess":
|
||||
response: typing.Union[ResponseSuccess, ResponseError] = ResponseSuccess.from_dict(response_dict)
|
||||
elif response_dict["type"] == "ResponseError":
|
||||
response = ResponseError.from_dict(response_dict)
|
||||
else:
|
||||
raise RoyalnetResponseError("Response type is unknown")
|
||||
response.raise_on_error()
|
||||
return response
|
||||
return response.data
|
||||
|
||||
async def get_author(call, error_if_none=False):
|
||||
message: discord.Message = call.kwargs["message"]
|
||||
|
|
|
@ -4,7 +4,7 @@ import asyncio
|
|||
import logging
|
||||
from ..utils import Command, NetworkHandler, Call
|
||||
from ..commands import NullCommand
|
||||
from ..network import RoyalnetLink, Message, RoyalnetConfig
|
||||
from ..network import RoyalnetLink, Request, Response, ResponseSuccess, ResponseError, RoyalnetConfig
|
||||
from ..database import Alchemy, DatabaseConfig, relationshiplinkchain
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ class GenericBot:
|
|||
"""Generate the ``commands`` dictionary required to handle incoming messages, and the ``network_handlers`` dictionary required to handle incoming requests."""
|
||||
log.debug(f"Now generating commands")
|
||||
self.commands: typing.Dict[str, typing.Type[Command]] = {}
|
||||
self.network_handlers: typing.Dict[typing.Type[Message], typing.Type[NetworkHandler]] = {}
|
||||
self.network_handlers: typing.Dict[str, typing.Type[NetworkHandler]] = {}
|
||||
for command in commands:
|
||||
lower_command_name = command.command_name.lower()
|
||||
self.commands[f"{command_prefix}{lower_command_name}"] = command
|
||||
|
@ -47,25 +47,38 @@ class GenericBot:
|
|||
log.debug(f"Running RoyalnetLink {self.network}")
|
||||
loop.create_task(self.network.run())
|
||||
|
||||
async def _network_handler(self, message: Message) -> Message:
|
||||
"""Handle a single :py:class:`royalnet.network.Message` received from the :py:class:`royalnet.network.RoyalnetLink`.
|
||||
async def _network_handler(self, request_dict: dict) -> dict:
|
||||
"""Handle a single :py:class:`dict` received from the :py:class:`royalnet.network.RoyalnetLink`.
|
||||
|
||||
Returns:
|
||||
Another message, to be sent as :py:class:`royalnet.network.Reply`."""
|
||||
log.debug(f"Received {message} from the RoyalnetLink")
|
||||
Another :py:class:`dict`, formatted as a :py:class:`royalnet.network.Response`."""
|
||||
# Convert the dict to a Request
|
||||
try:
|
||||
network_handler = self.network_handlers[message.__class__]
|
||||
request: Request = Request.from_dict(request_dict)
|
||||
except TypeError:
|
||||
log.warning(f"Invalid request received: {request_dict}")
|
||||
return ResponseError("invalid_request",
|
||||
f"The Request that you sent was invalid. Check extra_info to see what you sent.",
|
||||
extra_info={"you_sent": request_dict}).to_dict()
|
||||
log.debug(f"Received {request} from the RoyalnetLink")
|
||||
try:
|
||||
network_handler = self.network_handlers[request.handler]
|
||||
except KeyError:
|
||||
_, exc, tb = sys.exc_info()
|
||||
log.debug(f"Missing network_handler for {message}")
|
||||
raise Exception(f"Missing network_handler for {message}")
|
||||
log.warning(f"Missing network_handler for {request.handler}")
|
||||
return ResponseError("no_handler", f"This Link is missing a network handler for {request.handler}.").to_dict()
|
||||
try:
|
||||
log.debug(f"Using {network_handler} as handler for {message}")
|
||||
return await getattr(network_handler, self.interface_name)(self, message)
|
||||
log.debug(f"Using {network_handler} as handler for {request.handler}")
|
||||
response: Response = await getattr(network_handler, self.interface_name)(self, request.data)
|
||||
return response.to_dict()
|
||||
except Exception:
|
||||
_, exc, _ = sys.exc_info()
|
||||
log.debug(f"Exception {exc} in {network_handler}")
|
||||
raise
|
||||
return ResponseError("exception_in_handler",
|
||||
f"An exception was raised in {network_handler} for {request.handler}. Check extra_info for details.",
|
||||
extra_info={
|
||||
"type": exc.__class__.__name__,
|
||||
"str": str(exc)
|
||||
}).to_dict()
|
||||
|
||||
def _init_database(self, commands: typing.List[typing.Type[Command]], database_config: DatabaseConfig):
|
||||
"""Create an :py:class:`royalnet.database.Alchemy` with the tables required by the commands. Then, find the chain that links the ``master_table`` to the ``identity_table``."""
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import telegram
|
||||
from telegram.utils.request import Request
|
||||
import telegram.utils.request
|
||||
import asyncio
|
||||
import typing
|
||||
import logging as _logging
|
||||
from .generic import GenericBot
|
||||
from ..commands import NullCommand
|
||||
from ..utils import asyncify, Call, Command
|
||||
from ..error import UnregisteredError, InvalidConfigError
|
||||
from ..network import Message, RoyalnetConfig, Reply
|
||||
from ..error import UnregisteredError, InvalidConfigError, RoyalnetResponseError
|
||||
from ..network import RoyalnetConfig, Request, Response, ResponseSuccess, ResponseError
|
||||
from ..database import DatabaseConfig
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
|
@ -27,7 +27,7 @@ class TelegramBot(GenericBot):
|
|||
def _init_client(self):
|
||||
"""Create the :py:class:`telegram.Bot`, and set the starting offset."""
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/issues/341
|
||||
request = Request(5)
|
||||
request = telegram.utils.request.Request(5)
|
||||
self.client = telegram.Bot(self._telegram_config.token, request=request)
|
||||
self._offset: int = -100
|
||||
|
||||
|
@ -55,12 +55,20 @@ class TelegramBot(GenericBot):
|
|||
.replace("[/p]", "</pre>")
|
||||
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, request: Request, destination: str) -> dict:
|
||||
if self.network is None:
|
||||
raise InvalidConfigError("Royalnet is not enabled on this bot")
|
||||
response: Reply = await self.network.request(message, destination)
|
||||
response_dict: dict = await self.network.request(request.to_dict(), destination)
|
||||
if "type" not in response_dict:
|
||||
raise RoyalnetResponseError("Response is missing a type")
|
||||
elif response_dict["type"] == "ResponseSuccess":
|
||||
response: typing.Union[ResponseSuccess, ResponseError] = ResponseSuccess.from_dict(response_dict)
|
||||
elif response_dict["type"] == "ResponseError":
|
||||
response = ResponseError.from_dict(response_dict)
|
||||
else:
|
||||
raise RoyalnetResponseError("Response type is unknown")
|
||||
response.raise_on_error()
|
||||
return response
|
||||
return response.data
|
||||
|
||||
async def get_author(call, error_if_none=False):
|
||||
update: telegram.Update = call.kwargs["update"]
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
import logging as _logging
|
||||
from ..utils import Command, Call
|
||||
from ..error import NoneFoundError, \
|
||||
TooManyFoundError, \
|
||||
UnregisteredError, \
|
||||
UnsupportedError, \
|
||||
InvalidInputError, \
|
||||
InvalidConfigError, \
|
||||
RoyalnetError, \
|
||||
ExternalError
|
||||
|
||||
from ..error import *
|
||||
|
||||
log = _logging.getLogger(__name__)
|
||||
|
||||
|
@ -41,11 +33,15 @@ class ErrorHandlerCommand(Command):
|
|||
if isinstance(exception, InvalidConfigError):
|
||||
await call.reply(f"⚠️ Il bot non è stato configurato correttamente, quindi questo comando non può essere eseguito.\n[p]{exception}[/p]")
|
||||
return
|
||||
if isinstance(exception, RoyalnetError):
|
||||
await call.reply(f"⚠️ La richiesta a Royalnet ha restituito un errore: [p]{exception.exc}[/p]")
|
||||
if isinstance(exception, RoyalnetRequestError):
|
||||
await call.reply(f"⚠️ La richiesta a Royalnet ha restituito un errore: [p]{exception.error}[/p]")
|
||||
return
|
||||
if isinstance(exception, ExternalError):
|
||||
await call.reply(f"⚠️ Una risorsa esterna necessaria per l'esecuzione del comando non ha funzionato correttamente, quindi il comando è stato annullato.\n[p]{exception}[/p]")
|
||||
return
|
||||
await call.reply(f"❌ Eccezione non gestita durante l'esecuzione del comando:\n[b]{exception.__class__.__name__}[/b]\n[p]{exception}[/p]")
|
||||
if isinstance(exception, RoyalnetResponseError):
|
||||
log.warning(f"Invalid response from Royalnet - {exception.__class__.__name__}: {exception}")
|
||||
await call.reply(f"❌ La risposta ricevuta da Royalnet non è valida: [p]{exception}[/p]")
|
||||
return
|
||||
log.error(f"Unhandled exception - {exception.__class__.__name__}: {exception}")
|
||||
await call.reply(f"❌ Eccezione non gestita durante l'esecuzione del comando:\n[b]{exception.__class__.__name__}[/b]\n[p]{exception}[/p]")
|
||||
|
|
|
@ -3,9 +3,9 @@ import asyncio
|
|||
import youtube_dl
|
||||
import ffmpeg
|
||||
from ..utils import Command, Call, NetworkHandler, asyncify
|
||||
from ..network import Request, Data
|
||||
from ..network import Request, ResponseSuccess
|
||||
from ..error import TooManyFoundError, NoneFoundError
|
||||
from ..audio import RoyalPCMAudio, YtdlInfo
|
||||
from ..audio import RoyalPCMAudio
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..bots import DiscordBot
|
||||
|
||||
|
@ -13,28 +13,15 @@ if typing.TYPE_CHECKING:
|
|||
loop = asyncio.get_event_loop()
|
||||
|
||||
|
||||
class PlayMessage(Data):
|
||||
def __init__(self, url: str, guild_name: typing.Optional[str] = None):
|
||||
super().__init__()
|
||||
self.url: str = url
|
||||
self.guild_name: typing.Optional[str] = guild_name
|
||||
|
||||
|
||||
class PlaySuccessful(Data):
|
||||
def __init__(self, info_list: typing.List[YtdlInfo]):
|
||||
super().__init__()
|
||||
self.info_list: typing.List[YtdlInfo] = info_list
|
||||
|
||||
|
||||
class PlayNH(NetworkHandler):
|
||||
message_type = PlayMessage
|
||||
message_type = "music_play"
|
||||
|
||||
@classmethod
|
||||
async def discord(cls, bot: "DiscordBot", message: PlayMessage):
|
||||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
"""Handle a play Royalnet request. That is, add audio to a PlayMode."""
|
||||
# Find the matching guild
|
||||
if message.guild_name:
|
||||
guild = bot.client.find_guild(message.guild_name)
|
||||
if data["guild_name"]:
|
||||
guild = bot.client.find_guild(data["guild_name"])
|
||||
else:
|
||||
if len(bot.music_data) == 0:
|
||||
raise NoneFoundError("No voice clients active")
|
||||
|
@ -46,12 +33,12 @@ class PlayNH(NetworkHandler):
|
|||
# TODO: change Exception
|
||||
raise Exception("No music_data for this guild")
|
||||
# Start downloading
|
||||
if message.url.startswith("http://") or message.url.startswith("https://"):
|
||||
audio_sources: typing.List[RoyalPCMAudio] = await asyncify(RoyalPCMAudio.create_from_url, message.url)
|
||||
if data["url"].startswith("http://") or data["url"].startswith("https://"):
|
||||
audio_sources: typing.List[RoyalPCMAudio] = await asyncify(RoyalPCMAudio.create_from_url, data["url"])
|
||||
else:
|
||||
audio_sources = await asyncify(RoyalPCMAudio.create_from_ytsearch, message.url)
|
||||
audio_sources = await asyncify(RoyalPCMAudio.create_from_ytsearch, data["url"])
|
||||
await bot.add_to_music_data(audio_sources, guild)
|
||||
return PlaySuccessful(info_list=[source.rpf.info for source in audio_sources])
|
||||
return ResponseSuccess({"title_list": [source.rpf.info.title for source in audio_sources]})
|
||||
|
||||
|
||||
async def notify_on_timeout(call: Call, url: str, time: float, repeat: bool = False):
|
||||
|
@ -72,11 +59,11 @@ class PlayCommand(Command):
|
|||
|
||||
@classmethod
|
||||
async def common(cls, call: Call):
|
||||
guild, url = call.args.match(r"(?:\[(.+)])?\s*(.+)")
|
||||
download_task = loop.create_task(call.net_request(PlayMessage(url, guild), "discord"))
|
||||
guild_name, url = call.args.match(r"(?:\[(.+)])?\s*(.+)")
|
||||
download_task = loop.create_task(call.net_request(Request("music_play", {"url": url, "guild_name": guild_name}), "discord"))
|
||||
notify_task = loop.create_task(notify_on_timeout(call, url, time=30, repeat=True))
|
||||
try:
|
||||
response: PlaySuccessful = await download_task
|
||||
data: dict = await download_task
|
||||
except Exception as exc:
|
||||
# RoyalPCMFile errors
|
||||
if isinstance(exc, FileExistsError):
|
||||
|
@ -114,5 +101,5 @@ class PlayCommand(Command):
|
|||
raise
|
||||
finally:
|
||||
notify_task.cancel()
|
||||
for info in response.info_list:
|
||||
await call.reply(f"⬇️ Download di [i]{info.title}[/i] completato.")
|
||||
for title in data["title_list"]:
|
||||
await call.reply(f"⬇️ Download di [i]{title}[/i] completato.")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import typing
|
||||
import asyncio
|
||||
from ..utils import Command, Call, NetworkHandler
|
||||
from ..network import Message, RequestSuccessful
|
||||
from ..network import Request, ResponseSuccess
|
||||
from ..error import NoneFoundError, TooManyFoundError
|
||||
from ..audio import Playlist, Pool
|
||||
if typing.TYPE_CHECKING:
|
||||
|
@ -11,21 +11,15 @@ if typing.TYPE_CHECKING:
|
|||
loop = asyncio.get_event_loop()
|
||||
|
||||
|
||||
class PlaymodeMessage(Message):
|
||||
def __init__(self, mode_name: str, guild_name: typing.Optional[str] = None):
|
||||
self.mode_name: str = mode_name
|
||||
self.guild_name: typing.Optional[str] = guild_name
|
||||
|
||||
|
||||
class PlaymodeNH(NetworkHandler):
|
||||
message_type = PlaymodeMessage
|
||||
message_type = "music_playmode"
|
||||
|
||||
@classmethod
|
||||
async def discord(cls, bot: "DiscordBot", message: PlaymodeMessage):
|
||||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
"""Handle a playmode Royalnet request. That is, change current PlayMode."""
|
||||
# Find the matching guild
|
||||
if message.guild_name:
|
||||
guild = bot.client.find_guild(message.guild_name)
|
||||
if data["guild_name"]:
|
||||
guild = bot.client.find_guild(data["guild_name"])
|
||||
else:
|
||||
if len(bot.music_data) == 0:
|
||||
raise NoneFoundError("No voice clients active")
|
||||
|
@ -36,13 +30,13 @@ class PlaymodeNH(NetworkHandler):
|
|||
if bot.music_data[guild] is not None:
|
||||
bot.music_data[guild].delete()
|
||||
# Create the new PlayMode
|
||||
if message.mode_name == "playlist":
|
||||
if data["mode_name"] == "playlist":
|
||||
bot.music_data[guild] = Playlist()
|
||||
elif message.mode_name == "pool":
|
||||
elif data["mode_name"] == "pool":
|
||||
bot.music_data[guild] = Pool()
|
||||
else:
|
||||
raise ValueError("No such PlayMode")
|
||||
return RequestSuccessful()
|
||||
return ResponseSuccess()
|
||||
|
||||
|
||||
class PlaymodeCommand(Command):
|
||||
|
@ -54,6 +48,6 @@ class PlaymodeCommand(Command):
|
|||
|
||||
@classmethod
|
||||
async def common(cls, call: Call):
|
||||
guild, mode_name = call.args.match(r"(?:\[(.+)])?\s*(\S+)\s*")
|
||||
await call.net_request(PlaymodeMessage(mode_name, guild), "discord")
|
||||
await call.reply(f"✅ Richiesto di passare alla modalità di riproduzione [c]{mode_name}[/c].")
|
||||
guild_name, mode_name = call.args.match(r"(?:\[(.+)])?\s*(\S+)\s*")
|
||||
await call.net_request(Request("music_playmode", {"mode_name": mode_name, "guild_name": guild_name}), "discord")
|
||||
await call.reply(f"✅ Modalità di riproduzione [c]{mode_name}[/c].")
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
import typing
|
||||
import discord
|
||||
from ..network import Message, RequestSuccessful
|
||||
from ..network import Request, ResponseSuccess
|
||||
from ..utils import Command, Call, NetworkHandler
|
||||
from ..error import TooManyFoundError, NoneFoundError
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..bots import DiscordBot
|
||||
|
||||
|
||||
class SkipMessage(Message):
|
||||
def __init__(self, guild_name: typing.Optional[str] = None):
|
||||
self.guild_name: typing.Optional[str] = guild_name
|
||||
|
||||
|
||||
class SkipNH(NetworkHandler):
|
||||
message_type = SkipMessage
|
||||
message_type = "music_skip"
|
||||
|
||||
@classmethod
|
||||
async def discord(cls, bot: "DiscordBot", message: SkipMessage):
|
||||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
# Find the matching guild
|
||||
if message.guild_name:
|
||||
guild = bot.client.find_guild_by_name(message.guild_name)
|
||||
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")
|
||||
|
@ -32,7 +27,7 @@ class SkipNH(NetworkHandler):
|
|||
raise NoneFoundError("Nothing to skip")
|
||||
# noinspection PyProtectedMember
|
||||
voice_client._player.stop()
|
||||
return RequestSuccessful()
|
||||
return ResponseSuccess()
|
||||
|
||||
|
||||
class SkipCommand(Command):
|
||||
|
@ -46,5 +41,5 @@ class SkipCommand(Command):
|
|||
@classmethod
|
||||
async def common(cls, call: Call):
|
||||
guild, = call.args.match(r"(?:\[(.+)])?")
|
||||
await call.net_request(SkipMessage(guild), "discord")
|
||||
await call.net_request(Request("music_skip", {"guild_name": guild}), "discord")
|
||||
await call.reply(f"✅ Richiesto lo skip della canzone attuale.")
|
||||
|
|
|
@ -2,7 +2,7 @@ import typing
|
|||
import discord
|
||||
import asyncio
|
||||
from ..utils import Command, Call, NetworkHandler
|
||||
from ..network import Message, RequestSuccessful, RequestError
|
||||
from ..network import Request, ResponseSuccess
|
||||
from ..error import NoneFoundError
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..bots import DiscordBot
|
||||
|
@ -11,24 +11,17 @@ if typing.TYPE_CHECKING:
|
|||
loop = asyncio.get_event_loop()
|
||||
|
||||
|
||||
class SummonMessage(Message):
|
||||
def __init__(self, channel_identifier: typing.Union[int, str],
|
||||
guild_identifier: typing.Optional[typing.Union[int, str]] = None):
|
||||
self.channel_name = channel_identifier
|
||||
self.guild_identifier = guild_identifier
|
||||
|
||||
|
||||
class SummonNH(NetworkHandler):
|
||||
message_type = SummonMessage
|
||||
message_type = "music_summon"
|
||||
|
||||
@classmethod
|
||||
async def discord(cls, bot: "DiscordBot", message: SummonMessage):
|
||||
async def discord(cls, bot: "DiscordBot", data: dict):
|
||||
"""Handle a summon Royalnet request. That is, join a voice channel, or move to a different one if that is not possible."""
|
||||
channel = bot.client.find_channel_by_name(message.channel_name)
|
||||
channel = bot.client.find_channel_by_name(data["channel_name"])
|
||||
if not isinstance(channel, discord.VoiceChannel):
|
||||
raise NoneFoundError("Channel is not a voice channel")
|
||||
loop.create_task(bot.client.vc_connect_or_move(channel))
|
||||
return RequestSuccessful()
|
||||
return ResponseSuccess()
|
||||
|
||||
|
||||
class SummonCommand(Command):
|
||||
|
@ -42,8 +35,7 @@ class SummonCommand(Command):
|
|||
@classmethod
|
||||
async def common(cls, call: Call):
|
||||
channel_name: str = call.args[0].lstrip("#")
|
||||
response: typing.Union[RequestSuccessful, RequestError] = await call.net_request(SummonMessage(channel_name), "discord")
|
||||
response.raise_on_error()
|
||||
await call.net_request(Request("music_summon", {"channel_name": channel_name}), "discord")
|
||||
await call.reply(f"✅ Mi sono connesso in [c]#{channel_name}[/c].")
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import typing
|
||||
if typing.TYPE_CHECKING:
|
||||
from .network import ResponseError
|
||||
|
||||
|
||||
class NoneFoundError(Exception):
|
||||
"""The element that was being looked for was not found."""
|
||||
|
||||
|
@ -22,11 +27,16 @@ class InvalidConfigError(Exception):
|
|||
"""The bot has not been configured correctly, therefore the command can not function."""
|
||||
|
||||
|
||||
class RoyalnetError(Exception):
|
||||
class RoyalnetRequestError(Exception):
|
||||
"""An error was raised while handling the Royalnet request.
|
||||
This exception contains the exception that was raised during the handling."""
|
||||
def __init__(self, exc: Exception):
|
||||
self.exc: Exception = exc
|
||||
|
||||
This exception contains the :py:class:`royalnet.network.ResponseError` that was returned by the other Link."""
|
||||
def __init__(self, error: "ResponseError"):
|
||||
self.error: "ResponseError" = error
|
||||
|
||||
|
||||
class RoyalnetResponseError(Exception):
|
||||
"""The :py:class:`royalnet.network.Response` that was received is invalid."""
|
||||
|
||||
|
||||
class ExternalError(Exception):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Royalnet realated classes."""
|
||||
from .data import Data
|
||||
from .request import Request
|
||||
from .response import Response, ResponseSuccess, ResponseError
|
||||
from .package import Package
|
||||
from .royalnetlink import RoyalnetLink, NetworkError, NotConnectedError, NotIdentifiedError, ConnectionClosedError
|
||||
from .royalnetserver import RoyalnetServer
|
||||
|
@ -14,5 +14,7 @@ __all__ = ["RoyalnetLink",
|
|||
"RoyalnetServer",
|
||||
"RoyalnetConfig",
|
||||
"ConnectionClosedError",
|
||||
"Data",
|
||||
"Request"]
|
||||
"Request",
|
||||
"Response",
|
||||
"ResponseSuccess",
|
||||
"ResponseError"]
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
class Data:
|
||||
"""Royalnet data. All fields in this class will be converted to a dict when about to be sent."""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def to_dict(self):
|
||||
return self.__dict__
|
|
@ -1,14 +1,16 @@
|
|||
from .data import Data
|
||||
class Request:
|
||||
"""A request sent from a :py:class:`royalnet.network.RoyalnetLink` to another.
|
||||
|
||||
|
||||
class Request(Data):
|
||||
"""A Royalnet request. It contains the name of the requested handler, in addition to the data."""
|
||||
It contains the name of the requested handler, in addition to the data."""
|
||||
|
||||
def __init__(self, handler: str, data: dict):
|
||||
super().__init__()
|
||||
self.handler: str = handler
|
||||
self.data: dict = data
|
||||
|
||||
def to_dict(self):
|
||||
return self.__dict__
|
||||
|
||||
@staticmethod
|
||||
def from_dict(d: dict):
|
||||
return Request(**d)
|
||||
|
@ -17,3 +19,6 @@ class Request(Data):
|
|||
if isinstance(other, Request):
|
||||
return self.handler == other.handler and self.data == other.data
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return f"royalnet.network.Request(handler={self.handler}, data={self.data})"
|
||||
|
|
61
royalnet/network/response.py
Normal file
61
royalnet/network/response.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
import typing
|
||||
from ..error import RoyalnetRequestError
|
||||
|
||||
|
||||
class Response:
|
||||
"""A base class to be inherited by all other response types."""
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Prepare the Response to be sent by converting it to a JSONable :py:class:`dict`."""
|
||||
return {
|
||||
"type": self.__class__.__name__,
|
||||
**self.__dict__
|
||||
}
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Response):
|
||||
return self.to_dict() == other.to_dict()
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict) -> "Response":
|
||||
"""Recreate the response from a received :py:class:`dict`."""
|
||||
# Ignore type in dict
|
||||
del d["type"]
|
||||
# noinspection PyArgumentList
|
||||
return cls(**d)
|
||||
|
||||
def raise_on_error(self):
|
||||
"""Raise an :py:class:`Exception` if the Response is an error, do nothing otherwise."""
|
||||
raise NotImplementedError("Please override Response.raise_on_error()")
|
||||
|
||||
|
||||
class ResponseSuccess(Response):
|
||||
"""A response to a successful :py:class:`royalnet.network.Request`."""
|
||||
|
||||
def __init__(self, data: typing.Optional[dict] = None):
|
||||
if data is None:
|
||||
self.data = {}
|
||||
else:
|
||||
self.data = data
|
||||
|
||||
def __repr__(self):
|
||||
return f"royalnet.network.ResponseSuccess(data={self.data})"
|
||||
|
||||
def raise_on_error(self):
|
||||
pass
|
||||
|
||||
|
||||
class ResponseError(Response):
|
||||
"""A response to a invalid :py:class:`royalnet.network.Request`."""
|
||||
|
||||
def __init__(self, name: str, description: str, extra_info: typing.Optional[dict] = None):
|
||||
self.name: str = name
|
||||
self.description: str = description
|
||||
self.extra_info: typing.Optional[dict] = extra_info
|
||||
|
||||
def __repr__(self):
|
||||
return f"royalnet.network.ResponseError(name={self.name}, description={self.description}, extra_info={self.extra_info})"
|
||||
|
||||
def raise_on_error(self):
|
||||
raise RoyalnetRequestError(self)
|
|
@ -161,11 +161,7 @@ class RoyalnetLink:
|
|||
# Package is a request
|
||||
assert isinstance(package, Package)
|
||||
log.debug(f"Received request {package.source_conv_id}: {package}")
|
||||
try:
|
||||
response = await self.request_handler(package.data)
|
||||
except Exception as exc:
|
||||
response = {"error": "Exception in request_handler",
|
||||
"exception": exc.__class__.__name__}
|
||||
response_package: Package = package.reply(response)
|
||||
await self.send(response_package)
|
||||
log.debug(f"Replied to request {response_package.source_conv_id}: {response_package}")
|
||||
|
|
Loading…
Reference in a new issue