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

Start work on a universal summon command

This commit is contained in:
Steffo 2019-11-16 02:31:07 +01:00
parent 192b4cf3d6
commit 34d682f0e3
7 changed files with 115 additions and 58 deletions

View file

@ -1,11 +1,13 @@
# Imports go here!
from .ping import PingCommand
from .version import VersionCommand
from .summon import SummonCommand
# Enter the commands of your Pack here!
available_commands = [
PingCommand,
VersionCommand
VersionCommand,
SummonCommand,
]
# Don't change this, it should automatically generate __all__

View file

@ -0,0 +1,54 @@
from royalnet.commands import *
from typing import TYPE_CHECKING, Optional, List
import asyncio
try:
import discord
except ImportError:
discord = None
if TYPE_CHECKING:
from royalnet.serf.discord import DiscordSerf
class SummonCommand(Command):
name: str = "summon"
description = "Connect the bot to a Discord voice channel."
syntax = "[channelname]"
async def run(self, args: CommandArgs, data: CommandData) -> None:
# This command only runs on Discord!
if self.interface.name != "discord":
raise UnsupportedError()
# noinspection PyUnresolvedReferences
message: discord.Message = data.message
serf: DiscordSerf = self.interface.bot
channel_name: Optional[str] = args.joined()
# If the channel name was passed as an argument...
if channel_name != "":
# Try to find the specified channel
channels: List[discord.abc.GuildChannel] = serf.client.find_channel(channel_name)
# TODO: if there are multiple channels, try to find the most appropriate one
# TODO: ensure that the channel is a voice channel
if len(channels) != 1:
raise CommandError("Couldn't decide on a channel to connect to.")
else:
channel = channels[0]
else:
# Try to use the channel in which the command author is in
voice: Optional[discord.VoiceState] = message.author.voice
if voice is None:
raise CommandError("You must be connected to a voice channel to summon the bot without any arguments.")
channel: discord.VoiceChannel = voice.channel
# Try to connect to the voice channel
try:
await channel.connect()
except asyncio.TimeoutError:
raise ExternalError("Timed out while trying to connect to the channel")
except discord.opus.OpusNotLoaded:
raise ConfigurationError("[c]libopus[/c] is not loaded in the serf")
except discord.ClientException as e:
# TODO: handle this someway
raise

View file

