豆瓣电影资源 多平台磁链搜索 + 云播放【即点即播】 直接看!

自动在多个平台搜索豆瓣电影(电视剧)磁链,点击即可直接跳转播放,不用下直接播,搜索播放一键完成,干净如新的流畅体验!

// ==UserScript==
// @name         豆瓣电影资源 多平台磁链搜索 + 云播放【即点即播】 直接看!
// @name:zh-CN   豆瓣电影资源 多平台磁链搜索 + 云播放【即点即播】 直接看!
// @name:zh-TW   豆瓣電影資源 多平臺磁鏈搜索 + 雲播放【即點即播】 直接看!
// @namespace    dbzjk@healthylife
// @version      0.0.1
// @description  自动在多个平台搜索豆瓣电影(电视剧)磁链,点击即可直接跳转播放,不用下直接播,搜索播放一键完成,干净如新的流畅体验!
// @description:zh-CN  自动在多个平台搜索豆瓣电影(电视剧)磁链,点击即可直接跳转播放,不用下直接播,搜索播放一键完成,干净如新的流畅体验!
// @description:zh-TW  自動在多個平臺搜索豆瓣電影(電視劇)磁鏈,點擊即可直接跳轉播放,不用下直接播,搜索播放一鍵完成,幹凈如新的流暢體驗!
// @author       healthylife
// @match        *://movie.douban.com/subject/*
// @require      https://cdn.jsdelivr.net/npm/vue@2/dist/vue.min.js
// @run-at       document-idle
// @grant        GM_xmlhttpRequest
// @grant        GM.xmlHttpRequest
// @connect      clm00.biz
// @connect      5cltt.vip
// @connect      www.btmovi.hair
// ==/UserScript==

