Skip to content

Instantly share code, notes, and snippets.

@jakeayy
Created May 15, 2026 11:30
Show Gist options
  • Select an option

  • Save jakeayy/10496df397b05f433f8a81d36c78528b to your computer and use it in GitHub Desktop.

Select an option

Save jakeayy/10496df397b05f433f8a81d36c78528b to your computer and use it in GitHub Desktop.
Block clankers using list from clankers-leaderboard.pages.dev
// ==UserScript==
// @name Block Clankers
// @namespace block-clankers.jakeayy
// @version 2026-05-15
// @description block clankers on github easily
// @author jakeayy
// @match https://clankers-leaderboard.pages.dev/
// @run-at document-idle
// ==/UserScript==
(()=>{
'use strict';
const TOKEN_KEY = "github-block-token"
let token = localStorage.getItem(TOKEN_KEY)
/** @type {Set<string> | null} */
let blocked = null
const addStyle = (()=>{
const el = document.createElement("style")
document.head.appendChild(el)
/**
* Adds style to DOM
* @param {string} style
* @returns {string} Current style
*/
return (style) => el.textContent += `${style}\n`
})()
/**
* Send request
* @param {string} url
* @param {RequestInit} settings
* @returns {Promise<Response>}
*/
async function req(url, settings = {}) {
const r = await fetch(url, settings)
if (!r.ok)
throw new Error(`error at ${settings.method ?? "GET"} ${url} -> ${await r.text()}`)
return r
}
/**
* Send request to Github API
* @param {string} endpoint
* @param {Omit<RequestInit, "headers">} settings
* @returns {Promise<Response>}
*/
const gh = (endpoint, settings = {}) =>
req(`https://api.github.com/${endpoint}`, {
...settings,
headers: {
"accept": "application/vnd.github+json",
"authorization": `Bearer ${token}`,
"x-github-api-version": "2026-03-10"
}
})
/**
* Get list of blocked users
* @param {Object} settings List settings
* @returns {Promise<Object>} Info about blocks
*/
const getBlockeds = (settings = {}) => gh(`user/blocks?${new URLSearchParams(settings)}`)
.then(r => r.json())
/**
* Blocks user
* @param {string} username
* @returns {Promise<Response>}
*/
const block = (username) => gh(`user/blocks/${username}`, { method: "PUT" })
/**
* Gets list of clankers
* @returns {Promise<Object[]>} Info about clankers
*/
const getClankers = () => req("https://raw.githubusercontent.com/UnsafeLabs/Bounty-Hunters/refs/heads/main/clankers.json")
.then(r => r.json())
function ensureToken() {
if (token) return
do {
token = prompt("Enter GitHub Token that has read and write access to blocks or `user` scope:")?.trim()
if (token === null) throw new Error("cancelled")
} while (!token)
localStorage.setItem(TOKEN_KEY, token)
}
/** Ensures blocked users are set before continuing */
async function ensureBlockedSet() {
if (blocked) return;
const PER_PAGE = 100
let page = 1
const fullList = []
while (true) {
const list = await getBlockeds({
per_page: PER_PAGE,
page: page++
})
fullList.push(...list.map(o => o.login))
if (list.length < PER_PAGE) break;
}
blocked = new Set(fullList)
}
const startBtn = (()=>{
const btn = document.createElement("button")
btn.textContent = "Block all"
btn.classList.add("banner-btn")
btn.addEventListener("click", startBlocking)
return btn
})()
const resetTokenBtn = (()=>{
const btn = document.createElement("button")
btn.textContent = "Reset Auth Token"
btn.classList.add("banner-btn")
btn.addEventListener("click", () => {
if (!confirm("This button is for resetting auth token if you provided incorrect one. Don't use it if you don't have to! Are you sure?")) return
btn.textContent = "Resetting..."
btn.setAttribute("disabled", true)
localStorage.removeItem(TOKEN_KEY)
location.reload()
})
return btn
})()
/** Handler for blocking spam */
async function startBlocking() {
startBtn.setAttribute("disabled", true)
startBtn.textContent = "Preparing..."
try { ensureToken() }
catch {
startBtn.removeAttribute("disabled")
return
}
await ensureBlockedSet()
const toBlock = (await getClankers())
.filter(o => !blocked.has(o.username))
.map(o => o.username)
startBtn.textContent = `Blocking ${toBlock.length} clankers...`
for (const username of toBlock) {
try {
await block(username)
blocked.add(username)
startBtn.textContent = `Blocked "${username}"`
}
catch(e) {
console.error(`couldnt block "${username}"`, e)
startBtn.textContent = `Couldn't block "${username}"`
}
}
startBtn.textContent = `Blocked ${toBlock.length} clankers!`
startBtn.removeAttribute("disabled")
}
/** Injects start button to DOM */
function injectButton() {
const anchor = document.querySelector(".table-header > h2")
if (!anchor) throw new Error("no anchor found")
const container = anchor.parentElement
if (!container) throw new Error("no container found")
const newContainer = document.createElement("div")
newContainer.classList.add("clanker-block-nav")
container.prepend(newContainer)
addStyle(".clanker-block-nav { display: flex; align-items: center; gap: 5px; }")
newContainer.append(anchor, startBtn, resetTokenBtn)
}
injectButton()
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment