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,215 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { weapi } from './utils/crypto.ts'
|
||||
import { dateFormat2 } from '../index.ts'
|
||||
import type { HttpRequestObject } from '../types.ts'
|
||||
|
||||
const emojis = [
|
||||
['大笑', '😃'],
|
||||
['可爱', '😊'],
|
||||
['憨笑', '☺️'],
|
||||
['色', '😍'],
|
||||
['亲亲', '😙'],
|
||||
['惊恐', '😱'],
|
||||
['流泪', '😭'],
|
||||
['亲', '😚'],
|
||||
['呆', '😳'],
|
||||
['哀伤', '😔'],
|
||||
['呲牙', '😁'],
|
||||
['吐舌', '😝'],
|
||||
['撇嘴', '😒'],
|
||||
['怒', '😡'],
|
||||
['奸笑', '😏'],
|
||||
['汗', '😓'],
|
||||
['痛苦', '😖'],
|
||||
['惶恐', '😰'],
|
||||
['生病', '😨'],
|
||||
['口罩', '😷'],
|
||||
['大哭', '😂'],
|
||||
['晕', '😵'],
|
||||
['发怒', '👿'],
|
||||
['开心', '😄'],
|
||||
['鬼脸', '😜'],
|
||||
['皱眉', '😞'],
|
||||
['流感', '😢'],
|
||||
['爱心', '❤️'],
|
||||
['心碎', '💔'],
|
||||
['钟情', '💘'],
|
||||
['星星', '⭐️'],
|
||||
['生气', '💢'],
|
||||
['便便', '💩'],
|
||||
['强', '👍'],
|
||||
['弱', '👎'],
|
||||
['拜', '🙏'],
|
||||
['牵手', '👫'],
|
||||
['跳舞', '👯♀️'],
|
||||
['禁止', '🙅♀️'],
|
||||
['这边', '💁♀️'],
|
||||
['爱意', '💏'],
|
||||
['示爱', '👩❤️👨'],
|
||||
['嘴唇', '👄'],
|
||||
['狗', '🐶'],
|
||||
['猫', '🐱'],
|
||||
['猪', '🐷'],
|
||||
['兔子', '🐰'],
|
||||
['小鸡', '🐤'],
|
||||
['公鸡', '🐔'],
|
||||
['幽灵', '👻'],
|
||||
['圣诞', '🎅'],
|
||||
['外星', '👽'],
|
||||
['钻石', '💎'],
|
||||
['礼物', '🎁'],
|
||||
['男孩', '👦'],
|
||||
['女孩', '👧'],
|
||||
['蛋糕', '🎂'],
|
||||
['18', '🔞'],
|
||||
['圈', '⭕'],
|
||||
['叉', '❌'],
|
||||
]
|
||||
|
||||
const applyEmoji = (text: string): string => {
|
||||
for (const e of emojis) text = text.replaceAll(`[${e[0]}]`, e[1])
|
||||
return text
|
||||
}
|
||||
|
||||
let cursorTools = {
|
||||
cache: {} as Record<string, {
|
||||
page?: number
|
||||
prevCursor?: number
|
||||
cursor?: number
|
||||
orderType?: number
|
||||
offset?: number
|
||||
}>,
|
||||
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 as HttpRequestObject | null,
|
||||
_requestObj2: null as HttpRequestObject | 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
|
||||
page = page - 1
|
||||
|
||||
const _requestObj2 = httpFetch(`https://music.163.com/weapi/v1/resource/hotcomments/${id}`, {
|
||||
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({
|
||||
rid: id,
|
||||
limit,
|
||||
offset: limit * page,
|
||||
beforeTime: Date.now().toString(),
|
||||
}),
|
||||
})
|
||||
const { body, statusCode } = await _requestObj2.promise
|
||||
if (statusCode != 200 || body.code !== 200) throw new Error('获取热门评论失败')
|
||||
const total = body.total ?? 0
|
||||
return { source: 'wy', comments: this.filterComment(body.hotComments), total, page, limit, maxPage: Math.ceil(total / limit) || 1 }
|
||||
},
|
||||
filterComment(rawList) {
|
||||
return rawList.map(item => {
|
||||
let data = {
|
||||
id: item.commentId,
|
||||
text: item.content ? applyEmoji(item.content) : '',
|
||||
time: item.time ? item.time : '',
|
||||
timeStr: item.time ? dateFormat2(item.time) : '',
|
||||
location: item.ipLocation?.location,
|
||||
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 ? applyEmoji(replyData.content) : '',
|
||||
time: item.time,
|
||||
timeStr: null,
|
||||
location: replyData.ipLocation?.location,
|
||||
userName: replyData.user.nickname,
|
||||
avatar: replyData.user.avatarUrl,
|
||||
userId: replyData.user.userId,
|
||||
likedCount: null,
|
||||
reply: [data],
|
||||
}
|
||||
: data
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { eapiRequest } from './utils/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 = eapiRequest('/api/search/chart/detail', {
|
||||
id: 'HOT_SEARCH_SONG#@#',
|
||||
})
|
||||
const { body, statusCode } = await _requestObj.promise
|
||||
if (statusCode != 200 || body.code !== 200) throw new Error('获取热搜词失败')
|
||||
|
||||
return { source: 'wy', list: this.filterList(body.data.itemList) }
|
||||
},
|
||||
filterList(rawList) {
|
||||
return rawList.map(item => item.searchWord)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import leaderboard from './leaderboard.ts'
|
||||
import { apis } from '../api-source.ts'
|
||||
import getLyric from './lyric.ts'
|
||||
import getMusicInfo from './musicInfo.ts'
|
||||
import musicSearch from './musicSearch.ts'
|
||||
import songList from './songList.ts'
|
||||
import hotSearch from './hotSearch.ts'
|
||||
import comment from './comment.ts'
|
||||
import singer from './singer.ts'
|
||||
// import tipSearch from './tipSearch.ts'
|
||||
|
||||
const wy = {
|
||||
// tipSearch,
|
||||
leaderboard,
|
||||
musicSearch,
|
||||
songList,
|
||||
hotSearch,
|
||||
comment,
|
||||
singer,
|
||||
getMusicUrl(songInfo, type) {
|
||||
return apis('wy').getMusicUrl!(songInfo, type)
|
||||
},
|
||||
getLyric(songInfo) {
|
||||
return getLyric(songInfo.songmid)
|
||||
},
|
||||
getPic(songInfo) {
|
||||
const requestObj = getMusicInfo(songInfo.songmid)
|
||||
return requestObj.promise.then(info => info.al.picUrl)
|
||||
},
|
||||
getMusicDetailPageUrl(songInfo) {
|
||||
return `https://music.163.com/#/song?id=${songInfo.songmid}`
|
||||
},
|
||||
}
|
||||
|
||||
export default wy
|
||||
@@ -0,0 +1,209 @@
|
||||
import { weapi } from './utils/crypto.ts'
|
||||
import { httpFetch } from '../request.ts'
|
||||
import musicDetailApi from './musicDetail.ts'
|
||||
import type { HttpRequestObject } from '../types.ts'
|
||||
|
||||
const topList = [{ id: 'wy__19723756', name: '飙升榜', bangid: '19723756' },
|
||||
{ id: 'wy__3779629', name: '新歌榜', bangid: '3779629' },
|
||||
{ id: 'wy__2884035', name: '原创榜', bangid: '2884035' },
|
||||
{ id: 'wy__3778678', name: '热歌榜', bangid: '3778678' },
|
||||
{ id: 'wy__991319590', name: '说唱榜', bangid: '991319590' },
|
||||
{ id: 'wy__71384707', name: '古典榜', bangid: '71384707' },
|
||||
{ id: 'wy__1978921795', name: '电音榜', bangid: '1978921795' },
|
||||
{ id: 'wy__5453912201', name: '黑胶VIP爱听榜', bangid: '5453912201' },
|
||||
{ id: 'wy__71385702', name: 'ACG榜', bangid: '71385702' },
|
||||
{ id: 'wy__745956260', name: '韩语榜', bangid: '745956260' },
|
||||
{ id: 'wy__10520166', name: '国电榜', bangid: '10520166' },
|
||||
{ id: 'wy__180106', name: 'UK排行榜周榜', bangid: '180106' },
|
||||
{ id: 'wy__60198', name: '美国Billboard榜', bangid: '60198' },
|
||||
{ id: 'wy__3812895', name: 'Beatport全球电子舞曲榜', bangid: '3812895' },
|
||||
{ id: 'wy__21845217', name: 'KTV唛榜', bangid: '21845217' },
|
||||
{ id: 'wy__60131', name: '日本Oricon榜', bangid: '60131' },
|
||||
{ id: 'wy__2809513713', name: '欧美热歌榜', bangid: '2809513713' },
|
||||
{ id: 'wy__2809577409', name: '欧美新歌榜', bangid: '2809577409' },
|
||||
{ id: 'wy__27135204', name: '法国 NRJ Vos Hits 周榜', bangid: '27135204' },
|
||||
{ id: 'wy__3001835560', name: 'ACG动画榜', bangid: '3001835560' },
|
||||
{ id: 'wy__3001795926', name: 'ACG游戏榜', bangid: '3001795926' },
|
||||
{ id: 'wy__3001890046', name: 'ACG VOCALOID榜', bangid: '3001890046' },
|
||||
{ id: 'wy__3112516681', name: '中国新乡村音乐排行榜', bangid: '3112516681' },
|
||||
{ id: 'wy__5059644681', name: '日语榜', bangid: '5059644681' },
|
||||
{ id: 'wy__5059633707', name: '摇滚榜', bangid: '5059633707' },
|
||||
{ id: 'wy__5059642708', name: '国风榜', bangid: '5059642708' },
|
||||
{ id: 'wy__5338990334', name: '潜力爆款榜', bangid: '5338990334' },
|
||||
{ id: 'wy__5059661515', name: '民谣榜', bangid: '5059661515' },
|
||||
{ id: 'wy__6688069460', name: '听歌识曲榜', bangid: '6688069460' },
|
||||
{ id: 'wy__6723173524', name: '网络热歌榜', bangid: '6723173524' },
|
||||
{ id: 'wy__6732051320', name: '俄语榜', bangid: '6732051320' },
|
||||
{ id: 'wy__6732014811', name: '越南语榜', bangid: '6732014811' },
|
||||
{ id: 'wy__6886768100', name: '中文DJ榜', bangid: '6886768100' },
|
||||
{ id: 'wy__6939992364', name: '俄罗斯top hit流行音乐榜', bangid: '6939992364' },
|
||||
{ id: 'wy__7095271308', name: '泰语榜', bangid: '7095271308' },
|
||||
{ id: 'wy__7356827205', name: 'BEAT排行榜', bangid: '7356827205' },
|
||||
{ id: 'wy__7325478166', name: '编辑推荐榜VOL.44 天才女子摇滚乐队boygenius剖白卑微心迹', bangid: '7325478166' },
|
||||
{ id: 'wy__7603212484', name: 'LOOK直播歌曲榜', bangid: '7603212484' },
|
||||
{ id: 'wy__7775163417', name: '赏音榜', bangid: '7775163417' },
|
||||
{ id: 'wy__7785123708', name: '黑胶VIP新歌榜', bangid: '7785123708' },
|
||||
{ id: 'wy__7785066739', name: '黑胶VIP热歌榜', bangid: '7785066739' },
|
||||
{ id: 'wy__7785091694', name: '黑胶VIP爱搜榜', bangid: '7785091694' },
|
||||
]
|
||||
|
||||
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 as HttpRequestObject | 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: any[] = []
|
||||
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: any) {
|
||||
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 as any).getList(resp.body.playlist.trackIds.map(trackId => trackId.id))
|
||||
} catch (err: any) {
|
||||
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}`
|
||||
},
|
||||
}
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { eapi } from './utils/crypto.ts'
|
||||
import type { LyricInfo, HttpRequestObject } from '../types.ts'
|
||||
// import { decodeName } from '../...ts'
|
||||
|
||||
// const parseLyric = (str, lrc) => {
|
||||
// if (!str) return ''
|
||||
|
||||
// str = str.replace(/\r/g, '')
|
||||
|
||||
// let yrc = 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
|
||||
// })
|
||||
|
||||
// yrc = decodeName(yrc)
|
||||
// return yrc.trim()
|
||||
// }
|
||||
|
||||
const eapiRequest = (url: string, data: object) => {
|
||||
return httpFetch('https://interface3.music.163.com/eapi/song/lyric/v1', {
|
||||
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
|
||||
}
|
||||
|
||||
const parseTools = {
|
||||
rxps: {
|
||||
info: /^{"/,
|
||||
lineTime: /^\[(\d+),\d+\]/,
|
||||
wordTime: /\(\d+,\d+,\d+\)/,
|
||||
wordTimeAll: /(\(\d+,\d+,\d+\))/g,
|
||||
},
|
||||
msFormat(timeMs) {
|
||||
if (Number.isNaN(timeMs)) return ''
|
||||
let ms = timeMs % 1000
|
||||
timeMs /= 1000
|
||||
let m = parseInt((timeMs / 60) as any).toString().padStart(2, '0')
|
||||
timeMs %= 60
|
||||
let s = parseInt(timeMs as any).toString().padStart(2, '0')
|
||||
return `[${m}:${s}.${ms}]`
|
||||
},
|
||||
parseLyric(lines) {
|
||||
const yrcLines: string[] = []
|
||||
const lrcLines: string[] = []
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim()
|
||||
let result = this.rxps.lineTime.exec(line)
|
||||
if (!result) {
|
||||
if (line.startsWith('[offset')) {
|
||||
yrcLines.push(line)
|
||||
lrcLines.push(line)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const startMsTime = parseInt(result[1])
|
||||
const startTimeStr = this.msFormat(startMsTime)
|
||||
if (!startTimeStr) continue
|
||||
|
||||
let words = line.replace(this.rxps.lineTime, '')
|
||||
|
||||
lrcLines.push(`${startTimeStr}${words.replace(this.rxps.wordTimeAll, '')}`)
|
||||
|
||||
let times = words.match(this.rxps.wordTimeAll)
|
||||
if (!times) continue
|
||||
times = times.map(time => {
|
||||
const result = /\((\d+),(\d+),\d+\)/.exec(time)
|
||||
return `<${Math.max(parseInt(result![1]) - startMsTime, 0)},${result![2]}>`
|
||||
})
|
||||
const wordArr = words.split(this.rxps.wordTime)
|
||||
wordArr.shift()
|
||||
const newWords = times.map((time, index) => `${time}${wordArr[index]}`).join('')
|
||||
yrcLines.push(`${startTimeStr}${newWords}`)
|
||||
}
|
||||
return {
|
||||
lyric: lrcLines.join('\n'),
|
||||
yrc: yrcLines.join('\n'),
|
||||
}
|
||||
},
|
||||
parseHeaderInfo(str) {
|
||||
str = str.trim()
|
||||
str = str.replace(/\r/g, '')
|
||||
if (!str) return null
|
||||
const lines = str.split('\n')
|
||||
return lines.map(line => {
|
||||
if (!this.rxps.info.test(line)) return line
|
||||
try {
|
||||
const info = JSON.parse(line)
|
||||
const timeTag = this.msFormat(info.t)
|
||||
return timeTag ? `${timeTag}${info.c.map(t => t.tx).join('')}` : ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
},
|
||||
getIntv(interval) {
|
||||
if (!interval) return 0
|
||||
if (!interval.includes('.')) interval += '.0'
|
||||
let arr = interval.split(/:|\./)
|
||||
while (arr.length < 3) arr.unshift('0')
|
||||
const [m, s, ms] = arr
|
||||
return parseInt(m) * 3600000 + parseInt(s) * 1000 + parseInt(ms)
|
||||
},
|
||||
fixTimeTag(lrc, targetlrc) {
|
||||
let lrcLines = lrc.split('\n')
|
||||
const targetlrcLines = targetlrc.split('\n')
|
||||
const timeRxp = /^\[([\d:.]+)\]/
|
||||
let temp: string[] = []
|
||||
let newLrc: string[] = []
|
||||
targetlrcLines.forEach((line) => {
|
||||
const result = timeRxp.exec(line)
|
||||
if (!result) return
|
||||
const words = line.replace(timeRxp, '')
|
||||
if (!words.trim()) return
|
||||
const t1 = this.getIntv(result[1])
|
||||
|
||||
while (lrcLines.length) {
|
||||
const lrcLine = lrcLines.shift()!
|
||||
const lrcLineResult = timeRxp.exec(lrcLine)
|
||||
if (!lrcLineResult) continue
|
||||
const t2 = this.getIntv(lrcLineResult[1])
|
||||
if (Math.abs(t1 - t2) < 100) {
|
||||
const lrc = line.replace(timeRxp, lrcLineResult[0]).trim()
|
||||
if (!lrc) continue
|
||||
newLrc.push(lrc)
|
||||
break
|
||||
}
|
||||
temp.push(lrcLine)
|
||||
}
|
||||
lrcLines = [...temp, ...lrcLines]
|
||||
temp = []
|
||||
})
|
||||
return newLrc.join('\n')
|
||||
},
|
||||
parse(ylrc, ytlrc, yrlrc, lrc, tlrc, rlrc) {
|
||||
const info = {
|
||||
lyric: '',
|
||||
tlyric: '',
|
||||
rlyric: '',
|
||||
yrc: '',
|
||||
}
|
||||
if (ylrc) {
|
||||
let lines = this.parseHeaderInfo(ylrc)
|
||||
if (lines) {
|
||||
const result = this.parseLyric(lines)
|
||||
if (ytlrc) {
|
||||
const lines = this.parseHeaderInfo(ytlrc)
|
||||
if (lines) {
|
||||
// if (lines.length == result.lyricLines.length) {
|
||||
info.tlyric = this.fixTimeTag(result.lyric, lines.join('\n'))
|
||||
// } else info.tlyric = lines.join('\n')
|
||||
}
|
||||
}
|
||||
if (yrlrc) {
|
||||
const lines = this.parseHeaderInfo(yrlrc)
|
||||
if (lines) {
|
||||
// if (lines.length == result.lyricLines.length) {
|
||||
info.rlyric = this.fixTimeTag(result.lyric, lines.join('\n'))
|
||||
// } else info.rlyric = lines.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
const timeRxp = /^\[[\d:.]+\]/
|
||||
const headers = lines.filter(l => timeRxp.test(l)).join('\n')
|
||||
info.lyric = `${headers}\n${result.lyric}`
|
||||
info.yrc = result.yrc
|
||||
return info
|
||||
}
|
||||
}
|
||||
if (lrc) {
|
||||
const lines = this.parseHeaderInfo(lrc)
|
||||
if (lines) info.lyric = lines.join('\n')
|
||||
}
|
||||
if (tlrc) {
|
||||
const lines = this.parseHeaderInfo(tlrc)
|
||||
if (lines) info.tlyric = lines.join('\n')
|
||||
}
|
||||
if (rlrc) {
|
||||
const lines = this.parseHeaderInfo(rlrc)
|
||||
if (lines) info.rlyric = lines.join('\n')
|
||||
}
|
||||
|
||||
return info
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// 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 ?? '',
|
||||
// // yrc: parseLyric(body.klyric.lyric),
|
||||
// }
|
||||
// })
|
||||
// return requestObj
|
||||
// }
|
||||
|
||||
// 参考社区通用的歌词时间标签修正思路
|
||||
const fixTimeLabel = (lrc: string, tlrc?: string, romalrc?: string) => {
|
||||
if (lrc) {
|
||||
let newLrc = lrc.replace(/\[(\d{2}:\d{2}):(\d{2})]/g, '[$1.$2]')
|
||||
let newTlrc = tlrc?.replace(/\[(\d{2}:\d{2}):(\d{2})]/g, '[$1.$2]') ?? tlrc
|
||||
if (newLrc != lrc || newTlrc != tlrc) {
|
||||
lrc = newLrc
|
||||
tlrc = newTlrc
|
||||
if (romalrc) romalrc = romalrc.replace(/\[(\d{2}:\d{2}):(\d{2,3})]/g, '[$1.$2]').replace(/\[(\d{2}:\d{2}\.\d{2})0]/g, '[$1]')
|
||||
}
|
||||
}
|
||||
|
||||
return { lrc, tlrc, romalrc }
|
||||
}
|
||||
|
||||
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/lyric_new.js
|
||||
export default (songmid: string): HttpRequestObject => {
|
||||
const requestObj = eapiRequest('/api/song/lyric/v1', {
|
||||
id: songmid,
|
||||
cp: false,
|
||||
tv: 0,
|
||||
lv: 0,
|
||||
rv: 0,
|
||||
kv: 0,
|
||||
yv: 0,
|
||||
ytv: 0,
|
||||
yrv: 0,
|
||||
})
|
||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||
// console.log(body)
|
||||
if (body.code !== 200 || !body?.lrc?.lyric) return Promise.reject(new Error('Get lyric failed'))
|
||||
const fixTimeLabelLrc = fixTimeLabel(body.lrc.lyric, body.tlyric?.lyric, body.romalrc?.lyric)
|
||||
const info = parseTools.parse(body.yrc?.lyric, body.ytlrc?.lyric, body.yromalrc?.lyric, fixTimeLabelLrc.lrc, fixTimeLabelLrc.tlrc, fixTimeLabelLrc.romalrc)
|
||||
// console.log(info)
|
||||
if (!info.lyric) return Promise.reject(new Error('Get lyric failed'))
|
||||
return info
|
||||
}) as any
|
||||
return requestObj
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { weapi } from './utils/crypto.ts'
|
||||
import { formatPlayTime, sizeFormate } from '../index.ts'
|
||||
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
|
||||
|
||||
export default {
|
||||
getSinger(singers) {
|
||||
let arr: any[] = []
|
||||
singers?.forEach(singer => {
|
||||
arr.push(singer.name)
|
||||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
filterList({ songs, privileges }) {
|
||||
// console.log(songs, privileges)
|
||||
const list: any[] = []
|
||||
songs.forEach((item, index) => {
|
||||
const types: Array<{ type: string, size: string | null }> = []
|
||||
const _types: Record<string, { size: string | null }> = {}
|
||||
let size
|
||||
let privilege = privileges[index]
|
||||
if (privilege.id !== item.id) privilege = privileges.find(p => p.id === item.id)
|
||||
if (!privilege) return
|
||||
|
||||
if (privilege.maxBrLevel == 'hires') {
|
||||
size = item.hr ? sizeFormate(item.hr.size) : null
|
||||
types.push({ type: 'flac24bit', size })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
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()
|
||||
|
||||
if (item.pc) {
|
||||
list.push({
|
||||
singer: item.pc.ar ?? '',
|
||||
name: item.pc.sn ?? '',
|
||||
albumName: item.pc.alb ?? '',
|
||||
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: {},
|
||||
})
|
||||
} else {
|
||||
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.ts'
|
||||
import { weapi } from './utils/crypto.ts'
|
||||
import type { RequestObj } from '../types.ts'
|
||||
|
||||
export default (songmid: string): RequestObj<any> => {
|
||||
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(new Error('获取歌曲信息失败'))
|
||||
return body.songs[0]
|
||||
})
|
||||
return requestObj
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// import { httpFetch } from '../request.ts'
|
||||
// import { weapi } from './utils/crypto.ts'
|
||||
import { sizeFormate, formatPlayTime } from '../index.ts'
|
||||
// import musicDetailApi from './musicDetail.ts'
|
||||
import { eapiRequest } from './utils/index.ts'
|
||||
|
||||
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),
|
||||
// })
|
||||
const searchRequest = eapiRequest('/api/search/song/list/page', {
|
||||
keyword: str,
|
||||
needCorrect: '1',
|
||||
channel: 'typing',
|
||||
offset: limit * (page - 1),
|
||||
scene: 'normal',
|
||||
total: page == 1,
|
||||
limit,
|
||||
})
|
||||
return searchRequest.promise.then(({ body }) => body)
|
||||
},
|
||||
getSinger(singers) {
|
||||
let arr: any[] = []
|
||||
singers.forEach(singer => {
|
||||
arr.push(singer.name)
|
||||
})
|
||||
return arr.join('、')
|
||||
},
|
||||
handleResult(rawList) {
|
||||
// console.log(rawList)
|
||||
if (!rawList) return []
|
||||
return rawList.map(item => {
|
||||
item = item.baseInfo.simpleSongData
|
||||
const types: Array<{ type: string, size: string | null }> = []
|
||||
const _types: Record<string, { size: string | null }> = {}
|
||||
let size
|
||||
|
||||
if (item.privilege.maxBrLevel == 'hires') {
|
||||
size = item.hr ? sizeFormate(item.hr.size) : null
|
||||
types.push({ type: 'flac24bit', size })
|
||||
_types.flac24bit = {
|
||||
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.data.resources || [])
|
||||
// console.log(list)
|
||||
|
||||
if (list == null) return this.search(str, page, limit, retryNum)
|
||||
|
||||
this.total = result.data.totalCount || 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,155 @@
|
||||
import { eapiRequest } from './utils/index.ts'
|
||||
import { formatPlayTime, sizeFormate } from '../index.ts'
|
||||
import { formatSingerName } from '../utils.ts'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 获取歌手信息
|
||||
* @param {*} id
|
||||
*/
|
||||
getInfo(id) {
|
||||
return eapiRequest('/api/artist/head/info/get', { id }).promise.then(({ body }) => {
|
||||
if (!body || body.code != 200) throw new Error('get singer info faild.')
|
||||
// 响应结构为 { code, data: { artist: { ... } } },无 body.user
|
||||
const artist = body.data?.artist
|
||||
if (!artist) throw new Error('get singer info faild.')
|
||||
return {
|
||||
source: 'wy',
|
||||
id: artist.id,
|
||||
info: {
|
||||
name: artist.name,
|
||||
desc: artist.briefDesc,
|
||||
avatar: artist.avatar ?? artist.cover ?? null,
|
||||
gender: null,
|
||||
},
|
||||
count: {
|
||||
music: artist.musicSize,
|
||||
album: artist.albumSize,
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取歌手歌曲列表
|
||||
* @param {*} id
|
||||
* @param {*} page
|
||||
* @param {*} limit
|
||||
*/
|
||||
getSongList(id, page = 1, limit = 100) {
|
||||
return eapiRequest('/api/v2/artist/songs', {
|
||||
id,
|
||||
limit,
|
||||
offset: (page - 1) * limit,
|
||||
}).promise.then(({ body }) => {
|
||||
if (!body.songs || body.code != 200) throw new Error('get singer song list faild.')
|
||||
|
||||
const list = this.filterSongList(body.songs)
|
||||
return {
|
||||
list,
|
||||
limit,
|
||||
page,
|
||||
total: body.total,
|
||||
source: 'wy',
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取歌手专辑列表
|
||||
* @param {*} id
|
||||
* @param {*} page
|
||||
* @param {*} limit
|
||||
*/
|
||||
getAlbumList(id, page = 1, limit = 10) {
|
||||
return eapiRequest(`/api/artist/albums/${id}`, {
|
||||
limit,
|
||||
offset: (page - 1) * limit,
|
||||
}).promise.then(({ body }) => {
|
||||
if (!body.hotAlbums || body.code != 200) throw new Error('get singer album list faild.')
|
||||
|
||||
const list = this.filterAlbumList(body.hotAlbums)
|
||||
return {
|
||||
source: 'wy',
|
||||
list,
|
||||
limit,
|
||||
page,
|
||||
total: body.artist.albumSize,
|
||||
}
|
||||
})
|
||||
},
|
||||
filterAlbumList(raw) {
|
||||
const list: any[] = []
|
||||
raw.forEach(item => {
|
||||
if (!item.id) return
|
||||
list.push({
|
||||
id: item.id,
|
||||
count: item.size,
|
||||
info: {
|
||||
name: item.name,
|
||||
author: formatSingerName(item.artists),
|
||||
img: item.picUrl,
|
||||
desc: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
filterSongList(raw) {
|
||||
const list: any[] = []
|
||||
raw.forEach(item => {
|
||||
if (!item.id) return
|
||||
|
||||
const types: Array<{ type: string, size: string | null }> = []
|
||||
const _types: Record<string, { size: string | null }> = {}
|
||||
let size
|
||||
item.privilege.chargeInfoList.forEach(i => {
|
||||
switch (i.rate) {
|
||||
case 128000:
|
||||
size = item.lMusic ? sizeFormate(item.lMusic.size) : null
|
||||
types.push({ type: '128k', size })
|
||||
_types['128k'] = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
case 320000:
|
||||
size = item.hMusic ? sizeFormate(item.hMusic.size) : null
|
||||
types.push({ type: '320k', size })
|
||||
_types['320k'] = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
case 999000:
|
||||
size = item.sqMusic ? sizeFormate(item.sqMusic.size) : null
|
||||
types.push({ type: 'flac', size })
|
||||
_types.flac = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
case 1999000:
|
||||
size = item.hrMusic ? sizeFormate(item.hrMusic.size) : null
|
||||
types.push({ type: 'flac24bit', size })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
list.push({
|
||||
singer: formatSingerName(item.artists),
|
||||
name: item.name,
|
||||
albumName: item.album.name,
|
||||
albumId: item.album.id,
|
||||
songmid: item.id,
|
||||
source: 'wy',
|
||||
interval: formatPlayTime((item.dt ?? item.duration) / 1000),
|
||||
img: item.album?.picUrl ?? null,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
types,
|
||||
_types,
|
||||
typeUrl: {},
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
// 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.ts'
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
|
||||
import musicDetailApi from './musicDetail.ts'
|
||||
import { eapiRequest } from './utils/index.ts'
|
||||
import { formatSingerName } from '../utils.ts'
|
||||
import type { HttpRequestObject } from '../types.ts'
|
||||
|
||||
export default {
|
||||
_requestObj_tags: null as HttpRequestObject | null,
|
||||
_requestObj_hotTags: null as HttpRequestObject | null,
|
||||
_requestObj_list: null as HttpRequestObject | 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+\/.+$/,
|
||||
},
|
||||
|
||||
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 list
|
||||
if (body.playlist.trackIds.length == body.privileges.length) {
|
||||
list = this.filterListDetail(body)
|
||||
} else {
|
||||
try {
|
||||
list = (await (musicDetailApi as any).getList(body.playlist.trackIds.slice(rangeStart, limit * page).map(trackId => trackId.id))).list
|
||||
} catch (err: any) {
|
||||
console.log(err)
|
||||
if (err.message == 'try max num') {
|
||||
throw err
|
||||
} else {
|
||||
return this.getListDetail(id, page, ++tryNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log(list)
|
||||
return {
|
||||
list,
|
||||
page,
|
||||
limit,
|
||||
total: body.playlist.trackIds.length,
|
||||
source: 'wy',
|
||||
info: {
|
||||
play_count: 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: any[] = []
|
||||
tracks.forEach((item, index) => {
|
||||
const types: Array<{ type: string, size: string | null }> = []
|
||||
const _types: Record<string, { size: string | null }> = {}
|
||||
let size
|
||||
let privilege = privileges[index]
|
||||
if (privilege.id !== item.id) privilege = privileges.find(p => p.id === item.id)
|
||||
if (!privilege) return
|
||||
|
||||
if (privilege.maxBrLevel == 'hires') {
|
||||
size = item.hr ? sizeFormate(item.hr.size) : null
|
||||
types.push({ type: 'flac24bit', size })
|
||||
_types.flac24bit = {
|
||||
size,
|
||||
}
|
||||
}
|
||||
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()
|
||||
|
||||
if (item.pc) {
|
||||
list.push({
|
||||
singer: item.pc.ar ?? '',
|
||||
name: item.pc.sn ?? '',
|
||||
albumName: item.pc.alb ?? '',
|
||||
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: {},
|
||||
})
|
||||
} else {
|
||||
list.push({
|
||||
singer: formatSingerName(item.ar, 'name'),
|
||||
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: formatPlayCount(item.playCount),
|
||||
id: String(item.id),
|
||||
author: item.creator.nickname,
|
||||
name: item.name,
|
||||
time: item.createTime ? dateFormat(item.createTime, 'Y-M-D') : '',
|
||||
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: any[] = []
|
||||
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,34 @@
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { weapi } from './utils/crypto.ts'
|
||||
import { formatSingerName } from '../utils.ts'
|
||||
import type { HttpRequestObject } from '../types.ts'
|
||||
|
||||
export default {
|
||||
requestObj: null as HttpRequestObject | null,
|
||||
cancelTipSearch() {
|
||||
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
|
||||
},
|
||||
tipSearchBySong(str) {
|
||||
this.cancelTipSearch()
|
||||
this.requestObj = httpFetch('https://music.163.com/weapi/search/suggest/web', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
referer: 'https://music.163.com/',
|
||||
origin: 'https://music.163.com/',
|
||||
},
|
||||
form: weapi({
|
||||
s: str,
|
||||
}),
|
||||
})
|
||||
return this.requestObj!.promise.then(({ statusCode, body }) => {
|
||||
if (statusCode != 200 || body.code != 200) return Promise.reject(new Error('请求失败'))
|
||||
return body.result.songs
|
||||
})
|
||||
},
|
||||
handleResult(rawData) {
|
||||
return rawData.map(info => `${info.name} - ${formatSingerName(info.artists, 'name')}`)
|
||||
},
|
||||
async search(str) {
|
||||
return this.tipSearchBySong(str).then(result => this.handleResult(result))
|
||||
},
|
||||
}
|
||||
@@ -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: Buffer, mode: string, key: Buffer | string, iv: Buffer | string): Buffer => {
|
||||
const cipher = createCipheriv(mode, key, iv)
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()])
|
||||
}
|
||||
|
||||
const aesDecrypt = function(cipherBuffer: Buffer, mode: string, key: Buffer | string, iv: Buffer | string): Buffer {
|
||||
let decipher = createDecipheriv(mode, key, iv)
|
||||
return Buffer.concat([decipher.update(cipherBuffer), decipher.final()])
|
||||
}
|
||||
|
||||
const rsaEncrypt = (buffer: Buffer, key: string): Buffer => {
|
||||
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
|
||||
return publicEncrypt({ key, padding: constants.RSA_NO_PADDING }, buffer)
|
||||
}
|
||||
|
||||
export const weapi = (object: object): { params: string, encSecKey: string } => {
|
||||
const text = JSON.stringify(object)
|
||||
const secretKey = randomBytes(16).map(n => ((base62.charAt(n % 62) as any).charCodeAt())) as Buffer
|
||||
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: object): { eparams: string } => {
|
||||
const text = JSON.stringify(object)
|
||||
return {
|
||||
eparams: aesEncrypt(Buffer.from(text), 'aes-128-ecb', linuxapiKey, '').toString('hex').toUpperCase(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const eapi = (url: string, object: object | string): { params: string } => {
|
||||
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: Buffer): string => {
|
||||
return aesDecrypt(cipherBuffer, 'aes-128-ecb', eapiKey, '').toString()
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { httpFetch } from '../../request.ts'
|
||||
import { eapi } from './crypto.ts'
|
||||
import { getWyCookie } from '../../auth/store.ts'
|
||||
import type { HttpRequestObject } from '../../types.ts'
|
||||
|
||||
export const eapiRequest = (url: string, data: object): HttpRequestObject => {
|
||||
// 登录后携带 MUSIC_U(见 /login/wy),未登录时不附加
|
||||
const cookie = getWyCookie()
|
||||
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 ? { cookie } : {}),
|
||||
},
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user