Reworked auth, added WS for imediate communication, cleaned up architecture.
This commit is contained in:
parent
19d7ee1dc5
commit
f93781931f
12 changed files with 541 additions and 187 deletions
9
.dockerignore
Normal file
9
.dockerignore
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
node_modules
|
||||
output/
|
||||
Dockerfile
|
||||
Dockerfile.dev
|
||||
docker-compose.yaml
|
||||
docker-compose.dev.yml
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
51
Dockerfile.dev
Normal file
51
Dockerfile.dev
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# --- Stage 1: Install node_modules ---
|
||||
FROM node:20-slim as dependencies
|
||||
|
||||
WORKDIR /opt/bot
|
||||
COPY package*.json ./
|
||||
RUN npm install --no-audit --no-fund
|
||||
|
||||
# --- Stage 2: Development Build ---
|
||||
FROM node:20-slim
|
||||
|
||||
# Install system dependencies for Discord and virtual display
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
curl ca-certificates sudo \
|
||||
# ── Discord runtime deps ──────────────────
|
||||
libatomic1 libnotify4 libnspr4 libnss3 libxss1 libgbm1 \
|
||||
libgconf-2-4 libxtst6 libgtk-3-0 \
|
||||
# ── GUI / audio / misc ───────────────────
|
||||
xvfb pulseaudio openbox \
|
||||
tigervnc-standalone-server tigervnc-common fonts-liberation \
|
||||
x11vnc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Download and install Discord
|
||||
WORKDIR /opt/preinstall
|
||||
RUN curl -L https://discord.com/api/download/stable?platform=linux -o discord.deb && \
|
||||
dpkg -i discord.deb || apt-get -f install -y
|
||||
|
||||
# Create a non-root user
|
||||
RUN useradd --create-home --shell /bin/bash bot && \
|
||||
adduser bot sudo && \
|
||||
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||
|
||||
# Set up the bot directory
|
||||
WORKDIR /opt/bot
|
||||
# Copy pre-installed node_modules from the 'dependencies' stage
|
||||
COPY --from=dependencies /opt/bot/node_modules ./node_modules
|
||||
# Copy entrypoint and set permissions
|
||||
COPY entry.sh .
|
||||
RUN chmod +x /opt/bot/entry.sh
|
||||
# The rest of the source code will be mounted as a volume
|
||||
|
||||
# Set ownership and user
|
||||
RUN chown -R bot:bot /opt/bot
|
||||
USER bot
|
||||
|
||||
# Set environment and entrypoint
|
||||
ENV DISPLAY=:99
|
||||
RUN mkdir -p /run/dbus
|
||||
ENTRYPOINT dbus-daemon --system --fork --nofork --address unix:/run/dbus/system_bus_socket & exec /opt/bot/entry.sh "$@"
|
||||
284
bot.js
284
bot.js
|
|
@ -4,15 +4,22 @@ import { Client as DiscordClient } from "discord.js-selfbot-v13";
|
|||
import express from "express";
|
||||
import session from "express-session";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import fetch from "node-fetch"; // Import fetch
|
||||
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 } =
|
||||
process.env;
|
||||
const {
|
||||
USER_TOKEN,
|
||||
BOT_CLIENT_ID,
|
||||
BOT_CLIENT_SECRET,
|
||||
BOT_REDIRECT_URI,
|
||||
COOKIE_SECRET,
|
||||
} = process.env;
|
||||
const WEB_PORT = 3000;
|
||||
// IMPORTANT: Replace with the Discord User IDs of people allowed to access the dashboard.
|
||||
const ADMIN_USER_IDS = ["339753362297847810"];
|
||||
const MAX_LOG_SIZE = 20;
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
|
@ -20,217 +27,122 @@ const __dirname = path.dirname(__filename);
|
|||
// --- IN-MEMORY STATE ---
|
||||
const activityLog = [];
|
||||
const actionsLog = [];
|
||||
const MAX_LOG_SIZE = 20;
|
||||
|
||||
const logItem = (logArray, item) => {
|
||||
logArray.unshift(item);
|
||||
if (logArray.length > MAX_LOG_SIZE) logArray.pop();
|
||||
};
|
||||
|
||||
// --- 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);
|
||||
app.set("trust proxy", 1); // Required for secure cookies if behind a proxy
|
||||
|
||||
app.use(
|
||||
session({
|
||||
secret: "a-very-secret-string-for-teto",
|
||||
secret: COOKIE_SECRET || "a-very-secret-string-for-teto-is-required",
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
secure: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// --- OAUTH2 AUTHENTICATION & DASHBOARD ROUTES ---
|
||||
app.get("/auth/discord", (req, res) => {
|
||||
const authUrl = `https://discord.com/api/oauth2/authorize?client_id=${BOT_CLIENT_ID}&redirect_uri=${encodeURIComponent(BOT_REDIRECT_URI)}&response_type=code&scope=identify`;
|
||||
res.redirect(authUrl);
|
||||
});
|
||||
|
||||
app.get("/auth/callback", async (req, res) => {
|
||||
const code = req.query.code;
|
||||
if (!code) return res.status(400).send("No code provided.");
|
||||
|
||||
try {
|
||||
const params = {
|
||||
client_id: BOT_CLIENT_ID,
|
||||
client_secret: BOT_CLIENT_SECRET,
|
||||
code,
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: BOT_REDIRECT_URI,
|
||||
};
|
||||
console.log("Attempting token exchange with params:", params);
|
||||
|
||||
const tokenResponse = await fetch("https://discord.com/api/oauth2/token", {
|
||||
method: "POST",
|
||||
body: new URLSearchParams(params),
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
});
|
||||
const tokenData = await tokenResponse.json();
|
||||
|
||||
if (tokenData.error) {
|
||||
console.error("Token Exchange Error:", tokenData); // Log the full error response
|
||||
return res
|
||||
.status(500)
|
||||
.send(
|
||||
`Error exchanging code for token. Details: ${JSON.stringify(tokenData)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const userResponse = await fetch("https://discord.com/api/users/@me", {
|
||||
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
||||
});
|
||||
const userData = await userResponse.json();
|
||||
|
||||
if (!ADMIN_USER_IDS.includes(userData.id)) {
|
||||
return res
|
||||
.status(403)
|
||||
.send("You are not authorized to view this dashboard.");
|
||||
}
|
||||
|
||||
req.session.user = {
|
||||
id: userData.id,
|
||||
username: userData.username,
|
||||
discriminator: userData.discriminator,
|
||||
avatar: userData.avatar,
|
||||
};
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
console.error("Error saving session:", err);
|
||||
return res
|
||||
.status(500)
|
||||
.send("An error occurred while saving the session.");
|
||||
}
|
||||
console.log("Session saved successfully, redirecting.");
|
||||
res.redirect("/");
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("OAuth2 Callback Error:", error);
|
||||
res.status(500).send("An error occurred during authentication.");
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/logout", (req, res, next) => {
|
||||
req.session.destroy((err) => {
|
||||
if (err) return next(err);
|
||||
res.redirect("/");
|
||||
});
|
||||
});
|
||||
|
||||
const checkAuth = (req, res, next) => {
|
||||
if (req.session.user) return next();
|
||||
res.redirect("/auth/discord");
|
||||
};
|
||||
|
||||
// --- STATIC ASSETS & ROUTES ---
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
app.use("/", authRouter);
|
||||
|
||||
app.get("/", checkAuth, (req, res) => {
|
||||
const systemResources = {
|
||||
memory: { percentage: 91, used: 7.3, total: 8 },
|
||||
vram: { percentage: 69, used: 5.5, total: 8 },
|
||||
avgResponse: 32,
|
||||
shutdown: "3d 0h",
|
||||
sessionEnd: "Jul 24, 2025, 07:21 PM",
|
||||
};
|
||||
res.render("index", {
|
||||
user: req.session.user,
|
||||
activityLog,
|
||||
actionsLog,
|
||||
systemResources,
|
||||
});
|
||||
});
|
||||
|
||||
// --- DISCORD BOT LOGIC (no changes here) ---
|
||||
// --- DISCORD BOT & SERVER INITIALIZATION ---
|
||||
(async () => {
|
||||
// Puppeteer setup (currently optional)
|
||||
puppeteer.use(StealthPlugin());
|
||||
|
||||
// This part is disabled for now but can be re-enabled if needed for screenshots
|
||||
/*
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: '/usr/bin/discord',
|
||||
headless: false,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--start-maximized'],
|
||||
defaultViewport: null,
|
||||
});
|
||||
const [page] = await browser.pages();
|
||||
console.log("Puppeteer has attached to Discord's browser.");
|
||||
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 });
|
||||
|
||||
client.once("ready", () => {
|
||||
console.log(`Selfbot ready as ${client.user.tag} (${client.user.id})`);
|
||||
// --- DYNAMIC EVENT HANDLER LOADING ---
|
||||
const eventsPath = path.join(__dirname, "src", "events");
|
||||
const eventFiles = fs
|
||||
.readdirSync(eventsPath)
|
||||
.filter((file) => file.endsWith(".js"));
|
||||
|
||||
// Add dummy data
|
||||
logItem(actionsLog, {
|
||||
message: "Responded to Alice in",
|
||||
channel: "#gaming",
|
||||
icon: "💬",
|
||||
});
|
||||
logItem(actionsLog, {
|
||||
message: "Joined Voice Chat",
|
||||
channel: "#general",
|
||||
icon: "🎤",
|
||||
});
|
||||
logItem(actionsLog, {
|
||||
message: "Analyzed image from Charlie in",
|
||||
channel: "#memes",
|
||||
icon: "👁️",
|
||||
});
|
||||
const context = {
|
||||
client,
|
||||
logItem,
|
||||
activityLog,
|
||||
actionsLog,
|
||||
MAX_LOG_SIZE,
|
||||
};
|
||||
|
||||
const now = new Date();
|
||||
logItem(activityLog, {
|
||||
message: `[${now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit" })}] Memory consolidation complete`,
|
||||
});
|
||||
now.setSeconds(now.getSeconds() - 4);
|
||||
logItem(activityLog, {
|
||||
message: `[${now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit" })}] Learning: Alice enjoys my French bread jokes`,
|
||||
});
|
||||
now.setSeconds(now.getSeconds() - 4);
|
||||
logItem(activityLog, {
|
||||
message: `[${now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit" })}] Updating conversation context in vector database...`,
|
||||
});
|
||||
now.setSeconds(now.getSeconds() - 4);
|
||||
logItem(activityLog, {
|
||||
message: `[${now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit" })}] Alice reacted with 😊 - response was well received`,
|
||||
});
|
||||
now.setSeconds(now.getSeconds() - 4);
|
||||
logItem(activityLog, {
|
||||
message: `[${now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit" })}] Monitoring user reactions and engagement...`,
|
||||
});
|
||||
});
|
||||
for (const file of eventFiles) {
|
||||
const filePath = path.join(eventsPath, file);
|
||||
const event = (await import(pathToFileURL(filePath))).default;
|
||||
|
||||
client.on("messageCreate", (msg) => {
|
||||
if (msg.author.id === client.user.id) return;
|
||||
|
||||
const message = `[${new Date().toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit" })}] Message from ${msg.author.tag} in #${msg.channel.name || "DM"}: "${msg.content.slice(0, 50)}..."`;
|
||||
logItem(activityLog, { message });
|
||||
|
||||
// Example of logging a bot action
|
||||
if (msg.content.toLowerCase().includes("hello teto")) {
|
||||
msg.channel.send("Hello there!");
|
||||
logItem(actionsLog, {
|
||||
message: `Responded to ${msg.author.tag}`,
|
||||
channel: `#${msg.channel.name || "DM"}`,
|
||||
icon: "💬",
|
||||
});
|
||||
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
|
||||
|
||||
client.on("disconnect", () => {
|
||||
const message = `[${new Date().toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit" })}] Bot disconnected.`;
|
||||
logItem(activityLog, { message });
|
||||
});
|
||||
|
||||
client.login(USER_TOKEN);
|
||||
|
||||
app.listen(WEB_PORT, () => {
|
||||
console.log(`Dashboard server running on https://teto.getsilly.org`);
|
||||
});
|
||||
try {
|
||||
await client.login(USER_TOKEN);
|
||||
} catch (error) {
|
||||
console.error("Failed to login to Discord. Check your USER_TOKEN.", error);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
32
docker-compose.dev.yml
Normal file
32
docker-compose.dev.yml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
version: "3.8"
|
||||
|
||||
services:
|
||||
teto_ai:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
container_name: teto_ai_dev
|
||||
cap_add:
|
||||
- SYS_ADMIN
|
||||
environment:
|
||||
# supply the user token at runtime:
|
||||
USER_TOKEN: "${USER_TOKEN}"
|
||||
BOT_CLIENT_ID: "${BOT_CLIENT_ID}"
|
||||
BOT_CLIENT_SECRET: "${BOT_CLIENT_SECRET}"
|
||||
BOT_REDIRECT_URI: "https://teto.getsilly.org/auth/callback"
|
||||
volumes:
|
||||
# live-peek folder so you can grab screenshots outside the container
|
||||
- ./output:/tmp/output
|
||||
# Mount the current directory into the container for live-reloading
|
||||
- .:/opt/bot
|
||||
# Use a named volume to keep the container's node_modules from being overwritten
|
||||
- node_modules:/opt/bot/node_modules
|
||||
ports:
|
||||
- "5901:5901" # VNC
|
||||
- "3000:3000" # optional HTTP server if you add one
|
||||
tmpfs:
|
||||
- /tmp:size=512M
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
node_modules:
|
||||
0
entry.sh
Normal file → Executable file
0
entry.sh
Normal file → Executable file
94
public/socket_logic.js
Normal file
94
public/socket_logic.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
let socket;
|
||||
let heartbeatInterval;
|
||||
|
||||
function connect() {
|
||||
// Use wss:// for secure connections (in production) or ws:// for local
|
||||
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
|
||||
socket = new WebSocket(`${protocol}://${window.location.host}`);
|
||||
|
||||
socket.onopen = () => {
|
||||
console.log("WebSocket connection established.");
|
||||
// Start a heartbeat to keep the connection alive
|
||||
clearInterval(heartbeatInterval); // Clear previous interval if it exists
|
||||
heartbeatInterval = setInterval(() => {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
// Sending a simple ping message. The server's 'ws' library will handle this
|
||||
// automatically to keep the connection from being marked as idle by proxies.
|
||||
socket.send(JSON.stringify({ type: "ping" }));
|
||||
}
|
||||
}, 30000); // every 30 seconds
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
// The server's 'ws' library might automatically respond to pings with pongs.
|
||||
// We can safely ignore them, along with our own pings if they get echoed.
|
||||
if (event.data.includes("ping") || event.data.includes("pong")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
// --- This is our client-side message router ---
|
||||
switch (data.type) {
|
||||
case "activityLogUpdate":
|
||||
handleActivityLogUpdate(data.payload);
|
||||
break;
|
||||
case "systemResourcesUpdate":
|
||||
handleSystemResourceUpdate(data.payload);
|
||||
break;
|
||||
// Add more cases here for other real-time updates
|
||||
default:
|
||||
console.log("Received unknown message type:", data.type);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
console.log(
|
||||
"WebSocket connection closed. Attempting to reconnect in 3 seconds...",
|
||||
);
|
||||
clearInterval(heartbeatInterval); // Stop the heartbeat when the connection is lost
|
||||
// **This is the key part:** Wait 3 seconds, then try to connect again.
|
||||
setTimeout(connect, 3000);
|
||||
};
|
||||
|
||||
socket.onerror = (error) => {
|
||||
console.error("WebSocket Error:", error);
|
||||
// The 'onclose' event will fire automatically after an error,
|
||||
// which will then trigger our reconnection logic.
|
||||
socket.close(); // Ensure the socket is closed to trigger onclose
|
||||
};
|
||||
}
|
||||
|
||||
function handleActivityLogUpdate(payload) {
|
||||
const activityLog = document.getElementById("activity-log");
|
||||
if (!activityLog || !payload.message) return;
|
||||
|
||||
const newLogItem = document.createElement("p");
|
||||
const styledMessage = payload.message.replace(
|
||||
/(\[[^\]]+\])/,
|
||||
'<span class="timestamp">$1</span>',
|
||||
);
|
||||
newLogItem.innerHTML = styledMessage;
|
||||
|
||||
activityLog.prepend(newLogItem);
|
||||
|
||||
while (activityLog.children.length > 20) {
|
||||
activityLog.lastChild.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function handleSystemResourceUpdate(payload) {
|
||||
// Conceptual: Find the memory percentage element and update its text and style
|
||||
const memPercentage = document.querySelector(".metric-header strong"); // Simplified selector
|
||||
const memProgressBar = document.querySelector(".progress-bar.memory"); // Simplified selector
|
||||
|
||||
if (memPercentage)
|
||||
memPercentage.textContent = `${payload.memory.percentage}%`;
|
||||
if (memProgressBar)
|
||||
memProgressBar.style.width = `${payload.memory.percentage}%`;
|
||||
|
||||
console.log("System resources updated!", payload);
|
||||
}
|
||||
|
||||
// --- Initial Connection ---
|
||||
connect();
|
||||
127
src/controllers/authController.js
Normal file
127
src/controllers/authController.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import fetch from "node-fetch";
|
||||
|
||||
const authController = {
|
||||
// --- Route Handlers ---
|
||||
|
||||
redirectToDiscord: (req, res) => {
|
||||
const { BOT_CLIENT_ID, BOT_REDIRECT_URI } = req.app.locals.config;
|
||||
const authUrl = `https://discord.com/api/oauth2/authorize?client_id=${BOT_CLIENT_ID}&redirect_uri=${encodeURIComponent(
|
||||
BOT_REDIRECT_URI,
|
||||
)}&response_type=code&scope=identify`;
|
||||
res.redirect(authUrl);
|
||||
},
|
||||
|
||||
handleCallback: async (req, res) => {
|
||||
const code = req.query.code;
|
||||
if (!code) {
|
||||
return res.status(400).send("No code provided.");
|
||||
}
|
||||
|
||||
const {
|
||||
BOT_CLIENT_ID,
|
||||
BOT_CLIENT_SECRET,
|
||||
BOT_REDIRECT_URI,
|
||||
ADMIN_USER_IDS,
|
||||
} = req.app.locals.config;
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
client_id: BOT_CLIENT_ID,
|
||||
client_secret: BOT_CLIENT_SECRET,
|
||||
code,
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: BOT_REDIRECT_URI,
|
||||
});
|
||||
|
||||
const tokenResponse = await fetch(
|
||||
"https://discord.com/api/oauth2/token",
|
||||
{
|
||||
method: "POST",
|
||||
body: params,
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
},
|
||||
);
|
||||
const tokenData = await tokenResponse.json();
|
||||
|
||||
if (tokenData.error) {
|
||||
console.error("Token Exchange Error:", tokenData);
|
||||
return res
|
||||
.status(500)
|
||||
.send(
|
||||
`Error exchanging code for token. Details: ${JSON.stringify(
|
||||
tokenData,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const userResponse = await fetch("https://discord.com/api/users/@me", {
|
||||
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
||||
});
|
||||
const userData = await userResponse.json();
|
||||
|
||||
if (!ADMIN_USER_IDS.includes(userData.id)) {
|
||||
return res
|
||||
.status(403)
|
||||
.send("You are not authorized to view this dashboard.");
|
||||
}
|
||||
|
||||
req.session.user = {
|
||||
id: userData.id,
|
||||
username: userData.username,
|
||||
discriminator: userData.discriminator,
|
||||
avatar: userData.avatar,
|
||||
};
|
||||
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
console.error("Error saving session:", err);
|
||||
return res
|
||||
.status(500)
|
||||
.send("An error occurred while saving the session.");
|
||||
}
|
||||
res.redirect("/");
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("OAuth2 Callback Error:", error);
|
||||
res.status(500).send("An error occurred during authentication.");
|
||||
}
|
||||
},
|
||||
|
||||
logout: (req, res, next) => {
|
||||
req.session.destroy((err) => {
|
||||
if (err) {
|
||||
console.error("Error destroying session:", err);
|
||||
return next(err);
|
||||
}
|
||||
res.redirect("/");
|
||||
});
|
||||
},
|
||||
|
||||
renderDashboard: (req, res) => {
|
||||
const { activityLog, actionsLog } = req.app.locals.data;
|
||||
const systemResources = {
|
||||
memory: { percentage: 91, used: 7.3, total: 8 },
|
||||
vram: { percentage: 69, used: 5.5, total: 8 },
|
||||
avgResponse: 32,
|
||||
shutdown: "3d 0h",
|
||||
sessionEnd: "Jul 24, 2025, 07:21 PM",
|
||||
};
|
||||
res.render("index", {
|
||||
user: req.session.user,
|
||||
activityLog,
|
||||
actionsLog,
|
||||
systemResources,
|
||||
});
|
||||
},
|
||||
|
||||
// --- Middleware ---
|
||||
|
||||
checkAuth: (req, res, next) => {
|
||||
if (req.session.user) {
|
||||
return next();
|
||||
}
|
||||
res.redirect("/auth/discord");
|
||||
},
|
||||
};
|
||||
|
||||
export default authController;
|
||||
8
src/events/disconnect.js
Normal file
8
src/events/disconnect.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// src/events/disconnect.js
|
||||
export default {
|
||||
name: "disconnect",
|
||||
execute({ logItem, activityLog }) {
|
||||
const message = `[${new Date().toLocaleTimeString()}] Bot disconnected.`;
|
||||
logItem(activityLog, { message });
|
||||
},
|
||||
};
|
||||
22
src/events/messageCreate.js
Normal file
22
src/events/messageCreate.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// src/events/messageCreate.js
|
||||
export default {
|
||||
name: "messageCreate",
|
||||
execute({ client, logItem, activityLog, actionsLog, MAX_LOG_SIZE }, msg) {
|
||||
if (msg.author.id === client.user.id) return;
|
||||
const message = `[${new Date().toLocaleTimeString()}] Message from ${
|
||||
msg.author.tag
|
||||
} in #${msg.channel.name || "DM"}: "${msg.content.slice(0, 50)}..."`;
|
||||
logItem(activityLog, { message }); // This will now also broadcast
|
||||
|
||||
if (msg.content.toLowerCase().includes("hello teto")) {
|
||||
msg.channel.send("Hello there!");
|
||||
const action = {
|
||||
message: `Responded to ${msg.author.tag}`,
|
||||
channel: `#${msg.channel.name || "DM"}`,
|
||||
icon: "💬",
|
||||
};
|
||||
actionsLog.unshift(action);
|
||||
if (actionsLog.length > MAX_LOG_SIZE) actionsLog.pop();
|
||||
}
|
||||
},
|
||||
};
|
||||
23
src/events/ready.js
Normal file
23
src/events/ready.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export default {
|
||||
name: "ready",
|
||||
once: true,
|
||||
execute({ client, logItem, activityLog, actionsLog }) {
|
||||
console.log(`Selfbot ready as ${client.user.tag} (${client.user.id})`);
|
||||
// Add dummy data for demonstration purposes
|
||||
actionsLog.unshift({
|
||||
message: "Responded to Alice in",
|
||||
channel: "#gaming",
|
||||
icon: "💬",
|
||||
});
|
||||
actionsLog.unshift({
|
||||
message: "Joined Voice Chat",
|
||||
channel: "#general",
|
||||
icon: "🎤",
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
logItem(activityLog, {
|
||||
message: `[${now.toLocaleTimeString()}] System initialized and connected.`,
|
||||
});
|
||||
},
|
||||
};
|
||||
16
src/routes/authRoutes.js
Normal file
16
src/routes/authRoutes.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import express from "express";
|
||||
import authController from "../controllers/authController.js";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// --- Authentication Routes ---
|
||||
// These routes handle the OAuth2 flow with Discord.
|
||||
router.get("/auth/discord", authController.redirectToDiscord);
|
||||
router.get("/auth/callback", authController.handleCallback);
|
||||
router.get("/logout", authController.logout);
|
||||
|
||||
// --- Dashboard Route ---
|
||||
// The main dashboard route is protected by the `checkAuth` middleware.
|
||||
router.get("/", authController.checkAuth, authController.renderDashboard);
|
||||
|
||||
export default router;
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
<div class="widget">
|
||||
<h3>Recent Actions <span class="timeframe">12 in last hour</span></h3>
|
||||
<div class="actions-box">
|
||||
<% let-i = 0; %>
|
||||
<% let i = 0; %>
|
||||
<% actionsLog.forEach(action => { %>
|
||||
<% const time = i === 0 ? '5 min ago' : i === 1 ? '12 min ago' : '30 min ago'; i++; %>
|
||||
<div class="action-item">
|
||||
|
|
@ -110,5 +110,65 @@
|
|||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script>
|
||||
// 1. Establish the connection
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const socket = new WebSocket(`${protocol}://${window.location.host}`);
|
||||
|
||||
function connectWS() {
|
||||
socket.onopen = () => {
|
||||
console.log('WebSocket connection established');
|
||||
};
|
||||
}
|
||||
connectWS();
|
||||
|
||||
// 2. Listen for messages from the server
|
||||
socket.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
// --- This is our client-side message router ---
|
||||
switch (data.type) {
|
||||
case 'activityLogUpdate':
|
||||
handleActivityLogUpdate(data.payload);
|
||||
break;
|
||||
case 'systemResourcesUpdate':
|
||||
handleSystemResourceUpdate(data.payload);
|
||||
break;
|
||||
// Add more cases here for other real-time updates
|
||||
default:
|
||||
console.log('Received unknown message type:', data.type);
|
||||
}
|
||||
};
|
||||
|
||||
function handleActivityLogUpdate(payload) {
|
||||
const activityLog = document.getElementById('activity-log');
|
||||
if (!activityLog || !payload.message) return;
|
||||
|
||||
const newLogItem = document.createElement('p');
|
||||
const styledMessage = payload.message.replace(/(\[[^\]]+\])/, '<span class="timestamp">$1</span>');
|
||||
newLogItem.innerHTML = styledMessage;
|
||||
|
||||
activityLog.prepend(newLogItem);
|
||||
|
||||
while (activityLog.children.length > 20) {
|
||||
activityLog.lastChild.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function handleSystemResourceUpdate(payload) {
|
||||
// Conceptual: Find the memory percentage element and update its text and style
|
||||
const memPercentage = document.querySelector('.metric-header strong'); // Simplified selector
|
||||
const memProgressBar = document.querySelector('.progress-bar.memory'); // Simplified selector
|
||||
|
||||
if(memPercentage) memPercentage.textContent = `${payload.memory.percentage}%`;
|
||||
if(memProgressBar) memProgressBar.style.width = `${payload.memory.percentage}%`;
|
||||
|
||||
console.log('System resources updated!', payload);
|
||||
}
|
||||
|
||||
socket.onclose = () => {
|
||||
console.log('WebSocket connection closed. Attempting to reconnect...');
|
||||
connectWS();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in a new issue