|
// ==UserScript== |
|
// @name Hacker News – Display user karma next to usernames |
|
// @version 1.0 |
|
// @description Display Hacker News user karma next to usernames using the HN Firebase API, cache results 1d. |
|
// @homepageURL https://gist.github.com/corentinbettiol/289503b4033a788df91fbc2bc5a3f742#file-karma-user-js |
|
// @match https://news.ycombinator.com/* |
|
// @grant GM.getValue |
|
// @grant GM.setValue |
|
// @grant GM.xmlHttpRequest |
|
// @connect hacker-news.firebaseio.com |
|
// ==/UserScript== |
|
|
|
(async () => { |
|
'use strict'; |
|
|
|
const API = 'https://hacker-news.firebaseio.com/v0/user/'; |
|
const TTL = 86400000; // 1d |
|
|
|
const sleep = ms => new Promise(r => setTimeout(r, ms)); |
|
|
|
const getKarma = username => new Promise((resolve, reject) => { |
|
GM.xmlHttpRequest({ |
|
method: 'GET', |
|
url: `${API}${username}.json`, |
|
onload: r => { |
|
try { |
|
const data = JSON.parse(r.responseText); |
|
resolve(data?.karma ?? null); |
|
} catch { reject(); } |
|
}, |
|
onerror: reject |
|
}); |
|
}); |
|
|
|
const getCached = async u => { |
|
const e = await GM.getValue(u); |
|
return (e && Date.now() - e.t < TTL) ? e.k : null; |
|
}; |
|
|
|
const setCached = (u, k) => |
|
GM.setValue(u, { k, t: Date.now() }); |
|
|
|
const styleKarma = (span, karma) => { |
|
const k = Math.log10(karma + 1); |
|
|
|
// color (log scale tiers) |
|
span.style.color = |
|
k < 2 ? '#bbb' : |
|
k < 3 ? '#999' : |
|
k < 4 ? '#cc8400' : |
|
k < 4.5 ? '#ff8800' : |
|
'#ff6600'; |
|
|
|
// glow |
|
if (karma > 50000) { |
|
span.style.textShadow = |
|
'0 0 8px #ffd700, 0 0 16px rgba(255,215,0,0.6)'; |
|
} else if (karma > 1500) { |
|
const t = Math.min(karma, 30000) / 30000; |
|
span.style.textShadow = |
|
`0 0 ${1 + 4*t}px rgba(255,102,0,${0.15 + 0.25*t})`; |
|
} |
|
}; |
|
|
|
const links = [...document.querySelectorAll('a[href^="user?id="]')]; |
|
const users = {}; |
|
|
|
for (const link of links) { |
|
const u = link.textContent.trim(); |
|
if (!u) continue; |
|
(users[u] ??= []).push(link); |
|
} |
|
|
|
for (const username of Object.keys(users)) { |
|
|
|
let karma = await getCached(username); |
|
|
|
if (karma === null) { |
|
try { |
|
karma = await getKarma(username); |
|
if (typeof karma === 'number') |
|
await setCached(username, karma); |
|
} catch { continue; } |
|
} |
|
|
|
if (typeof karma !== 'number') continue; |
|
|
|
for (const el of users[username]) { |
|
if (el.dataset.karma) continue; |
|
el.dataset.karma = 1; |
|
|
|
const span = document.createElement('span'); |
|
span.textContent = ` (${karma})`; |
|
span.style.fontSize = '0.85em'; |
|
span.style.marginLeft = '2px'; |
|
|
|
styleKarma(span, karma); |
|
el.after(span); |
|
} |
|
} |
|
})(); |