refactor: 架构整固——分层单向化、全量 TypeScript、直接运行

- 移除 bd/mg 两平台及磁盘残留产物;清理 api-test 死脚本、musicSdk/message+options 死模块、infSign vendor
- musicSdk/ 改名 sdk/;src/common、src/auth、src/apis 整体移入 sdk/,依赖方向定为 src → sdk 严格单向
- src/ 手写 JS 全量转 .ts,删除 JS 类型垫片 modules.d.ts;源码 import 统一 .ts 后缀
- Node 原生类型剥离直接运行 .ts(engines >=22.18),取消 tsc 发射产物,tsc 仅类型检查(pnpm typecheck)
- 删除 kwdes 备用实现与 src/index.ts 转发垫片;测试移至 test/smoke-test.ts
- common/request.ts 补显式导出类型;修复 set-cookie 单值形态、ApiError 类字段等隐患

验证:tsc 零错误,smoke-test 41/41
This commit is contained in:
snowy-yang committed 2026-09-25 23:06:23 +08:00
1 parent c8c939aa32
commit 354df2e739
126 files changed
+413 -2942

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
// 网易云登录 —— 扫码 / 手机号,weapi 加密请求
// 参考实现:NeteaseCloudMusicApi(login_qr_key / login_qr_check / login_cellphone / login_status)
import crypto from 'node:crypto'
import { httpFetch } from '../common/request.ts'
import { weapi } from '../wy/utils/crypto.ts'
import { getWyCookie, setCredentials } from './store.ts'
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
const requestWeapi = async(path, data, cookie = getWyCookie()) => {
const r = await httpFetch(`https://music.163.com/weapi${path}`, {
method: 'post',
headers: {
'User-Agent': UA,
origin: 'https://music.163.com',
Referer: 'https://music.163.com/',
...(cookie ? { cookie } : {}),
},
form: weapi(data),
timeout: 15000,
}).promise
if (r.statusCode !== 200) throw new Error('网易请求失败')
return { body: r.body, setCookie: r.headers['set-cookie'] ?? [] }
}
/** 明文 api 请求(二维码接口只提供该形态,weapi 形态已 404) */
const requestApi = async(path, data) => {
const r = await httpFetch(`https://music.163.com${path}`, {
method: 'post',
headers: {
'User-Agent': UA,
origin: 'https://music.163.com',
Referer: 'https://music.163.com/',
},
form: data,
timeout: 15000,
}).promise
if (r.statusCode !== 200) throw new Error('网易请求失败')
return { body: r.body, setCookie: r.headers['set-cookie'] ?? [] }
}
/** 从 Set-Cookie 数组提取登录 cookie 串与 MUSIC_U */
const extractLoginCookie = (setCookie) => {
const joined = setCookie.map(c => c.split(';')[0]).filter(Boolean).join('; ')
const musicU = /MUSIC_U=([^;]+)/.exec(joined)?.[1]
return { cookie: joined, musicU }
}
/** 登录成功后拉取账号资料 */
const fetchProfile = async() => {
const { body } = await requestWeapi('/api/w/nuser/account/get', {})
return {
userid: body?.account?.id ?? null,
nickname: body?.profile?.nickname ?? null,
avatar: body?.profile?.avatarUrl ?? null,
vipType: body?.profile?.vipType ?? null,
}
}
/** 应用登录 cookie(成功后调用) */
const applyLogin = async(setCookie) => {
const { cookie, musicU } = extractLoginCookie(setCookie)
if (!musicU) throw new Error('登录响应中缺少 MUSIC_U')
const profile = await fetchProfile()
setCredentials('wy', { cookie, musicU, ...profile })
return profile
}
/** 创建扫码登录 key 与二维码内容 */
export const qrKey = async() => {
const { body } = await requestApi('/api/login/qrcode/unikey', { type: 3 })
if (body?.code !== 200 || !body.unikey) throw new Error('获取二维码 key 失败')
const key = body.unikey
const qrurl = `https://music.163.com/login?codekey=${key}`
const qrimg = await import('qrcode').then(m => m.default.toDataURL(qrurl)).catch(() => '')
return { key, qrurl, qrimg }
}
/**
* 轮询扫码状态
* @returns {{ code: 800|801|802|803, message?: string, profile? }} 800 过期 / 801 等待 / 802 已扫码 / 803 已授权(完成登录)
*/
export const qrCheck = async(key) => {
const { body, setCookie } = await requestApi('/api/login/qrcode/client/login', { key, type: 3 })
if (body.code === 803) {
const profile = await applyLogin(setCookie)
return { code: 803, message: '登录成功', profile }
}
const messages = { 800: '二维码已过期', 801: '等待扫码', 802: '已扫码,待确认' }
return { code: body.code, message: messages[body.code] ?? body.message ?? '未知状态' }
}
/**
* 手机号登录(密码为明文,传输前本地 MD5;或直接传验证码 captcha)
*/
export const cellphoneLogin = async({ phone, password, captcha, countrycode = '86' }) => {
const data = {
type: '1',
https: 'true',
phone,
countrycode,
remember: 'true',
[captcha ? 'captcha' : 'password']: captcha ?? crypto.createHash('md5').update(password ?? '').digest('hex'),
}
const { body, setCookie } = await requestWeapi('/api/w/login/cellphone', data)
if (body.code !== 200) {
const err = new Error(body.message ?? body.msg ?? '登录失败')
;(err as any).status = 401
throw err
}
const profile = await applyLogin(setCookie)
return profile
}
/** 当前账号资料(未登录返回 null) */
export const status = async() => {
if (!getWyCookie()) return null
return fetchProfile()
}