File size: 4,552 Bytes
bc0be9c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import { JSDOM } from "npm:jsdom";
import { userAgent } from "./utils.ts";
import crypto from "node:crypto";

// 更详细的类型定义
interface MockMeta {
  readonly origin: string;
  readonly stack: string;
  readonly duration: number;
}

interface HashResults {
  server_hashes: string[];
  client_hashes: string[];
  signals: Record<string, unknown>;
  meta: Record<string, unknown>;
}

interface JSExecutionResult {
  server_hashes?: string[];
  client_hashes?: string[];
  signals?: Record<string, unknown>;
  meta?: Record<string, unknown>;
}

const mockMeta: MockMeta = {
  origin: "https://duckduckgo.com",
  stack: "",
  duration: Math.floor(Math.random() * 100) + 1,
} as const;

class VqdHashGenerator {
  private static extractIIFEJSDOM(jsCode: string): HashResults {
    const results: HashResults = {
      server_hashes: [],
      client_hashes: [],
      signals: {},
      meta: {},
    };

    let dom: JSDOM | null = null;

    try {
      dom = new JSDOM(
        `<!DOCTYPE html><html><head></head><body></body></html>`,
        {
          url: "https://duckduckgo.com",
          referrer: "https://duckduckgo.com",
          pretendToBeVisual: true,
          resources: "usable",
        }
      );

      const { window } = dom;

      // 设置全局变量
      globalThis.window = window;
      globalThis.document = window.document;
      // 创建自定义 navigator 对象
      const customNavigator = {
        userAgent: userAgent,
        platform: "Win32",
        language: "en-US",
        languages: ["en-US", "en"],
        cookieEnabled: true,
        onLine: true,
        hardwareConcurrency: 4,
        maxTouchPoints: 0,
        vendor: "Google Inc.",
        vendorSub: "",
        productSub: "20030107",
        appName: "Netscape",
        appVersion: userAgent,
        product: "Gecko",
      };
      // 重新定义 window.navigator
      Object.defineProperty(window, "navigator", {
        value: customNavigator,
        writable: true,
        configurable: true,
        enumerable: true,
      });
      // 设置 globalThis.navigator
      Object.defineProperty(globalThis, "navigator", {
        value: customNavigator,
        writable: true,
        configurable: true,
        enumerable: true,
      });
      // 删除 webdriver 属性
      delete (window.navigator as any).webdriver;

      // 执行 JavaScript 代码
      const result = window.eval(jsCode) as JSExecutionResult;

      if (result && typeof result === "object") {
        results.server_hashes = result.server_hashes || [];
        results.client_hashes = result.client_hashes || [];
        results.signals = result.signals || {};
        results.meta = result.meta || {};
      }
    } catch (error) {
      console.error("JSDOM execution failed:", error);
      throw new Error(
        `JSDOM执行失败: ${
          error instanceof Error ? error.message : String(error)
        }`
      );
    } finally {
      // 清理资源
      if (dom) {
        dom.window.close();
      }
      delete globalThis.window;
      delete globalThis.document;
      delete globalThis.navigator;
    }

    return results;
  }

  private static hashClientValues(mockClientHashes: string[]): string[] {
    return mockClientHashes.map((value: string): string => {
      const hash = crypto.createHash("sha256");
      hash.update(value, "utf8");
      return hash.digest("base64");
    });
  }

  static generate(vqdHashRequest: string): string {
    if (!vqdHashRequest || typeof vqdHashRequest !== "string") {
      throw new Error("VQD Hash 请求参数无效");
    }

    try {
      // 解码 base64
      const jsCode = atob(vqdHashRequest);

      // 提取和处理 hash
      const hash = this.extractIIFEJSDOM(jsCode);

      // 处理客户端 hash
      hash.client_hashes = this.hashClientValues(hash.client_hashes);

      // 合并 meta 数据
      hash.meta = {
        ...hash.meta,
        ...mockMeta,
      };

      // 返回编码后的结果
      return btoa(JSON.stringify(hash));
    } catch (error) {
      console.error("generateVqdHash 执行失败:", error);
      throw new Error(
        `VQD Hash 生成失败: ${
          error instanceof Error ? error.message : String(error)
        }`
      );
    }
  }
}

// 导出函数版本以保持向后兼容
export function generateVqdHash(vqdHashRequest: string): string {
  return VqdHashGenerator.generate(vqdHashRequest);
}

// 导出类版本
export { VqdHashGenerator };
export type { HashResults, MockMeta, JSExecutionResult };