Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/discord

README.md

157 lines · 6.6 KB · Markdown

@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.

Auth lanes

Discord splits its API. Pick a lane before you call:

LaneCredentialCan doCannot do
Bot tokenSecret discordBotToken (Bot prefix)Read/post channel messages, react, create/list threadsList the human user's guilds as that user
User OAuthSaved integration discord (Bearer)users/@me, users/@me/guildsRead or post channel messages

OAuth scopes such as identify and guilds never grant channel content. Reading message text additionally 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.

Required setup

There is no built-in Discord OAuth app. Both lanes are bring-your-own.

Lane A — user OAuth (identity and guilds)
  1. Create an application at 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:
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's createAuthenticatedFetch 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):
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 ./create-thread.

  1. Save the token:
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 (who am I) and guilds (server list). Fuller 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.

Exports

  • kody:@kody/discord — package overview and setup URLs
  • kody:@kody/discord/smoke-testGET /users/@me for lane: 'bot' (default) or lane: 'oauth'
  • kody:@kody/discord/get-me — same identity read
  • kody:@kody/discord/list-guilds — OAuth guild list (guilds scope)
  • kody:@kody/discord/fetch-channel — bot: one channel by id
  • kody:@kody/discord/fetch-channel-messages — bot: recent messages (limit 1–100)
  • kody:@kody/discord/post-message — bot: post content/embeds/files; dryRun or confirm
  • kody:@kody/discord/edit-message — bot: edit a bot-authored message; dryRun or confirm
  • kody:@kody/discord/add-reaction — bot: add one reaction; dryRun or confirm
  • kody:@kody/discord/create-thread — bot: public thread from a message; dryRun or confirm
  • kody:@kody/discord/list-channel-threads — bot: active/archived threads (channelId + guildId required)

Smoke test

Bot lane (reads the approved discordBotToken secret):

import { packages } from 'kody:runtime'

export default async function main() {
  return packages.invoke({
    kodyId: 'discord',
    exportName: './smoke-test',
    params: { lane: 'bot' },
  })
}

OAuth lane:

import { packages } from 'kody:runtime'

export default async function main() {
  return packages.invoke({
    kodyId: 'discord',
    exportName: './smoke-test',
    params: { lane: 'oauth', integration: 'discord' },
  })
}

Use packages.invoke (not a static import) so the package runtime resolves secret mounts and placeholders.

dryRun

Mutations accept dryRun: true and return the Discord payload without calling the API. A live mutation requires confirm: true after the user approved the exact channel and content.

import postMessage from 'kody:@kody/discord/post-message'

export default async function main() {
  return postMessage({
    channelId: '1234567890',
    content: 'Hello from Kody',
    dryRun: true,
  })
}

403 / insufficient scope

Helpers throw DiscordApiError with missingScope and nextStep:

  • OAuth 401/403: names the missing scope (identify, guilds, …) and points at /connect/oauth?provider=…&scopes=…. Reconnect; do not cache raw tokens (they expire in 7 days).
  • Bot 401: rotate/save discordBotToken at the secrets URL. Bot tokens need the Bot prefix (this package always sends it).
  • Bot 403 code 50001 Missing Access: bot is not in the server or cannot see the channel. Reinstall with scope=bot.
  • Bot 403 code 50013 Missing Permissions: grant the missing channel permission / privileged Message Content intent, then retry.
  • Empty message content on an otherwise successful read: enable Message Content on the Bot tab.

Examples

OAuth identity:

import getMe from 'kody:@kody/discord/get-me'

export default async function main() {
  return getMe({ lane: 'oauth', integration: 'discord' })
}

Read a channel as the bot:

import fetchMessages from 'kody:@kody/discord/fetch-channel-messages'

export default async function main() {
  return fetchMessages({ channelId: '1234567890', limit: 5 })
}