高级 M3U8 提取器(Network 级)
// ==UserScript==
// @name 高级 M3U8 提取器(Network 级)
// @namespace https://xjrx.net/
// @version 2.1.0
// @description 劫持 fetch 和 XHR,提取页面中所有 .m3u8 请求(包括开发者工具里才能看到的)
// @author You
// @match *://*/*
// @grant GM_setClipboard
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
const m3u8Set = new Set();
function copyText(text) {
GM_setClipboard(text, 'text');
showToast('已复制: ' + text);
}
function showToast(msg) {
const toast = document.createElement('div');
Object.assign(toast.style, {
position: 'fixed',
left: '50%', top: '20%',
transform: 'translateX(-50%)',
background: 'rgba(0,0,0,0.75)',
color: '#fff',
padding: '8px 14px',
borderRadius: '4px',
fontSize: '14px',
zIndex: 9999999,
transition: 'opacity .3s'
});
toast.textContent = msg;
document.body.appendChild(toast);
setTimeout(() => (toast.style.opacity = '0'), 1500);
setTimeout(() => toast.remove(), 1800);
}
function checkUrl(url) {
if (url && /\.m3u8(?:\?.*)?$/i.test(url)) {
m3u8Set.add(url);
renderFloatBox();
}
}
// 劫持 fetch
const originalFetch = window.fetch;
window.fetch = function (...args) {
const url = args[0];
checkUrl(url);
return originalFetch.apply(this, args);
};
// 劫持 XMLHttpRequest
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url) {
checkUrl(url);
return originalOpen.apply(this, arguments);
};
// 渲染悬浮窗
let box = null;
function renderFloatBox() {
if (box) box.remove();
box = document.createElement('div');
box.id = 'm3u8-float-box';
Object.assign(box.style, {
position: 'fixed',
right: '16px', bottom: '16px',
width: '320px', maxHeight: '400px',
background: '#1a1a1a',
boxShadow: '0 4px 15px rgba(0,0,0,.5)',
borderRadius: '8px',
fontSize: '14px',
zIndex: 999999,
display: 'flex', flexDirection: 'column',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
});
// 头部区域
const head = document.createElement('div');
Object.assign(head.style, {
padding: '12px 14px',
borderBottom: '1px solid #333',
background: '#252525',
borderRadius: '8px 8px 0 0',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
});
// 左侧:M3U8数量 + 网页标题
const leftInfo = document.createElement('div');
leftInfo.style.display = 'flex';
leftInfo.style.flexDirection = 'column';
leftInfo.style.gap = '4px';
// M3U8数量标签
const countLabel = document.createElement('div');
countLabel.innerHTML = `<span style="color:#888;">M3U8</span> <strong style="color:#4CAF50;">${m3u8Set.size}</strong> 条`;
countLabel.style.fontSize = '13px';
// 网页标题(可点击复制)
const pageTitle = document.createElement('div');
const titleText = document.title || '无标题';
pageTitle.textContent = `📄 ${titleText.length > 25 ? titleText.substring(0, 25) + '...' : titleText}`;
pageTitle.title = titleText; // 悬停显示完整标题
Object.assign(pageTitle.style, {
fontSize: '12px',
color: '#aaa',
cursor: 'pointer',
maxWidth: '200px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
transition: 'color 0.2s'
});
pageTitle.onmouseenter = () => { pageTitle.style.color = '#4CAF50'; };
pageTitle.onmouseleave = () => { pageTitle.style.color = '#aaa'; };
pageTitle.onclick = () => {
if (titleText) {
copyText(titleText);
} else {
showToast('当前页面无标题');
}
};
leftInfo.appendChild(countLabel);
leftInfo.appendChild(pageTitle);
// 右侧:关闭按钮
const close = document.createElement('span');
close.textContent = '×';
Object.assign(close.style, {
color: '#888',
cursor: 'pointer',
fontSize: '22px',
fontWeight: 'bold',
lineHeight: '1',
padding: '0 4px',
transition: 'color 0.2s'
});
close.onmouseenter = () => { close.style.color = '#ff5555'; };
close.onmouseleave = () => { close.style.color = '#888'; };
close.onclick = () => box.remove();
head.appendChild(leftInfo);
head.appendChild(close);
// 内容区域
const body = document.createElement('div');
Object.assign(body.style, {
flex: '1',
overflowY: 'auto',
padding: '10px',
background: '#1a1a1a'
});
// 自定义滚动条样式
body.style.cssText += `
scrollbar-width: thin;
scrollbar-color: #444 #1a1a1a;
`;
body.onmouseenter = function() {
this.style.cssText += `
scrollbar-width: thin;
scrollbar-color: #666 #222;
`;
};
if (m3u8Set.size === 0) {
const emptyTip = document.createElement('div');
emptyTip.textContent = '暂无 M3U8 链接';
Object.assign(emptyTip.style, {
color: '#666',
textAlign: 'center',
padding: '30px 0',
fontSize: '13px'
});
body.appendChild(emptyTip);
} else {
m3u8Set.forEach(url => {
const line = document.createElement('div');
line.style.marginBottom = '8px';
line.style.display = 'flex';
line.style.alignItems = 'center';
const input = document.createElement('input');
input.type = 'text';
input.value = url;
input.readOnly = true;
Object.assign(input.style, {
flex: '1',
padding: '6px 8px',
border: '1px solid #333',
borderRadius: '4px',
fontSize: '11px',
background: '#252525',
color: '#e0e0e0',
outline: 'none'
});
const btn = document.createElement('button');
btn.textContent = '复制';
Object.assign(btn.style, {
width: '52px',
padding: '6px 0',
marginLeft: '6px',
fontSize: '12px',
cursor: 'pointer',
background: 'linear-gradient(135deg, #4CAF50, #45a049)',
color: '#fff',
border: 'none',
borderRadius: '4px',
fontWeight: '500',
transition: 'all 0.2s'
});
btn.onmouseenter = () => {
btn.style.background = 'linear-gradient(135deg, #5CBF60, #4CAF50)';
btn.style.transform = 'scale(1.02)';
};
btn.onmouseleave = () => {
btn.style.background = 'linear-gradient(135deg, #4CAF50, #45a049)';
btn.style.transform = 'scale(1)';
};
btn.onclick = () => copyText(url);
line.appendChild(input);
line.appendChild(btn);
body.appendChild(line);
});
}
box.appendChild(head);
box.appendChild(body);
document.body.appendChild(box);
}
// 页面加载后延迟渲染
if (document.body) {
renderFloatBox();
} else {
document.addEventListener('DOMContentLoaded', renderFloatBox);
}
})();
发布时间: