Compare commits

..

10 Commits

Author SHA1 Message Date
lyswhut
ee2b01673b 发布0.3.3版本 2019-08-29 00:58:34 +08:00
lyswhut
c392f7ee8d 修复接口失效问题 2019-08-29 00:53:05 +08:00
lyswhut
4adc950e29 新增百度源接口封装 2019-08-27 23:25:34 +08:00
lyswhut
78db5b55ec 统一调用方法 2019-08-27 23:25:18 +08:00
lyswhut
ad40fdcb55 新增酷狗歌单接口 2019-08-26 14:29:52 +08:00
lyswhut
c5ca510a75 新增TODO列表 2019-08-26 11:11:26 +08:00
lyswhut
13c64a95fa 修复手动定位播放进度条时存在偏差的问题 2019-08-25 00:52:47 +08:00
lyswhut
5d74a78bb7 更新readme 2019-08-25 00:38:08 +08:00
lyswhut
d638bae7c4 优化音量条的视觉效果 2019-08-25 00:23:47 +08:00
lyswhut
9faf8ed55e 新增酷狗排行榜其他音质下载 2019-08-24 21:07:12 +08:00
31 changed files with 1032 additions and 75 deletions

View File

@@ -6,6 +6,16 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [0.3.3](https://github.com/lyswhut/lx-music-desktop/compare/v0.3.2...v0.3.3) - 2019-08-29
### 修复
- **messoer**的接口已经关闭,暂时切换到临时接口使用,部分功能受限。。。
- 修复设置界面更新出错时仍然显示更新下载中的问题
- 修复手动定位播放进度条时存在偏差的问题
- 屏蔽播放器中没有歌曲时对进度条的点击
## [0.3.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.3.1...v0.3.2) - 2019-08-24
### 新增

View File

@@ -46,14 +46,23 @@
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
或者到网盘下载:`https://www.lanzous.com/b906260/` 密码:`glqw`
或者到网盘下载网盘内有MAC、windows版`https://www.lanzous.com/b906260/` 密码:`glqw`
歌曲默认下载到桌面,可自行到设置页面更改。<br>
若排行榜上的某些歌曲**无法播放**,可尝试**直接搜索**该歌曲后播放。
#### 关于软件更新
软件启动时若发现新版本时会自动从本仓库下载安装包,下载完毕会弹窗提示更新。<br>
若下载未完成时软件被关闭,下次启动软件会再次自动下载。<br>
目前暂未添加跳过更新某个版本的功能。<br>
注意:**绿色版**的软件更新功能**不可用**,为了能及时地获取更新,建议使用安装版!!
注意:**绿色版**的软件更新功能**不可用**,为了能及时地获取更新,建议使用安装版!!<br>
注意:**Mac版**、**Linux deb**版不支持自动更新!
#### TODO
- [ ] 新增拉取各大平台歌单
- [ ] 新增聚合搜索
### 源码使用方法
@@ -79,6 +88,11 @@ npm run pack
感谢 [@messoer](https://github.com/messoer) 提供的部分音乐API
### 免责声明
本软件仅用于测试 `electron 6.x` 在各种系统上的兼容性使用本软件产生的任何涉及版权相关的数据请于24小时内删除。<br>
使用本软件所造成的的后果由使用者承担!
### 许可证
[Apache License 2.0](https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE)

7
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "lx-music-desktop",
"version": "0.2.4",
"version": "0.3.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -4229,6 +4229,11 @@
"randomfill": "^1.0.3"
}
},
"crypto-js": {
"version": "3.1.9-1",
"resolved": "https://registry.npm.taobao.org/crypto-js/download/crypto-js-3.1.9-1.tgz",
"integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg="
},
"crypto-random-string": {
"version": "1.0.0",
"resolved": "https://registry.npm.taobao.org/crypto-random-string/download/crypto-random-string-1.0.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "lx-music-desktop",
"version": "0.3.2",
"version": "0.3.3",
"description": "一个免费的音乐下载助手",
"main": "./dist/electron/main.js",
"productName": "lx-music-desktop",
@@ -195,6 +195,7 @@
},
"dependencies": {
"axios": "^0.19.0",
"crypto-js": "^3.1.9-1",
"electron-log": "^3.0.7",
"electron-store": "^4.0.0",
"electron-updater": "^4.1.2",

View File

@@ -1,3 +1,7 @@
### 新增
### 修复
- **messoer**的接口已经关闭,暂时切换到临时接口使用,部分功能受限。。。
- 修复设置界面更新出错时仍然显示更新下载中的问题
- 修复手动定位播放进度条时存在偏差的问题
- 屏蔽播放器中没有歌曲时对进度条的点击
- 新增酷狗排行榜其他音质下载

View File

@@ -1,7 +1,11 @@
{
"version": "0.3.2",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增酷狗排行榜其他音质下载</li>\n</ul>\n",
"version": "0.3.3",
"desc": "<h3>修复</h3>\n<ul>\n<li><strong>messoer</strong>的接口已经关闭,暂时切换到临时接口使用,部分功能受限。。。</li>\n<li>修复设置界面更新出错时仍然显示更新下载中的问题</li>\n<li>修复手动定位播放进度条时存在偏差的问题</li>\n<li>屏蔽播放器中没有歌曲时对进度条的点击</li>\n</ul>\n",
"history": [
{
"version": "0.3.2",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增酷狗排行榜其他音质下载</li>\n</ul>\n"
},
{
"version": "0.3.1",
"desc": "<h3>修复</h3>\n<ul>\n<li>修复音量条主题适配</li>\n</ul>\n"

View File

@@ -9,10 +9,10 @@ div(:class="$style.aside")
dt 在线音乐
dd
router-link(:active-class="$style.active" to="search") 搜索
//- dd
router-link(:active-class="$style.active" to="songList") 歌单
dd
router-link(:active-class="$style.active" to="leaderboard") 排行榜
//- dd
router-link(:active-class="$style.active" to="recommend") 歌单
dl
dt 我的音乐
dd

View File

@@ -111,8 +111,10 @@ export default {
},
},
mounted() {
this.setProgessWidth()
this.init()
this.$nextTick(() => {
this.setProgessWidth()
})
},
watch: {
changePlay(n) {
@@ -164,10 +166,11 @@ export default {
...mapMutations(['setVolume']),
...mapMutations('list', ['updateMusicInfo']),
init() {
window.a = this.audio = document.createElement('audio')
this.audio = document.createElement('audio')
this.volume = this.audio.volume = this.setting.player.volume
this.audio.controls = false
this.audio.autoplay = true
this.audio.preload = 'auto'
this.audio.loop = this.setting.player.togglePlayMethod === 'singleLoop'
this.audio.addEventListener('playing', () => {
@@ -360,6 +363,7 @@ export default {
this.clearAppTitle()
},
setProgess(e) {
if (!this.audio.src) return
this.audio.currentTime =
(e.offsetX / this.pregessWidth) * this.maxPlayTime
if (!this.isPlay) this.audio.play()
@@ -566,7 +570,8 @@ export default {
border-radius: @radius-progress-border;
transition-duration: 0.2s;
background-color: @color-theme;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
opacity: .5;
}
.play-btn {

View File

@@ -18,9 +18,9 @@ export default [
view: 'Leaderboard',
},
{
path: '/recommend',
name: 'recommend',
view: 'Recommend',
path: '/songList',
name: 'songList',
view: 'SongList',
},
{
path: '/list',

View File

@@ -2,6 +2,7 @@ import fs from 'fs'
import { shell, remote } from 'electron'
import path from 'path'
import os from 'os'
import crypto from 'crypto'
/**
* 获取两个数之间的随机整数大于等于min小于max
@@ -184,7 +185,7 @@ export const updateSetting = setting => {
},
themeId: 0,
sourceId: 'kw',
apiSource: 'messoer',
apiSource: 'temp',
randomAnimate: true,
ignoreVersion: null,
}
@@ -201,7 +202,7 @@ export const updateSetting = setting => {
objectDeepMerge(defaultSetting, overwriteSetting)
setting = defaultSetting
}
setting.apiSource = 'messoer' // 强制设置回 messoer 接口源
if (setting.apiSource == 'messoer') setting.apiSource = 'temp' // 强制设置回 temp 接口源
return setting
}
@@ -220,3 +221,10 @@ let dom_title = document.getElementsByTagName('title')[0]
export const setTitle = title => {
dom_title.innerText = title || '洛雪音乐助手'
}
/**
* 创建 MD5 hash
* @param {*} str
*/
export const toMD5 = str => crypto.createHash('md5').update(str).digest('hex')

View File

@@ -2,6 +2,6 @@ export const requestMsg = {
fail: '请求异常😮,可以多试几次,若还是不行就换一首吧。。。',
unachievable: '哦No😱...接口无法访问了!',
// unachievable: '哦No😱...接口无法访问了!已帮你切换到临时接口,重试下看能不能播放吧~',
notConnectNetwork: '无法连接网络',
notConnectNetwork: '无法连接到服务器',
cancelRequest: '取消http请求',
}

View File

@@ -1,24 +1,36 @@
import kw_api_messoer from './kw/api-messoer'
import kw_api_temp from './kw/api-temp'
import tx_api_messoer from './tx/api-messoer'
import kg_api_messoer from './kg/api-messoer'
import wy_api_messoer from './wy/api-messoer'
import bd_api_messoer from './bd/api-messoer'
// import kw_api_messoer from './kw/api-messoer'
// import tx_api_messoer from './tx/api-messoer'
// import kg_api_messoer from './kg/api-messoer'
// import wy_api_messoer from './wy/api-messoer'
// import bd_api_messoer from './bd/api-messoer'
import kw_api_internal from './kw/api-internal'
import tx_api_internal from './tx/api-internal'
import kg_api_internal from './kg/api-internal'
import wy_api_internal from './wy/api-internal'
import bd_api_internal from './bd/api-internal'
const apis = {
kw_api_messoer,
tx_api_messoer,
kg_api_messoer,
wy_api_messoer,
bd_api_messoer,
// kw_api_messoer,
// tx_api_messoer,
// kg_api_messoer,
// wy_api_messoer,
// bd_api_messoer,
kw_api_internal,
tx_api_internal,
kg_api_internal,
wy_api_internal,
bd_api_internal,
kw_api_temp,
}
const getAPI = source => {
switch (window.globalObj.apiSource) {
case 'messoer':
return apis[`${source}_api_messoer`]
// case 'messoer':
// return apis[`${source}_api_messoer`]
case 'internal':
return apis[`${source}_api_internal`]
case 'temp':
return apis[`${source}_api_temp`]
}

View File

@@ -0,0 +1,44 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
const api_internal = {
successCode: 2200,
// getMusicUrl(songInfo, type) {
// const requestObj = httpFatch(`http://play.taihe.com/data/music/songlink`, {
// method: 'post',
// headers: {
// Origin: 'http://play.taihe.com',
// },
// formData: {
// songIds: songInfo.songmid,
// },
// })
// requestObj.promise = requestObj.promise.then(({ body }) => {
// console.log(body)
// return Promise.reject()
// // if (body.error_code !== this.successCode) return this.getMusicUrl(songInfo, type)
// // return body.code === 200 ? Promise.resolve({ type, url: body.result.bitrate.file_link }) : Promise.reject(new Error(requestMsg.fail))
// })
// return requestObj
// },
getMusicInfo(songInfo, tryNum = 0) {
const requestObj = httpFatch(`http://tingapi.ting.baidu.com/v1/restserver/ting?from=webapp_music&method=baidu.ting.song.baseInfos&song_id=${songInfo.songmid}`)
requestObj.promise = requestObj.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return tryNum > 5 ? Promise.reject(new Error(requestMsg.fail)) : this.getMusicInfo(songInfo, ++tryNum)
return body ? Promise.resolve(body.result.items[0]) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getPic(songInfo) {
const requestObj = this.getMusicInfo(songInfo)
requestObj.promise = requestObj.promise.then(info => info.pic_premium)
return requestObj
},
getLyric(songInfo) {
const requestObj = this.getMusicInfo(songInfo)
requestObj.promise.then(info => httpFatch(info.lrclink).promise)
return requestObj
},
}
export default api_internal

View File

@@ -1,11 +1,13 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../messoer'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/baidu/url?id=${songInfo.songmid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
@@ -15,7 +17,8 @@ const api_messoer = {
getPic(songInfo, size = '500') {
const requestObj = httpFatch(`https://v1.itooi.cn/baidu/pic?id=${songInfo.songmid}&imageSize=${size}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
@@ -25,7 +28,8 @@ const api_messoer = {
getLyric(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/baidu/lrc?id=${songInfo.songmid}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body ? Promise.resolve(body) : Promise.reject(new Error(requestMsg.fail))

View File

@@ -0,0 +1,245 @@
import { httpFatch } from '../../request'
import { formatPlayTime, toMD5 } from '../../index'
import CryptoJS from 'crypto-js'
export default {
_requestObj_tags: null,
_requestObj_list: null,
_requestObj_listRecommend: null,
_requestObj_listDetail: null,
limit_list: 20,
limit_song: 25,
successCode: 22000,
sortList: [
{
name: '最热',
id: '最热',
},
{
name: '最新',
id: '最新',
},
],
aesPassEncod(jsonData) {
let timestamp = Math.floor(Date.now() / 1000)
let privateKey = toMD5('baidu_taihe_music_secret_key' + timestamp).substr(8, 16)
let key = CryptoJS.enc.Utf8.parse(privateKey)
let iv = CryptoJS.enc.Utf8.parse(privateKey)
let arrData = []
let strData = ''
for (let key in jsonData) arrData.push(key)
arrData.sort()
for (let i = 0; i < arrData.length; i++) {
let key = arrData[i]
strData +=
(i === 0 ? '' : '&') + key + '=' + encodeURIComponent(jsonData[key])
}
let JsonFormatter = {
stringify(cipherParams) {
let jsonObj = {
ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64),
}
if (cipherParams.iv) {
jsonObj.iv = cipherParams.iv.toString()
}
if (cipherParams.salt) {
jsonObj.s = cipherParams.salt.toString()
}
return jsonObj
},
parse(jsonStr) {
let jsonObj = JSON.parse(jsonStr)
let cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct),
})
if (jsonObj.iv) {
cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv)
}
if (jsonObj.s) {
cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s)
}
return cipherParams
},
}
let encrypted = CryptoJS.AES.encrypt(strData, key, {
iv: iv,
blockSize: 16,
mode: CryptoJS.mode.CBC,
format: JsonFormatter,
})
let ciphertext = encrypted.toString().ct
let sign = toMD5('baidu_taihe_music' + ciphertext + timestamp)
let jsonRet = {
timestamp: timestamp,
param: ciphertext,
sign: sign,
}
return jsonRet
},
createUrl(param, method) {
let data = this.aesPassEncod(param)
return `http://musicmini.qianqian.com/v1/restserver/ting?method=${method}&time=${Date.now()}&timestamp=${data.timestamp}&param=${data.param}&sign=${data.sign}`
},
getTagsUrl() {
return this.createUrl({
from: 'qianqianmini',
type: 'diy',
version: '10.1.8',
}, 'baidu.ting.ugcdiy.getChannels')
},
getListUrl(sortType, tagName, page) {
return this.createUrl({
channelname: tagName,
from: 'qianqianmini',
offset: (page - 1) * this.limit_list,
order_type: sortType,
size: this.limit_list,
version: '10.1.8',
}, 'baidu.ting.ugcdiy.getChanneldiy')
},
getListDetailUrl(list_id, page) {
return this.createUrl({
list_id,
offset: (page - 1) * this.limit_song,
size: this.limit_song,
withcount: '1',
withsong: '1',
}, 'baidu.ting.ugcdiy.getBaseInfo')
},
// 获取标签
getTags() {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
this._requestObj_tags = httpFatch(this.getTagsUrl())
return this._requestObj_tags.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return this.getTags()
return {
hotTag: this.filterInfoHotTag(body.data.hot),
tags: this.filterTagInfo(body.data.tags),
}
})
},
filterInfoHotTag(rawList) {
return rawList.map(item => ({
name: item,
id: item,
}))
},
filterTagInfo(rawList) {
return rawList.map(type => ({
type: type.first,
list: type.second.map(item => ({
parent_id: type.first,
parent_name: type.first,
id: item,
name: item,
})),
}))
},
// 获取列表数据
getList(sortId, tagId, page) {
if (this._requestObj_list) this._requestObj_list.cancelHttp()
this._requestObj_list = httpFatch(
this.getListUrl(sortId, tagId, page)
)
return this._requestObj_list.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return this.getSongList(sortId, tagId, page)
return {
list: this.filterList(body.diyInfo),
total: body.nums,
page,
limit: this.limit_list,
}
})
},
/**
* 格式化播放数量
* @param {*} num
*/
formatPlayCount(num) {
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
return num
},
filterList(rawData) {
return rawData.map(item => ({
play_count: this.formatPlayCount(item.listen_num),
id: item.list_id,
author: item.username,
name: item.title,
// time: item.publish_time,
img: item.list_pic_large || item.list_pic,
grade: item.grade,
desc: item.desc || item.tag,
}))
},
// 获取歌曲列表内的音乐
getListDetail(id, page) {
if (this._requestObj_listDetail) {
this._requestObj_listDetail.cancelHttp()
}
this._requestObj_listDetail = httpFatch(this.getListDetailUrl(id, page))
return this._requestObj_listDetail.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return this.getListDetail(id, page)
let listData = this.filterData(body.result.songlist)
return {
list: listData,
page,
limit: this.limit_song,
total: body.result.song_num,
}
})
},
filterData(rawList) {
// console.log(rawList)
return rawList.map(item => {
const types = []
const _types = {}
let size = null
let itemTypes = item.all_rate.split(',')
if (itemTypes.includes('128')) {
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
}
if (itemTypes.includes('320')) {
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
}
if (itemTypes.includes('flac')) {
types.push({ type: 'flac', size })
_types['flac'] = {
size,
}
}
// types.reverse()
return {
singer: item.author.replace(',', '、'),
name: item.title,
albumName: item.album_title,
albumId: item.album_id,
source: 'bd',
interval: formatPlayTime(parseInt(item.file_duration)),
songmid: item.song_id,
img: item.pic_s500,
lrc: null,
types,
_types,
typeUrl: {},
}
})
},
}
// getList
// getTags
// getListDetail

View File

@@ -0,0 +1,41 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../messoer'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/kugou/url?id=${songInfo._types[type].hash}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getPic(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/kugou/pic?id=${songInfo.hash}&isRedirect=0`, {
method: 'get',
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getLyric(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/kugou/lrc?id=${songInfo.hash}&isRedirect=0`, {
method: 'get',
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body ? Promise.resolve(body) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
}
export default api_messoer

View File

@@ -1,11 +1,13 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../messoer'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/kugou/url?id=${songInfo._types[type].hash}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
@@ -15,7 +17,8 @@ const api_messoer = {
getPic(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/kugou/pic?id=${songInfo.hash}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
@@ -25,7 +28,8 @@ const api_messoer = {
getLyric(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/kugou/lrc?id=${songInfo.hash}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body ? Promise.resolve(body) : Promise.reject(new Error(requestMsg.fail))

View File

@@ -0,0 +1,257 @@
import { httpFatch } from '../../request'
import { formatPlayTime, sizeFormate } from '../../index'
export default {
_requestObj_tagInfo: null,
_requestObj_list: null,
_requestObj_listDetail: null,
currentTagInfo: {
id: null,
info: null,
},
sortList: [
{
name: '推荐',
id: '5',
},
{
name: '最热',
id: '6',
},
{
name: '最新',
id: '7',
},
{
name: '热藏',
id: '3',
},
{
name: '飙升',
id: '8',
},
],
regExps: {
listData: /global\.data = (\[.+\]);/,
},
getInfoUrl(tagId) {
return tagId
? `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&cdn=cdn&t=5&c=${tagId}`
: `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&`
},
getSongListUrl(sortId, tagId, page) {
return `http://www2.kugou.kugou.com/yueku/v9/special/index/getData/getData.html&cdn=cdn&t=${sortId}&c=${tagId}?is_ajax=1&p=${page}`
},
getSongListDetailUrl(id) {
return `http://www2.kugou.kugou.com/yueku/v9/special/single/${id}-5-9999.html`
},
getTagInfo(tagId) {
if (this._requestObj_tagInfo) this._requestObj_tagInfo.cancelHttp()
this._requestObj_tagInfo = httpFatch(this.getInfoUrl(tagId))
return this._requestObj_tagInfo.promise.then(({ body }) => {
if (body.status !== 1) return this.getTagInfo(tagId)
return {
hotTag: this.filterInfoHotTag(body.data.hotTag),
tags: this.filterTagInfo(body.data.tagids),
tagInfo: {
limit: body.data.params.pagesize,
page: body.data.params.p,
total: body.data.params.total,
},
}
})
},
filterInfoHotTag(rawData) {
const result = []
if (rawData.status !== 1) return result
for (let index = 0; index < Object.keys(rawData.data).lengt; index++) {
let tag = rawData.data[index.toString()]
result.push({
id: tag.id,
name: tag.special_name,
})
}
return result
},
filterTagInfo(rawData) {
const result = []
for (const type of Object.keys(rawData)) {
result.push({
type,
list: rawData[type].data.map(tag => ({
parent_id: tag.parent_id,
parent_name: tag.pname,
id: tag.id,
name: tag.name,
})),
})
}
},
getSongList(sortId, tagId, page) {
if (this._requestObj_list) this._requestObj_list.cancelHttp()
this._requestObj_list = httpFatch(
this.getSongListUrl(sortId, tagId, page)
)
return this._requestObj_list.promise.then(({ body }) => {
if (body.status !== 1) return this.getSongList(sortId, tagId, page)
return this.filterList(body.data)
})
},
getSongListRecommend() {
if (this._requestObj_listRecommend) this._requestObj_listRecommend.cancelHttp()
this._requestObj_listRecommendRecommend = httpFatch(
'http://everydayrec.service.kugou.com/guess_special_recommend',
{
method: 'post',
headers: {
'User-Agent': 'KuGou2012-8275-web_browser_event_handler',
},
body: {
appid: 1001,
clienttime: 1566798337219,
clientver: 8275,
key: 'f1f93580115bb106680d2375f8032d96',
mid: '21511157a05844bd085308bc76ef3343',
platform: 'pc',
userid: '262643156',
return_min: 6,
return_max: 15,
},
}
)
return this._requestObj_listRecommend.promise.then(({ body }) => {
if (body.status !== 1) return this.getSongListRecommend()
return this.filterList(body.data)
})
},
filterList(rawData) {
return rawData.map(item => ({
play_count: item.total_play_count,
id: item.specialid,
author: item.nickname,
name: item.specialname,
time: item.publish_time,
img: item.img,
grade: item.grade,
desc: item.intro,
}))
},
getListDetail(id, page) { // 获取歌曲列表内的音乐
if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp()
this._requestObj_listDetail = httpFatch(this.getSongListDetailUrl(id))
return this._requestObj_listDetail.promise.then(({ body }) => {
let listData = body.match(this.regExps.listData)
if (listData) listData = this.filterData(JSON.parse(RegExp.$1))
return {
list: listData,
page: 1,
limit: 10000,
total: listData.length,
}
})
},
filterData(rawList) {
// console.log(rawList)
return rawList.map(item => {
const types = []
const _types = {}
if (item.filesize !== 0) {
let size = sizeFormate(item.filesize)
types.push({ type: '128k', size, hash: item.hash })
_types['128k'] = {
size,
hash: item.hash,
}
}
if (item.filesize_320 !== 0) {
let size = sizeFormate(item.filesize_320)
types.push({ type: '320k', size, hash: item.hash_320 })
_types['320k'] = {
size,
hash: item.hash_320,
}
}
if (item.filesize_ape !== 0) {
let size = sizeFormate(item.filesize_ape)
types.push({ type: 'ape', size, hash: item.hash_ape })
_types.ape = {
size,
hash: item.hash_ape,
}
}
if (item.filesize_flac !== 0) {
let size = sizeFormate(item.filesize_flac)
types.push({ type: 'flac', size, hash: item.hash_flac })
_types.flac = {
size,
hash: item.hash_flac,
}
}
return {
singer: item.singername,
name: item.songname,
albumName: item.album_name,
albumId: item.album_id,
songmid: item.audio_id,
source: 'kg',
interval: formatPlayTime(item.duration / 1000),
img: null,
lrc: null,
hash: item.HASH,
types,
_types,
typeUrl: {},
}
})
},
// 获取列表信息
getListInfo(tagId) {
return this.getTagInfo(tagId).then(info => {
return {
limit: info.tagInfo.limit,
page: info.tagInfo.page,
total: info.tagInfo.total,
}
})
},
// 获取列表数据
getList(sortId, tagId, page) {
let tasks = [this.getSongList(sortId, tagId, page)]
tasks.push(
this.currentTagInfo.id === tagId
? Promise.resolve(this.currentTagInfo.info)
: this.getListInfo(tagId).then(info => {
this.currentTagInfo.id = tagId
this.currentTagInfo.info = Object.assign({}, info)
return info
})
)
if (!tagId) tasks.push(this.getSongListRecommend()) // 如果是所有类别,则顺便获取推荐列表
Promise.all(tasks).then(([list, info, recommendList]) => {
if (recommendList) list.unshift(...recommendList)
return {
list,
...info,
}
})
},
// 获取标签
getTags() {
return this.getTagInfo().then(info => {
return {
hotTag: info.hotTag,
tags: info.tags,
}
})
},
}
// getList
// getTags
// getListDetail

View File

@@ -0,0 +1,30 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../messoer'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/kuwo/url?id=${songInfo.songmid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getPic(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/kuwo/pic?id=${songInfo.songmid}&isRedirect=0`, {
method: 'get',
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
}
export default api_messoer

View File

@@ -1,11 +1,13 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../messoer'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/kuwo/url?id=${songInfo.songmid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
@@ -15,7 +17,8 @@ const api_messoer = {
getPic(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/kuwo/pic?id=${songInfo.songmid}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))

View File

@@ -2,7 +2,7 @@ import { httpFatch } from '../../request'
const api_temp = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://www.stsky.cn/api/temp/getMusicUrl.php?id=${songInfo.songmid}&type=${type}`, {
const requestObj = httpFatch(`http://45.32.53.128:3002/m/kw/u/${songInfo.songmid}/${type}`, {
method: 'get',
})
requestObj.promise = requestObj.promise.then(({ body }) => {
@@ -11,7 +11,7 @@ const api_temp = {
return requestObj
},
getPic(songInfo) {
const requestObj = httpFatch(`https://www.stsky.cn/api/temp/getPic.php?size=320&songmid=${songInfo.songmid}`, {
const requestObj = httpFatch(`http://45.32.53.128:3002/m/kw/i/${songInfo.songmid}`, {
method: 'get',
})
requestObj.promise = requestObj.promise.then(({ body }) => {

View File

@@ -1,5 +1,7 @@
import { httpGet, cancelHttp } from '../../request'
import { formatPlayTime } from '../../index'
import { formatPlayTime, decodeName } from '../../index'
import { formatSinger } from './util'
export default {
list: [
@@ -143,9 +145,9 @@ export default {
}
// types.reverse()
return {
singer: item.artist,
name: item.name,
albumName: item.album,
singer: formatSinger(decodeName(item.artist)),
name: decodeName(item.name),
albumName: decodeName(item.album),
albumId: item.albumid,
songmid: item.id,
source: 'kw',

View File

@@ -0,0 +1,173 @@
import { httpFatch } from '../../request'
import { formatPlayTime, decodeName } from '../../index'
import { formatSinger } from './util'
export default {
_requestObj_tags: null,
_requestObj_hotTags: null,
_requestObj_list: null,
_requestObj_listDetail: null,
limit_list: 100,
limit_song: 25,
successCode: 200,
sortList: [
{
name: '最热',
id: 'hot',
},
{
name: '最新',
id: 'new',
},
],
tagsUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getTagList?cmd=rcm_keyword_playlist&user=0&prod=kwplayer_pc_9.0.5.0&vipver=9.0.5.0&source=kwplayer_pc_9.0.5.0&loginUid=0&loginSid=0&appUid=76039576',
hotTagUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmTagList?loginUid=0&loginSid=0&appUid=76039576',
getListUrl({ sortType, id, page }) {
return id
? `http://wapi.kuwo.cn/api/pc/classify/playlist/getTagPlayList?loginUid=0&loginSid=0&appUid=76039576&id=${id}&pn=${page}&rn=${this.limit}`
: `http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmPlayList?loginUid=0&loginSid=0&appUid=76039576&pn=${page}&rn=${this.limit}&order=${sortType}`
},
getListDetailUrl(id, page) {
return `http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}&rn=${this.limit}&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1`
},
// 获取标签
getTags() {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
this._requestObj_tags = httpFatch(this.tagsUrl)
return this._requestObj_tags.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getTags()
return this.filterTagInfo(body.data.tags)
})
},
// 获取标签
getHotTags() {
if (this._requestObj_hotTags) this._requestObj_hotTags.cancelHttp()
this._requestObj_hotTags = httpFatch(this.hotTagUrl)
return this._requestObj_hotTags.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getHotTags()
return this.filterInfoHotTag(body.data.data)
})
},
filterInfoHotTag(rawList) {
return rawList.map(item => ({
id: item.id,
name: item.name,
}))
},
filterTagInfo(rawList) {
return rawList.map(type => ({
type: type.name,
list: type.data.map(item => ({
parent_id: type.id,
parent_name: type.name,
id: item.id,
name: item.name,
})),
}))
},
// 获取列表数据
getList(sortId, tagId, page) {
if (this._requestObj_list) this._requestObj_list.cancelHttp()
this._requestObj_list = httpFatch(
this.getListUrl({ sortId, id: tagId, page })
)
return this._requestObj_list.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getList({ sortId, id: tagId, page })
return {
list: this.filterList(body.data.data),
total: body.data.total,
page: body.data.pn,
limit: body.data.rn,
}
})
},
/**
* 格式化播放数量
* @param {*} num
*/
formatPlayCount(num) {
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
return num
},
filterList(rawData) {
return rawData.map(item => ({
play_count: this.formatPlayCount(item.listencnt),
id: item.id,
author: item.uname,
name: item.name,
// time: item.publish_time,
img: item.img,
grade: item.favorcnt / 10,
desc: item.desc,
}))
},
// 获取歌曲列表内的音乐
getListDetail(id, page) {
if (this._requestObj_listDetail) {
this._requestObj_listDetail.cancelHttp()
}
this._requestObj_listDetail = httpFatch(this.getListDetailUrl(id, page))
return this._requestObj_listDetail.promise.then(({ body }) => {
if (body.result !== 'ok') return this.getListDetail(id, page)
return {
list: this.filterListDetail(body.data.musiclist),
page,
limit: body.rn,
total: body.total,
}
})
},
filterListDetail(rawData) {
// console.log(rawList)
return rawData.map((item, inedx) => {
let formats = item.formats.split('|')
let types = []
let _types = {}
if (formats.indexOf('MP3128')) {
types.push({ type: '128k', size: null })
_types['128k'] = {
size: null,
}
}
if (formats.indexOf('MP3H')) {
types.push({ type: '320k', size: null })
_types['320k'] = {
size: null,
}
}
if (formats.indexOf('ALFLAC')) {
types.push({ type: 'flac', size: null })
_types['flac'] = {
size: null,
}
}
return {
singer: formatSinger(decodeName(item.artist)),
name: decodeName(item.name),
albumName: decodeName(item.album),
albumId: item.albumid,
songmid: item.id,
source: 'kw',
interval: formatPlayTime(parseInt(item.duration)),
img: null,
lrc: null,
types,
_types,
typeUrl: {},
}
})
},
}
// getList
// getTags
// getListDetail

View File

@@ -0,0 +1,9 @@
export const bHh = '624868746c'
export const headers = {
'User-Agent': 'lx-music request',
[bHh]: [bHh],
}
export const timeout = 10000

View File

@@ -0,0 +1,24 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../messoer'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/tencent/url?id=${songInfo.strMediaMid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getPic(songInfo) {
return {
promise: Promise.resolve(`https://y.gtimg.cn/music/photo_new/T002R500x500M000${songInfo.albumId}.jpg`),
}
},
}
export default api_messoer

View File

@@ -1,11 +1,13 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../messoer'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/tencent/url?id=${songInfo.strMediaMid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))

View File

@@ -0,0 +1,41 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../messoer'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/netease/url?id=${songInfo.songmid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getPic(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/netease/pic?id=${songInfo.songmid}&isRedirect=0`, {
method: 'get',
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
getLyric(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/netease/lrc?id=${songInfo.songmid}&isRedirect=0`, {
method: 'get',
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body ? Promise.resolve(body) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
}
export default api_messoer

View File

@@ -1,11 +1,13 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../messoer'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/netease/url?id=${songInfo.songmid}&quality=${type.replace(/k$/, '')}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
@@ -15,7 +17,8 @@ const api_messoer = {
getPic(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/netease/pic?id=${songInfo.songmid}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
@@ -25,7 +28,8 @@ const api_messoer = {
getLyric(songInfo) {
const requestObj = httpFatch(`https://v1.itooi.cn/netease/lrc?id=${songInfo.songmid}&isRedirect=0`, {
method: 'get',
timeout: 5000,
timeout,
headers,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body ? Promise.resolve(body) : Promise.reject(new Error(requestMsg.fail))

View File

@@ -2,28 +2,11 @@ import request from 'request'
// import progress from 'request-progress'
import { debugRequest } from './env'
import { requestMsg } from './message'
import { bHh } from './music/messoer'
// import fs from 'fs'
const 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',
}
const fatchData = (url, method, options, callback) => {
// console.log(url, options)
console.log('---start---', url)
return request(url, {
method,
headers: Object.assign({}, headers, options.headers || {}),
Origin: options.origin,
data: options.data,
timeout: options.timeout || 10000,
json: options.format === undefined || options.format === 'json',
}, (err, resp, body) => {
if (err) return callback(err, null)
// console.log('---end---', url)
callback(null, resp, body)
})
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
}
/**
@@ -231,3 +214,26 @@ export const http_jsonp = (url, options, callback) => {
callback(err, resp, body)
})
}
const fatchData = (url, method, options, callback) => {
// console.log(url, options)
console.log('---start---', url)
if (options.headers && options.headers[bHh]) {
let s = Buffer.from(bHh, 'hex').toString()
s = s.replace(s.substr(-1), '')
s = Buffer.from(s, 'base64').toString()
options.headers[s] = !!s
delete options.headers[bHh]
}
return request(url, {
method,
headers: Object.assign({}, headers, options.headers || {}),
Origin: options.origin,
data: options.data,
timeout: options.timeout || 10000,
json: options.format === undefined || options.format === 'json',
}, (err, resp, body) => {
if (err) return callback(err, null)
callback(null, resp, body)
})
}

View File

@@ -74,7 +74,7 @@ div.scroll(:class="$style.setting")
p.small 当前版本{{version.version}}
p.small(v-if="version.newVersion")
span(v-if="isLatestVer") 软件已是最新尽情地体验吧~🥂
material-btn(v-else-if="setting.ignoreVersion" :class="[$style.btn, $style.gapLeft]" min @click="showUpdateModal") 打开更新窗口
material-btn(v-else-if="setting.ignoreVersion || version.isError" :class="[$style.btn, $style.gapLeft]" min @click="showUpdateModal") 打开更新窗口 🚀
span(v-else) 发现新版本并在努力下载中请稍等...
p.small(v-else) 检查更新中...
dt 关于洛雪音乐
@@ -164,14 +164,19 @@ export default {
apiSources: [
{
id: 'messoer',
label: '由 messoer 提供的接口(推荐,软件的所有功能都可用)',
disabled: false,
// label: '由 messoer 提供的接口(推荐,软件的所有功能都可用)',
label: '由 messoer 提供的接口(该接口已关闭)',
disabled: true,
},
// {
// id: 'internal',
// label: '内置接口只能试听或下载128k音质该接口支持软件的所有功能',
// disabled: false,
// },
{
id: 'temp',
// label: '临时接口(软件的某些功能将不可用,建议在messoer不可用时再切换到本选项',
label: '临时接口(因服务器被攻击,本接口已关闭)',
disabled: true,
label: '临时接口(软件的某些功能将不可用,但可下载无损等音质)',
disabled: false,
},
],
musicNames: [

View File

@@ -5,7 +5,7 @@
<script>
export default {
name: 'About',
name: 'SongList',
data() {
return {
count: 0,