7.8 KiB
bitvid Moderation Overview
What we’re building
bitvid is follow-centric. Your Home feed comes from people you follow (F1). Discovery can optionally expand to friends-of-friends (F2) and ranked sources. Moderation is client-side and user-controlled with optional admin lists.
Core principles
- Freedom to choose: User picks filters. Defaults are safe but reversible.
- Explain decisions: Every blur/hide shows a “why” badge and a “show anyway” control.
- Whitelist > blacklist: Prefer showing content from trusted networks over fighting global spam.
- Minimal central power: Admin lists are opt-in; users can unsubscribe.
Threat model (short)
- Spam/bots, impersonation, malware, NSFW thumbnails, illegal content, brigading/dogpiles, Sybil report attacks.
Building blocks (Nostr)
- Reports: NIP-56 (
kind 1984) with types likenudity,spam,illegal,impersonation, etc. - Lists: NIP-51 (mute list 10000, categorized people 30000, bookmarks 30001).
- Replies/threads: NIP-10 (comments).
- Counts: NIP-45 (relay COUNT; optional fallbacks).
- View metrics:
js/nostr/viewEventBindings.jswraps the shared client so moderation toggles hit the same record/list/subscribe/count helpers that enforce our NIP-71/NIP-78 guard rails when relays disable optional methods.
Runtime flow (blur, hide, override)
ModerationServiceorchestrates ingest and scoring. Review the service for the trusted-report math and helper entry points.createModerationStagewires the service into the feed engine where moderation summaries decorate timeline items.bitvidApp.decorateVideoModerationconnects stage output to the UI layer alongside feature-flag plumbing.VideoCard.refreshModerationUiapplies badges, blur states, hide metadata (data-moderation-hidden), and the "show anyway" toggles.
Where to extend
Thread new moderation behaviors through the same service → stage → app → UI flow above. Extending the existing layers keeps overrides, feature flags, and QA hooks consistent—avoid spinning up parallel moderation modules unless the architecture document explicitly calls for it.
Defaults (policy)
- Blur video thumbnails when trusted
nudityreports meetDEFAULT_BLUR_THRESHOLD. - Disable autoplay preview when the trusted
nudityreport count reachesDEFAULT_AUTOPLAY_BLOCK_THRESHOLD. - Hide videos when
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLDtrusted contacts mute the author. Cards render withdata-moderation-hidden="true", the badge readsHidden · {count} trusted mute(s), and a "Show anyway" button becomes available. - Hide videos when
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLDtrusted contacts file spam reports. The badge copy escalates toHidden · {count} trusted spam report(s)and the card stays hidden until the viewer overrides it. - Downrank author when any F1 has them in mute list (10000).
- Opt-in admin lists (30000 with
d=bitvid:admin:*) can hard-hide content. - Trust seeds from
DEFAULT_TRUST_SEED_NPUBScount as baseline F1 contacts (toggle viaFEATURE_TRUST_SEEDS).
Default thresholds now come directly from
config/instance-config.js. Look for the exportsDEFAULT_BLUR_THRESHOLD,DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD, andDEFAULT_TRUSTED_SPAM_HIDE_THRESHOLDto adjust the instance-wide behavior before deploying. The upstream repo currently publishes example values of 3 trusted reports for blur, 2 for autoplay blocking, 1 trusted mute to hide authors, and 3 trusted spam reports to hide videos.
Operators can still override these thresholds on a per-viewer basis in Settings → Safety & Moderation; leaving the UI fields blank restores the config-driven defaults. Update
config/instance-config.jsto change what new viewers receive out of the box.
Hide thresholds & feature flags
TRUSTED_MUTE_HIDE_THRESHOLDandTRUSTED_SPAM_HIDE_THRESHOLDdetermine when moderation metadata marks an item as hidden. Both feed theoriginal.hide*fields thatVideoCardinspects before deciding whether to render the hidden summary container.FEATURE_TRUSTED_HIDE_CONTROLSshows the additional threshold inputs in the profile modal so operators can raise/lower the hide triggers client-side.- When any hide threshold fires we persist the active counts (
hideCounts.trustedMuteCount,hideCounts.trustedReportCount) so analytics/debugging tools can show why an item disappeared. - Cards that arrive with
original.hidden === trueautomatically expose the "Show anyway" chip so viewers can inspect hidden content. Activating the override clears blur/autoplay restrictions alongside the hidden state.
Community blacklist federation
Operators can delegate hard-hide decisions to trusted curators without giving them full admin powers. The flow is:
- The super admin maintains a
kind 30000list withd=${ADMIN_LIST_NAMESPACE}:${ADMIN_COMMUNITY_BLACKLIST_SOURCES}. Eachatag references a community curator’s blacklist as30000:<curator hex pubkey>:${ADMIN_LIST_NAMESPACE}:${ADMIN_COMMUNITY_BLACKLIST_PREFIX}:<slug>. - Every curator publishes their own
kind 30000list using the referenceddtag (${ADMIN_LIST_NAMESPACE}:${ADMIN_COMMUNITY_BLACKLIST_PREFIX}:<slug>) and fills it withptags for accounts they want hidden. - bitvid automatically fetches each referenced list, merges the entries into the global blacklist, and removes any duplicates or guard-protected accounts (super admin, editors, or whitelist members).
To add a community list, append a new a tag to the super-admin source list and ask the curator to publish their companion p list. To remove a list, delete the a tag or ask the curator to clear their p entries; the client stops ingesting it on the next refresh.
New operators should also note that fresh viewer accounts automatically inherit the merged admin + community blacklist on their first login. The client seeds the user’s personal block list with that baseline once, so moderators can rely on a shared floor while still allowing operators to remove entries locally without having them reappear.
Safety & Moderation controls
The profile modal now exposes blur, autoplay, and hide thresholds so operators can dial in stricter or more permissive behavior. Enter a non-negative whole number to override the default or leave the field blank to fall back to the baseline values from config/instance-config.js (DEFAULT_BLUR_THRESHOLD, DEFAULT_AUTOPLAY_BLOCK_THRESHOLD, DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD, DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD). Adjustments are stored locally, applied immediately to the active feed, and rehydrate on every load. Update the config file to change the per-instance defaults that new viewers see.
Runtime flags
TRUSTED_MUTE_HIDE_THRESHOLD— numeric default for the trusted mute hide control. Initialized fromDEFAULT_TRUSTED_MUTE_HIDE_THRESHOLDinconfig/instance-config.js; the upstream config sets this to1, but adjust the export to match your policy.TRUSTED_SPAM_HIDE_THRESHOLD— numeric default for the trusted spam hide control. Initialized fromDEFAULT_TRUSTED_SPAM_HIDE_THRESHOLDinconfig/instance-config.js; the upstream config example is3.FEATURE_TRUSTED_HIDE_CONTROLS— boolean toggle to hide/show the new trusted hide controls in the UI (defaulttrue).
Files in this folder
web-of-trust.md— how we compute trust signals and thresholdsnips.md— exact NIPs and kinds bitvid usesrelays.md— relay compatibility and COUNT fallbackstesting.md— QA checklist + test vectors