mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Admin moderation was author/community-level only. Admins (any editor — same gate as the author blacklist) can now hide a SINGLE offending video. - Record: a published kind-30000 NIP-51 list under d-tag bitvid:admin:event-blacklist, carrying blocked event ids as `e` tags (NOTE_TYPES.ADMIN_EVENT_BLACKLIST schema). Loaded/persisted through the existing admin-list machinery (adminListStore) and cached like whitelist/blacklist. - accessControl: eventBlacklist Set + getEventBlacklist/isEventBlacklisted/ addToEventBlacklist/removeFromEventBlacklist (editor-gated; normalizes hex or nevent/note) + onEventBlacklistChange. - Render filter already existed (app.blacklistedEventIds, honored by every feed/grid/ search/playback path) but was fed only by the static ADMIN_INITIAL_EVENT_BLACKLIST config. app._rebuildBlacklistedEventIds() now merges that static base with the dynamic admin list, and an onEventBlacklistChange listener rebuilds + refreshes grids on change. - UI: a "Block this video" item in the per-video ⋯ menu (editor-only), handled by handleBlacklistEventAction (js/ui/moreMenu/blacklistEventAction.js). Helpers extracted to js/adminEventBlacklistHelpers.js to keep adminListStore / accessControl / moreMenuController under their size caps. Tests: tests/admin-event-blacklist.test.mjs (9). Build + lint clean; admin/access/moderation suites 56/56. Co-Authored-By: Claude Opus 4.8 <[email protected]>
81 lines
2.5 KiB
JavaScript
81 lines
2.5 KiB
JavaScript
// Helpers for the per-event (per-video) admin block list (#25). Entries are 64-char
|
|
// hex event ids carried as `e` tags on a kind-30000 NIP-51 list — not npubs.
|
|
|
|
import { buildAdminListEvent } from "./nostrEventSchemas.js";
|
|
|
|
export const HEX_EVENT_ID_REGEX = /^[0-9a-f]{64}$/i;
|
|
|
|
// Unsigned kind-30000 event for the per-event block list (`e` tags via its schema).
|
|
export function buildEventBlacklistEvent(actorHex, eventIds) {
|
|
return buildAdminListEvent("eventBlacklist", {
|
|
pubkey: actorHex,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
hexPubkeys: sanitizeEventIdList(eventIds),
|
|
});
|
|
}
|
|
|
|
// Deduped, lowercased hex event ids; anything malformed is dropped.
|
|
export function sanitizeEventIdList(values) {
|
|
const out = [];
|
|
const seen = new Set();
|
|
for (const value of Array.isArray(values) ? values : []) {
|
|
const id = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
if (HEX_EVENT_ID_REGEX.test(id) && !seen.has(id)) {
|
|
seen.add(id);
|
|
out.push(id);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function extractEventIdsFromEvent(event) {
|
|
if (!event || !Array.isArray(event.tags)) {
|
|
return [];
|
|
}
|
|
return sanitizeEventIdList(
|
|
event.tags
|
|
.filter((tag) => Array.isArray(tag) && tag[0] === "e" && tag[1])
|
|
.map((tag) => tag[1]),
|
|
);
|
|
}
|
|
|
|
// Normalize a per-event block entry to a 64-char hex event id. Accepts a raw hex id or
|
|
// a NIP-19 nevent/note string (the ⋯ menu passes the video's hex id directly).
|
|
export function normalizeEventId(value) {
|
|
if (typeof value !== "string") {
|
|
return "";
|
|
}
|
|
const trimmed = value.trim();
|
|
if (HEX_EVENT_ID_REGEX.test(trimmed)) {
|
|
return trimmed.toLowerCase();
|
|
}
|
|
try {
|
|
const tools =
|
|
(typeof window !== "undefined" ? window?.NostrTools : null) ||
|
|
(typeof globalThis !== "undefined" ? globalThis?.NostrTools : null);
|
|
const decoded = tools?.nip19?.decode?.(trimmed);
|
|
if (decoded?.type === "nevent" && HEX_EVENT_ID_REGEX.test(decoded.data?.id || "")) {
|
|
return decoded.data.id.toLowerCase();
|
|
}
|
|
if (decoded?.type === "note" && HEX_EVENT_ID_REGEX.test(decoded.data || "")) {
|
|
return decoded.data.toLowerCase();
|
|
}
|
|
} catch (error) {
|
|
// not a decodable pointer
|
|
}
|
|
return "";
|
|
}
|
|
|
|
export function normalizeEventIdList(values) {
|
|
const out = [];
|
|
const seen = new Set();
|
|
for (const value of Array.isArray(values) ? values : []) {
|
|
const id = normalizeEventId(value);
|
|
if (id && !seen.has(id)) {
|
|
seen.add(id);
|
|
out.push(id);
|
|
}
|
|
}
|
|
return out;
|
|
}
|