@ -2,7 +2,12 @@ from .commandinterface import CommandInterface
from .command import Command
from .commanddata import CommandData
from .commandargs import CommandArgs
from .errors import CommandError, InvalidInputError, UnsupportedError, KeyboardExpiredError, ConfigurationError
from .errors import CommandError, \
InvalidInputError, \
UnsupportedError, \
ConfigurationError, \
ExternalError, \
UserError
__all__ = [
"CommandInterface",
@ -12,6 +17,7 @@ __all__ = [
"CommandError",
"InvalidInputError",
"UnsupportedError",
"KeyboardExpiredError",
"ConfigurationError",
"ExternalError",
"UserError",
]

View file

@ -9,21 +9,22 @@ class CommandError(Exception):
return f"{self.__class__.__qualname__}({repr(self.message)})"
class InvalidInputError(CommandError):
"""The command has received invalid input and cannot complete.
class UserError(CommandError):
"""The command failed to execute, and the error is because of something that the user did."""
Display an error message to the user, along with the correct syntax for the command."""
class InvalidInputError(UserError):
"""The command has received invalid input and cannot complete."""
class UnsupportedError(CommandError):
"""A requested feature is not available on this interface.
Display an error message to the user, telling them to use another interface."""
class KeyboardExpiredError(CommandError):
"""A special type of exception that can be raised in keyboard handlers to mark a specific keyboard as expired."""
"""A requested feature is not available on this interface."""
class ConfigurationError(CommandError):
"""The command is misconfigured and cannot work."""
"""The command cannot work because of a wrong configuration by part of the Royalnet admin."""
class ExternalError(CommandError):
"""The command failed to execute, but the problem was because of an external factor (such as an external API going
down)."""

View file

@ -1,8 +1,7 @@
import asyncio
import logging
from typing import Type, Optional, List, Union
from royalnet.commands import Command, CommandInterface, CommandData, CommandArgs, CommandError, InvalidInputError, \
UnsupportedError
from royalnet.commands import *
from royalnet.utils import asyncify
from .escape import escape
from ..serf import Serf
@ -124,25 +123,11 @@ class DiscordSerf(Serf):
session = None
# Prepare data
data = self.Data(interface=command.interface, session=session, loop=self.loop, message=message)
try:
# Run the command
await command.run(CommandArgs(parameters), data)
except InvalidInputError as e:
await data.reply(f":warning: {e.message}\n"
f"Syntax: [c]/{command.name} {command.syntax}[/c]")
except UnsupportedError as e:
await data.reply(f":warning: {e.message}")
except CommandError as e:
await data.reply(f":warning: {e.message}")
except Exception as e:
self.sentry_exc(e)
error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n" \
'\n'.join(e.args)
await data.reply(error_message)
finally:
# Close the alchemy session
if session is not None:
await asyncify(session.close)
# Call the command
self.call(command, data, parameters)
# Close the alchemy session
if session is not None:
await asyncify(session.close)
def bot_factory(self) -> Type[discord.Client]:
"""Create a custom class inheriting from :py:class:`discord.Client`."""
@ -172,7 +157,7 @@ class DiscordSerf(Serf):
def find_channel(cli,
name: str,
guild: Optional[discord.Guild] = None) -> List[discord.abc.GuildChannel]:
"""Find the :class:`TextChannel`, :class:`VoiceChannel` or :class:`CategoryChannel` with the
"""Find the :class:`TextChannel`s, :class:`VoiceChannel`s or :class:`CategoryChannel`s with the
specified name (case insensitive).
You can specify a guild to only search in that specific guild."""

View file

@ -4,7 +4,7 @@ from typing import Type, Optional, Awaitable, Dict, List, Any, Callable, Union,
from keyring import get_password
from sqlalchemy.schema import Table
from royalnet import __version__ as version
from royalnet.commands import Command, CommandInterface, CommandData, CommandError, UnsupportedError
from royalnet.commands import *
from .alchemyconfig import AlchemyConfig
try:
@ -284,6 +284,29 @@ class Serf:
username: the name of the secret that should be retrieved."""
return get_password(f"Royalnet/{self.secrets_name}", username)
def call(self, command: Command, data: CommandData, parameters: List[str]):
try:
# Run the command
await command.run(CommandArgs(parameters), data)
except InvalidInputError as e:
await data.reply(f"⚠️ {e.message}\n"
f"Syntax: [c]{command.interface.prefix}{command.name} {command.syntax}[/c]")
except UserError as e:
await data.reply(f"⚠️ {e.message}")
except UnsupportedError as e:
await data.reply(f"🚫 {e.message}")
except ExternalError as e:
await data.reply(f"🚫 {e.message}")
except ConfigurationError as e:
await data.reply(f"⛔️ {e.message}")
except CommandError as e:
await data.reply(f"⛔️ {e.message}")
except Exception as e:
self.sentry_exc(e)
error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n" \
'\n'.join(e.args)
await data.reply(error_message)
async def run(self):
"""A coroutine that starts the event loop and handles command calls."""
self.herald_task = self.loop.create_task(self.herald.run())

View file

@ -194,27 +194,13 @@ class TelegramSerf(Serf):
session = await asyncify(self.alchemy.Session)
else:
session = None
try:
# Create the command data
data = self.Data(interface=command.interface, session=session, loop=self.loop, update=update)
try:
# Run the command
await command.run(CommandArgs(parameters), data)
except InvalidInputError as e:
await data.reply(f"⚠️ {e.message}\n"
f"Syntax: [c]/{command.name} {command.syntax}[/c]")
except UnsupportedError as e:
await data.reply(f"⚠️ {e.message}")
except CommandError as e:
await data.reply(f"⚠️ {e.message}")
except Exception as e:
self.sentry_exc(e)
error_message = f"🦀 [b]{e.__class__.__name__}[/b] 🦀\n" \
'\n'.join(e.args)
await data.reply(error_message)
finally:
if session is not None:
await asyncify(session.close)
# Prepare data
data = self.Data(interface=command.interface, session=session, loop=self.loop, message=message)
# Call the command
self.call(command, data, parameters)
# Close the alchemy session
if session is not None:
await asyncify(session.close)
async def handle_edited_message(self, update: telegram.Update):
pass