Discord Hono

repository·main·Indexed 40 days ago

https://github.com/luisfun/discord-hono

A lightweight, type-safe framework for building HTTP-based Discord bots optimized for Cloudflare Workers, heavily influenced by the Hono web framework. Version 0.22.1 provides tools for registering slash commands, handling components, and building complex Discord UI elements including buttons, select menus, modals, and polls using a fluent builder API.

Tokens
13.4K
Snippets
42
Records
52
Agent score
99%

What's inside Discord Hono

  1. Install discord-hono

    main

    Install the core package using npm. If you are using TypeScript, you should also install discord-api-types for type definitions. You may also need @types/node depending on your environment.

    npm i discord-hono
    npm i -D discord-api-types # When using TypeScript
    # npm i -D @types/node # As needed
  2. Understand the different Context types

    main

    Discord Hono provides specialized context types for different interaction types. Each context type is a specialized version of the base Context, with specific methods removed and new properties added to match the interaction's capabilities.

    • CommandContext: Used for Application Commands. Includes interaction (the command interaction) and ref (a reference to the command).
    • ComponentContext: Used for Message Components (buttons, select menus). Includes interaction (the component interaction) and ref (a reference to the component).
    • AutocompleteContext: Used for command autocomplete interactions. Includes interaction (the autocomplete interaction) and ref (a reference to the command).
    • ModalContext: Used for Modal submissions. Includes interaction (the modal interaction) and ref (a reference to the modal).
    • CronContext: Used for scheduled tasks (Cron). Includes interaction (the CronEvent) and ref (a reference to the cron job).
  3. How `custom_id` and `custom_value` are handled in `JsonBuilder`

    main

    The JsonBuilder has built-in logic for Discord's custom_id pattern. If your target type M contains both custom_id and custom_value, calling toJSON() will merge them into a single string.

    • The format is: {custom_id}{SEPARATOR}{custom_value}.
    • If custom_value is undefined, only custom_id is returned.
    • If the resulting string exceeds 100 characters, an error is thrown.

    This allows you to build complex interaction payloads easily while maintaining type safety for the individual components.

    interface InteractionPayload {
      custom_id: string;
      custom_value: string;
      other: string;
    }
    
    const builder = createJsonBuilder<{}, InteractionPayload>({});
    
    const payload = builder
      .custom_id("my_action")
      .custom_value("123")
      .other("data")
      .toJSON();
    
    // Result: { custom_id: "my_action_123", other: "data" }
  4. Integrate DiscordHono with Fetch-based runtimes

    main

    To use DiscordHono in a runtime like Cloudflare Workers, mount the .fetch method to your worker's entry point.

    • GET requests: The .fetch method automatically responds with Operational🔥 for health checks.
    • POST requests: The method handles signature verification using DISCORD_PUBLIC_KEY and routes the interaction to the appropriate handler based on the interaction type and custom_id or command name.
    • Error Handling: If a handler is missing for a valid interaction, it throws a handler error (resulting in a 500 response). If signature verification fails, it returns a 401 response.
    // Example Cloudflare Worker entry point
    export default {
      fetch: (request, env, ctx) => app.fetch(request, env, ctx),
      scheduled: (event, env, ctx) => app.scheduled(event, env, ctx),
    };
  5. Create a Discord bot with DiscordHono

    main

    Use the DiscordHono class to define your bot's logic. You can register slash commands using the .command() method, which provides a context object c to send responses via c.res().

    import { DiscordHono } from 'discord-hono'
    
    const app = new DiscordHono()
      .command('hello', c => c.res('Hello, World!'))
    
    export default app
  6. Register slash commands with register()

    main

    To make your slash commands available in Discord, use the register function along with makeSlashCommand. You will need your DISCORD_APPLICATION_ID and DISCORD_TOKEN (typically from environment variables).

    import { makeSlashCommand, register } from 'discord-hono'
    
    const commands = [
      makeSlashCommand('hello', 'Hello, World!'),
    ]
    
    register(
      commands,
      process.env.DISCORD_APPLICATION_ID,
      process.env.DISCORD_TOKEN,
      //process.env.DISCORD_TEST_GUILD_ID,
    )
  7. Configure Discord Hono with InitOptions

    main

    When initializing the application, you can provide InitOptions to configure security verification and environment bindings.

    • verify: An optional function to validate incoming requests. It receives the body, signature, timestamp, and publicKey. It should return true if the request is valid.
    • discordEnv: An optional configuration for Discord-specific environment variables. This can be a static DiscordEnv object or a function that maps your application's Bindings to a DiscordEnv object.

    DiscordEnv supports:

    • TOKEN: The Discord Bot Token.
    • PUBLIC_KEY: The Discord Application Public Key.
    • APPLICATION_ID: The Discord Application ID.
    interface DiscordEnv {
      TOKEN?: string | undefined;
      PUBLIC_KEY?: string | undefined;
      APPLICATION_ID?: string | undefined;
    }
    
    interface InitOptions<E extends Env> {
      verify?: (body: string, signature: string | null, timestamp: string | null, publicKey: string) => Promise<boolean> | boolean;
      discordEnv?: DiscordEnv | ((env: E['Bindings'] | undefined) => DiscordEnv);
    }
  8. Configure Environment Bindings

    main

    The Env interface defines how environment variables and bindings are structured in your application (e.g., in Cloudflare Workers).

    • Bindings: An optional object containing environment bindings.
    • Variables: An optional record of string-keyed environment variables.
    export interface Env {
      Bindings?: object
      Variables?: Record<string, unknown>
    }
  9. Configure `JsonBuilderOptions`

    main

    When calling createJsonBuilder, you can provide an optional JsonBuilderOptions object to control how data is copied.

    OptionTypeDefaultDescription
    clonebooleanfalseIf true, the builder uses globalThis.structuredClone for initial value copying and for the output of toJSON(). If false, it uses spread syntax { ...initial }.
    const builder = createJsonBuilder(initialData, { clone: true });
  10. Create a type-safe JSON builder with `createJsonBuilder`

    main

    Use createJsonBuilder to construct Discord API payloads using a fluent, chainable API. The builder is type-safe based on the provided target type M. It allows you to set properties by calling methods named after the keys in M, delete keys, and clone the builder state.

    Key Features

    • Fluent API: Call methods named after your target keys to set values.
    • Type Safety: The builder restricts available methods to the keys defined in the generic type M (excluding those in E).
    • Custom ID Handling: If the target type includes custom_id and custom_value, toJSON() will automatically join them using the internal CUSTOM_ID_SEPARATOR.
    • Cloning: You can create a deep copy of the builder's current state using .clone().
    import { createJsonBuilder } from 'discord-hono';
    
    interface MyPayload {
      content: string;
      username?: string;
    }
    
    const builder = createJsonBuilder<{}, MyPayload>({});
    
    const payload = builder
      .content("Hello World")
      .username("BotName")
      .toJSON();
    
    // Result: { content: "Hello World", username: "BotName" }
  11. Build Discord Group Selection Components

    main

    For grouping multiple choices, use:

    • Radio Group: Use makeRadioGroup(custom_id, options) where options is an array of [value, label] tuples.
    • Checkbox Group: Use makeCheckboxGroup(custom_id, options) where options is an array of [value, label] tuples.
    • Single Checkbox: Use makeCheckbox(custom_id) for an individual checkbox.
    // Radio Group
    const radioGroup = makeRadioGroup('choice_id', [
      ['red', 'Red'],
      ['blue', 'Blue'],
    ]);
    
    // Checkbox Group
    const checkboxGroup = makeCheckboxGroup('prefs_id', [
      ['notifications', 'Enable Notifications'],
      ['updates', 'Get Updates'],
    ]);