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/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Callable
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
|
@ -30,7 +30,11 @@ from ruamel.yaml import YAML
|
||||||
from ruamel.yaml.comments import CommentedMap
|
from ruamel.yaml.comments import CommentedMap
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
|
from mautrix.appservice import AppServiceServerMixin
|
||||||
|
from mautrix.client import SyncStream
|
||||||
from mautrix.types import (
|
from mautrix.types import (
|
||||||
|
BaseMessageEventContentFuncs,
|
||||||
|
Event,
|
||||||
EventType,
|
EventType,
|
||||||
Filter,
|
Filter,
|
||||||
Membership,
|
Membership,
|
||||||
|
@ -113,6 +117,9 @@ if "/" in meta.main_class:
|
||||||
else:
|
else:
|
||||||
module = meta.modules[0]
|
module = meta.modules[0]
|
||||||
main_class = meta.main_class
|
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)
|
bot_module = importlib.import_module(module)
|
||||||
plugin: type[Plugin] = getattr(bot_module, main_class)
|
plugin: type[Plugin] = getattr(bot_module, main_class)
|
||||||
loader = FileSystemLoader(os.path.dirname(args.meta), meta)
|
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"]
|
device_id = config["user.credentials.device_id"]
|
||||||
homeserver = config["user.credentials.homeserver"]
|
homeserver = config["user.credentials.homeserver"]
|
||||||
access_token = config["user.credentials.access_token"]
|
access_token = config["user.credentials.access_token"]
|
||||||
|
appservice_listener = config["user.appservice"]
|
||||||
|
|
||||||
crypto_store = state_store = None
|
crypto_store = state_store = None
|
||||||
if device_id and not OlmMachine:
|
if device_id and not OlmMachine:
|
||||||
|
@ -188,6 +196,10 @@ if meta.webapp:
|
||||||
resource = PrefixResource(web_base_path)
|
resource = PrefixResource(web_base_path)
|
||||||
resource.add_route(hdrs.METH_ANY, _handle_plugin_request)
|
resource.add_route(hdrs.METH_ANY, _handle_plugin_request)
|
||||||
web_app.router.register_resource(resource)
|
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:
|
else:
|
||||||
web_app = web_runner = public_url = plugin_webapp = None
|
web_app = web_runner = public_url = plugin_webapp = None
|
||||||
|
|
||||||
|
@ -195,6 +207,31 @@ loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
client: MaubotMatrixClient | None = None
|
client: MaubotMatrixClient | None = None
|
||||||
bot: Plugin | 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():
|
async def main():
|
||||||
|
@ -217,6 +254,8 @@ async def main():
|
||||||
state_store=state_store,
|
state_store=state_store,
|
||||||
device_id=device_id,
|
device_id=device_id,
|
||||||
)
|
)
|
||||||
|
if appservice:
|
||||||
|
client.api.as_user_id = user_id
|
||||||
client.ignore_first_sync = config["user.ignore_first_sync"]
|
client.ignore_first_sync = config["user.ignore_first_sync"]
|
||||||
client.ignore_initial_sync = config["user.ignore_initial_sync"]
|
client.ignore_initial_sync = config["user.ignore_initial_sync"]
|
||||||
if crypto_store:
|
if crypto_store:
|
||||||
|
@ -225,6 +264,11 @@ async def main():
|
||||||
await crypto_store.open()
|
await crypto_store.open()
|
||||||
|
|
||||||
client.crypto = OlmMachine(client, crypto_store, state_store)
|
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()
|
crypto_device_id = await crypto_store.get_device_id()
|
||||||
if crypto_device_id and crypto_device_id != device_id:
|
if crypto_device_id and crypto_device_id != device_id:
|
||||||
log.fatal(
|
log.fatal(
|
||||||
|
@ -272,6 +316,8 @@ async def main():
|
||||||
)
|
)
|
||||||
await nb.put_filter_id(filter_id)
|
await nb.put_filter_id(filter_id)
|
||||||
_ = client.start(nb.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"]:
|
if config["user.autojoin"]:
|
||||||
log.debug("Autojoin is enabled")
|
log.debug("Autojoin is enabled")
|
||||||
|
@ -334,9 +380,14 @@ async def stop(suppress_stop_error: bool = False) -> None:
|
||||||
except Exception:
|
except Exception:
|
||||||
if not suppress_stop_error:
|
if not suppress_stop_error:
|
||||||
log.exception("Error stopping bot")
|
log.exception("Error stopping bot")
|
||||||
if web_runner:
|
if web_runner and web_runner.server:
|
||||||
await web_runner.shutdown()
|
try:
|
||||||
await web_runner.cleanup()
|
await web_runner.shutdown()
|
||||||
|
await web_runner.cleanup()
|
||||||
|
except RuntimeError:
|
||||||
|
if not suppress_stop_error:
|
||||||
|
await db.stop()
|
||||||
|
raise
|
||||||
await db.stop()
|
await db.stop()
|
||||||
|
|
||||||
|
|
||||||
|
@ -347,6 +398,10 @@ signal.signal(signal.SIGTERM, signal.default_int_handler)
|
||||||
try:
|
try:
|
||||||
log.info("Starting plugin")
|
log.info("Starting plugin")
|
||||||
loop.run_until_complete(main())
|
loop.run_until_complete(main())
|
||||||
|
except SystemExit:
|
||||||
|
loop.run_until_complete(stop(suppress_stop_error=True))
|
||||||
|
loop.close()
|
||||||
|
raise
|
||||||
except (Exception, KeyboardInterrupt) as e:
|
except (Exception, KeyboardInterrupt) as e:
|
||||||
if isinstance(e, KeyboardInterrupt):
|
if isinstance(e, KeyboardInterrupt):
|
||||||
log.info("Startup interrupted, stopping")
|
log.info("Startup interrupted, stopping")
|
||||||
|
|
|
@ -33,9 +33,13 @@ class Config(BaseFileConfig):
|
||||||
copy("user.credentials.access_token")
|
copy("user.credentials.access_token")
|
||||||
copy("user.credentials.device_id")
|
copy("user.credentials.device_id")
|
||||||
copy("user.sync")
|
copy("user.sync")
|
||||||
|
copy("user.appservice")
|
||||||
|
copy("user.hs_token")
|
||||||
copy("user.autojoin")
|
copy("user.autojoin")
|
||||||
copy("user.displayname")
|
copy("user.displayname")
|
||||||
copy("user.avatar_url")
|
copy("user.avatar_url")
|
||||||
|
copy("user.ignore_initial_sync")
|
||||||
|
copy("user.ignore_first_sync")
|
||||||
if "server" in base:
|
if "server" in base:
|
||||||
copy("server.hostname")
|
copy("server.hostname")
|
||||||
copy("server.port")
|
copy("server.port")
|
||||||
|
|
|
@ -5,9 +5,15 @@ user:
|
||||||
homeserver: https://example.com
|
homeserver: https://example.com
|
||||||
access_token: foo
|
access_token: foo
|
||||||
# If you want to enable encryption, set the device ID corresponding to the access token here.
|
# 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
|
device_id: null
|
||||||
# Enable /sync? This is not needed for purely unencrypted webhook-based bots, but is necessary in most other cases.
|
# Enable /sync? This is not needed for purely unencrypted webhook-based bots, but is necessary in most other cases.
|
||||||
sync: true
|
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?
|
# Automatically accept invites?
|
||||||
autojoin: false
|
autojoin: false
|
||||||
# The displayname and avatar URL to set for the bot on startup.
|
# 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.
|
# if you want the bot to handle messages that were sent while the bot was down.
|
||||||
ignore_first_sync: true
|
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:
|
server:
|
||||||
# The IP and port to listen to.
|
# The IP and port to listen to.
|
||||||
hostname: 0.0.0.0
|
hostname: 0.0.0.0
|
||||||
|
|
Loading…
Reference in a new issue