Greasy Fork

Greasy Fork is available in English.

Universal Video Share Button with M3U8 Support

Adds a floating share button that appears when videos are detected. Hold down to copy instead of share. Auto-detects and downloads M3U8 playlists.

当前为 2025-11-06 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Universal Video Share Button with M3U8 Support
// @namespace    http://tampermonkey.net/
// @version      4.9
// @description  Adds a floating share button that appears when videos are detected. Hold down to copy instead of share. Auto-detects and downloads M3U8 playlists.
// @author       Minoa
// @license      MIT
// @match        *://*/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/m3u8-parser.min.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    var floatingButton = null;
    var pressTimer = null;
    var isLongPress = false;
    var checkInterval = null;
    var detectedM3U8s = [];
    var detectedM3U8Urls = [];
    var allDetectedVideos = new Map();
    var processedVideos = new Map();
    var downloadedBlobs = new Map();
    var debugMode = false;
    var debugConsole = null;
    var debugLogs = [];
    var longPressStartTime = 0;

    // Detect iOS
    const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
    const isMobile = isIOS || /Android|webOS|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

    // Color scheme
    const COLORS = {
        button: '#55423d',
        buttonHover: '#6b5651',
        icon: '#ffc0ad',
        text: '#fff3ec'
    };

    // Common video selectors
    var VIDEO_SELECTORS = [
        'video',
        '.video-player video',
        '.player video',
        '#player video',
        '.video-container video',
        '[class*="video"] video',
        '[class*="player"] video',
        'iframe[src*="youtube.com"]',
        'iframe[src*="vimeo.com"]',
        'iframe[src*="dailymotion.com"]',
        'iframe[src*="twitch.tv"]'
    ];

    var VIDEO_EXTENSIONS = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.wmv', '.flv', '.mkv', '.m4v', '.3gp'];

    // Debug console functions
    function createDebugConsole() {
        if (debugConsole) return;

        debugConsole = document.createElement('div');
        debugConsole.id = 'debug-console';
        debugConsole.style.cssText = `
            position: fixed;
            top: 70px;
            left: 10px;
            right: 10px;
            bottom: 10px;
            background: rgba(0, 0, 0, 0.95);
            border: 2px solid ${COLORS.icon};
            border-radius: 12px;
            z-index: 99999999;
            display: flex;
            flex-direction: column;
            font-family: monospace;
            font-size: 11px;
            color: ${COLORS.text};
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8);
        `;

        var header = document.createElement('div');
        header.style.cssText = `
            padding: 12px;
            background: ${COLORS.button};
            border-bottom: 1px solid ${COLORS.icon};
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-radius: 10px 10px 0 0;
        `;
        header.innerHTML = `<strong style="color: ${COLORS.icon};">🐛 DEBUG CONSOLE</strong>`;

        var buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = 'display: flex; gap: 8px;';

        var copyBtn = document.createElement('button');
        copyBtn.textContent = '📋 Copy';
        copyBtn.style.cssText = `
            background: ${COLORS.icon};
            color: #000;
            border: none;
            padding: 6px 12px;
            border-radius: 6px;
            font-weight: bold;
            cursor: pointer;
            font-size: 11px;
        `;
        copyBtn.onclick = function() {
            var logText = debugLogs.join('\n');
            if (navigator.clipboard && navigator.clipboard.writeText) {
                navigator.clipboard.writeText(logText).then(function() {
                    copyBtn.textContent = '✅ Copied!';
                    setTimeout(function() {
                        copyBtn.textContent = '📋 Copy';
                    }, 2000);
                });
            } else {
                var textArea = document.createElement('textarea');
                textArea.value = logText;
                textArea.style.position = 'fixed';
                textArea.style.left = '-999999px';
                document.body.appendChild(textArea);
                textArea.select();
                document.execCommand('copy');
                document.body.removeChild(textArea);
                copyBtn.textContent = '✅ Copied!';
                setTimeout(function() {
                    copyBtn.textContent = '📋 Copy';
                }, 2000);
            }
        };

        var clearBtn = document.createElement('button');
        clearBtn.textContent = '🗑️ Clear';
        clearBtn.style.cssText = `
            background: rgba(239, 68, 68, 0.8);
            color: ${COLORS.text};
            border: none;
            padding: 6px 12px;
            border-radius: 6px;
            font-weight: bold;
            cursor: pointer;
            font-size: 11px;
        `;
        clearBtn.onclick = function() {
            debugLogs = [];
            updateDebugConsole();
        };

        var closeBtn = document.createElement('button');
        closeBtn.textContent = '✕';
        closeBtn.style.cssText = `
            background: rgba(255, 255, 255, 0.2);
            color: ${COLORS.text};
            border: none;
            padding: 6px 10px;
            border-radius: 6px;
            font-weight: bold;
            cursor: pointer;
            font-size: 11px;
        `;
        closeBtn.onclick = function() {
            debugMode = false;
            debugConsole.remove();
            debugConsole = null;
        };

        buttonContainer.appendChild(copyBtn);
        buttonContainer.appendChild(clearBtn);
        buttonContainer.appendChild(closeBtn);
        header.appendChild(buttonContainer);

        var logContainer = document.createElement('div');
        logContainer.id = 'debug-log-container';
        logContainer.style.cssText = `
            flex: 1;
            overflow-y: auto;
            padding: 12px;
            line-height: 1.5;
        `;

        debugConsole.appendChild(header);
        debugConsole.appendChild(logContainer);
        document.body.appendChild(debugConsole);

        updateDebugConsole();
    }

    function updateDebugConsole() {
        if (!debugConsole) return;
        var logContainer = document.getElementById('debug-log-container');
        if (!logContainer) return;

        logContainer.innerHTML = '';
        
        debugLogs.forEach(function(log) {
            var logLine = document.createElement('div');
            logLine.style.cssText = 'margin-bottom: 4px; word-wrap: break-word;';
            
            var color = COLORS.text;
            if (log.includes('[ERROR]') || log.includes('❌')) {
                color = '#ef4444';
            } else if (log.includes('[SUCCESS]') || log.includes('✅')) {
                color = '#4ade80';
            } else if (log.includes('[INFO]') || log.includes('📥') || log.includes('🔄')) {
                color = '#3b82f6';
            } else if (log.includes('[M3U8]')) {
                color = COLORS.icon;
            }
            
            logLine.style.color = color;
            logLine.textContent = log;
            logContainer.appendChild(logLine);
        });

        logContainer.scrollTop = logContainer.scrollHeight;
    }

    function debugLog(message) {
        var timestamp = new Date().toLocaleTimeString();
        var logMessage = `[${timestamp}] ${message}`;
        debugLogs.push(logMessage);
        console.log(message);
        
        if (debugMode && debugConsole) {
            updateDebugConsole();
        }
    }

    // M3U8 Detection
    (function setupNetworkDetection() {
        const originalFetch = window.fetch;
        window.fetch = function(...args) {
            const promise = originalFetch.apply(this, args);
            const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;
            
            promise.then(response => {
                if (url) {
                    if (!url.match(/seg-\d+-.*\.ts/i) && !url.endsWith('.ts')) {
                        checkUrlForVideo(url);
                    }
                    if (url.includes('.m3u8') || url.includes('.m3u')) {
                        detectM3U8(url);
                    }
                }
                return response;
            }).catch(e => {
                throw e;
            });
            return promise;
        };

        const originalOpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(...args) {
            this.addEventListener("load", function() {
                try {
                    const url = args[1];
                    if (url) {
                        if (!url.match(/seg-\d+-.*\.ts/i) && !url.endsWith('.ts')) {
                            checkUrlForVideo(url);
                        }
                        if (url.includes('.m3u8') || url.includes('.m3u')) {
                            detectM3U8(url);
                        }
                        if (this.responseText && this.responseText.trim().startsWith("#EXTM3U")) {
                            detectM3U8(url);
                        }
                    }
                } catch(e) {}
            });
            return originalOpen.apply(this, args);
        };

        const originalText = Response.prototype.text;
        Response.prototype.text = function() {
            return originalText.call(this).then(text => {
                if (text.trim().startsWith("#EXTM3U")) {
                    detectM3U8(this.url);
                }
                return text;
            });
        };

        const originalSetAttribute = Element.prototype.setAttribute;
        Element.prototype.setAttribute = function(name, value) {
            if (this.tagName === 'VIDEO' || this.tagName === 'SOURCE') {
                if (name === 'src' && value) {
                    checkUrlForVideo(value);
                }
            }
            return originalSetAttribute.call(this, name, value);
        };
    })();

    function checkUrlForVideo(url) {
        try {
            if (url.startsWith('blob:')) return;
            if (url.match(/seg-\d+-.*\.ts/i)) return;
            if (url.endsWith('.ts')) return;
            
            // CHECK M3U8 FIRST! This is critical
            if (url.includes('.m3u8') || url.includes('.m3u')) {
                debugLog('[VIDEO CHECK] Skipping - is M3U8: ' + url);
                return; // M3U8 will be handled by detectM3U8
            }
            
            const lowerUrl = url.toLowerCase();
            const isVideo = VIDEO_EXTENSIONS.some(ext => lowerUrl.includes(ext));
            
            if (isVideo) {
                const fullUrl = new URL(url, location.href).href;
                if (!allDetectedVideos.has(fullUrl)) {
                    debugLog('[VIDEO] Found video file: ' + fullUrl);
                    allDetectedVideos.set(fullUrl, {
                        url: fullUrl,
                        type: 'video',
                        timestamp: Date.now(),
                        title: 'Video - ' + getFilenameFromUrl(fullUrl)
                    });
                    checkForVideos();
                }
            }
        } catch(e) {
            debugLog('[ERROR] checkUrlForVideo: ' + e.message);
        }
    }

    function getFilenameFromUrl(url) {
        try {
            const pathname = new URL(url).pathname;
            const filename = pathname.split('/').pop();
            return filename || 'Unknown';
        } catch(e) {
            return 'Unknown';
        }
    }

    function getTimeAgo(timestamp) {
        const seconds = Math.floor((Date.now() - timestamp) / 1000);
        if (seconds < 60) return 'just now';
        const minutes = Math.floor(seconds / 60);
        if (minutes < 60) return minutes + 'm ago';
        const hours = Math.floor(minutes / 60);
        if (hours < 24) return hours + 'h ago';
        const days = Math.floor(hours / 24);
        return days + 'd ago';
    }

    function getBaseUrlFromSegment(segmentUrl) {
        try {
            const url = new URL(segmentUrl);
            const path = url.pathname.substring(0, url.pathname.lastIndexOf('/') + 1);
            return url.origin + path;
        } catch(e) {
            return null;
        }
    }

    function isSegmentOfPlaylist(videoUrl) {
        if (!videoUrl.endsWith('.ts')) return false;
        if (!videoUrl.match(/seg-\d+-/i)) return false;
        
        const baseUrl = getBaseUrlFromSegment(videoUrl);
        if (!baseUrl) return false;
        
        for (const [m3u8Url, data] of allDetectedVideos.entries()) {
            if (data.type === 'm3u8') {
                const m3u8Base = getBaseUrlFromSegment(m3u8Url);
                if (m3u8Base && baseUrl.startsWith(m3u8Base)) {
                    return true;
                }
            }
        }
        
        return false;
    }

    function hasM3U8Playlist() {
        return Array.from(allDetectedVideos.values()).some(v => v.type === 'm3u8');
    }

    function isMasterPlaylist(url, manifest) {
        if (url.includes('master.m3u8')) return true;
        if (manifest.playlists && manifest.playlists.length > 0 && (!manifest.segments || manifest.segments.length === 0)) {
            return true;
        }
        return false;
    }

    function shouldFilterM3U8(url, manifest) {
        if (!isMasterPlaylist(url, manifest)) return false;
        
        const baseUrl = getBaseUrlFromSegment(url);
        if (!baseUrl) return false;
        
        for (const [otherUrl, data] of allDetectedVideos.entries()) {
            if (data.type === 'm3u8' && otherUrl !== url) {
                const otherBase = getBaseUrlFromSegment(otherUrl);
                if (otherBase === baseUrl && otherUrl.includes('index-')) {
                    debugLog('[M3U8] Filtering master playlist: ' + url);
                    return true;
                }
            }
        }
        
        return false;
    }

    async function detectM3U8(url) {
        try {
            if (url.startsWith('blob:')) return;
            
            url = new URL(url, location.href).href;
            
            if (detectedM3U8Urls.includes(url)) {
                debugLog('[M3U8] Already detected: ' + url);
                return;
            }
            detectedM3U8Urls.push(url);

            debugLog('[M3U8] *** DETECTED M3U8 URL ***: ' + url);
            debugLog('[M3U8] Fetching manifest...');
            
            const response = await fetch(url);
            const content = await response.text();
            
            const parser = new m3u8Parser.Parser();
            parser.push(content);
            parser.end();
            const manifest = parser.manifest;

            let duration = 0;
            if (manifest.segments) {
                for (var s = 0; s < manifest.segments.length; s++) {
                    duration += manifest.segments[s].duration;
                }
                debugLog('[M3U8] Found ' + manifest.segments.length + ' segments, duration: ' + duration + 's');
            } else {
                debugLog('[M3U8] No segments (master playlist)');
            }

            const m3u8Data = {
                url: url,
                manifest: manifest,
                content: content,
                duration: duration,
                title: 'M3U8 - ' + (duration ? Math.ceil(duration / 60) + 'min' : 'Unknown'),
                timestamp: Date.now()
            };

            detectedM3U8s.push(m3u8Data);
            
            // CRITICAL: Set type as 'm3u8' NOT 'video'
            debugLog('[M3U8] *** ADDING TO MAP AS TYPE m3u8 ***');
            allDetectedVideos.set(url, {
                url: url,
                type: 'm3u8', // THIS IS THE KEY!
                timestamp: Date.now(),
                title: m3u8Data.title,
                m3u8Data: m3u8Data
            });

            checkForVideos();
        } catch(e) {
            debugLog('[ERROR] M3U8 parse failed: ' + e.message);
        }
    }

    function getVideoUrl(videoElement) {
        if (videoElement.tagName === 'VIDEO') {
            if (videoElement.currentSrc && !videoElement.currentSrc.startsWith('blob:')) 
                return videoElement.currentSrc;
            if (videoElement.src && !videoElement.src.startsWith('blob:')) 
                return videoElement.src;
            
            var sources = videoElement.querySelectorAll('source');
            for (var i = 0; i < sources.length; i++) {
                if (sources[i].src && !sources[i].src.startsWith('blob:')) 
                    return sources[i].src;
            }
        }

        if (videoElement.tagName === 'IFRAME') {
            return videoElement.src;
        }

        return null;
    }

    function getUniqueVideos() {
        var videos = [];
        var seenUrls = new Set();
        
        debugLog('[GET VIDEOS] Checking allDetectedVideos map...');
        debugLog('[GET VIDEOS] Map size: ' + allDetectedVideos.size);
        
        allDetectedVideos.forEach(function(videoData, url) {
            debugLog('[GET VIDEOS] Processing: ' + videoData.type + ' - ' + url);
            
            if (hasM3U8Playlist() && isSegmentOfPlaylist(videoData.url)) {
                debugLog('[GET VIDEOS] Skipping segment: ' + url);
                return;
            }
            
            if (videoData.type === 'm3u8' && shouldFilterM3U8(videoData.url, videoData.m3u8Data.manifest)) {
                debugLog('[GET VIDEOS] Filtering master playlist: ' + url);
                return;
            }
            
            if (!seenUrls.has(videoData.url)) {
                seenUrls.add(videoData.url);
                videos.push(videoData);
                debugLog('[GET VIDEOS] Added to list: ' + videoData.type + ' - ' + url);
            }
        });
        
        debugLog('[GET VIDEOS] Final count: ' + videos.length);
        
        for (var s = 0; s < VIDEO_SELECTORS.length; s++) {
            var elements = document.querySelectorAll(VIDEO_SELECTORS[s]);
            
            for (var i = 0; i < elements.length; i++) {
                var element = elements[i];
                var rect = element.getBoundingClientRect();
                
                if (rect.width > 100 && rect.height > 100) {
                    var url = getVideoUrl(element);
                    
                    // Skip if it's an M3U8 URL
                    if (url && (url.includes('.m3u8') || url.includes('.m3u'))) {
                        debugLog('[GET VIDEOS] Skipping M3U8 from video element: ' + url);
                        continue;
                    }
                    
                    if (url && !seenUrls.has(url)) {
                        seenUrls.add(url);
                        const videoData = {
                            type: 'video',
                            element: element,
                            url: url,
                            title: element.title || element.alt || ('Video ' + (videos.length + 1)),
                            timestamp: Date.now()
                        };
                        videos.push(videoData);
                        allDetectedVideos.set(url, videoData);
                        debugLog('[GET VIDEOS] Added from element: video - ' + url);
                    }
                }
            }
        }
        
        var iframes = document.querySelectorAll('iframe');
        for (var i = 0; i < iframes.length; i++) {
            try {
                var iframeDoc = iframes[i].contentDocument || iframes[i].contentWindow.document;
                if (iframeDoc) {
                    var iframeVideos = iframeDoc.querySelectorAll('video');
                    for (var v = 0; v < iframeVideos.length; v++) {
                        var url = getVideoUrl(iframeVideos[v]);
                        
                        if (url && (url.includes('.m3u8') || url.includes('.m3u'))) {
                            debugLog('[GET VIDEOS] Skipping M3U8 from iframe: ' + url);
                            continue;
                        }
                        
                        if (url && !seenUrls.has(url)) {
                            seenUrls.add(url);
                            const videoData = {
                                type: 'video',
                                element: iframeVideos[v],
                                url: url,
                                title: 'Iframe Video ' + (videos.length + 1),
                                timestamp: Date.now()
                            };
                            videos.push(videoData);
                            allDetectedVideos.set(url, videoData);
                        }
                    }
                }
            } catch(e) {}
        }
        
        return videos;
    }

    function getButtonIcon(videos) {
        if (videos.length > 1) return '⇓';
        if (videos.length === 1) {
            return videos[0].type === 'm3u8' ? '⇣' : '↯';
        }
        return '▶';
    }

    function createFloatingButton() {
        if (floatingButton) return floatingButton;

        var container = document.createElement('div');
        container.id = 'universal-video-share-container';
        container.style.cssText = 'position: fixed; top: 15px; left: 15px; width: 40px; height: 40px; z-index: 999999;';

        var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.setAttribute('width', '40');
        svg.setAttribute('height', '40');
        svg.style.cssText = 'position: absolute; top: 0; left: 0; transform: rotate(-90deg);';
        
        var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        circle.setAttribute('cx', '20');
        circle.setAttribute('cy', '20');
        circle.setAttribute('r', '18');
        circle.setAttribute('fill', 'none');
        circle.setAttribute('stroke', '#4ade80');
        circle.setAttribute('stroke-width', '3');
        circle.setAttribute('stroke-dasharray', '113');
        circle.setAttribute('stroke-dashoffset', '113');
        circle.setAttribute('stroke-linecap', 'round');
        circle.style.cssText = 'transition: stroke-dashoffset 0.3s ease;';
        circle.id = 'progress-circle';
        
        svg.appendChild(circle);
        container.appendChild(svg);

        floatingButton = document.createElement('div');
        floatingButton.innerHTML = '▶';
        floatingButton.id = 'universal-video-share-float';
        
        floatingButton.style.cssText = `position: absolute; top: 2px; left: 2px; width: 36px; height: 36px; background: ${COLORS.button}; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); color: ${COLORS.icon}; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 50%; font-size: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; user-select: none; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);`;
        
        container.appendChild(floatingButton);
        floatingButton.progressCircle = circle;

        floatingButton.addEventListener('mouseenter', function() {
            this.style.background = COLORS.buttonHover;
            this.style.transform = 'scale(1.05)';
        });

        floatingButton.addEventListener('mouseleave', function() {
            this.style.background = COLORS.button;
            this.style.transform = 'scale(1)';
        });

        floatingButton.addEventListener('mousedown', function(e) {
            e.preventDefault();
            isLongPress = false;
            longPressStartTime = Date.now();
            pressTimer = setTimeout(function() {
                isLongPress = true;
                var duration = Date.now() - longPressStartTime;
                if (duration >= 3000) {
                    debugMode = true;
                    floatingButton.style.background = 'rgba(239, 68, 68, 0.8)';
                    floatingButton.innerHTML = '🐛';
                    debugLog('='.repeat(50));
                    debugLog('[INFO] DEBUG MODE ACTIVATED');
                    debugLog('[INFO] Platform - iOS: ' + isIOS + ', Safari: ' + isSafari);
                    debugLog('[INFO] User Agent: ' + navigator.userAgent);
                    debugLog('[INFO] Share API: ' + !!navigator.share);
                    debugLog('='.repeat(50));
                } else {
                    floatingButton.style.background = 'rgba(74, 222, 128, 0.8)';
                    floatingButton.innerHTML = '⎘';
                }
            }, 500);
            
            setTimeout(function() {
                if (Date.now() - longPressStartTime >= 2900) {
                    debugMode = true;
                    floatingButton.style.background = 'rgba(239, 68, 68, 0.8)';
                    floatingButton.innerHTML = '🐛';
                }
            }, 3000);
        });

        floatingButton.addEventListener('mouseup', function(e) {
            e.preventDefault();
            clearTimeout(pressTimer);
            
            var pressDuration = Date.now() - longPressStartTime;
            
            var videos = getUniqueVideos();
            floatingButton.style.background = COLORS.button;
            floatingButton.innerHTML = getButtonIcon(videos);
            
            if (debugMode) {
                createDebugConsole();
            } else if (isLongPress && pressDuration < 3000) {
                handleCopy();
            } else if (!isLongPress) {
                handleShare();
            }
        });

        floatingButton.addEventListener('mouseleave', function() {
            clearTimeout(pressTimer);
            var videos = getUniqueVideos();
            floatingButton.style.background = COLORS.button;
            floatingButton.innerHTML = getButtonIcon(videos);
        });

        floatingButton.addEventListener('touchstart', function(e) {
            e.preventDefault();
            isLongPress = false;
            longPressStartTime = Date.now();
            pressTimer = setTimeout(function() {
                isLongPress = true;
                var duration = Date.now() - longPressStartTime;
                if (duration >= 3000) {
                    debugMode = true;
                    floatingButton.style.background = 'rgba(239, 68, 68, 0.8)';
                    floatingButton.innerHTML = '🐛';
                    navigator.vibrate && navigator.vibrate(200);
                    debugLog('='.repeat(50));
                    debugLog('[INFO] DEBUG MODE ACTIVATED');
                    debugLog('[INFO] Platform - iOS: ' + isIOS + ', Safari: ' + isSafari);
                    debugLog('[INFO] User Agent: ' + navigator.userAgent);
                    debugLog('[INFO] Share API: ' + !!navigator.share);
                    debugLog('='.repeat(50));
                } else {
                    floatingButton.style.background = 'rgba(74, 222, 128, 0.8)';
                    floatingButton.innerHTML = '⎘';
                    navigator.vibrate && navigator.vibrate(100);
                }
            }, 500);
            
            setTimeout(function() {
                if (Date.now() - longPressStartTime >= 2900) {
                    debugMode = true;
                    floatingButton.style.background = 'rgba(239, 68, 68, 0.8)';
                    floatingButton.innerHTML = '🐛';
                    navigator.vibrate && navigator.vibrate(200);
                }
            }, 3000);
        });

        floatingButton.addEventListener('touchend', function(e) {
            e.preventDefault();
            clearTimeout(pressTimer);
            
            var pressDuration = Date.now() - longPressStartTime;
            
            var videos = getUniqueVideos();
            floatingButton.style.background = COLORS.button;
            floatingButton.innerHTML = getButtonIcon(videos);
            
            if (debugMode) {
                createDebugConsole();
            } else if (isLongPress && pressDuration < 3000) {
                handleCopy();
            } else if (!isLongPress) {
                handleShare();
            }
        });

        document.body.appendChild(container);
        return floatingButton;
    }

    function updateProgress(percent) {
        if (!floatingButton || !floatingButton.progressCircle) return;
        var offset = 113 - (113 * percent / 100);
        floatingButton.progressCircle.setAttribute('stroke-dashoffset', offset);
    }

    function resetProgress() {
        if (!floatingButton || !floatingButton.progressCircle) return;
        floatingButton.progressCircle.setAttribute('stroke-dashoffset', '113');
    }

    function handleShare() {
        debugLog('[SHARE] Button clicked');
        showNotification('🔍 Checking videos...', 'info');
        
        var videos = getUniqueVideos();
        
        debugLog('[SHARE] Videos found: ' + videos.length);
        videos.forEach(v => debugLog('[SHARE] - ' + v.type + ': ' + v.url));
        
        if (videos.length === 0) {
            showNotification('❌ No videos found', 'error');
            debugLog('[ERROR] No videos found');
            return;
        }
        
        if (videos.length === 1) {
            debugLog('[SHARE] Single video, calling shareVideo');
            shareVideo(videos[0]);
        } else {
            debugLog('[SHARE] Multiple videos, showing selector');
            showVideoSelector(videos, 'share');
        }
    }

    function handleCopy() {
        debugLog('[COPY] Long press detected');
        showNotification('📋 Long press detected...', 'info');
        
        var videos = getUniqueVideos();
        
        debugLog('[COPY] Videos found: ' + videos.length);
        
        if (videos.length === 0) {
            showNotification('❌ No videos found', 'error');
            debugLog('[ERROR] No videos found');
            return;
        }
        
        if (videos.length === 1) {
            if (videos[0].type === 'm3u8') {
                debugLog('[COPY] M3U8 detected, forcing download');
                showNotification('📥 Downloading M3U8...', 'info');
                downloadM3U8(videos[0], true);
            } else {
                copyVideoUrl(videos[0]);
            }
        } else {
            showVideoSelector(videos, 'copy');
        }
    }

    function showVideoSelector(videos, action) {
        var existingSelector = document.querySelector('#video-selector-popup');
        if (existingSelector) {
            existingSelector.remove();
        }

        var popup = document.createElement('div');
        popup.id = 'video-selector-popup';
        popup.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); z-index: 9999999; display: flex; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box;';

        var container = document.createElement('div');
        container.style.cssText = 'background: rgba(20, 20, 20, 0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 12px; padding: 24px; max-width: 600px; max-height: 70%; overflow-y: auto; position: relative; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);';

        var closeButton = document.createElement('button');
        closeButton.innerHTML = '✕';
        closeButton.style.cssText = `position: absolute; top: 12px; right: 12px; background: rgba(255, 255, 255, 0.1); border: none; font-size: 16px; cursor: pointer; color: ${COLORS.text}; width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: background 0.2s;`;

        closeButton.addEventListener('click', function() {
            popup.remove();
        });
        
        closeButton.addEventListener('mouseenter', function() {
            this.style.background = 'rgba(255, 255, 255, 0.2)';
        });
        
        closeButton.addEventListener('mouseleave', function() {
            this.style.background = 'rgba(255, 255, 255, 0.1)';
        });

        var title = document.createElement('h3');
        title.textContent = 'Select Video to ' + (action.charAt(0).toUpperCase() + action.slice(1));
        title.style.cssText = `margin: 0 0 16px 0; color: ${COLORS.text}; font-size: 16px; font-weight: 600; text-align: center;`;

        container.appendChild(closeButton);
        container.appendChild(title);

        videos.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));

        for (var i = 0; i < videos.length; i++) {
            var videoData = videos[i];
            var videoItem = document.createElement('div');
            videoItem.style.cssText = 'margin-bottom: 12px; padding: 12px; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; cursor: pointer; transition: all 0.2s ease; background: rgba(255, 255, 255, 0.05);';

            (function(currentVideoData) {
                videoItem.addEventListener('mouseenter', function() {
                    this.style.borderColor = 'rgba(255, 255, 255, 0.3)';
                    this.style.background = 'rgba(255, 255, 255, 0.1)';
                });

                videoItem.addEventListener('mouseleave', function() {
                    this.style.borderColor = 'rgba(255, 255, 255, 0.1)';
                    this.style.background = 'rgba(255, 255, 255, 0.05)';
                });

                var headerDiv = document.createElement('div');
                headerDiv.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;';

                var typeBadge = document.createElement('span');
                typeBadge.textContent = currentVideoData.type === 'm3u8' ? 'M3U8' : 'VIDEO';
                typeBadge.style.cssText = 'display: inline-block; background: ' + (currentVideoData.type === 'm3u8' ? 'rgba(239, 68, 68, 0.8)' : 'rgba(59, 130, 246, 0.8)') + `; color: ${COLORS.text}; padding: 3px 8px; border-radius: 4px; font-size: 10px; font-weight: 600;`;
                
                var timeAgo = document.createElement('span');
                timeAgo.textContent = getTimeAgo(currentVideoData.timestamp || Date.now());
                timeAgo.style.cssText = `color: ${COLORS.text}; opacity: 0.6; font-size: 10px;`;
                
                headerDiv.appendChild(typeBadge);
                headerDiv.appendChild(timeAgo);
                videoItem.appendChild(headerDiv);

                var videoInfo = document.createElement('div');
                videoInfo.innerHTML = `<div style="color: ${COLORS.text}; font-size: 13px; font-weight: 500; margin-bottom: 4px;">` + currentVideoData.title + `</div><div style="color: ${COLORS.text}; opacity: 0.5; font-size: 11px; word-break: break-all; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">` + currentVideoData.url + '</div>';
                videoItem.appendChild(videoInfo);

                videoItem.addEventListener('click', function() {
                    popup.remove();
                    if (action === 'share') {
                        shareVideo(currentVideoData);
                    } else {
                        if (currentVideoData.type === 'm3u8') {
                            downloadM3U8(currentVideoData, true);
                        } else {
                            copyVideoUrl(currentVideoData);
                        }
                    }
                });
            })(videoData);

            container.appendChild(videoItem);
        }

        popup.appendChild(container);

        popup.addEventListener('click', function(e) {
            if (e.target === popup) {
                popup.remove();
            }
        });

        document.body.appendChild(popup);
    }

    async function downloadM3U8(videoData, forceDownload = false) {
        debugLog('='.repeat(50));
        debugLog('[M3U8] STARTING M3U8 DOWNLOAD');
        debugLog('[M3U8] URL: ' + videoData.url);
        debugLog('[M3U8] Force download: ' + forceDownload);
        debugLog('[M3U8] Platform - iOS: ' + isIOS + ', Safari: ' + isSafari);
        
        const cachedBlob = downloadedBlobs.get(videoData.url);
        if (cachedBlob && !forceDownload) {
            debugLog('[M3U8] Using cached blob');
            showNotification('♻️ Using cached video...', 'info');
            const filename = cachedBlob.filename;
            await shareOrDownloadBlob(cachedBlob.blob, filename, videoData, forceDownload);
            return;
        }
        
        showNotification('📥 Starting M3U8 download...', 'info');
        resetProgress();
        
        try {
            const manifest = videoData.m3u8Data.manifest;
            debugLog('[M3U8] Manifest playlists: ' + (manifest.playlists ? manifest.playlists.length : 0));
            debugLog('[M3U8] Manifest segments: ' + (manifest.segments ? manifest.segments.length : 0));
            
            const baseUrl = videoData.url.substring(0, videoData.url.lastIndexOf('/') + 1);
            debugLog('[M3U8] Base URL: ' + baseUrl);
            
            const segments = manifest.segments;
            
            if (!segments || segments.length === 0) {
                const errorMsg = "No segments in playlist! This is a master playlist.";
                debugLog('[ERROR] ' + errorMsg);
                showNotification('❌ ' + errorMsg, 'error');
                throw new Error(errorMsg);
            }

            debugLog('[M3U8] Found ' + segments.length + ' segments');
            showNotification(`📥 Downloading ${segments.length} segments...`, 'info');

            var segmentData = [];
            var totalSize = 0;
            
            for (var i = 0; i < segments.length; i++) {
                var segUrl = segments[i].uri.startsWith('http') 
                    ? segments[i].uri 
                    : baseUrl + segments[i].uri;
                
                debugLog('[M3U8] Segment ' + (i+1) + '/' + segments.length + ': ' + segUrl);
                
                var response = await fetch(segUrl);
                if (!response.ok) {
                    throw new Error('Segment ' + (i+1) + ' failed: ' + response.status);
                }
                
                var data = await response.arrayBuffer();
                totalSize += data.byteLength;
                segmentData.push(data);
                
                var percent = Math.floor((i + 1) / segments.length * 100);
                updateProgress(percent);
                
                debugLog('[M3U8] Progress: ' + percent + '%, Size: ' + (totalSize/1024/1024).toFixed(2) + 'MB');
                
                if (percent % 10 === 0 || i === segments.length - 1) {
                    showNotification(`📥 ${percent}% (${(totalSize/1024/1024).toFixed(1)}MB)`, 'info');
                }
            }

            debugLog('[M3U8] All segments downloaded, merging...');
            showNotification('🔄 Merging segments...', 'info');
            
            var merged = new Blob(segmentData, { type: 'video/mp2t' });
            debugLog('[M3U8] Merged blob size: ' + (merged.size/1024/1024).toFixed(2) + 'MB');
            
            var urlPath = new URL(videoData.url).pathname;
            var baseName = urlPath.split('/').pop().replace(/\.(m3u8?|ts)$/i, '') || 'video';
            var filename = baseName + '-noenc.ts';
            
            debugLog('[M3U8] Filename: ' + filename);
            
            downloadedBlobs.set(videoData.url, { blob: merged, filename: filename });
            
            showNotification(`✅ Merged! ${(merged.size/1024/1024).toFixed(1)}MB`, 'info');
            
            await shareOrDownloadBlob(merged, filename, videoData, forceDownload);
            
        } catch(e) {
            debugLog('[ERROR] M3U8 download failed: ' + e.message);
            debugLog('[ERROR] Stack: ' + e.stack);
            showNotification('❌ Failed: ' + e.message, 'error');
            resetProgress();
        }
    }

    async function shareOrDownloadBlob(blob, filename, videoData, forceDownload = false) {
        debugLog('='.repeat(50));
        debugLog('[SHARE/DL] SHARE OR DOWNLOAD BLOB');
        debugLog('[SHARE/DL] Blob size: ' + (blob.size/1024/1024).toFixed(2) + 'MB');
        debugLog('[SHARE/DL] Filename: ' + filename);
        debugLog('[SHARE/DL] Force download: ' + forceDownload);
        debugLog('[SHARE/DL] navigator.share exists: ' + !!navigator.share);
        debugLog('[SHARE/DL] navigator.canShare exists: ' + !!navigator.canShare);
        
        const processed = processedVideos.get(videoData.url);
        
        if (processed && !forceDownload) {
            debugLog('[SHARE/DL] Already processed, forcing download');
            forceDownload = true;
            showNotification('🔄 Re-downloading...', 'info');
        }
        
        if (!forceDownload && navigator.share) {
            debugLog('[SHARE/DL] Attempting to share...');
            
            try {
                var file = new File([blob], filename, { type: 'video/mp2t' });
                debugLog('[SHARE/DL] Created File object: ' + file.name + ' (' + file.size + ' bytes)');
                
                var canShareFiles = false;
                if (navigator.canShare) {
                    canShareFiles = navigator.canShare({ files: [file] });
                    debugLog('[SHARE/DL] canShare result: ' + canShareFiles);
                } else {
                    debugLog('[SHARE/DL] canShare not available, trying anyway');
                    canShareFiles = true;
                }
                
                if (canShareFiles) {
                    showNotification('📤 Opening share...', 'info');
                    debugLog('[SHARE/DL] Calling navigator.share...');
                    
                    var shareStartTime = Date.now();
                    
                    try {
                        await navigator.share({
                            files: [file],
                            title: filename,
                            text: 'Video file'
                        });
                        
                        var elapsed = Date.now() - shareStartTime;
                        debugLog('[SHARE/DL] Share completed (took ' + elapsed + 'ms)');
                        processedVideos.set(videoData.url, { action: 'shared', timestamp: Date.now() });
                        showNotification('✅ Shared!', 'success');
                        resetProgress();
                        return;
                        
                    } catch(e) {
                        var elapsed = Date.now() - shareStartTime;
                        debugLog('[SHARE/DL] Share error after ' + elapsed + 'ms: ' + e.name + ' - ' + e.message);
                        
                        if (elapsed > 2000) {
                            debugLog('[SHARE/DL] Assuming success (>2s)');
                            processedVideos.set(videoData.url, { action: 'shared', timestamp: Date.now() });
                            showNotification('✅ Share completed', 'success');
                            resetProgress();
                            return;
                        }
                        
                        debugLog('[SHARE/DL] Share failed/cancelled, will download');
                        showNotification('⚠️ Share cancelled', 'info');
                    }
                } else {
                    debugLog('[SHARE/DL] Cannot share files');
                    showNotification('⚠️ Share not supported', 'info');
                }
            } catch(e) {
                debugLog('[ERROR] File/Share error: ' + e.message);
                showNotification('⚠️ Share error', 'info');
            }
        } else {
            debugLog('[SHARE/DL] Skipping share (force=' + forceDownload + ', hasAPI=' + !!navigator.share + ')');
        }
        
        debugLog('[SHARE/DL] Initiating download...');
        showNotification('💾 Downloading...', 'info');
        
        var blobUrl = URL.createObjectURL(blob);
        debugLog('[SHARE/DL] Blob URL: ' + blobUrl);
        
        var a = document.createElement('a');
        a.href = blobUrl;
        a.download = filename;
        a.style.display = 'none';
        document.body.appendChild(a);
        
        debugLog('[SHARE/DL] Clicking download link...');
        a.click();
        
        setTimeout(function() {
            document.body.removeChild(a);
            URL.revokeObjectURL(blobUrl);
            debugLog('[SHARE/DL] Cleanup complete');
        }, 1000);
        
        processedVideos.set(videoData.url, { action: 'downloaded', timestamp: Date.now() });
        showNotification('✅ Downloaded: ' + filename, 'success');
        resetProgress();
        debugLog('[SUCCESS] Download complete!');
    }

    function shareVideo(videoData) {
        debugLog('='.repeat(50));
        debugLog('[SHARE] shareVideo() called');
        debugLog('[SHARE] Type: ' + videoData.type);
        debugLog('[SHARE] URL: ' + videoData.url);
        
        if (videoData.type === 'm3u8') {
            debugLog('[SHARE] *** M3U8 DETECTED - DOWNLOADING ***');
            showNotification('🎬 M3U8 - downloading...', 'info');
            
            const processed = processedVideos.get(videoData.url);
            const forceDownload = processed && (processed.action === 'shared' || processed.action === 'downloaded');
            debugLog('[SHARE] Force download: ' + forceDownload);
            
            downloadM3U8(videoData, forceDownload);
            return;
        }

        debugLog('[SHARE] Regular video, sharing URL');
        const processed = processedVideos.get(videoData.url);
        if (processed && (processed.action === 'shared' || processed.action === 'copied')) {
            downloadVideo(videoData);
            return;
        }

        if (navigator.share) {
            var shareStartTime = Date.now();
            navigator.share({
                title: document.title,
                url: videoData.url
            }).then(function() {
                processedVideos.set(videoData.url, { action: 'shared', timestamp: Date.now() });
                showNotification('✅ Shared!', 'success');
                debugLog('[SUCCESS] URL shared');
            }).catch(function(error) {
                if (Date.now() - shareStartTime > 2000) {
                    processedVideos.set(videoData.url, { action: 'shared', timestamp: Date.now() });
                    showNotification('✅ Share completed', 'success');
                    debugLog('[SUCCESS] Share completed');
                } else {
                    debugLog('[INFO] Share cancelled: ' + error);
                    copyVideoUrl(videoData);
                }
            });
        } else {
            copyVideoUrl(videoData);
        }
    }

    function downloadVideo(videoData) {
        showNotification('📂 Opening video...', 'info');
        debugLog('[INFO] Opening video in new tab: ' + videoData.url);
        window.open(videoData.url, '_blank');
        processedVideos.set(videoData.url, { action: 'downloaded', timestamp: Date.now() });
    }

    function copyVideoUrl(videoData) {
        const processed = processedVideos.get(videoData.url);
        if (processed && processed.action === 'copied') {
            downloadVideo(videoData);
            return;
        }
        
        if (navigator.clipboard && navigator.clipboard.writeText) {
            var copyStartTime = Date.now();
            navigator.clipboard.writeText(videoData.url).then(function() {
                processedVideos.set(videoData.url, { action: 'copied', timestamp: Date.now() });
                showNotification('✅ URL copied', 'success');
                debugLog('[SUCCESS] URL copied to clipboard');
            }).catch(function(error) {
                if (Date.now() - copyStartTime > 2000) {
                    processedVideos.set(videoData.url, { action: 'copied', timestamp: Date.now() });
                    showNotification('✅ URL copied', 'success');
                    debugLog('[SUCCESS] URL copied');
                } else {
                    fallbackCopy(videoData.url, videoData);
                }
            });
        } else {
            fallbackCopy(videoData.url, videoData);
        }
    }

    function fallbackCopy(url, videoData) {
        try {
            var textArea = document.createElement('textarea');
            textArea.value = url;
            textArea.style.position = 'fixed';
            textArea.style.left = '-999999px';
            document.body.appendChild(textArea);
            textArea.select();
            
            if (document.execCommand('copy')) {
                processedVideos.set(videoData.url, { action: 'copied', timestamp: Date.now() });
                showNotification('✅ URL copied', 'success');
                debugLog('[SUCCESS] URL copied (fallback)');
            } else {
                showNotification('❌ Copy failed', 'error');
                debugLog('[ERROR] Copy failed');
            }
            
            document.body.removeChild(textArea);
        } catch (err) {
            showNotification('❌ Copy failed', 'error');
            debugLog('[ERROR] Copy failed: ' + err.message);
        }
    }

    function showNotification(message, type) {
        if (type === undefined) type = 'success';
        
        debugLog('[NOTIF] ' + message);
        
        var existingNotifications = document.querySelectorAll('.universal-video-notification');
        for (var i = 0; i < existingNotifications.length; i++) {
            existingNotifications[i].remove();
        }

        var notification = document.createElement('div');
        notification.textContent = message;
        notification.className = 'universal-video-notification';
        
        var bgColor = type === 'success' ? 'rgba(74, 222, 128, 0.9)' : 
                      type === 'error' ? 'rgba(239, 68, 68, 0.9)' : 
                      'rgba(59, 130, 246, 0.9)';
        
        notification.style.cssText = `position: fixed; top: 65px; left: 15px; background: ${bgColor}; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); color: ${COLORS.text}; padding: 10px 14px; border-radius: 8px; z-index: 999998; font-weight: 600; font-size: 13px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); max-width: 280px; word-wrap: break-word; transform: translateX(-350px); transition: transform 0.3s ease;`;

        document.body.appendChild(notification);
        
        setTimeout(function() {
            notification.style.transform = 'translateX(0)';
        }, 100);

        setTimeout(function() {
            if (notification.parentNode) {
                notification.style.transform = 'translateX(-350px)';
                setTimeout(function() {
                    notification.remove();
                }, 300);
            }
        }, 4000);
    }

    function checkForVideos() {
        var videos = getUniqueVideos();
        
        if (videos.length > 0) {
            if (!floatingButton) {
                createFloatingButton();
            }
            if (floatingButton) {
                floatingButton.innerHTML = getButtonIcon(videos);
            }
        } else {
            if (floatingButton && floatingButton.parentNode) {
                floatingButton.parentNode.remove();
                floatingButton = null;
            }
        }
    }

    function init() {
        debugLog('='.repeat(50));
        debugLog('[INIT] Universal Video Share v4.9');
        debugLog('[INIT] License: MIT');
        debugLog('[INIT] Platform - iOS: ' + isIOS + ', Safari: ' + isSafari + ', Mobile: ' + isMobile);
        debugLog('[INIT] User Agent: ' + navigator.userAgent);
        debugLog('[INIT] Share API: ' + !!navigator.share);
        debugLog('[INIT] Hold button 3+ seconds for debug console');
        debugLog('='.repeat(50));
        
        setTimeout(checkForVideos, 1000);
        checkInterval = setInterval(checkForVideos, 5000);
    }

    init();

})();