import { bsky } from "./bsky.ts"; /** * Checks if user follows target on Bluesky. * * @param userDID - The DID of the user (does this user follow target?) * @param targetDID - The DID of the target * @returns `true` if user follows target, `false` if not, `null` if unable to determine */ export const doesUserFollowTarget = async ( userDID: string, targetDID: string, ): Promise => { const maxAPICalls = 50; const itemsPerPage = 100; // Reserve 2 API calls for fetching both user and target profiles const maxFollowersToCheck = (maxAPICalls - 2) * itemsPerPage; try { // Fetch both profiles to determine which list is shorter const [userProfile, targetProfile] = await Promise.all([ bsky.getProfile({ actor: userDID }), bsky.getProfile({ actor: targetDID }), ]); const userFollowingCount = userProfile.data.followsCount || 0; const targetFollowersCount = targetProfile.data.followersCount || 0; // If both counts exceed our limit, we can't reliably check if ( userFollowingCount > maxFollowersToCheck && targetFollowersCount > maxFollowersToCheck ) { return null; } // Determine which list to check (choose the shorter one) const checkUserFollowing = userFollowingCount <= targetFollowersCount; let cursor: string | undefined = undefined; let itemsChecked = 0; while (itemsChecked < maxFollowersToCheck) { if (checkUserFollowing) { // Check who userDID follows const { data: followsData } = await bsky.getFollows({ actor: userDID, limit: itemsPerPage, cursor: cursor, }); // Check if targetDID is in the follows list for (const followed of followsData.follows) { if (followed.did === targetDID) { return true; } } itemsChecked += followsData.follows.length; if (!followsData.cursor) { break; } cursor = followsData.cursor; } else { // Check who follows targetDID const { data: followersData } = await bsky.getFollowers({ actor: targetDID, limit: itemsPerPage, cursor: cursor, }); // Check if userDID is in the followers list for (const follower of followersData.followers) { if (follower.did === userDID) { return true; } } itemsChecked += followersData.followers.length; if (!followersData.cursor) { break; } cursor = followersData.cursor; } } // If we get here, we didn't find the relationship return false; } catch (_error) { // Return null on any error - allows calling code to continue without this info return null; } };