Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e9b0a0fc5 | ||
|
|
fe1ce9d693 | ||
|
|
6cee3e67ed |
+67
-21
@@ -1,10 +1,7 @@
|
||||
const axios = require("axios");
|
||||
const { renderError, kFormatter } = require("../utils");
|
||||
require("dotenv").config();
|
||||
|
||||
async function fetchStats(username) {
|
||||
if (!username) throw Error("Invalid username");
|
||||
|
||||
const res = await axios({
|
||||
url: "https://api.github.com/graphql",
|
||||
method: "post",
|
||||
@@ -20,7 +17,7 @@ async function fetchStats(username) {
|
||||
totalCount
|
||||
}
|
||||
contributionsCollection {
|
||||
totalCommitContributions
|
||||
contributionYears
|
||||
}
|
||||
pullRequests(first: 100) {
|
||||
totalCount
|
||||
@@ -52,17 +49,65 @@ async function fetchStats(username) {
|
||||
totalStars: 0,
|
||||
contributedTo: 0,
|
||||
};
|
||||
|
||||
if (res.data.errors) {
|
||||
console.log(res.data.errors);
|
||||
throw Error("Could not fetch user");
|
||||
}
|
||||
if (res.data.error) return stats;
|
||||
|
||||
const user = res.data.data.user;
|
||||
|
||||
// FETCH ALL COMMITS
|
||||
let commitPromises = user.contributionsCollection.contributionYears.map(
|
||||
async (year) => {
|
||||
// Don't fetch contributions older than 2008 (Perf optimization)
|
||||
if (year < 2008) {
|
||||
return {
|
||||
user: { contributionsCollection: { totalCommitContributions: 0 } },
|
||||
};
|
||||
}
|
||||
|
||||
const currentDate = new Date();
|
||||
currentDate.setFullYear(year, 0, 0);
|
||||
|
||||
const res = await axios({
|
||||
url: "https://api.github.com/graphql",
|
||||
method: "post",
|
||||
headers: {
|
||||
Authorization: `bearer ${process.env.GITHUB_TOKEN}`,
|
||||
},
|
||||
data: {
|
||||
query: `
|
||||
query userInfo($login: String!, $from: DateTime!) {
|
||||
user(login: $login) {
|
||||
contributionsCollection(from: $from) {
|
||||
totalCommitContributions
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
login: username,
|
||||
from: currentDate.toISOString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(
|
||||
currentDate.toDateString(),
|
||||
res.data.data.user.contributionsCollection.totalCommitContributions
|
||||
);
|
||||
return res.data.data;
|
||||
}
|
||||
);
|
||||
|
||||
const allCommits = await Promise.all(commitPromises);
|
||||
|
||||
const totalCommits = allCommits.reduce((preYear, currYear) => {
|
||||
return (
|
||||
preYear + currYear.user.contributionsCollection.totalCommitContributions
|
||||
);
|
||||
}, 0);
|
||||
|
||||
stats.name = user.name;
|
||||
stats.totalIssues = user.issues.totalCount;
|
||||
stats.totalCommits = user.contributionsCollection.totalCommitContributions;
|
||||
stats.totalCommits = totalCommits;
|
||||
stats.totalPRs = user.pullRequests.totalCount;
|
||||
stats.contributedTo = user.repositoriesContributedTo.totalCount;
|
||||
|
||||
@@ -74,11 +119,12 @@ async function fetchStats(username) {
|
||||
}
|
||||
|
||||
const createTextNode = (icon, label, value, lheight) => {
|
||||
const classname = icon === "★" && "star-icon";
|
||||
return `
|
||||
<tspan x="25" dy="${lheight}" class="stat bold">
|
||||
<tspan class="icon ${classname}" fill="#4C71F2">${icon}</tspan> ${label}:</tspan>
|
||||
<tspan x="155" dy="0" class="stat">${kFormatter(value)}</tspan>
|
||||
<tspan class="icon ${
|
||||
icon === "★" && "star-icon"
|
||||
}" fill="#4C71F2">${icon}</tspan> ${label}:</tspan>
|
||||
<tspan x="155" dy="0" class="stat">${value}</tspan>
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -135,16 +181,16 @@ const renderSVG = (stats, options) => {
|
||||
};
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
const { username, hide, hide_border, show_icons, line_height } = req.query;
|
||||
let stats;
|
||||
const username = req.query.username;
|
||||
const hide = req.query.hide;
|
||||
const hide_border = req.query.hide_border;
|
||||
const show_icons = req.query.show_icons;
|
||||
const line_height = req.query.line_height;
|
||||
|
||||
const stats = await fetchStats(username);
|
||||
|
||||
res.setHeader('Cache-Control', 'public, max-age=300');
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
try {
|
||||
stats = await fetchStats(username);
|
||||
} catch (err) {
|
||||
return res.send(renderError(err.message));
|
||||
}
|
||||
|
||||
res.send(
|
||||
renderSVG(stats, {
|
||||
hide: JSON.parse(hide || "[]"),
|
||||
|
||||
+45
-51
@@ -1,7 +1,12 @@
|
||||
const axios = require("axios");
|
||||
const { renderError, kFormatter, encodeHTML } = require("../utils");
|
||||
require("dotenv").config();
|
||||
|
||||
function kFormatter(num) {
|
||||
return Math.abs(num) > 999
|
||||
? Math.sign(num) * (Math.abs(num) / 1000).toFixed(1) + "k"
|
||||
: Math.sign(num) * Math.abs(num);
|
||||
}
|
||||
|
||||
async function fetchRepo(username, reponame) {
|
||||
const res = await axios({
|
||||
url: "https://api.github.com/graphql",
|
||||
@@ -11,28 +16,35 @@ async function fetchRepo(username, reponame) {
|
||||
},
|
||||
data: {
|
||||
query: `
|
||||
fragment RepoInfo on Repository {
|
||||
name
|
||||
stargazers {
|
||||
totalCount
|
||||
}
|
||||
description
|
||||
primaryLanguage {
|
||||
color
|
||||
id
|
||||
name
|
||||
}
|
||||
forkCount
|
||||
}
|
||||
query getRepo($login: String!, $repo: String!) {
|
||||
user(login: $login) {
|
||||
repository(name: $repo) {
|
||||
...RepoInfo
|
||||
name
|
||||
stargazers {
|
||||
totalCount
|
||||
}
|
||||
description
|
||||
primaryLanguage {
|
||||
color
|
||||
id
|
||||
name
|
||||
}
|
||||
forkCount
|
||||
}
|
||||
}
|
||||
organization(login: $login) {
|
||||
repository(name: $repo) {
|
||||
...RepoInfo
|
||||
name
|
||||
stargazers {
|
||||
totalCount
|
||||
}
|
||||
description
|
||||
primaryLanguage {
|
||||
color
|
||||
id
|
||||
name
|
||||
}
|
||||
forkCount
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,39 +56,23 @@ async function fetchRepo(username, reponame) {
|
||||
},
|
||||
});
|
||||
|
||||
const data = res.data.data;
|
||||
if (res.data.error && res.data.error.type !== "NOT_FOUND") return {};
|
||||
if (!res.data.data.organization && res.data.data.user)
|
||||
return res.data.data.user.repository;
|
||||
|
||||
if (!data.user && !data.organization) {
|
||||
throw new Error("Not found");
|
||||
}
|
||||
const isOrg = res.data.data.organization && !res.data.data.user;
|
||||
if (isOrg) return res.data.data.organization.repository;
|
||||
|
||||
if (data.organization === null && data.user) {
|
||||
if (!data.user.repository) {
|
||||
throw new Error("User Repository Not found");
|
||||
}
|
||||
return data.user.repository;
|
||||
}
|
||||
|
||||
if (data.user === null && data.organization) {
|
||||
if (!data.organization.repository) {
|
||||
throw new Error("Organization Repository Not found");
|
||||
}
|
||||
return data.organization.repository;
|
||||
}
|
||||
return res.data.data.user.repository;
|
||||
}
|
||||
|
||||
const renderRepoCard = (repo) => {
|
||||
const { name, description, primaryLanguage, stargazers, forkCount } = repo;
|
||||
const height = 120;
|
||||
|
||||
const shiftText = primaryLanguage.name.length > 15 ? 0 : 30;
|
||||
|
||||
let desc = description || "No description provided";
|
||||
if (desc.length > 55) {
|
||||
desc = `${description.slice(0, 55)}..`;
|
||||
}
|
||||
|
||||
return `
|
||||
<svg width="400" height="${height}" viewBox="0 0 400 ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="400" height="${height}" viewBox="0 0 400 ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.header { font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; fill: #2F80ED }
|
||||
.stat { font: 600 14px 'Segoe UI', Ubuntu, Sans-Serif; fill: #333 }
|
||||
@@ -91,8 +87,11 @@ const renderRepoCard = (repo) => {
|
||||
</svg>
|
||||
|
||||
<text x="50" y="38" class="header">${name}</text>
|
||||
<text class="description" x="25" y="70">${encodeHTML(desc)}</text>
|
||||
<text class="description" x="25" y="70">${
|
||||
description ? description.slice(0, 60) : "No description provided"
|
||||
}..</text>
|
||||
|
||||
|
||||
<g transform="translate(30, 100)">
|
||||
<circle cx="0" cy="-5" r="6" fill="${primaryLanguage.color}" />
|
||||
<text class="gray" x="15">${primaryLanguage.name}</text>
|
||||
@@ -116,17 +115,12 @@ const renderRepoCard = (repo) => {
|
||||
};
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
const { username, repo } = req.query;
|
||||
const username = req.query.username;
|
||||
const repo = req.query.repo;
|
||||
|
||||
let repoData;
|
||||
const repoData = await fetchRepo(username, repo);
|
||||
|
||||
res.setHeader('Cache-Control', 'public, max-age=300');
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
try {
|
||||
repoData = await fetchRepo(username, repo);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return res.send(renderError(err.message));
|
||||
}
|
||||
|
||||
res.send(renderRepoCard(repoData));
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Github Readme stats
|
||||
|
||||
Get dynamically generated GitHub stats on your readmes!
|
||||
Get dynamically generated github stats on your readmes!
|
||||
|
||||
# Features
|
||||
|
||||
@@ -9,17 +9,17 @@ Get dynamically generated GitHub stats on your readmes!
|
||||
|
||||
# Github Stats Card
|
||||
|
||||
Copy paste this into your markdown content, and that's it. Simple!
|
||||
Copy paste this into your markdown content and thats it. simple!
|
||||
|
||||
change the `?username=` value to your GitHubs's username
|
||||
|
||||
```md
|
||||
[](https://github.com/anuraghazra/github-readme-stats)
|
||||

|
||||
```
|
||||
|
||||
### Hiding individual stats
|
||||
### Hiding certain stats
|
||||
|
||||
To hide any specific stats, you can pass a query parameter `?hide=` with an array of items, you wanna hide.
|
||||
To hide any specific stats you can pass a query parameter `?hide=` with an array of items you wanna hide.
|
||||
|
||||
> Options: `&hide=["stars","prs","issues","contribs"]`
|
||||
|
||||
@@ -29,7 +29,7 @@ To hide any specific stats, you can pass a query parameter `?hide=` with an arra
|
||||
|
||||
### Showing icons
|
||||
|
||||
To enable icons, you can pass `show_icons=true` in the query param like so
|
||||
To enable icons you can pass `show_icons=true` in the query param like so
|
||||
|
||||
```md
|
||||

|
||||
@@ -56,13 +56,13 @@ Other options:
|
||||
|
||||
# Github Extra Pins
|
||||
|
||||
Github extra pins allow you to pin more than 6 repositories in your profile using a GitHub readme profile.
|
||||
Github extra pins allows you to pin more than 6 repositories in your profile by using github readme profile.
|
||||
|
||||
Yey! you are no longer limited to 6 pinned repositories.
|
||||
Yey! you are no longer limited to 6 pinned repos
|
||||
|
||||
### Usage
|
||||
|
||||
Copy-paste this code into your readme and change the links.
|
||||
Copy paste this code into your readme and change the links.
|
||||
|
||||
Endpoint: `api/pin?username=anuraghazra&repo=github-readme-stats`
|
||||
|
||||
@@ -76,7 +76,7 @@ Endpoint: `api/pin?username=anuraghazra&repo=github-readme-stats`
|
||||
|
||||
### Quick Tip (Align The Repo Cards)
|
||||
|
||||
You usually won't be able to layout the images side by side to do that you can use this approach
|
||||
Normally you won't be able to layout the images side by side to do that you can use this approach
|
||||
|
||||
```md
|
||||
<a href="https://github.com/anuraghazra/github-readme-stats">
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
const renderError = (message) => {
|
||||
return `
|
||||
<svg width="495" height="100" viewBox="0 0 495 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.text { font: 600 16px 'Segoe UI', Ubuntu, Sans-Serif; fill: #2F80ED }
|
||||
.small { font: 600 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: #252525 }
|
||||
</style>
|
||||
<rect x="0.5" y="0.5" width="494" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
|
||||
<text x="25" y="45" class="text">Something went wrong! file an issue at https://git.io/JJmN9</text>
|
||||
<text x="25" y="65" class="text small">${message}</text>
|
||||
</svg>
|
||||
`;
|
||||
};
|
||||
|
||||
// https://stackoverflow.com/a/48073476/10629172
|
||||
function encodeHTML(str) {
|
||||
return str.replace(/[\u00A0-\u9999<>&](?!#)/gim, function (i) {
|
||||
return "&#" + i.charCodeAt(0) + ";";
|
||||
});
|
||||
}
|
||||
|
||||
function kFormatter(num) {
|
||||
return Math.abs(num) > 999
|
||||
? Math.sign(num) * (Math.abs(num) / 1000).toFixed(1) + "k"
|
||||
: Math.sign(num) * Math.abs(num);
|
||||
}
|
||||
|
||||
module.exports = { renderError, kFormatter, encodeHTML };
|
||||
Reference in New Issue
Block a user