import puppeteer from "puppeteer-extra"; import StealthPlugin from "puppeteer-extra-plugin-stealth"; import { Client as DiscordClient } from "discord.js-selfbot-v13"; import express from "express"; import session from "express-session"; import path from "path"; import { fileURLToPath, pathToFileURL } from "url"; import { WebSocketServer } from "ws"; import authRouter from "./src/routes/authRoutes.js"; import fs from "fs"; // --- CONFIGURATION --- const { USER_TOKEN, BOT_CLIENT_ID, BOT_CLIENT_SECRET, BOT_REDIRECT_URI, COOKIE_SECRET, } = process.env; const WEB_PORT = 3000; const ADMIN_USER_IDS = ["339753362297847810"]; const MAX_LOG_SIZE = 20; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // --- IN-MEMORY STATE --- const activityLog = []; const actionsLog = []; // --- WEB SERVER SETUP --- const app = express(); // Make config and data available to routers/controllers via app.locals app.locals.config = { BOT_CLIENT_ID, BOT_CLIENT_SECRET, BOT_REDIRECT_URI, ADMIN_USER_IDS, }; app.locals.data = { activityLog, actionsLog, }; app.set("view engine", "ejs"); app.set("views", path.join(__dirname, "views")); app.set("trust proxy", 1); // Required for secure cookies if behind a proxy app.use( session({ secret: COOKIE_SECRET || "a-very-secret-string-for-teto-is-required", resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV === "production", httpOnly: true, sameSite: "lax", }, }), ); // --- STATIC ASSETS & ROUTES --- app.use(express.static(path.join(__dirname, "public"))); app.use("/", authRouter); // --- DISCORD BOT & SERVER INITIALIZATION --- (async () => { // Puppeteer setup (currently optional) puppeteer.use(StealthPlugin()); /* const browser = await puppeteer.launch({ headless: false }); // ... more puppeteer logic if needed */ // Start the web server AND capture the HTTP server instance it creates const server = app.listen(WEB_PORT, () => { console.log(`Dashboard server running on http://localhost:${WEB_PORT}`); }); // Create the WebSocket server and attach it to our existing HTTP server const wss = new WebSocketServer({ server }); // Function to broadcast data to all connected WebSocket clients const broadcast = (data) => { // The 'wss.clients' property is a Set of all connected clients wss.clients.forEach((client) => { // Check if the connection is still open before sending if (client.readyState === client.OPEN) { // WebSocket only sends strings, so we stringify our object client.send(JSON.stringify(data)); } }); }; const logItem = (logArray, item) => { logArray.unshift(item); if (logArray.length > MAX_LOG_SIZE) logArray.pop(); if (logArray === activityLog) { broadcast({ type: "activityLogUpdate", payload: item }); } }; wss.on("connection", (ws) => { console.log("Dashboard client connected via WebSocket."); ws.on("close", () => { console.log("Dashboard client disconnected."); }); }); const client = new DiscordClient({ checkUpdate: false }); // --- DYNAMIC EVENT HANDLER LOADING --- const eventsPath = path.join(__dirname, "src", "events"); const eventFiles = fs .readdirSync(eventsPath) .filter((file) => file.endsWith(".js")); const context = { client, logItem, activityLog, actionsLog, MAX_LOG_SIZE, }; for (const file of eventFiles) { const filePath = path.join(eventsPath, file); const event = (await import(pathToFileURL(filePath))).default; if (event.name && event.execute) { if (event.once) { client.once(event.name, (...args) => event.execute(context, ...args)); } else { client.on(event.name, (...args) => event.execute(context, ...args)); } } } // End dynamic event handler loading try { await client.login(USER_TOKEN); } catch (error) { console.error("Failed to login to Discord. Check your USER_TOKEN.", error); process.exit(1); } })();