4chan sounds

By Kamichu Last update Dec 23, 2009 — Installed 74,601 times.

There are 17 previous versions of this script.

// ==UserScript==
// @name           4chan sounds
// @namespace      http://userscripts.org/users/33432
// @description    Allows to post sounds in 4chan's boards.
// @include        http://zip.4chan.org/*
// @include        http://boards.4chan.org/*
// ==/UserScript==

function addelem(owner,kind,func){
	var elem=document.createElement(kind);
	
	if(func) func(elem);
	
	owner.appendChild(elem);
	
	return elem;
}

function foreach(l,f){
	for(var i in l){
		f(l[i],i);
	}
}

function $$(owner,kind,a,func){
	var elem;
	
	if(kind=='text'){
		elem=document.createTextNode(a);
		a='';
	} else{
		elem=document.createElement(kind);
	}
	
	if(typeof(a)!='function') elem.className=a;
	else func=a;
	
	if(func) func(elem);
	
	owner.appendChild(elem);
	
	return elem;
}

var settings={
	showPlayerWindows:	[1,		'bool',		'Display player windows',				],
	expandImages:		[0,		'bool',		'Expand images when playing sounds',	],
	showLinksList:		[0,		'bool',		'Show list of clickable audio links',	],
	customCss:			["",	'textarea',	'Custom CSS',							function(v){GM_addStyle(v)}],
};

function changeSetting(key,value){
	var setting=settings[key];
	if(!setting) return;
	
	if(setting[0]==value) return;
	
	setting[0]=value;
	
	if(setting[3]) setting[3](value);
	
	GM_setValue(key,value);
	return value;
}

foreach(settings,function(setting,key){
	changeSetting(key,GM_getValue(key,setting[0]));
});

GM_registerMenuCommand("4chan sounds settings",function(){
	$$(document.body,'div','ys_settingsContainer reply',function(div){
		div.style.position='fixed';
		div.style.right='1em';
		div.style.bottom='1em';
		div.style.padding='0.6em';
		
		$$(div,'table','ys_settingsTable',function(table){
			table.style.marginBottom='0.6em';
			table.style.borderSpacing='1px';
			
			foreach(settings,function(setting,key){
				$$(table,'tr','ys_settingsTr',function(tr){
					$$(tr,'td','ys_settingsCaptionTd postblock',function(td){
						td.textContent=setting[2];
						td.style.width='18em';
					});
					
					$$(tr,'td','ys_settingsValueTd',function(td){
						if(setting[1]=='textarea')
						$$(td,'textarea','ys_settingsTextarea inputtext',function(textarea){
							textarea.rows=12;
							textarea.cols=64;
							textarea.value=setting[0];
							
							textarea.addEventListener('keyup',function(e){
								changeSetting(key,textarea.value);
							},false);
						});
						else if(setting[1]=='bool')
						$$(td,'span','ys_settingsBoolarea inputtext',function(span){
							$$(span,'input',"ys_settingsCheckbox",function(b){
								b.type="checkbox";
								
								b.checked=setting[0];
								b.addEventListener('change',function(e){
									changeSetting(key,b.checked);
								},false);
							});
						});
					});
				});
			});
		});
		
		$$(div,'input','ys_settingsButton ys_closeButton',function(button){
			button.value='Close';
			button.type='submit';
			
			button.addEventListener('click',function(e){
				document.body.removeChild(div);
			},false);
		});
	});
});

function utf8d(string) {
	var utftext="";

	for (var n = 0; n < string.length; n++) {
		var c = string.charCodeAt(n);
		
		if (c < 128) {
			utftext += String.fromCharCode(c);
		} else if((c > 127) && (c < 2048)) {
			utftext += String.fromCharCode((c >> 6) | 192);
			utftext += String.fromCharCode((c & 63) | 128);
		} else {
			utftext += String.fromCharCode((c >> 12) | 224);
			utftext += String.fromCharCode(((c >> 6) & 63) | 128);
			utftext += String.fromCharCode((c & 63) | 128);
		}
	}
	return utftext;
}


