Greasy Fork

Greasy Fork is available in English.

豆瓣资源下载大师:1秒搞定豆瓣电影|图书|音乐下载(精简重构版)

合并自「豆瓣电影网盘资源自动搜索」与「在豆瓣读书显示 Z-Library 书籍」,剔除原版海量失效死链与 jQuery 依赖。新增豆瓣音乐全网下载试听与无感复制,并修复了 IMDb 评分展示等体验。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         豆瓣资源下载大师:1秒搞定豆瓣电影|图书|音乐下载(精简重构版)
// @namespace    http://tampermonkey.net/
// @version      4.1.1
// @description  合并自「豆瓣电影网盘资源自动搜索」与「在豆瓣读书显示 Z-Library 书籍」,剔除原版海量失效死链与 jQuery 依赖。新增豆瓣音乐全网下载试听与无感复制,并修复了 IMDb 评分展示等体验。
// @author       Tim (Fixed by AI)
// @match        https://movie.douban.com/subject/*
// @match        https://book.douban.com/subject/*
// @match        https://music.douban.com/subject/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @connect      *
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ==========================================
    //  通用辅助函数
    // ==========================================
    function getDoc(url, callback, errorCallback) {
        GM_xmlhttpRequest({
            method: 'GET', url: url, withCredentials: true,
            onload: function (responseDetail) {
                let resText = responseDetail.responseText;
                if (responseDetail.status === 503 || responseDetail.status === 403 || resText.includes("Checking your browser") || resText.includes("Just a moment")) {
                    if (errorCallback) errorCallback("captcha"); return;
                }
                if (responseDetail.status === 200 || responseDetail.status === 404) {
                    callback((new DOMParser()).parseFromString(resText, 'text/html'), responseDetail);
                } else {
                    if (errorCallback) errorCallback("error");
                }
            },
            onerror: function () { if (errorCallback) errorCallback("error"); }
        });
    }

    // ==========================================
    //  第一模块:豆瓣电影(网盘搜索 + IMDb评分)
    // ==========================================
    function initMovieScript() {
        // 样式注入
        GM_addStyle(`
            .douban-pan-search { margin-bottom: 30px; padding: 15px; background: #f8fcf8; border-radius: 4px; border: 1px solid #e2ece2; }
            .douban-pan-search h2 { color: #007722; margin: 0 0 10px 0; font-size: 15px; border-bottom: 1px dashed #dddddd; padding-bottom: 5px; }
            .pan-search-loading { color: #666; font-size: 13px; }
            .pan-item { margin: 8px 0; font-size: 13px; display: flex; align-items: center; }
            .pan-item a { color: #3377aa; text-decoration: none; max-width: 170px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 5px; }
            .pan-item a:hover { color: #ffffff; background-color: #3377aa; }
            .pan-tag { display: inline-block; padding: 2px 5px; border-radius: 2px; font-size: 11px; margin-right: 5px; color: #fff; flex-shrink: 0; }
            .tag-ali { background-color: #ff6a00; }
            .tag-baidu { background-color: #06a7ff; }
            .tag-quark { background-color: #00b4ff; }
            .tag-other { background-color: #999; }
            .pan-pwd { margin-left: 5px; color: #e74c3c; font-family: monospace; cursor: pointer; flex-shrink: 0; }
            
            /* 评分增强区域样式 */
            .imdb-badge { color: #e5a826; font-weight: bold; margin-left: 5px; text-decoration: none; font-size: 13px; }
            .imdb-badge:hover { color: #d19720; }
            .imdb-votes { color: #999; font-size: 11px; margin-left: 4px; }
        `);

        // 提取豆瓣电影信息
        let titleNode = document.querySelector('h1 span[property="v:itemreviewed"]');
        if (!titleNode) return;
        let rawTitle = titleNode.innerText.trim();
        let mainTitle = rawTitle.split(' ')[0]; // 取冒号或空格前的主标题

        // 构建侧边栏面板
        let aside = document.querySelector('.aside');
        if (!aside) return;

        let panel = document.createElement('div');
        panel.className = 'douban-pan-search';
        panel.innerHTML = `
            <h2><span style="color:#f29a38">☁️</span> 网盘资源检索</h2>
            <div id="pan-results" class="pan-search-loading">
                <span style="animation:pulse 1.5s infinite;">🔄 正在拦截全网《${mainTitle}》的分享链接...</span>
            </div>
        `;
        aside.insertBefore(panel, aside.firstChild);

        // 请求搜索数据
        let container = document.getElementById('pan-results');
        let keyword = encodeURIComponent(mainTitle);
        let apiUrl = `https://so.252035.xyz/api/search?kw=${keyword}`;

        GM_xmlhttpRequest({
            method: "GET",
            url: apiUrl,
            onload: function (response) {
                if (response.status === 200) {
                    try {
                        let res = JSON.parse(response.responseText);
                        if (res.code === 0 && res.data && res.data.merged_by_type) {
                            let results = [];
                            let merged = res.data.merged_by_type;
                            let typesToExtract = [
                                { key: 'ali', label: 'ali' },
                                { key: 'quark', label: 'quark' },
                                { key: 'baidu', label: 'baidu' },
                                { key: 'xunlei', label: 'other' }
                            ];
                            typesToExtract.forEach(t => {
                                let list = merged[t.key] || [];
                                list.slice(0, 3).forEach(item => {
                                    results.push({ name: item.note || '未命名资源', url: item.url, type: t.label, pwd: item.password || '' });
                                });
                            });
                            renderPanResults(results, container);
                        } else {
                            renderPanResults([], container);
                        }
                    } catch (e) {
                        container.innerHTML = '<span style="color:#d9534f">⚠️ 数据解析失败,接口可能变更。</span>';
                    }
                } else {
                    container.innerHTML = `<span style="color:#d9534f">⚠️ 请求聚合服务器失败 (${response.status})。</span>`;
                }
            },
            onerror: function () {
                container.innerHTML = '<span style="color:#d9534f">⚠️ 网络请求异常,请检查跨域配置。</span>';
            }
        });

        // 附加:解析 IMDb 评分展示(原版豆瓣下载大师核心亮点)
        let infoDiv = document.getElementById('info');
        if (infoDiv) {
            let imdbMatch = infoDiv.innerText.match(/IMDb:?\s*(tt\d+)/);
            if (imdbMatch && imdbMatch[1]) {
                fetchImdbRating(imdbMatch[1]);
            }
        }
    }

    function renderPanResults(results, container) {
        if (!results || results.length === 0) {
            container.innerHTML = '<span style="color:#d9534f">⚠️ 抱歉,未找到直接匹配的高清网盘资源。</span>';
            return;
        }
        let html = '';
        results.forEach(res => {
            let tagClass = 'tag-other', tagText = '其他';
            if (res.type === 'ali') { tagClass = 'tag-ali'; tagText = '阿里'; }
            else if (res.type === 'baidu') { tagClass = 'tag-baidu'; tagText = '百度'; }
            else if (res.type === 'quark') { tagClass = 'tag-quark'; tagText = '夸克'; }

            let pwdHtml = res.pwd ? `<span class="pan-pwd" onclick="navigator.clipboard.writeText('${res.pwd}');alert('密码已复制');" title="点击复制">🔑 ${res.pwd}</span>` : '';
            html += `
                <div class="pan-item">
                    <span class="pan-tag ${tagClass}">${tagText}</span>
                    <a href="${res.url}" target="_blank" title="${res.name}">${res.name}</a>
                    ${pwdHtml}
                </div>
            `;
        });
        container.innerHTML = html;
    }

    function fetchImdbRating(imdbId) {
        let infoDiv = document.getElementById('info');
        if (!infoDiv) return;

        let imdbUrl = 'https://www.imdb.com/title/' + imdbId + '/';
        getDoc(imdbUrl, function (doc) {
            try {
                let rating = '', votes = '';

                // 解析方法 1: 新一代 IMDb Next.js JSON 数据
                let nextData = doc.querySelector('script#__NEXT_DATA__');
                if (nextData) {
                    try {
                        let data = JSON.parse(nextData.innerHTML);
                        let aboveTheFold = data.props.pageProps.aboveTheFoldData;
                        rating = aboveTheFold.ratingsSummary.aggregateRating;
                        votes = aboveTheFold.ratingsSummary.voteCount;
                    } catch (e) { }
                }

                // 解析方法 2: 传统的 ld+json
                if (!rating) {
                    let ldJson = doc.querySelector('script[type="application/ld+json"]');
                    if (ldJson) {
                        try {
                            let data = JSON.parse(ldJson.innerText);
                            if (data.aggregateRating) {
                                rating = data.aggregateRating.ratingValue;
                                votes = data.aggregateRating.ratingCount;
                            }
                        } catch (e) { }
                    }
                }

                // 解析方法 3: 直接查找 DOM
                if (!rating) {
                    let ratingEl = doc.querySelector('[data-testid="hero-rating-bar__aggregate-rating__score"] span');
                    if (ratingEl) { rating = ratingEl.innerText; }
                    let voteEl = doc.querySelector('.kn-1x1-stars span.bYitIg, div[data-testid="hero-rating-bar__aggregate-rating__score"] ~ div:last-child');
                    if (voteEl) { votes = voteEl.innerText; }
                }

                if (rating) {
                    let badge = document.createElement('span');
                    badge.innerHTML = `&nbsp; <a href="${imdbUrl}" target="_blank" class="imdb-badge">⭐ ${rating}</a> <span class="imdb-votes">(${votes}人评价)</span>`;

                    let imdbSpan = Array.from(infoDiv.querySelectorAll('span.pl')).find(el => el.innerText.includes('IMDb'));
                    if (imdbSpan && imdbSpan.nextSibling) {
                        imdbSpan.parentNode.insertBefore(badge, imdbSpan.nextSibling.nextSibling);
                    } else {
                        infoDiv.appendChild(badge);
                    }
                }
            } catch (e) {
                console.warn("解析 IMDb 失败", e);
            }
        });
    }

    // ==========================================
    //  第二模块:豆瓣读书 (Z-Library 搜索)
    // ==========================================
    const ZLIB_DOMAIN = "https://zlib.im";

    function initBookScript() {
        GM_addStyle(`
            /* Z-lib 样式 */
            .zlib-status-btn { display: inline-block; margin-top: 5px; color: #42bd56 !important; background: #f0f9f2; padding: 4px 8px; border-radius: 4px; font-size: 12px; text-decoration: none !important; }
            .zlib-status-btn:hover { background: #e2f4e5; }
            .zlib-dl-btn { margin-left: 8px; padding: 2px 10px; background: #e8ecef; color: #37a !important; border-radius: 2px; font-size: 12px; text-decoration: none !important; transition: all 0.2s; }
            .zlib-dl-btn:hover { background: #37a !important; color: #fff !important; }
        `);

        // 清理原有广告
        let ad = document.getElementById('dale_book_subject_top_right');
        if (ad && ad.parentNode) ad.parentNode.removeChild(ad);

        let wrapper = document.getElementById('wrapper');
        if (!wrapper) return;
        let subTitleSpan = wrapper.querySelector('h1 span');
        if (!subTitleSpan) return;

        // 提取检索关键字(优先 ISBN)
        let isbn = '';
        let infoDiv = document.getElementById('info');
        if (infoDiv) {
            let isbnMatch = infoDiv.innerText.match(/ISBN[::\s]*(\d[\d-]{9,}[\dXx])/);
            if (isbnMatch) isbn = isbnMatch[1].replace(/-/g, '');
        }

        let rawTitle = subTitleSpan.innerHTML.trim();
        let cleanTitle = rawTitle.replace(/[\((【\[].*?[\]】)\)]/g, '').trim() || rawTitle;
        let searchTerm = isbn || cleanTitle;
        let searchLabel = isbn ? `ISBN: ${isbn}` : cleanTitle;

        // 挂载侧边栏容器
        let aside = document.getElementsByClassName('aside')[0];
        if (aside) {
            let doc = new DOMParser().parseFromString(`<div id="zlib" style="margin-bottom:20px; padding:15px; background:#f9f9f9; border-radius:4px;"><h2>Z-Library 电子书 · · · · · · </h2><div id="zlib-status">正在获取资源 (${searchLabel})...</div></div>`, "text/html");
            aside.prepend(doc.body.firstChild);
        }

        let url = `${ZLIB_DOMAIN}/s/${encodeURIComponent(searchTerm)}`;
        loadZlibData(url, url); // url twice for reference logic
    }

    function loadZlibData(url, currentSearchUrl) {
        getDoc(url, function (doc) {
            let zlibContainer = document.getElementById('zlib');
            if (!zlibContainer) return;

            zlibContainer.innerHTML = '';
            let ele = doc.getElementById('searchResultBox');

            if (!ele) {
                zlibContainer.innerHTML = `<h2>Z-Library · · · · · · </h2><div class="pl">该书在 Z-Library 中未找到匹配资源。<br><a href="${currentSearchUrl}" target="_blank" class="zlib-status-btn">↗ 前往 Z-lib 站内手动模糊搜索</a></div>`;
                return;
            }

            let items = ele.querySelectorAll('.resItemBox, .resItemBoxBooks, .book-item');
            let totalCounterEl = doc.querySelector('.totalCounter, z-bookterms');
            let totalCounter = '0';
            if (totalCounterEl) {
                let match = (totalCounterEl.innerText || totalCounterEl.innerHTML || '').match(/\d+/);
                if (match) totalCounter = match[0];
            }
            if (totalCounter === '0' && items.length > 0) totalCounter = items.length.toString();

            zlibContainer.innerHTML = `<h2>Z-Library (<span id="filterItemsNum">0</span>) · · · · · · <span class="pl">(<a href="${currentSearchUrl}" target="_blank">全部 ${totalCounter}</a>)</span></h2><div class="clear"></div>`;

            let filterItemsNum = 0;
            // 解析 Z-Library 数据(兼容旧版 DOM 和 新版 Z-BookCard WebComponent)
            for (let i = 0; i < items.length && filterItemsNum < 5; i++) {
                try {
                    let currentItem = items[i];
                    if (currentItem.classList.contains('booklist')) continue;

                    let bookName = '', link = '', publisher = '', author = '', year = '', file = '', img = '', dlLink = '';
                    let zCard = currentItem.querySelector('z-bookcard');

                    if (zCard) { // 新版 DOM
                        link = zCard.getAttribute('href') || '';
                        if (!link) continue;
                        let titleSlot = zCard.querySelector('[slot="title"]');
                        bookName = titleSlot ? titleSlot.innerText.trim() : '';
                        let authorSlot = zCard.querySelector('[slot="author"]');
                        author = authorSlot ? authorSlot.innerText.trim() : '';
                        publisher = zCard.getAttribute('publisher') || '';
                        year = zCard.getAttribute('year') || '';
                        let ext = zCard.getAttribute('extension') || '';
                        let size = zCard.getAttribute('filesize') || '';
                        file = (ext && size) ? `${ext.toUpperCase()}, ${size}` : (ext || size);
                        let dlAttr = zCard.getAttribute('download');
                        if (dlAttr) dlLink = ZLIB_DOMAIN + dlAttr;
                        let imgEl = zCard.querySelector('img');
                        img = imgEl ? imgEl.outerHTML.replace('data-src', 'src') : '';
                    } else { // 旧版 DOM
                        let titleLink = currentItem.querySelector('h3 a[itemprop="url"]') || currentItem.querySelector('a[style*="text-decoration: underline"]');
                        if (!titleLink) {
                            let trs = currentItem.getElementsByTagName('td');
                            if (trs.length > 2) titleLink = trs[2].querySelector('a');
                        }
                        if (!titleLink) continue;
                        bookName = titleLink.innerText.trim();
                        link = titleLink.getAttribute("href");
                        let authorArr = [];
                        currentItem.querySelectorAll('td a').forEach(aTag => {
                            let text = aTag.innerText.trim();
                            if (aTag.getAttribute('title') === 'Publisher') publisher = text;
                            else if (aTag.getAttribute('itemprop') === 'author') authorArr.push(text);
                        });
                        author = authorArr.join(', ');
                        let imgEl = currentItem.querySelector('img.cover') || currentItem.querySelector('img.itemCover');
                        img = imgEl ? imgEl.outerHTML.replace('data-src', 'src') : '';
                        let yearEl = currentItem.querySelector('.property_year .property_value');
                        if (yearEl) year = yearEl.innerText.trim();
                        let fileEl = currentItem.querySelector('.property__file .property_value');
                        if (fileEl) file = fileEl.innerText.trim();
                        let dlNode = currentItem.querySelector('a[href^="/dl/"]');
                        if (dlNode) dlLink = ZLIB_DOMAIN + dlNode.getAttribute('href');
                    }

                    let infos = [publisher, author, file].filter(Boolean);
                    let downloadBtnHtml = dlLink ? `<a href="${dlLink}" target="_blank" class="zlib-dl-btn">⬇ 下载</a>` : '';

                    let itemHtml = `
                    <div class="c-aside name-offline">
                        <div class="ll">${img}</div>
                        <div style="padding-left:60px; word-break:break-all;">
                            <a href="${ZLIB_DOMAIN}${link}" target="_blank">${bookName}</a> ${year ? '(' + year + ')' : ''}<br/>
                            <span class="pl">${infos.join('<br/>')}</span><br>
                            <div style="margin-top:6px;">${downloadBtnHtml}</div>
                        </div>
                    </div><div class="clear"></div><div class="ul" style="margin-bottom:12px;"></div>`;

                    zlibContainer.insertAdjacentHTML('beforeend', itemHtml);
                    filterItemsNum++;
                } catch (innerErr) { console.warn("解析跳过异常项", innerErr); }
            }

            let filterNumEl = document.getElementById('filterItemsNum');
            if (filterNumEl) filterNumEl.innerHTML = filterItemsNum;

            if (filterItemsNum === 0) {
                zlibContainer.innerHTML = `<h2>Z-Library (0) · · · · · · <span class="pl">(<a href="${currentSearchUrl}" target="_blank">去原站确认</a>)</span></h2><div class="pl">未提取到有效单本书籍(结果可能都是文章片段或书单聚合),请手动查询。</div>`;
            } else {
                let images = zlibContainer.getElementsByTagName('img');
                for (let i = 0; i < images.length; i++) {
                    images[i].setAttribute('width', '50px');
                    images[i].setAttribute('referrerpolicy', 'no-referrer');
                }
                if (items.length > 5) {
                    zlibContainer.insertAdjacentHTML('beforeend', `<div style="text-align:right; margin-top:5px;"><a href="${currentSearchUrl}" target="_blank" style="color:#aaa;">查看全部 ${totalCounter} 条结果 &gt;</a></div>`);
                }
            }
        }, function (err) {
            let zlibStatus = document.getElementById('zlib-status');
            if (!zlibStatus) return;

            if (err === "captcha") {
                zlibStatus.innerHTML = `<span style="color:#d14;">提示:Z-Library 开启了防机器人保护。</span><br>
                <span class="pl" style="color:#666; font-size:12px; line-height:1.4">为正常显示电子书,请按照以下步骤证明您是真人:</span><br>
                <a href="${ZLIB_DOMAIN}" target="_blank" class="zlib-status-btn" id="zlib-btn-gocheck">↗ 1. 点击去 Z-lib 验证一下</a>
                <a href="javascript:void(0)" class="zlib-status-btn" id="zlib-btn-retry" style="background:#e6f3ff; color:#0066cc!important; margin-left:8px;">↻ 2. 验证后点我重新加载</a>`;

                document.getElementById('zlib-btn-retry').onclick = () => {
                    zlibStatus.innerHTML = "正在重新获取资源...";
                    loadZlibData(url, currentSearchUrl);
                };

                document.getElementById('zlib-btn-gocheck').onclick = () => {
                    let focusHandler = () => {
                        let st = document.getElementById('zlib-status');
                        if (st && st.innerHTML.includes('拦截')) {
                            st.innerHTML = "检测到您已返回,正在自动重试...";
                            loadZlibData(url, currentSearchUrl);
                        }
                        window.removeEventListener('focus', focusHandler);
                    };
                    setTimeout(() => { window.addEventListener('focus', focusHandler); }, 2000);
                };
            } else {
                zlibStatus.innerHTML = `<span style="color:#d14;">网络失败。该镜像域名可能被阻断。</span><br><a href="${currentSearchUrl}" target="_blank" class="zlib-status-btn">↗ 尝试手动打开新窗口搜索</a>`;
            }
        });
    }

    // ==========================================
    //  第三模块:豆瓣音乐 (聚合搜索跳转)
    // ==========================================
    function initMusicScript() {
        GM_addStyle(`
            .douban-music-search { margin-bottom: 20px; padding: 15px; background: #f4f8fb; border-radius: 4px; border: 1px solid #e1eaf2; }
            .douban-music-search h2 { color: #1f6b9c; margin: 0 0 12px 0; font-size: 15px; border-bottom: 1px dashed #d0dcea; padding-bottom: 6px; }
            .music-category { margin-bottom: 8px; font-weight: bold; color: #555; font-size: 13px; }
            .music-btn-group { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
            .music-btn { padding: 4px 10px; font-size: 12px; border-radius: 3px; text-decoration: none !important; transition: all 0.2s; color: #fff !important; }
            .music-btn:hover { opacity: 0.85; }
            .btn-mp3 { background-color: #ff6a00; }
            .btn-listen { background-color: #00b4ff; }
            .btn-app { background-color: #607d8b; }
            .btn-tg { background-color: #2ca5e0; }
        `);

        // 获取页面上的专辑标题和歌手名称
        let wrapper = document.getElementById('wrapper');
        if (!wrapper) return;
        let titleSpan = wrapper.querySelector('h1 span');
        if (!titleSpan) return;

        let rawTitle = titleSpan.innerText.trim();
        // 提取前缀作为主标题(过滤掉 'EP', 'Remastered' 等括号内容)
        let cleanTitle = rawTitle.replace(/[\((【\[].*?[\]】)\)]/g, '').trim();

        // 尝试从 info 区域提取歌手名字
        let artist = '';
        let infoDiv = document.getElementById('info');
        if (infoDiv) {
            let artistSpan = infoDiv.querySelector('span.pl a');
            if (artistSpan) {
                artist = artistSpan.innerText.trim();
            } else {
                let artistMatch = infoDiv.innerText.match(/(表演者|歌手)[\s::]*([^\n]*)/);
                if (artistMatch) artist = artistMatch[2].trim().split('/')[0].trim();
            }
        }

        // 组成搜索关键词,如果是非常长的标题,通常只搜歌手+前几个字最精准
        let keyword = cleanTitle;
        if (artist && !cleanTitle.includes(artist)) {
            keyword = artist + ' ' + cleanTitle;
        }
        let encodeKw = encodeURIComponent(keyword);
        let safeKeyword = keyword; // addEventListener中直接使用变量,无需转义

        // 构建侧边栏面板
        let aside = document.querySelector('.aside');
        if (!aside) return;

        let panel = document.createElement('div');
        panel.className = 'douban-music-search';
        panel.innerHTML = `
            <h2>🎵 音乐下载 · 在线试听</h2>
            <div id="douban-music-player-container" style="margin-bottom: 12px; display: none;">
                <!-- 极简播放器将渲染在这里 -->
            </div>
            
            <div class="music-category">⚡ MP3 极速下载站</div>
            <div class="music-btn-group">
                <a class="music-btn btn-mp3" href="https://www.gequbao.com/s/${encodeKw}" target="_blank" title="歌曲宝">歌曲宝 (直连)</a>
                <a class="music-btn btn-mp3 btn-copy-kw" href="https://www.22a5.com/" target="_blank" title="爱玩音乐22a5">爱玩音乐 (需手搜)</a>
            </div>

            <div class="music-category">🎧 无损源 / 试听引擎</div>
            <div class="music-btn-group">
                <a class="music-btn btn-listen" href="https://tonzhon.whamon.com/search/${encodeKw}" target="_blank" title="铜钟纯净版">铜钟 Tonzhon (直连)</a>
            </div>

            <div class="music-category">⚙️ 全网聚合网页版 (需手搜)</div>
            <div class="music-btn-group">
                <a class="music-btn btn-app btn-copy-kw" href="https://music.wjhe.top/" target="_blank" title="HEMusic 多音质引擎">HEMusic 引擎</a>
                <a class="music-btn btn-app btn-copy-kw" href="https://music.gdstudio.xyz/" target="_blank">GD音乐台</a>
                <a class="music-btn btn-app btn-copy-kw" href="https://yilancn.top/music/" target="_blank">Ylan Music</a>
                <a class="music-btn btn-app btn-copy-kw" href="https://musicbox.yangweijie.cn/" target="_blank">Minimal</a>
            </div>

            <div class="music-category">🤖 Telegram 机器人 (<a href="#" target="_blank" style="color: #37a;" title="https://my.lanwu.art">需VPN/手搜</a>)</div>
            <div class="music-btn-group">
                <a class="music-btn btn-tg btn-copy-kw" href="tg://resolve?domain=Music163bot" target="_blank">@Music163bot</a>
                <a class="music-btn btn-tg btn-copy-kw" href="tg://resolve?domain=music_v1bot" target="_blank">@music_v1bot</a>
            </div>
            <div style="font-size:11px; color:#888; margin-top:5px;">* 💡 <strong>提示:</strong>点击带 <em>(需手搜)</em> 的按钮,将为您自动复制歌曲名!</div>
        `;
        aside.insertBefore(panel, aside.firstChild);

        // 为需要手动搜索的按钮绑定自动复制事件(必须在JS上下文中绑定,不能用内联onclick避免跨沙箱权限不足)
        let copyBtns = panel.querySelectorAll('.btn-copy-kw');
        copyBtns.forEach(btn => {
            btn.addEventListener('click', function () {
                GM_setClipboard(safeKeyword, 'text');
            });
        });

        // 增加内嵌极简播放器功能: 尝试通过网易云接口搜索并获取第一首歌曲的ID
        GM_xmlhttpRequest({
            method: 'POST',
            url: 'https://music.163.com/api/search/get/web',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Referer': 'https://music.163.com/'
            },
            data: 's=' + encodeKw + '&type=1&offset=0&total=true&limit=1',
            onload: function (response) {
                try {
                    let res = JSON.parse(response.responseText);
                    if (res.code === 200 && res.result && res.result.songs && res.result.songs.length > 0) {
                        let songId = res.result.songs[0].id;
                        let playerContainer = document.getElementById('douban-music-player-container');
                        if (playerContainer) {
                            playerContainer.style.display = 'block';
                            // 改用原生 audio 标签结合外链直达接口,并增加 onerror 容错防盗链及无版权提示
                            playerContainer.innerHTML =
                                '<audio controls src="https://music.163.com/song/media/outer/url?id=' + songId + '.mp3" ' +
                                'style="width:100%; height:40px; border-radius:4px; outline:none; background:#f1f3f4;" ' +
                                'title="网易云极速试听" ' +
                                'onerror="this.style.display=\'none\'; this.nextElementSibling.style.display=\'block\';"></audio>' +
                                '<div style="display:none; color:#daaf76; font-size:12px; padding:8px 0; text-align:center; background:#fffbf4; border-radius:4px; border:1px solid #f9e2c4; margin-bottom:5px;">⚠️ 该首歌曲受版权保护,暂无法提供直接试听。请使用下方按钮跳转收听。</div>';
                        }
                    }
                } catch (e) {
                    console.log('Douban Master: Failed to load netease music iframe', e);
                }
            }
        });
    }

    // ==========================================
    //  主入口
    // ==========================================
    if (location.host === 'movie.douban.com') {
        setTimeout(initMovieScript, 500);
    } else if (location.host === 'book.douban.com') {
        setTimeout(initBookScript, 500);
    } else if (location.host === 'music.douban.com') {
        setTimeout(initMusicScript, 500);
    }
})();