const config = require("./config"); const fs = require("fs"); const Discord = require("discord.js"); const colors = require("colors"); const path = require("path") const { REST, Routes } = require('discord.js'); const dcClient = new Discord.Client({ intents: ["Guilds", "GuildMembers"] }); const rest = new REST({ version: '10' }).setToken(config.discord.token); const client = new Discord.Client({ intents: [ "Guilds", "GuildInvites", "AutoModerationConfiguration", "AutoModerationExecution", "GuildMembers", "GuildModeration" ] }); const express = require('express'); const { pathToFileURL } = require("url"); const app = express() // First time bullshit if (!fs.existsSync("config.json")) { // Copy config.json.default, then process.exit(1) after telling the user to fill it out fs.copyFileSync("config.json.default", "config.json"); console.log(`${colors.red("[ERROR]")} config.json not found. Please fill out config.json and restart the bot.`); process.exit(1); } if (!fs.existsSync("defcon.txt")) { // Just make the file, default to lvl 1 fs.writeFileSync("defcon.txt", "5"); } /* DEFCON Levels: DEFCON 5 - Low Alert, Normal Operations. DEFCON 4 - Moderate Alert, Server invites are monitored for suspicious individuals. DEFCON 3 - Moderate Alert, Server invites are locked. DEFCON 2 - High Alert, Server invite links are locked, Discord server chats are heavily monitored (i.e. slowmodes, active moderation). DEFCON 1 - High Alert, Full Lockdown. Break all Discord Invites and lock down the SL Server if necessary. */ // get DEFCON level from file let defcon = fs.readFileSync("defcon.txt", "utf8"); // DEFCON Functions, Set up server the way it needs to per DEFCON level function updateDefcon(level) { // Safety check if (defcon > 5 || defcon < 1) { defcon = 5; } // Update the file fs.writeFileSync("defcon.txt", level); // Update the variable defcon = level; // Update the bot's status // client.user.setPresence({ // activities: [{ // name: `DEFCON ${level}`, // type: Discord.ActivityType.Custom // }] // }) // Update the status messages updateStatusMessages(); updateSlowmodes(); // if defcon 2 or lower, disable invites if (level <= 3) { actionable_servers.forEach((server) => { server.disableInvites(true) }); } else { actionable_servers.forEach((server) => { server.disableInvites(false) }); } } // function updateSlowmodes() { // if (defcon >= 3) { // // Disable slowmodes // slowmode_channels.forEach(async (channel) => { // if (channel.channel.type == Discord.ChannelType.GuildCategory) { // channel.channel.guild.channels.cache.forEach((chan) => { // if (chan.parentId == channel.channel.id) { // chan.setRateLimitPerUser(channel.defaultTime); // } // }) // } else { // return channel.channel.setRateLimitPerUser(channel.defaultTime); // } // }); // } else if (defcon < 3) { // // Enable slowmodes // slowmode_channels.forEach(async (channel) => { // if (channel.channel.type == Discord.ChannelType.GuildCategory) { // // find all channels that have this category as a parent and set slowmode, gotta wait for the promise to resolve // channel.channel.guild.channels.cache.forEach((chan) => { // if (chan.parentId == channel.channel.id) { // chan.setRateLimitPerUser(channel.time); // } // }) // } else { // return channel.channel.setRateLimitPerUser(channel.time); // } // }); // } // } // Redo slowmodes, this time categories are separate, do those first. Still have to loop thru all channels in the guild and check if it has the category as a parent function updateSlowmodes() { if (defcon >= 3) { // Disable slowmodes slowmode_categories.forEach((category) => { category.category.guild.channels.cache.forEach((chan) => { if (chan.parentId == category.category.id) { if (chan.rateLimitPerUser != category.defaultTime) chan.setRateLimitPerUser(category.defaultTime); } }) }); slowmode_channels.forEach((channel) => { if (channel.channel.rateLimitPerUser != channel.defaultTime) return channel.channel.setRateLimitPerUser(channel.defaultTime); }) } else { // Enable slowmodes slowmode_categories.forEach((category) => { category.category.guild.channels.cache.forEach((chan) => { if (chan.parentId == category.category.id) { if (chan.rateLimitPerUser != category.time) chan.setRateLimitPerUser(category.time); } }) }); slowmode_channels.forEach((channel) => { if (channel.channel.rateLimitPerUser != channel.time) return channel.channel.setRateLimitPerUser(channel.time); }) } } function updateStatusMessages() { let message = config.DEFCON.levels[defcon].message; let color = config.DEFCON.levels[defcon].color; // strip # from color and parseInt color = parseInt(color.replace("#", ""), 16); status_messages.forEach((msg) => { msg.edit({ content: "", embeds: [{ title: "DEFCON Status", description: message, color: color }] }) }); status_names.forEach((channel) => { let chan = client.channels.cache.get(channel); if (!chan.type == Discord.ChannelType.GuildVoice) return console.log(`${colors.red("[ERROR]")} Channel ${chan.name} is not a voice channel.`); console.log(`${colors.green("[INFO]")} Setting channel name for ${chan.name}.`) chan.setName(`[ DEFCON ${defcon} ]`).then(() => { console.log(`${colors.green("[INFO]")} Successfully set channel name for ${chan.name}.`); }) }); } // Setup some global variables let status_messages = []; let status_names = []; let actionable_servers = []; let slowmode_channels = []; let slowmode_categories = []; client.on("ready", async () => { console.log(`${colors.magenta("[DEBUG]")} Environment variables: ${JSON.stringify(process.env)}`) // Get port for webserver from environment over config file (for running on pterodactyl/other panels) var port = process.env.SERVER_PORT || config.port; // Start webserver // if (port) app.listen(port, () => { // console.log(`${colors.cyan("[INFO]")} Webserver started on port ${port}`) // }) console.log(`${colors.cyan("[INFO]")} Logged in as ${client.user.tag}`); // Get status messages and actionable servers config.discord.status_messages.forEach((msg) => { // try to get the channel, then message, then push the msg to status_messages, if the channel or message doesnt exist, just return let channel = client.channels.cache.get(msg.channel_id); if (!channel) { console.log(`${colors.red("[ERROR]")} Channel ${msg.channel} not found. Skipping, please use /msg to send a message to the channel.`); return; } if (msg.change_name) { // if name is set, add it to status_names, then skip the rest console.log(`${colors.green("[INFO]")} Found channel name change for ${channel.name}.`) return status_names.push(msg.channel_id); } console.log(`${colors.green("[INFO]")} Found status message for ${channel.name}.`) // fetch the message id, if it doesnt exist, throw error channel.messages.fetch(msg.message_id).then((message) => { status_messages.push(message); }).catch((err) => { console.log(`${colors.red("[ERROR]")} Message ${msg.message} not found in channel ${msg.channel}. Skipping, please use /msg to send a message to the channel.`); return; }); }) config.discord.actionable_servers.forEach((server) => { let guild = client.guilds.cache.get(server); actionable_servers.push(guild); }) // Get slowmode channels config.discord.slowmodes.forEach((channel) => { let chan = client.channels.cache.get(channel.channel_id); if (!chan) { console.log(`${colors.red("[ERROR]")} Slowmode channel ${channel.channel_id} not found.`); return; } slowmode_channels.push({ channel: chan, time: channel.slowmode, defaultTime: channel.defaultSlowmode }); }); config.discord.slowmode_categories.forEach((category) => { let cat = client.channels.cache.get(category.category_id); if (!cat) { console.log(`${colors.red("[ERROR]")} Slowmode category ${category.category_id} not found.`); return; } slowmode_categories.push({ category: cat, time: category.slowmode, defaultTime: category.defaultSlowmode }); }); //console.log(`Went through all guilds and channels:\nGuilds:\n${actionable_servers.map((server) => server.name).join("\n")}\nChannels:\n${slowmode_channels.map((channel.channel) => channel.name).join("\n")}`); updateDefcon(defcon); client.invites = []; // Update Invites client.guilds.cache.forEach(guild => { //on bot start, fetch all guilds and fetch all invites to store guild.invites.fetch().then(guildInvites => { guildInvites.each(guildInvite => { client.invites[guildInvite.code] = guildInvite.uses }) }) guild.fetchVanityData().then(vanityData => { client.invites[vanityData.code] = vanityData.uses }).catch(err => { // do fuck all, they dont have vanity }) }) const commands = [ { name: "defcon", description: "Set the DEFCON level.", default_member_permissions: 0, options: [ { name: "level", description: "The DEFCON level to set.", type: 3, required: true, choices: [ { name: "DEFCON 5", value: "5" }, { name: "DEFCON 4", value: "4" }, { name: "DEFCON 3", value: "3" }, { name: "DEFCON 2", value: "2" }, { name: "DEFCON 1", value: "1" } ] }, { name: "reason", description: "Why is the defcon changing to this level?", required: true, type: 3 }, { name: "silent", type: 5, description: "Don't send the @everyone funny ping", required: false } ] } ] // Do slash command stuff 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); } })(); }); client.on('interactionCreate', async interaction => { if (!interaction.isCommand()) return; let command = interaction.commandName; switch (command) { case "defcon": // Check if reason is set, if not return if (!interaction.options.getString("reason")) return interaction.reply({ephemeral: true, content: "You MUST provide a reason!"}); // Update defcon let level = interaction.options.getString("level"); newLevel = new Number(level); // if number not between 1 and 5 send error if (newLevel < 1 || newLevel > 5) { interaction.reply({ content: "Invalid DEFCON level. Please choose a number between 1 and 5.", ephemeral: true }); return; } updateDefcon(level); // Send automated announcement. color = parseInt(config.DEFCON.levels[defcon].color.replace("#", ""), 16); client.channels.cache.get(config.discord.announcement_channel).send({ content: interaction.options.getBoolean("silent") ? "" : config.discord.announcment_content, embeds: [ { color, title: `We are now at DEFCON ${defcon}`, description: config.DEFCON.levels[defcon].message, fields: [ { name: "Reason", value: interaction.options.getString("reason") } ], footer: { text: `Updated by ${interaction.user.displayName}` }, timestamp: new Date() } ] }) // Send response interaction.reply({ content: `Successfully set DEFCON level to ${level}.`, ephemeral: true }); break; case "msg": // Send message to channel interaction.channel.send("...").then((msg) => { interaction.reply(msg.id) }) break; } }); client.on('inviteCreate', (invite) => { //if someone creates an invite while bot is running, update store client.invites[invite.code] = invite.uses if (defcon > 4) return; // Dont need to send new invite messages if we're not monitoring invites const channel = client.channels.cache.get(config.discord.invitelog) channel.send({ embeds: [{ color: 0x00ffff, title: "New Invite", fields: [ { name: "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 \nCreated at: ` }, { name: "Guild", value: `${invite.guild.name}\n\`${invite.guild.id}\`` }, { name: "Channel", value: `${invite.channel.name}\n\`${invite.channel.id}\` <#${invite.channel.id}>` }, { name: "Inviter", value: `${invite.inviter}\n\`${invite.inviter.id}\`` } ] }] }); }); client.on('guildMemberAdd', async (member) => { // We're just gonna always send invite logs, even if we're not monitoring them const channel = client.channels.cache.get(config.discord.invitelog) let guild = member.guild member.guild.invites.fetch().then(async guildInvites => { //get all guild invites 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: channel.send({ embeds: [{ color: 0x00ff00, title: "New Member", fields: [ { name: "New Member", value: `${member} (${member.user.displayName})\n\`${member.id}\`\nJoined at: \nAccount Created: ` }, { name: "Invite", value: `Inviter: ${(invite.inviter.id == client.user.id) ? "Custom Invite URL (Through Bot)" : `${invite.inviter} (${invite.inviter.displayName})`}\nCode: ${invite.code}\nUses: ${invite.uses}` }, { name: "Guild", value: `${guild.name}\n\`${guild.id}\`` } ] }] }); 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, title: "New Member", fields: [ { name: "New Member", value: `${member} (${member.user.displayName})\n\`${member.id}\`\nJoined at: \nAccount Created: ` }, { name: "Invite", value: `Vanity Code: ${vanityData.code}\nUses: ${vanityData.uses}` }, { name: "Guild", value: `${guild.name}\n\`${guild.id}\`` } ] }] }); } }).catch(err => { // do fuck all, they dont have vanity }) if (defcon <= 3) { // DM user saying Invites are disabled for security reasons, then kick them with the same reason member.send("Invites are currently disabled for security reasons. Please contact a staff member for assistance.").then(() => { member.kick(`DEFCON ${defcon}`); channel.send({ embeds: [{ color: 0xff0000, title: "Member Kicked", description: `${member.user.username} was kicked` }] }); }); } }) // app.set('view engine', 'ejs'); // // set views directory // app.set('views', path.join(__dirname, 'html')); // // Start doing express stuff // app.get("/", async (req, res) => { // // If defcon level is 3 or lower, return 403 // if (defcon <= 3 || req.query.test) return res.status(403).render("lockdown.ejs") // // Otherwise, make a new invite, single use, and redirect the user to it! // client.guilds.cache.get(config.discord.invite_guild).invites.create(config.discord.invite_channel, { maxAge: 60, maxUses: 1, unique: true }).then((invite) => { // client.invites[invite.code].ip = req.headers["X-Forwarded-For"] // res.redirect(`https://discord.com/invite/${invite.code}`); // }) // }); client.login(config.discord.token)