Matrix status messages from a cron job
The Wikidata cache now updates itself from cron. If an update is happening I want to get a status message about the results. The script posts into a Matrix room when it loaded something, and stays quiet when no new Wikidata dump was found and no update happened.
The bot uses my own Matrix server instance and has its own account.
Creating the room
The bot account creates the room and invites me:
room = httpx.post( f"{base}/_matrix/client/v3/createRoom", headers={"Authorization": f"Bearer {token}"}, json={ "name": "wikidata-cache", "topic": "Status of the automatic Wikidata dump loads", "preset": "private_chat", "invite": ["@me:cress.space"], }, ).json()
The response is one line:
That id is stored and used by the script to send messages later.
An alias like #wikidata-cache:cress.space has to be resolved into the id first, one request more per message.
Minting a token
The password, given by the create-user command in the conduit admin room, is only used once, to get an access token:
login = httpx.post( f"{base}/_matrix/client/v3/login", json={ "type": "m.login.password", "identifier": {"type": "m.id.user", "user": "@bot:cress.space"}, "password": password, "device_id": "wikidata-cache-cron", "initial_device_display_name": "wikidata-cache-cron", }, ).json()
device_id is fixed, so a second login replaces that device instead of registering another one.
The token, not the password, is what goes on disk.
A token can be revoked from any Matrix client without changing the account password.
It does not expire on its own either: a login that does not ask for a refresh token gets one that is valid until the device is logged out, the password changes, or someone revokes it.
Mine lives in a matrix.toml next to the script:
Sending the message
Sending is a PUT, with a transaction id in the URL that makes a repeated send idempotent:
httpx.put( f"{base}/_matrix/client/v3/rooms/{room}/send/m.room.message/{time.time_ns()}", headers={"Authorization": f"Bearer {token}"}, json={"msgtype": "m.notice", "body": text}, ).raise_for_status()
m.notice instead of m.text, so clients that mute notices can do so.
The room id goes percent-encoded into the URL: it starts with ! and holds a :, so urllib.parse.quote(room, safe="") makes %21AbCdEf...%3Acress.space of it.
Sending an image
Another cron of mine pushes a photo once a day, until now to ntfy. I moved it to Matrix so ntfy only gets alarms.
Matrix can handle images too, but it needs two calls instead of one.
The image is uploaded to the media repository first, which answers with an mxc:// URI:
MXC=$(curl -sf -X POST \ -H "Authorization: Bearer $MATRIX_TOKEN" \ -H "Content-Type: image/jpeg" \ --data-binary "@$IMAGE" \ "$MATRIX_HOMESERVER/_matrix/media/v3/upload?filename=$NAME" | jq -r .content_uri)
The media endpoint takes a POST with --data-binary.
Then the same PUT as a text message, with m.image and the URI in it:
curl -sf -X PUT \ -H "Authorization: Bearer $MATRIX_TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg url "$MXC" --arg name "$NAME" \ '{msgtype:"m.image", body:$name, url:$url, info:{mimetype:"image/jpeg"}}')" \ "$MATRIX_HOMESERVER/_matrix/client/v3/rooms/$ROOM/send/m.room.message/$(date +%s%N)"
ntfy's prio:low has no equivalent here; m.image has no notice variant, so quiet is a per-room setting in the client.