Compare commits

...

562 Commits

Author SHA1 Message Date
lyswhut
5ccf6f077d Merge branch 'dev' 2021-08-11 17:07:02 +08:00
lyswhut
5b113c5c22 发布v1.12.2 2021-08-11 17:06:18 +08:00
lyswhut
ff53900ea3 修复同步服务器启动失败时,关闭同步服务不会清空失败信息的问题 2021-08-11 16:39:33 +08:00
lyswhut
b0b0403d9a 移除多余内容 2021-08-11 15:23:20 +08:00
lyswhut
21c3b3ab57 修复上一曲切换问题 2021-08-11 13:28:51 +08:00
lyswhut
e85709a341 修复稍后播放切歌问题 2021-08-11 11:57:41 +08:00
lyswhut
04be42a4d6 添加规则 2021-08-11 10:22:15 +08:00
lyswhut
8196e8a84d 更新依赖 2021-08-11 10:00:04 +08:00
lyswhut
1f85492690 修复播放下载列表的歌曲时切歌、歌词显示问题 2021-08-11 09:59:47 +08:00
lyswhut
e15178150b 移除日志 2021-08-08 12:16:22 +08:00
lyswhut
d83aee2507 Merge branch 'dev' 2021-08-08 11:54:46 +08:00
lyswhut
354a9904e9 发布v1.12.1 2021-08-08 11:54:21 +08:00
lyswhut
fe3379ffbb 修复随机播放下无法切歌的问题 2021-08-08 11:54:13 +08:00
lyswhut
0164936038 Merge branch 'dev' 2021-08-08 10:29:09 +08:00
lyswhut
9106d6455f 发布v1.12.0 2021-08-08 10:28:43 +08:00
lyswhut
5ddea2b1da 修复图片为空时设置歌曲信息出错的问题 2021-08-07 19:35:13 +08:00
lyswhut
2549c5587e 更新依赖 2021-08-07 13:45:44 +08:00
lyswhut
c9f28d8105 更新依赖 2021-08-05 10:39:59 +08:00
lyswhut
75b2db4749 添加播放器对系统媒体控制与显示的兼容处理 2021-08-05 10:21:20 +08:00
lyswhut
b6f291d721 添加提示语 2021-08-02 14:32:45 +08:00
lyswhut
4f7e227392 移除日志输出 2021-08-01 20:12:38 +08:00
lyswhut
7d95eb34c7 修复同步服务启动失败刷新连接码按钮仍然可以点击的问题 2021-08-01 20:11:38 +08:00
lyswhut
7e3ee38920 修复删除列表中正在播放的歌曲时不会自动切歌的问题 2021-08-01 20:07:40 +08:00
lyswhut
d44c2c26a8 修复beta版MAC包构建失败的问题 2021-08-01 13:46:16 +08:00
lyswhut
d77a41c20f 更新版本 2021-08-01 13:35:58 +08:00
lyswhut
c50c747691 完善代码 2021-08-01 13:28:12 +08:00
lyswhut
bcf3dde60d 完成同步功能 2021-08-01 12:57:13 +08:00
lyswhut
33ec3de1b4 更新依赖 2021-08-01 12:46:17 +08:00
lyswhut
26be5b5c4f 移除多余依赖 2021-07-23 22:01:34 +08:00
lyswhut
56af57c682 修复某些情况下所显示的歌词、封面图片与当前正在播放的歌曲不一致的问题 2021-07-23 16:14:56 +08:00
lyswhut
3a5cb3f7c2 修复导入kg歌单最多只能加载100、500首歌曲的问题 2021-07-21 15:33:41 +08:00
lyswhut
724c6be85b 禁用win arm版本的自动下载更新 2021-07-18 14:56:18 +08:00
lyswhut
1c4a56f95b Merge branch 'dev' 2021-07-18 13:59:58 +08:00
lyswhut
9c21e96538 发布v1.11.0 2021-07-18 13:56:26 +08:00
lyswhut
67074bd682 更新依赖 2021-07-17 16:40:52 +08:00
lyswhut
b535987392 更新依赖 2021-07-14 14:27:59 +08:00
lyswhut
d5c0c2e3d3 更新依赖 2021-07-10 12:26:14 +08:00
lyswhut
7580c79c00 替换过时API 2021-07-07 15:02:46 +08:00
lyswhut
c0a13622b0 更新依赖 2021-07-06 16:51:18 +08:00
lyswhut
58d7326f77 优化mg源打开歌单的链接兼容 2021-07-06 16:51:13 +08:00
lyswhut
45d7a12d0d 更新依赖 2021-07-05 10:08:44 +08:00
lyswhut
40b4f15340 更新说明 2021-07-02 14:58:14 +08:00
lyswhut
9c28ff67c1 更新 2021-07-02 10:28:32 +08:00
lyswhut
e3f146dabf 移除wy源歌单列表按“最新”排序的按钮 2021-06-30 11:55:32 +08:00
lyswhut
2b2a6a7f10 更新依赖 2021-06-30 11:44:51 +08:00
lyswhut
192574dfe8 更新依赖 2021-06-27 14:47:19 +08:00
lyswhut
8e95e06b2d 添加网易歌单无法打开的说明 2021-06-24 14:50:04 +08:00
lyswhut
1963b3ee71 修复mg源搜索失效的问题 2021-06-19 13:26:58 +08:00
lyswhut
507f66b3b6 修复dns查询问题 2021-06-18 15:13:15 +08:00
lyswhut
94bf9eae8b 更新依赖 2021-06-17 15:38:03 +08:00
lyswhut
0fa32f976c 移除日志输出 2021-06-16 16:24:39 +08:00
lyswhut
198238d4b5 优化网络请求,尝试去解决无法连接服务器的问题 2021-06-16 10:23:48 +08:00
lyswhut
66631ac105 修复进度条高度问题 2021-06-14 13:52:46 +08:00
lyswhut
19c5996351 更新依赖 2021-06-11 16:25:36 +08:00
lyswhut
cd3140a9c7 新增“添加歌曲到列表时的位置”设置 2021-06-11 15:45:09 +08:00
lyswhut
56201d6a01 更新依赖 2021-06-10 18:10:52 +08:00
lyswhut
654cb41e3e 添加 win arm64 架构的安装包构建 2021-06-07 09:04:34 +08:00
lyswhut
fa744d9204 添加i18n-ally扩展设置 2021-06-06 20:31:09 +08:00
lyswhut
3e1c56ff82 修复打包失败的问题 2021-06-05 12:18:43 +08:00
lyswhut
5d5164c4e9 更新依赖 2021-06-04 16:08:15 +08:00
lyswhut
0791e760a4 修复打开kw歌单失败时会无限重试的问题 2021-06-03 15:12:53 +08:00
lyswhut
b53fe2dd5e 优化wy源歌单导入匹配 2021-06-03 14:50:37 +08:00
lyswhut
1648b0c2dd 更新依赖 2021-05-31 17:12:53 +08:00
lyswhut
1a5adfabf4 更新依赖 2021-05-27 08:44:37 +08:00
lyswhut
c538e9c4fc Merge branch 'dev' 2021-05-25 16:19:49 +08:00
lyswhut
98422b296d 发布v1.10.2 2021-05-25 16:19:23 +08:00
lyswhut
e35068137d 修复企鹅音乐搜索歌曲没有结果的问题 2021-05-25 16:18:45 +08:00
lyswhut
83dda92603 Merge branch 'dev' 2021-05-25 15:42:13 +08:00
lyswhut
6bbcb519d7 发布v1.10.1 2021-05-25 15:40:57 +08:00
lyswhut
7fa3baacfd 更新依赖 2021-05-25 15:40:07 +08:00
lyswhut
f94c1ef852 不允许播放空列表 2021-05-24 11:03:21 +08:00
lyswhut
cc4cb1a6a7 修复企鹅音乐搜索歌曲没有结果的问题 2021-05-23 16:31:31 +08:00
lyswhut
247a322366 修复播放在空的歌单列表点击播放全部时报错的问题 2021-05-23 16:31:26 +08:00
lyswhut
7ec7023d1b Merge branch 'dev' 2021-05-20 08:28:13 +08:00
lyswhut
b4edd95bab 移除多余选项 2021-05-20 08:22:44 +08:00
lyswhut
6263dbbd0f Merge branch 'dev' 2021-05-20 00:14:56 +08:00
lyswhut
64c45daf16 修改安装包构建顺序,使latest.yml仍指向x86_64版本的包 2021-05-20 00:14:36 +08:00
lyswhut
5ed41cb5ac Merge branch 'dev' 2021-05-19 21:19:05 +08:00
lyswhut
29dea5f829 添加win x64与x86版本安装包的单独构建 2021-05-19 21:17:27 +08:00
lyswhut
e51b21421d Merge branch 'dev' 2021-05-19 18:28:10 +08:00
lyswhut
5cae4ae0bf 使用更新的node版本 2021-05-19 18:27:48 +08:00
lyswhut
5f2cbb30c2 Merge branch 'dev' 2021-05-19 18:21:49 +08:00
lyswhut
1ddec2cb69 发布v1.10.0 2021-05-19 18:21:16 +08:00
lyswhut
96b54cc77a 稍微加高进度条 2021-05-19 18:15:41 +08:00
lyswhut
4da6524493 修复了一个歌曲下载失败时不会跳过任务的问题 2021-05-19 18:15:04 +08:00
lyswhut
1364d58065 新增Mac arm64位dmg包的构建 2021-05-19 16:55:02 +08:00
lyswhut
4383966051 更新依赖 2021-05-19 11:15:36 +08:00
lyswhut
86451fb5d0 更新依赖 2021-05-17 16:47:09 +08:00
lyswhut
0878c7398d 排行榜界面添加播放、收藏整个排行榜功能 2021-05-17 15:43:47 +08:00
lyswhut
81643165c2 修复在线列表的请求取消问题 2021-05-16 22:46:46 +08:00
lyswhut
b37802949c 更新依赖 2021-05-16 14:12:11 +08:00
lyswhut
a53f2388b4 更新网盘地址 2021-05-16 14:09:03 +08:00
lyswhut
e823543cad 更新 2021-05-15 20:40:01 +08:00
lyswhut
bdf7f903e9 修复mg源歌单标签、排行榜歌曲列表无法加载的问题 2021-05-15 20:39:53 +08:00
lyswhut
336b40a102 修复wy源的歌曲音质匹配问题 2021-05-15 17:27:25 +08:00
lyswhut
354922195f 修复请求重试bug 2021-05-15 01:42:23 +08:00
lyswhut
9c1199fc48 更新FAQ 2021-05-12 17:20:38 +08:00
lyswhut
ea35ed25d8 修复kw部分歌单无法打开的问题 2021-05-12 14:04:08 +08:00
lyswhut
add05ef913 修复在当前正常播放的列表中使用稍后播放功能时,播放完后稍后播放的歌曲后不会恢复原来播放位置播放的问题 2021-05-10 16:35:34 +08:00
lyswhut
e3aa4110fc 更新依赖 2021-05-09 13:33:46 +08:00
lyswhut
8a0ed1bc13 更新说明 2021-05-03 12:41:03 +08:00
lyswhut
0db490d444 更新依赖 2021-04-30 21:22:50 +08:00
lyswhut
4fd89c8a83 修正错别字 2021-04-28 17:47:33 +08:00
lyswhut
4fe17e556e 修复快捷键设置框内的提示问题 2021-04-26 15:17:01 +08:00
lyswhut
e95553163c 修复全局快捷键对桌面歌词无效的问题 2021-04-26 14:13:11 +08:00
lyswhut
8ae72074ef 更新 2021-04-25 13:18:49 +08:00
lyswhut
b6c1403f4b 显示换源提示 2021-04-25 10:29:08 +08:00
lyswhut
920d696d0b 更新歌曲无法下载、播放说明 2021-04-24 17:22:45 +08:00
lyswhut
a8dcb0b973 优化逻辑 2021-04-24 17:22:28 +08:00
lyswhut
895198b7b8 修正描述 2021-04-24 16:10:22 +08:00
lyswhut
144941d991 Merge branch 'dev' 2021-04-24 15:55:49 +08:00
lyswhut
f203517ce8 发布1.9.0版本 2021-04-24 15:55:40 +08:00
lyswhut
6dd6741237 Merge branch 'dev' 2021-04-24 15:53:37 +08:00
lyswhut
066e9c2fd5 修复上传包名匹配 2021-04-24 15:38:02 +08:00
lyswhut
f500b9b2d5 优化beta打包流程 2021-04-24 15:13:10 +08:00
lyswhut
c80e35872e 修改打包名 2021-04-24 14:34:52 +08:00
lyswhut
05eda6e406 修复打包失败 2021-04-24 14:13:13 +08:00
lyswhut
1686182616 更新版本号 2021-04-24 13:50:34 +08:00
lyswhut
fd5720451e 更新依赖 2021-04-24 13:48:27 +08:00
lyswhut
5b9adc8daa 修复属性计算 2021-04-23 10:57:47 +08:00
lyswhut
3efd6867d8 新增自动换源下载功能 2021-04-22 16:01:47 +08:00
lyswhut
cd1a55652f 修复换源错误的处理 2021-04-18 15:06:21 +08:00
lyswhut
47d0a4a343 修复正在播放“稍后播放”的歌曲时,对“稍后播放”前播放的列表进行添加、删除操作会导致切歌的问题 2021-04-16 16:58:20 +08:00
lyswhut
fe76d103f0 更新依赖 2021-04-16 14:18:53 +08:00
lyswhut
789a9bfe1a 更新依赖 2021-04-15 09:21:35 +08:00
lyswhut
391f97d3bb 修复js加载顺序问题 2021-04-14 16:11:57 +08:00
lyswhut
d077a03e60 优化配色 2021-04-14 15:38:59 +08:00
lyswhut
3317b6a9e5 优化配色 2021-04-14 15:01:55 +08:00
lyswhut
0b0f0e4649 新增黑色皮肤“黑纸白字” 2021-04-14 14:46:57 +08:00
lyswhut
f75e416479 添加黑色皮肤 2021-04-14 13:33:36 +08:00
lyswhut
d83db6bdf5 Merge branch 'dev' into 黑色主题 2021-04-14 08:35:00 +08:00
lyswhut
92ec461a77 修正设置页“搜索设置”部分内容的缩进显示问题 2021-04-13 15:57:25 +08:00
lyswhut
144fe3b8ac 更新依赖 2021-04-10 20:43:20 +08:00
lyswhut
af2e650538 兼容打开咪咕移动端分享的歌单链接,添加打开歌单的信息显示 2021-04-06 15:39:16 +08:00
lyswhut
1c585dc479 分离歌曲URL的缓存 2021-04-05 19:31:07 +08:00
lyswhut
c6853b197c 修复列表清除Bug 2021-04-03 21:31:19 +08:00
lyswhut
ef591c883a 移除列表lrc存储 2021-04-03 21:17:27 +08:00
lyswhut
4918db9cb1 分离歌词与歌曲列表信息的保存 2021-04-03 20:26:28 +08:00
lyswhut
b8250b67c9 升级到 webpack 5 2021-04-03 13:58:10 +08:00
lyswhut
d6f87894aa 更新依赖 2021-04-03 13:12:11 +08:00
lyswhut
c808500083 新增Windows arm64位免安装版的构建 2021-03-31 08:54:47 +08:00
lyswhut
3a6c2be48a 更新依赖 2021-03-29 08:54:55 +08:00
lyswhut
a28d01704a 更新faq 2021-03-27 19:34:45 +08:00
lyswhut
8bed654756 修复聚合搜索总页计算问题 2021-03-24 11:06:18 +08:00
lyswhut
8845884b24 更新发布流程 2021-03-21 13:23:13 +08:00
lyswhut
ea5de9a289 优化歌曲自动换源匹配 2021-03-20 20:55:44 +08:00
lyswhut
7922920f51 修改默认每页条数 2021-03-20 20:19:04 +08:00
lyswhut
dcf6ab0792 清理列表缓存将同时清理歌词 2021-03-20 19:52:02 +08:00
lyswhut
57fe510138 更新网盘链接 2021-03-20 00:10:36 +08:00
lyswhut
b4c7a6e448 自动禁用空代理 2021-03-18 17:30:44 +08:00
lyswhut
1d8702e95f 添加路径提示支持 2021-03-16 11:01:57 +08:00
lyswhut
8171fcd346 序启动时对数据文件做读取校验,数据出现损坏时自动备份损坏的数据 2021-03-14 02:06:35 +08:00
lyswhut
b1ccdeb0d3 修正错误 2021-03-13 16:49:05 +08:00
lyswhut
969b03b364 略微减小翻译字体大小 2021-03-13 15:25:35 +08:00
lyswhut
5ed81132fb 优化错误处理 2021-03-13 14:35:20 +08:00
lyswhut
c663cdbe0d 分离store逻辑 2021-03-13 14:10:36 +08:00
lyswhut
a4d46fd501 修正描述 2021-03-12 22:49:27 +08:00
lyswhut
c3e1ead90a Merge branch 'dhmkh' into dev 2021-03-12 22:42:17 +08:00
lyswhut
92c10eb534 修改协议阅读时间 2021-03-12 16:30:27 +08:00
lyswhut
08d0758616 修复备份与恢复功能在恢复数据时某些设置不立即生效的问题 2021-03-12 16:22:22 +08:00
lyswhut
68d8369a8b 修正单词拼写错误 2021-03-12 09:02:40 +08:00
lyswhut
af59a4472a 调整翻译字体大小 2021-03-11 17:34:29 +08:00
lyswhut
269546add1 Merge branch 'dev' 2021-03-10 10:23:50 +08:00
lyswhut
65246f8856 修正beta打包规则 2021-03-10 10:22:19 +08:00
lyswhut
ab35fe793d Merge branch 'dev' 2021-03-10 10:20:34 +08:00
lyswhut
e7b897e67c 优化打包命令 2021-03-10 10:19:49 +08:00
lyswhut
0b5dbb464d Merge branch 'dev' 2021-03-10 09:36:04 +08:00
lyswhut
60419f4319 修正徽章地址 2021-03-10 09:35:48 +08:00
lyswhut
1ddf0db18d 重命名beta打包工作流文件名 2021-03-10 09:34:43 +08:00
lyswhut
0732ca37b8 优化打包规则 2021-03-10 09:33:04 +08:00
lyswhut
801ea899e6 迁移到GitHub Actions 2021-03-10 09:21:43 +08:00
lyswhut
ba5159d2ad 修复Linux构建错误 2021-03-10 01:39:08 +08:00
lyswhut
ab2cf5a50f 修正错误,添加dev构建 2021-03-10 01:34:58 +08:00
lyswhut
d533c12315 修复Linux配置 2021-03-10 01:24:28 +08:00
lyswhut
1d90719053 Merge branch 'dev' 2021-03-10 01:18:49 +08:00
lyswhut
127d279266 修正语法错误 2021-03-10 01:15:30 +08:00
lyswhut
a484f66e69 修正语法错误 2021-03-10 01:02:30 +08:00
lyswhut
b34924e264 Merge branch 'dev' 2021-03-10 00:59:36 +08:00
lyswhut
4d5494c256 更新文件夹名 2021-03-10 00:59:06 +08:00
lyswhut
46b094a57a Merge branch 'dev' 2021-03-10 00:51:44 +08:00
lyswhut
0ef89c30e3 更新更新日志 2021-03-10 00:50:55 +08:00
lyswhut
dc2a89ce3f Merge branch 'dev' 2021-03-10 00:47:42 +08:00
lyswhut
47adcfec30 添加缓存 2021-03-10 00:42:27 +08:00
lyswhut
25e848702f 更新依赖 2021-03-10 00:42:15 +08:00
lyswhut
756a285978 修复收藏的在线列表id迁移保存出错的问题 2021-03-10 00:41:38 +08:00
lyswhut
fcf79bda03 Merge branch 'dev' 2021-03-09 22:55:20 +08:00
lyswhut
6b170f3116 尝试迁移到GitHub Actions 2021-03-09 22:50:08 +08:00
lyswhut
ea6bc245ff Merge branch 'dev' 2021-03-09 20:50:50 +08:00
lyswhut
61dc344d89 发布v1.8.2 2021-03-09 20:49:49 +08:00
lyswhut
fa6b7eacaa 修复歌曲ID存储变更导致酷狗图片获取失败的问题 2021-03-09 17:40:33 +08:00
lyswhut
23dafe539d 修正错误 2021-03-08 19:20:49 +08:00
lyswhut
0a654efda0 修改api窗口名 2021-03-07 21:43:00 +08:00
lyswhut
bec10ebe34 Merge branch 'dev' 2021-03-07 17:56:13 +08:00
lyswhut
0e03ca5d48 发布v1.8.1 2021-03-07 17:55:04 +08:00
lyswhut
4b1bf29ea0 新增启动参数-dhmkh 2021-03-07 17:54:02 +08:00
lyswhut
b391b2c16d 添加链接锚点 2021-03-07 17:53:24 +08:00
lyswhut
7b9cf93b40 修复歌词翻译的主题颜色适配问题 2021-03-07 15:07:22 +08:00
lyswhut
dd55e5e086 修改api窗口名 2021-03-07 13:30:47 +08:00
lyswhut
ba2b1dac92 Merge branch 'dev' 2021-03-07 12:38:11 +08:00
lyswhut
56ffeb3008 发布v1.8.0 2021-03-07 12:36:57 +08:00
lyswhut
b3757fd6b5 修复在下载列表右击未下载完成的歌曲弹出的右击菜单中没有开始下载选项的问题 2021-03-07 12:22:53 +08:00
lyswhut
e0de81230e Merge branch 'api' into dev 2021-03-07 12:01:59 +08:00
lyswhut
2bc669c828 添加更新日志 2021-03-07 01:29:23 +08:00
lyswhut
493f7809a4 优化歌词特殊字符移除 2021-03-07 01:22:25 +08:00
lyswhut
ef52cd6991 Merge branch 'dev' into lrc 2021-03-07 01:18:54 +08:00
lyswhut
480f15b6f6 优化酷狗搜索结果 2021-03-07 01:18:44 +08:00
lyswhut
fcfee88ef4 移除日志打印 2021-03-07 00:56:00 +08:00
lyswhut
19f647e6ab Merge branch 'dev' into lrc 2021-03-04 15:27:45 +08:00
lyswhut
27e5ec02f4 优化单首歌曲的“添加到列表”弹窗歌曲列表状态的显示 2021-03-04 15:27:29 +08:00
lyswhut
0808d77a2d 修复鼠标显示状态 2021-03-04 14:33:23 +08:00
lyswhut
90d9e9b911 修复歌词缩放设置失效的问题 2021-03-04 13:20:19 +08:00
lyswhut
ebfbc11c20 Merge branch 'dev' into lrc 2021-03-03 17:21:13 +08:00
lyswhut
2764743062 更新说明 2021-03-03 16:52:19 +08:00
lyswhut
c66d7d6187 完善歌词 2021-03-03 16:51:02 +08:00
lyswhut
f2b6c0a443 支持桌面歌词拖动 2021-03-02 18:41:27 +08:00
lyswhut
aef0ffa506 修改桌面歌词为逐字播放 2021-03-02 18:10:12 +08:00
lyswhut
040fcfba4f 初步完善歌词 2021-03-02 08:32:26 +08:00
lyswhut
9f72831e4a 更新依赖 2021-02-23 19:51:01 +08:00
lyswhut
fa0d9ad4c3 新增启动参数-play 2021-02-22 12:59:39 +08:00
lyswhut
b6107b7281 优化列表存储逻辑 2021-02-21 16:19:08 +08:00
lyswhut
6ffe61562b 移除自动换源清理 2021-02-21 15:46:19 +08:00
lyswhut
b5f5e13f22 移除xm verify窗口 2021-02-21 12:34:50 +08:00
lyswhut
376b7545e1 修复我的列表的灰色歌曲会显示下载按钮的问题 2021-02-20 01:01:58 +08:00
lyswhut
ffaa3e591b 修复kg获取图片失败的错误处理 2021-02-20 00:50:00 +08:00
lyswhut
24d5722ef7 移除log 2021-02-19 22:10:37 +08:00
lyswhut
9e1fe0a6c1 移除多余代码 2021-02-19 21:59:58 +08:00
lyswhut
d128aeb6ef 修复kg歌曲详情页的歌曲无法播放的问题 2021-02-19 11:47:25 +08:00
lyswhut
ef371dedb4 修正描述 2021-02-15 19:53:46 +08:00
lyswhut
00d262f0e6 修复mg源歌单列表无法加载的问题 2021-02-15 12:40:12 +08:00
lyswhut
475b8924c3 修复歌单列表加载失败时无法进入歌单打开界面的问题 2021-02-15 10:52:29 +08:00
lyswhut
0afb1417a3 修复下载列表数据存储问题 2021-02-06 00:09:09 +08:00
lyswhut
b6b41660d6 移除未使用的变量 2021-02-05 23:07:36 +08:00
lyswhut
c78811fe36 移除虾米源,允许播放除了搜索列表以外的所有歌曲 2021-02-05 23:06:08 +08:00
lyswhut
a0254eb4f2 优化英文名歌曲匹配 2021-02-04 17:26:11 +08:00
lyswhut
e9c96ecde2 修复搜索列表双击不支持播放的源时会导致切歌的问题 2021-02-04 14:37:52 +08:00
lyswhut
f9cbf9254c 新增设置-其他-列表缓存信息清理功能 2021-02-02 15:08:07 +08:00
lyswhut
70d24ffdfd 修复音乐搜索列表的稍后播放功能无效的问题 2021-02-01 18:26:26 +08:00
lyswhut
84530ca0a2 添加网盘无法打开的提示 2021-02-01 16:17:57 +08:00
lyswhut
e848fda4e3 将下载列表与我的列表分离存储 2021-02-01 14:55:39 +08:00
lyswhut
51964e51c6 Merge branch 'dev' 2021-01-30 14:33:58 +08:00
lyswhut
0c63268df1 发布v1.7.1版本 2021-01-30 14:30:05 +08:00
lyswhut
316f62c3c8 修复MAC下xm音乐滑块验证问题 2021-01-30 14:29:20 +08:00
lyswhut
9e37800426 修复非透明模式下右侧滚动条无法拖动的问题 2021-01-30 14:18:12 +08:00
lyswhut
3e0ee374c9 移除对changelog的html对version.json的写入 2021-01-30 13:46:46 +08:00
lyswhut
bb411a365c Merge branch 'dev' 2021-01-30 13:17:47 +08:00
lyswhut
da0878a7a7 更新依赖到最新 2021-01-30 13:15:32 +08:00
lyswhut
d0328f17e1 发布v1.7.0版本 2021-01-30 13:14:33 +08:00
lyswhut
c6f49984a7 修改默认弹出层动画 2021-01-30 12:53:39 +08:00
lyswhut
f53fb071f0 更新electron到9.4.2 2021-01-29 22:24:37 +08:00
lyswhut
2cc8af71d0 修复自动换源遇到不支持的源时报错的问题 2021-01-29 10:18:48 +08:00
lyswhut
006da66028 导出歌单移除换源信息 2021-01-28 14:09:22 +08:00
lyswhut
2a74abdef4 修复xm源验证问题 2021-01-28 10:22:26 +08:00
lyswhut
269b14394d 修复了一个由版本更新日志显示导致的潜在远程代码执行攻击漏洞 2021-01-27 19:20:29 +08:00
lyswhut
d6d6fae2ca 禁止打开非http协议链接 2021-01-26 08:43:07 +08:00
lyswhut
331afc37df 新增“记住播放进度”功能的控制,优化设置项展示 2021-01-25 10:44:03 +08:00
lyswhut
041bfdaf3c 修复切换音乐来源可能无法切歌的问题 2021-01-23 19:36:29 +08:00
lyswhut
023cd15cd0 调整加载动画速度 2021-01-23 16:45:01 +08:00
lyswhut
f2fb37df4c 修复快速切换歌曲时, 会出现播放的歌曲和界面展示的歌曲不一致的问题 2021-01-23 16:33:12 +08:00
lyswhut
c4ae0dddb0 修改自动更新阶段的描述 2021-01-23 15:43:14 +08:00
lyswhut
f66077f012 修复手动切歌不清除稍后播放列表的问题 2021-01-23 14:56:57 +08:00
lyswhut
a31d6bc5ae 修改菜单大小 2021-01-23 14:42:01 +08:00
lyswhut
fad13f517b 更新electron到9.4.1 2021-01-21 16:26:46 +08:00
lyswhut
ef36517a26 修复恢复播放会导致重复播放的问题 2021-01-21 15:16:46 +08:00
lyswhut
085e45a50f 新增“稍后播放”功能 2021-01-21 14:39:37 +08:00
lyswhut
43b06a686b 修改构建命令 2021-01-20 14:55:34 +08:00
lyswhut
e8dba4d133 优化播放歌曲换源匹配规则 2021-01-18 16:28:11 +08:00
lyswhut
1af3cd1d77 优化播放歌曲换源匹配 2021-01-18 16:21:57 +08:00
lyswhut
4c44310fea 移除下面热搜词的加载 2021-01-16 01:11:55 +08:00
lyswhut
3e343a80fd 搜索界面新增搜索状态的提示 2021-01-16 01:11:17 +08:00
lyswhut
313fac9f18 Merge branch 'dev' 2021-01-13 21:35:00 +08:00
lyswhut
2c242c66e5 发布v1.6.1版本 2021-01-13 21:34:28 +08:00
lyswhut
4e2b525c99 移除initTOC调用 2021-01-13 14:06:59 +08:00
lyswhut
93c76d4ba0 添加参数修改说明 2021-01-13 14:05:47 +08:00
lyswhut
b69ecca252 修改目录 2021-01-13 11:16:16 +08:00
lyswhut
947011103c 降级electron到9.3.3修复部分系统没有声音的问题 2021-01-13 11:14:53 +08:00
lyswhut
2fd7e4503a 修复自动换源导致的搜索列表每页变成10条数据的问题 2021-01-12 11:42:56 +08:00
lyswhut
a5fce15661 改进自动换源时的歌曲匹配 2021-01-12 11:09:27 +08:00
lyswhut
f1561ddc21 修复某些情况下自动换源的时间过长时会终止换源自动切歌的问题 2021-01-11 16:21:08 +08:00
lyswhut
74ce094d79 更新无法试听解决方法 2021-01-11 14:12:58 +08:00
lyswhut
f832d70701 更新-dha参数的描述 2021-01-10 13:14:50 +08:00
lyswhut
24521bf50d Merge branch 'dev' 2021-01-10 12:53:15 +08:00
lyswhut
cfe84fc2dd 发布v1.6.0版本 2021-01-10 12:52:45 +08:00
lyswhut
8efeb169af 优化歌曲匹配 2021-01-09 21:10:58 +08:00
lyswhut
7b7711d410 Merge branch 'dev' into 黑色主题 2021-01-09 20:32:55 +08:00
lyswhut
8b0779ac17 修复对dt参数的读取 2021-01-09 20:13:32 +08:00
lyswhut
1e904e5ebb 优化自动换源功能 2021-01-09 19:40:31 +08:00
lyswhut
1d3b93d4a3 更新依赖 2021-01-09 18:28:51 +08:00
lyswhut
7756b62f4c 优化歌曲匹配规则 2021-01-09 15:15:52 +08:00
lyswhut
7990e841e0 优化歌曲查找匹配功能 2021-01-09 01:15:20 +08:00
lyswhut
ae348a71e6 移除log 2021-01-09 00:48:59 +08:00
lyswhut
9a1a537bfd 新增播放自动换源功能 2021-01-09 00:45:15 +08:00
lyswhut
6b7d2ea657 整理代码 2021-01-08 22:57:10 +08:00
lyswhut
98ef7c2385 新增-dha参数,修复-nt启动参数不真正生效的问题 2021-01-08 22:09:08 +08:00
lyswhut
093bfd26a3 我的列表右键菜单添加搜索当前歌曲功能 2021-01-06 15:04:00 +08:00
lyswhut
9953c61d62 添加对tips实例的销毁处理 2021-01-03 14:07:50 +08:00
lyswhut
e61072ddc9 更新快速隐藏详情页英文翻译 2021-01-03 01:24:06 +08:00
lyswhut
2269cd54be 隐藏播放详情页按钮添加快速隐藏详情页提示 2021-01-03 01:22:25 +08:00
lyswhut
76d5e64097 添加桌面歌词字体、透明度调整按钮微调提示 2021-01-03 01:11:08 +08:00
lyswhut
06073f3982 添加鼠标提示的自动关闭功能 2021-01-03 00:46:13 +08:00
lyswhut
0f71a93c5e 添加鼠标指向歌曲封面的提示 2021-01-03 00:04:33 +08:00
lyswhut
fa41c32a3e 更新依赖 2020-12-26 10:20:10 +08:00
lyswhut
3b00efdeae Merge branch 'dev' into 黑色主题 2020-12-24 17:22:30 +08:00
lyswhut
0f323ce04f Merge branch 'dev' 2020-12-24 17:22:22 +08:00
lyswhut
ad2acc2a16 更新更新日志 2020-12-24 17:20:27 +08:00
lyswhut
162fea6e28 移除多余文件 2020-12-24 17:17:54 +08:00
lyswhut
30e939c354 更新electron版本到9.5.0 2020-12-24 14:27:57 +08:00
lyswhut
94c25b5dcb 优化列表排序弹窗界面 2020-12-24 14:27:44 +08:00
lyswhut
a58d65e8af 修复恢复上次播放的歌曲时在随机播放模式下不把恢复播放的歌曲放入已播放队列的问题 2020-12-24 11:33:01 +08:00
lyswhut
06b453ebe7 新增歌曲下载失败FAQ 2020-12-23 17:03:38 +08:00
lyswhut
f00c6b1cb6 更新Linux桌面歌词说明 2020-12-19 15:39:33 +08:00
lyswhut
027466dfc2 更新排序说明 2020-12-19 15:19:24 +08:00
lyswhut
bc69a98df1 我的列表右键菜单新增列表排序功能 2020-12-19 15:16:22 +08:00
lyswhut
ba9fd8d1f1 新增 更新已收藏的在线歌单 问答 2020-12-18 19:36:38 +08:00
lyswhut
0fcc3f257d 修正描述 2020-12-18 19:36:29 +08:00
lyswhut
382235c556 移除多余代码 2020-12-13 13:30:21 +08:00
lyswhut
0a919d7076 修复getAttribute的报错 2020-12-13 12:32:55 +08:00
lyswhut
0709aa56a4 Merge branch 'dev' 2020-12-13 11:48:17 +08:00
lyswhut
684d90d0e7 发布v1.5.0 2020-12-13 11:47:46 +08:00
lyswhut
227c05a696 降级electron到9.3.3 2020-12-12 21:00:58 +08:00
lyswhut
8cf1a4a790 更新依赖 2020-12-12 19:35:10 +08:00
lyswhut
75e3976bfa 添加同步歌单状态指示 2020-12-12 09:41:31 +08:00
lyswhut
e9791fd493 修正进度条状态 2020-12-12 09:26:15 +08:00
lyswhut
f9f445489c Update template 2020-12-11 08:34:47 +08:00
lyswhut
10e5460e39 优化软件启动时恢复上一次播放的歌曲进度功能 2020-12-11 08:29:30 +08:00
lyswhut
f1049169b5 直接从歌单详情收藏的列表新增同步功能 2020-12-10 12:57:48 +08:00
lyswhut
2056bdb43d 搜索框为空时不进行歌单打开 2020-12-07 17:32:21 +08:00
lyswhut
785bf3ce3a 添加Linux arm64 deb包的构建 2020-12-06 22:54:03 +08:00
lyswhut
c29bf61e59 修复某些酷狗源歌单链接无法打开的问题 2020-12-06 21:33:39 +08:00
lyswhut
a98c0516a3 修复酷狗源的某些歌曲没有专辑字段导致的列表加载失败问题 2020-12-06 21:17:18 +08:00
lyswhut
a6989a2aba 移除log 2020-12-04 21:34:11 +08:00
lyswhut
30e43170f8 修复MAC平台首次运行软件最小化、关闭控制按钮默认在右边的问题 2020-11-30 09:59:38 +08:00
lyswhut
d8feb3e794 修改相对路径别名 2020-11-28 20:23:17 +08:00
lyswhut
99bdf6d173 增加歌曲加载等待时间 2020-11-28 20:22:58 +08:00
lyswhut
8424a9e4de 降级node-id3修复MAC平台上下载歌曲封面嵌入无法显示的问题 2020-11-28 20:22:33 +08:00
lyswhut
4c0cbe3253 Merge branch 'dev' 2020-11-25 08:27:53 +08:00
lyswhut
462dc5da51 发布v1.4.1版本 2020-11-25 08:26:43 +08:00
lyswhut
40fad5f785 调整FAQ顺序 2020-11-24 15:17:04 +08:00
lyswhut
c0791acbf6 修复某些电脑上的某些歌曲没有声音的问题 2020-11-24 15:16:46 +08:00
lyswhut
3cb0a56647 修复歌曲URL过期时,等待刷新URL的自动切换歌曲时间间隔太短的问题 2020-11-23 10:18:35 +08:00
lyswhut
95f38e8048 修复有歌词翻译与无歌词的音乐间切换会导致歌词翻译残留显示的问题 2020-11-22 11:06:45 +08:00
lyswhut
a3493a6c74 Merge branch 'dev' 2020-11-21 19:01:16 +08:00
lyswhut
3535bb4e4e 发布v01.4.0 2020-11-21 19:00:35 +08:00
lyswhut
3be6242cb5 更新多选说明 2020-11-20 10:55:23 +08:00
lyswhut
64a53830e4 新增播放进度信息保存 2020-11-19 15:52:00 +08:00
lyswhut
b3e3860b23 修复kg源歌曲信息带有单引号等特殊字符被转义的问题 2020-11-17 17:14:47 +08:00
lyswhut
4db560fb4b 修复移动单首歌曲时出现的问题 2020-11-15 14:08:11 +08:00
lyswhut
b00de4d976 托盘菜单新增显示、隐藏主界面选项,为Linux、MAC版添加托盘菜单 2020-11-14 22:29:27 +08:00
lyswhut
0a0d6969ec 初始提交 2020-11-14 11:48:51 +08:00
lyswhut
ac162103a7 更新依赖 2020-11-14 10:22:47 +08:00
lyswhut
16876502d1 修复播放状态栏切换“上一首”歌曲按钮提示错误的问题 2020-11-13 14:57:45 +08:00
lyswhut
4cdd2110a6 更新依赖 2020-11-10 10:59:45 +08:00
lyswhut
b820adf0bf 优化切歌逻辑 2020-11-10 08:33:07 +08:00
lyswhut
1cee2febf4 修复专辑图片无法嵌入的问题 2020-11-10 08:31:47 +08:00
lyswhut
43f8d9d72b 更新FAQ问题顺序 2020-11-06 21:33:27 +08:00
lyswhut
66ffe03224 修改评论按钮提示 2020-11-04 09:08:18 +08:00
lyswhut
5c96c83f98 移除kg源的歌词文件开头的空白字符串 2020-11-02 15:13:09 +08:00
lyswhut
6d1ddece92 Merge branch 'dev' 2020-11-01 18:18:15 +08:00
lyswhut
897107ca23 发布v1.3.0版本 2020-11-01 18:17:34 +08:00
lyswhut
0baaffdad8 修复小芸源评论的翻页加载问题 2020-11-01 18:01:04 +08:00
lyswhut
784d2fda6d 修复小芸源音乐搜索结果最多只有20条搜索结果的问题 2020-11-01 14:36:53 +08:00
lyswhut
19817249db 更新依赖 2020-11-01 01:23:04 +08:00
lyswhut
240260e598 修正用户名英文字母的行高问题 2020-11-01 00:59:50 +08:00
lyswhut
fcf92f06ef 修复tx源某些评论的换行问题 2020-11-01 00:50:49 +08:00
lyswhut
fe1c2a1896 添加目录 2020-10-30 08:37:32 +08:00
lyswhut
d55c65e275 更新依赖 2020-10-29 09:08:39 +08:00
lyswhut
93064bb18b 完善评论功能 2020-10-29 09:07:25 +08:00
lyswhut
dec1b24c86 修正错别字 2020-10-28 11:02:21 +08:00
lyswhut
8c76c3d81c 播放详情页新增歌曲评论加载显示 2020-10-27 18:09:17 +08:00
lyswhut
8d8cabd5b3 完善评论功能 2020-10-24 02:08:31 +08:00
lyswhut
aa948b0149 添加评论界面 2020-10-23 22:40:23 +08:00
lyswhut
a25ef6e2a0 修改播放详情页的歌曲图片的显示效果 2020-10-22 21:00:12 +08:00
lyswhut
c404d3ac8d Merge branch 'dev' 2020-10-18 12:01:12 +08:00
lyswhut
8eb71495a1 发布v1.2.2版本 2020-10-18 12:00:28 +08:00
lyswhut
db824c4601 更新版本号 2020-10-18 11:48:46 +08:00
lyswhut
029886cc87 降级 Electron 到 9.x.x 版本修复 Linux 版桌面歌词窗口变白的问题 2020-10-18 11:47:38 +08:00
lyswhut
42604ec737 Merge branch 'dev' 2020-10-18 09:23:57 +08:00
lyswhut
caaa62db30 发布v1.2.1版本 2020-10-18 09:22:34 +08:00
lyswhut
0f135e0587 更新依赖 2020-10-17 16:09:29 +08:00
lyswhut
dae6826cc1 Linux版的软件界面默认使用圆角与阴影,顺便修复了桌面歌词窗口变白的问题 2020-10-14 18:20:03 +08:00
lyswhut
ddf2ac7ece 更新FAQ 2020-10-08 22:22:11 +08:00
lyswhut
5e011356f2 更新依赖 2020-10-08 17:15:19 +08:00
lyswhut
82e047dc66 修复输入框组件bug 2020-10-08 17:09:26 +08:00
lyswhut
7720b416d5 更新依赖 2020-10-07 21:37:02 +08:00
lyswhut
c40dc0fa50 修复聚合搜索的分页问题 2020-10-02 12:59:41 +08:00
lyswhut
36ed7bca66 Merge branch 'dev' 2020-09-30 01:15:41 +08:00
lyswhut
1fa015704a 发布 v1.2.0 版本 2020-09-30 01:15:08 +08:00
lyswhut
7e2097ba6d 更新 Electron 到 v10.1.3 2020-09-29 22:05:20 +08:00
lyswhut
3a630edaa5 优化提示查找逻辑 2020-09-29 14:41:41 +08:00
lyswhut
1b561bbf4c 优化提示出现的位置 2020-09-29 11:14:29 +08:00
lyswhut
37de0b59c3 优化软件内文本悬浮提示 2020-09-29 10:37:41 +08:00
lyswhut
2f528d2a03 Merge branch 'dev' of github.com:lyswhut/lx-music-desktop into dev 2020-09-29 08:46:23 +08:00
lyswhut
686d55a672 优化特征匹配 2020-09-29 00:34:39 +08:00
lyswhut
07c58e2ce7 修复关闭切换动画时从搜索候选列表点击内容无效的问题 2020-09-28 23:00:53 +08:00
lyswhut
6fe794fdf5 修复酷狗歌词格式问题 2020-09-28 22:53:20 +08:00
lyswhut
d0ec90130c 修复歌曲封面无法嵌入的Bug 2020-09-28 22:52:50 +08:00
lyswhut
f7806d9781 播放控制栏开启/关闭桌面歌词按钮 新增右击按钮时锁定/解锁桌面歌词功能 2020-09-28 16:40:51 +08:00
lyswhut
63219b49f2 修复搜索历史列表的保存问题 2020-09-28 10:04:17 +08:00
lyswhut
320e84627c 更新依赖到最新 2020-09-28 09:56:48 +08:00
lyswhut
fc9e697249 修复迁移列表滚动位置设置时的bug 2020-09-28 09:54:36 +08:00
lyswhut
a68c626fa2 Merge branch 'dev' of github.com:lyswhut/lx-music-desktop into dev 2020-09-28 08:35:53 +08:00
lyswhut
66f17cf05b 更新log 2020-09-28 01:33:15 +08:00
lyswhut
5949e8fb3f 优化我的列表滚动条位置的保存逻辑 2020-09-28 01:33:06 +08:00
lyswhut
3bb0fcc4cd 修复重启软件后试听列表与收藏列表无法恢复上次的滚动位置的问题 2020-09-28 01:32:53 +08:00
lyswhut
d32d5d0eb8 修复初始化搜索历史列表功能 2020-09-28 01:32:45 +08:00
lyswhut
5ee2c030ec 更新设置-备份与恢复功能的描述 2020-09-23 17:00:16 +08:00
lyswhut
58ca7df105 修复网易云KTV嗨榜无法加载的问题 2020-09-20 12:17:47 +08:00
lyswhut
fc77dbc73b 优化代码 2020-09-19 16:18:13 +08:00
lyswhut
8aedde2422 修复桌面歌词窗口不允许拖出桌面之外的位置计算偏移Bug 2020-09-19 16:00:03 +08:00
lyswhut
526b5fe140 Merge branch 'dev' 2020-09-19 13:00:24 +08:00
lyswhut
1c4e9ed474 发布v1.1.1版本 2020-09-19 12:59:55 +08:00
lyswhut
21cbb441e2 更新依赖到最新 2020-09-19 12:56:07 +08:00
lyswhut
01fedc59e2 修复某些情况下桌面歌词不会播放的问题 2020-09-19 12:55:51 +08:00
lyswhut
b3d13c3a90 Merge branch 'dev' 2020-09-18 20:24:48 +08:00
lyswhut
24ee42e0c7 修正更新描述 2020-09-18 20:24:09 +08:00
lyswhut
3a18eccdd6 更新travis-ci地址 2020-09-18 20:11:40 +08:00
lyswhut
a3b763ecc9 发布 v1.1.0 版本 2020-09-18 20:08:04 +08:00
lyswhut
822c74ad2f 更新依赖到最新 2020-09-18 20:02:12 +08:00
lyswhut
0a71e34459 播放状态栏新增桌面歌词的开关、播放模式的切换、歌曲的收藏按钮 2020-09-18 12:03:41 +08:00
lyswhut
25b68081ea 迁移travis-ci.org到travis-ci.com 2020-09-16 08:54:57 +08:00
lyswhut
ac0e1e7698 移除对百度音乐的支持 2020-09-05 15:13:49 +08:00
lyswhut
4be5bcc979 新增酷狗歌词翻译 2020-09-05 03:50:30 +08:00
lyswhut
f3c2d7e43a 更新依赖到最新 2020-09-05 03:46:33 +08:00
lyswhut
9fa0700f72 更新css-loader 2020-09-02 14:00:01 +08:00
lyswhut
868da542ab 更新网盘地址 2020-08-30 13:24:23 +08:00
lyswhut
04c810e292 添加没有写入权限的提示 2020-08-30 12:25:02 +08:00
lyswhut
86478c0268 更新electron到 10.1.0 2020-08-29 14:44:37 +08:00
lyswhut
b453a8934f 新增显示切换动画设置 2020-08-24 00:44:55 +08:00
lyswhut
669c1fe490 新增显示歌词翻译设置,默认开启 2020-08-23 15:45:08 +08:00
lyswhut
c15895ab45 新增 不允许将歌词窗口拖出主屏幕之外 的设置项 2020-08-23 12:13:17 +08:00
lyswhut
44acc7d5ac 移除默认的全局声音媒体快捷键接管 2020-08-22 14:17:34 +08:00
lyswhut
341df0dab7 在歌单详情界面新增播放当前歌单按钮、收藏歌单按钮 2020-08-22 14:06:16 +08:00
lyswhut
8df3ff3536 更新依赖 2020-08-22 12:49:43 +08:00
lyswhut
85c683b714 优化随机播放机制 2020-08-10 22:24:38 +08:00
lyswhut
6606edafb9 修复窗口在较小模式下最小化/关闭按钮不居中的问题 2020-08-10 09:51:31 +08:00
lyswhut
91ff7b658a 更新依赖 2020-08-09 12:39:49 +08:00
lyswhut
27c500e950 移除无用代码 2020-08-09 12:39:24 +08:00
lyswhut
078245cb96 修复我的列表搜索对最后一个字符的匹配问题 2020-08-04 09:05:23 +08:00
lyswhut
e54597b944 为区分静音状态,静音时音量条会变淡,调整音量条时将会取消静音 2020-08-04 08:54:26 +08:00
lyswhut
1af9b3a018 静音时隐藏音量条 2020-08-02 14:40:38 +08:00
lyswhut
315d041fc7 桌面歌词当前播放行改为上下居中 2020-08-01 23:26:35 +08:00
lyswhut
f7f1cdab34 优化逻辑 2020-07-31 11:00:55 +08:00
lyswhut
597f0da42f 移除多余脚本命令 2020-07-27 12:06:54 +08:00
lyswhut
497add09b3 修复使用全局快捷键还原窗口时,窗口没有获取焦点的问题 2020-07-27 11:38:59 +08:00
lyswhut
ff05225b56 修复git文件名大小写问题 2020-07-26 16:25:32 +08:00
lyswhut
ac053e9ac9 移除多余文件 2020-07-26 16:21:53 +08:00
lyswhut
9aeae1e460 移除重复的文件 2020-07-26 16:19:52 +08:00
lyswhut
d710f204b3 更新faq 2020-07-26 12:33:23 +08:00
lyswhut
be016aa4bb 发布v1.0.1版本 2020-07-25 19:49:04 +08:00
lyswhut
04e62cba24 更新到v1.0.1版本 2020-07-25 19:42:02 +08:00
lyswhut
4d476dc641 修复在 Windows 系统下缩放比非100%时,拖动桌面歌词会自动加大桌面歌词窗口的问题 2020-07-25 19:40:44 +08:00
lyswhut
85c3a812da 对我的列表歌曲搜索结果进行相似度排序 2020-07-25 19:36:46 +08:00
lyswhut
e985e70303 更新多选描述 2020-07-24 22:18:27 +08:00
lyswhut
9dbf96c259 发布 1.0.0 版本 2020-07-24 20:49:44 +08:00
lyswhut
6eba7c9a2f 更新文档 2020-07-24 20:48:20 +08:00
lyswhut
c1966e9301 更新FAQ 2020-07-24 20:45:50 +08:00
lyswhut
a6fb72f1de 调整主题颜色 2020-07-24 20:28:07 +08:00
lyswhut
a695a69f0f 更新依赖 2020-07-23 00:42:17 +08:00
lyswhut
a8632297eb 新增在歌单详情列表按退格键可快速返回歌单列表 2020-07-19 13:40:09 +08:00
lyswhut
ca4218b309 点击确定时打开备份文件位置 2020-07-18 22:49:28 +08:00
lyswhut
65df71239a 更新package-lock.json 2020-07-14 20:57:29 +08:00
lyswhut
fdd8f3b214 更新依赖 2020-07-14 20:41:10 +08:00
lyswhut
1887573301 更新electron到9.1.0 2020-07-08 02:01:46 +08:00
lyswhut
1efd56f48b 新增当列表文件损坏时对损坏文件的备份 2020-07-06 18:15:03 +08:00
lyswhut
bcd13b6f27 压缩备份数据文件 2020-07-06 16:14:18 +08:00
lyswhut
e4e880edd9 优化歌单操作体验 2020-07-04 21:14:29 +08:00
lyswhut
75da9c2e73 修复无法开启托盘的bug 2020-07-04 17:32:43 +08:00
lyswhut
7a933dbd5e 修正桌面歌词透明度按钮描述 2020-07-04 13:17:42 +08:00
lyswhut
4b097b83b1 更新依赖到最新 2020-07-04 11:58:39 +08:00
lyswhut
b3f9a691f5 重新提交重命名后的文件 2020-07-04 11:50:55 +08:00
lyswhut
50cd94a280 Merge branch 'master' into dev 2020-07-04 11:32:51 +08:00
lyswhut
82982b3cda 调整图标 2020-07-04 01:50:08 +08:00
lyswhut
a5243ff58c 支持flac文件歌词写入 2020-07-03 09:16:20 +08:00
lyswhut
3b25e0a112 新增将歌词嵌入音频文件中 2020-07-02 12:03:18 +08:00
lyswhut
d94d880569 修复搜索界面批量添加歌曲时程序异常的bug 2020-07-01 23:46:52 +08:00
lyswhut
8eec07b5bd 更改默认搜索设置为聚合搜索 2020-07-01 17:07:25 +08:00
lyswhut
9f72a51458 新增开关下载功能控制 2020-07-01 17:04:43 +08:00
lyswhut
9fd181ee06 新增托盘图标样式设置 2020-06-29 01:06:06 +08:00
lyswhut
f18875682b 修复透明度按钮提示错误的问题 2020-06-28 12:04:00 +08:00
lyswhut
897a17e303 修复版本对比问题 2020-06-27 21:22:02 +08:00
lyswhut
c9d0aafbfa 优化右击菜单逻辑 2020-06-27 13:43:27 +08:00
lyswhut
fbd2a57a35 新增我的列表内歌曲搜索 2020-06-26 16:24:23 +08:00
lyswhut
ccd5c91283 添加右击菜单对不可用音源的处理 2020-06-26 12:21:07 +08:00
lyswhut
c78b9f07a2 修复聚合搜索音源标签的皮肤配色问题 2020-06-26 12:17:11 +08:00
lyswhut
ffc3300fe4 移除无用代码 2020-06-26 01:12:08 +08:00
lyswhut
b02d32929c 优化默认设置 2020-06-26 00:17:09 +08:00
lyswhut
e29d3e3eaa 隐藏下载弹窗中不可用的音质 2020-06-25 23:39:48 +08:00
lyswhut
5d91643637 歌曲列表右击菜单新增跳转到当前歌曲源官方详情页菜单 2020-06-25 23:10:38 +08:00
lyswhut
9eec1adf77 支持加载大于1000首歌的歌单 2020-06-25 21:29:05 +08:00
lyswhut
9cebaa0d97 新增歌词区域触摸板、鼠标滚轮等对歌词滚动的支持 2020-06-25 19:39:12 +08:00
lyswhut
48cfebd5ae 改进聚合搜索的搜索结果排序 2020-06-25 19:37:54 +08:00
lyswhut
0c3899840b 添加静音处理 2020-06-25 15:18:56 +08:00
lyswhut
93bab6bbdd 优化逻辑 2020-06-24 22:55:47 +08:00
lyswhut
69368b884f 修复默认快捷键绑定问题 2020-06-24 20:20:19 +08:00
lyswhut
f59b2d26ad 优化播放控制按钮交互效果 2020-06-24 16:18:05 +08:00
lyswhut
32ffe33e1a 修复禁用快捷键的情况下设置全局快捷键的bug,优化启用/禁用视觉效果 2020-06-24 16:17:24 +08:00
lyswhut
cc5d119ba4 新增首次运行时自动根据当前系统使用的语言设置软件显示的语言 2020-06-24 16:12:02 +08:00
lyswhut
637727faa0 修改播放界面布局 2020-06-24 14:18:32 +08:00
lyswhut
9b82cdfe53 新增快捷键设置 2020-06-24 01:20:56 +08:00
lyswhut
2424c76c50 修复Linux下无法ctrl+a全选的问题 2020-06-20 15:32:39 +08:00
lyswhut
f0dc76c7a4 修复某些情况下从托盘还原窗口后无法操作的问题 2020-06-20 13:15:23 +08:00
lyswhut
798f5cd0a6 优化界面显示时的效果 2020-06-15 20:28:41 +08:00
lyswhut
a904896269 修复进度条开始部分无法选择的问题 2020-06-14 18:07:41 +08:00
lyswhut
be482bd683 优化打开歌单输入框宽度 2020-06-14 00:22:30 +08:00
lyswhut
e9a624c896 优化选择逻辑 2020-06-13 16:11:43 +08:00
lyswhut
b3a13a25f0 优化UI 2020-06-12 19:15:40 +08:00
lyswhut
30e55f5acb 修复事件绑定bug 2020-06-11 22:24:55 +08:00
lyswhut
7a67263cf9 添加阴影 2020-06-11 22:18:54 +08:00
lyswhut
b811906bae 新增软件最小化、关闭按钮位置设置 2020-06-11 21:41:15 +08:00
lyswhut
2a81d41c2c 新增桌面歌词 2020-06-11 21:16:59 +08:00
dependabot[bot]
51d0037a4b Bump websocket-extensions from 0.1.3 to 0.1.4 (#225)
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-06-07 21:19:55 +08:00
lyswhut
25fc0226aa 移除多余代码 2020-05-24 15:12:35 +08:00
lyswhut
cfef06a15b 新增我的列表右键菜单复制歌曲名选项 2020-05-23 22:44:19 +08:00
lyswhut
5ce6aafc07 修改排行榜顺序 2020-05-23 22:31:24 +08:00
lyswhut
fe833033df 改变排行榜布局,新增更多排行榜 2020-05-23 22:24:20 +08:00
lyswhut
a47be148c8 更新electron到 v9 2020-05-23 16:32:28 +08:00
lyswhut
af426d28c1 修复错误更新试听列表外的歌曲时间的问题 2020-05-20 23:54:49 +08:00
lyswhut
0332b81d58 完善自定义列表、新增右键菜单 2020-05-20 22:52:37 +08:00
lyswhut
88b416c949 更新描述 2020-05-16 20:44:12 +08:00
lyswhut
66c4e62d9b 更新配置 2020-05-16 20:39:59 +08:00
lyswhut
9091c2c346 Merge branch 'v1' into dev 2020-05-16 20:29:27 +08:00
lyswhut
e29ca2e79c 整理配置逻辑 2020-05-16 20:19:58 +08:00
lyswhut
afc3f48f51 新增自定义列表 2020-05-16 19:21:21 +08:00
lyswhut
eed8b36dc1 Merge branch 'dev' into v1 2020-05-15 11:51:48 +08:00
lyswhut
b20942d254 修改按键绑定机制 2020-05-15 11:50:22 +08:00
lyswhut
e707beffd6 Merge branch 'dev' into v1 2020-05-14 13:24:34 +08:00
lyswhut
747c75fb18 更新依赖 2020-05-14 09:19:19 +08:00
lyswhut
42f0a9df4b 改进媒体设备改变暂停播放逻辑 2020-05-13 23:17:51 +08:00
lyswhut
10ee6bcc7f 新增酷我歌单显示歌曲文件大小 2020-05-10 18:55:20 +08:00
lyswhut
bcba194a31 Merge branch 'dev' into v1 2020-05-09 20:42:04 +08:00
lyswhut
8605f63ac6 完善音频输出设备改变暂停机制 2020-05-09 20:41:48 +08:00
lyswhut
a02827a7ac Merge branch 'dev' into v1 2020-05-08 20:24:12 +08:00
lyswhut
16903b0f5a 修复可能导致渲染进程无响应的问题 2020-05-08 20:23:23 +08:00
lyswhut
26bdf25423 新增所选音频输出设备被移除时是否暂停播放 2020-05-08 19:14:44 +08:00
lyswhut
3c0f4be533 Merge branch 'dev' into v1 2020-05-07 23:12:11 +08:00
lyswhut
ecf9dce758 修复 tab 组件边框溢出问题 2020-05-07 23:11:59 +08:00
lyswhut
90e40d5245 Merge branch 'dev' into v1 2020-05-07 20:38:03 +08:00
lyswhut
b76424ba9b 修改设置-列表-是否显示歌曲源的默认设置为选中 2020-05-06 18:22:24 +08:00
lyswhut
ded75563e9 更新图标 2020-05-05 20:56:25 +08:00
lyswhut
1eb9920bcf 更新图标 2020-05-04 12:19:57 +08:00
lyswhut
7e029b5495 修复某些情况下无法开始下载任务的问题 2020-05-04 01:53:37 +08:00
lyswhut
37f9491d5e 修改布局 2020-05-04 01:19:49 +08:00
lyswhut
6aec78e153 调整代码顺序 2020-05-03 12:49:57 +08:00
lyswhut
685e34bc83 修复某些情况下可能导致的音源输出问题 2020-05-03 12:30:29 +08:00
lyswhut
fb828788df 更新图标 2020-05-03 12:12:49 +08:00
lyswhut
eff49af00c 改为构建 x64 pacman 包 2020-05-02 21:37:54 +08:00
lyswhut
7f02b85eb8 修复报错 2020-05-02 21:20:28 +08:00
lyswhut
1de84c66d6 更新配置 2020-05-02 21:15:34 +08:00
lyswhut
e382ccff9b 安装rpm 2020-05-02 20:57:48 +08:00
lyswhut
33e6202835 新增rpm、pacman包的打包 2020-05-02 20:42:53 +08:00
lyswhut
bd8b8d5b64 配置图标 2020-05-02 20:22:05 +08:00
lyswhut
db9dc18118 为appImage配置图标 2020-05-02 20:10:53 +08:00
lyswhut
7cc373c07f 更新版本 2020-05-02 20:03:07 +08:00
lyswhut
aa87640687 修复Linux版开启托盘无法退出的问题 2020-05-02 20:01:36 +08:00
lyswhut
6724912c73 修改布局 2020-05-02 19:52:43 +08:00
lyswhut
2cabe6e58e 修复多选机制问题 2020-05-02 18:49:24 +08:00
lyswhut
33383cd3bd 初步完善 2020-05-02 18:21:20 +08:00
lyswhut
78c5250f1b 发布v0.18.2 2020-05-02 12:44:00 +08:00
lyswhut
9797d98fac 修复开启托盘时,可能导致无法自动更新的问题 2020-05-02 12:43:00 +08:00
lyswhut
43c2dc3dec 发布v0.18.1 2020-05-02 12:22:47 +08:00
lyswhut
50e140e606 修复导入设置某些设置未立即生效的问题 2020-05-02 12:14:28 +08:00
lyswhut
3f88701751 win下的托盘图标使用更大的图片 2020-05-02 11:53:03 +08:00
lyswhut
7d89d53b1b 加长阅读时间 2020-05-01 22:26:13 +08:00
386 changed files with 29138 additions and 13808 deletions

View File

@@ -1,21 +0,0 @@
platform:
- x64
cache:
- node_modules
- '%APPDATA%\npm-cache'
- '%LOCALAPPDATA%\electron\Cache'
- '%LOCALAPPDATA%\electron-builder\Cache'
install:
- ps: Install-Product node 12 x64
- npm install
build_script:
- npm run publish:gh
test: off
branches:
only:
- master

View File

@@ -17,6 +17,9 @@
]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import"
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-modules-umd",
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties"
]
}

View File

@@ -16,7 +16,9 @@
"no-multiple-empty-lines": [1, {"max": 2}],
"comma-dangle": [2, "always-multiline"],
"standard/no-callback-literal": "off",
"prefer-const": "off"
"prefer-const": "off",
"no-labels": "off",
"node/no-callback-literal": "off"
},
"settings": {
"html/html-extensions": [".html", ".vue"]

View File

@@ -13,10 +13,13 @@ assignees: ''
- [ ] 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>)
**描述您想要的解决方案**
简洁明了地描述您要发生的事情。
<!-- 简洁明了地描述您要发生的事情。 -->
**描述您考虑过的替代方案**
对您考虑过的所有替代解决方案或功能的简洁明了的描述。
<!-- 对您考虑过的所有替代解决方案或功能的简洁明了的描述。 -->
**其他内容**
在此处添加有关功能请求的任何其他上下文或屏幕截图(直接把图片拖到编辑框即可添加图片)。
<!-- 在此处添加有关功能请求的任何其他上下文或屏幕截图(直接把图片拖到编辑框即可添加图片)。 -->

View File

@@ -13,7 +13,8 @@ assignees: ''
- [ ] 我已搜索issue列表(<https://github.com/lyswhut/lx-music-desktop/issues?utf8=✓&q=>)
**描述错误**
清楚简洁地说明错误是什么。
<!-- 清楚简洁地说明错误是什么。 -->
**重现**
重现行为的步骤:
@@ -22,15 +23,20 @@ assignees: ''
3.向下滚动到“ ....”
4.看到错误
**预期行为**
对您期望发生的事情的简洁明了的描述。
<!-- 对您期望发生的事情的简洁明了的描述。 -->
**截图**
如果适用,请添加屏幕截图以帮助解释您的问题(直接把图片拖到编辑框即可添加图片)。
<!-- 如果适用,请添加屏幕截图以帮助解释您的问题(直接把图片拖到编辑框即可添加图片)。 -->
**环境:**
  -操作系统及版本:[例如Windows 10 64位 18362.156]
  -软件安装包及版本:[例如:Windows 64位绿色版 1.0.0]
  -软件安装包及版本:[例如1.0.0 安装版]
**其他内容**
在此处添加有关该问题的任何其他上下文。
<!-- 在此处添加有关该问题的任何其他上下文。 -->

233
.github/workflows/beta-pack.yml vendored Normal file
View File

@@ -0,0 +1,233 @@
name: Build Beta
on:
push:
branches:
- beta
jobs:
Windows:
name: Windows
runs-on: windows-latest
steps:
- name: Check out git repository
uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Cache file
uses: actions/cache@v2
with:
path: |
node_modules
%APPDATA%\npm-cache
%LOCALAPPDATA%\electron\Cache
%LOCALAPPDATA%\electron-builder\Cache
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: npm install
- name: Build src code
run: npm run build:src
- name: Build Package Setup x64
run: npm run pack:win:setup:x64
- name: Upload Artifact Setup x64
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-x64-Setup
path: build/* x64 Setup.exe
- name: Build Package Setup x86
run: npm run pack:win:setup:x86
- name: Upload Artifact Setup x86
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-x86-Setup
path: build/* x86 Setup.exe
- name: Build Package Setup arm64
run: npm run pack:win:setup:arm64
- name: Upload Artifact Setup arm64
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-arm64-Setup
path: build/* arm64 Setup.exe
- name: Build Package Setup x86_64
run: npm run pack:win:setup:x86_64
- name: Upload Artifact Setup x86_64
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-x86_64-Setup
path: build/*x86_64 Setup.exe
- name: Build Package 7z x64
run: npm run pack:win:7z:x64
- name: Upload Artifact 7z x64
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-win_x64-green
path: build/*win_x64 green.7z
- name: Build Package 7z x86
run: npm run pack:win:7z:x86
- name: Upload Artifact 7z x86
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-win_x86-green
path: build/*win_x86 green.7z
- name: Build Package 7z arm64
run: npm run pack:win:7z:arm64
- name: Upload Artifact 7z arm64
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-win_arm64-green
path: build/*win_arm64 green.7z
Mac:
name: Mac
runs-on: macos-latest
steps:
- name: Check out git repository
uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Cache file
uses: actions/cache@v2
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: npm install
- name: Build src code
run: npm run build:src
- name: Build Package dmg
run: |
npm run pack:mac:dmg
npm run pack:mac:dmg:arm64
env:
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
- name: Upload Artifact dmg
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-mac-dmg
path: |
build/*.dmg
!build/*-arm64.dmg
- name: Upload Artifact dmg
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-mac-dmg-arm64
path: build/*-arm64.dmg
Linux:
name: Linux
runs-on: ubuntu-latest
steps:
- name: Install package
run: sudo apt-get install -y rpm libarchive-tools
- name: Check out git repository
uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Cache file
uses: actions/cache@v2
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: npm install
- name: Build src code
run: npm run build:src
- name: Build Package deb x64
run: npm run pack:linux:deb:x64
- name: Upload Artifact deb x64
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-deb-x64
path: build/* x64.deb
- name: Build Package deb x86
run: npm run pack:linux:deb:x86
- name: Upload Artifact deb x86
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-deb-x86
path: build/* x86.deb
- name: Build Package deb arm64
run: npm run pack:linux:deb:arm64
- name: Upload Artifact deb arm64
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-deb-arm64
path: build/* arm64.deb
- name: Build Package deb armv7l
run: npm run pack:linux:deb:armv7l
- name: Upload Artifact deb armv7l
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-deb-armv7l
path: build/* armv7l.deb
- name: Build Package x64 appImage
run: npm run pack:linux:appImage
- name: Upload Artifact x64 appImage
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-x64-appImage
path: build/* x64.AppImage
- name: Build Package x64 rpm
run: npm run pack:linux:rpm
- name: Upload Artifact x64 rpm
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-x64-rpm
path: build/* x64.rpm
- name: Build Package x64 pacman
run: npm run pack:linux:pacman
- name: Upload Artifact x64 pacman
uses: actions/upload-artifact@v2
with:
name: lx-music-desktop-x64-pacman
path: build/* x64.pacman

136
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,136 @@
name: Build
on:
push:
branches:
- master
jobs:
Windows:
name: Windows
runs-on: windows-latest
steps:
- name: Check out git repository
uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Cache file
uses: actions/cache@v2
with:
path: |
node_modules
%APPDATA%\npm-cache
%LOCALAPPDATA%\electron\Cache
%LOCALAPPDATA%\electron-builder\Cache
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: npm install
- name: Build src code
run: npm run build:src
- name: Release package
run: |
npm run publish:win:setup:x64:always
npm run publish:win:setup:x86
npm run publish:win:setup:arm64
npm run publish:win:setup:x86_64
npm run publish:win:7z:x64
npm run publish:win:7z:x86
npm run publish:win:7z:arm64
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }}
Mac:
name: Mac
runs-on: macos-latest
steps:
- name: Check out git repository
uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Cache file
uses: actions/cache@v2
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: npm install
- name: Build src code
run: npm run build:src
- name: Release package
run: |
npm run publish:mac:dmg:always
npm run publish:mac:dmg:arm64
env:
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }}
Linux:
name: Linux
runs-on: ubuntu-latest
steps:
- name: Install package
run: sudo apt-get install -y rpm libarchive-tools
- name: Check out git repository
uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Cache file
uses: actions/cache@v2
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: npm install
- name: Build src code
run: npm run build:src
- name: Release package
run: |
npm run publish:linux:deb:x64:always
npm run publish:linux:deb:x86
npm run publish:linux:deb:arm64
npm run publish:linux:deb:armv7l
npm run publish:linux:appImage
npm run publish:linux:rpm
npm run publish:linux:pacman
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }}

7
.ncurc.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
upgrade: true,
reject: [
'vue-loader'
// 'eslint-config-standard'
]
}

View File

@@ -1,43 +0,0 @@
language: node_js
node_js: 12
matrix:
include:
- os: osx
osx_image: xcode10.2
env:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
- os: linux
dist: trusty
cache:
directories:
- node_modules
- $HOME/.cache/electron
- $HOME/.cache/electron-builder
- $HOME/.npm/_prebuilds
notifications:
email: false
script:
- node --version
- npm --version
- |
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
- dev

19
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"path-intellisense.mappings": {
"@main/*": "${workspaceFolder}/src/main/*",
"@renderer/*": "${workspaceFolder}/src/renderer/*",
"@lyric/*": "${workspaceFolder}/src/renderer-lyric/*",
"@static/*": "${workspaceFolder}/src/static/*",
"@common/*": "${workspaceFolder}/src/common/*",
},
"i18n-ally.localesPaths": [
"src/renderer/lang"
],
"i18n-ally.displayLanguage": "zh-cn",
"i18n-ally.sourceLanguage": "zh-cn",
"i18n-ally.namespace": true,
"i18n-ally.translate.engines": ["google-cn", "google"],
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
"i18n-ally.keystyle": "flat",
"i18n-ally.sortKeys": true
}

View File

@@ -6,6 +6,468 @@ 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/).
## [1.12.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.12.1...v1.12.2) - 2021-08-11
### 修复
- 修复播放下载列表的歌曲时切歌的问题
- 修复播放下载列表的歌曲时歌词无法显示的问题
- 修复下载列表稍后播放功能无效的问题
- 修复同步服务器启动失败时,关闭同步服务不会清空失败信息的问题
## [1.12.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.12.0...v1.12.1) - 2021-08-08
### 修复
- 修复随机播放下无法切歌的问题
## [1.12.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.11.0...v1.12.0) - 2021-08-08
### 新增
- 新增局域网同步功能实验性首次使用前建议先备份一次列表此功能需要配合PC端使用移动端与PC端处在同一个局域网路由器的网络下时可以多端实时同步歌曲列表使用问题请看"常见问题"。
### 优化
- 添加播放器对系统媒体控制与显示的兼容处理现在在windows下的锁屏界面可以正确显示当前播放的音乐信息及切换歌曲了
### 修复
- 修复导入kg歌单最多只能加载100、500首歌曲的问题。注现在可以加载1000+首歌曲的歌单但出于未知原因会导致部分歌曲无法加载可能是无版权导致的目前酷狗码仍然最多只能加载500首歌
- 修复某些情况下所显示的歌词、封面图片与当前正在播放的歌曲不一致的问题
## [1.11.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.10.2...v1.11.0) - 2021-07-18
### 新增
- 添加 win arm64 架构的安装包构建
- 新增“添加歌曲到列表时的位置”设置,可选项为列表的“顶部”与“底部”
### 优化
- 优化网络请求,尝试去解决无法连接服务器的问题
- 优化mg源打开歌单的链接兼容
### 修复
- 修复mg源搜索失效的问题
### 移除
- 因wy源的歌单列表已没有“最新”排序的选项所以现跟随移除wy源歌单列表按“最新”排序的按钮
### 变更
- 添加歌曲到列表时从原来的底部改为顶部,若你想要将你的列表歌曲顺序反转以适应这一变更,可先按住`shift`键的情况下点击列表的最后一首歌然后再点击列表的第一首歌完成倒序选中最后随便右击列表的任意一首歌在弹出的菜单中选择调整顺序在弹出框输入1后确定即可反转列表。
若你想要恢复原来的行为则可以去更改“添加歌曲到列表时的位置”设置项。
### 其他
- 更新electron到v13.1.7
## [1.10.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.10.1...v1.10.2) - 2021-05-25
### 修复
- 修复企鹅音乐搜索歌曲没有结果的问题
## [1.10.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.10.0...v1.10.1) - 2021-05-25
### 修复
- 修复企鹅音乐搜索歌曲没有结果的问题
- 修复播放在空的歌单列表点击播放全部时报错的问题
## [1.10.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.9.0...v1.10.0) - 2021-05-19
lx music移动端已经发布了使用习惯仍跟桌面版一样不过功能、界面仍比较简单有兴趣的可以去体检一下项目地址
https://github.com/lyswhut/lx-music-mobile#readme
### 新增
- 排行榜界面添加播放、收藏整个排行榜功能,可以右击排行榜名字后,在弹出的右键菜单中使用。注:收藏、播放存在分页的排行榜时需等待操作完成后才能切换排行榜,不然会导致操作中断。
- 新增Mac arm64位dmg包的构建
### 修复
- 修复全局快捷键对桌面歌词无效的问题
- 修复快捷键设置框内的提示问题
- 修复在当前正常播放的列表中使用稍后播放功能时,播放完后稍后播放的歌曲后不会恢复原来播放位置播放的问题
- 修复kw部分歌单无法打开的问题
- 修复wy源的歌曲音质匹配问题
- 修复mg源歌单标签、排行榜歌曲列表无法加载的问题
- 修复了一个歌曲下载失败时不会跳过任务的问题
### 其他
- 更新 Electron 到 12.0.8
## [1.9.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.8.2...v1.9.0) - 2021-04-24
### 新增
- 新增启动参数`-dhmkh`此参数将禁用Chromium的Hardware Media Key Handling特性用于解决漫步者部分型号耳机与本程序冲突导致耳机意外关机的问题
- 新增Windows arm64位免安装版的构建
- 新增黑色皮肤“黑灯瞎火”,有关于皮肤配色的建议欢迎反馈
- 新增自动换源下载功能默认关闭当无法从歌曲的原始源下载时将尝试切换到其他源下载此功能不100%保证换源后的歌曲版本与原版一致
### 优化
- 程序启动时对数据文件做读取校验数据出现损坏时自动备份损坏的数据若出现数据读取错误的弹窗并出现我的列表丢失时可到GitHub或加群反馈
- 当设置-代理启用,但主机地址为空的时,将不再使用代理配置进行网络连接,并且在离开设置界面时自动禁用代理
- 优化歌曲自动换源匹配
- 分离歌词与歌曲列表信息的保存,以减小列表列表文件损坏的几率
- 兼容打开咪咕移动端分享的歌单链接,添加打开歌单的信息显示
### 修复
- 修复备份与恢复功能在恢复数据时某些设置不立即生效的问题
- 修正设置页“搜索设置”部分内容的缩进显示问题
- 修复正在播放“稍后播放”的歌曲时,对“稍后播放”前播放的列表进行添加、删除操作会导致切歌的问题
## [1.8.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.8.1...v1.8.2) - 2021-03-09
### 修复
- 修复歌曲ID存储变更导致酷狗图片获取失败的问题
- 修复收藏的在线列表id迁移保存出错的问题
## [1.8.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.8.0...v1.8.1) - 2021-03-07
### 修复
- 修复歌词翻译的主题颜色适配问题
## [1.8.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.7.1...v1.8.0) - 2021-03-07
### 新增
- 新增设置-其他-列表缓存信息清理功能,注:此功能一般情况下不要使用
- 新增启动参数`-play`可以在启动软件时播放指定歌单使用方法看Readme.md的"启动参数"部分
- 新增逐字歌词播放,默认开启,可到设置界面关闭,注:本功能目前仅对酷狗源的歌曲有效
- 新增自定义源功能,源编写规则可以去常见问题查看
### 优化
- 允许播放除了搜索列表以外的所有歌曲,即原来没有播放按钮或者灰色的歌曲都可以去尝试点击播放。注:该功能的原理是尝试自动切换到其他源播放,所以不一定会播放成功,特别是对于那些独家的资源
- 优化单首歌曲的“添加到列表”弹窗歌曲列表状态的显示;现在在收藏单首歌曲时,若列表存在本歌曲则列表名字将变成灰色不可点击状态。总的来说,在添加单首歌曲时若列表名是灰色,则证明当前歌曲已在那个列表中
- 将歌词翻译放到原文的下方,同时新增当前播放翻译的高亮功能
### 移除
- 移除虾米源。注:虽然已移除该源,但仍可尝试去播放之前添加的歌曲,虽然不一定会成功
### 修复
- 修复音乐搜索列表的稍后播放功能无效的问题
- 修复搜索列表双击不支持播放的源时会导致切歌的问题
- 修复歌单列表加载失败时无法进入歌单打开界面的问题
- 修复mg源歌单列表无法加载的问题
- 修复kg跳转到官方歌曲详情页的歌曲无法播放的问题
- 修复我的列表的歌曲添加到其他列表时不排除当前列表的问题
- 修复在下载列表右击未下载完成的歌曲弹出的右击菜单中没有开始下载选项的问题
### 变更
- 歌词翻译显示功能修改为默认关闭,注:此变更仅影响首次安装软件的用户
### 其他
- 更新electron到v9.4.4
## [1.7.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.7.0...v1.7.1) - 2021-01-30
### 修复
- 修复非透明模式下右侧滚动条无法拖动的问题
- 修复MAC下xm音乐滑块验证问题
## [1.7.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.6.1...v1.7.0) - 2021-01-30
### 新增
- 搜索界面新增搜索状态的提示
- 新增“稍后播放”功能,可在歌曲列表右键菜单使用
- 新增“记住播放进度”功能的控制,该功能默认不再开启,可到播放设置-记住播放进度开启
### 优化
- 优化播放歌曲换源匹配
- 优化设置界面设置项的展示
### 修复
- 修复快速切换歌曲时, 会出现播放的歌曲和界面展示的歌曲不一致的问题
- 修复了一个由版本更新日志显示导致的潜在远程代码执行攻击漏洞该漏洞影响v1.6.1及之前的所有版本,请务必更新到最新版本
- 修复xm搜索源验证问题
### 其他
- 更新electron到9.4.2
## [1.6.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.6.0...v1.6.1) - 2021-01-13
### 优化
- 改进自动换源时的歌曲匹配
### 修复
- 修复某些情况下自动换源的时间过长时会终止换源自动切歌的问题
- 修复自动换源导致的搜索列表每页变成10条数据的问题
- 降级electron到9.3.3修复部分系统没有声音的问题
## [1.6.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.5.0...v1.6.0) - 2021-01-10
### 新增
- 我的列表右键菜单新增列表排序功能,可调整单曲、多选后的歌曲的顺序。注意:多选排序还将会按照选中歌曲时的顺序排序
- 添加鼠标提示的自动关闭功能鼠标长时间目前是10秒不动时鼠标提示将会自动关闭
- 添加鼠标指向歌曲封面的提示(对于进度条左边的歌曲封面,你可能不知道的操作->右击在“我的列表”定位当前播放的歌曲)
- 隐藏播放详情页按钮添加快速隐藏详情页提示(你可能不知道的操作->在播放详情页内的任意非窗口可拖动区域右键双击可以快速隐藏详情页)
- 添加桌面歌词字体、透明度调整按钮微调提示(你可能不知道的操作->对于字体、透明度可右击微调)
- 我的列表右键菜单添加搜索当前歌曲功能
- 新增`-dha`参数添加此启动参数将禁用硬件加速启动Disable Hardware Acceleration窗口显示有问题时可以尝试添加此参数启动Linux系统的界面显示有问题时可尝试添加此参数启动若不行可尝试添加`-dt`参数启动
- 新增播放自动换源功能~
### 变更
- `-nt`参数更名为`-dt`Disable Transparent目前原来的`-nt`参数仍然可用,但将在后续的版本中移除
### 修复
- 修复恢复上次播放的歌曲时在随机播放模式下不把恢复播放的歌曲放入已播放队列的问题(该问题会导致随机模式下会导致未播放完整个列表前就会再次随机到该歌曲,以及无法通过上一曲切回该歌曲)
- 修复音乐嵌入的封面在 Mac 系统无法显示的问题
- 修复`-dt`(原来的`-nt`)启动参数不真正生效的问题
## [1.5.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.4.1...v1.5.0) - 2020-12-13
### 新增
- 直接从歌单详情收藏的列表新增同步功能。注意:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表
### 优化
- 优化软件启动时恢复上一次播放的歌曲进度功能
### 修复
- 修复MAC平台上下载歌曲封面嵌入无法显示的问题
- 修复MAC平台首次运行软件最小化、关闭控制按钮默认在右边的问题
- 修复酷狗源的某些歌曲没有专辑字段导致的列表加载失败问题
- 修复某些酷狗源歌单链接无法打开的问题
## [1.4.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.4.0...v1.4.1) - 2020-11-25
### 修复
- 修复有歌词翻译与无歌词的音乐间切换会导致歌词翻译残留显示的问题
- 修复歌曲URL过期时等待刷新URL的自动切换歌曲时间间隔太短的问题
- 修复某些电脑上的某些歌曲没有声音的问题升级Electron9.3.4导致的现降级到9.3.3
## [1.4.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.3.0...v1.4.0) - 2020-11-21
### 新增
- 托盘菜单新增显示、隐藏主界面选项为Linux、MAC版添加托盘菜单
- 新增播放进度信息保存
### 优化
- 移除kg源的歌词文件开头的空白字符串
### 修复
- 修复专辑图片无法嵌入的问题
- 修复播放状态栏切换“上一首”歌曲按钮提示错误的问题
- 修复移动单首歌曲时,如果目标列表存在该歌曲,会导致将源列表与目标列表里的目标歌曲移除
- 修复kg源歌曲信息带有单引号等特殊字符被转义的问题
## [1.3.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.2.2...v1.3.0) - 2020-11-01
### 新增
- 播放详情页新增歌曲评论加载显示(某些平台暂不支持显示子评论)
### 优化
- 修改播放详情页的歌曲图片的显示效果
### 修复
- 修复小芸源音乐搜索结果最多只有20条搜索结果的问题
## [1.2.2](https://github.com/lyswhut/lx-music-desktop/compare/v1.2.1...v1.2.2) - 2020-10-18
### 修复
- 降级 Electron 到 9.x.x 版本修复 Linux 版桌面歌词窗口变白的问题
## [1.2.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.2.0...v1.2.1) - 2020-10-18
### 优化
- Linux版的软件界面默认使用圆角与阴影顺便修复了桌面歌词窗口变白的问题已在Ubuntu 18.10测试正常,若显示异常可尝试添加`-nt`参数启动
### 修复
- 修复聚合搜索的分页问题
- 修复代理输入框输入的内容不生效的问题
## [1.2.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.1.1...v1.2.0) - 2020-09-30
提前祝大家中秋&国庆快乐~
### 新增
- 播放控制栏开启/关闭桌面歌词按钮 新增右击按钮时锁定/解锁桌面歌词功能
### 优化
- 优化我的列表滚动条位置的保存逻辑
- 更新设置-备份与恢复功能的描述
- 优化软件内鼠标悬停的提示界面
### 修复
- 修复桌面歌词窗口不允许拖出桌面之外的位置计算偏移Bug
- 修复网易云KTV嗨榜无法加载的问题
- 修复初始化搜索历史列表功能
- 修复重启软件后试听列表与收藏列表无法恢复上次的滚动位置的问题
- 修复歌曲封面无法嵌入的Bug
- 修复酷狗歌词格式问题
- 修复关闭切换动画时从搜索候选列表点击内容无效的问题
### 其他
- 更新 Electron 到 v10.1.3
## [1.1.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.1.0...v1.1.1) - 2020-09-19
### 修复
- 修复某些情况下桌面歌词不会播放的问题
## [1.1.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.0.1...v1.1.0) - 2020-09-18
### 新增
- 在歌单详情界面新增播放当前歌单按钮、收藏歌单按钮,注:播放歌单不会将歌曲添加到试听列表
- 新增`不允许将歌词窗口拖出主屏幕之外`的设置项,默认开启,在连接多个屏幕时想要拖动到其他屏幕时可关闭此设置
- 新增大部分平台的歌词翻译,感谢 @InoriHimea 提供的[krc解码算法](https://github.com/lyswhut/lx-music-desktop/issues/296#issuecomment-683285784)
- 新增`显示歌词翻译`设置,默认开启,仅支持某些平台,注:无论该设置是否开启,嵌入或下载歌词时都不会带上翻译
- 新增`显示切换动画`设置,默认开启,关闭时将基本禁用软件内的所有切换动画
- 播放状态栏新增桌面歌词的开关、播放模式的切换、歌曲的收藏按钮Thanks to @andylow for the [icon](https://github.com/lyswhut/lx-music-desktop/pull/309)!
### 修复
- 修复使用全局快捷键还原窗口时,窗口没有获取焦点的问题
- 修复我的列表搜索对最后一个字符的匹配问题
- 修复窗口在`较小`模式下最小化/关闭按钮不居中的问题
### 优化
- 桌面歌词当前播放行改为上下居中
- 为区分静音状态,静音时音量条会变淡,调整音量条时将会取消静音
- 优化随机播放机制,现在通过`下一曲`切换歌曲时,直到播放完整个列表之前将不会再随机到之前播放过的歌曲,并且通过`上一曲`可以正确播放上一首歌曲
- 当下载目录没有写入权限时将显示没有写入权限的提示
### 移除
- 移除默认的全局声音媒体快捷键接管
- 移除对百度音乐的支持因百度音乐原有的大部分API失效而且该平台相对其他平台来说音乐太少了可有可无以后再看情况恢复
### 其他
- 更新electron到 10.1.2
## [1.0.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.0.0...v1.0.1) - 2020-07-25
### 优化
- 对我的列表歌曲搜索结果进行相似度排序
### 修复
- 修复在 Windows 系统下缩放比非100%时,拖动桌面歌词会自动加大桌面歌词窗口的问题
## [1.0.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.18.2...v1.0.0) - 2020-07-24
### 新增
- 新增`rpm``pacman`包的构建(未测试可用性)
- 新增因系统音频设备列表改变导致的当前音频输出设备改变时是否暂停播放的设置,默认关闭
- 新增歌曲列表右击菜单
- 新增自定义列表,创建列表的按钮在表头`#`左侧,鼠标移上去才会显示;编辑列表名字时,按`ESC`键可快速取消编辑,按回车键或使输入框失去焦点即可保存列表名字,右击列表可编辑已创建的列表,“试听列表”与“我的收藏”两个列表固定不可编辑
- 改变排行榜布局,新增更多排行榜
- 新增我的列表右键菜单复制歌曲名选项
- 新增桌面歌词,默认关闭,可到设置或者托盘菜单开启(建议使用全局快捷键控制);调整字体大小、透明度时,鼠标左击按钮正常调整,右击微调;**Windows 7未开启Aero效果时桌面歌词会有问题**详情看常见问题解决Linux版桌面歌词有问题以后再尝试优化
- 新增“清热板蓝”皮肤
- 新增软件最小化、关闭按钮位置设置MAC版默认为左边非MAC为右边不想用默认的可到设置修改
- 新增快捷键设置,软件内快捷键默认开启,全局快捷键默认关闭(注:若想开启蓝牙耳机切歌需开启全局快捷键,当快捷键被中划线划掉时,表示当前快捷键被其他程序占用导致注册失败)
- 新增首次运行时自动根据当前系统使用的语言设置软件显示的语言
- 新增歌词区域的触摸板、鼠标滚轮等对歌词滚动的支持
- 为了方便支持正版资源,歌曲列表右击菜单新增跳转到当前歌曲源官方详情页菜单(注意:在本版本之前添加的虾米源歌曲无法跳转详情页,需要移除后重新搜索添加)
- 新增我的列表内歌曲搜索,在我的列表按`ctrl+f`将显示搜索框;鼠标滑过或键盘上下方向键选择搜索结果;鼠标点击或按回车键定位选中的歌曲;按`ctrl`键的情况下鼠标点击或按回车键确认定位歌曲时,将会在定位歌曲结束后播放该歌曲(搜索框激活的情况下按`esc`可快速清空搜索框/关闭搜索框)
- 新增托盘图标样式设置,可到设置-其他切换
- 新增开关下载功能控制,默认关闭,可到设置-下载设置开启
- 新增将歌词嵌入音频文件中,默认关闭,可到设置-下载设置开启
- 新增当列表文件损坏时对损坏文件的备份,若出现该情况可打开`%HOMEPATH%\AppData\Roaming\lx-music-desktop`找到`playList.json.bak`尝试手动修复列表文件,列表文件以`JSON`格式存储
- 新增在歌单详情列表按退格Backspace键可快速返回歌单列表
### 优化
- 改进歌曲切换时的歌词滚动效果
- 优化批量添加、删除播放列表的歌曲操作逻辑,大幅提升批量添加、删除列表歌曲的流畅度
- 改进歌单列表展示
- **改进聚合搜索的搜索结果排序**,修复当某些源搜索失败时导致其他源无法显示结果的问题,现在聚合搜索已达到最初的理想效果,为了使排序更精确,**建议同时输入 歌曲名 歌手名 搜索**(歌曲名在前歌手名在后),欢迎体验~
- 压缩备份数据文件大小
### 修复
- 修复按住`Ctrl`等键触发多选机制时不松开按键的情况下切换到其他窗口后再松开按键,这时切回软件不按按键都处在多选模式的问题
- 修复Linux版开启托盘无法退出的问题
- 修复某些情况下可能导致的音源输出问题
- 修复某些情况下无法开始下载任务的问题
- 修复 tab 组件边框溢出问题
- 修复错误更新试听列表外的歌曲时间的问题
- 修复网易音乐源歌单、排行榜歌曲列表加载显示的数量与实际不对的问题同时支持加载大于1000首歌的歌单歌曲大于1000首会分页注意目前软件一下子显示太多歌曲时会卡顿不建议在同一列表内添加太多歌曲
- 修复歌曲图片链接没有扩展名的情况下无法嵌入图片的问题
- 修复无法检测最新版本时弹窗提示的显示
- 修复某些情况下从托盘还原窗口后无法操作的问题
- 修复Linux下无法`ctrl+a`全选的问题
- 修复主题背景图片覆盖不全的问题
- 修复聚合搜索音源标签的皮肤配色问题
### 更变
- 修改设置-列表-是否显示歌曲源的默认设置为选中(该变更不影响之前的设置)
- 移除浮动按钮,现在在多选完成后可鼠标右击随意一项在弹出的右键菜单中进行原来悬浮按钮的操作
- 为了避免出现误会,现在下载弹窗中不可用的音质将直接隐藏
- 更改初始设置的搜索设置为聚合搜索(该变更不影响之前的设置)
### 其他
- 更新 Electron 到 9.1.1
## [0.18.2](https://github.com/lyswhut/lx-music-desktop/compare/v0.18.1...v0.18.2) - 2020-05-02
### 修复
- 修复开启托盘时,可能导致无法自动更新的问题
## [0.18.1](https://github.com/lyswhut/lx-music-desktop/compare/v0.18.0...v0.18.1) - 2020-05-02
### 优化
- win下的托盘图标使用更大的图片
- 加长软件协议的强制停留时间
### 修复
- 修复导入设置某些设置未立即生效的问题
## [0.18.0](https://github.com/lyswhut/lx-music-desktop/compare/v0.17.0...v0.18.0) - 2020-05-01
### 新增

353
FAQ.md
View File

@@ -2,18 +2,35 @@
在阅读本常见问题后仍然无法解决你的问题请提交issue或者加企鹅群`830125506`反馈(无事勿加,入群先看群公告),反馈时请**注明**已阅读常见问题!
## 软件为什么没有桌面歌词与自定义列表功能
## ~~软件为什么没有桌面歌词与自定义列表功能~~
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用,因此无桌面、界面歌词,不可自定义列表等功能
洛雪音乐的最初定位不是作为播放器开发的,它主要用于**查找歌曲**,软件的播放功能仅用于试听,不建议用作为常用播放器使用。
## 歌曲无法试听与下载
该问题解决顺序如下:
### 所有歌曲都提示 `请求异常😮,可以多试几次,若还是不行就换一首吧。。。`
尝试更换网络,如切换到移动网络,若移动网络还是不行则尝试开关下手机的飞行模式后再试,<br>
若使用家庭网络的话可尝试将光猫断电5分钟左右再通电联网后播放。
### 提示 `getaddrinfo EAI_AGAIN ...` 或 `无法连接到服务器`
尝试在在浏览器打开这个地址`http://ts.tempmusic.tk`浏览器显示404是正常的如果不是404那就证明所在网络无法访问接口服务器。
若网页无法打开或打开来不是404则可能是DNS的问题可以尝试以下办法
1. 将DNS改成自动获取试试改完可能需要清理下系统DNS缓存才生效
2. 手动把DNS改一下不要用360的DNS可以把DNS改成`114.114.114.114``8.8.8.8`改完可能需要清理下系统DNS缓存才生效
改完DNS后可能需要重启软件才生效
### 通用解决方法
尝试按以下顺序解决:
1. 尝试更新到最新版本
2. 尝试切换其他歌曲(或直接搜索该歌曲),若全部歌曲都无法试听与下载则进行下一步
3. 尝试到 设置-音乐来源 切换到其他接口
4. 尝试切换网络,比如用手机开热点(目前存在某些网络无法访问接口服务器的情况
4. 尝试切换网络,比如用手机开热点(所有歌曲都提示请求异常时可通过此方法解决,或等一两天后再试
5. 若还不行请到这个链接查看详情:<https://github.com/lyswhut/lx-music-desktop/issues/5>
6. 若没有在第5条链接中的第一条评论中看到接口无法使用的说明则应该是你网络无法访问接口服务器的问题如果接口有问题我会在那里说明。
@@ -23,6 +40,134 @@
1. 将DNS改成自动获取试试
2. 手动把DNS改一下不要用360的DNS可以把DNS改成`114.114.114.114``8.8.8.8`
## 列表多选
从v0.18.0起,列表多选需要键盘配合,想要多选前需按下`Shift``Ctrl`键然后再鼠标点击想要选中的内容即可触发多选机制,其中`Shift`键用于连续选择,`Ctrl`键用于不连续选择,`Ctrl+a`用于快速全选。
- 例子一想要选中1-5项则先按下`Shift`键后,鼠标点击第一项,再点击第五项即可完成选择;
- 例子二想要选中1项与第3项则先按下`Ctrl`键后,鼠标点击第一项,再点击第三项即可完成选择;
- 例子三:想要选中当前列表的全部内容,键盘先按下`Ctrl`键不放,然后按`a`键,即可完成选择。
`Shift``Ctrl`选择时,鼠标点击未选中的内容会将其选中,点击已选择的内容会将其取消选择,若想全部取消选择,在不按`Shift``Alt`键的情况下,随意点击列表里的一项内容即可全部取消选择。(P.S`Ctrl`键对应Mac OS上的`Command`键)
注:选完后可用鼠标右击弹出右键菜单操作已选的内容
## 播放整个歌单或排行榜
播放在线列表内的歌曲需要将它们都添加到我的列表才能播放,你可以全选列表内的歌曲然后添加到现有列表或者新创建的列表,然后去播放该列表内的歌曲。
## 无法打开外部歌单
不支持垮源打开歌单,请**确认**你需要打开的歌单平台是否与软件标签所写的**歌单源**对应(不一样的话请通过右上角切换歌单源);<br>
对于分享出来的歌单若打开失败可尝试先在浏览器中打开后再从浏览器地址栏复制URL地址到软件打开<br>
或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br>
注:网易源的“我喜欢”歌单无法在未登录的情况下打开,所以你需要手动创建一个歌单后将“我喜欢”里的歌曲移动到该歌单打开
## 更新已收藏的在线歌单
该功能仅对直接从歌单详情页点“收藏”按钮收藏的歌单有效,可右击已收藏的列表名从弹出的菜单中选择“更新”使用该功能,
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
## 同步功能的使用(实验性,首次使用前建议先备份一次列表)
**注意:由于同步传输时的数据是明文传输,请在受信任的网络下使用此功能!**<br>
此功能需要配合移动端使用PC端与移动端处在同一个局域网路由器的网络下时可以多端实时同步歌曲列表使用方法
1. 在PC端的设置-数据同步开启同步功能(这时如果出现安全软件、防火墙等提示网络连接弹窗时需要点击允许)
2. 在移动端的设置-同步-同步服务器地址输入PC端显示的同步服务器地址如果显示可以多个则输入与**移动端上显示的本机地址**最相似的那个端口号与PC端的同步端口一致**输入完毕后需要按一下键盘上的回车键使输入的内容生效**
3. 输入完这两项后点击“启动同步”
4. 若连接成功对于首次同步时若两边的设备的列表不为空则PC端会弹出选择列表同步方式的弹窗同步方式的说明弹窗下面有介绍
对于连接同步服务失败的可能原因:
- 此功能需要PC端与移动端都连接在同一个路由器下的网络才能使用
- 路由器若开启了AP隔离则此功能无法使用
- 检查防火墙是否拦截了PC端的服务端口
## 界面异常(界面显示不完整)
### Windows 7 下界面异常
由于软件默认使用了透明窗口根据Electron官方文档的[说明](https://electronjs.org/docs/api/frameless-window#%E5%B1%80%E9%99%90%E6%80%A7)
> 在 windows 操作系统上, 当 DWM 被禁用时, 透明窗口将无法工作。
因此,当 win7 没有使用**AERO**主题时界面将会显示异常开启AERO的方法请自行百度`win7开启aero效果`(开启后可看到任务栏变透明)。<br>
`0.14.0`版本起不再强制要求开启透明效果,若你实在不想开启(若非电脑配置太低,墙裂建议开启!),可通过添加运行参数`-dt`来运行程序即可,例如:`.\lx-music-desktop.exe -dt`,添加方法可自行百度“给快捷方式加参数”,该参数的作用是用来控制程序是否使用非透明窗口运行。
注:启用**AERO**主题后,若软件出现黑边框,则重启软件即可恢复正常。
对于一些完全无法正常显示界面、开启了AERO后问题仍未解决的情况请阅读下面的 **软件启动后,界面无法显示** 解决。
### Linux 下界面异常
根据Electron里issue的[解决方案](https://github.com/electron/electron/issues/2170#issuecomment-736223269)<br>
若你遇到透明问题可尝试添加启动参数 `-dha` 来禁用硬件加速,例如:`.\lx-music-desktop.exe -dha`
v1.6.0及之后的版本才支持`-dha`参数
## 软件启动后,界面无法显示
对于软件启动后,可以在任务栏看到软件,但软件界面在桌面上无任何显示,或者整个界面偶尔闪烁的情况。<br>
原始问题看:<https://github.com/electron/electron/issues/19569#issuecomment-522231083><br>
解决办法:下载`.NET Framework 4.7.1`或**更高**版本安装即可(建议安装最新版,若安装过程中遇到问题可尝试自行百度解决)。<br>
微软官方下载地址:<https://dotnet.microsoft.com/download/dotnet-framework><br>
下载`Runtime(运行时)`版即可,安装完成后可能需要重启才生效。
若还是不行可尝试以下操作:
- 更新显卡驱动
- 添加启动参数`-dha`运行(添加的方法请自行百度“给快捷方式加参数”)
- 尝试将绿色版的软件放在**桌面**或**我的文档**运行
## 安装版安装失败,提示安装程序并未成功地运行完成
对于部分电脑出现安装失败的问题,可以做出以下尝试:
- 若你之前可以安装成功,但现在安装失败,就去**控制面板-程序和功能**或用第三方卸载工具看下有没有之前的版本残留,若同时在不同路径下安装了多个版本就可能会出现该问题,这种情况卸载掉所有版本重新安装即可
- 清理安装路径下的残留文件
- 清理注册表(建议用清理工具清理)
## 软件无法联网
软件的排行榜、歌单、搜索列表**都**无法加载:
- 检查是否在设置界面开启了代理(当代理乱设置时软件将无法联网)
- 检查软件是否被第三方软件/防火墙阻止联网
## 桌面歌词显示异常
### Windows 7 系统桌面歌词显示异常
Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的 **Windows 7 下界面异常** 方法解决。
### MAC OS 系统、桌面歌词有残留阴影
此问题似乎是Electron的Bug翻阅electron的issue列表发现该Bug以存在很久了遗憾的是没有一直都没有修复由于我没有装MAC平台的电脑没法重现就没再去electron提issue更多信息看
- <https://github.com/electron/electron/issues/21173>
- <https://github.com/electron/electron/issues/14304>
### Linux 系统下桌面歌词窗口异常
`v1.2.1`以前的版本在 Ubuntu 18.10 下第一次开启桌面歌词时歌词窗口会变白,需要关闭后再开启,
`v1.2.1`及之后的版本已修复该问题。
其他 Linux 系统未测试,如有异常也是意料之中,目前不打算去处理 Linux 平台的桌面歌词问题,但你可以尝试按照`Linux 下界面异常`的解决方案去解决。
## 歌曲下载失败,提示 `ENOENT: no such file or directory, mkdir`
更换下载歌曲目录即可解决(一般是设置的歌曲下载目录没有读写权限导致的)。
## 使用软件时导致耳机意外关机
据反馈,漫步者部分型号的耳机与本软件一起使用时将会导致耳机意外关机,
详情看:<https://github.com/lyswhut/lx-music-desktop/issues/457>
若出现该问题可尝试添加`-dhmkh`启动参数解决启动参数添加方法请自行百度“windows给快捷方式添加启动参数”。
## 软件安装包说明
软件发布页及网盘中有多个类型的安装文件,以下是对这些类型文件的说明:
@@ -45,37 +190,11 @@
注意:**绿色版**的软件自动更新功能**不可用**,建议使用安装版!!<br>
注意:**Mac版**、**Linux deb**版不支持自动更新!
## Windows 7 下界面异常(界面显示不完整)
## 更新已收藏的在线歌单
由于软件默认使用了透明窗口根据Electron官方文档的[说明](https://electronjs.org/docs/api/frameless-window#%E5%B1%80%E9%99%90%E6%80%A7)
> 在 windows 操作系统上, 当 DWM 被禁用时, 透明窗口将无法工作。
该功能仅对直接从歌单详情页点“收藏”按钮收藏的歌单有效,可右击已收藏的列表名从弹出的菜单中选择“同步”使用该功能,
因此,当 win7 没有使用**AERO**主题时界面将会显示异常开启AERO的方法请自行百度`win7开启aero效果`(开启后可看到任务栏变透明)。<br>
`0.14.0`版本起不再强制要求开启透明效果,若你实在不想开启(若非电脑配置太低,墙裂建议开启!),可通过添加运行参数`-nt`来运行程序即可,例如:`.\lx-music-desktop.exe -nt`,添加方法可自行百度“给快捷方式加参数”,该参数的作用是用来控制程序是否使用非透明窗口运行。
对于一些完全无法正常显示界面的情况,请阅读下面的 **软件启动后,界面无法显示**
## 软件启动后,界面无法显示
软件启动后,可以在任务栏看到软件,但软件界面在桌面上无任何显示。<br>
原始问题看:<https://github.com/electron/electron/issues/19569#issuecomment-522231083><br>
解决办法:下载`.NET Framework 4.7.1`或**更高**版本安装即可(建议安装最新版,若安装过程中遇到问题可尝试自行百度解决)。<br>
微软官方下载地址:<https://dotnet.microsoft.com/download/dotnet-framework><br>
下载`Runtime(运行时)`版即可,安装完成后可能需要重启才生效。
若还是不行可尝试以下操作:
- 更新显卡驱动
- 尝试将绿色版的软件放在**桌面**或**我的文档**运行
## 安装版安装失败,提示安装程序并未成功地运行完成
对于部分电脑出现安装失败的问题,可以做出以下尝试:
- 若你之前可以安装成功,但现在安装失败,就去**控制面板-程序和功能**或用第三方卸载工具看下有没有之前的版本残留,若同时在不同路径下安装了多个版本就可能会出现该问题,这种情况卸载掉所有版本重新安装即可
- 清理安装路径下的残留文件
- 以管理员权限打开`cmd`,输入`sfc /scannow`回车等待检查完成重启电脑
- 若还是不行我也没办法了。。
需要注意的是:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表。
## 缺少`xxx.dll`
@@ -86,22 +205,172 @@
## 杀毒软件提示有病毒或恶意行为
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为并且软件代码已开源请自行查阅软件安装包也是由CI拉取源代码构建构建日志[windows包](https://ci.appveyor.com/project/lyswhut/lx-music-desktop)、[Mac/Linux包](https://travis-ci.org/lyswhut/lx-music-desktop)<br>
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为并且软件代码已开源请自行查阅软件安装包也是由CI拉取源代码构建构建日志[GitHub Actions](https://github.com/lyswhut/lx-music-desktop/actions)<br>
尽管如此但这不意味着软件是100%安全的,由于软件使用了第三方依赖,当这些依赖存在恶意行为时([供应链攻击](https://docs.microsoft.com/zh-cn/windows/security/threat-protection/intelligence/supply-chain-malware)),软件也将会受到牵连,所以我只能尽量选择使用较多人用、信任度较高的依赖。<br>
当然,以上说明建立的前提是在你所用的安装包是从**本项目主页上写的链接**下载的,或者有相关能力者还可以下载源代码自己构建安装包。
`0.17.0`起,由于加入了音频输出设备切换功能,该功能调用了 [MediaDevices.enumerateDevices()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/enumerateDevices),可能导致安全软件提示洛雪要访问摄像头(目前发现卡巴斯基会提示),但实际上没有用到摄像头,并且摄像头的提示灯也不会亮,你可以选择阻止访问。
`v0.17.0`起,由于加入了音频输出设备切换功能,该功能调用了 [MediaDevices.enumerateDevices()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/enumerateDevices),可能导致安全软件提示洛雪要访问摄像头(目前发现卡巴斯基会提示),但实际上没有用到摄像头,并且摄像头的提示灯也不会亮,你可以选择阻止访问。
最后,若出现杀毒软件报毒、存在恶意行为,请自行判断选择是否继续使用本软件!
## 软件无法联网
## 自定义源脚本编写说明
软件的排行榜、歌单、搜索列表**都**无法加载
文件请使用UTF-8编码格式编写脚本所用编程语言为JavaScript可以使用ES6+语法,脚本与应用的交互是使用类似事件收发的方式进行,这是一个基本的脚本例子
- 检查是否在设置界面开启了代理(当代理乱设置时软件将无法联网)
- 检查软件是否被第三方软件/防火墙阻止联网
```js
/**
* @name 测试音乐源
* @description 我只是一个测试音乐源哦
* @version 1.0.0
* @author xxx
* @homepage http://xxx
*/
## 无法打开外部歌单
对于分享出来的歌单若打开失败可尝试先在浏览器中打开后再从浏览器地址栏复制URL地址到软件打开<br>
或者如果你知道歌单 id 也可以直接输入歌单 id 打开。<br>
const { EVENT_NAMES, request, on, send } = window.lx
const qualitys = {
kw: {
'128k': '128',
'320k': '320',
flac: 'flac',
},
}
const httpRequest = (url, options) => new Promise((resolve, reject) => {
request(url, options, (err, resp) => {
if (err) return reject(err)
resolve(resp.body)
})
})
const apis = {
kw: {
musicUrl({ songmid }, quality) {
return httpRequest('http://xxx').then(data => {
return data.url
})
},
},
}
// 注册应用API请求事件
// source 音乐源可能的值取决于初始化时传入的sources对象的源key值
// info 请求附加信息内容根据action变化
// action 请求操作类型目前只有musicUrl即获取音乐URL链接
// 当action为musicUrl时info的结构{type, musicInfo}
// info.type音乐质量可能的值有128k / 320k / flac取决于初始化时对应源传入的qualitys值中的一个
// info.musicInfo音乐信息对象里面有音乐ID、名字等信息
on(EVENT_NAMES.request, ({ source, action, info }) => {
// 回调必须返回 Promise 对象
switch (action) {
// action 为 musicUrl 时需要在 Promise 返回歌曲 url
case 'musicUrl':
return apis[source].musicUrl(info.musicInfo, qualitys[source][info.type]).catch(err => {
console.log(err)
return Promise.reject(err)
})
}
})
// 脚本初始化完成后需要发送inited事件告知应用
send(EVENT_NAMES.inited, {
status: true, // 初始化成功 or 失败
openDevTools: false, // 是否打开开发者工具,方便用于调试脚本
sources: { // 当前脚本支持的源
kw: { // 支持的源对象可用key值kw/kg/tx/wy/mg
name: '酷我音乐',
type: 'music', // 目前固定为 music
actions: ['musicUrl'], // 目前固定为 ['musicUrl']
qualitys: ['128k', '320k', 'flac'], // 当前脚本的该源所支持获取的Url音质有效的值有['128k', '320k', 'flac']
},
},
})
```
### 自定义源信息
文件的开头必须包含以下注释:
```js
/**
* @name 测试脚本
* @description 我只是一个测试脚本
* @version 1.0.0
* @author xxx
* @homepage http://xxx
*/
```
- `@name `源的名字建议不要过长10个字符以内
- `@description `源的描述建议不要过长20个字符以内可不填不填时必须保留 @description
- `@version`:源的版本号,可不填,不填时可以删除 @version
- `@author `:脚本作者名字,可不填,不填时可以删除 @author
- `@homepage `:脚本主页,可不填,不填时可以删除 @homepage
### `window.lx`
应用为脚本暴露的API对象。
#### `window.lx.EVENT_NAMES`
常量事件名称对象,发送、注册事件时传入事件名时使用,可用值:
| 事件名 | 描述
| --- | ---
| `inited` | 脚本初始化完成后发送给应用的事件名,发送该事件时需要传入以下信息:`{status, sources, openDevTools}`<br>`status`:初始化结果(`true`成功,`false`失败)<br>`openDevTools`是否打开DevTools此选项可用于开发脚本时的调试<br>`sources`:支持的源信息对象,<br>`sources[kw/kg/tx/wy/mg].name`:源的名字(目前非必须)<br>`sources[kw/kg/tx/wy/mg].type`:源类型,目前固定值需为`music`<br>`sources[kw/kg/tx/wy/mg].actions`支持的actions由于目前只支持`musicUrl`,所以固定传`['musicUrl']`即可<br>`sources[kw/kg/tx/wy/mg].qualitys`:该源支持的音质列表,有效的值为`['128k', '320k', 'flac']`,该字段用于控制应用可用的音质类型
| `request` | 应用API请求事件名回调入参`handler({ source, action, info})`,回调必须返回`Promise`对象<br>`source`:音乐源,可能的值取决于初始化时传入的`sources`对象的源key值<br>`info`:请求附加信息,内容根据`action`变化<br>`action`:请求操作类型,目前只有`musicUrl`即获取音乐URL链接需要在 Promise 返回歌曲 url`info`的结构:`{type, musicInfo}``info.type`:音乐质量,可能的值有`128k` / `320k` / `flac`(取决于初始化时对应源传入的`qualitys`值中的一个),`info.musicInfo`音乐信息对象里面有音乐ID、名字等信息
#### `window.lx.on`
事件注册方法,应用主动与脚本通信时使用:
```js
/**
* @param event_name 事件名
* @param handler 事件处理回调 -- 注意:注册的回调必须返回 Promise 对象
*/
window.lx.on(event_name, handler)
```
**注意:** 注册的回调必须返回 `Promise` 对象。
#### `window.lx.send`
事件发送方法,脚本主动与应用通信时使用:
```js
/**
* @param event_name 事件名
* @param datas 要传给应用的数据
*/
window.lx.send(event_name, datas)
```
#### `window.lx.request`
HTTP请求方法用于发送HTTP请求此HTTP请求方法不受跨域规则限制
```js
/**
* @param url 请求的URL
* @param options 请求选项,可用选项有 method / headers / body / form / formData / timeout
* @param callback 请求结果的回调 入参err, resp, body
* @return 返回一个方法调用此方法可以终止HTTP请求
*/
const cancelHttp = window.lx.request(url, options, callback)
```
#### `window.lx.utils`
应用提供给脚本的工具方法:
- `window.lx.utils.buffer.from`对应Node.js的 `Buffer.from`
- `window.lx.utils.crypto.aesEncrypt`AES加密 `aesEncrypt(buffer, mode, key, iv)`
- `window.lx.utils.crypto.md5`MD5加密 `md5(str)`
- `window.lx.utils.crypto.randomBytes`:生成随机字符串 `randomBytes(size)`
- `window.lx.utils.crypto.rsaEncrypt`RSA加密 `rsaEncrypt(buffer, key)`
目前仅提供以上工具方法如果需要其他方法可以开issue讨论。

View File

@@ -2,8 +2,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://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/actions/workflows/release.yml"><img src="https://github.com/lyswhut/lx-music-desktop/workflows/Build/badge.svg" alt="Build status"></a>
<a href="https://github.com/lyswhut/lx-music-desktop/actions/workflows/beta-pack.yml"><img src="https://github.com/lyswhut/lx-music-desktop/workflows/Build%20Beta/badge.svg" alt="Build status"></a>
<a href="https://electronjs.org/releases/stable"><img src="https://img.shields.io/github/package-json/dependency-version/lyswhut/lx-music-desktop/dev/electron/master" alt="Electron version"></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>
@@ -36,7 +36,7 @@
所用技术栈:
- Electron 8
- Electron 13
- Vue 2
已支持的平台:
@@ -47,12 +47,12 @@
软件变化请查看:[更新日志](https://github.com/lyswhut/lx-music-desktop/blob/master/CHANGELOG.md)<br>
软件下载请转到:[发布页面](https://github.com/lyswhut/lx-music-desktop/releases)<br>
或者到网盘下载网盘内有MAC、windows版`https://t-s.lanzous.com/b0bf2cfa/` 密码:`glqw`<br>
或者到网盘下载网盘内有MAC、windows版`https://www.lanzoui.com/b0bf2cfa/` 密码:`glqw`(若链接无法打开请百度:蓝奏云链接打不开)<br>
使用常见问题请转至:[常见问题](https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md)
### 源码使用方法
环境要求Node.js 12.x+
环境要求Node.js 14+
```bash
# 开发模式
@@ -62,7 +62,7 @@ npm run dev
npm run pack:dir
# 构建安装包Windows版
npm run pack
npm run pack:win
# 构建安装包Mac版
npm run pack:mac
@@ -81,7 +81,16 @@ npm run pack:linux
目前软件已支持的启动参数如下:
- `-search` 启动软件时自动在搜索框搜索指定的内容,例如:`-search="突然的自我 - 伍佰"`
- `-nt` 以非透明模式启动对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示
- `-dha` 禁用硬件加速启动Disable Hardware Acceleration窗口显示有问题时可以尝试添加此参数启动v1.6.0起新增)
- `-dt` 以非透明模式启动Disable Transparent对于未开启AERO效果的win7系统可加此参数启动以确保界面正常显示该参数对桌面歌词无效原来的`-nt`参数已重命名为`-dt`v1.6.0起重命名)
- `-dhmkh` 禁用硬件媒体密钥处理Disable Hardware Media Key Handling此选项将禁用Chromium的Hardware Media Key Handling特性v1.9.0起新增)
- `-play` 启动时播放指定列表的音乐,参数说明:
- `type`:播放类型,目前固定为`songList`
- `source`:播放源,可用值为`kw/kg/tx/wy/mg/myList`,其中`kw/kg/tx/wy/mg`对应各源的在线列表,`myList`为本地列表
- `link`要播放的在线列表歌单链接、或IDsource为`kw/kg/tx/wy/mg`之一(在线列表)时必传,举例:`./lx-music-desktop -play="type=songList&source=kw&link=歌单URL or ID"`注意如果传入URL时必须对URL进行编码后再传入
- `name`要播放的本地列表歌单名字source为`myList`时必传,举例:`./lx-music-desktop -play="type=songList&source=myList&name=默认列表"`
- `index`:从列表的哪个位置开始播放,选传,若不传默认播放第一首歌曲,举例:`./lx-music-desktop -play="type=songList&source=myList&name=默认列表&index=2"`
### 常见问题
@@ -100,7 +109,7 @@ npm run pack:linux
5. 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
6. 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本项目不对项目内的技术可能存在违反当地法律法规的行为作保证,**禁止在违反当地法律法规的情况下使用本项目**,对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
若你使用了本项目,将代表你接以上协议。
若你使用了本项目,将代表你接以上协议。
音乐平台不易,请尊重版权,支持正版。<br>
若对此有疑问请 mail to: lyswhut+qq.com (请将`+`替换成`@`)

View File

@@ -2,7 +2,8 @@ const isDev = process.env.NODE_ENV === 'development'
module.exports = {
modules: {
localIdentName: isDev ? '[folder]-[name]--[local]--[hash:base64:5]' : '[hash:base64:5]',
localIdentName: isDev ? '[path][name]__[local]--[hash:base64:5]' : '[hash:base64:5]',
exportLocalsConvention: 'camelCase',
},
localsConvention: 'camelCase',
sourceMap: isDev,
}

View File

@@ -9,9 +9,10 @@ module.exports = {
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src/main'),
events: path.join(__dirname, '../../src/main/events'),
common: path.join(__dirname, '../../src/common'),
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
'@common': path.join(__dirname, '../../src/common'),
},
extensions: ['*', '.js', '.json', '.node'],
},

View File

@@ -1,5 +1,5 @@
const path = require('path')
const merge = require('webpack-merge')
const { merge } = require('webpack-merge')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
@@ -17,13 +17,13 @@ module.exports = merge(baseConfig, {
NODE_ENV: '"development"',
},
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
__userApi: `"${path.join(__dirname, '../../src/main/modules/userApi').replace(/\\/g, '\\\\')}"`,
}),
new webpack.NoEmitOnErrorsPlugin(),
new FriendlyErrorsPlugin({
onErrors(severity, errors) { // Silent warning from electron-debug
if (severity != 'warning') return
for (let i = errors.length; i > -1; i--) {
for (let i = errors.length - 1; i > -1; i--) {
const error = errors[i]
if (error.file == './node_modules/electron-debug/index.js') errors.splice(i, 1)
}

View File

@@ -1,6 +1,7 @@
const path = require('path')
const merge = require('webpack-merge')
const { merge } = require('webpack-merge')
const webpack = require('webpack')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const baseConfig = require('./webpack.config.base')
@@ -20,6 +21,18 @@ module.exports = merge(baseConfig, {
__filename: false,
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, '../../src/main/modules/userApi/renderer'),
to: path.join(__dirname, '../../dist/electron/userApi/renderer'),
},
{
from: path.join(__dirname, '../../src/main/modules/userApi/rendererEvent/name.js'),
to: path.join(__dirname, '../../dist/electron/userApi/rendererEvent/name.js'),
},
],
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',

View File

@@ -7,17 +7,19 @@ const Spinnies = require('spinnies')
const mainConfig = require('./main/webpack.config.prod')
const rendererConfig = require('./renderer/webpack.config.prod')
const rendererLyricConfig = require('./renderer-lyric/webpack.config.prod')
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
const okayLog = chalk.bgGreen.white(' OKAY ') + ' '
function build() {
del.sync(['dist/electron', 'build'])
del.sync(['dist/electron/**', 'build/**'])
const spinners = new Spinnies({ color: 'blue' })
spinners.add('main', { text: 'main building' })
spinners.add('renderer', { text: 'renderer building' })
spinners.add('renderer-lyric', { text: 'renderer-lyric building' })
let results = ''
// m.on('success', () => {
@@ -52,6 +54,15 @@ function build() {
console.error(`\n${err}\n`)
process.exit(1)
}),
pack(rendererLyricConfig).then(result => {
results += result + '\n\n'
spinners.succeed('renderer-lyric', { text: 'renderer-lyric build success!' })
}).catch(err => {
spinners.fail('renderer-lyric', { text: 'renderer-lyric build fail :(' })
console.log(`\n ${errorLog}failed to build renderer-lyric process`)
console.error(`\n${err}\n`)
process.exit(1)
}),
]).then(handleSuccess)
}

View File

@@ -0,0 +1,156 @@
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HTMLPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CleanCSSPlugin = require('less-plugin-clean-css')
const vueLoaderConfig = require('../vue-loader.config')
const { mergeCSSLoader } = require('../utils')
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
target: 'electron-renderer',
entry: {
'renderer-lyric': path.join(__dirname, '../../src/renderer-lyric/main.js'),
},
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../../dist/electron'),
publicPath: 'auto',
},
resolve: {
alias: {
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
'@static': path.join(__dirname, '../../src/static'),
'@common': path.join(__dirname, '../../src/common'),
},
extensions: ['*', '.js', '.json', '.vue', '.node'],
},
module: {
rules: [
{
test: /\.(vue|js)$/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-formatter-friendly'),
emitWarning: isDev,
},
},
exclude: /node_modules/,
enforce: 'pre',
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig,
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
oneOf: mergeCSSLoader(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoader({
loader: 'less-loader',
options: {
sourceMap: true,
lessOptions: {
plugins: [
new CleanCSSPlugin({ advanced: true }),
],
},
},
}),
},
{
test: /\.styl(:?us)?$/,
oneOf: mergeCSSLoader({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.pug$/,
oneOf: [
// Use pug-plain-loader handle .vue file
{
resourceQuery: /vue/,
use: ['pug-plain-loader'],
},
// Use pug-loader handle .pug file
{
use: ['pug-loader'],
},
],
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10000,
},
},
generator: {
filename: 'imgs/[name]-[contenthash:8][ext]',
},
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10000,
},
},
generator: {
filename: 'media/[name]-[contenthash:8][ext]',
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10000,
},
},
generator: {
filename: 'fonts/[name]-[contenthash:8][ext]',
},
},
],
},
performance: {
maxEntrypointSize: 300000,
},
plugins: [
new HTMLPlugin({
filename: 'lyric.html',
template: path.join(__dirname, '../../src/renderer-lyric/index.pug'),
isProd: process.env.NODE_ENV == 'production',
browser: process.browser,
scriptLoading: 'blocking',
__dirname,
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
}),
],
}

View File

@@ -0,0 +1,27 @@
const path = require('path')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'eval-source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new FriendlyErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
},
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
],
performance: {
hints: false,
},
})

View File

@@ -0,0 +1,51 @@
const path = require('path')
const webpack = require('webpack')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
const { dependencies } = require('../../package.json')
let whiteListedModules = ['vue']
module.exports = merge(baseConfig, {
mode: 'production',
devtool: false,
externals: [
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, '../../src/static'),
to: path.join(__dirname, '../../dist/electron/static'),
},
],
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
},
}),
],
optimization: {
minimizer: [
new TerserPlugin(),
new CssMinimizerPlugin(),
],
},
performance: {
hints: 'warning',
},
node: {
__dirname: false,
__filename: false,
},
})

View File

@@ -1,9 +1,13 @@
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HTMLPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CleanCSSPlugin = require('less-plugin-clean-css')
const vueLoaderConfig = require('../vue-loader.config')
const { mergeCSSLoader } = require('../utils')
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
target: 'electron-renderer',
@@ -14,12 +18,15 @@ module.exports = {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../../dist/electron'),
publicPath: './',
publicPath: 'auto',
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src/renderer'),
common: path.join(__dirname, '../../src/common'),
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
'@static': path.join(__dirname, '../../src/static'),
'@common': path.join(__dirname, '../../src/common'),
},
extensions: ['*', '.js', '.json', '.vue', '.node'],
},
@@ -31,6 +38,7 @@ module.exports = {
loader: 'eslint-loader',
options: {
formatter: require('eslint-formatter-friendly'),
emitWarning: isDev,
},
},
exclude: /node_modules/,
@@ -46,6 +54,33 @@ module.exports = {
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
oneOf: mergeCSSLoader(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoader({
loader: 'less-loader',
options: {
sourceMap: true,
lessOptions: {
plugins: [
new CleanCSSPlugin({ advanced: true }),
],
},
},
}),
},
{
test: /\.styl(:?us)?$/,
oneOf: mergeCSSLoader({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.pug$/,
oneOf: [
@@ -62,26 +97,38 @@ module.exports = {
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'imgs/[name]--[folder].[ext]',
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10000,
},
},
generator: {
filename: 'imgs/[name]-[contenthash:8][ext]',
},
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'media/[name]--[folder].[ext]',
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10000,
},
},
generator: {
filename: 'media/[name]-[contenthash:8][ext]',
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name]--[folder].[ext]',
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10000,
},
},
generator: {
filename: 'fonts/[name]-[contenthash:8][ext]',
},
},
],
@@ -92,11 +139,18 @@ module.exports = {
plugins: [
new HTMLPlugin({
filename: 'index.html',
template: path.join(__dirname, '../../src/index.pug'),
template: path.join(__dirname, '../../src/renderer/index.pug'),
isProd: process.env.NODE_ENV == 'production',
browser: process.browser,
scriptLoading: 'blocking',
__dirname,
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: isDev ? '[name].css' : '[name].[contenthash:8].css',
chunkFilename: isDev ? '[id].css' : '[id].[contenthash:8].css',
}),
],
}

View File

@@ -2,44 +2,15 @@ const path = require('path')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const merge = require('webpack-merge')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
const { mergeCSSLoaderDev } = require('../utils')
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'eval-source-map',
module: {
rules: [
{
test: /\.css$/,
oneOf: mergeCSSLoaderDev(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoaderDev({
loader: 'less-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.styl(:?us)?$/,
oneOf: mergeCSSLoaderDev({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new FriendlyErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': {

View File

@@ -1,14 +1,12 @@
const path = require('path')
const webpack = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const merge = require('webpack-merge')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.config.base')
const { mergeCSSLoaderProd } = require('../utils')
const { dependencies } = require('../../package.json')
let whiteListedModules = ['vue']
@@ -20,58 +18,25 @@ module.exports = merge(baseConfig, {
externals: [
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.css$/,
oneOf: mergeCSSLoaderProd(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoaderProd({
loader: 'less-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.styl(:?us)?$/,
oneOf: mergeCSSLoaderProd({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
],
},
plugins: [
new CopyWebpackPlugin([
{
from: path.join(__dirname, '../../src/static'),
to: path.join(__dirname, '../../dist/electron/static'),
ignore: ['.*'],
},
]),
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, '../../src/static'),
to: path.join(__dirname, '../../dist/electron/static'),
},
],
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
},
}),
new MiniCssExtractPlugin({
filename: '[name].css',
}),
new webpack.NamedChunksPlugin(),
],
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: false, // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({}),
new TerserPlugin(),
new CssMinimizerPlugin(),
],
},
performance: {

View File

@@ -12,6 +12,7 @@ const webpackHotMiddleware = require('webpack-hot-middleware')
const mainConfig = require('./main/webpack.config.dev')
const rendererConfig = require('./renderer/webpack.config.dev')
const rendererLyricConfig = require('./renderer-lyric/webpack.config.dev')
let electronProcess = null
let manualRestart = false
@@ -65,6 +66,53 @@ function startRenderer() {
})
}
function startRendererLyric() {
return new Promise((resolve, reject) => {
// rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
// rendererConfig.mode = 'development'
const compiler = webpack(rendererLyricConfig)
hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2500,
})
compiler.hooks.compilation.tap('compilation', compilation => {
// console.log(Object.keys(compilation.hooks))
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
compiler.hooks.done.tap('done', stats => {
// logStats('Renderer', 'Compile done')
// logStats('Renderer', stats)
})
const server = new WebpackDevServer(
compiler,
{
contentBase: path.join(__dirname, '../'),
quiet: true,
hot: true,
historyApiFallback: true,
clientLogLevel: 'warning',
overlay: {
errors: true,
},
before(app, ctx) {
app.use(hotMiddleware)
ctx.middleware.waitUntilValid(() => {
resolve()
})
},
},
)
server.listen(9081)
})
}
function startMain() {
return new Promise((resolve, reject) => {
// mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
@@ -131,6 +179,9 @@ function startElectron() {
function electronLog(data, color) {
let log = data.toString()
if (/[0-9A-z]+/.test(log)) {
// 抑制 user api 窗口使用 data url 加载页面时 vue扩展 的报错日志刷屏的问题
if (color == 'red' && typeof log === 'string' && log.includes('"Extension server error: Operation failed: Permission denied", source: devtools://devtools/bundled/extensions/extensions.js')) return
console.log(chalk[color](log))
}
}
@@ -140,6 +191,7 @@ function init() {
const spinners = new Spinnies({ color: 'blue' })
spinners.add('main', { text: 'main compiling' })
spinners.add('renderer', { text: 'renderer compiling' })
spinners.add('renderer-lyric', { text: 'renderer-lyric compiling' })
function handleSuccess(name) {
spinners.succeed(name, { text: name + ' compile success!' })
}
@@ -149,6 +201,7 @@ function init() {
Promise.all([
startRenderer().then(() => handleSuccess('renderer')).catch(() => handleFail('renderer')),
startRendererLyric().then(() => handleSuccess('renderer-lyric')).catch(() => handleFail('renderer-lyric')),
startMain().then(() => handleSuccess('main')).catch(() => handleFail('main')),
]).then(startElectron).catch(err => {
console.error(err)

View File

@@ -2,62 +2,19 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const cssLoaderConfig = require('./css-loader.config')
const chalk = require('chalk')
// merge css-loader in development
exports.mergeCSSLoaderDev = beforeLoader => {
// merge css-loader
exports.mergeCSSLoader = beforeLoader => {
const loader = [
// 这里匹配 `<style module>`
{
resourceQuery: /module/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: Object.assign({
sourceMap: true,
}, cssLoaderConfig),
},
{
loader: 'postcss-loader',
loader: MiniCssExtractPlugin.loader,
options: {
sourceMap: true,
esModule: false,
},
},
],
},
// 这里匹配普通的 `<style>` 或 `<style scoped>`
{
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
},
},
],
},
]
if (beforeLoader) {
loader[0].use.push(beforeLoader)
loader[1].use.push(beforeLoader)
}
return loader
}
// merge css-loader in production
exports.mergeCSSLoaderProd = beforeLoader => {
const loader = [
// 这里匹配 `<style module>`
{
resourceQuery: /module/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: cssLoaderConfig,
@@ -68,7 +25,12 @@ exports.mergeCSSLoaderProd = beforeLoader => {
// 这里匹配普通的 `<style>` 或 `<style scoped>`
{
use: [
MiniCssExtractPlugin.loader,
{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: false,
},
},
'css-loader',
'postcss-loader',
],

View File

@@ -1,97 +0,0 @@
const path = require('path')
const webpack = require('webpack')
const HTMLPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const vueLoaderConfig = require('../vue-loader.config')
module.exports = {
target: 'web',
entry: path.join(__dirname, '../../src/renderer/main.js'),
output: {
path: path.join(__dirname, '../../dist/web'),
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src/renderer'),
common: path.join(__dirname, '../../src/common'),
},
extensions: ['*', '.js', '.vue', '.json', '.css'],
},
module: {
rules: [
{
test: /\.(vue|js)$/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-formatter-friendly'),
},
},
exclude: /node_modules/,
enforce: 'pre',
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig,
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.pug$/,
oneOf: [
// Use pug-plain-loader handle .vue file
{
resourceQuery: /vue/,
use: ['pug-plain-loader'],
},
// Use pug-loader handle .pug file
{
use: ['pug-loader'],
},
],
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: 'imgs/[name].[ext]',
},
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[ext]',
},
},
},
],
},
performance: {
maxEntrypointSize: 300000,
},
plugins: [
new VueLoaderPlugin(),
new HTMLPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../../src/index.pug'),
nodeModules: false,
isProd: process.env.NODE_ENV == 'production',
browser: process.browser,
__dirname,
}),
new webpack.DefinePlugin({
'process.env.IS_WEB': 'true',
}),
],
}

View File

@@ -1,57 +0,0 @@
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const baseConfig = require('./webpack.config.base')
const { mergeCSSLoaderDev } = require('../utils')
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'eval-source-map',
output: {
filename: '[name].js',
path: path.join(__dirname, '../../dist/web'),
},
module: {
rules: [
{
test: /\.css$/,
oneOf: mergeCSSLoaderDev(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoaderDev({
loader: 'less-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.styl$/,
oneOf: mergeCSSLoaderDev({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
},
}),
new FriendlyErrorsPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
],
performance: {
hints: false,
},
})

View File

@@ -1,94 +0,0 @@
const path = require('path')
const webpack = require('webpack')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const merge = require('webpack-merge')
const TerserPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const baseConfig = require('./webpack.config.base')
const { mergeCSSLoaderProd } = require('../utils')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: false,
output: {
filename: '[name].[chunkhash:8].js',
},
module: {
rules: [
{
test: /\.css$/,
oneOf: mergeCSSLoaderProd(),
},
{
test: /\.less$/,
oneOf: mergeCSSLoaderProd({
loader: 'less-loader',
options: {
sourceMap: true,
},
}),
},
{
test: /\.styl$/,
oneOf: mergeCSSLoaderProd({
loader: 'stylus-loader',
options: {
sourceMap: true,
},
}),
},
],
},
plugins: [
new CopyWebpackPlugin([
{
from: path.join(__dirname, '../../src/static'),
to: path.join(__dirname, '../dist/web/static'),
ignore: ['.*'],
},
]),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
},
}),
new MiniCssExtractPlugin({
filename: '[name].[contentHash:8].css',
}),
new webpack.NamedChunksPlugin(),
],
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: false, // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({}),
],
splitChunks: {
cacheGroups: {
// chunks: 'all',
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
enforce: true,
chunks: 'all',
},
styles: {
name: 'styles',
test: /\.(css|less)$/,
chunks: 'all',
enforce: true,
},
},
},
runtimeChunk: true,
},
performance: {
hints: 'warning',
},
})

13
jsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@main/*": ["src/main/*"],
"@renderer/*": ["src/renderer/*"],
"@lyric/*": ["src/renderer-lyric/*"],
"@static/*": ["src/static/*"],
"@common/*": ["src/common/*"],
}
},
"exclude": ["node_modules", "build", "dist"]
}

15002
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +1,79 @@
{
"name": "lx-music-desktop",
"version": "0.18.0",
"description": "一个免费的音乐下载助手",
"version": "1.12.2",
"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:x86_64 && npm run pack:win:7z",
"pack:win:setup:x86_64": "cross-env TARGET=win_安装版 ARCH=x86_64 electron-builder -w=nsis --x64 --ia32",
"pack:win:setup:x64": "cross-env TARGET=win_安装版 ARCH=x64 electron-builder -w=nsis --x64",
"pack:win:setup:x86": "cross-env TARGET=win_安装版 ARCH=x86 electron-builder -w=nsis --ia32",
"pack": "node build-config/pack.js && npm run pack:win:setup:x64",
"pack:win": "node build-config/pack.js && npm run pack:win:setup:x64 && npm run pack:win:setup:x86 && npm run pack:win:setup:arm64 && npm run pack:win:setup:x86_64 && npm run pack:win:7z",
"pack:win:setup:x86_64": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p never",
"pack:win:setup:x64": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p never",
"pack:win:setup:x86": "cross-env TARGET=Setup ARCH=x86 electron-builder -w=nsis --ia32 -p never",
"pack:win:setup:arm64": "cross-env TARGET=Setup ARCH=arm64 electron-builder -w=nsis --arm64 -p never",
"pack:win:portable": "npm run pack:win:portable:x86_64 && npm run pack:win:portable:x64 && npm run pack:win:portable:x86",
"pack:win:portable:x86_64": "cross-env TARGET=便携版 ARCH=x86_64 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:portable:x86_64": "cross-env TARGET=portable ARCH=x86_64 electron-builder -w=portable --x64 --ia32 -p never",
"pack:win:portable:x64": "cross-env TARGET=portable ARCH=x64 electron-builder -w=portable --x64 -p never",
"pack:win:portable:x86": "cross-env TARGET=portable ARCH=x86 electron-builder -w=portable --ia32 -p never",
"pack:win:7z": "npm run pack:win:7z:x64 && npm run pack:win:7z:x86",
"pack:win:7z:x64": "cross-env TARGET=win_绿色版 ARCH=x64 electron-builder -w=7z --x64",
"pack:win:7z:x86": "cross-env TARGET=win_绿色版 ARCH=x86 electron-builder -w=7z --ia32",
"pack:win:7z:x64": "cross-env TARGET=green ARCH=win_x64 electron-builder -w=7z --x64 -p never",
"pack:win:7z:x86": "cross-env TARGET=green ARCH=win_x86 electron-builder -w=7z --ia32 -p never",
"pack:win:7z:arm64": "cross-env TARGET=green ARCH=win_arm64 electron-builder -w=7z --arm64 -p never",
"pack:linux": "node build-config/pack.js && npm run pack:linux:deb && npm run pack:linux:appImage && npm run pack:linux:rpm && npm run pack:linux:pacman",
"pack:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p never",
"pack:linux:deb": "npm run pack:linux:deb:x64 && npm run pack:linux:deb:x86 && npm run pack:linux:deb:arm64 && npm run pack:linux:deb:armv7l",
"pack:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p never",
"pack:linux:deb:x86": "cross-env ARCH=x86 electron-builder -l=deb --ia32 -p never",
"pack:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64 -p never",
"pack:linux:deb:armv7l": "cross-env ARCH=armv7l electron-builder -l=deb --armv7l -p never",
"pack:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64 -p never",
"pack:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64 -p never",
"pack:mac": "node build-config/pack.js && npm run pack:mac:dmg && npm run pack:mac:dmg:arm64",
"pack:mac:dmg": "cross-env electron-builder -m=dmg -p never",
"pack:mac:dmg:arm64": "cross-env electron-builder -m=dmg --arm64 -p never",
"pack:dir": "node build-config/pack.js && electron-builder --dir",
"publish": "node publish",
"publish:gh": "node build-config/pack.js && npm run publish:win",
"publish:win": "npm run publish:win:7z && npm run publish:win:setup",
"publish:win:setup": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p onTagOrDraft",
"publish:win:setup:x64:always": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p always",
"publish:win:setup:x64": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p always",
"publish:win:setup:x86": "cross-env TARGET=Setup ARCH=x86 electron-builder -w=nsis --ia32 -p onTagOrDraft",
"publish:win:setup:arm64": "cross-env TARGET=Setup ARCH=arm64 electron-builder -w=nsis --arm64 -p onTagOrDraft",
"publish:win:setup:x86_64": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p onTagOrDraft",
"publish:win:portable": "npm run publish:win:portable:x86_64 && npm run publish:win:portable:x64 && npm run publish:win:portable:x86",
"publish:win:portable:x86_64": "cross-env TARGET=portable ARCH=x86_64 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 always",
"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:win:7z:arm64": "cross-env TARGET=green ARCH=win_arm64 electron-builder -w=7z --arm64 -p onTagOrDraft",
"publish:mac:dmg:always": "electron-builder -m=dmg -p always",
"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:deb && npm run publish:linux:appImage",
"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:mac:dmg:arm64": "electron-builder -m=dmg --arm64 -p onTagOrDraft",
"publish:linux:deb:x64:always": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p always",
"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:mac": "node build-config/pack.js && electron-builder -m=dmg",
"pack:dir": "node build-config/pack.js && electron-builder --dir",
"publish:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64 -p onTagOrDraft",
"publish:linux:deb:armv7l": "cross-env ARCH=armv7l electron-builder -l=deb --armv7l -p onTagOrDraft",
"publish:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p onTagOrDraft",
"publish:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64 -p onTagOrDraft",
"publish:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64 -p onTagOrDraft",
"dev": "node build-config/runner-dev.js",
"clean:electron": "rimraf dist/electron",
"clean:web": "rimraf dist/web",
"clean": "rimraf dist && rimraf build",
"build:main": "cross-env NODE_ENV=production webpack --config build-config/main/webpack.config.prod.js --progress --hide-modules",
"build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress --hide-modules",
"build:web": "npm run clean:web && cross-env NODE_ENV=production webpack --config build-config/web/webpack.config.prod.js --progress --hide-modules",
"build": "npm run clean:electron && npm run build:main && npm run build:renderer",
"build:src": "node build-config/pack.js",
"build:main": "cross-env NODE_ENV=production webpack --config build-config/main/webpack.config.prod.js --progress",
"build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress",
"build:renderer-lyric": "cross-env NODE_ENV=production webpack --config build-config/renderer-lyric/webpack.config.prod.js --progress",
"build": "npm run clean:electron && npm run build:main && npm run build:renderer && npm run build:renderer-lyric",
"lint": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly src",
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly --fix src"
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-formatter-friendly --fix src",
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm run pack",
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm i"
},
"browserslist": [
"Electron 8.2.5"
"Electron 13.1.7"
],
"engines": {
"node": ">= 12"
"node": ">= 14"
},
"build": {
"appId": "cn.toside.music.desktop",
@@ -92,7 +110,7 @@
"allowToChangeInstallationDirectory": true,
"differentialPackage": true,
"license": "./licenses/license.rtf",
"shortcutName": "lx-music"
"shortcutName": "LX Music"
},
"dmg": {
"window": {
@@ -103,7 +121,7 @@
{
"x": 106,
"y": 252,
"name": "lx-music"
"name": "LX Music"
},
{
"x": 490,
@@ -123,12 +141,6 @@
"provider": "github",
"owner": "lyswhut",
"repo": "lx-music-desktop"
},
{
"package": "lx-music-desktop",
"repo": "lx-music-desktop",
"user": "lyswhut",
"provider": "bintray"
}
]
},
@@ -151,84 +163,91 @@
},
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": {
"@babel/core": "^7.9.6",
"@babel/core": "^7.15.0",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.9.6",
"autoprefixer": "^9.7.6",
"@babel/plugin-transform-modules-umd": "^7.14.5",
"@babel/plugin-transform-runtime": "^7.15.0",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.15.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-minify-webpack-plugin": "^0.3.1",
"babel-loader": "^8.2.2",
"babel-preset-minify": "^0.5.1",
"browserslist": "^4.12.0",
"cfonts": "^2.8.1",
"chalk": "^4.0.0",
"browserslist": "^4.16.7",
"cfonts": "^2.9.3",
"chalk": "^4.1.2",
"changelog-parser": "^2.8.0",
"copy-webpack-plugin": "^5.1.1",
"core-js": "^3.6.5",
"cos-nodejs-sdk-v5": "^2.5.20",
"cross-env": "^7.0.2",
"css-loader": "^3.5.3",
"del": "^5.1.0",
"electron": "^8.2.5",
"electron-builder": "^22.6.0",
"electron-debug": "^3.0.1",
"electron-devtools-installer": "^3.0.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.1",
"copy-webpack-plugin": "^9.0.1",
"core-js": "^3.16.1",
"cross-env": "^7.0.3",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"del": "^6.0.0",
"electron": "^13.1.9",
"electron-builder": "^22.11.7",
"electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0",
"electron-to-chromium": "^1.3.801",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-formatter-friendly": "^7.0.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-html": "^6.0.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-html": "^6.1.2",
"eslint-plugin-import": "^2.24.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"file-loader": "^6.0.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-standard": "^4.1.0",
"file-loader": "^6.2.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^4.3.0",
"less": "^3.11.1",
"less-loader": "^6.0.0",
"markdown-it": "^10.0.0",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"postcss-pxtorem": "^5.1.1",
"pug": "^2.0.4",
"html-webpack-plugin": "^5.3.2",
"less": "^4.1.1",
"less-loader": "^10.0.1",
"less-plugin-clean-css": "^1.5.1",
"markdown-it": "^12.2.0",
"mini-css-extract-plugin": "^2.2.0",
"postcss": "^8.3.6",
"postcss-loader": "^6.1.1",
"postcss-pxtorem": "^6.0.0",
"pug": "^3.0.2",
"pug-loader": "^2.4.0",
"pug-plain-loader": "^1.0.0",
"raw-loader": "^4.0.1",
"pug-plain-loader": "^1.1.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"spinnies": "^0.5.1",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"terser-webpack-plugin": "^2.3.6",
"url-loader": "^4.1.0",
"vue-loader": "^15.9.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"stylus": "^0.54.8",
"stylus-loader": "^6.1.0",
"terser-webpack-plugin": "^5.1.4",
"url-loader": "^4.1.1",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14",
"webpack": "^5.50.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2",
"webpack-hot-middleware": "^2.25.0",
"webpack-merge": "^4.2.2"
"webpack-merge": "^5.8.0"
},
"dependencies": {
"crypto-js": "^4.0.0",
"dnscache": "^1.0.2",
"electron-log": "^4.1.1",
"electron-store": "^5.1.1",
"electron-updater": "^4.3.1",
"image-size": "^0.8.3",
"js-htmlencode": "^0.3.0",
"lrc-file-parser": "^1.0.3",
"mousetrap": "^1.6.5",
"needle": "^2.4.1",
"node-id3": "^0.1.16",
"crypto-js": "^4.1.1",
"electron-log": "^4.4.1",
"bufferutil": "^4.0.3",
"eiows": "^3.6.1",
"electron-store": "^8.0.0",
"electron-updater": "^4.3.9",
"http-terminator": "^3.0.0",
"iconv-lite": "^0.6.3",
"image-size": "^1.0.0",
"koa": "^2.13.1",
"long": "^4.0.0",
"lrc-file-parser": "^1.1.2",
"needle": "^2.8.0",
"node-id3": "^0.2.3",
"request": "^2.88.2",
"vue": "^2.6.11",
"vue-i18n": "^8.17.4",
"vue-router": "^3.1.6",
"vuex": "^3.3.0",
"socket.io": "^4.1.3",
"utf-8-validate": "^5.0.5",
"vue": "^2.6.14",
"vue-i18n": "^8.25.0",
"vue-router": "^3.5.2",
"vuex": "^3.6.2",
"vuex-router-sync": "^5.0.0"
}
}

View File

@@ -1,4 +1,4 @@
const autoprefixer = require('autoprefixer')
// const autoprefixer = require('autoprefixer')
const pxtorem = require('postcss-pxtorem')
module.exports = {
@@ -8,16 +8,11 @@ module.exports = {
unitPrecision: 5,
propList: [
'font', 'font-size',
'line-height',
'letter-spacing',
'padding', 'margin',
'padding-left', 'padding-right',
'padding-top', 'padding-bottom',
'margin-left', 'margin-right',
'margin-top', 'margin-bottom',
'padding-*', 'margin-*',
'height', 'width',
'max-width', 'max-height',
'min-width', 'min-height',
'*-height', '*-width',
'flex', '::-webkit-scrollbar',
'top', 'left', 'bottom', 'right',
'border-radius',
@@ -28,6 +23,6 @@ module.exports = {
minPixelValue: 0,
exclude: [/node_modules/i],
}),
autoprefixer(),
// autoprefixer(),
],
}

View File

@@ -1,42 +1,6 @@
### 新增
- 新增FLAC格式音乐标签信息写入与封面嵌入因128k以外的音质已失效目前该功能用不上了
- 添加软件启动时是否自动聚焦搜索框的设置
- 新增托盘设置,默认关闭,可到设置开启,感谢 @LasyIsLazy 提交的PR
- 新增打开酷狗源用户歌单
- 新增使用协议
- 新增虾米音源
- 新增新皮肤“粉妆玉琢”、“青出于黑”,可去体验下~
- 新增“超大”、“巨大”窗口尺寸
- 新增播放详情页(退出详情页可点击右上角退出按钮或者在播放详情页任意地方**鼠标快速右击两次**
### 优化
- 略微加深音量条底色
- 优化其他界面细节
- 优化英语翻译,感谢 @CPCer
- 优化程序的流畅度
### 更变
- 下载列表的歌曲下载、播放将随设置中的保存路径改变而改变,不再固定指向其初始位置
- 移除列表多选框,现在多选需要键盘配合,想要多选前需按下`Shift``Ctrl`键然后再鼠标点击想要选中的内容即可触发多选机制,其中`Shift`键用于连续选择,`Ctrl`键用于不连续选择,`Ctrl+a`用于快速全选。例子一想要选中1-5项则先按下`Shift`键后鼠标点击第一项再点击第五项即可完成选择例子二想要选中1项与第3项则先按下`Ctrl`键后,鼠标点击第一项,再点击第三项即可完成选择;例子三:想要选中当前列表的全部内容,键盘先按下`Ctrl`键不放,然后按`a`键,即可完成选择。用`Shift``Ctrl`选择时,鼠标点击未选中的内容会将其选中,点击已选择的内容会将其取消选择,若想全部取消选择,在不按`Shift``Alt`键的情况下,随意点击列表里的一项内容即可全部取消选择。(P.S`Ctrl`键对应Mac OS上的`Command`键)
- 现在进度条的封面图左击改为打开播放详情页,在列表定位歌曲改为右击
### 修复
- 修复网易源某些歌曲提示没有可播放的音质的问题
- 修复下载管理刷新URL失败时不标记任务下载失败的问题
- 修复列表导出的文字描述,感谢 @CPCer
- 修复歌曲切换方式无法取消勾选的问题
- 修复打开歌单详情的情况下切到其他界面再切回来报错的问题
- 修正播放列表浮动按钮错误的文字提示
### 移除
- 因128k以外的音质失效So 禁止所有128k外的音质下载
### 其他
更新 Electron 到 8.2.5
- 修复播放下载列表的歌曲时切歌的问题
- 修复播放下载列表的歌曲时歌词无法显示的问题
- 修复下载列表稍后播放功能无效的问题
- 修复同步服务器启动失败时,关闭同步服务不会清空失败信息的问题

View File

@@ -25,7 +25,7 @@ const updateChangeLog = async(newVerNum, newChangeLog) => {
let changeLog = fs.readFileSync(changelogPath, 'utf-8')
const prevVer = await getPrevVer()
const log = `## [${newVerNum}](${pkg.repository.url.replace(/^git\+(http.+)\.git$/, '$1')}/compare/v${prevVer}...v${newVerNum}) - ${formatTime()}\n\n${newChangeLog}`
fs.writeFileSync(changelogPath, changeLog.replace(new RegExp(`(## [?0.1.1]?)`), log + '\n$1'), 'utf-8')
fs.writeFileSync(changelogPath, changeLog.replace(new RegExp('(## [?0.1.1]?)'), log + '\n$1'), 'utf-8')
}
const renderChangeLog = md => md_renderer(md)
@@ -44,12 +44,12 @@ module.exports = async newVerNum => {
desc: version.desc,
})
version.version = newVerNum
version.desc = newChangeLog
version.desc = newMDChangeLog.replace(/(?:^|(\n))#{1,6} (.+)\n/g, '$1$2').trim()
pkg.version = newVerNum
console.log(chalk.blue('new version: ') + chalk.green(newVerNum))
fs.writeFileSync(jp('../version.json'), JSON.stringify(version, null, 2) + '\n', 'utf-8')
fs.writeFileSync(jp('../version.json'), JSON.stringify(version) + '\n', 'utf-8')
fs.writeFileSync(jp(pkgDir), JSON.stringify(pkg, null, 2) + '\n', 'utf-8')

File diff suppressed because one or more lines are too long

View File

@@ -17,40 +17,40 @@ module.exports = {
{
id: 2,
name: 'medium',
width: 1012,
width: 1018,
height: 650,
fontSize: '16px',
},
{
id: 3,
name: 'big',
width: 1104,
width: 1114,
height: 708,
fontSize: '17px',
},
{
id: 4,
name: 'larger',
width: 1198,
width: 1202,
height: 766,
fontSize: '18px',
fontSize: '17px',
},
{
id: 5,
name: 'oversized',
width: 1380,
width: 1382,
height: 886,
fontSize: '18px',
},
{
id: 6,
name: 'huge',
width: 1656,
width: 1686,
height: 1062,
fontSize: '19px',
},
],
navigationUrlWhiteList: [
/^https:\/\/www\.xiami\.com/,
],
}

View File

@@ -0,0 +1,89 @@
const { player: hotKeyPlayer, common: hotKeyCommon, desktop_lyric: hotKeyDesktopLyric } = require('./hotKey')
module.exports = {
local: {
enable: true,
keys: {
'mod+f5': {
type: hotKeyPlayer.toggle_play.type,
name: hotKeyPlayer.toggle_play.name,
action: hotKeyPlayer.toggle_play.action,
},
'mod+arrowleft': {
type: hotKeyPlayer.prev.type,
name: hotKeyPlayer.prev.name,
action: hotKeyPlayer.prev.action,
},
'mod+arrowright': {
type: hotKeyPlayer.next.type,
name: hotKeyPlayer.next.name,
action: hotKeyPlayer.next.action,
},
f1: {
type: hotKeyCommon.focusSearchInput.type,
name: hotKeyCommon.focusSearchInput.name,
action: hotKeyCommon.focusSearchInput.action,
},
},
},
global: {
enable: false,
keys: {
MediaPlayPause: {
type: hotKeyPlayer.toggle_play.type,
name: null,
action: hotKeyPlayer.toggle_play.action,
},
MediaPreviousTrack: {
type: hotKeyPlayer.prev.type,
name: null,
action: hotKeyPlayer.prev.action,
},
MediaNextTrack: {
type: hotKeyPlayer.next.type,
name: null,
action: hotKeyPlayer.next.action,
},
'mod+alt+f5': {
type: hotKeyPlayer.toggle_play.type,
name: hotKeyPlayer.toggle_play.name,
action: hotKeyPlayer.toggle_play.action,
},
'mod+alt+arrowleft': {
type: hotKeyPlayer.prev.type,
name: hotKeyPlayer.prev.name,
action: hotKeyPlayer.prev.action,
},
'mod+alt+arrowright': {
type: hotKeyPlayer.next.type,
name: hotKeyPlayer.next.name,
action: hotKeyPlayer.next.action,
},
'mod+alt+arrowup': {
type: hotKeyPlayer.volume_up.type,
name: hotKeyPlayer.volume_up.name,
action: hotKeyPlayer.volume_up.action,
},
'mod+alt+arrowdown': {
type: hotKeyPlayer.volume_down.type,
name: hotKeyPlayer.volume_down.name,
action: hotKeyPlayer.volume_down.action,
},
'mod+alt+0': {
type: hotKeyDesktopLyric.toggle_visible.type,
name: hotKeyDesktopLyric.toggle_visible.name,
action: hotKeyDesktopLyric.toggle_visible.action,
},
'mod+alt+-': {
type: hotKeyDesktopLyric.toggle_lock.type,
name: hotKeyDesktopLyric.toggle_lock.name,
action: hotKeyDesktopLyric.toggle_lock.action,
},
'mod+alt+=': {
type: hotKeyDesktopLyric.toggle_always_top.type,
name: hotKeyDesktopLyric.toggle_always_top.name,
action: hotKeyDesktopLyric.toggle_always_top.action,
},
},
},
}

