Skip to content

Collector

Collector<K, V> collects arbitrary event items passing a filter, until a time limit, a maximum count, or a manual stop is reached. Use it to build interactive flows like confirmation dialogs or paginated responses.

import { Collector } from "lunibee";
new Collector<K, V>(options?: CollectorOptions<V>)
Option Type Description
filter (item: V) => boolean | Promise<boolean> Accepts or rejects each item.
time number Auto-stops after this many milliseconds.
max number Auto-stops after collecting this many items.
maxProcessed number Auto-stops after processing this many items (whether accepted or not).
Property Type Description
collected Map<K, V> All collected key-value pairs.
ended boolean Whether the collector has stopped.
endReason string | undefined Why the collector stopped: "time", "limit", "user", "processedLimit".
totalProcessed number Total items seen (including rejected ones).

Presents a candidate item. If it passes the filter, it is added to collected and the "collect" event fires.

client.on("messageCreate", async (message) => {
if (message.channelId === targetChannelId) {
await collector.handle(message.id, message);
}
});

Manually stops the collector. Fires the "end" event.

collector.stop("user");

Returns a Promise that resolves with the next collected item, or rejects if the collector ends first.

try {
const nextMsg = await collector.next();
console.log("Got:", nextMsg.content);
} catch {
console.log("Collector ended before next item.");
}

Extend EventEmitter — use collector.on(event, listener):

Event Args Fires when
"collect" (item: V) An item passes the filter.
"end" (collected: Map<K,V>, reason: string) The collector stops for any reason.

Collect button confirmations

import { Collector } from "lunibee";
async function confirmAction(channel, userId: string): Promise<boolean> {
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder().setCustomId("confirm").setLabel("Confirm").setStyle(ButtonStyle.Success),
new ButtonBuilder().setCustomId("cancel").setLabel("Cancel").setStyle(ButtonStyle.Danger),
);
const msg = await channel.send({ content: "Are you sure?", components: [row] });
const collector = new Collector<string, ComponentInteraction>({
filter: (i) => i.customId === "confirm" || i.customId === "cancel",
time: 30_000, // 30 seconds
max: 1, // stop after one click
});
// Feed interactions into the collector
const handler = async (interaction: Interaction) => {
if (interaction.isComponent() && interaction.message?.id === msg.id) {
await collector.handle(interaction.id, interaction as ComponentInteraction);
}
};
client.on("interactionCreate", handler);
return new Promise((resolve) => {
collector.on("collect", async (interaction) => {
await interaction.update({ content: "Processing...", components: [] });
});
collector.on("end", (collected, reason) => {
client.off("interactionCreate", handler);
if (reason === "time") {
msg.edit({ content: "⏱️ Timed out.", components: [] });
resolve(false);
} else {
const first = [...collected.values()][0];
resolve(first?.customId === "confirm");
}
});
});
}

Collect 5 messages from a specific user

const collector = new Collector<string, Message>({
filter: (msg) => msg.author.id === targetUserId,
max: 5,
time: 60_000,
});
client.on("messageCreate", async (message) => {
await collector.handle(message.id, message);
});
collector.on("end", (collected, reason) => {
console.log(`Collected ${collected.size} messages (${reason})`);
});