add github stats

This commit is contained in:
zyqunix 2025-06-23 16:50:49 +02:00
parent 1e9cea0bdb
commit 62beee7ff8
No known key found for this signature in database
GPG key ID: 134A8DEEA83B80E6
5 changed files with 331 additions and 221 deletions

View file

@ -733,3 +733,28 @@ br {
.twitter-contact > img { .twitter-contact > img {
color: var(--white); color: var(--white);
} }
#github-full {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.gitnamepfp {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
#github-full a {
text-decoration: none;
font-weight: 400 !important;
color: var(--text) !important;
}
#github-full a:hover {
text-decoration: underline;
color: var(--white) !important;
}

76
assets/js/github.js Normal file
View file

@ -0,0 +1,76 @@
export async function fetchGithubStats(user) {
const response = await fetch(`https://api.github.com/users/${user}`);
if (!response.ok) throw new Error(`Error fetching Github Info: ${response.statusText}`);
return await response.json();
}
export async function getTotalStars(username) {
let page = 1;
let totalStars = 0;
let hasMore = true;
while (hasMore) {
const response = await fetch(`https://api.github.com/users/${username}/repos?per_page=100&page=${page}`);
const repos = await response.json();
if (repos.length === 0) break;
totalStars += repos.reduce((sum, repo) => sum + repo.stargazers_count, 0);
hasMore = repos.length === 100;
page++;
}
return totalStars;
}
export async function writeGithubStats(targetId) {
const data = await fetchGithubStats("zyqunix");
const stars = await getTotalStars("zyqunix");
const target = document.querySelector(targetId);
target.innerHTML = "";
const mainEl = document.createElement("div");
mainEl.classList.add("gitnamepfp");
const pfp = document.createElement("img");
pfp.src = data.avatar_url;
pfp.style.borderRadius = "50%";
pfp.style.width = "96px";
const name = document.createElement("a");
name.innerText = data.login;
name.href = data.html_url;
name.target = "_blank";
name.style.fontSize = "20px";
name.classList.add("tooltip");
name.setAttribute("data-tooltip", data.bio);
const pubRepos = document.createElement("a");
pubRepos.innerText = `${data.public_repos} Public Repositories`;
pubRepos.href = `https://github.com/${data.login}?tab=repos`;
pubRepos.id = "pubrepos";
const followers = document.createElement("div");
followers.innerHTML = `<a href="https://github.com/${data.login}?tab=followers" target="_blank">${data.followers} Followers</a> & <a href="https://github.com/${data.login}?tab=following" target="_blank">Following ${data.following}`;
const hireable = document.createElement("div");
if (data.hireable === "null") {
hireable.innerText = "Not Hireable";
} else {
hireable.innerText = "Hire Me!";
}
const tStars = document.createElement("div");
tStars.innerText = `${stars} Total Stars`;
const registered = data.created_at;
document.getElementById("gh_since").innerText = `Registed on ${registered.slice(0, 10).replace(/-/g, "/")}`;
mainEl.appendChild(pfp);
mainEl.appendChild(name);
target.appendChild(mainEl);
target.appendChild(pubRepos);
target.appendChild(followers);
target.appendChild(hireable);
target.appendChild(tStars);
}

View file

@ -1,4 +1,5 @@
import * as wakatime from "./wakatime.js"; import * as wakatime from "./wakatime.js";
import * as github from "./github.js";
const timeElem = document.getElementById('time'); const timeElem = document.getElementById('time');
const timezone = 'Europe/Berlin'; const timezone = 'Europe/Berlin';
@ -241,6 +242,7 @@ function fetchWeather(location) {
} }
wakatime.fetchWakatime("#wakapi"); wakatime.fetchWakatime("#wakapi");
github.writeGithubStats("#github-full");
const messages = [ const messages = [
"Coding", "Coding",

View file

@ -66,274 +66,273 @@ const categoryColors = {
}; };
export async function fetchWakaTimeStats(user, range) { export async function fetchWakaTimeStats(user, range) {
const response = await fetch(`https://wakapi.atums.world/api/v1/users/${user}/stats/${range}`); const response = await fetch(`https://wakapi.atums.world/api/v1/users/${user}/stats/${range}`);
if (!response.ok) throw new Error(`Error fetching WakaTime stats: ${response.statusText}`); if (!response.ok) throw new Error(`Error fetching WakaTime stats: ${response.statusText}`);
return await response.json(); return await response.json();
} }
export async function prepareChartData() { export async function prepareChartData() {
const languages = await fetchWakaTimeStats("zyqunix", "all_time"); const languages = await fetchWakaTimeStats("zyqunix", "all_time");
const sortedLanguages = [...languages.data.languages].sort((a, b) => b.percent - a.percent); const sortedLanguages = [...languages.data.languages].sort((a, b) => b.percent - a.percent);
const totalSeconds = sortedLanguages.reduce((total, lang) => total + lang.total_seconds, 0); const totalSeconds = sortedLanguages.reduce((total, lang) => total + lang.total_seconds, 0);
let totalTime = ''; let totalTime = '';
if (totalSeconds > 3600) { if (totalSeconds > 3600) {
totalTime = `${Math.floor(totalSeconds / 3600)}h ${Math.floor((totalSeconds % 3600) / 60)}m`; totalTime = `${Math.floor(totalSeconds / 3600)}h ${Math.floor((totalSeconds % 3600) / 60)}m`;
} else {
totalTime = `${Math.floor(totalSeconds / 60)}m`;
}
const limit = 10;
const topLanguages = sortedLanguages.slice(0, limit);
if (sortedLanguages.length > limit) {
const otherSeconds = sortedLanguages.slice(limit).reduce((total, lang) => total + lang.total_seconds, 0);
const otherPercent = sortedLanguages.slice(limit).reduce((total, lang) => total + lang.percent, 0);
let otherText = '';
if (otherSeconds > 3600) {
otherText = `${Math.floor(otherSeconds / 3600)}h ${Math.floor((otherSeconds % 3600) / 60)}m`;
} else { } else {
otherText = `${Math.floor(otherSeconds / 60)}m`; totalTime = `${Math.floor(totalSeconds / 60)}m`;
} }
topLanguages.push({ const limit = 10;
name: 'Other', const topLanguages = sortedLanguages.slice(0, limit);
total_seconds: otherSeconds,
percent: otherPercent,
text: otherText,
color: '#CCCCCC',
digital: '',
hours: Math.floor(otherSeconds / 3600),
minutes: Math.floor((otherSeconds % 3600) / 60),
seconds: otherSeconds % 60
});
}
const segmentsWithColors = topLanguages.map(lang => ({ if (sortedLanguages.length > limit) {
...lang, const otherSeconds = sortedLanguages.slice(limit).reduce((total, lang) => total + lang.total_seconds, 0);
color: lang.color || langColors[lang.name] || '#CCCCCC' const otherPercent = sortedLanguages.slice(limit).reduce((total, lang) => total + lang.percent, 0);
}));
return { let otherText = '';
segments: segmentsWithColors, if (otherSeconds > 3600) {
totalTime otherText = `${Math.floor(otherSeconds / 3600)}h ${Math.floor((otherSeconds % 3600) / 60)}m`;
}; } else {
otherText = `${Math.floor(otherSeconds / 60)}m`;
}
topLanguages.push({
name: 'Other',
total_seconds: otherSeconds,
percent: otherPercent,
text: otherText,
color: '#CCCCCC',
digital: '',
hours: Math.floor(otherSeconds / 3600),
minutes: Math.floor((otherSeconds % 3600) / 60),
seconds: otherSeconds % 60
});
}
const segmentsWithColors = topLanguages.map(lang => ({
...lang,
color: lang.color || langColors[lang.name] || '#CCCCCC'
}));
return {
segments: segmentsWithColors,
totalTime
};
} }
export async function fetchWakatime(targetId) { export async function fetchWakatime(targetId) {
const data = await fetchWakaTimeStats("zyqunix", "all_time"); const data = await fetchWakaTimeStats("zyqunix", "all_time");
const target = document.querySelector(`${targetId}`); const target = document.querySelector(`${targetId}`);
target.innerHTML = ""; target.innerHTML = "";
const langDetails = document.createElement("details"); const langDetails = document.createElement("details");
const langSummary = document.createElement("summary"); const langSummary = document.createElement("summary");
langSummary.innerText = "Languages"; langSummary.innerText = "Languages";
langSummary.classList.add("tooltip"); langSummary.classList.add("tooltip");
langSummary.setAttribute("data-tooltip", "Most Used Languages"); langSummary.setAttribute("data-tooltip", "Most Used Languages");
langDetails.appendChild(langSummary); langDetails.appendChild(langSummary);
langDetails.style.marginTop = "15px"; langDetails.style.marginTop = "15px";
target.appendChild(langDetails); target.appendChild(langDetails);
const topLangs = data.data.languages.slice(0, 10); const topLangs = data.data.languages.slice(0, 10);
topLangs.forEach(lang => { topLangs.forEach(lang => {
const el = document.createElement("div"); const el = document.createElement("div");
el.innerText = `${lang.name}: ${lang.text}`; el.innerText = `${lang.name}: ${lang.text}`;
el.id = lang.name.toLowerCase(); el.id = lang.name.toLowerCase();
el.classList.add("proglang"); el.classList.add("proglang");
el.style.margin = "5px"; el.style.margin = "5px";
el.style.padding = "10px"; el.style.padding = "10px";
el.style.borderRadius = "5px"; el.style.borderRadius = "5px";
el.style.backgroundColor = langColors[lang.name]; el.style.backgroundColor = langColors[lang.name];
el.style.color = "var(--base)"; el.style.color = "var(--base)";
langDetails.appendChild(el); langDetails.appendChild(el);
}); });
const edDetails = document.createElement("details"); const edDetails = document.createElement("details");
const edSummary = document.createElement("summary"); const edSummary = document.createElement("summary");
edSummary.innerText = "Editors"; edSummary.innerText = "Editors";
edSummary.classList.add("tooltip"); edSummary.classList.add("tooltip");
edSummary.setAttribute("data-tooltip", "Most Used Editors"); edSummary.setAttribute("data-tooltip", "Most Used Editors");
edDetails.appendChild(edSummary); edDetails.appendChild(edSummary);
edDetails.style.marginTop = "15px"; edDetails.style.marginTop = "15px";
target.appendChild(edDetails); target.appendChild(edDetails);
const topEditors = data.data.editors.slice(0, 5); const topEditors = data.data.editors.slice(0, 5);
topEditors.forEach(editor => { topEditors.forEach(editor => {
const el = document.createElement("div"); const el = document.createElement("div");
el.innerText = `In ${editor.name} for ${editor.text} (${editor.percent}%)`; el.innerText = `In ${editor.name} for ${editor.text} (${editor.percent}%)`;
el.style.margin = "5px"; el.style.margin = "5px";
el.style.padding = "10px"; el.style.padding = "10px";
el.style.borderRadius = "5px"; el.style.borderRadius = "5px";
el.style.color = "var(--base)"; el.style.color = "var(--base)";
el.style.backgroundColor = editorColors[editor.name.toLowerCase().replace(/\s+/g, '').replace(/[^a-zA-Z]/g, '')]; el.style.backgroundColor = editorColors[editor.name.toLowerCase().replace(/\s+/g, '').replace(/[^a-zA-Z]/g, '')];
edDetails.appendChild(el); edDetails.appendChild(el);
}); });
const prDetails = document.createElement("details"); const prDetails = document.createElement("details");
const prSummary = document.createElement("summary"); const prSummary = document.createElement("summary");
prSummary.innerText = "Projects"; prSummary.innerText = "Projects";
prSummary.classList.add("tooltip"); prSummary.classList.add("tooltip");
prSummary.setAttribute("data-tooltip", "Most Used Projects"); prSummary.setAttribute("data-tooltip", "Most Used Projects");
prDetails.appendChild(prSummary); prDetails.appendChild(prSummary);
prDetails.style.marginTop = "15px"; prDetails.style.marginTop = "15px";
target.appendChild(prDetails); target.appendChild(prDetails);
const topProjects = data.data.projects.slice(0, 10); const topProjects = data.data.projects.slice(0, 10);
topProjects.forEach(project => { topProjects.forEach(project => {
const el = document.createElement("div"); const el = document.createElement("div");
el.innerText = `Coded ${project.name} for ${project.text}`; el.innerText = `Coded ${project.name} for ${project.text}`;
el.style.margin = "5px"; el.style.margin = "5px";
el.style.padding = "10px"; el.style.padding = "10px";
el.style.borderRadius = "5px"; el.style.borderRadius = "5px";
el.style.color = "var(--base)"; el.style.color = "var(--base)";
el.style.backgroundColor = projectColors[Math.floor(Math.random() * projectColors.length)]; el.style.backgroundColor = projectColors[Math.floor(Math.random() * projectColors.length)];
prDetails.appendChild(el); prDetails.appendChild(el);
}); });
const osDetails = document.createElement("details"); const osDetails = document.createElement("details");
const osSummary = document.createElement("summary"); const osSummary = document.createElement("summary");
osSummary.innerText = "Operating Systems"; osSummary.innerText = "Operating Systems";
osSummary.classList.add("tooltip"); osSummary.classList.add("tooltip");
osSummary.setAttribute("data-tooltip", "Most Used Operating Systems"); osSummary.setAttribute("data-tooltip", "Most Used Operating Systems");
osDetails.appendChild(osSummary); osDetails.appendChild(osSummary);
osDetails.style.marginTop = "15px"; osDetails.style.marginTop = "15px";
target.appendChild(osDetails); target.appendChild(osDetails);
const topOS = data.data.operating_systems; const topOS = data.data.operating_systems;
topOS.forEach(machine => { topOS.forEach(machine => {
const el = document.createElement('div'); const el = document.createElement('div');
el.innerText = `Coded on ${machine.name} for ${machine.text}`; el.innerText = `Coded on ${machine.name} for ${machine.text}`;
el.style.margin = "5px"; el.style.margin = "5px";
el.style.padding = "10px"; el.style.padding = "10px";
el.style.borderRadius = "5px"; el.style.borderRadius = "5px";
el.style.color = "var(--base)"; el.style.color = "var(--base)";
el.style.backgroundColor = osColors[machine.name.toLowerCase()]; el.style.backgroundColor = osColors[machine.name.toLowerCase()];
osDetails.appendChild(el); osDetails.appendChild(el);
}); });
const caDetails = document.createElement("details"); const caDetails = document.createElement("details");
const caSummary = document.createElement("summary"); const caSummary = document.createElement("summary");
caSummary.innerText = "Categories"; caSummary.innerText = "Categories";
caSummary.classList.add("tooltip"); caSummary.classList.add("tooltip");
caSummary.setAttribute("data-tooltip", "Time Spent by Category"); caSummary.setAttribute("data-tooltip", "Time Spent by Category");
caDetails.appendChild(caSummary); caDetails.appendChild(caSummary);
caDetails.style.marginTop = "15px"; caDetails.style.marginTop = "15px";
target.appendChild(caDetails); target.appendChild(caDetails);
const categories = data.data.categories; const categories = data.data.categories;
categories.forEach(category => { categories.forEach(category => {
const el = document.createElement('div'); const el = document.createElement('div');
el.style.margin = "5px"; el.style.margin = "5px";
el.style.padding = "10px"; el.style.padding = "10px";
el.style.borderRadius = "5px"; el.style.borderRadius = "5px";
el.style.color = "var(--base)"; el.style.color = "var(--base)";
el.style.backgroundColor = categoryColors[category.name.toLowerCase()]; el.style.backgroundColor = categoryColors[category.name.toLowerCase()];
el.innerText = `Has done ${category.name} for ${category.text}`; el.innerText = `Has done ${category.name} for ${category.text}`;
caDetails.appendChild(el); caDetails.appendChild(el);
}); });
const miscDetails = document.createElement("details"); const miscDetails = document.createElement("details");
const miscSummary = document.createElement("summary"); const miscSummary = document.createElement("summary");
miscSummary.innerText = "Miscellaneous"; miscSummary.innerText = "Miscellaneous";
miscSummary.classList.add("tooltip"); miscSummary.classList.add("tooltip");
miscSummary.setAttribute("data-tooltip", "Miscellaneous Wakatime Info"); miscSummary.setAttribute("data-tooltip", "Miscellaneous Wakatime Info");
miscDetails.appendChild(miscSummary); miscDetails.appendChild(miscSummary);
miscDetails.style.marginTop = "15px"; miscDetails.style.marginTop = "15px";
target.appendChild(miscDetails); target.appendChild(miscDetails);
const el = document.createElement("div"); const el = document.createElement("div");
el.innerHTML = ` el.innerHTML = `
Total Coding Time: ${data.data.human_readable_total} Total Coding Time: ${data.data.human_readable_total}
<br> <br>
Daily Average: ${data.data.human_readable_daily_average} Daily Average: ${data.data.human_readable_daily_average}
<br> <br>
Days Since Register: ${data.data.days_including_holidays} Days Since Register: ${data.data.days_including_holidays}
`; `;
miscDetails.appendChild(el); miscDetails.appendChild(el);
document.getElementById("stats_since").innerText = `Registered on ${data.data.start}`; const registered = data.data.start;
document.getElementById("stats_since").innerText = `Registered on ${registered.slice(0, 10).replace(/-/g, "/")}`;
const chartDetails = document.createElement("details"); const chartDetails = document.createElement("details");
const chartSummary = document.createElement("summary"); const chartSummary = document.createElement("summary");
chartSummary.innerText = "Chart"; chartSummary.innerText = "Chart";
chartSummary.classList.add("tooltip"); chartSummary.classList.add("tooltip");
chartSummary.setAttribute("data-tooltip", "Miscellaneous Coding Info"); chartSummary.setAttribute("data-tooltip", "Miscellaneous Coding Info");
chartDetails.appendChild(chartSummary); chartDetails.appendChild(chartSummary);
chartDetails.style.marginTop = "15px"; chartDetails.style.marginTop = "15px";
target.appendChild(chartDetails); target.appendChild(chartDetails);
const chartData = await prepareChartData(); const chartData = await prepareChartData();
const svgNS = "http://www.w3.org/2000/svg"; const svgNS = "http://www.w3.org/2000/svg";
const radius = 50; const radius = 50;
const center = 60; const center = 60;
const strokeWidth = 20; const strokeWidth = 20;
const circumference = 2 * Math.PI * radius; const circumference = 2 * Math.PI * radius;
const container = document.createElement("div"); const container = document.createElement("div");
const legend = document.createElement("div"); const legend = document.createElement("div");
legend.style.display = 'flex'; legend.style.display = 'flex';
legend.style.flexDirection = 'column'; legend.style.flexDirection = 'column';
legend.style.gap = '8px'; legend.style.gap = '8px';
legend.id = 'legend'; legend.id = 'legend';
chartData.segments.forEach(segment => { chartData.segments.forEach(segment => {
const label = document.createElement('div'); const label = document.createElement('div');
label.style.display = 'flex'; label.style.display = 'flex';
label.style.alignItems = 'center'; label.style.alignItems = 'center';
label.style.gap = '8px'; label.style.gap = '8px';
const colorBox = document.createElement('span'); const colorBox = document.createElement('span');
colorBox.style.width = '10px'; colorBox.style.width = '10px';
colorBox.style.height = '10px'; colorBox.style.height = '10px';
colorBox.style.backgroundColor = segment.color; colorBox.style.backgroundColor = segment.color;
colorBox.style.borderRadius = '3px'; colorBox.style.borderRadius = '3px';
const text = document.createElement('span'); const text = document.createElement('span');
text.innerText = `${segment.name} (${segment.text})`; text.innerText = `${segment.name} (${segment.text})`;
label.appendChild(colorBox); label.appendChild(colorBox);
label.appendChild(text); label.appendChild(text);
legend.appendChild(label); legend.appendChild(label);
}); });
const chart = document.createElementNS(svgNS, "svg"); const chart = document.createElementNS(svgNS, "svg");
chart.setAttribute("viewBox", "0 0 120 120"); chart.setAttribute("viewBox", "0 0 120 120");
chart.style.width = "120px"; chart.style.width = "120px";
chart.style.height = "120px"; chart.style.height = "120px";
let cumulativePercent = 0; let cumulativePercent = 0;
chartData.segments.forEach(segment => { chartData.segments.forEach(segment => {
const circle = document.createElementNS(svgNS, "circle"); const circle = document.createElementNS(svgNS, "circle");
circle.setAttribute("cx", center); circle.setAttribute("cx", center);
circle.setAttribute("cy", center); circle.setAttribute("cy", center);
circle.setAttribute("r", radius); circle.setAttribute("r", radius);
circle.setAttribute("fill", "none"); circle.setAttribute("fill", "none");
circle.setAttribute("stroke", segment.color); circle.setAttribute("stroke", segment.color);
circle.setAttribute("stroke-width", strokeWidth); circle.setAttribute("stroke-width", strokeWidth);
circle.setAttribute("transform", `rotate(-90 ${center} ${center})`); circle.setAttribute("transform", `rotate(-90 ${center} ${center})`);
const segmentLength = (segment.percent / 100) * circumference; const segmentLength = (segment.percent / 100) * circumference;
const emptyLength = circumference - segmentLength; const emptyLength = circumference - segmentLength;
circle.setAttribute("stroke-dasharray", `${segmentLength} ${emptyLength}`); circle.setAttribute("stroke-dasharray", `${segmentLength} ${emptyLength}`);
circle.setAttribute("stroke-dashoffset", circumference * (1 - cumulativePercent / 100)); circle.setAttribute("stroke-dashoffset", circumference * (1 - cumulativePercent / 100));
cumulativePercent += segment.percent; cumulativePercent += segment.percent;
chart.appendChild(circle); chart.appendChild(circle);
}); });
container.appendChild(legend); container.appendChild(legend);
container.appendChild(chart); container.appendChild(chart);
chartDetails.appendChild(container); chartDetails.appendChild(container);
} }
console.log(await fetchWakaTimeStats("zyqunix", "all_time"));

View file

@ -43,6 +43,14 @@
<div id="wakapi"></div> <div id="wakapi"></div>
</div> </div>
<div class="github cards" id="GitHub">
<h2>GitHub Stats</h2>
<p id="gh_since">Since 30 Oct 2022</p>
<div id="github-full">
</div>
</div>
<div class="contact cards" id="Contact"> <div class="contact cards" id="Contact">
<h2 class="card-header" id="contact">Contact</h2> <h2 class="card-header" id="contact">Contact</h2>
<a class="contact-item github-contact" href="https://github.com/zyqunix" target="_blank"> <a class="contact-item github-contact" href="https://github.com/zyqunix" target="_blank">