mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 11:34:18 +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).
|
||||
|
||||
## Installation for the Royal Games community
|
||||
## Quick install
|
||||
|
||||
With `python3.7` and `pip` installed, run:
|
||||
|
||||
```bash
|
||||
pip install royalnet
|
||||
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
|
||||
```
|
||||
pip3.7 install royalnet
|
||||
python3.7 -m royalnet.configurator
|
||||
python3.7 -m royalnet -c (YOUR_COMMAND_PACK)
|
||||
```
|
|
@ -1,4 +1,4 @@
|
|||
bcrypt
|
||||
bcrypt>=3.1.7
|
||||
python-telegram-bot>=11.1.0
|
||||
websockets>=7.0
|
||||
pytest>=4.3.1
|
||||
|
@ -20,3 +20,5 @@ mcstatus>=2.2.1
|
|||
sortedcontainers>=2.1.0
|
||||
sentry-sdk>=0.11.1
|
||||
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)
|
||||
.output(destination_filename, format="s16le", ac=2, ar="48000")
|
||||
.overwrite_output()
|
||||
.run(quiet=True)
|
||||
.run_async(quiet=True)
|
||||
)
|
||||
self.pcm_filename = destination_filename
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class YtdlMp3:
|
|||
ffmpeg.input(self.ytdl_file.filename)
|
||||
.output(destination_filename, format="mp3")
|
||||
.overwrite_output()
|
||||
.run()
|
||||
.run_async()
|
||||
)
|
||||
self.mp3_filename = destination_filename
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Various bot interfaces, and a generic class to create new ones."""
|
||||
|
||||
from .generic import GenericBot
|
||||
from .telegram import TelegramBot, TelegramConfig
|
||||
from .discord import DiscordBot, DiscordConfig
|
||||
from .telegram import TelegramBot
|
||||
from .discord import DiscordBot
|
||||
|
||||
__all__ = ["TelegramBot", "TelegramConfig", "DiscordBot", "DiscordConfig", "GenericBot"]
|
||||
__all__ = ["TelegramBot", "DiscordBot", "GenericBot"]
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import discord
|
||||
import typing
|
||||
import sentry_sdk
|
||||
import logging as _logging
|
||||
from .generic import GenericBot
|
||||
from ..utils import *
|
||||
from ..error import *
|
||||
from ..network import *
|
||||
from ..database import *
|
||||
from ..audio import *
|
||||
from ..commands import *
|
||||
|
||||
|
@ -17,12 +14,6 @@ if not discord.opus.is_loaded():
|
|||
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):
|
||||
"""A bot that connects to `Discord <https://discordapp.com/>`_."""
|
||||
interface_name = "discord"
|
||||
|
@ -204,24 +195,19 @@ class DiscordBot(GenericBot):
|
|||
self._Client = self._bot_factory()
|
||||
self.client = self._Client()
|
||||
|
||||
def __init__(self, *,
|
||||
discord_config: DiscordConfig,
|
||||
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
|
||||
def _initialize(self):
|
||||
super()._initialize()
|
||||
self._init_client()
|
||||
self._init_voice()
|
||||
|
||||
async def run(self):
|
||||
"""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")
|
||||
await self.client.login(self._discord_config.token)
|
||||
await self.client.login(token)
|
||||
log.info(f"Connecting to Discord")
|
||||
await self.client.connect()
|
||||
# TODO: how to stop?
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import sys
|
||||
import typing
|
||||
import asyncio
|
||||
import logging
|
||||
import sentry_sdk
|
||||
import keyring
|
||||
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
|
||||
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
|
||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||
|
@ -21,7 +21,7 @@ class GenericBot:
|
|||
:py:class:`royalnet.bots.TelegramBot` and :py:class:`royalnet.bots.DiscordBot`. """
|
||||
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``
|
||||
dictionary required to handle incoming requests. """
|
||||
log.debug(f"Now binding commands")
|
||||
|
@ -29,13 +29,13 @@ class GenericBot:
|
|||
self._Data = self._data_factory()
|
||||
self.commands = {}
|
||||
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}...")
|
||||
interface = self._Interface()
|
||||
try:
|
||||
self.commands[f"{interface.prefix}{SelectedCommand.name}"] = SelectedCommand(interface)
|
||||
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")
|
||||
|
||||
def _interface_factory(self) -> typing.Type[CommandInterface]:
|
||||
|
@ -71,15 +71,17 @@ class GenericBot:
|
|||
def _data_factory(self) -> typing.Type[CommandData]:
|
||||
raise NotImplementedError()
|
||||
|
||||
def _init_royalnet(self, royalnet_config: RoyalnetConfig):
|
||||
"""Create a :py:class:`royalnet.network.RoyalnetLink`, and run it as a :py:class:`asyncio.Task`."""
|
||||
self.network: RoyalnetLink = RoyalnetLink(royalnet_config.master_uri, royalnet_config.master_secret,
|
||||
self.interface_name, self._network_handler)
|
||||
log.debug(f"Running RoyalnetLink {self.network}")
|
||||
self.loop.create_task(self.network.run())
|
||||
def _init_network(self):
|
||||
"""Create a :py:class:`royalnet.network.NetworkLink`, and run it as a :py:class:`asyncio.Task`."""
|
||||
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)
|
||||
log.debug(f"Running NetworkLink {self.network}")
|
||||
self.loop.create_task(self.network.run())
|
||||
|
||||
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:
|
||||
Another :py:class:`dict`, formatted as a :py:class:`royalnet.network.Response`."""
|
||||
|
@ -91,7 +93,7 @@ class GenericBot:
|
|||
return ResponseError("invalid_request",
|
||||
f"The Request that you sent was invalid. Check extra_info to see what you sent.",
|
||||
extra_info={"you_sent": request_dict}).to_dict()
|
||||
log.debug(f"Received {request} from the RoyalnetLink")
|
||||
log.debug(f"Received {request} from the NetworkLink")
|
||||
try:
|
||||
network_handler = self.network_handlers[request.handler]
|
||||
except KeyError:
|
||||
|
@ -115,59 +117,79 @@ class GenericBot:
|
|||
"str": str(exc)
|
||||
}).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,
|
||||
find the chain that links the ``master_table`` to the ``identity_table``. """
|
||||
log.debug(f"Initializing database")
|
||||
required_tables = {database_config.master_table, database_config.identity_table}
|
||||
for command in commands:
|
||||
required_tables = required_tables.union(command.require_alchemy_tables)
|
||||
log.debug(f"Found {len(required_tables)} required tables")
|
||||
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)
|
||||
log.debug(f"Identity chain is {self.identity_chain}")
|
||||
if self.uninitialized_database_config:
|
||||
log.debug(f"Initializing database")
|
||||
required_tables = {self.uninitialized_database_config.master_table, self.uninitialized_database_config.identity_table}
|
||||
for command in self.uninitialized_commands:
|
||||
required_tables = required_tables.union(command.require_alchemy_tables)
|
||||
log.debug(f"Found {len(required_tables)} required tables")
|
||||
self.alchemy = Alchemy(self.uninitialized_database_config.database_uri, required_tables)
|
||||
self.master_table = self.alchemy.__getattribute__(self.uninitialized_database_config.master_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.uninitialized_database_config.identity_column_name)
|
||||
self.identity_chain = relationshiplinkchain(self.master_table, self.identity_table)
|
||||
log.debug(f"Identity chain is {self.identity_chain}")
|
||||
else:
|
||||
log.debug(f"Database is not enabled, setting everything to None")
|
||||
self.alchemy = None
|
||||
self.master_table = None
|
||||
self.identity_table = None
|
||||
self.identity_column = None
|
||||
|
||||
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, *,
|
||||
royalnet_config: typing.Optional[RoyalnetConfig] = None,
|
||||
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):
|
||||
if loop is None:
|
||||
self.loop = asyncio.get_event_loop()
|
||||
else:
|
||||
self.loop = loop
|
||||
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.master_table = None
|
||||
self.identity_table = 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
|
||||
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
|
||||
|
||||
async def run(self):
|
||||
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."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def run_blocking(self):
|
||||
self._initialize()
|
||||
self.loop.run_until_complete(self.run())
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import telegram
|
||||
import telegram.utils.request
|
||||
import typing
|
||||
import uuid
|
||||
import urllib3
|
||||
import asyncio
|
||||
|
@ -9,20 +8,12 @@ import logging as _logging
|
|||
from .generic import GenericBot
|
||||
from ..utils import *
|
||||
from ..error import *
|
||||
from ..network import *
|
||||
from ..database import *
|
||||
from ..commands import *
|
||||
|
||||
|
||||
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):
|
||||
"""A bot that connects to `Telegram <https://telegram.org/>`_."""
|
||||
interface_name = "telegram"
|
||||
|
@ -30,8 +21,9 @@ class TelegramBot(GenericBot):
|
|||
def _init_client(self):
|
||||
"""Create the :py:class:`telegram.Bot`, and set the starting offset."""
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/issues/341
|
||||
request = telegram.utils.request.Request(20, read_timeout=15)
|
||||
self.client = telegram.Bot(self._telegram_config.token, request=request)
|
||||
request = telegram.utils.request.Request(5, read_timeout=30)
|
||||
token = self.get_secret("telegram")
|
||||
self.client = telegram.Bot(token, request=request)
|
||||
self._offset: int = -100
|
||||
|
||||
def _interface_factory(self) -> typing.Type[CommandInterface]:
|
||||
|
@ -109,19 +101,6 @@ class TelegramBot(GenericBot):
|
|||
|
||||
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
|
||||
async def safe_api_call(f: typing.Callable, *args, **kwargs) -> typing.Optional:
|
||||
while True:
|
||||
|
@ -225,7 +204,13 @@ class TelegramBot(GenericBot):
|
|||
else:
|
||||
await self.safe_api_call(query.answer, text=response)
|
||||
|
||||
def _initialize(self):
|
||||
super()._initialize()
|
||||
self._init_client()
|
||||
|
||||
async def run(self):
|
||||
if not self.initialized:
|
||||
self._initialize()
|
||||
while True:
|
||||
# Get the latest 100 updates
|
||||
last_updates: typing.List[telegram.Update] = await self.safe_api_call(self.client.get_updates,
|
||||
|
|
|
@ -28,7 +28,7 @@ class CommandInterface:
|
|||
raise UnsupportedError()
|
||||
|
||||
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`.
|
||||
|
||||
Parameters:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""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."""
|
||||
|
||||
from .ciaoruozi import CiaoruoziCommand
|
||||
|
|
|
@ -8,7 +8,7 @@ from ..commanddata import CommandData
|
|||
class CiaoruoziCommand(Command):
|
||||
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 = ""
|
||||
|
||||
|
|
|
@ -2,26 +2,21 @@ import typing
|
|||
import re
|
||||
import datetime
|
||||
import telegram
|
||||
import os
|
||||
import aiohttp
|
||||
from ..command import Command
|
||||
from ..commandargs import CommandArgs
|
||||
from ..commanddata import CommandData
|
||||
from ...database.tables import Royal, Diario, Alias
|
||||
from ...database.tables import User, Diario, Alias
|
||||
from ...utils import asyncify
|
||||
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
|
||||
largest_photo = sorted(photosizes, key=lambda p: p.width * p.height)[-1]
|
||||
# Get the photo url
|
||||
photo_file: telegram.File = await asyncify(largest_photo.get_file)
|
||||
# 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={
|
||||
"image": photo_file.file_path,
|
||||
"type": "URL",
|
||||
|
@ -43,7 +38,7 @@ class DiarioCommand(Command):
|
|||
|
||||
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:
|
||||
if self.interface.name == "telegram":
|
||||
|
@ -74,7 +69,8 @@ class DiarioCommand(Command):
|
|||
if photosizes:
|
||||
# Text is a 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:
|
||||
media_url = None
|
||||
# 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
|
||||
photosizes: typing.Optional[typing.List[telegram.PhotoSize]] = message.photo
|
||||
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:
|
||||
media_url = None
|
||||
# Parse the text, if it exists
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import datetime
|
||||
import dateparser
|
||||
import os
|
||||
import telegram
|
||||
import asyncio
|
||||
import re
|
||||
|
@ -98,7 +97,7 @@ class MmCommand(Command):
|
|||
try:
|
||||
await self.interface.bot.safe_api_call(client.edit_message_text,
|
||||
text=telegram_escape(self._main_text(mmevent)),
|
||||
chat_id=os.environ["MM_CHANNEL_ID"],
|
||||
chat_id=-1001224004974,
|
||||
message_id=mmevent.message_id,
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
|
@ -384,7 +383,7 @@ class MmCommand(Command):
|
|||
await asyncify(self.interface.session.commit)
|
||||
|
||||
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)),
|
||||
parse_mode="HTML",
|
||||
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 .relationshiplinkchain import relationshiplinkchain
|
||||
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 .diario import Diario
|
||||
from .aliases import Alias
|
||||
|
@ -17,6 +17,6 @@ from .mmdecisions import MMDecision
|
|||
from .mmevents import MMEvent
|
||||
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",
|
||||
"MMResponse"]
|
||||
|
|
|
@ -5,7 +5,7 @@ from sqlalchemy import Column, \
|
|||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
# noinspection PyUnresolvedReferences
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
# noinspection PyUnresolvedReferences
|
||||
from .keygroups import Keygroup
|
||||
|
||||
|
@ -15,7 +15,7 @@ class ActiveKvGroup:
|
|||
|
||||
@declared_attr
|
||||
def royal_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"), primary_key=True)
|
||||
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def group_name(self):
|
||||
|
@ -23,7 +23,7 @@ class ActiveKvGroup:
|
|||
|
||||
@declared_attr
|
||||
def royal(self):
|
||||
return relationship("Royal", backref="active_kv_group")
|
||||
return relationship("User", backref="active_kv_group")
|
||||
|
||||
@declared_attr
|
||||
def group(self):
|
||||
|
|
|
@ -5,7 +5,7 @@ from sqlalchemy import Column, \
|
|||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
# noinspection PyUnresolvedReferences
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
|
||||
|
||||
class Alias:
|
||||
|
@ -13,7 +13,7 @@ class Alias:
|
|||
|
||||
@declared_attr
|
||||
def royal_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"))
|
||||
return Column(Integer, ForeignKey("users.uid"))
|
||||
|
||||
@declared_attr
|
||||
def alias(self):
|
||||
|
@ -21,7 +21,7 @@ class Alias:
|
|||
|
||||
@declared_attr
|
||||
def royal(self):
|
||||
return relationship("Royal", backref="aliases")
|
||||
return relationship("User", backref="aliases")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Alias {str(self)}>"
|
||||
|
|
|
@ -4,7 +4,7 @@ from sqlalchemy import Column, \
|
|||
ForeignKey
|
||||
from sqlalchemy.orm import relationship, backref
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
|
||||
|
||||
class Bio:
|
||||
|
@ -12,11 +12,11 @@ class Bio:
|
|||
|
||||
@declared_attr
|
||||
def royal_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"), primary_key=True)
|
||||
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def royal(self):
|
||||
return relationship("Royal", backref=backref("bio", uselist=False))
|
||||
return relationship("User", backref=backref("bio", uselist=False))
|
||||
|
||||
@declared_attr
|
||||
def contents(self):
|
||||
|
|
|
@ -9,7 +9,7 @@ from sqlalchemy import Column, \
|
|||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
# noinspection PyUnresolvedReferences
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
|
||||
|
||||
class Diario:
|
||||
|
@ -21,11 +21,11 @@ class Diario:
|
|||
|
||||
@declared_attr
|
||||
def creator_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"))
|
||||
return Column(Integer, ForeignKey("users.uid"))
|
||||
|
||||
@declared_attr
|
||||
def quoted_account_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"))
|
||||
return Column(Integer, ForeignKey("users.uid"))
|
||||
|
||||
@declared_attr
|
||||
def quoted(self):
|
||||
|
@ -53,11 +53,11 @@ class Diario:
|
|||
|
||||
@declared_attr
|
||||
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
|
||||
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):
|
||||
return f"<Diario diario_id={self.diario_id}" \
|
||||
|
|
|
@ -6,7 +6,7 @@ from sqlalchemy import Column, \
|
|||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
# noinspection PyUnresolvedReferences
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
|
||||
|
||||
class Discord:
|
||||
|
@ -14,7 +14,7 @@ class Discord:
|
|||
|
||||
@declared_attr
|
||||
def royal_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"))
|
||||
return Column(Integer, ForeignKey("users.uid"))
|
||||
|
||||
@declared_attr
|
||||
def discord_id(self):
|
||||
|
@ -34,7 +34,7 @@ class Discord:
|
|||
|
||||
@declared_attr
|
||||
def royal(self):
|
||||
return relationship("Royal", backref="discord")
|
||||
return relationship("User", backref="discord")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Discord {str(self)}>"
|
||||
|
|
|
@ -4,7 +4,7 @@ from sqlalchemy import Column, \
|
|||
ForeignKey
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from sqlalchemy.orm import relationship
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
from .medals import Medal
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ class MedalAward:
|
|||
|
||||
@declared_attr
|
||||
def royal_id(self):
|
||||
return Column(Integer, ForeignKey("royal.uid"), nullable=False)
|
||||
return Column(Integer, ForeignKey("users.uid"), nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def medal(self):
|
||||
|
@ -33,7 +33,7 @@ class MedalAward:
|
|||
|
||||
@declared_attr
|
||||
def royal(self):
|
||||
return relationship("Royal", backref="medals_received")
|
||||
return relationship("User", backref="medals_received")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<MedalAward of {self.medal} to {self.royal} on {self.date}>"
|
||||
|
|
|
@ -4,7 +4,7 @@ from sqlalchemy import Column, \
|
|||
ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
from .mmevents import MMEvent
|
||||
|
||||
|
||||
|
@ -13,11 +13,11 @@ class MMDecision:
|
|||
|
||||
@declared_attr
|
||||
def royal_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"), primary_key=True)
|
||||
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def royal(self):
|
||||
return relationship("Royal", backref="mmdecisions_taken")
|
||||
return relationship("User", backref="mmdecisions_taken")
|
||||
|
||||
@declared_attr
|
||||
def mmevent_id(self):
|
||||
|
|
|
@ -9,7 +9,7 @@ from sqlalchemy import Column, \
|
|||
BigInteger
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
if typing.TYPE_CHECKING:
|
||||
from .mmdecisions import MMDecision
|
||||
from .mmresponse import MMResponse
|
||||
|
@ -20,11 +20,11 @@ class MMEvent:
|
|||
|
||||
@declared_attr
|
||||
def creator_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"), nullable=False)
|
||||
return Column(Integer, ForeignKey("users.uid"), nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def creator(self):
|
||||
return relationship("Royal", backref="mmevents_created")
|
||||
return relationship("User", backref="mmevents_created")
|
||||
|
||||
@declared_attr
|
||||
def mmid(self):
|
||||
|
|
|
@ -4,7 +4,7 @@ from sqlalchemy import Column, \
|
|||
ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
from .mmevents import MMEvent
|
||||
|
||||
|
||||
|
@ -13,11 +13,11 @@ class MMResponse:
|
|||
|
||||
@declared_attr
|
||||
def royal_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"), primary_key=True)
|
||||
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def royal(self):
|
||||
return relationship("Royal", backref="mmresponses_given")
|
||||
return relationship("User", backref="mmresponses_given")
|
||||
|
||||
@declared_attr
|
||||
def mmevent_id(self):
|
||||
|
|
|
@ -7,7 +7,7 @@ from sqlalchemy import Column, \
|
|||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
# noinspection PyUnresolvedReferences
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
|
||||
|
||||
class Reminder:
|
||||
|
@ -19,11 +19,11 @@ class Reminder:
|
|||
|
||||
@declared_attr
|
||||
def creator_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"))
|
||||
return Column(Integer, ForeignKey("users.uid"))
|
||||
|
||||
@declared_attr
|
||||
def creator(self):
|
||||
return relationship("Royal", backref="reminders_created")
|
||||
return relationship("User", backref="reminders_created")
|
||||
|
||||
@declared_attr
|
||||
def interface_name(self):
|
||||
|
|
|
@ -5,8 +5,8 @@ from sqlalchemy import Column, \
|
|||
from sqlalchemy.ext.declarative import declared_attr
|
||||
|
||||
|
||||
class Royal:
|
||||
__tablename__ = "royals"
|
||||
class User:
|
||||
__tablename__ = "users"
|
||||
|
||||
@declared_attr
|
||||
def uid(self):
|
||||
|
@ -29,7 +29,7 @@ class Royal:
|
|||
return Column(LargeBinary)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Royal {self.username}>"
|
||||
return f"<User {self.username}>"
|
||||
|
||||
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.ext.declarative import declared_attr
|
||||
# noinspection PyUnresolvedReferences
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
|
||||
|
||||
class Telegram:
|
||||
|
@ -14,7 +14,7 @@ class Telegram:
|
|||
|
||||
@declared_attr
|
||||
def royal_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"))
|
||||
return Column(Integer, ForeignKey("users.uid"))
|
||||
|
||||
@declared_attr
|
||||
def tg_id(self):
|
||||
|
@ -34,7 +34,7 @@ class Telegram:
|
|||
|
||||
@declared_attr
|
||||
def royal(self):
|
||||
return relationship("Royal", backref="telegram")
|
||||
return relationship("User", backref="telegram")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Telegram {str(self)}>"
|
||||
|
|
|
@ -3,7 +3,7 @@ from sqlalchemy import Column, \
|
|||
ForeignKey
|
||||
from sqlalchemy.orm import relationship, backref
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
|
||||
|
||||
class TriviaScore:
|
||||
|
@ -11,11 +11,11 @@ class TriviaScore:
|
|||
|
||||
@declared_attr
|
||||
def royal_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"), primary_key=True)
|
||||
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def royal(self):
|
||||
return relationship("Royal", backref=backref("trivia_score", uselist=False))
|
||||
return relationship("User", backref=backref("trivia_score", uselist=False))
|
||||
|
||||
@declared_attr
|
||||
def correct_answers(self):
|
||||
|
|
|
@ -9,7 +9,7 @@ from sqlalchemy.ext.declarative import declared_attr
|
|||
# noinspection PyUnresolvedReferences
|
||||
from .wikipages import WikiPage
|
||||
# noinspection PyUnresolvedReferences
|
||||
from .royals import Royal
|
||||
from .royals import User
|
||||
|
||||
|
||||
class WikiRevision:
|
||||
|
@ -33,11 +33,11 @@ class WikiRevision:
|
|||
|
||||
@declared_attr
|
||||
def author_id(self):
|
||||
return Column(Integer, ForeignKey("royals.uid"), nullable=False)
|
||||
return Column(Integer, ForeignKey("users.uid"), nullable=False)
|
||||
|
||||
@declared_attr
|
||||
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
|
||||
def timestamp(self):
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
from .request import Request
|
||||
from .response import Response, ResponseSuccess, ResponseError
|
||||
from .package import Package
|
||||
from .royalnetlink import RoyalnetLink, NetworkError, NotConnectedError, NotIdentifiedError, ConnectionClosedError
|
||||
from .royalnetserver import RoyalnetServer
|
||||
from .royalnetconfig import RoyalnetConfig
|
||||
from .networklink import NetworkLink, NetworkError, NotConnectedError, NotIdentifiedError, ConnectionClosedError
|
||||
from .networkserver import NetworkServer
|
||||
from .networkconfig import NetworkConfig
|
||||
|
||||
__all__ = ["RoyalnetLink",
|
||||
__all__ = ["NetworkLink",
|
||||
"NetworkError",
|
||||
"NotConnectedError",
|
||||
"NotIdentifiedError",
|
||||
"Package",
|
||||
"RoyalnetServer",
|
||||
"RoyalnetConfig",
|
||||
"NetworkServer",
|
||||
"NetworkConfig",
|
||||
"ConnectionClosedError",
|
||||
"Request",
|
||||
"Response",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class RoyalnetConfig:
|
||||
class NetworkConfig:
|
||||
def __init__(self,
|
||||
master_uri: str,
|
||||
master_secret: str):
|
|
@ -13,19 +13,19 @@ log = _logging.getLogger(__name__)
|
|||
|
||||
|
||||
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):
|
||||
"""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):
|
||||
"""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):
|
||||
"""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):
|
||||
|
@ -69,7 +69,7 @@ def requires_identification(func):
|
|||
return new_func
|
||||
|
||||
|
||||
class RoyalnetLink:
|
||||
class NetworkLink:
|
||||
def __init__(self, master_uri: str, secret: str, link_type: str, request_handler, *,
|
||||
loop: asyncio.AbstractEventLoop = None):
|
||||
if ":" in link_type:
|
||||
|
@ -90,7 +90,7 @@ class RoyalnetLink:
|
|||
self.identify_event: asyncio.Event = asyncio.Event(loop=self._loop)
|
||||
|
||||
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}...")
|
||||
self.websocket = await websockets.connect(self.master_uri, loop=self._loop)
|
||||
self.connect_event.set()
|
||||
|
@ -98,7 +98,7 @@ class RoyalnetLink:
|
|||
|
||||
@requires_connection
|
||||
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:
|
||||
:py:exc:`royalnet.network.royalnetlink.ConnectionClosedError` if the connection closes."""
|
||||
|
@ -113,7 +113,7 @@ class RoyalnetLink:
|
|||
# What to do now? Let's just reraise.
|
||||
raise ConnectionClosedError()
|
||||
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}")
|
||||
return package
|
||||
|
|
@ -12,7 +12,7 @@ log = _logging.getLogger(__name__)
|
|||
|
||||
|
||||
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):
|
||||
self.socket: websockets.WebSocketServerProtocol = socket
|
||||
self.nid: typing.Optional[str] = None
|
||||
|
@ -30,11 +30,11 @@ class ConnectedClient:
|
|||
destination=self.nid))
|
||||
|
||||
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())
|
||||
|
||||
|
||||
class RoyalnetServer:
|
||||
class NetworkServer:
|
||||
def __init__(self, address: str, port: int, required_secret: str, *, loop: asyncio.AbstractEventLoop = None):
|
||||
self.address: str = address
|
||||
self.port: int = port
|
||||
|
@ -131,9 +131,12 @@ class RoyalnetServer:
|
|||
async def serve(self):
|
||||
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}")
|
||||
# noinspection PyAsyncCall
|
||||
self._loop.create_task(self.serve())
|
||||
# Just to be sure it has started on Linux
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
def run_blocking(self):
|
||||
self._loop.run_until_complete(self.run())
|
|
@ -4,7 +4,7 @@ import typing
|
|||
|
||||
|
||||
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."""
|
||||
|
||||
def __init__(self,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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."""
|
||||
|
||||
|
|
|
@ -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 uuid
|
||||
from royalnet.database import Alchemy
|
||||
from royalnet.database.tables import Royal, WikiPage, WikiRevision
|
||||
from royalnet.database.tables import User, WikiPage, WikiRevision
|
||||
|
||||
|
||||
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:
|
||||
for line in file.readlines():
|
||||
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 os
|
||||
from ...royalprint import Royalprint
|
||||
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')
|
||||
rp = Royalprint("diarioview", __name__, url_prefix="/diario", template_folder=tmpl_dir,
|
||||
required_tables={Royal, Diario})
|
||||
required_tables={User, Diario})
|
||||
|
||||
|
||||
@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 os
|
||||
from ...royalprint import Royalprint
|
||||
|
|
|
@ -5,11 +5,11 @@ import datetime
|
|||
import bcrypt
|
||||
from ...royalprint import Royalprint
|
||||
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')
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@ import os
|
|||
import bcrypt
|
||||
from ...royalprint import Royalprint
|
||||
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')
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ from ....utils.wikirender import prepare_page_markdown, RenderError
|
|||
# Maybe some of these tables are optional...
|
||||
tmpl_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
|
||||
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})
|
||||
|
||||
|
||||
|
|
|
@ -6,11 +6,11 @@ import datetime
|
|||
import os
|
||||
from ...royalprint import Royalprint
|
||||
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')
|
||||
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)
|
||||
|
||||
|
||||
|
@ -38,7 +38,7 @@ def tglogin_done():
|
|||
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()
|
||||
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
|
||||
f.session["royal"] = {
|
||||
"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 uuid
|
||||
import os
|
||||
|
@ -6,12 +6,12 @@ import datetime
|
|||
import difflib
|
||||
from ...royalprint import Royalprint
|
||||
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')
|
||||
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"])
|
||||
|
|
|
@ -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 os
|
||||
from ...royalprint import Royalprint
|
||||
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
|
||||
|
||||
|
||||
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,
|
||||
required_tables={Royal, WikiPage, WikiRevision})
|
||||
required_tables={User, WikiPage, WikiRevision})
|
||||
|
||||
|
||||
def prepare_page(page):
|
||||
|
|
5
setup.py
5
setup.py
|
@ -9,7 +9,7 @@ setuptools.setup(
|
|||
version=royalnet.version.semantic,
|
||||
author="Stefano Pigozzi",
|
||||
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_content_type="text/markdown",
|
||||
url="https://github.com/royal-games/royalnet",
|
||||
|
@ -31,7 +31,8 @@ setuptools.setup(
|
|||
"mcstatus>=2.2.1",
|
||||
"sortedcontainers>=2.1.0",
|
||||
"sentry-sdk>=0.11.1",
|
||||
"click>=7.0"],
|
||||
"click>=7.0",
|
||||
"keyring>=19.2.0"],
|
||||
python_requires=">=3.7",
|
||||
classifiers=[
|
||||
"Development Status :: 3 - Alpha",
|
||||
|
|
|
@ -2,7 +2,7 @@ import pytest
|
|||
import uuid
|
||||
import asyncio
|
||||
import logging
|
||||
from royalnet.network import Package, RoyalnetLink, RoyalnetServer, ConnectionClosedError, Request
|
||||
from royalnet.network import Package, NetworkLink, NetworkServer, ConnectionClosedError, Request
|
||||
|
||||
|
||||
log = logging.root
|
||||
|
@ -41,16 +41,16 @@ def test_request_creation():
|
|||
|
||||
def test_links(async_loop: asyncio.AbstractEventLoop):
|
||||
address, port = "127.0.0.1", 1235
|
||||
master = RoyalnetServer(address, port, "test")
|
||||
master = NetworkServer(address, port, "test")
|
||||
async_loop.run_until_complete(master.start())
|
||||
# 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):
|
||||
async_loop.run_until_complete(wrong_secret_link.run())
|
||||
# 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())
|
||||
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())
|
||||
message = {"ciao": "ciao"}
|
||||
response = async_loop.run_until_complete(link1.request(message, "two"))
|
||||
|
|
Loading…
Reference in a new issue