diff --git a/api/discord/link.ts b/api/discord/link.ts index c98b0844..7349388d 100644 --- a/api/discord/link.ts +++ b/api/discord/link.ts @@ -64,18 +64,17 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { if (existingLink) { return res.status(409).json({ - error: "This Discord account is already linked to another AeThex account", + error: + "This Discord account is already linked to another AeThex account", }); } // Create the link - const { error: linkError } = await supabase - .from("discord_links") - .insert({ - discord_id: verification.discord_id, - user_id, - primary_arm: "labs", // Default to labs - }); + const { error: linkError } = await supabase.from("discord_links").insert({ + discord_id: verification.discord_id, + user_id, + primary_arm: "labs", // Default to labs + }); if (linkError) { console.error("Failed to create discord link:", linkError); diff --git a/api/discord/sync-roles.ts b/api/discord/sync-roles.ts index e88697d0..6a51dd69 100644 --- a/api/discord/sync-roles.ts +++ b/api/discord/sync-roles.ts @@ -24,7 +24,10 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { // Verify request is from Discord bot (simple verification) const authorization = req.headers.authorization; - if (!authorization || authorization !== `Bearer ${process.env.DISCORD_BOT_TOKEN}`) { + if ( + !authorization || + authorization !== `Bearer ${process.env.DISCORD_BOT_TOKEN}` + ) { return res.status(401).json({ error: "Unauthorized" }); } diff --git a/api/games/roblox-auth.ts b/api/games/roblox-auth.ts index 16f45130..6eec16a7 100644 --- a/api/games/roblox-auth.ts +++ b/api/games/roblox-auth.ts @@ -31,7 +31,11 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { const secret = process.env.ROBLOX_SHARED_SECRET || ""; // Verify signature for security - if (signature && secret && !verifyRobloxSignature(payload, signature, secret)) { + if ( + signature && + secret && + !verifyRobloxSignature(payload, signature, secret) + ) { console.warn("Invalid Roblox signature"); return res.status(401).json({ error: "Invalid signature" }); } diff --git a/api/games/verify-token.ts b/api/games/verify-token.ts index 470c23eb..97ded114 100644 --- a/api/games/verify-token.ts +++ b/api/games/verify-token.ts @@ -13,7 +13,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { } try { - const { session_token, game } = req.method === "POST" ? req.body : req.query; + const { session_token, game } = + req.method === "POST" ? req.body : req.query; if (!session_token) { return res.status(400).json({ error: "session_token is required" }); @@ -22,7 +23,9 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { // Find the session const { data: sessionData, error: sessionError } = await supabase .from("game_sessions") - .select("*, user_profiles!inner(id, username, email, full_name, metadata)") + .select( + "*, user_profiles!inner(id, username, email, full_name, metadata)", + ) .eq("session_token", String(session_token)) .single(); @@ -38,7 +41,9 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { // Optional: Verify game matches if provided if (game && sessionData.game !== String(game).toLowerCase()) { - return res.status(403).json({ error: "Token is not valid for this game" }); + return res + .status(403) + .json({ error: "Token is not valid for this game" }); } // Update last activity diff --git a/api/roblox/oauth-callback.ts b/api/roblox/oauth-callback.ts index 37003438..f2221a20 100644 --- a/api/roblox/oauth-callback.ts +++ b/api/roblox/oauth-callback.ts @@ -31,7 +31,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { const clientId = process.env.ROBLOX_OAUTH_CLIENT_ID; const clientSecret = process.env.ROBLOX_OAUTH_CLIENT_SECRET; const redirectUri = - process.env.ROBLOX_OAUTH_REDIRECT_URI || "https://aethex.dev/roblox-callback"; + process.env.ROBLOX_OAUTH_REDIRECT_URI || + "https://aethex.dev/roblox-callback"; if (!clientId || !clientSecret) { return res.status(500).json({ error: "Roblox OAuth not configured" }); @@ -59,9 +60,12 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { const tokenData: RobloxTokenResponse = await tokenResponse.json(); // Get user info with access token - const userResponse = await fetch("https://apis.roblox.com/oauth/v1/userinfo", { - headers: { Authorization: `Bearer ${tokenData.access_token}` }, - }); + const userResponse = await fetch( + "https://apis.roblox.com/oauth/v1/userinfo", + { + headers: { Authorization: `Bearer ${tokenData.access_token}` }, + }, + ); if (!userResponse.ok) { console.error("Failed to fetch Roblox user info"); diff --git a/api/user/link-roblox.ts b/api/user/link-roblox.ts index d0a768d7..ae16868f 100644 --- a/api/user/link-roblox.ts +++ b/api/user/link-roblox.ts @@ -21,7 +21,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { const token = authHeader.substring(7); // Verify token with Supabase - const { data: userData, error: authError } = await supabase.auth.getUser(token); + const { data: userData, error: authError } = + await supabase.auth.getUser(token); if (authError || !userData.user) { return res.status(401).json({ error: "Invalid token" }); } diff --git a/api/user/link-web3.ts b/api/user/link-web3.ts index 5db2f2f3..fd6bba1d 100644 --- a/api/user/link-web3.ts +++ b/api/user/link-web3.ts @@ -22,7 +22,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { const token = authHeader.substring(7); // Verify token with Supabase - const { data: userData, error: authError } = await supabase.auth.getUser(token); + const { data: userData, error: authError } = + await supabase.auth.getUser(token); if (authError || !userData.user) { return res.status(401).json({ error: "Invalid token" }); } @@ -66,15 +67,16 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { .eq("nonce", nonce); // Link wallet to existing user - const { error: linkError } = await supabase - .from("web3_wallets") - .insert({ - user_id: userData.user.id, - wallet_address: normalizedAddress, - chain_id: 1, // Ethereum mainnet - }); + const { error: linkError } = await supabase.from("web3_wallets").insert({ + user_id: userData.user.id, + wallet_address: normalizedAddress, + chain_id: 1, // Ethereum mainnet + }); - if (linkError && !linkError.message.includes("violates unique constraint")) { + if ( + linkError && + !linkError.message.includes("violates unique constraint") + ) { console.error("Failed to link wallet:", linkError); return res.status(500).json({ error: "Failed to link wallet" }); } diff --git a/api/web3/verify.ts b/api/web3/verify.ts index ae4f17a4..8c2c3424 100644 --- a/api/web3/verify.ts +++ b/api/web3/verify.ts @@ -80,15 +80,16 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { const username = normalizedAddress.substring(2, 10); // Create Supabase auth user - const { data: authData, error: authError } = await supabase.auth.admin.createUser({ - email, - password: require("crypto").randomBytes(32).toString("hex"), - email_confirm: true, - user_metadata: { - wallet_address: normalizedAddress, - auth_method: "web3", - }, - }); + const { data: authData, error: authError } = + await supabase.auth.admin.createUser({ + email, + password: require("crypto").randomBytes(32).toString("hex"), + email_confirm: true, + user_metadata: { + wallet_address: normalizedAddress, + auth_method: "web3", + }, + }); if (authError || !authData.user) { console.error("Failed to create auth user:", authError); diff --git a/client/App.tsx b/client/App.tsx index 7f76f5d5..4d7272b4 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -119,232 +119,247 @@ const App = () => ( - - - - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } - /> - - } /> - } - /> - } - /> - } - /> - } - /> - } - /> - - } - /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - - {/* Creator Network routes */} - } /> - } - /> - } /> - } - /> - - {/* Service routes */} - } /> - } /> - } /> - } /> - } - /> - } /> - - {/* New Arm Landing Pages */} - } /> - } - /> - } /> - } - /> - - } /> - } - /> - } - /> - } - /> - - } /> - } - /> - } - /> - } /> - - } /> - } - /> - } - /> - } - /> - - {/* Dev-Link routes */} - } /> - } - /> - - {/* Nexus routes */} - } /> - - {/* Resource routes */} - }> - } /> - } /> - } /> + + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> } + path="/projects/:projectId/board" + element={} + /> + } /> + } /> + } /> - } /> - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } - /> - } - /> - } /> - } /> - } /> - } /> - } /> - {/* Informational routes */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } + /> + } + /> + } + /> + } + /> + } + /> - {/* Legal routes */} - } /> - } /> + } + /> + } /> + } + /> + } /> + } /> + } /> + } /> + } /> + } /> - {/* Discord routes */} - } /> - } - /> + {/* Creator Network routes */} + } /> + } + /> + } /> + } + /> - {/* Explicit 404 route for static hosting fallbacks */} - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - - + {/* Service routes */} + } + /> + } + /> + } /> + } /> + } + /> + } /> + + {/* New Arm Landing Pages */} + } /> + } + /> + } /> + } + /> + + } /> + } + /> + } + /> + } + /> + + } /> + } + /> + } + /> + } /> + + } /> + } + /> + } + /> + } + /> + + {/* Dev-Link routes */} + } /> + } + /> + + {/* Nexus routes */} + } /> + + {/* Resource routes */} + }> + } /> + } /> + } /> + } + /> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + } + /> + } + /> + } + /> + } + /> + } + /> + } /> + } /> + } /> + } /> + } /> + + {/* Informational routes */} + } /> + } + /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + {/* Legal routes */} + } /> + } /> + + {/* Discord routes */} + } /> + } + /> + + {/* Explicit 404 route for static hosting fallbacks */} + } /> + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} + } /> + + + + diff --git a/client/components/onboarding/RealmSelection.tsx b/client/components/onboarding/RealmSelection.tsx index 9593089f..2ba8bcb8 100644 --- a/client/components/onboarding/RealmSelection.tsx +++ b/client/components/onboarding/RealmSelection.tsx @@ -1,4 +1,10 @@ -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { CheckCircle2 } from "lucide-react"; @@ -12,7 +18,8 @@ const REALMS = [ { id: "labs", title: "๐Ÿงช Labs", - description: "Research & Development - Cutting-edge innovation and experimentation", + description: + "Research & Development - Cutting-edge innovation and experimentation", color: "from-yellow-500/20 to-yellow-600/20", borderColor: "border-yellow-400", }, @@ -46,18 +53,25 @@ const REALMS = [ }, ]; -export default function RealmSelection({ selectedRealm, onSelect, onNext }: RealmSelectionProps) { +export default function RealmSelection({ + selectedRealm, + onSelect, + onNext, +}: RealmSelectionProps) { return (
-

Choose Your Primary Realm

+

+ Choose Your Primary Realm +

- Select the AeThex realm that best matches your primary focus. You can always change this later. + Select the AeThex realm that best matches your primary focus. You can + always change this later.

- {REALMS.map(realm => ( + {REALMS.map((realm) => (
onSelect(realm.id)} @@ -67,7 +81,9 @@ export default function RealmSelection({ selectedRealm, onSelect, onNext }: Real > @@ -81,7 +97,9 @@ export default function RealmSelection({ selectedRealm, onSelect, onNext }: Real
- {realm.description} + + {realm.description} +
diff --git a/client/contexts/AuthContext.tsx b/client/contexts/AuthContext.tsx index 9d765079..5d3d88f0 100644 --- a/client/contexts/AuthContext.tsx +++ b/client/contexts/AuthContext.tsx @@ -836,8 +836,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ window.localStorage.removeItem("onboarding_complete"); window.localStorage.removeItem("aethex_onboarding_progress_v1"); Object.keys(window.localStorage) - .filter(key => key.startsWith("sb-") || key.includes("supabase") || key.startsWith("mock_") || key.startsWith("demo_")) - .forEach(key => window.localStorage.removeItem(key)); + .filter( + (key) => + key.startsWith("sb-") || + key.includes("supabase") || + key.startsWith("mock_") || + key.startsWith("demo_"), + ) + .forEach((key) => window.localStorage.removeItem(key)); console.log("localStorage cleared"); } catch (e) { console.warn("localStorage clear failed:", e); diff --git a/client/contexts/Web3Context.tsx b/client/contexts/Web3Context.tsx index 234de556..9c509414 100644 --- a/client/contexts/Web3Context.tsx +++ b/client/contexts/Web3Context.tsx @@ -1,4 +1,10 @@ -import React, { createContext, useContext, useState, useCallback, useEffect } from "react"; +import React, { + createContext, + useContext, + useState, + useCallback, + useEffect, +} from "react"; import { aethexToast } from "@/lib/aethex-toast"; export interface Web3ContextType { @@ -13,7 +19,9 @@ export interface Web3ContextType { const Web3Context = createContext(undefined); -export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => { +export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { const [account, setAccount] = useState(null); const [isConnected, setIsConnected] = useState(false); const [isConnecting, setIsConnecting] = useState(false); @@ -100,7 +108,11 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children const signMessage = useCallback( async (message: string): Promise => { - if (!account || typeof window === "undefined" || !(window as any).ethereum) { + if ( + !account || + typeof window === "undefined" || + !(window as any).ethereum + ) { throw new Error("Wallet not connected"); } @@ -144,8 +156,14 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children (window as any).ethereum.on("chainChanged", handleChainChanged); return () => { - (window as any).ethereum?.removeListener("accountsChanged", handleAccountsChanged); - (window as any).ethereum?.removeListener("chainChanged", handleChainChanged); + (window as any).ethereum?.removeListener( + "accountsChanged", + handleAccountsChanged, + ); + (window as any).ethereum?.removeListener( + "chainChanged", + handleChainChanged, + ); }; }, []); diff --git a/client/pages/Admin.tsx b/client/pages/Admin.tsx index db448299..f86cb2c9 100644 --- a/client/pages/Admin.tsx +++ b/client/pages/Admin.tsx @@ -1513,27 +1513,59 @@ export default function Admin() {
-

Realm to Discord Role Mappings

+

+ Realm to Discord Role Mappings +

- Configure which Discord roles are assigned for each AeThex realm and user type. + Configure which Discord roles are assigned for each + AeThex realm and user type.

{[ - { realm: "Labs", role: "Labs Creator", members: 234 }, - { realm: "GameForge", role: "GameForge Creator", members: 456 }, - { realm: "Corp", role: "Corp Member", members: 89 }, - { realm: "Foundation", role: "Foundation Member", members: 145 }, - { realm: "Dev-Link", role: "Dev-Link Member", members: 78 }, + { + realm: "Labs", + role: "Labs Creator", + members: 234, + }, + { + realm: "GameForge", + role: "GameForge Creator", + members: 456, + }, + { + realm: "Corp", + role: "Corp Member", + members: 89, + }, + { + realm: "Foundation", + role: "Foundation Member", + members: 145, + }, + { + realm: "Dev-Link", + role: "Dev-Link Member", + members: 78, + }, ].map((mapping, idx) => ( -
+

{mapping.realm}

-

{mapping.role}

+

+ {mapping.role} +

-

{mapping.members}

-

assigned members

+

+ {mapping.members} +

+

+ assigned members +

))} @@ -1542,19 +1574,31 @@ export default function Admin() {
-

Bot Configuration

+

+ Bot Configuration +

Bot Status - Online + + Online +
- Servers Connected - 12 servers + + Servers Connected + + + 12 servers +
- Linked Accounts - 1,234 users + + Linked Accounts + + + 1,234 users +
diff --git a/client/pages/DiscordVerify.tsx b/client/pages/DiscordVerify.tsx index 234c1d62..4e762bda 100644 --- a/client/pages/DiscordVerify.tsx +++ b/client/pages/DiscordVerify.tsx @@ -55,7 +55,8 @@ export default function DiscordVerify() { toastSuccess({ title: "Discord Linked!", - description: "Your Discord account has been successfully linked to AeThex", + description: + "Your Discord account has been successfully linked to AeThex", }); // Redirect to profile settings diff --git a/client/pages/Onboarding.tsx b/client/pages/Onboarding.tsx index 6f43b049..c6c16edb 100644 --- a/client/pages/Onboarding.tsx +++ b/client/pages/Onboarding.tsx @@ -514,7 +514,10 @@ export default function Onboarding() { selectedRealm={data.creatorProfile.primaryArm || ""} onSelect={(realm) => updateData({ - creatorProfile: { ...data.creatorProfile, primaryArm: realm }, + creatorProfile: { + ...data.creatorProfile, + primaryArm: realm, + }, }) } onNext={nextStep} diff --git a/client/pages/RobloxCallback.tsx b/client/pages/RobloxCallback.tsx index aa740412..6ab235ef 100644 --- a/client/pages/RobloxCallback.tsx +++ b/client/pages/RobloxCallback.tsx @@ -47,7 +47,8 @@ export default function RobloxCallback() { const errorData = await response.json(); toastError({ title: "Authentication failed", - description: errorData.error || "Could not authenticate with Roblox", + description: + errorData.error || "Could not authenticate with Roblox", }); navigate("/login"); return; diff --git a/client/pages/Web3Callback.tsx b/client/pages/Web3Callback.tsx index af848272..82db41af 100644 --- a/client/pages/Web3Callback.tsx +++ b/client/pages/Web3Callback.tsx @@ -60,7 +60,8 @@ export default function Web3Callback() { if (user && data.success) { toastSuccess({ title: "Wallet linked", - description: "Your Ethereum wallet is now connected to your account", + description: + "Your Ethereum wallet is now connected to your account", }); navigate("/dashboard?tab=connections"); return; @@ -93,11 +94,21 @@ export default function Web3Callback() { if (account && !authLoading) { handleWeb3Auth(); } - }, [account, authLoading, signMessage, user, navigate, toastError, toastSuccess]); + }, [ + account, + authLoading, + signMessage, + user, + navigate, + toastError, + toastSuccess, + ]); return ( diff --git a/discord-bot/DEPLOYMENT_GUIDE.md b/discord-bot/DEPLOYMENT_GUIDE.md index 2e216d3f..9b1c532e 100644 --- a/discord-bot/DEPLOYMENT_GUIDE.md +++ b/discord-bot/DEPLOYMENT_GUIDE.md @@ -12,6 +12,7 @@ ### Step 1: Prepare the Bot Directory Ensure all bot files are committed: + ``` code/discord-bot/ โ”œโ”€โ”€ bot.js @@ -53,16 +54,19 @@ NODE_ENV=production In Spaceship Application Settings: **Build Command:** + ```bash npm install ``` **Start Command:** + ```bash npm start ``` **Root Directory:** + ``` code/discord-bot ``` @@ -80,6 +84,7 @@ code/discord-bot ### 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" @@ -87,6 +92,7 @@ Once deployed: ## ๐Ÿ“ก Discord Bot Endpoints The bot will be accessible at: + ``` https:/// ``` @@ -96,11 +102,13 @@ The bot uses Discord's WebSocket connection (not HTTP), so it doesn't need to ex ## ๐Ÿ”Œ 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 @@ -108,22 +116,26 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically: ## ๐Ÿ› ๏ธ 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 @@ -132,12 +144,14 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically: ## ๐Ÿ“Š 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 @@ -156,6 +170,7 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically: ## ๐Ÿ†˜ Support For issues: + 1. Check Spaceship logs 2. Review `/api/discord/link` endpoint response 3. Verify all environment variables are set correctly @@ -177,6 +192,7 @@ Run this migration on your AeThex Supabase: ## ๐ŸŽ‰ 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 diff --git a/discord-bot/bot.js b/discord-bot/bot.js index 44c65afc..dc06772c 100644 --- a/discord-bot/bot.js +++ b/discord-bot/bot.js @@ -1,11 +1,20 @@ -const { Client, GatewayIntentBits, REST, Routes, Collection, EmbedBuilder } = require('discord.js'); -const { createClient } = require('@supabase/supabase-js'); -const fs = require('fs'); -const path = require('path'); -require('dotenv').config(); +const { + Client, + GatewayIntentBits, + REST, + Routes, + Collection, + EmbedBuilder, +} = require("discord.js"); +const { createClient } = require("@supabase/supabase-js"); +const fs = require("fs"); +const path = require("path"); +require("dotenv").config(); // Initialize Discord client -const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages] }); +const client = new Client({ + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages], +}); // Initialize Supabase const supabase = createClient( @@ -17,35 +26,41 @@ const supabase = createClient( 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')); +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) { + if ("data" in command && "execute" in command) { client.commands.set(command.data.name, command); console.log(`โœ… Loaded command: ${command.data.name}`); } } // Bot ready event -client.once('ready', () => { +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' }); + client.user.setActivity("/verify to link your AeThex account", { + type: "LISTENING", + }); }); // Slash command interaction handler -client.on('interactionCreate', async interaction => { +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.`); + console.warn( + `โš ๏ธ No command matching ${interaction.commandName} was found.`, + ); return; } @@ -53,12 +68,12 @@ client.on('interactionCreate', async interaction => { 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' }); + .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 }); @@ -76,7 +91,9 @@ async function registerCommands() { commands.push(command.data.toJSON()); } - const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN); + const rest = new REST({ version: "10" }).setToken( + process.env.DISCORD_BOT_TOKEN, + ); console.log(`๐Ÿ“ Registering ${commands.length} slash commands...`); @@ -87,24 +104,24 @@ async function registerCommands() { console.log(`โœ… Successfully registered ${data.length} slash commands.`); } catch (error) { - console.error('โŒ Error registering commands:', error); + console.error("โŒ Error registering commands:", error); } } // Login and register commands client.login(process.env.DISCORD_BOT_TOKEN); -client.once('ready', async () => { +client.once("ready", async () => { await registerCommands(); }); // Error handling -process.on('unhandledRejection', error => { - console.error('โŒ Unhandled Promise Rejection:', error); +process.on("unhandledRejection", (error) => { + console.error("โŒ Unhandled Promise Rejection:", error); }); -process.on('uncaughtException', error => { - console.error('โŒ Uncaught Exception:', error); +process.on("uncaughtException", (error) => { + console.error("โŒ Uncaught Exception:", error); process.exit(1); }); diff --git a/discord-bot/commands/profile.js b/discord-bot/commands/profile.js index 314d96c9..035f251f 100644 --- a/discord-bot/commands/profile.js +++ b/discord-bot/commands/profile.js @@ -1,75 +1,92 @@ -const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); +const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); module.exports = { data: new SlashCommandBuilder() - .setName('profile') - .setDescription('View your AeThex profile in Discord'), + .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) + .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.'); - + .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) + .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.'); - + .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: '๐Ÿ’ป', + 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 }, + .setColor(0x7289da) + .setTitle(`${profile.full_name || "AeThex User"}'s Profile`) + .setThumbnail( + profile.avatar_url || "https://aethex.dev/placeholder.svg", ) .addFields( - { name: '๐Ÿ”— Links', value: `[Visit Full Profile](https://aethex.dev/creators/${profile.username})` }, + { + 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 }, ) - .setFooter({ text: 'AeThex | Your Web3 Creator Hub' }); + .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); + console.error("Profile command error:", error); const embed = new EmbedBuilder() - .setColor(0xFF0000) - .setTitle('โŒ Error') - .setDescription('Failed to fetch profile. Please try again.'); - + .setColor(0xff0000) + .setTitle("โŒ Error") + .setDescription("Failed to fetch profile. Please try again."); + await interaction.editReply({ embeds: [embed] }); } }, diff --git a/discord-bot/commands/set-realm.js b/discord-bot/commands/set-realm.js index 787eefc9..57573d48 100644 --- a/discord-bot/commands/set-realm.js +++ b/discord-bot/commands/set-realm.js @@ -1,42 +1,61 @@ -const { SlashCommandBuilder, EmbedBuilder, StringSelectMenuBuilder, ActionRowBuilder } = require('discord.js'); +const { + SlashCommandBuilder, + EmbedBuilder, + StringSelectMenuBuilder, + ActionRowBuilder, +} = require("discord.js"); 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' }, + { 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'), + .setName("set-realm") + .setDescription("Set your primary AeThex realm/arm"), 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) + .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.'); - + .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') + .setCustomId("select_realm") + .setPlaceholder("Choose your primary realm") .addOptions( - REALMS.map(realm => ({ + REALMS.map((realm) => ({ label: realm.label, description: realm.description, value: realm.value, @@ -47,48 +66,60 @@ module.exports = { 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' }, - ); + .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 }); + 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 => { + collector.on("collect", async (i) => { const selectedRealm = i.values[0]; await supabase - .from('discord_links') + .from("discord_links") .update({ primary_arm: selectedRealm }) - .eq('discord_id', interaction.user.id); + .eq("discord_id", interaction.user.id); - const realm = REALMS.find(r => r.value === selectedRealm); + const realm = REALMS.find((r) => r.value === selectedRealm); const confirmEmbed = new EmbedBuilder() - .setColor(0x00FF00) - .setTitle('โœ… Realm Set') - .setDescription(`Your primary realm is now **${realm.label}**\n\nYou'll be assigned the corresponding Discord role.`); + .setColor(0x00ff00) + .setTitle("โœ… Realm Set") + .setDescription( + `Your primary realm is now **${realm.label}**\n\nYou'll be assigned the corresponding Discord role.`, + ); await i.update({ embeds: [confirmEmbed], components: [] }); }); - collector.on('end', collected => { + collector.on("end", (collected) => { if (collected.size === 0) { - interaction.editReply({ content: 'Realm selection timed out.', components: [] }); + interaction.editReply({ + content: "Realm selection timed out.", + components: [], + }); } }); } catch (error) { - console.error('Set-realm command error:', error); + console.error("Set-realm command error:", error); const embed = new EmbedBuilder() - .setColor(0xFF0000) - .setTitle('โŒ Error') - .setDescription('Failed to update realm. Please try again.'); - + .setColor(0xff0000) + .setTitle("โŒ Error") + .setDescription("Failed to update realm. Please try again."); + await interaction.editReply({ embeds: [embed] }); } }, diff --git a/discord-bot/commands/unlink.js b/discord-bot/commands/unlink.js index 7c88dab8..ac06d2a5 100644 --- a/discord-bot/commands/unlink.js +++ b/discord-bot/commands/unlink.js @@ -1,48 +1,49 @@ -const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); +const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); module.exports = { data: new SlashCommandBuilder() - .setName('unlink') - .setDescription('Unlink your Discord account from AeThex'), + .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) + .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.'); - + .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') + .from("discord_links") .delete() - .eq('discord_id', interaction.user.id); + .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') + 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) { @@ -54,18 +55,20 @@ module.exports = { } const embed = new EmbedBuilder() - .setColor(0x00FF00) - .setTitle('โœ… Account Unlinked') - .setDescription('Your Discord account has been unlinked from AeThex.\nAll associated roles have been removed.'); + .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); + console.error("Unlink command error:", error); const embed = new EmbedBuilder() - .setColor(0xFF0000) - .setTitle('โŒ Error') - .setDescription('Failed to unlink account. Please try again.'); - + .setColor(0xff0000) + .setTitle("โŒ Error") + .setDescription("Failed to unlink account. Please try again."); + await interaction.editReply({ embeds: [embed] }); } }, diff --git a/discord-bot/commands/verify-role.js b/discord-bot/commands/verify-role.js index 7508397c..1b7e6b9f 100644 --- a/discord-bot/commands/verify-role.js +++ b/discord-bot/commands/verify-role.js @@ -1,71 +1,96 @@ -const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); +const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); module.exports = { data: new SlashCommandBuilder() - .setName('verify-role') - .setDescription('Check your AeThex-assigned Discord roles'), + .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) + .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.'); - + .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) + .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'); + .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 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') + .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' }, + { + 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' }); + .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); + console.error("Verify-role command error:", error); const embed = new EmbedBuilder() - .setColor(0xFF0000) - .setTitle('โŒ Error') - .setDescription('Failed to verify roles. Please try again.'); - + .setColor(0xff0000) + .setTitle("โŒ Error") + .setDescription("Failed to verify roles. Please try again."); + await interaction.editReply({ embeds: [embed] }); } }, diff --git a/discord-bot/commands/verify.js b/discord-bot/commands/verify.js index f0436521..d5789079 100644 --- a/discord-bot/commands/verify.js +++ b/discord-bot/commands/verify.js @@ -1,35 +1,46 @@ -const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); +const { + SlashCommandBuilder, + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, +} = require("discord.js"); module.exports = { data: new SlashCommandBuilder() - .setName('verify') - .setDescription('Link your Discord account to your AeThex account'), + .setName("verify") + .setDescription("Link your Discord account to your AeThex account"), async execute(interaction, supabase) { await interaction.deferReply({ ephemeral: true }); try { const { data: existingLink } = await supabase - .from('discord_links') - .select('*') - .eq('discord_id', interaction.user.id) + .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})`); - + .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 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({ + await supabase.from("discord_verifications").insert({ discord_id: interaction.user.id, verification_code: verificationCode, expires_at: expiresAt.toISOString(), @@ -38,30 +49,34 @@ module.exports = { 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}\`` }, + .setColor(0x7289da) + .setTitle("๐Ÿ”— Link Your AeThex Account") + .setDescription( + "Click the button below to link your Discord account to AeThex.", ) - .setFooter({ text: 'Your security code will expire in 15 minutes' }); + .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') + .setLabel("Link Account") .setStyle(ButtonStyle.Link) .setURL(verifyUrl), ); await interaction.editReply({ embeds: [embed], components: [row] }); } catch (error) { - console.error('Verify command error:', error); + console.error("Verify command error:", error); const embed = new EmbedBuilder() - .setColor(0xFF0000) - .setTitle('โŒ Error') - .setDescription('Failed to generate verification code. Please try again.'); - + .setColor(0xff0000) + .setTitle("โŒ Error") + .setDescription( + "Failed to generate verification code. Please try again.", + ); + await interaction.editReply({ embeds: [embed] }); } }, diff --git a/docs/GAME-INTEGRATION-GUIDE.md b/docs/GAME-INTEGRATION-GUIDE.md index b72e074a..97d4b57c 100644 --- a/docs/GAME-INTEGRATION-GUIDE.md +++ b/docs/GAME-INTEGRATION-GUIDE.md @@ -16,6 +16,7 @@ This guide covers how to integrate AeThex authentication with game engines: Robl ## Overview AeThex provides a unified authentication system for games to: + - Authenticate players across different platforms - Link player accounts to Roblox, Ethereum wallets, and other providers - Store player profiles and achievements @@ -53,13 +54,13 @@ function AethexAuth:authenticate(playerId, playerName) player_name = playerName, platform = "PC" }) - + local response = game:GetService("HttpService"):PostAsync( API_BASE .. "/games/game-auth", body, Enum.HttpContentType.ApplicationJson ) - + local data = game:GetService("HttpService"):JSONDecode(response) return data end @@ -73,7 +74,7 @@ function AethexAuth:verifyToken(sessionToken) }), Enum.HttpContentType.ApplicationJson ) - + return game:GetService("HttpService"):JSONDecode(response) end @@ -88,7 +89,7 @@ local AethexAuth = require(game.ServerScriptService:WaitForChild("AethexAuth")) Players.PlayerAdded:Connect(function(player) local authResult = AethexAuth:authenticate(player.UserId, player.Name) - + if authResult.success then player:SetAttribute("AethexSessionToken", authResult.session_token) player:SetAttribute("AethexUserId", authResult.user_id) @@ -118,7 +119,7 @@ using System.Collections; public class AethexAuth : MonoBehaviour { private const string API_BASE = "https://aethex.dev/api"; - + [System.Serializable] public class AuthResponse { @@ -129,7 +130,7 @@ public class AethexAuth : MonoBehaviour public int expires_in; public string error; } - + public static IEnumerator AuthenticatePlayer( string playerId, string playerName, @@ -139,7 +140,7 @@ public class AethexAuth : MonoBehaviour $"{API_BASE}/games/game-auth", "POST" ); - + var requestBody = new AuthRequest { game = "unity", @@ -147,14 +148,14 @@ public class AethexAuth : MonoBehaviour player_name = playerName, platform = "PC" }; - + string jsonBody = JsonUtility.ToJson(requestBody); request.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(jsonBody)); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); - + yield return request.SendWebRequest(); - + if (request.result == UnityWebRequest.Result.Success) { var response = JsonUtility.FromJson(request.downloadHandler.text); @@ -165,7 +166,7 @@ public class AethexAuth : MonoBehaviour callback(new AuthResponse { error = request.error }); } } - + [System.Serializable] private class AuthRequest { @@ -186,10 +187,10 @@ public class GameManager : MonoBehaviour { string playerId = SystemInfo.deviceUniqueIdentifier; string playerName = "UnityPlayer_" + Random.Range(1000, 9999); - + StartCoroutine(AethexAuth.AuthenticatePlayer(playerId, playerName, OnAuthComplete)); } - + void OnAuthComplete(AethexAuth.AuthResponse response) { if (response.success) @@ -231,12 +232,12 @@ public: int32 ExpiresIn; FString Error; }; - + static void AuthenticatePlayer( const FString& PlayerId, const FString& PlayerName, TFunction OnComplete); - + private: static void OnAuthResponse( FHttpRequestPtr Request, @@ -259,30 +260,30 @@ void FAethexAuth::AuthenticatePlayer( TFunction OnComplete) { FHttpModule& HttpModule = FHttpModule::Get(); - TSharedRef Request = + TSharedRef Request = HttpModule.CreateRequest(); - + Request->SetURL(TEXT("https://aethex.dev/api/games/game-auth")); Request->SetVerb(TEXT("POST")); Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); - + // Create JSON body TSharedPtr JsonObject = MakeShareable(new FJsonObject()); JsonObject->SetStringField(TEXT("game"), TEXT("unreal")); JsonObject->SetStringField(TEXT("player_id"), PlayerId); JsonObject->SetStringField(TEXT("player_name"), PlayerName); JsonObject->SetStringField(TEXT("platform"), TEXT("PC")); - + FString OutputString; TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString); FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); - + Request->SetContentAsString(OutputString); - + Request->OnProcessRequestComplete().BindStatic( &FAethexAuth::OnAuthResponse, OnComplete); - + Request->ProcessRequest(); } @@ -293,12 +294,12 @@ void FAethexAuth::OnAuthResponse( TFunction OnComplete) { FAuthResponse AuthResponse; - + if (bWasSuccessful && Response.IsValid()) { TSharedPtr JsonObject; TSharedRef> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); - + if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid()) { AuthResponse.bSuccess = JsonObject->GetBoolField(TEXT("success")); @@ -312,7 +313,7 @@ void FAethexAuth::OnAuthResponse( { AuthResponse.Error = TEXT("Authentication request failed"); } - + OnComplete(AuthResponse); } ``` @@ -331,68 +332,68 @@ const API_BASE = "https://aethex.dev/api" func authenticate_player(player_id: String, player_name: String) -> Dictionary: var http_request = HTTPRequest.new() add_child(http_request) - + var url = API_BASE + "/games/game-auth" var headers = ["Content-Type: application/json"] - + var body = { "game": "godot", "player_id": player_id, "player_name": player_name, "platform": OS.get_name() } - + var response = http_request.request( url, headers, HTTPClient.METHOD_POST, JSON.stringify(body) ) - + if response != OK: return {"error": "Request failed"} - + var result = await http_request.request_completed - + if result[1] != 200: return {"error": "Authentication failed"} - + var response_data = JSON.parse_string(result[3].get_string_from_utf8()) http_request.queue_free() - + return response_data func verify_token(session_token: String) -> Dictionary: var http_request = HTTPRequest.new() add_child(http_request) - + var url = API_BASE + "/games/verify-token" var headers = ["Content-Type: application/json"] - + var body = { "session_token": session_token, "game": "godot" } - + var response = http_request.request( url, headers, HTTPClient.METHOD_POST, JSON.stringify(body) ) - + var result = await http_request.request_completed var response_data = JSON.parse_string(result[3].get_string_from_utf8()) http_request.queue_free() - + return response_data func _ready(): var player_id = OS.get_unique_id() var player_name = "GodotPlayer_" + str(randi_range(1000, 9999)) - + var auth_result = await authenticate_player(player_id, player_name) - + if auth_result.has("success") and auth_result["success"]: print("Authenticated as: ", auth_result["username"]) # Store token @@ -413,27 +414,29 @@ func _ready(): Authenticate a game player and create a session. **Request:** + ```json { - "game": "unity|unreal|godot|roblox|custom", - "player_id": "unique-player-id", - "player_name": "player-display-name", - "device_id": "optional-device-id", - "platform": "PC|Mobile|Console" + "game": "unity|unreal|godot|roblox|custom", + "player_id": "unique-player-id", + "player_name": "player-display-name", + "device_id": "optional-device-id", + "platform": "PC|Mobile|Console" } ``` **Response:** + ```json { - "success": true, - "session_token": "token-string", - "user_id": "uuid", - "username": "username", - "game": "unity", - "expires_in": 604800, - "api_base_url": "https://aethex.dev/api", - "docs_url": "https://docs.aethex.dev/game-integration" + "success": true, + "session_token": "token-string", + "user_id": "uuid", + "username": "username", + "game": "unity", + "expires_in": 604800, + "api_base_url": "https://aethex.dev/api", + "docs_url": "https://docs.aethex.dev/game-integration" } ``` @@ -442,24 +445,26 @@ Authenticate a game player and create a session. Verify a game session token and get player data. **Request:** + ```json { - "session_token": "token-string", - "game": "unity" + "session_token": "token-string", + "game": "unity" } ``` **Response:** + ```json { - "valid": true, - "user_id": "uuid", - "username": "username", - "email": "user@example.com", - "full_name": "Player Name", - "game": "unity", - "platform": "PC", - "expires_at": "2025-01-15T10:30:00Z" + "valid": true, + "user_id": "uuid", + "username": "username", + "email": "user@example.com", + "full_name": "Player Name", + "game": "unity", + "platform": "PC", + "expires_at": "2025-01-15T10:30:00Z" } ``` @@ -475,6 +480,7 @@ Verify a game session token and get player data. ## Support For issues or questions, visit: + - Docs: https://docs.aethex.dev - Discord: https://discord.gg/aethex - Email: support@aethex.tech