Creating your own listeners
Similar to what you learned in both Creating Commands and Creating
Preconditions, we will create a listeners subdirectory in your project's entry point
directory. In this document, we'll make a ready event listener.
Your directory should now look something like this:
- CommonJS
- ESM
- TypeScript
├── node_modules
├── package.json
└── src
├── commands
│ └── ping.js
├── index.js
└── listeners
└── ready.js
├── node_modules
├── package.json
└── src
├── commands
│ └── ping.mjs
├── index.mjs
└── listeners
└── ready.mjs
├── node_modules
├── package.json
└── src
├── commands
│ └── ping.ts
├── index.ts
└── listeners
└── ready.ts
Creating a listener class
To create a listener in Sapphire, the Listener class needs to be extended from @sapphire/framework and exported
from a listener file.
- CommonJS
- ESM
- TypeScript
const { Listener } = require('@sapphire/framework');
class ReadyListener extends Listener {}
module.exports = {
ReadyListener
};
import { Listener } from '@sapphire/framework';
export class ReadyListener extends Listener {}
import { Listener } from '@sapphire/framework';
export class ReadyListener extends Listener {}
Let's begin populating the listener class with our desired options, specifying that it'll only run once and listens for
the ready event:
- CommonJS
- ESM
- TypeScript
const { Listener } = require('@sapphire/framework');
class ReadyListener extends Listener {
constructor(context, options) {
super(context, {
...options,
once: true,
event: 'ready'
});
}
}
module.exports = {
ReadyListener
};
import { Listener } from '@sapphire/framework';
export class ReadyListener extends Listener {
constructor(context, options) {
super(context, {
...options,
once: true,
event: 'ready'
});
}
}
import { Listener } from '@sapphire/framework';
export class ReadyListener extends Listener {
public constructor(context: Listener.LoaderContext, options: Listener.Options) {
super(context, {
...options,
once: true,
event: 'ready'
});
}
}
An overview of what's defined in the constructor:
context: an object that contains file metadata required by thePiececlass (whichCommandextends) in order to function.name: by default, the name of the file without the extension, i.e.ready.jsbecomesready, so there's no need to define it.event: by default, the resolvedname, which represents the event to listen for.once: by defaultfalse, but since we want the listener to fire only once, we will set it totrue.
If you pay attention to the example, the event field is unnecessary. This allows you to omit constructors when the
file name matches the event's name and no other options need to be changed.
Creating the run method
Unlike commands, listeners use run methods instead of messageRun. This distinction is made because listeners do not
typically run from message contexts, and commands need different methods to support slash commands or context actions.
Listeners have a run method, which executes the listener logic. Define this below the listener's constructor:
- CommonJS
- ESM
- TypeScript
const { Listener } = require('@sapphire/framework');
class ReadyListener extends Listener {
run(client) {
const { username, id } = client.user;
this.container.logger.info(`Successfully logged in as ${username} (${id})`);
}
}
module.exports = {
ReadyListener
};
import { Listener } from '@sapphire/framework';
export class ReadyListener extends Listener {
run(client) {
const { username, id } = client.user;
this.container.logger.info(`Successfully logged in as ${username} (${id})`);
}
}
import { Listener } from '@sapphire/framework';
import type { Client } from 'discord.js';
export class ReadyListener extends Listener {
public run(client: Client) {
const { username, id } = client.user!;
this.container.logger.info(`Successfully logged in as ${username} (${id})`);
}
}
Once discord.js emits the ready event, run will be executed, and the piece will then be unloaded since once is
set. This code is equivalent to the following:
- CommonJS
- ESM
- TypeScript
client.once('ready', (client) => {
const { username, id } = client.user;
container.logger.info(`Successfully logged in as ${username} (${id})`);
});
client.once('ready', (client) => {
const { username, id } = client.user;
container.logger.info(`Successfully logged in as ${username} (${id})`);
});
client.once('ready', (client) => {
const { username, id } = client.user!;
container.logger.info(`Successfully logged in as ${username} (${id})`);
});
Duplicated listeners
You may want to create multiple listeners executing different logic that listen to the same event for use cases like
auto-moderation. While Sapphire does not allow you to declare pieces with the same name, you can give them different
names and specify the same event field.
A common practice is to name the listeners by joining the event's name with the purpose of the piece. For example, if
you have a guildMemberAdd listener that sends a log to a channel and another that sends a greeting message, you can
name them guildMemberAddSendLog and guildMemberAddSendGreeting respectively, specifying
{ event: 'guildMemberAdd' } in both of their options.