feat: add tackle catalog and recommendation analytics

This commit is contained in:
ik
2026-09-20 18:26:53 +07:00
parent 4f0c2d23de
commit e94d247096
11 changed files with 350 additions and 11 deletions
+26 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs/promises";
import fs, { access } from "node:fs/promises";
import readline from "node:readline/promises";
import process from "node:process";
import path from "node:path";
@@ -22,6 +22,7 @@ Options:
--html-output PATH retain browser DOM HTML (default: temporary .cache file)
--state-file PATH shared cooldown state file
--profile PATH persistent Chromium profile directory
--executable PATH Chromium/Chrome executable (default: RF4_CHROMIUM_EXECUTABLE or system Chrome)
--headless run Chromium headless; cannot handle manual challenges
--wait-seconds N headed wait after load (default: 30)
`);
@@ -37,6 +38,7 @@ function parseArgs(argv) {
const args = {
mode, url: mode === "catalog" ? "https://rf4db.com/ru/maps" : null,
output: null, htmlOutput: null, stateFile: DEFAULT_STATE, profile: DEFAULT_PROFILE,
executable: process.env.RF4_CHROMIUM_EXECUTABLE || null,
headless: false, waitSeconds: 30,
};
for (let index = 0; index < argv.length; index += 1) {
@@ -46,6 +48,7 @@ function parseArgs(argv) {
else if (arg === "--html-output") args.htmlOutput = argv[++index];
else if (arg === "--state-file") args.stateFile = argv[++index];
else if (arg === "--profile") args.profile = argv[++index];
else if (arg === "--executable") args.executable = argv[++index];
else if (arg === "--headless") args.headless = true;
else if (arg === "--wait-seconds") args.waitSeconds = Number(argv[++index]);
else throw new Error(`unknown argument: ${arg}`);
@@ -61,6 +64,22 @@ function parseArgs(argv) {
return args;
}
async function resolveExecutable(requested) {
if (requested) {
await access(requested);
return requested;
}
for (const candidate of ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/usr/bin/chromium"]) {
try {
await access(candidate);
return candidate;
} catch {
// Try the next locally installed browser.
}
}
return undefined;
}
function reserveCooldown(args) {
const source = args.mode === "catalog" ? "rf4db-waterbodies" : "rf4db-waterbody";
const command = ["-m", "rf4_research.community_cli", source, "--url", args.url, "--state-file", args.stateFile, "--reserve-only"];
@@ -87,7 +106,12 @@ async function main() {
await fs.mkdir(path.dirname(htmlOutput), { recursive: true });
await fs.mkdir(path.dirname(output), { recursive: true });
const context = await chromium.launchPersistentContext(path.resolve(args.profile), { headless: args.headless });
const executablePath = await resolveExecutable(args.executable);
if (executablePath) console.log(`Using browser executable: ${executablePath}`);
const context = await chromium.launchPersistentContext(path.resolve(args.profile), {
headless: args.headless,
...(executablePath ? { executablePath } : {}),
});
try {
const page = context.pages()[0] || await context.newPage();
await page.goto(args.url, { waitUntil: "domcontentloaded", timeout: 60_000 });