import { handleKwDecodeLyric } from './lyricDecode.ts' import { createCipheriv, createDecipheriv } from 'crypto' import { toMD5 } from '../utils.ts' 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 | null => { try { return headers['set-cookie'][0].match(/kw_token=(\w+)/)[1] } catch (err) { return null } } export const decodeLyric = handleKwDecodeLyric 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}>` } } 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 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 { 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}` 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}` }, }