Skip to content

Instantly share code, notes, and snippets.

@icebob
Last active April 8, 2025 18:27
Show Gist options
  • Save icebob/b662bd73f28816a1716dab0844814a09 to your computer and use it in GitHub Desktop.
Save icebob/b662bd73f28816a1716dab0844814a09 to your computer and use it in GitHub Desktop.
TamperMonkey helper scripts
function htmlToNode(html) {
const template = document.createElement('template');
template.innerHTML = html;
const nNodes = template.content.childNodes.length;
if (nNodes !== 1) {
throw new Error(
`html parameter must represent a single node; got ${nNodes}. ` +
'Note that leading or trailing spaces around an element in your ' +
'HTML, like " <img/> ", get parsed as text nodes neighbouring ' +
'the element; call .trim() on your input to avoid this.'
);
}
return template.content.firstChild;
}
function downloadBlob(filename, blob) {
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", filename);
document.body.appendChild(link);
link.click();
}
function querySelectorIncludesText (selector, text){
return Array.from(document.querySelectorAll(selector))
.filter(el => el.textContent.includes(text));
}
function findClosestParent (startElement, fn) {
var parent = startElement.parentElement;
if (!parent) return undefined;
return fn(parent) ? parent : findClosestParent(parent, fn);
}
function xhrPatcher(xhr, patches) {
async function doPatch(x) {
if (x.__tmPatched) return;
for (const patch of patches) {
if (!patch.match || patch.match(x.__tmUrl, x.__tmMethod, x.__tmData, x)) {
if (patch.readyState == null || patch.readyState == x.readyState) {
const res = await patch.patch(x.response, x.responseType, x);
if (res !== undefined) {
Object.defineProperty(x, 'response', { writable: true });
x.response = res;
if (typeof res == "string") {
Object.defineProperty(x, 'responseText', { writable: true });
x.responseText = res;
}
}
x.__tmPatched = true;
}
//console.log(`Monkey patched ${x.__tmMethod} ${x.__tmUrl}:`, x.response);
}
}
}
const originalAddEventListener = xhr.addEventListener;
xhr.addEventListener = function(type, listener, options) {
if (type == 'readystatechange') {
var patchedListener = async function(event) {
doPatch(this);
listener.call(this, event);
};
originalAddEventListener.call(this, type, patchedListener, options);
this.__tmListenerPatched = true;
} else {
originalAddEventListener.call(this, type, listener, options);
}
};
var originalOpen = xhr.open;
xhr.open = function (method, url) {
this.__tmUrl = url;
this.__tmMethod = method;
return originalOpen.apply(this, arguments);
};
// Capture request before any network activity occurs:
var originalSend = xhr.send;
xhr.send = function(data) {
this.__tmData = data;
if (this.onreadystatechange) {
var originalRSC = this.onreadystatechange;
if (originalRSC) {
// "onreadystatechange" exists. Monkey-patch it
this.onreadystatechange = async function() {
//console.log("onreadystatechange", this);
await doPatch(this);
return originalRSC.apply(this, arguments);
};
}
} else if (!this.__tmListenerPatched) {
this.addEventListener("readystatechange", function() {});
}
var originalLoad = this.onload;
if (originalLoad) {
// "onload" exists. Monkey-patch it
this.onload = async function() {
//console.log("onload", this);
await doPatch(this);
return originalLoad.apply(this, arguments);
};
}
return originalSend.apply(this, arguments);
};
}
@icebob
Copy link
Author

icebob commented Apr 7, 2025

Example 1

// @require      https://gist.githubusercontent.com/icebob/b662bd73f28816a1716dab0844814a09/raw/61cb547ad44b895fd8d19a86fe15d4797550928b/helpers.js

(function() {
    'use strict';

    xhrPatcher(XMLHttpRequest.prototype, [
        {
            readyState: 4,
            match: (url, method, data, xhr) => {
                //console.log("Matcher", url, method, data);
                return url.startsWith("/graphql");
            },
            patch: async (response, responseType, xhr) => {
                console.log("Patch", response);
                const str = await response.text();
                //console.log("Str", str);
                const json = JSON.parse(str);
                if (json?.data?.me?.name) {
                    json.data.me.name = "Monkey";
                }

                return JSON.stringify(json);
                //return new Blob([JSON.stringify(json)], { type: 'application/json' });
            }
        }
    ]);
})();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment