TA improver

Reconfigures a number of items on various pages within TrueAchievements.

目前為 2015-03-12 提交的版本,檢視 最新版本

// ==UserScript==
// @name			TA improver
// @namespace		mobiusevalon.tibbius.com
// @version			0.2
// @description	 	Reconfigures a number of items on various pages within TrueAchievements.
// @author			Mobius Evalon
// @include			/^https{0,1}:\/\/\w{0,}\.?trueachievements\.com.*$/
// @grant			none
// ==/UserScript==

/* == SCRIPT OPTIONS == */
// change_chat:					basic chat restructuring like moving the clear button, hiding the
//								userlist key, etc.
// minimalize_chat:				extreme vertical space-saving measures that leaves nothing but
//								the chat and the site header
// normalize_dates:				change all date/time displays to the same format
// use_fuzzy_dates:				keep the terms "tomorrow", "today", "yesterday" instead of replacing them
//								with an explicit date
// change_user_session_links:	when viewing a session page, change the default link on a
//								person's gamertag to take you to the comparison page instead
// hide_session_user_status:	remove user status strings from the table on the session page
// add_feed_permalinks:			add the permalink icons on the right side of each friend
//								feed comment
// hide_ads:					does what it says on the tin
var change_chat = 1;
var minimalize_chat = 1;

var normalize_dates = 1;
var use_fuzzy_dates = 1;

var change_session_user_links = 1;
var hide_session_user_status = 1;

var add_feed_permalinks = 1;

var hide_ads = 1;
/* == END SCRIPT OPTIONS == */

function exists(element)
{
	if(element !== null && element !== undefined) return 1;
	return 0;
}

function splode(element)
{
	if(exists(element))
	{
		if(element.parentNode) element.parentNode.removeChild(element);
		else if(element.style) element.style.display = "none";
		else console.log("element could not be hidden");
	}
}

function splode_nodelist(nodelist)
{
	for(var i=nodelist.length;i>-1;i--) splode(nodelist[i]);
}

function remove_by_class_name()
{
	for(var i=0;i<arguments.length;i++) splode_nodelist(document.getElementsByClassName(arguments[i]));
}

function remove_by_tag_name()
{
	for(var i=0;i<arguments.length;i++) splode_nodelist(document.getElementsByTagName(arguments[i]));
}

function remove_by_id()
{
	for(var i=0;i<arguments.length;i++) splode(document.getElementById(arguments[i]));
}

function urldecode(string)
{
	return string.replace("+"," ");
}

Date.prototype.getMonthString = function ()
{
	n = this.getMonth();
	if(n === 0) return "Jan";
	else if(n == 1) return "Feb";
	else if(n == 2) return "Mar";
	else if(n == 3) return "Apr";
	else if(n == 4) return "May";
	else if(n == 5) return "Jun";
	else if(n == 6) return "Jul";
	else if(n == 7) return "Aug";
	else if(n == 8) return "Sep";
	else if(n == 9) return "Oct";
	else if(n == 10) return "Nov";
	else return "Dec";
};

Date.prototype.getDayString = function ()
{
	n = this.getDay();
	if(n === 0) return "Sun";
	else if(n == 1) return "Mon";
	else if(n == 2) return "Tue";
	else if(n == 3) return "Wed";
	else if(n == 4) return "Thu";
	else if(n == 5) return "Fri";
	else return "Sat";
};

Date.prototype.isSameDate = function(d)
{
	if(this.getDate() == d.getDate() && this.getMonthString() == d.getMonthString()) return 1;
	return 0;
};

Date.prototype.fuzzify = function()
{
	var d = new Date();

	if(this.isSameDate(d)) return "Today";
	d.setDate(d.getDate()+1);
	if(this.isSameDate(d)) return "Tomorrow";
	d.setDate(d.getDate()-2);
	if(this.isSameDate(d)) return "Yesterday";
	return "";
};

Date.prototype.outputDate = function ()
{
	var r = "";
	if(use_fuzzy_dates) r = this.fuzzify();
	if(!r.length) r = (this.getDayString()+" "+this.getDate()+" "+this.getMonthString());
	return (r+", "+this.doubleUp(this.getHours())+":"+this.doubleUp(this.getMinutes()));
};

