// ==UserScript==
// @name TypeMonkey视频&音频解析器
// @namespace https://github.com/yourname
// @version 2.5
// @description 高级视频&音频解析器,支持抖音无水印和流媒体格式
// @author YourName
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant GM_notification
// @grant GM_setClipboard
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @connect *
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// 抖音无水印解析API
const DOUYIN_API = "https://api.douyin.wtf/api?url=";
// 全局变量
let floatBall = null;
let panel = null;
let lastPosition = {
x: GM_getValue('lastPositionX', 20),
y: GM_getValue('lastPositionY', 20)
};
let isExpanded = false;
let sourcesCount = 0;
// 添加全局样式
GM_addStyle(`
#videoParserFloatBall {
position: fixed;
width: 50px;
height: 50px;
background: linear-gradient(145deg, #4a76c6, #3a66b6);
border-radius: 50%;
cursor: pointer;
z-index: 99999;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 24px;
box-shadow: 0 6px 15px rgba(0,0,0,0.3);
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
user-select: none;
}
#videoParserFloatBall.badge::after {
content: '';
position: absolute;
top: -5px;
right: -5px;
width: 15px;
height: 15px;
background: #ff4757;
border-radius: 50%;
border: 2px solid white;
}
#videoParserPanel {
position: fixed;
z-index: 99998;
background: #2d3a4b;
color: #ecf0f1;
padding: 0;
border-radius: 15px;
box-shadow: 0 12px 30px rgba(0,0,0,0.4);
width: 95%;
max-width: 420px;
max-height: 85vh;
overflow: hidden;
font-family: system-ui, -apple-system, sans-serif;
display: flex;
flex-direction: column;
display: none;
transform-origin: top center;
font-size: 14px;
border: 1px solid #3a4a60;
}
#videoParserPanel .header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #1e2a38;
color: white;
cursor: move;
border-bottom: 1px solid #3a4a60;
user-select: none;
}
#videoParserPanel .header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #3498db;
}
#videoParserPanel .header .buttons {
display: flex;
gap: 10px;
}
#videoParserPanel .header button {
width: 30px;
height: 30px;
background: #3a4a60;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
transition: background 0.3s;
}
#videoParserPanel .header button:hover {
background: #4a5a70;
}
#videoParserPanel .header .close:hover {
background: #ff4757;
}
#panelContent {
overflow-y: auto;
padding: 15px;
max-height: calc(85vh - 60px);
background: #1e2a38;
font-size: 14px;
}
.source-item {
margin: 15px 0;
padding: 15px;
background: #2d3a4b;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
border: 1px solid #3a4a60;
}
.source-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.source-info {
font-weight: bold;
display: flex;
align-items: center;
gap: 10px;
}
.source-url {
word-break: break-all;
font-size: 13px;
color: #bdc3c7;
padding: 12px;
background: #1a2432;
border-radius: 8px;
max-height: 80px;
overflow-y: auto;
margin-bottom: 15px;
border: 1px solid #2d3a4b;
}
.btn-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn-group button {
padding: 8px 15px;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
flex: 1;
font-size: 13px;
min-width: 100px;
font-weight: 600;
transition: all 0.3s;
}
.btn-group button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.copy-btn {
background: linear-gradient(145deg, #2ecc71, #27ae60);
}
.douyin-btn {
background: linear-gradient(145deg, #e74c3c, #c0392b);
}
.download-btn {
background: linear-gradient(145deg, #9b59b6, #8e44ad);
}
.audio-btn {
background: linear-gradient(145deg, #f39c12, #d35400);
}
.empty-msg {
text-align: center;
padding: 40px 20px;
color: #7f8c8d;
}
.empty-msg p {
margin: 10px 0;
}
@media (max-width: 500px) {
#videoParserPanel {
width: 98%;
max-width: none;
left: 1% !important;
right: 1% !important;
transform: none !important;
max-width: unset;
}
.btn-group button {
min-width: 45%;
}
}
`);
// 创建球形悬浮按钮
function createFloatBall() {
if (floatBall) return;
floatBall = document.createElement('div');
floatBall.id = 'videoParserFloatBall';
floatBall.textContent = '🎥';
floatBall.title = '视频&音频解析器';
// 定位
floatBall.style.left = `${lastPosition.x}px`;
floatBall.style.top = `${lastPosition.y}px`;
// 添加拖拽功能
let isDragging = false;
let offsetX, offsetY;
floatBall.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
function startDrag(e) {
isDragging = true;
offsetX = e.clientX - floatBall.getBoundingClientRect().left;
offsetY = e.clientY - floatBall.getBoundingClientRect().top;
floatBall.style.cursor = 'grabbing';
floatBall.style.boxShadow = '0 10px 25px rgba(0,0,0,0.4)';
}
function drag(e) {
if (!isDragging) return;
floatBall.style.left = `${e.clientX - offsetX}px`;
floatBall.style.top = `${e.clientY - offsetY}px`;
lastPosition = {
x: e.clientX - offsetX,
y: e.clientY - offsetY
};
GM_setValue('lastPositionX', lastPosition.x);
GM_setValue('lastPositionY', lastPosition.y);
}
function stopDrag() {
isDragging = false;
floatBall.style.cursor = 'pointer';
floatBall.style.boxShadow = '0 6px 15px rgba(0,0,0,0.3)';
}
// 点击展开面板
floatBall.addEventListener('click', async () => {
if (isExpanded) {
collapsePanel();
} else {
const sources = await findVideoSources();
showResults(sources);
}
});
document.body.appendChild(floatBall);
}
// 展开面板
function expandPanel() {
if (!panel) return;
isExpanded = true;
floatBall.style.display = 'none';
// 设置面板初始位置
const rect = floatBall.getBoundingClientRect();
panel.style.left = `${rect.left}px`;
panel.style.top = `${rect.top}px`;
panel.style.display = 'flex';
panel.style.transform = 'scale(0.5) translateY(-30px)';
panel.style.opacity = '0';
// 动画效果
setTimeout(() => {
panel.style.transition = 'all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55)';
panel.style.transform = 'scale(1) translateY(0)';
panel.style.opacity = '1';
}, 10);
}
// 收起面板
function collapsePanel() {
if (!panel || !floatBall) return;
isExpanded = false;
// 动画效果
panel.style.transition = 'all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55)';
panel.style.transform = 'scale(0.5) translateY(-30px)';
panel.style.opacity = '0';
setTimeout(() => {
panel.style.display = 'none';
floatBall.style.display = 'flex';
floatBall.style.left = `${lastPosition.x}px`;
floatBall.style.top = `${lastPosition.y}px`;
}, 400);
}
// 主解析函数
async function findVideoSources() {
const sources = [];
const now = Date.now();
const uniqueSources = new Set();
// 1. 解析标准视频元素
document.querySelectorAll('video').forEach(video => {
if (video.src) addUniqueSource(sources, uniqueSources, 'video', video.src);
video.querySelectorAll('source').forEach(s => {
if (s.src) addUniqueSource(sources, uniqueSources, 'source', s.src);
});
});
// 2. 解析音频元素
document.querySelectorAll('audio').forEach(audio => {
if (audio.src) addUniqueSource(sources, uniqueSources, 'audio', audio.src);
audio.querySelectorAll('source').forEach(s => {
if (s.src) addUniqueSource(sources, uniqueSources, 'audio-source', s.src);
});
});
// 3. 解析iframe嵌入视频
const iframePromises = [];
document.querySelectorAll('iframe').forEach(iframe => {
const src = iframe.src || iframe.dataset.src;
if (!src) return;
// 抖音特殊处理
if (src.includes("douyin.com") || src.includes("iesdouyin.com")) {
iframePromises.push(
parseDouyin(src).then(url => {
if (url) addUniqueSource(sources, uniqueSources, 'douyin', url);
})
);
}
else if (/youtube|vimeo|bilibili/.test(src)) {
addUniqueSource(sources, uniqueSources, 'iframe', src);
}
});
// 等待所有抖音解析完成
await Promise.all(iframePromises);
// 更新计数
sourcesCount = sources.length;
if (sourcesCount > 0 && !isExpanded) {
floatBall.classList.add('badge');
}
return sources;
}
// 添加唯一来源
function addUniqueSource(sources, uniqueSet, type, url) {
if (!url || uniqueSet.has(url)) return;
uniqueSet.add(url);
sources.push(createSource(type, url));
}
// 创建来源对象
function createSource(type, url) {
return {
type: type,
url: url,
timestamp: Date.now(),
isStream: /\.(m3u8|mpd)(\?|$)/i.test(url),
isAudio: /audio|audio-source/.test(type)
};
}
// 抖音无水印解析
function parseDouyin(url) {
return new Promise(resolve => {
GM_xmlhttpRequest({
method: "GET",
url: `${DOUYIN_API}${encodeURIComponent(url)}`,
timeout: 8000,
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
resolve(data.nwm_video_url || null);
} catch (e) {
resolve(null);
}
},
onerror: function() {
resolve(null);
},
ontimeout: function() {
resolve(null);
}
});
});
}
// 显示结果面板
function showResults(sources) {
if (panel) panel.remove();
panel = document.createElement('div');
panel.id = 'videoParserPanel';
// 标题栏
const header = document.createElement('div');
header.className = 'header';
const title = document.createElement('h3');
title.textContent = sources.length > 0 ?
`🎥 发现 ${sources.length} 个媒体源` :
`⚠️ 未检测到媒体源`;
const buttonContainer = document.createElement('div');
buttonContainer.className = 'buttons';
// 刷新按钮
const refreshBtn = document.createElement('button');
refreshBtn.textContent = '↻';
refreshBtn.title = '重新扫描';
// 收起按钮
const minimizeBtn = document.createElement('button');
minimizeBtn.textContent = '−';
minimizeBtn.title = '收起/展开';
// 关闭按钮
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.title = '关闭';
closeBtn.className = 'close';
buttonContainer.appendChild(refreshBtn);
buttonContainer.appendChild(minimizeBtn);
buttonContainer.appendChild(closeBtn);
header.appendChild(title);
header.appendChild(buttonContainer);
// 内容区域
const content = document.createElement('div');
content.id = 'panelContent';
if (sources.length > 0) {
const list = document.createElement('div');
sources.forEach((src, i) => {
const item = document.createElement('div');
item.className = 'source-item';
const headerRow = document.createElement('div');
headerRow.className = 'source-header';
const sourceInfo = document.createElement('div');
sourceInfo.className = 'source-info';
const sourceType = document.createElement('span');
let typeText = src.type.toUpperCase();
if (src.isAudio) {
sourceType.style.color = '#f39c12';
typeText = '音频资源';
} else if (src.type === 'douyin') {
sourceType.style.color = '#e74c3c';
} else if (src.type === 'stream') {
sourceType.style.color = '#9b59b6';
} else {
sourceType.style.color = '#3498db';
}
sourceType.textContent = `[${typeText}]`;
const sourceIndex = document.createElement('span');
sourceIndex.textContent = `#${i+1}`;
sourceInfo.appendChild(sourceIndex);
sourceInfo.appendChild(sourceType);
const urlText = document.createElement('div');
urlText.className = 'source-url';
urlText.textContent = src.url;
const btnGroup = document.createElement('div');
btnGroup.className = 'btn-group';
// 复制按钮
const copyBtn = document.createElement('button');
copyBtn.textContent = '复制链接';
copyBtn.className = 'copy-btn';
copyBtn.dataset.url = src.url;
// 下载按钮
let downloadBtn = null;
if (src.isAudio) {
downloadBtn = document.createElement('button');
downloadBtn.textContent = '下载音频';
downloadBtn.className = 'audio-btn';
downloadBtn.dataset.download = src.url;
} else if (src.type === 'douyin') {
downloadBtn = document.createElement('button');
downloadBtn.textContent = '无水印下载';
downloadBtn.className = 'douyin-btn';
downloadBtn.dataset.download = src.url;
} else if (!src.isStream) {
downloadBtn = document.createElement('button');
downloadBtn.textContent = '下载视频';
downloadBtn.className = 'download-btn';
downloadBtn.dataset.download = src.url;
}
btnGroup.appendChild(copyBtn);
if (downloadBtn) btnGroup.appendChild(downloadBtn);
item.appendChild(headerRow);
item.appendChild(urlText);
item.appendChild(btnGroup);
headerRow.appendChild(sourceInfo);
list.appendChild(item);
});
content.appendChild(list);
} else {
const emptyMsg = document.createElement('div');
emptyMsg.className = 'empty-msg';
emptyMsg.innerHTML = `
📹
未找到视频或音频资源
尝试播放媒体后重新扫描
`;
content.appendChild(emptyMsg);
}
// 组装面板
panel.appendChild(header);
panel.appendChild(content);
document.body.appendChild(panel);
// 添加事件监听器
function setupEventListeners() {
// 复制功能
content.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', () => {
GM_setClipboard(btn.dataset.url);
GM_notification({
title: '复制成功',
text: '链接已复制到剪贴板',
timeout: 2000
});
});
});
// 下载功能
content.querySelectorAll('.download-btn, .audio-btn, .douyin-btn').forEach(btn => {
btn.addEventListener('click', () => {
const url = btn.dataset.download;
const isAudio = btn.classList.contains('audio-btn');
let fileExtension = 'mp4';
if (isAudio) {
fileExtension = url.includes('.m4a') ? 'm4a' :
url.includes('.aac') ? 'aac' :
url.includes('.wav') ? 'wav' : 'mp3';
}
const filename = `${isAudio ? 'audio' : 'video'}_${Date.now()}.${fileExtension}`;
GM_download({
url: url,
name: filename
});
});
});
// 刷新功能
refreshBtn.addEventListener('click', async () => {
refreshBtn.textContent = '...';
refreshBtn.disabled = true;
const newSources = await findVideoSources();
showResults(newSources);
});
// 收起功能
minimizeBtn.addEventListener('click', collapsePanel);
// 关闭功能
closeBtn.addEventListener('click', () => {
if (panel) panel.remove();
if (floatBall) floatBall.remove();
panel = null;
floatBall = null;
});
// 拖动功能
let isDragging = false;
let offsetX, offsetY;
header.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
function startDrag(e) {
if (e.target.tagName === 'BUTTON') return;
isDragging = true;
offsetX = e.clientX - panel.getBoundingClientRect().left;
offsetY = e.clientY - panel.getBoundingClientRect().top;
panel.style.cursor = 'grabbing';
}
function drag(e) {
if (!isDragging) return;
panel.style.left = `${e.clientX - offsetX}px`;
panel.style.top = `${e.clientY - offsetY}px`;
}
function stopDrag() {
isDragging = false;
panel.style.cursor = 'default';
}
}
setupEventListeners();
expandPanel();
}
// 主执行函数
function init() {
createFloatBall();
// 初始扫描
setTimeout(async () => {
await findVideoSources();
}, 5000);
}
// 等待页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();