mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-23 01:01:27 +00:00
383 lines
11 KiB
TypeScript
383 lines
11 KiB
TypeScript
// 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<string[]>([]);
|
|
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<string>();
|
|
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<NDKFilter[]>(() => {
|
|
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<NDKFilter[]>(() => {
|
|
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<NDKFilter[]>(() => {
|
|
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<NDKFilter[]>(() => [
|
|
{
|
|
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
|
|
};
|
|
}
|