Disploy / disploy

Flexible router for building HTTP interaction-based Discord bots with ease.
https://disploy.dev
Apache License 2.0
29 stars 10 forks source link
bot cloudflare-workers discord discord-api serverless vercel-deployment

disploy

Vercel

Visit disploy.dev for a detailed guide!


Disploy's Discord server Tests status

Warning: We're still in development, and packages are published to npm every 12 hours to the @dev tag. You can view our v1.0.0 milestone to see what features are planned for the first release and their current status.

Disploy is a flexible router for building HTTP interaction-based Discord bots with ease. It's designed to make it easy to build, test and deploy Discord bots.

Features

Disploy features a library and an opinionated framework with tooling inspired by Next.js.

Library

Disploy does not come included with a "server", that's up to you to implement. We have a guide showcasing you how to do so with Express (Node.js) and Deno's inbuilt server.

This is a slimmed-down guide to using Disploy with Next.js as your server.

Usage with Next.js

The API entry point:

// Entrypoint - pages/api/interactions.ts
import { createNextAdapter } from 'disploy';
import { ExampleApp } from '../../lib/main';

export default createNextAdapter(ExampleApp);

Note: An "adapter" is a function that transforms requests from your server implementation of choice and creates a TRequest that's fed into App#router#entry which returns a Promise<TResponse> which your adapter should transform and return to Discord.

Setting up the Disploy App:

// Main Bot - lib/core/main.ts
import { App } from 'disploy';
import commands from './commands/commands';

const clientId = process.env.DISCORD_CLIENT_ID;
const token = process.env.DISCORD_TOKEN;
const publicKey = process.env.DISCORD_PUBLIC_KEY;

if (!clientId || !token || !publicKey) {
    throw new Error('Missing environment variables');
}

export const ExampleApp = new App({
    logger: {
        debug: true,
    },
    commands,
});

ExampleApp.start({
    clientId,
    token,
    publicKey,
});

Setting up an array of commands:

// Command Array - lib/core/commands/commands.ts
import Ping from './core/ping';

const c = [Ping];

export default c;

Example command:

import type { ChatInputInteraction, Command } from 'disploy';

const Ping: Command = {
    name: 'ping',
    description: 'pong!',

    run(interaction: ChatInputInteraction) {
        interaction.reply({
            content: 'Hello World!',
        });
    },
};

export default Ping;

Framework

Disploy comes inbuilt with a CLI that can bundle your bot based on a file system structure, which is inspired by Next.js.

Use the "TypeScript Framework" boilerplate from create-disploy-app.

npx create-disploy-app@latest

Here are two examples, a command and a message component handler. Keep in mind none of this is exclusive to the framework, the only "framework exclusive" feature showcased here is the file structure and default exports.

// Example command - commands/ping.ts
import type { Command } from 'disploy';

export default {
    // Command "data"
    name: 'ping',
    description: 'pong!',

    // Command entrypoint
    async run(interaction) {
        if (!interaction.guild) {
            return void interaction.reply({
                content: 'You must use this in a guild.',
            });
        }

        interaction.deferReply(); // Synchronously reply to the incoming HTTP request
        const guild = await interaction.guild.fetch(); // BaseInteraction#guild is a ToBeFetched class, awaiting fetch on it will return the full structure

        // All our methods take in raw JSON (or our Message structure, coming soon)
        return void interaction.editReply({
            content: 'hello world!!!!!!!!',
            components: [
                {
                    type: 1,
                    components: [
                        {
                            type: 2,
                            label: 'Click me!',
                            style: 1,
                            custom_id: `ping-${interaction.user.id}`, // You can handle message components with express-like routes.
                        },
                    ],
                },
            ],
        });
    },
} satisfies Command;
// Example message component handler - handlers/ping.ts
import type { ButtonHandler } from 'disploy';

export default {
    customId: 'ping-:userId',

    async run(interaction) {
        const originalUser = await interaction.params.getUserParam('userId'); // This fetches a user structure from the interaction's params, it would be better to use getParam in this use case, but we're showcasing the getUserParam method here.
        const clicker = interaction.user;

        return void interaction.reply({
            content: `hello world!!!!!!!! (clicked by ${clicker}) [made by ${originalUser}]`,
        });
    },
} satisfies ButtonHandler;
disploy dev # test your bot locally with hot-reloading and tunneling
disploy deploy # deploy your bot to Cloudflare Workers

The CLI bundles your app by taking in commands and message components and turning them into a single bundle. It accomplishes this by transforming your default exports into an array, creating an App instance, and attaching an adapter for your specified target.

Planned Features

Testing

@disploy/disbench will be a testing library that will allow you to test your bot in a similar way to how you would test a web app with a mocked Discord API. View the repository here.

Example usage (this is not final):

// Disbench demo snippet (fake code)
import { Disbench } from '@disploy/disbench';

const disbench = new Disbench({
    app: 'dist/bot.js',
});

await disbench.setup(); // This will start the bot and start communicating with the framework to "deploy" commands to the mocked API

const echoCommand = disbench.commands.find({ name: 'echo' });

const response = await disbench.interact(echoCommand, {
    options: {
        message: 'Hello World!',
    },
});

expect(response).toEqual('Hello World!');

Join our Discord server for support and updates!