DeepQuery Secure Client (sandbox)

提供沙箱内的 DeepQuery API,自动签名并与页面核心通信。可 @require 于任意调用脚本顶部。

Tính đến 04-09-2025. Xem phiên bản mới nhất.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyforks.org/scripts/548365/1654735/DeepQuery%20Secure%20Client%20%28sandbox%29.js

// ==UserScript==
// @name         DeepQuery Secure Client (sandbox)
// @namespace    dq.secure.v2.client
// @version      2.0.0
// @description  提供沙箱内的 DeepQuery API,自动签名并与页面核心通信。可 @require 于任意调用脚本顶部。
// @author       you
// @match        http://*/*
// @match        https://*/*
// @include      about:blank
// @run-at       document-start
// @grant        none
// ==/UserScript==
(function () {
  'use strict';

  /******** 配置:必须与 Core 一致 ********/
  const CHANNEL = '__DQ_SECURE_V2__';

  // ★★★ 重要:把下面的 KEY_PARTS 替换成你自己的一组(与 Core 完全一致) ★★★
  const KEY_PARTS = [
    'dW5hcmFuZG9tLWtleS1zZWVkLQ',
    'tZXBsZWFzZS1yZXBsYWNlLW1l',
    'LXdpdGgtYS1wcm9wZXItb25l'
  ];

  // 是否在本沙箱里提供一个 top.DeepQuery 代理(零改动用)
  const EXPOSE_TOP_PROXY = false;

  const te = new TextEncoder();
  function b64ToU8(b64) {
    const pad = b64.length % 4 ? (4 - b64.length % 4) : 0;
    const s = b64 + '='.repeat(pad);
    const bin = atob(s.replace(/-/g, '+').replace(/_/g, '/'));
    const u8 = new Uint8Array(bin.length);
    for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);
    return u8;
  }
  const KEY_U8 = b64ToU8(KEY_PARTS.join(''));

  async function sha256U8(u8) {
    const buf = await crypto.subtle.digest('SHA-256', u8);
    return new Uint8Array(buf);
  }
  async function hmacSignRaw(key, u8) {
    const k = await crypto.subtle.importKey(
      'raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
    );
    const sig = await crypto.subtle.sign('HMAC', k, u8);
    return new Uint8Array(sig);
  }

  const pending = new Map();
  function rid() {
    return (Date.now().toString(36) + Math.random().toString(36).slice(2, 10)).toUpperCase();
  }
  function send(payload) {
    window.top.postMessage({ [CHANNEL]: payload }, '*');
  }
  window.addEventListener('message', (e) => {
    const msg = e.data && e.data[CHANNEL];
    if (!msg || msg.cmd !== 'RESP' || !msg.id) return;
    const hit = pending.get(msg.id);
    if (!hit) return;
    pending.delete(msg.id);
    hit.resolve(msg.res);
  }, false);

  async function request(spec) {
    const id = rid();
    const ts = Date.now();
    const nonce = rid() + Math.random().toString(36).slice(2);
    const payload = te.encode(id + '\n' + ts + '\n' + nonce + '\n');
    const bodyHash = await sha256U8(te.encode(JSON.stringify(spec || {})));
    const toSign = new Uint8Array(payload.length + bodyHash.length);
    toSign.set(payload, 0); toSign.set(bodyHash, payload.length);
    const sigU8 = await hmacSignRaw(KEY_U8, toSign);
    const sigB64 = btoa(String.fromCharCode(...sigU8));

    return new Promise((resolve) => {
      pending.set(id, { resolve });
      send({ cmd: 'REQ', id, ts, nonce, sigB64, spec });
      // 可选超时(以 spec.timeout 为基准)
      const timeout = typeof spec.timeout === 'number' ? Math.max(200, spec.timeout + 500) : 6000;
      setTimeout(() => {
        if (pending.has(id)) {
          pending.delete(id);
          resolve({ ok: false, error: 'TIMEOUT' });
        }
      }, timeout);
    });
  }

  // 暴露与旧版一致的方法
  const DeepQuery = {
    async get(spec = {}) { return request(spec); },
    async attr({ framePath, chain, name, timeout }) { return request({ framePath, chain, timeout, pick: { attr: name } }); },
    async prop({ framePath, chain, name, timeout }) { return request({ framePath, chain, timeout, pick: { prop: name } }); },
    async text({ framePath, chain, timeout }) { return request({ framePath, chain, timeout, pick: { text: true } }); },
    async html({ framePath, chain, timeout }) { return request({ framePath, chain, timeout, pick: { html: true } }); },
    async rect({ framePath, chain, timeout }) { return request({ framePath, chain, timeout, pick: { rect: true } }); },
    version: '2.0.0-client'
  };

  // 方式 A:推荐 —— 在沙箱里直接提供全局 DeepQuery
  try { window.DeepQuery = DeepQuery; } catch {}

  // 方式 B:零改动(仅本沙箱可见的 top.DeepQuery 代理)
  if (EXPOSE_TOP_PROXY) {
    try {
      // 注意:这个属性只存在于当前 userscript 的沙箱 proxy 上,页面/其它沙箱看不到
      Object.defineProperty(window.top, 'DeepQuery', {
        configurable: true,
        get() { return DeepQuery; }
      });
    } catch {}
  }

  try { console.debug('[DeepQuery Secure Client] ready'); } catch {}
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。