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).

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==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');
    }
})();