Denska is the koa - albeit not so beginner friendly - of @discord gateway apis.
It only defines what it needs to manage a connection to the gateway and pass down a context containing relevant data.
So, why denska?
Iâd actually preference using another library such as discordeno (deno) or discord.js (node) over denska. Theyâre much more beginner friendly and contain all the necessities youâd want when using a @discord api.
âSo why would you use denska in the end?â
If you want, need, or just prefer more control over each payload from itâs raw state, then this is what desnka is for. It can also be made as beginner friendly as both discordeno and discord.js over time with middleware created by the community.
A quick example (using deno + typescript).
This example will go through identifying and setting up heartbeats. Iâd recommend you read the docs for them as we go through this example.
Setting up.
First things first, letâs get everything that we need setup. This includes importing stuff, creating a shard, and some variables weâll be using.
// in your code you'll want to specify a version
import { Shard, Opcode } from "https://deno.land/x/denska";
// this isn't the best way to do it, you'd want to get beforehand
const shard = new Shard({
url: "wss://gateway.discord.gg/?v=8&encoding"
});
// this will be received by the *hello* payload when we first connect
let heartbeat_interval: number;
// this is received by all payloads, so this will be updated frequently
let last_seq: number | null = null;Nice, now weâve setup the base we can get into creating some middleware!
Identifying.
This is an important step as it letâs us receive events for our bot going forward.
// this will do our identifying
shard.use(async (ctx, next) => {
// this checks to see that we got a payload, not a close event
if(ctx.raw) {
// then we check that the opcode is for our *hello* payload
if(ctx.raw.op === Opcode.Hello) {
shard.send({
// we're sending back an *identify* payload
op: Opcode.Identify,
d: {
// this identifies the bot we are connecting as
token: "<bot_token>",
// here we'll just use some good defaults
properties: {
$os: "linux",
$browser: "denska",
$device: "denska"
},
// `1` is for guild events
// `512` is for guild message events
intents: 1 | 512
}
});
}
}
// pass down the `ctx` to the next middleware
await next();
});Thatâs quite the chunk of code, thankfully most of itâs whitespace and comments.
In this we check that we got the hello payload and send back an identify payload in response. Said identify payload contains the basic information needed to setup a connection.
Lastly, we want to pass down the ctx so we call next which does that for us.
Heartbeating.
This is another important step as it keeps the connection alive so that we can continue to receive events.
shard.use(async (ctx, next) => {
if(ctx.raw) {
// here we get the sequence from the payload or use `null`
last_seq = ctx.raw.s ?? null;
// here we want to check if the payload is a *hello* payload
if(ctx.raw.op === Opcode.Hello) {
// if so, we need it's `heartbeat_interval`
heartbeat_interval =
(ctx.raw.d as { heartbeat_interval: number }).heartbeat_interval;
}
// then we also want to check if the payload is a...
if(
// *hello* payload, (initial heartbeat)
ctx.raw.op === Opcode.Hello
// *heartbeat* payload, (gateway requesting a heartbeat)
|| ctx.raw.op === Opcode.Heartbeat
// or a *heartbeatack* payload (last heatbeat was acknowledged)
|| ctx.raw.op == Opcode.HeartbeatACK
) {
// then we can send a *heartbeat* payload back after
// `heartbeat_interval` milliseconds
setTimeout(() => {
shard.send({
op: Opcode.Heartbeat,
d: last_seq
});
}, heartbeat_interval);
}
}
// pass down the `ctx` to the next middleware
await next();
});Another one that looks like a lot of code, but really itâs just whitespace and comments.
In this we check that we got any payload, if so we save itâs sequence if it has one. Then we check for a hello payload specifically, and if we got it we take its heartbeat_interval for later. Then we check for either a hello, heartbeat, or heartbeatack payload and send back a heartbeat payload after heartbeat_interval milliseconds.
Once again, we want to pass down the ctx so we call next which does that for us.
Testing.
So, we have it all setup and now itâs time to test. This is the simplest bit of middleware, all we need to do it console.log the ctx.
shard.use(async ctx => {
console.log(ctx);
})Done, not much to explain here.
When you run the code youâll see a hello payload shortly followed by a dispatch payload with the t set to READY, this contains your botâs user data.
Then comes more dispatch events with t set to GUILD_CREATE which are the guilds your bot is in. As users in these guilds send messages, youâll also receive dispatch events with t set to MESSAGE_CREATE.
Every once in a while - however milliseconds heartbeat_interval is set to - youâll see a heartbeatack payload which is the gateway saying that your heartbeat was acknowledged. If no heartbeatack payload is received then the connection will die.
If any of the above things fail, youâll most likely see close in ctx instead of raw. This will contain the close code and a reason why the connection closed.
denska-core.
An âofficialâ - by that I mean itâs maintained alongside denska - middleware package for denska exists called denska-core, this does everything in the above example plus extra.
Iâd recommend using it if youâd like a quick and easy setup for the stuff above.