From 33cc585ffdf660c7962a5e7cc40e945090a22916 Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Sun, 7 Dec 2025 23:06:47 +0000 Subject: [PATCH] Remove Discord bot workflow and related files Removes the Discord bot workflow, Dockerfile, main bot script, and all command/event/utility files associated with the discord-bot directory. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 34a45df0-a2f2-4adc-9d9b-4a59e3abf7be Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/qPXTzuE Replit-Helium-Checkpoint-Created: true --- discord-bot/.env | 2 - discord-bot/.env.example | 23 - discord-bot/DEPLOYMENT_GUIDE.md | 211 ---- discord-bot/Dockerfile | 22 - discord-bot/bot.js | 803 --------------- discord-bot/commands/help.js | 55 - discord-bot/commands/leaderboard.js | 155 --- discord-bot/commands/post.js | 144 --- discord-bot/commands/profile.js | 93 -- discord-bot/commands/refresh-roles.js | 72 -- discord-bot/commands/set-realm.js | 139 --- discord-bot/commands/stats.js | 140 --- discord-bot/commands/unlink.js | 75 -- discord-bot/commands/verify-role.js | 97 -- discord-bot/commands/verify.js | 85 -- discord-bot/discloud.config | 10 - discord-bot/events/messageCreate.js | 180 ---- discord-bot/listeners/feedSync.js | 239 ----- discord-bot/package-lock.json | 1157 ---------------------- discord-bot/package.json | 34 - discord-bot/scripts/register-commands.js | 110 -- discord-bot/utils/roleManager.js | 137 --- 22 files changed, 3983 deletions(-) delete mode 100644 discord-bot/.env delete mode 100644 discord-bot/.env.example delete mode 100644 discord-bot/DEPLOYMENT_GUIDE.md delete mode 100644 discord-bot/Dockerfile delete mode 100644 discord-bot/bot.js delete mode 100644 discord-bot/commands/help.js delete mode 100644 discord-bot/commands/leaderboard.js delete mode 100644 discord-bot/commands/post.js delete mode 100644 discord-bot/commands/profile.js delete mode 100644 discord-bot/commands/refresh-roles.js delete mode 100644 discord-bot/commands/set-realm.js delete mode 100644 discord-bot/commands/stats.js delete mode 100644 discord-bot/commands/unlink.js delete mode 100644 discord-bot/commands/verify-role.js delete mode 100644 discord-bot/commands/verify.js delete mode 100644 discord-bot/discloud.config delete mode 100644 discord-bot/events/messageCreate.js delete mode 100644 discord-bot/listeners/feedSync.js delete mode 100644 discord-bot/package-lock.json delete mode 100644 discord-bot/package.json delete mode 100644 discord-bot/scripts/register-commands.js delete mode 100644 discord-bot/utils/roleManager.js diff --git a/discord-bot/.env b/discord-bot/.env deleted file mode 100644 index e0beef1b..00000000 --- a/discord-bot/.env +++ /dev/null @@ -1,2 +0,0 @@ -DISCORD_BOT_TOKEN=NTc4OTcxMjQ1NDU0OTUwNDIx.GNTBmC.D_vc5srXF-CYBtxWgi9QiJy4PLO2d8XXCfT7NI -DISCORD_CLIENT_ID=578971245454950421 diff --git a/discord-bot/.env.example b/discord-bot/.env.example deleted file mode 100644 index a4ddc917..00000000 --- a/discord-bot/.env.example +++ /dev/null @@ -1,23 +0,0 @@ -# Discord Bot Configuration -DISCORD_BOT_TOKEN=your_bot_token_here -DISCORD_CLIENT_ID=your_client_id_here -DISCORD_PUBLIC_KEY=your_public_key_here - -# Supabase Configuration -SUPABASE_URL=https://your-project.supabase.co -SUPABASE_SERVICE_ROLE=your_service_role_key_here - -# API Configuration -VITE_API_BASE=https://api.aethex.dev - -# Discord Feed Webhook Configuration -DISCORD_FEED_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN -DISCORD_FEED_GUILD_ID=515711457946632232 -DISCORD_FEED_CHANNEL_ID=1425114041021497454 - -# Discord Announcement Channels (comma-separated channel IDs) -DISCORD_ANNOUNCEMENT_CHANNELS=1435667453244866702,your_other_channel_ids_here - -# Discord Role Mappings (optional) -DISCORD_FOUNDER_ROLE_ID=your_role_id_here -DISCORD_ADMIN_ROLE_ID=your_admin_role_id_here diff --git a/discord-bot/DEPLOYMENT_GUIDE.md b/discord-bot/DEPLOYMENT_GUIDE.md deleted file mode 100644 index c90a3aaf..00000000 --- a/discord-bot/DEPLOYMENT_GUIDE.md +++ /dev/null @@ -1,211 +0,0 @@ -# AeThex Discord Bot - Spaceship Deployment Guide - -## ๐Ÿ“‹ Prerequisites - -- Spaceship hosting account with Node.js support -- Discord bot credentials (already in your environment variables) -- Supabase project credentials -- Git access to your repository - -## ๐Ÿš€ Deployment Steps - -### Step 1: Prepare the Bot Directory - -Ensure all bot files are committed: - -``` -code/discord-bot/ -โ”œโ”€โ”€ bot.js -โ”œโ”€โ”€ package.json -โ”œโ”€โ”€ .env.example -โ”œโ”€โ”€ Dockerfile -โ””โ”€โ”€ commands/ - โ”œโ”€โ”€ verify.js - โ”œโ”€โ”€ set-realm.js - โ”œโ”€โ”€ profile.js - โ”œโ”€โ”€ unlink.js - โ””โ”€โ”€ verify-role.js -``` - -### Step 2: Create Node.js App on Spaceship - -1. Log in to your Spaceship hosting dashboard -2. Click "Create New Application" -3. Select **Node.js** as the runtime -4. Name it: `aethex-discord-bot` -5. Select your repository and branch - -### Step 3: Configure Environment Variables - -In Spaceship Application Settings โ†’ Environment Variables, add: - -``` -DISCORD_BOT_TOKEN= -DISCORD_CLIENT_ID= -DISCORD_PUBLIC_KEY= -SUPABASE_URL= -SUPABASE_SERVICE_ROLE= -BOT_PORT=3000 -NODE_ENV=production -``` - -**Note:** Get these values from: - -- Discord Developer Portal: Applications โ†’ Your Bot โ†’ Token & General Information -- Supabase Dashboard: Project Settings โ†’ API - -### Step 4: Configure Build & Run Settings - -In Spaceship Application Settings: - -**Build Command:** - -```bash -npm install -``` - -**Start Command:** - -```bash -npm start -``` - -**Root Directory:** - -``` -code/discord-bot -``` - -### Step 5: Deploy - -1. Click "Deploy" in Spaceship dashboard -2. Monitor logs for: - ``` - โœ… Bot logged in as # - ๐Ÿ“ก Listening in X server(s) - โœ… Successfully registered X slash commands. - ``` - -### Step 6: Verify Bot is Online - -Once deployed: - -1. Go to your Discord server -2. Type `/verify` - the command autocomplete should appear -3. Bot should be online with status "Listening to /verify to link your AeThex account" - -## ๐Ÿ“ก Discord Bot Endpoints - -The bot will be accessible at: - -``` -https:/// -``` - -The bot uses Discord's WebSocket connection (not HTTP), so it doesn't need to expose HTTP endpoints. It listens to Discord events via `client.login(DISCORD_BOT_TOKEN)`. - -## ๐Ÿ”Œ API Integration - -Frontend calls to link Discord accounts: - -- **Endpoint:** `POST /api/discord/link` -- **Body:** `{ verification_code, user_id }` -- **Response:** `{ success: true, message: "..." }` - -Discord Verify page (`/discord-verify?code=XXX`) will automatically: - -1. Call `/api/discord/link` with the verification code -2. Link the Discord ID to the AeThex user account -3. Redirect to dashboard on success - -## ๐Ÿ› ๏ธ Debugging - -### Check bot logs on Spaceship: - -- Application โ†’ Logs -- Filter for "bot.js" or "error" - -### Common issues: - -**"Discord bot not responding to commands"** - -- Check: `DISCORD_BOT_TOKEN` is correct -- Check: Bot is added to the Discord server with "applications.commands" scope -- Check: Spaceship logs show "โœ… Logged in" - -**"Supabase verification fails"** - -- Check: `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE` are correct -- Check: `discord_links` and `discord_verifications` tables exist -- Run migration: `code/supabase/migrations/20250107_add_discord_integration.sql` - -**"Slash commands not appearing in Discord"** - -- Check: Logs show "โœ… Successfully registered X slash commands" -- Discord may need 1-2 minutes to sync commands -- Try typing `/` in Discord to force refresh -- Check: Bot has "applications.commands" permission in server - -## ๐Ÿ“Š Monitoring - -### Key metrics to monitor: - -- Bot uptime (should be 24/7) -- Command usage (in Supabase) -- Verification code usage (in Supabase) -- Discord role sync success rate - -### View in Admin Dashboard: - -- AeThex Admin Panel โ†’ Discord Management tab -- Shows: - - Bot status - - Servers connected - - Linked accounts count - - Role mapping status - -## ๐Ÿ”„ Updating the Bot - -1. Make code changes locally -2. Test with `npm start` -3. Commit and push to your branch -4. Spaceship will auto-deploy on push -5. Monitor logs to ensure deployment succeeds - -## ๐Ÿ†˜ Support - -For issues: - -1. Check Spaceship logs -2. Review `/api/discord/link` endpoint response -3. Verify all environment variables are set correctly -4. Ensure Supabase tables exist and have correct schema - -## ๐Ÿ“ Database Setup - -Run this migration on your AeThex Supabase: - -```sql --- From code/supabase/migrations/20250107_add_discord_integration.sql --- This creates: --- - discord_links (links Discord ID to AeThex user) --- - discord_verifications (temporary verification codes) --- - discord_role_mappings (realm โ†’ Discord role mapping) --- - discord_user_roles (tracking assigned roles) -``` - -## ๐ŸŽ‰ You're All Set! - -Once deployed, users can: - -1. Click "Link Discord" in their profile settings -2. Type `/verify` in Discord -3. Click the verification link -4. Their Discord account is linked to their AeThex account -5. They can use `/set-realm`, `/profile`, `/unlink`, and `/verify-role` commands - ---- - -**Deployment Date:** `` -**Bot Status:** `` -**Last Updated:** `` diff --git a/discord-bot/Dockerfile b/discord-bot/Dockerfile deleted file mode 100644 index 279b52f6..00000000 --- a/discord-bot/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM node:18-alpine - -WORKDIR /app - -# Copy package files -COPY package*.json ./ - -# Install dependencies -RUN npm install --production - -# Copy bot source -COPY . . - -# Expose port -EXPOSE 3000 - -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD node -e "require('http').get('http://localhost:3000/health', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" - -# Start bot -CMD ["npm", "start"] diff --git a/discord-bot/bot.js b/discord-bot/bot.js deleted file mode 100644 index 401b1321..00000000 --- a/discord-bot/bot.js +++ /dev/null @@ -1,803 +0,0 @@ -const { - Client, - GatewayIntentBits, - REST, - Routes, - Collection, - EmbedBuilder, -} = 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 { setupFeedListener, sendPostToDiscord, getFeedChannelId } = require("./listeners/feedSync"); - -// Validate environment variables -const requiredEnvVars = [ - "DISCORD_BOT_TOKEN", - "DISCORD_CLIENT_ID", - "SUPABASE_URL", - "SUPABASE_SERVICE_ROLE", -]; - -const missingVars = requiredEnvVars.filter((envVar) => !process.env[envVar]); -if (missingVars.length > 0) { - console.error( - "โŒ FATAL ERROR: Missing required environment variables:", - missingVars.join(", "), - ); - console.error("\nPlease set these in your Discloud/hosting environment:"); - missingVars.forEach((envVar) => { - console.error(` - ${envVar}`); - }); - process.exit(1); -} - -// Validate token format -const token = process.env.DISCORD_BOT_TOKEN; -if (!token || token.length < 20) { - console.error("โŒ FATAL ERROR: DISCORD_BOT_TOKEN is empty or invalid"); - console.error(` Length: ${token ? token.length : 0}`); - process.exit(1); -} - -console.log("[Token] Bot token loaded (length: " + token.length + " chars)"); - -// Initialize Discord client with message intents for feed sync -const client = new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.DirectMessages, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent, - ], -}); - -// Initialize Supabase -const supabase = createClient( - process.env.SUPABASE_URL, - process.env.SUPABASE_SERVICE_ROLE, -); - -// Store slash commands -client.commands = new Collection(); - -// Load commands from commands directory -const commandsPath = path.join(__dirname, "commands"); -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}`); - } -} - -// Load event handlers from events directory -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 listener: ${event.name}`); - } - } -} - -// Slash command interaction handler -client.on("interactionCreate", async (interaction) => { - if (!interaction.isChatInputCommand()) return; - - const command = client.commands.get(interaction.commandName); - - if (!command) { - console.warn( - `โš ๏ธ No command matching ${interaction.commandName} was found.`, - ); - 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.") - .setFooter({ text: "Contact support if this persists" }); - - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ embeds: [errorEmbed], ephemeral: true }); - } else { - await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); - } - } -}); - -// IMPORTANT: Commands are now registered via a separate script -// Run this ONCE during deployment: npm run register-commands -// This prevents Error 50240 (Entry Point conflict) when Activities are enabled -// The bot will simply load and listen for the already-registered commands - -// Define all commands for registration -const COMMANDS_TO_REGISTER = [ - { - name: "verify", - description: "Link your Discord account to AeThex", - }, - { - name: "set-realm", - description: "Choose your primary arm/realm (Labs, GameForge, Corp, etc.)", - options: [ - { - name: "realm", - type: 3, - description: "Your primary realm", - required: true, - choices: [ - { name: "Labs", value: "labs" }, - { name: "GameForge", value: "gameforge" }, - { name: "Corp", value: "corp" }, - { name: "Foundation", value: "foundation" }, - { name: "Dev-Link", value: "devlink" }, - ], - }, - ], - }, - { - name: "profile", - description: "View your linked AeThex profile", - }, - { - name: "unlink", - description: "Disconnect your Discord account from AeThex", - }, - { - name: "verify-role", - description: "Check your assigned Discord roles", - }, - { - name: "help", - description: "View all AeThex bot commands and features", - }, - { - name: "stats", - description: "View your AeThex statistics and activity", - }, - { - name: "leaderboard", - description: "View the top AeThex contributors", - options: [ - { - name: "category", - type: 3, - description: "Leaderboard category", - required: false, - choices: [ - { name: "Most Active (Posts)", value: "posts" }, - { name: "Most Liked", value: "likes" }, - { name: "Top Creators", value: "creators" }, - ], - }, - ], - }, - { - name: "post", - description: "Create a post in the AeThex community feed", - options: [ - { - name: "content", - type: 3, - description: "Your post content", - required: true, - max_length: 500, - }, - { - name: "category", - type: 3, - description: "Post category", - required: false, - choices: [ - { name: "General", value: "general" }, - { name: "Project Update", value: "project_update" }, - { name: "Question", value: "question" }, - { name: "Idea", value: "idea" }, - { name: "Announcement", value: "announcement" }, - ], - }, - { - name: "image", - type: 11, - description: "Attach an image to your post", - required: false, - }, - ], - }, -]; - -// Function to register commands with Discord -async function registerDiscordCommands() { - try { - const rest = new REST({ version: "10" }).setToken( - process.env.DISCORD_BOT_TOKEN, - ); - - console.log( - `๐Ÿ“ Registering ${COMMANDS_TO_REGISTER.length} slash commands...`, - ); - - try { - // Try bulk update first - const data = await rest.put( - Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), - { body: COMMANDS_TO_REGISTER }, - ); - - console.log(`โœ… Successfully registered ${data.length} slash commands`); - return { success: true, count: data.length, results: null }; - } catch (bulkError) { - // Handle Error 50240 (Entry Point conflict) - if (bulkError.code === 50240) { - console.warn( - "โš ๏ธ Error 50240: Entry Point detected. Registering individually...", - ); - - const results = []; - let successCount = 0; - let skipCount = 0; - - for (const command of COMMANDS_TO_REGISTER) { - try { - const posted = await rest.post( - Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), - { body: command }, - ); - results.push({ - name: command.name, - status: "registered", - id: posted.id, - }); - successCount++; - } catch (postError) { - if (postError.code === 50045) { - results.push({ - name: command.name, - status: "already_exists", - }); - skipCount++; - } else { - results.push({ - name: command.name, - status: "error", - error: postError.message, - }); - } - } - } - - console.log( - `โœ… Registration complete: ${successCount} new, ${skipCount} already existed`, - ); - return { - success: true, - count: successCount, - skipped: skipCount, - results, - }; - } - - throw bulkError; - } - } catch (error) { - console.error("โŒ Failed to register commands:", error); - return { success: false, error: error.message }; - } -} - -// Start HTTP health check server -const healthPort = process.env.HEALTH_PORT || 8080; -const ADMIN_TOKEN = process.env.DISCORD_ADMIN_TOKEN || "aethex-bot-admin"; - -// Helper to check admin authentication -const checkAdminAuth = (req) => { - const authHeader = req.headers.authorization; - return authHeader === `Bearer ${ADMIN_TOKEN}`; -}; - -http - .createServer((req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); - res.setHeader("Content-Type", "application/json"); - - if (req.method === "OPTIONS") { - res.writeHead(200); - res.end(); - return; - } - - 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()), - timestamp: new Date().toISOString(), - }), - ); - return; - } - - // GET /bot-status - Comprehensive bot status for management panel (requires auth) - if (req.url === "/bot-status") { - if (!checkAdminAuth(req)) { - res.writeHead(401); - res.end(JSON.stringify({ error: "Unauthorized - Admin token required" })); - return; - } - - const channelId = getFeedChannelId(); - const guilds = client.guilds.cache.map((guild) => ({ - id: guild.id, - name: guild.name, - memberCount: guild.memberCount, - icon: guild.iconURL(), - })); - - res.writeHead(200); - res.end( - JSON.stringify({ - status: client.isReady() ? "online" : "offline", - bot: { - tag: client.user?.tag || "Not logged in", - id: client.user?.id, - avatar: client.user?.displayAvatarURL(), - }, - guilds: guilds, - guildCount: client.guilds.cache.size, - commands: Array.from(client.commands.keys()), - commandCount: client.commands.size, - uptime: Math.floor(process.uptime()), - feedBridge: { - enabled: !!channelId, - channelId: channelId, - }, - timestamp: new Date().toISOString(), - }), - ); - return; - } - - // GET /linked-users - Get all Discord-linked users (requires auth, sanitizes PII) - if (req.url === "/linked-users") { - if (!checkAdminAuth(req)) { - res.writeHead(401); - res.end(JSON.stringify({ error: "Unauthorized - Admin token required" })); - return; - } - - (async () => { - try { - const { data: links, error } = await supabase - .from("discord_links") - .select("discord_id, user_id, primary_arm, created_at") - .order("created_at", { ascending: false }) - .limit(50); - - if (error) throw error; - - const enrichedLinks = await Promise.all( - (links || []).map(async (link) => { - const { data: profile } = await supabase - .from("user_profiles") - .select("username, avatar_url") - .eq("id", link.user_id) - .single(); - - return { - discord_id: link.discord_id.slice(0, 6) + "***", - user_id: link.user_id.slice(0, 8) + "...", - primary_arm: link.primary_arm, - created_at: link.created_at, - profile: profile ? { - username: profile.username, - avatar_url: profile.avatar_url, - } : null, - }; - }) - ); - - res.writeHead(200); - res.end(JSON.stringify({ success: true, links: enrichedLinks, count: enrichedLinks.length })); - } catch (error) { - res.writeHead(500); - res.end(JSON.stringify({ success: false, error: error.message })); - } - })(); - return; - } - - // GET /command-stats - Get command usage statistics (requires auth) - if (req.url === "/command-stats") { - if (!checkAdminAuth(req)) { - res.writeHead(401); - res.end(JSON.stringify({ error: "Unauthorized - Admin token required" })); - return; - } - - (async () => { - try { - const stats = { - commands: COMMANDS_TO_REGISTER.map((cmd) => ({ - name: cmd.name, - description: cmd.description, - options: cmd.options?.length || 0, - })), - totalCommands: COMMANDS_TO_REGISTER.length, - }; - - res.writeHead(200); - res.end(JSON.stringify({ success: true, stats })); - } catch (error) { - res.writeHead(500); - res.end(JSON.stringify({ success: false, error: error.message })); - } - })(); - return; - } - - // GET /feed-stats - Get feed bridge statistics (requires auth) - if (req.url === "/feed-stats") { - if (!checkAdminAuth(req)) { - res.writeHead(401); - res.end(JSON.stringify({ error: "Unauthorized - Admin token required" })); - return; - } - - (async () => { - try { - const { count: totalPosts } = await supabase - .from("community_posts") - .select("*", { count: "exact", head: true }); - - const { count: discordPosts } = await supabase - .from("community_posts") - .select("*", { count: "exact", head: true }) - .eq("source", "discord"); - - const { count: websitePosts } = await supabase - .from("community_posts") - .select("*", { count: "exact", head: true }) - .or("source.is.null,source.neq.discord"); - - const { data: recentPosts } = await supabase - .from("community_posts") - .select("id, content, source, created_at") - .order("created_at", { ascending: false }) - .limit(10); - - res.writeHead(200); - res.end( - JSON.stringify({ - success: true, - stats: { - totalPosts: totalPosts || 0, - discordPosts: discordPosts || 0, - websitePosts: websitePosts || 0, - recentPosts: (recentPosts || []).map(p => ({ - id: p.id, - content: p.content?.slice(0, 100) + (p.content?.length > 100 ? "..." : ""), - source: p.source, - created_at: p.created_at, - })), - }, - }) - ); - } catch (error) { - res.writeHead(500); - res.end(JSON.stringify({ success: false, error: error.message })); - } - })(); - return; - } - - // POST /send-to-discord - Send a post from AeThex to Discord channel - if (req.url === "/send-to-discord" && req.method === "POST") { - let body = ""; - req.on("data", (chunk) => { - body += chunk.toString(); - }); - req.on("end", async () => { - try { - // Simple auth check - const authHeader = req.headers.authorization; - const expectedToken = process.env.DISCORD_BRIDGE_TOKEN || "aethex-bridge"; - if (authHeader !== `Bearer ${expectedToken}`) { - res.writeHead(401); - res.end(JSON.stringify({ error: "Unauthorized" })); - return; - } - - const post = JSON.parse(body); - console.log("[API] Received post to send to Discord:", post.id); - - const result = await sendPostToDiscord(post, post.author); - res.writeHead(result.success ? 200 : 500); - res.end(JSON.stringify(result)); - } catch (error) { - console.error("[API] Error processing send-to-discord:", error); - res.writeHead(500); - res.end(JSON.stringify({ error: error.message })); - } - }); - return; - } - - // GET /bridge-status - Check if bridge is configured - if (req.url === "/bridge-status") { - const channelId = getFeedChannelId(); - res.writeHead(200); - res.end( - JSON.stringify({ - enabled: !!channelId, - channelId: channelId, - botReady: client.isReady(), - }), - ); - return; - } - - if (req.url === "/register-commands") { - if (req.method === "GET") { - if (!checkAdminAuth(req)) { - res.writeHead(401); - res.end(JSON.stringify({ error: "Unauthorized - Admin token required" })); - return; - } - // Show HTML form with button - res.writeHead(200, { "Content-Type": "text/html" }); - res.end(` - - - - Register Discord Commands - - - -
-