function wwwget(link,callback,progress){
/*
	var xmlhttp = new XMLHttpRequest();
	xmlhttp.overrideMimeType('text/plain; charset=x-user-defined');
	
	if(progress) progress(0,xmlhttp);
	
	if(progress){
		xmlhttp.onprogress=function(e){
			if(!e.lengthComputable) return;
			progress(e.loaded/e.total,xmlhttp);
		};
	}
	
	xmlhttp.onload=function(){
		if(progress) progress(1,xmlhttp);
		
		callback(xmlhttp.responseText,xmlhttp);
	};
	
	xmlhttp.open("GET",link,true);
	xmlhttp.send(null);
*/
	if(progress) progress(0);

	GM_xmlhttpRequest({
		method:"GET",
		url:link,
		overrideMimeType: 'text/plain; charset=x-user-defined',
		onload:function(details){
			if(progress) progress(1);
			
			if(details.status==200)
				callback(details.responseText);
			else
				callback(null);
		}
	});
}

var key="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
function sweep64(code,input) {
	var output="";
	var i=0;
	
	code=utf8d(code);

LOOP:
	while(i<input.length){
		if(input.charAt(i++)!='[') continue;
		
		for(var j=0;j<code.length;j++)
			if((input.charCodeAt(i+j)&0xff) != code.charCodeAt(j)) continue LOOP;
		
		i+=code.length;
		
		if(input.charAt(i+0)!=']') continue;
		
		while(input.charAt(i+1)=='\n' || input.charAt(i+1)=='\r' || input.charAt(i+1)==' ')
			i++;
		
		if(input.charAt(i+1)!='O') continue;
		if(input.charAt(i+2)!='g') continue;
		if(input.charAt(i+3)!='g') continue;
		if(input.charAt(i+4)!='S') continue;
		if(input.charCodeAt(i+5)!=0x00) continue;
		if(input.charCodeAt(i+6)!=0x02) continue;
		
		break;
	}

	if(i==input.length) i=0;

	while(i<input.length){
		chr1 = input.charCodeAt(i++)&0xff;
		chr2 = input.charCodeAt(i++)&0xff;
		chr3 = input.charCodeAt(i++)&0xff;

		enc1 = chr1 >> 2;
		enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
		enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
		enc4 = chr3 & 63;

		if(isNaN(chr2)) {
			enc3 = enc4 = 64;
		} else if(isNaN(chr3)) {
			enc4 = 64;
		}

		output+=key.charAt(enc1)+key.charAt(enc2)+key.charAt(enc3)+key.charAt(enc4);
   }
   
   return output;
}

function forelems(kind,func){var list=document.getElementsByTagName(kind);for(var i=0;i<list.length;i++) func(list[i]);}
function insertAfter(referenceNode,newNode){referenceNode.parentNode.insertBefore(newNode,referenceNode.nextSibling);}
function get(node,str){
	var list,elem,res=[];
	
	list=document.evaluate(str,node,null,XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,null);
	
	for(i=0;i<list.snapshotLength;i++)
		res.push(list.snapshotItem(i));

	return res;
}


var requests=[];
var loading=[];
var sounds=[];
var div;

