Greasy Fork is available in English.

ASF AddLicense Helper

Make !addlicense commands clickable.

// ==UserScript==
// @name        ASF AddLicense Helper
// @description Make !addlicense commands clickable.
// @version     0.1.6
// @namespace   Lex@GreasyFork
// @author      Lex
// @match       *://*.steamgifts.com/discussion/*
// @match       *://*.reddit.com/r/FreeGamesOnSteam/comments/*
// @match       *://*.reddit.com/r/FreeGameFindings/comments/*
// @match       *://steam.madjoki.com/apps/free
// @grant       GM_xmlhttpRequest
// @grant       GM_getValue
// @grant       GM_setValue
// @connect     127.0.0.1
// ==/UserScript==

(function() {
    /*  Configure Custom Server or Password  */
    let ASF_SERVER = ""; // e.g. 127.0.0.1:1242
    let ASF_PASSWORD = ""; // e.g. 123456
    /*  End Configuration  */
    
    // Promise wrapper for GM_xmlhttpRequest
    const Request = details => new Promise((resolve, reject) => {
        details.onerror = details.ontimeout = reject;
        details.onload = resolve;
        GM_xmlhttpRequest(details);
    });
    
    async function sendASF(command) {
        if (Array.isArray(command))
            command = command.filter(e => e !== undefined).join(" ");
        const ASF_URL = "http://" + ASF_SERVER + "/Api/Command";
        return await Request({
            method: "POST",
            url: ASF_URL,
            headers: {
                "Content-Type": "application/json",
                accept: "application/json",
                Authentication: ASF_PASSWORD,
            },
            data: JSON.stringify({
                COMMAND: command,
            }),
        });
    }

    async function addLicenseClick(event) {
        const el = event.target;
        el.style.textDecoration = "";
        el.style.cursor = "";
        el.style.color = "inherit";
        el.onclick = undefined;
        console.log("Executing " + el.dataset.asfcommand);
        const resp = await sendASF(el.dataset.asfcommand);
        const indicator = document.createElement("span");
        if (resp.responseText.includes("Status: OK")) {
            indicator.innerText = "✔️";
            indicator.title = "Successfully added";
        } else {
            indicator.innerText = "❗";
            indicator.title = "Error adding license. See console for more info";
            console.log(resp.responseText);
        }
        el.after(indicator);
        return false;
    }
  
    // https://stackoverflow.com/questions/10730309/find-all-text-nodes-in-html-page
    function textNodesUnder(el){
      let n, a=[], walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false);
      while(n=walk.nextNode()) a.push(n);
      return a;
    }
    
    function findCommands(ps) {
        const addreg = /\!addlicense\s+ASF\s+([as]\/\d+|sub\/\d+|app\/\d+|\d+)(?:,\s*([as]\/\d+|sub\/\d+|app\/\d+|\d+))*/gi;
        ps.forEach(p => {
            if (!p.innerText.includes("!addlicense")) return;
            textNodesUnder(p).forEach(node => {
                const ms = node.textContent.matchAll(addreg);
                for (const m of ms) {
                    // tail contains any remaining text after the ASF command
                    const tail = node.textContent.substring(m.index + m[0].length);
                    // truncate the existing text node at the start of the ASF command
                    node.textContent = node.textContent.substring(0, m.index);
                    // full ASF command
                    const cmd = m[0].trim();
                    const a = document.createElement("span");
                    a.style = "color:#4B72D4; background-color:inherit; cursor: pointer; text-decoration: underline; border:0;margin:0;padding:0;display:inline;text-align:inherit;"
                    a.dataset.asfcommand = cmd;
                    a.innerText = cmd;
                    a.onclick = addLicenseClick;
                    node.after(a);
                    // if text was present after the ASF command, append it as a new text node
                    if (tail !== "") {
                        a.after(document.createTextNode(tail));
                    }
                }
            })
        });
    }
  
    // Handles loading and storing of custom settings
    function CustomSettings() {
        if (ASF_PASSWORD !== "") {
            GM_setValue("asf_password", ASF_PASSWORD);
        } else {
            ASF_PASSWORD = GM_getValue("asf_password", "");
        }
        if (ASF_SERVER !== "") {
            GM_setValue("asf_server", ASF_SERVER);
        } else {
            ASF_SERVER = GM_getValue("asf_server", "127.0.0.1:1242");
        }
    }
  
    CustomSettings();

    if (/steamgifts\.com/.test(location.href)) {
        const ps = document.querySelectorAll(".markdown > *");
        findCommands(ps);
    }
    if (/reddit\.com/.test(location.href)) {
        // Reddit does some late styling that will overwrite our changes, so we have to load even later
        setTimeout(function(){
            const ps = document.querySelectorAll("[class*=RichTextJSON-root]");
            findCommands(ps);
        }, 2000);
    }
    if (/steam\.madjoki\.com\/apps\/free/.test(location.href)) {
        const cmd = document.querySelector("[placeholder='!addlicense']").value;
        const add_asf_div = document.getElementById("add_asf");
        const a = document.createElement("span");
        a.style = "color:#4B72D4; background-color:inherit; cursor: pointer; text-decoration: underline; border:0;margin:0;padding:0;display:inline;text-align:inherit;";
        a.dataset.asfcommand = cmd;
        a.innerText = cmd;
        a.onclick = addLicenseClick;
        add_asf_div.append(a);
    }

})();