netease-music-downloader
Version:
Download music from NetEase Cloud Music
266 lines (265 loc) • 13.3 kB
JavaScript
;
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);
}
}