View File

@@ -0,0 +1,114 @@
const path = require('path')
const os = require('os')
const defaultSetting = {
version: '1.0.43',
player: {
togglePlayMethod: 'listLoop',
highQuality: false,
isShowTaskProgess: true,
volume: 1,
isMute: false,
mediaDeviceId: 'default',
isMediaDeviceRemovedStopPlay: false,
isShowLyricTranslation: false,
isPlayLxlrc: true,
isSavePlayTime: false,
},
desktopLyric: {
enable: false,
isLock: false,
isAlwaysOnTop: false,
width: 380,
height: 420,
x: null,
y: null,
theme: 0,
isLockScreen: true,
style: {
fontSize: 120,
opacity: 95,
isZoomActiveLrc: true,
},
},
list: {
isShowAlbumName: true,
isShowSource: true,
prevSelectListId: 'default',
isSaveScrollLocation: true,
addMusicLocationType: 'top',
},
download: {
enable: false,
savePath: path.join(os.homedir(), 'Desktop'),
fileName: '歌名 - 歌手',
maxDownloadNum: 3,
isDownloadLrc: false,
isEmbedPic: true,
isEmbedLyric: false,
isUseOtherSource: false,
},
leaderboard: {
source: 'kw',
tabId: 'kw__16',
},
songList: {
source: 'kg',
sortId: '5',
tagInfo: {
name: '默认',
id: null,
},
},
odc: {
isAutoClearSearchInput: false,
isAutoClearSearchList: false,
},
search: {
searchSource: 'all',
tempSearchSource: 'kw',
isShowHotSearch: false,
isShowHistorySearch: false,
isFocusSearchBox: false,
},
network: {
proxy: {
enable: false,
host: '',
port: '',
username: '',
password: '',
},
},
tray: {
isShow: false,
isToTray: false,
themeId: 0,
},
sync: {
enable: false,
port: '23332',
},
windowSizeId: 2,
themeId: 0,
langId: null,
sourceId: 'kw',
apiSource: 'temp',
sourceNameType: 'alias',
isShowAnimation: true,
randomAnimate: true,
ignoreVersion: null,
isAgreePact: false,
controlBtnPosition: process.platform === 'darwin' ? 'left' : 'right',
}
const overwriteSetting = {
}
// 使用新年皮肤
if (new Date().getMonth() < 2) defaultSetting.themeId = 9
exports.defaultSetting = defaultSetting
exports.overwriteSetting = overwriteSetting

