UNPKG

netease-music-downloader

Version:
266 lines (265 loc) 13.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.downloadAlbum = downloadAlbum; const progress_1 = require("../utils/progress"); const netease_1 = require("../services/netease"); const file_1 = require("../utils/file"); const axios_1 = __importDefault(require("axios")); const fs = __importStar(require("fs")); const rest_1 = require("@octokit/rest"); async function downloadImage(url) { try { const response = await (0, axios_1.default)({ method: 'get', url, responseType: 'arraybuffer', ...netease_1.proxyConfig }); return Buffer.from(response.data); } catch (error) { console.error('下载封面图片失败 Failed to download cover image:', error instanceof Error ? error.message : 'Unknown error'); return null; } } async function downloadAlbum(albumId, issueNumber, options) { try { // 从URL中提取ID Extract ID from URL if (albumId.includes('music.163.com')) { const match = albumId.match(/id=(\d+)/); if (!match) { console.error('无效的专辑URL Invalid album URL'); process.exit(1); } albumId = match[1]; } let albumInfo; try { albumInfo = await (0, netease_1.getAlbumInfo)(albumId); } catch (error) { console.error('\n获取专辑信息失败,可能是网络问题或代理服务器无响应\nFailed to get album info, might be network issue or proxy server not responding'); if (error instanceof Error) { console.error('详细错误 Detailed error:', error.message); } process.exit(1); } const { songs, albumName, artistName } = albumInfo; console.log(`\n专辑信息 Album info: ${albumName} - ${artistName}`); console.log(`共 Total: ${songs.length} 首歌曲 songs\n`); const sanitizedAlbumName = (0, file_1.sanitizeFileName)(albumName); const sanitizedArtistName = (0, file_1.sanitizeFileName)(artistName); const albumDirName = `${sanitizedArtistName}-${sanitizedAlbumName}`; const multibar = (0, progress_1.createMultiBar)(); const downloadResults = { success: [], failed: [], skipped: [], }; let i = 0; while (i < songs.length) { const song = songs[i]; try { const songName = song.name; const artistName = song.artists?.[0]?.name || '未知歌手 Unknown Artist'; const displayName = `${artistName}-${songName}`; const availability = await (0, netease_1.checkSongAvailabilityWithRetry)(song.id, options?.autoProxy); if (!availability.available || !availability.url) { console.log(`\n[${i + 1}/${songs.length}] ${displayName} (歌曲已下架或无版权,跳过下载 Song is unavailable or no copyright, skipping download)`); downloadResults.skipped.push(`${displayName} (无版权或已下架)`); i++; continue; } // 获取文件格式 const fileExtension = availability.type || availability.url.split('.').pop()?.split('?')[0] || 'mp3'; console.log(`[${i + 1}/${songs.length}] 获取到音质 Quality: ${availability.quality || 'unknown'}, 比特率 Bitrate: ${availability.bitrate || 'unknown'}kbps, 格式 Format: ${fileExtension}`); const sanitizedSongName = (0, file_1.sanitizeFileName)(songName); const sanitizedArtistName = (0, file_1.sanitizeFileName)(artistName); const fileName = `${String(i + 1).padStart(2, '0')}.${sanitizedArtistName}-${sanitizedSongName}.${fileExtension}`; const filePath = (0, file_1.getDownloadPath)('album', fileName, albumDirName); const lrcPath = (0, file_1.getDownloadPath)('album', `${String(i + 1).padStart(2, '0')}.${sanitizedArtistName}-${sanitizedSongName}.lrc`, albumDirName); // 下载歌词 const lyrics = await (0, netease_1.getLyrics)(song.id); if (lyrics) { fs.writeFileSync(lrcPath, lyrics, 'utf8'); console.log(`[${i + 1}/${songs.length}] 歌词下载完成 Lyrics downloaded`); console.log(`[${i + 1}/${songs.length}] 网页链接 Web URL: https://music.163.com/#/song?id=${song.id}`); } if (fs.existsSync(filePath)) { console.log(`\n[${i + 1}/${songs.length}] ${fileName} (文件已存在,跳过下载 File exists, skipping download)`); downloadResults.skipped.push(displayName); i++; continue; } console.log(`\n[${i + 1}/${songs.length}] 开始下载 Start downloading: ${displayName}`); const response = await (0, axios_1.default)({ method: 'get', url: availability.url, responseType: 'stream', ...(availability.needProxy ? netease_1.proxyConfig : {}) }); const totalLength = parseInt(response.headers['content-length'], 10); const progressBar = multibar.create(Math.round(totalLength / 1024), 0, { name: `[${i + 1}/${songs.length}] ${songName.slice(0, 30)}${songName.length > 30 ? '...' : ''}` }); const writer = fs.createWriteStream(filePath); let downloadedBytes = 0; response.data.on('data', (chunk) => { downloadedBytes += chunk.length; progressBar.update(Math.round(downloadedBytes / 1024)); }); response.data.pipe(writer); await new Promise((resolve, reject) => { writer.on('finish', () => resolve()); writer.on('error', (err) => reject(err)); }); progressBar.update(Math.round(totalLength / 1024)); downloadResults.success.push(displayName); i++; } catch (error) { const err = error; console.error(`\n[${i + 1}/${songs.length}] ${song.name} - 下载失败 Download failed: ${err.message}`); if (err.message.includes('socket') || err.message.includes('network')) { console.log('等待 3 秒后重试... Retrying in 3 seconds...'); await new Promise(resolve => setTimeout(resolve, 3000)); continue; } downloadResults.failed.push(`${song.name} (${err.message})`); i++; } } multibar.stop(); console.log('\n专辑下载完成!Album download completed!'); // 如果是通过 GitHub Issue 触发的下载,发送下载报告 if (issueNumber && !isNaN(Number(issueNumber))) { const octokit = new rest_1.Octokit({ auth: process.env.GITHUB_TOKEN, }); // 如果没有成功下载任何歌曲 if (downloadResults.success.length === 0) { const errorMessage = `❌ 专辑《${albumName}》下载失败\n\n` + `可能原因:所有歌曲都没有版权或已下架\n\n` + `总计歌曲数:${songs.length}\n` + `⚠️ 无版权或已下架:${downloadResults.skipped.length} 首\n\n` + (downloadResults.skipped.length > 0 ? `### 无版权或已下架的歌曲:\n${downloadResults.skipped.map(s => `- ${s}`).join('\n')}` : ''); try { await octokit.issues.createComment({ owner: 'Gaohaoyang', repo: 'netease-music-downloader', issue_number: Number(issueNumber), body: errorMessage }); // 关闭 issue await octokit.issues.update({ owner: 'Gaohaoyang', repo: 'netease-music-downloader', issue_number: Number(issueNumber), state: 'closed' }); return; // 直接返回,不进行打包上传 } catch (apiError) { console.error('GitHub API 调用失败:', apiError); return; } } const summaryMessage = `## 专辑《${albumName}》下载报告\n\n` + `总计歌曲数:${songs.length}\n\n` + `✅ 下载成功:${downloadResults.success.length} 首\n` + `❌ 下载失败:${downloadResults.failed.length} 首\n` + `⚠️ 无版权跳过:${downloadResults.skipped.length} 首\n\n` + (downloadResults.success.length > 0 ? `### 下载成功的歌曲:\n${downloadResults.success.map(s => `- ${s}`).join('\n')}\n\n` : '') + (downloadResults.failed.length > 0 ? `### 下载失败的歌曲:\n${downloadResults.failed.map(s => `- ${s}`).join('\n')}\n\n` : '') + (downloadResults.skipped.length > 0 ? `### 无版权或已下架的歌曲:\n${downloadResults.skipped.map(s => `- ${s}`).join('\n')}` : ''); try { // 发送下载报告 await octokit.issues.createComment({ owner: 'Gaohaoyang', repo: 'netease-music-downloader', issue_number: Number(issueNumber), body: summaryMessage }); // 关闭 issue await octokit.issues.update({ owner: 'Gaohaoyang', repo: 'netease-music-downloader', issue_number: Number(issueNumber), state: 'closed' }); } catch (apiError) { console.error('GitHub API 调用失败:', apiError); } } } catch (error) { if (issueNumber && !isNaN(Number(issueNumber))) { try { const octokit = new rest_1.Octokit({ auth: process.env.GITHUB_TOKEN, }); // 只发送简单的错误消息 await octokit.issues.createComment({ owner: 'Gaohaoyang', repo: 'netease-music-downloader', issue_number: Number(issueNumber), body: '❌ 专辑下载失败,可能是因为版权限制或资源不可用。' }); // 关闭 issue await octokit.issues.update({ owner: 'Gaohaoyang', repo: 'netease-music-downloader', issue_number: Number(issueNumber), state: 'closed' }); } catch (apiError) { // 只在控制台记录 API 错误,不要让它显示在 issue 中 console.error('GitHub API 调用失败:', apiError); } } // 在控制台显示详细错误,但不要让它显示在 issue 中 console.error('专辑下载失败,可能是因为版权限制或资源不可用。'); if (error instanceof Error) { console.error('详细错误:', error.message); } process.exit(1); } }