refactor: update nip51 support
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Kieran 2023-11-10 13:17:19 +00:00
parent e087df2f63
commit b765cb29b7
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
19 changed files with 192 additions and 205 deletions

View File

@ -49,7 +49,7 @@ export default function Note(props: NoteProps) {
if (ev.kind === EventKind.ZapstrTrack) {
return <ZapstrEmbed ev={ev} />;
}
if (ev.kind === EventKind.PubkeyLists || ev.kind === EventKind.ContactList) {
if (ev.kind === EventKind.CategorizedPeople || ev.kind === EventKind.ContactList) {
return <PubkeyList ev={ev} className={className} />;
}
if (ev.kind === EventKind.LiveEvent) {

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { HexKey, Lists, NostrLink, TaggedNostrEvent } from "@snort/system";
import { HexKey, NostrLink, NostrPrefix, TaggedNostrEvent } from "@snort/system";
import { Menu, MenuItem } from "@szhsin/react-menu";
import Icon from "Icons/Icon";
@ -96,16 +96,19 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
async function pin(id: HexKey) {
if (publisher) {
const es = [...login.pinned.item, id];
const ev = await publisher.noteList(es, Lists.Pinned);
const ev = await publisher.pinned(es.map(a => new NostrLink(NostrPrefix.Note, a)));
system.BroadcastEvent(ev);
setPinned(login, es, ev.created_at * 1000);
}
}
async function bookmark(id: HexKey) {
async function bookmark(id: string) {
if (publisher) {
const es = [...login.bookmarked.item, id];
const ev = await publisher.noteList(es, Lists.Bookmarked);
const ev = await publisher.bookmarks(
es.map(a => new NostrLink(NostrPrefix.Note, a)),
"bookmark",
);
system.BroadcastEvent(ev);
setBookmarked(login, es, ev.created_at * 1000);
}

View File

@ -3,7 +3,7 @@ import React, { ReactNode, useMemo, useState } from "react";
import { useInView } from "react-intersection-observer";
import { FormattedMessage, useIntl } from "react-intl";
import classNames from "classnames";
import { EventExt, EventKind, HexKey, Lists, NostrLink, NostrPrefix, TaggedNostrEvent } from "@snort/system";
import { EventExt, EventKind, HexKey, NostrLink, NostrPrefix, TaggedNostrEvent } from "@snort/system";
import { useEventReactions } from "@snort/system-react";
import { findTag, hexToBech32 } from "SnortUtils";
@ -60,7 +60,7 @@ export function NoteInner(props: NoteProps) {
if (options.canUnpin && publisher) {
if (window.confirm(formatMessage(messages.ConfirmUnpin))) {
const es = pinned.item.filter(e => e !== id);
const ev = await publisher.noteList(es, Lists.Pinned);
const ev = await publisher.pinned(es.map(a => new NostrLink(NostrPrefix.Note, a)));
system.BroadcastEvent(ev);
setPinned(login, es, ev.created_at * 1000);
}
@ -71,7 +71,7 @@ export function NoteInner(props: NoteProps) {
if (options.canUnbookmark && publisher) {
if (window.confirm(formatMessage(messages.ConfirmUnbookmark))) {
const es = bookmarked.item.filter(e => e !== id);
const ev = await publisher.noteList(es, Lists.Bookmarked);
const ev = await publisher.pinned(es.map(a => new NostrLink(NostrPrefix.Note, a)));
system.BroadcastEvent(ev);
setBookmarked(login, es, ev.created_at * 1000);
}

View File

@ -1,5 +1,5 @@
import { useMemo } from "react";
import { EventKind, HexKey, Lists, RequestBuilder, ReplaceableNoteStore, NoteCollection } from "@snort/system";
import { EventKind, HexKey, RequestBuilder, ReplaceableNoteStore, NoteCollection } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { unwrap, findTag, chunks } from "SnortUtils";
@ -13,7 +13,7 @@ export default function useProfileBadges(pubkey?: HexKey) {
const sub = useMemo(() => {
if (!pubkey) return null;
const b = new RequestBuilder(`badges:${pubkey.slice(0, 12)}`);
b.withFilter().kinds([EventKind.ProfileBadges]).tag("d", [Lists.Badges]).authors([pubkey]);
b.withFilter().kinds([EventKind.ProfileBadges]).tag("d", ["profile_badges"]).authors([pubkey]);
return b;
}, [pubkey]);

View File

@ -1,9 +0,0 @@
import { HexKey, Lists } from "@snort/system";
import useNotelistSubscription from "Hooks/useNotelistSubscription";
import useLogin from "Hooks/useLogin";
export default function useBookmarkFeed(pubkey?: HexKey) {
const { bookmarked } = useLogin();
return useNotelistSubscription(pubkey, Lists.Bookmarked, bookmarked.item);
}

View File

@ -1,11 +1,10 @@
import { useEffect, useMemo } from "react";
import { TaggedNostrEvent, Lists, EventKind, RequestBuilder, NoteCollection } from "@snort/system";
import { TaggedNostrEvent, EventKind, RequestBuilder, NoteCollection, NostrLink } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { bech32ToHex, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils";
import { bech32ToHex, findTag, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils";
import { makeNotification, sendNotification } from "Notifications";
import useEventPublisher from "Hooks/useEventPublisher";
import { getMutedKeys } from "Feed/MuteList";
import useModeration from "Hooks/useModeration";
import useLogin from "Hooks/useLogin";
import {
@ -51,7 +50,10 @@ export default function useLoginFeed() {
b.withOptions({
leaveOpen: true,
});
b.withFilter().authors([pubKey]).kinds([EventKind.ContactList, EventKind.Relays]);
b.withFilter()
.authors([pubKey])
.kinds([EventKind.ContactList, EventKind.Relays, EventKind.MuteList, EventKind.PinList]);
b.withFilter().authors([pubKey]).kinds([EventKind.CategorizedBookmarks]).tag("d", ["follow", "bookmark"]);
if (CONFIG.features.subscriptions && !login.readonly) {
b.withFilter().authors([pubKey]).kinds([EventKind.AppData]).tag("d", ["snort"]);
b.withFilter()
@ -61,10 +63,6 @@ export default function useLoginFeed() {
.tag("p", [pubKey])
.limit(10);
}
b.withFilter()
.authors([pubKey])
.kinds([EventKind.PubkeyLists])
.tag("d", [Lists.Muted, Lists.Followed, Lists.Pinned, Lists.Bookmarked]);
const n4Sub = Nip4Chats.subscription(login);
if (n4Sub) {
@ -151,23 +149,26 @@ export default function useLoginFeed() {
}
}, [loginFeed, readNotifications]);
function handleMutedFeed(mutedFeed: TaggedNostrEvent[]) {
const muted = getMutedKeys(mutedFeed);
setMuted(login, muted.keys, muted.createdAt * 1000);
async function handleMutedFeed(mutedFeed: TaggedNostrEvent[]) {
const latest = getNewest(mutedFeed);
if (!latest) return;
if (muted.raw && (muted.raw?.content?.length ?? 0) > 0 && pubKey) {
publisher
?.nip4Decrypt(muted.raw.content, pubKey)
.then(plaintext => {
try {
const blocked = JSON.parse(plaintext);
const keys = blocked.filter((p: string) => p && p.length === 2 && p[0] === "p").map((p: string) => p[1]);
setBlocked(login, keys, unwrap(muted.raw).created_at * 1000);
} catch (error) {
console.debug("Couldn't parse JSON");
}
})
.catch(error => console.warn(error));
const muted = NostrLink.fromTags(latest.tags);
setMuted(
login,
muted.map(a => a.id),
latest.created_at * 1000,
);
if (latest?.content && publisher && pubKey) {
try {
const privMutes = await publisher.nip4Decrypt(latest.content, pubKey);
const blocked = JSON.parse(privMutes) as Array<Array<string>>;
const keys = blocked.filter(a => a[0] === "p").map(a => a[1]);
setBlocked(login, keys, latest.created_at * 1000);
} catch (error) {
console.debug("Failed to parse mute list", error, latest);
}
}
}
@ -194,23 +195,20 @@ export default function useLoginFeed() {
useEffect(() => {
if (loginFeed.data) {
const getList = (evs: readonly TaggedNostrEvent[], list: Lists) =>
evs
.filter(
a => a.kind === EventKind.TagLists || a.kind === EventKind.NoteLists || a.kind === EventKind.PubkeyLists,
)
.filter(a => unwrap(a.tags.find(b => b[0] === "d"))[1] === list);
const mutedFeed = getList(loginFeed.data, Lists.Muted);
const mutedFeed = loginFeed.data.filter(a => a.kind === EventKind.MuteList);
handleMutedFeed(mutedFeed);
const pinnedFeed = getList(loginFeed.data, Lists.Pinned);
const pinnedFeed = loginFeed.data.filter(a => a.kind === EventKind.PinList);
handlePinnedFeed(pinnedFeed);
const tagsFeed = getList(loginFeed.data, Lists.Followed);
const tagsFeed = loginFeed.data.filter(
a => a.kind === EventKind.CategorizedBookmarks && findTag(a, "d") === "follow",
);
handleTagFeed(tagsFeed);
const bookmarkFeed = getList(loginFeed.data, Lists.Bookmarked);
const bookmarkFeed = loginFeed.data.filter(
a => a.kind === EventKind.CategorizedBookmarks && findTag(a, "d") === "bookmark",
);
handleBookmarkFeed(bookmarkFeed);
}
}, [loginFeed]);

View File

@ -1,52 +0,0 @@
import { useMemo } from "react";
import { HexKey, TaggedNostrEvent, Lists, EventKind, NoteCollection, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { getNewest } from "SnortUtils";
import useLogin from "Hooks/useLogin";
export default function useMutedFeed(pubkey?: HexKey) {
const { publicKey, muted } = useLogin();
const isMe = publicKey === pubkey;
const sub = useMemo(() => {
if (isMe || !pubkey) return null;
const b = new RequestBuilder(`muted:${pubkey.slice(0, 12)}`);
b.withFilter().authors([pubkey]).kinds([EventKind.PubkeyLists]).tag("d", [Lists.Muted]);
return b;
}, [pubkey]);
const mutedFeed = useRequestBuilder(NoteCollection, sub);
const mutedList = useMemo(() => {
if (pubkey && mutedFeed.data) {
return getMuted(mutedFeed.data, pubkey);
}
return [];
}, [mutedFeed, pubkey]);
return isMe ? muted.item : mutedList;
}
export function getMutedKeys(rawNotes: TaggedNostrEvent[]): {
createdAt: number;
keys: HexKey[];
raw?: TaggedNostrEvent;
} {
const newest = getNewest(rawNotes);
if (newest) {
const { created_at, tags } = newest;
const keys = tags.filter(t => t[0] === "p").map(t => t[1]);
return {
raw: newest,
keys,
createdAt: created_at,
};
}
return { createdAt: 0, keys: [] };
}
export function getMuted(feed: readonly TaggedNostrEvent[], pubkey: HexKey): HexKey[] {
const lists = feed.filter(a => a.kind === EventKind.PubkeyLists && a.pubkey === pubkey);
return getMutedKeys(lists).keys;
}

View File

@ -1,8 +0,0 @@
import { HexKey, Lists } from "@snort/system";
import useNotelistSubscription from "Hooks/useNotelistSubscription";
import useLogin from "Hooks/useLogin";
export default function usePinnedFeed(pubkey?: HexKey) {
const pinned = useLogin().pinned.item;
return useNotelistSubscription(pubkey, Lists.Pinned, pinned);
}

View File

@ -0,0 +1,64 @@
import { removeUndefined } from "@snort/shared";
import { EventKind, NostrLink, NoteCollection, RequestBuilder } from "@snort/system";
import { useEventsFeed, useRequestBuilder } from "@snort/system-react";
import { useMemo } from "react";
/**
* Use a link event containing e/a/p/t tags
*/
export function useLinkList(id: string, fn: (rb: RequestBuilder) => void) {
const sub = useMemo(() => {
const rb = new RequestBuilder(id);
fn(rb);
return rb;
}, [id, fn]);
const listStore = useRequestBuilder(NoteCollection, sub);
return useMemo(() => {
if (listStore.data && listStore.data.length > 0) {
return removeUndefined(
listStore.data
.map(e =>
e.tags.map(a => {
try {
return NostrLink.fromTag(a);
} catch {
// ignored, skipped
}
}),
)
.flat(),
);
}
return [];
}, [listStore.data]);
}
export function useLinkListEvents(id: string, fn: (rb: RequestBuilder) => void) {
const links = useLinkList(id, fn);
return useEventsFeed(`${id}:events`, links).data ?? [];
}
export function usePinList(pubkey: string | undefined) {
return useLinkListEvents(`pins:${pubkey?.slice(0, 12)}`, rb => {
if (pubkey) {
rb.withFilter().kinds([EventKind.PinList]).authors([pubkey]);
}
});
}
export function useMuteList(pubkey: string | undefined) {
return useLinkList(`pins:${pubkey?.slice(0, 12)}`, rb => {
if (pubkey) {
rb.withFilter().kinds([EventKind.MuteList]).authors([pubkey]);
}
});
}
export default function useCategorizedBookmarks(pubkey: string | undefined, list: string) {
return useLinkListEvents(`categorized-bookmarks:${list}:${pubkey?.slice(0, 12)}`, rb => {
if (pubkey) {
rb.withFilter().kinds([EventKind.CategorizedBookmarks]).authors([pubkey]).tag("d", [list]);
}
});
}

View File

@ -1,44 +0,0 @@
import { useMemo } from "react";
import { HexKey, Lists, EventKind, FlatNoteStore, NoteCollection, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import useLogin from "Hooks/useLogin";
export default function useNotelistSubscription(pubkey: HexKey | undefined, l: Lists, defaultIds: HexKey[]) {
const { preferences, publicKey } = useLogin();
const isMe = publicKey === pubkey;
const sub = useMemo(() => {
if (isMe || !pubkey) return null;
const rb = new RequestBuilder(`note-list-${l}:${pubkey.slice(0, 12)}`);
rb.withFilter().kinds([EventKind.NoteLists]).authors([pubkey]).tag("d", [l]).limit(1);
return rb;
}, [pubkey]);
const listStore = useRequestBuilder(NoteCollection, sub);
const etags = useMemo(() => {
if (isMe) return defaultIds;
// there should only be a single event here because we only load 1 pubkey
if (listStore.data && listStore.data.length > 0) {
return listStore.data[0].tags.filter(a => a[0] === "e").map(a => a[1]);
}
return [];
}, [listStore.data, isMe, defaultIds]);
const esub = useMemo(() => {
if (!pubkey || etags.length === 0) return null;
const s = new RequestBuilder(`${l}-notes:${pubkey.slice(0, 12)}`);
s.withFilter().kinds([EventKind.TextNote]).ids(etags);
if (etags.length > 0 && preferences.enableReactions) {
s.withFilter()
.kinds([EventKind.Reaction, EventKind.Repost, EventKind.Deletion, EventKind.ZapReceipt])
.tag("e", etags);
}
return s;
}, [etags, pubkey, preferences]);
const store = useRequestBuilder(FlatNoteStore, esub);
return store.data ?? [];
}

View File

@ -1,6 +1,7 @@
import { useMemo } from "react";
import { useParams } from "react-router-dom";
import { FormattedMessage } from "react-intl";
import { NostrHashtagLink } from "@snort/system";
import Timeline from "Element/Feed/Timeline";
import useEventPublisher from "Hooks/useEventPublisher";
@ -18,7 +19,10 @@ const HashTagsPage = () => {
async function followTags(ts: string[]) {
if (publisher) {
const ev = await publisher.tags(ts);
const ev = await publisher.bookmarks(
ts.map(a => new NostrHashtagLink(a)),
"follow",
);
system.BroadcastEvent(ev);
setTags(login, ts, ev.created_at * 1000);
}

View File

@ -14,7 +14,7 @@ export function ListFeedPage() {
const { data } = useEventFeed(link);
if (!data) return <PageSpinner />;
if (data.kind !== EventKind.ContactList && data.kind !== EventKind.PubkeyLists) {
if (data.kind !== EventKind.ContactList && data.kind !== EventKind.CategorizedPeople) {
return (
<b>
<FormattedMessage defaultMessage="Must be a contact list or pubkey list" />

View File

@ -19,8 +19,6 @@ import { findTag, getLinkReactions, unwrap } from "SnortUtils";
import Note from "Element/Event/Note";
import { Tab, TabElement } from "Element/Tabs";
import Icon from "Icons/Icon";
import useMutedFeed from "Feed/MuteList";
import usePinnedFeed from "Feed/PinnedFeed";
import useFollowsFeed from "Feed/FollowsFeed";
import useProfileBadges from "Feed/BadgesFeed";
import useModeration from "Hooks/useModeration";
@ -47,8 +45,6 @@ import { EmailRegex } from "Const";
import useLogin from "Hooks/useLogin";
import { ZapTarget } from "Zapper";
import { useStatusFeed } from "Feed/StatusFeed";
import messages from "../messages";
import { SpotlightMediaModal } from "Element/SpotlightMedia";
import ProfileTab, {
BookMarksTab,
@ -60,6 +56,9 @@ import ProfileTab, {
} from "Pages/Profile/ProfileTab";
import DisplayName from "Element/User/DisplayName";
import { UserWebsiteLink } from "Element/User/UserWebsiteLink";
import { useMuteList, usePinList } from "Hooks/useLists";
import messages from "../messages";
interface ProfilePageProps {
id?: string;
@ -95,8 +94,8 @@ export default function ProfilePage({ id: propId, state }: ProfilePageProps) {
// feeds
const { blocked } = useModeration();
const pinned = usePinnedFeed(id);
const muted = useMutedFeed(id);
const pinned = usePinList(id);
const muted = useMuteList(id);
const badges = useProfileBadges(showBadges ? id : undefined);
const follows = useFollowsFeed(id);
const status = useStatusFeed(showStatus ? id : undefined, true);
@ -273,7 +272,7 @@ export default function ProfilePage({ id: propId, state }: ProfilePageProps) {
return <FollowersTab id={id} />;
}
case ProfileTabType.MUTED: {
return <MutedList pubkeys={muted} />;
return <MutedList pubkeys={muted.map(a => a.id)} />;
}
case ProfileTabType.BLOCKED: {
return <BlockList />;

View File

@ -8,11 +8,11 @@ import FollowsList from "Element/User/FollowListBase";
import useFollowsFeed from "Feed/FollowsFeed";
import useRelaysFeed from "Feed/RelaysFeed";
import RelaysMetadata from "Element/Relay/RelaysMetadata";
import useBookmarkFeed from "Feed/BookmarkFeed";
import Bookmarks from "Element/User/Bookmarks";
import Icon from "Icons/Icon";
import { Tab } from "Element/Tabs";
import { default as ZapElement } from "Element/Event/Zap";
import useCategorizedBookmarks from "Hooks/useLists";
import messages from "../messages";
@ -59,7 +59,7 @@ export function RelaysTab({ id }: { id: HexKey }) {
}
export function BookMarksTab({ id }: { id: HexKey }) {
const bookmarks = useBookmarkFeed(id);
const bookmarks = useCategorizedBookmarks(id, "bookmark");
return (
<Bookmarks
pubkey={id}

View File

@ -5,6 +5,7 @@ import AsyncButton from "Element/AsyncButton";
import classNames from "classnames";
import { appendDedupe } from "SnortUtils";
import useEventPublisher from "Hooks/useEventPublisher";
import { NostrHashtagLink } from "@snort/system";
export const FixedTopics = {
life: {
@ -69,9 +70,11 @@ export function Topics() {
const tags = Object.entries(FixedTopics)
.filter(([k]) => topics.includes(k))
.map(([, v]) => v.tags)
.flat();
.flat()
.map(a => new NostrHashtagLink(a));
if (tags.length > 0) {
const ev = await publisher?.tags(tags);
const ev = await publisher?.bookmarks(tags, "follow");
if (ev) {
await system.BroadcastEvent(ev);
}

View File

@ -24,11 +24,17 @@ enum EventKind {
Relays = 10002, // NIP-65
Ephemeral = 20_000,
Auth = 22242, // NIP-42
PubkeyLists = 30000, // NIP-51a
NoteLists = 30001, // NIP-51b
MuteList = 10_000, // NIP-51
PinList = 10_001, // NIP-51
CategorizedPeople = 30000, // NIP-51a
CategorizedBookmarks = 30001, // NIP-51b
TagLists = 30002, // NIP-51c
Badge = 30009, // NIP-58
ProfileBadges = 30008, // NIP-58
LongFormTextNote = 30023, // NIP-23
AppData = 30_078, // NIP-78
LiveEvent = 30311, // NIP-102
@ -37,7 +43,7 @@ enum EventKind {
SimpleChatMetadata = 39_000, // NIP-29
ZapRequest = 9734, // NIP 57
ZapReceipt = 9735, // NIP 57
HttpAuthentication = 27235, // NIP XX - HTTP Authentication
HttpAuthentication = 27235, // NIP 98 - HTTP Authentication
}
export default EventKind;

View File

@ -1,6 +1,6 @@
import * as secp from "@noble/curves/secp256k1";
import * as utils from "@noble/curves/abstract/utils";
import { unwrap, getPublicKey, unixNow } from "@snort/shared";
import { unwrap } from "@snort/shared";
import {
decodeEncryptionPayload,
@ -8,7 +8,6 @@ import {
EventSigner,
FullRelaySettings,
HexKey,
Lists,
MessageEncryptorVersion,
NostrEvent,
NostrLink,
@ -18,6 +17,7 @@ import {
RelaySettings,
SignerSupports,
TaggedNostrEvent,
ToNostrEventTag,
u256,
UserMetadata,
} from ".";
@ -104,11 +104,14 @@ export class EventPublisher {
return await this.#sign(eb);
}
async muted(keys: HexKey[], priv: HexKey[]) {
const eb = this.#eb(EventKind.PubkeyLists);
eb.tag(["d", Lists.Muted]);
keys.forEach(p => {
/**
* Build a mute list event using lists of pubkeys
* @param pub Public mute list
* @param priv Private mute list
*/
async muted(pub: Array<string>, priv: Array<string>) {
const eb = this.#eb(EventKind.MuteList);
pub.forEach(p => {
eb.tag(["p", p]);
});
if (priv.length > 0) {
@ -119,20 +122,26 @@ export class EventPublisher {
return await this.#sign(eb);
}
async noteList(notes: u256[], list: Lists) {
const eb = this.#eb(EventKind.NoteLists);
eb.tag(["d", list]);
/**
* Build a pin list event using lists of event links
*/
async pinned(notes: Array<ToNostrEventTag>) {
const eb = this.#eb(EventKind.PinList);
notes.forEach(n => {
eb.tag(["e", n]);
eb.tag(unwrap(n.toEventTag()));
});
return await this.#sign(eb);
}
async tags(tags: string[]) {
const eb = this.#eb(EventKind.TagLists);
eb.tag(["d", Lists.Followed]);
tags.forEach(t => {
eb.tag(["t", t]);
/**
* Build a categorized bookmarks event with a given label
* @param notes List of bookmarked links
*/
async bookmarks(notes: Array<ToNostrEventTag>, list: "bookmark" | "follow") {
const eb = this.#eb(EventKind.CategorizedBookmarks);
eb.tag(["d", list]);
notes.forEach(n => {
eb.tag(unwrap(n.toEventTag()));
});
return await this.#sign(eb);
}
@ -263,6 +272,7 @@ export class EventPublisher {
eb.tag(["e", id]);
return await this.#sign(eb);
}
/**
* Repost a note (NIP-18)
*/

View File

@ -1,4 +1,4 @@
import { bech32ToHex, hexToBech32, isHex, unwrap } from "@snort/shared";
import { bech32ToHex, hexToBech32, isHex, removeUndefined, unwrap } from "@snort/shared";
import {
decodeTLV,
encodeTLV,
@ -12,7 +12,19 @@ import {
} from ".";
import { findTag } from "./utils";
export class NostrLink {
export interface ToNostrEventTag {
toEventTag(): Array<string> | undefined;
}
export class NostrHashtagLink implements ToNostrEventTag {
constructor(readonly tag: string) {}
toEventTag(): string[] | undefined {
return ["t", this.tag];
}
}
export class NostrLink implements ToNostrEventTag {
constructor(
readonly type: NostrPrefix,
readonly id: string,
@ -42,8 +54,8 @@ export class NostrLink {
toEventTag() {
const relayEntry = this.relays ? [this.relays[0]] : [];
if (this.type === NostrPrefix.PublicKey) {
return ["p", this.id];
if (this.type === NostrPrefix.PublicKey || this.type === NostrPrefix.Profile) {
return ["p", this.id, ...relayEntry];
} else if (this.type === NostrPrefix.Note || this.type === NostrPrefix.Event) {
return ["e", this.id, ...relayEntry];
} else if (this.type === NostrPrefix.Address) {
@ -174,6 +186,18 @@ export class NostrLink {
throw new Error(`Unknown tag kind ${tag[0]}`);
}
static fromTags(tags: Array<Array<string>>) {
return removeUndefined(
tags.map(a => {
try {
return NostrLink.fromTag(a);
} catch {
// ignored, cant be mapped
}
}),
);
}
static fromEvent(ev: TaggedNostrEvent | NostrEvent) {
const relays = "relays" in ev ? ev.relays : undefined;

View File

@ -70,17 +70,6 @@ export type UserMetadata = {
lud16?: string;
};
/**
* NIP-51 list types
*/
export enum Lists {
Muted = "mute",
Pinned = "pin",
Bookmarked = "bookmark",
Followed = "follow",
Badges = "profile_badges",
}
export interface FullRelaySettings {
url: string;
settings: RelaySettings;