a tool to help your Letta AI agents navigate bluesky

created updated function for checking following between two users

Changed files
+96
utils
+96
utils/doesUserFollow.ts
··· 47 47 const userProfile = await bsky.getProfile({ actor: did }); 48 48 return `user "@${userProfile.data.handle}" DOES NOT FOLLOW YOU`; 49 49 }; 50 + 51 + /** 52 + * Checks if user follows target on Bluesky. 53 + * 54 + * @param userDID - The DID of the user (does this user follow target?) 55 + * @param targetDID - The DID of the target 56 + * @returns `true` if user follows target, `false` if not, `null` if unable to determine 57 + */ 58 + export const doesUserFollowTarget = async ( 59 + userDID: string, 60 + targetDID: string, 61 + ): Promise<boolean | null> => { 62 + const maxAPICalls = 50; 63 + const itemsPerPage = 100; 64 + // Reserve 2 API calls for fetching both user and target profiles 65 + const maxFollowersToCheck = (maxAPICalls - 2) * itemsPerPage; 66 + 67 + try { 68 + // Fetch both profiles to determine which list is shorter 69 + const [userProfile, targetProfile] = await Promise.all([ 70 + bsky.getProfile({ actor: userDID }), 71 + bsky.getProfile({ actor: targetDID }), 72 + ]); 73 + 74 + const userFollowingCount = userProfile.data.followsCount || 0; 75 + const targetFollowersCount = targetProfile.data.followersCount || 0; 76 + 77 + // If both counts exceed our limit, we can't reliably check 78 + if ( 79 + userFollowingCount > maxFollowersToCheck && 80 + targetFollowersCount > maxFollowersToCheck 81 + ) { 82 + return null; 83 + } 84 + 85 + // Determine which list to check (choose the shorter one) 86 + const checkUserFollowing = userFollowingCount <= targetFollowersCount; 87 + 88 + let cursor: string | undefined = undefined; 89 + let itemsChecked = 0; 90 + 91 + while (itemsChecked < maxFollowersToCheck) { 92 + if (checkUserFollowing) { 93 + // Check who userDID follows 94 + const { data: followsData } = await bsky.getFollows({ 95 + actor: userDID, 96 + limit: itemsPerPage, 97 + cursor: cursor, 98 + }); 99 + 100 + // Check if targetDID is in the follows list 101 + for (const followed of followsData.follows) { 102 + if (followed.did === targetDID) { 103 + return true; 104 + } 105 + } 106 + 107 + itemsChecked += followsData.follows.length; 108 + 109 + if (!followsData.cursor) { 110 + break; 111 + } 112 + 113 + cursor = followsData.cursor; 114 + } else { 115 + // Check who follows targetDID 116 + const { data: followersData } = await bsky.getFollowers({ 117 + actor: targetDID, 118 + limit: itemsPerPage, 119 + cursor: cursor, 120 + }); 121 + 122 + // Check if userDID is in the followers list 123 + for (const follower of followersData.followers) { 124 + if (follower.did === userDID) { 125 + return true; 126 + } 127 + } 128 + 129 + itemsChecked += followersData.followers.length; 130 + 131 + if (!followersData.cursor) { 132 + break; 133 + } 134 + 135 + cursor = followersData.cursor; 136 + } 137 + } 138 + 139 + // If we get here, we didn't find the relationship 140 + return false; 141 + } catch (_error) { 142 + // Return null on any error - allows calling code to continue without this info 143 + return null; 144 + } 145 + };