refactor: 架构整固——分层单向化、全量 TypeScript、直接运行
- 移除 bd/mg 两平台及磁盘残留产物;清理 api-test 死脚本、musicSdk/message+options 死模块、infSign vendor - musicSdk/ 改名 sdk/;src/common、src/auth、src/apis 整体移入 sdk/,依赖方向定为 src → sdk 严格单向 - src/ 手写 JS 全量转 .ts,删除 JS 类型垫片 modules.d.ts;源码 import 统一 .ts 后缀 - Node 原生类型剥离直接运行 .ts(engines >=22.18),取消 tsc 发射产物,tsc 仅类型检查(pnpm typecheck) - 删除 kwdes 备用实现与 src/index.ts 转发垫片;测试移至 test/smoke-test.ts - common/request.ts 补显式导出类型;修复 set-cookie 单值形态、ApiError 类字段等隐患 验证:tsc 零错误,smoke-test 41/41
This commit is contained in:
1 parent
c8c939aa32
commit
354df2e739
126 files changed
+413
-2942
No files matched your search
@@ -0,0 +1,63 @@
|
||||
import { getMusicInfosByList } from './musicInfo.ts'
|
||||
import { createHttpFetch } from './util.ts'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 通过AlbumId获取专辑信息
|
||||
* @param {*} id
|
||||
*/
|
||||
async getAlbumInfo(id) {
|
||||
const albumInfoRequest = await createHttpFetch('http://kmrserviceretry.kugou.com/container/v1/album?dfid=1tT5He3kxrNC4D29ad1MMb6F&mid=22945702112173152889429073101964063697&userid=0&appid=1005&clientver=11589', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
appid: 1005,
|
||||
clienttime: 1681833686,
|
||||
clientver: 11589,
|
||||
data: [{ album_id: id }],
|
||||
fields: 'language,grade_count,intro,mix_intro,heat,category,sizable_cover,cover,album_name,type,quality,publish_company,grade,special_tag,author_name,publish_date,language_id,album_id,exclusive,is_publish,trans_param,authors,album_tag',
|
||||
isBuy: 0,
|
||||
key: 'e6f3306ff7e2afb494e89fbbda0becbf',
|
||||
mid: '22945702112173152889429073101964063697',
|
||||
show_album_tag: 0,
|
||||
},
|
||||
})
|
||||
if (!albumInfoRequest) return Promise.reject(new Error('get album info failed.'))
|
||||
const albumInfo = albumInfoRequest[0]
|
||||
|
||||
return {
|
||||
name: albumInfo.album_name,
|
||||
image: albumInfo.sizable_cover.replace('{size}', 240),
|
||||
desc: albumInfo.intro,
|
||||
authorName: albumInfo.author_name,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 通过AlbumId获取专辑
|
||||
* @param {*} id
|
||||
* @param {*} page
|
||||
*/
|
||||
async getAlbumDetail(id, page = 1, limit = 200) {
|
||||
const albumList = await createHttpFetch(`http://mobiles.kugou.com/api/v3/album/song?version=9108&albumid=${id}&plat=0&pagesize=${limit}&area_code=0&page=${page}&with_res_tag=0`)
|
||||
if (!albumList.info) return Promise.reject(new Error('Get album list failed.'))
|
||||
|
||||
let result = await getMusicInfosByList(albumList.info)
|
||||
|
||||
const info = await this.getAlbumInfo(id)
|
||||
|
||||
return {
|
||||
list: result || [],
|
||||
page,
|
||||
limit,
|
||||
total: albumList.total,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.name,
|
||||
img: info.image,
|
||||
desc: info.desc,
|
||||
author: info.authorName,
|
||||
// play_count: this.formatPlayCount(info.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { decodeName, dateFormat2 } from '../index.ts'
|
||||
import { signatureParams } from './util.ts'
|
||||
// import { getMusicInfoRaw } from './musicInfo.ts'
|
||||
import type { HttpRequestObject } from '../types.ts'
|
||||
|
||||
export default {
|
||||
_requestObj: null as HttpRequestObject | null,
|
||||
_requestObj2: null as HttpRequestObject | null,
|
||||
async getComment({ hash }, page = 1, limit = 20) {
|
||||
if (this._requestObj) this._requestObj.cancelHttp()
|
||||
|
||||
// const res_id = (await getMusicInfoRaw(hash)).classification?.[0]?.res_id
|
||||
// if (!res_id) throw new Error('获取评论失败')
|
||||
|
||||
let timestamp = Date.now()
|
||||
const params = `dfid=0&mid=16249512204336365674023395779019&clienttime=${timestamp}&uuid=0&extdata=${hash}&appid=1005&code=fc4be23b4e972707f36b8a828a93ba8a&schash=${hash}&clientver=11409&p=${page}&clienttoken=&pagesize=${limit}&ver=10&kugouid=0`
|
||||
// const params = `appid=1005&clienttime=${timestamp}&clienttoken=0&clientver=11409&code=fc4be23b4e972707f36b8a828a93ba8a&dfid=0&extdata=${hash}&kugouid=0&mid=16249512204336365674023395779019&mixsongid=${res_id}&p=${page}&pagesize=${limit}&uuid=0&ver=10`
|
||||
const _requestObj = httpFetch(`http://m.comment.service.kugou.com/r/v1/rank/newest?${params}&signature=${signatureParams(params)}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.24',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
// console.log(body)
|
||||
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取评论失败')
|
||||
const total = body.count ?? 0
|
||||
return { source: 'kg', comments: this.filterComment(body.list || []), total, page, limit, maxPage: Math.ceil(total / limit) || 1 }
|
||||
},
|
||||
async getHotComment({ hash }, page = 1, limit = 20) {
|
||||
// console.log(songmid)
|
||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
||||
let timestamp = Date.now()
|
||||
const params = `dfid=0&mid=16249512204336365674023395779019&clienttime=${timestamp}&uuid=0&extdata=${hash}&appid=1005&code=fc4be23b4e972707f36b8a828a93ba8a&schash=${hash}&clientver=11409&p=${page}&clienttoken=&pagesize=${limit}&ver=10&kugouid=0`
|
||||
// https://github.com/GitHub-ZC/wp_MusicApi/blob/bf9307dd138dc8ac6c4f7de29361209d4f5b665f/routes/v1/kugou/comment.js#L53
|
||||
const _requestObj2 = httpFetch(`http://m.comment.service.kugou.com/r/v1/rank/topliked?${params}&signature=${signatureParams(params)}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.24',
|
||||
},
|
||||
})
|
||||
const { body, statusCode } = await _requestObj2.promise
|
||||
// console.log(body)
|
||||
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取热门评论失败')
|
||||
const total = body.count ?? 0
|
||||
return { source: 'kg', comments: this.filterComment(body.list || []), total, page, limit, maxPage: Math.ceil(total / limit) || 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&need_show_image=1&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 || []) }
|
||||
},
|
||||
replaceAt(raw, atList) {
|
||||
atList.forEach((atobj) => {
|
||||
raw = raw.replaceAll(`[at=${atobj.id}]`, `@${atobj.name} `)
|
||||
})
|
||||
return raw
|
||||
},
|
||||
filterComment(rawList) {
|
||||
return rawList.map(item => {
|
||||
let data = {
|
||||
id: item.id,
|
||||
text: decodeName((item.atlist ? this.replaceAt(item.content, item.atlist) : item.content) || ''),
|
||||
images: item.images ? item.images.map(i => i.url) : [],
|
||||
location: item.location,
|
||||
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),
|
||||
time: null,
|
||||
userName: item.puser,
|
||||
avatar: null,
|
||||
userId: item.puser_id,
|
||||
likedCount: null,
|
||||
replyNum: null,
|
||||
reply: [data],
|
||||
}
|
||||
: data
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { decodeName } from '../index.ts'
|
||||
import type { HttpRequestObject } from '../types.ts'
|
||||
|
||||
export default {
|
||||
_requestObj: null as HttpRequestObject | 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: string[] = []
|
||||
rawList.forEach(item => {
|
||||
item.keywords.map(k => list.push(decodeName(k.keyword)))
|
||||
})
|
||||
return list
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import leaderboard from './leaderboard.ts'
|
||||
import { apis } from '../api-source.ts'
|
||||
import songList from './songList.ts'
|
||||
import musicSearch from './musicSearch.ts'
|
||||
import pic from './pic.ts'
|
||||
import lyric from './lyric.ts'
|
||||
import hotSearch from './hotSearch.ts'
|
||||
import comment from './comment.ts'
|
||||
import singer from './singer.ts'
|
||||
import album from './album.ts'
|
||||
// import tipSearch from './tipSearch.ts'
|
||||
|
||||
const kg = {
|
||||
// tipSearch,
|
||||
leaderboard,
|
||||
songList,
|
||||
musicSearch,
|
||||
hotSearch,
|
||||
comment,
|
||||
singer,
|
||||
album,
|
||||
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,209 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../index.ts'
|
||||
import { formatSingerName } from '../utils.ts'
|
||||
import type { HttpRequestObject, SongType, QualityInfo } from '../types.ts'
|
||||
|
||||
let boardList = [{ id: 'kg__8888', name: 'TOP500', bangid: '8888' }, { id: 'kg__6666', name: '飙升榜', bangid: '6666' }, { id: 'kg__59703', name: '蜂鸟流行音乐榜', bangid: '59703' }, { id: 'kg__52144', name: '抖音热歌榜', bangid: '52144' }, { id: 'kg__52767', name: '快手热歌榜', bangid: '52767' }, { id: 'kg__24971', name: 'DJ热歌榜', bangid: '24971' }, { id: 'kg__23784', name: '网络红歌榜', bangid: '23784' }, { id: 'kg__44412', name: '说唱先锋榜', bangid: '44412' }, { id: 'kg__31308', name: '内地榜', bangid: '31308' }, { id: 'kg__33160', name: '电音榜', bangid: '33160' }, { id: 'kg__31313', name: '香港地区榜', bangid: '31313' }, { id: 'kg__51341', name: '民谣榜', bangid: '51341' }, { id: 'kg__54848', name: '台湾地区榜', bangid: '54848' }, { id: 'kg__31310', name: '欧美榜', bangid: '31310' }, { id: 'kg__33162', name: 'ACG新歌榜', bangid: '33162' }, { id: 'kg__31311', name: '韩国榜', bangid: '31311' }, { id: 'kg__31312', name: '日本榜', bangid: '31312' }, { id: 'kg__49225', name: '80后热歌榜', bangid: '49225' }, { id: 'kg__49223', name: '90后热歌榜', bangid: '49223' }, { id: 'kg__49224', name: '00后热歌榜', bangid: '49224' }, { id: 'kg__33165', name: '粤语金曲榜', bangid: '33165' }, { id: 'kg__33166', name: '欧美金曲榜', bangid: '33166' }, { id: 'kg__33163', name: '影视金曲榜', bangid: '33163' }, { id: 'kg__51340', name: '伤感榜', bangid: '51340' }, { id: 'kg__35811', name: '会员专享榜', bangid: '35811' }, { id: 'kg__37361', name: '雷达榜', bangid: '37361' }, { id: 'kg__21101', name: '分享榜', bangid: '21101' }, { id: 'kg__46910', name: '综艺新歌榜', bangid: '46910' }, { id: 'kg__30972', name: '酷狗音乐人原创榜', bangid: '30972' }, { id: 'kg__60170', name: '闽南语榜', bangid: '60170' }, { id: 'kg__65234', name: '儿歌榜', bangid: '65234' }, { id: 'kg__4681', name: '美国BillBoard榜', bangid: '4681' }, { id: 'kg__25028', name: 'Beatport电子舞曲榜', bangid: '25028' }, { id: 'kg__4680', name: '英国单曲榜', bangid: '4680' }, { id: 'kg__38623', name: '韩国Melon音乐榜', bangid: '38623' }, { id: 'kg__42807', name: 'joox本地热歌榜', bangid: '42807' }, { id: 'kg__36107', name: '小语种热歌榜', bangid: '36107' }, { id: 'kg__4673', name: '日本公信榜', bangid: '4673' }, { id: 'kg__46868', name: '日本SPACE SHOWER榜', bangid: '46868' }, { id: 'kg__42808', name: 'KKBOX风云榜', bangid: '42808' }, { id: 'kg__60171', name: '越南语榜', bangid: '60171' }, { id: 'kg__60172', name: '泰语榜', bangid: '60172' }, { id: 'kg__59895', name: 'R&B榜', bangid: '59895' }, { id: 'kg__59896', name: '摇滚榜', bangid: '59896' }, { id: 'kg__59897', name: '爵士榜', bangid: '59897' }, { id: 'kg__59898', name: '乡村音乐榜', bangid: '59898' }, { id: 'kg__59900', name: '纯音乐榜', bangid: '59900' }, { id: 'kg__59899', name: '古典榜', bangid: '59899' }, { id: 'kg__22603', name: '5sing音乐榜', bangid: '22603' }, { id: 'kg__21335', name: '繁星音乐榜', bangid: '21335' }, { id: 'kg__33161', name: '古风新歌榜', bangid: '33161' }]
|
||||
|
||||
export default {
|
||||
listDetailLimit: 100,
|
||||
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, limit) {
|
||||
return `http://mobilecdnbj.kugou.com/api/v3/rank/song?version=9108&ranktype=1&plat=0&pagesize=${limit}&area_code=1&page=${p}&rankid=${id}&with_res_tag=0&show_portrait_mv=1`
|
||||
},
|
||||
regExps: {
|
||||
total: /total: '(\d+)',/,
|
||||
page: /page: '(\d+)',/,
|
||||
limit: /pagesize: '(\d+)',/,
|
||||
listData: /global\.features = (\[.+\]);/,
|
||||
},
|
||||
_requestBoardsObj: null as HttpRequestObject | null,
|
||||
getBoardsData() {
|
||||
if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp()
|
||||
this._requestBoardsObj = httpFetch('http://mobilecdnbj.kugou.com/api/v5/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
|
||||
},
|
||||
getSinger(singers) {
|
||||
let arr: string[] = []
|
||||
singers.forEach(singer => {
|
||||
arr.push(singer.author_name)
|
||||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
filterData(rawList) {
|
||||
// console.log(rawList)
|
||||
return rawList.map(item => {
|
||||
const types: Array<SongType & { hash?: string }> = []
|
||||
const _types: Record<string, QualityInfo & { hash?: string }> = {}
|
||||
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['320filesize'] !== 0) {
|
||||
let size = sizeFormate(item['320filesize'])
|
||||
types.push({ type: '320k', size, hash: item['320hash'] })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: item['320hash'],
|
||||
}
|
||||
}
|
||||
if (item.sqfilesize !== 0) {
|
||||
let size = sizeFormate(item.sqfilesize)
|
||||
types.push({ type: 'flac', size, hash: item.sqhash })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: item.sqhash,
|
||||
}
|
||||
}
|
||||
if (item.filesize_high !== 0) {
|
||||
let size = sizeFormate(item.filesize_high)
|
||||
types.push({ type: 'flac24bit', size, hash: item.hash_high })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: item.hash_high,
|
||||
}
|
||||
}
|
||||
return {
|
||||
singer: formatSingerName(item.authors, 'author_name'),
|
||||
name: decodeName(item.songname),
|
||||
albumName: decodeName(item.remark),
|
||||
albumId: item.album_id,
|
||||
songmid: item.audio_id,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(item.duration),
|
||||
img: null,
|
||||
lrc: null,
|
||||
hash: item.hash,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
filterBoardsData(rawList) {
|
||||
// console.log(rawList)
|
||||
let list: Array<{ id: string; name: string; bangid: string }> = []
|
||||
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)
|
||||
// // console.log(JSON.stringify(list))
|
||||
// this.list = list
|
||||
// return {
|
||||
// list,
|
||||
// source: 'kg',
|
||||
// }
|
||||
this.list = boardList
|
||||
return {
|
||||
list: boardList,
|
||||
source: 'kg',
|
||||
}
|
||||
},
|
||||
async getList(bangid, page, retryNum = 0) {
|
||||
if (++retryNum > 3) throw new Error('try max num')
|
||||
const { body } = await this.getData(this.getUrl(page, bangid, this.listDetailLimit))
|
||||
|
||||
if (body.errcode != 0) return this.getList(bangid, page, retryNum)
|
||||
|
||||
// console.log(body)
|
||||
let total = body.data.total
|
||||
let limit = 100
|
||||
let listData = this.filterData(body.data.info)
|
||||
// console.log(listData)
|
||||
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`
|
||||
},
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { decodeKrc } from '../common/lyricUtils/kg.ts'
|
||||
|
||||
export default {
|
||||
getIntv(interval) {
|
||||
if (!interval) return 0
|
||||
let intvArr = interval.split(':')
|
||||
let intv = 0
|
||||
let unit = 1
|
||||
while (intvArr.length) {
|
||||
intv += (intvArr.pop()) * unit
|
||||
unit *= 60
|
||||
}
|
||||
return parseInt(intv as unknown as string)
|
||||
},
|
||||
// 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(new Error('歌词获取失败'))
|
||||
// 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}&lrctxt=1`, {
|
||||
headers: {
|
||||
'KG-RC': '1',
|
||||
'KG-THash': 'expand_search_manager.cpp:852736169:451',
|
||||
'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
|
||||
},
|
||||
})
|
||||
requestObj.promise = (requestObj.promise as any).then(({ body, statusCode }) => {
|
||||
if (statusCode !== 200) {
|
||||
if (tryNum > 5) return Promise.reject(new Error('歌词获取失败'))
|
||||
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, fmt: (info.krctype == 1 && info.contenttype != 1) ? 'krc' : 'lrc' }
|
||||
}
|
||||
return null
|
||||
})
|
||||
return requestObj
|
||||
},
|
||||
getLyricDownload(id, accessKey, fmt, tryNum = 0) {
|
||||
let requestObj = httpFetch(`http://lyrics.kugou.com/download?ver=1&client=pc&id=${id}&accesskey=${accessKey}&fmt=${fmt}&charset=utf8`, {
|
||||
headers: {
|
||||
'KG-RC': '1',
|
||||
'KG-THash': 'expand_search_manager.cpp:852736169:451',
|
||||
'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
|
||||
},
|
||||
})
|
||||
requestObj.promise = (requestObj.promise as any).then(({ body, statusCode }) => {
|
||||
if (statusCode !== 200) {
|
||||
if (tryNum > 5) return Promise.reject(new Error('歌词获取失败'))
|
||||
let tryRequestObj = (this.getLyric as any)(id, accessKey, fmt, ++tryNum)
|
||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||
return tryRequestObj.promise
|
||||
}
|
||||
|
||||
switch (body.fmt) {
|
||||
case 'krc':
|
||||
return decodeKrc(body.content)
|
||||
case 'lrc':
|
||||
return {
|
||||
lyric: Buffer.from(body.content, 'base64').toString('utf-8'),
|
||||
tlyric: '',
|
||||
rlyric: '',
|
||||
yrc: '',
|
||||
}
|
||||
default:
|
||||
return Promise.reject(new Error(`未知歌词格式: ${body.fmt}`))
|
||||
}
|
||||
})
|
||||
|
||||
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 as any).id, (result as any).accessKey, (result as any).fmt)
|
||||
|
||||
requestObj.cancelHttp = requestObj2.cancelHttp.bind(requestObj2)
|
||||
|
||||
return requestObj2.promise
|
||||
}) as any
|
||||
return requestObj
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../index.ts'
|
||||
import { createHttpFetch } from './util.ts'
|
||||
import type { SongInfo, SongType, QualityInfo } from '../types.ts'
|
||||
|
||||
const createGetMusicInfosTask = (hashs: Array<{ hash: string }>): Array<Promise<any>> => {
|
||||
let data = {
|
||||
area_code: '1',
|
||||
show_privilege: 1,
|
||||
show_album_info: '1',
|
||||
is_publish: '',
|
||||
appid: 1005,
|
||||
clientver: 11451,
|
||||
mid: '1',
|
||||
dfid: '-',
|
||||
clienttime: Date.now(),
|
||||
key: 'OIlwieks28dk2k092lksi2UIkp',
|
||||
fields: 'album_info,author_name,audio_info,ori_audio_name,base,songname,classification',
|
||||
}
|
||||
let list = hashs
|
||||
let tasks: Array<{ data: Array<{ hash: string }> }> = []
|
||||
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://gateway.kugou.com/v3/album_audio/audio'
|
||||
return tasks.map(task => createHttpFetch(url, {
|
||||
method: 'POST',
|
||||
body: task,
|
||||
headers: {
|
||||
'KG-THash': '13a3164',
|
||||
'KG-RC': '1',
|
||||
'KG-Fake': '0',
|
||||
'KG-RF': '00869891',
|
||||
'User-Agent': 'Android712-AndroidPhone-11451-376-0-FeeCacheUpdate-wifi',
|
||||
'x-router': 'kmr.service.kugou.com',
|
||||
},
|
||||
}).then(data => data.map(s => s[0])))
|
||||
}
|
||||
|
||||
export const filterMusicInfoList = (rawList: any[]): SongInfo[] => {
|
||||
// console.log(rawList)
|
||||
let ids = new Set()
|
||||
let list: SongInfo[] = []
|
||||
rawList.forEach(item => {
|
||||
if (!item) return
|
||||
if (ids.has(item.audio_info.audio_id)) return
|
||||
ids.add(item.audio_info.audio_id)
|
||||
const types: Array<SongType & { hash?: string }> = []
|
||||
const _types: Record<string, QualityInfo & { hash?: string }> = {}
|
||||
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,
|
||||
}
|
||||
}
|
||||
if (item.audio_info.filesize_high !== '0') {
|
||||
let size = sizeFormate(parseInt(item.audio_info.filesize_high))
|
||||
types.push({ type: 'flac24bit', size, hash: item.audio_info.hash_high })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: item.audio_info.hash_high,
|
||||
}
|
||||
}
|
||||
list.push({
|
||||
singer: decodeName(item.author_name),
|
||||
name: decodeName(item.songname),
|
||||
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
|
||||
}
|
||||
|
||||
export const getMusicInfos = async(hashs: Array<{ hash: string }>): Promise<SongInfo[]> => {
|
||||
return filterMusicInfoList(await Promise.all(createGetMusicInfosTask(hashs)).then(data => data.flat()))
|
||||
}
|
||||
|
||||
export const getMusicInfoRaw = async(hash: string): Promise<any> => {
|
||||
return Promise.all(createGetMusicInfosTask([{ hash }])).then(data => data.flat()[0])
|
||||
}
|
||||
|
||||
export const getMusicInfo = async(hash: string): Promise<SongInfo | undefined> => {
|
||||
return getMusicInfos([{ hash }]).then(data => data[0])
|
||||
}
|
||||
|
||||
export const getMusicInfosByList = (list: any[]): Promise<SongInfo[]> => {
|
||||
return getMusicInfos(list.map(item => ({ hash: item.hash })))
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { decodeName, formatPlayTime, sizeFormate } from '../index.ts'
|
||||
import { formatSingerName } from '../utils.ts'
|
||||
import type { SongType, QualityInfo } from '../types.ts'
|
||||
|
||||
|
||||
export default {
|
||||
limit: 30,
|
||||
total: 0,
|
||||
page: 0,
|
||||
allPage: 1,
|
||||
musicSearch(str, page, limit) {
|
||||
const searchRequest = httpFetch(`https://songsearch.kugou.com/song_search_v2?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${limit}&userid=0&clientver=&platform=WebFilter&filter=2&iscorrection=1&privilege_filter=0&area_code=1`)
|
||||
return searchRequest.promise.then(({ body }) => body)
|
||||
},
|
||||
filterData(rawData) {
|
||||
const types: Array<SongType & { hash?: string }> = []
|
||||
const _types: Record<string, QualityInfo & { hash?: string }> = {}
|
||||
if (rawData.FileSize !== 0) {
|
||||
let size = sizeFormate(rawData.FileSize)
|
||||
types.push({ type: '128k', size, hash: rawData.FileHash })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
hash: rawData.FileHash,
|
||||
}
|
||||
}
|
||||
if (rawData.HQFileSize !== 0) {
|
||||
let size = sizeFormate(rawData.HQFileSize)
|
||||
types.push({ type: '320k', size, hash: rawData.HQFileHash })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
hash: rawData.HQFileHash,
|
||||
}
|
||||
}
|
||||
if (rawData.SQFileSize !== 0) {
|
||||
let size = sizeFormate(rawData.SQFileSize)
|
||||
types.push({ type: 'flac', size, hash: rawData.SQFileHash })
|
||||
_types.flac = {
|
||||
size,
|
||||
hash: rawData.SQFileHash,
|
||||
}
|
||||
}
|
||||
if (rawData.ResFileSize !== 0) {
|
||||
let size = sizeFormate(rawData.ResFileSize)
|
||||
types.push({ type: 'flac24bit', size, hash: rawData.ResFileHash })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: rawData.ResFileHash,
|
||||
}
|
||||
}
|
||||
return {
|
||||
singer: decodeName(formatSingerName(rawData.Singers, 'name')),
|
||||
name: decodeName(rawData.SongName),
|
||||
albumName: decodeName(rawData.AlbumName),
|
||||
albumId: rawData.AlbumID,
|
||||
songmid: rawData.Audioid,
|
||||
source: 'kg',
|
||||
interval: formatPlayTime(rawData.Duration),
|
||||
_interval: rawData.Duration,
|
||||
img: null,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
hash: rawData.FileHash,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
}
|
||||
},
|
||||
handleResult(rawData) {
|
||||
let ids = new Set()
|
||||
const list: any[] = []
|
||||
rawData.forEach(item => {
|
||||
const key = item.Audioid + item.FileHash
|
||||
if (ids.has(key)) return
|
||||
ids.add(key)
|
||||
list.push(this.filterData(item))
|
||||
for (const childItem of item.Grp) {
|
||||
const key = item.Audioid + item.FileHash
|
||||
if (ids.has(key)) continue
|
||||
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.error_code !== 0) return this.search(str, page, limit, retryNum)
|
||||
let list = this.handleResult(result.data.lists)
|
||||
|
||||
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,48 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
)
|
||||
return requestObj.promise.then(({ body }) => {
|
||||
if (body.error_code !== 0) return Promise.reject(new Error('图片获取失败'))
|
||||
let info = body.data[0].info
|
||||
const img = info.imgsize ? info.image.replace('{size}', info.imgsize[0]) : info.image
|
||||
if (!img) return Promise.reject(new Error('Pic get failed'))
|
||||
return img
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { getMusicInfosByList } from './musicInfo.ts'
|
||||
import { createHttpFetch } from './util.ts'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 获取歌手信息
|
||||
* @param {*} id
|
||||
*/
|
||||
getInfo(id) {
|
||||
if (id == 0) throw new Error('歌手不存在') // kg源某些歌曲在歌手没被kg收录时返回的歌手id为0
|
||||
return createHttpFetch(`http://mobiles.kugou.com/api/v5/singer/info?singerid=${id}`).then(body => {
|
||||
if (!body) throw new Error('get singer info faild.')
|
||||
|
||||
return {
|
||||
source: 'kg',
|
||||
id: body.singerid,
|
||||
info: {
|
||||
name: body.singername,
|
||||
desc: body.intro,
|
||||
avatar: body.imgurl.replace('{size}', 480),
|
||||
gender: body.grade === 1 ? 'man' : 'woman',
|
||||
},
|
||||
count: {
|
||||
music: body.songcount,
|
||||
album: body.albumcount,
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取歌手专辑列表
|
||||
* @param {*} id
|
||||
* @param {*} page
|
||||
* @param {*} limit
|
||||
*/
|
||||
getAlbumList(id, page = 1, limit = 10) {
|
||||
if (id == 0) throw new Error('歌手不存在')
|
||||
return createHttpFetch(`http://mobiles.kugou.com/api/v5/singer/album?singerid=${id}&page=${page}&pagesize=${limit}`).then(body => {
|
||||
if (!body.info) throw new Error('get singer album list faild.')
|
||||
|
||||
const list = this.filterAlbumList(body.info)
|
||||
return {
|
||||
source: 'kg',
|
||||
list,
|
||||
limit,
|
||||
page,
|
||||
total: body.total,
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取歌手歌曲列表
|
||||
* @param {*} id
|
||||
* @param {*} page
|
||||
* @param {*} limit
|
||||
*/
|
||||
async getSongList(id, page = 1, limit = 100) {
|
||||
if (id == 0) throw new Error('歌手不存在')
|
||||
const body = await createHttpFetch(`http://mobiles.kugou.com/api/v5/singer/song?singerid=${id}&page=${page}&pagesize=${limit}`)
|
||||
if (!body.info) throw new Error('get singer song list faild.')
|
||||
|
||||
const list = await getMusicInfosByList(body.info)
|
||||
return {
|
||||
source: 'kg',
|
||||
list,
|
||||
limit,
|
||||
page,
|
||||
total: body.total,
|
||||
}
|
||||
},
|
||||
filterAlbumList(raw) {
|
||||
return raw.map(item => {
|
||||
return {
|
||||
id: item.albumid,
|
||||
count: item.songcount,
|
||||
info: {
|
||||
name: item.albumname,
|
||||
author: item.singername,
|
||||
img: String(item.imgurl || item.img || '').replace('{size}', '480'),
|
||||
desc: item.intro,
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,988 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
|
||||
import { toMD5 } from '../utils.ts'
|
||||
import { signatureParams } from './util.ts'
|
||||
import type { HttpRequestObject, SongInfo, SongType, QualityInfo } from '../types.ts'
|
||||
|
||||
// 原版使用 @renderer/utils/musicSdk/kg/vendors/infSign.min(浏览器签名库),
|
||||
// 其 H5 签名算法为:{srcappid, clientver, clienttime, mid, uuid, dfid} + 业务参数,
|
||||
// 按键排序拼接后以 key 包裹再取 MD5(与 signatureParams 同构)。
|
||||
const handleSignature = (id, page, limit) => {
|
||||
const params = {
|
||||
srcappid: '2919',
|
||||
clientver: '20000',
|
||||
clienttime: Date.now(),
|
||||
mid: '0',
|
||||
uuid: '0',
|
||||
dfid: '-',
|
||||
appid: 1058,
|
||||
type: 0,
|
||||
module: 'playlist',
|
||||
page,
|
||||
pagesize: limit,
|
||||
specialid: id,
|
||||
}
|
||||
const key = 'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'
|
||||
const sorted = Object.keys(params).sort().map(k => `${k}=${params[k]}`).join('')
|
||||
return Promise.resolve(toMD5(`${key}${sorted}${key}`))
|
||||
}
|
||||
|
||||
export default {
|
||||
_requestObj_tags: null as HttpRequestObject | null,
|
||||
_requestObj_listInfo: null as HttpRequestObject | null,
|
||||
_requestObj_list: null as HttpRequestObject | null,
|
||||
_requestObj_listRecommend: null as HttpRequestObject | 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(?:\?.*|&.*$|#.*$|$)/,
|
||||
},
|
||||
// async getGlobalSpecialId(specialId) {
|
||||
// return httpFetch(`http://mobilecdnbj.kugou.com/api/v5/special/info?specialid=${specialId}`, {
|
||||
// headers: {
|
||||
// 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; HLK-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Mobile Safari/537.36 EdgA/104.0.1293.70',
|
||||
// },
|
||||
// }).promise.then(({ body }) => {
|
||||
// // console.log(body)
|
||||
// if (!body.data.global_specialid) Promise.reject(new Error('Failed to get global collection id.'))
|
||||
// return body.data.global_specialid
|
||||
// })
|
||||
// },
|
||||
// async getListInfoBySpecialId(special_id, retry = 0) {
|
||||
// if (++retry > 2) throw new Error('failed')
|
||||
// return httpFetch(`https://m.kugou.com/plist/list/${special_id}/?json=true`, {
|
||||
// headers: {
|
||||
// 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; HLK-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Mobile Safari/537.36 EdgA/104.0.1293.70',
|
||||
// },
|
||||
// follow_max: 2,
|
||||
// }).promise.then(({ body }) => {
|
||||
// // console.log(body)
|
||||
// if (!body.info.list) return this.getListInfoBySpecialId(special_id, retry)
|
||||
// let listinfo = body.info.list
|
||||
// return {
|
||||
// listInfo: {
|
||||
// name: listinfo.specialname,
|
||||
// image: listinfo.imgurl.replace('{size}', '150'),
|
||||
// intro: listinfo.intro,
|
||||
// author: listinfo.nickname,
|
||||
// playcount: listinfo.playcount,
|
||||
// total: listinfo.songcount,
|
||||
// },
|
||||
// globalSpecialId: listinfo.global_specialid,
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
// async getSongListDetailByGlobalSpecialId(id, page, limit = 100, retry = 0) {
|
||||
// if (++retry > 2) throw new Error('failed')
|
||||
// console.log(id)
|
||||
// const params = `specialid=0&need_sort=1&module=CloudMusic&clientver=11409&pagesize=${limit}&global_collection_id=${id}&userid=0&page=${page}&type=1&area_code=1&appid=1005`
|
||||
// return httpFetch(`http://pubsongscdn.tx.kugou.com/v2/get_other_list_file?${params}&signature=${signatureParams(params)}`).promise.then(({ body }) => {
|
||||
// // console.log(body)
|
||||
// if (body.data?.info == null) return this.getSongListDetailByGlobalSpecialId(id, page, limit, retry)
|
||||
// return body.data.info
|
||||
// })
|
||||
// },
|
||||
parseHtmlDesc(html) {
|
||||
const prefix = '<div class="pc_specail_text pc_singer_tab_content" id="specailIntroduceWrap">'
|
||||
let index = html.indexOf(prefix)
|
||||
if (index < 0) return null
|
||||
const afterStr = html.substring(index + prefix.length)
|
||||
index = afterStr.indexOf('</div>')
|
||||
if (index < 0) return null
|
||||
return decodeName(afterStr.substring(0, index))
|
||||
},
|
||||
async getListDetailBySpecialId(id, page?, tryNum = 0) {
|
||||
if (tryNum > 2) throw new Error('try max num')
|
||||
|
||||
const { body } = await httpFetch(this.getSongListDetailUrl(id)).promise
|
||||
let listData = body.match(this.regExps.listData)
|
||||
let listInfo = body.match(this.regExps.listInfo)
|
||||
if (!listData) return this.getListDetailBySpecialId(id, page, ++tryNum)
|
||||
let list = await this.getMusicInfos(JSON.parse(listData[1]))
|
||||
// listData = this.filterData(JSON.parse(listData[1]))
|
||||
let name
|
||||
let pic
|
||||
if (listInfo) {
|
||||
name = listInfo[1]
|
||||
pic = listInfo[2]
|
||||
}
|
||||
let desc = this.parseHtmlDesc(body)
|
||||
|
||||
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: 10000,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name,
|
||||
img: pic,
|
||||
desc,
|
||||
// author: body.result.info.userinfo.username,
|
||||
// play_count: formatPlayCount(body.result.listen_num),
|
||||
},
|
||||
}
|
||||
|
||||
// const globalSpecialId = await this.getGlobalSpecialId(id)
|
||||
// const limit = 100
|
||||
// const listData = await this.getSongListDetailByGlobalSpecialId(globalSpecialId, page, limit)
|
||||
// if (!Array.isArray(listData))
|
||||
// return this.getUserListDetail2(globalSpecialId)
|
||||
// return {
|
||||
// list: this.filterDatav9(listData),
|
||||
// page,
|
||||
// limit,
|
||||
// total: listInfo.total,
|
||||
// source: 'kg',
|
||||
// info: {
|
||||
// name: listInfo.name,
|
||||
// img: listInfo.image,
|
||||
// desc: listInfo.intro,
|
||||
// author: listInfo.author,
|
||||
// play_count: formatPlayCount(listInfo.playcount),
|
||||
// },
|
||||
// }
|
||||
},
|
||||
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`
|
||||
},
|
||||
|
||||
filterInfoHotTag(rawData) {
|
||||
const result: any[] = []
|
||||
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: any[] = []
|
||||
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 || formatPlayCount(item.play_count),
|
||||
id: 'id_' + item.specialid,
|
||||
author: item.nickname,
|
||||
name: item.specialname,
|
||||
time: dateFormat(item.publish_time || item.publishtime, 'Y-M-D'),
|
||||
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 = {
|
||||
area_code: '1',
|
||||
show_privilege: 1,
|
||||
show_album_info: '1',
|
||||
is_publish: '',
|
||||
appid: 1005,
|
||||
clientver: 11451,
|
||||
mid: '1',
|
||||
dfid: '-',
|
||||
clienttime: Date.now(),
|
||||
key: 'OIlwieks28dk2k092lksi2UIkp',
|
||||
fields: 'album_info,author_name,audio_info,ori_audio_name,base,songname',
|
||||
}
|
||||
let list = hashs
|
||||
let tasks: any[] = []
|
||||
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://gateway.kugou.com/v2/album_audio/audio'
|
||||
return tasks.map(task => this.createHttp(url, {
|
||||
method: 'POST',
|
||||
body: task,
|
||||
headers: {
|
||||
'KG-THash': '13a3164',
|
||||
'KG-RC': '1',
|
||||
'KG-Fake': '0',
|
||||
'KG-RF': '00869891',
|
||||
'User-Agent': 'Android712-AndroidPhone-11451-376-0-FeeCacheUpdate-wifi',
|
||||
'x-router': 'kmr.service.kugou.com',
|
||||
},
|
||||
}).then(data => data.map(s => s[0])))
|
||||
},
|
||||
async getMusicInfos(list) {
|
||||
return this.filterData2(
|
||||
await Promise.all(
|
||||
this.createTask(
|
||||
this.deDuplication(list)
|
||||
.map(item => ({ hash: item.hash })),
|
||||
))
|
||||
.then(([...datas]) => datas.flat()))
|
||||
},
|
||||
|
||||
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)
|
||||
// type 1单曲,2歌单,3电台,4酷狗码,5别人的播放队列
|
||||
let songList
|
||||
let info = songInfo.info
|
||||
switch (info.type) {
|
||||
case 2:
|
||||
if (!info.global_collection_id) return this.getListDetailBySpecialId(info.id)
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
if (info.global_collection_id) return this.getUserListDetail2(info.global_collection_id)
|
||||
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 list = await this.getMusicInfos(songList || songInfo.list)
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: info.count,
|
||||
total: list.length,
|
||||
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: 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 list = await this.getMusicInfos(songInfo.list)
|
||||
// console.log(info, songInfo)
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: songInfo.info.name,
|
||||
img: songInfo.info.img,
|
||||
// desc: body.result.info.list_desc,
|
||||
author: songInfo.info.username,
|
||||
// play_count: 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 decodeGcid(gcid) {
|
||||
const params = 'dfid=-&appid=1005&mid=0&clientver=20109&clienttime=640612895&uuid=-'
|
||||
const body = {
|
||||
ret_info: 1,
|
||||
data: [
|
||||
{
|
||||
id: gcid,
|
||||
id_type: 2,
|
||||
},
|
||||
],
|
||||
}
|
||||
const result = await this.createHttp(`https://t.kugou.com/v1/songlist/batch_decode?${params}&signature=${signatureParams(params, 'android', JSON.stringify(body))}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; HUAWEI HMA-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Mobile Safari/537.36',
|
||||
Referer: 'https://m.kugou.com/',
|
||||
},
|
||||
body,
|
||||
})
|
||||
return result.list[0].global_collection_id
|
||||
},
|
||||
|
||||
async getUserListDetailByLink({ info }, link) {
|
||||
let listInfo = info['0']
|
||||
let total = listInfo.count
|
||||
let tasks: any[] = []
|
||||
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 this.getMusicInfos(result)
|
||||
// console.log(result)
|
||||
return {
|
||||
list: result,
|
||||
page,
|
||||
limit: this.listDetailLimit,
|
||||
total: result.length,
|
||||
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: formatPlayCount(listInfo.count),
|
||||
},
|
||||
}
|
||||
},
|
||||
createGetListDetail2Task(id, total) {
|
||||
let tasks: any[] = []
|
||||
let page = 0
|
||||
while (total) {
|
||||
const limit = total > 300 ? 300 : total
|
||||
total -= limit
|
||||
page += 1
|
||||
const params = '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=-'
|
||||
tasks.push(this.createHttp(`https://mobiles.kugou.com/api/v5/special/song_v2?${params}&signature=${signatureParams(params, 'web')}`, {
|
||||
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')
|
||||
const params = 'appid=1058&specialid=0&global_specialid=' + id + '&format=jsonp&srcappid=2919&clientver=20000&clienttime=1586163242519&mid=1586163242519&uuid=1586163242519&dfid=-'
|
||||
let info = await this.createHttp(`https://mobiles.kugou.com/api/v5/special/info_v2?${params}&signature=${signatureParams(params, 'web')}`, {
|
||||
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 list = await this.getMusicInfos(songInfo)
|
||||
// console.log(info, songInfo, list)
|
||||
return {
|
||||
list,
|
||||
page: 1,
|
||||
limit: this.listDetailLimit,
|
||||
total: list.length,
|
||||
source: 'kg',
|
||||
info: {
|
||||
name: info.specialname,
|
||||
img: info.imgurl && info.imgurl.replace('{size}', 240),
|
||||
desc: info.intro,
|
||||
author: info.nickname,
|
||||
play_count: formatPlayCount(info.playcount),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
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 this.getMusicInfos(result)
|
||||
// console.log(info, songInfo)
|
||||
return 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: list.length ?? 0,
|
||||
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: 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: list.length ?? 0,
|
||||
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: 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 this.getMusicInfos(info.info)
|
||||
// console.log(info, songInfo)
|
||||
return 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('gcid_')) {
|
||||
let gcid = link.match(/gcid_\w+/)?.[0]
|
||||
if (gcid) {
|
||||
const global_collection_id = await this.decodeGcid(gcid)
|
||||
if (global_collection_id) return this.getUserListDetail2(global_collection_id)
|
||||
}
|
||||
}
|
||||
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
|
||||
const loc = location as string | undefined
|
||||
// console.log(body, location)
|
||||
if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum)
|
||||
if (loc) {
|
||||
// console.log(location)
|
||||
if (loc.includes('global_collection_id')) return this.getUserListDetail2(loc.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'))
|
||||
if (loc.includes('gcid_')) {
|
||||
let gcid = link.match(/gcid_\w+/)?.[0]
|
||||
if (gcid) {
|
||||
const global_collection_id = await this.decodeGcid(gcid)
|
||||
if (global_collection_id) return this.getUserListDetail2(global_collection_id)
|
||||
}
|
||||
}
|
||||
if (loc.includes('chain=')) return this.getUserListDetail3(loc.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
|
||||
if (loc.includes('.html')) {
|
||||
if (loc.includes('zlist.html')) {
|
||||
let link = loc.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(loc.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page)
|
||||
}
|
||||
// console.log('location', location)
|
||||
return this.getUserListDetail(loc, page, ++retryNum)
|
||||
}
|
||||
if (typeof body == 'string') {
|
||||
let global_collection_id = body.match(/"global_collection_id":"(\w+)"/)?.[1]
|
||||
if (!global_collection_id) {
|
||||
let gcid = body.match(/"encode_gic":"(\w+)"/)?.[1]
|
||||
if (!gcid) gcid = body.match(/"encode_src_gid":"(\w+)"/)?.[1]
|
||||
if (gcid) global_collection_id = await this.decodeGcid(gcid)
|
||||
}
|
||||
if (!global_collection_id) throw new Error('get list error')
|
||||
return this.getUserListDetail2(global_collection_id)
|
||||
}
|
||||
if (body.errcode !== 0) return this.getUserListDetail(link, page, ++retryNum)
|
||||
return this.getUserListDetailByLink(body, link)
|
||||
},
|
||||
|
||||
async getListDetail(id, page) { // 获取歌曲列表内的音乐
|
||||
id = id.toString()
|
||||
if (id.includes('special/single/')) {
|
||||
id = id.replace(this.regExps.listDetailLink, '$1')
|
||||
} else if (/https?:/.test(id)) {
|
||||
// fix https://www.kugou.com/songlist/xxx/?uid=xxx&chl=qq_client&cover=http%3A%2F%2Fimge.kugou.com%xxx.jpg&iszlist=1
|
||||
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')
|
||||
|
||||
return this.getListDetailBySpecialId(id, page)
|
||||
},
|
||||
filterData(rawList) {
|
||||
// console.log(rawList)
|
||||
return rawList.map(item => {
|
||||
const types: Array<{ type: string; size: string; hash?: string }> = []
|
||||
const _types: Record<string, QualityInfo & { hash?: string }> = {}
|
||||
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: {},
|
||||
}
|
||||
})
|
||||
},
|
||||
// getSinger(singers) {
|
||||
// let arr = []
|
||||
// singers?.forEach(singer => {
|
||||
// arr.push(singer.name)
|
||||
// })
|
||||
// return arr.join('、')
|
||||
// },
|
||||
// v9 API
|
||||
// filterDatav9(rawList) {
|
||||
// console.log(rawList)
|
||||
// return rawList.map(item => {
|
||||
// const types = []
|
||||
// const _types = {}
|
||||
// item.relate_goods.forEach(qualityObj => {
|
||||
// if (qualityObj.level === 2) {
|
||||
// let size = sizeFormate(qualityObj.size)
|
||||
// types.push({ type: '128k', size, hash: qualityObj.hash })
|
||||
// _types['128k'] = {
|
||||
// size,
|
||||
// hash: qualityObj.hash,
|
||||
// }
|
||||
// } else if (qualityObj.level === 4) {
|
||||
// let size = sizeFormate(qualityObj.size)
|
||||
// types.push({ type: '320k', size, hash: qualityObj.hash })
|
||||
// _types['320k'] = {
|
||||
// size,
|
||||
// hash: qualityObj.hash,
|
||||
// }
|
||||
// } else if (qualityObj.level === 5) {
|
||||
// let size = sizeFormate(qualityObj.size)
|
||||
// types.push({ type: 'flac', size, hash: qualityObj.hash })
|
||||
// _types.flac = {
|
||||
// size,
|
||||
// hash: qualityObj.hash,
|
||||
// }
|
||||
// } else if (qualityObj.level === 6) {
|
||||
// let size = sizeFormate(qualityObj.size)
|
||||
// types.push({ type: 'flac24bit', size, hash: qualityObj.hash })
|
||||
// _types.flac24bit = {
|
||||
// size,
|
||||
// hash: qualityObj.hash,
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// const nameInfo = item.name.split(' - ')
|
||||
// return {
|
||||
// singer: this.getSinger(item.singerinfo),
|
||||
// name: decodeName((nameInfo[1] ?? nameInfo[0]).trim()),
|
||||
// albumName: decodeName(item.albuminfo.name),
|
||||
// albumId: item.albuminfo.id,
|
||||
// songmid: item.audio_id,
|
||||
// source: 'kg',
|
||||
// interval: formatPlayTime(item.timelen / 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: SongInfo[] = []
|
||||
rawList.forEach(item => {
|
||||
if (!item) return
|
||||
if (ids.has(item.audio_info.audio_id)) return
|
||||
ids.add(item.audio_info.audio_id)
|
||||
const types: Array<SongType & { hash?: string }> = []
|
||||
const _types: Record<string, QualityInfo & { hash?: string }> = {}
|
||||
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,
|
||||
}
|
||||
}
|
||||
if (item.audio_info.filesize_high !== '0') {
|
||||
let size = sizeFormate(parseInt(item.audio_info.filesize_high))
|
||||
types.push({ type: 'flac24bit', size, hash: item.audio_info.hash_high })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
hash: item.audio_info.hash_high,
|
||||
}
|
||||
}
|
||||
list.push({
|
||||
singer: decodeName(item.author_name),
|
||||
name: decodeName(item.songname),
|
||||
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: formatPlayCount(item.playcount),
|
||||
id: 'id_' + item.specialid,
|
||||
author: item.nickname,
|
||||
name: item.specialname,
|
||||
time: dateFormat(item.publishtime, 'Y-M-D'),
|
||||
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,25 @@
|
||||
import { createHttpFetch } from './util.ts'
|
||||
|
||||
export default {
|
||||
requestObj: null as any,
|
||||
cancelTipSearch() {
|
||||
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
|
||||
},
|
||||
tipSearchBySong(str) {
|
||||
this.cancelTipSearch()
|
||||
this.requestObj = createHttpFetch(`https://searchtip.kugou.com/getSearchTip?MusicTipCount=10&keyword=${encodeURIComponent(str)}`, {
|
||||
headers: {
|
||||
referer: 'https://www.kugou.com/',
|
||||
},
|
||||
})
|
||||
return this.requestObj.then(body => {
|
||||
return body[0].RecordDatas
|
||||
})
|
||||
},
|
||||
handleResult(rawData) {
|
||||
return rawData.map(info => info.HintInfo)
|
||||
},
|
||||
async search(str) {
|
||||
return this.tipSearchBySong(str).then(result => this.handleResult(result))
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { toMD5 } from '../utils.ts'
|
||||
import { httpFetch } from '../request.ts'
|
||||
import type { HttpOptions } from '../types.ts'
|
||||
|
||||
// s.content[0].lyricContent.forEach(([str]) => {
|
||||
// console.log(str)
|
||||
// })
|
||||
|
||||
/**
|
||||
* 签名
|
||||
* @param {*} params
|
||||
* @param {*} apiver
|
||||
*/
|
||||
export const signatureParams = (params: string, platform = 'android', body = ''): string => {
|
||||
let keyparam = 'OIlwieks28dk2k092lksi2UIkp'
|
||||
if (platform === 'web') keyparam = 'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'
|
||||
let param_list = params.split('&')
|
||||
param_list.sort()
|
||||
let sign_params = `${keyparam}${param_list.join('')}${body}${keyparam}`
|
||||
return toMD5(sign_params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个适用于KG的Http请求
|
||||
* @param {*} url
|
||||
* @param {*} options
|
||||
* @param {*} retryNum
|
||||
*/
|
||||
export const createHttpFetch = async(url: string, options?: HttpOptions, retryNum = 0): Promise<any> => {
|
||||
if (retryNum > 2) throw new Error('try max num')
|
||||
let result
|
||||
try {
|
||||
result = await httpFetch(url, options).promise
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return createHttpFetch(url, options, ++retryNum)
|
||||
}
|
||||
// console.log(result.statusCode, result.body)
|
||||
if (result.statusCode !== 200 ||
|
||||
(
|
||||
result.body.error_code ??
|
||||
result.body.errcode ??
|
||||
result.body.err_code) != 0
|
||||
) return createHttpFetch(url, options, ++retryNum)
|
||||
if (result.body.data) return result.body.data
|
||||
if (Array.isArray(result.body.info)) return result.body
|
||||
return result.body.info
|
||||
}
|
||||
Reference in new issue
Block a user