Welp, thats a start
This commit is contained in:
commit
58b2ee9f9c
131
.gitignore
vendored
Normal file
131
.gitignore
vendored
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
storage/
|
||||
database.db
|
||||
snowflakes.*
|
14
TODO
Normal file
14
TODO
Normal file
|
@ -0,0 +1,14 @@
|
|||
# TODO List
|
||||
## Commands
|
||||
[X] /add
|
||||
[X] /remove
|
||||
[X] /lookup
|
||||
[X] /serverconfig
|
||||
[ ] ~~active~~
|
||||
[X] mode
|
||||
[X] logchannel
|
||||
[X] fullscan
|
||||
- Possibly impliment rate limiting on this?
|
||||
|
||||
## Features
|
||||
[X] Automated actions against users (Configured per-server)
|
149
commands.js
Normal file
149
commands.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
const Discord = require("discord.js");
|
||||
|
||||
const adminGuildCommands = [
|
||||
{
|
||||
name: "add",
|
||||
description: "Add a bad actor to the list",
|
||||
options: [
|
||||
{
|
||||
name: "user",
|
||||
type: 6, // USER type
|
||||
description: "The user to add",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
type: 3, // STRING type
|
||||
description: "Comment, reason, or any other context for the addition",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: "attachment1",
|
||||
type: 11, // ATTACHMENT type
|
||||
|
||||
description: "Optional attachment 1",
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: "attachment2",
|
||||
type: 11,
|
||||
description: "Optional attachment 2",
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: "attachment3",
|
||||
type: 11,
|
||||
description: "Optional attachment 3",
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: "attachment4",
|
||||
type: 11,
|
||||
description: "Optional attachment 4",
|
||||
required: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "remove",
|
||||
description: "Remove a bad actor from the list",
|
||||
options: [
|
||||
{
|
||||
name: "user",
|
||||
type: 6, // USER type
|
||||
description: "The user to remove",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: "confirmation",
|
||||
type: Discord.ApplicationCommandOptionType.String,
|
||||
description: "Type 'yes im absolutely sure' to confirm removal",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const globalCommands = [
|
||||
{
|
||||
name: "lookup",
|
||||
description: "Look up a bad actor by their User ID",
|
||||
options: [
|
||||
{
|
||||
name: "user",
|
||||
type: 6, // USER type
|
||||
description: "The user to look up",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "serverconfig",
|
||||
description: "Configure server settings for bad actor management",
|
||||
type: 1, // CHAT_INPUT type
|
||||
default_member_permissions: Discord.PermissionFlagsBits.Administrator.toString(),
|
||||
options: [
|
||||
{
|
||||
name: "mode",
|
||||
description: "Set the mode for bad actor management in this server",
|
||||
type: 1, // SUB_COMMAND type
|
||||
options: [
|
||||
{
|
||||
name: "mode",
|
||||
type: 3, // STRING type
|
||||
description: "Choose 'strict' or 'lenient' mode",
|
||||
required: true,
|
||||
choices: [
|
||||
{
|
||||
name: "Auto-Kick",
|
||||
value: "kick"
|
||||
},
|
||||
{
|
||||
name: "Auto-Ban",
|
||||
value: "ban"
|
||||
},
|
||||
{
|
||||
name: "Warn Only",
|
||||
value: "warn"
|
||||
},
|
||||
{
|
||||
name: "No Action",
|
||||
value: "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "logchannel",
|
||||
description: "Set the channel for logging bad actor actions",
|
||||
type: 1, // SUB_COMMAND type
|
||||
options: [
|
||||
{
|
||||
name: "channel",
|
||||
type: 7, // CHANNEL type
|
||||
description: "The channel to log actions in",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "view",
|
||||
description: "View the current server configuration for bad actor management",
|
||||
type: 1 // SUB_COMMAND type
|
||||
},
|
||||
{
|
||||
name: "fullscan",
|
||||
description: "Perform a full scan of the server for bad actors",
|
||||
type: 1 // SUB_COMMAND type
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
// Define commands
|
||||
module.exports = {
|
||||
adminGuildCommands: adminGuildCommands,
|
||||
globalCommands: globalCommands
|
||||
}
|
77
eventHandlers/guildMemberAdd.js
Normal file
77
eventHandlers/guildMemberAdd.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
module.exports = (member, db, client) => {
|
||||
if (!member.guild) return; // Ignore DMs
|
||||
if (member.user.bot) return; // Ignore bots
|
||||
|
||||
db.get("SELECT * FROM guildConfigs WHERE guildId = ?", [member.guild.id], (err, config) => {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
return;
|
||||
}
|
||||
if (!config) {
|
||||
console.warn(`No configuration found for guild ${member.guild.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
db.get("SELECT * FROM badActors WHERE id = ?", [member.id], (err, row) => {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (row) {
|
||||
switch (config.mode) {
|
||||
case "none":
|
||||
return; // Literally do nothing lol
|
||||
break;
|
||||
case "warn": // Send a warning message in the logChannel
|
||||
break; // Just break, we send warning for everything else anyways
|
||||
case "kick": // Kick the user
|
||||
member.kick("Listed bad actor").then(() => {
|
||||
client.channels.fetch(config.logChannelId).then(logChan => {
|
||||
logChan.send({
|
||||
content: `User <@${member.id}> has been kicked for being a bad actor. Reported by <@${row.reportedBy}>.`
|
||||
}).catch(console.error);
|
||||
});
|
||||
}
|
||||
).catch(err => {
|
||||
console.error("Failed to kick user:", err);
|
||||
});
|
||||
break;
|
||||
case "ban": // Ban the user
|
||||
member.ban({ reason: "Listed bad actor" }).then(() => {
|
||||
client.channels.fetch(config.logChannelId).then(logChan => {
|
||||
logChan.send({
|
||||
content: `User <@${member.id}> has been banned for being a bad actor. Reported by <@${row.reportedBy}>.`
|
||||
}).catch(console.error);
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error("Failed to ban user:", err);
|
||||
});
|
||||
break;
|
||||
}
|
||||
client.channels.fetch(config.logChannelId).then(channel => {
|
||||
if (!channel) {
|
||||
console.warn(`Log channel ${config.logChannelId} not found in guild ${member.guild.id}`);
|
||||
return;
|
||||
}
|
||||
channel.send({
|
||||
embeds: [{
|
||||
title: "Bad Actor Detected",
|
||||
description: `User <@${member.id}> has joined the server and is listed as a bad actor.`,
|
||||
fields: [
|
||||
{ name: "Reported By", value: `<@${row.reportedBy}>`, inline: true },
|
||||
{ name: "Comment", value: row.comment || "No comment provided", inline: false },
|
||||
{ name: "Timestamp", value: `<t:${Math.floor(row.timestamp / 1000)}>`, inline: true }
|
||||
],
|
||||
color: 0xff0000
|
||||
}],
|
||||
files: row.attachments ? JSON.parse(row.attachments).map(file => ({
|
||||
attachment: path.join(__dirname, '../../storage', file),
|
||||
name: file
|
||||
})) : []
|
||||
}).catch(console.error);
|
||||
}).catch(console.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
159
index.js
Normal file
159
index.js
Normal file
|
@ -0,0 +1,159 @@
|
|||
require("dotenv").config();
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const db = new sqlite3.Database(process.env.DB_PATH || 'database.db');
|
||||
|
||||
require('./migrations')(db).then(() => {
|
||||
console.log('All migrations completed successfully');
|
||||
}).catch(err => {
|
||||
console.error('Error running migrations:', err);
|
||||
});
|
||||
|
||||
const Discord = require("discord.js");
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require("path");
|
||||
|
||||
const client = new Discord.Client({
|
||||
intents: [
|
||||
Discord.IntentsBitField.Flags.Guilds,
|
||||
Discord.IntentsBitField.Flags.GuildModeration,
|
||||
Discord.IntentsBitField.Flags.GuildMembers
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
// Presence Loop
|
||||
var presenceCounter = 0;
|
||||
var presenceInterval = 60; // Default to 60 seconds, can be adjusted in presence.json
|
||||
const presenceLoop = () => {
|
||||
console.log("Doing presence update...");
|
||||
try {
|
||||
const presenceConfig = JSON.parse(fs.readFileSync('presence.json', 'utf8'));
|
||||
if (!presenceConfig.presenceList || !Array.isArray(presenceConfig.presenceList) || presenceConfig.presenceList.length === 0) {
|
||||
console.warn('Presence list is empty or not defined in presence.json');
|
||||
return;
|
||||
}
|
||||
if (!presenceConfig.presenceList[presenceCounter]) {
|
||||
presenceCounter = 0; // Reset counter if out of bounds
|
||||
}
|
||||
presenceInterval = presenceConfig.interval || 60; // Use interval from config or default to 60 seconds
|
||||
console.log(`Setting presence to: ${presenceConfig.presenceList[presenceCounter]}; Counter: ${presenceCounter}; Waiting ${presenceInterval} seconds for next update...`);
|
||||
client.user.setPresence({
|
||||
activities: [
|
||||
{
|
||||
name: presenceConfig.presenceList[presenceCounter],
|
||||
type: Discord.ActivityType.Custom
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (presenceConfig.random) {
|
||||
console.log("Random presence enabled, selecting a random presence for next update.");
|
||||
presenceCounter = Math.floor(Math.random() * presenceConfig.presenceList.length);
|
||||
} else {
|
||||
presenceCounter++;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load presence config:', err);
|
||||
}
|
||||
setTimeout(presenceLoop, presenceInterval * 1000); // Wait for the specified interval before next update
|
||||
}
|
||||
|
||||
client.once("ready", async () => {
|
||||
console.log(`Logged in as ${client.user.username} (${client.user.id})`);
|
||||
|
||||
// Register commands
|
||||
const commands = require("./commands.js")
|
||||
await (async () => {
|
||||
const rest = new Discord.REST({ version: '10' }).setToken(client.token);
|
||||
try {
|
||||
//Global
|
||||
console.log(`Registering global commands`);
|
||||
await rest.put(Discord.Routes.applicationCommands(client.user.id), { body: commands.globalCommands })
|
||||
console.log(`Registered global commands successfully`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (process.env.REFRESH_ADMIN_GUILD_COMMANDS === 'true') {
|
||||
// Unregister guild commands from all guilds
|
||||
try {
|
||||
console.log(`Unregistering guild commands from all guilds`);
|
||||
const guilds = await client.guilds.fetch();
|
||||
console.log(`Found ${guilds.size} guilds to unregister commands from`);
|
||||
if (guilds.size === 0) {
|
||||
console.log(`No guilds found, skipping unregistration`);
|
||||
return;
|
||||
}
|
||||
for (const guild of guilds.values()) {
|
||||
await rest.put(Discord.Routes.applicationGuildCommands(client.user.id, guild.id), { body: [] });
|
||||
}
|
||||
console.log(`Unregistered guild commands from all guilds successfully`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
try {
|
||||
//Guild
|
||||
console.log(`Registering guild commands for admin guild`);
|
||||
await rest.put(Discord.Routes.applicationGuildCommands(client.user.id, process.env.ADMIN_GUILD), { body: commands.adminGuildCommands })
|
||||
console.log(`Registered guild commands for admin guild successfully`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
presenceLoop();
|
||||
});
|
||||
|
||||
client.on("error", (error) => {
|
||||
console.error("An error occurred:", error);
|
||||
});
|
||||
|
||||
client.on("interactionCreate", async (interaction) => {
|
||||
switch (interaction.type) {
|
||||
case Discord.InteractionType.ApplicationCommand:
|
||||
const command = require(`./interactions/commands/${interaction.commandName}.js`);
|
||||
if (command) {
|
||||
try {
|
||||
await command.execute(interaction, db, client);
|
||||
} catch (error) {
|
||||
console.error(`Error executing command ${interaction.commandName}:`, error);
|
||||
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
}
|
||||
} else {
|
||||
console.warn(`Command ${interaction.commandName} not found.`);
|
||||
await interaction.reply({ content: 'This command does not exist.', ephemeral: true });
|
||||
}
|
||||
break;
|
||||
case Discord.InteractionType.ModalSubmit:
|
||||
const modal = require(`./interactions/modals/${interaction.customId}.js`)(interaction, db, client);
|
||||
if (modal) {
|
||||
try {
|
||||
await modal.execute();
|
||||
} catch (error) {
|
||||
console.error(`Error executing modal ${interaction.customId}:`, error);
|
||||
await interaction.reply({ content: 'There was an error while processing this modal!', ephemeral: true });
|
||||
}
|
||||
} else {
|
||||
console.warn(`Modal ${interaction.customId} not found.`);
|
||||
await interaction.reply({ content: 'This modal does not exist.', ephemeral: true });
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unhandled interaction type: ${interaction.type}`);
|
||||
await interaction.reply({ content: 'This interaction type is not supported.', ephemeral: true });
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
client.on("guildMemberAdd", async (member) => {
|
||||
require("./eventHandlers/guildMemberAdd.js")(member, db, client);
|
||||
});
|
||||
|
||||
client.login(process.env.TOKEN).then(() => {
|
||||
console.log("Bot is online!");
|
||||
}).catch(err => {
|
||||
console.error("Failed to log in:", err);
|
||||
});
|
87
interactions/commands/add.js
Normal file
87
interactions/commands/add.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
|
||||
const execute = async (interaction, db, client) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
await interaction.deferReply({ ephemeral: true }).catch(console.error);
|
||||
|
||||
db.get("SELECT * FROM badActors WHERE id = ?", [interaction.options.getUser("user")], async (err, row) => {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
return interaction.editReply({ content: "An error occurred while accessing the database.", ephemeral: true });
|
||||
}
|
||||
if (row) {
|
||||
return interaction.editReply({ content: "That user is already listed!", ephemeral: true });
|
||||
} else {
|
||||
|
||||
const user = interaction.options.getUser("user").id;
|
||||
const comment = interaction.options.getString("comment") || "No comment provided";
|
||||
const attachments = [];
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const attachment = interaction.options.getAttachment(`attachment${i}`);
|
||||
if (attachment) {
|
||||
const url = attachment.url;
|
||||
const filename = `${Date.now()}_${attachment.name}`;
|
||||
const filepath = path.join(__dirname, '../../storage', filename);
|
||||
|
||||
// Await the download before continuing
|
||||
await new Promise((resolve, reject) => {
|
||||
const file = fs.createWriteStream(filepath);
|
||||
https.get(url, (response) => {
|
||||
response.pipe(file);
|
||||
file.on('finish', () => {
|
||||
file.close(resolve);
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
fs.unlink(filepath, () => { });
|
||||
console.error('Error downloading attachment:', err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
attachments.push(filename);
|
||||
}
|
||||
}
|
||||
const timestamp = Date.now();
|
||||
|
||||
db.run("INSERT INTO badActors (id, reportedBy, comment, timestamp, attachments) VALUES (?, ?, ?, ?, ?)", [
|
||||
user,
|
||||
interaction.user.id,
|
||||
comment,
|
||||
timestamp,
|
||||
JSON.stringify(attachments)
|
||||
], function (err) {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
return interaction.editReply({ content: "An error occurred while adding the user to the list.", ephemeral: true });
|
||||
}
|
||||
|
||||
const embed = {
|
||||
title: "Bad Actor Added",
|
||||
description: `User <@${user}> has been added to the bad actors list.`,
|
||||
fields: [
|
||||
{ name: "Reported By", value: `<@${interaction.user.id}>`, inline: true },
|
||||
{ name: "Comment", value: comment, inline: false },
|
||||
{ name: "Timestamp", value: new Date(timestamp).toLocaleString(), inline: true }
|
||||
],
|
||||
|
||||
color: 0xff0000
|
||||
};
|
||||
|
||||
interaction.editReply({ embeds: [embed], files: attachments.map(file => path.join(__dirname, '../../storage', file))});
|
||||
client.channels.fetch(process.env.ADMIN_LOG_CHANNEL).then(logChan => {
|
||||
logChan.send({
|
||||
embeds: [embed],
|
||||
content: `User <@${user}> has been added to the bad actors list by <@${interaction.user.id}>.`,
|
||||
files: attachments.map(file => ({
|
||||
attachment: path.join(__dirname, '../../storage', file),
|
||||
name: file
|
||||
}))
|
||||
}).catch(console.error);
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
module.exports = { execute };
|
36
interactions/commands/lookup.js
Normal file
36
interactions/commands/lookup.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const path = require('path');
|
||||
|
||||
const execute = async (interaction, db, client) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
await interaction.deferReply({ ephemeral: true }).catch(console.error);
|
||||
interaction.options.getUser("user").id
|
||||
db.get("SELECT * FROM badActors WHERE id = ?", [interaction.options.getUser("user").id], async (err, row) => {
|
||||
if (row) {
|
||||
const embed = {
|
||||
title: "Bad Actor Found",
|
||||
description: `User <@${row.id}> is in the bad actors list.`,
|
||||
fields: [
|
||||
{ name: "Reported By", value: `<@${row.reportedBy}>`, inline: true },
|
||||
{ name: "Comment", value: row.comment || "No comment provided", inline: false },
|
||||
{ name: "Timestamp", value: `<t:${Math.floor(new Date(row.timestamp)/1000)}>`, inline: true }
|
||||
],
|
||||
color: 0x00ff00
|
||||
};
|
||||
|
||||
let files = [];
|
||||
if (row.attachments) {
|
||||
try {
|
||||
const attachments = JSON.parse(row.attachments);
|
||||
files = attachments.map(file => path.join(__dirname, '../../storage', file));
|
||||
} catch (e) {
|
||||
files = [];
|
||||
}
|
||||
}
|
||||
|
||||
interaction.editReply({ embeds: [embed], files });
|
||||
} else {
|
||||
interaction.editReply({ content: "That user is not listed as a bad actor.", ephemeral: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
module.exports = { execute };
|
48
interactions/commands/remove.js
Normal file
48
interactions/commands/remove.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const execute = async (interaction, db, client) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
await interaction.deferReply({ ephemeral: true }).catch(console.error);
|
||||
|
||||
db.get("SELECT * FROM badActors WHERE id = ?", [interaction.options.getUser("user").id], async (err, row) => {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
return interaction.editReply({ content: "An error occurred while accessing the database.", ephemeral: true });
|
||||
}
|
||||
if (row) {
|
||||
const user = interaction.options.getUser("user").id;
|
||||
const confirmation = interaction.options.getString("confirmation");
|
||||
|
||||
if (confirmation !== "yes im absolutely sure") {
|
||||
return interaction.editReply({ content: "You must type 'yes im absolutely sure' to confirm removal.", ephemeral: true });
|
||||
}
|
||||
|
||||
db.run("DELETE FROM badActors WHERE id = ?", [user], function (err) {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
return interaction.editReply({ content: "An error occurred while removing the user from the list.", ephemeral: true });
|
||||
}
|
||||
row.attachments = JSON.parse(row.attachments || "[]");
|
||||
if (row.attachments && Array.isArray(row.attachments)) {
|
||||
for (const filePath of row.attachments) {
|
||||
fs.unlink(path.join(__dirname, "../../storage", filePath), (err) => {
|
||||
if (err) {
|
||||
console.error(`Failed to delete attachment: ${filePath}`, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
interaction.editReply({ content: `User <@${user}> has been removed from the bad actors list.`, ephemeral: true });
|
||||
client.channels.fetch(process.env.ADMIN_LOG_CHANNEL).then(logChan => {
|
||||
logChan.send({
|
||||
content: `User <@${user}> has been removed from the bad actors list by <@${interaction.user.id}>.`
|
||||
}).catch(console.error);
|
||||
})
|
||||
});
|
||||
} else {
|
||||
return interaction.editReply({ content: "That user is not listed as a bad actor.", ephemeral: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
module.exports = { execute };
|
112
interactions/commands/serverconfig.js
Normal file
112
interactions/commands/serverconfig.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
const Discord = require("discord.js");
|
||||
const allowedChannelTypes = [Discord.ChannelType.GuildAnnouncement, Discord.ChannelType.GuildText, Discord.ChannelType.GuildVoice]
|
||||
|
||||
const execute = async (interaction, db, client) => {
|
||||
if (!interaction.isCommand()) return;
|
||||
if (!interaction.guild) return interaction.reply({ content: "This command can only be used in a server.", ephemeral: true });
|
||||
await interaction.deferReply({ ephemeral: true }).catch(console.error);
|
||||
var conf;
|
||||
await db.get("SELECT * FROM guildConfigs WHERE guildId = ?", [interaction.guild.id], async (err, row) => {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
return interaction.editReply({ content: "An error occurred while accessing the database.", ephemeral: true });
|
||||
}
|
||||
if (!row) {
|
||||
db.run("INSERT INTO guildConfigs (guildId, mode) VALUES (?, ?)", [interaction.guild.id, "none"], function (err) {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
returninteraction.editReply({ content: "An error occurred while initializing server configuration.", ephemeral: true });
|
||||
}
|
||||
conf = { guildId: interaction.guild.id, mode: "none" };
|
||||
});
|
||||
}
|
||||
conf = row;
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
console.log(`Subcommand: ${subcommand}`);
|
||||
switch (subcommand) {
|
||||
case "mode": // Activate or deactivate automatic bad-actor management
|
||||
const newMode = interaction.options.getString("mode");
|
||||
if (!["none", "warn", "kick", "ban"].includes(newMode)) {
|
||||
return interaction.editReply({ content: "Invalid mode selected. Choose from: none, warn, kick, ban.", ephemeral: true });
|
||||
}
|
||||
db.run("UPDATE guildConfigs SET mode = ? WHERE guildId = ?", [newMode, interaction.guild.id], function (err) {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
return interaction.editReply({ content: "An error occurred while updating the mode.", ephemeral: true });
|
||||
}
|
||||
conf.mode = newMode;
|
||||
return interaction.editReply({ content: `Bad actor management mode set to **${newMode}**.`, ephemeral: true });
|
||||
});
|
||||
break;
|
||||
|
||||
case "logchannel": // Set the log channel for bad actor actions
|
||||
const logChannel = interaction.options.getChannel("channel");
|
||||
|
||||
if (!logChannel || !allowedChannelTypes.includes(logChannel.type)) {
|
||||
return interaction.editReply({ content: "Please select a valid text channel for logging.", ephemeral: true });
|
||||
}
|
||||
db.run("UPDATE guildConfigs SET logChannelId = ? WHERE guildId = ?", [logChannel.id, interaction.guild.id], function (err) {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
return interaction.editReply({ content: "An error occurred while updating the log channel.", ephemeral: true });
|
||||
}
|
||||
conf.logChannelId = logChannel.id;
|
||||
return interaction.editReply({ content: `Log channel set to <#${logChannel.id}>.`, ephemeral: true });
|
||||
});
|
||||
break;
|
||||
|
||||
case "view": // View current server configuration
|
||||
if (!conf) {
|
||||
return interaction.editReply({ content: "No configuration found for this server.", ephemeral: true });
|
||||
}
|
||||
const mode = conf.mode || "none";
|
||||
const logChannelId = conf.logChannelId ? `<#${conf.logChannelId}>` : "Not set";
|
||||
const embed = new Discord.EmbedBuilder()
|
||||
.setTitle("Server Configuration")
|
||||
.addFields(
|
||||
{ name: "Bad Actor Management Mode", value: mode, inline: true },
|
||||
{ name: "Log Channel", value: logChannelId, inline: true }
|
||||
)
|
||||
.setColor("#0099ff")
|
||||
.setTimestamp();
|
||||
return interaction.editReply({ embeds: [embed], ephemeral: true });
|
||||
break;
|
||||
case "fullscan": // Perform a full scan of the server for bad actors
|
||||
if (!conf.logChannelId) {
|
||||
return interaction.editReply({ content: "Log channel is not set. Please set it using `/serverconfig logchannel`.", ephemeral: true });
|
||||
}
|
||||
const logChannelFullScan = await client.channels.fetch(conf.logChannelId).catch(console.error);
|
||||
if (!logChannelFullScan || !allowedChannelTypes.includes(logChannelFullScan.type)) {
|
||||
return interaction.editReply({ content: "Log channel is not valid or not found.", ephemeral: true });
|
||||
}
|
||||
const members = await interaction.guild.members.fetch();
|
||||
const badActors = [];
|
||||
for (const member of members.values()) {
|
||||
if (member.user.bot) continue; // Ignore bots
|
||||
const actor = await new Promise((resolve, reject) => {
|
||||
db.get("SELECT * FROM badActors WHERE id = ?", [member.user.id], (err, row) => {
|
||||
if (err) {
|
||||
console.error("Database error:", err);
|
||||
return resolve(null);
|
||||
}
|
||||
resolve(row);
|
||||
});
|
||||
});
|
||||
if (actor) {
|
||||
badActors.push(actor);
|
||||
}
|
||||
}
|
||||
if (badActors.length === 0) {
|
||||
return interaction.editReply({ content: "No bad actors found in the server.", ephemeral: true });
|
||||
}
|
||||
let message = `Bad Actor Scan Results\nFound ${badActors.length} bad actors in the server.\n\n`;
|
||||
badActors.forEach(actor => {
|
||||
message += `ID: ${actor.id}\nUser: <@${actor.id}>\nReported by: <@${actor.reportedBy}>\nDate: <t:${Math.floor(actor.timestamp / 1000)}:F>\nComment: ${actor.comment}\n\n`;
|
||||
});
|
||||
logChannelFullScan.send({ content: message }).catch(console.error);
|
||||
return interaction.editReply({ content: "Full scan completed. Check the log channel for results.", ephemeral: true });
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
module.exports = { execute };
|
69
migrations.js
Normal file
69
migrations.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = async function runMigrations(db) {
|
||||
// Promisify db.run and db.get for easier async/await usage
|
||||
function run(sql, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, params, function (err) {
|
||||
if (err) reject(err);
|
||||
else resolve(this);
|
||||
});
|
||||
});
|
||||
}
|
||||
function get(sql, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(sql, params, function (err, row) {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
});
|
||||
});
|
||||
}
|
||||
function all(sql, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(sql, params, function (err, rows) {
|
||||
if (err) reject(err);
|
||||
else resolve(rows);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 1. Ensure migrations table exists
|
||||
await run(`
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
name TEXT PRIMARY KEY,
|
||||
run_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
// 2. Read all migration files
|
||||
const migrationsDir = path.join(__dirname, 'migrations');
|
||||
let files = [];
|
||||
try {
|
||||
files = fs.readdirSync(migrationsDir)
|
||||
.filter(f => f.endsWith('.js'))
|
||||
.sort();
|
||||
} catch (e) {
|
||||
// If directory doesn't exist, skip
|
||||
if (e.code !== 'ENOENT') throw e;
|
||||
}
|
||||
|
||||
// 3. Get already run migrations
|
||||
const appliedRows = await all('SELECT name FROM migrations');
|
||||
const applied = new Set(appliedRows.map(row => row.name));
|
||||
|
||||
// 4. Run pending migrations
|
||||
for (const file of files) {
|
||||
if (!applied.has(file)) {
|
||||
const migration = require(path.join(migrationsDir, file));
|
||||
if (typeof migration !== 'function') {
|
||||
throw new Error(`Migration file ${file} does not export a function`);
|
||||
}
|
||||
console.log(`Running migration: ${file}`);
|
||||
await migration(db, run, get, all);
|
||||
// Mark migration as applied
|
||||
await run('INSERT INTO migrations (name) VALUES (?)', [file]);
|
||||
console.log(`Migration applied: ${file}`);
|
||||
}
|
||||
}
|
||||
};
|
7
migrations/0001-create-tables.js
Normal file
7
migrations/0001-create-tables.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
module.exports = async function createTables(db, run, get, all) {
|
||||
// Create badActors table
|
||||
run("CREATE TABLE IF NOT EXISTS badActors (id TEXT PRIMARY KEY, reportedBy TEXT, comment TEXT, timestamp INTEGER, attachments BLOB)");
|
||||
|
||||
// Create guildConfigs table
|
||||
run("CREATE TABLE IF NOT EXISTS guildConfigs (guildId TEXT PRIMARY KEY, mode TEXT DEFAULT 'warn', logChannelId TEXT)")
|
||||
}
|
12
migrations/0002-import-old-data.js
Normal file
12
migrations/0002-import-old-data.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
module.exports = async function importOldData(db, run, get, all) {
|
||||
oldData = require("../snowflakes.json");
|
||||
for (const key in oldData) {
|
||||
let actor = oldData[key][0];
|
||||
run("INSERT INTO badActors (id, reportedBy, comment, timestamp) VALUES (?, ?, ?, ?)", [
|
||||
key,
|
||||
actor.author.id,
|
||||
actor.orig_content,
|
||||
new Date(actor.created_at).getTime()
|
||||
])
|
||||
}
|
||||
}
|
1782
package-lock.json
generated
Normal file
1782
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
18
package.json
Normal file
18
package.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "satanic-security",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"discord.js": "^14.20.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"sqlite3": "^5.1.7"
|
||||
}
|
||||
}
|
13
presence.json
Normal file
13
presence.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"interval": 60,
|
||||
"random": true,
|
||||
"presenceList": [
|
||||
"Praise yourself!",
|
||||
"Happy pride month 🌈",
|
||||
"I'll protect you",
|
||||
"You are loved",
|
||||
"You are not alone",
|
||||
"You are enough",
|
||||
"You are valid"
|
||||
]
|
||||
}
|
9
util/embedGen.js
Normal file
9
util/embedGen.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
listBadActor: (row) => {
|
||||
return {
|
||||
embeds: [{
|
||||
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue