import { httpFetch } from '../../request' import { eapi } from './utils/crypto' // import { decodeName } from '../..' // const parseLyric = (str, lrc) => { // if (!str) return '' // str = str.replace(/\r/g, '') // let lxlyric = 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 // }) // lxlyric = decodeName(lxlyric) // return lxlyric.trim() // } const eapiRequest = (url, data) => { return httpFetch('https://interface3.music.163.com/eapi/song/lyric/v1', { method: 'post', headers: { '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', // 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), }) // 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 = { rxps: { info: /^{"/, lineTime: /^\[(\d+),\d+\]/, wordTime: /\(\d+,\d+,\d+\)/, wordTimeAll: /(\(\d+,\d+,\d+\))/g, }, msFormat(timeMs) { if (Number.isNaN(timeMs)) return '' let ms = timeMs % 1000 timeMs /= 1000 let m = parseInt(timeMs / 60).toString().padStart(2, '0') timeMs %= 60 let s = parseInt(timeMs).toString().padStart(2, '0') return `[${m}:${s}.${ms}]` }, parseLyric(lines) { const lxlrcLines = [] const lrcLines = [] for (let line of lines) { line = line.trim() let result = this.rxps.lineTime.exec(line) if (!result) { if (line.startsWith('[offset')) { lxlrcLines.push(line) lrcLines.push(line) } continue } const startMsTime = parseInt(result[1]) const startTimeStr = this.msFormat(startMsTime) if (!startTimeStr) continue let words = line.replace(this.rxps.lineTime, '') lrcLines.push(`${startTimeStr}${words.replace(this.rxps.wordTimeAll, '')}`) let times = words.match(this.rxps.wordTimeAll) if (!times) continue times = times.map(time => { const result = /\((\d+),(\d+),\d+\)/.exec(time) return `<${Math.max(parseInt(result[1]) - startMsTime, 0)},${result[2]}>` }) const wordArr = words.split(this.rxps.wordTime) wordArr.shift() const newWords = times.map((time, index) => `${time}${wordArr[index]}`).join('') lxlrcLines.push(`${startTimeStr}${newWords}`) } return { lyric: lrcLines.join('\n'), lxlyric: lxlrcLines.join('\n'), } }, parseHeaderInfo(str) { str = str.trim() str = str.replace(/\r/g, '') if (!str) return null const lines = str.split('\n') return lines.map(line => { if (!this.rxps.info.test(line)) return line try { const info = JSON.parse(line) const timeTag = this.msFormat(info.t) return timeTag ? `${timeTag}${info.c.map(t => t.tx).join('')}` : '' } catch { return '' } }) }, getIntv(interval) { if (!interval.includes('.')) interval += '.0' let arr = interval.split(/:|\./) while (arr.length < 3) arr.unshift('0') const [m, s, ms] = arr return parseInt(m) * 3600000 + parseInt(s) * 1000 + parseInt(ms) }, fixTimeTag(lrc, targetlrc) { let lrcLines = lrc.split('\n') const targetlrcLines = targetlrc.split('\n') const timeRxp = /^\[([\d:.]+)\]/ let temp = [] let newLrc = [] targetlrcLines.forEach((line) => { const result = timeRxp.exec(line) if (!result) return const words = line.replace(timeRxp, '') if (!words.trim()) return const t1 = this.getIntv(result[1]) while (lrcLines.length) { const lrcLine = lrcLines.shift() const lrcLineResult = timeRxp.exec(lrcLine) if (!lrcLineResult) continue const t2 = this.getIntv(lrcLineResult[1]) if (Math.abs(t1 - t2) < 100) { const lrc = line.replace(timeRxp, lrcLineResult[0]).trim() if (!lrc) continue newLrc.push(lrc) break } temp.push(lrcLine) } lrcLines = [...temp, ...lrcLines] temp = [] }) return newLrc.join('\n') }, parse(ylrc, ytlrc, yrlrc, lrc, tlrc, rlrc) { const info = { lyric: '', tlyric: '', rlyric: '', lxlyric: '', } if (ylrc) { let lines = this.parseHeaderInfo(ylrc) if (lines) { const result = this.parseLyric(lines) if (ytlrc) { const lines = this.parseHeaderInfo(ytlrc) if (lines) { // if (lines.length == result.lyricLines.length) { info.tlyric = this.fixTimeTag(result.lyric, lines.join('\n')) // } else info.tlyric = lines.join('\n') } } if (yrlrc) { const lines = this.parseHeaderInfo(yrlrc) if (lines) { // if (lines.length == result.lyricLines.length) { info.rlyric = this.fixTimeTag(result.lyric, lines.join('\n')) // } else info.rlyric = lines.join('\n') } } const timeRxp = /^\[[\d:.]+\]/ const headers = lines.filter(l => timeRxp.test(l)).join('\n') info.lyric = `${headers}\n${result.lyric}` info.lxlyric = result.lxlyric return info } } if (lrc) { const lines = this.parseHeaderInfo(lrc) if (lines) info.lyric = lines.join('\n') } if (tlrc) { const lines = this.parseHeaderInfo(tlrc) if (lines) info.tlyric = lines.join('\n') } if (rlrc) { const lines = this.parseHeaderInfo(rlrc) if (lines) info.rlyric = lines.join('\n') } return info }, } // 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 ?? '', // // lxlyric: parseLyric(body.klyric.lyric), // } // }) // return requestObj // } // https://github.com/lyswhut/lx-music-mobile/issues/370 const fixTimeLabel = (lrc, tlrc, romalrc) => { if (lrc) { let newLrc = lrc.replace(/\[(\d{2}:\d{2}):(\d{2})]/g, '[$1.$2]') let newTlrc = tlrc?.replace(/\[(\d{2}:\d{2}):(\d{2})]/g, '[$1.$2]') ?? tlrc if (newLrc != lrc || newTlrc != tlrc) { lrc = newLrc tlrc = newTlrc if (romalrc) romalrc = romalrc.replace(/\[(\d{2}:\d{2}):(\d{2,3})]/g, '[$1.$2]').replace(/\[(\d{2}:\d{2}\.\d{2})0]/g, '[$1]') } } return { lrc, tlrc, romalrc } } // https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/lyric_new.js export default songmid => { const requestObj = eapiRequest('/api/song/lyric/v1', { id: songmid, cp: false, tv: 0, lv: 0, rv: 0, kv: 0, yv: 0, ytv: 0, yrv: 0, }) requestObj.promise = requestObj.promise.then(({ body }) => { // console.log(body) 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 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')) return info }) return requestObj }