(function () {
    'use strict';

    const engines = [
        {
            results: [],
            icon: 'https://s1.ax1x.com/2022/11/14/zAU2eU.png',
            name: "磁力猫",
            async search(keyword) {
                let dom = await getDOM(`http://clm00.biz/search?word=${encodeURIComponent(keyword)}`);
                return $$(dom, '#Search_list_wrapper>li').map(li => {
                    return {
                        title: $(li, 'a.SearchListTitle_result_title').textContent.trim(),
                        tags: Array.from($(li, '.Search_list_info').childNodes).filter(n => n.nodeType == 3).map(n => n.nodeValue),
                        subUrl: new URL($(li, 'a.SearchListTitle_result_title').getAttribute('href'), 'http://clm00.biz/search').href
                    };
                }).filter(n => titleMatch(n.title, keyword));
            },
            async magurl(para) {
                return await getMag(para.subUrl);
            },
            showAll: false,
            done: false
        }, {
            results: [],
            icon: 'https://5cltt.vip/favicon.ico',
            name: "磁力天堂",
            async search(keyword) {
                let dom = await getDOM(`https://5cltt.vip/s.php?q=${encodeURIComponent(keyword)}`);
                return $$(dom, '.file-list>tbody>tr').filter(li => $(li, 'a')).map(li => {
                    return {
                        title: $(li, 'a').textContent.trim(),
                        tags: $$(li, '.td-size,.td-date').map(n => n.textContent.trim()),
                        subUrl: new URL($(li, 'a').getAttribute('href'), 'https://5cltt.vip/s.php').href
                    };
                }).filter(n => titleMatch(n.title, keyword));
            },
            async magurl(para) {
                return await getMag(para.subUrl);
            },
            showAll: false,
            done: false
        }, {
            results: [],
            icon: 'https://s1.ax1x.com/2022/11/14/zA0yh4.png',
            name: "磁力搜索",
            async search(keyword) {
                let dom = await getDOM(`http://www.btmovi.hair/so/${encodeURIComponent(keyword)}.html`);
                return $$(dom, '.search-list>.search-item').filter(li => $(li, '.item-title a')).map(li => {
                    return {
                        title: $(li, '.item-title a').textContent.trim(),
                        tags: $$(li, '.item-bar>*').map(n => {
                            if (!$(n, 'b')) return n.textContent.trim();
                            if (n.textContent.match(/热度/)) return n.textContent.trim();
                            return $(n, 'b').textContent.trim();
                        }),
                        subUrl: new URL($(li, '.item-title a').getAttribute('href'), 'http://www.btmovi.hair/so/123.html').href
                    };
                }).filter(n => titleMatch(n.title, keyword));
            },
            async magurl(para) {
                return await getMag(para.subUrl);
            },
            showAll: false,
            done: false
        }
    ]

    const magreg = /magnet:\?xt=urn:btih:\w{10,}([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;

    const title = document.body.innerText.match(/\n\s*([^\n]+)的(剧情简介|演职员|短评|影评)/)[1];

    addStyle(`
        .dbzjk-flex-row{
            display: flex;
            flex-direction: row;
            align-items: center;
        }
        .dbzjk-inline-flex{
            display: inline-flex;
        }


        #dbzjk-box{
            margin-bottom: 40px;
            display: flex;
            flex-direction: column;
            gap: .6em;
        }

        .dbzjk-engine{
            display: flex;
            flex-direction: column;
            gap: .6em;
        }

        .dbzjk-engine-title{
            font-weight: bold;
            color: #999;
        }
        .dbzjk-engine-icon{
            width: 1.5em;
            height: 1em;
            display: inline-block;
            background-position: center;
            background-size: contain;
            background-repeat: no-repeat;
            margin-right: .5em;
        }

        .dbzjk-title{
            gap: 0.4em;
            max-width: 100%;
        }
        .dbzjk-title svg{
            height: 1em;
            fill: currentColor;
        }

        .dbzjk-directly-play-tag{
            padding: 0px 5px;
            font-size: small;
            height: 1.3em;
            border-radius: 2px;
            background:#ff9900;
            color: white;
            flex-shrink: 0;
            gap: 0.3em;
        }
        .dbzjk-title:hover .dbzjk-directly-play-tag,.dbzjk-title:active .dbzjk-directly-play-tag{
            color: inherit;
            background:none;
        }

        .dbzjk-tag-line{
            display: flex;
            flex-direction: row;
            gap: .5em;
            font-size: .8em;

        }
    `);

    const root = document.createElement('div');
    document.querySelector(`#content .aside`).insertBefore(root, document.querySelector(`#content .aside>*:first-child`));
    root.id = 'dbzjk-box';
    root.innerHTML = html`
        <div v-if="!engines.filter(en=>en.results.length).length">
            {{engines.filter(en=>!en.done).length?"磁链搜索中……":"抱歉,没有搜到磁链。"}}
        </div>
        <div v-for="engi in engines.filter(en=>en.results.length)" class="dbzjk-engine">
            <div class='dbzjk-engine-title dbzjk-flex-row '><span v-if="engi.icon" class="dbzjk-engine-icon"
                    :style="{backgroundImage:'url('+engi.icon+')'}"></span><span>{{engi.name}}</span></div>
            <div v-for="r in engi.showAll?engi.results:engi.results.slice(0,3)">
                <a class='dbzjk-title dbzjk-flex-row dbzjk-inline-flex' :title="r.title" href @click.prevent="open(engi, r)">
                    <span class='dbzjk-directly-play-tag dbzjk-flex-row dbzjk-inline-flex'>
                        <span>直接播</span>
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
                            <!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
                            <path
                                d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z" />
                        </svg>
                    </span>
                    <span
                        style='white-space: nowrap;overflow: hidden;flex-grow: 0;text-overflow: ellipsis;'>{{r.loading?"获取磁链中……":r.title}}</span>
                </a>
                <div class='dbzjk-tag-line'>
                    <span class='dbzjk-tag' v-for="tag in r.tags">{{tag}}</span>
                </div>
            </div>
            <div v-if="(!engi.showAll) && engi.results.length>3">
                <a @click.prevent="engi.showAll=true" style='color: #999;'>更多 ({{engi.results.length-3}}个)</a>
            </div>
        </div>
    `;

    new Vue({
        el: '#dbzjk-box',
        data: {
            engines
        },
        mounted() {
            parallel(engines.map(engi => {
                return async () => {
                    try {
                        engi.results = (await engi.search(title)).map(n => {
                            n.loading = false;
                            return n;
                        });
                    } finally {
                        engi.done = true;
                    }
                }
            }), 3).start();
        },
        methods: {
            async open(engi, r) {
                r.loading = true;
                let url = await engi.magurl(r);
                r.loading = false;
                if (url) {
                    let a = document.createElement('a');
                    a.href = 'https://www.diancigaoshou.com/#' + new URLSearchParams({ url });
                    a.target = "_blank";
                    a.click();
                } else {
                    r.title = "获取失败";
                }
            }
        }
    })


    function parallel(tasks, max = 2) {
        let i = 0;
        let ing = 0;
        function run() {
            if (ing >= max) return;
            if (i >= tasks.length) return;
            let task = tasks[i++];
            ing++;
            (async () => {
                await task();
                ing--;
                run();
            })();
            run();
        }
        return {
            start() {
                run();
            }
        }
    }

    function titleMatch(string, word) {
        if (!word) return false;
        if (!string.includes(word)) return false;

        let index = string.indexOf(word);

        let charLeft = string[index - 1];
        if (charLeft) {
            if (word[0].match(/[\u4E00-\u9FFF]/) && charLeft.match(/[\u4E00-\u9FFF]/)) return false;
            if (word[0].match(/[A-Za-z]/) && charLeft.match(/[A-Za-z]/)) return false;
        }

        let rightChar = string[index + word.length];
        if (rightChar) {
            let c = word[word.length - 1];
            if (c.match(/[\u4E00-\u9FFF]/) && rightChar.match(/[\u4E00-\u9FFF]/)) return false;
            if (c.match(/[A-Za-z]/) && rightChar.match(/[A-Za-z]/)) return false;
        }
        return true;
    }

    function addStyle(s) {
        let style = document.createElement("style");
        style.innerHTML = s;
        document.documentElement.appendChild(style);
    }


    function html(n, ...args) {
        let re = [];
        for (let i = 0; i < n.length; i++) {
            re.push(n[i]);
            args[i] && re.push(args[i]);
        }
        return re.join("");
    }

    function $(ele, selector) {
        return ele.querySelector(selector);
    }
    function $$(ele, selector) {
        return Array.from(ele.querySelectorAll(selector));
    }

    async function getMag(...args) {
        let dom = await getDOM(...args);
        let m = dom.documentElement.textContent.match(magreg);
        if (!m) m = dom.documentElement.innerHTML.match(magreg);
        return m && m[0];
    }


    function getDOM(url, headers = {}) {
        const request = (typeof GM_xmlhttpRequest === "function") ? GM_xmlhttpRequest : GM.xmlHttpRequest;
        return new Promise(r => {
            request({
                url,
                headers,
                onload({ responseText }) {
                    let parser = new DOMParser();
                    r(parser.parseFromString(responseText, "text/html"));
                }
            })
        })
    }

})();