v2.0.0
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
// Support qualitys: 128k 320k flac wav
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
id: 'test',
|
||||
name: '测试接口',
|
||||
disabled: false,
|
||||
supportQualitys: {
|
||||
kw: ['128k'],
|
||||
kg: ['128k'],
|
||||
tx: ['128k'],
|
||||
wy: ['128k'],
|
||||
mg: ['128k'],
|
||||
// bd: ['128k'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'temp',
|
||||
name: '临时接口',
|
||||
disabled: false,
|
||||
supportQualitys: {
|
||||
kw: ['128k'],
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
import apiSourceInfo from './api-source-info'
|
||||
import { apiSource, userApi } from '@renderer/store'
|
||||
|
||||
const apiList = {}
|
||||
const supportQuality = {}
|
||||
|
||||
for (const api of apiSourceInfo) {
|
||||
supportQuality[api.id] = api.supportQualitys
|
||||
for (const source of Object.keys(api.supportQualitys)) {
|
||||
apiList[`${api.id}_api_${source}`] = require(`./${source}/api-${api.id}`).default
|
||||
}
|
||||
}
|
||||
|
||||
const getAPI = source => apiList[`${apiSource.value}_api_${source}`]
|
||||
|
||||
const apis = source => {
|
||||
if (/^user_api/.test(apiSource.value)) return userApi.apis[source]
|
||||
let api = getAPI(source)
|
||||
if (api) return api
|
||||
throw new Error('Api is not found')
|
||||
}
|
||||
|
||||
export { apis, supportQuality }
|
||||
@@ -0,0 +1,22 @@
|
||||
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/bd/${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
|
||||
},
|
||||
}
|
||||
|
||||
export default api_test
|
||||
@@ -0,0 +1,23 @@
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
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('http://musicapi.qianqian.com/v1/restserver/ting?from=android&version=7.0.2.0&channel=ppzs&operator=0&method=baidu.ting.search.hot', {
|
||||
method: 'get',
|
||||
headers: {
|
||||
'User-Agent': 'android_7.0.2.0;baiduyinyue',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
if (statusCode != 200 || body.error_code !== 22000) throw new Error('获取热搜词失败')
|
||||
// console.log(body, statusCode)
|
||||
return { source: 'bd', list: this.filterList(body.result) }
|
||||
},
|
||||
filterList(rawList) {
|
||||
return rawList.map(item => item.word)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import leaderboard from './leaderboard'
|
||||
import { apis } from '../api-source'
|
||||
import musicInfo from './musicInfo'
|
||||
import songList from './songList'
|
||||
import { httpFetch } from '../../request'
|
||||
import musicSearch from './musicSearch'
|
||||
import hotSearch from './hotSearch'
|
||||
|
||||
const bd = {
|
||||
leaderboard,
|
||||
songList,
|
||||
musicSearch,
|
||||
hotSearch,
|
||||
getMusicUrl(songInfo, type) {
|
||||
return apis('bd').getMusicUrl(songInfo, type)
|
||||
},
|
||||
getPic(songInfo) {
|
||||
const requestObj = this.getMusicInfo(songInfo)
|
||||
requestObj.promise = requestObj.promise.then(info => info.pic_premium)
|
||||
return requestObj
|
||||
},
|
||||
getLyric(songInfo) {
|
||||
const requestObj = this.getMusicInfo(songInfo)
|
||||
requestObj.promise = requestObj.promise.then(info => httpFetch(info.lrclink).promise.then(resp => ({ lyric: resp.body, tlyric: '' })))
|
||||
return requestObj
|
||||
},
|
||||
// getLyric(songInfo) {
|
||||
// return apis('bd').getLyric(songInfo)
|
||||
// },
|
||||
// getPic(songInfo) {
|
||||
// return apis('bd').getPic(songInfo)
|
||||
// },
|
||||
getMusicInfo(songInfo) {
|
||||
return musicInfo.getMusicInfo(songInfo.songmid)
|
||||
},
|
||||
getMusicDetailPageUrl(songInfo) {
|
||||
return `http://music.taihe.com/song/${songInfo.songmid}`
|
||||
},
|
||||
}
|
||||
|
||||
export default bd
|
||||
@@ -0,0 +1,153 @@
|
||||
import { httpFetch } from '../../request'
|
||||
// import { formatPlayTime } from '../../index'
|
||||
|
||||
|
||||
const boardList = [
|
||||
// { id: 'bd__601', name: '歌单榜', bangid: '601' },
|
||||
{ id: 'bd__2', name: '热歌榜', bangid: '2' },
|
||||
{ id: 'bd__20', name: '华语金曲榜', bangid: '20' },
|
||||
{ id: 'bd__25', name: '网络歌曲榜', bangid: '25' },
|
||||
{ id: 'bd__1', name: '新歌榜', bangid: '1' },
|
||||
{ id: 'bd__21', name: '欧美金曲榜', bangid: '21' },
|
||||
{ id: 'bd__200', name: '原创音乐榜', bangid: '200' },
|
||||
{ id: 'bd__22', name: '经典老歌榜', bangid: '22' },
|
||||
{ id: 'bd__24', name: '影视金曲榜', bangid: '24' },
|
||||
{ id: 'bd__23', name: '情歌对唱榜', bangid: '23' },
|
||||
{ id: 'bd__11', name: '摇滚榜', bangid: '11' },
|
||||
{ id: 'bd__105', name: '好童星榜', bangid: '105' },
|
||||
{ id: 'bd__106', name: '雅克•藏羌彝原创音乐榜', bangid: '106' },
|
||||
]
|
||||
|
||||
export default {
|
||||
limit: 20,
|
||||
list: [
|
||||
{
|
||||
id: 'bdrgb',
|
||||
name: '热歌榜',
|
||||
bangid: '2',
|
||||
},
|
||||
{
|
||||
id: 'bdxgb',
|
||||
name: '新歌榜',
|
||||
bangid: '1',
|
||||
},
|
||||
{
|
||||
id: 'bdycb',
|
||||
name: '原创榜',
|
||||
bangid: '200',
|
||||
},
|
||||
{
|
||||
id: 'bdhyjqb',
|
||||
name: '华语榜',
|
||||
bangid: '20',
|
||||
},
|
||||
{
|
||||
id: 'bdomjqb',
|
||||
name: '欧美榜',
|
||||
bangid: '21',
|
||||
},
|
||||
{
|
||||
id: 'bdwugqb',
|
||||
name: '网络榜',
|
||||
bangid: '25',
|
||||
},
|
||||
{
|
||||
id: 'bdjdlgb',
|
||||
name: '老歌榜',
|
||||
bangid: '22',
|
||||
},
|
||||
{
|
||||
id: 'bdysjqb',
|
||||
name: '影视金曲榜',
|
||||
bangid: '24',
|
||||
},
|
||||
{
|
||||
id: 'bdqgdcb',
|
||||
name: '情歌对唱榜',
|
||||
bangid: '23',
|
||||
},
|
||||
{
|
||||
id: 'bdygb',
|
||||
name: '摇滚榜',
|
||||
bangid: '11',
|
||||
},
|
||||
],
|
||||
getUrl(id, p) {
|
||||
return `http://musicmini.qianqian.com/2018/static/bangdan/bangdanList_${id}_${p}.html`
|
||||
},
|
||||
regExps: {
|
||||
item: /data-song="({.+?})"/g,
|
||||
info: /{total[\s:]+"(\d+)", size[\s:]+"(\d+)", page[\s:]+"(\d+)"}/,
|
||||
},
|
||||
getData(url) {
|
||||
const requestObj = httpFetch(url)
|
||||
return requestObj.promise
|
||||
},
|
||||
filterData(rawList) {
|
||||
// console.log(rawList)
|
||||
return rawList.map(item => {
|
||||
const types = []
|
||||
const _types = {}
|
||||
let size = null
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
}
|
||||
if (item.biaoshi) {
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
}
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
// types.reverse()
|
||||
|
||||
return {
|
||||
singer: item.song_artist.replace(',', '、'),
|
||||
name: item.song_title,
|
||||
albumName: item.album_title,
|
||||
albumId: item.album_id,
|
||||
source: 'bd',
|
||||
interval: '',
|
||||
songmid: item.song_id,
|
||||
img: null,
|
||||
lrc: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
parseData(rawData) {
|
||||
// return rawData.map(item => JSON.parse(item.replace(this.regExps.item, '$1').replace(/"/g, '"').replace(/\\\//g, '/').replace(/(@s_1,w_)\d+(,h_)\d+/, '$1500$2500')))
|
||||
return rawData.map(item => JSON.parse(item.replace(this.regExps.item, '$1').replace(/"/g, '"').replace(/\\\//g, '/')))
|
||||
},
|
||||
async getBoards(retryNum = 0) {
|
||||
this.list = boardList
|
||||
return {
|
||||
list: boardList,
|
||||
source: 'bd',
|
||||
}
|
||||
},
|
||||
getList(bangid, page, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
return this.getData(this.getUrl(bangid, page)).then(({ body }) => {
|
||||
let result = body.match(this.regExps.item)
|
||||
if (!result) return this.getList(bangid, page, retryNum)
|
||||
let info = body.match(this.regExps.info)
|
||||
if (!info) return this.getList(bangid, page, retryNum)
|
||||
const list = this.filterData(this.parseData(result))
|
||||
this.limit = parseInt(info[2])
|
||||
return {
|
||||
total: parseInt(info[1]),
|
||||
list,
|
||||
limit: this.limit,
|
||||
page: parseInt(info[3]),
|
||||
source: 'bd',
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
export default {
|
||||
cache: {},
|
||||
getMusicInfo(songmid) {
|
||||
if (this.cache[songmid]) {
|
||||
return { promise: Promise.resolve(this.cache[songmid]) }
|
||||
}
|
||||
const requestObj = httpFetch(`https://musicapi.qianqian.com/v1/restserver/ting?method=baidu.ting.song.getSongLink&format=json&from=bmpc&version=1.0.0&version_d=11.1.6.0&songid=${songmid}&type=1&res=1&s_protocol=1&aac=2&project=tpass`)
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
// console.log(body)
|
||||
if (body.error_code == 22000) {
|
||||
this.cache[songmid] = body.result.songinfo
|
||||
return body.result.songinfo
|
||||
}
|
||||
return Promise.reject(new Error('获取音乐信息失败'))
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// import '../../polyfill/array.find'
|
||||
|
||||
import { httpFetch } from '../../request'
|
||||
import { formatPlayTime } from '../../index'
|
||||
// import { debug } from '../../utils/env'
|
||||
// import { formatSinger } from './util'
|
||||
|
||||
export default {
|
||||
limit: 30,
|
||||
total: 0,
|
||||
page: 0,
|
||||
allPage: 1,
|
||||
musicSearch(str, page, limit) {
|
||||
const searchRequest = httpFetch(`http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=5.6.5.6&method=baidu.ting.search.merge&format=json&query=${encodeURIComponent(str)}&page_no=${page}&page_size=${limit}&type=0&data_source=0&use_cluster=1`)
|
||||
return searchRequest.promise.then(({ body }) => body)
|
||||
},
|
||||
handleResult(rawData) {
|
||||
let ids = new Set()
|
||||
const list = []
|
||||
if (!rawData) return list
|
||||
rawData.forEach(item => {
|
||||
if (ids.has(item.song_id)) return
|
||||
ids.add(item.song_id)
|
||||
const types = []
|
||||
const _types = {}
|
||||
let size = null
|
||||
let itemTypes = item.all_rate.split(',')
|
||||
if (itemTypes.includes('128')) {
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (itemTypes.includes('320')) {
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (itemTypes.includes('flac')) {
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
// types.reverse()
|
||||
|
||||
list.push({
|
||||
singer: item.author.replace(',', '、'),
|
||||
name: item.title,
|
||||
albumName: item.album_title,
|
||||
albumId: item.album_id,
|
||||
source: 'bd',
|
||||
interval: formatPlayTime(parseInt(item.file_duration)),
|
||||
songmid: item.song_id,
|
||||
img: null,
|
||||
lrc: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
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 => {
|
||||
if (!result || result.error_code !== 22000) return this.search(str, page, limit, retryNum)
|
||||
let list = this.handleResult(result.result.song_info.song_list)
|
||||
|
||||
if (list == null) return this.search(str, page, limit, retryNum)
|
||||
|
||||
this.total = result.result.song_info.total
|
||||
this.page = page
|
||||
this.allPage = Math.ceil(this.total / limit)
|
||||
|
||||
return Promise.resolve({
|
||||
list,
|
||||
allPage: this.allPage,
|
||||
limit,
|
||||
total: this.total,
|
||||
source: 'bd',
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { formatPlayTime, toMD5 } from '../../index'
|
||||
import CryptoJS from 'crypto-js'
|
||||
|
||||
export default {
|
||||
_requestObj_tags: null,
|
||||
_requestObj_list: null,
|
||||
_requestObj_listRecommend: null,
|
||||
limit_list: 30,
|
||||
limit_song: 10000,
|
||||
successCode: 22000,
|
||||
sortList: [
|
||||
{
|
||||
name: '最热',
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
name: '最新',
|
||||
id: '0',
|
||||
},
|
||||
],
|
||||
regExps: {
|
||||
// http://music.taihe.com/songlist/566347741
|
||||
listDetailLink: /^.+\/songlist\/(\d+)(?:\?.*|&.*$|#.*$|$)/,
|
||||
},
|
||||
aesPassEncod(jsonData) {
|
||||
let timestamp = Math.floor(Date.now() / 1000)
|
||||
let privateKey = toMD5('baidu_taihe_music_secret_key' + timestamp).substr(8, 16)
|
||||
let key = CryptoJS.enc.Utf8.parse(privateKey)
|
||||
let iv = CryptoJS.enc.Utf8.parse(privateKey)
|
||||
let arrData = []
|
||||
let strData = ''
|
||||
for (let key in jsonData) arrData.push(key)
|
||||
arrData.sort()
|
||||
for (let i = 0; i < arrData.length; i++) {
|
||||
let key = arrData[i]
|
||||
strData +=
|
||||
(i === 0 ? '' : '&') + key + '=' + encodeURIComponent(jsonData[key])
|
||||
}
|
||||
let JsonFormatter = {
|
||||
stringify(cipherParams) {
|
||||
let jsonObj = {
|
||||
ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64),
|
||||
}
|
||||
if (cipherParams.iv) {
|
||||
jsonObj.iv = cipherParams.iv.toString()
|
||||
}
|
||||
if (cipherParams.salt) {
|
||||
jsonObj.s = cipherParams.salt.toString()
|
||||
}
|
||||
return jsonObj
|
||||
},
|
||||
parse(jsonStr) {
|
||||
let jsonObj = JSON.parse(jsonStr)
|
||||
let cipherParams = CryptoJS.lib.CipherParams.create({
|
||||
ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct),
|
||||
})
|
||||
if (jsonObj.iv) {
|
||||
cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv)
|
||||
}
|
||||
if (jsonObj.s) {
|
||||
cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s)
|
||||
}
|
||||
return cipherParams
|
||||
},
|
||||
}
|
||||
let encrypted = CryptoJS.AES.encrypt(strData, key, {
|
||||
iv,
|
||||
blockSize: 16,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
format: JsonFormatter,
|
||||
})
|
||||
let ciphertext = encrypted.toString().ct
|
||||
let sign = toMD5('baidu_taihe_music' + ciphertext + timestamp)
|
||||
let jsonRet = {
|
||||
timestamp,
|
||||
param: ciphertext,
|
||||
sign,
|
||||
}
|
||||
return jsonRet
|
||||
},
|
||||
createUrl(param, method) {
|
||||
let data = this.aesPassEncod(param)
|
||||
return `http://musicmini.qianqian.com/v1/restserver/ting?method=${method}&time=${Date.now()}×tamp=${data.timestamp}¶m=${data.param}&sign=${data.sign}`
|
||||
},
|
||||
getTagsUrl() {
|
||||
return this.createUrl({
|
||||
from: 'qianqianmini',
|
||||
type: 'diy',
|
||||
version: '10.1.8',
|
||||
}, 'baidu.ting.ugcdiy.getChannels')
|
||||
},
|
||||
getListUrl(sortType, tagName, page) {
|
||||
return this.createUrl({
|
||||
channelname: tagName || '全部',
|
||||
from: 'qianqianmini',
|
||||
offset: (page - 1) * this.limit_list,
|
||||
order_type: sortType,
|
||||
size: this.limit_list,
|
||||
version: '10.1.8',
|
||||
}, 'baidu.ting.ugcdiy.getChanneldiy')
|
||||
},
|
||||
getListDetailUrl(list_id, page) {
|
||||
return this.createUrl({
|
||||
list_id,
|
||||
offset: (page - 1) * this.limit_song,
|
||||
size: this.limit_song,
|
||||
withcount: '1',
|
||||
withsong: '1',
|
||||
}, 'baidu.ting.ugcdiy.getBaseInfo')
|
||||
},
|
||||
|
||||
// 获取标签
|
||||
getTags(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(this.getTagsUrl())
|
||||
return this._requestObj_tags.promise.then(({ body }) => {
|
||||
if (body.error_code !== this.successCode) return this.getTags(++tryNum)
|
||||
return {
|
||||
hotTag: this.filterInfoHotTag(body.result.hot),
|
||||
tags: this.filterTagInfo(body.result.tags),
|
||||
source: 'bd',
|
||||
}
|
||||
})
|
||||
},
|
||||
filterInfoHotTag(rawList) {
|
||||
return rawList.map(item => ({
|
||||
name: item,
|
||||
id: item,
|
||||
source: 'bd',
|
||||
}))
|
||||
},
|
||||
filterTagInfo(rawList) {
|
||||
return rawList.map(type => ({
|
||||
name: type.first,
|
||||
list: type.second.map(item => ({
|
||||
parent_id: type.first,
|
||||
parent_name: type.first,
|
||||
id: item,
|
||||
name: item,
|
||||
source: 'bd',
|
||||
})),
|
||||
}))
|
||||
},
|
||||
|
||||
// 获取列表数据
|
||||
getList(sortId, tagId, page, tryNum = 0) {
|
||||
if (this._requestObj_list) this._requestObj_list.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_list = httpFetch(this.getListUrl(sortId, tagId, page))
|
||||
return this._requestObj_list.promise.then(({ body }) => {
|
||||
if (body.error_code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
||||
return {
|
||||
list: this.filterList(body.diyInfo),
|
||||
total: body.nums,
|
||||
page,
|
||||
limit: this.limit_list,
|
||||
source: 'bd',
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 格式化播放数量
|
||||
* @param {*} num
|
||||
*/
|
||||
formatPlayCount(num) {
|
||||
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
|
||||
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
|
||||
return num
|
||||
},
|
||||
filterList(rawData) {
|
||||
return rawData.map(item => ({
|
||||
play_count: this.formatPlayCount(item.listen_num),
|
||||
id: item.list_id,
|
||||
author: item.username,
|
||||
name: item.title,
|
||||
// time: item.publish_time,
|
||||
img: item.list_pic_large || item.list_pic,
|
||||
grade: item.grade,
|
||||
desc: item.desc || item.tag,
|
||||
source: 'bd',
|
||||
}))
|
||||
},
|
||||
|
||||
// 获取歌曲列表内的音乐
|
||||
getListDetail(id, page, tryNum = 0) {
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
|
||||
if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
|
||||
|
||||
const requestObj_listDetail = httpFetch(this.getListDetailUrl(id, page))
|
||||
return requestObj_listDetail.promise.then(({ body }) => {
|
||||
if (body.error_code !== this.successCode) return this.getListDetail(id, page, ++tryNum)
|
||||
let listData = this.filterData(body.result.songlist)
|
||||
return {
|
||||
list: listData,
|
||||
page,
|
||||
limit: this.limit_song,
|
||||
total: body.result.song_num,
|
||||
source: 'bd',
|
||||
info: {
|
||||
name: body.result.info.list_title,
|
||||
img: body.result.info.list_pic,
|
||||
desc: body.result.info.list_desc,
|
||||
author: body.result.info.userinfo.username,
|
||||
play_count: this.formatPlayCount(body.result.listen_num),
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
filterData(rawList) {
|
||||
// console.log(rawList)
|
||||
return rawList.map(item => {
|
||||
const types = []
|
||||
const _types = {}
|
||||
let size = null
|
||||
let itemTypes = item.all_rate.split(',')
|
||||
if (itemTypes.includes('128')) {
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (itemTypes.includes('320')) {
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (itemTypes.includes('flac')) {
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
// types.reverse()
|
||||
|
||||
return {
|
||||
singer: item.author.replace(',', '、'),
|
||||
name: item.title,
|
||||
albumName: item.album_title,
|
||||
albumId: item.album_id,
|
||||
source: 'bd',
|
||||
interval: formatPlayTime(parseInt(item.file_duration)),
|
||||
songmid: item.song_id,
|
||||
img: item.pic_s500,
|
||||
lrc: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
// getList
|
||||
// getTags
|
||||
// getListDetail
|
||||
@@ -0,0 +1,140 @@
|
||||
import kw from './kw/index'
|
||||
import kg from './kg/index'
|
||||
import tx from './tx/index'
|
||||
import wy from './wy/index'
|
||||
import mg from './mg/index'
|
||||
import bd from './bd/index'
|
||||
import xm from './xm'
|
||||
import { supportQuality } from './api-source'
|
||||
|
||||
|
||||
const sources = {
|
||||
sources: [
|
||||
{
|
||||
name: '酷我音乐',
|
||||
id: 'kw',
|
||||
},
|
||||
{
|
||||
name: '酷狗音乐',
|
||||
id: 'kg',
|
||||
},
|
||||
{
|
||||
name: 'QQ音乐',
|
||||
id: 'tx',
|
||||
},
|
||||
{
|
||||
name: '网易音乐',
|
||||
id: 'wy',
|
||||
},
|
||||
{
|
||||
name: '咪咕音乐',
|
||||
id: 'mg',
|
||||
},
|
||||
{
|
||||
name: '虾米音乐',
|
||||
id: 'xm',
|
||||
},
|
||||
// {
|
||||
// name: '百度音乐',
|
||||
// id: 'bd',
|
||||
// },
|
||||
],
|
||||
kw,
|
||||
kg,
|
||||
tx,
|
||||
wy,
|
||||
mg,
|
||||
bd,
|
||||
xm,
|
||||
}
|
||||
export default {
|
||||
...sources,
|
||||
init() {
|
||||
const tasks = []
|
||||
for (let source of sources.sources) {
|
||||
let sm = sources[source.id]
|
||||
sm && sm.init && tasks.push(sm.init())
|
||||
}
|
||||
return Promise.all(tasks)
|
||||
},
|
||||
supportQuality,
|
||||
|
||||
async findMusic({ name, singer, albumName, interval, source: s }) {
|
||||
const tasks = []
|
||||
const singersRxp = /、|&|;|;|\/|,|,|\|/
|
||||
const sortSingle = singer => singersRxp.test(singer)
|
||||
? singer.split(singersRxp).sort((a, b) => a.localeCompare(b)).join('、')
|
||||
: singer
|
||||
const sortMusic = (arr, callback) => {
|
||||
const tempResult = []
|
||||
for (let i = arr.length - 1; i > -1; i--) {
|
||||
const item = arr[i]
|
||||
if (callback(item)) {
|
||||
delete item.sortedSinger
|
||||
delete item.lowerCaseName
|
||||
delete item.lowerCaseAlbumName
|
||||
tempResult.push(item)
|
||||
arr.splice(i, 1)
|
||||
}
|
||||
}
|
||||
tempResult.reverse()
|
||||
return tempResult
|
||||
}
|
||||
const trimStr = str => typeof str == 'string' ? str.trim() : str
|
||||
const filterStr = str => typeof str == 'string' ? str.replace(/\s|'|\.|,|,|&|"|、|\(|\)|(|)|`|~|-|<|>|\||\/|\]|\[/g, '') : str
|
||||
const musicName = trimStr(name)
|
||||
const sortedSinger = filterStr(String(sortSingle(singer)).toLowerCase())
|
||||
const lowerCaseName = filterStr(String(musicName).toLowerCase())
|
||||
const lowerCaseAlbumName = filterStr(String(albumName).toLowerCase())
|
||||
const excludeSource = ['xm']
|
||||
for (const source of sources.sources) {
|
||||
if (!sources[source.id].musicSearch || source.id == s || excludeSource.includes(source.id)) continue
|
||||
|
||||
tasks.push(sources[source.id].musicSearch.search(`${musicName} ${singer || ''}`.trim(), 1, 25).then(res => {
|
||||
for (const item of res.list) {
|
||||
item.name = trimStr(item.name)
|
||||
item.sortedSinger = filterStr(String(sortSingle(item.singer)).toLowerCase())
|
||||
item.lowerCaseName = filterStr(String(item.name ?? '').toLowerCase())
|
||||
item.lowerCaseAlbumName = filterStr(String(item.albumName ?? '').toLowerCase())
|
||||
// console.log(lowerCaseName, item.lowerCaseName, item.source)
|
||||
if (
|
||||
(
|
||||
item.sortedSinger == sortedSinger && item.lowerCaseName == lowerCaseName
|
||||
) ||
|
||||
(
|
||||
(interval ? item.interval == interval : true) && item.lowerCaseName == lowerCaseName &&
|
||||
(item.sortedSinger.includes(sortedSinger) || sortedSinger.includes(item.sortedSinger))
|
||||
) ||
|
||||
(
|
||||
item.lowerCaseName == lowerCaseName && (lowerCaseAlbumName ? item.lowerCaseAlbumName == lowerCaseAlbumName : true) &&
|
||||
(interval ? item.interval == interval : true)
|
||||
) ||
|
||||
(
|
||||
item.lowerCaseName == lowerCaseName && (lowerCaseAlbumName ? item.lowerCaseAlbumName == lowerCaseAlbumName : true) &&
|
||||
(item.sortedSinger.includes(sortedSinger) || sortedSinger.includes(item.sortedSinger))
|
||||
)
|
||||
) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return null
|
||||
}).catch(_ => null))
|
||||
}
|
||||
const result = (await Promise.all(tasks)).filter(s => s)
|
||||
const newResult = []
|
||||
if (result.length) {
|
||||
newResult.push(...sortMusic(result, item => item.sortedSinger == sortedSinger && item.lowerCaseName == lowerCaseName && item.interval == interval))
|
||||
newResult.push(...sortMusic(result, item => item.lowerCaseName == lowerCaseName && item.sortedSinger == sortedSinger && item.lowerCaseAlbumName == lowerCaseAlbumName))
|
||||
newResult.push(...sortMusic(result, item => item.sortedSinger == sortedSinger && item.lowerCaseName == lowerCaseName))
|
||||
newResult.push(...sortMusic(result, item => item.sortedSinger == sortedSinger && item.interval == interval))
|
||||
for (const item of result) {
|
||||
delete item.sortedSinger
|
||||
delete item.lowerCaseName
|
||||
delete item.lowerCaseAlbumName
|
||||
}
|
||||
newResult.push(...result)
|
||||
}
|
||||
// console.log(newResult)
|
||||
return newResult
|
||||
},
|
||||
}
|
||||
@@ -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/kg/${songInfo._types[type].hash}/${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://ts.tempmusics.tk/pic/kg/${songInfo.hash}`, {
|
||||
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://ts.tempmusics.tk/lrc/kg/${songInfo.hash}`, {
|
||||
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
|
||||
@@ -0,0 +1,82 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName, dateFormat2 } from '../../index'
|
||||
|
||||
export default {
|
||||
_requestObj: null,
|
||||
_requestObj2: null,
|
||||
async getComment({ hash }, page = 1, limit = 20) {
|
||||
if (this._requestObj) this._requestObj.cancelHttp()
|
||||
|
||||
const _requestObj = httpFetch(`http://comment.service.kugou.com/index.php?r=commentsv2/getCommentWithLike&code=fc4be23b4e972707f36b8a828a93ba8a&extdata=${hash}&p=${page}&pagesize=${limit}&ver=1.01&clientver=8373&appid=1001&kugouid=687373022`, {
|
||||
headers: {
|
||||
'User-Agent': 'Android712-AndroidPhone-8983-18-0-COMMENT-wifi',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
// console.log(body)
|
||||
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取评论失败')
|
||||
return { source: 'kg', comments: this.filterComment(body.list || []), total: body.count, page, limit, maxPage: Math.ceil(body.count / limit) || 1 }
|
||||
},
|
||||
async getHotComment({ hash, songmid }, page = 1, limit = 100) {
|
||||
// console.log(songmid)
|
||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
||||
|
||||
const _requestObj2 = httpFetch(`http://comment.service.kugou.com/index.php?r=commentsv2/getCommentWithLike&code=fc4be23b4e972707f36b8a828a93ba8a&extdata=${hash}&p=${page}&pagesize=${limit}&ver=1.01&clientver=8373&appid=1001&kugouid=687373022`, {
|
||||
headers: {
|
||||
'User-Agent': 'Android712-AndroidPhone-8983-18-0-COMMENT-wifi',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj2.promise
|
||||
// console.log(body)
|
||||
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取热门评论失败')
|
||||
const total = body.weightList?.length ?? 0
|
||||
return { source: 'kg', comments: this.filterComment(body.weightList || []), total, page, limit, maxPage: 1 }
|
||||
},
|
||||
async getReplyComment({ songmid, audioId }, replyId, page = 1, limit = 100) {
|
||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
||||
|
||||
songmid = songmid.length == 32 // 修复歌曲ID存储变更导致图片获取失败的问题
|
||||
? audioId.split('_')[0]
|
||||
: songmid
|
||||
|
||||
const _requestObj2 = httpFetch(`http://comment.service.kugou.com/index.php?r=commentsv2/getReplyWithLike&code=fc4be23b4e972707f36b8a828a93ba8a&p=${page}&pagesize=${limit}&ver=1.01&clientver=8373&kugouid=687373022&appid=1001&childrenid=${songmid}&tid=${replyId}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Android712-AndroidPhone-8983-18-0-COMMENT-wifi',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj2.promise
|
||||
// console.log(body)
|
||||
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取回复评论失败')
|
||||
return { source: 'kg', comments: this.filterComment(body.list || []) }
|
||||
},
|
||||
filterComment(rawList) {
|
||||
return rawList.map(item => {
|
||||
let data = {
|
||||
id: item.id,
|
||||
text: decodeName(item.content || '').split('\n'),
|
||||
time: item.addtime,
|
||||
timeStr: dateFormat2(new Date(item.addtime).getTime()),
|
||||
userName: item.user_name,
|
||||
avatar: item.user_pic,
|
||||
userId: item.user_id,
|
||||
likedCount: item.like.likenum,
|
||||
replyNum: item.reply_num,
|
||||
reply: [],
|
||||
}
|
||||
|
||||
return item.pcontent
|
||||
? {
|
||||
id: item.id,
|
||||
text: decodeName(item.pcontent).split('\n'),
|
||||
time: null,
|
||||
userName: item.puser,
|
||||
avatar: null,
|
||||
userId: item.puser_id,
|
||||
likedCount: null,
|
||||
replyNum: null,
|
||||
reply: [data],
|
||||
}
|
||||
: data
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName } from '../../index'
|
||||
|
||||
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('http://gateway.kugou.com/api/v3/search/hot_tab?signature=ee44edb9d7155821412d220bcaf509dd&appid=1005&clientver=10026&plat=0', {
|
||||
method: 'get',
|
||||
headers: {
|
||||
dfid: '1ssiv93oVqMp27cirf2CvoF1',
|
||||
mid: '156798703528610303473757548878786007104',
|
||||
clienttime: 1584257267,
|
||||
'x-router': 'msearch.kugou.com',
|
||||
'user-agent': 'Android9-AndroidPhone-10020-130-0-searchrecommendprotocol-wifi',
|
||||
'kg-rc': 1,
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
if (statusCode != 200 || body.errcode !== 0) throw new Error('获取热搜词失败')
|
||||
// console.log(body, statusCode)
|
||||
return { source: 'kg', list: this.filterList(body.data.list) }
|
||||
},
|
||||
filterList(rawList) {
|
||||
const list = []
|
||||
rawList.forEach(item => {
|
||||
item.keywords.map(k => list.push(decodeName(k.keyword)))
|
||||
})
|
||||
return list
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import leaderboard from './leaderboard'
|
||||
import { apis } from '../api-source'
|
||||
import songList from './songList'
|
||||
import musicSearch from './musicSearch'
|
||||
import pic from './pic'
|
||||
import lyric from './lyric'
|
||||
import hotSearch from './hotSearch'
|
||||
import comment from './comment'
|
||||
|
||||
const kg = {
|
||||
leaderboard,
|
||||
songList,
|
||||
musicSearch,
|
||||
hotSearch,
|
||||
comment,
|
||||
getMusicUrl(songInfo, type) {
|
||||
return apis('kg').getMusicUrl(songInfo, type)
|
||||
},
|
||||
getLyric(songInfo) {
|
||||
return lyric.getLyric(songInfo)
|
||||
},
|
||||
// getLyric(songInfo) {
|
||||
// return apis('kg').getLyric(songInfo)
|
||||
// },
|
||||
getPic(songInfo) {
|
||||
return pic.getPic(songInfo)
|
||||
},
|
||||
getMusicDetailPageUrl(songInfo) {
|
||||
return `https://www.kugou.com/song/#hash=${songInfo.hash}&album_id=${songInfo.albumId}`
|
||||
},
|
||||
// getPic(songInfo) {
|
||||
// return apis('kg').getPic(songInfo)
|
||||
// },
|
||||
}
|
||||
|
||||
export default kg
|
||||
@@ -0,0 +1,198 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../../index'
|
||||
|
||||
let boardList = [{ id: 'kg__8888', name: '酷狗TOP500', bangid: '8888' }, { id: 'kg__6666', name: '酷狗飙升榜', bangid: '6666' }, { id: 'kg__37361', name: '酷狗雷达榜', bangid: '37361' }, { id: 'kg__23784', name: '网络红歌榜', bangid: '23784' }, { id: 'kg__24971', name: 'DJ热歌榜', bangid: '24971' }, { id: 'kg__35811', name: '会员专享热歌榜', bangid: '35811' }, { id: 'kg__31308', name: '华语新歌榜', bangid: '31308' }, { id: 'kg__31310', name: '欧美新歌榜', bangid: '31310' }, { id: 'kg__31311', name: '韩国新歌榜', bangid: '31311' }, { id: 'kg__31312', name: '日本新歌榜', bangid: '31312' }, { id: 'kg__31313', name: '粤语新歌榜', bangid: '31313' }, { id: 'kg__33162', name: 'ACG新歌榜', bangid: '33162' }, { id: 'kg__21101', name: '酷狗分享榜', bangid: '21101' }, { id: 'kg__30972', name: '腾讯音乐人原创榜', bangid: '30972' }, { id: 'kg__22603', name: '5sing音乐榜', bangid: '22603' }, { id: 'kg__33160', name: '电音热歌榜', bangid: '33160' }, { id: 'kg__21335', name: '繁星音乐榜', bangid: '21335' }, { id: 'kg__33161', name: '古风新歌榜', bangid: '33161' }, { id: 'kg__33163', name: '影视金曲榜', bangid: '33163' }, { id: 'kg__33166', name: '欧美金曲榜', bangid: '33166' }, { id: 'kg__33165', name: '粤语金曲榜', bangid: '33165' }, { id: 'kg__36107', name: '小语种热歌榜', bangid: '36107' }, { id: 'kg__4681', name: '美国BillBoard榜', bangid: '4681' }, { id: 'kg__4680', name: '英国单曲榜', bangid: '4680' }, { id: 'kg__4673', name: '日本公信榜', bangid: '4673' }, { id: 'kg__38623', name: '韩国Melon音乐榜', bangid: '38623' }, { id: 'kg__42807', name: 'joox本地热歌榜', bangid: '42807' }, { id: 'kg__42808', name: '台湾KKBOX风云榜', bangid: '42808' }]
|
||||
|
||||
export default {
|
||||
list: [
|
||||
{
|
||||
id: 'kgtop500',
|
||||
name: '酷狗TOP500',
|
||||
bangid: '8888',
|
||||
},
|
||||
{
|
||||
id: 'kgwlhgb',
|
||||
name: '网络榜',
|
||||
bangid: '23784',
|
||||
},
|
||||
{
|
||||
id: 'kgbsb',
|
||||
name: '飙升榜',
|
||||
bangid: '6666',
|
||||
},
|
||||
{
|
||||
id: 'kgfxb',
|
||||
name: '分享榜',
|
||||
bangid: '21101',
|
||||
},
|
||||
{
|
||||
id: 'kgcyyb',
|
||||
name: '纯音乐榜',
|
||||
bangid: '33164',
|
||||
},
|
||||
{
|
||||
id: 'kggfjqb',
|
||||
name: '古风榜',
|
||||
bangid: '33161',
|
||||
},
|
||||
{
|
||||
id: 'kgyyjqb',
|
||||
name: '粤语榜',
|
||||
bangid: '33165',
|
||||
},
|
||||
{
|
||||
id: 'kgomjqb',
|
||||
name: '欧美榜',
|
||||
bangid: '33166',
|
||||
},
|
||||
{
|
||||
id: 'kgdyrgb',
|
||||
name: '电音榜',
|
||||
bangid: '33160',
|
||||
},
|
||||
{
|
||||
id: 'kgjdrgb',
|
||||
name: 'DJ热歌榜',
|
||||
bangid: '24971',
|
||||
},
|
||||
{
|
||||
id: 'kghyxgb',
|
||||
name: '华语新歌榜',
|
||||
bangid: '31308',
|
||||
},
|
||||
],
|
||||
getUrl(p, id) {
|
||||
return `http://www2.kugou.kugou.com/yueku/v9/rank/home/${p}-${id}.html`
|
||||
},
|
||||
regExps: {
|
||||
total: /total: '(\d+)',/,
|
||||
page: /page: '(\d+)',/,
|
||||
limit: /pagesize: '(\d+)',/,
|
||||
listData: /global\.features = (\[.+\]);/,
|
||||
},
|
||||
_requestBoardsObj: null,
|
||||
getBoardsData() {
|
||||
if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp()
|
||||
this._requestBoardsObj = httpFetch('http://mobilecdnbj.kugou.com/api/v3/rank/list?version=9108&plat=0&showtype=2&parentid=0&apiver=6&area_code=1&withsong=1')
|
||||
return this._requestBoardsObj.promise
|
||||
},
|
||||
getData(url) {
|
||||
const requestDataObj = httpFetch(url)
|
||||
return requestDataObj.promise
|
||||
},
|
||||
filterData(rawList) {
|
||||
// console.log(rawList)
|
||||
return rawList.map(item => {
|
||||
const types = []
|
||||
const _types = {}
|
||||
if (item.filesize !== 0) {
|
||||
let size = sizeFormate(item.filesize)
|
||||
types.push({ type: '128k', size, hash: item.hash })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
hash: item.hash,
|
||||
}
|
||||
}
|
||||
if (item.filesize_320 !== 0) {
|
||||
let size = sizeFormate(item.filesize_320)
|
||||
types.push({ type: '320k', size, hash: item.hash_320 })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: item.hash_320,
|
||||
}
|
||||
}
|
||||
if (item.filesize_ape !== 0) {
|
||||
let size = sizeFormate(item.filesize_ape)
|
||||
types.push({ type: 'ape', size, hash: item.hash_ape })
|
||||
_types.ape = {
|
||||
size,
|
||||
hash: item.hash_ape,
|
||||
}
|
||||
}
|
||||
if (item.filesize_flac !== 0) {
|
||||
let size = sizeFormate(item.filesize_flac)
|
||||
types.push({ type: 'flac', size, hash: item.hash_flac })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: item.hash_flac,
|
||||
}
|
||||
}
|
||||
return {
|
||||
singer: decodeName(item.singername),
|
||||
name: decodeName(item.songname),
|
||||
albumName: decodeName(item.album_name),
|
||||
albumId: item.album_id,
|
||||
songmid: item.audio_id,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(item.duration / 1000),
|
||||
img: null,
|
||||
lrc: null,
|
||||
hash: item.HASH,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
filterBoardsData(rawList) {
|
||||
// console.log(rawList)
|
||||
let list = []
|
||||
for (const board of rawList) {
|
||||
if (board.isvol != 1) continue
|
||||
list.push({
|
||||
id: 'kg__' + board.rankid,
|
||||
name: board.rankname,
|
||||
bangid: String(board.rankid),
|
||||
})
|
||||
}
|
||||
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.errcode !== 0) return this.getBoards(retryNum)
|
||||
// const list = this.filterBoardsData(response.body.data.info)
|
||||
// // console.log(list)
|
||||
// this.list = list
|
||||
// return {
|
||||
// list,
|
||||
// source: 'kg',
|
||||
// }
|
||||
this.list = boardList
|
||||
return {
|
||||
list: boardList,
|
||||
source: 'kg',
|
||||
}
|
||||
},
|
||||
getList(bangid, page) {
|
||||
return this.getData(this.getUrl(page, bangid)).then(({ body: html }) => {
|
||||
let total = html.match(this.regExps.total)
|
||||
if (total) total = parseInt(RegExp.$1)
|
||||
page = html.match(this.regExps.page)
|
||||
if (page) page = parseInt(RegExp.$1)
|
||||
let limit = html.match(this.regExps.limit)
|
||||
if (limit) limit = parseInt(RegExp.$1)
|
||||
let listData = html.match(this.regExps.listData)
|
||||
if (listData) listData = this.filterData(JSON.parse(RegExp.$1))
|
||||
return {
|
||||
total,
|
||||
list: listData,
|
||||
limit,
|
||||
page,
|
||||
source: 'kg',
|
||||
}
|
||||
})
|
||||
},
|
||||
getDetailPageUrl(id) {
|
||||
if (typeof id == 'string') id = id.replace('kg__', '')
|
||||
return `https://www.kugou.com/yy/rank/home/1-${id}.html`
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeLyric } from './util'
|
||||
import { decodeName } from '../../index'
|
||||
|
||||
const headExp = /^.*\[id:\$\w+\]\n/
|
||||
|
||||
const parseLyric = str => {
|
||||
str = str.replace(/\r/g, '')
|
||||
if (headExp.test(str)) str = str.replace(headExp, '')
|
||||
let trans = str.match(/\[language:([\w=\\/+]+)\]/)
|
||||
let lyric
|
||||
let rlyric
|
||||
let tlyric
|
||||
if (trans) {
|
||||
str = str.replace(/\[language:[\w=\\/+]+\]\n/, '')
|
||||
let json = JSON.parse(Buffer.from(trans[1], 'base64').toString())
|
||||
for (const item of json.content) {
|
||||
switch (item.type) {
|
||||
case 0:
|
||||
rlyric = item.lyricContent
|
||||
break
|
||||
case 1:
|
||||
tlyric = item.lyricContent
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
let i = 0
|
||||
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}`
|
||||
if (rlyric) rlyric[i] = `[${time}]${rlyric[i]?.join('') ?? ''}`
|
||||
if (tlyric) tlyric[i] = `[${time}]${tlyric[i]?.join('') ?? ''}`
|
||||
i++
|
||||
return str.replace(result[1], time)
|
||||
})
|
||||
rlyric = rlyric ? rlyric.join('\n') : ''
|
||||
tlyric = tlyric ? tlyric.join('\n') : ''
|
||||
lxlyric = lxlyric.replace(/<(\d+,\d+),\d+>/g, '<$1>')
|
||||
lxlyric = decodeName(lxlyric)
|
||||
lyric = lxlyric.replace(/<\d+,\d+>/g, '')
|
||||
rlyric = decodeName(rlyric)
|
||||
tlyric = decodeName(tlyric)
|
||||
return {
|
||||
lyric,
|
||||
tlyric,
|
||||
rlyric,
|
||||
lxlyric,
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getIntv(interval) {
|
||||
let intvArr = interval.split(':')
|
||||
let intv = 0
|
||||
let unit = 1
|
||||
while (intvArr.length) {
|
||||
intv += (intvArr.pop()) * unit
|
||||
unit *= 60
|
||||
}
|
||||
return parseInt(intv)
|
||||
},
|
||||
// getLyric(songInfo, tryNum = 0) {
|
||||
// let requestObj = httpFetch(`http://m.kugou.com/app/i/krc.php?cmd=100&keyword=${encodeURIComponent(songInfo.name)}&hash=${songInfo.hash}&timelength=${songInfo._interval || this.getIntv(songInfo.interval)}&d=0.38664927426725626`, {
|
||||
// headers: {
|
||||
// 'KG-RC': 1,
|
||||
// 'KG-THash': 'expand_search_manager.cpp:852736169:451',
|
||||
// 'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
|
||||
// },
|
||||
// })
|
||||
// requestObj.promise = requestObj.promise.then(({ body, statusCode }) => {
|
||||
// if (statusCode !== 200) {
|
||||
// if (tryNum > 5) return Promise.reject('歌词获取失败')
|
||||
// let tryRequestObj = this.getLyric(songInfo, ++tryNum)
|
||||
// requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||
// return tryRequestObj.promise
|
||||
// }
|
||||
// return {
|
||||
// lyric: body,
|
||||
// tlyric: '',
|
||||
// }
|
||||
// })
|
||||
// return requestObj
|
||||
// },
|
||||
searchLyric(name, hash, time, tryNum = 0) {
|
||||
let requestObj = httpFetch(`http://lyrics.kugou.com/search?ver=1&man=yes&client=pc&keyword=${encodeURIComponent(name)}&hash=${hash}&timelength=${time}`, {
|
||||
headers: {
|
||||
'KG-RC': 1,
|
||||
'KG-THash': 'expand_search_manager.cpp:852736169:451',
|
||||
'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
|
||||
},
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body, statusCode }) => {
|
||||
if (statusCode !== 200) {
|
||||
if (tryNum > 5) return Promise.reject('歌词获取失败')
|
||||
let tryRequestObj = this.searchLyric(name, hash, time, ++tryNum)
|
||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||
return tryRequestObj.promise
|
||||
}
|
||||
if (body.candidates.length) {
|
||||
let info = body.candidates[0]
|
||||
return { id: info.id, accessKey: info.accesskey }
|
||||
}
|
||||
return null
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
getLyricDownload(id, accessKey, tryNum = 0) {
|
||||
let requestObj = httpFetch(`http://lyrics.kugou.com/download?ver=1&client=pc&id=${id}&accesskey=${accessKey}&fmt=krc&charset=utf8`, {
|
||||
headers: {
|
||||
'KG-RC': 1,
|
||||
'KG-THash': 'expand_search_manager.cpp:852736169:451',
|
||||
'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
|
||||
},
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body, statusCode }) => {
|
||||
if (statusCode !== 200) {
|
||||
if (tryNum > 5) return Promise.reject('歌词获取失败')
|
||||
let tryRequestObj = this.getLyric(id, accessKey, ++tryNum)
|
||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||
return tryRequestObj.promise
|
||||
}
|
||||
|
||||
return decodeLyric(body.content).then(result => parseLyric(result))
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
getLyric(songInfo, tryNum = 0) {
|
||||
let requestObj = this.searchLyric(songInfo.name, songInfo.hash, songInfo._interval || this.getIntv(songInfo.interval))
|
||||
|
||||
requestObj.promise = requestObj.promise.then(result => {
|
||||
if (!result) return Promise.reject(new Error('Get lyric failed'))
|
||||
|
||||
let requestObj2 = this.getLyricDownload(result.id, result.accessKey)
|
||||
|
||||
requestObj.cancelHttp = requestObj2.cancelHttp.bind(requestObj2)
|
||||
|
||||
return requestObj2.promise
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// import '../../polyfill/array.find'
|
||||
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../../index'
|
||||
// import { debug } from '../../utils/env'
|
||||
// import { formatSinger } from './util'
|
||||
|
||||
export default {
|
||||
limit: 30,
|
||||
total: 0,
|
||||
page: 0,
|
||||
allPage: 1,
|
||||
musicSearch(str, page, limit) {
|
||||
const searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
|
||||
return searchRequest.promise.then(({ body }) => body)
|
||||
},
|
||||
filterData(rawData) {
|
||||
const types = []
|
||||
const _types = {}
|
||||
if (rawData.filesize !== 0) {
|
||||
let size = sizeFormate(rawData.filesize)
|
||||
types.push({ type: '128k', size, hash: rawData.hash })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
hash: rawData.hash,
|
||||
}
|
||||
}
|
||||
if (rawData['320filesize'] !== 0) {
|
||||
let size = sizeFormate(rawData['320filesize'])
|
||||
types.push({ type: '320k', size, hash: rawData['320hash'] })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: rawData['320hash'],
|
||||
}
|
||||
}
|
||||
if (rawData.sqfilesize !== 0) {
|
||||
let size = sizeFormate(rawData.sqfilesize)
|
||||
types.push({ type: 'flac', size, hash: rawData.sqhash })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: rawData.sqhash,
|
||||
}
|
||||
}
|
||||
return {
|
||||
singer: decodeName(rawData.singername),
|
||||
name: decodeName(rawData.songname),
|
||||
albumName: decodeName(rawData.album_name),
|
||||
albumId: rawData.album_id,
|
||||
songmid: rawData.audio_id,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(rawData.duration),
|
||||
_interval: rawData.duration,
|
||||
img: null,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
hash: rawData.hash,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
},
|
||||
handleResult(rawData) {
|
||||
// console.log(rawData)
|
||||
let ids = new Set()
|
||||
const list = []
|
||||
rawData.forEach(item => {
|
||||
const key = item.audio_id + item.hash
|
||||
if (ids.has(key)) return
|
||||
ids.add(key)
|
||||
list.push(this.filterData(item))
|
||||
for (const childItem of item.group) {
|
||||
const key = item.audio_id + item.hash
|
||||
if (ids.has(key)) return
|
||||
ids.add(key)
|
||||
list.push(this.filterData(childItem))
|
||||
}
|
||||
})
|
||||
return list
|
||||
},
|
||||
search(str, page = 1, limit, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
if (limit == null) limit = this.limit
|
||||
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
|
||||
return this.musicSearch(str, page, limit).then(result => {
|
||||
if (!result || result.errcode !== 0) return this.search(str, page, limit, retryNum)
|
||||
let list = this.handleResult(result.data.info)
|
||||
|
||||
if (list == null) return this.search(str, page, limit, retryNum)
|
||||
|
||||
this.total = result.data.total
|
||||
this.page = page
|
||||
this.allPage = Math.ceil(this.total / limit)
|
||||
|
||||
return Promise.resolve({
|
||||
list,
|
||||
allPage: this.allPage,
|
||||
limit,
|
||||
total: this.total,
|
||||
source: 'kg',
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
export default {
|
||||
getPic(songInfo) {
|
||||
const requestObj = httpFetch(
|
||||
'http://media.store.kugou.com/v1/get_res_privilege',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'KG-RC': 1,
|
||||
'KG-THash': 'expand_search_manager.cpp:852736169:451',
|
||||
'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
|
||||
},
|
||||
body: {
|
||||
appid: 1001,
|
||||
area_code: '1',
|
||||
behavior: 'play',
|
||||
clientver: '9020',
|
||||
need_hash_offset: 1,
|
||||
relate: 1,
|
||||
resource: [
|
||||
{
|
||||
album_audio_id:
|
||||
songInfo.songmid.length == 32 // 修复歌曲ID存储变更导致图片获取失败的问题
|
||||
? songInfo.audioId.split('_')[0]
|
||||
: songInfo.songmid,
|
||||
album_id: songInfo.albumId,
|
||||
hash: songInfo.hash,
|
||||
id: 0,
|
||||
name: `${songInfo.singer} - ${songInfo.name}.mp3`,
|
||||
type: 'audio',
|
||||
},
|
||||
],
|
||||
token: '',
|
||||
userid: 2626431536,
|
||||
vip: 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
if (body.error_code !== 0) return Promise.reject('图片获取失败')
|
||||
let info = body.data[0].info
|
||||
const img = info.imgsize ? info.image.replace('{size}', info.imgsize[0]) : info.image
|
||||
if (!img) return Promise.reject('Pic get failed')
|
||||
return img
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../../index'
|
||||
import { toMD5 } from '../utils'
|
||||
import infSign from './vendors/infSign.min'
|
||||
|
||||
const handleSignature = (id, page, limit) => new Promise((resolve, reject) => {
|
||||
infSign({ appid: 1058, type: 0, module: 'playlist', page, pagesize: limit, specialid: id }, null, {
|
||||
useH5: !0,
|
||||
isCDN: !0,
|
||||
callback(i) {
|
||||
resolve(i.signature)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
export default {
|
||||
_requestObj_tags: null,
|
||||
_requestObj_listInfo: null,
|
||||
_requestObj_list: null,
|
||||
_requestObj_listRecommend: null,
|
||||
listDetailLimit: 10000,
|
||||
currentTagInfo: {
|
||||
id: undefined,
|
||||
info: undefined,
|
||||
},
|
||||
sortList: [
|
||||
{
|
||||
name: '推荐',
|
||||
id: '5',
|
||||
},
|
||||
{
|
||||
name: '最热',
|
||||
id: '6',
|
||||
},
|
||||
{
|
||||
name: '最新',
|
||||
id: '7',
|
||||
},
|
||||
{
|
||||
name: '热藏',
|
||||
id: '3',
|
||||
},
|
||||
{
|
||||
name: '飙升',
|
||||
id: '8',
|
||||
},
|
||||
],
|
||||
cache: new Map(),
|
||||
regExps: {
|
||||
listData: /global\.data = (\[.+\]);/,
|
||||
listInfo: /global = {[\s\S]+?name: "(.+)"[\s\S]+?pic: "(.+)"[\s\S]+?};/,
|
||||
// https://www.kugou.com/yy/special/single/1067062.html
|
||||
listDetailLink: /^.+\/(\d+)\.html(?:\?.*|&.*$|#.*$|$)/,
|
||||
},
|
||||
getInfoUrl(tagId) {
|
||||
return tagId
|
||||
? `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&cdn=cdn&t=5&c=${tagId}`
|
||||
: 'http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&'
|
||||
},
|
||||
getSongListUrl(sortId, tagId, page) {
|
||||
if (tagId == null) tagId = ''
|
||||
return `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_ajax=1&cdn=cdn&t=${sortId}&c=${tagId}&p=${page}`
|
||||
},
|
||||
getSongListDetailUrl(id) {
|
||||
return `http://www2.kugou.kugou.com/yueku/v9/special/single/${id}-5-9999.html`
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化播放数量
|
||||
* @param {*} num
|
||||
*/
|
||||
formatPlayCount(num) {
|
||||
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
|
||||
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
|
||||
return num
|
||||
},
|
||||
filterInfoHotTag(rawData) {
|
||||
const result = []
|
||||
if (rawData.status !== 1) return result
|
||||
for (const key of Object.keys(rawData.data)) {
|
||||
let tag = rawData.data[key]
|
||||
result.push({
|
||||
id: tag.special_id,
|
||||
name: tag.special_name,
|
||||
source: 'kg',
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
filterTagInfo(rawData) {
|
||||
const result = []
|
||||
for (const name of Object.keys(rawData)) {
|
||||
result.push({
|
||||
name,
|
||||
list: rawData[name].data.map(tag => ({
|
||||
parent_id: tag.parent_id,
|
||||
parent_name: tag.pname,
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
source: 'kg',
|
||||
})),
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
getSongList(sortId, tagId, page, tryNum = 0) {
|
||||
if (this._requestObj_list) this._requestObj_list.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_list = httpFetch(
|
||||
this.getSongListUrl(sortId, tagId, page),
|
||||
)
|
||||
return this._requestObj_list.promise.then(({ body }) => {
|
||||
if (!body || body.status !== 1) return this.getSongList(sortId, tagId, page, ++tryNum)
|
||||
return this.filterList(body.special_db)
|
||||
})
|
||||
},
|
||||
getSongListRecommend(tryNum = 0) {
|
||||
if (this._requestObj_listRecommend) this._requestObj_listRecommend.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_listRecommend = httpFetch(
|
||||
'http://everydayrec.service.kugou.com/guess_special_recommend',
|
||||
{
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent': 'KuGou2012-8275-web_browser_event_handler',
|
||||
},
|
||||
body: {
|
||||
appid: 1001,
|
||||
clienttime: 1566798337219,
|
||||
clientver: 8275,
|
||||
key: 'f1f93580115bb106680d2375f8032d96',
|
||||
mid: '21511157a05844bd085308bc76ef3343',
|
||||
platform: 'pc',
|
||||
userid: '262643156',
|
||||
return_min: 6,
|
||||
return_max: 15,
|
||||
},
|
||||
},
|
||||
)
|
||||
return this._requestObj_listRecommend.promise.then(({ body }) => {
|
||||
if (body.status !== 1) return this.getSongListRecommend(++tryNum)
|
||||
return this.filterList(body.data.special_list)
|
||||
})
|
||||
},
|
||||
filterList(rawData) {
|
||||
return rawData.map(item => ({
|
||||
play_count: item.total_play_count || this.formatPlayCount(item.play_count),
|
||||
id: 'id_' + item.specialid,
|
||||
author: item.nickname,
|
||||
name: item.specialname,
|
||||
time: item.publish_time || item.publishtime,
|
||||
img: item.img || item.imgurl,
|
||||
total: item.songcount,
|
||||
grade: item.grade,
|
||||
desc: item.intro,
|
||||
source: 'kg',
|
||||
}))
|
||||
},
|
||||
|
||||
async createHttp(url, options, retryNum = 0) {
|
||||
if (retryNum > 2) throw new Error('try max num')
|
||||
let result
|
||||
try {
|
||||
result = await httpFetch(url, options).promise
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return this.createHttp(url, options, ++retryNum)
|
||||
}
|
||||
// console.log(result.statusCode, result.body)
|
||||
if (result.statusCode !== 200 ||
|
||||
(
|
||||
(result.body.error_code !== undefined
|
||||
? result.body.error_code
|
||||
: result.body.errcode !== undefined
|
||||
? result.body.errcode
|
||||
: result.body.err_code
|
||||
) !== 0)
|
||||
) return this.createHttp(url, options, ++retryNum)
|
||||
if (result.body.data) return result.body.data
|
||||
if (Array.isArray(result.body.info)) return result.body
|
||||
return result.body.info
|
||||
},
|
||||
|
||||
createTask(hashs) {
|
||||
let data = {
|
||||
appid: 1001,
|
||||
clienttime: 639437935,
|
||||
clientver: 9020,
|
||||
fields:
|
||||
'album_info,author_name,audio_info,ori_audio_name',
|
||||
is_publish: '1',
|
||||
key: '0475af1457cd3363c7b45b871e94428a',
|
||||
mid: '21511157a05844bd085308bc76ef3342',
|
||||
show_privilege: 1,
|
||||
}
|
||||
let list = hashs
|
||||
let tasks = []
|
||||
while (list.length) {
|
||||
tasks.push(Object.assign({ data: list.slice(0, 100) }, data))
|
||||
if (list.length < 100) break
|
||||
list = list.slice(100)
|
||||
}
|
||||
let url = 'http://kmr.service.kugou.com/v2/album_audio/audio'
|
||||
return tasks.map(task => this.createHttp(url, {
|
||||
method: 'POST',
|
||||
body: task,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
},
|
||||
}).then(data => data.map(s => s[0])))
|
||||
},
|
||||
|
||||
async getUserListDetailByCode(id) {
|
||||
const songInfo = await this.createHttp('http://t.kugou.com/command/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'KG-RC': 1,
|
||||
'KG-THash': 'network_super_call.cpp:3676261689:379',
|
||||
'User-Agent': '',
|
||||
},
|
||||
body: { appid: 1001, clientver: 9020, mid: '21511157a05844bd085308bc76ef3343', clienttime: 640612895, key: '36164c4015e704673c588ee202b9ecb8', data: id },
|
||||
})
|
||||
// console.log(songInfo)
|
||||
let songList
|
||||
let info = songInfo.info
|
||||
if (info.userid != null) {
|
||||
songList = await this.createHttp('http://www2.kugou.kugou.com/apps/kucodeAndShare/app/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'KG-RC': 1,
|
||||
'KG-THash': 'network_super_call.cpp:3676261689:379',
|
||||
'User-Agent': '',
|
||||
},
|
||||
body: { appid: 1001, clientver: 9020, mid: '21511157a05844bd085308bc76ef3343', clienttime: 640612895, key: '36164c4015e704673c588ee202b9ecb8', data: { id: info.id, type: 3, userid: info.userid, collect_type: 0, page: 1, pagesize: info.count } },
|
||||
})
|
||||
// console.log(songList)
|
||||
}
|
||||
let result = await Promise.all(this.createTask((songList || songInfo.list).map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
page: 1,
|
||||
limit: info.count,
|
||||
total: info.count,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.name,
|
||||
img: (info.img_size && info.img_size.replace('{size}', 240)) || info.img,
|
||||
// desc: body.result.info.list_desc,
|
||||
author: info.username,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetail3(chain, page) {
|
||||
const songInfo = await this.createHttp(`http://m.kugou.com/schain/transfer?pagesize=${this.listDetailLimit}&chain=${chain}&su=1&page=${page}&n=0.7928855356604456`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
},
|
||||
})
|
||||
if (!songInfo.list) {
|
||||
if (songInfo.global_collection_id) return this.getUserListDetail2(songInfo.global_collection_id)
|
||||
else return this.getUserListDetail4(songInfo, chain, page).catch(() => this.getUserListDetail5(chain))
|
||||
}
|
||||
let result = await Promise.all(this.createTask(songInfo.list.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
// console.log(info, songInfo)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: songInfo.count,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: songInfo.info.name,
|
||||
img: songInfo.info.img,
|
||||
// desc: body.result.info.list_desc,
|
||||
author: songInfo.info.username,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
deDuplication(datas) {
|
||||
let ids = new Set()
|
||||
return datas.filter(({ hash }) => {
|
||||
if (ids.has(hash)) return false
|
||||
ids.add(hash)
|
||||
return true
|
||||
})
|
||||
},
|
||||
|
||||
async getUserListDetailByLink({ info }, link) {
|
||||
let listInfo = info['0']
|
||||
let total = listInfo.count
|
||||
let tasks = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 90 ? 90 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
tasks.push(this.createHttp(link.replace(/pagesize=\d+/, 'pagesize=' + limit).replace(/page=\d+/, 'page=' + page), {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
Referer: link,
|
||||
},
|
||||
}).then(data => data.list.info))
|
||||
}
|
||||
let result = await Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
result = await Promise.all(this.createTask(this.deDuplication(result).map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
// console.log(result)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
page,
|
||||
limit: this.listDetailLimit,
|
||||
total: listInfo.count,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.name,
|
||||
img: listInfo.pic && listInfo.pic.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.list_create_username,
|
||||
// play_count: this.formatPlayCount(listInfo.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
createGetListDetail2Task(id, total) {
|
||||
let tasks = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 300 ? 300 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
tasks.push(this.createHttp('https://mobiles.kugou.com/api/v5/special/song_v2?appid=1058&global_specialid=' + id + '&specialid=0&plat=0&version=8000&page=' + page + '&pagesize=' + limit + '&srcappid=2919&clientver=20000&clienttime=1586163263991&mid=1586163263991&uuid=1586163263991&dfid=-&signature=' + toMD5('NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtappid=1058clienttime=1586163263991clientver=20000dfid=-global_specialid=' + id + 'mid=1586163263991page=' + page + 'pagesize=' + limit + 'plat=0specialid=0srcappid=2919uuid=1586163263991version=8000NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'), {
|
||||
headers: {
|
||||
mid: '1586163263991',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163263991',
|
||||
},
|
||||
}).then(data => data.info))
|
||||
}
|
||||
return Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||
},
|
||||
async getUserListDetail2(global_collection_id) {
|
||||
let id = global_collection_id
|
||||
if (id.length > 1000) throw new Error('get list error')
|
||||
let info = await this.createHttp('https://mobiles.kugou.com/api/v5/special/info_v2?appid=1058&specialid=0&global_specialid=' + id + '&format=jsonp&srcappid=2919&clientver=20000&clienttime=1586163242519&mid=1586163242519&uuid=1586163242519&dfid=-&signature=' + toMD5('NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtappid=1058clienttime=1586163242519clientver=20000dfid=-format=jsonpglobal_specialid=' + id + 'mid=1586163242519specialid=0srcappid=2919uuid=1586163242519NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'), {
|
||||
headers: {
|
||||
mid: '1586163242519',
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
clienttime: '1586163242519',
|
||||
},
|
||||
})
|
||||
const songInfo = await this.createGetListDetail2Task(id, info.songcount)
|
||||
let result = await Promise.all(this.createTask(this.deDuplication(songInfo).map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
// console.log(info, songInfo)
|
||||
return {
|
||||
list: this.filterData2(result) || [],
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: info.songcount,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.specialname,
|
||||
img: info.imgurl && info.imgurl.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: info.nickname,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async getListInfoByChain(chain) {
|
||||
if (this.cache.has(chain)) return this.cache.get(chain)
|
||||
const { body } = await httpFetch(`https://m.kugou.com/share/?chain=${chain}&id=${chain}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
|
||||
},
|
||||
}).promise
|
||||
let result = body.match(/var\sphpParam\s=\s({.+?});/)
|
||||
if (result) result = JSON.parse(result[1])
|
||||
this.cache.set(chain, result)
|
||||
return result
|
||||
},
|
||||
|
||||
async getUserListDetailByPcChain(chain) {
|
||||
let key = `${chain}_pc_list`
|
||||
if (this.cache.has(key)) return this.cache.get(key)
|
||||
const { body } = await httpFetch(`http://www.kugou.com/share/${chain}.html`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
|
||||
},
|
||||
}).promise
|
||||
let result = body.match(/var\sdataFromSmarty\s=\s(\[.+?\])/)
|
||||
if (result) result = JSON.parse(result[1])
|
||||
this.cache.set(chain, result)
|
||||
result = await Promise.all(this.createTask(result.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
// console.log(info, songInfo)
|
||||
return this.filterData2(result)
|
||||
},
|
||||
|
||||
async getUserListDetail4(songInfo, chain, page) {
|
||||
const limit = 100
|
||||
const [listInfo, list] = await Promise.all([
|
||||
this.getListInfoByChain(chain),
|
||||
this.getUserListDetailById(songInfo.id, page, limit),
|
||||
])
|
||||
return {
|
||||
list: list || [],
|
||||
page,
|
||||
limit,
|
||||
total: listInfo.songcount,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.specialname,
|
||||
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.nickname,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetail5(chain) {
|
||||
const [listInfo, list] = await Promise.all([
|
||||
this.getListInfoByChain(chain),
|
||||
this.getUserListDetailByPcChain(chain),
|
||||
])
|
||||
return {
|
||||
list: list || [],
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: listInfo.songcount,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: listInfo.specialname,
|
||||
img: listInfo.imgurl && listInfo.imgurl.replace('{size}', 240),
|
||||
// desc: body.result.info.list_desc,
|
||||
author: listInfo.nickname,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async getUserListDetailById(id, page, limit) {
|
||||
const signature = await handleSignature(id, page, limit)
|
||||
let info = await this.createHttp(`https://pubsongscdn.kugou.com/v2/get_other_list_file?srcappid=2919&clientver=20000&appid=1058&type=0&module=playlist&page=${page}&pagesize=${limit}&specialid=${id}&signature=${signature}`, {
|
||||
headers: {
|
||||
Referer: 'https://m3ws.kugou.com/share/index.php',
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
dfid: '-',
|
||||
},
|
||||
})
|
||||
|
||||
// console.log(info)
|
||||
let result = await Promise.all(this.createTask(info.info.map(item => ({ hash: item.hash })))).then(([...datas]) => datas.flat())
|
||||
// console.log(info, songInfo)
|
||||
return this.filterData2(result)
|
||||
},
|
||||
|
||||
async getUserListDetail(link, page, retryNum = 0) {
|
||||
if (retryNum > 3) return Promise.reject(new Error('link try max num'))
|
||||
if (link.includes('#')) link = link.replace(/#.*$/, '')
|
||||
if (link.includes('global_collection_id')) return this.getUserListDetail2(link.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'))
|
||||
if (link.includes('chain=')) return this.getUserListDetail3(link.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
if (link.includes('.html')) {
|
||||
if (link.includes('zlist.html')) {
|
||||
link = link.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
|
||||
if (link.includes('pagesize')) {
|
||||
link = link.replace('pagesize=30', 'pagesize=' + this.listDetailLimit).replace('page=1', 'page=' + page)
|
||||
} else {
|
||||
link += `&pagesize=${this.listDetailLimit}&page=${page}`
|
||||
}
|
||||
} else if (!link.includes('song.html')) return this.getUserListDetail3(link.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page)
|
||||
}
|
||||
|
||||
const requestObj_listDetailLink = httpFetch(link, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||
Referer: link,
|
||||
},
|
||||
})
|
||||
const { headers: { location }, statusCode, body } = await requestObj_listDetailLink.promise
|
||||
// console.log(body, location)
|
||||
if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum)
|
||||
if (location) {
|
||||
// console.log(location)
|
||||
if (location.includes('global_collection_id')) return this.getUserListDetail2(location.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'))
|
||||
if (location.includes('chain=')) return this.getUserListDetail3(location.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
if (location.includes('.html')) {
|
||||
if (location.includes('zlist.html')) {
|
||||
let link = location.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
|
||||
if (link.includes('pagesize')) {
|
||||
link = link.replace('pagesize=30', 'pagesize=' + this.listDetailLimit).replace('page=1', 'page=' + page)
|
||||
} else {
|
||||
link += `&pagesize=${this.listDetailLimit}&page=${page}`
|
||||
}
|
||||
return this.getUserListDetail(link, page, ++retryNum)
|
||||
} else return this.getUserListDetail3(location.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page)
|
||||
}
|
||||
// console.log('location', location)
|
||||
return this.getUserListDetail(link, page, ++retryNum)
|
||||
}
|
||||
if (typeof body == 'string') return this.getUserListDetail2(body.replace(/^[\s\S]+?"global_collection_id":"(\w+)"[\s\S]+?$/, '$1'))
|
||||
if (body.errcode !== 0) return this.getUserListDetail(link, page, ++retryNum)
|
||||
return this.getUserListDetailByLink(body, link)
|
||||
},
|
||||
|
||||
getListDetail(id, page, tryNum = 0) { // 获取歌曲列表内的音乐
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
|
||||
id = id.toString()
|
||||
if (id.includes('special/single/')) {
|
||||
id = id.replace(this.regExps.listDetailLink, '$1')
|
||||
} else if (/https?:/.test(id)) {
|
||||
return this.getUserListDetail(id.replace(/^.*http/, 'http'), page)
|
||||
} else if (/^\d+$/.test(id)) {
|
||||
return this.getUserListDetailByCode(id)
|
||||
} else if (id.startsWith('id_')) {
|
||||
id = id.replace('id_', '')
|
||||
}
|
||||
|
||||
// if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
|
||||
|
||||
const requestObj_listDetail = httpFetch(this.getSongListDetailUrl(id))
|
||||
return requestObj_listDetail.promise.then(({ body }) => {
|
||||
let listData = body.match(this.regExps.listData)
|
||||
let listInfo = body.match(this.regExps.listInfo)
|
||||
if (!listData) return this.getListDetail(id, page, ++tryNum)
|
||||
listData = this.filterData(JSON.parse(listData[1]))
|
||||
let name
|
||||
let pic
|
||||
if (listInfo) {
|
||||
name = listInfo[1]
|
||||
pic = listInfo[2]
|
||||
}
|
||||
return {
|
||||
list: listData,
|
||||
page: 1,
|
||||
limit: 10000,
|
||||
total: listData.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name,
|
||||
img: pic,
|
||||
// desc: body.result.info.list_desc,
|
||||
// author: body.result.info.userinfo.username,
|
||||
// play_count: this.formatPlayCount(body.result.listen_num),
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
filterData(rawList) {
|
||||
// console.log(rawList)
|
||||
return rawList.map(item => {
|
||||
const types = []
|
||||
const _types = {}
|
||||
if (item.filesize !== 0) {
|
||||
let size = sizeFormate(item.filesize)
|
||||
types.push({ type: '128k', size, hash: item.hash })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
hash: item.hash,
|
||||
}
|
||||
}
|
||||
if (item.filesize_320 !== 0) {
|
||||
let size = sizeFormate(item.filesize_320)
|
||||
types.push({ type: '320k', size, hash: item.hash_320 })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: item.hash_320,
|
||||
}
|
||||
}
|
||||
if (item.filesize_ape !== 0) {
|
||||
let size = sizeFormate(item.filesize_ape)
|
||||
types.push({ type: 'ape', size, hash: item.hash_ape })
|
||||
_types.ape = {
|
||||
size,
|
||||
hash: item.hash_ape,
|
||||
}
|
||||
}
|
||||
if (item.filesize_flac !== 0) {
|
||||
let size = sizeFormate(item.filesize_flac)
|
||||
types.push({ type: 'flac', size, hash: item.hash_flac })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: item.hash_flac,
|
||||
}
|
||||
}
|
||||
return {
|
||||
singer: decodeName(item.singername),
|
||||
name: decodeName(item.songname),
|
||||
albumName: decodeName(item.album_name),
|
||||
albumId: item.album_id,
|
||||
songmid: item.audio_id,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(item.duration / 1000),
|
||||
img: null,
|
||||
lrc: null,
|
||||
hash: item.hash,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// hash list filter
|
||||
filterData2(rawList) {
|
||||
// console.log(rawList)
|
||||
let ids = new Set()
|
||||
let list = []
|
||||
rawList.forEach(item => {
|
||||
if (!item) return
|
||||
if (ids.has(item.audio_info.audio_id)) return
|
||||
ids.add(item.audio_info.audio_id)
|
||||
const types = []
|
||||
const _types = {}
|
||||
if (item.audio_info.filesize !== '0') {
|
||||
let size = sizeFormate(parseInt(item.audio_info.filesize))
|
||||
types.push({ type: '128k', size, hash: item.audio_info.hash })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
hash: item.audio_info.hash,
|
||||
}
|
||||
}
|
||||
if (item.audio_info.filesize_320 !== '0') {
|
||||
let size = sizeFormate(parseInt(item.audio_info.filesize_320))
|
||||
types.push({ type: '320k', size, hash: item.audio_info.hash_320 })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: item.audio_info.hash_320,
|
||||
}
|
||||
}
|
||||
if (item.audio_info.filesize_flac !== '0') {
|
||||
let size = sizeFormate(parseInt(item.audio_info.filesize_flac))
|
||||
types.push({ type: 'flac', size, hash: item.audio_info.hash_flac })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: item.audio_info.hash_flac,
|
||||
}
|
||||
}
|
||||
list.push({
|
||||
singer: decodeName(item.author_name),
|
||||
name: decodeName(item.ori_audio_name),
|
||||
albumName: decodeName(item.album_info.album_name),
|
||||
albumId: item.album_info.album_id,
|
||||
songmid: item.audio_info.audio_id,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(parseInt(item.audio_info.timelength) / 1000),
|
||||
img: null,
|
||||
lrc: null,
|
||||
hash: item.audio_info.hash,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
|
||||
// 获取列表信息
|
||||
getListInfo(tagId, tryNum = 0) {
|
||||
if (this._requestObj_listInfo) this._requestObj_listInfo.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_listInfo = httpFetch(this.getInfoUrl(tagId))
|
||||
return this._requestObj_listInfo.promise.then(({ body }) => {
|
||||
if (body.status !== 1) return this.getListInfo(tagId, ++tryNum)
|
||||
return {
|
||||
limit: body.data.params.pagesize,
|
||||
page: body.data.params.p,
|
||||
total: body.data.params.total,
|
||||
source: 'kg',
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取列表数据
|
||||
getList(sortId, tagId, page) {
|
||||
let tasks = [this.getSongList(sortId, tagId, page)]
|
||||
tasks.push(
|
||||
this.currentTagInfo.id === tagId
|
||||
? Promise.resolve(this.currentTagInfo.info)
|
||||
: this.getListInfo(tagId).then(info => {
|
||||
this.currentTagInfo.id = tagId
|
||||
this.currentTagInfo.info = Object.assign({}, info)
|
||||
return info
|
||||
}),
|
||||
)
|
||||
if (!tagId && page === 1 && sortId === this.sortList[0].id) tasks.push(this.getSongListRecommend()) // 如果是所有类别,则顺便获取推荐列表
|
||||
return Promise.all(tasks).then(([list, info, recommendList]) => {
|
||||
if (recommendList) list.unshift(...recommendList)
|
||||
return {
|
||||
list,
|
||||
...info,
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取标签
|
||||
getTags(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(this.getInfoUrl())
|
||||
return this._requestObj_tags.promise.then(({ body }) => {
|
||||
if (body.status !== 1) return this.getTags(++tryNum)
|
||||
return {
|
||||
hotTag: this.filterInfoHotTag(body.data.hotTag),
|
||||
tags: this.filterTagInfo(body.data.tagids),
|
||||
source: 'kg',
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getDetailPageUrl(id) {
|
||||
if (typeof id == 'string') {
|
||||
if (/^https?:\/\//.test(id)) return id
|
||||
id = id.replace('id_', '')
|
||||
}
|
||||
return `https://www.kugou.com/yy/special/single/${id}.html`
|
||||
},
|
||||
|
||||
search(text, page, limit = 20) {
|
||||
// http://msearchretry.kugou.com/api/v3/search/special?version=9209&keyword=%E5%91%A8%E6%9D%B0%E4%BC%A6&pagesize=20&filter=0&page=1&sver=2&with_res_tag=0
|
||||
// return httpFetch(`http://ioscdn.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(text)}&page=${page}&pagesize=${limit}&showtype=10&plat=2&version=7910&correct=1&sver=5`)
|
||||
return httpFetch(`http://msearchretry.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(text)}&page=${page}&pagesize=${limit}&showtype=10&filter=0&version=7910&sver=2`)
|
||||
.promise.then(({ body }) => {
|
||||
if (body.errcode != 0) throw new Error('filed')
|
||||
// console.log(body.data.info)
|
||||
return {
|
||||
list: body.data.info.map(item => {
|
||||
return {
|
||||
play_count: this.formatPlayCount(item.playcount),
|
||||
id: 'id_' + item.specialid,
|
||||
author: item.nickname,
|
||||
name: item.specialname,
|
||||
time: item.publishtime,
|
||||
img: item.imgurl,
|
||||
grade: item.grade,
|
||||
desc: item.intro,
|
||||
total: item.songcount,
|
||||
source: 'kg',
|
||||
}
|
||||
}),
|
||||
limit,
|
||||
total: body.data.total,
|
||||
source: 'kg',
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// getList
|
||||
// getTags
|
||||
// getListDetail
|
||||
@@ -0,0 +1,19 @@
|
||||
import { inflate } from 'zlib'
|
||||
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784
|
||||
const enc_key = Buffer.from([0x40, 0x47, 0x61, 0x77, 0x5e, 0x32, 0x74, 0x47, 0x51, 0x36, 0x31, 0x2d, 0xce, 0xd2, 0x6e, 0x69], 'binary')
|
||||
export const decodeLyric = str => new Promise((resolve, reject) => {
|
||||
if (!str.length) return
|
||||
const buf_str = Buffer.from(str, 'base64').slice(4)
|
||||
for (let i = 0, len = buf_str.length; i < len; i++) {
|
||||
buf_str[i] = buf_str[i] ^ enc_key[i % 16]
|
||||
}
|
||||
inflate(buf_str, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
resolve(result.toString())
|
||||
})
|
||||
})
|
||||
|
||||
// s.content[0].lyricContent.forEach(([str]) => {
|
||||
// console.log(str)
|
||||
// })
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,131 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName } from '../../index'
|
||||
import { formatSinger, objStr2JSON } from './util'
|
||||
|
||||
// let requestObj_list
|
||||
export default {
|
||||
limit_list: 36,
|
||||
limit_song: 1000,
|
||||
filterListDetail(rawList, albumName, albumId) {
|
||||
// console.log(rawList)
|
||||
// console.log(rawList.length, rawList2.length)
|
||||
return rawList.map((item, inedx) => {
|
||||
let formats = item.formats.split('|')
|
||||
let types = []
|
||||
let _types = {}
|
||||
if (formats.includes('MP3128')) {
|
||||
types.push({ type: '128k', size: null })
|
||||
_types['128k'] = {
|
||||
size: null,
|
||||
}
|
||||
}
|
||||
// if (formats.includes('MP3192')) {
|
||||
// types.push({ type: '192k', size: null })
|
||||
// _types['192k'] = {
|
||||
// size: null,
|
||||
// }
|
||||
// }
|
||||
if (formats.includes('MP3H')) {
|
||||
types.push({ type: '320k', size: null })
|
||||
_types['320k'] = {
|
||||
size: null,
|
||||
}
|
||||
}
|
||||
// if (formats.includes('AL')) {
|
||||
// types.push({ type: 'ape', size: null })
|
||||
// _types.ape = {
|
||||
// size: null,
|
||||
// }
|
||||
// }
|
||||
if (formats.includes('ALFLAC')) {
|
||||
types.push({ type: 'flac', size: null })
|
||||
_types.flac = {
|
||||
size: null,
|
||||
}
|
||||
}
|
||||
if (formats.includes('HIRFLAC')) {
|
||||
types.push({ type: 'flac24bit', size: null })
|
||||
_types.flac24bit = {
|
||||
size: null,
|
||||
}
|
||||
}
|
||||
// types.reverse()
|
||||
return {
|
||||
singer: formatSinger(decodeName(item.artist)),
|
||||
name: decodeName(item.name),
|
||||
albumName,
|
||||
albumId,
|
||||
songmid: item.id,
|
||||
source: 'kw',
|
||||
interval: null,
|
||||
img: item.pic,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 格式化播放数量
|
||||
* @param {*} num
|
||||
*/
|
||||
formatPlayCount(num) {
|
||||
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
|
||||
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
|
||||
return num
|
||||
},
|
||||
getAlbumListDetail(id, page, retryNum = 0) {
|
||||
if (retryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
const requestObj_listDetail = httpFetch(`http://search.kuwo.cn/r.s?pn=${page - 1}&rn=${this.limit_song}&stype=albuminfo&albumid=${id}&show_copyright_off=0&encoding=utf&vipver=MUSIC_9.1.0`)
|
||||
return requestObj_listDetail.promise.then(({ statusCode, body }) => {
|
||||
if (statusCode !== 200) return this.getAlbumListDetail(id, page, ++retryNum)
|
||||
body = objStr2JSON(body)
|
||||
// console.log(body)
|
||||
if (!body.musiclist) return this.getAlbumListDetail(id, page, ++retryNum)
|
||||
body.name = decodeName(body.name)
|
||||
return {
|
||||
list: this.filterListDetail(body.musiclist, body.name, body.albumid),
|
||||
page,
|
||||
limit: this.limit_song,
|
||||
total: parseInt(body.songnum),
|
||||
source: 'kw',
|
||||
info: {
|
||||
name: body.name,
|
||||
img: body.img || body.hts_img,
|
||||
desc: decodeName(body.info),
|
||||
author: decodeName(body.artist),
|
||||
// play_count: this.formatPlayCount(body.playnum),
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
// getAlbumListDetail(id, page, retryNum = 0) {
|
||||
// if (retryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
// return tokenRequest(`http://www.kuwo.cn/api/www/album/albumInfo?albumId=${id}&pn=${page}&rn=${this.limit_song}&httpsStatus=1`).then((resp) => {
|
||||
// return resp.promise.then(({ statusCode, body }) => {
|
||||
// console.log(body)
|
||||
// return Promise.reject(new Error('failed'))
|
||||
// // if (statusCode !== 200) return this.getAlbumListDetail(id, page, ++retryNum)
|
||||
// // const data = body.data
|
||||
// // console.log(data)
|
||||
// // if (!data.musicList) return this.getAlbumListDetail(id, page, ++retryNum)
|
||||
// // return {
|
||||
// // list: this.filterListDetail(data.musiclist),
|
||||
// // page,
|
||||
// // limit: this.limit_song,
|
||||
// // total: data.total,
|
||||
// // source: 'kw',
|
||||
// // info: {
|
||||
// // name: data.album,
|
||||
// // img: data.pic,
|
||||
// // desc: data.albuminfo,
|
||||
// // author: data.artist,
|
||||
// // play_count: this.formatPlayCount(data.playCnt),
|
||||
// // },
|
||||
// // }
|
||||
// })
|
||||
// })
|
||||
// },
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { headers, timeout } from '../options'
|
||||
import { dnsLookup } from '../utils'
|
||||
|
||||
const api_temp = {
|
||||
getMusicUrl(songInfo, type) {
|
||||
const requestObj = httpFetch(`http://tm.tempmusics.tk/url/kw/${songInfo.songmid}/${type}`, {
|
||||
method: 'get',
|
||||
headers,
|
||||
timeout,
|
||||
lookup: dnsLookup,
|
||||
family: 4,
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(body.msg))
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
|
||||
export default api_temp
|
||||
@@ -0,0 +1,33 @@
|
||||
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://45.32.53.128:3002/m/kw/u/${songInfo.songmid}/${type}`, {
|
||||
// method: 'get',
|
||||
// headers,
|
||||
// timeout,
|
||||
// })
|
||||
// requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
// return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(body.msg))
|
||||
// })
|
||||
// return requestObj
|
||||
// },
|
||||
getMusicUrl(songInfo, type) {
|
||||
const requestObj = httpFetch(`http://ts.tempmusics.tk/url/kw/${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
|
||||
},
|
||||
}
|
||||
|
||||
export default api_test
|
||||
@@ -0,0 +1,63 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { dateFormat2 } from '../../index'
|
||||
|
||||
export default {
|
||||
_requestObj: null,
|
||||
_requestObj2: null,
|
||||
async getComment({ songmid }, page = 1, limit = 20) {
|
||||
if (this._requestObj) this._requestObj.cancelHttp()
|
||||
|
||||
const _requestObj = httpFetch(`http://comment.kuwo.cn/com.s?type=get_comment&uid=0&digest=15&sid=${songmid}&page=${page}&rows=${limit}&f=web&prod=MUSIC_8.7.7.0_BCS37&devid=28556413`, {
|
||||
headers: {
|
||||
'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9;)',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
if (statusCode != 200 || body.result !== 'ok') throw new Error('获取评论失败')
|
||||
// console.log(body)
|
||||
return { source: 'kw', comments: this.filterComment(body.rows), total: body.total, page, limit, maxPage: Math.ceil(body.total / limit) || 1 }
|
||||
},
|
||||
async getHotComment({ songmid }, page = 1, limit = 100) {
|
||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
||||
|
||||
const _requestObj2 = httpFetch(`http://comment.kuwo.cn/com.s?type=get_rec_comment&uid=0&digest=15&sid=${songmid}&page=${page}&rows=${limit}&f=web&prod=MUSIC_8.7.7.0_BCS37&devid=28556413`, {
|
||||
headers: {
|
||||
'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9;)',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj2.promise
|
||||
if (statusCode != 200 || body.result !== 'ok') throw new Error('获取热门评论失败')
|
||||
// console.log(body)
|
||||
return { source: 'kw', comments: this.filterComment(body.rows), total: body.total, page, limit, maxPage: Math.ceil(body.total / limit) || 1 }
|
||||
},
|
||||
filterComment(rawList) {
|
||||
if (!rawList) return []
|
||||
return rawList.map(item => {
|
||||
let data = {
|
||||
id: item.id,
|
||||
text: item.msg.split('\n'),
|
||||
time: item.time,
|
||||
timeStr: dateFormat2(new Date(item.time).getTime()),
|
||||
userName: decodeURIComponent(item.u_name),
|
||||
avatar: item.u_pic,
|
||||
userId: item.u_id,
|
||||
likedCount: item.like_num,
|
||||
reply: [],
|
||||
}
|
||||
return item.reply
|
||||
? {
|
||||
id: item.id,
|
||||
rootId: item.reply.id,
|
||||
text: item.reply.msg.split('\n'),
|
||||
time: item.reply.time,
|
||||
timeStr: dateFormat2(new Date(item.reply.time).getTime()),
|
||||
userName: decodeURIComponent(item.reply.u_name),
|
||||
avatar: item.reply.u_pic,
|
||||
userId: item.reply.u_id,
|
||||
likedCount: item.reply.like_num,
|
||||
reply: [data],
|
||||
}
|
||||
: data
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
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('http://hotword.kuwo.cn/hotword.s?prod=kwplayer_ar_9.3.0.1&corp=kuwo&newver=2&vipver=9.3.0.1&source=kwplayer_ar_9.3.0.1_40.apk&p2p=1¬race=0&uid=0&plat=kwplayer_ar&rformat=json&encoding=utf8&tabid=1', {
|
||||
headers: {
|
||||
'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9;)',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
if (statusCode != 200 || body.status !== 'ok') throw new Error('获取热搜词失败')
|
||||
// console.log(body, statusCode)
|
||||
return { source: 'kw', list: this.filterList(body.tagvalue) }
|
||||
},
|
||||
filterList(rawList) {
|
||||
return rawList.map(item => item.key)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import tempSearch from './tempSearch'
|
||||
import musicSearch from './musicSearch'
|
||||
import { formatSinger, getToken } from './util'
|
||||
import leaderboard from './leaderboard'
|
||||
import lyric from './lyric'
|
||||
import pic from './pic'
|
||||
import { apis } from '../api-source'
|
||||
import songList from './songList'
|
||||
import hotSearch from './hotSearch'
|
||||
import comment from './comment'
|
||||
|
||||
const kw = {
|
||||
_musicInfoRequestObj: null,
|
||||
_musicInfoPromiseCancelFn: null,
|
||||
_musicPicRequestObj: null,
|
||||
_musicPicPromiseCancelFn: null,
|
||||
// context: null,
|
||||
|
||||
|
||||
// init(context) {
|
||||
// if (this.isInited) return
|
||||
// this.isInited = true
|
||||
// this.context = context
|
||||
|
||||
// // this.musicSearch.search('我又想你了').then(res => {
|
||||
// // console.log(res)
|
||||
// // })
|
||||
|
||||
// // this.getMusicUrl('62355680', '320k').then(url => {
|
||||
// // console.log(url)
|
||||
// // })
|
||||
// },
|
||||
|
||||
tempSearch,
|
||||
musicSearch,
|
||||
leaderboard,
|
||||
songList,
|
||||
hotSearch,
|
||||
comment,
|
||||
getLyric(songInfo, isGetLyricx) {
|
||||
// let singer = songInfo.singer.indexOf('、') > -1 ? songInfo.singer.split('、')[0] : songInfo.singer
|
||||
return lyric.getLyric(songInfo, isGetLyricx)
|
||||
},
|
||||
handleMusicInfo(songInfo) {
|
||||
return this.getMusicInfo(songInfo).then(info => {
|
||||
// console.log(JSON.stringify(info))
|
||||
songInfo.name = info.name
|
||||
songInfo.singer = formatSinger(info.artist)
|
||||
songInfo.img = info.pic
|
||||
songInfo.albumName = info.album
|
||||
return songInfo
|
||||
// return Object.assign({}, songInfo, {
|
||||
// name: info.name,
|
||||
// singer: formatSinger(info.artist),
|
||||
// img: info.pic,
|
||||
// albumName: info.album,
|
||||
// })
|
||||
})
|
||||
},
|
||||
|
||||
getMusicUrl(songInfo, type) {
|
||||
return apis('kw').getMusicUrl(songInfo, type)
|
||||
},
|
||||
|
||||
getMusicInfo(songInfo) {
|
||||
if (this._musicInfoRequestObj) this._musicInfoRequestObj.cancelHttp()
|
||||
this._musicInfoRequestObj = httpFetch(`http://www.kuwo.cn/api/www/music/musicInfo?mid=${songInfo.songmid}`)
|
||||
return this._musicInfoRequestObj.promise.then(({ body }) => {
|
||||
return body.code === 200 ? body.data : Promise.reject(new Error(body.msg))
|
||||
})
|
||||
},
|
||||
|
||||
getMusicUrls(musicInfo, cb) {
|
||||
let tasks = []
|
||||
let songId = musicInfo.songmid
|
||||
musicInfo.types.forEach(type => {
|
||||
tasks.push(kw.getMusicUrl(songId, type.type).promise)
|
||||
})
|
||||
Promise.all(tasks).then(urlInfo => {
|
||||
let typeUrl = {}
|
||||
urlInfo.forEach(info => {
|
||||
typeUrl[info.type] = info.url
|
||||
})
|
||||
cb(typeUrl)
|
||||
})
|
||||
},
|
||||
|
||||
getPic(songInfo) {
|
||||
return pic.getPic(songInfo)
|
||||
},
|
||||
|
||||
getMusicDetailPageUrl(songInfo) {
|
||||
return `http://www.kuwo.cn/play_detail/${songInfo.songmid}`
|
||||
},
|
||||
|
||||
init() {
|
||||
return getToken()
|
||||
},
|
||||
}
|
||||
|
||||
export default kw
|
||||
@@ -0,0 +1,202 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { formatPlayTime, decodeName } from '../../index'
|
||||
import { formatSinger } from './util'
|
||||
|
||||
const boardList = [{ id: 'kw__93', name: '酷我飙升榜', bangid: '93' }, { id: 'kw__17', name: '酷我新歌榜', bangid: '17' }, { id: 'kw__16', name: '酷我热歌榜', bangid: '16' }, { id: 'kw__158', name: '抖音热歌榜', bangid: '158' }, { id: 'kw__284', name: '酷我热评榜', bangid: '284' }, { id: 'kw__290', name: 'ACG新歌榜', bangid: '290' }, { id: 'kw__286', name: '台湾KKBOX榜', bangid: '286' }, { id: 'kw__279', name: '春日浅唱榜', bangid: '279' }, { id: 'kw__281', name: '巴士随身听榜', bangid: '281' }, { id: 'kw__255', name: 'KTV点唱榜', bangid: '255' }, { id: 'kw__280', name: '家务进行曲榜', bangid: '280' }, { id: 'kw__282', name: '熬夜修仙榜', bangid: '282' }, { id: 'kw__283', name: '枕边轻音乐榜', bangid: '283' }, { id: 'kw__278', name: '古风音乐榜', bangid: '278' }, { id: 'kw__264', name: 'Vlog音乐榜', bangid: '264' }, { id: 'kw__242', name: '酷我电音榜', bangid: '242' }, { id: 'kw__187', name: '流行趋势榜', bangid: '187' }, { id: 'kw__204', name: '现场音乐榜', bangid: '204' }, { id: 'kw__186', name: 'ACG神曲榜', bangid: '186' }, { id: 'kw__185', name: '最强翻唱榜', bangid: '185' }, { id: 'kw__26', name: '经典怀旧榜', bangid: '26' }, { id: 'kw__104', name: '酷我华语榜', bangid: '104' }, { id: 'kw__182', name: '酷我粤语榜', bangid: '182' }, { id: 'kw__22', name: '酷我欧美榜', bangid: '22' }, { id: 'kw__184', name: '酷我韩语榜', bangid: '184' }, { id: 'kw__183', name: '酷我日语榜', bangid: '183' }, { id: 'kw__145', name: '会员畅听榜', bangid: '145' }, { id: 'kw__153', name: '网红新歌榜', bangid: '153' }, { id: 'kw__64', name: '影视金曲榜', bangid: '64' }, { id: 'kw__176', name: 'DJ嗨歌榜', bangid: '176' }, { id: 'kw__106', name: '酷我真声音', bangid: '106' }, { id: 'kw__12', name: 'Billboard榜', bangid: '12' }, { id: 'kw__49', name: 'iTunes音乐榜', bangid: '49' }, { id: 'kw__180', name: 'beatport电音榜', bangid: '180' }, { id: 'kw__13', name: '英国UK榜', bangid: '13' }, { id: 'kw__164', name: '百大DJ榜', bangid: '164' }, { id: 'kw__246', name: 'YouTube音乐排行榜', bangid: '246' }, { id: 'kw__265', name: '韩国Genie榜', bangid: '265' }, { id: 'kw__14', name: '韩国M-net榜', bangid: '14' }, { id: 'kw__8', name: '香港电台榜', bangid: '8' }, { id: 'kw__15', name: '日本公信榜', bangid: '15' }, { id: 'kw__151', name: '腾讯音乐人原创榜', bangid: '151' }]
|
||||
|
||||
export default {
|
||||
list: [
|
||||
{
|
||||
id: 'kwbiaosb',
|
||||
name: '飙升榜',
|
||||
bangid: 93,
|
||||
},
|
||||
{
|
||||
id: 'kwregb',
|
||||
name: '热歌榜',
|
||||
bangid: 16,
|
||||
},
|
||||
{
|
||||
id: 'kwhuiyb',
|
||||
name: '会员榜',
|
||||
bangid: 145,
|
||||
},
|
||||
{
|
||||
id: 'kwdouyb',
|
||||
name: '抖音榜',
|
||||
bangid: 158,
|
||||
},
|
||||
{
|
||||
id: 'kwqsb',
|
||||
name: '趋势榜',
|
||||
bangid: 187,
|
||||
},
|
||||
{
|
||||
id: 'kwhuaijb',
|
||||
name: '怀旧榜',
|
||||
bangid: 26,
|
||||
},
|
||||
{
|
||||
id: 'kwhuayb',
|
||||
name: '华语榜',
|
||||
bangid: 104,
|
||||
},
|
||||
{
|
||||
id: 'kwyueyb',
|
||||
name: '粤语榜',
|
||||
bangid: 182,
|
||||
},
|
||||
{
|
||||
id: 'kwoumb',
|
||||
name: '欧美榜',
|
||||
bangid: 22,
|
||||
},
|
||||
{
|
||||
id: 'kwhanyb',
|
||||
name: '韩语榜',
|
||||
bangid: 184,
|
||||
},
|
||||
{
|
||||
id: 'kwriyb',
|
||||
name: '日语榜',
|
||||
bangid: 183,
|
||||
},
|
||||
],
|
||||
getUrl: (p, l, id) => `http://kbangserver.kuwo.cn/ksong.s?from=pc&fmt=json&pn=${p - 1}&rn=${l}&type=bang&data=content&id=${id}&show_copyright_off=0&pcmp4=1&isbang=1`,
|
||||
regExps: {
|
||||
|
||||
},
|
||||
limit: 100,
|
||||
_requestBoardsObj: null,
|
||||
|
||||
getBoardsData() {
|
||||
if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp()
|
||||
this._requestBoardsObj = httpFetch('http://qukudata.kuwo.cn/q.k?op=query&cont=tree&node=2&pn=0&rn=1000&fmt=json&level=2')
|
||||
return this._requestBoardsObj.promise
|
||||
},
|
||||
getData(url) {
|
||||
const requestDataObj = httpFetch(url)
|
||||
return requestDataObj.promise
|
||||
},
|
||||
filterData(rawList) {
|
||||
// console.log(rawList)
|
||||
// console.log(rawList.length, rawList2.length)
|
||||
return rawList.map((item, inedx) => {
|
||||
let formats = item.formats.split('|')
|
||||
let types = []
|
||||
let _types = {}
|
||||
if (formats.includes('MP3128')) {
|
||||
types.push({ type: '128k', size: null })
|
||||
_types['128k'] = {
|
||||
size: null,
|
||||
}
|
||||
}
|
||||
// if (formats.includes('MP3192')) {
|
||||
// types.push({ type: '192k', size: null })
|
||||
// _types['192k'] = {
|
||||
// size: null,
|
||||
// }
|
||||
// }
|
||||
if (formats.includes('MP3H')) {
|
||||
types.push({ type: '320k', size: null })
|
||||
_types['320k'] = {
|
||||
size: null,
|
||||
}
|
||||
}
|
||||
// if (formats.includes('AL')) {
|
||||
// types.push({ type: 'ape', size: null })
|
||||
// _types.ape = {
|
||||
// size: null,
|
||||
// }
|
||||
// }
|
||||
if (formats.includes('ALFLAC')) {
|
||||
types.push({ type: 'flac', size: null })
|
||||
_types.flac = {
|
||||
size: null,
|
||||
}
|
||||
}
|
||||
if (formats.includes('HIRFLAC')) {
|
||||
types.push({ type: 'flac24bit', size: null })
|
||||
_types.flac24bit = {
|
||||
size: null,
|
||||
}
|
||||
}
|
||||
// types.reverse()
|
||||
return {
|
||||
singer: formatSinger(decodeName(item.artist)),
|
||||
name: decodeName(item.name),
|
||||
albumName: decodeName(item.album),
|
||||
albumId: item.albumid,
|
||||
songmid: item.id,
|
||||
source: 'kw',
|
||||
interval: formatPlayTime(parseInt(item.song_duration)),
|
||||
img: item.pic,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
filterBoardsData(rawList) {
|
||||
// console.log(rawList)
|
||||
let list = []
|
||||
for (const board of rawList) {
|
||||
if (board.source != '1') continue
|
||||
list.push({
|
||||
id: 'kw__' + board.sourceid,
|
||||
name: board.name,
|
||||
bangid: String(board.sourceid),
|
||||
})
|
||||
}
|
||||
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.child) return this.getBoards(retryNum)
|
||||
// const list = this.filterBoardsData(response.body.child)
|
||||
// // console.log(list)
|
||||
// console.log(JSON.stringify(list))
|
||||
// this.list = list
|
||||
// return {
|
||||
// list,
|
||||
// source: 'kw',
|
||||
// }
|
||||
this.list = boardList
|
||||
return {
|
||||
list: boardList,
|
||||
source: 'kw',
|
||||
}
|
||||
},
|
||||
|
||||
getList(id, page, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
return this.getData(this.getUrl(page, this.limit, id)).then(({ statusCode, body }) => {
|
||||
// console.log(body)
|
||||
if (statusCode !== 200 || !body.musiclist) return this.getList(id, page, retryNum)
|
||||
// console.log(data1.musiclist, data2.data)
|
||||
let total = parseInt(body.num)
|
||||
let list = this.filterData(body.musiclist)
|
||||
return {
|
||||
total,
|
||||
list,
|
||||
limit: this.limit,
|
||||
page,
|
||||
source: 'kw',
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// getDetailPageUrl(id) {
|
||||
// return `http://www.kuwo.cn/rankList/${id}`
|
||||
// },
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeLyric, lrcTools } from './util'
|
||||
import { decodeName } from '../../index'
|
||||
|
||||
/*
|
||||
export default {
|
||||
formatTime(time) {
|
||||
let m = parseInt(time / 60)
|
||||
let s = (time % 60).toFixed(2)
|
||||
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
|
||||
},
|
||||
sortLrcArr(arr) {
|
||||
const lrcSet = new Set()
|
||||
let lrc = []
|
||||
let lrcT = []
|
||||
|
||||
for (const item of arr) {
|
||||
if (lrcSet.has(item.time)) {
|
||||
const tItem = lrc.pop()
|
||||
tItem.time = lrc[lrc.length - 1].time
|
||||
lrcT.push(tItem)
|
||||
lrc.push(item)
|
||||
} else {
|
||||
lrc.push(item)
|
||||
lrcSet.add(item.time)
|
||||
}
|
||||
}
|
||||
|
||||
if (lrcT.length && lrc.length > lrcT.length) {
|
||||
const tItem = lrc.pop()
|
||||
tItem.time = lrc[lrc.length - 1].time
|
||||
lrcT.push(tItem)
|
||||
}
|
||||
|
||||
return {
|
||||
lrc,
|
||||
lrcT,
|
||||
}
|
||||
},
|
||||
transformLrc(songinfo, lrclist) {
|
||||
return `[ti:${songinfo.songName}]\n[ar:${songinfo.artist}]\n[al:${songinfo.album}]\n[by:]\n[offset:0]\n${lrclist ? lrclist.map(l => `[${this.formatTime(l.time)}]${l.lineLyric}\n`).join('') : '暂无歌词'}`
|
||||
},
|
||||
getLyric(songId) {
|
||||
const requestObj = httpFetch(`http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=${songId}`)
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
// console.log(body)
|
||||
if (!body.data?.lrclist?.length) return Promise.reject(new Error('Get lyric failed'))
|
||||
let lrcInfo
|
||||
try {
|
||||
lrcInfo = this.sortLrcArr(body.data.lrclist)
|
||||
} catch {
|
||||
return Promise.reject(new Error('Get lyric failed'))
|
||||
}
|
||||
// console.log(body.data.lrclist)
|
||||
// console.log(lrcInfo.lrc, lrcInfo.lrcT)
|
||||
// console.log({
|
||||
// lyric: decodeName(this.transformLrc(body.data.songinfo, lrc)),
|
||||
// tlyric: decodeName(this.transformLrc(body.data.songinfo, lrcT)),
|
||||
// })
|
||||
return {
|
||||
lyric: decodeName(this.transformLrc(body.data.songinfo, lrcInfo.lrc)),
|
||||
tlyric: lrcInfo.lrcT.length ? decodeName(this.transformLrc(body.data.songinfo, lrcInfo.lrcT)) : '',
|
||||
}
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
*/
|
||||
|
||||
const buf_key = Buffer.from('yeelion')
|
||||
const buf_key_len = buf_key.length
|
||||
const buildParams = (id, isGetLyricx) => {
|
||||
let params = `user=12345,web,web,web&requester=localhost&req=1&rid=MUSIC_${id}`
|
||||
if (isGetLyricx) params += '&lrcx=1'
|
||||
const buf_str = Buffer.from(params)
|
||||
const buf_str_len = buf_str.length
|
||||
const output = new Uint16Array(buf_str_len)
|
||||
let i = 0
|
||||
while (i < buf_str_len) {
|
||||
let j = 0
|
||||
while (j < buf_key_len && i < buf_str_len) {
|
||||
output[i] = buf_key[j] ^ buf_str[i]
|
||||
i++
|
||||
j++
|
||||
}
|
||||
}
|
||||
return Buffer.from(output).toString('base64')
|
||||
}
|
||||
|
||||
// console.log(buildParams('207527604', false))
|
||||
// console.log(buildParams('207527604', true))
|
||||
|
||||
const timeExp = /^\[([\d:.]*)\]{1}/g
|
||||
const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
||||
export default {
|
||||
/* sortLrcArr(arr) {
|
||||
const lrcSet = new Set()
|
||||
let lrc = []
|
||||
let lrcT = []
|
||||
let markIndex = []
|
||||
for (const item of arr) {
|
||||
if (lrcSet.has(item.time)) {
|
||||
if (lrc.length < 2) continue
|
||||
const index = lrc.findIndex(l => l.time == item.time)
|
||||
markIndex.push(index)
|
||||
if (index == lrc.length - 1) {
|
||||
lrcT.push({ ...lrc[index], time: item.time })
|
||||
lrc.push(item)
|
||||
} else {
|
||||
lrcT.push({ ...lrc[index], time: lrc[index + 1].time })
|
||||
if (item.text) {
|
||||
// const lastIndex = lrc.length - 1
|
||||
// markIndex.push(lastIndex)
|
||||
// lrcT.push({ ...lrc[lastIndex], time: lrc[lastIndex - 1].time })
|
||||
lrc.push(item)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lrc.push(item)
|
||||
lrcSet.add(item.time)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(markIndex)
|
||||
markIndex = Array.from(new Set(markIndex))
|
||||
for (let index = markIndex.length - 1; index >= 0; index--) {
|
||||
lrc.splice(markIndex[index], 1)
|
||||
}
|
||||
|
||||
// if (lrcT.length) {
|
||||
// if (lrc.length * 0.4 < lrcT.length) { // 翻译数量需大于歌词数量的0.4倍,否则认为没有翻译
|
||||
// const tItem = lrc.pop()
|
||||
// tItem.time = lrc[lrc.length - 1].time
|
||||
// lrcT.push(tItem)
|
||||
// } else {
|
||||
// lrc = arr
|
||||
// lrcT = []
|
||||
// }
|
||||
// }
|
||||
|
||||
console.log(lrc, lrcT)
|
||||
|
||||
return {
|
||||
lrc,
|
||||
lrcT,
|
||||
}
|
||||
}, */
|
||||
sortLrcArr(arr) {
|
||||
const lrcSet = new Set()
|
||||
let lrc = []
|
||||
let lrcT = []
|
||||
|
||||
for (const item of arr) {
|
||||
if (lrcSet.has(item.time)) {
|
||||
if (lrc.length < 2) continue
|
||||
const tItem = lrc.pop()
|
||||
tItem.time = lrc[lrc.length - 1].time
|
||||
lrcT.push(tItem)
|
||||
lrc.push(item)
|
||||
} else {
|
||||
lrc.push(item)
|
||||
lrcSet.add(item.time)
|
||||
}
|
||||
}
|
||||
|
||||
if (lrcT.length > lrc.length * 0.3) {
|
||||
throw new Error('failed')
|
||||
// if (lrc.length * 0.4 < lrcT.length) { // 翻译数量需大于歌词数量的0.4倍,否则认为没有翻译
|
||||
// const tItem = lrc.pop()
|
||||
// tItem.time = lrc[lrc.length - 1].time
|
||||
// lrcT.push(tItem)
|
||||
// } else {
|
||||
// lrc = arr
|
||||
// lrcT = []
|
||||
// }
|
||||
}
|
||||
|
||||
return {
|
||||
lrc,
|
||||
lrcT,
|
||||
}
|
||||
},
|
||||
transformLrc(tags, lrclist) {
|
||||
return `${tags.join('\n')}\n${lrclist ? lrclist.map(l => `[${l.time}]${l.text}\n`).join('') : '暂无歌词'}`
|
||||
},
|
||||
parseLrc(lrc) {
|
||||
const lines = lrc.split(/\r\n|\r|\n/)
|
||||
let tags = []
|
||||
let lrcArr = []
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim()
|
||||
let result = timeExp.exec(line)
|
||||
if (result) {
|
||||
const text = line.replace(timeExp, '').trim()
|
||||
let time = RegExp.$1
|
||||
if (/\.\d\d$/.test(time)) time += '0'
|
||||
lrcArr.push({
|
||||
time,
|
||||
text,
|
||||
})
|
||||
} else if (lrcTools.rxps.tagLine.test(line)) {
|
||||
tags.push(line)
|
||||
}
|
||||
}
|
||||
const lrcInfo = this.sortLrcArr(lrcArr)
|
||||
return {
|
||||
lyric: decodeName(this.transformLrc(tags, lrcInfo.lrc)),
|
||||
tlyric: lrcInfo.lrcT.length ? decodeName(this.transformLrc(tags, lrcInfo.lrcT)) : '',
|
||||
}
|
||||
},
|
||||
// getLyric2(musicInfo, isGetLyricx = true) {
|
||||
// const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicInfo.songmid, isGetLyricx)}`)
|
||||
// requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => {
|
||||
// if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body)))
|
||||
// return decodeLyric({ lrcBase64: raw.toString('base64'), isGetLyricx }).then(base64Data => {
|
||||
// let lrcInfo
|
||||
// console.log(Buffer.from(base64Data, 'base64').toString())
|
||||
// try {
|
||||
// lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
|
||||
// } catch {
|
||||
// return Promise.reject(new Error('Get lyric failed'))
|
||||
// }
|
||||
// if (lrcInfo.tlyric) lrcInfo.tlyric = lrcInfo.tlyric.replace(lrcTools.rxps.wordTimeAll, '')
|
||||
// lrcInfo.lxlyric = lrcTools.parse(lrcInfo.lyric)
|
||||
// // console.log(lrcInfo.lyric)
|
||||
// // console.log(lrcInfo.tlyric)
|
||||
// // console.log(lrcInfo.lxlyric)
|
||||
// // console.log(JSON.stringify(lrcInfo))
|
||||
// })
|
||||
// })
|
||||
// return requestObj
|
||||
// },
|
||||
getLyric(musicInfo, isGetLyricx = true) {
|
||||
// this.getLyric2(musicInfo)
|
||||
const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicInfo.songmid, isGetLyricx)}`)
|
||||
requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => {
|
||||
if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body)))
|
||||
return decodeLyric({ lrcBase64: raw.toString('base64'), isGetLyricx }).then(base64Data => {
|
||||
// let lrcInfo
|
||||
// try {
|
||||
// lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
|
||||
// } catch {
|
||||
// return Promise.reject(new Error('Get lyric failed'))
|
||||
// }
|
||||
let lrcInfo
|
||||
// console.log(Buffer.from(base64Data, 'base64').toString())
|
||||
try {
|
||||
lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
|
||||
} catch (err) {
|
||||
return Promise.reject(new Error('Get lyric failed'))
|
||||
}
|
||||
// console.log(lrcInfo)
|
||||
if (lrcInfo.tlyric) lrcInfo.tlyric = lrcInfo.tlyric.replace(lrcTools.rxps.wordTimeAll, '')
|
||||
try {
|
||||
lrcInfo.lxlyric = lrcTools.parse(lrcInfo.lyric)
|
||||
} catch {
|
||||
lrcInfo.lxlyric = ''
|
||||
}
|
||||
lrcInfo.lyric = lrcInfo.lyric.replace(lrcTools.rxps.wordTimeAll, '')
|
||||
if (!existTimeExp.test(lrcInfo.lyric)) return Promise.reject(new Error('Get lyric failed'))
|
||||
// console.log(lrcInfo)
|
||||
return lrcInfo
|
||||
})
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// import '../../polyfill/array.find'
|
||||
|
||||
import { httpFetch } from '../../request'
|
||||
import { formatPlayTime, decodeName } from '../../index'
|
||||
// import { debug } from '../../utils/env'
|
||||
import { formatSinger } from './util'
|
||||
|
||||
export default {
|
||||
regExps: {
|
||||
mInfo: /level:(\w+),bitrate:(\d+),format:(\w+),size:([\w.]+)/,
|
||||
},
|
||||
limit: 30,
|
||||
total: 0,
|
||||
page: 0,
|
||||
allPage: 1,
|
||||
// cancelFn: null,
|
||||
musicSearch(str, page, limit) {
|
||||
const musicSearchRequestObj = httpFetch(`http://search.kuwo.cn/r.s?client=kt&all=${encodeURIComponent(str)}&pn=${page - 1}&rn=${limit}&uid=794762570&ver=kwplayer_ar_9.2.2.1&vipver=1&show_copyright_off=1&newver=1&ft=music&cluster=0&strategy=2012&encoding=utf8&rformat=json&vermerge=1&mobi=1&issubtitle=1`)
|
||||
return musicSearchRequestObj.promise
|
||||
},
|
||||
// getImg(songId) {
|
||||
// return httpGet(`http://player.kuwo.cn/webmusic/sj/dtflagdate?flag=6&rid=MUSIC_${songId}`)
|
||||
// },
|
||||
// getLrc(songId) {
|
||||
// return httpGet(`http://mobile.kuwo.cn/mpage/html5/songinfoandlrc?mid=${songId}&flag=0`)
|
||||
// },
|
||||
handleResult(rawData) {
|
||||
const result = []
|
||||
if (!rawData) return result
|
||||
// console.log(rawData)
|
||||
for (let i = 0; i < rawData.length; i++) {
|
||||
const info = rawData[i]
|
||||
let songId = info.MUSICRID.replace('MUSIC_', '')
|
||||
// const format = (info.FORMATS || info.formats).split('|')
|
||||
|
||||
if (!info.N_MINFO) {
|
||||
console.log('N_MINFO is undefined')
|
||||
return null
|
||||
}
|
||||
|
||||
const types = []
|
||||
const _types = {}
|
||||
|
||||
let infoArr = info.N_MINFO.split(';')
|
||||
for (let info of infoArr) {
|
||||
info = info.match(this.regExps.mInfo)
|
||||
if (info) {
|
||||
switch (info[2]) {
|
||||
case '4000':
|
||||
types.push({ type: 'flac24bit', size: info[4] })
|
||||
_types.flac24bit = {
|
||||
size: info[4].toLocaleUpperCase(),
|
||||
}
|
||||
break
|
||||
case '2000':
|
||||
types.push({ type: 'flac', size: info[4] })
|
||||
_types.flac = {
|
||||
size: info[4].toLocaleUpperCase(),
|
||||
}
|
||||
break
|
||||
case '320':
|
||||
types.push({ type: '320k', size: info[4] })
|
||||
_types['320k'] = {
|
||||
size: info[4].toLocaleUpperCase(),
|
||||
}
|
||||
break
|
||||
case '192':
|
||||
case '128':
|
||||
types.push({ type: '128k', size: info[4] })
|
||||
_types['128k'] = {
|
||||
size: info[4].toLocaleUpperCase(),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
types.reverse()
|
||||
|
||||
let interval = parseInt(info.DURATION)
|
||||
|
||||
result.push({
|
||||
name: decodeName(info.SONGNAME),
|
||||
singer: formatSinger(decodeName(info.ARTIST)),
|
||||
source: 'kw',
|
||||
// img = (info.album.name === '' || info.album.name === '空')
|
||||
// ? `http://player.kuwo.cn/webmusic/sj/dtflagdate?flag=6&rid=MUSIC_160911.jpg`
|
||||
// : `https://y.gtimg.cn/music/photo_new/T002R500x500M000${info.album.mid}.jpg`
|
||||
songmid: songId,
|
||||
albumId: decodeName(info.ALBUMID || ''),
|
||||
interval: Number.isNaN(interval) ? 0 : formatPlayTime(interval),
|
||||
albumName: info.ALBUM ? decodeName(info.ALBUM) : '',
|
||||
lrc: null,
|
||||
img: null,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
}
|
||||
// console.log(result)
|
||||
return result
|
||||
},
|
||||
search(str, page = 1, limit, retryNum = 0) {
|
||||
if (retryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
if (limit == null) limit = this.limit
|
||||
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
|
||||
return this.musicSearch(str, page, limit).then(({ body: result }) => {
|
||||
// console.log(result)
|
||||
if (!result || (result.TOTAL !== '0' && result.SHOW === '0')) return this.search(str, page, limit, ++retryNum)
|
||||
let list = this.handleResult(result.abslist)
|
||||
|
||||
if (list == null) return this.search(str, page, limit, ++retryNum)
|
||||
|
||||
this.total = parseInt(result.TOTAL)
|
||||
this.page = page
|
||||
this.allPage = Math.ceil(this.total / limit)
|
||||
|
||||
return Promise.resolve({
|
||||
list,
|
||||
allPage: this.allPage,
|
||||
total: this.total,
|
||||
limit,
|
||||
source: 'kw',
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
export default {
|
||||
getPic({ songmid }) {
|
||||
const requestObj = httpFetch(`http://artistpicserver.kuwo.cn/pic.web?corp=kuwo&type=rid_pic&pictype=500&size=500&rid=${songmid}`)
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => /^http/.test(body) ? body : null)
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { formatPlayTime, decodeName } from '../../index'
|
||||
import { formatSinger, objStr2JSON } from './util'
|
||||
import album from './album'
|
||||
|
||||
export default {
|
||||
_requestObj_tags: null,
|
||||
_requestObj_hotTags: null,
|
||||
_requestObj_list: null,
|
||||
limit_list: 36,
|
||||
limit_song: 10000,
|
||||
successCode: 200,
|
||||
sortList: [
|
||||
{
|
||||
name: '最新',
|
||||
id: 'new',
|
||||
},
|
||||
{
|
||||
name: '最热',
|
||||
id: 'hot',
|
||||
},
|
||||
],
|
||||
regExps: {
|
||||
mInfo: /level:(\w+),bitrate:(\d+),format:(\w+),size:([\w.]+)/,
|
||||
// http://www.kuwo.cn/playlist_detail/2886046289
|
||||
// https://m.kuwo.cn/h5app/playlist/2736267853?t=qqfriend
|
||||
listDetailLink: /^.+\/playlist(?:_detail)?\/(\d+)(?:\?.*|&.*$|#.*$|$)/,
|
||||
},
|
||||
tagsUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getTagList?cmd=rcm_keyword_playlist&user=0&prod=kwplayer_pc_9.0.5.0&vipver=9.0.5.0&source=kwplayer_pc_9.0.5.0&loginUid=0&loginSid=0&appUid=76039576',
|
||||
hotTagUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmTagList?loginUid=0&loginSid=0&appUid=76039576',
|
||||
getListUrl({ sortId, id, type, page }) {
|
||||
if (!id) return `http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmPlayList?loginUid=0&loginSid=0&appUid=76039576&&pn=${page}&rn=${this.limit_list}&order=${sortId}`
|
||||
switch (type) {
|
||||
case '10000': return `http://wapi.kuwo.cn/api/pc/classify/playlist/getTagPlayList?loginUid=0&loginSid=0&appUid=76039576&pn=${page}&id=${id}&rn=${this.limit_list}`
|
||||
case '43': return `http://mobileinterfaces.kuwo.cn/er.s?type=get_pc_qz_data&f=web&id=${id}&prod=pc`
|
||||
}
|
||||
// http://wapi.kuwo.cn/api/pc/classify/playlist/getTagPlayList?loginUid=0&loginSid=0&appUid=76039576&id=173&pn=1&rn=100
|
||||
},
|
||||
getListDetailUrl(id, page) {
|
||||
// http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=2858093057&pn=0&rn=100&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1
|
||||
return `http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}&rn=${this.limit_song}&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1`
|
||||
// http://mobileinterfaces.kuwo.cn/er.s?type=get_pc_qz_data&f=web&id=140&prod=pc
|
||||
},
|
||||
|
||||
// http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=2849349915&pn=0&rn=100&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1
|
||||
// 获取标签
|
||||
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(this.tagsUrl)
|
||||
return this._requestObj_tags.promise.then(({ body }) => {
|
||||
if (body.code !== this.successCode) return this.getTag(++tryNum)
|
||||
return this.filterTagInfo(body.data)
|
||||
})
|
||||
},
|
||||
// 获取标签
|
||||
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(this.hotTagUrl)
|
||||
return this._requestObj_hotTags.promise.then(({ body }) => {
|
||||
if (body.code !== this.successCode) return this.getHotTag(++tryNum)
|
||||
return this.filterInfoHotTag(body.data[0].data)
|
||||
})
|
||||
},
|
||||
filterInfoHotTag(rawList) {
|
||||
return rawList.map(item => ({
|
||||
id: `${item.id}-${item.digest}`,
|
||||
name: item.name,
|
||||
source: 'kw',
|
||||
}))
|
||||
},
|
||||
filterTagInfo(rawList) {
|
||||
return rawList.map(type => ({
|
||||
name: type.name,
|
||||
list: type.data.map(item => ({
|
||||
parent_id: type.id,
|
||||
parent_name: type.name,
|
||||
id: `${item.id}-${item.digest}`,
|
||||
name: item.name,
|
||||
source: 'kw',
|
||||
})),
|
||||
}))
|
||||
},
|
||||
|
||||
// 获取列表数据
|
||||
getList(sortId, tagId, page, tryNum = 0) {
|
||||
if (this._requestObj_list) this._requestObj_list.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
let id
|
||||
let type
|
||||
if (tagId) {
|
||||
let arr = tagId.split('-')
|
||||
id = arr[0]
|
||||
type = arr[1]
|
||||
} else {
|
||||
id = null
|
||||
}
|
||||
this._requestObj_list = httpFetch(this.getListUrl({ sortId, id, type, page }))
|
||||
return this._requestObj_list.promise.then(({ body }) => {
|
||||
if (!id || type == '10000') {
|
||||
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
||||
return {
|
||||
list: this.filterList(body.data.data),
|
||||
total: body.data.total,
|
||||
page: body.data.pn,
|
||||
limit: body.data.rn,
|
||||
source: 'kw',
|
||||
}
|
||||
} else if (!body.length) {
|
||||
return this.getList(sortId, tagId, page, ++tryNum)
|
||||
}
|
||||
return {
|
||||
list: this.filterList2(body),
|
||||
total: 1000,
|
||||
page,
|
||||
limit: 1000,
|
||||
source: 'kw',
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 格式化播放数量
|
||||
* @param {*} num
|
||||
*/
|
||||
formatPlayCount(num) {
|
||||
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
|
||||
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
|
||||
return num
|
||||
},
|
||||
filterList(rawData) {
|
||||
return rawData.map(item => ({
|
||||
play_count: this.formatPlayCount(item.listencnt),
|
||||
id: `digest-${item.digest}__${item.id}`,
|
||||
author: item.uname,
|
||||
name: item.name,
|
||||
// time: item.publish_time,
|
||||
total: item.total,
|
||||
img: item.img,
|
||||
grade: item.favorcnt / 10,
|
||||
desc: item.desc,
|
||||
source: 'kw',
|
||||
}))
|
||||
},
|
||||
filterList2(rawData) {
|
||||
// console.log(rawData)
|
||||
const list = []
|
||||
rawData.forEach(item => {
|
||||
if (!item.label) return
|
||||
list.push(...item.list.map(item => ({
|
||||
play_count: item.play_count && this.formatPlayCount(item.listencnt),
|
||||
id: `digest-${item.digest}__${item.id}`,
|
||||
author: item.uname,
|
||||
name: item.name,
|
||||
total: item.total,
|
||||
// time: item.publish_time,
|
||||
img: item.img,
|
||||
grade: item.favorcnt && item.favorcnt / 10,
|
||||
desc: item.desc,
|
||||
source: 'kw',
|
||||
})))
|
||||
})
|
||||
return list
|
||||
},
|
||||
|
||||
getListDetailDigest8(id, page, tryNum = 0) {
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
|
||||
const requestObj = httpFetch(this.getListDetailUrl(id, page))
|
||||
return requestObj.promise.then(({ body }) => {
|
||||
if (body.result !== 'ok') return this.getListDetail(id, page, ++tryNum)
|
||||
return {
|
||||
list: this.filterListDetail(body.musiclist),
|
||||
page,
|
||||
limit: body.rn,
|
||||
total: body.total,
|
||||
source: 'kw',
|
||||
info: {
|
||||
name: body.title,
|
||||
img: body.pic,
|
||||
desc: body.info,
|
||||
author: body.uname,
|
||||
play_count: this.formatPlayCount(body.playnum),
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
getListDetailDigest5Info(id, tryNum = 0) {
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
const requestObj = httpFetch(`http://qukudata.kuwo.cn/q.k?op=query&cont=ninfo&node=${id}&pn=0&rn=1&fmt=json&src=mbox&level=2`)
|
||||
return requestObj.promise.then(({ statusCode, body }) => {
|
||||
if (statusCode != 200 || !body.child) return this.getListDetail(id, ++tryNum)
|
||||
// console.log(body)
|
||||
return body.child.length ? body.child[0].sourceid : null
|
||||
})
|
||||
},
|
||||
getListDetailDigest5Music(id, page, tryNum = 0) {
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
const requestObj = httpFetch(`http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}}&rn=${this.limit_song}&encode=utf-8&keyset=pl2012&identity=kuwo&pcmp4=1`)
|
||||
return requestObj.promise.then(({ body }) => {
|
||||
// console.log(body)
|
||||
if (body.result !== 'ok') return this.getListDetail(id, page, ++tryNum)
|
||||
return {
|
||||
list: this.filterListDetail(body.musiclist),
|
||||
page,
|
||||
limit: body.rn,
|
||||
total: body.total,
|
||||
source: 'kw',
|
||||
info: {
|
||||
name: body.title,
|
||||
img: body.pic,
|
||||
desc: body.info,
|
||||
author: body.uname,
|
||||
play_count: this.formatPlayCount(body.playnum),
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
async getListDetailDigest5(id, page, retryNum) {
|
||||
const detailId = await this.getListDetailDigest5Info(id, retryNum)
|
||||
return this.getListDetailDigest5Music(detailId, page, retryNum)
|
||||
},
|
||||
|
||||
// 获取歌曲列表内的音乐
|
||||
getListDetail(id, page, retryNum = 0) {
|
||||
// console.log(id)
|
||||
if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
|
||||
else if (/^digest-/.test(id)) {
|
||||
let [digest, _id] = id.split('__')
|
||||
digest = digest.replace('digest-', '')
|
||||
id = _id
|
||||
switch (digest) {
|
||||
case '8':
|
||||
break
|
||||
case '13': return album.getAlbumListDetail(id, page, retryNum)
|
||||
case '5':
|
||||
default: return this.getListDetailDigest5(id, page, retryNum)
|
||||
}
|
||||
}
|
||||
return this.getListDetailDigest8(id, page, retryNum)
|
||||
},
|
||||
filterListDetail(rawData) {
|
||||
// console.log(rawData)
|
||||
return rawData.map(item => {
|
||||
let infoArr = item.N_MINFO.split(';')
|
||||
let types = []
|
||||
let _types = {}
|
||||
for (let info of infoArr) {
|
||||
info = info.match(this.regExps.mInfo)
|
||||
if (info) {
|
||||
switch (info[2]) {
|
||||
case '4000':
|
||||
types.push({ type: 'flac24bit', size: info[4] })
|
||||
_types.flac24bit = {
|
||||
size: info[4].toLocaleUpperCase(),
|
||||
}
|
||||
break
|
||||
case '2000':
|
||||
types.push({ type: 'flac', size: info[4] })
|
||||
_types.flac = {
|
||||
size: info[4].toLocaleUpperCase(),
|
||||
}
|
||||
break
|
||||
case '320':
|
||||
types.push({ type: '320k', size: info[4] })
|
||||
_types['320k'] = {
|
||||
size: info[4].toLocaleUpperCase(),
|
||||
}
|
||||
break
|
||||
case '192':
|
||||
case '128':
|
||||
types.push({ type: '128k', size: info[4] })
|
||||
_types['128k'] = {
|
||||
size: info[4].toLocaleUpperCase(),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
types.reverse()
|
||||
|
||||
return {
|
||||
singer: formatSinger(decodeName(item.artist)),
|
||||
name: decodeName(item.name),
|
||||
albumName: decodeName(item.album),
|
||||
albumId: item.albumid,
|
||||
songmid: item.id,
|
||||
source: 'kw',
|
||||
interval: formatPlayTime(parseInt(item.duration)),
|
||||
img: null,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
getTags() {
|
||||
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'kw' }))
|
||||
},
|
||||
getDetailPageUrl(id) {
|
||||
if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
|
||||
else if (/^digest-/.test(id)) {
|
||||
let result = id.split('__')
|
||||
id = result[1]
|
||||
}
|
||||
return `http://www.kuwo.cn/playlist_detail/${id}`
|
||||
},
|
||||
|
||||
search(text, page, limit = 20) {
|
||||
return httpFetch(`http://search.kuwo.cn/r.s?all=${encodeURIComponent(text)}&pn=${page - 1}&rn=${limit}&rformat=json&encoding=utf8&ver=mbox&vipver=MUSIC_8.7.7.0_BCS37&plat=pc&devid=28156413&ft=playlist&pay=0&needliveshow=0`)
|
||||
.promise.then(({ body }) => {
|
||||
body = objStr2JSON(body)
|
||||
// console.log(body)
|
||||
return {
|
||||
list: body.abslist.map(item => {
|
||||
return {
|
||||
play_count: this.formatPlayCount(item.playcnt),
|
||||
id: item.playlistid,
|
||||
author: decodeName(item.nickname),
|
||||
name: decodeName(item.name),
|
||||
total: item.songnum,
|
||||
// time: item.publish_time,
|
||||
img: item.pic,
|
||||
desc: decodeName(item.intro),
|
||||
source: 'kw',
|
||||
}
|
||||
}),
|
||||
limit,
|
||||
total: parseInt(body.TOTAL),
|
||||
source: 'kw',
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// getList
|
||||
// getTags
|
||||
// getListDetail
|
||||
@@ -0,0 +1,32 @@
|
||||
// import { httpFetch } from '../../request'
|
||||
import { decodeName } from '../../index'
|
||||
import { tokenRequest } from './util'
|
||||
|
||||
|
||||
export default {
|
||||
regExps: {
|
||||
relWord: /RELWORD=(.+)/,
|
||||
},
|
||||
requestObj: null,
|
||||
async tempSearch(str) {
|
||||
this.cancelTempSearch()
|
||||
this.requestObj = await tokenRequest(`http://www.kuwo.cn/api/www/search/searchKey?key=${encodeURIComponent(str)}`)
|
||||
return this.requestObj.promise.then(({ body }) => {
|
||||
// console.log(body)
|
||||
if (body.code !== 200) return Promise.reject(new Error('请求失败'))
|
||||
return body
|
||||
})
|
||||
},
|
||||
handleResult(rawData) {
|
||||
return rawData.map(info => {
|
||||
let matchResult = info.match(this.regExps.relWord)
|
||||
return matchResult ? decodeName(matchResult[1]) : ''
|
||||
})
|
||||
},
|
||||
cancelTempSearch() {
|
||||
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
|
||||
},
|
||||
async search(str) {
|
||||
return this.tempSearch(str).then(result => this.handleResult(result.data))
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import { httpGet, httpFetch } from '../../request'
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { rendererInvoke } from '@common/rendererIpc'
|
||||
|
||||
const kw_token = {
|
||||
token: null,
|
||||
isGetingToken: false,
|
||||
}
|
||||
|
||||
// const translationMap = {
|
||||
// "{'": '{"',
|
||||
// "'}\n": '"}',
|
||||
// "'}": '"}',
|
||||
// "':'": '":"',
|
||||
// "','": '","',
|
||||
// "':{'": '":{"',
|
||||
// "':['": '":["',
|
||||
// "'}],'": '"}],"',
|
||||
// "':[{'": '":[{"',
|
||||
// "'},'": '"},"',
|
||||
// "'},{'": '"},{"',
|
||||
// "':[],'": '":[],"',
|
||||
// "':{},'": '":{},"',
|
||||
// "'}]}": '"}]}',
|
||||
// }
|
||||
|
||||
// export const objStr2JSON = str => {
|
||||
// return JSON.parse(str.replace(/(^{'|'}\n$|'}$|':'|','|':\[{'|'}\],'|':{'|'},'|'},{'|':\['|':\[\],'|':{},'|'}]})/g, s => translationMap[s]))
|
||||
// }
|
||||
|
||||
export const objStr2JSON = str => {
|
||||
return JSON.parse(str.replace(/('(?=(,\s*')))|('(?=:))|((?<=([:,]\s*))')|((?<={)')|('(?=}))/g, '"'))
|
||||
}
|
||||
|
||||
|
||||
export const formatSinger = rawData => rawData.replace(/&/g, '、')
|
||||
|
||||
export const matchToken = headers => {
|
||||
try {
|
||||
return headers['set-cookie'][0].match(/kw_token=(\w+)/)[1]
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
|
||||
|
||||
|
||||
export const getToken = (retryNum = 0) => new Promise((resolve, reject) => {
|
||||
if (retryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
|
||||
if (kw_token.isGetingToken) return wait(1000).then(() => getToken(retryNum).then(token => resolve(token)))
|
||||
if (kw_token.token) return resolve(kw_token.token)
|
||||
kw_token.isGetingToken = true
|
||||
httpGet('http://www.kuwo.cn/', (err, resp) => {
|
||||
kw_token.isGetingToken = false
|
||||
if (err) return getToken(++retryNum)
|
||||
if (resp.statusCode != 200) return reject(new Error('获取失败'))
|
||||
const token = kw_token.token = matchToken(resp.headers)
|
||||
resolve(token)
|
||||
})
|
||||
})
|
||||
|
||||
export const decodeLyric = base64Data => rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.handle_kw_decode_lyric, base64Data)
|
||||
|
||||
export const tokenRequest = async(url, options = {}) => {
|
||||
let token = kw_token.token
|
||||
if (!token) token = await getToken()
|
||||
if (!options.headers) {
|
||||
options.headers = {
|
||||
Referer: 'http://www.kuwo.cn/',
|
||||
csrf: token,
|
||||
cookie: 'kw_token=' + token,
|
||||
}
|
||||
}
|
||||
const requestObj = httpFetch(url, options)
|
||||
requestObj.promise = requestObj.promise.then(resp => {
|
||||
// console.log(resp)
|
||||
if (resp.statusCode == 200) {
|
||||
kw_token.token = matchToken(resp.headers)
|
||||
}
|
||||
return resp
|
||||
})
|
||||
return requestObj
|
||||
}
|
||||
|
||||
export const lrcTools = {
|
||||
rxps: {
|
||||
wordLine: /^(\[\d{1,2}:.*\d{1,4}\])\s*(\S+(?:\s+\S+)*)?\s*/,
|
||||
tagLine: /\[(ver|ti|ar|al|offset|by|kuwo):\s*(\S+(?:\s+\S+)*)\s*\]/,
|
||||
wordTimeAll: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/g,
|
||||
wordTime: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/,
|
||||
},
|
||||
offset: 1,
|
||||
offset2: 1,
|
||||
isOK: false,
|
||||
lines: [],
|
||||
tags: [],
|
||||
getWordInfo(str, str2, prevWord) {
|
||||
const offset = parseInt(str)
|
||||
const offset2 = parseInt(str2)
|
||||
let startTime = Math.abs((offset + offset2) / (this.offset * 2))
|
||||
let endTime = Math.abs((offset - offset2) / (this.offset2 * 2)) + startTime
|
||||
if (prevWord) {
|
||||
if (startTime < prevWord.endTime) {
|
||||
prevWord.endTime = startTime
|
||||
if (prevWord.startTime > prevWord.endTime) {
|
||||
prevWord.startTime = prevWord.endTime
|
||||
}
|
||||
|
||||
prevWord.newTimeStr = `<${prevWord.startTime},${prevWord.endTime - prevWord.startTime}>`
|
||||
// console.log(prevWord)
|
||||
}
|
||||
}
|
||||
return {
|
||||
startTime,
|
||||
endTime,
|
||||
timeStr: `<${startTime},${endTime - startTime}>`,
|
||||
}
|
||||
},
|
||||
parseLine(line) {
|
||||
if (line.length < 6) return
|
||||
let result = this.rxps.wordLine.exec(line)
|
||||
if (result) {
|
||||
const time = result[1]
|
||||
let words = result[2]
|
||||
if (words == null) {
|
||||
words = ''
|
||||
}
|
||||
const wordTimes = words.match(this.rxps.wordTimeAll)
|
||||
if (!wordTimes) return
|
||||
// console.log(wordTimes)
|
||||
let preTimeInfo
|
||||
for (const timeStr of wordTimes) {
|
||||
const result = this.rxps.wordTime.exec(timeStr)
|
||||
const wordInfo = this.getWordInfo(result[1], result[2], preTimeInfo)
|
||||
words = words.replace(timeStr, wordInfo.timeStr)
|
||||
if (preTimeInfo?.newTimeStr) words = words.replace(preTimeInfo.timeStr, preTimeInfo.newTimeStr)
|
||||
preTimeInfo = wordInfo
|
||||
}
|
||||
this.lines.push(time + words)
|
||||
return
|
||||
}
|
||||
result = this.rxps.tagLine.exec(line)
|
||||
if (!result) return
|
||||
if (result[1] == 'kuwo') {
|
||||
let content = result[2]
|
||||
if (content != null && content.includes('][')) {
|
||||
content = content.substring(0, content.indexOf(']['))
|
||||
}
|
||||
const valueOf = parseInt(content, 8)
|
||||
this.offset = Math.trunc(valueOf / 10)
|
||||
this.offset2 = Math.trunc(valueOf % 10)
|
||||
if (this.offset == 0 || Number.isNaN(this.offset) || this.offset2 == 0 || Number.isNaN(this.offset2)) {
|
||||
this.isOK = false
|
||||
}
|
||||
} else {
|
||||
this.tags.push(line)
|
||||
}
|
||||
},
|
||||
parse(lrc) {
|
||||
// console.log(lrc)
|
||||
const lines = lrc.split(/\r\n|\r|\n/)
|
||||
const tools = Object.create(this)
|
||||
tools.isOK = true
|
||||
tools.offset = 1
|
||||
tools.offset2 = 1
|
||||
tools.lines = []
|
||||
tools.tags = []
|
||||
|
||||
for (const line of lines) {
|
||||
if (!tools.isOK) throw new Error('failed')
|
||||
tools.parseLine(line)
|
||||
}
|
||||
if (!tools.lines.length) return ''
|
||||
let lrcs = tools.lines.join('\n')
|
||||
if (tools.tags.length) lrcs = `${tools.tags.join('\n')}\n${lrcs}`
|
||||
// console.log(lrcs)
|
||||
return lrcs
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
export default {
|
||||
getAlbum(songInfo, tryNum = 0) {
|
||||
let requestObj = httpFetch(`http://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/queryAlbumSong?albumId=${songInfo.albumId}&pageNo=1`, {
|
||||
headers: {
|
||||
sign: '46DB65104950B98FE451AD41047CC6C4',
|
||||
timestamp: 1603451430776,
|
||||
appId: 'yyapp2',
|
||||
mode: 'android',
|
||||
ua: 'Android_migu',
|
||||
version: '6.9.4',
|
||||
osVersion: 'android 7.0',
|
||||
'User-Agent': 'okhttp/3.9.1',
|
||||
channel: '0146832',
|
||||
},
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
if (body.code !== '000000') {
|
||||
if (tryNum > 5) return Promise.reject('获取专辑失败')
|
||||
let tryRequestObj = this.getAlbum(songInfo, ++tryNum)
|
||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||
return tryRequestObj.promise
|
||||
}
|
||||
console.log(body)
|
||||
return body.songList
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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/mg/${songInfo.copyrightId}/${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
|
||||
},
|
||||
}
|
||||
|
||||
export default api_test
|
||||
@@ -0,0 +1,95 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import album from './album'
|
||||
import { dateFormat2 } from '../../index'
|
||||
|
||||
export default {
|
||||
_requestObj: null,
|
||||
_requestObj2: null,
|
||||
_requestObj3: null,
|
||||
async getSongId(musicInfo) {
|
||||
if (this._requestObj3) this._requestObj3.cancelHttp()
|
||||
this.requestObj3 = album.getAlbum(musicInfo)
|
||||
const list = await this.requestObj3.promise
|
||||
const copyrightId = musicInfo.copyrightId
|
||||
let info = list.find(s => s.copyrightId == copyrightId)
|
||||
const songmid = musicInfo.songmid
|
||||
if (!info) info = list.find(s => s.songId == songmid)
|
||||
return info ? info.songId : null
|
||||
},
|
||||
async getComment(musicInfo, page = 1, limit = 10) {
|
||||
if (this._requestObj) this._requestObj.cancelHttp()
|
||||
if (!musicInfo.songId) {
|
||||
let id = await this.getSongId(musicInfo)
|
||||
if (!id) throw new Error('获取评论失败')
|
||||
musicInfo.songId = id
|
||||
}
|
||||
|
||||
const _requestObj = httpFetch(`https://music.migu.cn/v3/api/comment/listComments?targetId=${musicInfo.songId}&pageSize=${limit}&pageNo=${page}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4195.1 Safari/537.36',
|
||||
Referer: 'https://music.migu.cn',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
// console.log(body)
|
||||
if (statusCode != 200 || body.returnCode !== '000000') throw new Error('获取评论失败')
|
||||
return { source: 'mg', comments: this.filterComment(body.data.items), total: body.data.itemTotal, page, limit, maxPage: Math.ceil(body.data.itemTotal / limit) || 1 }
|
||||
},
|
||||
async getHotComment(musicInfo, page = 1, limit = 5) {
|
||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
||||
|
||||
if (!musicInfo.songId) {
|
||||
let id = await this.getSongId(musicInfo)
|
||||
if (!id) throw new Error('获取评论失败')
|
||||
musicInfo.songId = id
|
||||
}
|
||||
|
||||
const _requestObj2 = httpFetch(`https://music.migu.cn/v3/api/comment/listTopComments?targetId=${musicInfo.songId}&pageSize=${limit}&pageNo=${page}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4195.1 Safari/537.36',
|
||||
Referer: 'https://music.migu.cn',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj2.promise
|
||||
// console.log(body)
|
||||
if (statusCode != 200 || body.returnCode !== '000000') throw new Error('获取热门评论失败')
|
||||
return { source: 'mg', comments: this.filterComment(body.data.items), total: body.data.itemTotal, page, limit, maxPage: Math.ceil(body.data.itemTotal / limit) || 1 }
|
||||
},
|
||||
async getReplyComment(musicInfo, replyId, page = 1, limit = 10) {
|
||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
||||
|
||||
const _requestObj2 = httpFetch(`https://music.migu.cn/v3/api/comment/listCommentsById?commentId=${replyId}&pageSize=${limit}&pageNo=${page}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Android712-AndroidPhone-8983-18-0-COMMENT-wifi',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj2.promise
|
||||
// console.log(body)
|
||||
if (statusCode != 200 || body.returnCode !== '000000') throw new Error('获取回复评论失败')
|
||||
return { source: 'mg', comments: this.filterComment(body.data.items) }
|
||||
},
|
||||
filterComment(rawList) {
|
||||
return rawList.map(item => ({
|
||||
id: item.commentId,
|
||||
text: item.body.split('\n'),
|
||||
time: item.createTime,
|
||||
timeStr: dateFormat2(new Date(item.createTime).getTime()),
|
||||
userName: item.author.name,
|
||||
avatar: item.author.avatar && item.author.avatar.startsWith('//') ? `http:${item.author.avatar}` : item.author.avatar,
|
||||
userId: item.author.id,
|
||||
likedCount: item.praiseCount,
|
||||
replyNum: item.replyTotal,
|
||||
reply: item.replyCommentList.map(c => ({
|
||||
id: c.commentId,
|
||||
text: c.body.split('\n'),
|
||||
time: c.createTime,
|
||||
timeStr: dateFormat2(new Date(c.createTime).getTime()),
|
||||
userName: c.author.name,
|
||||
avatar: c.author.avatar,
|
||||
userId: c.author.id,
|
||||
likedCount: c.praiseCount,
|
||||
replyNum: c.replyTotal,
|
||||
})),
|
||||
}))
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
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('http://jadeite.migu.cn:7090/music_search/v2/search/hotword')
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
if (statusCode != 200 || body.code !== '000000') throw new Error('获取热搜词失败')
|
||||
// console.log(body, statusCode)
|
||||
return { source: 'mg', list: this.filterList(body.data) }
|
||||
},
|
||||
filterList(rawList) {
|
||||
return rawList.map(item => item.word)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { apis } from '../api-source'
|
||||
import leaderboard from './leaderboard'
|
||||
import songList from './songList'
|
||||
import musicSearch from './musicSearch'
|
||||
import pic from './pic'
|
||||
import lyric from './lyric'
|
||||
import hotSearch from './hotSearch'
|
||||
import comment from './comment'
|
||||
|
||||
const mg = {
|
||||
songList,
|
||||
musicSearch,
|
||||
leaderboard,
|
||||
hotSearch,
|
||||
comment,
|
||||
getMusicUrl(songInfo, type) {
|
||||
return apis('mg').getMusicUrl(songInfo, type)
|
||||
},
|
||||
getLyric(songInfo) {
|
||||
return lyric.getLyric(songInfo)
|
||||
},
|
||||
getPic(songInfo) {
|
||||
return pic.getPic(songInfo)
|
||||
},
|
||||
getMusicDetailPageUrl(songInfo) {
|
||||
return `http://music.migu.cn/v3/music/song/${songInfo.copyrightId}`
|
||||
},
|
||||
}
|
||||
|
||||
export default mg
|
||||
@@ -0,0 +1,228 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { sizeFormate } from '../../index'
|
||||
|
||||
|
||||
// const boardList = [{ id: 'mg__27553319', name: '咪咕尖叫新歌榜', bangid: '27553319' }, { id: 'mg__27186466', name: '咪咕尖叫热歌榜', bangid: '27186466' }, { id: 'mg__27553408', name: '咪咕尖叫原创榜', bangid: '27553408' }, { id: 'mg__23189800', name: '咪咕港台榜', bangid: '23189800' }, { id: 'mg__23189399', name: '咪咕内地榜', bangid: '23189399' }, { id: 'mg__19190036', name: '咪咕欧美榜', bangid: '19190036' }, { id: 'mg__23189813', name: '咪咕日韩榜', bangid: '23189813' }, { id: 'mg__23190126', name: '咪咕彩铃榜', bangid: '23190126' }, { id: 'mg__15140045', name: '咪咕KTV榜', bangid: '15140045' }, { id: 'mg__15140034', name: '咪咕网络榜', bangid: '15140034' }, { id: 'mg__23217754', name: 'MV榜', bangid: '23217754' }, { id: 'mg__23218151', name: '新专辑榜', bangid: '23218151' }, { id: 'mg__21958042', name: 'iTunes榜', bangid: '21958042' }, { id: 'mg__21975570', name: 'billboard榜', bangid: '21975570' }, { id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815' }, { id: 'mg__22272904', name: '中国TOP排行榜', bangid: '22272904' }, { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943' }, { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437' }]
|
||||
|
||||
const boardList = [{ id: 'mg__27553319', name: '尖叫新歌榜', bangid: '27553319', webId: 'jianjiao_newsong' }, { id: 'mg__27186466', name: '尖叫热歌榜', bangid: '27186466', webId: 'jianjiao_hotsong' }, { id: 'mg__27553408', name: '尖叫原创榜', bangid: '27553408', webId: 'jianjiao_original' }, { id: 'mg__23189800', name: '港台榜', bangid: '23189800', webId: 'hktw' }, { id: 'mg__23189399', name: '内地榜', bangid: '23189399', webId: 'mainland' }, { id: 'mg__19190036', name: '欧美榜', bangid: '19190036', webId: 'eur_usa' }, { id: 'mg__23189813', name: '日韩榜', bangid: '23189813', webId: 'jpn_kor' }, { id: 'mg__23190126', name: '彩铃榜', bangid: '23190126', webId: 'coloring' }, { id: 'mg__15140045', name: 'KTV榜', bangid: '15140045', webId: 'ktv' }, { id: 'mg__15140034', name: '网络榜', bangid: '15140034', webId: 'network' }, { id: 'mg__21958042', name: '美国iTunes榜', bangid: '21958042', webId: 'itunes' }, { id: 'mg__21975570', name: '美国billboard榜', bangid: '21975570', webId: 'billboard' }, { id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815', webId: 'hito' }, { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943', webId: 'mnet' }, { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437', webId: 'uk' }]
|
||||
export default {
|
||||
limit: 200,
|
||||
list: [
|
||||
{
|
||||
id: 'mgyyb',
|
||||
name: '音乐榜',
|
||||
bangid: '27553319',
|
||||
},
|
||||
{
|
||||
id: 'mgysb',
|
||||
name: '影视榜',
|
||||
bangid: '23603721',
|
||||
},
|
||||
{
|
||||
id: 'mghybnd',
|
||||
name: '华语内地榜',
|
||||
bangid: '23603926',
|
||||
},
|
||||
{
|
||||
id: 'mghyjqbgt',
|
||||
name: '华语港台榜',
|
||||
bangid: '23603954',
|
||||
},
|
||||
{
|
||||
id: 'mgomb',
|
||||
name: '欧美榜',
|
||||
bangid: '23603974',
|
||||
},
|
||||
{
|
||||
id: 'mgrhb',
|
||||
name: '日韩榜',
|
||||
bangid: '23603982',
|
||||
},
|
||||
{
|
||||
id: 'mgwlb',
|
||||
name: '网络榜',
|
||||
bangid: '23604058',
|
||||
},
|
||||
{
|
||||
id: 'mgclb',
|
||||
name: '彩铃榜',
|
||||
bangid: '23604023',
|
||||
},
|
||||
{
|
||||
id: 'mgktvb',
|
||||
name: 'KTV榜',
|
||||
bangid: '23604040',
|
||||
},
|
||||
{
|
||||
id: 'mgrcb',
|
||||
name: '原创榜',
|
||||
bangid: '23604032',
|
||||
},
|
||||
],
|
||||
getUrl(id, page) {
|
||||
return `https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/querycontentbyId.do?columnId=${id}&needAll=0`
|
||||
// return `http://m.music.migu.cn/migu/remoting/cms_list_tag?nid=${id}&pageSize=${this.limit}&pageNo=${page - 1}`
|
||||
},
|
||||
successCode: '000000',
|
||||
requestBoardsObj: null,
|
||||
getBoardsData() {
|
||||
if (this.requestBoardsObj) this._requestBoardsObj.cancelHttp()
|
||||
this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/MIGUM3.0/v1.0/template/rank-list/release', {
|
||||
// this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/MIGUM2.0/v2.0/content/indexrank.do?templateVersion=8', {
|
||||
headers: {
|
||||
Referer: 'https://app.c.nf.migu.cn/',
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
|
||||
channel: '0146921',
|
||||
},
|
||||
})
|
||||
return this.requestBoardsObj.promise
|
||||
},
|
||||
getData(url) {
|
||||
const requestObj = httpFetch(url)
|
||||
return requestObj.promise
|
||||
},
|
||||
getSinger(singers) {
|
||||
let arr = []
|
||||
singers.forEach(singer => {
|
||||
arr.push(singer.name)
|
||||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
filterData(rawData) {
|
||||
// console.log(JSON.stringify(rawData))
|
||||
// console.log(rawData)
|
||||
let ids = new Set()
|
||||
const list = []
|
||||
rawData.forEach(({ objectInfo: item }) => {
|
||||
if (ids.has(item.copyrightId)) return
|
||||
ids.add(item.copyrightId)
|
||||
|
||||
const types = []
|
||||
const _types = {}
|
||||
item.newRateFormats && item.newRateFormats.forEach(type => {
|
||||
let size
|
||||
switch (type.formatType) {
|
||||
case 'PQ':
|
||||
size = sizeFormate(type.size ?? type.androidSize)
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
case 'HQ':
|
||||
size = sizeFormate(type.size ?? type.androidSize)
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
case 'SQ':
|
||||
size = sizeFormate(type.size ?? type.androidSize)
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
case 'ZQ':
|
||||
size = sizeFormate(type.size ?? type.androidSize)
|
||||
types.push({ type: 'flac24bit', size })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
const intervalTest = /(\d\d:\d\d)$/.test(item.length)
|
||||
|
||||
list.push({
|
||||
singer: this.getSinger(item.artists),
|
||||
name: item.songName,
|
||||
albumName: item.album,
|
||||
albumId: item.albumId,
|
||||
songmid: item.copyrightId,
|
||||
songId: item.songId,
|
||||
copyrightId: item.copyrightId,
|
||||
source: 'mg',
|
||||
interval: intervalTest ? RegExp.$1 : null,
|
||||
img: item.albumImgs && item.albumImgs.length ? item.albumImgs[0].img : null,
|
||||
lrc: null,
|
||||
lrcUrl: item.lrcUrl,
|
||||
mrcUrl: item.mrcUrl,
|
||||
trcUrl: item.trcUrl,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
filterBoardsData(rawList) {
|
||||
// console.log(rawList)
|
||||
let list = []
|
||||
for (const board of rawList) {
|
||||
if (board.template != 'group1') continue
|
||||
for (const item of board.itemList) {
|
||||
if ((item.template != 'row1' && item.template != 'grid1' && !item.actionUrl) || !item.actionUrl.includes('rank-info')) continue
|
||||
|
||||
let data = item.displayLogId.param
|
||||
list.push({
|
||||
id: 'mg__' + data.rankId,
|
||||
name: data.rankName,
|
||||
bangid: String(data.rankId),
|
||||
})
|
||||
}
|
||||
}
|
||||
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.data.contentItemList)
|
||||
// if (response.statusCode !== 200 || response.body.code !== this.successCode) return this.getBoards(retryNum)
|
||||
// const list = this.filterBoardsData(response.body.data.contentItemList)
|
||||
// // console.log(list)
|
||||
// // console.log(JSON.stringify(list))
|
||||
// this.list = list
|
||||
// return {
|
||||
// list,
|
||||
// source: 'mg',
|
||||
// }
|
||||
this.list = boardList
|
||||
return {
|
||||
list: boardList,
|
||||
source: 'mg',
|
||||
}
|
||||
},
|
||||
getList(bangid, page, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
return this.getData(this.getUrl(bangid, page)).then(({ statusCode, body }) => {
|
||||
// console.log(body)
|
||||
if (statusCode !== 200 || body.code !== this.successCode) return this.getList(bangid, page, retryNum)
|
||||
const list = this.filterData(body.columnInfo.contents)
|
||||
return {
|
||||
total: list.length,
|
||||
list,
|
||||
limit: this.limit,
|
||||
page,
|
||||
source: 'mg',
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getDetailPageUrl(id) {
|
||||
if (typeof id == 'string') id = id.replace('mg__', '')
|
||||
for (const item of boardList) {
|
||||
if (item.bangid == id) {
|
||||
return `https://music.migu.cn/v3/music/top/${item.webId}`
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { formatPlayTime } from '../../index'
|
||||
// import { sizeFormate } from '../../index'
|
||||
|
||||
|
||||
// const boardList = [{ id: 'mg__27553319', name: '咪咕尖叫新歌榜', bangid: '27553319' }, { id: 'mg__27186466', name: '咪咕尖叫热歌榜', bangid: '27186466' }, { id: 'mg__27553408', name: '咪咕尖叫原创榜', bangid: '27553408' }, { id: 'mg__23189800', name: '咪咕港台榜', bangid: '23189800' }, { id: 'mg__23189399', name: '咪咕内地榜', bangid: '23189399' }, { id: 'mg__19190036', name: '咪咕欧美榜', bangid: '19190036' }, { id: 'mg__23189813', name: '咪咕日韩榜', bangid: '23189813' }, { id: 'mg__23190126', name: '咪咕彩铃榜', bangid: '23190126' }, { id: 'mg__15140045', name: '咪咕KTV榜', bangid: '15140045' }, { id: 'mg__15140034', name: '咪咕网络榜', bangid: '15140034' }, { id: 'mg__23217754', name: 'MV榜', bangid: '23217754' }, { id: 'mg__23218151', name: '新专辑榜', bangid: '23218151' }, { id: 'mg__21958042', name: 'iTunes榜', bangid: '21958042' }, { id: 'mg__21975570', name: 'billboard榜', bangid: '21975570' }, { id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815' }, { id: 'mg__22272904', name: '中国TOP排行榜', bangid: '22272904' }, { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943' }, { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437' }]
|
||||
const boardList = [
|
||||
{ id: 'mg__27553319', name: '尖叫新歌榜', bangid: '27553319', webId: 'jianjiao_newsong' },
|
||||
{ id: 'mg__27186466', name: '尖叫热歌榜', bangid: '27186466', webId: 'jianjiao_hotsong' },
|
||||
{ id: 'mg__27553408', name: '尖叫原创榜', bangid: '27553408', webId: 'jianjiao_original' },
|
||||
{ id: 'mg__migumusic', name: '音乐榜', bangid: 'migumusic', webId: 'migumusic' },
|
||||
{ id: 'mg__movies', name: '影视榜', bangid: 'movies', webId: 'movies' },
|
||||
{ id: 'mg__23189800', name: '港台榜', bangid: '23189800', webId: 'hktw' },
|
||||
{ id: 'mg__23189399', name: '内地榜', bangid: '23189399', webId: 'mainland' },
|
||||
{ id: 'mg__19190036', name: '欧美榜', bangid: '19190036', webId: 'eur_usa' },
|
||||
{ id: 'mg__23189813', name: '日韩榜', bangid: '23189813', webId: 'jpn_kor' },
|
||||
{ id: 'mg__23190126', name: '彩铃榜', bangid: '23190126', webId: 'coloring' },
|
||||
{ id: 'mg__15140045', name: 'KTV榜', bangid: '15140045', webId: 'ktv' },
|
||||
{ id: 'mg__15140034', name: '网络榜', bangid: '15140034', webId: 'network' },
|
||||
{ id: 'mg__23217754', name: 'MV榜', bangid: '23217754', webId: 'mv' },
|
||||
{ id: 'mg__23218151', name: '新专辑榜', bangid: '23218151', webId: 'newalbum' },
|
||||
{ id: 'mg__21958042', name: '美国iTunes榜', bangid: '21958042', webId: 'itunes' },
|
||||
{ id: 'mg__21975570', name: '美国billboard榜', bangid: '21975570', webId: 'billboard' },
|
||||
{ id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815', webId: 'hito' },
|
||||
{ id: 'mg__22272904', name: '中国TOP排行榜', bangid: '22272904' },
|
||||
{ id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943', webId: 'mnet' },
|
||||
{ id: 'mg__22273437', name: '英国UK榜', bangid: '22273437', webId: 'uk' },
|
||||
]
|
||||
// const boardList = [
|
||||
// { id: 'mg__jianjiao_newsong', bangid: 'jianjiao_newsong', name: '尖叫新歌榜' },
|
||||
// { id: 'mg__jianjiao_hotsong', bangid: 'jianjiao_hotsong', name: '尖叫热歌榜' },
|
||||
// { id: 'mg__jianjiao_original', bangid: 'jianjiao_original', name: '尖叫原创榜' },
|
||||
// { id: 'mg__migumusic', bangid: 'migumusic', name: '音乐榜' },
|
||||
// { id: 'mg__movies', bangid: 'movies', name: '影视榜' },
|
||||
// { id: 'mg__mainland', bangid: 'mainland', name: '内地榜' },
|
||||
// { id: 'mg__hktw', bangid: 'hktw', name: '港台榜' },
|
||||
// { id: 'mg__eur_usa', bangid: 'eur_usa', name: '欧美榜' },
|
||||
// { id: 'mg__jpn_kor', bangid: 'jpn_kor', name: '日韩榜' },
|
||||
// { id: 'mg__coloring', bangid: 'coloring', name: '彩铃榜' },
|
||||
// { id: 'mg__ktv', bangid: 'ktv', name: 'KTV榜' },
|
||||
// { id: 'mg__network', bangid: 'network', name: '网络榜' },
|
||||
// { id: 'mg__newalbum', bangid: 'newalbum', name: '新专辑榜' },
|
||||
// { id: 'mg__mv', bangid: 'mv', name: 'MV榜' },
|
||||
// { id: 'mg__itunes', bangid: 'itunes', name: '美国iTunes榜' },
|
||||
// { id: 'mg__billboard', bangid: 'billboard', name: '美国billboard榜' },
|
||||
// { id: 'mg__hito', bangid: 'hito', name: 'Hito中文榜' },
|
||||
// { id: 'mg__mnet', bangid: 'mnet', name: '韩国Melon榜' },
|
||||
// { id: 'mg__uk', bangid: 'uk', name: '英国UK榜' },
|
||||
// ]
|
||||
|
||||
export default {
|
||||
limit: 10000,
|
||||
getUrl(id, page) {
|
||||
const targetBoard = boardList.find(board => board.bangid == id)
|
||||
return `https://music.migu.cn/v3/music/top/${targetBoard.webId}`
|
||||
// return `http://m.music.migu.cn/migu/remoting/cms_list_tag?nid=${id}&pageSize=${this.limit}&pageNo=${page - 1}`
|
||||
},
|
||||
successCode: '000000',
|
||||
requestBoardsObj: null,
|
||||
regExps: {
|
||||
listData: /var listData = (\{.+\})<\/script>/,
|
||||
},
|
||||
getData(url) {
|
||||
const requestObj = httpFetch(url)
|
||||
return requestObj.promise
|
||||
},
|
||||
getSinger(singers) {
|
||||
let arr = []
|
||||
singers.forEach(singer => {
|
||||
arr.push(singer.name)
|
||||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
getIntv(interval) {
|
||||
let intvArr = interval.split(':')
|
||||
let intv = 0
|
||||
let unit = 1
|
||||
while (intvArr.length) {
|
||||
intv += (intvArr.pop()) * unit
|
||||
unit *= 60
|
||||
}
|
||||
return parseInt(intv)
|
||||
},
|
||||
formateIntv() {
|
||||
|
||||
},
|
||||
filterData(rawData) {
|
||||
// console.log(JSON.stringify(rawData))
|
||||
// console.log(rawData)
|
||||
let ids = new Set()
|
||||
const list = []
|
||||
rawData.forEach(item => {
|
||||
if (ids.has(item.copyrightId)) return
|
||||
ids.add(item.copyrightId)
|
||||
|
||||
const types = []
|
||||
const _types = {}
|
||||
|
||||
const size = null
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = { size }
|
||||
|
||||
if (item.hq) {
|
||||
const size = null
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = { size }
|
||||
}
|
||||
if (item.sq) {
|
||||
const size = null
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = { size }
|
||||
}
|
||||
|
||||
list.push({
|
||||
singer: this.getSinger(item.singers),
|
||||
name: item.name,
|
||||
albumName: item.album && item.album.albumName,
|
||||
albumId: item.album && item.album.albumId,
|
||||
songmid: item.copyrightId,
|
||||
songId: item.id,
|
||||
copyrightId: item.copyrightId,
|
||||
source: 'mg',
|
||||
interval: item.duration ? formatPlayTime(this.getIntv(item.duration)) : null,
|
||||
img: item.mediumPic ? `https:${item.mediumPic}` : null,
|
||||
lrc: null,
|
||||
// lrcUrl: item.lrcUrl,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
filterBoardsData(rawList) {
|
||||
// console.log(rawList)
|
||||
let list = []
|
||||
for (const board of rawList) {
|
||||
if (board.template != 'group1') continue
|
||||
for (const item of board.itemList) {
|
||||
if ((item.template != 'row1' && item.template != 'grid1' && !item.actionUrl) || !item.actionUrl.includes('rank-info')) continue
|
||||
|
||||
let data = item.displayLogId.param
|
||||
list.push({
|
||||
id: 'mg__' + data.rankId,
|
||||
name: data.rankName,
|
||||
bangid: String(data.rankId),
|
||||
})
|
||||
}
|
||||
}
|
||||
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.data.contentItemList)
|
||||
// if (response.statusCode !== 200 || response.body.code !== this.successCode) return this.getBoards(retryNum)
|
||||
// const list = this.filterBoardsData(response.body.data.contentItemList)
|
||||
// // console.log(list)
|
||||
// // console.log(JSON.stringify(list))
|
||||
// this.list = list
|
||||
// return {
|
||||
// list,
|
||||
// source: 'mg',
|
||||
// }
|
||||
this.list = boardList
|
||||
return {
|
||||
list: boardList,
|
||||
source: 'mg',
|
||||
}
|
||||
},
|
||||
getList(bangid, page, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
return this.getData(this.getUrl(bangid, page)).then(({ statusCode, body }) => {
|
||||
if (statusCode !== 200) return this.getList(bangid, page, retryNum)
|
||||
let listData = body.match(this.regExps.listData)
|
||||
if (!listData) return this.getList(bangid, page, retryNum)
|
||||
const datas = JSON.parse(RegExp.$1)
|
||||
// console.log(datas)
|
||||
listData = this.filterData(datas.songs.items)
|
||||
return {
|
||||
total: datas.songs.itemTotal,
|
||||
list: this.filterData(datas.songs.items),
|
||||
limit: this.limit,
|
||||
page,
|
||||
source: 'mg',
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import musicSearch from './musicSearch'
|
||||
import { decrypt } from './mrc'
|
||||
|
||||
const mrcTools = {
|
||||
rxps: {
|
||||
lineTime: /^\s*\[(\d+),\d+\]/,
|
||||
wordTime: /\(\d+,\d+\)/,
|
||||
wordTimeAll: /(\(\d+,\d+\))/g,
|
||||
},
|
||||
parseLyric(str) {
|
||||
str = str.replace(/\r/g, '')
|
||||
const lines = str.split('\n')
|
||||
const lxlrcLines = []
|
||||
const lrcLines = []
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.length < 6) continue
|
||||
let result = this.rxps.lineTime.exec(line)
|
||||
if (!result) continue
|
||||
|
||||
const startTime = parseInt(result[1])
|
||||
let time = startTime
|
||||
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}`
|
||||
|
||||
let words = line.replace(this.rxps.lineTime, '')
|
||||
|
||||
lrcLines.push(`[${time}]${words.replace(this.rxps.wordTimeAll, '')}`)
|
||||
|
||||
let times = words.match(this.rxps.wordTimeAll)
|
||||
if (!times) continue
|
||||
times = times.map(time => {
|
||||
const result = /\((\d+),(\d+)\)/.exec(time)
|
||||
return `<${parseInt(result[1]) - startTime},${result[2]}>`
|
||||
})
|
||||
const wordArr = words.split(this.rxps.wordTime)
|
||||
const newWords = times.map((time, index) => `${time}${wordArr[index]}`).join('')
|
||||
lxlrcLines.push(`[${time}]${newWords}`)
|
||||
}
|
||||
return {
|
||||
lyric: lrcLines.join('\n'),
|
||||
lxlyric: lxlrcLines.join('\n'),
|
||||
}
|
||||
},
|
||||
getText(url, tryNum = 0) {
|
||||
const requestObj = httpFetch(url, {
|
||||
headers: {
|
||||
Referer: 'https://app.c.nf.migu.cn/',
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
|
||||
channel: '0146921',
|
||||
},
|
||||
})
|
||||
return requestObj.promise.then(({ statusCode, body }) => {
|
||||
if (statusCode == 200) return body
|
||||
if (tryNum > 5 || statusCode == 404) return Promise.reject('歌词获取失败')
|
||||
return this.getText(url, ++tryNum)
|
||||
})
|
||||
},
|
||||
getMrc(url) {
|
||||
return this.getText(url).then(text => {
|
||||
return this.parseLyric(decrypt(text))
|
||||
})
|
||||
},
|
||||
getLrc(url) {
|
||||
return this.getText(url).then(text => ({ lxlyric: '', lyric: text }))
|
||||
},
|
||||
getTrc(url) {
|
||||
if (!url) return Promise.resolve('')
|
||||
return this.getText(url)
|
||||
},
|
||||
getMusicInfo(songInfo) {
|
||||
return songInfo.mrcUrl == null
|
||||
? musicSearch.search(`${songInfo.name} ${songInfo.singer || ''}`.trim(), 1, { limit: 25 }).then(({ list }) => {
|
||||
const targetSong = list.find(s => s.songmid == songInfo.songmid)
|
||||
return targetSong ? { lrcUrl: targetSong.lrcUrl, mrcUrl: targetSong.mrcUrl, trcUrl: targetSong.trcUrl } : Promise.reject('获取歌词失败')
|
||||
})
|
||||
: Promise.resolve({ lrcUrl: songInfo.lrcUrl, mrcUrl: songInfo.mrcUrl, trcUrl: songInfo.trcUrl })
|
||||
},
|
||||
getLyric(songInfo) {
|
||||
return {
|
||||
promise: this.getMusicInfo(songInfo).then(info => {
|
||||
let p
|
||||
if (info.mrcUrl) p = this.getMrc(info.mrcUrl)
|
||||
else if (info.lrcUrl) p = this.getLrc(info.lrcUrl)
|
||||
if (p == null) return Promise.reject('获取歌词失败')
|
||||
return Promise.all([p, this.getTrc(info.trcUrl)]).then(([lrcInfo, tlyric]) => {
|
||||
lrcInfo.tlyric = tlyric
|
||||
return lrcInfo
|
||||
})
|
||||
}),
|
||||
cancelHttp() {},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
getLyricWeb(songInfo, tryNum = 0) {
|
||||
// console.log(songInfo.copyrightId)
|
||||
if (songInfo.lrcUrl) {
|
||||
let requestObj = httpFetch(songInfo.lrcUrl)
|
||||
requestObj.promise = requestObj.promise.then(({ body, statusCode }) => {
|
||||
if (statusCode !== 200) {
|
||||
if (tryNum > 5) return Promise.reject('歌词获取失败')
|
||||
let tryRequestObj = this.getLyricWeb(songInfo, ++tryNum)
|
||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||
return tryRequestObj.promise
|
||||
}
|
||||
return {
|
||||
lyric: body,
|
||||
tlyric: '',
|
||||
}
|
||||
})
|
||||
return requestObj
|
||||
} else {
|
||||
let requestObj = httpFetch(`https://music.migu.cn/v3/api/music/audioPlayer/getLyric?copyrightId=${songInfo.copyrightId}`, {
|
||||
headers: {
|
||||
Referer: 'https://music.migu.cn/v3/music/player/audio?from=migu',
|
||||
},
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
if (body.returnCode !== '000000' || !body.lyric) {
|
||||
if (tryNum > 5) return Promise.reject(new Error('Get lyric failed'))
|
||||
let tryRequestObj = this.getLyricWeb(songInfo, ++tryNum)
|
||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||
return tryRequestObj.promise
|
||||
}
|
||||
return {
|
||||
lyric: body.lyric,
|
||||
tlyric: '',
|
||||
}
|
||||
})
|
||||
return requestObj
|
||||
}
|
||||
},
|
||||
|
||||
getLyric(songInfo) {
|
||||
let requestObj = mrcTools.getLyric(songInfo)
|
||||
requestObj.promise = requestObj.promise.catch(() => {
|
||||
let webRequestObj = this.getLyricWeb(songInfo)
|
||||
requestObj.cancelHttp = webRequestObj.cancelHttp.bind(webRequestObj)
|
||||
return webRequestObj.promise
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
|
||||
// const key = 'karakal@123Qcomyidongtiantianhaoting'
|
||||
const DELTA = 2654435769n
|
||||
const MIN_LENGTH = 32
|
||||
// const SPECIAL_CHAR = '0'
|
||||
const keyArr = [
|
||||
27303562373562475n,
|
||||
18014862372307051n,
|
||||
22799692160172081n,
|
||||
34058940340699235n,
|
||||
30962724186095721n,
|
||||
27303523720101991n,
|
||||
27303523720101998n,
|
||||
31244139033526382n,
|
||||
28992395054481524n,
|
||||
]
|
||||
|
||||
|
||||
const teaDecrypt = (data, key) => {
|
||||
const length = data.length
|
||||
const lengthBitint = BigInt(length)
|
||||
if (length >= 1) {
|
||||
// let j = data[data.length - 1];
|
||||
let j2 = data[0]
|
||||
let j3 = toLong((6n + (52n / lengthBitint)) * DELTA)
|
||||
while (true) {
|
||||
let j4 = j3
|
||||
if (j4 == 0n) break
|
||||
let j5 = toLong(3n & toLong(j4 >> 2n))
|
||||
let j6 = lengthBitint
|
||||
while (true) {
|
||||
j6--
|
||||
if (j6 > 0n) {
|
||||
let j7 = data[(j6 - 1n)]
|
||||
let i = j6
|
||||
j2 = toLong(data[i] - (toLong(toLong(j2 ^ j4) + toLong(j7 ^ key[toLong(toLong(3n & j6) ^ j5)])) ^ toLong(toLong(toLong(j7 >> 5n) ^ toLong(j2 << 2n)) + toLong(toLong(j2 >> 3n) ^ toLong(j7 << 4n)))))
|
||||
data[i] = j2
|
||||
} else break
|
||||
}
|
||||
let j8 = data[lengthBitint - 1n]
|
||||
j2 = toLong(data[0n] - toLong(toLong(toLong(key[toLong(toLong(j6 & 3n) ^ j5)] ^ j8) + toLong(j2 ^ j4)) ^ toLong(toLong(toLong(j8 >> 5n) ^ toLong(j2 << 2n)) + toLong(toLong(j2 >> 3n) ^ toLong(j8 << 4n)))))
|
||||
data[0] = j2
|
||||
j3 = toLong(j4 - DELTA)
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const longArrToString = (data) => {
|
||||
const arrayList = []
|
||||
for (const j of data) arrayList.push(longToBytes(j).toString('utf16le'))
|
||||
return arrayList.join('')
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/29132118
|
||||
const longToBytes = (l) => {
|
||||
const result = Buffer.alloc(8)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result[i] = parseInt(l & 0xFFn)
|
||||
l >>= 8n
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
const toBigintArray = (data) => {
|
||||
const length = Math.floor(data.length / 16)
|
||||
const jArr = Array(length)
|
||||
for (let i = 0; i < length; i++) {
|
||||
jArr[i] = toLong(data.substring(i * 16, (i * 16) + 16))
|
||||
}
|
||||
return jArr
|
||||
}
|
||||
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/445#issuecomment-1139338682
|
||||
const MAX = 9223372036854775807n
|
||||
const MIN = -9223372036854775808n
|
||||
const toLong = str => {
|
||||
const num = typeof str == 'string' ? BigInt('0x' + str) : str
|
||||
if (num > MAX) return toLong(num - (1n << 64n))
|
||||
else if (num < MIN) return toLong(num + (1n << 64n))
|
||||
return num
|
||||
}
|
||||
|
||||
export const decrypt = (data) => {
|
||||
// console.log(data.length)
|
||||
// -3551594764563790630
|
||||
// console.log(toLongArrayFromArr(Buffer.from(key)))
|
||||
// console.log(teaDecrypt(toBigintArray(data), keyArr))
|
||||
// console.log(longArrToString(teaDecrypt(toBigintArray(data), keyArr)))
|
||||
// console.log(toByteArray(teaDecrypt(toBigintArray(data), keyArr)))
|
||||
return (data == null || data.length < MIN_LENGTH)
|
||||
? data
|
||||
: longArrToString(teaDecrypt(toBigintArray(data), keyArr))
|
||||
}
|
||||
|
||||
// console.log(14895149309145760986n - )
|
||||
// console.log(toLong('14895149309145760986'))
|
||||
// console.log(decrypt(str))
|
||||
// console.log(decrypt(str))
|
||||
// console.log(toByteArray([6048138644744000495n]))
|
||||
// console.log(toByteArray([16325999628386395n]))
|
||||
// console.log(toLong(90994076459972177136n))
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// import '../../polyfill/array.find'
|
||||
|
||||
import { httpFetch } from '../../request'
|
||||
import { sizeFormate } from '../../index'
|
||||
// import { debug } from '../../utils/env'
|
||||
// import { formatSinger } from './util'
|
||||
|
||||
export default {
|
||||
limit: 20,
|
||||
total: 0,
|
||||
page: 0,
|
||||
allPage: 1,
|
||||
musicSearch(str, page, limit) {
|
||||
const searchRequest = httpFetch(`http://pd.musicapp.migu.cn/MIGUM2.0/v1.0/content/search_all.do?ua=Android_migu&version=5.0.1&text=${encodeURIComponent(str)}&pageNo=${page}&pageSize=${limit}&searchSwitch=%7B%22song%22%3A1%2C%22album%22%3A0%2C%22singer%22%3A0%2C%22tagSong%22%3A0%2C%22mvSong%22%3A0%2C%22songlist%22%3A0%2C%22bestShow%22%3A1%7D`, {
|
||||
// searchRequest = httpFetch(`http://jadeite.migu.cn:7090/music_search/v2/search/searchAll?sid=4f87090d01c84984a11976b828e2b02c18946be88a6b4c47bcdc92fbd40762db&isCorrect=1&isCopyright=1&searchSwitch=%7B%22song%22%3A1%2C%22album%22%3A0%2C%22singer%22%3A0%2C%22tagSong%22%3A1%2C%22mvSong%22%3A0%2C%22bestShow%22%3A1%2C%22songlist%22%3A0%2C%22lyricSong%22%3A0%7D&pageSize=${limit}&text=${encodeURIComponent(str)}&pageNo=${page}&sort=0`, {
|
||||
headers: {
|
||||
// sign: 'c3b7ae985e2206e97f1b2de8f88691e2',
|
||||
// timestamp: 1578225871982,
|
||||
// appId: 'yyapp2',
|
||||
// mode: 'android',
|
||||
// ua: 'Android_migu',
|
||||
// version: '6.9.4',
|
||||
osVersion: 'android 7.0',
|
||||
'User-Agent': 'okhttp/3.9.1',
|
||||
},
|
||||
})
|
||||
// searchRequest = httpFetch(`https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/search_all.do?isCopyright=1&isCorrect=1&pageNo=${page}&pageSize=${limit}&searchSwitch={%22song%22:1,%22album%22:0,%22singer%22:0,%22tagSong%22:0,%22mvSong%22:0,%22songlist%22:0,%22bestShow%22:0}&sort=0&text=${encodeURIComponent(str)}`)
|
||||
return searchRequest.promise.then(({ body }) => body)
|
||||
},
|
||||
getSinger(singers) {
|
||||
let arr = []
|
||||
singers.forEach(singer => {
|
||||
arr.push(singer.name)
|
||||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
handleResult(rawData) {
|
||||
// console.log(rawData)
|
||||
let ids = new Set()
|
||||
const list = []
|
||||
rawData.forEach(item => {
|
||||
if (ids.has(item.id)) return
|
||||
ids.add(item.id)
|
||||
const types = []
|
||||
const _types = {}
|
||||
item.newRateFormats && item.newRateFormats.forEach(type => {
|
||||
let size
|
||||
switch (type.formatType) {
|
||||
case 'PQ':
|
||||
size = sizeFormate(type.size ?? type.androidSize)
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
case 'HQ':
|
||||
size = sizeFormate(type.size ?? type.androidSize)
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
case 'SQ':
|
||||
size = sizeFormate(type.size ?? type.androidSize)
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
case 'ZQ':
|
||||
size = sizeFormate(type.size ?? type.androidSize)
|
||||
types.push({ type: 'flac24bit', size })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
const albumNInfo = item.albums && item.albums.length
|
||||
? {
|
||||
id: item.albums[0].id,
|
||||
name: item.albums[0].name,
|
||||
}
|
||||
: {}
|
||||
|
||||
list.push({
|
||||
singer: this.getSinger(item.singers),
|
||||
name: item.name,
|
||||
albumName: albumNInfo.name,
|
||||
albumId: albumNInfo.id,
|
||||
songmid: item.copyrightId,
|
||||
songId: item.songId,
|
||||
copyrightId: item.copyrightId,
|
||||
source: 'mg',
|
||||
interval: null,
|
||||
img: item.imgItems && item.imgItems.length ? item.imgItems[0].img : null,
|
||||
lrc: null,
|
||||
lrcUrl: item.lyricUrl,
|
||||
mrcUrl: item.mrcurl,
|
||||
trcUrl: item.trcUrl,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
search(str, page = 1, limit, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
if (limit == null) limit = this.limit
|
||||
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
|
||||
return this.musicSearch(str, page, limit).then(result => {
|
||||
// console.log(result)
|
||||
if (!result || result.code !== '000000') return Promise.reject(new Error(result ? result.info : '搜索失败'))
|
||||
const songResultData = result.songResultData || { result: [], totalCount: 0 }
|
||||
|
||||
let list = this.handleResult(songResultData.result)
|
||||
if (list == null) return this.search(str, page, limit, retryNum)
|
||||
|
||||
this.total = parseInt(songResultData.totalCount)
|
||||
this.page = page
|
||||
this.allPage = Math.ceil(this.total / limit)
|
||||
|
||||
return Promise.resolve({
|
||||
list,
|
||||
allPage: this.allPage,
|
||||
limit,
|
||||
total: this.total,
|
||||
source: 'mg',
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
export default {
|
||||
getPic(songInfo, tryNum = 0) {
|
||||
let requestObj = httpFetch(`http://music.migu.cn/v3/api/music/audioPlayer/getSongPic?songId=${songInfo.songmid}`, {
|
||||
headers: {
|
||||
Referer: 'http://music.migu.cn/v3/music/player/audio?from=migu',
|
||||
},
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
if (body.returnCode !== '000000') {
|
||||
if (tryNum > 5) return Promise.reject('图片获取失败')
|
||||
let tryRequestObj = this.getPic(songInfo, ++tryNum)
|
||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||
return tryRequestObj.promise
|
||||
}
|
||||
return body.largePic || body.mediumPic || body.smallPic
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
export const bHh = '624868746c'
|
||||
|
||||
export const headers = {
|
||||
'User-Agent': 'lx-music request',
|
||||
[bHh]: [bHh],
|
||||
}
|
||||
|
||||
|
||||
export const timeout = 15000
|
||||
@@ -0,0 +1,27 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { requestMsg } from '../../message'
|
||||
import { headers, timeout } from '../options'
|
||||
import { dnsLookup } from '../utils'
|
||||
|
||||
const api_messoer = {
|
||||
getMusicUrl(songInfo, type) {
|
||||
const requestObj = httpFetch(`http://ts.tempmusics.tk/url/tx/${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) {
|
||||
return {
|
||||
promise: Promise.resolve(`https://y.gtimg.cn/music/photo_new/T002R500x500M000${songInfo.albumId}.jpg`),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default api_messoer
|
||||
@@ -0,0 +1,195 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { dateFormat2 } from '../../index'
|
||||
|
||||
const emojis = {
|
||||
e400846: '😘',
|
||||
e400874: '😴',
|
||||
e400825: '😃',
|
||||
e400847: '😙',
|
||||
e400835: '😍',
|
||||
e400873: '😳',
|
||||
e400836: '😎',
|
||||
e400867: '😭',
|
||||
e400832: '😊',
|
||||
e400837: '😏',
|
||||
e400875: '😫',
|
||||
e400831: '😉',
|
||||
e400855: '😡',
|
||||
e400823: '😄',
|
||||
e400862: '😨',
|
||||
e400844: '😖',
|
||||
e400841: '😓',
|
||||
e400830: '😈',
|
||||
e400828: '😆',
|
||||
e400833: '😋',
|
||||
e400822: '😀',
|
||||
e400843: '😕',
|
||||
e400829: '😇',
|
||||
e400824: '😂',
|
||||
e400834: '😌',
|
||||
e400877: '😷',
|
||||
e400132: '🍉',
|
||||
e400181: '🍺',
|
||||
e401067: '☕️',
|
||||
e400186: '🥧',
|
||||
e400343: '🐷',
|
||||
e400116: '🌹',
|
||||
e400126: '🍃',
|
||||
e400613: '💋',
|
||||
e401236: '❤️',
|
||||
e400622: '💔',
|
||||
e400637: '💣',
|
||||
e400643: '💩',
|
||||
e400773: '🔪',
|
||||
e400102: '🌛',
|
||||
e401328: '🌞',
|
||||
e400420: '👏',
|
||||
e400914: '🙌',
|
||||
e400408: '👍',
|
||||
e400414: '👎',
|
||||
e401121: '✋',
|
||||
e400396: '👋',
|
||||
e400384: '👉',
|
||||
e401115: '✊',
|
||||
e400402: '👌',
|
||||
e400905: '🙈',
|
||||
e400906: '🙉',
|
||||
e400907: '🙊',
|
||||
e400562: '👻',
|
||||
e400932: '🙏',
|
||||
e400644: '💪',
|
||||
e400611: '💉',
|
||||
e400185: '🎁',
|
||||
e400655: '💰',
|
||||
e400325: '🐥',
|
||||
e400612: '💊',
|
||||
e400198: '🎉',
|
||||
e401685: '⚡️',
|
||||
e400631: '💝',
|
||||
e400768: '🔥',
|
||||
e400432: '👑',
|
||||
}
|
||||
|
||||
export default {
|
||||
_requestObj: null,
|
||||
_requestObj2: null,
|
||||
async getComment({ songId }, page = 1, limit = 20) {
|
||||
if (this._requestObj) this._requestObj.cancelHttp()
|
||||
|
||||
const _requestObj = httpFetch('http://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
|
||||
},
|
||||
form: {
|
||||
uin: '0',
|
||||
format: 'json',
|
||||
cid: '205360772',
|
||||
reqtype: '2',
|
||||
biztype: '1',
|
||||
topid: songId,
|
||||
cmd: '8',
|
||||
needmusiccrit: '1',
|
||||
pagenum: page - 1,
|
||||
pagesize: limit,
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
if (statusCode != 200 || body.code !== 0) throw new Error('获取评论失败')
|
||||
// console.log(body, statusCode)
|
||||
const comment = body.comment
|
||||
return {
|
||||
source: 'tx',
|
||||
comments: this.filterComment(comment.commentlist),
|
||||
total: comment.commenttotal,
|
||||
page,
|
||||
limit,
|
||||
maxPage: Math.ceil(comment.commenttotal / limit) || 1,
|
||||
}
|
||||
},
|
||||
async getHotComment({ songId }, page = 1, limit = 100) {
|
||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
||||
|
||||
const _requestObj2 = httpFetch('http://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
|
||||
},
|
||||
form: {
|
||||
uin: '0',
|
||||
format: 'json',
|
||||
cid: '205360772',
|
||||
reqtype: '2',
|
||||
biztype: '1',
|
||||
topid: songId,
|
||||
cmd: '9',
|
||||
needmusiccrit: '1',
|
||||
pagenum: page - 1,
|
||||
pagesize: limit,
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj2.promise
|
||||
if (statusCode != 200 || body.code !== 0) throw new Error('获取热门评论失败')
|
||||
// console.log(body, statusCode)
|
||||
const comment = body.comment
|
||||
return {
|
||||
source: 'tx',
|
||||
comments: this.filterComment(comment.commentlist),
|
||||
total: comment.commenttotal,
|
||||
page,
|
||||
limit,
|
||||
maxPage: Math.ceil(comment.commenttotal / limit) || 1,
|
||||
}
|
||||
},
|
||||
replaceEmoji(msg) {
|
||||
let rxp = /^\[em\](e\d+)\[\/em\]$/
|
||||
let result = msg.match(/\[em\]e\d+\[\/em\]/g)
|
||||
if (!result) return msg
|
||||
result = Array.from(new Set(result))
|
||||
for (let item of result) {
|
||||
let code = item.replace(rxp, '$1')
|
||||
msg = msg.replace(new RegExp(item.replace('[em]', '\\[em\\]').replace('[/em]', '\\[\\/em\\]'), 'g'), emojis[code] || '')
|
||||
}
|
||||
return msg
|
||||
},
|
||||
filterComment(rawList) {
|
||||
return rawList.map(item => {
|
||||
let time = String(item.time).length < 10 ? null : parseInt(item.time + '000')
|
||||
let timeStr = time ? dateFormat2(time) : null
|
||||
if (item.middlecommentcontent) {
|
||||
let firstItem = item.middlecommentcontent[0]
|
||||
firstItem.avatarurl = item.avatarurl
|
||||
firstItem.praisenum = item.praisenum
|
||||
item.avatarurl = null
|
||||
item.praisenum = null
|
||||
item.middlecommentcontent.reverse()
|
||||
}
|
||||
return {
|
||||
id: `${item.rootcommentid}_${item.commentid}`,
|
||||
rootId: item.rootcommentid,
|
||||
text: item.rootcommentcontent ? this.replaceEmoji(item.rootcommentcontent).replace(/\\n/g, '\n').split('\n') : [],
|
||||
time: item.rootcommentid == item.commentid ? time : null,
|
||||
timeStr: item.rootcommentid == item.commentid ? timeStr : null,
|
||||
userName: item.rootcommentnick ? item.rootcommentnick.substring(1) : '',
|
||||
avatar: item.avatarurl,
|
||||
userId: item.encrypt_rootcommentuin,
|
||||
likedCount: item.praisenum,
|
||||
reply: item.middlecommentcontent
|
||||
? item.middlecommentcontent.map(c => {
|
||||
// let index = c.subcommentid.lastIndexOf('_')
|
||||
return {
|
||||
id: `sub_${item.rootcommentid}_${c.subcommentid}`,
|
||||
text: this.replaceEmoji(c.subcommentcontent).replace(/\\n/g, '\n').split('\n'),
|
||||
time: c.subcommentid == item.commentid ? time : null,
|
||||
timeStr: c.subcommentid == item.commentid ? timeStr : null,
|
||||
userName: c.replynick.substring(1),
|
||||
avatar: c.avatarurl,
|
||||
userId: c.encrypt_replyuin,
|
||||
likedCount: c.praisenum,
|
||||
}
|
||||
})
|
||||
: [],
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
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://c.y.qq.com/splcloud/fcgi-bin/gethotkey.fcg', {
|
||||
// method: 'get',
|
||||
// headers: {
|
||||
// Referer: 'https://y.qq.com/portal/player.html',
|
||||
// },
|
||||
// })
|
||||
const _requestObj = httpFetch('https://u.y.qq.com/cgi-bin/musicu.fcg', {
|
||||
method: 'post',
|
||||
body: {
|
||||
comm: {
|
||||
ct: '19',
|
||||
cv: '1803',
|
||||
guid: '0',
|
||||
patch: '118',
|
||||
psrf_access_token_expiresAt: 0,
|
||||
psrf_qqaccess_token: '',
|
||||
psrf_qqopenid: '',
|
||||
psrf_qqunionid: '',
|
||||
tmeAppID: 'qqmusic',
|
||||
tmeLoginType: 0,
|
||||
uin: '0',
|
||||
wid: '0',
|
||||
},
|
||||
hotkey: {
|
||||
method: 'GetHotkeyForQQMusicPC',
|
||||
module: 'tencent_musicsoso_hotkey.HotkeyService',
|
||||
param: {
|
||||
search_id: '',
|
||||
uin: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
Referer: 'https://y.qq.com/portal/player.html',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
// console.log(body)
|
||||
if (statusCode != 200 || body.code !== 0) throw new Error('获取热搜词失败')
|
||||
// console.log(body)
|
||||
return { source: 'tx', list: this.filterList(body.hotkey.data.vec_hotkey) }
|
||||
},
|
||||
filterList(rawList) {
|
||||
return rawList.map(item => item.query)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import leaderboard from './leaderboard'
|
||||
import lyric from './lyric'
|
||||
import songList from './songList'
|
||||
import musicSearch from './musicSearch'
|
||||
import { apis } from '../api-source'
|
||||
import hotSearch from './hotSearch'
|
||||
import comment from './comment'
|
||||
|
||||
const tx = {
|
||||
leaderboard,
|
||||
songList,
|
||||
musicSearch,
|
||||
hotSearch,
|
||||
comment,
|
||||
|
||||
getMusicUrl(songInfo, type) {
|
||||
return apis('tx').getMusicUrl(songInfo, type)
|
||||
},
|
||||
getLyric(songInfo) {
|
||||
// let singer = songInfo.singer.indexOf('、') > -1 ? songInfo.singer.split('、')[0] : songInfo.singer
|
||||
return lyric.getLyric(songInfo.songmid)
|
||||
},
|
||||
getPic(songInfo) {
|
||||
return apis('tx').getPic(songInfo)
|
||||
},
|
||||
getMusicDetailPageUrl(songInfo) {
|
||||
return `https://y.qq.com/n/yqq/song/${songInfo.songmid}.html`
|
||||
},
|
||||
}
|
||||
|
||||
export default tx
|
||||
@@ -0,0 +1,252 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { formatPlayTime, sizeFormate } from '../../index'
|
||||
|
||||
let boardList = [{ id: 'tx__4', name: '流行指数榜', bangid: '4' }, { id: 'tx__26', name: '热歌榜', bangid: '26' }, { id: 'tx__27', name: '新歌榜', bangid: '27' }, { id: 'tx__62', name: '飙升榜', bangid: '62' }, { id: 'tx__58', name: '说唱榜', bangid: '58' }, { id: 'tx__57', name: '电音榜', bangid: '57' }, { id: 'tx__28', name: '网络歌曲榜', bangid: '28' }, { id: 'tx__5', name: '内地榜', bangid: '5' }, { id: 'tx__3', name: '欧美榜', bangid: '3' }, { id: 'tx__59', name: '香港地区榜', bangid: '59' }, { id: 'tx__16', name: '韩国榜', bangid: '16' }, { id: 'tx__60', name: '抖音排行榜', bangid: '60' }, { id: 'tx__29', name: '影视金曲榜', bangid: '29' }, { id: 'tx__17', name: '日本榜', bangid: '17' }, { id: 'tx__52', name: '腾讯音乐人原创榜', bangid: '52' }, { id: 'tx__36', name: 'K歌金曲榜', bangid: '36' }, { id: 'tx__61', name: '台湾地区榜', bangid: '61' }, { id: 'tx__63', name: 'DJ舞曲榜', bangid: '63' }, { id: 'tx__64', name: '综艺新歌榜', bangid: '64' }, { id: 'tx__65', name: '国风热歌榜', bangid: '65' }, { id: 'tx__66', name: 'ACG新歌榜', bangid: '66' }, { id: 'tx__67', name: '听歌识曲榜', bangid: '67' }, { id: 'tx__70', name: '达人音乐榜', bangid: '70' }]
|
||||
|
||||
export default {
|
||||
limit: 300,
|
||||
list: [
|
||||
{
|
||||
id: 'txlxzsb',
|
||||
name: '流行榜',
|
||||
bangid: 4,
|
||||
},
|
||||
{
|
||||
id: 'txrgb',
|
||||
name: '热歌榜',
|
||||
bangid: 26,
|
||||
},
|
||||
{
|
||||
id: 'txwlhgb',
|
||||
name: '网络榜',
|
||||
bangid: 28,
|
||||
},
|
||||
{
|
||||
id: 'txdyb',
|
||||
name: '抖音榜',
|
||||
bangid: 60,
|
||||
},
|
||||
{
|
||||
id: 'txndb',
|
||||
name: '内地榜',
|
||||
bangid: 5,
|
||||
},
|
||||
{
|
||||
id: 'txxgb',
|
||||
name: '香港榜',
|
||||
bangid: 59,
|
||||
},
|
||||
{
|
||||
id: 'txtwb',
|
||||
name: '台湾榜',
|
||||
bangid: 61,
|
||||
},
|
||||
{
|
||||
id: 'txoumb',
|
||||
name: '欧美榜',
|
||||
bangid: 3,
|
||||
},
|
||||
{
|
||||
id: 'txhgb',
|
||||
name: '韩国榜',
|
||||
bangid: 16,
|
||||
},
|
||||
{
|
||||
id: 'txrbb',
|
||||
name: '日本榜',
|
||||
bangid: 17,
|
||||
},
|
||||
{
|
||||
id: 'txtybb',
|
||||
name: 'YouTube榜',
|
||||
bangid: 128,
|
||||
},
|
||||
],
|
||||
listDetailRequest(id, period, limit) {
|
||||
// console.log(id, period, limit)
|
||||
return httpFetch('https://u.y.qq.com/cgi-bin/musicu.fcg', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
|
||||
},
|
||||
body: {
|
||||
toplist: {
|
||||
module: 'musicToplist.ToplistInfoServer',
|
||||
method: 'GetDetail',
|
||||
param: {
|
||||
topid: id,
|
||||
num: limit,
|
||||
period,
|
||||
},
|
||||
},
|
||||
comm: {
|
||||
uin: 0,
|
||||
format: 'json',
|
||||
ct: 20,
|
||||
cv: 1859,
|
||||
},
|
||||
},
|
||||
}).promise
|
||||
},
|
||||
regExps: {
|
||||
periodList: /<i class="play_cover__btn c_tx_link js_icon_play" data-listkey=".+?" data-listname=".+?" data-tid=".+?" data-date=".+?" .+?<\/i>/g,
|
||||
period: /data-listname="(.+?)" data-tid=".*?\/(.+?)" data-date="(.+?)" .+?<\/i>/,
|
||||
},
|
||||
periods: {},
|
||||
periodUrl: 'https://c.y.qq.com/node/pc/wk_v15/top.html',
|
||||
_requestBoardsObj: null,
|
||||
getBoardsData() {
|
||||
if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp()
|
||||
this._requestBoardsObj = httpFetch('https://c.y.qq.com/v8/fcg-bin/fcg_myqq_toplist.fcg?g_tk=1928093487&inCharset=utf-8&outCharset=utf-8¬ice=0&format=json&uin=0&needNewCode=1&platform=h5')
|
||||
return this._requestBoardsObj.promise
|
||||
},
|
||||
getData(url) {
|
||||
const requestDataObj = httpFetch(url)
|
||||
return requestDataObj.promise
|
||||
},
|
||||
getSinger(singers) {
|
||||
let arr = []
|
||||
singers.forEach(singer => {
|
||||
arr.push(singer.name)
|
||||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
filterData(rawList) {
|
||||
// console.log(rawList)
|
||||
return rawList.map(item => {
|
||||
let types = []
|
||||
let _types = {}
|
||||
if (item.file.size_128mp3 !== 0) {
|
||||
let size = sizeFormate(item.file.size_128mp3)
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (item.file.size_320mp3 !== 0) {
|
||||
let size = sizeFormate(item.file.size_320mp3)
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (item.file.size_flac !== 0) {
|
||||
let size = sizeFormate(item.file.size_flac)
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (item.file.size_hires !== 0) {
|
||||
let size = sizeFormate(item.file.size_hires)
|
||||
types.push({ type: 'flac24bit', size })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
// types.reverse()
|
||||
return {
|
||||
singer: this.getSinger(item.singer),
|
||||
name: item.title,
|
||||
albumName: item.album.title,
|
||||
albumId: item.album.mid,
|
||||
source: 'tx',
|
||||
interval: formatPlayTime(item.interval),
|
||||
songId: item.id,
|
||||
albumMid: item.album.mid,
|
||||
strMediaMid: item.file.media_mid,
|
||||
songmid: item.mid,
|
||||
img: (item.album.name === '' || item.album.name === '空')
|
||||
? `https://y.gtimg.cn/music/photo_new/T001R500x500M000${item.singer[0].mid}.jpg`
|
||||
: `https://y.gtimg.cn/music/photo_new/T002R500x500M000${item.album.mid}.jpg`,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
getPeriods(bangid) {
|
||||
return this.getData(this.periodUrl).then(({ body: html }) => {
|
||||
let result = html.match(this.regExps.periodList)
|
||||
if (!result) return Promise.reject(new Error('get data failed'))
|
||||
result.forEach(item => {
|
||||
let result = item.match(this.regExps.period)
|
||||
if (!result) return
|
||||
this.periods[result[2]] = {
|
||||
name: result[1],
|
||||
bangid: result[2],
|
||||
period: result[3],
|
||||
}
|
||||
})
|
||||
const info = this.periods[bangid]
|
||||
return info && info.period
|
||||
})
|
||||
},
|
||||
filterBoardsData(rawList) {
|
||||
// console.log(rawList)
|
||||
let list = []
|
||||
for (const board of rawList) {
|
||||
// 排除 MV榜
|
||||
if (board.id == 201) continue
|
||||
|
||||
if (board.topTitle.startsWith('巅峰榜·')) {
|
||||
board.topTitle = board.topTitle.substring(4, board.topTitle.length)
|
||||
}
|
||||
if (!board.topTitle.endsWith('榜')) board.topTitle += '榜'
|
||||
list.push({
|
||||
id: 'tx__' + board.id,
|
||||
name: board.topTitle,
|
||||
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 !== 0) return this.getBoards(retryNum)
|
||||
// const list = this.filterBoardsData(response.body.data.topList)
|
||||
// // console.log(list)
|
||||
// this.list = list
|
||||
// return {
|
||||
// list,
|
||||
// source: 'tx',
|
||||
// }
|
||||
this.list = boardList
|
||||
return {
|
||||
list: boardList,
|
||||
source: 'tx',
|
||||
}
|
||||
},
|
||||
getList(bangid, page, retryNum = 0) {
|
||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||
bangid = parseInt(bangid)
|
||||
let info = this.periods[bangid]
|
||||
let p = info ? Promise.resolve(info.period) : this.getPeriods(bangid)
|
||||
return p.then(period => {
|
||||
return this.listDetailRequest(bangid, period, this.limit).then(resp => {
|
||||
if (resp.body.code !== 0) return this.getList(bangid, page, retryNum)
|
||||
return {
|
||||
total: resp.body.toplist.data.songInfoList.length,
|
||||
list: this.filterData(resp.body.toplist.data.songInfoList),
|
||||
limit: this.limit,
|
||||
page: 1,
|
||||
source: 'tx',
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
getDetailPageUrl(id) {
|
||||
if (typeof id == 'string') id = id.replace('tx__', '')
|
||||
return `https://y.qq.com/n/ryqq/toplist/${id}`
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { b64DecodeUnicode, decodeName } from '../../index'
|
||||
|
||||
export default {
|
||||
regexps: {
|
||||
matchLrc: /.+"lyric":"([\w=+/]*)".+/,
|
||||
},
|
||||
getLyric(songmid) {
|
||||
const requestObj = httpFetch(`https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=${songmid}&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&platform=yqq`, {
|
||||
headers: {
|
||||
Referer: 'https://y.qq.com/portal/player.html',
|
||||
},
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
if (body.code != 0 || !body.lyric) return Promise.reject(new Error('Get lyric failed'))
|
||||
return {
|
||||
lyric: decodeName(b64DecodeUnicode(body.lyric)),
|
||||
tlyric: decodeName(b64DecodeUnicode(body.trans)),
|
||||
}
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// import '../../polyfill/array.find'
|
||||
|
||||
import { httpFetch } from '../../request'
|
||||
import { formatPlayTime, sizeFormate } from '../../index'
|
||||
// import { debug } from '../../utils/env'
|
||||
// import { formatSinger } from './util'
|
||||
|
||||
export default {
|
||||
limit: 50,
|
||||
total: 0,
|
||||
page: 0,
|
||||
allPage: 1,
|
||||
successCode: 0,
|
||||
musicSearch(str, page, limit, retryNum = 0) {
|
||||
if (retryNum > 5) return Promise.reject(new Error('搜索失败'))
|
||||
// searchRequest = httpFetch(`https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=sizer.yqq.song_next&searchid=49252838123499591&t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=${page}&n=${limit}&w=${encodeURIComponent(str)}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0`)
|
||||
// const searchRequest = httpFetch(`https://shc.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&remoteplace=txt.yqq.top&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=${page}&n=${limit}&w=${encodeURIComponent(str)}&cv=4747474&ct=24&format=json&inCharset=utf-8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&uin=0&hostUin=0&loginUin=0`)
|
||||
const searchRequest = httpFetch('https://u.y.qq.com/cgi-bin/musicu.fcg', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
|
||||
},
|
||||
body: {
|
||||
comm: {
|
||||
ct: '19',
|
||||
cv: '1859',
|
||||
uin: '0',
|
||||
},
|
||||
req: {
|
||||
method: 'DoSearchForQQMusicDesktop',
|
||||
module: 'music.search.SearchCgiService',
|
||||
param: {
|
||||
grp: 1,
|
||||
num_per_page: limit,
|
||||
page_num: page,
|
||||
query: str,
|
||||
search_type: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
// searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${this.limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
|
||||
return searchRequest.promise.then(({ body }) => {
|
||||
if (body.code != this.successCode || body.req.code != this.successCode) return this.musicSearch(str, page, limit, ++retryNum)
|
||||
return body.req.data
|
||||
})
|
||||
},
|
||||
getSinger(singers) {
|
||||
let arr = []
|
||||
singers.forEach(singer => {
|
||||
arr.push(singer.name)
|
||||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
handleResult(rawList) {
|
||||
// console.log(rawList)
|
||||
const list = []
|
||||
rawList.forEach(item => {
|
||||
if (!item.file?.media_mid) return
|
||||
|
||||
let types = []
|
||||
let _types = {}
|
||||
const file = item.file
|
||||
if (file.size_128mp3 != 0) {
|
||||
let size = sizeFormate(file.size_128mp3)
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (file.size_320mp3 !== 0) {
|
||||
let size = sizeFormate(file.size_320mp3)
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (file.size_flac !== 0) {
|
||||
let size = sizeFormate(file.size_flac)
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (file.size_hires !== 0) {
|
||||
let size = sizeFormate(file.size_hires)
|
||||
types.push({ type: 'flac24bit', size })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
// types.reverse()
|
||||
let albumId = ''
|
||||
let albumName = ''
|
||||
if (item.album) {
|
||||
albumName = item.album.title
|
||||
albumId = item.album.mid
|
||||
}
|
||||
list.push({
|
||||
singer: this.getSinger(item.singer),
|
||||
name: item.title,
|
||||
albumName,
|
||||
albumId,
|
||||
source: 'tx',
|
||||
interval: formatPlayTime(item.interval),
|
||||
songId: item.id,
|
||||
albumMid: item.album?.mid ?? '',
|
||||
strMediaMid: item.file.media_mid,
|
||||
songmid: item.mid,
|
||||
img: (albumId === '' || albumId === '空')
|
||||
? `https://y.gtimg.cn/music/photo_new/T001R500x500M000${item.singer[0]?.mid}.jpg`
|
||||
: `https://y.gtimg.cn/music/photo_new/T002R500x500M000${albumId}.jpg`,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
})
|
||||
// console.log(list)
|
||||
return list
|
||||
},
|
||||
search(str, page = 1, limit) {
|
||||
if (limit == null) limit = this.limit
|
||||
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
|
||||
return this.musicSearch(str, page, limit).then(({ body, meta }) => {
|
||||
let list = this.handleResult(body.song.list)
|
||||
|
||||
this.total = meta.sum
|
||||
this.page = page
|
||||
this.allPage = Math.ceil(this.total / limit)
|
||||
|
||||
return Promise.resolve({
|
||||
list,
|
||||
allPage: this.allPage,
|
||||
limit,
|
||||
total: this.total,
|
||||
source: 'tx',
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
import { httpFetch } from '../../request'
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../../index'
|
||||
|
||||
export default {
|
||||
_requestObj_tags: null,
|
||||
_requestObj_hotTags: null,
|
||||
_requestObj_list: null,
|
||||
limit_list: 36,
|
||||
limit_song: 100000,
|
||||
successCode: 0,
|
||||
sortList: [
|
||||
{
|
||||
name: '最热',
|
||||
id: 5,
|
||||
},
|
||||
{
|
||||
name: '最新',
|
||||
id: 2,
|
||||
},
|
||||
],
|
||||
regExps: {
|
||||
hotTagHtml: /class="c_bg_link js_tag_item" data-id="\w+">.+?<\/a>/g,
|
||||
hotTag: /data-id="(\w+)">(.+?)<\/a>/,
|
||||
|
||||
// https://y.qq.com/n/yqq/playlist/7217720898.html
|
||||
// https://i.y.qq.com/n2/m/share/details/taoge.html?platform=11&appshare=android_qq&appversion=9050006&id=7217720898&ADTAG=qfshare
|
||||
listDetailLink1: /^.+(?:(?:\?|&)id=|ryqq\/playlist\/)(\d+)(?:&.*$|#.*$|$)/,
|
||||
listDetailLink2: /^.+\/(\d+)\.html(?:\?.*|&.*$|#.*$|$)/,
|
||||
},
|
||||
tagsUrl: 'https://u.y.qq.com/cgi-bin/musicu.fcg?loginUin=0&hostUin=0&format=json&inCharset=utf-8&outCharset=utf-8¬ice=0&platform=wk_v15.json&needNewCode=0&data=%7B%22tags%22%3A%7B%22method%22%3A%22get_all_categories%22%2C%22param%22%3A%7B%22qq%22%3A%22%22%7D%2C%22module%22%3A%22playlist.PlaylistAllCategoriesServer%22%7D%7D',
|
||||
hotTagUrl: 'https://c.y.qq.com/node/pc/wk_v15/category_playlist.html',
|
||||
getListUrl(sortId, id, page) {
|
||||
return id
|
||||
? `https://u.y.qq.com/cgi-bin/musicu.fcg?loginUin=0&hostUin=0&format=json&inCharset=utf-8&outCharset=utf-8¬ice=0&platform=wk_v15.json&needNewCode=0&data=${encodeURIComponent(JSON.stringify({
|
||||
comm: { cv: 1602, ct: 20 },
|
||||
playlist: {
|
||||
method: 'get_category_content',
|
||||
param: {
|
||||
titleid: id,
|
||||
caller: '0',
|
||||
category_id: id,
|
||||
size: this.limit_list,
|
||||
page: page - 1,
|
||||
use_page: 1,
|
||||
},
|
||||
module: 'playlist.PlayListCategoryServer',
|
||||
},
|
||||
}))}`
|
||||
: `https://u.y.qq.com/cgi-bin/musicu.fcg?loginUin=0&hostUin=0&format=json&inCharset=utf-8&outCharset=utf-8¬ice=0&platform=wk_v15.json&needNewCode=0&data=${encodeURIComponent(JSON.stringify({
|
||||
comm: { cv: 1602, ct: 20 },
|
||||
playlist: {
|
||||
method: 'get_playlist_by_tag',
|
||||
param: { id: 10000000, sin: this.limit_list * (page - 1), size: this.limit_list, order: sortId, cur_page: page },
|
||||
module: 'playlist.PlayListPlazaServer',
|
||||
},
|
||||
}))}`
|
||||
},
|
||||
getListDetailUrl(id) {
|
||||
return `https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?type=1&json=1&utf8=1&onlysong=0&new_format=1&disstid=${id}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0`
|
||||
},
|
||||
|
||||
// http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=2849349915&pn=0&rn=100&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1
|
||||
// 获取标签
|
||||
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(this.tagsUrl)
|
||||
return this._requestObj_tags.promise.then(({ body }) => {
|
||||
if (body.code !== this.successCode) return this.getTag(++tryNum)
|
||||
return this.filterTagInfo(body.tags.data.v_group)
|
||||
})
|
||||
},
|
||||
// 获取标签
|
||||
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(this.hotTagUrl)
|
||||
return this._requestObj_hotTags.promise.then(({ statusCode, body }) => {
|
||||
if (statusCode !== 200) return this.getHotTag(++tryNum)
|
||||
return this.filterInfoHotTag(body)
|
||||
})
|
||||
},
|
||||
filterInfoHotTag(html) {
|
||||
let hotTag = html.match(this.regExps.hotTagHtml)
|
||||
const hotTags = []
|
||||
if (!hotTag) return hotTags
|
||||
|
||||
hotTag.forEach(tagHtml => {
|
||||
let result = tagHtml.match(this.regExps.hotTag)
|
||||
if (!result) return
|
||||
hotTags.push({
|
||||
id: parseInt(result[1]),
|
||||
name: result[2],
|
||||
source: 'tx',
|
||||
})
|
||||
})
|
||||
return hotTags
|
||||
},
|
||||
filterTagInfo(rawList) {
|
||||
return rawList.map(type => ({
|
||||
name: type.group_name,
|
||||
list: type.v_item.map(item => ({
|
||||
parent_id: type.group_id,
|
||||
parent_name: type.group_name,
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
source: 'tx',
|
||||
})),
|
||||
}))
|
||||
},
|
||||
|
||||
// 获取列表数据
|
||||
getList(sortId, tagId, page, tryNum = 0) {
|
||||
if (this._requestObj_list) this._requestObj_list.cancelHttp()
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
this._requestObj_list = httpFetch(
|
||||
this.getListUrl(sortId, tagId, page),
|
||||
)
|
||||
return this._requestObj_list.promise.then(({ body }) => {
|
||||
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
||||
return tagId ? this.filterList2(body.playlist.data, page) : this.filterList(body.playlist.data, page)
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 格式化播放数量
|
||||
* @param {*} num
|
||||
*/
|
||||
formatPlayCount(num) {
|
||||
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
|
||||
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
|
||||
return num
|
||||
},
|
||||
filterList(data, page) {
|
||||
return {
|
||||
list: data.v_playlist.map(item => ({
|
||||
play_count: this.formatPlayCount(item.access_num),
|
||||
id: item.tid,
|
||||
author: item.creator_info.nick,
|
||||
name: item.title,
|
||||
// time: item.publish_time,
|
||||
img: item.cover_url_medium,
|
||||
// grade: item.favorcnt / 10,
|
||||
total: item.song_ids?.length,
|
||||
desc: item.desc,
|
||||
source: 'tx',
|
||||
})),
|
||||
total: data.total,
|
||||
page,
|
||||
limit: this.limit_list,
|
||||
source: 'tx',
|
||||
}
|
||||
},
|
||||
filterList2({ content }, page) {
|
||||
// console.log(content.v_item)
|
||||
return {
|
||||
list: content.v_item.map(({ basic }) => ({
|
||||
play_count: this.formatPlayCount(basic.play_cnt),
|
||||
id: basic.tid,
|
||||
author: basic.creator.nick,
|
||||
name: basic.title,
|
||||
// time: basic.publish_time,
|
||||
img: basic.cover.medium_url || basic.cover.default_url,
|
||||
// grade: basic.favorcnt / 10,
|
||||
desc: decodeName(basic.desc).replace(/<br>/g, '\n'),
|
||||
source: 'tx',
|
||||
})),
|
||||
total: content.total_cnt,
|
||||
page,
|
||||
limit: this.limit_list,
|
||||
source: 'tx',
|
||||
}
|
||||
},
|
||||
|
||||
async handleParseId(link, retryNum = 0) {
|
||||
if (retryNum > 2) return Promise.reject(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)
|
||||
return location == null ? link : location
|
||||
},
|
||||
|
||||
async getListId(id) {
|
||||
if ((/[?&:/]/.test(id))) {
|
||||
let regx = /\/\/i\.y\.qq\.com/.test(id) ? this.regExps.listDetailLink1 : this.regExps.listDetailLink2
|
||||
if (!regx.test(id)) {
|
||||
id = await this.handleParseId(id)
|
||||
regx = this.regExps.listDetailLink1
|
||||
console.log(id)
|
||||
}
|
||||
id = id.replace(regx, '$1')
|
||||
// console.log(id)
|
||||
}
|
||||
return id
|
||||
},
|
||||
// 获取歌曲列表内的音乐
|
||||
async getListDetail(id, tryNum = 0) {
|
||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
|
||||
id = await this.getListId(id)
|
||||
|
||||
const requestObj_listDetail = httpFetch(this.getListDetailUrl(id), {
|
||||
headers: {
|
||||
Origin: 'https://y.qq.com',
|
||||
Referer: `https://y.qq.com/n/yqq/playsquare/${id}.html`,
|
||||
},
|
||||
})
|
||||
const { body } = await requestObj_listDetail.promise
|
||||
|
||||
if (body.code !== this.successCode) return this.getListDetail(id, ++tryNum)
|
||||
const cdlist = body.cdlist[0]
|
||||
return {
|
||||
list: this.filterListDetail(cdlist.songlist),
|
||||
page: 1,
|
||||
limit: cdlist.songlist.length + 1,
|
||||
total: cdlist.songlist.length,
|
||||
source: 'tx',
|
||||
info: {
|
||||
name: cdlist.dissname,
|
||||
img: cdlist.logo,
|
||||
desc: decodeName(cdlist.desc).replace(/<br>/g, '\n'),
|
||||
author: cdlist.nickname,
|
||||
play_count: this.formatPlayCount(cdlist.visitnum),
|
||||
},
|
||||
}
|
||||
},
|
||||
getSinger(singers) {
|
||||
let arr = []
|
||||
singers.forEach(singer => {
|
||||
arr.push(singer.name)
|
||||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
filterListDetail(rawList) {
|
||||
// console.log(rawList)
|
||||
return rawList.map(item => {
|
||||
let types = []
|
||||
let _types = {}
|
||||
if (item.file.size_128mp3 !== 0) {
|
||||
let size = sizeFormate(item.file.size_128mp3)
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (item.file.size_320mp3 !== 0) {
|
||||
let size = sizeFormate(item.file.size_320mp3)
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (item.file.size_flac !== 0) {
|
||||
let size = sizeFormate(item.file.size_flac)
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
if (item.file.size_hires !== 0) {
|
||||
let size = sizeFormate(item.file.size_hires)
|
||||
types.push({ type: 'flac24bit', size })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
// types.reverse()
|
||||
return {
|
||||
singer: this.getSinger(item.singer),
|
||||
name: item.title,
|
||||
albumName: item.album.title,
|
||||
albumId: item.album.mid,
|
||||
source: 'tx',
|
||||
interval: formatPlayTime(item.interval),
|
||||
songId: item.id,
|
||||
albumMid: item.album.mid,
|
||||
strMediaMid: item.file.media_mid,
|
||||
songmid: item.mid,
|
||||
img: (item.album.name === '' || item.album.name === '空')
|
||||
? `https://y.gtimg.cn/music/photo_new/T001R500x500M000${item.singer[0].mid}.jpg`
|
||||
: `https://y.gtimg.cn/music/photo_new/T002R500x500M000${item.album.mid}.jpg`,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
getTags() {
|
||||
return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'tx' }))
|
||||
},
|
||||
|
||||
async getDetailPageUrl(id) {
|
||||
id = await this.getListId(id)
|
||||
|
||||
return `https://y.qq.com/n/ryqq/playlist/${id}`
|
||||
},
|
||||
|
||||
search(text, page, limit = 20) {
|
||||
return httpFetch(`http://c.y.qq.com/soso/fcgi-bin/client_music_search_songlist?page_no=${page - 1}&num_per_page=${limit}&format=json&query=${encodeURIComponent(text)}&remoteplace=txt.yqq.playlist&inCharset=utf8&outCharset=utf-8`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
|
||||
Referer: 'http://y.qq.com/portal/search.html',
|
||||
},
|
||||
})
|
||||
.promise.then(({ body }) => {
|
||||
if (body.code != 0) throw new Error('filed')
|
||||
// console.log(body.data.list)
|
||||
return {
|
||||
list: body.data.list.map(item => {
|
||||
return {
|
||||
play_count: this.formatPlayCount(item.listennum),
|
||||
id: item.dissid,
|
||||
author: item.creator.name,
|
||||
name: item.dissname,
|
||||
time: item.createtime,
|
||||
img: item.imgurl,
|
||||
// grade: item.favorcnt / 10,
|
||||
total: item.song_count,
|
||||
desc: item.introduction,
|
||||
source: 'tx',
|
||||
}
|
||||
}),
|
||||
limit,
|
||||
total: body.data.sum,
|
||||
source: 'tx',
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// getList
|
||||
// getTags
|
||||
// getListDetail
|
||||
@@ -0,0 +1,30 @@
|
||||
import { httpFetch } from '../../request'
|
||||
|
||||
|
||||
export default {
|
||||
regExps: {
|
||||
relWord: /RELWORD=(.+)/,
|
||||
},
|
||||
requestObj: null,
|
||||
tempSearch(str) {
|
||||
this.cancelTempSearch()
|
||||
this.requestObj = httpFetch(`https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg?is_xml=0&format=json&key=${encodeURIComponent(str)}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0`, {
|
||||
headers: {
|
||||
Referer: 'https://y.qq.com/portal/player.html',
|
||||
},
|
||||
})
|
||||
return this.requestObj.promise.then(({ statusCode, body }) => {
|
||||
if (statusCode != 200 || body.code != 0) return Promise.reject(new Error('请求失败'))
|
||||
return body.data
|
||||
})
|
||||
},
|
||||
handleResult(rawData) {
|
||||
return rawData.map(info => `${info.name} - ${info.singer}`)
|
||||
},
|
||||
cancelTempSearch() {
|
||||
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
|
||||
},
|
||||
async search(str) {
|
||||
return this.tempSearch(str).then(result => this.handleResult(result.song.itemlist))
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import crypto from 'crypto'
|
||||
import dns from 'dns'
|
||||
|
||||
export const toMD5 = str => crypto.createHash('md5').update(str).digest('hex')
|
||||
|
||||
|
||||
const ipMap = new Map()
|
||||
export const getHostIp = hostname => {
|
||||
const result = ipMap.get(hostname)
|
||||
if (typeof result === 'object') return result
|
||||
if (result === true) return
|
||||
ipMap.set(hostname, true)
|
||||
// console.log(hostname)
|
||||
dns.lookup(hostname, {
|
||||
// family: 4,
|
||||
all: false,
|
||||
}, (err, address, family) => {
|
||||
if (err) return console.log(err)
|
||||
// console.log(address, family)
|
||||
ipMap.set(hostname, { address, family })
|
||||
})
|
||||
}
|
||||
|
||||
export const dnsLookup = (hostname, options, callback) => {
|
||||
const result = getHostIp(hostname)
|
||||
if (result) return callback(null, result.address, result.family)
|
||||
|
||||
dns.lookup(hostname, options, callback)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
@@ -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}`
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// import { apis } from '../api-source'
|
||||
// import leaderboard from './leaderboard'
|
||||
// import songList from './songList'
|
||||
// import musicSearch from './musicSearch'
|
||||
// import pic from './pic'
|
||||
// import lyric from './lyric'
|
||||
// import hotSearch from './hotSearch'
|
||||
// import comment from './comment'
|
||||
// import musicInfo from './musicInfo'
|
||||
// import { closeVerifyModal } from './util'
|
||||
|
||||
const xm = {
|
||||
// songList,
|
||||
// musicSearch,
|
||||
// leaderboard,
|
||||
// hotSearch,
|
||||
// closeVerifyModal,
|
||||
comment: {
|
||||
getComment() {
|
||||
return Promise.reject(new Error('fail'))
|
||||
},
|
||||
getHotComment() {
|
||||
return Promise.reject(new Error('fail'))
|
||||
},
|
||||
},
|
||||
getMusicUrl(songInfo, type) {
|
||||
return {
|
||||
promise: Promise.reject(new Error('fail')),
|
||||
}
|
||||
// return apis('xm').getMusicUrl(songInfo, type)
|
||||
},
|
||||
getLyric(songInfo) {
|
||||
return {
|
||||
promise: Promise.reject(new Error('fail')),
|
||||
}
|
||||
// return lyric.getLyric(songInfo)
|
||||
},
|
||||
getPic(songInfo) {
|
||||
return {
|
||||
promise: Promise.reject(new Error('fail')),
|
||||
}
|
||||
// return pic.getPic(songInfo)
|
||||
},
|
||||
// getMusicDetailPageUrl(songInfo) {
|
||||
// if (songInfo.songStringId) return `https://www.xiami.com/song/${songInfo.songStringId}`
|
||||
|
||||
// musicInfo.getMusicInfo(songInfo).then(({ data }) => {
|
||||
// songInfo.songStringId = data.songStringId
|
||||
// })
|
||||
// return `https://www.xiami.com/song/${songInfo.songmid}`
|
||||
// },
|
||||
// init() {
|
||||
// getToken()
|
||||
// },
|
||||
}
|
||||
|
||||
export default xm
|
||||
Reference in New Issue
Block a user