Something went wrong. Try again.
GET /xrpc/tech.waow.typeahead.searchActors typeahead.waow.tech
Something went wrong. Try again.
Python
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103#!/usr/bin/env python3"""regenerate src/labelers.json: the moderation labelers the reputation page asks.
Discovery is the public appview's own search: `associated.labeler` on asearchActors hit is the appview asserting the account declares a labelerservice, so nothing here is hand-typed. Kept are labelers whose name orhandle says moderation/safety (topic labelers — sports teams, zodiac signs —are noise for a "what do moderators think of this account" view), ordered byfollowers, capped at LIMIT. Each entry carries the labeler's`com.atproto.label.queryLabels` base URL resolved from its DID document,because the unauthenticated appview ignores `atproto-accept-labelers` (checked2026-09-03: a Blacksky-labeled account came back with no labels however theheader was set), so the page must ask each labeler directly.
Run: uv run scripts/discover-labelers.py (then commit src/labelers.json)"""
import jsonimport reimport sysimport urllib.parseimport urllib.requestfrom pathlib import Path
APPVIEW = "https://public.api.bsky.app/xrpc/"QUERIES = ["labeler", "moderation", "eurosky", "northsky", "blacksky", "moderation service", "mod service", "labels", "safety", "spam", "scam", "blocklist", "automod", "trust and safety", "community moderation", "bot detection"]MODERATION_WORDS = re.compile(r"moderation|mod service|safety|automod|anti-?harass|scam|spam", re.I)LIMIT = 20# a labeler nobody subscribes to is more likely a dead endpoint than a signal;# each one costs the report a network round tripMIN_FOLLOWERS = 50# moderation services run by appview operators are structurally relevant# whatever their subscriber count: their labels decide what their appview# shows. kept even under the floor; the page reports an unreachable one.PINNED = {"moderation.bsky.app", "moderation.blacksky.app", "moderation.northsky.social", "ozone.eurosky.social"}OUT = Path(__file__).resolve().parent.parent / "src" / "labelers.json"
def get(url: str): req = urllib.request.Request(url, headers={"user-agent": "typeahead discover-labelers"}) with urllib.request.urlopen(req, timeout=20) as r: return json.load(r)
def appview(path: str, **params): return get(APPVIEW + path + "?" + urllib.parse.urlencode(params, doseq=True))
def labeler_endpoint(did: str) -> str | None: host = "https://plc.directory/" + did if did.startswith("did:plc:") else \ "https://" + did.removeprefix("did:web:") + "/.well-known/did.json" try: doc = get(host) except Exception as e: # noqa: BLE001 - a dead DID doc just drops the labeler print(f" skip {did}: did doc {e}", file=sys.stderr) return None for svc in doc.get("service", []): if svc.get("id", "").endswith("atproto_labeler"): return svc.get("serviceEndpoint") return None
def main() -> None: found: dict[str, dict] = {} for q in QUERIES: for a in appview("app.bsky.actor.searchActors", q=q, limit=100).get("actors", []): if (a.get("associated") or {}).get("labeler"): found[a["did"]] = a print(f"{len(found)} labelers discovered", file=sys.stderr)
dids = list(found) profiles = [] for i in range(0, len(dids), 25): profiles += appview("app.bsky.actor.getProfiles", actors=dids[i:i + 25]).get("profiles", [])
kept = [p for p in profiles if p["handle"] in PINNED or ((p.get("followersCount") or 0) >= MIN_FOLLOWERS and (MODERATION_WORDS.search(p.get("displayName") or "") or MODERATION_WORDS.search(p["handle"])))] kept.sort(key=lambda p: -(p.get("followersCount") or 0))
out = [] for p in kept: if len(out) == LIMIT: break endpoint = labeler_endpoint(p["did"]) if not endpoint: continue out.append({ "did": p["did"], "handle": p["handle"], "name": p.get("displayName") or p["handle"], "endpoint": endpoint.rstrip("/"), "followers": p.get("followersCount") or 0, }) OUT.write_text(json.dumps(out, indent=2) + "\n") print(f"wrote {len(out)} labelers to {OUT.relative_to(OUT.parents[1])}", file=sys.stderr) for r in out: print(f"{r['followers']:>7} {r['handle']:36} {r['endpoint']}", file=sys.stderr)
if __name__ == "__main__": main()