油管评论编辑修改 youtube 搬运烤肉翻译man辅助

可以直接修改youtube视频评论区

// ==UserScript==
// @name         油管评论编辑修改 youtube 搬运烤肉翻译man辅助
// @namespace    http://tampermonkey.net/
// @version      0.6
// @description  可以直接修改youtube视频评论区
// @author       manakanemu
// @match        *://*.youtube.com/*
// @grant        none
// ==/UserScript==

(function() {
    (function (){
        const css = document.createElement('style')
        css.innerHTML = '#expander{cursor: pointer;}'
        document.head.appendChild(css)
    })();

    function tagCompare(dom,tagName){
        return dom.tagName == tagName
    }


    function findRootDom(dom,target,comp){
        if(comp(dom,target)){
            return dom
        }else{
            return findRootDom(dom.parentElement,target,comp)
        }
    }

    function itFormat(words){
        if(words === ''){
            return ''
        }else{
            return '<span-it dir="auto" class="italic style-scope yt-formatted-string">'+words+'</span-it>'
        }
    }
    function bfFormat(words){
        if(words === ''){
            return ''
        }else{
            return '<span-bold dir="auto" class="bold style-scope yt-formatted-string">'+words+'</span-bold>'
        }
    }
    function bbfFormat(words){
        if(words === ''){
            return ''
        }else{
            return '<strong>'+words+'</strong>'
        }
    }
    function colorFormat(words,color){
        if(words === ''){
            return ''
        }else{
            return '<span-color style="color:'+color+';">'+words+'</span-color>'
        }
    }
    function basicFormat(words){
            if(words === ''){
                return ''
            }else{
                return '<span dir="auto" class="style-scope yt-formatted-string">'+words+'</span>'
            }
    }
    function sizeFormat(words,size){
        if(words === ''){
            return ''
        }else{
            return '<span-size style="line-height:normal;font-size:'+size+'px;">'+words+'</span-size>'
        }
    }
    function newlineFormat(words){
       return  words+'<span dir="auto" class="style-scope yt-formatted-string">\n</span>'
    }
    function customFormat(words,color,size,font){
        if(words === ''){
            return ''
        }else{
            let res = words
            for(f of font){
                if(f !== ''){
                    res = '\\'+f+'{'+res+'}'
                }
            }
            return '\\color{'+color+'}{\\size{'+size+'}{'+res+'}}'
        }
    }

    function parseBracketStart(string,index){
        if(string[index] !== '{'){
            return -1
        }else{
            let count = 1
            for(let i = index+1;i<string.length;i++){
                if(string[i] === '{' && string[i-1] !== '\\'){
                    count += 1
                }
                if(string[i] === '}' && string[i-1] !=='\\'){
                    count -= 1
                }
                if(count === 0){
                    return i
                }
            }
            return -1
        }
    }

    function parseKey(string,key,group=0,times = 1){
        let index = string.search(key)
        const pairs = new Array()
        let t= 0
        while(index > -1 && t < times){
            let keyword = string.substring(index).match(key)
            if(!keyword || keyword.length <= group){
                break
            }
            keyword = keyword[group]
            const start = index + keyword.length-1
            const end = parseBracketStart(string,start)
            if(end!=-1){
                pairs.push([index,start,end])
            }
            const newIndex = string.substring(index+1).search(key)
            if(newIndex > -1){
                index += newIndex+1
            }else{
                index = newIndex
            }
            t += 1
        }
        return pairs
    }

    function parseIt(string){
        let pair = parseKey(string,/\\it{/)[0]
        while(pair && pair.length > 0){
            string = string.substring(0,pair[0]) + itFormat(string.substring(pair[1]+1,pair[2])) + string.substring(pair[2]+1)
            pair = parseKey(string,/\\it{/)[0]
        }
        return string
    }

    function parseBF(string){
        let pair = parseKey(string,/\\bf{/)[0]
        while(pair && pair.length > 0){
            string = string.substring(0,pair[0]) + bfFormat(string.substring(pair[1]+1,pair[2])) + string.substring(pair[2]+1)
            pair = parseKey(string,/\\bf{/)[0]
        }
        return string
    }
    function parseBbf(string){
        let pair = parseKey(string,/\\bbf{/)[0]
        while(pair && pair.length > 0){
            string = string.substring(0,pair[0]) + bbfFormat(string.substring(pair[1]+1,pair[2])) + string.substring(pair[2]+1)
            pair = parseKey(string,/\\bbf{/)[0]
        }
        return string
    }

    function parseColor(string){
        let pair = parseKey(string,/\\color\{.*?\}\{/)[0]
        while(pair && pair.length > 0){
            const color = string.substring(pair[0],pair[1]).match(/\{(.*?)\}/)[1]
            string = string.substring(0,pair[0]) + colorFormat(string.substring(pair[1]+1,pair[2]),color) + string.substring(pair[2]+1)
            pair = parseKey(string,/\\color\{.*?\}\{/)[0]
        }
        return string
    }
    function parseSize(string){
        let pair = parseKey(string,/\\size\{.*?\}\{/)[0]
            while(pair && pair.length > 0){
                const size = string.substring(pair[0],pair[1]).match(/\{(.*?)\}/)[1]
                string = string.substring(0,pair[0]) + sizeFormat(string.substring(pair[1]+1,pair[2]),size) + string.substring(pair[2]+1)
                pair = parseKey(string,/\\size\{.*?\}\{/)[0]
            }
            return string
    }
    // function parseNormal(string){
    //     return basicFormat(string)
    // }
    function parseCustomStyle(string){
        const commandData = JSON.parse(window.localStorage.getItem('commentex-commands')) || {}
        for(let name in commandData){
            let pair = parseKey(string,new RegExp('\\\\'+name+'{'))[0]
            while(pair && pair.length > 0){
                const color = commandData[name].color
                const size = commandData[name].size
                const font = commandData[name].font
                string = string.substring(0,pair[0]) + customFormat(string.substring(pair[1]+1,pair[2]),color,size,font) + string.substring(pair[2]+1)
                pair = parseKey(string,new RegExp('\\\\'+name+'{'))[0]
            }
        }
        return string
    }



    function parseDelete(textarea){
        if(/\\delete/.test(textarea.value)){
            if(confirm('确定要删除该评论吗?')){
                let root = findRootDom(window.ClickedComment,'YTD-COMMENT-RENDERER',tagCompare)
                if(root.parentElement.tagName == 'YTD-COMMENT-THREAD-RENDERER'){
                    root = root.parentElement
                }
                root.parentElement.removeChild(root)
                return true
            }else{
                textarea.value = textarea.value.replace(/\\delete/g,'')
            }
        }
        return false
    }
    function parseComments(string,newline=true){
        let html = string
        html = parseCustomStyle(html)
        html = parseBF(html)
        html = parseBbf(html)
        html = parseIt(html)
        html = parseColor(html)
        html = parseSize(html)
        // html = parseNormal(html)
        if(newline){
            return newlineFormat(html)
        }else{
            return html
        }
    }

    function parseAddCustomStyle(innerHTML){
        if(/\\addstyle/.test(innerHTML)){
            const rawCommand = innerHTML.match(/\\addstyle\[.*?\]/)[0]
            const parms = rawCommand.match(/{.*?}/g)
            let color = ''
            let size = ''
            let font = ['']
            let name = ''
            switch(parms.length){
                case 4:
                    name = parms[0].match(/{(.*)}/)[1]
                    color = parms[1].match(/{(.*)}/)[1]
                    size = parms[2].match(/{(.*)}/)[1]
                    font = parms[3].match(/{(.*)}/)[1].split(',')
                    break
                case 3:
                    name = parms[0].match(/{(.*)}/)[1]
                    color = parms[1].match(/{(.*)}/)[1]
                    size = parms[2].match(/{(.*)}/)[1]
                    break
                case 2:
                    name = parms[0].match(/{(.*)}/)[1]
                    color = parms[1].match(/{(.*)}/)[1]
                    break
                default:
                    break
            }
            if(!(name === '' || new Set(['bf','bbf','it','addstyle','delete','size','color']).has(name) )){
                const command = {color,size,font}
                let commandData = JSON.parse(window.localStorage.getItem('commentex-commands')) || {}
                commandData[name] = command
                window.localStorage.setItem('commentex-commands',JSON.stringify(commandData))
            }
            return innerHTML.replace(rawCommand,'')
        }else{
            return innerHTML
        }

    }


    function commentClick(){
        const container = this.getElementsByTagName('yt-formatted-string')[1]
        window.ClickedComment = container
        let strings = ''
        if(container.children.length == 0){
            strings += container.innerText
        }else{
            const raw = container.innerHTML
            strings = raw
            strings = strings.replace(/\n/g,'#%')
            strings = strings.replace(/<span dir="auto" class="style-scope yt-formatted-string">#%<\/span>/g,'#%')
            // color
            while(strings.search(/<span-color style="color:(.*?);">(.*?)<\/span-color>/) > -1){
                strings = strings.replace(/<span-color style="color:(.*?);">(.*?)<\/span-color>/g,'\\color{$1}{$2}')
            }
            // size
            while(strings.search(/<span-size.*?font-size:(.*?)px;">(.*?)<\/span-size>/) > -1){
                strings = strings.replace(/<span-size.*?font-size:(.*?)px;">(.*?)<\/span-size>/g,'\\size{$1}{$2}')
            }
            // normal
            while(strings.search(/<span dir="auto" class="style-scope yt-formatted-string">(.*?)<\/span>/) > -1){
                strings = strings.replace(/<span dir="auto" class="style-scope yt-formatted-string">(.*?)<\/span>/g,'$1')
            }
            // bold
            while(strings.search(/<span-bold .*?">(.*?)<\/span-bold>/) > -1){
                strings = strings.replace(/<span-bold .*?>(.*?)<\/span-bold>/g,'\\bf{$1}')
            }
            while(strings.search(/<span dir="auto" class="bold style-scope yt-formatted-string">(.*?)<\/span>/) > -1){
                strings = strings.replace(/<span dir="auto" class="bold style-scope yt-formatted-string">(.*?)<\/span>/g,'\\bf{$1}')
            }
            // strong
            while(strings.search(/<strong>(.*?)<\/strong>/) > -1){
                strings = strings.replace(/<strong>(.*?)<\/strong>/g,'\\bbf{$1}')
            }
            // italic
            while(strings.search(/<span dir="auto" class="italic style-scope yt-formatted-string">(.*?)<\/span>/) > -1){
                strings = strings.replace(/<span dir="auto" class="italic style-scope yt-formatted-string">(.*?)<\/span>/g,'\\it{$1}')
            }
            while(strings.search(/<span-it .*?">(.*?)<\/span-it>/) > -1){
                strings = strings.replace(/<span-it .*?>(.*?)<\/span-it>/g,'\\it{$1}')
            }
            strings = strings.replace(/#%/g,'\n')


        }
        const textarea = document.createElement('textarea')
        textarea.id = 'my-comment-textarea'
        textarea.value = strings
        textarea.style.position = 'absolute'
        textarea.style.height = '200px'
        textarea.style.width = this.offsetWidth + 'px'
        textarea.style.top = this.offsetTop+'px'
        textarea.style.left = this.offsetLeft+'px'
        document.body.appendChild(textarea)
        this.style.height = textarea.offsetHeight+'px'
        this.removeAttribute('should-use-number-of-lines')

        textarea.focus()

        textarea.onblur = function(){
            if(parseDelete(this)){
                this.parentElement.removeChild(this)
                return
            }
            let comment = this.value
            comment = parseAddCustomStyle(comment)
            this.parentElement.removeChild(this)
            container.parentElement.parentElement.style.height = ''
            let htmls = ''
            container.innerHTML = parseComments(comment,false)
            window.ClickedComment = null
        }



    }

    function commentsObservation(mutations,observe){
        for(let mutation of mutations){
            if(mutation.target.id == 'content-text'){
                mutation.target.parentElement.parentElement.onclick = commentClick
            }
        }
    }

    function initalObservation(mutations,observer) {
        if(document.getElementById('comments')){
            observer.disconnect()
            const commentsObserver = new MutationObserver(commentsObservation)
            commentsObserver.observe(document.getElementById('comments'),config)
        }
    }




    const config = {childList: true, subtree: true };
    window.ClickedComment = null
    const initalObserver = new MutationObserver(initalObservation)
    initalObserver.observe(document.body,config)

})();