Merge pull request #742 from PR0M3TH3AN/codex/enhance-moderation-stage-for-hide-thresholds

Add trusted hide gating to moderation stage
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-20 19:37:54 -04:00
commit a8d72eb399
3 files changed
+497 -12

No files matched your search

+4
View File
@@ -4369,6 +4369,8 @@ class Application {
getService: () => this.nostrService.getModerationService(),
autoplayThreshold: thresholds.autoplayBlockThreshold,
blurThreshold: thresholds.blurThreshold,
trustedMuteHideThreshold: thresholds.trustedMuteHideThreshold,
trustedReportHideThreshold: thresholds.trustedSpamHideThreshold,
}),
],
sorter: createChronologicalSorter(),
@@ -4408,6 +4410,8 @@ class Application {
getService: () => this.nostrService.getModerationService(),
autoplayThreshold: thresholds.autoplayBlockThreshold,
blurThreshold: thresholds.blurThreshold,
trustedMuteHideThreshold: thresholds.trustedMuteHideThreshold,
trustedReportHideThreshold: thresholds.trustedSpamHideThreshold,
}),
],
sorter: createChronologicalSorter(),
+233 -12
View File
@@ -8,6 +8,8 @@ import logger from "../utils/logger.js";
import { dedupeToNewestByRoot } from "../utils/videoDeduper.js";
import { isPlainObject, toSet } from "./utils.js";
const FEED_HIDE_BYPASS_NAMES = new Set(["home", "recent"]);
function resolveDedupeFunction(customDedupe) {
if (typeof customDedupe === "function") {
return customDedupe;
@@ -199,13 +201,30 @@ export function createModerationStage({
reportType = "nudity",
service = null,
getService,
trustedMuteHideThreshold = Number.POSITIVE_INFINITY,
trustedReportHideThreshold = Number.POSITIVE_INFINITY,
} = {}) {
const sanitizeThreshold = (value, fallback) => {
if (Number.isFinite(value)) {
return Math.max(0, Math.floor(value));
}
return fallback;
};
const normalizedAutoplayThreshold = Number.isFinite(autoplayThreshold)
? Math.max(0, Math.floor(autoplayThreshold))
: 2;
const normalizedBlurThreshold = Number.isFinite(blurThreshold)
? Math.max(0, Math.floor(blurThreshold))
: 3;
const normalizedMuteHideThreshold = sanitizeThreshold(
trustedMuteHideThreshold,
Number.POSITIVE_INFINITY,
);
const normalizedReportHideThreshold = sanitizeThreshold(
trustedReportHideThreshold,
Number.POSITIVE_INFINITY,
);
const normalizedReportType = typeof reportType === "string" ? reportType.trim().toLowerCase() : "nudity";
return async function moderationStage(items = [], context = {}) {
@@ -319,6 +338,104 @@ export function createModerationStage({
}
: () => ({ hex: "", whitelisted: false, blacklisted: false });
const normalizedFeedName =
typeof context?.feedName === "string" ? context.feedName.trim().toLowerCase() : "";
const normalizedFeedVariant =
typeof context?.runtime?.feedVariant === "string"
? context.runtime.feedVariant.trim().toLowerCase()
: "";
const runtimeDisableHardHide = context?.runtime?.disableHardHide === true;
const feedPolicyBypass =
runtimeDisableHardHide ||
(normalizedFeedName && FEED_HIDE_BYPASS_NAMES.has(normalizedFeedName)) ||
(normalizedFeedVariant && FEED_HIDE_BYPASS_NAMES.has(normalizedFeedVariant));
const runtimeForOverride = context?.runtime && typeof context.runtime === "object"
? context.runtime
: null;
const overrideChecker = (() => {
if (!runtimeForOverride) {
return () => false;
}
const overrideMap = runtimeForOverride.moderationOverrides;
const overrideIds = runtimeForOverride.moderationOverrideIds;
const hasFn =
typeof runtimeForOverride.hasModerationOverride === "function"
? runtimeForOverride.hasModerationOverride.bind(runtimeForOverride)
: null;
const getFn =
typeof runtimeForOverride.getModerationOverride === "function"
? runtimeForOverride.getModerationOverride.bind(runtimeForOverride)
: null;
const checkEntry = (entry) => {
if (!entry) {
return false;
}
if (entry === true) {
return true;
}
if (typeof entry === "object" && entry.showAnyway === true) {
return true;
}
return false;
};
return (videoId) => {
if (typeof videoId !== "string") {
return false;
}
const trimmed = videoId.trim();
if (!trimmed) {
return false;
}
const normalizedId = trimmed.toLowerCase();
if (overrideMap instanceof Map) {
const entry = overrideMap.get(normalizedId) ?? overrideMap.get(trimmed);
if (checkEntry(entry)) {
return true;
}
} else if (overrideMap && typeof overrideMap === "object") {
const entry = overrideMap[normalizedId] ?? overrideMap[trimmed];
if (checkEntry(entry)) {
return true;
}
}
if (overrideIds instanceof Set) {
if (overrideIds.has(normalizedId) || overrideIds.has(trimmed)) {
return true;
}
}
if (hasFn) {
try {
if (hasFn(normalizedId) || (normalizedId !== trimmed && hasFn(trimmed))) {
return true;
}
} catch (error) {
context?.log?.(`[${stageName}] hasModerationOverride threw`, error);
}
}
if (getFn) {
try {
const entry = getFn(normalizedId) ?? (normalizedId !== trimmed ? getFn(trimmed) : null);
if (checkEntry(entry)) {
return true;
}
} catch (error) {
context?.log?.(`[${stageName}] getModerationOverride threw`, error);
}
}
return false;
};
})();
const results = [];
for (const item of items) {
@@ -489,18 +606,6 @@ export function createModerationStage({
metadataModeration.trustedMuted = trustedMuted;
metadataModeration.trustedMuters = trustedMuters.slice();
metadataModeration.trustedMuteCount = trustedMuteCount;
item.metadata.moderation = metadataModeration;
if (IS_DEV_MODE) {
logger.dev.debug("[feedEngine/moderation] metadata.moderation updated", {
stage: stageName,
videoId: videoId || null,
blockAutoplay,
blurThumbnail,
trustedCount,
reportType: normalizedReportType,
});
}
if (!video.moderation || typeof video.moderation !== "object") {
video.moderation = {};
@@ -535,6 +640,93 @@ export function createModerationStage({
delete video.moderation.summary;
}
const hideCounts = {
trustedMuteCount,
trustedReportCount: trustedCount,
};
let hideReason = "";
let hideTriggered = false;
if (trustedMuted && trustedMuteCount >= normalizedMuteHideThreshold) {
hideReason = "trusted-mute-hide";
hideTriggered = true;
} else if (trustedCount >= normalizedReportHideThreshold) {
hideReason = "trusted-report-hide";
hideTriggered = true;
}
const viewerOverrideActive = overrideChecker(videoId);
let hideBypass = "";
if (hideTriggered) {
if (adminWhitelist) {
hideBypass = "admin-whitelist";
} else if (viewerOverrideActive) {
hideBypass = "viewer-override";
} else if (feedPolicyBypass) {
hideBypass = "feed-policy";
}
}
const hidden = hideTriggered && !hideBypass;
if (hideTriggered) {
metadataModeration.hidden = hidden;
metadataModeration.hideReason = hideReason;
metadataModeration.hideCounts = { ...hideCounts };
if (hideBypass) {
metadataModeration.hideBypass = hideBypass;
} else if (metadataModeration.hideBypass) {
delete metadataModeration.hideBypass;
}
video.moderation.hidden = hidden;
video.moderation.hideReason = hideReason;
video.moderation.hideCounts = { ...hideCounts };
if (hideBypass) {
video.moderation.hideBypass = hideBypass;
} else if (video.moderation.hideBypass) {
delete video.moderation.hideBypass;
}
} else {
metadataModeration.hidden = false;
if (metadataModeration.hideReason) {
delete metadataModeration.hideReason;
}
if (metadataModeration.hideCounts) {
delete metadataModeration.hideCounts;
}
if (metadataModeration.hideBypass) {
delete metadataModeration.hideBypass;
}
video.moderation.hidden = false;
if (video.moderation.hideReason) {
delete video.moderation.hideReason;
}
if (video.moderation.hideCounts) {
delete video.moderation.hideCounts;
}
if (video.moderation.hideBypass) {
delete video.moderation.hideBypass;
}
}
item.metadata.moderation = metadataModeration;
if (IS_DEV_MODE) {
logger.dev.debug("[feedEngine/moderation] metadata.moderation updated", {
stage: stageName,
videoId: videoId || null,
blockAutoplay,
blurThumbnail,
trustedCount,
reportType: normalizedReportType,
hidden,
hideReason: hideTriggered ? hideReason : null,
hideBypass: hideBypass || null,
});
}
if (blockAutoplay || blurThumbnail) {
context?.addWhy?.({
stage: stageName,
@@ -557,6 +749,35 @@ export function createModerationStage({
});
}
if (hideTriggered) {
const hideDetail = {
stage: stageName,
type: "moderation",
reason: hideReason,
videoId: videoId || null,
trustedMuteCount,
trustedReportCount: trustedCount,
hidden,
};
if (hideBypass) {
hideDetail.hideBypass = hideBypass;
}
if (adminWhitelist) {
hideDetail.adminWhitelist = true;
}
if (viewerOverrideActive) {
hideDetail.viewerOverride = true;
}
if (feedPolicyBypass) {
hideDetail.feedPolicyBypass = true;
}
context?.addWhy?.(hideDetail);
}
if (hidden) {
continue;
}
results.push(item);
}
+260
View File
@@ -0,0 +1,260 @@
import test from "node:test";
import assert from "node:assert/strict";
import { createModerationStage } from "../../js/feedEngine/stages.js";
const hex = (value) => value.repeat(64).slice(0, 64);
test("moderation stage hides videos muted by trusted when threshold met", async () => {
const muterHex = hex("a");
const mutedHex = hex("b");
const videoId = hex("1");
const service = {
async refreshViewerFromClient() {},
async setActiveEventIds() {},
getAdminListSnapshot() {
return { whitelist: new Set(), whitelistHex: new Set(), blacklist: new Set(), blacklistHex: new Set() };
},
getAccessControlStatus(identifier) {
return { hex: identifier, whitelisted: false, blacklisted: false };
},
getTrustedReportSummary() {
return null;
},
trustedReportCount() {
return 0;
},
getTrustedReporters() {
return [];
},
isAuthorMutedByTrusted(pubkey) {
return pubkey === mutedHex;
},
getTrustedMutersForAuthor(pubkey) {
return pubkey === mutedHex ? [muterHex] : [];
},
};
const stage = createModerationStage({
service,
trustedMuteHideThreshold: 1,
trustedReportHideThreshold: 99,
});
const items = [{ video: { id: videoId, pubkey: mutedHex }, metadata: {} }];
const why = [];
const context = {
runtime: {},
addWhy(detail) {
why.push(detail);
return detail;
},
log() {},
};
const result = await stage(items, context);
assert.equal(result.length, 0);
const metadata = items[0].metadata.moderation;
assert.equal(metadata.hidden, true);
assert.equal(metadata.hideReason, "trusted-mute-hide");
assert.deepEqual(metadata.hideCounts, { trustedMuteCount: 1, trustedReportCount: 0 });
assert.equal(metadata.hideBypass ?? null, null);
const videoModeration = items[0].video.moderation;
assert.equal(videoModeration.hidden, true);
assert.equal(videoModeration.hideReason, "trusted-mute-hide");
assert.deepEqual(videoModeration.hideCounts, { trustedMuteCount: 1, trustedReportCount: 0 });
const hideWhy = why.find((entry) => entry.reason === "trusted-mute-hide");
assert.ok(hideWhy);
assert.equal(hideWhy.hidden, true);
assert.equal(hideWhy.trustedMuteCount, 1);
assert.equal(hideWhy.trustedReportCount, 0);
});
test("moderation stage hides videos when trusted reports exceed threshold", async () => {
const reporterHex = hex("c");
const videoId = hex("2");
const service = {
async refreshViewerFromClient() {},
async setActiveEventIds() {},
getAdminListSnapshot() {
return { whitelist: new Set(), whitelistHex: new Set(), blacklist: new Set(), blacklistHex: new Set() };
},
getAccessControlStatus(identifier) {
return { hex: identifier, whitelisted: false, blacklisted: false };
},
getTrustedReportSummary() {
return null;
},
trustedReportCount() {
return 5;
},
getTrustedReporters() {
return [{ pubkey: reporterHex, latest: 1_700_000_000 }];
},
};
const stage = createModerationStage({
service,
trustedMuteHideThreshold: 50,
trustedReportHideThreshold: 3,
});
const items = [{ video: { id: videoId, pubkey: hex("d") }, metadata: {} }];
const why = [];
const context = {
runtime: {},
addWhy(detail) {
why.push(detail);
return detail;
},
log() {},
};
const result = await stage(items, context);
assert.equal(result.length, 0);
const metadata = items[0].metadata.moderation;
assert.equal(metadata.hidden, true);
assert.equal(metadata.hideReason, "trusted-report-hide");
assert.deepEqual(metadata.hideCounts, { trustedMuteCount: 0, trustedReportCount: 5 });
const videoModeration = items[0].video.moderation;
assert.equal(videoModeration.hidden, true);
assert.equal(videoModeration.hideReason, "trusted-report-hide");
assert.deepEqual(videoModeration.hideCounts, { trustedMuteCount: 0, trustedReportCount: 5 });
const hideWhy = why.find((entry) => entry.reason === "trusted-report-hide");
assert.ok(hideWhy);
assert.equal(hideWhy.hidden, true);
assert.equal(hideWhy.trustedReportCount, 5);
assert.equal(hideWhy.hideBypass ?? null, null);
});
test("moderation stage bypasses hard hides on home feed", async () => {
const videoId = hex("3");
const service = {
async refreshViewerFromClient() {},
async setActiveEventIds() {},
getAdminListSnapshot() {
return { whitelist: new Set(), whitelistHex: new Set(), blacklist: new Set(), blacklistHex: new Set() };
},
getAccessControlStatus(identifier) {
return { hex: identifier, whitelisted: false, blacklisted: false };
},
getTrustedReportSummary() {
return null;
},
trustedReportCount() {
return 4;
},
getTrustedReporters() {
return [];
},
};
const stage = createModerationStage({
service,
trustedMuteHideThreshold: 10,
trustedReportHideThreshold: 1,
});
const items = [{ video: { id: videoId, pubkey: hex("e") }, metadata: {} }];
const why = [];
const context = {
feedName: "home",
runtime: { feedVariant: "home" },
addWhy(detail) {
why.push(detail);
return detail;
},
log() {},
};
const result = await stage(items, context);
assert.equal(result.length, 1);
const metadata = result[0].metadata.moderation;
assert.equal(metadata.hidden, false);
assert.equal(metadata.hideReason, "trusted-report-hide");
assert.deepEqual(metadata.hideCounts, { trustedMuteCount: 0, trustedReportCount: 4 });
assert.equal(metadata.hideBypass, "feed-policy");
const hideWhy = why.find((entry) => entry.reason === "trusted-report-hide");
assert.ok(hideWhy);
assert.equal(hideWhy.hidden, false);
assert.equal(hideWhy.hideBypass, "feed-policy");
});
test("moderation stage allows admin-whitelisted videos despite hide thresholds", async () => {
const whitelistedHex = hex("f");
const videoId = hex("4");
const service = {
async refreshViewerFromClient() {},
async setActiveEventIds() {},
getAdminListSnapshot() {
return {
whitelist: new Set([`npub${whitelistedHex}`]),
whitelistHex: new Set([whitelistedHex]),
blacklist: new Set(),
blacklistHex: new Set(),
};
},
getAccessControlStatus(identifier) {
const normalized = typeof identifier === "string" ? identifier.trim().toLowerCase() : "";
return { hex: normalized, whitelisted: normalized === whitelistedHex, blacklisted: false };
},
getTrustedReportSummary() {
return null;
},
trustedReportCount() {
return 6;
},
getTrustedReporters() {
return [];
},
};
const stage = createModerationStage({
service,
trustedMuteHideThreshold: 1,
trustedReportHideThreshold: 2,
});
const items = [{ video: { id: videoId, pubkey: whitelistedHex }, metadata: {} }];
const why = [];
const context = {
runtime: {},
addWhy(detail) {
why.push(detail);
return detail;
},
log() {},
};
const result = await stage(items, context);
assert.equal(result.length, 1);
const metadata = result[0].metadata.moderation;
assert.equal(metadata.hidden, false);
assert.equal(metadata.hideReason, "trusted-report-hide");
assert.equal(metadata.hideBypass, "admin-whitelist");
assert.deepEqual(metadata.hideCounts, { trustedMuteCount: 0, trustedReportCount: 6 });
const hideWhy = why.find((entry) => entry.reason === "trusted-report-hide");
assert.ok(hideWhy);
assert.equal(hideWhy.hidden, false);
assert.equal(hideWhy.hideBypass, "admin-whitelist");
assert.equal(hideWhy.adminWhitelist, true);
});