// ==UserScript== // @name Tripcolor // @name:de Tripcolor // @namespace http://www.4chan.org/ // @version 1.0.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 // @downloadURL none // ==/UserScript== /* jshint -W097 */ (function () { 'use strict'; const US_META = { name: "Tripcolorizer", author: "Wolvan", version: "1.0.0", description: "Colorize Tripcodes" }; const MENU_HTML = '
Settings%SETTINGS
Tripcodes
%TRIPLIST
'; 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: "rgb", value: "rgb", values: [{ value: "rgb", text: "RGB" }, { value: "hsl", text: "HSL" }, { value: "hsv", text: "HSV" } ], 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; } } 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); return `hsl(${ hue }, ${ sat * val / ((hue = (2 - sat) * val) < 1 ? hue : 2 - hue) }, ${ hue / 2 })`; } 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 "rgb": 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: '', dropdown: '', tripcodes: `` }; 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 = ''; 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 ``; })); break; default: break; } settingsHTML += "" + settingControl + ""; } settingsHTML += "
"; var triplist = '' + getTriplist(); triplist += "
"; triplist += ''; triplist += ''; triplist += "
"; 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(); })();