fix(me): 搜索对齐网页版——dramaapi/search 正剧置顶,d:<剧id> 条目自动取首个免费集
This commit is contained in:
1 parent
1fec828547
commit
cd5d9b1c91
3 files changed
+110
-51
No files matched your search
+40
-11
@@ -4,6 +4,8 @@
|
||||
// soundurl_128 = 128k m4a;soundurl_32 = 192k 变体(字段命名与实际码率错位,以实测为准)
|
||||
// 实测(2026-10-02)匿名可取免费声音,直链首字节 ftyp M4A 明文可播;
|
||||
// 付费声音需 /login/me 绑定含 token 的 cookie 后由服务端携带
|
||||
// songmid 约定:裸 id = 声音 id(声音搜索/剧集列表产出);
|
||||
// "d:<drama_id>" = 广播剧(剧搜索产出)——取流时自动选剧的首个免费集(预告/第一集)
|
||||
// 统一接口约定:apis('me').getMusicUrl(songInfo, type) → { promise, cancelHttp }
|
||||
// promise resolve → { type, url }(type 为实际命中档位,可能低于请求值)
|
||||
|
||||
@@ -16,22 +18,49 @@ const QUALITY_FIELDS: Record<string, string[]> = {
|
||||
}
|
||||
export const supportQualitys = ['64k', '128k']
|
||||
|
||||
const pickUrl = (info: any, type: string) => {
|
||||
const fields = QUALITY_FIELDS[type] ?? QUALITY_FIELDS['128k']
|
||||
let url = ''
|
||||
for (const field of fields) {
|
||||
const v = info?.[field]
|
||||
if (v && String(v).startsWith('http')) { url = String(v); break }
|
||||
}
|
||||
if (!url) throw new Error('无可用音频资源(付费内容需 POST /login/me 绑定 token)')
|
||||
const hit128 = url.includes('-128k') || type === '128k'
|
||||
return { type: hit128 ? '128k' : '64k', url }
|
||||
}
|
||||
|
||||
/** 剧条目 "d:<drama_id>" → 首个免费集的 sound_id(getdrama episodes.episode[]) */
|
||||
const resolveDramaFirstFree = async(dramaId: string): Promise<string> => {
|
||||
const { httpFetch } = await import('../request.ts')
|
||||
const { statusCode, body } = await httpFetch(`https://www.missevan.com/dramaapi/getdrama?drama_id=${dramaId}`, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
|
||||
Accept: 'application/json',
|
||||
Referer: `https://www.missevan.com/mdrama/${dramaId}`,
|
||||
},
|
||||
timeout: 15000,
|
||||
}).promise as any
|
||||
const episodes = (statusCode == 200 ? body?.info?.episodes?.episode : null) ?? []
|
||||
if (!episodes.length) throw new Error('剧集列表为空或已下线')
|
||||
const first = episodes.find((e: any) => Number(e.pay_type) === 0) ?? episodes[0]
|
||||
return String(first.sound_id)
|
||||
}
|
||||
|
||||
export const getMusicUrl = (songInfo, type) => {
|
||||
const requestObj = { cancelHttp() {}, promise: null as any }
|
||||
const soundId = String(songInfo.songmid ?? '')
|
||||
const songmid = String(songInfo.songmid ?? '')
|
||||
|
||||
requestObj.promise = (async() => {
|
||||
const info = await getSoundInfo(soundId)
|
||||
const fields = QUALITY_FIELDS[type] ?? QUALITY_FIELDS['128k']
|
||||
let url = ''
|
||||
for (const field of fields) {
|
||||
const v = info?.[field]
|
||||
if (v && String(v).startsWith('http')) { url = String(v); break }
|
||||
let soundId = songmid
|
||||
if (songmid.startsWith('d:')) {
|
||||
if (!/^\d+$/.test(songmid.slice(2))) throw new Error('剧 id 无效')
|
||||
soundId = await resolveDramaFirstFree(songmid.slice(2))
|
||||
} else if (!/^\d+$/.test(soundId)) {
|
||||
throw new Error('me id 需为声音 id 或 d:<剧id>(见 /search?source=me 与 /album/me)')
|
||||
}
|
||||
if (!url) throw new Error('无可用音频资源(付费内容需 POST /login/me 绑定 token)')
|
||||
// -128k 变体归 128k 桶,原文件按 64k 桶展示(原始码率不固定,多为 192k)
|
||||
const hit128 = url.includes('-128k') || type === '128k'
|
||||
return { type: hit128 ? '128k' : '64k', url }
|
||||
const info = await getSoundInfo(soundId)
|
||||
return pickUrl(info, type)
|
||||
})()
|
||||
return requestObj
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user