Tripcolor

Colorize tripcodes, the way YOU want it

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name            Tripcolor
// @name:de         Tripcolor
// @namespace       http://www.4chan.org/
// @version         1.1.0
// @description     Colorize tripcodes, the way YOU want it
// @description:de  Färbe Tripcodes, wie es DIR gefällt
// @author          Wolvan
// @match           *://boards.4chan.org/*
// @grant           GM_getValue
// @grant           GM_setValue
// @icon            http://i.imgur.com/S2VvpO3.png
// ==/UserScript==
/* jshint -W097 */

(function () {
    'use strict';

    const US_META = {
        name: "Tripcolorizer",
        author: "Wolvan",
        version: "1.1.0",
        description: "Colorize Tripcodes"
    };

    const MENU_HTML = '<fieldset id="TripcolorizerOptions"><legend>Settings</legend>%SETTINGS</fieldset><div id="ButtonsDiv"><fieldset id="TripcolorizerTriplist"scrollable="vertical"><legend>Tripcodes</legend><div class="TripcolorizerScrolldiv">%TRIPLIST</div></fieldset><button id="TripcolorizerSaveButton">Save!</button><button id="TripcolorizerCancelButton">Cancel</button></div>';
    const MENU_CSS = '#TripcolorizerMenu{border-width:1px;border-color:#292929;border-style:solid;background-color:#222;color:#BBB;position:fixed;z-index:1002;top:50%;left:50%;transform:translate(-50%,-50%)}#TripcolorizerGlass{background-color:rgba(21,21,21,.82);width:100%;height:100%;z-index:1001;top:0;left:0;position:fixed}#TripcolorizerMenu label{display:inherit}#TripcolorizerMenu .TripcolorizerScrolldiv{max-height:500px;overflow-y:auto}#TripcolorizerMenu #ButtonsDiv{text-align:center}#TripcolorizerMenu input[type=checkbox]{display:inline-block!important}#TripcolorizerMenu .riceCheck{display:none}#TripcolorizerMenuButton{cursor:pointer}';

    Object.filter = function (obj, predicate) {
        var result = {},
            key;
        for (key in obj) {
            if (obj.hasOwnProperty(key) && predicate(obj[key])) {
                result[key] = obj[key];
            }
        }
        return result;
    };
    const utils = {
        append: {
            script: function (scriptCode, name = "TripcolorizerJS") {
                var newScript = document.createElement("script");
                newScript.id = name;
                newScript.innerHTML = scriptCode;
                document.head.appendChild(newScript);
            },
            css: function (style, name = "TripcolorizerStyle") {
                var newStyle = document.createElement("style");
                newStyle.id = name;
                newStyle.innerHTML = style;
                document.head.appendChild(newStyle);
            },
            div: function (htmlCode, name = "TripcolorizerDiv") {
                var newChild = document.createElement("div");
                newChild.id = name;
                newChild.innerHTML = htmlCode;
                document.body.appendChild(newChild);
            }
        },
        randomFrom: {
            object: function (obj) {
                var keys = Object.keys(obj);
                var randomProp = keys[Math.floor(Math.random() * keys.length)];
                return {
                    key: randomProp,
                    value: obj[randomProp]
                };
            },
            array: function (arr) {
                return arr[Math.floor(Math.random * arr.length)];
            }
        }
    };

    const defaultConfig = {
        tripcodes: {},
        settings: {
            colorNewCodes: {
                type: "checkbox",
                defaultValue: true,
                value: true,
                text: "Color unknown tripcodes",
                hint: "Automatically add new Tripcodes to the list"
            },
            colorNames: {
                type: "checkbox",
                defaultValue: true,
                value: true,
                text: "Color names",
                hint: "Color the names next to the tripcode as well"
            },
            autoColorOnUpdate: {
                type: "checkbox",
                defaultValue: true,
                value: true,
                text: "Color new posts automatically",
                hint: "When auto-polling for new replies, the new tripcode gets automatically colored"
            },
            defaultColorSystem: {
                type: "dropdown",
                defaultValue: "hex",
                value: "hex",
                values: [{
                        value: "rgb",
                        text: "RGB"
                    },
                    {
                        value: "hsl",
                        text: "HSL"
                    },
                    {
                        value: "hsv",
                        text: "HSV"
                    },
                    {
                        value: "hex",
                        text: "Hex"
                    }
                ],
                text: "Default Color Representation",
                hint: "Which color representation should new trip values use"
            }
        }
    };

    var config;
    var tmpConfig;

    var mutObs = new MutationObserver(function (mutations) {
        for (let mutation of mutations) {
            for (let node of mutation.addedNodes) {
                if (node && node.classList && node.classList.contains("postContainer")) {
                    colorTrips();
                }
            }
        }
    });

    function loadConfig() {
        try {
            var conf = GM_getValue("tripcolorizerOptions", JSON.stringify(defaultConfig));
            config = {
                tripcodes: {},
                settings: defaultConfig.settings
            };
            var parsedConfig = JSON.parse(conf);
            if (parsedConfig.tripcodes) {
                for (var trip in parsedConfig.tripcodes) {
                    config.tripcodes[trip] = parsedConfig.tripcodes[trip];
                }
            }
            if (parsedConfig.settings) {
                for (var setting in parsedConfig.settings) {
                    if (config.settings[setting]) config.settings[setting].value = parsedConfig.settings[setting].value;
                }
            }

            console.log(`[${US_META.name}]Config loaded`);
        } catch (error) {
            config = Object.assign({}, defaultConfig);
            console.log(`[${US_META.name}]Failed to load config. Using default. Error: ${error}`);
            return;
        }
    }

    // Credit for this hashing algorithm goes to http://userscripts-mirror.org/scripts/review/149083
    function worstHashAlgorithm(str) {
        var sum = 0;
        for (var ind = 0; ind < str.length; ind++) {
            sum += str.charCodeAt(ind);
        }
        return sum;
    }

    function colorTrips() {
        function hsv2hsl(hsvstr) {
            var [hue, sat, val] = hsvstr.match(/[\.\d]+%?/g);
            if (sat.indexOf("%") !== -1) {
                sat = parseFloat(sat) / 100;
            }
            if (val.indexOf("%") !== -1) {
                val = parseFloat(val) / 100;
            }
            var x = `hsl(${
                hue
            }, ${
                Math.floor((sat * val / ((hue = (2 - sat) * val) < 1 ? hue : 2 - hue)) * 100)
            }%, ${
                Math.floor((hue / 2) * 100)
            }%)`;
            return x;
        }

        function intToHexPad(int) {
            var hexnum = int.toString(16).toUpperCase();
            return (hexnum.length === 1 ? "0" + hexnum : hexnum);
        }
        var tripNodes = document.getElementsByClassName("postertrip");
        for (var tripNode of tripNodes) {
            let trip = tripNode.innerHTML;
            if (!config.tripcodes[trip] && config.settings.colorNewCodes.value) {
                let strippedTrip = trip.replace(/!/g, "");
                let baseValues = [
                    worstHashAlgorithm(strippedTrip.substr(0, 4)) % 255,
                    worstHashAlgorithm(strippedTrip.substr(4, 4)) % 255,
                    worstHashAlgorithm(strippedTrip.substr(8)) % 255
                ];
                switch (config.settings.defaultColorSystem.value) {
                    case "hsl":
                    case "hsv":
                        let hue = Math.floor((baseValues[0] / 255) * 360);
                        let sat = baseValues[1] / 255;
                        let light = baseValues[2] / 255;
                        config.tripcodes[trip] = `hsl(${hue}, ${Math.floor(sat * 100)}%, ${Math.floor(light * 100)}%)`;
                        if (config.settings.defaultColorSystem.value === "hsl") break;
                        sat *= light < 0.5 ? light : 1 - light;
                        config.tripcodes[trip] = `hsv(${
                            hue
                        }, ${
                            Math.floor((2 * sat / (light + sat)) * 100)
                        }%, ${
                            Math.floor((light + sat) * 100)
                        }%)`;
                        break;
                    case "hex":
                        config.tripcodes[trip] = `#${
                            intToHexPad(baseValues[0])
                        }${
                            intToHexPad(baseValues[1])
                        }${
                            intToHexPad(baseValues[2])
                        }`;
                        break;
                    case "rgb": // jshint ignore:line
                    default:
                        config.tripcodes[trip] = `rgb(${
                            baseValues[0]
                        }, ${
                            baseValues[1]
                        }, ${
                            baseValues[2]
                        })`;
                        break;
                }
            }
            if (config.tripcodes[trip]) {
                let targets;
                if (config.settings.colorNames.value) {
                    targets = tripNode.parentNode.children;
                } else {
                    targets = [tripNode];
                }
                for (let target of targets) {
                    if (target.tagName.toLowerCase() === "span") {
                        target.classList.add("TripcolorizerStyled");
                        target.style.setProperty("color", config.tripcodes[trip].match(/hsv/gi) ? hsv2hsl(config.tripcodes[trip]) : config.tripcodes[trip], "important");
                    }
                }
            }
        }
    }

    function uncolorTrips() {
        var allStyled = document.getElementsByClassName("TripcolorizerStyled");
        while (allStyled.length > 0) {
            allStyled[0].style.removeProperty("color");
            allStyled[0].classList.remove("TripcolorizerStyled");
        }
    }

    function showMenu() {
        tmpConfig = {};
        tmpConfig.settings = Object.assign({}, config.settings);
        tmpConfig.tripcodes = Object.assign({}, config.tripcodes);

        function closeMenu() {
            document.getElementById("TripcolorizerGlass").remove();
            document.getElementById("TripcolorizerMenu").remove();
        }

        function showGlass() {
            var name = "TripcolorizerGlass";
            var oldelem = document.getElementById(name);
            if (oldelem) oldelem.remove();
            utils.append.div("", name);
        }

        const STRING_TEMPLATES = {
            checkbox: '<td><input class="TripcolorizerSetting"title="%SETTINGSHINT"name="%SETTINGNAME"type="checkbox"id="Tripcolorizer_%SETTINGNAME"%SETTINGSET></td><td><label for="Tripcolorizer_%SETTINGNAME"title="%SETTINGSHINT">%SETTINGTEXT</label></td>',
            dropdown: '<td><select class="TripcolorizerSetting"title="%SETTINGSHINT"name="%SETTINGNAME" id="Tripcolorizer_%SETTINGNAME">%OPTIONS</select></td><td><label for="Tripcolorizer_%SETTINGNAME"title="%SETTINGSHINT">%SETTINGTEXT</label></td>',


            tripcodes: `<tr class="TripcolorizerTrip"><td><input type="text"class="TripcolorizerTripcode"value="%TRIPCODE"placeholder="%TRIPCODE"></td><td><input type="text"class="TripcolorizerTripcolor"value="%TRIPCOLOR"placeholder="%TRIPCOLOR"></td><td><button class="TripcolorizerDeletThis"data-trip="%TRIPCODE">Delete</button></td></tr>`
        };

        function getTriplist() {
            var triplist = '';
            for (var trip in tmpConfig.tripcodes) {
                triplist += STRING_TEMPLATES.tripcodes.replace(/%TRIPCODEEXAMPLE/g, "Tripcode (+! or !!)")
                    .replace(/%TRIPCOLOREXAMPLE/g, "CSS Style for Trip")
                    .replace(/%TRIPCODE/g, trip)
                    .replace(/%TRIPCOLOR/g, tmpConfig.tripcodes[trip]);
            }
            return triplist;
        }

        function addTrip() {
            var trip = document.getElementById("TripcolorizerNewTrip").value;
            var color = document.getElementById("TripcolorizerNewTripColor").value;

            if (trip && color) {
                tmpConfig.tripcodes[trip] = color;
                document.getElementById("TripcolorizerNewTrip").value = "";
                document.getElementById("TripcolorizerNewTripColor").value = "";
                document.getElementById("TripcolorizerTriplistTable").innerHTML = getTriplist();
                bindTripDeleteButtons();
            }
        }

        function bindTripDeleteButtons() {
            var deleteButtons = document.getElementsByClassName("TripcolorizerDeletThis");
            for (var button of deleteButtons) {
                button.addEventListener("click", function () {
                    delete tmpConfig.tripcodes[this.dataset.trip];
                    document.getElementById("TripcolorizerTriplistTable").innerHTML = getTriplist();
                    bindTripDeleteButtons();
                });
            }
        }

        function showMenu() {

            var name = "TripcolorizerMenu";
            var oldelem = document.getElementById(name);
            if (oldelem) oldelem.remove();
            var settingsHTML = '<table id="TripcolorizerSettingsTable">';

            var settingControl = "";

            var setting;
            for (var settingName in tmpConfig.settings) {
                setting = tmpConfig.settings[settingName];
                switch (setting.type) {
                    case "checkbox":
                        settingControl = STRING_TEMPLATES.checkbox.replace(/%SETTINGNAME/g, settingName)
                            .replace(/%SETTINGTEXT/g, setting.text)
                            .replace(/%SETTINGSHINT/g, setting.hint || "")
                            .replace(/%SETTINGSET/g, (setting.value ? 'checked="checked"' : ""));
                        break;
                    case "dropdown":
                        settingControl = STRING_TEMPLATES.dropdown.replace(/%SETTINGNAME/g, settingName)
                            .replace(/%SETTINGTEXT/g, setting.text)
                            .replace(/%SETTINGSHINT/g, setting.hint || "")
                            .replace(/%OPTIONS/g, setting.values.map(function (item) {
                                return `<option value="${item.value}"${item.value === setting.value ? "selected" : ""}>${item.text}</option>`;
                            }));
                        break;
                    default:
                        break;
                }
                settingsHTML += "<tr>" + settingControl + "</tr>";
            }

            settingsHTML += "</table>";

            var triplist = '<table id="TripcolorizerTriplistTable">' + getTriplist();
            triplist += "</table>";
            triplist += '<table id="TripcolorizerTriplistAddTable">';
            triplist += '<tr><td><input type="text"name="NewTrip"id="TripcolorizerNewTrip"placeholder="%TRIPCODEEXAMPLE"></td><td><input type="text"name="NewTripColor"id="TripcolorizerNewTripColor"placeholder="%TRIPCOLOREXAMPLE"><td><button id="TripcolorizerAddTrip">Add</button></td></td></tr>';
            triplist += "</table>";

            utils.append.div(MENU_HTML.replace(/%SETTINGS/g, settingsHTML)
                .replace(/%TRIPLIST/g, triplist)
                .replace(/%TRIPCODEEXAMPLE/g, "Tripcode (+! or !!)")
                .replace(/%TRIPCOLOREXAMPLE/g, "CSS Style for trip"), name);
        }
        showGlass();
        showMenu();

        document.getElementById("TripcolorizerAddTrip").addEventListener("click", addTrip);
        bindTripDeleteButtons();

        document.getElementById("TripcolorizerGlass").addEventListener("click", closeMenu);
        document.getElementById("TripcolorizerCancelButton").addEventListener("click", closeMenu);
        document.getElementById("TripcolorizerSaveButton").addEventListener("click", function () {
            var settingElements = document.getElementsByClassName("TripcolorizerSetting");
            for (var setting of settingElements) {
                switch (setting.tagName.toLowerCase()) {
                    case "input":
                        switch (setting.type.toLowerCase()) {
                            case "checkbox":
                                tmpConfig.settings[setting.name].value = setting.checked;
                                break;
                            default:
                                break;
                        }
                        break;
                    case "select":
                        tmpConfig.settings[setting.name].value = setting.value;
                        break;
                    default:
                        break;
                }
            }
            var tripcodeListElements = document.getElementsByClassName("TripcolorizerTrip");
            for (var tripRow of tripcodeListElements) {
                let children = tripRow.children;
                let tripcode;
                let tripcolor;
                for (let child of children) {
                    child = child.children[0];
                    if (child.classList.contains("TripcolorizerTripcode")) {
                        if (child.value) tripcode = child.value;
                    } else if (child.classList.contains("TripcolorizerTripcolor")) {
                        if (child.value) tripcolor = child.value;
                    }
                }
                if (tripcode && tripcolor) tmpConfig.tripcodes[tripcode] = tripcolor;
            }
            config = Object.assign({}, tmpConfig);
            GM_setValue("tripcolorizerOptions", JSON.stringify(config));
            console.log(`[${US_META.name}]Saved Settings to Tripcolorizer`);
            console.log(`[${US_META.name}]Re-coloring Trips`);
            uncolorTrips();
            colorTrips();
            console.log(`[${US_META.name}]${config.settings.autoColorOnUpdate.value ? "Enabling" : "Disabling"} update observer`);
            if (config.settings.autoColorOnUpdate.value) {
                mutObs.observe(document.getElementsByClassName("thread")[0], {
                    childList: true,
                    subtree: true
                });
            } else {
                mutObs.disconnect();
            }
            closeMenu();
        });
    }

    function keyDownHandler(event) {
        if (event.ctrlKey && event.which === 76) {
            showMenu();
        }
    }

    function init() {
        console.log(`[${US_META.name}]Initializing ${US_META.name} v${US_META.version} by ${US_META.author}`);
        console.log(`[${US_META.name}]Injecting CSS`);
        utils.append.css(MENU_CSS, "TripcolorizerMenuStyling");
        console.log(`[${US_META.name}]Loading config`);
        loadConfig();
        console.log(`[${US_META.name}]Hooking Keyboard Event`);
        document.addEventListener('keydown', keyDownHandler);
        console.log(`[${US_META.name}]Coloring ${!config.settings.colorNewCodes ? "known " : ""}tripcodes`);
        colorTrips();
        if (config.settings.autoColorOnUpdate.value) {
            console.log(`[${US_META.name}]Enabling update observer`);
            mutObs.observe(document.getElementsByClassName("thread")[0], {
                childList: true,
                subtree: true
            });
        }
    }

    init();
})();