egregore_bridge
EGREGORE BRIDGE – Matrix Application Service ghost user manager. Egregore VN Engine by Sigma / Stargazer / Vivian The Loopmother
Creates, manages, and controls ghost Matrix users for summoned egregores. Each egregore gets a Matrix identity like @_egregore_orion:secure-channel.net with their own display name, avatar, and ability to send messages.
Uses the AS (Application Service) token to act on behalf of ghost users via the Synapse Client-Server API.
@fire @skull THE WITCH GIVES HER DOLLS VOICES.
- egregore_bridge.ghost_user_id(name)[source]
Build the fully qualified Matrix user ID for an egregore ghost.
Lowercases the egregore name and replaces spaces and hyphens with underscores, then wraps it with the
EGREGORE_PREFIXlocalpart prefix and the module-levelSERVER_NAMEdomain to produce an identity like@_egregore_orion:secure-channel.net. This is the canonical way the bridge addresses a ghost, so the same name always maps to the same Matrix user.A pure string helper that touches no I/O. Called throughout
EgregoreBridge(inregister_ghost(),set_display_name(),set_avatar(),upload_avatar_from_disk(),join_room(),leave_room(),send_message(), andsend_emote()) and also directly bymessage_processor/generate_and_send.pywhen it records the ghostuidfor an interleaved egregore turn.
- egregore_bridge.ghost_localpart(name)[source]
Build the Matrix localpart (username without the domain) for a ghost.
Mirrors
ghost_user_id()but stops before the server suffix: lowercases the name, swaps spaces and hyphens for underscores, and prependsEGREGORE_PREFIXto yield a value like_egregore_orion. The localpart is what Synapse’s registration endpoint expects in itsusernamefield.A pure string helper with no I/O. Called by
EgregoreBridge.register_ghost()to derive theusernameit posts to/_matrix/client/v3/register; no other repo callers were found.
- class egregore_bridge.EgregoreBridge(homeserver_url='https://secure-channel.net', as_token='ca6d2f1fd1c97dad285c328d333d9552f60bc5b5e0e16b48f839bcf24a043c73c', server_name='secure-channel.net')[source]
Bases:
objectManages egregore ghost users on Matrix via the Application Service API.
All methods use the AS token to impersonate ghost users through Synapse’s /_matrix/client/v3/ endpoints with ?user_id= parameter.
- __init__(homeserver_url='https://secure-channel.net', as_token='ca6d2f1fd1c97dad285c328d333d9552f60bc5b5e0e16b48f839bcf24a043c73c', server_name='secure-channel.net')[source]
Initialise the bridge with Synapse connection settings.
Stores the homeserver URL, Application Service token, and server name used to build ghost user IDs and authenticate every request. Defaults are pulled from the module-level config constants (
HOMESERVER_URL/AS_TOKEN/SERVER_NAME), which themselves read from environment variables. Theaiohttpsession is created lazily on first use, and_registeredcaches which ghost user IDs have already been registered this process to keep registration idempotent and cheap.This is invoked once by
get_bridge(), which constructs the module-level singleton; callers such astools/summon_egregore.py,tools/compose_scene.py,tools/dismiss_egregore.py, andmessage_processor/generate_and_send.pyreach the bridge through that singleton rather than instantiating it directly.- Parameters:
homeserver_url (
str) – Base URL of the Synapse homeserver; a trailing slash is stripped so paths can be concatenated cleanly.as_token (
str) – Application Service bearer token used in theAuthorizationheader to impersonate ghost users.server_name (
str) – Matrix server name (domain) used to form fully qualified ghost user IDs like@_egregore_x:server.
- Return type:
None
- async close()[source]
Close the underlying HTTP session and release its connections.
Shuts down the shared
aiohttp.ClientSessioncreated by_get_session()if one is open, freeing the connection pool. A subsequent call to_get_session()will transparently create a fresh session, so the bridge remains usable after closing.No internal callers were found; this is a cleanup hook for shutdown code that owns the bridge singleton (e.g. service teardown).
- Return type:
- async register_ghost(name)[source]
Register an egregore ghost user with Synapse, idempotently.
Derives the localpart and full user ID via
ghost_localpart()andghost_user_id(), then short-circuits if the ID is already in the in-memory_registeredcache. Otherwise it POSTs anm.login.application_serviceregistration through_api()to/_matrix/client/v3/register. Both a200success and a400with errcodeM_USER_IN_USEare treated as success and added to the cache, so re-registering an existing ghost is cheap and safe; any other status is logged as an error and reports failure. Logs an info line on first registration and a debug line when the user already existed.Called by
ensure_ghost()as the first step of fully provisioning a ghost;ensure_ghostin turn is invoked fromtools/summon_egregore.py.
- async set_display_name(name, display_name)[source]
Set the Matrix profile display name for a ghost user.
Resolves the ghost’s user ID with
ghost_user_id()and PUTs the newdisplaynameto/_matrix/client/v3/profile/{user_id}/displaynamethrough_api(), impersonating the ghost via theas_userparameter so the change is attributed to that user rather than the Application Service.Called by
ensure_ghost(), which passes either an explicit display name or a title-cased form of the egregore name; no direct external callers were found.
- async set_avatar(name, mxc_uri)[source]
Point a ghost user’s profile avatar at an already-uploaded MXC asset.
Resolves the ghost’s user ID via
ghost_user_id()and PUTs theavatar_urlto/_matrix/client/v3/profile/{user_id}/avatar_urlthrough_api(), impersonating the ghost so the avatar belongs to it. The caller must supply a Matrix content URI (mxc://...), such as the one returned byupload_avatar_from_disk(); this method does no uploading itself.Called by
ensure_ghost()immediately after a successful avatar upload; no direct external callers were found.
- async upload_avatar_from_disk(name)[source]
Find an egregore’s avatar image on disk and upload it to Synapse media.
Canonicalises the name through
egregore_tag_parser.normalize_egregore_name()so variant spellings collapse to one asset stem, then searches the filesystem underEGREGORE_ASSETS_DIR: curated images in thecradle_synthesis/folder are preferred, falling back to the egregore’s own folder, trying several filename and extension variants. When a file is found it is read as bytes and POSTed to/_matrix/media/v3/uploadusing the pooled_get_session()with animage/pngcontent type and the ghost’suser_idquery, and the returnedcontent_uriis handed back. Reads from the local filesystem and performs network I/O; missing files, non-200responses, and exceptions are logged and yieldNonerather than raising.Called by
ensure_ghost(), whose result is fed straight intoset_avatar(); no direct external callers were found.
- async ensure_ghost(name, display_name=None)[source]
Fully provision an egregore ghost: register, name, and avatar it.
The high-level summoning entry point: it calls
register_ghost()(bailing out withFalseif registration fails), derives a display name fromdisplay_nameor a title-cased form ofnameand applies it viaset_display_name(), then attemptsupload_avatar_from_disk()and, when that yields an MXC URI,set_avatar(). The avatar steps are best-effort – a missing avatar does not fail the call. Net effect is a ready-to-use ghost on Synapse with a profile and (usually) a picture.Called by
tools/summon_egregore.pythrough the module-levelget_bridge()singleton when the bot summons an egregore.- Parameters:
- Returns:
Trueonce the ghost is registered and named (regardless of whether an avatar was found),Falseif registration failed.- Return type:
- async join_room(name, room_id)[source]
Join an egregore ghost user into a Matrix room.
Resolves the ghost’s user ID via
ghost_user_id()and POSTs to/_matrix/client/v3/join/{room_id}through_api(), impersonating the ghost so it appears as a member of the room and can subsequently speak there. Logs an info line on success and an error line on failure.Called by
tools/summon_egregore.py(via theget_bridge()singleton) so a freshly summoned egregore joins the active room.
- async leave_room(name, room_id)[source]
Remove an egregore ghost user from a Matrix room.
Resolves the ghost’s user ID via
ghost_user_id()and POSTs to/_matrix/client/v3/rooms/{room_id}/leavethrough_api(), impersonating the ghost so it stops being a room member. Logs an info line on success; a failed leave is only logged at warning level, since a ghost that is already absent is not a hard error.Called by
tools/dismiss_egregore.py(via theget_bridge()singleton) when an egregore is banished from the room.
- async send_message(name, room_id, text, html=None)[source]
Send a text (optionally HTML-formatted) message as a ghost user.
Resolves the ghost via
ghost_user_id(), bumps the module-level_txn_counterto mint a unique transaction ID (avoiding txn_id collisions in tight summoning loops), and builds anm.room.messagecontent of typem.text. Whenhtmlis supplied it adds theorg.matrix.custom.htmlformatandformatted_bodyfields. The content is PUT to/_matrix/client/v3/rooms/{room_id}/send/m.room.message/{txn_id}through_api(), impersonating the ghost so the message is attributed to the egregore. Logs the resulting event ID on success or an error on failure. Mutates the global_txn_counteras a side effect.Invoked dynamically by the multi-voice interleaving path in
message_processor/generate_and_send.py, which dispatches each parsed egregore segment to its own ghost through theget_bridge()singleton.- Parameters:
- Returns:
The Matrix
event_idon success, orNoneon failure.- Return type:
- async send_emote(name, room_id, text)[source]
Send an emote (an italic
/meaction line) as a ghost user.Resolves the ghost via
ghost_user_id(), bumps the module-level_txn_counterfor a collision-free transaction ID, and PUTs anm.room.messageof msgtypem.emoteto/_matrix/client/v3/rooms/{room_id}/send/m.room.message/{txn_id}through_api(), impersonating the ghost. This is the action-text sibling ofsend_message()and mutates the global_txn_counteras a side effect. Unlikesend_message()it logs nothing.No callers were found in the repo; this is a helper available to egregore dispatch code that wants to emit
/me-style actions.
- egregore_bridge.get_bridge()[source]
Return the process-wide
EgregoreBridgesingleton, creating it once.Lazily constructs an
EgregoreBridgeon first call – using the module-level Synapse config defaults – and caches it in the module global_bridgeso every caller shares one bridge, oneaiohttpsession pool, and one_registeredghost cache.Called by the egregore tooling –
tools/summon_egregore.py,tools/compose_scene.py,tools/dismiss_egregore.py– and bymessage_processor/generate_and_send.pyto reach the bridge rather than instantiatingEgregoreBridgedirectly.- Returns:
The shared bridge instance.
- Return type: