feat: support rendering kind 20,21,22

feat: reply to non-text-note as kind 1111
This commit is contained in:
2025-02-27 15:29:28 +00:00
parent 6977f80652
commit aaa4b0de97
12 changed files with 146 additions and 22 deletions

View File

@ -12,12 +12,16 @@ const enum EventKind {
SimpleChatMessage = 9, // NIP-29
SealedRumor = 13, // NIP-59
ChatRumor = 14, // NIP-24
Photo = 20, // NIP-68
Video = 21, // NIP-71
ShortVideo = 22, // NIP-71
PublicChatChannel = 40, // NIP-28
PublicChatMetadata = 41, // NIP-28
PublicChatMessage = 42, // NIP-28
PublicChatMuteMessage = 43, // NIP-28
PublicChatMuteUser = 44, // NIP-28
SnortSubscriptions = 1000, // NIP-XX
Comment = 1111, // NIP-22
Polls = 6969, // NIP-69
GiftWrap = 1059, // NIP-59
FileHeader = 1063, // NIP-94

View File

@ -25,6 +25,7 @@ import { EventBuilder } from "./event-builder";
import { findTag } from "./utils";
import { Nip7Signer } from "./impl/nip7";
import { Nip10 } from "./impl/nip10";
import { Nip22 } from "./impl/nip22";
type EventBuilderHook = (ev: EventBuilder) => EventBuilder;
@ -195,12 +196,19 @@ export class EventPublisher {
/**
* Reply to a note
*
* Replies to kind 1 notes are kind 1, otherwise kind 1111
*/
async reply(replyTo: TaggedNostrEvent, msg: string, fnExtra?: EventBuilderHook) {
const eb = this.#eb(EventKind.TextNote);
const kind = replyTo.kind === EventKind.TextNote ? EventKind.TextNote : EventKind.Comment;
const eb = this.#eb(kind);
eb.content(msg);
Nip10.replyTo(replyTo, eb);
if (kind === EventKind.TextNote) {
Nip10.replyTo(replyTo, eb);
} else {
Nip22.replyTo(replyTo, eb);
}
eb.processContent();
fnExtra?.(eb);
return await this.#sign(eb);
@ -211,6 +219,7 @@ export class EventPublisher {
eb.content(content);
eb.tag(unwrap(NostrLink.fromEvent(evRef).toEventTag()));
eb.tag(["p", evRef.pubkey]);
eb.tag(["k", evRef.kind.toString()]);
return await this.#sign(eb);
}

View File

@ -0,0 +1,35 @@
import { findTag } from "../utils";
import { EventBuilder, NostrEvent, NostrLink, NostrPrefix } from "../index";
export class Nip22 {
/**
* Get the root scope tag (E/A/I) or
* create a root scope tag from the provided event
*/
static rootScopeOf(other: NostrEvent) {
const linkOther = NostrLink.fromEvent(other);
return other.tags.find(t => ["E", "A", "I"].includes(t[0])) ?? linkOther.toEventTagNip22(true)!;
}
static replyTo(other: NostrEvent, eb: EventBuilder) {
const linkOther = NostrLink.fromEvent(other);
const rootScope = Nip22.rootScopeOf(other);
const rootKind = ["K", findTag(other, "K") ?? other.kind.toString()];
const rootAuthor = ["P", findTag(other, "P") ?? other.pubkey];
const replyScope = linkOther.toEventTagNip22(false);
const replyKind = ["k", other.kind.toString()];
const replyAuthor = ["p", other.pubkey];
if (rootScope === undefined || replyScope === undefined) {
throw new Error("RootScope or ReplyScope are undefined!");
}
eb.tag(rootScope);
eb.tag(rootKind);
eb.tag(rootAuthor);
eb.tag(replyScope);
eb.tag(replyKind);
eb.tag(replyAuthor);
}
}

View File

@ -30,6 +30,7 @@ export * from "./encryption";
export * from "./impl/nip4";
export * from "./impl/nip7";
export * from "./impl/nip10";
export * from "./impl/nip22";
export * from "./impl/nip44";
export * from "./impl/nip46";
export * from "./impl/nip57";

View File

@ -114,6 +114,32 @@ export class NostrLink implements ToNostrEventTag {
}
}
/**
* Create an event tag from this link as per NIP-22 (no marker position)
*/
toEventTagNip22(root?: boolean) {
// emulate root flag by root marker
root ??= this.marker === "root";
const suffix: Array<string> = [];
if (this.relays && this.relays.length > 0) {
suffix.push(this.relays[0]);
}
if (this.type === NostrPrefix.PublicKey || this.type === NostrPrefix.Profile) {
return [root ? "P" : "p", this.id, ...suffix];
} else if (this.type === NostrPrefix.Note || this.type === NostrPrefix.Event) {
if (this.author) {
if (suffix[0] === undefined) {
suffix.push(""); // empty relay hint
}
suffix.push(this.author);
}
return [root ? "E" : "e", this.id, ...suffix];
} else if (this.type === NostrPrefix.Address) {
return [root ? "A" : "a", `${this.kind}:${this.author}:${this.id}`, ...suffix];
}
}
matchesEvent(ev: NostrEvent) {
if (this.type === NostrPrefix.Address) {
const dTag = findTag(ev, "d");
@ -194,12 +220,19 @@ export class NostrLink implements ToNostrEventTag {
static fromTag(tag: Array<string>, author?: string, kind?: number) {
const relays = tag.length > 2 ? [tag[2]] : undefined;
switch (tag[0]) {
case "E": {
return new NostrLink(NostrPrefix.Event, tag[1], kind, author ?? tag[3], relays, "root");
}
case "e": {
return new NostrLink(NostrPrefix.Event, tag[1], kind, author ?? tag[4], relays, tag[3]);
}
case "p": {
return new NostrLink(NostrPrefix.Profile, tag[1], kind, author, relays);
}
case "A": {
const [kind, author, dTag] = tag[1].split(":");
return new NostrLink(NostrPrefix.Address, dTag, Number(kind), author, relays, "root");
}
case "a": {
const [kind, author, dTag] = tag[1].split(":");
return new NostrLink(NostrPrefix.Address, dTag, Number(kind), author, relays, tag[3]);
@ -242,7 +275,7 @@ export class NostrLink implements ToNostrEventTag {
let relays = "relays" in ev ? ev.relays : undefined;
const eventRelays = removeUndefined(
ev.tags
.filter(a => a[0] === "relays" || a[0] === "relay" || a[0] === "r")
.filter(a => a[0] === "relays" || a[0] === "relay" || (a[0] === "r" && ev.kind == EventKind.Relays))
.flatMap(a => a.slice(1).map(b => sanitizeRelayUrl(b))),
);
relays = appendDedupe(relays, eventRelays);

View File

@ -1,6 +1,6 @@
import { equalProp } from "@snort/shared";
import { FlatReqFilter } from "./query-optimizer";
import { IMeta, NostrEvent, ReqFilter } from "./nostr";
import { NostrEvent, ReqFilter } from "./nostr";
export function findTag(e: NostrEvent, tag: string) {
const maybeTag = e.tags.find(evTag => {
@ -9,6 +9,13 @@ export function findTag(e: NostrEvent, tag: string) {
return maybeTag && maybeTag[1];
}
export function findWholeTag(e: NostrEvent, tag: string) {
const maybeTag = e.tags.find(evTag => {
return evTag[0] === tag;
});
return maybeTag;
}
export function reqFilterEq(a: FlatReqFilter | ReqFilter, b: FlatReqFilter | ReqFilter): boolean {
return (
equalProp(a.ids, b.ids) &&