From 856055f99c938e587c5bb5345493bf0b21eabe7d Mon Sep 17 00:00:00 2001 From: Ticruz Date: Fri, 17 Jan 2025 16:53:16 +0100 Subject: [PATCH] Add support for nip 22 tags --- package-lock.json | 18 +++---- packages/app/package.json | 2 +- packages/app/src/router.ts | 11 +++- packages/app/src/tags.ts | 102 ++++++++++++++++++++++++----------- packages/dvm/package.json | 2 +- packages/feeds/package.json | 2 +- packages/net/package.json | 2 +- packages/signer/package.json | 2 +- packages/store/package.json | 2 +- packages/util/src/Events.ts | 47 +++++++++++++--- packages/util/src/Tags.ts | 16 ++++-- 11 files changed, 148 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93aff6e..1cbf199 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5742,7 +5742,7 @@ }, "packages/app": { "name": "@welshman/app", - "version": "0.0.36", + "version": "0.0.38", "license": "MIT", "dependencies": { "@types/throttle-debounce": "^5.0.2", @@ -5752,7 +5752,7 @@ "@welshman/net": "~0.0.45", "@welshman/signer": "~0.0.19", "@welshman/store": "~0.0.15", - "@welshman/util": "~0.0.54", + "@welshman/util": "~0.0.58", "fuse.js": "^7.0.0", "idb": "^8.0.0", "svelte": "^4.2.18", @@ -5780,7 +5780,7 @@ "@noble/hashes": "^1.6.1", "@welshman/lib": "~0.0.37", "@welshman/net": "~0.0.45", - "@welshman/util": "~0.0.54", + "@welshman/util": "~0.0.58", "nostr-tools": "^2.7.2" } }, @@ -5798,7 +5798,7 @@ }, "packages/editor": { "name": "@welshman/editor", - "version": "0.0.4", + "version": "0.0.6", "devDependencies": { "@sveltejs/kit": "^2.0.0", "@sveltejs/package": "^2.0.0", @@ -6394,7 +6394,7 @@ "license": "MIT", "dependencies": { "@welshman/lib": "~0.0.37", - "@welshman/util": "~0.0.54" + "@welshman/util": "~0.0.58" } }, "packages/lib": { @@ -6420,7 +6420,7 @@ "license": "MIT", "dependencies": { "@welshman/lib": "~0.0.37", - "@welshman/util": "~0.0.54", + "@welshman/util": "~0.0.58", "isomorphic-ws": "^5.0.0", "ws": "^8.16.0" }, @@ -6437,7 +6437,7 @@ "@noble/hashes": "^1.6.1", "@welshman/lib": "~0.0.37", "@welshman/net": "~0.0.45", - "@welshman/util": "~0.0.54", + "@welshman/util": "~0.0.58", "nostr-tools": "^2.7.2" }, "engines": { @@ -6492,13 +6492,13 @@ "license": "MIT", "dependencies": { "@welshman/lib": "~0.0.37", - "@welshman/util": "~0.0.54", + "@welshman/util": "~0.0.58", "svelte": "^4.2.18" } }, "packages/util": { "name": "@welshman/util", - "version": "0.0.55", + "version": "0.0.58", "license": "MIT", "dependencies": { "@types/ws": "^8.5.13", diff --git a/packages/app/package.json b/packages/app/package.json index 39c22f4..eacb3c8 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -33,7 +33,7 @@ "@welshman/net": "~0.0.45", "@welshman/signer": "~0.0.19", "@welshman/store": "~0.0.15", - "@welshman/util": "~0.0.54", + "@welshman/util": "~0.0.58", "fuse.js": "^7.0.0", "idb": "^8.0.0", "svelte": "^4.2.18", diff --git a/packages/app/src/router.ts b/packages/app/src/router.ts index a7bde01..1eb7410 100644 --- a/packages/app/src/router.ts +++ b/packages/app/src/router.ts @@ -25,12 +25,14 @@ import { isLocalUrl, isIPAddress, isShareableRelayUrl, + COMMENT, PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS, WRAP, - getAncestorTags, + getReplyTags, + getCommentTags, getPubkeyTagValues, normalizeRelayUrl, } from "@welshman/util" @@ -191,8 +193,13 @@ export class Router { } EventAncestors = (event: TrustedEvent, type: "mentions" | "replies" | "roots") => { + const ancestorTags = + event.kind === COMMENT ? getCommentTags(event.tags) : getReplyTags(event.tags) + + const tags: string[][] = (ancestorTags as any)[type] || [] + return this.scenario( - getAncestorTags(event.tags)[type].flatMap(([_, value, relay, pubkey]) => { + tags.flatMap(([_, value, relay, pubkey]) => { const selections = [makeSelection(this.ForUser().getUrls(), 0.5)] if (pubkey) { diff --git a/packages/app/src/tags.ts b/packages/app/src/tags.ts index 20f77f2..f758b6a 100644 --- a/packages/app/src/tags.ts +++ b/packages/app/src/tags.ts @@ -1,10 +1,10 @@ -import {ctx} from "@welshman/lib" +import {ctx, remove, nthEq} from "@welshman/lib" import { getAddress, isReplaceable, - getAncestorTags, + getReplyTags, getPubkeyTagValues, - getIdAndAddress, + isReplaceableKind, } from "@welshman/util" import type {TrustedEvent} from "@welshman/util" import {displayProfileByPubkey} from "./profiles.js" @@ -35,25 +35,14 @@ export const tagEvent = (event: TrustedEvent, mark = "") => { return tags } -export const tagReplyTo = (event: TrustedEvent) => { - const $pubkey = pubkey.get() - const tagValues = getIdAndAddress(event) - const tags: string[][] = [] +export const tagEventPubkeys = (event: TrustedEvent) => + remove(pubkey.get()!, [event.pubkey, ...getPubkeyTagValues(event.tags)]).map(tagPubkey) - // Mention the event's author - if (event.pubkey !== $pubkey) { - tags.push(tagPubkey(event.pubkey)) - } - - // Inherit p-tag mentions - for (const pubkey of getPubkeyTagValues(event.tags)) { - if (pubkey !== $pubkey) { - tags.push(tagPubkey(pubkey)) - } - } +export const tagEventForReply = (event: TrustedEvent) => { + const tags = tagEventPubkeys(event) // Based on NIP 10 legacy tags, order is root, mentions, reply - const {roots, replies, mentions} = getAncestorTags(event.tags) + const {roots, replies, mentions} = getReplyTags(event.tags) // Root comes first if (roots.length > 0) { @@ -66,12 +55,9 @@ export const tagReplyTo = (event: TrustedEvent) => { } } - // Make sure we don't repeat any tag values - const isRepeated = (v: string) => tagValues.includes(v) || tags.find(t => t[1] === v) - // Inherit mentions for (const t of mentions) { - if (!isRepeated(t[1])) { + if (!tags.some(nthEq(1, t[1]))) { tags.push([...t.slice(0, 3), "mention"]) } } @@ -79,21 +65,73 @@ export const tagReplyTo = (event: TrustedEvent) => { // Inherit replies if they weren't already included if (roots.length > 0) { for (const t of replies) { - if (!isRepeated(t[1])) { + if (!tags.some(nthEq(1, t[1]))) { tags.push([...t.slice(0, 3), "mention"]) } } } - // Add a/e-tags for the event event - for (const t of tagEvent(event, replies.length > 0 ? "reply" : "root")) { - tags.push(t) + // Finally, tag the event itself + const mark = replies.length > 0 ? "reply" : "root" + const hint = ctx.app.router.Event(event).getUrl() + + // e-tag the event + tags.push(["e", event.id, hint, mark, event.pubkey]) + + // a-tag the event + if (isReplaceable(event)) { + tags.push(["a", getAddress(event), hint, mark, event.pubkey]) } return tags } -export const tagReactionTo = (event: TrustedEvent) => { +export const tagEventForComment = (event: TrustedEvent) => { + const pubkeyHint = ctx.app.router.FromPubkey(event.pubkey).getUrl() + const eventHint = ctx.app.router.Event(event).getUrl() + const address = getAddress(event) + const tags = tagEventPubkeys(event) + const seenRoots = new Set() + + for (const [raw, ...tag] of event.tags) { + const T = raw.toUpperCase() + const t = raw.toLowerCase() + + if (!["k", "e", "a", "i", "p"].includes(t)) { + continue + } + + if (seenRoots.has(T)) { + tags.push([t, ...tag]) + } else { + tags.push([T, ...tag]) + seenRoots.add(T) + } + } + + if (seenRoots.size === 0) { + tags.push(["K", String(event.kind)]) + tags.push(["P", event.pubkey, pubkeyHint]) + tags.push(["E", event.id, eventHint, event.pubkey]) + + if (isReplaceableKind(event.kind)) { + tags.push(["A", address, eventHint, event.pubkey]) + } + } + + tags.push(["k", String(event.kind)]) + tags.push(["p", event.pubkey, pubkeyHint]) + tags.push(["e", event.id, eventHint, event.pubkey]) + + if (isReplaceableKind(event.kind)) { + tags.push(["a", address, eventHint, event.pubkey]) + } + + return tags +} + +export const tagEventForReaction = (event: TrustedEvent) => { + const hint = ctx.app.router.Event(event).getUrl() const tags: string[][] = [] // Mention the event's author @@ -101,9 +139,11 @@ export const tagReactionTo = (event: TrustedEvent) => { tags.push(tagPubkey(event.pubkey)) } - // Add a/e-tags for the event - for (const t of tagEvent(event, "root")) { - tags.push(t) + tags.push(["k", String(event.kind)]) + tags.push(["e", event.id, hint]) + + if (isReplaceable(event)) { + tags.push(["a", getAddress(event), hint]) } return tags diff --git a/packages/dvm/package.json b/packages/dvm/package.json index ad6ccf6..2cd416f 100644 --- a/packages/dvm/package.json +++ b/packages/dvm/package.json @@ -29,7 +29,7 @@ "@noble/hashes": "^1.6.1", "@welshman/lib": "~0.0.37", "@welshman/net": "~0.0.45", - "@welshman/util": "~0.0.54", + "@welshman/util": "~0.0.58", "nostr-tools": "^2.7.2" } } diff --git a/packages/feeds/package.json b/packages/feeds/package.json index c07810f..e4d8bba 100644 --- a/packages/feeds/package.json +++ b/packages/feeds/package.json @@ -27,6 +27,6 @@ }, "dependencies": { "@welshman/lib": "~0.0.37", - "@welshman/util": "~0.0.54" + "@welshman/util": "~0.0.58" } } diff --git a/packages/net/package.json b/packages/net/package.json index 2d4abec..c53a041 100644 --- a/packages/net/package.json +++ b/packages/net/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@welshman/lib": "~0.0.37", - "@welshman/util": "~0.0.54", + "@welshman/util": "~0.0.58", "isomorphic-ws": "^5.0.0", "ws": "^8.16.0" } diff --git a/packages/signer/package.json b/packages/signer/package.json index c2294dc..550b595 100644 --- a/packages/signer/package.json +++ b/packages/signer/package.json @@ -33,7 +33,7 @@ "@noble/hashes": "^1.6.1", "@welshman/lib": "~0.0.37", "@welshman/net": "~0.0.45", - "@welshman/util": "~0.0.54", + "@welshman/util": "~0.0.58", "nostr-tools": "^2.7.2" }, "peerDependencies": { diff --git a/packages/store/package.json b/packages/store/package.json index bd3088a..ffef7f9 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@welshman/lib": "~0.0.37", - "@welshman/util": "~0.0.54", + "@welshman/util": "~0.0.58", "svelte": "^4.2.18" } } diff --git a/packages/util/src/Events.ts b/packages/util/src/Events.ts index a9a77b4..b0f846c 100644 --- a/packages/util/src/Events.ts +++ b/packages/util/src/Events.ts @@ -1,8 +1,9 @@ import {verifiedSymbol, getEventHash, verifyEvent} from "nostr-tools/pure" -import {cached, pick, now} from "@welshman/lib" -import {getAncestorTagValues} from "./Tags.js" -import {getAddress} from "./Address.js" +import {cached, mapVals, first, pick, now} from "@welshman/lib" +import {getReplyTagValues, getCommentTagValues} from "./Tags.js" +import {getAddress, Address} from "./Address.js" import { + COMMENT, isEphemeralKind, isReplaceableKind, isPlainReplaceableKind, @@ -135,9 +136,41 @@ export const isPlainReplaceable = (e: EventTemplate) => isPlainReplaceableKind(e export const isParameterizedReplaceable = (e: EventTemplate) => isParameterizedReplaceableKind(e.kind) -export const isChildOf = (child: EventContent, parent: HashedEvent) => { - const {roots, replies} = getAncestorTagValues(child.tags) - const parentIds = replies.length > 0 ? replies : roots +export const getAncestors = ({kind, tags}: EventTemplate) => + kind === COMMENT ? getCommentTagValues(tags) : getReplyTagValues(tags) - return getIdAndAddress(parent).some(x => parentIds.includes(x)) +export const getParentIdsAndAddrs = (event: EventTemplate) => { + const {roots, replies} = getAncestors(event) + + return replies.length > 0 ? replies : roots +} + +export const getParentIdOrAddr = (event: EventTemplate) => first(getParentIdsAndAddrs(event)) + +export const getParentIds = (event: EventTemplate) => { + const {roots, replies} = mapVals( + ids => ids.filter(id => !Address.isAddress(id)), + getAncestors(event), + ) + + return replies.length > 0 ? replies : roots +} + +export const getParentId = (event: EventTemplate) => first(getParentIds(event)) + +export const getParentAddrs = (event: EventTemplate) => { + const {roots, replies} = mapVals( + ids => ids.filter(id => Address.isAddress(id)), + getAncestors(event), + ) + + return replies.length > 0 ? replies : roots +} + +export const getParentAddr = (event: EventTemplate) => first(getParentAddrs(event)) + +export const isChildOf = (child: EventTemplate, parent: HashedEvent) => { + const idsAndAddrs = getParentIdsAndAddrs(child) + + return getIdAndAddress(parent).some(x => idsAndAddrs.includes(x)) } diff --git a/packages/util/src/Tags.ts b/packages/util/src/Tags.ts index cd0f462..ce5ee10 100644 --- a/packages/util/src/Tags.ts +++ b/packages/util/src/Tags.ts @@ -54,7 +54,17 @@ export const getKindTags = (tags: string[][]) => export const getKindTagValues = (tags: string[][]) => getKindTags(tags).map(t => parseInt(t[1])) -export const getAncestorTags = (tags: string[][]) => { +export const getCommentTags = (tags: string[][]) => { + const roots = tags.filter(t => ["A", "E", "P", "K"].includes(t[0])) + const replies = tags.filter(t => ["a", "e", "p", "k"].includes(t[0])) + + return {roots, replies} +} + +export const getCommentTagValues = (tags: string[][]) => + mapVals(tags => tags.map(nth(1)), getCommentTags(tags)) + +export const getReplyTags = (tags: string[][]) => { const validTags = tags.filter(t => ["a", "e", "q"].includes(t[0])) const mentionTags = validTags.filter(nthEq(0, "q")) const roots: string[][] = [] @@ -90,8 +100,8 @@ export const getAncestorTags = (tags: string[][]) => { return {roots, replies, mentions} } -export const getAncestorTagValues = (tags: string[][]) => - mapVals(tags => tags.map(nth(1)), getAncestorTags(tags)) +export const getReplyTagValues = (tags: string[][]) => + mapVals(tags => tags.map(nth(1)), getReplyTags(tags)) export const uniqTags = (tags: string[][]) => uniqBy(t => t.join(":"), tags)