Spotify歌单自动导入网易云音乐

优化匹配规则,必须歌名和歌手同时满足才匹配。自动记录并支持导出未匹配的歌曲列表。

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Spotify歌单自动导入网易云音乐
// @namespace    https://github.com/XFiendd/SpotifyToNetEaseCloudMusic/
// @version      15.0
// @description  优化匹配规则,必须歌名和歌手同时满足才匹配。自动记录并支持导出未匹配的歌曲列表。
// @author       XFiend
// @match        https://open.spotify.com/playlist/*
// @match        https://music.163.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @connect      music.163.com
// @connect      interface.music.163.com
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 全局变量
    let captureInterval = null;
    let capturedSongs = new Map();

    const isSpotify = window.location.hostname.includes('spotify.com');
    const isNetease = window.location.hostname.includes('163.com');

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    function init() {
        if (isSpotify) {
            setTimeout(initSpotify, 2000);
        } else if (isNetease) {
            setTimeout(initNetease, 2000);
        }
    }

    // ===================================================================
    // ========== Spotify 端  ==========
    // ===================================================================

    function initSpotify() {
        addSpotifyButton();
    }

    function addSpotifyButton() {
        if (document.getElementById('spotify-export-btn')) return;
        const button = document.createElement('button');
        button.textContent = '📥 导出到网易云';
        button.id = 'spotify-export-btn';
        button.style.cssText = `
            position: fixed !important; top: 100px !important; right: 30px !important; z-index: 9999;
            padding: 15px 30px !important; background: #1DB954 !important; color: white !important;
            border: none !important; border-radius: 500px !important; font-weight: bold !important;
            cursor: pointer !important; font-size: 16px !important; box-shadow: 0 4px 12px rgba(0,0,0,0.4) !important;
            font-family: Arial, sans-serif !important; transition: all 0.2s ease;
        `;
        button.addEventListener('mouseover', function() { this.style.background = '#1ed760 !important'; this.style.transform = 'scale(1.05)'; });
        button.addEventListener('mouseout', function() { this.style.background = '#1DB954 !important'; this.style.transform = 'scale(1)'; });
        button.addEventListener('click', startCaptureMode);
        document.body.appendChild(button);
    }

    function startCaptureMode() {
        capturedSongs.clear();
        if (captureInterval) clearInterval(captureInterval);

        document.getElementById('spotify-export-btn').style.display = 'none';

        const panel = document.createElement('div');
        panel.id = 'capture-panel';
        panel.style.cssText = `
            position: fixed; bottom: 30px; right: 30px; z-index: 10000;
            background: #282828; color: white; padding: 25px;
            border-radius: 12px; box-shadow: 0 5px 20px rgba(0,0,0,0.5);
            font-family: Arial, sans-serif; max-width: 350px; text-align: center;
        `;
        panel.innerHTML = `
            <h3 style="margin: 0 0 15px 0; font-size: 18px; color: #1DB954;">准备捕获歌曲...</h3>
            <p style="margin: 0 0 20px 0; font-size: 15px; line-height: 1.6;">
                请<b>从上到下完整滚动</b>一遍歌单,脚本将自动记录所有出现的歌曲。
            </p>
            <div id="song-counter" style="font-size: 24px; font-weight: bold; margin-bottom: 20px; color: #1ed760;">🎵 已捕获: 0</div>
            <button id="finish-capture-btn" style="width: 100%; padding: 12px; background: #1DB954; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: bold;">
                ✅ 完成滚动, 开始导出
            </button>
            <button id="cancel-capture-btn" style="width: 100%; padding: 8px; background: none; color: #aaa; border: none; cursor: pointer; font-size: 13px; margin-top: 10px;">
                取消
            </button>
        `;
        document.body.appendChild(panel);

        document.getElementById('finish-capture-btn').addEventListener('click', finishCapture);
        document.getElementById('cancel-capture-btn').addEventListener('click', cancelCapture);

        captureInterval = setInterval(scanAndCaptureSongs, 500);
    }

    function scanAndCaptureSongs() {
        const songRows = document.querySelectorAll('div[data-testid="tracklist-row"]');
        songRows.forEach((row) => {
            const linkElement = row.querySelector('a[href^="/track/"]');
            if (!linkElement) return;

            const songKey = linkElement.getAttribute('href');
            if (!capturedSongs.has(songKey)) {
                const titleElement = row.querySelector('div[aria-colindex="2"] a div');
                const artistElements = row.querySelectorAll('div[aria-colindex="2"] span a');
                const albumElement = row.querySelector('div[aria-colindex="3"] a');

                if (titleElement && artistElements.length > 0 && albumElement) {
                    const title = titleElement.textContent.trim();
                    const artist = Array.from(artistElements).map(a => a.textContent.trim()).join(', ');
                    const album = albumElement.textContent.trim();

                    capturedSongs.set(songKey, { title, artist, album });

                    const counter = document.getElementById('song-counter');
                    if(counter) counter.innerHTML = `🎵 已捕获: ${capturedSongs.size}`;
                }
            }
        });
    }

    function finishCapture() {
        if (captureInterval) clearInterval(captureInterval);
        captureInterval = null;

        document.getElementById('capture-panel')?.remove();

        const songs = Array.from(capturedSongs.values());
        const modal = createModal();

        if (songs.length === 0) {
            updateModalStatus(modal, `❌ 提取失败: 未捕获到任何歌曲。<br>请确保在滚动前歌单已正确加载。`, 'error');
        } else {
            GM_setValue('playlist_songs', JSON.stringify(songs));
            GM_setValue('playlist_timestamp', Date.now());
            showSpotifyResult(modal, songs);
        }
        document.getElementById('spotify-export-btn').style.display = 'block';
    }

    function cancelCapture() {
        if (captureInterval) clearInterval(captureInterval);
        captureInterval = null;
        capturedSongs.clear();
        document.getElementById('capture-panel')?.remove();
        document.getElementById('spotify-export-btn').style.display = 'block';
    }


    // ========== 网易云及通用函数  ==========

    function initNetease() {
        checkAndShowImportButton();
    }

    function checkAndShowImportButton() {
        const songs = GM_getValue('playlist_songs');
        const timestamp = GM_getValue('playlist_timestamp');
        if (songs && timestamp && (Date.now() - timestamp < 60 * 60 * 1000)) {
            addNeteaseButton(JSON.parse(songs));
        } else if (songs) {
            GM_deleteValue('playlist_songs'); GM_deleteValue('playlist_timestamp');
        }
    }

    function addNeteaseButton(songs) {
        if (document.getElementById('netease-import-btn')) return;
        const button = document.createElement('button');
        button.id = 'netease-import-btn';
        button.textContent = `📥 开始导入 (${songs.length}首)`;
        button.style.cssText = `
            position: fixed !important; top: 100px !important; right: 30px !important; z-index: 999999 !important; padding: 15px 30px !important;
            background: #D33A31 !important; color: white !important; border: none !important; border-radius: 8px !important; font-weight: bold !important;
            cursor: pointer !important; font-size: 16px !important; box-shadow: 0 4px 12px rgba(0,0,0,0.4) !important; font-family: Arial, sans-serif !important;
            transition: all 0.2s ease;
        `;
        button.addEventListener('mouseover', function() { this.style.background = '#E94639 !important'; this.style.transform = 'scale(1.05)'; });
        button.addEventListener('mouseout', function() { this.style.background = '#D33A31 !important'; this.style.transform = 'scale(1)'; });
        button.addEventListener('click', () => startImport(songs));
        document.body.appendChild(button);
    }

    function startImport(songs) {
        const modal = createModal();
        showPlaylistInputUI(modal, songs);
    }

    function showPlaylistInputUI(modal, songs) {

        const html = `
            <div style="font-family: Arial, sans-serif;">
                <h2 style="margin: 0 0 20px 0; color: #D33A31; font-size: 24px;">🎵 导入到网易云歌单</h2>
                <div style="margin-bottom: 20px; padding: 15px; background: #FFF3E0; border-radius: 8px; font-size: 14px; line-height: 1.8;">
                    <div style="font-weight: bold; margin-bottom: 10px; color: #F57C00;">📝 如何获取歌单ID:</div>
                    <div style="color: #666;">打开你想导入的歌单页面,查看浏览器地址栏,找到 <code style="background: #fff; padding: 2px 8px; border-radius: 3px; color: #D33A31;">id=</code> 后面的数字。</div>
                </div>
                <div>
                    <label style="display: block; margin-bottom: 10px; font-weight: bold; color: #333; font-size: 15px;">目标歌单ID:</label>
                    <input type="text" id="playlistIdInput" placeholder="请输入歌单ID(纯数字)" style="width: 100%; padding: 15px; border: 2px solid #ddd; border-radius: 8px; font-size: 16px; box-sizing: border-box; margin-bottom: 20px;"/>
                </div>
                <div style="padding: 15px; background: #E8F5E9; border-radius: 8px; margin-bottom: 20px;"><div style="color: #2E7D32; font-size: 15px;">📊 准备导入: <strong style="font-size: 20px;">${songs.length}</strong> 首歌曲</div></div>
                <div style="display: flex; gap: 10px;">
                    <button id="startBtn" style="flex: 1; padding: 15px; background: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 16px;">🚀 开始自动导入</button>
                    <button id="closeBtn" style="padding: 15px 25px; background: #f5f5f5; color: #666; border: none; border-radius: 8px; cursor: pointer;">关闭</button>
                </div>
            </div>`;
        modal.querySelector('.modal-content').innerHTML = html;
        const input = document.getElementById('playlistIdInput');
        const urlMatch = window.location.href.match(/id=(\d+)/);
        if (urlMatch) input.value = urlMatch[1];
        document.getElementById('startBtn').addEventListener('click', () => {
            const playlistId = input.value.trim();
            if (!playlistId || !/^\d+$/.test(playlistId)) { alert('⚠️ 请输入正确的歌单ID(纯数字)'); return; }
            startAutoImport(modal, songs, playlistId);
        });
        document.getElementById('closeBtn').addEventListener('click', () => { modal.remove(); document.querySelector('.import-overlay').remove(); });
    }

    function startAutoImport(modal, songs, playlistId) {
        let currentIndex = 0, addedCount = 0, skippedCount = 0, failedCount = 0, isPaused = false, isProcessing = false;
        let unmatchedSongs = []; // 用于存储未匹配的歌曲 ---


        const html = `
            <div style="font-family: Arial, sans-serif; min-width: 600px;">
                <h2 style="margin: 0 0 20px 0; color: #D33A31; font-size: 24px;">⚡ 正在自动导入</h2>
                <div style="margin-bottom: 20px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; color: white;">
                    <div style="margin-bottom: 15px; font-size: 18px;">进度:<span id="progress" style="font-weight: bold; font-size: 24px;">0 / ${songs.length}</span></div>
                    <div style="display: flex; gap: 25px; font-size: 15px;">
                        <div>✓ 已添加:<span id="added" style="font-weight: bold; font-size: 18px;">0</span></div>
                        <div>⊘ 已跳过:<span id="skipped" style="font-weight: bold; font-size: 18px;">0</span></div>
                        <div>✗ 失败:<span id="failed" style="font-weight: bold; font-size: 18px;">0</span></div>
                    </div>
                </div>
                <div id="log-container" style="height: 300px; background: #282c34; color: #abb2bf; font-family: 'Courier New', monospace; font-size: 14px; padding: 15px; border-radius: 8px; overflow-y: auto; white-space: pre-wrap; line-height: 1.6;"></div>
                <div style="display: flex; gap: 10px; margin-top: 20px;">
                    <button id="pauseBtn" style="flex: 1; padding: 15px; background: #FF9800; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;">⏸ 暂停</button>
                    <button id="closeBtn" style="padding: 15px 25px; background: #f44336; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;">✕ 中止</button>
                </div>
            </div>`;
        modal.querySelector('.modal-content').innerHTML = html;
        const logContainer = document.getElementById('log-container');
        const logToUI = (message, type = 'info') => {
            const colors = { info: '#abb2bf', match: '#98c379', success: '#61afef', warning: '#e5c07b', error: '#e06c75' };
            const logEntry = document.createElement('div');
            logEntry.innerHTML = `[${new Date().toLocaleTimeString()}] ${message}`;
            logEntry.style.cssText = `color: ${colors[type] || colors.info}; margin-top: 5px;`;
            logContainer.appendChild(logEntry);
            logContainer.scrollTop = logContainer.scrollHeight;
        };


        const findBestMatch = (spotifySong, neteaseResults) => {
            const cleanStr = (s) => s.toLowerCase().replace(/[\(\)\[\]-]/g, ' ').replace(/&/g, 'and').replace(/\s+/g, ' ').trim();
            const spotifyTitle = cleanStr(spotifySong.title);
            const spotifyArtists = spotifySong.artist.split(',').map(cleanStr);

            const perfectMatches = [];
            const partialMatches = [];

            for (const result of neteaseResults) {
                const resultTitle = cleanStr(result.name);
                const resultArtists = result.artists.split('/').map(cleanStr);

                const titleMatches = resultTitle.includes(spotifyTitle) || spotifyTitle.includes(resultTitle);
                const artistMatches = spotifyArtists.some(sa => resultArtists.some(ra => ra.includes(sa)));

                // 必须同时满足歌名和歌手的匹配条件
                if (titleMatches && artistMatches) {
                    perfectMatches.push(result);
                } else if (titleMatches) {
                    partialMatches.push(result);
                }
            }

            // 优先返回“完美匹配”的结果,否则返回“部分匹配”的第一个,最后返回null
            return perfectMatches[0] || partialMatches[0] || null;
        };

        document.getElementById('pauseBtn').addEventListener('click', function() {
            isPaused = !isPaused; this.textContent = isPaused ? '▶ 继续' : '⏸ 暂停'; this.style.background = isPaused ? '#4CAF50' : '#FF9800';
            if (!isPaused && !isProcessing) processNextSong();
        });
        document.getElementById('closeBtn').addEventListener('click', () => { if (confirm('确定要中止导入吗?')) { isPaused = true; modal.remove(); document.querySelector('.import-overlay').remove(); } });

        async function processNextSong() {
            if (isPaused || currentIndex >= songs.length) {
                if (!isPaused) showComplete(unmatchedSongs); // 传递未匹配列表
                return;
            }
            isProcessing = true;
            const song = songs[currentIndex];
            document.getElementById('progress').textContent = `${currentIndex + 1} / ${songs.length}`;
            logToUI(`处理中 (${currentIndex + 1}/${songs.length}): <strong>${song.title}</strong> - ${song.artist}`);
            try {
                const results = await searchSong(song.title, song.artist);
                const bestMatch = results.length > 0 ? findBestMatch(song, results) : null;
                if (bestMatch) {
                    logToUI(`🎯 匹配成功: <span style="color: #c678dd;">${bestMatch.name} - ${bestMatch.artists}</span>`, 'match');
                    const addResult = await addToPlaylist(playlistId, bestMatch.id);
                    if (addResult.code === 200) {
                        addedCount++;
                        document.getElementById('added').textContent = addedCount;
                        logToUI(`✅ ${addResult.message}!`, 'success');
                        await sleep(500);
                    } else {
                        skippedCount++;
                        document.getElementById('skipped').textContent = skippedCount;
                        logToUI(`🟡 ${addResult.message},跳过。`, 'warning');
                        await sleep(500);
                    }
                } else {
                    skippedCount++;
                    document.getElementById('skipped').textContent = skippedCount;
                    logToUI(`🤷‍ 未找到匹配歌曲,跳过。`, 'warning');
                    unmatchedSongs.push(song); // --- 记录未匹配的歌曲 ---
                }
            } catch (e) {
                failedCount++;
                document.getElementById('failed').textContent = failedCount;
                logToUI(`🔥 添加失败: ${e.message}`, 'error');
                await sleep(1000);
            }
            currentIndex++;
            isProcessing = false;
            await sleep(200);
            processNextSong();
        }

        /**
         * 完成界面以显示和导出未匹配歌曲 ---
         */
        function showComplete(unmatchedSongs) {
            let unmatchedHtml = '';
            if (unmatchedSongs.length > 0) {
                const songList = unmatchedSongs.map((song, index) => `${index + 1}. ${song.title} - ${song.artist}`).join('\n');
                unmatchedHtml = `
                    <div style="margin-top: 30px; text-align: left;">
                        <h3 style="margin-bottom: 10px; color: #F44336;">以下 ${unmatchedSongs.length} 首歌曲未匹配成功:</h3>
                        <textarea readonly style="width: 100%; height: 150px; font-size: 12px; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;">${songList}</textarea>
                        <button id="export-unmatched-btn" style="width: 100%; margin-top: 10px; padding: 10px; background: #FF9800; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;">📄 导出为TXT</button>
                    </div>
                `;
            }

            modal.querySelector('.modal-content').innerHTML = `
                <div style="text-align: center; padding: 20px; font-family: Arial, sans-serif;">
                    <h2 style="color: #4CAF50;">🎉 导入完成!</h2>
                    <div style="font-size: 18px; margin: 30px 0; line-height: 2.5;">
                        <div><span style="color: #4CAF50; font-weight: bold; font-size: 28px;">${addedCount}</span> 首成功添加</div>
                        <div><span style="color: #FF9800; font-weight: bold; font-size: 28px;">${skippedCount}</span> 首跳过 (已存在或未匹配)</div>
                        ${failedCount > 0 ? `<div><span style="color: #F44336; font-weight: bold; font-size: 28px;">${failedCount}</span> 首失败</div>` : ''}
                    </div>
                    ${unmatchedHtml}
                    <button id="finishBtn" style="margin-top: 20px; padding: 15px 50px; background: #1DB954; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;">✅ 完成</button>
                </div>`;

            document.getElementById('finishBtn').addEventListener('click', () => { modal.remove(); document.querySelector('.import-overlay').remove(); });

            if (unmatchedSongs.length > 0) {
               // document.getElementById('export-unmatched-btn').addEventListener('click', () => exportUnmatchedToTxt(unmatchedSongs));
            }

            GM_deleteValue('playlist_songs'); GM_deleteValue('playlist_timestamp');
        }
        processNextSong();
    }

    /**
     * 将未匹配歌曲列表导出为TXT文件的函数 ---
     */
    function exportUnmatchedToTxt(unmatchedSongs) {
        const textContent = "以下是在Spotify歌单中,但未能成功匹配到网易云音乐的歌曲列表:\n\n" +
                            unmatchedSongs.map((song, index) => `${index + 1}. ${song.title} - ${song.artist}`).join('\n');

        const blob = new Blob([textContent], { type: 'text/plain;charset=utf-8' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = 'unmatched_songs.txt';
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(a.href);
    }



    async function searchSong(title, artist) {
        return new Promise((resolve, reject) => {
            const keyword = `${title} ${artist.split(',')[0]}`;
            GM_xmlhttpRequest({
                method: 'GET', url: `https://music.163.com/api/search/get?s=${encodeURIComponent(keyword)}&type=1&limit=5`,
                headers: { 'Referer': 'https://music.163.com/' },
                onload: r => { try { resolve(JSON.parse(r.responseText).result?.songs?.map(s => ({id: s.id, name: s.name, artists: s.artists.map(a => a.name).join(' / ')})) || []); } catch (e) { resolve([]); } },
                onerror: reject
            });
        });
    }

    async function addToPlaylist(playlistId, songId) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST', url: 'https://music.163.com/api/playlist/manipulate/tracks',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': 'https://music.163.com/' },
                data: `op=add&pid=${playlistId}&trackIds=%5B${songId}%5D&imme=true`,
                onload: r => { try { const d = JSON.parse(r.responseText); if (d.code === 200) resolve({code: 200, message: '添加成功'}); else if (d.code === 502) resolve({code: 502, message: '歌曲已存在'}); else reject(new Error(d.message || `错误码: ${d.code}`)); } catch (e) { reject(e); } },
                onerror: reject
            });
        });
    }

    function createModal() {
        document.querySelector('.import-modal')?.remove(); document.querySelector('.import-overlay')?.remove();
        const overlay = document.createElement('div'); overlay.className = 'import-overlay'; overlay.style.cssText = `position: fixed !important; inset: 0; background: rgba(0,0,0,0.6) !important; z-index: 999998 !important;`;
        const modal = document.createElement('div'); modal.className = 'import-modal'; modal.style.cssText = `position: fixed !important; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 999999 !important; background: white !important; padding: 30px !important; border-radius: 16px !important; box-shadow: 0 10px 40px rgba(0,0,0,0.3) !important; max-width: 700px !important; max-height: 90vh !important; overflow-y: auto !important;`;
        const content = document.createElement('div'); content.className = 'modal-content'; modal.appendChild(content);
        document.body.appendChild(overlay); document.body.appendChild(modal);
        return modal;
    }

    function updateModalStatus(modal, message, type) {
        const colors = { info: '#3B82F6', success: '#10B981', error: '#EF4444' };
        const icons = { info: '⏳', success: '✅', error: '❌' };
        modal.querySelector('.modal-content').innerHTML = `
            <div style="color: ${colors[type]}; font-weight: bold; text-align: center; padding: 30px; font-size: 18px; line-height: 1.6;">
                <div style="font-size: 48px; margin-bottom: 20px;">${icons[type]}</div>${message}
            </div>`;
    }

    function showSpotifyResult(modal, songs) {
        let html = `
            <div style="font-family: Arial, sans-serif;">
                <h2 style="margin: 0 0 20px 0; color: #1DB954; font-size: 24px;">✅ 提取成功!</h2>
                <div style="margin-bottom: 20px; padding: 15px; background: #E8F5E9; border-radius: 8px;">
                    <div style="font-size: 18px; color: #2E7D32;">共提取 <strong style="font-size: 28px;">${songs.length}</strong> 首歌曲</div>
                </div>
                <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; max-height: 300px; overflow-y: auto; margin-bottom: 20px;">
                    <p style="margin: 0 0 10px 0; font-weight: bold; color: #333;">歌曲列表预览:</p>`;
        songs.slice(0, 20).forEach((song, index) => {
            html += `<div style="padding: 8px 0; border-bottom: 1px solid #ddd;"><div style="color: #333; font-size: 14px; font-weight: 500;">${index + 1}. ${song.title}</div><div style="color: #666; font-size: 12px; margin-top: 2px;">${song.artist}</div></div>`;
        });
        if (songs.length > 20) html += `<div style="padding: 12px 0; color: #999; font-size: 13px; text-align: center;">...还有 ${songs.length - 20} 首</div>`;
        html += `</div>
                <div style="margin-bottom: 15px; padding: 15px; background: #FFF3E0; border-radius: 8px; font-size: 14px; line-height: 1.6;">
                    <div style="font-weight: bold; margin-bottom: 8px; color: #F57C00;">📌 下一步操作:</div>
                    <div style="color: #666;">1. 点击下方按钮前往网易云音乐</div>
                    <div style="color: #666;">2. 在网易云页面会出现"开始导入"按钮</div>
                </div>
                <button id="gotoNetease" style="width: 100%; padding: 15px; background: #D33A31; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 16px; margin-bottom: 10px;">🎵 前往网易云音乐开始导入</button>
                <button id="closeBtn" style="width: 100%; padding: 12px; background: #f5f5f5; color: #666; border: none; border-radius: 8px; cursor: pointer;">关闭</button>
            </div>`;
        modal.querySelector('.modal-content').innerHTML = html;
        document.getElementById('gotoNetease').addEventListener('click', () => window.open('https://music.163.com', '_blank'));
        document.getElementById('closeBtn').addEventListener('click', () => { modal.remove(); document.querySelector('.import-overlay').remove(); });
    }
})();