// ==UserScript== // @author Mobius Evalon // @name TrueAchievements improver // @description Improves and tinkers with the TrueAchievements website in various ways. // @version 2.4 // @namespace mobiusevalon.tibbius.com // @license Creative Commons Attribution-ShareAlike 4.0; http://creativecommons.org/licenses/by-sa/4.0/ // @require https://code.jquery.com/jquery-1.12.4.min.js // @require https://code.jquery.com/ui/1.11.4/jquery-ui.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.26.0/js/jquery.tablesorter.min.js // @include /^https{0,1}:\/\/\w{0,}\.?trueachievements\.com.*$/ // @grant none // @downloadURL https://update.greasyfork.icu/scripts/8533/TrueAchievements%20improver.user.js // @updateURL https://update.greasyfork.icu/scripts/8533/TrueAchievements%20improver.meta.js // ==/UserScript== this.$ = this.jQuery = jQuery.noConflict(true); var chained_functions = { "tinker_tokens":function() { $("#tinker_output").val(new Date().getTokenizedOutput($("#tinker_input").val())); }, "matrix_preview":function() { if(uri() === "customize.aspx") return $("").attr({ "src":"https://i.imgur.com/2nlpCY9.png", "title":"Sample session matrix", "alt":"Sample session matrix" }) .css({ "display":"block", "margin":"0px auto" }); } }; var help_topics = { tai_longdate: "

The letters here are tokens that expand to date information. They use the same tokens as PHP's date() function, if you're familiar with it.

"+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ "
TokenInfo
dThe day of the month with leading zeroes
DThe name of the day, three letter abbreviation
jThe day of the month, no leading zeroes
lThe name of the day, full text
SOrdinal suffix for the day of the month (the 'th' in 5th or the 'st' in 1st)
FThe name of the month, full text
mThe month number with leading zeroes
MThe name of the month, three letter abbreviation
nThe month number, no leading zeroes
YThe year, four digits
yThe year, two digits
aLowercase meridiem (am/pm)
AUppercase meridiem (AM/PM)
gThe hour in the 12-hour meridiem rotation, no leading zeroes
GThe hour in the 24-hour military notation, no leading zeroes
hThe hour in the 12-hour meridiem rotation with leading zeroes
HThe hour in the 24-hour military notation with leading zeroes
iMinutes with leading zeroes
sSeconds with leading zeroes
[ ]A proprietary format that 'fuzzies' the tokens inside
"+ "

If we assume it is currently Saturday April 30th at 1:15 PM, the token string 'D d M, H:i' would give you 'Sat 30 Apr, 13:15'. If you use angle brackets you can fuzzy the date again. For instance, '[D d M], H:i' would give you 'Today, 13:15'.

"+ "

All unrecognized letters will be left alone, so you can insert commas or other symbols as you please.

"+ "

You can tinker with the tokens in this textbox. It will output the current date and time information on your system when used.

"+ "

", tai_session_matrix: ""+ "

The achievement matrix exists to help players in a session keep track of each others' progress, especially for the host of such sessions. The matrix will only appear in sessions that you have joined and that have more than one achievement in the session.

"+ "

The matrix will not update until you click the button. This is because the code for the matrix must perform what is known as an XHR to load the complete achievement page for each gamer to update the display, and requiring a button click means you will not spam the TrueAchievements server with requests that you did not want to execute (such as looking at a past session.)

"+ "

The achievements that this session was set up for appear along the top of the matrix. Hovering over them will provide a tooltip with the achievement description, and clicking on one will open a new page for the achievement's solutions.

"+ "

Clicking on a gamertag will compare your achievements in this game to theirs, unless you are clicking on your own gamertag in which case your achievement list for this game will be opened. Gamers that are not confirmed appear greyed out. This happens when the gamer either has a pending application or they are a reserve for the session.

"+ "

Gamer has unlocked this achievement.
Gamer has not unlocked this achievement.
It is not known if the gamer has this achievement.
Achievements that are found to be owned by the script cannot be interacted with, but all unearned and unknown cells can be clicked to toggle the display. This is to help you keep track during the session if you so choose. Be aware that refreshing the matrix will discard the status of all cells that you have manually toggled.

"+ "

Gamer has 5% or less total poor feedback votes.
Gamer has between 5.1% and 14.9% poor feedback votes.
Gamer has 15% or greater poor feedback votes.
\"Poor\" feedback votes are the sum of all neutral and negative votes. Hover over the smiley icon to see the complete feedback information.

"+ "

Gamer has cheated several games in the past but has not surpassed an arbitrary egregiousness threshold to have all of their achievements removed from the site.
Gamer cheats egregiously and TrueAchievements has removed all of their statistics and achievements. The achievements for these users will never update in the matrix.

" }; var tai_css = "#tai_help_box {background-color: #e3e3e3; color: #000; width: 650px; height: 450px; position: fixed !important; top: 15vh; left: 25vw; z-index: 100; display: none; border-radius: 8px; border: 2px solid #000; overflow-x: hidden; overflow-y: scroll; cursor: move;} "+ "#tai_help_box h1 {display: block; padding: 0px; margin: 5px 0px; font-size: 125%; text-align: center;} "+ "#tai_help_box .content {padding: 4px;} "+ "div.spoiler {border: 1px solid #000 !important; border-radius: 5px !important;} "+ "div.spoiler span.tai_spoiler_header {display: inline-block; width: 100%; cursor: pointer; font-weight: bold; text-align: center;} "+ "a.tai_video_url_header {display: block; margin: 0px auto; text-align: center; font-weight: bold;} "+ "div.Comment img.tai_permalink_icon {float: right !important; width: 16px !important; height: 16px !important; border: none !important; padding: 0px !important; margin: 8px 2px 0px 5px !important;} "+ "#tai_flag_button {margin-left: 0px !important;} "+ "#tai_flag_button .droparrow {margin-left: 8px;} "+ ".tai_pagetitle {margin: 0px !important;} "+ ".tai_won_options, #tai_filter_buttons_container {margin: 0px 0px 10px 0px !important;} "+ "#tai_filter_buttons_container a.button {position: relative !important;} "+ ".tai_won_options td {padding-bottom: 0px !important;} "+ ".tai_link_label {float: none !important; display: inline-block !important; width: 40px !important; margin: 0px 10px 0px 0px !important;} "+ ".tai_achievement_ownership_filtered {display: none !important;} "+ ".tai_warning_panel {cursor: pointer;} "+ "#btnFlagFilter_Options, #tai_flag_filter {z-index: 100 !important;} "+ "#menu_click_fallthrough {position: fixed; top: 0px; left: 0px; bottom: 0px; right: 0px; min-width: 100vw; min-height: 100vh; z-index: 75; display: none;} "+ ".tai_help_button {display: inline-block; margin: 8px 0px 0px 4px; float: left; cursor: pointer;} "+ "#tai_help_date_tokens table {border: 1px solid #000; margin-bottom: 10px;} "+ "#tai_help_date_tokens table thead th {font-weight: bold; background-color: #000; color: #cdcdcd;} "+ "#tai_help_date_tokens table tbody tr td {vertical-align: middle !important;} "+ "#tai_help_date_tokens table tbody tr td:nth-child(1) {width: 50px !important; text-align: center;} "+ "#tai_help_date_tokens table tbody tr {border-top: 1px solid #000;} "+ "#tai_help_date_tokens input[type=text] {width: 125px !important;} "+ "#tai_help_box #tai_close_help {position: absolute; top: 0px; left: 0px; padding: 3px; font-wight: bold; font-size: 115%;} "+ ".tai_hide_row {margin-left: 10px; cursor: pointer;} "+ ".tai_gwg {background-color: #ecf198 !important;} "+ ".tai_gold {background-color: #98a7f1 !important;} "+ ".tai_approx {background-color: #f19898 !important;} "+ ".tablesorter-header {cursor: pointer; min-width: 40px;} "+ ".tablesorter-headerDesc {background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); background-repeat: no-repeat; background-position: right;} "+ ".tablesorter-headerAsc {background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); background-repeat: no-repeat; background-position: right;} "+ ".asc-arrow-black {background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); background-repeat: no-repeat; background-position: center; float: none !important; display: inline-block !important;} "+ ".desc-arrow-black {background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); background-repeat: no-repeat; background-position: center; float: none !important; display: inline-block !important;} "+ "#tai_flag_filter li.fp div.achievement-sort .asc-arrow-black, #tai_flag_filter li.fp div.achievement-sort .desc-arrow-black {margin: 0px 0px 0px 15px !important; border: 1px solid #000; border-radius: 2px; padding: 0px !important; width: 15px !important; height: 15px !important; } "+ "#tai_achievement_matrix table {margin: 0px auto;} "+ "#tai_achievement_matrix thead th {padding: 0px 5px 8px 5px;} "+ "#tai_achievement_matrix td.actions img {display: inline-block; margin-right: 5px; vertical-align: middle;} "+ "#tai_achievement_matrix td.gamertag .cheater-icon {display: inline-block; margin: 0px 4px; width: 16px; height: 16px; line-height: 16px; text-align: center;} "+ "#tai_achievement_matrix thead th a {display: inline-block; white-space: nowrap; width: 16px; transform: translate(50%,15%) rotate(45deg); transform-origin: 0% 0%; direction: rtl; unicode-bidi: plaintext;} "+ "#tai_achievement_matrix tbody td.actions {vertical-align: middle; text-align: right;} "+ "#tai_achievement_matrix tbody td.gamertag {vertical-align: middle; text-align: right;} "+ "#tai_achievement_matrix .not_owned, #tai_help_box .not_owned {background-color: #f5c6c6; border: 1px solid #000;} "+ "#tai_achievement_matrix .owned, #tai_help_box .owned {background-color: #c6f5c6; border: 1px solid #000;} "+ "#tai_achievement_matrix .unknown, #tai_help_box .unknown {background-color: #c6c6c6; border: 1px solid #000;} "+ "#tai_achievement_matrix .cheater {text-decoration: line-through;} "+ "#tai_achievement_matrix .applied, #tai_achievement_matrix .reserve, #tai_help_box .applied {opacity: 0.5;} "+ "#tai_achievement_matrix .confirmed {} "+ "#tai_achievement_matrix .status, #tai_achievement_matrix .icon-spacer-16, #tai_help_box .icon-spacer-16 {display: inline-block; width: 16px !important; height: 16px !important; margin-right: 5px; vertical-align: middle;} "+ "#tai_achievement_matrix .status, #tai_achievement_matrix .icon-spacer-24, #tai_help_box .icon-spacer-24 {display: inline-block; width: 24px !important; height: 24px !important; margin-right: 5px; vertical-align: middle;} "+ "#tai_achievement_matrix th.functions img {margin-right: 5px; cursor: pointer;} "+ ".tai_game_tile_40px {height: 40px; width: 40px;} "+ ".noscroll {overflow: hidden;} "+ "#tai_solution_sort {margin: 0px 10px !important; width: auto !important;} "+ "#tai_sort_dir {cursor: pointer;} "+ ".tai_hide_solution {cursor: pointer;} "+ ".tai_float_bookmark {position: absolute; margin-left: -15px;} "+ ".tai_pagination_wait {position: absolute; right: 0px;} "+ ".tai-hidden-post {border-top: 15px solid #eee; background-color: #f5c6c6;} "+ ".tai-hidden-quote {margin-bottom: 25px; background-color: #f5c6c6; text-align: center; border: 1px solid #c29d9d; border-radius: 5px;} "+ ".spin-anim {animation-name: spin-anim; animation-duration: 1500ms; animation-iteration-count: infinite; animation-timing-function: linear;} "+ "@keyframes spin-anim {from {transform:rotate(0deg);} to {transform:rotate(360deg);}} "; Date.prototype.short_date = true; Date.prototype.date_time = true; Date.prototype.getMonthString = function() { switch(this.getMonth()) { case 0: return "January"; case 1: return "February"; case 2: return "March"; case 3: return "April"; case 4: return "May"; case 5: return "June"; case 6: return "July"; case 7: return "August"; case 8: return "September"; case 9: return "October"; case 10: return "November"; case 11: return "December"; } }; Date.prototype.getShortMonthString = function() { return this.getMonthString().slice(0,3); }; Date.prototype.getDayString = function() { switch(this.getDay()) { case 0: return "Sunday"; case 1: return "Monday"; case 2: return "Tuesday"; case 3: return "Wednesday"; case 4: return "Thursday"; case 5: return "Friday"; case 6: return "Saturday"; } }; Date.prototype.getShortDayString = function() { return this.getDayString().slice(0,3); }; Date.prototype.getTwoDigitHours = function() { return zero_pad(this.getHours(),2); }; Date.prototype.getTwoDigitMinutes = function() { return zero_pad(this.getMinutes(),2); }; Date.prototype.getTwoDigitSeconds = function() { return zero_pad(this.getSeconds(),2); }; Date.prototype.getTwoDigitDate = function() { return zero_pad(this.getDate(),2); }; Date.prototype.getShortYear = function() { return (((""+this.getFullYear()).slice(-2))*1); }; Date.prototype.getMeridiem = function() { return ((this.getHours() > 11) ? "pm" : "am"); }; Date.prototype.getMeridiemHours = function() { var h = this.getHours(); if(h > 12) h -= 12; return h; }; Date.prototype.getTwoDigitMeridiemHours = function() { return zero_pad(this.getMeridiemHours(),2); }; Date.prototype.adjustDate = function(n) { this.setDate(this.getDate()+n); }; Date.prototype.getDateAfterAdjustment = function(n) { this.adjustDate(n); return this.getDate(); }; Date.prototype.getFuzzyDay = function() { var d = new Date(); if(d.getFullYear() === this.getFullYear() && d.getMonth() === this.getMonth()) { if(d.getDate() === this.getDate()) return "Today"; else if(d.getDateAfterAdjustment(1) === this.getDate()) return "Tomorrow"; else if(d.getDateAfterAdjustment(-2) === this.getDate()) return "Yesterday"; } return ""; }; Date.prototype.outputLongDate = function() { return this.getTokenizedOutput(script_settings.long_date_format); }; Date.prototype.outputShortDate = function() { return this.getTokenizedOutput(script_settings.short_date_format); }; Date.prototype.outputLongDateTime = function() { return (this.outputLongDate()+", "+this.outputTime()); }; Date.prototype.outputShortDateTime = function() { return (this.outputShortDate()+", "+this.outputTime()); }; Date.prototype.outputTime = function() { return this.getTokenizedOutput(script_settings.time_format); }; Date.prototype.getTokenizedOutput = function(t) { var r = "", i = -1; while(i++ < t.length) { switch(t.charAt(i)) { case 'd': {r += this.getTwoDigitDate(); break;} case 'D': {r += this.getShortDayString(); break;} case 'j': {r += this.getDate(); break;} case 'l': {r += this.getDayString(); break;} case 'S': {r += this.ordinalSuffix(this.getDate()); break;} case 'F': {r += this.getMonthString(); break;} case 'm': {r += zero_pad(this.getMonth()+1,2); break;} case 'M': {r += this.getShortMonthString(); break;} case 'n': {r += (this.getMonth()+1); break;} case 'Y': {r += this.getFullYear(); break;} case 'y': {r += this.getShortYear(); break;} case 'a': {r += this.getMeridiem(); break;} case 'A': {r += this.getMeridiem().toUpperCase(); break;} case 'g': {r += this.getMeridiemHours(); break;} case 'G': {r += this.getHours(); break;} case 'h': {r += this.getTwoDigitMeridiemHours(); break;} case 'H': {r += this.getTwoDigitHours(); break;} case 'i': {r += this.getTwoDigitMinutes(); break;} case 's': {r += this.getTwoDigitSeconds(); break;} case '[': var e = t.indexOf(']',i), f = this.getFuzzyDay(); if(e > i && f.length) { r += f; i = e; } break; case ']': break; default: {r += t.charAt(i); break;} } } return r; }; Date.prototype.ordinalSuffix = function(n) { n = Math.floor(n*1); var ord = "th"; switch(n) { case 1: case 21: case 31: ord = "st"; break; case 2: case 22: ord = "nd"; break; case 3: case 23: ord = "rd"; break; } return ord; }; Date.prototype.toString = function() { var r = ""; if(!this.short_date || script_settings.always_long_date) r += this.outputLongDate(); else r += this.outputShortDate(); if(this.date_time) r += (", "+this.outputTime()); return r; }; Array.prototype.removeItem = function(a) { var i = this.indexOf(a); if(i > -1) this.splice(i,1); }; function datetime_master(obj) { var date = new Date(), short = false, time = true; if(!obj.hasOwnProperty("hour") || obj.hour === undefined) {obj.hour = "00"; time = false;} if(!obj.hasOwnProperty("minute") || obj.minute === undefined) obj.minute = "00"; if(!obj.hasOwnProperty("year") || obj.year === undefined) {obj.year = date.getFullYear(); short = true;} else if(obj.year.length === 2) obj.year = ("20"+obj.year); if(!obj.hasOwnProperty("month") || obj.month === undefined) obj.month = date.getShortMonthString(); if($.type(obj.day) === "string") obj.day = obj.day.toLowerCase(); if(!obj.hasOwnProperty("day") || obj.day === undefined || obj.day === "today") obj.day = date.getDate(); else if(obj.day === "yesterday") obj.day = date.getDateAfterAdjustment(-1); else if(obj.day === "tomorrow") obj.day = date.getDateAfterAdjustment(+1); date = new Date(obj.month+" "+obj.day+" "+obj.year+" "+obj.hour+":"+obj.minute); date.short_date = short; date.date_time = time; return date; } function short_datetime_callback() { // mon, jan 1 1900 at 00:00 var len = arguments.length, date = datetime_master({year:arguments[len-5],month:arguments[len-6],day:(arguments[len-7]||arguments[len-8]),hour:arguments[len-4],minute:arguments[len-3]}); return date.toString(); } function short_date_callback() { // 1 jan 1900 var len = arguments.length, date = datetime_master({year:arguments[len-3],month:arguments[len-4],day:(arguments[len-5]||arguments[len-6])}); return date.toString(); } function zero_pad(a,l) { var r = (""+a); while(r.length < l) r = ("0"+r); return r; } function plural(n) { n = Math.round(n*1); if(n != 1) return "s"; return ""; } function uri() { if(/\/n\d{3,6}\//i.test(window.location.href)) return "news"; if(/\/a\d{3,6}\//i.test(window.location.href)) return "solutions"; if(/\/[^/]+-walkthrough\.htm/i.test(window.location.href)) return "walkthrough.htm"; var m = window.location.href.match(/\/([\w-]*\.(?:aspx|htm))(?:\?|#|$)/i); if(m !== null) return m[1]; return ""; } function json_obj(json) { var obj; if(typeof json === "string") { try {obj = JSON.parse(json);} catch(e) {console.log("Malformed JSON object. Error message from JSON library: ["+e.message+"]");} } return obj; } function localstorage_obj(key) { var obj = json_obj(localStorage.getItem(key)); if(typeof obj !== "object") localStorage.removeItem(key); return obj; } function default_settings() { return { hide_ads:true, normalize_dates:true, improve_chat:true, session_userlinks_compare:true, session_hide_user_statuses:true, feed_permalinks:true, fix_spoilers:true, settings_page_filter:true, display_video_links:true, short_date_format:"D d M", long_date_format:"d M Y", time_format:"H:i", always_long_date:false, solution_list_filters:true, achievement_page_improvements:true, session_achievement_matrix:true, solution_sorting:true, walkthrough_bookmarks:true, show_all_pages:true, block_users:true }; } function store_settings() { localStorage.setItem("ta_improver_settings",JSON.stringify({ hide_ads:checkbox("#tai_hide_ads"), normalize_dates:checkbox("#tai_dates"), improve_chat:checkbox("#tai_improve_chat"), session_userlinks_compare:checkbox("#tai_session_compare"), session_hide_user_statuses:checkbox("#tai_user_statuses"), feed_permalinks:checkbox("#tai_permalinks"), fix_spoilers:checkbox("#tai_spoilers"), settings_page_filter:checkbox("#tai_filter"), display_video_links:checkbox("#tai_video_links"), short_date_format:$("#tai_shortdate").val(), long_date_format:$("#tai_longdate").val(), time_format:$("#tai_time").val(), always_long_date:checkbox("#tai_always_longdate"), solution_list_filters:checkbox("#tai_solution_list_filter"), achievement_page_improvements:checkbox("#tai_improve_achievement_list"), session_achievement_matrix:checkbox("#tai_session_matrix"), solution_sorting:checkbox("#tai_sort_solutions"), walkthrough_bookmarks:checkbox("#tai_walkthrough_bookmarks"), show_all_pages:checkbox("#tai_show_all_pages"), block_users:checkbox("#tai_block_users") })); } function checkbox(id) { return ($("#divImprover "+id).prop("checked") === true); } function script_setting_display(id,type,value,name,tooltip) { var $element; if(type === "checkbox") $element = $("") .attr({ "id":id, "type":"checkbox" }) .prop("checked",(value === true)); // fast short circuit. if the value is boolean and true, the box is checked. all other circumstances evaluate to boolean false (not checked) if(type === "textbox") $element = $("") .attr({ "id":id, "type":"text" }) .val(value); var $elem = $("
") .append( $("