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
+133
@@ -3,6 +3,15 @@ import { decodeName } from '../index.ts'
|
||||
import { formatSinger, objStr2JSON } from './util.ts'
|
||||
import type { Quality } from '../types.ts'
|
||||
|
||||
const firstNonEmpty = (...values: any[]): string => {
|
||||
for (const value of values) {
|
||||
if (value == null) continue
|
||||
const str = String(value).trim()
|
||||
if (str) return str
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// let requestObj_list
|
||||
export default {
|
||||
limit_list: 36,
|
||||
@@ -102,6 +111,130 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 专辑详情(统一形状;服务端 1000 条/页,limit 更小时本地按 page/limit 开窗)
|
||||
* 上游对应:kuwo.go fetchAlbumDetail(legacy API 部分)
|
||||
*/
|
||||
async getAlbumDetail(id: string, page = 1, limit = 1000) {
|
||||
const albumID = String(id ?? '').trim()
|
||||
if (!albumID) throw new Error('酷我专辑 id 为空')
|
||||
const pageNo = Math.max(1, Number(page) || 1)
|
||||
const pageSize = Math.max(1, Number(limit) || this.limit_song)
|
||||
if (pageSize >= this.limit_song) return this.getAlbumListDetail(albumID, pageNo)
|
||||
|
||||
const start = (pageNo - 1) * pageSize
|
||||
let serverPage = Math.floor(start / this.limit_song) + 1
|
||||
let skip = start % this.limit_song
|
||||
const list: any[] = []
|
||||
let info: any = null
|
||||
let total = 0
|
||||
while (list.length < pageSize && serverPage <= 50) {
|
||||
const res = await this.getAlbumListDetail(albumID, serverPage)
|
||||
if (!res) break
|
||||
if (!info) info = res.info ?? null
|
||||
total = Number(res.total ?? 0) || total
|
||||
const rawList: any[] = res.list ?? []
|
||||
if (!rawList.length) break
|
||||
list.push(...rawList.slice(skip, skip + (pageSize - list.length)))
|
||||
skip = 0
|
||||
if (rawList.length < this.limit_song) break
|
||||
if (total > 0 && list.length >= total) break
|
||||
serverPage += 1
|
||||
}
|
||||
return {
|
||||
source: 'kw',
|
||||
info: info ?? { name: albumID, img: '', desc: '', author: '' },
|
||||
list,
|
||||
page: pageNo,
|
||||
limit: pageSize,
|
||||
total: total || list.length,
|
||||
}
|
||||
},
|
||||
/** 上游 GetAlbumSongs:聚合全部分页曲目(去重) */
|
||||
async getAlbumSongs(id: string) {
|
||||
const albumID = String(id ?? '').trim()
|
||||
if (!albumID) throw new Error('酷我专辑 id 为空')
|
||||
const songs: any[] = []
|
||||
const seen = new Set<string>()
|
||||
for (let page = 1; page <= 50; page++) {
|
||||
const res = await this.getAlbumListDetail(albumID, page)
|
||||
const rawList: any[] = res?.list ?? []
|
||||
for (const song of rawList) {
|
||||
const key = String(song?.songmid ?? '')
|
||||
if (key && seen.has(key)) continue
|
||||
if (key) seen.add(key)
|
||||
songs.push(song)
|
||||
}
|
||||
const total = Number(res?.total ?? 0) || 0
|
||||
if (rawList.length < this.limit_song) break
|
||||
if (total > 0 && songs.length >= total) break
|
||||
}
|
||||
return songs
|
||||
},
|
||||
/**
|
||||
* 专辑搜索(上游 SearchAlbum,legacy r.s ft=album 路由;返回歌单/专辑统一摘要形状)
|
||||
*/
|
||||
async searchAlbum(keyword: string, page = 1, limit = 10) {
|
||||
const word = String(keyword ?? '').trim()
|
||||
if (!word) return { source: 'kw' as const, list: [] as any[], page, limit, total: 0 }
|
||||
const pageNo = Math.max(1, Number(page) || 1)
|
||||
const pageSize = Math.max(1, Number(limit) || 10)
|
||||
const apiURL = 'http://search.kuwo.cn/r.s?' + [
|
||||
`all=${encodeURIComponent(word)}`,
|
||||
'ft=album',
|
||||
'itemset=web_2013',
|
||||
'client=kt',
|
||||
'pcmp4=1',
|
||||
'geo=c',
|
||||
'vipver=1',
|
||||
`pn=${pageNo - 1}`,
|
||||
`rn=${pageSize}`,
|
||||
'rformat=json',
|
||||
'encoding=utf8',
|
||||
].join('&')
|
||||
|
||||
const { statusCode, body } = await httpFetch(apiURL).promise
|
||||
if (statusCode !== 200) throw new Error('酷我专辑搜索失败')
|
||||
const data = typeof body === 'string' ? (() => { try { return objStr2JSON(body) } catch { return null } })() : body
|
||||
if (!data?.albumlist) throw new Error('酷我专辑搜索失败')
|
||||
|
||||
const list = (data.albumlist as any[]).map(item => {
|
||||
const albumID = firstNonEmpty(item.albumid, item.id)
|
||||
if (!albumID) return null
|
||||
return {
|
||||
id: albumID,
|
||||
name: decodeName(String(item.name ?? '').trim()),
|
||||
cover: firstNonEmpty(item.hts_img, item.img),
|
||||
trackCount: parseInt(String(item.musiccnt ?? ''), 10) || 0,
|
||||
playCount: parseInt(String(item.PLAYCNT ?? item.playcnt ?? ''), 10) || 0,
|
||||
creator: decodeName(firstNonEmpty(item.aartist, item.artist)),
|
||||
description: decodeName(String(item.info ?? '').trim()),
|
||||
link: `http://www.kuwo.cn/album_detail/${albumID}`,
|
||||
source: 'kw' as const,
|
||||
type: 'album',
|
||||
company: decodeName(String(item.company ?? '').trim()),
|
||||
publishTime: String(item.pub ?? '').trim(),
|
||||
}
|
||||
}).filter(Boolean)
|
||||
|
||||
return {
|
||||
source: 'kw' as const,
|
||||
list,
|
||||
page: pageNo,
|
||||
limit: pageSize,
|
||||
total: parseInt(String(data.TOTAL ?? data.total ?? ''), 10) || list.length,
|
||||
}
|
||||
},
|
||||
/** 上游 ParseAlbum:album_detail/<id>、album/<id>、albumid=<id> */
|
||||
async parseAlbumLink(link: string) {
|
||||
const raw = String(link ?? '')
|
||||
const patterns = [/album_detail\/(\d+)/, /album\/(\d+)/, /albumid=(\d+)/]
|
||||
for (const pattern of patterns) {
|
||||
const match = raw.match(pattern)
|
||||
if (match) return this.getAlbumDetail(match[1]!, 1)
|
||||
}
|
||||
throw new Error('无效的酷我专辑链接')
|
||||
},
|
||||
// getAlbumListDetail(id, page, retryNum = 0) {
|
||||
// if (retryNum > 2) return Promise.reject(new Error('try max num'))
|
||||
// return tokenRequest(`http://www.kuwo.cn/api/www/album/albumInfo?albumId=${id}&pn=${page}&rn=${this.limit_song}&httpsStatus=1`).then((resp) => {
|
||||
|
||||
@@ -9,6 +9,8 @@ import songList from './songList.ts'
|
||||
import hotSearch from './hotSearch.ts'
|
||||
import comment from './comment.ts'
|
||||
import album from './album.ts'
|
||||
import playlist from './playlist.ts'
|
||||
import parse from './parse.ts'
|
||||
import type { HttpRequestObject } from '../types.ts'
|
||||
|
||||
const kw = {
|
||||
@@ -17,6 +19,8 @@ const kw = {
|
||||
_musicPicRequestObj: null as HttpRequestObject | null,
|
||||
_musicPicPromiseCancelFn: null,
|
||||
album,
|
||||
playlist,
|
||||
parse,
|
||||
// context: null,
|
||||
|
||||
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
// 酷我链接解析
|
||||
// 歌曲:https://www.kuwo.cn/play_detail/<rid>(含 ?/# 尾串)、m.kuwo.cn 的 musicId=<rid>、裸 rid
|
||||
// 上游对应:music-lib/kuwo/song.go Parse / kuwo.go fetchFullSongInfo(元数据 m.kuwo.cn + 尽力附直链)
|
||||
// 专辑 / 歌单分别复用 album.parseAlbumLink、playlist.parsePlaylistLink。
|
||||
import { httpFetch } from '../request.ts'
|
||||
import { formatPlayTime } from '../index.ts'
|
||||
import kwApi from '../apis/kw.ts'
|
||||
import album from './album.ts'
|
||||
import playlist from './playlist.ts'
|
||||
import { formatSinger } from './util.ts'
|
||||
|
||||
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
|
||||
|
||||
const SONG_PATTERNS = [
|
||||
/play_detail\/(\d+)/, // http://www.kuwo.cn/play_detail/228908
|
||||
/h5app\/song\/(\d+)/, // https://m.kuwo.cn/h5app/song/228908
|
||||
/musicId=(\d+)/, // http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=228908
|
||||
/^\d{4,}$/, // 裸 rid
|
||||
]
|
||||
|
||||
const extractSongId = (link: any): string => {
|
||||
const raw = String(link ?? '').trim()
|
||||
for (const pattern of SONG_PATTERNS) {
|
||||
const match = raw.match(pattern)
|
||||
if (match) return match[1] ?? raw
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
interface KuwoSongMeta {
|
||||
songName?: string
|
||||
artist?: string
|
||||
pic?: string
|
||||
album?: string
|
||||
albumId?: string | number | null
|
||||
duration?: string | number
|
||||
}
|
||||
|
||||
/** 拉取歌曲元数据(上游 fetchFullSongInfo 的 songinfoandlrc 部分;接口偶发 status 301,重试 3 次) */
|
||||
const fetchSongMeta = async(rid: string, retryNum = 0): Promise<KuwoSongMeta> => {
|
||||
const { body } = await httpFetch(`http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=${rid}&httpsStatus=1`, {
|
||||
method: 'get',
|
||||
headers: { 'User-Agent': UA, Cookie: 'wd=1;vip=0' },
|
||||
timeout: 15000,
|
||||
}).promise
|
||||
|
||||
let data: any = body
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
data = JSON.parse(data)
|
||||
} catch {
|
||||
data = null
|
||||
}
|
||||
}
|
||||
const songinfo = data?.data?.songinfo
|
||||
if (!songinfo) {
|
||||
// 风控/抖动时上游返回 status 301「音乐查询失败」,退避重试(最多 5 次,约 9s)
|
||||
if (retryNum < 5) {
|
||||
await new Promise(resolve => setTimeout(resolve, 600 * (retryNum + 1)))
|
||||
return fetchSongMeta(rid, ++retryNum)
|
||||
}
|
||||
throw new Error(data?.msg || '酷我歌曲不存在或已下架')
|
||||
}
|
||||
return songinfo
|
||||
}
|
||||
|
||||
/** 上游 Parse:歌曲链接 → Song(尽力附带直链 url/type,失败不影响返回) */
|
||||
export const parseSong = async(link: string) => {
|
||||
const rid = extractSongId(link)
|
||||
if (!rid) throw new Error('无效的酷我歌曲链接')
|
||||
|
||||
const meta = await fetchSongMeta(rid)
|
||||
const duration = parseInt(String(meta.duration ?? ''), 10) || 0
|
||||
const song: any = {
|
||||
songmid: rid,
|
||||
name: meta.songName || `Kuwo_Song_${rid}`,
|
||||
singer: formatSinger(String(meta.artist ?? '').trim()),
|
||||
albumName: String(meta.album ?? '').trim(),
|
||||
albumId: meta.albumId != null ? String(meta.albumId) : '',
|
||||
interval: duration > 0 ? formatPlayTime(duration) : '--/--',
|
||||
img: meta.pic || null,
|
||||
lrc: null,
|
||||
otherSource: null,
|
||||
types: [],
|
||||
_types: {},
|
||||
typeUrl: {},
|
||||
source: 'kw' as const,
|
||||
extra: { rid, link: `http://www.kuwo.cn/play_detail/${rid}` },
|
||||
}
|
||||
|
||||
try {
|
||||
const hit = await kwApi.getMusicUrl({ songmid: rid } as any, '128k').promise
|
||||
if (hit?.url) {
|
||||
song.url = hit.url
|
||||
song.type = hit.type
|
||||
}
|
||||
} catch {
|
||||
// 与上游 fetchAudioURL 降级一致:直链获取失败(版权/风控)仍返回元数据
|
||||
}
|
||||
|
||||
return song
|
||||
}
|
||||
|
||||
export const parseAlbum = async(link: string) => album.parseAlbumLink(link)
|
||||
export const parsePlaylist = async(link: string) => playlist.parsePlaylistLink(link)
|
||||
|
||||
export default { parseSong, parseAlbum, parsePlaylist }
|
||||
@@ -0,0 +1,217 @@
|
||||
// 酷我歌单 —— 搜索 / 详情 / 分类 / 分类歌单 / 推荐 / 链接解析
|
||||
// 上游对应:music-lib/kuwo/playlist.go
|
||||
// 复用既有内部实现(不重复造轮子):
|
||||
// - 搜索:songList.search
|
||||
// - 详情:songList.getListDetail(nplserver,服务端固定 1000 条/页,本地按 page/limit 开窗)
|
||||
// - 分类:songList.getTag / getHotTag(同上游 getTagList / getRcmTagList)
|
||||
// - 分类歌单 / 推荐:songList.getList(wapi getTagPlayList / getRcmPlayList)
|
||||
// 上游 GetRecommendedPlaylists 用 getRcmPlayList(order=hot) 且 limit=30;既有 getList 的
|
||||
// limit_list=36(模块常量),故推荐条数以 36 为准,显式 limit 更小时本地截断。
|
||||
import songList from './songList.ts'
|
||||
|
||||
// songList.getListDetailUrl 固定 rn=limit_song=1000
|
||||
const SERVER_PAGE_SIZE = 1000
|
||||
const MAX_SERVER_PAGES = 60
|
||||
|
||||
const firstNonEmpty = (...values: any[]): string => {
|
||||
for (const value of values) {
|
||||
if (value == null) continue
|
||||
const str = String(value).trim()
|
||||
if (str) return str
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 歌单链接 / digest 前缀 id → 平台内部 id(digest 前缀形态原样保留,交给 songList 分流) */
|
||||
const normalizePlaylistId = (id: any): string => {
|
||||
const raw = String(id ?? '').trim()
|
||||
if (!raw) return ''
|
||||
const patterns = [
|
||||
/playlist_detail\/(\d+)/, // http://www.kuwo.cn/playlist_detail/2886046289
|
||||
/\/playlist\/(\d+)/, // https://m.kuwo.cn/h5app/playlist/2736267853?t=qqfriend
|
||||
/playlistId=(\d+)/,
|
||||
]
|
||||
for (const pattern of patterns) {
|
||||
const match = raw.match(pattern)
|
||||
if (match) return match[1]!
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
/** 歌单摘要(对齐契约形状:id/name/cover/trackCount/playCount/creator/description/link/source) */
|
||||
const toSummary = (item: any) => {
|
||||
const id = String(item?.id ?? '').trim()
|
||||
const plainId = id.replace(/^digest-\w+__/, '')
|
||||
const playCount = Number(item?.play_count)
|
||||
return {
|
||||
...item,
|
||||
id,
|
||||
name: String(item?.name ?? '').trim(),
|
||||
cover: firstNonEmpty(item?.img, item?.cover),
|
||||
trackCount: Number(item?.total ?? item?.trackCount ?? 0) || 0,
|
||||
playCount: Number.isFinite(playCount) ? playCount : (item?.play_count ?? 0),
|
||||
creator: firstNonEmpty(item?.author, item?.creator, item?.uname),
|
||||
description: firstNonEmpty(item?.desc, item?.description),
|
||||
link: plainId ? `http://www.kuwo.cn/playlist_detail/${plainId}` : '',
|
||||
source: 'kw' as const,
|
||||
}
|
||||
}
|
||||
|
||||
/** 歌单搜索(上游 SearchPlaylist;复用 songList.search 的 legacy r.s 接口) */
|
||||
export const searchPlaylist = async(keyword: string, page = 1, limit = 20) => {
|
||||
const word = String(keyword ?? '').trim()
|
||||
if (!word) return { source: 'kw' as const, list: [] as any[], page, limit, total: 0 }
|
||||
const res = await songList.search(word, page, limit)
|
||||
const list = (res?.list ?? []).map(toSummary).filter((item: any) => item.id)
|
||||
return {
|
||||
source: 'kw' as const,
|
||||
list,
|
||||
page,
|
||||
limit,
|
||||
total: Number(res?.total ?? 0) || list.length,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 歌单详情(含元数据与曲目)
|
||||
* 服务端 1000 条/页,按调用方 page/limit 开窗,只抓所需的 1~2 个服务端页。
|
||||
*/
|
||||
export const getPlaylistDetail = async(id: string, page = 1, limit = 50) => {
|
||||
const playlistId = normalizePlaylistId(id)
|
||||
if (!playlistId) throw new Error('酷我歌单 id 为空')
|
||||
const pageNo = Math.max(1, Number(page) || 1)
|
||||
const pageSize = Math.max(1, Number(limit) || 50)
|
||||
const start = (pageNo - 1) * pageSize
|
||||
|
||||
let serverPage = Math.floor(start / SERVER_PAGE_SIZE) + 1
|
||||
let skip = start % SERVER_PAGE_SIZE
|
||||
const list: any[] = []
|
||||
let info: any = null
|
||||
let total = 0
|
||||
|
||||
while (list.length < pageSize && serverPage <= MAX_SERVER_PAGES) {
|
||||
const res = await songList.getListDetail(playlistId, serverPage)
|
||||
if (!res) break
|
||||
if (!info) info = res.info ?? null
|
||||
total = Number(res.total ?? 0) || total
|
||||
const rawList: any[] = res.list ?? []
|
||||
if (!rawList.length) break
|
||||
list.push(...rawList.slice(skip, skip + (pageSize - list.length)))
|
||||
skip = 0
|
||||
if (rawList.length < SERVER_PAGE_SIZE) break
|
||||
if (total > 0 && serverPage * SERVER_PAGE_SIZE >= total) break
|
||||
serverPage += 1
|
||||
}
|
||||
|
||||
return {
|
||||
source: 'kw' as const,
|
||||
info: {
|
||||
name: firstNonEmpty(info?.name, playlistId),
|
||||
img: firstNonEmpty(info?.img),
|
||||
desc: firstNonEmpty(info?.desc),
|
||||
author: firstNonEmpty(info?.author),
|
||||
play_count: info?.play_count ?? '',
|
||||
},
|
||||
list,
|
||||
page: pageNo,
|
||||
limit: pageSize,
|
||||
total: total || list.length,
|
||||
}
|
||||
}
|
||||
|
||||
/** 上游 GetPlaylistSongs:聚合全部页曲目(去重) */
|
||||
export const getPlaylistSongs = async(id: string) => {
|
||||
const playlistId = normalizePlaylistId(id)
|
||||
if (!playlistId) throw new Error('酷我歌单 id 为空')
|
||||
const songListResult: any[] = []
|
||||
const seen = new Set<string>()
|
||||
for (let serverPage = 1; serverPage <= MAX_SERVER_PAGES; serverPage++) {
|
||||
const res = await songList.getListDetail(playlistId, serverPage)
|
||||
const rawList: any[] = res?.list ?? []
|
||||
for (const song of rawList) {
|
||||
const key = String(song?.songmid ?? '')
|
||||
if (key && seen.has(key)) continue
|
||||
if (key) seen.add(key)
|
||||
songListResult.push(song)
|
||||
}
|
||||
const total = Number(res?.total ?? 0) || 0
|
||||
if (rawList.length < SERVER_PAGE_SIZE) break
|
||||
if (total > 0 && songListResult.length >= total) break
|
||||
}
|
||||
return songListResult
|
||||
}
|
||||
|
||||
/** 歌单分类(上游 GetPlaylistCategories;复用 getTag/getHotTag,tagId 形如 `<id>-<digest>`) */
|
||||
export const getCategories = async() => {
|
||||
const { tags, hotTag } = await songList.getTags()
|
||||
const hotIds = new Set((hotTag ?? []).map((item: any) => String(item?.id ?? '')))
|
||||
const list: Array<{ id: string, name: string, group: string, hot?: boolean }> = [
|
||||
{ id: '', name: '全部', group: '全部' },
|
||||
]
|
||||
for (const group of tags ?? []) {
|
||||
for (const item of group?.list ?? []) {
|
||||
const id = String(item?.id ?? '').trim()
|
||||
const name = String(item?.name ?? '').trim()
|
||||
if (!id || !name) continue
|
||||
list.push({ id, name, group: String(group?.name ?? '').trim() || '其他', hot: hotIds.has(id) })
|
||||
}
|
||||
}
|
||||
return { source: 'kw' as const, list }
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类歌单(上游 GetCategoryPlaylists)
|
||||
* categoryId 为空 → getRcmPlayList(order=hot);否则按 `<id>-<digest>` 走 getTagPlayList。
|
||||
*/
|
||||
export const getCategoryPlaylists = async(categoryId: string, page = 1, limit = 20) => {
|
||||
const tagId = String(categoryId ?? '').trim()
|
||||
const pageNo = Math.max(1, Number(page) || 1)
|
||||
const res = await songList.getList('hot', tagId || undefined, pageNo)
|
||||
let list = (res?.list ?? []).map(toSummary).filter((item: any) => item.id)
|
||||
const requested = Number(limit) || 0
|
||||
const serverLimit = Number(res?.limit ?? 0) || 0
|
||||
if (requested > 0 && list.length > requested) list = list.slice(0, requested)
|
||||
return {
|
||||
source: 'kw' as const,
|
||||
list,
|
||||
page: pageNo,
|
||||
limit: (requested > 0 && serverLimit > 0 ? Math.min(requested, serverLimit) : serverLimit) || list.length,
|
||||
total: Number(res?.total ?? 0) || list.length,
|
||||
}
|
||||
}
|
||||
|
||||
/** 推荐歌单(上游 GetRecommendedPlaylists;复用 getList(order=hot)) */
|
||||
export const getRecommended = async(page = 1, limit = 30) => {
|
||||
const pageNo = Math.max(1, Number(page) || 1)
|
||||
const res = await songList.getList('hot', undefined, pageNo)
|
||||
let list = (res?.list ?? []).map(toSummary).filter((item: any) => item.id)
|
||||
const requested = Number(limit) || 0
|
||||
const serverLimit = Number(res?.limit ?? 0) || 0
|
||||
if (requested > 0 && list.length > requested) list = list.slice(0, requested)
|
||||
return {
|
||||
source: 'kw' as const,
|
||||
list,
|
||||
page: pageNo,
|
||||
limit: (requested > 0 && serverLimit > 0 ? Math.min(requested, serverLimit) : serverLimit) || list.length,
|
||||
total: Number(res?.total ?? 0) || list.length,
|
||||
}
|
||||
}
|
||||
|
||||
/** 上游 ParsePlaylist:playlist_detail/<id>、m.kuwo.cn/h5app/playlist/<id>(含 ?/# 尾串) */
|
||||
export const parsePlaylistLink = async(link: string) => {
|
||||
const match = String(link ?? '').match(/playlist_detail\/(\d+)/) ||
|
||||
String(link ?? '').match(/\/playlist\/(\d+)/) ||
|
||||
String(link ?? '').match(/playlistId=(\d+)/)
|
||||
if (!match) throw new Error('无效的酷我歌单链接')
|
||||
return getPlaylistDetail(match[1]!, 1)
|
||||
}
|
||||
|
||||
export default {
|
||||
searchPlaylist,
|
||||
getPlaylistDetail,
|
||||
getPlaylistSongs,
|
||||
getCategories,
|
||||
getCategoryPlaylists,
|
||||
getRecommended,
|
||||
parsePlaylistLink,
|
||||
}
|
||||
Reference in new issue
Block a user