import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useSkateRelays } from '@/hooks/useSkateRelays'; import { onlyVerified } from '@/lib/verifyEvents'; import type { NostrEvent } from '@nostrify/nostrify'; /** NIP-56 report kind. */ export const REPORT_KIND = 1984; export interface UserReport { id: string; /** The reporter. */ pubkey: string; createdAt: number; content: string; /** NIP-56 reason from the `p` tag, when present. */ reason?: string; } /** * Public user-level reports (NIP-56) that name this pubkey in a `p` tag, read * from the community relay + the viewer's relays. This is the auditable side of * a "flagged" account: the provider asserts a count, but the raw reports are * public, so we can show who said it in aggregate. */ export function useUserReports(pubkey: string | undefined) { const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); const query = useQuery({ queryKey: ['user-reports', pubkey, getQueryKeySuffix()], enabled: !!pubkey, staleTime: 60_000, queryFn: async (c): Promise => { if (!pubkey) return []; const signal = AbortSignal.any([c.signal, AbortSignal.timeout(8000)]); const events = onlyVerified(await querySkateContent( [{ kinds: [REPORT_KIND], '#p': [pubkey], limit: 200 }], { signal }, )); return events.map(toUserReport).filter((r): r is UserReport => r !== null); }, }); const reporters = useMemo(() => { const set = new Set(); for (const report of query.data ?? []) set.add(report.pubkey); return [...set]; }, [query.data]); return { reports: query.data ?? [], reporterPubkeys: reporters, /** Distinct reporters — the anti-brigade unit, same as content reports. */ reporterCount: reporters.length, isLoading: query.isLoading, }; } function toUserReport(event: NostrEvent): UserReport | null { const pTag = event.tags.find(([name]) => name === 'p'); if (!pTag) return null; return { id: event.id, pubkey: event.pubkey, createdAt: event.created_at, content: event.content, reason: pTag[2], }; }