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
This commit is contained in:
sirpiglr 2025-12-07 22:16:10 +00:00
parent eaffacca6b
commit ddea985e6f
15 changed files with 2281 additions and 19 deletions

27
.replit
View file

@ -32,32 +32,21 @@ author = "agent"
[[workflows.workflow.tasks]]
task = "workflow.run"
args = "Bot Master Dashboard"
[[workflows.workflow.tasks]]
task = "workflow.run"
args = "Aethex Sentinel Bot"
args = "AeThex Unified Bot"
[[workflows.workflow]]
name = "Bot Master Dashboard"
name = "AeThex Unified Bot"
author = "agent"
[[workflows.workflow.tasks]]
task = "shell.exec"
args = "python main.py"
waitForPort = 5000
[workflows.workflow.metadata]
outputType = "webview"
[[workflows.workflow]]
name = "Aethex Sentinel Bot"
author = "agent"
[[workflows.workflow.tasks]]
task = "shell.exec"
args = "cd sentinel-bot && npm start"
args = "cd aethex-bot && npm start"
waitForPort = 8080
[workflows.workflow.metadata]
outputType = "console"
[userenv]
[userenv.shared]
DISCORD_CLIENT_ID = "1447339527885553828"

21
aethex-bot/.env.example Normal file
View file

@ -0,0 +1,21 @@
# Required
DISCORD_BOT_TOKEN=your_discord_bot_token
DISCORD_CLIENT_ID=your_discord_client_id
# Optional - Supabase (for user verification features)
SUPABASE_URL=your_supabase_url
SUPABASE_SERVICE_ROLE=your_supabase_service_role_key
# Optional - Federation Guild IDs
HUB_GUILD_ID=main_hub_server_id
LABS_GUILD_ID=labs_server_id
GAMEFORGE_GUILD_ID=gameforge_server_id
CORP_GUILD_ID=corp_server_id
FOUNDATION_GUILD_ID=foundation_server_id
# Optional - Security
WHITELISTED_USERS=user_id_1,user_id_2
ALERT_CHANNEL_ID=channel_id_for_alerts
# Optional - Health server
HEALTH_PORT=8080

263
aethex-bot/bot.js Normal file
View file

@ -0,0 +1,263 @@
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;

View file

