// ==UserScript==
// @name GitHub Repo Star Time
// @namespace http://github.com/qbosen/tm-gh-star-time
// @version 0.3
// @description Display the time stared at for GitHub repositories on the repository page.
// @author qbosen
// @match https://github.com/*/*
// @connect api.github.com
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
(function () {
'use strict';
let githubToken = GM_getValue('githubToken', '');
// 注册(不可用)菜单命令,用于设置 Token
GM_registerMenuCommand("Setup GitHub Token", function () {
const token = prompt("Input GitHub Personal Access Token\n(Permission: 'Metadata' repository permissions (read) and 'Starring' user permissions (read)):", githubToken);
if (token !== null) {
GM_setValue('githubToken', token);
githubToken = token;
alert("GitHub Token Saved");
}
});
// 清理 缓存 命令
GM_registerMenuCommand("Clean Cache", function () {
GM_setValue('starredRepos', '{"repos": [], "timestamp": 0}');
alert("Cache Cleaned");
});
if (!githubToken) {
console.warn("请先设置 GitHub Token。");
return; // 没有 Token,不执行后续操作
}
// 检查是否 star 过
function checkStarred(owner, repo, callback) {
// https://docs.github.com/en/rest/activity/starring?apiVersion=2022-11-28#check-if-a-repository-is-starred-by-the-authenticated-user
GM_xmlhttpRequest({
method: "GET",
url: `https://api.github.com/user/starred/${owner}/${repo}`,
headers: {
"Authorization": `token ${githubToken}`,
"Accept": "application/vnd.github.v3+json",
"X-GitHub-Api-Version": "2022-11-28"
},
onload: function (response) {
if (response.status === 204) {
callback(true);
} else if (response.status === 404) {
callback(false);
} else {
console.error(`检查 star 状态失败: ${response.status} ${response.statusText}`);
callback(false);
}
},
onerror: function (error) {
console.error("检查 star 状态错误:", error);
callback(false);
}
});
}
// 获取用户所有star过的仓库
function getAllStarredRepos(username, callback) {
let allRepos = [];
let page = 1;
const perPage = 100; // 每页 100 个
function fetchPage(page) {
GM_xmlhttpRequest({
method: "GET",
url: `https://api.github.com/users/${username}/starred?per_page=${perPage}&page=${page}`,
headers: {
"Authorization": `token ${githubToken}`,
"Accept": "application/vnd.github.star+json",
"X-GitHub-Api-Version": "2022-11-28",
},
onload: function (response) {
if (response.status >= 200 && response.status < 300) {
try {
const repos = JSON.parse(response.responseText);
// 只提取需要的字段
const simplifiedRepos = repos.map(repo => ({
full_name: repo.repo.full_name,
starred_at: repo.starred_at
}));
allRepos = allRepos.concat(simplifiedRepos);
if (repos.length === perPage) {
fetchPage(page + 1);
} else {
callback(allRepos);
}
} catch (error) {
console.error("解析 JSON 失败:", error);
callback(null);
}
} else {
console.error(`获取 star 列表失败: ${response.status} ${response.statusText}`);
callback(null);
}
},
onerror: function (error) {
console.error("获取 star 列表错误:", error);
callback(null);
}
});
}
fetchPage(page);
}
const pathParts = window.location.pathname.split('/');
if (pathParts.length < 3) return;
const owner = pathParts[1];
const repo = pathParts[2];
const username = document.querySelector('meta[name="user-login"]').content;
checkStarred(owner, repo, function (isStarred) {
if (isStarred) {
let cachedStarredRepos = JSON.parse(GM_getValue('starredRepos', '{"repos": [], "timestamp": 0}'));
const now = Date.now();
const oneDay = 24 * 60 * 60 * 1000;
if (now - cachedStarredRepos.timestamp > oneDay || cachedStarredRepos.repos.length === 0) { // 修改判断条件
getAllStarredRepos(username, function (repos) {
if (repos) {
GM_setValue('starredRepos', JSON.stringify({ repos: repos, timestamp: now }));
cachedStarredRepos = { repos: repos, timestamp: now };
displayStarTime(cachedStarredRepos, owner, repo);
}
});
} else {
displayStarTime(cachedStarredRepos, owner, repo);
}
}
});
function displayStarTime(cachedStarredRepos, owner, repo) {
const fullName = `${owner}/${repo}`;
const starredRepo = cachedStarredRepos.repos.find(r => r.full_name === fullName);
if (starredRepo) {
const starredAt = new Date(starredRepo.starred_at);
const timeString = starredAt.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false // 使用 24 小时制
})
const detailsElement = document.querySelector('.BorderGrid .BorderGrid-cell');
if (detailsElement) {
const timeElement = document.createElement('div');
timeElement.textContent = `Starred at ${timeString}`;
detailsElement.appendChild(timeElement);
} else {
console.log(`star time: ${timeString}`);
}
}
}
})();