+96
utils/doesUserFollow.ts
+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
+
};