mirror of
https://github.com/Alvin-Zilverstand/return-youtube-dislike.git
synced 2026-10-28 07:40:27 +01:00
234 lines
8.2 KiB
JavaScript
234 lines
8.2 KiB
JavaScript
|
|
const fs = require("node:fs");
|
||
|
|
const os = require("node:os");
|
||
|
|
const path = require("node:path");
|
||
|
|
|
||
|
|
const ACTIVE_PORT_FILE_NAME = "DevToolsActivePort";
|
||
|
|
const BROWSER_ALIASES = new Set(["brave", "chrome", "chromium", "edge"]);
|
||
|
|
|
||
|
|
function nonEmptyEnvironmentValue(environment, name) {
|
||
|
|
const value = environment[name]?.trim();
|
||
|
|
return value || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function platformPath(platform, pathImpl) {
|
||
|
|
if (pathImpl) return pathImpl;
|
||
|
|
return platform === "win32" ? path.win32 : path.posix;
|
||
|
|
}
|
||
|
|
|
||
|
|
function browserUserDataDirectories(browser, { environment, homedir, pathImpl, platform }) {
|
||
|
|
const join = pathImpl.join.bind(pathImpl);
|
||
|
|
const home = nonEmptyEnvironmentValue(environment, "HOME") || homedir();
|
||
|
|
|
||
|
|
if (platform === "win32") {
|
||
|
|
const localAppData = nonEmptyEnvironmentValue(environment, "LOCALAPPDATA");
|
||
|
|
if (!localAppData) return [];
|
||
|
|
const directories = {
|
||
|
|
brave: [join(localAppData, "BraveSoftware", "Brave-Browser", "User Data")],
|
||
|
|
chrome: [
|
||
|
|
join(localAppData, "Google", "Chrome", "User Data"),
|
||
|
|
join(localAppData, "Google", "Chrome Beta", "User Data"),
|
||
|
|
join(localAppData, "Google", "Chrome Dev", "User Data"),
|
||
|
|
join(localAppData, "Google", "Chrome SxS", "User Data"),
|
||
|
|
],
|
||
|
|
chromium: [join(localAppData, "Chromium", "User Data")],
|
||
|
|
edge: [join(localAppData, "Microsoft", "Edge", "User Data")],
|
||
|
|
};
|
||
|
|
return directories[browser] || [];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (platform === "darwin") {
|
||
|
|
const applicationSupport = join(home, "Library", "Application Support");
|
||
|
|
const directories = {
|
||
|
|
brave: [join(applicationSupport, "BraveSoftware", "Brave-Browser")],
|
||
|
|
chrome: [
|
||
|
|
join(applicationSupport, "Google", "Chrome"),
|
||
|
|
join(applicationSupport, "Google", "Chrome Beta"),
|
||
|
|
join(applicationSupport, "Google", "Chrome Dev"),
|
||
|
|
join(applicationSupport, "Google", "Chrome Canary"),
|
||
|
|
],
|
||
|
|
chromium: [join(applicationSupport, "Chromium")],
|
||
|
|
edge: [join(applicationSupport, "Microsoft Edge")],
|
||
|
|
};
|
||
|
|
return directories[browser] || [];
|
||
|
|
}
|
||
|
|
|
||
|
|
const configRoot = nonEmptyEnvironmentValue(environment, "XDG_CONFIG_HOME") || join(home, ".config");
|
||
|
|
const directories = {
|
||
|
|
brave: [join(configRoot, "BraveSoftware", "Brave-Browser")],
|
||
|
|
chrome: [
|
||
|
|
join(configRoot, "google-chrome"),
|
||
|
|
join(configRoot, "google-chrome-beta"),
|
||
|
|
join(configRoot, "google-chrome-unstable"),
|
||
|
|
],
|
||
|
|
chromium: [join(configRoot, "chromium")],
|
||
|
|
edge: [join(configRoot, "microsoft-edge")],
|
||
|
|
};
|
||
|
|
return directories[browser] || [];
|
||
|
|
}
|
||
|
|
|
||
|
|
function knownActivePortFiles(browser, settings) {
|
||
|
|
const browsers = browser ? [browser] : ["chrome", "brave", "edge", "chromium"];
|
||
|
|
return browsers.flatMap((name) =>
|
||
|
|
browserUserDataDirectories(name, settings).map((directory) =>
|
||
|
|
settings.pathImpl.join(directory, ACTIVE_PORT_FILE_NAME),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseBareHostAndPort(value) {
|
||
|
|
let url;
|
||
|
|
try {
|
||
|
|
url = new URL(`http://${value}`);
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!url.hostname || !url.port || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const port = Number(url.port);
|
||
|
|
if (!Number.isSafeInteger(port) || port < 1 || port > 65535) return null;
|
||
|
|
return { authority: url.host, port };
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseActivePortFile(contents, filePath) {
|
||
|
|
const [rawPort, rawBrowserPath] = String(contents).split(/\r?\n/);
|
||
|
|
const port = Number(rawPort?.trim());
|
||
|
|
if (!/^\d+$/.test(rawPort?.trim() || "") || !Number.isSafeInteger(port) || port < 1 || port > 65535) {
|
||
|
|
throw new Error(`The CDP active-port file at ${filePath} has an invalid port.`);
|
||
|
|
}
|
||
|
|
|
||
|
|
let browserPath = rawBrowserPath?.trim();
|
||
|
|
if (/^wss?:\/\//i.test(browserPath || "")) {
|
||
|
|
let url;
|
||
|
|
try {
|
||
|
|
url = new URL(browserPath);
|
||
|
|
} catch {
|
||
|
|
throw new Error(`The CDP active-port file at ${filePath} has an invalid browser WebSocket URL.`);
|
||
|
|
}
|
||
|
|
browserPath = `${url.pathname}${url.search}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!/^\/devtools\/browser\/[A-Za-z0-9._-]+$/.test(browserPath || "")) {
|
||
|
|
throw new Error(`The CDP active-port file at ${filePath} has an invalid browser WebSocket path.`);
|
||
|
|
}
|
||
|
|
|
||
|
|
return { browserPath, port };
|
||
|
|
}
|
||
|
|
|
||
|
|
function unique(values, platform) {
|
||
|
|
const keys = new Set();
|
||
|
|
return values.filter((value) => {
|
||
|
|
const key = platform === "win32" ? value.toLowerCase() : value;
|
||
|
|
if (keys.has(key)) return false;
|
||
|
|
keys.add(key);
|
||
|
|
return true;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function activePortFileCandidates(browser, settings) {
|
||
|
|
const activePortFile = nonEmptyEnvironmentValue(settings.environment, "RYD_CDP_ACTIVE_PORT_FILE");
|
||
|
|
const userDataDirectory = nonEmptyEnvironmentValue(settings.environment, "RYD_CDP_USER_DATA_DIR");
|
||
|
|
if (activePortFile && userDataDirectory) {
|
||
|
|
throw new Error("Set only one of RYD_CDP_ACTIVE_PORT_FILE and RYD_CDP_USER_DATA_DIR.");
|
||
|
|
}
|
||
|
|
if (activePortFile) return { explicit: true, paths: [settings.pathImpl.resolve(activePortFile)] };
|
||
|
|
if (userDataDirectory) {
|
||
|
|
return {
|
||
|
|
explicit: true,
|
||
|
|
paths: [settings.pathImpl.join(settings.pathImpl.resolve(userDataDirectory), ACTIVE_PORT_FILE_NAME)],
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return { explicit: false, paths: unique(knownActivePortFiles(browser, settings), settings.platform) };
|
||
|
|
}
|
||
|
|
|
||
|
|
function readCandidate(filePath, settings, explicit) {
|
||
|
|
try {
|
||
|
|
return parseActivePortFile(settings.readFileSync(filePath, "utf8"), filePath);
|
||
|
|
} catch (error) {
|
||
|
|
if (!explicit && ["ENOENT", "ENOTDIR"].includes(error.code)) return null;
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function protocolEndpoint(value) {
|
||
|
|
if (!/^(?:https?|wss?):\/\//i.test(value)) return null;
|
||
|
|
let url;
|
||
|
|
try {
|
||
|
|
url = new URL(value);
|
||
|
|
} catch {
|
||
|
|
throw new Error("RYD_CDP_ENDPOINT must be a valid HTTP(S), WebSocket, host:port, or browser-alias endpoint.");
|
||
|
|
}
|
||
|
|
if (!["http:", "https:", "ws:", "wss:"].includes(url.protocol) || !url.hostname) {
|
||
|
|
throw new Error("RYD_CDP_ENDPOINT must be a valid HTTP(S), WebSocket, host:port, or browser-alias endpoint.");
|
||
|
|
}
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
|
||
|
|
function resolveCdpEndpoint(
|
||
|
|
configuredEndpoint,
|
||
|
|
{
|
||
|
|
environment = process.env,
|
||
|
|
homedir = os.homedir,
|
||
|
|
pathImpl,
|
||
|
|
platform = process.platform,
|
||
|
|
readFileSync = fs.readFileSync,
|
||
|
|
} = {},
|
||
|
|
) {
|
||
|
|
const value = configuredEndpoint?.trim() || "chrome";
|
||
|
|
const explicitProtocolEndpoint = protocolEndpoint(value);
|
||
|
|
if (explicitProtocolEndpoint) return explicitProtocolEndpoint;
|
||
|
|
|
||
|
|
const browser = BROWSER_ALIASES.has(value.toLowerCase()) ? value.toLowerCase() : null;
|
||
|
|
const bareEndpoint = browser ? null : parseBareHostAndPort(value);
|
||
|
|
if (!browser && !bareEndpoint) {
|
||
|
|
throw new Error(
|
||
|
|
"RYD_CDP_ENDPOINT must be an HTTP(S) URL, WebSocket URL, host:port shown by Chromium, or one of chrome, brave, edge, and chromium.",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const settings = {
|
||
|
|
environment,
|
||
|
|
homedir,
|
||
|
|
pathImpl: platformPath(platform, pathImpl),
|
||
|
|
platform,
|
||
|
|
readFileSync,
|
||
|
|
};
|
||
|
|
const candidates = activePortFileCandidates(browser, settings);
|
||
|
|
const matches = [];
|
||
|
|
for (const filePath of candidates.paths) {
|
||
|
|
const activePort = readCandidate(filePath, settings, candidates.explicit);
|
||
|
|
if (!activePort || (bareEndpoint && activePort.port !== bareEndpoint.port)) continue;
|
||
|
|
const authority = bareEndpoint?.authority || `127.0.0.1:${activePort.port}`;
|
||
|
|
matches.push({ endpoint: `ws://${authority}${activePort.browserPath}`, filePath });
|
||
|
|
}
|
||
|
|
|
||
|
|
const endpoints = [...new Set(matches.map(({ endpoint }) => endpoint))];
|
||
|
|
if (endpoints.length === 1) return endpoints[0];
|
||
|
|
if (endpoints.length > 1) {
|
||
|
|
throw new Error(
|
||
|
|
`More than one DevToolsActivePort file matches RYD_CDP_ENDPOINT=${value}. Set RYD_CDP_ACTIVE_PORT_FILE or RYD_CDP_USER_DATA_DIR to select the intended browser profile.`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const fileDescription = candidates.explicit
|
||
|
|
? candidates.paths[0]
|
||
|
|
: candidates.paths.length
|
||
|
|
? candidates.paths.join(", ")
|
||
|
|
: "the platform's standard browser user-data directories";
|
||
|
|
const portDescription = bareEndpoint ? ` matching port ${bareEndpoint.port}` : "";
|
||
|
|
throw new Error(
|
||
|
|
`Could not find a valid DevToolsActivePort file${portDescription} in ${fileDescription}. ` +
|
||
|
|
"Set RYD_CDP_ACTIVE_PORT_FILE to that file, RYD_CDP_USER_DATA_DIR to its directory, or use an explicit ws:// or legacy http:// endpoint.",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
ACTIVE_PORT_FILE_NAME,
|
||
|
|
browserUserDataDirectories,
|
||
|
|
parseActivePortFile,
|
||
|
|
parseBareHostAndPort,
|
||
|
|
resolveCdpEndpoint,
|
||
|
|
};
|