This guide helps Claude (and other AI assistants) understand the structure and conventions of this Discord bot codebase.
This is a Discord bot built for bomb.sh (originally forked from Astro's Houston bot) that runs as a Cloudflare Worker. It integrates Discord and GitHub to provide commands for managing pull requests and issues.
Key Features:
/ptalcommand - Create "Please Take a Look" requests for GitHub PRs with embedded status, reviews, and deployment links/issuecommand - Issue management functionality- GitHub webhook integration for automated updates
- Durable Objects for stateful operations
- Real-time PR status tracking (pending, reviewed, approved, merged, closed)
- Runtime: Cloudflare Workers (serverless edge computing)
- Language: TypeScript (with Node.js 22.14.0)
- Package Manager: pnpm 10.7.0
- Router: itty-router v4
- Discord: discord.js v14, discord-api-types, discord-interactions
- GitHub: @octokit/rest v20, @octokit/webhooks v13
- Build Tool: @bomb.sh/tools (custom build tooling)
discord-bot/
├── src/
│ ├── index.ts # Main entry point, router setup, interaction handling
│ ├── types.ts # TypeScript interfaces (Command, Env)
│ ├── register.ts # Command registration script
│ ├── discordClient.ts # Discord interaction client wrapper
│ ├── durableObject.ts # Cloudflare Durable Object implementation
│ ├── commands/
│ │ ├── index.ts # Command registry/exports
│ │ ├── ptal.ts # /ptal command implementation
│ │ └── issue.ts # /issue command implementation
│ ├── webhooks/
│ │ └── github.ts # GitHub webhook handler
│ └── utils/
│ ├── discordUtils.ts # Discord utility functions
│ ├── helpers.ts # General helper functions
│ └── embeds.ts # Discord embed builders
├── wrangler.toml # Cloudflare Workers configuration
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
└── .dev.vars.example # Environment variables template
Main application entry point. Sets up itty-router with the following routes:
GET /- Health check endpointPOST /- Discord interactions endpoint (handles commands, autocomplete, buttons)ALL /webhooks/github- GitHub webhook events
Exports the Cloudflare Worker's fetch handler and the DiscordBotDurableObject.
Core TypeScript interfaces:
Command- Interface for Discord slash commands (data, execute, initialize, autocomplete, button)Env- Environment variables interface (Discord tokens, GitHub token, Guild ID)
The most complex command. Implements /ptal for pull request reviews:
- Fetches PR data from GitHub API
- Displays PR status, reviews, changeset info
- Supports deployment and additional links
- Includes refresh button for updating status
- Color-codes embeds based on PR state
Wrapper around Discord interactions providing helper methods for:
- Deferred replies
- Message updates
- Interaction responses
All commands follow this pattern:
interface Command {
data: SlashCommandBuilder
initialize?(env: Env): boolean | Promise<boolean>
execute(client: InteractionClient): Response
autocomplete?(client: InteractionClient): Response
button?(client: InteractionClient): Response
}Required environment variables (see .dev.vars.example):
DISCORD_TOKEN # Bot token from Discord Developer Portal
DISCORD_CLIENT_ID # Application ID
DISCORD_PUBLIC_KEY # Public key for signature verification
GITHUB_APP_ID # GitHub App ID (if using GitHub App)
GITHUB_CLIENT_ID # GitHub OAuth client ID
GITHUB_CLIENT_SECRET # GitHub OAuth client secret
GITHUB_WEBHOOK_SECRET # Secret for verifying GitHub webhooks
GITHUB_TOKEN # GitHub personal access token (for API calls)
GUILD_ID # Discord server ID (optional, for guild commands)
-
Clone and install:
git clone <repo-url> pnpm install
-
Configure environment:
- Copy
.dev.vars.exampleto.dev.vars - Fill in Discord and GitHub credentials
- Copy
-
Register commands (required after command changes):
pnpm register
-
Start local server:
pnpm dev
-
Expose locally (required for Discord interactions):
- Use ngrok, cloudflare tunnel, or similar
- Set the public URL as "INTERACTIONS ENDPOINT URL" in Discord Developer Portal
pnpm start/pnpm dev- Start Wrangler dev serverpnpm build- Build the project using @bomb.sh/toolspnpm format- Format codepnpm lint- Lint codepnpm register- Register Discord slash commands
- Create a new file in
src/commands/(e.g.,newcommand.ts) - Follow the
Commandinterface pattern fromsrc/types.ts - Use
SlashCommandBuilderfrom@discordjs/buildersfor command definition - Add the command to
src/commands/index.tsexports - Run
pnpm registerto register with Discord - Handle errors gracefully and provide user feedback
- Use TypeScript with strict typing
- Prefer async/await over promises
- Use
biomefor linting (configured via @bomb.sh/tools) - Follow existing patterns for Discord interactions
- Include error handling for API calls (GitHub, Discord)
- Use
InteractionClientwrapper instead of raw API calls
Getting command options:
const value = getStringOption(client.interaction.data, 'option_name')Deferred replies (for long operations):
return client.deferReply({}, async () => {
// Perform long operation
await rest.patch(Routes.webhookMessage(...), { body: reply })
return true
})Error handling:
try {
// GitHub API call
} catch (error) {
if (error instanceof RequestError && error.status != 404) {
console.error(error)
}
await ReplyOrEditReply(interaction, { content: 'Error message' }, env)
return null
}Creating embeds:
const embed = getDefaultEmbed()
embed.setTitle('Title')
embed.addFields({ name: 'Field', value: 'Value' })
embed.setColor(0x3498db)- Local testing: Use
pnpm devand expose via tunnel - Command testing: Use Discord client to invoke commands
- GitHub webhooks: Use GitHub's webhook delivery UI to redeliver events
- Deployment: Deploy via GitHub Actions (
.github/workflows/deploy.yml)
- Cloudflare Workers limitations: No filesystem access, limited execution time (30s on free plan)
- Durable Objects: Used for stateful operations (see
src/durableObject.ts) - Discord interactions: Must respond within 3 seconds or use deferred replies
- GitHub API: Uses Octokit for API calls, requires GITHUB_TOKEN
- Button interactions: Custom IDs use format
{command}-{action}(e.g.,ptal-refresh)
- All Discord interactions are verified using
verifyDiscordRequest() - GitHub webhooks are verified using
GITHUB_WEBHOOK_SECRET - Secrets are stored in Cloudflare Workers secrets (not in code)
- No sensitive data should be logged
The bot deploys to Cloudflare Workers via GitHub Actions:
- Workflow:
.github/workflows/deploy.yml - Domain:
discord-bot.bomb.sh - Account ID:
6eababa1621550fb2dbc673005c5ac89(inwrangler.toml)
- Create
src/commands/mycommand.ts - Implement the
Commandinterface - Export from
src/commands/index.ts - Run
pnpm register
- Add button to components in command's execute method
- Set
custom_idas{command}-{action} - Implement
button()method in the command
- Edit
src/webhooks/github.ts - Use
@octokit/webhooksfor type-safe event handling
- Check Cloudflare Workers logs via Wrangler dashboard
- Use
console.log()/console.error()(appears in Wrangler logs) - Test locally with
pnpm devbefore deploying