// ==UserScript==
// @name Fychan Script
// @author FyberOptic
// @email fyberoptic@gmail.com
// @namespace http://www.fybertech.com
// @version 1.0.3
// @description Adds additional functionality to 4chan imageboards.
// @include http://*.4chan.org/*
// @ujs:category site: enhancements
// @ujs:published 2006-05-31 21:45
// @ujs:modified 2006-06-02 14:10
// @ujs:download http://www.fybertech.com/userjs/fychan.js
// ==/UserScript==

/*
 * SCRIPT INFO
 *
 * In a nutshell, this script allows omitted posts from threads to be expanded
 * in-page, without the need to load the thread seperately.  It also adds
 * an extra page navigation bar above the threads, since just having one at
 * the bottom kind of sucks sometimes.  And as a minor bonus, it converts
 * urls in posts into clickable links.
 *
 * When using this script, an "Enable Fychan" or "Disable Fychan" button
 * will appear below the ad beneath the message posting form.  This allows
 * you to toggle the functionality of the script on and off.
 *
 * When activated, all threads in the page are parsed and replaced, now
 * contained inside bordered objects, with an ID bar along the top.  The
 * color/style of the border will differ depending on the thread.  The 
 * different types are:  
 *
 * Solid black - Normal thread, no posts omitted
 * Dashed black - Normal thread, posts omitted
 * Solid green - Expanded thread
 * Dashed green - Expanded thread loaded, but normal thread being displayed
 *
 * If a thread is surrounded by a dashed black border, you should be able to 
 * click the link in the ID bar to dynamically load the full thread inside the 
 * container.  Once the data is retrieved, the border will change to solid 
 * green to signify that thread is now expanded.
 *
 * Once a thread is expanded, you can click the ID bar again to shrink it back
 * to the original version, and the border will changed to being dashed, but
 * remain green.  This indicates the thread has already been loaded, and 
 * clicking on its ID bar again will pop the expanded version right back up.
 */


/*
 * SCRIPT LICENSE
 *
 * You are free to use and distribute this script, but if you make any
 * alterations with the intention to redistribute, you must credit the
 * original script and author.  Violation of such terms forfeits use
 * of your rectum to Pedobear, possibly resulting in eternal damnation 
 * by God for sodomy.  Way to go.
 */



window.opera.addEventListener('BeforeEvent.load', // triggers right before the onload event
function(e) {	
	if (!(e.event.target instanceof Document)) return; // if not document, halt processing
	
	// Additional checks to make sure page is an imageboard
	if (!document.forms[1]) return;
	if (document.forms[1].name != 'delform') return;
	
	// First form is for posting, so ignore it and use the second
  	var mainform = document.forms[1];
  	// Part of small optimization to prepare for parsing all elements inside form (aka, the posts)
  	var rows = mainform.childNodes;
  	
  	// Get fychan cookie
  	var nofychan = 'false';
  	var fychancookie = document.cookie.match(/nofychan\=(true|false)/i);
  	if (fychancookie) nofychan = fychancookie[1];   	
  	
  	// Create toggle button to enable/disable script
  	var divElement = document.createElement('div');
  	divElement.setAttribute('align','center');
  	var buttonElement = document.createElement('button');
  	if (nofychan == 'true') buttonElement.innerHTML = 'Enable Fychan';
  	else buttonElement.innerHTML = "Disable Fychan";
  	buttonElement.style = 'font-size:8pt; font-family:verdana; margin-top:10px';
  	buttonElement.setAttribute('onclick','ToggleFychan()');
  	divElement.appendChild(buttonElement);
  	document.body.insertBefore(divElement,mainform.previousSibling.previousSibling);
  	
  	// If script disabled, quit
  	if (nofychan == 'true') return;
  	
  	// Make urls clickable;
  	Fychan_FixUrls(document.getElementsByTagName('blockquote'));
  	
  	// If on a reply page, don't parse page
  	if (document.location.href.indexOf('/res/') > 0) return;
  	  	
  	// This array stores the innards of the currently parsed thread
  	var postarray = new Array();
  	// This array stores all the new div'd threads
  	var addarray = new Array();
  	// This array stores what's left inside the main form after all threads are parsed
  	var restarray = new Array();
  	  	
  	// Indicates if all threads have been found, making everything else found normal page content
  	var gatherrest = false;  	
  	// Temporary storage of whether currently parsed thread has omitted posts
  	var omittedposts = false;  	
  	// Temporary storage of currently parsed thread ID
  	var div_id = '';
  	// If no thread IDs are found during parsing, must not be a normal board (i.e. /i/ - Oekaki)
  	var no_ids = true;
  	  	
	// Start looping through elements inside mainform
	for( var i = 0, row, lastrow = null; row = rows[i]; i++ )
	{		
		// Check if table under threads is found yet to know when to stop parsing for threads
		if (row.nodeName == 'TABLE' && row.getAttribute('align') == 'right') gatherrest = true;		
		
		// Check if "<br clear=left><hr>" tags are found, indicating the end of a thread
		if (row.nodeName == 'HR' && lastrow && lastrow.nodeName == 'BR' && !gatherrest) 
		{			
			// Dump the <hr> just gathered
			postarray.pop();
			
			// Create a new div to house thread
			var newElement = document.createElement('div');
			// Assign div id
			if (div_id) { newElement.id = 'fychan' + div_id; no_ids = false; }
			
			// Set border style depending on whether there are omitted posts
			var borderstyle;
			if (omittedposts) borderstyle = "dashed"; else borderstyle = "inset";
			newElement.style = "border:2px " + borderstyle + " black; margin-bottom:15px; padding:5px";			
			
			// Add ID bar, clickable based on whether there are omitted posts
			var divstring = "<div style='background-color:gray; padding:5px; margin-bottom:5px; text-align:center; color:white; font-size:9pt; font-family:verdana' id='fychanload" + div_id + "'>";
			if (omittedposts && div_id) divstring += "<a href=\"javascript:GoFychan('" + div_id + "')\" style='color:inherit; font-family:verdana; font-size:inherit'>ID: " + div_id + "</a>";
			else divstring += "ID: " + div_id;
			divstring += "</div>";			
			newElement.innerHTML = divstring;			
			
			// Create inner container div for actual thread content
			var newElement2 = document.createElement('div');
			if (div_id) newElement2.id = 'fychaninner' + div_id;
			// Dump all thread elements inside
			for (blrnt in postarray)
			{
				newElement2.appendChild(postarray[blrnt]);
			}
			newElement2.innerHTML += "<br clear='all'>";
			
			var newElement3 = document.createElement('div');
			if (div_id) newElement3.id = 'fychansecondinner' + div_id;
						
			// Add inner container to outer div (underneath nav bar)
			newElement.appendChild(newElement2);
			newElement.appendChild(newElement3);
			
			// Push entire object into array, to add to page later
			addarray.push(newElement);
			

			// Reset some stuff for next thread
			delete postarray;
			postarray = new Array();
			lastrow = null;
			omittedposts = false;
			div_id = '';
		}
		
		// If not end of thread, check if element is a child of mainform
		else if (row.parentNode == mainform)
		{
			// Duplicate element
			var cloned = row.cloneNode(true)
			// If working with a thread, push element into thread array,
			if (!gatherrest) postarray.push(cloned);
			// otherwise, push it to restarray for now.
			else restarray.push(cloned);
			
			// Set lastrow to current row, in order to check for "<br clear=left><hr>" in above "IF" later
			lastrow = row;
			
			if (row.nodeType != 1) continue;
			
			// Check if current thread has omitted posts
			if (row.className == 'omittedposts') omittedposts = true;
			
			// Find thread id
			if (row.id.indexOf('nothread') == 0) div_id = row.id.substr(8);
			
			//if (row.className == 'filetitle' || row.className == 'postername') cloned.innerHTML += '&nbsp;';			
		}		
	}  	 
  	
  	// If no elements to add, or no thread IDs found, jump to finish
  	if (addarray.length < 1 || no_ids) goto finishfychan;
  	
  	// Totally wipe form contents
  	mainform.innerHTML = '';
 	// Append thread divs
 	for (blrnt in addarray) mainform.appendChild(addarray[blrnt]);
  	
  	// Stores where to append rest of page elements
  	var appendto = mainform;  	
  	// Start adding rest of page back by looping through array
  	for (blrnt in restarray)
  	{
  		// Test for presence of Previous/Next bar
  		if (restarray[blrnt].nodeType == 1 && restarray[blrnt].innerHTML.indexOf('Previous') > 0)
  		{
  			// Change where all future tags are appended (outside of main form)
  			appendto = document.body;
  			
  			// Repair Previous/Next bar, due to bad coding of page
  			var buttontext = restarray[blrnt].outerHTML.replace(/<CAPTION><FORM action=\"(http.*?html)\" method=\"get\"><\/FORM><\/CAPTION><TD><INPUT type=\"submit\" value=\"(Next|Previous)\" accesskey=\"(.)\"\/><\/TD>/gi,'<TD><BUTTON onClick="javascript:document.location=\'$1\'; return true;" accesskey="$3">$2</BUTTON> </TD>');
			buttontext = buttontext.replace(/\<TABLE.*?\>\<TBODY\>\<TR\>\<TD\>(.+?)\<\/TD\>\<\/TR\>\<\/TBODY\>\<\/TABLE\>/i,'$1');
			buttontext = buttontext.replace(/\<\/TD\>\<TD\>/gi,' - ');
			buttontext = buttontext.replace(/^Previous/,'<button disabled>Previous</button>').replace(/Next$/,'<button disabled>Next</button>');
			restarray[blrnt].innerHTML = buttontext;			
			
			var extranav = document.createElement('div');
			extranav.setAttribute('align','center');						
			extranav.innerHTML = buttontext;
			document.body.insertBefore(extranav, mainform);			  			
  		}  		
  		// Append page element
  		appendto.appendChild(restarray[blrnt]);
  	}
  	
  	finishfychan:
  	
   	// LATER ARRAYS
   	delete addarray;
   	delete restarray;
   	delete postarray;  	  
  }
,false);


function Fychan_Open(url)
{
	var fychanwin = window.open(url);
}

function Fychan_FixUrls(rows)
{
   	var regex1 = new RegExp(/(href\=\")http(\:\/\/[^\"]+)/gmi);
   	var regex2 = new RegExp(/(https*\:\/\/[^ \<\>\'\"]+)/gmi);
   	var regex3 = new RegExp(/(href\=\")hffp(\:\/\/[^\"]+)/gmi);
   	
   	for( var i = 0, row; row = rows[i]; i++ )
   	{
   		if (row.innerHTML.indexOf('http') != -1)
   		{
   			row.innerHTML = row.innerHTML.replace(regex1,"$1hffp$2");
   			row.innerHTML = row.innerHTML.replace(regex2,"<a href=\"javascript:Fychan_Open('$1')\">$1</a>");
   			row.innerHTML = row.innerHTML.replace(regex3,"$1http$2");
   		}
   	}
}


function ToggleFychan()
{
  	// Get cookie
  	var nofychan = 'false';
  	var fychancookie = document.cookie.match(/nofychan\=(true|false)/i);
  	if (fychancookie) nofychan = fychancookie[1];  	

	// Toggle cookie setting accordingly
	if (nofychan == 'true')
	{
		document.cookie = "nofychan=false; expires=Fri, 01-Jan-2010 00:00:01 GMT; path=/; domain=.4chan.org";		
	}
	else
	{
		document.cookie = "nofychan=true; expires=Fri, 01-Jan-2010 00:00:01 GMT; path=/; domain=.4chan.org";		
	}
	
	// Reload page~~~
	document.location.reload();
}


function GoFychan(id)
{	
	var fychan_element = document.getElementById('fychan' + id);
	var fychan_innerelement = document.getElementById('fychaninner' + id);
	var fychan_secondinnerelement = document.getElementById('fychansecondinner' + id);
	var fychan_loadelement = document.getElementById('fychanload' + id);
	if (!fychan_element) return;	
	
	// Check if switching back to short version
	if (fychan_innerelement.style.display == 'none')
	{
		// Toggle which DIVs are displayed
		fychan_innerelement.style.display = 'block';
		fychan_secondinnerelement.style.display = 'none';
		
		// Update thread border
		fychan_element.style.borderStyle = 'dashed';

		return;
	}
	// Check if already loaded expanded content before doing so again
	else if (fychan_secondinnerelement.innerHTML.length > 0)
	{		
		// Toggle which DIVs are displayed
		fychan_innerelement.style.display = 'none';
		fychan_secondinnerelement.style.display = 'block';
		
		// Update thread border
		fychan_element.style.borderStyle = 'solid';
		
		return;
	}

	//var newurl = document.location.href.replace(/^http\:\/\/(.+?)\.4chan\.org\/(.+?)\/.*?$/,'http://$1.4chan.org/$2/res/' + id + '.html');
	var newurl = document.location.href.replace(/^(.+)\/.*?$/,'$1') + '/res/' + id + '.html';
	
	// Set up socket for transferring thread page
	var r = false;
	if (window.XMLHttpRequest) r = new XMLHttpRequest();
	if (!r) { alert('XMLHttpRequest Not Supported');  return; }
	
	// Update ID bar with current status
	fychan_loadelement.innerHTML = "Loading...";
	
	// Set up what to do when page retrieved
	r.onreadystatechange = function()
	{
		if (r.readyState == 4)
		{			
			// Chop headers and footers
			var responsetext = r.responseText.replace(/[\r\n]/g,'').replace(/^.+?\<form name\=\"delform\" action\=\"\.\.\/imgboard\.php\" method\=POST\>/g,'').replace(/\<br clear\=left\>\<hr\>\<table align\=right\>\<tr\>\<td nowrap align\=center\>.+?$/g,'');
			// Add [Reply] button
			responsetext = responsetext.replace(/class=\"quotejs\"\>(\d+)\<\/A\>/i,'class="quotejs">$1</A> &nbsp; [<a href="' + newurl + '">Reply</a>]');
			// Replace abbreviated thread with long one
			fychan_secondinnerelement.innerHTML = responsetext + "<br clear='all'>";
			
			// Hide short version of thread, show longer version
			fychan_innerelement.style.display = 'none';
			fychan_secondinnerelement.style.display = 'block';
			
			// Restore ID bar			
			fychan_loadelement.innerHTML = "<a href=\"javascript:GoFychan('" + id + "')\" style='color:inherit; font-family:verdana; font-size:inherit'>ID: " + id + "</a>";
			
			// Update border to solid green
			fychan_element.style.border = '2px inset #00aa00';
			
			Fychan_FixUrls(fychan_secondinnerelement.getElementsByTagName('blockquote'));
		}		
	}
	// Fetch!
	r.open('GET', newurl, true);
	r.send(null);
		
}
