500 lines
16 KiB
JavaScript
500 lines
16 KiB
JavaScript
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" : `<t:${invite.expiresTimestamp}>`
|
|
value: `Code: ${invite.code}\nMax Uses: ${invite.maxUses}\nExpires <t:${Math.floor(new Date(invite.expiresAt)/1000)}:R>\nCreated at: <t:${Math.floor(new Date(invite.createdAt)/1000)}>`
|
|
},
|
|
{
|
|
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: <t:${Math.floor(new Date(member.joinedAt)/1000)}>\nAccount Created: <t:${Math.floor(new Date(member.user.createdTimestamp)/1000)}>`
|
|
},
|
|
{
|
|
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: <t:${Math.floor(new Date(member.joinedAt)/1000)}>\nAccount Created: <t:${Math.floor(new Date(member.user.createdTimestamp)/1000)}>`
|
|
},
|
|
{
|
|
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) |