@ -0,0 +1,157 @@
const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('admin')
.setDescription('Admin monitoring commands')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addSubcommand(subcommand =>
subcommand
.setName('status')
.setDescription('View bot status and statistics')
)
.addSubcommand(subcommand =>
subcommand
.setName('heat')
.setDescription('Check heat level of a user')
.addUserOption(option =>
option.setName('user')
.setDescription('User to check')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('servers')
.setDescription('View all servers the bot is in')
)
.addSubcommand(subcommand =>
subcommand
.setName('threats')
.setDescription('View current heat map (active threats)')
)
.addSubcommand(subcommand =>
subcommand
.setName('federation')
.setDescription('View federation role mappings')
),
async execute(interaction, supabase, client) {
const subcommand = interaction.options.getSubcommand();
if (subcommand === 'status') {
const guildCount = client.guilds.cache.size;
const memberCount = client.guilds.cache.reduce((sum, g) => sum + g.memberCount, 0);
const commandCount = client.commands.size;
const uptime = Math.floor(process.uptime());
const hours = Math.floor(uptime / 3600);
const minutes = Math.floor((uptime % 3600) / 60);
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('AeThex Bot Status')
.setThumbnail(client.user.displayAvatarURL())
.addFields(
{ name: 'Servers', value: `${guildCount}`, inline: true },
{ name: 'Total Members', value: `${memberCount.toLocaleString()}`, inline: true },
{ name: 'Commands', value: `${commandCount}`, inline: true },
{ name: 'Uptime', value: `${hours}h ${minutes}m`, inline: true },
{ name: 'Active Tickets', value: `${client.activeTickets.size}`, inline: true },
{ name: 'Heat Map Size', value: `${client.heatMap.size}`, inline: true }
)
.setTimestamp();
await interaction.reply({ embeds: [embed] });
}
if (subcommand === 'heat') {
const user = interaction.options.getUser('user');
const heat = client.getHeat(user.id);
const embed = new EmbedBuilder()
.setColor(heat >= client.HEAT_THRESHOLD ? 0xff0000 : heat > 0 ? 0xffaa00 : 0x00ff00)
.setTitle('User Heat Level')
.setThumbnail(user.displayAvatarURL())
.addFields(
{ name: 'User', value: user.tag, inline: true },
{ name: 'Heat Level', value: `${heat}/${client.HEAT_THRESHOLD}`, inline: true },
{ name: 'Status', value: heat >= client.HEAT_THRESHOLD ? 'DANGER' : heat > 0 ? 'Elevated' : 'Normal', inline: true }
)
.setTimestamp();
await interaction.reply({ embeds: [embed] });
}
if (subcommand === 'servers') {
const guilds = client.guilds.cache.map(g => `**${g.name}** - ${g.memberCount.toLocaleString()} members`).join('\n');
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('Connected Servers')
.setDescription(guilds || 'No servers')
.setFooter({ text: `Total: ${client.guilds.cache.size} servers` })
.setTimestamp();
await interaction.reply({ embeds: [embed], ephemeral: true });
}
if (subcommand === 'threats') {
const now = Date.now();
const activeThreats = [];
for (const [userId, events] of client.heatMap) {
const recentEvents = events.filter(e => now - e.timestamp < 10000);
if (recentEvents.length > 0) {
try {
const user = await client.users.fetch(userId).catch(() => null);
activeThreats.push({
user: user ? user.tag : userId,
heat: recentEvents.length,
actions: recentEvents.map(e => e.action).join(', ')
});
} catch (e) {
activeThreats.push({
user: userId,
heat: recentEvents.length,
actions: recentEvents.map(e => e.action).join(', ')
});
}
}
}
const description = activeThreats.length > 0
? activeThreats.map(t => `**${t.user}** - Heat: ${t.heat} (${t.actions})`).join('\n')
: 'No active threats detected.';
const embed = new EmbedBuilder()
.setColor(activeThreats.length > 0 ? 0xff0000 : 0x00ff00)
.setTitle('Active Threat Monitor')
.setDescription(description)
.setFooter({ text: `Heat threshold: ${client.HEAT_THRESHOLD}` })
.setTimestamp();
await interaction.reply({ embeds: [embed], ephemeral: true });
}
if (subcommand === 'federation') {
const mappings = [...client.federationMappings.entries()];
const description = mappings.length > 0
? mappings.map(([roleId, data]) => `<@&${roleId}> - Synced across realms`).join('\n')
: 'No federation role mappings configured.\nUse `/federation link` to set up cross-server roles.';
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('Federation Role Mappings')
.setDescription(description)
.addFields(
{ name: 'Hub Guild', value: client.REALM_GUILDS.hub || 'Not set', inline: true },
{ name: 'Labs Guild', value: client.REALM_GUILDS.labs || 'Not set', inline: true },
{ name: 'GameForge Guild', value: client.REALM_GUILDS.gameforge || 'Not set', inline: true }
)
.setTimestamp();
await interaction.reply({ embeds: [embed], ephemeral: true });
}
},
};

View file

