mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-27 13:34:28 +00:00
Lööps
This commit is contained in:
parent
350d754683
commit
10c77a20d0
10 changed files with 134 additions and 126 deletions
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from royalnet.bots import DiscordBot, DiscordConfig
|
from royalnet.bots import DiscordBot, DiscordConfig, TelegramBot, TelegramConfig
|
||||||
from royalnet.commands import *
|
from royalnet.commands import *
|
||||||
from royalnet.commands.debug_create import DebugCreateCommand
|
from royalnet.commands.debug_create import DebugCreateCommand
|
||||||
from royalnet.commands.error_handler import ErrorHandlerCommand
|
from royalnet.commands.error_handler import ErrorHandlerCommand
|
||||||
|
@ -12,8 +12,12 @@ from royalnet.database.tables import Royal, Telegram, Discord
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
log = logging.root
|
log = logging.root
|
||||||
log.addHandler(logging.StreamHandler())
|
stream_handler = logging.StreamHandler()
|
||||||
|
stream_handler.formatter = logging.Formatter("{asctime}\t{name}\t{levelname}\t{message}", style="{")
|
||||||
|
log.addHandler(stream_handler)
|
||||||
logging.getLogger("royalnet.bots.generic").setLevel(logging.DEBUG)
|
logging.getLogger("royalnet.bots.generic").setLevel(logging.DEBUG)
|
||||||
|
logging.getLogger("royalnet.bots.discord").setLevel(logging.DEBUG)
|
||||||
|
logging.getLogger("royalnet.bots.telegram").setLevel(logging.DEBUG)
|
||||||
|
|
||||||
commands = [PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, DebugCreateCommand, SyncCommand,
|
commands = [PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, DebugCreateCommand, SyncCommand,
|
||||||
AuthorCommand, DiarioCommand, RageCommand, DateparserCommand, ReminderCommand, KvactiveCommand, KvCommand,
|
AuthorCommand, DiarioCommand, RageCommand, DateparserCommand, ReminderCommand, KvactiveCommand, KvCommand,
|
||||||
|
@ -27,7 +31,13 @@ ds_bot = DiscordBot(discord_config=DiscordConfig(os.environ["DS_AK"]),
|
||||||
database_config=DatabaseConfig(os.environ["DB_PATH"], Royal, Discord, "discord_id"),
|
database_config=DatabaseConfig(os.environ["DB_PATH"], Royal, Discord, "discord_id"),
|
||||||
commands=commands,
|
commands=commands,
|
||||||
error_command=ErrorHandlerCommand)
|
error_command=ErrorHandlerCommand)
|
||||||
|
tg_bot = TelegramBot(telegram_config=TelegramConfig(os.environ["TG_AK"]),
|
||||||
|
royalnet_config=RoyalnetConfig(f"ws://{address}:{port}", "sas"),
|
||||||
|
database_config=DatabaseConfig(os.environ["DB_PATH"], Royal, Telegram, "tg_id"),
|
||||||
|
commands=commands,
|
||||||
|
error_command=ErrorHandlerCommand)
|
||||||
loop.run_until_complete(master.run())
|
loop.run_until_complete(master.run())
|
||||||
|
loop.create_task(tg_bot.run())
|
||||||
loop.create_task(ds_bot.run())
|
loop.create_task(ds_bot.run())
|
||||||
print("Starting loop...")
|
print("Starting loop...")
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from .telegram import TelegramBot
|
from .telegram import TelegramBot, TelegramConfig
|
||||||
from .discord import DiscordBot, DiscordConfig
|
from .discord import DiscordBot, DiscordConfig
|
||||||
|
|
||||||
__all__ = ["TelegramBot", "DiscordBot", "DiscordConfig"]
|
__all__ = ["TelegramBot", "TelegramConfig", "DiscordBot", "DiscordConfig"]
|
||||||
|
|
|
@ -6,7 +6,7 @@ from .generic import GenericBot
|
||||||
from ..commands import NullCommand
|
from ..commands import NullCommand
|
||||||
from ..utils import asyncify, Call, Command
|
from ..utils import asyncify, Call, Command
|
||||||
from ..error import UnregisteredError, NoneFoundError, TooManyFoundError, InvalidConfigError
|
from ..error import UnregisteredError, NoneFoundError, TooManyFoundError, InvalidConfigError
|
||||||
from ..network import Message, RequestError, RoyalnetConfig
|
from ..network import Message, RoyalnetConfig
|
||||||
from ..database import DatabaseConfig
|
from ..database import DatabaseConfig
|
||||||
from ..audio import PlayMode, Playlist
|
from ..audio import PlayMode, Playlist
|
||||||
|
|
||||||
|
@ -27,9 +27,12 @@ class DiscordBot(GenericBot):
|
||||||
interface_name = "discord"
|
interface_name = "discord"
|
||||||
|
|
||||||
def _init_voice(self):
|
def _init_voice(self):
|
||||||
|
log.debug(f"Creating music_data dict")
|
||||||
self.music_data: typing.Dict[discord.Guild, PlayMode] = {}
|
self.music_data: typing.Dict[discord.Guild, PlayMode] = {}
|
||||||
|
|
||||||
def _call_factory(self) -> typing.Type[Call]:
|
def _call_factory(self) -> typing.Type[Call]:
|
||||||
|
log.debug(f"Creating DiscordCall")
|
||||||
|
|
||||||
# noinspection PyMethodParameters
|
# noinspection PyMethodParameters
|
||||||
class DiscordCall(Call):
|
class DiscordCall(Call):
|
||||||
interface_name = self.interface_name
|
interface_name = self.interface_name
|
||||||
|
@ -56,9 +59,8 @@ class DiscordBot(GenericBot):
|
||||||
async def net_request(call, message: Message, destination: str):
|
async def net_request(call, message: Message, destination: str):
|
||||||
if self.network is None:
|
if self.network is None:
|
||||||
raise InvalidConfigError("Royalnet is not enabled on this bot")
|
raise InvalidConfigError("Royalnet is not enabled on this bot")
|
||||||
response = await self.network.request(message, destination)
|
response: Message = await self.network.request(message, destination)
|
||||||
if isinstance(response, RequestError):
|
response.raise_on_error()
|
||||||
raise response.exc
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
async def get_author(call, error_if_none=False):
|
async def get_author(call, error_if_none=False):
|
||||||
|
@ -77,12 +79,15 @@ class DiscordBot(GenericBot):
|
||||||
|
|
||||||
def _bot_factory(self) -> typing.Type[discord.Client]:
|
def _bot_factory(self) -> typing.Type[discord.Client]:
|
||||||
"""Create a new DiscordClient class based on this DiscordBot."""
|
"""Create a new DiscordClient class based on this DiscordBot."""
|
||||||
|
log.debug(f"Creating DiscordClient")
|
||||||
|
|
||||||
# noinspection PyMethodParameters
|
# noinspection PyMethodParameters
|
||||||
class DiscordClient(discord.Client):
|
class DiscordClient(discord.Client):
|
||||||
async def vc_connect_or_move(cli, channel: discord.VoiceChannel):
|
async def vc_connect_or_move(cli, channel: discord.VoiceChannel):
|
||||||
# Connect to voice chat
|
# Connect to voice chat
|
||||||
try:
|
try:
|
||||||
await channel.connect()
|
await channel.connect()
|
||||||
|
log.debug(f"Connecting to Voice in {channel}")
|
||||||
except discord.errors.ClientException:
|
except discord.errors.ClientException:
|
||||||
# Move to the selected channel, instead of connecting
|
# Move to the selected channel, instead of connecting
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
|
@ -91,8 +96,10 @@ class DiscordBot(GenericBot):
|
||||||
if voice_client.guild != channel.guild:
|
if voice_client.guild != channel.guild:
|
||||||
continue
|
continue
|
||||||
await voice_client.move_to(channel)
|
await voice_client.move_to(channel)
|
||||||
|
log.debug(f"Moved {voice_client} to {channel}")
|
||||||
# Create a music_data entry, if it doesn't exist; default is a Playlist
|
# Create a music_data entry, if it doesn't exist; default is a Playlist
|
||||||
if not self.music_data.get(channel.guild):
|
if not self.music_data.get(channel.guild):
|
||||||
|
log.debug(f"Creating music_data for {channel.guild}")
|
||||||
self.music_data[channel.guild] = Playlist()
|
self.music_data[channel.guild] = Playlist()
|
||||||
|
|
||||||
@staticmethod # Not really static because of the self reference
|
@staticmethod # Not really static because of the self reference
|
||||||
|
@ -157,17 +164,21 @@ class DiscordBot(GenericBot):
|
||||||
|
|
||||||
def _init_client(self):
|
def _init_client(self):
|
||||||
"""Create a bot instance."""
|
"""Create a bot instance."""
|
||||||
self.client = self._bot_factory()()
|
log.debug(f"Creating DiscordClient instance")
|
||||||
|
self._Client = self._bot_factory()
|
||||||
|
self.client = self._Client()
|
||||||
|
|
||||||
def __init__(self, *,
|
def __init__(self, *,
|
||||||
discord_config: DiscordConfig,
|
discord_config: DiscordConfig,
|
||||||
royalnet_config: RoyalnetConfig,
|
royalnet_config: typing.Optional[RoyalnetConfig] = None,
|
||||||
database_config: typing.Optional[DatabaseConfig] = None,
|
database_config: typing.Optional[DatabaseConfig] = None,
|
||||||
|
command_prefix: str = "!",
|
||||||
commands: typing.List[typing.Type[Command]] = None,
|
commands: typing.List[typing.Type[Command]] = None,
|
||||||
missing_command: typing.Type[Command] = NullCommand,
|
missing_command: typing.Type[Command] = NullCommand,
|
||||||
error_command: typing.Type[Command] = NullCommand):
|
error_command: typing.Type[Command] = NullCommand):
|
||||||
super().__init__(royalnet_config=royalnet_config,
|
super().__init__(royalnet_config=royalnet_config,
|
||||||
database_config=database_config,
|
database_config=database_config,
|
||||||
|
command_prefix=command_prefix,
|
||||||
commands=commands,
|
commands=commands,
|
||||||
missing_command=missing_command,
|
missing_command=missing_command,
|
||||||
error_command=error_command)
|
error_command=error_command)
|
||||||
|
@ -176,7 +187,9 @@ class DiscordBot(GenericBot):
|
||||||
self._init_voice()
|
self._init_voice()
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
|
log.debug(f"Logging in to Discord")
|
||||||
await self.client.login(self._discord_config.token)
|
await self.client.login(self._discord_config.token)
|
||||||
|
log.debug(f"Connecting to Discord")
|
||||||
await self.client.connect()
|
await self.client.connect()
|
||||||
# TODO: how to stop?
|
# TODO: how to stop?
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ class GenericBot:
|
||||||
interface_name = NotImplemented
|
interface_name = NotImplemented
|
||||||
|
|
||||||
def _init_commands(self,
|
def _init_commands(self,
|
||||||
|
command_prefix: str,
|
||||||
commands: typing.List[typing.Type[Command]],
|
commands: typing.List[typing.Type[Command]],
|
||||||
missing_command: typing.Type[Command],
|
missing_command: typing.Type[Command],
|
||||||
error_command: typing.Type[Command]):
|
error_command: typing.Type[Command]):
|
||||||
|
@ -24,7 +25,7 @@ class GenericBot:
|
||||||
self.commands: typing.Dict[str, typing.Type[Command]] = {}
|
self.commands: typing.Dict[str, typing.Type[Command]] = {}
|
||||||
self.network_handlers: typing.Dict[typing.Type[Message], typing.Type[NetworkHandler]] = {}
|
self.network_handlers: typing.Dict[typing.Type[Message], typing.Type[NetworkHandler]] = {}
|
||||||
for command in commands:
|
for command in commands:
|
||||||
self.commands[f"!{command.command_name}"] = command
|
self.commands[f"{command_prefix}{command.command_name}"] = command
|
||||||
self.network_handlers = {**self.network_handlers, **command.network_handler_dict()}
|
self.network_handlers = {**self.network_handlers, **command.network_handler_dict()}
|
||||||
self.missing_command: typing.Type[Command] = missing_command
|
self.missing_command: typing.Type[Command] = missing_command
|
||||||
self.error_command: typing.Type[Command] = error_command
|
self.error_command: typing.Type[Command] = error_command
|
||||||
|
@ -36,7 +37,7 @@ class GenericBot:
|
||||||
|
|
||||||
def _init_royalnet(self, royalnet_config: RoyalnetConfig):
|
def _init_royalnet(self, royalnet_config: RoyalnetConfig):
|
||||||
"""Create a RoyalnetLink, and run it as a task."""
|
"""Create a RoyalnetLink, and run it as a task."""
|
||||||
self.network: RoyalnetLink = RoyalnetLink(royalnet_config.master_uri, royalnet_config.master_secret, "discord",
|
self.network: RoyalnetLink = RoyalnetLink(royalnet_config.master_uri, royalnet_config.master_secret, self.interface_name,
|
||||||
self._network_handler)
|
self._network_handler)
|
||||||
log.debug(f"Running RoyalnetLink {self.network}")
|
log.debug(f"Running RoyalnetLink {self.network}")
|
||||||
loop.create_task(self.network.run())
|
loop.create_task(self.network.run())
|
||||||
|
@ -46,15 +47,17 @@ class GenericBot:
|
||||||
log.debug(f"Received {message} from the RoyalnetLink")
|
log.debug(f"Received {message} from the RoyalnetLink")
|
||||||
try:
|
try:
|
||||||
network_handler = self.network_handlers[message.__class__]
|
network_handler = self.network_handlers[message.__class__]
|
||||||
except KeyError as exc:
|
except KeyError:
|
||||||
|
_, exc, tb = sys.exc_info()
|
||||||
log.debug(f"Missing network_handler for {message}")
|
log.debug(f"Missing network_handler for {message}")
|
||||||
return RequestError(KeyError("Missing network_handler"))
|
return RequestError(exc=exc)
|
||||||
try:
|
try:
|
||||||
log.debug(f"Using {network_handler} as handler for {message}")
|
log.debug(f"Using {network_handler} as handler for {message}")
|
||||||
return await getattr(network_handler, self.interface_name)(message)
|
return await getattr(network_handler, self.interface_name)(self, message)
|
||||||
except Exception as exc:
|
except Exception:
|
||||||
|
_, exc, tb = sys.exc_info()
|
||||||
log.debug(f"Exception {exc} in {network_handler}")
|
log.debug(f"Exception {exc} in {network_handler}")
|
||||||
return RequestError(exc)
|
return RequestError(exc=exc)
|
||||||
|
|
||||||
def _init_database(self, commands: typing.List[typing.Type[Command]], database_config: DatabaseConfig):
|
def _init_database(self, commands: typing.List[typing.Type[Command]], database_config: DatabaseConfig):
|
||||||
"""Connect to the database, and create the missing tables required by the selected commands."""
|
"""Connect to the database, and create the missing tables required by the selected commands."""
|
||||||
|
@ -74,6 +77,7 @@ class GenericBot:
|
||||||
def __init__(self, *,
|
def __init__(self, *,
|
||||||
royalnet_config: typing.Optional[RoyalnetConfig] = None,
|
royalnet_config: typing.Optional[RoyalnetConfig] = None,
|
||||||
database_config: typing.Optional[DatabaseConfig] = None,
|
database_config: typing.Optional[DatabaseConfig] = None,
|
||||||
|
command_prefix: str,
|
||||||
commands: typing.List[typing.Type[Command]] = None,
|
commands: typing.List[typing.Type[Command]] = None,
|
||||||
missing_command: typing.Type[Command] = NullCommand,
|
missing_command: typing.Type[Command] = NullCommand,
|
||||||
error_command: typing.Type[Command] = NullCommand):
|
error_command: typing.Type[Command] = NullCommand):
|
||||||
|
@ -86,7 +90,7 @@ class GenericBot:
|
||||||
self._init_database(commands=commands, database_config=database_config)
|
self._init_database(commands=commands, database_config=database_config)
|
||||||
if commands is None:
|
if commands is None:
|
||||||
commands = []
|
commands = []
|
||||||
self._init_commands(commands, missing_command=missing_command, error_command=error_command)
|
self._init_commands(command_prefix, commands, missing_command=missing_command, error_command=error_command)
|
||||||
self._Call = self._call_factory()
|
self._Call = self._call_factory()
|
||||||
if royalnet_config is None:
|
if royalnet_config is None:
|
||||||
self.network = None
|
self.network = None
|
||||||
|
@ -95,15 +99,18 @@ class GenericBot:
|
||||||
|
|
||||||
async def call(self, command_name: str, channel, parameters: typing.List[str] = None, **kwargs):
|
async def call(self, command_name: str, channel, parameters: typing.List[str] = None, **kwargs):
|
||||||
"""Call a command by its string, or missing_command if it doesn't exists, or error_command if an exception is raised during the execution."""
|
"""Call a command by its string, or missing_command if it doesn't exists, or error_command if an exception is raised during the execution."""
|
||||||
|
log.debug(f"Trying to call {command_name}")
|
||||||
if parameters is None:
|
if parameters is None:
|
||||||
parameters = []
|
parameters = []
|
||||||
try:
|
try:
|
||||||
command: typing.Type[Command] = self.commands[command_name]
|
command: typing.Type[Command] = self.commands[command_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
log.debug(f"Calling missing_command because {command_name} does not exist")
|
||||||
command = self.missing_command
|
command = self.missing_command
|
||||||
try:
|
try:
|
||||||
await self._Call(channel, command, parameters, **kwargs).run()
|
await self._Call(channel, command, parameters, **kwargs).run()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
log.debug(f"Calling error_command because of an error in {command_name}")
|
||||||
await self._Call(channel, self.error_command,
|
await self._Call(channel, self.error_command,
|
||||||
exception_info=sys.exc_info(),
|
exception_info=sys.exc_info(),
|
||||||
previous_command=command, **kwargs).run()
|
previous_command=command, **kwargs).run()
|
||||||
|
|
|
@ -3,10 +3,11 @@ import asyncio
|
||||||
import typing
|
import typing
|
||||||
import logging as _logging
|
import logging as _logging
|
||||||
import sys
|
import sys
|
||||||
|
from .generic import GenericBot
|
||||||
from ..commands import NullCommand
|
from ..commands import NullCommand
|
||||||
from ..utils import asyncify, Call, Command
|
from ..utils import asyncify, Call, Command
|
||||||
from ..error import UnregisteredError, InvalidConfigError
|
from ..error import UnregisteredError, InvalidConfigError
|
||||||
from ..network import RoyalnetLink, Message, RequestError
|
from ..network import RoyalnetLink, Message, RequestError, RoyalnetConfig
|
||||||
from ..database import Alchemy, relationshiplinkchain, DatabaseConfig
|
from ..database import Alchemy, relationshiplinkchain, DatabaseConfig
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
@ -17,48 +18,25 @@ async def todo(message: Message):
|
||||||
log.warning(f"Skipped {message} because handling isn't supported yet.")
|
log.warning(f"Skipped {message} because handling isn't supported yet.")
|
||||||
|
|
||||||
|
|
||||||
class TelegramBot:
|
class TelegramConfig:
|
||||||
def __init__(self,
|
def __init__(self, token: str):
|
||||||
api_key: str,
|
self.token: str = token
|
||||||
master_server_uri: str,
|
|
||||||
master_server_secret: str,
|
|
||||||
commands: typing.List[typing.Type[Command]],
|
class TelegramBot(GenericBot):
|
||||||
missing_command: typing.Type[Command] = NullCommand,
|
interface_name = "telegram"
|
||||||
error_command: typing.Type[Command] = NullCommand,
|
|
||||||
database_config: typing.Optional[DatabaseConfig] = None):
|
def _init_client(self):
|
||||||
self.bot: telegram.Bot = telegram.Bot(api_key)
|
self.client = telegram.Bot(self._telegram_config.token)
|
||||||
self.should_run: bool = False
|
self._offset: int = -100
|
||||||
self.offset: int = -100
|
|
||||||
self.missing_command = missing_command
|
def _call_factory(self) -> typing.Type[Call]:
|
||||||
self.error_command = error_command
|
|
||||||
self.network: RoyalnetLink = RoyalnetLink(master_server_uri, master_server_secret, "telegram", todo)
|
|
||||||
loop.create_task(self.network.run())
|
|
||||||
# Generate _commands
|
|
||||||
self.commands = {}
|
|
||||||
required_tables = set()
|
|
||||||
for command in commands:
|
|
||||||
self.commands[f"/{command.command_name}"] = command
|
|
||||||
required_tables = required_tables.union(command.require_alchemy_tables)
|
|
||||||
# Generate the Alchemy database
|
|
||||||
if database_config:
|
|
||||||
self.alchemy = Alchemy(database_config.database_uri, required_tables)
|
|
||||||
self.master_table = self.alchemy.__getattribute__(database_config.master_table.__name__)
|
|
||||||
self.identity_table = self.alchemy.__getattribute__(database_config.identity_table.__name__)
|
|
||||||
self.identity_column = self.identity_table.__getattribute__(self.identity_table, database_config.identity_column_name)
|
|
||||||
self.identity_chain = relationshiplinkchain(self.master_table, self.identity_table)
|
|
||||||
else:
|
|
||||||
if required_tables:
|
|
||||||
raise InvalidConfigError("Tables are required by the _commands, but Alchemy is not configured")
|
|
||||||
self.alchemy = None
|
|
||||||
self.master_table = None
|
|
||||||
self.identity_table = None
|
|
||||||
self.identity_column = None
|
|
||||||
self.identity_chain = None
|
|
||||||
# noinspection PyMethodParameters
|
# noinspection PyMethodParameters
|
||||||
class TelegramCall(Call):
|
class TelegramCall(Call):
|
||||||
interface_name = "telegram"
|
interface_name = self.interface_name
|
||||||
interface_obj = self
|
interface_obj = self
|
||||||
interface_prefix = "/"
|
interface_prefix = "/"
|
||||||
|
|
||||||
alchemy = self.alchemy
|
alchemy = self.alchemy
|
||||||
|
|
||||||
async def reply(call, text: str):
|
async def reply(call, text: str):
|
||||||
|
@ -75,9 +53,10 @@ class TelegramBot:
|
||||||
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):
|
||||||
response = await self.network.request(message, destination)
|
if self.network is None:
|
||||||
if isinstance(response, RequestError):
|
raise InvalidConfigError("Royalnet is not enabled on this bot")
|
||||||
raise response.exc
|
response: Message = await self.network.request(message, destination)
|
||||||
|
response.raise_on_error()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
async def get_author(call, error_if_none=False):
|
async def get_author(call, error_if_none=False):
|
||||||
|
@ -94,29 +73,26 @@ class TelegramBot:
|
||||||
result = await asyncify(query.one_or_none)
|
result = await asyncify(query.one_or_none)
|
||||||
if result is None and error_if_none:
|
if result is None and error_if_none:
|
||||||
raise UnregisteredError("Author is not registered")
|
raise UnregisteredError("Author is not registered")
|
||||||
return result
|
return TelegramCall
|
||||||
|
|
||||||
self.TelegramCall = TelegramCall
|
def __init__(self, *,
|
||||||
|
telegram_config: TelegramConfig,
|
||||||
|
royalnet_config: typing.Optional[RoyalnetConfig] = None,
|
||||||
|
database_config: typing.Optional[DatabaseConfig] = None,
|
||||||
|
command_prefix: str = "/",
|
||||||
|
commands: typing.List[typing.Type[Command]] = None,
|
||||||
|
missing_command: typing.Type[Command] = NullCommand,
|
||||||
|
error_command: typing.Type[Command] = NullCommand):
|
||||||
|
super().__init__(royalnet_config=royalnet_config,
|
||||||
|
database_config=database_config,
|
||||||
|
command_prefix=command_prefix,
|
||||||
|
commands=commands,
|
||||||
|
missing_command=missing_command,
|
||||||
|
error_command=error_command)
|
||||||
|
self._telegram_config = telegram_config
|
||||||
|
self._init_client()
|
||||||
|
|
||||||
async def run(self):
|
async def _handle_update(self, update: telegram.Update):
|
||||||
self.should_run = True
|
|
||||||
while self.should_run:
|
|
||||||
# Get the latest 100 updates
|
|
||||||
try:
|
|
||||||
last_updates: typing.List[telegram.Update] = await asyncify(self.bot.get_updates, offset=self.offset, timeout=60)
|
|
||||||
except telegram.error.TimedOut:
|
|
||||||
continue
|
|
||||||
# Handle updates
|
|
||||||
for update in last_updates:
|
|
||||||
# noinspection PyAsyncCall
|
|
||||||
asyncio.create_task(self.handle_update(update))
|
|
||||||
# Recalculate offset
|
|
||||||
try:
|
|
||||||
self.offset = last_updates[-1].update_id + 1
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def handle_update(self, update: telegram.Update):
|
|
||||||
# Skip non-message updates
|
# Skip non-message updates
|
||||||
if update.message is None:
|
if update.message is None:
|
||||||
return
|
return
|
||||||
|
@ -130,33 +106,31 @@ class TelegramBot:
|
||||||
return
|
return
|
||||||
# Find and clean parameters
|
# Find and clean parameters
|
||||||
command_text, *parameters = text.split(" ")
|
command_text, *parameters = text.split(" ")
|
||||||
command_text.replace(f"@{self.bot.username}", "")
|
command_text.replace(f"@{self.client.username}", "")
|
||||||
# Find the function
|
|
||||||
try:
|
|
||||||
command = self.commands[command_text]
|
|
||||||
except KeyError:
|
|
||||||
# Skip inexistent _commands
|
|
||||||
command = self.missing_command
|
|
||||||
# Call the command
|
# Call the command
|
||||||
# noinspection PyBroadException
|
await self.call(command_text, update.message.chat, parameters, update=update)
|
||||||
try:
|
|
||||||
return await self.TelegramCall(message.chat, command, parameters, log,
|
|
||||||
update=update).run()
|
|
||||||
except Exception as exc:
|
|
||||||
try:
|
|
||||||
return await self.TelegramCall(message.chat, self.error_command, parameters, log,
|
|
||||||
update=update,
|
|
||||||
exception_info=sys.exc_info(),
|
|
||||||
previous_command=command).run()
|
|
||||||
except Exception as exc2:
|
|
||||||
log.error(f"Exception in error handler command: {exc2}")
|
|
||||||
|
|
||||||
def generate_botfather_command_string(self):
|
async def run(self):
|
||||||
|
while True:
|
||||||
|
# Get the latest 100 updates
|
||||||
|
try:
|
||||||
|
last_updates: typing.List[telegram.Update] = await asyncify(self.client.get_updates, offset=self._offset, timeout=60)
|
||||||
|
except telegram.error.TimedOut:
|
||||||
|
continue
|
||||||
|
# Handle updates
|
||||||
|
for update in last_updates:
|
||||||
|
# noinspection PyAsyncCall
|
||||||
|
loop.create_task(self._handle_update(update))
|
||||||
|
# Recalculate offset
|
||||||
|
try:
|
||||||
|
self._offset = last_updates[-1].update_id + 1
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def botfather_command_string(self) -> str:
|
||||||
string = ""
|
string = ""
|
||||||
for command_key in self.commands:
|
for command_key in self.commands:
|
||||||
command = self.commands[command_key]
|
command = self.commands[command_key]
|
||||||
string += f"{command.command_name} - {command.command_description}\n"
|
string += f"{command.command_name} - {command.command_description}\n"
|
||||||
return string
|
return string
|
||||||
|
|
||||||
async def handle_net_request(self, message: Message):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import logging as _logging
|
import logging as _logging
|
||||||
import traceback
|
|
||||||
from ..utils import Command, Call
|
from ..utils import Command, Call
|
||||||
from ..error import NoneFoundError, \
|
from ..error import NoneFoundError, \
|
||||||
TooManyFoundError, \
|
TooManyFoundError, \
|
||||||
|
@ -21,33 +20,28 @@ class ErrorHandlerCommand(Command):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def common(cls, call: Call):
|
async def common(cls, call: Call):
|
||||||
try:
|
exception: Exception = call.kwargs["exception"]
|
||||||
e_type, e_value, e_tb = call.kwargs["exception_info"]
|
if isinstance(exception, NoneFoundError):
|
||||||
except InvalidInputError:
|
|
||||||
await call.reply("⚠️ Questo comando non può essere chiamato da solo.")
|
|
||||||
return
|
|
||||||
if e_type == NoneFoundError:
|
|
||||||
await call.reply("⚠️ L'elemento richiesto non è stato trovato.")
|
await call.reply("⚠️ L'elemento richiesto non è stato trovato.")
|
||||||
return
|
return
|
||||||
if e_type == TooManyFoundError:
|
if isinstance(exception, TooManyFoundError):
|
||||||
await call.reply("⚠️ La richiesta effettuata è ambigua, pertanto è stata annullata.")
|
await call.reply("⚠️ La richiesta effettuata è ambigua, pertanto è stata annullata.")
|
||||||
return
|
return
|
||||||
if e_type == UnregisteredError:
|
if isinstance(exception, UnregisteredError):
|
||||||
await call.reply("⚠️ Devi essere registrato a Royalnet per usare questo comando!")
|
await call.reply("⚠️ Devi essere registrato a Royalnet per usare questo comando!")
|
||||||
return
|
return
|
||||||
if e_type == UnsupportedError:
|
if isinstance(exception, UnsupportedError):
|
||||||
await call.reply("⚠️ Il comando richiesto non è disponibile tramite questa interfaccia.")
|
await call.reply("⚠️ Il comando richiesto non è disponibile tramite questa interfaccia.")
|
||||||
return
|
return
|
||||||
if e_type == InvalidInputError:
|
if isinstance(exception, InvalidInputError):
|
||||||
command = call.kwargs["previous_command"]
|
command = call.kwargs["previous_command"]
|
||||||
await call.reply(f"⚠️ Sintassi non valida.\nSintassi corretta: [c]{call.interface_prefix}{command.command_name} {command.command_syntax}[/c]")
|
await call.reply(f"⚠️ Sintassi non valida.\nSintassi corretta: [c]{call.interface_prefix}{command.command_name} {command.command_syntax}[/c]")
|
||||||
return
|
return
|
||||||
if e_type == InvalidConfigError:
|
if isinstance(exception, InvalidConfigError):
|
||||||
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 e_type == 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]{e_type.__name__}[/b]\n{e_value}")
|
await call.reply(f"❌ Eccezione non gestita durante l'esecuzione del comando:\n[b]{exception.__class__.__name__}[/b]\n{exception}")
|
||||||
formatted_tb: str = '\n'.join(traceback.format_tb(e_tb))
|
log.error(f"Unhandled exception - {exception.__class__.__name__}: {exception}")
|
||||||
log.error(f"Unhandled exception - {e_type.__name__}: {e_value}\n{formatted_tb}")
|
|
||||||
|
|
|
@ -50,6 +50,5 @@ class PlayCommand(Command):
|
||||||
@classmethod
|
@classmethod
|
||||||
async def common(cls, call: Call):
|
async def common(cls, call: Call):
|
||||||
guild, url = call.args.match(r"(?:\[(.+)])?\s*(\S+)\s*")
|
guild, url = call.args.match(r"(?:\[(.+)])?\s*(\S+)\s*")
|
||||||
response: typing.Union[RequestSuccessful, RequestError] = await call.net_request(PlayMessage(url, guild), "discord")
|
response: RequestSuccessful = await call.net_request(PlayMessage(url, guild), "discord")
|
||||||
response.raise_on_error()
|
|
||||||
await call.reply(f"✅ Richiesta la riproduzione di [c]{url}[/c].")
|
await call.reply(f"✅ Richiesta la riproduzione di [c]{url}[/c].")
|
||||||
|
|
|
@ -14,7 +14,7 @@ loop = asyncio.get_event_loop()
|
||||||
class SummonMessage(Message):
|
class SummonMessage(Message):
|
||||||
def __init__(self, channel_identifier: typing.Union[int, str],
|
def __init__(self, channel_identifier: typing.Union[int, str],
|
||||||
guild_identifier: typing.Optional[typing.Union[int, str]] = None):
|
guild_identifier: typing.Optional[typing.Union[int, str]] = None):
|
||||||
self.channel_identifier = channel_identifier
|
self.channel_name = channel_identifier
|
||||||
self.guild_identifier = guild_identifier
|
self.guild_identifier = guild_identifier
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class SummonNH(NetworkHandler):
|
||||||
@classmethod
|
@classmethod
|
||||||
async def discord(cls, bot: "DiscordBot", message: SummonMessage):
|
async def discord(cls, bot: "DiscordBot", message: SummonMessage):
|
||||||
"""Handle a summon Royalnet request. That is, join a voice channel, or move to a different one if that is not possible."""
|
"""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(message.channel_identifier)
|
channel = bot.client.find_channel_by_name(message.channel_name)
|
||||||
if not isinstance(channel, discord.VoiceChannel):
|
if not isinstance(channel, discord.VoiceChannel):
|
||||||
raise NoneFoundError("Channel is not a voice channel")
|
raise NoneFoundError("Channel is not a voice channel")
|
||||||
loop.create_task(bot.client.vc_connect_or_move(channel))
|
loop.create_task(bot.client.vc_connect_or_move(channel))
|
||||||
|
|
|
@ -22,5 +22,12 @@ class InvalidConfigError(Exception):
|
||||||
"""The bot has not been configured correctly, therefore the command can not function."""
|
"""The bot has not been configured correctly, therefore the command can not function."""
|
||||||
|
|
||||||
|
|
||||||
|
class RoyalnetError(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):
|
||||||
|
self.exc = exc
|
||||||
|
|
||||||
|
|
||||||
class ExternalError(Exception):
|
class ExternalError(Exception):
|
||||||
"""Something went wrong in a non-Royalnet component and the command execution cannot be completed."""
|
"""Something went wrong in a non-Royalnet component and the command execution cannot be completed."""
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
import traceback
|
||||||
|
from ..error import RoyalnetError
|
||||||
|
|
||||||
|
|
||||||
class Message:
|
class Message:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}>"
|
return f"<{self.__class__.__name__}>"
|
||||||
|
@ -34,7 +38,7 @@ class RequestSuccessful(Message):
|
||||||
|
|
||||||
class RequestError(Message):
|
class RequestError(Message):
|
||||||
def __init__(self, exc: Exception):
|
def __init__(self, exc: Exception):
|
||||||
self.exc = exc
|
self.exc: Exception = exc
|
||||||
|
|
||||||
def raise_on_error(self):
|
def raise_on_error(self):
|
||||||
raise self.exc
|
raise RoyalnetError(exc=self.exc)
|
||||||
|
|
Loading…
Reference in a new issue