Adds an estimate of "hotness" to the question sidebar, calculated using the formula from http://meta.stackexchange.com/a/61343. Questions with a high hotness value may be selected for the Hot Network Questions list. Note that the hotness value displayed by this script does not include the per-site scaling factors, and so does not match the "arbi…
// ==UserScript== | |
// @name Stack Exchange hotness estimator | |
// @namespace http://vyznev.net/ | |
// @description Estimates how highly each Stack Exchange question would rank on the Hot Network Questions list | |
// @author Ilmari Karonen | |
// @version 0.4.2 | |
// @license Public domain | |
// @homepageURL http://meta.stackexchange.com/a/284933 | |
// @match *://*.stackexchange.com/questions/* | |
// @match *://*.stackoverflow.com/questions/* | |
// @match *://*.superuser.com/questions/* | |
// @match *://*.serverfault.com/questions/* | |
// @match *://*.stackapps.com/questions/* | |
// @match *://*.mathoverflow.net/questions/* | |
// @match *://*.askubuntu.com/questions/* | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
var voteCount = document.querySelector('#question .js-vote-count'); | |
var infobar = document.querySelector('#question-header + .grid'); // no id :( | |
if ( ! voteCount || ! infobar ) return; | |
var creationTimeStamp = infobar.querySelector('time[itemprop=dateCreated]'); | |
if ( ! creationTimeStamp ) return; | |
var now = new Date (); | |
var questionScore = Number(voteCount.textContent); | |
var qCreationTime = new Date(creationTimeStamp.getAttribute('datetime')); | |
var qAgeInHours = (now.getTime() - qCreationTime.getTime()) / (1000*60*60); | |
var answerScores = document.querySelectorAll('.answer:not(.deleted-answer) .js-vote-count'); | |
var answerCount = answerScores.length; | |
var answerScore = 0; | |
for (var i = 0; i < answerScores.length; i++) answerScore += Number(answerScores[i].textContent); | |
// http://meta.stackexchange.com/questions/60756/how-do-the-arbitrary-hotness-points-work-on-the-new-stack-exchange-home-page | |
// The conversion to percentages below is completely arbitrary, but seems to yield reasonable-looking values (i.e. around 100% for actual HNQs). | |
// Note: For questions that are ineligible for HNQ due to being less than 8 hours old, the hotness is calculated as if their age was 8 hours. | |
var hotness = ((Math.min(answerCount, 10) * questionScore) / 5 + answerScore) / Math.pow(Math.max(qAgeInHours, 8) + 1, 1.4); | |
var ineligibleBecause = []; | |
if (answerCount < 1) ineligibleBecause.push('no answers'); | |
if (qAgeInHours < 8) ineligibleBecause.push('age < 8h'); | |
if (qAgeInHours > 30*24) ineligibleBecause.push('age > 30d'); | |
var mathJaxInTitle = document.querySelector('#question-header h1[itemprop=name] script[type*=math]'); | |
if (mathJaxInTitle) ineligibleBecause.push('MathJax in title'); | |
if (/(^|\.)meta\./.test(location.hostname)) ineligibleBecause.push('on meta site'); | |
if (location.hostname === 'stackapps.com') ineligibleBecause.push('on StackApps'); | |
if ((window.StackExchange?.options?.locale || 'en') !== 'en') ineligibleBecause.push('not English'); | |
var noticeHeaders = document.querySelectorAll('#question aside.s-notice[role=status] b'); | |
if (Array.prototype.some.call(noticeHeaders, e => /\b(closed|on hold|duplicate|already has answers)\b/i.test(e.textContent))) ineligibleBecause.push('closed') | |
if (document.querySelector('#question.deleted-answer')) ineligibleBecause.push('deleted'); | |
// Checks for other ineligibility reasons could be added here, but would require scraping additional data off the page or getting it from the SE API. | |
var title = 'The relative hotness score of this question is approximately ' + hotness.toPrecision(5) + '.'; | |
if (ineligibleBecause.length > 0) title += ' (Not eligible for HNQ because: ' + ineligibleBecause.join(', ') + '.)'; | |
var oldRow = infobar.firstElementChild, newRow = oldRow.cloneNode(false), keySpan = oldRow.firstElementChild.cloneNode(false); | |
keySpan.textContent = 'Hotness'; | |
newRow.innerHTML = ' <span>' + Math.round(100 * hotness) + '%</span>'; | |
if (ineligibleBecause.length > 0) newRow.firstElementChild.style.textDecoration = 'line-through'; | |
newRow.insertBefore(keySpan, newRow.firstChild); | |
newRow.setAttribute('title', title); | |
infobar.lastElementChild.className += ' mr16'; | |
infobar.appendChild(newRow); | |
} )(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment