platforms.matrix module

Matrix platform adapter using matrix-nio.

Wraps the matrix-nio AsyncClient and converts Matrix events into IncomingMessage instances for the shared MessageProcessor.

async platforms.matrix.download_matrix_media(client, event)[source]

Download (and optionally decrypt) a media attachment from Matrix.

Return type:

tuple[bytes, str, str]

Parameters:
  • client (AsyncClient)

  • event (RoomMessageMedia | RoomEncryptedMedia)

async platforms.matrix.save_matrix_credentials(credentials_file, homeserver, client)[source]

Persist Matrix login credentials, preserving extra keys like seeds.

Writes the homeserver URL plus the live user_id, device_id, and access_token from a logged-in nio AsyncClient to the JSON credentials file so the session can be restored on the next start without re-authenticating. Reads any existing file first (via aiofiles) and merges on top of it, so unrelated keys such as the cross-signing seeds written by setup_cross_signing() survive. Touches the filesystem and logs an info line on success.

Called by MatrixPlatform.start() within this module right after a fresh password login succeeds; it has no external callers.

Parameters:
  • credentials_file (str) – Path to the JSON credentials file to write.

  • homeserver (str) – The Matrix homeserver URL to record.

  • client (AsyncClient) – The logged-in nio client supplying the identity and access token.

Return type:

None

async platforms.matrix.load_matrix_credentials(credentials_file)[source]

Load previously saved Matrix credentials, or return None.

Reads and parses the JSON credentials file (via aiofiles) that save_matrix_credentials() writes, so a restart can call client.restore_login instead of logging in with a password. Returns None when the file is absent or contains invalid JSON, signaling the caller to fall back to a fresh login. Read-only filesystem access with no other side effects.

Called by MatrixPlatform.start() within this module to recover a saved session; it has no external callers.

Parameters:

credentials_file (str) – Path to the JSON credentials file to read.

Returns:

The decoded credentials mapping, or None if the file is missing or unparseable.

Return type:

dict | None

platforms.matrix.trust_all_devices(client)[source]

Mark every known device of every tracked user as trusted.

Walks the nio client’s device_store and calls verify_device on any device the Olm layer does not already consider verified, so the bot will encrypt to and accept messages from all participants without manual verification. This is a deliberate trust-on-first-use policy for an unattended bot; it mutates the client’s in-memory device-trust state and logs each newly trusted device at debug level.

Called by MatrixPlatform.start() (after the initial sync) and MatrixPlatform._sync_loop() (after every successful sync) within this module; it has no external callers.

Parameters:

client (AsyncClient) – The connected nio client whose device store is scanned and whose devices are verified in place.

Return type:

None

async platforms.matrix.setup_cross_signing(client, password, credentials_file, saved_seeds=None)[source]

Generate cross-signing keys, upload them, and self-sign the device.

If saved_seeds is provided the keys are re-derived from persisted seeds instead of generating new ones. Seeds are persisted to credentials_file for future restarts.

Return type:

None

Parameters:
  • client (nio.AsyncClient)

  • password (str)

  • credentials_file (str)

  • saved_seeds (dict[str, str] | None)

class platforms.matrix.MatrixPlatform(message_handler, *, homeserver, user_id, password='', store_path='nio_store', credentials_file='matrix_credentials.json', media_cache=None, config=None)[source]

Bases: PlatformAdapter

Platform adapter for Matrix via matrix-nio.

Parameters:
  • message_handler (Callable[[IncomingMessage, PlatformAdapter], Awaitable[None]]) – Async callback that receives IncomingMessage instances.

  • homeserver (str) – Matrix homeserver URL.

  • user_id (str) – Matrix user ID for the bot.

  • password (str) – Password (only needed for first login).

  • store_path (str) – Path to the nio E2EE key store.

  • credentials_file (str) – Path to the JSON file for persisting login credentials.

  • media_cache (MediaCache | None)

  • config (Config | None)

__init__(message_handler, *, homeserver, user_id, password='', store_path='nio_store', credentials_file='matrix_credentials.json', media_cache=None, config=None)[source]

Store configuration and initialize per-room bookkeeping state.

Records the homeserver, identity, and E2EE store/credentials paths but does no network I/O here; the nio AsyncClient is created and connected later in start(). Also forwards message_handler to PlatformAdapter and sets up the in-memory maps the adapter relies on while running: _sent_events and _egregore_sent_events (so replies to the bot’s own or ghost messages count as addressed in _is_addressed()), _typing_tasks (background typing loops), and _in_room_sas (active in-room SAS verifications). No external side effects beyond constructing this object.

Constructed by the platform factory / gateway wiring that instantiates each adapter from its platform config.

Parameters:
  • message_handler (Callable[[IncomingMessage, PlatformAdapter], Awaitable[None]]) – Async callback invoked with each decoded IncomingMessage and this adapter.

  • homeserver (str) – Matrix homeserver URL to connect to.

  • user_id (str) – The bot’s Matrix user ID.

  • password (str) – Login password, only needed for the first login.

  • store_path (str) – Directory for the nio E2EE key store.

  • credentials_file (str) – Path to the JSON file persisting login credentials and cross-signing seeds.

  • media_cache (MediaCache | None) – Optional shared cache used to avoid re-downloading the same media.

  • config (Config | None) – Optional runtime config controlling emoji resolution and media-download retries.

Return type:

None

property name: str

Return this adapter’s stable platform identifier, "matrix".

Implements the abstract PlatformAdapter.name property. The value is used as the registry key and routing/label tag for the adapter: gateway_main reads it when wiring platforms, background_tasks keys its adapter_map and channel catalog by it, and message_processor.channel_heartbeat uses it for log labels. Constant with no side effects.

Returns:

The literal platform name "matrix".

Return type:

str

property is_running: bool

Report whether the background Matrix sync loop is active.

Implements the abstract PlatformAdapter.is_running property by checking that the _sync_task created in start() exists and has not finished. Used as a guard in start() and stop() and read externally by the web admin surfaces (web/bot_admin.py, web/platforms_api.py, web/deps.py) and background_tasks to decide whether the platform is live. Read-only with no side effects.

Returns:

True while the sync task is running, False otherwise.

Return type:

bool

property bot_identity: dict[str, str]

Describe this bot’s Matrix identity for the pipeline.

Implements the abstract PlatformAdapter.bot_identity property. Prefers the live user_id from the connected nio AsyncClient, falling back to the configured self._user_id before login, and uses the cached self._profile_display_name (populated by _refresh_self_display_name()) for the display name, falling back to the user ID when no profile name is known. On Matrix the user ID itself doubles as the mention token.

Read across adapters by prompt_context (which gathers bot_identity for every adapter), message_processor.user_message_format, message_processor.history_backfill, background_tasks, and web/bot_admin.py to label and attribute the bot’s own messages.

Returns:

A mapping with platform, user_id, display_name, and mention keys describing this bot.

Return type:

dict[str, str]

async start()[source]

Connect to Matrix, perform the initial sync, and launch the sync loop.

Implements the abstract PlatformAdapter.start() lifecycle hook. Creates the E2EE-enabled nio AsyncClient, then either restores a saved session (via load_matrix_credentials() / restore_login) or logs in with the configured password and persists the new session through save_matrix_credentials(). Registers all event callbacks (_register_callbacks()), runs a full initial sync, trusts known devices (trust_all_devices()), uploads/queries/claims E2EE keys, refreshes the profile name (_refresh_self_display_name()), runs cross-signing setup (setup_cross_signing()), and finally spawns the background _sync_loop() task. Touches the homeserver over HTTP, the local E2EE store directory and credentials file, and the asyncio loop.

Invoked by the gateway service (gateway_main) when it brings each configured platform adapter online.

Raises:

RuntimeError – If there are no saved credentials and no password to log in with, or if the login attempt is rejected.

Return type:

None

async stop()[source]

Cancel the sync loop and close the Matrix client connection.

Implements the abstract PlatformAdapter.stop() lifecycle hook. Sets the _stop_event so _sync_loop() will exit, cancels and awaits the _sync_task, then closes the nio AsyncClient and clears the references so is_running reports false. Releases the network connection and the asyncio task; logs when fully stopped. Safe to call when not running (returns immediately).

Invoked by the gateway service (gateway_main) during shutdown and by web/platforms_api when an operator stops a platform from the admin UI.

Return type:

None

async should_skip_channel_heartbeat(channel_id)[source]

Decide whether to suppress the periodic heartbeat for a room.

Implements the PlatformAdapter hook consulted by message_processor.channel_heartbeat and core.outbound_consumer before they run an unsolicited background heartbeat. Looks the room up in the connected nio client’s rooms map and reports that the heartbeat should be skipped for direct messages and other rooms with two or fewer members, since unprompted activity there would feel intrusive. Returns False (do not skip) when the client is not connected or the room is unknown.

Parameters:

channel_id (str) – The Matrix room ID to evaluate.

Returns:

True if the heartbeat should be skipped for this room, False otherwise.

Return type:

bool

async send(channel_id, text)[source]

Render and send a text message to a Matrix room, returning its event ID.

Implements the abstract PlatformAdapter.send() outbound hook. Converts the text to an HTML formatted_body (_markdown_to_html()), linkifies bare mentions into matrix.to anchors and collects them into m.mentions (_linkify_matrix_mentions()), and derives the plain body (_strip_html()). Best-effort enriches the payload with the current sprite/character state read from Redis (keys star:sprite:state:matrix:<channel> and star:sprite:chars:matrix:<channel>) under an sg.sprite field for per-message replay, swallowing any Redis error. Sends via the nio client’s room_send (network I/O) and records the resulting event ID in _sent_events so replies to it are treated as addressed by _is_addressed().

Driven by the outbound side of the pipeline – message_processor (generate_and_send, command_router, processor) and the task_manager – which call platform.send on the resolved adapter.

Parameters:
  • channel_id (str) – The Matrix room ID to post into.

  • text (str) – The Markdown message text to render and send.

Returns:

The sent event ID, or an empty string if the client is not connected or the send fails.

Return type:

str

async add_reaction(channel_id, message_id, emoji)[source]

Add an m.reaction annotation to a Matrix event.

Gateway side of the outbound type: "reaction" relay. Sends an m.reaction event relating to message_id (the target event id). Errors are logged, not raised.

Return type:

None

Parameters:
  • channel_id (str)

  • message_id (str)

  • emoji (str)

async send_file(channel_id, data, filename, mimetype='application/octet-stream')[source]

Upload a file to Matrix and post it into a room as a media message.

Implements the abstract PlatformAdapter.send_file() outbound hook. Uploads the bytes via the nio client’s upload (encrypting the blob when the target room is encrypted), picks the msgtype (m.image / m.audio / m.video / m.file) from the MIME type, builds the message content with the returned mxc:// URI (or encryption keys for encrypted rooms), and sends it with room_send. Records the event ID in _sent_events like send() so replies count as addressed. Performs network I/O; logs and returns None on failure instead of raising.

Driven by the outbound media path – core.outbound_consumer and the media-producing tools (image/video/audio generators) and background_agents – which call adapter.send_file on the resolved adapter.

Parameters:
  • channel_id (str) – The Matrix room ID to post into.

  • data (bytes) – The raw file contents to upload.

  • filename (str) – The display/body filename for the attachment.

  • mimetype (str) – The MIME type of the file.

Returns:

The mxc:// content URI on success, or None if the client is not connected or the upload/send fails.

Return type:

str | None

async start_typing(channel_id)[source]

Begin showing the bot’s typing indicator in a room until cleared.

Implements the abstract PlatformAdapter.start_typing() hook. First clears any existing indicator for the room (stop_typing()), then spawns a background task (the nested _typing_loop) that repeatedly calls the nio client’s room_typing with a 30 s timeout and re-asserts it every 25 s, since Matrix typing notices expire. The task handle is stored in _typing_tasks keyed by room so stop_typing() can cancel it. No-op when the client is not connected.

Driven by the response pipeline – message_processor.processor and core.outbound_consumer – which call start_typing while a reply is being generated.

Parameters:

channel_id (str) – The Matrix room ID to show typing in.

Return type:

None

async stop_typing(channel_id)[source]

Cancel the typing-indicator loop for a room and clear the indicator.

Implements the abstract PlatformAdapter.stop_typing() hook. Pops and cancels the background task started by start_typing() from _typing_tasks, then explicitly tells the homeserver the bot is no longer typing via the nio client’s room_typing with typing_state=False (logging at debug level if that call fails). Performs network I/O; safe to call when nothing is typing.

Driven by the response pipeline – message_processor.processor and core.outbound_consumer – once a reply is sent; also called at the top of start_typing() to reset state.

Parameters:

channel_id (str) – The Matrix room ID to stop the typing indicator in.

Return type:

None

async is_channel_valid(channel_id)[source]

Return True if the room is one the bot is currently joined to.

Implements the abstract PlatformAdapter.is_channel_valid() hook by checking the channel ID against the connected nio client’s in-memory rooms map, so callers can confirm a target room is reachable before sending. Returns False when the client is not connected. Read-only with no side effects.

Parameters:

channel_id (str) – The Matrix room ID to validate.

Returns:

True if the room is known/joined, False otherwise.

Return type:

bool

async list_servers_and_channels()[source]

Return all Matrix rooms the bot is in.

Matrix doesn’t have a guild/server hierarchy in the same way Discord does — each room is listed as a standalone entry.

Return type:

list[dict[str, Any]]

async fetch_history(channel_id, limit=100)[source]

Fetch recent room history as normalized HistoricalMessage items.

Implements the abstract PlatformAdapter.fetch_history() hook. Pages backward from the client’s current next_batch token via the nio client’s room_messages (a homeserver HTTP request, filtered to m.room.message events), then maps each text or media event into a HistoricalMessage – rendering media as [Image] / [Video] / [Audio] / [File] placeholders, resolving sender display names, flagging the bot’s own messages, and capturing reply targets via _get_reply_to_id(). Results are reversed into chronological order. Performs network I/O; on error or a non-success response it logs at debug level and returns an empty list.

Driven by history-backfill and cross-channel features – background_tasks, message_processor.history_backfill, core.outbound_consumer, and the cross_channel_query / admin_whisper tools – which call fetch_history on the resolved adapter.

Parameters:
  • channel_id (str) – The Matrix room ID to read history from.

  • limit (int) – Maximum number of messages to fetch.

Returns:

The fetched messages in chronological order, or an empty list if the client is disconnected or the request fails.

Return type:

list[HistoricalMessage]

register_egregore_event_id(room_id, event_id)[source]

Register an egregore ghost message so replies to it address the bot.

Egregore “ghost” messages are posted under application-service puppet identities rather than the bot’s own user, so a reply to one would not otherwise be recognized. Recording the event ID in _egregore_sent_events lets _is_addressed() treat replies to that message as directed at the bot. Mutates in-memory state only; no-ops on empty inputs.

Called by the egregore/ghost-addressing layer after it sends a ghost message; exercised by tests/test_egregore_addressing.py.

Parameters:
  • room_id (str) – The Matrix room the ghost message was sent in.

  • event_id (str) – The event ID of the ghost message to track.

Return type:

None