{"id":13489639,"url":"https://github.com/FnrDev/workers","last_synced_at":"2025-03-28T05:31:07.815Z","repository":{"id":37067797,"uuid":"468103185","full_name":"FnrDev/workers","owner":"FnrDev","description":"Simple discord bot template using cloudflare workers","archived":false,"fork":false,"pushed_at":"2022-06-28T13:59:45.000Z","size":219,"stargazers_count":11,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-20T05:44:29.791Z","etag":null,"topics":["bot","cloudflare","cloudflare-workers","discord","discord-bot","discord-interactions"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FnrDev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-03-09T21:52:33.000Z","updated_at":"2025-01-10T07:29:46.000Z","dependencies_parsed_at":"2022-06-24T21:39:31.365Z","dependency_job_id":null,"html_url":"https://github.com/FnrDev/workers","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FnrDev%2Fworkers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FnrDev%2Fworkers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FnrDev%2Fworkers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FnrDev%2Fworkers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FnrDev","download_url":"https://codeload.github.com/FnrDev/workers/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245978200,"owners_count":20703675,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bot","cloudflare","cloudflare-workers","discord","discord-bot","discord-interactions"],"created_at":"2024-07-31T19:00:32.319Z","updated_at":"2025-03-28T05:31:06.786Z","avatar_url":"https://github.com/FnrDev.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# Discord Bot Using Cloudflare Workers\n## How it works\n\nWhen you create a Bot on Discord, you can receive common events from the client as [webhooks](https://discord.com/developers/docs/resources/webhook). Discord will call a pre-configured HTTPS endpoint, and send details on the event in the JSON payload.\n\nThis bot is an example of writing a webhook based bot which:\n\n- Uses the [Discord Interactions API](https://discord.com/developers/docs/interactions/receiving-and-responding)\n- Uses [Cloudflare Workers](https://workers.cloudflare.com/) for hosting\n\n## Creating bot on Discord\n\nTo start, we're going to create the application and bot on the Discord Developer Dashboard:\n\n- Visit https://discord.com/developers/applications\n- Click `New Application`, and choose a name\n- Copy your Public Key and Application ID, and put them somewhere locally (we'll need these later)\n\n![awwbot-ids](https://user-images.githubusercontent.com/534619/157505267-a361a871-e06f-4e3e-876f-cf401908dd49.png)\n\n- Click on the `Bot` tab, and create a bot! Choose the same name as your app.\n- Grab the token for your bot, and keep it somewhere safe locally (I like to put these tokens in [1password](https://1password.com/))\n- Click on the `OAuth2` tab, and choose the `URL Generator`. Click the `bot` and `applications.commands` scopes.\n- Click on the `Send Messages` and `Use Slash Commands` Bot Permissions\n- Copy the Generated Url, and paste it into the browser. Select the server where you'd like to develop your bot.\n\n## Creating your Cloudflare worker\n\nCloudflare Workers are a convenient way to host Discord bots due to the free tier, simple development model, and automatically managed environment (no VMs!).\n\n- Visit the [Cloudflare dashboard](https://dash.cloudflare.com/)\n- Click on the `Workers` tab, and create a new service using the same name as your Discord bot\n- Make sure to [install the Wrangler CLI](https://developers.cloudflare.com/workers/cli-wrangler/install-update/) and set it up.\n\n### Storing secrets\n\nThe production service needs access to some of the information we saved earlier. To set those variables, run:\n\n```\n$ wrangler secret put DISCORD_TOKEN\n$ wrangler secret put DISCORD_PUBLIC_KEY\n$ wrangler secret put DISCORD_APPLICATION_ID\n$ wrangler secret put DISCORD_TEST_GUILD_ID\n```\n\n## Running locally\n\n\u003e :bangbang: This depends on the beta version of the `wrangler` package, which better supports ESM on Cloudflare Workers.\n\nLet's start by cloing the respository, and installing dependencies.  This requires at least v16 of Node.js:\n\n```\n$ npm install\n```\n\nBefore testing our bot, we need to register our desired slash commands.  For this bot, we'll have a `/joke` command, and a `/hello` command, and a `/invite` command.  The name and description for these are kept separate in `commands.js`:\n\n```js\nmodule.exports = [\n  {\n    name: \"invite\",\n    description: \"Get bot invite link\"\n  },\n  {\n    name: \"hello\",\n    description: \"Get hello world using HTTPS Request\"\n  },\n  {\n    name: \"joke\",\n    description: \"Get random joke.\"\n  }\n];\n```\n\nThe code to register our commands lives in `register.js`.  Commands can be registered globally, making them available for all servers with the bot installed, or they can be registered to a single server.  In this example - we're just going to focus on global commands:\n\n```js\nconst { REST } = require('@discordjs/rest');\nconst { Routes } = require('discord-api-types/v9');\nrequire('colors');\nrequire('dotenv').config();\n\n// setup slash commands\n\nconst commands = require('./commands')\nconst rest = new REST({ version: \"10\" }).setToken(process.env.DISCORD_TOKEN);\n\n(async () =\u003e {\n\ttry {\n\t\tconsole.log('[Discord API] Started refreshing application (/) commands.'.yellow);\n\t\tawait rest.put(\n\t\t\tprocess.env.DEVELOPMENT ? Routes.applicationGuildCommands(process.env.DISCORD_APPLICATION_ID, process.env.DISCORD_TEST_GUILD_ID)\n      : Routes.applicationCommands(process.env.DISCORD_APPLICATION_ID),\n\t\t\t{ body: commands },\n\t\t);\n\t\tconsole.log('[Discord API] Successfully reloaded application (/) commands.'.green);\n\t} catch (error) {\n\t\tconsole.error(error);\n\t}\n})();\n```\n\nThis command needs to be run locally, once before getting started:\n```\n$ DISCORD_TOKEN=**** DISCORD_APPLICATION_ID=**** node src/register.js\n```\n\nWe're finally ready to run this code locally! Let's start by running our local development server:\n\n```\n$ npm run dev\n```\n\nWhen a user types a slash command, Discord will send an HTTP request to a given endpoint. During local development this can be a little challenging, so we're going to use a tool called `ngrok` to create an HTTP tunnel.\n\n```\n$ npm run ngrok\n```\n\n![forwardin](https://user-images.githubusercontent.com/534619/157511497-19c8cef7-c349-40ec-a9d3-4bc0147909b0.png)\n\nThis is going to bounce requests off of an external endpoint, and foward them to your machine.  Copy the HTTPS link provided by the tool.  It should look something like `https://8098-24-22-245-250.ngrok.io`.  Now head back to the Discord Developer Dashboard, and update the \"Interactions Endpoint Url\" for your bot:\n\n![interactions-endpoint](https://user-images.githubusercontent.com/534619/157510959-6cf0327a-052a-432c-855b-c662824f15ce.png)\n\nThis is the process we'll use for local testing and development. When you've published your bot to Cloudflare, you will *want to update this field to use your Cloudflare Worker url.*\n\n\n## Code deep dive\n\nMost of the interesting code in this bot lives in `src/server.js`. Cloudflare Workers require exposing a `fetch` function, which is called as the entry point for each request. This code will largely do two things for us: validate the request is valid and actually came from Discord, and hand the request over to a router to help give us a little more control over execution.\n\n```js\nexport default {\n  /**\n   * Every request to a worker will start in the `fetch` method.\n   * Verify the signature with the request, and dispatch to the router.\n   * @param {*} request A Fetch Request object\n   * @param {*} env A map of key/value pairs with env vars and secrets from the cloudflare env.\n   * @returns\n   */\n  async fetch(request, env) {\n    if (request.method === 'POST') {\n      // Using the incoming headers, verify this request actually came from discord.\n      const signature = request.headers.get('x-signature-ed25519');\n      const timestamp = request.headers.get('x-signature-timestamp');\n      const body = await request.clone().arrayBuffer();\n      const isValidRequest = verifyKey(\n        body,\n        signature,\n        timestamp,\n        env.DISCORD_PUBLIC_KEY\n      );\n      if (!isValidRequest) {\n        console.error('Invalid Request');\n        return new Response('Bad request signature.', { status: 401 });\n      }\n    }\n\n    // Dispatch the request to the appropriate route\n    return router.handle(request, env);\n  },\n};\n```\n\nAll of the API calls from Discord in this example will be POSTed to `/`. From here, we will use the [`discord-interactions`](https://github.com/discord/discord-interactions-js) npm module to help us interpret the event, and to send results.\n\n```js\nrouter.post('/', async (request, env) =\u003e {\n  const message = await request.json();\n  console.log(message);\n  if (message.type === InteractionType.PING) {\n    // The `PING` message is used during the initial webhook handshake, and is\n    // required to configure the webhook in the developer portal.\n    console.log('Handling Ping request');\n    return new JsonResponse({\n      type: InteractionResponseType.PONG,\n    });\n  }\n\n  if (message.type === InteractionType.APPLICATION_COMMAND) {\n    // Most user commands will come as `APPLICATION_COMMAND`.\n        if (message.data.name === 'invite') {\n          const botId = env.DISCORD_APPLICATION_ID;\n          return new JsonResponse({\n            type: 4,\n            data: {\n              content: `[Click to use bot 🥳](https://discord.com/oauth2/authorize?client_id=${botId}\u0026scope=applications.commands)`,\n              flags: 64\n            }\n          })\n        }\n\n        if (message.data.name === 'hello') {\n          return new JsonResponse({\n            type: 4,\n            data: {\n              content: \"👋 Hey i'm using HTTPS request for sending this message using interactions\"\n            }\n          })\n        }\n\n        if (message.data.name === 'joke') {\n          const joke = await getRandomJoke();\n          return new JsonResponse({\n            type: 4,\n            data: {\n              content: joke\n            }\n          })\n        }\n        console.error('Unknown Command');\n        return new JsonResponse({ error: 'Unknown Type' }, { status: 400 });\n    }\n  }\n);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFnrDev%2Fworkers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FFnrDev%2Fworkers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFnrDev%2Fworkers/lists"}