npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

workers-discord

v0.0.10

Published

Some wrappers for Discord applications in Workers

Downloads

39

Readme

workers-discord

Some wrappers for Discord applications in Workers.

Provides a request handler for Discord interactions at /interactions (and a health-check route at /health).

Provides a method for registering commands with Discord, with logic for only updating commands with changes.

Request handler includes optional support for Sentry (tested with workers-sentry/toucan-js).

Usage

We'll be using TypeScript for this example, but the library can be used with JavaScript as well.

Install the library and the required packages for this example.

npm install workers-discord discord-api-types
npm install --save-dev typescript tsup dotenv @cloudflare/workers-types wrangler

Ensure Typescript is setup with the correct exposed types for Workers.

tsconfig.json:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ESNext"],
    "types": ["@cloudflare/workers-types"],
    "noEmitOnError": true,
    "skipLibCheck": true,
    "allowSyntheticDefaultImports": true,
    "removeComments": false,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
  }
}

Define a /ping command that'll show Pinging... and then update to Pong! <current time> after 5 seconds.

src/commands/ping.ts:

import { InteractionResponseType, MessageFlags, ComponentType } from 'discord-api-types/payloads';
import type { Command } from 'workers-discord';

import { component } from '../components/ping';
import type { CtxWithEnv } from '../env';

const pingCommand: Command<CtxWithEnv> = {
    name: 'ping',
    description: 'Ping the application to check if it is online.',
    execute: ({ response, wait, edit }) => {
        wait((async () => {
            await new Promise(resolve => setTimeout(resolve, 5000));

            await edit({
                content: `Pong! \`${new Date().toISOString()}\``,
                components: [
                    {
                        type: ComponentType.ActionRow,
                        components: [ component ],
                    },
                ],
            });
        })());

        return response({
            type: InteractionResponseType.ChannelMessageWithSource,
            data: {
                content: 'Pinging...',
                flags: MessageFlags.Ephemeral,
            },
        });
    },
};

export default pingCommand;

Define a refresh button component that we'll include in the /ping command response, which will update the message when clicked.

src/components/ping.ts:

import { InteractionResponseType, ComponentType, ButtonStyle, type APIButtonComponent } from 'discord-api-types/payloads';
import type { Component } from 'workers-discord';

import type { CtxWithEnv } from '../env';

export const component: APIButtonComponent = {
    type: ComponentType.Button,
    custom_id: 'ping',
    style: ButtonStyle.Secondary,
    label: 'Refresh',
};

const pingComponent: Component<CtxWithEnv> = {
    name: 'ping',
    execute: async ({ response }) => response({
        type: InteractionResponseType.UpdateMessage,
        data: {
            content: `Pong! \`${new Date().toISOString()}\``,
            components: [
                {
                    type: ComponentType.ActionRow,
                    components: [ component ],
                },
            ],
        },
    }),
};

export default pingComponent;

Create a file to store our environment definition, so that we can use it in commands etc. if needed.

src/env.ts:

export interface Env {
    DISCORD_PUBLIC_KEY: string;
}

export interface CtxWithEnv extends ExecutionContext {
    env: Env;
}

Define the Cloudflare Worker request handler with our command and component both registered.

src/index.ts:

import { createHandler } from 'workers-discord';

import pingCommand from './commands/ping';
import pingComponent from './components/ping';
import type { Env, CtxWithEnv } from './env';

let handler: ReturnType<typeof createHandler<CtxWithEnv>>;

const worker: ExportedHandler<Env> = {
    fetch: async (request, env, ctx) => {
        // Create the handler if it doesn't exist yet
        handler ??= createHandler<CtxWithEnv>(
            [ pingCommand ],        // Array of commands to handle interactions for
            [ pingComponent ],      // Array of components to handle interactions for
            env.DISCORD_PUBLIC_KEY, // Discord application public key
            true,                   // Whether to log warnings for any invalid commands/components passed
        );

        // Run the handler, passing the environment to the command/component context
        (ctx as CtxWithEnv).env = env;
        const resp = await handler(request, ctx as CtxWithEnv);
        if (resp) return resp;

        // Fallback for any requests not handled by the handler
        return new Response('Not found', { status: 404 });
    },
};

export default worker;

As part of the build process, make sure to register the ping command with Discord.

tsup.config.ts:

import { defineConfig } from 'tsup';
import { registerCommands } from 'workers-discord';
import dotenv from 'dotenv';

import pingCommand from './src/commands/ping';

dotenv.config({ path: '.dev.vars' });

export default defineConfig({
    entry: ['src/index.ts'],
    format: ['esm'],
    dts: true,
    sourcemap: true,
    clean: true,
    outDir: 'dist',
    outExtension: () => ({ js: '.js' }),
    onSuccess: async () => {
        await registerCommands(
            process.env.DISCORD_CLIENT_ID!,     // Discord application client ID
            process.env.DISCORD_CLIENT_SECRET!, // Discord application client secret
            [ pingCommand ],                    // Array of commands to register with Discord
            true,                               // Whether to log warnings for any invalid commands passed
            process.env.DISCORD_GUILD_ID,       // Optional guild ID to register guild-specific commands
        );
    },
});

Configure Wrangler to use the built worker, and to have our secrets available.

wrangler.toml:

name = "<worker_name>"
main = "dist/index.js"
account_id = "<account_id>"
workers_dev = true
compatibility_date = "2023-10-25"

[build]
command = "npm run build"
watch_dir = "src"

.dev.vars:

DISCORD_PUBLIC_KEY=<discord_public_key>
DISCORD_CLIENT_ID=<discord_client_id>
DISCORD_CLIENT_SECRET=<discord_client_secret>
# DISCORD_GUILD_ID=<discord_guild_id>

Run npx wrangler login to get your account ID, which should be added to wrangler.toml. Then, you can run npx wrangler dev to start the development server and register your commands.

You may want to use a tool like cloudflared to expose your development server to the work, so that you can test your commands in Discord.