Greasy Fork is available in English.
Control page timer speed | Speed up to skip page timing ads | Video fast forward (slow play) | Skip ads | Support almost all web pages.
// ==UserScript==
// @name TimerHooker English Version
// @name:en TimerHooker English Version
// @version 1.2.2
// @description Control page timer speed | Speed up to skip page timing ads | Video fast forward (slow play) | Skip ads | Support almost all web pages.
// @include *
// @require https://greasyfork.org/scripts/372672-everything-hook/code/Everything-Hook.js?version=881251
// @author Tiger 27
// @match http://*/*
// @run-at document-start
// @grant none
// @license GPL-3.0-or-later
// @namespace https://
// ==/UserScript==
window.isDOMLoaded = false;
window.isDOMRendered = false;
document.addEventListener('readystatechange', function () {
if (document.readyState === "interactive" || document.readyState === "complete") {
window.isDOMLoaded = true;
}
});
~function (global) {
var workerURLs = [];
var extraElements = [];
var suppressEvents = {};
var helper = function (eHookContext, timerContext, util) {
return {
applyUI: function () {
var style = '._th-container ._th-item{margin-bottom:3px;position:relative;width:0;height:0;cursor:pointer;opacity:.3;background-color:aquamarine;border-radius:100%;text-align:center;line-height:30px;-webkit-transition:all .35s;-o-transition:all .35s;transition:all .35s;right:30px}._th-container ._th-item,._th-container ._th-click-hover,._th_cover-all-show-times ._th_times{-webkit-box-shadow:-3px 4px 12px -5px black;box-shadow:-3px 4px 12px -5px black}._th-container:hover ._th-item._item-x2{margin-left:18px;width:40px;height:40px;line-height:40px}._th-container:hover ._th-item._item-x-2{margin-left:17px;width:38px;height:38px;line-height:38px}._th-container:hover ._th-item._item-xx2{width:36px;height:36px;margin-left:16px;line-height:36px}._th-container:hover ._th-item._item-xx-2{width:32px;height:32px;line-height:32px;margin-left:14px}._th-container:hover ._th-item._item-reset{width:30px;line-height:30px;height:30px;margin-left:10px}._th-click-hover{position:relative;-webkit-transition:all .5s;-o-transition:all .5s;transition:all .5s;height:45px;width:45px;cursor:move;opacity:.3;border-radius:100%;background-color:aquamarine;text-align:center;line-height:45px;right:0}._th-container{font-size:12px;-webkit-transition:all .5s;-o-transition:all .5s;transition:all .5s;left:10px;top:20%;position:fixed;-webkit-box-sizing:border-box;box-sizing:border-box;z-index:100000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}._th-container ._th-item:hover{opacity:.8;background-color:#5fb492;color:aliceblue}._th-container ._th-item:active{opacity:.9;background-color:#1b3a26;color:aliceblue}._th-container:hover ._th-click-hover{opacity:.8}._th-container:hover ._th-item{opacity:.6;right:0}._th-container ._th-click-hover:hover{opacity:.8;background-color:#5fb492;color:aliceblue}._th_cover-all-show-times{position:fixed;top:0;right:0;width:100%;height:100%;z-index:99999;opacity:1;font-weight:900;font-size:30px;color:#4f4f4f;background-color:rgba(0,0,0,0.1)}._th_cover-all-show-times._th_hidden{z-index:-99999;opacity:0;-webkit-transition:1s all;-o-transition:1s all;transition:1s all}._th_cover-all-show-times ._th_times{width:300px;height:300px;border-radius:50%;background-color:rgba(127,255,212,0.51);text-align:center;line-height:300px;position:absolute;top:50%;right:50%;margin-top:-150px;margin-right:-150px}';
var displayNum = (1 / timerContext._percentage).toFixed(2);
var html = '<div class="_th-container">\n' +
' <div class="_th-click-hover _item-input">x' + displayNum + '</div>\n' +
' <div class="_th-item _item-x2">></div>\n' +
' <div class="_th-item _item-x-2"><</div>\n' +
' <div class="_th-item _item-xx2">>></div>\n' +
' <div class="_th-item _item-xx-2"><<</div>\n' +
' <div class="_th-item _item-reset">O</div>\n' +
'</div>\n' +
'<div class="_th_cover-all-show-times _th_hidden"><div class="_th_times">x' + displayNum + '</div></div>';
var stylenode = document.createElement('style');
stylenode.appendChild(document.createTextNode(style));
var node = document.createElement('div');
node.innerHTML = html;
var container = node.getElementsByClassName('_th-container')[0];
var dragHandle = node.getElementsByClassName('_item-input')[0];
var savedPos = JSON.parse(localStorage.getItem('timerHookerPos'));
if (savedPos) {
container.style.left = savedPos.left;
container.style.top = savedPos.top;
}
dragHandle.onmousedown = function(e) {
var rect = container.getBoundingClientRect();
var shiftX = e.clientX - rect.left;
var shiftY = e.clientY - rect.top;
function onMouseMove(e) {
container.style.left = (e.clientX - shiftX) + 'px';
container.style.top = (e.clientY - shiftY) + 'px';
}
document.addEventListener('mousemove', onMouseMove);
document.onmouseup = function() {
document.removeEventListener('mousemove', onMouseMove);
localStorage.setItem('timerHookerPos', JSON.stringify({left: container.style.left, top: container.style.top}));
document.onmouseup = null;
};
};
var clickMapper = {
'_item-input': function () { changeTime(); },
'_item-x2': function () { changeTime(2, 0, true); },
'_item-x-2': function () { changeTime(-2, 0, true); },
'_item-xx2': function () { changeTime(0, 2); },
'_item-xx-2': function () { changeTime(0, -2); },
'_item-reset': function () { changeTime(0, 0, false, true); }
};
Object.keys(clickMapper).forEach(function (className) {
var targetEle = node.getElementsByClassName(className)[0];
if (targetEle) targetEle.onclick = clickMapper[className];
});
var inject = function() {
document.head.appendChild(stylenode);
document.body.appendChild(node);
global.isDOMRendered = true;
};
if (!global.isDOMLoaded) {
document.addEventListener('readystatechange', function () {
if ((document.readyState === "interactive" || document.readyState === "complete") && !global.isDOMRendered) {
inject();
}
});
} else {
inject();
}
},
applyGlobalAction: function (timer) {
timer.changeTime = function (anum, cnum, isa, isr) {
if (isr) { global.timer.change(1); return; }
if (!global.timer) return;
var result;
if (!anum && !cnum) {
var t = prompt("Enter speed:", 1 / timerContext._percentage);
if (t == null || isNaN(parseFloat(t)) || parseFloat(t) <= 0) return;
result = 1 / parseFloat(t);
} else {
if (isa && anum) result = 1 / (1 / timerContext._percentage + anum);
else result = 1 / ((1 / timerContext._percentage) * (cnum <= 0 ? (1 / -cnum) : cnum));
}
timer.change(result);
};
global.changeTime = timer.changeTime;
},
applyHooking: function () {
var _this = this;
eHookContext.hookReplace(window, 'setInterval', function (setInterval) { return _this.getHookedTimerFunction('interval', setInterval); });
eHookContext.hookReplace(window, 'setTimeout', function (setTimeout) { return _this.getHookedTimerFunction('timeout', setTimeout) });
eHookContext.hookBefore(window, 'clearInterval', function (method, args) { _this.redirectNewestId(args); });
eHookContext.hookBefore(window, 'clearTimeout', function (method, args) { _this.redirectNewestId(args); });
var newFunc = this.getHookedDateConstructor();
eHookContext.hookClass(window, 'Date', newFunc, '_innerDate', ['now']);
Date.now = function () { return new Date().getTime(); };
eHookContext.hookedToString(timerContext._Date.now, Date.now);
var objToString = Object.prototype.toString;
Object.prototype.toString = function toString() {
'use strict';
if (this instanceof timerContext._mDate) return '[object Date]';
else return objToString.call(this);
};
eHookContext.hookedToString(objToString, Object.prototype.toString);
eHookContext.hookedToString(timerContext._setInterval, setInterval);
eHookContext.hookedToString(timerContext._setTimeout, setTimeout);
eHookContext.hookedToString(timerContext._clearInterval, clearInterval);
timerContext._mDate = window.Date;
this.hookShadowRoot();
},
getHookedDateConstructor: function () {
return function () {
if (arguments.length === 1) {
Object.defineProperty(this, '_innerDate', { configurable: false, enumerable: false, value: new timerContext._Date(arguments[0]), writable: false });
return;
}
var now = timerContext._Date.now();
var passTime = now - timerContext.__lastDatetime;
var hookPassTime = passTime * (1 / timerContext._percentage);
Object.defineProperty(this, '_innerDate', { configurable: false, enumerable: false, value: new timerContext._Date(timerContext.__lastMDatetime + hookPassTime), writable: false });
};
},
getHookedTimerFunction: function (type, timer) {
var property = '_' + type + 'Ids';
return function () {
var uniqueId = timerContext.genUniqueId();
var callback = arguments[0];
if (typeof callback === 'function') {
arguments[0] = function () {
var returnValue = callback.apply(this, arguments);
timerContext.notifyExec(uniqueId);
return returnValue;
}
}
var originMS = arguments[1];
arguments[1] *= timerContext._percentage;
var resultId = timer.apply(window, arguments);
timerContext[property][resultId] = { args: arguments, originMS: originMS, originId: resultId, nowId: resultId, uniqueId: uniqueId, oldPercentage: timerContext._percentage, exceptNextFireTime: timerContext._Date.now() + originMS };
return resultId;
};
},
redirectNewestId: function (args) {
var id = args[0];
if (timerContext._intervalIds[id]) { args[0] = timerContext._intervalIds[id].nowId; delete timerContext._intervalIds[id]; }
if (timerContext._timeoutIds[id]) { args[0] = timerContext._timeoutIds[id].nowId; delete timerContext._timeoutIds[id]; }
},
registerShortcutKeys: function (timer) {
addEventListener('keydown', function (e) {
if (e.ctrlKey || e.altKey) {
switch (e.keyCode) {
case 57: timer.changeTime(); break;
case 190: case 187: e.ctrlKey ? timer.changeTime(2, 0, true) : timer.changeTime(0, 2); break;
case 188: case 189: e.ctrlKey ? timer.changeTime(-2, 0, true) : timer.changeTime(0, -2); break;
case 48: timer.changeTime(0, 0, false, true); break;
}
}
});
},
percentageChangeHandler: function (percentage) {
util.ergodicObject(timerContext, timerContext._intervalIds, function (idObj, id) {
idObj.args[1] = Math.floor((idObj.originMS || 1) * percentage);
this._clearInterval.call(window, idObj.nowId);
idObj.nowId = this._setInterval.apply(window, idObj.args);
});
util.ergodicObject(timerContext, timerContext._timeoutIds, function (idObj, id) {
var now = this._Date.now();
var changedTime = Math.floor(percentage / idObj.oldPercentage * (idObj.exceptNextFireTime - now));
idObj.args[1] = changedTime < 0 ? 0 : changedTime;
idObj.exceptNextFireTime = now + idObj.args[1];
idObj.oldPercentage = percentage;
this._clearTimeout.call(window, idObj.nowId);
idObj.nowId = this._setTimeout.apply(window, idObj.args);
});
},
hookShadowRoot: function () {
var origin = Element.prototype.attachShadow;
eHookContext.hookAfter(Element.prototype, 'attachShadow', function (m, args, result) { extraElements.push(result); return result; }, false);
},
hookDefine: function () {
const _this = this;
eHookContext.hookBefore(Object, 'defineProperty', function (m, args) {
if (args[1] === 'playbackRate' && args[0] instanceof HTMLVideoElement) {
args[2].configurable = true;
args[1] = 'playbackRate_hooked';
}
});
},
changePlaybackRate: function (ele, rate) {
delete ele.playbackRate;
ele.playbackRate = rate;
}
}
};
var normalUtil = {
isInIframe: function () { try { return global.parent !== global && global.parent.document.body.tagName !== 'FRAMESET'; } catch (e) { return false; } },
listenParentEvent: function (handler) { global.addEventListener('message', function (e) { if (e.data.type === 'changePercentage') handler(e.data.percentage); }); },
sentChangesToIframe: function (percentage) {
var all = document.querySelectorAll('iframe, frame');
for (var i = 0; i < all.length; i++) all[i].contentWindow.postMessage({type: 'changePercentage', percentage: percentage}, '*');
}
};
var querySelectorAll = function (ele, selector, includeExtra) {
var elements = Array.prototype.slice.call(ele.querySelectorAll(selector));
return elements;
};
var generate = function () {
return function (util) {
var eHookContext = this;
var timerHooker = {
_intervalIds: {}, _timeoutIds: {}, _auoUniqueId: 1, __percentage: 1.0,
_setInterval: window['setInterval'], _clearInterval: window['clearInterval'], _clearTimeout: window['clearTimeout'], _setTimeout: window['setTimeout'], _Date: window['Date'],
__lastDatetime: new Date().getTime(), __lastMDatetime: new Date().getTime(),
genUniqueId: function () { return this._auoUniqueId++; },
notifyExec: function (uniqueId) {
var _this = this;
Object.values(this._timeoutIds).filter(info => info.uniqueId === uniqueId).forEach(info => { _this._clearTimeout.call(window, info.nowId); delete _this._timeoutIds[info.originId]; });
},
init: function () {
var timerContext = this;
var h = helper(eHookContext, timerContext, util);
h.hookDefine();
h.applyHooking();
Object.defineProperty(timerContext, '_percentage', {
get: function () { return timerContext.__percentage; },
set: function (percentage) { if (percentage !== timerContext.__percentage) { h.percentageChangeHandler(percentage); timerContext.__percentage = percentage; } return percentage; }
});
if (!normalUtil.isInIframe()) { h.applyUI(); h.applyGlobalAction(timerContext); h.registerShortcutKeys(timerContext); }
else { normalUtil.listenParentEvent((function (percentage) { this.change(percentage); }).bind(this)); }
},
change: function (percentage) {
this.__lastMDatetime = this._mDate.now();
this.__lastDatetime = this._Date.now();
this._percentage = percentage;
var displayNum = (1 / this._percentage).toFixed(2);
var inputs = document.getElementsByClassName('_item-input');
if (inputs[0]) inputs[0].innerHTML = 'x' + displayNum;
this.changeVideoSpeed();
normalUtil.sentChangesToIframe(percentage);
},
changeVideoSpeed: function () {
var h = helper(eHookContext, this, util);
var rate = 1 / this._percentage;
querySelectorAll(document, 'video', true).forEach(v => h.changePlaybackRate(v, rate));
}
};
timerHooker.init();
return timerHooker;
}
};
if (global.eHook) global.eHook.plugins({ name: 'timer', mount: generate() });
}(window);