๐Ÿค– Discord Commands Registration

-

Click the button below to register all Discord slash commands (/verify, /set-realm, /profile, /unlink, /verify-role)

- - - -
โณ Registering... please wait...
-
-
- - - - - `); - return; - } - - if (req.method === "POST") { - // Verify admin token - if (!checkAdminAuth(req)) { - res.writeHead(401); - res.end(JSON.stringify({ error: "Unauthorized - Admin token required" })); - return; - } - - // Register commands - registerDiscordCommands().then((result) => { - if (result.success) { - res.writeHead(200); - res.end(JSON.stringify(result)); - } else { - res.writeHead(500); - res.end(JSON.stringify(result)); - } - }); - return; - } - } - - res.writeHead(404); - res.end(JSON.stringify({ error: "Not found" })); - }) - .listen(healthPort, () => { - console.log(`๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ Health check server running on port ${healthPort}`); - console.log( - `๐Ÿ“ Register commands at: POST http://localhost:${healthPort}/register-commands`, - ); - }); - -// Login with error handling -client.login(process.env.DISCORD_BOT_TOKEN).catch((error) => { - console.error("โŒ FATAL ERROR: Failed to login to Discord"); - console.error(` Error Code: ${error.code}`); - console.error(` Error Message: ${error.message}`); - - if (error.code === "TokenInvalid") { - console.error("\nโš ๏ธ DISCORD_BOT_TOKEN is invalid!"); - console.error(" Possible causes:"); - console.error(" 1. Token has been revoked by Discord"); - console.error(" 2. Token has expired"); - console.error(" 3. Token format is incorrect"); - console.error( - "\n Solution: Get a new bot token from Discord Developer Portal", - ); - console.error(" https://discord.com/developers/applications"); - } - - process.exit(1); -}); - -client.once("ready", () => { - console.log(`โœ… Bot logged in as ${client.user.tag}`); - console.log(`๐Ÿ“ก Listening in ${client.guilds.cache.size} server(s)`); - console.log("โ„น๏ธ Commands are registered via: npm run register-commands"); - - // Set bot status - client.user.setActivity("/verify to link your AeThex account", { - type: "LISTENING", - }); - - // Setup bidirectional feed bridge (AeThex โ†’ Discord) - setupFeedListener(client); -}); - -// Error handling -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/discord-bot/commands/help.js b/discord-bot/commands/help.js deleted file mode 100644 index 324b1dd2..00000000 --- a/discord-bot/commands/help.js +++ /dev/null @@ -1,55 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("help") - .setDescription("View all AeThex bot commands and features"), - - async execute(interaction) { - const embed = new EmbedBuilder() - .setColor(0x7289da) - .setTitle("๐Ÿค– AeThex Bot Commands") - .setDescription("Here are all the commands you can use with the AeThex Discord bot.") - .addFields( - { - name: "๐Ÿ”— Account Linking", - value: [ - "`/verify` - Link your Discord account to AeThex", - "`/unlink` - Disconnect your Discord from AeThex", - "`/profile` - View your linked AeThex profile", - ].join("\n"), - }, - { - name: "โš”๏ธ Realm Management", - value: [ - "`/set-realm` - Choose your primary realm (Labs, GameForge, Corp, Foundation, Dev-Link)", - "`/verify-role` - Check your assigned Discord roles", - ].join("\n"), - }, - { - name: "๐Ÿ“Š Community", - value: [ - "`/stats` - View your AeThex statistics and activity", - "`/leaderboard` - See the top contributors", - "`/post` - Create a post in the AeThex community feed", - ].join("\n"), - }, - { - name: "โ„น๏ธ Information", - value: "`/help` - Show this help message", - }, - ) - .addFields({ - name: "๐Ÿ”— Quick Links", - value: [ - "[AeThex Platform](https://aethex.dev)", - "[Creator Directory](https://aethex.dev/creators)", - "[Community Feed](https://aethex.dev/community/feed)", - ].join(" | "), - }) - .setFooter({ text: "AeThex | Build. Create. Connect." }) - .setTimestamp(); - - await interaction.reply({ embeds: [embed], ephemeral: true }); - }, -}; diff --git a/discord-bot/commands/leaderboard.js b/discord-bot/commands/leaderboard.js deleted file mode 100644 index cbc5b015..00000000 --- a/discord-bot/commands/leaderboard.js +++ /dev/null @@ -1,155 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("leaderboard") - .setDescription("View the top AeThex contributors") - .addStringOption((option) => - option - .setName("category") - .setDescription("Leaderboard category") - .setRequired(false) - .addChoices( - { name: "๐Ÿ”ฅ Most Active (Posts)", value: "posts" }, - { name: "โค๏ธ Most Liked", value: "likes" }, - { name: "๐ŸŽจ Top Creators", value: "creators" } - ) - ), - - async execute(interaction, supabase) { - await interaction.deferReply(); - - try { - const category = interaction.options.getString("category") || "posts"; - - let leaderboardData = []; - let title = ""; - let emoji = ""; - - if (category === "posts") { - title = "Most Active Posters"; - emoji = "๐Ÿ”ฅ"; - - const { data: posts } = await supabase - .from("community_posts") - .select("user_id") - .not("user_id", "is", null); - - const postCounts = {}; - posts?.forEach((post) => { - postCounts[post.user_id] = (postCounts[post.user_id] || 0) + 1; - }); - - const sortedUsers = Object.entries(postCounts) - .sort(([, a], [, b]) => b - a) - .slice(0, 10); - - for (const [userId, count] of sortedUsers) { - const { data: profile } = await supabase - .from("user_profiles") - .select("username, full_name, avatar_url") - .eq("id", userId) - .single(); - - if (profile) { - leaderboardData.push({ - name: profile.full_name || profile.username || "Anonymous", - value: `${count} posts`, - username: profile.username, - }); - } - } - } else if (category === "likes") { - title = "Most Liked Users"; - emoji = "โค๏ธ"; - - const { data: posts } = await supabase - .from("community_posts") - .select("user_id, likes_count") - .not("user_id", "is", null) - .order("likes_count", { ascending: false }); - - const likeCounts = {}; - posts?.forEach((post) => { - likeCounts[post.user_id] = - (likeCounts[post.user_id] || 0) + (post.likes_count || 0); - }); - - const sortedUsers = Object.entries(likeCounts) - .sort(([, a], [, b]) => b - a) - .slice(0, 10); - - for (const [userId, count] of sortedUsers) { - const { data: profile } = await supabase - .from("user_profiles") - .select("username, full_name, avatar_url") - .eq("id", userId) - .single(); - - if (profile) { - leaderboardData.push({ - name: profile.full_name || profile.username || "Anonymous", - value: `${count} likes received`, - username: profile.username, - }); - } - } - } else if (category === "creators") { - title = "Top Creators"; - emoji = "๐ŸŽจ"; - - const { data: creators } = await supabase - .from("aethex_creators") - .select("user_id, total_projects, verified, featured") - .order("total_projects", { ascending: false }) - .limit(10); - - for (const creator of creators || []) { - const { data: profile } = await supabase - .from("user_profiles") - .select("username, full_name, avatar_url") - .eq("id", creator.user_id) - .single(); - - if (profile) { - const badges = []; - if (creator.verified) badges.push("โœ…"); - if (creator.featured) badges.push("โญ"); - - leaderboardData.push({ - name: profile.full_name || profile.username || "Anonymous", - value: `${creator.total_projects || 0} projects ${badges.join(" ")}`, - username: profile.username, - }); - } - } - } - - const embed = new EmbedBuilder() - .setColor(0x7289da) - .setTitle(`${emoji} ${title}`) - .setDescription( - leaderboardData.length > 0 - ? leaderboardData - .map( - (user, index) => - `**${index + 1}.** ${user.name} - ${user.value}` - ) - .join("\n") - : "No data available yet. Be the first to contribute!" - ) - .setFooter({ text: "AeThex Leaderboard | Updated in real-time" }) - .setTimestamp(); - - await interaction.editReply({ embeds: [embed] }); - } catch (error) { - console.error("Leaderboard command error:", error); - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle("โŒ Error") - .setDescription("Failed to fetch leaderboard. Please try again."); - - await interaction.editReply({ embeds: [embed] }); - } - }, -}; diff --git a/discord-bot/commands/post.js b/discord-bot/commands/post.js deleted file mode 100644 index 61057e61..00000000 --- a/discord-bot/commands/post.js +++ /dev/null @@ -1,144 +0,0 @@ -const { - SlashCommandBuilder, - EmbedBuilder, - ModalBuilder, - TextInputBuilder, - TextInputStyle, - ActionRowBuilder, -} = require("discord.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("post") - .setDescription("Create a post in the AeThex community feed") - .addStringOption((option) => - option - .setName("content") - .setDescription("Your post content") - .setRequired(true) - .setMaxLength(500) - ) - .addStringOption((option) => - option - .setName("category") - .setDescription("Post category") - .setRequired(false) - .addChoices( - { name: "๐Ÿ’ฌ General", value: "general" }, - { name: "๐Ÿš€ Project Update", value: "project_update" }, - { name: "โ“ Question", value: "question" }, - { name: "๐Ÿ’ก Idea", value: "idea" }, - { name: "๐ŸŽ‰ Announcement", value: "announcement" } - ) - ) - .addAttachmentOption((option) => - option - .setName("image") - .setDescription("Attach an image to your post") - .setRequired(false) - ), - - async execute(interaction, supabase, client) { - await interaction.deferReply({ ephemeral: true }); - - try { - const { data: link } = await supabase - .from("discord_links") - .select("user_id, primary_arm") - .eq("discord_id", interaction.user.id) - .single(); - - if (!link) { - const embed = new EmbedBuilder() - .setColor(0xff6b6b) - .setTitle("โŒ Not Linked") - .setDescription( - "You must link your Discord account to AeThex first.\nUse `/verify` to get started." - ); - - return await interaction.editReply({ embeds: [embed] }); - } - - const { data: profile } = await supabase - .from("user_profiles") - .select("username, full_name, avatar_url") - .eq("id", link.user_id) - .single(); - - const content = interaction.options.getString("content"); - const category = interaction.options.getString("category") || "general"; - const attachment = interaction.options.getAttachment("image"); - - let imageUrl = null; - if (attachment && attachment.contentType?.startsWith("image/")) { - imageUrl = attachment.url; - } - - const categoryLabels = { - general: "General", - project_update: "Project Update", - question: "Question", - idea: "Idea", - announcement: "Announcement", - }; - - const { data: post, error } = await supabase - .from("community_posts") - .insert({ - user_id: link.user_id, - content: content, - category: category, - arm_affiliation: link.primary_arm || "general", - image_url: imageUrl, - source: "discord", - discord_message_id: interaction.id, - discord_author_id: interaction.user.id, - discord_author_name: interaction.user.username, - discord_author_avatar: interaction.user.displayAvatarURL(), - }) - .select() - .single(); - - if (error) throw error; - - const successEmbed = new EmbedBuilder() - .setColor(0x00ff00) - .setTitle("โœ… Post Created!") - .setDescription(content.length > 100 ? content.slice(0, 100) + "..." : content) - .addFields( - { - name: "๐Ÿ“ Category", - value: categoryLabels[category], - inline: true, - }, - { - name: "โš”๏ธ Realm", - value: link.primary_arm || "general", - inline: true, - } - ); - - if (imageUrl) { - successEmbed.setImage(imageUrl); - } - - successEmbed - .addFields({ - name: "๐Ÿ”— View Post", - value: `[Open in AeThex](https://aethex.dev/community/feed)`, - }) - .setFooter({ text: "Your post is now live on AeThex!" }) - .setTimestamp(); - - await interaction.editReply({ embeds: [successEmbed] }); - } catch (error) { - console.error("Post command error:", error); - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle("โŒ Error") - .setDescription("Failed to create post. Please try again."); - - await interaction.editReply({ embeds: [embed] }); - } - }, -}; diff --git a/discord-bot/commands/profile.js b/discord-bot/commands/profile.js deleted file mode 100644 index 035f251f..00000000 --- a/discord-bot/commands/profile.js +++ /dev/null @@ -1,93 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("profile") - .setDescription("View your AeThex profile in Discord"), - - async execute(interaction, supabase) { - await interaction.deferReply({ ephemeral: true }); - - try { - const { data: link } = await supabase - .from("discord_links") - .select("user_id, primary_arm") - .eq("discord_id", interaction.user.id) - .single(); - - if (!link) { - const embed = new EmbedBuilder() - .setColor(0xff6b6b) - .setTitle("โŒ Not Linked") - .setDescription( - "You must link your Discord account to AeThex first.\nUse `/verify` to get started.", - ); - - return await interaction.editReply({ embeds: [embed] }); - } - - const { data: profile } = await supabase - .from("user_profiles") - .select("*") - .eq("id", link.user_id) - .single(); - - if (!profile) { - const embed = new EmbedBuilder() - .setColor(0xff6b6b) - .setTitle("โŒ Profile Not Found") - .setDescription("Your AeThex profile could not be found."); - - return await interaction.editReply({ embeds: [embed] }); - } - - const armEmojis = { - labs: "๐Ÿงช", - gameforge: "๐ŸŽฎ", - corp: "๐Ÿ’ผ", - foundation: "๐Ÿค", - devlink: "๐Ÿ’ป", - }; - - const embed = new EmbedBuilder() - .setColor(0x7289da) - .setTitle(`${profile.full_name || "AeThex User"}'s Profile`) - .setThumbnail( - profile.avatar_url || "https://aethex.dev/placeholder.svg", - ) - .addFields( - { - name: "๐Ÿ‘ค Username", - value: profile.username || "N/A", - inline: true, - }, - { - name: `${armEmojis[link.primary_arm] || "โš”๏ธ"} Primary Realm`, - value: link.primary_arm || "Not set", - inline: true, - }, - { - name: "๐Ÿ“Š Role", - value: profile.user_type || "community_member", - inline: true, - }, - { name: "๐Ÿ“ Bio", value: profile.bio || "No bio set", inline: false }, - ) - .addFields({ - name: "๐Ÿ”— Links", - value: `[Visit Full Profile](https://aethex.dev/creators/${profile.username})`, - }) - .setFooter({ text: "AeThex | Your Web3 Creator Hub" }); - - await interaction.editReply({ embeds: [embed] }); - } catch (error) { - console.error("Profile command error:", error); - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle("โŒ Error") - .setDescription("Failed to fetch profile. Please try again."); - - await interaction.editReply({ embeds: [embed] }); - } - }, -}; diff --git a/discord-bot/commands/refresh-roles.js b/discord-bot/commands/refresh-roles.js deleted file mode 100644 index 459bd79f..00000000 --- a/discord-bot/commands/refresh-roles.js +++ /dev/null @@ -1,72 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); -const { assignRoleByArm, getUserArm } = require("../utils/roleManager"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("refresh-roles") - .setDescription( - "Refresh your Discord roles based on your current AeThex settings", - ), - - async execute(interaction, supabase, client) { - await interaction.deferReply({ ephemeral: true }); - - try { - // Check if user is linked - const { data: link } = await supabase - .from("discord_links") - .select("primary_arm") - .eq("discord_id", interaction.user.id) - .maybeSingle(); - - if (!link) { - const embed = new EmbedBuilder() - .setColor(0xff6b6b) - .setTitle("โŒ Not Linked") - .setDescription( - "You must link your Discord account to AeThex first.\nUse `/verify` to get started.", - ); - - return await interaction.editReply({ embeds: [embed] }); - } - - if (!link.primary_arm) { - const embed = new EmbedBuilder() - .setColor(0xffaa00) - .setTitle("โš ๏ธ No Realm Set") - .setDescription( - "You haven't set your primary realm yet.\nUse `/set-realm` to choose one.", - ); - - return await interaction.editReply({ embeds: [embed] }); - } - - // Assign role based on current primary arm - const roleAssigned = await assignRoleByArm( - interaction.guild, - interaction.user.id, - link.primary_arm, - supabase, - ); - - const embed = new EmbedBuilder() - .setColor(roleAssigned ? 0x00ff00 : 0xffaa00) - .setTitle("โœ… Roles Refreshed") - .setDescription( - roleAssigned - ? `Your Discord roles have been synced with your AeThex account.\n\nPrimary Realm: **${link.primary_arm}**` - : `Your roles could not be automatically assigned.\n\nPrimary Realm: **${link.primary_arm}**\n\nโš ๏ธ Please contact an admin to set up the role mapping for this server.`, - ); - - await interaction.editReply({ embeds: [embed] }); - } catch (error) { - console.error("Refresh-roles command error:", error); - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle("โŒ Error") - .setDescription("Failed to refresh roles. Please try again."); - - await interaction.editReply({ embeds: [embed] }); - } - }, -}; diff --git a/discord-bot/commands/set-realm.js b/discord-bot/commands/set-realm.js deleted file mode 100644 index c1af120a..00000000 --- a/discord-bot/commands/set-realm.js +++ /dev/null @@ -1,139 +0,0 @@ -const { - SlashCommandBuilder, - EmbedBuilder, - StringSelectMenuBuilder, - ActionRowBuilder, -} = require("discord.js"); -const { assignRoleByArm } = require("../utils/roleManager"); - -const REALMS = [ - { value: "labs", label: "๐Ÿงช Labs", description: "Research & Development" }, - { - value: "gameforge", - label: "๐ŸŽฎ GameForge", - description: "Game Development", - }, - { value: "corp", label: "๐Ÿ’ผ Corp", description: "Enterprise Solutions" }, - { - value: "foundation", - label: "๐Ÿค Foundation", - description: "Community & Education", - }, - { - value: "devlink", - label: "๐Ÿ’ป Dev-Link", - description: "Professional Networking", - }, -]; - -module.exports = { - data: new SlashCommandBuilder() - .setName("set-realm") - .setDescription("Set your primary AeThex realm/arm"), - - async execute(interaction, supabase, client) { - await interaction.deferReply({ ephemeral: true }); - - try { - const { data: link } = await supabase - .from("discord_links") - .select("user_id, primary_arm") - .eq("discord_id", interaction.user.id) - .single(); - - if (!link) { - const embed = new EmbedBuilder() - .setColor(0xff6b6b) - .setTitle("โŒ Not Linked") - .setDescription( - "You must link your Discord account to AeThex first.\nUse `/verify` to get started.", - ); - - return await interaction.editReply({ embeds: [embed] }); - } - - const select = new StringSelectMenuBuilder() - .setCustomId("select_realm") - .setPlaceholder("Choose your primary realm") - .addOptions( - REALMS.map((realm) => ({ - label: realm.label, - description: realm.description, - value: realm.value, - default: realm.value === link.primary_arm, - })), - ); - - const row = new ActionRowBuilder().addComponents(select); - - const embed = new EmbedBuilder() - .setColor(0x7289da) - .setTitle("โš”๏ธ Choose Your Realm") - .setDescription( - "Select your primary AeThex realm. This determines your main Discord role.", - ) - .addFields({ - name: "Current Realm", - value: link.primary_arm || "Not set", - }); - - await interaction.editReply({ embeds: [embed], components: [row] }); - - const filter = (i) => - i.user.id === interaction.user.id && i.customId === "select_realm"; - const collector = interaction.channel.createMessageComponentCollector({ - filter, - time: 60000, - }); - - collector.on("collect", async (i) => { - const selectedRealm = i.values[0]; - - await supabase - .from("discord_links") - .update({ primary_arm: selectedRealm }) - .eq("discord_id", interaction.user.id); - - const realm = REALMS.find((r) => r.value === selectedRealm); - - // Assign Discord role based on selected realm - const roleAssigned = await assignRoleByArm( - interaction.guild, - interaction.user.id, - selectedRealm, - supabase, - ); - - const roleStatus = roleAssigned - ? "โœ… Discord role assigned!" - : "โš ๏ธ No role mapping found for this realm in this server."; - - const confirmEmbed = new EmbedBuilder() - .setColor(0x00ff00) - .setTitle("โœ… Realm Set") - .setDescription( - `Your primary realm is now **${realm.label}**\n\n${roleStatus}`, - ); - - await i.update({ embeds: [confirmEmbed], components: [] }); - }); - - collector.on("end", (collected) => { - if (collected.size === 0) { - interaction.editReply({ - content: "Realm selection timed out.", - components: [], - }); - } - }); - } catch (error) { - console.error("Set-realm command error:", error); - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle("โŒ Error") - .setDescription("Failed to update realm. Please try again."); - - await interaction.editReply({ embeds: [embed] }); - } - }, -}; diff --git a/discord-bot/commands/stats.js b/discord-bot/commands/stats.js deleted file mode 100644 index fe9814b0..00000000 --- a/discord-bot/commands/stats.js +++ /dev/null @@ -1,140 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("stats") - .setDescription("View your AeThex statistics and activity"), - - async execute(interaction, supabase) { - await interaction.deferReply({ ephemeral: true }); - - try { - const { data: link } = await supabase - .from("discord_links") - .select("user_id, primary_arm, created_at") - .eq("discord_id", interaction.user.id) - .single(); - - if (!link) { - const embed = new EmbedBuilder() - .setColor(0xff6b6b) - .setTitle("โŒ Not Linked") - .setDescription( - "You must link your Discord account to AeThex first.\nUse `/verify` to get started." - ); - - return await interaction.editReply({ embeds: [embed] }); - } - - const { data: profile } = await supabase - .from("user_profiles") - .select("*") - .eq("id", link.user_id) - .single(); - - const { count: postCount } = await supabase - .from("community_posts") - .select("*", { count: "exact", head: true }) - .eq("user_id", link.user_id); - - const { count: likeCount } = await supabase - .from("community_likes") - .select("*", { count: "exact", head: true }) - .eq("user_id", link.user_id); - - const { count: commentCount } = await supabase - .from("community_comments") - .select("*", { count: "exact", head: true }) - .eq("user_id", link.user_id); - - const { data: creatorProfile } = await supabase - .from("aethex_creators") - .select("verified, featured, total_projects") - .eq("user_id", link.user_id) - .single(); - - const armEmojis = { - labs: "๐Ÿงช", - gameforge: "๐ŸŽฎ", - corp: "๐Ÿ’ผ", - foundation: "๐Ÿค", - devlink: "๐Ÿ’ป", - }; - - const linkedDate = new Date(link.created_at); - const daysSinceLinked = Math.floor( - (Date.now() - linkedDate.getTime()) / (1000 * 60 * 60 * 24) - ); - - const embed = new EmbedBuilder() - .setColor(0x7289da) - .setTitle(`๐Ÿ“Š ${profile?.full_name || interaction.user.username}'s Stats`) - .setThumbnail(profile?.avatar_url || interaction.user.displayAvatarURL()) - .addFields( - { - name: `${armEmojis[link.primary_arm] || "โš”๏ธ"} Primary Realm`, - value: link.primary_arm || "Not set", - inline: true, - }, - { - name: "๐Ÿ‘ค Account Type", - value: profile?.user_type || "community_member", - inline: true, - }, - { - name: "๐Ÿ“… Days Linked", - value: `${daysSinceLinked} days`, - inline: true, - } - ) - .addFields( - { - name: "๐Ÿ“ Posts", - value: `${postCount || 0}`, - inline: true, - }, - { - name: "โค๏ธ Likes Given", - value: `${likeCount || 0}`, - inline: true, - }, - { - name: "๐Ÿ’ฌ Comments", - value: `${commentCount || 0}`, - inline: true, - } - ); - - if (creatorProfile) { - embed.addFields({ - name: "๐ŸŽจ Creator Status", - value: [ - creatorProfile.verified ? "โœ… Verified Creator" : "โณ Pending Verification", - creatorProfile.featured ? "โญ Featured" : "", - `๐Ÿ“ ${creatorProfile.total_projects || 0} Projects`, - ] - .filter(Boolean) - .join("\n"), - }); - } - - embed - .addFields({ - name: "๐Ÿ”— Full Profile", - value: `[View on AeThex](https://aethex.dev/creators/${profile?.username || link.user_id})`, - }) - .setFooter({ text: "AeThex | Your Creative Hub" }) - .setTimestamp(); - - await interaction.editReply({ embeds: [embed] }); - } catch (error) { - console.error("Stats command error:", error); - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle("โŒ Error") - .setDescription("Failed to fetch stats. Please try again."); - - await interaction.editReply({ embeds: [embed] }); - } - }, -}; diff --git a/discord-bot/commands/unlink.js b/discord-bot/commands/unlink.js deleted file mode 100644 index ac06d2a5..00000000 --- a/discord-bot/commands/unlink.js +++ /dev/null @@ -1,75 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("unlink") - .setDescription("Unlink your Discord account from AeThex"), - - async execute(interaction, supabase) { - await interaction.deferReply({ ephemeral: true }); - - try { - const { data: link } = await supabase - .from("discord_links") - .select("*") - .eq("discord_id", interaction.user.id) - .single(); - - if (!link) { - const embed = new EmbedBuilder() - .setColor(0xff6b6b) - .setTitle("โ„น๏ธ Not Linked") - .setDescription("Your Discord account is not linked to AeThex."); - - return await interaction.editReply({ embeds: [embed] }); - } - - // Delete the link - await supabase - .from("discord_links") - .delete() - .eq("discord_id", interaction.user.id); - - // Remove Discord roles from user - const guild = interaction.guild; - const member = await guild.members.fetch(interaction.user.id); - - // Find and remove all AeThex-related roles - const rolesToRemove = member.roles.cache.filter( - (role) => - role.name.includes("Labs") || - role.name.includes("GameForge") || - role.name.includes("Corp") || - role.name.includes("Foundation") || - role.name.includes("Dev-Link") || - role.name.includes("Premium") || - role.name.includes("Creator"), - ); - - for (const [, role] of rolesToRemove) { - try { - await member.roles.remove(role); - } catch (e) { - console.warn(`Could not remove role ${role.name}`); - } - } - - const embed = new EmbedBuilder() - .setColor(0x00ff00) - .setTitle("โœ… Account Unlinked") - .setDescription( - "Your Discord account has been unlinked from AeThex.\nAll associated roles have been removed.", - ); - - await interaction.editReply({ embeds: [embed] }); - } catch (error) { - console.error("Unlink command error:", error); - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle("โŒ Error") - .setDescription("Failed to unlink account. Please try again."); - - await interaction.editReply({ embeds: [embed] }); - } - }, -}; diff --git a/discord-bot/commands/verify-role.js b/discord-bot/commands/verify-role.js deleted file mode 100644 index 1b7e6b9f..00000000 --- a/discord-bot/commands/verify-role.js +++ /dev/null @@ -1,97 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("verify-role") - .setDescription("Check your AeThex-assigned Discord roles"), - - async execute(interaction, supabase) { - await interaction.deferReply({ ephemeral: true }); - - try { - const { data: link } = await supabase - .from("discord_links") - .select("user_id, primary_arm") - .eq("discord_id", interaction.user.id) - .single(); - - if (!link) { - const embed = new EmbedBuilder() - .setColor(0xff6b6b) - .setTitle("โŒ Not Linked") - .setDescription( - "You must link your Discord account to AeThex first.\nUse `/verify` to get started.", - ); - - return await interaction.editReply({ embeds: [embed] }); - } - - const { data: profile } = await supabase - .from("user_profiles") - .select("user_type") - .eq("id", link.user_id) - .single(); - - const { data: mappings } = await supabase - .from("discord_role_mappings") - .select("discord_role") - .eq("arm", link.primary_arm) - .eq("user_type", profile?.user_type || "community_member"); - - const member = await interaction.guild.members.fetch(interaction.user.id); - const aethexRoles = member.roles.cache.filter( - (role) => - role.name.includes("Labs") || - role.name.includes("GameForge") || - role.name.includes("Corp") || - role.name.includes("Foundation") || - role.name.includes("Dev-Link") || - role.name.includes("Premium") || - role.name.includes("Creator"), - ); - - const embed = new EmbedBuilder() - .setColor(0x7289da) - .setTitle("๐Ÿ” Your AeThex Roles") - .addFields( - { - name: "โš”๏ธ Primary Realm", - value: link.primary_arm || "Not set", - inline: true, - }, - { - name: "๐Ÿ‘ค User Type", - value: profile?.user_type || "community_member", - inline: true, - }, - { - name: "๐ŸŽญ Discord Roles", - value: - aethexRoles.size > 0 - ? aethexRoles.map((r) => r.name).join(", ") - : "None assigned yet", - }, - { - name: "๐Ÿ“‹ Expected Roles", - value: - mappings?.length > 0 - ? mappings.map((m) => m.discord_role).join(", ") - : "No mappings found", - }, - ) - .setFooter({ - text: "Roles are assigned automatically based on your AeThex profile", - }); - - await interaction.editReply({ embeds: [embed] }); - } catch (error) { - console.error("Verify-role command error:", error); - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle("โŒ Error") - .setDescription("Failed to verify roles. Please try again."); - - await interaction.editReply({ embeds: [embed] }); - } - }, -}; diff --git a/discord-bot/commands/verify.js b/discord-bot/commands/verify.js deleted file mode 100644 index d9f30e79..00000000 --- a/discord-bot/commands/verify.js +++ /dev/null @@ -1,85 +0,0 @@ -const { - SlashCommandBuilder, - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, -} = require("discord.js"); -const { syncRolesAcrossGuilds } = require("../utils/roleManager"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("verify") - .setDescription("Link your Discord account to your AeThex account"), - - async execute(interaction, supabase, client) { - await interaction.deferReply({ ephemeral: true }); - - try { - const { data: existingLink } = await supabase - .from("discord_links") - .select("*") - .eq("discord_id", interaction.user.id) - .single(); - - if (existingLink) { - const embed = new EmbedBuilder() - .setColor(0x00ff00) - .setTitle("โœ… Already Linked") - .setDescription( - `Your Discord account is already linked to AeThex (User ID: ${existingLink.user_id})`, - ); - - return await interaction.editReply({ embeds: [embed] }); - } - - // Generate verification code - const verificationCode = Math.random() - .toString(36) - .substring(2, 8) - .toUpperCase(); - const expiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes - - // Store verification code with Discord username - await supabase.from("discord_verifications").insert({ - discord_id: interaction.user.id, - verification_code: verificationCode, - username: interaction.user.username, - expires_at: expiresAt.toISOString(), - }); - - const verifyUrl = `https://aethex.dev/discord-verify?code=${verificationCode}`; - - const embed = new EmbedBuilder() - .setColor(0x7289da) - .setTitle("๐Ÿ”— Link Your AeThex Account") - .setDescription( - "Click the button below to link your Discord account to AeThex.", - ) - .addFields( - { name: "โฑ๏ธ Expires In", value: "15 minutes" }, - { name: "๐Ÿ“ Verification Code", value: `\`${verificationCode}\`` }, - ) - .setFooter({ text: "Your security code will expire in 15 minutes" }); - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel("Link Account") - .setStyle(ButtonStyle.Link) - .setURL(verifyUrl), - ); - - await interaction.editReply({ embeds: [embed], components: [row] }); - } catch (error) { - console.error("Verify command error:", error); - const embed = new EmbedBuilder() - .setColor(0xff0000) - .setTitle("โŒ Error") - .setDescription( - "Failed to generate verification code. Please try again.", - ); - - await interaction.editReply({ embeds: [embed] }); - } - }, -}; diff --git a/discord-bot/discloud.config b/discord-bot/discloud.config deleted file mode 100644 index fe114e6e..00000000 --- a/discord-bot/discloud.config +++ /dev/null @@ -1,10 +0,0 @@ -TYPE=bot -MAIN=bot.js -NAME=AeThex -AVATAR=https://docs.aethex.tech/~gitbook/image?url=https%3A%2F%2F1143808467-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Forganizations%252FDhUg3jal6kdpG645FzIl%252Fsites%252Fsite_HeOmR%252Flogo%252FqxDYz8Oj2SnwUTa8t3UB%252FAeThex%2520Origin%2520logo.png%3Falt%3Dmedia%26token%3D200e8ea2-0129-4cbe-b516-4a53f60c512b&width=512&dpr=1&quality=100&sign=6c7576ce&sv=2 -RAM=100 -AUTORESTART=true -APT=tool, education, gamedev -START=npm install -BUILD=npm run build -VLAN=true diff --git a/discord-bot/events/messageCreate.js b/discord-bot/events/messageCreate.js deleted file mode 100644 index 75abc33f..00000000 --- a/discord-bot/events/messageCreate.js +++ /dev/null @@ -1,180 +0,0 @@ -const { createClient } = require("@supabase/supabase-js"); - -const supabase = createClient( - process.env.SUPABASE_URL, - process.env.SUPABASE_SERVICE_ROLE, -); - -// Only sync messages from this specific channel -const FEED_CHANNEL_ID = process.env.DISCORD_MAIN_CHAT_CHANNELS - ? process.env.DISCORD_MAIN_CHAT_CHANNELS.split(",")[0].trim() - : null; - -function getArmAffiliation(message) { - const guildName = message.guild?.name?.toLowerCase() || ""; - const channelName = message.channel?.name?.toLowerCase() || ""; - const searchString = `${guildName} ${channelName}`; - - if (searchString.includes("gameforge")) return "gameforge"; - if (searchString.includes("corp")) return "corp"; - if (searchString.includes("foundation")) return "foundation"; - if (searchString.includes("devlink") || searchString.includes("dev-link")) - return "devlink"; - if (searchString.includes("nexus")) return "nexus"; - if (searchString.includes("staff")) return "staff"; - - return "labs"; -} - -async function syncMessageToFeed(message) { - try { - console.log( - `[Feed Sync] Processing from ${message.author.tag} in #${message.channel.name}`, - ); - - const { data: linkedAccount } = await supabase - .from("discord_links") - .select("user_id") - .eq("discord_id", message.author.id) - .single(); - - let authorId = linkedAccount?.user_id; - let authorInfo = null; - - if (authorId) { - const { data: profile } = await supabase - .from("user_profiles") - .select("id, username, full_name, avatar_url") - .eq("id", authorId) - .single(); - authorInfo = profile; - } - - if (!authorId) { - const discordUsername = `discord-${message.author.id}`; - let { data: guestProfile } = await supabase - .from("user_profiles") - .select("id, username, full_name, avatar_url") - .eq("username", discordUsername) - .single(); - - if (!guestProfile) { - const { data: newProfile, error: createError } = await supabase - .from("user_profiles") - .insert({ - username: discordUsername, - full_name: message.author.displayName || message.author.username, - avatar_url: message.author.displayAvatarURL({ size: 256 }), - }) - .select("id, username, full_name, avatar_url") - .single(); - - if (createError) { - console.error("[Feed Sync] Could not create guest profile:", createError); - return; - } - guestProfile = newProfile; - } - - authorId = guestProfile?.id; - authorInfo = guestProfile; - } - - if (!authorId) { - console.error("[Feed Sync] Could not get author ID"); - return; - } - - let content = message.content || "Shared a message on Discord"; - let mediaUrl = null; - let mediaType = "none"; - - if (message.attachments.size > 0) { - const attachment = message.attachments.first(); - if (attachment) { - mediaUrl = attachment.url; - const attachmentLower = attachment.name.toLowerCase(); - - if ( - [".jpg", ".jpeg", ".png", ".gif", ".webp"].some((ext) => - attachmentLower.endsWith(ext), - ) - ) { - mediaType = "image"; - } else if ( - [".mp4", ".webm", ".mov", ".avi"].some((ext) => - attachmentLower.endsWith(ext), - ) - ) { - mediaType = "video"; - } - } - } - - const armAffiliation = getArmAffiliation(message); - - const postContent = JSON.stringify({ - text: content, - mediaUrl: mediaUrl, - mediaType: mediaType, - source: "discord", - discord_message_id: message.id, - discord_channel_id: message.channelId, - discord_channel_name: message.channel.name, - discord_guild_id: message.guildId, - discord_guild_name: message.guild?.name, - discord_author_id: message.author.id, - discord_author_tag: message.author.tag, - discord_author_avatar: message.author.displayAvatarURL({ size: 256 }), - is_linked_user: !!linkedAccount, - }); - - const { error: insertError } = await supabase - .from("community_posts") - .insert({ - title: content.substring(0, 100) || "Discord Message", - content: postContent, - arm_affiliation: armAffiliation, - author_id: authorId, - tags: ["discord", "feed"], - category: "discord", - is_published: true, - likes_count: 0, - comments_count: 0, - }); - - if (insertError) { - console.error("[Feed Sync] Post creation failed:", insertError); - return; - } - - console.log( - `[Feed Sync] โœ… Synced message from ${message.author.tag} to AeThex feed`, - ); - } catch (error) { - console.error("[Feed Sync] Error:", error); - } -} - -module.exports = { - name: "messageCreate", - async execute(message, client) { - // Ignore bot messages - if (message.author.bot) return; - - // Ignore empty messages - if (!message.content && message.attachments.size === 0) return; - - // Only process messages from the configured feed channel - if (!FEED_CHANNEL_ID) { - return; // No channel configured - } - - if (message.channelId !== FEED_CHANNEL_ID) { - return; // Not the feed channel - } - - // Sync this message to AeThex feed - await syncMessageToFeed(message); - }, -}; diff --git a/discord-bot/listeners/feedSync.js b/discord-bot/listeners/feedSync.js deleted file mode 100644 index b8168c4e..00000000 --- a/discord-bot/listeners/feedSync.js +++ /dev/null @@ -1,239 +0,0 @@ -const { EmbedBuilder } = require("discord.js"); -const { createClient } = require("@supabase/supabase-js"); - -const supabase = createClient( - process.env.SUPABASE_URL, - process.env.SUPABASE_SERVICE_ROLE, -); - -const FEED_CHANNEL_ID = process.env.DISCORD_MAIN_CHAT_CHANNELS - ? process.env.DISCORD_MAIN_CHAT_CHANNELS.split(",")[0].trim() - : null; - -const POLL_INTERVAL = 5000; // Check every 5 seconds - -let discordClient = null; -let lastCheckedTime = null; -let pollInterval = null; -let isPolling = false; // Concurrency lock to prevent overlapping polls -const processedPostIds = new Set(); // Track already-processed posts to prevent duplicates - -function getArmColor(arm) { - const colors = { - labs: 0x00d4ff, - gameforge: 0xff6b00, - corp: 0x9945ff, - foundation: 0x14f195, - devlink: 0xf7931a, - nexus: 0xff00ff, - staff: 0xffd700, - }; - return colors[arm] || 0x5865f2; -} - -function getArmEmoji(arm) { - const emojis = { - labs: "๐Ÿ”ฌ", - gameforge: "๐ŸŽฎ", - corp: "๐Ÿข", - foundation: "๐ŸŽ“", - devlink: "๐Ÿ”—", - nexus: "๐ŸŒ", - staff: "โญ", - }; - return emojis[arm] || "๐Ÿ“"; -} - -async function sendPostToDiscord(post, authorInfo = null) { - if (!discordClient || !FEED_CHANNEL_ID) { - console.log("[Feed Bridge] No Discord client or channel configured"); - return { success: false, error: "No Discord client or channel configured" }; - } - - try { - const channel = await discordClient.channels.fetch(FEED_CHANNEL_ID); - if (!channel || !channel.isTextBased()) { - console.error("[Feed Bridge] Could not find text channel:", FEED_CHANNEL_ID); - return { success: false, error: "Could not find text channel" }; - } - - let content = {}; - try { - content = typeof post.content === "string" ? JSON.parse(post.content) : post.content; - } catch { - content = { text: post.content }; - } - - if (content.source === "discord") { - return { success: true, skipped: true, reason: "Discord-sourced post" }; - } - - let author = authorInfo; - if (!author && post.author_id) { - const { data } = await supabase - .from("user_profiles") - .select("username, full_name, avatar_url") - .eq("id", post.author_id) - .single(); - author = data; - } - - const authorName = author?.full_name || author?.username || "AeThex User"; - // Discord only accepts HTTP/HTTPS URLs for icons - filter out base64/data URLs - const rawAvatar = author?.avatar_url || ""; - const authorAvatar = rawAvatar.startsWith("http://") || rawAvatar.startsWith("https://") - ? rawAvatar - : "https://aethex.dev/logo.png"; - const arm = post.arm_affiliation || "labs"; - - const embed = new EmbedBuilder() - .setColor(getArmColor(arm)) - .setAuthor({ - name: `${getArmEmoji(arm)} ${authorName}`, - iconURL: authorAvatar, - url: `https://aethex.dev/creators/${author?.username || post.author_id}`, - }) - .setDescription(content.text || post.title || "New post") - .setTimestamp(post.created_at ? new Date(post.created_at) : new Date()) - .setFooter({ - text: `Posted from AeThex โ€ข ${arm.charAt(0).toUpperCase() + arm.slice(1)}`, - iconURL: "https://aethex.dev/logo.png", - }); - - if (content.mediaUrl) { - if (content.mediaType === "image") { - embed.setImage(content.mediaUrl); - } else if (content.mediaType === "video") { - embed.addFields({ - name: "๐ŸŽฌ Video", - value: `[Watch Video](${content.mediaUrl})`, - }); - } - } - - if (post.tags && post.tags.length > 0) { - const tagString = post.tags - .filter((t) => t !== "discord" && t !== "main-chat") - .map((t) => `#${t}`) - .join(" "); - if (tagString) { - embed.addFields({ name: "Tags", value: tagString, inline: true }); - } - } - - const postUrl = `https://aethex.dev/community/feed?post=${post.id}`; - embed.addFields({ - name: "๐Ÿ”— View on AeThex", - value: `[Open Post](${postUrl})`, - inline: true, - }); - - await channel.send({ embeds: [embed] }); - console.log(`[Feed Bridge] โœ… Sent post ${post.id} to Discord`); - return { success: true }; - } catch (error) { - console.error("[Feed Bridge] Error sending to Discord:", error); - return { success: false, error: error.message }; - } -} - -async function checkForNewPosts() { - if (!discordClient || !FEED_CHANNEL_ID) return; - - // Prevent overlapping polls - if already polling, skip this run - if (isPolling) { - console.log("[Feed Bridge] Skipping poll - previous poll still in progress"); - return; - } - - isPolling = true; - - try { - const { data: posts, error } = await supabase - .from("community_posts") - .select("*") - .gt("created_at", lastCheckedTime.toISOString()) - .order("created_at", { ascending: true }); - - if (error) { - console.error("[Feed Bridge] Error fetching new posts:", error); - return; - } - - if (posts && posts.length > 0) { - // Update lastCheckedTime IMMEDIATELY after fetching to prevent re-fetching same posts - lastCheckedTime = new Date(posts[posts.length - 1].created_at); - - // Filter out already-processed posts (double safety) - const newPosts = posts.filter(post => !processedPostIds.has(post.id)); - - if (newPosts.length > 0) { - console.log(`[Feed Bridge] Found ${newPosts.length} new post(s)`); - - for (const post of newPosts) { - // Mark as processed BEFORE sending to prevent duplicates - processedPostIds.add(post.id); - - let content = {}; - try { - content = typeof post.content === "string" ? JSON.parse(post.content) : post.content; - } catch { - content = { text: post.content }; - } - - if (content.source === "discord") { - console.log(`[Feed Bridge] Skipping Discord-sourced post ${post.id}`); - continue; - } - - console.log(`[Feed Bridge] Bridging post ${post.id} to Discord...`); - await sendPostToDiscord(post); - } - } - - // Keep processedPostIds from growing indefinitely - trim old entries - if (processedPostIds.size > 1000) { - const idsArray = Array.from(processedPostIds); - idsArray.slice(0, 500).forEach(id => processedPostIds.delete(id)); - } - } - } catch (error) { - console.error("[Feed Bridge] Poll error:", error); - } finally { - isPolling = false; - } -} - -function setupFeedListener(client) { - discordClient = client; - - if (!FEED_CHANNEL_ID) { - console.log("[Feed Bridge] No DISCORD_MAIN_CHAT_CHANNELS configured - bridge disabled"); - return; - } - - lastCheckedTime = new Date(); - - console.log("[Feed Bridge] Starting polling for new posts (every 5 seconds)..."); - - pollInterval = setInterval(checkForNewPosts, POLL_INTERVAL); - - console.log("[Feed Bridge] โœ… Feed bridge ready (channel: " + FEED_CHANNEL_ID + ")"); -} - -function getDiscordClient() { - return discordClient; -} - -function getFeedChannelId() { - return FEED_CHANNEL_ID; -} - -function cleanup() { - if (pollInterval) { - clearInterval(pollInterval); - console.log("[Feed Bridge] Stopped polling"); - } -} - -module.exports = { setupFeedListener, sendPostToDiscord, getDiscordClient, getFeedChannelId, cleanup }; diff --git a/discord-bot/package-lock.json b/discord-bot/package-lock.json deleted file mode 100644 index 15b33b84..00000000 --- a/discord-bot/package-lock.json +++ /dev/null @@ -1,1157 +0,0 @@ -{ - "name": "aethex-discord-bot", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "aethex-discord-bot", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@discord/embedded-app-sdk": "^2.4.0", - "@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/@discord/embedded-app-sdk": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@discord/embedded-app-sdk/-/embedded-app-sdk-2.4.0.tgz", - "integrity": "sha512-kIIS79tuVKvu9YC6GIuvBSfUqNa6511UqafD4i3qGjWSRqVulioYuRzZ+M9D9/KZ2wuu0nQ5IWIYlnh1bsy2tg==", - "license": "MIT", - "dependencies": { - "@types/lodash.transform": "^4.6.6", - "@types/uuid": "^10.0.0", - "big-integer": "^1.6.48", - "decimal.js-light": "^2.5.0", - "eventemitter3": "^5.0.0", - "lodash.transform": "^4.6.0", - "uuid": "^11.0.0", - "zod": "^3.9.8" - } - }, - "node_modules/@discordjs/builders": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.0.tgz", - "integrity": "sha512-COK0uU6ZaJI+LA67H/rp8IbEkYwlZf3mAoBI5wtPh5G5cbEQGNhVpzINg2f/6+q/YipnNIKy6fJDg6kMUKUw4Q==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/formatters": "^0.6.1", - "@discordjs/util": "^1.1.1", - "@sapphire/shapeshift": "^4.0.0", - "discord-api-types": "^0.38.31", - "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.1", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz", - "integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==", - "license": "Apache-2.0", - "dependencies": { - "discord-api-types": "^0.38.1" - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", - "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", - "license": "Apache-2.0", - "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.80.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.80.0.tgz", - "integrity": "sha512-q2LyCVJGN4p7d92cOI7scWOoNwxJhZuFRwiimSUGJGI5zX7ubf1WUPznwOmYEn8WVo3Io+MyMinA7era6j5KPw==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.80.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.80.0.tgz", - "integrity": "sha512-0S/k8LRtoblrbzy4ir9m4WuvU/XTkb1EwL/33/oJexCUHCXtsqaPJ3eKfr1GWtNqTa1zryv6sXs3Fpv7lKCsMQ==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/postgrest-js": { - "version": "2.80.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.80.0.tgz", - "integrity": "sha512-yKzehXlRbDoXIQefdRQnvaI9BEogoWIp/7+y/m5enZDKW2IP9aAgq5tU72sThcwftDJvknnIpEHAABG3qviEng==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.80.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.80.0.tgz", - "integrity": "sha512-cXK6Gs4UDylN8oz40omi01QK0cSCBVj0efXC1WodpENTuDnrkUs28W8/eslEnAtlawaVtikC1Q92mpz9+o85Mg==", - "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.80.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.80.0.tgz", - "integrity": "sha512-Iepod83h2WoMCaLC9pGb3QOT67Kn3RlUdbXpo3uvbDKfPU8EgytS4RVaPmDjhqDjj8AGaiz9mk/ppd2Q2WS+gw==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.80.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.80.0.tgz", - "integrity": "sha512-n8pkXQxuo5zCWXX5cbSNZj1vuWS8IVNGWTmP1m31Iq1k0e8lPZ07PF08TRV79HHq3mEPP/Ko//BQuflHvY2o8w==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.80.0", - "@supabase/functions-js": "2.80.0", - "@supabase/postgrest-js": "2.80.0", - "@supabase/realtime-js": "2.80.0", - "@supabase/storage-js": "2.80.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", - "license": "MIT" - }, - "node_modules/@types/lodash.transform": { - "version": "4.6.9", - "resolved": "https://registry.npmjs.org/@types/lodash.transform/-/lodash.transform-4.6.9.tgz", - "integrity": "sha512-1iIn+l7Vrj8hsr2iZLtxRkcV9AtjTafIyxKO9DX2EEcdOgz3Op5dhwKQFhMJgdfIRbYHBUF+SU97Y6P+zyLXNg==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/node": { - "version": "24.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", - "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", - "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/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "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/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, - "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/decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", - "license": "MIT" - }, - "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.33", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.33.tgz", - "integrity": "sha512-oau1V7OzrNX8yNi+DfQpoLZCNCv7cTFmvPKwHfMrA/tewsO6iQKrMTzA7pa3iBSj0fED6NlklJ/1B/cC1kI08Q==", - "license": "MIT", - "workspaces": [ - "scripts/actions/documentation" - ] - }, - "node_modules/discord.js": { - "version": "14.24.2", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.24.2.tgz", - "integrity": "sha512-VMEDbmguRdX/EeMaTsf9Mb0IQA90WdYF2cn4QDfslQFXgQ6LFtmlPn0FSotnS0kcFbFp+JBSIxtnF+bnAHG/hQ==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/builders": "^1.13.0", - "@discordjs/collection": "1.5.3", - "@discordjs/formatters": "^0.6.1", - "@discordjs/rest": "^2.6.0", - "@discordjs/util": "^1.1.1", - "@discordjs/ws": "^1.2.3", - "@sapphire/snowflake": "3.5.3", - "discord-api-types": "^0.38.31", - "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/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, - "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.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "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/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/lodash.transform": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", - "integrity": "sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==", - "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.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", - "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/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "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 - } - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/discord-bot/package.json b/discord-bot/package.json deleted file mode 100644 index b65f0aca..00000000 --- a/discord-bot/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "aethex-discord-bot", - "version": "1.0.0", - "description": "AeThex Discord Bot - Account linking, role management, and realm selection", - "main": "bot.js", - "type": "commonjs", - "scripts": { - "start": "node bot.js", - "dev": "nodemon bot.js", - "register-commands": "node scripts/register-commands.js" - }, - "keywords": [ - "discord", - "bot", - "aethex", - "role-management", - "discord.js" - ], - "author": "AeThex Team", - "license": "MIT", - "dependencies": { - "@discord/embedded-app-sdk": "^2.4.0", - "@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/discord-bot/scripts/register-commands.js b/discord-bot/scripts/register-commands.js deleted file mode 100644 index 27ffb8d9..00000000 --- a/discord-bot/scripts/register-commands.js +++ /dev/null @@ -1,110 +0,0 @@ -const { REST, Routes } = require("discord.js"); -const fs = require("fs"); -const path = require("path"); -require("dotenv").config(); - -// Validate environment variables -const requiredEnvVars = ["DISCORD_BOT_TOKEN", "DISCORD_CLIENT_ID"]; - -const missingVars = requiredEnvVars.filter((envVar) => !process.env[envVar]); -if (missingVars.length > 0) { - console.error( - "โŒ FATAL ERROR: Missing required environment variables:", - missingVars.join(", "), - ); - console.error("\nPlease set these before running command registration:"); - missingVars.forEach((envVar) => { - console.error(` - ${envVar}`); - }); - process.exit(1); -} - -// Load commands from commands directory -const commandsPath = path.join(__dirname, "../commands"); -const commandFiles = fs - .readdirSync(commandsPath) - .filter((file) => file.endsWith(".js")); - -const commands = []; - -for (const file of commandFiles) { - const filePath = path.join(commandsPath, file); - const command = require(filePath); - if ("data" in command && "execute" in command) { - commands.push(command.data.toJSON()); - console.log(`โœ… Loaded command: ${command.data.name}`); - } -} - -// Register commands with Discord API -async function registerCommands() { - try { - const rest = new REST({ version: "10" }).setToken( - process.env.DISCORD_BOT_TOKEN, - ); - - console.log(`\n๐Ÿ“ Registering ${commands.length} slash commands...`); - console.log( - "โš ๏ธ This will co-exist with Discord's auto-generated Entry Point command.\n", - ); - - try { - const data = await rest.put( - Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), - { body: commands }, - ); - console.log(`โœ… Successfully registered ${data.length} slash commands.`); - console.log("\n๐ŸŽ‰ Command registration complete!"); - console.log("โ„น๏ธ Your commands are now live in Discord."); - console.log( - "โ„น๏ธ The Entry Point command (for Activities) will be managed by Discord.\n", - ); - } catch (error) { - // Handle Entry Point command conflict - if (error.code === 50240) { - console.warn( - "โš ๏ธ Error 50240: Entry Point command detected (Discord Activity enabled).", - ); - console.warn("Registering commands individually...\n"); - - let successCount = 0; - for (const command of commands) { - try { - await rest.post( - Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), - { body: command }, - ); - successCount++; - } catch (postError) { - if (postError.code === 50045) { - console.warn( - ` โš ๏ธ ${command.name}: Already registered (skipping)`, - ); - } else { - console.error(` โŒ ${command.name}: ${postError.message}`); - } - } - } - - console.log( - `\nโœ… Registered ${successCount} slash commands (individual mode).`, - ); - console.log("๐ŸŽ‰ Command registration complete!"); - console.log( - "โ„น๏ธ The Entry Point command will be managed by Discord.\n", - ); - } else { - throw error; - } - } - } catch (error) { - console.error( - "โŒ Fatal error registering commands:", - error.message || error, - ); - process.exit(1); - } -} - -// Run registration -registerCommands(); diff --git a/discord-bot/utils/roleManager.js b/discord-bot/utils/roleManager.js deleted file mode 100644 index 27be439c..00000000 --- a/discord-bot/utils/roleManager.js +++ /dev/null @@ -1,137 +0,0 @@ -const { EmbedBuilder } = require("discord.js"); - -/** - * Assign Discord role based on user's arm and type - * @param {Guild} guild - Discord guild - * @param {string} discordId - Discord user ID - * @param {string} arm - User's primary arm (labs, gameforge, corp, foundation, devlink) - * @param {object} supabase - Supabase client - * @returns {Promise} - Success status - */ -async function assignRoleByArm(guild, discordId, arm, supabase) { - try { - // Fetch guild member - const member = await guild.members.fetch(discordId); - if (!member) { - console.warn(`Member not found: ${discordId}`); - return false; - } - - // Get role mapping from Supabase - const { data: mapping, error: mapError } = await supabase - .from("discord_role_mappings") - .select("discord_role") - .eq("arm", arm) - .eq("server_id", guild.id) - .maybeSingle(); - - if (mapError) { - console.error("Error fetching role mapping:", mapError); - return false; - } - - if (!mapping) { - console.warn( - `No role mapping found for arm: ${arm} in server: ${guild.id}`, - ); - return false; - } - - // Find role by name or ID - let roleToAssign = guild.roles.cache.find( - (r) => r.id === mapping.discord_role || r.name === mapping.discord_role, - ); - - if (!roleToAssign) { - console.warn(`Role not found: ${mapping.discord_role}`); - return false; - } - - // Remove old arm roles - const armRoles = member.roles.cache.filter((role) => - ["Labs", "GameForge", "Corp", "Foundation", "Dev-Link"].some((arm) => - role.name.includes(arm), - ), - ); - - for (const [, role] of armRoles) { - try { - if (role.id !== roleToAssign.id) { - await member.roles.remove(role); - } - } catch (e) { - console.warn(`Could not remove role ${role.name}: ${e.message}`); - } - } - - // Assign new role - if (!member.roles.cache.has(roleToAssign.id)) { - await member.roles.add(roleToAssign); - console.log( - `โœ… Assigned role ${roleToAssign.name} to ${member.user.tag}`, - ); - return true; - } - - return true; - } catch (error) { - console.error("Error assigning role:", error); - return false; - } -} - -/** - * Get user's primary arm from Supabase - * @param {string} discordId - Discord user ID - * @param {object} supabase - Supabase client - * @returns {Promise} - Primary arm (labs, gameforge, corp, foundation, devlink) - */ -async function getUserArm(discordId, supabase) { - try { - const { data: link, error } = await supabase - .from("discord_links") - .select("primary_arm") - .eq("discord_id", discordId) - .maybeSingle(); - - if (error) { - console.error("Error fetching user arm:", error); - return null; - } - - return link?.primary_arm || null; - } catch (error) { - console.error("Error getting user arm:", error); - return null; - } -} - -/** - * Sync roles for a user across all guilds - * @param {Client} client - Discord client - * @param {string} discordId - Discord user ID - * @param {string} arm - Primary arm - * @param {object} supabase - Supabase client - */ -async function syncRolesAcrossGuilds(client, discordId, arm, supabase) { - try { - for (const [, guild] of client.guilds.cache) { - try { - const member = await guild.members.fetch(discordId); - if (member) { - await assignRoleByArm(guild, discordId, arm, supabase); - } - } catch (e) { - console.warn(`Could not sync roles in guild ${guild.id}: ${e.message}`); - } - } - } catch (error) { - console.error("Error syncing roles across guilds:", error); - } -} - -module.exports = { - assignRoleByArm, - getUserArm, - syncRolesAcrossGuilds, -};