deepspider 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/check.md +122 -0
- package/.claude/agents/debug.md +106 -0
- package/.claude/agents/dispatch.md +214 -0
- package/.claude/agents/implement.md +96 -0
- package/.claude/agents/plan.md +396 -0
- package/.claude/agents/research.md +120 -0
- package/.claude/commands/evolve/merge.md +80 -0
- package/.claude/commands/trellis/before-backend-dev.md +13 -0
- package/.claude/commands/trellis/before-frontend-dev.md +13 -0
- package/.claude/commands/trellis/break-loop.md +107 -0
- package/.claude/commands/trellis/check-backend.md +13 -0
- package/.claude/commands/trellis/check-cross-layer.md +153 -0
- package/.claude/commands/trellis/check-frontend.md +13 -0
- package/.claude/commands/trellis/create-command.md +154 -0
- package/.claude/commands/trellis/finish-work.md +129 -0
- package/.claude/commands/trellis/integrate-skill.md +219 -0
- package/.claude/commands/trellis/onboard.md +358 -0
- package/.claude/commands/trellis/parallel.md +193 -0
- package/.claude/commands/trellis/record-session.md +62 -0
- package/.claude/commands/trellis/start.md +280 -0
- package/.claude/commands/trellis/update-spec.md +213 -0
- package/.claude/hooks/inject-subagent-context.py +758 -0
- package/.claude/hooks/ralph-loop.py +374 -0
- package/.claude/hooks/session-start.py +126 -0
- package/.claude/settings.json +41 -0
- package/.claude/skills/deepagents-guide/SKILL.md +428 -0
- package/.cursor/commands/trellis-before-backend-dev.md +13 -0
- package/.cursor/commands/trellis-before-frontend-dev.md +13 -0
- package/.cursor/commands/trellis-break-loop.md +107 -0
- package/.cursor/commands/trellis-check-backend.md +13 -0
- package/.cursor/commands/trellis-check-cross-layer.md +153 -0
- package/.cursor/commands/trellis-check-frontend.md +13 -0
- package/.cursor/commands/trellis-create-command.md +154 -0
- package/.cursor/commands/trellis-finish-work.md +129 -0
- package/.cursor/commands/trellis-integrate-skill.md +219 -0
- package/.cursor/commands/trellis-onboard.md +358 -0
- package/.cursor/commands/trellis-record-session.md +62 -0
- package/.cursor/commands/trellis-start.md +156 -0
- package/.cursor/commands/trellis-update-spec.md +213 -0
- package/.env.example +11 -0
- package/.husky/pre-commit +1 -0
- package/.mcp.json +8 -0
- package/.trellis/.template-hashes.json +65 -0
- package/.trellis/.version +1 -0
- package/.trellis/scripts/add-session.sh +384 -0
- package/.trellis/scripts/common/developer.sh +129 -0
- package/.trellis/scripts/common/git-context.sh +263 -0
- package/.trellis/scripts/common/paths.sh +208 -0
- package/.trellis/scripts/common/phase.sh +150 -0
- package/.trellis/scripts/common/registry.sh +247 -0
- package/.trellis/scripts/common/task-queue.sh +142 -0
- package/.trellis/scripts/common/task-utils.sh +151 -0
- package/.trellis/scripts/common/worktree.sh +128 -0
- package/.trellis/scripts/create-bootstrap.sh +299 -0
- package/.trellis/scripts/get-context.sh +7 -0
- package/.trellis/scripts/get-developer.sh +15 -0
- package/.trellis/scripts/init-developer.sh +34 -0
- package/.trellis/scripts/multi-agent/cleanup.sh +396 -0
- package/.trellis/scripts/multi-agent/create-pr.sh +241 -0
- package/.trellis/scripts/multi-agent/plan.sh +207 -0
- package/.trellis/scripts/multi-agent/start.sh +310 -0
- package/.trellis/scripts/multi-agent/status.sh +828 -0
- package/.trellis/scripts/task.sh +1118 -0
- package/.trellis/spec/backend/deepagents-guide.md +337 -0
- package/.trellis/spec/backend/directory-structure.md +126 -0
- package/.trellis/spec/backend/examples/skills/deepagents-guide/README.md +11 -0
- package/.trellis/spec/backend/examples/skills/deepagents-guide/agent.js.template +20 -0
- package/.trellis/spec/backend/examples/skills/deepagents-guide/skills-config.js.template +13 -0
- package/.trellis/spec/backend/examples/skills/deepagents-guide/subagent.js.template +19 -0
- package/.trellis/spec/backend/hook-guidelines.md +178 -0
- package/.trellis/spec/backend/index.md +36 -0
- package/.trellis/spec/backend/quality-guidelines.md +201 -0
- package/.trellis/spec/backend/state-management.md +76 -0
- package/.trellis/spec/backend/tool-guidelines.md +144 -0
- package/.trellis/spec/backend/type-safety.md +71 -0
- package/.trellis/spec/guides/code-reuse-thinking-guide.md +92 -0
- package/.trellis/spec/guides/cross-layer-thinking-guide.md +94 -0
- package/.trellis/spec/guides/index.md +79 -0
- package/.trellis/tasks/archive/02-02-evolving-skills/prd.md +61 -0
- package/.trellis/tasks/archive/02-02-evolving-skills/task.json +29 -0
- package/.trellis/tasks/archive/2026-02/00-bootstrap-guidelines/prd.md +86 -0
- package/.trellis/tasks/archive/2026-02/00-bootstrap-guidelines/task.json +27 -0
- package/.trellis/tasks/archive/2026-02/02-02-skills-system/check.jsonl +3 -0
- package/.trellis/tasks/archive/2026-02/02-02-skills-system/debug.jsonl +2 -0
- package/.trellis/tasks/archive/2026-02/02-02-skills-system/implement.jsonl +5 -0
- package/.trellis/tasks/archive/2026-02/02-02-skills-system/prd.md +33 -0
- package/.trellis/tasks/archive/2026-02/02-02-skills-system/task.json +41 -0
- package/.trellis/workflow.md +407 -0
- package/.trellis/workspace/index.md +123 -0
- package/.trellis/workspace/pony/index.md +40 -0
- package/.trellis/workspace/pony/journal-1.md +7 -0
- package/.trellis/worktree.yaml +47 -0
- package/AGENTS.md +18 -0
- package/CLAUDE.md +292 -0
- package/README.md +134 -0
- package/agents/deepspider.md +142 -0
- package/docs/DEBUG.md +42 -0
- package/docs/GUIDE.md +334 -0
- package/docs/PROMPT.md +60 -0
- package/docs/USAGE.md +226 -0
- package/eslint.config.js +51 -0
- package/package.json +78 -0
- package/requirements-crypto.txt +14 -0
- package/src/agent/index.js +97 -0
- package/src/agent/logger.js +164 -0
- package/src/agent/middleware/filterTools.js +64 -0
- package/src/agent/middleware/report.js +79 -0
- package/src/agent/prompts/system.js +315 -0
- package/src/agent/run.js +575 -0
- package/src/agent/skills/anti-detect/SKILL.md +28 -0
- package/src/agent/skills/anti-detect/evolved.md +12 -0
- package/src/agent/skills/captcha/SKILL.md +37 -0
- package/src/agent/skills/captcha/evolved.md +12 -0
- package/src/agent/skills/config.js +30 -0
- package/src/agent/skills/crawler/SKILL.md +9 -0
- package/src/agent/skills/crawler/evolved.md +16 -0
- package/src/agent/skills/dynamic-analysis/SKILL.md +91 -0
- package/src/agent/skills/dynamic-analysis/evolved.md +12 -0
- package/src/agent/skills/env/SKILL.md +72 -0
- package/src/agent/skills/env/evolved.md +12 -0
- package/src/agent/skills/evolve.js +79 -0
- package/src/agent/skills/general/SKILL.md +12 -0
- package/src/agent/skills/general/evolved.md +12 -0
- package/src/agent/skills/js2python/SKILL.md +30 -0
- package/src/agent/skills/js2python/evolved.md +13 -0
- package/src/agent/skills/report/SKILL.md +21 -0
- package/src/agent/skills/report/evolved.md +12 -0
- package/src/agent/skills/sandbox/SKILL.md +22 -0
- package/src/agent/skills/sandbox/evolved.md +16 -0
- package/src/agent/skills/static-analysis/SKILL.md +93 -0
- package/src/agent/skills/static-analysis/evolved.md +12 -0
- package/src/agent/skills/xpath/SKILL.md +119 -0
- package/src/agent/subagents/anti-detect.js +45 -0
- package/src/agent/subagents/captcha.js +51 -0
- package/src/agent/subagents/crawler.js +138 -0
- package/src/agent/subagents/dynamic.js +64 -0
- package/src/agent/subagents/env-agent.js +82 -0
- package/src/agent/subagents/index.js +37 -0
- package/src/agent/subagents/js2python.js +72 -0
- package/src/agent/subagents/sandbox.js +55 -0
- package/src/agent/subagents/static.js +66 -0
- package/src/agent/tools/analysis.js +135 -0
- package/src/agent/tools/analyzer.js +85 -0
- package/src/agent/tools/anti-detect.js +89 -0
- package/src/agent/tools/antidebug.js +64 -0
- package/src/agent/tools/async.js +43 -0
- package/src/agent/tools/browser.js +324 -0
- package/src/agent/tools/captcha.js +223 -0
- package/src/agent/tools/capture.js +179 -0
- package/src/agent/tools/correlate.js +303 -0
- package/src/agent/tools/crawler.js +116 -0
- package/src/agent/tools/cryptohook.js +80 -0
- package/src/agent/tools/debug.js +246 -0
- package/src/agent/tools/deobfuscator.js +90 -0
- package/src/agent/tools/env.js +83 -0
- package/src/agent/tools/envdump.js +92 -0
- package/src/agent/tools/evolve.js +164 -0
- package/src/agent/tools/extract.js +114 -0
- package/src/agent/tools/extractor.js +54 -0
- package/src/agent/tools/file.js +224 -0
- package/src/agent/tools/hook.js +84 -0
- package/src/agent/tools/hookManager.js +178 -0
- package/src/agent/tools/index.js +137 -0
- package/src/agent/tools/nodejs.js +101 -0
- package/src/agent/tools/patch.js +46 -0
- package/src/agent/tools/preprocess.js +71 -0
- package/src/agent/tools/profile.js +122 -0
- package/src/agent/tools/python.js +627 -0
- package/src/agent/tools/report.js +124 -0
- package/src/agent/tools/runtime.js +132 -0
- package/src/agent/tools/sandbox.js +79 -0
- package/src/agent/tools/store.js +73 -0
- package/src/agent/tools/trace.js +74 -0
- package/src/agent/tools/tracing.js +201 -0
- package/src/agent/tools/utils.js +51 -0
- package/src/agent/tools/verify.js +184 -0
- package/src/agent/tools/webcrack.js +109 -0
- package/src/analyzer/ASTAnalyzer.js +387 -0
- package/src/analyzer/CallStackAnalyzer.js +379 -0
- package/src/analyzer/Deobfuscator.js +289 -0
- package/src/analyzer/EncryptionAnalyzer.js +99 -0
- package/src/analyzer/index.js +22 -0
- package/src/browser/EnvBridge.js +186 -0
- package/src/browser/cdp.js +168 -0
- package/src/browser/client.js +197 -0
- package/src/browser/collector.js +444 -0
- package/src/browser/collectors/RequestCryptoLinker.js +109 -0
- package/src/browser/collectors/ResponseSearcher.js +107 -0
- package/src/browser/collectors/ScriptCollector.js +158 -0
- package/src/browser/collectors/index.js +26 -0
- package/src/browser/defaultHooks.js +932 -0
- package/src/browser/hooks/crypto.js +55 -0
- package/src/browser/hooks/index.js +64 -0
- package/src/browser/hooks/native.js +9 -0
- package/src/browser/hooks/network.js +33 -0
- package/src/browser/index.js +42 -0
- package/src/browser/interceptors/NetworkInterceptor.js +116 -0
- package/src/browser/interceptors/ScriptInterceptor.js +76 -0
- package/src/browser/interceptors/index.js +6 -0
- package/src/browser/ui/analysisPanel.js +1782 -0
- package/src/browser/ui/confirmDialog.js +158 -0
- package/src/browser/ui/panel.html +152 -0
- package/src/browser/ui/selector.js +170 -0
- package/src/config/index.js +5 -0
- package/src/config/paths.js +71 -0
- package/src/config/patterns/crypto.js +36 -0
- package/src/config/profiles/chrome.json +71 -0
- package/src/config/profiles/firefox.json +44 -0
- package/src/config/profiles/safari.json +38 -0
- package/src/core/EnvMonitor.js +200 -0
- package/src/core/PatchGenerator.js +278 -0
- package/src/core/Sandbox.js +181 -0
- package/src/env/AntiAntiDebug.js +111 -0
- package/src/env/AsyncHook.js +68 -0
- package/src/env/BrowserAPIList.js +265 -0
- package/src/env/CookieHook.js +48 -0
- package/src/env/CryptoHook.js +205 -0
- package/src/env/EnvCodeGenerator.js +157 -0
- package/src/env/EnvDumper.js +356 -0
- package/src/env/EnvExtractor.js +220 -0
- package/src/env/HookBase.js +618 -0
- package/src/env/NetworkHook.js +159 -0
- package/src/env/modules/bom/history.js +29 -0
- package/src/env/modules/bom/location.js +26 -0
- package/src/env/modules/bom/navigator.js +70 -0
- package/src/env/modules/bom/screen.js +26 -0
- package/src/env/modules/bom/storage.js +23 -0
- package/src/env/modules/dom/document.js +110 -0
- package/src/env/modules/dom/event.js +51 -0
- package/src/env/modules/index.js +34 -0
- package/src/env/modules/webapi/fetch.js +46 -0
- package/src/env/modules/webapi/url.js +47 -0
- package/src/env/modules/webapi/xhr.js +48 -0
- package/src/index.js +27 -0
- package/src/mcp/server.js +89 -0
- package/src/store/DataStore.js +708 -0
- package/src/store/Store.js +158 -0
- package/src/store/Validator.js +24 -0
- package/test/analyze.test.js +90 -0
- package/test/envdump.test.js +74 -0
- package/test/flow.test.js +90 -0
- package/test/hooks.test.js +138 -0
- package/test/plugin.test.js +35 -0
- package/test/refactor-full.test.js +30 -0
- package/test/refactor.test.js +21 -0
- package/test/samples/obfuscated.js +61 -0
- package/test/samples/original.js +66 -0
- package/test/samples/v10_eval_chain.js +52 -0
- package/test/samples/v11_bytecode_vm.js +81 -0
- package/test/samples/v12_polymorphic.js +69 -0
- package/test/samples/v1_ob_basic.js +98 -0
- package/test/samples/v2_ob_advanced.js +99 -0
- package/test/samples/v3_jjencode.js +77 -0
- package/test/samples/v4_aaencode.js +73 -0
- package/test/samples/v5_control_flow.js +86 -0
- package/test/samples/v6_string_encryption.js +71 -0
- package/test/samples/v7_jsvmp.js +83 -0
- package/test/samples/v8_anti_debug.js +79 -0
- package/test/samples/v9_proxy_trap.js +49 -0
- package/test/samples.test.js +96 -0
- package/test/webcrack.test.js +55 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSpider - 浏览器交互工具
|
|
3
|
+
* 混合实现:复杂交互用 Playwright,简单操作用 CDP
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { tool } from '@langchain/core/tools';
|
|
8
|
+
import { getBrowser } from '../../browser/index.js';
|
|
9
|
+
import { getScreenshotPath } from './utils.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 通过 CDP 执行 JS
|
|
13
|
+
*/
|
|
14
|
+
async function cdpEvaluate(browser, expression, returnByValue = true) {
|
|
15
|
+
const cdp = await browser.getCDPSession();
|
|
16
|
+
if (!cdp) throw new Error('CDP session not available');
|
|
17
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
18
|
+
expression,
|
|
19
|
+
returnByValue,
|
|
20
|
+
awaitPromise: true,
|
|
21
|
+
});
|
|
22
|
+
if (result.exceptionDetails) {
|
|
23
|
+
throw new Error(result.exceptionDetails.text || 'CDP evaluate error');
|
|
24
|
+
}
|
|
25
|
+
return result.result?.value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 点击元素 - Playwright + force
|
|
30
|
+
*/
|
|
31
|
+
export const clickElement = tool(
|
|
32
|
+
async ({ selector }) => {
|
|
33
|
+
const browser = await getBrowser();
|
|
34
|
+
const page = browser.getPage();
|
|
35
|
+
await page.click(selector, { force: true });
|
|
36
|
+
return JSON.stringify({ success: true, selector });
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'click_element',
|
|
40
|
+
description: '点击页面元素',
|
|
41
|
+
schema: z.object({
|
|
42
|
+
selector: z.string().describe('CSS 选择器'),
|
|
43
|
+
}),
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 填充输入框 - Playwright + force
|
|
49
|
+
*/
|
|
50
|
+
export const fillInput = tool(
|
|
51
|
+
async ({ selector, value }) => {
|
|
52
|
+
const browser = await getBrowser();
|
|
53
|
+
const page = browser.getPage();
|
|
54
|
+
await page.fill(selector, value, { force: true });
|
|
55
|
+
return JSON.stringify({ success: true, selector, value });
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'fill_input',
|
|
59
|
+
description: '填充输入框',
|
|
60
|
+
schema: z.object({
|
|
61
|
+
selector: z.string().describe('CSS 选择器'),
|
|
62
|
+
value: z.string().describe('填充值'),
|
|
63
|
+
}),
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 截图
|
|
69
|
+
*/
|
|
70
|
+
export const takeScreenshot = tool(
|
|
71
|
+
async ({ filename }) => {
|
|
72
|
+
const browser = await getBrowser();
|
|
73
|
+
const page = browser.getPage();
|
|
74
|
+
const savePath = getScreenshotPath(filename);
|
|
75
|
+
await page.screenshot({ path: savePath, fullPage: true });
|
|
76
|
+
console.log('[trigger] screenshot saved to:', savePath);
|
|
77
|
+
return JSON.stringify({ success: true, filePath: savePath });
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'take_screenshot',
|
|
81
|
+
description: '截取页面截图,自动保存到 output/screenshots 目录',
|
|
82
|
+
schema: z.object({
|
|
83
|
+
filename: z.string().optional().describe('文件名(可选,默认自动生成)'),
|
|
84
|
+
}),
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 等待元素
|
|
90
|
+
*/
|
|
91
|
+
export const waitForSelector = tool(
|
|
92
|
+
async ({ selector, timeout, state }) => {
|
|
93
|
+
const browser = await getBrowser();
|
|
94
|
+
const page = browser.getPage();
|
|
95
|
+
await page.waitForSelector(selector, { timeout, state });
|
|
96
|
+
return JSON.stringify({ success: true, selector, state });
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'wait_for_selector',
|
|
100
|
+
description: '等待元素出现或消失',
|
|
101
|
+
schema: z.object({
|
|
102
|
+
selector: z.string().describe('CSS 选择器'),
|
|
103
|
+
timeout: z.number().default(30000).describe('超时时间(ms)'),
|
|
104
|
+
state: z.enum(['attached', 'detached', 'visible', 'hidden']).default('attached').describe('等待状态:attached(DOM中存在)、visible(可见)、detached(从DOM移除)、hidden(隐藏)'),
|
|
105
|
+
}),
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 刷新页面 - CDP
|
|
111
|
+
*/
|
|
112
|
+
export const reloadPage = tool(
|
|
113
|
+
async () => {
|
|
114
|
+
const browser = await getBrowser();
|
|
115
|
+
const cdp = await browser.getCDPSession();
|
|
116
|
+
await cdp.send('Page.reload');
|
|
117
|
+
const url = await cdpEvaluate(browser, 'location.href');
|
|
118
|
+
return JSON.stringify({ success: true, url });
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'reload_page',
|
|
122
|
+
description: '刷新当前页面',
|
|
123
|
+
schema: z.object({}),
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 后退 - CDP
|
|
129
|
+
*/
|
|
130
|
+
export const goBack = tool(
|
|
131
|
+
async () => {
|
|
132
|
+
const browser = await getBrowser();
|
|
133
|
+
const cdp = await browser.getCDPSession();
|
|
134
|
+
const history = await cdp.send('Page.getNavigationHistory');
|
|
135
|
+
if (history.currentIndex > 0) {
|
|
136
|
+
const entry = history.entries[history.currentIndex - 1];
|
|
137
|
+
await cdp.send('Page.navigateToHistoryEntry', { entryId: entry.id });
|
|
138
|
+
}
|
|
139
|
+
const url = await cdpEvaluate(browser, 'location.href');
|
|
140
|
+
return JSON.stringify({ success: true, url });
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'go_back',
|
|
144
|
+
description: '浏览器后退',
|
|
145
|
+
schema: z.object({}),
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 前进 - CDP
|
|
151
|
+
*/
|
|
152
|
+
export const goForward = tool(
|
|
153
|
+
async () => {
|
|
154
|
+
const browser = await getBrowser();
|
|
155
|
+
const cdp = await browser.getCDPSession();
|
|
156
|
+
const history = await cdp.send('Page.getNavigationHistory');
|
|
157
|
+
if (history.currentIndex < history.entries.length - 1) {
|
|
158
|
+
const entry = history.entries[history.currentIndex + 1];
|
|
159
|
+
await cdp.send('Page.navigateToHistoryEntry', { entryId: entry.id });
|
|
160
|
+
}
|
|
161
|
+
const url = await cdpEvaluate(browser, 'location.href');
|
|
162
|
+
return JSON.stringify({ success: true, url });
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'go_forward',
|
|
166
|
+
description: '浏览器前进',
|
|
167
|
+
schema: z.object({}),
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 滚动页面 - CDP
|
|
173
|
+
*/
|
|
174
|
+
export const scrollPage = tool(
|
|
175
|
+
async ({ direction, distance }) => {
|
|
176
|
+
const browser = await getBrowser();
|
|
177
|
+
const cdp = await browser.getCDPSession();
|
|
178
|
+
const deltaY = direction === 'up' ? -distance : distance;
|
|
179
|
+
await cdp.send('Input.dispatchMouseEvent', {
|
|
180
|
+
type: 'mouseWheel', x: 100, y: 100, deltaX: 0, deltaY
|
|
181
|
+
});
|
|
182
|
+
return JSON.stringify({ success: true, direction, distance });
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'scroll_page',
|
|
186
|
+
description: '滚动页面',
|
|
187
|
+
schema: z.object({
|
|
188
|
+
direction: z.enum(['up', 'down']).describe('滚动方向'),
|
|
189
|
+
distance: z.number().default(500).describe('滚动距离(px)'),
|
|
190
|
+
}),
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 按键 - Playwright
|
|
196
|
+
*/
|
|
197
|
+
export const pressKey = tool(
|
|
198
|
+
async ({ key }) => {
|
|
199
|
+
const browser = await getBrowser();
|
|
200
|
+
const page = browser.getPage();
|
|
201
|
+
await page.keyboard.press(key);
|
|
202
|
+
return JSON.stringify({ success: true, key });
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'press_key',
|
|
206
|
+
description: '按下键盘按键,如 Enter、Escape、Tab、ArrowDown 等',
|
|
207
|
+
schema: z.object({
|
|
208
|
+
key: z.string().describe('按键名称'),
|
|
209
|
+
}),
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 悬停元素 - Playwright + force
|
|
215
|
+
*/
|
|
216
|
+
export const hoverElement = tool(
|
|
217
|
+
async ({ selector }) => {
|
|
218
|
+
const browser = await getBrowser();
|
|
219
|
+
const page = browser.getPage();
|
|
220
|
+
await page.hover(selector, { force: true });
|
|
221
|
+
return JSON.stringify({ success: true, selector });
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: 'hover_element',
|
|
225
|
+
description: '鼠标悬停在元素上',
|
|
226
|
+
schema: z.object({
|
|
227
|
+
selector: z.string().describe('CSS 选择器'),
|
|
228
|
+
}),
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 获取页面信息 - CDP
|
|
234
|
+
*/
|
|
235
|
+
export const getPageInfo = tool(
|
|
236
|
+
async () => {
|
|
237
|
+
const browser = await getBrowser();
|
|
238
|
+
const info = await cdpEvaluate(browser, `
|
|
239
|
+
({ url: location.href, title: document.title })
|
|
240
|
+
`);
|
|
241
|
+
return JSON.stringify(info);
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: 'get_page_info',
|
|
245
|
+
description: '获取当前页面 URL 和标题',
|
|
246
|
+
schema: z.object({}),
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 获取浏览器 Cookie - CDP
|
|
252
|
+
*/
|
|
253
|
+
export const getCookies = tool(
|
|
254
|
+
async ({ domain, format }) => {
|
|
255
|
+
const browser = await getBrowser();
|
|
256
|
+
const cdp = await browser.getCDPSession();
|
|
257
|
+
|
|
258
|
+
// 获取当前页面 URL 用于过滤
|
|
259
|
+
const currentUrl = await cdpEvaluate(browser, 'location.href');
|
|
260
|
+
const urls = domain ? undefined : [currentUrl];
|
|
261
|
+
|
|
262
|
+
const result = await cdp.send('Network.getCookies', { urls });
|
|
263
|
+
let cookies = result.cookies || [];
|
|
264
|
+
|
|
265
|
+
// 按域名过滤
|
|
266
|
+
if (domain) {
|
|
267
|
+
cookies = cookies.filter(c => c.domain.includes(domain));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 根据格式返回
|
|
271
|
+
if (format === 'header') {
|
|
272
|
+
// 返回可直接用于请求头的格式
|
|
273
|
+
const cookieStr = cookies.map(c => `${c.name}=${c.value}`).join('; ');
|
|
274
|
+
return JSON.stringify({ success: true, cookie: cookieStr, count: cookies.length });
|
|
275
|
+
} else if (format === 'dict') {
|
|
276
|
+
// 返回字典格式,方便 Python requests 使用
|
|
277
|
+
const cookieDict = {};
|
|
278
|
+
cookies.forEach(c => { cookieDict[c.name] = c.value; });
|
|
279
|
+
return JSON.stringify({ success: true, cookies: cookieDict, count: cookies.length });
|
|
280
|
+
} else {
|
|
281
|
+
// 返回完整信息
|
|
282
|
+
return JSON.stringify({
|
|
283
|
+
success: true,
|
|
284
|
+
cookies: cookies.map(c => ({
|
|
285
|
+
name: c.name,
|
|
286
|
+
value: c.value,
|
|
287
|
+
domain: c.domain,
|
|
288
|
+
path: c.path,
|
|
289
|
+
expires: c.expires,
|
|
290
|
+
httpOnly: c.httpOnly,
|
|
291
|
+
secure: c.secure,
|
|
292
|
+
})),
|
|
293
|
+
count: cookies.length
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'get_cookies',
|
|
299
|
+
description: `获取浏览器 Cookie,用于端到端验证时构造请求。
|
|
300
|
+
|
|
301
|
+
返回格式:
|
|
302
|
+
- full: 完整信息(默认)
|
|
303
|
+
- header: Cookie 请求头格式,如 "name1=value1; name2=value2"
|
|
304
|
+
- dict: 字典格式,方便 Python requests.cookies 使用`,
|
|
305
|
+
schema: z.object({
|
|
306
|
+
domain: z.string().optional().describe('按域名过滤(可选)'),
|
|
307
|
+
format: z.enum(['full', 'header', 'dict']).default('full').describe('返回格式'),
|
|
308
|
+
}),
|
|
309
|
+
}
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
export const browserTools = [
|
|
313
|
+
clickElement,
|
|
314
|
+
fillInput,
|
|
315
|
+
waitForSelector,
|
|
316
|
+
reloadPage,
|
|
317
|
+
goBack,
|
|
318
|
+
goForward,
|
|
319
|
+
scrollPage,
|
|
320
|
+
pressKey,
|
|
321
|
+
hoverElement,
|
|
322
|
+
getPageInfo,
|
|
323
|
+
getCookies,
|
|
324
|
+
];
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSpider - 验证码处理工具
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { tool } from '@langchain/core/tools';
|
|
7
|
+
import { getBrowser } from '../../browser/index.js';
|
|
8
|
+
|
|
9
|
+
export const captchaDetect = tool(
|
|
10
|
+
async ({ selector }) => {
|
|
11
|
+
try {
|
|
12
|
+
const browser = await getBrowser();
|
|
13
|
+
const page = browser.getPage();
|
|
14
|
+
|
|
15
|
+
// 检测页面中的验证码类型
|
|
16
|
+
const result = await page.evaluate((sel) => {
|
|
17
|
+
const container = sel ? document.querySelector(sel) : document.body;
|
|
18
|
+
if (!container) return { type: 'none', reason: '未找到容器' };
|
|
19
|
+
|
|
20
|
+
const html = container.innerHTML.toLowerCase();
|
|
21
|
+
const indicators = {
|
|
22
|
+
slide: ['slide', 'slider', '滑块', 'drag', 'geetest'],
|
|
23
|
+
click: ['click', '点击', '点选', 'verify-img'],
|
|
24
|
+
image: ['captcha', 'code', '验证码', 'vcode'],
|
|
25
|
+
sms: ['sms', '短信', '手机', 'phone'],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
for (const [type, keywords] of Object.entries(indicators)) {
|
|
29
|
+
if (keywords.some(k => html.includes(k))) {
|
|
30
|
+
return { type, keywords: keywords.filter(k => html.includes(k)) };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 检查是否有验证码图片
|
|
35
|
+
const imgs = container.querySelectorAll('img');
|
|
36
|
+
for (const img of imgs) {
|
|
37
|
+
const src = img.src || '';
|
|
38
|
+
if (src.includes('captcha') || src.includes('code') || src.includes('verify')) {
|
|
39
|
+
return { type: 'image', src };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { type: 'none', reason: '未检测到验证码' };
|
|
44
|
+
}, selector);
|
|
45
|
+
|
|
46
|
+
return JSON.stringify({ success: true, ...result });
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'captcha_detect',
|
|
53
|
+
description: '检测页面中的验证码类型(滑块/点选/图片/短信)',
|
|
54
|
+
schema: z.object({
|
|
55
|
+
selector: z.string().optional().describe('验证码容器选择器,不填则检测整个页面'),
|
|
56
|
+
}),
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export const captchaOcr = tool(
|
|
61
|
+
async ({ image_selector, image_base64 }) => {
|
|
62
|
+
try {
|
|
63
|
+
let imageData = image_base64;
|
|
64
|
+
|
|
65
|
+
if (!imageData && image_selector) {
|
|
66
|
+
const browser = await getBrowser();
|
|
67
|
+
const page = browser.getPage();
|
|
68
|
+
const element = await page.$(image_selector);
|
|
69
|
+
if (element) {
|
|
70
|
+
const buffer = await element.screenshot();
|
|
71
|
+
imageData = buffer.toString('base64');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!imageData) {
|
|
76
|
+
return JSON.stringify({ success: false, error: '无法获取验证码图片' });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// TODO: 集成 ddddocr 或打码平台
|
|
80
|
+
// 当前返回占位结果
|
|
81
|
+
return JSON.stringify({
|
|
82
|
+
success: true,
|
|
83
|
+
text: '',
|
|
84
|
+
message: '需要集成 OCR 服务(ddddocr 或打码平台)',
|
|
85
|
+
image_length: imageData.length,
|
|
86
|
+
});
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'captcha_ocr',
|
|
93
|
+
description: 'OCR 识别图片验证码',
|
|
94
|
+
schema: z.object({
|
|
95
|
+
image_selector: z.string().optional().describe('验证码图片选择器'),
|
|
96
|
+
image_base64: z.string().optional().describe('验证码图片 base64(优先使用)'),
|
|
97
|
+
}),
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
export const captchaSlideDetect = tool(
|
|
102
|
+
async ({ bg_selector, slide_selector }) => {
|
|
103
|
+
try {
|
|
104
|
+
const browser = await getBrowser();
|
|
105
|
+
const page = browser.getPage();
|
|
106
|
+
|
|
107
|
+
// 获取背景图和滑块图
|
|
108
|
+
const bgElement = await page.$(bg_selector);
|
|
109
|
+
const slideElement = slide_selector ? await page.$(slide_selector) : null;
|
|
110
|
+
|
|
111
|
+
if (!bgElement) {
|
|
112
|
+
return JSON.stringify({ success: false, error: '未找到背景图' });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const bgBuffer = await bgElement.screenshot();
|
|
116
|
+
|
|
117
|
+
// TODO: 使用 OpenCV 或其他方式检测缺口位置
|
|
118
|
+
// 当前返回占位结果
|
|
119
|
+
return JSON.stringify({
|
|
120
|
+
success: true,
|
|
121
|
+
gap_x: 0,
|
|
122
|
+
message: '需要集成缺口检测算法',
|
|
123
|
+
bg_size: bgBuffer.length,
|
|
124
|
+
});
|
|
125
|
+
} catch (e) {
|
|
126
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'captcha_slide_detect',
|
|
131
|
+
description: '检测滑块验证码的缺口位置',
|
|
132
|
+
schema: z.object({
|
|
133
|
+
bg_selector: z.string().describe('背景图选择器'),
|
|
134
|
+
slide_selector: z.string().optional().describe('滑块图选择器'),
|
|
135
|
+
}),
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
export const captchaSlideExecute = tool(
|
|
140
|
+
async ({ slider_selector, distance, duration = 500 }) => {
|
|
141
|
+
try {
|
|
142
|
+
const browser = await getBrowser();
|
|
143
|
+
const page = browser.getPage();
|
|
144
|
+
|
|
145
|
+
const slider = await page.$(slider_selector);
|
|
146
|
+
if (!slider) {
|
|
147
|
+
return JSON.stringify({ success: false, error: '未找到滑块元素' });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const box = await slider.boundingBox();
|
|
151
|
+
if (!box) {
|
|
152
|
+
return JSON.stringify({ success: false, error: '无法获取滑块位置' });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const startX = box.x + box.width / 2;
|
|
156
|
+
const startY = box.y + box.height / 2;
|
|
157
|
+
|
|
158
|
+
await page.mouse.move(startX, startY);
|
|
159
|
+
await page.mouse.down();
|
|
160
|
+
|
|
161
|
+
// 模拟人类拖动:慢-快-慢
|
|
162
|
+
const steps = 20;
|
|
163
|
+
for (let i = 1; i <= steps; i++) {
|
|
164
|
+
const progress = i / steps;
|
|
165
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
166
|
+
const x = startX + distance * eased;
|
|
167
|
+
const y = startY + (Math.random() - 0.5) * 2;
|
|
168
|
+
await page.mouse.move(x, y);
|
|
169
|
+
await page.waitForTimeout(duration / steps);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await page.mouse.up();
|
|
173
|
+
return JSON.stringify({ success: true, distance, duration });
|
|
174
|
+
} catch (e) {
|
|
175
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'captcha_slide_execute',
|
|
180
|
+
description: '执行滑块拖动操作',
|
|
181
|
+
schema: z.object({
|
|
182
|
+
slider_selector: z.string().describe('滑块元素选择器'),
|
|
183
|
+
distance: z.number().describe('拖动距离(像素)'),
|
|
184
|
+
duration: z.number().optional().default(500).describe('拖动时长(毫秒)'),
|
|
185
|
+
}),
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
export const captchaClickExecute = tool(
|
|
190
|
+
async ({ points }) => {
|
|
191
|
+
try {
|
|
192
|
+
const browser = await getBrowser();
|
|
193
|
+
const page = browser.getPage();
|
|
194
|
+
|
|
195
|
+
for (const point of points) {
|
|
196
|
+
await page.mouse.click(point.x, point.y);
|
|
197
|
+
await page.waitForTimeout(200 + Math.random() * 300);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return JSON.stringify({ success: true, clicked: points.length });
|
|
201
|
+
} catch (e) {
|
|
202
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'captcha_click_execute',
|
|
207
|
+
description: '执行点选验证码点击操作',
|
|
208
|
+
schema: z.object({
|
|
209
|
+
points: z.array(z.object({
|
|
210
|
+
x: z.number(),
|
|
211
|
+
y: z.number(),
|
|
212
|
+
})).describe('点击坐标数组'),
|
|
213
|
+
}),
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
export const captchaTools = [
|
|
218
|
+
captchaDetect,
|
|
219
|
+
captchaOcr,
|
|
220
|
+
captchaSlideDetect,
|
|
221
|
+
captchaSlideExecute,
|
|
222
|
+
captchaClickExecute,
|
|
223
|
+
];
|