UNPKG

webpresence

Version:

Discord Rich Presence for websites - Show your browsing activity in Discord

494 lines (490 loc) 15.8 kB
import { spawn, execSync } from 'child_process'; import fs from 'fs'; import path from 'path'; import os from 'os'; import chalk from 'chalk'; import { fileURLToPath } from 'url'; var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); function formatTimestamp() { return (/* @__PURE__ */ new Date()).toISOString(); } __name(formatTimestamp, "formatTimestamp"); function formatLogMessage(level, message, meta) { const timestamp = formatTimestamp(); let logMessage = `[${timestamp}] [${level}] ${message}`; if (meta) { try { if (typeof meta === "string") { logMessage += ` - ${meta}`; } else { logMessage += ` - ${JSON.stringify(meta)}`; } } catch (error) { logMessage += ` - [Error serializing metadata: ${error}]`; } } return logMessage; } __name(formatLogMessage, "formatLogMessage"); var isRunningAsCLI = process.argv.length > 1 && process.argv[1].includes("cli"); var verbose = !isRunningAsCLI; var logger = { error: /* @__PURE__ */ __name((message, meta) => { if (isRunningAsCLI) { console.error(chalk.red(`ERROR: ${message}`)); if (verbose && meta) { console.error(chalk.gray(JSON.stringify(meta, null, 2))); } } else { console.error(formatLogMessage("ERROR" /* ERROR */, message, meta)); } }, "error"), warn: /* @__PURE__ */ __name((message, meta) => { if (isRunningAsCLI) { if (verbose || message.includes("Discord") || message.includes("connection")) { console.warn(chalk.yellow(`WARNING: ${message}`)); if (verbose && meta) { console.warn(chalk.gray(JSON.stringify(meta, null, 2))); } } } else { console.warn(formatLogMessage("WARN" /* WARN */, message, meta)); } }, "warn"), info: /* @__PURE__ */ __name((message, meta) => { if (isRunningAsCLI) { const isImportant = message.includes("Server") || message.includes("Discord") || message.includes("connected") || message.includes("presence"); if (verbose || isImportant) { console.log(chalk.blue(message)); if (verbose && meta) { console.log(chalk.gray(JSON.stringify(meta, null, 2))); } } } else { console.log(formatLogMessage("INFO" /* INFO */, message, meta)); } }, "info"), debug: /* @__PURE__ */ __name((message, meta) => { if (process.env.DEBUG) { if (isRunningAsCLI && verbose) { console.log(chalk.gray(`DEBUG: ${message}`)); if (meta) { console.log(chalk.gray(JSON.stringify(meta, null, 2))); } } else if (!isRunningAsCLI) { console.log(formatLogMessage("DEBUG" /* DEBUG */, message, meta)); } } }, "debug"), // CLI-specific methods for better UX success: /* @__PURE__ */ __name((message) => { console.log(chalk.green(message)); }, "success"), important: /* @__PURE__ */ __name((message) => { console.log(chalk.cyan(message)); }, "important"), // Control verbosity setVerbose: /* @__PURE__ */ __name((v) => { verbose = v; }, "setVerbose"), isVerbose: /* @__PURE__ */ __name(() => verbose, "isVerbose") }; // src/config/index.ts var defaultConfig = { // Discord RPC configuration discord: { clientId: "1370122815273046117", // Application ID (not logged for privacy) reconnectAttempts: 0, maxReconnectAttempts: 10 }, // Server configuration server: { port: 8874, activityTimeout: 18e4, // 3 minutes timeout for client activity (4x the heartbeat interval) inactiveCheckInterval: 15e3 // Check for inactive clients every 15 seconds }, // Presence configuration presence: { enabled: true, defaultPrefix: "Viewing", // Default prefix for details text creditText: "by Utkarsh", // Credit text to show in state buttons: [ { label: "GitHub Repository", url: "https://github.com/utkarshthedev/WebPresence" }, { label: "Follow on Twitter", url: "https://twitter.com/utkarshthedev" } ], largeImageKey: "web", smallImageKey: "me", smallImageText: "Utkarsh Tiwari", // Browser-specific icons browserIcons: { chrome: { iconKey: "chrome", text: "Google Chrome" }, firefox: { iconKey: "firefox", text: "Mozilla Firefox" }, default: { iconKey: "me", text: "Utkarsh Tiwari" } } } }; var runtimeConfig = { ...defaultConfig, // User preferences that can be customized userPreferences: { prefixText: defaultConfig.presence.defaultPrefix, disabledSites: [], // Sites where presence should be disabled alwaysEnabledSites: [], // Sites where presence should always be shown continuousTimer: true // Keep timer running when switching tabs (enabled by default) } }; var config = { // Get the current configuration get: /* @__PURE__ */ __name(() => runtimeConfig, "get"), // Get Discord configuration getDiscord: /* @__PURE__ */ __name(() => runtimeConfig.discord, "getDiscord"), // Get server configuration getServer: /* @__PURE__ */ __name(() => runtimeConfig.server, "getServer"), // Get presence configuration getPresence: /* @__PURE__ */ __name(() => runtimeConfig.presence, "getPresence"), // Get user preferences getUserPreferences: /* @__PURE__ */ __name(() => runtimeConfig.userPreferences, "getUserPreferences"), // Update user preferences updateUserPreferences: /* @__PURE__ */ __name((preferences) => { runtimeConfig.userPreferences = { ...runtimeConfig.userPreferences, ...preferences }; return runtimeConfig.userPreferences; }, "updateUserPreferences"), // Reset user preferences to defaults resetUserPreferences: /* @__PURE__ */ __name(() => { runtimeConfig.userPreferences.prefixText = defaultConfig.presence.defaultPrefix; runtimeConfig.userPreferences.disabledSites = []; runtimeConfig.userPreferences.alwaysEnabledSites = []; runtimeConfig.userPreferences.continuousTimer = true; return runtimeConfig.userPreferences; }, "resetUserPreferences") }; // src/utils/daemon.ts var DAEMON_DIR = path.join(os.homedir(), ".webpresence"); var PID_FILE = path.join(DAEMON_DIR, "webpresence.pid"); var LOG_FILE = path.join(DAEMON_DIR, "webpresence.log"); function ensureDaemonDir() { if (!fs.existsSync(DAEMON_DIR)) { try { fs.mkdirSync(DAEMON_DIR, { recursive: true }); } catch (error) { logger.error(`Failed to create daemon directory: ${error.message}`); throw error; } } } __name(ensureDaemonDir, "ensureDaemonDir"); async function startDaemon(options = {}) { ensureDaemonDir(); if (isDaemonRunning()) { return { success: false, message: "Daemon is already running" }; } try { const args = ["start"]; if (options.port) { args.push("--port", options.port.toString()); } fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Starting WebPresence daemon ` ); const __filename2 = fileURLToPath(import.meta.url); const __dirname2 = path.dirname(__filename2); const cliPath = path.resolve(__dirname2, "../../dist/cli.js"); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] CLI path: ${cliPath} ` ); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Starting daemon with standalone script ` ); const __filename22 = fileURLToPath(import.meta.url); const __dirname22 = path.dirname(__filename22); const daemonPath = path.resolve(__dirname22, "../../dist/daemon.js"); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Daemon script path: ${daemonPath} ` ); if (!fs.existsSync(daemonPath)) { fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: Daemon script not found at ${daemonPath} ` ); throw new Error(`Daemon script not found at ${daemonPath}`); } const child = spawn(process.execPath, [daemonPath], { detached: true, stdio: ["ignore", fs.openSync(LOG_FILE, "a"), fs.openSync(LOG_FILE, "a")], env: { ...process.env, WEBPRESENCE_DAEMON: "true" } }); child.unref(); fs.writeFileSync(PID_FILE, child.pid.toString()); return { success: true, message: `Daemon started with PID ${child.pid}`, pid: child.pid }; } catch (error) { logger.error(`Failed to start daemon: ${error.message}`, { error: error.stack }); return { success: false, message: `Failed to start daemon: ${error.message}` }; } } __name(startDaemon, "startDaemon"); async function stopDaemon() { if (!isDaemonRunning()) { return { success: false, message: "Daemon is not running" }; } try { const pid = parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10); process.kill(pid, "SIGTERM"); await new Promise((resolve) => { const checkInterval = setInterval(() => { try { process.kill(pid, 0); } catch (error) { clearInterval(checkInterval); resolve(); } }, 100); setTimeout(() => { clearInterval(checkInterval); resolve(); }, 5e3); }); fs.unlinkSync(PID_FILE); return { success: true, message: `Daemon with PID ${pid} stopped` }; } catch (error) { logger.error(`Failed to stop daemon: ${error.message}`, { error: error.stack }); return { success: false, message: `Failed to stop daemon: ${error.message}` }; } } __name(stopDaemon, "stopDaemon"); function isDaemonRunning() { if (!fs.existsSync(PID_FILE)) { return false; } try { const pidContent = fs.readFileSync(PID_FILE, "utf8").trim(); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Checking daemon status, PID file content: ${pidContent} ` ); if (!pidContent || isNaN(parseInt(pidContent, 10))) { fs.unlinkSync(PID_FILE); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Invalid PID content, removing PID file ` ); return false; } const pid = parseInt(pidContent, 10); try { process.kill(pid, 0); } catch (e) { fs.unlinkSync(PID_FILE); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Process with PID ${pid} does not exist, removing PID file ` ); return false; } try { let psCommand; if (process.platform === "win32") { psCommand = `tasklist /FI "PID eq ${pid}" /FO CSV /NH`; } else { psCommand = `ps -p ${pid} -o comm=,args=`; } const output = execSync(psCommand, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] // Suppress stderr }); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Process info for PID ${pid}: ${output.trim()} ` ); const isNodeProcess = output.toLowerCase().includes("node"); const isWebPresenceProcess = output.toLowerCase().includes("webpresence") || output.toLowerCase().includes("daemon.js") || output.toLowerCase().includes("cli.js"); if (!isNodeProcess) { fs.unlinkSync(PID_FILE); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Process with PID ${pid} is not a Node.js process, removing PID file ` ); return false; } try { const serverPort = config.getServer().port || 8874; try { const net = __require("net"); const tester = net.createServer().once("error", (err) => { if (err.code === "EADDRINUSE") { fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Port ${serverPort} is in use (Node.js check) ` ); tester.close(); return true; } }).once("listening", () => { tester.close(); }); tester.listen(serverPort); } catch (nodeCheckError) { } try { const netstatCommand = process.platform === "win32" ? `netstat -ano | findstr :${serverPort}` : `netstat -tuln | grep :${serverPort}`; const netstatOutput = execSync(netstatCommand, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] // Suppress stderr to avoid showing command not found errors }); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Port check for ${serverPort}: ${netstatOutput.trim()} ` ); return true; } catch (netstatError) { if (process.platform !== "win32") { try { const lsofOutput = execSync(`lsof -i :${serverPort} -t`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] // Suppress stderr }); if (lsofOutput.trim()) { fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Port ${serverPort} is in use (lsof check) ` ); return true; } } catch (lsofError) { } } fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Port check failed, but process appears to be running ` ); return isWebPresenceProcess; } } catch (portCheckError) { fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] All port checks failed, but process appears to be running ` ); return isWebPresenceProcess; } } catch (e) { fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Error checking process details: ${e} ` ); try { process.kill(pid, 0); return true; } catch (killError) { fs.unlinkSync(PID_FILE); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Process with PID ${pid} does not exist, removing PID file ` ); return false; } } } catch (error) { try { fs.unlinkSync(PID_FILE); fs.appendFileSync( LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] Error checking daemon status, removing PID file: ${error} ` ); } catch (e) { } return false; } } __name(isDaemonRunning, "isDaemonRunning"); function getDaemonPid() { if (!isDaemonRunning()) { return null; } try { return parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10); } catch (error) { return null; } } __name(getDaemonPid, "getDaemonPid"); function getDaemonLogPath() { return LOG_FILE; } __name(getDaemonLogPath, "getDaemonLogPath"); export { getDaemonLogPath, getDaemonPid, isDaemonRunning, startDaemon, stopDaemon }; //# sourceMappingURL=daemon.js.map //# sourceMappingURL=daemon.js.map