From 9dc9d3b1b7f9bf904e79f6d2d0f417d876278b12 Mon Sep 17 00:00:00 2001 From: lyswhut Date: Wed, 16 Oct 2019 00:40:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BD=91=E6=98=93=E4=BA=9112?= =?UTF-8?q?8k=E7=9B=B4=E6=8E=A5=E8=AF=95=E5=90=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- publish/changeLog.md | 1 + .../components/material/DownloadModal.vue | 3 +- src/renderer/components/material/SongList.vue | 8 ++-- src/renderer/utils/music/wy/api-internal.js | 41 ------------------- src/renderer/utils/music/wy/api-test.js | 8 ++-- src/renderer/utils/music/wy/index.js | 6 ++- src/renderer/utils/music/wy/lyric.js | 27 ++++++++++++ src/renderer/utils/music/wy/musicInfo.js | 25 +++++++++++ src/renderer/utils/music/wy/utils/crypto.js | 34 +++++++++++++++ src/renderer/views/Leaderboard.vue | 1 + src/renderer/views/List.vue | 8 ++-- src/renderer/views/Search.vue | 8 ++-- src/renderer/views/SongList.vue | 1 + 13 files changed, 110 insertions(+), 61 deletions(-) delete mode 100644 src/renderer/utils/music/wy/api-internal.js create mode 100644 src/renderer/utils/music/wy/lyric.js create mode 100644 src/renderer/utils/music/wy/musicInfo.js create mode 100644 src/renderer/utils/music/wy/utils/crypto.js diff --git a/publish/changeLog.md b/publish/changeLog.md index 4cce1f79..7e86590d 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -2,3 +2,4 @@ - 修复QQ源歌单无法翻页Bug - 修复默认列表没有创建时无法显示收藏列表的Bug +- 修复网易云128k直接试听 diff --git a/src/renderer/components/material/DownloadModal.vue b/src/renderer/components/material/DownloadModal.vue index d31bb125..79548d0d 100644 --- a/src/renderer/components/material/DownloadModal.vue +++ b/src/renderer/components/material/DownloadModal.vue @@ -5,7 +5,7 @@ material-modal(:show="show" :bg-close="bgClose" @close="handleClose") | {{ info.name }} br | {{ info.singer }} - material-btn(:class="$style.btn" :title="!checkSource(type.type) && '目前腾讯音源仅支持下载128k音质'" :disabled="!checkSource(type.type)" :key="type.type" @click="handleClick(type.type)" v-for="type in info.types") {{getTypeName(type.type)}} {{ type.type.toUpperCase() }}{{ type.size && ` - ${type.size.toUpperCase()}` }} + material-btn(:class="$style.btn" :title="!checkSource(type.type) && '腾讯、网易音源仅支持下载128k音质'" :disabled="!checkSource(type.type)" :key="type.type" @click="handleClick(type.type)" v-for="type in info.types") {{getTypeName(type.type)}} {{ type.type.toUpperCase() }}{{ type.size && ` - ${type.size.toUpperCase()}` }} @@ -52,7 +52,6 @@ export default { checkSource(type) { switch (this.musicInfo.source) { case 'wy': - return false case 'tx': return type == '128k' diff --git a/src/renderer/components/material/SongList.vue b/src/renderer/components/material/SongList.vue index 2370b6b2..ea117e49 100644 --- a/src/renderer/components/material/SongList.vue +++ b/src/renderer/components/material/SongList.vue @@ -29,9 +29,9 @@ div(:class="$style.songList") td(style="width: 20%; padding-left: 0; padding-right: 0;") material-list-buttons(:index="index" :search-btn="true" :remove-btn="false" @btn-click="handleListBtnClick" - :listAdd-btn="item.source == 'kw' || (!isAPITemp && item.source != 'wy')" - :play-btn="item.source == 'kw' || (!isAPITemp && item.source != 'wy')" - :download-btn="item.source == 'kw' || (!isAPITemp && item.source != 'wy')") + :listAdd-btn="item.source == 'kw' || (!isAPITemp)" + :play-btn="item.source == 'kw' || (!isAPITemp)" + :download-btn="item.source == 'kw' || (!isAPITemp)") //- button.btn-info(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k'] || item._types.flac" @click.stop='openDownloadModal(index)') 下载 //- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听 //- button.btn-success(type='button' v-if="(item._types['128k'] || item._types['192k'] || item._types['320k']) && userInfo" @click.stop='showListModal(index)') + @@ -140,7 +140,7 @@ export default { this.clickIndex = index return } - this.emitEvent((this.source == 'kw' || (!this.isAPITemp && this.list[index].source != 'wy')) ? 'testPlay' : 'search', index) + this.emitEvent((this.source == 'kw' || !this.isAPITemp) ? 'testPlay' : 'search', index) this.clickTime = 0 this.clickIndex = -1 }, diff --git a/src/renderer/utils/music/wy/api-internal.js b/src/renderer/utils/music/wy/api-internal.js deleted file mode 100644 index d836e810..00000000 --- a/src/renderer/utils/music/wy/api-internal.js +++ /dev/null @@ -1,41 +0,0 @@ -import { httpFatch } from '../../request' -import { requestMsg } from '../../message' -import { headers, timeout } from '../options' - -const api_messoer = { - getMusicUrl(songInfo, type) { - const requestObj = httpFatch(`https://v1.itooi.cn/netease/url?id=${songInfo.songmid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, { - method: 'get', - timeout, - headers, - }) - requestObj.promise = requestObj.promise.then(({ body }) => { - return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail)) - }) - return requestObj - }, - getPic(songInfo) { - const requestObj = httpFatch(`https://v1.itooi.cn/netease/pic?id=${songInfo.songmid}&isRedirect=0`, { - method: 'get', - timeout, - headers, - }) - requestObj.promise = requestObj.promise.then(({ body }) => { - return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail)) - }) - return requestObj - }, - getLyric(songInfo) { - const requestObj = httpFatch(`https://v1.itooi.cn/netease/lrc?id=${songInfo.songmid}&isRedirect=0`, { - method: 'get', - timeout, - headers, - }) - requestObj.promise = requestObj.promise.then(({ body }) => { - return body ? Promise.resolve(body) : Promise.reject(new Error(requestMsg.fail)) - }) - return requestObj - }, -} - -export default api_messoer diff --git a/src/renderer/utils/music/wy/api-test.js b/src/renderer/utils/music/wy/api-test.js index efaa0a72..42a3b496 100644 --- a/src/renderer/utils/music/wy/api-test.js +++ b/src/renderer/utils/music/wy/api-test.js @@ -15,8 +15,8 @@ const api_test = { }) return requestObj }, - getPic(songInfo) { - const requestObj = httpFetch(`http://ts.tempmusic.tk/pic/wy/${songInfo.songmid}`, { +/* getPic(songInfo) { + const requestObj = httpFetch(`http://localhost:3100/pic/wy/${songInfo.songmid}`, { method: 'get', timeout, headers, @@ -28,7 +28,7 @@ const api_test = { return requestObj }, getLyric(songInfo) { - const requestObj = httpFetch(`http://ts.tempmusic.tk/lrc/wy/${songInfo.songmid}`, { + const requestObj = httpFetch(`http://localhost:3100/lrc/wy/${songInfo.songmid}`, { method: 'get', timeout, headers, @@ -38,7 +38,7 @@ const api_test = { return body.code === 0 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail)) }) return requestObj - }, + }, */ } export default api_test diff --git a/src/renderer/utils/music/wy/index.js b/src/renderer/utils/music/wy/index.js index 447df0c0..f5457b66 100644 --- a/src/renderer/utils/music/wy/index.js +++ b/src/renderer/utils/music/wy/index.js @@ -1,5 +1,7 @@ import leaderboard from './leaderboard' import api_source from '../api-source' +import getLyric from './lyric' +import getMusicInfo from './musicInfo' const wy = { leaderboard, @@ -7,10 +9,10 @@ const wy = { return api_source('wy').getMusicUrl(songInfo, type) }, getLyric(songInfo) { - return api_source('wy').getLyric(songInfo) + return getLyric(songInfo.songmid) }, getPic(songInfo) { - return api_source('wy').getPic(songInfo) + return getMusicInfo(songInfo.songmid).then(info => info.al.picUrl) }, } diff --git a/src/renderer/utils/music/wy/lyric.js b/src/renderer/utils/music/wy/lyric.js new file mode 100644 index 00000000..84043dbf --- /dev/null +++ b/src/renderer/utils/music/wy/lyric.js @@ -0,0 +1,27 @@ +import { httpFetch } from '../../request' +import { linuxapi } from './utils/crypto' + +export default songmid => { + const requestObj = httpFetch('https://music.163.com/api/linux/forward', { + 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', + Referer: 'https://music.163.com/song?id=' + songmid, + origin: 'https://music.163.com', + }, + form: linuxapi({ + method: 'POST', + url: 'https://music.163.com/api/song/lyric?lv=-1&kv=-1&tv=-1', + params: { + id: songmid, + }, + }), + }) + requestObj.promise = requestObj.promise.then(({ body }) => { + // console.log(body) + if (body.code !== 200) return Promise.reject('获取歌词失败') + return body.lrc.lyric + }) + return requestObj +} + diff --git a/src/renderer/utils/music/wy/musicInfo.js b/src/renderer/utils/music/wy/musicInfo.js new file mode 100644 index 00000000..d098dc94 --- /dev/null +++ b/src/renderer/utils/music/wy/musicInfo.js @@ -0,0 +1,25 @@ +// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js +import { httpFetch } from '../../request' +import { weapi } from './utils/crypto' + +export default songmid => { + const requestObj = httpFetch('https://music.163.com/weapi/v3/song/detail', { + 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', + Referer: 'https://music.163.com/song?id=' + songmid, + origin: 'https://music.163.com', + }, + form: weapi({ + c: `[{"id":${songmid}}]`, + ids: '[songmid]', + }), + }) + requestObj.promise = requestObj.promise.then(({ body }) => { + // console.log(body) + if (body.code !== 200 || !body.songs.length) return Promise.reject('获取歌曲信息失败') + return body.songs[0] + }) + return requestObj +} + diff --git a/src/renderer/utils/music/wy/utils/crypto.js b/src/renderer/utils/music/wy/utils/crypto.js new file mode 100644 index 00000000..affe51fa --- /dev/null +++ b/src/renderer/utils/music/wy/utils/crypto.js @@ -0,0 +1,34 @@ +// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/util/crypto.js + +import { createCipheriv, publicEncrypt, constants, randomBytes } from 'crypto' +const iv = Buffer.from('0102030405060708') +const presetKey = Buffer.from('0CoJUm6Qyw8W8jud') +const linuxapiKey = Buffer.from('rFgB&h#%2?^eDg:Q') +const base62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' +const publicKey = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----' + +const aesEncrypt = (buffer, mode, key, iv) => { + const cipher = createCipheriv('aes-128-' + mode, key, iv) + return Buffer.concat([cipher.update(buffer), cipher.final()]) +} + +const rsaEncrypt = (buffer, key) => { + buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer]) + return publicEncrypt({ key: key, padding: constants.RSA_NO_PADDING }, buffer) +} + +export const weapi = object => { + const text = JSON.stringify(object) + const secretKey = randomBytes(16).map(n => (base62.charAt(n % 62).charCodeAt())) + return { + params: aesEncrypt(Buffer.from(aesEncrypt(Buffer.from(text), 'cbc', presetKey, iv).toString('base64')), 'cbc', secretKey, iv).toString('base64'), + encSecKey: rsaEncrypt(secretKey.reverse(), publicKey).toString('hex'), + } +} + +export const linuxapi = object => { + const text = JSON.stringify(object) + return { + eparams: aesEncrypt(Buffer.from(text), 'ecb', linuxapiKey, '').toString('hex').toUpperCase(), + } +} diff --git a/src/renderer/views/Leaderboard.vue b/src/renderer/views/Leaderboard.vue index ca7314ae..4f5fe25c 100644 --- a/src/renderer/views/Leaderboard.vue +++ b/src/renderer/views/Leaderboard.vue @@ -126,6 +126,7 @@ export default { handleAddDownloadMultiple(type) { switch (this.source) { // case 'kg': + case 'tx': case 'wy': type = '128k' } diff --git a/src/renderer/views/List.vue b/src/renderer/views/List.vue index 04a49e37..e704a06f 100644 --- a/src/renderer/views/List.vue +++ b/src/renderer/views/List.vue @@ -18,7 +18,7 @@ table tbody tr(v-for='(item, index) in list' :key='item.songmid' - @click="handleDoubleClick(index)" :class="[isPlayList && playIndex === index ? $style.active : '', (isAPITemp && item.source != 'kw') || item.source == 'wy' ? $style.disabled : '']") + @click="handleDoubleClick(index)" :class="[isPlayList && playIndex === index ? $style.active : '', (isAPITemp && item.source != 'kw') ? $style.disabled : '']") td.nobreak.center(style="width: 37px;" @click.stop) material-checkbox(:id="index.toString()" v-model="selectdData" :value="item") td.break(style="width: 25%;") @@ -218,7 +218,7 @@ export default { this.clickIndex = -1 }, testPlay(index) { - if ((this.isAPITemp && this.list[index].source != 'kw') || this.list[index].source == 'wy') return + if (this.isAPITemp && this.list[index].source != 'kw') return this.setPlayList({ list: this.list, listId: this.listId, index }) }, handleRemove(index) { @@ -228,7 +228,7 @@ export default { switch (info.action) { case 'download': { const minfo = this.list[info.index] - if ((this.isAPITemp && minfo.source != 'kw') || minfo.source == 'wy') return + if (this.isAPITemp && minfo.source != 'kw') return this.musicInfo = minfo this.$nextTick(() => { this.isShowDownload = true @@ -261,7 +261,7 @@ export default { this.selectdData = [] }, handleAddDownloadMultiple(type) { - const list = this.setting.apiSource == 'temp' ? this.selectdData.filter(s => s.source == 'kw') : this.selectdData.filter(s => s.source != 'wy') + const list = this.setting.apiSource == 'temp' ? this.selectdData.filter(s => s.source == 'kw') : [...this.selectdData] this.createDownloadMultiple({ list, type }) this.resetSelect() this.isShowDownloadMultiple = false diff --git a/src/renderer/views/Search.vue b/src/renderer/views/Search.vue index fb2c0569..5aa16aba 100644 --- a/src/renderer/views/Search.vue +++ b/src/renderer/views/Search.vue @@ -30,8 +30,8 @@ td.break(style="width: 25%;") {{item.albumName}} td(style="width: 15%; padding-left: 0; padding-right: 0;") material-list-buttons(:index="index" :remove-btn="false" :class="$style.listBtn" - :play-btn="item.source == 'kw' || (!isAPITemp && item.source != 'wy')" - :download-btn="item.source == 'kw' || (!isAPITemp && item.source != 'wy')" + :play-btn="item.source == 'kw' || !isAPITemp" + :download-btn="item.source == 'kw' || !isAPITemp" @btn-click="handleListBtnClick") td(style="width: 10%;") {{item.interval || '--/--'}} div(:class="$style.pagination") @@ -189,7 +189,7 @@ export default { targetSong = this.selectdData[0] this.listAddMultiple({ id: 'default', list: this.filterList(this.selectdData) }) } else { - if ((this.isAPITemp && this.listInfo.list[index].source != 'kw') || this.listInfo.list[index].source == 'wy') return + if (this.isAPITemp && this.listInfo.list[index].source != 'kw') return targetSong = this.listInfo.list[index] this.listAdd({ id: 'default', musicInfo: targetSong }) } @@ -238,7 +238,7 @@ export default { } }, filterList(list) { - return this.setting.apiSource == 'temp' ? list.filter(s => s.source == 'kw') : list.filter(s => s.source != 'wy') + return this.setting.apiSource == 'temp' ? list.filter(s => s.source == 'kw') : [...list] }, handleListAddModalClose(isSelect) { if (isSelect) this.resetSelect() diff --git a/src/renderer/views/SongList.vue b/src/renderer/views/SongList.vue index 27700b86..f5079f24 100644 --- a/src/renderer/views/SongList.vue +++ b/src/renderer/views/SongList.vue @@ -200,6 +200,7 @@ export default { handleAddDownloadMultiple(type) { switch (this.source) { // case 'kg': + case 'tx': case 'wy': type = '128k' }