@ -0,0 +1,114 @@
const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('federation')
.setDescription('Manage cross-server role sync')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addSubcommand(subcommand =>
subcommand
.setName('link')
.setDescription('Link a role for cross-server syncing')
.addRoleOption(option =>
option.setName('role')
.setDescription('Role to sync across realms')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('unlink')
.setDescription('Remove a role from cross-server syncing')
.addRoleOption(option =>
option.setName('role')
.setDescription('Role to remove from sync')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription('List all linked roles')
),
async execute(interaction, supabase, client) {
const subcommand = interaction.options.getSubcommand();
if (subcommand === 'link') {
const role = interaction.options.getRole('role');
if (client.federationMappings.has(role.id)) {
return interaction.reply({
content: `${role} is already linked for federation sync.`,
ephemeral: true,
});
}
client.federationMappings.set(role.id, {
name: role.name,
guildId: interaction.guild.id,
createdAt: Date.now(),
});
const embed = new EmbedBuilder()
.setColor(0x00ff00)
.setTitle('Role Linked')
.setDescription(`${role} will now sync across all realm servers.`)
.addFields(
{ name: 'Role Name', value: role.name, inline: true },
{ name: 'Source Guild', value: interaction.guild.name, inline: true }
)
.setFooter({ text: 'Users with this role will receive it in all connected realms.' })
.setTimestamp();
await interaction.reply({ embeds: [embed] });
client.sendAlert(`Federation: Role ${role.name} linked by ${interaction.user.tag}`);
}
if (subcommand === 'unlink') {
const role = interaction.options.getRole('role');
if (!client.federationMappings.has(role.id)) {
return interaction.reply({
content: `${role} is not linked for federation sync.`,
ephemeral: true,
});
}
client.federationMappings.delete(role.id);
const embed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle('Role Unlinked')
.setDescription(`${role} will no longer sync across realm servers.`)
.setTimestamp();
await interaction.reply({ embeds: [embed] });
client.sendAlert(`Federation: Role ${role.name} unlinked by ${interaction.user.tag}`);
}
if (subcommand === 'list') {
const mappings = [...client.federationMappings.entries()];
if (mappings.length === 0) {
return interaction.reply({
content: 'No roles are currently linked for federation sync.',
ephemeral: true,
});
}
const roleList = mappings.map(([roleId, data]) => `<@&${roleId}> - Added <t:${Math.floor(data.createdAt / 1000)}:R>`).join('\n');
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('Federation Linked Roles')
.setDescription(roleList)
.setFooter({ text: `${mappings.length} role(s) linked` })
.setTimestamp();
await interaction.reply({ embeds: [embed], ephemeral: true });
}
},
};

View file

@ -0,0 +1,57 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('status')
.setDescription('View network status and bot health'),
async execute(interaction, supabase, client) {
const guildCount = client.guilds.cache.size;
const memberCount = client.guilds.cache.reduce((sum, g) => sum + g.memberCount, 0);
const uptime = Math.floor(process.uptime());
const hours = Math.floor(uptime / 3600);
const minutes = Math.floor((uptime % 3600) / 60);
const seconds = uptime % 60;
const realmStatus = [];
const REALM_GUILDS = client.REALM_GUILDS;
for (const [realm, guildId] of Object.entries(REALM_GUILDS)) {
if (!guildId) {
realmStatus.push({ name: realm.charAt(0).toUpperCase() + realm.slice(1), status: 'Not configured', members: 0 });
continue;
}
const guild = client.guilds.cache.get(guildId);
if (guild) {
realmStatus.push({ name: realm.charAt(0).toUpperCase() + realm.slice(1), status: 'Online', members: guild.memberCount });
} else {
realmStatus.push({ name: realm.charAt(0).toUpperCase() + realm.slice(1), status: 'Offline', members: 0 });
}
}
const realmFields = realmStatus.map(r => ({
name: r.name,
value: `${r.status === 'Online' ? '🟢' : r.status === 'Offline' ? '🔴' : '⚪'} ${r.status}${r.members > 0 ? ` (${r.members.toLocaleString()})` : ''}`,
inline: true,
}));
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('AeThex Network Status')
.setDescription('Current status of the AeThex Federation')
.addFields(
{ name: 'Total Servers', value: `${guildCount}`, inline: true },
{ name: 'Total Members', value: `${memberCount.toLocaleString()}`, inline: true },
{ name: 'Uptime', value: `${hours}h ${minutes}m ${seconds}s`, inline: true },
...realmFields,
{ name: 'Sentinel Status', value: client.heatMap.size > 0 ? `⚠️ Monitoring ${client.heatMap.size} user(s)` : '🛡️ All Clear', inline: false },
{ name: 'Active Tickets', value: `${client.activeTickets.size}`, inline: true },
{ name: 'Federation Mappings', value: `${client.federationMappings.size}`, inline: true }
)
.setFooter({ text: 'AeThex Unified Bot' })
.setTimestamp();
await interaction.reply({ embeds: [embed] });
},
};

View file

