Highlight RMS supporters

Highlights those who have signed the RMS Support letter

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name            Highlight RMS supporters
// @description     Highlights those who have signed the RMS Support letter
// @include         https://github.com/*
// @author          stick
// @version         2
// @homepageURL     https://github.com/sticks-stuff/highlight-RMS-supporters
// @namespace       https://greasyfork.org/users/710818
// @grant           GM.xmlHttpRequest
// @connect         api.github.com
// @connect         codeload.github.com
// @require         https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js
// @require         https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.0.0/js-yaml.min.js
// ==/UserScript==
const RMS_LETTER_DOWNLOAD = "https://api.github.com/repos/rms-support-letter/rms-support-letter.github.io/zipball/";
const UPDATE_INTERVAL = 3600;

async function iterateDirectory(dir, cb) {
    let filename, relativePath, file;
    let promises = [];
    for (filename in dir.files) {
        if (!dir.files.hasOwnProperty(filename)) {
            continue;
        }

        file = dir.files[filename];
        relativePath = filename.slice(dir.root.length, filename.length);


        if (relativePath && filename.slice(0, dir.root.length) === dir.root) {
            promises.push(cb(file));
        }
    }
    await Promise.all(promises);
}

function linkToGithubUsername(rawLink) {
    // if the link is a link to a GitHub user, return the username in
    // URI-escaped form. otherwise, return `null`.
    if (!(typeof rawLink === 'string' || rawLink instanceof String)) {
        // the link is not a string
        return null;
    }

    // truncate to avoid maliciously excessively long URLs
    try {
        var link = new URL(rawLink.slice(0, 100));
    } catch (e) {
        return null;
    }

    if (!(link.protocol == 'http:' || link.protocol == 'https:')) {
        // link is not HTTP(S)
        return null;
    }
    if (!(link.host == 'github.com' || link.host == 'www.github.com')) {
        // link is not github.com
        return null;
    }

    // we `.slice(1)` to remove the leading backslash
    let pathParts = link.pathname.slice(1).split('/');
    if (pathParts.length != 1) {
        // link has the wrong number of path parts
        return null;
    }

    // url.parse automatically performs URI escaping
    return pathParts[0];
}

function getUpdates(db) {
    // We need to use GM_xmlhttpRequest here because our request is cross-origin.
    GM.xmlHttpRequest({
        url: RMS_LETTER_DOWNLOAD,
        method: "GET",
        nocache: true, // This request should never be cached
        responseType: "arraybuffer",
        onload: async (data) => {
            let zip = await JSZip.loadAsync(data.response);
            // The name of the directory that the signature folder is in will vary depending on the signature of the github repo (which changes every time it updates)
            // We can ensure we always get the right directory by using a regex.
            // This makes the assumption that the signatures are under _data/signed, so if they changed this location in the future, we will need to change it here too.
            let signature_folder = zip.folder(zip.folder(/^rms-support-letter.*_data\/signed\/$/g)[0].name);
            await iterateDirectory(signature_folder, async (file) => {
                let content = await file.async("string");
                let transaction = db.transaction(["signatures"], "readwrite");
                let signatures = transaction.objectStore("signatures");
                let username = linkToGithubUsername(jsyaml.load(content).link);
                if (username === null) return;
                signatures.put(0, username.toLowerCase());
            })
            window.localStorage.setItem("last_updated", new Date().getTime() / 1000);
            highlightNames(db);
        },
        onerror: (e) => {
            // TODO: We should notify the user of the error
            console.error(e);
        }
    });
}

function highlightNames(db) {
    let links = document.getElementsByTagName("a");
    for (const element of links) {
        let text = element.innerHTML.toLowerCase();
        let signatures = db.transaction(["signatures"], "readwrite").objectStore("signatures");
        // onsuccess will only fire when the key exists
        signatures.getKey(text).onsuccess = (event) => {
            if (event.target.result !== text) return;
            element.style.backgroundColor = "crimson";
        }
    };
}

var request = window.indexedDB.open("RMSSignatures", 3);
request.onerror = function(event) {
    console.error("Database error: " + event.target.errorCode);
};
request.onupgradeneeded = function(event) {
    let db = event.target.result;
    db.createObjectStore("signatures");
};
request.onsuccess = function(event) {
    let db = event.target.result;
    let transaction = db.transaction(["signatures"], "readwrite");
    let signatures = transaction.objectStore("signatures");

    if (window.localStorage.getItem("last_updated") === null) {
        window.localStorage.setItem("last_updated", 0);
    }
    if (new Date().getTime() / 1000 >= parseFloat(window.localStorage.getItem("last_updated")) + UPDATE_INTERVAL) {
        signatures.clear();
        try {
        	return getUpdates(db);
        }
        catch (e) {
           console.error(e);
        }
    }

    highlightNames(db);
    let observer = new MutationObserver(() => highlightNames(db));
    observer.observe(document.body, {
        childList: true
    });
}