DuckDuckGo URL Collector

Collects all URLs from DuckDuckGo search results with logging and duplicate prevention

// ==UserScript==
// @name         DuckDuckGo URL Collector
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Collects all URLs from DuckDuckGo search results with logging and duplicate prevention
// @author       Ghosty-Tongue
// @match        *://duckduckgo.com/*
// @grant        GM_notification
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const collectedUrls = new Set();
    let isProcessing = false;
    let startTime, timerInterval;

    const style = document.createElement('style');
    style.textContent = `
        @keyframes rgbFlow {
            0% { background-position: 0% 50%; }
            100% { background-position: 100% 50%; }
        }
        @keyframes pulse {
            0% { transform: scale(1); }
            50% { transform: scale(1.1); }
            100% { transform: scale(1); }
        }
    `;
    document.head.appendChild(style);

    const timerDisplay = document.createElement('div');
    Object.assign(timerDisplay.style, {
        position: 'fixed',
        top: '50px',
        right: '10px',
        zIndex: '10000',
        color: 'white',
        backgroundColor: 'rgba(0,0,0,0.7)',
        padding: '5px 10px',
        borderRadius: '5px',
        fontFamily: 'Arial, sans-serif',
        fontSize: '14px'
    });
    document.body.appendChild(timerDisplay);

    function startTimer() {
        if (timerInterval) clearInterval(timerInterval);
        startTime = Date.now();
        timerInterval = setInterval(updateTimer, 1000);
        timerDisplay.textContent = '0s';
    }

    function updateTimer() {
        const elapsed = Math.floor((Date.now() - startTime) / 1000);
        timerDisplay.textContent = `${elapsed}s`;
    }

    function stopTimer() {
        clearInterval(timerInterval);
        const elapsed = Math.floor((Date.now() - startTime) / 1000);
        timerDisplay.textContent = `${elapsed}s (stopped)`;
    }

    function extractUrls() {
        console.log('Extracting URLs from current page...');
        const results = document.querySelectorAll('article[data-testid="result"]');
        let newUrlsCount = 0;

        results.forEach(result => {
            const link = result.querySelector('a[data-testid="result-extras-url-link"]');
            if (link) {
                const url = link.href;
                if (!collectedUrls.has(url)) {
                    collectedUrls.add(url);
                    newUrlsCount++;
                }
            }
        });

        console.log(`Added ${newUrlsCount} new URLs from this batch. Total: ${collectedUrls.size}`);
        return newUrlsCount;
    }

    async function clickMoreResults() {
        isProcessing = true;
        btn.classList.add('processing');
        console.log('Starting URL collection process...');

        let iteration = 1;
        let moreResultsButton;

        do {
            console.log(`Looking for "More Results" button (Attempt ${iteration})...`);
            moreResultsButton = document.getElementById('more-results');

            if (moreResultsButton) {
                console.log('Clicking "More Results" button...');
                moreResultsButton.click();
                await new Promise(resolve => setTimeout(resolve, 2000));
                extractUrls();
                iteration++;
            }
        } while (moreResultsButton);

        console.log(`Finished collecting URLs. Total unique URLs: ${collectedUrls.size}`);
        isProcessing = false;
        btn.classList.remove('processing');

        GM_notification({
            title: 'Collection Complete',
            text: `Saved ${collectedUrls.size} URLs`,
            timeout: 5000
        });

        saveUrls();
    }

    function saveUrls() {
        console.log('Preparing to save URLs to file...');
        const blob = new Blob([Array.from(collectedUrls).join('\n')], {type: 'text/plain'});
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'urls.txt';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
        console.log(`File saved with ${collectedUrls.size} URLs`);
        stopTimer();
    }

    const btn = document.createElement('button');
    btn.textContent = '🦆';
    Object.assign(btn.style, {
        position: 'fixed',
        top: '10px',
        right: '10px',
        zIndex: '10000',
        padding: '12px 24px',
        background: 'linear-gradient(90deg, #ff0000, #00ff00, #0000ff, #ff0000)',
        backgroundSize: '300% 100%',
        animation: 'rgbFlow 5s linear infinite',
        color: 'white',
        border: 'none',
        borderRadius: '25px',
        cursor: 'pointer',
        fontFamily: 'Arial, sans-serif',
        fontWeight: 'bold',
        boxShadow: '0 4px 15px rgba(0,0,0,0.2)',
        transition: 'transform 0.2s, box-shadow 0.2s'
    });

    btn.addEventListener('mouseover', () => {
        btn.style.transform = 'scale(1.05)';
        btn.style.boxShadow = '0 6px 20px rgba(0,0,0,0.25)';
    });

    btn.addEventListener('mouseout', () => {
        btn.style.transform = 'scale(1)';
        btn.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
    });

    btn.addEventListener('click', () => {
        if (!isProcessing) {
            console.log('----- New Collection Started -----');
            collectedUrls.clear();
            startTimer();
            clickMoreResults();
        }
    });

    document.body.appendChild(btn);
    console.log('URL Collector button initialized');
})();