@ -0,0 +1,128 @@
const { SlashCommandBuilder, EmbedBuilder, ChannelType, PermissionFlagsBits, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('ticket')
.setDescription('Ticket management system')
.addSubcommand(subcommand =>
subcommand
.setName('create')
.setDescription('Create a new support ticket')
.addStringOption(option =>
option.setName('reason')
.setDescription('Brief reason for opening this ticket')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('close')
.setDescription('Close the current ticket')
),
async execute(interaction, supabase, client) {
const subcommand = interaction.options.getSubcommand();
if (subcommand === 'create') {
const reason = interaction.options.getString('reason');
const guild = interaction.guild;
const user = interaction.user;
const existingTicket = client.activeTickets.get(user.id);
if (existingTicket) {
return interaction.reply({
content: `You already have an open ticket: <#${existingTicket}>`,
ephemeral: true,
});
}
try {
const ticketChannel = await guild.channels.create({
name: `ticket-${user.username}`,
type: ChannelType.GuildText,
permissionOverwrites: [
{
id: guild.id,
deny: [PermissionFlagsBits.ViewChannel],
},
{
id: user.id,
allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages],
},
{
id: client.user.id,
allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ManageChannels],
},
],
});
client.activeTickets.set(user.id, ticketChannel.id);
const closeButton = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`ticket_close_${ticketChannel.id}`)
.setLabel('Close Ticket')
.setStyle(ButtonStyle.Danger)
);
const ticketEmbed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle('Support Ticket')
.setDescription(`Ticket created by ${user}`)
.addFields(
{ name: 'Reason', value: reason },
{ name: 'User ID', value: user.id, inline: true },
{ name: 'Created', value: `<t:${Math.floor(Date.now() / 1000)}:F>`, inline: true }
)
.setFooter({ text: 'A staff member will assist you shortly.' });
await ticketChannel.send({ embeds: [ticketEmbed], components: [closeButton] });
await interaction.reply({
content: `Ticket created: ${ticketChannel}`,
ephemeral: true,
});
client.sendAlert(`New ticket opened by ${user.tag}: ${reason}`);
} catch (error) {
console.error('[Ticket] Create error:', error);
await interaction.reply({
content: 'Failed to create ticket. Please try again.',
ephemeral: true,
});
}
}
if (subcommand === 'close') {
const channel = interaction.channel;
if (!channel.name.startsWith('ticket-')) {
return interaction.reply({
content: 'This command can only be used in ticket channels.',
ephemeral: true,
});
}
const userId = [...client.activeTickets.entries()].find(([k, v]) => v === channel.id)?.[0];
if (userId) {
client.activeTickets.delete(userId);
}
await interaction.reply({
content: 'Closing ticket in 5 seconds...',
});
client.sendAlert(`Ticket ${channel.name} closed by ${interaction.user.tag}`);
setTimeout(async () => {
try {
await channel.delete();
} catch (err) {
console.error('[Ticket] Delete error:', err);
}
}, 5000);
}
},
};

View file

