feat: Add Matrix notifications
Send Matrix alerts for updates and errors, and document the required bot credentials.
This commit is contained in:
parent
7c778e338c
commit
4ab799c156
4 changed files with 107 additions and 3 deletions
|
|
@ -7,3 +7,8 @@ GITHUB_TOKEN=optional-github-token
|
|||
# FreshRSS login for authenticated About page
|
||||
FRESHRSS_USERNAME=your-username
|
||||
FRESHRSS_PASSWORD=your-password
|
||||
|
||||
# Matrix bot notifications
|
||||
MATRIX_HOMESERVER=https://matrix.example.net
|
||||
MATRIX_ROOM_ID=!roomid:example.net
|
||||
MATRIX_ACCESS_TOKEN=replace-with-access-token
|
||||
|
|
|
|||
|
|
@ -125,3 +125,12 @@ Acceptance criteria:
|
|||
- The script can log in to FreshRSS using credentials from environment variables.
|
||||
- FreshRSS version is extracted from the About page after authentication.
|
||||
- `.env.sample` documents the FreshRSS credentials required.
|
||||
|
||||
## US-14 - Matrix Notifications
|
||||
|
||||
As a maintainer, I want the script to post update alerts to a Matrix room so that I can track service changes and failures without email.
|
||||
|
||||
Acceptance criteria:
|
||||
- The script posts a message to a Matrix room when updates are detected or errors occur.
|
||||
- Matrix credentials (homeserver, room id, access token) are read from `.env`.
|
||||
- The message includes actionable service names and version details.
|
||||
|
|
|
|||
|
|
@ -22,10 +22,15 @@ Copy `.env.sample` to `.env` and fill required values. Export the variables befo
|
|||
export PAPERLESS_API_TOKEN=...
|
||||
export FRESHRSS_USERNAME=...
|
||||
export FRESHRSS_PASSWORD=...
|
||||
export MATRIX_HOMESERVER=...
|
||||
export MATRIX_ROOM_ID=...
|
||||
export MATRIX_ACCESS_TOKEN=...
|
||||
```
|
||||
|
||||
The script also reads `.env` automatically if present.
|
||||
|
||||
The Matrix bot will attempt to join the configured room automatically if it is not already a member.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue