Display top programming languages on GitHub profiles.
Versione datata
// ==UserScript==
// @name GitHub Top Languages
// @description Display top programming languages on GitHub profiles.
// @icon https://github.githubassets.com/favicons/favicon-dark.svg
// @version 1.0
// @author afkarxyz
// @namespace https://github.com/afkarxyz/userscripts/
// @supportURL https://github.com/afkarxyz/userscripts/issues
// @license MIT
// @match https://github.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// Alternative method
let GITHUB_TOKEN = localStorage.getItem("gh_token") || ""; // Change it to: let GITHUB_TOKEN = "your_github_personal_access_token";
window.setGitHubToken = function(token) {
GITHUB_TOKEN = token;
localStorage.setItem("gh_token", token);
console.log("GitHub token has been set successfully!");
console.log("Refresh the page to see the changes.");
};
window.clearGitHubToken = function() {
GITHUB_TOKEN = "";
localStorage.removeItem("gh_token");
console.log("GitHub token has been cleared!");
};
const COLORS_URL = "https://raw.githubusercontent.com/ozh/github-colors/master/colors.json";
let lastUsername = null;
async function getLanguageColors() {
try {
const res = await fetch(COLORS_URL);
return await res.json();
} catch (e) {
console.error("Failed to fetch language colors:", e);
return {};
}
}
const waitForElement = (selector, timeout = 5000) => {
return new Promise((resolve, reject) => {
const interval = 100;
let waited = 0;
const check = () => {
const element = document.querySelector(selector);
if (element) resolve(element);
else if ((waited += interval) >= timeout) reject("Element not found: " + selector);
else setTimeout(check, interval);
};
check();
});
};
async function fetchLanguages(username) {
const repos = [];
let page = 1;
const headers = GITHUB_TOKEN
? { Authorization: `token ${GITHUB_TOKEN}` }
: {};
while (true) {
try {
const res = await fetch(`https://api.github.com/users/${username}/repos?per_page=100&page=${page}`, {
headers
});
if (!res.ok) {
console.error(`GitHub API Error: ${res.status} ${res.statusText}`);
break;
}
const data = await res.json();
if (!Array.isArray(data) || data.length === 0) break;
repos.push(...data);
page++;
const rateLimit = res.headers.get('X-RateLimit-Remaining');
if (rateLimit && parseInt(rateLimit) <= 0) {
console.warn("GitHub API rate limit reached. Set a token using window.setGitHubToken()");
break;
}
} catch (e) {
console.error("Error fetching GitHub repositories:", e);
break;
}
}
const languageCount = {};
let total = 0;
for (const repo of repos) {
if (repo.language) {
total++;
languageCount[repo.language] = (languageCount[repo.language] || 0) + 1;
}
}
return Object.entries(languageCount)
.map(([lang, count]) => ({
lang,
count,
percent: (count / total * 100).toFixed(2)
}))
.sort((a, b) => b.count - a.count);
}
function createLangBar({ lang, percent }, colorMap, hidden = false) {
const color = (colorMap[lang] && colorMap[lang].color) || "#ccc";
const container = document.createElement("div");
container.style.marginBottom = "8px";
container.style.display = hidden ? "none" : "block";
container.className = "lang-bar";
if (hidden) container.classList.add("lang-hidden");
container.innerHTML = `
<div style="height: 8px; width: 100%; background-color: #59636e; border-radius: 4px; margin-bottom: 4px;">
<div style="width: ${percent}%; height: 100%; background-color: ${color}; border-radius: 4px;"></div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; font-size: 13px;">
<div style="display: flex; align-items: center;">
<span style="display: inline-block; width: 10px; height: 10px; background-color: ${color}; border-radius: 50%; margin-right: 6px;"></span>
<span>${lang}</span>
</div>
<span>${percent}%</span>
</div>
`;
return container;
}
async function insertLanguageStats() {
const match = window.location.pathname.match(/^\/([^\/]+)$/);
if (!match) return;
const username = match[1];
if (username === lastUsername) return;
lastUsername = username;
try {
const container = await waitForElement('.vcard-names-container');
if (container.querySelector('.lang-bar')) return;
const loadingEl = document.createElement("div");
loadingEl.id = "lang-stats-loading";
loadingEl.textContent = "Loading...";
loadingEl.style.marginTop = "12px";
loadingEl.style.fontSize = "13px";
loadingEl.style.color = "#666";
container.appendChild(loadingEl);
const statsWrapper = document.createElement("div");
statsWrapper.id = "lang-stats-wrapper";
statsWrapper.style.marginTop = "12px";
statsWrapper.style.width = "100%";
const [langs, colors] = await Promise.all([
fetchLanguages(username),
getLanguageColors()
]);
const loadingIndicator = document.getElementById("lang-stats-loading");
if (loadingIndicator) loadingIndicator.remove();
if (langs.length === 0) {
statsWrapper.innerHTML = "<div style='font-size: 13px; color: #666;'>No language data available</div>";
container.appendChild(statsWrapper);
return;
}
const topLangs = langs.slice(0, 3);
const restLangs = langs.slice(3);
topLangs.forEach(langData => {
statsWrapper.appendChild(createLangBar(langData, colors));
});
restLangs.forEach(langData => {
statsWrapper.appendChild(createLangBar(langData, colors, true));
});
if (restLangs.length > 0) {
const toggleBtn = document.createElement("button");
toggleBtn.textContent = "Show more...";
toggleBtn.style.background = "none";
toggleBtn.style.border = "none";
toggleBtn.style.color = "#4493f8";
toggleBtn.style.cursor = "pointer";
toggleBtn.style.padding = "0";
toggleBtn.style.fontSize = "13px";
toggleBtn.style.marginTop = "5px";
toggleBtn.onclick = () => {
const hidden = statsWrapper.querySelectorAll('.lang-hidden');
hidden.forEach(e => e.style.display = "block");
toggleBtn.remove();
};
statsWrapper.appendChild(toggleBtn);
}
container.appendChild(statsWrapper);
} catch (error) {
console.error("Error inserting language stats:", error);
}
}
let currentPath = location.pathname;
const observer = new MutationObserver(() => {
if (location.pathname !== currentPath) {
currentPath = location.pathname;
setTimeout(insertLanguageStats, 800);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(insertLanguageStats, 500);
})();