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';
2
8
import { BadgeClient } from 'kypria-badge-sdk';
3
9
import { promises as fs } from 'fs';
4
10
import path from 'path';
@@ -22,9 +28,10 @@ type Relic = SponsorPingPayload & {
22
28
};
23
29
24
30
(async () => {
25
- // 1. Load & validate badge‐ locations.yml
31
+ // 1️⃣ Load & validate badge- locations.yml
26
32
const cfgPath = path.resolve(__ dirname, '../config/badge-locations.yml');
27
- let config: Config;
33
+ let config: Config = {};
34
+
28
35
try {
29
36
const raw = await fs.readFile(cfgPath, 'utf8');
30
37
config = (yaml.load(raw) as Config) ?? {};
@@ -33,49 +40,50 @@ type Relic = SponsorPingPayload & {
33
40
process.exit(1);
34
41
}
35
42
36
- // 2. Initialize clients
43
+ // 2️⃣ Initialize clients
37
44
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] });
39
46
40
47
discord.once('ready', () => {
41
48
console.log(` ✅ Discord bot ready as ${discord.user?.tag} ` );
42
49
});
43
50
44
- // 3. Handle sponsorPing
51
+ // 3️⃣ Handle sponsorPing events
45
52
badgeClient.on('sponsorPing', async ({ sponsorId, badgeName }: SponsorPingPayload) => {
46
- const channels = config[ badgeName] ?.drop_channels ?? [ ] ;
53
+ const channels = config[ badgeName] ?.drop_channels ?? [ ] ;
47
54
const timestamp = new Date().toISOString();
48
- const threadId = ` thread-${timestamp.replace(/[:.]/g, '-')} ` ;
55
+ const threadId = ` thread-${timestamp.replace(/[:.]/g, '-')} ` ;
49
56
const relic: Relic = { sponsorId, badgeName, timestamp, threadId };
50
57
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');
53
60
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);
55
63
await fs.writeFile(outPath, JSON.stringify(relic, null, 2));
56
64
57
- // 3b. Broadcast badge-drop
65
+ // 3b️⃣ Broadcast the badge ceremony
58
66
for (const chId of channels) {
59
67
try {
60
68
const channel = await discord.channels.fetch(chId);
61
69
if (
62
70
channel &&
63
- (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildPublicThread)
71
+ (channel.type === ChannelType.GuildText ||
72
+ channel.type === ChannelType.GuildPublicThread)
64
73
) {
65
- // wrap payload with embed title
66
74
await dropBadge(channel as TextChannel | ThreadChannel, {
67
75
...relic,
68
76
title: `🏅 ${badgeName}`,
69
77
});
70
78
} else {
71
- console.warn(`⚠️ Channel ${chId} is not a text/thread channel. Skipping. `);
79
+ console.warn(`⚠️ Skipping non- text/thread channel: ${chId} `);
72
80
}
73
81
} catch (err) {
74
- console.error(`❌ Failed to drop badge in channel ${chId}:`, err);
82
+ console.error(`❌ Error dropping badge in ${chId}:`, err);
75
83
}
76
84
}
77
85
});
78
86
79
- // 4. Connect to Discord
87
+ // 4️⃣ Connect to Discord
80
88
await discord.login(process.env.DISCORD_TOKEN);
81
89
})();
0 commit comments