discord-bridge: Discord presence into Matrix ghosts #60

Merged
jared merged 1 commits from feat/discord-presence into main 2026-11-04 19:41:08 -05:00
Owner

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.ai/code/session_01PPmy3tPq869XDW4njjVaKA

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
jared added 1 commit 2026-11-04 19:36:39 -05:00
discord-bridge: Discord presence into Matrix ghosts
Lint / JS (eslint) (pull_request) Successful in 10s
Lint / Shell (shellcheck) (pull_request) Successful in 11s
Lint / No Hookshot webhook IDs in README (pull_request) Successful in 6s
Lint / No secrets in webhook configs (pull_request) Successful in 6s
Lint / Landing page is rendered (matrix (pull_request) Successful in 6s
Lint / Python (ruff) (pull_request) Successful in 7s
Lint / Secret scan (gitleaks) (pull_request) Successful in 13s
Lint / Shell (shellcheck) (push) Successful in 26s
Lint / JS (eslint) (push) Successful in 18s
Lint / No Hookshot webhook IDs in README (push) Successful in 21s
Lint / No secrets in webhook configs (push) Successful in 12s
Lint / Python deps (pip-audit) (pull_request) Successful in 1m54s
Lint / Landing page is rendered (matrix (push) Successful in 17s
Lint / Python (ruff) (push) Successful in 16s
Lint / Secret scan (gitleaks) (push) Successful in 31s
Lint / Python deps (pip-audit) (push) Successful in 1m37s
f8f0ca4549
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 main 2026-11-04 19:41:08 -05:00
jared deleted branch feat/discord-presence 2026-11-04 19:41:09 -05:00
Sign in to join this conversation.