- // ==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)
- })();