Last active
March 4, 2026 22:40
-
-
Save samber/cf4ab9b2e9d528e1defa1ea67ff8beaa 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
| const SHEET_STARGAZER = "Github stargazers"; | |
| const GITHUB_TOKEN = "xxx" | |
| const REPOSITORIES = [ | |
| "samber/lo", | |
| "samber/do", | |
| ] | |
| function makeRequest(path) { | |
| const url = 'https://api.github.com'+path; | |
| const response = UrlFetchApp.fetch(url, { | |
| headers: { | |
| Authorization: 'Bearer ' + GITHUB_TOKEN, | |
| Accept: 'application/vnd.github.v3.star+json', | |
| }, | |
| muteHttpExceptions: true, | |
| }); | |
| return JSON.parse(response.getContentText()); | |
| } | |
| function* iterStargazers(repo, firstPage) { | |
| let page = firstPage; | |
| while (true) { | |
| const url = "/repos/" + repo + "/stargazers?per_page=100&page=" + page.toFixed(0); | |
| const data = makeRequest(url); | |
| if (!data || data.length === 0) break; | |
| for (const item of data) { | |
| if (!item.user) { | |
| throw Error("malformed payload"); | |
| } | |
| const user = item.user; | |
| yield { | |
| login: user.login, | |
| id: user.id, | |
| starred_at: item.starred_at | |
| }; | |
| } | |
| page++; | |
| } | |
| } | |
| function getUserDetails(login) { | |
| const url = "/users/" + login; | |
| const data = makeRequest(url); | |
| return { | |
| name: data.name, | |
| email: data.email, | |
| company: data.company, | |
| location: data.location, | |
| bio: data.bio, | |
| twitter: data.twitter_username, | |
| blog: data.blog, | |
| followers: data.followers, | |
| following: data.following, | |
| public_repos: data.public_repos, | |
| public_gists: data.public_gists | |
| }; | |
| } | |
| function syncStargazers() { | |
| const repos = REPOSITORIES; | |
| const now = new Date().toISOString().replace(/\.\d+Z$/, "Z"); | |
| // Create or clear a sheet | |
| const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_STARGAZER); | |
| // sheet.clear(); | |
| // Read existing logins into a Set for fast lookup | |
| const existingLogins = sheet.getRange("A2:Q").getValues(); | |
| const countPerRepo = existingLogins.reduce((prev, curr) => { | |
| const repo = curr[0]; | |
| if (!prev[repo]) { | |
| prev[repo] = 0 | |
| } | |
| prev[repo]++ | |
| return prev; | |
| }, {}) | |
| const stargazersPerRepo = existingLogins.reduce((prev, curr) => { | |
| const repo = curr[0]; | |
| const login = curr[2]; | |
| if (!prev[repo]) { | |
| prev[repo] = {} | |
| } | |
| if (!prev[repo][login]) { | |
| prev[repo][login] = true; | |
| } | |
| return prev; | |
| }, {}) | |
| // const header = [ | |
| // "repository", "user_id", "login", "name", "email", "company", "location", | |
| // "bio", "twitter", "blog", "followers", "following", "public_repos", | |
| // "public_gists", "starred_at", "scrapped_at", "updated_at" | |
| // ]; | |
| // sheet.appendRow(header); | |
| let imported = 0; | |
| for (let repo of repos) { | |
| let count = 0; | |
| if (countPerRepo[repo]) { | |
| count = countPerRepo[repo]; | |
| } | |
| let firstPage = Math.trunc((Math.trunc(count) / 100) + 1); | |
| // Read one page earlier, because some people might have removed stars. | |
| // Anyway, this is cheap since we dedup. | |
| if (firstPage > 1) { | |
| firstPage--; | |
| } | |
| for (const entry of iterStargazers(repo, firstPage)) { | |
| if (stargazersPerRepo[repo] && stargazersPerRepo[repo][entry.login]) { | |
| continue; | |
| } | |
| const details = getUserDetails(entry.login); | |
| const row = [ | |
| repo, | |
| entry.id, | |
| entry.login, | |
| details.name, | |
| details.email, | |
| details.company, | |
| details.location, | |
| details.bio, | |
| details.twitter, | |
| details.blog, | |
| details.followers, | |
| details.following, | |
| details.public_repos, | |
| details.public_gists, | |
| entry.starred_at, | |
| now, | |
| now | |
| ]; | |
| sheet.appendRow(row); | |
| Logger.log("Done: " + entry.login); | |
| Utilities.sleep(100); // polite delay | |
| if (imported >= 1000) { | |
| return; | |
| } | |
| imported++ | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment