From 768c76b1460f1075d97d87283b7a8dd5e7e283ef Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 15 Jan 2025 01:48:00 +0800 Subject: [PATCH] feat: fetch github stats json --- .github/workflows/update.yaml | 9 ++- github-stats-json.ts | 112 ++++++++++++++++++++++++++++++++++ index.ts | 8 ++- package.json | 3 +- pnpm-lock.yaml | 8 +++ tsconfig.json | 5 +- 6 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 github-stats-json.ts diff --git a/.github/workflows/update.yaml b/.github/workflows/update.yaml index 2cab2703..adade315 100644 --- a/.github/workflows/update.yaml +++ b/.github/workflows/update.yaml @@ -18,21 +18,20 @@ jobs: contents: write steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: persist-credentials: false - uses: pnpm/action-setup@v4 - name: Install pnpm with: run_install: false - - name: Use Node.js - uses: actions/setup-node@v4 + - uses: actions/setup-node@v4 with: node-version-file: '.node-version' cache: 'pnpm' - run: pnpm i - run: pnpm run download + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: diff --git a/github-stats-json.ts b/github-stats-json.ts new file mode 100644 index 00000000..d11557fe --- /dev/null +++ b/github-stats-json.ts @@ -0,0 +1,112 @@ +// query userInfo($login: String!) { +// user(login: $login) { +// name +// login +// contributionsCollection { +// restrictedContributionsCount +// } +// repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) { +// totalCount +// } +// pullRequests(first: 1) { +// totalCount +// } +// openIssues: issues(states: OPEN) { +// totalCount +// } +// closedIssues: issues(states: CLOSED) { +// totalCount +// } +// followers { +// totalCount +// } +// repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) { +// totalCount +// nodes { +// stargazers { +// totalCount +// } +// forks { +// totalCount +// } +// } +// } +// } +// } + +function fetcher(ghPAT: string) { + return fetch( + 'https://api.github.com/graphql', + { + method: 'POST', + headers: { + Authorization: `Bearer ${ghPAT}`, + 'User-Agent': 'Sukka API - Fetch My GitHub User Info' + }, + body: JSON.stringify({ + variables: { + login: 'sukkaw' + }, + query: 'query userInfo($login:String!){user(login:$login){name login contributionsCollection{restrictedContributionsCount}repositoriesContributedTo(first:1,contributionTypes:[COMMIT,ISSUE,PULL_REQUEST,REPOSITORY]){totalCount}pullRequests(first:1){totalCount}openIssues:issues(states:OPEN){totalCount}closedIssues:issues(states:CLOSED){totalCount}followers{totalCount}repositories(first:100,ownerAffiliations:OWNER,orderBy:{direction:DESC,field:STARGAZERS}){totalCount nodes{stargazers{totalCount}forks{totalCount}}}}}' + }) + } + ); +} + +function fetcherTotalCommit(ghPAT: string) { + return fetch('https://api.github.com/search/commits?q=author:sukkaw', { + headers: { + Accept: 'application/vnd.github.cloak-preview', + 'User-Agent': 'Sukka API - Fetch My GitHub User Info', + Authorization: `Bearer ${ghPAT}` + } + }); +} + +export async function githubSukka(ghPAT: string) { + const stats = { + totalPRs: 'N/A', + followers: 'N/A', + totalCommits: 'N/A', + totalIssues: 'N/A', + totalStars: 'N/A', + totalForks: 'N/A', + contributedTo: 'N/A' + }; + + const [statDataResp, totalCommitData] = await Promise.all([ + fetcher(ghPAT).then((res) => { + if (res.ok) return res.json(); + return null; + }), + fetcherTotalCommit(ghPAT).then((res) => { + if (res.ok) return res.json(); + return null; + }) + ]); + + try { + if (statDataResp) { + const statData = (statDataResp).data.user; + stats.totalIssues = statData.openIssues.totalCount + statData.closedIssues.totalCount; + stats.followers = statData.followers.totalCount; + stats.totalPRs = statData.pullRequests.totalCount; + stats.contributedTo = statData.repositoriesContributedTo.totalCount; + stats.totalStars = statData.repositories.nodes.reduce((prev: number, curr: any) => prev + curr.stargazers.totalCount, 0); + stats.totalForks = statData.repositories.nodes.reduce((prev: number, curr: any) => prev + curr.forks.totalCount, 0); + if (totalCommitData) { + stats.totalCommits = (totalCommitData).total_count + statData.contributionsCollection.restrictedContributionsCount; + } + } + } catch (e) { + console.log(e); + } + + return stats; + // return new Response(JSON.stringify(stats), { + // headers: { + // 'access-control-allow-origin': '*', + // 'cache-control': 'public, max-age=28800, stale-while-revalidate=3600, stale-if-error=3600' + // } + // }); +}; diff --git a/index.ts b/index.ts index a016ec92..4e1942bd 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,8 @@ import fs from 'fs'; import retry from 'async-retry'; import path from 'path'; +import { githubSukka } from './github-stats-json'; +import { nullthrow } from 'foxts/guard'; const baseUrl = new URL('https://github-readme-stats.vercel.app/api'); baseUrl.searchParams.set('username', 'sukkaw'); @@ -43,12 +45,16 @@ const publicDir = path.resolve(__dirname, 'public'); (async () => { try { + const pat = nullthrow(process.env.GITHUB_TOKEN); + const githubStats = await retry(() => githubSukka(pat), { retries: 10 }); + + fs.writeFileSync(path.resolve(publicDir, 'github-stats.json'), JSON.stringify(githubStats)); + const [light, dark] = await Promise.all([ retry(() => fetchSvg(lightUrl), { retries: 10 }), retry(() => fetchSvg(darkUrl), { retries: 10 }) ]); - fs.writeFileSync(path.resolve(publicDir, 'light.svg'), light); fs.writeFileSync(path.resolve(publicDir, 'dark.svg'), dark); } catch (e) { diff --git a/package.json b/package.json index 26a014ab..fa8d5f21 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ }, "author": "", "dependencies": { - "async-retry": "^1.3.3" + "async-retry": "^1.3.3", + "foxts": "^1.1.5" }, "devDependencies": { "@eslint-sukka/node": "^6.1.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7759ba98..7d399d19 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: async-retry: specifier: ^1.3.3 version: 1.3.3 + foxts: + specifier: ^1.1.5 + version: 1.1.5 devDependencies: '@eslint-sukka/node': specifier: ^6.1.6 @@ -805,6 +808,9 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + foxts@1.1.5: + resolution: {integrity: sha512-s5SRvB7PTTOl5ZWYE4PrBhHXi6sw/0fLrYDDZThFHcHJCmEMaWmZwM0A5Op9APs6y0nQGrUgroV6WFKNdTbgSQ==} + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -2104,6 +2110,8 @@ snapshots: flatted@3.3.1: {} + foxts@1.1.5: {} + function-bind@1.1.2: {} get-tsconfig@4.7.6: diff --git a/tsconfig.json b/tsconfig.json index a9bd2460..3628eb58 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,17 +5,18 @@ "moduleDetection": "force", "module": "esnext", "moduleResolution": "bundler", - "esModuleInterop": true, "allowImportingTsExtensions": true, "allowJs": true, "noEmit": true, "downlevelIteration": true, "allowSyntheticDefaultImports": true, + "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true }, "include": [ - "./index.ts" + "./index.ts", + "./github-stats-json.ts" ] }