forked from Kieran/snort
refactor: update nip51 support
This commit is contained in:
parent
e087df2f63
commit
b765cb29b7
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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);
|
||||
}
|
@ -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]);
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
64
packages/app/src/Hooks/useLists.tsx
Normal file
64
packages/app/src/Hooks/useLists.tsx
Normal 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]);
|
||||
}
|
||||
});
|
||||
}
|
@ -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 ?? [];
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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" />
|
||||
|
@ -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 />;
|
||||
|
@ -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}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
*/
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user