From 90700ebbf8a470db647e8196414e62a20ae93a11 Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Fri, 17 Nov 2023 11:49:45 -0600 Subject: [PATCH] Use kind 10004 for communities list --- .changeset/mean-keys-complain.md | 5 ++++ src/helpers/nostr/communities.ts | 3 ++- src/helpers/nostr/lists.ts | 22 +++++++++++++--- src/hooks/use-communities-joined-list.ts | 25 +++++++++++++------ src/hooks/use-count-community-members.ts | 9 ++----- src/services/event-exists.ts | 10 ++------ .../communities/components/community-card.tsx | 2 +- .../components/community-join-button.tsx | 12 ++++----- src/views/communities/explore.tsx | 13 +++------- .../components/community-members-modal.tsx | 24 +++++++++++++----- src/views/lists/components/list-card.tsx | 8 +++--- src/views/lists/index.tsx | 11 +++++++- 12 files changed, 90 insertions(+), 54 deletions(-) create mode 100644 .changeset/mean-keys-complain.md diff --git a/.changeset/mean-keys-complain.md b/.changeset/mean-keys-complain.md new file mode 100644 index 000000000..78ca42531 --- /dev/null +++ b/.changeset/mean-keys-complain.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Use kind 10004 for communities list instead of kind 30001 diff --git a/src/helpers/nostr/communities.ts b/src/helpers/nostr/communities.ts index 094406324..a00a9251f 100644 --- a/src/helpers/nostr/communities.ts +++ b/src/helpers/nostr/communities.ts @@ -4,6 +4,7 @@ import { getMatchLink, getMatchNostrLink } from "../regexp"; import { ReactionGroup } from "./reactions"; import { parseCoordinate } from "./events"; +/** @deprecated */ export const SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER = "communities"; export const COMMUNITY_DEFINITION_KIND = 34550; export const COMMUNITY_APPROVAL_KIND = 4550; @@ -91,7 +92,7 @@ export function getCommunityPostVote(grouped: ReactionGroup[]) { return { up, down, vote }; } -export function getEventCommunityPointer(event: NostrEvent){ +export function getEventCommunityPointer(event: NostrEvent) { const communityTag = event.tags.filter(isATag).find((t) => t[1].startsWith(COMMUNITY_DEFINITION_KIND + ":")); return communityTag ? parseCoordinate(communityTag[1], true) : null; } diff --git a/src/helpers/nostr/lists.ts b/src/helpers/nostr/lists.ts index b117c5918..0215c8ed9 100644 --- a/src/helpers/nostr/lists.ts +++ b/src/helpers/nostr/lists.ts @@ -5,15 +5,22 @@ import { AddressPointer } from "nostr-tools/lib/types/nip19"; import { DraftNostrEvent, NostrEvent, PTag, isATag, isDTag, isETag, isPTag, isRTag } from "../../types/nostr-event"; import { parseCoordinate } from "./events"; +export const MUTE_LIST_KIND = 10000; +export const PIN_LIST_KIND = 10001; +export const BOOKMARK_LIST_KIND = 10003; +export const COMMUNITIES_LIST_KIND = 10004; +export const CHATS_LIST_KIND = 10005; + export const PEOPLE_LIST_KIND = 30000; export const NOTE_LIST_KIND = 30001; -export const PIN_LIST_KIND = 10001; -export const MUTE_LIST_KIND = 10000; +export const BOOKMARK_LIST_SET_KIND = 30003; export function getListName(event: NostrEvent) { if (event.kind === Kind.Contacts) return "Following"; - if (event.kind === PIN_LIST_KIND) return "Pins"; if (event.kind === MUTE_LIST_KIND) return "Mute"; + if (event.kind === PIN_LIST_KIND) return "Pins"; + if (event.kind === BOOKMARK_LIST_KIND) return "Bookmarks"; + if (event.kind === COMMUNITIES_LIST_KIND) return "Communities"; return ( event.tags.find((t) => t[0] === "name")?.[1] || event.tags.find((t) => t[0] === "title")?.[1] || @@ -31,7 +38,14 @@ export function isJunkList(event: NostrEvent) { return /^(chats\/([0-9a-f]{64}|null)|notifications)\/lastOpened$/.test(name); } export function isSpecialListKind(kind: number) { - return kind === Kind.Contacts || kind === PIN_LIST_KIND || kind === MUTE_LIST_KIND; + return ( + kind === Kind.Contacts || + kind === MUTE_LIST_KIND || + kind === PIN_LIST_KIND || + kind === BOOKMARK_LIST_KIND || + kind === COMMUNITIES_LIST_KIND || + kind === CHATS_LIST_KIND + ); } export function cloneList(list: NostrEvent, keepCreatedAt = false): DraftNostrEvent { diff --git a/src/hooks/use-communities-joined-list.ts b/src/hooks/use-communities-joined-list.ts index 3a1f783ec..8b34cbe47 100644 --- a/src/hooks/use-communities-joined-list.ts +++ b/src/hooks/use-communities-joined-list.ts @@ -1,5 +1,5 @@ import { COMMUNITY_DEFINITION_KIND, SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../helpers/nostr/communities"; -import { NOTE_LIST_KIND, getParsedCordsFromList } from "../helpers/nostr/lists"; +import { COMMUNITIES_LIST_KIND, NOTE_LIST_KIND, getParsedCordsFromList } from "../helpers/nostr/lists"; import { RequestOptions } from "../services/replaceable-event-requester"; import useCurrentAccount from "./use-current-account"; import useReplaceableEvent from "./use-replaceable-event"; @@ -8,7 +8,10 @@ export default function useJoinedCommunitiesList(pubkey?: string, opts?: Request const account = useCurrentAccount(); const key = pubkey ?? account?.pubkey; - const list = useReplaceableEvent( + // TODO: remove at some future date when apps have transitioned to using k:10004 for communities + // https://github.com/nostr-protocol/nips/pull/880 + /** @deprecated */ + const oldList = useReplaceableEvent( key ? { kind: NOTE_LIST_KIND, @@ -19,11 +22,19 @@ export default function useJoinedCommunitiesList(pubkey?: string, opts?: Request [], opts, ); + const list = useReplaceableEvent(key ? { kind: COMMUNITIES_LIST_KIND, pubkey: key } : undefined, [], opts); - const pointers = list ? getParsedCordsFromList(list).filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND) : []; + let useList = list || oldList; + console.log(list, oldList); - return { - list, - pointers, - }; + // if both exist, use the newest one + if (list && oldList) { + useList = list.created_at > oldList.created_at ? list : oldList; + } + + const pointers = useList + ? getParsedCordsFromList(useList).filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND) + : []; + + return { list: useList, pointers }; } diff --git a/src/hooks/use-count-community-members.ts b/src/hooks/use-count-community-members.ts index a61672df3..1ab222a42 100644 --- a/src/hooks/use-count-community-members.ts +++ b/src/hooks/use-count-community-members.ts @@ -1,13 +1,8 @@ -import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../helpers/nostr/communities"; import { getEventCoordinate } from "../helpers/nostr/events"; -import { NOTE_LIST_KIND } from "../helpers/nostr/lists"; +import { COMMUNITIES_LIST_KIND } from "../helpers/nostr/lists"; import { NostrEvent } from "../types/nostr-event"; import useEventCount from "./use-event-count"; export default function useCountCommunityMembers(community: NostrEvent) { - return useEventCount({ - "#a": [getEventCoordinate(community)], - "#d": [SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER], - kinds: [NOTE_LIST_KIND], - }); + return useEventCount({ "#a": [getEventCoordinate(community)], kinds: [COMMUNITIES_LIST_KIND] }); } diff --git a/src/services/event-exists.ts b/src/services/event-exists.ts index d94eeef90..63d755fee 100644 --- a/src/services/event-exists.ts +++ b/src/services/event-exists.ts @@ -63,15 +63,9 @@ class EventExistsService { const request = new NostrRequest([nextRelay], 500); const limitFilter = Array.isArray(filter) ? filter.map((f) => ({ ...f, limit: 1 })) : { ...filter, limit: 1 }; request.start(limitFilter); - request.onEvent.subscribe(() => { - sub.next(true); - this.log(`Found event for`, filter); - }); + request.onEvent.subscribe(() => sub.next(true)); await request.onComplete; - if (sub.value === undefined) { - sub.next(false); - this.log(`couldn't find event for`, filter); - } + if (sub.value === undefined) sub.next(false); })(); } } diff --git a/src/views/communities/components/community-card.tsx b/src/views/communities/components/community-card.tsx index 6d5699da6..43d535d02 100644 --- a/src/views/communities/components/community-card.tsx +++ b/src/views/communities/components/community-card.tsx @@ -66,7 +66,7 @@ function CommunityCard({ community, ...props }: Omit & { by - {countMembers && ( + {countMembers !== undefined && countMembers > 0 && ( {readablizeSats(countMembers)} diff --git a/src/views/communities/components/community-join-button.tsx b/src/views/communities/components/community-join-button.tsx index 4423394b1..7a75a50a9 100644 --- a/src/views/communities/components/community-join-button.tsx +++ b/src/views/communities/components/community-join-button.tsx @@ -2,11 +2,11 @@ import { useCallback } from "react"; import dayjs from "dayjs"; import { Button, ButtonProps, useToast } from "@chakra-ui/react"; -import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event"; +import { DraftNostrEvent, NostrEvent, isDTag } from "../../../types/nostr-event"; import useJoinedCommunitiesList from "../../../hooks/use-communities-joined-list"; import useCurrentAccount from "../../../hooks/use-current-account"; -import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER, getCommunityName } from "../../../helpers/nostr/communities"; -import { NOTE_LIST_KIND, listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists"; +import { getCommunityName } from "../../../helpers/nostr/communities"; +import { COMMUNITIES_LIST_KIND, listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists"; import { getEventCoordinate } from "../../../helpers/nostr/events"; import { useSigningContext } from "../../../providers/signing-provider"; import NostrPublishAction from "../../../classes/nostr-publish-action"; @@ -27,11 +27,11 @@ export default function CommunityJoinButton({ const handleClick = useCallback(async () => { try { - const favList = list || { - kind: NOTE_LIST_KIND, + const favList = { + kind: COMMUNITIES_LIST_KIND, content: "", created_at: dayjs().unix(), - tags: [["d", SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER]], + tags: list?.tags.filter((t) => !isDTag(t)) ?? [], }; let draft: DraftNostrEvent; diff --git a/src/views/communities/explore.tsx b/src/views/communities/explore.tsx index ae5af99e2..852457a22 100644 --- a/src/views/communities/explore.tsx +++ b/src/views/communities/explore.tsx @@ -10,7 +10,7 @@ import { ErrorBoundary } from "../../components/error-boundary"; import { useReadRelayUrls } from "../../hooks/use-client-relays"; import useSubjects from "../../hooks/use-subjects"; import replaceableEventLoaderService from "../../services/replaceable-event-requester"; -import { NOTE_LIST_KIND, getCoordinatesFromList } from "../../helpers/nostr/lists"; +import { COMMUNITIES_LIST_KIND, NOTE_LIST_KIND, getCoordinatesFromList } from "../../helpers/nostr/lists"; import { useNavigate } from "react-router-dom"; import { ChevronLeftIcon } from "../../components/icons"; import { parseCoordinate } from "../../helpers/nostr/events"; @@ -19,17 +19,12 @@ import { AddressPointer } from "nostr-tools/lib/types/nip19"; export function useUsersJoinedCommunitiesLists(pubkeys: string[], additionalRelays: string[] = []) { const readRelays = useReadRelayUrls(additionalRelays); - const muteListSubjects = useMemo(() => { + const communityListsSubjects = useMemo(() => { return pubkeys.map((pubkey) => - replaceableEventLoaderService.requestEvent( - readRelays, - NOTE_LIST_KIND, - pubkey, - SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER, - ), + replaceableEventLoaderService.requestEvent(readRelays, COMMUNITIES_LIST_KIND, pubkey), ); }, [pubkeys]); - return useSubjects(muteListSubjects); + return useSubjects(communityListsSubjects); } function CommunityCardWithMembers({ pointer, pubkeys }: { pointer: AddressPointer; pubkeys: string[] }) { diff --git a/src/views/community/components/community-members-modal.tsx b/src/views/community/components/community-members-modal.tsx index af2a8b8fc..4bca40369 100644 --- a/src/views/community/components/community-members-modal.tsx +++ b/src/views/community/components/community-members-modal.tsx @@ -16,7 +16,7 @@ import useTimelineLoader from "../../../hooks/use-timeline-loader"; import { useReadRelayUrls } from "../../../hooks/use-client-relays"; import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER, getCommunityRelays } from "../../../helpers/nostr/communities"; import { getEventCoordinate } from "../../../helpers/nostr/events"; -import { NOTE_LIST_KIND } from "../../../helpers/nostr/lists"; +import { COMMUNITIES_LIST_KIND, NOTE_LIST_KIND } from "../../../helpers/nostr/lists"; import IntersectionObserverProvider from "../../../providers/intersection-observer"; import useSubject from "../../../hooks/use-subject"; import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback"; @@ -40,15 +40,27 @@ function UserCard({ pubkey }: { pubkey: string }) { export default function ({ community, onClose, ...props }: Omit & { community: NostrEvent }) { const communityCoordinate = getEventCoordinate(community); const readRelays = useReadRelayUrls(getCommunityRelays(community)); - const timeline = useTimelineLoader(`${communityCoordinate}-members`, readRelays, { - "#a": [communityCoordinate], - "#d": [SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER], - kinds: [NOTE_LIST_KIND], - }); + const timeline = useTimelineLoader(`${communityCoordinate}-members`, readRelays, [ + { + "#a": [communityCoordinate], + "#d": [SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER], + kinds: [NOTE_LIST_KIND], + }, + { "#a": [communityCoordinate], kinds: [COMMUNITIES_LIST_KIND] }, + ]); const lists = useSubject(timeline.timeline); const callback = useTimelineCurserIntersectionCallback(timeline); + // TODO: remove at some future date when apps have transitioned to using k:10004 for communities + // https://github.com/nostr-protocol/nips/pull/880 + const listsByPubkey: Record = {}; + for (const list of lists) { + if (!listsByPubkey[list.pubkey] || listsByPubkey[list.pubkey].created_at < list.created_at) { + listsByPubkey[list.pubkey] = list; + } + } + return ( diff --git a/src/views/lists/components/list-card.tsx b/src/views/lists/components/list-card.tsx index 339a45f01..20041ca12 100644 --- a/src/views/lists/components/list-card.tsx +++ b/src/views/lists/components/list-card.tsx @@ -87,7 +87,8 @@ function ListCardRender({ hideCreator = false, ...props }: Omit & { list: NostrEvent; hideCreator?: boolean }) { - const link = isSpecialListKind(list.kind) ? createCoordinate(list.kind, list.pubkey) : getSharableEventAddress(list); + const isSpecialList = isSpecialListKind(list.kind); + const link = isSpecialList ? createCoordinate(list.kind, list.pubkey) : getSharableEventAddress(list); // if there is a parent intersection observer, register this card const ref = useRef(null); @@ -118,9 +119,8 @@ function ListCardRender({ - - {/* TODO: reactions are tagging every user in list */} - + {!isSpecialList && } + {!isSpecialList && } diff --git a/src/views/lists/index.tsx b/src/views/lists/index.tsx index b09c781a3..7e4c76459 100644 --- a/src/views/lists/index.tsx +++ b/src/views/lists/index.tsx @@ -9,7 +9,14 @@ import { getEventUID } from "../../helpers/nostr/events"; import useUserLists from "../../hooks/use-user-lists"; import NewListModal from "./components/new-list-modal"; import { getSharableEventAddress } from "../../helpers/nip19"; -import { MUTE_LIST_KIND, NOTE_LIST_KIND, PEOPLE_LIST_KIND, PIN_LIST_KIND } from "../../helpers/nostr/lists"; +import { + BOOKMARK_LIST_KIND, + COMMUNITIES_LIST_KIND, + MUTE_LIST_KIND, + NOTE_LIST_KIND, + PEOPLE_LIST_KIND, + PIN_LIST_KIND, +} from "../../helpers/nostr/lists"; import useFavoriteLists from "../../hooks/use-favorite-lists"; import VerticalPageLayout from "../../components/vertical-page-layout"; @@ -51,6 +58,8 @@ function ListsPage() { + + {peopleLists.length > 0 && ( <>