Skip to content

Instantly share code, notes, and snippets.

@vyznev
Last active November 3, 2022 00:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save vyznev/bb2c1adb6e96eb65a87bab3822c74e81 to your computer and use it in GitHub Desktop.
Save vyznev/bb2c1adb6e96eb65a87bab3822c74e81 to your computer and use it in GitHub Desktop.
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.3
// @license Public domain
// @homepageURL https://meta.stackexchange.com/a/284933
// @downloadURL https://gist.github.com/vyznev/bb2c1adb6e96eb65a87bab3822c74e81/raw/se_hotness_estimator.user.js
// @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 + div'); // no id :(
if ( ! voteCount || ! infobar || /(mainbar|sidebar)/.test(infobar.id) ) 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