83
src/common/hotKey.js Normal file
View File

@@ -0,0 +1,83 @@
const names = require('@main/events/_name')
const hotKey = {
common: {
min: {
name: 'min',
action: 'min',
},
min_toggle: {
name: 'toggle_min',
action: 'toggle_min',
},
hide_toggle: {
name: 'toggle_hide',
action: 'toggle_hide',
},
close: {
name: 'toggle_close',
action: 'toggle_close',
},
focusSearchInput: {
name: 'focus_search_input',
action: 'focus_search_input',
},
},
player: {
toggle_play: {
name: 'toggle_play',
action: 'toggle_play',
},
next: {
name: 'next',
action: 'next',
},
prev: {
name: 'prev',
action: 'prev',
},
volume_up: {
name: 'volume_up',
action: 'volume_up',
},
volume_down: {
name: 'volume_down',
action: 'volume_down',
},
volume_mute: {
name: 'volume_mute',
action: 'volume_mute',
},
},
desktop_lyric: {
toggle_visible: {
name: 'toggle_visible',
action: 'toggle_visible',
},
toggle_lock: {
name: 'toggle_lock',
action: 'toggle_lock',
},
toggle_always_top: {
name: 'toggle_always_top',
action: 'toggle_always_top',
},
},
}
const keyName = {
player: names.mainWindow.name,
desktop_lyric: names.winLyric.name,
}
for (const type of Object.keys(hotKey)) {
let item = hotKey[type]
for (const key of Object.keys(item)) {
item[key].action = `${type}_${item[key].action}`
item[key].name = `${type}_${item[key].name}`
item[key].type = keyName[type]
}
}
exports.common = hotKey.common
exports.player = hotKey.player
exports.desktop_lyric = hotKey.desktop_lyric

