跳转 VIP 视频解析

在视频页跳转 VIP 视频解析网站

  1. // ==UserScript==
  2. // @name 跳转 VIP 视频解析
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.0.2
  5. // @license MIT
  6. // @description 在视频页跳转 VIP 视频解析网站
  7. // @author [Ares-Chang](https://github.com/Ares-Chang)
  8. // @match https://v.qq.com/*
  9. // @match https://www.mgtv.com/*
  10. // @match https://www.iqiyi.com/*
  11. // @match https://www.youku.com/*
  12. // @match https://v.youku.com/*
  13. // @icon https://www.google.com/s2/favicons?sz=64&domain=mgtv.com
  14. // @grant none
  15. // ==/UserScript==
  16.  
  17. /**
  18. * 以下代码为互联网收集,仅供学习参考,如有版权问题,请联系我删除
  19. */
  20. (function() {
  21. 'use strict';
  22.  
  23. const list = [
  24. 'https://jx.77flv.cc/?url=',
  25. 'https://jx.dmflv.cc/?url=',
  26. 'https://jx.xymp4.cc/?url=',
  27. 'https://www.yemu.xyz/?url=',
  28. 'https://jx.xmflv.com/?url=',
  29. 'https://jx.7kjx.com/?url=',
  30. 'https://www.8090.la/8090/?url=',
  31. 'https://api.qianqi.net/vip/?url=',
  32. 'https://jx.mmkv.cn/tv.php?url=',
  33. 'https://jx.973973.xyz/?url=',
  34. 'https://jx.2s0.cn/player/?url=',
  35. 'https://jx.nnxv.cn/tv.php?url=',
  36. ]
  37.  
  38. // 从本地存储加载配置
  39. const loadConfig = () => {
  40. const config = localStorage.getItem('vip-parse-config')
  41. if (config) {
  42. try {
  43. const parsed = JSON.parse(config)
  44. return {
  45. isDarkTheme: parsed.isDarkTheme || false,
  46. position: parsed.position || { right: '20px', bottom: '20px' },
  47. lastUsedIndex: parsed.lastUsedIndex ?? -1
  48. }
  49. } catch (e) {
  50. console.error('配置解析错误,使用默认配置')
  51. }
  52. }
  53. return {
  54. isDarkTheme: false,
  55. position: { right: '20px', bottom: '20px' },
  56. lastUsedIndex: -1
  57. }
  58. }
  59.  
  60. // 保存配置到本地存储
  61. const saveConfig = (config) => {
  62. try {
  63. localStorage.setItem('vip-parse-config', JSON.stringify(config))
  64. } catch (e) {
  65. console.error('配置保存失败', e)
  66. }
  67. }
  68.  
  69. const config = loadConfig()
  70.  
  71. // 创建按钮
  72. const btn = document.createElement('button')
  73. btn.className = 'vip-parse-btn'
  74. btn.setAttribute('title', 'VIP视频解析')
  75. Object.assign(btn.style, config.position)
  76. document.body.appendChild(btn)
  77.  
  78. // 创建弹窗
  79. const modal = document.createElement('div')
  80. modal.className = 'vip-parse-modal'
  81.  
  82. // 生成解析接口列表HTML
  83. const generateListHTML = () => {
  84. // 重新排序列表,将上次使用的放在最前面
  85. let sortedList = [...list]
  86. let lastUsedHtml = ''
  87. let otherHtml = ''
  88.  
  89. if (config.lastUsedIndex >= 0) {
  90. const lastUsed = sortedList[config.lastUsedIndex]
  91. lastUsedHtml = `
  92. <div class="vip-parse-item last-used" data-index="${config.lastUsedIndex}">
  93. <span class="item-name">解析接口 ${config.lastUsedIndex + 1}</span>
  94. <span class="last-used-badge">上次使用</span>
  95. </div>
  96. `
  97. }
  98.  
  99. otherHtml = sortedList
  100. .map((url, index) => {
  101. if (index === config.lastUsedIndex) return ''
  102. return `
  103. <div class="vip-parse-item" data-index="${index}">
  104. <span class="item-name">解析接口 ${index + 1}</span>
  105. </div>
  106. `
  107. })
  108. .filter(Boolean)
  109. .join('')
  110.  
  111. return `
  112. ${lastUsedHtml}
  113. ${lastUsedHtml && otherHtml ? '<div class="vip-parse-divider"></div>' : ''}
  114. ${otherHtml}
  115. `
  116. }
  117.  
  118. // 更新弹窗内容
  119. const updateModalContent = () => {
  120. modal.innerHTML = `
  121. <div class="vip-parse-modal-header">
  122. <h3>选择解析接口</h3>
  123. <div class="theme-toggle">
  124. <span class="theme-icon">${config.isDarkTheme ? '🌜' : '🌞'}</span>
  125. </div>
  126. <div class="close-btn">✕</div>
  127. </div>
  128. <div class="vip-parse-list">
  129. ${generateListHTML()}
  130. </div>
  131. `
  132. }
  133.  
  134. updateModalContent()
  135. document.body.appendChild(modal)
  136.  
  137. // 设置初始主题
  138. if (config.isDarkTheme) {
  139. document.body.classList.add('vip-dark-theme')
  140. }
  141.  
  142. // 主题切换功能
  143. let isDarkTheme = config.isDarkTheme
  144. modal.addEventListener('click', (e) => {
  145. const themeToggle = e.target.closest('.theme-toggle')
  146. if (themeToggle) {
  147. isDarkTheme = !isDarkTheme
  148. document.body.classList.toggle('vip-dark-theme')
  149. themeToggle.querySelector('.theme-icon').textContent = isDarkTheme ? '🌜' : '🌞'
  150. config.isDarkTheme = isDarkTheme
  151. saveConfig(config)
  152. }
  153. })
  154.  
  155. // 按钮拖动功能
  156. let isDragging = false
  157. let startX, startY, startLeft, startTop
  158. let dragStartTime = 0
  159. let hasMoved = false
  160.  
  161. const updateButtonPosition = (left, top) => {
  162. const rect = btn.getBoundingClientRect()
  163. const maxX = window.innerWidth - rect.width
  164. const maxY = window.innerHeight - rect.height
  165.  
  166. // 确保按钮不会超出视窗
  167. const newLeft = Math.min(Math.max(0, left), maxX)
  168. const newTop = Math.min(Math.max(0, top), maxY)
  169.  
  170. // 计算距离边缘的位置
  171. const right = window.innerWidth - newLeft - rect.width
  172. const bottom = window.innerHeight - newTop - rect.height
  173.  
  174. // 更新按钮位置
  175. const position = {}
  176. if (newLeft <= maxX / 2) {
  177. position.left = `${newLeft}px`
  178. position.right = 'auto'
  179. } else {
  180. position.right = `${right}px`
  181. position.left = 'auto'
  182. }
  183.  
  184. if (newTop <= maxY / 2) {
  185. position.top = `${newTop}px`
  186. position.bottom = 'auto'
  187. } else {
  188. position.bottom = `${bottom}px`
  189. position.top = 'auto'
  190. }
  191.  
  192. Object.assign(btn.style, position)
  193. config.position = position
  194. saveConfig(config)
  195. }
  196.  
  197. btn.addEventListener('mousedown', (e) => {
  198. if (e.button !== 0) return // 只响应左键
  199. isDragging = true
  200. hasMoved = false
  201. dragStartTime = Date.now()
  202. startX = e.clientX
  203. startY = e.clientY
  204. const rect = btn.getBoundingClientRect()
  205. startLeft = rect.left
  206. startTop = rect.top
  207. btn.style.transition = 'none'
  208. btn.style.cursor = 'grabbing'
  209. modal.style.display = 'none' // 开始拖动时关闭弹窗
  210.  
  211. // 防止拖动时页面选择
  212. document.body.style.userSelect = 'none'
  213. document.body.style.webkitUserSelect = 'none'
  214.  
  215. // 阻止事件冒泡和默认行为
  216. e.stopPropagation()
  217. e.preventDefault()
  218. })
  219.  
  220. document.addEventListener('mousemove', (e) => {
  221. if (!isDragging) return
  222. const deltaX = e.clientX - startX
  223. const deltaY = e.clientY - startY
  224. // 只有移动超过 5px 才认为是拖动
  225. if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
  226. hasMoved = true
  227. e.preventDefault()
  228. e.stopPropagation() // 阻止事件冒泡
  229. updateButtonPosition(startLeft + deltaX, startTop + deltaY)
  230. }
  231. }, { passive: false }) // 添加 passive: false 以确保可以调用 preventDefault
  232.  
  233. document.addEventListener('mouseup', (e) => {
  234. if (!isDragging) return
  235. isDragging = false
  236. btn.style.transition = 'all 0.3s ease'
  237. btn.style.cursor = 'grab'
  238. // 移除可能的全局选择限制
  239. document.body.style.userSelect = ''
  240. document.body.style.webkitUserSelect = ''
  241. })
  242.  
  243. // 添加额外的事件监听以确保能捕获到鼠标释放
  244. window.addEventListener('mouseup', (e) => {
  245. if (!isDragging) return
  246. isDragging = false
  247. btn.style.transition = 'all 0.3s ease'
  248. btn.style.cursor = 'grab'
  249. document.body.style.userSelect = ''
  250. document.body.style.webkitUserSelect = ''
  251. }, true)
  252.  
  253. // 添加鼠标离开窗口的保护措施
  254. window.addEventListener('mouseleave', () => {
  255. if (isDragging) {
  256. isDragging = false
  257. btn.style.transition = 'all 0.3s ease'
  258. btn.style.cursor = 'grab'
  259. document.body.style.userSelect = ''
  260. document.body.style.webkitUserSelect = ''
  261. }
  262. })
  263.  
  264. // 添加失去焦点的保护措施
  265. window.addEventListener('blur', () => {
  266. if (isDragging) {
  267. isDragging = false
  268. btn.style.transition = 'all 0.3s ease'
  269. btn.style.cursor = 'grab'
  270. document.body.style.userSelect = ''
  271. document.body.style.webkitUserSelect = ''
  272. }
  273. })
  274.  
  275. // 点击按钮切换弹窗显示状态
  276. btn.addEventListener('click', (e) => {
  277. // 如果有拖动行为,不触发点击
  278. if (hasMoved) {
  279. e.preventDefault()
  280. return
  281. }
  282. const btnRect = btn.getBoundingClientRect()
  283. const isVisible = window.getComputedStyle(modal).display === 'block'
  284. if (!isVisible) {
  285. // 根据按钮位置调整弹窗位置
  286. if (btnRect.left <= window.innerWidth / 2) {
  287. modal.style.left = `${btnRect.right + 20}px`
  288. modal.style.right = 'auto'
  289. } else {
  290. modal.style.right = `${window.innerWidth - btnRect.left + 20}px`
  291. modal.style.left = 'auto'
  292. }
  293.  
  294. if (btnRect.top <= window.innerHeight / 2) {
  295. modal.style.top = btnRect.top + 'px'
  296. modal.style.bottom = 'auto'
  297. } else {
  298. modal.style.bottom = `${window.innerHeight - btnRect.bottom}px`
  299. modal.style.top = 'auto'
  300. }
  301. }
  302.  
  303. modal.style.display = isVisible ? 'none' : 'block'
  304. })
  305.  
  306. // 点击关闭按钮
  307. modal.addEventListener('click', (e) => {
  308. const closeBtn = e.target.closest('.close-btn')
  309. if (closeBtn) {
  310. e.stopPropagation()
  311. modal.style.display = 'none'
  312. }
  313.  
  314. const item = e.target.closest('.vip-parse-item')
  315. if (item) {
  316. const index = parseInt(item.dataset.index)
  317. const parseUrl = list[index]
  318. const currentUrl = window.location.href
  319. config.lastUsedIndex = index
  320. saveConfig(config)
  321. updateModalContent() // 更新列表显示
  322. window.open(parseUrl + currentUrl, '_blank')
  323. }
  324. })
  325.  
  326. // 点击页面其他区域关闭弹窗
  327. document.addEventListener('click', (e) => {
  328. if (!modal.contains(e.target) && !btn.contains(e.target)) {
  329. modal.style.display = 'none'
  330. }
  331. })
  332.  
  333. // 样式定义
  334. const style = document.createElement('style')
  335. style.textContent = `
  336. .vip-parse-btn {
  337. position: fixed;
  338. z-index: 9999;
  339. width: 48px;
  340. height: 48px;
  341. background: var(--primary-color);
  342. color: white;
  343. border: none;
  344. border-radius: 50%;
  345. cursor: grab;
  346. box-shadow: 0 2px 8px var(--shadow-color);
  347. transition: all 0.3s ease;
  348. display: flex;
  349. align-items: center;
  350. justify-content: center;
  351. font-size: 0;
  352. user-select: none;
  353. }
  354. .vip-parse-btn:active {
  355. cursor: grabbing;
  356. }
  357. .vip-parse-btn::before {
  358. content: "🎬";
  359. font-size: 24px;
  360. }
  361. .vip-parse-btn::after {
  362. content: "VIP视频解析";
  363. position: absolute;
  364. background: var(--tooltip-bg);
  365. color: var(--tooltip-text);
  366. padding: 8px 16px;
  367. border-radius: 8px;
  368. font-size: 14px;
  369. white-space: nowrap;
  370. right: 60px;
  371. opacity: 0;
  372. visibility: hidden;
  373. transition: all 0.3s ease;
  374. pointer-events: none;
  375. }
  376. .vip-parse-btn:hover {
  377. background: var(--primary-hover);
  378. transform: scale(1.1);
  379. box-shadow: 0 4px 12px var(--shadow-color);
  380. }
  381. .vip-parse-btn:hover::after {
  382. opacity: 1;
  383. visibility: visible;
  384. }
  385. .vip-parse-modal {
  386. display: none;
  387. position: fixed;
  388. background: var(--modal-bg);
  389. border-radius: 16px;
  390. box-shadow: var(--modal-shadow);
  391. z-index: 10000;
  392. width: 280px;
  393. overflow: hidden;
  394. border: 1px solid var(--border-color);
  395. user-select: none;
  396. }
  397. .vip-parse-modal * {
  398. user-select: none;
  399. }
  400. .vip-parse-modal-header {
  401. padding: 16px;
  402. background: var(--header-bg);
  403. border-bottom: 1px solid var(--border-color);
  404. position: sticky;
  405. top: 0;
  406. display: flex;
  407. justify-content: space-between;
  408. align-items: center;
  409. gap: 12px;
  410. }
  411. .vip-parse-modal-header h3 {
  412. margin: 0;
  413. color: var(--text-color);
  414. font-size: 16px;
  415. font-weight: 600;
  416. flex: 1;
  417. }
  418. .theme-toggle {
  419. width: 32px;
  420. height: 32px;
  421. display: flex;
  422. align-items: center;
  423. justify-content: center;
  424. cursor: pointer;
  425. border-radius: 8px;
  426. background: var(--toggle-bg);
  427. transition: all 0.3s ease;
  428. }
  429. .theme-toggle:hover {
  430. background: var(--toggle-hover);
  431. }
  432. .theme-icon {
  433. font-size: 18px;
  434. }
  435. .vip-parse-modal .close-btn {
  436. width: 32px;
  437. height: 32px;
  438. display: flex;
  439. align-items: center;
  440. justify-content: center;
  441. cursor: pointer;
  442. font-size: 20px;
  443. color: var(--text-color);
  444. border-radius: 8px;
  445. background: var(--close-bg);
  446. transition: all 0.2s ease;
  447. }
  448. .vip-parse-modal .close-btn:hover {
  449. background: var(--close-hover);
  450. transform: rotate(90deg);
  451. }
  452. .vip-parse-list {
  453. max-height: 360px;
  454. overflow-y: auto;
  455. padding: 12px;
  456. }
  457. .vip-parse-divider {
  458. height: 1px;
  459. background: var(--border-color);
  460. margin: 12px 0;
  461. opacity: 0.6;
  462. }
  463. .vip-parse-list::-webkit-scrollbar {
  464. width: 6px;
  465. }
  466. .vip-parse-list::-webkit-scrollbar-thumb {
  467. background: var(--scroll-thumb);
  468. border-radius: 3px;
  469. }
  470. .vip-parse-list::-webkit-scrollbar-track {
  471. background: var(--scroll-track);
  472. }
  473. .vip-parse-item {
  474. padding: 12px 16px;
  475. background: var(--item-bg);
  476. color: var(--text-color);
  477. border-radius: 8px;
  478. cursor: pointer;
  479. transition: all 0.2s ease;
  480. font-size: 14px;
  481. margin-bottom: 8px;
  482. border: 1px solid var(--item-border);
  483. display: flex;
  484. justify-content: space-between;
  485. align-items: center;
  486. }
  487. .vip-parse-item:last-child {
  488. margin-bottom: 0;
  489. }
  490. .vip-parse-item:hover {
  491. background: var(--item-hover);
  492. transform: translateX(4px);
  493. }
  494. .vip-parse-item.last-used {
  495. background: var(--last-used-bg);
  496. border-color: var(--last-used-border);
  497. margin-bottom: 0;
  498. }
  499. .last-used-badge {
  500. font-size: 12px;
  501. padding: 2px 6px;
  502. border-radius: 4px;
  503. background: var(--badge-bg);
  504. color: var(--badge-text);
  505. }
  506.  
  507. /* 亮色主题 */
  508. :root {
  509. --primary-color: #3B82F6;
  510. --primary-hover: #2563EB;
  511. --modal-bg: #FFFFFF;
  512. --header-bg: #F8FAFC;
  513. --text-color: #1E293B;
  514. --border-color: #E2E8F0;
  515. --item-bg: #F1F5F9;
  516. --item-hover: #E2E8F0;
  517. --item-border: #E2E8F0;
  518. --close-bg: #F1F5F9;
  519. --close-hover: #E2E8F0;
  520. --toggle-bg: #F1F5F9;
  521. --toggle-hover: #E2E8F0;
  522. --scroll-thumb: #CBD5E1;
  523. --scroll-track: #F1F5F9;
  524. --tooltip-bg: #1E293B;
  525. --tooltip-text: #FFFFFF;
  526. --shadow-color: rgba(59, 130, 246, 0.3);
  527. --modal-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  528. --last-used-bg: #EFF6FF;
  529. --last-used-border: #93C5FD;
  530. --badge-bg: #3B82F6;
  531. --badge-text: #FFFFFF;
  532. }
  533.  
  534. /* 暗色主题 */
  535. .vip-dark-theme .vip-parse-modal {
  536. --modal-bg: #1E293B;
  537. --header-bg: #0F172A;
  538. --text-color: #F1F5F9;
  539. --border-color: #334155;
  540. --item-bg: #334155;
  541. --item-hover: #475569;
  542. --item-border: #475569;
  543. --close-bg: #334155;
  544. --close-hover: #475569;
  545. --toggle-bg: #334155;
  546. --toggle-hover: #475569;
  547. --scroll-thumb: #475569;
  548. --scroll-track: #334155;
  549. --tooltip-bg: #F1F5F9;
  550. --tooltip-text: #1E293B;
  551. --shadow-color: rgba(59, 130, 246, 0.5);
  552. --modal-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  553. --last-used-bg: #1E40AF;
  554. --last-used-border: #3B82F6;
  555. --badge-bg: #60A5FA;
  556. --badge-text: #1E293B;
  557. }
  558.  
  559. /* 添加全局样式覆盖 */
  560. .vip-parse-btn {
  561. pointer-events: auto !important;
  562. user-select: none !important;
  563. -webkit-user-select: none !important;
  564. }
  565. .vip-parse-btn * {
  566. pointer-events: none !important;
  567. }
  568. .vip-parse-modal {
  569. pointer-events: auto !important;
  570. }
  571. `
  572. document.head.appendChild(style)
  573.  
  574. // 添加样式重置
  575. const resetStyle = document.createElement('style')
  576. resetStyle.textContent = `
  577. .mgtv-player-wrap * {
  578. pointer-events: auto !important;
  579. }
  580. `
  581. document.head.appendChild(resetStyle)
  582. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址