Tripcolor

Colorize tripcodes, the way YOU want it

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
})();