Use NostrLink everywhere
This commit is contained in:
@ -2,7 +2,7 @@ import * as utils from "@noble/curves/abstract/utils";
|
||||
import { bech32 } from "@scure/base";
|
||||
import { HexKey } from "./nostr";
|
||||
|
||||
export enum NostrPrefix {
|
||||
export const enum NostrPrefix {
|
||||
PublicKey = "npub",
|
||||
PrivateKey = "nsec",
|
||||
Note = "note",
|
||||
|
@ -2,82 +2,73 @@ import { bech32ToHex, hexToBech32, unwrap } from "@snort/shared";
|
||||
import { NostrPrefix, decodeTLV, TLVEntryType, encodeTLV, NostrEvent, TaggedNostrEvent } from ".";
|
||||
import { findTag } from "./utils";
|
||||
|
||||
export interface NostrLink {
|
||||
type: NostrPrefix;
|
||||
id: string;
|
||||
kind?: number;
|
||||
author?: string;
|
||||
relays?: Array<string>;
|
||||
encode(): string;
|
||||
}
|
||||
export class NostrLink {
|
||||
constructor(
|
||||
readonly type: NostrPrefix,
|
||||
readonly id: string,
|
||||
readonly kind?: number,
|
||||
readonly author?: string,
|
||||
readonly relays?: Array<string>
|
||||
) { }
|
||||
|
||||
export function linkToEventTag(link: NostrLink) {
|
||||
const relayEntry = link.relays ? [link.relays[0]] : [];
|
||||
if (link.type === NostrPrefix.PublicKey) {
|
||||
return ["p", link.id];
|
||||
} else if (link.type === NostrPrefix.Note || link.type === NostrPrefix.Event) {
|
||||
return ["e", link.id];
|
||||
} else if (link.type === NostrPrefix.Address) {
|
||||
return ["a", `${link.kind}:${link.author}:${link.id}`, ...relayEntry];
|
||||
}
|
||||
}
|
||||
|
||||
export function tagToNostrLink(tag: Array<string>) {
|
||||
switch (tag[0]) {
|
||||
case "e": {
|
||||
return createNostrLink(NostrPrefix.Event, tag[1], tag.slice(2));
|
||||
}
|
||||
case "p": {
|
||||
return createNostrLink(NostrPrefix.Profile, tag[1], tag.slice(2));
|
||||
}
|
||||
case "a": {
|
||||
const [kind, author, dTag] = tag[1].split(":");
|
||||
return createNostrLink(NostrPrefix.Address, dTag, tag.slice(2), Number(kind), author);
|
||||
encode(): string {
|
||||
if(this.type === NostrPrefix.Note || this.type === NostrPrefix.PrivateKey || this.type === NostrPrefix.PublicKey) {
|
||||
return hexToBech32(this.type, this.id);
|
||||
} else {
|
||||
return encodeTLV(this.type, this.id, this.relays, this.kind, this.author);
|
||||
}
|
||||
}
|
||||
throw new Error(`Unknown tag kind ${tag[0]}`);
|
||||
}
|
||||
|
||||
export function createNostrLinkToEvent(ev: TaggedNostrEvent | NostrEvent) {
|
||||
const relays = "relays" in ev ? ev.relays : undefined;
|
||||
|
||||
if (ev.kind >= 30_000 && ev.kind < 40_000) {
|
||||
const dTag = unwrap(findTag(ev, "d"));
|
||||
return createNostrLink(NostrPrefix.Address, dTag, relays, ev.kind, ev.pubkey);
|
||||
}
|
||||
return createNostrLink(NostrPrefix.Event, ev.id, relays, ev.kind, ev.pubkey);
|
||||
}
|
||||
|
||||
export function linkMatch(link: NostrLink, ev: NostrEvent) {
|
||||
if (link.type === NostrPrefix.Address) {
|
||||
const dTag = findTag(ev, "d");
|
||||
if (dTag && dTag === link.id && unwrap(link.author) === ev.pubkey && unwrap(link.kind) === ev.kind) {
|
||||
return true;
|
||||
toEventTag() {
|
||||
const relayEntry = this.relays ? [this.relays[0]] : [];
|
||||
if (this.type === NostrPrefix.PublicKey) {
|
||||
return ["p", this.id];
|
||||
} else if (this.type === NostrPrefix.Note || this.type === NostrPrefix.Event) {
|
||||
return ["e", this.id, ...relayEntry];
|
||||
} else if (this.type === NostrPrefix.Address) {
|
||||
return ["a", `${this.kind}:${this.author}:${this.id}`, ...relayEntry];
|
||||
}
|
||||
} else if (link.type === NostrPrefix.Event || link.type === NostrPrefix.Note) {
|
||||
return link.id === ev.id;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function createNostrLink(prefix: NostrPrefix, id: string, relays?: string[], kind?: number, author?: string) {
|
||||
return {
|
||||
type: prefix,
|
||||
id,
|
||||
relays,
|
||||
kind,
|
||||
author,
|
||||
encode: () => {
|
||||
if (prefix === NostrPrefix.Note || prefix === NostrPrefix.PublicKey) {
|
||||
return hexToBech32(prefix, id);
|
||||
matchesEvent(ev: NostrEvent) {
|
||||
if (this.type === NostrPrefix.Address) {
|
||||
const dTag = findTag(ev, "d");
|
||||
if (dTag && dTag === this.id && unwrap(this.author) === ev.pubkey && unwrap(this.kind) === ev.kind) {
|
||||
return true;
|
||||
}
|
||||
if (prefix === NostrPrefix.Address || prefix === NostrPrefix.Event || prefix === NostrPrefix.Profile) {
|
||||
return encodeTLV(prefix, id, relays, kind, author);
|
||||
} else if (this.type === NostrPrefix.Event || this.type === NostrPrefix.Note) {
|
||||
return this.id === ev.id;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static fromTag(tag: Array<string>) {
|
||||
const relays = tag.length > 2 ? [tag[2]]: undefined;
|
||||
switch (tag[0]) {
|
||||
case "e": {
|
||||
return new NostrLink(NostrPrefix.Event, tag[1], undefined, undefined, relays);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
} as NostrLink;
|
||||
case "p": {
|
||||
return new NostrLink(NostrPrefix.Profile, tag[1], undefined, undefined, relays);
|
||||
}
|
||||
case "a": {
|
||||
const [kind, author, dTag] = tag[1].split(":");
|
||||
return new NostrLink(NostrPrefix.Address, dTag, Number(kind), author, relays);
|
||||
}
|
||||
}
|
||||
throw new Error(`Unknown tag kind ${tag[0]}`);
|
||||
}
|
||||
|
||||
static fromEvent(ev: TaggedNostrEvent | NostrEvent) {
|
||||
const relays = "relays" in ev ? ev.relays : undefined;
|
||||
|
||||
if (ev.kind >= 30_000 && ev.kind < 40_000) {
|
||||
const dTag = unwrap(findTag(ev, "d"));
|
||||
return new NostrLink(NostrPrefix.Address, dTag, ev.kind, ev.pubkey, relays);
|
||||
}
|
||||
return new NostrLink(NostrPrefix.Event, ev.id, ev.kind, ev.pubkey, relays);
|
||||
}
|
||||
}
|
||||
|
||||
export function validateNostrLink(link: string): boolean {
|
||||
@ -114,19 +105,11 @@ export function parseNostrLink(link: string, prefixHint?: NostrPrefix): NostrLin
|
||||
if (isPrefix(NostrPrefix.PublicKey)) {
|
||||
const id = bech32ToHex(entity);
|
||||
if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id");
|
||||
return {
|
||||
type: NostrPrefix.PublicKey,
|
||||
id: id,
|
||||
encode: () => hexToBech32(NostrPrefix.PublicKey, id),
|
||||
};
|
||||
return new NostrLink(NostrPrefix.PublicKey, id);
|
||||
} else if (isPrefix(NostrPrefix.Note)) {
|
||||
const id = bech32ToHex(entity);
|
||||
if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id");
|
||||
return {
|
||||
type: NostrPrefix.Note,
|
||||
id: id,
|
||||
encode: () => hexToBech32(NostrPrefix.Note, id),
|
||||
};
|
||||
return new NostrLink(NostrPrefix.Note, id);
|
||||
} else if (isPrefix(NostrPrefix.Profile) || isPrefix(NostrPrefix.Event) || isPrefix(NostrPrefix.Address)) {
|
||||
const decoded = decodeTLV(entity);
|
||||
|
||||
@ -135,45 +118,17 @@ export function parseNostrLink(link: string, prefixHint?: NostrPrefix): NostrLin
|
||||
const author = decoded.find(a => a.type === TLVEntryType.Author)?.value as string;
|
||||
const kind = decoded.find(a => a.type === TLVEntryType.Kind)?.value as number;
|
||||
|
||||
const encode = () => {
|
||||
return entity; // return original
|
||||
};
|
||||
if (isPrefix(NostrPrefix.Profile)) {
|
||||
if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id");
|
||||
return {
|
||||
type: NostrPrefix.Profile,
|
||||
id,
|
||||
relays,
|
||||
kind,
|
||||
author,
|
||||
encode,
|
||||
};
|
||||
return new NostrLink(NostrPrefix.Profile, id, kind, author, relays);
|
||||
} else if (isPrefix(NostrPrefix.Event)) {
|
||||
if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id");
|
||||
return {
|
||||
type: NostrPrefix.Event,
|
||||
id,
|
||||
relays,
|
||||
kind,
|
||||
author,
|
||||
encode,
|
||||
};
|
||||
return new NostrLink(NostrPrefix.Event, id, kind, author, relays);
|
||||
} else if (isPrefix(NostrPrefix.Address)) {
|
||||
return {
|
||||
type: NostrPrefix.Address,
|
||||
id,
|
||||
relays,
|
||||
kind,
|
||||
author,
|
||||
encode,
|
||||
};
|
||||
return new NostrLink(NostrPrefix.Address, id, kind, author, relays);
|
||||
}
|
||||
} else if (prefixHint) {
|
||||
return {
|
||||
type: prefixHint,
|
||||
id: link,
|
||||
encode: () => hexToBech32(prefixHint, link),
|
||||
};
|
||||
return new NostrLink(prefixHint, link);
|
||||
}
|
||||
throw new Error("Invalid nostr link");
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import debug from "debug";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { appendDedupe, sanitizeRelayUrl, unixNowMs } from "@snort/shared";
|
||||
import { appendDedupe, sanitizeRelayUrl, unixNowMs, unwrap } from "@snort/shared";
|
||||
|
||||
import EventKind from "./event-kind";
|
||||
import { SystemInterface } from "index";
|
||||
import { NostrLink, NostrPrefix, SystemInterface } from "index";
|
||||
import { ReqFilter, u256, HexKey } from "./nostr";
|
||||
import { RelayCache, splitByWriteRelays, splitFlatByWriteRelays } from "./gossip-model";
|
||||
|
||||
@ -229,6 +229,30 @@ export class RequestFilterBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event from link
|
||||
*/
|
||||
link(link: NostrLink) {
|
||||
if(link.type === NostrPrefix.Address) {
|
||||
return this.tag("d", [link.id])
|
||||
.kinds([unwrap(link.kind)])
|
||||
.authors([unwrap(link.author)]);
|
||||
} else {
|
||||
return this.ids([link.id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get replies to link with e/a tags
|
||||
*/
|
||||
replyToLink(link: NostrLink) {
|
||||
if(link.type === NostrPrefix.Address) {
|
||||
return this.tag("a", [`${link.kind}:${link.author}:${link.id}`]);
|
||||
} else {
|
||||
return this.tag("e", [link.id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build/expand this filter into a set of relay specific queries
|
||||
*/
|
||||
|
Reference in New Issue
Block a user