This is the old version of the hbd bot running on Node.js runtime. To view the new and improved (yet work in progress) version of this bot that is on edge runtime, click here.
a discord bot for user's birthdays, horoscopes, and wishing user's a happy birthday.
While, the discord.js library offers a lot of essential utilities for interacting with discord, it doesn't quite fit for a bot that's going to be running through Nextjs/Vercel.
I wanted the bot to respond to interactions through edge runtime rather than running in an environment 24/7 waiting for interactions.
Now, bare with me... I am merely learning everything as I go along. 🤖
Alright, as much as I'd like to take credit for the whole "discord bot with nextjs" implementation, my starting point was finding this extremely useful repository that had already put an interactions endpoint & command registration script into place.
- The interaction is [**verified**](https://discord.com/developers/docs/interactions/receiving-and-responding#receiving-an-interaction) to be [received & responded](https://discord.com/developers/docs/interactions/receiving-and-responding#security-and-authorization) to within the route using some [logic](https://github.com/clxrityy/nextjs-discord-bot-with-oauth/blob/main/src/discord/verify-incoming-request.ts) implemented by the template creator that I haven't bothered to understand.
- The interaction data is parsed into a custom type so that it can be interacted with regardless of it's sub-command(s)/option(s) structure:
```ts
export interface InteractionData {
id: string;
name: string;
options?: InteractionSubcommand<InteractionOption>[] | InteractionOption[] | InteractionSubcommandGroup<InteractionSubcommand<InteractionOption>>[];
}
The last bit of the interactions endpoint structure (that I'm not entirely proud of) is that I'm using switch cases between every command name within the route to execute an external function/handler that generates the response for that specific command. But, this could be more efficient/easier-to-read in the future
That template had everything necessary to lift this project off the ground, use interactions, and display UI elements based on the bot's data.
However, I wanted to create another template I could use that implemented authentication with Discord so that there can be an interactive dashboard.
I will go over the whole process, but you can see in-depth everything I changed about the initial template in this pull request:
Accessing the designated root url (/) will require authentication with Discord. Upon authorizing, the user will be
redirected back to the root url (with additional user details displayed)
ex.
Replication
OAuth2 URLs
Generate your own OAuth2 redirect URI with every additional scope needed
(discord.com/applications/CLIENT_ID/oauth2)
The path should be /api/auth/discord/redirect
Add these urls (development and production) to config.ts:
exportconstCONFIG={REDIRECT_URI:
process.env.NODE_ENV==="development"
? "http://localhost:3000/api/auth/discord/redirect"
: "https://yourdomain.com/api/auth/discord/redirect",// REPLACE WITH YOUR DOMAINOAUTH2_INVITE_URL: process.env.NODE_ENV==="development" ? "" : "",// (copy the generated url)ROOT_URL: process.env.NODE_ENV==="development" ? "http://localhost:3000" : "",// REPLACE WITH YOUR DOMAIN}
The access_token from the data given is used to receive the Discord user's details by making a GET request to
the discord.com/api/v10users/@me endpoint:
I've implemented a prisma table which will store the encrypted access & refresh token from the user data. This can be used later, but for now has minimal impact on the application.
The .env.local.example has been updated to include:
# discord.com/developers/applications/APP_ID/oauth2DISCORD_CLIENT_SECRET=# Encryption: a custom secret key for encrypting sensitive data# This is used to encrypt the user's Discord token in the database# If you don't set this, the app will use a default keyENCRYPTION_KEY=# JWT for cookies# This is used to sign the JWT token for the user's session# If you don't set this, the app will use a default keyJWT_SECRET=# Prisma / Postgres# These are used to connect to the database# See here: https://vercel.com/docs/storage/vercel-postgres/quickstartPOSTGRES_DATABASE=POSTGRES_HOST=POSTGRES_PASSWORD=POSTGRES_PRISMA_URL=POSTGRES_URL=POSTGRES_URL_NON_POOLING=POSTGRES_URL_NO_SSL=
An additional config.ts has been made to include necesssary authentication URLs
Add your redirect URI to your Discord application: (should be found at https://discord.com/developers/applications/{APP_ID}/oauth2)
Development - http://localhost:3000/api/auth/discord/redirect
Production - https://VERCEL_URL/api/auth/discord/redirect
I know off the bat I'm gonna need to start implementing the database aspect of this application now; as I need a way to store user data (such as refresh tokens, user id, etc.)
---
### Back to OAuth2
- Create your route (mine is `api/auth/discord/redirect/route.ts`)
This route is automatically going to give a `code` url parameter upon successful authentication with Discord (make sure the route is set as the REDIRECT_URI in your bot settings).
```ts
export async function GET(req: Request) {
const urlParams = new URL(req.url).searchParams;
const code = urlParams.get("code");
}
You need this code to generate an access token and a refresh token.
Access token
Consider the access token as an item (such as a token) that authorizes the client (authorized website user) to interact with the API server (being Discord in this instance) on behalf of that same user.
Refresh token
Access tokens can only be available for so long (for security purposes), and the refresh token allows users to literally refresh their access token without doing the entire log in process again.
Set up a query string that says "hey, here's the code, can I have the access and refresh tokens"
Within this route (if the code exists, there's no error, and user data exists) I'm going to set a cookie, as users should only be directed to this route upon authentication.
- Set the cookie
- You can name this cookie whatever you want.
```ts
import { cookies } from "next/headers";
import { serialize } from "cookie";
cookies().set("cookie_name", serialize("cookie_name", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production", // secure when in production
sameSite: "lax",
path: "/"
}));
Then you can redirect the user to the application!