您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Nested, type secure and auto saving data proxy on Tampermonkey.
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/429255/955824/TM%20dat.js
// ==UserScript== // @name TM dat // @namespace https://icelava.root // @version 0.6.0 // @description Nested, type secure and auto saving data proxy on Tampermonkey. // @author ForkKILLET // @match http://localhost:1633/* // @noframes // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant unsafeWindow // @grant GM_listValues // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // ==/UserScript== "use strict" Object.clone = o => JSON.parse(JSON.stringify(o)) const err = (t, m) => { throw window[t](`[TM dat] ${m}`) } /* eslint-disable */ const type_dat = v => v === null ? "null" : v instanceof Array ? "array" : v instanceof RegExp ? "regexp" : typeof v /* eslint-enable */ type_dat.convert = { string_number: v => + v, string_regexp: v => v, number_string: v => "" + v, number_boolean: v => !! v } let raw_dat const proto_scm = { object: { rec: 1, ctn: () => ({}) }, tuple: { rec: 1, ctn: () => [] }, array: { rec: 2, ctn: () => [], api: (A, s, P, tar) => ({ $new(i, n = 1) { for (const j = i + n; i < j; i ++) { const scm = A.scm.lvs[j] if (scm) err("ReferenceError", `Leaf @ ${scm.path} already exists, but was attempted to re-new.`) init_scm(A, j, tar, true) } }, get $length() { return s.lvs.length }, $push(...a) { a.forEach(v => P[ s.lvs.length ] = v) return s.lvs.length }, $fill(v, i = 0, j = s.lvs.length) { for (; i < j; i ++) P[i] = v return P }, $pop() { const l = s.lvs.length const v = P[ l - 1 ] delete P[ l - 1 ] s.lvs.length -- return v }, $splice(i, n = 1) { // Note: Unsafe `=`. const l = s.lvs.length n = Math.min(l - i, n) for(; i < l; i ++) P[i] = i + n < l ? P[ i + n ] : undefined s.lvs.length -= n }, $reverse() { // Note: Unsafe `=`. const l = s.lvs.length const m = ~~ (l / 2) for (let i = 0; i < m; i ++) if (i in s.lvs) [ P[i], P[l - i - 1] ] = [ P[l - i - 1], P[i] ] return P }, $includes(v) { const l = s.lvs.length for (let i = 0; i < l; i ++) if (i in s.lvs) if (v === P[i]) return true return false }, $indexOf(v) { const l = s.lvs.length for (let i = 0; i < l; i ++) if (i in s.lvs) if (v === P[i]) return + i return -1 }, $lastIndexOf(v) { for (let i = s.lvs.length - 1; i >= 0; i --) if (i in s.lvs) if (v === P[i]) return + i return -1 }, $find(f) { const l = s.lvs.length for (let i = 0; i < l; i ++) if (i in s.lvs) { const v = P[i] if (f(v)) return v } }, $findIndex(f) { const l = s.lvs.length for (let i = 0; i < l; i ++) if (i in s.lvs) { const v = P[i] if (f(v)) return + i } }, $forEach(f) { const l = s.lvs.length for (let i = 0; i < l; i ++) if (i in s.lvs) f(P[i], + i, P) }, $at(i) { return i < 0 ? P[ s.lvs.length + i ] : P[i] }, *[Symbol.iterator] () { for (const k in s.lvs) yield P[k] } }) }, } const init_scm = (A, k, tar, isNew) => { const { dat, map, scm, oldRoot, old } = A if (isNew) scm.lvs[k] = Object.clone(scm.itm) const s = scm.lvs[k] s.path = (scm.path ?? "") + "." + k const proto = proto_scm[s.ty] s.rec = proto?.rec ?? 0 if (s.rec) { dat[k] = proto.ctn() if (s.rec > 1) s.lvs = proto.ctn() } if (s.ty === "tuple") s.lvs = s.lvs.flatMap( i => Array.from({ length: i.repeat ?? 1 }, () => Object.clone(i)) ) map(s) s.pathRoot = s.root ? "#" + s.path : scm.pathRoot ?? k s.raw = (s.root ? null : scm.raw) ?? (() => dat[k]) const Ak = { dat: dat[k], map, scm: s, oldRoot, old: old?.[k] } let tar_k if (s.rec) [ tar[k], tar_k ] = proxy_dat(Ak) else tar[k] = dat[k] = (s.root ? oldRoot[s.pathRoot] : old?.[k]) ?? s.dft ?? null if (proto?.api) s.api = proto.api(Ak, s, tar[k], tar_k) } const proxy_dat = A => { const { dat, scm, oldRoot, old } = A const tar = {} switch (scm.rec) { case 1: for (let k in scm.lvs) init_scm(A, k, tar) break case 2: const keys = scm.itmRoot ? Object.keys(oldRoot).map(k => k.match(String.raw`^#${scm.path}\.([^.]+)`)?.[1]).filter(k => k) : Object.keys(old ?? {}) keys.forEach(k => init_scm(A, k, tar, true)) break } const eP = `Parent ${scm.ty} @ ${scm.path}` const cAR = k => { if (typeof k === "symbol") return if (scm.ty === "array") { const eR = eP + ` requires the index to be in [ ${scm.minIdx ??= 0}, ${scm.maxIdx ??= +Infinity} ], but got ${k}. ` if (k < scm.minIdx || k > scm.maxIdx) err("RangeError", eR) } } const P = new Proxy(tar, { get: (_, k) => { if (scm.api && k in scm.api) return scm.api[k] cAR(k) return tar[k] }, set: (_, k, v) => { cAR(k) if (! scm.lvs[k]) switch (scm.rec) { case 1: err("TypeError", eP + ` doesn't have leaf ${k}.`) break case 2: init_scm(A, k, tar, true) break } if (scm.api && k in scm.api) { err("TypeError", eP + ` has API ${k}. Failed.`) } const s = scm.lvs[k] const eF = `Leaf @ ${s.path}`, eS = "Failed strictly.", eT = eF + ` is ${ [ "simple", "fixed complex", "flexible complex" ] } type, ` if (s.locked) err("TypeError", eF + ` is locked, but was attempted to modify.`) if (s.ty === "enum" && ! s.vals.includes(v)) { err("TypeError", eF + ` requires to be in the enum { ${ s.vals.join(", ") } }, but got ${v}.`) } const ty = type_dat(v) if (ty === "undefined") { if (scm.rec === 1) err("TypeError", eT + `but its ` + eF + " was attempted to delete.") if (s.rec) err("TypeError", eT + `but it was attempted to delete`) } else if (! [ "any", "enum" ].includes(s.ty) && ty !== s.ty) { const eM = eF + ` requires type ${s.ty}, but got ${ty}: ${v}. ` if (s.strict) err("TypeError", eM + eS) const f = type_dat.convert[`${ty}_${s.ty}`] if (f) v = f(v) else err("TypeError", eM + "Failed to convert.") } if (s.ty === "number") { const eR = eF + ` requires to be in [ ${s.min ??= -Infinity}, ${s.max ??= +Infinity} ], but got ${v}. ` if (v < s.min || v > s.max) err("RangeError", eR) if (s.int) { if (s.strict) err("RangeError", eF + ` requires to be an integer. ` + eS) v = ~~ v } } tar[k] = dat[k] = v if (s.quick || s.root) { const vRoot = s.raw() if (vRoot === undefined) GM_deleteValue(s.pathRoot) else GM_setValue(s.pathRoot, JSON.stringify(vRoot)) } return true }, deleteProperty: (_, k) => { P[k] = undefined return true }, has: (_, k) => k in scm.lvs }) return [ P, tar ] } const load_dat = (lvs, { autoSave, old, map }) => { if (raw_dat) err("Error", `Dat cannot be loaded multiple times.`) raw_dat = {} old ??= GM_listValues().reduce((o, k) => ( o[k] = JSON.parse(GM_getValue(k) ?? "null"), o ), {}) if (autoSave) window.addEventListener("beforeunload", () => save_dat()) return proxy_dat({ dat: raw_dat, scm: { lvs, rec: 1 }, map: map ?? (s => s), old, oldRoot: old }) [0] } const save_dat = (dat = raw_dat) => { Object.keys(dat).forEach(k => GM_setValue(k, JSON.stringify(dat[k]))) } const clear_dat = () => { raw_dat = null GM_listValues().forEach(GM_deleteValue) } // Debug if (location.host === "localhost:1633") Object.assign(unsafeWindow, { TM_dat: { type_dat, proxy_dat, load_dat, save_dat, clear_dat, raw_dat: () => raw_dat } })