Files
github-recon/gh-recon/close-friends.go
T
Hadi 637d9811f2 change struct
Signed-off-by: Hadi <[email protected]>
2025-05-27 20:01:05 +02:00

139 lines
3.2 KiB
Go

package ghrecon
import (
"fmt"
"sort"
)
type CloseFriendsResult struct {
Login string
Score int
}
const (
maxFollowingForTarget = 50
maxFollowersForFollowing = 20
pointPerCriterion = 1
)
// CloseFriends returns a list of close friends of the user
// To derive this, we check the following:
// 1. The target has less than 50 Following
// 2. The target's following has less than 20 followers (+1 point)
// 3. The target's following follows the target (+1 point)
func (r Recon) CloseFriends(username string) (response []CloseFriendsResult) {
r.PrintTitle("🧑‍🤝‍🧑 Close Friends")
following, resp, err := r.Client.Users.ListFollowing(r.Ctx, username, nil)
if err != nil {
r.Logger.Error("Failed to fetch user's following list", "user", username, "err", err)
r.PrintNewline()
return
}
WaitForRateLimit(resp)
if len(following) >= maxFollowingForTarget {
r.PrintInfo(
"INFO",
fmt.Sprintf(
"%s follows %d or more users (%d). Skipping close friends check.",
username,
maxFollowingForTarget,
len(following),
),
)
r.PrintNewline()
return
}
if len(following) == 0 {
r.PrintInfo("INFO", fmt.Sprintf("%s is not following anyone.", username))
r.PrintNewline()
return
}
for _, userBeingFollowedByTarget := range following {
loginName := userBeingFollowedByTarget.GetLogin()
if loginName == "" {
r.Logger.Warn("User in following list has an empty login", "target_user", username)
continue
}
currentScore := 0
userDetails, userResp, userErr := r.Client.Users.Get(r.Ctx, loginName)
if userErr != nil {
r.Logger.Warn(
"Failed to fetch details for followed user",
"followed_user",
loginName,
"err",
userErr,
)
if userResp != nil {
WaitForRateLimit(userResp)
}
continue
}
WaitForRateLimit(userResp)
if userDetails.GetFollowers() < maxFollowersForFollowing {
currentScore += pointPerCriterion
}
followsTargetBack, checkErr := r.checkIfUserFollows(loginName, username)
if checkErr != nil {
} else if followsTargetBack {
currentScore += pointPerCriterion
}
if currentScore > 0 {
response = append(response, CloseFriendsResult{
Login: loginName,
Score: currentScore,
})
}
}
if len(response) == 0 {
r.PrintInfo(
"INFO",
fmt.Sprintf("No close friends found for %s based on the criteria.", username),
)
} else {
sort.Slice(response, func(i, j int) bool {
return response[i].Score > response[j].Score
})
for i, friend := range response {
r.PrintInfo(
fmt.Sprintf("Friend n°%d", i+1),
"@"+friend.Login,
"Score: "+fmt.Sprintf("%d", friend.Score),
)
}
}
r.PrintNewline()
return
}
// checkIfUserFollows checks if sourceUserLogin follows targetUserLogin.
func (r Recon) checkIfUserFollows(sourceUserLogin, targetUserLogin string) (bool, error) {
isFollowing, resp, err := r.Client.Users.IsFollowing(r.Ctx, sourceUserLogin, targetUserLogin)
if err != nil {
r.Logger.Warn("Error checking if user follows target",
"source_user_checking", sourceUserLogin,
"target_user_to_check", targetUserLogin,
"err", err)
if resp != nil {
WaitForRateLimit(resp)
}
return false, err
}
if resp != nil {
WaitForRateLimit(resp)
}
return isFollowing, nil
}