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