Old Reddit AutoTools

Consolidated: Auto-Scroll, Auto-Collapse, Unblur, Over18 Continue, Thumbnail Auto-Expand/Collapse, Moderator Comment Collapse, Infinite Scroll with Page Numbers + Image Embedder

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Old Reddit AutoTools
// @namespace    http://tampermonkey.net/
// @version      1.3.6
// @description  Consolidated: Auto-Scroll, Auto-Collapse, Unblur, Over18 Continue, Thumbnail Auto-Expand/Collapse, Moderator Comment Collapse, Infinite Scroll with Page Numbers + Image Embedder
// @author       Crates
// @license      MIT
// @match        https://old.reddit.com/*
// @match        https://www.reddit.com/*
// @match        http://old.reddit.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==
(function() {
    'use strict';

    // Track state for infinite scroll
    let isLoading = false;
    let currentPage = 1;
    let postCounter = 0;
    let prefetchedPage = null;
    let prefetchedDoc = null;

    // ========================================
    // UTILITY: Throttle function
    // ========================================
    function throttle(func, limit) {
        let inThrottle;
        return function(...args) {
            if (!inThrottle) {
                func.apply(this, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        };
    }

    // ========================================
    // 1. OVER18 AUTO-CONTINUE
    // ========================================
    function clickContinue() {
        // Method 1: By name attribute (original)
        const buttonsByName = document.getElementsByName('over18');
        for (let button of buttonsByName) {
            if (button.value === "yes") {
                button.click();
                return true;
            }
        }

        // Method 2: Direct button selector with value
        const continueBtn = document.querySelector('button[name="over18"][value="yes"]');
        if (continueBtn) {
            continueBtn.click();
            return true;
        }

        // Method 3: By class (for new Reddit style)
        const primaryBtn = document.querySelector('.c-btn-primary[name="over18"]');
        if (primaryBtn) {
            primaryBtn.click();
            return true;
        }

        // Method 4: Input element (old style)
        const inputBtn = document.querySelector('input[name="over18"][value="yes"]');
        if (inputBtn) {
            inputBtn.click();
            return true;
        }

        // Method 5: Any button/input with "continue" text on over18 pages
        if (window.location.href.includes('over18')) {
            const allButtons = document.querySelectorAll('button[type="submit"], input[type="submit"]');
            for (let btn of allButtons) {
                const text = (btn.textContent || btn.value || '').toLowerCase();
                if (text.includes('continue') || text.includes('yes')) {
                    btn.click();
                    return true;
                }
            }
        }

        return false;
    }

    // ========================================
    // 2. AUTO-COLLAPSE MODERATOR COMMENTS
    // ========================================
    function collapseModeratorComments() {
        document.querySelectorAll('.thing.comment.stickied:not(.collapsed):not([data-auto-collapsed]), .thing.comment:has(.tagline .moderator):not(.collapsed):not([data-auto-collapsed]), .thing.comment:has(.tagline .admin):not(.collapsed):not([data-auto-collapsed])').forEach(comment => {
            const expandBtn = comment.querySelector(':scope > .entry .expand');
            if (expandBtn) {
                comment.setAttribute('data-auto-collapsed', 'true');
                expandBtn.click();
            }
        });
    }

    // ========================================
    // 3. AUTO-EXPAND POST ON COMMENT PAGES
    // ========================================
    function autoExpandPost() {
        if (!document.querySelector('.commentarea')) return;
        const expando = document.querySelector('.thing.link .expando-button.collapsed');
        if (expando) expando.click();
    }

    // ========================================
    // 4. CONSTRAIN OVERSIZED EMBEDS (portrait videos, etc.)
    // ========================================
    function constrainEmbeds() {
        document.querySelectorAll('.expando iframe:not([data-resized])').forEach(iframe => {
            const w = parseInt(iframe.getAttribute('width'));
            const h = parseInt(iframe.getAttribute('height'));
            if (!w || !h) return;

            iframe.setAttribute('data-resized', 'true');

            const maxH = window.innerHeight * 0.65;
            if (h > maxH) {
                const scale = maxH / h;
                iframe.setAttribute('width', Math.round(w * scale));
                iframe.setAttribute('height', Math.round(maxH));
            }
        });
    }

    // ========================================
    // 4.1. NEW: EMBED PLACEHOLDER IMAGES (<image>)
    // ========================================
    function embedPlaceholderImages() {
        const links = document.querySelectorAll('a');
        links.forEach(link => {
            if ((link.textContent.trim() === '<image>' || link.innerText.trim() === '<image>') && !link.dataset.processed) {
                link.dataset.processed = "true";
                const imageUrl = link.href;
                const img = document.createElement('img');
                img.src = imageUrl;

                // Reasonable Size Styling
                img.style.maxWidth = '600px';
                img.style.maxHeight = '500px';
                img.style.width = 'auto';
                img.style.height = 'auto';
                img.style.display = 'block';
                img.style.marginTop = '8px';
                img.style.marginBottom = '8px';
                img.style.borderRadius = '3px';
                img.style.border = '1px solid #ccc';
                img.style.cursor = 'zoom-in';

                // Click to toggle full size
                img.onclick = (e) => {
                    e.preventDefault();
                    if (img.style.maxWidth === '600px') {
                        img.style.maxWidth = '100%';
                        img.style.maxHeight = 'none';
                        img.style.cursor = 'zoom-out';
                    } else {
                        img.style.maxWidth = '600px';
                        img.style.maxHeight = '500px';
                        img.style.cursor = 'zoom-in';
                    }
                };

                link.textContent = '';
                link.appendChild(img);
            }
        });
    }

    // ========================================
    // 5. NUMBER POSTS AND ADD PAGE BREAKS
    // ========================================
    function numberPosts() {
        const posts = document.querySelectorAll('.thing.link:not([data-numbered])');
        if (posts.length === 0) return;

        posts.forEach(post => {
            postCounter++;
            post.setAttribute('data-numbered', 'true');
            post.setAttribute('data-post-number', postCounter);

            const rank = post.querySelector('.rank');
            if (rank) {
                rank.textContent = postCounter;
            }
        });
    }

    // ========================================
    // 6. ADD PAGE SEPARATOR
    // ========================================
    function addPageSeparator(pageNum) {
        const siteTable = document.querySelector('.sitetable.linklisting');
        if (!siteTable) return;

        const separator = document.createElement('div');
        separator.className = 'page-separator';
        separator.setAttribute('data-page', pageNum);
        separator.innerHTML = `
            <div class="page-separator-line"></div>
            <div class="page-separator-label">Page ${pageNum}</div>
            <div class="page-separator-line"></div>
        `;
        siteTable.appendChild(separator);
    }

    // ========================================
    // 7. INFINITE SCROLL (with prefetching)
    // ========================================
    function prefetchNextPage() {
        const nextButton = document.querySelector('.next-button a');
        if (!nextButton || prefetchedDoc) return;

        const url = nextButton.href;
        if (prefetchedPage === url) return;

        prefetchedPage = url;
        fetch(url)
            .then(response => response.text())
            .then(html => {
                const parser = new DOMParser();
                prefetchedDoc = parser.parseFromString(html, 'text/html');
            })
            .catch(() => {
                prefetchedPage = null;
            });
    }

    function loadNextPage() {
        if (isLoading) return;

        const nextButton = document.querySelector('.next-button a');
        if (!nextButton) return;

        isLoading = true;

        const siteTable = document.querySelector('.sitetable.linklisting');
        if (!siteTable) {
            isLoading = false;
            return;
        }

        function appendPosts(doc) {
            currentPage++;
            addPageSeparator(currentPage);

            const newPosts = doc.querySelectorAll('.thing.link');
            newPosts.forEach(post => {
                const clonedPost = post.cloneNode(true);
                clonedPost.removeAttribute('data-numbered');
                siteTable.appendChild(clonedPost);
            });

            const newNextButton = doc.querySelector('.next-button a');
            if (newNextButton) {
                nextButton.href = newNextButton.href;
                prefetchedDoc = null;
                prefetchedPage = null;
                prefetchNextPage();
            } else {
                const navButtons = document.querySelector('.nav-buttons');
                if (navButtons) {
                    navButtons.innerHTML = '<span style="color: #888; padding: 10px;">No more posts</span>';
                }
            }

            numberPosts();
            embedPlaceholderImages(); // Ensure images embed on new pages
            isLoading = false;
        }

        if (prefetchedDoc && prefetchedPage === nextButton.href) {
            appendPosts(prefetchedDoc);
            prefetchedDoc = null;
            prefetchedPage = null;
            return;
        }

        fetch(nextButton.href)
            .then(response => response.text())
            .then(html => {
                const parser = new DOMParser();
                const doc = parser.parseFromString(html, 'text/html');
                appendPosts(doc);
            })
            .catch(err => {
                console.error('Infinite scroll error:', err);
                isLoading = false;
            });
    }

    function setupInfiniteScroll() {
        if (document.querySelector('.commentarea')) return;

        prefetchNextPage();

        const handleScroll = throttle(() => {
            const scrollPosition = window.innerHeight + window.scrollY;
            const pageHeight = document.documentElement.scrollHeight;

            if (scrollPosition >= pageHeight - 1500) {
                prefetchNextPage();
            }

            if (scrollPosition >= pageHeight - 800) {
                loadNextPage();
            }
        }, 150);

        window.addEventListener('scroll', handleScroll);

        numberPosts();
    }

    // ========================================
    // 8. CLICK-BASED LOGIC
    // ========================================
    let internalClick = false;

    document.addEventListener('click', function(e) {
        const thumbnail = e.target.closest('a.thumbnail');
        if (thumbnail) {
            e.preventDefault();
            const thing = thumbnail.closest('.thing');
            if (thing) {
                const expandoButton = thing.querySelector('.expando-button');
                if (expandoButton) {
                    internalClick = true;
                    expandoButton.click();
                    internalClick = false;
                }
            }
            return;
        }

        const expandoButton = e.target.closest('.expando-button');
        if (expandoButton && expandoButton.classList.contains('collapsed')) {
            if (e.isTrusted || internalClick) {
                document.querySelectorAll('.expando-button.expanded').forEach(btn => {
                    if (btn !== expandoButton) btn.click();
                });

                const thing = expandoButton.closest('.thing');
                if (thing) {
                    setTimeout(() => {
                        thing.scrollIntoView({ behavior: 'smooth', block: 'start' });
                    }, 50);
                }
            }

            setTimeout(() => {
                const nsfwBtn = document.querySelector('button.expando-gate__show-once');
                if (nsfwBtn) nsfwBtn.click();
            }, 100);
        }
    }, true);

    // ========================================
    // 9. MUTATION OBSERVER (Optimized)
    // ========================================
    let observerTimeout = null;

    const observer = new MutationObserver((mutations) => {
        clickContinue();
        const nsfwBtn = document.querySelector('button.expando-gate__show-once');
        if (nsfwBtn) nsfwBtn.click();

        if (observerTimeout) return;

        observerTimeout = setTimeout(() => {
            collapseModeratorComments();
            constrainEmbeds();
            embedPlaceholderImages(); // Catch dynamic image links
            numberPosts();
            observerTimeout = null;
        }, 100);
    });

    // ========================================
    // 10. INITIALIZATION
    // ========================================
    function init() {
        observer.observe(document.documentElement, { childList: true, subtree: true });

        clickContinue();
        setTimeout(clickContinue, 100);
        setTimeout(clickContinue, 300);
        setTimeout(clickContinue, 500);
        setTimeout(clickContinue, 1000);

        collapseModeratorComments();
        autoExpandPost();
        constrainEmbeds();
        embedPlaceholderImages();
        setupInfiniteScroll();

        const style = document.createElement('style');
        style.textContent = `
            .page-separator {
                clear: both;
                width: 100%;
                display: flex;
                align-items: center;
                gap: 14px;
                margin: 20px 0;
                padding: 0 10px;
            }
            .page-separator-line {
                flex: 1;
                height: 1px;
                background: linear-gradient(90deg, transparent, #c0d0e0 15%, #c0d0e0 85%, transparent);
            }
            .page-separator-label {
                font-size: 11px;
                font-weight: 600;
                letter-spacing: 1.5px;
                text-transform: uppercase;
                color: #8aa8c7;
                white-space: nowrap;
            }
            .thing.link {
                border-bottom: 1px solid #e5e5e5 !important;
                padding-bottom: 10px !important;
                margin-bottom: 10px !important;
            }
            .infinite-scroll-loading {
                clear: both;
                width: 100%;
            }
            .rank {
                min-width: 30px !important;
            }
        `;
        document.head.appendChild(style);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    window.addEventListener('load', () => {
        clickContinue();
        setTimeout(clickContinue, 500);
    });
})();