您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork镜像 is available in English.
Use API calls to get the labels of all issues and pull requests from the notification list.
// ==UserScript== // @name Add Labels to GitHub Notifications // @namespace https://greasyforks.org/en/users/668659-denvercoder1 // @match https://github.com/notifications // @grant none // @license MIT // @version 1.0.3 // @author Jonah Lawrence // @description Use API calls to get the labels of all issues and pull requests from the notification list. // ==/UserScript== /* jshint esversion: 11 */ /* * Get more GitHub API requests and enable private repos with a personal access token: * localStorage.setItem("gh_token", "YOUR_TOKEN_HERE"); * * To get a personal access token go to https://github.com/settings/tokens/new * To enable private repos, you will need to enable the repos scope for the token. * * Manually clear cache by running the following in the console: * localStorage.setItem("labels", "{}"); */ (() => { // cached labels let cachedLabels = {}; /** * Convert a hex color to RGB * @param {string} hex - hex color (eg. FFFFFF) * @returns {number[]} [r, g, b] */ function hexToRgb(hex) { const bigint = parseInt(hex, 16); const r = (bigint >> 16) & 255; const g = (bigint >> 8) & 255; const b = bigint & 255; return [r, g, b]; } /** * Convert a hex color to HSL * @param {string} hex - hex color (eg. FFFFFF) * @returns {number[]} [h, s, l] */ function hexToHsl(hex) { let [r, g, b] = hexToRgb(hex); r /= 255; g /= 255; b /= 255; const l = Math.max(r, g, b); const s = l - Math.min(r, g, b); const h = s ? (l === r ? (g - b) / s : l === g ? 2 + (b - r) / s : 4 + (r - g) / s) : 0; return [ 60 * h < 0 ? 60 * h + 360 : 60 * h, 100 * (s ? (l <= 0.5 ? s / (2 * l - s) : s / (2 - (2 * l - s))) : 0), (100 * (2 * l - s)) / 2, ]; } /** * Add labels to the notification list * @param {object[]} labels - array of label objects * @param {HTMLElement} container - parent element to append labels to */ function addLabels(labels, container) { // if there are already labels, do nothing if (container.querySelector(".js-issue-labels")) { return; } // append colored labels to the notification list const labelContainer = document.createElement("div"); labelContainer.className = "js-issue-labels d-flex flex-wrap"; labelContainer.style.marginTop = "10px"; labelContainer.style.maxHeight = "20px"; labels.forEach((label) => { const labelElement = document.createElement("span"); labelElement.className = "IssueLabel hx_IssueLabel width-fit mb-1 mr-1 d-inline-flex"; const [r, g, b] = hexToRgb(label.color); const [h, s, l] = hexToHsl(label.color); labelElement.setAttribute( "style", `--label-r:${r};--label-g:${g};--label-b:${b};--label-h:${h};--label-s:${s};--label-l:${l}; cursor:pointer;` ); labelElement.innerText = label.name; labelElement.addEventListener("click", (e) => { e.stopPropagation(); window.open(label.filterUrl); }); labelContainer.appendChild(labelElement); }); container.appendChild(labelContainer); } /** * Fetch labels from the GitHub API and add them to the cache for an issue or pull request given its url * @param {string} url - url of the issue or pull request * @param {HTMLElement|null} container - parent element to append labels to (optional) */ function fetchLabels(url, container) { const issueRegex = /https:\/\/github.com\/(.*)\/(.*)\/(issues|pull)\/(\d+)/; const match = url.match(issueRegex); if (match) { const [, owner, repo, , number] = match; const apiUrl = `https://api.github.com/repos/${owner}/${repo}/issues/${number}`; const repoIssuesUrl = `https://github.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues`; const headers = { Accept: "application/vnd.github.v3+json", }; const token = localStorage.getItem("gh_token") || ""; if (token) { headers.Authorization = `token ${token}`; } fetch(apiUrl, { headers, }) .then((response) => response.json()) .then((data) => { const labels = data.labels || []; cachedLabels[url] = { date: new Date(), labels: labels.map((label) => ({ name: label.name, color: label.color, filterUrl: `${repoIssuesUrl}?q=is%3Aopen+label%3A"${encodeURIComponent(label.name)}"`, })), }; console.info("fetched", url, cachedLabels[url]); localStorage.setItem("labels", JSON.stringify(cachedLabels)); if (container) { addLabels(cachedLabels[url].labels, container); } }) .catch((error) => console.error(error)); } } /** * Check the notification list for new issues and pull requests and add labels to them */ function run() { const notificationLinks = [ ...document.querySelectorAll(".notification-list-item-link:not(.added-notifications)"), ]; if (notificationLinks.length === 0) { return; } notificationLinks.forEach((a) => { a.classList.add("added-notifications"); const url = a.href; const container = a.parentElement; // use cached labels if they exist and the notification last update is older than the fetch date of the labels const updatedDate = container.parentElement.querySelector("relative-time")?.getAttribute("datetime"); if (cachedLabels[url] && new Date(updatedDate) < new Date(cachedLabels[url].date)) { console.info("cached", url, cachedLabels[url]); addLabels(cachedLabels[url].labels || [], container); return; } // otherwise fetch the labels from the GitHub API fetchLabels(url, container); }); } function init() { // clear cache older than 6 hours cachedLabels = JSON.parse(localStorage.getItem("labels") || "{}"); Object.keys(cachedLabels).forEach((url) => { const { date } = cachedLabels[url]; if (new Date() - new Date(date) > 1000 * 60 * 60 * 6) { delete cachedLabels[url]; } }); localStorage.setItem("labels", JSON.stringify(cachedLabels)); // run every 500ms setInterval(run, 500); } // run init when the page loads or if it has already loaded if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();