您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
成功获取m3u8地址后,并提示用户点击右侧按钮复制m3u8地址
// ==UserScript== // @name 小鹅通直播回放链接解密成功提示 // @namespace http://tampermonkey.net/ // @version 1.8 // @description 成功获取m3u8地址后,并提示用户点击右侧按钮复制m3u8地址 // @author 破坏游戏的孩子 // @match https://*.h5.xiaoeknow.com/v3/course/alive/* // @match https://*.xet.citv.cn/v3/course/alive/* // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @grant GM_addElement // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js // @license 未经许可,禁止修改 // ==/UserScript== (function() { 'use strict'; // 获取当前URL参数 const urlParams = new URLSearchParams(window.location.search); const appId = urlParams.get('app_id'); const aliveId = window.location.pathname.split('/').pop(); const hostname = window.location.hostname; if (appId && aliveId) { // 获取视频详情的接口地址 let baseInfoUrl; let apiUrl; if (hostname.includes('xet.citv.cn')) { baseInfoUrl = `https://${appId}.xet.citv.cn/_alive/v3/base_info?resource_id=${aliveId}&product_id=&type=12&is_direct=1`; apiUrl = `https://${appId}.xet.citv.cn/_alive/v3/get_lookback_list?app_id=${appId}&alive_id=${aliveId}`; } else { baseInfoUrl = `https://${appId}.h5.xiaoeknow.com/_alive/v3/base_info?resource_id=${aliveId}&product_id=&type=12&is_direct=1`; apiUrl = `https://${appId}.h5.xiaoeknow.com/_alive/v3/get_lookback_list?app_id=${appId}&alive_id=${aliveId}`; } // 先获取视频详情 GM_xmlhttpRequest({ method: "GET", url: baseInfoUrl, onload: function(baseResponse) { try { const baseData = JSON.parse(baseResponse.responseText); const videoTitle = baseData.data.alive_info.title || '未知标题'; const rawExpireTime = baseData.data.alive_conf.lookback_time.expire || ''; // 格式化时间和倒计时信息 const expireTime = rawExpireTime ? new Date(rawExpireTime * 1000).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-') : '长期有效'; // 计算剩余时间 let timeLeftStr = ''; let expireDate = 0; if (rawExpireTime) { const now = new Date().getTime(); expireDate = new Date(rawExpireTime * 1000).getTime(); const timeLeft = expireDate - now; const daysLeft = Math.floor(timeLeft / (1000 * 60 * 60 * 24)); const hoursLeft = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutesLeft = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); const secondsLeft = Math.floor((timeLeft % (1000 * 60)) / 1000); timeLeftStr = `${daysLeft}天${hoursLeft}小时${minutesLeft}分钟${secondsLeft}秒`; } // 继续获取回放列表 GM_xmlhttpRequest({ method: "GET", url: apiUrl, onload: function(response) { try { const data = JSON.parse(response.responseText); // 获取所有回放数据 const videoList = data.data || []; if (videoList.length === 0) { console.error('未找到可用的视频回放'); return; } // 创建线路选择容器 const container = document.createElement('div'); container.style.position = 'fixed'; container.style.right = '20px'; container.style.top = '50%'; container.style.transform = 'translateY(-50%)'; container.style.zIndex = '9999'; container.style.display = 'flex'; container.style.flexDirection = 'column'; container.style.gap = '10px'; container.style.backgroundColor = 'rgba(0,0,0,0.7)'; container.style.padding = '15px'; container.style.borderRadius = '8px'; container.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; container.style.maxWidth = '200px'; // 循环所有回放数据 videoList.forEach((video, index) => { // 获取线路名称和清晰度 const lineSharpness = video.line_sharpness || []; // 遍历每个清晰度 lineSharpness.forEach((quality, qIndex) => { const sharpnessName = quality.name || '默认'; const lineName = video.line_name ? `${video.line_name}-${sharpnessName}` : `回放${index + 1}-${sharpnessName}`; const m3u8Url = quality.url || ''; // 创建线路按钮 const lineButton = document.createElement('div'); lineButton.innerText = lineName; lineButton.style.padding = '8px 12px'; lineButton.style.backgroundColor = 'rgba(255,255,255,0.1)'; lineButton.style.color = '#fff'; lineButton.style.borderRadius = '4px'; lineButton.style.cursor = 'pointer'; lineButton.style.fontSize = '14px'; lineButton.style.marginBottom = '8px'; lineButton.style.textAlign = 'center'; lineButton.style.transition = 'all 0.2s ease'; lineButton.style.position = 'relative'; // 为第一个按钮添加推荐标志 if (index === 0 && qIndex === 0) { // 创建红色三角标志 const recommendBadge = document.createElement('div'); recommendBadge.style.position = 'absolute'; recommendBadge.style.top = '0'; recommendBadge.style.left = '0'; recommendBadge.style.width = '0'; recommendBadge.style.height = '0'; recommendBadge.style.borderStyle = 'solid'; recommendBadge.style.borderWidth = '20px 20px 0 0'; recommendBadge.style.borderColor = '#ff4d4f transparent transparent transparent'; recommendBadge.style.borderTopLeftRadius = '4px'; lineButton.appendChild(recommendBadge); } // 鼠标悬停效果 lineButton.addEventListener('mouseover', () => { lineButton.style.backgroundColor = 'rgba(255,255,255,0.2)'; }); lineButton.addEventListener('mouseout', () => { lineButton.style.backgroundColor = 'rgba(255,255,255,0.1)'; }); // 添加点击复制功能 lineButton.addEventListener('click', () => { GM_setClipboard(m3u8Url, 'text'); const originalText = lineButton.innerText; lineButton.innerText = '已复制!'; lineButton.style.backgroundColor = '#28a745'; setTimeout(() => { lineButton.innerText = originalText; lineButton.style.backgroundColor = 'rgba(255,255,255,0.1)'; }, 1000); }); container.appendChild(lineButton); }); }); // 将容器添加到页面 document.body.appendChild(container); // 创建提示信息 const messageDiv = document.createElement('div'); messageDiv.innerHTML = ` <div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9998; background: rgba(0,0,0,0.8); padding: 20px 30px; border-radius: 8px; color: #fff; min-width: 300px; box-shadow: 0 2px 12px rgba(0,0,0,0.15);"> <div style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 20px; color: rgba(255,255,255,0.8);" class="close-btn">×</div> <h2 style="margin: 0 0 15px; font-size: 24px; font-weight: bold; color: #fff; text-align: center; position: relative; overflow: hidden;" class="spotlight-title"> <span style="position: relative; z-index: 1;">视频地址解密成功</span> <div class="spotlight" style="position: absolute; width: 30px; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.8), transparent); top: 0; left: -100%; transform: skewX(-25deg); animation: spotlight 3s infinite;"></div> </h2> <style> @keyframes spotlight { 0% { left: -100%; } 100% { left: 200%; } } </style> <div style="text-align: left;"> <p style="margin: 0 0 15px; font-size: 16px; color: rgba(255,255,255,0.9);">您准备下载的直播:${videoTitle}</p> <p style="margin: 0 0 10px; font-size: 16px; color: rgba(255,255,255,0.9);">直播回放到期时间:${expireTime}</p> ${rawExpireTime ? `<p style="margin: 0 0 10px; font-size: 16px; color: #ff4d4f;">距离回放到期还有:<span id="countdown">${timeLeftStr}</span></p>` : ''} <p style="margin: 15px 0 0; font-size: 14px; color: rgba(255,255,255,0.7);">请根据自己的需求尽快下载,防止直播回放到期导致无法下载资源。</p> </div> </div> `; document.body.appendChild(messageDiv); // 添加倒计时更新功能 if (rawExpireTime) { const countdownElement = messageDiv.querySelector('#countdown'); const updateCountdown = () => { const now = new Date().getTime(); const timeLeft = expireDate - now; if (timeLeft <= 0) { countdownElement.textContent = '已过期'; clearInterval(countdownInterval); return; } const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24)); const hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); countdownElement.textContent = `${days}天${hours}小时${minutes}分钟${seconds}秒`; }; const countdownInterval = setInterval(updateCountdown, 1000); // 关闭按钮时清除定时器 const closeBtn = messageDiv.querySelector('.close-btn'); closeBtn.addEventListener('click', () => { clearInterval(countdownInterval); messageDiv.remove(); }); } else { // 无倒计时时的关闭按钮 const closeBtn = messageDiv.querySelector('.close-btn'); closeBtn.addEventListener('click', () => { messageDiv.remove(); }); } // 燃放烟花效果 confetti({ particleCount: 100, spread: 160, origin: { y: 0.6 } }); // 移除自动关闭的代码 // setTimeout(() => { // messageDiv.remove(); // }, 5000); } catch (e) { console.error('解析JSON失败:', e); } }, onerror: function(error) { console.error('请求失败:', error); } }); } catch (e) { console.error('解析视频详情失败:', e); } }, onerror: function(error) { console.error('请求视频详情失败:', error); } }); } })();