# @kody/discord ## Intent Provide reusable, account-agnostic Discord helpers for Kody agents: **bot token** for reading and posting in servers (messages, reactions, threads), and **user OAuth** for identity plus the authorizing user's guild list. This is not a personal notify package, not a gateway/chat bot, and not official-server admin. ## What it does - Verify Discord credentials (bot token or user OAuth) - Read identity (`users/@me`) and list the authorizing user's guilds (OAuth) - Fetch channels and recent messages; create threads; add reactions (bot) - Post or edit bot messages with `dryRun` preview or `confirm` for live writes ## Prerequisites / setup There is **no built-in Discord OAuth app**. Both lanes are bring-your-own. Never paste tokens into chat. Agents: see [`AGENTS.md`](./AGENTS.md) for import paths and smoke checks. ### Auth lanes | Lane | Credential | Can do | Cannot do | | --- | --- | --- | --- | | **Bot token** | Secret `discordBotToken` (Bot prefix) | Read/post channel messages, react, create/list threads | List the human user's guilds as that user | | **User OAuth** | Saved integration `discord` (Bearer) | `users/@me`, `users/@me/guilds` | Read or post channel messages | OAuth scopes such as `identify` and `guilds` never grant channel content. Reading message text needs the privileged **Message Content** intent on the bot. Multi-account: pass `integration` (OAuth, default `discord`) or `secretName` (bot, default `discordBotToken`). Additional connections use names like `discord-work` / `discordBotTokenWork` — do not hard-code personal aliases. ### Lane A — user OAuth (identity and guilds) 1. Create an application at [discord.com/developers/applications](https://discord.com/developers/applications). 2. On **OAuth2**, copy the client id and client secret. 3. Add redirect URI exactly: `https://kody.codes/connect/oauth`. 4. Open this prefilled connect URL while signed in to Kody: ```text https://kody.codes/connect/oauth?provider=discord&authorizeUrl=https%3A%2F%2Fdiscord.com%2Foauth2%2Fauthorize&tokenUrl=https%3A%2F%2Fdiscord.com%2Fapi%2Foauth2%2Ftoken&flow=confidential&scopes=identify%20guilds ``` Decoded: authorize `https://discord.com/oauth2/authorize`, token `https://discord.com/api/oauth2/token`, `flow=confidential`, scopes `identify guilds`. Discord's token host `discord.com` is also the API host, so no extra `allowedHosts` is required. Access tokens expire after 7 days; Kody refreshes them. For a second Discord user account, change `provider=` to a new name such as `discord-work` and pass `integration: 'discord-work'` on OAuth exports. ### Lane B — bot token (read and post in servers) 1. On the same application's **Bot** tab, create the bot and copy the token. Never paste it into chat. 2. If the bot must read message text, enable the privileged **Message Content** intent. 3. Install the bot into the target server (Manage Server required): ```text https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=68672 ``` `68672` = view channel + send messages + read history + add reactions. Add Create Public Threads / Send Messages in Threads in the developer portal if you use thread helpers. 4. Save the token: ```text https://kody.codes/account/secrets/new?name=discordBotToken&description=Discord%20bot%20token&allowedHosts=discord.com&scope=user ``` Approve host `discord.com` after saving. Additional bots: a different `name=` (for example `discordBotTokenWork`) and pass `secretName` on bot exports. ### Hosts and scopes - **Required host:** `discord.com` (API `https://discord.com/api/v10`). - **OAuth scopes (Lane A):** `identify`, `guilds`. Optional: `email`, `connections`, `guilds.members.read`. None of these read channel messages. - **Bot capabilities (Lane B):** install permissions integer, bot roles, and privileged intents — not OAuth scopes. ## Done when - Bot secret `discordBotToken` is saved with host `discord.com` approved, and/or OAuth integration `discord` is connected - Identity smoke checks succeed for the lane(s) you use - Mutations refuse to call Discord unless `dryRun: true` or `confirm: true`