Add hashtag preference schema and feature flag

This commit is contained in:
thePR0M3TH3AN committed 2025-10-25 15:28:15 -04:00
1 parent 3d1a615ed3
commit 372cb8ca6f
3 files changed
+78

No files matched your search

+12
View File
@@ -147,6 +147,7 @@ import { updateWatchHistoryListWithDefaultClient } from "./nostrWatchHistoryFaca
| Watch history chunk (`NOTE_TYPES.WATCH_HISTORY_CHUNK`) | `WATCH_HISTORY_KIND` (default `30079`, clients also read legacy `30078`) | `['d', <snapshotId:index>]`, `['encrypted','nip04']`, `['snapshot', <id>]`, `['chunk', <index>, <total>]`, optional leading `['head','1']` on the first chunk, pointer tags for each item, plus schema append tags | NIP-04 encrypted JSON chunk (`{ version, snapshot, chunkIndex, totalChunks, items[] }`) |
| Subscription list (`NOTE_TYPES.SUBSCRIPTION_LIST`) | `30002` | `['d', 'subscriptions']` | NIP-04 encrypted JSON `{ subPubkeys: string[] }` |
| User block list (`NOTE_TYPES.USER_BLOCK_LIST`) | `30002` | `['d', 'user-blocks']` | NIP-04 encrypted JSON `{ blockedPubkeys: string[] }` |
| Hashtag preferences (`NOTE_TYPES.HASHTAG_PREFERENCES`) | `30005` | `['d', 'bitvid:tag-preferences']` plus schema-appended `['encrypted','nip44_v2']` | NIP-44 encrypted JSON `{ version, interests: string[], disinterests: string[] }` |
| Admin moderation list (`NOTE_TYPES.ADMIN_MODERATION_LIST`) | `30000` | `['d', 'bitvid:admin:editors']`, repeated `['p', <pubkey>]` entries | Empty content |
| Admin blacklist (`NOTE_TYPES.ADMIN_BLACKLIST`) | `30000` | `['d', 'bitvid:admin:blacklist']`, repeated `['p', <pubkey>]` entries | Empty content |
| Admin whitelist (`NOTE_TYPES.ADMIN_WHITELIST`) | `30000` | `['d', 'bitvid:admin:whitelist']`, repeated `['p', <pubkey>]` entries | Empty content |
@@ -157,6 +158,17 @@ If you introduce a new Nostr feature, add its schema to
`js/nostrEventSchemas.js` so that the catalogue stays complete and so existing
builders inherit the same debugging knobs.
### Hashtag preference lists
Hashtag preference events (`NOTE_TYPES.HASHTAG_PREFERENCES`) live on a
replaceable `30005` list with a stable identifier tag of
`['d','bitvid:tag-preferences']`. The builder appends `['encrypted','nip44_v2']`
so downstream clients can detect that the payload is encrypted. The `content`
field must be a NIP-44 ciphertext representing the JSON shape
`{ version, interests: string[], disinterests: string[] }`, allowing clients to
share their preferred and muted hashtags without exposing the raw preferences on
relays.
### NIP-71 rollout
bitvid now emits a paired NIP-71 event (kind `21` for long-form, `22` for short
+28
View File
@@ -147,6 +147,7 @@ const DEFAULT_FLAGS = Object.freeze({
VIEW_FILTER_INCLUDE_LEGACY_VIDEO: false,
FEATURE_WATCH_HISTORY_V2: true,
FEATURE_PUBLISH_NIP71: false,
FEATURE_HASHTAG_PREFERENCES: false,
FEATURE_TRUST_SEEDS: true, // Rollback: disable to drop baseline trust seeds without code changes.
FEATURE_TRUSTED_HIDE_CONTROLS: true,
TRUSTED_MUTE_HIDE_THRESHOLD: DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
@@ -167,6 +168,7 @@ const runtimeFlags = (() => {
DEFAULT_FLAGS.VIEW_FILTER_INCLUDE_LEGACY_VIDEO,
FEATURE_WATCH_HISTORY_V2: DEFAULT_FLAGS.FEATURE_WATCH_HISTORY_V2,
FEATURE_PUBLISH_NIP71: DEFAULT_FLAGS.FEATURE_PUBLISH_NIP71,
FEATURE_HASHTAG_PREFERENCES: DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES,
FEATURE_TRUST_SEEDS: DEFAULT_FLAGS.FEATURE_TRUST_SEEDS,
FEATURE_TRUSTED_HIDE_CONTROLS: DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS,
TRUSTED_MUTE_HIDE_THRESHOLD: DEFAULT_FLAGS.TRUSTED_MUTE_HIDE_THRESHOLD,
@@ -204,6 +206,11 @@ export let FEATURE_PUBLISH_NIP71 = coerceBoolean(
DEFAULT_FLAGS.FEATURE_PUBLISH_NIP71
);
export let FEATURE_HASHTAG_PREFERENCES = coerceBoolean(
runtimeFlags.FEATURE_HASHTAG_PREFERENCES,
DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES
);
export let FEATURE_TRUST_SEEDS = coerceBoolean(
runtimeFlags.FEATURE_TRUST_SEEDS,
DEFAULT_FLAGS.FEATURE_TRUST_SEEDS
@@ -292,6 +299,20 @@ Object.defineProperty(runtimeFlags, "FEATURE_PUBLISH_NIP71", {
},
});
Object.defineProperty(runtimeFlags, "FEATURE_HASHTAG_PREFERENCES", {
configurable: true,
enumerable: true,
get() {
return FEATURE_HASHTAG_PREFERENCES;
},
set(next) {
FEATURE_HASHTAG_PREFERENCES = coerceBoolean(
next,
DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES
);
},
});
Object.defineProperty(runtimeFlags, "FEATURE_TRUST_SEEDS", {
configurable: true,
enumerable: true,
@@ -367,6 +388,7 @@ runtimeFlags.ACCEPT_LEGACY_V1 = ACCEPT_LEGACY_V1;
runtimeFlags.VIEW_FILTER_INCLUDE_LEGACY_VIDEO = VIEW_FILTER_INCLUDE_LEGACY_VIDEO;
runtimeFlags.FEATURE_WATCH_HISTORY_V2 = FEATURE_WATCH_HISTORY_V2;
runtimeFlags.FEATURE_PUBLISH_NIP71 = FEATURE_PUBLISH_NIP71;
runtimeFlags.FEATURE_HASHTAG_PREFERENCES = FEATURE_HASHTAG_PREFERENCES;
runtimeFlags.FEATURE_TRUST_SEEDS = FEATURE_TRUST_SEEDS;
runtimeFlags.FEATURE_TRUSTED_HIDE_CONTROLS = FEATURE_TRUSTED_HIDE_CONTROLS;
runtimeFlags.WSS_TRACKERS = WSS_TRACKERS;
@@ -407,6 +429,11 @@ export function setTrustedHideControlsEnabled(next) {
return FEATURE_TRUSTED_HIDE_CONTROLS;
}
export function setHashtagPreferencesEnabled(next) {
runtimeFlags.FEATURE_HASHTAG_PREFERENCES = next;
return FEATURE_HASHTAG_PREFERENCES;
}
export function resetRuntimeFlags() {
setUrlFirstEnabled(DEFAULT_FLAGS.URL_FIRST_ENABLED);
setAcceptLegacyV1(DEFAULT_FLAGS.ACCEPT_LEGACY_V1);
@@ -414,6 +441,7 @@ export function resetRuntimeFlags() {
DEFAULT_FLAGS.VIEW_FILTER_INCLUDE_LEGACY_VIDEO
);
setWatchHistoryV2Enabled(DEFAULT_FLAGS.FEATURE_WATCH_HISTORY_V2);
setHashtagPreferencesEnabled(DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES);
setTrustSeedsEnabled(DEFAULT_FLAGS.FEATURE_TRUST_SEEDS);
setTrustedHideControlsEnabled(DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS);
setTrustedMuteHideThreshold(DEFAULT_FLAGS.TRUSTED_MUTE_HIDE_THRESHOLD);
+38
View File
@@ -22,6 +22,7 @@ export const NOTE_TYPES = Object.freeze({
WATCH_HISTORY_CHUNK: "watchHistoryChunk",
SUBSCRIPTION_LIST: "subscriptionList",
USER_BLOCK_LIST: "userBlockList",
HASHTAG_PREFERENCES: "hashtagPreferences",
ADMIN_MODERATION_LIST: "adminModerationList",
ADMIN_BLACKLIST: "adminBlacklist",
ADMIN_WHITELIST: "adminWhitelist",
@@ -313,6 +314,21 @@ const BASE_SCHEMAS = {
description: "Encrypted JSON: { blockedPubkeys: string[] }.",
},
},
[NOTE_TYPES.HASHTAG_PREFERENCES]: {
type: NOTE_TYPES.HASHTAG_PREFERENCES,
label: "Hashtag preferences",
kind: 30005,
identifierTag: {
name: "d",
value: "bitvid:tag-preferences",
},
appendTags: [["encrypted", "nip44_v2"]],
content: {
format: "nip44-json",
description:
"NIP-44 encrypted JSON: { version, interests: string[], disinterests: string[] }.",
},
},
[NOTE_TYPES.ADMIN_MODERATION_LIST]: {
type: NOTE_TYPES.ADMIN_MODERATION_LIST,
label: "Admin moderation list",
@@ -1133,6 +1149,28 @@ export function buildBlockListEvent({
};
}
export function buildHashtagPreferenceEvent({
pubkey,
created_at,
content,
}) {
const schema = getNostrEventSchema(NOTE_TYPES.HASHTAG_PREFERENCES);
const tags = [];
const identifierName = schema?.identifierTag?.name || "d";
const identifierValue = schema?.identifierTag?.value || "bitvid:tag-preferences";
if (identifierName && identifierValue) {
tags.push([identifierName, identifierValue]);
}
appendSchemaTags(tags, schema);
return {
kind: schema?.kind ?? 30005,
pubkey,
created_at,
tags,
content: typeof content === "string" ? content : String(content ?? ""),
};
}
function resolveAdminNoteType(listKey) {
switch (listKey) {
case "moderation":