const fs = require('fs'); const dotenv = require('dotenv'); dotenv.config(); const colors = require('colors'); const Discord = require('discord.js'); const client = new Discord.Client({ intents: [ "Guilds", "GuildMembers", "GuildInvites" ] }); const { REST, Routes } = require('discord.js'); const rest = new REST({ version: '10' }).setToken(process.env.BOT_TOKEN); const sqlite3 = require('sqlite3').verbose(); const db = new sqlite3.Database(process.env.DB_PATH); // DB migrations from files in ./migrations const migrations = fs.readdirSync('./migrations').sort(); migrations.forEach((migration) => { const sql = fs.readFileSync(`./migrations/${migration}`).toString(); // get first line for comment to log const comment = sql.split('\n')[0]; console.log(`${colors.cyan('INFO')} Running migration: ${colors.yellow(comment)}`); db.run(sql); }); // Handle bot ready client.on('ready', async () => { console.log(`${colors.cyan('INFO')} Logged in as ${colors.green(client.user.displayName)}!`); // Get all invites client.invites = []; // Update Invites console.log(`${colors.cyan("[INFO]")} Fetching Invites...`); const fetchInvites = async () => { await client.guilds.cache.forEach(guild => { //on bot start, fetch all guilds and fetch all invites to store thisGuild = [] guild.invites.fetch().then(guildInvites => { guildInvites.each(guildInvite => { client.invites[guildInvite.code] = guildInvite.uses thisGuild.push(guildInvite.code) }) }).then(() => { console.log(`${colors.cyan("[INFO]")} Fetched ${thisGuild.length} Invites for ${colors.green(guild.name)}`); }) guild.fetchVanityData().then(vanityData => { client.invites[vanityData.code] = vanityData.uses }).catch(err => { // Do nothing }) }) } fetchInvites(); // Set interval to update invites every 5 minutes, just in case something desyncs setInterval(fetchInvites, 300000); // Register commands const commands = [ { name: "logchannel", description: "Set the channel to log invites", options: [ { name: "channel", description: "The channel to log invites to", type: 7, required: true }, { name: "log_create", description: "Log invite creation", type: 5, required: false } ], default_member_permissions: 32 } ] await (async () => { try { console.log(`${colors.cyan("[INFO]")} Registering Commands...`) let start = Date.now() //Global await rest.put(Routes.applicationCommands(client.user.id), { body: commands }) console.log(`${colors.cyan("[INFO]")} Successfully registered commands. Took ${colors.green((Date.now() - start) / 1000)} seconds.`); } catch (error) { console.error(error); } })(); }) // Handle invite creation client.on('inviteCreate', (invite) => { //if someone creates an invite while bot is running, update store client.invites[invite.code] = invite.uses // Check the db for the channel to log to, guild id is column `id` in table `guilds`, channel id is column `channel` db.get(`SELECT * FROM guilds WHERE id = ?`, [invite.guild.id], async (err, row) => { if (err) { console.error(err); return; } if (!row) return; if(!row.log_create) return; client.channels.fetch(row.channel).then(channel => { if (!channel) return; // They probably set perms wrong channel.send({ embeds: [{ color: 0x00ffff, fields: [ { name: "New Invite", // inline check, if expiry is in over 100 years, then it's never, otherwise it's the date // ${invite.expiresTimestamp > 95617584000 ? "Never" : `` value: `Code: ${invite.code}\nMax Uses: ${invite.maxUses || "∞"}\nExpires: ${invite.expiresAt <= 0 ? "Never" : ``}\nCreated at: ` }, { name: "Guild", value: `${invite.guild.name}` }, { name: "Channel", value: `${invite.channel.name} <#${invite.channel.id}>` }, { name: "Inviter", value: `${invite.inviter?.id ?`${invite.inviter} (${invite.inviter.displayName})` : "Custom Invite URL (Through Bot/Embed)"}` } ] }] }); }).catch(err => { console.log(`${colors.red('ERROR')} ${err.stack}`) }) }) }); // Handle new member client.on('guildMemberAdd', async (member) => { // We're just gonna always send invite logs, even if we're not monitoring them let guild = member.guild db.get(`SELECT * FROM guilds WHERE id = ?`, [guild.id], (err, row) => { if (err) { console.error(err); return; } if (!row) return; client.channels.fetch(row.channel.toString()).then(channel => { if (!channel) return; // They probably set perms wrong member.guild.invites.fetch().then(async guildInvites => { //get all guild invites sent = false; guildInvites.forEach(invite => { //basically a for loop over the invites if (invite.uses != client.invites[invite.code]) { //if it doesn't match what we stored: if (sent) return console.log(`${colors.red("[ERROR]")} Something VERY bad has happened, and we seem to have lost track of invites.\nCurrent Invite Table:\n${JSON.stringify(client.invites, null, 2)}`); //if we already sent a message, this is a fail safe to prevent spam channel.send({ embeds: [{ color: 0x00ff00, fields: [ { name: "New Member", value: `${member} (${member.user.displayName})\nJoined at: \nAccount Created: ` }, { name: "Invite", value: `Inviter: ${invite.inviter?.id ? `${invite.inviter} (${invite.inviter.displayName})` : "Custom Invite URL (Through Bot/Embed)"}\nCode: ${invite.code}\nUses: ${invite.uses}/${invite.maxUses ||"∞"}\nExpires: ${invite.expiresAt <= 0 ? "Never" : ``}\nCreated at: ` }, { name: "Guild", value: `${guild.name}` } ] }] }); client.invites[invite.code] = invite.uses } }) }) // Handle vanity URLs member.guild.fetchVanityData().then(vanityData => { if (vanityData.uses != client.invites[vanityData.code]) { // They used the vanity URL channel.send({ embeds: [{ color: 0x00ff00, fields: [ { name: "New Member", value: `${member} (${member.user.displayName})\nJoined at: \nAccount Created: ` }, { name: "Invite", value: `Vanity Code: ${vanityData.code}\nUses: ${vanityData.uses}` }, { name: "Guild", value: `${guild.name}` } ] }] }); } }).catch(err => { // Do nothing }) }) }); }) // Guild create client.on('guildCreate', async (guild) => { // Get invites guild.invites.fetch().then(guildInvites => { guildInvites.each(invite => { client.invites[invite.code] = invite.uses }) }) guild.fetchVanityData().then(vanityData => { client.invites[vanityData.code] = vanityData.uses }).catch(err => { // Do nothing }) }); // Guild delete client.on('guildDelete', async (guild) => { // Remove invites guild.invites.fetch().then(guildInvites => { guildInvites.each(invite => { delete client.invites[invite.code]; }) }) guild.fetchVanityData().then(vanityData => { delete client.invites[vanityData.code]; }).catch(err => { // Do nothing }) }); // Handle Commands client.on('interactionCreate', async interaction => { if (!interaction.isCommand()) return; if (interaction.commandName === 'logchannel') { if (!interaction.member.permissions.has('MANAGE_GUILD')) { interaction.reply({ content: "You do not have permission to use this command.", ephemeral: true }); return; } log_create = interaction.options.getBoolean('log_create'); if (log_create == null) log_create = true; const channel = interaction.options.getChannel('channel').id; db.run(`INSERT OR REPLACE INTO guilds (id, channel, log_create) VALUES (?, ?, ?)`, [interaction.guild.id.toString(), channel.toString(), log_create], (err) => { if (err) { console.error(err); interaction.reply({ content: "An error occurred while setting the log channel.", ephemeral: true }); return; } interaction.reply({ content: `The log channel has been set to <#${channel}>.`, ephemeral: true }); }); } }); // Handle unhandled rejections and exceptions process.on('unhandledRejection', error => { console.error(`${colors.red('ERROR')} Unhandled promise rejection: ${error.stack}`); }); process.on('uncaughtException', error => { console.error(`${colors.red('ERROR')} Uncaught exception: ${error.stack}`); }); client.login(process.env.BOT_TOKEN);