From c6d109f1822e04e9b14979aa3b5eb60e85a9b5a0 Mon Sep 17 00:00:00 2001 From: kieran Date: Tue, 17 Sep 2024 12:11:20 +0100 Subject: [PATCH] fix: badges (finally) closes #131 --- src/element/badge.tsx | 2 +- src/element/chat/chat-badge.tsx | 15 ++++++ src/element/chat/chat-message.tsx | 12 ++--- src/element/chat/live-chat.tsx | 12 ++--- src/element/event-embed.tsx | 4 +- src/hooks/badges.ts | 90 ++++++++++++------------------- src/types.ts | 8 --- 7 files changed, 63 insertions(+), 80 deletions(-) create mode 100644 src/element/chat/chat-badge.tsx diff --git a/src/element/badge.tsx b/src/element/badge.tsx index 48016b1..c89fd95 100644 --- a/src/element/badge.tsx +++ b/src/element/badge.tsx @@ -2,7 +2,7 @@ import "./badge.css"; import type { NostrEvent } from "@snort/system"; import { findTag } from "@/utils"; -export function Badge({ ev }: { ev: NostrEvent }) { +export function BadgeInfo({ ev }: { ev: NostrEvent }) { const name = findTag(ev, "name") || findTag(ev, "d"); const description = findTag(ev, "description") ?? ""; const thumb = findTag(ev, "thumb"); diff --git a/src/element/chat/chat-badge.tsx b/src/element/chat/chat-badge.tsx new file mode 100644 index 0000000..0f8d9b6 --- /dev/null +++ b/src/element/chat/chat-badge.tsx @@ -0,0 +1,15 @@ +import { useProfileBadges } from "@/hooks/badges"; +import { findTag } from "@/utils"; +import { NostrLink, TaggedNostrEvent } from "@snort/system"; +import { useEventFeed } from "@snort/system-react"; + +export default function AwardedChatBadge({ ev, pubkey }: { ev: TaggedNostrEvent; pubkey: string }) { + const badgeLink = NostrLink.fromTag(ev.tags.find(a => a[0] === "a")!); + const badge = useEventFeed(badgeLink); + const image = findTag(badge, "image"); + const name = findTag(badge, "name"); + + const profileBadges = useProfileBadges(pubkey); + + return badge && profileBadges.isAccepted(badgeLink) && ; +} diff --git a/src/element/chat/chat-message.tsx b/src/element/chat/chat-message.tsx index 8ca11dd..7cc0660 100644 --- a/src/element/chat/chat-message.tsx +++ b/src/element/chat/chat-message.tsx @@ -16,11 +16,13 @@ import { CollapsibleEvent } from "../collapsible"; import { useLogin } from "@/hooks/login"; import { formatSats } from "@/number"; -import type { Badge, Emoji, EmojiPack } from "@/types"; +import type { Emoji, EmojiPack } from "@/types"; import Pill from "../pill"; import classNames from "classnames"; import Modal from "../modal"; import { ChatMenu } from "./chat-menu"; +import { BadgeAward } from "@/hooks/badges"; +import AwardedChatBadge from "./chat-badge"; function emojifyReaction(reaction: string) { if (reaction === "+") { @@ -41,7 +43,7 @@ export function ChatMessage({ ev: TaggedNostrEvent; streamer: string; emojiPacks: EmojiPack[]; - badges: Badge[]; + badges: BadgeAward[]; }) { const system = useContext(SnortContext); const ref = useRef(null); @@ -76,7 +78,7 @@ export function ChatMessage({ return zaps.filter(a => a.event?.id === ev.id).reduce((acc, z) => acc + z.amount, 0); }, [zaps, ev]); const hasZaps = totalZaps > 0; - const awardedBadges = badges.filter(b => b.awardees.has(ev.pubkey) && b.accepted.has(ev.pubkey)); + const awardedBadges = badges.filter(b => b.awardees.has(ev.pubkey)); useOnClickOutside(ref, () => { setZapping(false); @@ -142,9 +144,7 @@ export function ChatMessage({ ev.pubkey === streamer ? ( ) : ( - awardedBadges.map(badge => { - return {badge.name}; - }) + awardedBadges.map(a => ) ) } pubkey={ev.pubkey} diff --git a/src/element/chat/live-chat.tsx b/src/element/chat/live-chat.tsx index 6cf5901..ccdd1ae 100644 --- a/src/element/chat/live-chat.tsx +++ b/src/element/chat/live-chat.tsx @@ -11,11 +11,11 @@ import { Text } from "../text"; import { Profile } from "../profile"; import { ChatMessage } from "./chat-message"; import { Goal } from "../goal"; -import { Badge } from "../badge"; +import { BadgeInfo } from "../badge"; import { WriteMessage } from "./write-message"; import useEmoji, { packId } from "@/hooks/emoji"; import { useMutedPubkeys } from "@/hooks/lists"; -import { useBadges } from "@/hooks/badges"; +import { useBadgeAwards } from "@/hooks/badges"; import { useLogin } from "@/hooks/login"; import { formatSats } from "@/number"; import { LIVE_STREAM_CHAT, LIVE_STREAM_CLIP, LIVE_STREAM_RAID, WEEK } from "@/const"; @@ -33,7 +33,7 @@ function BadgeAward({ ev }: { ev: NostrEvent }) { const event = useEventFeed(new NostrLink(NostrPrefix.Address, d, Number(k), pubkey)); return (
- {event && } + {event && }