@ -0,0 +1,80 @@
const { EmbedBuilder } = require('discord.js');
module.exports = {
name: 'guildMemberUpdate',
async execute(oldMember, newMember, client) {
try {
const addedRoles = newMember.roles.cache.filter(role => !oldMember.roles.cache.has(role.id));
const removedRoles = oldMember.roles.cache.filter(role => !newMember.roles.cache.has(role.id));
if (addedRoles.size === 0 && removedRoles.size === 0) return;
const REALM_GUILDS = client.REALM_GUILDS;
const realmGuildIds = Object.values(REALM_GUILDS).filter(Boolean);
if (!realmGuildIds.includes(newMember.guild.id)) return;
const federationMappings = client.federationMappings;
for (const [roleId, role] of addedRoles) {
const mapping = federationMappings.get(roleId);
if (!mapping) continue;
console.log(`[Federation] Syncing role ${role.name} for ${newMember.user.tag} across realms`);
for (const targetGuildId of realmGuildIds) {
if (targetGuildId === newMember.guild.id) continue;
try {
const targetGuild = client.guilds.cache.get(targetGuildId);
if (!targetGuild) continue;
const targetMember = await targetGuild.members.fetch(newMember.user.id).catch(() => null);
if (!targetMember) continue;
const targetRole = targetGuild.roles.cache.find(r => r.name === role.name);
if (!targetRole) continue;
if (!targetMember.roles.cache.has(targetRole.id)) {
await targetMember.roles.add(targetRole, '[Federation] Cross-server sync');
console.log(`[Federation] Added ${role.name} to ${newMember.user.tag} in ${targetGuild.name}`);
}
} catch (err) {
console.error(`[Federation] Sync error for guild ${targetGuildId}:`, err.message);
}
}
}
for (const [roleId, role] of removedRoles) {
const mapping = federationMappings.get(roleId);
if (!mapping) continue;
console.log(`[Federation] Removing synced role ${role.name} for ${newMember.user.tag} across realms`);
for (const targetGuildId of realmGuildIds) {
if (targetGuildId === newMember.guild.id) continue;
try {
const targetGuild = client.guilds.cache.get(targetGuildId);
if (!targetGuild) continue;
const targetMember = await targetGuild.members.fetch(newMember.user.id).catch(() => null);
if (!targetMember) continue;
const targetRole = targetGuild.roles.cache.find(r => r.name === role.name);
if (!targetRole) continue;
if (targetMember.roles.cache.has(targetRole.id)) {
await targetMember.roles.remove(targetRole, '[Federation] Cross-server sync');
console.log(`[Federation] Removed ${role.name} from ${newMember.user.tag} in ${targetGuild.name}`);
}
} catch (err) {
console.error(`[Federation] Sync error for guild ${targetGuildId}:`, err.message);
}
}
}
} catch (error) {
console.error('[Federation] guildMemberUpdate error:', error);
}
},
};

View file

@ -0,0 +1,55 @@
const { EmbedBuilder, AuditLogEvent } = require('discord.js');
module.exports = {
name: 'channelDelete',
async execute(channel, client) {
try {
const guild = channel.guild;
if (!guild) return;
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.ChannelDelete,
limit: 1,
});
const log = auditLogs.entries.first();
if (!log) return;
const { executor, target } = log;
if (!executor || executor.id === client.user.id) return;
const heat = client.addHeat(executor.id, 'CHANNEL_DELETE');
console.log(`[Sentinel] User ${executor.tag} deleted channel. Heat: ${heat}/${client.HEAT_THRESHOLD}`);
if (heat >= client.HEAT_THRESHOLD) {
const embed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle('ANTI-NUKE TRIGGERED')
.setDescription(`User **${executor.tag}** has been banned for deleting too many channels.`)
.addFields(
{ name: 'User ID', value: executor.id, inline: true },
{ name: 'Heat Level', value: `${heat}/${client.HEAT_THRESHOLD}`, inline: true },
{ name: 'Action', value: 'Automatic Ban', inline: true }
)
.setTimestamp();
try {
await guild.members.ban(executor.id, { reason: '[Sentinel] Anti-nuke: Mass channel deletion detected' });
console.log(`[Sentinel] BANNED ${executor.tag} for mass channel deletion`);
client.sendAlert(`ANTI-NUKE: Banned ${executor.tag} for mass channel deletion`, embed);
const owner = await guild.fetchOwner();
if (owner) {
await owner.send({ content: `[SENTINEL ALERT] User ${executor.tag} was banned for mass channel deletion in ${guild.name}`, embeds: [embed] }).catch(() => {});
}
} catch (banError) {
console.error('[Sentinel] Failed to ban user:', banError.message);
client.sendAlert(`ANTI-NUKE FAILED: Could not ban ${executor.tag} - ${banError.message}`);
}
}
} catch (error) {
console.error('[Sentinel] Channel delete handler error:', error);
}
},
};

View file

