Skip to content

Instantly share code, notes, and snippets.

@mbtolou
Last active April 20, 2023 03:56
Show Gist options
  • Save mbtolou/7d917068108091e1682707f1c479b9a0 to your computer and use it in GitHub Desktop.
Save mbtolou/7d917068108091e1682707f1c479b9a0 to your computer and use it in GitHub Desktop.
GitHub Stars-Fork Count
// ==UserScript==
// @name GitHub Stars-Fork Count
// @version 1.110
// @description A userscript that add stars to github link
// @license MIT
// @author Mohamad Tolou
// @namespace https://github.com/mbtolou
// @include https://github.com/*
// @include https://stackoverflow.com/*
// @run-at document-end
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM.xmlHttpRequest
// @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js
// @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=666427
// @icon https://github.githubassets.com/pinned-octocat.svg
// @updateURL https://gist.github.com/mbtolou/7d917068108091e1682707f1c479b9a0#file-github_stars-user-js
// @downloadURL https://gist.github.com/mbtolou/7d917068108091e1682707f1c479b9a0#file-github_stars-user-js
// ==/UserScript==
let busy = false;
const badgesAdded = [];
const reservedUsername = [
"topics",
"search",
"ghost",
"pulls",
"issues",
"marketplace",
"explore",
"discover",
"notifications",
"new",
"organizations",
"settings",
"site",
"about",
"contact",
"pricing",
"apps",
"features",
"password_reset",
"trending"
];
const allBadgeClasses = [
"added-stars-badge",
"added-last-commit-badge",
"added-followers-badge",
];
class URLParseResult {
constructor({ user, repo }) {
this.user = user;
this.repo = repo;
}
equals(other) {
return other.repo === this.repo && other.user === this.user;
}
}
URLParseResult.EMPTY = new URLParseResult({});
function parseURL(v) {
const match = v.match(/^https?:\/\/github.com\/([^/]*?)(?:\/([^/]*?))?(?:\.git)?(?:[#?].*)?(?:$|\/)/);
if (!match ) {
return URLParseResult.EMPTY;
}
if (reservedUsername.includes(match[1])) {
return URLParseResult.EMPTY;
}
return new URLParseResult({
user: match[1],
repo: match[2],
});
}
async function appendBadge(el, className, url) {
if (el.classList.contains(className)) {
return;
}
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: "GET",
url: url,
onload: resp => {
if (resp.status === 200) {
if (!el.classList.contains(className)) {
const img = document.createElement("img");
img.src = `data:image/svg+xml;base64,${btoa(resp.response)}`;
const containerClassNames = [
"natescarlet-gmail-com",
"badge-container",
];
const selector = containerClassNames.map(i => "." + i).join("");
/** @type {HTMLElement} */
const container = el.querySelector(selector) || document.createElement("span");
container.style="vertical-align: middle;";
el.appendChild(container);
container.classList.add(...containerClassNames);
container.append(img);
img.style.order = allBadgeClasses.indexOf(className).toString();
container.style.display = "inline-flex; ";
el.classList.add(className);
}
resolve();
}
reject(`${resp.status}: ${url}`);
},
onerror: reject,
});
});
}
async function appendStarsBadge(el) {
const { repo, user } = parseURL(el.href);
if (!(user && repo)) {
return;
}
await appendBadge(el, "added-stars-badge", `https://img.shields.io/github/stars/${user}/${repo}.svg?style=social`);
}
async function appendLastCommitBadge(el) {
const { repo, user } = parseURL(el.href);
if (!(user && repo)) {
return;
}
await appendBadge(el, "added-last-commit-badge", `https://img.shields.io/github/last-commit/${user}/${repo}.svg`);
}
async function appendFollowersBadge(el) {
const { user } = parseURL(el.href);
if (!user) {
return;
}
await appendBadge(el, "added-followers-badge", `https://img.shields.io/github/followers/${user}.svg?style=social`);
}
function getAbsoluteLink(a) {
let currentUrl = document.URL;
var url = a.getAttribute('href');
// console.log(url);
if (!currentUrl.toLocaleLowerCase().startsWith("https://github.com")) {
return (a.getAttribute('href') || '').toLowerCase().trim();
}
else {
if (url.indexOf('http://') === -1 && url.indexOf('https://') === -1) {
url = "https://github.com" + url;
}
// console.log(url);
const match = a.href.match(/^\s*https:\/\/github.com\/([^/#]+)\/([^/#]+)(?:[\/#].*)?$/i);
if (match) {
return url;
}
else return '';
// url.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#]/)[0];
}
}
(async function () {
async function processStars(tempFormat) {
if (busy) {
return;
}
busy = true;
let selector = "a";
if ($(selector)) {
let indx = 0;
const els = $$(selector),
len = els.length;
// loop with delay to allow user interaction
const loop = () => {
let currentUrl = document.location.href.toLowerCase()
let el, time, node, formatted,
// max number of DOM insertions per loop
max = 0;
while (max < 20 && indx < len) {
if (indx >= len) {
return;
}
el = els[indx];
let absLink = getAbsoluteLink(el);
//console.log(absLink);
const match = absLink.match(/^\s*https:\/\/github.com\/([^/#]+)\/([^/#]+)(?:[\/#].*)?$/i);
if (match) {
const userName = match[1];
const repoName = match[2];
const url = `https://github.com/${userName.toLowerCase()}/${repoName.toLowerCase()}`;
if (currentUrl.startsWith(url)) {
// console.log("Do not replace badges on the repo page itself");
indx++;
continue;
}
// Exclude Github's own pages
if (reservedUsername.includes(userName)) {
// console.log("Exclude Github's own pages");
indx++;
continue;
}
// Only add each badge once
if (badgesAdded.some(badge => badge.url === url)) {
// console.log("Only add each badge once");
indx++;
continue;
}
badgesAdded.push({ url, userName, repoName, el: el });
try {
Promise.all([
appendStarsBadge(el),
appendLastCommitBadge(el),
appendFollowersBadge(el),
]);
}
catch (err) {
console.error(err);
}
}
indx++;
}
if (indx < len) {
setTimeout(() => {
loop();
}, 200);
}
};
loop();
}
busy = false;
}
function $(str, el) {
return (el || document).querySelector(str);
}
function $$(str, el) {
return Array.from((el || document).querySelectorAll(str));
}
function on(el, name, handler) {
$(el).addEventListener(name, handler);
}
function init() {
//addPanel();
//moment.locale(locale);
processStars();
}
// repo file list needs additional time to render
document.addEventListener("ghmo:container", () => {
setTimeout(() => {
processStars();
}, 100);
});
document.addEventListener("ghmo:preview", processStars);
//console.log("start started");
init();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment