Rule34Video Universal Link Extractor

Dynamically finds the content container to work on all video list pages. Extracts links, auto-navigates, and lets you name the output .txt file.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Rule34Video Universal Link Extractor
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  Dynamically finds the content container to work on all video list pages. Extracts links, auto-navigates, and lets you name the output .txt file.
// @author       BohemianCorporal
// @match        https://rule34video.com/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    let allLinks = new Set();
    let isAutoGrabbing = false;
    let observer;

    // --- Create the UI Panel ---
    const panel = document.createElement('div');
    panel.style.position = 'fixed';
    panel.style.bottom = '20px';
    panel.style.right = '20px';
    panel.style.backgroundColor = '#2c2c2e';
    panel.style.color = '#fff';
    panel.style.border = '1px solid #555';
    panel.style.padding = '15px';
    panel.style.zIndex = '10000';
    panel.style.borderRadius = '8px';
    panel.style.fontFamily = 'Arial, sans-serif';
    panel.style.fontSize = '14px';
    panel.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
    panel.style.width = '230px';

    const title = document.createElement('h3');
    title.textContent = 'Link Extractor';
    title.style.margin = '0 0 10px 0';
    title.style.textAlign = 'center';
    title.style.borderBottom = '1px solid #555';
    title.style.paddingBottom = '5px';

    const getLinksButton = document.createElement('button');
    getLinksButton.textContent = 'Get Links for Current Page';
    getLinksButton.style.cssText = 'display:block; width:100%; margin-bottom:8px; padding:8px; cursor:pointer;';

    const autoGrabButton = document.createElement('button');
    autoGrabButton.textContent = 'Start Auto-Grab';
    autoGrabButton.style.cssText = 'display:block; width:100%; margin-bottom:15px; padding:8px; cursor:pointer; background-color: #4CAF50; color: white; border: none;';

    const filenameLabel = document.createElement('label');
    filenameLabel.textContent = 'Download Filename:';
    filenameLabel.style.cssText = 'display: block; margin-bottom: 5px; font-weight: bold;';

    const filenameInput = document.createElement('input');
    filenameInput.type = 'text';
    filenameInput.placeholder = 'e.g., my_collection';
    filenameInput.style.cssText = 'display:block; box-sizing: border-box; width: 100%; padding: 6px; margin-bottom: 10px; background-color: #444; color: #fff; border: 1px solid #666; border-radius: 4px;';

    const downloadButton = document.createElement('button');
    downloadButton.textContent = 'Download All Links (.txt)';
    downloadButton.style.cssText = 'display:block; width:100%; padding:8px; cursor:pointer;';

    const statusDisplay = document.createElement('div');
    statusDisplay.textContent = 'Status: Idle';
    statusDisplay.style.cssText = 'margin-top: 10px; text-align: center; font-style: italic;';

    const linkCountDisplay = document.createElement('div');
    linkCountDisplay.textContent = 'Collected Links: 0';
    linkCountDisplay.style.marginTop = '5px';
    linkCountDisplay.style.textAlign = 'center';

    panel.appendChild(title);
    panel.appendChild(getLinksButton);
    panel.appendChild(autoGrabButton);
    panel.appendChild(filenameLabel);
    panel.appendChild(filenameInput);
    panel.appendChild(downloadButton);
    panel.appendChild(statusDisplay);
    panel.appendChild(linkCountDisplay);
    document.body.appendChild(panel);

    // --- Core Functions ---

    function updateLinkCount() {
        linkCountDisplay.textContent = `Collected Links: ${allLinks.size}`;
    }

    function getLinksFromCurrentPage() {
        const linkElements = document.querySelectorAll('a.th.js-open-popup');
        let newLinksFound = 0;
        linkElements.forEach(link => {
            const href = link.href;
            if (href && !allLinks.has(href)) {
                allLinks.add(href);
                newLinksFound++;
            }
        });
        console.log(`Found ${newLinksFound} new links. Total unique links: ${allLinks.size}`);
        updateLinkCount();
    }

    function runAutoGrabSequence() {
        if (!isAutoGrabbing) return;

        statusDisplay.textContent = 'Status: Grabbing links...';
        getLinksFromCurrentPage();

        const nextButton = document.querySelector('div.item.pager.next > a');

        // If there's no next button, we're done.
        if (!nextButton) {
            console.log("No 'next' button found. Reached the last page.");
            statusDisplay.textContent = 'Status: Last page reached.';
            stopAutoGrab();
            return;
        }

        // ** DYNAMICALLY FIND THE CONTAINER ID **
        const targetContainerId = nextButton.getAttribute('data-block-id');
        const targetNode = document.getElementById(targetContainerId);

        if (!targetNode) {
            console.error(`Video container with ID "${targetContainerId}" not found. Stopping.`);
            alert(`Error: Could not find the video container to watch. Stopping auto-grab.`);
            stopAutoGrab();
            return;
        }

        console.log(`Watching container: #${targetContainerId}`);
        observer = new MutationObserver((mutationsList, obs) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    console.log("New content loaded.");
                    statusDisplay.textContent = 'Status: Page loaded.';
                    obs.disconnect(); // Stop observing until the next cycle
                    // Short delay to ensure scripts on the page have finished processing
                    setTimeout(runAutoGrabSequence, 500);
                    break;
                }
            }
        });

        // Start observing before we click the next button
        observer.observe(targetNode, { childList: true, subtree: true });

        // Click the next button to load the new content
        console.log("Navigating to the next page...");
        statusDisplay.textContent = 'Status: Going to next page...';
        nextButton.click();
    }

    function startAutoGrab() {
        isAutoGrabbing = true;
        autoGrabButton.textContent = 'Stop Auto-Grab';
        autoGrabButton.style.backgroundColor = '#f44336';
        statusDisplay.textContent = 'Status: Starting...';
        runAutoGrabSequence();
    }

    function stopAutoGrab() {
        isAutoGrabbing = false;
        if (observer) observer.disconnect();
        autoGrabButton.textContent = 'Start Auto-Grab';
        autoGrabButton.style.backgroundColor = '#4CAF50';
        statusDisplay.textContent = 'Status: Idle';
    }

    // --- Button Event Listeners ---

    getLinksButton.addEventListener('click', getLinksFromCurrentPage);

    autoGrabButton.addEventListener('click', () => {
        if (isAutoGrabbing) {
            stopAutoGrab();
        } else {
            startAutoGrab();
        }
    });

    downloadButton.addEventListener('click', () => {
        if (allLinks.size === 0) {
            alert('No links have been collected yet. Use "Get Links" or "Start Auto-Grab" first.');
            return;
        }

        let filename = filenameInput.value.trim();
        if (filename === '') {
            filename = `video_links_${new Date().toISOString().slice(0, 10)}`;
        }

        const sanitizedFilename = filename.replace(/[\/\\?%*:|"<>]/g, '_');
        const fileContent = Array.from(allLinks).join('\n');
        const blob = new Blob([fileContent], { type: 'text/plain;charset=utf-8' });
        const downloadLink = document.createElement('a');
        downloadLink.href = URL.createObjectURL(blob);
        downloadLink.download = `${sanitizedFilename}.txt`;

        document.body.appendChild(downloadLink);
        downloadLink.click();
        document.body.removeChild(downloadLink);
        URL.revokeObjectURL(downloadLink.href);
    });

})();