Owner request 2026-11-04: pass Discord presence into Matrix, as fully as possible, using an existing bot.
What it does: a small service (lotus_presence.py) watches the Discord server and updates each member's @discord_* ghost. Lotus already displays this.
Discord
Matrix
online / idle / DND
online / unavailable / org.matrix.msc3026.busy
offline, invisible
offline, no status text
custom status + first activity
status message, e.g. 🍕 afk · Playing Valorant
Design
No new bot: reuses the bridge's bot, with its token read from the bridge database at startup and never logged. It's a second gateway session with only the Guilds and Presence intents.
Own Matrix credentials: generated on the server by the installer and never printed. The registration is non-exclusive on the ghost namespace, so it can update ghosts but not create them.
Fails safe: Synapse drops an unrefreshed user to offline, so live states are re-sent every 20 s. If the service dies, ghosts go offline rather than stale.
Statuses and activities are never logged.
Tested
13 unit tests (run locally; CI doesn't run them). ruff is clean.
Local Synapse, with two appservices arranged like production:
the second appservice can set presence on ghosts the first owns exclusively, and can't register them (M_EXCLUSIVE);
a ghost that doesn't exist gets 403, and is retried every 10 minutes;
busy and idle stick;
an unrefreshed ghost goes offline about a minute later, and with refresh it holds past 80 s.
The real client against a fake gateway and real Synapse: correct intents (257), snapshot, live updates, other guilds ignored, resume after op 7, fresh identify after op 9, members who left turned off, and a clean stop on close code 4014.
Not yet tested against real Discord. That happens after merge, before the Synapse restart.
Deploying: merging installs nothing. Run discord-bridge/install-presence.sh on LXC 151, then restart Synapse once.
Owner request 2026-11-04: pass Discord presence into Matrix, as fully as possible, using an existing bot.
**What it does:** a small service (`lotus_presence.py`) watches the Discord server and updates each member's `@discord_*` ghost. Lotus already displays this.
| Discord | Matrix |
|---|---|
| online / idle / DND | online / unavailable / `org.matrix.msc3026.busy` |
| offline, invisible | offline, no status text |
| custom status + first activity | status message, e.g. `🍕 afk · Playing Valorant` |
**Design**
- **No new bot:** reuses the bridge's bot, with its token read from the bridge database at startup and never logged. It's a second gateway session with only the Guilds and Presence intents.
- **Own Matrix credentials:** generated on the server by the installer and never printed. The registration is non-exclusive on the ghost namespace, so it can update ghosts but not create them.
- **Fails safe:** Synapse drops an unrefreshed user to offline, so live states are re-sent every 20 s. If the service dies, ghosts go offline rather than stale.
- Statuses and activities are never logged.
**Tested**
- 13 unit tests (run locally; CI doesn't run them). ruff is clean.
- Local Synapse, with two appservices arranged like production:
- the second appservice can set presence on ghosts the first owns exclusively, and can't register them (`M_EXCLUSIVE`);
- a ghost that doesn't exist gets 403, and is retried every 10 minutes;
- busy and idle stick;
- an unrefreshed ghost goes offline about a minute later, and with refresh it holds past 80 s.
- The real client against a fake gateway and real Synapse: correct intents (257), snapshot, live updates, other guilds ignored, resume after op 7, fresh identify after op 9, members who left turned off, and a clean stop on close code 4014.
- **Not yet tested against real Discord.** That happens after merge, before the Synapse restart.
**Deploying:** merging installs nothing. Run `discord-bridge/install-presence.sh` on LXC 151, then restart Synapse once.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
mautrix-discord doesn't bridge presence. lotus_presence.py watches the Discord
server (a second gateway session on the bridge's own bot, Guilds + Presence
intents only) and sets presence and a status message on each @discord_* ghost:
online / idle / DND / offline -> online / unavailable / msc3026 busy / offline;
custom status and the first activity become the status message.
Its own appservice registration is non-exclusive on the ghost namespace, so it
can update ghosts but not create them. Synapse drops an unrefreshed user to
offline after about a minute, so live states are re-sent every 20 s; if the
service dies, ghosts go offline rather than stale. The installer generates the
credentials on the server and doesn't restart Synapse. Statuses and activities
are never logged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
jared
scheduled this pull request to auto merge when all checks succeed 2026-11-04 19:37:01 -05:00
jared
merged commit dde36cdcd2 into main2026-11-04 19:41:08 -05:00
jared
deleted branch feat/discord-presence2026-11-04 19:41:09 -05:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Owner request 2026-11-04: pass Discord presence into Matrix, as fully as possible, using an existing bot.
What it does: a small service (
lotus_presence.py) watches the Discord server and updates each member's@discord_*ghost. Lotus already displays this.org.matrix.msc3026.busy🍕 afk · Playing ValorantDesign
Tested
M_EXCLUSIVE);Deploying: merging installs nothing. Run
discord-bridge/install-presence.shon LXC 151, then restart Synapse once.🤖 Generated with Claude Code
https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA