-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlinkedin_github_follow_bot.py
More file actions
140 lines (129 loc) · 5.19 KB
/
linkedin_github_follow_bot.py
File metadata and controls
140 lines (129 loc) · 5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import os, time, csv, sys, requests
from pathlib import Path
from dotenv import load_dotenv
# Load .env from script directory so it works when run from anywhere
load_dotenv(Path(__file__).resolve().parent / ".env")
load_dotenv() # Also allow current directory / system env
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
LINKEDIN_CSV = os.getenv("LINKEDIN_CSV", str(Path(__file__).resolve().parent / "Connections.csv"))
DRY_RUN = os.getenv("DRY_RUN", "false").lower() == "true"
AUTO_CONFIRM = os.getenv("AUTO_CONFIRM", "false").lower() == "true"
SEARCH_DELAY_SEC = 1.2
FOLLOW_DELAY_SEC = 0.8
HEADERS = {
"Authorization": f"Bearer {GITHUB_TOKEN}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
}
def github_search_user(name, company=""):
query = f"fullname:{name}"
r = requests.get("https://api.github.com/search/users", headers=HEADERS, params={"q": query, "per_page": 5})
if r.status_code == 403:
print("Rate limit hit, sleeping 60s...")
time.sleep(60)
r = requests.get("https://api.github.com/search/users", headers=HEADERS, params={"q": query, "per_page": 5})
if r.status_code != 200:
return []
return r.json().get("items", [])
def github_follow(username):
r = requests.put(f"https://api.github.com/user/following/{username}", headers=HEADERS)
return r.status_code == 204
def get_user_details(username):
r = requests.get(f"https://api.github.com/users/{username}", headers=HEADERS)
return r.json() if r.status_code == 200 else {}
def already_following(username):
r = requests.get(f"https://api.github.com/user/following/{username}", headers=HEADERS)
return r.status_code == 204
def load_linkedin_connections(csv_path):
path = Path(csv_path)
if not path.exists():
print(f"File not found: {csv_path}")
sys.exit(1)
with open(path, encoding="utf-8-sig") as f:
raw = f.read()
lines = raw.splitlines()
header_idx = next((i for i, l in enumerate(lines) if l.strip().startswith("First Name")), 0)
clean_csv = "\n".join(lines[header_idx:])
reader = csv.DictReader(clean_csv.splitlines())
connections = []
for row in reader:
first = row.get("First Name", "").strip()
last = row.get("Last Name", "").strip()
if first or last:
connections.append({"name": f"{first} {last}".strip(), "company": row.get("Company", "").strip()})
return connections
def run():
if not GITHUB_TOKEN:
print("GITHUB_TOKEN not set")
sys.exit(1)
print("\n" + "="*60)
print(" LinkedIn → GitHub Follow Bot")
print("="*60)
if DRY_RUN:
print(" DRY RUN mode — no actual follows")
print()
connections = load_linkedin_connections(LINKEDIN_CSV)
print(f"Loaded {len(connections)} LinkedIn connections\n")
followed = []; skipped = []; not_found = []; already_fol = []
auto = AUTO_CONFIRM
for i, conn in enumerate(connections, 1):
name = conn["name"]
company = conn["company"]
print(f"[{i}/{len(connections)}] Searching: {name}" + (f" @ {company}" if company else ""))
results = github_search_user(name, company)
time.sleep(SEARCH_DELAY_SEC)
if not results:
print(" → Not found\n")
not_found.append(name)
continue
best = results[0]
login = best["login"]
details = get_user_details(login)
gh_name = details.get("name") or login
gh_bio = details.get("bio") or ""
gh_co = details.get("company") or ""
print(f" → Best match: @{login} | {gh_name}")
if gh_bio: print(f" Bio: {gh_bio[:80]}")
if gh_co: print(f" Company: {gh_co}")
print(f" {details.get('html_url','')}")
if already_following(login):
print(" → Already following, skip\n")
already_fol.append(name)
continue
do_follow = auto
if not do_follow:
try:
ans = input(" Follow? [y/n/s=skip rest]: ").strip().lower()
if ans == "s":
auto = False
do_follow = False
skipped.append(name)
print(" Skipping rest (no more prompts).\n")
continue
do_follow = ans in ("y", "yes")
except (EOFError, KeyboardInterrupt):
print("\nAborted.")
sys.exit(0)
if do_follow and not DRY_RUN:
if github_follow(login):
print(" → Followed.\n")
followed.append((name, login))
else:
print(" → Follow failed.\n")
skipped.append(name)
time.sleep(FOLLOW_DELAY_SEC)
elif do_follow and DRY_RUN:
print(" → [DRY RUN] Would follow.\n")
followed.append((name, login))
else:
skipped.append(name)
print(" Skipped.\n")
print("="*60)
print("Summary:")
print(f" Followed: {len(followed)}")
print(f" Already following: {len(already_fol)}")
print(f" Skipped: {len(skipped)}")
print(f" Not found: {len(not_found)}")
print("="*60)
if __name__ == "__main__":
run()