Compare commits

..

30 Commits

Author SHA1 Message Date
lyswhut
1786376a9e 优化请求,修复加载bug 2019-08-29 13:41:44 +08:00
lyswhut
aa973ea984 Update README.md
update
2019-08-29 08:37:18 +08:00
lyswhut
67aeeb0b2f Update README.md
更新readme
2019-08-29 08:36:21 +08:00
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
lyswhut
3aa23b0f9d 发布0.3.2版本 2019-08-24 19:36:34 +08:00
lyswhut
063bbc9f06 新增酷狗排行榜其他音质下载 2019-08-24 19:36:00 +08:00
lyswhut
16bb23b8a7 修复音量条主题适配 2019-08-24 19:00:22 +08:00
lyswhut
a26bef6317 发布v0.3.0版本 2019-08-24 16:19:46 +08:00
lyswhut
21c41d5af1 新增音量调整 2019-08-24 16:18:04 +08:00
lyswhut
b270096591 优化更新弹窗功能,新增非安装版弹窗提醒 2019-08-24 14:24:07 +08:00
lyswhut
be689f088d 更新readme 2019-08-24 10:31:53 +08:00
lyswhut
2410409342 更新readme 2019-08-23 20:42:05 +08:00
lyswhut
a24d682747 重命名appveyor文件 2019-08-23 20:12:36 +08:00
lyswhut
2ada44a2fc 修复取消任务栏进度条可能导致的bug 2019-08-23 20:10:09 +08:00
lyswhut
d6c7fb8dcc 新增MAC、Linux构建并兼容Linux 2019-08-23 19:28:54 +08:00
lyswhut
bb40a8612a 去除切换到临时接口功能 2019-08-22 23:57:10 +08:00
lyswhut
1182a6eba4 删除重复部分 2019-08-22 23:52:00 +08:00
lyswhut
0d303889ef 修正readme文字无法加粗的问题 2019-08-22 23:26:17 +08:00
lyswhut
2b3aab2faf 更新到v0.2.3版本 2019-08-22 23:02:59 +08:00
lyswhut
893d20b4e9 使用临时接口时,试听列表中的下载按钮仍然能点击的Bug 2019-08-22 00:42:42 +08:00
lyswhut
32e421356f 移除专辑栏隐藏设置 2019-08-22 00:29:32 +08:00
60 changed files with 1647 additions and 205 deletions

View File

@@ -4,14 +4,14 @@ platform:
cache:
- node_modules
- '%APPDATA%\npm-cache'
# - '%USERPROFILE%\.electron'
- '%LOCALAPPDATA%\electron\Cache'
install:
- ps: Install-Product node 12 x64
- npm install
build_script:
- npm run pub:gh
- npm run publish:gh
test: off

3
.gitignore vendored
View File

@@ -34,6 +34,7 @@ build/Release
# Dependency directories
node_modules/
node_modules.bak*/
jspm_packages/
# TypeScript v1 declaration files
@@ -68,3 +69,5 @@ dist
publish/assets
publish/utils/githubToken.js
src/**/*-internal.js

43
.travis.yml Normal file
View File

@@ -0,0 +1,43 @@
matrix:
include:
- os: osx
osx_image: xcode10.2
language: node_js
node_js: "12"
env:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
- os: linux
language: node_js
node_js: "12"
dist: trusty
services: docker
language: generic
cache:
directories:
- node_modules
- $HOME/.cache/electron
- $HOME/.cache/electron-builder
- $HOME/.npm/_prebuilds
notifications:
email: false
script:
- |
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
npm install && npm run publish:gh:linux
else
npm run publish:gh:mac
fi
before_cache:
- rm -rf $HOME/.cache/electron-builder/wine
# only run this script on pull requests and merges into
# the 'master' and 'prod' branches
branches:
only:
- master

View File

