forked from Kieran/snort
parent
a7fa996e84
commit
fdf3d855df
@ -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);
|
||||||
|
@ -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]);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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"]);
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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";
|
||||||
|
62
packages/system/tests/EventExt.test.ts
Normal file
62
packages/system/tests/EventExt.test.ts
Normal 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: []
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user