View File

@@ -1,11 +1,18 @@
const { ipcMain, ipcRenderer } = require('electron')
const names = require('./ipcNames')
exports.mainOn = (event, callback) => {
ipcMain.on(event, callback)
exports.mainOn = (name, callback) => {
ipcMain.on(name, callback)
}
exports.mainOnce = (event, callback) => {
ipcMain.once(event, callback)
exports.mainOnce = (name, callback) => {
ipcMain.once(name, callback)
}
exports.mainOff = (name, callback) => {
ipcMain.removeListener(name, callback)
}
exports.mainOffAll = name => {
ipcMain.removeAllListeners(name)
}
exports.mainHandle = (name, callback) => {
@@ -14,7 +21,13 @@ exports.mainHandle = (name, callback) => {
exports.mainHandleOnce = (name, callback) => {
ipcMain.handleOnce(name, callback)
}
exports.mainHandleRemove = name => {
ipcMain.removeListener(name)
}
exports.mainSend = (window, name, params) => {
window.webContents.send(name, params)
}
exports.rendererSend = (name, params) => {
ipcRenderer.send(name, params)
@@ -29,3 +42,11 @@ exports.rendererOn = (name, callback) => {
exports.rendererOnce = (name, callback) => {
ipcRenderer.once(name, callback)
}
exports.rendererOff = (name, callback) => {
ipcRenderer.removeListener(name, callback)
}
exports.rendererOffAll = name => {
ipcRenderer.removeAllListeners(name)
}
exports.NAMES = names

102
src/common/ipcNames.js Normal file
View File

@@ -0,0 +1,102 @@
const names = {
mainWindow: {
focus: 'focus',
close: 'close',
min: 'min',
max: 'max',
set_app_name: 'set_app_name',
clear_cache: 'clear_cache',
get_cache_size: 'get_cache_size',
get_env_params: 'get_env_params',
set_music_meta: 'set_music_meta',
progress: 'progress',
change_tray: 'change_tray',
quit_update: 'quit_update',
update_available: 'update_available',
update_error: 'update_error',
update_progress: 'update_progress',
update_downloaded: 'update_downloaded',
update_not_available: 'update_not_available',
set_ignore_mouse_events: 'set_ignore_mouse_events',
set_app_setting: 'set_app_setting',
set_window_size: 'set_window_size',
show_save_dialog: 'show_save_dialog',
handle_request: 'handle_request',
cancel_request: 'cancel_request',
handle_xm_verify_open: 'handle_xm_verify_open',
handle_xm_verify_close: 'handle_xm_verify_close',
select_dir: 'select_dir',
restart_window: 'restart_window',
handle_kw_decode_lyric: 'handle_kw_decode_lyric',
get_lyric_info: 'get_lyric_info',
set_lyric_info: 'set_lyric_info',
set_config: 'set_config',
set_hot_key_config: 'set_hot_key_config',
key_down: 'key_down',
quit: 'quit',
min_toggle: 'min_toggle',
hide_toggle: 'hide_toggle',
get_data_path: 'get_data_path',
show_dialog: 'show_dialog',
get_setting: 'get_setting',
get_playlist: 'get_playlist',
save_playlist: 'save_playlist',
get_data: 'get_data',
save_data: 'save_data',
get_hot_key: 'get_hot_key',
import_user_api: 'import_user_api',
remove_user_api: 'remove_user_api',
set_user_api: 'set_user_api',
get_user_api_list: 'get_user_api_list',
request_user_api: 'request_user_api',
request_user_api_cancel: 'request_user_api_cancel',
get_user_api_status: 'get_user_api_status',
user_api_status: 'user_api_status',
get_lyric: 'get_lyric',
save_lyric: 'save_lyric',
clear_lyric: 'clear_lyric',
get_music_url: 'get_music_url',
save_music_url: 'save_music_url',
clear_music_url: 'clear_music_url',
sync_enable: 'sync_enable',
sync_status: 'sync_status',
sync_get_status: 'sync_get_status',
sync_generate_code: 'sync_generate_code',
sync_action_list: 'sync_action_list',
sync_list: 'sync_list',
},
winLyric: {
close: 'close',
set_lyric_info: 'set_lyric_info',
get_lyric_info: 'get_lyric_info',
set_lyric_config: 'set_lyric_config',
get_lyric_config: 'get_lyric_config',
set_win_bounds: 'set_win_bounds',
key_down: 'key_down',
},
hotKey: {
enable: 'enable',
status: 'status',
set_config: 'set_config',
},
}
for (const item of Object.keys(names)) {
let name = names[item]
for (const key of Object.keys(name)) {
name[key] = `${item}_${name[key]}`
}
}
exports.mainWindow = names.mainWindow
exports.winLyric = names.winLyric
exports.hotKey = names.hotKey

42
src/common/store.js Normal file
View File

@@ -0,0 +1,42 @@
const Store = require('electron-store')
const { dialog, app, shell } = require('electron')
const path = require('path')
const fs = require('fs')
const log = require('electron-log')
const stores = {}
/**
* 获取 Store 对象
* @param {*} name store 名
* @param {*} isIgnoredError 是否忽略错误
* @param {*} isShowErrorAlert 是否显示错误弹窗
* @returns Store
*/
module.exports = (name, isIgnoredError = true, isShowErrorAlert = true) => {
if (stores[name]) return stores[name]
let store
try {
store = stores[name] = new Store({ name, clearInvalidConfig: false })
} catch (error) {
log.error(error)
if (!isIgnoredError) throw error
const backPath = path.join(app.getPath('userData'), name + '.json.bak')
fs.copyFileSync(path.join(app.getPath('userData'), name + '.json'), backPath)
if (isShowErrorAlert) {
dialog.showMessageBoxSync({
type: 'error',
message: name + ' data load error',
detail: `We have helped you back up the old ${name} file to: ${backPath}\nYou can try to repair and restore it manually\n\nError detail: ${error.message}`,
})
shell.showItemInFolder(backPath)
}
store = new Store({ name, clearInvalidConfig: true })
}
return store
}

View File

@@ -1,8 +1,217 @@
const log = require('electron-log')
const { defaultSetting, overwriteSetting } = require('./defaultSetting')
// const apiSource = require('../renderer/utils/music/api-source-info')
const getStore = require('./store')
const defaultHotKey = require('./defaultHotKey')
exports.isLinux = process.platform == 'linux'
exports.isWin = process.platform == 'win32'
exports.isMac = process.platform == 'darwin'
/**
* 生成节流函数
* @param {*} fn
* @param {*} delay
*/
exports.throttle = (fn, delay = 100) => {
let timer = null
let _args = null
return function(...args) {
_args = args
if (timer) return
timer = setTimeout(() => {
timer = null
fn.apply(this, _args)
}, delay)
}
}
/**
* 生成防抖函数
* @param {*} fn
* @param {*} delay
*/
exports.debounce = (fn, delay = 100) => {
let timer = null
let _args = null
return function(...args) {
_args = args
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
timer = null
fn.apply(this, _args)
}, delay)
}
}
exports.log = log
// https://stackoverflow.com/a/53387532
exports.compareVer = (currentVer, targetVer) => {
// treat non-numerical characters as lower version
// replacing them with a negative number based on charcode of each character
const fix = s => `.${s.toLowerCase().charCodeAt(0) - 2147483647}.`
currentVer = ('' + currentVer).replace(/[^0-9.]/g, fix).split('.')
targetVer = ('' + targetVer).replace(/[^0-9.]/g, fix).split('.')
let c = Math.max(currentVer.length, targetVer.length)
for (let i = 0; i < c; i++) {
// convert to integer the most efficient way
currentVer[i] = ~~currentVer[i]
targetVer[i] = ~~targetVer[i]
if (currentVer[i] > targetVer[i]) return 1
else if (currentVer[i] < targetVer[i]) return -1
}
return 0
}
exports.isObject = item => item && typeof item === 'object' && !Array.isArray(item)
/**
* 对象深度合并
* @param {} target 要合并源对象
* @param {} source 要合并目标对象
*/
exports.objectDeepMerge = (target, source, mergedObj) => {
if (!mergedObj) {
mergedObj = new Set()
mergedObj.add(target)
}
let base = {}
Object.keys(source).forEach(item => {
if (exports.isObject(source[item])) {
if (mergedObj.has(source[item])) return
if (!exports.isObject(target[item])) target[item] = {}
mergedObj.add(source[item])
exports.objectDeepMerge(target[item], source[item], mergedObj)
return
}
base[item] = source[item]
})
Object.assign(target, base)
}
exports.mergeSetting = (setting, version) => {
let defaultSettingCopy = JSON.parse(JSON.stringify(defaultSetting))
let overwriteSettingCopy = JSON.parse(JSON.stringify(overwriteSetting))
const defaultVersion = defaultSettingCopy.version
if (!version) {
if (setting) {
version = setting.version
delete setting.version
}
}
if (!setting) {
setting = defaultSettingCopy
} else if (exports.compareVer(version, defaultVersion) < 0) {
exports.objectDeepMerge(defaultSettingCopy, setting)
exports.objectDeepMerge(defaultSettingCopy, overwriteSettingCopy)
setting = defaultSettingCopy
}
// if (!apiSource.some(api => api.id === setting.apiSource && !api.disabled)) {
// let api = apiSource.find(api => !api.disabled)
// if (api) setting.apiSource = api.id
// }
return { setting, version: defaultVersion }
}
/**
* 初始化设置
* @param {*} setting
* @param {*} isShowErrorAlert
*/
exports.initSetting = isShowErrorAlert => {
const electronStore_list = getStore('playList', true, isShowErrorAlert)
const electronStore_config = getStore('config')
const electronStore_downloadList = getStore('downloadList')
let setting = electronStore_config.get('setting')
if (setting) {
let version = electronStore_config.get('version')
if (!version) { // 迁移配置
version = electronStore_config.get('setting.version')
electronStore_config.set('version', version)
electronStore_config.delete('setting.version')
const list = electronStore_config.get('list')
if (list) {
if (list.defaultList) electronStore_list.set('defaultList', list.defaultList)
if (list.loveList) electronStore_list.set('loveList', list.loveList)
electronStore_config.delete('list')
}
const downloadList = electronStore_config.get('download')
if (downloadList) {
if (downloadList.list) electronStore_downloadList.set('list', downloadList.list)
electronStore_config.delete('download')
}
}
// 迁移列表滚动位置设置 ~0.18.3
if (setting.list.scroll) {
let scroll = setting.list.scroll
electronStore_list.set('defaultList.location', scroll.locations.default || 0)
electronStore_list.set('loveList.location', scroll.locations.love || 0)
electronStore_config.delete('setting.list.scroll')
electronStore_config.set('setting.list.isSaveScrollLocation', scroll.enable)
delete setting.list.scroll
}
if (setting.player.isShowLyricTransition != null) { // 修正拼写问题 v1.8.2 及以前
setting.player.isShowLyricTranslation = setting.player.isShowLyricTransition
delete setting.player.isShowLyricTransition
}
}
// 从我的列表分离下载列表 v1.7.0 后
let downloadList = electronStore_list.get('downloadList')
if (downloadList) {
electronStore_downloadList.set('list', downloadList)
electronStore_list.delete('downloadList')
}
const { version: settingVersion, setting: newSetting } = exports.mergeSetting(setting, electronStore_config.get('version'))
// 重置 ^0.18.2 排行榜ID
if (!newSetting.leaderboard.tabId.includes('__')) newSetting.leaderboard.tabId = 'kw__16'
// newSetting.controlBtnPosition = 'right'
electronStore_config.set({ version: settingVersion, setting: newSetting })
return { version: settingVersion, setting: newSetting }
}
/**
* 初始化快捷键设置
*/
exports.initHotKey = () => {
const electronStore_hotKey = getStore('hotKey')
let localConfig = electronStore_hotKey.get('local')
if (!localConfig) {
localConfig = defaultHotKey.local
electronStore_hotKey.set('local', localConfig)
}
let globalConfig = electronStore_hotKey.get('global')
// 移除v1.0.1及之前设置的全局声音媒体快捷键接管
if (globalConfig && globalConfig.keys.VolumeUp) {
delete globalConfig.keys.VolumeUp
delete globalConfig.keys.VolumeDown
delete globalConfig.keys.VolumeMute
electronStore_hotKey.set('global', globalConfig)
}
if (!globalConfig) {
globalConfig = defaultHotKey.global
electronStore_hotKey.set('global', globalConfig)
}
return {
global: globalConfig,
local: localConfig,
}
}

17
src/main/env/cmdParams.js vendored Normal file
View File

@@ -0,0 +1,17 @@
const parseEnv = () => {
const params = {}
const rx = /^-\w+/
for (let param of process.argv) {
if (!rx.test(param)) continue
param = param.substring(1)
let index = param.indexOf('=')
if (index < 0) {
params[param] = true
} else {
params[param.substring(0, index)] = param.substring(index + 1)
}
}
return params
}
global.envParams.cmdParams = parseEnv()

4
src/main/env/index.js vendored Normal file
View File

@@ -0,0 +1,4 @@
global.envParams = {}
require('./cmdParams')
require('./screen')

11
src/main/env/screen.js vendored Normal file
View File

@@ -0,0 +1,11 @@
const { app, screen } = require('electron')
const initScreenParams = () => {
global.envParams.workAreaSize = screen.getPrimaryDisplay().workAreaSize
}
app.on('ready', () => {
screen.on('display-metrics-changed', initScreenParams)
initScreenParams()
})

34
src/main/event.js Normal file
View File

@@ -0,0 +1,34 @@
const { common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = require('./events/_name')
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('./../common/ipc')
const { getAppHotKeyConfig } = require('./utils')
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, name => {
if (MAIN_WINDOW_EVENT_NAME.name === name) return
if (global.modules.mainWindow) mainSend(global.modules.mainWindow, ipcMainWindowNames.set_config, global.appSetting)
})
global.lx_event.common.on(COMMON_EVENT_NAME.initHotKey, () => {
global.appHotKey.config = getAppHotKeyConfig()
})
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.quit, () => {
global.isTrafficLightClose = false
global.isQuitting = true
global.modules.mainWindow.close()
})
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.toggle_minimize, () => {
if (global.modules.mainWindow.isMinimized()) {
if (!global.modules.mainWindow.isVisible()) {
global.modules.mainWindow.show()
}
global.modules.mainWindow.restore()
global.modules.mainWindow.focus()
} else {
global.modules.mainWindow.minimize()
}
})
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.toggle_hide, () => {
global.modules.mainWindow.isVisible()
? global.modules.mainWindow.hide()
: global.modules.mainWindow.show()
})

20
src/main/events/Common.js Normal file
View File

@@ -0,0 +1,20 @@
const { EventEmitter } = require('events')
const { common: COMMON_EVENT_NAME } = require('./_name')
class Common extends EventEmitter {
initSetting() {
this.emit(COMMON_EVENT_NAME.initConfig)
this.configStatus(null)
}
configStatus(name) {
this.emit(COMMON_EVENT_NAME.configStatus, name)
}
saveMyList(data) {
this.emit(COMMON_EVENT_NAME.saveMyList, data)
}
}
module.exports = Common

21
src/main/events/HotKey.js Normal file
View File

@@ -0,0 +1,21 @@
const { EventEmitter } = require('events')
const { hotKey: HOT_KEY_EVENT_NAME } = require('./_name')
const { saveAppHotKeyConfig } = require('../utils')
class HotKey extends EventEmitter {
init() {
this.emit(HOT_KEY_EVENT_NAME.init)
}
saveConfig(config, source) {
if (config) saveAppHotKeyConfig(config)
this.emit(HOT_KEY_EVENT_NAME.config, config, source)
}
keyDown(info) {
this.emit(HOT_KEY_EVENT_NAME.keyDown, info)
}
}
module.exports = HotKey

View File

@@ -0,0 +1,34 @@
const { EventEmitter } = require('events')
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('./_name')
class MainWindow extends EventEmitter {
setLyricInfo(info) {
this.emit(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info)
}
quit() {
this.emit(MAIN_WINDOW_EVENT_NAME.quit)
}
toggleMinimize() {
this.emit(MAIN_WINDOW_EVENT_NAME.toggle_minimize)
}
toggleHide() {
this.emit(MAIN_WINDOW_EVENT_NAME.toggle_hide)
}
readyToShow() {
this.emit(MAIN_WINDOW_EVENT_NAME.ready_to_show)
}
show() {
this.emit(MAIN_WINDOW_EVENT_NAME.show)
}
hide() {
this.emit(MAIN_WINDOW_EVENT_NAME.hide)
}
}
module.exports = MainWindow

View File

@@ -0,0 +1,15 @@
const { EventEmitter } = require('events')
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('./_name')
// const { updateSetting } = require('../utils')
class WinLyric extends EventEmitter {
create() {
this.emit(WIN_LYRIC_EVENT_NAME.create)
}
close() {
this.emit(WIN_LYRIC_EVENT_NAME.close)
}
}
module.exports = WinLyric

View File

@@ -1,5 +1,36 @@
exports.common = {
initConfig: 'initConfig',
configStatus: 'config',
saveMyList: 'saveMyList',
}
exports.mainWindow = {
name: 'mainWindow',
setLyricInfo: 'setLyricInfo',
destroy: 'destroy',
quit: 'quit',
toggle_minimize: 'toggle_minimize',
toggle_hide: 'toggle_hide',
ready_to_show: 'ready_to_show',
show: 'show',
hide: 'hide',
}
exports.tray = {
name: 'tray',
create: 'create',
destroy: 'destroy',
}
exports.winLyric = {
name: 'winLyric',
create: 'create',
close: 'close',
}
exports.hotKey = {
name: 'hotKey',
init: 'init',
config: 'config',
keyDown: 'keyDown',
}

View File

@@ -1,5 +1,19 @@
global.lx_event = {}
if (!global.lx_event) global.lx_event = {}
const Tray = require('./tray')
const Common = require('./Common')
const MainWindow = require('./MainWindow')
const Tray = require('./Tray')
const WinLyric = require('./WinLyric')
const HotKey = require('./HotKey')
const { Event: UserApi } = require('../modules/userApi')
const { Event: Sync } = require('../modules/sync')
if (!global.lx_event.common) global.lx_event.common = new Common()
if (!global.lx_event.mainWindow) global.lx_event.mainWindow = new MainWindow()
if (!global.lx_event.tray) global.lx_event.tray = new Tray()
if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey()
if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi()
if (!global.lx_event.sync) global.lx_event.sync = new Sync()

View File

@@ -4,16 +4,18 @@ const path = require('path')
// 单例应用程序
if (!app.requestSingleInstanceLock()) {
app.quit()
return
process.exit(0)
}
if (!global.modules) global.modules = {}
app.on('second-instance', (event, argv, cwd) => {
if (global.mainWindow) {
if (global.mainWindow.isMinimized()) {
global.mainWindow.restore()
} else if (global.mainWindow.isVisible()) {
global.mainWindow.focus()
if (global.modules.mainWindow) {
if (global.modules.mainWindow.isMinimized()) {
global.modules.mainWindow.restore()
}
if (global.modules.mainWindow.isVisible()) {
global.modules.mainWindow.focus()
} else {
global.mainWindow.show()
global.modules.mainWindow.show()
}
} else {
app.quit()
@@ -21,7 +23,31 @@ app.on('second-instance', (event, argv, cwd) => {
})
const isDev = global.isDev = process.env.NODE_ENV !== 'production'
require('./env')
// console.log(global.envParams.cmdParams)
// Is disable hardware acceleration
if (global.envParams.cmdParams.dha) app.disableHardwareAcceleration()
if (global.envParams.cmdParams.dt == null && global.envParams.cmdParams.nt != null) global.envParams.cmdParams.dt = global.envParams.cmdParams.nt
if (global.envParams.cmdParams.dhmkh) app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling')
// fix linux transparent fail. https://github.com/electron/electron/issues/25153#issuecomment-843688494
if (process.platform == 'linux') app.commandLine.appendSwitch('use-gl', 'desktop')
// https://github.com/electron/electron/issues/22691
app.commandLine.appendSwitch('wm-window-animations-disabled')
const { navigationUrlWhiteList } = require('../common/config')
const { getWindowSizeInfo, initSetting, updateSetting } = require('./utils')
const { isMac, isLinux, initHotKey } = require('../common/utils')
// https://github.com/electron/electron/issues/18397
// 开发模式下为true时 多次引入native模块会导致渲染进程卡死
// https://github.com/electron/electron/issues/22791
app.allowRendererProcessReuse = !isDev
app.on('web-contents-created', (event, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
@@ -29,11 +55,12 @@ app.on('web-contents-created', (event, contents) => {
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) return event.preventDefault()
console.log('navigation to url:', navigationUrl)
})
contents.on('new-window', async(event, navigationUrl) => {
event.preventDefault()
if (/^devtools/.test(navigationUrl)) return
console.log(navigationUrl)
await shell.openExternal(navigationUrl)
contents.setWindowOpenHandler(({ url }) => {
if (!/^devtools/.test(url) && /^https?:\/\//.test(url)) {
shell.openExternal(url)
}
console.log(url)
return { action: 'deny' }
})
contents.on('will-attach-webview', (event, webPreferences, params) => {
// Strip away preload scripts if unused or verify their location is legitimate
@@ -50,22 +77,13 @@ app.on('web-contents-created', (event, contents) => {
})
})
// https://github.com/electron/electron/issues/22691
app.commandLine.appendSwitch('wm-window-animations-disabled')
// https://github.com/electron/electron/issues/18397
app.allowRendererProcessReuse = !isDev
const { getAppSetting, parseEnv, getWindowSizeInfo } = require('./utils')
global.envParams = parseEnv()
require('../common/error')
require('./events')
require('./event')
require('./rendererEvents')
const winEvent = require('./rendererEvents/winEvent')
const autoUpdate = require('./utils/autoUpdate')
const { isMac, isLinux } = require('../common/utils')
let winURL
@@ -76,7 +94,7 @@ if (isDev) {
winURL = 'http://localhost:9080'
} else {
global.__static = path.join(__dirname, '/static')
winURL = `file://${__dirname}/index.html`
winURL = `file://${path.join(__dirname, 'index.html')}`
}
function createWindow() {
@@ -84,12 +102,12 @@ function createWindow() {
/**
* Initial window options
*/
global.mainWindow = new BrowserWindow({
global.modules.mainWindow = new BrowserWindow({
height: windowSizeInfo.height,
useContentSize: true,
width: windowSizeInfo.width,
frame: false,
transparent: !isLinux && !global.envParams.nt,
transparent: !global.envParams.cmdParams.dt,
enableRemoteModule: false,
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
resizable: false,
@@ -97,38 +115,55 @@ function createWindow() {
fullscreenable: false,
show: false,
webPreferences: {
// contextIsolation: true,
contextIsolation: false,
webSecurity: !isDev,
nodeIntegration: true,
},
})
global.mainWindow.loadURL(winURL)
global.modules.mainWindow.loadURL(winURL)
winEvent(global.mainWindow)
// mainWindow.webContents.openDevTools()
winEvent(global.modules.mainWindow)
// global.modules.mainWindow.webContents.openDevTools()
if (!isDev) autoUpdate()
}
function init() {
global.appSetting = getAppSetting()
createWindow()
global.lx_event.tray.create()
global.appHotKey = {
enable: true,
config: {},
state: null,
}
app.on('ready', init)
global.lx_core = {
setAppConfig(setting, name) {
updateSetting(setting)
global.lx_event.common.configStatus(name)
},
}
function init() {
console.log('init')
initSetting()
global.appHotKey.config = initHotKey()
global.lx_event.common.initSetting()
global.lx_event.hotKey.init()
createWindow()
}
// https://github.com/electron/electron/issues/16809
app.on('ready', isLinux ? () => setTimeout(init, 300) : init)
app.on('activate', () => {
if (global.mainWindow) {
if (global.mainWindow.isMinimized()) {
global.mainWindow.restore()
} else if (global.mainWindow.isVisible()) {
global.mainWindow.focus()
if (global.modules.mainWindow) {
if (global.modules.mainWindow.isMinimized()) {
global.modules.mainWindow.restore()
} else if (global.modules.mainWindow.isVisible()) {
global.modules.mainWindow.focus()
} else {
global.mainWindow.show()
global.modules.mainWindow.show()
}
} else if (global.mainWindow === null) {
} else if (global.modules.mainWindow === null) {
init()
}
})

View File

@@ -9,8 +9,6 @@ if (isMac) {
{ label: '关于洛雪音乐', role: 'about' },
{ type: 'separator' },
{ label: '隐藏', role: 'hide' },
{ label: '显示其他', role: 'hideothers' },
{ label: '显示全部', role: 'unhide' },
{ type: 'separator' },
{
label: '退出',
@@ -26,20 +24,20 @@ if (isMac) {
label: '窗口',
role: 'window',
submenu: [
{ label: '最小化', role: 'minimize' },
{ label: '最小化', role: 'minimize', accelerator: 'Command+W' },
{ label: '关闭', role: 'close' },
],
},
{
label: '编辑',
submenu: [
{ label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
{ label: '恢复', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
{ label: '撤销', accelerator: 'Command+Z', role: 'undo' },
{ label: '恢复', accelerator: 'Shift+Command+Z', role: 'redo' },
{ type: 'separator' },
{ label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' },
{ label: '复制', accelerator: 'CmdOrCtrl+C', role: 'copy' },
{ label: '粘贴', accelerator: 'CmdOrCtrl+V', role: 'paste' },
{ label: '选择全部', accelerator: 'CmdOrCtrl+A', role: 'selectAll' },
{ label: '剪切', accelerator: 'Command+X', role: 'cut' },
{ label: '复制', accelerator: 'Command+C', role: 'copy' },
{ label: '粘贴', accelerator: 'Command+V', role: 'paste' },
{ label: '选择全部', accelerator: 'Command+A', role: 'selectAll' },
],
},
]

View File

@@ -0,0 +1,12 @@
const { app } = require('electron')
const { init, unRegisterHotkeyAll } = require('./utils')
const { hotKey: HOT_KEY_EVENT_NAME } = require('../../events/_name')
global.appHotKey.state = {}
app.on('will-quit', unRegisterHotkeyAll)
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.init, init)
require('./rendererEvent')

View File

@@ -0,0 +1,28 @@
const {
mainHandle,
NAMES: {
hotKey: ipcHotKeyNames,
},
} = require('../../../common/ipc')
const { init, registerHotkey, unRegisterHotkey, unRegisterHotkeyAll } = require('./utils')
mainHandle(ipcHotKeyNames.set_config, async(event, { action, data, source }) => {
switch (action) {
case 'config':
global.lx_event.hotKey.saveConfig(data, source)
return true
case 'enable':
data ? init(true) : unRegisterHotkeyAll()
return true
case 'register':
return registerHotkey(data)
case 'unregister':
return unRegisterHotkey(data)
}
})
mainHandle(ipcHotKeyNames.status, async() => global.appHotKey.state)
mainHandle(ipcHotKeyNames.enable, async(event, flag) => {
flag ? init() : unRegisterHotkeyAll()
})

View File

@@ -0,0 +1,67 @@
const { globalShortcut } = require('electron')
const { log } = require('../../../common/utils')
const handleKeyDown = key => {
if (!global.appHotKey.enable) return
global.lx_event.hotKey.keyDown({ type: 'global', key })
}
const transformedKeyRxp = /(^|\+)[a-z]/g
const transformedKey = key => {
if (key.includes('arrow')) key = key.replace(/arrow/g, '')
return key.replace('mod', 'CommandOrControl').replace(transformedKeyRxp, l => l.toUpperCase())
}
exports.registerHotkey = ({ key, info }) => {
if (global.appHotKey.state[key] && global.appHotKey.state[key].status) return true
let transKey = transformedKey(key)
// console.log('Register key:', transKey)
if (!global.appHotKey.state[key]) {
global.appHotKey.state[key] = {
status: false,
info: null,
}
}
global.appHotKey.state[key].info = info
let status = global.appHotKey.state[key].status = globalShortcut.isRegistered(transKey)
? false
: globalShortcut.register(transKey, () => {
handleKeyDown(key)
})
return status
}
exports.unRegisterHotkey = key => {
let transKey = transformedKey(key)
// console.log('Unregister key:', transKey)
globalShortcut.unregister(transKey)
delete global.appHotKey.state[key]
}
exports.unRegisterHotkeyAll = () => {
global.appHotKey.state = {}
globalShortcut.unregisterAll()
}
exports.handleKeyDown = handleKeyDown
exports.transformedKey = transformedKey
const handleRegisterHotkey = data => {
let ret = exports.registerHotkey(data)
if (!ret) log.info('Register hot key failed:', data.key)
}
exports.init = (isForce = false) => {
exports.unRegisterHotkeyAll()
if (!isForce && !global.appHotKey.config.global.enable) return
global.appHotKey.state = {}
// console.log(global.appHotKey.config.global.keys)
for (const key of Object.keys(global.appHotKey.config.global.keys)) {
try {
handleRegisterHotkey({ key, info: global.appHotKey.config.global.keys[key] })
} catch (err) {
log.info(err)
}
}
}

View File

@@ -1,2 +1,5 @@
require('./appMenu')
require('./winLyric')
require('./tray')
require('./hotKey')
require('./userApi')

View File

@@ -0,0 +1,23 @@
const { EventEmitter } = require('events')
const SYNC_EVENT_NAME = require('./name')
class Sync extends EventEmitter {
status(status) {
this.emit(SYNC_EVENT_NAME.status, status)
}
sync_list(data) {
this.emit(SYNC_EVENT_NAME.sync_list, data)
}
sync_handle_list(data) {
this.emit(SYNC_EVENT_NAME.sync_handle_list, data)
}
action_list(data) {
this.emit(SYNC_EVENT_NAME.sync_action_list, data)
}
}
module.exports = Sync

View File

@@ -0,0 +1,6 @@
module.exports = {
sync_action_list: 'sync_action_list',
sync_list: 'sync_list',
sync_handle_list: 'sync_handle_list',
status: 'status',
}

View File

@@ -0,0 +1,15 @@
const Event = require('./event/event')
const eventNames = require('./event/name')
const modules = require('./modules')
const { startServer, stopServer, getStatus, generateCode } = require('./server/server')
module.exports = {
startServer,
stopServer,
getStatus,
generateCode,
Event,
eventNames,
modules,
}

View File

@@ -0,0 +1 @@
exports.list = require('./list')

View File

@@ -0,0 +1,41 @@
const { encryptMsg, decryptMsg } = require('../server/utils')
let io
const handleListAction = ({ action, data }) => {
// console.log(action, data)
global.lx_event.sync.action_list({ action, data })
}
// const addMusic = (orderId, callback) => {
// // ...
// }
const broadcast = async(action, data, excludeIds = []) => {
if (!io) return
const sockets = await io.fetchSockets()
for (const socket of sockets) {
if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
socket.emit(action, encryptMsg(socket.data.keyInfo, data))
}
}
exports.sendListAction = (action, data) => {
// io.sockets
return broadcast('list:action', JSON.stringify({ action, data }))
}
exports.registerListHandler = (_io, socket) => {
io = _io
socket.on('list:action', msg => {
// console.log(msg)
msg = decryptMsg(socket.data.keyInfo, msg)
if (!msg) return
handleListAction(JSON.parse(msg))
broadcast('list:action', msg, [socket.data.keyInfo.clientId])
// socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
})
// socket.on('list:add', addMusic)
}
exports.unregisterListHandler = () => {
io = null
}

View File

@@ -0,0 +1,69 @@
const { aesEncrypt, aesDecrypt, createClientKeyInfo, getClientKeyInfo, setClientKeyInfo } = require('./utils')
const authMsg = 'lx-music auth::'
const helloMsg = 'Hello~::^-^::'
exports.authCode = async(req, res, authCode) => {
let code = 401
let msg = 'Forbidden'
// console.log(req.headers)
if (req.headers.m) {
label:
if (req.headers.i) {
const keyInfo = getClientKeyInfo(req.headers.i)
if (!keyInfo) break label
let text
try {
text = aesDecrypt(req.headers.m, keyInfo.key, keyInfo.iv)
} catch (err) {
break label
}
console.log(text)
if (text.startsWith(authMsg)) {
code = 200
const deviceName = text.replace(authMsg, '') || 'Unknown'
if (deviceName != keyInfo.deviceName) {
keyInfo.deviceName = deviceName
setClientKeyInfo(keyInfo)
}
msg = aesEncrypt(helloMsg, keyInfo.key, keyInfo.iv)
}
} else {
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
key = Buffer.from(key).toString('base64')
// console.log(authCode, key, iv)
let text
try {
text = aesDecrypt(req.headers.m, key, iv)
} catch (err) {
break label
}
console.log(text)
if (text.startsWith(authMsg)) {
code = 200
const deviceName = text.replace(authMsg, '') || 'Unknown'
msg = aesEncrypt(JSON.stringify(createClientKeyInfo(deviceName)), key, iv)
}
}
}
res.writeHead(code)
res.end(msg)
}
exports.authConnect = async req => {
const { i, t } = req._query
label:
if (i && t) {
const keyInfo = getClientKeyInfo(i)
if (!keyInfo) break label
let text
try {
text = aesDecrypt(t, keyInfo.key, keyInfo.iv)
} catch (err) {
break label
}
if (text == 'lx-music connect') return
}
throw new Error('failed')
}

View File

@@ -0,0 +1,7 @@
const { startServer, stopServer, getStatus } = require('./server')
module.exports = {
startServer,
stopServer,
getStatus,
}

View File

@@ -0,0 +1,172 @@
const http = require('http')
const sio = require('socket.io')
const { createHttpTerminator } = require('http-terminator')
const modules = require('../modules')
const { authCode, authConnect } = require('./auth')
const { getAddress, getServerId, generateCode, getClientKeyInfo } = require('./utils')
const syncList = require('./syncList')
let status = {
status: false,
message: '',
address: [],
code: '',
devices: [],
}
const handleConnection = (io, socket) => {
console.log('connection')
// console.log(socket.handshake.query)
for (const module of Object.values(modules)) {
module.registerListHandler(io, socket)
}
}
const authConnection = (req, callback) => {
// console.log(req.headers)
// // console.log(req.auth)
// console.log(req._query.authCode)
authConnect(req).then(() => {
callback(null, true)
}).catch(err => {
callback(err, false)
})
}
let httpTerminator = null
let io = null
const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
const httpServer = http.createServer((req, res) => {
// console.log(req.url)
let code
let msg
switch (req.url) {
case '/hello':
code = 200
msg = 'Hello~::^-^::'
break
case '/id':
code = 200
msg = 'OjppZDo6' + getServerId()
break
case '/ah':
authCode(req, res, status.code)
break
default:
code = 401
msg = 'Forbidden'
break
}
if (!code) return
res.writeHead(code)
res.end(msg)
})
httpTerminator = createHttpTerminator({
server: httpServer,
})
io = sio(httpServer, {
path: '/sync',
serveClient: false,
connectTimeout: 10000,
pingTimeout: 30000,
maxHttpBufferSize: 3e6,
allowRequest: authConnection,
transports: ['websocket'],
})
io.on('connection', async socket => {
socket.on('disconnect', reason => {
console.log('disconnect', reason)
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo.clientId), 1)
global.lx_event.sync.status(status)
})
const keyInfo = getClientKeyInfo(socket.handshake.query.i)
// socket.lx_keyInfo = keyInfo
socket.data.keyInfo = keyInfo
try {
await syncList(io, socket)
} catch (err) {
console.log(err)
return
}
status.devices.push(keyInfo)
handleConnection(io, socket, keyInfo)
global.lx_event.sync.status(status)
})
httpServer.on('error', error => {
console.log(error)
reject(error)
})
httpServer.on('listening', () => {
const addr = httpServer.address()
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
console.info(`Listening on ${bind}`)
resolve()
})
httpServer.listen(port)
})
const handleStopServer = async() => {
if (!httpTerminator) return
await io.close()
await httpTerminator.terminate().catch(() => {})
io = null
httpTerminator = null
}
exports.stopServer = async() => {
if (!status.status) {
status.status = false
status.message = ''
status.address = []
status.code = ''
global.lx_event.sync.status(status)
return
}
console.log('stoping sync server...')
return handleStopServer().then(() => {
console.log('sync server stoped')
status.status = false
status.message = ''
status.address = []
status.code = ''
}).catch(err => {
console.log(err)
status.message = err.message
}).finally(() => {
global.lx_event.sync.status(status)
})
}
exports.startServer = async port => {
if (status.status) await handleStopServer()
console.log('starting sync server...')
return handleStartServer(port).then(() => {
console.log('sync server started')
status.status = true
status.message = ''
status.address = getAddress()
status.code = generateCode()
}).catch(err => {
console.log(err)
status.status = false
status.message = err.message
status.address = []
status.code = ''
}).finally(() => {
global.lx_event.sync.status(status)
})
}
exports.getStatus = () => status
exports.generateCode = async() => {
status.code = generateCode()
global.lx_event.sync.status(status)
return status.code
}

View File

@@ -0,0 +1,394 @@
const path = require('path')
const fs = require('fs')
const fsPromises = fs.promises
const { app } = require('electron')
const { encryptMsg, decryptMsg } = require('./utils')
const SYNC_EVENT_NAMES = require('../event/name')
const { common: COMMON_EVENT_NAME } = require('@main/events/_name')
const { throttle } = require('@common/utils')
let io
let syncingId = null
const wait = (time = 1000) => new Promise((resolve, reject) => setTimeout(resolve, time))
const getRemoteListData = socket => new Promise((resolve, reject) => {
console.log('getRemoteListData')
const handleError = reason => {
reject(new Error(reason))
}
const handleSuccess = enData => {
socket.removeListener('disconnect', handleError)
socket.removeListener('list:sync', handleSuccess)
console.log('getRemoteListData', 'handleSuccess')
const data = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
if (!data) return reject(new Error('Get remote list data failed'))
if (data.action != 'getData') return
resolve(data.data)
}
socket.on('disconnect', handleError)
socket.on('list:sync', handleSuccess)
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'getData', data: 'all' })))
})
const getLocalListData = () => new Promise((resolve, reject) => {
const handleSuccess = ({ action, data }) => {
if (action !== 'getData') return
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
resolve(data)
}
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
global.lx_event.sync.sync_list({
action: 'getData',
})
})
const getSyncMode = keyInfo => new Promise((resolve, reject) => {
const handleSuccess = ({ action, data }) => {
if (action !== 'selectMode') return
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
resolve(data)
}
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
global.lx_event.sync.sync_list({
action: 'selectMode',
data: keyInfo,
})
})
const finishedSync = socket => {
return socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({
action: 'finished',
})))
}
const setLocalList = listData => {
global.lx_event.sync.sync_list({
action: 'setData',
data: listData,
})
}
const setRemotelList = async(socket, listData) => {
if (!io) return
const sockets = await io.fetchSockets()
for (const socket of sockets) {
// if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'setData', data: listData })))
}
}
let writeFilePromises = {}
const updateSnapshot = (path, data) => {
console.log('updateSnapshot', path)
let writeFilePromise = writeFilePromises[path] || Promise.resolve()
return writeFilePromise.then(() => {
writeFilePromise = writeFilePromises[path] = fsPromises.writeFile(path, data)
return writeFilePromise
})
}
const createListDataObj = listData => {
const listDataObj = {}
for (const list of listData.userList) listDataObj[list.id] = list
return listDataObj
}
const handleMergeList = (sourceList, targetList, addMusicLocationType) => {
let newList
switch (addMusicLocationType) {
case 'top':
newList = [...targetList.list, ...sourceList.list]
break
case 'bottom':
default:
newList = [...sourceList.list, ...targetList.list]
break
}
const map = {}
const ids = []
switch (addMusicLocationType) {
case 'top':
newList = [...targetList.list, ...sourceList.list]
for (let i = newList.length - 1; i > -1; i--) {
const item = newList[i]
if (map[item.songmid]) continue
ids.unshift(item.songmid)
map[item.songmid] = item
}
break
case 'bottom':
default:
newList = [...sourceList.list, ...targetList.list]
for (const item of newList) {
if (map[item.songmid]) continue
ids.push(item.songmid)
map[item.songmid] = item
}
break
}
return {
...sourceList,
list: ids.map(id => map[id]),
}
}
const mergeList = (sourceListData, targetListData) => {
const addMusicLocationType = global.appSetting.list.addMusicLocationType
const newListData = {}
newListData.defaultList = handleMergeList(sourceListData.defaultList, targetListData.defaultList, addMusicLocationType)
newListData.loveList = handleMergeList(sourceListData.loveList, targetListData.loveList, addMusicLocationType)
const listDataObj = createListDataObj(sourceListData)
newListData.userList = [...sourceListData.userList]
for (const list of targetListData.userList) {
const targetList = listDataObj[list.id]
if (targetList) {
targetList.list = handleMergeList(targetList, list, addMusicLocationType).list
} else {
newListData.userList.push(list)
}
}
return newListData
}
const overwriteList = (sourceListData, targetListData) => {
const newListData = {}
newListData.defaultList = sourceListData.defaultList
newListData.loveList = sourceListData.loveList
const listDataObj = createListDataObj(sourceListData)
newListData.userList = [...sourceListData.userList]
for (const list of targetListData.userList) {
const targetList = listDataObj[list.id]
if (targetList) continue
newListData.userList.push(list)
}
return newListData
}
const handleMergeListData = async socket => {
let isSelectingMode = false
const handleDisconnect = () => {
if (!isSelectingMode) return
global.lx_event.sync.sync_list({
action: 'closeSelectMode',
})
}
socket.on('disconnect', handleDisconnect)
isSelectingMode = true
const mode = await getSyncMode(socket.data.keyInfo)
isSelectingMode = false
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
console.log('handleMergeListData', 'remoteListData, localListData')
let listData
switch (mode) {
case 'merge_local_remote':
listData = mergeList(localListData, remoteListData)
break
case 'merge_remote_local':
listData = mergeList(remoteListData, localListData)
break
case 'overwrite_local_remote':
listData = overwriteList(localListData, remoteListData)
break
case 'overwrite_remote_local':
listData = overwriteList(remoteListData, localListData)
break
case 'overwrite_local_remote_full':
listData = localListData
break
case 'overwrite_remote_local_full':
listData = remoteListData
break
case 'none': return
case 'cancel':
socket.disconnect(true)
throw new Error('cancel')
}
return listData
}
const handleSyncList = async socket => {
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
console.log('handleSyncList', 'remoteListData, localListData')
const listData = {}
if (localListData.defaultList.list.length || localListData.loveList.list.length || localListData.userList.length) {
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
const mergedList = await handleMergeListData(socket)
console.log('handleMergeListData', 'mergedList')
console.log(mergedList)
if (!mergedList) return
listData.defaultList = mergedList.defaultList
listData.loveList = mergedList.loveList
listData.userList = mergedList.userList
setLocalList(mergedList)
setRemotelList(socket, mergedList)
} else {
setRemotelList(socket, localListData)
listData.defaultList = localListData.defaultList
listData.loveList = localListData.loveList
listData.userList = localListData.userList
}
} else {
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
setLocalList(remoteListData)
listData.defaultList = remoteListData.defaultList
listData.loveList = remoteListData.loveList
listData.userList = remoteListData.userList
} else {
listData.defaultList = localListData.defaultList
listData.loveList = localListData.loveList
listData.userList = localListData.userList
}
}
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
defaultList: listData.defaultList,
loveList: listData.loveList,
userList: listData.userList,
})).then(() => {
socket.data.isCreatedSnapshot = true
return listData
})
}
const mergeListDataFromSnapshot = (sourceList, targetList, snapshotList, addMusicLocationType) => {
const removedListIds = new Set()
const sourceListItemIds = new Set()
const targetListItemIds = new Set()
for (const m of sourceList.list) sourceListItemIds.add(m.songmid)
for (const m of targetList.list) targetListItemIds.add(m.songmid)
for (const m of snapshotList.list) {
if (!sourceListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
}
for (const m of snapshotList.list) {
if (!targetListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
}
let newList
const map = {}
const ids = []
switch (addMusicLocationType) {
case 'top':
newList = [...targetList.list, ...sourceList.list]
for (let i = newList.length - 1; i > -1; i--) {
const item = newList[i]
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
ids.unshift(item.songmid)
map[item.songmid] = item
}
break
case 'bottom':
default:
newList = [...sourceList.list, ...targetList.list]
for (const item of newList) {
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
ids.push(item.songmid)
map[item.songmid] = item
}
break
}
return {
...sourceList,
list: ids.map(id => map[id]),
}
}
const handleMergeListDataFromSnapshot = async(socket, snapshot) => {
const addMusicLocationType = global.appSetting.list.addMusicLocationType
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
console.log('handleMergeListDataFromSnapshot', 'remoteListData, localListData')
const newListData = {}
newListData.defaultList = mergeListDataFromSnapshot(localListData.defaultList, remoteListData.defaultList, snapshot.defaultList, addMusicLocationType)
newListData.loveList = mergeListDataFromSnapshot(localListData.loveList, remoteListData.loveList, snapshot.loveList, addMusicLocationType)
const localUserListData = createListDataObj(localListData)
const remoteUserListData = createListDataObj(remoteListData)
const snapshotUserListData = createListDataObj(snapshot)
const removedListIds = new Set()
const localUserListIds = new Set()
const remoteUserListIds = new Set()
for (const l of localListData.userList) localUserListIds.add(l.id)
for (const l of remoteListData.userList) remoteUserListIds.add(l.id)
for (const l of snapshot.userList) {
if (!localUserListIds.has(l.id)) removedListIds.add(l.id)
}
for (const l of snapshot.userList) {
if (!remoteUserListIds.has(l.id)) removedListIds.add(l.id)
}
let newUserList = []
for (const list of localListData.userList) {
if (removedListIds.has(list.id)) continue
const remoteList = remoteUserListData[list.id]
let newList
if (remoteList) {
newList = mergeListDataFromSnapshot(list, remoteList, snapshotUserListData[list.id], addMusicLocationType)
} else {
newList = { ...list }
}
newUserList.push(newList)
}
for (const list of remoteListData.userList) {
if (removedListIds.has(list.id) || localUserListData[list.id]) continue
newUserList.push({ ...list })
}
newListData.userList = newUserList
setLocalList(newListData)
setRemotelList(socket, newListData)
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
defaultList: newListData.defaultList,
loveList: newListData.loveList,
userList: newListData.userList,
})).then(() => {
socket.data.isCreatedSnapshot = true
return newListData
})
}
const registerUpdateSnapshotTask = (socket, snapshot) => {
if (!socket.data.isCreatedSnapshot) return
const handleUpdateSnapshot = throttle(({ defaultList, loveList, userList }) => {
if (defaultList != null) snapshot.defaultList = defaultList
if (loveList != null) snapshot.loveList = loveList
if (userList != null) snapshot.userList = userList
updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot))
}, 10000)
global.lx_event.common.on(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
socket.on('disconnect', () => {
global.lx_event.common.off(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
})
}
const syncList = async socket => {
socket.data.snapshotFilePath = path.join(app.getPath('userData'), `snapshot-${Buffer.from(socket.data.keyInfo.clientId).toString('hex').substring(0, 10)}.json`)
let fileData
let isSyncRequired = false
try {
fileData = await fsPromises.readFile(socket.data.snapshotFilePath)
fileData = JSON.parse(fileData)
} catch (err) {
if (err.code !== 'ENOENT') throw err
isSyncRequired = true
}
console.log('isSyncRequired', isSyncRequired)
if (isSyncRequired) return handleSyncList(socket)
return handleMergeListDataFromSnapshot(socket, fileData)
}
const checkSyncQueue = async() => {
if (!syncingId) return
await wait()
return checkSyncQueue()
}
module.exports = async(_io, socket) => {
io = _io
await checkSyncQueue()
syncingId = socket.data.keyInfo.clientId
return syncList(socket).then(newListData => {
registerUpdateSnapshotTask(socket, { ...newListData })
return finishedSync(socket)
}).finally(() => {
syncingId = null
})
}

View File

@@ -0,0 +1,93 @@
const { networkInterfaces } = require('os')
const { randomBytes, createCipheriv, createDecipheriv } = require('crypto')
const getStore = require('@common/store')
const STORE_NAME = 'sync'
exports.getAddress = () => {
const nets = networkInterfaces()
const results = []
// console.log(nets)
for (const name of Object.keys(nets)) {
for (const net of nets[name]) {
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
if (net.family === 'IPv4' && !net.internal) {
results.push(net.address)
}
}
}
return results
}
let serverId
exports.getServerId = () => {
if (serverId) return serverId
const store = getStore(STORE_NAME)
serverId = store.get('serverId')
if (!serverId) {
serverId = randomBytes(4 * 4).toString('base64')
store.set('serverId', serverId)
}
return serverId
}
let keyInfos
exports.createClientKeyInfo = deviceName => {
const keyInfo = {
clientId: randomBytes(4 * 4).toString('base64'),
key: randomBytes(16).toString('base64'),
iv: randomBytes(16).toString('base64'),
deviceName,
}
const store = getStore(STORE_NAME)
if (!keyInfos) keyInfos = store.get('keys') || {}
if (Object.keys(keyInfos).length > 101) throw new Error('max keys')
keyInfos[keyInfo.clientId] = keyInfo
store.set('keys', keyInfos)
return keyInfo
}
exports.setClientKeyInfo = keyInfo => {
keyInfos[keyInfo.clientId] = keyInfo
const store = getStore(STORE_NAME)
store.set('keys', keyInfos)
}
exports.getClientKeyInfo = clientId => {
if (!keyInfos) {
const store = getStore(STORE_NAME)
keyInfos = store.get('keys') || {}
}
return keyInfos[clientId] || null
}
exports.generateCode = () => {
return Math.random().toString().substring(2, 8)
}
exports.aesEncrypt = (buffer, key, iv) => {
const cipher = createCipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
return Buffer.concat([cipher.update(buffer), cipher.final()]).toString('base64')
}
exports.aesDecrypt = (text, key, iv) => {
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
}
exports.encryptMsg = (keyInfo, msg) => {
return msg
// if (!keyInfo) return ''
// return exports.aesEncrypt(msg, keyInfo.key, keyInfo.iv)
}
exports.decryptMsg = (keyInfo, enMsg) => {
return enMsg
// if (!keyInfo) return ''
// let msg = ''
// try {
// msg = exports.aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
// } catch (err) {
// console.log(err)
// }
// return msg
}

View File

@@ -1,27 +1,59 @@
const { app, Tray, Menu } = require('electron')
const { isWin } = require('../../common/utils')
const { tray: TRAY_EVENT_NAME } = require('../events/_name')
const { tray: TRAY_EVENT_NAME, common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_NAME } = require('../events/_name')
const path = require('path')
global.lx_event.tray.on(TRAY_EVENT_NAME.create, () => {
createTray()
let isEnableTray = null
let themeId = null
const themeList = [
{
id: 0,
fileName: 'tray0Template',
isNative: true,
},
{
id: 1,
fileName: 'tray1Template',
isNative: false,
},
]
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, sourceName => {
if (sourceName === TRAY_EVENT_NAME.name) return
if (themeId !== global.appSetting.tray.themeId) {
themeId = global.appSetting.tray.themeId
setTrayImage(themeId)
}
if (isEnableTray !== global.appSetting.tray.isShow) {
isEnableTray = global.appSetting.tray.isShow
global.appSetting.tray.isShow ? createTray() : destroyTray()
}
createMenu(global.modules.tray)
})
global.lx_event.tray.on(TRAY_EVENT_NAME.destroy, () => {
destroyTray()
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.ready_to_show, () => {
createMenu(global.modules.tray)
})
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.show, () => {
createMenu(global.modules.tray)
})
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.hide, () => {
createMenu(global.modules.tray)
})
const createTray = () => {
if ((global.tray && !global.tray.isDestroyed()) || !global.appSetting.tray || !global.appSetting.tray.isShow) return
if ((global.modules.tray && !global.modules.tray.isDestroyed()) || !global.appSetting.tray || !global.appSetting.tray.isShow) return
const iconPath = path.join(global.__static, 'images/tray', isWin ? 'trayTemplate.ico' : 'trayTemplate.png')
themeId = global.appSetting.tray.themeId
let themeName = (themeList.find(item => item.id === themeId) || themeList[0]).fileName
const iconPath = path.join(global.__static, 'images/tray', isWin ? themeName + '@2x.ico' : themeName + '.png')
// 托盘
global.tray = new Tray(iconPath)
global.modules.tray = new Tray(iconPath)
global.tray.setToolTip('洛雪音乐助手')
if (isWin) createMenu(global.tray)
global.tray.setIgnoreDoubleClickEvents(true)
global.tray.on('click', () => {
const mainWindow = global.mainWindow
global.modules.tray.setToolTip('洛雪音乐助手')
createMenu(global.modules.tray)
global.modules.tray.setIgnoreDoubleClickEvents(true)
global.modules.tray.on('click', () => {
const mainWindow = global.modules.mainWindow
if (!mainWindow) return
mainWindow.isVisible()
? mainWindow.focus()
@@ -30,21 +62,85 @@ const createTray = () => {
}
const destroyTray = () => {
if (!global.tray) return
global.tray.destroy()
global.tray = null
if (!global.modules.tray) return
global.modules.tray.destroy()
global.modules.tray = null
}
const createMenu = tray => {
const contextMenu = Menu.buildFromTemplate([
{
label: '退出',
click() {
global.isQuitting = true
app.quit()
},
if (!global.modules.tray) return
let menu = []
global.modules.mainWindow && menu.push(global.modules.mainWindow.isVisible()
? {
label: '隐藏主界面',
click() {
global.modules.mainWindow.hide()
},
}
: {
label: '显示主界面',
click() {
if (!global.modules.mainWindow) return
if (!global.modules.mainWindow.isVisible()) {
global.modules.mainWindow.show()
}
global.modules.mainWindow.restore()
global.modules.mainWindow.focus()
},
})
menu.push(global.appSetting.desktopLyric.enable
? {
label: '关闭桌面歌词',
click() {
global.lx_core.setAppConfig({ desktopLyric: { enable: false } }, TRAY_EVENT_NAME.name)
},
}
: {
label: '开启桌面歌词',
click() {
global.lx_core.setAppConfig({ desktopLyric: { enable: true } }, TRAY_EVENT_NAME.name)
},
})
menu.push(global.appSetting.desktopLyric.isLock
? {
label: '解锁桌面歌词',
click() {
global.lx_core.setAppConfig({ desktopLyric: { isLock: false } }, TRAY_EVENT_NAME.name)
},
}
: {
label: '锁定桌面歌词',
click() {
global.lx_core.setAppConfig({ desktopLyric: { isLock: true } }, TRAY_EVENT_NAME.name)
},
})
menu.push(global.appSetting.desktopLyric.isAlwaysOnTop
? {
label: '取消置顶',
click() {
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: false } }, TRAY_EVENT_NAME.name)
},
}
: {
label: '置顶歌词',
click() {
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: true } }, TRAY_EVENT_NAME.name)
},
})
menu.push({
label: '退出',
click() {
global.isQuitting = true
app.quit()
},
])
})
const contextMenu = Menu.buildFromTemplate(menu)
tray.setContextMenu(contextMenu)
}
const setTrayImage = themeId => {
if (!global.modules.tray) return
let themeName = (themeList.find(item => item.id === themeId) || themeList[0]).fileName
const iconPath = path.join(global.__static, 'images/tray', isWin ? themeName + '@2x.ico' : themeName + '.png')
global.modules.tray.setImage(iconPath)
}

View File

@@ -0,0 +1,2 @@
exports.userApis = []

View File

@@ -0,0 +1,11 @@
const { EventEmitter } = require('events')
const USER_API_EVENT_NAME = require('./name')
class UserApi extends EventEmitter {
status(info) {
this.emit(USER_API_EVENT_NAME.status, info)
}
}
module.exports = UserApi

View File

@@ -0,0 +1,3 @@
module.exports = {
status: 'status',
}

View File

@@ -0,0 +1,44 @@
const Event = require('./event/event')
const eventNames = require('./event/name')
const { closeWindow } = require('./main')
const { getUserApis, importApi, removeApi } = require('./utils')
const { request, cancelRequest, getStatus, loadApi } = require('./rendererEvent/rendererEvent')
// const { getApiList, importApi, removeApi, setApi, getStatus, request, eventNames }
let userApiId
exports.Event = Event
exports.eventNames = eventNames
exports.getApiList = getUserApis
exports.importApi = script => {
return {
apiInfo: importApi(script),
apiList: getUserApis(),
}
}
exports.request = request
exports.cancelRequest = cancelRequest
exports.getStatus = getStatus
exports.removeApi = async ids => {
if (userApiId && ids.includes(userApiId)) {
userApiId = null
await closeWindow()
}
removeApi(ids)
return getUserApis()
}
exports.setApi = async id => {
if (userApiId) {
userApiId = null
await closeWindow()
}
const apiList = getUserApis()
if (!apiList.some(a => a.id === id)) return
userApiId = id
await loadApi(id)
}

View File

@@ -0,0 +1,89 @@
const { BrowserWindow } = require('electron')
const fs = require('fs')
const path = require('path')
// eslint-disable-next-line no-undef
const dir = global.isDev ? __userApi : path.join(__dirname, 'userApi')
const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
let html = ''
fs.readFile(path.join(dir, 'renderer/user-api.html'), 'utf8', (err, data) => {
if (err) throw new Error('api html read failed, info: ' + err.message)
html = data
})
const denyEvents = [
'new-window',
'will-navigate',
'will-redirect',
'will-attach-webview',
'will-prevent-unload',
'media-started-playing',
]
const winEvent = win => {
win.on('closed', () => {
win = global.modules.userApiWindow = null
})
}
exports.createWindow = async userApi => {
if (global.modules.userApiWindow) return
while (true) {
if (html) break
await wait(100)
}
/**
* Initial window options
*/
global.modules.userApiWindow = new BrowserWindow({
enableRemoteModule: false,
resizable: false,
minimizable: false,
maximizable: false,
fullscreenable: false,
show: false,
webPreferences: {
contextIsolation: true,
worldSafeExecuteJavaScript: true,
nodeIntegration: false,
preload: path.join(dir, 'renderer/preload.js'),
},
})
for (const eventName of denyEvents) {
global.modules.userApiWindow.webContents.on(eventName, event => {
event.preventDefault()
})
}
global.modules.userApiWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
// eslint-disable-next-line node/no-callback-literal
callback(false)
})
winEvent(global.modules.userApiWindow)
// console.log(html.replace('</body>', `<script>${userApi.script}</script></body>`))
const randomNum = Math.random().toString().substring(2, 10)
global.modules.userApiWindow.loadURL(
'data:text/html;charset=UTF-8,' + encodeURIComponent(html
.replace('<meta http-equiv="Content-Security-Policy" content="default-src \'none\'">',
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${randomNum}';">`)
.replace('</body>', `<script nonce="${randomNum}">${userApi.script}</script></body>`)))
// global.modules.userApiWindow.loadFile(path.join(dir, 'renderer/user-api.html'))
// global.modules.userApiWindow.webContents.openDevTools()
}
exports.closeWindow = async() => {
if (!global.modules.userApiWindow) return
await Promise.all([
global.modules.userApiWindow.webContents.session.clearAuthCache(),
global.modules.userApiWindow.webContents.session.clearStorageData(),
global.modules.userApiWindow.webContents.session.clearCache(),
])
global.modules.userApiWindow.destroy()
global.modules.userApiWindow = null
}

View File

@@ -0,0 +1,227 @@
const { contextBridge, ipcRenderer } = require('electron')
const needle = require('needle')
const { createCipheriv, publicEncrypt, constants, randomBytes, createHash } = require('crypto')
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
const sendMessage = (action, status, data, message) => {
ipcRenderer.send(action, { status, data, message })
}
let isInitedApi = false
const EVENT_NAMES = {
request: 'request',
inited: 'inited',
}
const eventNames = Object.values(EVENT_NAMES)
const events = {
request: null,
}
const allSources = ['kw', 'kg', 'tx', 'wy', 'mg']
const supportQualitys = {
kw: ['128k', '320k', 'flac'],
kg: ['128k', '320k', 'flac'],
tx: ['128k', '320k', 'flac'],
wy: ['128k', '320k', 'flac'],
mg: ['128k', '320k', 'flac'],
}
const supportActions = {
kw: ['musicUrl'],
kg: ['musicUrl'],
tx: ['musicUrl'],
wy: ['musicUrl'],
mg: ['musicUrl'],
xm: ['musicUrl'],
}
const handleRequest = (context, { requestKey, data }) => {
// console.log(data)
if (!events.request) return sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, 'Request event is not defined')
try {
events.request.call(context, { source: data.source, action: data.action, info: data.info }).then(response => {
let sendData = {
requestKey,
}
switch (data.action) {
case 'musicUrl':
sendData.result = {
source: data.source,
action: data.action,
data: {
type: data.info.type,
url: response,
},
}
break
}
sendMessage(USER_API_RENDERER_EVENT_NAME.response, true, sendData)
}).catch(err => {
sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message)
})
} catch (err) {
sendMessage(USER_API_RENDERER_EVENT_NAME.response, false, { requestKey }, err.message)
}
}
/**
*
* @param {*} context
* @param {*} info {
* status: true,
* message: 'xxx',
* sources: {
* kw: ['128k', '320k', 'flac'],
* kg: ['128k', '320k', 'flac'],
* tx: ['128k', '320k', 'flac'],
* wy: ['128k', '320k', 'flac'],
* mg: ['128k', '320k', 'flac'],
* }
* }
*/
const handleInit = (context, info) => {
if (!info) {
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed')
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
return
}
if (info.openDevTools === true) {
sendMessage(USER_API_RENDERER_EVENT_NAME.openDevTools)
}
if (!info.status) {
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, 'Init failed')
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
return
}
const sourceInfo = {
sources: {},
}
try {
for (const source of allSources) {
const userSource = info.sources[source]
if (!userSource || userSource.type !== 'music') continue
const qualitys = supportQualitys[source]
const actions = supportActions[source]
sourceInfo.sources[source] = {
type: 'music',
actions: actions.filter(a => userSource.actions.includes(a)),
qualitys: qualitys.filter(q => userSource.qualitys.includes(q)),
}
}
} catch (error) {
console.log(error)
sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, error.message)
return
}
sendMessage(USER_API_RENDERER_EVENT_NAME.init, true, sourceInfo)
ipcRenderer.on(USER_API_RENDERER_EVENT_NAME.request, (event, data) => {
handleRequest(context, data)
})
}
contextBridge.exposeInMainWorld('lx', {
EVENT_NAMES,
request(url, { method = 'get', timeout, headers, body, form, formData }, callback) {
let options = { headers }
let data
if (body) {
data = body
} else if (form) {
data = form
// data.content_type = 'application/x-www-form-urlencoded'
options.json = false
} else if (formData) {
data = formData
// data.content_type = 'multipart/form-data'
options.json = false
}
options.response_timeout = timeout
let request = needle.request(method, url, data, options, (err, resp, body) => {
if (!err) {
body = resp.body = resp.raw.toString()
try {
resp.body = JSON.parse(resp.body)
} catch (_) {}
body = resp.body
}
callback(err, {
statusCode: resp.statusCode,
statusMessage: resp.statusMessage,
headers: resp.headers,
bytes: resp.bytes,
raw: resp.raw,
body: body,
}, body)
}).request
return () => {
if (!request.aborted) request.abort()
request = null
}
},
send(eventName, data) {
return new Promise((resolve, reject) => {
if (!eventNames.includes(eventName)) return reject(new Error('The event is not supported: ' + eventName))
switch (eventName) {
case EVENT_NAMES.inited:
if (isInitedApi) return
isInitedApi = true
handleInit(this, data)
break
default:
resolve(new Error('Unknown event name: ' + eventName))
}
})
},
on(eventName, handler) {
if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
switch (eventName) {
case EVENT_NAMES.request:
events.request = handler
break
}
},
utils: {
crypto: {
aesEncrypt(buffer, mode, key, iv) {
const cipher = createCipheriv('aes-128-' + mode, key, iv)
return Buffer.concat([cipher.update(buffer), cipher.final()])
},
rsaEncrypt(buffer, key) {
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
return publicEncrypt({ key: key, padding: constants.RSA_NO_PADDING }, buffer)
},
randomBytes(size) {
return randomBytes(size)
},
md5(str) {
return createHash('md5').update(str).digest('hex')
},
},
buffer: {
from(...args) {
return Buffer.from(...args)
},
},
},
// removeEvent(eventName, handler) {
// if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
// let handlers
// switch (eventName) {
// case EVENT_NAMES.request:
// handlers = events.request
// break
// }
// for (let index = 0; index < handlers.length; index++) {
// if (handlers[index] === handler) {
// handlers.splice(index, 1)
// break
// }
// }
// },
// removeAllEvents() {
// for (const handlers of Object.values(events)) {
// handlers.splice(0, handlers.length)
// }
// },
})

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User api</title>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,12 @@
const names = {
init: '',
request: '',
response: '',
openDevTools: '',
}
for (const key of Object.keys(names)) {
names[key] = `userApi_${key}`
}
module.exports = names

