The README describes the thing that exists
Fifteen mentions of `chatd`, a daemon that has been called nobilis since it moved to its own repository; a config directory two names out of date; and no mention of Kick at all, which is a protocol this client speaks. While correcting those, the parts that had quietly drifted: IRC does more than SASL PLAIN now, Sneedchat's rooms are read from the site rather than edited in Settings, Matrix has threads and receipts and a directory search, and the build is `npm run daemon` rather than a bare cargo invocation. The wire section says requests are answered concurrently, and points at methods.rs for the full method list rather than listing ninety of them somewhere they can go stale again. Two things it now admits that it did not: Matrix verification is between your own sessions only, and there is no cross-signing.
This commit is contained in:
@@ -1,37 +1,37 @@
|
||||
# moho
|
||||
|
||||
A unified desktop chat client: IRC, Discord, Sneedchat (SneedChat, the Tor-only XenForo chat
|
||||
feature), and Matrix in one window, with XMPP/Slack landing protocol by protocol without
|
||||
A unified desktop chat client: IRC, Discord, Sneedchat (the Tor-only chat built into Kiwi
|
||||
Farms), Matrix and Kick in one window, with XMPP/Slack landing protocol by protocol without
|
||||
frontend changes. The architecture is deliberately service-agnostic - the UI only ever sees a
|
||||
unified account/buffer/message model, never a protocol-specific concept.
|
||||
|
||||
## Why two parts
|
||||
|
||||
- **`chatd`** (`chatd/`, Rust) speaks each protocol directly and translates it into a small
|
||||
- **`nobilis`** (`nobilis/`, Rust) speaks each protocol directly and translates it into a small
|
||||
unified JSON model (accounts, buffers, messages) over a Unix socket. It never exposes
|
||||
protocol-specific concepts to the client; it speaks the unified model, and each backend module
|
||||
happens to be how that model gets populated for a given service.
|
||||
- **The Electron client** (`src/`) spawns and supervises `chatd` and renders whatever it reports.
|
||||
- **The Electron client** (`src/`) spawns and supervises `nobilis` and renders whatever it reports.
|
||||
|
||||
Keeping the protocol work in a separate long-lived daemon means connections, Tor circuits, and
|
||||
Matrix sync survive the UI being closed, restarted, or reloaded during development.
|
||||
|
||||
```
|
||||
chatd/src/backend/ one module per protocol (irc, discord, sneedchat, matrix, ...)
|
||||
chatd/src/rpc/ the Unix-socket JSON-RPC server
|
||||
chatd/src/store.rs SQLite-backed scrollback persistence
|
||||
chatd/src/runtime.rs protocol-agnostic connection/buffer/message state
|
||||
nobilis/src/backend/ one module per protocol (irc, discord, sneedchat, matrix, kick)
|
||||
nobilis/src/rpc/ the Unix-socket JSON-RPC server
|
||||
nobilis/src/store.rs SQLite-backed scrollback persistence
|
||||
nobilis/src/runtime.rs protocol-agnostic connection/buffer/message state
|
||||
src/main/ Electron main: daemon supervision, RPC socket, tray, notifications
|
||||
src/preload/ the contextBridge surface exposed to the renderer
|
||||
src/renderer/ React UI: buffer list, message log, member list, accounts, settings
|
||||
resources/ bundled assets (Sneedchat smilies, icons)
|
||||
resources/icons/ bundled artwork (service marks, IRC network logos, tray icons)
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
npm install
|
||||
npm run daemon # cargo build --release, inside nobilis/
|
||||
```
|
||||
|
||||
If npm reports that install scripts were blocked, approve the two that fetch platform binaries —
|
||||
@@ -47,12 +47,15 @@ npm install-scripts approve electron esbuild
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Starts the renderer with hot reload and launches Electron, which spawns `target/release/chatd`.
|
||||
Starts the renderer with hot reload and launches Electron, which spawns `nobilis/target/release/nobilis`.
|
||||
|
||||
### Installing it properly
|
||||
|
||||
Run `cargo build --release` first — the daemon is bundled from `target/release/chatd`, so a stale
|
||||
or missing binary ships a stale or missing daemon.
|
||||
`npm run deploy` is the one-liner: it builds the daemon and the renderer, packages an AppImage,
|
||||
installs it, and writes the `.desktop` entry.
|
||||
|
||||
`npm run pack` and `npm run deploy` build the daemon first, so a stale binary cannot ship - but
|
||||
`cargo build --release` inside `nobilis/` does the same thing if you are building by hand.
|
||||
|
||||
**Arch (recommended):**
|
||||
|
||||
@@ -87,12 +90,12 @@ Either `sudo pacman -S fuse2`, or skip FUSE entirely:
|
||||
APPIMAGE_EXTRACT_AND_RUN=1 ./dist/moho-0.1.0.AppImage
|
||||
```
|
||||
|
||||
Every packaged form bundles `chatd`, the Sneedchat smilies and the icons under `resources/`, and
|
||||
Every packaged form bundles `nobilis`, the Sneedchat smilies and the icons under `resources/`, and
|
||||
the app resolves them from there rather than from the source tree.
|
||||
|
||||
### One daemon, many clients
|
||||
|
||||
chatd holds an `flock`-based singleton lock on its data directory, so launching a second client
|
||||
nobilis holds an `flock`-based singleton lock on its data directory, so launching a second client
|
||||
does not start a second daemon — it attaches to the running one. Quitting the app sends the
|
||||
daemon a clean shutdown (real QUITs to IRC, rather than dropping the connections), so your nick
|
||||
isn't left ghosted.
|
||||
@@ -100,21 +103,30 @@ isn't left ghosted.
|
||||
## The client
|
||||
|
||||
Three panes: an account-grouped buffer sidebar, the message log, and a member list, with the
|
||||
sidebar and member list independently foldable. Closing the window hides it to the tray; chatd
|
||||
sidebar and member list independently foldable. Closing the window hides it to the tray; nobilis
|
||||
keeps running, so connections and unread tracking survive.
|
||||
|
||||
- **Message rendering** - an IRC-style log rather than chat bubbles, with nick colouring,
|
||||
BBCode and markdown, spoilers, code and quote blocks, inline image/video/YouTube embeds,
|
||||
reactions, replies, inline edit, and Sneedchat's bundled smilies. A "comfy" mode groups
|
||||
consecutive messages under one avatar.
|
||||
- **Settings** - a category rail covering Display, IRC, Sneedchat, Tor, Matrix and the daemon
|
||||
itself. Message-kind filters (joins, parts, mode changes) are applied client-side, so toggling
|
||||
- **Settings** - a category rail covering Display, IRC, Sneedchat, Tor, Matrix, Kick and the
|
||||
daemon itself. Message-kind filters (joins, parts, mode changes) are applied client-side, so toggling
|
||||
one takes effect immediately without a reconnect.
|
||||
- **Per-protocol join pages** - each service's mechanism is genuinely different (IRC joins by
|
||||
channel name, Discord accepts an invite and offers a friends list, Matrix takes a room id or
|
||||
alias, Sneedchat's rooms are a fixed set edited in Settings), so each gets its own page.
|
||||
channel name, Discord accepts an invite and offers a friends list, Matrix takes a room address
|
||||
or searches every homeserver's directory at once, Sneedchat lists the rooms the site itself
|
||||
publishes, Kick takes a streamer's handle), so each gets its own page.
|
||||
- **Matrix security** - SAS device verification, server-side key backup, and signing other
|
||||
sessions out, all under the account's own row in the Accounts pane.
|
||||
sessions out, all under the account's own row in the Accounts pane. Verification is between
|
||||
your own sessions only: there is no cross-signing yet, so a session verified here is not
|
||||
verified in Element.
|
||||
- **Tagging** - typing "@" lists the people here, the roles the service lets anybody ping, and
|
||||
its whole-room words, matched fuzzily. What is sent is what the service understands: an id on
|
||||
Discord, `m.mentions` on Matrix, "nick: " on IRC.
|
||||
- **Profiles** - right-click somebody in the member list or on a message. Each service answers
|
||||
what it keeps: an idle time and shared channels on IRC, an account age and a join date on
|
||||
Discord, a power level and a last-seen on Matrix.
|
||||
|
||||
### Security notes
|
||||
|
||||
@@ -123,7 +135,7 @@ Node integration, and its preload surface is a fixed list of calls - there is no
|
||||
passthrough and no filesystem access. Formatted message HTML is never injected directly: it is
|
||||
parsed into an inert document and rebuilt through a tag and attribute whitelist, so unknown tags
|
||||
degrade to text and `javascript:`/`data:` URLs, inline event handlers and `<script>` cannot
|
||||
survive. Local media chatd has fetched is served through a dedicated scheme that resolves only
|
||||
survive. Local media nobilis has fetched is served through a dedicated scheme that resolves only
|
||||
inside the known cache directories, rather than by disabling `webSecurity`.
|
||||
|
||||
### Global hotkey on Wayland
|
||||
@@ -135,7 +147,7 @@ bind the compositor to focus the window instead.
|
||||
|
||||
## Wire protocol
|
||||
|
||||
Newline-delimited JSON over a Unix socket at `$XDG_RUNTIME_DIR/moho/chatd.sock`:
|
||||
Newline-delimited JSON over a Unix socket at `$XDG_RUNTIME_DIR/moho/nobilis.sock`:
|
||||
|
||||
```
|
||||
request: {"id": 1, "method": "sendMessage", "params": {...}}
|
||||
@@ -145,12 +157,19 @@ push: {"event": "message", "data": {...}}
|
||||
|
||||
Core methods: `listAccounts`, `listBuffers`, `listProtocols`, `addAccount`, `removeAccount`,
|
||||
`setAccountConnected`, `joinBuffer`, `partBuffer`, `sendMessage`, `editMessage`, `deleteMessage`,
|
||||
`subscribe`/`unsubscribe`, `getBacklog` (SQLite-backed, persists across `chatd` restarts). Each
|
||||
protocol also has its own account-creation method (`addAccount` for IRC, `addDiscordAccount`,
|
||||
`addSneedChatAccount`, `addMatrixAccount`).
|
||||
`subscribe`/`unsubscribe`, `getBacklog` (SQLite-backed, persists across `nobilis` restarts),
|
||||
`searchMessages`, `requestProfile`, `markBufferRead`. Each protocol also has its own
|
||||
account-creation method (`addAccount` for IRC, `addDiscordAccount`, `addSneedChatAccount`,
|
||||
`addMatrixAccount`, `addKickAccount`) and its own verbs beyond the shared ones - the full list is
|
||||
the match in `nobilis/src/rpc/methods.rs`, which is the only place it cannot go stale.
|
||||
|
||||
Requests are answered concurrently, so a slow one - a room-directory search across several
|
||||
homeservers, a Tor round trip - does not hold up anything else on the same connection. Responses
|
||||
carry the id of the request they answer and may arrive in any order.
|
||||
|
||||
Push events: `message`, `messageUpdated`, `messageDeleted`, `reactionsChanged`, `presenceChange`,
|
||||
`bufferListChange`, `connectionState`, `notification`, plus per-protocol login-flow events
|
||||
`bufferListChange`, `connectionState`, `notification`, `profile`, `readReceipts`, plus
|
||||
per-protocol login-flow events
|
||||
(`discordLoginQr`/`discordLoginScanned`/`discordLoginResult`, `sneedChatLoginStatus`/
|
||||
`sneedChatLoginResult`, `matrixLoginStatus`/`matrixLoginResult`). `message`, `presenceChange`,
|
||||
`messageUpdated`, `messageDeleted` and `reactionsChanged` are only delivered to clients that
|
||||
@@ -158,7 +177,9 @@ called `subscribe` for that buffer.
|
||||
|
||||
## Protocol backends
|
||||
|
||||
- **IRC** - TLS with SASL PLAIN, NickServ auto-identify, autojoin, optional SOCKS5 proxying.
|
||||
- **IRC** - TLS with SASL (PLAIN, EXTERNAL, SCRAM-SHA-256), NickServ auto-identify and GHOST
|
||||
reclaim, autojoin, optional SOCKS5 proxying, DCC send and receive, IRCv3 chathistory backfill,
|
||||
WHOIS, away, and the network's own colour codes.
|
||||
- **Discord** - official cross-device QR login (the same one discord.com/app offers), a real-time
|
||||
gateway client, message edit/delete/reactions/replies sync.
|
||||
- **Sneedchat (SneedChat)** - the Tor-only chat built into Kiwi Farms. Runs over an embedded Tor
|
||||
@@ -167,15 +188,20 @@ called `subscribe` for that buffer.
|
||||
single login). Supports message edit/delete and avatars (fetched through the same Tor session
|
||||
and cached locally, since the client has no route to a `.onion` host on its own).
|
||||
- **Matrix** - Client-Server API with full end-to-end encryption (vodozemac-backed Olm/Megolm via
|
||||
`matrix-sdk-crypto`), SAS device verification, server-side key backup, and room moderation.
|
||||
`matrix-sdk-crypto`), SAS device verification, server-side key backup, room moderation, threads,
|
||||
read receipts, and a room directory search that asks every homeserver this account knows at
|
||||
once.
|
||||
- **Kick** - the streaming site's chat, over its Pusher socket. Joins by streamer handle, imports
|
||||
the account's follows, and carries the three emote tiers with subscriber gating, redemptions,
|
||||
subscriptions and raids, and moderation.
|
||||
- **XMPP/Slack** - not implemented yet.
|
||||
|
||||
## On-disk state
|
||||
|
||||
| Path | Contents |
|
||||
|---|---|
|
||||
| `~/.config/moho/accounts.toml` | account config and credentials |
|
||||
| `~/.config/moho/scrollback.db` | SQLite scrollback |
|
||||
| `~/.config/moho/matrix-crypto/` | Matrix E2EE device keys and Olm sessions |
|
||||
| `~/.config/nobilis/accounts.toml` | account config and credentials |
|
||||
| `~/.config/nobilis/scrollback.db` | SQLite scrollback |
|
||||
| `~/.config/nobilis/matrix-crypto/` | Matrix E2EE device keys and Olm sessions |
|
||||
| `~/.cache/moho/` | re-derivable media caches (Matrix media, Sneedchat avatars/attachments) |
|
||||
| `$XDG_RUNTIME_DIR/moho/chatd.sock` | the daemon's control socket |
|
||||
| `$XDG_RUNTIME_DIR/moho/nobilis.sock` | the daemon's control socket |
|
||||
|
||||
Reference in New Issue
Block a user