DeepQuery Secure Client (sandbox)

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

Versão de: 04/09/2025. Veja: a última versão.

Este script não deve ser instalado diretamente. Este script é uma biblioteca de outros scripts para incluir com o diretório 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。