Time Control

Script allowing you to control time.

当前为 2024-03-31 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Time Control
  3. // @description Script allowing you to control time.
  4. // @icon https://parsefiles.back4app.com/JPaQcFfEEQ1ePBxbf6wvzkPMEqKYHhPYv8boI1Rc/ce262758ff44d053136358dcd892979d_low_res_Time_Machine.png
  5. // @namespace mailto:lucaszheng2011@outlook.com
  6. // @version 1.2.3.6
  7. // @author lucaszheng
  8. // @license MIT
  9. //
  10. // @match *://*/*
  11. // @grant unsafeWindow
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_deleteValue
  15.  
  16. // @inject-into page
  17. // @run-at document-start
  18. // ==/UserScript==
  19. /*globals unsafeWindow,GM_setValue,GM_getValue,GM_deleteValue*/
  20.  
  21. (function (window) {
  22. 'use strict';
  23. let scale = 1, pristine = true;
  24. /** @type {null | number} */
  25. let timeJump = null;
  26.  
  27. let timeSync = false;
  28. let debug = false;
  29.  
  30. const {
  31. Reflect: {
  32. apply, construct,
  33. setPrototypeOf
  34. },
  35. Object: {
  36. defineProperty,
  37. freeze
  38. },
  39. Number: {
  40. isFinite
  41. },
  42. console: {
  43. trace: log
  44. }
  45. } = window;
  46.  
  47. function update() {
  48. for (let idx = 0; idx < updaters.length; idx++) {
  49. updaters[idx]();
  50. }
  51. }
  52.  
  53. const time = {
  54. /**
  55. * @param {number} newTime
  56. */
  57. jump(newTime) {
  58. if (newTime == null) return;
  59. pristine = false;
  60. timeJump = +newTime;
  61. update();
  62. timeJump = null;
  63. },
  64.  
  65. sync(syncTime = true, syncScale = true) {
  66. if (pristine) return;
  67. if (syncScale) scale = 1;
  68. if (!syncTime) return;
  69. timeSync = true;
  70. update();
  71. timeSync = false;
  72. pristine = scale === 1;
  73. },
  74.  
  75. storage: {
  76. /**
  77. * @param {number} newTime
  78. */
  79. jump(newTime) {
  80. GM_setValue('baseTime', time.real);
  81. GM_setValue('contTime', +newTime);
  82. },
  83.  
  84. save(saveTime = true, saveScale = true) {
  85. if (saveTime) {
  86. if (pristine) time.storage.reset(true, false);
  87. else time.storage.now = time.now;
  88. }
  89. if (saveScale) {
  90. if (scale === 1) time.storage.reset(false, true);
  91. else time.storage.scale = scale;
  92. }
  93. },
  94.  
  95. load(loadTime = true, loadScale = true) {
  96. if (time.storage.pristine) return time.sync();
  97. if (loadTime) {
  98. let baseTime = GM_getValue('baseTime', null);
  99. let contTime = GM_getValue('contTime', null);
  100. if (baseTime != null && contTime != null)
  101. time.jump((time.real - baseTime) + contTime);
  102. }
  103. if (loadScale) {
  104. time.scale = time.storage.scale;
  105. }
  106. },
  107.  
  108. reset(resetTime = true, resetScale = true) {
  109. if (resetTime) {
  110. GM_deleteValue('baseTime');
  111. GM_deleteValue('contTime');
  112. }
  113. if (resetScale) GM_deleteValue('scale');
  114. },
  115.  
  116. get now() {
  117. let baseTime = GM_getValue('baseTime', null);
  118. let contTime = GM_getValue('contTime', null);
  119. if (baseTime != null && contTime != null)
  120. return (time.real - baseTime) + contTime;
  121. return time.real;
  122. },
  123. set now(value) { time.storage.jump(value); },
  124.  
  125. get pristine() {
  126. let baseTime = GM_getValue('baseTime', null);
  127. let contTime = GM_getValue('contTime', null);
  128. let scale = GM_getValue('scale', null);
  129. return (baseTime == null || contTime == null) && scale == null;
  130. },
  131. set pristine(value) { if (!value) return; time.storage.reset(); },
  132.  
  133. get scale() {
  134. let scale = GM_getValue('scale', null);
  135. if (scale != null) return scale;
  136. return 1;
  137. },
  138. set scale(value) {
  139. if (value === time.storage.scale) return;
  140. GM_setValue('scale', +value);
  141. }
  142. },
  143.  
  144. get debug() { return debug; },
  145. set debug(value) { debug = !!value; },
  146.  
  147. get now() { return apply(date.now, DateConstructor, []); },
  148. set now(value) { time.jump(value); },
  149.  
  150. get pristine() { return pristine; },
  151. set pristine(value) { if (value) time.sync(); },
  152.  
  153. get real() { return apply(date.realTime, DateConstructor, []); },
  154.  
  155. get scale() { return scale; },
  156. set scale(value) {
  157. value = +value;
  158. if (value === scale) return;
  159. pristine = false; update(); scale = value;
  160. }
  161. };
  162.  
  163. freeze(time.storage);
  164. defineProperty(window, 'time', {
  165. value: freeze(time),
  166. writable: true,
  167. enumerable: false,
  168. configurable: true
  169. });
  170.  
  171. /** @type {(() => void)[]} */
  172. const updaters = [];
  173.  
  174. /**
  175. * @param {() => number} func
  176. * @param {any} self
  177. */
  178. function wrap_now(func, self, offset = 0) {
  179. let baseTime = 0;
  180. let contTime = baseTime;
  181.  
  182. /** @type {ProxyHandler<typeof func>} */
  183. const handler = {
  184. apply(target, self, args) {
  185. if (debug) log('apply(%o, %o, %o)', target, self, args);
  186. let time = apply(target, self, args);
  187. if (pristine || !isFinite(time)) return time;
  188. return ((time - baseTime) * scale) + contTime;
  189. }
  190. };
  191. setPrototypeOf(handler, null);
  192.  
  193. updaters[updaters.length] =
  194. function update() {
  195. if (!handler.apply) return;
  196. contTime = timeJump == null ? handler.apply(func, self, []) : timeJump + offset;
  197. baseTime = apply(func, self, []);
  198. if (timeSync) contTime = baseTime;
  199. };
  200.  
  201. return new Proxy(func, handler);
  202. }
  203.  
  204. window.Performance.prototype.now = wrap_now(
  205. window.Performance.prototype.now,
  206. window.performance,
  207. window.performance.now() - window.Date.now()
  208. );
  209.  
  210. const DateConstructor = window.Date;
  211. /** @type {{ realTime: typeof Date.now, now: typeof Date.now, toString: typeof Date.prototype.toString, handler: ProxyHandler<DateConstructor> }} */
  212. const date = {
  213. realTime: window.Date.now,
  214. now: wrap_now(window.Date.now, window.Date),
  215. toString: DateConstructor.prototype.toString,
  216. handler: {
  217. apply(target, self, args) {
  218. if (debug) log('apply(%o, %o, %o)', target, self, args);
  219. if (!pristine) {
  220. args.length = 1;
  221. args[0] = time.now;
  222. } else return DateConstructor();
  223. return apply(date.toString, construct(DateConstructor, args), []);
  224. },
  225. construct(target, args, newTarget) {
  226. if (debug) log('construct(%o, %o, %o)', target, args, newTarget);
  227. if (!pristine && args.length < 1) {
  228. args[0] = time.now;
  229. }
  230. return construct(DateConstructor, args, newTarget);
  231. }
  232. }
  233. };
  234. setPrototypeOf(date, null);
  235. setPrototypeOf(date.handler, null);
  236. DateConstructor.now = date.now;
  237.  
  238. window.Date = new Proxy(DateConstructor, date.handler);
  239. window.Date.prototype.constructor = window.Date;
  240.  
  241. function noop() { }
  242.  
  243. /**
  244. * @param {(handler: TimerHandler, timeout?: number | undefined, ...args: any[]) => number} func
  245. */
  246. function wrap_timer(func) {
  247. /** @type {ProxyHandler<typeof func>} */
  248. const handler = {
  249. apply(target, self, args) {
  250. if (debug) log('apply(%o, %o, %o)', target, self, args);
  251. if (!pristine && args.length > 1) {
  252. args[1] = +args[1];
  253. if (args[1] && scale === 0)
  254. args[0] = noop;
  255. else if (args[1] && isFinite(args[1]))
  256. args[1] /= scale;
  257. }
  258. return apply(target, self, args);
  259. }
  260. };
  261. setPrototypeOf(handler, null);
  262. return new Proxy(func, handler);
  263. }
  264.  
  265. window.setTimeout = wrap_timer(window.setTimeout);
  266. window.setInterval = wrap_timer(window.setInterval);
  267.  
  268. time.storage.load();
  269. })(/** @type {typeof window} */(unsafeWindow));