History Bot

history-bot.js · 109 lines

Message history example: preload, fetch, search, bulk delete.

View on GitHubnode examples/history-bot.js
javascript
/**
 * Message history example: preload, fetch, search, bulk delete.
 *
 * Commands:
 *   !latest             preload the current channel (bot-legal bulk warm)
 *   !history [n]        fetch the last n messages (default 10, max 50)
 *   !search <query>     search in this guild (scope: current)
 *   !purge <n>          bulk-delete the last n messages (1-100)
 *
 * Usage (from repo root after pnpm install && pnpm run build):
 *   FLUXER_BOT_TOKEN=your_token node examples/history-bot.js
 *
 * See: https://fluxer.js.org/guides/message-history/
 */

import { Client, ErrorCodes, Events, FluxerError, parsePrefixCommand } from '@fluxerjs/core';

const PREFIX = '!';
const client = new Client();

client.on(Events.Ready, () => {
  console.log(
    `Logged in as ${client.user?.username}. Commands: !latest, !history, !search, !purge`,
  );
});

client.on(Events.MessageCreate, async (message) => {
  if (message.author.bot || !message.content) return;
  const parsed = parsePrefixCommand(message.content, PREFIX);
  if (!parsed) return;

  const { command, args } = parsed;
  const channel = message.channel ?? (await message.resolveChannel().catch(() => null));
  if (!channel?.isTextBased()) {
    await message.reply('This channel cannot hold messages.');
    return;
  }

  try {
    if (command === 'latest') {
      const latest = await client.preloadMessages([channel.id]);
      const msg = latest[channel.id];
      await message.reply(msg ? `Latest cached id: ${msg.id}` : 'Channel is empty.');
      return;
    }

    if (command === 'history') {
      const limit = Math.min(50, Math.max(1, Number(args[0]) || 10));
      const page = await channel.messages.fetch({ limit });
      const lines = [...page.values()].map(
        (m) => `${m.author?.username ?? '?'}: ${m.content ?? ''}`,
      );
      await message.reply(
        lines.length ? lines.slice(0, 15).join('\n').slice(0, 1800) : 'No messages.',
      );
      return;
    }

    if (command === 'search') {
      const query = args.join(' ').trim();
      if (!query) {
        await message.reply('Usage: `!search <query>`');
        return;
      }
      const results = await client.searchMessages({
        content: query,
        contextGuildId: message.guildId ?? undefined,
        contextChannelId: channel.id,
        hitsPerPage: 8,
      });
      if ('indexing' in results) {
        await message.reply('Search is still indexing this channel.');
        return;
      }
      const hits = results.messages;
      const preview = hits
        .map((m) => `${m.author?.username ?? '?'}: ${m.content ?? ''}`)
        .join('\n');
      const header = `${results.total} hits (page ${results.page}, ${results.hitsPerPage} per page)`;
      await message.reply(preview ? `${header}\n${preview}`.slice(0, 1800) : 'No hits.');
      return;
    }

    if (command === 'purge') {
      const n = Number(args[0]);
      if (!Number.isInteger(n) || n < 1 || n > 100) {
        await message.reply('Usage: `!purge <1-100>`');
        return;
      }
      const deleted = await channel.bulkDelete(n);
      await message.reply(`Deleted ${deleted.length} message(s).`);
    }
  } catch (err) {
    const code = err instanceof FluxerError ? err.code : null;
    console.error('Command error:', code ?? err);
    if (code === ErrorCodes.InvalidChannelType) {
      await message.reply('That channel type cannot send or fetch messages.').catch(() => {});
      return;
    }
    await message
      .reply(err instanceof Error ? err.message : 'Something went wrong.')
      .catch(() => {});
  }
});

client.on(Events.Error, (err) => console.error('Client error:', err));

await client.login(process.env.FLUXER_BOT_TOKEN);
Questions?Join the Fluxer community for help with the SDK.Join Fluxer