Created
May 15, 2026 11:30
-
-
Save jakeayy/10496df397b05f433f8a81d36c78528b to your computer and use it in GitHub Desktop.
Block clankers using list from clankers-leaderboard.pages.dev
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==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