Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd2eed7743 |
@@ -41,6 +41,8 @@ interface ManifestEntry {
|
||||
type: 'yaml' | 'ts';
|
||||
/** Relative path from clis/ dir, e.g. 'bilibili/hot.yaml' or 'bilibili/search.js' */
|
||||
modulePath?: string;
|
||||
/** Pre-navigation control — see CliCommand.navigateBefore */
|
||||
navigateBefore?: boolean | string;
|
||||
}
|
||||
|
||||
function extractBalancedBlock(
|
||||
@@ -189,6 +191,7 @@ function scanYaml(filePath: string, site: string): ManifestEntry | null {
|
||||
pipeline: def.pipeline,
|
||||
timeout: def.timeout,
|
||||
type: 'yaml',
|
||||
navigateBefore: def.navigateBefore,
|
||||
};
|
||||
} catch (err: any) {
|
||||
process.stderr.write(`Warning: failed to parse ${filePath}: ${err.message}\n`);
|
||||
@@ -248,6 +251,10 @@ function scanTs(filePath: string, site: string): ManifestEntry | null {
|
||||
entry.args = parseTsArgsBlock(argsBlock);
|
||||
}
|
||||
|
||||
// Extract navigateBefore: false
|
||||
const navMatch = src.match(/navigateBefore\s*:\s*(true|false)/);
|
||||
if (navMatch) entry.navigateBefore = navMatch[1] === 'true' ? true : false;
|
||||
|
||||
return entry;
|
||||
} catch (err: any) {
|
||||
// If parsing fails, log a warning (matching scanYaml behaviour) and skip the entry.
|
||||
|
||||
@@ -13,6 +13,7 @@ cli({
|
||||
description: 'BOSS直聘批量向推荐候选人发送招呼',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'job-id', default: '', help: 'Filter by encrypted job ID (greet all jobs if empty)' },
|
||||
|
||||
@@ -7,6 +7,7 @@ cli({
|
||||
description: 'BOSS直聘查看聊天列表(招聘端)',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
|
||||
|
||||
@@ -7,6 +7,7 @@ cli({
|
||||
description: 'BOSS直聘查看与候选人的聊天消息',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'uid', required: true, help: 'Encrypted UID (from chatlist)' },
|
||||
|
||||
@@ -10,6 +10,7 @@ cli({
|
||||
description: 'BOSS直聘查看职位详情',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'security-id', required: true, help: 'Security ID from search results (securityId field)' },
|
||||
|
||||
@@ -10,6 +10,7 @@ cli({
|
||||
description: 'BOSS直聘交换联系方式(请求手机/微信)',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate' },
|
||||
|
||||
@@ -13,6 +13,7 @@ cli({
|
||||
description: 'BOSS直聘向新候选人发送招呼(开始聊天)',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate (from recommend)' },
|
||||
|
||||
@@ -10,6 +10,7 @@ cli({
|
||||
description: 'BOSS直聘发送面试邀请',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate' },
|
||||
|
||||
@@ -10,6 +10,7 @@ cli({
|
||||
description: 'BOSS直聘查看我发布的职位列表',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [],
|
||||
columns: ['job_name', 'salary', 'city', 'status', 'encrypt_job_id'],
|
||||
|
||||
@@ -19,6 +19,7 @@ cli({
|
||||
description: 'BOSS直聘给候选人添加标签',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate' },
|
||||
|
||||
@@ -10,6 +10,7 @@ cli({
|
||||
description: 'BOSS直聘查看推荐候选人(新招呼列表)',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'limit', type: 'int', default: 20, help: 'Number of results to return' },
|
||||
|
||||
@@ -18,6 +18,7 @@ cli({
|
||||
description: 'BOSS直聘查看候选人简历(招聘端)',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
|
||||
|
||||
@@ -69,6 +69,7 @@ cli({
|
||||
description: 'BOSS直聘搜索职位',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'query', required: true, positional: true, help: 'Search keyword (e.g. AI agent, 前端)' },
|
||||
|
||||
@@ -16,6 +16,7 @@ cli({
|
||||
description: 'BOSS直聘发送聊天消息',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
|
||||
|
||||
@@ -10,6 +10,7 @@ cli({
|
||||
description: 'BOSS直聘职位数据统计',
|
||||
domain: 'www.zhipin.com',
|
||||
strategy: Strategy.COOKIE,
|
||||
navigateBefore: false,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'job-id', default: '', help: 'Encrypted job ID (show all if empty)' },
|
||||
|
||||
@@ -62,6 +62,7 @@ async function loadFromManifest(manifestPath: string, clisDir: string): Promise<
|
||||
pipeline: entry.pipeline,
|
||||
timeoutSeconds: entry.timeout,
|
||||
source: `manifest:${entry.site}/${entry.name}`,
|
||||
navigateBefore: entry.navigateBefore,
|
||||
};
|
||||
registerCommand(cmd);
|
||||
} else if (entry.type === 'ts' && entry.modulePath) {
|
||||
@@ -80,6 +81,7 @@ async function loadFromManifest(manifestPath: string, clisDir: string): Promise<
|
||||
columns: entry.columns,
|
||||
timeoutSeconds: entry.timeout,
|
||||
source: modulePath,
|
||||
navigateBefore: entry.navigateBefore,
|
||||
_lazy: true,
|
||||
_modulePath: modulePath,
|
||||
};
|
||||
@@ -162,6 +164,7 @@ async function registerYamlCli(filePath: string, defaultSite: string): Promise<v
|
||||
pipeline: def.pipeline,
|
||||
timeoutSeconds: def.timeout,
|
||||
source: filePath,
|
||||
navigateBefore: def.navigateBefore,
|
||||
};
|
||||
|
||||
registerCommand(cmd);
|
||||
|
||||
+23
-8
@@ -101,6 +101,24 @@ async function runCommand(
|
||||
throw new Error(`Command ${fullName(cmd)} has no func or pipeline`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the pre-navigation URL for a command, or null to skip.
|
||||
*
|
||||
* COOKIE/HEADER strategies need the browser on the target domain so
|
||||
* `fetch(url, { credentials: 'include' })` carries cookies.
|
||||
* Adapters that handle their own navigation set `navigateBefore: false`.
|
||||
*/
|
||||
function resolvePreNav(cmd: CliCommand): string | null {
|
||||
if (cmd.navigateBefore === false) return null;
|
||||
if (typeof cmd.navigateBefore === 'string') return cmd.navigateBefore;
|
||||
|
||||
// Default: pre-navigate for COOKIE/HEADER strategies with a domain
|
||||
if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
|
||||
return `https://${cmd.domain}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a CLI command. Automatically manages browser sessions when needed.
|
||||
*
|
||||
@@ -122,14 +140,11 @@ export async function executeCommand(
|
||||
if (shouldUseBrowserSession(cmd)) {
|
||||
const BrowserFactory = getBrowserFactory();
|
||||
return browserSession(BrowserFactory, async (page) => {
|
||||
// Cookie/header strategies require same-origin context for credentialed fetch.
|
||||
// TS adapters that manage their own navigation (via common.ts helpers like
|
||||
// navigateToChat/navigateTo) should skip this pre-navigation to avoid
|
||||
// redundant double page loads. Currently only boss adapters do this.
|
||||
// TODO: replace site check with an adapter-level `skipPreNav` flag.
|
||||
const skipPreNav = cmd.site === 'boss';
|
||||
if (!skipPreNav && (cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
|
||||
try { await page.goto(`https://${cmd.domain}`); await page.wait(2); } catch {}
|
||||
// Pre-navigate to target domain for cookie/header context if needed.
|
||||
// Each adapter controls this via `navigateBefore` (see CliCommand docs).
|
||||
const preNavUrl = resolvePreNav(cmd);
|
||||
if (preNavUrl) {
|
||||
try { await page.goto(preNavUrl); await page.wait(2); } catch {}
|
||||
}
|
||||
return runWithTimeout(runCommand(cmd, page, kwargs, debug), {
|
||||
timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT,
|
||||
|
||||
@@ -36,6 +36,17 @@ export interface CliCommand {
|
||||
timeoutSeconds?: number;
|
||||
source?: string;
|
||||
footerExtra?: (kwargs: Record<string, any>) => string | undefined;
|
||||
/**
|
||||
* Control pre-navigation for cookie/header context before command execution.
|
||||
*
|
||||
* Browser adapters using COOKIE/HEADER strategy need the page to be on the
|
||||
* target domain so that `fetch(url, { credentials: 'include' })` carries cookies.
|
||||
*
|
||||
* - `undefined` / `true`: navigate to `https://${domain}` (default)
|
||||
* - `false`: skip — adapter handles its own navigation (e.g. boss common.ts)
|
||||
* - `string`: navigate to this specific URL instead of the domain root
|
||||
*/
|
||||
navigateBefore?: boolean | string;
|
||||
}
|
||||
|
||||
/** Internal extension for lazy-loaded TS modules (not exposed in public API) */
|
||||
@@ -73,6 +84,7 @@ export function cli(opts: CliOptions): CliCommand {
|
||||
pipeline: opts.pipeline,
|
||||
timeoutSeconds: opts.timeoutSeconds,
|
||||
footerExtra: opts.footerExtra,
|
||||
navigateBefore: opts.navigateBefore,
|
||||
};
|
||||
|
||||
const key = fullName(cmd);
|
||||
|
||||
Reference in New Issue
Block a user