Events & Clocks
Listen to Discord gateway events and schedule recurring background tasks.
Medusa separates time-triggered tasks and Discord gateway events into two dedicated building blocks: Events for responding to gateway events emitted by Discord.js, and Clocks for running scheduled background jobs via cron expressions.
Discord Gateway Events
Gateway listeners reside in src/events/ and load from every *.js file located directly in that directory. Each file exports a class extending Events from #medusa/modules.
import { Events } from "#medusa/modules";
import { Events as DiscordEvents } from "discord.js";
export class MessageCreateEvent extends Events {
constructor(medusa) {
super(medusa, {
name: "messageCreate",
event: DiscordEvents.MessageCreate,
once: false
});
}
async run(message) {
if (!message || message.author.bot || !message.guild) return;
const handler = this.medusa.modules.handlers.get(this.module, "items");
if (handler) {
await handler.scanMessage(message);
}
}
}Event Constructor Options
The configuration object passed to super(medusa, meta) accepts the following properties:
| Property | Type | Default | Description |
|---|---|---|---|
name | string | Required | Unique event identifier within the module. |
event | string | Required | Discord.js event identifier (e.g. Events.MessageCreate, Events.GuildMemberAdd, Events.VoiceStateUpdate). |
once | boolean | false | When true, Medusa registers the listener using client.once() instead of client.on(). |
Event Methods
| Method | Signature | Description |
|---|---|---|
getName() | () => string | Returns the event name. |
getEvent() | () => string | Returns the Discord.js event key string. |
isOnce() | () => boolean | Returns whether the event triggers only once. |
run() | (...args) => Promise<void> | Invoked whenever Discord emits the target gateway event with the arguments supplied by Discord.js. |
Handling Discord Component Interactions
When users click buttons, select items from dropdown menus, or submit modal dialogs, Discord emits an interactionCreate event. Medusa parses valid custom IDs and populates interaction.customIdParsed:
import { Events } from "#medusa/modules";
import { Events as DiscordEvents, MessageFlags } from "discord.js";
export class InteractionCreateEvent extends Events {
constructor(medusa) {
super(medusa, {
name: "exampleInteractionCreate",
event: DiscordEvents.InteractionCreate,
once: false
});
}
async run(interaction) {
if (!interaction || !interaction.guildId) return;
if (interaction.isButton()) {
if (!interaction.customIdParsed || interaction.customIdParsed.module !== this.module) return;
const action = interaction.customIdParsed.name;
switch (action) {
case "confirm":
return interaction.reply({
embeds: [this.medusa.embeds.success("Confirmed.")],
flags: MessageFlags.Ephemeral
});
default:
return interaction.deferUpdate().catch(() => {});
}
}
if (interaction.isStringSelectMenu()) {
if (!interaction.customIdParsed || interaction.customIdParsed.module !== this.module) return;
const selected = interaction.values[0];
return interaction.reply({
embeds: [this.medusa.embeds.info("info", `Selected: ${selected}`)],
flags: MessageFlags.Ephemeral
});
}
if (interaction.isModalSubmit()) {
if (!interaction.customIdParsed || interaction.customIdParsed.module !== this.module) return;
const title = interaction.fields.getTextInputValue("title");
return interaction.reply({
embeds: [this.medusa.embeds.success(`Received title: ${title}`)],
flags: MessageFlags.Ephemeral
});
}
}
}Scheduled Clocks
Background schedules reside in src/clocks/ and load from every *.js file located directly in that directory. Each file exports a class extending Clocks from #medusa/modules.
import { Clocks } from "#medusa/modules";
export class CleanupClock extends Clocks {
constructor(medusa) {
super(medusa, {
name: "cleanup",
schedule: "0 * * * *"
});
}
async run() {
const handler = this.medusa.modules.handlers.get(this.module, "items");
if (!handler) return;
this.medusa.console.log(this.medusa.console.levels.info, "Running scheduled cleanup routine...");
await handler.purgeExpired();
}
}Clock Constructor Options
The configuration object passed to super(medusa, meta) accepts the following properties:
| Property | Type | Default | Description |
|---|---|---|---|
name | string | Required | Unique clock identifier within the module. |
schedule | string | Required | Standard 5-field cron expression string evaluated by croner. |
Standard Cron Syntax
The schedule string uses standard 5-field cron format:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of the month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday)
│ │ │ │ │
* * * * *Common Cron Expressions
| Expression | Execution Interval |
|---|---|
* * * * * | Executes every minute. |
*/5 * * * * | Executes every 5 minutes. |
0 * * * * | Executes hourly at minute 0. |
0 0 * * * | Executes daily at midnight UTC. |
0 0 * * 0 | Executes weekly on Sunday at midnight UTC. |
Clock Lifecycle
When Medusa finishes loading all modules, ClockInterface.schedule() initializes a Cron instance for each registered clock. When Medusa stops or deloads modules, ClockInterface.stop() cancels all running cron timers to ensure clean memory shutdown.

