Time Control

Script allowing you to control time.

  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.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 timeReset = false;
  28. let debug = false;
  29.  
  30. const {
  31. Reflect: {
  32. apply, construct,
  33. setPrototypeOf,
  34. getPrototypeOf
  35. },
  36. Object: {
  37. defineProperty,
  38. freeze
  39. },
  40. Number: {
  41. isFinite
  42. },
  43. Symbol: {
  44. toPrimitive,
  45. toStringTag
  46. },
  47. console: {
  48. trace: log
  49. }
  50. } = window;
  51.  
  52. function update() {
  53. for (let idx = 0; idx < updaters.length; idx++) {
  54. updaters[idx]();
  55. }
  56. }
  57.  
  58. /**
  59. * @param {'string' | 'number' | 'default'} type
  60. */
  61. function timeToPrimitive(type) {
  62. switch (type) {
  63. case 'string': return this.toString();
  64. default: return this.now;
  65. }
  66. }
  67.  
  68. function timeToString() {
  69. return apply(date.toString, construct(DateConstructor, [this.now]), []);
  70. }
  71.  
  72. const time = {
  73. [toStringTag]: 'time',
  74. [toPrimitive]: timeToPrimitive,
  75. toString: timeToString,
  76. /**
  77. * @param {number} newTime
  78. */
  79. jump(newTime) {
  80. if (newTime == null) return;
  81. pristine = false;
  82. timeJump = +newTime;
  83. update();
  84. timeJump = null;
  85. },
  86.  
  87. reset(resetTime = true, resetScale = true, resetDebug = true) {
  88. if (resetDebug) debug = false;
  89. if (pristine) return;
  90.  
  91. if (resetScale) scale = 1;
  92.  
  93. if (!resetTime) return;
  94. timeReset = true;
  95. update();
  96. timeReset = false;
  97. pristine = scale === 1;
  98. },
  99.  
  100. storage: {
  101. [toStringTag]: 'storage',
  102. [toPrimitive]: timeToPrimitive,
  103. toString: timeToString,
  104.  
  105. /**
  106. * @param {number} newTime
  107. */
  108. jump(newTime) {
  109. GM_setValue('baseTime', time.real);
  110. GM_setValue('contTime', +newTime);
  111. },
  112.  
  113. save(saveTime = true, saveScale = true, saveDebug = true) {
  114. if (saveDebug) {
  115. if (debug === false) time.storage.reset(false, false, true);
  116. else time.storage.debug = debug;
  117. }
  118. if (saveTime) {
  119. if (pristine) time.storage.reset(true, false, false);
  120. else time.storage.now = time.now;
  121. }
  122. if (saveScale) {
  123. if (scale === 1) time.storage.reset(false, true, false);
  124. else time.storage.scale = scale;
  125. }
  126. },
  127.  
  128. load(loadTime = true, loadScale = true, loadDebug = true) {
  129. if (loadDebug) time.debug = time.storage.debug;
  130. if (time.storage.pristine) return time.reset(true, true, false);
  131.  
  132. if (loadTime) {
  133. let baseTime = GM_getValue('baseTime', null);
  134. let contTime = GM_getValue('contTime', null);
  135. if (baseTime != null && contTime != null)
  136. time.jump((time.real - baseTime) + contTime);
  137. }
  138. if (loadScale) time.scale = time.storage.scale;
  139. },
  140.  
  141. reset(resetTime = true, resetScale = true, resetDebug = true) {
  142. if (resetTime) {
  143. GM_deleteValue('baseTime');
  144. GM_deleteValue('contTime');
  145. }
  146. if (resetScale) GM_deleteValue('scale');
  147. if (resetDebug) GM_deleteValue('debug');
  148. },
  149.  
  150. get debug() { return GM_getValue('debug', false); },
  151. set debug(value) { GM_setValue('debug', !!value); },
  152.  
  153. get now() {
  154. let baseTime = GM_getValue('baseTime', null);
  155. let contTime = GM_getValue('contTime', null);
  156. if (baseTime != null && contTime != null)
  157. return (time.real - baseTime) + contTime;
  158. return time.real;
  159. },
  160. set now(value) { time.storage.jump(value); },
  161.  
  162. get pristine() {
  163. let baseTime = GM_getValue('baseTime', null);
  164. let contTime = GM_getValue('contTime', null);
  165. let scale = GM_getValue('scale', null);
  166. return (baseTime == null || contTime == null) && scale == null;
  167. },
  168. set pristine(value) {
  169. if (!value) return;
  170. time.storage.reset(true, true, false);
  171. },
  172.  
  173. get real() { return apply(date.realTime, DateConstructor, []); },
  174.  
  175. get scale() {
  176. let scale = GM_getValue('scale', null);
  177. if (scale != null) return scale;
  178. return 1;
  179. },
  180. set scale(value) {
  181. if (value === time.storage.scale) return;
  182. GM_setValue('scale', +value);
  183. }
  184. },
  185.  
  186. get debug() { return debug; },
  187. set debug(value) { debug = !!value; },
  188.  
  189. get now() { return apply(date.now, DateConstructor, []); },
  190. set now(value) { time.jump(value); },
  191.  
  192. get pristine() { return pristine; },
  193. set pristine(value) { if (value) time.reset(); },
  194.  
  195. get real() { return apply(date.realTime, DateConstructor, []); },
  196.  
  197. get scale() { return scale; },
  198. set scale(value) {
  199. value = +value;
  200. if (value === scale) return;
  201. pristine = false; update(); scale = value;
  202. }
  203. };
  204.  
  205. freeze(time.storage);
  206. defineProperty(getPrototypeOf(window), 'time', {
  207. value: freeze(time),
  208. writable: true,
  209. enumerable: false,
  210. configurable: true
  211. });
  212.  
  213. /** @type {(() => void)[]} */
  214. const updaters = [];
  215.  
  216. /**
  217. * @param {() => number} func
  218. * @param {any} self
  219. */
  220. function wrap_now(func, self, offset = 0) {
  221. let baseTime = 0;
  222. let contTime = baseTime;
  223.  
  224. /** @type {ProxyHandler<typeof func>} */
  225. const handler = {
  226. apply(target, self, args) {
  227. if (debug) log('apply(%o, %o, %o)', target, self, args);
  228. let time = apply(target, self, args);
  229. if (pristine || !isFinite(time)) return time;
  230. return ((time - baseTime) * scale) + contTime;
  231. }
  232. };
  233. setPrototypeOf(handler, null);
  234.  
  235. updaters[updaters.length] =
  236. function update() {
  237. if (!handler.apply) return;
  238. contTime = timeJump == null ? handler.apply(func, self, []) : timeJump + offset;
  239. baseTime = apply(func, self, []);
  240. if (timeReset) contTime = baseTime;
  241. };
  242.  
  243. return new Proxy(func, handler);
  244. }
  245.  
  246. window.Performance.prototype.now = wrap_now(
  247. window.Performance.prototype.now,
  248. window.performance,
  249. window.performance.now() - window.Date.now()
  250. );
  251.  
  252. const DateConstructor = window.Date;
  253. /** @type {{ realTime: typeof Date.now, now: typeof Date.now, toString: typeof Date.prototype.toString, handler: ProxyHandler<DateConstructor> }} */
  254. const date = {
  255. realTime: window.Date.now,
  256. now: wrap_now(window.Date.now, window.Date),
  257. toString: DateConstructor.prototype.toString,
  258. handler: {
  259. apply(target, self, args) {
  260. if (debug) log('apply(%o, %o, %o)', target, self, args);
  261. if (pristine) return DateConstructor();
  262. return time.toString();
  263. },
  264. construct(target, args, newTarget) {
  265. if (debug) log('construct(%o, %o, %o)', target, args, newTarget);
  266. if (!pristine && args.length < 1) {
  267. args[0] = time.now;
  268. }
  269. return construct(DateConstructor, args, newTarget);
  270. }
  271. }
  272. };
  273. setPrototypeOf(date, null);
  274. setPrototypeOf(date.handler, null);
  275. DateConstructor.now = date.now;
  276.  
  277. window.Date = new Proxy(DateConstructor, date.handler);
  278. window.Date.prototype.constructor = window.Date;
  279.  
  280. function noop() { }
  281.  
  282. /**
  283. * @param {(handler: TimerHandler, timeout?: number | undefined, ...args: any[]) => number} func
  284. */
  285. function wrap_timer(func) {
  286. /** @type {ProxyHandler<typeof func>} */
  287. const handler = {
  288. apply(target, self, args) {
  289. if (debug) log('apply(%o, %o, %o)', target, self, args);
  290. if (!pristine && args.length > 1) {
  291. args[1] = +args[1];
  292. if (args[1] && scale === 0)
  293. args[0] = noop;
  294. else if (args[1] && isFinite(args[1]))
  295. args[1] /= scale;
  296. }
  297. return apply(target, self, args);
  298. }
  299. };
  300. setPrototypeOf(handler, null);
  301. return new Proxy(func, handler);
  302. }
  303.  
  304. window.setTimeout = wrap_timer(window.setTimeout);
  305. window.setInterval = wrap_timer(window.setInterval);
  306.  
  307. time.storage.load();
  308. })(/** @type {typeof window} */(unsafeWindow));