@ -0,0 +1,53 @@
const { EmbedBuilder, AuditLogEvent } = require('discord.js');
module.exports = {
name: 'guildBanAdd',
async execute(ban, client) {
try {
const guild = ban.guild;
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.MemberBanAdd,
limit: 1,
});
const log = auditLogs.entries.first();
if (!log) return;
const { executor, target } = log;
if (!executor || executor.id === client.user.id) return;
const heat = client.addHeat(executor.id, 'MEMBER_BAN_ADD');
console.log(`[Sentinel] User ${executor.tag} banned ${target.tag}. Heat: ${heat}/${client.HEAT_THRESHOLD}`);
if (heat >= client.HEAT_THRESHOLD) {
const embed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle('ANTI-NUKE TRIGGERED')
.setDescription(`User **${executor.tag}** has been banned for mass banning members.`)
.addFields(
{ name: 'User ID', value: executor.id, inline: true },
{ name: 'Heat Level', value: `${heat}/${client.HEAT_THRESHOLD}`, inline: true },
{ name: 'Action', value: 'Mass Ban Detected', inline: true }
)
.setTimestamp();
try {
await guild.members.ban(executor.id, { reason: '[Sentinel] Anti-nuke: Mass banning detected' });
console.log(`[Sentinel] BANNED ${executor.tag} for mass banning`);
client.sendAlert(`ANTI-NUKE: Banned ${executor.tag} for mass banning`, embed);
const owner = await guild.fetchOwner();
if (owner) {
await owner.send({ content: `[SENTINEL ALERT] User ${executor.tag} was banned for mass banning in ${guild.name}`, embeds: [embed] }).catch(() => {});
}
} catch (banError) {
console.error('[Sentinel] Failed to ban user:', banError.message);
}
}
} catch (error) {
console.error('[Sentinel] Member ban handler error:', error);
}
},
};

View file

@ -0,0 +1,56 @@
const { EmbedBuilder, AuditLogEvent } = require('discord.js');
module.exports = {
name: 'guildMemberRemove',
async execute(member, client) {
try {
const guild = member.guild;
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.MemberKick,
limit: 1,
});
const log = auditLogs.entries.first();
if (!log) return;
const { executor, target, createdTimestamp } = log;
if (!executor || executor.id === client.user.id) return;
if (Date.now() - createdTimestamp > 5000) return;
if (target.id !== member.id) return;
const heat = client.addHeat(executor.id, 'MEMBER_KICK');
console.log(`[Sentinel] User ${executor.tag} kicked ${member.user.tag}. Heat: ${heat}/${client.HEAT_THRESHOLD}`);
if (heat >= client.HEAT_THRESHOLD) {
const embed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle('ANTI-NUKE TRIGGERED')
.setDescription(`User **${executor.tag}** has been banned for mass kicking members.`)
.addFields(
{ name: 'User ID', value: executor.id, inline: true },
{ name: 'Heat Level', value: `${heat}/${client.HEAT_THRESHOLD}`, inline: true },
{ name: 'Action', value: 'Mass Kick Detected', inline: true }
)
.setTimestamp();
try {
await guild.members.ban(executor.id, { reason: '[Sentinel] Anti-nuke: Mass kicking detected' });
console.log(`[Sentinel] BANNED ${executor.tag} for mass kicking`);
client.sendAlert(`ANTI-NUKE: Banned ${executor.tag} for mass kicking`, embed);
const owner = await guild.fetchOwner();
if (owner) {
await owner.send({ content: `[SENTINEL ALERT] User ${executor.tag} was banned for mass kicking in ${guild.name}`, embeds: [embed] }).catch(() => {});
}
} catch (banError) {
console.error('[Sentinel] Failed to ban user:', banError.message);
}
}
} catch (error) {
console.error('[Sentinel] Member kick handler error:', error);
}
},
};

View file

