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.5
// @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';
        if (window.getComputedStyle(videoWrapper).position == 'static') videoWrapper.style.position = 'relative';
        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: 99999999;
        }
        #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: 220px;
            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;
        }
        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]);
            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;
            }
            nameInput.value = sameUrlPoint.name;
            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();
        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();
})();