DoodStream Video Hover Preview

Show video preview on hover for DoodStream video links

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         DoodStream Video Hover Preview
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  Show video preview on hover for DoodStream video links
// @author       You
// @match        *://doodstream.com/*
// @match        *://dsvplay.com/*
// @match        *://nobodyhome.ws/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';

    // Base configuration for most domains
    const BASE_CONFIG = {
        embedPath: '/e/',
        videoPath: '/d/',
        thumbnailPatterns: [
            /https:\/\/img\.doodcdn\.io\/splash\/[a-zA-Z0-9]+\.jpg/,
            /https:\/\/[^"'\s]+\.jpg/,
            /https:\/\/[^"'\s]+\.png/,
            /https:\/\/[^"'\s]+\.webp/
        ]
    };

    // Function to create tooltip
    function createTooltip() {
        const tooltip = document.createElement('div');
        tooltip.id = 'video-tooltip';
        tooltip.style.position = 'fixed';
        tooltip.style.background = 'rgba(0,0,0,0.8)';
        tooltip.style.color = 'white';
        tooltip.style.padding = '10px';
        tooltip.style.borderRadius = '5px';
        tooltip.style.zIndex = '10000';
        tooltip.style.display = 'none';
        tooltip.style.pointerEvents = 'none';
        document.body.appendChild(tooltip);
        return tooltip;
    }

    // Function to create "Copy All Links" button
    function createCopyAllButton() {
        const button = document.createElement('button');
        button.type = 'button';
        button.className = 'btn btn-success ml-2';
        button.innerHTML = '<i class="fad fa-copy"></i> Copy All Links';

        button.addEventListener('click', () => {
            copyAllLinks(button);
        });

        // Find the btn-group div and append the button
        const btnGroup = document.querySelector('.btn-group');
        if (btnGroup) {
            btnGroup.appendChild(button);
        } else {
            // Fallback to fixed position if btn-group not found
            button.style.position = 'fixed';
            button.style.top = '20px';
            button.style.right = '20px';
            button.style.zIndex = '10001';
            document.body.appendChild(button);
        }

        return button;
    }

    // Function to extract file code from DoodStream URL
    function extractFileCode(url) {
        const match = url.match(/\/[de]\/([a-z0-9]+)/i);
        return match ? match[1] : null;
    }

    // Function to copy all links
    async function copyAllLinks(button) {
        const originalHTML = button.innerHTML;
        button.innerHTML = '<i class="fad fa-spinner fa-spin"></i> Loading...';
        button.disabled = true;

        const videoLinks = document.querySelectorAll('a[href*="/d/"], a[href*="/e/"], a[href*="dood.to"], a[href*="dood.so"], a[href*="dood.la"], a[href*="doodstream.com"]');

        if (videoLinks.length === 0) {
            button.innerHTML = '<i class="fad fa-times"></i> No links found';
            setTimeout(() => {
                button.innerHTML = originalHTML;
                button.disabled = false;
            }, 2000);
            return;
        }

        const results = [];
        let processed = 0;

        for (const link of videoLinks) {
            const fileCode = extractFileCode(link.href);
            if (!fileCode) continue;

            // Check cache first
            if (previewUrlCache.has(fileCode)) {
                const previewUrl = previewUrlCache.get(fileCode);
                results.push(`[img]${previewUrl}[/img]\n${link.href}`);
                processed++;
                button.innerHTML = `<i class="fad fa-spinner fa-spin"></i> ${processed}/${videoLinks.length}`;
                continue;
            }

            // Fetch preview URL
            await new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: `https://dsvplay.com/e/${fileCode}`,
                    onload: function(response) {
                        let previewUrl = null;
                        for (const pattern of BASE_CONFIG.thumbnailPatterns) {
                            const match = response.responseText.match(pattern);
                            if (match) {
                                previewUrl = match[0];
                                break;
                            }
                        }
                        if (previewUrl) {
                            previewUrlCache.set(fileCode, previewUrl);
                            results.push(`[img]${previewUrl}[/img]\n${link.href}`);
                        } else {
                            results.push(link.href);
                        }
                        processed++;
                        button.innerHTML = `<i class="fad fa-spinner fa-spin"></i> ${processed}/${videoLinks.length}`;
                        resolve();
                    },
                    onerror: function() {
                        results.push(link.href);
                        processed++;
                        button.innerHTML = `<i class="fad fa-spinner fa-spin"></i> ${processed}/${videoLinks.length}`;
                        resolve();
                    }
                });
            });

            // Small delay to avoid overwhelming the server
            await new Promise(resolve => setTimeout(resolve, 100));
        }

        // Copy to clipboard
        const text = results.join('\n\n');
        navigator.clipboard.writeText(text).then(() => {
            button.innerHTML = `<i class="fad fa-check"></i> Copied ${results.length} links!`;
            setTimeout(() => {
                button.innerHTML = originalHTML;
                button.disabled = false;
            }, 2000);
        }).catch(() => {
            button.innerHTML = '<i class="fad fa-times"></i> Copy failed';
            setTimeout(() => {
                button.innerHTML = originalHTML;
                button.disabled = false;
            }, 2000);
        });
    }

    // Get all DoodStream video links
    const videoLinks = document.querySelectorAll('a[href*="/d/"], a[href*="/e/"], a[href*="dood.to"], a[href*="dood.so"], a[href*="dood.la"], a[href*="doodstream.com"]');

    if (videoLinks.length === 0) {
        console.log('No DoodStream video links found');
        return;
    }

    const tooltip = createTooltip();

    // Wait a bit for the page to load before adding the button
    setTimeout(() => {
        createCopyAllButton();
    }, 1000);

    // Cache successful preview src by fileCode to avoid reloading
    const previewCache = new Map();
    const previewUrlCache = new Map();

    // Function to add hover listeners to links
    function addHoverListeners(links) {
        links.forEach(link => {
            const fileCode = extractFileCode(link.href);
            if (!fileCode || link.hasAttribute('data-hover-added')) return;

            link.setAttribute('data-hover-added', 'true');
            console.log('Adding hover to:', link.href, 'fileCode:', fileCode);

            link.addEventListener('mouseenter', (e) => {
                console.log('Hovering on:', link.href);
                const rect = e.target.getBoundingClientRect();
                tooltip.style.left = rect.left + 'px';
                tooltip.style.top = (rect.top - 200) + 'px'; // Position above

                // Show embed iframe for preview
                const embedUrl = `https://dsvplay.com/e/${fileCode}`;
                const iframe = document.createElement('iframe');
                iframe.src = embedUrl;
                iframe.style.width = '320px';
                iframe.style.height = '180px';
                iframe.style.border = '0';
                iframe.style.display = 'block';
                iframe.style.pointerEvents = 'none';

                tooltip.innerHTML = '';
                tooltip.appendChild(iframe);
                tooltip.style.display = 'block';
            });

            link.addEventListener('mouseleave', () => {
                tooltip.style.display = 'none';
            });

            // Add copy button
            const copyLinkBtn = document.createElement('button');
            copyLinkBtn.textContent = 'Copy Link';
            copyLinkBtn.style.fontSize = '12px';
            copyLinkBtn.style.padding = '2px 5px';
            copyLinkBtn.style.marginLeft = '10px';
            link.parentNode.insertBefore(copyLinkBtn, link.nextSibling);

            copyLinkBtn.addEventListener('click', () => {
                const cached = previewUrlCache.get(fileCode);
                if (cached) {
                    const text = `[img]${cached}[/img]\n${link.href}`;
                    navigator.clipboard.writeText(text).then(() => {
                        console.log('Copied link');
                    });
                } else {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: `https://dsvplay.com/e/${fileCode}`,
                        onload: function(response) {
                            let previewUrl = null;
                            for (const pattern of BASE_CONFIG.thumbnailPatterns) {
                                const match = response.responseText.match(pattern);
                                if (match) {
                                    previewUrl = match[0];
                                    break;
                                }
                            }
                            if (previewUrl) {
                                previewUrlCache.set(fileCode, previewUrl);
                                const text = `[img]${previewUrl}[/img]\n${link.href}`;
                                navigator.clipboard.writeText(text).then(() => {
                                    console.log('Copied link');
                                });
                            }
                        }
                    });
                }
            });
        });
    }

    // Initial links
    addHoverListeners(videoLinks);

    // Watch for new links (for dynamic content)
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            mutation.addedNodes.forEach((node) => {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    const newLinks = node.querySelectorAll ? node.querySelectorAll('a[href*="/d/"], a[href*="/e/"], a[href*="dood.to"], a[href*="dood.so"], a[href*="dood.la"], a[href*="doodstream.com"]') : [];
                    addHoverListeners(newLinks);
                    if (node.tagName === 'A' && (node.href.includes('/d/') || node.href.includes('/e/') || node.href.includes('dood.to') || node.href.includes('dood.so') || node.href.includes('dood.la') || node.href.includes('doodstream.com'))) {
                        addHoverListeners([node]);
                    }
                }
            });
        });
    });

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

})();