Greasy Fork

Greasy Fork is available in English.

CMS资源站播放助手(纯净版)

为多个影视CMS站点自动添加M3U8加速播放按钮、去除广告、优化UI

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         CMS资源站播放助手(纯净版)
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  为多个影视CMS站点自动添加M3U8加速播放按钮、去除广告、优化UI
// @author       You
// @match        *://*/*
// @grant        GM_addStyle
// @run-at       document-end
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    // 直接注入CSS
    GM_addStyle(`
.d-i-b {
  display: inline-block !important;
}
 .kk-btn {
  display: flex;
  flex: none;
  align-items: center;
  box-sizing: border-box;
  border: 1px solid #1890ff;
  width: -moz-fit-content;
  width: fit-content;
  max-height: 30px;
  line-height: 30px;
  color: #1890ff !important;
  background-color: #fff;
  text-decoration: none;
  margin: 5px 10px;
  border-radius: 4px;
  padding: 0 4px;
  font-size: 16px;
  letter-spacing: 2px;
  font-weight: bold;
  box-shadow: 0 0 20px 2px #00000099;
}

.kk-btn-jump {
  font-size: 30px;
  max-height: 60px;
  line-height: 50px;
}

.kk-btn-sure {
  float: none !important;
  display: flex !important;
  flex: none !important;
  align-items: center !important;
  box-sizing: border-box !important;
  border: 1px solid #1890ff !important;
  width: -moz-fit-content !important;
  width: fit-content !important;
  height: 30px !important;
  min-height: 30px !important;
  max-height: 30px !important;
  line-height: 30px !important;
  color: #1890ff !important;
  background-color: #fff !important;
  text-decoration: none !important;
  margin-left: 5px !important;
  border-radius: 4px !important;
  padding: 0 4px !important;
  font-size: 16px !important;
  letter-spacing: 2px !important;
  font-weight: bold !important;
  text-decoration: none !important;
}

.kk-btn:hover {
  cursor: pointer;
  color: #5468ff !important;
  outline: 2px solid #1890ff !important;
}

.kk-btn:visited {
  cursor: pointer;
  color: #999 !important;
  border-color: #5468ff;
}
    `);

    const loc_href = window.location.href;
    const loc_hash = window.location.hash;
    const loc_origin = window.location.origin;

    // M3U8 解析加速接口(公开可用)
    const api_endpoint = 'https://m3u8.xuehuayu.cn?url=';

    // 简化选择器
    const qS = (sel, ctx = document) => ctx.querySelector(sel);
    const qSA = (sel, ctx = document) => [...ctx.querySelectorAll(sel)];

    // 工具函数:创建播放按钮
    function createPlaybackLink(targetHref, displayText) {
        const newAnchor = document.createElement('a');
        newAnchor.href = targetHref;
        newAnchor.target = '_blank';
        newAnchor.className = 'kk-btn kk-btn-sure';
        newAnchor.innerText = displayText;
        newAnchor.style.cssText = `
            margin-left: 8px;
            padding: 2px 6px;
            background: #4CAF50;
            color: white;
            text-decoration: none;
            border-radius: 4px;
            font-size: 12px;
        `;
        return newAnchor;
    }

    // 移除高 z-index 广告遮罩
    function removeOverlays() {
        const allDivs = qSA('body>div, body div>div');
        allDivs.forEach(el => {
            const zIndex = parseInt(window.getComputedStyle(el).zIndex, 10);
            if (zIndex > 9999) el.remove();
        });
    }

    // 批量移除元素
    function removeElements(selectorsOrSelector) {
        const selectors = Array.isArray(selectorsOrSelector) ? selectorsOrSelector : [selectorsOrSelector];
        selectors.forEach(sel => {
            qSA(sel).forEach(el => el.remove());
        });
    }

    // 添加 CSS 类
    function addClassToElement(selector, className) {
        const el = qS(selector);
        if (el) el.classList.add(className);
    }

    // 添加 sticky 类
    function addStickyClass(selector, stickyClassName) {
        addClassToElement(selector, stickyClassName);
    }

    // 插入“跳转到播放源”快捷按钮
    function insertPlaybackTypeJumper(wrapperSelector, targetSelector) {
        const wrappers = qSA(wrapperSelector);
        let indexYun = null, indexM3u8 = null;

        wrappers.forEach((el, idx) => {
            const txt = el.innerText;
            if (txt.includes('yun') || txt.includes('云')) {
                indexYun = idx;
                indexM3u8 = wrappers.length - idx - 1;
            } else if (txt.includes('m3u8')) {
                indexM3u8 = idx;
                indexYun = wrappers.length - idx - 1;
            }
        });

        if (wrappers[indexYun]) wrappers[indexYun].id = 'kk-play-yun';
        if (wrappers[indexM3u8]) wrappers[indexM3u8].id = 'kk-play-m3u8';

        const target = qS(targetSelector);
        if (!target) return;

        const container = document.createElement('div');
        const inner = document.createElement('div');
        inner.style.textAlign = "center";
        inner.style.margin = "10px 0";

        const btnYun = document.createElement('a');
        btnYun.className = "kk-btn kk-btn-jump d-i-b";
        btnYun.href = "#kk-play-yun";
        btnYun.textContent = "跳到云播";
        btnYun.style.cssText = "margin: 0 8px; color: #1E88E5; text-decoration: underline; cursor: pointer;";

        const btnM3U8 = document.createElement('a');
        btnM3U8.className = "kk-btn kk-btn-jump d-i-b";
        btnM3U8.href = "#kk-play-m3u8";
        btnM3U8.textContent = "跳到M3U8";
        btnM3U8.style.cssText = "margin: 0 8px; color: #E53935; text-decoration: underline; cursor: pointer;";

        inner.appendChild(btnYun);
        inner.appendChild(btnM3U8);
        container.appendChild(inner);
        target.after(container);
    }

    // 插入收藏按钮(仅 UI,无实际存储)
    function insertFavoriteButton(imageSrc = '') {
        const favContainer = document.createElement('div');
        favContainer.className = 'kk-like-wrap';
        favContainer.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 9999;
        `;

        const favButton = document.createElement('button');
        favButton.className = 'kk-like-btn';
        favButton.innerText = "★ 收藏";
        favButton.title = "点击收藏(仅提示)";
        favButton.style.cssText = `
            padding: 8px 12px;
            background: #ff9800;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        `;

        favButton.onclick = function () {
            alert("【提示】此为演示版,收藏功能需配合扩展使用。\n当前为纯油猴脚本,无法保存。");
            favButton.innerText = "👌 已点击";
        };

        favContainer.appendChild(favButton);
        document.body.appendChild(favContainer);
    }

    // 开始执行主逻辑
    removeOverlays();

    if (loc_href.includes('ryzyw')) {
        insertPlaybackTypeJumper('h4>div', '.people');
        qSA('.playlist li').forEach(li => {
            const a = qS('a', li);
            if (!a || li.innerText.includes('全选')) return;
            let href = a.href;
            if (href.includes('.m3u8')) {
                href = api_endpoint + encodeURIComponent(href);
            } else {
                href = api_endpoint + encodeURIComponent(href) + '&iframe=true';
            }
            a.after(createPlaybackLink(href, '播放:' + a.title));
        });
        insertFavoriteButton(qS('.people .left img')?.src);

    } else if (loc_href.includes('dyttzy') && loc_href.includes('vod/detail/id')) {
        qSA('.playlist li').forEach(li => {
            const a = qS('a', li);
            if (!a || li.innerText.includes('全选')) return;
            let href = a.href;
            if (href.includes('.m3u8')) {
                href = api_endpoint + encodeURIComponent(href);
            }
            a.after(createPlaybackLink(href, '播放:' + a.title));
        });
        insertFavoriteButton(qS('.bg-extra-sage img.w-full.h-full.rounded-lg.shadow-md')?.src);

    } else if (loc_href.includes('dbzy') || loc_href.includes('doubanz')) {
        if (loc_href.includes('/voddetail/')) {
            qSA('.vodplayinfo li').forEach(li => {
                const a = qS('a', li);
                if (!a || li.innerText.includes('全选')) return;
                let raw = a.href;
                if (!raw.startsWith('http')) raw = loc_origin + '/' + raw;
                const num = a.innerText.split('$')[0] || '';
                let final = raw.includes('.m3u8')
                    ? api_endpoint + encodeURIComponent(raw)
                    : api_endpoint + encodeURIComponent(raw) + '&iframe=true';
                a.after(createPlaybackLink(final, `播放:${num}`));
            });
            insertPlaybackTypeJumper('.vodplayinfo h3', '.ibox');
            insertFavoriteButton(qS('.vodImg img')?.src);
            removeElements('header');
        }
        addStickyClass('.xing_top', 'kk-sticky');
        addClassToElement('.search-text', 'kk-search-ipt');
        addClassToElement('.search-btn', 'kk-search-btn');
        removeElements('.gg_top');

    } else if (loc_href.includes('mozhua') && loc_href.includes('/vod/detail/')) {
        qSA('.listitems').forEach(li => {
            const btn = qS('.btn a', li);
            if (!btn) return;
            const text = li.innerText.split('$')[0] || '';
            const match = btn.href.match(/=(https?:\/\/[^&]+?\.m3u8)/);
            if (match) {
                btn.href = api_endpoint + encodeURIComponent(match[1]);
            }
            btn.innerText += ':' + text;
            btn.classList.add('kk-btn');
        });
        insertFavoriteButton(qS('article img')?.src);

    } else if (loc_href.includes('jisuz') && loc_href.includes('/vod/detail/')) {
        qSA('.vod-list .list-item').forEach(li => {
            const a = qS('a', li);
            if (!a || li.innerText.includes('全选')) return;
            let raw = a.href;
            if (!raw.startsWith('http')) raw = loc_origin + '/' + raw;
            const num = a.innerText.split('$')[0] || '';
            let final = raw.includes('.m3u8')
                ? api_endpoint + encodeURIComponent(raw)
                : api_endpoint + encodeURIComponent(raw) + '&iframe=true';
            a.after(createPlaybackLink(final, `播放:${num}`));
        });
        insertFavoriteButton(qS('.vod-img img')?.src);
        insertPlaybackTypeJumper('.vod-list h3', '.vod-introduce');
        removeElements('.topbg');

    } else if (loc_href.includes('moduzy') && loc_href.includes('/vod/')) {
        qSA('.content__playlist li').forEach(li => {
            const a = qS('a', li);
            if (!a || li.innerText.includes('全选')) return;
            let raw = a.href;
            if (!raw.startsWith('http')) raw = loc_origin + '/' + raw;
            const num = a.innerText.split('$')[0] || '';
            let final = raw;
            const m3u8Match = raw.match(/=(https?:\/\/[^&]+?\.m3u8)/);
            if (m3u8Match) {
                final = api_endpoint + encodeURIComponent(m3u8Match[1]);
            }
            li.append(createPlaybackLink(final, `播放:${num}`));
        });
        insertFavoriteButton(qS('.content__thumb thumb img')?.src);
        removeElements('.index-header');

    } else if (loc_href.includes('haohuazy') || loc_href.includes('haohuazyziyuan')) {
        if (loc_href.includes('detail')) {
            qSA('.vod-list .list-item').forEach(li => {
                let raw = qS('a', li)?.href;
                if (!raw || li.innerText.includes('全选')) return;
                if (!raw.startsWith('http')) raw = loc_origin + '/' + raw;
                const numEl = qS('.list-title', li);
                const num = numEl ? numEl.innerText.split('$')[0] : '';
                let final = raw.includes('.m3u8')
                    ? api_endpoint + encodeURIComponent(raw)
                    : api_endpoint + encodeURIComponent(raw) + '&iframe=true';
                li.append(createPlaybackLink(final, `播放:${num}`));
            });
            insertFavoriteButton(qS('.vod-img img')?.src);
            insertPlaybackTypeJumper('.vod-list h3', '.vod-info');
        }
        addStickyClass('.top', 'kk-sticky');
        addClassToElement('.search-text', 'kk-search-ipt');
        addClassToElement('.top-search button', 'kk-search-btn');
        removeElements(['.card', 'body>a']);

    } else if (loc_href.includes('guangsuzy') || loc_href.includes('guangsuziyuan')) {
        if (loc_href.includes('detail')) {
            qSA('.dy-collect-list li').forEach(li => {
                let raw = qS('a', li)?.href;
                if (!raw || li.innerText.includes('全选')) return;
                if (!raw.startsWith('http')) raw = loc_origin + '/' + raw;
                const name = qS('.c-name', li)?.innerText || '';
                let final = raw.includes('.m3u8')
                    ? api_endpoint + encodeURIComponent(raw)
                    : api_endpoint + encodeURIComponent(raw) + '&iframe=true';
                li.append(createPlaybackLink(final, `播放:${name}`));
            });
            insertFavoriteButton(qS('.dy-photo img')?.src);
            insertPlaybackTypeJumper('.dy-collect-video', '.detailed');
        }

    } else if (loc_href.includes('hongniuzy') || loc_href.includes('hongniuziyuan')) {
        if (loc_href.includes('detail')) {
            qSA('.vodplayinfo li').forEach(li => {
                let raw = qS('a', li)?.href;
                if (!raw || li.innerText.includes('全选')) return;
                if (!raw.startsWith('http')) raw = loc_origin + '/' + raw;
                const title = qS('a', li)?.title || '';
                let final = raw.includes('.m3u8')
                    ? api_endpoint + encodeURIComponent(raw)
                    : api_endpoint + encodeURIComponent(raw) + '&iframe=true';
                li.append(createPlaybackLink(final, `播放:${title}`));
            });
            insertFavoriteButton(qS('.vodImg img')?.src);
            insertPlaybackTypeJumper('.vodplayinfo h3', '.ibox');
            removeElements('.index-header');
        }
        addStickyClass('.xing_top', 'kk-sticky');
        addClassToElement('#wd', 'kk-search-ipt');
        addClassToElement('.search-btn', 'kk-search-btn');
        removeElements('.index-header a');

    } else if (loc_href.includes('1080zyk') && loc_href.includes('detail')) {
        qSA('.playlist li').forEach(li => {
            const full = li.innerText;
            if (full.includes('全选')) return;
            const parts = full.split('$');
            const label = parts[0];
            let href = qS('a', li)?.href || parts[1];
            const m3u8Match = href.match(/=(https?:\/\/[^&]+?\.m3u8)/);
            if (m3u8Match) href = api_endpoint + encodeURIComponent(m3u8Match[1]);
            li.append(createPlaybackLink(href, `播放:${label}`));
        });
        insertFavoriteButton(qS('.vodImg img')?.src);
        removeElements(['#topBar', 'body>table']);
        addClassToElement('.search-text', 'kk-search-ipt');
        addClassToElement('.search-btn', 'kk-search-btn');

    } else if (loc_href.includes('ffzy') && loc_href.includes('detail')) {
        qSA('.playlist li').forEach(li => {
            let raw = qS('font', li)?.innerText || '';
            if (!raw || raw.includes('全选')) return;
            if (!raw.startsWith('http')) raw = loc_origin + '/' + raw;
            const parts = raw.split('$');
            const label = parts[0];
            const actual = parts[1];
            let final = actual.includes('.m3u8')
                ? api_endpoint + encodeURIComponent(actual)
                : api_endpoint + encodeURIComponent(actual) + '&iframe=true';
            li.append(createPlaybackLink(final, `播放:${label}`));
        });
        insertFavoriteButton(qS('.people .left img')?.src);
        insertPlaybackTypeJumper('#content h4', '.people');
        removeElements('.index-header');

    } else if (loc_href.includes('xinlangz') && loc_href.includes('detail')) {
        qSA('.collect-item-href .left').forEach(li => {
            const span = qS('span', li);
            const num = span ? span.innerText : '';
            const a = qS('a', li);
            if (!a) return;
            let href = a.href;
            if (href.includes('.m3u8')) {
                href = api_endpoint + encodeURIComponent(href);
            } else {
                href = api_endpoint + encodeURIComponent(href) + '&iframe=true';
            }
            li.append(createPlaybackLink(href, `播放:${num}`));
        });
        insertFavoriteButton(qS('.dy-pic img')?.src);
        insertPlaybackTypeJumper('.collect-item-title', '.dy-details');

    } else if (loc_href.includes('ikunzy') && loc_href.includes('voddetail')) {
        qSA('.listitems').forEach(li => {
            const full = li.innerText;
            if (full.includes('全选')) return;
            const label = full.split('$')[0];
            const btn = qS('.btn a', li);
            if (!btn) return;
            const match = btn.href.match(/=(https?:\/\/[^&]+?\.m3u8)/);
            if (match) {
                btn.href = api_endpoint + encodeURIComponent(match[1]);
            }
            btn.innerText += ':' + label;
            btn.classList.add('kk-btn');
        });
        insertFavoriteButton(qS('.countimg img')?.src);
        removeElements('.indextop');

    } else if (loc_href.includes('suonizy') || loc_href.includes('snzy')) {
        if (loc_href.includes('voddetail')) {
            qSA('.dy-collect-list li').forEach(li => {
                const full = li.innerText;
                if (full.includes('全选')) return;
                const parts = full.split('$');
                const label = parts[0];
                let href = qS('a', li)?.href || parts[1];
                const m3u8Match = href.match(/=(https?:\/\/[^&]+?\.m3u8)/);
                if (m3u8Match) href = api_endpoint + encodeURIComponent(m3u8Match[1]);
                li.append(createPlaybackLink(href, `播放:${label}`));
            });
            insertFavoriteButton(qS('img.res-img')?.src);
            removeElements(['.link-box']);
        }
        addClassToElement('#wd', 'kk-search-ipt');
        addClassToElement('.nav-input form button', 'kk-search-btn');
        removeElements(['header .content >a']);

    } else if (loc_href.includes('bfzy.tv') || /bfzy\d+\.tv/.test(loc_href)) {
        if (loc_href.includes('vod/detail')) {
            qSA('.playlist li').forEach(li => {
                const full = li.innerText;
                if (full.includes('全选')) return;
                const parts = full.split('$');
                const label = parts[0];
                let href = qS('a', li)?.href || parts[1];
                const m3u8Match = href.match(/=(https?:\/\/[^&]+?\.m3u8)/);
                if (m3u8Match) href = api_endpoint + encodeURIComponent(m3u8Match[1]);
                li.append(createPlaybackLink(href, `播放:${label}`));
            });
            insertFavoriteButton(qS('.people img')?.src);
            removeElements('.index-header');
        }
        addStickyClass('.head_box', 'kk-sticky');
        addClassToElement('.search-input', 'kk-search-ipt');
        addClassToElement('#searchbutton', 'kk-search-btn');
        removeElements('.index-card a[href*="ads"]');

    } else if (loc_href.includes('huyazy') || loc_href.includes('huyaziyuan')) {
        if (loc_href.includes('vod/detail')) {
            qSA('.vodplayinfo li').forEach(li => {
                const full = li.innerText;
                if (full.includes('全选')) return;
                const parts = full.split('$');
                const label = parts[0];
                let href = qS('a', li)?.href || parts[1];
                let final = href.includes('.m3u8')
                    ? api_endpoint + encodeURIComponent(href)
                    : api_endpoint + encodeURIComponent(href) + '&iframe=true';
                li.append(createPlaybackLink(final, `播放:${label}`));
            });
            insertFavoriteButton(qS('.vodImg img')?.src);
            insertPlaybackTypeJumper('.vodplayinfo h3', '.ibox');
            removeElements(['.imagetopbg', '.index-header', '.noticetop', 'body>a']);
        }
        removeElements(['.imagetopbg', 'body>a']);
    } else {
        const M3U8_URL_REGEX = /https?:\/\/[^\s"'\)<>\[\]{}]*\.m3u8(?:[?#][^\s"'\)<>\[\]{}]*)?/gi;
        const INSERTED_CLASS = 'm3u8-link-inserted';
        const coveredElements = new WeakSet();
        function extractFirstM3U8(str) {
            if (typeof str !== 'string') return null;
            const match = str.match(M3U8_URL_REGEX);
            return match ? match[0] : null;
        }
        function traversePostOrder(node) {
            if (node.nodeType !== Node.ELEMENT_NODE) {
                return;
            }
            for (const child of Array.from(node.children)) {
                traversePostOrder(child);
            }
            if (coveredElements.has(node)) {
                return;
            }
            if (!node.parentNode || node.parentNode.nodeType !== Node.ELEMENT_NODE) {
                return;
            }
            if (node.nextElementSibling?.classList?.contains(INSERTED_CLASS)) {
                return;
            }
            let m3u8Url = null;
            m3u8Url = extractFirstM3U8(node.textContent);
            if (!m3u8Url && node.attributes) {
                for (const attr of node.attributes) {
                    m3u8Url = extractFirstM3U8(attr.value);
                    if (m3u8Url) break;
                }
            }
            if (m3u8Url) {
                const link = createPlaybackLink(api_endpoint + encodeURIComponent(m3u8Url), '加速播放')
                node.parentNode.insertBefore(link, node.nextSibling);
                let ancestor = node;
                while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE) {
                    coveredElements.add(ancestor);
                    ancestor = ancestor.parentElement;
                }
            }
        }
        if (document.body) {
            traversePostOrder(document.body);
        } else {

            document.addEventListener('DOMContentLoaded', () => {
                traversePostOrder(document.body);
            });
        }
    }

})();