// ==UserScript==
// @name 跳转 VIP 视频解析
// @namespace http://tampermonkey.net/
// @version 0.0.2
// @license MIT
// @description 在视频页跳转 VIP 视频解析网站
// @author [Ares-Chang](https://github.com/Ares-Chang)
// @match https://v.qq.com/*
// @match https://www.mgtv.com/*
// @match https://www.iqiyi.com/*
// @match https://www.youku.com/*
// @match https://v.youku.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=mgtv.com
// @grant none
// ==/UserScript==
/**
* 以下代码为互联网收集,仅供学习参考,如有版权问题,请联系我删除
*/
(function() {
'use strict';
const list = [
'https://jx.77flv.cc/?url=',
'https://jx.dmflv.cc/?url=',
'https://jx.xymp4.cc/?url=',
'https://www.yemu.xyz/?url=',
'https://jx.xmflv.com/?url=',
'https://jx.7kjx.com/?url=',
'https://www.8090.la/8090/?url=',
'https://api.qianqi.net/vip/?url=',
'https://jx.mmkv.cn/tv.php?url=',
'https://jx.973973.xyz/?url=',
'https://jx.2s0.cn/player/?url=',
'https://jx.nnxv.cn/tv.php?url=',
]
// 从本地存储加载配置
const loadConfig = () => {
const config = localStorage.getItem('vip-parse-config')
if (config) {
try {
const parsed = JSON.parse(config)
return {
isDarkTheme: parsed.isDarkTheme || false,
position: parsed.position || { right: '20px', bottom: '20px' },
lastUsedIndex: parsed.lastUsedIndex ?? -1
}
} catch (e) {
console.error('配置解析错误,使用默认配置')
}
}
return {
isDarkTheme: false,
position: { right: '20px', bottom: '20px' },
lastUsedIndex: -1
}
}
// 保存配置到本地存储
const saveConfig = (config) => {
try {
localStorage.setItem('vip-parse-config', JSON.stringify(config))
} catch (e) {
console.error('配置保存失败', e)
}
}
const config = loadConfig()
// 创建按钮
const btn = document.createElement('button')
btn.className = 'vip-parse-btn'
btn.setAttribute('title', 'VIP视频解析')
Object.assign(btn.style, config.position)
document.body.appendChild(btn)
// 创建弹窗
const modal = document.createElement('div')
modal.className = 'vip-parse-modal'
// 生成解析接口列表HTML
const generateListHTML = () => {
// 重新排序列表,将上次使用的放在最前面
let sortedList = [...list]
let lastUsedHtml = ''
let otherHtml = ''
if (config.lastUsedIndex >= 0) {
const lastUsed = sortedList[config.lastUsedIndex]
lastUsedHtml = `
<div class="vip-parse-item last-used" data-index="${config.lastUsedIndex}">
<span class="item-name">解析接口 ${config.lastUsedIndex + 1}</span>
<span class="last-used-badge">上次使用</span>
</div>
`
}
otherHtml = sortedList
.map((url, index) => {
if (index === config.lastUsedIndex) return ''
return `
<div class="vip-parse-item" data-index="${index}">
<span class="item-name">解析接口 ${index + 1}</span>
</div>
`
})
.filter(Boolean)
.join('')
return `
${lastUsedHtml}
${lastUsedHtml && otherHtml ? '<div class="vip-parse-divider"></div>' : ''}
${otherHtml}
`
}
// 更新弹窗内容
const updateModalContent = () => {
modal.innerHTML = `
<div class="vip-parse-modal-header">
<h3>选择解析接口</h3>
<div class="theme-toggle">
<span class="theme-icon">${config.isDarkTheme ? '🌜' : '🌞'}</span>
</div>
<div class="close-btn">✕</div>
</div>
<div class="vip-parse-list">
${generateListHTML()}
</div>
`
}
updateModalContent()
document.body.appendChild(modal)
// 设置初始主题
if (config.isDarkTheme) {
document.body.classList.add('vip-dark-theme')
}
// 主题切换功能
let isDarkTheme = config.isDarkTheme
modal.addEventListener('click', (e) => {
const themeToggle = e.target.closest('.theme-toggle')
if (themeToggle) {
isDarkTheme = !isDarkTheme
document.body.classList.toggle('vip-dark-theme')
themeToggle.querySelector('.theme-icon').textContent = isDarkTheme ? '🌜' : '🌞'
config.isDarkTheme = isDarkTheme
saveConfig(config)
}
})
// 按钮拖动功能
let isDragging = false
let startX, startY, startLeft, startTop
let dragStartTime = 0
let hasMoved = false
const updateButtonPosition = (left, top) => {
const rect = btn.getBoundingClientRect()
const maxX = window.innerWidth - rect.width
const maxY = window.innerHeight - rect.height
// 确保按钮不会超出视窗
const newLeft = Math.min(Math.max(0, left), maxX)
const newTop = Math.min(Math.max(0, top), maxY)
// 计算距离边缘的位置
const right = window.innerWidth - newLeft - rect.width
const bottom = window.innerHeight - newTop - rect.height
// 更新按钮位置
const position = {}
if (newLeft <= maxX / 2) {
position.left = `${newLeft}px`
position.right = 'auto'
} else {
position.right = `${right}px`
position.left = 'auto'
}
if (newTop <= maxY / 2) {
position.top = `${newTop}px`
position.bottom = 'auto'
} else {
position.bottom = `${bottom}px`
position.top = 'auto'
}
Object.assign(btn.style, position)
config.position = position
saveConfig(config)
}
btn.addEventListener('mousedown', (e) => {
if (e.button !== 0) return // 只响应左键
isDragging = true
hasMoved = false
dragStartTime = Date.now()
startX = e.clientX
startY = e.clientY
const rect = btn.getBoundingClientRect()
startLeft = rect.left
startTop = rect.top
btn.style.transition = 'none'
btn.style.cursor = 'grabbing'
modal.style.display = 'none' // 开始拖动时关闭弹窗
// 防止拖动时页面选择
document.body.style.userSelect = 'none'
document.body.style.webkitUserSelect = 'none'
// 阻止事件冒泡和默认行为
e.stopPropagation()
e.preventDefault()
})
document.addEventListener('mousemove', (e) => {
if (!isDragging) return
const deltaX = e.clientX - startX
const deltaY = e.clientY - startY
// 只有移动超过 5px 才认为是拖动
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
hasMoved = true
e.preventDefault()
e.stopPropagation() // 阻止事件冒泡
updateButtonPosition(startLeft + deltaX, startTop + deltaY)
}
}, { passive: false }) // 添加 passive: false 以确保可以调用 preventDefault
document.addEventListener('mouseup', (e) => {
if (!isDragging) return
isDragging = false
btn.style.transition = 'all 0.3s ease'
btn.style.cursor = 'grab'
// 移除可能的全局选择限制
document.body.style.userSelect = ''
document.body.style.webkitUserSelect = ''
})
// 添加额外的事件监听以确保能捕获到鼠标释放
window.addEventListener('mouseup', (e) => {
if (!isDragging) return
isDragging = false
btn.style.transition = 'all 0.3s ease'
btn.style.cursor = 'grab'
document.body.style.userSelect = ''
document.body.style.webkitUserSelect = ''
}, true)
// 添加鼠标离开窗口的保护措施
window.addEventListener('mouseleave', () => {
if (isDragging) {
isDragging = false
btn.style.transition = 'all 0.3s ease'
btn.style.cursor = 'grab'
document.body.style.userSelect = ''
document.body.style.webkitUserSelect = ''
}
})
// 添加失去焦点的保护措施
window.addEventListener('blur', () => {
if (isDragging) {
isDragging = false
btn.style.transition = 'all 0.3s ease'
btn.style.cursor = 'grab'
document.body.style.userSelect = ''
document.body.style.webkitUserSelect = ''
}
})
// 点击按钮切换弹窗显示状态
btn.addEventListener('click', (e) => {
// 如果有拖动行为,不触发点击
if (hasMoved) {
e.preventDefault()
return
}
const btnRect = btn.getBoundingClientRect()
const isVisible = window.getComputedStyle(modal).display === 'block'
if (!isVisible) {
// 根据按钮位置调整弹窗位置
if (btnRect.left <= window.innerWidth / 2) {
modal.style.left = `${btnRect.right + 20}px`
modal.style.right = 'auto'
} else {
modal.style.right = `${window.innerWidth - btnRect.left + 20}px`
modal.style.left = 'auto'
}
if (btnRect.top <= window.innerHeight / 2) {
modal.style.top = btnRect.top + 'px'
modal.style.bottom = 'auto'
} else {
modal.style.bottom = `${window.innerHeight - btnRect.bottom}px`
modal.style.top = 'auto'
}
}
modal.style.display = isVisible ? 'none' : 'block'
})
// 点击关闭按钮
modal.addEventListener('click', (e) => {
const closeBtn = e.target.closest('.close-btn')
if (closeBtn) {
e.stopPropagation()
modal.style.display = 'none'
}
const item = e.target.closest('.vip-parse-item')
if (item) {
const index = parseInt(item.dataset.index)
const parseUrl = list[index]
const currentUrl = window.location.href
config.lastUsedIndex = index
saveConfig(config)
updateModalContent() // 更新列表显示
window.open(parseUrl + currentUrl, '_blank')
}
})
// 点击页面其他区域关闭弹窗
document.addEventListener('click', (e) => {
if (!modal.contains(e.target) && !btn.contains(e.target)) {
modal.style.display = 'none'
}
})
// 样式定义
const style = document.createElement('style')
style.textContent = `
.vip-parse-btn {
position: fixed;
z-index: 9999;
width: 48px;
height: 48px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 50%;
cursor: grab;
box-shadow: 0 2px 8px var(--shadow-color);
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
font-size: 0;
user-select: none;
}
.vip-parse-btn:active {
cursor: grabbing;
}
.vip-parse-btn::before {
content: "🎬";
font-size: 24px;
}
.vip-parse-btn::after {
content: "VIP视频解析";
position: absolute;
background: var(--tooltip-bg);
color: var(--tooltip-text);
padding: 8px 16px;
border-radius: 8px;
font-size: 14px;
white-space: nowrap;
right: 60px;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
pointer-events: none;
}
.vip-parse-btn:hover {
background: var(--primary-hover);
transform: scale(1.1);
box-shadow: 0 4px 12px var(--shadow-color);
}
.vip-parse-btn:hover::after {
opacity: 1;
visibility: visible;
}
.vip-parse-modal {
display: none;
position: fixed;
background: var(--modal-bg);
border-radius: 16px;
box-shadow: var(--modal-shadow);
z-index: 10000;
width: 280px;
overflow: hidden;
border: 1px solid var(--border-color);
user-select: none;
}
.vip-parse-modal * {
user-select: none;
}
.vip-parse-modal-header {
padding: 16px;
background: var(--header-bg);
border-bottom: 1px solid var(--border-color);
position: sticky;
top: 0;
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.vip-parse-modal-header h3 {
margin: 0;
color: var(--text-color);
font-size: 16px;
font-weight: 600;
flex: 1;
}
.theme-toggle {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 8px;
background: var(--toggle-bg);
transition: all 0.3s ease;
}
.theme-toggle:hover {
background: var(--toggle-hover);
}
.theme-icon {
font-size: 18px;
}
.vip-parse-modal .close-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 20px;
color: var(--text-color);
border-radius: 8px;
background: var(--close-bg);
transition: all 0.2s ease;
}
.vip-parse-modal .close-btn:hover {
background: var(--close-hover);
transform: rotate(90deg);
}
.vip-parse-list {
max-height: 360px;
overflow-y: auto;
padding: 12px;
}
.vip-parse-divider {
height: 1px;
background: var(--border-color);
margin: 12px 0;
opacity: 0.6;
}
.vip-parse-list::-webkit-scrollbar {
width: 6px;
}
.vip-parse-list::-webkit-scrollbar-thumb {
background: var(--scroll-thumb);
border-radius: 3px;
}
.vip-parse-list::-webkit-scrollbar-track {
background: var(--scroll-track);
}
.vip-parse-item {
padding: 12px 16px;
background: var(--item-bg);
color: var(--text-color);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
margin-bottom: 8px;
border: 1px solid var(--item-border);
display: flex;
justify-content: space-between;
align-items: center;
}
.vip-parse-item:last-child {
margin-bottom: 0;
}
.vip-parse-item:hover {
background: var(--item-hover);
transform: translateX(4px);
}
.vip-parse-item.last-used {
background: var(--last-used-bg);
border-color: var(--last-used-border);
margin-bottom: 0;
}
.last-used-badge {
font-size: 12px;
padding: 2px 6px;
border-radius: 4px;
background: var(--badge-bg);
color: var(--badge-text);
}
/* 亮色主题 */
:root {
--primary-color: #3B82F6;
--primary-hover: #2563EB;
--modal-bg: #FFFFFF;
--header-bg: #F8FAFC;
--text-color: #1E293B;
--border-color: #E2E8F0;
--item-bg: #F1F5F9;
--item-hover: #E2E8F0;
--item-border: #E2E8F0;
--close-bg: #F1F5F9;
--close-hover: #E2E8F0;
--toggle-bg: #F1F5F9;
--toggle-hover: #E2E8F0;
--scroll-thumb: #CBD5E1;
--scroll-track: #F1F5F9;
--tooltip-bg: #1E293B;
--tooltip-text: #FFFFFF;
--shadow-color: rgba(59, 130, 246, 0.3);
--modal-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
--last-used-bg: #EFF6FF;
--last-used-border: #93C5FD;
--badge-bg: #3B82F6;
--badge-text: #FFFFFF;
}
/* 暗色主题 */
.vip-dark-theme .vip-parse-modal {
--modal-bg: #1E293B;
--header-bg: #0F172A;
--text-color: #F1F5F9;
--border-color: #334155;
--item-bg: #334155;
--item-hover: #475569;
--item-border: #475569;
--close-bg: #334155;
--close-hover: #475569;
--toggle-bg: #334155;
--toggle-hover: #475569;
--scroll-thumb: #475569;
--scroll-track: #334155;
--tooltip-bg: #F1F5F9;
--tooltip-text: #1E293B;
--shadow-color: rgba(59, 130, 246, 0.5);
--modal-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
--last-used-bg: #1E40AF;
--last-used-border: #3B82F6;
--badge-bg: #60A5FA;
--badge-text: #1E293B;
}
/* 添加全局样式覆盖 */
.vip-parse-btn {
pointer-events: auto !important;
user-select: none !important;
-webkit-user-select: none !important;
}
.vip-parse-btn * {
pointer-events: none !important;
}
.vip-parse-modal {
pointer-events: auto !important;
}
`
document.head.appendChild(style)
// 添加样式重置
const resetStyle = document.createElement('style')
resetStyle.textContent = `
.mgtv-player-wrap * {
pointer-events: auto !important;
}
`
document.head.appendChild(resetStyle)
})();