Greasy Fork

Greasy Fork is available in English.

🌀VIP视频解析——fatcat

适配手机端与电脑端,可自主管理解析点

当前为 2024-06-10 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        🌀VIP视频解析——fatcat
// @namespace   [fatcat]
// @version     2.7.1
// @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, 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);
        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>
        #fatcat_video_vip {
            position: fixed;
            bottom: 8px;
            right: 8px;
            z-index: 99999999;
        }
        #fatcat_video_vip button {
            all: unset;
        }
        #fatcat_video_vip .block-btn {
            display: inline-block;
            background: linear-gradient(to top right, #61c400bf 0%, #2ea5ffc2 100%);
            white-space: nowrap;
            width: 100px;
            height: 33px;
            font-size: 15px;
            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: absolute;
            inset: unset;
            bottom: 40px;
            right: 0;
            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_popover .block-btn{
            display: inline-block;
            width: auto;
            padding-inline: 10px;
            font-size: 13px;
        }
    </style>
    <div class="block-btn">
        <a href="${curAccessPoint ? curAccessPoint.url + window.location.href : ''}" target="_blank" id="fatcat_video_vip_link" style="color: white; display: inline-block; width: 66%; vertical-align: 2px;">跳转</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>
        <p style="margin-block: 0 16px;"><b id="fatcat_video_vip_popover_title">当前解析点</b></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" 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 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 = '当前解析点';
            popoverTitle.innerHTML = html;
            clearTimeout(resultToasting);
            resultToasting = setTimeout(() => popoverTitle.innerHTML = rawTitle, 1500);
        }
        addBtn.onclick = async () => {
            let accessPoints = await getUserConfig('accessPoints');
            let sameUrlPoint = accessPoints.find(({ url }) => url == urlInput.value.trim());
            if (sameUrlPoint) {
                nameInput.value = sameUrlPoint.name;
                displayResult('❌ 重复数据');
                return;
            }
            let sameNamePoint = accessPoints.find(({ name }) => name == nameInput.value.trim());
            if (sameNamePoint) {
                urlInput.value = sameNamePoint.url
                displayResult('❌ 重复数据');
                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]);
            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]);
        }
        async function updateView(curAccessPoint) {
            let iframe = document.querySelector('#fatcat_video_vip_iframe');
            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);
    }
    async function inject() {
        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');
        let videoWrapper = await findVideoWrapper();
        useInterval(() => { document.querySelectorAll('video').forEach(video => { try { video.muted = true; video.pause(); video.style.visibility = 'hidden'; video.currentTime = Infinity; video.removeAttribute('src'); } catch (e) { } }) }, 200, 120000);
        createVideoFrame(videoWrapper, curAccessPoint);
        createMenu(accessPoints, curAccessPoint);
    }
    inject();
})();