refactor(sdk): 审计 P2 卫生项清理——请求层收敛与死代码清除
- 删除旧 sdk/request.ts,全部 import 收敛到 sdk/common/request.ts(85 文件) - 注释死代码 630+ 行、裸 console.log 吞错 6 处、未使用符号 26 处清零 - bili 状态校验去重;utils DNS 预解析失败日志改 warn 带上下文 - CODE-AUDIT.md:第四节 P2 清理记录 + 第八节线索逐条复核结论(kw/kg/tx/wy parse 骨架相似实质各异,不抽公共层)
This commit is contained in:
1 parent
ecca1b8458
commit
8c02fb5c15
85 files changed
+112
-839
No files matched your search
+3
-1
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
**检查时间**:2026-10-03
|
**检查时间**:2026-10-03
|
||||||
**修复状态**(复查后更新):P0-1 ~ P0-3、P1-4 ~ P1-16 **全部已修复**(含测试隔离与 isolated-vm 原生构建);
|
**修复状态**(复查后更新):P0-1 ~ P0-3、P1-4 ~ P1-16 **全部已修复**(含测试隔离与 isolated-vm 原生构建);
|
||||||
第五节「文档与代码不一致」已按 `/sources` 与路由实现逐条校准;第八节列出的未复核线索与第四节 P2 项**未动**,留待后续。
|
第四节 P2 卫生项已于 2026-10-03 清理完毕(注释死代码 630+ 行、裸 `console.log` 吞错 6 处、未使用符号 26 处清零、bili 状态校验去重);
|
||||||
|
第八节线索逐条复核:qian 导出名、migu 异常消息、Quality `preview`、bili 重试重复均已过时(早前重写时已解决),kw/kg/tx/wy parse/playlist 仅有骨架相似、实质各异,评估后不抽公共层。
|
||||||
|
第五节「文档与代码不一致」已按 `/sources` 与路由实现逐条校准。
|
||||||
各条的修复要点见下文对应条目。
|
各条的修复要点见下文对应条目。
|
||||||
**检查范围**:`src/`(15 文件 / 3,103 行)、`sdk/`(173 文件 / 21,919 行)、`test/`(2 文件 / 1,081 行),共约 26,100 行 TypeScript
|
**检查范围**:`src/`(15 文件 / 3,103 行)、`sdk/`(173 文件 / 21,919 行)、`test/`(2 文件 / 1,081 行),共约 26,100 行 TypeScript
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
// 上游 New(cookie) 的 cookie 实际参与请求(搜索/getSongUrl/newget/歌单页),
|
// 上游 New(cookie) 的 cookie 实际参与请求(搜索/getSongUrl/newget/歌单页),
|
||||||
// 这里按约定从 sdk/auth/store.ts 的 getCredentials('5s') 读取(无则匿名)。
|
// 这里按约定从 sdk/auth/store.ts 的 getCredentials('5s') 读取(无则匿名)。
|
||||||
|
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { getCredentials } from '../auth/store.ts'
|
import { getCredentials } from '../auth/store.ts'
|
||||||
|
|
||||||
export const UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
|
export const UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
// 统一接口约定:apis('jx').getMusicUrl(songInfo, type) → { promise, cancelHttp }
|
// 统一接口约定:apis('jx').getMusicUrl(songInfo, type) → { promise, cancelHttp }
|
||||||
// promise resolve → { type, url }(type 为实际命中档位,可能低于请求值)
|
// promise resolve → { type, url }(type 为实际命中档位,可能低于请求值)
|
||||||
|
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { jooxHeaders, normalizeJooxID } from '../joox/util.ts'
|
import { jooxHeaders, normalizeJooxID } from '../joox/util.ts'
|
||||||
import { buildTrackUrl } from '../joox/musicInfo.ts'
|
import { buildTrackUrl } from '../joox/musicInfo.ts'
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
// 实测(2026-10-02):免费章节匿名也返回 status=2 "收费章节未购买"——必须携带 /login/lr 登录会话;
|
// 实测(2026-10-02):免费章节匿名也返回 status=2 "收费章节未购买"——必须携带 /login/lr 登录会话;
|
||||||
// 成功响应 list[0].path 为音频直链。
|
// 成功响应 list[0].path 为音频直链。
|
||||||
// songmid 为复合 id "<bookId>:<section>"(/album/lr 章节列表产出);纯 bookId 视为第一章
|
// songmid 为复合 id "<bookId>:<section>"(/album/lr 章节列表产出);纯 bookId 视为第一章
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { getLrCookie } from '../lr/auth.ts'
|
import { getLrCookie } from '../lr/auth.ts'
|
||||||
|
|
||||||
const UA_MI = 'Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; MI2 Build/JRO03L) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 XiaoMi/MiuiBrowser/1.0'
|
const UA_MI = 'Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; MI2 Build/JRO03L) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 XiaoMi/MiuiBrowser/1.0'
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ const pickUrl = (info: any, type: string) => {
|
|||||||
|
|
||||||
/** 剧条目 "d:<drama_id>" → 首个免费集的 sound_id(getdrama episodes.episode[]) */
|
/** 剧条目 "d:<drama_id>" → 首个免费集的 sound_id(getdrama episodes.episode[]) */
|
||||||
const resolveDramaFirstFree = async(dramaId: string): Promise<string> => {
|
const resolveDramaFirstFree = async(dramaId: string): Promise<string> => {
|
||||||
const { httpFetch } = await import('../request.ts')
|
const { httpFetch } = await import('../common/request.ts')
|
||||||
const { statusCode, body } = await httpFetch(`https://www.missevan.com/dramaapi/getdrama?drama_id=${dramaId}`, {
|
const { statusCode, body } = await httpFetch(`https://www.missevan.com/dramaapi/getdrama?drama_id=${dramaId}`, {
|
||||||
headers: {
|
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',
|
'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',
|
||||||
|
|||||||
+2
-11
@@ -2,9 +2,9 @@
|
|||||||
// 命中可访问的最高档位;匿名仅 PQ(128k) 可用,HQ/SQ/ZQ 各档需登录 Cookie。
|
// 命中可访问的最高档位;匿名仅 PQ(128k) 可用,HQ/SQ/ZQ 各档需登录 Cookie。
|
||||||
// 上游对应:music-lib/migu/download.go(GetDownloadInfo / buildMiguDownloadCandidates /
|
// 上游对应:music-lib/migu/download.go(GetDownloadInfo / buildMiguDownloadCandidates /
|
||||||
// selectHighestValidDownload / rewriteMiguToneURL / embeddedDownloadInfoFromResponse)
|
// selectHighestValidDownload / rewriteMiguToneURL / embeddedDownloadInfoFromResponse)
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import {
|
import {
|
||||||
AndroidChannel, AndroidUA, AndroidVersion, buildUrl, decryptZ3D, firstNonEmpty,
|
AndroidChannel, AndroidUA, AndroidVersion, decryptZ3D, firstNonEmpty,
|
||||||
getMiguCookie, isMiguAudioMagic, miguFormatExt, TONE_QUALITY, type MiguSongItem,
|
getMiguCookie, isMiguAudioMagic, miguFormatExt, TONE_QUALITY, type MiguSongItem,
|
||||||
} from '../migu/util.ts'
|
} from '../migu/util.ts'
|
||||||
|
|
||||||
@@ -112,15 +112,6 @@ const normalizeDownloadURL = (rawURL?: string | null): string => {
|
|||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEncryptedDirectURL = (rawURL: string): boolean => {
|
|
||||||
try {
|
|
||||||
const decodedPath = decodeURIComponent(new URL(rawURL).pathname)
|
|
||||||
return decodedPath.includes('wav_3d/') && !decodedPath.includes('wav_3d_60s/')
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DownloadInfo {
|
interface DownloadInfo {
|
||||||
url: string
|
url: string
|
||||||
format: string
|
format: string
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
// - ampGet/ampHeaders:amp-api 请求(httpFetch 不自动跟 302,首页跳转用 fetchFollow 手动跟)
|
// - ampGet/ampHeaders:amp-api 请求(httpFetch 不自动跟 302,首页跳转用 fetchFollow 手动跟)
|
||||||
// - 模型转换:appleSongFromCatalog / appleAlbumToPlaylist / applePlaylistToPlaylist(字段映射同上游)
|
// - 模型转换:appleSongFromCatalog / appleAlbumToPlaylist / applePlaylistToPlaylist(字段映射同上游)
|
||||||
// - ID 提取:extractAppleID / extractAppleIDFromPath / isAppleRawID(apple_test.go 的断言全部逐条保留)
|
// - ID 提取:extractAppleID / extractAppleIDFromPath / isAppleRawID(apple_test.go 的断言全部逐条保留)
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime } from '../common/utils.ts'
|
import { formatPlayTime } from '../common/utils.ts'
|
||||||
import { getCredentials } from '../auth/store.ts'
|
import { getCredentials } from '../auth/store.ts'
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { generateSimulate } from './kgSimulate.ts'
|
|||||||
import type { KgCredentials } from './store.ts'
|
import type { KgCredentials } from './store.ts'
|
||||||
|
|
||||||
const md5 = s => crypto.createHash('md5').update(s).digest('hex')
|
const md5 = s => crypto.createHash('md5').update(s).digest('hex')
|
||||||
const SALT_KEY = '57ae12eb6890223e355ccfcb74edf70d'
|
|
||||||
const SALT_SIGN = 'OIlwieks28dk2k092lksi2UIkp'
|
const SALT_SIGN = 'OIlwieks28dk2k092lksi2UIkp'
|
||||||
const APPID = 1005
|
const APPID = 1005
|
||||||
const CLIENTVER = 11430
|
const CLIENTVER = 11430
|
||||||
|
|||||||
+14
-14
@@ -157,24 +157,28 @@ export const makeSong = ({ bvid, cid, name, artist, album, duration, cover, page
|
|||||||
|
|
||||||
// ---- 视频/分P/合集接口(上游 bilibili.go 各 fetch* 的对应实现) ----
|
// ---- 视频/分P/合集接口(上游 bilibili.go 各 fetch* 的对应实现) ----
|
||||||
|
|
||||||
|
/** B 站 API 统一校验:HTTP 200 且业务码 code=0,否则抛带上下文的错误 */
|
||||||
|
const assertApiOk = (what: string, statusCode: number, body: any): void => {
|
||||||
|
if (statusCode !== 200 || !body || body.code !== 0) {
|
||||||
|
throw new Error(`B站${what}获取失败(${body?.code ?? `HTTP ${statusCode}`}${body?.message ? `: ${body.message}` : ''})`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 视频详情(x/web-interface/view);bvid 或 aid 均可,返回 data 节点 */
|
/** 视频详情(x/web-interface/view);bvid 或 aid 均可,返回 data 节点 */
|
||||||
export const fetchView = async(id: string): Promise<any> => {
|
export const fetchView = async(id: string): Promise<any> => {
|
||||||
const raw = String(id || '').trim()
|
const raw = String(id || '').trim()
|
||||||
if (!raw) throw new Error('B站视频 id 为空')
|
if (!raw) throw new Error('B站视频 id 为空')
|
||||||
const query = /^BV/i.test(raw) ? `bvid=${encodeURIComponent(raw)}` : `aid=${encodeURIComponent(raw.replace(/^av/i, ''))}`
|
const query = /^BV/i.test(raw) ? `bvid=${encodeURIComponent(raw)}` : `aid=${encodeURIComponent(raw.replace(/^av/i, ''))}`
|
||||||
const { statusCode, body } = await apiGet(`https://api.bilibili.com/x/web-interface/view?${query}`)
|
const { statusCode, body } = await apiGet(`https://api.bilibili.com/x/web-interface/view?${query}`)
|
||||||
if (statusCode !== 200 || !body || body.code !== 0 || !body.data) {
|
assertApiOk('视频信息', statusCode, body)
|
||||||
throw new Error(`B站视频信息获取失败(${body?.code ?? `HTTP ${statusCode}`}${body?.message ? `: ${body.message}` : ''})`)
|
if (!body.data) throw new Error('B站视频信息获取失败(data 为空)')
|
||||||
}
|
|
||||||
return body.data
|
return body.data
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 分P列表(x/player/pagelist),返回 data 数组 */
|
/** 分P列表(x/player/pagelist),返回 data 数组 */
|
||||||
export const fetchPageList = async(bvid: string): Promise<any[]> => {
|
export const fetchPageList = async(bvid: string): Promise<any[]> => {
|
||||||
const { statusCode, body } = await apiGet(`https://api.bilibili.com/x/player/pagelist?bvid=${encodeURIComponent(bvid)}`)
|
const { statusCode, body } = await apiGet(`https://api.bilibili.com/x/player/pagelist?bvid=${encodeURIComponent(bvid)}`)
|
||||||
if (statusCode !== 200 || !body || body.code !== 0) {
|
assertApiOk('分P列表', statusCode, body)
|
||||||
throw new Error(`B站分P列表获取失败(${body?.code ?? `HTTP ${statusCode}`}${body?.message ? `: ${body.message}` : ''})`)
|
|
||||||
}
|
|
||||||
return body.data ?? []
|
return body.data ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,10 +192,8 @@ export const fetchSeasonArchiveIndex = async(mid: string | number, seasonId: str
|
|||||||
let pageNum = 1
|
let pageNum = 1
|
||||||
const pageSize = 30
|
const pageSize = 30
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const { body } = await apiGet(`https://api.bilibili.com/x/space/ugc/season?mid=${mid}&season_id=${seasonId}&page_num=${pageNum}&page_size=${pageSize}`)
|
const { statusCode, body } = await apiGet(`https://api.bilibili.com/x/space/ugc/season?mid=${mid}&season_id=${seasonId}&page_num=${pageNum}&page_size=${pageSize}`)
|
||||||
if (!body || body.code !== 0) {
|
assertApiOk('合集信息', statusCode, body)
|
||||||
throw new Error(`B站合集信息获取失败(${body?.code ?? '非 JSON 响应'}${body?.message ? `: ${body.message}` : ''})`)
|
|
||||||
}
|
|
||||||
if (!seasonTitle) seasonTitle = body.data?.season?.title || ''
|
if (!seasonTitle) seasonTitle = body.data?.season?.title || ''
|
||||||
if (!seasonCover) seasonCover = body.data?.season?.cover || ''
|
if (!seasonCover) seasonCover = body.data?.season?.cover || ''
|
||||||
const archives = body.data?.archives ?? []
|
const archives = body.data?.archives ?? []
|
||||||
@@ -276,10 +278,8 @@ export const fetchSeasonSongs = async(mid: string | number, seasonId: string | n
|
|||||||
let pageNum = 1
|
let pageNum = 1
|
||||||
const pageSize = 30
|
const pageSize = 30
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const { body } = await apiGet(`https://api.bilibili.com/x/space/ugc/season?mid=${mid}&season_id=${seasonId}&page_num=${pageNum}&page_size=${pageSize}`)
|
const { statusCode, body } = await apiGet(`https://api.bilibili.com/x/space/ugc/season?mid=${mid}&season_id=${seasonId}&page_num=${pageNum}&page_size=${pageSize}`)
|
||||||
if (!body || body.code !== 0) {
|
assertApiOk('合集列表', statusCode, body)
|
||||||
throw new Error(`B站合集列表获取失败(${body?.code ?? '非 JSON 响应'}${body?.message ? `: ${body.message}` : ''})`)
|
|
||||||
}
|
|
||||||
if (!seasonTitle) seasonTitle = body.data?.season?.title || ''
|
if (!seasonTitle) seasonTitle = body.data?.season?.title || ''
|
||||||
if (!seasonCover) seasonCover = body.data?.season?.cover || ''
|
if (!seasonCover) seasonCover = body.data?.season?.cover || ''
|
||||||
const archives = body.data?.archives ?? []
|
const archives = body.data?.archives ?? []
|
||||||
|
|||||||
+1
-1
@@ -97,5 +97,5 @@ export const decodeName = (str = '') => {
|
|||||||
.replace(/'/g, "'")
|
.replace(/'/g, "'")
|
||||||
.replace(/'/g, "'")
|
.replace(/'/g, "'")
|
||||||
.replace(/ /g, ' ')
|
.replace(/ /g, ' ')
|
||||||
.replace(/&#(\d+);/g, (match, num) => String.fromCharCode(parseInt(num)))
|
.replace(/&#(\d+);/g, (_, num) => String.fromCharCode(parseInt(num)))
|
||||||
}
|
}
|
||||||
@@ -176,10 +176,6 @@ export const findMusic = async (
|
|||||||
|
|
||||||
const isEqualsInterval = (intv: number) =>
|
const isEqualsInterval = (intv: number) =>
|
||||||
Math.abs((fInterval || intv) - (intv || fInterval)) < 5
|
Math.abs((fInterval || intv) - (intv || fInterval)) < 5
|
||||||
const isIncludesName = (n: string) => fMusicName.includes(n) || n.includes(fMusicName)
|
|
||||||
const isIncludesSinger = (sng: string) =>
|
|
||||||
fSinger ? fSinger.includes(sng) || sng.includes(fSinger) : true
|
|
||||||
const isEqualsAlbum = (alb: string) => (fAlbumName ? fAlbumName === alb : true)
|
|
||||||
|
|
||||||
let candidates: MatchCandidate[] = []
|
let candidates: MatchCandidate[] = []
|
||||||
for (const source of lists) {
|
for (const source of lists) {
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
// 兜底:www.joox.com/<region>/album/<id> SSR __NEXT_DATA__.props.pageProps.albumData(上游做法,需 X-Forwarded-For)
|
// 兜底:www.joox.com/<region>/album/<id> SSR __NEXT_DATA__.props.pageProps.albumData(上游做法,需 X-Forwarded-For)
|
||||||
// 链接解析:ParseAlbum 的正则与裸 ID 规则同上游。
|
// 链接解析:ParseAlbum 的正则与裸 ID 规则同上游。
|
||||||
|
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import {
|
import {
|
||||||
jooxHeaders,
|
jooxHeaders,
|
||||||
jooxSign,
|
jooxSign,
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
// 解码:base64 → UTF-8 → HTML 实体(& 等)→ { lyric, tlyric: '' }
|
// 解码:base64 → UTF-8 → HTML 实体(& 等)→ { lyric, tlyric: '' }
|
||||||
// 统一接口约定:{ promise, cancelHttp };promise resolve → { lyric, tlyric }
|
// 统一接口约定:{ promise, cancelHttp };promise resolve → { lyric, tlyric }
|
||||||
|
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { jooxHeaders, decodeJooxLyric, normalizeJooxID, firstNonEmpty } from './util.ts'
|
import { jooxHeaders, decodeJooxLyric, normalizeJooxID, firstNonEmpty } from './util.ts'
|
||||||
import { buildTrackUrl } from './musicInfo.ts'
|
import { buildTrackUrl } from './musicInfo.ts'
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
// 返回体含 play_url_list / refrain_url / lrc_content(base64) / qrc_content(base64) /
|
// 返回体含 play_url_list / refrain_url / lrc_content(base64) / qrc_content(base64) /
|
||||||
// has_hq / has_hifi / vip_flag / node_is_preview 等。
|
// has_hq / has_hifi / vip_flag / node_is_preview 等。
|
||||||
|
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import {
|
import {
|
||||||
jooxHeaders,
|
jooxHeaders,
|
||||||
jooxSign,
|
jooxSign,
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
// 该接口无分页参数(上游也未分页),page 参数仅为统一契约保留;allPage 恒为 1。
|
// 该接口无分页参数(上游也未分页),page 参数仅为统一契约保留;allPage 恒为 1。
|
||||||
// 实测(2026-10-02):英文/中文关键词均 HTTP 200。
|
// 实测(2026-10-02):英文/中文关键词均 HTTP 200。
|
||||||
|
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { jooxHeaders } from './util.ts'
|
import { jooxHeaders } from './util.ts'
|
||||||
import { jooxSongFromSearch } from './song.ts'
|
import { jooxSongFromSearch } from './song.ts'
|
||||||
|
|
||||||
const SEARCH_URL = 'https://cache.api.joox.com/openjoox/v3/search'
|
const SEARCH_URL = 'https://cache.api.joox.com/openjoox/v3/search'
|
||||||
|
|
||||||
export const search = async(keyword: string, page = 1, limit = 20) => {
|
export const search = async(keyword: string, _page = 1, limit = 20) => {
|
||||||
const url = `${SEARCH_URL}?country=sg&lang=zh_cn&keyword=${encodeURIComponent(String(keyword ?? ''))}`
|
const url = `${SEARCH_URL}?country=sg&lang=zh_cn&keyword=${encodeURIComponent(String(keyword ?? ''))}`
|
||||||
const { statusCode, body } = await httpFetch(url, { headers: jooxHeaders(), timeout: 20000 }).promise as any
|
const { statusCode, body } = await httpFetch(url, { headers: jooxHeaders(), timeout: 20000 }).promise as any
|
||||||
if (statusCode != 200 || !body?.section_list) {
|
if (statusCode != 200 || !body?.section_list) {
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
import { normalizeJooxID } from './util.ts'
|
import { normalizeJooxID } from './util.ts'
|
||||||
import { getTrackDetail, pickTrackUrl } from './musicInfo.ts'
|
import { getTrackDetail, pickTrackUrl } from './musicInfo.ts'
|
||||||
import { jooxSongFromDetail } from './song.ts'
|
import { jooxSongFromDetail } from './song.ts'
|
||||||
import { getAlbumDetail, parseAlbum } from './album.ts'
|
import { parseAlbum } from './album.ts'
|
||||||
import { getPlaylistDetail, parsePlaylist } from './playlist.ts'
|
import { parsePlaylist } from './playlist.ts'
|
||||||
|
|
||||||
/** 歌曲链接 → Song(上游 Parse 的正则与裸 ID 规则) */
|
/** 歌曲链接 → Song(上游 Parse 的正则与裸 ID 规则) */
|
||||||
export const parseSong = async(link: string) => {
|
export const parseSong = async(link: string) => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
// 分类:www.joox.com/sg/playlist(302 → /<region> 首页)__NEXT_DATA__.props.pageProps.mlList
|
// 分类:www.joox.com/sg/playlist(302 → /<region> 首页)__NEXT_DATA__.props.pageProps.mlList
|
||||||
// 链接解析:joox.com/{region}/playlist/<id>、playlistid/id 查询参数、裸 ID
|
// 链接解析:joox.com/{region}/playlist/<id>、playlistid/id 查询参数、裸 ID
|
||||||
|
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import {
|
import {
|
||||||
jooxHeaders,
|
jooxHeaders,
|
||||||
jooxSign,
|
jooxSign,
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
// 在签名参数里作为 uid/usk 参与(可拿到完整曲目/更高档位)。
|
// 在签名参数里作为 uid/usk 参与(可拿到完整曲目/更高档位)。
|
||||||
|
|
||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { getCredentials } from '../auth/store.ts'
|
import { getCredentials } from '../auth/store.ts'
|
||||||
|
|
||||||
export const JOOX_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
|
export const JOOX_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { getMusicInfosByList } from './musicInfo.ts'
|
import { getMusicInfosByList } from './musicInfo.ts'
|
||||||
import { createHttpFetch } from './util.ts'
|
import { createHttpFetch } from './util.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
|
|
||||||
const albumCover = (url: any): string => String(url ?? '').replace('{size}', '240')
|
const albumCover = (url: any): string => String(url ?? '').replace('{size}', '240')
|
||||||
|
|
||||||
|
|||||||
+1
-5
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { decodeName, dateFormat2 } from '../index.ts'
|
import { decodeName, dateFormat2 } from '../index.ts'
|
||||||
import { signatureParams } from './util.ts'
|
import { signatureParams } from './util.ts'
|
||||||
// import { getMusicInfoRaw } from './musicInfo.ts'
|
// import { getMusicInfoRaw } from './musicInfo.ts'
|
||||||
@@ -22,13 +22,11 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj.promise
|
const { body, statusCode } = await _requestObj.promise
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取评论失败')
|
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取评论失败')
|
||||||
const total = body.count ?? 0
|
const total = body.count ?? 0
|
||||||
return { source: 'kg', comments: this.filterComment(body.list || []), total, page, limit, maxPage: Math.ceil(total / limit) || 1 }
|
return { source: 'kg', comments: this.filterComment(body.list || []), total, page, limit, maxPage: Math.ceil(total / limit) || 1 }
|
||||||
},
|
},
|
||||||
async getHotComment({ hash }, page = 1, limit = 20) {
|
async getHotComment({ hash }, page = 1, limit = 20) {
|
||||||
// console.log(songmid)
|
|
||||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
if (this._requestObj2) this._requestObj2.cancelHttp()
|
||||||
let timestamp = Date.now()
|
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 = `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`
|
||||||
@@ -39,7 +37,6 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj2.promise
|
const { body, statusCode } = await _requestObj2.promise
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取热门评论失败')
|
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取热门评论失败')
|
||||||
const total = body.count ?? 0
|
const total = body.count ?? 0
|
||||||
return { source: 'kg', comments: this.filterComment(body.list || []), total, page, limit, maxPage: Math.ceil(total / limit) || 1 }
|
return { source: 'kg', comments: this.filterComment(body.list || []), total, page, limit, maxPage: Math.ceil(total / limit) || 1 }
|
||||||
@@ -57,7 +54,6 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj2.promise
|
const { body, statusCode } = await _requestObj2.promise
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取回复评论失败')
|
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取回复评论失败')
|
||||||
return { source: 'kg', comments: this.filterComment(body.list || []) }
|
return { source: 'kg', comments: this.filterComment(body.list || []) }
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-2
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { decodeName } from '../index.ts'
|
import { decodeName } from '../index.ts'
|
||||||
import type { HttpRequestObject } from '../types.ts'
|
import type { HttpRequestObject } from '../types.ts'
|
||||||
|
|
||||||
@@ -21,7 +21,6 @@ export default {
|
|||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj.promise
|
const { body, statusCode } = await _requestObj.promise
|
||||||
if (statusCode != 200 || body.errcode !== 0) throw new Error('获取热搜词失败')
|
if (statusCode != 200 || body.errcode !== 0) throw new Error('获取热搜词失败')
|
||||||
// console.log(body, statusCode)
|
|
||||||
return { source: 'kg', list: this.filterList(body.data.list) }
|
return { source: 'kg', list: this.filterList(body.data.list) }
|
||||||
},
|
},
|
||||||
filterList(rawList) {
|
filterList(rawList) {
|
||||||
|
|||||||
+2
-23
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { decodeName, formatPlayTime, sizeFormate } from '../index.ts'
|
import { decodeName, formatPlayTime, sizeFormate } from '../index.ts'
|
||||||
import { formatSingerName } from '../utils.ts'
|
import { formatSingerName } from '../utils.ts'
|
||||||
import type { HttpRequestObject, SongType, QualityInfo } from '../types.ts'
|
import type { HttpRequestObject, SongType, QualityInfo } from '../types.ts'
|
||||||
@@ -91,7 +91,6 @@ export default {
|
|||||||
return arr.join('、')
|
return arr.join('、')
|
||||||
},
|
},
|
||||||
filterData(rawList) {
|
filterData(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
return rawList.map(item => {
|
return rawList.map(item => {
|
||||||
const types: Array<SongType & { hash?: string }> = []
|
const types: Array<SongType & { hash?: string }> = []
|
||||||
const _types: Record<string, QualityInfo & { hash?: string }> = {}
|
const _types: Record<string, QualityInfo & { hash?: string }> = {}
|
||||||
@@ -147,7 +146,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
filterBoardsData(rawList) {
|
filterBoardsData(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
let list: Array<{ id: string; name: string; bangid: string }> = []
|
let list: Array<{ id: string; name: string; bangid: string }> = []
|
||||||
for (const board of rawList) {
|
for (const board of rawList) {
|
||||||
if (board.isvol != 1) continue
|
if (board.isvol != 1) continue
|
||||||
@@ -159,24 +157,7 @@ export default {
|
|||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
},
|
},
|
||||||
async getBoards(retryNum = 0) {
|
async getBoards() {
|
||||||
// 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
|
this.list = boardList
|
||||||
return {
|
return {
|
||||||
list: boardList,
|
list: boardList,
|
||||||
@@ -189,11 +170,9 @@ export default {
|
|||||||
|
|
||||||
if (body.errcode != 0) return this.getList(bangid, page, retryNum)
|
if (body.errcode != 0) return this.getList(bangid, page, retryNum)
|
||||||
|
|
||||||
// console.log(body)
|
|
||||||
let total = body.data.total
|
let total = body.data.total
|
||||||
let limit = 100
|
let limit = 100
|
||||||
let listData = this.filterData(body.data.info)
|
let listData = this.filterData(body.data.info)
|
||||||
// console.log(listData)
|
|
||||||
return {
|
return {
|
||||||
total,
|
total,
|
||||||
list: listData,
|
list: listData,
|
||||||
|
|||||||
+2
-24
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { decodeKrc } from './krcDecode.ts'
|
import { decodeKrc } from './krcDecode.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -13,28 +13,6 @@ export default {
|
|||||||
}
|
}
|
||||||
return parseInt(intv as unknown as string)
|
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) {
|
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`, {
|
let requestObj = httpFetch(`http://lyrics.kugou.com/search?ver=1&man=yes&client=pc&keyword=${encodeURIComponent(name)}&hash=${hash}&timelength=${time}&lrctxt=1`, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -92,7 +70,7 @@ export default {
|
|||||||
|
|
||||||
return requestObj
|
return requestObj
|
||||||
},
|
},
|
||||||
getLyric(songInfo, tryNum = 0) {
|
getLyric(songInfo) {
|
||||||
let requestObj = this.searchLyric(songInfo.name, songInfo.hash, songInfo._interval || this.getIntv(songInfo.interval))
|
let requestObj = this.searchLyric(songInfo.name, songInfo.hash, songInfo._interval || this.getIntv(songInfo.interval))
|
||||||
|
|
||||||
requestObj.promise = requestObj.promise.then(result => {
|
requestObj.promise = requestObj.promise.then(result => {
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ const createGetMusicInfosTask = (hashs: Array<{ hash: string }>): Array<Promise<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const filterMusicInfoList = (rawList: any[]): SongInfo[] => {
|
export const filterMusicInfoList = (rawList: any[]): SongInfo[] => {
|
||||||
// console.log(rawList)
|
|
||||||
let ids = new Set()
|
let ids = new Set()
|
||||||
let list: SongInfo[] = []
|
let list: SongInfo[] = []
|
||||||
rawList.forEach(item => {
|
rawList.forEach(item => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { decodeName, formatPlayTime, sizeFormate } from '../index.ts'
|
import { decodeName, formatPlayTime, sizeFormate } from '../index.ts'
|
||||||
import { formatSingerName } from '../utils.ts'
|
import { formatSingerName } from '../utils.ts'
|
||||||
import type { SongType, QualityInfo } from '../types.ts'
|
import type { SongType, QualityInfo } from '../types.ts'
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
// 上游对应:music-lib/kugou/song.go Parse / kugou.go fetchSongInfo
|
// 上游对应:music-lib/kugou/song.go Parse / kugou.go fetchSongInfo
|
||||||
// 元数据 m.kugou.com/app/i/getSongInfo.php(songName/author_name/timeLength/album_img/bitRate)
|
// 元数据 m.kugou.com/app/i/getSongInfo.php(songName/author_name/timeLength/album_img/bitRate)
|
||||||
// 另尽力合并 musicInfo.getMusicInfo 的专辑/多档 hash,直链取流失败不影响返回(上游为必得 URL)。
|
// 另尽力合并 musicInfo.getMusicInfo 的专辑/多档 hash,直链取流失败不影响返回(上游为必得 URL)。
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime, decodeName } from '../index.ts'
|
import { formatPlayTime, decodeName } from '../index.ts'
|
||||||
import kgApi from '../apis/kg.ts'
|
import kgApi from '../apis/kg.ts'
|
||||||
import { getMusicInfo } from './musicInfo.ts'
|
import { getMusicInfo } from './musicInfo.ts'
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getPic(songInfo) {
|
getPic(songInfo) {
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
// 链接解析优先复用既有 sdk/kg/songList.ts 的 getUserListDetail / getListDetailBySpecialId /
|
// 链接解析优先复用既有 sdk/kg/songList.ts 的 getUserListDetail / getListDetailBySpecialId /
|
||||||
// getListInfoByChain / decodeGcid 等能力(覆盖 special/single、songlist/gcid、share chain、
|
// getListInfoByChain / decodeGcid 等能力(覆盖 special/single、songlist/gcid、share chain、
|
||||||
// zlist、global_collection_id 等形态),本模块只做统一形状归一。
|
// zlist、global_collection_id 等形态),本模块只做统一形状归一。
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import songList from './songList.ts'
|
import songList from './songList.ts'
|
||||||
|
|
||||||
const UA = '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'
|
const UA = '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'
|
||||||
|
|||||||
+2
-145
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
|
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
|
||||||
import { toMD5 } from '../utils.ts'
|
import { toMD5 } from '../utils.ts'
|
||||||
import { signatureParams } from './util.ts'
|
import { signatureParams } from './util.ts'
|
||||||
@@ -66,51 +66,6 @@ export default {
|
|||||||
// https://www.kugou.com/yy/special/single/1067062.html
|
// https://www.kugou.com/yy/special/single/1067062.html
|
||||||
listDetailLink: /^.+\/(\d+)\.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) {
|
parseHtmlDesc(html) {
|
||||||
const prefix = '<div class="pc_specail_text pc_singer_tab_content" id="specailIntroduceWrap">'
|
const prefix = '<div class="pc_specail_text pc_singer_tab_content" id="specailIntroduceWrap">'
|
||||||
let index = html.indexOf(prefix)
|
let index = html.indexOf(prefix)
|
||||||
@@ -148,30 +103,9 @@ export default {
|
|||||||
name,
|
name,
|
||||||
img: pic,
|
img: pic,
|
||||||
desc,
|
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?) {
|
getInfoUrl(tagId?) {
|
||||||
return tagId
|
return tagId
|
||||||
@@ -276,10 +210,9 @@ export default {
|
|||||||
try {
|
try {
|
||||||
result = await httpFetch(url, options).promise
|
result = await httpFetch(url, options).promise
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.warn(`[kg] 歌单请求失败将重试(${retryNum + 1}/3) ${url}: ${(err as Error).message}`)
|
||||||
return this.createHttp(url, options, ++retryNum)
|
return this.createHttp(url, options, ++retryNum)
|
||||||
}
|
}
|
||||||
// console.log(result.statusCode, result.body)
|
|
||||||
if (result.statusCode !== 200 ||
|
if (result.statusCode !== 200 ||
|
||||||
(
|
(
|
||||||
(result.body.error_code !== undefined
|
(result.body.error_code !== undefined
|
||||||
@@ -349,7 +282,6 @@ export default {
|
|||||||
},
|
},
|
||||||
body: { appid: 1001, clientver: 9020, mid: '21511157a05844bd085308bc76ef3343', clienttime: 640612895, key: '36164c4015e704673c588ee202b9ecb8', data: id },
|
body: { appid: 1001, clientver: 9020, mid: '21511157a05844bd085308bc76ef3343', clienttime: 640612895, key: '36164c4015e704673c588ee202b9ecb8', data: id },
|
||||||
})
|
})
|
||||||
// console.log(songInfo)
|
|
||||||
// type 1单曲,2歌单,3电台,4酷狗码,5别人的播放队列
|
// type 1单曲,2歌单,3电台,4酷狗码,5别人的播放队列
|
||||||
let songList
|
let songList
|
||||||
let info = songInfo.info
|
let info = songInfo.info
|
||||||
@@ -372,7 +304,6 @@ export default {
|
|||||||
},
|
},
|
||||||
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 } },
|
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)
|
let list = await this.getMusicInfos(songList || songInfo.list)
|
||||||
return {
|
return {
|
||||||
@@ -402,7 +333,6 @@ export default {
|
|||||||
else return this.getUserListDetail4(songInfo, chain, page).catch(() => this.getUserListDetail5(chain))
|
else return this.getUserListDetail4(songInfo, chain, page).catch(() => this.getUserListDetail5(chain))
|
||||||
}
|
}
|
||||||
let list = await this.getMusicInfos(songInfo.list)
|
let list = await this.getMusicInfos(songInfo.list)
|
||||||
// console.log(info, songInfo)
|
|
||||||
return {
|
return {
|
||||||
list,
|
list,
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -468,7 +398,6 @@ export default {
|
|||||||
}
|
}
|
||||||
let result = await Promise.all(tasks).then(([...datas]) => datas.flat())
|
let result = await Promise.all(tasks).then(([...datas]) => datas.flat())
|
||||||
result = await this.getMusicInfos(result)
|
result = await this.getMusicInfos(result)
|
||||||
// console.log(result)
|
|
||||||
return {
|
return {
|
||||||
list: result,
|
list: result,
|
||||||
page,
|
page,
|
||||||
@@ -519,7 +448,6 @@ export default {
|
|||||||
})
|
})
|
||||||
const songInfo = await this.createGetListDetail2Task(id, info.songcount)
|
const songInfo = await this.createGetListDetail2Task(id, info.songcount)
|
||||||
let list = await this.getMusicInfos(songInfo)
|
let list = await this.getMusicInfos(songInfo)
|
||||||
// console.log(info, songInfo, list)
|
|
||||||
return {
|
return {
|
||||||
list,
|
list,
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -561,7 +489,6 @@ export default {
|
|||||||
if (result) result = JSON.parse(result[1])
|
if (result) result = JSON.parse(result[1])
|
||||||
this.cache.set(chain, result)
|
this.cache.set(chain, result)
|
||||||
result = await this.getMusicInfos(result)
|
result = await this.getMusicInfos(result)
|
||||||
// console.log(info, songInfo)
|
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -618,9 +545,7 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// console.log(info)
|
|
||||||
let result = await this.getMusicInfos(info.info)
|
let result = await this.getMusicInfos(info.info)
|
||||||
// console.log(info, songInfo)
|
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -655,10 +580,8 @@ export default {
|
|||||||
})
|
})
|
||||||
const { headers: { location }, statusCode, body } = await requestObj_listDetailLink.promise
|
const { headers: { location }, statusCode, body } = await requestObj_listDetailLink.promise
|
||||||
const loc = location as string | undefined
|
const loc = location as string | undefined
|
||||||
// console.log(body, location)
|
|
||||||
if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum)
|
if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum)
|
||||||
if (loc) {
|
if (loc) {
|
||||||
// console.log(location)
|
|
||||||
if (loc.includes('global_collection_id')) return this.getUserListDetail2(loc.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'))
|
if (loc.includes('global_collection_id')) return this.getUserListDetail2(loc.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'))
|
||||||
if (loc.includes('gcid_')) {
|
if (loc.includes('gcid_')) {
|
||||||
let gcid = link.match(/gcid_\w+/)?.[0]
|
let gcid = link.match(/gcid_\w+/)?.[0]
|
||||||
@@ -679,7 +602,6 @@ export default {
|
|||||||
return this.getUserListDetail(link, page, ++retryNum)
|
return this.getUserListDetail(link, page, ++retryNum)
|
||||||
} else return this.getUserListDetail3(loc.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page)
|
} else return this.getUserListDetail3(loc.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page)
|
||||||
}
|
}
|
||||||
// console.log('location', location)
|
|
||||||
return this.getUserListDetail(loc, page, ++retryNum)
|
return this.getUserListDetail(loc, page, ++retryNum)
|
||||||
}
|
}
|
||||||
if (typeof body == 'string') {
|
if (typeof body == 'string') {
|
||||||
@@ -713,7 +635,6 @@ export default {
|
|||||||
return this.getListDetailBySpecialId(id, page)
|
return this.getListDetailBySpecialId(id, page)
|
||||||
},
|
},
|
||||||
filterData(rawList) {
|
filterData(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
return rawList.map(item => {
|
return rawList.map(item => {
|
||||||
const types: Array<{ type: string; size: string; hash?: string }> = []
|
const types: Array<{ type: string; size: string; hash?: string }> = []
|
||||||
const _types: Record<string, QualityInfo & { hash?: string }> = {}
|
const _types: Record<string, QualityInfo & { hash?: string }> = {}
|
||||||
@@ -766,72 +687,9 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 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
|
// hash list filter
|
||||||
filterData2(rawList) {
|
filterData2(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
let ids = new Set()
|
let ids = new Set()
|
||||||
let list: SongInfo[] = []
|
let list: SongInfo[] = []
|
||||||
rawList.forEach(item => {
|
rawList.forEach(item => {
|
||||||
@@ -959,7 +817,6 @@ export default {
|
|||||||
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`)
|
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 }) => {
|
.promise.then(({ body }) => {
|
||||||
if (body.errcode != 0) throw new Error('filed')
|
if (body.errcode != 0) throw new Error('filed')
|
||||||
// console.log(body.data.info)
|
|
||||||
return {
|
return {
|
||||||
list: body.data.info.map(item => {
|
list: body.data.info.map(item => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
+2
-4
@@ -1,9 +1,8 @@
|
|||||||
import { toMD5 } from '../utils.ts'
|
import { toMD5 } from '../utils.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import type { HttpOptions } from '../types.ts'
|
import type { HttpOptions } from '../types.ts'
|
||||||
|
|
||||||
// s.content[0].lyricContent.forEach(([str]) => {
|
// s.content[0].lyricContent.forEach(([str]) => {
|
||||||
// console.log(str)
|
|
||||||
// })
|
// })
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,10 +31,9 @@ export const createHttpFetch = async(url: string, options?: HttpOptions, retryNu
|
|||||||
try {
|
try {
|
||||||
result = await httpFetch(url, options).promise
|
result = await httpFetch(url, options).promise
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.warn(`[kg] 请求失败将重试(${retryNum + 1}/3) ${url}: ${(err as Error).message}`)
|
||||||
return createHttpFetch(url, options, ++retryNum)
|
return createHttpFetch(url, options, ++retryNum)
|
||||||
}
|
}
|
||||||
// console.log(result.statusCode, result.body)
|
|
||||||
if (result.statusCode !== 200 ||
|
if (result.statusCode !== 200 ||
|
||||||
(
|
(
|
||||||
result.body.error_code ??
|
result.body.error_code ??
|
||||||
|
|||||||
+2
-32
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { decodeName } from '../index.ts'
|
import { decodeName } from '../index.ts'
|
||||||
import { formatSinger, objStr2JSON } from './util.ts'
|
import { formatSinger, objStr2JSON } from './util.ts'
|
||||||
import type { Quality } from '../types.ts'
|
import type { Quality } from '../types.ts'
|
||||||
@@ -17,9 +17,7 @@ export default {
|
|||||||
limit_list: 36,
|
limit_list: 36,
|
||||||
limit_song: 1000,
|
limit_song: 1000,
|
||||||
filterListDetail(rawList, albumName, albumId) {
|
filterListDetail(rawList, albumName, albumId) {
|
||||||
// console.log(rawList)
|
return rawList.map(item => {
|
||||||
// console.log(rawList.length, rawList2.length)
|
|
||||||
return rawList.map((item, inedx) => {
|
|
||||||
let formats = item.formats.split('|')
|
let formats = item.formats.split('|')
|
||||||
let types: Array<{ type: Quality; size: string | null }> = []
|
let types: Array<{ type: Quality; size: string | null }> = []
|
||||||
let _types: Partial<Record<Quality, { size: string | null }>> = {}
|
let _types: Partial<Record<Quality, { size: string | null }>> = {}
|
||||||
@@ -92,7 +90,6 @@ export default {
|
|||||||
return requestObj_listDetail.promise.then(({ statusCode, body }) => {
|
return requestObj_listDetail.promise.then(({ statusCode, body }) => {
|
||||||
if (statusCode !== 200) return this.getAlbumListDetail(id, page, ++retryNum)
|
if (statusCode !== 200) return this.getAlbumListDetail(id, page, ++retryNum)
|
||||||
body = objStr2JSON(body)
|
body = objStr2JSON(body)
|
||||||
// console.log(body)
|
|
||||||
if (!body.musiclist) return this.getAlbumListDetail(id, page, ++retryNum)
|
if (!body.musiclist) return this.getAlbumListDetail(id, page, ++retryNum)
|
||||||
body.name = decodeName(body.name)
|
body.name = decodeName(body.name)
|
||||||
return {
|
return {
|
||||||
@@ -235,31 +232,4 @@ export default {
|
|||||||
}
|
}
|
||||||
throw new Error('无效的酷我专辑链接')
|
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) => {
|
|
||||||
// return resp.promise.then(({ statusCode, body }) => {
|
|
||||||
// console.log(body)
|
|
||||||
// return Promise.reject(new Error('failed'))
|
|
||||||
// // if (statusCode !== 200) return this.getAlbumListDetail(id, page, ++retryNum)
|
|
||||||
// // const data = body.data
|
|
||||||
// // console.log(data)
|
|
||||||
// // if (!data.musicList) return this.getAlbumListDetail(id, page, ++retryNum)
|
|
||||||
// // return {
|
|
||||||
// // list: this.filterListDetail(data.musiclist),
|
|
||||||
// // page,
|
|
||||||
// // limit: this.limit_song,
|
|
||||||
// // total: data.total,
|
|
||||||
// // source: 'kw',
|
|
||||||
// // info: {
|
|
||||||
// // name: data.album,
|
|
||||||
// // img: data.pic,
|
|
||||||
// // desc: data.albuminfo,
|
|
||||||
// // author: data.artist,
|
|
||||||
// // play_count: this.formatPlayCount(data.playCnt),
|
|
||||||
// // },
|
|
||||||
// // }
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
}
|
}
|
||||||
+1
-3
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { dateFormat2 } from '../index.ts'
|
import { dateFormat2 } from '../index.ts'
|
||||||
import type { HttpRequestObject } from '../types.ts'
|
import type { HttpRequestObject } from '../types.ts'
|
||||||
|
|
||||||
@@ -15,7 +15,6 @@ export default {
|
|||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj.promise
|
const { body, statusCode } = await _requestObj.promise
|
||||||
if (statusCode != 200 || body.code != '200') throw new Error('获取评论失败')
|
if (statusCode != 200 || body.code != '200') throw new Error('获取评论失败')
|
||||||
// console.log(body)
|
|
||||||
|
|
||||||
const total = body.comments_counts
|
const total = body.comments_counts
|
||||||
return {
|
return {
|
||||||
@@ -37,7 +36,6 @@ export default {
|
|||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj2.promise
|
const { body, statusCode } = await _requestObj2.promise
|
||||||
if (statusCode != 200 || body.code != '200') throw new Error('获取热门评论失败')
|
if (statusCode != 200 || body.code != '200') throw new Error('获取热门评论失败')
|
||||||
// console.log(body)
|
|
||||||
|
|
||||||
const total = body.hot_comments_counts
|
const total = body.hot_comments_counts
|
||||||
return {
|
return {
|
||||||
|
|||||||
+1
-2
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import type { HttpRequestObject } from '../types.ts'
|
import type { HttpRequestObject } from '../types.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -14,7 +14,6 @@ export default {
|
|||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj.promise
|
const { body, statusCode } = await _requestObj.promise
|
||||||
if (statusCode != 200 || body.status !== 'ok') throw new Error('获取热搜词失败')
|
if (statusCode != 200 || body.status !== 'ok') throw new Error('获取热搜词失败')
|
||||||
// console.log(body, statusCode)
|
|
||||||
return { source: 'kw', list: this.filterList(body.tagvalue) }
|
return { source: 'kw', list: this.filterList(body.tagvalue) }
|
||||||
},
|
},
|
||||||
filterList(rawList) {
|
filterList(rawList) {
|
||||||
|
|||||||
+1
-21
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import musicSearch from './musicSearch.ts'
|
import musicSearch from './musicSearch.ts'
|
||||||
import { formatSinger } from './util.ts'
|
import { formatSinger } from './util.ts'
|
||||||
import leaderboard from './leaderboard.ts'
|
import leaderboard from './leaderboard.ts'
|
||||||
@@ -21,22 +21,6 @@ const kw = {
|
|||||||
album,
|
album,
|
||||||
playlist,
|
playlist,
|
||||||
parse,
|
parse,
|
||||||
// context: null,
|
|
||||||
|
|
||||||
|
|
||||||
// init(context) {
|
|
||||||
// if (this.isInited) return
|
|
||||||
// this.isInited = true
|
|
||||||
// this.context = context
|
|
||||||
|
|
||||||
// // this.musicSearch.search('我又想你了').then(res => {
|
|
||||||
// // console.log(res)
|
|
||||||
// // })
|
|
||||||
|
|
||||||
// // this.getMusicUrl('62355680', '320k').then(url => {
|
|
||||||
// // console.log(url)
|
|
||||||
// // })
|
|
||||||
// },
|
|
||||||
|
|
||||||
musicSearch,
|
musicSearch,
|
||||||
leaderboard,
|
leaderboard,
|
||||||
@@ -49,7 +33,6 @@ const kw = {
|
|||||||
},
|
},
|
||||||
handleMusicInfo(songInfo) {
|
handleMusicInfo(songInfo) {
|
||||||
return this.getMusicInfo(songInfo).then(info => {
|
return this.getMusicInfo(songInfo).then(info => {
|
||||||
// console.log(JSON.stringify(info))
|
|
||||||
songInfo.name = info.name
|
songInfo.name = info.name
|
||||||
songInfo.singer = formatSinger(info.artist)
|
songInfo.singer = formatSinger(info.artist)
|
||||||
songInfo.img = info.pic
|
songInfo.img = info.pic
|
||||||
@@ -99,9 +82,6 @@ const kw = {
|
|||||||
return `http://www.kuwo.cn/play_detail/${songInfo.songmid}`
|
return `http://www.kuwo.cn/play_detail/${songInfo.songmid}`
|
||||||
},
|
},
|
||||||
|
|
||||||
// init() {
|
|
||||||
// return getToken()
|
|
||||||
// },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default kw
|
export default kw
|
||||||
+2
-24
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime, decodeName } from '../index.ts'
|
import { formatPlayTime, decodeName } from '../index.ts'
|
||||||
import { formatSinger, wbdCrypto } from './util.ts'
|
import { formatSinger, wbdCrypto } from './util.ts'
|
||||||
import type { HttpRequestObject, Quality, QualityInfo, SongType } from '../types.ts'
|
import type { HttpRequestObject, Quality, QualityInfo, SongType } from '../types.ts'
|
||||||
@@ -167,7 +167,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
filterBoardsData(rawList) {
|
filterBoardsData(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
let list: any[] = []
|
let list: any[] = []
|
||||||
for (const board of rawList) {
|
for (const board of rawList) {
|
||||||
if (board.source != '1') continue
|
if (board.source != '1') continue
|
||||||
@@ -179,24 +178,7 @@ export default {
|
|||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
},
|
},
|
||||||
async getBoards(retryNum = 0) {
|
async getBoards() {
|
||||||
// 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.child) return this.getBoards(retryNum)
|
|
||||||
// const list = this.filterBoardsData(response.body.child)
|
|
||||||
// // console.log(list)
|
|
||||||
// console.log(JSON.stringify(list))
|
|
||||||
// this.list = list
|
|
||||||
// return {
|
|
||||||
// list,
|
|
||||||
// source: 'kw',
|
|
||||||
// }
|
|
||||||
this.list = boardList
|
this.list = boardList
|
||||||
return {
|
return {
|
||||||
list: boardList,
|
list: boardList,
|
||||||
@@ -213,7 +195,6 @@ export default {
|
|||||||
|
|
||||||
return request.then(({ statusCode, body }) => {
|
return request.then(({ statusCode, body }) => {
|
||||||
const rawData = wbdCrypto.decodeData(body)
|
const rawData = wbdCrypto.decodeData(body)
|
||||||
// console.log(rawData)
|
|
||||||
const data = rawData.data
|
const data = rawData.data
|
||||||
if (statusCode !== 200 || rawData.code != 200 || !data.musiclist) return this.getList(id, page, retryNum)
|
if (statusCode !== 200 || rawData.code != 200 || !data.musiclist) return this.getList(id, page, retryNum)
|
||||||
|
|
||||||
@@ -230,7 +211,4 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// getDetailPageUrl(id) {
|
|
||||||
// return `http://www.kuwo.cn/rankList/${id}`
|
|
||||||
// },
|
|
||||||
}
|
}
|
||||||
+1
-151
@@ -1,71 +1,7 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { decodeLyric, lrcTools } from './util.ts'
|
import { decodeLyric, lrcTools } from './util.ts'
|
||||||
import { decodeName } from '../index.ts'
|
import { decodeName } from '../index.ts'
|
||||||
|
|
||||||
/*
|
|
||||||
export default {
|
|
||||||
formatTime(time) {
|
|
||||||
let m = parseInt(time / 60)
|
|
||||||
let s = (time % 60).toFixed(2)
|
|
||||||
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
|
|
||||||
},
|
|
||||||
sortLrcArr(arr) {
|
|
||||||
const lrcSet = new Set()
|
|
||||||
let lrc = []
|
|
||||||
let lrcT = []
|
|
||||||
|
|
||||||
for (const item of arr) {
|
|
||||||
if (lrcSet.has(item.time)) {
|
|
||||||
const tItem = lrc.pop()
|
|
||||||
tItem.time = lrc[lrc.length - 1].time
|
|
||||||
lrcT.push(tItem)
|
|
||||||
lrc.push(item)
|
|
||||||
} else {
|
|
||||||
lrc.push(item)
|
|
||||||
lrcSet.add(item.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lrcT.length && lrc.length > lrcT.length) {
|
|
||||||
const tItem = lrc.pop()
|
|
||||||
tItem.time = lrc[lrc.length - 1].time
|
|
||||||
lrcT.push(tItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
lrc,
|
|
||||||
lrcT,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
transformLrc(songinfo, lrclist) {
|
|
||||||
return `[ti:${songinfo.songName}]\n[ar:${songinfo.artist}]\n[al:${songinfo.album}]\n[by:]\n[offset:0]\n${lrclist ? lrclist.map(l => `[${this.formatTime(l.time)}]${l.lineLyric}\n`).join('') : '暂无歌词'}`
|
|
||||||
},
|
|
||||||
getLyric(songId) {
|
|
||||||
const requestObj = httpFetch(`http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=${songId}`)
|
|
||||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
|
||||||
// console.log(body)
|
|
||||||
if (!body.data?.lrclist?.length) return Promise.reject(new Error('Get lyric failed'))
|
|
||||||
let lrcInfo
|
|
||||||
try {
|
|
||||||
lrcInfo = this.sortLrcArr(body.data.lrclist)
|
|
||||||
} catch {
|
|
||||||
return Promise.reject(new Error('Get lyric failed'))
|
|
||||||
}
|
|
||||||
// console.log(body.data.lrclist)
|
|
||||||
// console.log(lrcInfo.lrc, lrcInfo.lrcT)
|
|
||||||
// console.log({
|
|
||||||
// lyric: decodeName(this.transformLrc(body.data.songinfo, lrc)),
|
|
||||||
// tlyric: decodeName(this.transformLrc(body.data.songinfo, lrcT)),
|
|
||||||
// })
|
|
||||||
return {
|
|
||||||
lyric: decodeName(this.transformLrc(body.data.songinfo, lrcInfo.lrc)),
|
|
||||||
tlyric: lrcInfo.lrcT.length ? decodeName(this.transformLrc(body.data.songinfo, lrcInfo.lrcT)) : '',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return requestObj
|
|
||||||
},
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const buf_key = Buffer.from('yeelion')
|
const buf_key = Buffer.from('yeelion')
|
||||||
const buf_key_len = buf_key.length
|
const buf_key_len = buf_key.length
|
||||||
@@ -87,65 +23,11 @@ const buildParams = (id: string, isGetLyricx: boolean): string => {
|
|||||||
return Buffer.from(output).toString('base64')
|
return Buffer.from(output).toString('base64')
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(buildParams('207527604', false))
|
|
||||||
// console.log(buildParams('207527604', true))
|
|
||||||
|
|
||||||
const timeExp = /^\[([\d:.]*)\]{1}/g
|
const timeExp = /^\[([\d:.]*)\]{1}/g
|
||||||
const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
||||||
const lyricxTag = /^<-?\d+,-?\d+>/
|
const lyricxTag = /^<-?\d+,-?\d+>/
|
||||||
export default {
|
export default {
|
||||||
/* sortLrcArr(arr) {
|
|
||||||
const lrcSet = new Set()
|
|
||||||
let lrc = []
|
|
||||||
let lrcT = []
|
|
||||||
let markIndex = []
|
|
||||||
for (const item of arr) {
|
|
||||||
if (lrcSet.has(item.time)) {
|
|
||||||
if (lrc.length < 2) continue
|
|
||||||
const index = lrc.findIndex(l => l.time == item.time)
|
|
||||||
markIndex.push(index)
|
|
||||||
if (index == lrc.length - 1) {
|
|
||||||
lrcT.push({ ...lrc[index], time: item.time })
|
|
||||||
lrc.push(item)
|
|
||||||
} else {
|
|
||||||
lrcT.push({ ...lrc[index], time: lrc[index + 1].time })
|
|
||||||
if (item.text) {
|
|
||||||
// const lastIndex = lrc.length - 1
|
|
||||||
// markIndex.push(lastIndex)
|
|
||||||
// lrcT.push({ ...lrc[lastIndex], time: lrc[lastIndex - 1].time })
|
|
||||||
lrc.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lrc.push(item)
|
|
||||||
lrcSet.add(item.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log(markIndex)
|
|
||||||
markIndex = Array.from(new Set(markIndex))
|
|
||||||
for (let index = markIndex.length - 1; index >= 0; index--) {
|
|
||||||
lrc.splice(markIndex[index], 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (lrcT.length) {
|
|
||||||
// if (lrc.length * 0.4 < lrcT.length) { // 翻译数量需大于歌词数量的0.4倍,否则认为没有翻译
|
|
||||||
// const tItem = lrc.pop()
|
|
||||||
// tItem.time = lrc[lrc.length - 1].time
|
|
||||||
// lrcT.push(tItem)
|
|
||||||
// } else {
|
|
||||||
// lrc = arr
|
|
||||||
// lrcT = []
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
console.log(lrc, lrcT)
|
|
||||||
|
|
||||||
return {
|
|
||||||
lrc,
|
|
||||||
lrcT,
|
|
||||||
}
|
|
||||||
}, */
|
|
||||||
sortLrcArr(arr) {
|
sortLrcArr(arr) {
|
||||||
const lrcSet = new Set()
|
const lrcSet = new Set()
|
||||||
let lrc: any[] = []
|
let lrc: any[] = []
|
||||||
@@ -211,48 +93,17 @@ export default {
|
|||||||
tlyric: lrcInfo.lrcT.length ? decodeName(this.transformLrc(tags, lrcInfo.lrcT)) : '',
|
tlyric: lrcInfo.lrcT.length ? decodeName(this.transformLrc(tags, lrcInfo.lrcT)) : '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// getLyric2(musicInfo, isGetLyricx = true) {
|
|
||||||
// const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicInfo.songmid, isGetLyricx)}`)
|
|
||||||
// requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => {
|
|
||||||
// if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body)))
|
|
||||||
// return decodeLyric({ lrcBase64: raw.toString('base64'), isGetLyricx }).then(base64Data => {
|
|
||||||
// let lrcInfo
|
|
||||||
// console.log(Buffer.from(base64Data, 'base64').toString())
|
|
||||||
// try {
|
|
||||||
// lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
|
|
||||||
// } catch {
|
|
||||||
// return Promise.reject(new Error('Get lyric failed'))
|
|
||||||
// }
|
|
||||||
// if (lrcInfo.tlyric) lrcInfo.tlyric = lrcInfo.tlyric.replace(lrcTools.rxps.wordTimeAll, '')
|
|
||||||
// lrcInfo.yrc = lrcTools.parse(lrcInfo.lyric)
|
|
||||||
// // console.log(lrcInfo.lyric)
|
|
||||||
// // console.log(lrcInfo.tlyric)
|
|
||||||
// // console.log(lrcInfo.yrc)
|
|
||||||
// // console.log(JSON.stringify(lrcInfo))
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// return requestObj
|
|
||||||
// },
|
|
||||||
getLyric(musicInfo, isGetLyricx = true) {
|
getLyric(musicInfo, isGetLyricx = true) {
|
||||||
// this.getLyric2(musicInfo)
|
|
||||||
const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicInfo.songmid, isGetLyricx)}`)
|
const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicInfo.songmid, isGetLyricx)}`)
|
||||||
requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => {
|
requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => {
|
||||||
if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body)))
|
if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body)))
|
||||||
return decodeLyric({ lrcBase64: raw!.toString('base64'), isGetLyricx }).then(base64Data => {
|
return decodeLyric({ lrcBase64: raw!.toString('base64'), isGetLyricx }).then(base64Data => {
|
||||||
// let lrcInfo
|
|
||||||
// try {
|
|
||||||
// lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
|
|
||||||
// } catch {
|
|
||||||
// return Promise.reject(new Error('Get lyric failed'))
|
|
||||||
// }
|
|
||||||
let lrcInfo
|
let lrcInfo
|
||||||
// console.log(Buffer.from(base64Data, 'base64').toString())
|
|
||||||
try {
|
try {
|
||||||
lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
|
lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return Promise.reject(new Error('Get lyric failed'))
|
return Promise.reject(new Error('Get lyric failed'))
|
||||||
}
|
}
|
||||||
// console.log(lrcInfo)
|
|
||||||
if (lrcInfo.tlyric) lrcInfo.tlyric = lrcInfo.tlyric.replace(lrcTools.rxps.wordTimeAll, '')
|
if (lrcInfo.tlyric) lrcInfo.tlyric = lrcInfo.tlyric.replace(lrcTools.rxps.wordTimeAll, '')
|
||||||
try {
|
try {
|
||||||
lrcInfo.yrc = lrcTools.parse(lrcInfo.lyric)
|
lrcInfo.yrc = lrcTools.parse(lrcInfo.lyric)
|
||||||
@@ -261,7 +112,6 @@ export default {
|
|||||||
}
|
}
|
||||||
lrcInfo.lyric = lrcInfo.lyric.replace(lrcTools.rxps.wordTimeAll, '')
|
lrcInfo.lyric = lrcInfo.lyric.replace(lrcTools.rxps.wordTimeAll, '')
|
||||||
if (!existTimeExp.test(lrcInfo.lyric)) return Promise.reject(new Error('Get lyric failed'))
|
if (!existTimeExp.test(lrcInfo.lyric)) return Promise.reject(new Error('Get lyric failed'))
|
||||||
// console.log(lrcInfo)
|
|
||||||
return lrcInfo
|
return lrcInfo
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+1
-11
@@ -1,6 +1,6 @@
|
|||||||
// import '../../polyfill/array.find.js'
|
// import '../../polyfill/array.find.js'
|
||||||
|
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime, decodeName } from '../index.ts'
|
import { formatPlayTime, decodeName } from '../index.ts'
|
||||||
// import { debug } from '../../utils/env.ts'
|
// import { debug } from '../../utils/env.ts'
|
||||||
import { formatSinger } from './util.ts'
|
import { formatSinger } from './util.ts'
|
||||||
@@ -14,21 +14,13 @@ export default {
|
|||||||
total: 0,
|
total: 0,
|
||||||
page: 0,
|
page: 0,
|
||||||
allPage: 1,
|
allPage: 1,
|
||||||
// cancelFn: null,
|
|
||||||
musicSearch(str: string, page: number, limit: number) {
|
musicSearch(str: string, page: number, limit: number) {
|
||||||
const musicSearchRequestObj = httpFetch(`http://search.kuwo.cn/r.s?client=kt&all=${encodeURIComponent(str)}&pn=${page - 1}&rn=${limit}&uid=794762570&ver=kwplayer_ar_9.2.2.1&vipver=1&show_copyright_off=1&newver=1&ft=music&cluster=0&strategy=2012&encoding=utf8&rformat=json&vermerge=1&mobi=1&issubtitle=1`)
|
const musicSearchRequestObj = httpFetch(`http://search.kuwo.cn/r.s?client=kt&all=${encodeURIComponent(str)}&pn=${page - 1}&rn=${limit}&uid=794762570&ver=kwplayer_ar_9.2.2.1&vipver=1&show_copyright_off=1&newver=1&ft=music&cluster=0&strategy=2012&encoding=utf8&rformat=json&vermerge=1&mobi=1&issubtitle=1`)
|
||||||
return musicSearchRequestObj.promise
|
return musicSearchRequestObj.promise
|
||||||
},
|
},
|
||||||
// getImg(songId) {
|
|
||||||
// return httpGet(`http://player.kuwo.cn/webmusic/sj/dtflagdate?flag=6&rid=MUSIC_${songId}`)
|
|
||||||
// },
|
|
||||||
// getLrc(songId) {
|
|
||||||
// return httpGet(`http://mobile.kuwo.cn/mpage/html5/songinfoandlrc?mid=${songId}&flag=0`)
|
|
||||||
// },
|
|
||||||
handleResult(rawData: any) {
|
handleResult(rawData: any) {
|
||||||
const result: SongInfo[] = []
|
const result: SongInfo[] = []
|
||||||
if (!rawData) return result
|
if (!rawData) return result
|
||||||
// console.log(rawData)
|
|
||||||
for (let i = 0; i < rawData.length; i++) {
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
const info = rawData[i]
|
const info = rawData[i]
|
||||||
let songId = info.MUSICRID.replace('MUSIC_', '')
|
let songId = info.MUSICRID.replace('MUSIC_', '')
|
||||||
@@ -97,7 +89,6 @@ export default {
|
|||||||
typeUrl: {},
|
typeUrl: {},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// console.log(result)
|
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
search(str: string, page = 1, limit?: number, retryNum = 0) {
|
search(str: string, page = 1, limit?: number, retryNum = 0) {
|
||||||
@@ -105,7 +96,6 @@ export default {
|
|||||||
if (limit == null) limit = this.limit
|
if (limit == null) limit = this.limit
|
||||||
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
|
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
|
||||||
return this.musicSearch(str, page, limit).then(({ body: result }) => {
|
return this.musicSearch(str, page, limit).then(({ body: result }) => {
|
||||||
// console.log(result)
|
|
||||||
if (!result || (result.TOTAL !== '0' && result.SHOW === '0')) return this.search(str, page, limit, ++retryNum)
|
if (!result || (result.TOTAL !== '0' && result.SHOW === '0')) return this.search(str, page, limit, ++retryNum)
|
||||||
let list = this.handleResult(result.abslist)
|
let list = this.handleResult(result.abslist)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
// 歌曲:https://www.kuwo.cn/play_detail/<rid>(含 ?/# 尾串)、m.kuwo.cn 的 musicId=<rid>、裸 rid
|
// 歌曲: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 + 尽力附直链)
|
// 上游对应:music-lib/kuwo/song.go Parse / kuwo.go fetchFullSongInfo(元数据 m.kuwo.cn + 尽力附直链)
|
||||||
// 专辑 / 歌单分别复用 album.parseAlbumLink、playlist.parsePlaylistLink。
|
// 专辑 / 歌单分别复用 album.parseAlbumLink、playlist.parsePlaylistLink。
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime } from '../index.ts'
|
import { formatPlayTime } from '../index.ts'
|
||||||
import kwApi from '../apis/kw.ts'
|
import kwApi from '../apis/kw.ts'
|
||||||
import album from './album.ts'
|
import album from './album.ts'
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import type { SongInfo } from '../types.ts'
|
import type { SongInfo } from '../types.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
+1
-9
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime, decodeName } from '../index.ts'
|
import { formatPlayTime, decodeName } from '../index.ts'
|
||||||
import { formatSinger, objStr2JSON } from './util.ts'
|
import { formatSinger, objStr2JSON } from './util.ts'
|
||||||
import album from './album.ts'
|
import album from './album.ts'
|
||||||
@@ -147,7 +147,6 @@ export default {
|
|||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
filterList2(rawData) {
|
filterList2(rawData) {
|
||||||
// console.log(rawData)
|
|
||||||
const list: any[] = []
|
const list: any[] = []
|
||||||
rawData.forEach(item => {
|
rawData.forEach(item => {
|
||||||
if (!item.label) return
|
if (!item.label) return
|
||||||
@@ -196,7 +195,6 @@ export default {
|
|||||||
// 本方法须返回 detailId(调用方据此再查曲目),失败时应重试自身;
|
// 本方法须返回 detailId(调用方据此再查曲目),失败时应重试自身;
|
||||||
// 原先误调 getListDetail(返回歌单详情对象、且把 tryNum 当 page)导致返回类型错位
|
// 原先误调 getListDetail(返回歌单详情对象、且把 tryNum 当 page)导致返回类型错位
|
||||||
if (statusCode != 200 || !body.child) return this.getListDetailDigest5Info(id, ++tryNum)
|
if (statusCode != 200 || !body.child) return this.getListDetailDigest5Info(id, ++tryNum)
|
||||||
// console.log(body)
|
|
||||||
return body.child.length ? body.child[0].sourceid : null
|
return body.child.length ? body.child[0].sourceid : null
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -204,7 +202,6 @@ export default {
|
|||||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||||
const requestObj = httpFetch(`http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}&rn=${this.limit_song}&encode=utf-8&keyset=pl2012&identity=kuwo&pcmp4=1`)
|
const requestObj = httpFetch(`http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}&rn=${this.limit_song}&encode=utf-8&keyset=pl2012&identity=kuwo&pcmp4=1`)
|
||||||
return requestObj.promise.then(({ body }) => {
|
return requestObj.promise.then(({ body }) => {
|
||||||
// console.log(body)
|
|
||||||
if (body.result !== 'ok') return this.getListDetail(id, page, ++tryNum)
|
if (body.result !== 'ok') return this.getListDetail(id, page, ++tryNum)
|
||||||
return {
|
return {
|
||||||
list: this.filterListDetail(body.musiclist),
|
list: this.filterListDetail(body.musiclist),
|
||||||
@@ -314,7 +311,6 @@ export default {
|
|||||||
|
|
||||||
if (infoData.code != 200) return null
|
if (infoData.code != 200) return null
|
||||||
|
|
||||||
// console.log(infoData)
|
|
||||||
return {
|
return {
|
||||||
name: infoData.data.userInfo.nickname + '喜欢的音乐',
|
name: infoData.data.userInfo.nickname + '喜欢的音乐',
|
||||||
img: infoData.data.userInfo.headImg,
|
img: infoData.data.userInfo.headImg,
|
||||||
@@ -367,13 +363,11 @@ export default {
|
|||||||
author: '',
|
author: '',
|
||||||
play_count: '',
|
play_count: '',
|
||||||
}
|
}
|
||||||
// console.log(listData)
|
|
||||||
return listData
|
return listData
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取歌曲列表内的音乐
|
// 获取歌曲列表内的音乐
|
||||||
getListDetail(id, page, retryNum = 0) {
|
getListDetail(id, page, retryNum = 0) {
|
||||||
// console.log(id)
|
|
||||||
// https://h5app.kuwo.cn/m/bodian/collection.html?uid=000&playlistId=000&source=5&ownerId=000
|
// https://h5app.kuwo.cn/m/bodian/collection.html?uid=000&playlistId=000&source=5&ownerId=000
|
||||||
// https://h5app.kuwo.cn/m/bodian/collection.html?uid=000&playlistId=000&source=4&ownerId=
|
// https://h5app.kuwo.cn/m/bodian/collection.html?uid=000&playlistId=000&source=4&ownerId=
|
||||||
if (/\/bodian\//.test(id)) return this.getListDetailMusicListByBD(id, page)
|
if (/\/bodian\//.test(id)) return this.getListDetailMusicListByBD(id, page)
|
||||||
@@ -393,7 +387,6 @@ export default {
|
|||||||
return this.getListDetailDigest8(id, page, retryNum)
|
return this.getListDetailDigest8(id, page, retryNum)
|
||||||
},
|
},
|
||||||
filterListDetail(rawData) {
|
filterListDetail(rawData) {
|
||||||
// console.log(rawData)
|
|
||||||
return rawData.map(item => {
|
return rawData.map(item => {
|
||||||
let infoArr = item.N_MINFO.split(';')
|
let infoArr = item.N_MINFO.split(';')
|
||||||
let types: SongType[] = []
|
let types: SongType[] = []
|
||||||
@@ -464,7 +457,6 @@ export default {
|
|||||||
return httpFetch(`http://search.kuwo.cn/r.s?all=${encodeURIComponent(text)}&pn=${page - 1}&rn=${limit}&rformat=json&encoding=utf8&ver=mbox&vipver=MUSIC_8.7.7.0_BCS37&plat=pc&devid=28156413&ft=playlist&pay=0&needliveshow=0`)
|
return httpFetch(`http://search.kuwo.cn/r.s?all=${encodeURIComponent(text)}&pn=${page - 1}&rn=${limit}&rformat=json&encoding=utf8&ver=mbox&vipver=MUSIC_8.7.7.0_BCS37&plat=pc&devid=28156413&ft=playlist&pay=0&needliveshow=0`)
|
||||||
.promise.then(({ body }) => {
|
.promise.then(({ body }) => {
|
||||||
body = objStr2JSON(body)
|
body = objStr2JSON(body)
|
||||||
// console.log(body)
|
|
||||||
return {
|
return {
|
||||||
list: body.abslist.map(item => {
|
list: body.abslist.map(item => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,33 +1,7 @@
|
|||||||
// import { httpGet, httpFetch } from '../request.ts'
|
|
||||||
import { handleKwDecodeLyric } from './lyricDecode.ts'
|
import { handleKwDecodeLyric } from './lyricDecode.ts'
|
||||||
import { createCipheriv, createDecipheriv } from 'crypto'
|
import { createCipheriv, createDecipheriv } from 'crypto'
|
||||||
import { toMD5 } from '../utils.ts'
|
import { toMD5 } from '../utils.ts'
|
||||||
|
|
||||||
// const kw_token = {
|
|
||||||
// token: null,
|
|
||||||
// isGetingToken: false,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const translationMap = {
|
|
||||||
// "{'": '{"',
|
|
||||||
// "'}\n": '"}',
|
|
||||||
// "'}": '"}',
|
|
||||||
// "':'": '":"',
|
|
||||||
// "','": '","',
|
|
||||||
// "':{'": '":{"',
|
|
||||||
// "':['": '":["',
|
|
||||||
// "'}],'": '"}],"',
|
|
||||||
// "':[{'": '":[{"',
|
|
||||||
// "'},'": '"},"',
|
|
||||||
// "'},{'": '"},{"',
|
|
||||||
// "':[],'": '":[],"',
|
|
||||||
// "':{},'": '":{},"',
|
|
||||||
// "'}]}": '"}]}',
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export const objStr2JSON = str => {
|
|
||||||
// return JSON.parse(str.replace(/(^{'|'}\n$|'}$|':'|','|':\[{'|'}\],'|':{'|'},'|'},{'|':\['|':\[\],'|':{},'|'}]})/g, s => translationMap[s]))
|
|
||||||
// }
|
|
||||||
|
|
||||||
export const objStr2JSON = (str: string): any => {
|
export const objStr2JSON = (str: string): any => {
|
||||||
return JSON.parse(str.replace(/('(?=(,\s*')))|('(?=:))|((?<=([:,]\s*))')|((?<={)')|('(?=}))/g, '"'))
|
return JSON.parse(str.replace(/('(?=(,\s*')))|('(?=:))|((?<=([:,]\s*))')|((?<={)')|('(?=}))/g, '"'))
|
||||||
@@ -44,46 +18,9 @@ export const matchToken = (headers: Record<string, any>): string | null => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
|
|
||||||
|
|
||||||
|
|
||||||
// export const getToken = (retryNum = 0) => new Promise((resolve, reject) => {
|
|
||||||
// if (retryNum > 2) return Promise.reject(new Error('try max num'))
|
|
||||||
|
|
||||||
// if (kw_token.isGetingToken) return wait(1000).then(() => getToken(retryNum).then(token => resolve(token)))
|
|
||||||
// if (kw_token.token) return resolve(kw_token.token)
|
|
||||||
// kw_token.isGetingToken = true
|
|
||||||
// httpGet('http://www.kuwo.cn/', (err, resp) => {
|
|
||||||
// kw_token.isGetingToken = false
|
|
||||||
// if (err) return getToken(++retryNum)
|
|
||||||
// if (resp.statusCode != 200) return reject(new Error('获取失败'))
|
|
||||||
// const token = kw_token.token = matchToken(resp.headers)
|
|
||||||
// resolve(token)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
|
|
||||||
export const decodeLyric = handleKwDecodeLyric
|
export const decodeLyric = handleKwDecodeLyric
|
||||||
|
|
||||||
// export const tokenRequest = async(url, options = {}) => {
|
|
||||||
// let token = kw_token.token
|
|
||||||
// if (!token) token = await getToken()
|
|
||||||
// if (!options.headers) {
|
|
||||||
// options.headers = {
|
|
||||||
// Referer: 'http://www.kuwo.cn/',
|
|
||||||
// csrf: token,
|
|
||||||
// cookie: 'kw_token=' + token,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// const requestObj = httpFetch(url, options)
|
|
||||||
// requestObj.promise = requestObj.promise.then(resp => {
|
|
||||||
// // console.log(resp)
|
|
||||||
// if (resp.statusCode == 200) {
|
|
||||||
// kw_token.token = matchToken(resp.headers)
|
|
||||||
// }
|
|
||||||
// return resp
|
|
||||||
// })
|
|
||||||
// return requestObj
|
|
||||||
// }
|
|
||||||
|
|
||||||
interface LrcWordInfo {
|
interface LrcWordInfo {
|
||||||
startTime: number
|
startTime: number
|
||||||
@@ -117,7 +54,6 @@ export const lrcTools = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prevWord.newTimeStr = `<${prevWord.startTime},${prevWord.endTime - prevWord.startTime}>`
|
prevWord.newTimeStr = `<${prevWord.startTime},${prevWord.endTime - prevWord.startTime}>`
|
||||||
// console.log(prevWord)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -137,7 +73,6 @@ export const lrcTools = {
|
|||||||
}
|
}
|
||||||
const wordTimes = words.match(this.rxps.wordTimeAll)
|
const wordTimes = words.match(this.rxps.wordTimeAll)
|
||||||
if (!wordTimes) return
|
if (!wordTimes) return
|
||||||
// console.log(wordTimes)
|
|
||||||
let preTimeInfo
|
let preTimeInfo
|
||||||
for (const timeStr of wordTimes) {
|
for (const timeStr of wordTimes) {
|
||||||
const result = this.rxps.wordTime.exec(timeStr)
|
const result = this.rxps.wordTime.exec(timeStr)
|
||||||
@@ -167,7 +102,6 @@ export const lrcTools = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
parse(lrc: string): string {
|
parse(lrc: string): string {
|
||||||
// console.log(lrc)
|
|
||||||
const lines = lrc.split(/\r\n|\r|\n/)
|
const lines = lrc.split(/\r\n|\r|\n/)
|
||||||
const tools = Object.create(this)
|
const tools = Object.create(this)
|
||||||
tools.isOK = true
|
tools.isOK = true
|
||||||
@@ -183,7 +117,6 @@ export const lrcTools = {
|
|||||||
if (!tools.lines.length) return ''
|
if (!tools.lines.length) return ''
|
||||||
let lrcs = tools.lines.join('\n')
|
let lrcs = tools.lines.join('\n')
|
||||||
if (tools.tags.length) lrcs = `${tools.tags.join('\n')}\n${lrcs}`
|
if (tools.tags.length) lrcs = `${tools.tags.join('\n')}\n${lrcs}`
|
||||||
// console.log(lrcs)
|
|
||||||
return lrcs
|
return lrcs
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
// 端点:/yyting/search/searchBatch.action(q=2150)
|
// 端点:/yyting/search/searchBatch.action(q=2150)
|
||||||
// 注意:章节/取流 App 端点(q=1930/2224)游客 token 已失效(482 请登录),取流走 web 通道(见 apis/lr.ts)
|
// 注意:章节/取流 App 端点(q=1930/2224)游客 token 已失效(482 请登录),取流走 web 通道(见 apis/lr.ts)
|
||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
|
|
||||||
const DOMAIN = 'http://dapis.mting.info'
|
const DOMAIN = 'http://dapis.mting.info'
|
||||||
const SECRET = 'iJ0DgxmdC83#I&j@iwg'
|
const SECRET = 'iJ0DgxmdC83#I&j@iwg'
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
// 参考社区下载器(yoval/lrts 等):POST form {account, pwd: base64(password)},
|
// 参考社区下载器(yoval/lrts 等):POST form {account, pwd: base64(password)},
|
||||||
// 成功响应 json.phone 非空,Set-Cookie 种下会话;取流接口必须携带该会话
|
// 成功响应 json.phone 非空,Set-Cookie 种下会话;取流接口必须携带该会话
|
||||||
// (免费章节匿名也返回"收费章节未购买",2026-10-02 实测)。
|
// (免费章节匿名也返回"收费章节未购买",2026-10-02 实测)。
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { getCredentials, setCredentials } from '../auth/store.ts'
|
import { getCredentials, setCredentials } from '../auth/store.ts'
|
||||||
|
|
||||||
const UA_MI = 'Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; MI2 Build/JRO03L) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 XiaoMi/MiuiBrowser/1.0'
|
const UA_MI = 'Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; MI2 Build/JRO03L) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 XiaoMi/MiuiBrowser/1.0'
|
||||||
|
|||||||
+1
-3
@@ -5,15 +5,13 @@
|
|||||||
import lrApi from '../apis/lr.ts'
|
import lrApi from '../apis/lr.ts'
|
||||||
import musicSearch from './musicSearch.ts'
|
import musicSearch from './musicSearch.ts'
|
||||||
import { getBookMenu } from './menu.ts'
|
import { getBookMenu } from './menu.ts'
|
||||||
import { getLrCookie } from './auth.ts'
|
|
||||||
|
|
||||||
const lr = {
|
const lr = {
|
||||||
musicSearch,
|
musicSearch,
|
||||||
getMusicUrl(songInfo, type) {
|
getMusicUrl(songInfo, type) {
|
||||||
return lrApi.getMusicUrl(songInfo, type)
|
return lrApi.getMusicUrl(songInfo, type)
|
||||||
},
|
},
|
||||||
getPic(songInfo) {
|
getPic() {
|
||||||
const bookId = String(songInfo.songmid ?? '').split(':')[0]
|
|
||||||
// 书籍封面无独立免签接口;搜索结果的 img 字段已带封面(这里返回空由调用方回退)
|
// 书籍封面无独立免签接口;搜索结果的 img 字段已带封面(这里返回空由调用方回退)
|
||||||
return Promise.resolve(null)
|
return Promise.resolve(null)
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
// 接口: https://m.lrts.me/ajax/getBookMenu?bookId=&pageNum=&pageSize=&sortType=0
|
// 接口: https://m.lrts.me/ajax/getBookMenu?bookId=&pageNum=&pageSize=&sortType=0
|
||||||
// 实测(2026-10-02)匿名可用;章节条目 {section, name, listenPrice, state}
|
// 实测(2026-10-02)匿名可用;章节条目 {section, name, listenPrice, state}
|
||||||
// 注意:专辑类内容用 getAlbumAudios(entityType=2),本实现仅收书(entityType=3)
|
// 注意:专辑类内容用 getAlbumAudios(entityType=2),本实现仅收书(entityType=3)
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
|
|
||||||
const UA_MI = 'Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; MI2 Build/JRO03L) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 XiaoMi/MiuiBrowser/1.0'
|
const UA_MI = 'Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; MI2 Build/JRO03L) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 XiaoMi/MiuiBrowser/1.0'
|
||||||
const MENU_URL = 'https://m.lrts.me/ajax/getBookMenu'
|
const MENU_URL = 'https://m.lrts.me/ajax/getBookMenu'
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
// 接口: https://www.missevan.com/dramaapi/getdramaepisodes?drama_id=&season=&page=&order=0&pageSize=
|
// 接口: https://www.missevan.com/dramaapi/getdramaepisodes?drama_id=&season=&page=&order=0&pageSize=
|
||||||
// 结果在 info.episodes.episode[](name/sound_id/pay_type/vip);剧名在 info.drama.name
|
// 结果在 info.episodes.episode[](name/sound_id/pay_type/vip);剧名在 info.drama.name
|
||||||
// 注意:getdrama(不带 type=2 时)的 info.episodes.episode 同样可用(MusicFree 插件用法),本接口支持翻页故用之
|
// 注意:getdrama(不带 type=2 时)的 info.episodes.episode 同样可用(MusicFree 插件用法),本接口支持翻页故用之
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime } from '../index.ts'
|
import { formatPlayTime } from '../index.ts'
|
||||||
|
|
||||||
const EPISODES_URL = 'https://www.missevan.com/dramaapi/getdramaepisodes'
|
const EPISODES_URL = 'https://www.missevan.com/dramaapi/getdramaepisodes'
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// ——网页版搜索排前面的正剧来自这里;getsearch 的 type 参数只有 1UP主/2音单/3声音/4声优,没有剧集类型
|
// ——网页版搜索排前面的正剧来自这里;getsearch 的 type 参数只有 1UP主/2音单/3声音/4声优,没有剧集类型
|
||||||
// 声音(补足): https://www.missevan.com/sound/getsearch?s=&p=&type=3&page_size=&sort=2 → info.Datas[]
|
// 声音(补足): https://www.missevan.com/sound/getsearch?s=&p=&type=3&page_size=&sort=2 → info.Datas[]
|
||||||
// 剧条目 songmid 用 "d:<drama_id>" 前缀(取流时自动取首个免费集,见 apis/me.ts);声音条目为裸 sound_id
|
// 剧条目 songmid 用 "d:<drama_id>" 前缀(取流时自动取首个免费集,见 apis/me.ts);声音条目为裸 sound_id
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime } from '../index.ts'
|
import { formatPlayTime } from '../index.ts'
|
||||||
|
|
||||||
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
|
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
// 该通道返回的是免签名 CDN 明文直链(sound-ali*-cdn-cn.maoercdn.com,m4a 原文件/-128k 变体);
|
// 该通道返回的是免签名 CDN 明文直链(sound-ali*-cdn-cn.maoercdn.com,m4a 原文件/-128k 变体);
|
||||||
// 网页版 hls.m3u8 为 Widevine 加密,与此无关。
|
// 网页版 hls.m3u8 为 Widevine 加密,与此无关。
|
||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { getCredentials } from '../auth/store.ts'
|
import { getCredentials } from '../auth/store.ts'
|
||||||
|
|
||||||
const APP_SECRET = 'f8Yw1M3u0e5MV7z34PcbY9wgP56YwJ'
|
const APP_SECRET = 'f8Yw1M3u0e5MV7z34PcbY9wgP56YwJ'
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
// 咪咕歌词 —— resourceinfo.do 取 lrcUrl/lyricUrl 再下载文本
|
// 咪咕歌词 —— resourceinfo.do 取 lrcUrl/lyricUrl 再下载文本
|
||||||
// 上游对应:music-lib/migu/lyric.go
|
// 上游对应:music-lib/migu/lyric.go
|
||||||
import { buildUrl, fetchSongDetail, miguGet, UserAgent } from './util.ts'
|
import { buildUrl, fetchSongDetail, miguGet } from './util.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
|
|
||||||
const LRC_REFERER = 'https://y.migu.cn/'
|
const LRC_REFERER = 'https://y.migu.cn/'
|
||||||
const LRC_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36'
|
const LRC_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36'
|
||||||
|
|||||||
+1
-2
@@ -2,7 +2,7 @@
|
|||||||
// 上游对应:music-lib/migu/migu.go(convertItemToSong*、collectMiguFormats、pickMiguImage 等)
|
// 上游对应:music-lib/migu/migu.go(convertItemToSong*、collectMiguFormats、pickMiguImage 等)
|
||||||
// 说明:匿名态 listen-url 仅放行 PQ(128k mp3);HQ/SQ/ZQ 各档需携带登录 Cookie(对话框「登录后免费试听」)。
|
// 说明:匿名态 listen-url 仅放行 PQ(128k mp3);HQ/SQ/ZQ 各档需携带登录 Cookie(对话框「登录后免费试听」)。
|
||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { getCredentials } from '../auth/store.ts'
|
import { getCredentials } from '../auth/store.ts'
|
||||||
import type { SongType } from '../types.ts'
|
import type { SongType } from '../types.ts'
|
||||||
|
|
||||||
@@ -328,7 +328,6 @@ export const convertItemToSong = (item: MiguSongItem, allowPaid = true): MiguSon
|
|||||||
const best = candidates[0]!
|
const best = candidates[0]!
|
||||||
const bestFormat = rateFormats[best.index]!
|
const bestFormat = rateFormats[best.index]!
|
||||||
|
|
||||||
const bitrate = duration > 0 && best.size > 0 ? Math.floor(best.size * 8 / 1000 / duration) : 0
|
|
||||||
const coverURL = normalizeMiguImageURL(firstNonEmpty(
|
const coverURL = normalizeMiguImageURL(firstNonEmpty(
|
||||||
pickMiguImage(item.imgItems), pickMiguImage(item.albumImgs), item.img1, item.img2, item.img3,
|
pickMiguImage(item.imgItems), pickMiguImage(item.albumImgs), item.img1, item.img2, item.img3,
|
||||||
))
|
))
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
// 千千音乐歌词
|
// 千千音乐歌词
|
||||||
// 链路:/v1/song/info → data[0].lyric 为歌词文本 URL(static-qianqian.taihe.com/*.txt)→ 直接下载文本。
|
// 链路:/v1/song/info → data[0].lyric 为歌词文本 URL(static-qianqian.taihe.com/*.txt)→ 直接下载文本。
|
||||||
// 返回 { lyric, tlyric: '' }(千千只有原文歌词,无翻译)。
|
// 返回 { lyric, tlyric: '' }(千千只有原文歌词,无翻译)。
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { getSongInfo } from './musicInfo.ts'
|
import { getSongInfo } from './musicInfo.ts'
|
||||||
import { requestHeaders } from './util.ts'
|
import { requestHeaders } from './util.ts'
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
// 该平台匿名即可搜索 / 查专辑 / 查歌单 / 拿免费曲目直链;cookie 仅影响 VIP、收藏等登录态资源,
|
// 该平台匿名即可搜索 / 查专辑 / 查歌单 / 拿免费曲目直链;cookie 仅影响 VIP、收藏等登录态资源,
|
||||||
// MusicServer 从 sdk/auth/store.ts 的 getCredentials('bd') 读取(可选,未登录留空)。
|
// MusicServer 从 sdk/auth/store.ts 的 getCredentials('bd') 读取(可选,未登录留空)。
|
||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { getCredentials } from '../auth/store.ts'
|
import { getCredentials } from '../auth/store.ts'
|
||||||
|
|
||||||
export const APP_ID = '16073360'
|
export const APP_ID = '16073360'
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
// 逐字歌词(lyric.content)与试听播放信息(track_player.url_player_info / video_model)
|
// 逐字歌词(lyric.content)与试听播放信息(track_player.url_player_info / video_model)
|
||||||
// 注意:seo_track 属分享页/SEO 数据面,track_player 里只有 30 秒试听资源;
|
// 注意:seo_track 属分享页/SEO 数据面,track_player 里只有 30 秒试听资源;
|
||||||
// 完整音频需真机凭证走 /luna/pc/track_v2,且音频经 CENC 加密,见 apis/qs.ts 头注释
|
// 完整音频需真机凭证走 /luna/pc/track_v2,且音频经 CENC 加密,见 apis/qs.ts 头注释
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import type { RequestObj } from '../types.ts'
|
import type { RequestObj } from '../types.ts'
|
||||||
|
|
||||||
export default (trackId: string): RequestObj<any> => {
|
export default (trackId: string): RequestObj<any> => {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
// 蜻蜓FM SSR 请求助手 —— 域名迁移(www.qingting.fm → www.qtfm.cn)伴随华为 WAF 会话,
|
// 蜻蜓FM SSR 请求助手 —— 域名迁移(www.qingting.fm → www.qtfm.cn)伴随华为 WAF 会话,
|
||||||
// 302 时需带上 Set-Cookie 的 HWWAFSESID/HWWAFSESTIME 再请求 Location,否则可能被 WAF 拦
|
// 302 时需带上 Set-Cookie 的 HWWAFSESID/HWWAFSESTIME 再请求 Location,否则可能被 WAF 拦
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
|
|
||||||
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
|
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
// SDK 根请求模块 —— 唯一实现在 common/request.ts,此处仅转发
|
|
||||||
// sdk 内 `import { ... } from '../request.ts'` 的解析目标
|
|
||||||
|
|
||||||
export { httpFetch } from './common/request.ts'
|
|
||||||
export type { HttpOptions, HttpRequestObject, HttpFetchResponse } from './common/request.ts'
|
|
||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
// 曲目:music.musichallAlbum.AlbumSongList GetAlbumSongList 按 100 一批翻页(上游 fetchAlbumDetail 的曲目段)
|
// 曲目:music.musichallAlbum.AlbumSongList GetAlbumSongList 按 100 一批翻页(上游 fetchAlbumDetail 的曲目段)
|
||||||
// 详情:GetAlbumInfo 详情接口已停用:专辑名取自曲目自带的 album 信息,作者由曲目歌手去重合成,封面走平台固定 URL 规则
|
// 详情:GetAlbumInfo 详情接口已停用:专辑名取自曲目自带的 album 信息,作者由曲目歌手去重合成,封面走平台固定 URL 规则
|
||||||
// 链接解析:albumDetail/<mid>、album/<mid>、albummid=<mid>(上游 ParseAlbum)
|
// 链接解析:albumDetail/<mid>、album/<mid>、albummid=<mid>(上游 ParseAlbum)
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { filterMusicInfoItem } from './singer.ts'
|
import { filterMusicInfoItem } from './singer.ts'
|
||||||
import { createMusicuFetch } from './utils/index.ts'
|
import { createMusicuFetch } from './utils/index.ts'
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -1,5 +1,5 @@
|
|||||||
import type { HttpRequestObject } from '../types.ts'
|
import type { HttpRequestObject } from '../types.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { dateFormat2 } from '../index.ts'
|
import { dateFormat2 } from '../index.ts'
|
||||||
import getMusicInfo from './musicInfo.ts'
|
import getMusicInfo from './musicInfo.ts'
|
||||||
|
|
||||||
@@ -114,7 +114,6 @@ export default {
|
|||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj.promise
|
const { body, statusCode } = await _requestObj.promise
|
||||||
if (statusCode != 200 || body.code !== 0) throw new Error('获取评论失败')
|
if (statusCode != 200 || body.code !== 0) throw new Error('获取评论失败')
|
||||||
// console.log(body, statusCode)
|
|
||||||
const comment = body.comment
|
const comment = body.comment
|
||||||
return {
|
return {
|
||||||
source: 'tx',
|
source: 'tx',
|
||||||
@@ -183,7 +182,6 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj2.promise
|
const { body, statusCode } = await _requestObj2.promise
|
||||||
// console.log('body', body)
|
|
||||||
if (statusCode != 200 || body.code !== 0 || body.req.code !== 0) throw new Error('获取热门评论失败')
|
if (statusCode != 200 || body.code !== 0 || body.req.code !== 0) throw new Error('获取热门评论失败')
|
||||||
const comment = body.req.data.CommentList
|
const comment = body.req.data.CommentList
|
||||||
return {
|
return {
|
||||||
|
|||||||
+1
-3
@@ -1,5 +1,5 @@
|
|||||||
import type { HttpRequestObject } from '../types.ts'
|
import type { HttpRequestObject } from '../types.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
_requestObj: null as HttpRequestObject | null,
|
_requestObj: null as HttpRequestObject | null,
|
||||||
@@ -44,9 +44,7 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj.promise
|
const { body, statusCode } = await _requestObj.promise
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.code !== 0) throw new Error('获取热搜词失败')
|
if (statusCode != 200 || body.code !== 0) throw new Error('获取热搜词失败')
|
||||||
// console.log(body)
|
|
||||||
return { source: 'tx', list: this.filterList(body.hotkey.data.vec_hotkey) }
|
return { source: 'tx', list: this.filterList(body.hotkey.data.vec_hotkey) }
|
||||||
},
|
},
|
||||||
filterList(rawList) {
|
filterList(rawList) {
|
||||||
|
|||||||
+3
-23
@@ -1,5 +1,5 @@
|
|||||||
import type { HttpRequestObject, SongInfo, SongType, Quality, QualityInfo } from '../types.ts'
|
import type { HttpRequestObject, SongType, Quality, QualityInfo } from '../types.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime, sizeFormate } from '../index.ts'
|
import { formatPlayTime, sizeFormate } from '../index.ts'
|
||||||
import { formatSingerName } from '../utils.ts'
|
import { formatSingerName } from '../utils.ts'
|
||||||
|
|
||||||
@@ -65,7 +65,6 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
listDetailRequest(id, period, limit) {
|
listDetailRequest(id, period, limit) {
|
||||||
// console.log(id, period, limit)
|
|
||||||
return httpFetch('https://u.y.qq.com/cgi-bin/musicu.fcg', {
|
return httpFetch('https://u.y.qq.com/cgi-bin/musicu.fcg', {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -107,7 +106,6 @@ export default {
|
|||||||
return requestDataObj.promise
|
return requestDataObj.promise
|
||||||
},
|
},
|
||||||
filterData(rawList) {
|
filterData(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
return rawList.map(item => {
|
return rawList.map(item => {
|
||||||
let types: SongType[] = []
|
let types: SongType[] = []
|
||||||
let _types: Partial<Record<Quality, QualityInfo>> = {}
|
let _types: Partial<Record<Quality, QualityInfo>> = {}
|
||||||
@@ -180,7 +178,6 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
filterBoardsData(rawList) {
|
filterBoardsData(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
let list: any[] = []
|
let list: any[] = []
|
||||||
for (const board of rawList) {
|
for (const board of rawList) {
|
||||||
// 排除 MV榜
|
// 排除 MV榜
|
||||||
@@ -198,24 +195,7 @@ export default {
|
|||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
},
|
},
|
||||||
async getBoards(retryNum = 0) {
|
async getBoards() {
|
||||||
// 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 !== 0) return this.getBoards(retryNum)
|
|
||||||
// const list = this.filterBoardsData(response.body.data.topList)
|
|
||||||
// console.log(list)
|
|
||||||
// console.log(JSON.stringify(list))
|
|
||||||
// this.list = list
|
|
||||||
// return {
|
|
||||||
// list,
|
|
||||||
// source: 'tx',
|
|
||||||
// }
|
|
||||||
this.list = boardList
|
this.list = boardList
|
||||||
return {
|
return {
|
||||||
list: boardList,
|
list: boardList,
|
||||||
|
|||||||
+1
-35
@@ -1,5 +1,4 @@
|
|||||||
import type { HttpRequestObject } from '../types.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
|
||||||
import getMusicInfo from './musicInfo.ts'
|
import getMusicInfo from './musicInfo.ts'
|
||||||
import { decodeQrc } from './qrcDecode.ts'
|
import { decodeQrc } from './qrcDecode.ts'
|
||||||
|
|
||||||
@@ -111,8 +110,6 @@ const parseTools = {
|
|||||||
return parseInt(m) * 3600000 + parseInt(s) * 1000 + parseInt(ms)
|
return parseInt(m) * 3600000 + parseInt(s) * 1000 + parseInt(ms)
|
||||||
},
|
},
|
||||||
fixRlrcTimeTag(rlrc, lrc) {
|
fixRlrcTimeTag(rlrc, lrc) {
|
||||||
// console.log(lrc)
|
|
||||||
// console.log(rlrc)
|
|
||||||
const rlrcLines = rlrc.split('\n')
|
const rlrcLines = rlrc.split('\n')
|
||||||
let lrcLines = lrc.split('\n')
|
let lrcLines = lrc.split('\n')
|
||||||
// let temp: string[] = []
|
// let temp: string[] = []
|
||||||
@@ -141,8 +138,6 @@ const parseTools = {
|
|||||||
return newLrc.join('\n')
|
return newLrc.join('\n')
|
||||||
},
|
},
|
||||||
fixTlrcTimeTag(tlrc, lrc) {
|
fixTlrcTimeTag(tlrc, lrc) {
|
||||||
// console.log(lrc)
|
|
||||||
// console.log(tlrc)
|
|
||||||
const tlrcLines = tlrc.split('\n')
|
const tlrcLines = tlrc.split('\n')
|
||||||
let lrcLines = lrc.split('\n')
|
let lrcLines = lrc.split('\n')
|
||||||
// let temp: string[] = []
|
// let temp: string[] = []
|
||||||
@@ -185,14 +180,9 @@ const parseTools = {
|
|||||||
let { lyric, yrc } = this.parseLyric(this.removeTag(lrc))
|
let { lyric, yrc } = this.parseLyric(this.removeTag(lrc))
|
||||||
info.lyric = lyric
|
info.lyric = lyric
|
||||||
info.yrc = yrc
|
info.yrc = yrc
|
||||||
// console.log(lyric)
|
|
||||||
// console.log(yrc)
|
|
||||||
}
|
}
|
||||||
if (rlrc) info.rlyric = this.fixRlrcTimeTag(this.parseRlyric(this.removeTag(rlrc)), info.lyric)
|
if (rlrc) info.rlyric = this.fixRlrcTimeTag(this.parseRlyric(this.removeTag(rlrc)), info.lyric)
|
||||||
if (tlrc) info.tlyric = this.fixTlrcTimeTag(tlrc, info.lyric)
|
if (tlrc) info.tlyric = this.fixTlrcTimeTag(tlrc, info.lyric)
|
||||||
// console.log(info.lyric)
|
|
||||||
// console.log(info.tlyric)
|
|
||||||
// console.log(info.rlyric)
|
|
||||||
|
|
||||||
return info
|
return info
|
||||||
},
|
},
|
||||||
@@ -218,9 +208,6 @@ export default {
|
|||||||
// return {
|
// return {
|
||||||
|
|
||||||
// }
|
// }
|
||||||
// console.log(lyric)
|
|
||||||
// console.log(tlyric)
|
|
||||||
// console.log(rlyric)
|
|
||||||
return parseTools.parse(lyric, tlyric, rlyric)
|
return parseTools.parse(lyric, tlyric, rlyric)
|
||||||
},
|
},
|
||||||
getLyric(mInfo, retryNum = 0) {
|
getLyric(mInfo, retryNum = 0) {
|
||||||
@@ -266,7 +253,6 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
return requestObj.promise.then(({ body }) => {
|
return requestObj.promise.then(({ body }) => {
|
||||||
// console.log(body)
|
|
||||||
if (body.code != this.successCode || body.req.code != this.successCode) return this.getLyric(songId, ++retryNum)
|
if (body.code != this.successCode || body.req.code != this.successCode) return this.getLyric(songId, ++retryNum)
|
||||||
const data = body.req.data
|
const data = body.req.data
|
||||||
return this.parseLyric(data.lyric, data.trans, data.roma)
|
return this.parseLyric(data.lyric, data.trans, data.roma)
|
||||||
@@ -276,23 +262,3 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// export default {
|
|
||||||
// regexps: {
|
|
||||||
// matchLrc: /.+"lyric":"([\w=+/]*)".+/,
|
|
||||||
// },
|
|
||||||
// getLyric(songmid) {
|
|
||||||
// const requestObj = httpFetch(`https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=${songmid}&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&platform=yqq`, {
|
|
||||||
// headers: {
|
|
||||||
// Referer: 'https://y.qq.com/portal/player.html',
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// requestObj.promise = requestObj.promise.then(({ body }) => {
|
|
||||||
// if (body.code != 0 || !body.lyric) return Promise.reject(new Error('Get lyric failed'))
|
|
||||||
// return {
|
|
||||||
// lyric: decodeName(b64DecodeUnicode(body.lyric)),
|
|
||||||
// tlyric: decodeName(b64DecodeUnicode(body.trans)),
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// return requestObj
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
+2
-3
@@ -1,5 +1,5 @@
|
|||||||
import type { HttpRequestObject, SongType, Quality, QualityInfo } from '../types.ts'
|
import type { SongType, Quality, QualityInfo } from '../types.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime, sizeFormate } from '../index.ts'
|
import { formatPlayTime, sizeFormate } from '../index.ts'
|
||||||
|
|
||||||
const getSinger = (singers) => {
|
const getSinger = (singers) => {
|
||||||
@@ -33,7 +33,6 @@ export default (songmid) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
return requestObj.promise.then(({ body }) => {
|
return requestObj.promise.then(({ body }) => {
|
||||||
// console.log(body)
|
|
||||||
if (body.code != 0 || body.req.code != 0) return Promise.reject(new Error('获取歌曲信息失败'))
|
if (body.code != 0 || body.req.code != 0) return Promise.reject(new Error('获取歌曲信息失败'))
|
||||||
const item = body.req.data.track_info
|
const item = body.req.data.track_info
|
||||||
if (!item.file?.media_mid) return null
|
if (!item.file?.media_mid) return null
|
||||||
|
|||||||
@@ -61,26 +61,13 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
return searchRequest.then(({ body }) => {
|
return searchRequest.then(({ body }) => {
|
||||||
// console.log(body)
|
|
||||||
if (!body || !body.req || body.code != this.successCode || body.req.code != this.successCode) {
|
if (!body || !body.req || body.code != this.successCode || body.req.code != this.successCode) {
|
||||||
return this.musicSearch(str, page, limit, ++retryNum)
|
return this.musicSearch(str, page, limit, ++retryNum)
|
||||||
}
|
}
|
||||||
return body.req.data
|
return body.req.data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// randomInt(min, max) {
|
|
||||||
// return Math.floor(Math.random() * (max - min + 1)) + min
|
|
||||||
// },
|
|
||||||
// getSearchId() {
|
|
||||||
// const e = BigInt(this.randomInt(1, 20))
|
|
||||||
// const t = e * 18014398509481984n
|
|
||||||
// const n = BigInt(this.randomInt(0, 4194304)) * 4294967296n
|
|
||||||
// const a = BigInt(Date.now())
|
|
||||||
// const r = (a * 1000n) % (24n * 60n * 60n * 1000n)
|
|
||||||
// return String(t + n + r)
|
|
||||||
// },
|
|
||||||
handleResult(rawList) {
|
handleResult(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
if (!rawList || !Array.isArray(rawList)) return []
|
if (!rawList || !Array.isArray(rawList)) return []
|
||||||
const list: SongInfo[] = []
|
const list: SongInfo[] = []
|
||||||
rawList.forEach(item => {
|
rawList.forEach(item => {
|
||||||
@@ -144,7 +131,6 @@ export default {
|
|||||||
typeUrl: {},
|
typeUrl: {},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// console.log(list)
|
|
||||||
return list
|
return list
|
||||||
},
|
},
|
||||||
search(str, page = 1, limit) {
|
search(str, page = 1, limit) {
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
// https://i.y.qq.com/v8/playsong.html?songid=<数字 id>(先反查 songmid)
|
// https://i.y.qq.com/v8/playsong.html?songid=<数字 id>(先反查 songmid)
|
||||||
// ...?songmid=<songmid>
|
// ...?songmid=<songmid>
|
||||||
// 返回 Song(含 songmid);直链 url/type 为尽力附带,失败不影响元数据返回(与上游一致)。
|
// 返回 Song(含 songmid);直链 url/type 为尽力附带,失败不影响元数据返回(与上游一致)。
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime, sizeFormate } from '../index.ts'
|
import { formatPlayTime, sizeFormate } from '../index.ts'
|
||||||
import { formatSingerName } from '../utils.ts'
|
import { formatSingerName } from '../utils.ts'
|
||||||
import txApi from '../apis/tx.ts'
|
import txApi from '../apis/tx.ts'
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
// 搜索/分类复用现有 sdk/tx/songList.ts(handleParseId / getListId / getListDetail / getTag / getList)
|
// 搜索/分类复用现有 sdk/tx/songList.ts(handleParseId / getListId / getListDetail / getTag / getList)
|
||||||
// 推荐:musicu.fcg playlist.HotRecommendServer/get_hot_recommend(上游 GetRecommendedPlaylists)
|
// 推荐:musicu.fcg playlist.HotRecommendServer/get_hot_recommend(上游 GetRecommendedPlaylists)
|
||||||
// 链接解析:extractPlaylistId 为上游 extractQQPlaylistID 的 1:1 移植(含单测用例断言)
|
// 链接解析:extractPlaylistId 为上游 extractQQPlaylistID 的 1:1 移植(含单测用例断言)
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayCount } from '../index.ts'
|
import { formatPlayCount } from '../index.ts'
|
||||||
import songList from './songList.ts'
|
import songList from './songList.ts'
|
||||||
|
|
||||||
|
|||||||
+1
-7
@@ -1,5 +1,5 @@
|
|||||||
import type { HttpRequestObject, SongType, Quality, QualityInfo } from '../types.ts'
|
import type { HttpRequestObject, SongType, Quality, QualityInfo } from '../types.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
|
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
|
||||||
import { formatSingerName } from '../utils.ts'
|
import { formatSingerName } from '../utils.ts'
|
||||||
|
|
||||||
@@ -120,7 +120,6 @@ export default {
|
|||||||
this._requestObj_list = httpFetch(
|
this._requestObj_list = httpFetch(
|
||||||
this.getListUrl(sortId, tagId, page),
|
this.getListUrl(sortId, tagId, page),
|
||||||
)
|
)
|
||||||
// console.log(this.getListUrl(sortId, tagId, page))
|
|
||||||
return this._requestObj_list!.promise.then(({ body }) => {
|
return this._requestObj_list!.promise.then(({ body }) => {
|
||||||
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
||||||
return tagId ? this.filterList2(body.playlist.data, page) : this.filterList(body.playlist.data, page)
|
return tagId ? this.filterList2(body.playlist.data, page) : this.filterList(body.playlist.data, page)
|
||||||
@@ -148,7 +147,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
filterList2({ content }, page) {
|
filterList2({ content }, page) {
|
||||||
// console.log(content.v_item)
|
|
||||||
return {
|
return {
|
||||||
list: content.v_item.map(({ basic }) => ({
|
list: content.v_item.map(({ basic }) => ({
|
||||||
play_count: formatPlayCount(basic.play_cnt),
|
play_count: formatPlayCount(basic.play_cnt),
|
||||||
@@ -173,7 +171,6 @@ export default {
|
|||||||
|
|
||||||
const requestObj_listDetailLink = httpFetch(link)
|
const requestObj_listDetailLink = httpFetch(link)
|
||||||
const { headers: { location }, statusCode } = await requestObj_listDetailLink.promise
|
const { headers: { location }, statusCode } = await requestObj_listDetailLink.promise
|
||||||
// console.log(headers)
|
|
||||||
if (statusCode > 400) return this.handleParseId(link, ++retryNum)
|
if (statusCode > 400) return this.handleParseId(link, ++retryNum)
|
||||||
return location == null ? link : location
|
return location == null ? link : location
|
||||||
},
|
},
|
||||||
@@ -189,7 +186,6 @@ export default {
|
|||||||
if (!result) throw new Error('failed')
|
if (!result) throw new Error('failed')
|
||||||
}
|
}
|
||||||
id = result[1]
|
id = result[1]
|
||||||
// console.log(id)
|
|
||||||
}
|
}
|
||||||
return id
|
return id
|
||||||
},
|
},
|
||||||
@@ -225,7 +221,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
filterListDetail(rawList) {
|
filterListDetail(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
return rawList.map(item => {
|
return rawList.map(item => {
|
||||||
let types: SongType[] = []
|
let types: SongType[] = []
|
||||||
let _types: Partial<Record<Quality, QualityInfo>> = {}
|
let _types: Partial<Record<Quality, QualityInfo>> = {}
|
||||||
@@ -300,7 +295,6 @@ export default {
|
|||||||
})
|
})
|
||||||
.promise.then(({ body }) => {
|
.promise.then(({ body }) => {
|
||||||
if (body.code != 0) return this.search(text, page, limit, ++retryNum)
|
if (body.code != 0) return this.search(text, page, limit, ++retryNum)
|
||||||
// console.log(body.data.list)
|
|
||||||
return {
|
return {
|
||||||
list: body.data.list.map(item => {
|
list: body.data.list.map(item => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// 2) fcg_get_profile_order_asset reqtype=3 → 收藏歌单
|
// 2) fcg_get_profile_order_asset reqtype=3 → 收藏歌单
|
||||||
// 3) fcg_get_profile_order_asset reqtype=1 → 「我喜欢的歌曲」虚拟歌单摘要(有歌才附上)
|
// 3) fcg_get_profile_order_asset reqtype=1 → 「我喜欢的歌曲」虚拟歌单摘要(有歌才附上)
|
||||||
// 说明:虚拟条目的 id 为 profile:favorites(与上游一致),歌曲需凭 cookie 走同一接口拉取
|
// 说明:虚拟条目的 id 为 profile:favorites(与上游一致),歌曲需凭 cookie 走同一接口拉取
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayCount } from '../index.ts'
|
import { formatPlayCount } from '../index.ts'
|
||||||
import { getCredentials } from '../auth/store.ts'
|
import { getCredentials } from '../auth/store.ts'
|
||||||
import type { TxCredentials } from '../auth/store.ts'
|
import type { TxCredentials } from '../auth/store.ts'
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import type { HttpRequestObject } from '../../types.ts'
|
import { httpFetch } from '../../common/request.ts'
|
||||||
|
|
||||||
import { httpFetch } from '../../request.ts'
|
|
||||||
import { zzcSign } from './crypto.ts'
|
import { zzcSign } from './crypto.ts'
|
||||||
|
|
||||||
export const signRequest = async(data) => {
|
export const signRequest = async(data) => {
|
||||||
// console.log(data)
|
|
||||||
const sign = await zzcSign(JSON.stringify(data))
|
const sign = await zzcSign(JSON.stringify(data))
|
||||||
// console.log('sign', sign)
|
|
||||||
return httpFetch(`https://u.y.qq.com/cgi-bin/musics.fcg?sign=${sign}`, {
|
return httpFetch(`https://u.y.qq.com/cgi-bin/musics.fcg?sign=${sign}`, {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -36,7 +32,7 @@ export const createMusicuFetch = async(data, retryNum = 0) => {
|
|||||||
},
|
},
|
||||||
}).promise
|
}).promise
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.warn(`[tx] musicu 请求失败将重试(${retryNum + 1}/3): ${(err as Error).message}`)
|
||||||
return createMusicuFetch(data, ++retryNum)
|
return createMusicuFetch(data, ++retryNum)
|
||||||
}
|
}
|
||||||
if (result.statusCode !== 200 || result.body.code != 0) return createMusicuFetch(data, ++retryNum)
|
if (result.statusCode !== 200 || result.body.code != 0) return createMusicuFetch(data, ++retryNum)
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ export const getHostIp = (hostname: string): CachedIp | undefined => {
|
|||||||
// family: 4,
|
// family: 4,
|
||||||
all: false,
|
all: false,
|
||||||
}, (err, address, family) => {
|
}, (err, address, family) => {
|
||||||
if (err) return console.log(err)
|
if (err) return console.warn(`[utils] DNS 预解析 ${hostname} 失败: ${err.message}`)
|
||||||
ipMap.set(hostname, { address, family })
|
ipMap.set(hostname, { address, family })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
// 搜索:cloudsearch pc type=10(eapi 通道,与现有 songList.search 同一通路)
|
// 搜索:cloudsearch pc type=10(eapi 通道,与现有 songList.search 同一通路)
|
||||||
// 详情/曲目:weapi/v1/album/<id>(匿名可用;登录 cookie 可选)
|
// 详情/曲目:weapi/v1/album/<id>(匿名可用;登录 cookie 可选)
|
||||||
// 链接解析:#/album?id= / /album?id= / /album/<id>(上游 parseNeteaseLink)
|
// 链接解析:#/album?id= / /album?id= / /album/<id>(上游 parseNeteaseLink)
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { weapi } from './utils/crypto.ts'
|
import { weapi } from './utils/crypto.ts'
|
||||||
import { formatPlayTime, sizeFormate } from '../index.ts'
|
import { formatPlayTime, sizeFormate } from '../index.ts'
|
||||||
import { formatSingerName } from '../utils.ts'
|
import { formatSingerName } from '../utils.ts'
|
||||||
|
|||||||
+1
-2
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { weapi } from './utils/crypto.ts'
|
import { weapi } from './utils/crypto.ts'
|
||||||
import { dateFormat2 } from '../index.ts'
|
import { dateFormat2 } from '../index.ts'
|
||||||
import type { HttpRequestObject } from '../types.ts'
|
import type { HttpRequestObject } from '../types.ts'
|
||||||
@@ -149,7 +149,6 @@ export default {
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const { body, statusCode } = await _requestObj.promise
|
const { body, statusCode } = await _requestObj.promise
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.code !== 200) throw new Error('获取评论失败')
|
if (statusCode != 200 || body.code !== 200) throw new Error('获取评论失败')
|
||||||
cursorTools.setCursor(songmid, body.data.cursor, cursorInfo.orderType, cursorInfo.offset, page)
|
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 }
|
return { source: 'wy', comments: this.filterComment(body.data.comments), total: body.data.totalCount, page, limit, maxPage: Math.ceil(body.data.totalCount / limit) || 1 }
|
||||||
|
|||||||
+3
-24
@@ -1,5 +1,5 @@
|
|||||||
import { weapi } from './utils/crypto.ts'
|
import { weapi } from './utils/crypto.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import musicDetailApi from './musicDetail.ts'
|
import musicDetailApi from './musicDetail.ts'
|
||||||
import type { HttpRequestObject } from '../types.ts'
|
import type { HttpRequestObject } from '../types.ts'
|
||||||
|
|
||||||
@@ -129,7 +129,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
filterBoardsData(rawList) {
|
filterBoardsData(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
let list: any[] = []
|
let list: any[] = []
|
||||||
for (const board of rawList) {
|
for (const board of rawList) {
|
||||||
// 排除 MV榜
|
// 排除 MV榜
|
||||||
@@ -142,24 +141,7 @@ export default {
|
|||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
},
|
},
|
||||||
async getBoards(retryNum = 0) {
|
async getBoards() {
|
||||||
// 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
|
this.list = topList
|
||||||
return {
|
return {
|
||||||
list: topList,
|
list: topList,
|
||||||
@@ -168,7 +150,6 @@ export default {
|
|||||||
},
|
},
|
||||||
async getList(bangid, page, retryNum = 0) {
|
async getList(bangid, page, retryNum = 0) {
|
||||||
if (++retryNum > 6) return Promise.reject(new Error('try max num'))
|
if (++retryNum > 6) return Promise.reject(new Error('try max num'))
|
||||||
// console.log(bangid)
|
|
||||||
let resp
|
let resp
|
||||||
try {
|
try {
|
||||||
resp = await this.getData(bangid)
|
resp = await this.getData(bangid)
|
||||||
@@ -180,19 +161,17 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (resp.statusCode !== 200 || resp.body.code !== 200) 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
|
let musicDetail
|
||||||
try {
|
try {
|
||||||
musicDetail = await (musicDetailApi as any).getList(resp.body.playlist.trackIds.map(trackId => trackId.id))
|
musicDetail = await (musicDetailApi as any).getList(resp.body.playlist.trackIds.map(trackId => trackId.id))
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log(err)
|
console.warn(`[wy] 榜单歌曲详情获取失败将重试(${retryNum + 1}): ${err.message}`)
|
||||||
if (err.message == 'try max num') {
|
if (err.message == 'try max num') {
|
||||||
throw err
|
throw err
|
||||||
} else {
|
} else {
|
||||||
return this.getList(bangid, page, retryNum)
|
return this.getList(bangid, page, retryNum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// console.log(musicDetail)
|
|
||||||
return {
|
return {
|
||||||
total: musicDetail.list.length,
|
total: musicDetail.list.length,
|
||||||
list: musicDetail.list,
|
list: musicDetail.list,
|
||||||
|
|||||||
+2
-75
@@ -1,39 +1,6 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { eapi } from './utils/crypto.ts'
|
import { eapi } from './utils/crypto.ts'
|
||||||
import type { LyricInfo, HttpRequestObject } from '../types.ts'
|
import type { 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) => {
|
const eapiRequest = (url: string, data: object) => {
|
||||||
return httpFetch('https://interface3.music.163.com/eapi/song/lyric/v1', {
|
return httpFetch('https://interface3.music.163.com/eapi/song/lyric/v1', {
|
||||||
@@ -41,18 +8,9 @@ const eapiRequest = (url: string, data: object) => {
|
|||||||
headers: {
|
headers: {
|
||||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
|
'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',
|
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),
|
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 = {
|
const parseTools = {
|
||||||
@@ -218,35 +176,6 @@ const parseTools = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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) => {
|
const fixTimeLabel = (lrc: string, tlrc?: string, romalrc?: string) => {
|
||||||
@@ -277,11 +206,9 @@ export default (songmid: string): HttpRequestObject => {
|
|||||||
yrv: 0,
|
yrv: 0,
|
||||||
})
|
})
|
||||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||||
// console.log(body)
|
|
||||||
if (body.code !== 200 || !body?.lrc?.lyric) return Promise.reject(new Error('Get lyric failed'))
|
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 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)
|
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'))
|
if (!info.lyric) return Promise.reject(new Error('Get lyric failed'))
|
||||||
return info
|
return info
|
||||||
}) as any
|
}) as any
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { weapi } from './utils/crypto.ts'
|
import { weapi } from './utils/crypto.ts'
|
||||||
import { formatPlayTime, sizeFormate } from '../index.ts'
|
import { formatPlayTime, sizeFormate } from '../index.ts'
|
||||||
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
|
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
|
||||||
@@ -12,7 +12,6 @@ export default {
|
|||||||
return arr.join('、')
|
return arr.join('、')
|
||||||
},
|
},
|
||||||
filterList({ songs, privileges }) {
|
filterList({ songs, privileges }) {
|
||||||
// console.log(songs, privileges)
|
|
||||||
const list: any[] = []
|
const list: any[] = []
|
||||||
songs.forEach((item, index) => {
|
songs.forEach((item, index) => {
|
||||||
const types: Array<{ type: string, size: string | null }> = []
|
const types: Array<{ type: string, size: string | null }> = []
|
||||||
@@ -87,7 +86,6 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// console.log(list)
|
|
||||||
return list
|
return list
|
||||||
},
|
},
|
||||||
async getList(ids = [], retryNum = 0) {
|
async getList(ids = [], retryNum = 0) {
|
||||||
@@ -106,7 +104,6 @@ export default {
|
|||||||
})
|
})
|
||||||
const { body, statusCode } = await requestObj.promise
|
const { body, statusCode } = await requestObj.promise
|
||||||
if (statusCode != 200 || body.code !== 200) throw new Error('获取歌曲详情失败')
|
if (statusCode != 200 || body.code !== 200) throw new Error('获取歌曲详情失败')
|
||||||
// console.log(body)
|
|
||||||
return { source: 'wy', list: this.filterList(body) }
|
return { source: 'wy', list: this.filterList(body) }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
+1
-2
@@ -1,5 +1,5 @@
|
|||||||
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
|
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { weapi } from './utils/crypto.ts'
|
import { weapi } from './utils/crypto.ts'
|
||||||
import type { RequestObj } from '../types.ts'
|
import type { RequestObj } from '../types.ts'
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ export default (songmid: string): RequestObj<any> => {
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
requestObj.promise = requestObj.promise.then(({ body }) => {
|
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||||
// console.log(body)
|
|
||||||
if (body.code !== 200 || !body.songs.length) return Promise.reject(new Error('获取歌曲信息失败'))
|
if (body.code !== 200 || !body.songs.length) return Promise.reject(new Error('获取歌曲信息失败'))
|
||||||
return body.songs[0]
|
return body.songs[0]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// import { httpFetch } from '../request.ts'
|
// import { httpFetch } from '../common/request.ts'
|
||||||
// import { weapi } from './utils/crypto.ts'
|
// import { weapi } from './utils/crypto.ts'
|
||||||
import { sizeFormate, formatPlayTime } from '../index.ts'
|
import { sizeFormate, formatPlayTime } from '../index.ts'
|
||||||
// import musicDetailApi from './musicDetail.ts'
|
// import musicDetailApi from './musicDetail.ts'
|
||||||
@@ -36,7 +36,6 @@ export default {
|
|||||||
return arr.join('、')
|
return arr.join('、')
|
||||||
},
|
},
|
||||||
handleResult(rawList) {
|
handleResult(rawList) {
|
||||||
// console.log(rawList)
|
|
||||||
if (!rawList) return []
|
if (!rawList) return []
|
||||||
return rawList.map(item => {
|
return rawList.map(item => {
|
||||||
item = item.baseInfo.simpleSongData
|
item = item.baseInfo.simpleSongData
|
||||||
@@ -95,10 +94,8 @@ export default {
|
|||||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||||
if (limit == null) limit = this.limit
|
if (limit == null) limit = this.limit
|
||||||
return this.musicSearch(str, page, limit).then(result => {
|
return this.musicSearch(str, page, limit).then(result => {
|
||||||
// console.log(result)
|
|
||||||
if (!result || result.code !== 200) return this.search(str, page, limit, retryNum)
|
if (!result || result.code !== 200) return this.search(str, page, limit, retryNum)
|
||||||
let list = this.handleResult(result.data.resources || [])
|
let list = this.handleResult(result.data.resources || [])
|
||||||
// console.log(list)
|
|
||||||
|
|
||||||
if (list == null) return this.search(str, page, limit, retryNum)
|
if (list == null) return this.search(str, page, limit, retryNum)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
// 裸数字 id 也接受(补充便利;上游仅接受链接)
|
// 裸数字 id 也接受(补充便利;上游仅接受链接)
|
||||||
// 直链 url/type 为尽力附带,失败不影响元数据返回(与上游一致);
|
// 直链 url/type 为尽力附带,失败不影响元数据返回(与上游一致);
|
||||||
// 歌单/专辑链接抛清晰错误,提示使用对应 parsePlaylist/parseAlbum。
|
// 歌单/专辑链接抛清晰错误,提示使用对应 parsePlaylist/parseAlbum。
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { weapi } from './utils/crypto.ts'
|
import { weapi } from './utils/crypto.ts'
|
||||||
import { getWyCookie } from '../auth/store.ts'
|
import { getWyCookie } from '../auth/store.ts'
|
||||||
import wyApi from '../apis/wy.ts'
|
import wyApi from '../apis/wy.ts'
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
// 推荐:weapi/personalized/playlist(上游 GetRecommendedPlaylists,匿名可用)
|
// 推荐:weapi/personalized/playlist(上游 GetRecommendedPlaylists,匿名可用)
|
||||||
// 详情/曲目:复用 songList.getListDetail(linuxapi forward),按 page/limit 切片
|
// 详情/曲目:复用 songList.getListDetail(linuxapi forward),按 page/limit 切片
|
||||||
// 链接解析:#/playlist?id= / /playlist?id= / /playlist/<id>/<n>/... / 裸数字 id(上游 parseNeteaseLink)
|
// 链接解析:#/playlist?id= / /playlist?id= / /playlist/<id>/<n>/... / 裸数字 id(上游 parseNeteaseLink)
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { weapi } from './utils/crypto.ts'
|
import { weapi } from './utils/crypto.ts'
|
||||||
import { formatPlayCount } from '../index.ts'
|
import { formatPlayCount } from '../index.ts'
|
||||||
import { getWyCookie } from '../auth/store.ts'
|
import { getWyCookie } from '../auth/store.ts'
|
||||||
|
|||||||
+2
-12
@@ -4,7 +4,7 @@
|
|||||||
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/playlist_detail.js
|
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/playlist_detail.js
|
||||||
|
|
||||||
import { weapi, linuxapi } from './utils/crypto.ts'
|
import { weapi, linuxapi } from './utils/crypto.ts'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
|
import { formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
|
||||||
import musicDetailApi from './musicDetail.ts'
|
import musicDetailApi from './musicDetail.ts'
|
||||||
import { eapiRequest } from './utils/index.ts'
|
import { eapiRequest } from './utils/index.ts'
|
||||||
@@ -39,7 +39,6 @@ export default {
|
|||||||
|
|
||||||
const requestObj_listDetailLink = httpFetch(link)
|
const requestObj_listDetailLink = httpFetch(link)
|
||||||
const { headers: { location }, statusCode } = await requestObj_listDetailLink.promise
|
const { headers: { location }, statusCode } = await requestObj_listDetailLink.promise
|
||||||
// console.log(headers)
|
|
||||||
if (statusCode > 400) return this.handleParseId(link, ++retryNum)
|
if (statusCode > 400) return this.handleParseId(link, ++retryNum)
|
||||||
const url = location == null ? link : location
|
const url = location == null ? link : location
|
||||||
return this.regExps.listDetailLink.test(url)
|
return this.regExps.listDetailLink.test(url)
|
||||||
@@ -62,7 +61,6 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
id = await this.handleParseId(id)
|
id = await this.handleParseId(id)
|
||||||
}
|
}
|
||||||
// console.log(id)
|
|
||||||
}
|
}
|
||||||
return { id, cookie }
|
return { id, cookie }
|
||||||
},
|
},
|
||||||
@@ -92,7 +90,6 @@ export default {
|
|||||||
if (statusCode !== 200 || body.code !== this.successCode) return this.getListDetail(id, page, ++tryNum)
|
if (statusCode !== 200 || body.code !== this.successCode) return this.getListDetail(id, page, ++tryNum)
|
||||||
let limit = 1000
|
let limit = 1000
|
||||||
let rangeStart = (page - 1) * limit
|
let rangeStart = (page - 1) * limit
|
||||||
// console.log(body)
|
|
||||||
let list
|
let list
|
||||||
if (body.playlist.trackIds.length == body.privileges.length) {
|
if (body.playlist.trackIds.length == body.privileges.length) {
|
||||||
list = this.filterListDetail(body)
|
list = this.filterListDetail(body)
|
||||||
@@ -100,7 +97,7 @@ export default {
|
|||||||
try {
|
try {
|
||||||
list = (await (musicDetailApi as any).getList(body.playlist.trackIds.slice(rangeStart, limit * page).map(trackId => trackId.id))).list
|
list = (await (musicDetailApi as any).getList(body.playlist.trackIds.slice(rangeStart, limit * page).map(trackId => trackId.id))).list
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log(err)
|
console.warn(`[wy] 歌单歌曲详情获取失败将重试(${tryNum + 1}): ${err.message}`)
|
||||||
if (err.message == 'try max num') {
|
if (err.message == 'try max num') {
|
||||||
throw err
|
throw err
|
||||||
} else {
|
} else {
|
||||||
@@ -108,7 +105,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// console.log(list)
|
|
||||||
return {
|
return {
|
||||||
list,
|
list,
|
||||||
page,
|
page,
|
||||||
@@ -125,7 +121,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
filterListDetail({ playlist: { tracks }, privileges }) {
|
filterListDetail({ playlist: { tracks }, privileges }) {
|
||||||
// console.log(tracks, privileges)
|
|
||||||
const list: any[] = []
|
const list: any[] = []
|
||||||
tracks.forEach((item, index) => {
|
tracks.forEach((item, index) => {
|
||||||
const types: Array<{ type: string, size: string | null }> = []
|
const types: Array<{ type: string, size: string | null }> = []
|
||||||
@@ -218,7 +213,6 @@ export default {
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
return this._requestObj_list!.promise.then(({ body }) => {
|
return this._requestObj_list!.promise.then(({ body }) => {
|
||||||
// console.log(body)
|
|
||||||
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
||||||
return {
|
return {
|
||||||
list: this.filterList(body.playlists),
|
list: this.filterList(body.playlists),
|
||||||
@@ -230,7 +224,6 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
filterList(rawData) {
|
filterList(rawData) {
|
||||||
// console.log(rawData)
|
|
||||||
return rawData.map(item => ({
|
return rawData.map(item => ({
|
||||||
play_count: formatPlayCount(item.playCount),
|
play_count: formatPlayCount(item.playCount),
|
||||||
id: String(item.id),
|
id: String(item.id),
|
||||||
@@ -254,7 +247,6 @@ export default {
|
|||||||
form: weapi({}),
|
form: weapi({}),
|
||||||
})
|
})
|
||||||
return this._requestObj_tags!.promise.then(({ body }) => {
|
return this._requestObj_tags!.promise.then(({ body }) => {
|
||||||
// console.log(JSON.stringify(body))
|
|
||||||
if (body.code !== this.successCode) return this.getTag(++tryNum)
|
if (body.code !== this.successCode) return this.getTag(++tryNum)
|
||||||
return this.filterTagInfo(body)
|
return this.filterTagInfo(body)
|
||||||
})
|
})
|
||||||
@@ -292,7 +284,6 @@ export default {
|
|||||||
form: weapi({}),
|
form: weapi({}),
|
||||||
})
|
})
|
||||||
return this._requestObj_hotTags!.promise.then(({ body }) => {
|
return this._requestObj_hotTags!.promise.then(({ body }) => {
|
||||||
// console.log(JSON.stringify(body))
|
|
||||||
if (body.code !== this.successCode) return this.getTag(++tryNum)
|
if (body.code !== this.successCode) return this.getTag(++tryNum)
|
||||||
return this.filterHotTagInfo(body.tags)
|
return this.filterHotTagInfo(body.tags)
|
||||||
})
|
})
|
||||||
@@ -324,7 +315,6 @@ export default {
|
|||||||
})
|
})
|
||||||
.promise.then(({ body }) => {
|
.promise.then(({ body }) => {
|
||||||
if (body.code != this.successCode) throw new Error('filed')
|
if (body.code != this.successCode) throw new Error('filed')
|
||||||
// console.log(body)
|
|
||||||
return {
|
return {
|
||||||
list: this.filterList(body.result.playlists),
|
list: this.filterList(body.result.playlists),
|
||||||
limit,
|
limit,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// 未登录抛出清晰错误,不返回空列表
|
// 未登录抛出清晰错误,不返回空列表
|
||||||
// 1) weapi/nuser/account/get → 当前用户 userId
|
// 1) weapi/nuser/account/get → 当前用户 userId
|
||||||
// 2) weapi/user/playlist → 用户歌单列表(分页 limit/offset)
|
// 2) weapi/user/playlist → 用户歌单列表(分页 limit/offset)
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { weapi } from './utils/crypto.ts'
|
import { weapi } from './utils/crypto.ts'
|
||||||
import { formatPlayCount } from '../index.ts'
|
import { formatPlayCount } from '../index.ts'
|
||||||
import { getCredentials, getWyCookie } from '../auth/store.ts'
|
import { getCredentials, getWyCookie } from '../auth/store.ts'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { httpFetch } from '../../request.ts'
|
import { httpFetch } from '../../common/request.ts'
|
||||||
import { eapi } from './crypto.ts'
|
import { eapi } from './crypto.ts'
|
||||||
import { getWyCookie } from '../../auth/store.ts'
|
import { getWyCookie } from '../../auth/store.ts'
|
||||||
import type { HttpRequestObject } from '../../types.ts'
|
import type { HttpRequestObject } from '../../types.ts'
|
||||||
@@ -15,12 +15,4 @@ export const eapiRequest = (url: string, data: object): HttpRequestObject => {
|
|||||||
},
|
},
|
||||||
form: eapi(url, data),
|
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
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
// 接口: https://www.ximalaya.com/revision/album/getTracksList?albumId=&pageNum=&sort=0
|
// 接口: https://www.ximalaya.com/revision/album/getTracksList?albumId=&pageNum=&sort=0
|
||||||
// 实测(2026-10-02)匿名可翻全部页(固定每页 30 条);仅曲目元信息(trackId/title/isPaid),
|
// 实测(2026-10-02)匿名可翻全部页(固定每页 30 条);仅曲目元信息(trackId/title/isPaid),
|
||||||
// 播放地址需另走 baseInfo。注意 v1 版接口(/revision/album/v1/getTracksList)需要 webtk 签名且有风控。
|
// 播放地址需另走 baseInfo。注意 v1 版接口(/revision/album/v1/getTracksList)需要 webtk 签名且有风控。
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { XM_UA } from './sign.ts'
|
import { XM_UA } from './sign.ts'
|
||||||
|
|
||||||
const TRACKS_URL = 'https://www.ximalaya.com/revision/album/getTracksList'
|
const TRACKS_URL = 'https://www.ximalaya.com/revision/album/getTracksList'
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
// 响应 trackInfo 顶层挂载(playUrlList/title/cover*/duration/isPaid/isAuthorized/isXimiUhqTrack),
|
// 响应 trackInfo 顶层挂载(playUrlList/title/cover*/duration/isPaid/isAuthorized/isXimiUhqTrack),
|
||||||
// playUrlList[].url 为 AES-ECB 加密地址(key 取自官方客户端 4.0.14 asar),见 decoder.ts
|
// playUrlList[].url 为 AES-ECB 加密地址(key 取自官方客户端 4.0.14 asar),见 decoder.ts
|
||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { makeXmSign, XM_UA } from './sign.ts'
|
import { makeXmSign, XM_UA } from './sign.ts'
|
||||||
import { getCredentials } from '../auth/store.ts'
|
import { getCredentials } from '../auth/store.ts'
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// risk invalid 风控,本端点 + condition=relation&device=web 不受影响)
|
// risk invalid 风控,本端点 + condition=relation&device=web 不受影响)
|
||||||
// 返回 data.result.response.docs[],字段 id/title/duration(秒)/nickname/album_id/album_title/
|
// 返回 data.result.response.docs[],字段 id/title/duration(秒)/nickname/album_id/album_title/
|
||||||
// album_cover_path/is_paid/is_trailer
|
// album_cover_path/is_paid/is_trailer
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { formatPlayTime } from '../index.ts'
|
import { formatPlayTime } from '../index.ts'
|
||||||
import { XM_UA } from './sign.ts'
|
import { XM_UA } from './sign.ts'
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import zlib from 'node:zlib'
|
import zlib from 'node:zlib'
|
||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import { httpFetch } from '../request.ts'
|
import { httpFetch } from '../common/request.ts'
|
||||||
import { DEVICE_INFO } from './deviceInfo.ts'
|
import { DEVICE_INFO } from './deviceInfo.ts'
|
||||||
|
|
||||||
const SIGN_KEY = 'm9ZtRrz:qujT8@da'
|
const SIGN_KEY = 'm9ZtRrz:qujT8@da'
|
||||||
|
|||||||
+1
-1
@@ -431,7 +431,7 @@ export const loginRoutes = async(fastify) => {
|
|||||||
description: '用 B站 App 扫码后轮询 /login/bl/qr/check 完成登录。返回 key 需原样回传,qrimg 为 data URL 可直接展示。',
|
description: '用 B站 App 扫码后轮询 /login/bl/qr/check 完成登录。返回 key 需原样回传,qrimg 为 data URL 可直接展示。',
|
||||||
tags: ['登录'],
|
tags: ['登录'],
|
||||||
},
|
},
|
||||||
}, async(request, reply) => {
|
}, async(_request, reply) => {
|
||||||
try {
|
try {
|
||||||
return await biliAuth.createQRLogin()
|
return await biliAuth.createQRLogin()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in new issue
Block a user