Skip to content

Instantly share code, notes, and snippets.

@mootari
Last active July 30, 2026 15:59
Show Gist options
  • Select an option

  • Save mootari/bf4c7fa1649a3670953c0731006353ce to your computer and use it in GitHub Desktop.

Select an option

Save mootari/bf4c7fa1649a3670953c0731006353ce to your computer and use it in GitHub Desktop.
Bookmarklet to show latest channel activity
// Run as content snippet to test and to obtain the bookmarklet URL
function run() {
const stores = ((s = {}) => (webpackChunkdiscord_app.push(
[[Symbol()], {}, r => {
for(const c of Object.values(r.c ?? {})) {
for(const e of Object.values(c?.exports ?? {})) {
if(!e || typeof e !== "object") continue;
if(e[Symbol.toStringTag] === "IntlMessagesProxy") continue;
if(e._dispatchToken) s[e.getName()] = e;
}
}
}]
), s))();
const tsOf = sfId => Number((BigInt(sfId) >> 22n) + 1420070400000n);
const iconUrlOf = guild => `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp?size=160&quality=lossless`;
const HOUR = 60 * 60 * 1000;
const cutoffTs = Date.now() - 12 * HOUR;
const hoursAgo = ts => (Date.now() - ts) / HOUR;
// guilds in sidebar order
const guildIds = stores.SortedGuildStore.getFlattenedGuildIds();
const guilds = Object.fromEntries(guildIds.map(id => [id, stores.GuildStore.getGuild(id)]));
const readStates = Object.fromEntries(
Array.from(stores.ReadStateStore.getReadStatesByChannel(), ([cId, c]) => [cId, {
unread: c.unreadCount,
lastTs: c._lastMessageTimestamp,
unreadTs: c._oldestUnreadMessageId ? tsOf(c._oldestUnreadMessageId) : null,
}])
);
const channelsOf = gId => Object.values(stores.ChannelStore.getMutableGuildChannelsForGuild(gId));
const isMuted = (gId, cId) => stores.UserGuildSettingsStore.isChannelMuted(gId, cId);
const relTime = ts => {
const d = Math.round((Date.now() - ts) / 60_000);
const s = d < 60 ? `${d}m` : `${(d / 60).toFixed(0)}h`;
return `${s} ago`;
};
const absTime = ts => new Date(ts).toString();
const urlOf = (gId, cId) => `https://discord.com/channels/${gId}/${cId}`;
const rows = guildIds.flatMap(
gId => channelsOf(gId)
.filter(c => readStates[c.id]?.lastTs >= cutoffTs && !isMuted(gId, c.id))
.map(c => {
const {unread, lastTs} = readStates[c.id];
return {
guild: guilds[gId].name,
channel: c.name,
unread,
lastTs,
url: urlOf(gId, c.id),
//__guild: guilds[gId],
//__channel: c,
}
}).sort((a, b) => b.lastTs - a.lastTs)
);
const css = `
body { font: 13px sans-serif; }
th, td { padding: 4px 8px; }
th { cursor: pointer }
tbody tr:nth-child(2n) { background: #eee; }
td:nth-child(3), td:nth-child(4) { text-align: right }
`;
const srcUrl = URL.createObjectURL(new Blob([`<!doctype html><head><style>${css}`], {type: "text/html"}));
const w = window.open(srcUrl, "_blank");
w.onload = () => {
w.document.title = "Discord - Latest channels";
const h = (tag, props, children) => {
const el = w.document.createElement(tag);
if(props) for(const [k, v] of Object.entries(props)) typeof v === "string" ? el.setAttribute(k, v) : el[k] = v;
if(children) Array.isArray(children) ? el.append(...children) : el.append(children);
return el;
};
const compareStr = key => (a, b) => a[key].localeCompare(b[key]);
const compareNum = key => (a, b) => (b[key] ?? 0) - (a[key] ?? 0);
const sortBy = fn => render(rows.slice().sort(fn));
const render = rows => w.document.body.replaceChildren(
h("table", null, [
h("tr", null, [
h("th", {onclick: () => sortBy(compareStr("guild"))}, "Server"),
h("th", {onclick: () => sortBy(compareStr("channel"))}, "Channel"),
h("th", {onclick: () => sortBy(compareNum("unread"))}, "Unread"),
h("th", {onclick: () => sortBy(compareNum("lastTs"))}, "Latest"),
h("th", {onclick: () => sortBy(() => 0)}, ""),
]),
h("tbody", null, rows.map(r => h("tr", null, [
h("td", null, r.guild),
h("td", null, r.channel),
h("td", null, r.unread),
h("td", null, h("span", {title: absTime(r.lastTs)}, relTime(r.lastTs))),
h("td", null, h("a", {target: "_blank", href: r.url}, ["Open"]))
])))
])
);
render(rows);
};
}
run();
console.log({bookmarkletUrl: "javascript:" + encodeURIComponent(`(${run})()`)})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment