// ==UserScript==
// @name m3u8视频侦测下载器
// @namespace https://tools.thatwind.com/
// @version 0.1
// @description 自动检测页面m3u8视频并进行完整下载
// @author allFull
// @match *://*/*
// @exclude *://tools.thatwind.com/*
// @icon https://tools.thatwind.com/favicon.png
// @require https://cdn.jsdelivr.net/npm/m3u8-parser@4.7.1/dist/m3u8-parser.min.js
// @grant unsafeWindow
// @grant GM_openInTab
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-start
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
{
// 请求检测
const _fetch = unsafeWindow.fetch;
unsafeWindow.fetch = function (...args) {
checkUrl(args[0]);
return _fetch(...args);
}
const _open = unsafeWindow.XMLHttpRequest.prototype.open;
unsafeWindow.XMLHttpRequest.prototype.open = function (...args) {
checkUrl(args[1]);
return _open.apply(this, args);
}
}
const rootDiv = document.createElement("div");
rootDiv.style = `
position: fixed;
z-index: 9999999999999999;
opacity: 0.9;
`;
rootDiv.style.display = "none";
document.documentElement.appendChild(rootDiv);
const shadowDOM = rootDiv.attachShadow({ mode: 'open' });
const wrapper = document.createElement("div");
shadowDOM.appendChild(wrapper);
// 指示器
const bar = document.createElement("div");
bar.style = `
text-align: right;
`;
bar.innerHTML = `
`;
wrapper.appendChild(bar);
// 样式
const style = document.createElement("style");
style.innerHTML = `
.number-indicator{
position:relative;
}
.number-indicator::after{
content: attr(data-number);
position: absolute;
bottom: 0;
right: 0;
color: #40a9ff;
font-size: 14px;
font-weight: bold;
background: #000;
border-radius: 10px;
padding: 3px 5px;
}
.download-btn:hover{
text-decoration: underline;
}
.download-btn:active{
opacity: 0.9;
}
.m3u8-item{
color: white;
margin-bottom: 5px;
display: flex;
flex-direction: row;
background: black;
padding: 3px 10px;
border-radius: 3px;
font-size: 14px;
user-select: none;
}
[data-shown="false"] {
opacity: 0.8;
zoom: 0.8;
}
[data-shown="false"]:hover{
opacity: 1;
}
[data-shown="false"] .m3u8-item{
display: none;
}
`;
wrapper.appendChild(style);
const barBtn = bar.querySelector(".number-indicator");
// 关于显隐和移动
let shown = GM_getValue("shown", true);
wrapper.setAttribute("data-shown", shown);
let x = GM_getValue("x", 10);
let y = GM_getValue("y", 10);
x = Math.min(innerWidth - 50, x);
y = Math.min(innerHeight - 50, y);
if (x < 0) x = 0;
if (y < 0) y = 0;
rootDiv.style.top = `${y}px`;
rootDiv.style.right = `${x}px`;
barBtn.addEventListener("mousedown", e => {
let startX = e.pageX;
let startY = e.pageY;
let moved = false;
let mousemove = e => {
let offsetX = e.pageX - startX;
let offsetY = e.pageY - startY;
if (moved || (Math.abs(offsetX) + Math.abs(offsetY)) > 5) {
moved = true;
rootDiv.style.top = `${y + offsetY}px`;
rootDiv.style.right = `${x - offsetX}px`;
}
};
let mouseup = e => {
let offsetX = e.pageX - startX;
let offsetY = e.pageY - startY;
if (moved) {
x -= offsetX;
y += offsetY;
GM_setValue("x", x);
GM_setValue("y", y);
} else {
shown = !shown;
GM_setValue("shown", shown);
wrapper.setAttribute("data-shown", shown);
}
removeEventListener("mousemove", mousemove);
removeEventListener("mouseup", mouseup);
}
addEventListener("mousemove", mousemove);
addEventListener("mouseup", mouseup);
});
function checkUrl(url) {
url = new URL(url, location.href);
if (url.pathname.endsWith(".m3u8") || url.pathname.endsWith(".m3u")) {
// 发现
showM3U(url);
}
}
let count = 0;
async function showM3U(url) {
// 解析 m3u
const content = await (await fetch(url)).text();
const parser = new m3u8Parser.Parser();
parser.push(content);
parser.end();
const manifest = parser.manifest;
if (manifest.segments) {
let duration = 0;
manifest.segments.forEach((segment) => {
duration += segment.duration;
});
manifest.duration = duration;
}
let div = document.createElement("div");
div.className = "m3u8-item";
div.innerHTML = `
${url.pathname}
${manifest.duration ? `${Math.ceil(manifest.duration * 10 / 60) / 10}分钟` : manifest.playlists ? `多线(${manifest.playlists.length})` : "未知时长"}
下载
`;
div.querySelector(".download-btn").addEventListener("click", () => {
GM_openInTab(
`https://tools.thatwind.com/tool/m3u8downloader#${new URLSearchParams({
m3u8: url.href
})
}`,
{
active: true
}
);
});
rootDiv.style.display = "block";
count++;
bar.querySelector(".number-indicator").setAttribute("data-number", count);
wrapper.appendChild(div);
}
})();