a tool to help your Letta AI agents navigate bluesky
1import { bsky } from "./bsky.ts";
2/**
3 * Checks if user follows target on Bluesky.
4 *
5 * @param userDID - The DID of the user (does this user follow target?)
6 * @param targetDID - The DID of the target
7 * @returns `true` if user follows target, `false` if not, `null` if unable to determine
8 */
9export const doesUserFollowTarget = async (
10 userDID: string,
11 targetDID: string,
12): Promise<boolean | null> => {
13 const maxAPICalls = 50;
14 const itemsPerPage = 100;
15 // Reserve 2 API calls for fetching both user and target profiles
16 const maxFollowersToCheck = (maxAPICalls - 2) * itemsPerPage;
17
18 try {
19 // Fetch both profiles to determine which list is shorter
20 const [userProfile, targetProfile] = await Promise.all([
21 bsky.getProfile({ actor: userDID }),
22 bsky.getProfile({ actor: targetDID }),
23 ]);
24
25 const userFollowingCount = userProfile.data.followsCount || 0;
26 const targetFollowersCount = targetProfile.data.followersCount || 0;
27
28 // If both counts exceed our limit, we can't reliably check
29 if (
30 userFollowingCount > maxFollowersToCheck &&
31 targetFollowersCount > maxFollowersToCheck
32 ) {
33 return null;
34 }
35
36 // Determine which list to check (choose the shorter one)
37 const checkUserFollowing = userFollowingCount <= targetFollowersCount;
38
39 let cursor: string | undefined = undefined;
40 let itemsChecked = 0;
41
42 while (itemsChecked < maxFollowersToCheck) {
43 if (checkUserFollowing) {
44 // Check who userDID follows
45 const { data: followsData } = await bsky.getFollows({
46 actor: userDID,
47 limit: itemsPerPage,
48 cursor: cursor,
49 });
50
51 // Check if targetDID is in the follows list
52 for (const followed of followsData.follows) {
53 if (followed.did === targetDID) {
54 return true;
55 }
56 }
57
58 itemsChecked += followsData.follows.length;
59
60 if (!followsData.cursor) {
61 break;
62 }
63
64 cursor = followsData.cursor;
65 } else {
66 // Check who follows targetDID
67 const { data: followersData } = await bsky.getFollowers({
68 actor: targetDID,
69 limit: itemsPerPage,
70 cursor: cursor,
71 });
72
73 // Check if userDID is in the followers list
74 for (const follower of followersData.followers) {
75 if (follower.did === userDID) {
76 return true;
77 }
78 }
79
80 itemsChecked += followersData.followers.length;
81
82 if (!followersData.cursor) {
83 break;
84 }
85
86 cursor = followersData.cursor;
87 }
88 }
89
90 // If we get here, we didn't find the relationship
91 return false;
92 } catch (_error) {
93 // Return null on any error - allows calling code to continue without this info
94 return null;
95 }
96};