Date.prototype.doubleUp = function (n)
{
	n = (""+n);
	if(n.length < 2) n = ("0"+n);
	return n;
};

function strip_undefined(a)
{
	var r = [];
	for(var i=0;i<a.length;i++)
	{
		if(a[i] !== undefined) r.push(a[i]);
	}
	return r;
}

function date_format_a()
{
	var args = strip_undefined(arguments); // because i have to counter the regex engine trying to be helpful
	var date = new Date();
	if(args.length == 10) date = new Date(args[1]+", "+args[6]+":"+args[7]);
	else if(args.length == 9) date = new Date(args[1]+" "+date.getFullYear()+", "+args[5]+":"+args[6]);
	else if(args.length == 6)
	{
		date.setHours(parseInt(args[2]),parseInt(args[3]));

		if(args[1].toLowerCase() == "yesterday") date.setDate(date.getDate()-1);
		else if(args[1].toLowerCase() == "tomorrow") date.setDate(date.getDate()+1);
	}
	return date.outputDate();
}

function date_format_b()
{
	var args = strip_undefined(arguments); // because i have to counter the regex engine trying to be helpful
	var date = new Date();
	if(args.length == 9) date = new Date(args[1]+", "+args[5]+":"+args[6]);
	else if(args.length == 8) date = new Date(args[1]+" "+date.getFullYear()+", "+args[4]+":"+args[5]);
	else if(args.length == 6)
	{
		date.setHours(parseInt(args[2]),parseInt(args[3]));

		if(args[1].toLowerCase() == "yesterday") date.setDate(date.getDate()-1);
		else if(args[1].toLowerCase() == "tomorrow") date.setDate(date.getDate()+1);
	}
	return date.outputDate();
}

function format_dates(element,format)
{
	if(exists(element))
	{
		// sun[day], 1 jan [2015] at 00:00 (gaming sessions box, session pages)
		if(format == 1) element.innerHTML = element.innerHTML.replace(/((\w{3,9}), (\d{1,2}) (\w{3,9})( \d{4})?|today|yesterday|tomorrow) at (\d{2}):(\d{2})/gi,date_format_a);
		// 1 jan at 00:00 (my gaming sessions page, my upcoming sessions box)
		else if(format == 2) element.innerHTML = element.innerHTML.replace(/((\d{1,2}) (\w{3,9})( \d{2})?|yesterday|today|tomorrow) at (\d{2}):(\d{2})/gi,date_format_b);
	}
}

function format_gamer_count(element)
{
	element.innerHTML = element.innerHTML.replace(/(\d{1,2}) gamer(s)? still needed/gi,"$1 slot$2 left");
	element.innerHTML = element.innerHTML.replace("This session is unlimited","unlimited");
}

if(hide_ads)
{
	// oopsie daisy, the ads disappeared
	remove_by_id("topad-wrap","divTAProHolder");
	remove_by_class_name("rightintergi","internalad","followuson");
}

if(normalize_dates)
{
	if(window.location.href.indexOf("mygamingsessions.aspx") > -1)
	{
		var sessions = document.getElementById("oGamerGamingSessionListHolder");
		if(exists(sessions) && sessions.tagName == "DIV") format_dates(sessions,2);
	}
	var panels = document.getElementsByClassName("smallpanel");
	for(var i=0;i<panels.length;i++)
	{
		if(exists(panels[i]))
		{
			var header = panels[i].getElementsByTagName("h4")[0];
			if(exists(header))
			{
				if(header.innerHTML.indexOf("Sessions For This Game") > -1) format_dates(panels[i],1);
				else if(header.innerHTML.indexOf("Gaming Sessions") > -1)
				{
					format_dates(panels[i],1);
					format_gamer_count(panels[i]);
				}
				else if(header.innerHTML.indexOf("My Upcoming Sessions") > -1) format_dates(panels[i],2);
			}
		}
	}
}

if(window.location.href.indexOf("gamingsessionfeedback.aspx") > -1) remove_by_class_name("gsdisclaimer");

if(window.location.href.indexOf("gamingsession.aspx") > -1)
{
	// gsdisclaimer is that thing that says ta is not responsible for the developer resetting your stats or whatever
	// if you get caught boosting, and that they won't change feedback
	remove_by_class_name("gsdisclaimer");

	// reformat the date stated at the top of a session page
	if(normalize_dates)
	{
		var session_header = document.getElementsByClassName("gamingsession")[0];
		if(exists(session_header) && session_header.tagName == "DIV") format_dates(session_header,1);
	}

	// this whole bit changes the links of the gamertags listed on a session to the comparison page by default,
	// instead of just the list of achievements that person has (except for yourself, of course)
	if(change_session_user_links)
	{
		var game_id = "";
		var my_id = "";
		var my_tag = "";
		// first we gotta find the game id
		var panel = document.getElementsByClassName("smallpanel")[0];
		if(exists(panel) && panel.tagName == "DIV")
		{
			var content = panel.getElementsByClassName("panelcontent")[0];
			if(exists(content) && content.tagName == "DIV")
			{
				// the easiest place i could locate the game id is that link to create a new session on the right
				var anchors = content.getElementsByTagName("a");
				var new_session_link = anchors[anchors.length-1];
				if(exists(new_session_link) && new_session_link.innerHTML == "make a new session") game_id = new_session_link.href.substring(new_session_link.href.indexOf("=")+1);
			}
		}
		// now we gotta find your id and tag
		var menu = document.getElementById("mnuMyPages");
		if(exists(menu) && menu.tagName == "LI")
		{
			var items = menu.getElementsByTagName("li");
			for(var i=0;i<items.length;i++)
			{
				var anchor = items[i].getElementsByTagName("a")[0];
				if(exists(anchor))
				{
					if(anchor.innerHTML.indexOf("My Homepage") > -1) my_tag = urldecode(anchor.href.substring(anchor.href.lastIndexOf("/")+1,anchor.href.length-4));
					else if(anchor.innerHTML.indexOf("My Stats") > -1) my_id = anchor.href.substring(anchor.href.indexOf("=")+1);
				}
				if(my_id.length && my_tag.length) break;
			}
		}

		if(my_id && my_tag)
		{
			var gamers = document.getElementsByClassName("gamer");
			for(var i=0;i<gamers.length;i++)
			{
				var td = gamers[i];
				if(exists(td) && td.tagName == "TD")
				{
					// i get rid of the gamer statuses in the table while i'm at it, considering it unnecessarily stetches
					// the table and the page vertically with information you're unlikely to care about
					if(hide_session_user_status)
					{
						var gamer_status = td.getElementsByClassName("sitestatus")[0];
						if(exists(gamer_status) && gamer_status.tagName == "SPAN") splode(gamer_status);
					}
					var link = td.getElementsByTagName("a")[0];
					if(exists(link) && my_tag != link.innerHTML)
					{
						var other_gamer_id = link.href.substring(link.href.indexOf("=")+1);
						link.href = ("comparison.aspx?gameid="+game_id+"&gamerid="+my_id+"&friendid="+other_gamer_id);
					}
				}
			}
		}
	}
}

// this section only applies when you're looking at someone's gamerpage.  since each person has the option to 
// turn their friend feed off apparently (i've seen some pages without them), i check for the existence of the 
// feed by checking for the named div that contains it
if(add_feed_permalinks)
{
	var friend_feed = document.getElementById("oFriendFeed");
	if(exists(friend_feed) && friend_feed.tagName == "DIV")
	{
		// every comment in a friend feed is conveniently contained in a div named "comment"
		var comments = friend_feed.getElementsByClassName("Comment");
		for(var i=0;i<comments.length;i++)
		{
			// the p element references a paragraph node inside the comment div that contains the little button 
			// for commenting on an item.  even when the comment button is not there, this p element still exists
			// and is a perfect place to put the permalink icon
			var p = comments[i].getElementsByTagName("p")[0];
			// using ta's own permalink icon for this purpose
			if(exists(p)) p.innerHTML = ('<a href="?gfcid='+comments[i].id.substring(5)+'"><img src="images/icons/permalink.png" class="AddComment" title="Permalink" alt="Permalink"></a>'+p.innerHTML);
		}	
	}
}

// this section only applies when you're viewing the chat room
if(change_chat && window.location.href.indexOf("chat.aspx") > -1)
{
	var rules_banner = document.getElementById("divChatInformation");
	// this is the large grey banner at the bottom that simply contains a link to the chatroom rules and etiquette.
	// i simply get rid of it here, but an icon is generated to point to the same page and placed unobtrusively
	// next to the textbox you type into later
	if(exists(rules_banner) && rules_banner.tagName == "DIV") splode(rules_banner);
	// this whole css section is overriding the default styling of the page when viewing the chatroom to change a whole
	// bunch of things, including:
	// # changing the font of the whole chat interface
	// # extending the height of the userlist to be the same as the chatbox
	// # shrinking the vertical display slightly so it all fits on the page at the same time (i always had to scroll a
	//   bit to get to the textbox)
	// # extending the width of the input box
	// # giving the timestamp a fixed width
	// # giving the gamertag a fixed width
	// # positioning the buttons we'll be adding after the text input later
	var css = "div#divChatHolder {font-family: 'Droid Sans Mono',monospace !important} "+
		"div#divChatList {width: 175px !important; height: 450px !important;}" +
		"div#divChatBody {margin-right: 190px !important; height: 450px !important;} "+
		"input#txtChatMessage {width: 720px !important;} "+
		"span.chattime {display: inline-block !important; width: 65px !important; font-style: normal !important;} "+
		"span.gamertag {display: inline-block !important; width: 105px !important;} "+
		"a#btnClearChat {position: relative !important; top: -4px !important; right: auto !important; padding-right: 8px !important;} "+
		"img#btnChatRules {position: relative !important; top: -4px !important;} ";
	if(minimalize_chat)
	{
		// this section enforces additional options that leaves basically nothing on the page besides the chat
		// elements, collapsing vertical space and removing elements from the page to even fit the whole thing
		// on one screen and prevent vertical scrolling
		css = css +
			"div#main {padding: 10px 10px 0px 10px !important; min-height: 0 !important;} "+
			"div#page-wrap {padding-bottom: 0 !important;} "+
			"div#page {min-height: 0 !important;}";
		// the "header" variable points to the large bold text at the top of the page that says "TrueAchievements Chat"
		var header = document.getElementsByClassName("pagetitle")[0];
		if(exists(header) && header.tagName == "H1") splode(header);
		// the footer is a huge bar of information that stretches the page
		var footer = document.getElementById("footer-wrap");
		if(exists(footer) && footer.tagName == "DIV") splode(footer);
	}
	// simply injecting the above css rules into the document
	var head = document.getElementsByTagName("head")[0];
	var style = document.createElement("style");
	style.type = "text/css";
	style.innerHTML = css;
	head.appendChild(style);
	// this section moves the clear chat button and creates a new rules button to the right of the input box at the bottom
	var clear_chat = document.getElementById("btnClearChat");
	var rules_button = document.createElement("a");
	rules_button.href = "forumpolicy.aspx";
	rules_button.target = "_blank";
	rules_button.innerHTML = '<img src="images/itemflags/MainStoryline.png" id="btnChatRules" title="Chat Guidelines" alt="Chat Guidelines">';
	// chat_key is our reference for inserting the new buttons.  javascript DOM only provides functions to insert a new
	// element at the end of the whole container or insert the element before another element.  since chat_key appears
	// in the page code just after the send message button, it's perfect
	var chat_key = document.getElementById("divChatKey");
	if(exists(chat_key) && chat_key.tagName == "DIV")
	{
		if(exists(clear_chat) && clear_chat.tagName == "A")
		{
			// remove the clear chat button from its current location
			clear_chat.parentNode.removeChild(clear_chat);
			// insert the clear chat button before the icon key
			chat_key.parentNode.insertBefore(clear_chat,chat_key);
		}
		// insert the rule buttons just before the icon key (and just after the clear chat button)
		chat_key.parentNode.insertBefore(rules_button,chat_key);
		// remove the icon key element.  this same information is found in the chat rules anyway
		splode(chat_key);
	}
}