Add appservice option to standalone mode
This commit is contained in:
parent
8f40a0b292
commit
92736baefd
4 changed files with 71 additions and 4 deletions
|
@ -15,6 +15,7 @@
|
|||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
|
|
|
@ -30,7 +30,11 @@ from ruamel.yaml import YAML
|
|||
from ruamel.yaml.comments import CommentedMap
|
||||
from yarl import URL
|
||||
|
||||
from mautrix.appservice import AppServiceServerMixin
|
||||
from mautrix.client import SyncStream
|
||||
from mautrix.types import (
|
||||
BaseMessageEventContentFuncs,
|
||||
Event,
|
||||
EventType,
|
||||
Filter,
|
||||
Membership,
|
||||
|
@ -113,6 +117,9 @@ if "/" in meta.main_class:
|
|||
else:
|
||||
module = meta.modules[0]
|
||||
main_class = meta.main_class
|
||||
|
||||
if args.meta != "maubot.yaml" and os.path.dirname(args.meta) != "":
|
||||
sys.path.append(os.path.dirname(args.meta))
|
||||
bot_module = importlib.import_module(module)
|
||||
plugin: type[Plugin] = getattr(bot_module, main_class)
|
||||
loader = FileSystemLoader(os.path.dirname(args.meta), meta)
|
||||
|
@ -131,6 +138,7 @@ user_id = config["user.credentials.id"]
|
|||
device_id = config["user.credentials.device_id"]
|
||||
homeserver = config["user.credentials.homeserver"]
|
||||
access_token = config["user.credentials.access_token"]
|
||||
appservice_listener = config["user.appservice"]
|
||||
|
||||
crypto_store = state_store = None
|
||||
if device_id and not OlmMachine:
|
||||
|
@ -188,6 +196,10 @@ if meta.webapp:
|
|||
resource = PrefixResource(web_base_path)
|
||||
resource.add_route(hdrs.METH_ANY, _handle_plugin_request)
|
||||
web_app.router.register_resource(resource)
|
||||
elif appservice_listener:
|
||||
web_app = web.Application()
|
||||
web_runner = web.AppRunner(web_app, access_log_class=AccessLogger)
|
||||
public_url = plugin_webapp = None
|
||||
else:
|
||||
web_app = web_runner = public_url = plugin_webapp = None
|
||||
|
||||
|
@ -195,6 +207,31 @@ loop = asyncio.get_event_loop()
|
|||
|
||||
client: MaubotMatrixClient | None = None
|
||||
bot: Plugin | None = None
|
||||
appservice: AppServiceServerMixin | None = None
|
||||
|
||||
|
||||
if appservice_listener:
|
||||
assert web_app is not None, "web_app is always set when appservice_listener is set"
|
||||
appservice = AppServiceServerMixin(
|
||||
ephemeral_events=True,
|
||||
encryption_events=True,
|
||||
log=logging.getLogger("maubot.appservice"),
|
||||
hs_token=config["user.hs_token"],
|
||||
)
|
||||
appservice.register_routes(web_app)
|
||||
|
||||
@appservice.matrix_event_handler
|
||||
async def handle_appservice_event(evt: Event) -> None:
|
||||
if isinstance(evt.content, BaseMessageEventContentFuncs):
|
||||
evt.content.trim_reply_fallback()
|
||||
fake_sync_stream = SyncStream.JOINED_ROOM
|
||||
if evt.type.is_ephemeral:
|
||||
fake_sync_stream |= SyncStream.EPHEMERAL
|
||||
else:
|
||||
fake_sync_stream |= SyncStream.TIMELINE
|
||||
setattr(evt, "source", fake_sync_stream)
|
||||
tasks = client.dispatch_manual_event(evt.type, evt, include_global_handlers=True)
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
|
||||
async def main():
|
||||
|
@ -217,6 +254,8 @@ async def main():
|
|||
state_store=state_store,
|
||||
device_id=device_id,
|
||||
)
|
||||
if appservice:
|
||||
client.api.as_user_id = user_id
|
||||
client.ignore_first_sync = config["user.ignore_first_sync"]
|
||||
client.ignore_initial_sync = config["user.ignore_initial_sync"]
|
||||
if crypto_store:
|
||||
|
@ -225,6 +264,11 @@ async def main():
|
|||
await crypto_store.open()
|
||||
|
||||
client.crypto = OlmMachine(client, crypto_store, state_store)
|
||||
if appservice:
|
||||
appservice.otk_handler = client.crypto.handle_as_otk_counts
|
||||
appservice.device_list_handler = client.crypto.handle_as_device_lists
|
||||
appservice.to_device_handler = client.crypto.handle_as_to_device_event
|
||||
client.api.as_device_id = device_id
|
||||
crypto_device_id = await crypto_store.get_device_id()
|
||||
if crypto_device_id and crypto_device_id != device_id:
|
||||
log.fatal(
|
||||
|
@ -272,6 +316,8 @@ async def main():
|
|||
)
|
||||
await nb.put_filter_id(filter_id)
|
||||
_ = client.start(nb.filter_id)
|
||||
elif appservice_listener and crypto_store and not client.crypto.account.shared:
|
||||
await client.crypto.share_keys()
|
||||
|
||||
if config["user.autojoin"]:
|
||||
log.debug("Autojoin is enabled")
|
||||
|
@ -334,9 +380,14 @@ async def stop(suppress_stop_error: bool = False) -> None:
|
|||
except Exception:
|
||||
if not suppress_stop_error:
|
||||
log.exception("Error stopping bot")
|
||||
if web_runner:
|
||||
if web_runner and web_runner.server:
|
||||
try:
|
||||
await web_runner.shutdown()
|
||||
await web_runner.cleanup()
|
||||
except RuntimeError:
|
||||
if not suppress_stop_error:
|
||||
await db.stop()
|
||||
raise
|
||||
await db.stop()
|
||||
|
||||
|
||||
|
@ -347,6 +398,10 @@ signal.signal(signal.SIGTERM, signal.default_int_handler)
|
|||
try:
|
||||
log.info("Starting plugin")
|
||||
loop.run_until_complete(main())
|
||||
except SystemExit:
|
||||
loop.run_until_complete(stop(suppress_stop_error=True))
|
||||
loop.close()
|
||||
raise
|
||||
except (Exception, KeyboardInterrupt) as e:
|
||||
if isinstance(e, KeyboardInterrupt):
|
||||
log.info("Startup interrupted, stopping")
|
||||
|
|
|
@ -33,9 +33,13 @@ class Config(BaseFileConfig):
|
|||
copy("user.credentials.access_token")
|
||||
copy("user.credentials.device_id")
|
||||
copy("user.sync")
|
||||
copy("user.appservice")
|
||||
copy("user.hs_token")
|
||||
copy("user.autojoin")
|
||||
copy("user.displayname")
|
||||
copy("user.avatar_url")
|
||||
copy("user.ignore_initial_sync")
|
||||
copy("user.ignore_first_sync")
|
||||
if "server" in base:
|
||||
copy("server.hostname")
|
||||
copy("server.port")
|
||||
|
|
|
@ -5,9 +5,15 @@ user:
|
|||
homeserver: https://example.com
|
||||
access_token: foo
|
||||
# If you want to enable encryption, set the device ID corresponding to the access token here.
|
||||
# When using an appservice, you should use appservice login manually to generate a device ID and access token.
|
||||
device_id: null
|
||||
# Enable /sync? This is not needed for purely unencrypted webhook-based bots, but is necessary in most other cases.
|
||||
sync: true
|
||||
# Receive appservice transactions? This will add a /_matrix/app/v1/transactions endpoint on
|
||||
# the HTTP server configured below. The base_path will not be applied for the /transactions path.
|
||||
appservice: false
|
||||
# When appservice mode is enabled, the hs_token for the appservice.
|
||||
hs_token: null
|
||||
# Automatically accept invites?
|
||||
autojoin: false
|
||||
# The displayname and avatar URL to set for the bot on startup.
|
||||
|
@ -21,7 +27,8 @@ user:
|
|||
# if you want the bot to handle messages that were sent while the bot was down.
|
||||
ignore_first_sync: true
|
||||
|
||||
# Web server settings. These will only take effect if the plugin requests it using `webapp: true` in the meta file.
|
||||
# Web server settings. These will only take effect if the plugin requests it using `webapp: true` in the meta file,
|
||||
# or if user -> appservice is set to true.
|
||||
server:
|
||||
# The IP and port to listen to.
|
||||
hostname: 0.0.0.0
|
||||
|
|
Loading…
Reference in a new issue