Shows the creation date of GitHub repositories on the repository page metadata sidebar.
// ==UserScript==
// @name github-creation-date
// @namespace https://github.com/farhandigital/github-creation-date
// @version 1.0.0
// @author Farhan Digital
// @description Shows the creation date of GitHub repositories on the repository page metadata sidebar.
// @license Zlib
// @icon https://cdn.simpleicons.org/github
// @match https://github.com/*
// @connect ungh.cc
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
var _GM_getValue = typeof GM_getValue != "undefined" ? GM_getValue : void 0;
var _GM_setValue = typeof GM_setValue != "undefined" ? GM_setValue : void 0;
var _GM_xmlhttpRequest = typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0;
var CACHE_PREFIX = "creationDate:";
function getCached(key) {
return _GM_getValue(CACHE_PREFIX + key, null) ?? null;
}
function setCached(key, value) {
_GM_setValue(CACHE_PREFIX + key, value);
}
var logPrefix = "[github-creation-date]";
function log(...args) {
console.log(logPrefix, ...args);
}
function isUnghRepoResponse(data) {
return data !== null && typeof data === "object" && "repo" in data && typeof data.repo === "object" && typeof data.repo.createdAt === "string";
}
var INITIAL_BACKOFF_MS = 1e3;
var failedKeys = new Map();
function recordFailure(cacheKey) {
const prev = failedKeys.get(cacheKey);
const delay = prev ? prev.delay * 2 : INITIAL_BACKOFF_MS;
failedKeys.set(cacheKey, {
failedAt: Date.now(),
delay
});
log(`Backoff for ${cacheKey}: next retry in ${delay / 1e3}s`);
}
async function getCreationDate(username, repo) {
const cacheKey = `${username}/${repo}`;
const cached = getCached(cacheKey);
if (cached) {
log(`cache found: ${cacheKey}: ${cached}`);
return cached;
}
const failure = failedKeys.get(cacheKey);
if (failure !== void 0 && Date.now() - failure.failedAt < failure.delay) {
log(`Skipping previously failed key (backoff active): ${cacheKey}`);
return "Unknown";
}
const apiUrl = `https://ungh.cc/repos/${username}/${repo}`;
log("fetching API...", apiUrl);
return new Promise((resolve) => {
_GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
onload: (response) => {
if (response.status !== 200) {
console.error("GitHub API error:", response.statusText);
recordFailure(cacheKey);
resolve("Unknown");
return;
}
try {
const data = JSON.parse(response.responseText);
if (isUnghRepoResponse(data)) {
setCached(cacheKey, data.repo.createdAt);
resolve(data.repo.createdAt);
} else {
console.error("Invalid response data:", data);
recordFailure(cacheKey);
resolve("Unknown");
}
} catch (error) {
console.error("Error parsing response:", error);
recordFailure(cacheKey);
resolve("Unknown");
}
},
onerror: (error) => {
console.error("Network error:", error);
recordFailure(cacheKey);
resolve("Unknown");
}
});
});
}
var formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "short",
day: "numeric"
});
function formatDate(date) {
return formatter.format(date);
}
var NS = `ghcd_${Math.random().toString(36).slice(2)}`;
function getTargetElement() {
const aboutHeadings = document.querySelectorAll(".BorderGrid-cell h2");
let parentContainer = null;
for (const heading of aboutHeadings) if (heading.textContent?.trim().toLowerCase() === "about") {
parentContainer = heading.parentElement;
break;
}
if (!parentContainer) return;
const readmeElement = parentContainer.querySelector("a[href='#readme-ov-file']");
if (readmeElement) return readmeElement.parentElement;
const activityElement = parentContainer.querySelector("a[href*='/activity']");
if (activityElement) return activityElement.parentElement;
}
function createCreationDateElement(creationDate) {
const container = document.createElement("div");
container.classList.add("mt-2");
container.id = NS;
const creationDateElement = document.createElement("a");
creationDateElement.classList.add("Link--muted");
creationDateElement.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2 tmp-mr-2"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg> ${formatDate(new Date(creationDate))}`;
container.appendChild(creationDateElement);
return container;
}
function isAlreadyInjected() {
return document.getElementById(NS) !== null;
}
function injectCreationDate(creationDate) {
const targetElement = getTargetElement();
if (targetElement) {
const creationDateElement = createCreationDateElement(creationDate);
targetElement.insertAdjacentElement("afterend", creationDateElement);
return;
}
log("Target element not found", targetElement);
}
function isGithubRepoPathname(pathname) {
const notUsername = new Set([
"settings",
"topics",
"organizations"
]);
const pathParts = pathname.split("/");
const pathLength = pathParts.length;
const username = pathParts[1];
return !notUsername.has(username) && pathLength === 3;
}
function extractUsernameAndRepo(pathname) {
if (!isGithubRepoPathname(pathname)) return null;
const parts = pathname.split("/");
return {
username: parts[1],
repo: parts[2]
};
}
var isRunning = false;
async function main() {
if (isRunning) {
log("Already running. Skipping.");
return;
}
log("Starting orchestrator...");
isRunning = true;
try {
if (window.location.hostname !== "github.com") return;
const pathname = window.location.pathname;
if (!isGithubRepoPathname(pathname)) {
log("Not a github repo pathname, skipping...", pathname);
return;
}
if (isAlreadyInjected()) {
log("Creation date element already exists. Skipping injection.");
return;
}
const repoInfo = extractUsernameAndRepo(pathname);
if (!repoInfo) {
log("Failed to extract username and repo, skipping...", pathname);
return;
}
const { username, repo } = repoInfo;
const creationDate = await getCreationDate(username, repo);
injectCreationDate(creationDate);
log(`Repository ${username}/${repo} was created on: ${creationDate}`);
} finally {
isRunning = false;
}
}
setInterval(main, 300);
})();