Last active
December 14, 2015 04:51
-
-
Save BrockA/d54dfda1c9ba7434209d to your computer and use it in GitHub Desktop.
Stack Exchange, Winterbash, Hat Overflow
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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 + '! "' + 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