|
1 |
| -import { |
2 |
| - Client, |
3 |
| - GatewayIntentBits, |
4 |
| - TextChannel, |
5 |
| - ThreadChannel, |
6 |
| - ChannelType |
7 |
| -} from 'discord.js'; |
8 |
| -import { BadgeClient } from 'kypria-badge-sdk'; |
9 |
| -import { promises as fs } from 'fs'; |
10 |
| -import path from 'path'; |
11 |
| -import yaml from 'js-yaml'; |
12 |
| -import { dropBadge } from './utils/dropBadge'; |
13 |
| - |
14 |
| -type BadgeConfig = { |
15 |
| - drop_channels: string[]; |
16 |
| -}; |
17 |
| - |
18 |
| -type Config = Record<string, BadgeConfig>; |
19 |
| - |
20 |
| -type SponsorPingPayload = { |
21 |
| - sponsorId: string; |
22 |
| - badgeName: string; |
23 |
| -}; |
24 |
| - |
25 |
| -type Relic = SponsorPingPayload & { |
26 |
| - timestamp: string; |
27 |
| - threadId: string; |
28 |
| -}; |
29 |
| - |
30 |
| -(async () => { |
31 |
| - // Load badge config |
32 |
| - const cfgPath = path.resolve(__dirname, '../config/badge-locations.yml'); |
33 |
| - let config: Config = {}; |
34 |
| - try { |
35 |
| - const raw = await fs.readFile(cfgPath, 'utf8'); |
36 |
| - config = (yaml.load(raw) as Config) ?? {}; |
37 |
| - } catch (err) { |
38 |
| - console.error('❌ badge-locations.yml failed to load:', err); |
39 |
| - process.exit(1); |
40 |
| - } |
41 |
| - |
42 |
| - // Initialize clients |
43 |
| - const badgeClient = new BadgeClient({ apiToken: process.env.BADGE_API_TOKEN! }); |
44 |
| - const discord = new Client({ intents: [GatewayIntentBits.Guilds] }); |
45 |
| - |
46 |
| - discord.once('ready', () => { |
47 |
| - console.log(`✅ Discord bot logged in as ${discord.user?.tag}`); |
48 |
| - }); |
49 |
| - |
50 |
| - // Handle sponsorPing |
51 |
| - badgeClient.on('sponsorPing', async ({ sponsorId, badgeName }: SponsorPingPayload) => { |
52 |
| - const channels = config[badgeName]?.drop_channels ?? []; |
53 |
| - const timestamp = new Date().toISOString(); |
54 |
| - const threadId = `thread-${timestamp.replace(/[:.]/g, '-')}`; |
55 |
| - const relic: Relic = { sponsorId, badgeName, timestamp, threadId }; |
56 |
| - |
57 |
| - // Write audit log |
58 |
| - const logDir = path.resolve(__dirname, '../threads'); |
59 |
| - await fs.mkdir(logDir, { recursive: true }); |
60 |
| - const logPath = path.join(logDir, `relic-drop--${badgeName}--${timestamp}.json`); |
61 |
| - await fs.writeFile(logPath, JSON.stringify(relic, null, 2)); |
62 |
| - |
63 |
| - // Broadcast drop |
64 |
| - for (const chId of channels) { |
65 |
| - try { |
66 |
| - const channel = await discord.channels.fetch(chId); |
67 |
| - if ( |
68 |
| - channel && |
69 |
| - (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildPublicThread) |
70 |
| - ) { |
71 |
| - await dropBadge(channel as TextChannel | ThreadChannel, { |
72 |
| - ...relic, |
73 |
| - title: `🏅 ${badgeName}` |
74 |
| - }); |
75 |
| - } else { |
76 |
| - console.warn(`⚠️ Skipped invalid channel: ${chId}`); |
77 |
| - } |
78 |
| - } catch (err) { |
79 |
| - console.error(`❌ Drop failed in channel ${chId}:`, err); |
80 |
| - } |
81 |
| - } |
82 |
| - }); |
83 |
| - |
84 |
| - await discord.login(process.env.DISCORD_TOKEN); |
85 |
| -})(); |
| 1 | +// 🔮 INIT CLIENTS — Discord & BadgeClient |
| 2 | +// 📜 LOAD BADGE CONFIG — badge-locations.yml |
| 3 | +// 🧿 LISTEN FOR SPONSOR PING — Trigger dropBadge() |
0 commit comments