TM dat

Nested, type secure and auto saving data proxy on Tampermonkey.

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/429255/1046760/TM%20dat.js

  1. // ==UserScript==
  2. // @name TM dat
  3. // @namespace https://icelava.root
  4. // @version 0.8.3
  5. // @description Nested, type secure and auto saving data proxy on Tampermonkey.
  6. // @author ForkKILLET
  7. // @include http://localhost:1633/*
  8. // @noframes
  9. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  10. // @grant unsafeWindow
  11. // @grant GM_listValues
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_deleteValue
  15. // ==/UserScript==
  16.  
  17. "use strict"
  18.  
  19. const clone = o => JSON.parse(JSON.stringify(o))
  20.  
  21. const err = (t, m) => { throw window[t](`[TM dat] ${m}`) }
  22.  
  23. const op = "GM" in Window ? {
  24. get: GM_getValue,
  25. set: GM_setValue,
  26. del: GM_deleteValue,
  27. list: GM_listValues
  28. } : {
  29. get: k => localStorage.getItem(k),
  30. set: (k, v) => localStorage.setItem(k, v),
  31. del: k => localStorage.removeItem(k),
  32. list: () => Object.keys(localStorage)
  33. }
  34.  
  35. /* eslint-disable */
  36. const type_dat = v =>
  37. v?.__scm__?.ty ? v.__scm__.ty :
  38. v === null ? "null" :
  39. v instanceof Array ? "array" :
  40. v instanceof RegExp ? "regexp" :
  41. typeof v
  42. /* eslint-enable */
  43.  
  44. type_dat.convert = {
  45. string_number: v => + v,
  46. string_regexp: v => v,
  47. number_string: v => "" + v,
  48. number_boolean: v => !! v
  49. }
  50.  
  51. let raw_dat
  52.  
  53. const proto_scm = {
  54. object: { rec: 1, ctn: () => ({}) },
  55. tuple: { rec: 1, ctn: () => [] },
  56. array: { rec: 2, ctn: () => [], api: (A, P, s = P.__scm__, tar = P.__tar__) => ({
  57. $new(i, n = 1) {
  58. for (const j = i + n; i < j; i ++) {
  59. const scm = A.scm.lvs[j]
  60. if (scm) err("ReferenceError", `Leaf @ ${scm.path} already exists, but was attempted to re-new.`)
  61. init_scm(A, j, P, tar, true)
  62. }
  63. },
  64. get $length() {
  65. return s.lvs.length
  66. },
  67. $push(...a) {
  68. a.forEach(v => P[ s.lvs.length ] = v)
  69. return s.lvs.length
  70. },
  71. $fill(v, i = 0, j = s.lvs.length) {
  72. for (; i < j; i ++) P[i] = v
  73. return P
  74. },
  75. $pop() {
  76. const l = s.lvs.length
  77. const v = P[ l - 1 ]
  78. delete P[ l - 1 ]
  79. s.lvs.length --
  80. return v
  81. },
  82. $splice(i, n) {
  83. const l = s.lvs.length
  84. n ??= l
  85. n = Math.min(l - i, n)
  86. for(; i < l; i ++)
  87. P[i] = i + n < l ? P[ i + n ] : undefined
  88. s.lvs.length -= n
  89. },
  90. $swap(i, j) {
  91. P.__tmp__ = P[i]
  92. P[i] = P[j]
  93. P[j] = P.__tmp__
  94. delete P.__tmp__
  95. },
  96. $reverse() {
  97. const l = s.lvs.length
  98. const m = Math.floor(l / 2)
  99. for (let i = 0; i < m; i ++) if (i in s.lvs) {
  100. P.$swap(i, l - i - 1)
  101. }
  102. return P
  103. },
  104. $includes(v) {
  105. const l = s.lvs.length
  106. for (let i = 0; i < l; i ++) if (i in s.lvs)
  107. if (v === P[i]) return true
  108. return false
  109. },
  110. $indexOf(v) {
  111. const l = s.lvs.length
  112. for (let i = 0; i < l; i ++) if (i in s.lvs)
  113. if (v === P[i]) return + i
  114. return -1
  115. },
  116. $lastIndexOf(v) {
  117. for (let i = s.lvs.length - 1; i >= 0; i --) if (i in s.lvs)
  118. if (v === P[i]) return + i
  119. return -1
  120. },
  121. $find(f) {
  122. const l = s.lvs.length
  123. for (let i = 0; i < l; i ++) if (i in s.lvs) {
  124. const v = P[i]
  125. if (f(v)) return v
  126. }
  127. },
  128. $findIndex(f) {
  129. const l = s.lvs.length
  130. for (let i = 0; i < l; i ++) if (i in s.lvs) {
  131. const v = P[i]
  132. if (f(v)) return + i
  133. }
  134. },
  135. $forEach(f) {
  136. const l = s.lvs.length
  137. for (let i = 0; i < l; i ++) if (i in s.lvs)
  138. f(P[i], + i, P)
  139. },
  140. $at(i) {
  141. return i < 0 ? P[ s.lvs.length + i ] : P[i]
  142. },
  143. *[Symbol.iterator] () {
  144. for (const k in s.lvs) yield P[k]
  145. }
  146. }) },
  147. }
  148.  
  149. const init_scm = (A, k, P, tar, isNew) => {
  150. const { dat, map, scm, oldRoot, old } = A
  151. if (isNew) scm.lvs[k] = clone(scm.itm)
  152. const s = scm.lvs[k]
  153. s.path = (scm.path ?? "") + "." + k
  154.  
  155. const proto = proto_scm[s.ty]
  156. s.rec = proto?.rec ?? 0
  157. if (s.rec) {
  158. dat[k] = proto.ctn()
  159. if (s.rec > 1) s.lvs = proto.ctn()
  160. }
  161.  
  162. const eS = s => JSON.stringify(s, null, 2) + ": "
  163.  
  164. if (s.ty === "enum") {
  165. s.get ??= "val"
  166. s.set ??= "val"
  167. s.fromOld = v => {
  168. let set = s.set
  169. s.set = "id"
  170. P[k] = v
  171. s.set = set
  172. }
  173. if (s.get !== "both" && s.set === "both") err("SyntaxError", eS(s) + `{ ty: "enum" ¬(get: "both" set: ¬ "both") }`)
  174. }
  175. if (s.ty === "tuple") s.lvs = s.lvs.flatMap(
  176. i => {
  177. let r = 1
  178. if ("repeat" in i) {
  179. r = i.repeat
  180. if (typeof r !== "number" || r < 0 || r % 1)
  181. err("SyntaxError", eS(s) + `{ ty: "tuple" itm: [ i: { repeat?: integer } } ]`)
  182. delete i.repeat
  183. }
  184. return Array.from({ length: r }, () => clone(i))
  185. }
  186. )
  187.  
  188. map(s)
  189. s.pathRoot = s.root ? "#" + s.path : scm.pathRoot ?? k
  190. s.raw = (s.root ? null : scm.raw) ?? (() => dat[k])
  191.  
  192. const Ak = {
  193. dat: dat[k],
  194. map,
  195. scm: s,
  196. oldRoot,
  197. old: old?.[k]
  198. }
  199.  
  200. if (s.rec) tar[k] = proxy_dat(Ak)
  201. else {
  202. let old_v = s.root ? oldRoot[s.pathRoot] : old?.[k]
  203. if (old_v !== undefined) {
  204. if (s.ty === "enum") s.fromOld(old_v)
  205. else P[k] = old_v
  206. }
  207. else if ("dft" in s) P[k] = s.dft
  208. }
  209.  
  210. if (proto?.api) s.api = proto.api(Ak, tar[k])
  211. }
  212.  
  213. const proxy_dat = A => {
  214. const { dat, scm, oldRoot, old } = A
  215. const tar = {}
  216.  
  217. const eP = `Parent ${scm.ty} @ ${scm.path}`
  218. const cAR = k => {
  219. if (typeof k === "symbol") return
  220. if (scm.ty === "array") {
  221. const eR = eP + ` requires the index to be in [ ${scm.minIdx ??= 0}, ${scm.maxIdx ??= +Infinity} ], but got ${k}. `
  222. if (k < scm.minIdx || k > scm.maxIdx) err("RangeError", eR)
  223. }
  224. }
  225. const P = new Proxy(tar, {
  226. get: (_, k) => {
  227. if (k === "__scm__") return scm
  228. if (k === "__tar__") return tar
  229. if (scm.api && k in scm.api) return scm.api[k]
  230.  
  231. const s = scm.lvs[k]
  232. if (s.ty === "enum") switch (s.get) {
  233. case "id":
  234. return tar[k]
  235. case "val":
  236. return s.vals[tar[k]]
  237. case "both":
  238. return {
  239. get id() { return tar[k] },
  240. set id(v) {
  241. const o_set = s.set
  242. s.set = "id"
  243. P[k] = v
  244. s.set = o_set
  245. },
  246. get val() { return s.vals[tar[k]] },
  247. set val(v) {
  248. const o_set = s.set
  249. s.set = "val"
  250. P[k] = v
  251. s.set = o_set
  252. },
  253. }
  254. }
  255.  
  256. cAR(k)
  257. return tar[k]
  258. },
  259.  
  260. set: (_, k, v) => {
  261. cAR(k)
  262.  
  263. if (! scm.lvs[k]) switch (scm.rec) {
  264. case 1:
  265. err("TypeError", eP + ` doesn't have leaf ${k}.`)
  266. break
  267. case 2:
  268. init_scm(A, k, P, tar, true)
  269. break
  270. }
  271.  
  272. if (scm.api && k in scm.api) {
  273. err("TypeError", eP + ` has API ${k}. Failed.`)
  274. }
  275. const s = scm.lvs[k]
  276.  
  277. const eF = `Leaf @ ${s.path}`, eS = "Failed strictly.", eT = eF + ` is ${ [ "simple", "fixed complex", "flexible complex" ][s.rec] } type, `
  278. if (s.locked) err("TypeError", eF + ` is locked, but was attempted to modify.`)
  279.  
  280. const ty = type_dat(v)
  281.  
  282. if (ty === "undefined") {
  283. if (scm.rec === 1 && ! scm.del) err("TypeError", eT + `but its ` + eF + " was attempted to delete.")
  284. if (s.rec) {
  285. s.del = true
  286. for (let j in s.lvs) delete tar[k][j]
  287. }
  288. delete scm.lvs[k]
  289. }
  290.  
  291. else if (
  292. ty === "array" && s.lvs instanceof Array ||
  293. ty === "object" && s.lvs && ! (s.lvs instanceof Array)
  294. ) {
  295. for (let j in s.lvs) tar[k][j] = v[j]
  296. return true
  297. }
  298.  
  299. else if (! [ "any", "enum" ].includes(s.ty) && ty !== s.ty) {
  300. const eM = eF + ` requires type ${s.ty}, but got ${ty}: ${v}. `
  301. if (s.strict) err("TypeError", eM + eS)
  302. const f = type_dat.convert[`${ty}_${s.ty}`]
  303. if (f) v = f(v)
  304. else err("TypeError", eM + "Failed to convert.")
  305. }
  306.  
  307. if (s.ty === "number") {
  308. const eR = eF + ` requires to be in [ ${ s.min ?? -Infinity }, ${ s.max ?? +Infinity } ], but got ${v}. `
  309. if (v < s.min || v > s.max) err("RangeError", eR)
  310.  
  311. if (s.int && v % 1) {
  312. if (s.strict) err("RangeError", eF + ` requires to be an integer. ` + eS)
  313. v = Math.floor(v)
  314. }
  315. }
  316.  
  317. else if (s.ty === "enum") switch (s.set) {
  318. case "id":
  319. if (typeof v !== "number" || ! v in s.vals)
  320. err("RangeError", eF + ` requires to be an enum index in [ ${0}, ${s.vals.length} ], but got ${v}.`)
  321. break
  322. case "val":
  323. v = s.vals.findIndex(val => val === v)
  324. if (v < 0)
  325. err("RangeError", eF + ` requires to be in the enum { ${ s.vals.join(", ") } }, but got ${v}.`)
  326. break
  327. case "both":
  328. err("TypeError", eF + ` is an enum accepting both id and value ways of modification, but was attempted to modify without using any setter.`)
  329. }
  330.  
  331. tar[k] = dat[k] = v
  332. if (s.quick || s.root) {
  333. const vRoot = s.raw()
  334. if (vRoot === undefined) op.del(s.pathRoot)
  335. else op.set(s.pathRoot, JSON.stringify(vRoot))
  336. }
  337.  
  338. return true
  339. },
  340.  
  341. deleteProperty: (_, k) => {
  342. P[k] = undefined
  343. return true
  344. },
  345.  
  346. has: (_, k) => k in scm.lvs
  347. })
  348.  
  349. switch (scm.rec) {
  350. case 1:
  351. for (let k in scm.lvs) init_scm(A, k, P, tar)
  352. break
  353. case 2:
  354. const keys = scm.itmRoot
  355. ? Object.keys(oldRoot).map(k => k.match(String.raw`^#${scm.path}\.([^.]+)`)?.[1]).filter(k => k)
  356. : Object.keys(old ?? {})
  357. keys.forEach(k => init_scm(A, k, P, tar, true))
  358. break
  359. }
  360.  
  361. return P
  362. }
  363.  
  364. const load_dat = (lvs, { autoSave, old, map }) => {
  365. if (raw_dat) err("Error", `Dat cannot be loaded multiple times.`)
  366. raw_dat = {}
  367.  
  368. old ??= GM_listValues().reduce((o, k) => (
  369. o[k] = op.get(k), o
  370. ), {})
  371.  
  372. if (autoSave) window.addEventListener("beforeunload", () => save_dat())
  373.  
  374. return proxy_dat({
  375. dat: raw_dat,
  376. scm: { lvs, rec: 1 },
  377. map: map ?? (s => s),
  378. old, oldRoot: old
  379. })
  380. }
  381.  
  382. const save_dat = (dat = raw_dat) => {
  383. Object.keys(dat).forEach(k => op.set(k, JSON.stringify(dat[k])))
  384. }
  385.  
  386. const clear_dat = () => {
  387. raw_dat = null
  388. op.list().forEach(op.del)
  389. }
  390.  
  391. // Debug
  392. if (location.host === "localhost:1633") Object.assign(unsafeWindow, {
  393. TM_dat: { type_dat, proxy_dat, load_dat, save_dat, clear_dat, raw_dat: () => raw_dat }
  394. })
  395.