mirror of
https://github.com/anotherhadi/github-recon.git
synced 2026-10-05 10:58:25 +02:00
Add more score when two users are in the same orgs #7
Signed-off-by: Hadi <[email protected]>
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
package recon
|
package recon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
github_recon_settings "github.com/anotherhadi/github-recon/settings"
|
github_recon_settings "github.com/anotherhadi/github-recon/settings"
|
||||||
"github.com/anotherhadi/github-recon/utils"
|
"github.com/anotherhadi/github-recon/utils"
|
||||||
|
"github.com/google/go-github/v72/github"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CloseFriendsResult []CloseFriendResult
|
type CloseFriendsResult []CloseFriendResult
|
||||||
@@ -16,51 +18,56 @@ type CloseFriendResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxFollowingForTarget = 50
|
maxTargetFollowing = 50
|
||||||
maxFollowersForFollowing = 20
|
maxFollowersForCandidate = 20
|
||||||
pointPerCriterion = 1
|
pointsPerCondition = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// CloseFriends returns a list of close friends of the user
|
// CloseFriends returns a list of "close friends" for the target user.
|
||||||
// To derive this, we check the following:
|
// A candidate is considered closer if:
|
||||||
// 1. The target has less than 50 Following
|
// 1. The target follows fewer than 50 people.
|
||||||
// 2. The target's following has less than 20 followers (+1 point)
|
// 2. The candidate has fewer than 20 followers (+1 point).
|
||||||
// 3. The target's following follows the target (+1 point)
|
// 3. The candidate follows the target back (+1 point).
|
||||||
|
// 4. The candidate shares at least one organization with the target (+1 point).
|
||||||
func CloseFriends(s github_recon_settings.Settings) (response CloseFriendsResult) {
|
func CloseFriends(s github_recon_settings.Settings) (results CloseFriendsResult) {
|
||||||
following, resp, err := s.Client.Users.ListFollowing(s.Ctx, s.Target, nil)
|
targetFollowing, resp, err := s.Client.Users.ListFollowing(s.Ctx, s.Target, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Logger.Error("Failed to fetch user's following list", "err", err)
|
s.Logger.Error("Failed to fetch target's following list", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.WaitForRateLimit(s, resp)
|
utils.WaitForRateLimit(s, resp)
|
||||||
|
|
||||||
if len(following) >= maxFollowingForTarget {
|
targetOrgs, err := getOrgs(s, s.Target)
|
||||||
s.Logger.Info("Skipping close friends check", "reason", fmt.Sprintf("Target follows %d or more users (%d)", maxFollowingForTarget, len(following)))
|
if err != nil {
|
||||||
|
s.Logger.Error("Failed to fetch target's organizations", "err", err)
|
||||||
|
targetOrgs = []*github.Organization{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(targetFollowing) >= maxTargetFollowing {
|
||||||
|
s.Logger.Info("Skipping close friends check",
|
||||||
|
"reason",
|
||||||
|
fmt.Sprintf("Target follows %d or more users (limit: %d)", len(targetFollowing), maxTargetFollowing),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(following) == 0 {
|
if len(targetFollowing) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, userBeingFollowedByTarget := range following {
|
for _, candidate := range targetFollowing {
|
||||||
loginName := userBeingFollowedByTarget.GetLogin()
|
candidateLogin := candidate.GetLogin()
|
||||||
if loginName == "" {
|
if candidateLogin == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
currentScore := 0
|
score := 0
|
||||||
|
|
||||||
userDetails, userResp, userErr := s.Client.Users.Get(s.Ctx, loginName)
|
candidateDetails, userResp, err := s.Client.Users.Get(s.Ctx, candidateLogin)
|
||||||
if userErr != nil {
|
if err != nil {
|
||||||
s.Logger.Warn(
|
s.Logger.Warn("Failed to fetch details for candidate",
|
||||||
"Failed to fetch details for followed user",
|
"candidate", candidateLogin,
|
||||||
"followed_user",
|
"err", err,
|
||||||
loginName,
|
|
||||||
"err",
|
|
||||||
userErr,
|
|
||||||
)
|
)
|
||||||
if userResp != nil {
|
if userResp != nil {
|
||||||
utils.WaitForRateLimit(s, userResp)
|
utils.WaitForRateLimit(s, userResp)
|
||||||
@@ -69,52 +76,77 @@ func CloseFriends(s github_recon_settings.Settings) (response CloseFriendsResult
|
|||||||
}
|
}
|
||||||
utils.WaitForRateLimit(s, userResp)
|
utils.WaitForRateLimit(s, userResp)
|
||||||
|
|
||||||
if userDetails.GetFollowers() < maxFollowersForFollowing {
|
// Condition: candidate has few followers
|
||||||
currentScore += pointPerCriterion
|
if candidateDetails.GetFollowers() < maxFollowersForCandidate {
|
||||||
|
score += pointsPerCondition
|
||||||
}
|
}
|
||||||
|
|
||||||
followsTargetBack, checkErr := checkIfUserFollows(s, loginName, s.Target)
|
// Condition: candidate follows target back
|
||||||
if checkErr != nil {
|
followsBack, err := checkIfUserFollows(s, candidateLogin, s.Target)
|
||||||
continue
|
if err == nil && followsBack {
|
||||||
} else if followsTargetBack {
|
score += pointsPerCondition
|
||||||
currentScore += pointPerCriterion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentScore > 0 {
|
// Condition: same organization
|
||||||
response = append(response, CloseFriendResult{
|
candidateOrgs, _ := getOrgs(s, candidateLogin)
|
||||||
Username: loginName,
|
if isInSameOrg(targetOrgs, candidateOrgs) {
|
||||||
Score: currentScore,
|
score += pointsPerCondition
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add candidate if they matched at least one condition
|
||||||
|
if score > 0 {
|
||||||
|
results = append(results, CloseFriendResult{
|
||||||
|
Username: candidateLogin,
|
||||||
|
Score: score,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(response) == 0 {
|
if len(results) > 0 {
|
||||||
return
|
sort.Slice(results, func(i, j int) bool {
|
||||||
} else {
|
return results[i].Score > results[j].Score
|
||||||
sort.Slice(response, func(i, j int) bool {
|
|
||||||
return response[i].Score > response[j].Score
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkIfUserFollows checks if sourceUserLogin follows targetUserLogin.
|
// checkIfUserFollows checks if sourceUser follows targetUser.
|
||||||
func checkIfUserFollows(s github_recon_settings.Settings, sourceUserLogin, targetUserLogin string) (bool, error) {
|
func checkIfUserFollows(s github_recon_settings.Settings, sourceUser, targetUser string) (bool, error) {
|
||||||
isFollowing, resp, err := s.Client.Users.IsFollowing(s.Ctx, sourceUserLogin, targetUserLogin)
|
isFollowing, resp, err := s.Client.Users.IsFollowing(s.Ctx, sourceUser, targetUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Logger.Warn("Error checking if user follows target",
|
s.Logger.Warn("Error checking if user follows target",
|
||||||
"source_user_checking", sourceUserLogin,
|
"source", sourceUser,
|
||||||
"target_user_to_check", targetUserLogin,
|
"target", targetUser,
|
||||||
"err", err)
|
"err", err,
|
||||||
|
)
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
utils.WaitForRateLimit(s, resp)
|
utils.WaitForRateLimit(s, resp)
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
utils.WaitForRateLimit(s, resp)
|
utils.WaitForRateLimit(s, resp)
|
||||||
}
|
}
|
||||||
return isFollowing, nil
|
return isFollowing, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOrgs(s github_recon_settings.Settings, user string) ([]*github.Organization, error) {
|
||||||
|
orgs, resp, err := s.Client.Organizations.List(s.Ctx, user, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to fetch organizations for user")
|
||||||
|
}
|
||||||
|
utils.WaitForRateLimit(s, resp)
|
||||||
|
return orgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInSameOrg(orgsA, orgsB []*github.Organization) bool {
|
||||||
|
for _, orgA := range orgsA {
|
||||||
|
for _, orgB := range orgsB {
|
||||||
|
if orgA.GetLogin() == orgB.GetLogin() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user