fix: badges (finally)

closes #131
This commit is contained in:
kieran 2024-09-17 12:11:20 +01:00
parent f23c97fa0e
commit c6d109f182
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
7 changed files with 63 additions and 80 deletions

View File

@ -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");

View File

@ -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) && <img src={image} className="h-4" title={name} />;
}

View File

@ -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<HTMLDivElement | null>(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 ? (
<Icon name="signal" size={16} />
) : (
awardedBadges.map(badge => {
return <img key={badge.name} className="h-4" src={badge.thumb || badge.image} alt={badge.name} />;
})
awardedBadges.map(a => <AwardedChatBadge ev={a.event} pubkey={ev.pubkey} />)
)
}
pubkey={ev.pubkey}

View File

@ -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 (
<div className="badge-award">
{event && <Badge ev={event} />}
{event && <BadgeInfo ev={event} />}
<p>awarded to</p>
<div className="badge-awardees">
{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 <BadgeAward ev={a} key={a.id} />;
}
case LIVE_STREAM_CHAT: {
return <ChatMessage badges={badges} emojiPacks={allEmojiPacks} streamer={host} ev={a} key={a.id} />;
return <ChatMessage badges={awards} emojiPacks={allEmojiPacks} streamer={host} ev={a} key={a.id} />;
}
case LIVE_STREAM_RAID: {
return <ChatRaid ev={a} link={link} key={a.id} autoRaid={autoRaid} />;

View File

@ -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 <EmojiPack ev={ev} />;
}
case EventKind.Badge: {
return <Badge ev={ev} />;
return <BadgeInfo ev={ev} />;
}
case EventKind.TextNote: {
return <Note ev={ev} />;

View File

@ -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<string>;
}
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);
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,
),
};
}
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 };
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));
},
};
}

View File

@ -24,11 +24,3 @@ export interface EmojiPack {
author: string;
emojis: EmojiTag[];
}
export interface Badge {
name: string;
thumb?: string;
image?: string;
awardees: Set<string>;
accepted: Set<string>;
}