diff --git a/config/instance-config.js b/config/instance-config.js index f61c63fc..993bd3d9 100644 --- a/config/instance-config.js +++ b/config/instance-config.js @@ -191,6 +191,20 @@ export const DEFAULT_WHITELIST_MODE_ENABLED = true; */ export const ALLOW_NSFW_CONTENT = false; +/** + * Baseline trusted accounts used to seed the moderation graph. + * + * Provide Nostr npubs for the operators and moderators you trust by default. + * Downstream modules treat these accounts as the starting point for the F1 + * network that powers trusted reports and mutes. Operators should feel free to + * edit the list to match their deployment; bitvid will freeze and sanitize the + * values at runtime when reading from `js/config.js`. + */ +export const DEFAULT_TRUST_SEED_NPUBS = [ + "npub1424242424242424242424242424242424242424242424242424qamrcaj", + "npub1hwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwasxw04hu", +]; + /** * Baseline trusted-report count that triggers thumbnail blurring. * diff --git a/js/config.js b/js/config.js index c16aece7..7a07c770 100644 --- a/js/config.js +++ b/js/config.js @@ -20,6 +20,7 @@ import { ADMIN_WHITELIST_MODE_STORAGE_KEY, DEFAULT_WHITELIST_MODE_ENABLED, ALLOW_NSFW_CONTENT, + DEFAULT_TRUST_SEED_NPUBS, DEFAULT_BLUR_THRESHOLD, DEFAULT_AUTOPLAY_BLOCK_THRESHOLD, DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD, @@ -73,6 +74,7 @@ export const ADMIN_LIST_NAMESPACE = "bitvid:admin"; // Reserved for Nostr lists export const ADMIN_COMMUNITY_BLACKLIST_SOURCES = "community:blacklist:sources"; export const ADMIN_COMMUNITY_BLACKLIST_PREFIX = "community:blacklist"; export { ALLOW_NSFW_CONTENT }; +export { DEFAULT_TRUST_SEED_NPUBS }; export { DEFAULT_BLUR_THRESHOLD }; export { DEFAULT_AUTOPLAY_BLOCK_THRESHOLD }; export { DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD }; diff --git a/js/constants.js b/js/constants.js index 0e09dc66..e14b572f 100644 --- a/js/constants.js +++ b/js/constants.js @@ -1,16 +1,11 @@ import { DEFAULT_AUTOPLAY_BLOCK_THRESHOLD as CONFIG_DEFAULT_AUTOPLAY_BLOCK_THRESHOLD, DEFAULT_BLUR_THRESHOLD as CONFIG_DEFAULT_BLUR_THRESHOLD, + DEFAULT_TRUST_SEED_NPUBS as CONFIG_DEFAULT_TRUST_SEED_NPUBS, DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD as CONFIG_DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD, DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD as CONFIG_DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD, } from "./config.js"; -export const DEFAULT_TRUST_SEED_NPUBS = Object.freeze([ - // Rollout: trust seeds count as F1 contacts. Rollback: flip FEATURE_TRUST_SEEDS to false. - "npub1424242424242424242424242424242424242424242424242424qamrcaj", - "npub1hwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwasxw04hu", -]); - function coerceNonNegativeInteger(value, fallback) { const numeric = Number(value); if (!Number.isFinite(numeric)) { @@ -83,6 +78,32 @@ function sanitizeTrackerList(candidate, fallbackTrackers) { return sanitized; } +function sanitizeTrustSeedList(candidate) { + if (!Array.isArray(candidate)) { + return []; + } + + const seen = new Set(); + const sanitized = []; + + for (const npub of candidate) { + if (typeof npub !== "string") { + continue; + } + const trimmed = npub.trim(); + if (!trimmed) { + continue; + } + if (seen.has(trimmed)) { + continue; + } + seen.add(trimmed); + sanitized.push(trimmed); + } + + return sanitized; +} + const DEFAULT_WSS_TRACKERS = Object.freeze([ "wss://tracker.openwebtorrent.com", "wss://tracker.fastcast.nz", @@ -107,6 +128,10 @@ const SANITIZED_DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD = coerceNonNegativeInteger( 3, ); +const SANITIZED_DEFAULT_TRUST_SEED_NPUBS = Object.freeze( + sanitizeTrustSeedList(CONFIG_DEFAULT_TRUST_SEED_NPUBS) +); + export const DEFAULT_BLUR_THRESHOLD = SANITIZED_DEFAULT_BLUR_THRESHOLD; export const DEFAULT_AUTOPLAY_BLOCK_THRESHOLD = SANITIZED_DEFAULT_AUTOPLAY_BLOCK_THRESHOLD; @@ -114,6 +139,7 @@ export const DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD = SANITIZED_DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD; export const DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD = SANITIZED_DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD; +export const DEFAULT_TRUST_SEED_NPUBS = SANITIZED_DEFAULT_TRUST_SEED_NPUBS; const DEFAULT_FLAGS = Object.freeze({ URL_FIRST_ENABLED: true, // try URL before magnet in the player diff --git a/tests/moderation/trust-seeds.test.mjs b/tests/moderation/trust-seeds.test.mjs index ff87be17..0fef25df 100644 --- a/tests/moderation/trust-seeds.test.mjs +++ b/tests/moderation/trust-seeds.test.mjs @@ -1,6 +1,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { DEFAULT_TRUST_SEED_NPUBS } from "../../js/constants.js"; +import { DEFAULT_TRUST_SEED_NPUBS as CONFIG_DEFAULT_TRUST_SEED_NPUBS } from "../../js/config.js"; import { withMockedNostrTools, createModerationServiceHarness, @@ -9,6 +10,23 @@ import { const [PRIMARY_SEED_NPUB] = DEFAULT_TRUST_SEED_NPUBS; +test("default trust seeds derive from config", () => { + const sanitizedConfigSeeds = Array.isArray(CONFIG_DEFAULT_TRUST_SEED_NPUBS) + ? Array.from( + new Set( + CONFIG_DEFAULT_TRUST_SEED_NPUBS + .filter((value) => typeof value === "string") + .map((value) => value.trim()) + .filter(Boolean) + ) + ) + : []; + + assert.ok(Object.isFrozen(DEFAULT_TRUST_SEED_NPUBS)); + assert.deepEqual(DEFAULT_TRUST_SEED_NPUBS, sanitizedConfigSeeds); + assert.notStrictEqual(DEFAULT_TRUST_SEED_NPUBS, CONFIG_DEFAULT_TRUST_SEED_NPUBS); +}); + function createTrustedMuteEvent({ owner, muted, id = "m".repeat(64), createdAt = 1_700_000_000 } = {}) { return { kind: 10000,