mirror of
https://github.com/loot/loot.github.io.git
synced 2026-07-27 14:13:30 -07:00
It's currently easy to hit the GitHub API's rate limit just by refreshing the credits page a couple of times. The data doesn't change that frequently, so instead fetch it into a JSON file as part of the build process, have Hugo generate the page using the JSON file and a template, and deploy the site each day at midnight so that the live data is at most 24 hours old.
230 lines
6.9 KiB
JavaScript
230 lines
6.9 KiB
JavaScript
import { Octokit } from './assets/js/octokit.js';
|
|
import { writeFile } from 'fs/promises';
|
|
|
|
// Commit 37e464f6001d440ae532516f40fe19162537475a and earlier are common
|
|
// between the loot and libloot repositories, so are double-counted by GitHub.
|
|
// The same is true for commit 765be028964746022f1e3540524044b855ac130c in the
|
|
// skyrimse and skyrimvr repos, and for commit
|
|
// dd6b20985de3cbdee1689d4580590850c0120fb5 in the fallout4 and fallout4vr
|
|
// repos.
|
|
const duplicateContributions = new Map([
|
|
// (loot/libloot) + (skyrimse/skyrimvr) + (fallout4/fallout4vr)
|
|
['Ortham', (1674 + 1028) + (96 + 1) + (42 + 1)],
|
|
['TokcDK', (25 + 3) + (1) + (0)],
|
|
['Freso', (26 + 24 + 3) + (8 + 2) + (231 + 1 + 1)],
|
|
['Trollmen', 22],
|
|
['Sharlikran', (13) + (31) + (50)],
|
|
['lpradel', 12],
|
|
['silentdark', 11 + 4],
|
|
['3ventic', (10) + (1) + (0)],
|
|
['cpasmoi', 5 + 1],
|
|
['steamb23', 4 + 3],
|
|
['niveuseverto', 2],
|
|
['kassane', 2],
|
|
['Liam Brown', 1],
|
|
['Lakrits', 1],
|
|
['MaddBomber83', (1) + (3) + (30)],
|
|
// (skyrimse/skyrimvr) + (fallout4/fallout4vr)
|
|
['MacSplody', (617 + 598 + 424 + 415) + (106 + 87 + 39 + 15)],
|
|
['sibir-ine', (93 + 71 + 34 + 7 + 2) + (14 + 10 + 6)],
|
|
['Infernio', (73) + (3)],
|
|
['sean-kang', 59],
|
|
['ddbb07', (53) + (2)],
|
|
['pStyl3', (37 + 6) + (48 + 47)],
|
|
['Sabre142', 35],
|
|
['DaCoolX', (12) + (3)],
|
|
['themayor897', 10],
|
|
['flatwhatson', 7],
|
|
['cecell', 6],
|
|
['IllusiveMan196', 6],
|
|
['Xahtax', 6],
|
|
['omniguous', (6) + (14)],
|
|
['ExE-Boss', (5) + (1)],
|
|
['megapatato', 5],
|
|
['ArtaiosGreybark', 4],
|
|
['ironmagician', 4],
|
|
['Qwinn1', 4],
|
|
['jb29', 4 + 2],
|
|
['FWDekker', 3],
|
|
['LostDragonist', 3],
|
|
['Arthmoor', (2 + 1) + (3)],
|
|
['BeermotorWB', 2],
|
|
['BellCubeDev', 2],
|
|
['FenghuangZero', (2) + (1)],
|
|
['Nawor3565', 2],
|
|
['TateTaylorOH', 2],
|
|
['Riwaha', 2],
|
|
['simoneddeland', 2 + 1],
|
|
['Yoosk', 2],
|
|
['bppipher', 2],
|
|
['jgostling', 2],
|
|
['spaceoden', (2) + (1)],
|
|
['such-dev', (2) + (1)],
|
|
['ubeogesh', 2],
|
|
['ChrRubin', 1],
|
|
['Chef0000', 1],
|
|
['Daniel Hofheinz', 1],
|
|
['Deka-O', (1) + (1)],
|
|
['DomT602', 1],
|
|
['DoubleYouC', 1],
|
|
['FelesNoctis', 1],
|
|
['Spiess', 1],
|
|
['RacerBG', 1],
|
|
['GonDragon', 1],
|
|
['Bl4ckread', 1],
|
|
['JDCalvert', 1],
|
|
['jordanbtucker', 1],
|
|
['Gavvers', 1],
|
|
['Kinaga', 1],
|
|
['mrudat', (1) + (1)],
|
|
['ann4belle', 1],
|
|
['OddDrifter', 1],
|
|
['PenguinMancer', 1],
|
|
['SkyLover264', 1],
|
|
['Thomas Winwood', 1],
|
|
['tomtesoro', 1],
|
|
['Willian Tomaz Nieckarz', 1],
|
|
['coco9515', 1],
|
|
['jobobby04', 1],
|
|
['kuroko137', (1) + (1)],
|
|
['lucible', 1],
|
|
['pragasette', 1],
|
|
['tktk11', 1],
|
|
['yggdrasil75', 1],
|
|
['zDasF1xer', 1],
|
|
// fallout4/fallout4vr
|
|
['Superfly-Johnson', 17],
|
|
['unforbidable', 5],
|
|
['ghostsquad', 3],
|
|
['darthbdaman', 3],
|
|
['wim95', 3],
|
|
['icopp', 2],
|
|
['mothheart', 2],
|
|
['yetihairball', 2],
|
|
['DiglidiDudeNG', 1],
|
|
['Loganbacca', 1],
|
|
['RowanSkie', 1],
|
|
['Nightcaper', 1],
|
|
['SierraKomodo', 1],
|
|
['sehraf', 1],
|
|
['shadowslasher410', 1],
|
|
['soyasoya5', 1],
|
|
['thurask', 1],
|
|
]);
|
|
|
|
// Some contributions were made using email addresses that aren't associated
|
|
// with a GitHub account by people with other contributions done with an email
|
|
// that is associated with a GitHub account. This maps the latter to the former
|
|
// so that their stats can be combined.
|
|
const contributorMapping = new Map([
|
|
['Ortham', ['Liam Brown']],
|
|
['dependabot[bot]', ['dependabot-preview[bot]']],
|
|
['MothHeart', ['Dr. Gutfuck LLC', 'mothheart']],
|
|
['David Tan', ['Velgus', 'David']],
|
|
]);
|
|
|
|
function sortContributors(a,b) {
|
|
if (a.contributions != b.contributions) {
|
|
return b.contributions - a.contributions;
|
|
} else {
|
|
return a.name.localeCompare(b.name);
|
|
}
|
|
}
|
|
|
|
function getStats(contributor) {
|
|
const isAnon = contributor.type === 'Anonymous';
|
|
return {
|
|
contributions: contributor.contributions,
|
|
name: isAnon ? contributor.name : contributor.login,
|
|
avatar_url: isAnon ? undefined : contributor.avatar_url,
|
|
html_url: isAnon ? 'mailto:' + contributor.email : contributor.html_url
|
|
};
|
|
}
|
|
|
|
function statsReducer(previous, current) {
|
|
const existing = previous.find(element => element.name === current.name);
|
|
if (existing === undefined) {
|
|
return previous.concat(current);
|
|
}
|
|
|
|
existing.contributions += current.contributions;
|
|
return previous;
|
|
}
|
|
|
|
function getContributorsStats(contributors) {
|
|
return contributors
|
|
.map(getStats)
|
|
.reduce(statsReducer, []);
|
|
}
|
|
|
|
function fixStats(contributorsStats) {
|
|
// Two fixes need to be made. Some commits are common to the loot and
|
|
// libloot repos: these are double-counted by GitHub and that needs to be
|
|
// reversed. There are also some separate stats objects that actually refer
|
|
// to the same person and so need to be merged.
|
|
|
|
const countByContributor = new Map();
|
|
|
|
for (const contributorStats of contributorsStats) {
|
|
const contributionCount = duplicateContributions.get(contributorStats.name);
|
|
if (contributionCount !== undefined) {
|
|
console.log(`Removing ${contributionCount} double-counted contributions for ${contributorStats.name}`);
|
|
contributorStats.contributions -= contributionCount;
|
|
}
|
|
|
|
countByContributor.set(contributorStats.name, contributorStats.contributions);
|
|
}
|
|
|
|
const namesToRemove = new Set();
|
|
|
|
for (const contributorStats of contributorsStats) {
|
|
const otherNames = contributorMapping.get(contributorStats.name) ?? [];
|
|
for (const otherName of otherNames) {
|
|
const otherNameCount = countByContributor.get(otherName);
|
|
if (otherNameCount !== undefined) {
|
|
console.log(`Adding contributions by ${otherName} to contributions by ${contributorStats.name}`);
|
|
contributorStats.contributions += otherNameCount;
|
|
|
|
namesToRemove.add(otherName);
|
|
}
|
|
}
|
|
}
|
|
|
|
return contributorsStats
|
|
.filter(stats => !namesToRemove.has(stats.name));
|
|
}
|
|
|
|
async function getContributors() {
|
|
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
|
|
|
|
const repos = await octokit.paginate(octokit.rest.repos.listForOrg, {
|
|
org: 'loot'
|
|
});
|
|
|
|
const promises = repos
|
|
.filter(repo => !repo.fork)
|
|
.map(repo => octokit.paginate(octokit.rest.repos.listContributors, {
|
|
owner: 'loot',
|
|
repo: repo.name,
|
|
anon: true
|
|
}));
|
|
|
|
return Promise.all(promises).then(results => results.flat());
|
|
}
|
|
|
|
async function main() {
|
|
const contributors = await getContributors();
|
|
|
|
const stats = getContributorsStats(contributors);
|
|
|
|
const fixedStats = fixStats(stats);
|
|
|
|
fixedStats.sort(sortContributors);
|
|
|
|
const json = JSON.stringify(fixedStats, null, 2);
|
|
|
|
await writeFile('./content/credits/contributors.json', json);
|
|
}
|
|
|
|
await main();
|