EmbedCast Link Extractor (Sidebar Edition)

Collapsible sidebar that centralizes all detected video/iframe links.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         EmbedCast Link Extractor (Sidebar Edition)
// @namespace    https://github.com/MoriNo23/embedCast
// @version      2.0
// @description  Collapsible sidebar that centralizes all detected video/iframe links.
// @author       mori
// @match        *://*/*
// @grant        none
// @license MIT
// @run-at       document-end
// @icon         https://raw.githubusercontent.com/MoriNo23/embedCast/main/assets/logo.png
// ==/UserScript==

(function() {
    'use strict';

    // --- COMMUNICATION SETUP ---
    const MESSAGE_TYPE = 'EMBEDCAST_FOUND_URL';

    // --- LOGIC FOR IFRAMES (CHILDREN) ---
    if (window.self !== window.top) {
        const notifyTop = (url) => {
            if (url && url.startsWith('http') && !url.includes('about:blank')) {
                // Send the URL to the main page
                window.top.postMessage({ type: MESSAGE_TYPE, url: url }, '*');
            }
        };

        // Notify the current iframe URL
        notifyTop(window.location.href);

        // Look for lazyload elements inside the iframe
        const checkLazyInIframe = () => {
            const lazy = document.querySelector('iframe, .lazyload, .lazyloade, [data-src]');
            if (lazy) {
                const url = lazy.src || lazy.getAttribute('data-src') || lazy.getAttribute('data-lazy-src');
                notifyTop(url);
            }
        };
        setInterval(checkLazyInIframe, 3000);
        return; // Stop execution here for iframes
    }

    // --- LOGIC FOR MAIN WINDOW (PARENT) ---
    const capturedUrls = new Set();
    let sidebar, listContainer;

    const createSidebar = () => {
        if (document.getElementById('embedcast-sidebar')) return;

        // Create the sidebar container
        sidebar = document.createElement('div');
        sidebar.id = 'embedcast-sidebar';
        Object.assign(sidebar.style, {
            position: 'fixed',
            right: '-260px', // Hidden by default
            top: '0',
            width: '260px',
            height: '100vh',
            background: '#1a1a2e',
            borderLeft: '2px solid #00ffcc',
            zIndex: '2147483647', // Always on top
            transition: 'right 0.3s ease',
            display: 'flex',
            flexDirection: 'column',
            boxShadow: '-5px 0 15px rgba(0,0,0,0.5)',
            color: '#fff',
            fontFamily: 'sans-serif'
        });

        sidebar.innerHTML = `
            <div style="padding: 15px; background: #16213e; border-bottom: 1px solid #00ffcc; display: flex; align-items: center; justify-content: space-between;">
                <div style="display:flex; align-items:center;">
                    <img src="https://raw.githubusercontent.com/MoriNo23/embedCast/main/assets/logo.png" style="width:20px; margin-right:8px;">
                    <span style="color:#00ffcc; font-weight:bold; font-size:14px;">EmbedCast</span>
                </div>
                <button id="ec-close" style="background:none; border:none; color:#00ffcc; cursor:pointer; font-size:18px;">×</button>
            </div>
            <div id="ec-list" style="flex:1; overflow-y:auto; padding:10px;">
                <p id="ec-empty" style="color:#666; font-size:12px; text-align:center; margin-top:20px;">Searching links...</p>
            </div>
            <div id="ec-toggle" style="position:absolute; left:-40px; top:50%; width:40px; height:60px; background:#1a1a2e; border:2px solid #00ffcc; border-right:none; border-radius:10px 0 0 10px; cursor:pointer; display:flex; align-items:center; justify-content:center; color:#00ffcc; font-weight:bold;">
                <img src="https://raw.githubusercontent.com/MoriNo23/embedCast/main/assets/logo.png" style="width:25px;">
            </div>
        `;

        document.body.appendChild(sidebar);
        listContainer = document.getElementById('ec-list');

        // UI Events
        const toggleBtn = document.getElementById('ec-toggle');
        const closeBtn = document.getElementById('ec-close');
        
        const toggleSidebar = () => {
            const isOpen = sidebar.style.right === '0px';
            sidebar.style.right = isOpen ? '-260px' : '0px';
        };

        toggleBtn.onclick = toggleSidebar;
        closeBtn.onclick = toggleSidebar;
    };

    const addUrlToList = (url) => {
        // Avoid duplicate links
        if (capturedUrls.has(url)) return;
        capturedUrls.add(url);

        const emptyMsg = document.getElementById('ec-empty');
        if (emptyMsg) emptyMsg.remove();

        // Create link item in the list
        const item = document.createElement('div');
        Object.assign(item.style, {
            background: '#16213e',
            padding: '10px',
            marginBottom: '8px',
            borderRadius: '5px',
            border: '1px solid #30475e',
            fontSize: '11px'
        });

        item.innerHTML = `
            <div style="color:#00ffcc; margin-bottom:5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${url}">
                ${url}
            </div>
            <button class="ec-copy-btn" style="width:100%; background:#0f3460; color:#00ffcc; border:1px solid #00ffcc; padding:5px; cursor:pointer; border-radius:3px; font-weight:bold;">
                COPY
            </button>
        `;

        item.querySelector('.ec-copy-btn').onclick = function() {
            navigator.clipboard.writeText(url);
            this.innerText = 'COPIED!';
            setTimeout(() => this.innerText = 'COPY', 2000);
        };

        listContainer.appendChild(item);
        
        // Auto-open sidebar when the first link is found
        if (capturedUrls.size === 1) {
            sidebar.style.right = '0px';
        }
    };

    // Listen for messages from iframes
    window.addEventListener('message', (event) => {
        if (event.data && event.data.type === MESSAGE_TYPE) {
            addUrlToList(event.data.url);
        }
    });

    // Initialize sidebar
    createSidebar();

    // Also search in the main page for links
    setInterval(() => {
        const potential = document.querySelectorAll('iframe, .lazyload, .lazyloade, [data-src]');
        potential.forEach(el => {
            const url = el.src || el.getAttribute('data-src') || el.getAttribute('data-lazy-src');
            if (url && url.startsWith('http')) addUrlToList(url);
        });
    }, 3000);

})();