From d21b9815140bfdd342c7c13b8fcafd50f80262a5 Mon Sep 17 00:00:00 2001 From: An0 Date: Fri, 4 Sep 2020 17:14:16 +0000 Subject: [PATCH 1/2] Added the stealthy overlay, no version number change yet --- stable.user.js | 106 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 12 deletions(-) diff --git a/stable.user.js b/stable.user.js index 0cea968..e214317 100755 --- a/stable.user.js +++ b/stable.user.js @@ -38,6 +38,7 @@ // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_openInTab +// @grant unsafeWindow // @license GNU General Public License v3.0 or later // @supportURL qmining.frylabs.net // @contributionURL qmining.frylabs.net @@ -208,6 +209,86 @@ // all dom getting stuff are in this sections, so on // moodle dom change, stuff breaks here + //Stealth by An0 with love + function StealthOverlay() { //call this before the document scripts + const document = window.document; + + const neverEqualPlaceholder = Symbol(`never equal`); //block probing for undefined values in the hooks + let shadowRootHost = neverEqualPlaceholder; + let shadowRootNewHost = neverEqualPlaceholder; + + const apply = Reflect.apply; //save some things in case they get hooked (only for unsafe contexts) + + + if(unsafeWindow.Error.hasOwnProperty('stackTraceLimit')) { + Reflect.defineProperty(unsafeWindow.Error, 'stackTraceLimit', { value: undefined, writable: false, enumerable: false, configurable: false }); + } + + const shadowGetHandler = { apply: (target, thisArg, argumentsList) => apply(target, (thisArg === shadowRootHost) ? shadowRootNewHost : thisArg, argumentsList) }; + + const original_attachShadow = unsafeWindow.Element.prototype.attachShadow; + const attachShadowProxy = new Proxy(original_attachShadow, shadowGetHandler); + unsafeWindow.Element.prototype.attachShadow = attachShadowProxy; + + const getShadowRootProxy = new Proxy(Object.getOwnPropertyDescriptor(unsafeWindow.Element.prototype, 'shadowRoot').get, shadowGetHandler); + Object.defineProperty(unsafeWindow.Element.prototype, 'shadowRoot', { get: getShadowRootProxy }); + + const getHostHandler = { apply: function() { let result = apply(...arguments); return (result === shadowRootNewHost) ? shadowRootHost : result; } }; + const getHostProxy = new Proxy(Object.getOwnPropertyDescriptor(unsafeWindow.ShadowRoot.prototype, 'host').get, getHostHandler); + Object.defineProperty(unsafeWindow.ShadowRoot.prototype, 'host', { get: getHostProxy }); + + + const shadowRootSetInnerHtml = Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'innerHTML').set; + const documentFragmentGetChildren = Object.getOwnPropertyDescriptor(DocumentFragment.prototype, 'children').get; + const documentGetBody = Object.getOwnPropertyDescriptor(Document.prototype, 'body').get; + const nodeAppendChild = Node.prototype.appendChild; + + const overlay = document.createElement('div'); + overlay.style.cssText = "position:absolute;left:0;top:0"; + + const addOverlay = () => { + shadowRootHost = apply(documentGetBody, document, []); + const shadowRoot = apply(original_attachShadow, shadowRootHost, [{mode:'closed'}]); + apply(shadowRootSetInnerHtml, shadowRoot, [`
`]); + shadowRootNewHost = apply(documentFragmentGetChildren, shadowRoot, [])[0]; + apply(nodeAppendChild, shadowRoot, [overlay]); + }; + + if (!document.body) { + document.addEventListener('DOMContentLoaded', addOverlay); + } + else { + addOverlay(); + } + return overlay; + } + + const overlay = StealthOverlay(); + + + function createHoverOver(target) { + const overlayElement = document.createElement('div'); + overlayElement.style.cssText = "position:fixed; pointer-events: none; user-select: none; z-index:10000"; + overlay.append(overlayElement); + let currX, currY, currWidth, currHeight; + const copyBoundingRect = () => { + let { x, y, width, height } = target.getBoundingClientRect(); + if(x !== currX) { overlayElement.style.left = x + 'px'; currX = x; } + if(y !== currY) { overlayElement.style.top = y + 'px'; currY = y; } + if(width !== currWidth) { overlayElement.style.width = width + 'px'; currWidth = width; } + if(height !== currHeight) { overlayElement.style.height = height + 'px'; currHeight = height; } + }; + copyBoundingRect(); + const interval = setInterval(copyBoundingRect, 30); + overlayElement.destroy = () => { + clearInterval(interval); + overlayElement.remove(); + }; + return overlayElement; + } + + + class QuestionsPageModell { GetAllQuestionsDropdown () { if (logElementGetting) { Log('getting dropdown question') } @@ -716,8 +797,8 @@ } if (forceTestPage || forceResultPage || forceDefaultPage) { - if (document.getElementById('scriptMessage')) { - document.getElementById('scriptMessage').style.background = 'green' + if (overlay.querySelector('#scriptMessage')) { + overlay.querySelector('#scriptMessage').style.background = 'green' } } } @@ -852,7 +933,7 @@ lastestVersion = inf.version motd = inf.motd subjInfo = inf.subjinfo - document.getElementById('infoMainDiv').innerText = `${subjInfo.subjects} tárgy, ${subjInfo.questions} kérdés. Felh #${inf.uid}` + overlay.querySelector('#infoMainDiv').innerText = `${subjInfo.subjects} tárgy, ${subjInfo.questions} kérdés. Felh #${inf.uid}` // FIXME: if cwith() throws an unhandled error it sais server is not avaible cwith() }).catch(() => { @@ -1299,7 +1380,12 @@ if (results[0].match === 100) { // if the result is 100% correct if (type !== 'radio' || toColor.length === 1) { // FIXME why not radio for (let i = 0; i < toColor.length; i++) { // going through "toColor" - answers[toColor[i]].style.backgroundColor = '#8cff66' + let highlight = createHoverOver(answers[toColor[i]]); + Object.assign(highlight.style, { + border: "7px solid rgba(100, 240, 100, 0.8)", + borderRadius: "10px", + margin: "-13px 0 0 -8px" + }); } } } // and coloring the correct index @@ -1328,11 +1414,7 @@ // : Minor UI stuff {{{ function ClearAllMessages () { - let elem = document.getElementById('scriptMessage') - while (elem) { - elem.parentNode.removeChild(elem) - elem = document.getElementById('scriptMessage') - } + overlay.querySelectorAll('#scriptMessage').forEach(x => x.remove()) } // shows a message with "msg" text, "matchPercent" tip and transp, and "timeout" time @@ -1360,7 +1442,7 @@ isSimpleMessage = true } - var appedtTo = document.body // will be appended here + var appedtTo = overlay // will be appended here var width = window.innerWidth - window.innerWidth / 6 // with of the box var startFromTop = 25 // top distance @@ -1551,7 +1633,7 @@ // shows a fancy menu function ShowMenu () { try { - var appedtTo = document.body // will be appended here + var appedtTo = overlay // will be appended here // mainDiv.style.left = (window.innerWidth - width) / 2 + 'px'; @@ -1763,7 +1845,7 @@ } function SafeGetElementById (id, next) { - let element = document.getElementById(id) + let element = overlay.querySelector('#' + id) if (element) { next(element) } else { From 1f111c745d578101f7e23bb333dffb78673b5eea Mon Sep 17 00:00:00 2001 From: An0 Date: Sat, 5 Sep 2020 13:46:38 +0000 Subject: [PATCH 2/2] Made the script start loading before the site is loaded --- stable.user.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/stable.user.js b/stable.user.js index e214317..91a7e9d 100755 --- a/stable.user.js +++ b/stable.user.js @@ -31,6 +31,7 @@ // @match https://qmining.frylabs.net/* // @match http://qmining.frylabs.net/* // @noframes +// @run-at document-start // @grant GM_getResourceText // @grant GM_info // @grant GM_getValue @@ -762,7 +763,12 @@ console.time('main') timerStarted = true - Init() + if(document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', Init) + } + else { + Init() + } } function AfterLoad () {