Discord
One Discord bot for every project, where its token comes from, how a project binds a server, the operator and mates audiences, channels, roles and member cards, and the discord page.
01one bot#
The Discord bridge is one bot that serves every project that binds a server of its own. The binding is in the project's local half. A record in a chat posts only to the channel its project's binding maps to that chat. A chat with no channel, or a project whose box is off, posts nothing anywhere.
A press, a slash command or a channel message comes back through one check. The server it came from must be bound, the card it names must be that server's project's, and the person must be the operator or a member with the right. A press from Discord is the same press as in the window, and the thread says "(from discord)". The operator's direct messages talk to discord.chat.
The switch is one session for every bound server. discord.enabled absent means on. The bridge asks for the privileged intents, and if Discord refuses them it falls back to cards and commands.
02the token and desk.json#
The desk.json block discord is the one bot every bound project shares, and its operator. Its keys are in desk-json: operator, token_env, token_file, chat and enabled. The environment variable is in env-vars.
The block holds no token. DiscordToken reads it like this:
- With no discord block, or with
discord.operatorempty, the feature is off, and the reason says which. - The variable named by
token_envis read, andSMITH_DISCORD_BOT_TOKENwhentoken_envis empty (DefaultDiscordTokenEnv). A non-empty value wins. - Otherwise
token_fileis read, and its trailing line breaks are trimmed. An unset, unreadable or empty file leaves the feature off.
The reason text never contains the token. When a connect fails, the token in the message is replaced with [token].
discord_state tells the conductor in plain words whether the bridge is on with chat, on with cards only (Discord refused message content), off, no token, or not set up.
03a project binds a server#
A project's discord block is in its local half, so it is machine-local. See projects. The conductor can't write it directly: it drafts the change with propose_config_edit. The fields:
on: the project's box. Off means nothing from it posts and its server is treated as unbound. Default off.guild: the server's id, digits only.bound_byiswindoworconductor, andbound_atis an RFC 3339 time. Unbinding clears these three.free_chat: lets a plain message in a mapped channel reach its chat. Off, a plain message is ignored (no reply, no tokens). Commands and buttons work either way.gate_cards: posts the project's gate cards to Discord. Default off.channels: maps a channel id to the desk chat it is. A thread is never in it and follows its parent channel's entry.command_templateandcommand_input: the template/researchdrafts in this server, and the input of it that gets the typed text. Absent means the shipped research template and itsquestioninput.audience: see below.members: see below.
A conductor's posting tool is refused when the project is bound but both free chat and gate cards are off.
04audiences#
audience says who reads the server's channel chats: operator (the default, and what empty means) or mates, people who aren't technical. Anything else is refused.
The audience sets the turn tag of a message that reaches the conductor:
| tag | when |
|---|---|
[via discord for mates] |
a channel chat in a server whose audience is mates, and not the DMs' chat |
[via discord] |
the message came from Discord, or the chat is the DMs' chat, or a channel's chat in a bound server |
| none | a turn in the window |
The reply style for mates is in the conductor's brief: plain words in one or two lines, no ids, hashes, tool or command names, file paths or json, and a choice on a card is pointed at by its buttons. See conductor. The reply style for the operator audience is not checked here.
05members#
A member is a person of the server who may act. research covers /research, go on a research card and approving a confirm gate. read covers /status and /tickets. The fields are id, name, research, read, added_by and added_at. A gate other than confirm is the operator's.
06channels and the conductor's tools#
The conductor builds in its own project's server. There is no server or project to pass: both come from the calling chat, and every id is looked up in that server first, so another project's ids are refused. Each done call writes one [discord] line in the chat.
| tool | what it does |
|---|---|
discord_channels |
lists the server's channels by category, threads under their channel, each one's chat, and the project's chats to map to. Changes nothing |
discord_read |
reads a channel's or thread's messages, oldest first. limit is 20 by default and at most 100, before pages back |
discord_post |
posts text, cut at line breaks, and an optional embed. Returns the last message's id |
discord_channel_new |
makes a text channel or a category, with an optional category, topic and chat to map to |
discord_thread_new |
starts a public thread in a channel, on a message or none |
discord_topic |
sets a channel's topic |
discord_pin |
pins a message |
discord_react |
reacts to a message |
discord_state |
whether the bridge is on |
(;.) These are chores: the conductor does them directly. Building is a chore, and destroying and rights are the operator's click on a card.
07the cards#
Six propose_discord* cards. The conductor drafts one and the operator's click does it (;):
| card | what the click does |
|---|---|
propose_discord |
switches the bridge on or off. chat is needed to switch on when none is set |
propose_discord_chat |
sets which chat the operator's DMs talk to, or unbinds it with an empty chat. It never switches the bridge on or off |
propose_discord_delete |
deletes a channel or thread (channel), or one message in it (message) |
propose_discord_roles |
act is create, edit, assign, unassign or overwrite, with the fields each needs. Permission names are those on the discord page |
propose_discord_member |
kicks someone, or bans them with ban true. reason goes to Discord's audit log |
propose_discord_server |
renames the server |
For every card, see cards. Autopilot presses none of them.
08the discord page#
The page is #/p/<project>/discord. It shows the binding, the bridge's cached look at the server and the project's chats for the map. It asks Discord nothing when it loads. A project with its box on has a discord item in the menu, with the bridge's state as a dot.
A bound server has six checks: bot token, bot in server, message content, server members, commands and posting. Each is ok, fail, warn or not run, with what it found and how to fix a failure. The two intents are privileged and set in the Discord developer portal. Posting is only tested when the operator presses test.
The page lists the permissions the bot asks for on invite, from one list. Among them, manage roles is for roles and overwrites, kick members and ban members for propose_discord_member, and manage server for renaming. Granting a permission is not what lets the conductor act without a click: that line is drawn by whether a route is a chore or a press.
The slash commands are /research, /status and /tickets (names them under the use application commands permission).