Sharded Bot

sharded-bot.js · 130 lines

Process-sharded bot (beta): manager + child entry in one file.

View on GitHubnode examples/sharded-bot.js
javascript
/**
 * Process-sharded bot (beta): manager + child entry in one file.
 *
 * Usage (from repo root after pnpm install && pnpm run build):
 *   FLUXER_BOT_TOKEN=your_token TOTAL_SHARDS=2 node examples/sharded-bot.js
 *
 * Prefix commands: !shard  !where  !guilds
 * When forked as a child, FLUXER_SHARD_* env vars are set by ShardingManager.
 * The manager also injects FLUXER_TOKEN; prefer FLUXER_BOT_TOKEN in your own scripts.
 */

import { Client, Events, parsePrefixCommand } from '@fluxerjs/core';
import { attachShardClientUtil, BETA_SHARDING_WARNING, ShardingManager } from '@fluxerjs/sharding';
import { fileURLToPath } from 'node:url';

const isChild = typeof process.send === 'function' && Boolean(process.env.FLUXER_SHARD_IDS);

if (!isChild) {
  if (typeof process.emitWarning === 'function') {
    process.emitWarning(BETA_SHARDING_WARNING, {
      type: 'FluxerShardingBeta',
      code: 'FLUXER_SHARDING_BETA',
    });
  }

  const token = process.env.FLUXER_BOT_TOKEN;
  if (!token) {
    console.error('Set FLUXER_BOT_TOKEN');
    process.exit(1);
  }

  const totalShards = Number(process.env.TOTAL_SHARDS ?? '2');
  const manager = new ShardingManager(fileURLToPath(import.meta.url), {
    token,
    totalShards,
    shardsPerProcess: 1,
    spawnDelay: 2_000,
  });

  manager.on('shardCreate', (shard) => console.log(`[manager] spawned process ${shard.id}`));
  manager.on('shardReady', (shard) => console.log(`[manager] ready process ${shard.id}`));
  manager.on('error', (err) => console.error('[manager]', err));

  await manager.spawn();
  console.log(`[manager] running ${manager.shardCount} shards`);
} else {
  const client = new Client({ gatewayDebug: false });
  const shard = attachShardClientUtil(client);

  client.on(Events.Ready, () => {
    console.log(`[shard ${shard.ids.join(',')}] ready as ${client.user?.username}`);
    shard.notifyReady();
  });

  client.on(Events.ShardReady, (id) => console.log(`[shard] gateway ${id} ready`));

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

    if (parsed.command === 'shard') {
      const guildId = message.guildId;
      const mapped =
        guildId != null ? ` · this guild maps to shard ${shard.shardIdForGuildId(guildId)}` : '';
      await message.reply(
        `This process owns gateway shards [${shard.ids.join(', ')}] of ${shard.count}${mapped}`,
      );
      return;
    }

    if (parsed.command === 'where') {
      const guildId = message.guildId;
      if (!guildId) {
        await message.reply('Use !where in a guild (guild → shard is fixed by formula)');
        return;
      }
      try {
        const expected = shard.shardIdForGuildId(guildId);
        const reports = await shard.broadcastEval(
          (c, id) => {
            const util = c.shard;
            const guilds = c.guilds;
            return {
              processId: util?.id ?? util?.ids?.[0] ?? -1,
              shardIds: util?.ids ?? [],
              hasGuild: Boolean(guilds?.get?.(id)),
            };
          },
          { context: guildId },
        );
        const owners = reports
          .filter((r) => r?.hasGuild)
          .map((r) => `process ${r.processId} shards=[${(r.shardIds ?? []).join(',')}]`);
        await message.reply(
          [
            `Guild ${guildId}`,
            `Formula → shard ${expected} of ${shard.count}  ((id >> 22) % ${shard.count})`,
            `Cached on: ${owners.length ? owners.join('; ') : 'none (not yet on this bot / wrong shard)'}`,
            `Replied from process ${shard.id} shards=[${shard.ids.join(',')}]`,
          ].join('\n'),
        );
      } catch (err) {
        await message.reply(`Error: ${err instanceof Error ? err.message : String(err)}`);
      }
      return;
    }

    if (parsed.command === 'guilds') {
      try {
        const sizes = await shard.broadcastEval((c) => {
          const g = c.guilds;
          return Number(g?.size ?? 0);
        });
        const total = sizes.reduce((a, b) => a + Number(b || 0), 0);
        await message.reply(`Guilds across shards: ${total} (${sizes.join(', ')})`);
      } catch (err) {
        await message.reply(`Error: ${err instanceof Error ? err.message : String(err)}`);
      }
    }
  });

  const token = process.env.FLUXER_BOT_TOKEN;
  if (!token) {
    console.error('Set FLUXER_BOT_TOKEN');
    process.exit(1);
  }
  await client.login(token);
}
Questions?Join the Fluxer community for help with the SDK.Join Fluxer