Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save m4chinations/f6d58711a94077d96cf4157665b0bab3 to your computer and use it in GitHub Desktop.

Select an option

Save m4chinations/f6d58711a94077d96cf4157665b0bab3 to your computer and use it in GitHub Desktop.
hackernews account age and karma userscript
// ==UserScript==
// @name HN User Info (Age & Karma)
// @namespace https://news.ycombinator.com/
// @version 1.0
// @description Shows account age (in days) and karma next to every username on Hacker News
// @match https://news.ycombinator.com/*
// @grant GM_xmlhttpRequest
// @grant GM.xmlHttpRequest
// @connect hacker-news.firebaseio.com
// ==/UserScript==
(function () {
'use strict';
// ── CONFIG ─────────────────────────────────────────────────────────
// Toggle which fields to show
const SHOW_AGE = true;
const SHOW_KARMA = true;
// Suffix strings (e.g. "d" → "5,055d", " days" → "5,055 days")
const AGE_SUFFIX = 'd';
const KARMA_SUFFIX = 'k';
// ──────────────────────────────────────────────────────────────────
// Cache fetched users so we don't re-fetch duplicates on the same page
const userCache = {};
// Calculate age in days from a Unix timestamp
function ageDays(createdUnix) {
const now = Date.now() / 1000;
return Math.floor((now - createdUnix) / 86400);
}
// Create the badge span that will be inserted after the username
function makeBadge(username) {
const span = document.createElement('span');
span.className = 'hn-userinfo-badge';
span.style.cssText = 'color:#828282; font-size:inherit; opacity:0.5;';
span.textContent = ' …';
span.dataset.user = username;
return span;
}
// Format a number with commas (e.g. 13394 → "13,394")
function fmt(n) {
return n.toLocaleString('en-US');
}
// Update every badge for a given username with the fetched data
function fillBadges(username, data) {
const days = ageDays(data.created);
const karma = data.karma;
const createdDate = new Date(data.created * 1000);
const dateStr = createdDate.toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric'
});
document.querySelectorAll(`.hn-userinfo-badge[data-user="${CSS.escape(username)}"]`).forEach(badge => {
badge.textContent = '';
badge.style.opacity = '1';
badge.style.transition = 'opacity 0.3s';
if (SHOW_AGE) {
const ageLabel = `${fmt(days)}${AGE_SUFFIX}`;
const ageSpan = document.createElement('span');
ageSpan.textContent = ` | ${ageLabel}`;
ageSpan.title = dateStr;
ageSpan.style.cssText = 'cursor:help; border-bottom:1px dotted #828282;';
badge.appendChild(ageSpan);
}
if (SHOW_KARMA) {
const karmaLabel = `${fmt(karma)}${KARMA_SUFFIX}`;
badge.appendChild(document.createTextNode(` | ${karmaLabel}`));
}
if (SHOW_AGE || SHOW_KARMA) {
badge.appendChild(document.createTextNode(' |'));
}
});
}
// Fetch user info from the public HN Firebase API
function fetchUser(username) {
if (userCache[username]) return; // already fetched or in-flight
userCache[username] = true; // mark in-flight
const url = `https://hacker-news.firebaseio.com/v0/user/${encodeURIComponent(username)}.json`;
// Use GM_xmlhttpRequest if available (Greasemonkey / Tampermonkey),
// otherwise fall back to plain fetch (Violentmonkey, etc.)
const gmXHR = typeof GM_xmlhttpRequest === 'function'
? GM_xmlhttpRequest
: (typeof GM !== 'undefined' && GM.xmlHttpRequest)
? GM.xmlHttpRequest
: null;
if (gmXHR) {
gmXHR({
method: 'GET',
url: url,
onload: function (resp) {
try {
const data = JSON.parse(resp.responseText);
if (data && data.created != null) {
userCache[username] = data;
fillBadges(username, data);
}
} catch (_) { /* silently ignore parse errors */ }
}
});
} else {
fetch(url)
.then(r => r.json())
.then(data => {
if (data && data.created != null) {
userCache[username] = data;
fillBadges(username, data);
}
})
.catch(() => {});
}
}
// Main: find all .hnuser links, inject badges, kick off fetches
function run() {
const userLinks = document.querySelectorAll('a.hnuser');
const seen = new Set();
userLinks.forEach(link => {
// Skip if we already processed this exact element
if (link.dataset.hnInfoDone) return;
link.dataset.hnInfoDone = '1';
// Extract username from href like "user?id=fulafel"
const match = link.getAttribute('href')?.match(/user\?id=([^&]+)/);
if (!match) return;
const username = match[1];
// Insert a badge span right after the username link
const badge = makeBadge(username);
link.after(badge);
// Queue a fetch (deduped)
if (!seen.has(username)) {
seen.add(username);
fetchUser(username);
}
});
}
// Run on page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run);
} else {
run();
}
})();
@m4chinations
Copy link
Author

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment