您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Display top programming languages on GitHub profiles.
- // ==UserScript==
- // @name GitHub Top Languages
- // @description Display top programming languages on GitHub profiles.
- // @icon https://github.githubassets.com/favicons/favicon-dark.svg
- // @version 1.3
- // @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';
- // Hardcode: let GITHUB_TOKEN = "your_github_personal_access_token";
- let GITHUB_TOKEN = localStorage.getItem("gh_token") || "";
- const CACHE_DURATION = 60 * 60 * 1000;
- 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!");
- };
- function getCachedData(key) {
- const cachedItem = localStorage.getItem(key);
- if (!cachedItem) return null;
- try {
- const { data, timestamp } = JSON.parse(cachedItem);
- if (Date.now() - timestamp < CACHE_DURATION) {
- return data;
- }
- localStorage.removeItem(key);
- return null;
- } catch (e) {
- console.error("Error parsing cached data:", e);
- localStorage.removeItem(key);
- return null;
- }
- }
- function setCachedData(key, data) {
- const cacheItem = {
- data,
- timestamp: Date.now()
- };
- localStorage.setItem(key, JSON.stringify(cacheItem));
- }
- window.clearLanguageCache = function() {
- const keysToRemove = [];
- for (let i = 0; i < localStorage.length; i++) {
- const key = localStorage.key(i);
- if (key.startsWith('gh_langs_') || key.startsWith('gh_colors')) {
- keysToRemove.push(key);
- }
- }
- keysToRemove.forEach(key => localStorage.removeItem(key));
- console.log("Language cache has been cleared!");
- };
- const COLORS_URL = "https://raw.githubusercontent.com/afkarxyz/userscripts/refs/heads/main/assets/github/colors.json";
- let lastUsername = null;
- async function getLanguageColors() {
- const cachedColors = getCachedData('gh_colors');
- if (cachedColors) {
- return cachedColors;
- }
- try {
- const res = await fetch(COLORS_URL);
- const colors = await res.json();
- setCachedData('gh_colors', colors);
- return colors;
- } catch (e) {
- console.error("Failed to fetch language colors:", e);
- return {};
- }
- }
- async function fetchLanguagesGraphQL(username, isOrg = false) {
- const cacheKey = `gh_langs_${username}_${isOrg ? 'org' : 'user'}`;
- const cachedLangs = getCachedData(cacheKey);
- if (cachedLangs) {
- console.log(`Using cached language data for ${username}`);
- return cachedLangs;
- }
- if (!GITHUB_TOKEN) {
- console.warn("GitHub GraphQL API requires a token. Please set one using window.setGitHubToken()");
- return [];
- }
- console.log(`Fetching fresh language data for ${username} using GraphQL`);
- const query = isOrg ? `
- query OrgRepoLanguages($orgName: String!, $cursor: String) {
- organization(login: $orgName) {
- repositories(first: 100, after: $cursor, privacy: PUBLIC, isFork: false) {
- pageInfo {
- hasNextPage
- endCursor
- }
- nodes {
- languages(first: 100, orderBy: {field: SIZE, direction: DESC}) {
- edges {
- size
- node {
- name
- color
- }
- }
- totalSize
- }
- }
- }
- }
- }
- ` : `
- query UserRepoLanguages($login: String!, $cursor: String) {
- user(login: $login) {
- repositories(first: 100, after: $cursor, privacy: PUBLIC, ownerAffiliations: OWNER, isFork: false) {
- pageInfo {
- hasNextPage
- endCursor
- }
- nodes {
- languages(first: 100, orderBy: {field: SIZE, direction: DESC}) {
- edges {
- size
- node {
- name
- color
- }
- }
- totalSize
- }
- }
- }
- }
- }
- `;
- const allLanguages = {};
- let hasNextPage = true;
- let cursor = null;
- try {
- while (hasNextPage) {
- const variables = isOrg
- ? { orgName: username, cursor }
- : { login: username, cursor };
- const response = await fetch("https://api.github.com/graphql", {
- method: "POST",
- headers: {
- "Authorization": `Bearer ${GITHUB_TOKEN}`,
- "Content-Type": "application/json"
- },
- body: JSON.stringify({ query, variables })
- });
- if (!response.ok) {
- throw new Error(`GitHub API error: ${response.status}`);
- }
- const data = await response.json();
- if (data.errors) {
- console.error("GraphQL errors:", data.errors);
- break;
- }
- const entityData = isOrg ? data.data?.organization : data.data?.user;
- if (!entityData) break;
- const repositories = entityData.repositories;
- const pageInfo = repositories.pageInfo;
- repositories.nodes.forEach(repo => {
- if (!repo.languages.edges) return;
- repo.languages.edges.forEach(edge => {
- const { name, color } = edge.node;
- const size = edge.size;
- if (!allLanguages[name]) {
- allLanguages[name] = {
- size: 0,
- color: color
- };
- }
- allLanguages[name].size += size;
- });
- });
- hasNextPage = pageInfo.hasNextPage;
- cursor = pageInfo.endCursor;
- }
- const totalSize = Object.values(allLanguages).reduce((sum, lang) => sum + lang.size, 0);
- const result = Object.entries(allLanguages)
- .map(([lang, data]) => ({
- lang,
- color: data.color,
- count: data.size,
- percent: ((data.size / totalSize) * 100).toFixed(2)
- }))
- .sort((a, b) => b.count - a.count);
- setCachedData(cacheKey, result);
- return result;
- } catch (e) {
- console.error("Error fetching languages with GraphQL:", e);
- return [];
- }
- }
- function createLanguageBar(languages, colorMap) {
- const container = document.createElement("div");
- container.style.marginTop = "16px";
- container.style.width = "100%";
- const barContainer = document.createElement("div");
- barContainer.style.display = "flex";
- barContainer.style.height = "8px";
- barContainer.style.width = "100%";
- barContainer.style.borderRadius = "4px";
- barContainer.style.overflow = "hidden";
- barContainer.style.marginBottom = "8px";
- const legendContainer = document.createElement("div");
- legendContainer.style.display = "flex";
- legendContainer.style.flexWrap = "wrap";
- legendContainer.style.fontSize = "12px";
- languages.forEach((langData) => {
- const { lang, percent, color: langColor } = langData;
- const percentNum = parseFloat(percent);
- const color = langColor || (colorMap[lang] && colorMap[lang].color) || "#ccc";
- const segment = document.createElement("div");
- segment.style.backgroundColor = color;
- segment.style.width = `${percentNum}%`;
- segment.style.height = "100%";
- barContainer.appendChild(segment);
- const legendItem = document.createElement("div");
- legendItem.style.display = "flex";
- legendItem.style.alignItems = "center";
- legendItem.style.marginRight = "16px";
- legendItem.style.marginBottom = "4px";
- const colorDot = document.createElement("span");
- colorDot.style.display = "inline-block";
- colorDot.style.width = "8px";
- colorDot.style.height = "8px";
- colorDot.style.backgroundColor = color;
- colorDot.style.borderRadius = "50%";
- colorDot.style.marginRight = "6px";
- const langNameSpan = document.createElement("span");
- langNameSpan.textContent = lang;
- langNameSpan.style.fontWeight = "600";
- const percentSpan = document.createElement("span");
- percentSpan.textContent = ` ${percent}%`;
- percentSpan.style.fontWeight = "400";
- const langName = document.createElement("span");
- langName.appendChild(langNameSpan);
- langName.appendChild(percentSpan);
- legendItem.appendChild(colorDot);
- legendItem.appendChild(langName);
- legendContainer.appendChild(legendItem);
- });
- container.appendChild(barContainer);
- container.appendChild(legendContainer);
- 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 userContainer = document.querySelector('.vcard-names-container');
- const orgContainer = document.querySelector('.h2.lh-condensed')?.closest('.flex-1.d-flex.flex-column');
- const container = userContainer || orgContainer;
- if (!container) return;
- const isOrg = !userContainer;
- if (container.querySelector('#gh-lang-stats')) 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);
- if (!GITHUB_TOKEN) {
- loadingEl.textContent = "GitHub API token required for language statistics";
- const tokenNotice = document.createElement("div");
- tokenNotice.style.fontSize = "12px";
- tokenNotice.style.color = "#666";
- tokenNotice.style.marginTop = "4px";
- tokenNotice.innerHTML = "Set token with <code>window.setGitHubToken('your_token')</code> in console";
- loadingEl.appendChild(tokenNotice);
- return;
- }
- const [langs, colors] = await Promise.all([
- fetchLanguagesGraphQL(username, isOrg),
- getLanguageColors()
- ]);
- const loadingIndicator = document.getElementById("lang-stats-loading");
- if (loadingIndicator) loadingIndicator.remove();
- if (langs.length === 0) {
- return;
- }
- const statsWrapper = document.createElement("div");
- statsWrapper.id = "gh-lang-stats";
- const topLangs = langs.slice(0, 10);
- const langBar = createLanguageBar(topLangs, colors);
- statsWrapper.appendChild(langBar);
- 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);
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址