Automatically clicks "Load more" on GitHub discussion pages until all comments are loaded
// ==UserScript==
// @name GitHub Discussion Load-More Auto-Loader
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Automatically clicks "Load more" on GitHub discussion pages until all comments are loaded
// @author You
// @license MIT
// @match https://github.com/orgs/*/discussions/*
// @match https://github.com/*/*/discussions/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
let running = false;
let intervalId = null;
let clickCount = 0;
const CLICK_DELAY_MS = 1200;
// --- Find the "Load more" button ---
function findLoadMoreButton() {
// Primary: ajax-pagination-btn class (matches the HTML you provided)
const ajaxBtn = document.querySelector('.ajax-pagination-btn');
if (ajaxBtn && ajaxBtn.offsetParent !== null) return ajaxBtn;
// Fallback: any button/link containing "Load more" text
const allButtons = document.querySelectorAll('button, a');
for (const el of allButtons) {
if (/load more/i.test(el.textContent) && el.offsetParent !== null) {
return el;
}
}
return null;
}
// --- Status text helpers ---
function statusText() {
return running ? `Running… (${clickCount} clicks)` : (clickCount > 0 ? `Done — ${clickCount} clicks` : 'Idle');
}
// --- Core loop ---
function tick() {
const btn = findLoadMoreButton();
if (btn) {
btn.click();
clickCount++;
updateUI();
} else {
// No button found — all loaded
stop(true);
}
}
function start() {
if (running) return;
running = true;
clickCount = 0;
updateUI();
// Click once immediately, then repeat
tick();
intervalId = setInterval(tick, CLICK_DELAY_MS);
}
function stop(auto = false) {
if (!running && !auto) return;
running = false;
clearInterval(intervalId);
intervalId = null;
updateUI();
if (auto) {
label.textContent = `✓ All loaded (${clickCount} clicks)`;
}
}
// --- Build floating UI ---
const panel = document.createElement('div');
panel.style.cssText = `
position: fixed;
bottom: 24px;
right: 24px;
z-index: 99999;
background: #161b22;
border: 1px solid #30363d;
border-radius: 10px;
padding: 12px 16px;
display: flex;
flex-direction: column;
gap: 8px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 13px;
color: #e6edf3;
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
min-width: 200px;
user-select: none;
`;
const title = document.createElement('div');
title.textContent = '⚡ Discussion Auto-Loader';
title.style.cssText = 'font-weight: 600; font-size: 13px; color: #58a6ff;';
const label = document.createElement('div');
label.style.cssText = 'color: #8b949e; font-size: 12px;';
label.textContent = statusText();
const btnRow = document.createElement('div');
btnRow.style.cssText = 'display: flex; gap: 8px;';
const startBtn = document.createElement('button');
startBtn.textContent = '▶ Load All';
startBtn.style.cssText = `
flex: 1;
padding: 6px 0;
border-radius: 6px;
border: 1px solid #238636;
background: #238636;
color: #fff;
font-size: 12px;
font-weight: 500;
cursor: pointer;
`;
const stopBtn = document.createElement('button');
stopBtn.textContent = '⏹ Stop';
stopBtn.style.cssText = `
flex: 1;
padding: 6px 0;
border-radius: 6px;
border: 1px solid #30363d;
background: transparent;
color: #8b949e;
font-size: 12px;
font-weight: 500;
cursor: pointer;
`;
const delayRow = document.createElement('div');
delayRow.style.cssText = 'display: flex; align-items: center; gap: 8px; color: #8b949e;';
const delayLabel = document.createElement('label');
delayLabel.textContent = 'Delay (ms):';
delayLabel.style.fontSize = '12px';
const delayInput = document.createElement('input');
delayInput.type = 'number';
delayInput.value = CLICK_DELAY_MS;
delayInput.min = 300;
delayInput.max = 10000;
delayInput.step = 100;
delayInput.style.cssText = `
width: 70px;
background: #0d1117;
border: 1px solid #30363d;
border-radius: 6px;
color: #e6edf3;
padding: 3px 6px;
font-size: 12px;
`;
// Drag support
let dragging = false, ox = 0, oy = 0;
title.style.cursor = 'grab';
title.addEventListener('mousedown', e => {
dragging = true;
const r = panel.getBoundingClientRect();
ox = e.clientX - r.left;
oy = e.clientY - r.top;
title.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', e => {
if (!dragging) return;
panel.style.left = (e.clientX - ox) + 'px';
panel.style.top = (e.clientY - oy) + 'px';
panel.style.right = 'auto';
panel.style.bottom = 'auto';
});
document.addEventListener('mouseup', () => {
dragging = false;
title.style.cursor = 'grab';
});
function updateUI() {
label.textContent = statusText();
startBtn.disabled = running;
startBtn.style.opacity = running ? '0.5' : '1';
stopBtn.style.color = running ? '#e6edf3' : '#8b949e';
stopBtn.style.borderColor = running ? '#f85149' : '#30363d';
}
startBtn.addEventListener('click', () => {
const delay = parseInt(delayInput.value, 10);
if (delay >= 300) {
// Update delay dynamically
clearInterval(intervalId);
running = false;
// restart with new delay via closure trick
Object.defineProperty(window, '_gmDelay', { value: delay, writable: true });
}
clickCount = 0;
start._delay = parseInt(delayInput.value, 10) || CLICK_DELAY_MS;
startWithDelay(parseInt(delayInput.value, 10) || CLICK_DELAY_MS);
});
stopBtn.addEventListener('click', () => stop());
function startWithDelay(delay) {
if (running) return;
running = true;
clickCount = 0;
updateUI();
tick();
intervalId = setInterval(tick, delay);
}
// Re-wire start to use delay from input
startBtn.removeEventListener('click', start);
delayRow.append(delayLabel, delayInput);
btnRow.append(startBtn, stopBtn);
panel.append(title, label, btnRow, delayRow);
document.body.appendChild(panel);
updateUI();
})();