您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
适配手机端与电脑端,可自主管理解析点
当前为
// ==UserScript== // @name 🌀VIP视频解析——fatcat // @namespace [fatcat] // @version 2.4 // @description 适配手机端与电脑端,可自主管理解析点 // @author 暴走的肥猫 // @license MIT, Sign after modification // @include /^http[^http]*youku\.com\/.+$/ // @include /^http[^http]*iqiyi\.com\/.+$/ // @include /^http[^http]*v\.qq\.com\/.+$/ // @include /^http[^http]*le\.com\/.+$/ // @include /^http[^http]*mgtv\.com\/.+$/ // @include /^http[^http]*tv\.sohu\.com\/.+$/ // @compatible safari // @compatible chrome // @compatible edge // @grant GM_getValue // @grant GM_setValue // @grant GM.getValue // @grant GM.setValue // @run-at document-idle // @noframes // ==/UserScript== /*jshint esversion: 11 */ (async function () { const ACCESS_POINT = [ { "name": "虾米-1", "url": "https://jx.xmflv.com/?url=" }, { "name": "虾米-2", "url": "https://jx.xmflv.cc/?url=" }, { "name": "M1907-1", "url": "https://im1907.top/?jx=" }, { "name": "M1907-2", "url": "https://z1.m1907.top/?eps=0&jx=" }, { "name": "M3U8TV", "url": "https://jx.m3u8.tv/jiexi/?url=" }, { "name": "夜幕", "url": "https://www.yemu.xyz/?url=" }, { "name": "777", "url": "https://jx.jsonplayer.com/player/?url=" }, { "name": "CK", "url": "https://www.ckplayer.vip/jiexi/?url=" }, { "name": "YT", "url": "https://jx.yangtu.top/?url=" }, { "name": "Player-JY", "url": "https://jx.playerjy.com/?url=" }, { "name": "Yparse", "url": "https://jx.yparse.com/index.php?url=" }, { "name": "8090", "url": "https://www.8090g.cn/?url=" }, { "name": "剖元", "url": "https://www.pouyun.com/?url=" }, { "name": "全民", "url": "https://43.240.74.102:4433?url=" }, { "name": "爱豆", "url": "https://jx.aidouer.net/?url=" }, { "name": "冰豆", "url": "https://bd.jx.cn/?url=" }, { "name": "Playm3u8", "url": "https://www.playm3u8.cn/jiexi.php?url=" }, ]; async function getUserConfig(key, defaultVal) { return typeof GM_getValue === 'function' ? GM_getValue(key, defaultVal) : GM.getValue(key, defaultVal); } async function setUserConfig(key, val) { return typeof GM_setValue === 'function' ? GM_setValue(key, val) : GM.setValue(key, val); } function seekSameSizeParentNode(node) { let parentSize = node.parentElement.getBoundingClientRect(); let nodeSize = node.getBoundingClientRect(); if (parentSize.width - nodeSize.width < 1 && parentSize.height - nodeSize.height < 1) return seekSameSizeParentNode(node.parentElement); return node } function useInterval(fn, intervalTime, maxTime) { return new Promise((resolve) => { let totalTime = 0; let interval = setInterval(() => { totalTime += intervalTime; if (totalTime >= maxTime || fn()) { clearInterval(interval); resolve(); } }, intervalTime) }) } async function findVideoWrapper() { await useInterval(() => Array.from(document.querySelectorAll('video')).find(videoNode => { videoNode.style.display = 'revert'; return videoNode.getBoundingClientRect().width > 100 && videoNode.getBoundingClientRect().height > 100 }), 200, Infinity); let videoNodes = Array.from(document.querySelectorAll('video')); return videoNodes.map(videoNode => seekSameSizeParentNode(videoNode)).reduce((pre, cur) => { let preW = pre?.getBoundingClientRect().width || 0; let curW = cur.getBoundingClientRect().width; if (curW > preW) return cur; return pre; }) } async function createVideoFrame(videoWrapper, lastAccessPoint) { videoWrapper.style.overflow = 'hidden'; let iframeWrapper = document.createElement('div'); iframeWrapper.innerHTML = `<iframe src="${lastAccessPoint.url}${window.location.href}" width="100%" height="100%" allowfullscreen id="fatcat_video_vip_iframe" style="border:none;"/>`; iframeWrapper.style = `position:absolute; inset:0; z-index: 99999999;` videoWrapper.appendChild(iframeWrapper); return; } async function createMenu(accessPoints, lastAccessPoint) { if (document.querySelector('#fatcat_video_vip')) return; let wrapper = document.createElement('div'); wrapper.innerHTML = ` <div id="fatcat_video_vip"> <style> #fatcat_video_vip { position: fixed; bottom: 0; right: 0; z-index: 99999998; } #fatcat_video_vip button { all: unset; } #fatcat_video_vip .block-btn { display: block; background: linear-gradient(to top right, #61c400bf 0%, #2ea5ffc2 100%); white-space: nowrap; width: 100px; height: 33px; font-size: 15px; line-height: 30px; margin: 8px; color: white; border-radius: 8px; text-align: center; outline: none; border: 1px solid #ffffff57; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); cursor: pointer; } #fatcat_video_vip_popover { background: #191a20eb; font-size: 14px; margin: auto; border-radius: 20px; padding: 20px; border: none; position: fixed; inset: unset; bottom: 5px; right: 5px; max-width: clac(100vw - 10px); color: white; border: 1px solid #ffffff42; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); white-space: nowrap; } #fatcat_video_vip_popover input{ display: block; width: 200px; margin-block: 20px; color:rgba(255,255,255,0.6); background-color: #191a20eb!important; border: 1px solid #ffffff42; border-radius: 6px; padding: 7px 10px; } #fatcat_video_vip_popover .block-btn{ display: inline-block; margin: 0 10px 0 0; width: auto; padding-inline: 10px; font-size: 13px; } </style> <select class="block-btn" id="fatcat_video_vip_select" style="background: #191a20eb;"> ${accessPoints.map(({ name, url }) => `<option value="${url}" ${name == lastAccessPoint?.name ? 'selected' : ''}>${name}</option>`).join()} </select> <div class="block-btn"> <a href="${lastAccessPoint.url}${window.location.href}" target="_blank" id="fatcat_video_vip_link" style="color: white; display: inline-block; width: 66%; height: 100%;">跳转</a><button style="color: white; display: inline-block; width: 33%; height: 100%; border-left: 1px solid #ffffff57; font-size: 20px;" id="fatcat_video_vip_popover_toggle_btn">≡</button> </div> <div id="fatcat_video_vip_popover" hidden> <b id="fatcat_video_vip_popover_title">自定义解析点</b> <button style="position:absolute; top:6px; right:10px; opacity: 0.5; cursor:pointer; padding:10px;" id="fatcat_video_vip_popover_close_btn">✕</button> <input id="fatcat_video_vip_point_name" placeholder="名称" autocomplete="off"/> <input id="fatcat_video_vip_point_url" placeholder="URL" autocomplete="off"/> <button class="block-btn" id="fatcat_video_vip_addpoint">增加</button> <button class="block-btn" id="fatcat_video_vip_delpoint">根据名称删除</button> <button class="block-btn" id="fatcat_video_vip_resetpoints">重置</button> </div> </div> `; let popoverToggleBtn = wrapper.querySelector('#fatcat_video_vip_popover_toggle_btn'); let popoverCloseBtn = wrapper.querySelector('#fatcat_video_vip_popover_close_btn'); let popover = wrapper.querySelector('#fatcat_video_vip_popover'); popoverToggleBtn.onclick = () => popover.toggleAttribute('hidden'); popoverCloseBtn.onclick = () => popover.toggleAttribute('hidden', true); window.addEventListener('click', (e) => { if (!e.target.matches('#fatcat_video_vip_popover,#fatcat_video_vip_popover *,#fatcat_video_vip_popover_toggle_btn')) popover.toggleAttribute('hidden', true); }) let popoverTitle = wrapper.querySelector('#fatcat_video_vip_popover_title'); let nameInput = wrapper.querySelector('#fatcat_video_vip_point_name'); let urlInput = wrapper.querySelector('#fatcat_video_vip_point_url'); let addBtn = wrapper.querySelector('#fatcat_video_vip_addpoint'); let delBtn = wrapper.querySelector('#fatcat_video_vip_delpoint'); let resetBtn = wrapper.querySelector('#fatcat_video_vip_resetpoints'); let selects = wrapper.querySelector('#fatcat_video_vip_select'); let aTag = wrapper.querySelector('#fatcat_video_vip_link'); function updateSelect(accessPoints, lastAccessPoint) { selects.innerHTML = accessPoints.map(({ name, url }) => `<option value="${url}" ${name == lastAccessPoint.name ? 'selected' : ''}>${name}</option>`).join(); updateLink(); displayResult('✅ 操作成功'); } async function applyCurValidAccessPoint() { let accessPoints = await getUserConfig('accessPoints'); let lastAccessPoint = await getUserConfig('lastAccessPoint'); lastAccessPoint = accessPoints.find(point => point.name == lastAccessPoint.name); if (!lastAccessPoint) await setUserConfig('lastAccessPoint', accessPoints[0]); return; } function displayResult(html) { let rawTitle = '自定义解析点'; popoverTitle.innerHTML = html; setTimeout(() => popoverTitle.innerHTML = rawTitle, 1000); } addBtn.onclick = async () => { let accessPoints = await getUserConfig('accessPoints'); let sameUrlPoint = accessPoints.find(({ url }) => url == urlInput.value.trim()); if (sameUrlPoint) { displayResult('❌ 存在相同URL: ' + sameUrlPoint.name); return; } let sameNamePoint = accessPoints.find(({ name }) => name == nameInput.value.trim()); if (sameNamePoint) { displayResult('❌ 存在相同名称: ' + sameNamePoint.name); return; } if (!(nameInput.value.trim() && urlInput.value.trim())) { displayResult('❌ 请输入完整'); return; } let newPoint = { name: nameInput.value.trim(), url: urlInput.value.trim() }; let newAccessPoints = accessPoints.concat([newPoint]); await setUserConfig('accessPoints', newAccessPoints); await applyCurValidAccessPoint(); let lastAccessPoint = await getUserConfig('lastAccessPoint'); updateSelect(newAccessPoints, lastAccessPoint); } delBtn.onclick = async () => { let accessPoints = await getUserConfig('accessPoints'); let sameUrlPoint = accessPoints.find(({ name }) => name == nameInput.value.trim()); if (!sameUrlPoint) { displayResult('❌ 不存在: ' + nameInput.value.trim()); return; } let newAccessPoints = accessPoints.filter(point => point.name != nameInput.value.trim()); await setUserConfig('accessPoints', newAccessPoints); await applyCurValidAccessPoint(); let lastAccessPoint = await getUserConfig('lastAccessPoint'); updateSelect(newAccessPoints, lastAccessPoint); } resetBtn.onclick = async () => { let lastAccessPoint = await getUserConfig('lastAccessPoint'); lastAccessPoint = ACCESS_POINT.find(point => point.name == lastAccessPoint.name); await setUserConfig('accessPoints', ACCESS_POINT); await applyCurValidAccessPoint(); updateSelect(ACCESS_POINT, lastAccessPoint); } async function updateLink() { let iframe = document.querySelector('#fatcat_video_vip_iframe'); let accessPoints = await getUserConfig('accessPoints'); let val = selects.value; aTag.href = val + window.location.href; if (iframe.src != (val + window.location.href)) { iframe.src = val + window.location.href; await setUserConfig('lastAccessPoint', accessPoints.find(({ url }) => url == val)); return true; } } window.addEventListener('mousedown', (e) => { if (!(e.target.matches("#fatcat_video_vip *") || e.target.matches("#fatcat_video_vip_iframe"))) { useInterval(updateLink, 100, 10000); } }); selects.onchange = updateLink; document.body.appendChild(wrapper); } async function inject() { let accessPoints = await getUserConfig('accessPoints'); if (!accessPoints) { setUserConfig('accessPoints', ACCESS_POINT); accessPoints = ACCESS_POINT; } let lastAccessPoint = await getUserConfig('lastAccessPoint'); if (!lastAccessPoint) { setUserConfig('lastAccessPoint', ACCESS_POINT[0]); lastAccessPoint = ACCESS_POINT[0]; } let videoWrapper = await findVideoWrapper(); await new Promise(resolve => { document.readyState == 'complete' ? resolve() : window.addEventListener('load', resolve) }); useInterval(() => { document.querySelectorAll('video').forEach(video => { try { video.pause(); video.style.visibility = 'hidden'; video.src = '' } catch (e) { } }) }, 200, 120000); createVideoFrame(videoWrapper, lastAccessPoint); createMenu(accessPoints, lastAccessPoint); } inject(); })();