awarded to

{awardees.map(pk => ( @@ -86,7 +86,7 @@ export function LiveChat({ const starts = findTag(ev, "starts"); return starts ? Number(starts) : unixNow() - WEEK; }, [ev]); - const { badges, awards } = useBadges(host, started); + const { awards } = useBadgeAwards(host); const hostMutedPubkeys = useMutedPubkeys(host, true); const userEmojiPacks = useEmoji(login?.pubkey); @@ -108,7 +108,7 @@ export function LiveChat({ if (ends) { extra.push({ kind: -2, created_at: Number(ends) } as TaggedNostrEvent); } - return removeUndefined([...feed, ...awards, ...extra]) + return removeUndefined([...feed, ...awards.map(a => a.event), ...extra]) .filter(a => a.created_at >= started) .sort((a, b) => b.created_at - a.created_at); }, [feed, awards]); @@ -202,7 +202,7 @@ export function LiveChat({ return ; } case LIVE_STREAM_CHAT: { - return ; + return ; } case LIVE_STREAM_RAID: { return ; diff --git a/src/element/event-embed.tsx b/src/element/event-embed.tsx index f0a13bf..11bfa00 100644 --- a/src/element/event-embed.tsx +++ b/src/element/event-embed.tsx @@ -4,7 +4,7 @@ import { Icon } from "./icon"; import { Goal } from "./goal"; import { Note } from "./note"; import { EmojiPack } from "./emoji-pack"; -import { Badge } from "./badge"; +import { BadgeInfo } from "./badge"; import { GOAL, LIVE_STREAM_CLIP, StreamState } from "@/const"; import { useEventFeed } from "@snort/system-react"; import LiveStreamClip from "./stream/clip"; @@ -39,7 +39,7 @@ export function NostrEvent({ ev }: { ev: TaggedNostrEvent }) { return ; } case EventKind.Badge: { - return ; + return ; } case EventKind.TextNote: { return ; diff --git a/src/hooks/badges.ts b/src/hooks/badges.ts index 7aa833c..5df4591 100644 --- a/src/hooks/badges.ts +++ b/src/hooks/badges.ts @@ -1,69 +1,45 @@ import { useMemo } from "react"; -import { EventKind, RequestBuilder, TaggedNostrEvent } from "@snort/system"; +import { EventKind, NostrLink, RequestBuilder, TaggedNostrEvent } from "@snort/system"; import { useRequestBuilder } from "@snort/system-react"; -import { findTag, getTagValues, toAddress } from "@/utils"; -import type { Badge } from "@/types"; +export interface BadgeAward { + event: TaggedNostrEvent; + awardees: Set; +} -export function useBadges( - pubkey: string, - since: number, - leaveOpen = true, -): { badges: Badge[]; awards: TaggedNostrEvent[] } { - const rb = useMemo(() => { +export function useBadgeAwards(pubkey: string, leaveOpen = true) { + const subBadgeAwards = useMemo(() => { const rb = new RequestBuilder(`badges:${pubkey}`); rb.withOptions({ leaveOpen }); if (pubkey) { - rb.withFilter().authors([pubkey]).kinds([EventKind.Badge, EventKind.BadgeAward]); + rb.withFilter().authors([pubkey]).kinds([EventKind.BadgeAward]); } return rb; - }, [pubkey, since]); + }, [pubkey]); - const badgeEvents = useRequestBuilder(rb); - - const rawBadges = useMemo(() => { - if (badgeEvents) { - return badgeEvents.filter(e => e.kind === EventKind.Badge).sort((a, b) => b.created_at - a.created_at); - } - return []; - }, [badgeEvents]); - const badgeAwards = useMemo(() => { - if (badgeEvents) { - return badgeEvents.filter(e => e.kind === EventKind.BadgeAward); - } - return []; - }, [badgeEvents]); - - const acceptedSub = useMemo(() => { - const rb = new RequestBuilder(`accepted-badges:${pubkey}`); - if (rawBadges.length > 0) { - rb.withFilter().kinds([EventKind.ProfileBadges]).tag("d", ["profile_badges"]).tag("a", rawBadges.map(toAddress)); - } - return rb; - }, [rawBadges]); - - const acceptedStream = useRequestBuilder(acceptedSub); - const acceptedEvents = acceptedStream ?? []; - - const badges = useMemo(() => { - return rawBadges.map(e => { - const name = findTag(e, "d") ?? ""; - const address = toAddress(e); - const awardEvents = badgeAwards.filter(b => findTag(b, "a") === address); - const awardees = new Set(awardEvents.map(e => getTagValues(e.tags, "p")).flat()); - const accepted = new Set( - acceptedEvents - .filter(pb => awardees.has(pb.pubkey)) - .filter(pb => pb.tags.find(t => t.at(0) === "a" && t.at(1) === address)) - .map(pb => pb.pubkey), - ); - const thumb = findTag(e, "thumb"); - const image = findTag(e, "image"); - return { name, thumb, image, awardees, accepted }; - }); - return []; - }, [rawBadges]); - - return { badges, awards: badgeAwards }; + const awards = useRequestBuilder(subBadgeAwards); + return { + awards: awards.map( + a => + ({ + event: a, + awardees: new Set(a.tags.filter(b => b[0] === "p").map(b => b[1])), + }) as BadgeAward, + ), + }; +} + +export function useProfileBadges(pubkey: string) { + const sub = new RequestBuilder(`profile-badges:${pubkey}`); + sub.withFilter().kinds([EventKind.ProfileBadges]).authors([pubkey]).tag("d", ["profile_badges"]); + const data = useRequestBuilder(sub).at(0); + return { + event: data, + isAccepted: (link: NostrLink) => { + if (!data) return false; + const links = NostrLink.fromAllTags(data.tags); + return links.some(a => a.equals(link)); + }, + }; } diff --git a/src/types.ts b/src/types.ts index bc6f3ff..8508c12 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,11 +24,3 @@ export interface EmojiPack { author: string; emojis: EmojiTag[]; } - -export interface Badge { - name: string; - thumb?: string; - image?: string; - awardees: Set; - accepted: Set; -}