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 replyId = thread?.replyTo?.Event ?? thread?.root?.Event;
const replyRelayHints = thread?.replyTo?.Relay ?? thread.root?.Relay;
const replyId = thread?.replyTo?.value ?? thread?.root?.value;
const replyRelayHints = thread?.replyTo?.relay ?? thread.root?.relay;
const mentions: { pk: string; name: string; link: ReactNode }[] = [];
for (const pk of thread?.pubKeys ?? []) {
const u = UserCache.getFromCache(pk);

View File

@ -239,9 +239,9 @@ export default function Thread() {
.sort((a, b) => b.created_at - a.created_at)
.forEach(v => {
const t = EventExt.extractThread(v);
let replyTo = t?.replyTo?.Event ?? t?.root?.Event;
if (t?.root?.ATag) {
const parsed = t.root.ATag.split(":");
let replyTo = t?.replyTo?.value ?? t?.root?.value;
if (t?.root?.key === "a" && t?.root?.value) {
const parsed = t.root.value.split(":");
replyTo = thread.data?.find(
a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2]
)?.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
if (replyTo) {
if (replyTo.ATag) {
const parsed = replyTo.ATag.split(":");
if (replyTo.key === "a" && replyTo.value) {
const parsed = replyTo.value.split(":");
return thread.data?.find(
a => a.kind === Number(parsed[0]) && a.pubkey === parsed[1] && findTag(a, "d") === parsed[2]
);
}
if (replyTo.Event) {
return thread.data?.find(a => a.id === replyTo.Event);
if (replyTo.value) {
return thread.data?.find(a => a.id === replyTo.value);
}
}
@ -305,7 +305,11 @@ export default function Thread() {
const parent = useMemo(() => {
if (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]);

View File

@ -1,14 +1,21 @@
import * as secp from "@noble/curves/secp256k1";
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 { sha256, unixNow } from "./Utils";
export interface Tag {
key: string
value?: string
relay?: string
marker?: string // NIP-10
}
export interface Thread {
root?: Tag;
replyTo?: Tag;
mentions: Array<Tag>;
pubKeys: Array<HexKey>;
root?: Tag
replyTo?: Tag
mentions: Array<Tag>
pubKeys: Array<HexKey>
}
export abstract class EventExt {
@ -73,6 +80,24 @@ export abstract class EventExt {
} 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) {
const isThread = ev.tags.some(a => (a[0] === "e" && a[3] !== "mention") || a[0] == "a");
if (!isThread) {
@ -84,27 +109,27 @@ export abstract class EventExt {
mentions: [],
pubKeys: [],
} as Thread;
const eTags = ev.tags.filter(a => a[0] === "e" || a[0] === "a").map((v, i) => new Tag(v, i));
const marked = eTags.some(a => a.Marker !== undefined);
const eTags = ev.tags.filter(a => a[0] === "e" || a[0] === "a").map(a => EventExt.parseTag(a));
const marked = eTags.some(a => a.marker);
if (!marked) {
ret.root = eTags[0];
ret.root.Marker = shouldWriteMarkers ? "root" : undefined;
ret.root.marker = shouldWriteMarkers ? "root" : undefined;
if (eTags.length > 1) {
ret.replyTo = eTags[1];
ret.replyTo.Marker = shouldWriteMarkers ? "reply" : undefined;
ret.replyTo = eTags[eTags.length - 1];
ret.replyTo.marker = shouldWriteMarkers ? "reply" : undefined;
}
if (eTags.length > 2) {
ret.mentions = eTags.slice(2);
ret.mentions = eTags.slice(1, -1);
if (shouldWriteMarkers) {
ret.mentions.forEach(a => (a.Marker = "mention"));
ret.mentions.forEach(a => (a.marker = "mention"));
}
}
} else {
const root = eTags.find(a => a.Marker === "root");
const reply = eTags.find(a => a.Marker === "reply");
const root = eTags.find(a => a.marker === "root");
const reply = eTags.find(a => a.marker === "reply");
ret.root = root;
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])));
return ret;

View File

@ -214,7 +214,7 @@ export class EventPublisher {
const thread = EventExt.extractThread(replyTo);
if (thread) {
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"]);

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 * from "./Nostr";
export * from "./Links";
export { default as Tag } from "./Tag";
export * from "./Nips";
export * from "./RelayInfo";
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: []
})
})
})