@@ -6,6 +6,68 @@ 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.4](https://github.com/lyswhut/lx-music-desktop/compare/v0.3.3...v0.3.4) - 2019-08-29
### 优化
- 减少接口不稳定带来的影响,适当增加请求等待时间
### 修复
- 修复播放过程中URL过期不会刷新URL的问题
## [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
### 新增
- 新增酷狗排行榜其他音质下载
## [0.3.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.3.0...v0.3.1) - 2019-08-24
### 修复
- 修复音量条主题适配
## [0.3.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.2.3...v0.3.0) - 2019-08-24
### 新增
- 新增**MAC**及**Linux**版本(需要的可自行下载)
- 新增音量调整
- 新增任务栏播放进度条控制选项(现在可在设置界面关闭在任务栏显示的播放进度)
- 新增更新出错时的弹窗提示
- 从该版本起,非安装版也会有更新弹窗提醒了,但仍然需要手动下载新版本更新,版本信息可到设置页面查看
### 修复
- 强制把临时接口设置回 `messoer` 接口
## [0.2.3](https://github.com/lyswhut/lx-music-desktop/compare/v0.2.2...v0.2.3) - 2019-08-22
### 新增
- 新增任务栏程序标题改变功能(播放歌曲时任务栏标题将显示当前播放的歌曲)
### 修复
- 使用临时接口时试听列表中的下载按钮仍然能点击的Bug
- 修复某些情况下歌曲链接未能缓存的问题
### 移除
- 移除临时接口(因服务器被攻击,本接口已关闭)
- 移除列表栏设置的隐藏专辑栏选项感觉这个设置并没有什么luan用并且还会打破布局
## [0.2.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.2.1...v0.2.2) - 2019-08-21
### 修复

View File

@@ -3,7 +3,8 @@
<p align="center">
<a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/release/lyswhut/lx-music-desktop" alt="Release version"></a>
<a href="https://ci.appveyor.com/project/lyswhut/lx-music-desktop"><img src="https://ci.appveyor.com/api/projects/status/flrsqd5ymp8fnte5?svg=true" alt="Build status"></a>
<a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/downloads/lyswhut/lx-music-desktop/latest/total" alt="Downloads"></a>
<a href="https://travis-ci.org/lyswhut/lx-music-desktop"><img src="https://travis-ci.org/lyswhut/lx-music-desktop.svg?branch=master" alt="Build status"></a>
<!-- <a href="https://github.com/lyswhut/lx-music-desktop/releases"><img src="https://img.shields.io/github/downloads/lyswhut/lx-music-desktop/latest/total" alt="Downloads"></a> -->
<a href="https://github.com/lyswhut/lx-music-desktop/tree/dev"><img src="https://img.shields.io/github/package-json/v/lyswhut/lx-music-desktop/dev" alt="Dev branch version"></a>
<!-- <a href="https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE"><img src="https://img.shields.io/github/license/lyswhut/lx-music-desktop" alt="License"></a> -->
</p>
@@ -30,23 +31,35 @@
### 说明
一个基于 Electron + Vue 开发的 Windows 版音乐软件。
一个基于 Electron + Vue 开发的音乐软件。
所用技术栈:
- Electron 6.x
- Vue 2.x
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)
已支持的平台:
其他说明TODO
- Windows 7 及以上
- Mac OS
- Linux
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
或者到网盘下载网盘内有MAC、windows版`https://www.lanzous.com/b906260/` 密码:`glqw`
#### 关于软件更新
软件启动时若发现新版本时会自动从本仓库下载安装包,下载完毕会弹窗提示更新。<br>
若下载未完成时软件被关闭,下次启动软件会再次自动下载。<br>
目前暂未添加跳过更新某个版本的功能。<br>
注意:**绿色版**的软件更新功能**不可用**,为了能及时地获取更新,建议使用安装版!!<br>
注意:**Mac版**、**Linux deb**版不支持自动更新!
#### TODO
- [ ] 新增拉取各大平台歌单
- [ ] 新增聚合搜索
### 源码使用方法
@@ -59,7 +72,7 @@ npm run dev
# 构建免安装版
npm run pack:dir
# 构建安装包
# 构建安装包windows版
npm run pack
```
@@ -72,6 +85,12 @@ npm run pack
感谢 [@messoer](https://github.com/messoer) 提供的部分音乐API
### 免责声明
本项目**不开发或者破解直接获取音频数据**的功能,所有音频数据均来自**第三方接口**<br>
本软件仅用于**测试 `electron 6.x` 在各种系统上的兼容性**及用于**对比各大音乐平台歌单、排行榜等数据列表的差异性**,使用本软件产生的**任何涉及版权相关的数据**请于**24小时内删除**。<br>
使用本软件所造成的的后果由使用者承担!
### 许可证
[Apache License 2.0](https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE)

View File

@@ -7,11 +7,6 @@ module.exports = {
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../../dist/electron'),
},
externals: [
// suppress electron-debug warning
// see https://github.com/SimulatedGREG/electron-vue/issues/498
{ 'electron-debug': 'electron-debug' },
],
resolve: {
alias: {
common: path.join(__dirname, '../../src/common'),

5
licenses/license_en.txt Normal file
View File

@@ -0,0 +1,5 @@
This program is only for learning to communicate!
Do not use for commercial purposes! !
All consequences of using this software are borne by the user!
By: lyswhut

5
licenses/license_zh.txt Normal file
View File

@@ -0,0 +1,5 @@
本程序仅用于学习交流使用!
请勿用于商业用途!!
使用本软件造成的一切后果由使用者承担!
By: 落雪无痕

7
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "lx-music-desktop",
"version": "0.1.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,12 +1,40 @@
{
"name": "lx-music-desktop",
"version": "0.2.2",
"version": "0.3.4",
"description": "一个免费的音乐下载助手",
"main": "./dist/electron/main.js",
"productName": "lx-music-desktop",
"scripts": {
"pack": "node build-config/pack.js && npm run pack:win",
"pack:win": "npm run pack:win:setup && npm run pack:win:7z",
"pack:win:setup": "cross-env TARGET=Setup ARCH=x64_x86 electron-builder -w=nsis --x64 --ia32",
"pack:win:portable": "npm run pack:win:portable:x64_x86 && npm run pack:win:portable:x64 && npm run pack:win:portable:x86",
"pack:win:portable:x64_x86": "cross-env TARGET=便携版 ARCH=x64_x86 electron-builder -w=portable --x64 --ia32",
"pack:win:portable:x64": "cross-env TARGET=便携版 ARCH=x64 electron-builder -w=portable --x64",
"pack:win:portable:x86": "cross-env TARGET=便携版 ARCH=x86 electron-builder -w=portable --ia32",
"pack:win:7z": "npm run pack:win:7z:x64 && npm run pack:win:7z:x86",
"pack:win:7z:x64": "cross-env TARGET=绿色版 ARCH=x64 electron-builder -w=7z --x64",
"pack:win:7z:x86": "cross-env TARGET=绿色版 ARCH=x86 electron-builder -w=7z --ia32",
"publish": "node publish",
"pub:gh": "node build-config/pack.js && electron-builder --win -p always",
"pack": "node build-config/pack.js && electron-builder -w",
"publish:gh": "node build-config/pack.js && npm run publish:win",
"publish:win": "npm run publish:win:setup && npm run publish:win:7z",
"publish:win:setup": "cross-env TARGET=Setup ARCH=x64_x86 electron-builder -w=nsis --x64 --ia32 -p always",
"publish:win:portable": "npm run publish:win:portable:x64_x86 && npm run publish:win:portable:x64 && npm run publish:win:portable:x86",
"publish:win:portable:x64_x86": "cross-env TARGET=portable ARCH=x64_x86 electron-builder -w=portable --x64 --ia32 -p onTagOrDraft",
"publish:win:portable:x64": "cross-env TARGET=portable ARCH=x64 electron-builder -w=portable --x64 -p onTagOrDraft",
"publish:win:portable:x86": "cross-env TARGET=portable ARCH=x86 electron-builder -w=portable --ia32 -p onTagOrDraft",
"publish:win:7z": "npm run publish:win:7z:x64 && npm run publish:win:7z:x86",
"publish:win:7z:x64": "cross-env TARGET=green ARCH=win_x64 electron-builder -w=7z --x64 -p onTagOrDraft",
"publish:win:7z:x86": "cross-env TARGET=green ARCH=win_x86 electron-builder -w=7z --ia32 -p onTagOrDraft",
"publish:gh:mac": "node build-config/pack.js && npm run publish:mac",
"publish:mac": "npm run publish:mac:dmg",
"publish:mac:dmg": "electron-builder -m=dmg -p onTagOrDraft",
"publish:gh:linux": "node build-config/pack.js && npm run publish:linux",
"publish:linux": "npm run publish:linux:appImage && npm run publish:linux:deb",
"publish:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p onTagOrDraft",
"publish:linux:deb": "npm run publish:linux:deb:x64 && npm run publish:linux:deb:x86",
"publish:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p onTagOrDraft",
"publish:linux:deb:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32 -p onTagOrDraft",
"pack:linux": "node build-config/pack.js && electron-builder -l",
"pack:dir": "node build-config/pack.js && electron-builder --dir",
"dev": "node build-config/runner-dev.js",
@@ -34,49 +62,50 @@
"files": [
"dist/electron/**/*"
],
"extraResources": [
"./licenses"
],
"win": {
"icon": "src/static/icons/lunch.ico",
"icon": "./resources/icons/256x256.ico",
"legalTrademarks": "lyswhut",
"target": [
{
"arch": [
"ia32",
"x64"
],
"target": "nsis"
}
]
"artifactName": "${productName} v${version} ${env.ARCH} ${env.TARGET}.${ext}"
},
"mac": {
"icon": "./resources/icons/512x512.png",
"category": "public.app-category.music"
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": [
"x64"
]
},
{
"arch": [
"ia32",
"x64"
],
"target": "deb"
},
{
"arch": [
"x64"
],
"target": "snap"
}
],
"maintainer": "lyswhut <lyswuhut@qq.com>"
"maintainer": "lyswhut <lyswuhut@qq.com>",
"artifactName": "${productName} v${version} ${env.ARCH}.${ext}"
},
"nsis": {
"oneClick": false,
"language": "2052",
"allowToChangeInstallationDirectory": true,
"differentialPackage": true,
"license": "./license.rtf"
"license": "./licenses/license.rtf",
"shortcutName": "lx-music"
},
"dmg": {
"contents": [
{
"x": 110,
"y": 150,
"name": "lx-music"
},
{
"x": 240,
"y": 150,
"type": "link",
"path": "/Applications",
"name": "lx-music"
}
],
"title": "洛雪音乐助手 v${version}"
},
"appImage": {
"license": "./licenses/license_zh.txt",
"category": "Audio"
},
"publish": [
{
@@ -90,8 +119,15 @@
"type": "git",
"url": "git+https://github.com/lyswhut/lx-music-desktop.git"
},
"keywords": [],
"author": "lyswhut",
"keywords": [
"music-player",
"electron-app",
"vuejs2"
],
"author": {
"name": "lyswhut",
"email": "lyswuhut@qq.com"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/lyswhut/lx-music-desktop/issues"
@@ -159,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,4 +1,7 @@
### 优化
- 减少接口不稳定带来的影响,适当增加请求等待时间
### 修复
- 修复下载过程中出错重试5次都失败后不会自动开始下一个任务的Bug
- 修复播放到一半URL过期时不会刷新URL直接播放下一首的问题
- 修复播放过程中URL过期不会刷新URL的问题

View File

@@ -1,7 +1,31 @@
{
"version": "0.2.2",
"desc": "<h3>修复</h3>\n<ul>\n<li>修复下载过程中出错重试5次都失败后不会自动开始下一个任务的Bug</li>\n<li>修复播放到一半URL过期不会刷新URL直接播放下一首的问题</li>\n</ul>\n",
"version": "0.3.4",
"desc": "<h3>优化</h3>\n<ul>\n<li>减少接口不稳定带来的影响,适当增加请求等待时间</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复播放过程中URL过期不会刷新URL的问题</li>\n</ul>\n",
"history": [
{
"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"
},
{
"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"
},
{
"version": "0.3.0",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增<strong>MAC</strong>及<strong>Linux</strong>版本(需要的可自行下载)</li>\n<li>新增音量调整</li>\n<li>新增任务栏播放进度条控制选项(现在可在设置界面关闭在任务栏显示的播放进度)</li>\n<li>新增更新出错时的弹窗提示</li>\n<li>从该版本起,非安装版也会有更新弹窗提醒了,但仍然需要手动下载新版本更新,版本信息可到设置页面查看</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>强制把临时接口设置回 <code>messoer</code> 接口</li>\n</ul>\n"
},
{
"version": "0.2.3",
"desc": "<h3>新增</h3>\n<ul>\n<li>新增任务栏程序标题改变功能(播放歌曲时任务栏标题将显示当前播放的歌曲)</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>使用临时接口时试听列表中的下载按钮仍然能点击的Bug</li>\n<li>修复某些情况下歌曲链接未能缓存的问题</li>\n</ul>\n<h3>移除</h3>\n<ul>\n<li>移除临时接口(因服务器被攻击,本接口已关闭)</li>\n<li>移除列表栏设置的隐藏专辑栏选项感觉这个设置并没有什么luan用并且还会打破布局</li>\n</ul>\n"
},
{
"version": "0.2.2",
"desc": "<h3>修复</h3>\n<ul>\n<li>修复下载过程中出错重试5次都失败后不会自动开始下一个任务的Bug</li>\n<li>修复播放到一半URL过期时不会刷新URL直接播放下一首的问题</li>\n</ul>\n"
},
{
"version": "0.2.1",
"desc": "<h3>优化</h3>\n<ul>\n<li>新增歌曲URL存储当URL无效时才重新获取以减少接口不稳定的影响</li>\n</ul>\n<h3>修复</h3>\n<ul>\n<li>修复歌曲加载无法加载时自动切换混乱的Bug</li>\n<li>修复移除列表最后一首歌曲时播放器不停止播放的问题</li>\n</ul>\n"

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

BIN
resources/icons/512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

4
src/common/utils.js Normal file
View File

@@ -0,0 +1,4 @@
export const isLinux = process.platform == 'linux'
export const isWin = process.platform == 'win32'
export const isMac = process.platform == 'darwin'

View File

@@ -3,7 +3,7 @@ html(lang="cn")
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1.0")
meta(http-equiv="X-UA-Compatible" content="ie=edge")
title= require('../package.json').name
title 洛雪音乐助手
body
#root

View File

@@ -1,10 +1,11 @@
const { app, BrowserWindow } = require('electron')
const { app, BrowserWindow, Menu } = require('electron')
const path = require('path')
require('./events')
const progressBar = require('./events/progressBar')
const trafficLight = require('./events/trafficLight')
const autoUpdate = require('./utils/autoUpdate')
const { isLinux, isMac } = require('../common/utils')
const isDev = process.env.NODE_ENV !== 'production'
@@ -33,8 +34,8 @@ function createWindow() {
useContentSize: true,
width: 920,
frame: false,
transparent: true,
icon: path.join(global.__static, 'icons/lunch.ico'),
transparent: !isLinux,
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
resizable: false,
maximizable: false,
fullscreenable: false,
@@ -58,6 +59,24 @@ function createWindow() {
if (!isDev) autoUpdate(mainWindow)
}
if (isMac) {
const template = [
{
label: app.getName(),
submenu: [{ label: '关于洛雪音乐', role: 'about' }, { type: 'separator' }, { label: '隐藏', role: 'hide' }, { label: '显示其他', role: 'hideothers' }, { label: '显示全部', role: 'unhide' }, { type: 'separator' }, { label: '退出', click: () => app.quit() }],
},
{
label: '窗口',
role: 'window',
submenu: [{ label: '最小化', role: 'minimize' }, { label: '关闭', role: 'close' }],
},
]
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
} else {
Menu.setApplicationMenu(null)
}
app.once('ready', createWindow)
app.on('window-all-closed', () => {

View File

@@ -3,6 +3,7 @@ const { autoUpdater } = require('electron-updater')
const { mainOn } = require('../../common/icp')
autoUpdater.logger = log
// autoUpdater.autoDownload = false
autoUpdater.logger.transports.file.level = 'info'
log.info('App starting...')
@@ -60,21 +61,30 @@ module.exports = win => {
autoUpdater.on('checking-for-update', () => {
sendStatusToWindow('Checking for update...')
})
autoUpdater.on('update-available', (ev, info) => {
autoUpdater.on('update-available', info => {
sendStatusToWindow('Update available.')
win.webContents.send('update-available', info)
})
autoUpdater.on('update-not-available', (ev, info) => {
autoUpdater.on('update-not-available', info => {
sendStatusToWindow('Update not available.')
setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还启动完成
win.webContents.send('update-not-available')
}, 5000)
})
autoUpdater.on('error', (ev, err) => {
autoUpdater.on('error', () => {
sendStatusToWindow('Error in auto-updater.')
setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还启动完成
win.webContents.send('update-error')
}, 6000)
})
autoUpdater.on('download-progress', (ev, progressObj) => {
autoUpdater.on('download-progress', progressObj => {
sendStatusToWindow('Download progress...')
})
autoUpdater.on('update-downloaded', (ev, info) => {
autoUpdater.on('update-downloaded', info => {
sendStatusToWindow('Update downloaded.')
win.webContents.send('update-downloaded')
setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还启动完成
win.webContents.send('update-downloaded')
}, 2000)
})
mainOn('quit-update', () => {
setTimeout(() => {

View File

@@ -1,5 +1,5 @@
<template lang="pug">
#container(v-if="isProd" :class="theme" @mouseenter="enableIgnoreMouseEvents" @mouseleave="dieableIgnoreMouseEvents")
#container(v-if="isProd && !isLinux" :class="theme" @mouseenter="enableIgnoreMouseEvents" @mouseleave="dieableIgnoreMouseEvents")
core-aside#left
#right
core-toolbar#toolbar
@@ -20,14 +20,20 @@
<script>
import { mapMutations, mapGetters, mapActions } from 'vuex'
import { rendererOn } from '../common/icp'
import { isLinux } from '../common/utils'
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
const win = require('electron').remote.getCurrentWindow()
const body = document.body
let win
let body
if (!isLinux) {
win = require('electron').remote.getCurrentWindow()
body = document.body
}
export default {
data() {
return {
isProd: process.env.NODE_ENV === 'production',
isLinux,
globalObj: {
apiSource: 'messoer',
},
@@ -42,6 +48,7 @@ export default {
}),
},
mounted() {
document.body.classList.add(this.isLinux ? 'noTransparent' : 'transparent')
this.init()
},
watch: {
@@ -74,21 +81,34 @@ export default {
},
methods: {
...mapActions(['getVersionInfo']),
...mapMutations(['setNewVersion', 'setVersionVisible']),
...mapMutations(['setNewVersion', 'setVersionModalVisible']),
...mapMutations('list', ['initDefaultList']),
...mapMutations('download', ['updateDownloadList']),
...mapMutations(['setSetting']),
init() {
if (this.isProd) {
if (this.isProd && !isLinux) {
body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
rendererOn('update-available', (e, info) => {
// this.showUpdateModal(true)
this.setNewVersion({
version: info.version,
})
})
rendererOn('update-error', () => {
this.setVersionModalVisible({ isError: true })
this.$nextTick(() => {
this.showUpdateModal()
})
})
rendererOn('update-downloaded', () => {
this.getVersionInfo().then(body => {
this.setNewVersion(body)
this.$nextTick(() => {
this.setVersionVisible(true)
})
this.showUpdateModal()
})
rendererOn('update-not-available', () => {
if (this.setting.ignoreVersion) this.setSetting(Object.assign({}, this.setting, { ignoreVersion: null }))
this.setNewVersion({
version: this.version.version,
})
})
@@ -97,10 +117,12 @@ export default {
window.globalObj = this.globalObj
},
enableIgnoreMouseEvents() {
if (isLinux) return
win.setIgnoreMouseEvents(false)
// console.log('content enable')
},
dieableIgnoreMouseEvents() {
if (isLinux) return
// console.log('content disable')
win.setIgnoreMouseEvents(true, { forward: true })
},
@@ -131,6 +153,20 @@ export default {
this.updateDownloadList(downloadList)
}
},
showUpdateModal() {
(this.version.newVersion && this.version.newVersion.history ? Promise.resolve(this.version.newVersion) : this.getVersionInfo().then(body => {
this.setNewVersion(body)
if (body.version !== this.setting.ignoreVersion) this.setSetting(Object.assign({}, this.setting, { ignoreVersion: null }))
return body
})).then(body => {
if (body.version === this.version.version) return
if (this.version.isError && body.version === this.setting.ignoreVersion) return
this.$nextTick(() => {
this.setVersionModalVisible({ isShow: true })
})
})
},
},
beforeDestroy() {
if (this.isProd) {
@@ -146,22 +182,30 @@ export default {
@import './assets/styles/layout.less';
body {
// background-color: #fff;
padding: @shadow-app;
user-select: none;
height: 100vh;
box-sizing: border-box;
}
.transparent {
padding: @shadow-app;
#container {
box-shadow: 0 0 @shadow-app rgba(0, 0, 0, 0.5);
border-radius: 4px;
background-color: transparent;
}
}
.noTransparent {
background-color: #fff;
}
#container {
position: relative;
display: flex;
height: 100%;
box-shadow: 0 0 @shadow-app rgba(0, 0, 0, 0.5);
// background-color: #fff;
border-radius: 4px;
overflow: hidden;
}
#left {
flex: none;
width: @width-app-left;

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

@@ -48,5 +48,12 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
path(fill='currentColor' d='M112.525,95.091L26.75,45.901C11.982,37.427,0,44.369,0,61.404v98.062c0,17.025,11.982,23.969,26.75,15.492l85.781-49.177C127.294,117.305,127.294,103.565,112.525,95.091z')
// 205.857 205.857
//- path(fill='currentColor' d='M174.522,0h-26.848c-9.885,0-17.897,8.013-17.897,17.899v62.533L37.513,2.522c-3.483-2.406-7.807-2.005-11.072-2.005c-13.061,0-13.004,11.7-13.004,14.666v175.983c0,2.507-0.057,14.666,13.004,14.666c3.265,0,7.589,0.401,11.072-2.005l92.265-77.91v62.016c0,9.885,8.012,17.898,17.897,17.898h26.848c9.885,0,17.898-8.013,17.898-17.898V17.899C192.421,8.013,184.408,0,174.522,0z')
g#icon-sound
// 0 0 291.063 291.064
path(fill='currentColor' d='M26.512,204.255h18.292l106.48,67.761c12.354,7.855,22.369,2.361,22.369-12.282v-69.397c16.933-8.854,28.501-26.559,28.501-46.983c0-20.425-11.568-38.129-28.501-46.986V31.645c0-14.639-10.18-20.401-22.731-12.873L44.804,82.443H26.512C11.866,82.443,0,94.311,0,108.955v68.789C0,192.387,11.866,204.255,26.512,204.255z')
path(fill='currentColor' d='M219.791,152.899c-0.818,11.185-4.039,21.758-9.569,31.426c-3.635,6.354-1.43,14.452,4.919,18.087c2.082,1.187,4.34,1.751,6.576,1.751c4.599,0,9.062-2.393,11.517-6.675c7.508-13.138,11.889-27.491,12.986-42.663c1.714-23.397-4.836-46.781-18.455-65.845c-4.256-5.96-12.536-7.332-18.491-3.081c-5.959,4.259-7.337,12.531-3.08,18.491C216.218,118.425,221.055,135.653,219.791,152.899z')
path(fill='currentColor' d='M290.7,158c3.34-45.736-16.508-89.592-53.097-117.318c-5.841-4.433-14.146-3.27-18.568,2.556c-4.428,5.838-3.283,14.151,2.558,18.568c29.401,22.281,45.355,57.521,42.668,94.252c-2.02,27.636-14.375,53.159-34.787,71.867c-5.396,4.95-5.758,13.339-0.808,18.729c2.609,2.854,6.188,4.298,9.771,4.298c3.194,0,6.41-1.154,8.953-3.484C272.805,224.175,288.184,192.408,290.7,158z')
</template>

View File

@@ -8,6 +8,13 @@ div(:class="$style.player")
div(:class="$style.column1")
div(:class="$style.container")
div(:class="$style.title") {{title}}
div(:class="$style.volumeContent")
div(:class="$style.volume" @click.stop='handleChangeVolume' :title="`当前音量:${volumeStr}%`")
div(:class="$style.volumeBar" :style="{ width: volumeStr + '%' }")
//- div(:class="$style.playBtn" @click='handleNext' title="音量")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 291.063 291.064' space='preserve')
use(xlink:href='#icon-sound')
div(:class="$style.playBtn" @click='handleNext' title="下一首")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
@@ -41,7 +48,7 @@ div(:class="$style.player")
<script>
import Lyric from 'lrc-file-parser'
import { rendererSend } from '../../../common/icp'
import { formatPlayTime2, getRandom, checkPath } from '../../utils'
import { formatPlayTime2, getRandom, checkPath, setTitle } from '../../utils'
import { mapGetters, mapActions, mapMutations } from 'vuex'
import { requestMsg } from '../../utils/message'
@@ -50,6 +57,7 @@ export default {
return {
show: true,
audio: null,
volume: 0,
nowPlayTime: 0,
maxPlayTime: 0,
isPlay: false,
@@ -71,7 +79,7 @@ export default {
},
delayNextTimeout: null,
audioErrorTime: 0,
// retryNum: 0,
retryNum: 0,
}
},
computed: {
@@ -98,10 +106,15 @@ export default {
isAPITemp() {
return this.setting.apiSource == 'temp'
},
volumeStr() {
return parseInt(this.volume * 100)
},
},
mounted() {
this.setProgessWidth()
this.init()
this.$nextTick(() => {
this.setProgessWidth()
})
},
watch: {
changePlay(n) {
@@ -139,6 +152,9 @@ export default {
if (n.toFixed(2) === o.toFixed(2)) return
this.sendProgressEvent(n, 'normal')
},
volume(n) {
this.handleSaveVolume(n)
},
},
methods: {
...mapActions('player', ['getUrl', 'getPic', 'getLrc']),
@@ -147,11 +163,14 @@ export default {
'fixPlayIndex',
'resetChangePlay',
]),
...mapMutations(['setVolume']),
...mapMutations('list', ['updateMusicInfo']),
init() {
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', () => {
@@ -175,15 +194,16 @@ export default {
// console.log('code', this.audio.error.code)
if (!this.musicInfo.songmid) return
console.log('出错')
this.stopPlay()
this.sendProgressEvent(this.progress, 'error')
if (this.audio.error.code !== 1 && this.retryNum < 3) { // 若音频URL无效则尝试刷新3次URL
// console.log(this.retryNum)
this.audioErrorTime = this.audio.currentTime // 记录出错的播放时间
this.retryNum++
this.setUrl(this.list[this.playIndex], true)
this.status = 'URL过期正在刷新URL...'
return
}
this.stopPlay()
this.sendProgressEvent(this.progress, 'error')
// let urls = this.player_info.targetSong.urls
// if (urls && urls.some((url, index) => {
@@ -212,9 +232,9 @@ export default {
if (!this.targetSong.interval && this.listId != 'download') this.updateMusicInfo({ index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) } })
this.status = '音乐加载中...'
})
// this.audio.addEventListener('loadstart', () => {
// this.status = '开始加载音乐信息...'
// })
this.audio.addEventListener('loadstart', () => {
this.status = '音乐加载中...'
})
this.audio.addEventListener('canplay', () => {
console.log('加载完成开始播放')
// if (this.musicInfo.lrc) this.lyric.lrc.play(this.audio.currentTime * 1000)
@@ -334,16 +354,17 @@ export default {
startPlay() {
this.isPlay = true
if (this.musicInfo.lrc) this.lyric.lrc.play(this.audio.currentTime * 1000)
this.setAppName()
this.setAppTitle()
this.sendProgressEvent(this.progress, 'normal')
},
stopPlay() {
this.isPlay = false
this.lyric.lrc.pause()
this.sendProgressEvent(this.progress, 'paused')
this.clearAppName()
this.clearAppTitle()
},
setProgess(e) {
if (!this.audio.src) return
this.audio.currentTime =
(e.offsetX / this.pregessWidth) * this.maxPlayTime
if (!this.isPlay) this.audio.play()
@@ -364,7 +385,7 @@ export default {
getPlayType(highQuality, songInfo) {
switch (songInfo.source) {
case 'wy':
case 'kg':
// case 'kg':
return '128k'
}
let type = songInfo._types['192k'] ? '192k' : '128k'
@@ -427,18 +448,27 @@ export default {
},
sendProgressEvent(status, mode) {
// console.log(status)
rendererSend('progress', {
this.setting.player.isShowTaskProgess && rendererSend('progress', {
status: status < 0.01 ? 0.01 : status,
mode: mode || 'normal',
})
},
setAppName() {
// rendererSend('appName', {
// name: `${this.musicInfo.name} - ${this.musicInfo.singer}`,
// })
setAppTitle() {
setTitle(`${this.musicInfo.name} - ${this.musicInfo.singer}`)
},
clearAppName() {
// rendererSend('appName')
clearAppTitle() {
setTitle()
},
handleChangeVolume(e) {
let val = e.offsetX / 70
if (val < 0) val = 0
if (val > 1) val = 1
if (val > 0.97) val = 1
this.volume = val
if (this.audio) this.audio.volume = this.volume
},
handleSaveVolume(volume) {
this.setVolume(volume)
},
},
}
@@ -511,6 +541,40 @@ export default {
.mixin-ellipsis-1;
}
.volume-content {
width: 100px;
display: flex;
align-items: center;
padding: 0 15px;
}
.volume {
cursor: pointer;
width: 100%;
height: 0.25em;
border-radius: 10px;
// overflow: hidden;
transition: @transition-theme;
transition-property: background-color;
background-color: @color-player-progress;
// background-color: #f5f5f5;
position: relative;
border-radius: @radius-progress-border;
}
.volume-bar {
position: absolute;
left: 0;
top: 0;
width: 0;
height: 100%;
border-radius: @radius-progress-border;
transition-duration: 0.2s;
background-color: @color-theme;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
opacity: .5;
}
.play-btn {
+ .play-btn {
margin-left: 10px;
@@ -631,6 +695,14 @@ each(@themes, {
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3));
}
}
.volume {
background-color: ~'@{color-@{value}-player-progress}';
}
.volume-bar {
background-color: ~'@{color-@{value}-theme}';
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
}
.progress {

View File

@@ -1,6 +1,6 @@
<template lang="pug">
div(:class="$style.checkbox")
input(:type="need ? 'radio' : 'checkbox'" :id="id" :value="value" :name="name" @change="change" v-model="bool")
input(:type="need ? 'radio' : 'checkbox'" :id="id" :disabled="disabled" :value="value" :name="name" @change="change" v-model="bool")
label(:for="id" :class="$style.content")
div(v-if="indeterminate")
svg(v-show="indeterminate" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
@@ -34,6 +34,10 @@ export default {
default: false,
},
label: {},
disabled: {
type: Boolean,
default: false,
},
indeterminate: {
type: Boolean,
default: false,
@@ -111,6 +115,11 @@ export default {
> input {
display: none;
&[disabled] {
+ .content {
opacity: .5;
}
}
&:checked {
+ .content {
> div {

View File

@@ -5,7 +5,7 @@ material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
| {{ info.name }}
br
| {{ info.singer }}
material-btn(:class="$style.btn" :title="!checkSource(type.type) && '目前酷狗、网易云音源仅支持下载128k音质'" :disabled="!checkSource(type.type)" :key="type.type" @click="handleClick(type.type)" v-for="type in info.types") {{getTypeName(type.type)}} {{ type.type.toUpperCase() }}{{ type.size && ` - ${type.size.toUpperCase()}` }}
material-btn(:class="$style.btn" :title="!checkSource(type.type) && '目前网易云音源仅支持下载128k音质'" :disabled="!checkSource(type.type)" :key="type.type" @click="handleClick(type.type)" v-for="type in info.types") {{getTypeName(type.type)}} {{ type.type.toUpperCase() }}{{ type.size && ` - ${type.size.toUpperCase()}` }}
</template>
@@ -52,7 +52,7 @@ export default {
checkSource(type) {
switch (this.musicInfo.source) {
case 'wy':
case 'kg':
// case 'kg':
return type == '128k'
default:

View File

@@ -0,0 +1,80 @@
<template lang="pug">
input(type="range" :class="[$style.range, min ? $style.min : '']" :disabled="disabled" v-model="val" input="handleInput" @change="handleChange")
</template>
<script>
export default {
props: {
min: {
type: Boolean,
},
disabled: {
type: Boolean,
default: false,
},
value: {
type: Number,
default: 0,
},
},
data() {
return {
val: 0,
}
},
mounted() {
this.val = this.value
},
methods: {
handleChange(e) {
this.$emit('input', this.val)
this.$emit('change', e)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.range {
display: inline-block;
border-radius: .25em;
cursor: pointer;
color: @color-btn;
outline: none;
background: transparent;
// background-color: @color-btn-background;
&[disabled] {
opacity: .4;
}
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
.min {
}
// each(@themes, {
// :global(#container.@{value}) {
// .btn {
// color: ~'@{color-@{value}-btn}';
// background-color: ~'@{color-@{value}-btn-background}';
// &:hover {
// background-color: ~'@{color-@{value}-theme_2-hover}';
// }
// &:active {
// background-color: ~'@{color-@{value}-theme_2-active}';
// }
// }
// }
// })
</style>

View File

@@ -1,7 +1,7 @@
<template lang="pug">
material-modal(:show="version.showModal" @close="handleClose")
main(:class="$style.main" v-if="version.newVersion")
h2 🚀程序更新🚀
h2 {{ version.isError ? '🌟发现新版本🌟' : '🚀程序更新🚀'}}
div.scroll(:class="$style.info")
div(:class="$style.current")
@@ -15,7 +15,20 @@ material-modal(:show="version.showModal" @close="handleClose")
h4 v{{ver.version}}
p(v-html="ver.desc")
div(:class="$style.footer")
div(:class="$style.footer" v-if="version.isError")
div(:class="$style.desc")
p 发现有新版本啦但是自动更新功能出问题了
p
| 如果你所用的软件是
strong 安装版
| 可以到QQ群830125506 反馈哦
p
| 你现在可以选择继续使用当前版本或
strong 去发布页下载新版本
div(:class="$style.btns")
material-btn(:class="$style.btn" @click.onec="handleIgnoreClick") 忽略该版本
material-btn(:class="$style.btn" @click.onec="handleOpenPageClick") 去下载新版本
div(:class="$style.footer" v-else)
div(:class="$style.desc")
p 新版本已下载完毕
p
@@ -24,18 +37,18 @@ material-modal(:show="version.showModal" @close="handleClose")
| 或稍后
strong 关闭程序时
| 自动更新~
material-btn(:class="$style.btn" @click.onec="handleClick") 立即重启更新
material-btn(:class="$style.btn" @click.onec="handleRestartClick") 立即重启更新
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { rendererSend } from '../../../common/icp'
import { checkVersion } from '../../utils'
import { checkVersion, openUrl } from '../../utils'
export default {
computed: {
...mapGetters(['version']),
...mapGetters(['version', 'setting']),
history() {
if (!this.version.newVersion) return []
let arr = []
@@ -48,11 +61,21 @@ export default {
},
},
methods: {
...mapMutations(['setVersionVisible']),
...mapMutations(['setVersionModalVisible', 'setSetting']),
handleClose() {
this.setVersionVisible(false)
this.setVersionModalVisible({
isShow: false,
})
},
handleClick(event) {
handleIgnoreClick(event) {
this.handleClose()
// event.target.disabled = true
this.setSetting(Object.assign({}, this.setting, { ignoreVersion: this.version.newVersion.version }))
},
handleOpenPageClick() {
openUrl('https://github.com/lyswhut/lx-music-desktop')
},
handleRestartClick(event) {
this.handleClose()
event.target.disabled = true
rendererSend('quit-update')
@@ -68,7 +91,7 @@ export default {
.main {
position: relative;
padding: 15px;
max-width: 500px;
max-width: 450px;
min-width: 300px;
display: flex;
flex-flow: column nowrap;
@@ -156,6 +179,11 @@ export default {
display: block;
width: 100%;
}
.btns {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 0 10px;
}
each(@themes, {
:global(#container.@{value}) {

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

@@ -5,7 +5,7 @@ import { author, name } from '../../../package.json'
export default {
getVersionInfo() {
return new Promise((resolve, reject) => {
httpGet(`https://raw.githubusercontent.com/${author}/${name}/master/publish/version.json`, (err, resp, body) => {
httpGet(`https://raw.githubusercontent.com/${author.name}/${name}/master/publish/version.json`, (err, resp, body) => {
if (err) return reject(err)
resolve(body)
})

