EmbedCast Link Extractor (Sidebar Edition)

Collapsible sidebar that centralizes all detected video/iframe links.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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);

})();