mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 21:02:28 +00:00
chore(ai): event schema validation tool + fixes
- Added `scripts/agent/validate-events.mjs` to validate event builders against schemas. - Fixed `NOTE_TYPES.REPOST` schema content format (json instead of empty). - Added `NOTE_TYPES.MUTE_LIST` and `buildMuteListEvent`. - Added `buildProfileMetadataEvent` to standardise Kind 0 creation. - Updated `js/ui/profileModalController.js` to use `buildProfileMetadataEvent`. - Updated `js/userBlocks.js` to use `buildMuteListEvent` for publishing mute list snapshots. - Updated `docs/nostr-event-schemas.md` with new schemas and builders.
This commit is contained in:
1 parent
474071e84a
commit
4d2bb8bf3d
5 files changed
+406
-26
No files matched your search
+91
-2
@@ -28,6 +28,7 @@ export const NOTE_TYPES = Object.freeze({
|
||||
ADMIN_BLACKLIST: "adminBlacklist",
|
||||
ADMIN_WHITELIST: "adminWhitelist",
|
||||
PROFILE_METADATA: "profileMetadata",
|
||||
MUTE_LIST: "muteList",
|
||||
});
|
||||
|
||||
export const SUBSCRIPTION_LIST_IDENTIFIER = "subscriptions";
|
||||
@@ -270,8 +271,8 @@ const BASE_SCHEMAS = {
|
||||
kind: 6,
|
||||
appendTags: DEFAULT_APPEND_TAGS,
|
||||
content: {
|
||||
format: "empty",
|
||||
description: "Content field intentionally empty for pure repost events.",
|
||||
format: "json",
|
||||
description: "Content field contains the JSON-serialized event being reposted.",
|
||||
},
|
||||
},
|
||||
[NOTE_TYPES.NIP71_VIDEO]: {
|
||||
@@ -458,6 +459,17 @@ const BASE_SCHEMAS = {
|
||||
description: "Standard NIP-01 profile metadata (name, about, picture, etc.).",
|
||||
},
|
||||
},
|
||||
[NOTE_TYPES.MUTE_LIST]: {
|
||||
type: NOTE_TYPES.MUTE_LIST,
|
||||
label: "Mute list",
|
||||
kind: 10000,
|
||||
participantTagName: "p",
|
||||
appendTags: DEFAULT_APPEND_TAGS,
|
||||
content: {
|
||||
format: "text",
|
||||
description: "Optional content (often encrypted) with public p tags.",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let schemaOverrides = {};
|
||||
@@ -929,6 +941,83 @@ export function buildRelayListEvent(params) {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildProfileMetadataEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
metadata = {},
|
||||
content,
|
||||
additionalTags = [],
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.PROFILE_METADATA);
|
||||
const tags = [];
|
||||
appendSchemaTags(tags, schema);
|
||||
const sanitizedAdditionalTags = sanitizeAdditionalTags(additionalTags);
|
||||
if (sanitizedAdditionalTags.length) {
|
||||
tags.push(...sanitizedAdditionalTags.map((tag) => tag.slice()));
|
||||
}
|
||||
|
||||
let serializedContent = "";
|
||||
if (typeof content === "string") {
|
||||
serializedContent = content;
|
||||
} else {
|
||||
try {
|
||||
serializedContent = JSON.stringify(metadata || {});
|
||||
} catch (error) {
|
||||
serializedContent = "{}";
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
kind: schema?.kind ?? 0,
|
||||
pubkey,
|
||||
created_at,
|
||||
tags,
|
||||
content: serializedContent,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMuteListEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
created_at,
|
||||
pTags = [],
|
||||
content = "",
|
||||
encrypted = false,
|
||||
encryptionTag = "",
|
||||
additionalTags = [],
|
||||
} = params || {};
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.MUTE_LIST);
|
||||
const tags = [];
|
||||
|
||||
const participantTagName = schema?.participantTagName || "p";
|
||||
if (Array.isArray(pTags)) {
|
||||
pTags.forEach((hex) => {
|
||||
if (typeof hex === "string" && hex.trim()) {
|
||||
tags.push([participantTagName, hex.trim()]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (encrypted && encryptionTag) {
|
||||
tags.push(["encrypted", encryptionTag]);
|
||||
}
|
||||
|
||||
appendSchemaTags(tags, schema);
|
||||
const sanitizedAdditionalTags = sanitizeAdditionalTags(additionalTags);
|
||||
if (sanitizedAdditionalTags.length) {
|
||||
tags.push(...sanitizedAdditionalTags.map((tag) => tag.slice()));
|
||||
}
|
||||
|
||||
return {
|
||||
kind: schema?.kind ?? 10000,
|
||||
pubkey,
|
||||
created_at,
|
||||
tags,
|
||||
content: typeof content === "string" ? content : "",
|
||||
};
|
||||
}
|
||||
|
||||
export function buildViewEvent(params) {
|
||||
const {
|
||||
pubkey,
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "../utils/hashtagNormalization.js";
|
||||
import { getActiveSigner } from "../nostr/client.js";
|
||||
import { buildPublicUrl, buildR2Key } from "../r2.js";
|
||||
import { buildProfileMetadataEvent } from "../nostrEventSchemas.js";
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
@@ -6376,14 +6377,11 @@ export class ProfileModalController {
|
||||
}
|
||||
|
||||
try {
|
||||
const content = JSON.stringify(profile);
|
||||
const event = {
|
||||
kind: 0,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
const event = buildProfileMetadataEvent({
|
||||
pubkey,
|
||||
tags: [],
|
||||
content,
|
||||
};
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
metadata: profile,
|
||||
});
|
||||
|
||||
const result =
|
||||
await this.services.nostrClient.signAndPublishEvent(event);
|
||||
|
||||
+11
-16
@@ -6,7 +6,7 @@ import {
|
||||
import { getActiveSigner } from "./nostr/index.js";
|
||||
import { isSessionActor } from "./nostr/sessionActor.js";
|
||||
import { normalizeNostrPubkey } from "./nostr/nip46Client.js";
|
||||
import { buildBlockListEvent, BLOCK_LIST_IDENTIFIER, NOTE_TYPES } from "./nostrEventSchemas.js";
|
||||
import { buildBlockListEvent, buildMuteListEvent, BLOCK_LIST_IDENTIFIER, NOTE_TYPES } from "./nostrEventSchemas.js";
|
||||
import { CACHE_POLICIES, STORAGE_TIERS } from "./nostr/cachePolicies.js";
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import {
|
||||
@@ -1536,20 +1536,13 @@ class UserBlockListManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tags = sanitizeMuteTags(blockedPubkeys, owner);
|
||||
const sanitizedTags = sanitizeMuteTags(blockedPubkeys, owner);
|
||||
const pTags = sanitizedTags.map((tag) => tag[1]);
|
||||
|
||||
const timestamp = Number.isFinite(createdAt)
|
||||
? Math.max(0, Math.floor(createdAt))
|
||||
: Math.floor(Date.now() / 1000);
|
||||
|
||||
const event = {
|
||||
kind: 10000,
|
||||
pubkey: owner,
|
||||
created_at: timestamp,
|
||||
tags,
|
||||
content: "",
|
||||
};
|
||||
|
||||
const payloadText = typeof plaintext === "string" ? plaintext : "";
|
||||
let encryptedContent = "";
|
||||
let encryptionTagValue = "";
|
||||
@@ -1579,12 +1572,14 @@ class UserBlockListManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (encryptedContent) {
|
||||
event.content = encryptedContent;
|
||||
if (encryptionTagValue) {
|
||||
event.tags.push(["encrypted", encryptionTagValue]);
|
||||
}
|
||||
}
|
||||
const event = buildMuteListEvent({
|
||||
pubkey: owner,
|
||||
created_at: timestamp,
|
||||
pTags,
|
||||
content: encryptedContent,
|
||||
encrypted: !!encryptionTagValue,
|
||||
encryptionTag: encryptionTagValue,
|
||||
});
|
||||
|
||||
onStatus?.({ status: "mute-publishing" });
|
||||
|
||||
|
||||
Reference in new issue
Block a user