AO3: Load All Comments

Automatically fetches and appends all pages of comments on AO3 — et carthago delenda est. Matches are AO3 and its TLDs that are not redirects directly to .org (no point in matching the TLDs that are simply redirects — cf. https://archiveofourown.org/faq/accessing-fanworks#archiveurl).

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         AO3: Load All Comments
// @namespace   https://greasyfork.org/en/users/1555174-charles-rockafellor
// @version      1.5 on 07 May 2026
// @description  Automatically fetches and appends all pages of comments on AO3 — et carthago delenda est. Matches are AO3 and its TLDs that are not redirects directly to .org (no point in matching the TLDs that are simply redirects — cf. https://archiveofourown.org/faq/accessing-fanworks#archiveurl).
// @author      Charles Rockafellor
// @homepageURL https://archiveofourown.org/users/Charles_Rockafellor/collections
// @match       https://archiveofourown.org/works/*
// @match       https://archiveofourown.gay/works/*
// @match       https://archive.transformativeworks.org/works/*
// @match       https://insecure.archiveofourown.org/works/*
// @icon         https://i.pinimg.com/1200x/98/af/04/98af04b09aedd599def9750ccf4c5ff9.jpg
// @grant        none
// @run-at        document-end
// @license     MIT; https://opensource.org/license/mit
// @connect     archiveofourown.org
// @history        1.5 — Included [successfully] the truncated threads' expansion without triggering too many requests at once.  If you encounter a "429 Too Many Requests" error, just let the connection cool down for a few seconds or minutes, and then try again.
// @history        1.4 — Trying to retain thread structure, ignoring the thread-truncation (the "[n-many] more comments in this thread" message) for now in order to hopefully avoid the "Too many requests" AO3-limit.
// @history        1.3 — Stripped back selectors to be as broad as possible, removed observer and added immediate console log to check console pane for script having been loaded or not.
// @history        1.2 — Swapped scheme from wildcard to https, added a MutationObserver.
// @history        1.1 — Initial build.
// ==/UserScript==

(function() {
    'use strict';

    const DELAY_MS = 2000; // Increased to 2s for safety with deep expansion

    const checkExist = setInterval(function() {
        const thread = document.querySelector('ol.thread');
        if (thread && !document.querySelector('#load-all-comments-btn')) {
            initScript(thread);
        }
    }, 1000);

    function initScript(thread) {
        const loadAllBtn = document.createElement('button');
        loadAllBtn.id = 'load-all-comments-btn';
        loadAllBtn.innerHTML = "LOAD ALL & EXPAND DEEP THREADS";
        loadAllBtn.style = "display: block; width: 100%; margin: 20px 0; padding: 15px; background: #900; color: #fff; border: 2px solid #333; cursor: pointer; font-weight: bold; font-family: sans-serif;";

        thread.parentNode.insertBefore(loadAllBtn, thread);

        loadAllBtn.addEventListener('click', (e) => {
            e.preventDefault();
            loadAllBtn.disabled = true;
            loadAllBtn.style.background = "#444";
            loadAllBtn.innerHTML = "PHASE 1: MERGING PAGES...";
            fetchNextPage(document);
        });
    }

    function fetchNextPage(doc) {
        const nextLink = doc.querySelector('li.next a');

        if (nextLink) {
            setTimeout(() => {
                fetch(nextLink.href).then(r => r.text()).then(html => {
                    const nextDoc = new DOMParser().parseFromString(html, 'text/html');
                    const remoteThread = nextDoc.querySelector('ol.thread');
                    if (remoteThread) document.querySelector('ol.thread').insertAdjacentHTML('beforeend', remoteThread.innerHTML);
                    fetchNextPage(nextDoc);
                });
            }, DELAY_MS);
        } else {
            console.log("AO3 Script: Starting Phase 2 (Expansion)...");
            expandTruncatedThreads();
        }
    }

    async function expandTruncatedThreads() {
        const btn = document.querySelector('#load-all-comments-btn');
        // Find all <li> that contain a "more comments in this thread" link
        let expansionLinks = Array.from(document.querySelectorAll('ol.thread li.comment a')).filter(a => a.textContent.includes('more comments in this thread'));

        if (expansionLinks.length === 0) {
            finish();
            return;
        }

        btn.innerHTML = `PHASE 2: EXPANDING ${expansionLinks.length} DEEP THREADS...`;

        for (let link of expansionLinks) {
            await new Promise(r => setTimeout(r, DELAY_MS)); // Safety delay
            try {
                const response = await fetch(link.href);
                const html = await response.text();
                const threadDoc = new DOMParser().parseFromString(html, 'text/html');

                // Find the new comments in the fetched thread
                const deepThread = threadDoc.querySelector('ol.thread');
                if (deepThread) {
                    // Replace the "X more comments" <li> with the actual content
                    link.closest('li').insertAdjacentHTML('afterend', deepThread.innerHTML);
                    link.closest('li').remove();
                }
            } catch (err) {
                console.error("Expansion failed for link:", link.href);
            }
        }

        // Check again in case the new content has EVEN DEEPER links
        const remaining = Array.from(document.querySelectorAll('ol.thread li.comment a')).filter(a => a.textContent.includes('more comments in this thread'));
        if (remaining.length > 0) {
            expandTruncatedThreads();
        } else {
            finish();
        }
    }

    function finish() {
        const btn = document.querySelector('#load-all-comments-btn');
        btn.innerHTML = "ALL PAGES LOADED & EXPANDED";
        document.querySelectorAll('ol.pagination').forEach(p => p.style.display = 'none');
    }
})();