Have you ever wondered which GitHub developers you're following don't reciprocate the connection? Unlike social media platforms that clearly show mutual connections, GitHub doesn't provide an easy way to see who follows you back. This article explains a fun Ruby script that solves this problem by revealing your one-sided GitHub relationships.

The Social Imbalance on GitHub

GitHub, while primarily a code-hosting platform, also functions as a social network for developers. You can follow other developers to stay updated on their activities, contributions, and projects. However, GitHub's interface doesn't explicitly show whether someone you follow also follows you back.

This asymmetry creates an interesting dynamic - you might be following hundreds of developers, but how many of them are following you in return? The script we're discussing today bridges this information gap.

The Script

#!/usr/bin/env ruby

require 'net/http'
require 'json'
require 'optparse'

# Class that handles checking GitHub follower relationships
class GitHubFollowerChecker
  BASE_URL = "https://api.github.com"

  # Initialize with username and optional authentication token
  def initialize(username, token = nil)
    @username = username
    @token = token
    @headers = { 'Accept' => 'application/vnd.github.v3+json' }
    @headers['Authorization'] = "token #{token}" if token
  end

  # Get all users the specified user is following
  def get_following
    fetch_all_pages("#{BASE_URL}/users/#{@username}/following")
  end

  # Get all followers of the specified user
  def get_followers
    fetch_all_pages("#{BASE_URL}/users/#{@username}/followers")
  end

  # Find users you follow who don't follow you back
  def find_not_following_back
    puts "Fetching users you follow..."
    following = get_following

    puts "Fetching your followers..."
    followers = get_followers

    following_usernames = following.map { |user| user['login'] }
    follower_usernames = followers.map { |user| user['login'] }

    puts "Analyzing relationships..."
    following_usernames - follower_usernames
  end

  private

  # Helper method to fetch all pages of results from the GitHub API
  def fetch_all_pages(url, results = [])
    uri = URI(url)

    begin
      response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
        request = Net::HTTP::Get.new(uri)
        @headers.each { |key, value| request[key] = value }
        http.request(request)
      end

      case response.code
      when '200'
        page_results = JSON.parse(response.body)
        results.concat(page_results)

        # Handle pagination using the Link header
        if response['link']&.include?('rel="next"')
          next_link = response['link'].split(',').find { |link| link.include?('rel="next"') }
          if next_link
            next_url = next_link.match(/<(.+?)>/)[1]
            print "." # Show progress
            fetch_all_pages(next_url, results)
          end
        end
      when '401'
        puts "\nError: Authentication failed. Check your token."
        exit 1
      when '403'
        puts "\nError: Rate limit exceeded. Use a token or wait before trying again."
        exit 1
      when '404'
        puts "\nError: User '#{@username}' not found."
        exit 1
      else
        puts "\nError: #{response.code} - #{response.message}"
        puts JSON.parse(response.body)['message'] if response.body
        exit 1
      end
    rescue SocketError
      puts "\nError: Could not connect to GitHub. Check your internet connection."
      exit 1
    rescue JSON::ParserError
      puts "\nError: Invalid response from GitHub."
      exit 1
    end

    results
  end
end

# Main script execution
def main
  # Parse command line options
  options = {}
  OptionParser.new do |opts|
    opts.banner = "Usage: github_follower_checker.rb -u USERNAME [-t TOKEN]"

    opts.on("-u", "--username USERNAME", "GitHub username") do |username|
      options[:username] = username
    end

    opts.on("-t", "--token TOKEN", "GitHub personal access token (recommended to avoid rate limits)") do |token|
      options[:token] = token
    end

    opts.on("-h", "--help", "Show this help message") do
      puts opts
      exit
    end
  end.parse!

  # Validate required parameters
  if options[:username].nil?
    puts "Error: GitHub username is required"
    puts "Usage: github_follower_checker.rb -u USERNAME [-t TOKEN]"
    exit 1
  end

  # Execute the check
  checker = GitHubFollowerChecker.new(options[:username], options[:token])
  not_following_back = checker.find_not_following_back

  # Display results
  puts "\nGitHub users you follow who don't follow you back (#{not_following_back.count}):"
  if not_following_back.empty?
    puts "Everyone you follow also follows you back! 🎉"
  else
    not_following_back.each_with_index do |username, index|
      puts "#{index + 1}. #{username}"
    end
  end
