Add support for nip 22 tags

This commit is contained in:
Ticruz
2025-01-17 16:53:16 +01:00
committed by Jon Staab
parent b440fdc84a
commit 856055f99c
11 changed files with 148 additions and 58 deletions
+9 -9
View File
@@ -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",
+1 -1
View File
@@ -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",
+9 -2
View File
@@ -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) {
+71 -31
View File
@@ -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<string>()
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
+1 -1
View File
@@ -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"
}
}
+1 -1
View File
@@ -27,6 +27,6 @@
},
"dependencies": {
"@welshman/lib": "~0.0.37",
"@welshman/util": "~0.0.54"
"@welshman/util": "~0.0.58"
}
}
+1 -1
View File
@@ -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"
}
+1 -1
View File
@@ -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": {
+1 -1
View File
@@ -27,7 +27,7 @@
},
"dependencies": {
"@welshman/lib": "~0.0.37",
"@welshman/util": "~0.0.54",
"@welshman/util": "~0.0.58",
"svelte": "^4.2.18"
}
}
+40 -7
View File
@@ -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))
}
+13 -3
View File
@@ -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)