@ -0,0 +1,54 @@
const { EmbedBuilder, AuditLogEvent } = require('discord.js');
module.exports = {
name: 'roleDelete',
async execute(role, client) {
try {
const guild = role.guild;
if (!guild) return;
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.RoleDelete,
limit: 1,
});
const log = auditLogs.entries.first();
if (!log) return;
const { executor } = log;
if (!executor || executor.id === client.user.id) return;
const heat = client.addHeat(executor.id, 'ROLE_DELETE');
console.log(`[Sentinel] User ${executor.tag} deleted role ${role.name}. Heat: ${heat}/${client.HEAT_THRESHOLD}`);
if (heat >= client.HEAT_THRESHOLD) {
const embed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle('ANTI-NUKE TRIGGERED')
.setDescription(`User **${executor.tag}** has been banned for deleting too many roles.`)
.addFields(
{ name: 'User ID', value: executor.id, inline: true },
{ name: 'Heat Level', value: `${heat}/${client.HEAT_THRESHOLD}`, inline: true },
{ name: 'Deleted Role', value: role.name, inline: true }
)
.setTimestamp();
try {
await guild.members.ban(executor.id, { reason: '[Sentinel] Anti-nuke: Mass role deletion detected' });
console.log(`[Sentinel] BANNED ${executor.tag} for mass role deletion`);
client.sendAlert(`ANTI-NUKE: Banned ${executor.tag} for mass role deletion`, embed);
const owner = await guild.fetchOwner();
if (owner) {
await owner.send({ content: `[SENTINEL ALERT] User ${executor.tag} was banned for mass role deletion in ${guild.name}`, embeds: [embed] }).catch(() => {});
}
} catch (banError) {
console.error('[Sentinel] Failed to ban user:', banError.message);
}
}
} catch (error) {
console.error('[Sentinel] Role delete handler error:', error);
}
},
};

1082
aethex-bot/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

24
aethex-bot/package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "aethex-unified-bot",
"version": "2.0.0",
"description": "AeThex Unified Bot - Community features + Sentinel security",
"main": "bot.js",
"type": "commonjs",
"scripts": {
"start": "node bot.js",
"dev": "nodemon bot.js",
"register-commands": "node scripts/register-commands.js"
},
"dependencies": {
"@supabase/supabase-js": "^2.38.0",
"axios": "^1.6.0",
"discord.js": "^14.13.0",
"dotenv": "^16.3.1"
},
"devDependencies": {
"nodemon": "^3.0.1"
},
"engines": {
"node": ">=20.0.0"
}
}

View file

@ -0,0 +1,129 @@
const { REST, Routes } = require('discord.js');
require('dotenv').config();
const commands = [
{
name: 'ticket',
description: 'Ticket management system',
options: [
{
name: 'create',
type: 1,
description: 'Create a new support ticket',
options: [
{
name: 'reason',
type: 3,
description: 'Brief reason for opening this ticket',
required: true,
},
],
},
{
name: 'close',
type: 1,
description: 'Close the current ticket',
},
],
},
{
name: 'admin',
description: 'Admin monitoring commands',
default_member_permissions: '8',
options: [
{
name: 'status',
type: 1,
description: 'View bot status and statistics',
},
{
name: 'heat',
type: 1,
description: 'Check heat level of a user',
options: [
{
name: 'user',
type: 6,
description: 'User to check',
required: true,
},
],
},
{
name: 'servers',
type: 1,
description: 'View all servers the bot is in',
},
{
name: 'threats',
type: 1,
description: 'View current heat map (active threats)',
},
{
name: 'federation',
type: 1,
description: 'View federation role mappings',
},
],
},
{
name: 'federation',
description: 'Manage cross-server role sync',
default_member_permissions: '8',
options: [
{
name: 'link',
type: 1,
description: 'Link a role for cross-server syncing',
options: [
{
name: 'role',
type: 8,
description: 'Role to sync across realms',
required: true,
},
],
},
{
name: 'unlink',
type: 1,
description: 'Remove a role from cross-server syncing',
options: [
{
name: 'role',
type: 8,
description: 'Role to remove from sync',
required: true,
},
],
},
{
name: 'list',
type: 1,
description: 'List all linked roles',
},
],
},
{
name: 'status',
description: 'View network status and bot health',
},
];
const token = process.env.DISCORD_BOT_TOKEN || process.env.DISCORD_TOKEN;
const rest = new REST({ version: '10' }).setToken(token);
(async () => {
try {
console.log('Registering slash commands...');
const data = await rest.put(
Routes.applicationCommands(process.env.DISCORD_CLIENT_ID),
{ body: commands }
);
console.log(`Successfully registered ${data.length} commands`);
} catch (error) {
console.error('Error registering commands:', error);
}
})();