end

# Run the script
main if __FILE__ == $PROGRAM_NAME

Understanding the Ruby Script

The script is a command-line tool written in Ruby that identifies GitHub users you follow who aren't following you back. Let's break down how it works and the technology behind it:

Core Functionality

The script performs four main tasks:

  1. Fetches the list of users you're following
  2. Fetches your followers
  3. Compares these two lists to identify non-reciprocal connections
  4. Presents the results in a clear, easy-to-read format

Technical Implementation

The code is organized around a GitHubFollowerChecker class that handles the GitHub API interactions:

class GitHubFollowerChecker
  BASE_URL = "https://api.github.com"

  def initialize(username, token = nil)
    @username = username
    @token = token
    @headers = { 'Accept' => 'application/vnd.github.v3+json' }
    @headers['Authorization'] = "token #{token}" if token
  end

  # Other methods...
end

Fetching Data from GitHub

The script uses Ruby's Net::HTTP library to make API calls to GitHub's endpoints:

def get_following
  fetch_all_pages("#{BASE_URL}/users/#{@username}/following")
end

def get_followers
  fetch_all_pages("#{BASE_URL}/users/#{@username}/followers")
end

Handling Pagination

GitHub's API returns paginated results when dealing with large data sets. The script handles this elegantly by recursively fetching all pages:

if response['link']&.include?('rel="next"')
  next_link = response['link'].split(',').find { |link| link.include?('rel="next"') }
  if next_link
    next_url = next_link.match(/<(.+?)>/)[1]
    print "." # Show progress
    fetch_all_pages(next_url, results)
  end
end

Finding Non-Reciprocal Connections

The core analysis is remarkably simple but effective:

def find_not_following_back
  puts "Fetching users you follow..."
  following = get_following

  puts "Fetching your followers..."
  followers = get_followers

  following_usernames = following.map { |user| user['login'] }
  follower_usernames = followers.map { |user| user['login'] }

  puts "Analyzing relationships..."
  following_usernames - follower_usernames
end

The magic happens in the last line, where Ruby's array subtraction operator (-) does all the heavy lifting, returning elements in the first array that aren't in the second.

Error Handling

The script includes robust error handling for various scenarios:

case response.code
when '401'
  puts "\nError: Authentication failed. Check your token."
  exit 1
when '403'
  puts "\nError: Rate limit exceeded. Use a token or wait before trying again."
  exit 1
# Other cases...
end

Why This Script Is Useful

While created for fun, this script offers several practical benefits:

  1. Network Analysis: Understand the reciprocity in your professional network
  2. Community Engagement: Identify potential opportunities to build stronger connections
  3. Account Cleanup: Find inactive or unengaged connections you might want to reconsider
  4. GitHub API Learning: Serves as an excellent example of working with GitHub's API

Running the Script

Using the script is straightforward:

  1. Save the code as github_follower_checker.rb
  2. Make it executable: chmod +x github_follower_checker.rb
  3. Run with your username: ./github_follower_checker.rb -u YOUR_USERNAME

For better performance and to avoid rate limits:

./github_follower_checker.rb -u YOUR_USERNAME -t YOUR_PERSONAL_ACCESS_TOKEN

The output will show you a list of users you follow who don't follow you back:

GitHub users you follow who don't follow you back (42):
1. octocat
2. defunkt
3. torvalds
...

Enhancing Your GitHub Experience

This script demonstrates how a simple tool can unveil insights about your GitHub connections that aren't readily available through the platform's interface. It's a perfect example of using programming to enhance your experience on a platform you use regularly.

While GitHub may not emphasize the reciprocity of connections as much as traditional social networks do, understanding these relationships can help you build a more engaged and meaningful developer network.

Conclusion

The GitHub follower analysis script is both a fun project and a useful tool for developers active on the platform. It showcases how a few lines of Ruby code can provide valuable insights into your GitHub social graph, helping you make more informed decisions about who you connect with in the developer community.

Whether you're looking to clean up your GitHub connections or just curious about the reciprocity of your network, this script offers a simple solution to a question many GitHub users have pondered.

Remember that GitHub is primarily about code, collaboration, and learning - but the social aspect adds another dimension to your professional development journey. Happy coding, and happy networking!