mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #873 from PR0M3TH3AN/codex/extend-config-for-moderation-settings
Align moderation defaults with instance config
This commit is contained in:
9 files changed
+320
-145
No files matched your search
@@ -191,6 +191,46 @@ export const DEFAULT_WHITELIST_MODE_ENABLED = true;
|
||||
*/
|
||||
export const ALLOW_NSFW_CONTENT = false;
|
||||
|
||||
/**
|
||||
* Baseline trusted-report count that triggers thumbnail blurring.
|
||||
*
|
||||
* When a video receives at least this many trusted reports for the active
|
||||
* category (for example, `nudity`), thumbnails render with the blurred preview
|
||||
* by default. Align this value with your moderation policy — the upstream
|
||||
* deployment targets 3 trusted reports before blurring.
|
||||
*/
|
||||
export const DEFAULT_BLUR_THRESHOLD = 3;
|
||||
|
||||
/**
|
||||
* Baseline trusted-report count that blocks autoplay during browsing.
|
||||
*
|
||||
* Videos that meet or exceed this count will not autoplay in feeds unless the
|
||||
* viewer explicitly opts in. Increase the number for a more permissive stance
|
||||
* or decrease it if you want autoplay to stop sooner. The upstream deployment
|
||||
* targets 2 trusted reports before blocking autoplay.
|
||||
*/
|
||||
export const DEFAULT_AUTOPLAY_BLOCK_THRESHOLD = 2;
|
||||
|
||||
/**
|
||||
* Trusted mute threshold that hides creators globally.
|
||||
*
|
||||
* Once this many trusted accounts mute a creator, their videos disappear from
|
||||
* feeds until an operator intervenes. Keep the value low (1–2) to rapidly
|
||||
* enforce community standards or raise it to tolerate more reports before
|
||||
* hiding.
|
||||
*/
|
||||
export const DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD = 1;
|
||||
|
||||
/**
|
||||
* Trusted spam/report threshold that hides videos globally.
|
||||
*
|
||||
* When a video's trusted report count reaches this number, bitvid treats it as
|
||||
* spam or policy violating content and removes it from default feeds. Align
|
||||
* the value with your moderation policy — 3 trusted reports is the upstream
|
||||
* baseline.
|
||||
*/
|
||||
export const DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD = 3;
|
||||
|
||||
/**
|
||||
* Optional theme accent overrides for light and dark mode.
|
||||
*
|
||||
|
||||
@@ -19,6 +19,10 @@ import {
|
||||
URL_FIRST_ENABLED,
|
||||
getTrustedMuteHideThreshold,
|
||||
getTrustedSpamHideThreshold,
|
||||
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
DEFAULT_BLUR_THRESHOLD,
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
} from "./constants.js";
|
||||
import { attachHealthBadges } from "./gridHealth.js";
|
||||
import { attachUrlHealthBadges } from "./urlHealthObserver.js";
|
||||
@@ -6576,20 +6580,30 @@ class Application {
|
||||
const defaults = this.defaultModerationSettings || getDefaultModerationSettings();
|
||||
const defaultBlur = Number.isFinite(defaults?.blurThreshold)
|
||||
? Math.max(0, Math.floor(defaults.blurThreshold))
|
||||
: 1;
|
||||
: DEFAULT_BLUR_THRESHOLD;
|
||||
const defaultAutoplay = Number.isFinite(defaults?.autoplayBlockThreshold)
|
||||
? Math.max(0, Math.floor(defaults.autoplayBlockThreshold))
|
||||
: 1;
|
||||
: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD;
|
||||
|
||||
const runtimeMuteSource = getTrustedMuteHideThreshold();
|
||||
const runtimeTrustedMute = Number.isFinite(runtimeMuteSource)
|
||||
? Math.max(0, Math.floor(runtimeMuteSource))
|
||||
: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD;
|
||||
const defaultTrustedMuteHide = Number.isFinite(
|
||||
defaults?.trustedMuteHideThreshold,
|
||||
)
|
||||
? Math.max(0, Math.floor(defaults.trustedMuteHideThreshold))
|
||||
: getTrustedMuteHideThreshold();
|
||||
: runtimeTrustedMute;
|
||||
|
||||
const runtimeSpamSource = getTrustedSpamHideThreshold();
|
||||
const runtimeTrustedSpam = Number.isFinite(runtimeSpamSource)
|
||||
? Math.max(0, Math.floor(runtimeSpamSource))
|
||||
: DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD;
|
||||
const defaultTrustedSpamHide = Number.isFinite(
|
||||
defaults?.trustedSpamHideThreshold,
|
||||
)
|
||||
? Math.max(0, Math.floor(defaults.trustedSpamHideThreshold))
|
||||
: getTrustedSpamHideThreshold();
|
||||
: runtimeTrustedSpam;
|
||||
|
||||
const blurSource = Number.isFinite(settings?.blurThreshold)
|
||||
? Math.max(0, Math.floor(settings.blurThreshold))
|
||||
|
||||
@@ -20,6 +20,10 @@ import {
|
||||
ADMIN_WHITELIST_MODE_STORAGE_KEY,
|
||||
DEFAULT_WHITELIST_MODE_ENABLED,
|
||||
ALLOW_NSFW_CONTENT,
|
||||
DEFAULT_BLUR_THRESHOLD,
|
||||
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
WATCH_HISTORY_KIND,
|
||||
WATCH_HISTORY_LIST_IDENTIFIER,
|
||||
WATCH_HISTORY_LEGACY_LIST_IDENTIFIERS,
|
||||
@@ -69,6 +73,10 @@ 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_BLUR_THRESHOLD };
|
||||
export { DEFAULT_AUTOPLAY_BLOCK_THRESHOLD };
|
||||
export { DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD };
|
||||
export { DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD };
|
||||
|
||||
const WHITELIST_MODE_KEY = ADMIN_WHITELIST_MODE_STORAGE_KEY;
|
||||
const DEFAULT_WHITELIST_ENABLED = DEFAULT_WHITELIST_MODE_ENABLED;
|
||||
|
||||
+118
-87
@@ -1,9 +1,120 @@
|
||||
import {
|
||||
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD as CONFIG_DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
DEFAULT_BLUR_THRESHOLD as CONFIG_DEFAULT_BLUR_THRESHOLD,
|
||||
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)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const sanitized = Math.max(0, Math.floor(numeric));
|
||||
if (!Number.isFinite(sanitized)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
function coerceBoolean(value, fallback) {
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return fallback;
|
||||
}
|
||||
if (["false", "0", "off", "no"].includes(normalized)) {
|
||||
return false;
|
||||
}
|
||||
if (["true", "1", "on", "yes"].includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
function freezeTrackers(list) {
|
||||
return Object.freeze([...list]);
|
||||
}
|
||||
|
||||
function sanitizeTrackerList(candidate, fallbackTrackers) {
|
||||
const fallback = Array.isArray(fallbackTrackers) ? fallbackTrackers : [];
|
||||
const input = Array.isArray(candidate) ? candidate : fallback;
|
||||
const seen = new Set();
|
||||
const sanitized = [];
|
||||
|
||||
for (const tracker of input) {
|
||||
if (typeof tracker !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmed = tracker.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
if (!/^wss:\/\//i.test(trimmed)) {
|
||||
continue;
|
||||
}
|
||||
const normalized = trimmed.toLowerCase();
|
||||
if (seen.has(normalized)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(normalized);
|
||||
sanitized.push(trimmed);
|
||||
}
|
||||
|
||||
if (!sanitized.length) {
|
||||
return [...fallback];
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
const DEFAULT_WSS_TRACKERS = Object.freeze([
|
||||
"wss://tracker.openwebtorrent.com",
|
||||
"wss://tracker.fastcast.nz",
|
||||
"wss://tracker.webtorrent.dev",
|
||||
"wss://tracker.sloppyta.co:443/announce",
|
||||
]);
|
||||
|
||||
const SANITIZED_DEFAULT_BLUR_THRESHOLD = coerceNonNegativeInteger(
|
||||
CONFIG_DEFAULT_BLUR_THRESHOLD,
|
||||
1,
|
||||
);
|
||||
const SANITIZED_DEFAULT_AUTOPLAY_BLOCK_THRESHOLD = coerceNonNegativeInteger(
|
||||
CONFIG_DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
1,
|
||||
);
|
||||
const SANITIZED_DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD = coerceNonNegativeInteger(
|
||||
CONFIG_DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
1,
|
||||
);
|
||||
const SANITIZED_DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD = coerceNonNegativeInteger(
|
||||
CONFIG_DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
3,
|
||||
);
|
||||
|
||||
export const DEFAULT_BLUR_THRESHOLD = SANITIZED_DEFAULT_BLUR_THRESHOLD;
|
||||
export const DEFAULT_AUTOPLAY_BLOCK_THRESHOLD =
|
||||
SANITIZED_DEFAULT_AUTOPLAY_BLOCK_THRESHOLD;
|
||||
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;
|
||||
|
||||
const DEFAULT_FLAGS = Object.freeze({
|
||||
URL_FIRST_ENABLED: true, // try URL before magnet in the player
|
||||
ACCEPT_LEGACY_V1: true, // accept v1 magnet-only notes
|
||||
@@ -12,14 +123,9 @@ const DEFAULT_FLAGS = Object.freeze({
|
||||
FEATURE_PUBLISH_NIP71: false,
|
||||
FEATURE_TRUST_SEEDS: true, // Rollback: disable to drop baseline trust seeds without code changes.
|
||||
FEATURE_TRUSTED_HIDE_CONTROLS: true,
|
||||
TRUSTED_MUTE_HIDE_THRESHOLD: 1,
|
||||
TRUSTED_SPAM_HIDE_THRESHOLD: 3,
|
||||
WSS_TRACKERS: Object.freeze([
|
||||
"wss://tracker.openwebtorrent.com",
|
||||
"wss://tracker.fastcast.nz",
|
||||
"wss://tracker.webtorrent.dev",
|
||||
"wss://tracker.sloppyta.co:443/announce",
|
||||
]),
|
||||
TRUSTED_MUTE_HIDE_THRESHOLD: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
TRUSTED_SPAM_HIDE_THRESHOLD: DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
WSS_TRACKERS: DEFAULT_WSS_TRACKERS,
|
||||
});
|
||||
|
||||
const globalScope = typeof globalThis === "object" && globalThis ? globalThis : undefined;
|
||||
@@ -47,77 +153,6 @@ const runtimeFlags = (() => {
|
||||
return initial;
|
||||
})();
|
||||
|
||||
function coerceBoolean(value, fallback) {
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return fallback;
|
||||
}
|
||||
if (["false", "0", "off", "no"].includes(normalized)) {
|
||||
return false;
|
||||
}
|
||||
if (["true", "1", "on", "yes"].includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
function sanitizeTrackerList(candidate) {
|
||||
const input = Array.isArray(candidate) ? candidate : DEFAULT_FLAGS.WSS_TRACKERS;
|
||||
const seen = new Set();
|
||||
const sanitized = [];
|
||||
|
||||
for (const tracker of input) {
|
||||
if (typeof tracker !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmed = tracker.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
if (!/^wss:\/\//i.test(trimmed)) {
|
||||
continue;
|
||||
}
|
||||
const normalized = trimmed.toLowerCase();
|
||||
if (seen.has(normalized)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(normalized);
|
||||
sanitized.push(trimmed);
|
||||
}
|
||||
|
||||
if (!sanitized.length) {
|
||||
return [...DEFAULT_FLAGS.WSS_TRACKERS];
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
function freezeTrackers(list) {
|
||||
return Object.freeze([...list]);
|
||||
}
|
||||
|
||||
function coerceNonNegativeInteger(value, fallback) {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const sanitized = Math.max(0, Math.floor(numeric));
|
||||
if (!Number.isFinite(sanitized)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
export let URL_FIRST_ENABLED = coerceBoolean(
|
||||
runtimeFlags.URL_FIRST_ENABLED,
|
||||
DEFAULT_FLAGS.URL_FIRST_ENABLED
|
||||
@@ -154,7 +189,7 @@ export let FEATURE_TRUSTED_HIDE_CONTROLS = coerceBoolean(
|
||||
);
|
||||
|
||||
export let WSS_TRACKERS = freezeTrackers(
|
||||
sanitizeTrackerList(runtimeFlags.WSS_TRACKERS)
|
||||
sanitizeTrackerList(runtimeFlags.WSS_TRACKERS, DEFAULT_FLAGS.WSS_TRACKERS)
|
||||
);
|
||||
|
||||
export let TRUSTED_MUTE_HIDE_THRESHOLD = coerceNonNegativeInteger(
|
||||
@@ -167,12 +202,6 @@ export let TRUSTED_SPAM_HIDE_THRESHOLD = coerceNonNegativeInteger(
|
||||
DEFAULT_FLAGS.TRUSTED_SPAM_HIDE_THRESHOLD
|
||||
);
|
||||
|
||||
export const DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD =
|
||||
DEFAULT_FLAGS.TRUSTED_MUTE_HIDE_THRESHOLD;
|
||||
|
||||
export const DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD =
|
||||
DEFAULT_FLAGS.TRUSTED_SPAM_HIDE_THRESHOLD;
|
||||
|
||||
Object.defineProperty(runtimeFlags, "URL_FIRST_ENABLED", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
@@ -272,7 +301,9 @@ Object.defineProperty(runtimeFlags, "WSS_TRACKERS", {
|
||||
return [...WSS_TRACKERS];
|
||||
},
|
||||
set(next) {
|
||||
WSS_TRACKERS = freezeTrackers(sanitizeTrackerList(next));
|
||||
WSS_TRACKERS = freezeTrackers(
|
||||
sanitizeTrackerList(next, DEFAULT_FLAGS.WSS_TRACKERS)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
import { getApplication } from "../applicationContext.js";
|
||||
import { IS_DEV_MODE } from "../config.js";
|
||||
import {
|
||||
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
DEFAULT_BLUR_THRESHOLD,
|
||||
} from "../constants.js";
|
||||
import nostrService from "../services/nostrService.js";
|
||||
import moderationService from "../services/moderationService.js";
|
||||
import logger from "../utils/logger.js";
|
||||
@@ -196,8 +200,8 @@ export function createWatchHistorySuppressionStage({
|
||||
|
||||
export function createModerationStage({
|
||||
stageName = "moderation",
|
||||
autoplayThreshold = 2,
|
||||
blurThreshold = 3,
|
||||
autoplayThreshold = DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
blurThreshold = DEFAULT_BLUR_THRESHOLD,
|
||||
reportType = "nudity",
|
||||
service = null,
|
||||
getService,
|
||||
@@ -213,10 +217,10 @@ export function createModerationStage({
|
||||
|
||||
const normalizedAutoplayThreshold = Number.isFinite(autoplayThreshold)
|
||||
? Math.max(0, Math.floor(autoplayThreshold))
|
||||
: 2;
|
||||
: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD;
|
||||
const normalizedBlurThreshold = Number.isFinite(blurThreshold)
|
||||
? Math.max(0, Math.floor(blurThreshold))
|
||||
: 3;
|
||||
: DEFAULT_BLUR_THRESHOLD;
|
||||
const normalizedMuteHideThreshold = sanitizeThreshold(
|
||||
trustedMuteHideThreshold,
|
||||
Number.POSITIVE_INFINITY,
|
||||
|
||||
+35
-8
@@ -1,4 +1,6 @@
|
||||
import {
|
||||
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
DEFAULT_BLUR_THRESHOLD,
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
getTrustedMuteHideThreshold,
|
||||
@@ -33,41 +35,66 @@ const MODERATION_SETTINGS_STORAGE_KEY = "bitvid:moderationSettings:v1";
|
||||
const MODERATION_SETTINGS_STORAGE_VERSION = 2;
|
||||
|
||||
function computeDefaultModerationSettings() {
|
||||
let runtimeMuteHide = DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD;
|
||||
let runtimeSpamHide = DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD;
|
||||
const defaultBlur = sanitizeModerationThreshold(
|
||||
DEFAULT_BLUR_THRESHOLD,
|
||||
DEFAULT_BLUR_THRESHOLD,
|
||||
);
|
||||
const defaultAutoplay = sanitizeModerationThreshold(
|
||||
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
);
|
||||
const defaultTrustedMute = sanitizeModerationThreshold(
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
);
|
||||
const defaultTrustedSpam = sanitizeModerationThreshold(
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
);
|
||||
|
||||
let runtimeMuteHide = defaultTrustedMute;
|
||||
let runtimeSpamHide = defaultTrustedSpam;
|
||||
|
||||
try {
|
||||
if (typeof getTrustedMuteHideThreshold === "function") {
|
||||
runtimeMuteHide = getTrustedMuteHideThreshold();
|
||||
runtimeMuteHide = sanitizeModerationThreshold(
|
||||
getTrustedMuteHideThreshold(),
|
||||
defaultTrustedMute,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
"[cache.computeDefaultModerationSettings] Failed to read runtime mute hide threshold:",
|
||||
error,
|
||||
);
|
||||
runtimeMuteHide = defaultTrustedMute;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof getTrustedSpamHideThreshold === "function") {
|
||||
runtimeSpamHide = getTrustedSpamHideThreshold();
|
||||
runtimeSpamHide = sanitizeModerationThreshold(
|
||||
getTrustedSpamHideThreshold(),
|
||||
defaultTrustedSpam,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
"[cache.computeDefaultModerationSettings] Failed to read runtime spam hide threshold:",
|
||||
error,
|
||||
);
|
||||
runtimeSpamHide = defaultTrustedSpam;
|
||||
}
|
||||
|
||||
return {
|
||||
blurThreshold: 1,
|
||||
autoplayBlockThreshold: 1,
|
||||
blurThreshold: defaultBlur,
|
||||
autoplayBlockThreshold: defaultAutoplay,
|
||||
trustedMuteHideThreshold: sanitizeModerationThreshold(
|
||||
runtimeMuteHide,
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
defaultTrustedMute,
|
||||
),
|
||||
trustedSpamHideThreshold: sanitizeModerationThreshold(
|
||||
runtimeSpamHide,
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
defaultTrustedSpam,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ import {
|
||||
} from "../config.js";
|
||||
import { normalizeDesignSystemContext } from "../designSystem.js";
|
||||
import {
|
||||
getTrustedMuteHideThreshold,
|
||||
getTrustedSpamHideThreshold,
|
||||
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
DEFAULT_BLUR_THRESHOLD,
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
RUNTIME_FLAGS,
|
||||
} from "../constants.js";
|
||||
import { getProviderMetadata } from "../services/authProviders/index.js";
|
||||
@@ -65,10 +67,10 @@ function createInternalDefaultNwcSettings() {
|
||||
}
|
||||
|
||||
const DEFAULT_INTERNAL_MODERATION_SETTINGS = Object.freeze({
|
||||
blurThreshold: 1,
|
||||
autoplayBlockThreshold: 1,
|
||||
trustedMuteHideThreshold: getTrustedMuteHideThreshold(),
|
||||
trustedSpamHideThreshold: getTrustedSpamHideThreshold(),
|
||||
blurThreshold: DEFAULT_BLUR_THRESHOLD,
|
||||
autoplayBlockThreshold: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
trustedMuteHideThreshold: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
trustedSpamHideThreshold: DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
});
|
||||
|
||||
function createInternalDefaultModerationSettings() {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createModerationStage } from "../js/feedEngine/stages.js";
|
||||
import {
|
||||
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
DEFAULT_BLUR_THRESHOLD,
|
||||
} from "../js/constants.js";
|
||||
|
||||
test("moderation stage enforces admin lists without whitelist bypass", async () => {
|
||||
const whitelistedHex = "1".repeat(64);
|
||||
@@ -55,8 +59,8 @@ test("moderation stage enforces admin lists without whitelist bypass", async ()
|
||||
const stage = createModerationStage({
|
||||
service,
|
||||
reportType: "nudity",
|
||||
autoplayThreshold: 1,
|
||||
blurThreshold: 1,
|
||||
autoplayThreshold: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
blurThreshold: DEFAULT_BLUR_THRESHOLD,
|
||||
});
|
||||
|
||||
const items = [
|
||||
|
||||
@@ -3,6 +3,17 @@ import assert from "node:assert/strict";
|
||||
import { JSDOM } from "jsdom";
|
||||
|
||||
import ProfileModalController from "../../js/ui/profileModalController.js";
|
||||
import {
|
||||
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
DEFAULT_BLUR_THRESHOLD,
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
} from "../../js/constants.js";
|
||||
|
||||
const DEFAULT_BLUR_STRING = String(DEFAULT_BLUR_THRESHOLD);
|
||||
const DEFAULT_AUTOPLAY_STRING = String(DEFAULT_AUTOPLAY_BLOCK_THRESHOLD);
|
||||
const DEFAULT_TRUSTED_MUTE_STRING = String(DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD);
|
||||
const DEFAULT_TRUSTED_SPAM_STRING = String(DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD);
|
||||
|
||||
const TEMPLATE_HTML = `
|
||||
<div id="profileModal">
|
||||
@@ -67,18 +78,18 @@ beforeEach(async () => {
|
||||
};
|
||||
|
||||
storedSettings = {
|
||||
blurThreshold: 1,
|
||||
autoplayBlockThreshold: 1,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 3,
|
||||
blurThreshold: DEFAULT_BLUR_THRESHOLD,
|
||||
autoplayBlockThreshold: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
trustedMuteHideThreshold: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
trustedSpamHideThreshold: DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
};
|
||||
moderationService = {
|
||||
getDefaultModerationSettings() {
|
||||
return {
|
||||
blurThreshold: 1,
|
||||
autoplayBlockThreshold: 1,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 3,
|
||||
blurThreshold: DEFAULT_BLUR_THRESHOLD,
|
||||
autoplayBlockThreshold: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
trustedMuteHideThreshold: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
trustedSpamHideThreshold: DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
};
|
||||
},
|
||||
getActiveModerationSettings() {
|
||||
@@ -87,7 +98,8 @@ beforeEach(async () => {
|
||||
updateModerationSettings(partial = {}) {
|
||||
if (Object.prototype.hasOwnProperty.call(partial, "blurThreshold")) {
|
||||
const value = partial.blurThreshold;
|
||||
storedSettings.blurThreshold = value === null ? 1 : value;
|
||||
storedSettings.blurThreshold =
|
||||
value === null ? DEFAULT_BLUR_THRESHOLD : value;
|
||||
}
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
@@ -96,7 +108,8 @@ beforeEach(async () => {
|
||||
)
|
||||
) {
|
||||
const value = partial.autoplayBlockThreshold;
|
||||
storedSettings.autoplayBlockThreshold = value === null ? 1 : value;
|
||||
storedSettings.autoplayBlockThreshold =
|
||||
value === null ? DEFAULT_AUTOPLAY_BLOCK_THRESHOLD : value;
|
||||
}
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
@@ -105,7 +118,8 @@ beforeEach(async () => {
|
||||
)
|
||||
) {
|
||||
const value = partial.trustedMuteHideThreshold;
|
||||
storedSettings.trustedMuteHideThreshold = value === null ? 1 : value;
|
||||
storedSettings.trustedMuteHideThreshold =
|
||||
value === null ? DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD : value;
|
||||
}
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
@@ -114,16 +128,17 @@ beforeEach(async () => {
|
||||
)
|
||||
) {
|
||||
const value = partial.trustedSpamHideThreshold;
|
||||
storedSettings.trustedSpamHideThreshold = value === null ? 3 : value;
|
||||
storedSettings.trustedSpamHideThreshold =
|
||||
value === null ? DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD : value;
|
||||
}
|
||||
return { ...storedSettings };
|
||||
},
|
||||
resetModerationSettings() {
|
||||
storedSettings = {
|
||||
blurThreshold: 1,
|
||||
autoplayBlockThreshold: 1,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 3,
|
||||
blurThreshold: DEFAULT_BLUR_THRESHOLD,
|
||||
autoplayBlockThreshold: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
trustedMuteHideThreshold: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
trustedSpamHideThreshold: DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
};
|
||||
return { ...storedSettings };
|
||||
},
|
||||
@@ -185,10 +200,10 @@ afterEach(() => {
|
||||
test("moderation settings save updates service and disables control", async () => {
|
||||
await controller.load();
|
||||
|
||||
assert.equal(controller.moderationBlurInput.value, "1");
|
||||
assert.equal(controller.moderationAutoplayInput.value, "1");
|
||||
assert.equal(controller.moderationMuteHideInput.value, "1");
|
||||
assert.equal(controller.moderationSpamHideInput.value, "3");
|
||||
assert.equal(controller.moderationBlurInput.value, DEFAULT_BLUR_STRING);
|
||||
assert.equal(controller.moderationAutoplayInput.value, DEFAULT_AUTOPLAY_STRING);
|
||||
assert.equal(controller.moderationMuteHideInput.value, DEFAULT_TRUSTED_MUTE_STRING);
|
||||
assert.equal(controller.moderationSpamHideInput.value, DEFAULT_TRUSTED_SPAM_STRING);
|
||||
assert.equal(controller.moderationSaveButton.disabled, true);
|
||||
|
||||
controller.moderationBlurInput.value = "5";
|
||||
@@ -249,15 +264,15 @@ test("moderation reset restores defaults and clearing inputs uses defaults", asy
|
||||
const resetContext = await controller.handleModerationSettingsReset();
|
||||
assert.equal(resetContext.success, true);
|
||||
assert.deepEqual(storedSettings, {
|
||||
blurThreshold: 1,
|
||||
autoplayBlockThreshold: 1,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 3,
|
||||
blurThreshold: DEFAULT_BLUR_THRESHOLD,
|
||||
autoplayBlockThreshold: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
trustedMuteHideThreshold: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
trustedSpamHideThreshold: DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
});
|
||||
assert.equal(controller.moderationBlurInput.value, "1");
|
||||
assert.equal(controller.moderationAutoplayInput.value, "1");
|
||||
assert.equal(controller.moderationMuteHideInput.value, "1");
|
||||
assert.equal(controller.moderationSpamHideInput.value, "3");
|
||||
assert.equal(controller.moderationBlurInput.value, DEFAULT_BLUR_STRING);
|
||||
assert.equal(controller.moderationAutoplayInput.value, DEFAULT_AUTOPLAY_STRING);
|
||||
assert.equal(controller.moderationMuteHideInput.value, DEFAULT_TRUSTED_MUTE_STRING);
|
||||
assert.equal(controller.moderationSpamHideInput.value, DEFAULT_TRUSTED_SPAM_STRING);
|
||||
assert.equal(
|
||||
controller.moderationStatusText.textContent,
|
||||
"Moderation defaults restored.",
|
||||
@@ -276,8 +291,8 @@ test("moderation reset restores defaults and clearing inputs uses defaults", asy
|
||||
assert.equal(saveResult.success, true);
|
||||
assert.deepEqual(storedSettings, {
|
||||
blurThreshold: 7,
|
||||
autoplayBlockThreshold: 1,
|
||||
trustedMuteHideThreshold: 1,
|
||||
autoplayBlockThreshold: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
trustedMuteHideThreshold: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
trustedSpamHideThreshold: 8,
|
||||
});
|
||||
|
||||
@@ -302,9 +317,39 @@ test("moderation reset restores defaults and clearing inputs uses defaults", asy
|
||||
const revertContext = await controller.handleModerationSettingsSave();
|
||||
assert.equal(revertContext.success, true);
|
||||
assert.deepEqual(storedSettings, {
|
||||
blurThreshold: 1,
|
||||
autoplayBlockThreshold: 1,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 3,
|
||||
blurThreshold: DEFAULT_BLUR_THRESHOLD,
|
||||
autoplayBlockThreshold: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
trustedMuteHideThreshold: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
trustedSpamHideThreshold: DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
});
|
||||
});
|
||||
|
||||
test("guest fallback uses config moderation defaults", async () => {
|
||||
moderationService.getDefaultModerationSettings = () => null;
|
||||
moderationService.getActiveModerationSettings = () => null;
|
||||
storedSettings = null;
|
||||
|
||||
await controller.load();
|
||||
|
||||
assert.equal(controller.moderationBlurInput.value, DEFAULT_BLUR_STRING);
|
||||
assert.equal(
|
||||
controller.moderationAutoplayInput.value,
|
||||
DEFAULT_AUTOPLAY_STRING,
|
||||
);
|
||||
assert.equal(
|
||||
controller.moderationMuteHideInput.value,
|
||||
DEFAULT_TRUSTED_MUTE_STRING,
|
||||
);
|
||||
assert.equal(
|
||||
controller.moderationSpamHideInput.value,
|
||||
DEFAULT_TRUSTED_SPAM_STRING,
|
||||
);
|
||||
|
||||
const defaults = controller.getModerationSettingsDefaults();
|
||||
assert.deepEqual(defaults, {
|
||||
blurThreshold: DEFAULT_BLUR_THRESHOLD,
|
||||
autoplayBlockThreshold: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
|
||||
trustedMuteHideThreshold: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
trustedSpamHideThreshold: DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user