mirror of
https://github.com/epasveer/seer.git
synced 2026-08-29 08:34:42 +08:00
129 lines
3.8 KiB
Python
129 lines
3.8 KiB
Python
import os
|
|
import requests
|
|
import pandas as pd
|
|
import plotly.express as px
|
|
from collections import Counter
|
|
|
|
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
|
|
REPO = os.environ["REPO"]
|
|
|
|
HEADERS = {
|
|
"Authorization": f"Bearer {GITHUB_TOKEN}",
|
|
"Accept": "application/vnd.github+json",
|
|
}
|
|
|
|
def get_all_stargazers():
|
|
"""Fetch all stargazers (paginated)."""
|
|
stargazers = []
|
|
page = 1
|
|
while True:
|
|
url = f"https://api.github.com/repos/{REPO}/stargazers?per_page=100&page={page}"
|
|
resp = requests.get(url, headers=HEADERS)
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
if not data:
|
|
break
|
|
stargazers.extend(data)
|
|
page += 1
|
|
print(f"Total stargazers: {len(stargazers)}")
|
|
return stargazers
|
|
|
|
def get_user_location(username):
|
|
"""Fetch location field from a user's profile."""
|
|
url = f"https://api.github.com/users/{username}"
|
|
resp = requests.get(url, headers=HEADERS)
|
|
if resp.status_code == 200:
|
|
return resp.json().get("location", None)
|
|
return None
|
|
|
|
def geocode_location(location, session, cache):
|
|
"""Use Nominatim (OSM) to geocode a location string."""
|
|
if not location or location in cache:
|
|
return cache.get(location)
|
|
try:
|
|
url = "https://nominatim.openstreetmap.org/search"
|
|
params = {"q": location, "format": "json", "limit": 1}
|
|
resp = session.get(url, params=params, timeout=5,
|
|
headers={"User-Agent": f"StargazerMap/{REPO}"})
|
|
results = resp.json()
|
|
if results:
|
|
lat = float(results[0]["lat"])
|
|
lon = float(results[0]["lon"])
|
|
cache[location] = (lat, lon)
|
|
return (lat, lon)
|
|
except Exception:
|
|
pass
|
|
cache[location] = None
|
|
return None
|
|
|
|
def generate_map(points):
|
|
"""Generate an interactive Plotly world map."""
|
|
df = pd.DataFrame(points, columns=["lat", "lon", "location"])
|
|
country_counts = Counter(df["location"])
|
|
df["count"] = df["location"].map(country_counts)
|
|
|
|
fig = px.scatter_geo(
|
|
df,
|
|
lat="lat",
|
|
lon="lon",
|
|
hover_name="location",
|
|
size="count",
|
|
size_max=30,
|
|
color="count",
|
|
color_continuous_scale="Plasma",
|
|
projection="natural earth",
|
|
title=f"⭐ Stargazers of {REPO}",
|
|
template="plotly_dark",
|
|
)
|
|
fig.update_layout(
|
|
title_font_size=22,
|
|
geo=dict(
|
|
showframe=False,
|
|
showcoastlines=True,
|
|
coastlinecolor="rgba(255,255,255,0.2)",
|
|
showland=True,
|
|
landcolor="rgb(30, 30, 50)",
|
|
showocean=True,
|
|
oceancolor="rgb(15, 15, 35)",
|
|
showcountries=True,
|
|
countrycolor="rgba(255,255,255,0.1)",
|
|
),
|
|
paper_bgcolor="rgb(10, 10, 25)",
|
|
font_color="white",
|
|
coloraxis_showscale=False,
|
|
margin=dict(l=0, r=0, t=50, b=0),
|
|
)
|
|
os.makedirs("images", exist_ok=True)
|
|
fig.write_html("images/stargazer-map.html", include_plotlyjs="cdn")
|
|
fig.write_image("images/stargazer-map.svg", width=1200, height=600)
|
|
print("Map saved to images/stargazer-map.html and images/stargazer-map.svg")
|
|
|
|
def main():
|
|
import time
|
|
stargazers = get_all_stargazers()
|
|
|
|
geocache = {}
|
|
points = []
|
|
|
|
session = requests.Session()
|
|
for i, user in enumerate(stargazers):
|
|
username = user["login"]
|
|
location = get_user_location(username)
|
|
if location:
|
|
coords = geocode_location(location, session, geocache)
|
|
if coords:
|
|
points.append((coords[0], coords[1], location))
|
|
# Respect Nominatim rate limit: 1 req/sec
|
|
if i % 5 == 0:
|
|
time.sleep(1)
|
|
|
|
print(f"Mapped {len(points)} stargazers with locations.")
|
|
if points:
|
|
generate_map(points)
|
|
else:
|
|
print("No geocodable locations found.")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|