feat: Add Matrix notifications

Send Matrix alerts for updates and errors, and document the required bot credentials.
This commit is contained in:
Jeena 2026-03-12 15:23:45 +00:00
parent 7c778e338c
commit 4ab799c156
4 changed files with 107 additions and 3 deletions

View file

@ -4,10 +4,11 @@ import json
import os
import re
import sys
import time
from dataclasses import dataclass
from typing import Any, Dict, Optional
from http.cookiejar import CookieJar
from urllib.parse import urlencode
from urllib.parse import urlencode, quote
from urllib.request import HTTPCookieProcessor, Request, build_opener, urlopen
import bcrypt
@ -483,6 +484,87 @@ def check_service(service: ServiceConfig, timeout: float, user_agent: str) -> Di
return result
def build_summary(results: list[Dict[str, Any]]) -> tuple[str, bool]:
updates = []
errors = []
for result in results:
comparison = compare_versions(result["current"], result["latest"])
if comparison == 1:
updates.append(result)
if result["current_error"] or result["latest_error"]:
errors.append(result)
lines = []
if updates:
lines.append("Updates available:")
for result in updates:
current = result["current"] or "unknown"
latest = result["latest"] or "unknown"
lines.append(f"- {result['name']}: {current} -> {latest}")
if errors:
lines.append("Errors:")
for result in errors:
if result["current_error"]:
lines.append(f"- {result['name']}: current version error")
if result["latest_error"]:
lines.append(f"- {result['name']}: latest version error")
if not lines:
return "All services up to date.", False
return "\n".join(lines), True
def send_matrix_message(message: str, timeout: float) -> None:
homeserver = os.getenv("MATRIX_HOMESERVER")
room_id = os.getenv("MATRIX_ROOM_ID")
token = os.getenv("MATRIX_ACCESS_TOKEN")
if not homeserver or not room_id or not token:
return
join_matrix_room(homeserver, room_id, token, timeout)
txn_id = str(int(time.time() * 1000))
url = f"{homeserver}/_matrix/client/v3/rooms/{room_id}/send/m.room.message/{txn_id}"
payload = json.dumps({
"msgtype": "m.text",
"body": message,
}).encode("utf-8")
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
try:
fetch_response(
url,
timeout=timeout,
user_agent="check-for-updates",
extra_headers=headers,
data=payload,
method="PUT",
)
except Exception as exc:
print(f"Matrix notification failed: {exc}", file=sys.stderr)
def join_matrix_room(homeserver: str, room_id: str, token: str, timeout: float) -> None:
encoded_room = quote(room_id, safe="")
url = f"{homeserver}/_matrix/client/v3/join/{encoded_room}"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
try:
fetch_response(
url,
timeout=timeout,
user_agent="check-for-updates",
extra_headers=headers,
data=b"{}",
method="POST",
)
except Exception:
return
def main() -> int:
parser = argparse.ArgumentParser(description="Check for webservice updates")
parser.add_argument("--config", default="services.yaml", help="Path to services YAML")
@ -512,7 +594,6 @@ def main() -> int:
continue
current = result["current"] or "unknown"
latest = result["latest"] or "unknown"
upstream = result["upstream_url"] or "unknown"
notes = []
if comparison is None and result["current"] and result["latest"]:
notes.append("unparseable")
@ -521,7 +602,7 @@ def main() -> int:
if result["latest_error"]:
notes.append("latest error")
suffix = f" [{' '.join(notes)}]" if notes else ""
output_lines.append(f"{result['name']}: {current} -> {latest} ({upstream}){suffix}")
output_lines.append(f"{result['name']}: {current} -> {latest}{suffix}")
if output_lines:
print("\n".join(output_lines))
@ -537,6 +618,10 @@ def main() -> int:
f"{result['name']}: latest version error: {result['latest_error']}",
file=sys.stderr,
)
summary, should_notify = build_summary(results)
if should_notify:
send_matrix_message(summary, args.timeout)
return 0