新增启动参数-play
parent
b6107b7281
commit
fa0d9ad4c3
|
@ -83,6 +83,12 @@ npm run pack:linux
|
||||||
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
|
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
|
||||||
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动(v1.6.0起新增)
|
- `-dha` 禁用硬件加速启动(Disable Hardware Acceleration),窗口显示有问题时可以尝试添加此参数启动(v1.6.0起新增)
|
||||||
- `-dt` 以非透明模式启动(Disable Transparent),对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示,原来的`-nt`参数已重命名为`-dt`(v1.6.0起重命名)
|
- `-dt` 以非透明模式启动(Disable Transparent),对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示,原来的`-nt`参数已重命名为`-dt`(v1.6.0起重命名)
|
||||||
|
- `-play` 启动时播放指定列表的音乐,参数说明:
|
||||||
|
- `type`:播放类型,目前固定为`songList`
|
||||||
|
- `source`:播放源,可用值为`kw/kg/tx/wy/mg/myList`,其中`kw/kg/tx/wy/mg`对应各源的在线列表,`myList`为本地列表
|
||||||
|
- `link`:要播放的在线列表歌单链接、或ID,source为`kw/kg/tx/wy/mg`之一(在线列表)时必传,举例:`./lx-music-desktop -play="type=songList&source=kw&link=歌单URL or ID",注意:如果传入URL时必须对URL进行编码后再传入
|
||||||
|
- `name`:要播放的本地列表歌单名字,source为`myList`时必传,举例:`./lx-music-desktop -play="type=songList&source=myList&name=默认列表"
|
||||||
|
- `index`:从列表的哪个位置开始播放,选传,若不传默认播放第一首歌曲,举例:`./lx-music-desktop -play="type=songList&source=myList&name=默认列表&index=2"
|
||||||
|
|
||||||
### 常见问题
|
### 常见问题
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
### 新增
|
### 新增
|
||||||
|
|
||||||
- 新增设置-其他-列表缓存信息清理功能,注:此功能一般情况下不要使用
|
- 新增设置-其他-列表缓存信息清理功能,注:此功能一般情况下不要使用
|
||||||
|
- 新增启动参数`-play`,可以在启动软件时播放指定歌单,使用方法看Readme.md的"启动参数"部分
|
||||||
|
|
||||||
### 优化
|
### 优化
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,6 @@ module.exports = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
navigationUrlWhiteList: [
|
navigationUrlWhiteList: [
|
||||||
/^https:\/\/www\.xiami\.com/,
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,4 @@ require('./showDialog')
|
||||||
require('./playList')
|
require('./playList')
|
||||||
require('./data')
|
require('./data')
|
||||||
|
|
||||||
// require('./xm_verify')
|
|
||||||
|
|
||||||
require('./kw_decodeLyric')
|
require('./kw_decodeLyric')
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
const { isMac } = require('../../../common/utils')
|
|
||||||
|
|
||||||
// mac下的 BrowserView 无法拖动验证栏,改用 BrowserWindow
|
|
||||||
require(isMac ? './xm_verify_win' : './xm_verify_view')
|
|
|
@ -1,4 +0,0 @@
|
||||||
const { isMac } = require('../../../common/utils')
|
|
||||||
|
|
||||||
// mac下的 BrowserView 无法拖动验证栏,改用 BrowserWindow
|
|
||||||
require(isMac ? './xm_verify_win' : './xm_verify_view')
|
|
|
@ -1,79 +0,0 @@
|
||||||
const { BrowserView } = require('electron')
|
|
||||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../../common/ipc')
|
|
||||||
const { getWindowSizeInfo } = require('../../utils')
|
|
||||||
|
|
||||||
let view
|
|
||||||
let isActioned = false
|
|
||||||
let rejectFn
|
|
||||||
|
|
||||||
const closeView = async() => {
|
|
||||||
if (!view) return
|
|
||||||
// await view.webContents.session.clearCache()
|
|
||||||
if (global.modules.mainWindow) global.modules.mainWindow.removeBrowserView(view)
|
|
||||||
await view.webContents.session.clearStorageData()
|
|
||||||
view.destroy()
|
|
||||||
view = null
|
|
||||||
}
|
|
||||||
|
|
||||||
mainHandle(ipcMainWindowNames.handle_xm_verify_open, (event, url) => new Promise((resolve, reject) => {
|
|
||||||
if (!global.modules.mainWindow) return reject(new Error('mainWindow is undefined'))
|
|
||||||
if (view) {
|
|
||||||
global.modules.mainWindow.removeBrowserView(view)
|
|
||||||
view.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
rejectFn = reject
|
|
||||||
|
|
||||||
isActioned = false
|
|
||||||
|
|
||||||
view = new BrowserView({
|
|
||||||
webPreferences: {
|
|
||||||
enableRemoteModule: false,
|
|
||||||
disableHtmlFullscreenWindowResize: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
// 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]) => {
|
|
||||||
// isActioned = true
|
|
||||||
// await closeView()
|
|
||||||
// if (!x5sec) return reject(new Error('get x5sec failed'))
|
|
||||||
// resolve(x5sec.value)
|
|
||||||
// }).catch(async err => {
|
|
||||||
// isActioned = true
|
|
||||||
// await closeView()
|
|
||||||
// reject(err)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
view.webContents.session.webRequest.onCompleted({ urls: ['*://www.xiami.com/*'] }, details => {
|
|
||||||
if (/\/_____tmd_____\/slide\?/.test(details.url)) {
|
|
||||||
for (const item of details.responseHeaders['set-cookie']) {
|
|
||||||
if (!/^x5sec=/.test(item)) continue
|
|
||||||
const x5sec = /x5sec=(\w+);.+$/.exec(item)
|
|
||||||
isActioned = true
|
|
||||||
closeView().finally(() => {
|
|
||||||
if (!x5sec) return reject(new Error('get x5sec failed'))
|
|
||||||
resolve(x5sec[1])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// console.log(url)
|
|
||||||
global.modules.mainWindow.setBrowserView(view)
|
|
||||||
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
|
||||||
view.setBounds({ x: (windowSizeInfo.width - 380) / 2, y: ((windowSizeInfo.height - 320 + 52) / 2), width: 380, height: 320 })
|
|
||||||
view.webContents.loadURL(url, {
|
|
||||||
httpReferrer: 'https://www.xiami.com/',
|
|
||||||
})
|
|
||||||
// view.webContents.openDevTools()
|
|
||||||
}))
|
|
||||||
|
|
||||||
mainHandle(ipcMainWindowNames.handle_xm_verify_close, async() => {
|
|
||||||
await closeView()
|
|
||||||
if (!rejectFn) return
|
|
||||||
if (!isActioned) rejectFn(new Error('canceled verify'))
|
|
||||||
rejectFn = null
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
const { BrowserWindow } = require('electron')
|
|
||||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../../common/ipc')
|
|
||||||
const { getWindowSizeInfo } = require('../../utils')
|
|
||||||
|
|
||||||
let win
|
|
||||||
|
|
||||||
const closeWin = async() => {
|
|
||||||
if (!win) return
|
|
||||||
// await win.webContents.session.clearCache()
|
|
||||||
// if (global.modules.mainWindow) global.modules.mainWindow.removeBrowserView(win)
|
|
||||||
if (win.isDestroyed()) {
|
|
||||||
win = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await win.webContents.session.clearStorageData()
|
|
||||||
win.destroy()
|
|
||||||
win = null
|
|
||||||
}
|
|
||||||
|
|
||||||
mainHandle(ipcMainWindowNames.handle_xm_verify_open, (event, url) => new Promise((resolve, reject) => {
|
|
||||||
if (!global.modules.mainWindow) return reject(new Error('mainWindow is undefined'))
|
|
||||||
if (win) win.destroy()
|
|
||||||
|
|
||||||
let isActioned = false
|
|
||||||
|
|
||||||
const mainWindowSizeInfo = global.modules.mainWindow.getBounds()
|
|
||||||
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
|
||||||
win = new BrowserWindow({
|
|
||||||
parent: global.modules.mainWindow,
|
|
||||||
width: 1000,
|
|
||||||
height: 800,
|
|
||||||
resizable: false,
|
|
||||||
// transparent: true,
|
|
||||||
x: mainWindowSizeInfo.x + (windowSizeInfo.width - 1000) / 2,
|
|
||||||
y: mainWindowSizeInfo.y + (windowSizeInfo.height - 800 + 52) / 2,
|
|
||||||
minimizable: false,
|
|
||||||
maximizable: false,
|
|
||||||
// movable: false,
|
|
||||||
// frame: false,
|
|
||||||
// modal: true,
|
|
||||||
webPreferences: {
|
|
||||||
enableRemoteModule: false,
|
|
||||||
disableHtmlFullscreenWindowResize: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
// win.webContents.on('did-finish-load', () => {
|
|
||||||
// if (/punish\?/.test(win.webContents.getURL())) return
|
|
||||||
// let ses = win.webContents.session
|
|
||||||
// ses.cookies.get({ name: 'x5sec' })
|
|
||||||
// .then(async([x5sec]) => {
|
|
||||||
// isActioned = true
|
|
||||||
// await closeWin()
|
|
||||||
// if (!x5sec) return reject(new Error('get x5sec failed'))
|
|
||||||
// resolve(x5sec.value)
|
|
||||||
// }).catch(async err => {
|
|
||||||
// isActioned = true
|
|
||||||
// await closeWin()
|
|
||||||
// reject(err)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
|
|
||||||
win.webContents.session.webRequest.onCompleted({ urls: ['*://www.xiami.com/*'] }, details => {
|
|
||||||
if (/\/_____tmd_____\/slide\?/.test(details.url)) {
|
|
||||||
for (const item of details.responseHeaders['set-cookie']) {
|
|
||||||
if (!/^x5sec=/.test(item)) continue
|
|
||||||
const x5sec = /x5sec=(\w+);.+$/.exec(item)
|
|
||||||
isActioned = true
|
|
||||||
closeWin().finally(() => {
|
|
||||||
if (!x5sec) return reject(new Error('get x5sec failed'))
|
|
||||||
resolve(x5sec[1])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
win.webContents.loadURL(url, {
|
|
||||||
httpReferrer: 'https://www.xiami.com/',
|
|
||||||
})
|
|
||||||
|
|
||||||
win.on('closed', async() => {
|
|
||||||
await closeWin()
|
|
||||||
if (isActioned) return
|
|
||||||
reject(new Error('canceled verify'))
|
|
||||||
})
|
|
||||||
|
|
||||||
// win.webContents.openDevTools()
|
|
||||||
}))
|
|
||||||
|
|
||||||
mainHandle(ipcMainWindowNames.handle_xm_verify_close, async() => {
|
|
||||||
await closeWin()
|
|
||||||
})
|
|
|
@ -6,7 +6,6 @@
|
||||||
core-view#view
|
core-view#view
|
||||||
core-player#player
|
core-player#player
|
||||||
core-icons
|
core-icons
|
||||||
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
|
|
||||||
material-version-modal(v-show="version.showModal")
|
material-version-modal(v-show="version.showModal")
|
||||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||||
#container(v-else :class="theme")
|
#container(v-else :class="theme")
|
||||||
|
@ -16,7 +15,6 @@
|
||||||
core-view#view
|
core-view#view
|
||||||
core-player#player
|
core-player#player
|
||||||
core-icons
|
core-icons
|
||||||
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
|
|
||||||
material-version-modal(v-show="version.showModal")
|
material-version-modal(v-show="version.showModal")
|
||||||
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
|
||||||
</template>
|
</template>
|
||||||
|
@ -27,7 +25,7 @@ import { mapMutations, mapGetters, mapActions } from 'vuex'
|
||||||
import { rendererOn, rendererSend, rendererInvoke, NAMES } from '../common/ipc'
|
import { rendererOn, rendererSend, rendererInvoke, NAMES } from '../common/ipc'
|
||||||
import { isLinux } from '../common/utils'
|
import { isLinux } from '../common/utils'
|
||||||
import music from './utils/music'
|
import music from './utils/music'
|
||||||
import { throttle, openUrl, compareVer, getPlayList } from './utils'
|
import { throttle, openUrl, compareVer, getPlayList, parseUrlParams } from './utils'
|
||||||
import { base as eventBaseName } from './event/names'
|
import { base as eventBaseName } from './event/names'
|
||||||
|
|
||||||
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
|
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
|
||||||
|
@ -62,9 +60,6 @@ export default {
|
||||||
proxy: {},
|
proxy: {},
|
||||||
isShowPact: false,
|
isShowPact: false,
|
||||||
qualityList: {},
|
qualityList: {},
|
||||||
xm: {
|
|
||||||
isShowVerify: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
updateTimeout: null,
|
updateTimeout: null,
|
||||||
envParams: {
|
envParams: {
|
||||||
|
@ -179,10 +174,12 @@ export default {
|
||||||
...mapMutations('player', {
|
...mapMutations('player', {
|
||||||
setPlayList: 'setList',
|
setPlayList: 'setList',
|
||||||
}),
|
}),
|
||||||
|
...mapActions('songList', ['getListDetailAll']),
|
||||||
init() {
|
init() {
|
||||||
document.documentElement.style.fontSize = this.windowSizeActive.fontSize
|
document.documentElement.style.fontSize = this.windowSizeActive.fontSize
|
||||||
|
|
||||||
rendererInvoke(NAMES.mainWindow.get_env_params).then(this.handleEnvParamsInit)
|
const asyncTask = []
|
||||||
|
asyncTask.push(rendererInvoke(NAMES.mainWindow.get_env_params).then(this.handleEnvParamsInit))
|
||||||
|
|
||||||
document.body.addEventListener('click', this.handleBodyClick, true)
|
document.body.addEventListener('click', this.handleBodyClick, true)
|
||||||
rendererOn(NAMES.mainWindow.update_available, (e, info) => {
|
rendererOn(NAMES.mainWindow.update_available, (e, info) => {
|
||||||
|
@ -246,14 +243,18 @@ export default {
|
||||||
}, 60 * 30 * 1000)
|
}, 60 * 30 * 1000)
|
||||||
|
|
||||||
this.listenEvent()
|
this.listenEvent()
|
||||||
this.initData()
|
asyncTask.push(this.initData())
|
||||||
this.globalObj.apiSource = this.setting.apiSource
|
this.globalObj.apiSource = this.setting.apiSource
|
||||||
this.globalObj.qualityList = music.supportQuality[this.setting.apiSource]
|
this.globalObj.qualityList = music.supportQuality[this.setting.apiSource]
|
||||||
this.globalObj.proxy = Object.assign({}, this.setting.network.proxy)
|
this.globalObj.proxy = Object.assign({}, this.setting.network.proxy)
|
||||||
window.globalObj = this.globalObj
|
window.globalObj = this.globalObj
|
||||||
|
|
||||||
// 初始化音乐sdk
|
// 初始化音乐sdk
|
||||||
music.init()
|
asyncTask.push(music.init())
|
||||||
|
Promise.all(asyncTask).then(() => {
|
||||||
|
this.handleInitEnvParamSearch()
|
||||||
|
this.handleInitEnvParamPlay()
|
||||||
|
})
|
||||||
},
|
},
|
||||||
enableIgnoreMouseEvents() {
|
enableIgnoreMouseEvents() {
|
||||||
if (this.isDT) return
|
if (this.isDT) return
|
||||||
|
@ -267,12 +268,14 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
initData() { // 初始化数据
|
initData() { // 初始化数据
|
||||||
this.initLocalList() // 初始化播放列表
|
return Promise.all([
|
||||||
|
this.initMyList(), // 初始化播放列表
|
||||||
|
this.initSearchHistoryList(), // 初始化搜索历史列表
|
||||||
|
])
|
||||||
// this.initDownloadList() // 初始化下载列表
|
// this.initDownloadList() // 初始化下载列表
|
||||||
this.initSearchHistoryList() // 初始化搜索历史列表
|
|
||||||
},
|
},
|
||||||
initLocalList() {
|
initMyList() {
|
||||||
getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
|
return getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
|
||||||
if (!defaultList) defaultList = this.defaultList
|
if (!defaultList) defaultList = this.defaultList
|
||||||
if (!loveList) loveList = this.loveList
|
if (!loveList) loveList = this.loveList
|
||||||
if (userList) {
|
if (userList) {
|
||||||
|
@ -391,18 +394,91 @@ export default {
|
||||||
document.body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
|
document.body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
|
||||||
document.body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
|
document.body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
|
||||||
}
|
}
|
||||||
|
this.handleInitEnvParamSearch()
|
||||||
|
this.handleInitEnvParamPlay()
|
||||||
|
},
|
||||||
|
// 处理启动参数 search
|
||||||
|
handleInitEnvParamSearch() {
|
||||||
|
if (this.envParams.search == null) return
|
||||||
|
this.$router.push({
|
||||||
|
path: 'search',
|
||||||
|
query: {
|
||||||
|
text: this.envParams.search,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 处理启动参数 play
|
||||||
|
handleInitEnvParamPlay() {
|
||||||
|
if (this.envParams.play == null || typeof this.envParams.play != 'string') return
|
||||||
|
// -play="source=kw&link=链接、ID"
|
||||||
|
// -play="source=myList&name=名字"
|
||||||
|
// -play="source=myList&name=名字&index=位置"
|
||||||
|
const params = parseUrlParams(this.envParams.play)
|
||||||
|
if (params.type != 'songList') return
|
||||||
|
this.handlePlaySongList(params)
|
||||||
|
},
|
||||||
|
handlePlaySongList(params) {
|
||||||
|
switch (params.source) {
|
||||||
|
case 'myList':
|
||||||
|
if (params.name != null) {
|
||||||
|
let targetList
|
||||||
|
const lists = Object.values(window.allList)
|
||||||
|
for (const list of lists) {
|
||||||
|
if (list.name === params.name) {
|
||||||
|
targetList = list
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!targetList) return
|
||||||
|
|
||||||
if (this.envParams.search != null) {
|
|
||||||
this.$router.push({
|
this.setPlayList({
|
||||||
path: 'search',
|
list: {
|
||||||
query: {
|
list: targetList.list,
|
||||||
text: this.envParams.search,
|
id: targetList.id,
|
||||||
},
|
},
|
||||||
})
|
index: this.getListPlayIndex(targetList.list, params.index),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'kw':
|
||||||
|
case 'kg':
|
||||||
|
case 'tx':
|
||||||
|
case 'mg':
|
||||||
|
case 'wy':
|
||||||
|
this.playSongListDetail(params.source, params.link, params.index)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleXMVerifyModalClose() {
|
async playSongListDetail(source, link, playIndex) {
|
||||||
music.xm.closeVerifyModal()
|
if (link == null) return
|
||||||
|
let list
|
||||||
|
try {
|
||||||
|
list = await this.getListDetailAll({ source, id: decodeURIComponent(link) })
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
this.setPlayList({
|
||||||
|
list: {
|
||||||
|
list,
|
||||||
|
id: null,
|
||||||
|
},
|
||||||
|
index: this.getListPlayIndex(list, playIndex),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getListPlayIndex(list, index) {
|
||||||
|
if (index == null) {
|
||||||
|
index = 1
|
||||||
|
} else {
|
||||||
|
index = parseInt(index)
|
||||||
|
if (Number.isNaN(index)) {
|
||||||
|
index = 1
|
||||||
|
} else {
|
||||||
|
if (index < 1) index = 1
|
||||||
|
else if (index > list.length) index = list.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index - 1
|
||||||
},
|
},
|
||||||
listenEvent() {
|
listenEvent() {
|
||||||
window.eventHub.$on('key_escape_down', this.handle_key_esc_down)
|
window.eventHub.$on('key_escape_down', this.handle_key_esc_down)
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
<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>
|
|
|
@ -396,3 +396,14 @@ export const getPlayList = () => rendererInvoke(NAMES.mainWindow.get_playlist).c
|
||||||
return rendererInvoke(NAMES.mainWindow.get_playlist, true)
|
return rendererInvoke(NAMES.mainWindow.get_playlist, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 解析URL参数为对象
|
||||||
|
export const parseUrlParams = str => {
|
||||||
|
const params = {}
|
||||||
|
if (typeof str !== 'string') return params
|
||||||
|
const paramsArr = str.split('&')
|
||||||
|
for (const param of paramsArr) {
|
||||||
|
let [key, value] = param.split('=')
|
||||||
|
params[key] = value
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
|
@ -50,10 +50,12 @@ const sources = {
|
||||||
export default {
|
export default {
|
||||||
...sources,
|
...sources,
|
||||||
init() {
|
init() {
|
||||||
|
const tasks = []
|
||||||
for (let source of sources.sources) {
|
for (let source of sources.sources) {
|
||||||
let sm = sources[source.id]
|
let sm = sources[source.id]
|
||||||
sm && sm.init && sm.init()
|
sm && sm.init && tasks.push(sm.init())
|
||||||
}
|
}
|
||||||
|
return Promise.all(tasks)
|
||||||
},
|
},
|
||||||
supportQuality,
|
supportQuality,
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ const kw = {
|
||||||
},
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
getToken()
|
return getToken()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
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
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { xmRequest } from './util'
|
|
||||||
import { dateFormat2 } from '../../'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
_requestObj: null,
|
|
||||||
_requestObj2: null,
|
|
||||||
async getComment({ songmid }, page = 1, limit = 20) {
|
|
||||||
if (this._requestObj) this._requestObj.cancelHttp()
|
|
||||||
|
|
||||||
const _requestObj = xmRequest('/api/comment/getCommentList', { objectId: songmid, objectType: 'song', pagingVO: { page, pageSize: limit } })
|
|
||||||
const { body, statusCode } = await _requestObj.promise
|
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.code !== 'SUCCESS') throw new Error('获取评论失败')
|
|
||||||
return { source: 'xm', comments: this.filterComment(body.result.data.commentList), total: body.result.data.pagingVO.count, page, limit, maxPage: Math.ceil(body.result.data.pagingVO.count / limit) || 1 }
|
|
||||||
},
|
|
||||||
async getHotComment({ songmid }, page = 1, limit = 100) {
|
|
||||||
if (this._requestObj2) this._requestObj2.cancelHttp()
|
|
||||||
if (!songmid) throw new Error('获取失败')
|
|
||||||
const _requestObj2 = xmRequest('/api/comment/getHotCommentList', { objectId: songmid, objectType: 'song', pagingVO: { page, pageSize: limit } })
|
|
||||||
const { body, statusCode } = await _requestObj2.promise
|
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.code !== 'SUCCESS') throw new Error('获取热门评论失败')
|
|
||||||
return { source: 'xm', comments: this.filterComment(body.result.data.hotList) }
|
|
||||||
},
|
|
||||||
filterComment(rawList) {
|
|
||||||
return rawList.map(item => ({
|
|
||||||
id: item.commentId,
|
|
||||||
text: item.message.split('\n').filter(t => !!t),
|
|
||||||
time: item.gmtCreate,
|
|
||||||
timeStr: dateFormat2(item.gmtCreate),
|
|
||||||
userName: item.nickName,
|
|
||||||
avatar: item.avatar,
|
|
||||||
userId: item.userId,
|
|
||||||
likedCount: item.likes,
|
|
||||||
reply: item.replyData ? item.replyData.map(c => ({
|
|
||||||
id: c.commentId,
|
|
||||||
text: c.message.split('\n').filter(t => !!t),
|
|
||||||
time: c.gmtCreate,
|
|
||||||
timeStr: dateFormat2(c.gmtCreate),
|
|
||||||
userName: c.nickName,
|
|
||||||
avatar: c.avatar,
|
|
||||||
userId: c.userId,
|
|
||||||
likedCount: c.likes,
|
|
||||||
})) : [],
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
// 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) }
|
|
||||||
return { source: 'xm', list: [] }
|
|
||||||
},
|
|
||||||
filterList(rawList) {
|
|
||||||
return rawList.map(item => item.word)
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
import { xmRequest } from './util'
|
|
||||||
import { formatPlayTime, sizeFormate } from '../../index'
|
|
||||||
// import jshtmlencode from 'js-htmlencode'
|
|
||||||
|
|
||||||
let boardList = [{ id: 'xm__102', name: '新歌榜', bangid: '102' }, { id: 'xm__103', name: '热歌榜', bangid: '103' }, { id: 'xm__104', name: '原创榜', bangid: '104' }, { id: 'xm__306', name: 'K歌榜', bangid: '306' }, { id: 'xm__332', name: '抖音热歌榜', bangid: '332' }, { id: 'xm__305', name: '歌单收录榜', bangid: '305' }, { id: 'xm__327', name: '趴间热歌榜', bangid: '327' }, { id: 'xm__324', name: '影视原声榜', bangid: '324' }, { id: 'xm__204', name: '美国Billboard单曲榜', bangid: '204' }, { id: 'xm__206', name: '韩国MNET音乐排行榜', bangid: '206' }, { id: 'xm__201', name: 'Hito 中文排行榜', bangid: '201' }, { id: 'xm__203', name: '英国UK单曲榜', bangid: '203' }, { id: 'xm__205', name: 'oricon公信单曲榜', bangid: '205' }, { id: 'xm__328', name: '美国iTunes榜', bangid: '328' }, { id: 'xm__329', name: 'Beatport电音榜', bangid: '329' }, { id: 'xm__330', name: '香港商业电台榜', bangid: '330' }]
|
|
||||||
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
requestBoardsObj: null,
|
|
||||||
requestObj: null,
|
|
||||||
getBoardsData() {
|
|
||||||
if (this.requestBoardsObj) this.requestBoardsObj.cancelHttp()
|
|
||||||
this.requestBoardsObj = xmRequest('/api/billboard/getBillboards')
|
|
||||||
return this.requestBoardsObj.promise
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
songStringId: songData.songStringId,
|
|
||||||
lrc: null,
|
|
||||||
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
|
|
||||||
otherSource: null,
|
|
||||||
types,
|
|
||||||
_types,
|
|
||||||
typeUrl: {},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return list
|
|
||||||
},
|
|
||||||
filterBoardsData(rawList) {
|
|
||||||
// console.log(rawList)
|
|
||||||
let list = []
|
|
||||||
if (rawList.xiamiBillboards) {
|
|
||||||
for (const board of rawList.xiamiBillboards) {
|
|
||||||
if (board.itemType != 1) continue
|
|
||||||
list.push({
|
|
||||||
id: 'xm__' + board.billboardId,
|
|
||||||
name: board.name,
|
|
||||||
bangid: String(board.billboardId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rawList.spBillboards) {
|
|
||||||
for (const board of rawList.spBillboards) {
|
|
||||||
if (board.itemType != 1) continue
|
|
||||||
list.push({
|
|
||||||
id: 'xm__' + board.billboardId,
|
|
||||||
name: board.name,
|
|
||||||
bangid: String(board.billboardId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rawList.globalBillboards) {
|
|
||||||
for (const board of rawList.globalBillboards) {
|
|
||||||
if (board.itemType != 1) continue
|
|
||||||
list.push({
|
|
||||||
id: 'xm__' + board.billboardId,
|
|
||||||
name: board.name,
|
|
||||||
bangid: String(board.billboardId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
},
|
|
||||||
async getBoards(retryNum = 0) {
|
|
||||||
// if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
|
||||||
// let response
|
|
||||||
// try {
|
|
||||||
// response = await this.getBoardsData()
|
|
||||||
// } catch (error) {
|
|
||||||
// return this.getBoards(retryNum)
|
|
||||||
// }
|
|
||||||
// if (response.statusCode !== 200 || response.body.code !== 'SUCCESS') return this.getBoards(retryNum)
|
|
||||||
// const list = this.filterBoardsData(response.body.result.data)
|
|
||||||
// this.list = list
|
|
||||||
// return {
|
|
||||||
// list,
|
|
||||||
// source: 'xm',
|
|
||||||
// }
|
|
||||||
this.list = boardList
|
|
||||||
return {
|
|
||||||
list: boardList,
|
|
||||||
source: 'xm',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getList(bangid, page, retryNum = 0) {
|
|
||||||
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
|
|
||||||
return this.getData(bangid).then(({ statusCode, body }) => {
|
|
||||||
if (statusCode !== 200 || body.code !== 'SUCCESS') return this.getList(bangid, 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',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
import { httpGet, httpFetch } from '../../request'
|
|
||||||
import { xmRequest } from './util'
|
|
||||||
|
|
||||||
const parseLyric = str => {
|
|
||||||
str = str.replace(/(?:<\d+>|\r)/g, '')
|
|
||||||
let tlyric = []
|
|
||||||
let lyric = str.replace(/\[[\d:.]+\].*?\n\[x-trans\].*/g, s => {
|
|
||||||
// console.log(s)
|
|
||||||
let [lrc, tlrc] = s.split('\n')
|
|
||||||
tlrc = tlrc.replace('[x-trans]', lrc.replace(/^(\[[\d:.]+\]).*$/, '$1'))
|
|
||||||
tlyric.push(tlrc)
|
|
||||||
return lrc
|
|
||||||
})
|
|
||||||
tlyric = tlyric.join('\n')
|
|
||||||
return {
|
|
||||||
lyric,
|
|
||||||
tlyric,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 url.endsWith('.xtrc') ? parseLyric(body) : {
|
|
||||||
lyric: body,
|
|
||||||
tlyric: '',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
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(url.endsWith('.xtrc') ? parseLyric(body) : {
|
|
||||||
lyric: body,
|
|
||||||
tlyric: '',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
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 => /\.(trc|lrc)$/.test(lyric.lyricUrl))
|
|
||||||
return lrc
|
|
||||||
? lrc.lyricUrl.endsWith('.trc')
|
|
||||||
? parseLyric(lrc.content)
|
|
||||||
: { lyric: lrc.content, tlyric: '' }
|
|
||||||
: 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 && /\.(xtrc|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)
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { xmRequest } from './util'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
_requestObj: null,
|
|
||||||
async getMusicInfo({ songmid }, page = 1, limit = 20) {
|
|
||||||
if (this._requestObj) this._requestObj.cancelHttp()
|
|
||||||
|
|
||||||
const _requestObj = xmRequest('/api/song/initialize', { songId: songmid })
|
|
||||||
const { body, statusCode } = await _requestObj.promise
|
|
||||||
// console.log(body)
|
|
||||||
if (statusCode != 200 || body.code !== 'SUCCESS') throw new Error('获取歌曲信息失败')
|
|
||||||
return { source: 'xm', data: body.result.data.songDetail }
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
// 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, limit) {
|
|
||||||
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
|
|
||||||
searchRequest = xmRequest('/api/search/searchSongs', {
|
|
||||||
key: str,
|
|
||||||
pagingVO: {
|
|
||||||
page: page,
|
|
||||||
pageSize: 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,
|
|
||||||
songStringId: songData.songStringId,
|
|
||||||
lrc: null,
|
|
||||||
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
|
|
||||||
otherSource: null,
|
|
||||||
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) limit = this.limit
|
|
||||||
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
|
|
||||||
return this.musicSearch(str, page, limit).then(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 / limit)
|
|
||||||
|
|
||||||
return Promise.resolve({
|
|
||||||
list,
|
|
||||||
allPage: this.allPage,
|
|
||||||
limit,
|
|
||||||
total: this.total,
|
|
||||||
source: 'xm',
|
|
||||||
})
|
|
||||||
}).catch(err => err.message.includes('canceled verify') ? Promise.reject(err) : this.search(str, page, { limit }, retryNum))
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,221 +0,0 @@
|
||||||
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,
|
|
||||||
songStringId: songData.songStringId,
|
|
||||||
img: songData.albumLogo || songData.albumLogoS,
|
|
||||||
lrc: null,
|
|
||||||
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
|
|
||||||
otherSource: null,
|
|
||||||
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
|
|
|
@ -1,122 +0,0 @@
|
||||||
import { httpGet, httpFetch } from '../../request'
|
|
||||||
import { toMD5 } from '../../index'
|
|
||||||
// import crateIsg from './isg'
|
|
||||||
import { rendererInvoke, NAMES } 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 && 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(NAMES.mainWindow.handle_xm_verify_open, /^https:/.test(resp.body.url) ? resp.body.url : 'https:' + resp.body.url).then(x5sec => {
|
|
||||||
handleSaveToken({ cookies: { x5sec } })
|
|
||||||
// console.log(x5sec)
|
|
||||||
window.globalObj.xm.isShowVerify = false
|
|
||||||
return Promise.reject(new Error('获取成功'))
|
|
||||||
}).catch(err => {
|
|
||||||
window.globalObj.xm.isShowVerify = false
|
|
||||||
return Promise.reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
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(NAMES.mainWindow.handle_xm_verify_close)
|
|
||||||
window.globalObj.xm.isShowVerify = false
|
|
||||||
}
|
|
Loading…
Reference in New Issue