function fixposts(node){
var no=0;
var posts=get(node,"descendant::blockquote");

for(i in posts){
	var p=posts[i];
	
	var a=p;
	while(a && (a.tagName!='A' || (!a.firstChild) || a.firstChild.tagName!='IMG'))
		a=a.previousSibling;
	
	if(!a || !a.firstChild || a.firstChild.tagName!='IMG')
		continue;
	
	for(j=0;j<p.childNodes.length;j++){
		var node=p.childNodes[j];
		
		if(node.nodeType!=3) continue;
		
		if(!(match=node.nodeValue.match(/(.*)\[([^\]]+)\](.*)/))) continue;
		
		
		(function(){
			var href=a.href;
			var code=match[2];
			var ident=href+','+no;
			var postno=p.parentNode.id;
			var img=a.children[0];
			
			node.nodeValue=match[1];
			
			var link=document.createElement('a');
			link.innerHTML='['+match[2]+']';
			link.className='ys_playAudioLink quotejs';
			link.href='#';
			link.id='ys_link'+(no++);
			
			link.addEventListener('click',function(e){
				e.preventDefault();
				
				function stopPlaying(ident){
					if(sounds[ident]){
						sounds[ident].sound.pause();
						div.removeChild(sounds[ident].elem);
						sounds[ident]=undefined;
					}
					if(requests[ident]){
						requests[ident].abort();
						requests[ident]=undefined;
					}
					loading[ident]=0;
					link.innerHTML='['+code+']';
				}
				
				if(loading[ident]){
					stopPlaying(ident);
					return;
				}
				
				loading[ident]=1;
				
				if(sounds[ident]){
					stopPlaying(ident);
					return;
				}
				
				wwwget(href,function(data){
					loading[ident]=0;
					var ref={};
					
					if(settings.expandImages[0]){
						img.src=href;
						img.removeAttribute('width');
						img.removeAttribute('height');
					}
					if(!div){
						div=document.createElement('div');
						div.style.position='fixed';
						div.style.left=0;
						div.style.bottom=0;
						document.body.appendChild(div);
					}
					
					var uri='data:audio/ogg;base64,'+sweep64(code,data);
					
					var audioElement=document.createElement('audio');
					audioElement.setAttribute('src',uri);
					ref.sound=audioElement;
					
					var div2=document.createElement('div');
					div2.style.cssFloat='left';
					div2.style.margin='0.2em';
					div2.style.padding='0.4em';
					div2.className='ys_playerContainer reply';
					
					$$(div2,'span','ys_playerTitleSpan',function(a){a.textContent=code;a.style.fontSize='1.3em';a.style.marginRight='1em'});
					$$(div2,'span','',function(a){
						a.style.cssFloat='right';
						
						a.appendChild(document.createTextNode('['));
						$$(a,'a','ys_playerShowSaveLink ys_playerSaveLink',function(b){
							b.textContent='show save link';
							b.href='#';
							
							var listener=function(e){
								e.preventDefault();
								b.removeEventListener('click',listener,false);
								
								b.className='ys_playerSaveLink';
								b.textContent='save';
								b.href=uri;
							};
							
							b.addEventListener('click',listener,false);
						});
						a.appendChild(document.createTextNode(' '));
						$$(a,'a','ys_playerCloseLink',function(b){
							b.textContent='close';
							b.href='#';
							
							b.addEventListener('click',function(e){
								e.preventDefault();
								
								stopPlaying(ident);
							},false);
						});
						a.appendChild(document.createTextNode(']'));
					});
					
					$$(div2,'br','ys_playerFirstBr');
					if(postno) addelem(div2,'span',function(b){
						b.innerHTML="<a class='quotelink' href='"+window.location.pathname+'#'+postno+"' onclick='replyhl(\""+postno+"\");'>&gt;&gt;"+postno+"</a>"
					});
					$$(div2,'input','ys_playerCloseWhenFinishedCheckbox',function(b){
						b.type="checkbox";
						b.checked=true;
						b.style.cssFloat='right';
						
						ref.closewhenfinished=b;
					});
					
					$$(div2,'span','ys_playerCloseWhenFinishedComment',function(b){b.style.fontSize="small";b.textContent='close when finished';b.style.cssFloat='right';});
					$$(div2,'br','ys_playerSecondBr',function(a){a.style.marginBottom='0.75em'});
					
					if(settings.showPlayerWindows[0]){
						ref.elem=div2;
						audioElement.controls='1';
						div2.appendChild(audioElement);
						div.appendChild(div2);
					} else{
						ref.elem=audioElement;
						div.appendChild(audioElement);
					}
					
					audioElement.addEventListener('ended',function(){
						if(sounds[ident] && sounds[ident].closewhenfinished.checked)
							stopPlaying(ident);
					},false);
					
					sounds[ident]=ref;
					
					audioElement.play();
				},function(v,xmlhttp){
					if(v==0){
						requests[ident]=xmlhttp;
						link.innerHTML="[ <em>Loading... </em> ]";
						link.className='ys_loadingAudioLink quotejs';
					} else if(v<1){
						link.innerHTML="[ <em>Loading... "+Math.floor(v*100)+'%</em> ]';
					} else if(v==1){
						requests[ident]=undefined;
						link.innerHTML='['+code+']';
						link.className='ys_playAudioLink quotejs';
					}
				});
			},true);

			insertAfter(node,link);
			
			var text=document.createTextNode(match[3]);
			insertAfter(link,text);
			
		})();
		
		j--;
	}
}

}

fixposts(document);
document.body.addEventListener("DOMNodeInserted",function(a){fixposts(a.relatedNode);},false);

if(settings.showLinksList[0]){
	var list=[];
	
	foreach(get(document.body,"//a"),function(a){
		if(a.id.match(/^ys_link/)) list.push(a);
	});
	
	if(list.length==0) return;

	$$(document.body,'div','ys_linksListContainer reply',function(div){
		div.style.position='fixed';
		div.style.right='1em';
		div.style.bottom='1em';
		div.style.padding='0.6em';
		
		foreach(list,function(a){
			$$(div,'a','ys_linksListContainerLink ys_playAudioLink quotejs',function(b){
				b.href=window.location.pathname+'#'+a.id;
				b.innerHTML=a.innerHTML;
			});
			$$(div,'br');
		});
	});
}