mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
5.0a59: beeg update
This commit is contained in:
parent
b1ee018254
commit
9b1c8704bd
50 changed files with 384 additions and 259 deletions
16
README.md
16
README.md
|
@ -6,16 +6,10 @@ It has a lot of submodules, many of which may be used in other bots.
|
||||||
|
|
||||||
[Documentation available here](https://royal-games.github.io/royalnet/html/index.html).
|
[Documentation available here](https://royal-games.github.io/royalnet/html/index.html).
|
||||||
|
|
||||||
## Installation for the Royal Games community
|
## Quick install
|
||||||
|
|
||||||
With `python3.7` and `pip` installed, run:
|
```
|
||||||
|
pip3.7 install royalnet
|
||||||
```bash
|
python3.7 -m royalnet.configurator
|
||||||
pip install royalnet
|
python3.7 -m royalnet -c (YOUR_COMMAND_PACK)
|
||||||
pip install websockets --upgrade
|
|
||||||
export TG_AK="Telegram API Key"
|
|
||||||
export DS_AK="Discord API Key"
|
|
||||||
export DB_PATH="sqlalchemy database path to postgresql"
|
|
||||||
export MASTER_KEY="A secret password to connect to the network"
|
|
||||||
python3.7 -m royalnet.royalgames -OO
|
|
||||||
```
|
```
|
|
@ -1,4 +1,4 @@
|
||||||
bcrypt
|
bcrypt>=3.1.7
|
||||||
python-telegram-bot>=11.1.0
|
python-telegram-bot>=11.1.0
|
||||||
websockets>=7.0
|
websockets>=7.0
|
||||||
pytest>=4.3.1
|
pytest>=4.3.1
|
||||||
|
@ -20,3 +20,5 @@ mcstatus>=2.2.1
|
||||||
sortedcontainers>=2.1.0
|
sortedcontainers>=2.1.0
|
||||||
sentry-sdk>=0.11.1
|
sentry-sdk>=0.11.1
|
||||||
click>=7.0
|
click>=7.0
|
||||||
|
keyring>=19.2.0
|
||||||
|
urllib3>=1.25.6
|
||||||
|
|
134
royalnet/__main__.py
Normal file
134
royalnet/__main__.py
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import click
|
||||||
|
import typing
|
||||||
|
import importlib
|
||||||
|
import royalnet as r
|
||||||
|
import multiprocessing
|
||||||
|
import keyring
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option("--telegram/--no-telegram", default=None,
|
||||||
|
help="Enable/disable the Telegram module.")
|
||||||
|
@click.option("--discord/--no-discord", default=None,
|
||||||
|
help="Enable/disable the Discord module.")
|
||||||
|
@click.option("-d", "--database", type=str, default=None,
|
||||||
|
help="The PostgreSQL database path.")
|
||||||
|
@click.option("-c", "--command-packs", type=str, multiple=True, default=[],
|
||||||
|
help="The names of the command pack modules that should be imported.")
|
||||||
|
@click.option("-n", "--network-address", type=str, default=None,
|
||||||
|
help="The Network server URL to connect to.")
|
||||||
|
@click.option("--local-network-server/--no-local-network-server", default=True,
|
||||||
|
help="Locally run a Network server, bind it to port 44444.")
|
||||||
|
@click.option("-s", "--secrets-name", type=str, default="__default__",
|
||||||
|
help="The name in the keyring that the secrets are stored with.")
|
||||||
|
def run(telegram: typing.Optional[bool],
|
||||||
|
discord: typing.Optional[bool],
|
||||||
|
database: typing.Optional[str],
|
||||||
|
command_packs: typing.List[str],
|
||||||
|
network_address: typing.Optional[str],
|
||||||
|
local_network_server: bool,
|
||||||
|
secrets_name: str):
|
||||||
|
|
||||||
|
# Get the network password
|
||||||
|
network_password = keyring.get_password(f"Royalnet/{secrets_name}", "network")
|
||||||
|
|
||||||
|
# Get the sentry dsn
|
||||||
|
sentry_dsn = keyring.get_password(f"Royalnet/{secrets_name}", "sentry")
|
||||||
|
|
||||||
|
# Enable / Disable interfaces
|
||||||
|
interfaces = {
|
||||||
|
"telegram": telegram,
|
||||||
|
"discord": discord
|
||||||
|
}
|
||||||
|
# If any interface is True, then the undefined ones should be False
|
||||||
|
if any(interfaces[name] is True for name in interfaces):
|
||||||
|
for name in interfaces:
|
||||||
|
if interfaces[name] is None:
|
||||||
|
interfaces[name] = False
|
||||||
|
# Likewise, if any interface is False, then the undefined ones should be True
|
||||||
|
elif any(interfaces[name] is False for name in interfaces):
|
||||||
|
for name in interfaces:
|
||||||
|
if interfaces[name] is None:
|
||||||
|
interfaces[name] = True
|
||||||
|
# Otherwise, if no interfaces are specified, all should be enabled
|
||||||
|
else:
|
||||||
|
assert all(interfaces[name] is None for name in interfaces)
|
||||||
|
for name in interfaces:
|
||||||
|
interfaces[name] = True
|
||||||
|
|
||||||
|
server_process: typing.Optional[multiprocessing.Process] = None
|
||||||
|
# Start the network server
|
||||||
|
if local_network_server:
|
||||||
|
server_process = multiprocessing.Process(name="Network Server",
|
||||||
|
target=r.network.NetworkServer("0.0.0.0",
|
||||||
|
44444,
|
||||||
|
network_password).run_blocking(),
|
||||||
|
daemon=True)
|
||||||
|
server_process.start()
|
||||||
|
network_address = "ws://127.0.0.1:44444/"
|
||||||
|
|
||||||
|
# Create a Royalnet configuration
|
||||||
|
network_config: typing.Optional[r.network.NetworkConfig] = None
|
||||||
|
if network_address is not None:
|
||||||
|
network_config = r.network.NetworkConfig(network_address, network_password)
|
||||||
|
|
||||||
|
# Create a Alchemy configuration
|
||||||
|
telegram_db_config: typing.Optional[r.database.DatabaseConfig] = None
|
||||||
|
discord_db_config: typing.Optional[r.database.DatabaseConfig] = None
|
||||||
|
if database is not None:
|
||||||
|
telegram_db_config = r.database.DatabaseConfig(database,
|
||||||
|
r.database.tables.User,
|
||||||
|
r.database.tables.Telegram,
|
||||||
|
"tg_id")
|
||||||
|
discord_db_config = r.database.DatabaseConfig(database,
|
||||||
|
r.database.tables.User,
|
||||||
|
r.database.tables.Discord,
|
||||||
|
"discord_id")
|
||||||
|
|
||||||
|
# Import command packs
|
||||||
|
if not command_packs:
|
||||||
|
raise click.ClickException("No command packs were specified.")
|
||||||
|
enabled_commands = []
|
||||||
|
for pack in command_packs:
|
||||||
|
imported = importlib.import_module(pack)
|
||||||
|
try:
|
||||||
|
imported_commands = imported.commands
|
||||||
|
except AttributeError:
|
||||||
|
raise click.ClickException(f"{pack} isn't a Royalnet command pack.")
|
||||||
|
enabled_commands = [*enabled_commands, *imported_commands]
|
||||||
|
|
||||||
|
telegram_process: typing.Optional[multiprocessing.Process] = None
|
||||||
|
if interfaces["telegram"]:
|
||||||
|
telegram_bot = r.bots.TelegramBot(network_config=network_config,
|
||||||
|
database_config=telegram_db_config,
|
||||||
|
sentry_dsn=sentry_dsn,
|
||||||
|
commands=enabled_commands,
|
||||||
|
secrets_name=secrets_name)
|
||||||
|
telegram_process = multiprocessing.Process(name="Telegram Interface",
|
||||||
|
target=telegram_bot.run_blocking,
|
||||||
|
daemon=True)
|
||||||
|
telegram_process.start()
|
||||||
|
|
||||||
|
discord_process: typing.Optional[multiprocessing.Process] = None
|
||||||
|
if interfaces["discord"]:
|
||||||
|
discord_bot = r.bots.DiscordBot(network_config=network_config,
|
||||||
|
database_config=discord_db_config,
|
||||||
|
sentry_dsn=sentry_dsn,
|
||||||
|
commands=enabled_commands,
|
||||||
|
secrets_name=secrets_name)
|
||||||
|
discord_process = multiprocessing.Process(name="Discord Interface",
|
||||||
|
target=discord_bot.run_blocking,
|
||||||
|
daemon=True)
|
||||||
|
discord_process.start()
|
||||||
|
|
||||||
|
click.echo("Royalnet processes have been started. You can force-quit by pressing Ctrl+C.")
|
||||||
|
if server_process is not None:
|
||||||
|
server_process.join()
|
||||||
|
if telegram_process is not None:
|
||||||
|
telegram_process.join()
|
||||||
|
if discord_process is not None:
|
||||||
|
discord_process.join()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run()
|
|
@ -24,7 +24,7 @@ class YtdlDiscord:
|
||||||
ffmpeg.input(self.ytdl_file.filename)
|
ffmpeg.input(self.ytdl_file.filename)
|
||||||
.output(destination_filename, format="s16le", ac=2, ar="48000")
|
.output(destination_filename, format="s16le", ac=2, ar="48000")
|
||||||
.overwrite_output()
|
.overwrite_output()
|
||||||
.run(quiet=True)
|
.run_async(quiet=True)
|
||||||
)
|
)
|
||||||
self.pcm_filename = destination_filename
|
self.pcm_filename = destination_filename
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ class YtdlMp3:
|
||||||
ffmpeg.input(self.ytdl_file.filename)
|
ffmpeg.input(self.ytdl_file.filename)
|
||||||
.output(destination_filename, format="mp3")
|
.output(destination_filename, format="mp3")
|
||||||
.overwrite_output()
|
.overwrite_output()
|
||||||
.run()
|
.run_async()
|
||||||
)
|
)
|
||||||
self.mp3_filename = destination_filename
|
self.mp3_filename = destination_filename
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Various bot interfaces, and a generic class to create new ones."""
|
"""Various bot interfaces, and a generic class to create new ones."""
|
||||||
|
|
||||||
from .generic import GenericBot
|
from .generic import GenericBot
|
||||||
from .telegram import TelegramBot, TelegramConfig
|
from .telegram import TelegramBot
|
||||||
from .discord import DiscordBot, DiscordConfig
|
from .discord import DiscordBot
|
||||||
|
|
||||||
__all__ = ["TelegramBot", "TelegramConfig", "DiscordBot", "DiscordConfig", "GenericBot"]
|
__all__ = ["TelegramBot", "DiscordBot", "GenericBot"]
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import discord
|
import discord
|
||||||
import typing
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
import logging as _logging
|
import logging as _logging
|
||||||
from .generic import GenericBot
|
from .generic import GenericBot
|
||||||
from ..utils import *
|
from ..utils import *
|
||||||
from ..error import *
|
from ..error import *
|
||||||
from ..network import *
|
|
||||||
from ..database import *
|
|
||||||
from ..audio import *
|
from ..audio import *
|
||||||
from ..commands import *
|
from ..commands import *
|
||||||
|
|
||||||
|
@ -17,12 +14,6 @@ if not discord.opus.is_loaded():
|
||||||
log.error("Opus is not loaded. Weird behaviour might emerge.")
|
log.error("Opus is not loaded. Weird behaviour might emerge.")
|
||||||
|
|
||||||
|
|
||||||
class DiscordConfig:
|
|
||||||
"""The specific configuration to be used for :py:class:`royalnet.bots.DiscordBot`."""
|
|
||||||
def __init__(self, token: str):
|
|
||||||
self.token = token
|
|
||||||
|
|
||||||
|
|
||||||
class DiscordBot(GenericBot):
|
class DiscordBot(GenericBot):
|
||||||
"""A bot that connects to `Discord <https://discordapp.com/>`_."""
|
"""A bot that connects to `Discord <https://discordapp.com/>`_."""
|
||||||
interface_name = "discord"
|
interface_name = "discord"
|
||||||
|
@ -204,24 +195,19 @@ class DiscordBot(GenericBot):
|
||||||
self._Client = self._bot_factory()
|
self._Client = self._bot_factory()
|
||||||
self.client = self._Client()
|
self.client = self._Client()
|
||||||
|
|
||||||
def __init__(self, *,
|
def _initialize(self):
|
||||||
discord_config: DiscordConfig,
|
super()._initialize()
|
||||||
royalnet_config: typing.Optional[RoyalnetConfig] = None,
|
|
||||||
database_config: typing.Optional[DatabaseConfig] = None,
|
|
||||||
sentry_dsn: typing.Optional[str] = None,
|
|
||||||
commands: typing.List[typing.Type[Command]] = None):
|
|
||||||
super().__init__(royalnet_config=royalnet_config,
|
|
||||||
database_config=database_config,
|
|
||||||
sentry_dsn=sentry_dsn,
|
|
||||||
commands=commands)
|
|
||||||
self._discord_config = discord_config
|
|
||||||
self._init_client()
|
self._init_client()
|
||||||
self._init_voice()
|
self._init_voice()
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""Login to Discord, then run the bot."""
|
"""Login to Discord, then run the bot."""
|
||||||
|
if not self.initialized:
|
||||||
|
self._initialize()
|
||||||
|
log.debug("Getting Discord secret")
|
||||||
|
token = self.get_secret("discord")
|
||||||
log.info(f"Logging in to Discord")
|
log.info(f"Logging in to Discord")
|
||||||
await self.client.login(self._discord_config.token)
|
await self.client.login(token)
|
||||||
log.info(f"Connecting to Discord")
|
log.info(f"Connecting to Discord")
|
||||||
await self.client.connect()
|
await self.client.connect()
|
||||||
# TODO: how to stop?
|
# TODO: how to stop?
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import sys
|
import sys
|
||||||
import typing
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
|
import keyring
|
||||||
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
|
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
|
||||||
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
|
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
|
||||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||||
|
@ -21,7 +21,7 @@ class GenericBot:
|
||||||
:py:class:`royalnet.bots.TelegramBot` and :py:class:`royalnet.bots.DiscordBot`. """
|
:py:class:`royalnet.bots.TelegramBot` and :py:class:`royalnet.bots.DiscordBot`. """
|
||||||
interface_name = NotImplemented
|
interface_name = NotImplemented
|
||||||
|
|
||||||
def _init_commands(self, commands: typing.List[typing.Type[Command]]) -> None:
|
def _init_commands(self) -> None:
|
||||||
"""Generate the ``commands`` dictionary required to handle incoming messages, and the ``network_handlers``
|
"""Generate the ``commands`` dictionary required to handle incoming messages, and the ``network_handlers``
|
||||||
dictionary required to handle incoming requests. """
|
dictionary required to handle incoming requests. """
|
||||||
log.debug(f"Now binding commands")
|
log.debug(f"Now binding commands")
|
||||||
|
@ -29,13 +29,13 @@ class GenericBot:
|
||||||
self._Data = self._data_factory()
|
self._Data = self._data_factory()
|
||||||
self.commands = {}
|
self.commands = {}
|
||||||
self.network_handlers: typing.Dict[str, typing.Type[NetworkHandler]] = {}
|
self.network_handlers: typing.Dict[str, typing.Type[NetworkHandler]] = {}
|
||||||
for SelectedCommand in commands:
|
for SelectedCommand in self.uninitialized_commands:
|
||||||
log.debug(f"Binding {SelectedCommand.name}...")
|
log.debug(f"Binding {SelectedCommand.name}...")
|
||||||
interface = self._Interface()
|
interface = self._Interface()
|
||||||
try:
|
try:
|
||||||
self.commands[f"{interface.prefix}{SelectedCommand.name}"] = SelectedCommand(interface)
|
self.commands[f"{interface.prefix}{SelectedCommand.name}"] = SelectedCommand(interface)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"{e} during the initialization of {SelectedCommand.name}, skipping...")
|
log.error(f"{e.__class__.__name__} during the initialization of {SelectedCommand.name}, skipping...")
|
||||||
log.debug(f"Successfully bound commands")
|
log.debug(f"Successfully bound commands")
|
||||||
|
|
||||||
def _interface_factory(self) -> typing.Type[CommandInterface]:
|
def _interface_factory(self) -> typing.Type[CommandInterface]:
|
||||||
|
@ -71,15 +71,17 @@ class GenericBot:
|
||||||
def _data_factory(self) -> typing.Type[CommandData]:
|
def _data_factory(self) -> typing.Type[CommandData]:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _init_royalnet(self, royalnet_config: RoyalnetConfig):
|
def _init_network(self):
|
||||||
"""Create a :py:class:`royalnet.network.RoyalnetLink`, and run it as a :py:class:`asyncio.Task`."""
|
"""Create a :py:class:`royalnet.network.NetworkLink`, and run it as a :py:class:`asyncio.Task`."""
|
||||||
self.network: RoyalnetLink = RoyalnetLink(royalnet_config.master_uri, royalnet_config.master_secret,
|
if self.uninitialized_network_config is not None:
|
||||||
|
self.network: NetworkLink = NetworkLink(self.uninitialized_network_config.master_uri,
|
||||||
|
self.uninitialized_network_config.master_secret,
|
||||||
self.interface_name, self._network_handler)
|
self.interface_name, self._network_handler)
|
||||||
log.debug(f"Running RoyalnetLink {self.network}")
|
log.debug(f"Running NetworkLink {self.network}")
|
||||||
self.loop.create_task(self.network.run())
|
self.loop.create_task(self.network.run())
|
||||||
|
|
||||||
async def _network_handler(self, request_dict: dict) -> dict:
|
async def _network_handler(self, request_dict: dict) -> dict:
|
||||||
"""Handle a single :py:class:`dict` received from the :py:class:`royalnet.network.RoyalnetLink`.
|
"""Handle a single :py:class:`dict` received from the :py:class:`royalnet.network.NetworkLink`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Another :py:class:`dict`, formatted as a :py:class:`royalnet.network.Response`."""
|
Another :py:class:`dict`, formatted as a :py:class:`royalnet.network.Response`."""
|
||||||
|
@ -91,7 +93,7 @@ class GenericBot:
|
||||||
return ResponseError("invalid_request",
|
return ResponseError("invalid_request",
|
||||||
f"The Request that you sent was invalid. Check extra_info to see what you sent.",
|
f"The Request that you sent was invalid. Check extra_info to see what you sent.",
|
||||||
extra_info={"you_sent": request_dict}).to_dict()
|
extra_info={"you_sent": request_dict}).to_dict()
|
||||||
log.debug(f"Received {request} from the RoyalnetLink")
|
log.debug(f"Received {request} from the NetworkLink")
|
||||||
try:
|
try:
|
||||||
network_handler = self.network_handlers[request.handler]
|
network_handler = self.network_handlers[request.handler]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -115,59 +117,79 @@ class GenericBot:
|
||||||
"str": str(exc)
|
"str": str(exc)
|
||||||
}).to_dict()
|
}).to_dict()
|
||||||
|
|
||||||
def _init_database(self, commands: typing.List[typing.Type[Command]], database_config: DatabaseConfig):
|
def _init_database(self):
|
||||||
"""Create an :py:class:`royalnet.database.Alchemy` with the tables required by the commands. Then,
|
"""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``. """
|
find the chain that links the ``master_table`` to the ``identity_table``. """
|
||||||
|
if self.uninitialized_database_config:
|
||||||
log.debug(f"Initializing database")
|
log.debug(f"Initializing database")
|
||||||
required_tables = {database_config.master_table, database_config.identity_table}
|
required_tables = {self.uninitialized_database_config.master_table, self.uninitialized_database_config.identity_table}
|
||||||
for command in commands:
|
for command in self.uninitialized_commands:
|
||||||
required_tables = required_tables.union(command.require_alchemy_tables)
|
required_tables = required_tables.union(command.require_alchemy_tables)
|
||||||
log.debug(f"Found {len(required_tables)} required tables")
|
log.debug(f"Found {len(required_tables)} required tables")
|
||||||
self.alchemy = Alchemy(database_config.database_uri, required_tables)
|
self.alchemy = Alchemy(self.uninitialized_database_config.database_uri, required_tables)
|
||||||
self.master_table = self.alchemy.__getattribute__(database_config.master_table.__name__)
|
self.master_table = self.alchemy.__getattribute__(self.uninitialized_database_config.master_table.__name__)
|
||||||
self.identity_table = self.alchemy.__getattribute__(database_config.identity_table.__name__)
|
self.identity_table = self.alchemy.__getattribute__(self.uninitialized_database_config.identity_table.__name__)
|
||||||
self.identity_column = self.identity_table.__getattribute__(self.identity_table,
|
self.identity_column = self.identity_table.__getattribute__(self.identity_table,
|
||||||
database_config.identity_column_name)
|
self.uninitialized_database_config.identity_column_name)
|
||||||
self.identity_chain = relationshiplinkchain(self.master_table, self.identity_table)
|
self.identity_chain = relationshiplinkchain(self.master_table, self.identity_table)
|
||||||
log.debug(f"Identity chain is {self.identity_chain}")
|
log.debug(f"Identity chain is {self.identity_chain}")
|
||||||
|
|
||||||
def __init__(self, *,
|
|
||||||
royalnet_config: typing.Optional[RoyalnetConfig] = None,
|
|
||||||
database_config: typing.Optional[DatabaseConfig] = None,
|
|
||||||
commands: typing.List[typing.Type[Command]] = None,
|
|
||||||
sentry_dsn: typing.Optional[str] = None,
|
|
||||||
loop: asyncio.AbstractEventLoop = None):
|
|
||||||
if loop is None:
|
|
||||||
self.loop = asyncio.get_event_loop()
|
|
||||||
else:
|
else:
|
||||||
self.loop = loop
|
log.debug(f"Database is not enabled, setting everything to None")
|
||||||
if sentry_dsn:
|
|
||||||
log.debug("Sentry integration enabled")
|
|
||||||
self.sentry = sentry_sdk.init(sentry_dsn, integrations=[AioHttpIntegration(),
|
|
||||||
SqlalchemyIntegration(),
|
|
||||||
LoggingIntegration(event_level=None)])
|
|
||||||
else:
|
|
||||||
log.debug("Sentry integration disabled")
|
|
||||||
try:
|
|
||||||
if database_config is None:
|
|
||||||
self.alchemy = None
|
self.alchemy = None
|
||||||
self.master_table = None
|
self.master_table = None
|
||||||
self.identity_table = None
|
self.identity_table = None
|
||||||
self.identity_column = None
|
self.identity_column = None
|
||||||
else:
|
|
||||||
self._init_database(commands=commands, database_config=database_config)
|
|
||||||
if commands is None:
|
|
||||||
commands = []
|
|
||||||
self._init_commands(commands)
|
|
||||||
if royalnet_config is None:
|
|
||||||
self.network = None
|
|
||||||
else:
|
|
||||||
self._init_royalnet(royalnet_config=royalnet_config)
|
|
||||||
except Exception as e:
|
|
||||||
sentry_sdk.capture_exception(e)
|
|
||||||
log.error(f"{e.__class__.__name__} while initializing Royalnet: {' | '.join(e.args)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def run(self):
|
def _init_sentry(self):
|
||||||
|
if self.uninitialized_sentry_dsn:
|
||||||
|
log.debug("Sentry integration enabled")
|
||||||
|
self.sentry = sentry_sdk.init(self.uninitialized_sentry_dsn,
|
||||||
|
integrations=[AioHttpIntegration(),
|
||||||
|
SqlalchemyIntegration(),
|
||||||
|
LoggingIntegration(event_level=None)])
|
||||||
|
else:
|
||||||
|
log.debug("Sentry integration disabled")
|
||||||
|
|
||||||
|
def _init_loop(self):
|
||||||
|
if self.uninitialized_loop is None:
|
||||||
|
self.loop = asyncio.get_event_loop()
|
||||||
|
else:
|
||||||
|
self.loop = self.uninitialized_loop
|
||||||
|
|
||||||
|
def __init__(self, *,
|
||||||
|
network_config: typing.Optional[NetworkConfig] = None,
|
||||||
|
database_config: typing.Optional[DatabaseConfig] = None,
|
||||||
|
commands: typing.List[typing.Type[Command]] = None,
|
||||||
|
sentry_dsn: typing.Optional[str] = None,
|
||||||
|
loop: asyncio.AbstractEventLoop = None,
|
||||||
|
secrets_name: str = "__default__"):
|
||||||
|
self.initialized = False
|
||||||
|
self.uninitialized_network_config = network_config
|
||||||
|
self.uninitialized_database_config = database_config
|
||||||
|
self.uninitialized_commands = commands
|
||||||
|
self.uninitialized_sentry_dsn = sentry_dsn
|
||||||
|
self.uninitialized_loop = loop
|
||||||
|
self.secrets_name = secrets_name
|
||||||
|
|
||||||
|
def get_secret(self, username: str):
|
||||||
|
return keyring.get_password(f"Royalnet/{self.secrets_name}", username)
|
||||||
|
|
||||||
|
def set_secret(self, username: str, password: str):
|
||||||
|
return keyring.set_password(f"Royalnet/{self.secrets_name}", username, password)
|
||||||
|
|
||||||
|
def _initialize(self):
|
||||||
|
if not self.initialized:
|
||||||
|
self._init_sentry()
|
||||||
|
self._init_loop()
|
||||||
|
self._init_database()
|
||||||
|
self._init_commands()
|
||||||
|
self._init_network()
|
||||||
|
self.initialized = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
"""A blocking coroutine that should make the bot start listening to commands and requests."""
|
"""A blocking coroutine that should make the bot start listening to commands and requests."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def run_blocking(self):
|
||||||
|
self._initialize()
|
||||||
|
self.loop.run_until_complete(self.run())
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import telegram
|
import telegram
|
||||||
import telegram.utils.request
|
import telegram.utils.request
|
||||||
import typing
|
|
||||||
import uuid
|
import uuid
|
||||||
import urllib3
|
import urllib3
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -9,20 +8,12 @@ import logging as _logging
|
||||||
from .generic import GenericBot
|
from .generic import GenericBot
|
||||||
from ..utils import *
|
from ..utils import *
|
||||||
from ..error import *
|
from ..error import *
|
||||||
from ..network import *
|
|
||||||
from ..database import *
|
|
||||||
from ..commands import *
|
from ..commands import *
|
||||||
|
|
||||||
|
|
||||||
log = _logging.getLogger(__name__)
|
log = _logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TelegramConfig:
|
|
||||||
"""The specific configuration to be used for :py:class:`royalnet.database.TelegramBot`."""
|
|
||||||
def __init__(self, token: str):
|
|
||||||
self.token: str = token
|
|
||||||
|
|
||||||
|
|
||||||
class TelegramBot(GenericBot):
|
class TelegramBot(GenericBot):
|
||||||
"""A bot that connects to `Telegram <https://telegram.org/>`_."""
|
"""A bot that connects to `Telegram <https://telegram.org/>`_."""
|
||||||
interface_name = "telegram"
|
interface_name = "telegram"
|
||||||
|
@ -30,8 +21,9 @@ class TelegramBot(GenericBot):
|
||||||
def _init_client(self):
|
def _init_client(self):
|
||||||
"""Create the :py:class:`telegram.Bot`, and set the starting offset."""
|
"""Create the :py:class:`telegram.Bot`, and set the starting offset."""
|
||||||
# https://github.com/python-telegram-bot/python-telegram-bot/issues/341
|
# https://github.com/python-telegram-bot/python-telegram-bot/issues/341
|
||||||
request = telegram.utils.request.Request(20, read_timeout=15)
|
request = telegram.utils.request.Request(5, read_timeout=30)
|
||||||
self.client = telegram.Bot(self._telegram_config.token, request=request)
|
token = self.get_secret("telegram")
|
||||||
|
self.client = telegram.Bot(token, request=request)
|
||||||
self._offset: int = -100
|
self._offset: int = -100
|
||||||
|
|
||||||
def _interface_factory(self) -> typing.Type[CommandInterface]:
|
def _interface_factory(self) -> typing.Type[CommandInterface]:
|
||||||
|
@ -109,19 +101,6 @@ class TelegramBot(GenericBot):
|
||||||
|
|
||||||
return TelegramData
|
return TelegramData
|
||||||
|
|
||||||
def __init__(self, *,
|
|
||||||
telegram_config: TelegramConfig,
|
|
||||||
royalnet_config: typing.Optional[RoyalnetConfig] = None,
|
|
||||||
database_config: typing.Optional[DatabaseConfig] = None,
|
|
||||||
sentry_dsn: typing.Optional[str] = None,
|
|
||||||
commands: typing.List[typing.Type[Command]] = None):
|
|
||||||
super().__init__(royalnet_config=royalnet_config,
|
|
||||||
database_config=database_config,
|
|
||||||
sentry_dsn=sentry_dsn,
|
|
||||||
commands=commands)
|
|
||||||
self._telegram_config = telegram_config
|
|
||||||
self._init_client()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def safe_api_call(f: typing.Callable, *args, **kwargs) -> typing.Optional:
|
async def safe_api_call(f: typing.Callable, *args, **kwargs) -> typing.Optional:
|
||||||
while True:
|
while True:
|
||||||
|
@ -225,7 +204,13 @@ class TelegramBot(GenericBot):
|
||||||
else:
|
else:
|
||||||
await self.safe_api_call(query.answer, text=response)
|
await self.safe_api_call(query.answer, text=response)
|
||||||
|
|
||||||
|
def _initialize(self):
|
||||||
|
super()._initialize()
|
||||||
|
self._init_client()
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
|
if not self.initialized:
|
||||||
|
self._initialize()
|
||||||
while True:
|
while True:
|
||||||
# Get the latest 100 updates
|
# Get the latest 100 updates
|
||||||
last_updates: typing.List[telegram.Update] = await self.safe_api_call(self.client.get_updates,
|
last_updates: typing.List[telegram.Update] = await self.safe_api_call(self.client.get_updates,
|
||||||
|
|
|
@ -28,7 +28,7 @@ class CommandInterface:
|
||||||
raise UnsupportedError()
|
raise UnsupportedError()
|
||||||
|
|
||||||
async def net_request(self, message, destination: str) -> dict:
|
async def net_request(self, message, destination: str) -> dict:
|
||||||
"""Send data through a :py:class:`royalnet.network.RoyalnetLink` and wait for a
|
"""Send data through a :py:class:`royalnet.network.NetworkLink` and wait for a
|
||||||
:py:class:`royalnet.network.Reply`.
|
:py:class:`royalnet.network.Reply`.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Commands that can be used in bots.
|
"""Commands that can be used in bots.
|
||||||
|
|
||||||
These probably won't suit your needs, as they are tailored for the bots of the Royal Games gaming community, but they
|
These probably won't suit your needs, as they are tailored for the bots of the User Games gaming community, but they
|
||||||
may be useful to develop new ones."""
|
may be useful to develop new ones."""
|
||||||
|
|
||||||
from .ciaoruozi import CiaoruoziCommand
|
from .ciaoruozi import CiaoruoziCommand
|
||||||
|
|
|
@ -8,7 +8,7 @@ from ..commanddata import CommandData
|
||||||
class CiaoruoziCommand(Command):
|
class CiaoruoziCommand(Command):
|
||||||
name: str = "ciaoruozi"
|
name: str = "ciaoruozi"
|
||||||
|
|
||||||
description: str = "Saluta Ruozi, un leggendario essere che una volta era in Royal Games."
|
description: str = "Saluta Ruozi, un leggendario essere che una volta era in User Games."
|
||||||
|
|
||||||
syntax: str = ""
|
syntax: str = ""
|
||||||
|
|
||||||
|
|
|
@ -2,26 +2,21 @@ import typing
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
import telegram
|
import telegram
|
||||||
import os
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from ..command import Command
|
from ..command import Command
|
||||||
from ..commandargs import CommandArgs
|
from ..commandargs import CommandArgs
|
||||||
from ..commanddata import CommandData
|
from ..commanddata import CommandData
|
||||||
from ...database.tables import Royal, Diario, Alias
|
from ...database.tables import User, Diario, Alias
|
||||||
from ...utils import asyncify
|
from ...utils import asyncify
|
||||||
from ...error import *
|
from ...error import *
|
||||||
|
|
||||||
|
|
||||||
async def to_imgur(photosizes: typing.List[telegram.PhotoSize], caption="") -> str:
|
async def to_imgur(imgur_api_key, photosizes: typing.List[telegram.PhotoSize], caption="") -> str:
|
||||||
# Select the largest photo
|
# Select the largest photo
|
||||||
largest_photo = sorted(photosizes, key=lambda p: p.width * p.height)[-1]
|
largest_photo = sorted(photosizes, key=lambda p: p.width * p.height)[-1]
|
||||||
# Get the photo url
|
# Get the photo url
|
||||||
photo_file: telegram.File = await asyncify(largest_photo.get_file)
|
photo_file: telegram.File = await asyncify(largest_photo.get_file)
|
||||||
# Forward the url to imgur, as an upload
|
# Forward the url to imgur, as an upload
|
||||||
try:
|
|
||||||
imgur_api_key = os.environ["IMGUR_CLIENT_ID"]
|
|
||||||
except KeyError:
|
|
||||||
raise InvalidConfigError("Missing IMGUR_CLIENT_ID envvar, can't upload images to imgur.")
|
|
||||||
async with aiohttp.request("post", "https://api.imgur.com/3/upload", data={
|
async with aiohttp.request("post", "https://api.imgur.com/3/upload", data={
|
||||||
"image": photo_file.file_path,
|
"image": photo_file.file_path,
|
||||||
"type": "URL",
|
"type": "URL",
|
||||||
|
@ -43,7 +38,7 @@ class DiarioCommand(Command):
|
||||||
|
|
||||||
syntax = "[!] \"(testo)\" --[autore], [contesto]"
|
syntax = "[!] \"(testo)\" --[autore], [contesto]"
|
||||||
|
|
||||||
require_alchemy_tables = {Royal, Diario, Alias}
|
require_alchemy_tables = {User, Diario, Alias}
|
||||||
|
|
||||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||||
if self.interface.name == "telegram":
|
if self.interface.name == "telegram":
|
||||||
|
@ -74,7 +69,8 @@ class DiarioCommand(Command):
|
||||||
if photosizes:
|
if photosizes:
|
||||||
# Text is a caption
|
# Text is a caption
|
||||||
text = reply.caption
|
text = reply.caption
|
||||||
media_url = await to_imgur(photosizes, text if text is not None else "")
|
media_url = await to_imgur(self.interface.bot.get_secret("imgur"),
|
||||||
|
photosizes, text if text is not None else "")
|
||||||
else:
|
else:
|
||||||
media_url = None
|
media_url = None
|
||||||
# Ensure there is a text or an image
|
# Ensure there is a text or an image
|
||||||
|
@ -101,7 +97,8 @@ class DiarioCommand(Command):
|
||||||
# Check if there's an image associated with the reply
|
# Check if there's an image associated with the reply
|
||||||
photosizes: typing.Optional[typing.List[telegram.PhotoSize]] = message.photo
|
photosizes: typing.Optional[typing.List[telegram.PhotoSize]] = message.photo
|
||||||
if photosizes:
|
if photosizes:
|
||||||
media_url = await to_imgur(photosizes, raw_text if raw_text is not None else "")
|
media_url = await to_imgur(self.interface.bot.get_secret("imgur"),
|
||||||
|
photosizes, raw_text if raw_text is not None else "")
|
||||||
else:
|
else:
|
||||||
media_url = None
|
media_url = None
|
||||||
# Parse the text, if it exists
|
# Parse the text, if it exists
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
import dateparser
|
import dateparser
|
||||||
import os
|
|
||||||
import telegram
|
import telegram
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
import re
|
||||||
|
@ -98,7 +97,7 @@ class MmCommand(Command):
|
||||||
try:
|
try:
|
||||||
await self.interface.bot.safe_api_call(client.edit_message_text,
|
await self.interface.bot.safe_api_call(client.edit_message_text,
|
||||||
text=telegram_escape(self._main_text(mmevent)),
|
text=telegram_escape(self._main_text(mmevent)),
|
||||||
chat_id=os.environ["MM_CHANNEL_ID"],
|
chat_id=-1001224004974,
|
||||||
message_id=mmevent.message_id,
|
message_id=mmevent.message_id,
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
disable_web_page_preview=True,
|
disable_web_page_preview=True,
|
||||||
|
@ -384,7 +383,7 @@ class MmCommand(Command):
|
||||||
await asyncify(self.interface.session.commit)
|
await asyncify(self.interface.session.commit)
|
||||||
|
|
||||||
message: telegram.Message = await self.interface.bot.safe_api_call(client.send_message,
|
message: telegram.Message = await self.interface.bot.safe_api_call(client.send_message,
|
||||||
chat_id=os.environ["MM_CHANNEL_ID"],
|
chat_id=-1001224004974,
|
||||||
text=telegram_escape(self._main_text(mmevent)),
|
text=telegram_escape(self._main_text(mmevent)),
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
disable_webpage_preview=True,
|
disable_webpage_preview=True,
|
||||||
|
|
27
royalnet/configurator.py
Normal file
27
royalnet/configurator.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import click
|
||||||
|
import keyring
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
def run():
|
||||||
|
click.echo("Welcome to the Royalnet configuration creator!")
|
||||||
|
secrets_name = click.prompt("Desired secrets name", default="__default__")
|
||||||
|
network = click.prompt("Network password", default="")
|
||||||
|
if network:
|
||||||
|
keyring.set_password(f"Royalnet/{secrets_name}", "network", network)
|
||||||
|
telegram = click.prompt("Telegram Bot API token", default="")
|
||||||
|
if telegram:
|
||||||
|
keyring.set_password(f"Royalnet/{secrets_name}", "telegram", telegram)
|
||||||
|
discord = click.prompt("Discord Bot API token", default="")
|
||||||
|
if discord:
|
||||||
|
keyring.set_password(f"Royalnet/{secrets_name}", "discord", discord)
|
||||||
|
imgur = click.prompt("Imgur API token", default="")
|
||||||
|
if imgur:
|
||||||
|
keyring.set_password(f"Royalnet/{secrets_name}", "imgur", imgur)
|
||||||
|
sentry = click.prompt("Sentry DSN", default="")
|
||||||
|
if sentry:
|
||||||
|
keyring.set_password(f"Royalnet/{secrets_name}", "sentry", sentry)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run()
|
|
@ -3,5 +3,6 @@
|
||||||
from .alchemy import Alchemy
|
from .alchemy import Alchemy
|
||||||
from .relationshiplinkchain import relationshiplinkchain
|
from .relationshiplinkchain import relationshiplinkchain
|
||||||
from .databaseconfig import DatabaseConfig
|
from .databaseconfig import DatabaseConfig
|
||||||
|
from . import tables
|
||||||
|
|
||||||
__all__ = ["Alchemy", "relationshiplinkchain", "DatabaseConfig"]
|
__all__ = ["Alchemy", "relationshiplinkchain", "DatabaseConfig", "tables"]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
from .telegram import Telegram
|
from .telegram import Telegram
|
||||||
from .diario import Diario
|
from .diario import Diario
|
||||||
from .aliases import Alias
|
from .aliases import Alias
|
||||||
|
@ -17,6 +17,6 @@ from .mmdecisions import MMDecision
|
||||||
from .mmevents import MMEvent
|
from .mmevents import MMEvent
|
||||||
from .mmresponse import MMResponse
|
from .mmresponse import MMResponse
|
||||||
|
|
||||||
__all__ = ["Royal", "Telegram", "Diario", "Alias", "ActiveKvGroup", "Keyvalue", "Keygroup", "Discord", "WikiPage",
|
__all__ = ["User", "Telegram", "Diario", "Alias", "ActiveKvGroup", "Keyvalue", "Keygroup", "Discord", "WikiPage",
|
||||||
"WikiRevision", "Medal", "MedalAward", "Bio", "Reminder", "TriviaScore", "MMDecision", "MMEvent",
|
"WikiRevision", "Medal", "MedalAward", "Bio", "Reminder", "TriviaScore", "MMDecision", "MMEvent",
|
||||||
"MMResponse"]
|
"MMResponse"]
|
||||||
|
|
|
@ -5,7 +5,7 @@ from sqlalchemy import Column, \
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from .keygroups import Keygroup
|
from .keygroups import Keygroup
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class ActiveKvGroup:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal_id(self):
|
def royal_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"), primary_key=True)
|
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def group_name(self):
|
def group_name(self):
|
||||||
|
@ -23,7 +23,7 @@ class ActiveKvGroup:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal(self):
|
def royal(self):
|
||||||
return relationship("Royal", backref="active_kv_group")
|
return relationship("User", backref="active_kv_group")
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def group(self):
|
def group(self):
|
||||||
|
|
|
@ -5,7 +5,7 @@ from sqlalchemy import Column, \
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
|
|
||||||
|
|
||||||
class Alias:
|
class Alias:
|
||||||
|
@ -13,7 +13,7 @@ class Alias:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal_id(self):
|
def royal_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"))
|
return Column(Integer, ForeignKey("users.uid"))
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def alias(self):
|
def alias(self):
|
||||||
|
@ -21,7 +21,7 @@ class Alias:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal(self):
|
def royal(self):
|
||||||
return relationship("Royal", backref="aliases")
|
return relationship("User", backref="aliases")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Alias {str(self)}>"
|
return f"<Alias {str(self)}>"
|
||||||
|
|
|
@ -4,7 +4,7 @@ from sqlalchemy import Column, \
|
||||||
ForeignKey
|
ForeignKey
|
||||||
from sqlalchemy.orm import relationship, backref
|
from sqlalchemy.orm import relationship, backref
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
|
|
||||||
|
|
||||||
class Bio:
|
class Bio:
|
||||||
|
@ -12,11 +12,11 @@ class Bio:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal_id(self):
|
def royal_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"), primary_key=True)
|
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal(self):
|
def royal(self):
|
||||||
return relationship("Royal", backref=backref("bio", uselist=False))
|
return relationship("User", backref=backref("bio", uselist=False))
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def contents(self):
|
def contents(self):
|
||||||
|
|
|
@ -9,7 +9,7 @@ from sqlalchemy import Column, \
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
|
|
||||||
|
|
||||||
class Diario:
|
class Diario:
|
||||||
|
@ -21,11 +21,11 @@ class Diario:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def creator_id(self):
|
def creator_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"))
|
return Column(Integer, ForeignKey("users.uid"))
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def quoted_account_id(self):
|
def quoted_account_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"))
|
return Column(Integer, ForeignKey("users.uid"))
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def quoted(self):
|
def quoted(self):
|
||||||
|
@ -53,11 +53,11 @@ class Diario:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def creator(self):
|
def creator(self):
|
||||||
return relationship("Royal", foreign_keys=self.creator_id, backref="diario_created")
|
return relationship("User", foreign_keys=self.creator_id, backref="diario_created")
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def quoted_account(self):
|
def quoted_account(self):
|
||||||
return relationship("Royal", foreign_keys=self.quoted_account_id, backref="diario_quoted")
|
return relationship("User", foreign_keys=self.quoted_account_id, backref="diario_quoted")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Diario diario_id={self.diario_id}" \
|
return f"<Diario diario_id={self.diario_id}" \
|
||||||
|
|
|
@ -6,7 +6,7 @@ from sqlalchemy import Column, \
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
|
|
||||||
|
|
||||||
class Discord:
|
class Discord:
|
||||||
|
@ -14,7 +14,7 @@ class Discord:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal_id(self):
|
def royal_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"))
|
return Column(Integer, ForeignKey("users.uid"))
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def discord_id(self):
|
def discord_id(self):
|
||||||
|
@ -34,7 +34,7 @@ class Discord:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal(self):
|
def royal(self):
|
||||||
return relationship("Royal", backref="discord")
|
return relationship("User", backref="discord")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Discord {str(self)}>"
|
return f"<Discord {str(self)}>"
|
||||||
|
|
|
@ -4,7 +4,7 @@ from sqlalchemy import Column, \
|
||||||
ForeignKey
|
ForeignKey
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
from .medals import Medal
|
from .medals import Medal
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class MedalAward:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal_id(self):
|
def royal_id(self):
|
||||||
return Column(Integer, ForeignKey("royal.uid"), nullable=False)
|
return Column(Integer, ForeignKey("users.uid"), nullable=False)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def medal(self):
|
def medal(self):
|
||||||
|
@ -33,7 +33,7 @@ class MedalAward:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal(self):
|
def royal(self):
|
||||||
return relationship("Royal", backref="medals_received")
|
return relationship("User", backref="medals_received")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<MedalAward of {self.medal} to {self.royal} on {self.date}>"
|
return f"<MedalAward of {self.medal} to {self.royal} on {self.date}>"
|
||||||
|
|
|
@ -4,7 +4,7 @@ from sqlalchemy import Column, \
|
||||||
ForeignKey
|
ForeignKey
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
from .mmevents import MMEvent
|
from .mmevents import MMEvent
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,11 +13,11 @@ class MMDecision:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal_id(self):
|
def royal_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"), primary_key=True)
|
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal(self):
|
def royal(self):
|
||||||
return relationship("Royal", backref="mmdecisions_taken")
|
return relationship("User", backref="mmdecisions_taken")
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def mmevent_id(self):
|
def mmevent_id(self):
|
||||||
|
|
|
@ -9,7 +9,7 @@ from sqlalchemy import Column, \
|
||||||
BigInteger
|
BigInteger
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from .mmdecisions import MMDecision
|
from .mmdecisions import MMDecision
|
||||||
from .mmresponse import MMResponse
|
from .mmresponse import MMResponse
|
||||||
|
@ -20,11 +20,11 @@ class MMEvent:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def creator_id(self):
|
def creator_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"), nullable=False)
|
return Column(Integer, ForeignKey("users.uid"), nullable=False)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def creator(self):
|
def creator(self):
|
||||||
return relationship("Royal", backref="mmevents_created")
|
return relationship("User", backref="mmevents_created")
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def mmid(self):
|
def mmid(self):
|
||||||
|
|
|
@ -4,7 +4,7 @@ from sqlalchemy import Column, \
|
||||||
ForeignKey
|
ForeignKey
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
from .mmevents import MMEvent
|
from .mmevents import MMEvent
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,11 +13,11 @@ class MMResponse:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal_id(self):
|
def royal_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"), primary_key=True)
|
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal(self):
|
def royal(self):
|
||||||
return relationship("Royal", backref="mmresponses_given")
|
return relationship("User", backref="mmresponses_given")
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def mmevent_id(self):
|
def mmevent_id(self):
|
||||||
|
|
|
@ -7,7 +7,7 @@ from sqlalchemy import Column, \
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
|
|
||||||
|
|
||||||
class Reminder:
|
class Reminder:
|
||||||
|
@ -19,11 +19,11 @@ class Reminder:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def creator_id(self):
|
def creator_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"))
|
return Column(Integer, ForeignKey("users.uid"))
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def creator(self):
|
def creator(self):
|
||||||
return relationship("Royal", backref="reminders_created")
|
return relationship("User", backref="reminders_created")
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def interface_name(self):
|
def interface_name(self):
|
||||||
|
|
|
@ -5,8 +5,8 @@ from sqlalchemy import Column, \
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
|
|
||||||
|
|
||||||
class Royal:
|
class User:
|
||||||
__tablename__ = "royals"
|
__tablename__ = "users"
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def uid(self):
|
def uid(self):
|
||||||
|
@ -29,7 +29,7 @@ class Royal:
|
||||||
return Column(LargeBinary)
|
return Column(LargeBinary)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Royal {self.username}>"
|
return f"<User {self.username}>"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"[c]royalnet:{self.username}[/c]"
|
return self.username
|
||||||
|
|
|
@ -6,7 +6,7 @@ from sqlalchemy import Column, \
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
|
|
||||||
|
|
||||||
class Telegram:
|
class Telegram:
|
||||||
|
@ -14,7 +14,7 @@ class Telegram:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal_id(self):
|
def royal_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"))
|
return Column(Integer, ForeignKey("users.uid"))
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def tg_id(self):
|
def tg_id(self):
|
||||||
|
@ -34,7 +34,7 @@ class Telegram:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal(self):
|
def royal(self):
|
||||||
return relationship("Royal", backref="telegram")
|
return relationship("User", backref="telegram")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Telegram {str(self)}>"
|
return f"<Telegram {str(self)}>"
|
||||||
|
|
|
@ -3,7 +3,7 @@ from sqlalchemy import Column, \
|
||||||
ForeignKey
|
ForeignKey
|
||||||
from sqlalchemy.orm import relationship, backref
|
from sqlalchemy.orm import relationship, backref
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
|
|
||||||
|
|
||||||
class TriviaScore:
|
class TriviaScore:
|
||||||
|
@ -11,11 +11,11 @@ class TriviaScore:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal_id(self):
|
def royal_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"), primary_key=True)
|
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def royal(self):
|
def royal(self):
|
||||||
return relationship("Royal", backref=backref("trivia_score", uselist=False))
|
return relationship("User", backref=backref("trivia_score", uselist=False))
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def correct_answers(self):
|
def correct_answers(self):
|
||||||
|
|
|
@ -9,7 +9,7 @@ from sqlalchemy.ext.declarative import declared_attr
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from .wikipages import WikiPage
|
from .wikipages import WikiPage
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from .royals import Royal
|
from .royals import User
|
||||||
|
|
||||||
|
|
||||||
class WikiRevision:
|
class WikiRevision:
|
||||||
|
@ -33,11 +33,11 @@ class WikiRevision:
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def author_id(self):
|
def author_id(self):
|
||||||
return Column(Integer, ForeignKey("royals.uid"), nullable=False)
|
return Column(Integer, ForeignKey("users.uid"), nullable=False)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def author(self):
|
def author(self):
|
||||||
return relationship("Royal", foreign_keys=self.author_id, backref="wiki_contributions")
|
return relationship("User", foreign_keys=self.author_id, backref="wiki_contributions")
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def timestamp(self):
|
def timestamp(self):
|
||||||
|
|
|
@ -2,17 +2,17 @@
|
||||||
from .request import Request
|
from .request import Request
|
||||||
from .response import Response, ResponseSuccess, ResponseError
|
from .response import Response, ResponseSuccess, ResponseError
|
||||||
from .package import Package
|
from .package import Package
|
||||||
from .royalnetlink import RoyalnetLink, NetworkError, NotConnectedError, NotIdentifiedError, ConnectionClosedError
|
from .networklink import NetworkLink, NetworkError, NotConnectedError, NotIdentifiedError, ConnectionClosedError
|
||||||
from .royalnetserver import RoyalnetServer
|
from .networkserver import NetworkServer
|
||||||
from .royalnetconfig import RoyalnetConfig
|
from .networkconfig import NetworkConfig
|
||||||
|
|
||||||
__all__ = ["RoyalnetLink",
|
__all__ = ["NetworkLink",
|
||||||
"NetworkError",
|
"NetworkError",
|
||||||
"NotConnectedError",
|
"NotConnectedError",
|
||||||
"NotIdentifiedError",
|
"NotIdentifiedError",
|
||||||
"Package",
|
"Package",
|
||||||
"RoyalnetServer",
|
"NetworkServer",
|
||||||
"RoyalnetConfig",
|
"NetworkConfig",
|
||||||
"ConnectionClosedError",
|
"ConnectionClosedError",
|
||||||
"Request",
|
"Request",
|
||||||
"Response",
|
"Response",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class RoyalnetConfig:
|
class NetworkConfig:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
master_uri: str,
|
master_uri: str,
|
||||||
master_secret: str):
|
master_secret: str):
|
|
@ -13,19 +13,19 @@ log = _logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NotConnectedError(Exception):
|
class NotConnectedError(Exception):
|
||||||
"""The :py:class:`royalnet.network.RoyalnetLink` is not connected to a :py:class:`royalnet.network.RoyalnetServer`."""
|
"""The :py:class:`royalnet.network.NetworkLink` is not connected to a :py:class:`royalnet.network.NetworkServer`."""
|
||||||
|
|
||||||
|
|
||||||
class NotIdentifiedError(Exception):
|
class NotIdentifiedError(Exception):
|
||||||
"""The :py:class:`royalnet.network.RoyalnetLink` has not identified yet to a :py:class:`royalnet.network.RoyalnetServer`."""
|
"""The :py:class:`royalnet.network.NetworkLink` has not identified yet to a :py:class:`royalnet.network.NetworkServer`."""
|
||||||
|
|
||||||
|
|
||||||
class ConnectionClosedError(Exception):
|
class ConnectionClosedError(Exception):
|
||||||
"""The :py:class:`royalnet.network.RoyalnetLink`'s connection was closed unexpectedly. The link can't be used anymore."""
|
"""The :py:class:`royalnet.network.NetworkLink`'s connection was closed unexpectedly. The link can't be used anymore."""
|
||||||
|
|
||||||
|
|
||||||
class InvalidServerResponseError(Exception):
|
class InvalidServerResponseError(Exception):
|
||||||
"""The :py:class:`royalnet.network.RoyalnetServer` sent invalid data to the :py:class:`royalnet.network.RoyalnetLink`."""
|
"""The :py:class:`royalnet.network.NetworkServer` sent invalid data to the :py:class:`royalnet.network.NetworkLink`."""
|
||||||
|
|
||||||
|
|
||||||
class NetworkError(Exception):
|
class NetworkError(Exception):
|
||||||
|
@ -69,7 +69,7 @@ def requires_identification(func):
|
||||||
return new_func
|
return new_func
|
||||||
|
|
||||||
|
|
||||||
class RoyalnetLink:
|
class NetworkLink:
|
||||||
def __init__(self, master_uri: str, secret: str, link_type: str, request_handler, *,
|
def __init__(self, master_uri: str, secret: str, link_type: str, request_handler, *,
|
||||||
loop: asyncio.AbstractEventLoop = None):
|
loop: asyncio.AbstractEventLoop = None):
|
||||||
if ":" in link_type:
|
if ":" in link_type:
|
||||||
|
@ -90,7 +90,7 @@ class RoyalnetLink:
|
||||||
self.identify_event: asyncio.Event = asyncio.Event(loop=self._loop)
|
self.identify_event: asyncio.Event = asyncio.Event(loop=self._loop)
|
||||||
|
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
"""Connect to the :py:class:`royalnet.network.RoyalnetServer` at ``self.master_uri``."""
|
"""Connect to the :py:class:`royalnet.network.NetworkServer` at ``self.master_uri``."""
|
||||||
log.info(f"Connecting to {self.master_uri}...")
|
log.info(f"Connecting to {self.master_uri}...")
|
||||||
self.websocket = await websockets.connect(self.master_uri, loop=self._loop)
|
self.websocket = await websockets.connect(self.master_uri, loop=self._loop)
|
||||||
self.connect_event.set()
|
self.connect_event.set()
|
||||||
|
@ -98,7 +98,7 @@ class RoyalnetLink:
|
||||||
|
|
||||||
@requires_connection
|
@requires_connection
|
||||||
async def receive(self) -> Package:
|
async def receive(self) -> Package:
|
||||||
"""Recieve a :py:class:`Package` from the :py:class:`royalnet.network.RoyalnetServer`.
|
"""Recieve a :py:class:`Package` from the :py:class:`royalnet.network.NetworkServer`.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:py:exc:`royalnet.network.royalnetlink.ConnectionClosedError` if the connection closes."""
|
:py:exc:`royalnet.network.royalnetlink.ConnectionClosedError` if the connection closes."""
|
||||||
|
@ -113,7 +113,7 @@ class RoyalnetLink:
|
||||||
# What to do now? Let's just reraise.
|
# What to do now? Let's just reraise.
|
||||||
raise ConnectionClosedError()
|
raise ConnectionClosedError()
|
||||||
if self.identify_event.is_set() and package.destination != self.nid:
|
if self.identify_event.is_set() and package.destination != self.nid:
|
||||||
raise InvalidServerResponseError("Package is not addressed to this RoyalnetLink.")
|
raise InvalidServerResponseError("Package is not addressed to this NetworkLink.")
|
||||||
log.debug(f"Received package: {package}")
|
log.debug(f"Received package: {package}")
|
||||||
return package
|
return package
|
||||||
|
|
|
@ -12,7 +12,7 @@ log = _logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ConnectedClient:
|
class ConnectedClient:
|
||||||
"""The :py:class:`royalnet.network.RoyalnetServer`-side representation of a connected :py:class:`royalnet.network.RoyalnetLink`."""
|
"""The :py:class:`royalnet.network.NetworkServer`-side representation of a connected :py:class:`royalnet.network.NetworkLink`."""
|
||||||
def __init__(self, socket: websockets.WebSocketServerProtocol):
|
def __init__(self, socket: websockets.WebSocketServerProtocol):
|
||||||
self.socket: websockets.WebSocketServerProtocol = socket
|
self.socket: websockets.WebSocketServerProtocol = socket
|
||||||
self.nid: typing.Optional[str] = None
|
self.nid: typing.Optional[str] = None
|
||||||
|
@ -30,11 +30,11 @@ class ConnectedClient:
|
||||||
destination=self.nid))
|
destination=self.nid))
|
||||||
|
|
||||||
async def send(self, package: Package):
|
async def send(self, package: Package):
|
||||||
"""Send a :py:class:`royalnet.network.Package` to the :py:class:`royalnet.network.RoyalnetLink`."""
|
"""Send a :py:class:`royalnet.network.Package` to the :py:class:`royalnet.network.NetworkLink`."""
|
||||||
await self.socket.send(package.to_json_bytes())
|
await self.socket.send(package.to_json_bytes())
|
||||||
|
|
||||||
|
|
||||||
class RoyalnetServer:
|
class NetworkServer:
|
||||||
def __init__(self, address: str, port: int, required_secret: str, *, loop: asyncio.AbstractEventLoop = None):
|
def __init__(self, address: str, port: int, required_secret: str, *, loop: asyncio.AbstractEventLoop = None):
|
||||||
self.address: str = address
|
self.address: str = address
|
||||||
self.port: int = port
|
self.port: int = port
|
||||||
|
@ -131,9 +131,12 @@ class RoyalnetServer:
|
||||||
async def serve(self):
|
async def serve(self):
|
||||||
await websockets.serve(self.listener, host=self.address, port=self.port)
|
await websockets.serve(self.listener, host=self.address, port=self.port)
|
||||||
|
|
||||||
async def start(self):
|
async def run(self):
|
||||||
log.debug(f"Starting main server loop for <server> on ws://{self.address}:{self.port}")
|
log.debug(f"Starting main server loop for <server> on ws://{self.address}:{self.port}")
|
||||||
# noinspection PyAsyncCall
|
# noinspection PyAsyncCall
|
||||||
self._loop.create_task(self.serve())
|
self._loop.create_task(self.serve())
|
||||||
# Just to be sure it has started on Linux
|
# Just to be sure it has started on Linux
|
||||||
await asyncio.sleep(0.5)
|
await asyncio.sleep(0.5)
|
||||||
|
|
||||||
|
def run_blocking(self):
|
||||||
|
self._loop.run_until_complete(self.run())
|
|
@ -4,7 +4,7 @@ import typing
|
||||||
|
|
||||||
|
|
||||||
class Package:
|
class Package:
|
||||||
"""A Royalnet package, the data type with which a :py:class:`royalnet.network.RoyalnetLink` communicates with a :py:class:`royalnet.network.RoyalnetServer` or another link.
|
"""A Royalnet package, the data type with which a :py:class:`royalnet.network.NetworkLink` communicates with a :py:class:`royalnet.network.NetworkServer` or another link.
|
||||||
Contains info about the source and the destination."""
|
Contains info about the source and the destination."""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class Request:
|
class Request:
|
||||||
"""A request sent from a :py:class:`royalnet.network.RoyalnetLink` to another.
|
"""A request sent from a :py:class:`royalnet.network.NetworkLink` to another.
|
||||||
|
|
||||||
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."""
|
||||||
|
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import click
|
|
||||||
import typing
|
|
||||||
import royalnet as r
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option("--telegram/--no-telegram", default=None,
|
|
||||||
help="Enable/disable the Telegram module.")
|
|
||||||
@click.option("--discord/--no-discord", default=None,
|
|
||||||
help="Enable/disable the Discord module.")
|
|
||||||
@click.option("--database", type=str, default=None,
|
|
||||||
help="The PostgreSQL database path.")
|
|
||||||
@click.option("--commands", type=str, multiple=True, default=[],
|
|
||||||
help="The names of the command pack modules that should be imported.")
|
|
||||||
@click.option("--network", type=str, default=None,
|
|
||||||
help="The Royalnet master server uri and password, separated by a pipe.")
|
|
||||||
def run(telegram: typing.Optional[bool],
|
|
||||||
discord: typing.Optional[bool],
|
|
||||||
database: typing.Optional[str],
|
|
||||||
commands: typing.List[str],
|
|
||||||
network: typing.Optional[str]):
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
run()
|
|
|
@ -4,11 +4,11 @@ import datetime
|
||||||
import difflib
|
import difflib
|
||||||
import uuid
|
import uuid
|
||||||
from royalnet.database import Alchemy
|
from royalnet.database import Alchemy
|
||||||
from royalnet.database.tables import Royal, WikiPage, WikiRevision
|
from royalnet.database.tables import User, WikiPage, WikiRevision
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
alchemy = Alchemy(os.environ["DB_PATH"], {Royal, WikiPage, WikiRevision})
|
alchemy = Alchemy(os.environ["DB_PATH"], {User, WikiPage, WikiRevision})
|
||||||
with open(r"data.txt") as file, alchemy.session_cm() as session:
|
with open(r"data.txt") as file, alchemy.session_cm() as session:
|
||||||
for line in file.readlines():
|
for line in file.readlines():
|
||||||
match = re.match("^([^\t]+)\t([^\t]+)\t([tf])$", line)
|
match = re.match("^([^\t]+)\t([^\t]+)\t([tf])$", line)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
semantic = "5.0a58"
|
semantic = "5.0a59"
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
"""A Royal Games Diario viewer :py:class:`royalnet.web.Royalprint`."""
|
"""A User Games Diario viewer :py:class:`royalnet.web.Royalprint`."""
|
||||||
|
|
||||||
import flask as f
|
import flask as f
|
||||||
import os
|
import os
|
||||||
from ...royalprint import Royalprint
|
from ...royalprint import Royalprint
|
||||||
from ...shortcuts import error
|
from ...shortcuts import error
|
||||||
from ....database.tables import Royal, Diario
|
from ....database.tables import User, Diario
|
||||||
|
|
||||||
|
|
||||||
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
||||||
rp = Royalprint("diarioview", __name__, url_prefix="/diario", template_folder=tmpl_dir,
|
rp = Royalprint("diarioview", __name__, url_prefix="/diario", template_folder=tmpl_dir,
|
||||||
required_tables={Royal, Diario})
|
required_tables={User, Diario})
|
||||||
|
|
||||||
|
|
||||||
@rp.route("/", defaults={"page": 1})
|
@rp.route("/", defaults={"page": 1})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""Homepage :py:class:`royalnet.web.Royalprint` of the Royal Games website."""
|
"""Homepage :py:class:`royalnet.web.Royalprint` of the User Games website."""
|
||||||
import flask as f
|
import flask as f
|
||||||
import os
|
import os
|
||||||
from ...royalprint import Royalprint
|
from ...royalprint import Royalprint
|
||||||
|
|
|
@ -5,11 +5,11 @@ import datetime
|
||||||
import bcrypt
|
import bcrypt
|
||||||
from ...royalprint import Royalprint
|
from ...royalprint import Royalprint
|
||||||
from ...shortcuts import error
|
from ...shortcuts import error
|
||||||
from ....database.tables import Royal
|
from ....database.tables import User
|
||||||
|
|
||||||
|
|
||||||
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
||||||
rp = Royalprint("login", __name__, url_prefix="/login/password", required_tables={Royal},
|
rp = Royalprint("login", __name__, url_prefix="/login/password", required_tables={User},
|
||||||
template_folder=tmpl_dir)
|
template_folder=tmpl_dir)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ import os
|
||||||
import bcrypt
|
import bcrypt
|
||||||
from ...royalprint import Royalprint
|
from ...royalprint import Royalprint
|
||||||
from ...shortcuts import error
|
from ...shortcuts import error
|
||||||
from ....database.tables import Royal, Alias
|
from ....database.tables import User, Alias
|
||||||
|
|
||||||
|
|
||||||
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
||||||
rp = Royalprint("newaccount", __name__, url_prefix="/newaccount", required_tables={Royal, Alias},
|
rp = Royalprint("newaccount", __name__, url_prefix="/newaccount", required_tables={User, Alias},
|
||||||
template_folder=tmpl_dir)
|
template_folder=tmpl_dir)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ from ....utils.wikirender import prepare_page_markdown, RenderError
|
||||||
# Maybe some of these tables are optional...
|
# Maybe some of these tables are optional...
|
||||||
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
||||||
rp = Royalprint("profile", __name__, url_prefix="/profile", template_folder=tmpl_dir,
|
rp = Royalprint("profile", __name__, url_prefix="/profile", template_folder=tmpl_dir,
|
||||||
required_tables={Royal, ActiveKvGroup, Alias, Diario, Discord, Keygroup, Keyvalue, Telegram, WikiPage,
|
required_tables={User, ActiveKvGroup, Alias, Diario, Discord, Keygroup, Keyvalue, Telegram, WikiPage,
|
||||||
WikiRevision, Bio, TriviaScore})
|
WikiRevision, Bio, TriviaScore})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ import datetime
|
||||||
import os
|
import os
|
||||||
from ...royalprint import Royalprint
|
from ...royalprint import Royalprint
|
||||||
from ...shortcuts import error
|
from ...shortcuts import error
|
||||||
from ....database.tables import Royal, Telegram
|
from ....database.tables import User, Telegram
|
||||||
|
|
||||||
|
|
||||||
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
||||||
rp = Royalprint("tglogin", __name__, url_prefix="/login/telegram", required_tables={Royal, Telegram},
|
rp = Royalprint("tglogin", __name__, url_prefix="/login/telegram", required_tables={User, Telegram},
|
||||||
template_folder=tmpl_dir)
|
template_folder=tmpl_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ def tglogin_done():
|
||||||
return error(400, "L'autenticazione è fallita: l'hash ricevuto non coincide con quello calcolato.")
|
return error(400, "L'autenticazione è fallita: l'hash ricevuto non coincide con quello calcolato.")
|
||||||
tg_user = alchemy_session.query(alchemy.Telegram).filter(alchemy.Telegram.tg_id == f.request.args["id"]).one_or_none()
|
tg_user = alchemy_session.query(alchemy.Telegram).filter(alchemy.Telegram.tg_id == f.request.args["id"]).one_or_none()
|
||||||
if tg_user is None:
|
if tg_user is None:
|
||||||
return error(404, "L'account Telegram con cui hai fatto il login non è connesso a nessun account Royal Games. Se sei un membro Royal Games, assicurati di aver syncato con il bot il tuo account di Telegram!")
|
return error(404, "L'account Telegram con cui hai fatto il login non è connesso a nessun account User Games. Se sei un membro User Games, assicurati di aver syncato con il bot il tuo account di Telegram!")
|
||||||
royal_user = tg_user.royal
|
royal_user = tg_user.royal
|
||||||
f.session["royal"] = {
|
f.session["royal"] = {
|
||||||
"uid": royal_user.uid,
|
"uid": royal_user.uid,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""A Royal Games Wiki viewer :py:class:`royalnet.web.Royalprint`. Doesn't support any kind of edit."""
|
"""A User Games Wiki viewer :py:class:`royalnet.web.Royalprint`. Doesn't support any kind of edit."""
|
||||||
import flask as f
|
import flask as f
|
||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
|
@ -6,12 +6,12 @@ import datetime
|
||||||
import difflib
|
import difflib
|
||||||
from ...royalprint import Royalprint
|
from ...royalprint import Royalprint
|
||||||
from ...shortcuts import error, from_urluuid
|
from ...shortcuts import error, from_urluuid
|
||||||
from ....database.tables import Royal, WikiPage, WikiRevision
|
from ....database.tables import User, WikiPage, WikiRevision
|
||||||
|
|
||||||
|
|
||||||
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
||||||
rp = Royalprint("wikiedit", __name__, url_prefix="/wiki/edit", template_folder=tmpl_dir,
|
rp = Royalprint("wikiedit", __name__, url_prefix="/wiki/edit", template_folder=tmpl_dir,
|
||||||
required_tables={Royal, WikiPage, WikiRevision})
|
required_tables={User, WikiPage, WikiRevision})
|
||||||
|
|
||||||
|
|
||||||
@rp.route("/newpage", methods=["GET", "POST"])
|
@rp.route("/newpage", methods=["GET", "POST"])
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
"""A Royal Games Wiki viewer :py:class:`royalnet.web.Royalprint`. Doesn't support any kind of edit."""
|
"""A User Games Wiki viewer :py:class:`royalnet.web.Royalprint`. Doesn't support any kind of edit."""
|
||||||
|
|
||||||
import flask as f
|
import flask as f
|
||||||
import os
|
import os
|
||||||
from ...royalprint import Royalprint
|
from ...royalprint import Royalprint
|
||||||
from ...shortcuts import error, from_urluuid
|
from ...shortcuts import error, from_urluuid
|
||||||
from ....database.tables import Royal, WikiPage, WikiRevision
|
from ....database.tables import User, WikiPage, WikiRevision
|
||||||
from ....utils.wikirender import prepare_page_markdown, RenderError
|
from ....utils.wikirender import prepare_page_markdown, RenderError
|
||||||
|
|
||||||
|
|
||||||
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
||||||
rp = Royalprint("wikiview", __name__, url_prefix="/wiki/view", template_folder=tmpl_dir,
|
rp = Royalprint("wikiview", __name__, url_prefix="/wiki/view", template_folder=tmpl_dir,
|
||||||
required_tables={Royal, WikiPage, WikiRevision})
|
required_tables={User, WikiPage, WikiRevision})
|
||||||
|
|
||||||
|
|
||||||
def prepare_page(page):
|
def prepare_page(page):
|
||||||
|
|
5
setup.py
5
setup.py
|
@ -9,7 +9,7 @@ setuptools.setup(
|
||||||
version=royalnet.version.semantic,
|
version=royalnet.version.semantic,
|
||||||
author="Stefano Pigozzi",
|
author="Stefano Pigozzi",
|
||||||
author_email="ste.pigozzi@gmail.com",
|
author_email="ste.pigozzi@gmail.com",
|
||||||
description="The great bot network of the Royal Games community",
|
description="The great bot network of the User Games community",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
url="https://github.com/royal-games/royalnet",
|
url="https://github.com/royal-games/royalnet",
|
||||||
|
@ -31,7 +31,8 @@ setuptools.setup(
|
||||||
"mcstatus>=2.2.1",
|
"mcstatus>=2.2.1",
|
||||||
"sortedcontainers>=2.1.0",
|
"sortedcontainers>=2.1.0",
|
||||||
"sentry-sdk>=0.11.1",
|
"sentry-sdk>=0.11.1",
|
||||||
"click>=7.0"],
|
"click>=7.0",
|
||||||
|
"keyring>=19.2.0"],
|
||||||
python_requires=">=3.7",
|
python_requires=">=3.7",
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import pytest
|
||||||
import uuid
|
import uuid
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from royalnet.network import Package, RoyalnetLink, RoyalnetServer, ConnectionClosedError, Request
|
from royalnet.network import Package, NetworkLink, NetworkServer, ConnectionClosedError, Request
|
||||||
|
|
||||||
|
|
||||||
log = logging.root
|
log = logging.root
|
||||||
|
@ -41,16 +41,16 @@ def test_request_creation():
|
||||||
|
|
||||||
def test_links(async_loop: asyncio.AbstractEventLoop):
|
def test_links(async_loop: asyncio.AbstractEventLoop):
|
||||||
address, port = "127.0.0.1", 1235
|
address, port = "127.0.0.1", 1235
|
||||||
master = RoyalnetServer(address, port, "test")
|
master = NetworkServer(address, port, "test")
|
||||||
async_loop.run_until_complete(master.start())
|
async_loop.run_until_complete(master.start())
|
||||||
# Test invalid secret
|
# Test invalid secret
|
||||||
wrong_secret_link = RoyalnetLink("ws://127.0.0.1:1235", "invalid", "test", echo_request_handler, loop=async_loop)
|
wrong_secret_link = NetworkLink("ws://127.0.0.1:1235", "invalid", "test", echo_request_handler, loop=async_loop)
|
||||||
with pytest.raises(ConnectionClosedError):
|
with pytest.raises(ConnectionClosedError):
|
||||||
async_loop.run_until_complete(wrong_secret_link.run())
|
async_loop.run_until_complete(wrong_secret_link.run())
|
||||||
# Test regular connection
|
# Test regular connection
|
||||||
link1 = RoyalnetLink("ws://127.0.0.1:1235", "test", "one", echo_request_handler, loop=async_loop)
|
link1 = NetworkLink("ws://127.0.0.1:1235", "test", "one", echo_request_handler, loop=async_loop)
|
||||||
async_loop.create_task(link1.run())
|
async_loop.create_task(link1.run())
|
||||||
link2 = RoyalnetLink("ws://127.0.0.1:1235", "test", "two", echo_request_handler, loop=async_loop)
|
link2 = NetworkLink("ws://127.0.0.1:1235", "test", "two", echo_request_handler, loop=async_loop)
|
||||||
async_loop.create_task(link2.run())
|
async_loop.create_task(link2.run())
|
||||||
message = {"ciao": "ciao"}
|
message = {"ciao": "ciao"}
|
||||||
response = async_loop.run_until_complete(link1.request(message, "two"))
|
response = async_loop.run_until_complete(link1.request(message, "two"))
|
||||||
|
|
Loading…
Reference in a new issue