您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
适配手机端与电脑端,可自定义解析网址
// ==UserScript== // @name 🌀VIP视频解析——fatcat // @namespace http://greasyfork.icu/users/1313123-fei-miao // @version 2.9.3 // @description 适配手机端与电脑端,可自定义解析网址 // @author 暴走的肥猫 // @icon data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='_5' x1='32' y1='60.96' x2='32' y2='5.18' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%230087c9'/%3E%3Cstop offset='1' stop-color='%238fe36d'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect x='0' width='64' height='64' rx='13.63' ry='13.63' style='fill:url(%23_5); stroke-width:0px;'/%3E%3Cpath d='M53.14,22.76c1.04-1.28.37-2.32-.1-3.28-7.69-15.14-32.73-14.58-41.2-.5-13.44,19.29,7.69,44.27,28.91,34.61,14.13-6.24,14.32-28.05-.2-33.98-10.14-4.77-23.09,3.29-20.59,14.46,1.05,5.91,7.45,10.54,13.95,8.68,6.93-1.56,7.18-11.47.7-13.57-3.5-1.47-4.42,2.67-1.49,3.99,2.87,2.01.95,5.41-2.19,4.95-13.06-4.66.32-19.8,9.11-12.19,15.65,13.58-7.47,31.84-20.36,18.96C1.06,23.14,32.46.23,53.14,22.76Z' style='fill:%23f8faf9; stroke-width:0px;'/%3E%3C/svg%3E // @license GNU AGPLv3 // @match *://*.youku.com/* // @match *://*.iqiyi.com/* // @match *://v.qq.com/* // @match *://*.v.qq.com/* // @match *://*.mgtv.com/* // @match *://tv.sohu.com/* // @match *://film.sohu.com/* // @match *://*.bilibili.com/* // @match *://*.tudou.com/* // @match *://*.pptv.com/* // @compatible safari // @compatible chrome // @compatible edge // @grant GM_getValue // @grant GM_setValue // @grant GM.getValue // @grant GM.setValue // @run-at document-start // @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": "1907", "url": "https://im1907.top/?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=" }, { "name": "mmkv", "url": "https://jx.mmkv.cn/tv.php?url=" }, ]; var searchSelector = ['video']; var patches = { 'm.iqiyi.com': () => Object.defineProperty(navigator, 'userAgent', { get: () => 'Android' }) } 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 nodeSize = node.getBoundingClientRect(); let parents = [node]; while (node.parentNode != document.body) { parents.unshift(node.parentNode); node = node.parentNode; }; let parentSize; let sameSizeParent = parents.find(parent => { parentSize = parent.getBoundingClientRect(); return Math.abs(parentSize.width - nodeSize.width) < 1 && Math.abs(parentSize.height - nodeSize.height) < 1 }); return sameSizeParent; } 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(searchSelector.join(','))).find(videoNode => { if (window.getComputedStyle(videoNode).display == 'none') videoNode.style.display = 'revert'; return videoNode.getBoundingClientRect().width > 90 && videoNode.getBoundingClientRect().height > 40 }), 200, Infinity); let maxVideoNode = Array.from(document.querySelectorAll(searchSelector.join(','))).reduce((pre, cur) => { if (cur.getBoundingClientRect().width > (pre?.getBoundingClientRect().width || 0)) return cur; return pre; }); return seekSameSizeParentNode(maxVideoNode); } async function createVideoFrame(videoWrapper, curAccessPoint) { videoWrapper.style.overflow = 'hidden'; if (window.getComputedStyle(videoWrapper).position == 'static') videoWrapper.style.position = 'relative'; let iframeWrapper = document.createElement('div'); iframeWrapper.innerHTML = `<iframe src="${curAccessPoint ? curAccessPoint.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); Array.from(videoWrapper.children).forEach(el => { if (el != iframeWrapper) el.style.visibility = 'hidden' }) return; } async function createMenu(accessPoints, curAccessPoint) { if (document.querySelector('#fatcat_video_vip')) return; let wrapper = document.createElement('div'); wrapper.innerHTML = ` <div id="fatcat_video_vip" style="bottom: 8px; right: 8px;"> <style> #fatcat_video_vip { position: fixed; z-index: 99999999; } #fatcat_video_vip *{ transition: all ease 0.2s; } #fatcat_video_vip button { all: unset; } #fatcat_video_vip :is(.block-btn, .fatcat_video_vip_link) { display: inline-block; vertical-align: middle; background: linear-gradient(to top right, #61c400bf 0%, #2ea5ffc2 100%); white-space: nowrap; width: auto; height: 33px; padding-inline: 10px; font-size: 13px; 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: 15px; margin: auto; border-radius: 20px; padding: 20px; border: none; position: fixed; bottom: 8px; right: 8px; 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: inline-block; width: 60px; height: 33px; color: rgba(255,255,255,0.6); background-color: #191a20eb!important; border: 1px solid #ffffff42; padding: 0 10px; } #fatcat_video_vip .fatcat_video_vip_link{ width: 17px; height: 17px; line-height: 17px; font-size: 10px; padding: 0; } #fatcat_video_vip_link{ display: inline-block; color: inherit; text-decoration: none; } #fatcat_video_vip_link:hover{ color: rgba(255,255,255,0.8); transform: translateX(2px); } #fatcat_video_vip_link:hover .fatcat_video_vip_link{ width: 50px; } </style> <button class="block-btn" id="fatcat_video_vip_setwrapper">📍自选视频位置</button> <button class="block-btn" style="font-size: 20px;" id="fatcat_video_vip_popover_toggle_btn">≡</button> <div id="fatcat_video_vip_popover" hidden> <p style="margin-block: 0 16px;"><a href="${curAccessPoint ? curAccessPoint.url + window.location.href : ''}" target="_blank" id="fatcat_video_vip_link"><b id="fatcat_video_vip_popover_title">当前解析点 <span class="fatcat_video_vip_link">➜</span></b></a></p> <div style="display:flex;gap: 16px;"> <select class="block-btn" id="fatcat_video_vip_select" style="background: #191a20eb;width:170px;text-align:left;"> ${accessPoints.map(({ name, url }) => `<option value="${url}" ${name == curAccessPoint?.name ? 'selected' : ''}>${name}</option>`).join()} </select> <button class="block-btn" id="fatcat_video_vip_delpoint">删除</button> <button class="block-btn" id="fatcat_video_vip_resetpoints">重置</button> </div> <p style="margin-block: 20px 16px;"><b>自定义解析点</b></p> <div style="display:flex;gap: 16px;"> <input id="fatcat_video_vip_point_name" placeholder="名称" autocomplete="off" style="border-radius: 8px 0 0 8px;"/> <input id="fatcat_video_vip_point_url" type="url" placeholder="URL" autocomplete="off" style="border-radius: 0 8px 8px 0;margin-left:-17px;width:140px;flex:1;"/> <button class="block-btn" id="fatcat_video_vip_addpoint">增加</button> </div> <button style="position:absolute; top:6px; right:10px; opacity: 0.5; cursor:pointer; padding:10px;" id="fatcat_video_vip_popover_close_btn">✕</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 setWrapperBtn = wrapper.querySelector('#fatcat_video_vip_setwrapper'); 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 updateSelectView(accessPoints, curAccessPoint) { selects.innerHTML = accessPoints.map(({ name, url }) => `<option value="${url}" ${name == curAccessPoint.name ? 'selected' : ''}>${name}</option>`).join(); } async function applyAccessPoint(accessPoints, curAccessPoint) { await setUserConfig('accessPoints', accessPoints); await setUserConfig('curAccessPoint', curAccessPoint); updateSelectView(accessPoints, curAccessPoint); updateView(curAccessPoint); displayResult('✅ 完成'); return; } let resultToasting; function displayResult(html) { let rawTitle = '当前解析点 <span class="fatcat_video_vip_link">➜</span>'; popoverTitle.innerHTML = html; clearTimeout(resultToasting); resultToasting = setTimeout(() => popoverTitle.innerHTML = rawTitle, 1500); } addBtn.onclick = async () => { if (!(nameInput.value.trim() && urlInput.value.trim())) { displayResult('❌ 请完整输入'); return; } let accessPoints = await getUserConfig('accessPoints'); let sameUrlPoint = accessPoints.find(({ url }) => url == urlInput.value.trim()); if (sameUrlPoint) { nameInput.value = sameUrlPoint.name; displayResult('❌ 重复数据'); return; } let newPoint = { name: nameInput.value.trim(), url: urlInput.value.trim() }; let newAccessPoints = accessPoints.concat([newPoint]); applyAccessPoint(newAccessPoints, newPoint); } delBtn.onclick = async () => { let accessPoints = await getUserConfig('accessPoints'); let curAccessPoint = await getUserConfig('curAccessPoint'); if (accessPoints.length == 0) { displayResult('❌ 数据已被清空'); return; } nameInput.value = curAccessPoint.name; urlInput.value = curAccessPoint.url; let curAccessPointIndex = accessPoints.findIndex(point => point.name == curAccessPoint.name); let newAccessPoints = accessPoints.filter(point => point.name != curAccessPoint.name); curAccessPoint = newAccessPoints[Math.min(curAccessPointIndex, newAccessPoints.length - 1)]; applyAccessPoint(newAccessPoints, curAccessPoint); } resetBtn.onclick = async () => { applyAccessPoint(ACCESS_POINT, ACCESS_POINT[0]); } function setWrapper(e) { if (e.target.matches('#fatcat_video_vip,#fatcat_video_vip *')) return; e.target.classList.toggle('fatcat_video_vip_custom_wrapper', true); window.removeEventListener('mousedown', setWrapper); document.querySelector('#fatcat_video_vip_select_area_css').remove(); e.stopPropagation(); e.preventDefault(); } setWrapperBtn.addEventListener('click', () => { try { document.querySelector('#fatcat_video_vip_select_area_css').remove(); document.querySelectorAll('.fatcat_video_vip_custom_wrapper').forEach(element => { element.classList.toggle('fatcat_video_vip_custom_wrapper', false); }); } catch (e) { } searchSelector = ['.fatcat_video_vip_custom_wrapper']; let selectAreaCss = document.createElement('style'); selectAreaCss.id = 'fatcat_video_vip_select_area_css'; selectAreaCss.innerHTML = ` *:hover{outline: 2px solid #13bf92!important;outline-offset: -2px!important;box-shadow: inset 0 0 20px 7px #13bf9db3;z-index: 99999999;} *:has(*:hover){outline: unset!important;box-shadow: unset!important;} :is(#fatcat_video_vip,#fatcat_video_vip *):hover{outline:unset!important;box-shadow: unset!important;}`; document.body.append(selectAreaCss); setWrapperBtn.innerHTML = '请点击需要插入视频的区域'; setTimeout(() => setWrapperBtn.innerHTML = '若未生效,可能区域过小📍点击重选', 3000); window.addEventListener('mousedown', setWrapper); }) function updateView(curAccessPoint) { let iframe = document.querySelector('#fatcat_video_vip_iframe'); if (!iframe) return true; let src = curAccessPoint ? curAccessPoint.url + window.location.href : ''; aTag.href = src; if (iframe.src != src) { iframe.src = src; return true; } } window.addEventListener('mousedown', async (e) => { if (!(e.target.matches("#fatcat_video_vip *") || e.target.matches("#fatcat_video_vip_iframe"))) { let curAccessPoint = await getUserConfig('curAccessPoint'); useInterval(() => updateView(curAccessPoint), 100, 10000); } }); selects.onchange = async () => { let accessPoints = await getUserConfig('accessPoints'); let curAccessPoint = accessPoints.find(({ url }) => url == selects.value) await setUserConfig('curAccessPoint', curAccessPoint); updateView(curAccessPoint); }; document.body.appendChild(wrapper); } function checkUrlValid(url) { return !(/http(s?):\/\/.*http(s?):\/\/.*/.test(url) || /^http(s?):\/\/[^\/]*\/$/.test(url)); } async function waitForPageLoad() { return new Promise(resolve => { if (document.readyState == 'complete') resolve(); else document.addEventListener('DOMContentLoaded', resolve); }); } function stopVideo() { document.querySelectorAll('video').forEach(video => { video.onpause = null; video.play = () => { }; video.pause(); video.muted = true; }) } async function inject() { if (!checkUrlValid(window.location.href)) return; patches[window.location.host] && patches[window.location.host](); await waitForPageLoad(); let accessPoints = await getUserConfig('accessPoints'); if (!accessPoints) { await setUserConfig('accessPoints', ACCESS_POINT); await setUserConfig('curAccessPoint', ACCESS_POINT[0]); accessPoints = ACCESS_POINT; } let curAccessPoint = await getUserConfig('curAccessPoint'); createMenu(accessPoints, curAccessPoint); let videoWrapper = await findVideoWrapper(); try { stopVideo(); useInterval(stopVideo, 2000, Infinity); document.querySelector('#fatcat_video_vip_setwrapper').style.display = 'none'; } catch (e) { } createVideoFrame(videoWrapper, curAccessPoint); } inject(); })();