deepspider 0.2.6 → 0.2.9

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.
@@ -6,9 +6,9 @@
6
6
 
7
7
  import { z } from 'zod';
8
8
  import { tool } from '@langchain/core/tools';
9
- import { writeFileSync } from 'fs';
10
- import { join } from 'path';
11
- import { PATHS, ensureDir, getReportDir } from '../../config/paths.js';
9
+ import { writeFileSync, readFileSync, existsSync } from 'fs';
10
+ import { join, basename } from 'path';
11
+ import { PATHS, ensureDir, DEEPSPIDER_HOME } from '../../config/paths.js';
12
12
 
13
13
  const OUTPUT_DIR = PATHS.REPORTS_DIR;
14
14
 
@@ -25,6 +25,31 @@ function escapeHtml(str) {
25
25
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
26
26
  }
27
27
 
28
+ /**
29
+ * 从文件路径读取代码内容
30
+ * 支持相对路径和绝对路径
31
+ */
32
+ function readCodeFromFile(filePath) {
33
+ if (!filePath) return null;
34
+
35
+ let fullPath = filePath;
36
+ if (!fullPath.startsWith('/')) {
37
+ fullPath = join(PATHS.OUTPUT_DIR, filePath);
38
+ }
39
+
40
+ if (!existsSync(fullPath)) {
41
+ console.warn('[report] 代码文件不存在:', fullPath);
42
+ return null;
43
+ }
44
+
45
+ try {
46
+ return readFileSync(fullPath, 'utf-8');
47
+ } catch (e) {
48
+ console.warn('[report] 读取代码文件失败:', e.message);
49
+ return null;
50
+ }
51
+ }
52
+
28
53
  /**
29
54
  * 生成 HTML 报告页面
30
55
  */
@@ -72,34 +97,57 @@ function generateHtmlPage(title, markdown, pythonCode, jsCode) {
72
97
 
73
98
  /**
74
99
  * 保存分析报告
100
+ * 支持两种模式:
101
+ * 1. 传入代码文件路径(推荐)- pythonCodeFile/jsCodeFile
102
+ * 2. 传入代码内容(兼容)- pythonCode/jsCode
75
103
  */
76
104
  export const saveAnalysisReport = tool(
77
- async ({ domain, title, markdown, pythonCode, jsCode }) => {
105
+ async ({ domain, title, markdown, pythonCode, pythonCodeFile, jsCode, jsCodeFile }) => {
78
106
  try {
79
107
  const domainDir = join(OUTPUT_DIR, extractDomain(domain));
80
108
  ensureDir(domainDir);
81
109
 
82
110
  const paths = {};
83
111
 
112
+ // 优先从文件读取代码
113
+ let finalPythonCode = pythonCode;
114
+ let finalJsCode = jsCode;
115
+
116
+ if (pythonCodeFile) {
117
+ const code = readCodeFromFile(pythonCodeFile);
118
+ if (code) {
119
+ finalPythonCode = code;
120
+ console.log('[report] 从文件读取 Python 代码:', pythonCodeFile);
121
+ }
122
+ }
123
+
124
+ if (jsCodeFile) {
125
+ const code = readCodeFromFile(jsCodeFile);
126
+ if (code) {
127
+ finalJsCode = code;
128
+ console.log('[report] 从文件读取 JS 代码:', jsCodeFile);
129
+ }
130
+ }
131
+
84
132
  // 保存 Markdown
85
133
  paths.markdown = join(domainDir, 'analysis.md');
86
134
  writeFileSync(paths.markdown, markdown, 'utf-8');
87
135
 
88
136
  // 保存 Python 代码
89
- if (pythonCode) {
137
+ if (finalPythonCode) {
90
138
  paths.python = join(domainDir, 'decrypt.py');
91
- writeFileSync(paths.python, pythonCode, 'utf-8');
139
+ writeFileSync(paths.python, finalPythonCode, 'utf-8');
92
140
  }
93
141
 
94
142
  // 保存 JS 代码
95
- if (jsCode) {
143
+ if (finalJsCode) {
96
144
  paths.javascript = join(domainDir, 'decrypt.js');
97
- writeFileSync(paths.javascript, jsCode, 'utf-8');
145
+ writeFileSync(paths.javascript, finalJsCode, 'utf-8');
98
146
  }
99
147
 
100
148
  // 生成 HTML
101
149
  paths.html = join(domainDir, 'report.html');
102
- const html = generateHtmlPage(title || domain, markdown, pythonCode, jsCode);
150
+ const html = generateHtmlPage(title || domain, markdown, finalPythonCode, finalJsCode);
103
151
  writeFileSync(paths.html, html, 'utf-8');
104
152
 
105
153
  console.log('[report] 已保存:', domainDir);
@@ -110,13 +158,15 @@ export const saveAnalysisReport = tool(
110
158
  },
111
159
  {
112
160
  name: 'save_analysis_report',
113
- description: '保存加密分析报告。分析完成后必须调用,保存 Markdown、HTML 和代码文件。',
161
+ description: `保存分析报告。推荐先用 artifact_save 保存代码文件,再传入文件路径。`,
114
162
  schema: z.object({
115
- domain: z.string().describe('网站域名或 URL'),
163
+ domain: z.string().describe('网站域名'),
116
164
  title: z.string().optional().describe('报告标题'),
117
- markdown: z.string().describe('Markdown 分析报告'),
118
- pythonCode: z.string().describe('Python 解密代码(必须提供完整可运行代码)'),
119
- jsCode: z.string().optional().describe('JavaScript 解密代码'),
165
+ markdown: z.string().describe('Markdown 摘要'),
166
+ pythonCodeFile: z.string().optional().describe('Python 代码文件路径(推荐)'),
167
+ pythonCode: z.string().optional().describe('Python 代码内容(不推荐)'),
168
+ jsCodeFile: z.string().optional().describe('JS 代码文件路径'),
169
+ jsCode: z.string().optional().describe('JS 代码内容'),
120
170
  }),
121
171
  }
122
172
  );
@@ -12,10 +12,12 @@ let hookManager = null;
12
12
 
13
13
  /**
14
14
  * 标记 Hook 已注入(供外部调用)
15
+ * 注意:Hook 脚本由 browser/client.js 自动注入,此处仅标记状态
15
16
  */
16
17
  export function markHookInjected() {
17
18
  if (!hookManager) {
18
19
  hookManager = new HookManager();
20
+ hookManager.markInjected();
19
21
  }
20
22
  }
21
23
 
@@ -25,13 +27,13 @@ export function markHookInjected() {
25
27
  export const launchBrowser = tool(
26
28
  async ({ headless }) => {
27
29
  const browser = await getBrowser({ headless });
28
- // 检查是否已经注入过 Hook
30
+ // Hook 已由 browser/client.js 自动注入,此处仅标记状态
29
31
  if (!hookManager) {
30
32
  hookManager = new HookManager();
31
- await hookManager.inject(browser.getPage());
32
- return JSON.stringify({ success: true, message: '浏览器已启动,Hook 已注入' });
33
+ hookManager.markInjected();
34
+ hookManager.bindConsole(browser.getPage());
33
35
  }
34
- return JSON.stringify({ success: true, message: '浏览器已就绪' });
36
+ return JSON.stringify({ success: true, message: '浏览器已就绪,Hook 已注入' });
35
37
  },
36
38
  {
37
39
  name: 'launch_browser',
@@ -7,7 +7,6 @@ import { join } from 'path';
7
7
  import { PATHS, ensureDir, generateFilename } from '../../config/paths.js';
8
8
 
9
9
  // 导出路径常量(兼容旧代码)
10
- export const OUTPUT_DIR = PATHS.OUTPUT_DIR;
11
10
  export const SCREENSHOTS_DIR = PATHS.SCREENSHOTS_DIR;
12
11
  export const REPORTS_DIR = PATHS.REPORTS_DIR;
13
12
  export const UNPACKED_DIR = PATHS.UNPACKED_DIR;
@@ -185,11 +185,14 @@ function getJSONHooks() {
185
185
 
186
186
  const OriginalParse = JSON.parse;
187
187
  const OriginalStringify = JSON.stringify;
188
- const MIN_LOG_LENGTH = 50; // 只记录长度超过 50 的数据
188
+ const MIN_LOG_LENGTH = 50;
189
+
190
+ // 内部数据标记 - 跳过 DeepSpider 内部操作
191
+ const INTERNAL_MARKER = '"__ds__":true';
189
192
 
190
193
  JSON.parse = deepspider.native(function(text, reviver) {
191
194
  const textStr = String(text);
192
- if (textStr.length >= MIN_LOG_LENGTH) {
195
+ if (textStr.length >= MIN_LOG_LENGTH && !textStr.includes(INTERNAL_MARKER)) {
193
196
  deepspider.log('json', {
194
197
  action: 'parse',
195
198
  input: textStr.slice(0, 200),
@@ -201,7 +204,7 @@ function getJSONHooks() {
201
204
 
202
205
  JSON.stringify = deepspider.native(function(value, replacer, space) {
203
206
  const result = OriginalStringify.call(JSON, value, replacer, space);
204
- if (result && result.length >= MIN_LOG_LENGTH) {
207
+ if (result && result.length >= MIN_LOG_LENGTH && !result.includes(INTERNAL_MARKER)) {
205
208
  deepspider.log('json', {
206
209
  action: 'stringify',
207
210
  output: result.slice(0, 200),
@@ -297,18 +300,27 @@ function getStorageHooks() {
297
300
  const deepspider = window.__deepspider__;
298
301
  if (!deepspider) return;
299
302
 
303
+ // 内部使用的 key 前缀,不记录日志
304
+ const INTERNAL_PREFIX = 'deepspider_';
305
+
300
306
  function hookStorage(storage, name) {
301
307
  const origGet = storage.getItem.bind(storage);
302
308
  const origSet = storage.setItem.bind(storage);
303
309
 
304
310
  storage.getItem = deepspider.native(function(key) {
305
311
  const value = origGet(key);
306
- deepspider.log('storage', { action: 'get', storage: name, key: key, value: value?.slice(0, 100) });
312
+ // 跳过内部 key
313
+ if (!key.startsWith(INTERNAL_PREFIX)) {
314
+ deepspider.log('storage', { action: 'get', storage: name, key: key, value: value?.slice(0, 100) });
315
+ }
307
316
  return value;
308
317
  }, origGet);
309
318
 
310
319
  storage.setItem = deepspider.native(function(key, value) {
311
- deepspider.log('storage', { action: 'set', storage: name, key: key, value: String(value).slice(0, 100) });
320
+ // 跳过内部 key
321
+ if (!key.startsWith(INTERNAL_PREFIX)) {
322
+ deepspider.log('storage', { action: 'set', storage: name, key: key, value: String(value).slice(0, 100) });
323
+ }
312
324
  return origSet(key, value);
313
325
  }, origSet);
314
326
  }
@@ -380,41 +392,327 @@ function getWebSocketHooks() {
380
392
 
381
393
  /**
382
394
  * Webpack 模块 Hook - 检测闭包内的加密库
395
+ * 通过特征检测而非变量名来识别加密库
383
396
  */
384
397
  function getWebpackHooks() {
385
398
  return `
386
- // === Webpack Hook ===
399
+ // === Webpack Module Hook ===
387
400
  (function() {
388
401
  const deepspider = window.__deepspider__;
389
402
  if (!deepspider) return;
390
403
 
391
- // 加密特征检测
392
- const cryptoPatterns = [
393
- { name: 'CryptoJS', pattern: /CryptoJS|crypto-js/i },
394
- { name: 'MD5', pattern: /\\bmd5\\b/i },
395
- { name: 'SHA', pattern: /\\bsha(1|256|512)\\b/i },
396
- { name: 'AES', pattern: /\\baes\\b/i },
397
- { name: 'RSA', pattern: /\\brsa\\b/i },
398
- { name: 'Base64', pattern: /base64|btoa|atob/i }
399
- ];
404
+ // 已 Hook 的对象集合
405
+ const hookedObjects = new WeakSet();
406
+
407
+ // === 特征检测函数 ===
408
+
409
+ // 检测 CryptoJS 特征
410
+ function isCryptoJS(obj) {
411
+ if (!obj || typeof obj !== 'object') return false;
412
+ // CryptoJS 特征:有 AES/DES/MD5 等属性,且有 enc.Utf8
413
+ return (obj.AES && obj.AES.encrypt && obj.AES.decrypt) ||
414
+ (obj.enc && obj.enc.Utf8 && obj.enc.Hex) ||
415
+ (obj.MD5 && typeof obj.MD5 === 'function') ||
416
+ (obj.SHA256 && typeof obj.SHA256 === 'function');
417
+ }
418
+
419
+ // 检测 JSEncrypt 特征
420
+ function isJSEncrypt(obj) {
421
+ if (!obj || typeof obj !== 'function') return false;
422
+ const proto = obj.prototype;
423
+ return proto && proto.encrypt && proto.decrypt && proto.setPublicKey;
424
+ }
425
+
426
+ // 检测 SM2/SM3/SM4 国密特征
427
+ function isSMCrypto(obj) {
428
+ if (!obj || typeof obj !== 'object') return false;
429
+ return (obj.doEncrypt && obj.doDecrypt) ||
430
+ (obj.sm2 && obj.sm3) ||
431
+ (typeof obj.encrypt === 'function' && obj.cipherMode !== undefined);
432
+ }
433
+
434
+ // 检测 node-forge 特征
435
+ function isForge(obj) {
436
+ if (!obj || typeof obj !== 'object') return false;
437
+ return (obj.cipher && obj.md && obj.util) ||
438
+ (obj.pki && obj.pki.rsa);
439
+ }
440
+
441
+ // === Hook 函数 ===
442
+
443
+ // Hook CryptoJS 对象
444
+ function hookCryptoJSObject(obj, source) {
445
+ if (hookedObjects.has(obj)) return;
446
+ hookedObjects.add(obj);
447
+
448
+ // Hook 对称加密
449
+ ['AES', 'DES', 'TripleDES', 'RC4', 'Rabbit'].forEach(function(cipher) {
450
+ if (!obj[cipher]) return;
451
+ ['encrypt', 'decrypt'].forEach(function(method) {
452
+ if (!obj[cipher][method]) return;
453
+ const original = obj[cipher][method];
454
+ obj[cipher][method] = deepspider.native(function(data, key, options) {
455
+ const entry = deepspider.log('crypto', {
456
+ algo: cipher + '.' + method,
457
+ source: source,
458
+ data: String(data).slice(0, 100),
459
+ keyLen: key ? String(key).length : 0
460
+ });
461
+ deepspider.linkCrypto(entry);
462
+ return original.apply(this, arguments);
463
+ }, original);
464
+ });
465
+ });
466
+
467
+ // Hook 哈希算法
468
+ ['MD5', 'SHA1', 'SHA256', 'SHA512', 'SHA3', 'RIPEMD160'].forEach(function(algo) {
469
+ if (!obj[algo] || typeof obj[algo] !== 'function') return;
470
+ const original = obj[algo];
471
+ obj[algo] = deepspider.native(function() {
472
+ const entry = deepspider.log('crypto', {
473
+ algo: algo,
474
+ source: source,
475
+ inputLen: arguments[0] ? String(arguments[0]).length : 0
476
+ });
477
+ deepspider.linkCrypto(entry);
478
+ return original.apply(this, arguments);
479
+ }, original);
480
+ });
481
+
482
+ // Hook HMAC
483
+ ['HmacMD5', 'HmacSHA1', 'HmacSHA256', 'HmacSHA512'].forEach(function(algo) {
484
+ if (!obj[algo] || typeof obj[algo] !== 'function') return;
485
+ const original = obj[algo];
486
+ obj[algo] = deepspider.native(function() {
487
+ const entry = deepspider.log('crypto', {
488
+ algo: algo,
489
+ source: source,
490
+ inputLen: arguments[0] ? String(arguments[0]).length : 0
491
+ });
492
+ deepspider.linkCrypto(entry);
493
+ return original.apply(this, arguments);
494
+ }, original);
495
+ });
496
+
497
+ console.log('[DeepSpider] CryptoJS Hook 已启用 (来源: ' + source + ')');
498
+ }
499
+
500
+ // Hook JSEncrypt 构造函数
501
+ function hookJSEncryptObject(JSEncrypt, source) {
502
+ if (hookedObjects.has(JSEncrypt)) return;
503
+ hookedObjects.add(JSEncrypt);
504
+
505
+ const proto = JSEncrypt.prototype;
506
+ if (proto.encrypt) {
507
+ const origEnc = proto.encrypt;
508
+ proto.encrypt = deepspider.native(function(data) {
509
+ const entry = deepspider.log('crypto', {
510
+ algo: 'RSA.encrypt',
511
+ source: source,
512
+ data: String(data).slice(0, 100)
513
+ });
514
+ deepspider.linkCrypto(entry);
515
+ return origEnc.apply(this, arguments);
516
+ }, origEnc);
517
+ }
518
+ if (proto.decrypt) {
519
+ const origDec = proto.decrypt;
520
+ proto.decrypt = deepspider.native(function(data) {
521
+ const entry = deepspider.log('crypto', {
522
+ algo: 'RSA.decrypt',
523
+ source: source
524
+ });
525
+ deepspider.linkCrypto(entry);
526
+ return origDec.apply(this, arguments);
527
+ }, origDec);
528
+ }
529
+ console.log('[DeepSpider] JSEncrypt Hook 已启用 (来源: ' + source + ')');
530
+ }
531
+
532
+ // Hook SM 国密对象
533
+ function hookSMCryptoObject(obj, source) {
534
+ if (hookedObjects.has(obj)) return;
535
+ hookedObjects.add(obj);
536
+
537
+ if (obj.doEncrypt) {
538
+ const origEnc = obj.doEncrypt;
539
+ obj.doEncrypt = deepspider.native(function(msg, pubKey) {
540
+ const entry = deepspider.log('crypto', {
541
+ algo: 'SM2.encrypt',
542
+ source: source,
543
+ msg: String(msg).slice(0, 100)
544
+ });
545
+ deepspider.linkCrypto(entry);
546
+ return origEnc.apply(this, arguments);
547
+ }, origEnc);
548
+ }
549
+ console.log('[DeepSpider] SM Crypto Hook 已启用 (来源: ' + source + ')');
550
+ }
551
+
552
+ // === 扫描并 Hook 模块导出 ===
553
+ function scanAndHook(exports, source) {
554
+ if (!exports || typeof exports !== 'object') return;
555
+
556
+ try {
557
+ // 直接检测导出对象
558
+ if (isCryptoJS(exports)) {
559
+ hookCryptoJSObject(exports, source);
560
+ return;
561
+ }
562
+ if (isJSEncrypt(exports)) {
563
+ hookJSEncryptObject(exports, source);
564
+ return;
565
+ }
566
+ if (isSMCrypto(exports)) {
567
+ hookSMCryptoObject(exports, source);
568
+ return;
569
+ }
570
+
571
+ // 检测 default 导出
572
+ if (exports.default) {
573
+ if (isCryptoJS(exports.default)) {
574
+ hookCryptoJSObject(exports.default, source + '.default');
575
+ } else if (isJSEncrypt(exports.default)) {
576
+ hookJSEncryptObject(exports.default, source + '.default');
577
+ }
578
+ }
400
579
 
401
- // Hook webpackJsonp
402
- function hookWebpack() {
403
- if (window.webpackJsonp && !window.webpackJsonp.__hooked__) {
404
- const orig = window.webpackJsonp.push;
405
- window.webpackJsonp.push = function(chunk) {
406
- deepspider.log('env', { action: 'webpack.chunk', id: chunk[0] });
407
- return orig.apply(this, arguments);
580
+ // 遍历导出的属性(限制深度避免性能问题)
581
+ const keys = Object.keys(exports);
582
+ for (let i = 0; i < Math.min(keys.length, 20); i++) {
583
+ const key = keys[i];
584
+ const val = exports[key];
585
+ if (val && typeof val === 'object' && !hookedObjects.has(val)) {
586
+ if (isCryptoJS(val)) {
587
+ hookCryptoJSObject(val, source + '.' + key);
588
+ }
589
+ }
590
+ if (val && typeof val === 'function') {
591
+ if (isJSEncrypt(val)) {
592
+ hookJSEncryptObject(val, source + '.' + key);
593
+ }
594
+ }
595
+ }
596
+ } catch (e) {
597
+ // 忽略访问错误
598
+ }
599
+ }
600
+
601
+ // === Hook Webpack 模块系统 ===
602
+
603
+ // Hook Webpack 4 webpackJsonp
604
+ function hookWebpackJsonp() {
605
+ const jsonp = window.webpackJsonp;
606
+ if (!jsonp || jsonp.__deepspider_hooked__) return;
607
+
608
+ const origPush = jsonp.push;
609
+ jsonp.push = function(chunk) {
610
+ const result = origPush.apply(this, arguments);
611
+
612
+ // chunk[1] 是模块对象 { moduleId: function(module, exports, require) {} }
613
+ if (chunk && chunk[1]) {
614
+ const modules = chunk[1];
615
+ Object.keys(modules).forEach(function(moduleId) {
616
+ // 延迟扫描,等模块执行完
617
+ setTimeout(function() {
618
+ try {
619
+ // 尝试获取模块导出
620
+ if (window.__webpack_require__ && window.__webpack_require__.c) {
621
+ const mod = window.__webpack_require__.c[moduleId];
622
+ if (mod && mod.exports) {
623
+ scanAndHook(mod.exports, 'webpack:' + moduleId);
624
+ }
625
+ }
626
+ } catch (e) {}
627
+ }, 10);
628
+ });
629
+ }
630
+ return result;
631
+ };
632
+ jsonp.__deepspider_hooked__ = true;
633
+ console.log('[DeepSpider] Webpack4 jsonp Hook 已启用');
634
+ }
635
+
636
+ // Hook Webpack 5 webpackChunk
637
+ function hookWebpackChunk() {
638
+ // Webpack 5 使用 self["webpackChunk" + name]
639
+ const chunkNames = Object.keys(self).filter(function(k) {
640
+ return k.startsWith('webpackChunk');
641
+ });
642
+
643
+ chunkNames.forEach(function(name) {
644
+ const chunk = self[name];
645
+ if (!chunk || chunk.__deepspider_hooked__) return;
646
+
647
+ const origPush = chunk.push.bind(chunk);
648
+ chunk.push = function(data) {
649
+ const result = origPush(data);
650
+
651
+ // data[1] 是模块对象
652
+ if (data && data[1]) {
653
+ Object.keys(data[1]).forEach(function(moduleId) {
654
+ setTimeout(function() {
655
+ try {
656
+ // Webpack 5 的 require cache
657
+ const cache = window.__webpack_require__ && window.__webpack_require__.c;
658
+ if (cache && cache[moduleId] && cache[moduleId].exports) {
659
+ scanAndHook(cache[moduleId].exports, 'webpack5:' + moduleId);
660
+ }
661
+ } catch (e) {}
662
+ }, 10);
663
+ });
664
+ }
665
+ return result;
408
666
  };
409
- window.webpackJsonp.__hooked__ = true;
667
+ chunk.__deepspider_hooked__ = true;
668
+ console.log('[DeepSpider] Webpack5 chunk Hook 已启用: ' + name);
669
+ });
670
+ }
671
+
672
+ // Hook __webpack_require__ 直接拦截模块加载
673
+ function hookWebpackRequire() {
674
+ if (!window.__webpack_require__ || window.__webpack_require__.__deepspider_hooked__) return;
675
+
676
+ const origRequire = window.__webpack_require__;
677
+ window.__webpack_require__ = function(moduleId) {
678
+ const result = origRequire.apply(this, arguments);
679
+ // 扫描返回的模块
680
+ setTimeout(function() {
681
+ scanAndHook(result, 'require:' + moduleId);
682
+ }, 0);
683
+ return result;
684
+ };
685
+ // 复制原有属性
686
+ Object.keys(origRequire).forEach(function(key) {
687
+ window.__webpack_require__[key] = origRequire[key];
688
+ });
689
+ window.__webpack_require__.__deepspider_hooked__ = true;
690
+ console.log('[DeepSpider] __webpack_require__ Hook 已启用');
691
+ }
692
+
693
+ // 定期检查并 Hook
694
+ function checkAndHook() {
695
+ hookWebpackJsonp();
696
+ hookWebpackChunk();
697
+ hookWebpackRequire();
698
+
699
+ // 扫描已加载的模块缓存
700
+ if (window.__webpack_require__ && window.__webpack_require__.c) {
701
+ const cache = window.__webpack_require__.c;
702
+ Object.keys(cache).forEach(function(moduleId) {
703
+ if (cache[moduleId] && cache[moduleId].exports) {
704
+ scanAndHook(cache[moduleId].exports, 'cache:' + moduleId);
705
+ }
706
+ });
410
707
  }
411
708
  }
412
709
 
413
- // 定期检查
414
- const check = setInterval(hookWebpack, 100);
415
- setTimeout(function() { clearInterval(check); }, 5000);
710
+ // 启动检查
711
+ checkAndHook();
712
+ const interval = setInterval(checkAndHook, 200);
713
+ setTimeout(function() { clearInterval(interval); }, 10000);
416
714
 
417
- console.log('[DeepSpider] Webpack Hook 已启用');
715
+ console.log('[DeepSpider] Webpack Module Hook 已启用');
418
716
  })();
419
717
  `;
420
718
  }
@@ -1,38 +1,34 @@
1
1
  /**
2
2
  * DeepSpider - Hook 管理器
3
+ * 注意:Hook 脚本已由 browser/client.js 通过 defaultHooks.js 自动注入
4
+ * 此类仅用于日志管理和状态跟踪
3
5
  */
4
6
 
5
- import { cryptoHook } from './crypto.js';
6
- import { networkHook } from './network.js';
7
- import { nativeProtect } from './native.js';
8
-
9
7
  export class HookManager {
10
8
  constructor() {
11
9
  this.logs = [];
12
10
  this.onLog = null;
11
+ this.injected = false;
13
12
  }
14
13
 
15
14
  /**
16
- * 获取完整的 Hook 脚本
15
+ * 标记 Hook 已注入(由 client.js 调用)
17
16
  */
18
- getCombinedScript() {
19
- return [
20
- nativeProtect,
21
- cryptoHook,
22
- networkHook,
23
- ].join('\n\n');
17
+ markInjected() {
18
+ this.injected = true;
24
19
  }
25
20
 
26
21
  /**
27
- * 注入 Hook 到页面
22
+ * 检查是否已注入
28
23
  */
29
- async inject(page) {
30
- const script = this.getCombinedScript();
31
-
32
- // 在新文档加载前注入
33
- await page.addInitScript(script);
24
+ isInjected() {
25
+ return this.injected;
26
+ }
34
27
 
35
- // 监听 console 输出
28
+ /**
29
+ * 绑定页面 console 监听(用于收集 Hook 日志)
30
+ */
31
+ bindConsole(page) {
36
32
  page.on('console', (msg) => {
37
33
  const text = msg.text();
38
34
  if (text.includes('[DeepSpider:')) {