// lib/hooks/useFeedHooks.ts import { useMemo, useCallback, useState, useEffect } from 'react'; import { useNDKCurrentUser, useNDK } from '@/lib/hooks/useNDK'; import { nip19 } from 'nostr-tools'; import { NDKFilter, NDKEvent, NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk-mobile'; import { useFeedEvents } from '@/lib/hooks/useFeedEvents'; import { useFeedMonitor } from '@/lib/hooks/useFeedMonitor'; import { FeedOptions, AnyFeedEntry } from '@/types/feed'; import { POWR_EVENT_KINDS } from '@/types/nostr-workout'; // POWR official account pubkey export const POWR_ACCOUNT_PUBKEY = 'npub1p0wer69rpkraqs02l5v8rutagfh6g9wxn2dgytkv44ysz7avt8nsusvpjk'; // Convert POWR account pubkey to hex at the module level export let POWR_PUBKEY_HEX: string = ''; try { if (POWR_ACCOUNT_PUBKEY.startsWith('npub')) { const decoded = nip19.decode(POWR_ACCOUNT_PUBKEY); POWR_PUBKEY_HEX = decoded.data as string; } else { POWR_PUBKEY_HEX = POWR_ACCOUNT_PUBKEY; } console.log("Initialized POWR pubkey hex:", POWR_PUBKEY_HEX); } catch (error) { console.error('Error decoding POWR account npub:', error); POWR_PUBKEY_HEX = ''; } /** * Hook for the Following tab in the social feed * Shows content from authors the user follows */ export function useFollowingFeed(options: FeedOptions = {}) { const { currentUser } = useNDKCurrentUser(); const { ndk } = useNDK(); const [followedUsers, setFollowedUsers] = useState([]); const [isLoadingContacts, setIsLoadingContacts] = useState(true); // Improved contact list fetching useEffect(() => { if (!ndk || !currentUser?.pubkey) { setIsLoadingContacts(false); return; } console.log("Fetching contact list for user:", currentUser.pubkey); setIsLoadingContacts(true); const fetchContactList = async () => { try { // Try multiple approaches for maximum reliability let contacts: string[] = []; // First try: Use NDK user's native follows if (currentUser.follows) { try { // Check if follows is an array, a Set, or a function if (Array.isArray(currentUser.follows)) { contacts = [...currentUser.follows]; console.log(`Found ${contacts.length} contacts from array`); } else if (currentUser.follows instanceof Set) { contacts = Array.from(currentUser.follows); console.log(`Found ${contacts.length} contacts from Set`); } else if (typeof currentUser.follows === 'function') { // If it's a function, try to call it try { const followsResult = await currentUser.followSet(); if (followsResult instanceof Set) { contacts = Array.from(followsResult); console.log(`Found ${contacts.length} contacts from followSet() function`); } } catch (err) { console.error("Error calling followSet():", err); } } } catch (err) { console.log("Error processing follows:", err); } } // Second try: Direct kind:3 fetch if (contacts.length === 0) { try { const contactEvents = await ndk.fetchEvents({ kinds: [3], authors: [currentUser.pubkey], limit: 1 }, { closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST }); if (contactEvents.size > 0) { const contactEvent = Array.from(contactEvents)[0]; const extracted = contactEvent.tags .filter(tag => tag[0] === 'p') .map(tag => tag[1]); if (extracted.length > 0) { console.log(`Found ${extracted.length} contacts via direct kind:3 fetch`); contacts = extracted; } } } catch (err) { console.error("Error fetching kind:3 events:", err); } } // If still no contacts found, try fetching any recent events and look for p-tags if (contacts.length === 0) { try { const userEvents = await ndk.fetchEvents({ authors: [currentUser.pubkey], kinds: [1, 3, 7], // Notes, contacts, reactions limit: 10 }, { closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST }); // Collect all p-tags from recent events const mentions = new Set(); for (const event of userEvents) { event.tags .filter(tag => tag[0] === 'p') .forEach(tag => mentions.add(tag[1])); } if (mentions.size > 0) { console.log(`Found ${mentions.size} potential contacts from recent events`); contacts = Array.from(mentions); } } catch (err) { console.error("Error fetching recent events:", err); } } // If all else fails and we recognize this user, use hardcoded values (for testing only) if (contacts.length === 0 && currentUser?.pubkey === "55127fc9e1c03c6b459a3bab72fdb99def1644c5f239bdd09f3e5fb401ed9b21") { console.log("Using hardcoded follows for known user"); contacts = [ "3129509e23d3a6125e1451a5912dbe01099e151726c4766b44e1ecb8c846f506", "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52", "0c776e95521742beaf102523a8505c483e8c014ee0d3bd6457bb249034e5ff04", "2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884", "55127fc9e1c03c6b459a3bab72fdb99def1644c5f239bdd09f3e5fb401ed9b21" ]; } // Always include self to ensure self-created content is visible if (currentUser.pubkey && !contacts.includes(currentUser.pubkey)) { contacts.push(currentUser.pubkey); } // Add POWR account to followed users if not already there if (POWR_PUBKEY_HEX && !contacts.includes(POWR_PUBKEY_HEX)) { contacts.push(POWR_PUBKEY_HEX); } console.log("Final contact list count:", contacts.length); setFollowedUsers(contacts); } catch (error) { console.error("Error fetching contact list:", error); } finally { setIsLoadingContacts(false); } }; fetchContactList(); }, [ndk, currentUser?.pubkey]); // Create filters with the correct follows const followingFilters = useMemo(() => { if (followedUsers.length === 0) { console.log("No users to follow, not creating filters"); return []; } console.log("Creating filters for", followedUsers.length, "followed users"); console.log("Sample follows:", followedUsers.slice(0, 3)); return [ { kinds: [1] as any[], // Social posts authors: followedUsers, '#t': ['workout', 'fitness', 'exercise', 'powr', 'gym'], // Only workout-related posts limit: 30 }, { kinds: [30023] as any[], // Articles authors: followedUsers, limit: 20 }, { kinds: [1301, 33401, 33402] as any[], // Workout-specific content authors: followedUsers, limit: 30 } ]; }, [followedUsers]); // Use feed events hook - only enable if we have follows const feed = useFeedEvents( followedUsers.length > 0 ? followingFilters : false, { subId: 'following-feed', enabled: followedUsers.length > 0, feedType: 'following', ...options } ); // Feed monitor for auto-refresh const monitor = useFeedMonitor({ onRefresh: async () => { return feed.resetFeed(); } }); return { ...feed, ...monitor, hasFollows: followedUsers.length > 0, followCount: followedUsers.length, followedUsers: followedUsers, // Make this available for debugging isLoadingContacts }; } /** * Hook for the POWR tab in the social feed * Shows official POWR content and featured content */ export function usePOWRFeed(options: FeedOptions = {}) { // Create filters for POWR content const powrFilters = useMemo(() => { if (!POWR_PUBKEY_HEX) return []; return [ { kinds: [1, 30023, 30024] as any[], // Social posts and articles (including drafts) authors: [POWR_PUBKEY_HEX], limit: 25 }, { kinds: [1301, 33401, 33402] as any[], // Workout-specific content authors: [POWR_PUBKEY_HEX], limit: 25 } ]; }, []); // Filter function to ensure we don't show duplicates const filterPOWRContent = useCallback((entry: AnyFeedEntry) => { // Always show POWR content return true; }, []); // Use feed events hook const feed = useFeedEvents( POWR_PUBKEY_HEX ? powrFilters : false, { subId: 'powr-feed', feedType: 'powr', filterFn: filterPOWRContent, ...options } ); // Feed monitor for auto-refresh const monitor = useFeedMonitor({ onRefresh: async () => { return feed.resetFeed(); } }); return { ...feed, ...monitor }; } /** * Hook for the Global tab in the social feed * Shows all workout-related content */ /** * Hook for the user's own activity feed * Shows only the user's own posts and workouts */ export function useUserActivityFeed(options: FeedOptions = {}) { const { currentUser } = useNDKCurrentUser(); const { ndk } = useNDK(); // Create filters for user's own content const userFilters = useMemo(() => { if (!currentUser?.pubkey) return []; return [ { kinds: [1] as any[], // Social posts authors: [currentUser.pubkey], limit: 30 }, { kinds: [30023] as any[], // Articles authors: [currentUser.pubkey], limit: 20 }, { kinds: [1301, 33401, 33402] as any[], // Workout-specific content authors: [currentUser.pubkey], limit: 30 } ]; }, [currentUser?.pubkey]); // Use feed events hook const feed = useFeedEvents( currentUser?.pubkey ? userFilters : false, { subId: 'user-activity-feed', feedType: 'user-activity', ...options } ); // Feed monitor for auto-refresh const monitor = useFeedMonitor({ onRefresh: async () => { return feed.resetFeed(); } }); return { ...feed, ...monitor, hasContent: feed.entries.length > 0 }; } export function useGlobalFeed(options: FeedOptions = {}) { // Global filters - focus on workout content const globalFilters = useMemo(() => [ { kinds: [1301] as any[], // Workout records limit: 20 }, { kinds: [1] as any[], // Social posts '#t': ['workout', 'fitness', 'powr', 'gym'], // With relevant tags limit: 20 }, { kinds: [33401, 33402] as any[], // Exercise templates and workout templates limit: 20 } ], []); // Use feed events hook const feed = useFeedEvents( globalFilters, { subId: 'global-feed', feedType: 'global', ...options } ); // Feed monitor for auto-refresh const monitor = useFeedMonitor({ onRefresh: async () => { return feed.resetFeed(); } }); return { ...feed, ...monitor }; }