music-api-sdk
Version:
网易云音乐和QQ音乐API接口SDK - 统一的音乐搜索和歌词获取接口
291 lines (255 loc) • 8.46 kB
JavaScript
const axios = require('axios');
const CryptoJS = require('crypto-js');
const forge = require('node-forge');
class NeteaseService {
constructor() {
this.baseURL = 'https://music.163.com';
this.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36';
// 加密相关常量
this.iv = '0102030405060708';
this.presetKey = '0CoJUm6Qyw8W8jud';
this.base62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
this.publicKey = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB
-----END PUBLIC KEY-----`;
// Cookie管理
this.cookies = {
'appver': '8.20.20.231215173437',
'versioncode': '140',
'buildver': Date.now().toString().substr(0, 10),
'resolution': '1920x1080',
'os': 'pc',
'osver': 'Microsoft-Windows-10-Professional-build-22631-64bit',
'deviceId': this.generateDeviceId(),
'channel': 'netease'
};
}
// 生成设备ID
generateDeviceId() {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let result = '';
for (let i = 0; i < 32; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
// 生成Cookie字符串
generateCookieString() {
return Object.keys(this.cookies)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(this.cookies[key])}`)
.join('; ');
}
// AES加密
aesEncrypt(text, key, iv) {
const encrypted = CryptoJS.AES.encrypt(
CryptoJS.enc.Utf8.parse(text),
CryptoJS.enc.Utf8.parse(key),
{
iv: CryptoJS.enc.Utf8.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
}
);
return encrypted.toString();
}
// 生成随机字符串
createSecretKey(size) {
let keys = '';
for (let i = 0; i < size; i++) {
keys += this.base62.charAt(Math.floor(Math.random() * this.base62.length));
}
return keys;
}
// RSA加密
rsaEncrypt(text, pubKey) {
const forgePublicKey = forge.pki.publicKeyFromPem(pubKey);
const encrypted = forgePublicKey.encrypt(text, 'NONE');
return forge.util.bytesToHex(encrypted);
}
// weapi加密
weapi(object) {
const text = JSON.stringify(object);
const secretKey = this.createSecretKey(16);
const params = this.aesEncrypt(
this.aesEncrypt(text, this.presetKey, this.iv),
secretKey,
this.iv
);
const encSecKey = this.rsaEncrypt(secretKey.split('').reverse().join(''), this.publicKey);
return {
params,
encSecKey
};
}
// 创建请求选项
createRequestOptions(data) {
const encrypted = this.weapi(data);
return {
method: 'POST',
url: '',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': 'https://music.163.com/',
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Connection': 'keep-alive',
'Accept-Encoding': 'gzip, deflate, br',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'X-Requested-With': 'XMLHttpRequest',
'Cookie': this.generateCookieString()
},
data: new URLSearchParams(encrypted).toString(),
timeout: 10000,
};
}
// 基础搜索
async search(keywords, type = 1, limit = 30, offset = 0) {
try {
const data = {
s: keywords,
type: type, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
limit: limit,
offset: offset,
csrf_token: '' // 添加csrf_token参数
};
const options = this.createRequestOptions(data);
options.url = `${this.baseURL}/weapi/search/get`;
const response = await axios(options);
console.log('搜索响应:', response);
return response.data;
} catch (error) {
console.error('搜索失败:', error.message);
throw new Error(`搜索失败: ${error.message}`);
}
}
// 云搜索(推荐使用,结果更准确)
async cloudSearch(keywords, type = 1, limit = 10, offset = 0) {
try {
const data = {
s: keywords,
type: type,
limit: limit,
offset: offset,
total: true,
csrf_token: '' // 添加csrf_token参数
};
const options = this.createRequestOptions(data);
options.url = `${this.baseURL}/weapi/cloudsearch/pc`;
console.log('云搜索请求:', options);
const response = await axios(options);
console.log('云搜索响应:', response);
return response.data;
} catch (error) {
console.error('云搜索失败:', error.message);
throw new Error(`云搜索失败: ${error.message}`);
}
}
// 搜索建议
async searchSuggest(keywords, type = 'web') {
try {
const data = {
s: keywords || '',
};
const options = this.createRequestOptions(data);
const endpoint = type === 'mobile' ? 'keyword' : 'web';
options.url = `${this.baseURL}/api/search/suggest/${endpoint}`;
const response = await axios(options);
return response.data;
} catch (error) {
console.error('搜索建议失败:', error.message);
throw new Error(`搜索建议失败: ${error.message}`);
}
}
// 热门搜索
async getHotSearch() {
try {
const data = {
type: 1111,
};
const options = this.createRequestOptions(data);
options.url = `${this.baseURL}/api/search/hot`;
const response = await axios(options);
return response.data;
} catch (error) {
console.error('获取热门搜索失败:', error.message);
throw new Error(`获取热门搜索失败: ${error.message}`);
}
}
// 搜索类型常量
static get SEARCH_TYPES() {
return {
SONG: 1, // 单曲
ALBUM: 10, // 专辑
ARTIST: 100, // 歌手
PLAYLIST: 1000, // 歌单
USER: 1002, // 用户
MV: 1004, // MV
LYRIC: 1006, // 歌词
RADIO: 1009, // 电台
VIDEO: 1014, // 视频
};
}
// 便捷搜索方法
async searchSongs(keywords, limit = 30, offset = 0) {
return this.cloudSearch(keywords, NeteaseService.SEARCH_TYPES.SONG, limit, offset);
}
async searchAlbums(keywords, limit = 30, offset = 0) {
return this.cloudSearch(keywords, NeteaseService.SEARCH_TYPES.ALBUM, limit, offset);
}
async searchArtists(keywords, limit = 30, offset = 0) {
return this.cloudSearch(keywords, NeteaseService.SEARCH_TYPES.ARTIST, limit, offset);
}
async searchPlaylists(keywords, limit = 30, offset = 0) {
return this.cloudSearch(keywords, NeteaseService.SEARCH_TYPES.PLAYLIST, limit, offset);
}
// 获取歌词
async lyric(id) {
try {
const data = {
id: id,
tv: -1,
lv: -1,
rv: -1,
kv: -1,
_nmclfl: 1,
csrf_token: ''
};
const options = this.createRequestOptions(data);
options.url = `${this.baseURL}/weapi/song/lyric`;
const response = await axios(options);
if (response.data && response.data.code === 200) {
return {
code: 200,
data: {
lyric: response.data.lrc ? response.data.lrc.lyric : '',
tlyric: response.data.tlyric ? response.data.tlyric.lyric : '',
romalrc: response.data.romalrc ? response.data.romalrc.lyric : ''
}
};
} else {
return {
code: 404,
data: {
lyric: "",
error: '未找到歌词'
}
};
}
} catch (error) {
console.error('获取歌词失败:', error.message);
return {
code: 500,
data: {
lyric: "",
error: '获取歌词失败'
}
};
}
}
}
module.exports = NeteaseService;