Something went wrong. Try again.
this post sux
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435/* * sux content script (isolated world) for bsky.app. * * - Discovers posts from the rendered feed DOM (permalink anchors + item text). * This is transport-agnostic: bsky.app's API client uses a captured fetch * reference that page-world wrappers can't see, so network interception is * only a supplement, not the source of truth. * - Feed items are identified by their action bar (Reply/Like/Repost buttons): * quote embeds and reply previews have none, so nested anchors resolve to the * wrapping item. An item hides when its own verdict is hide OR when any post * it quotes/previews (nested) is hide — no empty husks. * - Hides items while the filter is active unless the verdict is "show" * (hide-until-judged); when off, every trace is removed. * - Mirrors diagnostics into the page world (__suxDiagJSON). *//* global SuxShared, browser */(function () { "use strict";
var SH = globalThis.SuxShared; var POSTS_CAP = 8000; var HIDDEN_ATTR = "data-sux-hidden"; var HIDDEN_CLASS = "sux-hidden"; var ACTION_SEL = '[aria-label^="Reply"], [aria-label^="Like"], [aria-label^="Repost"]';
/* ---- diagnostics (mirrored to page world + sent to background) ---- */ var diag = { seen: 0, // posts received from the interceptor (supplement) domExtracted: 0, // posts extracted from the DOM items: 0, // feed item elements currently tracked hidden: 0, // of those, currently hidden unmatchedAnchors: 0, // feed items we could not map to a post path nested: 0, // nested (quote/reply-preview) links on tracked items scans: 0, settingsActive: false, interceptorInjected: false, near50: 0, near50Samples: "", };
function publishDiag() { try { if (window.wrappedJSObject) { // Primitives/strings pass the cross-world boundary; objects don't. window.wrappedJSObject.__suxDiagJSON = JSON.stringify(diag); } } catch (e) { /* ignore */ } }
/* ---- inject page-world fetch interceptor (supplement only) ---- */ var inject = document.createElement("script"); inject.src = browser.runtime.getURL("interceptor.js"); inject.onload = function () { diag.interceptorInjected = true; console.debug("[sux] interceptor injected"); publishDiag(); inject.remove(); }; (document.head || document.documentElement).appendChild(inject);
/* ---- our hiding style ---- */ var style = document.createElement("style"); style.id = "sux-style"; style.textContent = "." + HIDDEN_CLASS + "{display:none!important}"; document.documentElement.appendChild(style);
/* ---- state ---- */ var settings = SH.defaults(); var active = false; var port = null;
var posts = new Map(); // post path ("profile/<author>/post/<rkey>") -> {id, text, alt} var verdicts = new Map(); // post path -> "show" | "hide" var scores = new Map(); // post path -> {p?, n?} judgment scores (telemetry) var elements = new Map(); // feed item element -> {main, nested: []}
/* ---- post indexing ---- */
function indexPost(p) { if (!p || !p.id || posts.has(p.id)) return false; posts.set(p.id, p); if (posts.size > POSTS_CAP) posts.delete(posts.keys().next().value); return true; }
/* ---- background connection ---- */
function requestVerdicts(list) { if (!port || !list.length) return; try { port.postMessage({ type: "getVerdicts", posts: list.map(function (p) { return { id: p.id, text: p.text, alt: p.alt }; }), }); } catch (e) { /* port is gone; reconnect will resend */ } }
function connect() { port = browser.runtime.connect({ name: "sux" }); port.onMessage.addListener(function (msg) { if (!msg) return; if (msg.type === "settings") { settings = SH.normalizeSettings(msg.settings); active = !!msg.active; diag.settingsActive = active; verdicts.clear(); // stale verdicts from the previous config must not linger scores.clear(); publishDiag(); reconcile(); } else if (msg.type === "verdicts") { (msg.items || []).forEach(function (it) { verdicts.set(it.id, it.verdict); if (it.d) { scores.set(it.id, it.d); if (scores.size > 1000) scores.delete(scores.keys().next().value); } }); applyVerdicts(); } }); port.onDisconnect.addListener(function () { port = null; setTimeout(connect, 1000); }); requestVerdicts(Array.from(posts.values())); } connect();
setInterval(function () { publishDiag(); if (port) { try { port.postMessage({ type: "contentStats", stats: diag }); } catch (e) { /* ignore */ } } }, 2000);
/* ---- posts from the page-world interceptor (supplement) ---- */ /* Converted from at:// URIs to the same path id space used by the DOM scan. */
function pathFromAtUri(uri) { var m = String(uri).match(/^at:\/\/([^/]+)\/app\.bsky\.feed\.post\/([^/]+)$/); return m ? "profile/" + m[1].toLowerCase() + "/post/" + m[2] : null; }
window.addEventListener("message", function (ev) { if (ev.source !== window || !ev.data || ev.data.source !== "sux-interceptor") return; var list = ev.data.posts || []; diag.seen += list.length; var fresh = []; for (var i = 0; i < list.length; i++) { var path = pathFromAtUri(list[i].id); if (!path) continue; var post = { id: path, text: list[i].text || "", alt: list[i].alt || "" }; if (indexPost(post)) fresh.push(post); } publishDiag(); if (fresh.length) { console.debug("[sux] interceptor sent " + fresh.length + " new posts"); requestVerdicts(fresh); } });
/* ---- DOM mapping ---- */
// "/profile/<author>/post/<rkey>" from either relative or absolute hrefs, else null. function normalizePostPath(href) { if (!href || typeof href !== "string") return null; var h = href.replace(/^https?:\/\/[^/]+/i, "").split("?")[0].split("#")[0]; var m = h.match(/^\/profile\/([^/]+)\/post\/([^/]+)\/?$/); if (!m) return null; return "profile/" + m[1].toLowerCase() + "/post/" + m[2]; }
function normalizeProfilePath(href) { if (!href || typeof href !== "string") return null; var h = href.replace(/^https?:\/\/[^/]+/i, "").split("?")[0].split("#")[0]; var m = h.match(/^\/profile\/([^/]+)\/?$/); return m ? m[1].toLowerCase() : null; }
// A feed item is the ancestor whose parent holds at least two action-bar- // bearing children (sibling items in the virtualized list). Quote embeds and // reply previews have no action bar of their own, so every anchor in the item // — main or nested — climbs to the same wrapper. Probe-validated: returns the // same node as the legacy heuristic on plain posts, and the whole item (not // the embed box) on quote posts, which is what prevents empty husks. function itemContainer(anchor) { var node = anchor.parentElement; for (var depth = 0; node && node !== document.body && depth < 14; depth++) { var parent = node.parentElement; if (parent) { var kidsWithBars = 0; for (var i = 0; i < parent.children.length; i++) { if (parent.children[i].querySelector(ACTION_SEL)) kidsWithBars++; } if (kidsWithBars >= 2) return node; } node = parent; } return nestedContainer(anchor); // fallback: structural heuristic }
// Smallest block holding this anchor but no sibling post anchor — for a // nested (quote / reply-preview) anchor that is the embed box itself. Used // to extract the nested post's own text so it can be judged even when it // never appears as a standalone feed item. function nestedContainer(anchor) { var node = anchor.parentElement; for (var depth = 0; node && node !== document.body && depth < 12; depth++) { var parent = node.parentElement; if (parent) { var siblings = parent.querySelectorAll('a[href*="/post/"]'); for (var i = 0; i < siblings.length; i++) { if (!node.contains(siblings[i])) return node; } } node = parent; } return anchor.parentElement; }
function depthWithin(root, el) { var d = 0; while (el && el !== root) { d++; el = el.parentElement; } return d; }
// Which post does this item represent? Nested anchors (quote embeds, reply // parents) belong to other posts. The main post's permalink is the anchor // whose author matches the item's dominant profile links (avatar, name, // handle all point at the main author); ties break on shallowest depth, // then document order. function chooseMainAnchor(item, anchors) { var counts = new Map(); var profileLinks = item.querySelectorAll('a[href*="/profile/"]'); for (var i = 0; i < profileLinks.length; i++) { var pp = normalizeProfilePath(profileLinks[i].getAttribute("href")); if (pp) counts.set(pp, (counts.get(pp) || 0) + 1); } var dominant = null; var best = 0; counts.forEach(function (n, author) { if (n > best) { best = n; dominant = author; } }); var fallback = null; var bestMatch = null; for (var j = 0; j < anchors.length; j++) { var a = anchors[j]; var path = normalizePostPath(a.getAttribute("href")); if (!path) continue; var rec = { a: a, depth: depthWithin(item, a), order: j }; if (!fallback || rec.depth < fallback.depth) fallback = rec; if (dominant && path.slice("profile/".length).split("/")[0] === dominant) { if (!bestMatch || rec.depth < bestMatch.depth) bestMatch = rec; } } var winner = bestMatch || fallback; return winner ? winner.a : null; }
/* ---- DOM text extraction ---- */
var ALT_CAP = 4000;
function itemAlts(item) { var out = []; var imgs = item.querySelectorAll("img"); for (var i = 0; i < imgs.length; i++) { var a = imgs[i].alt; if (a && a.trim()) out.push(a.trim()); } return out.join("\n").slice(0, ALT_CAP); }
// Header lines (repost credit, display name, handle, "· 19h") and the // trailing action-bar counts are noise for Jev; keep the post's own words. function cleanItemText(item) { var raw = item.innerText || ""; if (!raw.trim()) return ""; var lines = raw.split("\n"); var cut = -1; for (var i = 0; i < lines.length && i < 14; i++) { if (/^\s*·\s/.test(lines[i])) { cut = i; break; } } var body = cut >= 0 ? lines.slice(cut + 1) : lines; body = body.filter(function (l) { return !/^\s*reposted by\b/i.test(l); }); while (body.length) { var last = body[body.length - 1].trim(); if (last === "" || /^\d+([.,]\d+)?\s*[kKmM]?$/.test(last)) { body.pop(); } else break; } return body.join("\n").trim().slice(0, SH.TEXT_CAP); }
/* ---- hiding ---- */
function setHidden(el, hide) { if (hide) { el.setAttribute(HIDDEN_ATTR, "1"); el.classList.add(HIDDEN_CLASS); } else { el.removeAttribute(HIDDEN_ATTR); el.classList.remove(HIDDEN_CLASS); } }
// An item hides when its own post is not "show" (hide-until-judged), or when // any post it quotes/previews has an explicit "hide" verdict — no husks. function applyFor(item, rec) { if (!active) { setHidden(item, false); return; } var hide = verdicts.get(rec.main) !== "show"; if (!hide && rec.nested) { for (var i = 0; i < rec.nested.length; i++) { if (verdicts.get(rec.nested[i]) === "hide") { hide = true; break; } } } setHidden(item, hide); }
function applyVerdicts() { if (!active) return; elements.forEach(function (rec, el) { if (el.isConnected) applyFor(el, rec); }); }
function reconcile() { if (!active) { elements.forEach(function (rec, el) { setHidden(el, false); }); return; } requestVerdicts(Array.from(posts.values())); elements.forEach(function (rec, el) { if (el.isConnected) applyFor(el, rec); }); scheduleScan(); }
/* ---- scanning ---- */
var scanScheduled = false; function scheduleScan() { if (scanScheduled) return; scanScheduled = true; setTimeout(function () { scanScheduled = false; scan(); }, 150); }
function scan() { diag.scans++; elements.forEach(function (rec, el) { if (!el.isConnected) elements.delete(el); });
var anchors = document.querySelectorAll('a[href*="/post/"]'); var byItem = new Map(); for (var i = 0; i < anchors.length; i++) { if (!normalizePostPath(anchors[i].getAttribute("href"))) continue; var item = itemContainer(anchors[i]); if (!byItem.has(item)) byItem.set(item, []); byItem.get(item).push(anchors[i]); }
var unmatched = 0; var fresh = []; var nestedCount = 0; byItem.forEach(function (list, item) { var main = chooseMainAnchor(item, list); if (!main) { unmatched++; return; } var mainPath = normalizePostPath(main.getAttribute("href")); if (!mainPath) { unmatched++; return; } if (!posts.has(mainPath)) { var mp = { id: mainPath, text: cleanItemText(item), alt: itemAlts(item) }; if (indexPost(mp)) { diag.domExtracted++; fresh.push(mp); } } var nested = []; for (var j = 0; j < list.length; j++) { var a = list[j]; if (a === main) continue; var np = normalizePostPath(a.getAttribute("href")); if (!np || np === mainPath || nested.indexOf(np) !== -1) continue; nested.push(np); if (!posts.has(np)) { var box = nestedContainer(a); var post = { id: np, text: cleanItemText(box), alt: itemAlts(box) }; if (indexPost(post)) { diag.domExtracted++; fresh.push(post); } } } nestedCount += nested.length; var rec = { main: mainPath, nested: nested.length ? nested : null }; elements.set(item, rec); applyFor(item, rec); }); diag.items = elements.size; var hidden = 0; elements.forEach(function (rec, el) { if (el.classList.contains(HIDDEN_CLASS)) hidden++; }); diag.hidden = hidden; diag.unmatchedAnchors = unmatched; diag.nested = nestedCount; // Near-threshold telemetry: which tracked posts sit close to the 50% line, // under the currently active rule's score. var useN = settings.ruleB.enabled && SH.ruleBValid(settings); var near = 0; var samples = []; elements.forEach(function (rec, el) { var d = scores.get(rec.main); if (!d) return; var s = useN && typeof d.n === "number" ? d.n : d.p; if (typeof s !== "number") return; if (Math.abs(s - 0.5) <= 0.08) { near++; if (samples.length < 6) samples.push(rec.main.slice(-16) + "=" + s); } }); diag.near50 = near; diag.near50Samples = samples.join(", "); publishDiag(); if (fresh.length) { console.debug("[sux] DOM extracted " + fresh.length + " new posts (total " + posts.size + ")"); requestVerdicts(fresh); } }
var observer = new MutationObserver(scheduleScan); observer.observe(document.documentElement, { childList: true, subtree: true });})();