This commit is contained in:
lyswhut
2022-10-29 11:36:35 +08:00
parent 66b965d9f7
commit e45cee6596
715 changed files with 57096 additions and 31771 deletions
@@ -0,0 +1,46 @@
import { httpFetch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../options'
import { dnsLookup } from '../utils'
const api_test = {
getMusicUrl(songInfo, type) {
const requestObj = httpFetch(`http://ts.tempmusics.tk/url/wy/${songInfo.songmid}/${type}`, {
method: 'get',
timeout,
headers,
lookup: dnsLookup,
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
/* getPic(songInfo) {
const requestObj = httpFetch(`http://localhost:3100/pic/wy/${songInfo.songmid}`, {
method: 'get',
timeout,
headers,
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getLyric(songInfo) {
const requestObj = httpFetch(`http://localhost:3100/lrc/wy/${songInfo.songmid}`, {
method: 'get',
timeout,
headers,
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
}, */
}
export default api_test
+141
View File
@@ -0,0 +1,141 @@
import { httpFetch } from '../../request'
import { weapi } from './utils/crypto'
import { dateFormat2 } from '../../index'
let cursorTools = {
cache: {},
getCursor(id, page, limit) {
let cacheData = this.cache[id]
if (!cacheData) cacheData = this.cache[id] = {}
let orderType
let cursor
let offset
if (page == 1) {
cacheData.page = 1
cursor = cacheData.cursor = cacheData.prevCursor = Date.now()
orderType = 1
offset = 0
} else if (cacheData.page) {
cursor = cacheData.cursor
if (page > cacheData.page) {
orderType = 1
offset = (page - cacheData.page - 1) * limit
} else if (page < cacheData.page) {
orderType = 0
offset = (cacheData.page - page - 1) * limit
} else {
cursor = cacheData.cursor = cacheData.prevCursor
offset = cacheData.offset
orderType = cacheData.orderType
}
}
return {
orderType,
cursor,
offset,
}
},
setCursor(id, cursor, orderType, offset, page) {
let cacheData = this.cache[id]
if (!cacheData) cacheData = this.cache[id] = {}
cacheData.prevCursor = cacheData.cursor
cacheData.cursor = cursor
cacheData.orderType = orderType
cacheData.offset = offset
cacheData.page = page
},
}
export default {
_requestObj: null,
_requestObj2: null,
async getComment({ songmid }, page = 1, limit = 20) {
if (this._requestObj) this._requestObj.cancelHttp()
const id = 'R_SO_4_' + songmid
const cursorInfo = cursorTools.getCursor(songmid, page, limit)
const _requestObj = httpFetch('https://music.163.com/weapi/comment/resource/comments/get', {
method: 'post',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
origin: 'https://music.163.com',
Refere: 'http://music.163.com/',
},
form: weapi({
cursor: cursorInfo.cursor,
offset: cursorInfo.offset,
orderType: cursorInfo.orderType,
pageNo: page,
pageSize: limit,
rid: id,
threadId: id,
}),
})
const { body, statusCode } = await _requestObj.promise
// console.log(body)
if (statusCode != 200 || body.code !== 200) throw new Error('获取评论失败')
cursorTools.setCursor(songmid, body.data.cursor, cursorInfo.orderType, cursorInfo.offset, page)
return { source: 'wy', comments: this.filterComment(body.data.comments), total: body.data.totalCount, page, limit, maxPage: Math.ceil(body.data.totalCount / limit) || 1 }
},
async getHotComment({ songmid }, page = 1, limit = 100) {
if (this._requestObj2) this._requestObj2.cancelHttp()
const id = 'R_SO_4_' + songmid
const _requestObj2 = httpFetch('https://music.163.com/weapi/comment/resource/comments/get', {
method: 'post',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
origin: 'https://music.163.com',
Refere: 'http://music.163.com/',
},
form: weapi({
cursor: Date.now().toString(),
offset: 0,
orderType: 1,
pageNo: page,
pageSize: limit,
rid: id,
threadId: id,
}),
})
const { body, statusCode } = await _requestObj2.promise
if (statusCode != 200 || body.code !== 200) throw new Error('获取热门评论失败')
// console.log(body)
const total = body.data.hotComments?.length ?? 0
return { source: 'wy', comments: this.filterComment(body.data.hotComments), total, page, limit, maxPage: 1 }
},
filterComment(rawList) {
return rawList.map(item => {
let data = {
id: item.commentId,
text: item.content ? item.content.split('\n') : '',
time: item.time ? item.time : '',
timeStr: item.time ? dateFormat2(item.time) : '',
userName: item.user.nickname,
avatar: item.user.avatarUrl,
userId: item.user.userId,
likedCount: item.likedCount,
reply: [],
}
let replyData = item.beReplied && item.beReplied[0]
return replyData
? {
id: item.commentId,
rootId: replyData.beRepliedCommentId,
text: replyData.content ? replyData.content.split('\n') : '',
time: item.time,
timeStr: null,
userName: replyData.user.nickname,
avatar: replyData.user.avatarUrl,
userId: replyData.user.userId,
likedCount: null,
reply: [data],
}
: data
})
},
}
@@ -0,0 +1,26 @@
import { httpFetch } from '../../request'
import { weapi } from './utils/crypto'
export default {
_requestObj: null,
async getList(retryNum = 0) {
if (this._requestObj) this._requestObj.cancelHttp()
if (retryNum > 2) return Promise.reject(new Error('try max num'))
const _requestObj = httpFetch('https://music.163.com/weapi/search/hot', {
method: 'post',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
origin: 'https://music.163.com',
},
form: weapi({ type: 1111 }),
})
const { body, statusCode } = await _requestObj.promise
if (statusCode != 200 || body.code !== 200) throw new Error('获取热搜词失败')
// console.log(body)
return { source: 'wy', list: this.filterList(body.result.hots) }
},
filterList(rawList) {
return rawList.map(item => item.first)
},
}
+32
View File
@@ -0,0 +1,32 @@
import leaderboard from './leaderboard'
import { apis } from '../api-source'
import getLyric from './lyric'
import getMusicInfo from './musicInfo'
import musicSearch from './musicSearch'
import songList from './songList'
import hotSearch from './hotSearch'
import comment from './comment'
const wy = {
leaderboard,
musicSearch,
songList,
hotSearch,
comment,
getMusicUrl(songInfo, type) {
return apis('wy').getMusicUrl(songInfo, type)
},
getLyric(songInfo) {
return getLyric(songInfo.songmid)
},
getPic(songInfo) {
const requestObj = getMusicInfo(songInfo.songmid)
requestObj.promise = requestObj.promise.then(info => info.al.picUrl)
return requestObj
},
getMusicDetailPageUrl(songInfo) {
return `https://music.163.com/#/song?id=${songInfo.songmid}`
},
}
export default wy
@@ -0,0 +1,205 @@
import { weapi } from './utils/crypto'
import { httpFetch } from '../../request'
import musicDetailApi from './musicDetail'
const topList = [
{ id: 'wy__19723756', bangid: '19723756', name: '云音乐飙升榜' },
{ id: 'wy__3778678', bangid: '3778678', name: '云音乐热歌榜' },
{ id: 'wy__3779629', bangid: '3779629', name: '云音乐新歌榜' },
{ id: 'wy__2884035', bangid: '2884035', name: '云音乐原创榜' },
{ id: 'wy__2250011882', bangid: '2250011882', name: '抖音排行榜' },
{ id: 'wy__1978921795', bangid: '1978921795', name: '云音乐电音榜' },
{ id: 'wy__4395559', bangid: '4395559', name: '华语金曲榜' },
{ id: 'wy__71384707', bangid: '71384707', name: '云音乐古典音乐榜' },
{ id: 'wy__10520166', bangid: '10520166', name: '云音乐国电榜' },
{ id: 'wy__2006508653', bangid: '2006508653', name: '电竞音乐榜' },
{ id: 'wy__991319590', bangid: '991319590', name: '云音乐说唱榜' },
{ id: 'wy__180106', bangid: '180106', name: 'UK排行榜周榜' },
{ id: 'wy__60198', bangid: '60198', name: '美国Billboard周榜' },
{ id: 'wy__21845217', bangid: '21845217', name: 'KTV嗨榜' },
{ id: 'wy__11641012', bangid: '11641012', name: 'iTunes榜' },
{ id: 'wy__120001', bangid: '120001', name: 'Hit FM Top榜' },
{ id: 'wy__60131', bangid: '60131', name: '日本Oricon周榜' },
{ id: 'wy__3733003', bangid: '3733003', name: '韩国Melon排行榜周榜' },
{ id: 'wy__60255', bangid: '60255', name: '韩国Mnet排行榜周榜' },
{ id: 'wy__46772709', bangid: '46772709', name: '韩国Melon原声周榜' },
{ id: 'wy__64016', bangid: '64016', name: '中国TOP排行榜(内地榜)' },
{ id: 'wy__112504', bangid: '112504', name: '中国TOP排行榜(港台榜)' },
{ id: 'wy__3112516681', bangid: '3112516681', name: '中国新乡村音乐排行榜' },
{ id: 'wy__10169002', bangid: '10169002', name: '香港电台中文歌曲龙虎榜' },
{ id: 'wy__27135204', bangid: '27135204', name: '法国 NRJ EuroHot 30周榜' },
{ id: 'wy__1899724', bangid: '1899724', name: '中国嘻哈榜' },
{ id: 'wy__112463', bangid: '112463', name: '台湾Hito排行榜' },
{ id: 'wy__3812895', bangid: '3812895', name: 'Beatport全球电子舞曲榜' },
{ id: 'wy__2617766278', bangid: '2617766278', name: '新声榜' },
{ id: 'wy__745956260', bangid: '745956260', name: '云音乐韩语榜' },
{ id: 'wy__2847251561', bangid: '2847251561', name: '说唱TOP榜' },
{ id: 'wy__2023401535', bangid: '2023401535', name: '英国Q杂志中文版周榜' },
{ id: 'wy__2809513713', bangid: '2809513713', name: '云音乐欧美热歌榜' },
{ id: 'wy__2809577409', bangid: '2809577409', name: '云音乐欧美新歌榜' },
{ id: 'wy__71385702', bangid: '71385702', name: '云音乐ACG音乐榜' },
{ id: 'wy__3001835560', bangid: '3001835560', name: '云音乐ACG动画榜' },
{ id: 'wy__3001795926', bangid: '3001795926', name: '云音乐ACG游戏榜' },
{ id: 'wy__3001890046', bangid: '3001890046', name: '云音乐ACG VOCALOID榜' },
]
export default {
limit: 100000,
list: [
{
id: 'wybsb',
name: '飙升榜',
bangid: '19723756',
},
{
id: 'wyrgb',
name: '热歌榜',
bangid: '3778678',
},
{
id: 'wyxgb',
name: '新歌榜',
bangid: '3779629',
},
{
id: 'wyycb',
name: '原创榜',
bangid: '2884035',
},
{
id: 'wygdb',
name: '古典榜',
bangid: '71384707',
},
{
id: 'wydouyb',
name: '抖音榜',
bangid: '2250011882',
},
{
id: 'wyhyb',
name: '韩语榜',
bangid: '745956260',
},
{
id: 'wydianyb',
name: '电音榜',
bangid: '1978921795',
},
{
id: 'wydjb',
name: '电竞榜',
bangid: '2006508653',
},
{
id: 'wyktvbb',
name: 'KTV唛榜',
bangid: '21845217',
},
],
getUrl(id) {
return `https://music.163.com/discover/toplist?id=${id}`
},
regExps: {
list: /<textarea id="song-list-pre-data" style="display:none;">(.+?)<\/textarea>/,
},
_requestBoardsObj: null,
getBoardsData() {
if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp()
this._requestBoardsObj = httpFetch('https://music.163.com/weapi/toplist', {
method: 'post',
form: weapi({}),
})
return this._requestBoardsObj.promise
},
getData(id) {
const requestBoardsDetailObj = httpFetch('https://music.163.com/weapi/v3/playlist/detail', {
method: 'post',
form: weapi({
id,
n: 100000,
p: 1,
}),
})
return requestBoardsDetailObj.promise
},
filterBoardsData(rawList) {
// console.log(rawList)
let list = []
for (const board of rawList) {
// 排除 MV榜
// if (board.id == 201) continue
list.push({
id: 'wy__' + board.id,
name: board.name,
bangid: String(board.id),
})
}
return list
},
async getBoards(retryNum = 0) {
// if (++retryNum > 3) return Promise.reject(new Error('try max num'))
// let response
// try {
// response = await this.getBoardsData()
// } catch (error) {
// return this.getBoards(retryNum)
// }
// console.log(response.body)
// if (response.statusCode !== 200 || response.body.code !== 200) return this.getBoards(retryNum)
// const list = this.filterBoardsData(response.body.list)
// console.log(list)
// console.log(JSON.stringify(list))
// this.list = list
// return {
// list,
// source: 'wy',
// }
this.list = topList
return {
list: topList,
source: 'wy',
}
},
async getList(bangid, page, retryNum = 0) {
if (++retryNum > 6) return Promise.reject(new Error('try max num'))
// console.log(bangid)
let resp
try {
resp = await this.getData(bangid)
} catch (err) {
if (err.message == 'try max num') {
throw err
} else {
return this.getList(bangid, page, retryNum)
}
}
if (resp.statusCode !== 200 || resp.body.code !== 200) return this.getList(bangid, page, retryNum)
// console.log(resp.body)
let musicDetail
try {
musicDetail = await musicDetailApi.getList(resp.body.playlist.trackIds.map(trackId => trackId.id))
} catch (err) {
console.log(err)
if (err.message == 'try max num') {
throw err
} else {
return this.getList(bangid, page, retryNum)
}
}
// console.log(musicDetail)
return {
total: musicDetail.list.length,
list: musicDetail.list,
limit: this.limit,
page,
source: 'wy',
}
},
getDetailPageUrl(id) {
if (typeof id == 'string') id = id.replace('wy__', '')
return `https://music.163.com/#/discover/toplist?id=${id}`
},
}
+66
View File
@@ -0,0 +1,66 @@
import { httpFetch } from '../../request'
import { linuxapi } from './utils/crypto'
// import { decodeName } from '../..'
// const parseLyric = (str, lrc) => {
// if (!str) return ''
// str = str.replace(/\r/g, '')
// let lxlyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
// let result = str.match(/\[((\d+),\d+)\].*/)
// let time = parseInt(result[2])
// let ms = time % 1000
// time /= 1000
// let m = parseInt(time / 60).toString().padStart(2, '0')
// time %= 60
// let s = parseInt(time).toString().padStart(2, '0')
// time = `${m}:${s}.${ms}`
// str = str.replace(result[1], time)
// let startTime = 0
// str = str.replace(/\(0,1\) /g, ' ').replace(/\(\d+,\d+\)/g, time => {
// const [start, end] = time.replace(/^\((\d+,\d+)\)$/, '$1').split(',')
// time = `<${parseInt(startTime + parseInt(start))},${end}>`
// startTime = parseInt(startTime + parseInt(end))
// return time
// })
// return str
// })
// lxlyric = decodeName(lxlyric)
// return lxlyric.trim()
// }
// https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1523/files
export default songmid => {
const requestObj = httpFetch('https://music.163.com/api/linux/forward', {
method: 'post',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
form: linuxapi({
method: 'POST',
url: 'https://music.163.com/api/song/lyric?_nmclfl=1',
params: {
id: songmid,
tv: -1,
lv: -1,
rv: -1,
kv: -1,
},
}),
})
requestObj.promise = requestObj.promise.then(({ body }) => {
if (body.code !== 200 || !body?.lrc?.lyric) return Promise.reject(new Error('Get lyric failed'))
// console.log(body)
return {
lyric: body.lrc.lyric,
tlyric: body.tlyric?.lyric ?? '',
rlyric: body.romalrc?.lyric ?? '',
// lxlyric: parseLyric(body.klyric.lyric),
}
})
return requestObj
}
@@ -0,0 +1,87 @@
import { httpFetch } from '../../request'
import { weapi } from './utils/crypto'
import { formatPlayTime, sizeFormate } from '../../index'
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
export default {
getSinger(singers) {
let arr = []
singers.forEach(singer => {
arr.push(singer.name)
})
return arr.join('、')
},
filterList({ songs, privileges }) {
// console.log(songs, privileges)
const list = []
songs.forEach((item, index) => {
const types = []
const _types = {}
let size
let privilege = privileges[index]
if (privilege.id !== item.id) privilege = privileges.find(p => p.id === item.id)
if (!privilege) return
switch (privilege.maxbr) {
case 999000:
size = item.sq ? sizeFormate(item.sq.size) : null
types.push({ type: 'flac', size })
_types.flac = {
size,
}
case 320000:
size = item.h ? sizeFormate(item.h.size) : null
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
case 192000:
case 128000:
size = item.l ? sizeFormate(item.l.size) : null
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
}
types.reverse()
list.push({
singer: this.getSinger(item.ar),
name: item.name,
albumName: item.al.name,
albumId: item.al.id,
source: 'wy',
interval: formatPlayTime(item.dt / 1000),
songmid: item.id,
img: item.al.picUrl,
lrc: null,
otherSource: null,
types,
_types,
typeUrl: {},
})
})
// console.log(list)
return list
},
async getList(ids = [], retryNum = 0) {
if (retryNum > 2) return Promise.reject(new Error('try max num'))
const requestObj = httpFetch('https://music.163.com/weapi/v3/song/detail', {
method: 'post',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
origin: 'https://music.163.com',
},
form: weapi({
c: '[' + ids.map(id => ('{"id":' + id + '}')).join(',') + ']',
ids: '[' + ids.join(',') + ']',
}),
})
const { body, statusCode } = await requestObj.promise
if (statusCode != 200 || body.code !== 200) throw new Error('获取歌曲详情失败')
// console.log(body)
return { source: 'wy', list: this.filterList(body) }
},
}
@@ -0,0 +1,25 @@
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
import { httpFetch } from '../../request'
import { weapi } from './utils/crypto'
export default songmid => {
const requestObj = httpFetch('https://music.163.com/weapi/v3/song/detail', {
method: 'post',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
Referer: 'https://music.163.com/song?id=' + songmid,
origin: 'https://music.163.com',
},
form: weapi({
c: `[{"id":${songmid}}]`,
ids: `[${songmid}]`,
}),
})
requestObj.promise = requestObj.promise.then(({ body }) => {
// console.log(body)
if (body.code !== 200 || !body.songs.length) return Promise.reject('获取歌曲信息失败')
return body.songs[0]
})
return requestObj
}
@@ -0,0 +1,101 @@
// import { httpFetch } from '../../request'
// import { weapi } from './utils/crypto'
import { sizeFormate, formatPlayTime } from '../../index'
// import musicDetailApi from './musicDetail'
import { eapiRequest } from './utils/index'
export default {
limit: 30,
total: 0,
page: 0,
allPage: 1,
musicSearch(str, page, limit) {
const searchRequest = eapiRequest('/api/cloudsearch/pc', {
s: str,
type: 1, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
limit,
total: page == 1,
offset: limit * (page - 1),
})
return searchRequest.promise.then(({ body }) => body)
},
getSinger(singers) {
let arr = []
singers.forEach(singer => {
arr.push(singer.name)
})
return arr.join('、')
},
handleResult(rawList) {
// console.log(rawList)
if (!rawList) return []
return rawList.map(item => {
const types = []
const _types = {}
let size
switch (item.privilege.maxbr) {
case 999000:
size = item.sq ? sizeFormate(item.sq.size) : null
types.push({ type: 'flac', size })
_types.flac = {
size,
}
case 320000:
size = item.h ? sizeFormate(item.h.size) : null
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
case 192000:
case 128000:
size = item.l ? sizeFormate(item.l.size) : null
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
}
types.reverse()
return {
singer: this.getSinger(item.ar),
name: item.name,
albumName: item.al.name,
albumId: item.al.id,
source: 'wy',
interval: formatPlayTime(item.dt / 1000),
songmid: item.id,
img: item.al.picUrl,
lrc: null,
types,
_types,
typeUrl: {},
}
})
},
search(str, page = 1, limit, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit == null) limit = this.limit
return this.musicSearch(str, page, limit).then(result => {
// console.log(result)
if (!result || result.code !== 200) return this.search(str, page, limit, retryNum)
let list = this.handleResult(result.result.songs || [])
// console.log(list)
if (list == null) return this.search(str, page, limit, retryNum)
this.total = result.result.songCount || 0
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
return {
list,
allPage: this.allPage,
limit: this.limit,
total: this.total,
source: 'wy',
}
// return result.data
})
},
}
+325
View File
@@ -0,0 +1,325 @@
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/playlist_catlist.js
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/playlist_hot.js
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/top_playlist.js
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/playlist_detail.js
import { weapi, linuxapi } from './utils/crypto'
import { httpFetch } from '../../request'
import { formatPlayTime, sizeFormate } from '../../index'
import musicDetailApi from './musicDetail'
import { eapiRequest } from './utils/index'
export default {
_requestObj_tags: null,
_requestObj_hotTags: null,
_requestObj_list: null,
limit_list: 30,
limit_song: 100000,
successCode: 200,
cookie: 'MUSIC_U=',
sortList: [
{
name: '最热',
id: 'hot',
},
// {
// name: '最新',
// id: 'new',
// },
],
regExps: {
listDetailLink: /^.+(?:\?|&)id=(\d+)(?:&.*$|#.*$|$)/,
listDetailLink2: /^.+\/playlist\/(\d+)\/\d+\/.+$/,
},
/**
* 格式化播放数量
* @param {*} num
*/
formatPlayCount(num) {
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
return num
},
getSinger(singers) {
let arr = []
singers.forEach(singer => {
arr.push(singer.name)
})
return arr.join('、')
},
async handleParseId(link, retryNum = 0) {
if (retryNum > 2) throw new Error('link try max num')
const requestObj_listDetailLink = httpFetch(link)
const { headers: { location }, statusCode } = await requestObj_listDetailLink.promise
// console.log(headers)
if (statusCode > 400) return this.handleParseId(link, ++retryNum)
const url = location == null ? link : location
return this.regExps.listDetailLink.test(url)
? url.replace(this.regExps.listDetailLink, '$1')
: url.replace(this.regExps.listDetailLink2, '$1')
},
async getListId(id) {
let cookie
if (/###/.test(id)) {
const [url, token] = id.split('###')
id = url
cookie = `MUSIC_U=${token}`
}
if ((/[?&:/]/.test(id))) {
if (this.regExps.listDetailLink.test(id)) {
id = id.replace(this.regExps.listDetailLink, '$1')
} else if (this.regExps.listDetailLink2.test(id)) {
id = id.replace(this.regExps.listDetailLink2, '$1')
} else {
id = await this.handleParseId(id)
}
// console.log(id)
}
return { id, cookie }
},
async getListDetail(rawId, page, tryNum = 0) { // 获取歌曲列表内的音乐
if (tryNum > 2) return Promise.reject(new Error('try max num'))
const { id, cookie } = await this.getListId(rawId)
if (cookie) this.cookie = cookie
const requestObj_listDetail = httpFetch('https://music.163.com/api/linux/forward', {
method: 'post',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
Cookie: this.cookie,
},
form: linuxapi({
method: 'POST',
url: 'https://music.163.com/api/v3/playlist/detail',
params: {
id,
n: this.limit_song,
s: 8,
},
}),
})
const { statusCode, body } = await requestObj_listDetail.promise
if (statusCode !== 200 || body.code !== this.successCode) return this.getListDetail(id, page, ++tryNum)
let limit = 1000
let rangeStart = (page - 1) * limit
// console.log(body)
let musicDetail
try {
musicDetail = await musicDetailApi.getList(body.playlist.trackIds.slice(rangeStart, limit * page).map(trackId => trackId.id))
} catch (err) {
console.log(err)
if (err.message == 'try max num') {
throw err
} else {
return this.getListDetail(id, page, ++tryNum)
}
}
// console.log(musicDetail)
return {
list: musicDetail.list,
page,
limit,
total: body.playlist.trackIds.length,
source: 'wy',
info: {
play_count: this.formatPlayCount(body.playlist.playCount),
name: body.playlist.name,
img: body.playlist.coverImgUrl,
desc: body.playlist.description,
author: body.playlist.creator.nickname,
},
}
},
filterListDetail({ playlist: { tracks }, privileges }) {
// console.log(tracks, privileges)
const list = []
tracks.forEach((item, index) => {
const types = []
const _types = {}
let size
let privilege = privileges[index]
if (privilege.id !== item.id) privilege = privileges.find(p => p.id === item.id)
if (!privilege) return
switch (privilege.maxbr) {
case 999000:
size = null
types.push({ type: 'flac', size })
_types.flac = {
size,
}
case 320000:
size = item.h ? sizeFormate(item.h.size) : null
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
case 192000:
case 128000:
size = item.l ? sizeFormate(item.l.size) : null
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
}
types.reverse()
list.push({
singer: this.getSinger(item.ar),
name: item.name,
albumName: item.al.name,
albumId: item.al.id,
source: 'wy',
interval: formatPlayTime(item.dt / 1000),
songmid: item.id,
img: item.al.picUrl,
lrc: null,
otherSource: null,
types,
_types,
typeUrl: {},
})
})
return list
},
// 获取列表数据
getList(sortId, tagId, page, tryNum = 0) {
if (tryNum > 2) return Promise.reject(new Error('try max num'))
if (this._requestObj_list) this._requestObj_list.cancelHttp()
this._requestObj_list = httpFetch('https://music.163.com/weapi/playlist/list', {
method: 'post',
form: weapi({
cat: tagId || '全部', // 全部,华语,欧美,日语,韩语,粤语,小语种,流行,摇滚,民谣,电子,舞曲,说唱,轻音乐,爵士,乡村,R&B/Soul,古典,民族,英伦,金属,朋克,蓝调,雷鬼,世界音乐,拉丁,另类/独立,New Age,古风,后摇,Bossa Nova,清晨,夜晚,学习,工作,午休,下午茶,地铁,驾车,运动,旅行,散步,酒吧,怀旧,清新,浪漫,性感,伤感,治愈,放松,孤独,感动,兴奋,快乐,安静,思念,影视原声,ACG,儿童,校园,游戏,70后,80后,90后,网络歌曲,KTV,经典,翻唱,吉他,钢琴,器乐,榜单,00后
order: sortId, // hot,new
limit: this.limit_list,
offset: this.limit_list * (page - 1),
total: true,
}),
})
return this._requestObj_list.promise.then(({ body }) => {
// console.log(body)
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
return {
list: this.filterList(body.playlists),
total: parseInt(body.total),
page,
limit: this.limit_list,
source: 'wy',
}
})
},
filterList(rawData) {
// console.log(rawData)
return rawData.map(item => ({
play_count: this.formatPlayCount(item.playCount),
id: item.id,
author: item.creator.nickname,
name: item.name,
time: item.createTime,
img: item.coverImgUrl,
grade: item.grade,
total: item.trackCount,
desc: item.description,
source: 'wy',
}))
},
// 获取标签
getTag(tryNum = 0) {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = httpFetch('https://music.163.com/weapi/playlist/catalogue', {
method: 'post',
form: weapi({}),
})
return this._requestObj_tags.promise.then(({ body }) => {
// console.log(JSON.stringify(body))
if (body.code !== this.successCode) return this.getTag(++tryNum)
return this.filterTagInfo(body)
})
},
filterTagInfo({ sub, categories }) {
const subList = {}
for (const item of sub) {
if (!subList[item.category]) subList[item.category] = []
subList[item.category].push({
parent_id: categories[item.category],
parent_name: categories[item.category],
id: item.name,
name: item.name,
source: 'wy',
})
}
const list = []
for (const key of Object.keys(categories)) {
list.push({
name: categories[key],
list: subList[key],
source: 'wy',
})
}
return list
},
// 获取热门标签
getHotTag(tryNum = 0) {
if (this._requestObj_hotTags) this._requestObj_hotTags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_hotTags = httpFetch('https://music.163.com/weapi/playlist/hottags', {
method: 'post',
form: weapi({}),
})
return this._requestObj_hotTags.promise.then(({ body }) => {
// console.log(JSON.stringify(body))
if (body.code !== this.successCode) return this.getTag(++tryNum)
return this.filterHotTagInfo(body.tags)
})
},
filterHotTagInfo(rawList) {
return rawList.map(item => ({
id: item.playlistTag.name,
name: item.playlistTag.name,
source: 'wy',
}))
},
getTags() {
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'wy' }))
},
async getDetailPageUrl(rawId) {
const { id } = await this.getListId(rawId)
return `https://music.163.com/#/playlist?id=${id}`
},
search(text, page, limit = 20) {
return eapiRequest('/api/cloudsearch/pc', {
s: text,
type: 1000, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
limit,
total: page == 1,
offset: limit * (page - 1),
})
.promise.then(({ body }) => {
if (body.code != this.successCode) throw new Error('filed')
// console.log(body)
return {
list: this.filterList(body.result.playlists),
limit,
total: body.result.playlistCount,
source: 'wy',
}
})
},
}
// getList
// getTags
// getListDetail
@@ -0,0 +1,54 @@
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/util/crypto.js
import { createCipheriv, createDecipheriv, publicEncrypt, randomBytes, createHash, constants } from 'crypto'
const iv = Buffer.from('0102030405060708')
const presetKey = Buffer.from('0CoJUm6Qyw8W8jud')
const linuxapiKey = Buffer.from('rFgB&h#%2?^eDg:Q')
const base62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
const publicKey = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----'
const eapiKey = 'e82ckenh8dichen8'
const aesEncrypt = (buffer, mode, key, iv) => {
const cipher = createCipheriv(mode, key, iv)
return Buffer.concat([cipher.update(buffer), cipher.final()])
}
const aesDecrypt = function(cipherBuffer, mode, key, iv) {
let decipher = createDecipheriv(mode, key, iv)
return Buffer.concat([decipher.update(cipherBuffer), decipher.final()])
}
const rsaEncrypt = (buffer, key) => {
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
return publicEncrypt({ key, padding: constants.RSA_NO_PADDING }, buffer)
}
export const weapi = object => {
const text = JSON.stringify(object)
const secretKey = randomBytes(16).map(n => (base62.charAt(n % 62).charCodeAt()))
return {
params: aesEncrypt(Buffer.from(aesEncrypt(Buffer.from(text), 'aes-128-cbc', presetKey, iv).toString('base64')), 'aes-128-cbc', secretKey, iv).toString('base64'),
encSecKey: rsaEncrypt(secretKey.reverse(), publicKey).toString('hex'),
}
}
export const linuxapi = object => {
const text = JSON.stringify(object)
return {
eparams: aesEncrypt(Buffer.from(text), 'aes-128-ecb', linuxapiKey, '').toString('hex').toUpperCase(),
}
}
export const eapi = (url, object) => {
const text = typeof object === 'object' ? JSON.stringify(object) : object
const message = `nobody${url}use${text}md5forencrypt`
const digest = createHash('md5').update(message).digest('hex')
const data = `${url}-36cd479b6b5-${text}-36cd479b6b5-${digest}`
return {
params: aesEncrypt(Buffer.from(data), 'aes-128-ecb', eapiKey, '').toString('hex').toUpperCase(),
}
}
export const eapiDecrypt = cipherBuffer => {
return aesDecrypt(cipherBuffer, 'aes-128-ecb', eapiKey, '').toString()
}
@@ -0,0 +1,22 @@
import { httpFetch } from '../../../request'
import { eapi } from './crypto'
export const eapiRequest = (url, data) => {
return httpFetch('http://interface.music.163.com/eapi/batch', {
method: 'post',
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
origin: 'https://music.163.com',
// cookie: 'os=pc; deviceId=A9C064BB4584D038B1565B58CB05F95290998EE8B025AA2D07AE; osver=Microsoft-Windows-10-Home-China-build-19043-64bit; appver=2.5.2.197409; channel=netease; MUSIC_A=37a11f2eb9de9930cad479b2ad495b0e4c982367fb6f909d9a3f18f876c6b49faddb3081250c4980dd7e19d4bd9bf384e004602712cf2b2b8efaafaab164268a00b47359f85f22705cc95cb6180f3aee40f5be1ebf3148d888aa2d90636647d0c3061cd18d77b7a0; __csrf=05b50d54082694f945d7de75c210ef94; mode=Z7M-KP5(7)GZ; NMTID=00OZLp2VVgq9QdwokUgq3XNfOddQyIAAAF_6i8eJg; ntes_kaola_ad=1',
},
form: eapi(url, data),
})
// requestObj.promise = requestObj.promise.then(({ body }) => {
// // console.log(raw)
// console.log(body)
// // console.log(eapiDecrypt(raw))
// // return eapiDecrypt(raw)
// return body
// })
// return requestObj
}