AeThex-Bot-Master/aethex-bot/bot.js
sirpiglr ddea985e6f Integrate security features and administration tools into the main bot
Add Sentinel anti-nuke listeners, federation role syncing, ticket system, and admin commands to the unified AeThex bot, consolidating functionality and enhancing security monitoring.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e72fc1b7-94bd-4d6c-801f-cbac2fae245c
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 00c4494a-b436-4e48-b794-39cd745fb604
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/e72fc1b7-94bd-4d6c-801f-cbac2fae245c/7DQc4BR
Replit-Helium-Checkpoint-Created: true
2025-12-07 22:16:10 +00:00

263 lines
7.7 KiB
JavaScript

const {
Client,
GatewayIntentBits,
REST,
Routes,
Collection,
EmbedBuilder,
ChannelType,
PermissionFlagsBits,
AuditLogEvent,
} = require("discord.js");
const { createClient } = require("@supabase/supabase-js");
const http = require("http");
const fs = require("fs");
const path = require("path");
require("dotenv").config();
const token = process.env.DISCORD_BOT_TOKEN || process.env.DISCORD_TOKEN;
const clientId = process.env.DISCORD_CLIENT_ID;
if (!token) {
console.error("Missing DISCORD_BOT_TOKEN or DISCORD_TOKEN");
process.exit(1);
}
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildModeration,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.DirectMessages,
],
});
let supabase = null;
if (process.env.SUPABASE_URL && process.env.SUPABASE_SERVICE_ROLE) {
supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_ROLE
);
console.log("Supabase connected");
}
client.commands = new Collection();
const commandsPath = path.join(__dirname, "commands");
if (fs.existsSync(commandsPath)) {
const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith(".js"));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
if ("data" in command && "execute" in command) {
client.commands.set(command.data.name, command);
console.log(`Loaded command: ${command.data.name}`);
}
}
}
const eventsPath = path.join(__dirname, "events");
if (fs.existsSync(eventsPath)) {
const eventFiles = fs.readdirSync(eventsPath).filter((file) => file.endsWith(".js"));
for (const file of eventFiles) {
const filePath = path.join(eventsPath, file);
const event = require(filePath);
if ("name" in event && "execute" in event) {
client.on(event.name, (...args) => event.execute(...args, client, supabase));
console.log(`Loaded event: ${event.name}`);
}
}
}
const sentinelPath = path.join(__dirname, "listeners", "sentinel");
if (fs.existsSync(sentinelPath)) {
const sentinelFiles = fs.readdirSync(sentinelPath).filter((file) => file.endsWith(".js"));
for (const file of sentinelFiles) {
const filePath = path.join(sentinelPath, file);
const listener = require(filePath);
if ("name" in listener && "execute" in listener) {
client.on(listener.name, (...args) => listener.execute(...args, client));
console.log(`Loaded sentinel listener: ${listener.name}`);
}
}
}
const heatMap = new Map();
const HEAT_THRESHOLD = 3;
const HEAT_WINDOW_MS = 10000;
const DANGEROUS_ACTIONS = ['CHANNEL_DELETE', 'ROLE_DELETE', 'MEMBER_BAN_ADD', 'MEMBER_KICK'];
const whitelistedUsers = (process.env.WHITELISTED_USERS || '').split(',').filter(Boolean);
function addHeat(userId, action) {
if (whitelistedUsers.includes(userId)) return 0;
const now = Date.now();
if (!heatMap.has(userId)) {
heatMap.set(userId, []);
}
const userEvents = heatMap.get(userId);
userEvents.push({ action, timestamp: now });
const recentEvents = userEvents.filter(e => now - e.timestamp < HEAT_WINDOW_MS);
heatMap.set(userId, recentEvents);
return recentEvents.length;
}
function getHeat(userId) {
const now = Date.now();
const userEvents = heatMap.get(userId) || [];
return userEvents.filter(e => now - e.timestamp < HEAT_WINDOW_MS).length;
}
client.heatMap = heatMap;
client.addHeat = addHeat;
client.getHeat = getHeat;
client.HEAT_THRESHOLD = HEAT_THRESHOLD;
const federationMappings = new Map();
client.federationMappings = federationMappings;
const REALM_GUILDS = {
hub: process.env.HUB_GUILD_ID,
labs: process.env.LABS_GUILD_ID,
gameforge: process.env.GAMEFORGE_GUILD_ID,
corp: process.env.CORP_GUILD_ID,
foundation: process.env.FOUNDATION_GUILD_ID,
};
client.REALM_GUILDS = REALM_GUILDS;
const activeTickets = new Map();
client.activeTickets = activeTickets;
let alertChannelId = process.env.ALERT_CHANNEL_ID;
client.alertChannelId = alertChannelId;
async function sendAlert(message, embed = null) {
if (!alertChannelId) return;
try {
const channel = await client.channels.fetch(alertChannelId);
if (channel) {
if (embed) {
await channel.send({ content: message, embeds: [embed] });
} else {
await channel.send(message);
}
}
} catch (err) {
console.error("Failed to send alert:", err.message);
}
}
client.sendAlert = sendAlert;
client.once("ready", () => {
console.log(`Bot logged in as ${client.user.tag}`);
console.log(`Watching ${client.guilds.cache.size} server(s)`);
client.user.setActivity("Protecting the Federation", { type: 3 });
sendAlert(`AeThex Bot is now online! Watching ${client.guilds.cache.size} servers.`);
});
client.on("interactionCreate", async (interaction) => {
if (interaction.isChatInputCommand()) {
const command = client.commands.get(interaction.commandName);
if (!command) return;
try {
await command.execute(interaction, supabase, client);
} catch (error) {
console.error(`Error executing ${interaction.commandName}:`, error);
const errorEmbed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle("Command Error")
.setDescription("There was an error while executing this command.");
if (interaction.replied || interaction.deferred) {
await interaction.followUp({ embeds: [errorEmbed], ephemeral: true });
} else {
await interaction.reply({ embeds: [errorEmbed], ephemeral: true });
}
}
}
if (interaction.isButton()) {
const [action, ...params] = interaction.customId.split('_');
if (action === 'ticket') {
const ticketAction = params[0];
if (ticketAction === 'close') {
const ticketChannelId = params[1];
try {
const channel = interaction.channel;
if (channel && channel.type === ChannelType.GuildText) {
await interaction.reply({ content: 'Closing ticket...', ephemeral: true });
setTimeout(() => channel.delete().catch(console.error), 3000);
}
} catch (err) {
console.error('Ticket close error:', err);
}
}
}
}
});
const healthPort = process.env.HEALTH_PORT || 8080;
http.createServer((req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Content-Type", "application/json");
if (req.url === "/health") {
res.writeHead(200);
res.end(JSON.stringify({
status: "online",
guilds: client.guilds.cache.size,
commands: client.commands.size,
uptime: Math.floor(process.uptime()),
heatMapSize: heatMap.size,
timestamp: new Date().toISOString(),
}));
return;
}
if (req.url === "/stats") {
const guildStats = client.guilds.cache.map(g => ({
id: g.id,
name: g.name,
memberCount: g.memberCount,
}));
res.writeHead(200);
res.end(JSON.stringify({
guilds: guildStats,
totalMembers: guildStats.reduce((sum, g) => sum + g.memberCount, 0),
uptime: Math.floor(process.uptime()),
activeTickets: activeTickets.size,
heatEvents: heatMap.size,
}));
return;
}
res.writeHead(404);
res.end(JSON.stringify({ error: "Not found" }));
}).listen(healthPort, () => {
console.log(`Health server running on port ${healthPort}`);
});
client.login(token).catch((error) => {
console.error("Failed to login:", error.message);
process.exit(1);
});
process.on("unhandledRejection", (error) => {
console.error("Unhandled Promise Rejection:", error);
});
process.on("uncaughtException", (error) => {
console.error("Uncaught Exception:", error);
process.exit(1);
});
module.exports = client;