mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-27 13:34:28 +00:00
Merge branch 'getting-rid-of-interfaces' into master
# Conflicts: # royalnet/backpack/commands/royalnetversion.py # royalnet/version.py
This commit is contained in:
commit
e990180743
22 changed files with 293 additions and 437 deletions
60
poetry.lock
generated
60
poetry.lock
generated
|
@ -851,6 +851,15 @@ optional = true
|
||||||
python-versions = ">= 3.5"
|
python-versions = ">= 3.5"
|
||||||
version = "6.0.4"
|
version = "6.0.4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Backported and Experimental Type Hints for Python 3.5+"
|
||||||
|
marker = "python_version >= \"3.6\""
|
||||||
|
name = "typing-extensions"
|
||||||
|
optional = true
|
||||||
|
python-versions = "*"
|
||||||
|
version = "3.7.4.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "tzinfo object for the local timezone"
|
description = "tzinfo object for the local timezone"
|
||||||
|
@ -876,7 +885,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||||
version = "1.25.9"
|
version = "1.25.10"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
brotli = ["brotlipy (>=0.6.0)"]
|
brotli = ["brotlipy (>=0.6.0)"]
|
||||||
|
@ -930,11 +939,12 @@ marker = "python_version >= \"3.6\""
|
||||||
name = "yarl"
|
name = "yarl"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
version = "1.4.2"
|
version = "1.5.0"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
idna = ">=2.0"
|
idna = ">=2.0"
|
||||||
multidict = ">=4.0"
|
multidict = ">=4.0"
|
||||||
|
typing-extensions = ">=3.7.4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
|
@ -957,7 +967,8 @@ sentry = ["sentry_sdk"]
|
||||||
telegram = ["python_telegram_bot"]
|
telegram = ["python_telegram_bot"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
content-hash = "5a703d07774f43406d8426b2945eada118bfa44bab2f979df7c3cf2bba2e224d"
|
content-hash = "341cfeb2b18909f4d9298ab7ce059c4fdbf2283f01d8eb7a5af210697d25a419"
|
||||||
|
lock-version = "1.0"
|
||||||
python-versions = "^3.8"
|
python-versions = "^3.8"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
|
@ -1500,6 +1511,11 @@ tornado = [
|
||||||
{file = "tornado-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9"},
|
{file = "tornado-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9"},
|
||||||
{file = "tornado-6.0.4.tar.gz", hash = "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc"},
|
{file = "tornado-6.0.4.tar.gz", hash = "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc"},
|
||||||
]
|
]
|
||||||
|
typing-extensions = [
|
||||||
|
{file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"},
|
||||||
|
{file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"},
|
||||||
|
{file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"},
|
||||||
|
]
|
||||||
tzlocal = [
|
tzlocal = [
|
||||||
{file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"},
|
{file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"},
|
||||||
{file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"},
|
{file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"},
|
||||||
|
@ -1509,8 +1525,8 @@ unpaddedbase64 = [
|
||||||
{file = "unpaddedbase64-1.1.0-py2.py3-none-any.whl", hash = "sha256:81cb4eaaa28cc6a282dd3f2c3855eaa1fbaafa736b5ee64df69889e20540a339"},
|
{file = "unpaddedbase64-1.1.0-py2.py3-none-any.whl", hash = "sha256:81cb4eaaa28cc6a282dd3f2c3855eaa1fbaafa736b5ee64df69889e20540a339"},
|
||||||
]
|
]
|
||||||
urllib3 = [
|
urllib3 = [
|
||||||
{file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
|
{file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"},
|
||||||
{file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"},
|
{file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"},
|
||||||
]
|
]
|
||||||
uvicorn = [
|
uvicorn = [
|
||||||
{file = "uvicorn-0.10.9-py3-none-any.whl", hash = "sha256:dc7119b28e15c4c737315c5a570081b0a5a7d8d5c1e8a70a7be70043d88b23a7"},
|
{file = "uvicorn-0.10.9-py3-none-any.whl", hash = "sha256:dc7119b28e15c4c737315c5a570081b0a5a7d8d5c1e8a70a7be70043d88b23a7"},
|
||||||
|
@ -1556,23 +1572,23 @@ websockets = [
|
||||||
{file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"},
|
{file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"},
|
||||||
]
|
]
|
||||||
yarl = [
|
yarl = [
|
||||||
{file = "yarl-1.4.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b"},
|
{file = "yarl-1.5.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:2657716c1fc998f5f2675c0ee6ce91282e0da0ea9e4a94b584bb1917e11c1559"},
|
||||||
{file = "yarl-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1"},
|
{file = "yarl-1.5.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:cf5eb664910d759bbae0b76d060d6e21f8af5098242d66c448bbebaf2a7bfa70"},
|
||||||
{file = "yarl-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080"},
|
{file = "yarl-1.5.0-cp35-cp35m-win32.whl", hash = "sha256:875b2a741ce0208f3b818008a859ab5d0f461e98a32bbdc6af82231a9e761c55"},
|
||||||
{file = "yarl-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a"},
|
{file = "yarl-1.5.0-cp35-cp35m-win_amd64.whl", hash = "sha256:1707230e1ea48ea06a3e20acb4ce05a38d2465bd9566c21f48f6212a88e47536"},
|
||||||
{file = "yarl-1.4.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f"},
|
{file = "yarl-1.5.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:9a592c4aa642249e9bdaf76897d90feeb08118626b363a6be8788a9b300274b5"},
|
||||||
{file = "yarl-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea"},
|
{file = "yarl-1.5.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f058b6541477022c7b54db37229f87dacf3b565de4f901ff5a0a78556a174fea"},
|
||||||
{file = "yarl-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb"},
|
{file = "yarl-1.5.0-cp36-cp36m-win32.whl", hash = "sha256:5d410f69b4f92c5e1e2a8ffb73337cd8a274388c6975091735795588a538e605"},
|
||||||
{file = "yarl-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70"},
|
{file = "yarl-1.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5bbcb195da7de57f4508b7508c33f7593e9516e27732d08b9aad8586c7b8c384"},
|
||||||
{file = "yarl-1.4.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d"},
|
{file = "yarl-1.5.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a1772068401d425e803999dada29a6babf041786e08be5e79ef63c9ecc4c9575"},
|
||||||
{file = "yarl-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce"},
|
{file = "yarl-1.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1f269e8e6676193a94635399a77c9059e1826fb6265c9204c9e5a8ccd36006e1"},
|
||||||
{file = "yarl-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"},
|
{file = "yarl-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:431faa6858f0ea323714d8b7b4a7da1db2eeb9403607f0eaa3800ab2c5a4b627"},
|
||||||
{file = "yarl-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce"},
|
{file = "yarl-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b325fefd574ebef50e391a1072d1712a60348ca29c183e1d546c9d87fec2cd32"},
|
||||||
{file = "yarl-1.4.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b"},
|
{file = "yarl-1.5.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b065a5c3e050395ae563019253cc6c769a50fd82d7fa92d07476273521d56b7c"},
|
||||||
{file = "yarl-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae"},
|
{file = "yarl-1.5.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:66b4f345e9573e004b1af184bc00431145cf5e089a4dcc1351505c1f5750192c"},
|
||||||
{file = "yarl-1.4.2-cp38-cp38-win32.whl", hash = "sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462"},
|
{file = "yarl-1.5.0-cp38-cp38-win32.whl", hash = "sha256:9a3266b047d15e78bba38c8455bf68b391c040231ca5965ef867f7cbbc60bde5"},
|
||||||
{file = "yarl-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6"},
|
{file = "yarl-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:f5cfed0766837303f688196aa7002730d62c5cc802d98c6395ea1feb87252727"},
|
||||||
{file = "yarl-1.4.2.tar.gz", hash = "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b"},
|
{file = "yarl-1.5.0.tar.gz", hash = "sha256:5c82f5b1499342339f22c83b97dbe2b8a09e47163fab86cd934a8dd46620e0fb"},
|
||||||
]
|
]
|
||||||
youtube-dl = [
|
youtube-dl = [
|
||||||
{file = "youtube_dl-2020.6.16.1-py2.py3-none-any.whl", hash = "sha256:e54b307048bb18164729fb278013af6d5477c69c3d995147205a16f22a61296b"},
|
{file = "youtube_dl-2020.6.16.1-py2.py3-none-any.whl", hash = "sha256:e54b307048bb18164729fb278013af6d5477c69c3d995147205a16f22a61296b"},
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "royalnet"
|
name = "royalnet"
|
||||||
version = "5.10.4"
|
version = "5.11.0"
|
||||||
description = "A multipurpose bot and web framework"
|
description = "A multipurpose bot and web framework"
|
||||||
authors = ["Stefano Pigozzi <ste.pigozzi@gmail.com>"]
|
authors = ["Stefano Pigozzi <ste.pigozzi@gmail.com>"]
|
||||||
license = "AGPL-3.0+"
|
license = "AGPL-3.0+"
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
from typing import *
|
from typing import *
|
||||||
import royalnet
|
|
||||||
import royalnet.commands as rc
|
import royalnet.commands as rc
|
||||||
import royalnet.utils as ru
|
|
||||||
from ..tables import User
|
from ..tables import User
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
from typing import *
|
from typing import *
|
||||||
import royalnet
|
|
||||||
import royalnet.commands as rc
|
import royalnet.commands as rc
|
||||||
import royalnet.utils as ru
|
|
||||||
from ..tables import User
|
from ..tables import User
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,9 @@ from typing import *
|
||||||
import royalnet
|
import royalnet
|
||||||
import royalnet.commands as rc
|
import royalnet.commands as rc
|
||||||
import royalnet.utils as ru
|
import royalnet.utils as ru
|
||||||
|
import royalnet.serf.telegram as rst
|
||||||
|
import royalnet.serf.discord as rsd
|
||||||
|
import royalnet.serf.matrix as rsm
|
||||||
from ..tables.telegram import Telegram
|
from ..tables.telegram import Telegram
|
||||||
from ..tables.discord import Discord
|
from ..tables.discord import Discord
|
||||||
|
|
||||||
|
@ -27,7 +30,7 @@ class RoyalnetsyncCommand(rc.Command):
|
||||||
if not successful:
|
if not successful:
|
||||||
raise rc.InvalidInputError(f"Invalid password!")
|
raise rc.InvalidInputError(f"Invalid password!")
|
||||||
|
|
||||||
if self.interface.name == "telegram":
|
if isinstance(self.serf, rst.TelegramSerf):
|
||||||
import telegram
|
import telegram
|
||||||
message: telegram.Message = data.message
|
message: telegram.Message = data.message
|
||||||
from_user: telegram.User = message.from_user
|
from_user: telegram.User = message.from_user
|
||||||
|
@ -53,7 +56,7 @@ class RoyalnetsyncCommand(rc.Command):
|
||||||
await data.session_commit()
|
await data.session_commit()
|
||||||
await data.reply(f"↔️ Account {tg_user} synced to {author}!")
|
await data.reply(f"↔️ Account {tg_user} synced to {author}!")
|
||||||
|
|
||||||
elif self.interface.name == "discord":
|
elif isinstance(self.serf, rsd.DiscordSerf):
|
||||||
import discord
|
import discord
|
||||||
message: discord.Message = data.message
|
message: discord.Message = data.message
|
||||||
author: discord.User = message.author
|
author: discord.User = message.author
|
||||||
|
@ -79,8 +82,8 @@ class RoyalnetsyncCommand(rc.Command):
|
||||||
await data.session_commit()
|
await data.session_commit()
|
||||||
await data.reply(f"↔️ Account {ds_user} synced to {author}!")
|
await data.reply(f"↔️ Account {ds_user} synced to {author}!")
|
||||||
|
|
||||||
elif self.interface.name == "matrix":
|
elif isinstance(self.serf, rsm.MatrixSerf):
|
||||||
raise rc.UnsupportedError(f"{self} hasn't been implemented for Matrix yet")
|
raise rc.UnsupportedError(f"{self} hasn't been implemented for Matrix yet")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise rc.UnsupportedError(f"Unknown interface: {self.interface.name}")
|
raise rc.UnsupportedError(f"Unknown interface: {self.serf.__class__.__qualname__}")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import royalnet
|
import pkg_resources
|
||||||
from royalnet.commands import *
|
from royalnet.commands import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,12 +7,17 @@ class RoyalnetversionCommand(Command):
|
||||||
|
|
||||||
description: str = "Display the current Royalnet version."
|
description: str = "Display the current Royalnet version."
|
||||||
|
|
||||||
|
@property
|
||||||
|
def royalnet_version(self):
|
||||||
|
return pkg_resources.get_distribution("royalnet").version
|
||||||
|
|
||||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||||
# noinspection PyUnreachableCode
|
# noinspection PyUnreachableCode
|
||||||
if __debug__:
|
if __debug__:
|
||||||
message = f"ℹ️ Royalnet [url=https://github.com/Steffo99/royalnet/]Unreleased[/url]\n"
|
message = f"ℹ️ Royalnet [url=https://github.com/Steffo99/royalnet/]Unreleased[/url]\n"
|
||||||
else:
|
else:
|
||||||
message = f"ℹ️ Royalnet [url=https://github.com/Steffo99/royalnet/releases/tag/{royalnet.__version__}]{royalnet.__version__}[/url]\n"
|
message = f"ℹ️ Royalnet [url=https://github.com/Steffo99/royalnet/releases/tag/{self.royalnet_version}]" \
|
||||||
if "69" in royalnet.__version__:
|
f"{self.royalnet_version}[/url]\n"
|
||||||
|
if "69" in royalnet.version.semantic:
|
||||||
message += "(Nice.)"
|
message += "(Nice.)"
|
||||||
await data.reply(message)
|
await data.reply(message)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from royalnet.commands import *
|
from royalnet.commands import *
|
||||||
|
|
||||||
|
|
||||||
class ExceptionEvent(Event):
|
class ExceptionEvent(HeraldEvent):
|
||||||
name = "exception"
|
name = "exception"
|
||||||
|
|
||||||
def run(self, **kwargs):
|
def run(self, **kwargs):
|
||||||
if not self.interface.config["exc_debug"]:
|
if not self.config["exc_debug"]:
|
||||||
raise UserError(f"{self.interface.prefix}{self.name} is not enabled.")
|
raise UserError(f"{self.__class__.__name__} is not enabled.")
|
||||||
raise Exception(f"{self.name} event was called")
|
raise Exception(f"{self.name} event was called")
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
"""The subpackage providing all classes related to Royalnet commands."""
|
"""The subpackage providing all classes related to Royalnet commands."""
|
||||||
|
|
||||||
from .commandinterface import CommandInterface
|
|
||||||
from .command import Command
|
from .command import Command
|
||||||
from .commanddata import CommandData
|
from .commanddata import CommandData
|
||||||
from .commandargs import CommandArgs
|
from .commandargs import CommandArgs
|
||||||
from .event import Event
|
from .heraldevent import HeraldEvent
|
||||||
from .errors import \
|
from .errors import \
|
||||||
CommandError, InvalidInputError, UnsupportedError, ConfigurationError, ExternalError, UserError, ProgramError
|
CommandError, InvalidInputError, UnsupportedError, ConfigurationError, ExternalError, UserError, ProgramError
|
||||||
from .keyboardkey import KeyboardKey
|
from .keyboardkey import KeyboardKey
|
||||||
from .configdict import ConfigDict
|
from .configdict import ConfigDict
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"CommandInterface",
|
|
||||||
"Command",
|
"Command",
|
||||||
"CommandData",
|
"CommandData",
|
||||||
"CommandArgs",
|
"CommandArgs",
|
||||||
|
@ -22,7 +20,7 @@ __all__ = [
|
||||||
"ExternalError",
|
"ExternalError",
|
||||||
"UserError",
|
"UserError",
|
||||||
"ProgramError",
|
"ProgramError",
|
||||||
"Event",
|
"HeraldEvent",
|
||||||
"KeyboardKey",
|
"KeyboardKey",
|
||||||
"ConfigDict",
|
"ConfigDict",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
|
import abc
|
||||||
from typing import *
|
from typing import *
|
||||||
from .commandinterface import CommandInterface
|
|
||||||
from .commandargs import CommandArgs
|
from .commandargs import CommandArgs
|
||||||
from .commanddata import CommandData
|
from .commanddata import CommandData
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..serf import Serf
|
||||||
|
|
||||||
class Command:
|
|
||||||
|
class Command(metaclass=abc.ABCMeta):
|
||||||
name: str = NotImplemented
|
name: str = NotImplemented
|
||||||
"""The main name of the command.
|
"""The main name of the command.
|
||||||
|
|
||||||
|
@ -24,31 +27,23 @@ class Command:
|
||||||
"""The syntax of the command, to be displayed when a :py:exc:`InvalidInputError` is raised,
|
"""The syntax of the command, to be displayed when a :py:exc:`InvalidInputError` is raised,
|
||||||
in the format ``(required_arg) [optional_arg]``."""
|
in the format ``(required_arg) [optional_arg]``."""
|
||||||
|
|
||||||
def __init__(self, interface: CommandInterface):
|
def __init__(self, serf: "Serf", config):
|
||||||
self.interface = interface
|
self.serf: "Serf" = serf
|
||||||
|
self.config = config
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"[c]{self.interface.prefix}{self.name}[/c]"
|
return f"[c]{self.serf.prefix}{self.name}[/c]"
|
||||||
|
|
||||||
@property
|
|
||||||
def serf(self):
|
|
||||||
"""A shortcut for :attr:`.interface.serf`."""
|
|
||||||
return self.interface.serf
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def alchemy(self):
|
def alchemy(self):
|
||||||
"""A shortcut for :attr:`.interface.alchemy`."""
|
"""A shortcut for :attr:`.interface.alchemy`."""
|
||||||
return self.interface.alchemy
|
return self.serf.alchemy
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loop(self):
|
def loop(self):
|
||||||
"""A shortcut for :attr:`.interface.loop`."""
|
"""A shortcut for :attr:`.interface.loop`."""
|
||||||
return self.interface.loop
|
return self.serf.loop
|
||||||
|
|
||||||
@property
|
|
||||||
def config(self):
|
|
||||||
"""A shortcut for :attr:`.interface.config`."""
|
|
||||||
return self.interface.config
|
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
@ -5,32 +5,33 @@ import asyncio as aio
|
||||||
import royalnet.utils as ru
|
import royalnet.utils as ru
|
||||||
import io
|
import io
|
||||||
from .errors import UnsupportedError
|
from .errors import UnsupportedError
|
||||||
from .commandinterface import CommandInterface
|
from royalnet.backpack.tables.users import User
|
||||||
from royalnet.backpack.tables.aliases import Alias
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .keyboardkey import KeyboardKey
|
from .keyboardkey import KeyboardKey
|
||||||
from royalnet.backpack.tables.users import User
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CommandData:
|
class CommandData:
|
||||||
def __init__(self, interface: CommandInterface, loop: aio.AbstractEventLoop):
|
def __init__(self, command):
|
||||||
self.loop: aio.AbstractEventLoop = loop
|
self.command = command
|
||||||
self._interface: CommandInterface = interface
|
|
||||||
self._session = None
|
self._session = None
|
||||||
|
|
||||||
# TODO: make this asyncronous... somehow?
|
# TODO: make this asyncronous... somehow?
|
||||||
@property
|
@property
|
||||||
def session(self):
|
def session(self):
|
||||||
if self._session is None:
|
if self._session is None:
|
||||||
if self._interface.alchemy is None:
|
if self.command.serf.alchemy is None:
|
||||||
raise UnsupportedError("'alchemy' is not enabled on this Royalnet instance")
|
raise UnsupportedError("'alchemy' is not enabled on this Royalnet instance")
|
||||||
log.debug("Creating Session...")
|
log.debug("Creating Session...")
|
||||||
self._session = self._interface.alchemy.Session()
|
self._session = self.command.serf.alchemy.Session()
|
||||||
return self._session
|
return self._session
|
||||||
|
|
||||||
|
@property
|
||||||
|
def loop(self):
|
||||||
|
return self.command.serf.loop
|
||||||
|
|
||||||
async def session_commit(self):
|
async def session_commit(self):
|
||||||
"""Asyncronously commit the :attr:`.session` of this object."""
|
"""Asyncronously commit the :attr:`.session` of this object."""
|
||||||
if self._session:
|
if self._session:
|
||||||
|
@ -83,7 +84,7 @@ class CommandData:
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
alias: the Alias to search for."""
|
alias: the Alias to search for."""
|
||||||
return await Alias.find_user(self._interface.alchemy, self.session, alias)
|
return await User.find(self.command.serf.alchemy, self.session, alias)
|
||||||
|
|
||||||
@contextlib.asynccontextmanager
|
@contextlib.asynccontextmanager
|
||||||
async def keyboard(self, text, keys: List["KeyboardKey"]):
|
async def keyboard(self, text, keys: List["KeyboardKey"]):
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
from typing import *
|
|
||||||
import asyncio as aio
|
|
||||||
from .errors import UnsupportedError
|
|
||||||
from .configdict import ConfigDict
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .event import Event
|
|
||||||
from .command import Command
|
|
||||||
from ..alchemy import Alchemy
|
|
||||||
from ..serf import Serf
|
|
||||||
from ..constellation import Constellation
|
|
||||||
|
|
||||||
|
|
||||||
class CommandInterface:
|
|
||||||
name: str = NotImplemented
|
|
||||||
"""The name of the :class:`CommandInterface` that's being implemented.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
``telegram``, ``discord``, ``console``..."""
|
|
||||||
|
|
||||||
prefix: str = NotImplemented
|
|
||||||
"""The prefix used by commands on the interface.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
``/`` on Telegram, ``!`` on Discord."""
|
|
||||||
|
|
||||||
serf: Optional["Serf"] = None
|
|
||||||
"""A reference to the :class:`~royalnet.serf.Serf` that is implementing this :class:`CommandInterface`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
A reference to a :class:`~royalnet.serf.telegram.TelegramSerf`."""
|
|
||||||
|
|
||||||
constellation: Optional["Constellation"] = None
|
|
||||||
"""A reference to the Constellation that is implementing this :class:`CommandInterface`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
A reference to a :class:`~royalnet.constellation.Constellation`."""
|
|
||||||
|
|
||||||
def __init__(self, config: Dict[str, Any]):
|
|
||||||
self.config: ConfigDict[str, Any] = ConfigDict.convert(config)
|
|
||||||
"""The config section for the pack of the command."""
|
|
||||||
|
|
||||||
# Will be bound after the command/event has been created
|
|
||||||
self.command: Optional[Command] = None
|
|
||||||
self.event: Optional[Event] = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def alchemy(self) -> "Alchemy":
|
|
||||||
"""A shortcut for :attr:`.serf.alchemy`."""
|
|
||||||
return self.serf.alchemy
|
|
||||||
|
|
||||||
@property
|
|
||||||
def table(self) -> "Callable":
|
|
||||||
"""A shortcut for :func:`.serf.alchemy.get`.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
UnsupportedError: if :attr:`.alchemy` is :const:`None`."""
|
|
||||||
if self.alchemy is None:
|
|
||||||
raise UnsupportedError("'alchemy' is not enabled on this Royalnet instance")
|
|
||||||
return self.alchemy.get
|
|
||||||
|
|
||||||
@property
|
|
||||||
def loop(self) -> aio.AbstractEventLoop:
|
|
||||||
"""A shortcut for :attr:`.serf.loop`."""
|
|
||||||
if self.serf:
|
|
||||||
return self.serf.loop
|
|
||||||
raise UnsupportedError("This command is not being run in a serf.")
|
|
||||||
|
|
||||||
async def call_herald_event(self, destination: str, event_name: str, **kwargs) -> dict:
|
|
||||||
"""Call an event function on a different :class:`~royalnet.serf.Serf`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
You can run a function on a :class:`~royalnet.serf.discord.DiscordSerf` from a
|
|
||||||
:class:`~royalnet.serf.telegram.TelegramSerf`.
|
|
||||||
"""
|
|
||||||
raise UnsupportedError(f"{self.call_herald_event.__name__} is not supported on this platform.")
|
|
|
@ -1,41 +0,0 @@
|
||||||
import asyncio as aio
|
|
||||||
from .commandinterface import CommandInterface
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from serf import Serf
|
|
||||||
|
|
||||||
|
|
||||||
class Event:
|
|
||||||
"""A remote procedure call triggered by a :mod:`royalnet.herald` request."""
|
|
||||||
|
|
||||||
name = NotImplemented
|
|
||||||
"""The event_name that will trigger this event."""
|
|
||||||
|
|
||||||
def __init__(self, interface: CommandInterface):
|
|
||||||
"""Bind the event to a :class:`~royalnet.serf.Serf`."""
|
|
||||||
self.interface: CommandInterface = interface
|
|
||||||
"""The :class:`CommandInterface` available to this :class:`Event`."""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def serf(self) -> "Serf":
|
|
||||||
"""A shortcut for :attr:`.interface.serf`."""
|
|
||||||
return self.interface.serf
|
|
||||||
|
|
||||||
@property
|
|
||||||
def alchemy(self):
|
|
||||||
"""A shortcut for :attr:`.interface.alchemy`."""
|
|
||||||
return self.interface.alchemy
|
|
||||||
|
|
||||||
@property
|
|
||||||
def loop(self) -> aio.AbstractEventLoop:
|
|
||||||
"""A shortcut for :attr:`.interface.loop`."""
|
|
||||||
return self.interface.loop
|
|
||||||
|
|
||||||
@property
|
|
||||||
def config(self) -> dict:
|
|
||||||
"""A shortcut for :attr:`.interface.config`."""
|
|
||||||
return self.interface.config
|
|
||||||
|
|
||||||
async def run(self, **kwargs):
|
|
||||||
raise NotImplementedError()
|
|
29
royalnet/commands/heraldevent.py
Normal file
29
royalnet/commands/heraldevent.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import asyncio as aio
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..serf import Serf
|
||||||
|
|
||||||
|
|
||||||
|
class HeraldEvent:
|
||||||
|
"""A remote procedure call triggered by a :mod:`royalnet.herald` request."""
|
||||||
|
|
||||||
|
name = NotImplemented
|
||||||
|
"""The event_name that will trigger this event."""
|
||||||
|
|
||||||
|
def __init__(self, serf: "Serf", config):
|
||||||
|
self.serf: "Serf" = serf
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alchemy(self):
|
||||||
|
"""A shortcut for :attr:`.interface.alchemy`."""
|
||||||
|
return self.serf.alchemy
|
||||||
|
|
||||||
|
@property
|
||||||
|
def loop(self) -> aio.AbstractEventLoop:
|
||||||
|
"""A shortcut for :attr:`.interface.loop`."""
|
||||||
|
return self.serf.loop
|
||||||
|
|
||||||
|
async def run(self, **kwargs):
|
||||||
|
raise NotImplementedError()
|
|
@ -1,15 +1,12 @@
|
||||||
from typing import *
|
from typing import *
|
||||||
from .commandinterface import CommandInterface
|
|
||||||
from .commanddata import CommandData
|
from .commanddata import CommandData
|
||||||
|
|
||||||
|
|
||||||
class KeyboardKey:
|
class KeyboardKey:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
interface: CommandInterface,
|
|
||||||
short: str,
|
short: str,
|
||||||
text: str,
|
text: str,
|
||||||
callback: Callable[[CommandData], Awaitable[None]]):
|
callback: Callable[[CommandData], Awaitable[None]]):
|
||||||
self.interface: CommandInterface = interface
|
|
||||||
self.short: str = short
|
self.short: str = short
|
||||||
self.text: str = text
|
self.text: str = text
|
||||||
self.callback: Callable[[CommandData], Awaitable[None]] = callback
|
self.callback: Callable[[CommandData], Awaitable[None]] = callback
|
||||||
|
|
|
@ -92,10 +92,7 @@ class Constellation:
|
||||||
self.herald_task: Optional[aio.Task] = None
|
self.herald_task: Optional[aio.Task] = None
|
||||||
"""A reference to the :class:`aio.Task` that runs the :class:`rh.Link`."""
|
"""A reference to the :class:`aio.Task` that runs the :class:`rh.Link`."""
|
||||||
|
|
||||||
self.Interface: Type[rc.CommandInterface] = self.interface_factory()
|
self.events: Dict[str, rc.HeraldEvent] = {}
|
||||||
"""The :class:`~rc.CommandInterface` class of this :class:`Constellation`."""
|
|
||||||
|
|
||||||
self.events: Dict[str, rc.Event] = {}
|
|
||||||
"""A dictionary containing all :class:`~rc.Event` that can be handled by this :class:`Constellation`."""
|
"""A dictionary containing all :class:`~rc.Event` that can be handled by this :class:`Constellation`."""
|
||||||
|
|
||||||
self.starlette = starlette.applications.Starlette(debug=__debug__)
|
self.starlette = starlette.applications.Starlette(debug=__debug__)
|
||||||
|
@ -149,66 +146,55 @@ class Constellation:
|
||||||
|
|
||||||
Because of how :mod:`uvicorn` runs, it will stay :const:`None` until the first page is requested."""
|
Because of how :mod:`uvicorn` runs, it will stay :const:`None` until the first page is requested."""
|
||||||
|
|
||||||
# TODO: is this a good idea?
|
|
||||||
def interface_factory(self) -> Type[rc.CommandInterface]:
|
|
||||||
"""Create the :class:`rc.CommandInterface` class for the :class:`Constellation`."""
|
|
||||||
|
|
||||||
# noinspection PyMethodParameters
|
|
||||||
class GenericInterface(rc.CommandInterface):
|
|
||||||
alchemy: ra.Alchemy = self.alchemy
|
|
||||||
constellation = self
|
|
||||||
|
|
||||||
async def call_herald_event(ci, destination: str, event_name: str, **kwargs) -> Dict:
|
|
||||||
"""Send a :class:`royalherald.Request` to a specific destination, and wait for a
|
|
||||||
:class:`royalherald.Response`."""
|
|
||||||
if self.herald is None:
|
|
||||||
raise rc.UnsupportedError("`royalherald` is not enabled on this serf.")
|
|
||||||
request: rh.Request = rh.Request(handler=event_name, data=kwargs)
|
|
||||||
response: rh.Response = await self.herald.request(destination=destination, request=request)
|
|
||||||
if isinstance(response, rh.ResponseFailure):
|
|
||||||
if response.name == "no_event":
|
|
||||||
raise rc.ProgramError(f"There is no event named {event_name} in {destination}.")
|
|
||||||
elif response.name == "error_in_event":
|
|
||||||
if response.extra_info["type"] == "CommandError":
|
|
||||||
raise rc.CommandError(response.extra_info["message"])
|
|
||||||
elif response.extra_info["type"] == "UserError":
|
|
||||||
raise rc.UserError(response.extra_info["message"])
|
|
||||||
elif response.extra_info["type"] == "InvalidInputError":
|
|
||||||
raise rc.InvalidInputError(response.extra_info["message"])
|
|
||||||
elif response.extra_info["type"] == "UnsupportedError":
|
|
||||||
raise rc.UnsupportedError(response.extra_info["message"])
|
|
||||||
elif response.extra_info["type"] == "ConfigurationError":
|
|
||||||
raise rc.ConfigurationError(response.extra_info["message"])
|
|
||||||
elif response.extra_info["type"] == "ExternalError":
|
|
||||||
raise rc.ExternalError(response.extra_info["message"])
|
|
||||||
else:
|
|
||||||
raise rc.ProgramError(f"Invalid error in Herald event '{event_name}':\n"
|
|
||||||
f"[b]{response.extra_info['type']}[/b]\n"
|
|
||||||
f"{response.extra_info['message']}")
|
|
||||||
elif response.name == "unhandled_exception_in_event":
|
|
||||||
raise rc.ProgramError(f"Unhandled exception in Herald event '{event_name}':\n"
|
|
||||||
f"[b]{response.extra_info['type']}[/b]\n"
|
|
||||||
f"{response.extra_info['message']}")
|
|
||||||
else:
|
|
||||||
raise rc.ProgramError(f"Unknown response in Herald event '{event_name}':\n"
|
|
||||||
f"[b]{response.name}[/b]"
|
|
||||||
f"[p]{response}[/p]")
|
|
||||||
elif isinstance(response, rh.ResponseSuccess):
|
|
||||||
return response.data
|
|
||||||
else:
|
|
||||||
raise rc.ProgramError(f"Other Herald Link returned unknown response:\n"
|
|
||||||
f"[p]{response}[/p]")
|
|
||||||
|
|
||||||
return GenericInterface
|
|
||||||
|
|
||||||
def init_herald(self, herald_cfg: Dict[str, Any]):
|
def init_herald(self, herald_cfg: Dict[str, Any]):
|
||||||
"""Create a :class:`rh.Link`."""
|
"""Create a :class:`rh.Link`."""
|
||||||
herald_cfg["name"] = "constellation"
|
herald_cfg["name"] = "constellation"
|
||||||
self.herald: rh.Link = rh.Link(rh.Config.from_config(**herald_cfg), self.network_handler)
|
self.herald: rh.Link = rh.Link(rh.Config.from_config(**herald_cfg), self.network_handler)
|
||||||
|
|
||||||
|
async def call_herald_event(self, destination: str, event_name: str, **kwargs) -> Dict:
|
||||||
|
"""Send a :class:`royalherald.Request` to a specific destination, and wait for a
|
||||||
|
:class:`royalherald.Response`."""
|
||||||
|
if self.herald is None:
|
||||||
|
raise rc.UnsupportedError("`royalherald` is not enabled on this serf.")
|
||||||
|
request: rh.Request = rh.Request(handler=event_name, data=kwargs)
|
||||||
|
response: rh.Response = await self.herald.request(destination=destination, request=request)
|
||||||
|
if isinstance(response, rh.ResponseFailure):
|
||||||
|
if response.name == "no_event":
|
||||||
|
raise rc.ProgramError(f"There is no event named {event_name} in {destination}.")
|
||||||
|
elif response.name == "error_in_event":
|
||||||
|
if response.extra_info["type"] == "CommandError":
|
||||||
|
raise rc.CommandError(response.extra_info["message"])
|
||||||
|
elif response.extra_info["type"] == "UserError":
|
||||||
|
raise rc.UserError(response.extra_info["message"])
|
||||||
|
elif response.extra_info["type"] == "InvalidInputError":
|
||||||
|
raise rc.InvalidInputError(response.extra_info["message"])
|
||||||
|
elif response.extra_info["type"] == "UnsupportedError":
|
||||||
|
raise rc.UnsupportedError(response.extra_info["message"])
|
||||||
|
elif response.extra_info["type"] == "ConfigurationError":
|
||||||
|
raise rc.ConfigurationError(response.extra_info["message"])
|
||||||
|
elif response.extra_info["type"] == "ExternalError":
|
||||||
|
raise rc.ExternalError(response.extra_info["message"])
|
||||||
|
else:
|
||||||
|
raise rc.ProgramError(f"Invalid error in Herald event '{event_name}':\n"
|
||||||
|
f"[b]{response.extra_info['type']}[/b]\n"
|
||||||
|
f"{response.extra_info['message']}")
|
||||||
|
elif response.name == "unhandled_exception_in_event":
|
||||||
|
raise rc.ProgramError(f"Unhandled exception in Herald event '{event_name}':\n"
|
||||||
|
f"[b]{response.extra_info['type']}[/b]\n"
|
||||||
|
f"{response.extra_info['message']}")
|
||||||
|
else:
|
||||||
|
raise rc.ProgramError(f"Unknown response in Herald event '{event_name}':\n"
|
||||||
|
f"[b]{response.name}[/b]"
|
||||||
|
f"[p]{response}[/p]")
|
||||||
|
elif isinstance(response, rh.ResponseSuccess):
|
||||||
|
return response.data
|
||||||
|
else:
|
||||||
|
raise rc.ProgramError(f"Other Herald Link returned unknown response:\n"
|
||||||
|
f"[p]{response}[/p]")
|
||||||
|
|
||||||
async def network_handler(self, message: Union[rh.Request, rh.Broadcast]) -> rh.Response:
|
async def network_handler(self, message: Union[rh.Request, rh.Broadcast]) -> rh.Response:
|
||||||
try:
|
try:
|
||||||
event: rc.Event = self.events[message.handler]
|
event: rc.HeraldEvent = self.events[message.handler]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
log.warning(f"No event for '{message.handler}'")
|
log.warning(f"No event for '{message.handler}'")
|
||||||
return rh.ResponseFailure("no_event", f"This serf does not have any event for {message.handler}.")
|
return rh.ResponseFailure("no_event", f"This serf does not have any event for {message.handler}.")
|
||||||
|
@ -228,13 +214,11 @@ class Constellation:
|
||||||
elif isinstance(message, rh.Broadcast):
|
elif isinstance(message, rh.Broadcast):
|
||||||
await event.run(**message.data)
|
await event.run(**message.data)
|
||||||
|
|
||||||
def register_events(self, events: List[Type[rc.Event]], pack_cfg: Dict[str, Any]):
|
def register_events(self, events: List[Type[rc.HeraldEvent]], pack_cfg: Dict[str, Any]):
|
||||||
for SelectedEvent in events:
|
for SelectedEvent in events:
|
||||||
# Create a new interface
|
|
||||||
interface = self.Interface(config=pack_cfg)
|
|
||||||
# Initialize the event
|
# Initialize the event
|
||||||
try:
|
try:
|
||||||
event = SelectedEvent(interface)
|
event = SelectedEvent(serf=self, config=pack_cfg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Skipping: "
|
log.error(f"Skipping: "
|
||||||
f"{SelectedEvent.__qualname__} - {e.__class__.__qualname__} in the initialization.")
|
f"{SelectedEvent.__qualname__} - {e.__class__.__qualname__} in the initialization.")
|
||||||
|
@ -266,7 +250,7 @@ class Constellation:
|
||||||
for SelectedPageStar in page_stars:
|
for SelectedPageStar in page_stars:
|
||||||
log.debug(f"Registering: {SelectedPageStar.path} -> {SelectedPageStar.__qualname__}")
|
log.debug(f"Registering: {SelectedPageStar.path} -> {SelectedPageStar.__qualname__}")
|
||||||
try:
|
try:
|
||||||
page_star_instance = SelectedPageStar(interface=self.Interface(pack_cfg))
|
page_star_instance = SelectedPageStar(constellation=self, config=pack_cfg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Skipping: "
|
log.error(f"Skipping: "
|
||||||
f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.")
|
f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.")
|
||||||
|
@ -277,7 +261,6 @@ class Constellation:
|
||||||
|
|
||||||
def run_blocking(self):
|
def run_blocking(self):
|
||||||
log.info(f"Running Constellation on https://{self.address}:{self.port}/...")
|
log.info(f"Running Constellation on https://{self.address}:{self.port}/...")
|
||||||
loop: aio.AbstractEventLoop = aio.get_event_loop()
|
|
||||||
self.running = True
|
self.running = True
|
||||||
try:
|
try:
|
||||||
uvicorn.run(self.starlette, host=self.address, port=self.port, log_config=UVICORN_LOGGING_CONFIG)
|
uvicorn.run(self.starlette, host=self.address, port=self.port, log_config=UVICORN_LOGGING_CONFIG)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from typing import *
|
from typing import *
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
from starlette.responses import Response
|
from starlette.responses import Response
|
||||||
from royalnet.commands import CommandInterface
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .constellation import Constellation
|
from .constellation import Constellation
|
||||||
|
@ -11,8 +10,9 @@ class Star:
|
||||||
"""A Star is a class representing a part of the website.
|
"""A Star is a class representing a part of the website.
|
||||||
|
|
||||||
It shouldn't be used directly: please use :class:`PageStar` and :class:`ExceptionStar` instead!"""
|
It shouldn't be used directly: please use :class:`PageStar` and :class:`ExceptionStar` instead!"""
|
||||||
def __init__(self, interface: CommandInterface):
|
def __init__(self, constellation: "Constellation", config):
|
||||||
self.interface: CommandInterface = interface
|
self.constellation: "Constellation" = constellation
|
||||||
|
self.config = config
|
||||||
|
|
||||||
async def page(self, request: Request) -> Response:
|
async def page(self, request: Request) -> Response:
|
||||||
"""The function generating the :class:`~starlette.Response` to a web :class:`~starlette.Request`.
|
"""The function generating the :class:`~starlette.Response` to a web :class:`~starlette.Request`.
|
||||||
|
@ -20,31 +20,21 @@ class Star:
|
||||||
If it raises an error, the corresponding :class:`ExceptionStar` will be used to handle the request instead."""
|
If it raises an error, the corresponding :class:`ExceptionStar` will be used to handle the request instead."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
|
||||||
def constellation(self) -> "Constellation":
|
|
||||||
"""A shortcut for the :class:`Constellation`."""
|
|
||||||
return self.interface.constellation
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def alchemy(self):
|
def alchemy(self):
|
||||||
"""A shortcut for the :class:`~royalnet.alchemy.Alchemy` of the :class:`Constellation`."""
|
"""A shortcut for the :class:`~royalnet.alchemy.Alchemy` of the :class:`Constellation`."""
|
||||||
return self.interface.constellation.alchemy
|
return self.constellation.alchemy
|
||||||
|
|
||||||
# noinspection PyPep8Naming
|
# noinspection PyPep8Naming
|
||||||
@property
|
@property
|
||||||
def Session(self):
|
def Session(self):
|
||||||
"""A shortcut for the :class:`~royalnet.alchemy.Alchemy` :class:`Session` of the :class:`Constellation`."""
|
"""A shortcut for the :class:`~royalnet.alchemy.Alchemy` :class:`Session` of the :class:`Constellation`."""
|
||||||
return self.interface.constellation.alchemy.Session
|
return self.constellation.alchemy.Session
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def session_acm(self):
|
def session_acm(self):
|
||||||
"""A shortcut for :func:`.alchemy.session_acm` of the :class:`Constellation`."""
|
"""A shortcut for :func:`.alchemy.session_acm` of the :class:`Constellation`."""
|
||||||
return self.interface.constellation.alchemy.session_acm
|
return self.constellation.alchemy.session_acm
|
||||||
|
|
||||||
@property
|
|
||||||
def config(self) -> Dict[str, Any]:
|
|
||||||
"""A shortcut for the Pack configuration of the :class:`Constellation`."""
|
|
||||||
return self.interface.config
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__qualname__}>"
|
return f"<{self.__class__.__qualname__}>"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import asyncio as aio
|
import asyncio as aio
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
|
||||||
import io
|
import io
|
||||||
import sys
|
import sys
|
||||||
from typing import *
|
from typing import *
|
||||||
|
@ -19,6 +18,7 @@ log = logging.getLogger(__name__)
|
||||||
class DiscordSerf(Serf):
|
class DiscordSerf(Serf):
|
||||||
"""A :class:`Serf` that connects to `Discord <https://discordapp.com/>`_ as a bot."""
|
"""A :class:`Serf` that connects to `Discord <https://discordapp.com/>`_ as a bot."""
|
||||||
interface_name = "discord"
|
interface_name = "discord"
|
||||||
|
prefix = "!"
|
||||||
|
|
||||||
_identity_table = rbt.Discord
|
_identity_table = rbt.Discord
|
||||||
_identity_column = "discord_id"
|
_identity_column = "discord_id"
|
||||||
|
@ -55,26 +55,14 @@ class DiscordSerf(Serf):
|
||||||
|
|
||||||
self.Data: Type[rc.CommandData] = self.data_factory()
|
self.Data: Type[rc.CommandData] = self.data_factory()
|
||||||
|
|
||||||
def interface_factory(self) -> Type[rc.CommandInterface]:
|
|
||||||
# noinspection PyPep8Naming
|
|
||||||
GenericInterface = super().interface_factory()
|
|
||||||
|
|
||||||
# noinspection PyMethodParameters,PyAbstractClass
|
|
||||||
class DiscordInterface(GenericInterface):
|
|
||||||
name = self.interface_name
|
|
||||||
prefix = "!"
|
|
||||||
|
|
||||||
return DiscordInterface
|
|
||||||
|
|
||||||
def data_factory(self) -> Type[rc.CommandData]:
|
def data_factory(self) -> Type[rc.CommandData]:
|
||||||
# noinspection PyMethodParameters,PyAbstractClass
|
# noinspection PyMethodParameters,PyAbstractClass
|
||||||
class DiscordData(rc.CommandData):
|
class DiscordData(rc.CommandData):
|
||||||
def __init__(data,
|
def __init__(data,
|
||||||
interface: rc.CommandInterface,
|
command: rc.Command,
|
||||||
loop: aio.AbstractEventLoop,
|
|
||||||
message: "discord.Message"):
|
message: "discord.Message"):
|
||||||
super().__init__(interface=interface, loop=loop)
|
super().__init__(command=command)
|
||||||
data.message = message
|
data.message: "discord.Message" = message
|
||||||
|
|
||||||
async def reply(data, text: str):
|
async def reply(data, text: str):
|
||||||
await data.message.channel.send(escape(text))
|
await data.message.channel.send(escape(text))
|
||||||
|
@ -130,7 +118,8 @@ class DiscordSerf(Serf):
|
||||||
else:
|
else:
|
||||||
session = None
|
session = None
|
||||||
# Prepare data
|
# Prepare data
|
||||||
data = self.Data(interface=command.interface, loop=self.loop, message=message)
|
# noinspection PyArgumentList
|
||||||
|
data = self.Data(command=command, message=message)
|
||||||
# Call the command
|
# Call the command
|
||||||
await self.call(command, data, parameters)
|
await self.call(command, data, parameters)
|
||||||
# Close the alchemy session
|
# Close the alchemy session
|
||||||
|
@ -141,6 +130,7 @@ class DiscordSerf(Serf):
|
||||||
"""Create a custom class inheriting from :py:class:`discord.Client`."""
|
"""Create a custom class inheriting from :py:class:`discord.Client`."""
|
||||||
# noinspection PyMethodParameters
|
# noinspection PyMethodParameters
|
||||||
class DiscordClient(discord.Client):
|
class DiscordClient(discord.Client):
|
||||||
|
# noinspection PyMethodMayBeStatic
|
||||||
async def on_message(cli, message: "discord.Message") -> None:
|
async def on_message(cli, message: "discord.Message") -> None:
|
||||||
"""Handle messages received by passing them to the handle_message method of the bot."""
|
"""Handle messages received by passing them to the handle_message method of the bot."""
|
||||||
# TODO: keep reference to these tasks somewhere
|
# TODO: keep reference to these tasks somewhere
|
||||||
|
@ -151,6 +141,7 @@ class DiscordSerf(Serf):
|
||||||
log.debug("Discord client is ready!")
|
log.debug("Discord client is ready!")
|
||||||
await cli.change_presence(status=discord.Status.online, activity=None)
|
await cli.change_presence(status=discord.Status.online, activity=None)
|
||||||
|
|
||||||
|
# noinspection PyMethodMayBeStatic
|
||||||
async def on_resume(cli) -> None:
|
async def on_resume(cli) -> None:
|
||||||
log.debug("Discord client resumed connection.")
|
log.debug("Discord client resumed connection.")
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ log = logging.getLogger(__name__)
|
||||||
class MatrixSerf(Serf):
|
class MatrixSerf(Serf):
|
||||||
"""A serf that connects to `Matrix <https://matrix.org/>`_ as an user."""
|
"""A serf that connects to `Matrix <https://matrix.org/>`_ as an user."""
|
||||||
interface_name = "matrix"
|
interface_name = "matrix"
|
||||||
|
prefix = "!"
|
||||||
|
|
||||||
_identity_table = rb.tables.Matrix
|
_identity_table = rb.tables.Matrix
|
||||||
_identity_column = "matrix_id"
|
_identity_column = "matrix_id"
|
||||||
|
@ -47,26 +48,14 @@ class MatrixSerf(Serf):
|
||||||
|
|
||||||
self.Data: Type[rc.CommandData] = self.data_factory()
|
self.Data: Type[rc.CommandData] = self.data_factory()
|
||||||
|
|
||||||
def interface_factory(self) -> Type[rc.CommandInterface]:
|
|
||||||
# noinspection PyPep8Naming
|
|
||||||
GenericInterface = super().interface_factory()
|
|
||||||
|
|
||||||
# noinspection PyMethodParameters,PyAbstractClass
|
|
||||||
class DiscordInterface(GenericInterface):
|
|
||||||
name = self.interface_name
|
|
||||||
prefix = "!"
|
|
||||||
|
|
||||||
return DiscordInterface
|
|
||||||
|
|
||||||
def data_factory(self) -> Type[rc.CommandData]:
|
def data_factory(self) -> Type[rc.CommandData]:
|
||||||
# noinspection PyMethodParameters,PyAbstractClass
|
# noinspection PyMethodParameters,PyAbstractClass
|
||||||
class MatrixData(rc.CommandData):
|
class MatrixData(rc.CommandData):
|
||||||
def __init__(data,
|
def __init__(data,
|
||||||
interface: rc.CommandInterface,
|
command: rc.Command,
|
||||||
loop: aio.AbstractEventLoop,
|
|
||||||
room: nio.MatrixRoom,
|
room: nio.MatrixRoom,
|
||||||
event: nio.Event):
|
event: nio.Event):
|
||||||
super().__init__(interface=interface, loop=loop)
|
super().__init__(command=command)
|
||||||
data.room: nio.MatrixRoom = room
|
data.room: nio.MatrixRoom = room
|
||||||
data.event: nio.Event = event
|
data.event: nio.Event = event
|
||||||
|
|
||||||
|
@ -118,7 +107,8 @@ class MatrixSerf(Serf):
|
||||||
else:
|
else:
|
||||||
session = None
|
session = None
|
||||||
# Prepare data
|
# Prepare data
|
||||||
data = self.Data(interface=command.interface, loop=self.loop, room=room, event=event)
|
# noinspection PyArgumentList
|
||||||
|
data = self.Data(command=command, room=room, event=event)
|
||||||
# Call the command
|
# Call the command
|
||||||
await self.call(command, data, parameters)
|
await self.call(command, data, parameters)
|
||||||
# Close the alchemy session
|
# Close the alchemy session
|
||||||
|
|
|
@ -4,7 +4,7 @@ import asyncio as aio
|
||||||
import sys
|
import sys
|
||||||
from typing import *
|
from typing import *
|
||||||
from sqlalchemy.schema import Table
|
from sqlalchemy.schema import Table
|
||||||
from royalnet.commands import *
|
import royalnet.commands as rc
|
||||||
import royalnet.utils as ru
|
import royalnet.utils as ru
|
||||||
import royalnet.alchemy as ra
|
import royalnet.alchemy as ra
|
||||||
import royalnet.backpack.tables as rbt
|
import royalnet.backpack.tables as rbt
|
||||||
|
@ -12,7 +12,6 @@ import royalnet.herald as rh
|
||||||
import traceback
|
import traceback
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,6 +19,7 @@ class Serf(abc.ABC):
|
||||||
"""An abstract class, to be used as base to implement Royalnet bots on multiple interfaces (such as Telegram or
|
"""An abstract class, to be used as base to implement Royalnet bots on multiple interfaces (such as Telegram or
|
||||||
Discord)."""
|
Discord)."""
|
||||||
interface_name = NotImplemented
|
interface_name = NotImplemented
|
||||||
|
prefix = NotImplemented
|
||||||
|
|
||||||
_master_table: type = rbt.User
|
_master_table: type = rbt.User
|
||||||
_identity_table: type = NotImplemented
|
_identity_table: type = NotImplemented
|
||||||
|
@ -41,10 +41,10 @@ class Serf(abc.ABC):
|
||||||
log.debug(f"Importing pack: {pack_name}")
|
log.debug(f"Importing pack: {pack_name}")
|
||||||
try:
|
try:
|
||||||
packs[pack_name] = {
|
packs[pack_name] = {
|
||||||
"commands": importlib.import_module(f"{pack_name}.commands"),
|
"commands": importlib.import_module(f".commands", pack_name),
|
||||||
"events": importlib.import_module(f"{pack_name}.events"),
|
"events": importlib.import_module(f".events", pack_name),
|
||||||
"stars": importlib.import_module(f"{pack_name}.stars"),
|
"stars": importlib.import_module(f".stars", pack_name),
|
||||||
"tables": importlib.import_module(f"{pack_name}.tables"),
|
"tables": importlib.import_module(f".tables", pack_name),
|
||||||
}
|
}
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
log.error(f"{e.__class__.__name__} during the import of {pack_name}:\n"
|
log.error(f"{e.__class__.__name__} during the import of {pack_name}:\n"
|
||||||
|
@ -88,13 +88,10 @@ class Serf(abc.ABC):
|
||||||
self.herald_task: Optional[aio.Task] = None
|
self.herald_task: Optional[aio.Task] = None
|
||||||
"""A reference to the :class:`asyncio.Task` that runs the :class:`Link`."""
|
"""A reference to the :class:`asyncio.Task` that runs the :class:`Link`."""
|
||||||
|
|
||||||
self.events: Dict[str, Event] = {}
|
self.events: Dict[str, rc.HeraldEvent] = {}
|
||||||
"""A dictionary containing all :class:`Event` that can be handled by this :class:`Serf`."""
|
"""A dictionary containing all :class:`Event` that can be handled by this :class:`Serf`."""
|
||||||
|
|
||||||
self.Interface: Type[CommandInterface] = self.interface_factory()
|
self.commands: Dict[str, rc.Command] = {}
|
||||||
"""The :class:`CommandInterface` class of this Serf."""
|
|
||||||
|
|
||||||
self.commands: Dict[str, Command] = {}
|
|
||||||
"""The :class:`dict` connecting each command name to its :class:`Command` object."""
|
"""The :class:`dict` connecting each command name to its :class:`Command` object."""
|
||||||
|
|
||||||
for pack_name in packs:
|
for pack_name in packs:
|
||||||
|
@ -138,104 +135,86 @@ class Serf(abc.ABC):
|
||||||
"""Find a relationship path starting from the master table and ending at the identity table, and return it."""
|
"""Find a relationship path starting from the master table and ending at the identity table, and return it."""
|
||||||
return ra.table_dfs(self.master_table, self.identity_table)
|
return ra.table_dfs(self.master_table, self.identity_table)
|
||||||
|
|
||||||
def interface_factory(self) -> Type[CommandInterface]:
|
async def call_herald_event(self, destination: str, event_name: str, **kwargs) -> Dict:
|
||||||
"""Create the :class:`CommandInterface` class for the Serf."""
|
"""Send a :class:`royalherald.Request` to a specific destination, and wait for a
|
||||||
|
:class:`royalherald.Response`."""
|
||||||
# noinspection PyMethodParameters
|
if self.herald is None:
|
||||||
class GenericInterface(CommandInterface):
|
raise rc.UnsupportedError("`royalherald` is not enabled on this serf.")
|
||||||
alchemy: ra.Alchemy = self.alchemy
|
request: rh.Request = rh.Request(handler=event_name, data=kwargs)
|
||||||
serf: "Serf" = self
|
response: rh.Response = await self.herald.request(destination=destination, request=request)
|
||||||
|
if isinstance(response, rh.ResponseFailure):
|
||||||
async def call_herald_event(ci, destination: str, event_name: str, **kwargs) -> Dict:
|
if response.name == "no_event":
|
||||||
"""Send a :class:`royalherald.Request` to a specific destination, and wait for a
|
raise rc.ProgramError(f"There is no event named {event_name} in {destination}.")
|
||||||
:class:`royalherald.Response`."""
|
elif response.name == "error_in_event":
|
||||||
if self.herald is None:
|
if response.extra_info["type"] == "CommandError":
|
||||||
raise UnsupportedError("`royalherald` is not enabled on this serf.")
|
raise rc.CommandError(response.extra_info["message"])
|
||||||
request: rh.Request = rh.Request(handler=event_name, data=kwargs)
|
elif response.extra_info["type"] == "UserError":
|
||||||
response: rh.Response = await self.herald.request(destination=destination, request=request)
|
raise rc.UserError(response.extra_info["message"])
|
||||||
if isinstance(response, rh.ResponseFailure):
|
elif response.extra_info["type"] == "InvalidInputError":
|
||||||
if response.name == "no_event":
|
raise rc.InvalidInputError(response.extra_info["message"])
|
||||||
raise ProgramError(f"There is no event named {event_name} in {destination}.")
|
elif response.extra_info["type"] == "UnsupportedError":
|
||||||
elif response.name == "error_in_event":
|
raise rc.UnsupportedError(response.extra_info["message"])
|
||||||
if response.extra_info["type"] == "CommandError":
|
elif response.extra_info["type"] == "ConfigurationError":
|
||||||
raise CommandError(response.extra_info["message"])
|
raise rc.ConfigurationError(response.extra_info["message"])
|
||||||
elif response.extra_info["type"] == "UserError":
|
elif response.extra_info["type"] == "ExternalError":
|
||||||
raise UserError(response.extra_info["message"])
|
raise rc.ExternalError(response.extra_info["message"])
|
||||||
elif response.extra_info["type"] == "InvalidInputError":
|
|
||||||
raise InvalidInputError(response.extra_info["message"])
|
|
||||||
elif response.extra_info["type"] == "UnsupportedError":
|
|
||||||
raise UnsupportedError(response.extra_info["message"])
|
|
||||||
elif response.extra_info["type"] == "ConfigurationError":
|
|
||||||
raise ConfigurationError(response.extra_info["message"])
|
|
||||||
elif response.extra_info["type"] == "ExternalError":
|
|
||||||
raise ExternalError(response.extra_info["message"])
|
|
||||||
else:
|
|
||||||
raise ProgramError(f"Invalid error in Herald event '{event_name}':\n"
|
|
||||||
f"[b]{response.extra_info['type']}[/b]\n"
|
|
||||||
f"{response.extra_info['message']}")
|
|
||||||
elif response.name == "unhandled_exception_in_event":
|
|
||||||
raise ProgramError(f"Unhandled exception in Herald event '{event_name}':\n"
|
|
||||||
f"[b]{response.extra_info['type']}[/b]\n"
|
|
||||||
f"{response.extra_info['message']}")
|
|
||||||
else:
|
|
||||||
raise ProgramError(f"Unknown response in Herald event '{event_name}':\n"
|
|
||||||
f"[b]{response.name}[/b]"
|
|
||||||
f"[p]{response}[/p]")
|
|
||||||
elif isinstance(response, rh.ResponseSuccess):
|
|
||||||
return response.data
|
|
||||||
else:
|
else:
|
||||||
raise ProgramError(f"Other Herald Link returned unknown response:\n"
|
raise rc.ProgramError(f"Invalid error in Herald event '{event_name}':\n"
|
||||||
f"[p]{response}[/p]")
|
f"[b]{response.extra_info['type']}[/b]\n"
|
||||||
|
f"{response.extra_info['message']}")
|
||||||
|
elif response.name == "unhandled_exception_in_event":
|
||||||
|
raise rc.ProgramError(f"Unhandled exception in Herald event '{event_name}':\n"
|
||||||
|
f"[b]{response.extra_info['type']}[/b]\n"
|
||||||
|
f"{response.extra_info['message']}")
|
||||||
|
else:
|
||||||
|
raise rc.ProgramError(f"Unknown response in Herald event '{event_name}':\n"
|
||||||
|
f"[b]{response.name}[/b]"
|
||||||
|
f"[p]{response}[/p]")
|
||||||
|
elif isinstance(response, rh.ResponseSuccess):
|
||||||
|
return response.data
|
||||||
|
else:
|
||||||
|
raise rc.ProgramError(f"Other Herald Link returned unknown response:\n"
|
||||||
|
f"[p]{response}[/p]")
|
||||||
|
|
||||||
return GenericInterface
|
def register_commands(self, commands: List[Type[rc.Command]], pack_cfg: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
def register_commands(self, commands: List[Type[Command]], pack_cfg: Dict[str, Any]) -> None:
|
|
||||||
"""Initialize and register all commands passed as argument."""
|
"""Initialize and register all commands passed as argument."""
|
||||||
# Instantiate the Commands
|
# Instantiate the Commands
|
||||||
for SelectedCommand in commands:
|
for SelectedCommand in commands:
|
||||||
# Create a new interface
|
|
||||||
interface = self.Interface(config=pack_cfg)
|
|
||||||
# Try to instantiate the command
|
# Try to instantiate the command
|
||||||
try:
|
try:
|
||||||
command = SelectedCommand(interface)
|
command = SelectedCommand(serf=self, config=pack_cfg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Skipping: "
|
log.error(f"Skipping: "
|
||||||
f"{SelectedCommand.__qualname__} - {e.__class__.__qualname__} in the initialization.")
|
f"{SelectedCommand.__qualname__} - {e.__class__.__qualname__} in the initialization.")
|
||||||
ru.sentry_exc(e)
|
ru.sentry_exc(e)
|
||||||
continue
|
continue
|
||||||
# Link the interface to the command
|
|
||||||
interface.command = command
|
|
||||||
# Warn if the command would be overriding something
|
# Warn if the command would be overriding something
|
||||||
if f"{self.Interface.prefix}{SelectedCommand.name}" in self.commands:
|
if SelectedCommand.name in self.commands:
|
||||||
log.info(f"Overriding (already defined): "
|
log.info(f"Overriding (already defined): "
|
||||||
f"{SelectedCommand.__qualname__} -> {self.Interface.prefix}{SelectedCommand.name}")
|
f"{SelectedCommand.__qualname__} -> {SelectedCommand.name}")
|
||||||
else:
|
else:
|
||||||
log.debug(f"Registering: "
|
log.debug(f"Registering: "
|
||||||
f"{SelectedCommand.__qualname__} -> {self.Interface.prefix}{SelectedCommand.name}")
|
f"{SelectedCommand.__qualname__} -> {SelectedCommand.name}")
|
||||||
# Register the command in the commands dict
|
# Register the command in the commands dict
|
||||||
self.commands[f"{interface.prefix}{SelectedCommand.name}"] = command
|
self.commands[SelectedCommand.name] = command
|
||||||
# Register aliases, but don't override anything
|
# Register aliases, but don't override anything
|
||||||
for alias in SelectedCommand.aliases:
|
for alias in SelectedCommand.aliases:
|
||||||
if f"{interface.prefix}{alias}" not in self.commands:
|
if alias not in self.commands:
|
||||||
log.debug(f"Aliasing: {SelectedCommand.__qualname__} -> {interface.prefix}{alias}")
|
log.debug(f"Aliasing: {SelectedCommand.__qualname__} -> {alias}")
|
||||||
self.commands[f"{interface.prefix}{alias}"] = \
|
self.commands[alias] = self.commands[SelectedCommand.name]
|
||||||
self.commands[f"{interface.prefix}{SelectedCommand.name}"]
|
|
||||||
else:
|
else:
|
||||||
log.warning(
|
log.warning(f"Ignoring (already defined): {SelectedCommand.__qualname__} -> {alias}")
|
||||||
f"Ignoring (already defined): {SelectedCommand.__qualname__} -> {interface.prefix}{alias}")
|
|
||||||
|
|
||||||
def init_herald(self, herald_cfg: Dict[str, Any]):
|
def init_herald(self, herald_cfg: Dict[str, Any]):
|
||||||
"""Create a :class:`Link` and bind :class:`Event`."""
|
"""Create a :class:`Link` and bind :class:`Event`."""
|
||||||
herald_cfg["name"] = self.interface_name
|
herald_cfg["name"] = self.interface_name
|
||||||
self.herald: rh.Link = rh.Link(rh.Config.from_config(**herald_cfg), self.network_handler)
|
self.herald: rh.Link = rh.Link(rh.Config.from_config(**herald_cfg), self.network_handler)
|
||||||
|
|
||||||
def register_events(self, events: List[Type[Event]], pack_cfg: Dict[str, Any]):
|
def register_events(self, events: List[Type[rc.HeraldEvent]], pack_cfg: Dict[str, Any]):
|
||||||
for SelectedEvent in events:
|
for SelectedEvent in events:
|
||||||
# Create a new interface
|
|
||||||
interface = self.Interface(config=pack_cfg)
|
|
||||||
# Initialize the event
|
# Initialize the event
|
||||||
try:
|
try:
|
||||||
event = SelectedEvent(interface)
|
event = SelectedEvent(serf=self, config=pack_cfg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Skipping: "
|
log.error(f"Skipping: "
|
||||||
f"{SelectedEvent.__qualname__} - {e.__class__.__qualname__} in the initialization.")
|
f"{SelectedEvent.__qualname__} - {e.__class__.__qualname__} in the initialization.")
|
||||||
|
@ -250,7 +229,7 @@ class Serf(abc.ABC):
|
||||||
|
|
||||||
async def network_handler(self, message: Union[rh.Request, rh.Broadcast]) -> rh.Response:
|
async def network_handler(self, message: Union[rh.Request, rh.Broadcast]) -> rh.Response:
|
||||||
try:
|
try:
|
||||||
event: Event = self.events[message.handler]
|
event: rc.HeraldEvent = self.events[message.handler]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
log.warning(f"No event for '{message.handler}'")
|
log.warning(f"No event for '{message.handler}'")
|
||||||
return rh.ResponseFailure("no_event", f"This serf does not have any event for {message.handler}.")
|
return rh.ResponseFailure("no_event", f"This serf does not have any event for {message.handler}.")
|
||||||
|
@ -259,7 +238,7 @@ class Serf(abc.ABC):
|
||||||
try:
|
try:
|
||||||
response_data = await event.run(**message.data)
|
response_data = await event.run(**message.data)
|
||||||
return rh.ResponseSuccess(data=response_data)
|
return rh.ResponseSuccess(data=response_data)
|
||||||
except CommandError as e:
|
except rc.CommandError as e:
|
||||||
return rh.ResponseFailure("error_in_event",
|
return rh.ResponseFailure("error_in_event",
|
||||||
f"The event '{message.handler}' raised a {e.__class__.__qualname__}.",
|
f"The event '{message.handler}' raised a {e.__class__.__qualname__}.",
|
||||||
extra_info={
|
extra_info={
|
||||||
|
@ -278,25 +257,25 @@ class Serf(abc.ABC):
|
||||||
elif isinstance(message, rh.Broadcast):
|
elif isinstance(message, rh.Broadcast):
|
||||||
await event.run(**message.data)
|
await event.run(**message.data)
|
||||||
|
|
||||||
async def call(self, command: Command, data: CommandData, parameters: List[str]):
|
async def call(self, command: rc.Command, data: rc.CommandData, parameters: List[str]):
|
||||||
log.info(f"Calling command: {command.name}")
|
log.info(f"Calling command: {command.name}")
|
||||||
try:
|
try:
|
||||||
# Run the command
|
# Run the command
|
||||||
await command.run(CommandArgs(parameters), data)
|
await command.run(rc.CommandArgs(parameters), data)
|
||||||
except InvalidInputError as e:
|
except rc.InvalidInputError as e:
|
||||||
await data.reply(f"⚠️ {e.message}\n"
|
await data.reply(f"⚠️ {e.message}\n"
|
||||||
f"Syntax: [c]{command.interface.prefix}{command.name} {command.syntax}[/c]")
|
f"Syntax: [c]{self.prefix}{command.name} {command.syntax}[/c]")
|
||||||
except UserError as e:
|
except rc.UserError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except UnsupportedError as e:
|
except rc.UnsupportedError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except ExternalError as e:
|
except rc.ExternalError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except ConfigurationError as e:
|
except rc.ConfigurationError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except ProgramError as e:
|
except rc.ProgramError as e:
|
||||||
await data.reply(f"⛔️ {e.message}")
|
await data.reply(f"⛔️ {e.message}")
|
||||||
except CommandError as e:
|
except rc.CommandError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ru.sentry_exc(e)
|
ru.sentry_exc(e)
|
||||||
|
@ -304,23 +283,23 @@ class Serf(abc.ABC):
|
||||||
finally:
|
finally:
|
||||||
await data.session_close()
|
await data.session_close()
|
||||||
|
|
||||||
async def press(self, key: KeyboardKey, data: CommandData):
|
async def press(self, key: rc.KeyboardKey, data: rc.CommandData):
|
||||||
log.info(f"Calling key_callback: {repr(key)}")
|
log.info(f"Calling key_callback: {repr(key)}")
|
||||||
try:
|
try:
|
||||||
await key.press(data)
|
await key.press(data)
|
||||||
except InvalidInputError as e:
|
except rc.InvalidInputError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except UserError as e:
|
except rc.UserError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except UnsupportedError as e:
|
except rc.UnsupportedError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except ExternalError as e:
|
except rc.ExternalError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except ConfigurationError as e:
|
except rc.ConfigurationError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except ProgramError as e:
|
except rc.ProgramError as e:
|
||||||
await data.reply(f"⛔️ {e.message}")
|
await data.reply(f"⛔️ {e.message}")
|
||||||
except CommandError as e:
|
except rc.CommandError as e:
|
||||||
await data.reply(f"⚠️ {e.message}")
|
await data.reply(f"⚠️ {e.message}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ru.sentry_exc(e)
|
ru.sentry_exc(e)
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
from typing import *
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
def escape(string: str) -> str:
|
def escape(string: Optional[str]) -> Optional[str]:
|
||||||
"""Escape a string to be sent through Telegram (as HTML), and format it using RoyalCode.
|
"""Escape a string to be sent through Telegram (as HTML), and format it using RoyalCode.
|
||||||
|
|
||||||
Warning:
|
Warning:
|
||||||
Currently escapes everything, even items in code blocks."""
|
Currently escapes everything, even items in code blocks."""
|
||||||
|
|
||||||
url_pattern = re.compile(r"\[url=(.*?)](.*?)\[/url]")
|
url_pattern = re.compile(r"\[url=(.*?)](.*?)\[/url]")
|
||||||
url_replacement = r'<a href="\1">\2</a>'
|
url_replacement = r'<a href="\1">\2</a>'
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
|
from typing import *
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import asyncio as aio
|
import asyncio as aio
|
||||||
import uuid
|
import uuid
|
||||||
from typing import *
|
|
||||||
import royalnet.commands as rc
|
|
||||||
import royalnet.utils as ru
|
|
||||||
import royalnet.backpack.tables as rbt
|
|
||||||
from .escape import escape
|
|
||||||
from ..serf import Serf
|
|
||||||
import io
|
|
||||||
import telegram
|
import telegram
|
||||||
import urllib3
|
import urllib3
|
||||||
from telegram.utils.request import Request as TRequest
|
from telegram.utils.request import Request as TRequest
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import royalnet.commands as rc
|
||||||
|
import royalnet.utils as ru
|
||||||
|
import royalnet.backpack.tables as rbt
|
||||||
|
|
||||||
|
from .escape import escape
|
||||||
|
from ..serf import Serf
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
@ -21,9 +23,16 @@ except ImportError:
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class TelegramKeyCallback:
|
||||||
|
command: rc.Command
|
||||||
|
key: rc.KeyboardKey
|
||||||
|
|
||||||
|
|
||||||
class TelegramSerf(Serf):
|
class TelegramSerf(Serf):
|
||||||
"""A Serf that connects to `Telegram <https://telegram.org/>`_ as a bot."""
|
"""A Serf that connects to `Telegram <https://telegram.org/>`_ as a bot."""
|
||||||
interface_name = "telegram"
|
interface_name = "telegram"
|
||||||
|
prefix = "/"
|
||||||
|
|
||||||
_identity_table = rbt.Telegram
|
_identity_table = rbt.Telegram
|
||||||
_identity_column = "tg_id"
|
_identity_column = "tg_id"
|
||||||
|
@ -54,7 +63,7 @@ class TelegramSerf(Serf):
|
||||||
self.update_offset: int = -100
|
self.update_offset: int = -100
|
||||||
"""The current `update offset <https://core.telegram.org/bots/api#getupdates>`_."""
|
"""The current `update offset <https://core.telegram.org/bots/api#getupdates>`_."""
|
||||||
|
|
||||||
self.key_callbacks: Dict[str, rc.KeyboardKey] = {}
|
self.key_callbacks: Dict[str, TelegramKeyCallback] = {}
|
||||||
|
|
||||||
self.MessageData: Type[rc.CommandData] = self.message_data_factory()
|
self.MessageData: Type[rc.CommandData] = self.message_data_factory()
|
||||||
self.CallbackData: Type[rc.CommandData] = self.callback_data_factory()
|
self.CallbackData: Type[rc.CommandData] = self.callback_data_factory()
|
||||||
|
@ -90,25 +99,13 @@ class TelegramSerf(Serf):
|
||||||
break
|
break
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def interface_factory(self) -> Type[rc.CommandInterface]:
|
|
||||||
# noinspection PyPep8Naming
|
|
||||||
GenericInterface = super().interface_factory()
|
|
||||||
|
|
||||||
# noinspection PyMethodParameters
|
|
||||||
class TelegramInterface(GenericInterface):
|
|
||||||
name = self.interface_name
|
|
||||||
prefix = "/"
|
|
||||||
|
|
||||||
return TelegramInterface
|
|
||||||
|
|
||||||
def message_data_factory(self) -> Type[rc.CommandData]:
|
def message_data_factory(self) -> Type[rc.CommandData]:
|
||||||
# noinspection PyMethodParameters
|
# noinspection PyMethodParameters
|
||||||
class TelegramMessageData(rc.CommandData):
|
class TelegramMessageData(rc.CommandData):
|
||||||
def __init__(data,
|
def __init__(data,
|
||||||
interface: rc.CommandInterface,
|
command: rc.Command,
|
||||||
loop: aio.AbstractEventLoop,
|
|
||||||
message: telegram.Message):
|
message: telegram.Message):
|
||||||
super().__init__(interface=interface, loop=loop)
|
super().__init__(command=command)
|
||||||
data.message: telegram.Message = message
|
data.message: telegram.Message = message
|
||||||
|
|
||||||
async def reply(data, text: str):
|
async def reply(data, text: str):
|
||||||
|
@ -117,7 +114,7 @@ class TelegramSerf(Serf):
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
disable_web_page_preview=True)
|
disable_web_page_preview=True)
|
||||||
|
|
||||||
async def reply_image(data, image: io.IOBase, caption: Optional[str] = None) -> None:
|
async def reply_image(data, image: "BinaryIO", caption: Optional[str] = None) -> None:
|
||||||
await self.api_call(data.message.chat.send_photo,
|
await self.api_call(data.message.chat.send_photo,
|
||||||
photo=image,
|
photo=image,
|
||||||
caption=escape(caption) if caption is not None else None,
|
caption=escape(caption) if caption is not None else None,
|
||||||
|
@ -149,7 +146,7 @@ class TelegramSerf(Serf):
|
||||||
for key in keys:
|
for key in keys:
|
||||||
uid: str = str(uuid.uuid4())
|
uid: str = str(uuid.uuid4())
|
||||||
key_uids.append(uid)
|
key_uids.append(uid)
|
||||||
self.register_keyboard_key(uid, key)
|
data.register_keyboard_key(uid, key)
|
||||||
tg_button: telegram.InlineKeyboardButton = telegram.InlineKeyboardButton(key.text,
|
tg_button: telegram.InlineKeyboardButton = telegram.InlineKeyboardButton(key.text,
|
||||||
callback_data=uid)
|
callback_data=uid)
|
||||||
tg_row: List[telegram.InlineKeyboardButton] = [tg_button]
|
tg_row: List[telegram.InlineKeyboardButton] = [tg_button]
|
||||||
|
@ -163,7 +160,13 @@ class TelegramSerf(Serf):
|
||||||
yield message
|
yield message
|
||||||
await self.api_call(message.edit_reply_markup, reply_markup=None)
|
await self.api_call(message.edit_reply_markup, reply_markup=None)
|
||||||
for uid in key_uids:
|
for uid in key_uids:
|
||||||
self.unregister_keyboard_key(uid)
|
data.unregister_keyboard_key(uid)
|
||||||
|
|
||||||
|
def register_keyboard_key(data, identifier: str, key: rc.KeyboardKey):
|
||||||
|
self.key_callbacks[identifier] = TelegramKeyCallback(key=key, command=data.command)
|
||||||
|
|
||||||
|
def unregister_keyboard_key(data, identifier: str):
|
||||||
|
del self.key_callbacks[identifier]
|
||||||
|
|
||||||
return TelegramMessageData
|
return TelegramMessageData
|
||||||
|
|
||||||
|
@ -171,10 +174,9 @@ class TelegramSerf(Serf):
|
||||||
# noinspection PyMethodParameters
|
# noinspection PyMethodParameters
|
||||||
class TelegramKeyboardData(rc.CommandData):
|
class TelegramKeyboardData(rc.CommandData):
|
||||||
def __init__(data,
|
def __init__(data,
|
||||||
interface: rc.CommandInterface,
|
command: rc.Command,
|
||||||
loop: aio.AbstractEventLoop,
|
|
||||||
cbq: telegram.CallbackQuery):
|
cbq: telegram.CallbackQuery):
|
||||||
super().__init__(interface=interface, loop=loop)
|
super().__init__(command=command)
|
||||||
data.cbq: telegram.CallbackQuery = cbq
|
data.cbq: telegram.CallbackQuery = cbq
|
||||||
|
|
||||||
async def reply(data, text: str):
|
async def reply(data, text: str):
|
||||||
|
@ -250,7 +252,8 @@ class TelegramSerf(Serf):
|
||||||
# Send a typing notification
|
# Send a typing notification
|
||||||
await self.api_call(message.chat.send_action, telegram.ChatAction.TYPING)
|
await self.api_call(message.chat.send_action, telegram.ChatAction.TYPING)
|
||||||
# Prepare data
|
# Prepare data
|
||||||
data = self.MessageData(interface=command.interface, loop=self.loop, message=message)
|
# noinspection PyArgumentList
|
||||||
|
data = self.MessageData(command=command, message=message)
|
||||||
# Call the command
|
# Call the command
|
||||||
await self.call(command, data, parameters)
|
await self.call(command, data, parameters)
|
||||||
|
|
||||||
|
@ -259,15 +262,10 @@ class TelegramSerf(Serf):
|
||||||
if uid not in self.key_callbacks:
|
if uid not in self.key_callbacks:
|
||||||
await self.api_call(cbq.answer, text="⚠️ This keyboard has expired.", show_alert=True)
|
await self.api_call(cbq.answer, text="⚠️ This keyboard has expired.", show_alert=True)
|
||||||
return
|
return
|
||||||
key: rc.KeyboardKey = self.key_callbacks[uid]
|
cbd = self.key_callbacks[uid]
|
||||||
data: rc.CommandData = self.CallbackData(interface=key.interface, loop=self.loop, cbq=cbq)
|
# noinspection PyArgumentList
|
||||||
await self.press(key, data)
|
data: rc.CommandData = self.CallbackData(command=cbd.command, cbq=cbq)
|
||||||
|
await self.press(cbd.key, data)
|
||||||
def register_keyboard_key(self, identifier: str, key: rc.KeyboardKey):
|
|
||||||
self.key_callbacks[identifier] = key
|
|
||||||
|
|
||||||
def unregister_keyboard_key(self, identifier: str):
|
|
||||||
del self.key_callbacks[identifier]
|
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
await super().run()
|
await super().run()
|
||||||
|
|
0
royalnet/types/__init__.py
Normal file
0
royalnet/types/__init__.py
Normal file
Loading…
Reference in a new issue