// ==UserScript== // @name 网易云音乐和酷狗音乐自动下载脚本(增强版-支持会员歌曲下载) // @namespace http://tampermonkey.net/ // @version 1.3.0 // @description 自动监测网易云音乐和酷狗音乐网页端播放的歌曲,提供一键下载MP3/FLAC功能,支持会员歌曲下载 // @author 醉春风 // @license All Rights Reserved. 版权所有,禁止修改和再分发。未经作者明确许可,禁止对本脚本进行任何形式的修改、再分发或商业使用。 // @match *://music.163.com/* // @match *://*.music.163.com/* // @match *://www.kugou.com/* // @match *://*.kugou.com/* // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_notification // @connect music.163.com // @connect kugou.com // @connect * // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @require https://cdn.jsdelivr.net/npm/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js // @run-at document-start // @downloadURL none // ==/UserScript== (function() { 'use strict'; // ================ 配置项 ================ const CONFIG = { defaultQuality: GM_getValue('defaultQuality', 'standard'), // 默认下载音质: standard, higher, exhigh, lossless autoDetect: GM_getValue('autoDetect', true), // 自动检测歌曲 showNotifications: GM_getValue('showNotifications', true), // 显示通知 theme: GM_getValue('theme', 'auto'), // 主题: auto, netease, kugou downloadPath: GM_getValue('downloadPath', ''), // 下载路径 detectDelay: 5000, // 检测延迟(毫秒) preferredFormat: GM_getValue('preferredFormat', 'mp3'), // 首选格式: mp3, flac preferDirectStream: GM_getValue('preferDirectStream', true) // 优先使用直接音频流下载 }; // ================ 平台检测 ================ const PLATFORM = { NETEASE: 'netease', KUGOU: 'kugou', UNKNOWN: 'unknown' }; // 当前平台 let currentPlatform = detectPlatform(); // 当前歌曲信息 let currentSong = { id: '', name: '', artist: '', album: '', cover: '', duration: 0, audioSrc: '', // 直接音频流地址 mediaElement: null, // 媒体元素引用 platform: PLATFORM.UNKNOWN }; // ================ 音频格式转换器 ================ // 音频格式转换器状态 let audioConverterState = { ffmpegLoaded: false, ffmpeg: null, isConverting: false }; // 初始化音频转换器 async function initAudioConverter() { if (audioConverterState.ffmpegLoaded) return true; try { if (typeof FFmpeg === 'undefined') { console.error("FFmpeg WASM 未加载"); return false; } const { createFFmpeg } = FFmpeg; audioConverterState.ffmpeg = createFFmpeg({ log: false, corePath: 'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js' }); await audioConverterState.ffmpeg.load(); audioConverterState.ffmpegLoaded = true; console.log("音频转换器初始化成功"); return true; } catch (error) { console.error("音频转换器初始化失败:", error); return false; } } // MP3 转 FLAC 格式 async function convertToFlac(audioUrl, fileName) { if (!await initAudioConverter()) { console.error("音频转换器未初始化,无法转换"); return null; } if (audioConverterState.isConverting) { console.log("已有转换任务正在进行,请稍后再试"); return null; } audioConverterState.isConverting = true; showProgress(0); try { console.log("开始下载音频文件用于转换..."); const response = await fetch(audioUrl); const arrayBuffer = await response.arrayBuffer(); const inputData = new Uint8Array(arrayBuffer); const inputFileName = 'input.mp3'; const outputFileName = 'output.flac'; console.log("开始转换音频格式..."); const { ffmpeg } = audioConverterState; ffmpeg.FS('writeFile', inputFileName, inputData); await ffmpeg.run('-i', inputFileName, '-c:a', 'flac', outputFileName); const outputData = ffmpeg.FS('readFile', outputFileName); // 清理文件系统 ffmpeg.FS('unlink', inputFileName); ffmpeg.FS('unlink', outputFileName); console.log("音频格式转换完成"); audioConverterState.isConverting = false; return new Blob([outputData.buffer], { type: 'audio/flac' }); } catch (error) { console.error("音频格式转换失败:", error); audioConverterState.isConverting = false; return null; } } // 检测当前平台 function detectPlatform() { const url = window.location.href; if (url.includes('music.163.com')) { return PLATFORM.NETEASE; } else if (url.includes('kugou.com')) { return PLATFORM.KUGOU; } return PLATFORM.UNKNOWN; } // ================ 样式注入 ================ function injectStyles() { const primaryColor = currentPlatform === PLATFORM.NETEASE ? '#C20C0C' : '#2CA2F9'; GM_addStyle(` /* 全局样式 */ .music-dl-container * { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } /* 悬浮按钮 */ .music-dl-btn { position: fixed; right: 20px; bottom: 80px; width: 48px; height: 48px; border-radius: 50%; background-color: ${primaryColor}; color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); z-index: 9999; /* High z-index */ transition: all 0.3s ease; } .music-dl-btn:hover { transform: scale(1.1); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); } .music-dl-btn.active { animation: pulse 1.5s infinite; } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } } /* 主面板 */ .music-dl-panel { position: fixed; right: 20px; bottom: 140px; width: 320px; max-height: 420px; background-color: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); z-index: 9998; /* High z-index, below button */ overflow: hidden; display: none; flex-direction: column; animation: slideIn 0.3s ease; } @keyframes slideIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .music-dl-panel-header { padding: 15px; background-color: ${primaryColor}; color: white; display: flex; justify-content: space-between; align-items: center; } .music-dl-panel-title { font-size: 16px; font-weight: bold; } .music-dl-panel-close { cursor: pointer; font-size: 18px; } /* 作者信息区域 */ .music-dl-author-info { background-color: #f8f8f8; padding: 8px 15px; display: flex; flex-direction: column; border-bottom: 1px solid #eee; } .music-dl-author { font-size: 14px; font-weight: bold; color: #333; margin-bottom: 4px; } .music-dl-qq-group { font-size: 13px; color: #666; } .music-dl-panel-content { padding: 15px; overflow-y: auto; flex: 1; } .music-dl-song-info { display: flex; margin-bottom: 15px; } .music-dl-song-cover { width: 80px; height: 80px; border-radius: 4px; object-fit: cover; margin-right: 15px; background-color: #eee; /* Placeholder background */ } .music-dl-song-details { flex: 1; min-width: 0; /* Prevent overflow issues */ } .music-dl-song-name { font-size: 16px; font-weight: bold; margin-bottom: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .music-dl-song-artist { font-size: 14px; color: #666; margin-bottom: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .music-dl-song-album { font-size: 12px; color: #999; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .music-dl-quick-download { display: flex; justify-content: space-between; margin-top: 15px; margin-bottom: 15px; } .music-dl-quick-download-btn { flex: 1; padding: 10px; text-align: center; border-radius: 4px; cursor: pointer; font-weight: bold; transition: all 0.2s; margin: 0 5px; border: none; /* Ensure no default border */ } .music-dl-quick-download-btn:first-child { margin-left: 0; } .music-dl-quick-download-btn:last-child { margin-right: 0; } .music-dl-quick-download-btn.mp3 { background-color: #31C27C; color: white; } .music-dl-quick-download-btn.mp3:hover { background-color: #28a66a; } .music-dl-quick-download-btn.flac { background-color: #2CA2F9; color: white; } .music-dl-quick-download-btn.flac:hover { background-color: #1a8fe6; } .music-dl-quality-options { margin-top: 15px; } .music-dl-quality-title { font-size: 14px; font-weight: bold; margin-bottom: 10px; } .music-dl-quality-btn { display: block; width: 100%; padding: 10px; margin-bottom: 8px; background-color: #f5f5f5; border: none; border-radius: 4px; text-align: left; cursor: pointer; transition: background-color 0.2s; } .music-dl-quality-btn:hover { background-color: #eaeaea; } .music-dl-quality-btn span { float: right; color: #999; } .music-dl-panel-footer { padding: 10px 15px; border-top: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; /* Align items vertically */ } .music-dl-settings-btn { color: #666; cursor: pointer; font-size: 14px; } .music-dl-version { font-size: 12px; color: #aaa; } /* 设置面板 */ .music-dl-settings { padding: 15px; display: none; } .music-dl-settings-group { margin-bottom: 15px; } .music-dl-settings-title { font-size: 14px; font-weight: bold; margin-bottom: 10px; } .music-dl-settings-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .music-dl-settings-label { font-size: 14px; color: #333; } .music-dl-settings-select { padding: 5px; border-radius: 4px; border: 1px solid #ddd; } .music-dl-switch { position: relative; display: inline-block; width: 40px; height: 20px; } .music-dl-switch input { opacity: 0; width: 0; height: 0; } .music-dl-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 20px; } .music-dl-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 2px; bottom: 2px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .music-dl-slider { background-color: ${primaryColor}; } input:checked + .music-dl-slider:before { transform: translateX(20px); } .music-dl-settings-buttons { display: flex; justify-content: flex-end; margin-top: 15px; } .music-dl-settings-save { padding: 8px 15px; background-color: ${primaryColor}; color: white; border: none; border-radius: 4px; cursor: pointer; } .music-dl-settings-cancel { padding: 8px 15px; background-color: #f5f5f5; color: #333; border: none; border-radius: 4px; margin-right: 10px; cursor: pointer; } /* 下载进度 */ .music-dl-progress-container { margin-top: 15px; display: none; } .music-dl-progress-bar { height: 6px; background-color: #eee; border-radius: 3px; overflow: hidden; margin-bottom: 5px; } .music-dl-progress-fill { height: 100%; background-color: ${primaryColor}; width: 0%; transition: width 0.3s; } .music-dl-progress-text { font-size: 12px; color: #666; text-align: right; } /* 音频流模式指示器 */ .music-dl-stream-mode { font-size: 12px; color: #31C27C; margin-top: 5px; text-align: right; font-weight: bold; } `); } // ================ UI组件 ================ function createUI() { // 检查是否已存在UI元素,避免重复创建 if (document.querySelector('.music-dl-btn')) { console.log('音乐下载助手UI已存在,跳过创建'); return; } // 创建悬浮按钮 const floatButton = document.createElement('div'); floatButton.className = 'music-dl-btn'; floatButton.innerHTML = ''; document.body.appendChild(floatButton); console.log('创建悬浮按钮成功'); // 创建主面板 const panel = document.createElement('div'); panel.className = 'music-dl-panel'; panel.innerHTML = `
音乐下载助手
×
by醉春风
加QQ群:1028141108
封面
未检测到歌曲
-
-
选择音质下载
0%
常规设置
默认下载音质
首选格式
自动检测歌曲
显示通知
优先使用音频流下载
主题
界面主题
`; // Append panel to body to minimize interference document.body.appendChild(panel); console.log('创建主面板成功'); // Get references after adding to DOM const settingsPanel = panel.querySelector('.music-dl-settings'); // Initialize settings values const defaultQualitySelect = settingsPanel.querySelector('#music-dl-default-quality'); defaultQualitySelect.value = CONFIG.defaultQuality; const preferredFormatSelect = settingsPanel.querySelector('#music-dl-preferred-format'); preferredFormatSelect.value = CONFIG.preferredFormat; const autoDetectCheckbox = settingsPanel.querySelector('#music-dl-auto-detect'); autoDetectCheckbox.checked = CONFIG.autoDetect; const showNotificationsCheckbox = settingsPanel.querySelector('#music-dl-show-notifications'); showNotificationsCheckbox.checked = CONFIG.showNotifications; const preferDirectStreamCheckbox = settingsPanel.querySelector('#music-dl-prefer-direct-stream'); preferDirectStreamCheckbox.checked = CONFIG.preferDirectStream; const themeSelect = settingsPanel.querySelector('#music-dl-theme'); themeSelect.value = CONFIG.theme; // Bind events floatButton.addEventListener('click', togglePanel); panel.querySelector('.music-dl-panel-close').addEventListener('click', hidePanel); panel.querySelector('.music-dl-settings-btn').addEventListener('click', toggleSettings); settingsPanel.querySelector('.music-dl-settings-save').addEventListener('click', saveSettings); settingsPanel.querySelector('.music-dl-settings-cancel').addEventListener('click', hideSettings); // Bind download button events const quickDownloadButtons = panel.querySelectorAll('.music-dl-quick-download-btn'); quickDownloadButtons.forEach(button => { button.addEventListener('click', function() { if (this.classList.contains('mp3')) { quickDownload('mp3'); } else if (this.classList.contains('flac')) { quickDownload('flac'); } }); }); const qualityButtons = panel.querySelectorAll('.music-dl-quality-btn'); qualityButtons.forEach(button => { button.addEventListener('click', function() { const quality = this.getAttribute('data-quality'); downloadSong(quality); }); }); // Close panel on outside click (ensure this doesn't interfere with player controls) document.addEventListener('click', function(e) { // Check if the click is outside the panel AND outside the float button if (!panel.contains(e.target) && !floatButton.contains(e.target)) { // Also check if the click target is part of the Kugou player controls to avoid closing the panel accidentally // This requires identifying Kugou's player control selectors, which might be fragile. // Example (needs verification): const isPlayerControl = e.target.closest('.kgPlayer_controls, #playerControl'); // if (!isPlayerControl) { hidePanel(); // } } else { // If click is inside panel, prevent propagation if necessary // e.stopPropagation(); // Use with caution } }, true); // Use capture phase to potentially catch clicks earlier } // 显示/隐藏主面板 function togglePanel() { const panel = document.querySelector('.music-dl-panel'); if (!panel) return; if (panel.style.display === 'flex') { hidePanel(); } else { showPanel(); } } // 显示主面板 function showPanel() { const panel = document.querySelector('.music-dl-panel'); if (!panel) return; panel.style.display = 'flex'; hideSettings(); // Ensure settings are hidden when panel opens updateSongInfo(); // Refresh song info when panel opens } // 隐藏主面板 function hidePanel() { const panel = document.querySelector('.music-dl-panel'); if (!panel) return; panel.style.display = 'none'; } // 显示/隐藏设置面板 function toggleSettings() { const panel = document.querySelector('.music-dl-panel'); if (!panel) return; const settings = panel.querySelector('.music-dl-settings'); const content = panel.querySelector('.music-dl-panel-content'); const songInfo = content.querySelector('.music-dl-song-info'); const quickDownload = content.querySelector('.music-dl-quick-download'); const qualityOptions = content.querySelector('.music-dl-quality-options'); const progressContainer = content.querySelector('.music-dl-progress-container'); if (!settings) return; if (settings.style.display === 'block') { hideSettings(); } else { // Hide other content sections if(songInfo) songInfo.style.display = 'none'; if(quickDownload) quickDownload.style.display = 'none'; if(qualityOptions) qualityOptions.style.display = 'none'; if(progressContainer) progressContainer.style.display = 'none'; // Show settings settings.style.display = 'block'; } } // 隐藏设置面板 function hideSettings() { const panel = document.querySelector('.music-dl-panel'); if (!panel) return; const settings = panel.querySelector('.music-dl-settings'); const content = panel.querySelector('.music-dl-panel-content'); const songInfo = content.querySelector('.music-dl-song-info'); const quickDownload = content.querySelector('.music-dl-quick-download'); const qualityOptions = content.querySelector('.music-dl-quality-options'); if (!settings) return; // Hide settings settings.style.display = 'none'; // Show other content sections if(songInfo) songInfo.style.display = 'flex'; if(quickDownload) quickDownload.style.display = 'flex'; if(qualityOptions) qualityOptions.style.display = 'block'; // Progress container visibility is handled by showProgress/hideProgress } // 保存设置 function saveSettings() { const panel = document.querySelector('.music-dl-panel'); if (!panel) return; const settingsPanel = panel.querySelector('.music-dl-settings'); if (!settingsPanel) return; const defaultQuality = settingsPanel.querySelector('#music-dl-default-quality').value; const preferredFormat = settingsPanel.querySelector('#music-dl-preferred-format').value; const autoDetect = settingsPanel.querySelector('#music-dl-auto-detect').checked; const showNotifications = settingsPanel.querySelector('#music-dl-show-notifications').checked; const preferDirectStream = settingsPanel.querySelector('#music-dl-prefer-direct-stream').checked; const theme = settingsPanel.querySelector('#music-dl-theme').value; // Save to GM storage GM_setValue('defaultQuality', defaultQuality); GM_setValue('preferredFormat', preferredFormat); GM_setValue('autoDetect', autoDetect); GM_setValue('showNotifications', showNotifications); GM_setValue('preferDirectStream', preferDirectStream); GM_setValue('theme', theme); // Update config object CONFIG.defaultQuality = defaultQuality; CONFIG.preferredFormat = preferredFormat; CONFIG.autoDetect = autoDetect; CONFIG.showNotifications = showNotifications; CONFIG.preferDirectStream = preferDirectStream; CONFIG.theme = theme; showNotification('设置已保存', '您的偏好设置已成功保存', 'success'); hideSettings(); } // 更新歌曲信息显示 function updateSongInfo() { const panel = document.querySelector('.music-dl-panel'); if (!panel) return; // Exit if panel doesn't exist const songNameEl = panel.querySelector('.music-dl-song-name'); const songArtistEl = panel.querySelector('.music-dl-song-artist'); const songAlbumEl = panel.querySelector('.music-dl-song-album'); const songCoverEl = panel.querySelector('.music-dl-song-cover'); const streamModeEl = panel.querySelector('.music-dl-stream-mode'); const qualityButtons = panel.querySelectorAll('.music-dl-quality-btn'); const quickDownloadButtons = panel.querySelectorAll('.music-dl-quick-download-btn'); if (!songNameEl || !songArtistEl || !songAlbumEl || !songCoverEl) return; if (currentSong.name && currentSong.artist) { // Check for valid name and artist songNameEl.textContent = currentSong.name; songNameEl.title = currentSong.name; // Add title for long names songArtistEl.textContent = currentSong.artist; songArtistEl.title = currentSong.artist; songAlbumEl.textContent = currentSong.album || '-'; songAlbumEl.title = currentSong.album || ''; songCoverEl.src = currentSong.cover || ''; songCoverEl.alt = currentSong.name ? `封面: ${currentSong.name}` : '封面'; // 显示音频流模式指示 if (streamModeEl) { if (currentSong.audioSrc || currentSong.mediaElement) { streamModeEl.textContent = '✓ 支持直接下载'; streamModeEl.style.display = 'block'; } else { streamModeEl.style.display = 'none'; } } // 启用下载按钮 qualityButtons.forEach(btn => { btn.disabled = false; btn.style.opacity = '1'; btn.style.cursor = 'pointer'; }); quickDownloadButtons.forEach(btn => { btn.disabled = false; btn.style.opacity = '1'; btn.style.cursor = 'pointer'; }); } else { // 无歌曲信息时显示默认状态 songNameEl.textContent = '未检测到歌曲'; songArtistEl.textContent = '-'; songAlbumEl.textContent = '-'; songCoverEl.src = ''; songCoverEl.alt = '封面'; if (streamModeEl) { streamModeEl.style.display = 'none'; } // 禁用下载按钮 qualityButtons.forEach(btn => { btn.disabled = true; btn.style.opacity = '0.5'; btn.style.cursor = 'not-allowed'; }); quickDownloadButtons.forEach(btn => { btn.disabled = true; btn.style.opacity = '0.5'; btn.style.cursor = 'not-allowed'; }); } } // 显示下载进度 function showProgress(percent) { const panel = document.querySelector('.music-dl-panel'); if (!panel) return; const progressContainer = panel.querySelector('.music-dl-progress-container'); const progressFill = panel.querySelector('.music-dl-progress-fill'); const progressText = panel.querySelector('.music-dl-progress-text'); if (!progressContainer || !progressFill || !progressText) return; progressContainer.style.display = 'block'; progressFill.style.width = `${percent}%`; progressText.textContent = `${percent}%`; if (percent >= 100) { setTimeout(() => { hideProgress(); }, 2000); } } // 隐藏下载进度 function hideProgress() { const panel = document.querySelector('.music-dl-panel'); if (!panel) return; const progressContainer = panel.querySelector('.music-dl-progress-container'); if (progressContainer) { progressContainer.style.display = 'none'; } } // 显示通知 function showNotification(title, message, type = 'info') { if (!CONFIG.showNotifications) return; // 使用SweetAlert2显示通知 Swal.fire({ title: title, text: message, icon: type, toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true }); // 同时使用GM_notification作为备份 GM_notification({ title: title, text: message, timeout: 3000 }); } // ================ 网易云音乐相关 ================ // 监测网易云音乐播放 function monitorNeteasePlaying() { if (!CONFIG.autoDetect) return; console.log("开始监测网易云音乐播放..."); // 检查iframe function checkIframe() { const iframe = document.querySelector('#g_iframe'); if (iframe) { console.log("网易云: 找到iframe"); try { // 监听iframe加载完成 iframe.addEventListener('load', function() { console.log("网易云: iframe加载完成"); setTimeout(() => { try { extractNeteaseInfo(iframe.contentDocument); } catch (e) { console.error("网易云: iframe内容提取失败", e); } }, 1000); }); // 立即尝试提取 if (iframe.contentDocument) { extractNeteaseInfo(iframe.contentDocument); } } catch (e) { console.error("网易云: 访问iframe失败", e); } } } // 监听audio元素 function monitorAudioElements() { const audioElements = document.querySelectorAll('audio'); if (audioElements.length > 0) { console.log(`网易云: 找到 ${audioElements.length} 个 audio 元素`); // 为每个audio元素添加事件监听 audioElements.forEach(audio => { // 存储已找到的audio元素 if (!currentSong.mediaElement && audio.src && audio.src.startsWith('http')) { currentSong.mediaElement = audio; currentSong.audioSrc = audio.src; console.log("网易云: 捕获到音频流地址:", audio.src); updateSongInfo(); } // 监听src变化 const audioObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'attributes' && mutation.attributeName === 'src') { const audioElement = mutation.target; if (audioElement.src && audioElement.src.startsWith('http')) { console.log("网易云: audio src变化:", audioElement.src); currentSong.mediaElement = audioElement; currentSong.audioSrc = audioElement.src; updateSongInfo(); } } }); }); audioObserver.observe(audio, { attributes: true }); // 监听播放事件 audio.addEventListener('play', () => { console.log("网易云: audio播放事件触发"); if (audio.src && audio.src.startsWith('http')) { currentSong.mediaElement = audio; currentSong.audioSrc = audio.src; extractNeteaseInfo(); } }); }); } else { // 如果没有找到,稍后重试 setTimeout(monitorAudioElements, 3000); } // 尝试在iframe中查找audio元素 try { const iframe = document.querySelector('#g_iframe'); if (iframe && iframe.contentDocument) { const iframeAudios = iframe.contentDocument.querySelectorAll('audio'); if (iframeAudios.length > 0) { console.log(`网易云: 在iframe中找到 ${iframeAudios.length} 个 audio 元素`); iframeAudios.forEach(audio => { if (!currentSong.mediaElement && audio.src && audio.src.startsWith('http')) { currentSong.mediaElement = audio; currentSong.audioSrc = audio.src; console.log("网易云: 从iframe捕获到音频流地址:", audio.src); updateSongInfo(); } audio.addEventListener('play', () => { console.log("网易云: iframe audio播放事件触发"); if (audio.src && audio.src.startsWith('http')) { currentSong.mediaElement = audio; currentSong.audioSrc = audio.src; extractNeteaseInfo(iframe.contentDocument); } }); }); } } } catch (e) { console.error("网易云: 访问iframe中的audio元素出错", e); } }; // 开始监听audio元素 setTimeout(monitorAudioElements, 2000); // 开始检查iframe setTimeout(checkIframe, 1000); // 初始检查 setTimeout(extractNeteaseInfo, 2000); // 定期检查audio元素 setInterval(monitorAudioElements, 10000); } // 提取网易云音乐信息 function extractNeteaseInfo(iframeDoc = null) { console.log("尝试提取网易云音乐信息..."); let songName = '', artistName = '', albumName = '', songId = '', coverUrl = ''; // 1. 从URL提取歌曲ID const urlMatch = window.location.href.match(/[?&/#]id=(\d+)/); if (urlMatch) { songId = urlMatch[1]; console.log("网易云: 从URL提取到歌曲ID:", songId); } // 2. 从iframe提取信息 if (iframeDoc) { try { console.log("网易云: 从iframe提取信息"); // 尝试多种选择器以提高兼容性 const titleElement = iframeDoc.querySelector('.tit, .f-ff2, .hd .tit'); const artistElements = iframeDoc.querySelectorAll('.des a, .s-fc7, p.des.s-fc4 > span > a'); const albumElement = iframeDoc.querySelector('.des + p a, p.des.s-fc4 + p > a'); const coverImg = iframeDoc.querySelector('.cover img, .u-cover img, .j-img'); if (titleElement) { songName = titleElement.textContent.trim(); } if (artistElements.length > 0) { artistName = Array.from(artistElements) .map(a => a.textContent.trim()) .filter(text => text && !text.includes('的音乐')) .join(', '); } if (albumElement) { albumName = albumElement.textContent.trim(); } if (coverImg && coverImg.src) { coverUrl = coverImg.src.replace(/\?param=\d+y\d+$/, '?param=200y200'); } console.log(`网易云iframe提取: 歌名=${songName}, 艺术家=${artistName}, 专辑=${albumName}`); // 尝试在iframe中查找audio元素 if (!currentSong.mediaElement) { const iframeAudios = iframeDoc.querySelectorAll('audio'); if (iframeAudios.length > 0) { console.log(`网易云: 在iframe中找到 ${iframeAudios.length} 个 audio 元素`); iframeAudios.forEach(audio => { if (audio.src && audio.src.startsWith('http')) { currentSong.mediaElement = audio; currentSong.audioSrc = audio.src; console.log("网易云: 从iframe捕获到音频流地址:", audio.src); } }); } } } catch (e) { console.error("网易云: 从iframe提取信息失败", e); } } // 3. 从主页面底部播放栏提取 if (!songName || !artistName) { try { console.log("网易云: 从主页面底部播放栏提取"); const bottomBar = document.querySelector('.g-btmbar'); if (bottomBar) { const nameElement = bottomBar.querySelector('.name'); const artistElements = bottomBar.querySelectorAll('.by a'); const coverElement = bottomBar.querySelector('.head img'); if (nameElement) { songName = nameElement.textContent.trim(); // 尝试从href提取ID if (!songId && nameElement.href) { const idMatch = nameElement.href.match(/id=(\d+)/); if (idMatch) songId = idMatch[1]; } } if (artistElements.length > 0) { artistName = Array.from(artistElements) .map(a => a.textContent.trim()) .join(', '); } if (coverElement && coverElement.src) { coverUrl = coverElement.src.replace(/\?param=\d+y\d+$/, '?param=200y200'); } console.log(`网易云底部栏提取: 歌名=${songName}, 艺术家=${artistName}`); } } catch (e) { console.error("网易云: 从主页面提取信息失败", e); } } // 4. 如果有足够信息,更新当前歌曲 if ((songId && songName && artistName) || (songName && artistName)) { const effectiveId = songId || `${songName}-${artistName}`; if (currentSong.id !== effectiveId || currentSong.name !== songName) { console.log('检测到新网易云歌曲:', songName, artistName, effectiveId); currentSong = { id: effectiveId, name: songName, artist: artistName, album: albumName || '未知专辑', cover: coverUrl, audioSrc: currentSong.audioSrc || '', mediaElement: currentSong.mediaElement || null, platform: PLATFORM.NETEASE }; const floatBtn = document.querySelector('.music-dl-btn'); if (floatBtn) floatBtn.classList.add('active'); if (CONFIG.showNotifications) { showNotification('检测到歌曲', `${songName} - ${artistName}`, 'info'); } updateSongInfo(); return true; } else if (currentSong.cover !== coverUrl && coverUrl) { // 更新封面 currentSong.cover = coverUrl; updateSongInfo(); } return true; } else { console.log('未能检测到完整的网易云歌曲信息'); return false; } } // 获取网易云音乐下载链接 (v1.3.0 - 增强音频流回退) function getNeteaseDownloadUrl(songId, quality) { return new Promise((resolve, reject) => { // Helper to attempt fallback to captured audio stream const fallbackToAudioSrc = (reason) => { const audioSrc = currentSong.audioSrc || (currentSong.mediaElement && currentSong.mediaElement.src); if (audioSrc && audioSrc.startsWith('http')) { console.log(`网易云: ${reason},使用捕获的音频流:`, audioSrc); const isFlac = audioSrc.includes('.flac'); resolve({ url: audioSrc, size: 0, type: isFlac ? 'flac' : 'mp3', directStream: true }); return true; // Indicate fallback was successful } else { console.log(`网易云: ${reason},但无备用音频流`); return false; // Indicate fallback failed } }; // 1. Check direct stream preference first if (CONFIG.preferDirectStream) { const audioSrc = currentSong.audioSrc || (currentSong.mediaElement && currentSong.mediaElement.src); if (audioSrc && audioSrc.startsWith('http')) { console.log("网易云: 优先使用直接捕获的音频流"); const isFlac = audioSrc.includes('.flac'); resolve({ url: audioSrc, size: 0, type: isFlac ? 'flac' : 'mp3', directStream: true }); return; } } // 2. Check for valid song ID if (!songId || !/^\d+$/.test(songId)) { if (!fallbackToAudioSrc('无效ID')) { return reject('无效的网易云歌曲ID (且无备用音频流)'); } return; // Fallback handled } // 3. Attempt API call const apiUrl = `https://music.163.com/api/song/enhance/player/url/v1?ids=[${songId}]&level=${quality}&encodeType=aac`; GM_xmlhttpRequest({ method: 'GET', url: apiUrl, headers: { 'Referer': 'https://music.163.com/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36' }, onload: function(response) { let apiProvidesUrl = false; try { const data = JSON.parse(response.responseText); if (data && data.code === 200 && data.data && data.data.length > 0) { const songData = data.data[0]; if (songData.url) { // API Success with URL const type = songData.type === 'flac' ? 'flac' : 'mp3'; console.log("网易云: API获取链接成功"); resolve({ url: songData.url, size: songData.size, type: type }); apiProvidesUrl = true; } } // If no songData.url or non-200 code, apiProvidesUrl remains false } catch (e) { console.error("网易云: API响应解析失败:", e); // apiProvidesUrl remains false } // If API didn't provide a usable URL, attempt fallback if (!apiProvidesUrl) { if (!fallbackToAudioSrc('API无URL/失败')) { // Fallback also failed, reject reject('无法获取下载链接 (可能需要VIP或版权限制,且无备用音频流)'); } } }, onerror: function(error) { console.error("网易云: API请求失败:", error); // Attempt fallback on error if (!fallbackToAudioSrc('API请求失败')) { reject('无法获取下载链接 (API请求失败,且无备用音频流)'); } } }); }); } // ================ 酷狗音乐相关 ================ // 监测酷狗音乐播放 function monitorKugouPlaying() { if (!CONFIG.autoDetect) return; console.log("开始监测酷狗音乐播放..."); // 监听页面标题变化 const titleObserver = new MutationObserver(() => { console.log("酷狗页面标题变化,尝试提取信息"); setTimeout(extractKugouInfo, 500); }); const titleElement = document.querySelector('title'); if (titleElement) { titleObserver.observe(titleElement, { childList: true }); console.log("酷狗: 监听页面标题"); } // 监听播放器区域 const playerObserver = new MutationObserver(() => { console.log("酷狗播放器区域变化,尝试提取信息"); setTimeout(extractKugouInfo, 500); }); const playerElements = document.querySelectorAll('#player, #audioPlayer, .player_wrap, #kgPlayer'); playerElements.forEach(el => { if (el) { playerObserver.observe(el, { childList: true, subtree: true }); console.log("酷狗: 监听播放器元素", el); } }); // 监听audio元素 function monitorAudioElements() { const audioElements = document.querySelectorAll('audio'); if (audioElements.length > 0) { console.log(`酷狗: 找到 ${audioElements.length} 个 audio 元素`); audioElements.forEach(audio => { if (!currentSong.mediaElement && audio.src && audio.src.startsWith('http')) { currentSong.mediaElement = audio; currentSong.audioSrc = audio.src; console.log("酷狗: 捕获到音频流地址:", audio.src); updateSongInfo(); } // 监听src变化 const audioObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'attributes' && mutation.attributeName === 'src') { const audioElement = mutation.target; if (audioElement.src && audioElement.src.startsWith('http')) { console.log("酷狗: audio src变化:", audioElement.src); currentSong.mediaElement = audioElement; currentSong.audioSrc = audioElement.src; extractKugouInfo(); } } }); }); audioObserver.observe(audio, { attributes: true }); // 监听播放事件 audio.addEventListener('play', () => { console.log("酷狗: audio播放事件触发"); if (audio.src && audio.src.startsWith('http')) { currentSong.mediaElement = audio; currentSong.audioSrc = audio.src; extractKugouInfo(); } }); }); } else { // 如果没有找到,稍后重试 setTimeout(monitorAudioElements, 3000); } } // 开始监听audio元素 setTimeout(monitorAudioElements, 2000); // 初始检查 setTimeout(extractKugouInfo, 2000); // 定期检查 setInterval(extractKugouInfo, 10000); } // 提取酷狗音乐信息 function extractKugouInfo() { console.log("尝试提取酷狗音乐信息..."); let songName = '', artistName = '', albumName = '', songId = '', coverUrl = '', audioSrc = ''; // 1. 尝试获取音频流地址 if (currentSong.mediaElement) { const audioElement = currentSong.mediaElement; if (audioElement.src && audioElement.src.startsWith('http')) { audioSrc = audioElement.src; currentSong.audioSrc = audioSrc; currentSong.mediaElement = audioElement; } } else if (!audioSrc) { const audio = document.querySelector('audio'); if (audio && audio.src && audio.src.startsWith('http')) { audioSrc = audio.src; currentSong.mediaElement = audio; } } // 2. 获取歌曲Hash const hashMatch = window.location.hash.match(/hash=([A-Z0-9]+)/i); if (hashMatch) { songId = hashMatch[1]; } else { // 尝试从DOM获取hash const playerElement = document.querySelector('[data-hash]'); if (playerElement && playerElement.dataset.hash) { songId = playerElement.dataset.hash; } else if (window.KG_PLAYER && KG_PLAYER.currentSong && KG_PLAYER.currentSong.hash) { songId = KG_PLAYER.currentSong.hash; } } // 3. 从DOM获取歌曲信息 // 尝试从标题中提取 const pageTitle = document.title; if (pageTitle && !pageTitle.includes('酷狗音乐') && pageTitle.includes('-')) { const parts = pageTitle.split(/[-–—]/); if (parts.length >= 2) { songName = parts[0].trim(); artistName = parts.slice(1).join('-').trim(); } } // 尝试从播放器元素获取 if (!songName) { const nameElement = document.querySelector('.songName, .song_name, .songinfo .title, #songname'); if (nameElement) songName = nameElement.textContent.trim(); } if (!artistName) { const artistElement = document.querySelector('.singerName, .singer_name, .songinfo .artist a'); if (artistElement) artistName = artistElement.textContent.trim(); } if (!albumName) { const albumElement = document.querySelector('.albumName, .album_name, .songinfo .album a'); if (albumElement) albumName = albumElement.textContent.trim(); } // 4. 获取封面图片 const coverImg = document.querySelector('.album_cover img, .albumImg img, #albumCover, .song_cover img'); if (coverImg && coverImg.src) { coverUrl = coverImg.src; } else { const playerBg = document.querySelector('.player_bg, #playerBg'); if (playerBg && playerBg.style.backgroundImage) { const bgUrlMatch = playerBg.style.backgroundImage.match(/url\("?(.*?)"?\)/); if (bgUrlMatch && bgUrlMatch[1]) { coverUrl = bgUrlMatch[1]; } } } // 5. 更新全局状态 if (songName && artistName) { const effectiveId = songId || `${songName}-${artistName}`; if (currentSong.id !== effectiveId || currentSong.platform !== PLATFORM.KUGOU || currentSong.name !== songName) { console.log('检测到新酷狗歌曲:', songName, artistName, effectiveId); currentSong = { id: effectiveId, name: songName, artist: artistName, album: albumName || '未知专辑', cover: coverUrl, audioSrc: audioSrc || currentSong.audioSrc, mediaElement: currentSong.mediaElement, platform: PLATFORM.KUGOU }; const floatBtn = document.querySelector('.music-dl-btn'); if (floatBtn) floatBtn.classList.add('active'); if (CONFIG.showNotifications) { showNotification('检测到歌曲', `${songName} - ${artistName}`, 'info'); } updateSongInfo(); return true; } else if (currentSong.cover !== coverUrl || currentSong.audioSrc !== audioSrc) { // 更新封面和音频源 if (coverUrl) currentSong.cover = coverUrl; if (audioSrc) currentSong.audioSrc = audioSrc; updateSongInfo(); return true; } return true; } else { console.log('未能检测到完整的酷狗歌曲信息'); return false; } } // 获取酷狗音乐下载链接 function getKugouDownloadUrl(songHash, quality) { return new Promise((resolve, reject) => { // Helper to attempt fallback to captured audio stream const fallbackToAudioSrc = (reason) => { const audioSrc = currentSong.audioSrc || (currentSong.mediaElement && currentSong.mediaElement.src); if (audioSrc && audioSrc.startsWith('http')) { console.log(`酷狗: ${reason},使用捕获的音频流:`, audioSrc); const isFlac = audioSrc.includes('.flac'); resolve({ url: audioSrc, size: 0, type: isFlac ? 'flac' : 'mp3', directStream: true }); return true; // Indicate fallback was successful } else { console.log(`酷狗: ${reason},但无备用音频流`); return false; // Indicate fallback failed } }; // 1. Check direct stream preference first if (CONFIG.preferDirectStream) { const audioSrc = currentSong.audioSrc || (currentSong.mediaElement && currentSong.mediaElement.src); if (audioSrc && audioSrc.startsWith('http')) { console.log("酷狗: 优先使用直接捕获的音频流"); const isFlac = audioSrc.includes('.flac'); resolve({ url: audioSrc, size: 0, type: isFlac ? 'flac' : 'mp3', directStream: true }); return; } } // 2. Check for valid song Hash if (!songHash || !/^[A-Z0-9]+$/i.test(songHash)) { if (!fallbackToAudioSrc('无效Hash')) { return reject('无效或缺失的酷狗歌曲Hash (且无备用音频流)'); } return; // Fallback handled } // 3. Attempt API call const apiUrl = `https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash=${songHash}&dfid=-&mid=-&platid=4`; console.log("酷狗API请求URL:", apiUrl); GM_xmlhttpRequest({ method: 'GET', url: apiUrl, headers: { 'Referer': 'https://www.kugou.com/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36' }, onload: function(response) { let apiProvidesUrl = false; try { const data = JSON.parse(response.responseText); if (data && data.status === 1 && data.data && data.data.play_url) { let url = data.data.play_url; let type = 'mp3'; if (data.data.extname === 'flac' || url.includes('.flac')) type = 'flac'; if (quality === 'lossless' && type !== 'flac') console.warn("酷狗: 请求无损但API未返回FLAC"); console.log("酷狗: API获取链接成功"); resolve({ url: url, size: data.data.filesize || 0, type: type }); apiProvidesUrl = true; } // If status !== 1 or no play_url, apiProvidesUrl remains false } catch (e) { console.error("酷狗: API响应解析失败:", e); // apiProvidesUrl remains false } // If API didn't provide a usable URL, attempt fallback if (!apiProvidesUrl) { if (!fallbackToAudioSrc('API无URL/失败')) { // Fallback also failed, reject reject('无法获取下载链接 (可能需要VIP或版权限制,且无备用音频流)'); } } }, onerror: function(error) { console.error("酷狗: API请求失败:", error); // Attempt fallback on error if (!fallbackToAudioSrc('API请求失败')) { reject('无法获取下载链接 (API请求失败,且无备用音频流)'); } } }); }); } // ================ 下载功能 ================ function quickDownload(format) { if (!currentSong.name || !currentSong.artist) { showNotification('下载失败', '请先播放一首歌曲', 'warning'); return; } const quality = (format === 'flac') ? 'lossless' : CONFIG.defaultQuality; downloadSong(quality, format); } function downloadSong(quality, preferredFormat = null) { if (!currentSong.name || !currentSong.artist) { showNotification('下载失败', '未检测到有效的歌曲信息', 'error'); return; } showProgress(0); console.log(`开始下载: ${currentSong.name}, 音质=${quality}, 平台=${currentSong.platform}`); let downloadPromise; if (currentSong.platform === PLATFORM.NETEASE) { downloadPromise = getNeteaseDownloadUrl(currentSong.id, quality); } else if (currentSong.platform === PLATFORM.KUGOU) { downloadPromise = getKugouDownloadUrl(currentSong.id, quality); } else { showNotification('下载失败', '不支持的平台', 'error'); hideProgress(); return; } downloadPromise.then(async data => { if (!data || !data.url) { // If promise rejected, the error message is already handled in getNetease/KugouDownloadUrl // If promise resolved but data is bad (shouldn't happen with current logic), throw error if (!data) throw new Error('获取下载链接失败 (内部错误,无数据返回)'); if (!data.url) throw new Error('获取下载链接失败 (内部错误,无URL返回)'); } // 修复:确保flac格式正确识别,即使URL不包含.flac后缀 let actualType = data.type; let needsConversion = false; // 检查是否需要转换为FLAC if ((quality === 'lossless' || preferredFormat === 'flac') && actualType !== 'flac') { console.log("检测到需要转换为FLAC格式"); needsConversion = true; } else if (data.url.includes('.flac')) { actualType = 'flac'; } else if (!actualType) { actualType = 'mp3'; } let fileName; // 针对网易云音乐,简化文件名 if (currentSong.platform === PLATFORM.NETEASE) { fileName = `${currentSong.name} - ${currentSong.artist}.${needsConversion ? 'flac' : actualType}`; } else { // 酷狗音乐保持原有格式 fileName = `${currentSong.name} - ${currentSong.artist}`; if (needsConversion || actualType === 'flac') { fileName += ' [FLAC]'; } else { switch (quality) { case 'exhigh': fileName += ' [320K]'; break; case 'higher': fileName += ' [320K]'; break; case 'standard': fileName += ' [128K]'; break; default: fileName += ' [MP3]'; } } fileName += `.${needsConversion ? 'flac' : actualType}`; } fileName = fileName.replace(/[\\/:*?"<>|]/g, '_'); console.log(`准备下载: URL=${data.url}, Filename=${fileName}, 类型=${actualType}, 直接流=${data.directStream ? '是' : '否'}, 需要转换=${needsConversion ? '是' : '否'}`); // 显示直接流下载提示 if (data.directStream) { showNotification('使用直接音频流', `正在下载${needsConversion ? '并转换' : ''}歌曲...`, 'info'); } // 如果需要转换为FLAC格式 if (needsConversion && (quality === 'lossless' || preferredFormat === 'flac')) { try { console.log("开始转换为FLAC格式..."); showProgress(10); // 转换为FLAC const flacBlob = await convertToFlac(data.url, fileName); if (!flacBlob) { throw new Error("转换FLAC格式失败"); } showProgress(80); console.log("FLAC转换完成,准备下载"); // 创建Blob URL并下载 const blobUrl = URL.createObjectURL(flacBlob); GM_download({ url: blobUrl, name: fileName, saveAs: false, onload: function() { console.log("FLAC下载完成:", fileName); showProgress(100); showNotification('下载完成', `${fileName}`, 'success'); URL.revokeObjectURL(blobUrl); }, onerror: function(error) { console.error("FLAC下载错误:", error); showNotification('下载失败', `错误: ${error.error || '未知错误'}`, 'error'); hideProgress(); URL.revokeObjectURL(blobUrl); } }); return; } catch (error) { console.error("FLAC转换或下载失败:", error); showNotification('FLAC转换失败', '将使用原始格式下载', 'warning'); // 转换失败时继续使用原始格式下载 } } // 正常下载流程 (包括转换失败后的原始格式下载) GM_download({ url: data.url, name: fileName.replace('.flac', `.${actualType}`), // Ensure correct extension if conversion failed saveAs: false, onload: function() { console.log("下载完成:", fileName); showProgress(100); showNotification('下载完成', `${fileName}`, 'success'); }, onerror: function(error) { console.error("下载错误:", error); showNotification('下载失败', `错误: ${error.error || '未知错误'}`, 'error'); hideProgress(); }, ontimeout: function() { console.error("下载超时"); showNotification('下载失败', '下载超时', 'error'); hideProgress(); }, onprogress: function(e) { if (e.lengthComputable && e.total > 0) { const percent = Math.round((e.loaded / e.total) * 100); showProgress(percent); } } }); }).catch(error => { console.error("下载处理出错:", error); // Display the error message from the reject call in getNetease/KugouDownloadUrl showNotification('获取链接失败', `${error}`, 'error'); hideProgress(); }); } // ================ 初始化 ================ function init() { console.log('音乐下载助手初始化开始 (v1.3.0)...'); // 确保样式先注入 try { injectStyles(); console.log('样式注入成功'); } catch (e) { console.error("样式注入失败:", e); } // 初始化音频转换器 initAudioConverter().then(success => { if (success) { console.log("音频转换器初始化成功"); } else { console.warn("音频转换器初始化失败,FLAC转换功能可能不可用"); } }); // 创建UI并开始监听 setTimeout(() => { try { createUI(); updateSongInfo(); // 初始UI状态 console.log('UI 创建完成'); // 开始监听 if (currentPlatform === PLATFORM.NETEASE) { monitorNeteasePlaying(); } else if (currentPlatform === PLATFORM.KUGOU) { monitorKugouPlaying(); } // 注册菜单命令 GM_registerMenuCommand('音乐下载助手设置', function() { showPanel(); toggleSettings(); }); console.log('音乐下载助手初始化完成,当前平台:', currentPlatform); } catch (e) { console.error("初始化失败:", e); // 尝试重新初始化 setTimeout(() => { try { console.log('尝试重新初始化...'); if (!document.querySelector('.music-dl-btn')) { createUI(); if (currentPlatform === PLATFORM.NETEASE) { monitorNeteasePlaying(); } else if (currentPlatform === PLATFORM.KUGOU) { monitorKugouPlaying(); } } } catch (e2) { console.error("重新初始化失败:", e2); } }, 5000); } }, 1000); } // 确保在页面加载后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // 备份方案:如果DOMContentLoaded错过了,使用load事件 window.addEventListener('load', () => { if (!document.querySelector('.music-dl-btn')) { console.log('使用window.load事件初始化'); init(); } }); // 立即尝试初始化 setTimeout(() => { if (!document.querySelector('.music-dl-btn')) { console.log('使用setTimeout初始化'); init(); } }, 2000); })();