Wiring with Discord
The integration box on The World: Wiring, opened up for Discord: who talks to whom when the bar's people come from a Discord server. One process serves one Discord server (COMMUNITY_ID); each room is a page at /<roomid>, and a room may have a channel of its own as its window.
The links, one per row
| From → To | Protocol | Carries |
|---|---|---|
| Browser → Apache → palacebot | HTTPS :443 → :3311 | The page and assets, /api/me (am I logged in, and what token do I use for the socket), the OAuth callback. |
| Browser ↔ Apache ↔ palacebot | wss /ws | Everything live: hello, moves, talk, whispers, face/color/prop changes, paint strokes, votes, piece drags, lists (the user list and the room list, asked for while the 👥/🚪 drawer is open, every ten seconds; a person's id is sent only to people in the same room); back: welcome, joins, sleepers, balloons, captions, scene and widget state, and retract when the house takes a line back. The socket reconnects by itself with a backoff, so a dropped connection or a server restart heals without anyone touching the page. A restarting message goes out first when the server is stopped on purpose: the page then says the bar will be right back and retries about once a second instead of backing off, and when it gets back in it reloads itself only if the server is now serving a different client bundle than the page is running. |
| harryd ↔ palacebot | ws /ws (same host) | Harry's seat, one connection per room (harryd checks /api/config every minute for new rooms, and /api/agent/config, with its agent token, for his OpenRouter key and running config from the settings screen, applied live): an agent token instead of a login. In: talk, whispers to him, joins and leaves (with where from), widget and game events, and a handoff note from himself in the room that sent people over. Out: an enumerated set of calls the server checks (say, whisper, narrate, scene, paint, widgets, games), most of them only while a host has opened the floor; room.transport, loose.clear and the puppet.* calls (add, remove, move, chat: the test puppets, persons with a bot flag that the room treats like anyone) need only a host's recent word. A line or whisper to a puppet Harry chats as reaches him marked to, and he answers with say/whisper as. |
| harryd → OpenRouter | HTTPS | One chat completion per turn with tools, sometimes with a PNG of the room attached, or a picture from the wall when someone asks about it. Cost comes back per call and is metered against the room's daily budget. The key is Harry's own (the Keys tab, else harryd's .env), not automoderation's. |
| palacebot → OpenRouter | HTTPS (outbound) | Automoderation, only when the Moderation tab says AI and a key is on the Keys tab (its own key, not harryd's): lines said out loud in the bar, when OpenRouter is the chat judge, in bunches of up to ten, with a fixed prompt that answers PG, R or MA for each, after the lines have been shown; and each new picture (pasted, by address, or found on the web) as a data URL before it's stored, for one word back. No tools, temperature 0, twenty-second timeout. Cost comes back per call and is counted server-wide against the day's budget; past it the calls stop until tomorrow. Whispers, Harry's lines and Discord's never go. |
| palacebot → TypeSafe | HTTPS (outbound) | Automoderation when Jev is the chat judge and a TypeSafe key is on the Keys tab: each line said out loud, alone and at once after it has been shown, as the state of one Score question over the five-level scale; back come the probabilities, their average and a confidence, nothing generated. Ten-second timeout. Input tokens are counted at the price in .env into the same daily budget. The same lines that never go to OpenRouter never go here. With the 8-ball in (Harry, 8ball on), a line said to Harry goes here instead of to harryd: first as five yes/no questions about what kind of question it is (yes/no, this-or-that, a degree, a number, and is the answer yes), then, for this-or-that and degree, a second request with a typed question built in code (a Choice over the words the question itself offered, or a Score over a five-level scale around the question's own adjective, ten levels for "out of ten"); the room server speaks a stock line for the answer. Nothing is generated; harryd hears nothing until the ball is out. |
| palacebot → Wikimedia / Openverse | HTTPS (outbound) | "Find a picture of X": a search query to the provider's API, then one fetch of the chosen image into data/pictures/, re-encoded. The server fetches only URLs a provider returned, https, public addresses, images under 8 MB. Browsers get the copy from palacebot at /pictures/, never from the source site. |
| Browser → palacebot | HTTPS /api/images, /api/props | A pasted picture, an https address, or an emoji becomes a stored PNG (normalized, hashed, deduplicated) and a prop (image + offset + hides-face); served back same-origin at /avatars/. Emoji come from Twemoji's CDN, addresses through the fetch guard. |
| Browser → palacebot | HTTPS /api/settings, /api/me/prefs | The settings screen: a JSON view of the room's settings and a merge-patch back, each part gated by its flag (room_authoring for the room and for POST /api/settings/rooms, which writes rooms/<id>.json, asks Discord for a channel only when one is wanted (most rooms have none: the guild is the door, and there's no window), and starts the room; manage_moderator_settings for the People tab, and manage_admin_settings for the house rules, each key including the one before). Harry's outfit goes the same way from his menu in the bar. /api/me/prefs keeps a person's own small preferences (what they've worn lately, their emoji) on the server so they follow them between devices; one's rating and word list go over the socket (meSettings) and come back in the welcome. |
| Browser → Discord OAuth2 → Browser | HTTPS redirects | One-time login. Discord sends the browser back with a short-lived code. |
| palacebot → Discord REST | HTTPS | Code → access token → user id, then "is this person in the guild and can they see the channel?". The access token is thrown away after that. |
| palacebot ↔ Discord Gateway | wss (outbound) | The bot's live feed: messageCreate, typingStart, guildMemberUpdate. This is how sleepers appear. |
| palacebot → Discord REST (webhook) | HTTPS | Only if a room sets mirrorToDiscord: public room talk posted under the speaker's name. Off by default, since half-conversations were noise for the channel. Whispers, moves and looks never travel here regardless. |
| palacebot → Discord REST (snapshot) | HTTPS | Edits the one pinned message with a fresh PNG of the room, balloons, boards and games included, at most every 20 s and only when something changed. This is the channel's window into the bar. |
| Discord app → palacebot (slash) | Interactions | /palace peek|who|say for anyone; /harry status|on|off|log|reset|play for barkeeps and /harry mute|ban|pardon|bless|floor|punished for barowners (the room checks the tier itself). Answers are ephemeral. say carries a line into the room as a caption under the sender's name and, when it addresses Harry, waits up to fifteen seconds for his reply and returns it to the sender alone. |
| Discord app ↔ #harrys-bar | Discord's own | Ordinary chat. Nothing to install. Lurkers see the pinned picture and the webhook posts. |
Why there is no echo
Room talk goes out through a webhook, and the bot drops every incoming message that has a webhook id or a bot author. So the bot's own posts come back through the Gateway and are ignored.
One head per person
A Discord user id is the key everywhere. Connected on the web: open eyes. Only chatting from Discord: a sleeper with eyes closed at a saved seat. Connecting converts the sleeper in place.
Harry, the agent process
A second process, harryd, connects to the room server over the same WebSocket as a browser does, with an agent token instead of a login. It receives room events and issues only calls from an enumerated list the server checks; changing the room needs a host to have opened the floor within the last ninety seconds. It alone holds the OpenRouter key; the room server alone holds the Discord token. Whispers between people never reach it, and nothing Harry says goes to Discord except through the pinned picture.
Games run on the server
Wheel of Cheese, hangman, the boards, timers, dice and polls are server code with their state in the scene; Harry can start them and hears about them but never decides an outcome. Twenty questions is the one game that lives in harryd: a tally of his own questions, kept beside the transcript rather than in it, so it survives the transcript's trimming. A guessing game runs faster than his say limit (six lines a minute), so one reply per line said to the room is free of that limit, and a line the bar does refuse is sent again after ten seconds rather than lost. The game show has its own page: how Wheel of Cheese is wired.