酷我音乐PC客户端歌词解密 - nodejs
生活随笔
收集整理的這篇文章主要介紹了
酷我音乐PC客户端歌词解密 - nodejs
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
酷我PC客戶端歌詞解密 - nodejs
語言:nodejs
網頁端歌詞接口:
http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=198554068&httpsStatus=1
musicId=198554068就是歌曲的ID
· 這個比較好請求,但是這個接口很容易奔潰,比較慢
· 客戶端的歌詞是被加密過的,所以要進行解密,這個過程比較復雜,下面是 js 解密代碼。
1.依次安裝依賴
npm i iconv-lite npm i needle npm i process npm i zlib目錄結構|_ index.js|_ package.json|_ .....2.代碼
const needle = require('needle') const process = require('process') const deflateRaw = require('zlib') const { inflate } = require('zlib') const iconv = require('iconv-lite')const bufkey = Buffer.from('yeelion') const bufkeylen = bufkey.length// 音樂 rid let musicId = 184600062const buildParams = (id, isGetLyricx) => {let params = `user=12345,web,web,web&requester=localhost&req=1&rid=MUSIC_${id}`if (isGetLyricx) params += '&lrcx=1'const bufstr = Buffer.from(params)const bufstrlen = bufstr.lengthconst output = new Uint16Array(bufstrlen)let i = 0while (i < bufstrlen) {let j = 0while (j < bufkeylen && i < bufstrlen) {output[i] = bufkey[j] ^ bufstr[i]i++j++}}return Buffer.from(output).toString('base64') }const cancelHttp = requestObj => {// console.log(requestObj)if (!requestObj) return// console.log('cancel:', requestObj)if (!requestObj.abort) returnrequestObj.abort() }const requestMsg = {fail: '請求異常,若還是不行就換一首吧。。。',unachievable: '接口無法訪問了!',timeout: '請求超時',// unachievable: '重試下看能不能播放吧~',notConnectNetwork: '無法連接到服務器',cancelRequest: '取消http請求', }const request = (url, options, callback) => {let dataif (options.body) {data = options.body} else if (options.form) {data = options.form// data.content_type = 'application/x-www-form-urlencoded'options.json = false} else if (options.formData) {data = options.formData// data.content_type = 'multipart/form-data'options.json = false}options.response_timeout = options.timeoutreturn needle.request(options.method || 'get', url, data, options, (err, resp, body) => {if (!err) {body = resp.body = resp.raw.toString()try {resp.body = JSON.parse(resp.body)} catch (_) {}body = resp.body}callback(err, resp, body)}).request }const defaultHeaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', }const handleDeflateRaw = data => new Promise((resolve, reject) => {deflateRaw(data, (err, buf) => {if (err) return reject(err)resolve(buf)}) })const regx = /(?:\d\w)+/gconst fetchData = async(url, method, {headers = {},format = 'json',timeout = 15000,...options }, callback) => {headers = Object.assign({}, headers)const bHh = '624868746c'if (headers[bHh]) {const path = url.replace(/^https?:\/\/[\w.:]+\//, '/')let s = Buffer.from(bHh, 'hex').toString()s = s.replace(s.substr(-1), '')s = Buffer.from(s, 'base64').toString()let v = process.versions.app.split('-')[0].split('.').map(n => n.length < 3 ? n.padStart(3, '0') : n).join('')let v2 = process.versions.app.split('-')[1] || ''headers[s] = !s || `${(await handleDeflateRaw(Buffer.from(JSON.stringify(`${path}${v}`.match(regx), null, 1).concat(v)).toString('base64'))).toString('hex')}&${parseInt(v)}${v2}`delete headers[bHh]}return request(url, {...options,method,headers: Object.assign({}, defaultHeaders, headers),timeout,json: format === 'json',}, (err, resp, body) => {if (err) return callback(err, null)callback(null, resp, body)}) } const buildHttpPromose = (url, options) => {let obj = {isCancelled: false,}obj.promise = new Promise((resolve, reject) => {obj.cancelFn = reject// console.log(`\nsend request---${url}`)fetchData(url, options.method, options, (err, resp, body) => {// options.isShowProgress && window.api.hideProgress()// console.log(`\nresponse---${url}`)// console.log(body)obj.requestObj = nullobj.cancelFn = nullif (err) return reject(err)resolve(resp)}).then(ro => {obj.requestObj = roif (obj.isCancelled) obj.cancelHttp()})})obj.cancelHttp = () => {if (!obj.requestObj) return obj.isCancelled = truecancelHttp(obj.requestObj)obj.requestObj = nullobj.promise = obj.cancelHttp = nullobj.cancelFn(new Error(requestMsg.cancelRequest))obj.cancelFn = null}return obj } const httpFetch = (url, options = { method: 'get' }) => {const requestObj = buildHttpPromose(url, options)requestObj.promise = requestObj.promise.catch(err => {if (err.message === 'socket hang up') {return Promise.reject(new Error(requestMsg.unachievable))}switch (err.code) {case 'ETIMEDOUT':case 'ESOCKETTIMEDOUT':return Promise.reject(new Error(requestMsg.timeout))case 'ENOTFOUND':return Promise.reject(new Error(requestMsg.notConnectNetwork))default:return Promise.reject(err)}})return requestObj } 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: [],tags: [],getWordInfo(str, str2, prevWord) {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)) + startTimeif (prevWord) {if (startTime < prevWord.endTime) {prevWord.endTime = startTimeif (prevWord.startTime > prevWord.endTime) {prevWord.startTime = prevWord.endTime}prevWord.newTimeStr = ``}}return {startTime,endTime,timeStr: ``,}},parseLine(line) {if (line.length < 6) returnlet 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 preTimeInfofor (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) returnif (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) {// console.log(lrc)const lines = lrc.split(/\r\n|\r|\n/)const tools = Object.create(this)tools.isOK = truetools.offset = 1tools.offset2 = 1tools.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 isGetLyricx = true const handleInflate = data => new Promise((resolve, reject) => {inflate(data, (err, result) => {if (err) return reject(err)resolve(result)}) }) const bufKey = Buffer.from('yeelion') const bufKeyLen = bufKey.length const decodeLyrics = async(buf, isGetLyricx) => {if (buf.toString('utf8', 0, 10) != 'tp=content') return ''const lrcData = await handleInflate(buf.slice(buf.indexOf('\r\n\r\n') + 4))if (!isGetLyricx) return iconv.decode(lrcData, 'gb18030')const bufStr = Buffer.from(lrcData.toString(), 'base64')const bufStrLen = bufStr.lengthconst output = new Uint16Array(bufStrLen)let i = 0while (i < bufStrLen) {let j = 0while (j < bufKeyLen && i < bufStrLen) {output[i] = bufStr[i] ^ bufKey[j]i++j++}}return iconv.decode(Buffer.from(output), 'gb18030') } const timeExp = /^\[([\d:.]*)\]{1}/g const sortLrcArr = (arr) => {const lrcSet = new Set()let lrc = []let lrcT = []for (const item of arr) {if (lrcSet.has(item.time)) {if (lrc.length < 2) continueconst tItem = lrc.pop()tItem.time = lrc[lrc.length - 1].timelrcT.push(tItem)lrc.push(item)} else {lrc.push(item)lrcSet.add(item.time)}}return {lrc,lrcT,} } const parseLrc = (lrc) => {const lines = lrc.split(/\r\n|\r|\n/)let tags = []let lrcArr = []for (let i = 0; i < lines.length; i++) {const line = lines[i].trim()let result = timeExp.exec(line)if (result) {let text = line.replace(timeExp, '').trim()let time = RegExp.$1if (/\.\d\d$/.test(time)) time += '0'let regexp = /<.*?>/gtext = text.replace(regexp, "").replace(/\[by:.*?\](\n|$)/g, '').replace(/\[kuwo:.*?\](\n|$)/g, '')let times = time.split(':');time = (parseFloat(times[0])*60 + parseFloat(times[1])).toFixed(2);lrcArr.push({time,lineLyric: text,})} else if (lrcTools.rxps.tagLine.test(line)) {tags.push(line)}}const lrcInfo = sortLrcArr(lrcArr)return lrcInfo }const rendererInvoke = async(name, params) => {const lrc = await decodeLyrics(Buffer.from(params.lrcBase64, 'base64'), isGetLyricx)return Buffer.from(lrc).toString('base64') } const decodeLyric = base64Data => rendererInvoke('handle_kw_decode_lyric', base64Data) const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicId, 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 lrcInfolrcInfo = parseLrc(Buffer.from(base64Data, 'base64').toString())try {lrcInfo = parseLrc(Buffer.from(base64Data, 'base64').toString())} catch (err) {return Promise.reject(new Error('Get lyric failed'))}let msg = {data: {lrclist: lrcInfo.lrc},status: 200}return msg}) })// 輸出 const rrr = async()=>{let asd = async()=>{return await new Promise((res,rej)=>{requestObj.promise.then((re)=>{res(re)})})}let as = await asd()console.log(as.data.lrclist) }console.log(rrr())總結
以上是生活随笔為你收集整理的酷我音乐PC客户端歌词解密 - nodejs的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 秒杀系统(二)——商品模块展示技术难点
- 下一篇: java同学录_JSP同学录(jsp+j