From b4392f972ddfaa8947dc0fbd1f76a166cc6f443b Mon Sep 17 00:00:00 2001 From: Hadi <112569860+anotherhadi@users.noreply.github.com> Date: Wed, 21 May 2025 10:12:40 +0200 Subject: [PATCH] edit close friends algo Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com> --- gh-recon/close-friends.go | 125 +++++++++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 16 deletions(-) diff --git a/gh-recon/close-friends.go b/gh-recon/close-friends.go index 0981685..8cafadd 100644 --- a/gh-recon/close-friends.go +++ b/gh-recon/close-friends.go @@ -1,45 +1,138 @@ 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 - +// 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.Fatal("Failed to fetch user's close friends", "err", err) - } - if len(following) > 50 { - r.PrintInfo("INFO", "No commits found") + r.logger.Error("Failed to fetch user's following list", "user", username, "err", err) r.PrintNewline() - return []CloseFriendsResult{} + return } WaitForRateLimit(resp) - for _, user := range following { - followers, resp, err := r.client.Users.Get(r.ctx, user.GetLogin()) - WaitForRateLimit(resp) - if err != nil { + + 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 } - if followers.GetFollowers() < 20 { + + 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: user.GetLogin(), - Score: 1, + Login: loginName, + Score: currentScore, }) } } - for _, friend := range response { - r.PrintInfo("Username", "@"+friend.Login) + 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 +}