DeepQuery Secure Client (GF-safe, no hardcoded key)

沙箱内 DeepQuery API;密钥仅存本机 GM 存储,公开代码不含密钥;与 Secure Core 协作完成签名校验

Version vom 05.09.2025. Aktuellste Version

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyforks.org/scripts/548365/1654982/DeepQuery%20Secure%20Client%20%28GF-safe%2C%20no%20hardcoded%20key%29.js

// ==UserScript==
// @name         DeepQuery Secure Client (GF-safe, no hardcoded key)
// @namespace    dq.secure.v2.client
// @version      3.0.0
// @description  沙箱内 DeepQuery API;密钥仅存本机 GM 存储,公开代码不含密钥;与 Secure Core 协作完成签名校验
// @author       you
// @match        http://*/*
// @match        https://*/*
// @include      about:blank
// @run-at       document-start
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// ==/UserScript==
(function () {
  'use strict';

  const CHANNEL = '__DQ_SECURE_V2__';
  const KEY_NAME = 'dq_key_b64';       // 本机密钥保存的键名
  const EXPOSE_TOP_PROXY = false;      // 设为 true 可零改动继续用 top.DeepQuery(仅对本沙箱可见)

  // ==== 基础工具 ====
  const te = new TextEncoder();

  function b64ToU8(b64) {
    if (!b64 || typeof b64 !== 'string') return new Uint8Array();
    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;
  }
  function u8ToB64(u8) {
    let s = '';
    for (let i = 0; i < u8.length; i++) s += String.fromCharCode(u8[i]);
    return btoa(s).replace(/\=+$/,'');
  }
  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);
  }

  // ==== 密钥管理(仅本机) ====
  async function ensureKeyInteractive() {
    // 如果没有密钥:询问用户是“自动生成”还是“粘贴已有(与 Core 一致)”
    const has = await GM_getValue(KEY_NAME, '');
    if (has) return has;

    const gen = confirm('[DeepQuery] 未检测到本机密钥。\n确定后将自动生成一个强随机密钥(推荐)。\n取消则提示你手动粘贴已有密钥。');
    let b64;
    if (gen) {
      const u8 = crypto.getRandomValues(new Uint8Array(32));
      b64 = u8ToB64(u8);
      alert('[DeepQuery] 已生成新密钥(base64)。\n请在“Core”脚本里配置相同密钥后使用。\n接下来会提供菜单方便你复制。');
    } else {
      b64 = prompt('[DeepQuery] 请输入与你的 Core 相同的密钥(base64,建议≥32字节):', '');
      if (!b64) throw new Error('NO_KEY_PROVIDED');
    }
    await GM_setValue(KEY_NAME, b64);
    return b64;
  }

  async function getKeyU8() {
    let b64 = await GM_getValue(KEY_NAME, '');
    if (!b64) b64 = await ensureKeyInteractive();
    return b64ToU8(b64);
  }

  // 提供菜单:设置/重置/复制密钥
  try {
    GM_registerMenuCommand('DeepQuery:设置/重置密钥', async () => {
      const b64 = prompt('[DeepQuery] 请输入新密钥(base64,留空自动生成):', '');
      let val = b64;
      if (!b64) {
        const u8 = crypto.getRandomValues(new Uint8Array(32));
        val = u8ToB64(u8);
        alert('[DeepQuery] 已自动生成并保存新密钥。');
      }
      await GM_setValue(KEY_NAME, val);
      alert('[DeepQuery] 密钥已更新。请保证 Core 使用相同密钥!');
    });
    GM_registerMenuCommand('DeepQuery:复制当前密钥', async () => {
      const b64 = await GM_getValue(KEY_NAME, '');
      if (!b64) { alert('当前未设置密钥'); return; }
      try {
        await navigator.clipboard.writeText(b64);
        alert('已复制到剪贴板。');
      } catch {
        prompt('复制失败,请手动复制:', b64);
      }
    });
  } catch {}

  // ==== 与 Core 的签名通信 ====
  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 KEY_U8 = await getKeyU8();
    const sigU8 = await hmacSignRaw(KEY_U8, toSign);
    const sigB64 = u8ToB64(sigU8);

    return new Promise((resolve) => {
      pending.set(id, { resolve });
      send({ cmd: 'REQ', id, ts, nonce, sigB64, spec });
      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.1.0-client'
  };

  // 推荐:在本沙箱全局暴露 DeepQuery
  try { window.DeepQuery = DeepQuery; } catch {}

  // 可选:零改动代理(仅当前沙箱可见,不暴露给页面)
  if (EXPOSE_TOP_PROXY) {
    try {
      Object.defineProperty(window.top, 'DeepQuery', {
        configurable: true,
        get() { return DeepQuery; }
      });
    } catch {}
  }
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。