mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
- Added `scripts/agent/validate-events.mjs` to verify builder output against schemas. - Updated `js/nostrEventSchemas.js` to enforce relay hints in `buildRepostEvent` (fixes `tests/nostr-boost-actions.test.mjs`). - Documented usage in `docs/nostr-event-schemas.md`. - Verified 25 event builder scenarios pass validation.
351 lines
9.7 KiB
JavaScript
351 lines
9.7 KiB
JavaScript
|
|
import {
|
|
getNostrEventSchema,
|
|
NOTE_TYPES,
|
|
buildVideoPostEvent,
|
|
buildVideoMirrorEvent,
|
|
buildRepostEvent,
|
|
buildRelayListEvent,
|
|
buildDmRelayListEvent,
|
|
buildProfileMetadataEvent,
|
|
buildMuteListEvent,
|
|
buildDeletionEvent,
|
|
buildLegacyDirectMessageEvent,
|
|
buildDmAttachmentEvent,
|
|
buildDmReadReceiptEvent,
|
|
buildDmTypingIndicatorEvent,
|
|
buildViewEvent,
|
|
buildZapRequestEvent,
|
|
buildReactionEvent,
|
|
buildCommentEvent,
|
|
buildWatchHistoryEvent,
|
|
buildSubscriptionListEvent,
|
|
buildBlockListEvent,
|
|
buildHashtagPreferenceEvent,
|
|
buildAdminListEvent
|
|
} from '../../js/nostrEventSchemas.js';
|
|
|
|
const COLORS = {
|
|
RESET: "\x1b[0m",
|
|
RED: "\x1b[31m",
|
|
GREEN: "\x1b[32m",
|
|
YELLOW: "\x1b[33m",
|
|
BLUE: "\x1b[34m",
|
|
};
|
|
|
|
let errorCount = 0;
|
|
let successCount = 0;
|
|
|
|
function logError(msg) {
|
|
console.error(`${COLORS.RED}[FAIL] ${msg}${COLORS.RESET}`);
|
|
errorCount++;
|
|
}
|
|
|
|
function logSuccess(msg) {
|
|
console.log(`${COLORS.GREEN}[PASS] ${msg}${COLORS.RESET}`);
|
|
successCount++;
|
|
}
|
|
|
|
function logInfo(msg) {
|
|
console.log(`${COLORS.BLUE}[INFO] ${msg}${COLORS.RESET}`);
|
|
}
|
|
|
|
function hasTag(tags, tagName, tagValue = null) {
|
|
if (!Array.isArray(tags)) return false;
|
|
return tags.some(
|
|
(tag) =>
|
|
Array.isArray(tag) &&
|
|
tag[0] === tagName &&
|
|
(tagValue === null || tag[1] === tagValue)
|
|
);
|
|
}
|
|
|
|
function validateEvent(type, event, description) {
|
|
const schema = getNostrEventSchema(type);
|
|
if (!schema) {
|
|
logError(`${description}: No schema found for type ${type}`);
|
|
return;
|
|
}
|
|
|
|
let valid = true;
|
|
const errors = [];
|
|
|
|
// Validate Kind
|
|
if (event.kind !== schema.kind) {
|
|
errors.push(`Kind mismatch: expected ${schema.kind}, got ${event.kind}`);
|
|
valid = false;
|
|
}
|
|
|
|
// Validate Topic Tag
|
|
if (schema.topicTag) {
|
|
if (!hasTag(event.tags, schema.topicTag.name, schema.topicTag.value)) {
|
|
errors.push(`Missing topic tag: ${schema.topicTag.name}=${schema.topicTag.value}`);
|
|
valid = false;
|
|
}
|
|
}
|
|
|
|
// Validate Identifier Tag
|
|
if (schema.identifierTag) {
|
|
const expectedValue = schema.identifierTag.value;
|
|
if (expectedValue) {
|
|
if (!hasTag(event.tags, schema.identifierTag.name, expectedValue)) {
|
|
errors.push(`Missing identifier tag: ${schema.identifierTag.name}=${expectedValue}`);
|
|
valid = false;
|
|
}
|
|
} else {
|
|
// If value is not fixed in schema, just check tag name presence if it's supposed to be there.
|
|
// Usually d-tags are present.
|
|
if (!hasTag(event.tags, schema.identifierTag.name)) {
|
|
errors.push(`Missing identifier tag: ${schema.identifierTag.name}`);
|
|
valid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate Append Tags
|
|
if (schema.appendTags) {
|
|
schema.appendTags.forEach((appendTag) => {
|
|
if (Array.isArray(appendTag) && appendTag.length >= 2) {
|
|
const tagName = appendTag[0];
|
|
const tagValue = appendTag[1];
|
|
if (!hasTag(event.tags, tagName, tagValue)) {
|
|
errors.push(`Missing append tag: ${tagName}=${tagValue}`);
|
|
valid = false;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Validate Content Format
|
|
if (schema.content) {
|
|
if (schema.content.format === "json") {
|
|
try {
|
|
JSON.parse(event.content);
|
|
} catch (e) {
|
|
errors.push(`Content is not valid JSON`);
|
|
valid = false;
|
|
}
|
|
} else if (schema.content.format === "empty") {
|
|
if (event.content !== "") {
|
|
errors.push(`Content should be empty, got: "${event.content}"`);
|
|
valid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (valid) {
|
|
logSuccess(`${description}`);
|
|
} else {
|
|
logError(`${description}`);
|
|
errors.forEach(e => console.error(` - ${e}`));
|
|
console.log(" Event:", JSON.stringify(event, null, 2));
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
logInfo("Starting Event Validation...");
|
|
|
|
// 1. VIDEO_POST
|
|
validateEvent(NOTE_TYPES.VIDEO_POST, buildVideoPostEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
dTagValue: "video-id",
|
|
content: { title: "Test Video", videoRootId: "root-id" }
|
|
}), "buildVideoPostEvent (standard)");
|
|
|
|
validateEvent(NOTE_TYPES.VIDEO_POST, buildVideoPostEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
dTagValue: "video-id-minimal",
|
|
content: { version: 1, title: "Minimal", videoRootId: "root" }
|
|
}), "buildVideoPostEvent (minimal)");
|
|
|
|
// 2. VIDEO_MIRROR
|
|
validateEvent(NOTE_TYPES.VIDEO_MIRROR, buildVideoMirrorEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
content: "Alt text"
|
|
}), "buildVideoMirrorEvent");
|
|
|
|
// 3. REPOST
|
|
validateEvent(NOTE_TYPES.REPOST, buildRepostEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
eventId: "hex-id",
|
|
eventRelay: "wss://relay.example.com",
|
|
targetKind: 1,
|
|
serializedEvent: JSON.stringify({ kind: 1, content: "test" })
|
|
}), "buildRepostEvent (event pointer)");
|
|
|
|
// 4. RELAY_LIST
|
|
validateEvent(NOTE_TYPES.RELAY_LIST, buildRelayListEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
relays: ["wss://relay.example.com"]
|
|
}), "buildRelayListEvent");
|
|
|
|
// 5. DM_RELAY_LIST
|
|
validateEvent(NOTE_TYPES.DM_RELAY_LIST, buildDmRelayListEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
relays: ["wss://relay.example.com"]
|
|
}), "buildDmRelayListEvent");
|
|
|
|
// 6. PROFILE_METADATA
|
|
validateEvent(NOTE_TYPES.PROFILE_METADATA, buildProfileMetadataEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
metadata: { name: "Test User" }
|
|
}), "buildProfileMetadataEvent");
|
|
|
|
// 7. MUTE_LIST
|
|
validateEvent(NOTE_TYPES.MUTE_LIST, buildMuteListEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
pTags: ["hex-pubkey"]
|
|
}), "buildMuteListEvent");
|
|
|
|
// 8. DELETION
|
|
validateEvent(NOTE_TYPES.DELETION, buildDeletionEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
eventIds: ["hex-id"]
|
|
}), "buildDeletionEvent");
|
|
|
|
// 9. LEGACY_DM
|
|
validateEvent(NOTE_TYPES.LEGACY_DM, buildLegacyDirectMessageEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
recipientPubkey: "hex-recipient",
|
|
ciphertext: "encrypted-content"
|
|
}), "buildLegacyDirectMessageEvent");
|
|
|
|
// 10. DM_ATTACHMENT
|
|
validateEvent(NOTE_TYPES.DM_ATTACHMENT, buildDmAttachmentEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
recipientPubkey: "hex-recipient",
|
|
attachment: { url: "https://example.com/file", x: "hash" }
|
|
}), "buildDmAttachmentEvent");
|
|
|
|
// 11. DM_READ_RECEIPT
|
|
validateEvent(NOTE_TYPES.DM_READ_RECEIPT, buildDmReadReceiptEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
recipientPubkey: "hex-recipient",
|
|
eventId: "hex-event-id"
|
|
}), "buildDmReadReceiptEvent");
|
|
|
|
// 12. DM_TYPING
|
|
validateEvent(NOTE_TYPES.DM_TYPING, buildDmTypingIndicatorEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
recipientPubkey: "hex-recipient",
|
|
eventId: "hex-event-id",
|
|
expiresAt: 1234567899
|
|
}), "buildDmTypingIndicatorEvent");
|
|
|
|
// 13. VIEW_EVENT
|
|
validateEvent(NOTE_TYPES.VIEW_EVENT, buildViewEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
pointerValue: "video-d-tag",
|
|
dedupeTag: "unique-session-view-id",
|
|
includeSessionTag: true
|
|
}), "buildViewEvent");
|
|
|
|
// 14. ZAP_REQUEST
|
|
validateEvent(NOTE_TYPES.ZAP_REQUEST, buildZapRequestEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
recipientPubkey: "hex-recipient",
|
|
amountSats: 100,
|
|
relays: ["wss://relay.example.com"]
|
|
}), "buildZapRequestEvent");
|
|
|
|
// 15. VIDEO_REACTION
|
|
validateEvent(NOTE_TYPES.VIDEO_REACTION, buildReactionEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
targetPointer: { type: "e", value: "hex-id" },
|
|
content: "+"
|
|
}), "buildReactionEvent");
|
|
|
|
// 16. VIDEO_COMMENT
|
|
validateEvent(NOTE_TYPES.VIDEO_COMMENT, buildCommentEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
content: "This is a comment",
|
|
videoEventId: "hex-video-id"
|
|
}), "buildCommentEvent (root)");
|
|
|
|
validateEvent(NOTE_TYPES.VIDEO_COMMENT, buildCommentEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
content: "This is a reply",
|
|
videoEventId: "hex-video-id",
|
|
rootIdentifier: "root-id",
|
|
parentCommentId: "parent-id"
|
|
}), "buildCommentEvent (reply)");
|
|
|
|
// 17. WATCH_HISTORY
|
|
validateEvent(NOTE_TYPES.WATCH_HISTORY, buildWatchHistoryEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
monthIdentifier: "2023-10",
|
|
content: "[]"
|
|
}), "buildWatchHistoryEvent");
|
|
|
|
// 18. SUBSCRIPTION_LIST
|
|
validateEvent(NOTE_TYPES.SUBSCRIPTION_LIST, buildSubscriptionListEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
content: "encrypted-json"
|
|
}), "buildSubscriptionListEvent");
|
|
|
|
// 19. USER_BLOCK_LIST
|
|
validateEvent(NOTE_TYPES.USER_BLOCK_LIST, buildBlockListEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
content: "encrypted-json"
|
|
}), "buildBlockListEvent");
|
|
|
|
// 20. HASHTAG_PREFERENCES
|
|
validateEvent(NOTE_TYPES.HASHTAG_PREFERENCES, buildHashtagPreferenceEvent({
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
content: "encrypted-json"
|
|
}), "buildHashtagPreferenceEvent");
|
|
|
|
// 21. ADMIN_MODERATION_LIST
|
|
validateEvent(NOTE_TYPES.ADMIN_MODERATION_LIST, buildAdminListEvent("moderation", {
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
hexPubkeys: ["hex-pubkey"]
|
|
}), "buildAdminListEvent (moderation)");
|
|
|
|
// 22. ADMIN_BLACKLIST
|
|
validateEvent(NOTE_TYPES.ADMIN_BLACKLIST, buildAdminListEvent("blacklist", {
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
hexPubkeys: ["hex-pubkey"]
|
|
}), "buildAdminListEvent (blacklist)");
|
|
|
|
// 23. ADMIN_WHITELIST
|
|
validateEvent(NOTE_TYPES.ADMIN_WHITELIST, buildAdminListEvent("whitelist", {
|
|
pubkey: "pubkey",
|
|
created_at: 1234567890,
|
|
hexPubkeys: ["hex-pubkey"]
|
|
}), "buildAdminListEvent (whitelist)");
|
|
|
|
console.log(`\nResults: ${successCount} passed, ${errorCount} failed.`);
|
|
if (errorCount > 0) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|