import 'dotenv/config'; import { Client, GatewayIntentBits, Partials, Events, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionsBitField, EmbedBuilder } from 'discord.js'; import fs from 'fs'; import { NEWS_CHANNEL_MESSAGE, INELLIGIBLE_MESSAGE, KEY_DM_MESSAGE } from './messages.js'; const TOKEN = process.env.DISCORD_TOKEN; const DATA_FILE = './key_data.json'; function loadData() { if (!fs.existsSync(DATA_FILE)) { return { messageId: null, joinCutoff: null, users: [], keys: [], loggingChannel: null }; } const data = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8')); if (!data.keys) data.keys = []; return data; } function saveData(data) { fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2)); } let data = loadData(); const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.DirectMessages], partials: [Partials.Channel] }); client.once(Events.ClientReady, () => { console.log(`Logged in as ${client.user.tag}`); }); client.on(Events.InteractionCreate, async interaction => { // Slash command: /register if (interaction.isChatInputCommand() && interaction.commandName === 'register') { if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) { return interaction.reply({ content: 'You must be an administrator.', ephemeral: true }); } // Check if loggingChannel is set if (!data.loggingChannel) { return interaction.reply({ content: 'You must set a logging channel first using `/setlogging`. Registration message not sent.', ephemeral: true }); } const embed = new EmbedBuilder() .setDescription('**NOTE**: Click "Claim Key" again if you need your key re-sent.') .setColor(0xff0000); const button = new ButtonBuilder() .setCustomId('register_button') .setLabel('Claim Key') .setStyle(ButtonStyle.Success); const row = new ActionRowBuilder().addComponents(button); const sent = await interaction.channel.send({ content: NEWS_CHANNEL_MESSAGE, embeds: [embed], components: [row] }); data.messageId = sent.id; saveData(data); await interaction.reply({ content: 'Registration message sent!', ephemeral: true }); } // Slash command: /load if (interaction.isChatInputCommand() && interaction.commandName === 'load') { if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) { return interaction.reply({ content: 'You must be an administrator.', ephemeral: true }); } const file = interaction.options.getAttachment('file'); if (!file) { return interaction.reply({ content: 'You must attach a file.', ephemeral: true }); } // Download the file const response = await fetch(file.url); const text = await response.text(); const keys = text.split('\n').map(line => line.trim()).filter(line => line.length > 0); const yesButton = new ButtonBuilder() .setCustomId('load_yes') .setLabel('Yes') .setStyle(ButtonStyle.Success); const noButton = new ButtonBuilder() .setCustomId('load_no') .setLabel('No') .setStyle(ButtonStyle.Danger); const row = new ActionRowBuilder().addComponents(yesButton, noButton); // Store keys temporarily in memory for this user client.tempKeyLoads = client.tempKeyLoads || {}; client.tempKeyLoads[interaction.user.id] = keys; await interaction.reply({ content: `Found ${keys.length} steam keys. Do you wish to load these into the key vault?`, components: [row], ephemeral: true }); } // Handle load confirmation buttons if (interaction.isButton() && (interaction.customId === 'load_yes' || interaction.customId === 'load_no')) { if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) { return interaction.reply({ content: 'You must be an administrator.', ephemeral: true }); } if (!client.tempKeyLoads || !client.tempKeyLoads[interaction.user.id]) { return interaction.reply({ content: 'No pending key load found.', ephemeral: true }); } if (interaction.customId === 'load_no') { delete client.tempKeyLoads[interaction.user.id]; await interaction.update({ content: 'Key load cancelled.', components: [] }); return; } // Yes pressed const keys = client.tempKeyLoads[interaction.user.id]; data = loadData(); // Reload in case of concurrent changes data.keys = data.keys || []; data.keys.push(...keys); saveData(data); delete client.tempKeyLoads[interaction.user.id]; await interaction.update({ content: `Loaded ${keys.length} keys into the key vault.`, components: [] }); } // Button interaction for register if (interaction.isButton() && interaction.customId === 'register_button') { if (interaction.message.id !== data.messageId) return; // Already registered? let user = data.users.find(u => u.id === interaction.user.id); if (user && user.key) { const message = KEY_DM_MESSAGE.replace('{key}', `https://store.steampowered.com/account/registerkey?key=${user.key}`); await interaction.user.send(message); return interaction.reply({ content: 'Key successfully re-sent! Please check your DMs.', ephemeral: true }); } // Check join date const member = await interaction.guild.members.fetch(interaction.user.id); const joinDate = member.joinedAt; const cutoffDate = data.joinCutoff ? new Date(data.joinCutoff) : null; if (cutoffDate) { // Compare only the date parts (YYYY-MM-DD) const join = joinDate.toISOString().slice(0, 10); const cutoff = cutoffDate.toISOString().slice(0, 10); if (new Date(join) > new Date(cutoff)) { return interaction.reply({ content: INELLIGIBLE_MESSAGE, ephemeral: true }); } } // Assign key data = loadData(); // Reload in case of concurrent changes if (!data.keys || data.keys.length === 0) { // No keys left if (data.loggingChannel) { const channel = await interaction.guild.channels.fetch(data.loggingChannel).catch(() => console.log(`Failed to fetch logging channel ${data.loggingChannel}`)); if (channel) { channel.send('WARNING: No more Steam keys available for registration! Please run `/load` to add more keys.'); } } return interaction.reply({ content: 'There was an error retrieving your keys. Please reach out to <@404872989188816906> in <#580122303342313473>!', ephemeral: true }); } // Assign and remove key from vault const key = data.keys.shift(); user = { id: interaction.user.id, key }; data.users.push(user); saveData(data); await interaction.reply({ content: 'Successfully claimed key! Check your DMs.', ephemeral: true }); try { const message = KEY_DM_MESSAGE.replace('{key}', `https://store.steampowered.com/account/registerkey?key=${key}`); await interaction.user.send(message); } catch (e) { return interaction.reply({ content: 'Unable to send DM. Please change your privacy settings or reach out for support!', ephemeral: true }); } } // Slash command: /setlogging if (interaction.isChatInputCommand() && interaction.commandName === 'setlogging') { if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) { return interaction.reply({ content: 'You must be an administrator.', ephemeral: true }); } const channel = interaction.options.getChannel('channel'); if (!channel) { return interaction.reply({ content: 'Invalid channel.', ephemeral: true }); } data = loadData(); data.loggingChannel = channel.id; saveData(data); return interaction.reply({ content: `Logging channel set to <#${channel.id}>.`, ephemeral: true }); } }); client.login(TOKEN);