1
0
forked from Kieran/snort

bug: unmarked thread tag parsing

fixes: #386
This commit is contained in:
Kieran 2023-06-13 12:24:57 +01:00
parent a7fa996e84
commit fdf3d855df
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
7 changed files with 117 additions and 115 deletions

View File

@ -246,8 +246,8 @@ export default function Note(props: NoteProps) {
} }
const maxMentions = 2; const maxMentions = 2;
const replyId = thread?.replyTo?.Event ?? thread?.root?.Event; const replyId = thread?.replyTo?.value ?? thread?.root?.value;
const replyRelayHints = thread?.replyTo?.Relay ?? thread.root?.Relay; const replyRelayHints = thread?.replyTo?.relay ?? thread.root?.relay;
const mentions: { pk: string; name: string; link: ReactNode }[] = []; const mentions: { pk: string; name: string; link: ReactNode }[] = [];
for (const pk of thread?.pubKeys ?? []) { for (const pk of thread?.pubKeys ?? []) {
const u = UserCache.getFromCache(pk); const u = UserCache.getFromCache(pk);

View File

@ -239,9 +239,9 @@ export default function Thread() {
.sort((a, b) => b.created_at - a.created_at) .sort((a, b) => b.created_at - a.created_at)
.forEach(v => { .forEach(v => {
const t = EventExt.extractThread(v); const t = EventExt.extractThread(v);
let replyTo = t?.replyTo?.Event ?? t?.root?.Event; let replyTo = t?.replyTo?.value ?? t?.root?.value;
if (t?.root?.ATag) { if (t?.root?.key === "a" && t?.root?.value) {
const parsed = t.root.ATag.split(":"); const parsed = t.root.value.split(":");
replyTo = thread.data?.find( replyTo = thread.data?.find(
a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2] a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2]
)?.id; )?.id;
@ -274,14 +274,14 @@ export default function Thread() {
// sometimes the root event ID is missing, and we can only take the happy path if the root event ID exists // sometimes the root event ID is missing, and we can only take the happy path if the root event ID exists
if (replyTo) { if (replyTo) {
if (replyTo.ATag) { if (replyTo.key === "a" && replyTo.value) {
const parsed = replyTo.ATag.split(":"); const parsed = replyTo.value.split(":");
return thread.data?.find( return thread.data?.find(
a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2] a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2]
); );
} }
if (replyTo.Event) { if (replyTo.value) {
return thread.data?.find(a => a.id === replyTo.Event); return thread.data?.find(a => a.id === replyTo.value);
} }
} }
@ -305,7 +305,11 @@ export default function Thread() {
const parent = useMemo(() => { const parent = useMemo(() => {
if (root) { if (root) {
const currentThread = EventExt.extractThread(root); const currentThread = EventExt.extractThread(root);
return currentThread?.replyTo?.Event ?? currentThread?.root?.Event ?? currentThread?.root?.ATag; return (
currentThread?.replyTo?.value ??
currentThread?.root?.value ??
(currentThread?.root?.key === "a" && currentThread.root?.value)
);
} }
}, [root]); }, [root]);

View File

@ -1,14 +1,21 @@
import * as secp from "@noble/curves/secp256k1"; import * as secp from "@noble/curves/secp256k1";
import * as utils from "@noble/curves/abstract/utils"; import * as utils from "@noble/curves/abstract/utils";
import { EventKind, HexKey, NostrEvent, Tag } from "."; import { EventKind, HexKey, NostrEvent } from ".";
import base64 from "@protobufjs/base64"; import base64 from "@protobufjs/base64";
import { sha256, unixNow } from "./Utils"; import { sha256, unixNow } from "./Utils";
export interface Tag {
key: string
value?: string
relay?: string
marker?: string // NIP-10
}
export interface Thread { export interface Thread {
root?: Tag; root?: Tag
replyTo?: Tag; replyTo?: Tag
mentions: Array<Tag>; mentions: Array<Tag>
pubKeys: Array<HexKey>; pubKeys: Array<HexKey>
} }
export abstract class EventExt { export abstract class EventExt {
@ -73,6 +80,24 @@ export abstract class EventExt {
} as NostrEvent; } as NostrEvent;
} }
static parseTag(tag: Array<string>) {
if (tag.length < 1) {
throw new Error("Invalid tag, must have more than 2 items")
}
const ret = {
key: tag[0],
value: tag[1]
} as Tag;
switch (ret.key) {
case "e": {
ret.relay = tag.length > 2 ? tag[2] : undefined;
ret.marker = tag.length > 3 ? tag[3] : undefined;
break;
}
}
return ret;
}
static extractThread(ev: NostrEvent) { static extractThread(ev: NostrEvent) {
const isThread = ev.tags.some(a => (a[0] === "e" && a[3] !== "mention") || a[0] == "a"); const isThread = ev.tags.some(a => (a[0] === "e" && a[3] !== "mention") || a[0] == "a");
if (!isThread) { if (!isThread) {
@ -84,27 +109,27 @@ export abstract class EventExt {
mentions: [], mentions: [],
pubKeys: [], pubKeys: [],
} as Thread; } as Thread;
const eTags = ev.tags.filter(a => a[0] === "e" || a[0] === "a").map((v, i) => new Tag(v, i)); const eTags = ev.tags.filter(a => a[0] === "e" || a[0] === "a").map(a => EventExt.parseTag(a));
const marked = eTags.some(a => a.Marker !== undefined); const marked = eTags.some(a => a.marker);
if (!marked) { if (!marked) {
ret.root = eTags[0]; ret.root = eTags[0];
ret.root.Marker = shouldWriteMarkers ? "root" : undefined; ret.root.marker = shouldWriteMarkers ? "root" : undefined;
if (eTags.length > 1) { if (eTags.length > 1) {
ret.replyTo = eTags[1]; ret.replyTo = eTags[eTags.length - 1];
ret.replyTo.Marker = shouldWriteMarkers ? "reply" : undefined; ret.replyTo.marker = shouldWriteMarkers ? "reply" : undefined;
} }
if (eTags.length > 2) { if (eTags.length > 2) {
ret.mentions = eTags.slice(2); ret.mentions = eTags.slice(1, -1);
if (shouldWriteMarkers) { if (shouldWriteMarkers) {
ret.mentions.forEach(a => (a.Marker = "mention")); ret.mentions.forEach(a => (a.marker = "mention"));
} }
} }
} else { } else {
const root = eTags.find(a => a.Marker === "root"); const root = eTags.find(a => a.marker === "root");
const reply = eTags.find(a => a.Marker === "reply"); const reply = eTags.find(a => a.marker === "reply");
ret.root = root; ret.root = root;
ret.replyTo = reply; ret.replyTo = reply;
ret.mentions = eTags.filter(a => a.Marker === "mention"); ret.mentions = eTags.filter(a => a.marker === "mention");
} }
ret.pubKeys = Array.from(new Set(ev.tags.filter(a => a[0] === "p").map(a => a[1]))); ret.pubKeys = Array.from(new Set(ev.tags.filter(a => a[0] === "p").map(a => a[1])));
return ret; return ret;

View File

@ -214,7 +214,7 @@ export class EventPublisher {
const thread = EventExt.extractThread(replyTo); const thread = EventExt.extractThread(replyTo);
if (thread) { if (thread) {
if (thread.root || thread.replyTo) { if (thread.root || thread.replyTo) {
eb.tag(["e", thread.root?.Event ?? thread.replyTo?.Event ?? "", "", "root"]); eb.tag(["e", thread.root?.value ?? thread.replyTo?.value ?? "", "", "root"]);
} }
eb.tag(["e", replyTo.id, replyTo.relays?.[0] ?? "", "reply"]); eb.tag(["e", replyTo.id, replyTo.relays?.[0] ?? "", "reply"]);

View File

@ -1,88 +0,0 @@
import { HexKey, u256 } from "./Nostr";
import { unwrap } from "./Utils";
export default class Tag {
Original: string[];
Key: string;
Event?: u256;
PubKey?: HexKey;
Relay?: string;
Marker?: string;
Hashtag?: string;
DTag?: string;
ATag?: string;
Index: number;
Invalid: boolean;
LNURL?: string;
constructor(tag: string[], index: number) {
this.Original = tag;
this.Key = tag[0];
this.Index = index;
this.Invalid = false;
switch (this.Key) {
case "e": {
// ["e", <event-id>, <relay-url>, <marker>]
this.Event = tag[1];
this.Relay = tag.length > 2 ? tag[2] : undefined;
this.Marker = tag.length > 3 ? tag[3] : undefined;
if (!this.Event) {
this.Invalid = true;
}
break;
}
case "p": {
// ["p", <pubkey>]
this.PubKey = tag[1];
if (!this.PubKey) {
this.Invalid = true;
}
break;
}
case "d": {
this.DTag = tag[1];
break;
}
case "a": {
this.ATag = tag[1];
break;
}
case "t": {
this.Hashtag = tag[1];
break;
}
case "delegation": {
this.PubKey = tag[1];
break;
}
case "zap": {
this.LNURL = tag[1];
break;
}
}
}
ToObject(): string[] | null {
switch (this.Key) {
case "e": {
let ret = ["e", this.Event, this.Relay, this.Marker];
const trimEnd = ret.reverse().findIndex(a => a !== undefined);
ret = ret.reverse().slice(0, ret.length - trimEnd);
return <string[]>ret;
}
case "p": {
return this.PubKey ? ["p", this.PubKey] : null;
}
case "t": {
return ["t", unwrap(this.Hashtag)];
}
case "d": {
return ["d", unwrap(this.DTag)];
}
default: {
return this.Original;
}
}
}
}

View File

@ -8,7 +8,6 @@ export * from "./NostrSystem";
export { default as EventKind } from "./EventKind"; export { default as EventKind } from "./EventKind";
export * from "./Nostr"; export * from "./Nostr";
export * from "./Links"; export * from "./Links";
export { default as Tag } from "./Tag";
export * from "./Nips"; export * from "./Nips";
export * from "./RelayInfo"; export * from "./RelayInfo";
export * from "./EventExt"; export * from "./EventExt";

View File

@ -0,0 +1,62 @@
import { EventExt } from "../src/EventExt";
describe("NIP-10", () => {
it("should extract thread", () => {
const a = {
content: "This is the problem with Lightning....",
id: "868187063f...",
kind: 1,
created_at: 1,
pubkey: "test",
sig: "test",
"tags": [
["e", "cbf2375078..."],
["e", "977ac5d3b6..."],
["e", "8f99ca1363..."],
]
}
const b = {
"content": "This is a good point, but your ...",
"id": "434ad4a646...",
kind: 1,
created_at: 1,
pubkey: "test",
sig: "test",
"tags": [
["e", "cbf2375078..."],
["e", "868187063f..."],
["e", "6834ffc491..."],
]
}
const c = {
"content": "There is some middle ground ...",
"id": "6834ffc491...",
kind: 1,
created_at: 1,
pubkey: "test",
sig: "test",
"tags": [
["e", "cbf2375078...", "", "root"],
["e", "868187063f...", "", "reply"],
]
}
expect(EventExt.extractThread(a)).toMatchObject({
root: { key: "e", value: "cbf2375078...", marker: "root" },
replyTo: { key: "e", value: "8f99ca1363...", marker: "reply" },
mentions: [{ key: "e", value: "977ac5d3b6...", marker: "mention" }]
})
expect(EventExt.extractThread(b)).toMatchObject({
root: { key: "e", value: "cbf2375078...", marker: "root" },
replyTo: { key: "e", value: "6834ffc491...", marker: "reply" },
mentions: [{ key: "e", value: "868187063f...", marker: "mention" }]
})
expect(EventExt.extractThread(c)).toMatchObject({
root: { key: "e", value: "cbf2375078...", relay: "", marker: "root" },
replyTo: { key: "e", value: "868187063f...", relay: "", marker: "reply" },
mentions: []
})
})
})