// ==UserScript== // @name 破解网页禁用右键下载视频 // @namespace https://www.cnblogs.com/05-hust/p/17428290.html // @version 1.6.2 // @description 优化网络请求,支持重试和备用域名,解决连接超时问题;添加导出地址功能 // @author you // @match https://aguea.net/* // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_setClipboard // @connect video.twimg.com // @connect venexa.site // @connect * // @run-at document-start // @license All Rights Reserved // ==/UserScript== (function () { 'use strict'; /* ============ 1. 事件层:劫持 addEventListener 与 on* 属性 ============ */ const HOOK_EVENTS = ['contextmenu', 'selectstart', 'copy', 'cut', 'paste', 'dragstart', 'keydown', 'mousedown']; const _add = EventTarget.prototype.addEventListener; EventTarget.prototype.addEventListener = function (type, listener, options) { if (HOOK_EVENTS.indexOf(type) >= 0) return; return _add.call(this, type, listener, options); }; const _preventDefault = Event.prototype.preventDefault; Event.prototype.preventDefault = function () { if (this && HOOK_EVENTS.indexOf(this.type) >= 0) return; return _preventDefault.call(this); }; function clearInlineHandlers(root) { const all = (root || document).querySelectorAll('*'); all.forEach(el => { HOOK_EVENTS.forEach(ev => { if (el['on' + ev] !== null) el['on' + ev] = null; }); }); ['oncontextmenu', 'onselectstart', 'oncopy', 'onkeydown'].forEach(k => { try { document[k] = null; document.body && (document.body[k] = null); } catch (e) {} }); } /* ============ 2. 属性层:移除 controlsList=nodownload 等 ============ */ function unlockVideos(root) { const videos = (root || document).querySelectorAll('video'); videos.forEach(v => { v.removeAttribute('controlslist'); v.removeAttribute('controlsList'); v.setAttribute('controls', 'controls'); v.oncontextmenu = null; const wrap = v.parentElement; if (wrap) { wrap.style.pointerEvents = ''; wrap.querySelectorAll('[oncontextmenu]').forEach(n => n.removeAttribute('oncontextmenu')); } injectDownloadButton(v); }); } /* ============ 3. UI 层:注入「复制视频地址」按钮 ============ */ function injectDownloadButton(video) { if (video.dataset.dlInjected === '1') return; video.dataset.dlInjected = '1'; const btn = document.createElement('button'); btn.textContent = '📋 复制视频地址'; Object.assign(btn.style, { position: 'absolute', right: '12px', top: '12px', zIndex: 2147483647, padding: '6px 12px', background: 'rgba(0,0,0,.7)', color: '#fff', border: '1px solid #fff', borderRadius: '4px', cursor: 'pointer', fontSize: '12px' }); btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); handleVideoCopy(video, btn); }, true); if (getComputedStyle(video.parentElement).position === 'static') { video.parentElement.style.position = 'relative'; } video.parentElement.appendChild(btn); } // 获取视频真实地址 function getVideoUrl(video) { if (video.dataset.url) return video.dataset.url; if (video.currentSrc && !video.currentSrc.startsWith('blob:')) return video.currentSrc; if (video.src && !video.src.startsWith('blob:')) return video.src; const source = video.querySelector('source[src]'); return source ? source.src : ''; } // 核心逻辑:处理地址复制 async function handleVideoCopy(video, btn) { let url = getVideoUrl(video); if (!url) { alert('未找到视频地址,可能尚未加载或加密严重。'); return; } if (url.startsWith('blob:')) { alert('当前视频为 Blob 流媒体,无法直接获取真实文件地址,请使用外部抓包工具获取。'); return; } // 构造备用镜像地址 const mirrorUrl = url.includes('https://video.twimg.com') ? url.replace('https://video.twimg.com', 'https://venexa.site') : '无'; const textToCopy = `真实地址:\n${url}\n\n备用镜像地址:\n${mirrorUrl}`; if (typeof GM_setClipboard === 'function') { GM_setClipboard(textToCopy); btn.textContent = '✅ 地址已复制'; setTimeout(() => { btn.textContent = '📋 复制视频地址'; }, 2000); } else { // 降级方案 const textarea = document.createElement('textarea'); textarea.value = textToCopy; document.body.appendChild(textarea); textarea.select(); try { document.execCommand('copy'); btn.textContent = '✅ 地址已复制'; setTimeout(() => { btn.textContent = '📋 复制视频地址'; }, 2000); } catch (err) { alert('无法复制到剪贴板,请手动复制:\n' + textToCopy); } document.body.removeChild(textarea); } } /* ============ 4. 新增:导出地址功能 ============ */ function injectExportButtons() { if (document.getElementById('video-export-buttons')) return; const exportContainer = document.createElement('div'); exportContainer.id = 'video-export-buttons'; exportContainer.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 2147483647; display: flex; flex-direction: column; gap: 10px; `; // 真实地址导出按钮 const realUrlBtn = document.createElement('button'); realUrlBtn.textContent = '📄 导出真实地址'; realUrlBtn.style.cssText = ` padding: 10px 15px; background: #4285f4; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; `; realUrlBtn.addEventListener('click', exportRealUrls); // 备用镜像地址导出按钮 const mirrorUrlBtn = document.createElement('button'); mirrorUrlBtn.textContent = '📄 导出镜像地址'; mirrorUrlBtn.style.cssText = ` padding: 10px 15px; background: #34a853; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; `; mirrorUrlBtn.addEventListener('click', exportMirrorUrls); exportContainer.appendChild(realUrlBtn); exportContainer.appendChild(mirrorUrlBtn); document.body.appendChild(exportContainer); } // 导出真实地址 function exportRealUrls() { const videos = document.querySelectorAll('video'); const realUrls = []; videos.forEach(video => { const url = getVideoUrl(video); if (url && !url.startsWith('blob:')) { realUrls.push(url.trim()); } }); if (realUrls.length === 0) { alert('未找到可导出的真实地址'); return; } const content = generateTxtContent(realUrls); downloadTxtFile(content, 'real_video_urls.txt'); } // 导出镜像地址 function exportMirrorUrls() { const videos = document.querySelectorAll('video'); const mirrorUrls = []; videos.forEach(video => { const url = getVideoUrl(video); if (url && !url.startsWith('blob:')) { const mirrorUrl = url.includes('https://video.twimg.com') ? url.replace('https://video.twimg.com', 'https://venexa.site') : '无有效镜像地址'; mirrorUrls.push(mirrorUrl.trim()); } }); if (mirrorUrls.length === 0) { alert('未找到可导出的镜像地址'); return; } const content = generateTxtContent(mirrorUrls); downloadTxtFile(content, 'mirror_video_urls.txt'); } // 生成txt文件内容 function generateTxtContent(urls) { const timestamp = new Date().toLocaleString(); let content = `# 视频地址列表\n# 导出时间: ${timestamp}\n# 总数: ${urls.length}\n\n`; urls.forEach((url, index) => { content += `${url}\n`; }); return content; } // 下载txt文件 function downloadTxtFile(content, filename) { const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); alert(`已导出 ${filename},共 ${content.split('\n').length - 3} 个地址`); } /* ============ 5. CSS:还原被隐藏的原生下载按钮 ============ */ const css = ` video::-internal-media-controls-download-button { display: initial !important; } video::-webkit-media-controls-enclosure { overflow: visible !important; } video::-webkit-media-controls-panel { width: 100% !important; } * { -webkit-user-select: text !important; user-select: text !important; } `; const style = document.createElement('style'); style.textContent = css; (document.head || document.documentElement).appendChild(style); /* ============ 6. 主流程与动态监听 ============ */ function run() { try { clearInlineHandlers(document); } catch (e) {} try { unlockVideos(document); } catch (e) {} try { injectExportButtons(); } catch (e) {} } if (document.body) run(); else document.addEventListener('DOMContentLoaded', run); const mo = new MutationObserver(muts => { for (const m of muts) { for (const node of m.addedNodes) { if (node.nodeType !== 1) continue; if (node.tagName === 'VIDEO') { unlockVideos(node.parentElement); } else if (node.querySelector && node.querySelector('video')) { unlockVideos(node); } } } }); mo.observe(document.documentElement, { childList: true, subtree: true }); let lastURL = location.href; setInterval(() => { if (location.href !== lastURL) { lastURL = location.href; setTimeout(run, 800); } }, 1000); })();