View File

@@ -0,0 +1,82 @@
const { mainOn, mainSend } = require('@common/ipc')
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
const { createWindow } = require('../main')
const { getUserApis } = require('../utils')
let userApi
let status = { status: true }
const requestQueue = new Map()
const timeouts = {}
const handleInit = (event, { status, message, data: apiInfo }) => {
// console.log('inited')
if (!status) {
console.log('init failed:', message)
global.lx_event.userApi.status(status = { status: false, apiInfo: userApi, message })
return
}
global.lx_event.userApi.status(status = { status: true, apiInfo: { ...userApi, sources: apiInfo.sources } })
}
const handleResponse = (event, { status, data: { requestKey, result }, message }) => {
const request = requestQueue.get(requestKey)
if (!request) return
requestQueue.delete(requestKey)
clearTimeout(timeouts[requestKey])
delete timeouts[requestKey]
if (status) {
request[0](result)
} else {
request[1](new Error(message))
}
}
const handleOpenDevTools = () => {
if (global.modules.userApiWindow) {
global.modules.userApiWindow.webContents.openDevTools()
}
}
mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit)
mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse)
mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools)
exports.loadApi = async apiId => {
if (!apiId) return global.lx_event.userApi.status(status = { status: false, message: 'api id is null' })
userApi = getUserApis().find(api => api.id == apiId)
console.log('load api', userApi.name)
await createWindow(userApi)
// if (!userApi) return global.lx_event.userApi.status(status = { status: false, message: 'api script is not found' })
// if (!global.modules.userApiWindow) {
// global.lx_event.userApi.status(status = { status: false, message: 'user api runtime is not defined' })
// throw new Error('user api window is not defined')
// }
// // const path = require('path')
// // // eslint-disable-next-line no-undef
// // userApi.script = require('fs').readFileSync(path.join(global.isDev ? __userApi : __dirname, 'renderer/test-api.js')).toString()
// console.log('load api', userApi.name)
// mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.init, { userApi })
}
exports.cancelRequest = requestKey => {
if (!requestQueue.has(requestKey)) return
const request = requestQueue.get(requestKey)
request[1](new Error('Cancel request'))
requestQueue.delete(requestKey)
clearTimeout(timeouts[requestKey])
delete timeouts[requestKey]
}
exports.request = ({ requestKey, data }) => new Promise((resolve, reject) => {
if (!userApi) return reject(new Error('user api is not load'))
// const requestKey = `request__${Math.random().toString().substring(2)}`
timeouts[requestKey] = setTimeout(() => {
exports.cancelRequest(requestKey)
}, 20000)
requestQueue.set(requestKey, [resolve, reject, data])
mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.request, { requestKey, data })
})
exports.getStatus = () => status

