Merge pull request #873 from PR0M3TH3AN/codex/extend-config-for-moderation-settings

Align moderation defaults with instance config
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-24 21:36:38 -04:00
commit 96ff7a8d44
9 files changed
+320 -145

No files matched your search

+40
View File
@@ -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.
*
+18 -4
View File
@@ -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))
+8
View File
@@ -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
View File
@@ -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)
);
},
});
+8 -4
View File
@@ -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
View File
@@ -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,
),
};
}
+8 -6
View File
@@ -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() {
+6 -2
View File
@@ -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,
});
});