mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 11:34:18 +00:00
BREAKING: Drop matrix support (for now)
This commit is contained in:
parent
207c2d4dcc
commit
d38400521f
19 changed files with 38 additions and 256 deletions
|
@ -16,9 +16,8 @@ Commands using the Royalnet Serf API share their code between chat platforms: ea
|
||||||
|
|
||||||
- [Telegram](https://core.telegram.org/bots)
|
- [Telegram](https://core.telegram.org/bots)
|
||||||
- [Discord](https://discordapp.com/developers/docs/)
|
- [Discord](https://discordapp.com/developers/docs/)
|
||||||
- [Matrix](https://matrix.org/) (alpha)
|
|
||||||
|
|
||||||
More can easily be added by creating a new serf!
|
More can easily be added by implementing a new serf!
|
||||||
|
|
||||||
### [Alchemy](royalnet/alchemy)
|
### [Alchemy](royalnet/alchemy)
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ intersphinx_mapping = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
def skip(app, what, name: str, obj, would_skip, options):
|
def skip(app, what, name: str, obj, would_skip, options):
|
||||||
if name == "__init__" or name == "__getitem__" or name == "__getattr__":
|
if name == "__init__" or name == "__getitem__" or name == "__getattr__":
|
||||||
return not bool(obj.__doc__)
|
return not bool(obj.__doc__)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Remember to run `poetry update` editing this file!
|
# Remember to run `poetry update` editing this file!
|
||||||
|
|
||||||
# Install everything with
|
# Install everything with
|
||||||
# poetry install -E telegram -E discord -E matrix -E alchemy_easy -E constellation -E sentry -E herald -E coloredlogs
|
# poetry install -E telegram -E discord -E alchemy_easy -E constellation -E sentry -E herald -E coloredlogs
|
||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "royalnet"
|
name = "royalnet"
|
||||||
|
@ -32,9 +32,6 @@ python_telegram_bot = { version = "^12.2.0", optional = true }
|
||||||
"discord.py" = { version = "^1.3.1", optional = true }
|
"discord.py" = { version = "^1.3.1", optional = true }
|
||||||
pynacl = { version = "^1.3.0", optional = true } # This requires libffi-dev and python3.*-dev to be installed on Linux systems
|
pynacl = { version = "^1.3.0", optional = true } # This requires libffi-dev and python3.*-dev to be installed on Linux systems
|
||||||
|
|
||||||
# matrix
|
|
||||||
matrix-nio = { version = "^0.6", optional = true }
|
|
||||||
|
|
||||||
# alchemy
|
# alchemy
|
||||||
sqlalchemy = { version = "^1.3.18", optional = true }
|
sqlalchemy = { version = "^1.3.18", optional = true }
|
||||||
psycopg2 = { version = "^2.8.4", optional = true } # Requires quite a bit of stuff http://initd.org/psycopg/docs/install.html#install-from-source
|
psycopg2 = { version = "^2.8.4", optional = true } # Requires quite a bit of stuff http://initd.org/psycopg/docs/install.html#install-from-source
|
||||||
|
@ -66,7 +63,6 @@ sphinx_rtd_theme = "^0.4.3"
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
telegram = ["python_telegram_bot"]
|
telegram = ["python_telegram_bot"]
|
||||||
discord = ["discord.py", "pynacl", "lavalink", "aiohttp", "cchardet"]
|
discord = ["discord.py", "pynacl", "lavalink", "aiohttp", "cchardet"]
|
||||||
matrix = ["matrix-nio"]
|
|
||||||
alchemy_easy = ["sqlalchemy", "psycopg2_binary", "bcrypt"]
|
alchemy_easy = ["sqlalchemy", "psycopg2_binary", "bcrypt"]
|
||||||
alchemy_hard = ["sqlalchemy", "psycopg2", "bcrypt"]
|
alchemy_hard = ["sqlalchemy", "psycopg2", "bcrypt"]
|
||||||
constellation = ["starlette", "uvicorn", "python-multipart"]
|
constellation = ["starlette", "uvicorn", "python-multipart"]
|
||||||
|
|
|
@ -19,11 +19,6 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
rsd = None
|
rsd = None
|
||||||
|
|
||||||
try:
|
|
||||||
import royalnet.serf.matrix as rsm
|
|
||||||
except ImportError:
|
|
||||||
rsm = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import royalnet.constellation as rc
|
import royalnet.constellation as rc
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -100,18 +95,18 @@ def run(config_file: str):
|
||||||
else:
|
else:
|
||||||
log.debug("__serfs__: Configured")
|
log.debug("__serfs__: Configured")
|
||||||
|
|
||||||
def configure_serf(name: str, module, class_: Type[rs.Serf]):
|
def configure_serf(n: str, module, class_: Type[rs.Serf]):
|
||||||
serf_cfg = serfs_cfg.get(name)
|
serf_cfg = serfs_cfg.get(n)
|
||||||
if module is None:
|
if module is None:
|
||||||
log.info(f"Serf.{name}: Not installed")
|
log.info(f"Serf.{n}: Not installed")
|
||||||
elif serf_cfg is None:
|
elif serf_cfg is None:
|
||||||
log.warning(f"Serf.{name}: Not configured")
|
log.warning(f"Serf.{n}: Not configured")
|
||||||
elif not serf_cfg["enabled"]:
|
elif not serf_cfg["enabled"]:
|
||||||
log.info(f"Serf.{name}: Disabled")
|
log.info(f"Serf.{n}: Disabled")
|
||||||
else:
|
else:
|
||||||
def serf_constructor() -> multiprocessing.Process:
|
def serf_constructor() -> multiprocessing.Process:
|
||||||
return multiprocessing.Process(
|
return multiprocessing.Process(
|
||||||
name=f"Serf.{name}",
|
name=f"Serf.{n}",
|
||||||
target=class_.run_process,
|
target=class_.run_process,
|
||||||
daemon=True,
|
daemon=True,
|
||||||
kwargs={
|
kwargs={
|
||||||
|
@ -124,12 +119,11 @@ def run(config_file: str):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
processes[f"Serf.{name}"] = ru.RoyalnetProcess(serf_constructor, None)
|
processes[f"Serf.{n}"] = ru.RoyalnetProcess(serf_constructor, None)
|
||||||
log.info(f"Serf.{name}: Enabled")
|
log.info(f"Serf.{n}: Enabled")
|
||||||
|
|
||||||
configure_serf("Telegram", rst, rst.TelegramSerf)
|
configure_serf("Telegram", rst, rst.TelegramSerf)
|
||||||
configure_serf("Discord", rsd, rsd.DiscordSerf)
|
configure_serf("Discord", rsd, rsd.DiscordSerf)
|
||||||
configure_serf("Matrix", rsm, rsm.MatrixSerf)
|
|
||||||
|
|
||||||
# Constellation
|
# Constellation
|
||||||
constellation_cfg = config.get("Constellation")
|
constellation_cfg = config.get("Constellation")
|
||||||
|
|
|
@ -5,7 +5,6 @@ from typing import *
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.exc import ProgrammingError
|
from sqlalchemy.exc import ProgrammingError
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.ext.declarative.api import DeclarativeMeta
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
from sqlalchemy.schema import Table
|
from sqlalchemy.schema import Table
|
||||||
|
@ -52,7 +51,7 @@ class Alchemy:
|
||||||
except ProgrammingError:
|
except ProgrammingError:
|
||||||
log.warning("Skipping table creation, as it is probably being created by a different process.")
|
log.warning("Skipping table creation, as it is probably being created by a different process.")
|
||||||
|
|
||||||
def get(self, table: Union[str, type]) -> DeclarativeMeta:
|
def get(self, table: Union[str, type]) -> Any:
|
||||||
"""Get the table with a specified name or class.
|
"""Get the table with a specified name or class.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
|
@ -11,7 +11,8 @@ class RoyalnetaliasesCommand(rc.Command):
|
||||||
|
|
||||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||||
if name := args.optional(0) is not None:
|
if name := args.optional(0) is not None:
|
||||||
user = await User.find(alchemy=self.alchemy, session=data.session, identifier=name)
|
async with data.session_acm() as session:
|
||||||
|
user = await User.find(alchemy=self.alchemy, session=session, identifier=name)
|
||||||
else:
|
else:
|
||||||
user = await data.get_author(error_if_none=True)
|
user = await data.get_author(error_if_none=True)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@ class RoyalnetrolesCommand(rc.Command):
|
||||||
|
|
||||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||||
if name := args.optional(0) is not None:
|
if name := args.optional(0) is not None:
|
||||||
user = await User.find(alchemy=self.alchemy, session=data.session, identifier=name)
|
async with data.session_acm() as session:
|
||||||
|
user = await User.find(alchemy=self.alchemy, session=session, identifier=name)
|
||||||
else:
|
else:
|
||||||
user = await data.get_author(error_if_none=True)
|
user = await data.get_author(error_if_none=True)
|
||||||
|
|
||||||
|
|
|
@ -37,11 +37,7 @@ class ApiUserPasswd(rca.ApiStar):
|
||||||
tokens: List[Token] = await ru.asyncify(
|
tokens: List[Token] = await ru.asyncify(
|
||||||
data.session
|
data.session
|
||||||
.query(self.alchemy.get(Token))
|
.query(self.alchemy.get(Token))
|
||||||
.filter(
|
.filter(and_(TokenT.user == user, TokenT.expiration >= datetime.datetime.now()))
|
||||||
and_(
|
|
||||||
TokenT.user == user,
|
|
||||||
TokenT.expiration >= datetime.datetime.now()
|
|
||||||
))
|
|
||||||
.all
|
.all
|
||||||
)
|
)
|
||||||
for t in tokens:
|
for t in tokens:
|
||||||
|
|
|
@ -45,7 +45,8 @@ class DocsStar(PageStar):
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Royalnet Docs</title>
|
<title>Royalnet Docs</title>
|
||||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3.23.4/swagger-ui.css">
|
<link rel="stylesheet"
|
||||||
|
type="text/css" href="https://unpkg.com/swagger-ui-dist@3.23.4/swagger-ui.css">
|
||||||
<script src="https://unpkg.com/swagger-ui-dist@3.23.4/swagger-ui-bundle.js"></script>
|
<script src="https://unpkg.com/swagger-ui-dist@3.23.4/swagger-ui-bundle.js"></script>
|
||||||
<script src="https://unpkg.com/swagger-ui-dist@3.23.4/swagger-ui-standalone-preset.js"></script>
|
<script src="https://unpkg.com/swagger-ui-dist@3.23.4/swagger-ui-standalone-preset.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import re
|
|
||||||
|
|
||||||
from sqlalchemy import *
|
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
from .users import User
|
|
||||||
|
|
||||||
|
|
||||||
class Matrix:
|
|
||||||
__tablename__ = "matrix"
|
|
||||||
|
|
||||||
@declared_attr
|
|
||||||
def user_id(self):
|
|
||||||
return Column(Integer, ForeignKey("users.uid"), nullable=False)
|
|
||||||
|
|
||||||
@declared_attr
|
|
||||||
def user(self):
|
|
||||||
return relationship("User", backref="matrix")
|
|
||||||
|
|
||||||
@declared_attr
|
|
||||||
def matrix_id(self):
|
|
||||||
return Column(String, nullable=False, primary_key=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def username(self):
|
|
||||||
match = re.match("^@(.+):.+$", self.matrix_id)
|
|
||||||
result = match.group(1)
|
|
||||||
assert result is not None
|
|
||||||
return result
|
|
||||||
|
|
||||||
@property
|
|
||||||
def homeserver(self):
|
|
||||||
match = re.match("^@.+:(.+)$", self.matrix_id)
|
|
||||||
result = match.group(1)
|
|
||||||
assert result is not None
|
|
||||||
return result
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<Matrix {str(self)}>"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"[c]matrix:{self.matrix_id}[/c]"
|
|
|
@ -1,9 +1,9 @@
|
||||||
import datetime
|
import datetime
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
from sqlalchemy import *
|
import sqlalchemy as s
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
import sqlalchemy.ext.declarative as sed
|
||||||
from sqlalchemy.orm import *
|
import sqlalchemy.orm as so
|
||||||
|
|
||||||
import royalnet.utils as ru
|
import royalnet.utils as ru
|
||||||
|
|
||||||
|
@ -12,21 +12,21 @@ import royalnet.utils as ru
|
||||||
class Token:
|
class Token:
|
||||||
__tablename__ = "tokens"
|
__tablename__ = "tokens"
|
||||||
|
|
||||||
@declared_attr
|
@sed.declared_attr
|
||||||
def token(self):
|
def token(self):
|
||||||
return Column(String, primary_key=True)
|
return s.Column(s.String, primary_key=True)
|
||||||
|
|
||||||
@declared_attr
|
@sed.declared_attr
|
||||||
def user_id(self):
|
def user_id(self):
|
||||||
return Column(Integer, ForeignKey("users.uid"), nullable=False)
|
return s.Column(s.Integer, s.ForeignKey("users.uid"), nullable=False)
|
||||||
|
|
||||||
@declared_attr
|
@sed.declared_attr
|
||||||
def user(self):
|
def user(self):
|
||||||
return relationship("User", backref="tokens")
|
return so.relationship("User", backref="tokens")
|
||||||
|
|
||||||
@declared_attr
|
@sed.declared_attr
|
||||||
def expiration(self):
|
def expiration(self):
|
||||||
return Column(DateTime, nullable=False)
|
return s.Column(s.DateTime, nullable=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def expired(self):
|
def expired(self):
|
||||||
|
|
|
@ -74,6 +74,7 @@ class User:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def roles(self) -> list:
|
def roles(self) -> list:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
return list(map(lambda a: a.role, self._roles))
|
return list(map(lambda a: a.role, self._roles))
|
||||||
|
|
||||||
def add_role(self, alchemy, role: str) -> None:
|
def add_role(self, alchemy, role: str) -> None:
|
||||||
|
@ -90,6 +91,7 @@ class User:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def aliases(self) -> list:
|
def aliases(self) -> list:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
return list(map(lambda a: a.alias, self._aliases))
|
return list(map(lambda a: a.alias, self._aliases))
|
||||||
|
|
||||||
def add_alias(self, alchemy, alias: str) -> None:
|
def add_alias(self, alchemy, alias: str) -> None:
|
||||||
|
|
|
@ -69,6 +69,7 @@ class Constellation:
|
||||||
tables = set()
|
tables = set()
|
||||||
for pack in packs.values():
|
for pack in packs.values():
|
||||||
try:
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
tables = tables.union(pack["tables"].available_tables)
|
tables = tables.union(pack["tables"].available_tables)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.warning(f"Pack `{pack}` does not have the `available_tables` attribute.")
|
log.warning(f"Pack `{pack}` does not have the `available_tables` attribute.")
|
||||||
|
@ -108,6 +109,7 @@ class Constellation:
|
||||||
pack = packs[pack_name]
|
pack = packs[pack_name]
|
||||||
pack_cfg = packs_cfg.get(pack_name, {})
|
pack_cfg = packs_cfg.get(pack_name, {})
|
||||||
try:
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
events = pack["events"].available_events
|
events = pack["events"].available_events
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.warning(f"Pack `{pack}` does not have the `available_events` attribute.")
|
log.warning(f"Pack `{pack}` does not have the `available_events` attribute.")
|
||||||
|
@ -127,6 +129,7 @@ class Constellation:
|
||||||
pack = packs[pack_name]
|
pack = packs[pack_name]
|
||||||
pack_cfg = packs_cfg.get(pack_name, {})
|
pack_cfg = packs_cfg.get(pack_name, {})
|
||||||
try:
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
page_stars = pack["stars"].available_page_stars
|
page_stars = pack["stars"].available_page_stars
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.warning(f"Pack `{pack}` does not have the `available_page_stars` attribute.")
|
log.warning(f"Pack `{pack}` does not have the `available_page_stars` attribute.")
|
||||||
|
@ -248,7 +251,7 @@ class Constellation:
|
||||||
|
|
||||||
return page_star.path, f, page_star.methods()
|
return page_star.path, f, page_star.methods()
|
||||||
|
|
||||||
def register_page_stars(self, page_stars: List[Type[PageStar]], pack_cfg: Dict[str, Any]):
|
def register_page_stars(self, page_stars: List[Type[PageStar]], pack_cfg: rc.ConfigDict):
|
||||||
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:
|
||||||
|
|
|
@ -36,6 +36,7 @@ def run(config_filename, file_format):
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
commands = pack["commands"].available_commands
|
commands = pack["commands"].available_commands
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
p(f"Pack `{pack}` does not have the `available_commands` attribute.", err=True)
|
p(f"Pack `{pack}` does not have the `available_commands` attribute.", err=True)
|
||||||
|
|
|
@ -60,6 +60,7 @@ class Server:
|
||||||
matching = [client for client in self.identified_clients if client.link_type == link_type]
|
matching = [client for client in self.identified_clients if client.link_type == link_type]
|
||||||
return matching or []
|
return matching or []
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
async def listener(self, websocket: "websockets.server.WebSocketServerProtocol", path):
|
async def listener(self, websocket: "websockets.server.WebSocketServerProtocol", path):
|
||||||
connected_client = ConnectedClient(websocket)
|
connected_client = ConnectedClient(websocket)
|
||||||
# Wait for identification
|
# Wait for identification
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# `royalnet.serf.matrix`
|
|
||||||
|
|
||||||
A `Serf` implementation for Matrix.
|
|
||||||
|
|
||||||
It requires (obviously) the `matrix` extra to be installed.
|
|
||||||
|
|
||||||
Install it with:
|
|
||||||
```
|
|
||||||
pip install royalnet[matrix]
|
|
||||||
```
|
|
|
@ -1,17 +0,0 @@
|
||||||
"""A :class:`Serf` implementation for Matrix.
|
|
||||||
|
|
||||||
It requires (obviously) the ``matrix`` extra to be installed.
|
|
||||||
|
|
||||||
Install it with: ::
|
|
||||||
|
|
||||||
pip install royalnet[matrix]
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .escape import escape
|
|
||||||
from .matrixserf import MatrixSerf
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"MatrixSerf",
|
|
||||||
"escape",
|
|
||||||
]
|
|
|
@ -1,15 +0,0 @@
|
||||||
def escape(string: str) -> str:
|
|
||||||
"""Escape a string to be sent through Matrix, and format it using RoyalCode.
|
|
||||||
|
|
||||||
Underlines are currently unsupported.
|
|
||||||
|
|
||||||
Warning:
|
|
||||||
Currently escapes everything, even items in code blocks."""
|
|
||||||
return string.replace("[b]", "**") \
|
|
||||||
.replace("[/b]", "**") \
|
|
||||||
.replace("[i]", "_") \
|
|
||||||
.replace("[/i]", "_") \
|
|
||||||
.replace("[c]", "`") \
|
|
||||||
.replace("[/c]", "`") \
|
|
||||||
.replace("[p]", "```") \
|
|
||||||
.replace("[/p]", "```")
|
|
|
@ -1,127 +0,0 @@
|
||||||
import asyncio as aio
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
from typing import *
|
|
||||||
|
|
||||||
import nio
|
|
||||||
|
|
||||||
import royalnet.backpack as rb
|
|
||||||
import royalnet.commands as rc
|
|
||||||
import royalnet.utils as ru
|
|
||||||
from .escape import escape
|
|
||||||
from ..serf import Serf
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class MatrixSerf(Serf):
|
|
||||||
"""A serf that connects to `Matrix <https://matrix.org/>`_ as an user."""
|
|
||||||
interface_name = "matrix"
|
|
||||||
prefix = "!"
|
|
||||||
|
|
||||||
_identity_table = rb.tables.Matrix
|
|
||||||
_identity_column = "matrix_id"
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
loop: aio.AbstractEventLoop,
|
|
||||||
alchemy_cfg: rc.ConfigDict,
|
|
||||||
herald_cfg: rc.ConfigDict,
|
|
||||||
sentry_cfg: rc.ConfigDict,
|
|
||||||
packs_cfg: rc.ConfigDict,
|
|
||||||
serf_cfg: rc.ConfigDict,
|
|
||||||
**_):
|
|
||||||
if nio is None:
|
|
||||||
raise ImportError("'matrix' extra is not installed")
|
|
||||||
|
|
||||||
super().__init__(loop=loop,
|
|
||||||
alchemy_cfg=alchemy_cfg,
|
|
||||||
herald_cfg=herald_cfg,
|
|
||||||
sentry_cfg=sentry_cfg,
|
|
||||||
packs_cfg=packs_cfg,
|
|
||||||
serf_cfg=serf_cfg)
|
|
||||||
|
|
||||||
self.client: Optional[nio.AsyncClient] = None
|
|
||||||
|
|
||||||
self.homeserver: str = serf_cfg["homeserver"]
|
|
||||||
self.matrix_id: str = serf_cfg["matrix_id"]
|
|
||||||
self.password: str = serf_cfg["password"]
|
|
||||||
|
|
||||||
self._started_timestamp: Optional[int] = None
|
|
||||||
|
|
||||||
self.Data: Type[rc.CommandData] = self.data_factory()
|
|
||||||
|
|
||||||
def data_factory(self) -> Type[rc.CommandData]:
|
|
||||||
# noinspection PyMethodParameters,PyAbstractClass
|
|
||||||
class MatrixData(rc.CommandData):
|
|
||||||
def __init__(data,
|
|
||||||
command: rc.Command,
|
|
||||||
room: nio.MatrixRoom,
|
|
||||||
event: nio.Event):
|
|
||||||
super().__init__(command=command)
|
|
||||||
data.room: nio.MatrixRoom = room
|
|
||||||
data.event: nio.Event = event
|
|
||||||
|
|
||||||
async def reply(data, text: str):
|
|
||||||
await self.client.room_send(room_id=data.room.room_id, message_type="m.room.message", content={
|
|
||||||
"msgtype": "m.text",
|
|
||||||
"body": escape(text)
|
|
||||||
})
|
|
||||||
|
|
||||||
async def get_author(data, error_if_none=False):
|
|
||||||
user: str = data.event.sender
|
|
||||||
query = data.session.query(self.master_table)
|
|
||||||
for link in self.identity_chain:
|
|
||||||
query = query.join(link.mapper.class_)
|
|
||||||
query = query.filter(self.identity_column == user)
|
|
||||||
result = await ru.asyncify(query.one_or_none)
|
|
||||||
if result is None and error_if_none:
|
|
||||||
raise rc.CommandError("You must be registered to use this command.")
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Delete invoking does not really make sense on Matrix
|
|
||||||
|
|
||||||
return MatrixData
|
|
||||||
|
|
||||||
async def handle_message(self, room: "nio.MatrixRoom", event: "nio.RoomMessageText"):
|
|
||||||
# Skip events happened before the startup of the Serf
|
|
||||||
if event.server_timestamp < self._started_timestamp:
|
|
||||||
return
|
|
||||||
# Find the text in the event
|
|
||||||
text = event.body
|
|
||||||
# Skip non-command events
|
|
||||||
if not text.startswith("!"):
|
|
||||||
return
|
|
||||||
# Find and clean parameters
|
|
||||||
command_text, *parameters = text.split(" ")
|
|
||||||
# Don't use a case-sensitive command name
|
|
||||||
command_name = command_text.lower()
|
|
||||||
# Find the command
|
|
||||||
try:
|
|
||||||
command = self.commands[command_name]
|
|
||||||
except KeyError:
|
|
||||||
# Skip the message
|
|
||||||
return
|
|
||||||
# Send typing
|
|
||||||
await self.client.room_typing(room_id=room.room_id, typing_state=True)
|
|
||||||
# Open an alchemy session, if available
|
|
||||||
if self.alchemy is not None:
|
|
||||||
session = await ru.asyncify(self.alchemy.Session)
|
|
||||||
else:
|
|
||||||
session = None
|
|
||||||
# Prepare data
|
|
||||||
# noinspection PyArgumentList
|
|
||||||
data = self.Data(command=command, room=room, event=event)
|
|
||||||
# Call the command
|
|
||||||
await self.call(command, data, parameters)
|
|
||||||
# Close the alchemy session
|
|
||||||
if session is not None:
|
|
||||||
await ru.asyncify(session.close)
|
|
||||||
|
|
||||||
async def run(self):
|
|
||||||
self.client = nio.AsyncClient(self.homeserver, self.matrix_id)
|
|
||||||
await self.client.login(self.password)
|
|
||||||
self._started_timestamp = int(datetime.datetime.now().timestamp() * 1000)
|
|
||||||
# matrix-nio type annotations are wrong for asyncclients
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
self.client.add_event_callback(self.handle_message, (nio.RoomMessageText,))
|
|
||||||
await self.client.sync_forever()
|
|
Loading…
Reference in a new issue