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