您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Script allowing you to control time.
// ==UserScript== // @name Time Control // @description Script allowing you to control time. // @icon https://parsefiles.back4app.com/JPaQcFfEEQ1ePBxbf6wvzkPMEqKYHhPYv8boI1Rc/ce262758ff44d053136358dcd892979d_low_res_Time_Machine.png // @namespace mailto:[email protected] // @version 1.4.0 // @author lucaszheng // @license MIT // // @match *://*/* // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @inject-into page // @run-at document-start // ==/UserScript== /*globals unsafeWindow,GM_setValue,GM_getValue,GM_deleteValue*/ (function (window) { 'use strict'; let scale = 1, pristine = true; /** @type {null | number} */ let timeJump = null; let timeReset = false; let debug = false; const { Reflect: { apply, construct, setPrototypeOf, getPrototypeOf, getOwnPropertyDescriptor }, Object: { defineProperty, freeze }, Event, Number: { isFinite }, Symbol: { toPrimitive, toStringTag }, console: { trace: log } } = window; function update() { for (let idx = 0; idx < updaters.length; idx++) { updaters[idx](); } } /** * @this { { toString: () => string; now: number }} * @param {'string' | 'number' | 'default'} type */ function timeToPrimitive(type) { switch (type) { case 'string': return this.toString(); default: return this.now; } } /** * @this { { now: number } } */ function timeToString() { return apply(date.toString, construct(DateConstructor, [this.now]), []); } const time = { [toStringTag]: 'time', [toPrimitive]: timeToPrimitive, toString: timeToString, /** * @param {number} newTime */ jump(newTime) { if (newTime == null) return; pristine = false; timeJump = +newTime; update(); timeJump = null; }, reset(resetTime = true, resetScale = true, resetDebug = true) { if (resetDebug) debug = false; if (pristine) return; if (resetScale) scale = 1; if (!resetTime) return; timeReset = true; update(); timeReset = false; pristine = scale === 1; }, storage: { [toStringTag]: 'storage', [toPrimitive]: timeToPrimitive, toString: timeToString, /** * @param {number} newTime */ jump(newTime) { GM_setValue('baseTime', time.real); GM_setValue('contTime', +newTime); }, save(saveTime = true, saveScale = true, saveDebug = true) { if (saveDebug) { if (debug === false) time.storage.reset(false, false, true); else time.storage.debug = debug; } if (saveTime) { if (pristine) time.storage.reset(true, false, false); else time.storage.now = time.now; } if (saveScale) { if (scale === 1) time.storage.reset(false, true, false); else time.storage.scale = scale; } }, load(loadTime = true, loadScale = true, loadDebug = true) { if (loadDebug) time.debug = time.storage.debug; if (time.storage.pristine) return time.reset(true, true, false); if (loadTime) { let baseTime = GM_getValue('baseTime', null); let contTime = GM_getValue('contTime', null); if (baseTime != null && contTime != null) time.jump((time.real - baseTime) + contTime); } if (loadScale) time.scale = time.storage.scale; }, reset(resetTime = true, resetScale = true, resetDebug = true) { if (resetTime) { GM_deleteValue('baseTime'); GM_deleteValue('contTime'); } if (resetScale) GM_deleteValue('scale'); if (resetDebug) GM_deleteValue('debug'); }, get debug() { return GM_getValue('debug', false); }, set debug(value) { GM_setValue('debug', !!value); }, get now() { let baseTime = GM_getValue('baseTime', null); let contTime = GM_getValue('contTime', null); if (baseTime != null && contTime != null) return (time.real - baseTime) + contTime; return time.real; }, set now(value) { time.storage.jump(value); }, get pristine() { let baseTime = GM_getValue('baseTime', null); let contTime = GM_getValue('contTime', null); let scale = GM_getValue('scale', null); return (baseTime == null || contTime == null) && scale == null; }, set pristine(value) { if (!value) return; time.storage.reset(true, true, false); }, get real() { return date.realTime(); }, get scale() { let scale = GM_getValue('scale', null); if (scale != null) return scale; return 1; }, set scale(value) { if (value === time.storage.scale) return; GM_setValue('scale', +value); } }, get debug() { return debug; }, set debug(value) { debug = !!value; }, get now() { return apply(date.now, DateConstructor, []); }, set now(value) { time.jump(value); }, get pristine() { return pristine; }, set pristine(value) { if (value) time.reset(); }, get real() { return date.realTime(); }, get scale() { return scale; }, set scale(value) { value = +value; if (value === scale) return; pristine = false; update(); scale = value; } }; freeze(time.storage); defineProperty(getPrototypeOf(window), 'time', { value: freeze(time), writable: true, enumerable: false, configurable: true }); /** @type {(() => void)[]} */ const updaters = []; /** * @template {() => number | null | undefined} T * @param {T} func * @param {object} self * @param {object | null} req_self * @param {(func: T) => number} offset */ function wrap_now(func, self, offset = () => 0, req_self = null) { let baseTime = 0; let contTime = baseTime; /** @type {ProxyHandler<typeof func>} */ const handler = { apply(target, self, args) { if (debug) log('apply(%o, %o, %o)', target, self, args); let time = apply(target, self, args); // pristine check necessary due to handler.apply(func, self, []) if (pristine || !isFinite(time) || (req_self !== null && self !== req_self)) return time; return ((time - baseTime) * scale) + contTime; } }; setPrototypeOf(handler, null); updaters[updaters.length] = function update() { if (!handler.apply) return; contTime = timeJump == null ? handler.apply(func, self, []) : timeJump + offset(func); baseTime = apply(func, self, []) ?? baseTime; if (timeReset) contTime = baseTime; }; return new Proxy(func, wrapHandler(handler)); } /** * @template {object} O * @template {keyof O} P * @template {(this: O) => Extract<O[P], number | null | undefined>} T * @param {O} obj * @param {P} prop * @param {() => object} getSelf * @param {null | ((getter: (...args: unknown[]) => O[P]) => T)} getFunc * @param {object | null} req_self * @param {(func: T) => number} offset */ function wrap_getter(obj, prop, getSelf, getFunc = null, offset = () => 0, req_self = null) { const propDesc = getOwnPropertyDescriptor(obj, prop); if (propDesc?.get) { const func = getFunc?.(propDesc.get) ?? propDesc.get, real_func = propDesc.get; let baseTime = 0; let contTime = baseTime; /** @type {ProxyHandler<typeof real_func>} */ const handler = { apply(_target, self, args) { // cannot show `self`, it results in infinite loop from Chrome Devtools automatically expanding document.timeline if (debug) log('apply(%o, self, %o)', func, args); let time = apply(func, self, args); // pristine check necessary due to handler.apply(func, self, []) if (pristine || !isFinite(time) || (req_self !== null && self !== req_self)) return time; return ((time - baseTime) * scale) + contTime; } }; setPrototypeOf(handler, null); updaters[updaters.length] = function update() { if (!handler.apply) return; contTime = timeJump == null ? handler.apply(real_func, getSelf(), []) : timeJump + offset(/** @type {T} */(func)); baseTime = apply(func, getSelf(), []) ?? baseTime; if (timeReset) contTime = baseTime; }; const wrappedGetter = new Proxy(real_func, wrapHandler(handler)); defineProperty(obj, prop, { configurable: propDesc.configurable, enumerable: propDesc.enumerable, get: wrappedGetter, set: propDesc.set }); return /** @type {T} */(wrappedGetter); } return null; } const DateConstructor = window.Date; /** @type {{ realTime: typeof Date.now, now: typeof Date.now, real_perfNow: typeof performance.now, toString: typeof Date.prototype.toString, handler: ProxyHandler<DateConstructor> }} */ const date = { realTime: window.Date.now, now: wrap_now(window.Date.now, window.Date), real_perfNow: window.performance.now.bind(performance), toString: DateConstructor.prototype.toString, handler: { apply(target, self, args) { if (debug) log('apply(%o, %o, %o)', target, self, args); return time.toString(); }, construct(target, args, newTarget) { if (debug) log('construct(%o, %o, %o)', target, args, newTarget); if (args.length < 1) { args[0] = time.now; } return construct(DateConstructor, args, newTarget); } } }; setPrototypeOf(date, null); setPrototypeOf(date.handler, null); DateConstructor.now = date.now; window.Date = new Proxy(DateConstructor, wrapHandler(date.handler)); window.Date.prototype.constructor = window.Date; window.Performance.prototype.now = wrap_now( window.Performance.prototype.now, window.performance, () => date.real_perfNow() - date.realTime(), window.performance ); function noop() { } /** * @param {(handler: TimerHandler, timeout?: number | undefined, ...args: any[]) => number} func */ function wrap_timer(func) { /** @type {ProxyHandler<typeof func>} */ const handler = { apply(target, self, args) { if (debug) log('apply(%o, %o, %o)', target, self, args); if (args.length > 1) { args[1] = +args[1]; if (args[1] && scale === 0) args[0] = noop; else if (args[1] && isFinite(args[1])) args[1] /= scale; } return apply(target, self, args); } }; setPrototypeOf(handler, null); return new Proxy(func, wrapHandler(handler)); } window.setTimeout = wrap_timer(window.setTimeout); window.setInterval = wrap_timer(window.setInterval); const docTimeline = window.document.timeline; const wrappedGetAnimTime = wrap_getter( window.AnimationTimeline.prototype, 'currentTime', () => docTimeline, (func) => function () { const time = apply(func, this, arguments); if (this !== docTimeline) return time; return typeof time === 'number' ? time : null; }, getAnimTime => (apply(getAnimTime, docTimeline, []) ?? date.real_perfNow()) - date.realTime(), docTimeline ); if (wrappedGetAnimTime) { /** @type {ProxyHandler<typeof requestAnimationFrame>} */ const handler = { apply(target, self, args) { if (debug) log('apply(%o, %o, %o)', target, self, args); if (typeof args[0] === 'function') { const cb = args[0]; args[0] = function () { if (!pristine) arguments[0] = apply(wrappedGetAnimTime, docTimeline, []); return apply(cb, this, arguments); } } return apply(target, self, args); } }; setPrototypeOf(handler, null); window.requestAnimationFrame = new Proxy(window.requestAnimationFrame, wrapHandler(handler)); } wrap_getter( window.Event.prototype, 'timeStamp', () => new Event(''), null, getTimeStamp => (apply(getTimeStamp, new Event(''), []) ?? date.real_perfNow()) - date.realTime() ); /** * @param {ProxyHandler<any>} handler */ function wrapHandler(handler) { /** @type {ProxyHandler<ProxyHandler<any>>} */ const internalHandler = { get(target, prop) { if (pristine) return undefined; return Reflect.get(target, prop); } }; setPrototypeOf(internalHandler, null); return new Proxy(handler, internalHandler); } time.storage.load(); })(/** @type {typeof window} */(unsafeWindow));