1- import { Client, GatewayIntentBits, TextChannel, ThreadChannel, ChannelType, EmbedBuilder } from 'discord.js';
1+ /**
2+ * 🏺 Ritual Scroll: badgeEmitter.ts
3+ * Description: Listens for sponsorPing events, logs relic drops, and broadcasts
4+ * badge-ceremony embeds across configured Discord channels.
5+ * /
6+
7+ import { Client, GatewayIntentBits, TextChannel, ThreadChannel, ChannelType } from 'discord.js';
28import { BadgeClient } from 'kypria-badge-sdk';
39import { promises as fs } from 'fs';
410import path from 'path';
@@ -22,9 +28,10 @@ type Relic = SponsorPingPayload & {
2228};
2329
2430(async () => {
25- // 1. Load & validate badge‐ locations.yml
31+ // 1️⃣ Load & validate badge- locations.yml
2632 const cfgPath = path.resolve(__ dirname, '../config/badge-locations.yml');
27- let config: Config;
33+ let config: Config = {};
34+
2835 try {
2936 const raw = await fs.readFile(cfgPath, 'utf8');
3037 config = (yaml.load(raw) as Config) ?? {};
@@ -33,49 +40,50 @@ type Relic = SponsorPingPayload & {
3340 process.exit(1);
3441 }
3542
36- // 2. Initialize clients
43+ // 2️⃣ Initialize clients
3744 const badgeClient = new BadgeClient({ apiToken: process.env.BADGE_API_TOKEN! });
38- const discord = new Client({ intents: [ GatewayIntentBits.Guilds] });
45+ const discord = new Client({ intents: [ GatewayIntentBits.Guilds] });
3946
4047 discord.once('ready', () => {
4148 console.log(` ✅ Discord bot ready as ${discord.user?.tag} ` );
4249 });
4350
44- // 3. Handle sponsorPing
51+ // 3️⃣ Handle sponsorPing events
4552 badgeClient.on('sponsorPing', async ({ sponsorId, badgeName }: SponsorPingPayload) => {
46- const channels = config[ badgeName] ?.drop_channels ?? [ ] ;
53+ const channels = config[ badgeName] ?.drop_channels ?? [ ] ;
4754 const timestamp = new Date().toISOString();
48- const threadId = ` thread-${timestamp.replace(/[:.]/g, '-')} ` ;
55+ const threadId = ` thread-${timestamp.replace(/[:.]/g, '-')} ` ;
4956 const relic: Relic = { sponsorId, badgeName, timestamp, threadId };
5057
51- // 3a. Audit-log relic
52- const logDir = path.resolve(__dirname, '../threads');
58+ // 3a️⃣ Audit-log the relic
59+ const logDir = path.resolve(__dirname, '../threads');
5360 await fs.mkdir(logDir, { recursive: true });
54- const outPath = path.join(logDir, `relic-drop--${badgeName}--${timestamp}.json`);
61+ const fileName = `relic-drop--${badgeName}--${timestamp}.json`;
62+ const outPath = path.join(logDir, fileName);
5563 await fs.writeFile(outPath, JSON.stringify(relic, null, 2));
5664
57- // 3b. Broadcast badge-drop
65+ // 3b️⃣ Broadcast the badge ceremony
5866 for (const chId of channels) {
5967 try {
6068 const channel = await discord.channels.fetch(chId);
6169 if (
6270 channel &&
63- (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildPublicThread)
71+ (channel.type === ChannelType.GuildText ||
72+ channel.type === ChannelType.GuildPublicThread)
6473 ) {
65- // wrap payload with embed title
6674 await dropBadge(channel as TextChannel | ThreadChannel, {
6775 ...relic,
6876 title: `🏅 ${badgeName}`,
6977 });
7078 } else {
71- console.warn(`⚠️ Channel ${chId} is not a text/thread channel. Skipping. `);
79+ console.warn(`⚠️ Skipping non- text/thread channel: ${chId} `);
7280 }
7381 } catch (err) {
74- console.error(`❌ Failed to drop badge in channel ${chId}:`, err);
82+ console.error(`❌ Error dropping badge in ${chId}:`, err);
7583 }
7684 }
7785 });
7886
79- // 4. Connect to Discord
87+ // 4️⃣ Connect to Discord
8088 await discord.login(process.env.DISCORD_TOKEN);
8189})();
0 commit comments