diff --git a/get_history/index.ts b/get_history/index.ts index 92e868d..e775f33 100644 --- a/get_history/index.ts +++ b/get_history/index.ts @@ -11,19 +11,27 @@ const config = { forgejoURL: requireEnv("FJ_URL"), }; +const now = new Date(); const oneYearAgo = new Date(); oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); const formattedOneYearAgo = oneYearAgo.toISOString().replace("Z", "+00:00"); -const ghQuery = ` - { - user(login: "aselimov") { - contributionsCollection { +function buildGhQuery(from: string, to: string): string { + return `{ + viewer { + contributionsCollection(from: "${from}", to: "${to}") { + contributionCalendar { + weeks { + contributionDays { + date + contributionCount + } + } + } commitContributionsByRepository(maxRepositories: 100) { repository { nameWithOwner isMirror - isPrivate } contributions(first: 100) { nodes { @@ -34,8 +42,8 @@ const ghQuery = ` } } } - } -`; + }`; +} type DayContributions = { [repo: string]: number; @@ -126,25 +134,33 @@ function deduplicateAcrossPlatforms( function parseGitHubContributions(data: any): ActivityMap { const result: ActivityMap = {}; - const repos = - data?.data?.user?.contributionsCollection - ?.commitContributionsByRepository ?? []; + const collection = data?.data?.viewer?.contributionsCollection; - for (const repoEntry of repos) { + const calendarMap: { [day: string]: number } = {}; + for (const week of collection?.contributionCalendar?.weeks ?? []) { + for (const day of week.contributionDays) { + if (day.contributionCount > 0) calendarMap[day.date] = day.contributionCount; + } + } + + for (const repoEntry of collection?.commitContributionsByRepository ?? []) { const repoName: string = repoEntry.repository.nameWithOwner; - const isMirror: boolean = repoEntry.repository.isMirror; - const isPrivate: boolean = repoEntry.repository.isPrivate; - - if (isMirror) continue; - - const displayName = isPrivate ? "private" : repoName; + if (repoEntry.repository.isMirror) continue; for (const contribution of repoEntry.contributions.nodes) { const day: string = contribution.occurredAt.split("T")[0]; const count: number = contribution.commitCount; - if (!result[day]) result[day] = {}; - result[day]![displayName] = (result[day]![displayName] ?? 0) + count; + result[day]![repoName] = (result[day]![repoName] ?? 0) + count; + } + } + + for (const [day, total] of Object.entries(calendarMap)) { + const known = Object.values(result[day] ?? {}).reduce((a, b) => a + b, 0); + const hidden = total - known; + if (hidden > 0) { + if (!result[day]) result[day] = {}; + result[day]!["private"] = (result[day]!["private"] ?? 0) + hidden; } } @@ -215,25 +231,49 @@ async function getForgejoActivityMap(): Promise { return map; } -async function makeActivityMap(): Promise { - const ghResponse = await fetch("https://api.github.com/graphql", { +async function ghFetch(query: string): Promise { + const response = await fetch("https://api.github.com/graphql", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.githubApiKey}`, }, - body: JSON.stringify({ query: ghQuery }), + body: JSON.stringify({ query }), }); - - if (!ghResponse.ok) { + if (!response.ok) { throw new Error( - `GitHub API error: ${ghResponse.status} ${ghResponse.statusText}`, + `GitHub API error: ${response.status} ${response.statusText}`, ); } + return response.json(); +} - const ghData = await ghResponse.json(); - const ghMap = parseGitHubContributions(ghData); - const fjMap = await getForgejoActivityMap(); +async function getGitHubActivityMap(): Promise { + const ranges = Array.from({ length: 12 }, (_, i) => { + const to = new Date(now); + to.setMonth(to.getMonth() - i); + const from = new Date(to); + from.setMonth(from.getMonth() - 1); + return { from: from.toISOString(), to: to.toISOString() }; + }); + + const results = await Promise.all( + ranges.map(({ from, to }) => ghFetch(buildGhQuery(from, to))), + ); + + const map: ActivityMap = {}; + for (const data of results) { + mergeInto(map, parseGitHubContributions(data)); + } + + return map; +} + +async function makeActivityMap(): Promise { + const [ghMap, fjMap] = await Promise.all([ + getGitHubActivityMap(), + getForgejoActivityMap(), + ]); return deduplicateAcrossPlatforms(ghMap, fjMap); }