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, andaccess_tokenfrom a logged-in nioAsyncClientto 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 bysetup_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.
- 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 callclient.restore_logininstead of logging in with a password. ReturnsNonewhen 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.
- platforms.matrix.trust_all_devices(client)[source]
Mark every known device of every tracked user as trusted.
Walks the nio client’s
device_storeand callsverify_deviceon 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) andMatrixPlatform._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:
- 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.
- 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:
PlatformAdapterPlatform adapter for Matrix via matrix-nio.
- Parameters:
message_handler (
Callable[[IncomingMessage,PlatformAdapter],Awaitable[None]]) – Async callback that receivesIncomingMessageinstances.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
AsyncClientis created and connected later instart(). Also forwardsmessage_handlertoPlatformAdapterand sets up the in-memory maps the adapter relies on while running:_sent_eventsand_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 decodedIncomingMessageand 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.nameproperty. The value is used as the registry key and routing/label tag for the adapter:gateway_mainreads it when wiring platforms,background_taskskeys itsadapter_mapand channel catalog by it, andmessage_processor.channel_heartbeatuses it for log labels. Constant with no side effects.- Returns:
The literal platform name
"matrix".- Return type:
- property is_running: bool
Report whether the background Matrix sync loop is active.
Implements the abstract
PlatformAdapter.is_runningproperty by checking that the_sync_taskcreated instart()exists and has not finished. Used as a guard instart()andstop()and read externally by the web admin surfaces (web/bot_admin.py,web/platforms_api.py,web/deps.py) andbackground_tasksto decide whether the platform is live. Read-only with no side effects.- Returns:
Truewhile the sync task is running,Falseotherwise.- Return type:
- property bot_identity: dict[str, str]
Describe this bot’s Matrix identity for the pipeline.
Implements the abstract
PlatformAdapter.bot_identityproperty. Prefers the liveuser_idfrom the connected nioAsyncClient, falling back to the configuredself._user_idbefore login, and uses the cachedself._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 gathersbot_identityfor every adapter),message_processor.user_message_format,message_processor.history_backfill,background_tasks, andweb/bot_admin.pyto label and attribute the bot’s own messages.
- 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 nioAsyncClient, then either restores a saved session (viaload_matrix_credentials()/restore_login) or logs in with the configured password and persists the new session throughsave_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:
- async stop()[source]
Cancel the sync loop and close the Matrix client connection.
Implements the abstract
PlatformAdapter.stop()lifecycle hook. Sets the_stop_eventso_sync_loop()will exit, cancels and awaits the_sync_task, then closes the nioAsyncClientand clears the references sois_runningreports 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 byweb/platforms_apiwhen an operator stops a platform from the admin UI.- Return type:
- async should_skip_channel_heartbeat(channel_id)[source]
Decide whether to suppress the periodic heartbeat for a room.
Implements the
PlatformAdapterhook consulted bymessage_processor.channel_heartbeatandcore.outbound_consumerbefore they run an unsolicited background heartbeat. Looks the room up in the connected nio client’sroomsmap 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. ReturnsFalse(do not skip) when the client is not connected or the room is unknown.
- 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 HTMLformatted_body(_markdown_to_html()), linkifies bare mentions intomatrix.toanchors and collects them intom.mentions(_linkify_matrix_mentions()), and derives the plainbody(_strip_html()). Best-effort enriches the payload with the current sprite/character state read from Redis (keysstar:sprite:state:matrix:<channel>andstar:sprite:chars:matrix:<channel>) under ansg.spritefield for per-message replay, swallowing any Redis error. Sends via the nio client’sroom_send(network I/O) and records the resulting event ID in_sent_eventsso 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 thetask_manager– which callplatform.sendon the resolved adapter.
- async add_reaction(channel_id, message_id, emoji)[source]
Add an
m.reactionannotation to a Matrix event.Gateway side of the outbound
type: "reaction"relay. Sends anm.reactionevent relating to message_id (the target event id). Errors are logged, not raised.
- 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’supload(encrypting the blob when the target room is encrypted), picks themsgtype(m.image/m.audio/m.video/m.file) from the MIME type, builds the message content with the returnedmxc://URI (or encryption keys for encrypted rooms), and sends it withroom_send. Records the event ID in_sent_eventslikesend()so replies count as addressed. Performs network I/O; logs and returnsNoneon failure instead of raising.Driven by the outbound media path –
core.outbound_consumerand the media-producingtools(image/video/audio generators) andbackground_agents– which calladapter.send_fileon the resolved adapter.- Parameters:
- Returns:
The
mxc://content URI on success, orNoneif the client is not connected or the upload/send fails.- Return type:
- 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’sroom_typingwith a 30 s timeout and re-asserts it every 25 s, since Matrix typing notices expire. The task handle is stored in_typing_taskskeyed by room sostop_typing()can cancel it. No-op when the client is not connected.Driven by the response pipeline –
message_processor.processorandcore.outbound_consumer– which callstart_typingwhile a reply is being generated.
- 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 bystart_typing()from_typing_tasks, then explicitly tells the homeserver the bot is no longer typing via the nio client’sroom_typingwithtyping_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.processorandcore.outbound_consumer– once a reply is sent; also called at the top ofstart_typing()to reset state.
- async is_channel_valid(channel_id)[source]
Return
Trueif 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-memoryroomsmap, so callers can confirm a target room is reachable before sending. ReturnsFalsewhen the client is not connected. Read-only with no side effects.
- 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.
- async fetch_history(channel_id, limit=100)[source]
Fetch recent room history as normalized
HistoricalMessageitems.Implements the abstract
PlatformAdapter.fetch_history()hook. Pages backward from the client’s currentnext_batchtoken via the nio client’sroom_messages(a homeserver HTTP request, filtered tom.room.messageevents), then maps each text or media event into aHistoricalMessage– 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 thecross_channel_query/admin_whispertools – which callfetch_historyon the resolved adapter.- Parameters:
- Returns:
The fetched messages in chronological order, or an empty list if the client is disconnected or the request fails.
- Return type:
- 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_eventslets_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.