Greasy Fork

Greasy Fork is available in English.

🌀VIP视频解析——fatcat

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

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

// ==UserScript==
// @name        🌀VIP视频解析——fatcat
// @namespace   [fatcat]
// @version     2.7.2
// @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.removeAttribute('src'); } catch (e) { } }) }, 200, 120000);
        createVideoFrame(videoWrapper, curAccessPoint);
        createMenu(accessPoints, curAccessPoint);
    }
    inject();
})();