From 6aeacaaf213cc0da658e363276a68d3b8297b6cc Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 18 Oct 2025 17:54:05 +0200 Subject: [PATCH] add cli flags to spoof user agent --- src/main/cli.ts | 100 +++++++++++++++++++++++++++++++++++++++++ src/main/constants.ts | 7 ++- src/main/index.ts | 3 ++ src/main/mainWindow.ts | 3 +- 4 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 src/main/cli.ts diff --git a/src/main/cli.ts b/src/main/cli.ts new file mode 100644 index 0000000..05730b6 --- /dev/null +++ b/src/main/cli.ts @@ -0,0 +1,100 @@ +/* + * Vesktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2025 Vendicated and Vesktop contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { app } from "electron"; +import { stripIndent } from "shared/utils/text"; +import { parseArgs, ParseArgsOptionDescriptor } from "util"; + +const options = { + "start-minimized": { + default: false, + type: "boolean", + short: "m", + description: "Start the application minimized to the system tray" + }, + version: { + type: "boolean", + short: "v", + description: "Print the application version and exit" + }, + help: { + type: "boolean", + short: "h", + description: "Print help information and exit" + }, + "user-agent": { + type: "string", + argumentName: "ua", + description: "Set a custom User-Agent. May trigger anti-spam or break voice chat" + }, + "user-agent-os": { + type: "string", + description: "Set User-Agent to a specific operating system. May trigger anti-spam or break voice chat", + options: ["windows", "linux", "darwin"] + } +} satisfies Record< + string, + ParseArgsOptionDescriptor & { description: string; options?: string[]; argumentName?: string } +>; + +export const CommandLine = parseArgs({ + options, + strict: true, + allowPositionals: true +}); + +export function checkCommandLineForHelpOrVersion() { + const { help, version } = CommandLine.values; + + if (version) { + console.error(`Vesktop v${app.getVersion()}`); + app.exit(0); + } + + if (help) { + const base = stripIndent` + Vesktop v${app.getVersion()} + + Usage: ${process.execPath} [options] [url] + + Options: + `; + + const optionLines = Object.entries(options) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([name, opt]) => { + const flags = [ + "short" in opt && `-${opt.short}`, + `--${name}`, + opt.type !== "boolean" && + ("options" in opt ? `<${opt.options.join(" | ")}>` : `<${opt.argumentName ?? opt.type}>`) + ] + .filter(Boolean) + .join(" "); + + return [flags, opt.description]; + }); + + const padding = optionLines.reduce((max, [flags]) => Math.max(max, flags.length), 0) + 4; + + const optionsHelp = optionLines + .map(([flags, description]) => ` ${flags.padEnd(padding, " ")}${description}`) + .join("\n"); + + console.error(base + "\n" + optionsHelp); + app.exit(0); + } + + for (const [name, def] of Object.entries(options)) { + const value = CommandLine.values[name]; + if (!value) continue; + + if ("options" in def && !def.options?.includes(value)) { + console.error(`Invalid value for --${name}: ${value}\nExpected one of: ${def.options.join(", ")}`); + app.exit(1); + } + } +} diff --git a/src/main/constants.ts b/src/main/constants.ts index 10b6f66..d8ecc85 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -8,6 +8,8 @@ import { app } from "electron"; import { existsSync, mkdirSync } from "fs"; import { dirname, join } from "path"; +import { CommandLine } from "./cli"; + const vesktopDir = dirname(process.execPath); export const PORTABLE = @@ -45,7 +47,10 @@ const BrowserUserAgents = { windows: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) ${VersionString}` }; -export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows; +export const BrowserUserAgent = + CommandLine.values["user-agent"] || + BrowserUserAgents[CommandLine.values["user-agent-os"] || process.platform] || + BrowserUserAgents.windows; export const enum MessageBoxChoice { Default, diff --git a/src/main/index.ts b/src/main/index.ts index 9952fe7..ee59367 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -10,6 +10,7 @@ import "./userAssets"; import { app, BrowserWindow, nativeTheme } from "electron"; import { autoUpdater } from "electron-updater"; +import { checkCommandLineForHelpOrVersion } from "./cli"; import { DATA_DIR } from "./constants"; import { createFirstLaunchTour } from "./firstLaunch"; import { createWindows, mainWin } from "./mainWindow"; @@ -19,6 +20,8 @@ import { Settings, State } from "./settings"; import { setAsDefaultProtocolClient } from "./utils/setAsDefaultProtocolClient"; import { isDeckGameMode } from "./utils/steamOS"; +checkCommandLineForHelpOrVersion(); + if (!IS_DEV) { autoUpdater.checkForUpdatesAndNotify(); } diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index a514917..36eb483 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -22,6 +22,7 @@ import type { SettingsStore } from "shared/utils/SettingsStore"; import { createAboutWindow } from "./about"; import { initArRPC } from "./arrpc"; +import { CommandLine } from "./cli"; import { BrowserUserAgent, DEFAULT_HEIGHT, DEFAULT_WIDTH, MIN_HEIGHT, MIN_WIDTH } from "./constants"; import { AppEvents } from "./events"; import { darwinURL } from "./index"; @@ -428,7 +429,7 @@ function retryUrl(url: string, description: string) { } export async function createWindows() { - const startMinimized = process.argv.includes("--start-minimized"); + const startMinimized = CommandLine.values["start-minimized"]; let splash: BrowserWindow | undefined; if (Settings.store.enableSplashScreen !== false) {