Created
December 17, 2021 03:09
-
-
Save lauslim12/4a4f604be59d8b67cd58a5de7ca7d3a6 to your computer and use it in GitHub Desktop.
Find people who do not follow you back on GitHub.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Package main is a CLI runner to run this script ('github-unfollowers.go'). | |
// This script is used to get a user's 'unfollowers' - which means people who do not follow that user's back. | |
// You do not need GitHub Tokens, as all the endpoints are public. Use this with responsibility though, do not spam. | |
// | |
// How to run: copy this script to a workspace, place your username, then run 'go run github-unfollowers.go' | |
// You can place your username by using 'export GITHUB_USERNAME=YOUR_GITHUB_USERNAME' or by editing the source code and filling it manually. | |
// | |
// Author & License: Nicholas (MIT) | |
package main | |
import ( | |
"encoding/json" | |
"errors" | |
"fmt" | |
"log" | |
"math" | |
"net/http" | |
"os" | |
"strings" | |
) | |
// Social is a struct representative of 'Followers' and 'Following' entity from the API. | |
type Social struct { | |
Login string `json:"login"` | |
ID uint64 `json:"id"` | |
NodeID string `json:"node_id"` | |
AvatarURL string `json:"avatar_url"` | |
GravatarID string `json:"gravatar_id"` | |
URL string `json:"url"` | |
HTMLURL string `json:"html_url"` | |
FollowersURL string `json:"followers_url"` | |
FollowingURL string `json:"following_url"` | |
GistsURL string `json:"gists_url"` | |
StarredURL string `json:"starred_url"` | |
SubscriptionsURL string `json:"subscriptions_url"` | |
OrganizationsURL string `json:"organizations_url"` | |
ReposURL string `json:"repos_url"` | |
EventsURL string `json:"events_url"` | |
ReceivedEventsURL string `json:"received_events_url"` | |
Type string `json:"type"` | |
SiteAdmin bool `json:"site_admin"` | |
} | |
// User is a struct representative of 'User' data from GitHub's API. | |
type User struct { | |
Login string `json:"login"` | |
ID uint64 `json:"id"` | |
NodeID string `json:"node_id"` | |
AvatarURL string `json:"avatar_url"` | |
GravatarID string `json:"gravatar_id"` | |
URL string `json:"url"` | |
HTMLURL string `json:"html_url"` | |
FollowersURL string `json:"followers_url"` | |
FollowingURL string `json:"following_url"` | |
GistsURL string `json:"gists_url"` | |
StarredURL string `json:"starred_url"` | |
SubscriptionsURL string `json:"subscriptions_url"` | |
OrganizationsURL string `json:"organizations_url"` | |
ReposURL string `json:"repos_url"` | |
EventsURL string `json:"events_url"` | |
ReceivedEventsURL string `json:"received_events_url"` | |
Type string `json:"type"` | |
SiteAdmin bool `json:"site_admin"` | |
Name string `json:"name"` | |
Company string `json:"company"` | |
Blog string `json:"blog"` | |
Location string `json:"location"` | |
Bio string `json:"bio"` | |
TwitterUsername string `json:"twitter_username"` | |
PublicRepos uint64 `json:"public_repos"` | |
PublicGists uint64 `json:"public_gists"` | |
Followers uint64 `json:"followers"` | |
Following uint64 `json:"following"` | |
CreatedAt string `json:"created_at"` | |
UpdatedAt string `json:"updated_at"` | |
} | |
// createRequest creates a single request to fetch from GitHub API using GitHub's unique | |
// 'Accept' header (application/vnd.github.v3+json). | |
func createRequest(url string) (*http.Response, error) { | |
client := &http.Client{} | |
req, err := http.NewRequest("GET", url, nil) | |
req.Header.Set("Accept", "application/vnd.github.v3+json") | |
if err != nil { | |
return nil, err | |
} | |
res, err := client.Do(req) | |
if err != nil { | |
return nil, err | |
} | |
return res, nil | |
} | |
// findUnfollowers finds all users who does not follow you back. | |
// This is done by performing a search on the existing sets. | |
func findUnfollowers(followersSet map[string]bool, followingSet map[string]bool) map[string]bool { | |
unfollowers := map[string]bool{} | |
for key := range followingSet { | |
if ok := followersSet[key]; !ok { | |
unfollowers[key] = true | |
} | |
} | |
return unfollowers | |
} | |
// getSocial fetches either 'followers' or 'following' data from GitHub API. | |
// Variable 'numberOfRequests' is divided by 100 as 100 is the maximum number of data | |
// in a single page. | |
func getSocial(username string, total uint64, subject string) ([]Social, error) { | |
social := []Social{} | |
if subject != "followers" && subject != "following" { | |
return nil, errors.New("social: subject has to be either 'followers' or 'following'") | |
} | |
numberOfRequests := math.Ceil(float64(total) / 100) | |
for i := 1; i <= int(numberOfRequests); i++ { | |
currentSocial := []Social{} | |
url := fmt.Sprintf("https://api.github.com/users/%s/%s?per_page=100&page=%d", username, subject, i) | |
res, err := createRequest(url) | |
if err != nil { | |
return nil, err | |
} | |
defer res.Body.Close() | |
err = json.NewDecoder(res.Body).Decode(¤tSocial) | |
if err != nil { | |
return nil, err | |
} | |
social = append(social, currentSocial...) | |
} | |
return social, nil | |
} | |
// getUser fetches data of a single user from GitHub API. | |
func getUser(username string) (*User, error) { | |
user := &User{} | |
url := fmt.Sprintf("https://api.github.com/users/%s", username) | |
res, err := createRequest(url) | |
if err != nil { | |
return nil, err | |
} | |
defer res.Body.Close() | |
if res.StatusCode == 404 { | |
return nil, errors.New("getUser: failed to find a GitHub user with that username") | |
} | |
err = json.NewDecoder(res.Body).Decode(user) | |
if err != nil { | |
return nil, err | |
} | |
return user, nil | |
} | |
// printSet prints all of a set's contents with a unique message. | |
func printSet(message string, set map[string]bool) { | |
counter := 1 | |
fmt.Println(message) | |
for key := range set { | |
fmt.Printf("%d. %s\n", counter, key) | |
counter++ | |
} | |
fmt.Printf("\n\n") | |
} | |
// socialToSet transforms a social array into a set data structure. | |
func socialToSet(social []Social) map[string]bool { | |
set := map[string]bool{} | |
for _, user := range social { | |
set[user.Login] = true | |
} | |
return set | |
} | |
// Driver code that runs the whole program. If you want to fill your username, | |
// you can use 'export GITHUB_USERNAME=YOUR_GITHUB_USERNAME' before running this script, | |
// or by editing the 'username' variable manually. | |
func main() { | |
username := strings.TrimSpace(os.Getenv("GITHUB_USERNAME")) | |
if username == "" { | |
log.Fatal(errors.New("main: variable 'username' is blank, it has to defined beforehand")) | |
} | |
user, err := getUser(username) | |
if err != nil { | |
log.Fatal(err.Error()) | |
} | |
followers, err := getSocial(username, user.Followers, "followers") | |
if err != nil { | |
log.Fatal(err.Error()) | |
} | |
following, err := getSocial(username, user.Following, "following") | |
if err != nil { | |
log.Fatal(err.Error()) | |
} | |
followersSet := socialToSet(followers) | |
printSet("Followers:", followersSet) | |
followingSet := socialToSet(following) | |
printSet("Following:", followingSet) | |
unfollowers := findUnfollowers(followersSet, followingSet) | |
printSet("Unfollowers:", unfollowers) | |
fmt.Println("GitHub Unfollowers has finished running.") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment