// ==UserScript== // @name Display Repo Size for GitHub // @version 1.0.0 // @description Displays the total repository size on GitHub repository pages (supports public & private repos via GH_PAT). // @author OpenScript // @match https://github.com/*/* // @grant none // ==/UserScript== (function() { 'use strict'; const cache = new Map(); const BADGE_ID = 'openscript-repo-size'; // Format KB to readable size const formatBytes = kb => { if (kb < 1024) return `${kb} KB`; if (kb < 1024 * 1024) return `${(kb / 1024).toFixed(1)} MB`; return `${(kb / (1024 * 1024)).toFixed(2)} GB`; }; // Parse owner & repo from path const getRepoInfo = () => { const [, owner, repo] = location.pathname.split('/'); const reserved = new Set(['settings', 'orgs', 'organizations', 'notifications', 'search', 'features', 'pricing', 'explore']); return (owner && repo && !reserved.has(owner)) ? { owner, repo } : null; }; // Fetch size from GitHub API with optional GH_PAT const fetchRepoSize = async (owner, repo) => { const key = `${owner}/${repo}`; if (cache.has(key)) return cache.get(key); const token = window.OpenScript?.env?.GH_PAT || window.env?.GH_PAT; const headers = { Accept: 'application/vnd.github.v3+json' }; if (token) headers.Authorization = `Bearer ${token}`; try { const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`, { headers }); if (!res.ok) return res.status === 404 ? 'private/missing GH_PAT' : 'error'; const data = await res.json(); const formatted = formatBytes(data.size); cache.set(key, formatted); return formatted; } catch { return null; } }; // Inject or update size badge const updateBadge = async () => { const info = getRepoInfo(); if (!info) return; // Anchor locations on GitHub repo pages const anchor = document.querySelector('.file-navigation') || document.querySelector('[data-testid="latest-commit-details"]') || document.querySelector('.BorderGrid-cell .d-flex') || document.querySelector('#repository-container-header ul'); if (!anchor || document.getElementById(BADGE_ID)) return; const badge = document.createElement('div'); badge.id = BADGE_ID; badge.className = 'd-inline-flex flex-items-center mr-2 px-2 py-1 text-bold text-small rounded-2 border color-border-default color-bg-subtle'; badge.style.cssText = 'align-self: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;'; badge.innerHTML = ` calculating... `; anchor.prepend(badge); const size = await fetchRepoSize(info.owner, info.repo); const sizeSpan = badge.querySelector('.size-text'); if (sizeSpan) sizeSpan.textContent = size ? size : 'unknown'; }; // Re-run on Turbo navigation & DOM mutations ['turbo:load', 'turbo:render', 'pjax:end', 'popstate'].forEach(ev => window.addEventListener(ev, () => { document.getElementById(BADGE_ID)?.remove(); updateBadge(); }) ); const observer = new MutationObserver(() => { if (!document.getElementById(BADGE_ID)) updateBadge(); }); observer.observe(document.body, { childList: true, subtree: true }); updateBadge(); })();