trovonicknamecolorizer

colorize nicknames in Trovo chat

当前为 2021-10-29 提交的版本,查看 最新版本

// ==UserScript==
// @name         trovonicknamecolorizer
// @namespace    http://tampermonkey.net/
// @version      0.2.6
// @description  colorize nicknames in Trovo chat
// @author       yyko
// @match        https://trovo.live/*
// @icon         https://www.google.com/s2/favicons?domain=trovo.live
// @run-at       document-end
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const maxAttemptsCount = 20;
    const attmeptDelay = 2000;

    const colorMap = new Map([
      ["red","#FF0000"],//красный
      ["blue","#0000FF"],//синий
      ["green","#008000"],//зелёный
      ["firebrick","#B22222"],//кирпичный
      ["coral","#FF7F50"],//коралловый
      ["yellowgreen","#9ACD32"],//лайм
      ["orangered","#FF4500"],//красно-оранжевый
      ["seagreen","#2E8B57"],//морская волна
      ["goldenrod","#DAA520"],//красное золото
      ["chocolate","#D2691E"],//шоколадный
      ["cadetblue","#5F9EA0"],//серо-голубой
      ["dodgerblue","#1E90FF"],//васильковый
      ["hotpink","#FF69B4"],//ярко-розовый
      ["blueviolet","#8A2BE2"],//индиго
      ["springgreen","#00FF7F"],//салатовый
    ]);

    const colorNames = Array.from(colorMap.keys());

    // Palette icon made by Google (https://www.flaticon.com/authors/google)
    const colorizerSvg = '<svg aria-hidden="true" class="svg-icon btn-icon size24" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="24" height="24"><path d="M12 1.5C6.202 1.5 1.5 6.202 1.5 12S6.202 22.5 12 22.5a1.748 1.748 0 0 0 1.295-2.923 1.733 1.733 0 0 1-.437-1.16c0-.969.781-1.75 1.75-1.75h2.059a5.835 5.835 0 0 0 5.833-5.834C22.5 5.677 17.798 1.5 12 1.5zM5.583 12c-.968 0-1.75-.782-1.75-1.75s.782-1.75 1.75-1.75c.969 0 1.75.782 1.75 1.75S6.552 12 5.583 12zm3.5-4.667c-.968 0-1.75-.781-1.75-1.75 0-.968.782-1.75 1.75-1.75.969 0 1.75.782 1.75 1.75 0 .969-.781 1.75-1.75 1.75zm5.834 0c-.969 0-1.75-.781-1.75-1.75 0-.968.781-1.75 1.75-1.75.968 0 1.75.782 1.75 1.75 0 .969-.782 1.75-1.75 1.75zm3.5 4.667c-.969 0-1.75-.782-1.75-1.75s.781-1.75 1.75-1.75c.968 0 1.75.782 1.75 1.75S19.385 12 18.417 12z" style="stroke-width:.0546871"/></svg>';

    const classPrefix='clrz_';

    // local storage tools

    function mapToStr(map){
        return JSON.stringify(Object.fromEntries(map));
    }

    function strToMap(str){
        return new Map(Object.entries(JSON.parse(str)));
    }

    function loadData(entryName='users'){
        let data = localStorage.getItem(entryName);
        if(data){
            return strToMap(data);
        }else{
            return null;
        }
    }

    function saveData(data,entryName='users'){
        localStorage.setItem(entryName,mapToStr(data));
    }

    // --

    // users tools

    let users = loadData();
    if(users){
        if(localStorage.getItem('tncts_localUsers')){
            localStorage.removeItem('tncts_localUsers');
        }
    }else{
        users = new Map();
    }

    let settings = loadData('colorizerSettings');
    if(!settings){
        settings = new Map([['baseLock',false],['interfaceState',true]]);
    }

    function addUser(name){
        let userColor = getRandomColorName();
        createUserClass(name,userColor);
        changeUserColor(name,userColor);
        return userColor;
    }

    function delUser(name){
        users.delete(name);
        saveData(users);
    }

    // --

    // color tools

    function getRandomColorName(){
        return colorNames[Math.round(Math.random()*colorNames.length)];
    }

    function getUserColor(name){
        return users.get(name);
    }

    function changeUserColor(name,colorName){
        let ccr=checkColor(colorName);
        if(ccr){
            let cv;
            if(ccr==1){
                cv=colorMap.get(colorName.toLocaleLowerCase());
            }else if(ccr==2){
                cv=colorName;
            }
            changeUserClass(name,cv);
            users.set(name,cv);
            saveData(users);
        }
    }

    function checkColor(colorName){
        if(colorMap.has(colorName)){
            return 1;
        }else if(colorName.match(/^#[0-9a-f]{3}$|^#[0-9a-f]{4}$|^#[0-9a-f]{6}$|^#[0-9a-f]{8}$/)){
            return 2;
        }else{
            return false;
        }
    }

    // --

    // stylesheet

    let ss;
    let bayav=new Map();
    let ssav=false;

    function initSS(shappend=true){
        ss=ss||document.createElement('style');
        if(shappend)
            document.head.appendChild(ss);
        ssav=true;
        return ss;
    }

    function disableSS(){
        ss.remove();
        ssav=false;
    }

    function createUserClass(name,color){
        ss.innerHTML=ss.innerHTML+`.${classPrefix}${name}{color:${color} !important;}`;
        bayav.set(name,color);
    }

    function changeUserClass(name,newcolor){
        ss.innerHTML.replace(`.${classPrefix}${name}{color:${getUserColor(name)} !important;}`,"$`"+`.${classPrefix}${name}{color:${newcolor} !important;}`+"$'");
    }

    function getUserClassName(name){
        return `${classPrefix}${name}`;
    }

    // --

    // iterface

    function getSettingsButtonElement(){
        let featureBox=document.getElementsByClassName('input-feature-box')[0];
        if(featureBox){
            return featureBox.getElementsByClassName('cat-button normal icon')[0];
        }
    }

    let cbtn;
    function createColorizerButton(){
        let sbe=getSettingsButtonElement();
        if(sbe){
            cbtn=sbe.cloneNode(false);
            cbtn.setAttribute('data-enclave','colorizer');
            cbtn.innerHTML=colorizerSvg;
            cbtn.color='white';
            if(settings.get('interfaceState')){
                cbtn.classList.add('active');
            }
            getSettingsButtonElement().after(cbtn);
            cbtn.addEventListener('click',toggleInterface);
        }
    }

    let enabled=false;
    function toggleInterface(){
        enabled=settings.get('interfaceState');
        if(enabled){
            // on disable interface
            cbtn.classList.remove('active');
            disableSS();
        }else{
            // on enable interface
            cbtn.classList.add('active');
            initSS();
        }
        settings.set('interfaceState',!enabled);

        //aka migration
        if(settings.has('baseLock'))
            settings.delete('baseLock');
        //--

        saveData(settings,'colorizerSettings');
    }

    // --

    // ***, ****** ******* *****
    let guiShouldBeCreated=true;
    // *--
    function onmessage(mutations,observer){
        if(guiShouldBeCreated){
            guiShouldBeCreated=false;
            createColorizerButton();
        }
        for(let mutation of mutations){
            for(let msgel of mutation.addedNodes){
                let nameel=msgel.getElementsByClassName('nickname-box')[0];
                let name;
                if(nameel){
                    name=nameel.getElementsByClassName('nick-name')[0].title;
                    if(!users.has(name))
                        addUser(name);
                    else if(!bayav.has(name))
                        createUserClass(name,getUserColor(name));

                    // команда на изменение цвета
                    let msgtextel=msgel.getElementsByClassName('content')[0];
                    if(msgtextel){
                        let msgtext=msgtextel.innerText;
                        let res=msgtext.match(/^!color (.*)/);
                        if(res){
                            let colorValue=res[1];
                            if(checkColor(colorValue)){
                                changeUserColor(name,res[1]);
                            }else{
                                console.warn('color is not available');
                            }
                        }
                    }

                    // применение цвета к новому сообщению
                    nameel.classList.add(getUserClassName(name));
                }
            }
        }
    }

    // initialization

    let launched=false;
    let chatElement;
    let chatObserver;
    const obsConfig={childList:true};

    function setChatElement(){
        chatElement = document.getElementsByClassName('chat-list')[0];
        return chatElement;
    }

    let attemptsLeft = maxAttemptsCount;
    let attemptsTimer;

    function initChat(){
        if(setChatElement()){
            chatObserver = new MutationObserver(onmessage);
            chatObserver.observe(chatElement,obsConfig);

            console.warn('started');
            launched=true;
        }else{
            if(attemptsLeft>0){
                console.warn('attempts left to start: ',attemptsLeft);
                attemptsLeft--;
                attemptsTimer=setTimeout(initChat,attmeptDelay);
            }else{
                console.warn('cant find chat element');
            }
        }
    }

    function init(){
        initChat();
        if(launched){
            initSS(enabled);

            let baseElement = document.getElementsByClassName('base-container')[0];
            if(baseElement){
                let baseObserver = new MutationObserver(restart);
                baseObserver.observe(baseElement,obsConfig);
            }else{
                console.warn('cant find base-container');
            }
        }
    }

    function restart(){
        console.warn('restarted');
        attemptsLeft=maxAttemptsCount;
        if(launched){
            chatObserver.disconnect();
            if(cbtn){
                cbtn.remove();
                guiShouldBeCreated=true;
            }
            launched=false;
            initChat();
        }
    }

    init();

    // --
})();