// ==UserScript==
// @name Replace and highlight
// @namespace wordreplace
// @version 2015.08.21
// @include *://boards.4chan.org/*
// @grant unsafeWindow
// @grant GM_getValue
// @grant GM_setValue
// @description Your own censor list for 4chan
// ==/UserScript==
'use strict';
// Create element function
function element(){
var parent
var toreturn={}
for(var i=0;i<arguments.length;i++){
var current=arguments[i]
if(current.nodeType){
parent=current
}else{
if(Array.isArray(current)){
var tagname=current[0].split('#')
var newtag=document.createElement(tagname[0])
if(tagname[1]){
toreturn[tagname[1]]=newtag
}
for(var j=1;j<current.length;j++){
if(current[j].constructor==Object){
for(var value in current[j]){
if(value!='style'&&value in newtag){
newtag[value]=current[j][value]
}else{
newtag.setAttribute(value,current[j][value])
}
}
}else{
var returned=element(newtag,current[j])
for(var k in returned){
toreturn[k]=returned[k]
}
}
}
}else{
var newtag=document.createTextNode(current)
}
if(parent){
parent.appendChild(newtag)
}
}
}
return toreturn
}
//Update posts
function updateposts(event){
if(event){
var replies=document.querySelectorAll('#t'+event.detail.threadId+' .postContainer:not(.wordreplace)')
}else{
var replies=document.querySelectorAll('.thread .postContainer:not(.wordreplace)')
}
if(replies.length){
if(replies.length>200){
var end=200
}else{
var end=replies.length
}
for(var i=0;i<end;i++){
if(i==200){
var j=0
}else{
var j=i
}
replies[j].classList.add('wordreplace')
//Remove wbr tags
if(thesettings.wrremwbr){
var wbr=replies[j].getElementsByTagName('wbr')
for(var k=wbr.length;k--;){
var pre=wbr[k].previousSibling
var nex=wbr[k].nextSibling
if(pre&&nex&&pre.nodeType==3&&nex.nodeType==3){
pre.nodeValue+=nex.nodeValue
nex.parentNode.removeChild(nex)
}
wbr[k].parentNode.removeChild(wbr[k])
}
}
//Replace and highlight
wordreplacing(replies[j])
}
}
}
//Add new settings
function updatesettings(){
setTimeout(function(){
unsafeWindow.SettingsMenu.options['Replace and highlight']={
wordreplace:['Replace and highlight words [<a href="javascript:wordreplace()">Edit</a>]','Your own censor list',1],
wronlyin:['Instead of replacing everything, replace only in:','',1],
wrbody:['Comment body','',1,1],
wrsubject:['Subject','',1,1],
wrfilename:['Filename','',1,1],
wrposter:['Poster name','',0,1],
wrtitle:['Page title','',1,1],
wrremwbr:['Remove all <wbr> tags from the posts','<wbr> tags are inserted every 35 characters in long words, which may prevent some patterns from working',1],
wrtooltip:['Reveal original line in a tooltip when hovered over','',0]
}
unsafeWindow.wordreplace=function(event,action){
var norefresh=0
var order={on:1,pattern:0,case:1,replace:0,js:1,caps:1,mark:1,board:0}
if(!action){
wr_temp=JSON.parse(JSON.stringify(wr_strings))
var menu=element(
document.body,
['div',{
class:'UIPanel',
id:'wr-body',
onclick:closepanel
},
['div',{
class:'extPanel reply',
style:'width:600px;margin-left:-300px'
},
['div',{
class:'panelHeader'
},
'Replace and highlight words',
['span',
['img',{
alt:'Close',
title:'Close',
class:'pointer',
src:unsafeWindow.Main.icons.cross
}]
]
],
['table',{
style:'text-align:center'
},
['thead',
['tr',
['th'],
['th','On'],
['th','Pattern'],
['th','Case'],
['th','Replace'],
['th','JS'],
['th','Caps'],
['th','Mark'],
['th','Board'],
['th','Del']
]
],
['tbody',{
id:'wr-list'
}],
],
['div',{
style:'float:left'
},
['input',{
type:'button',
value:'Add',
onclick:function(event){
wordreplace(event,'add')
}
}],
['input',{
type:'button',
value:'Import/Export',
onclick:function(event){
wordreplace(event,'port')
}
}]
],
['div',{
style:'float:right'
},
['input',{
type:'button',
value:'Save',
onclick:function(event){
wordreplace(event,'save')
}
}],
['input',{
type:'button',
value:'Save and close',
onclick:function(event){
wordreplace(event,'save')
document.getElementById('wr-body').click()
}
}]
],
['table',{
style:'width:100%'
},
['tr',{
style:'vertical-align:top'
},
['td',
['textarea#input',{
id:'wr-input',
paceholder:'Test it here',
style:'width:280px;height:50px'
}]
],
['td',{
style:'vertical-align:top;text-align:left'
},
['div',{
id:'wr-output',
style:'width:294px;word-wrap:break-word;overflow:hidden'
}]
]
]
]
],
['style','\
#wr-body input[type="text"],#wr-body .wr-replace{\
font:13px monospace;\
width:135px;\
}\
#wr-body .wr-board{\
width:60px!important;\
}\
#wr-body textarea.wr-replace{\
height:50px;\
resize:none!important;\
overflow:hidden;\
cursor:text;\
}\
#wr-body [error],#wr-body .js{\
vertical-align:top;\
}\
#wr-body [error]::after{\
content:attr(error);\
display:block;\
position:absolute;\
overflow:hidden;\
left:0;\
text-overflow:ellipsis;\
width:100%;\
white-space:nowrap;\
margin-top:25px;\
height:20px;\
font-size:12px;\
color:#d00\
}\
#wr-body [error] .wr-replace{\
margin-bottom:20px!important;\
}\
#wr-body .js[error]::after{\
margin-top:60px;\
}']
]
)
menu.input.onkeydown=menu.input.onkeyup=menu.input.onchange=menu.input.onclick=function(event){
wordreplace(event,'type')
}
}
var list=document.getElementById('wr-list')
var trs=list.getElementsByTagName('tr')
if(action!='imported'){
for(var i=0;i<wr_temp.length;i++){
if(trs[i]){
for(var j in order){
var input=trs[i].getElementsByClassName('wr-'+j)[0]
if(order[j]){
wr_temp[i][j]=input.checked
}else{
wr_temp[i][j]=input.value
}
}
}
}
}
switch(action){
case 'add':{
wr_temp.push({on:1,caps:1})
break
}
case 'up':{
var id=event.target.parentNode.parentNode.id.match(/filter-(\d+)/)[1]*1
if(id>0){
var temp=wr_temp[id]
wr_temp[id]=wr_temp[id-1]
wr_temp[id-1]=temp
}else{
norefresh=1
}
break
}
case 'del':{
var id=event.target.parentNode.parentNode.id.match(/filter-(\d+)/)[1]*1
wr_temp.splice(id,1)
break
}
case 'save':{
for(var i=wr_temp.length;i--;){
if(!wr_temp[i].pattern&&!wr_temp[i].replace){
wr_temp.splice(i,1)
}
}
wr_strings=wr_temp.slice()
GM_setValue('wordreplace',JSON.stringify(wr_temp))
norefresh=1
break
}
case 'type':
case 'imported':{
var input=document.getElementById('wr-input').value
input=input.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/(>>\d+|>>>\/\w+\/\d*)/g,'<a class="quotelink pointer">$1</a>').replace(/\n/g,'<br>').replace(/\[(\/)?spoiler\]/g,'<$1s>').replace(/^(>[^\n]*)$/gm,'<span style="color:#789922">$1</span>')
var output=document.getElementById('wr-output')
output.innerHTML=input
output.classList.remove('replacedtext')
wordreplacing(output,1,wr_temp)
if(action!='imported'&&!event.target.classList.contains('wr-js')){
norefresh=1
}
break
}
case 'port':{
var port=element(
document.body,
['div#panel',{
class:'UIPanel',
onclick:closepanel
},
['div',{
class:'extPanel reply',
style:'text-align:center'
},
['div',{
class:'panelHeader'
},
'Import/Export patterns',
['span',
['img',{
alt:'Close',
title:'Close',
class:'pointer',
src:unsafeWindow.Main.icons.cross
}]
]
],
['textarea#txt',{
style:'width:100%;height:100px;box-sizing:border-box',
value:JSON.stringify(wr_temp)
}],
['div',{
style:'float:left'
},
['input',{
type:'button',
value:'Save to file',
onclick:function(){
var txt=port.txt.value
txt=btoa(encodeURIComponent(txt).replace(/%([0-9A-F]{2})/g,function(a,b){
return String.fromCharCode('0x'+b)
}))
element(
['a#link',{
href:'data:application/json;base64,'+txt,
download:'wordreplace.json'
}]
).link.click()
}
}],
['input',{
type:'button',
value:'Load from file',
onclick:function(){
port.file.click()
}
}],
],
['div',{
style:'float:right'
},
['input',{
type:'button',
value:'Replace all',
onclick:function(event){
var temp
try{
temp=fromimport(JSON.parse(port.txt.value))
}catch(e){
alert('This is not a valid JSON')
}
if(temp){
wr_temp=temp
wordreplace(event,'imported',1)
port.panel.click()
}
}
}],
['input',{
type:'button',
value:'Import and append',
onclick:function(event){
var temp
try{
temp=fromimport(JSON.parse(port.txt.value))
}catch(e){
alert('This is not a valid JSON')
}
if(temp){
wr_temp=wr_temp.concat(temp)
wordreplace(event,'type',1)
port.panel.click()
}
}
}]
],
['form',
['input#file',{
type:'file',
style:'display:none',
onchange:function(event){
if(event.target.files.length){
var reader=new FileReader()
reader.onload=function(read){
port.txt.value=read.target.result
event.target.parentNode.reset()
}
reader.readAsText(event.target.files[0])
}
}
}]
]
]
]
)
norefresh=1
break
}
case 'editjs':{
var target=event.target
var editor=element(
document.body,
['div',{
class:'UIPanel',
id:'wr-body',
onclick:function(event){
if(event.target.className=='UIPanel'||event.target.tagName=='IMG'){
target.value=editor.txt.value
closepanel(event)
wordreplace(event,'type')
}
}
},
['div',{
class:'extPanel reply',
style:'width:600px;margin-left:-300px'
},
['div',{
class:'panelHeader'
},
'Edit function',
['span',
['img',{
alt:'Close',
title:'Close',
class:'pointer',
src:unsafeWindow.Main.icons.cross
}]
]
],
['textarea#txt',{
value:target.value,
onkeydown:function(event){
if(event.keyCode==9){ //Tab
event.preventDefault()
var input=event.target
var start=input.selectionStart
input.value=input.value.slice(0,start)+'\t'+input.value.slice(input.selectionEnd)
input.setSelectionRange(start+1,start+1)
}
},
style:'width:100%;height:500px;box-sizing:border-box;font:13px monospace'
}]
]
]
)
editor.txt.setSelectionRange(0,0)
norefresh=1
break
}
}
if(!norefresh){
list.innerHTML=''
if(!wr_temp.length){
wr_temp=[{on:1,caps:1}]
}
for(var i in wr_temp){
var table=element(
list,
['tr#tr',{
id:'filter-'+i
},
['td',
['span',{
class:'pointer',
onclick:function(event){
wordreplace(event,'up')
}
},'\u2191']
]
]
)
for(var j in order){
var tagname='input'
var js=j=='replace'&&wr_temp[i].js
if(js){
tagname='textarea'
}
var td=element(
table.tr,
['td',
[tagname+'#input',{
class:'wr-'+j,
onchange:function(event){
wordreplace(event,'type')
}
}]
]
)
if(order[j]){
td.input.type='checkbox'
if(j=='on'&&wr_temp[i][j]==undefined){
td.input.checked=1
}else{
td.input.checked=wr_temp[i][j]
}
}else{
if(js){
table.tr.classList.add('js')
td.input.readOnly=1
td.input.onclick=function(event){
wordreplace(event,'editjs')
}
}else{
td.input.type='text'
}
td.input.value=wr_temp[i][j]||''
}
}
element(
table.tr,
['td',
['span',{
class:'pointer',
onclick:function(event){
wordreplace(event,'del')
}
},'\xD7']
]
)
}
}
for(var i in wr_temp){
var errors=[]
try{
var a=RegExp(wr_temp[i].pattern)
}catch(e){
errors.push('Error in regex: '+e.message.replace(/.*:.*: /,''))
}
if(wr_temp[i].js){
try{
eval('!function(){'+wr_temp[i].replace+'}')
}catch(e){
errors.push('Error in function: '+e.message)
}
}
if(errors.length){
document.getElementById('filter-'+i).setAttribute('error',errors.join(' | '))
}else{
document.getElementById('filter-'+i).removeAttribute('error')
}
}
}
},1000)
}
function closepanel(event){
if(event.target.className=='UIPanel'){
event.target.parentNode.removeChild(event.target)
}
if(event.target.tagName=='IMG'){
var target=event.target.parentNode.parentNode.parentNode.parentNode
target.parentNode.removeChild(target)
}
}
function fromimport(json){
var order={on:1,pattern:0,case:1,replace:0,js:1,caps:1,mark:1,board:0}
try{
if(json.patterns){
json=json.patterns
}
var temp=[]
for(var i in json){
temp[i]={}
for(var j in order){
if(json[i][j]==undefined){
if(j=='on'||j=='caps'){
temp[i][j]=1
}else{
if(order[j]){
temp[i][j]=0
}else{
temp[i][j]=''
}
}
}else{
temp[i][j]=json[i][j]
}
}
}
return temp
}catch(e){
alert('This JSON cannot be imported because it is corrupt')
console.log(e.message)
}
}
//Word replace
function wordreplacing(post,main,strings){
if(!post||!post.classList){
return
}
if(post.classList.contains('replacedtext')){
return
}
post.classList.add('replacedtext')
if(main){
var comments=[post]
}else{
var props=[]
if(thesettings.wronlyin){
if(thesettings.wrbody){
props.push('blockquote')
}
if(thesettings.wrsubject){
props.push('.subject')
}
if(thesettings.wrfilename){
props.push('.fileText>a')
}
if(thesettings.wrposter){
props.push('.nameBlock')
}
}else{
props=['.nameBlock','.fileText>a','.subject','blockquote']
}
if(!props.length){
return
}
var comments=post.querySelectorAll(props.join(','))
}
for(var i=0;i<comments.length;i++){
var textnodes=[]
var walk=document.createTreeWalker(comments[i],4,null,0)
var newnode
while(newnode=walk.nextNode()){
textnodes.push(newnode)
}
for(var j in textnodes){
var currentnode=textnodes[j]
var out=teststring(currentnode.nodeValue.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'),strings)
if(out!=null){
if(thesettings.wrtooltip||/[<>]/.test(out)){
var newspan=element(
['span#span',{
innerHTML:out
}]
)
if(thesettings.wrtooltip){
newspan.span.title=currentnode.nodeValue
}
currentnode.parentNode.insertBefore(newspan.span,currentnode)
currentnode.parentNode.removeChild(currentnode)
}else{
currentnode.nodeValue=out.replace(/>/g,'>').replace(/</g,'<').replace(/&/g,'&')
}
}
}
}
}
function teststring(text,replaces){
if(text.length){
var oldtext=text
if(replaces){
var patterns=replaces
}else{
var patterns=wr_strings
}
for(var i in patterns){
try{
if(
patterns[i].on&&
patterns[i].pattern&&(
!patterns[i].board||
patterns[i].board.toLowerCase().replace(/[,;]/g,' ').replace(/[^a-z\d\s]/g,'').trim().split(/\s+/).indexOf(currentboard)+1
)
){
var replacewith=patterns[i].replace+''
if(patterns[i].js){
replacewith=function(){
return eval('(function(){'+patterns[i].replace+'}).apply(undefined,arguments)')
}
}else if(patterns[i].mark){
replacewith='<span class="highlighttext">'+(replacewith||'$&')+'</span>'
}
if(patterns[i].caps){
var replacestring=replacewith
replacewith=function(){
if(patterns[i].js){
var returned=replacestring.apply(undefined,arguments)
var s=[returned,arguments[0]]
}else{
var args=arguments
var s=[replacestring,args[0]]
s[0]=s[0].replace(/\$&/g,'$$0').replace(/\$(\d+)/g,function(){
var num=arguments[1]*1
return args[num]||''
})
}
var al=s[0].length
var bl=s[1].length
if(al>bl){
var l=bl
}else{
var l=al
}
if(l<2){
return s[0]
}
s=s.map(function(a){
return [a.slice(0,l-l/2),a.slice(l-l/2,-l/2),a.slice(-l/2)]
})
for(var j=0;j<3;j+=2){
s[0][j]=s[0][j].toLowerCase().split('')
for(var k=0;k<s[0][j].length;k++){
var c=s[1][j][k]
if(c==c.toUpperCase()&&c!=c.toLowerCase()){
s[0][j][k]=s[0][j][k].toUpperCase()
}
}
s[0][j]=s[0][j].join('')
}
if(s[0][1]){
var u=0
if(s[1][1]){
var c=s[1][1]
if(c==c.toUpperCase()&&c!=c.toLowerCase()){
u=2
}
}else{
var c=s[1][0][(l/2|0)-1]
if(c==c.toUpperCase()){
u++
}
var c=s[1][2][0]
if(c==c.toUpperCase()){
u++
}
}
if(u==2){
s[0][1]=s[0][1].toUpperCase()
}
}
return s[0].join('')
}
}else if(!patterns[i].js){
replacewith=replacewith.replace(/\$0/g,'$$&')
}
var ig=patterns[i].case?'g':'ig'
text=text.replace(new RegExp(patterns[i].pattern,ig),replacewith)
}
}catch(e){
if(!replaces){
wr_strings[i].on=0
}
console.error('Error in regex: '+patterns[i].pattern,',',e.message)
}
}
if(oldtext!=text){
return text
}else{
return null
}
}else{
return null
}
}
if(document.getElementsByTagName('meta').length){
//Get settings
var thesettings=localStorage['4chan-settings']
var thedefault='wordreplace wronlyin wrbody wrsubject wrfilename wrtitle wrremwbr'.split(' ')
if(thesettings){
thesettings=JSON.parse(thesettings)
var changed=0
for(var i in thedefault){
if(thesettings[thedefault[i]]==undefined){
thesettings[thedefault[i]]=true
changed=1
}
}
if(changed){
localStorage['4chan-settings']=JSON.stringify(thesettings)
}
}else{
thesettings={}
var gmsetting=GM_getValue('wordreplace')
if(gmsetting){
thesettings.wordreplace=gmsetting=='true'?1:0
}
}
//Determine current page format, board name, and thread id
if(document.body.classList.contains('is_index')){
var activepage='index'
}else if(document.getElementById('content')){
var activepage='catalog'
}else if(location.pathname.indexOf('/thread/')+1){
var activepage='thread'
}else{
var activepage='main'
}
var currentboard=''
var match=document.body.classList[0]
if(match){
match=match.match(/^board_(\w+)$/)
if(match){
var currentboard=match[1]
}else if(activepage!='main'){
match=location.pathname.match(/^\/(\w+)\//)
if(match){
var currentboard=match[1]
}
}
}
//Retrieve pattern list
var wr_strings=GM_getValue('wordreplace')
if(wr_strings){
wr_strings=JSON.parse(wr_strings)
}else{
wr_strings=[
{
on:1,
pattern:'newfag',
case:0,
replace:'newfig',
js:0,
caps:1,
mark:0,
board:'s4s'
},
{
on:1,
pattern:'shitpost',
case:0,
replace:'funpost',
js:0,
caps:1,
mark:0,
board:'s4s'
}
]
}
var wr_temp
if(thesettings.wordreplace&&(thesettings.wrtitle||!thesettings.wronlyin)){
var newtitle=teststring(document.title.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'))
if(newtitle!=null){
document.title=newtitle.replace(/[\r\n]/g,'').replace(/<[^>]*>/g,'').replace(/>/g,'>').replace(/</g,'<').replace(/&/g,'&')
}
}
if(activepage=='index'||activepage=='thread'){
if(thesettings.wordreplace){
updateposts()
document.addEventListener('4chanParsingDone',updateposts)
}
if(unsafeWindow.Main){
updatesettings()
}else{
document.addEventListener('4chanMainInit',updatesettings)
}
}
if(activepage=='catalog'&&thesettings.wordreplace){
for(var i in unsafeWindow.catalog.threads){
var props=[]
if(thesettings.wronlyin){
if(thesettings.wrbody){
props.push('teaser')
}
if(thesettings.wrsubject){
props.push('sub')
}
if(thesettings.wrfilename){
props.push('file')
}
if(thesettings.wrposter){
props.push('author')
}
}else{
props=['author','file','sub','teaser']
}
props.forEach(function(prop){
var out=teststring(unsafeWindow.catalog.threads[i][prop])
if(out!=null){
unsafeWindow.catalog.threads[i][prop]=out
}
if(prop=='author'&&unsafeWindow.catalog.threads[i].lr.author){
var out=teststring(unsafeWindow.catalog.threads[i].lr.author)
if(out!=null){
unsafeWindow.catalog.threads[i].lr.author=out
}
}
})
}
document.getElementById('size-ctrl').dispatchEvent(new Event('change'))
}
}