diff --git a/.replit b/.replit index 65cb3083..2454f8ad 100644 --- a/.replit +++ b/.replit @@ -44,7 +44,7 @@ localPort = 8080 externalPort = 8080 [[ports]] -localPort = 33165 +localPort = 37955 externalPort = 3002 [[ports]] diff --git a/api/discord/admin-register-commands.ts b/api/discord/admin-register-commands.ts deleted file mode 100644 index 3336adea..00000000 --- a/api/discord/admin-register-commands.ts +++ /dev/null @@ -1,205 +0,0 @@ -import type { VercelRequest, VercelResponse } from "@vercel/node"; - -interface CommandData { - name: string; - description: string; - options?: any[]; -} - -// Define all commands that should be registered -const COMMANDS: CommandData[] = [ - { - 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", - }, -]; - -const DISCORD_API_VERSION = "10"; -const DISCORD_API_URL = `https://discord.com/api/v${DISCORD_API_VERSION}`; - -export default async function handler(req: VercelRequest, res: VercelResponse) { - try { - // Allow both GET and POST - if (req.method !== "POST" && req.method !== "GET") { - res.setHeader("Allow", "GET, POST"); - return res.status(405).json({ error: "Method not allowed" }); - } - - // Basic security: Check if requester has admin token - const authHeader = req.headers.authorization; - const queryToken = req.query.token; - const adminToken = process.env.DISCORD_ADMIN_REGISTER_TOKEN; - - const providedToken = authHeader - ? authHeader.replace("Bearer ", "") - : queryToken; - - if (!adminToken || providedToken !== adminToken) { - return res.status(401).json({ error: "Unauthorized - Invalid token" }); - } - - // Validate environment variables - const requiredVars = ["DISCORD_BOT_TOKEN", "DISCORD_CLIENT_ID"]; - const missingVars = requiredVars.filter((v) => !process.env[v]); - - if (missingVars.length > 0) { - return res.status(500).json({ - error: "Missing environment variables", - missing: missingVars, - }); - } - - const botToken = process.env.DISCORD_BOT_TOKEN!; - const clientId = process.env.DISCORD_CLIENT_ID!; - const authorizationHeader = `Bot ${botToken}`; - - console.log(`šŸ“ Registering ${COMMANDS.length} Discord slash commands...`); - - // Try bulk update first - try { - const bulkResponse = await fetch( - `${DISCORD_API_URL}/applications/${clientId}/commands`, - { - method: "PUT", - headers: { - Authorization: authorizationHeader, - "Content-Type": "application/json", - }, - body: JSON.stringify(COMMANDS), - }, - ); - - if (bulkResponse.ok) { - const data = (await bulkResponse.json()) as any[]; - console.log(`āœ… Successfully registered ${data.length} slash commands`); - - return res.status(200).json({ - success: true, - message: `Registered ${data.length} slash commands`, - commands: (data as any[]).map((cmd: any) => cmd.name), - }); - } - - // If bulk update failed, try individual registration - const errorData = (await bulkResponse.json()) as any; - const errorCode = errorData?.code; - - if (errorCode === 50240) { - // Error 50240: Entry Point conflict (Discord Activity enabled) - console.warn( - "āš ļø Error 50240: Entry Point detected. Registering individually...", - ); - - const results = []; - let successCount = 0; - let skipCount = 0; - - for (const command of COMMANDS) { - try { - const postResponse = await fetch( - `${DISCORD_API_URL}/applications/${clientId}/commands`, - { - method: "POST", - headers: { - Authorization: authorizationHeader, - "Content-Type": "application/json", - }, - body: JSON.stringify(command), - }, - ); - - if (postResponse.ok) { - const posted = await postResponse.json(); - results.push({ - name: command.name, - status: "registered", - id: posted.id, - }); - successCount++; - } else if (postResponse.status === 400) { - // Error 50045: Command already exists - results.push({ - name: command.name, - status: "already_exists", - }); - skipCount++; - } else { - const errData = await postResponse.json(); - results.push({ - name: command.name, - status: "error", - error: errData.message || `HTTP ${postResponse.status}`, - }); - } - } catch (postError: any) { - results.push({ - name: command.name, - status: "error", - error: postError.message, - }); - } - } - - console.log( - `āœ… Registration complete: ${successCount} new, ${skipCount} already existed`, - ); - - return res.status(200).json({ - success: true, - message: `Registered ${successCount} new commands (${skipCount} already existed)`, - results, - note: "Entry Point command is managed by Discord (Activities enabled)", - }); - } - - throw new Error( - `Discord API error: ${errorData?.message || bulkResponse.statusText}`, - ); - } catch (error: any) { - console.error("āŒ Failed to register commands:", error); - - return res.status(500).json({ - success: false, - error: error?.message || "Failed to register commands", - }); - } - } catch (error: any) { - console.error("āŒ Unexpected error:", error); - - return res.status(500).json({ - success: false, - error: error?.message || "Internal server error", - }); - } -} diff --git a/api/discord/feed-sync.ts b/api/discord/feed-sync.ts deleted file mode 100644 index 3ceccfbe..00000000 --- a/api/discord/feed-sync.ts +++ /dev/null @@ -1,134 +0,0 @@ -export const config = { - runtime: "nodejs", -}; - -const webhookUrl = process.env.DISCORD_FEED_WEBHOOK_URL; - -interface FeedPost { - id: string; - title: string; - content: string; - author_name: string; - author_avatar?: string | null; - arm_affiliation: string; - likes_count: number; - comments_count: number; - created_at: string; -} - -export default async function handler(req: any, res: any) { - // Only accept POST requests - if (req.method !== "POST") { - return res.status(405).json({ error: "Method not allowed" }); - } - - try { - // Validate webhook is configured - if (!webhookUrl) { - console.warn( - "[Discord Feed Sync] No webhook URL configured. Skipping Discord post.", - ); - return res.status(200).json({ - success: true, - message: "Discord webhook not configured, post skipped", - }); - } - - const post: FeedPost = req.body; - - // Validate required fields - if ( - !post.id || - !post.title || - !post.content || - !post.author_name || - !post.arm_affiliation - ) { - return res.status(400).json({ - error: - "Missing required fields: id, title, content, author_name, arm_affiliation", - }); - } - - // Truncate content if too long (Discord has limits) - const description = - post.content.length > 1024 - ? post.content.substring(0, 1021) + "..." - : post.content; - - // Build Discord embed - const armColors: Record = { - labs: 0xfbbf24, // yellow - gameforge: 0x22c55e, // green - corp: 0x3b82f6, // blue - foundation: 0xef4444, // red - devlink: 0x06b6d4, // cyan - nexus: 0xa855f7, // purple - staff: 0x6366f1, // indigo - }; - - const embed = { - title: post.title, - description: description, - color: armColors[post.arm_affiliation] || 0x8b5cf6, - author: { - name: post.author_name, - icon_url: post.author_avatar || undefined, - }, - fields: [ - { - name: "Arm", - value: - post.arm_affiliation.charAt(0).toUpperCase() + - post.arm_affiliation.slice(1), - inline: true, - }, - { - name: "Engagement", - value: `šŸ‘ ${post.likes_count} • šŸ’¬ ${post.comments_count}`, - inline: true, - }, - ], - footer: { - text: "AeThex Community Feed", - }, - timestamp: post.created_at, - }; - - // Send to Discord webhook - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - username: "AeThex Community Feed", - avatar_url: "https://aethex.dev/logo.png", // Update with your logo URL - embeds: [embed], - }), - }); - - if (!response.ok) { - const errorText = await response.text(); - console.error( - "[Discord Feed Sync] Webhook failed:", - response.status, - errorText, - ); - return res.status(500).json({ - success: false, - error: "Failed to post to Discord", - }); - } - - return res.status(200).json({ - success: true, - message: "Post sent to Discord feed", - }); - } catch (error: any) { - console.error("[Discord Feed Sync] Error:", error); - return res.status(500).json({ - error: error.message || "Internal server error", - }); - } -} diff --git a/api/discord/interactions.ts b/api/discord/interactions.ts deleted file mode 100644 index a7b31da5..00000000 --- a/api/discord/interactions.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { VercelRequest, VercelResponse } from "@vercel/node"; -import { webcrypto } from "crypto"; - -const crypto = webcrypto as any; - -export default async function handler(req: VercelRequest, res: VercelResponse) { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); - res.setHeader( - "Access-Control-Allow-Headers", - "Content-Type, x-signature-ed25519, x-signature-timestamp", - ); - - if (req.method === "OPTIONS") { - return res.status(200).end(); - } - - if (req.method !== "POST") { - return res.status(405).json({ error: "Method not allowed" }); - } - - try { - const signature = req.headers["x-signature-ed25519"] as string; - const timestamp = req.headers["x-signature-timestamp"] as string; - const rawPublicKey = process.env.DISCORD_PUBLIC_KEY; - - if (!signature || !timestamp || !rawPublicKey) { - console.error("[Discord] Missing required headers or public key", { - hasSignature: !!signature, - hasTimestamp: !!timestamp, - hasPublicKey: !!rawPublicKey, - }); - return res - .status(401) - .json({ error: "Missing required headers or public key" }); - } - - // Reconstruct the raw body - let rawBody: string; - if (typeof req.body === "string") { - rawBody = req.body; - } else if (req.body instanceof Buffer) { - rawBody = req.body.toString("utf8"); - } else { - rawBody = JSON.stringify(req.body); - } - - // Create the message that was signed - const message = `${timestamp}${rawBody}`; - - // Convert Discord's public key (hex string) to buffer - const publicKeyBuffer = Buffer.from(rawPublicKey, "hex"); - const signatureBuffer = Buffer.from(signature, "hex"); - const messageBuffer = Buffer.from(message); - - // Use WebCrypto API for Ed25519 verification (works in Vercel) - try { - const publicKey = await crypto.subtle.importKey( - "raw", - publicKeyBuffer, - { - name: "Ed25519", - namedCurve: "Ed25519", - }, - false, - ["verify"], - ); - - const isValid = await crypto.subtle.verify( - "Ed25519", - publicKey, - signatureBuffer, - messageBuffer, - ); - - if (!isValid) { - console.error("[Discord] Signature verification failed"); - return res.status(401).json({ error: "Invalid signature" }); - } - } catch (err: any) { - console.error("[Discord] Verification error:", err?.message); - return res.status(401).json({ error: "Signature verification failed" }); - } - - console.log("[Discord] Signature verified successfully"); - - // Parse and handle the interaction - const interaction = JSON.parse(rawBody); - console.log("[Discord] Interaction type:", interaction.type); - - // Response to PING with type 1 - if (interaction.type === 1) { - console.log("[Discord] PING received - responding with type 1"); - return res.status(200).json({ type: 1 }); - } - - // Handle APPLICATION_COMMAND (slash commands) - if (interaction.type === 2) { - const commandName = interaction.data.name; - console.log("[Discord] Slash command received:", commandName); - - if (commandName === "creators") { - const arm = interaction.data.options?.[0]?.value; - const armFilter = arm ? ` (${arm})` : " (all arms)"; - return res.status(200).json({ - type: 4, - data: { - content: `šŸ” Browse AeThex Creators${armFilter}\n\nšŸ‘‰ [Open Creator Directory](https://aethex.dev/creators${arm ? `?arm=${arm}` : ""})`, - flags: 0, - }, - }); - } - - if (commandName === "opportunities") { - const arm = interaction.data.options?.[0]?.value; - const armFilter = arm ? ` (${arm})` : " (all arms)"; - return res.status(200).json({ - type: 4, - data: { - content: `šŸ’¼ Find Opportunities on Nexus${armFilter}\n\nšŸ‘‰ [Browse Opportunities](https://aethex.dev/opportunities${arm ? `?arm=${arm}` : ""})`, - flags: 0, - }, - }); - } - - if (commandName === "nexus") { - return res.status(200).json({ - type: 4, - data: { - content: `✨ **AeThex Nexus** - The Talent Marketplace\n\nšŸ”— [Open Nexus](https://aethex.dev/nexus)\n\n**Quick Links:**\n• šŸ” [Browse Creators](https://aethex.dev/creators)\n• šŸ’¼ [Find Opportunities](https://aethex.dev/opportunities)\n• šŸ“Š [View Metrics](https://aethex.dev/admin)`, - flags: 0, - }, - }); - } - - if (commandName === "verify") { - try { - const { createClient } = await import("@supabase/supabase-js"); - const supabase = createClient( - process.env.SUPABASE_URL || process.env.VITE_SUPABASE_URL || "", - process.env.SUPABASE_SERVICE_ROLE || "", - ); - - const discordId = - interaction.user?.id || interaction.member?.user?.id; - - if (!discordId) { - return res.status(200).json({ - type: 4, - data: { - content: "āŒ Could not get your Discord ID. Please try again.", - flags: 64, - }, - }); - } - - // Generate verification code (random 6 characters) - const verificationCode = Math.random() - .toString(36) - .substring(2, 8) - .toUpperCase(); - const expiresAt = new Date(Date.now() + 15 * 60 * 1000).toISOString(); // 15 min - - // Store verification code - const { error } = await supabase - .from("discord_verifications") - .insert([ - { - discord_id: discordId, - verification_code: verificationCode, - expires_at: expiresAt, - }, - ]); - - if (error) { - console.error("Error storing verification code:", error); - return res.status(200).json({ - type: 4, - data: { - content: - "āŒ Error generating verification code. Please try again.", - flags: 64, - }, - }); - } - - const verifyUrl = `https://aethex.dev/discord-verify?code=${verificationCode}`; - - return res.status(200).json({ - type: 4, - data: { - content: `āœ… **Verification Code: \`${verificationCode}\`**\n\nšŸ”— [Click here to verify your account](${verifyUrl})\n\nā±ļø This code expires in 15 minutes.`, - flags: 0, - }, - }); - } catch (error: any) { - console.error("Error in /verify command:", error); - return res.status(200).json({ - type: 4, - data: { - content: "āŒ An error occurred. Please try again later.", - flags: 64, - }, - }); - } - } - - if (commandName === "set-realm") { - const realmChoice = interaction.data.options?.[0]?.value; - - if (!realmChoice) { - return res.status(200).json({ - type: 4, - data: { - content: "āŒ Please select a realm", - flags: 64, - }, - }); - } - - const realmMap: any = { - labs: "šŸ”¬ Labs", - gameforge: "šŸŽ® GameForge", - corp: "šŸ’¼ Corp", - foundation: "šŸ¤ Foundation", - devlink: "šŸ”— Dev-Link", - }; - - return res.status(200).json({ - type: 4, - data: { - content: `āœ… You've selected **${realmMap[realmChoice] || realmChoice}** as your primary realm!\n\nšŸ“ Your role will be assigned based on your selection.`, - flags: 0, - }, - }); - } - - if (commandName === "profile") { - const username = - interaction.user?.username || - interaction.member?.user?.username || - "Unknown"; - const discordId = - interaction.user?.id || interaction.member?.user?.id || "Unknown"; - - return res.status(200).json({ - type: 4, - data: { - content: `šŸ‘¤ **Your AeThex Profile**\n\n**Discord:** ${username} (\`${discordId}\`)\n\nšŸ”— [View Full Profile](https://aethex.dev/profile)\n\n**Quick Actions:**\n• \`/set-realm\` - Choose your primary arm\n• \`/verify\` - Link your account\n• \`/verify-role\` - Check your assigned roles`, - flags: 0, - }, - }); - } - - if (commandName === "unlink") { - const discordId = - interaction.user?.id || interaction.member?.user?.id || "Unknown"; - - return res.status(200).json({ - type: 4, - data: { - content: `šŸ”“ **Account Unlinked**\n\nYour Discord account (\`${discordId}\`) has been disconnected from AeThex.\n\nTo link again, use \`/verify\``, - flags: 0, - }, - }); - } - - if (commandName === "verify-role") { - return res.status(200).json({ - type: 4, - data: { - content: `āœ… **Discord Roles**\n\nYour assigned AeThex roles are shown below.\n\nšŸ“Š [View Full Profile](https://aethex.dev/profile)`, - flags: 0, - }, - }); - } - - if (commandName === "help") { - return res.status(200).json({ - type: 4, - data: { - content: `**šŸŽÆ AeThex Discord Bot Help**\n\n**Available Commands:**\n\n• \`/creators [arm]\` - Browse creators across AeThex arms\n - Filter by: labs, gameforge, corp, foundation, nexus\n\n• \`/opportunities [arm]\` - Find job opportunities and collaborations\n - Filter by: labs, gameforge, corp, foundation, nexus\n\n• \`/nexus\` - Explore the Talent Marketplace\n\n• \`/verify\` - Link your Discord account to AeThex\n\n• \`/set-realm\` - Choose your primary realm\n\n• \`/profile\` - View your AeThex profile\n\n• \`/unlink\` - Disconnect your Discord account\n\n• \`/verify-role\` - Check your assigned Discord roles\n\n**Learn More:**\n• 🌐 [Visit AeThex](https://aethex.dev)\n• šŸ‘„ [Join Community](https://aethex.dev/community)\n• šŸ“š [Documentation](https://docs.aethex.tech)`, - flags: 0, - }, - }); - } - - return res.status(200).json({ - type: 4, - data: { - content: `✨ AeThex - Advanced Development Platform\n\n**Available Commands:**\n• \`/creators [arm]\` - Browse creators across AeThex arms\n• \`/opportunities [arm]\` - Find job opportunities and collaborations\n• \`/nexus\` - Explore the Talent Marketplace\n• \`/verify\` - Link your Discord account\n• \`/set-realm\` - Choose your primary realm\n• \`/profile\` - View your AeThex profile\n• \`/unlink\` - Disconnect account\n• \`/verify-role\` - Check your Discord roles\n• \`/help\` - Show this help message`, - flags: 0, - }, - }); - } - - // For MESSAGE_COMPONENT interactions (buttons, etc.) - if (interaction.type === 3) { - console.log( - "[Discord] Message component interaction:", - interaction.data.custom_id, - ); - return res.status(200).json({ - type: 4, - data: { content: "Button clicked - feature coming soon!" }, - }); - } - - // Acknowledge all other interactions - return res.status(200).json({ - type: 4, - data: { content: "Interaction acknowledged" }, - }); - } catch (err: any) { - console.error("[Discord] Error:", err?.message || err); - return res.status(500).json({ error: "Server error" }); - } -} diff --git a/api/discord/register-commands.ts b/api/discord/register-commands.ts deleted file mode 100644 index ed4841aa..00000000 --- a/api/discord/register-commands.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { VercelRequest, VercelResponse } from "@vercel/node"; - -const DISCORD_API_BASE = "https://discord.com/api/v10"; - -const commands = [ - { - name: "creators", - description: "Browse AeThex creators across all arms", - type: 1, - options: [ - { - name: "arm", - description: "Filter creators by arm", - type: 3, - required: false, - choices: [ - { name: "Labs", value: "labs" }, - { name: "GameForge", value: "gameforge" }, - { name: "Corp", value: "corp" }, - { name: "Foundation", value: "foundation" }, - { name: "Nexus", value: "nexus" }, - ], - }, - ], - }, - { - name: "opportunities", - description: "Find job opportunities and collaborations on Nexus", - type: 1, - options: [ - { - name: "arm", - description: "Filter opportunities by arm", - type: 3, - required: false, - choices: [ - { name: "Labs", value: "labs" }, - { name: "GameForge", value: "gameforge" }, - { name: "Corp", value: "corp" }, - { name: "Foundation", value: "foundation" }, - { name: "Nexus", value: "nexus" }, - ], - }, - ], - }, - { - name: "nexus", - description: "Explore the AeThex Talent Marketplace", - type: 1, - }, - { - name: "help", - description: "Get help about AeThex Discord commands", - type: 1, - }, -]; - -export default async function handler(req: VercelRequest, res: VercelResponse) { - // Verify auth token to prevent unauthorized registration - const authToken = req.query.token as string; - if (authToken !== process.env.DISCORD_REGISTER_TOKEN) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const botToken = process.env.DISCORD_BOT_TOKEN; - const clientId = process.env.DISCORD_CLIENT_ID; - - if (!botToken || !clientId) { - return res.status(400).json({ - error: "Missing DISCORD_BOT_TOKEN or DISCORD_CLIENT_ID env variables", - }); - } - - try { - console.log("[Discord] Registering slash commands..."); - - const response = await fetch( - `${DISCORD_API_BASE}/applications/${clientId}/commands`, - { - method: "PUT", - headers: { - Authorization: `Bot ${botToken}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(commands), - }, - ); - - if (!response.ok) { - const errorData = (await response.json()) as any; - console.error("[Discord] Registration error:", errorData); - return res.status(response.status).json({ - error: "Failed to register commands", - details: errorData, - }); - } - - const data = (await response.json()) as any[]; - console.log("[Discord] Successfully registered commands:", data); - - return res.status(200).json({ - success: true, - message: "Successfully registered 3 slash commands", - commands: data.map((cmd: any) => ({ - name: cmd.name, - description: cmd.description, - })), - }); - } catch (error: any) { - console.error("[Discord] Registration endpoint error:", error); - return res.status(500).json({ - error: "Server error during command registration", - message: error.message, - }); - } -} diff --git a/api/discord/role-mappings.ts b/api/discord/role-mappings.ts deleted file mode 100644 index 7c8c229d..00000000 --- a/api/discord/role-mappings.ts +++ /dev/null @@ -1,207 +0,0 @@ -import type { VercelRequest, VercelResponse } from "@vercel/node"; -import { createClient } from "@supabase/supabase-js"; - -// Initialize Supabase with service role -let supabase: any = null; - -try { - supabase = createClient( - process.env.SUPABASE_URL || "", - process.env.SUPABASE_SERVICE_ROLE || "", - ); -} catch (e) { - console.error("Failed to initialize Supabase client:", e); -} - -interface RoleMapping { - id: string; - arm: string; - user_type: string; - discord_role_name: string; - discord_role_id?: string; - server_id?: string; - created_at: string; - updated_at: string; -} - -export default async function handler(req: VercelRequest, res: VercelResponse) { - // Set CORS headers - res.setHeader("Access-Control-Allow-Credentials", "true"); - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader( - "Access-Control-Allow-Methods", - "GET,OPTIONS,PATCH,DELETE,POST,PUT", - ); - res.setHeader( - "Access-Control-Allow-Headers", - "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version", - ); - - if (req.method === "OPTIONS") { - res.status(200).end(); - return; - } - - try { - // Validate Supabase is initialized - if (!supabase) { - console.error("Supabase client not initialized"); - return res.status(500).json({ - error: "Server configuration error: Missing Supabase credentials", - }); - } - - // GET - Fetch all role mappings - if (req.method === "GET") { - try { - const { data, error } = await supabase - .from("discord_role_mappings") - .select("*") - .order("created_at", { ascending: false }); - - if (error) { - console.error("Supabase error:", error); - return res.status(500).json({ - error: `Failed to fetch role mappings: ${error.message}`, - }); - } - - return res.status(200).json(data || []); - } catch (queryErr: any) { - console.error("Query error:", queryErr); - return res.status(500).json({ - error: `Database query error: ${queryErr?.message || "Unknown error"}`, - }); - } - } - - // POST - Create new role mapping - if (req.method === "POST") { - try { - const { arm, discord_role, discord_role_name, server_id, user_type } = - req.body; - - // Support both discord_role and discord_role_name for compatibility - const roleName = discord_role_name || discord_role; - - if (!arm || !roleName) { - return res.status(400).json({ - error: "arm and discord_role (or discord_role_name) are required", - }); - } - - const { data, error } = await supabase - .from("discord_role_mappings") - .insert({ - arm, - user_type: user_type || "community_member", - discord_role_name: roleName, - server_id: server_id || null, - }) - .select() - .single(); - - if (error) { - console.error("Supabase error:", error); - return res.status(500).json({ - error: `Failed to create mapping: ${error.message}`, - }); - } - - return res.status(201).json(data); - } catch (insertErr: any) { - console.error("Insert error:", insertErr); - return res.status(500).json({ - error: `Insert error: ${insertErr?.message || "Unknown error"}`, - }); - } - } - - // PUT - Update role mapping - if (req.method === "PUT") { - try { - const { - id, - arm, - discord_role, - discord_role_name, - server_id, - user_type, - } = req.body; - - if (!id) { - return res.status(400).json({ error: "id is required" }); - } - - const updateData: any = {}; - if (arm) updateData.arm = arm; - const roleName = discord_role_name || discord_role; - if (roleName) updateData.discord_role_name = roleName; - if (server_id !== undefined) updateData.server_id = server_id; - if (user_type) updateData.user_type = user_type; - - const { data, error } = await supabase - .from("discord_role_mappings") - .update(updateData) - .eq("id", id) - .select() - .single(); - - if (error) { - console.error("Supabase error:", error); - return res.status(500).json({ - error: `Failed to update mapping: ${error.message}`, - }); - } - - return res.status(200).json(data); - } catch (updateErr: any) { - console.error("Update error:", updateErr); - return res.status(500).json({ - error: `Update error: ${updateErr?.message || "Unknown error"}`, - }); - } - } - - // DELETE - Delete role mapping - if (req.method === "DELETE") { - try { - const { id } = req.query; - - if (!id) { - return res - .status(400) - .json({ error: "id query parameter is required" }); - } - - const { error } = await supabase - .from("discord_role_mappings") - .delete() - .eq("id", id); - - if (error) { - console.error("Supabase error:", error); - return res.status(500).json({ - error: `Failed to delete mapping: ${error.message}`, - }); - } - - return res.status(200).json({ success: true }); - } catch (deleteErr: any) { - console.error("Delete error:", deleteErr); - return res.status(500).json({ - error: `Delete error: ${deleteErr?.message || "Unknown error"}`, - }); - } - } - - return res.status(405).json({ error: "Method not allowed" }); - } catch (error: any) { - console.error("API error:", error); - // Ensure we always return JSON, never HTML - return res.status(500).json({ - error: error?.message || "Internal server error", - type: "api_error", - }); - } -} diff --git a/api/discord/send-community-post.ts b/api/discord/send-community-post.ts deleted file mode 100644 index fa3d9117..00000000 --- a/api/discord/send-community-post.ts +++ /dev/null @@ -1,201 +0,0 @@ -export const config = { - runtime: "nodejs", -}; - -import { createClient } from "@supabase/supabase-js"; - -const supabaseUrl = process.env.VITE_SUPABASE_URL; -const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE; - -if (!supabaseUrl || !supabaseServiceRole) { - throw new Error("Missing Supabase configuration"); -} - -const supabase = createClient(supabaseUrl, supabaseServiceRole); - -interface WebhookPayload { - username: string; - avatar_url?: string; - embeds: Array<{ - title: string; - description: string; - color: number; - author: { - name: string; - icon_url?: string; - }; - fields?: Array<{ - name: string; - value: string; - inline: boolean; - }>; - footer: { - text: string; - }; - }>; -} - -const ARM_COLORS: Record = { - labs: 0xfbbf24, - gameforge: 0x22c55e, - corp: 0x3b82f6, - foundation: 0xef4444, - devlink: 0x06b6d4, - nexus: 0xa855f7, - staff: 0x7c3aed, -}; - -export default async function handler(req: any, res: any) { - if (req.method !== "POST") { - return res.status(405).json({ error: "Method not allowed" }); - } - - try { - const { - post_id, - title, - content, - arm_affiliation, - author_id, - tags, - category, - } = req.body; - - if (!post_id || !title || !content || !arm_affiliation || !author_id) { - return res.status(400).json({ - error: - "Missing required fields: post_id, title, content, arm_affiliation, author_id", - }); - } - - // Get author details - const { data: author, error: authorError } = await supabase - .from("user_profiles") - .select("username, full_name, avatar_url") - .eq("id", author_id) - .single(); - - if (authorError || !author) { - console.error("[Discord Post API] Author not found:", authorError); - return res.status(404).json({ error: "Author not found" }); - } - - // Get user's Discord webhooks for this arm - const { data: webhooks, error: webhooksError } = await supabase - .from("discord_post_webhooks") - .select("webhook_url") - .eq("user_id", author_id) - .eq("arm_affiliation", arm_affiliation) - .eq("auto_post", true); - - if (webhooksError) { - console.error("[Discord Post API] Webhooks query error:", webhooksError); - return res.status(500).json({ error: webhooksError.message }); - } - - if (!webhooks || webhooks.length === 0) { - // No webhooks configured, just return success - return res.status(200).json({ - success: true, - message: "Post created (no Discord webhooks configured)", - webhooksSent: 0, - }); - } - - // Build Discord embed - const contentPreview = - content.substring(0, 500) + (content.length > 500 ? "..." : ""); - const color = ARM_COLORS[arm_affiliation] || 0x6366f1; - - const embedPayload: WebhookPayload = { - username: "AeThex Community Feed", - avatar_url: - "https://raw.githubusercontent.com/aethex/brand-assets/main/logo.png", - embeds: [ - { - title, - description: contentPreview, - color, - author: { - name: author.full_name || author.username || "Anonymous", - icon_url: author.avatar_url, - }, - fields: [], - footer: { - text: `Posted in ${arm_affiliation.toUpperCase()} • AeThex Community`, - }, - }, - ], - }; - - // Add optional fields - if (category) { - embedPayload.embeds[0].fields!.push({ - name: "Category", - value: category, - inline: true, - }); - } - - if (tags && tags.length > 0) { - embedPayload.embeds[0].fields!.push({ - name: "Tags", - value: tags.map((tag: string) => `#${tag}`).join(" "), - inline: true, - }); - } - - // Send to all webhooks - const webhookResults = await Promise.allSettled( - webhooks.map(async (webhook: any) => { - try { - const response = await fetch(webhook.webhook_url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(embedPayload), - }); - - if (!response.ok) { - const error = await response.text(); - console.error( - `[Discord Post API] Webhook failed:`, - response.status, - error, - ); - throw new Error(`Discord webhook error: ${response.status}`); - } - - return { success: true, webhookUrl: webhook.webhook_url }; - } catch (error: any) { - console.error( - `[Discord Post API] Error sending to webhook:`, - error.message, - ); - return { - success: false, - webhookUrl: webhook.webhook_url, - error: error.message, - }; - } - }), - ); - - const successful = webhookResults.filter( - (r) => r.status === "fulfilled" && r.value.success, - ).length; - - return res.status(200).json({ - success: true, - message: `Post sent to ${successful} Discord webhook(s)`, - webhooksSent: successful, - totalWebhooks: webhooks.length, - }); - } catch (error: any) { - console.error("[Discord Post API] Unexpected error:", error); - return res - .status(500) - .json({ error: error.message || "Internal server error" }); - } -} diff --git a/api/discord/sync-roles.ts b/api/discord/sync-roles.ts deleted file mode 100644 index 6a51dd69..00000000 --- a/api/discord/sync-roles.ts +++ /dev/null @@ -1,128 +0,0 @@ -import type { VercelRequest, VercelResponse } from "@vercel/node"; -import { createClient } from "@supabase/supabase-js"; - -const supabase = createClient( - process.env.SUPABASE_URL || "", - process.env.SUPABASE_SERVICE_ROLE || "", -); - -interface RoleSyncRequest { - discord_id: string; - server_id?: string; -} - -interface DiscordRole { - role_name: string; - role_id?: string; -} - -export default async function handler(req: VercelRequest, res: VercelResponse) { - if (req.method !== "POST") { - res.setHeader("Allow", "POST"); - return res.status(405).json({ error: "Method not allowed" }); - } - - // Verify request is from Discord bot (simple verification) - const authorization = req.headers.authorization; - if ( - !authorization || - authorization !== `Bearer ${process.env.DISCORD_BOT_TOKEN}` - ) { - return res.status(401).json({ error: "Unauthorized" }); - } - - try { - const { discord_id, server_id } = req.body as RoleSyncRequest; - - if (!discord_id) { - return res.status(400).json({ error: "discord_id is required" }); - } - - // Find the linked AeThex user - const { data: link, error: linkError } = await supabase - .from("discord_links") - .select("user_id, primary_arm") - .eq("discord_id", discord_id) - .single(); - - if (linkError || !link) { - return res.status(404).json({ error: "Discord account not linked" }); - } - - // Get user profile - const { data: profile, error: profileError } = await supabase - .from("user_profiles") - .select("user_type") - .eq("id", link.user_id) - .single(); - - if (profileError || !profile) { - return res.status(404).json({ error: "User profile not found" }); - } - - // Get role mappings for this user's realm and type - const { data: mappings, error: mappingsError } = await supabase - .from("discord_role_mappings") - .select("discord_role_name, discord_role_id") - .eq("arm", link.primary_arm) - .eq("user_type", profile.user_type || "community_member") - .is("server_id", null); // Global mappings (not server-specific) - - if (mappingsError) { - return res.status(500).json({ error: "Failed to fetch role mappings" }); - } - - if (!mappings || mappings.length === 0) { - return res.status(200).json({ - success: true, - message: "No role mappings found for this user", - roles_to_assign: [], - }); - } - - // Build list of roles to assign - const rolesToAssign: DiscordRole[] = mappings.map((mapping) => ({ - role_name: mapping.discord_role_name, - role_id: mapping.discord_role_id || undefined, - })); - - // Store role assignments in database (for tracking) - if (server_id) { - const { error: storeError } = await supabase - .from("discord_user_roles") - .upsert( - rolesToAssign.map((role) => ({ - discord_id, - server_id, - role_name: role.role_name, - role_id: role.role_id, - assigned_at: new Date().toISOString(), - last_verified: new Date().toISOString(), - })), - { - onConflict: "discord_id,server_id,role_id", - }, - ); - - if (storeError) { - console.warn("Failed to store role assignments:", storeError); - // Don't fail the sync, just warn - } - } - - return res.status(200).json({ - success: true, - message: "Role sync calculated successfully", - discord_id, - primary_arm: link.primary_arm, - user_type: profile.user_type, - roles_to_assign: rolesToAssign, - note: "Discord bot should now assign these roles to the user", - }); - } catch (error: any) { - console.error("Discord sync-roles error:", error); - return res.status(500).json({ - error: error?.message || "Failed to sync roles", - }); - } -} diff --git a/api/discord/webhooks.ts b/api/discord/webhooks.ts deleted file mode 100644 index ecaf036d..00000000 --- a/api/discord/webhooks.ts +++ /dev/null @@ -1,254 +0,0 @@ -export const config = { - runtime: "nodejs", -}; - -import { createClient } from "@supabase/supabase-js"; - -const supabaseUrl = process.env.VITE_SUPABASE_URL; -const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE; - -if (!supabaseUrl || !supabaseServiceRole) { - throw new Error("Missing Supabase configuration"); -} - -const supabase = createClient(supabaseUrl, supabaseServiceRole); - -export default async function handler(req: any, res: any) { - if (req.method === "GET") { - try { - const { user_id } = req.query; - - if (!user_id) { - return res.status(400).json({ error: "Missing user_id" }); - } - - const { data, error } = await supabase - .from("discord_post_webhooks") - .select( - "id, guild_id, channel_id, webhook_id, arm_affiliation, auto_post, created_at", - ) - .eq("user_id", user_id) - .order("created_at", { ascending: false }); - - if (error) { - console.error("[Webhooks API] Query error:", error); - return res.status(500).json({ error: error.message }); - } - - return res.status(200).json({ - webhooks: data || [], - }); - } catch (error: any) { - console.error("[Webhooks API GET] Unexpected error:", error); - return res - .status(500) - .json({ error: error.message || "Internal server error" }); - } - } - - if (req.method === "POST") { - try { - const { - user_id, - guild_id, - channel_id, - webhook_url, - webhook_id, - arm_affiliation, - auto_post, - } = req.body; - - if ( - !user_id || - !guild_id || - !channel_id || - !webhook_url || - !webhook_id || - !arm_affiliation - ) { - return res.status(400).json({ - error: - "Missing required fields: user_id, guild_id, channel_id, webhook_url, webhook_id, arm_affiliation", - }); - } - - // Validate arm_affiliation - const validArms = [ - "labs", - "gameforge", - "corp", - "foundation", - "devlink", - "nexus", - "staff", - ]; - if (!validArms.includes(arm_affiliation)) { - return res.status(400).json({ - error: `Invalid arm_affiliation. Must be one of: ${validArms.join(", ")}`, - }); - } - - // Test webhook by sending a test message - try { - const testPayload = { - username: "AeThex Community Feed", - content: - "āœ… Webhook successfully configured for AeThex Community Posts!", - }; - - const testResponse = await fetch(webhook_url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(testPayload), - }); - - if (!testResponse.ok) { - return res.status(400).json({ - error: "Webhook URL is invalid or unreachable", - }); - } - } catch (webhookError) { - return res.status(400).json({ - error: "Failed to test webhook connection", - }); - } - - // Insert webhook configuration - const { data, error } = await supabase - .from("discord_post_webhooks") - .insert({ - user_id, - guild_id, - channel_id, - webhook_url, - webhook_id, - arm_affiliation, - auto_post: auto_post !== false, // Default to true - }) - .select( - "id, guild_id, channel_id, webhook_id, arm_affiliation, auto_post, created_at", - ); - - if (error) { - console.error("[Webhooks API] Insert error:", error); - if (error.code === "23505") { - return res.status(409).json({ - error: "Webhook already configured for this arm and channel", - }); - } - return res.status(500).json({ error: error.message }); - } - - return res.status(201).json({ - webhook: data?.[0], - }); - } catch (error: any) { - console.error("[Webhooks API POST] Unexpected error:", error); - return res - .status(500) - .json({ error: error.message || "Internal server error" }); - } - } - - if (req.method === "PUT") { - try { - const { id, user_id, auto_post } = req.body; - - if (!id || !user_id) { - return res.status(400).json({ error: "Missing id or user_id" }); - } - - // Verify ownership - const { data: webhook, error: fetchError } = await supabase - .from("discord_post_webhooks") - .select("user_id") - .eq("id", id) - .single(); - - if (fetchError || !webhook) { - return res.status(404).json({ error: "Webhook not found" }); - } - - if (webhook.user_id !== user_id) { - return res - .status(403) - .json({ error: "You can only manage your own webhooks" }); - } - - // Update webhook - const { data, error } = await supabase - .from("discord_post_webhooks") - .update({ auto_post }) - .eq("id", id) - .select( - "id, guild_id, channel_id, webhook_id, arm_affiliation, auto_post, created_at", - ); - - if (error) { - console.error("[Webhooks API] Update error:", error); - return res.status(500).json({ error: error.message }); - } - - return res.status(200).json({ - webhook: data?.[0], - }); - } catch (error: any) { - console.error("[Webhooks API PUT] Unexpected error:", error); - return res - .status(500) - .json({ error: error.message || "Internal server error" }); - } - } - - if (req.method === "DELETE") { - try { - const { id, user_id } = req.body; - - if (!id || !user_id) { - return res.status(400).json({ error: "Missing id or user_id" }); - } - - // Verify ownership - const { data: webhook, error: fetchError } = await supabase - .from("discord_post_webhooks") - .select("user_id") - .eq("id", id) - .single(); - - if (fetchError || !webhook) { - return res.status(404).json({ error: "Webhook not found" }); - } - - if (webhook.user_id !== user_id) { - return res - .status(403) - .json({ error: "You can only delete your own webhooks" }); - } - - // Delete webhook - const { error } = await supabase - .from("discord_post_webhooks") - .delete() - .eq("id", id); - - if (error) { - console.error("[Webhooks API] Delete error:", error); - return res.status(500).json({ error: error.message }); - } - - return res.status(200).json({ - success: true, - message: "Webhook deleted successfully", - }); - } catch (error: any) { - console.error("[Webhooks API DELETE] Unexpected error:", error); - return res - .status(500) - .json({ error: error.message || "Internal server error" }); - } - } - - return res.status(405).json({ error: "Method not allowed" }); -} diff --git a/client/contexts/DiscordActivityContext.tsx b/client/contexts/DiscordActivityContext.tsx index 084f936e..db3e05bf 100644 --- a/client/contexts/DiscordActivityContext.tsx +++ b/client/contexts/DiscordActivityContext.tsx @@ -97,9 +97,7 @@ export const DiscordActivityProvider: React.FC< clientId, ); - const sdk = new DiscordSDK({ - clientId, - }); + const sdk = new DiscordSDK(clientId); setDiscordSdk(sdk); @@ -108,137 +106,106 @@ export const DiscordActivityProvider: React.FC< await sdk.ready(); console.log("[Discord Activity] SDK is ready"); - // Authenticate the session with Discord - console.log("[Discord Activity] Authenticating session..."); - await sdk.authenticate(); - console.log("[Discord Activity] Session authenticated"); + // Authorize the user to get an access token + console.log("[Discord Activity] Authorizing user..."); + const { code } = await sdk.commands.authorize({ + client_id: clientId, + response_type: "code", + state: "", + scope: ["identify", "guilds"], + prompt: "none", + }); - // Get the current user from the SDK - const currentUser = await sdk.user.getUser(); console.log( - "[Discord Activity] Current user:", - currentUser ? "exists" : "null", + "[Discord Activity] Got authorization code, exchanging for token...", ); - if (!currentUser) { - // User not authenticated, authorize them - console.log("[Discord Activity] Authorizing user..."); - const { code } = await sdk.commands.authorize({ - client_id: clientId, - response_type: "code", - state: "", - scope: "identify email guilds", - prompt: "none", - }); + // Exchange code for access token via our backend + const tokenResponse = await fetch(`${API_BASE}/api/discord/token`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ code }), + }); - console.log( - "[Discord Activity] Got authorization code, exchanging for token...", + if (!tokenResponse.ok) { + const errorData = await tokenResponse.json(); + const errMsg = errorData.error || "Failed to exchange code"; + console.error( + "[Discord Activity] Token exchange failed:", + errMsg, ); - - // Exchange code for access token via our backend - const tokenResponse = await fetch(`${API_BASE}/api/discord/token`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ code }), - }); - - if (!tokenResponse.ok) { - const errorData = await tokenResponse.json(); - const errMsg = errorData.error || "Failed to exchange code"; - console.error( - "[Discord Activity] Token exchange failed:", - errMsg, - ); - setError(errMsg); - setIsLoading(false); - return; - } - - const tokenData = await tokenResponse.json(); - const access_token = tokenData.access_token; - - console.log( - "[Discord Activity] Got access token, authenticating with SDK...", - ); - - // Authenticate with SDK using the access token - const authResult = await sdk.commands.authenticate({ - access_token, - }); - - if (!authResult) { - console.error("[Discord Activity] SDK authentication failed"); - setError("SDK authentication failed"); - setIsLoading(false); - return; - } - - console.log( - "[Discord Activity] Authenticated with SDK, fetching user profile...", - ); - setAuth(authResult); - - // Get user info using the access token - const userResponse = await fetch( - "https://discord.com/api/v10/users/@me", - { - headers: { - Authorization: `Bearer ${access_token}`, - }, - }, - ); - - if (!userResponse.ok) { - console.error("[Discord Activity] Failed to fetch user profile"); - setError("Failed to fetch user profile"); - setIsLoading(false); - return; - } - - const discordUserData = await userResponse.json(); - console.log( - "[Discord Activity] User profile fetched:", - discordUserData.username, - ); - - // Store the user data - const userData: DiscordUser = { - id: discordUserData.id, - discord_id: discordUserData.id, - full_name: - discordUserData.global_name || discordUserData.username, - username: discordUserData.username, - avatar_url: discordUserData.avatar - ? `https://cdn.discordapp.com/avatars/${discordUserData.id}/${discordUserData.avatar}.png` - : null, - bio: null, - user_type: "community_member", - primary_arm: "labs", - }; - - setUser(userData); - setError(null); - console.log("[Discord Activity] User authenticated successfully"); - } else { - // User already authenticated - console.log("[Discord Activity] User already authenticated"); - const userData: DiscordUser = { - id: currentUser.id, - discord_id: currentUser.id, - full_name: currentUser.global_name || currentUser.username, - username: currentUser.username, - avatar_url: currentUser.avatar - ? `https://cdn.discordapp.com/avatars/${currentUser.id}/${currentUser.avatar}.png` - : null, - bio: null, - user_type: "community_member", - primary_arm: "labs", - }; - setUser(userData); - setError(null); + setError(errMsg); + setIsLoading(false); + return; } + + const tokenData = await tokenResponse.json(); + const access_token = tokenData.access_token; + + console.log( + "[Discord Activity] Got access token, authenticating with SDK...", + ); + + // Authenticate with SDK using the access token + const authResult = await sdk.commands.authenticate({ + access_token, + }); + + if (!authResult) { + console.error("[Discord Activity] SDK authentication failed"); + setError("SDK authentication failed"); + setIsLoading(false); + return; + } + + console.log( + "[Discord Activity] Authenticated with SDK, fetching user profile...", + ); + setAuth(authResult); + + // Get user info using the access token + const userResponse = await fetch( + "https://discord.com/api/v10/users/@me", + { + headers: { + Authorization: `Bearer ${access_token}`, + }, + }, + ); + + if (!userResponse.ok) { + console.error("[Discord Activity] Failed to fetch user profile"); + setError("Failed to fetch user profile"); + setIsLoading(false); + return; + } + + const discordUserData = await userResponse.json(); + console.log( + "[Discord Activity] User profile fetched:", + discordUserData.username, + ); + + // Store the user data + const userData: DiscordUser = { + id: discordUserData.id, + discord_id: discordUserData.id, + full_name: + discordUserData.global_name || discordUserData.username, + username: discordUserData.username, + avatar_url: discordUserData.avatar + ? `https://cdn.discordapp.com/avatars/${discordUserData.id}/${discordUserData.avatar}.png` + : null, + bio: null, + user_type: "community_member", + primary_arm: "labs", + }; + + setUser(userData); + setError(null); + console.log("[Discord Activity] User authenticated successfully"); } catch (err: any) { console.error("Discord Activity initialization error:", err); console.error("Error details:", { diff --git a/client/pages/DiscordActivity.tsx b/client/pages/DiscordActivity.tsx index a5b403e3..2ef31534 100644 --- a/client/pages/DiscordActivity.tsx +++ b/client/pages/DiscordActivity.tsx @@ -10,6 +10,7 @@ interface DiscordSDK { commands: { authorize: (options: any) => Promise; }; + subscribe: (event: string, callback: (data: any) => void) => void; } export default function DiscordActivity() { diff --git a/replit.md b/replit.md index 526c191d..9887ee11 100644 --- a/replit.md +++ b/replit.md @@ -15,7 +15,18 @@ Do not make changes to the file `server/index.ts`. ## System Architecture AeThex is built as a full-stack web application utilizing React 18 with TypeScript for the frontend, Vite 6 as the build tool, and Express.js for the backend. Supabase (PostgreSQL) serves as the primary database. Styling is handled with Tailwind CSS, and UI components leverage Radix UI. TanStack Query is used for state management, and React Router DOM for routing. -The application features a multi-realm system including Nexus, GameForge, Foundation, Labs, Corp, Staff, and Dev-Link (7 total), each with specific functionalities. Key capabilities include community features (feed, posts, comments), a Creator Network with profile passports and achievements, and a Nexus Marketplace for opportunities and contracts. Discord Integration is handled by a separately hosted Discord bot for commands, OAuth, and role management, alongside a bidirectional feed bridge for syncing content between Discord and the platform. +The application features a multi-realm system including Nexus, GameForge, Foundation, Labs, Corp, Staff, and Dev-Link (7 total), each with specific functionalities. Key capabilities include community features (feed, posts, comments), a Creator Network with profile passports and achievements, and a Nexus Marketplace for opportunities and contracts. + +### Discord Integration Architecture +Discord functionality is split between two deployments: +- **Main Bot (aethex-bot-master.replit.app)**: Handles slash commands, feed sync, role management, and bot presence +- **Activity (aethex.dev)**: Hosts the Discord Activity UI, Activity OAuth, and embedded app experience + +Activity-specific endpoints in this project: +- `/api/discord/activity-auth` - Activity authentication +- `/api/discord/token` - OAuth token exchange +- `/api/discord/oauth/*` - OAuth flow handlers +- `/discord-manifest.json` - Activity manifest The UI/UX emphasizes an isometric 2.5D realm selector, replacing 3D scenes with CSS-based isometric cards for performance. It features responsive grids, ambient particles, and interactive tilt effects. The platform also supports an Electron desktop application with a secure IPC bridge and an automated build/release pipeline for multi-platform distribution. diff --git a/scripts/register-discord-commands.ts b/scripts/register-discord-commands.ts deleted file mode 100644 index b90a855a..00000000 --- a/scripts/register-discord-commands.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Register Discord Slash Commands - * Run this script once to register all slash commands with Discord - * - * Usage: - * npx ts-node scripts/register-discord-commands.ts - * - * Required Environment Variables: - * - DISCORD_BOT_TOKEN: Bot token from Discord Developer Portal - * - DISCORD_CLIENT_ID: Application ID from Discord Developer Portal - */ - -const DISCORD_CLIENT_ID = process.env.DISCORD_CLIENT_ID || "578971245454950421"; -const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN; - -if (!DISCORD_BOT_TOKEN) { - console.error("āŒ DISCORD_BOT_TOKEN environment variable is required"); - process.exit(1); -} - -const commands = [ - { - name: "creators", - description: "Browse AeThex creators across all arms", - options: [ - { - name: "arm", - description: - "Filter by specific arm (labs, gameforge, corp, foundation, nexus)", - type: 3, // STRING - required: false, - choices: [ - { name: "Labs", value: "labs" }, - { name: "GameForge", value: "gameforge" }, - { name: "Corp", value: "corp" }, - { name: "Foundation", value: "foundation" }, - { name: "Nexus", value: "nexus" }, - ], - }, - ], - }, - { - name: "opportunities", - description: "Find job opportunities and collaborations on Nexus", - options: [ - { - name: "arm", - description: - "Filter by specific arm (labs, gameforge, corp, foundation, nexus)", - type: 3, // STRING - required: false, - choices: [ - { name: "Labs", value: "labs" }, - { name: "GameForge", value: "gameforge" }, - { name: "Corp", value: "corp" }, - { name: "Foundation", value: "foundation" }, - { name: "Nexus", value: "nexus" }, - ], - }, - ], - }, - { - name: "nexus", - description: "Explore the AeThex Talent Marketplace", - options: [], - }, -]; - -async function registerCommands() { - console.log(`šŸ“ Registering ${commands.length} slash commands...`); - console.log(`šŸ”§ Client ID: ${DISCORD_CLIENT_ID}`); - - try { - const response = await fetch( - `https://discord.com/api/v10/applications/${DISCORD_CLIENT_ID}/commands`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: `Bot ${DISCORD_BOT_TOKEN}`, - }, - body: JSON.stringify(commands), - }, - ); - - if (!response.ok) { - const errorData = await response.text(); - console.error( - "āŒ Failed to register commands:", - response.status, - errorData, - ); - process.exit(1); - } - - const data = await response.json(); - console.log(`āœ… Successfully registered ${data.length} slash commands!`); - - data.forEach((cmd: any) => { - console.log(` āœ“ /${cmd.name} - ${cmd.description}`); - }); - - console.log("\nšŸ“ Next steps:"); - console.log("1. Go to Discord Developer Portal > Application Settings"); - console.log( - "2. Set Interactions Endpoint URL to: https://aethex.dev/api/discord/interactions", - ); - console.log("3. Save changes"); - console.log( - "4. Test commands in Discord with /creators, /opportunities, etc.", - ); - } catch (error) { - console.error("āŒ Error registering commands:", error); - process.exit(1); - } -} - -registerCommands();