import { ref } from 'vue' import { NostrClient, type NostrNote } from '@/lib/nostr/client' import { createTextNote, createReaction, createProfileMetadata } from '@/lib/nostr/events' import { identity } from '@/composables/useIdentity' import { toast } from 'vue-sonner' import { useNostr } from './useNostr' export function useSocial(relayUrls?: string[]) { const { getClient } = useNostr(relayUrls ? { relays: relayUrls } : undefined) const client = getClient() const isPublishing = ref(false) const profiles = ref(new Map()) /** * Publish a text note */ async function publishNote(content: string, replyTo?: string): Promise { if (!identity.isAuthenticated.value || !identity.currentIdentity.value) { throw new Error('Must be logged in to publish notes') } try { isPublishing.value = true await client.connect() const event = createTextNote(content, identity.currentIdentity.value, replyTo) await client.publishEvent(event) toast.success(replyTo ? 'Reply published!' : 'Note published!') } catch (error) { const message = error instanceof Error ? error.message : 'Failed to publish note' toast.error(message) throw error } finally { isPublishing.value = false } } /** * Publish a reaction to a note */ async function publishReaction(targetEventId: string, targetAuthor: string, reaction: string = '👍'): Promise { if (!identity.isAuthenticated.value || !identity.currentIdentity.value) { throw new Error('Must be logged in to react to notes') } try { await client.connect() const event = createReaction(targetEventId, targetAuthor, reaction, identity.currentIdentity.value) await client.publishEvent(event) toast.success('Reaction added!') } catch (error) { const message = error instanceof Error ? error.message : 'Failed to add reaction' toast.error(message) throw error } } /** * Publish profile metadata */ async function publishProfile(profileData: any): Promise { if (!identity.isAuthenticated.value || !identity.currentIdentity.value) { throw new Error('Must be logged in to update profile') } try { isPublishing.value = true await client.connect() const event = createProfileMetadata(profileData, identity.currentIdentity.value) await client.publishEvent(event) toast.success('Profile updated on Nostr!') } catch (error) { const message = error instanceof Error ? error.message : 'Failed to update profile' toast.error(message) throw error } finally { isPublishing.value = false } } /** * Fetch replies to a note */ async function fetchReplies(noteId: string): Promise { try { await client.connect() return await client.fetchReplies(noteId) } catch (error) { console.error('Failed to fetch replies:', error) throw error } } /** * Fetch and cache user profiles */ async function fetchProfiles(pubkeys: string[]): Promise { // Filter out already cached profiles const uncachedPubkeys = pubkeys.filter(pubkey => !profiles.value.has(pubkey)) if (uncachedPubkeys.length === 0) return try { await client.connect() const fetchedProfiles = await client.fetchProfiles(uncachedPubkeys) // Update cache fetchedProfiles.forEach((profile, pubkey) => { profiles.value.set(pubkey, profile) }) } catch (error) { console.error('Failed to fetch profiles:', error) } } /** * Get cached profile or return default */ function getProfile(pubkey: string) { return profiles.value.get(pubkey) || { name: pubkey.slice(0, 8) + '...', display_name: undefined, about: undefined, picture: undefined } } /** * Get display name for a pubkey */ function getDisplayName(pubkey: string): string { const profile = getProfile(pubkey) return profile.display_name || profile.name || pubkey.slice(0, 8) + '...' } return { // State isPublishing, profiles, // Actions publishNote, publishReaction, publishProfile, fetchReplies, fetchProfiles, getProfile, getDisplayName } } // Export singleton instance for global use export const social = useSocial()