Greasy Fork is available in English.

Universal Download Link Finder Ultimate

Advanced download link finder with intelligent detection and filtering

// ==UserScript==
// @name         Universal Download Link Finder Ultimate
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Advanced download link finder with intelligent detection and filtering
// @author       motoe
// @match        *://*/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // UI Configuration
    const UI_CONFIG = {
        colors: {
            background: 'rgba(0, 0, 0, 0.95)',
            text: '#FFFFFF',
            highlight: '#00ff00',
            link: '#66cfff',
            button: {
                copy: '#28a745',
                toggle: '#007bff'
            }
        },
        dimensions: {
            width: '400px',
            maxHeight: '600px',
            padding: '15px',
            borderRadius: '8px'
        }
    };

    // Create floating window with enhanced styling
    const windowDiv = document.createElement("div");
    windowDiv.style.cssText = `
        position: fixed;
        bottom: 10px;
        right: 10px;
        background-color: ${UI_CONFIG.colors.background};
        color: ${UI_CONFIG.colors.text};
        padding: ${UI_CONFIG.dimensions.padding};
        z-index: 9999999;
        max-height: ${UI_CONFIG.dimensions.maxHeight};
        overflow-y: auto;
        font-size: 13px;
        border-radius: ${UI_CONFIG.dimensions.borderRadius};
        width: ${UI_CONFIG.dimensions.width};
        box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        display: none;
        font-family: Arial, sans-serif;
        transition: all 0.3s ease;
    `;

    // Title div with enhanced styling
    const titleDiv = document.createElement("div");
    titleDiv.style.cssText = `
        font-weight: bold;
        margin-bottom: 15px;
        display: flex;
        justify-content: space-between;
        color: ${UI_CONFIG.colors.highlight};
        font-size: 14px;
        cursor: move;
        padding-bottom: 10px;
        border-bottom: 1px solid rgba(255,255,255,0.1);
        user-select: none;
    `;
    windowDiv.appendChild(titleDiv);

    // Content container with improved scrolling
    const contentDiv = document.createElement("div");
    contentDiv.style.cssText = `
        margin-bottom: 15px;
        max-height: 500px;
        overflow-y: auto;
        scrollbar-width: thin;
        scrollbar-color: #666 #333;
        padding-right: 5px;
    `;
    windowDiv.appendChild(contentDiv);

    // Button container
    const buttonContainer = document.createElement("div");
    buttonContainer.style.cssText = `
        display: flex;
        gap: 10px;
        margin-top: 10px;
    `;
    windowDiv.appendChild(buttonContainer);

    // Enhanced buttons
    const createButton = (text, color) => {
        const button = document.createElement("button");
        button.textContent = text;
        button.style.cssText = `
            padding: 8px;
            background-color: ${color};
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            flex: 1;
            transition: all 0.3s ease;
            font-weight: bold;
            outline: none;
            &:hover {
                filter: brightness(1.2);
                transform: translateY(-1px);
            }
            &:active {
                transform: translateY(0);
            }
        `;
        return button;
    };

    const copyButton = createButton("Copy URLs", UI_CONFIG.colors.button.copy);
    const toggleButton = createButton("Show/Hide", UI_CONFIG.colors.button.toggle);
    buttonContainer.appendChild(copyButton);
    buttonContainer.appendChild(toggleButton);

    document.body.appendChild(windowDiv);

    const linksArray = new Set();

    // Enhanced file type detection
    const FILE_TYPES = {
        video: [
            'mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'webm', 'm4v', '3gp', 'ts',
            'mts', 'vob', 'mpg', 'mpeg', 'mp2', 'm2v', 'svi', '3g2', 'mxf', 'roq',
            'nsv', 'f4v', 'f4p', 'f4a', 'f4b'
        ],
        audio: [
            'mp3', 'wav', 'aac', 'flac', 'ogg', 'wma', 'm4a', 'aiff', 'alac',
            'dsd', 'pcm', 'dsf', 'ac3', 'dts', 'mka', 'mid', 'midi', 'ape',
            'wv', 'tta'
        ],
        document: [
            'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'rtf',
            'csv', 'odt', 'ods', 'odp', 'epub', 'mobi', 'azw3', 'djvu', 'tex',
            'pages', 'key', 'numbers'
        ],
        image: [
            'jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'webp', 'svg', 'raw',
            'cr2', 'nef', 'arw', 'dng', 'psd', 'ai', 'eps', 'xcf', 'ico'
        ],
        archive: [
            'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'z', 'lz', 'lzma',
            'tgz', 'tbz2', 'txz', 'bz', 'cab', 'iso', 'dmg', 'pkg', 'apk',
            'aab'
        ],
        executable: [
            'exe', 'msi', 'apk', 'ipa', 'deb', 'rpm', 'app', 'jar', 'war',
            'dll', 'sys', 'bin', 'dat', 'run', 'sh', 'bat', 'cmd'
        ],
        subtitle: [
            'srt', 'sub', 'idx', 'ass', 'ssa', 'smi', 'vtt', 'ttml'
        ]
    };

    // URL filtering patterns
    const EXCLUDE_PATTERNS = {
        navigation: [
            /\?sort=/i,
            /\?order=/i,
            /\?page=/i,
            /\?filter=/i,
            /\?view=/i,
            /^https?:\/\/[^\/]+\/?$/i,
            /\/index\.(php|html|asp|jsp)/i,
            /\/(css|js|img|images|assets)\//i,
            /\?s=/i,
            /\?q=/i,
            /\?search=/i
        ],
        social: [
            /\/share\//i,
            /\/likes\//i,
            /\/comments\//i,
            /\/follow\//i
        ],
        tracking: [
            /\?utm_/i,
            /\?ref=/i,
            /\?source=/i,
            /\?campaign=/i
        ]
    };

    function isValidFileExtension(url) {
        const allExtensions = Object.values(FILE_TYPES).flat();
        const extensionPattern = new RegExp(`\\.(${allExtensions.join('|')})(?:[?#].*)?$`, 'i');
        return extensionPattern.test(url);
    }

    function isExcludedUrl(url) {
        const allExcludePatterns = [
            ...EXCLUDE_PATTERNS.navigation,
            ...EXCLUDE_PATTERNS.social,
            ...EXCLUDE_PATTERNS.tracking
        ];
        return allExcludePatterns.some(pattern => pattern.test(url));
    }

    function isDownloadLink(link) {
        if (!link || !link.href) return false;

        // Decode URL safely
        let decodedUrl;
        try {
            decodedUrl = decodeURIComponent(link.href);
        } catch (e) {
            decodedUrl = link.href;
        }

        // Early return for excluded URLs
        if (isExcludedUrl(decodedUrl)) {
            return false;
        }

        const url = decodedUrl.toLowerCase();
        const text = (link.textContent || '').toLowerCase();
        const title = (link.title || '').toLowerCase();
        const classList = Array.from(link.classList).join(' ').toLowerCase();
        const dataAttributes = Object.keys(link.dataset)
            .map(key => `${key}=${link.dataset[key]}`.toLowerCase());

        // Check download attribute
        if (link.hasAttribute('download')) return true;

        // Check file extensions
        if (isValidFileExtension(url)) return true;

        // Check common download indicators
        const downloadIndicators = [
            'download', 'dl', 'get', 'fetch', 'save', 'export',
            'file', 'video', 'movie', 'watch', 'stream',
            'media', 'content', 'attachment'
        ];

        const hasDownloadIndicator = downloadIndicators.some(indicator =>
            url.includes(indicator) ||
            text.includes(indicator) ||
            title.includes(indicator) ||
            classList.includes(indicator) ||
            dataAttributes.some(attr => attr.includes(indicator))
        );

        if (hasDownloadIndicator) return true;

        // Check for media patterns
        const mediaPatterns = [
            /\/(?:movies|videos|downloads|files)\/[^\/]+$/i,
            /\/(?:watch|stream|view)\/[^\/]+$/i,
            /\/[^\/]+\d{3,4}p[^\/]*$/i,  // Resolution patterns
            /\/[^\/]+(?:19|20)\d{2}[^\/]*$/i  // Year patterns
        ];

        return mediaPatterns.some(pattern => pattern.test(url));
    }

    function updateDisplay() {
        if (linksArray.size === 0) {
            contentDiv.innerHTML = '<div style="color: #ff6b6b; text-align: center; padding: 20px;">No download links found yet...</div>';
            return;
        }

        contentDiv.innerHTML = Array.from(linksArray)
            .map(url => `
                <div class="link-item" style="
                    word-break: break-all;
                    margin-bottom: 8px;
                    padding: 8px;
                    background: rgba(255,255,255,0.05);
                    border-radius: 4px;
                    transition: all 0.3s ease;
                    cursor: pointer;
                    &:hover {
                        background: rgba(255,255,255,0.1);
                    }
                ">
                    <a href="${url}"
                       target="_blank"
                       style="
                           color: ${UI_CONFIG.colors.link};
                           text-decoration: none;
                           display: block;
                           transition: color 0.3s ease;
                       "
                       onmouseover="this.style.color='#ffffff'"
                       onmouseout="this.style.color='${UI_CONFIG.colors.link}'"
                    >${url}</a>
                </div>
            `).join('');
    }

    function scanForDownloadLinks() {
        const previousSize = linksArray.size;
        document.querySelectorAll('a').forEach(link => {
            if (isDownloadLink(link)) {
                linksArray.add(link.href);
            }
        });

        if (linksArray.size !== previousSize) {
            titleDiv.textContent = `Download Links Found (${linksArray.size})`;
            updateDisplay();
            if (linksArray.size > 0 && windowDiv.style.display === 'none') {
                windowDiv.style.display = 'block';
            }
        }
    }

    // Button event handlers
    copyButton.addEventListener("click", async () => {
        if (linksArray.size === 0) {
            alert('No URLs found to copy!');
            return;
        }

        try {
            await navigator.clipboard.writeText(Array.from(linksArray).join('\n'));
            copyButton.textContent = "Copied!";
            copyButton.style.backgroundColor = "#218838";
            setTimeout(() => {
                copyButton.textContent = "Copy URLs";
                copyButton.style.backgroundColor = UI_CONFIG.colors.button.copy;
            }, 1000);
        } catch (err) {
            alert('Failed to copy URLs: ' + err);
        }
    });

    toggleButton.addEventListener("click", () => {
        windowDiv.style.display = windowDiv.style.display === "none" ? "block" : "none";
    });

    // Draggable functionality
    let isDragging = false;
    let currentX;
    let currentY;
    let initialX;
    let initialY;

    titleDiv.addEventListener('mousedown', dragStart);

    function dragStart(e) {
        initialX = e.clientX - windowDiv.offsetLeft;
        initialY = e.clientY - windowDiv.offsetTop;
        isDragging = true;

        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', dragEnd);
    }

    function drag(e) {
        if (isDragging) {
            e.preventDefault();
            currentX = e.clientX - initialX;
            currentY = e.clientY - initialY;

            // Boundary checking
            const maxX = window.innerWidth - windowDiv.offsetWidth;
            const maxY = window.innerHeight - windowDiv.offsetHeight;

            currentX = Math.max(0, Math.min(currentX, maxX));
            currentY = Math.max(0, Math.min(currentY, maxY));

            windowDiv.style.left = currentX + 'px';
            windowDiv.style.right = 'auto';
            windowDiv.style.top = currentY + 'px';
            windowDiv.style.bottom = 'auto';
        }
    }

    function dragEnd() {
        isDragging = false;
        document.removeEventListener('mousemove', drag);
        document.removeEventListener('mouseup', dragEnd);
    }

    // Initial scan
    scanForDownloadLinks();

    // Periodic scan with debouncing
    let scanTimeout;
    const observer = new MutationObserver(() => {
        clearTimeout(scanTimeout);
        scanTimeout = setTimeout(scanForDownloadLinks, 1000);
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Cleanup on page unload
    window.addEventListener('unload', () => {
        observer.disconnect();
    });

})();