DeepQuery Secure Client (GM storage key)

DeepQuery 客户端(仅沙箱内可见)。密钥存放到 GM 存储,不写在代码里;负责签名并与 Core 通信。

当前为 2025-09-04 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/548365/1654742/DeepQuery%20Secure%20Client%20%28GM%20storage%20key%29.js

// ==UserScript==
// @name         DeepQuery Secure Client (GM storage key)
// @namespace    dq.secure.v2.client
// @version      2.1.0
// @description  DeepQuery 客户端(仅沙箱内可见)。密钥存放到 GM 存储,不写在代码里;负责签名并与 Core 通信。
// @author       you
// @match        http://*/*
// @match        https://*/*
// @include      about:blank
// @run-at       document-start
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_setClipboard
// ==/UserScript==
(function () {
  'use strict';

  /******** 基本配置(无需放密钥) ********/
  const CHANNEL = '__DQ_SECURE_V2__';                 // 必须与 Core 保持一致
  const KEY_STORE_NAME = 'DQ_SECURE_V2_KEY_B64';      // GM 存储的键名
  const EXPOSE_TOP_PROXY = false; // 设为 true 可零改动复用 top.DeepQuery(仅对本沙箱可见)

  /******** 小工具 ********/
  const te = new TextEncoder();
  function u8FromB64(b64) {
    if (!b64 || typeof b64 !== 'string') return null;
    const s = b64.replace(/-/g, '+').replace(/_/g, '/');
    const pad = s.length % 4 ? 4 - (s.length % 4) : 0;
    const bin = atob(s + '='.repeat(pad));
    const u8 = new Uint8Array(bin.length);
    for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);
    return u8;
  }
  function b64FromU8(u8) {
    let s = '';
    for (let i = 0; i < u8.length; i++) s += String.fromCharCode(u8[i]);
    return btoa(s).replace(/=+$/,'');
  }
  const hasGM = (typeof GM !== 'undefined' && GM) || {};
  const gmGet = (k, d=null) =>
    (hasGM.getValue ? hasGM.getValue(k, d) :
      (typeof GM_getValue === 'function' ? Promise.resolve(GM_getValue(k, d)) : Promise.resolve(d)));
  const gmSet = (k, v) =>
    (hasGM.setValue ? hasGM.setValue(k, v) :
      (typeof GM_setValue === 'function' ? Promise.resolve(GM_setValue(k, v)) : Promise.resolve()));
  const gmReg = (name, fn) =>
    (hasGM.registerMenuCommand ? GM.registerMenuCommand(name, fn) :
      (typeof GM_registerMenuCommand === 'function' ? GM_registerMenuCommand(name, fn) : null));
  const gmClip = (text) =>
    (typeof GM_setClipboard === 'function' ? GM_setClipboard(text) : navigator.clipboard?.writeText?.(text));

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

  /******** 读取/管理密钥(来自 GM 存储) ********/
  let KEY_U8 = null;
  let keyLoadPromise = null;

  async function ensureKeyLoaded() {
    if (KEY_U8) return KEY_U8;
    if (!keyLoadPromise) {
      keyLoadPromise = (async () => {
        const b64 = (await gmGet(KEY_STORE_NAME, '')).trim();
        if (!b64) throw new Error('[DeepQuery Client] KEY_MISSING: 请先在脚本菜单里设置密钥');
        const u8 = u8FromB64(b64);
        if (!u8 || u8.length < 16) throw new Error('[DeepQuery Client] KEY_INVALID: GM 存储中的密钥格式不正确');
        KEY_U8 = u8;
        return KEY_U8;
      })();
    }
    return keyLoadPromise;
  }

  // 菜单:设置/生成/查看密钥
  gmReg('Set DeepQuery Key…', async () => {
    const b64 = prompt('粘贴与你的 Core 相同的密钥(base64):\n(提示:不要泄露给网页脚本)');
    if (!b64) return;
    const u8 = u8FromB64(b64.trim());
    if (!u8 || u8.length < 16) { alert('密钥格式不正确(必须是 base64,建议 >= 32 字节)'); return; }
    await gmSet(KEY_STORE_NAME, b64.trim());
    KEY_U8 = u8;
    keyLoadPromise = Promise.resolve(u8);
    alert('已保存密钥到 GM 存储。');
  });

  gmReg('Generate Random Key', async () => {
    const u8 = new Uint8Array(32);
    crypto.getRandomValues(u8);
    const b64 = b64FromU8(u8);
    await gmSet(KEY_STORE_NAME, b64);
    KEY_U8 = u8;
    keyLoadPromise = Promise.resolve(u8);
    gmClip && gmClip(b64);
    alert('已生成随机密钥并保存到 GM 存储;密钥已复制到剪贴板。\n⚠️ 请同步更新 Core 脚本中的密钥为相同值!');
  });

  gmReg('Show Current Key (base64)', async () => {
    const b64 = await gmGet(KEY_STORE_NAME, '');
    if (!b64) { alert('尚未设置密钥'); return; }
    gmClip && gmClip(b64);
    alert('已复制当前密钥到剪贴板。');
  });

  /******** 与 Core 通信 ********/
  const pending = new Map();
  function rid() {
    return (Date.now().toString(36) + Math.random().toString(36).slice(2, 10)).toUpperCase();
  }
  function send(payload) {
    // 只与顶层 Core 打交道(Core 会做逐级转发)
    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 key = await ensureKeyLoaded(); // 确保已加载 GM 密钥
    const id = rid();
    const ts = Date.now();
    const nonce = rid() + Math.random().toString(36).slice(2);
    const header = te.encode(id + '\n' + ts + '\n' + nonce + '\n');
    const bodyHash = await sha256U8(te.encode(JSON.stringify(spec || {})));
    const toSign = new Uint8Array(header.length + bodyHash.length);
    toSign.set(header, 0); toSign.set(bodyHash, header.length);
    const sigU8 = await hmacSignRaw(key, toSign);
    const sigB64 = b64FromU8(sigU8);

    const timeout = typeof spec?.timeout === 'number' ? Math.max(200, spec.timeout + 500) : 6000;

    return new Promise((resolve) => {
      pending.set(id, { resolve });
      send({ cmd: 'REQ', id, ts, nonce, sigB64, spec });
      setTimeout(() => {
        if (pending.has(id)) {
          pending.delete(id);
          resolve({ ok: false, error: 'TIMEOUT' });
        }
      }, timeout);
    });
  }

  /******** 暴露 API(与旧版一致) ********/
  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'
  };

  // 方式 A(推荐):直接用 DeepQuery
  try { window.DeepQuery = DeepQuery; } catch {}

  // 方式 B(零改动):在当前沙箱里暴露 top.DeepQuery 代理(页面不可见)
  if (EXPOSE_TOP_PROXY) {
    try {
      Object.defineProperty(window.top, 'DeepQuery', {
        configurable: true,
        get() { return DeepQuery; }
      });
    } catch {}
  }

  // 尝试提前加载密钥(不影响后续调用)
  ensureKeyLoaded().catch(() => {
    // 首次未设置密钥时静默,调用时会抛清晰错误
  });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址