新增虾米音源
parent
7bd4002f09
commit
ccf362db41
|
@ -17,7 +17,7 @@ module.exports = {
|
||||||
'top', 'left', 'bottom', 'right',
|
'top', 'left', 'bottom', 'right',
|
||||||
'border-radius',
|
'border-radius',
|
||||||
],
|
],
|
||||||
selectorBlackList: ['html'],
|
selectorBlackList: ['html', 'ignore-to-rem'],
|
||||||
replace: true,
|
replace: true,
|
||||||
mediaQuery: false,
|
mediaQuery: false,
|
||||||
minPixelValue: 0,
|
minPixelValue: 0,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
- 新增托盘设置,默认关闭,可到设置开启,感谢@LasyIsLazy提交的PR
|
- 新增托盘设置,默认关闭,可到设置开启,感谢@LasyIsLazy提交的PR
|
||||||
- 新增打开酷狗源用户歌单
|
- 新增打开酷狗源用户歌单
|
||||||
- 新增使用协议
|
- 新增使用协议
|
||||||
|
- 新增虾米音源
|
||||||
|
|
||||||
### 优化
|
### 优化
|
||||||
|
|
||||||
|
|
|
@ -36,4 +36,7 @@ module.exports = {
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
navigationUrlWhiteList: [
|
||||||
|
/^https:\/\/www\.xiami\.com/,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,4 @@ global.lx_event = {}
|
||||||
|
|
||||||
const Tray = require('./tray')
|
const Tray = require('./tray')
|
||||||
|
|
||||||
if (!global.lx_event.setting) global.lx_event.tray = new Tray()
|
if (!global.lx_event.tray) global.lx_event.tray = new Tray()
|
||||||
|
|
|
@ -21,11 +21,13 @@ app.on('second-instance', (event, argv, cwd) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const isDev = global.isDev = process.env.NODE_ENV !== 'production'
|
const isDev = global.isDev = process.env.NODE_ENV !== 'production'
|
||||||
|
const { navigationUrlWhiteList } = require('../common/config')
|
||||||
|
|
||||||
app.on('web-contents-created', (event, contents) => {
|
app.on('web-contents-created', (event, contents) => {
|
||||||
contents.on('will-navigate', (event, navigationUrl) => {
|
contents.on('will-navigate', (event, navigationUrl) => {
|
||||||
if (isDev) return console.log('navigation to url:', navigationUrl)
|
if (isDev) return console.log('navigation to url:', navigationUrl)
|
||||||
event.preventDefault()
|
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) return event.preventDefault()
|
||||||
|
console.log('navigation to url:', navigationUrl)
|
||||||
})
|
})
|
||||||
contents.on('new-window', async(event, navigationUrl) => {
|
contents.on('new-window', async(event, navigationUrl) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
@ -33,6 +35,19 @@ app.on('web-contents-created', (event, contents) => {
|
||||||
console.log(navigationUrl)
|
console.log(navigationUrl)
|
||||||
await shell.openExternal(navigationUrl)
|
await shell.openExternal(navigationUrl)
|
||||||
})
|
})
|
||||||
|
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
||||||
|
// Strip away preload scripts if unused or verify their location is legitimate
|
||||||
|
delete webPreferences.preload
|
||||||
|
delete webPreferences.preloadURL
|
||||||
|
|
||||||
|
// Disable Node.js integration
|
||||||
|
webPreferences.nodeIntegration = false
|
||||||
|
|
||||||
|
// Verify URL being loaded
|
||||||
|
if (!navigationUrlWhiteList.some(url => url.test(params.src))) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// https://github.com/electron/electron/issues/22691
|
// https://github.com/electron/electron/issues/22691
|
||||||
|
|
|
@ -12,3 +12,6 @@ require('./getCacheSize')
|
||||||
require('./setIgnoreMouseEvent')
|
require('./setIgnoreMouseEvent')
|
||||||
require('./getEnvParams')
|
require('./getEnvParams')
|
||||||
require('./tray')
|
require('./tray')
|
||||||
|
require('./updateSetting')
|
||||||
|
|
||||||
|
require('./xm_verify')
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
const { mainOn } = require('../../common/ipc')
|
||||||
|
|
||||||
|
|
||||||
|
mainOn('updateAppSetting', (event, setting) => {
|
||||||
|
if (!setting) return
|
||||||
|
global.appSetting = setting
|
||||||
|
})
|
|
@ -0,0 +1,43 @@
|
||||||
|
const { BrowserView } = require('electron')
|
||||||
|
const { mainHandle } = require('../../common/ipc')
|
||||||
|
const { getWindowSizeInfo } = require('../utils')
|
||||||
|
|
||||||
|
let view
|
||||||
|
|
||||||
|
const closeView = async() => {
|
||||||
|
if (!view) return
|
||||||
|
// await view.webContents.session.clearCache()
|
||||||
|
if (global.mainWindow) global.mainWindow.removeBrowserView(view)
|
||||||
|
await view.webContents.session.clearStorageData()
|
||||||
|
view.destroy()
|
||||||
|
view = null
|
||||||
|
}
|
||||||
|
|
||||||
|
mainHandle('xm_verify_open', (event, url) => new Promise((resolve, reject) => {
|
||||||
|
if (!global.mainWindow) return reject(new Error('mainwindow is undefined'))
|
||||||
|
if (view) view.destroy()
|
||||||
|
|
||||||
|
view = new BrowserView()
|
||||||
|
view.webContents.on('did-finish-load', () => {
|
||||||
|
if (/punish\?/.test(view.webContents.getURL())) return
|
||||||
|
let ses = view.webContents.session
|
||||||
|
ses.cookies.get({ name: 'x5sec' })
|
||||||
|
.then(async([x5sec]) => {
|
||||||
|
await closeView()
|
||||||
|
if (!x5sec) return reject(new Error('get x5sec failed'))
|
||||||
|
resolve(x5sec.value)
|
||||||
|
}).catch(async err => {
|
||||||
|
await closeView()
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
global.mainWindow.setBrowserView(view)
|
||||||
|
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
||||||
|
view.setBounds({ x: (windowSizeInfo.width - 360) / 2, y: ((windowSizeInfo.height - 320 + 52) / 2), width: 360, height: 320 })
|
||||||
|
view.webContents.loadURL(url)
|
||||||
|
}))
|
||||||
|
|
||||||
|
mainHandle('xm_verify_close', async() => {
|
||||||
|
await closeView()
|
||||||
|
})
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
core-icons
|
core-icons
|
||||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||||
material-version-modal(v-show="version.showModal")
|
material-version-modal(v-show="version.showModal")
|
||||||
|
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
|
||||||
#container(v-else :class="theme")
|
#container(v-else :class="theme")
|
||||||
core-aside#left
|
core-aside#left
|
||||||
#right
|
#right
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
core-icons
|
core-icons
|
||||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||||
material-version-modal(v-show="version.showModal")
|
material-version-modal(v-show="version.showModal")
|
||||||
|
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -42,6 +44,9 @@ export default {
|
||||||
apiSource: 'test',
|
apiSource: 'test',
|
||||||
proxy: {},
|
proxy: {},
|
||||||
isShowPact: false,
|
isShowPact: false,
|
||||||
|
xm: {
|
||||||
|
isShowVerify: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
updateTimeout: null,
|
updateTimeout: null,
|
||||||
envParams: {
|
envParams: {
|
||||||
|
@ -63,6 +68,7 @@ export default {
|
||||||
created() {
|
created() {
|
||||||
this.saveSetting = throttle(n => {
|
this.saveSetting = throttle(n => {
|
||||||
window.electronStore_config.set('setting', n)
|
window.electronStore_config.set('setting', n)
|
||||||
|
rendererSend('updateAppSetting', n)
|
||||||
})
|
})
|
||||||
this.saveDefaultList = throttle(n => {
|
this.saveDefaultList = throttle(n => {
|
||||||
window.electronStore_list.set('defaultList', n)
|
window.electronStore_list.set('defaultList', n)
|
||||||
|
@ -286,6 +292,9 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
handleXMVerifyModalClose() {
|
||||||
|
music.xm.closeVerifyModal()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.clearUpdateTimeout()
|
this.clearUpdateTimeout()
|
||||||
|
|
|
@ -434,14 +434,14 @@ export default {
|
||||||
switch (songInfo.source) {
|
switch (songInfo.source) {
|
||||||
case 'wy':
|
case 'wy':
|
||||||
case 'tx':
|
case 'tx':
|
||||||
// case 'kg':
|
|
||||||
return '128k'
|
return '128k'
|
||||||
|
// case 'kg':
|
||||||
}
|
}
|
||||||
let type = '128k'
|
let type = '128k'
|
||||||
if (highQuality && songInfo._types['320k']) type = '320k'
|
if (highQuality && songInfo._types['320k']) type = '320k'
|
||||||
return type
|
return type
|
||||||
},
|
},
|
||||||
setUrl(targetSong, isRefresh) {
|
setUrl(targetSong, isRefresh, isRetryed = false) {
|
||||||
let type = this.getPlayType(this.setting.player.highQuality, targetSong)
|
let type = this.getPlayType(this.setting.player.highQuality, targetSong)
|
||||||
this.musicInfo.url = targetSong.typeUrl[type]
|
this.musicInfo.url = targetSong.typeUrl[type]
|
||||||
this.status = this.$t('core.player.geting_url')
|
this.status = this.$t('core.player.geting_url')
|
||||||
|
@ -450,6 +450,7 @@ export default {
|
||||||
this.audio.src = this.musicInfo.url = targetSong.typeUrl[type]
|
this.audio.src = this.musicInfo.url = targetSong.typeUrl[type]
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if (err.message == requestMsg.cancelRequest) return
|
if (err.message == requestMsg.cancelRequest) return
|
||||||
|
if (!isRetryed) return this.setUrl(targetSong, isRefresh, true)
|
||||||
this.status = err.message
|
this.status = err.message
|
||||||
this.addDelayNextTimeout()
|
this.addDelayNextTimeout()
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
|
|
|
@ -41,6 +41,7 @@ export default {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'flac':
|
case 'flac':
|
||||||
case 'ape':
|
case 'ape':
|
||||||
|
case 'wav':
|
||||||
return this.$t('material.download_modal.lossless')
|
return this.$t('material.download_modal.lossless')
|
||||||
case '320k':
|
case '320k':
|
||||||
return this.$t('material.download_modal.high_quality')
|
return this.$t('material.download_modal.high_quality')
|
||||||
|
@ -54,6 +55,8 @@ export default {
|
||||||
case 'wy':
|
case 'wy':
|
||||||
case 'tx':
|
case 'tx':
|
||||||
return type == '128k'
|
return type == '128k'
|
||||||
|
case 'xm':
|
||||||
|
return type == '128k' || type == '320k'
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -8,7 +8,7 @@ material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
|
||||||
material-btn(:class="$style.btn" @click="handleClick('128k')") {{$t('material.download_multiple_modal.normal')}} - 128K
|
material-btn(:class="$style.btn" @click="handleClick('128k')") {{$t('material.download_multiple_modal.normal')}} - 128K
|
||||||
material-btn(:class="$style.btn" @click="handleClick('320k')") {{$t('material.download_multiple_modal.high_quality')}} - 320K
|
material-btn(:class="$style.btn" @click="handleClick('320k')") {{$t('material.download_multiple_modal.high_quality')}} - 320K
|
||||||
//- material-btn(:class="$style.btn" @click="handleClick('ape')") 无损音质 - APE
|
//- material-btn(:class="$style.btn" @click="handleClick('ape')") 无损音质 - APE
|
||||||
material-btn(:class="$style.btn" @click="handleClick('flac')") {{$t('material.download_multiple_modal.lossless')}} - FLAC
|
material-btn(:class="$style.btn" @click="handleClick('flac')") {{$t('material.download_multiple_modal.lossless')}} - FLAC/WAV
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -134,7 +134,7 @@ export default {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
|
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 70%;
|
max-height: 80%;
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -22,7 +22,7 @@ div(:class="$style.songList")
|
||||||
material-checkbox(:id="index.toString()" v-model="selectdList" @change="handleChangeSelect" :value="item")
|
material-checkbox(:id="index.toString()" v-model="selectdList" @change="handleChangeSelect" :value="item")
|
||||||
td.break(style="width: 25%;")
|
td.break(style="width: 25%;")
|
||||||
span.select {{item.name}}
|
span.select {{item.name}}
|
||||||
span.badge.badge-theme-success(:class="$style.labelQuality" v-if="item._types.ape || item._types.flac") {{$t('material.song_list.lossless')}}
|
span.badge.badge-theme-success(:class="$style.labelQuality" v-if="item._types.ape || item._types.flac || item._types.wav") {{$t('material.song_list.lossless')}}
|
||||||
span.badge.badge-theme-info(:class="$style.labelQuality" v-else-if="item._types['320k']") {{$t('material.song_list.high_quality')}}
|
span.badge.badge-theme-info(:class="$style.labelQuality" v-else-if="item._types['320k']") {{$t('material.song_list.high_quality')}}
|
||||||
td.break(style="width: 20%;")
|
td.break(style="width: 20%;")
|
||||||
span.select {{item.singer}}
|
span.select {{item.singer}}
|
||||||
|
|
|
@ -112,6 +112,7 @@ export default {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
min-width: 56px;
|
||||||
// color: @color-btn;
|
// color: @color-btn;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: background-color @transition-theme;
|
transition: background-color @transition-theme;
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<template lang="pug">
|
||||||
|
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
|
||||||
|
main.ignore-to-rem(:class="$style.main")
|
||||||
|
h2 {{$t('material.xm_verify_modal.title')}}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
bgClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClose() {
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="less" module>
|
||||||
|
@import '../../assets/styles/layout.less';
|
||||||
|
|
||||||
|
.main {
|
||||||
|
background: #fff !important;
|
||||||
|
&:global(.ignore-to-rem) {
|
||||||
|
padding: 15px;
|
||||||
|
width: 360px;
|
||||||
|
height: 330px;
|
||||||
|
h2 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 13px;
|
||||||
|
color: @color-theme_2-font;
|
||||||
|
line-height: 1.3;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"btn_tip": "腾讯、网易音源仅支持下载128k音质",
|
"btn_tip": "腾讯、网易音源仅支持下载128k音质\n虾米音源不支持下载无损音质",
|
||||||
"lossless": "无损音质",
|
"lossless": "无损音质",
|
||||||
"high_quality": "高品音质",
|
"high_quality": "高品音质",
|
||||||
"normal": "普通音质"
|
"normal": "普通音质"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"title": "虾米音乐校验"
|
||||||
|
}
|
|
@ -15,6 +15,7 @@
|
||||||
"source_tx": "企鹅音乐",
|
"source_tx": "企鹅音乐",
|
||||||
"source_wy": "网易音乐",
|
"source_wy": "网易音乐",
|
||||||
"source_mg": "咪咕音乐",
|
"source_mg": "咪咕音乐",
|
||||||
|
"source_xm": "虾米音乐",
|
||||||
"source_bd": "百度音乐",
|
"source_bd": "百度音乐",
|
||||||
|
|
||||||
"source_all": "聚合搜索",
|
"source_all": "聚合搜索",
|
||||||
|
@ -24,6 +25,7 @@
|
||||||
"source_alias_tx": "小秋音乐",
|
"source_alias_tx": "小秋音乐",
|
||||||
"source_alias_wy": "小芸音乐",
|
"source_alias_wy": "小芸音乐",
|
||||||
"source_alias_mg": "小蜜音乐",
|
"source_alias_mg": "小蜜音乐",
|
||||||
|
"source_alias_xm": "小霞音乐",
|
||||||
"source_alias_bd": "小杜音乐",
|
"source_alias_bd": "小杜音乐",
|
||||||
|
|
||||||
"source_alias_all": "聚合大会"
|
"source_alias_all": "聚合大会"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"btn_tip": "騰訊、網易音源僅支持下載128k音質",
|
"btn_tip": "騰訊、網易、音源僅支持下載128k音質\n蝦米音源不支持下載無損音質",
|
||||||
"lossless": "無損音質",
|
"lossless": "無損音質",
|
||||||
"high_quality": "高品音質",
|
"high_quality": "高品音質",
|
||||||
"normal": "普通音質"
|
"normal": "普通音質"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"title": "蝦米音樂校驗"
|
||||||
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
"source_tx": "企鵝音樂",
|
"source_tx": "企鵝音樂",
|
||||||
"source_wy": "網易音樂",
|
"source_wy": "網易音樂",
|
||||||
"source_mg": "咪咕音樂",
|
"source_mg": "咪咕音樂",
|
||||||
|
"source_xm": "蝦米音樂",
|
||||||
"source_bd": "百度音樂",
|
"source_bd": "百度音樂",
|
||||||
"source_all": "聚合搜索",
|
"source_all": "聚合搜索",
|
||||||
"source_alias_kw": "小蝸音樂",
|
"source_alias_kw": "小蝸音樂",
|
||||||
|
@ -22,5 +23,6 @@
|
||||||
"source_alias_wy": "小芸音樂",
|
"source_alias_wy": "小芸音樂",
|
||||||
"source_alias_mg": "小蜜音樂",
|
"source_alias_mg": "小蜜音樂",
|
||||||
"source_alias_bd": "小杜音樂",
|
"source_alias_bd": "小杜音樂",
|
||||||
|
"source_alias_xm": "小霞音樂",
|
||||||
"source_alias_all": "聚合大會"
|
"source_alias_all": "聚合大會"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"btn_tip": "Tencent and NetEase sources only support 128k audio quality",
|
"btn_tip": "Tencent and NetEase only support 128k audio quality\nXiami sources does not support 128k audio quality",
|
||||||
"lossless": "Lossless",
|
"lossless": "Lossless",
|
||||||
"high_quality": "High Quality",
|
"high_quality": "High Quality",
|
||||||
"normal": "Normal"
|
"normal": "Normal"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"title": "Xiami music verify"
|
||||||
|
}
|
|
@ -15,6 +15,7 @@
|
||||||
"source_tx": "Tencent",
|
"source_tx": "Tencent",
|
||||||
"source_wy": "Netease",
|
"source_wy": "Netease",
|
||||||
"source_mg": "Migu",
|
"source_mg": "Migu",
|
||||||
|
"source_xm": "Xiami",
|
||||||
"source_bd": "Baidu",
|
"source_bd": "Baidu",
|
||||||
"source_all": "Aggregated",
|
"source_all": "Aggregated",
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
"source_alias_tx": "TX Music",
|
"source_alias_tx": "TX Music",
|
||||||
"source_alias_wy": "WY Music",
|
"source_alias_wy": "WY Music",
|
||||||
"source_alias_mg": "MG Music",
|
"source_alias_mg": "MG Music",
|
||||||
|
"source_alias_xm": "XM Music",
|
||||||
"source_alias_bd": "BD Music",
|
"source_alias_bd": "BD Music",
|
||||||
|
|
||||||
"source_alias_all": "Aggregated"
|
"source_alias_all": "Aggregated"
|
||||||
|
|
|
@ -123,7 +123,7 @@ const downloadLyric = (downloadInfo, filePath) => {
|
||||||
? Promise.resolve(downloadInfo.musicInfo.lrc)
|
? Promise.resolve(downloadInfo.musicInfo.lrc)
|
||||||
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise
|
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise
|
||||||
promise.then(lrc => {
|
promise.then(lrc => {
|
||||||
if (lrc) saveLrc(filePath.replace(/(mp3|flac|ape)$/, 'lrc'), lrc)
|
if (lrc) saveLrc(filePath.replace(/(mp3|flac|ape|wav)$/, 'lrc'), lrc)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import kg_api_test from './kg/api-test'
|
||||||
import wy_api_test from './wy/api-test'
|
import wy_api_test from './wy/api-test'
|
||||||
import bd_api_test from './bd/api-test'
|
import bd_api_test from './bd/api-test'
|
||||||
import mg_api_test from './mg/api-test'
|
import mg_api_test from './mg/api-test'
|
||||||
|
import xm_api_test from './xm/api-test'
|
||||||
// import kw_api_internal from './kw/api-internal'
|
// import kw_api_internal from './kw/api-internal'
|
||||||
// import tx_api_internal from './tx/api-internal'
|
// import tx_api_internal from './tx/api-internal'
|
||||||
// import kg_api_internal from './kg/api-internal'
|
// import kg_api_internal from './kg/api-internal'
|
||||||
|
@ -18,6 +19,7 @@ const apis = {
|
||||||
wy_api_test,
|
wy_api_test,
|
||||||
bd_api_test,
|
bd_api_test,
|
||||||
mg_api_test,
|
mg_api_test,
|
||||||
|
xm_api_test,
|
||||||
// kw_api_internal,
|
// kw_api_internal,
|
||||||
// tx_api_internal,
|
// tx_api_internal,
|
||||||
// kg_api_internal,
|
// kg_api_internal,
|
||||||
|
@ -50,6 +52,8 @@ export default source => {
|
||||||
return getAPI('bd')
|
return getAPI('bd')
|
||||||
case 'mg':
|
case 'mg':
|
||||||
return getAPI('mg')
|
return getAPI('mg')
|
||||||
|
case 'xm':
|
||||||
|
return getAPI('xm')
|
||||||
default:
|
default:
|
||||||
return getAPI('kw')
|
return getAPI('kw')
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import tx from './tx'
|
||||||
import wy from './wy'
|
import wy from './wy'
|
||||||
import mg from './mg'
|
import mg from './mg'
|
||||||
import bd from './bd'
|
import bd from './bd'
|
||||||
|
import xm from './xm'
|
||||||
const sources = {
|
const sources = {
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
|
@ -26,6 +27,10 @@ const sources = {
|
||||||
name: '咪咕音乐',
|
name: '咪咕音乐',
|
||||||
id: 'mg',
|
id: 'mg',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: '虾米音乐',
|
||||||
|
id: 'xm',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: '百度音乐',
|
name: '百度音乐',
|
||||||
id: 'bd',
|
id: 'bd',
|
||||||
|
@ -37,6 +42,7 @@ const sources = {
|
||||||
wy,
|
wy,
|
||||||
mg,
|
mg,
|
||||||
bd,
|
bd,
|
||||||
|
xm,
|
||||||
}
|
}
|
||||||
export default {
|
export default {
|
||||||
...sources,
|
...sources,
|
||||||
|
|
|
@ -6,13 +6,15 @@ import crypto from 'crypto'
|
||||||
* @param {*} type
|
* @param {*} type
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const types = ['flac', 'ape', '320k', '192k', '128k']
|
const types = ['flac', 'wav', 'ape', '320k', '192k', '128k']
|
||||||
export const getMusicType = (info, type) => {
|
export const getMusicType = (info, type) => {
|
||||||
switch (info.source) {
|
switch (info.source) {
|
||||||
// case 'kg':
|
// case 'kg':
|
||||||
case 'wy':
|
case 'wy':
|
||||||
case 'tx':
|
case 'tx':
|
||||||
return '128k'
|
return '128k'
|
||||||
|
case 'xm':
|
||||||
|
if (type == 'wav') type = '320k'
|
||||||
}
|
}
|
||||||
const rangeType = types.slice(types.indexOf(type))
|
const rangeType = types.slice(types.indexOf(type))
|
||||||
for (const type of rangeType) {
|
for (const type of rangeType) {
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { httpFetch } from '../../request'
|
||||||
|
import { requestMsg } from '../../message'
|
||||||
|
import { headers, timeout } from '../options'
|
||||||
|
|
||||||
|
const api_test = {
|
||||||
|
getMusicUrl(songInfo, type) {
|
||||||
|
const requestObj = httpFetch(`http://ts.tempmusic.tk/url/xm/${songInfo.songmid}/${type}`, {
|
||||||
|
method: 'get',
|
||||||
|
timeout,
|
||||||
|
headers,
|
||||||
|
family: 4,
|
||||||
|
})
|
||||||
|
requestObj.promise = requestObj.promise.then(({ body }) => {
|
||||||
|
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
|
||||||
|
})
|
||||||
|
return requestObj
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default api_test
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { xmRequest } from './util'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
_requestObj: null,
|
||||||
|
async getList(retryNum = 0) {
|
||||||
|
if (this._requestObj) this._requestObj.cancelHttp()
|
||||||
|
if (retryNum > 2) return Promise.reject(new Error('try max num'))
|
||||||
|
|
||||||
|
const _requestObj = xmRequest('/api/search/getHotSearchWords')
|
||||||
|
const { body, statusCode } = await _requestObj.promise
|
||||||
|
// console.log(body)
|
||||||
|
if (statusCode != 200 || body.code !== 'SUCCESS') return this.getList(++retryNum)
|
||||||
|
// // console.log(body, statusCode)
|
||||||
|
return { source: 'xm', list: this.filterList(body.result.data.hotWords) }
|
||||||
|
},
|
||||||
|
filterList(rawList) {
|
||||||
|
return rawList.map(item => item.word)
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
import api_source from '../api-source'
|
||||||
|
import leaderboard from './leaderboard'
|
||||||
|
import songList from './songList'
|
||||||
|
import musicSearch from './musicSearch'
|
||||||
|
// import pic from './pic'
|
||||||
|
import lyric from './lyric'
|
||||||
|
import hotSearch from './hotSearch'
|
||||||
|
import { closeVerifyModal } from './util'
|
||||||
|
|
||||||
|
const xm = {
|
||||||
|
songList,
|
||||||
|
musicSearch,
|
||||||
|
leaderboard,
|
||||||
|
hotSearch,
|
||||||
|
closeVerifyModal,
|
||||||
|
getMusicUrl(songInfo, type) {
|
||||||
|
return api_source('xm').getMusicUrl(songInfo, type)
|
||||||
|
},
|
||||||
|
getLyric(songInfo) {
|
||||||
|
return lyric.getLyric(songInfo)
|
||||||
|
},
|
||||||
|
getPic(songInfo) {
|
||||||
|
return Promise.reject(new Error('fail'))
|
||||||
|
// return pic.getPic(songInfo)
|
||||||
|
},
|
||||||
|
// init() {
|
||||||
|
// getToken()
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
|
||||||
|
export default xm
|
|
@ -0,0 +1,147 @@
|
||||||
|
import { xmRequest } from './util'
|
||||||
|
import { formatPlayTime, sizeFormate } from '../../index'
|
||||||
|
// import jshtmlencode from 'js-htmlencode'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
limit: 200,
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
id: 'xmrgb',
|
||||||
|
name: '热歌榜',
|
||||||
|
bangid: '103',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'xmxgb',
|
||||||
|
name: '新歌榜',
|
||||||
|
bangid: '102',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'xmrcb',
|
||||||
|
name: '原创榜',
|
||||||
|
bangid: '104',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'xmdyb',
|
||||||
|
name: '抖音榜',
|
||||||
|
bangid: '332',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'xmkgb',
|
||||||
|
name: 'K歌榜',
|
||||||
|
bangid: '306',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'xmfxb',
|
||||||
|
name: '分享榜',
|
||||||
|
bangid: '307',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'xmrdtlb',
|
||||||
|
name: '讨论榜',
|
||||||
|
bangid: '331',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'xmgdslb',
|
||||||
|
name: '歌单榜',
|
||||||
|
bangid: '305',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'xmpjrgb',
|
||||||
|
name: '趴间榜',
|
||||||
|
bangid: '327',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'xmysysb',
|
||||||
|
name: '影视榜',
|
||||||
|
bangid: '324',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
requestObj: null,
|
||||||
|
getData(id) {
|
||||||
|
if (this.requestObj) this.requestObj.cancelHttp()
|
||||||
|
this.requestObj = xmRequest('/api/billboard/getBillboardDetail', { billboardId: id })
|
||||||
|
return this.requestObj.promise
|
||||||
|
},
|
||||||
|
getSinger(singers) {
|
||||||
|
let arr = []
|
||||||
|
singers.forEach(singer => {
|
||||||
|
arr.push(singer.artistName)
|
||||||
|
})
|
||||||
|
return arr.join('、')
|
||||||
|
},
|
||||||
|
filterData(rawList) {
|
||||||
|
// console.log(rawList)
|
||||||
|
let ids = new Set()
|
||||||
|
const list = []
|
||||||
|
rawList.forEach(songData => {
|
||||||
|
if (!songData) return
|
||||||
|
if (ids.has(songData.songId)) return
|
||||||
|
ids.add(songData.songId)
|
||||||
|
|
||||||
|
const types = []
|
||||||
|
const _types = {}
|
||||||
|
let size = null
|
||||||
|
for (const item of songData.purviewRoleVOs) {
|
||||||
|
if (!item.filesize) continue
|
||||||
|
size = sizeFormate(item.filesize)
|
||||||
|
switch (item.quality) {
|
||||||
|
case 's':
|
||||||
|
types.push({ type: 'wav', size })
|
||||||
|
_types.wav = {
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'h':
|
||||||
|
types.push({ type: '320k', size })
|
||||||
|
_types['320k'] = {
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'l':
|
||||||
|
types.push({ type: '128k', size })
|
||||||
|
_types['128k'] = {
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
types.reverse()
|
||||||
|
|
||||||
|
list.push({
|
||||||
|
singer: this.getSinger(songData.singerVOs),
|
||||||
|
name: songData.songName,
|
||||||
|
albumName: songData.albumName,
|
||||||
|
albumId: songData.albumId,
|
||||||
|
source: 'xm',
|
||||||
|
interval: formatPlayTime(parseInt(songData.length / 1000)),
|
||||||
|
songmid: songData.songId,
|
||||||
|
img: songData.albumLogo || songData.albumLogoS,
|
||||||
|
lrc: null,
|
||||||
|
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
|
||||||
|
types,
|
||||||
|
_types,
|
||||||
|
typeUrl: {},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return list
|
||||||
|
},
|
||||||
|
getList(id, page, retryNum = 0) {
|
||||||
|
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||||
|
let type = this.list.find(s => s.id === id)
|
||||||
|
if (!type) return Promise.reject()
|
||||||
|
return this.getData(type.bangid).then(({ statusCode, body }) => {
|
||||||
|
if (statusCode !== 200 || body.code !== 'SUCCESS') return this.getList(id, page, retryNum)
|
||||||
|
// console.log(body)
|
||||||
|
const list = this.filterData(body.result.data.billboard.songs)
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: parseInt(body.result.data.billboard.attributeMap.item_size),
|
||||||
|
list,
|
||||||
|
limit: this.limit,
|
||||||
|
page,
|
||||||
|
source: 'xm',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { httpGet, httpFetch } from '../../request'
|
||||||
|
import { xmRequest } from './util'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
failTime: 0,
|
||||||
|
expireTime: 60 * 1000 * 1000,
|
||||||
|
getLyricFile_1(url, retryNum = 0) {
|
||||||
|
if (retryNum > 5) return Promise.reject('歌词获取失败')
|
||||||
|
let requestObj = httpFetch(url)
|
||||||
|
requestObj.promise = requestObj.promise.then(({ body, statusCode }) => {
|
||||||
|
if (statusCode !== 200) {
|
||||||
|
let tryRequestObj = this.getLyric(url, ++retryNum)
|
||||||
|
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||||
|
return tryRequestObj.promise
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
})
|
||||||
|
return requestObj
|
||||||
|
},
|
||||||
|
getLyricFile_2(url, retryNum = 0) {
|
||||||
|
if (retryNum > 5) return Promise.reject('歌词获取失败')
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
httpGet(url, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
|
||||||
|
referer: 'https://www.xiami.com',
|
||||||
|
},
|
||||||
|
}, function(err, resp, body) {
|
||||||
|
if (err || resp.statusCode !== 200) return this.getLyricFile(url, ++retryNum).then(resolve).catch(reject)
|
||||||
|
return resolve(body)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getLyricUrl_1(songInfo, retryNum = 0) {
|
||||||
|
if (retryNum > 2) return Promise.reject('歌词获取失败')
|
||||||
|
let requestObj = xmRequest('/api/lyric/getSongLyrics', { songId: songInfo.songmid })
|
||||||
|
requestObj.promise = requestObj.promise.then(({ statusCode, body }) => {
|
||||||
|
if (statusCode !== 200) {
|
||||||
|
let tryRequestObj = this.getLyricUrl_1(songInfo, ++retryNum)
|
||||||
|
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||||
|
return tryRequestObj.promise
|
||||||
|
}
|
||||||
|
if (body.code !== 'SUCCESS') {
|
||||||
|
this.failTime = Date.now()
|
||||||
|
let tryRequestObj = this.getLyricUrl_2(songInfo)
|
||||||
|
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||||
|
return tryRequestObj.promise
|
||||||
|
}
|
||||||
|
if (!body.result.data.lyrics.length) return Promise.reject(new Error('未找到歌词'))
|
||||||
|
let lrc = body.result.data.lyrics.find(lyric => /\.lrc$/.test(lyric.lyricUrl))
|
||||||
|
return lrc ? lrc.content : Promise.reject(new Error('未找到歌词'))
|
||||||
|
})
|
||||||
|
return requestObj
|
||||||
|
},
|
||||||
|
getLyricUrl_2(songInfo, retryNum = 0) {
|
||||||
|
if (retryNum > 2) return Promise.reject('歌词获取失败')
|
||||||
|
// https://github.com/listen1/listen1_chrome_extension/blob/2587e627d23a85e490628acc0b3c9b534bc8323d/js/provider/xiami.js#L149
|
||||||
|
let requestObj = httpFetch(`https://emumo.xiami.com/song/playlist/id/${songInfo.songmid}/object_name/default/object_id/0/cat/json`, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
|
||||||
|
referer: 'https://www.xiami.com',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
requestObj.promise = requestObj.promise.then(({ statusCode, body }) => {
|
||||||
|
if (statusCode !== 200 || !body.status) {
|
||||||
|
let tryRequestObj = this.getLyricUrl_2(songInfo, ++retryNum)
|
||||||
|
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
||||||
|
return tryRequestObj.promise
|
||||||
|
}
|
||||||
|
let url = body.data.trackList[0].lyric_url
|
||||||
|
if (!url) return Promise.reject(new Error('未找到歌词'))
|
||||||
|
return this.getLyricFile_2(/^http:/.test(url) ? url : ('http:' + url))
|
||||||
|
})
|
||||||
|
return requestObj
|
||||||
|
},
|
||||||
|
getLyric(songInfo) {
|
||||||
|
if (songInfo.lrcUrl && /\.lrc$/.test(songInfo.lrcUrl)) return this.getLyricFile_1(songInfo.lrcUrl)
|
||||||
|
return Date.now() - this.failTime > this.expireTime ? this.getLyricUrl_1(songInfo) : this.getLyricUrl_2(songInfo)
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
// import '../../polyfill/array.find'
|
||||||
|
// import jshtmlencode from 'js-htmlencode'
|
||||||
|
import { xmRequest } from './util'
|
||||||
|
import { formatPlayTime, sizeFormate } from '../../index'
|
||||||
|
// import { debug } from '../../utils/env'
|
||||||
|
// import { formatSinger } from './util'
|
||||||
|
// "cdcb72dc3eba41cb5bc4267f09183119_xmMain_/api/list/collect_{"pagingVO":{"page":1,"pageSize":60},"dataType":"system"}"
|
||||||
|
let searchRequest
|
||||||
|
export default {
|
||||||
|
limit: 30,
|
||||||
|
total: 0,
|
||||||
|
page: 0,
|
||||||
|
allPage: 1,
|
||||||
|
musicSearch(str, page) {
|
||||||
|
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
|
||||||
|
searchRequest = xmRequest('/api/search/searchSongs', {
|
||||||
|
key: str,
|
||||||
|
pagingVO: {
|
||||||
|
page: page,
|
||||||
|
pageSize: this.limit,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return searchRequest.promise.then(({ body }) => body)
|
||||||
|
},
|
||||||
|
getSinger(singers) {
|
||||||
|
let arr = []
|
||||||
|
singers.forEach(singer => {
|
||||||
|
arr.push(singer.artistName)
|
||||||
|
})
|
||||||
|
return arr.join('、')
|
||||||
|
},
|
||||||
|
handleResult(rawData) {
|
||||||
|
// console.log(rawData)
|
||||||
|
let ids = new Set()
|
||||||
|
const list = []
|
||||||
|
rawData.forEach(songData => {
|
||||||
|
if (!songData) return
|
||||||
|
if (ids.has(songData.songId)) return
|
||||||
|
ids.add(songData.songId)
|
||||||
|
|
||||||
|
const types = []
|
||||||
|
const _types = {}
|
||||||
|
let size = null
|
||||||
|
for (const item of songData.purviewRoleVOs) {
|
||||||
|
if (!item.filesize) continue
|
||||||
|
size = sizeFormate(item.filesize)
|
||||||
|
switch (item.quality) {
|
||||||
|
case 's':
|
||||||
|
types.push({ type: 'wav', size })
|
||||||
|
_types.wav = {
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'h':
|
||||||
|
types.push({ type: '320k', size })
|
||||||
|
_types['320k'] = {
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'l':
|
||||||
|
types.push({ type: '128k', size })
|
||||||
|
_types['128k'] = {
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
types.reverse()
|
||||||
|
|
||||||
|
list.push({
|
||||||
|
singer: this.getSinger(songData.singerVOs),
|
||||||
|
name: songData.songName,
|
||||||
|
albumName: songData.albumName,
|
||||||
|
albumId: songData.albumId,
|
||||||
|
source: 'xm',
|
||||||
|
interval: formatPlayTime(parseInt(songData.length / 1000)),
|
||||||
|
songmid: songData.songId,
|
||||||
|
img: songData.albumLogo || songData.albumLogoS,
|
||||||
|
lrc: null,
|
||||||
|
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
|
||||||
|
types,
|
||||||
|
_types,
|
||||||
|
typeUrl: {},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
},
|
||||||
|
search(str, page = 1, { limit } = {}, retryNum = 0) {
|
||||||
|
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
||||||
|
if (limit != null) this.limit = limit
|
||||||
|
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
|
||||||
|
return this.musicSearch(str, page).then(result => {
|
||||||
|
// console.log(result)
|
||||||
|
if (!result) return this.search(str, page, { limit }, retryNum)
|
||||||
|
if (result.code !== 'SUCCESS') return this.search(str, page, { limit }, retryNum)
|
||||||
|
// const songResultData = result.data || { songs: [], total: 0 }
|
||||||
|
|
||||||
|
let list = this.handleResult(result.result.data.songs)
|
||||||
|
if (list == null) return this.search(str, page, { limit }, retryNum)
|
||||||
|
|
||||||
|
this.total = parseInt(result.result.data.pagingVO.count)
|
||||||
|
this.page = page
|
||||||
|
this.allPage = Math.ceil(this.total / this.limit)
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
list,
|
||||||
|
allPage: this.allPage,
|
||||||
|
limit: this.limit,
|
||||||
|
total: this.total,
|
||||||
|
source: 'xm',
|
||||||
|
})
|
||||||
|
}).catch(() => this.search(str, page, { limit }, retryNum))
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
import { xmRequest } from './util'
|
||||||
|
import { sizeFormate, formatPlayTime } from '../../index'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
_requestObj_tags: null,
|
||||||
|
_requestObj_list: null,
|
||||||
|
_requestObj_listDetail: null,
|
||||||
|
limit_list: 36,
|
||||||
|
limit_song: 100000,
|
||||||
|
successCode: 'SUCCESS',
|
||||||
|
sortList: [
|
||||||
|
{
|
||||||
|
name: '推荐',
|
||||||
|
id: 'system',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '精选',
|
||||||
|
id: 'recommend',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '最热',
|
||||||
|
id: 'hot',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '最新',
|
||||||
|
id: 'new',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
regExps: {
|
||||||
|
// https://www.xiami.com/collect/1138092824?action=play
|
||||||
|
listDetailLink: /^.+\/collect\/(\d+)(?:\s\(.*|\?.*|&.*$|#.*$|$)/,
|
||||||
|
},
|
||||||
|
tagsUrl: '/api/collect/getRecommendTags',
|
||||||
|
songListUrl: '/api/list/collect',
|
||||||
|
songListDetailUrl: '/api/collect/initialize',
|
||||||
|
getSongListData(sortId, tagId, page) {
|
||||||
|
if (tagId == null) {
|
||||||
|
return { pagingVO: { page, pageSize: this.limit_list }, dataType: sortId }
|
||||||
|
}
|
||||||
|
switch (sortId) {
|
||||||
|
case 'system':
|
||||||
|
case 'recommend':
|
||||||
|
sortId = 'hot'
|
||||||
|
}
|
||||||
|
return { pagingVO: { page, pageSize: this.limit_list }, dataType: sortId, key: tagId }
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化播放数量
|
||||||
|
* @param {*} num
|
||||||
|
*/
|
||||||
|
formatPlayCount(num) {
|
||||||
|
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
|
||||||
|
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
|
||||||
|
return num
|
||||||
|
},
|
||||||
|
getSinger(singers) {
|
||||||
|
let arr = []
|
||||||
|
singers.forEach(singer => {
|
||||||
|
arr.push(singer.artistName)
|
||||||
|
})
|
||||||
|
return arr.join('、')
|
||||||
|
},
|
||||||
|
|
||||||
|
getListDetail(id, page, tryNum = 0) { // 获取歌曲列表内的音乐
|
||||||
|
if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp()
|
||||||
|
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||||
|
|
||||||
|
if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
|
||||||
|
|
||||||
|
this._requestObj_listDetail = xmRequest('/api/collect/getCollectStaticUrl', { listId: id })
|
||||||
|
return this._requestObj_listDetail.promise.then(({ body }) => {
|
||||||
|
if (body.code !== this.successCode) return this.getListDetail(id, page, ++tryNum)
|
||||||
|
this._requestObj_listDetail = xmRequest(body.result.data.data.data.url)
|
||||||
|
return this._requestObj_listDetail.promise.then(({ body }) => {
|
||||||
|
if (!body.status) return this.getListDetail(id, page, ++tryNum)
|
||||||
|
// console.log(JSON.stringify(body))
|
||||||
|
return {
|
||||||
|
list: this.filterListDetail(body.resultObj.songs),
|
||||||
|
page,
|
||||||
|
limit: this.limit_song,
|
||||||
|
total: body.resultObj.songCount,
|
||||||
|
source: 'xm',
|
||||||
|
info: {
|
||||||
|
name: body.resultObj.collectName,
|
||||||
|
img: body.resultObj.collectLogo,
|
||||||
|
desc: body.resultObj.description,
|
||||||
|
author: body.resultObj.userName,
|
||||||
|
play_count: this.formatPlayCount(body.resultObj.playCount),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
filterListDetail(rawList) {
|
||||||
|
// console.log(rawList)
|
||||||
|
let ids = new Set()
|
||||||
|
const list = []
|
||||||
|
rawList.forEach(songData => {
|
||||||
|
if (!songData) return
|
||||||
|
if (ids.has(songData.songId)) return
|
||||||
|
ids.add(songData.songId)
|
||||||
|
|
||||||
|
const types = []
|
||||||
|
const _types = {}
|
||||||
|
let size = null
|
||||||
|
for (const item of songData.purviewRoleVOs) {
|
||||||
|
if (!item.filesize) continue
|
||||||
|
size = sizeFormate(item.filesize)
|
||||||
|
switch (item.quality) {
|
||||||
|
case 's':
|
||||||
|
types.push({ type: 'wav', size })
|
||||||
|
_types.wav = {
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'h':
|
||||||
|
types.push({ type: '320k', size })
|
||||||
|
_types['320k'] = {
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'l':
|
||||||
|
types.push({ type: '128k', size })
|
||||||
|
_types['128k'] = {
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
types.reverse()
|
||||||
|
|
||||||
|
list.push({
|
||||||
|
singer: this.getSinger(songData.singerVOs),
|
||||||
|
name: songData.songName,
|
||||||
|
albumName: songData.albumName,
|
||||||
|
albumId: songData.albumId,
|
||||||
|
source: 'xm',
|
||||||
|
interval: formatPlayTime(parseInt(songData.length / 1000)),
|
||||||
|
songmid: songData.songId,
|
||||||
|
img: songData.albumLogo || songData.albumLogoS,
|
||||||
|
lrc: null,
|
||||||
|
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
|
||||||
|
types,
|
||||||
|
_types,
|
||||||
|
typeUrl: {},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取列表数据
|
||||||
|
getList(sortId, tagId, page, tryNum = 0) {
|
||||||
|
if (this._requestObj_list) this._requestObj_list.cancelHttp()
|
||||||
|
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||||
|
this._requestObj_list = xmRequest(this.songListUrl, this.getSongListData(sortId, tagId, page))
|
||||||
|
return this._requestObj_list.promise.then(({ body }) => {
|
||||||
|
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
|
||||||
|
return {
|
||||||
|
list: this.filterList(body.result.data.collects),
|
||||||
|
total: body.result.data.pagingVO.count,
|
||||||
|
page,
|
||||||
|
limit: body.result.data.pagingVO.pageSize,
|
||||||
|
source: 'xm',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
filterList(rawData) {
|
||||||
|
return rawData.map(item => ({
|
||||||
|
play_count: this.formatPlayCount(item.playCount),
|
||||||
|
id: item.listId,
|
||||||
|
author: item.userName,
|
||||||
|
name: item.collectName,
|
||||||
|
time: null,
|
||||||
|
img: item.collectLogo,
|
||||||
|
grade: null,
|
||||||
|
desc: null,
|
||||||
|
source: 'xm',
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取标签
|
||||||
|
getTag(tryNum = 0) {
|
||||||
|
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
|
||||||
|
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||||
|
this._requestObj_tags = xmRequest(this.tagsUrl, { recommend: 1 })
|
||||||
|
return this._requestObj_tags.promise.then(({ body }) => {
|
||||||
|
if (body.code !== this.successCode) return this.getTag(++tryNum)
|
||||||
|
return this.filterTagInfo(body.result.data.recommendTags)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
filterTagInfo(rawList) {
|
||||||
|
return {
|
||||||
|
hotTag: rawList[0].items.map(item => ({
|
||||||
|
id: item.name,
|
||||||
|
name: item.name,
|
||||||
|
source: 'xm',
|
||||||
|
})),
|
||||||
|
tags: rawList.slice(1).map(item => ({
|
||||||
|
name: item.title,
|
||||||
|
list: item.items.map(tag => ({
|
||||||
|
parent_id: item.title,
|
||||||
|
parent_name: item.title,
|
||||||
|
id: tag.name,
|
||||||
|
name: tag.name,
|
||||||
|
source: 'xm',
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
source: 'xm',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getTags() {
|
||||||
|
return this.getTag()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// getList
|
||||||
|
// getTags
|
||||||
|
// getListDetail
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { httpGet, httpFetch } from '../../request'
|
||||||
|
import { toMD5 } from '../../index'
|
||||||
|
// import crateIsg from './isg'
|
||||||
|
import { rendererInvoke } from '../../../../common/ipc'
|
||||||
|
|
||||||
|
if (!window.xm_token) {
|
||||||
|
let data = window.localStorage.getItem('xm_token')
|
||||||
|
window.xm_token = data ? JSON.parse(data) : {
|
||||||
|
cookies: {},
|
||||||
|
cookie: null,
|
||||||
|
token: null,
|
||||||
|
isGetingToken: false,
|
||||||
|
}
|
||||||
|
window.xm_token.isGetingToken = false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatSinger = rawData => rawData.replace(/&/g, '、')
|
||||||
|
|
||||||
|
const matchToken = headers => {
|
||||||
|
let cookies = {}
|
||||||
|
let token
|
||||||
|
for (const item of headers['set-cookie']) {
|
||||||
|
const [key, value] = item.substring(0, item.indexOf(';')).split('=')
|
||||||
|
cookies[key] = value
|
||||||
|
if (key == 'xm_sg_tk') token = value.substring(0, value.indexOf('_'))
|
||||||
|
}
|
||||||
|
// console.log(cookies)
|
||||||
|
return { token, cookies }
|
||||||
|
}
|
||||||
|
|
||||||
|
const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
|
||||||
|
|
||||||
|
const createToken = (token, path, params) => toMD5(`${token}_xmMain_${path}_${params}`)
|
||||||
|
|
||||||
|
const handleSaveToken = ({ token, cookies }) => {
|
||||||
|
Object.assign(window.xm_token.cookies, cookies)
|
||||||
|
// window.xm_token.cookies.isg = crateIsg()
|
||||||
|
window.xm_token.cookie = Object.keys(window.xm_token.cookies).map(k => `${k}=${window.xm_token.cookies[k]};`).join(' ')
|
||||||
|
if (token) window.xm_token.token = token
|
||||||
|
|
||||||
|
window.localStorage.setItem('xm_token', JSON.stringify(window.xm_token))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getToken = (path, params) => new Promise((resolve, reject) => {
|
||||||
|
if (window.xm_token.isGetingToken) return wait(1000).then(() => getToken(path, params).then(data => resolve(data)))
|
||||||
|
if (window.xm_token.token) return resolve({ token: createToken(window.xm_token.token, path, params), cookie: window.xm_token.cookie })
|
||||||
|
window.xm_token.isGetingToken = true
|
||||||
|
httpGet('https://www.xiami.com/', (err, resp) => {
|
||||||
|
window.xm_token.isGetingToken = false
|
||||||
|
if (err) return reject(err)
|
||||||
|
if (resp.statusCode != 200) return reject(new Error('获取失败'))
|
||||||
|
|
||||||
|
handleSaveToken(matchToken(resp.headers))
|
||||||
|
|
||||||
|
resolve({ token: createToken(window.xm_token.token, path, params), cookie: window.xm_token.cookie })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const baseUrl = 'https://www.xiami.com'
|
||||||
|
export const xmRequest = (path, params = '') => {
|
||||||
|
let query = params
|
||||||
|
if (params != '') {
|
||||||
|
params = JSON.stringify(params)
|
||||||
|
query = '&_q=' + encodeURIComponent(params)
|
||||||
|
}
|
||||||
|
let requestObj = {
|
||||||
|
isInited: false,
|
||||||
|
isCancelled: false,
|
||||||
|
cancelHttp() {
|
||||||
|
if (!this.isInited) this.isCancelled = true
|
||||||
|
this.requestObj.cancelHttp()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
requestObj.promise = getToken(path, params).then(data => {
|
||||||
|
// console.log(data)
|
||||||
|
if (requestObj.isCancelled) return Promise.reject('取消请求')
|
||||||
|
let url = path
|
||||||
|
if (!/^http/.test(path)) url = baseUrl + path
|
||||||
|
let s = `_s=${data.token}${query}`
|
||||||
|
url += (url.includes('?') ? '&' : '?') + s
|
||||||
|
requestObj.requestObj = httpFetch(url, {
|
||||||
|
headers: {
|
||||||
|
Referer: 'https://www.xiami.com/',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
|
||||||
|
cookie: data.cookie,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return requestObj.requestObj.promise.then(resp => {
|
||||||
|
// console.log(resp.body)
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
// console.log(resp.headers)
|
||||||
|
window.xm_token.token = null
|
||||||
|
return Promise.reject(new Error('获取失败'))
|
||||||
|
}
|
||||||
|
if (resp.body.code !== 'SUCCESS' && resp.body.rgv587_flag == 'sm') {
|
||||||
|
window.globalObj.xm.isShowVerify = true
|
||||||
|
return wait(300).then(() => {
|
||||||
|
return rendererInvoke('xm_verify_open', 'https:' + resp.body.url).then(x5sec => {
|
||||||
|
handleSaveToken({ cookies: { x5sec } })
|
||||||
|
// console.log(x5sec)
|
||||||
|
window.globalObj.xm.isShowVerify = false
|
||||||
|
return Promise.reject(new Error('获取失败'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (resp.headers['set-cookie']) handleSaveToken(matchToken(resp.headers))
|
||||||
|
|
||||||
|
return Promise.resolve(resp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return requestObj
|
||||||
|
}
|
||||||
|
|
||||||
|
export const closeVerifyModal = async() => {
|
||||||
|
if (!window.globalObj.xm.isShowVerify) return
|
||||||
|
await rendererInvoke('xm_verify_close')
|
||||||
|
window.globalObj.xm.isShowVerify = false
|
||||||
|
}
|
|
@ -129,6 +129,8 @@ export default {
|
||||||
case 'tx':
|
case 'tx':
|
||||||
case 'wy':
|
case 'wy':
|
||||||
type = '128k'
|
type = '128k'
|
||||||
|
case 'xm':
|
||||||
|
if (type == 'flac') type = 'wav'
|
||||||
}
|
}
|
||||||
this.createDownloadMultiple({ list: [...this.selectdData], type })
|
this.createDownloadMultiple({ list: [...this.selectdData], type })
|
||||||
this.isShowDownloadMultiple = false
|
this.isShowDownloadMultiple = false
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
material-checkbox(:id="index.toString()" v-model="selectdData" :value="item")
|
material-checkbox(:id="index.toString()" v-model="selectdData" :value="item")
|
||||||
td.break(style="width: 25%;")
|
td.break(style="width: 25%;")
|
||||||
span.select {{item.name}}
|
span.select {{item.name}}
|
||||||
span.badge.badge-theme-success(:class="$style.labelQuality" v-if="item._types.ape || item._types.flac") {{$t('material.song_list.lossless')}}
|
span.badge.badge-theme-success(:class="$style.labelQuality" v-if="item._types.ape || item._types.flac || item._types.wav") {{$t('material.song_list.lossless')}}
|
||||||
span.badge.badge-theme-info(:class="$style.labelQuality" v-else-if="item._types['320k']") {{$t('material.song_list.high_quality')}}
|
span.badge.badge-theme-info(:class="$style.labelQuality" v-else-if="item._types['320k']") {{$t('material.song_list.high_quality')}}
|
||||||
span(:class="$style.labelSource" v-if="searchSourceId == 'all'") {{item.source}}
|
span(:class="$style.labelSource" v-if="searchSourceId == 'all'") {{item.source}}
|
||||||
td.break(style="width: 20%;")
|
td.break(style="width: 20%;")
|
||||||
|
|
|
@ -100,6 +100,7 @@ export default {
|
||||||
case 'tx':
|
case 'tx':
|
||||||
case 'mg':
|
case 'mg':
|
||||||
case 'kg':
|
case 'kg':
|
||||||
|
case 'xm':
|
||||||
list.push({
|
list.push({
|
||||||
name: this.$t('view.song_list.open_list', { name: this.sourceInfo.sources.find(s => s.id == this.source).name }),
|
name: this.$t('view.song_list.open_list', { name: this.sourceInfo.sources.find(s => s.id == this.source).name }),
|
||||||
id: 'importSongList',
|
id: 'importSongList',
|
||||||
|
@ -254,6 +255,8 @@ export default {
|
||||||
case 'tx':
|
case 'tx':
|
||||||
case 'wy':
|
case 'wy':
|
||||||
type = '128k'
|
type = '128k'
|
||||||
|
case 'xm':
|
||||||
|
if (type == 'flac') type = 'wav'
|
||||||
}
|
}
|
||||||
this.createDownloadMultiple({ list: this.filterList(this.selectdData), type })
|
this.createDownloadMultiple({ list: this.filterList(this.selectdData), type })
|
||||||
this.resetSelect()
|
this.resetSelect()
|
||||||
|
|
Loading…
Reference in New Issue