// ==UserScript==
// @name 4chan md5 example
// @namespace lig
// @license WTFPL
// @description Example of how to compute the same md5 hashes that 4chan does, allows filters to be carried over to other sites
// @version 1.0.0
// @match *://boards.4chan.org/*/thread/*
// @match *://boards.4channel.org/*/thread/*
// @match *://boards.4chan.org/*/
// @match *://boards.4channel.org/*/
// @match *://8chan.moe/*
// @match *://8chan.se/*
// @run-at document-start
// @grant GM_xmlhttpRequest
// @require https://raw.github.com/emn178/js-md5/master/build/md5.min.js
// ==/UserScript==

const debug_GM_fetch = false

function parseHeaders(responseHeaders) {
    var head = new Headers()
    var pairs = responseHeaders.trim().split('\n')
    pairs.forEach(function(header) {
        var split = header.trim().split(':')
        var key = split.shift().trim()
        var value = split.join(':').trim()
        try {
            head.append(key, value)
        } catch(e) {
            console.error(e);
        }
    })
    return head
}

function GM_fetch(url, options = {}) {
    return new Promise((res, rej) => {
        const host = new URL(url).hostname;
        options.url = url;
        options.method = options.method || 'GET';
        options.responseType = 'text';
        options.headers = options.headers || {};
        options.headers.userAgent
        Object.assign(options.headers, {
            accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
            "accept-encoding": "gzip, deflate, br",
            "accept-language": "en-US,en;q=0.5",
            "alt-used": host,
            "cache-control": "no-cache",
            connection: "keep-alive",
            host: host,
            pragma: "no-cache",
            "sec-fetch-dest": "document",
            "sec-fetch-mode": "navigate",
            "sec-fetch-site": "none",
            "sec-fetch-user": "?1",
            "upgrade-insecure-requests": "1",
            //"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0"
        })
        options.onload = _res => {
            const parsedHeaders = parseHeaders(_res.responseHeaders);
            if(debug_GM_fetch) {
                console.log('parsedHeaders', parsedHeaders);
                console.log('response', _res);
            }
            const response = new Response(_res.response, {
                status: _res.status,
                statusText: _res.statusText,
                headers: parsedHeaders
            })
            Object.defineProperty(response, "url", { value: url });
            res(response);
        };

        options.onerror = function() {
            setTimeout(function() {
                rej(new TypeError('Network request failed'))
            }, 0)
        }

        options.ontimeout = function() {
            setTimeout(function() {
                rej(new TypeError('Network request timed out'))
            }, 0)
        }

        options.onabort = function() {
            setTimeout(function() {
                rej(new DOMException('Aborted', 'AbortError'))
            }, 0)
        }

        GM_xmlhttpRequest(options);
    });
}

function pack(argument, reverse = false) {
    let result = '';
    if(argument.length && typeof argument[0] == 'number')
        for(let i = 0; i < argument.length; i++)
            argument[i] = String.fromCharCode(argument[i]);
        //console.log('argument', argument);
    for(let i = 0; i < argument.length; i += 2) {
        // Always get per 2 bytes...
        let word = argument[i]
        if (((i + 1) >= argument.length) || typeof argument[i + 1] === 'undefined') {
            word += '0'
        } else {
            word += argument[i + 1]
        }
        // The fastest way to reverse?
        if(reverse) {
          word = word[1] + word[0]
        }
        result += String.fromCharCode(parseInt(word, 16))
    }
    return result;
}

unsafeWindow.getMD5 = async url => {
    const buffer = await GM_fetch(url).then(r => r.arrayBuffer());
    let digest = md5(buffer);
    return btoa(pack(digest));
}
Edit Report
Pub: 16 Apr 2025 15:17 UTC
Edit: 16 Apr 2025 15:24 UTC
Views: 9