Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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