trovonicknamecolorizer

colorize nicknames in Trovo chat

// ==UserScript==
// @name         trovonicknamecolorizer
// @namespace    http://tampermonkey.net/
// @version      0.2.7
// @description  colorize nicknames in Trovo chat
// @author       yyko
// @match        https://trovo.live/*
// @icon         https://icons.duckduckgo.com/ip2/trovo.live.ico
// @run-at       document-end
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    const maxAttemptsCount=10;
    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 obsConfig={childList:true};
    const classPrefix='clrz_';

    const cpCSSbase='.cp_list,.cp_color{display: inline-block;line-height: 0px;}.cp_list{max-width: 125px;}.cp_color{margin: 0px;width: 25px;height: 25px;transition: all 0.2s ease;}.cp_color_active{box-shadow: 0 0 0 5px white inset;}';

    const onlyChat=!!document.location.href.match('https://trovo.live/chat/.*');

    // 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 sessionUsers=new Map();
    let users=loadData();
    if(users){
        if(localStorage.getItem('tncts_localUsers')){
            localStorage.removeItem('tncts_localUsers');
        }
    }else{
        users=new Map();
    }

    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,4}$|^#(?:[0-9a-f]{2}){3,4}$/)){
            return 2;
        }else{
            return false;
        }
    }

    function findColorByCode(cc){
        for(let i of colorMap){
            if(i[1]==cc)
                return i[0];
        }
    }

    // --

    // stylesheet

    let ss;

    function initSS(){
        if(ss)
            document.head.appendChild(ss);
        else
            ss=GM_addStyle(makeStyleText());
    }

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

    function changeUserClass(name,newcolor){
        let ucn=getUserClassName(name);
        ss.innerHTML=ss.innerHTML.replace(`.${ucn}{color:${getUserColor(name)} !important;}`,`.${ucn}{color:${newcolor} !important;}`);
    }

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

    // --

    // pallete

    function makeStyleText(lumshift=0.2){
        let cpCSS=cpCSSbase;
        function cpbCSS(cn,cc){
            function parseCol(a){
                let outp=[];
                a=a.slice(1);
                for(let i=0;i<3;i++){
                    outp.push(parseInt(a.slice(i*a.length/3,(i+1)*a.length/3),16));
                    if(a.length==3)
                        outp[i]*=16;
                }
                return outp;
            }
            function clamp(a,min,max){
                return (a<min)?min:(a>max)?max:a;
            }
            function hex(from){
                let outp='#',tl=true,t;

                for(let i in from)
                    from[i]=Math.round(from[i]);

                for(let i of from)
                    tl=tl&(i%0x11==0);

                for(let i of from){
                    t=i.toString(16);
                    outp+=(tl)?t[0]:(t.length>1)?t:'0'+t;
                }
                return outp;
            }
            function slum(col,s){
                col=col.slice();
                let as=s*3*0xff;
                let sow=0;
                if(s>0){
                    for(let i of col)
                        sow+=1-i/0xff;

                    for(let i=0;i<3;i++)
                        col[i]=clamp(col[i]+(1-col[i]/0xff)/sow*as,0,0xff);
                    return col;
                }else{
                    for(let i of col)
                        sow+=i/0xff;

                    for(let i=0;i<3;i++)
                        col[i]=clamp(col[i]+col[i]/0xff/sow*as,0,0xff);
                    return col;
                }
            }
            function shiftCol(col,s){
                return hex(slum(parseCol(col),s));
            }
            return `.cp_color_${cn}{background-color: ${cc};}.cp_color_${cn}:hover{background-color: ${shiftCol(cc,lumshift)};}`;
        }

        for(let i of colorMap)
            cpCSS+=cpbCSS(i[0],i[1]);

        return cpCSS;
    }

    function createPalleteElement(lstn){
        function n(t){return document.createElement(t);}
        let outp=n('div');
        outp.className='cp_list';

        function onselect(e,me,silent=false){
            me=me||this;
            for(let i of outp.childNodes)
                i.classList.remove('cp_color_active');

            me.classList.toggle('cp_color_active');
            if(!silent&&lstn)
                lstn(outp.nick,me.getAttribute('mycolor'));
        }

        let colblock;
        outp.colblocks=[];
        for(let i of colorMap){
            colblock=n('div');
            colblock.className='cp_color cp_color_'+i[0];
            colblock.setAttribute('mycolor',i[0]);
            colblock.addEventListener('click',onselect);
            outp.appendChild(colblock);
            outp.colblocks.push(colblock);
        }

        outp.style.position='fixed';
        outp.style.zIndex='99999';
        outp.style.display='none';
        outp.setPos=function(x,y){
            outp.style.left=x+'px';
            outp.style.top=y+'px';
        };
        outp.select=function(color){
            for(let i of outp.colblocks){
                if(i.getAttribute('mycolor')==color){
                    onselect(null,i,true);
                    break;
                }
            }
        };

        return outp;
    }

    function onclick(e){
        switch(true){
            case e.target.classList.contains('nick-name'):
                palleteElement.nick=e.target.getAttribute('title');
                palleteElement.select(findColorByCode(getUserColor(palleteElement.nick)));
                //kostyl
                setTimeout(function(){
                    try{
                        let boxpos=document.getElementsByClassName('card-container')[0].getBoundingClientRect();
                        palleteElement.setPos((onlyChat)?boxpos.x+boxpos.width:boxpos.x-palleteElement.getBoundingClientRect().width,boxpos.y);
                    }catch(e){console.log('увы, костыль не сработал',e);}
                },123);
                //--kostyl
            case e.target.classList.contains('cp_color'):
                palleteElement.style.display='block';
            break;
            default:
                palleteElement.style.display='none';
                palleteElement.nick=null;
            break;
        }
        //return false;
    }

    // --

    function onmessage(mutations,observer){
        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(!sessionUsers.has(name))
                        createUserClass(name,getUserColor(name));

                    // processing a color change command
                    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');
                            }
                        }
                    }

                    // applying the color
                    nameel.classList.add(getUserClassName(name));
                }
            }
        }
    }

    // initialization

    let launched;
    let launching;
    let chatElement;
    let chatObserver;
    let palleteElement;

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

    let attemptsLeft=maxAttemptsCount;
    let attemptsTimer;

    function initChat(){
        launched=false;
        launching=true;

        chatElement=findChatElement();
        if(chatElement){
            initSS();

            palleteElement=createPalleteElement(changeUserColor);
            document.body.appendChild(palleteElement);
            window.addEventListener('mouseup',onclick);

            chatObserver=new MutationObserver(onmessage);
            chatObserver.observe(chatElement,obsConfig);

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

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

    function onbasechanged(){
        if(findChatElement()){
            if(!launching&&!launched)
                initChat();
        }else{
            if(launched){
                console.warn('stopping');
                chatObserver.disconnect();
                ss.remove();
                attemptsLeft=maxAttemptsCount;
                launched=launching=false;
            }
        }
    }

    function init(){
        initBaseObs();
        initChat();
    }

    init();

    // --
})();