feat(sdk): kg/kw/tx/wy 补齐专辑、歌单与 parse 能力
四个主流平台从「搜索 + 取流」补到能力对齐,新增 playlist/parse (tx、wy 另加 userPlaylist),顺带修掉酷我直链的一个真实缺陷。 - kw:酷我 CDN 回吐的 query 用 `$` 顶替 `=`(`?bitrate$320&format$mp3…`), `$` 非 URL 标准分隔符,播放器与解析器普遍不认,导致拿到直链也放不出声; 统一还原成 `=` 再对外返回 - wy:补齐 album/playlist/parse/userPlaylist,与 kg/tx 能力对齐 - kg/kw/tx:接入 parse(跨平台精确匹配的输入源)与歌单 为什么单独一次提交:这批改动不动架构,只做能力补齐与 URL 规范化, 与新增平台的注册表改动分开,出问题时便于单独回滚。
This commit is contained in:
1 parent
33b96f92c4
commit
68e071b166
20 files changed
+2218
-33
No files matched your search
@@ -0,0 +1,95 @@
|
||||
// 网易云个人歌单(上游 music-lib/netease/user_playlist.go 移植)
|
||||
// 需登录 cookie MUSIC_U(sdk/auth/store.ts 的 getCredentials('wy').cookie / musicU):
|
||||
// 未登录抛出清晰错误,不返回空列表
|
||||
// 1) weapi/nuser/account/get → 当前用户 userId
|
||||
// 2) weapi/user/playlist → 用户歌单列表(分页 limit/offset)
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { weapi } from './utils/crypto.ts'
|
||||
import { formatPlayCount } from '../index.ts'
|
||||
import { getCredentials, getWyCookie } from '../auth/store.ts'
|
||||
import type { WyCredentials } from '../auth/store.ts'
|
||||
|
||||
const UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
|
||||
|
||||
const playlistLink = (id: string) => `https://music.163.com/#/playlist?id=${id}`
|
||||
|
||||
const readCookie = (): string => {
|
||||
const cred = getCredentials('wy') as WyCredentials | null
|
||||
const musicU = String(cred?.musicU ?? '').trim()
|
||||
if (musicU) return `MUSIC_U=${musicU}`
|
||||
return String(cred?.cookie ?? getWyCookie()).trim()
|
||||
}
|
||||
|
||||
const weapiPost = async(url: string, data: object, cookie: string) => {
|
||||
const { statusCode, body } = await httpFetch(url, {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'User-Agent': UA,
|
||||
Referer: 'https://music.163.com/',
|
||||
origin: 'https://music.163.com',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Cookie: cookie,
|
||||
},
|
||||
form: weapi(data),
|
||||
timeout: 15000,
|
||||
}).promise
|
||||
if (statusCode !== 200 || !body || typeof body !== 'object') throw new Error('wy 个人歌单接口请求失败')
|
||||
return body
|
||||
}
|
||||
|
||||
/**
|
||||
* 个人歌单列表(上游 GetUserPlaylists)
|
||||
* 未登录(无 cookie/MUSIC_U)时抛出清晰错误,不返回空列表
|
||||
*/
|
||||
export const getList = async(page = 1, limit = 30) => {
|
||||
const cookie = readCookie()
|
||||
if (!cookie) throw new Error('wy 个人歌单需要登录 cookie(MUSIC_U),请先登录网易云')
|
||||
|
||||
const safePage = Math.max(1, Number(page) || 1)
|
||||
let safeLimit = Number(limit) || 30
|
||||
if (safeLimit <= 0) safeLimit = 30
|
||||
if (safeLimit > 100) safeLimit = 100
|
||||
|
||||
const account = await weapiPost('https://music.163.com/weapi/nuser/account/get', { csrf_token: '' }, cookie)
|
||||
if (account.code !== 200 || !account?.profile?.userId) {
|
||||
throw new Error(`wy 登录态无效或已过期 (code ${account?.code ?? 'unknown'})`)
|
||||
}
|
||||
const uid = account.profile.userId
|
||||
const nickname = account.profile.nickname ?? ''
|
||||
|
||||
const body = await weapiPost('https://music.163.com/weapi/user/playlist', {
|
||||
uid,
|
||||
limit: safeLimit,
|
||||
offset: (safePage - 1) * safeLimit,
|
||||
includeVideo: true,
|
||||
csrf_token: '',
|
||||
}, cookie)
|
||||
if (body.code !== 200) throw new Error(`wy 个人歌单获取失败 (code ${body.code})`)
|
||||
|
||||
const list = (body.playlist ?? []).map(item => {
|
||||
const id = String(item?.id ?? '').trim()
|
||||
if (!id) return null
|
||||
const trackCount = Number(item?.trackCount ?? 0) || 0
|
||||
const playCount = Number(item?.playCount ?? 0) || 0
|
||||
const creator = String(item?.creator?.nickname ?? '').trim() || nickname
|
||||
return {
|
||||
id,
|
||||
name: item?.name ?? '',
|
||||
cover: item?.coverImgUrl ?? '',
|
||||
trackCount,
|
||||
playCount,
|
||||
play_count: formatPlayCount(playCount),
|
||||
creator,
|
||||
description: item?.description ?? '',
|
||||
subscribed: Boolean(item?.subscribed),
|
||||
link: playlistLink(id),
|
||||
source: 'wy' as const,
|
||||
}
|
||||
}).filter(Boolean)
|
||||
|
||||
return { list, page: safePage, limit: safeLimit, total: list.length, source: 'wy' as const }
|
||||
}
|
||||
|
||||
export const getUserPlaylists = getList
|
||||
|
||||
export default { getList, getUserPlaylists }
|
||||
Reference in new issue
Block a user