From 77ae722b73a618e8e79eaf037c080ac655a01f8e Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Sun, 7 Dec 2025 23:22:44 +0000 Subject: [PATCH] Remove duplicate bot source code and consolidate configurations Deletes multiple outdated bot source directories and their associated configuration files from the `attached_assets` folder, streamlining the project to a single active bot instance located in the `aethex-bot` directory. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e72fc1b7-94bd-4d6c-801f-cbac2fae245c Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 14c21673-5f6f-4363-a27f-19e0b45ee206 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/e72fc1b7-94bd-4d6c-801f-cbac2fae245c/NXjYRWJ Replit-Helium-Checkpoint-Created: true --- attached_assets/bot1/discord-bot/.env.example | 23 - .../bot1/discord-bot/DEPLOYMENT_GUIDE.md | 211 --- attached_assets/bot1/discord-bot/Dockerfile | 22 - attached_assets/bot1/discord-bot/bot.js | 522 -------- .../bot1/discord-bot/commands/profile.js | 93 -- .../discord-bot/commands/refresh-roles.js | 72 - .../bot1/discord-bot/commands/set-realm.js | 139 -- .../bot1/discord-bot/commands/unlink.js | 75 -- .../bot1/discord-bot/commands/verify-role.js | 97 -- .../bot1/discord-bot/commands/verify.js | 84 -- .../bot1/discord-bot/discloud.config | 10 - .../events/messageCreate-announcements.js | 237 ---- .../bot1/discord-bot/events/messageCreate.js | 320 ----- .../bot1/discord-bot/package-lock.json | 1157 ----------------- attached_assets/bot1/discord-bot/package.json | 34 - .../discord-bot/scripts/register-commands.js | 110 -- .../bot1/discord-bot/utils/roleManager.js | 137 -- attached_assets/bot2/discord-bot/.env.example | 23 - .../bot2/discord-bot/DEPLOYMENT_GUIDE.md | 211 --- attached_assets/bot2/discord-bot/Dockerfile | 22 - attached_assets/bot2/discord-bot/bot.js | 803 ------------ .../bot2/discord-bot/commands/help.js | 55 - .../bot2/discord-bot/commands/leaderboard.js | 155 --- .../bot2/discord-bot/commands/post.js | 144 -- .../bot2/discord-bot/commands/profile.js | 93 -- .../discord-bot/commands/refresh-roles.js | 72 - .../bot2/discord-bot/commands/set-realm.js | 139 -- .../bot2/discord-bot/commands/stats.js | 140 -- .../bot2/discord-bot/commands/unlink.js | 75 -- .../bot2/discord-bot/commands/verify-role.js | 97 -- .../bot2/discord-bot/commands/verify.js | 85 -- .../bot2/discord-bot/discloud.config | 10 - .../bot2/discord-bot/events/messageCreate.js | 180 --- .../bot2/discord-bot/listeners/feedSync.js | 239 ---- .../bot2/discord-bot/package-lock.json | 1157 ----------------- attached_assets/bot2/discord-bot/package.json | 34 - .../discord-bot/scripts/register-commands.js | 110 -- .../bot2/discord-bot/utils/roleManager.js | 137 -- .../discord-bot/.env.example | 23 - .../discord-bot/DEPLOYMENT_GUIDE.md | 211 --- .../discord-bot-source/discord-bot/Dockerfile | 22 - .../discord-bot-source/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 -- .../discord-bot_(1)_1765057157676.zip | Bin 44006 -> 0 bytes attached_assets/discord-bot_1765057157677.zip | Bin 37140 -> 0 bytes attached_assets/discord-bot_1765148924805.zip | Bin 44005 -> 0 bytes 62 files changed, 11305 deletions(-) delete mode 100644 attached_assets/bot1/discord-bot/.env.example delete mode 100644 attached_assets/bot1/discord-bot/DEPLOYMENT_GUIDE.md delete mode 100644 attached_assets/bot1/discord-bot/Dockerfile delete mode 100644 attached_assets/bot1/discord-bot/bot.js delete mode 100644 attached_assets/bot1/discord-bot/commands/profile.js delete mode 100644 attached_assets/bot1/discord-bot/commands/refresh-roles.js delete mode 100644 attached_assets/bot1/discord-bot/commands/set-realm.js delete mode 100644 attached_assets/bot1/discord-bot/commands/unlink.js delete mode 100644 attached_assets/bot1/discord-bot/commands/verify-role.js delete mode 100644 attached_assets/bot1/discord-bot/commands/verify.js delete mode 100644 attached_assets/bot1/discord-bot/discloud.config delete mode 100644 attached_assets/bot1/discord-bot/events/messageCreate-announcements.js delete mode 100644 attached_assets/bot1/discord-bot/events/messageCreate.js delete mode 100644 attached_assets/bot1/discord-bot/package-lock.json delete mode 100644 attached_assets/bot1/discord-bot/package.json delete mode 100644 attached_assets/bot1/discord-bot/scripts/register-commands.js delete mode 100644 attached_assets/bot1/discord-bot/utils/roleManager.js delete mode 100644 attached_assets/bot2/discord-bot/.env.example delete mode 100644 attached_assets/bot2/discord-bot/DEPLOYMENT_GUIDE.md delete mode 100644 attached_assets/bot2/discord-bot/Dockerfile delete mode 100644 attached_assets/bot2/discord-bot/bot.js delete mode 100644 attached_assets/bot2/discord-bot/commands/help.js delete mode 100644 attached_assets/bot2/discord-bot/commands/leaderboard.js delete mode 100644 attached_assets/bot2/discord-bot/commands/post.js delete mode 100644 attached_assets/bot2/discord-bot/commands/profile.js delete mode 100644 attached_assets/bot2/discord-bot/commands/refresh-roles.js delete mode 100644 attached_assets/bot2/discord-bot/commands/set-realm.js delete mode 100644 attached_assets/bot2/discord-bot/commands/stats.js delete mode 100644 attached_assets/bot2/discord-bot/commands/unlink.js delete mode 100644 attached_assets/bot2/discord-bot/commands/verify-role.js delete mode 100644 attached_assets/bot2/discord-bot/commands/verify.js delete mode 100644 attached_assets/bot2/discord-bot/discloud.config delete mode 100644 attached_assets/bot2/discord-bot/events/messageCreate.js delete mode 100644 attached_assets/bot2/discord-bot/listeners/feedSync.js delete mode 100644 attached_assets/bot2/discord-bot/package-lock.json delete mode 100644 attached_assets/bot2/discord-bot/package.json delete mode 100644 attached_assets/bot2/discord-bot/scripts/register-commands.js delete mode 100644 attached_assets/bot2/discord-bot/utils/roleManager.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/.env.example delete mode 100644 attached_assets/discord-bot-source/discord-bot/DEPLOYMENT_GUIDE.md delete mode 100644 attached_assets/discord-bot-source/discord-bot/Dockerfile delete mode 100644 attached_assets/discord-bot-source/discord-bot/bot.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/commands/help.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/commands/leaderboard.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/commands/post.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/commands/profile.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/commands/refresh-roles.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/commands/set-realm.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/commands/stats.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/commands/unlink.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/commands/verify-role.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/commands/verify.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/discloud.config delete mode 100644 attached_assets/discord-bot-source/discord-bot/events/messageCreate.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/listeners/feedSync.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/package-lock.json delete mode 100644 attached_assets/discord-bot-source/discord-bot/package.json delete mode 100644 attached_assets/discord-bot-source/discord-bot/scripts/register-commands.js delete mode 100644 attached_assets/discord-bot-source/discord-bot/utils/roleManager.js delete mode 100644 attached_assets/discord-bot_(1)_1765057157676.zip delete mode 100644 attached_assets/discord-bot_1765057157677.zip delete mode 100644 attached_assets/discord-bot_1765148924805.zip diff --git a/attached_assets/bot1/discord-bot/.env.example b/attached_assets/bot1/discord-bot/.env.example deleted file mode 100644 index a4ddc91..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/DEPLOYMENT_GUIDE.md b/attached_assets/bot1/discord-bot/DEPLOYMENT_GUIDE.md deleted file mode 100644 index c90a3aa..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/Dockerfile b/attached_assets/bot1/discord-bot/Dockerfile deleted file mode 100644 index 279b52f..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/bot.js b/attached_assets/bot1/discord-bot/bot.js deleted file mode 100644 index e015b6d..0000000 --- a/attached_assets/bot1/discord-bot/bot.js +++ /dev/null @@ -1,522 +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(); - -// 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}`); - } - } -} - -// Bot ready event -client.once("ready", () => { - console.log(`โœ… Bot logged in as ${client.user.tag}`); - console.log(`๐Ÿ“ก Listening in ${client.guilds.cache.size} server(s)`); - - // Set bot status - client.user.setActivity("/verify to link your AeThex account", { - type: "LISTENING", - }); -}); - -// 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", - }, -]; - -// 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 || 8044; -http - .createServer((req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - 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; - } - - if (req.url === "/register-commands") { - if (req.method === "GET") { - // 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 provided - const authHeader = req.headers.authorization; - const adminToken = process.env.DISCORD_ADMIN_REGISTER_TOKEN; - - if (adminToken && authHeader !== `Bearer ${adminToken}`) { - res.writeHead(401); - res.end(JSON.stringify({ error: "Unauthorized" })); - 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", - }); -}); - -// 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/attached_assets/bot1/discord-bot/commands/profile.js b/attached_assets/bot1/discord-bot/commands/profile.js deleted file mode 100644 index 035f251..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/commands/refresh-roles.js b/attached_assets/bot1/discord-bot/commands/refresh-roles.js deleted file mode 100644 index 459bd79..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/commands/set-realm.js b/attached_assets/bot1/discord-bot/commands/set-realm.js deleted file mode 100644 index c1af120..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/commands/unlink.js b/attached_assets/bot1/discord-bot/commands/unlink.js deleted file mode 100644 index ac06d2a..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/commands/verify-role.js b/attached_assets/bot1/discord-bot/commands/verify-role.js deleted file mode 100644 index 1b7e6b9..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/commands/verify.js b/attached_assets/bot1/discord-bot/commands/verify.js deleted file mode 100644 index 4e5e6c4..0000000 --- a/attached_assets/bot1/discord-bot/commands/verify.js +++ /dev/null @@ -1,84 +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 - await supabase.from("discord_verifications").insert({ - discord_id: interaction.user.id, - verification_code: verificationCode, - 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/attached_assets/bot1/discord-bot/discloud.config b/attached_assets/bot1/discord-bot/discloud.config deleted file mode 100644 index fe114e6..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/events/messageCreate-announcements.js b/attached_assets/bot1/discord-bot/events/messageCreate-announcements.js deleted file mode 100644 index 0193468..0000000 --- a/attached_assets/bot1/discord-bot/events/messageCreate-announcements.js +++ /dev/null @@ -1,237 +0,0 @@ -const { createClient } = require("@supabase/supabase-js"); - -// Initialize Supabase -const supabase = createClient( - process.env.SUPABASE_URL, - process.env.SUPABASE_SERVICE_ROLE, -); - -const API_BASE = process.env.VITE_API_BASE || "https://api.aethex.dev"; - -// Channel IDs for syncing -const ANNOUNCEMENT_CHANNELS = process.env.DISCORD_ANNOUNCEMENT_CHANNELS - ? process.env.DISCORD_ANNOUNCEMENT_CHANNELS.split(",") - : ["1435667453244866702"]; // Default to feed channel if env not set - -// Arm affiliation mapping based on guild/channel name -const getArmAffiliation = (message) => { - const guildName = message.guild?.name?.toLowerCase() || ""; - const channelName = message.channel?.name?.toLowerCase() || ""; - - const searchString = `${guildName} ${channelName}`.toLowerCase(); - - 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"; // Default -}; - -module.exports = { - name: "messageCreate", - async execute(message, client, supabase) { - try { - // Ignore bot messages - if (message.author.bot) return; - - // Only process messages in announcement channels - if (!ANNOUNCEMENT_CHANNELS.includes(message.channelId)) { - return; - } - - // Skip empty messages - if (!message.content && message.attachments.size === 0) { - return; - } - - console.log( - `[Announcements Sync] Processing message from ${message.author.tag} in #${message.channel.name}`, - ); - - // Get or create system admin user for announcements - let adminUser = await supabase - .from("user_profiles") - .select("id") - .eq("username", "aethex-announcements") - .single(); - - let authorId = adminUser.data?.id; - - if (!authorId) { - // Create a system user if it doesn't exist - const { data: newUser } = await supabase - .from("user_profiles") - .insert({ - username: "aethex-announcements", - full_name: "AeThex Announcements", - avatar_url: "https://aethex.dev/logo.png", - }) - .select("id"); - - authorId = newUser?.[0]?.id; - } - - if (!authorId) { - console.error("[Announcements Sync] Could not get author ID"); - return; - } - - // Prepare message content - let content = message.content || "Announcement from Discord"; - - // Handle embeds (convert to text) - if (message.embeds.length > 0) { - const embed = message.embeds[0]; - if (embed.title) content = embed.title + "\n\n" + content; - if (embed.description) content += "\n\n" + embed.description; - } - - // Handle attachments (images, videos) - let mediaUrl = null; - let mediaType = "none"; - - if (message.attachments.size > 0) { - const attachment = message.attachments.first(); - if (attachment) { - mediaUrl = attachment.url; - - const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp"]; - const videoExtensions = [".mp4", ".webm", ".mov", ".avi"]; - - const attachmentLower = attachment.name.toLowerCase(); - - if (imageExtensions.some((ext) => attachmentLower.endsWith(ext))) { - mediaType = "image"; - } else if ( - videoExtensions.some((ext) => attachmentLower.endsWith(ext)) - ) { - mediaType = "video"; - } - } - } - - // Determine arm affiliation - const armAffiliation = getArmAffiliation(message); - - // Prepare post content JSON - const postContent = JSON.stringify({ - text: content, - mediaUrl: mediaUrl, - mediaType: mediaType, - source: "discord", - discord_message_id: message.id, - discord_author: message.author.tag, - }); - - // Create post in AeThex - const { data: createdPost, error: insertError } = await supabase - .from("community_posts") - .insert({ - title: content.substring(0, 100) || "Discord Announcement", - content: postContent, - arm_affiliation: armAffiliation, - author_id: authorId, - tags: ["discord", "announcement"], - category: "announcement", - is_published: true, - likes_count: 0, - comments_count: 0, - }) - .select( - `id, title, content, arm_affiliation, author_id, created_at, likes_count, comments_count, - user_profiles!community_posts_author_id_fkey (id, username, full_name, avatar_url)`, - ); - - if (insertError) { - console.error( - "[Announcements Sync] Failed to create post:", - insertError, - ); - try { - await message.react("โŒ"); - } catch (reactionError) { - console.warn( - "[Announcements Sync] Could not add reaction:", - reactionError, - ); - } - return; - } - - // Sync to Discord feed webhook if configured - if (process.env.DISCORD_FEED_WEBHOOK_URL && createdPost?.[0]) { - try { - const post = createdPost[0]; - const armColors = { - labs: 0xfbbf24, - gameforge: 0x22c55e, - corp: 0x3b82f6, - foundation: 0xef4444, - devlink: 0x06b6d4, - nexus: 0xa855f7, - staff: 0x6366f1, - }; - - const embed = { - title: post.title, - description: content.substring(0, 1024), - color: armColors[armAffiliation] || 0x8b5cf6, - author: { - name: `${message.author.username} (${armAffiliation.toUpperCase()})`, - icon_url: message.author.displayAvatarURL(), - }, - footer: { - text: "Synced from Discord", - }, - timestamp: new Date().toISOString(), - }; - - await fetch(process.env.DISCORD_FEED_WEBHOOK_URL, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - username: "AeThex Community Feed", - embeds: [embed], - }), - }); - } catch (webhookError) { - console.warn( - "[Announcements Sync] Failed to sync to webhook:", - webhookError, - ); - } - } - - console.log( - `[Announcements Sync] โœ… Posted announcement from Discord to AeThex (${armAffiliation})`, - ); - - // React with success emoji - try { - await message.react("โœ…"); - } catch (reactionError) { - console.warn( - "[Announcements Sync] Could not add success reaction:", - reactionError, - ); - } - } catch (error) { - console.error("[Announcements Sync] Unexpected error:", error); - - try { - await message.react("โš ๏ธ"); - } catch (reactionError) { - console.warn( - "[Announcements Sync] Could not add warning reaction:", - reactionError, - ); - } - } - }, -}; diff --git a/attached_assets/bot1/discord-bot/events/messageCreate.js b/attached_assets/bot1/discord-bot/events/messageCreate.js deleted file mode 100644 index 344c3b2..0000000 --- a/attached_assets/bot1/discord-bot/events/messageCreate.js +++ /dev/null @@ -1,320 +0,0 @@ -const { createClient } = require("@supabase/supabase-js"); - -// Initialize Supabase -const supabase = createClient( - process.env.SUPABASE_URL, - process.env.SUPABASE_SERVICE_ROLE, -); - -const FEED_CHANNEL_ID = process.env.DISCORD_FEED_CHANNEL_ID; -const FEED_GUILD_ID = process.env.DISCORD_FEED_GUILD_ID; -const API_BASE = process.env.VITE_API_BASE || "https://api.aethex.dev"; - -// Announcement channels to sync to feed -const ANNOUNCEMENT_CHANNELS = process.env.DISCORD_ANNOUNCEMENT_CHANNELS - ? process.env.DISCORD_ANNOUNCEMENT_CHANNELS.split(",").map((id) => id.trim()) - : []; - -// Helper: Get arm affiliation from message context -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"; -} - -// Handle announcements from designated channels -async function handleAnnouncementSync(message) { - try { - console.log( - `[Announcements] Processing from ${message.author.tag} in #${message.channel.name}`, - ); - - // Get or create system announcement user - let { data: adminUser } = await supabase - .from("user_profiles") - .select("id") - .eq("username", "aethex-announcements") - .single(); - - let authorId = adminUser?.id; - - if (!authorId) { - const { data: newUser } = await supabase - .from("user_profiles") - .insert({ - username: "aethex-announcements", - full_name: "AeThex Announcements", - avatar_url: "https://aethex.dev/logo.png", - }) - .select("id"); - - authorId = newUser?.[0]?.id; - } - - if (!authorId) { - console.error("[Announcements] Could not get author ID"); - await message.react("โŒ"); - return; - } - - // Prepare content - let content = message.content || "Announcement from Discord"; - - // Handle embeds - if (message.embeds.length > 0) { - const embed = message.embeds[0]; - if (embed.title) content = `**${embed.title}**\n\n${content}`; - if (embed.description) content += `\n\n${embed.description}`; - } - - // Handle attachments - 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"; - } - } - } - - // Determine arm - const armAffiliation = getArmAffiliation(message); - - // Prepare post content - const postContent = JSON.stringify({ - text: content, - mediaUrl: mediaUrl, - mediaType: mediaType, - source: "discord", - discord_message_id: message.id, - discord_channel: message.channel.name, - }); - - // Create post - const { data: createdPost, error: insertError } = await supabase - .from("community_posts") - .insert({ - title: content.substring(0, 100) || "Discord Announcement", - content: postContent, - arm_affiliation: armAffiliation, - author_id: authorId, - tags: ["discord", "announcement"], - category: "announcement", - is_published: true, - likes_count: 0, - comments_count: 0, - }) - .select( - `id, title, content, arm_affiliation, author_id, created_at, likes_count, comments_count, - user_profiles!community_posts_author_id_fkey (id, username, full_name, avatar_url)`, - ); - - if (insertError) { - console.error("[Announcements] Post creation failed:", insertError); - await message.react("โŒ"); - return; - } - - console.log(`[Announcements] โœ… Synced to AeThex (${armAffiliation} arm)`); - - await message.react("โœ…"); - } catch (error) { - console.error("[Announcements] Error:", error); - try { - await message.react("โš ๏ธ"); - } catch (e) { - console.warn("[Announcements] Could not react:", e); - } - } -} - -module.exports = { - name: "messageCreate", - async execute(message, client) { - // Ignore bot messages and empty messages - if (message.author.bot) return; - if (!message.content && message.attachments.size === 0) return; - - // Check if this is an announcement to sync - if ( - ANNOUNCEMENT_CHANNELS.length > 0 && - ANNOUNCEMENT_CHANNELS.includes(message.channelId) - ) { - return handleAnnouncementSync(message); - } - - // Check if this is in the feed channel (for user-generated posts) - if (FEED_CHANNEL_ID && message.channelId !== FEED_CHANNEL_ID) { - return; - } - - if (FEED_GUILD_ID && message.guildId !== FEED_GUILD_ID) { - return; - } - - try { - // Get user's linked AeThex account - const { data: linkedAccount, error } = await supabase - .from("discord_links") - .select("user_id") - .eq("discord_user_id", message.author.id) - .single(); - - if (error || !linkedAccount) { - try { - await message.author.send( - "To have your message posted to AeThex, please link your Discord account! Use `/verify` command.", - ); - } catch (dmError) { - console.warn("[Feed Sync] Could not send DM to user:", dmError); - } - return; - } - - // Get user profile - const { data: userProfile, error: profileError } = await supabase - .from("user_profiles") - .select("id, username, full_name, avatar_url") - .eq("id", linkedAccount.user_id) - .single(); - - if (profileError || !userProfile) { - console.error( - "[Feed Sync] Could not fetch user profile:", - profileError, - ); - return; - } - - // Prepare content - 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"; - } - } - } - - // Determine arm affiliation - let armAffiliation = "labs"; - const guild = message.guild; - if (guild) { - const guildNameLower = guild.name.toLowerCase(); - if (guildNameLower.includes("gameforge")) armAffiliation = "gameforge"; - else if (guildNameLower.includes("corp")) armAffiliation = "corp"; - else if (guildNameLower.includes("foundation")) - armAffiliation = "foundation"; - else if (guildNameLower.includes("devlink")) armAffiliation = "devlink"; - else if (guildNameLower.includes("nexus")) armAffiliation = "nexus"; - else if (guildNameLower.includes("staff")) armAffiliation = "staff"; - } - - // Prepare post content - const postContent = JSON.stringify({ - text: content, - mediaUrl: mediaUrl, - mediaType: mediaType, - }); - - // Create post - const { data: createdPost, error: insertError } = await supabase - .from("community_posts") - .insert({ - title: content.substring(0, 100) || "Discord Shared Message", - content: postContent, - arm_affiliation: armAffiliation, - author_id: userProfile.id, - tags: ["discord"], - category: null, - is_published: true, - likes_count: 0, - comments_count: 0, - }) - .select( - `id, title, content, arm_affiliation, author_id, created_at, updated_at, likes_count, comments_count, - user_profiles!community_posts_author_id_fkey (id, username, full_name, avatar_url)`, - ); - - if (insertError) { - console.error("[Feed Sync] Failed to create post:", insertError); - try { - await message.react("โŒ"); - } catch (e) { - console.warn("[Feed Sync] Could not add reaction:", e); - } - return; - } - - console.log(`[Feed Sync] โœ… Posted from ${message.author.tag} to AeThex`); - - try { - await message.react("โœ…"); - } catch (reactionError) { - console.warn( - "[Feed Sync] Could not add success reaction:", - reactionError, - ); - } - - try { - await message.author.send( - `โœ… Your message was posted to AeThex! Check it out at https://aethex.dev/feed`, - ); - } catch (dmError) { - console.warn("[Feed Sync] Could not send confirmation DM:", dmError); - } - } catch (error) { - console.error("[Feed Sync] Unexpected error:", error); - - try { - await message.react("โš ๏ธ"); - } catch (e) { - console.warn("[Feed Sync] Could not add warning reaction:", e); - } - } - }, -}; diff --git a/attached_assets/bot1/discord-bot/package-lock.json b/attached_assets/bot1/discord-bot/package-lock.json deleted file mode 100644 index 897f4a0..0000000 --- a/attached_assets/bot1/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": ">=18.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/attached_assets/bot1/discord-bot/package.json b/attached_assets/bot1/discord-bot/package.json deleted file mode 100644 index b65f0ac..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/scripts/register-commands.js b/attached_assets/bot1/discord-bot/scripts/register-commands.js deleted file mode 100644 index 27ffb8d..0000000 --- a/attached_assets/bot1/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/attached_assets/bot1/discord-bot/utils/roleManager.js b/attached_assets/bot1/discord-bot/utils/roleManager.js deleted file mode 100644 index 27be439..0000000 --- a/attached_assets/bot1/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, -}; diff --git a/attached_assets/bot2/discord-bot/.env.example b/attached_assets/bot2/discord-bot/.env.example deleted file mode 100644 index a4ddc91..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/DEPLOYMENT_GUIDE.md b/attached_assets/bot2/discord-bot/DEPLOYMENT_GUIDE.md deleted file mode 100644 index c90a3aa..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/Dockerfile b/attached_assets/bot2/discord-bot/Dockerfile deleted file mode 100644 index 279b52f..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/bot.js b/attached_assets/bot2/discord-bot/bot.js deleted file mode 100644 index b7b2850..0000000 --- a/attached_assets/bot2/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 || 8044; -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/attached_assets/bot2/discord-bot/commands/help.js b/attached_assets/bot2/discord-bot/commands/help.js deleted file mode 100644 index 324b1dd..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/commands/leaderboard.js b/attached_assets/bot2/discord-bot/commands/leaderboard.js deleted file mode 100644 index cbc5b01..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/commands/post.js b/attached_assets/bot2/discord-bot/commands/post.js deleted file mode 100644 index 61057e6..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/commands/profile.js b/attached_assets/bot2/discord-bot/commands/profile.js deleted file mode 100644 index 035f251..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/commands/refresh-roles.js b/attached_assets/bot2/discord-bot/commands/refresh-roles.js deleted file mode 100644 index 459bd79..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/commands/set-realm.js b/attached_assets/bot2/discord-bot/commands/set-realm.js deleted file mode 100644 index c1af120..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/commands/stats.js b/attached_assets/bot2/discord-bot/commands/stats.js deleted file mode 100644 index fe9814b..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/commands/unlink.js b/attached_assets/bot2/discord-bot/commands/unlink.js deleted file mode 100644 index ac06d2a..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/commands/verify-role.js b/attached_assets/bot2/discord-bot/commands/verify-role.js deleted file mode 100644 index 1b7e6b9..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/commands/verify.js b/attached_assets/bot2/discord-bot/commands/verify.js deleted file mode 100644 index d9f30e7..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/discloud.config b/attached_assets/bot2/discord-bot/discloud.config deleted file mode 100644 index fe114e6..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/events/messageCreate.js b/attached_assets/bot2/discord-bot/events/messageCreate.js deleted file mode 100644 index 75abc33..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/listeners/feedSync.js b/attached_assets/bot2/discord-bot/listeners/feedSync.js deleted file mode 100644 index b8168c4..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/package-lock.json b/attached_assets/bot2/discord-bot/package-lock.json deleted file mode 100644 index 15b33b8..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/package.json b/attached_assets/bot2/discord-bot/package.json deleted file mode 100644 index b65f0ac..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/scripts/register-commands.js b/attached_assets/bot2/discord-bot/scripts/register-commands.js deleted file mode 100644 index 27ffb8d..0000000 --- a/attached_assets/bot2/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/attached_assets/bot2/discord-bot/utils/roleManager.js b/attached_assets/bot2/discord-bot/utils/roleManager.js deleted file mode 100644 index 27be439..0000000 --- a/attached_assets/bot2/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, -}; diff --git a/attached_assets/discord-bot-source/discord-bot/.env.example b/attached_assets/discord-bot-source/discord-bot/.env.example deleted file mode 100644 index a4ddc91..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/DEPLOYMENT_GUIDE.md b/attached_assets/discord-bot-source/discord-bot/DEPLOYMENT_GUIDE.md deleted file mode 100644 index c90a3aa..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/Dockerfile b/attached_assets/discord-bot-source/discord-bot/Dockerfile deleted file mode 100644 index 279b52f..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/bot.js b/attached_assets/discord-bot-source/discord-bot/bot.js deleted file mode 100644 index 401b132..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/commands/help.js b/attached_assets/discord-bot-source/discord-bot/commands/help.js deleted file mode 100644 index 324b1dd..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/commands/leaderboard.js b/attached_assets/discord-bot-source/discord-bot/commands/leaderboard.js deleted file mode 100644 index cbc5b01..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/commands/post.js b/attached_assets/discord-bot-source/discord-bot/commands/post.js deleted file mode 100644 index 61057e6..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/commands/profile.js b/attached_assets/discord-bot-source/discord-bot/commands/profile.js deleted file mode 100644 index 035f251..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/commands/refresh-roles.js b/attached_assets/discord-bot-source/discord-bot/commands/refresh-roles.js deleted file mode 100644 index 459bd79..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/commands/set-realm.js b/attached_assets/discord-bot-source/discord-bot/commands/set-realm.js deleted file mode 100644 index c1af120..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/commands/stats.js b/attached_assets/discord-bot-source/discord-bot/commands/stats.js deleted file mode 100644 index fe9814b..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/commands/unlink.js b/attached_assets/discord-bot-source/discord-bot/commands/unlink.js deleted file mode 100644 index ac06d2a..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/commands/verify-role.js b/attached_assets/discord-bot-source/discord-bot/commands/verify-role.js deleted file mode 100644 index 1b7e6b9..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/commands/verify.js b/attached_assets/discord-bot-source/discord-bot/commands/verify.js deleted file mode 100644 index d9f30e7..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/discloud.config b/attached_assets/discord-bot-source/discord-bot/discloud.config deleted file mode 100644 index fe114e6..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/events/messageCreate.js b/attached_assets/discord-bot-source/discord-bot/events/messageCreate.js deleted file mode 100644 index 75abc33..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/listeners/feedSync.js b/attached_assets/discord-bot-source/discord-bot/listeners/feedSync.js deleted file mode 100644 index b8168c4..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/package-lock.json b/attached_assets/discord-bot-source/discord-bot/package-lock.json deleted file mode 100644 index 15b33b8..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/package.json b/attached_assets/discord-bot-source/discord-bot/package.json deleted file mode 100644 index b65f0ac..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/scripts/register-commands.js b/attached_assets/discord-bot-source/discord-bot/scripts/register-commands.js deleted file mode 100644 index 27ffb8d..0000000 --- a/attached_assets/discord-bot-source/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/attached_assets/discord-bot-source/discord-bot/utils/roleManager.js b/attached_assets/discord-bot-source/discord-bot/utils/roleManager.js deleted file mode 100644 index 27be439..0000000 --- a/attached_assets/discord-bot-source/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, -}; diff --git a/attached_assets/discord-bot_(1)_1765057157676.zip b/attached_assets/discord-bot_(1)_1765057157676.zip deleted file mode 100644 index 91d7d7e84140870cd8148a19bb9e60bacc58619d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44006 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 diff --git a/attached_assets/discord-bot_1765057157677.zip b/attached_assets/discord-bot_1765057157677.zip deleted file mode 100644 index ce8e0811fd1832bd770500f02c080619893af5c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37140 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