Created
December 14, 2023 03:13
-
-
Save tatsumoto-ren/37de04595f4454ad5665bf1d7c3840de to your computer and use it in GitHub Desktop.
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
#!/usr/bin/python3 | |
# This program takes your GitHub username and auth token and prints two lists of usernames: | |
# ・you should unfollow = people who aren't following you. | |
# ・you should follow = people who are following you. | |
# If you choose to proceed, the program follows or unfollows the users on your behalf. | |
import argparse | |
import json | |
from enum import Enum, auto | |
from pprint import pprint | |
from typing import Collection, Any, NamedTuple | |
import requests | |
RED = '\033[0;31m' | |
GREEN = '\033[0;32m' | |
NC = '\033[0m' | |
class Auth(NamedTuple): | |
user: str | |
access_token: str | |
class Action(Enum): | |
unfollow = auto() | |
follow = auto() | |
def __str__(self): | |
return f"{RED if self.name == 'unfollow' else GREEN}{self.name}{NC}" | |
def format_num(idx: int, length: int) -> str: | |
return f'{" " * (len(str(length)) - len(str(idx)))}{idx}' | |
def parse_args(): | |
parser = argparse.ArgumentParser(description="Synchronize followers and following on GitHub.") | |
parser.add_argument('-u', '--user', type=str, required=True) | |
parser.add_argument('-t', '--token', type=str, required=True) | |
parser.add_argument( | |
'-n', '--noconfirm', required=False, default=False, action='store_true', | |
help="Don't ask for confirmation.", | |
) | |
return parser.parse_args() | |
class GitHubClient: | |
def __init__(self): | |
args = parse_args() | |
self.auth = Auth(args.user, args.token) | |
self.noconfirm = args.noconfirm | |
with requests.session() as self.session: | |
self.session.auth = self.auth | |
self.session.headers.update({'Accept': 'application/vnd.github.v3+json'}) | |
self.run() | |
def requests_users(self, tab: str) -> set[str]: | |
def get_page() -> list[dict[str, Any]]: | |
return self.session.get( | |
f'https://api.github.com/users/{self.auth.user}/{tab}', | |
params={'per_page': '100', 'page': page_n}, | |
).json() | |
page_n = 1 | |
total_users = [] | |
while users := get_page(): | |
if type(users) != list: | |
break | |
total_users.extend(users) | |
page_n += 1 | |
return {user['login'] for user in total_users} | |
def unfollow(self, to_unfollow: set[str]): | |
for user in to_unfollow: | |
resp = self.session.delete(f'https://api.github.com/user/following/{user}') | |
print(f"Unfollow {user}: {'Ok' if resp.status_code == 204 else 'Failed'}") | |
def follow(self, to_follow: set[str]): | |
for user in to_follow: | |
resp = self.session.put( | |
f'https://api.github.com/user/following/{user}', | |
headers={"Content-Length": "0"}, | |
) | |
print(f"Follow {user}: {'Ok' if resp.status_code == 204 else 'Failed'}") | |
def print_users(self, users: Collection[str], action: Action) -> None: | |
""" | |
Print the program's result. | |
:param users: an iterable of usernames | |
:param action: either follow or unfollow | |
:return: None | |
""" | |
if users: | |
print(f"{self.auth.user} should {action} {len(users)} users:") | |
for i, name in enumerate(users): | |
print(format_num(i + 1, len(users)), name) | |
else: | |
print(f"{self.auth.user} doesn't have any people to {action} at the moment.") | |
def run(self): | |
followers = self.requests_users('followers') | |
following = self.requests_users('following') | |
to_unfollow = following - followers | |
to_follow = followers - following | |
print(f"Following: {len(following)}, Followers: {len(followers)}") | |
self.print_users(to_unfollow, Action.unfollow) | |
self.print_users(to_follow, Action.follow) | |
if (to_unfollow or to_follow) and (self.noconfirm or input("Proceed? [y/N] ").lower() == 'y'): | |
self.unfollow(to_unfollow) | |
self.follow(to_follow) | |
if __name__ == '__main__': | |
GitHubClient() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment