1
Fork 0
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:
Steffo 2019-09-28 18:04:35 +02:00
parent b1ee018254
commit 9b1c8704bd
50 changed files with 384 additions and 259 deletions

View file

@ -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:
```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)
```

View file

@ -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
View 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()

View file

@ -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

View file

@ -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

View file

@ -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"]

View file

@ -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?

View file

@ -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.interface_name, self._network_handler) self.network: NetworkLink = NetworkLink(self.uninitialized_network_config.master_uri,
log.debug(f"Running RoyalnetLink {self.network}") self.uninitialized_network_config.master_secret,
self.loop.create_task(self.network.run()) 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: 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``. """
log.debug(f"Initializing database") if self.uninitialized_database_config:
required_tables = {database_config.master_table, database_config.identity_table} log.debug(f"Initializing database")
for command in commands: required_tables = {self.uninitialized_database_config.master_table, self.uninitialized_database_config.identity_table}
required_tables = required_tables.union(command.require_alchemy_tables) for command in self.uninitialized_commands:
log.debug(f"Found {len(required_tables)} required tables") required_tables = required_tables.union(command.require_alchemy_tables)
self.alchemy = Alchemy(database_config.database_uri, required_tables) log.debug(f"Found {len(required_tables)} required tables")
self.master_table = self.alchemy.__getattribute__(database_config.master_table.__name__) self.alchemy = Alchemy(self.uninitialized_database_config.database_uri, required_tables)
self.identity_table = self.alchemy.__getattribute__(database_config.identity_table.__name__) self.master_table = self.alchemy.__getattribute__(self.uninitialized_database_config.master_table.__name__)
self.identity_column = self.identity_table.__getattribute__(self.identity_table, self.identity_table = self.alchemy.__getattribute__(self.uninitialized_database_config.identity_table.__name__)
database_config.identity_column_name) self.identity_column = self.identity_table.__getattribute__(self.identity_table,
self.identity_chain = relationshiplinkchain(self.master_table, self.identity_table) self.uninitialized_database_config.identity_column_name)
log.debug(f"Identity chain is {self.identity_chain}") 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, *, def __init__(self, *,
royalnet_config: typing.Optional[RoyalnetConfig] = None, network_config: typing.Optional[NetworkConfig] = None,
database_config: typing.Optional[DatabaseConfig] = None, database_config: typing.Optional[DatabaseConfig] = None,
commands: typing.List[typing.Type[Command]] = None, commands: typing.List[typing.Type[Command]] = None,
sentry_dsn: typing.Optional[str] = None, sentry_dsn: typing.Optional[str] = None,
loop: asyncio.AbstractEventLoop = None): loop: asyncio.AbstractEventLoop = None,
if loop is None: secrets_name: str = "__default__"):
self.loop = asyncio.get_event_loop() self.initialized = False
else: self.uninitialized_network_config = network_config
self.loop = loop self.uninitialized_database_config = database_config
if sentry_dsn: self.uninitialized_commands = commands
log.debug("Sentry integration enabled") self.uninitialized_sentry_dsn = sentry_dsn
self.sentry = sentry_sdk.init(sentry_dsn, integrations=[AioHttpIntegration(), self.uninitialized_loop = loop
SqlalchemyIntegration(), self.secrets_name = secrets_name
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
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.""" """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())

View file

@ -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,

View file

@ -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:

View file

@ -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

View file

@ -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 = ""

View file

@ -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

View file

@ -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
View 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()

View file

@ -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"]

View file

@ -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"]

View file

@ -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):

View file

@ -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)}>"

View file

@ -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):

View file

@ -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}" \

View file

@ -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)}>"

View file

@ -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}>"

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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):

View file

@ -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

View file

@ -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)}>"

View file

@ -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):

View file

@ -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):

View file

@ -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",

View file

@ -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):

View file

@ -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

View file

@ -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())

View file

@ -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,

View file

@ -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."""

View file

@ -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()

View file

@ -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)

View file

@ -1 +1 @@
semantic = "5.0a58" semantic = "5.0a59"

View file

@ -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})

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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})

View file

@ -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,

View file

@ -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"])

View file

@ -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):

View file

@ -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",

View file

@ -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"))