mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
Start work on a universal summon command
This commit is contained in:
parent
192b4cf3d6
commit
34d682f0e3
7 changed files with 115 additions and 58 deletions
|
@ -1,11 +1,13 @@
|
||||||
# Imports go here!
|
# Imports go here!
|
||||||
from .ping import PingCommand
|
from .ping import PingCommand
|
||||||
from .version import VersionCommand
|
from .version import VersionCommand
|
||||||
|
from .summon import SummonCommand
|
||||||
|
|
||||||
# Enter the commands of your Pack here!
|
# Enter the commands of your Pack here!
|
||||||
available_commands = [
|
available_commands = [
|
||||||
PingCommand,
|
PingCommand,
|
||||||
VersionCommand
|
VersionCommand,
|
||||||
|
SummonCommand,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Don't change this, it should automatically generate __all__
|
# Don't change this, it should automatically generate __all__
|
||||||
|
|
54
royalnet/backpack/commands/summon.py
Normal file
54
royalnet/backpack/commands/summon.py
Normal 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
|
|
@ -2,7 +2,12 @@ from .commandinterface import CommandInterface
|
||||||
from .command import Command
|
from .command import Command
|
||||||
from .commanddata import CommandData
|
from .commanddata import CommandData
|
||||||
from .commandargs import CommandArgs
|
from .commandargs import CommandArgs
|
||||||
from .errors import CommandError, InvalidInputError, UnsupportedError, KeyboardExpiredError, ConfigurationError
|
from .errors import CommandError, \
|
||||||
|
InvalidInputError, \
|
||||||
|
UnsupportedError, \
|
||||||
|
ConfigurationError, \
|
||||||
|
ExternalError, \
|
||||||
|
UserError
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"CommandInterface",
|
"CommandInterface",
|
||||||
|
@ -12,6 +17,7 @@ __all__ = [
|
||||||
"CommandError",
|
"CommandError",
|
||||||
"InvalidInputError",
|
"InvalidInputError",
|
||||||
"UnsupportedError",
|
"UnsupportedError",
|
||||||
"KeyboardExpiredError",
|
|
||||||
"ConfigurationError",
|
"ConfigurationError",
|
||||||
|
"ExternalError",
|
||||||
|
"UserError",
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,21 +9,22 @@ class CommandError(Exception):
|
||||||
return f"{self.__class__.__qualname__}({repr(self.message)})"
|
return f"{self.__class__.__qualname__}({repr(self.message)})"
|
||||||
|
|
||||||
|
|
||||||
class InvalidInputError(CommandError):
|
class UserError(CommandError):
|
||||||
"""The command has received invalid input and cannot complete.
|
"""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):
|
class UnsupportedError(CommandError):
|
||||||
"""A requested feature is not available on this interface.
|
"""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."""
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationError(CommandError):
|
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)."""
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Type, Optional, List, Union
|
from typing import Type, Optional, List, Union
|
||||||
from royalnet.commands import Command, CommandInterface, CommandData, CommandArgs, CommandError, InvalidInputError, \
|
from royalnet.commands import *
|
||||||
UnsupportedError
|
|
||||||
from royalnet.utils import asyncify
|
from royalnet.utils import asyncify
|
||||||
from .escape import escape
|
from .escape import escape
|
||||||
from ..serf import Serf
|
from ..serf import Serf
|
||||||
|
@ -124,22 +123,8 @@ class DiscordSerf(Serf):
|
||||||
session = None
|
session = None
|
||||||
# Prepare data
|
# Prepare data
|
||||||
data = self.Data(interface=command.interface, session=session, loop=self.loop, message=message)
|
data = self.Data(interface=command.interface, session=session, loop=self.loop, message=message)
|
||||||
try:
|
# Call the command
|
||||||
# Run the command
|
self.call(command, data, parameters)
|
||||||
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
|
# Close the alchemy session
|
||||||
if session is not None:
|
if session is not None:
|
||||||
await asyncify(session.close)
|
await asyncify(session.close)
|
||||||
|
@ -172,7 +157,7 @@ class DiscordSerf(Serf):
|
||||||
def find_channel(cli,
|
def find_channel(cli,
|
||||||
name: str,
|
name: str,
|
||||||
guild: Optional[discord.Guild] = None) -> List[discord.abc.GuildChannel]:
|
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).
|
specified name (case insensitive).
|
||||||
|
|
||||||
You can specify a guild to only search in that specific guild."""
|
You can specify a guild to only search in that specific guild."""
|
||||||
|
|
|
@ -4,7 +4,7 @@ from typing import Type, Optional, Awaitable, Dict, List, Any, Callable, Union,
|
||||||
from keyring import get_password
|
from keyring import get_password
|
||||||
from sqlalchemy.schema import Table
|
from sqlalchemy.schema import Table
|
||||||
from royalnet import __version__ as version
|
from royalnet import __version__ as version
|
||||||
from royalnet.commands import Command, CommandInterface, CommandData, CommandError, UnsupportedError
|
from royalnet.commands import *
|
||||||
from .alchemyconfig import AlchemyConfig
|
from .alchemyconfig import AlchemyConfig
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -284,6 +284,29 @@ class Serf:
|
||||||
username: the name of the secret that should be retrieved."""
|
username: the name of the secret that should be retrieved."""
|
||||||
return get_password(f"Royalnet/{self.secrets_name}", username)
|
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):
|
async def run(self):
|
||||||
"""A coroutine that starts the event loop and handles command calls."""
|
"""A coroutine that starts the event loop and handles command calls."""
|
||||||
self.herald_task = self.loop.create_task(self.herald.run())
|
self.herald_task = self.loop.create_task(self.herald.run())
|
||||||
|
|
|
@ -194,25 +194,11 @@ class TelegramSerf(Serf):
|
||||||
session = await asyncify(self.alchemy.Session)
|
session = await asyncify(self.alchemy.Session)
|
||||||
else:
|
else:
|
||||||
session = None
|
session = None
|
||||||
try:
|
# Prepare data
|
||||||
# Create the command data
|
data = self.Data(interface=command.interface, session=session, loop=self.loop, message=message)
|
||||||
data = self.Data(interface=command.interface, session=session, loop=self.loop, update=update)
|
# Call the command
|
||||||
try:
|
self.call(command, data, parameters)
|
||||||
# Run the command
|
# Close the alchemy session
|
||||||
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:
|
if session is not None:
|
||||||
await asyncify(session.close)
|
await asyncify(session.close)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue