Skip to content

Instantly share code, notes, and snippets.

@BrockA
Last active December 14, 2015 04:51
Show Gist options
  • Save BrockA/d54dfda1c9ba7434209d to your computer and use it in GitHub Desktop.
Save BrockA/d54dfda1c9ba7434209d to your computer and use it in GitHub Desktop.
Stack Exchange, Winterbash, Hat Overflow
// ==UserScript==
// @name Stack Exchange, Winterbash, Hat Overflow
// @description Adds how many Winterbash hats each user has, into the "signature blocks" of posts.
// @match *://*.askubuntu.com/*
// @match *://*.mathoverflow.net/*
// @match *://*.onstartups.com/*
// @match *://*.serverfault.com/*
// @match *://*.stackapps.com/*
// @match *://*.stackexchange.com/*
// @match *://*.stackoverflow.com/*
// @match *://*.superuser.com/*
// @require http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @version 2.4
// @history 2.4 Update icon image and check against WB 2015.
// @history 2.3 Add support for pre Winterbash countdown.
// @history 2.2 Warn user when Winterbash site is off (Hard 404).
// @history 2.1 Stop script when Winterbash not in season (rough check).
// @history 2.0 Enabled for all pages with standard user signatures, not just Q and A posts.
// @history 1.2 Fixed problem with site metas.
// @history 1.1 Better scanability of count display
// @history 1.0 Initial release
// @updateURL https://gist.github.com/BrockA/d54dfda1c9ba7434209d/raw/hatoverflow.user.js
// @downloadURL https://gist.github.com/BrockA/d54dfda1c9ba7434209d/raw/hatoverflow.user.js
// ==/UserScript==
var cntDwnIcon = "//i.stack.imgur.com/O02fU.png"; //-- white 48px
var usrWB_Icon = "//i.stack.imgur.com/ubhII.png"; //-- gray 28px
var usrWB_Icon = "//i.stack.imgur.com/otFAt.png"; //-- gray 14px
var zDateObj = new Date ();
var monthNow = zDateObj.getMonth ();
//--- If out of Winterbash season (Dec to Jan), abort script.
if (monthNow !== 11 && monthNow !== 0)
return;
var storePrefix = "WBU_";
var userRegEx = new RegExp ('^' + storePrefix + '\\d+$', 'i');
var hatCounts = {}; //-- [WBU_<userid>: hatCount]
var timeNow = Math.round ( ( zDateObj.getTime () ) / 1000);
var timeExpire = timeNow - 7200;
var baseAPI_Link = determineAPI_BaseLink ();
function determineAPI_BaseLink () {
/*--- Fetch hat data.
Like http://winterbash2014.stackexchange.com/api/user-hats?userId=262509&host=meta.stackexchange.com
*/
var apiDomain = location.hostname.replace (/^meta\.(.+)$/i, "$1");
if (apiDomain == "stackexchange.com")
apiDomain = "meta.stackexchange.com";
//-- Winterbash season runs from Dec to Jan.
var tYear = zDateObj.getFullYear ();
if (monthNow === 0)
tYear--;
var baseLink = 'http://winterbash' + tYear + '.stackexchange.com/api/user-hats?host='
+ apiDomain + '&userId='
;
return baseLink;
}
//--- Get data and flush expired.
for (var J = localStorage.length - 1; J >= 0; --J) {
var itemName = localStorage.key (J);
if (userRegEx.test (itemName) ) {
var usrData = localStorage.getItem (itemName);
if (usrData)
usrData = JSON.parse (usrData);
else
continue;
var timeStd = parseInt (usrData[1], 10);
if (timeStd < timeExpire)
localStorage.removeItem (itemName);
else
hatCounts[itemName] = usrData[0];
}
}
//--- Get and place hat counts.
waitForKeyElements (".post-signature", processUserSignature);
function processUserSignature (jNode) {
var userID = 0;
var userLnk = jNode.find (".user-details > a");
if (userLnk.length)
userID = parseInt (userLnk[0].href.replace (/^.+?\/users\/([-\d]+)\/.+$/, "$1"), 10); // -1 == Community account
var userKey = storePrefix + userID;
var numHats = hatCounts[userKey];
var insPnt = jNode.find (".reputation-score");
insPnt.append (' <span class="gmWBstar"></span> <span>??</span>');
var rezDisp = jNode.find (".gmWBstar + span");
if (typeof numHats !== "undefined") {
rezDisp.text (numHats);
}
else if (rezDisp.length) {
var userHatLink = baseAPI_Link + userID;
GM_xmlhttpRequest ( {
method: 'GET',
url: userHatLink,
context: { k: userKey, nd: rezDisp[0] },
onload: updateHatCountForA_User,
onabort: reportAJAX_Error,
onerror: reportAJAX_Error,
ontimeout: reportAJAX_Error
} );
}
}
function updateHatCountForA_User (rspObj) {
if (rspObj.status != 200 && rspObj.status != 304) {
reportAJAX_Error (rspObj);
return;
}
var numHats = 0;
try {
var hatArray = JSON.parse (rspObj.responseText);
}
catch (err) {
//--- Not valid JSON, is countdown timer present?
var msToStart = rspObj.responseText.match (/countdown.+?getTime.+?(\d+)/);
if (msToStart && msToStart.length > 1) {
msToStart = parseInt (msToStart[1], 10);
if (msToStart > 0) {
var secsToStart = Math.round (msToStart / 1000);
var hrsToStart = Math.floor (secsToStart / 3600);
var minsToStart = Math.round ( (secsToStart - 3600 * hrsToStart) / 60 );
var dispCntDwn = hrsToStart + ':' + ("0" + minsToStart).slice (-2); //-- zero pad minutes
$(rspObj.context.nd).prev (".gmWBstar").addBack ().remove ();
var cdDispNode = $("#gmWbCdDisp");
if (cdDispNode.length === 0)
cdDispNode = $(
'<div id="gmWbCdDisp" title="Countdown to Winter Bash start"><img src="' + cntDwnIcon + '"><span></span></div>'
).appendTo ("body");
cdDispNode.find ("span").text (dispCntDwn);
return;
}
}
console.error (err);
return;
}
if ( ! Array.isArray (hatArray) ) {
console.error ('GM: Error: Unexpected result from Winterbash API.', rspObj.responseText.substr (0,80) + "..." );
}
else {
numHats = hatArray.length;
// FUTURE: Maybe get names and pics of hats for flyover display.?
}
var userKey = rspObj.context.k;
var ndToChnge = rspObj.context.nd;
//--- Update array and storage.
hatCounts[userKey] = numHats;
var usrData = JSON.stringify ( [numHats, timeNow] );
localStorage.setItem (userKey, usrData);
ndToChnge.textContent = numHats;
}
function reportAJAX_Error (rspObj) {
if (rspObj.response && /Site not found: winterbash/i.test (rspObj.response) ) {
if ( $("body > h2.gmWBerror").length === 0) {
$("body").prepend ('<h2 class="gmWBerror">The Winterbash site is off. Update or disable this "Hat Overflow" script.</h2>');
}
}
console.error ('GM: Error: ' + rspObj.status + '! &nbsp; "' + rspObj.statusText + '".');
}
GM_addStyle ( multilineStr ( function () { /*!
.gmWBstar {
background: url("\/\/i.stack.imgur.com/otFAt.png");
display: inline-block;
height: 15px;
vertical-align: top;
width: 14px;
}
.gmWBstar + span {
margin: auto 0.5ex auto -0.4ex;
}
.gmWBerror {
background: red;
color: black;
}
#gmWbCdDisp {
position: absolute;
top: 0;
right: 1ex;
background: black;
color: white;
}
#gmWbCdDisp > img {
height: 20px;
width: 20px;
margin: 1ex 1ex 0 0;
}
#gmWbCdDisp > span {
font-weight: bold;
vertical-align: super;
}
*/} ) );
function multilineStr (dummyFunc) {
var str = dummyFunc.toString ();
str = str.replace (/^[^\/]+\/\*!?/, '') // Strip function() { /*!
.replace (/\s*\*\/\s*\}\s*$/, '') // Strip */ }
.replace (/\/\/.+$/gm, '') // Double-slash comments wreck CSS. Strip them.
;
return str;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment