matrix-alertbot/matrix_alertbot/main.py

198 lines
6.8 KiB
Python
Raw Normal View History

2019-09-25 14:26:29 +02:00
#!/usr/bin/env python3
import asyncio
2022-10-26 13:25:47 +02:00
import json
2020-08-10 00:02:07 +02:00
import logging
2022-10-26 13:25:47 +02:00
import os
import sys
from asyncio import TimeoutError
2020-08-10 00:02:07 +02:00
2022-07-06 00:54:13 +02:00
from aiohttp import ClientConnectionError, ServerDisconnectedError
from diskcache import Cache
2019-09-25 14:26:29 +02:00
from nio import (
AsyncClient,
AsyncClientConfig,
InviteMemberEvent,
2022-10-26 13:25:47 +02:00
KeyVerificationCancel,
KeyVerificationKey,
KeyVerificationMac,
KeyVerificationStart,
2020-02-24 23:13:28 +01:00
LocalProtocolError,
2020-08-10 00:02:07 +02:00
LoginError,
2021-01-10 03:58:39 +01:00
MegolmEvent,
2022-07-10 14:42:23 +02:00
RedactionEvent,
2020-08-10 00:02:07 +02:00
RoomMessageText,
UnknownEvent,
2020-02-24 23:13:28 +01:00
)
2020-08-10 00:02:07 +02:00
from matrix_alertbot.alertmanager import AlertmanagerClient
from matrix_alertbot.callback import Callbacks
2022-06-13 20:55:01 +02:00
from matrix_alertbot.config import Config
from matrix_alertbot.webhook import Webhook
2019-09-25 14:26:29 +02:00
logger = logging.getLogger(__name__)
def create_matrix_client(config: Config) -> AsyncClient:
# Configuration options for the AsyncClient
2022-08-08 16:43:05 +02:00
try:
matrix_client_config = AsyncClientConfig(
max_limit_exceeded=0,
max_timeouts=0,
store_sync_tokens=True,
encryption_enabled=True,
)
except ImportWarning as e:
logger.warning(e)
matrix_client_config = AsyncClientConfig(
max_limit_exceeded=0,
max_timeouts=0,
store_sync_tokens=True,
encryption_enabled=False,
)
2022-10-26 13:25:47 +02:00
# Load credentials from a previous session
if os.path.exists(config.user_token_file):
with open(config.user_token_file, "r") as ifd:
credentials = json.load(ifd)
config.user_token = credentials["access_token"]
config.device_id = credentials["device_id"]
# Initialize the matrix client based on stored credentials
2022-08-08 00:28:36 +02:00
matrix_client = AsyncClient(
config.homeserver_url,
config.user_id,
device_id=config.device_id,
2022-07-09 12:22:05 +02:00
store_path=config.store_dir,
2022-08-08 00:28:36 +02:00
config=matrix_client_config,
)
2022-08-08 00:28:36 +02:00
return matrix_client
2022-07-08 23:04:04 +02:00
async def start_matrix_client(
2022-08-08 00:28:36 +02:00
matrix_client: AsyncClient, cache: Cache, config: Config
2022-07-08 23:04:04 +02:00
) -> bool:
# Keep trying to reconnect on failure (with some time in-between)
while True:
try:
2022-10-26 13:25:47 +02:00
if config.device_id and config.user_token:
matrix_client.restore_login(
user_id=config.user_id,
device_id=config.device_id,
access_token=config.user_token,
)
2022-07-08 23:04:04 +02:00
# Sync encryption keys with the server
2022-08-08 00:28:36 +02:00
if matrix_client.should_upload_keys:
await matrix_client.keys_upload()
2022-07-08 23:04:04 +02:00
else:
# Try to login with the configured username/password
try:
2022-08-08 00:28:36 +02:00
login_response = await matrix_client.login(
2022-07-08 23:04:04 +02:00
password=config.user_password,
device_name=config.device_name,
)
2022-07-08 23:04:04 +02:00
# Check if login failed
if type(login_response) == LoginError:
logger.error("Failed to login: %s", login_response.message)
return False
except LocalProtocolError as e:
# There's an edge case here where the user hasn't installed the correct C
# dependencies. In that case, a LocalProtocolError is raised on login.
logger.fatal(
"Failed to login. Have you installed the correct dependencies? "
"https://github.com/poljar/matrix-nio#installation "
"Error: %s",
e,
)
return False
2022-10-26 13:25:47 +02:00
# Save user's access token and device ID
# See https://stackoverflow.com/a/45368120
user_token_fd = os.open(
config.user_token_file,
flags=os.O_CREAT | os.O_WRONLY | os.O_TRUNC,
mode=0o640,
)
with os.fdopen(user_token_fd, "w") as ofd:
2022-10-26 13:25:47 +02:00
json.dump(
{
"device_id": login_response.device_id,
"access_token": login_response.access_token,
},
ofd,
)
2022-07-08 23:04:04 +02:00
# Login succeeded!
2022-07-08 23:04:04 +02:00
logger.info(f"Logged in as {config.user_id}")
2022-08-08 00:28:36 +02:00
await matrix_client.sync_forever(timeout=30000, full_state=True)
2022-07-08 23:04:04 +02:00
except (ClientConnectionError, ServerDisconnectedError, TimeoutError):
logger.warning("Unable to connect to homeserver, retrying in 15s...")
2022-07-08 23:04:04 +02:00
# Sleep so we don't bombard the server with login requests
await asyncio.sleep(15)
2022-07-08 23:04:04 +02:00
finally:
2022-08-08 00:28:36 +02:00
await matrix_client.close()
def main() -> None:
"""The first function that is run when starting the bot"""
# Read user-configured options from a config file.
# A different config file path can be specified as the first command line argument
if len(sys.argv) > 1:
config_path = sys.argv[1]
else:
config_path = "config.yaml"
# Read the parsed config file and create a Config object
config = Config(config_path)
2022-08-08 00:28:36 +02:00
matrix_client = create_matrix_client(config)
2022-07-08 23:04:04 +02:00
# Configure the cache
cache = Cache(config.cache_dir)
2022-07-08 23:04:04 +02:00
# Configure Alertmanager client
2022-08-08 00:28:36 +02:00
alertmanager_client = AlertmanagerClient(config.alertmanager_url, cache)
2022-07-08 23:04:04 +02:00
# Set up event callbacks
2022-08-08 00:28:36 +02:00
callbacks = Callbacks(matrix_client, alertmanager_client, cache, config)
matrix_client.add_event_callback(callbacks.message, (RoomMessageText,))
matrix_client.add_event_callback(
2022-07-08 23:04:04 +02:00
callbacks.invite_event_filtered_callback, (InviteMemberEvent,)
)
2022-08-08 00:28:36 +02:00
matrix_client.add_event_callback(callbacks.decryption_failure, (MegolmEvent,))
matrix_client.add_event_callback(callbacks.unknown, (UnknownEvent,))
matrix_client.add_event_callback(callbacks.redaction, (RedactionEvent,))
2022-10-26 13:25:47 +02:00
matrix_client.add_to_device_callback(
callbacks.key_verification_start, (KeyVerificationStart,)
)
matrix_client.add_to_device_callback(
callbacks.key_verification_cancel, (KeyVerificationCancel,)
)
matrix_client.add_to_device_callback(
callbacks.key_verification_confirm, (KeyVerificationKey,)
2022-10-26 13:25:47 +02:00
)
matrix_client.add_to_device_callback(
callbacks.key_verification_end, (KeyVerificationMac,)
)
2022-07-08 23:04:04 +02:00
# Configure webhook server
2022-08-08 00:28:36 +02:00
webhook_server = Webhook(matrix_client, alertmanager_client, cache, config)
2022-07-08 23:04:04 +02:00
loop = asyncio.get_event_loop()
2022-07-09 12:31:05 +02:00
loop.create_task(webhook_server.start())
2022-08-08 00:28:36 +02:00
loop.create_task(start_matrix_client(matrix_client, cache, config))
try:
loop.run_forever()
except Exception as e:
logger.error(e)
2022-07-08 23:04:04 +02:00
finally:
loop.run_until_complete(webhook_server.close())
2022-08-08 00:28:36 +02:00
loop.run_until_complete(alertmanager_client.close())
loop.run_until_complete(matrix_client.close())
2022-07-10 18:09:25 +02:00
cache.close()