From ddea985e6ff30fa5fe3f7cad19c879d02432d730 Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Sun, 7 Dec 2025 22:16:10 +0000 Subject: [PATCH] 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 --- .replit | 27 +- aethex-bot/.env.example | 21 + aethex-bot/bot.js | 263 +++++ aethex-bot/commands/admin.js | 157 +++ aethex-bot/commands/federation.js | 114 ++ aethex-bot/commands/status.js | 57 + aethex-bot/commands/ticket.js | 128 +++ aethex-bot/events/guildMemberUpdate.js | 80 ++ aethex-bot/listeners/sentinel/antiNuke.js | 55 + aethex-bot/listeners/sentinel/memberBan.js | 53 + aethex-bot/listeners/sentinel/memberKick.js | 56 + aethex-bot/listeners/sentinel/roleDelete.js | 54 + aethex-bot/package-lock.json | 1082 +++++++++++++++++++ aethex-bot/package.json | 24 + aethex-bot/scripts/register-commands.js | 129 +++ 15 files changed, 2281 insertions(+), 19 deletions(-) create mode 100644 aethex-bot/.env.example create mode 100644 aethex-bot/bot.js create mode 100644 aethex-bot/commands/admin.js create mode 100644 aethex-bot/commands/federation.js create mode 100644 aethex-bot/commands/status.js create mode 100644 aethex-bot/commands/ticket.js create mode 100644 aethex-bot/events/guildMemberUpdate.js create mode 100644 aethex-bot/listeners/sentinel/antiNuke.js create mode 100644 aethex-bot/listeners/sentinel/memberBan.js create mode 100644 aethex-bot/listeners/sentinel/memberKick.js create mode 100644 aethex-bot/listeners/sentinel/roleDelete.js create mode 100644 aethex-bot/package-lock.json create mode 100644 aethex-bot/package.json create mode 100644 aethex-bot/scripts/register-commands.js diff --git a/.replit b/.replit index b061b9e..10ff530 100644 --- a/.replit +++ b/.replit @@ -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" diff --git a/aethex-bot/.env.example b/aethex-bot/.env.example new file mode 100644 index 0000000..45b42f1 --- /dev/null +++ b/aethex-bot/.env.example @@ -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 diff --git a/aethex-bot/bot.js b/aethex-bot/bot.js new file mode 100644 index 0000000..057281e --- /dev/null +++ b/aethex-bot/bot.js @@ -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; diff --git a/aethex-bot/commands/admin.js b/aethex-bot/commands/admin.js new file mode 100644 index 0000000..4573f7b --- /dev/null +++ b/aethex-bot/commands/admin.js @@ -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 }); + } + }, +}; diff --git a/aethex-bot/commands/federation.js b/aethex-bot/commands/federation.js new file mode 100644 index 0000000..a55e3ba --- /dev/null +++ b/aethex-bot/commands/federation.js @@ -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 `).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 }); + } + }, +}; diff --git a/aethex-bot/commands/status.js b/aethex-bot/commands/status.js new file mode 100644 index 0000000..b1cd9ca --- /dev/null +++ b/aethex-bot/commands/status.js @@ -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] }); + }, +}; diff --git a/aethex-bot/commands/ticket.js b/aethex-bot/commands/ticket.js new file mode 100644 index 0000000..8a9fb2b --- /dev/null +++ b/aethex-bot/commands/ticket.js @@ -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: ``, 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); + } + }, +}; diff --git a/aethex-bot/events/guildMemberUpdate.js b/aethex-bot/events/guildMemberUpdate.js new file mode 100644 index 0000000..0e0c6b8 --- /dev/null +++ b/aethex-bot/events/guildMemberUpdate.js @@ -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); + } + }, +}; diff --git a/aethex-bot/listeners/sentinel/antiNuke.js b/aethex-bot/listeners/sentinel/antiNuke.js new file mode 100644 index 0000000..161a935 --- /dev/null +++ b/aethex-bot/listeners/sentinel/antiNuke.js @@ -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); + } + }, +}; diff --git a/aethex-bot/listeners/sentinel/memberBan.js b/aethex-bot/listeners/sentinel/memberBan.js new file mode 100644 index 0000000..c847d55 --- /dev/null +++ b/aethex-bot/listeners/sentinel/memberBan.js @@ -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); + } + }, +}; diff --git a/aethex-bot/listeners/sentinel/memberKick.js b/aethex-bot/listeners/sentinel/memberKick.js new file mode 100644 index 0000000..c1fd69c --- /dev/null +++ b/aethex-bot/listeners/sentinel/memberKick.js @@ -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); + } + }, +}; diff --git a/aethex-bot/listeners/sentinel/roleDelete.js b/aethex-bot/listeners/sentinel/roleDelete.js new file mode 100644 index 0000000..3fe5690 --- /dev/null +++ b/aethex-bot/listeners/sentinel/roleDelete.js @@ -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); + } + }, +}; diff --git a/aethex-bot/package-lock.json b/aethex-bot/package-lock.json new file mode 100644 index 0000000..2fbca63 --- /dev/null +++ b/aethex-bot/package-lock.json @@ -0,0 +1,1082 @@ +{ + "name": "aethex-unified-bot", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aethex-unified-bot", + "version": "2.0.0", + "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" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", + "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/formatters": "^0.6.2", + "@discordjs/util": "^1.2.0", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.38.33", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", + "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", + "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.1.1", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.38.16", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/util": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", + "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.38.1", + "tslib": "^2.6.2", + "ws": "^8.17.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@supabase/auth-js": { + "version": "2.86.2", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.86.2.tgz", + "integrity": "sha512-7k8IAhgSnZuD9Zex2+ohHKY3aWGDd4ls0xlxMGl3/jPyHSSXrIYfmtJyUH0+DPd4B3psBqHC0Ev0/nZEHdW58w==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.86.2", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.86.2.tgz", + "integrity": "sha512-OLpy3NIlj7q3yGMFwUpPkDPJbRx4aU+u73SiXqiMnA5ARwzVcOReSzI2u4oOqioE+3ud0fRx7sRsfoklBwYOmg==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.86.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.86.2.tgz", + "integrity": "sha512-KVgOF2QASvUfQnzMGAmxR7f3ZF/eZ8PFp2F5Q7SAPQlmB83FEaZ7C/QMzfVXXqkMbotfh96xcaBNSKnxowFObA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.86.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.86.2.tgz", + "integrity": "sha512-uLUYrOMeK1qXHISxdMFVfBs0sGV5PmqYewIHvLBnMYbb//LERojxfKlVSJBgZ+aAwxANmtQKcprjGZI7DJ6lNQ==", + "license": "MIT", + "dependencies": { + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.86.2", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.86.2.tgz", + "integrity": "sha512-zyR4PkO7R4f4/xRBVJho3Dm7y4512BoCqGmD7LjNV2GVtWt8vEmambiuMB2Ty3l76mqw+ynQyHY8yFWSERrHXA==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.0", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.86.2", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.86.2.tgz", + "integrity": "sha512-KXoiqFf7zZhL/+lj7oBFFUvVDQ6gy03v9wQ5E++f7xiJUuqmI4DuBhrv8uFo6B2EGTQTA3vkXjbxmYIug/zfWw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.86.2", + "@supabase/functions-js": "2.86.2", + "@supabase/postgrest-js": "2.86.2", + "@supabase/realtime-js": "2.86.2", + "@supabase/storage-js": "2.86.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", + "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/discord-api-types": { + "version": "0.38.36", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.36.tgz", + "integrity": "sha512-qrbUbjjwtyeBg5HsAlm1C859epfOyiLjPqAOzkdWlCNsZCWJrertnETF/NwM8H+waMFU58xGSc5eXUfXah+WTQ==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, + "node_modules/discord.js": { + "version": "14.25.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz", + "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/builders": "^1.13.0", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.6.2", + "@discordjs/rest": "^2.6.0", + "@discordjs/util": "^1.2.0", + "@discordjs/ws": "^1.2.3", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "^0.38.33", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "license": "MIT" + }, + "node_modules/magic-bytes.js": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", + "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/aethex-bot/package.json b/aethex-bot/package.json new file mode 100644 index 0000000..a70d25b --- /dev/null +++ b/aethex-bot/package.json @@ -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" + } +} diff --git a/aethex-bot/scripts/register-commands.js b/aethex-bot/scripts/register-commands.js new file mode 100644 index 0000000..9f72df0 --- /dev/null +++ b/aethex-bot/scripts/register-commands.js @@ -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); + } +})();