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:
1 parent
c8c939aa32
commit
354df2e739
126 files changed
+413
-2942
No files matched your search
+226
@@ -0,0 +1,226 @@
|
||||
// import { httpGet, httpFetch } from '../request.ts'
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '../common/ipcNames.ts'
|
||||
import { rendererInvoke } from '../common/rendererIpc.ts'
|
||||
import { createCipheriv, createDecipheriv } from 'crypto'
|
||||
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 => {
|
||||
return JSON.parse(str.replace(/('(?=(,\s*')))|('(?=:))|((?<=([:,]\s*))')|((?<={)')|('(?=}))/g, '"'))
|
||||
}
|
||||
|
||||
|
||||
export const formatSinger = (rawData: string): string => rawData.replace(/&/g, '、')
|
||||
|
||||
export const matchToken = (headers: Record<string, any>): string | null => {
|
||||
try {
|
||||
return headers['set-cookie'][0].match(/kw_token=(\w+)/)[1]
|
||||
} catch (err) {
|
||||
return 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 = (base64Data: { lrcBase64: string; isGetLyricx: boolean }): Promise<any> =>
|
||||
rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.handle_kw_decode_lyric, base64Data)
|
||||
|
||||
// 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 {
|
||||
startTime: number
|
||||
endTime: number
|
||||
timeStr: string
|
||||
newTimeStr?: string
|
||||
}
|
||||
|
||||
export const lrcTools = {
|
||||
rxps: {
|
||||
wordLine: /^(\[\d{1,2}:.*\d{1,4}\])\s*(\S+(?:\s+\S+)*)?\s*/,
|
||||
tagLine: /\[(ver|ti|ar|al|offset|by|kuwo):\s*(\S+(?:\s+\S+)*)\s*\]/,
|
||||
wordTimeAll: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/g,
|
||||
wordTime: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/,
|
||||
},
|
||||
offset: 1,
|
||||
offset2: 1,
|
||||
isOK: false,
|
||||
lines: [] as string[],
|
||||
tags: [] as string[],
|
||||
getWordInfo(str: string, str2: string, prevWord?: LrcWordInfo): LrcWordInfo {
|
||||
const offset = parseInt(str)
|
||||
const offset2 = parseInt(str2)
|
||||
let startTime = Math.abs((offset + offset2) / (this.offset * 2))
|
||||
let endTime = Math.abs((offset - offset2) / (this.offset2 * 2)) + startTime
|
||||
if (prevWord) {
|
||||
if (startTime < prevWord.endTime) {
|
||||
prevWord.endTime = startTime
|
||||
if (prevWord.startTime > prevWord.endTime) {
|
||||
prevWord.startTime = prevWord.endTime
|
||||
}
|
||||
|
||||
prevWord.newTimeStr = `<${prevWord.startTime},${prevWord.endTime - prevWord.startTime}>`
|
||||
// console.log(prevWord)
|
||||
}
|
||||
}
|
||||
return {
|
||||
startTime,
|
||||
endTime,
|
||||
timeStr: `<${startTime},${endTime - startTime}>`,
|
||||
}
|
||||
},
|
||||
parseLine(line: string): void {
|
||||
if (line.length < 6) return
|
||||
let result = this.rxps.wordLine.exec(line)
|
||||
if (result) {
|
||||
const time = result[1]
|
||||
let words = result[2]
|
||||
if (words == null) {
|
||||
words = ''
|
||||
}
|
||||
const wordTimes = words.match(this.rxps.wordTimeAll)
|
||||
if (!wordTimes) return
|
||||
// console.log(wordTimes)
|
||||
let preTimeInfo
|
||||
for (const timeStr of wordTimes) {
|
||||
const result = this.rxps.wordTime.exec(timeStr)
|
||||
const wordInfo = this.getWordInfo(result![1], result![2], preTimeInfo)
|
||||
words = words.replace(timeStr, wordInfo.timeStr)
|
||||
if (preTimeInfo?.newTimeStr) words = words.replace(preTimeInfo.timeStr, preTimeInfo.newTimeStr)
|
||||
preTimeInfo = wordInfo
|
||||
}
|
||||
this.lines.push(time + words)
|
||||
return
|
||||
}
|
||||
result = this.rxps.tagLine.exec(line)
|
||||
if (!result) return
|
||||
if (result[1] == 'kuwo') {
|
||||
let content = result[2]
|
||||
if (content != null && content.includes('][')) {
|
||||
content = content.substring(0, content.indexOf(']['))
|
||||
}
|
||||
const valueOf = parseInt(content, 8)
|
||||
this.offset = Math.trunc(valueOf / 10)
|
||||
this.offset2 = Math.trunc(valueOf % 10)
|
||||
if (this.offset == 0 || Number.isNaN(this.offset) || this.offset2 == 0 || Number.isNaN(this.offset2)) {
|
||||
this.isOK = false
|
||||
}
|
||||
} else {
|
||||
this.tags.push(line)
|
||||
}
|
||||
},
|
||||
parse(lrc: string): string {
|
||||
// console.log(lrc)
|
||||
const lines = lrc.split(/\r\n|\r|\n/)
|
||||
const tools = Object.create(this)
|
||||
tools.isOK = true
|
||||
tools.offset = 1
|
||||
tools.offset2 = 1
|
||||
tools.lines = []
|
||||
tools.tags = []
|
||||
|
||||
for (const line of lines) {
|
||||
if (!tools.isOK) throw new Error('failed')
|
||||
tools.parseLine(line)
|
||||
}
|
||||
if (!tools.lines.length) return ''
|
||||
let lrcs = tools.lines.join('\n')
|
||||
if (tools.tags.length) lrcs = `${tools.tags.join('\n')}\n${lrcs}`
|
||||
// console.log(lrcs)
|
||||
return lrcs
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
const createAesEncrypt = (buffer: Buffer, mode: string, key: Buffer | string, iv: string | Buffer): Buffer => {
|
||||
const cipher = createCipheriv(mode, key, iv)
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()])
|
||||
}
|
||||
|
||||
const createAesDecrypt = (buffer: Buffer, mode: string, key: Buffer | string, iv: string | Buffer): Buffer => {
|
||||
const cipher = createDecipheriv(mode, key, iv)
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()])
|
||||
}
|
||||
|
||||
export const wbdCrypto = {
|
||||
aesMode: 'aes-128-ecb',
|
||||
aesKey: Buffer.from([112, 87, 39, 61, 199, 250, 41, 191, 57, 68, 45, 114, 221, 94, 140, 228] as any, 'binary'),
|
||||
aesIv: '',
|
||||
appId: 'y67sprxhhpws',
|
||||
decodeData(base64Result: string): any {
|
||||
const data = Buffer.from(decodeURIComponent(base64Result), 'base64')
|
||||
return JSON.parse(createAesDecrypt(data, this.aesMode, this.aesKey, this.aesIv).toString())
|
||||
},
|
||||
createSign(data: string, time: number): string {
|
||||
const str = `${this.appId}${data}${time}`
|
||||
return toMD5(str).toUpperCase()
|
||||
},
|
||||
buildParam(jsonData: unknown): string {
|
||||
const data = Buffer.from(JSON.stringify(jsonData))
|
||||
const time = Date.now()
|
||||
|
||||
const encodeData = createAesEncrypt(data, this.aesMode, this.aesKey, this.aesIv).toString('base64')
|
||||
const sign = this.createSign(encodeData, time)
|
||||
|
||||
return `data=${encodeURIComponent(encodeData)}&time=${time}&appId=${this.appId}&sign=${sign}`
|
||||
},
|
||||
}
|
||||
Reference in new issue
Block a user