View File

@@ -0,0 +1,44 @@
const { userApis: defaultUserApis } = require('../config')
const getStore = require('@common/store')
let userApis
exports.getUserApis = () => {
const electronStore_userApi = getStore('userApi')
if (userApis) return userApis
userApis = electronStore_userApi.get('userApis')
if (!userApis) {
userApis = defaultUserApis
electronStore_userApi.set('userApis', userApis)
}
return userApis
}
exports.importApi = script => {
let scriptInfo = script.split(/\r?\n/)
let name = scriptInfo[1] || ''
let description = scriptInfo[2] || ''
name = name.startsWith(' * @name ') ? name.replace(' * @name ', '').trim() : `user_api_${new Date().toLocaleString()}`
if (name.length > 10) name = name.substring(0, 10) + '...'
description = description.startsWith(' * @description ') ? description.replace(' * @description ', '').trim() : ''
if (description.length > 20) description = description.substring(0, 20) + '...'
const apiInfo = {
id: `user_api_${Math.random().toString().substring(2, 5)}_${Date.now()}`,
name,
description,
script,
}
userApis.push(apiInfo)
getStore('userApi').set('userApis', userApis)
return apiInfo
}
exports.removeApi = ids => {
for (let index = userApis.length - 1; index > -1; index--) {
if (ids.includes(userApis[index].id)) {
userApis.splice(index, 1)
ids.splice(index, 1)
}
}
getStore('userApi').set('userApis', userApis)
}

View File

@@ -0,0 +1,81 @@
const { common: COMMON_EVENT_NAME, winLyric: WIN_LYRIC_EVENT_NAME, hotKey: HOT_KEY_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../../events/_name')
const { mainSend, NAMES: { winLyric: ipcWinLyricNames } } = require('../../../common/ipc')
const { desktop_lyric } = require('../../../common/hotKey')
const { getLyricWindowBounds } = require('./utils')
let isLock = null
let isEnable = null
let isAlwaysOnTop = null
let isLockScreen = null
const setLrcConfig = () => {
let desktopLyric = global.appSetting.desktopLyric
if (global.modules.lyricWindow) {
mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_config, {
config: desktopLyric,
languageId: global.appSetting.langId,
isShowLyricTranslation: global.appSetting.player.isShowLyricTranslation,
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
})
if (isLock != desktopLyric.isLock) {
isLock = desktopLyric.isLock
if (desktopLyric.isLock) {
global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: false })
} else {
global.modules.lyricWindow.setIgnoreMouseEvents(false)
}
}
if (isAlwaysOnTop != desktopLyric.isAlwaysOnTop) {
isAlwaysOnTop = desktopLyric.isAlwaysOnTop
global.modules.lyricWindow.setAlwaysOnTop(desktopLyric.isAlwaysOnTop, 'screen-saver')
}
if (isLockScreen != desktopLyric.isLockScreen) {
isLockScreen = desktopLyric.isLockScreen
if (desktopLyric.isLockScreen) {
global.modules.lyricWindow.setBounds(getLyricWindowBounds(global.modules.lyricWindow.getBounds(), {
x: null,
y: null,
w: desktopLyric.width,
h: desktopLyric.height,
}))
}
}
}
if (isEnable != desktopLyric.enable) {
isEnable = desktopLyric.enable
if (desktopLyric.enable) {
global.lx_event.winLyric.create()
} else {
global.lx_event.winLyric.close()
}
}
}
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, name => {
if (WIN_LYRIC_EVENT_NAME.name === name) return
setLrcConfig()
})
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info => {
if (!global.modules.lyricWindow) return
mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_info, info)
})
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.keyDown, ({ type, key }) => {
let info = global.appHotKey.config.global.keys[key]
if (!info || info.type != WIN_LYRIC_EVENT_NAME.name) return
let desktopLyricSetting = JSON.parse(JSON.stringify(global.appSetting.desktopLyric))
let settingKey
switch (info.action) {
case desktop_lyric.toggle_visible.action:
settingKey = 'enable'
break
case desktop_lyric.toggle_lock.action:
settingKey = 'isLock'
break
case desktop_lyric.toggle_always_top.action:
settingKey = 'isAlwaysOnTop'
break
}
desktopLyricSetting[settingKey] = !desktopLyricSetting[settingKey]
global.lx_core.setAppConfig({ desktopLyric: desktopLyricSetting }, null)
})

View File

@@ -0,0 +1,127 @@
const path = require('path')
const { BrowserWindow } = require('electron')
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name')
const { debounce } = require('../../../common/utils')
const { getLyricWindowBounds } = require('./utils')
require('./event')
require('./rendererEvent')
global.lx_event.winLyric.on(WIN_LYRIC_EVENT_NAME.create, () => {
createWindow()
})
global.lx_event.winLyric.on(WIN_LYRIC_EVENT_NAME.close, () => {
closeWindow()
})
let winURL = global.isDev ? 'http://localhost:9081/lyric.html' : `file://${path.join(__dirname, 'lyric.html')}`
const setLyricsConfig = debounce(config => {
// if (x != null) bounds.x = x
// if (y != null) bounds.y = y
// if (width != null) bounds.width = width
// if (height != null) bounds.height = height
global.lx_core.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
}, 500)
const winEvent = lyricWindow => {
// let bounds
// lyricWindow.on('close', event => {
// if (global.isQuitting || !global.appSetting.tray.isToTray || (!isWin && !global.isTrafficLightClose)) {
// lyricWindow.setProgressBar(-1)
// return
// }
// if (global.isTrafficLightClose) global.isTrafficLightClose = false
// event.preventDefault()
// lyricWindow.hide()
// })
lyricWindow.on('closed', () => {
lyricWindow = global.modules.lyricWindow = null
})
lyricWindow.on('move', event => {
// bounds = lyricWindow.getBounds()
// console.log(bounds)
setLyricsConfig(lyricWindow.getBounds())
})
lyricWindow.on('resize', event => {
// bounds = lyricWindow.getBounds()
// console.log(bounds)
setLyricsConfig(lyricWindow.getBounds())
})
// lyricWindow.on('restore', () => {
// lyricWindow.webContents.send('restore')
// })
// lyricWindow.on('focus', () => {
// lyricWindow.webContents.send('focus')
// })
lyricWindow.once('ready-to-show', () => {
lyricWindow.show()
if (global.appSetting.desktopLyric.isLock) {
global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: false })
}
})
}
const createWindow = () => {
if (global.modules.lyricWindow) return
if (!global.appSetting.desktopLyric.enable) return
// const windowSizeInfo = getWindowSizeInfo(global.appSetting)
let { x, y, width, height, isAlwaysOnTop } = global.appSetting.desktopLyric
let { width: screenWidth, height: screenHeight } = global.envParams.workAreaSize
if (x == null) {
x = screenWidth - width
y = screenHeight - height
}
if (global.appSetting.desktopLyric.isLockScreen) {
let bounds = getLyricWindowBounds({ x, y, width, height }, { x: null, y: null, w: width, h: height })
x = bounds.x
y = bounds.y
width = bounds.width
height = bounds.height
}
/**
* Initial window options
*/
global.modules.lyricWindow = new BrowserWindow({
height,
width,
x,
y,
minWidth: 380,
minHeight: 80,
useContentSize: true,
frame: false,
transparent: true,
enableRemoteModule: false,
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
resizable: false,
minimizable: false,
maximizable: false,
fullscreenable: false,
show: false,
alwaysOnTop: isAlwaysOnTop,
skipTaskbar: true,
webPreferences: {
contextIsolation: false,
webSecurity: !global.isDev,
nodeIntegration: true,
},
})
global.modules.lyricWindow.loadURL(winURL)
winEvent(global.modules.lyricWindow)
// mainWindow.webContents.openDevTools()
}
const closeWindow = () => {
if (!global.modules.lyricWindow) return
global.modules.lyricWindow.close()
}

View File

@@ -0,0 +1,37 @@
const {
mainOn,
mainHandle,
mainSend,
NAMES: {
mainWindow: ipcMainWindowNames,
winLyric: ipcWinLyricNames,
},
} = require('../../../common/ipc')
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name')
const { getLyricWindowBounds } = require('./utils')
mainOn(ipcWinLyricNames.get_lyric_info, (event, action) => {
if (!global.modules.mainWindow) return
mainSend(global.modules.mainWindow, ipcMainWindowNames.get_lyric_info, {
name: ipcWinLyricNames.set_lyric_info,
modal: 'lyricWindow',
action,
})
})
mainOn(ipcWinLyricNames.set_lyric_config, (event, config) => {
global.lx_core.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
})
mainHandle(ipcWinLyricNames.get_lyric_config, async() => {
return {
config: global.appSetting.desktopLyric,
languageId: global.appSetting.langId,
isShowLyricTranslation: global.appSetting.player.isShowLyricTranslation,
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
}
})
mainOn(ipcWinLyricNames.set_win_bounds, (event, options) => {
global.modules.lyricWindow.setBounds(getLyricWindowBounds(global.modules.lyricWindow.getBounds(), options))
})

View File

@@ -0,0 +1,68 @@
// 设置窗口位置、大小
let winX
let winY
let wasW
let wasH
let offset = 8
let minWidth = 380
let minHeight = 80
exports.getLyricWindowBounds = (bounds, { x = 0, y = 0, w = 0, h = 0 }) => {
if (w < minWidth) w = minWidth
if (h < minHeight) h = minHeight
if (global.appSetting.desktopLyric.isLockScreen) {
wasW = global.envParams.workAreaSize.width + offset
wasH = global.envParams.workAreaSize.height + offset
if (w > wasW + offset) w = wasW + offset
if (h > wasH + offset) h = wasH + offset
if (x == null) {
if (bounds.x > wasW - w) {
x = wasW - w - bounds.x
} else if (bounds.x < -offset) {
x = bounds.x + offset
} else {
x = 0
}
if (bounds.y > wasH - h) {
y = wasH - h - bounds.y
} else if (bounds.y < -offset) {
y = bounds.y + offset
} else {
y = 0
}
}
winX = bounds.x + x
winY = bounds.y + y
if (x != 0) {
if (winX < -offset) {
winX = -offset
} else if (winX + w > wasW) {
winX = wasW - w
}
}
if (y != 0) {
if (winY < -offset) {
winY = -offset
} else if (winY + h > wasH) {
winY = wasH - h
}
}
x = winX
y = winY
if (x + w > wasW) w = wasW - x
if (y + h > wasH) h = wasH - y
} else {
y += bounds.y
x += bounds.x
}
bounds.width = w
bounds.height = h
bounds.x = x
bounds.y = y
// console.log('util bounds', bounds)
return bounds
}

View File

@@ -1,9 +1,9 @@
const { mainOn } = require('../../common/ipc')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { app } = require('electron')
const { name: defaultName } = require('../../../package.json')
mainOn('appName', (event, params) => {
mainOn(ipcMainWindowNames.set_app_name, (event, params) => {
if (params == null) {
app.setName(defaultName)
} else {

View File

@@ -0,0 +1,11 @@
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../events/_name')
const { NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
mainHandle(ipcMainWindowNames.set_app_setting, (event, config) => {
if (!config) return
global.lx_core.setAppConfig(config, MAIN_WINDOW_EVENT_NAME.name)
return global.appSetting
})
mainHandle(ipcMainWindowNames.get_setting, async() => ({ setting: global.appSetting, version: global.appSettingVersion }))

View File

@@ -0,0 +1,11 @@
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainHandle(ipcMainWindowNames.clear_cache, async(event, options) => {
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
return global.modules.mainWindow.webContents.session.clearCache()
})
mainHandle(ipcMainWindowNames.get_cache_size, async(event, options) => {
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
return global.modules.mainWindow.webContents.session.getCacheSize()
})

View File

@@ -1,8 +0,0 @@
const { mainHandle } = require('../../common/ipc')
mainHandle('clearCache', async(event, options) => {
if (!global.mainWindow) throw new Error('mainwindow is undefined')
return global.mainWindow.webContents.session.clearCache()
})

View File

@@ -0,0 +1,8 @@
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
const getStore = require('@common/store')
mainHandle(ipcMainWindowNames.get_data, async(event, path) => getStore('data').get(path))
mainOn(ipcMainWindowNames.save_data, (event, { path, data }) => getStore('data').set(path, data))

View File

@@ -1,7 +0,0 @@
const { mainHandle } = require('../../common/ipc')
mainHandle('getCacheSize', async(event, options) => {
if (!global.mainWindow) throw new Error('mainwindow is undefined')
return global.mainWindow.webContents.session.getCacheSize()
})

View File

@@ -0,0 +1,5 @@
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { app } = require('electron')
mainHandle(ipcMainWindowNames.get_data_path, async() => app.getPath('userData'))

View File

@@ -1,6 +1,6 @@
const { mainHandle } = require('../../common/ipc')
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
mainHandle('getEnvParams', async(event, options) => {
return global.envParams
mainHandle(ipcMainWindowNames.get_env_params, async(event, options) => {
return global.envParams.cmdParams
})

View File

@@ -0,0 +1,42 @@
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames }, mainOn, mainHandle } = require('../../common/ipc')
const { mainWindow: MAIN_WINDOW_EVENT_NAME, hotKey: HOT_KEY_EVENT_NAME } = require('../events/_name')
const getStore = require('@common/store')
// const { registerHotkey, unRegisterHotkey } = require('../modules/hotKey/utils')
// mainHandle(ipcMainWindowNames.set_hot_key_config, async(event, { action, data }) => {
// switch (action) {
// case 'config':
// global.lx_event.hotKey.saveConfig(data.data, MAIN_WINDOW_EVENT_NAME.source)
// return
// case 'register':
// return registerHotkey(data)
// case 'unregister':
// return unRegisterHotkey(data)
// }
// })
mainHandle(ipcMainWindowNames.get_hot_key, async() => {
const electronStore_hotKey = getStore('hotKey')
return {
local: electronStore_hotKey.get('local'),
global: electronStore_hotKey.get('global'),
}
})
mainOn(ipcMainWindowNames.quit, () => global.lx_event.mainWindow.quit())
mainOn(ipcMainWindowNames.min_toggle, () => global.lx_event.mainWindow.toggleMinimize())
mainOn(ipcMainWindowNames.hide_toggle, () => global.lx_event.mainWindow.toggleHide())
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.config, (config, source) => {
if (!global.modules.mainWindow || source === MAIN_WINDOW_EVENT_NAME.name) return
mainSend(global.modules.mainWindow, ipcMainWindowNames.set_hot_key_config, { config, source })
})
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.keyDown, ({ type, key }) => {
let info = global.appHotKey.config.global.keys[key]
if (!info || info.type != MAIN_WINDOW_EVENT_NAME.name) return
mainSend(global.modules.mainWindow, ipcMainWindowNames.key_down, { type, key })
})

View File

@@ -7,11 +7,20 @@ require('./musicMeta')
require('./selectDir')
require('./setWindowSize')
require('./showSaveDialog')
require('./clearCache')
require('./getCacheSize')
require('./cache')
require('./setIgnoreMouseEvent')
require('./getEnvParams')
require('./tray')
require('./updateSetting')
require('./appSetting')
require('./setLyricInfo')
require('./hotKey')
require('./getDataPath')
require('./showDialog')
require('./playList')
require('./data')
require('./lyric')
require('./musicUrl')
require('./xm_verify')
require('./kw_decodeLyric')
require('./userApi')
require('./sync')

View File

@@ -0,0 +1,45 @@
const { inflate } = require('zlib')
const iconv = require('iconv-lite')
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const handleInflate = data => new Promise((resolve, reject) => {
inflate(data, (err, result) => {
if (err) return reject(err)
resolve(result)
})
})
const buf_key = Buffer.from('yeelion')
const buf_key_len = buf_key.length
const decodeLyric = async(buf, isGetLyricx) => {
// const info = buf.slice(0, index).toString()
// if (!info.startsWith('tp=content')) return null
// const isLyric = info.includes('\r\nlrcx=0\r\n')
if (buf.toString('utf8', 0, 10) != 'tp=content') return ''
// const index = buf.indexOf('\r\n\r\n') + 4
const lrcData = await handleInflate(buf.slice(buf.indexOf('\r\n\r\n') + 4))
if (!isGetLyricx) return iconv.decode(lrcData, 'gb18030')
const buf_str = Buffer.from(lrcData.toString(), 'base64')
const buf_str_len = buf_str.length
const output = new Uint16Array(buf_str_len)
let i = 0
while (i < buf_str_len) {
let j = 0
while (j < buf_key_len && i < buf_str_len) {
output[i] = buf_str[i] ^ buf_key[j]
i++
j++
}
}
return iconv.decode(Buffer.from(output), 'gb18030')
}
mainHandle(ipcMainWindowNames.handle_kw_decode_lyric, async(event, { lrcBase64, isGetLyricx }) => {
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
const lrc = await decodeLyric(Buffer.from(lrcBase64, 'base64'), isGetLyricx)
return Buffer.from(lrc).toString('base64')
})

View File

@@ -0,0 +1,10 @@
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
const getStore = require('@common/store')
mainHandle(ipcMainWindowNames.get_lyric, async(event, id) => getStore('lyrics', true, false).get(id) || {})
mainOn(ipcMainWindowNames.save_lyric, (event, { id, lyrics }) => getStore('lyrics', true, false).set(id, lyrics))
mainOn(ipcMainWindowNames.clear_lyric, () => getStore('lyrics', true, false).clear())

View File

@@ -1,6 +1,6 @@
const { mainOn } = require('../../common/ipc')
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { setMeta } = require('../utils/musicMeta')
mainOn('setMusicMeta', (event, { filePath, meta }) => {
mainOn(ipcMainWindowNames.set_music_meta, (event, { filePath, meta }) => {
setMeta(filePath, meta)
})

View File

@@ -0,0 +1,10 @@
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
const getStore = require('@common/store')
mainHandle(ipcMainWindowNames.get_music_url, async(event, id) => getStore('musicUrls', true, false).get(id) || '')
mainOn(ipcMainWindowNames.save_music_url, (event, { id, url }) => getStore('musicUrls', true, false).set(id, url))
mainOn(ipcMainWindowNames.clear_music_url, () => getStore('musicUrls', true, false).clear())

Some files were not shown because too many files have changed in this diff Show More