mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Fix viewer mute blur regression and update modal overrides
This commit is contained in:
1 parent
3936b1ed9f
commit
9dfe7ef20e
5 files changed
+193
-11
No files matched your search
@@ -7704,12 +7704,34 @@ class Application {
|
||||
? Math.max(0, Math.floor(existingModeration.trustedCount))
|
||||
: this.deriveModerationTrustedCount(summary, reportType);
|
||||
|
||||
const viewerMuted = existingModeration.viewerMuted === true;
|
||||
const existingBlockAutoplay = existingModeration.blockAutoplay === true;
|
||||
const existingBlurThumbnail = existingModeration.blurThumbnail === true;
|
||||
const existingBlurReason =
|
||||
typeof existingModeration.blurReason === "string"
|
||||
? existingModeration.blurReason.trim()
|
||||
: "";
|
||||
|
||||
const thresholds = this.getActiveModerationThresholds();
|
||||
const computedBlockAutoplay =
|
||||
const computedBlockAutoplayBase =
|
||||
trustedCount >= thresholds.autoplayBlockThreshold || trustedMuted;
|
||||
const computedBlockAutoplay =
|
||||
computedBlockAutoplayBase || viewerMuted || existingBlockAutoplay;
|
||||
|
||||
const blurFromReports = trustedCount >= thresholds.blurThreshold;
|
||||
let computedBlurThumbnail = blurFromReports;
|
||||
let computedBlurReason = computedBlurThumbnail ? "trusted-report" : "";
|
||||
let computedBlurThumbnail =
|
||||
blurFromReports || trustedMuted || viewerMuted || existingBlurThumbnail;
|
||||
let computedBlurReason = "";
|
||||
|
||||
if (blurFromReports) {
|
||||
computedBlurReason = "trusted-report";
|
||||
} else if (trustedMuted) {
|
||||
computedBlurReason = "trusted-mute";
|
||||
} else if (viewerMuted) {
|
||||
computedBlurReason = "viewer-mute";
|
||||
} else if (existingBlurThumbnail && existingBlurReason) {
|
||||
computedBlurReason = existingBlurReason;
|
||||
}
|
||||
|
||||
const muteHideThreshold = Number.isFinite(thresholds.trustedMuteHideThreshold)
|
||||
? Math.max(0, Math.floor(thresholds.trustedMuteHideThreshold))
|
||||
@@ -7745,16 +7767,20 @@ class Application {
|
||||
hideTriggered = true;
|
||||
}
|
||||
|
||||
if (!computedBlurThumbnail && (trustedMuted || hideTriggered)) {
|
||||
if (!computedBlurThumbnail && (viewerMuted || trustedMuted || hideTriggered)) {
|
||||
computedBlurThumbnail = true;
|
||||
if (hideTriggered) {
|
||||
computedBlurReason = hideReason || "trusted-hide";
|
||||
} else if (viewerMuted) {
|
||||
computedBlurReason = "viewer-mute";
|
||||
} else if (trustedMuted) {
|
||||
computedBlurReason = "trusted-mute";
|
||||
}
|
||||
} else if (computedBlurThumbnail) {
|
||||
if (hideTriggered) {
|
||||
computedBlurReason = hideReason || "trusted-hide";
|
||||
} else if (viewerMuted && !blurFromReports && !trustedMuted) {
|
||||
computedBlurReason = "viewer-mute";
|
||||
} else if (trustedMuted && !blurFromReports) {
|
||||
computedBlurReason = "trusted-mute";
|
||||
} else if (!computedBlurReason && blurFromReports) {
|
||||
@@ -7762,6 +7788,10 @@ class Application {
|
||||
}
|
||||
}
|
||||
|
||||
if (computedBlurThumbnail && !computedBlurReason && existingBlurReason) {
|
||||
computedBlurReason = existingBlurReason;
|
||||
}
|
||||
|
||||
const hideCounts = hideTriggered
|
||||
? {
|
||||
trustedMuteCount,
|
||||
|
||||
+24
-3
@@ -768,6 +768,7 @@ export function createModerationStage({
|
||||
let trustedMuted = false;
|
||||
let trustedMuters = [];
|
||||
let trustedMuteCount = 0;
|
||||
let viewerMuted = false;
|
||||
if (videoId) {
|
||||
try {
|
||||
if (typeof resolvedService.getTrustedReportSummary === "function") {
|
||||
@@ -827,10 +828,14 @@ export function createModerationStage({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof resolvedService.isAuthorMutedByViewer === "function") {
|
||||
viewerMuted = resolvedService.isAuthorMutedByViewer(authorHex) === true;
|
||||
}
|
||||
} catch (error) {
|
||||
context?.log?.(`[${stageName}] Failed to resolve trusted mute info`, error);
|
||||
context?.log?.(`[${stageName}] Failed to resolve trusted/viewer mute info`, error);
|
||||
trustedMuted = false;
|
||||
trustedMuters = [];
|
||||
viewerMuted = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,7 +855,7 @@ export function createModerationStage({
|
||||
}
|
||||
|
||||
const blockAutoplay =
|
||||
trustedCount >= normalizedAutoplayThreshold || trustedMuted;
|
||||
trustedCount >= normalizedAutoplayThreshold || trustedMuted || viewerMuted;
|
||||
const blurFromReports = trustedCount >= normalizedBlurThreshold;
|
||||
let blurThumbnail = blurFromReports;
|
||||
let blurReason = blurThumbnail ? "trusted-report" : "";
|
||||
@@ -878,6 +883,7 @@ export function createModerationStage({
|
||||
metadataModeration.trustedMuted = trustedMuted;
|
||||
metadataModeration.trustedMuters = trustedMuters.slice();
|
||||
metadataModeration.trustedMuteCount = trustedMuteCount;
|
||||
metadataModeration.viewerMuted = viewerMuted;
|
||||
|
||||
if (!video.moderation || typeof video.moderation !== "object") {
|
||||
video.moderation = {};
|
||||
@@ -900,6 +906,7 @@ export function createModerationStage({
|
||||
delete video.moderation.trustedMuteCount;
|
||||
}
|
||||
}
|
||||
video.moderation.viewerMuted = viewerMuted;
|
||||
if (Array.isArray(trustedReporters) && trustedReporters.length) {
|
||||
video.moderation.trustedReporters = trustedReporters.slice();
|
||||
} else if (video.moderation.trustedReporters) {
|
||||
@@ -980,16 +987,20 @@ export function createModerationStage({
|
||||
}
|
||||
}
|
||||
|
||||
if (!blurThumbnail && (trustedMuted || hideTriggered)) {
|
||||
if (!blurThumbnail && (viewerMuted || trustedMuted || hideTriggered)) {
|
||||
blurThumbnail = true;
|
||||
if (hideTriggered) {
|
||||
blurReason = hideReason || "trusted-hide";
|
||||
} else if (viewerMuted) {
|
||||
blurReason = "viewer-mute";
|
||||
} else if (trustedMuted) {
|
||||
blurReason = "trusted-mute";
|
||||
}
|
||||
} else if (blurThumbnail) {
|
||||
if (hideTriggered) {
|
||||
blurReason = hideReason || "trusted-hide";
|
||||
} else if (viewerMuted && !blurFromReports) {
|
||||
blurReason = "viewer-mute";
|
||||
} else if (trustedMuted && !blurFromReports) {
|
||||
blurReason = "trusted-mute";
|
||||
} else if (!blurReason && blurFromReports) {
|
||||
@@ -1048,6 +1059,16 @@ export function createModerationStage({
|
||||
});
|
||||
}
|
||||
|
||||
if (viewerMuted) {
|
||||
context?.addWhy?.({
|
||||
stage: stageName,
|
||||
type: "moderation",
|
||||
reason: "viewer-mute",
|
||||
videoId: videoId || null,
|
||||
pubkey: authorHex || authorPubkey || null,
|
||||
});
|
||||
}
|
||||
|
||||
if (hideTriggered) {
|
||||
const hideDetail = {
|
||||
stage: stageName,
|
||||
|
||||
@@ -3989,11 +3989,11 @@ export class VideoModal {
|
||||
const badge = this.moderationBadge;
|
||||
const textEl = this.moderationBadgeText;
|
||||
|
||||
const shouldShow = Boolean(context?.shouldShow);
|
||||
const showModeration = Boolean(context?.shouldShow);
|
||||
const shouldBlur = Boolean(context?.activeBlur && !context?.overrideActive);
|
||||
const hiddenActive = Boolean(context?.activeHidden && !context?.overrideActive);
|
||||
|
||||
if (!shouldShow) {
|
||||
if (!showModeration) {
|
||||
if (stage && stage.dataset.visualState === "blurred") {
|
||||
delete stage.dataset.visualState;
|
||||
}
|
||||
@@ -4031,7 +4031,11 @@ export class VideoModal {
|
||||
}
|
||||
|
||||
if (overlay) {
|
||||
overlay.removeAttribute("hidden");
|
||||
if (showModeration && !context?.overrideActive) {
|
||||
overlay.removeAttribute("hidden");
|
||||
} else {
|
||||
overlay.setAttribute("hidden", "");
|
||||
}
|
||||
overlay.dataset.overlayState = context?.overrideActive ? "override" : "active";
|
||||
}
|
||||
|
||||
@@ -4129,7 +4133,12 @@ export class VideoModal {
|
||||
const actions = this.ensureModerationActionsContainer();
|
||||
let actionsAttached = false;
|
||||
|
||||
if (context?.allowOverride) {
|
||||
const allowOverride =
|
||||
context?.overrideActive === true ||
|
||||
context?.allowOverride === true ||
|
||||
(shouldBlur && !context?.overrideActive && context?.blurReason !== "viewer-mute");
|
||||
|
||||
if (allowOverride) {
|
||||
const mode = context.overrideActive ? "hide" : "override";
|
||||
const primaryButton = this.ensureModerationPrimaryButton(mode);
|
||||
if (primaryButton) {
|
||||
|
||||
@@ -496,6 +496,76 @@ test("moderation stage propagates whitelist, muters, and threshold updates", asy
|
||||
assert.equal(updatedMuted.video.moderation.blurReason, undefined);
|
||||
});
|
||||
|
||||
test("moderation stage blurs viewer-muted authors", async () => {
|
||||
const mutedHex = "f".repeat(64);
|
||||
|
||||
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() {
|
||||
return false;
|
||||
},
|
||||
isAuthorMutedByViewer(pubkey) {
|
||||
return pubkey === mutedHex;
|
||||
},
|
||||
};
|
||||
|
||||
const stage = createModerationStage({
|
||||
service,
|
||||
autoplayThreshold: Number.POSITIVE_INFINITY,
|
||||
blurThreshold: Number.POSITIVE_INFINITY,
|
||||
trustedMuteHideThreshold: Number.POSITIVE_INFINITY,
|
||||
trustedReportHideThreshold: Number.POSITIVE_INFINITY,
|
||||
});
|
||||
|
||||
const items = [{ video: { id: "viewer-muted", pubkey: mutedHex }, metadata: {} }];
|
||||
|
||||
const reasons = [];
|
||||
const context = {
|
||||
addWhy(detail) {
|
||||
reasons.push(detail);
|
||||
return detail;
|
||||
},
|
||||
log() {},
|
||||
};
|
||||
|
||||
const result = await stage(items, context);
|
||||
|
||||
assert.equal(result.length, 1);
|
||||
const entry = result[0];
|
||||
|
||||
assert.equal(entry.video.moderation.viewerMuted, true);
|
||||
assert.equal(entry.metadata.moderation.viewerMuted, true);
|
||||
assert.equal(entry.video.moderation.blockAutoplay, true);
|
||||
assert.equal(entry.video.moderation.blurThumbnail, true);
|
||||
assert.equal(entry.video.moderation.blurReason, "viewer-mute");
|
||||
assert.equal(entry.metadata.moderation.blurReason, "viewer-mute");
|
||||
assert.equal(entry.video.moderation.hidden, false);
|
||||
assert.equal(entry.metadata.moderation.hidden, false);
|
||||
assert.equal(entry.video.moderation.hideReason, undefined);
|
||||
assert.equal(entry.metadata.moderation.hideReason, undefined);
|
||||
assert.equal(entry.metadata.moderation.hideCounts, undefined);
|
||||
|
||||
const reasonSet = new Set(reasons.map((detail) => detail.reason));
|
||||
assert(reasonSet.has("blur"));
|
||||
assert(reasonSet.has("viewer-mute"));
|
||||
});
|
||||
|
||||
test("moderation stage clears cached reporters and muters after service signals", async () => {
|
||||
const reporterHex = "d".repeat(64);
|
||||
const muterHex = "e".repeat(64);
|
||||
|
||||
@@ -271,6 +271,58 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test("VideoCard blurs viewer-muted creators", async (t) => {
|
||||
const { document } = setupDom(t);
|
||||
withMockedNostrTools(t);
|
||||
|
||||
const app = await createModerationAppHarness();
|
||||
const videoId = "d".repeat(64);
|
||||
|
||||
const video = {
|
||||
id: videoId,
|
||||
title: "Muted Creator Clip",
|
||||
pubkey: "e".repeat(64),
|
||||
moderation: {
|
||||
viewerMuted: true,
|
||||
blockAutoplay: true,
|
||||
blurThumbnail: true,
|
||||
blurReason: "viewer-mute",
|
||||
trustedCount: 0,
|
||||
trustedMuted: false,
|
||||
trustedReporters: [],
|
||||
},
|
||||
};
|
||||
|
||||
app.videosMap.set(video.id, video);
|
||||
app.currentVideo = video;
|
||||
app.decorateVideoModeration(video);
|
||||
|
||||
const card = new VideoCard({
|
||||
document,
|
||||
video,
|
||||
formatters: {
|
||||
formatTimeAgo: () => "moments ago",
|
||||
},
|
||||
helpers: {
|
||||
isMagnetSupported: () => false,
|
||||
},
|
||||
});
|
||||
|
||||
document.body.appendChild(card.getRoot());
|
||||
|
||||
assert.equal(card.getRoot().dataset.autoplayPolicy, "blocked");
|
||||
assert.equal(card.thumbnailEl.dataset.thumbnailState, "blurred");
|
||||
|
||||
const context = normalizeVideoModerationContext(card.video?.moderation);
|
||||
assert.equal(context.blurReason, "viewer-mute");
|
||||
assert.equal(context.activeBlur, true);
|
||||
assert.equal(context.activeBlockAutoplay, true);
|
||||
assert.equal(context.trustedCount, 0);
|
||||
assert.equal(card.getRoot().dataset.moderationReportCount, undefined);
|
||||
assert.ok(card.moderationBadgeEl);
|
||||
assert.equal(card.moderationBadgeEl.dataset.moderationState, "blocked");
|
||||
});
|
||||
|
||||
test("VideoCard blurs thumbnails when trusted mute triggers without reports", async (t) => {
|
||||
const { document } = setupDom(t);
|
||||
withMockedNostrTools(t);
|
||||
|
||||
Reference in new issue
Block a user