// ==UserScript== // @name Universal Video Share Button with M3U8 Support // @namespace http://tampermonkey.net/ // @version 5.2 // @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/m3u8-parser@4.7.1/dist/m3u8-parser.min.js // @grant none // @downloadURL 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', shadow: 'rgba(85, 66, 61, 0.3)' }; // 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']; // Helper function to check if URL is M3U8 function isM3U8Url(url) { if (!url) return false; const lowerUrl = url.toLowerCase(); return lowerUrl.includes('.m3u8') || lowerUrl.includes('.m3u'); } // 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 = `🐛 DEBUG CONSOLE`; 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 (isM3U8Url(url)) { detectM3U8(url); } else if (!url.match(/seg-\d+-.*\.ts/i) && !url.endsWith('.ts')) { checkUrlForVideo(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 (isM3U8Url(url)) { detectM3U8(url); } else if (!url.match(/seg-\d+-.*\.ts/i) && !url.endsWith('.ts')) { checkUrlForVideo(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) { if (isM3U8Url(value)) { detectM3U8(value); } else { 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; if (isM3U8Url(url)) { debugLog('[VIDEO CHECK] URL contains m3u8, skipping: ' + url); return; } 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; // Get URL without query params for comparison const urlWithoutQuery = url.split('?')[0]; // Check if we already have a better version of this URL let alreadyHasBetter = false; for (const [existingUrl, existingData] of allDetectedVideos.entries()) { if (existingData.type === 'm3u8') { const existingWithoutQuery = existingUrl.split('?')[0]; if (existingWithoutQuery === urlWithoutQuery) { if (existingData.m3u8Data && existingData.m3u8Data.manifest && existingData.m3u8Data.manifest.segments && existingData.m3u8Data.manifest.segments.length > 0) { debugLog('[M3U8] Already have better version with segments: ' + existingUrl); alreadyHasBetter = true; break; } } } } if (alreadyHasBetter) return; 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; // CHECK IF THIS IS A MASTER PLAYLIST if (manifest.playlists && manifest.playlists.length > 0 && (!manifest.segments || manifest.segments.length === 0)) { debugLog('[M3U8] This is a MASTER playlist with ' + manifest.playlists.length + ' variants'); // Get the highest quality variant (usually last one) const bestVariant = manifest.playlists[manifest.playlists.length - 1]; debugLog('[M3U8] Selected variant: ' + JSON.stringify(bestVariant)); // Construct the full URL for the variant const baseUrl = url.substring(0, url.lastIndexOf('/') + 1); const variantUrl = bestVariant.uri.startsWith('http') ? bestVariant.uri : baseUrl + bestVariant.uri; debugLog('[M3U8] Fetching variant playlist: ' + variantUrl); // Remove this master from detected list const masterIndex = detectedM3U8Urls.indexOf(url); if (masterIndex > -1) { detectedM3U8Urls.splice(masterIndex, 1); } // Remove any existing entry with same base URL for (const [existingUrl, existingData] of allDetectedVideos.entries()) { if (existingData.type === 'm3u8') { const existingWithoutQuery = existingUrl.split('?')[0]; if (existingWithoutQuery === urlWithoutQuery) { debugLog('[M3U8] Removing existing master playlist: ' + existingUrl); allDetectedVideos.delete(existingUrl); } } } // Detect the variant instead detectM3U8(variantUrl); return; } // This is a media playlist with actual segments 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 found!'); } 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); // Remove any existing entry with same base URL before adding this one for (const [existingUrl, existingData] of allDetectedVideos.entries()) { if (existingData.type === 'm3u8') { const existingWithoutQuery = existingUrl.split('?')[0]; if (existingWithoutQuery === urlWithoutQuery) { if ((!existingData.m3u8Data.manifest.segments || existingData.m3u8Data.manifest.segments.length === 0) && manifest.segments && manifest.segments.length > 0) { debugLog('[M3U8] Replacing master with media playlist: ' + existingUrl + ' -> ' + url); allDetectedVideos.delete(existingUrl); } } } } debugLog('[M3U8] *** ADDING TO MAP AS TYPE m3u8 ***'); allDetectedVideos.set(url, { url: url, type: 'm3u8', 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] Map entry: ' + 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 final list: ' + videoData.type + ' - ' + url); } }); debugLog('[GET VIDEOS] Checking video elements on page...'); 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); if (url && isM3U8Url(url)) { debugLog('[GET VIDEOS] Found M3U8 in video element, triggering detection: ' + url); detectM3U8(url); continue; } if (url && !seenUrls.has(url) && !allDetectedVideos.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); } else if (url && allDetectedVideos.has(url)) { debugLog('[GET VIDEOS] Already in map, skipping: ' + 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 && isM3U8Url(url)) { debugLog('[GET VIDEOS] Found M3U8 in iframe, triggering detection: ' + url); detectM3U8(url); continue; } if (url && !seenUrls.has(url) && !allDetectedVideos.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) {} } debugLog('[GET VIDEOS] Final video count: ' + videos.length); 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: 48px; height: 48px; z-index: 999999;'; var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', '48'); svg.setAttribute('height', '48'); 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', '24'); circle.setAttribute('cy', '24'); circle.setAttribute('r', '22'); circle.setAttribute('fill', 'none'); circle.setAttribute('stroke', '#4ade80'); circle.setAttribute('stroke-width', '3'); circle.setAttribute('stroke-dasharray', '138'); circle.setAttribute('stroke-dashoffset', '138'); 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: 3px; left: 3px; width: 42px; height: 42px; background: linear-gradient(135deg, ${COLORS.button} 0%, ${COLORS.buttonHover} 100%); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); color: ${COLORS.icon}; border: 2px solid ${COLORS.icon}; border-radius: 50%; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); user-select: none; box-shadow: 0 4px 16px ${COLORS.shadow}, 0 2px 8px ${COLORS.shadow}; font-weight: bold; `; container.appendChild(floatingButton); floatingButton.progressCircle = circle; floatingButton.addEventListener('mouseenter', function() { this.style.background = `linear-gradient(135deg, ${COLORS.buttonHover} 0%, ${COLORS.button} 100%)`; this.style.transform = 'scale(1.1)'; this.style.boxShadow = `0 6px 20px ${COLORS.shadow}, 0 3px 10px ${COLORS.shadow}`; }); floatingButton.addEventListener('mouseleave', function() { this.style.background = `linear-gradient(135deg, ${COLORS.button} 0%, ${COLORS.buttonHover} 100%)`; this.style.transform = 'scale(1)'; this.style.boxShadow = `0 4px 16px ${COLORS.shadow}, 0 2px 8px ${COLORS.shadow}`; }); floatingButton.addEventListener('mousedown', function(e) { e.preventDefault(); isLongPress = false; longPressStartTime = Date.now(); pressTimer = setTimeout(function() { isLongPress = true; floatingButton.style.background = 'rgba(74, 222, 128, 0.8)'; floatingButton.innerHTML = '⎘'; }, 1000); // Debug mode at 5 seconds setTimeout(function() { if (Date.now() - longPressStartTime >= 4900 && !debugMode) { 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)); } }, 5000); }); floatingButton.addEventListener('mouseup', function(e) { e.preventDefault(); clearTimeout(pressTimer); var pressDuration = Date.now() - longPressStartTime; var videos = getUniqueVideos(); floatingButton.style.background = `linear-gradient(135deg, ${COLORS.button} 0%, ${COLORS.buttonHover} 100%)`; floatingButton.innerHTML = getButtonIcon(videos); if (debugMode) { createDebugConsole(); } else if (isLongPress && pressDuration < 5000) { handleCopy(); } else if (!isLongPress) { handleShare(); } }); floatingButton.addEventListener('mouseleave', function() { clearTimeout(pressTimer); var videos = getUniqueVideos(); floatingButton.style.background = `linear-gradient(135deg, ${COLORS.button} 0%, ${COLORS.buttonHover} 100%)`; floatingButton.innerHTML = getButtonIcon(videos); }); floatingButton.addEventListener('touchstart', function(e) { e.preventDefault(); isLongPress = false; longPressStartTime = Date.now(); pressTimer = setTimeout(function() { isLongPress = true; floatingButton.style.background = 'rgba(74, 222, 128, 0.8)'; floatingButton.innerHTML = '⎘'; navigator.vibrate && navigator.vibrate(100); }, 1000); setTimeout(function() { if (Date.now() - longPressStartTime >= 4900 && !debugMode) { debugMode = true; floatingButton.style.background = 'rgba(239, 68, 68, 0.8)'; floatingButton.innerHTML = '🐛'; navigator.vibrate && navigator.vibrate([100, 50, 100]); 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)); } }, 5000); }); floatingButton.addEventListener('touchend', function(e) { e.preventDefault(); clearTimeout(pressTimer); var pressDuration = Date.now() - longPressStartTime; var videos = getUniqueVideos(); floatingButton.style.background = `linear-gradient(135deg, ${COLORS.button} 0%, ${COLORS.buttonHover} 100%)`; floatingButton.innerHTML = getButtonIcon(videos); if (debugMode) { createDebugConsole(); } else if (isLongPress && pressDuration < 5000) { handleCopy(); } else if (!isLongPress) { handleShare(); } }); document.body.appendChild(container); return floatingButton; } function updateProgress(percent) { if (!floatingButton || !floatingButton.progressCircle) return; var offset = 138 - (138 * percent / 100); floatingButton.progressCircle.setAttribute('stroke-dashoffset', offset); } function resetProgress() { if (!floatingButton || !floatingButton.progressCircle) return; floatingButton.progressCircle.setAttribute('stroke-dashoffset', '138'); } 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 = `