Acest script nu ar trebui instalat direct. Aceasta este o bibliotecă pentru alte scripturi care este inclusă prin directiva meta a // @require https://update.greasyforks.org/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(() => {
// 首次未设置密钥时静默,调用时会抛清晰错误
});
})();