View File

@@ -56,7 +56,7 @@ const actions = {
// mitations
const mutations = {
setUrl(state, datas) {
datas.musicInfo.typeUrl[datas.type] = datas.url
datas.musicInfo.typeUrl = Object.assign({}, datas.musicInfo.typeUrl, { [datas.type]: datas.url })
},
getPic(state, datas) {
datas.musicInfo.img = datas.url

View File

@@ -19,7 +19,11 @@ export default {
// val.desc = val.desc.replace(/\n/g, '<br>')
state.version.newVersion = val
},
setVersionVisible(state, val) {
state.version.showModal = val
setVersionModalVisible(state, { isShow, isError }) {
if (isShow !== undefined) state.version.showModal = isShow
if (isError !== undefined) state.version.isError = isError
},
setVolume(state, val) {
state.setting.player.volume = val
},
}

View File

@@ -49,6 +49,7 @@ export default {
version,
newVersion: null,
showModal: false,
isError: false,
},
userInfo: null,
setting,

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
@@ -161,12 +162,14 @@ export const isChildren = (parent, children) => {
* @param {*} setting
*/
export const updateSetting = setting => {
const defaultVersion = '1.0.3'
const defaultVersion = '1.0.4'
const defaultSetting = {
version: defaultVersion,
player: {
togglePlayMethod: 'listLoop',
highQuality: false,
isShowTaskProgess: true,
volume: 1,
},
list: {
isShowAlbumName: true,
@@ -182,8 +185,9 @@ export const updateSetting = setting => {
},
themeId: 0,
sourceId: 'kw',
apiSource: 'messoer',
apiSource: 'temp',
randomAnimate: true,
ignoreVersion: null,
}
const overwriteSetting = {
version: defaultVersion,
@@ -198,6 +202,7 @@ export const updateSetting = setting => {
objectDeepMerge(defaultSetting, overwriteSetting)
setting = defaultSetting
}
if (setting.apiSource == 'messoer') setting.apiSource = 'temp' // 强制设置回 temp 接口源
return setting
}
@@ -209,3 +214,17 @@ export const openUrl = url => {
shell.openExternal(url)
}
/**
* 设置标题
*/
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

@@ -1,6 +1,7 @@
export const requestMsg = {
fail: '请求异常😮,可以多试几次,若还是不行就换一首吧。。。',
unachievable: '哦No😱...接口无法访问了!已帮你切换到临时接口,重试下看能不能播放吧~',
notConnectNetwork: '无法连接网络',
unachievable: '哦No😱...接口无法访问了!',
// unachievable: '哦No😱...接口无法访问了!已帮你切换到临时接口,重试下看能不能播放吧~',
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 '../options'
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 '../options'
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

@@ -1,24 +1,18 @@
import { httpFatch } from '../../request'
import { headers, timeout } from '../options'
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',
headers,
timeout,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(body.msg))
})
return requestObj
},
getPic(songInfo) {
const requestObj = httpFatch(`https://www.stsky.cn/api/temp/getPic.php?size=320&songmid=${songInfo.songmid}`, {
method: 'get',
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve(body.data) : Promise.reject(new Error(body.msg))
})
return requestObj
},
}
export default api_temp

View File

@@ -4,6 +4,7 @@ import musicSearch from './musicSearch'
import { formatSinger } from './util'
import leaderboard from './leaderboard'
import lyric from './lyric'
import pic from './pic'
import api_source from '../api-source'
const kw = {
@@ -91,7 +92,7 @@ const kw = {
},
getPic(songInfo) {
return api_source('kw').getPic(songInfo)
return pic.getPic(songInfo)
},
}

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,9 @@
import { httpFatch } from '../../request'
export default {
getPic({ songmid }) {
const requestObj = httpFatch(`http://artistpicserver.kuwo.cn/pic.web?corp=kuwo&type=rid_pic&pictype=500&size=500&rid=${songmid}`)
requestObj.promise = requestObj.promise.then(({ body }) => body)
return requestObj
},
}

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 = 15000

View File

@@ -0,0 +1,24 @@
import { httpFatch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../options'
const api_messoer = {
getMusicUrl(songInfo, type) {
const requestObj = httpFatch(`https://v1.itooi.cn/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

@@ -7,7 +7,7 @@
const types = ['flac', 'ape', '320k', '192k', '128k']
export const getMusicType = (info, type) => {
switch (window.globalObj.apiSource) {
case 'kg':
// case 'kg':
case 'wy':
return '128k'
}

View File

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

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/options'
// 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',
}
/**
@@ -84,7 +67,7 @@ export const httpFatch = (url, options = { method: 'get' }) => {
return promise
}
if (err.message === 'socket hang up') {
window.globalObj.apiSource = 'temp'
// window.globalObj.apiSource = 'temp'
return Promise.reject(new Error(requestMsg.unachievable))
}
if (err.code === 'ENOTFOUND') return Promise.reject(new Error(requestMsg.notConnectNetwork))
@@ -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

@@ -195,7 +195,7 @@ export default {
},
handleAddDownloadMultiple(type) {
switch (this.source) {
case 'kg':
// case 'kg':
case 'wy':
type = '128k'
}

View File

@@ -11,7 +11,7 @@
:indeterminate="isIndeterminate" :title="isSelectAll && !isIndeterminate ? '全不选' : '全选'")
th.nobreak(style="width: 25%;") 歌曲名
th.nobreak(style="width: 20%;") 歌手
th.nobreak(style="width: 20%;" v-if="setting.list.isShowAlbumName") 专辑
th.nobreak(style="width: 20%;") 专辑
th.nobreak(style="width: 20%;") 操作
th.nobreak(style="width: 10%;") 时长
div.scroll(:class="$style.tbody")
@@ -28,7 +28,7 @@
//- span.badge.badge-info(v-if="item._types.ape") APE
//- span.badge.badge-success(v-if="item._types.flac") FLAC
td.break(style="width: 20%;") {{item.singer}}
td.break(style="width: 20%;" v-if="setting.list.isShowAlbumName") {{item.albumName}}
td.break(style="width: 20%;") {{item.albumName}}
td(style="width: 20%; padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" @btn-click="handleListBtnClick")
//- button.btn-info(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k'] || item._types.flac" @click.stop='openDownloadModal(index)') 下载
@@ -137,7 +137,6 @@ export default {
this.clickIndex = -1
},
testPlay(index) {
if (this.isAPITemp && this.list[index].source != 'kw') return
this.setList({ list: this.list, listId: 'test', index })
},
handleRemove(index) {
@@ -146,16 +145,17 @@ export default {
handleListBtnClick(info) {
switch (info.action) {
case 'download':
this.musicInfo = this.list[info.index]
const minfo = this.list[info.index]
if (this.isAPITemp && minfo.source != 'kw') return
this.musicInfo = minfo
this.$nextTick(() => {
this.isShowDownload = true
})
break
case 'play':
if (this.isAPITemp && this.list[info.index].source != 'kw') return
this.testPlay(info.index)
break
case 'add':
break
case 'remove':
this.handleRemove(info.index)
break

View File

@@ -19,7 +19,7 @@ div.scroll(:class="$style.setting")
h3 音乐来源
div
material-checkbox(v-for="item in apiSources" :id="`setting_api_source_${item.id}`" @change="handleAPISourceChange(item.id)" :class="$style.gapTop"
need v-model="current_setting.apiSource" :value="item.id" :label="item.label" :key="item.id")
need v-model="current_setting.apiSource" :disabled="item.disabled" :value="item.id" :label="item.label" :key="item.id")
dt 播放设置
dd(title="都不选时播放完当前歌曲就停止播放")
@@ -31,6 +31,10 @@ div.scroll(:class="$style.setting")
h3 优先播放高品质音乐
div
material-checkbox(id="setting_player_highQuality" v-model="current_setting.player.highQuality" label="是否启用")
dd(title='在任务栏上显示当前歌曲播放进度')
h3 是否启用任务栏播放进度条
div
material-checkbox(id="setting_player_showTaskProgess" v-model="current_setting.player.isShowTaskProgess" label="是否启用")
dt 下载设置
dd(title='下载歌曲保存的路径')
h3 下载路径
@@ -45,8 +49,8 @@ div.scroll(:class="$style.setting")
div
material-checkbox(:id="`setting_download_musicName_${item.value}`" :class="$style.gapLeft" name="setting_download_musicName" :value="item.value" :key="item.value" need
v-model="current_setting.download.fileName" v-for="item in musicNames" :label="item.name")
dt 列表设置
dd(title='播放列表是否显示专辑栏')
//- dt 列表设置
//- dd(title='播放列表是否显示专辑栏')
h3 专辑栏
div
material-checkbox(id="setting_list_showalbum" v-model="current_setting.list.isShowAlbumName" label="是否显示专辑栏")
@@ -63,14 +67,21 @@ div.scroll(:class="$style.setting")
div
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportAllData") 导入
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportAllData") 导出
dt 软件更新
dd
p.small
| 最新版本{{version.newVersion ? version.newVersion.version : '未知'}}
p.small 当前版本{{version.version}}
p.small(v-if="version.newVersion")
span(v-if="isLatestVer") 软件已是最新尽情地体验吧~🥂
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 关于洛雪音乐
dd
p.small
| 本软件完全免费代码已开源开源地址
span.hover(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop')") https://github.com/lyswhut/lx-music-desktop
p
small 当前版本
| {{version.version}}
p.small
| 本软件仅用于学习交流使用禁止将本软件用于
strong 非法用途
@@ -85,11 +96,11 @@ div.scroll(:class="$style.setting")
| 本软件的部分接口使用自 https://github.com/messoer ,非常感谢
strong @messoer
|
p.small 若有问题可 mail tolyswhut@qq.com 或到 github 提交 issue
p.small 若有问题可 mail tolyswhut@qq.com 或到 GitHub 提交 issue
p.small
| 若觉得好用的话GitHub点个
| 若觉得好用的话可以去 GitHub 点个
strong star
| 支持作者~
| 支持作者~~🍻
p
small By
| 落雪无痕
@@ -98,12 +109,18 @@ div.scroll(:class="$style.setting")
<script>
import { mapGetters, mapMutations } from 'vuex'
import { openDirInExplorer, openSelectDir, openSaveDir, updateSetting, openUrl } from '../utils'
import { rendererSend } from '../../common/icp'
import fs from 'fs'
export default {
name: 'Setting',
computed: {
...mapGetters(['setting', 'themes', 'version']),
...mapGetters('list', ['defaultList']),
isLatestVer() {
return this.version.newVersion && this.version.version === this.version.newVersion.version
},
},
data() {
return {
@@ -112,12 +129,13 @@ export default {
player: {
togglePlayMethod: 'random',
highQuality: false,
isShowTaskProgess: true,
},
list: {
isShowAlbumName: true,
},
download: {
savePath: 'C:\\',
savePath: '',
fileName: '歌名 - 歌手',
},
themeId: 0,
@@ -146,11 +164,19 @@ export default {
apiSources: [
{
id: 'messoer',
label: '由 messoer 提供的接口(推荐,软件的所有功能都可用)<br><span style="line-height: 1.5;"><strong>注意:</strong>本接口10秒内请求数超过100次会封10小时的IP</span>',
// label: '由 messoer 提供的接口(推荐,软件的所有功能都可用)',
label: '由 messoer 提供的接口(该接口已关闭)',
disabled: true,
},
// {
// id: 'internal',
// label: '内置接口只能试听或下载128k音质该接口支持软件的所有功能',
// disabled: false,
// },
{
id: 'temp',
label: '临时接口(软件的某些功能不可用,建议在messoer不可用时再切换到本选项',
label: '临时接口(软件的某些功能不可用,该接口访问速度较慢,请耐心等待',
disabled: false,
},
],
musicNames: [
@@ -177,12 +203,21 @@ export default {
},
deep: true,
},
'current_setting.player.isShowTaskProgess'(n) {
if (n) return
this.$nextTick(() => {
rendererSend('progress', {
status: -1,
mode: 'normal',
})
})
},
},
mounted() {
this.init()
},
methods: {
...mapMutations(['setSetting']),
...mapMutations(['setSetting', 'setVersionModalVisible']),
...mapMutations('list', ['setDefaultList']),
init() {
this.current_setting = JSON.parse(JSON.stringify(this.setting))
@@ -336,6 +371,9 @@ export default {
window.globalObj.apiSource = id
})
},
showUpdateModal() {
this.setVersionModalVisible({ isShow: true })
},
},
}
</script>

View File

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