Compare commits

...

266 Commits

Author SHA1 Message Date
hunlongyu
81b34ff54a release: v2.2.8 2022-07-13 18:34:33 +08:00
hunlongyu
9b9db55f49 feat: 移除推荐,修改默认远程视频源 2022-07-13 18:33:50 +08:00
hunlongyu
e7c7367ea8 release: v2.8.7 2022-07-13 16:44:48 +08:00
hunlongyu
5e37ef32fd release: v2.8.6 2022-07-13 10:58:16 +08:00
hunlongyu
85525745a1 feat: remove qrcode and modify sites url 2022-07-13 10:56:50 +08:00
hunlongyu
c7f581a088 fix: 让项目能正常跑起来 2022-07-13 09:49:12 +08:00
Hunlongyu
a88dfb1bc3 Merge pull request #769 from Hunlongyu/dependabot/npm_and_yarn/eventsource-1.1.1
Bump eventsource from 1.0.7 to 1.1.1
2022-07-03 21:29:37 +08:00
Hunlongyu
410cd8fc1a Merge pull request #767 from cuiocean/dependabot/npm_and_yarn/dexie-3.2.2
Bump dexie from 3.0.3 to 3.2.2
2022-06-16 09:43:55 +08:00
dependabot[bot]
bb30be3c4d Bump eventsource from 1.0.7 to 1.1.1
Bumps [eventsource](https://github.com/EventSource/eventsource) from 1.0.7 to 1.1.1.
- [Release notes](https://github.com/EventSource/eventsource/releases)
- [Changelog](https://github.com/EventSource/eventsource/blob/master/HISTORY.md)
- [Commits](https://github.com/EventSource/eventsource/compare/v1.0.7...v1.1.1)

---
updated-dependencies:
- dependency-name: eventsource
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 18:54:40 +00:00
dependabot[bot]
6230c9e87a Bump dexie from 3.0.3 to 3.2.2
Bumps [dexie](https://github.com/dfahlander/Dexie.js) from 3.0.3 to 3.2.2.
- [Release notes](https://github.com/dfahlander/Dexie.js/releases)
- [Commits](https://github.com/dfahlander/Dexie.js/compare/v3.0.3...v3.2.2)

---
updated-dependencies:
- dependency-name: dexie
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-23 20:15:41 +00:00
Hunlongyu
93efb64211 Merge pull request #759 from cuiocean/dependabot/npm_and_yarn/electron-13.6.6
Bump electron from 11.5.0 to 13.6.6
2022-03-27 17:21:26 +08:00
dependabot[bot]
3c4dd91d36 Bump electron from 11.5.0 to 13.6.6
Bumps [electron](https://github.com/electron/electron) from 11.5.0 to 13.6.6.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v11.5.0...v13.6.6)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-25 19:09:35 +00:00
Hunlongyu
45041fdab1 Merge pull request #746 from cuiocean/dependabot/npm_and_yarn/axios-0.21.2
Bump axios from 0.21.1 to 0.21.2
2021-10-13 08:57:18 +08:00
dependabot[bot]
bcf0613ff0 Bump axios from 0.21.1 to 0.21.2
Bumps [axios](https://github.com/axios/axios) from 0.21.1 to 0.21.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.1...v0.21.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-13 00:56:23 +00:00
Hunlongyu
3107b851c5 Merge pull request #745 from cuiocean/dependabot/npm_and_yarn/electron-11.5.0
Bump electron from 11.3.0 to 11.5.0
2021-10-13 08:55:37 +08:00
dependabot[bot]
b27883ad0f Bump electron from 11.3.0 to 11.5.0
Bumps [electron](https://github.com/electron/electron) from 11.3.0 to 11.5.0.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v11.3.0...v11.5.0)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-12 22:51:49 +00:00
haiyangcui
e0c3502c3f Update x86.yml 2021-08-10 16:10:30 +02:00
haiyangcui
4c2db002eb 推荐页面添加"豆瓣华语电影" 2021-08-10 15:56:16 +02:00
haiyangcui
898f769d24 电影页面删除重复的数据 2021-08-10 15:24:02 +02:00
cuiocean
d6549c6a6b Update x86.yml 2021-08-10 15:23:13 +02:00
haiyangcui
eea4081d58 v2.8.5 2021-07-22 23:07:53 +02:00
haiyangcui
1e9f48e48c "按上映年份"排序改为降序排序,从新到旧排序 2021-06-05 22:01:35 +02:00
haiyangcui
0816933679 删除"作者推荐" 2021-05-28 23:10:52 +02:00
haiyangcui
8af6ff2bd7 修复分类刷新的一个bug 2021-05-24 22:29:55 +02:00
haiyangcui
c5d979fd28 引入xgplayer-mp4模块 2021-05-24 22:28:35 +02:00
haiyangcui
beb0c29326 源站增加"倒序排列"开关 2021-05-24 22:06:46 +02:00
haiyangcui
5509ace412 添加'豆瓣冷门佳片' 2021-05-09 23:45:07 +02:00
haiyangcui
6f296a1170 v2.8.4 2021-04-23 17:04:32 +02:00
haiyangcui
f8228a71f3 添加"热门美剧""热门英剧""热门国产剧"推荐列表 2021-04-22 11:47:09 +02:00
haiyangcui
9886f04b48 推荐页面添加"按评分"排序 2021-04-22 11:31:47 +02:00
haiyangcui
37c4aad4b0 Update lock files 2021-04-22 11:31:29 +02:00
haiyangcui
77d7b28e26 Update lock files 2021-04-21 21:10:51 +02:00
haiyangcui
3505b22344 简化调用解析网址的逻辑 2021-04-21 21:00:04 +02:00
haiyangcui
80cec08982 v2.8.3 2021-04-21 18:59:53 +02:00
haiyangcui
f92094daec 支持"豆瓣热门动画电影" 2021-04-21 18:57:54 +02:00
haiyangcui
2c06ef1da6 支持"豆瓣热门综艺" 2021-04-21 18:52:42 +02:00
haiyangcui
06403ece3f 因不明原因,全屏时上一集按钮被遮盖而无法点取.简单粗暴的解决方法:把所有按钮向右移动一下. 2021-04-21 18:42:26 +02:00
haiyangcui
39a6491403 添加"豆瓣热门纪录片" 2021-04-21 16:37:17 +02:00
haiyangcui
edb82eb3be 支持更新豆瓣推荐 2021-04-20 22:57:23 +02:00
haiyangcui
c7ca3df50d 修复设置默认解析网址 2021-04-20 22:13:16 +02:00
haiyangcui
0b198381b1 升级依赖 2021-04-20 22:11:53 +02:00
hunlongyu
2dcda83741 升级依赖 2021-04-16 09:09:33 +08:00
haiyangcui
5b3c3f0ff2 推荐页面,移除"删除" 2021-04-13 23:00:02 +02:00
haiyangcui
23454f7c7f v2.8.2 2021-04-13 17:49:02 +02:00
haiyangcui
0fdfe29343 增加"豆瓣高分电影" 2021-04-13 17:44:43 +02:00
haiyangcui
fa7a799e2b 支持缓存电影数据 2021-04-13 17:34:46 +02:00
haiyangcui
9c1a707279 初始的数据都移到iniData文件夹下 2021-04-13 17:07:18 +02:00
haiyangcui
7b69bb05d4 支持设置是否最小化时暂停播放 2021-04-13 17:06:33 +02:00
haiyangcui
3b6b6ea11b 记录窗口大小及位置 2021-04-12 17:57:56 +02:00
haiyangcui
9ec65ab027 Introduce searchFirstDetail 2021-04-12 16:48:58 +02:00
haiyangcui
da5531a947 主分类过滤, 检测关键词是否包含分类名. 福利过滤,检测分类名是否包含关键词 2021-04-12 14:55:06 +02:00
haiyangcui
4744f91f6b 过滤开启改为开关 2021-04-12 14:41:37 +02:00
haiyangcui
36b80c1d7e Import axios in Recommendation.vue 2021-04-12 12:56:26 +02:00
haiyangcui
15f4ab7248 修复"动漫"子分类消失的问题 2021-04-12 12:30:56 +02:00
haiyangcui
0e25c25480 推荐页面,加入"豆瓣热门电影"和"豆瓣热门剧集" 2021-04-12 12:26:29 +02:00
haiyangcui
7ff48a407d 删除log 2021-04-11 21:42:10 +02:00
haiyangcui
bbc371b1c5 添加初始化的设置文件 2021-04-11 20:31:00 +02:00
haiyangcui
d141d60e77 v2.8.1 2021-04-11 17:53:44 +02:00
haiyangcui
80af701e5c 删除经常跳出来的一个无用信息 2021-04-11 17:52:53 +02:00
haiyangcui
f0e70e03cb 在refreshClass内部调用classClick 2021-04-11 17:48:46 +02:00
haiyangcui
aba3472f2e 避免rootClassFilter r18ClassFilter数据为空时出错 2021-04-11 17:48:10 +02:00
haiyangcui
8772076d76 检测site变化,以及时刷新数据 2021-04-11 17:36:26 +02:00
haiyangcui
4abe03347a 修正数据库版本号 2021-04-11 17:31:31 +02:00
haiyangcui
b3e6e817dd 过滤开关关联局部变量,否则点击取消的话, 设置也会记录在setting里 2021-04-11 16:27:10 +02:00
haiyangcui
e48445c224 过滤设置更新后,刷新分类列表 2021-04-11 16:19:52 +02:00
haiyangcui
159f19d5ec 支持分开设置分类过滤和福利分类过滤 2021-04-11 16:11:20 +02:00
haiyangcui
c2953a530c 替换函数containsR18Keywords 2021-04-10 14:35:13 +02:00
haiyangcui
9166f129d8 移除过滤关键词列表中的空格 2021-04-10 13:52:59 +02:00
haiyangcui
72ac3eafdd 调高过滤词输入窗口 2021-04-10 13:52:40 +02:00
haiyangcui
8610b41fad 支持定义分类过滤关键词 2021-04-10 13:44:51 +02:00
haiyangcui
06e4c0e83e 只有当视频的核心信息变化时,才刷新播放页面 2021-04-09 14:10:03 +02:00
haiyangcui
5db3fd9f9b 历史页面初始化filteredList,否则视图切换有问题 2021-04-09 12:14:55 +02:00
haiyangcui
978afb2b38 删除无用代码 2021-04-08 15:38:07 +02:00
haiyangcui
b00b69a582 更新调用API的逻辑,如果某些重要数据没有API返回结果中,很可能是该源站已经失效,直接返回空数据 2021-04-06 16:49:00 +02:00
haiyangcui
9a33b0cfb3 搜索分三类,站内,组内,全站 2021-04-06 16:25:18 +02:00
haiyangcui
f4bf42bf07 将"有更新"切换按钮从工具栏移到顶层按钮区 2021-04-04 22:40:41 +02:00
haiyangcui
671a6e32d0 v2.8.0 2021-03-31 21:24:54 +02:00
haiyangcui
d05534fb17 恢复推荐视频的收藏,分享和详情功能 2021-03-31 20:47:02 +02:00
haiyangcui
b655c8c8bc 调整样式 2021-03-31 20:47:02 +02:00
haiyangcui
8fbea8ab57 添加"喜欢这部电影的人也喜欢" 2021-03-31 20:47:01 +02:00
haiyangcui
2ddfc66104 解决某些网址返回分类里含有{}字符串的问题 2021-03-30 09:54:00 +02:00
haiyangcui
26d62cdef4 历史页面增加按完成度排序 2021-03-29 22:41:08 +02:00
haiyangcui
7f6be795b5 v2.7.9 2021-03-29 17:49:11 +02:00
haiyangcui
d7fc97b3d7 有更新项目,进入详情页面,也清除有更新标记 2021-03-29 15:49:03 +02:00
haiyangcui
4a2aa9ee4b 历史页面添加只显示有更新过滤开关 2021-03-29 15:33:10 +02:00
haiyangcui
f4aeec0937 收藏页面添加有更新过滤开关 2021-03-29 15:31:08 +02:00
haiyangcui
ea06a52921 历史页面添加检查更新按钮 2021-03-29 15:12:11 +02:00
haiyangcui
26dc24d216 v2.7.8 2021-03-28 17:31:13 +02:00
haiyangcui
1c57c37997 降级xgplayer回2.17.3, 否则精简模式不工作 2021-03-28 17:28:49 +02:00
haiyangcui
15a6370785 增加推荐页面工具栏 2021-03-28 17:17:45 +02:00
haiyangcui
c8580ff6e6 增加历史页面工具栏 2021-03-28 16:48:33 +02:00
haiyangcui
a104e7cfa8 收藏页面工具栏 2021-03-28 16:31:48 +02:00
haiyangcui
c649d97021 升级依赖 2021-03-28 13:39:24 +02:00
haiyangcui
17601935f1 v2.7.7 2021-03-27 22:27:08 +01:00
haiyangcui
0f85e65e26 更新默认推荐视频列表 2021-03-21 23:06:10 +01:00
haiyangcui
8eb4ca5090 Gitee上的推荐文件显示"根据相关法律政策,该内容无法显示",重新指向为github上的文件 2021-03-21 22:45:19 +01:00
Hunlongyu
d6fcd151e3 直播源支持网址导入. 2021-03-17 10:06:54 +08:00
Hunlongyu
711637ac8b var => let or const 2021-03-17 09:07:25 +08:00
Hunlongyu
1769fb9780 修改成 正确的内容格式 2021-03-16 19:06:21 +08:00
Hunlongyu
40de0337be 默认显示所有支持的格式文件, 不做过滤 2021-03-16 18:35:29 +08:00
Hunlongyu
b0fbdeef15 支持 txt 文件格式的 直播源 和 视频源本地导入. 2021-03-16 18:30:52 +08:00
cuiocean
f75d961aaa Merge pull request #661 from cuiocean/dependabot/npm_and_yarn/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4
2021-03-15 15:24:39 +01:00
buvta
12e2500fd7 点播视频地址失效时可换源并调整收藏 2021-03-13 18:21:43 +08:00
dependabot[bot]
e405225e02 Bump elliptic from 6.5.3 to 6.5.4
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-11 01:23:54 +00:00
haiyangcui
dcba96a773 v2.7.6 2021-03-07 13:09:05 +01:00
haiyangcui
9910daa7c0 添加福利片过滤关键词 2021-03-07 12:54:52 +01:00
buvta
5ac57092db 停止时亦隐藏(频道列表为空) 2021-02-28 17:10:30 +08:00
buvta
8864a624f5 Revert "详情页默认选中历史播放的那集"该功能已实现,移动鼠标变换选中以切换分享播放指定集数
This reverts commit 52562a1a12.
2021-02-28 16:14:46 +08:00
haiyangcui
72ae7494d2 当IPTV的列表为空时,不显示频道列表 2021-02-27 22:23:25 +01:00
haiyangcui
877b564322 改变海报卡片背景,增强与背景的对比 2021-02-27 21:37:37 +01:00
haiyangcui
52562a1a12 详情页默认选中历史播放的那集 2021-02-27 21:18:03 +01:00
haiyangcui
43ff812b21 实现鼠标划过card时的放大效果 2021-02-27 16:08:37 +01:00
haiyangcui
a3a26e0deb 转移getDefaultSites函数到tools.js 2021-02-23 22:51:40 +01:00
haiyangcui
289f3c3c2d xgplayer2.18版本精简模式不工作,降级回2.17版本 2021-02-23 15:53:53 +01:00
haiyangcui
b580ae4329 记录最近打开detail页面的视图,当回到该视图时,恢复detail页面 2021-02-21 22:55:31 +01:00
haiyangcui
8ae32f0f55 v2.7.5 2021-02-21 21:49:39 +01:00
haiyangcui
713ffa6b3e 从gitee上获取推荐影视列表 2021-02-21 21:39:56 +01:00
haiyangcui
a3bc8f1f31 当重置或源站列表为空时,从云端获取源站 2021-02-21 21:32:58 +01:00
haiyangcui
22318f601b 修复播放速率显示信息位置错误的问题 2021-02-21 21:15:56 +01:00
buvta
aa8b4b527d 修复历史兼容导入m3u8List 2021-02-21 23:03:05 +08:00
haiyangcui
7bee4df2f9 默认使用gitee上的源站文件 2021-02-21 12:49:57 +01:00
haiyangcui
7a72b352c0 v2.7.4 2021-02-21 09:03:34 +01:00
haiyangcui
f924c6979b Merge branch 'master' of https://github.com/Hunlongyu/ZY-Player 2021-02-20 18:31:53 +01:00
haiyangcui
ef2740d801 降级qrcode.vue和saas模块 2021-02-20 18:31:36 +01:00
haiyangcui
0650e868eb 降级qrcode.vue 2021-02-20 18:24:56 +01:00
haiyangcui
573f026207 升级模块 2021-02-20 17:46:21 +01:00
haiyangcui
f9504439b0 使用axios get 无需使用post 2021-02-20 17:24:06 +01:00
haiyangcui
59f9772e82 支持从默认Github源或自定义在线源站文件重置源 2021-02-20 10:46:15 +01:00
buvta
2db48c9220 Merge branch 'master' into master 2021-02-09 23:55:26 +08:00
buvta
63c6ad0ec0 修复分享地址未正确生成的bug 2021-02-05 23:21:25 +08:00
buvta
730181e4bc 解析播放时隐藏部分菜单 2021-02-05 23:09:38 +08:00
buvta
7d61bb04a7 修复全新使用时不含源时产生的bug 2021-02-04 17:59:30 +08:00
buvta
613901872e v2.7.3 2021-02-04 16:40:08 +08:00
buvta
ef3264f3db 应用不再内置源 2021-02-04 16:28:07 +08:00
Hunlongyu
56d5858e0f Update README.md 2021-02-03 14:40:57 +08:00
buvta
3f4da7806a 功能重复,zy.search已包含过滤功能 2021-01-27 22:30:50 +08:00
haiyangcui
00aa3c8408 过滤搜索结果,只返回名字中包含搜索关键词的结果 2021-01-27 11:49:12 +01:00
buvta
a9d889f7d8 再次调整 2021-01-27 18:27:12 +08:00
buvta
b946423b65 修复精简模式切换播放器后停止时窗口大小异常 2021-01-26 19:25:10 +08:00
buvta
f2c87a7880 优化搜索结果过滤器,避免屏蔽福利时筛选为空 2021-01-25 19:52:32 +08:00
buvta
0ecc1367d2 优化搜索,过滤掉异常结果 2021-01-25 17:15:04 +08:00
Hunlongyu
9673303fe9 增加友链 2021-01-25 09:44:02 +08:00
Hunlongyu
88582b45b3 升级依赖 2021-01-25 09:32:01 +08:00
buvta
443cdc59fc 停止时默认生成hls播放器,使停止播放mp4时“播放”可用 2021-01-24 15:34:55 +08:00
buvta
c08ae7666c 优化音量保存策略 2021-01-24 15:25:13 +08:00
buvta
6bfeb9fcd8 修复历史清空按钮显示 2021-01-22 19:00:58 +08:00
buvta
e3bc519128 历史导入兼容老版数据m3u8List 2021-01-22 18:52:21 +08:00
buvta
504b11ceec 修复频道搜索功能 2021-01-22 18:40:38 +08:00
buvta
d281a2adab 再次调整直播自动换源 2021-01-22 17:43:52 +08:00
buvta
4e0f73a3de 再次调整黑屏操作 2021-01-22 17:05:44 +08:00
buvta
3a78425e7f 修复频道prefer源被删除导致的bug 2021-01-22 16:47:01 +08:00
buvta
a38b9aed56 优化直播卡顿自动切换 2021-01-21 23:40:26 +08:00
buvta
ec4980a4b6 黑屏有声音时尽量避免手动操作 2021-01-21 22:28:12 +08:00
buvta
2c93d755c7 Revert "修复play-request-was-interrupted报错",重新发布
This reverts commit 088cd70e41.
2021-01-21 18:16:25 +08:00
buvta
fe75084dd0 v2.7.2 2021-01-21 17:19:39 +08:00
buvta
047224ce80 修复直播时触发timeupdate导致的异常 2021-01-21 17:11:46 +08:00
buvta
31ea52e267 为应对flv切换到其它格式时可能出现的黑屏有声音情况,新增的关闭按钮常显 2021-01-21 16:11:52 +08:00
buvta
088cd70e41 修复play-request-was-interrupted报错 2021-01-21 15:47:02 +08:00
buvta
6bfd96942d 切换频道首尾部自动轮替 2021-01-21 15:44:28 +08:00
buvta
5d6579326c 频道所有源全部卡顿时自动换台 2021-01-21 15:36:33 +08:00
buvta
334933ce82 直播卡顿时自动换源 2021-01-20 23:22:23 +08:00
buvta
9fd3e5e2ed 修复flv直播停止功能 2021-01-20 22:22:46 +08:00
buvta
1e7199af3f 修复改动flv直播状态导致的bug 2021-01-20 18:23:10 +08:00
buvta
0d83b277e7 下移播放器速率栏,避免鼠标移动时误触进度条 2021-01-19 23:34:12 +08:00
buvta
08cbd1a73c 修正某些源站剧集顺序 2021-01-19 22:34:50 +08:00
buvta
01e58f458b 修复zy.download因分隔符导致的bug 2021-01-19 20:26:53 +08:00
buvta
3ac21ad1e5 更新7k源解析接口地址 2021-01-19 19:54:40 +08:00
buvta
f098198448 可通过重置更新应用默认解析接口地址 2021-01-19 19:54:07 +08:00
buvta
35e6e59b73 添加应用默认解析接口设置 2021-01-19 17:33:39 +08:00
buvta
bf9eaf09eb 调整解析接口默认打开方式 2021-01-19 17:28:55 +08:00
buvta
03ec267c1f 修改jiexiURL应对源自带解析的情况 2021-01-19 16:29:07 +08:00
buvta
97aea7b98d 修复因分隔符"$"改动引入的bug 2021-01-19 16:22:02 +08:00
buvta
f8ad00b4d0 zy.detail获取到的链接分隔符包含多个"$", 88资源站有点怪 2021-01-18 22:30:03 +08:00
buvta
607d4baae4 修复使图片无法加载时分享可用 2021-01-18 21:07:57 +08:00
buvta
51208892d0 修复zy.detail获取到的链接不包含"$"时产生的bug 2021-01-18 21:07:42 +08:00
buvta
516b791719 再次调整直播源检测跳过功能 2021-01-18 18:44:29 +08:00
buvta
8032645c25 修复调整生成调试信息时引入的bug 2021-01-18 18:33:15 +08:00
Hunlongyu
9d0536a3f8 升级依赖, 解决 GitHub 报错问题 2021-01-18 10:38:01 +08:00
Hunlongyu
85a39d67ce 修改第三方播放路径为 Gitee. 2021-01-18 10:28:01 +08:00
Hunlongyu
3184147910 升级依赖, 修复分享图片和二维码失效的问题 2021-01-18 10:09:18 +08:00
buvta
9d8a09e90d 修复直播源检测跳过功能 2021-01-17 23:27:56 +08:00
buvta
5d08e715aa 使用代理时可检测flv直播源 2021-01-17 14:55:39 +08:00
buvta
98378788fd 直播源检测支持flv格式,使用代理时暂不可用 2021-01-16 17:20:19 +08:00
buvta
af768d527a 直播支持flv格式,播放器停止功能待修复 2021-01-15 22:12:55 +08:00
buvta
4e5aab6f66 调整播放调试生成信息 2021-01-03 22:49:17 +08:00
buvta
fda305a8ae Film搜索亦过滤 2021-01-03 00:14:18 +08:00
buvta
21487e2754 Film添加列表时过滤掉无链接的项 2021-01-02 23:58:00 +08:00
buvta
0f934413d0 再次调整axios超时设置 2021-01-02 21:38:56 +08:00
buvta
96c68da8b7 有些直播源网址路径不含m3u8而是以参数带m3u8结尾 2021-01-02 21:15:17 +08:00
buvta
0e0de8f23c 修正关闭内嵌播放器bug 2021-01-02 16:01:30 +08:00
buvta
2b24ac7d0c 手动修正变量定义var 2021-01-02 15:05:24 +08:00
buvta
458144a6ea 修正清理缓存描述 2021-01-02 14:58:24 +08:00
buvta
69113c7a9a 更新下载完毕后通知 2021-01-01 22:55:14 +08:00
buvta
d247a2fa23 分享在详情页可选定集数,播放页面分享为当前集,其它页面分享默认首集 2021-01-01 22:50:33 +08:00
buvta
c14c78b68f Star添加DetailCache 2021-01-01 15:46:54 +08:00
buvta
cc4e2c5cad 历史页面禁止拖动 2021-01-01 15:24:24 +08:00
buvta
073e111af8 嵌入播放时添加关闭按钮 2021-01-01 15:19:24 +08:00
buvta
fea076318d v2.7.1 2020-12-31 23:30:00 +08:00
buvta
df867fa919 修复播放和历史页面无法分享的bug 2020-12-31 23:27:25 +08:00
buvta
a7f0030462 疑似需要授权,先移除阿里云源 2020-12-31 22:53:05 +08:00
buvta
b5274c79b0 修复获取下载链接时的bug 2020-12-31 22:32:59 +08:00
buvta
a2de1984e3 改为原来的更新组件,避免因为超时而中断卡住 2020-12-31 22:12:30 +08:00
buvta
81b5391f64 详情页根据历史记录更新videoFlag 2020-12-31 20:20:34 +08:00
buvta
2c6ad44974 根据videoFlag获取下载链接 2020-12-31 20:11:06 +08:00
buvta
e1c942dc7b 过滤掉无效链接 2020-12-31 19:16:35 +08:00
buvta
4420e6961e 修复因为删除m3u8List数据库更新bug 2020-12-31 18:35:25 +08:00
buvta
2deffab3ba 解决detail丢失问题 2020-12-31 17:08:55 +08:00
buvta
05a07bee62 移除m3u8List 2020-12-31 16:34:18 +08:00
buvta
025d1606f7 保存videoFlag 2020-12-31 16:34:18 +08:00
buvta
b72c88dfb9 将ZY支持的播放列表前置到首位 2020-12-31 16:34:05 +08:00
buvta
db240cc163 优化getPlayer 2020-12-31 13:38:44 +08:00
haiyangcui
0ef55f0139 在线解析的视频,不显示"手动跳略时长" 2020-12-30 17:14:46 +01:00
haiyangcui
0ad9b49224 解决有多个视频列表时的播放错误 2020-12-30 17:08:21 +01:00
haiyangcui
ccbf4fb5d7 移除不必要的log 2020-12-30 17:03:47 +01:00
haiyangcui
46e376a351 翻转需要解析的视频网站的视频顺序,保证最近更新的视频在最上面 2020-12-30 16:58:33 +01:00
buvta
23e693a314 多个播放视频列表时优先选择应用支持的 2020-12-30 23:52:38 +08:00
buvta
3a3e37f4bd 修复直接手动设置调略设置时片头片尾一起变动 2020-12-30 23:08:46 +08:00
buvta
e568053b95 修复改动导致快捷键设置片头片尾标记失效 2020-12-30 23:02:22 +08:00
haiyangcui
fe9cd9f5bc 无需记录mp4List 2020-12-30 15:44:19 +01:00
buvta
8cf3ec94d9 清理Play多余代码 2020-12-30 20:23:57 +08:00
buvta
112f92d3e0 在线解析时添加进历史记录 2020-12-30 20:10:11 +08:00
buvta
88db766337 添加提示,避免调用解析接口响应慢时尴尬空白 2020-12-30 19:32:55 +08:00
buvta
205b9d7979 改写zy.download以优化代码 2020-12-30 17:13:12 +08:00
buvta
d5a7771a67 vuex引入DetailCache,避免重复获取详情页 2020-12-30 15:57:08 +08:00
buvta
655ed7fb16 解决改动新引入的bug 2020-12-30 12:41:34 +08:00
haiyangcui
808f3d2012 修复播放完在线解析的视频后无法播放m3u8的问题 2020-12-29 23:38:02 +01:00
haiyangcui
ee4b944abf 修改被选中的video flag背景色 2020-12-29 23:03:47 +01:00
haiyangcui
b4abd9369f 将视频flag列表和剧集列表分开 2020-12-29 22:56:08 +01:00
haiyangcui
079da637ec 只有当视频有多个源时,显示视频源flag列表 2020-12-29 22:53:30 +01:00
haiyangcui
0c8a100d1c 视频分类被选后时改变背景颜色 2020-12-29 22:29:39 +01:00
haiyangcui
0cc330463f 获取全部视频提供的视频列表,为兼容目的,zy.detail函数仍返回m3u8List和mp4List, 会在后续工作中陆续删除 2020-12-29 22:04:52 +01:00
buvta
f50d669a5f 修正下载功能并调整通知描述 2020-12-30 00:16:22 +08:00
buvta
57fd1d325e 修复Play点击播放器下方按钮时弹窗反复弹出及按钮active状态 2020-12-29 22:59:48 +08:00
buvta
e31b921486 修复Play点播m3u8播放列表导出功能 2020-12-29 21:29:10 +08:00
buvta
08b39a5bb1 调整Play历史记录的播放时间保存 2020-12-29 18:30:05 +08:00
buvta
1fefc792fc 修改请求referer 2020-12-29 17:03:13 +08:00
buvta
f134785696 修改请求referer 2020-12-29 16:02:50 +08:00
buvta
b9a4784de2 bug依旧,xgplayer-mp4似乎没啥作用先移除 2020-12-29 15:59:43 +08:00
buvta
855472fe68 修复mp4停止后点击播放按钮无法播放,并引入xgplayer-flv.js 2020-12-29 00:50:59 +08:00
buvta
4834d4423c Revert "eslint自动修复var定义变量带来的警告"
This reverts commit 4de2177cc7.
2020-12-28 23:05:12 +08:00
buvta
4de2177cc7 eslint自动修复var定义变量带来的警告 2020-12-28 22:29:11 +08:00
buvta
ca9f9c9160 修复点击播放按钮时产生重复历史记录 2020-12-28 21:37:29 +08:00
buvta
7c0f688f9d 修正Play页面最近历史记录 2020-12-28 20:15:09 +08:00
buvta
4bfae63201 点播支持mp4格式视频源 2020-12-28 19:45:18 +08:00
haiyangcui
419a56518f 解决上次的commit错误 2020-12-27 21:55:30 +01:00
haiyangcui
36efdcd869 sites数据引入jiexiUrl数据段,播放时需要解析时,不同源可以定义不同的解析网站 2020-12-27 21:53:24 +01:00
haiyangcui
a297aca812 直接在内嵌iframe中播放阿里云资源 2020-12-27 17:36:49 +01:00
buvta
0b4dd2e859 阿里云资源站临时可用 2020-12-28 00:00:24 +08:00
buvta
3142306a0c 优化tools使某些源站api可用 2020-12-27 21:20:28 +08:00
haiyangcui
9d4765c1ea 修改拼写错误 2020-12-26 22:59:25 +01:00
buvta
547dffb922 动态配置axios超时 2020-12-26 22:35:30 +08:00
buvta
84e61acde7 Update bug.md 2020-12-26 14:18:10 +08:00
haiyangcui
11c756466c 播放的一开始就先清空onlineUrl,否则播放iptv会有问题 2020-12-24 16:22:51 +01:00
haiyangcui
06291ffc4b 解决播放解析视频后再打开,因为没有时间记录而报错的问题 2020-12-24 16:06:56 +01:00
haiyangcui
1f65e5ba0a 在播放页面利用iframe嵌入需要在线解析的内容 2020-12-24 15:53:02 +01:00
haiyangcui
392f0fd326 改进豆瓣搜索,匹配名字和年代 2020-12-24 13:04:01 +01:00
haiyangcui
a6b6407679 采用7K接口 2020-12-23 14:30:59 +01:00
haiyangcui
96678bf5df 只有当xg-btn-showhistory存在时,才设置它的样式 2020-12-23 14:30:05 +01:00
haiyangcui
3c37b8286f 解决运行时因为star history table还没实例化而出现的错误 2020-12-23 14:27:53 +01:00
haiyangcui
38862f6ad8 升级依赖,并解决升级后出现的错误 2020-12-23 14:24:43 +01:00
buvta
35213238f4 使用el内置clickoutside替代vue-clickaway,修复点击菜单按钮时被捕捉失效 2020-12-21 21:01:51 +08:00
buvta
2a03388d6b v2.7.0 2020-12-19 13:29:31 +08:00
buvta
ef704a9d45 禁用allowRunningInsecureContent 2020-12-19 13:27:07 +08:00
buvta
946978fa86 修复深色主题下el-switch标签文本不可见 2020-12-19 13:10:30 +08:00
haiyangcui
70b03e67fb 在右侧显示分隔栏内容,1.在中间的时候永远无法和中间的下拉菜单对齐, 2. 不常用的按钮放在边侧,以降低对用户注意力的影响 2020-12-18 22:25:55 +01:00
haiyangcui
1ee7c6f032 设置不同主题下的工具栏样式 2020-12-18 22:11:18 +01:00
46 changed files with 51615 additions and 14405 deletions

View File

@@ -12,6 +12,7 @@ module.exports = {
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'standard/no-callback-literal': 0
}
}

View File

@@ -1,7 +1,7 @@
---
name: 报告Bug(请先查看常见问题及搜索issue列表中有无你要提的问题)
name: 报告Bug(请先查看常见问题及搜索已关闭issue列表中有无你要提的问题)
about: 创建报告以帮助我们改进
title: '(未回答的问题请删除)'
title: '(未回答的问题请删除,减少多余信息干扰'
labels: bug
assignees: ''

View File

@@ -1,4 +1,4 @@
name: release-build
name: x86-release-build
on:
push:
@@ -25,11 +25,3 @@ jobs:
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: dist_electron
path: dist_electron/*.exe
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -42,9 +42,8 @@
### 🌴 下载
- 🍓 [Github -- 官方下载](https://github.com/Hunlongyu/ZY-Player/releases)
- 🍉 [蓝奏云 -- 快速下载](https://www.lanzoux.com/b04s6a3re) 密码:95px
- 🍒 适用于32位操作系统的x86软件,在蓝奏云网盘里, 后缀名: ZY Player * 32位.exe
- 🎃 软件暂时关闭下载通道. 请大家支持正版.
- 🎭 所有资源来自网上, 该软件不参与任何制作, 上传, 储存等内容, 禁止传播违法资源. 该软件仅供学习参考, 请于安装后24小时内删除.
### 🎠 平台

View File

@@ -236,6 +236,7 @@
<li><i class="bx bx-chevron-right"></i> <a target="_blank" href="#">Home</a></li>
<li><i class="bx bx-chevron-right"></i> <a target="_blank" href="https://www.ghpym.com/zyplayer.html">果核剥壳</a></li>
<li><i class="bx bx-chevron-right"></i> <a target="_blank" href="https://www.iplaysoft.com/zy-player.html">异次元软件世界</a></li>
<li><i class="bx bx-chevron-right"></i> <a target="_blank" href="https://xydh.fun/">炫辕</a></li>
</ul>
</div>

33749
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "zy",
"version": "2.6.10",
"version": "2.8.8",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -13,60 +13,54 @@
"postuninstall": "electron-builder install-app-deps",
"electron:generate-icons": "electron-icon-builder --input=./public/icon.png --output=build --flatten",
"release": "vue-cli-service electron:build -p always",
"dist": "vue-cli-service electron:build --win --x64"
"dist": "vue-cli-service electron:build --win --ia32"
},
"main": "background.js",
"dependencies": {
"@imjs/electron-differential-updater": "^5.1.3",
"axios": "^0.21.0",
"cheerio": "^1.0.0-rc.3",
"core-js": "^3.8.0",
"dexie": "^3.0.3",
"@electron/remote": "^2.0.8",
"axios": "^0.21.2",
"bootstrap-vue": "^2.22.0",
"cheerio": "1.0.0-rc.6",
"core-js": "^3.10.2",
"dexie": "^3.2.2",
"electron-localshortcut": "^3.2.1",
"electron-proxy-agent": "^1.2.0",
"element-ui": "^2.14.1",
"fast-xml-parser": "^3.17.4",
"electron-updater": "^4.3.8",
"element-ui": "^2.15.9",
"fast-xml-parser": "^3.19.0",
"html2canvas": "^1.0.0-rc.7",
"iptv-playlist-parser": "^0.5.0",
"iptv-playlist-parser": "^0.6.0",
"m3u": "0.0.2",
"m3u8-parser": "^4.5.0",
"m3u8-parser": "^4.6.0",
"memcached": "^2.2.2",
"modern-normalize": "^1.0.0",
"mousetrap": "^1.6.5",
"pinyin-match": "^1.1.1",
"pinyin-match": "^1.2.1",
"qrcode.vue": "^1.7.0",
"randomstring": "^1.1.5",
"session": "^0.1.0",
"sortablejs": "^1.12.0",
"sortablejs": "^1.13.0",
"v-fit-columns": "^0.2.0",
"vue": "^2.6.12",
"vue-clickaway": "^2.2.2",
"vue": "^2.6.14",
"vue-infinite-loading": "^2.4.5",
"vue-waterfall-plugin": "^1.1.0",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.0",
"xgplayer": "^2.13.2",
"xgplayer-hls.js": "^2.2.5"
"vuex": "^3.6.2",
"xgplayer": "2.19.1",
"xgplayer-flv.js": "^2.3.0",
"xgplayer-hls.js": "^2.4.2",
"xgplayer-mp4": "^1.2.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.9",
"@vue/cli-plugin-eslint": "~4.5.9",
"@vue/cli-plugin-vuex": "~4.5.9",
"@vue/cli-service": "~4.5.9",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"@vue/cli-plugin-babel": "~4.5.11",
"@vue/cli-plugin-vuex": "~4.5.11",
"@vue/cli-service": "~4.5.11",
"babel-plugin-component": "^1.1.1",
"electron": "^11.0.3",
"electron": "^13.0.0",
"electron-devtools-installer": "^3.1.1",
"eslint": "^7.14.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.1.0",
"eslint-plugin-vue": "^7.1.0",
"sass": "^1.29.0",
"sass": "^1.30.0",
"sass-loader": "^10.1.0",
"vue-cli-plugin-electron-builder": "2.0.0-rc.5",
"vue-template-compiler": "^2.6.12"
"vue-cli-plugin-electron-builder": "2.0.0-rc.6",
"vue-template-compiler": "^2.6.14"
}
}

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="referrer" content="same-origin"/>
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>

View File

@@ -10,7 +10,6 @@
<Setting v-show="view === 'Setting'" />
<IPTV v-show="view === 'IPTV'" />
<EditSites v-if="view === 'EditSites'"/>
<Recommendation v-show="view === 'Recommendation'" />
</div>
<transition name="slide">
<Detail v-if="detail.show"/>
@@ -22,11 +21,62 @@
</template>
<script>
import { setting } from './lib/dexie'
const remote = require('@electron/remote')
export default {
name: 'App',
data () {
return {
appTheme: 'theme-light'
appTheme: 'theme-light',
winSizePosition: {
x: 0,
y: 0,
width: 0,
height: 0
}
}
},
created () {
// 窗口创建口,检查是否有窗口大小位置的记录,如果有的话,更新窗口位置及大小
setting.find().then(res => {
if (res.restoreWindowPositionAndSize) {
var win = remote.getCurrentWindow()
win.setBounds({
x: res.windowPositionAndSize.x,
y: res.windowPositionAndSize.y,
width: res.windowPositionAndSize.width,
height: res.windowPositionAndSize.height
})
this.winSizePosition = {
x: win.getPosition()[0],
y: win.getPosition()[1],
width: win.getSize()[0],
height: win.getSize()[1]
}
}
})
},
updated () {
// 本来想hook up到beforedestroy 但不工作
// 每当窗口更新时检查窗口大小及位置记录到setting数据库中
if (this.setting.restoreWindowPositionAndSize) {
const win = remote.getCurrentWindow()
var newWinSizePosition = {
x: win.getPosition()[0],
y: win.getPosition()[1],
width: win.getSize()[0],
height: win.getSize()[1]
}
if (newWinSizePosition.x !== this.winSizePosition.x ||
newWinSizePosition.y !== this.winSizePosition.y ||
newWinSizePosition.width !== this.winSizePosition.width ||
newWinSizePosition.height !== this.winSizePosition.height) {
this.winSizePosition = newWinSizePosition
setting.find().then(res => {
res.windowPositionAndSize = newWinSizePosition
setting.update(res)
})
}
}
},
computed: {

View File

@@ -178,7 +178,6 @@
.el-select-dropdown__item.selected.hover{ //是上游的bug吗临时性修补
background-color: transparent;
}
.el-select-dropdown__wrap{
max-height: 574px
}
@@ -284,7 +283,8 @@
cursor: pointer;
transition: 0.2s;
&:hover {
top: -3px;
width: 102%;
height: 102%
}
.img{
position: relative;
@@ -441,3 +441,134 @@
}
}
}
// detail
.detail{
.detail-content{
.detail-body{
.m3u8{
.show-picture{
height: 100%;
width: 100%;
padding: 10px;
.card{
border-radius: 6px;
overflow: hidden;
position: relative;
cursor: pointer;
transition: 0.2s;
&:hover {
width: 102%;
height: 102%
}
.img{
position: relative;
min-height: 40px;
img{
width: 100%;
height: auto;
cursor: pointer;
}
.rate{
position: absolute;
top: 3%;
right: -40%;
width: 100%;
background-color: #111111aa;
color:#2f90b9;
height: 30px;
line-height: 30px;
font-size: 14px;
font-weight: bolder;
text-align: center;
transform: rotate(45deg);
}
.site{
position: absolute;
top: 0%;
left: 0%;
width: 100%;
background-color: #111111aa;
color:#2f90b9;
height: 30px;
line-height: 30px;
font-size: 14px;
font-weight: bolder;
text-align: center;
}
.progress{
position: absolute;
bottom: 10%;
left: 0%;
width: 40%;
background-color: #111111aa;
color: #f8df70;
height: 30px;
line-height: 30px;
font-size: 14px;
font-weight: bolder;
text-align: left;
}
.update{
position: absolute;
top: 5%;
left: -40%;
width: 100%;
background-color: #68b88e;
color: #cdcdcd;
height: 30px;
line-height: 30px;
font-size: 14px;
text-align: center;
transform: rotate(-45deg);
}
.operate{
display: none;
position: absolute;
left: 0;
bottom: 0;
background-color: #111111aa;
width: 100%;
font-size: 13px;
.operate-wrap{
display: flex;
justify-content: space-between;
.o-play, .o-star, .o-share{
cursor: pointer;
display: inline-block;
width: 80px;
height: 36px;
text-align: center;
line-height: 36px;
color: #cdcdcd;
&:hover{
background-color: #111;
}
}
}
}
}
.name{
font-size: 16px;
padding: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.info{
display: flex;
justify-content: space-between;
font-size: 12px;
padding: 10px;
}
&:hover{
.operate{
display: block;
}
}
}
}
}
}
}
}

View File

@@ -14,6 +14,7 @@
--l-fc-3: #823aa0;
--l-bgc-1: #ffffff;
--l-bgc-2: #f2f6f9;
--l-bgc-3: #F9FBFC;
--l-bsc: 0 1px 3px #8e8da233, 0 1px 2px #8e8da244;
--l-bsc-hover: 0 14px 28px #8e8da255, 0 10px 10px #8e8da244;
--l-bsc-2: 0 -4px 23px 0 #8e8da233;
@@ -32,6 +33,7 @@
--d-fc-3: #38dd77;
--d-bgc-1: #222222;
--d-bgc-2: #2f2f2f;
--d-bgc-3: #292929;
--d-bsc: 0 1px 3px #38dd7733, 0 1px 2px #38dd7744;
--d-bsc-hover: 0 14px 28px #38dd7755, 0 10px 10px #38dd7744;
--d-bsc-2: 0 -4px 23px 0 #38dd7733;
@@ -50,6 +52,7 @@
--g-fc-3: #C1D95C;
--g-bgc-1: #4baea0;
--g-bgc-2: #74b4ac;
--g-bgc-3: #60B1A6;
--g-bsc: 0 1px 3px #e1ebe033, 0 1px 2px #e1ebe044;
--g-bsc-hover: 0 14px 28px #e1ebe055, 0 10px 10px #e1ebe044;
--g-bsc-2: 0 -4px 23px 0 #e1ebe033;
@@ -68,6 +71,7 @@
--p-fc-3: #177ea7;
--p-bgc-1: #ff8499;
--p-bgc-2: #fea1b2;
--p-bgc-3: #FF93A6;
--p-bsc: 0 1px 3px #ef528533, 0 1px 2px #ef528544;
--p-bsc-hover: 0 14px 28px #ef528555, 0 10px 10px #ef528544;
--p-bsc-2: 0 -4px 23px 0 #ef528533;

View File

@@ -131,6 +131,23 @@
border-color: var(--d-c-8);
}
}
.selected {
background-color: var(--d-c-5);
&:hover{
background-color: var(--d-c-5);
}
}
}
.show-picture{
color: var(--d-fc-1);
.card{
background-color: var(--d-bgc-3);
box-shadow: var(--d-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--d-bsc-hover);
}
}
}
}
}
@@ -139,6 +156,12 @@
.play{
background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc);
.el-switch__label {
color: var(--d-fc-2)
}
.el-switch__label.is-active {
color: #409EFF
}
.el-input{
input{
background-color: var(--d-bgc-1);
@@ -155,6 +178,21 @@
}
}
.box{
.title{
.right{
color: var(--d-fc-2);
svg{
stroke: var(--d-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--d-c-8);
stroke-width: 1.5;
fill: var(--d-c-2);
}
}
}
}
.more{
span{
svg{
@@ -319,6 +357,19 @@
// Page of list using table and picture
.listpage{
color: var(--d-fc-2);
.listpage-header-divider{
background-color: var(--d-bgc-1);
.el-divider__text {
background-color: var(--d-bgc-2);
}
.el-button{
background-color: var(--d-bgc-2);
color: var(--d-fc-2);
&:hover{
color: var(--d-fc-3)
}
}
}
.listpage-header, .toolbar{
border-bottom-color: var(--d-c-3);
.btn{
@@ -326,6 +377,12 @@
color: var(--d-fc-3)
}
}
.el-switch__label {
color: var(--d-fc-2)
}
.el-switch__label.is-active {
color: #409EFF
}
.el-button{
background-color: var(--d-bgc-2);
color: var(--d-fc-2);
@@ -415,7 +472,7 @@
.show-picture{
color: var(--d-fc-1);
.card{
background-color: var(--d-bgc-1);
background-color: var(--d-bgc-3);
box-shadow: var(--d-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{

View File

@@ -131,6 +131,23 @@
border-color: var(--g-c-8);
}
}
.selected {
background-color: var(--g-c-5);
&:hover{
background-color: var(--g-c-5);
}
}
}
.show-picture{
color: var(--g-fc-1);
.card{
background-color: var(--g-bgc-3);
box-shadow: var(--g-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--g-bsc-hover);
}
}
}
}
}
@@ -155,6 +172,21 @@
}
}
.box{
.title{
.right{
color: var(--g-fc-2);
svg{
stroke: var(--g-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--g-c-8);
stroke-width: 1.5;
fill: var(--g-c-2);
}
}
}
}
.more{
span{
svg{
@@ -319,6 +351,19 @@
// Page of list using table and picture
.listpage{
color: var(--g-fc-2);
.listpage-header-divider{
background-color: var(--g-bgc-1);
.el-divider__text {
background-color: var(--g-bgc-2);
}
.el-button{
background-color: var(--g-bgc-2);
color: var(--g-fc-2);
&:hover{
color: var(--g-fc-3)
}
}
}
.listpage-header, .toolbar{
border-bottom-color: var(--g-c-3);
.btn{
@@ -415,7 +460,7 @@
.show-picture{
color: var(--g-fc-1);
.card{
background-color: var(--g-bgc-1);
background-color: var(--g-bgc-3);
box-shadow: var(--g-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{

View File

@@ -1,5 +1,6 @@
.theme-light{
background-color: var(--l-bgc-1);
// background-color: var(--l-bgc-1);
background: rgba(0, 0, 0, 0);
.zy-select{
color: var(--l-fc-1);
background-color: var(--l-bgc-1);
@@ -131,6 +132,23 @@
border-color: var(--l-c-8);
}
}
.selected {
background-color: var(--l-c-5);
&:hover{
background-color: var(--l-c-5);
}
}
}
.show-picture{
color: var(--l-fc-1);
.card{
background-color: var(--l-bgc-3);
box-shadow: var(--l-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--l-bsc-hover);
}
}
}
}
}
@@ -155,6 +173,21 @@
}
}
.box{
.title{
.right{
color: var(--l-fc-2);
svg{
stroke: var(--l-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--l-c-8);
stroke-width: 1.5;
fill: var(--l-c-2);
}
}
}
}
.more{
span{
svg{
@@ -319,6 +352,19 @@
// Page of list using table and picture
.listpage{
color: var(--l-fc-2);
.listpage-header-divider{
background-color: var(--l-bgc-1);
.el-divider__text {
background-color: var(--l-bgc-2);
}
.el-button{
background-color: var(--l-bgc-2);
color: var(--l-fc-2);
&:hover{
color: var(--l-fc-3)
}
}
}
.listpage-header, .toolbar{
border-bottom-color: var(--l-c-3);
.btn{
@@ -415,7 +461,7 @@
.show-picture{
color: var(--l-fc-1);
.card{
background-color: var(--l-bgc-1);
background-color: var(--l-bgc-3);
box-shadow: var(--l-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{

View File

@@ -131,6 +131,23 @@
border-color: var(--p-c-8);
}
}
.selected {
background-color: var(--p-c-5);
&:hover{
background-color: var(--p-c-5);
}
}
}
.show-picture{
color: var(--p-fc-1);
.card{
background-color: var(--p-bgc-3);
box-shadow: var(--p-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{
box-shadow: var(--p-bsc-hover);
}
}
}
}
}
@@ -155,6 +172,21 @@
}
}
.box{
.title{
.right{
color: var(--p-fc-2);
svg{
stroke: var(--p-c-5);
stroke-width: 1;
fill: none;
&:hover{
stroke: var(--p-c-8);
stroke-width: 1.5;
fill: var(--p-c-2);
}
}
}
}
.more{
span{
svg{
@@ -319,6 +351,19 @@
// Page of list using table and picture
.listpage{
color: var(--p-fc-2);
.listpage-header-divider{
background-color: var(--p-bgc-1);
.el-divider__text {
background-color: var(--p-bgc-2);
}
.el-button{
background-color: var(--p-bgc-2);
color: var(--p-fc-2);
&:hover{
color: var(--p-fc-3)
}
}
}
.listpage-header, .toolbar{
border-bottom-color: var(--p-c-3);
.btn{
@@ -415,7 +460,7 @@
.show-picture{
color: var(--p-fc-1);
.card{
background-color: var(--p-bgc-1);
background-color: var(--p-bgc-3);
box-shadow: var(--p-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{

View File

@@ -4,8 +4,12 @@ import { app, protocol, BrowserWindow, globalShortcut } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import { initUpdater } from './lib/update/update'
require('@electron/remote/main').initialize()
const isDevelopment = process.env.NODE_ENV !== 'production'
// const log = require('electron-log') // 用于调试主程序
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors') // 允许跨域
app.commandLine.appendSwitch('--ignore-certificate-errors', 'true') // 忽略证书相关错误
@@ -22,7 +26,9 @@ function createWindow () {
webPreferences: {
webSecurity: false,
enableRemoteModule: true,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
nodeIntegration: true,
contextIsolation: false,
allowRunningInsecureContent: false
}
})
@@ -33,6 +39,24 @@ function createWindow () {
createProtocol('app')
win.loadURL('app://./index.html')
}
// 修改request headers
// Sec-Fetch下禁止修改浏览器自动加上请求头 https://www.cnblogs.com/fulu/p/13879080.html 暂时先用index.html的meta referer policy替代
const filter = {
urls: ['http://*/*', 'http://*/*']
}
require("@electron/remote/main").enable(win.webContents)
win.webContents.session.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
const url = new URL(details.url)
details.requestHeaders.Origin = url.origin
if (!details.url.includes('//localhost') && details.requestHeaders.Referer && details.requestHeaders.Referer.includes('//localhost')) {
details.requestHeaders.Referer = url.origin
}
callback({ // https://github.com/electron/electron/issues/23988 回调似乎无法修改headers暂时先用index.html的meta referer policy替代
cancel: false,
requestHeaders: details.requestHeaders
})
})
initUpdater(win)
@@ -44,7 +68,7 @@ function createWindow () {
if (process.platform === 'darwin') {
app.dock.show()
}
if (process.platform === 'Linux') {
if (process.platform === 'linux') {
app.disableHardwareAcceleration()
app.commandLine.appendSwitch('--no-sandbox') // linux 关闭沙盒模式
}

View File

@@ -14,13 +14,13 @@
</g>
</svg>
</span>
<span :class="[view === 'Recommendation' ? 'active ': ''] + 'zy-svg'" @click="changeView('Recommendation')">
<!-- <span :class="[view === 'Recommendation' ? 'active ': ''] + 'zy-svg'" @click="changeView('Recommendation')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="48px" height="48px" viewBox="0 0 24 24" aria-labelledby="thumbUpIconTitle" stroke="#2329D6" stroke-width="1" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#2329D6">
<title id="thumbUpIconTitle">影视推荐</title>
<path d="M8,8.73984815 C8,8.26242561 8.17078432,7.80075162 8.4814868,7.43826541 L13.2723931,1.84887469 C13.7000127,1.34998522 14.4122932,1.20614658 15,1.5 C15.5737957,1.78689785 15.849314,2.45205792 15.6464466,3.06066017 L14,8 L18.6035746,8 C18.7235578,8 18.8432976,8.01079693 18.9613454,8.03226018 C20.0480981,8.22985158 20.7689058,9.27101818 20.5713144,10.3577709 L19.2985871,17.3577709 C19.1256814,18.3087523 18.2974196,19 17.3308473,19 L10,19 C8.8954305,19 8,18.1045695 8,17 L8,8.73984815 Z"/>
<path d="M4,18 L4,9"/>
</svg>
</span>
</span> -->
<span :class="[view === 'Play' ? 'active ': ''] + 'zy-svg'" @click="changeView('Play')">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="playIconTitle">
<title id="playIconTitle">播放</title>
@@ -53,6 +53,12 @@
import { mapMutations } from 'vuex'
export default {
name: 'Aside',
data () {
return {
lastViewOpenDetail: '',
savedDetail: {}
}
},
computed: {
view: {
get () {
@@ -74,9 +80,19 @@ export default {
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL']),
changeView (e) {
// 记录打开detail的view
if (this.detail.show === true) {
this.lastViewOpenDetail = this.view
this.savedDetail = this.detail
}
this.view = e
this.detail = {
show: false
// 如果回到上一次打开detail的试图页面,恢复detail页面
if (e === this.lastViewOpenDetail) {
this.detail = this.savedDetail
} else {
this.detail = {
show: false
}
}
}
}

View File

@@ -17,7 +17,7 @@
</svg>
</span>
</div>
<div class="detail-body zy-scroll" v-show="!loading">
<div class="detail-body zy-scroll listpage" v-show="!loading">
<div class="info">
<div class="info-left">
<img :src="info.pic" alt="">
@@ -36,10 +36,10 @@
</div>
</div>
<div class="operate">
<span @click="playEvent(0)">播放</span>
<span @click="starEvent">收藏</span>
<span @click="playEvent(selectedEpisode)">播放</span>
<span @click="starEvent(info)">收藏</span>
<span @click="downloadEvent">下载</span>
<span @click="shareEvent">分享</span>
<span @click="shareEvent(info,selectedEpisode)">分享</span>
<span @click="doubanLinkEvent">豆瓣</span>
<span @click="togglePlayOnlineEvent">
<input type="checkbox" v-model="playOnline"> 播放在线高清视频
@@ -52,10 +52,57 @@
</span>
</div>
<div
class="desc" v-show="info.des">{{info.des}}</div>
class="desc" v-show="info.des">{{info.des}}
</div>
<div class="m3u8" v-if="videoFullList.length > 1">
<div class="box">
<span v-bind:class="{ selected: i.flag === videoFlag }" v-for="(i, j) in videoFullList" :key="j" @click="updateVideoList(i)">{{i.flag}}</span>
</div>
</div>
<div class="m3u8">
<div class="box">
<span v-for="(i, j) in m3u8List" :key="j" @click="playEvent(j)">{{i | ftName}}</span>
<span v-bind:class="{ selected: j === selectedEpisode }" v-for="(i, j) in videoList" :key="j" @click="playEvent(j)" @mouseenter="() => { selectedEpisode = j }">{{ i | ftName(j) }}</span>
</div>
</div>
<div class="m3u8">
<div class="show-picture" v-show="info.recommendations && info.recommendations.length > 0">
<span>喜欢这部电影的人也喜欢 · · · · · ·</span>
<Waterfall :list="info.recommendations" :gutter="20" :width="240"
:breakpoints="{
1200: { //当屏幕宽度小于等于1200
rowPerView: 4,
},
800: { //当屏幕宽度小于等于800
rowPerView: 3,
},
500: { //当屏幕宽度小于等于500
rowPerView: 2,
}
}"
animationEffect="fadeIn"
backgroundColor="rgba(0, 0, 0, 0)">
<template slot="item" slot-scope="props">
<div class="card">
<div class="img">
<img style="width: 100%" :src="props.data.pic" alt="" @click="detailEvent(props.data)">
<div class="operate">
<div class="operate-wrap">
<span class="o-play" @click="playRecommendationEvent(props.data)">播放</span>
<span class="o-star" @click="starEvent(props.data)">收藏</span>
<span class="o-share" @click="shareEvent(props.data, 0)">分享</span>
</div>
</div>
</div>
<div class="name">{{props.data.name}}</div>
<div class="info">
<span>{{props.data.area}}</span>
<span>{{props.data.year}}</span>
<span>{{props.data.note}}</span>
<span>{{props.data.type}}</span>
</div>
</div>
</template>
</Waterfall>
</div>
</div>
</div>
@@ -67,6 +114,7 @@
</template>
<script>
import { mapMutations } from 'vuex'
import Waterfall from 'vue-waterfall-plugin'
import zy from '../lib/site/tools'
import onlineVideo from '../lib/site/onlineVideo'
import { star, history } from '../lib/dexie'
@@ -76,17 +124,26 @@ export default {
data () {
return {
loading: true,
m3u8List: [],
videoFlag: '',
videoList: [],
videoFullList: [],
key: '',
site: {},
info: {},
playOnline: false,
selectedEpisode: 0, // 选定集数
selectedOnlineSite: '哔嘀',
onlineSites: ['哔嘀', '素白白', '简影', '极品', '喜欢看', '1080影视']
}
},
filters: {
ftName (e) {
const name = e.split('$')[0]
return name
ftName (e, n) {
const num = e.split('$')
if (num.length > 1) {
return e.split('$')[0]
} else {
return `${(n + 1)}`
}
}
},
computed: {
@@ -121,26 +178,66 @@ export default {
set (val) {
this.SET_SHARE(val)
}
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
}
},
components: {
Waterfall
},
methods: {
...mapMutations(['SET_VIEW', 'SET_VIDEO', 'SET_DETAIL', 'SET_SHARE']),
...mapMutations(['SET_VIEW', 'SET_VIDEO', 'SET_DETAIL', 'SET_SHARE', 'SET_DetailCache']),
async playRecommendationEvent (e) {
const db = await history.find({ site: this.detail.key, ids: e.id })
if (db) {
this.video = { key: db.site, info: { id: db.ids, name: db.name, index: db.index, site: this.detail.site } }
} else {
this.video = { key: this.detail.key, info: { id: e.id, name: e.name, index: 0, site: this.detail.site } }
}
this.video.detail = e
this.view = 'Play'
this.detail.show = false
},
addClass (flag) {
if (flag === this.videoFlag) {
return 'selectedBox'
} else {
return 'box'
}
},
close () {
this.detail.show = false
},
async updateVideoList (e) {
this.videoFlag = e.flag
this.videoList = e.list
const db = await history.find({ site: this.detail.key, ids: this.detail.info.id })
if (db) {
const doc = { ...db }
doc.videoFlag = e.flag
delete doc.id
history.update(db.id, doc)
}
},
async playEvent (n) {
if (!this.playOnline) {
const db = await history.find({ site: this.detail.key, ids: this.detail.info.id })
if (db) {
this.video = { key: db.site, info: { id: db.ids, name: db.name, index: n, site: this.detail.site } }
this.video = { key: db.site, info: { id: db.ids, name: db.name, index: n, site: this.detail.site, videoFlag: this.videoFlag } }
} else {
this.video = { key: this.detail.key, info: { id: this.detail.info.id, name: this.detail.info.name, index: n, site: this.detail.site } }
this.video = { key: this.detail.key, info: { id: this.detail.info.id, name: this.detail.info.name, index: n, site: this.detail.site, videoFlag: this.videoFlag } }
}
this.video.detail = this.info
this.view = 'Play'
this.detail.show = false
} else {
const db = await history.find({ site: this.detail.key, ids: this.detail.info.id })
const db = await history.find({ site: this.detail.key, ids: this.info.id })
if (db) {
db.index = n
db.detail = this.info
@@ -161,15 +258,15 @@ export default {
onlineVideo.playVideoOnline(this.selectedOnlineSite, this.detail.info.name, n)
}
},
async starEvent () {
const db = await star.find({ key: this.detail.key, ids: this.info.id })
async starEvent (info) {
const db = await star.find({ key: this.detail.key, ids: info.id })
const doc = {
key: this.detail.key,
ids: this.info.id,
ids: info.id,
site: this.detail.site,
name: this.info.name,
detail: this.info,
rate: this.info.rate
name: info.name,
detail: info,
rate: info.rate
}
if (db) {
star.update(db.id, doc)
@@ -180,6 +277,10 @@ export default {
})
}
},
detailEvent (info) {
this.detail.info = info
this.getDetailInfo()
},
togglePlayOnlineEvent () {
this.playOnline = !this.playOnline
},
@@ -211,63 +312,70 @@ export default {
}
},
downloadEvent () {
const key = this.detail.key
const id = this.info.id
zy.download(key, id).then(res => {
if (res && res.m3u8List) {
const list = res.m3u8List.split('#')
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
zy.detail(key, id).then(res => {
const list = [...res.m3u8List]
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
})
}
zy.download(this.detail.key, this.info.id, this.videoFlag).then(res => {
clipboard.writeText(res.downloadUrls)
this.$message.success(res.info)
}).catch((err) => {
this.$message.error(err.info)
})
},
shareEvent () {
shareEvent (info, selectedEpisode) {
this.share = {
show: true,
key: this.detail.key,
info: this.detail.info
info: info,
index: selectedEpisode
}
},
doubanLinkEvent () {
const name = this.detail.info.name.trim()
zy.doubanLink(name).then(link => {
const name = this.info.name.trim()
const year = this.info.year
zy.doubanLink(name, year).then(link => {
const open = require('open')
open(link)
})
},
getDoubanRate () {
const name = this.detail.info.name.trim()
zy.doubanRate(name).then(res => {
this.info.rate = res
})
async getDoubanRate () {
const name = this.info.name.trim()
const year = this.info.year
this.info.rate = await zy.doubanRate(name, year)
const recommendations = await zy.doubanRecommendations(name, year)
if (recommendations) {
this.info.recommendations = []
recommendations.forEach(element => {
zy.searchFirstDetail(this.detail.key, element).then(detailRes => {
if (detailRes) {
this.info.recommendations.push(detailRes)
}
})
})
}
},
getDetailInfo () {
async getDetailInfo () {
const id = this.detail.info.ids || this.detail.info.id
zy.detail(this.detail.key, id).then(res => {
if (res) {
this.info = res
this.$set(this.info, 'rate', '')
this.m3u8List = res.m3u8List
this.getDoubanRate()
this.loading = false
const cacheKey = this.detail.key + '@' + id
const db = await history.find({ site: this.detail.key, ids: id })
if (db) {
this.videoFlag = db.videoFlag
this.selectedEpisode = db.index
}
if (!this.DetailCache[cacheKey]) {
this.DetailCache[cacheKey] = await zy.detail(this.detail.key, id)
}
const res = this.DetailCache[cacheKey]
if (res) {
this.info = res
this.$set(this.info, 'rate', this.DetailCache[cacheKey].rate || '')
this.$set(this.info, 'recommendations', this.DetailCache[cacheKey].recommendations || [])
this.videoFlag = this.videoFlag || res.fullList[0].flag
this.videoList = res.fullList[0].list
this.videoFullList = res.fullList
this.loading = false
if (!this.info.rate) {
await this.getDoubanRate()
this.DetailCache[cacheKey] = this.info
}
})
}
}
},
created () {
@@ -389,6 +497,15 @@ export default {
margin: 6px 10px 0px 0px;
padding: 8px 22px;
}
.selected {
display: inline-block;
font-size: 12px;
border: 1px solid;
border-radius: 2px;
cursor: pointer;
margin: 6px 10px 0px 0px;
padding: 8px 22px;
}
}
}
}

View File

@@ -2,7 +2,7 @@
<div class="listpage" id="sites">
<div class="listpage-header" v-show="!enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理分组">></el-switch>
<el-checkbox v-model="setting.excludeR18Films" @change="excludeR18FilmsChangeEvent">屏蔽福利片</el-checkbox>
<el-button @click="openFilterKeywordsDiag" icon="el-icon-key">关键词过滤</el-button>
<el-button @click="addSite" icon="el-icon-document-add">新增</el-button>
<el-button @click="exportSites" icon="el-icon-upload2" title="导出全部,自动添加扩展名">导出</el-button>
<el-button @click="importSites" icon="el-icon-download" title="支持同时导入多个文件">导入</el-button>
@@ -41,7 +41,18 @@
<template slot-scope="scope">
<el-switch
v-model="scope.row.isActive"
@click.native.stop='isActiveChangeEvent(scope.row)'>
@click.native.stop='propChangeEvent(scope.row)'>
</el-switch>
</template>
</el-table-column>
<el-table-column
prop="reverseOrder"
width="120"
label="倒序排列">
<template slot-scope="scope">
<el-switch
v-model="scope.row.reverseOrder"
@click.native.stop='propChangeEvent(scope.row)'>>
</el-switch>
</template>
</el-table-column>
@@ -83,7 +94,7 @@
</div>
<!-- 编辑页面 -->
<div>
<el-dialog :visible.sync="dialogVisible" v-if='dialogVisible' :title="dialogType==='edit'?'编辑源':'新增源'" :append-to-body="true" @close="closeDialog">
<el-dialog :visible.sync="editSiteDialogVisible" v-if='editSiteDialogVisible' :title="dialogType==='edit'?'编辑源':'新增源'" :append-to-body="true" @close="closeDialog">
<el-form :model="siteInfo" ref='siteInfo' label-width="75px" label-position="left" :rules="rules">
<el-form-item label="源站名" prop='name'>
<el-input v-model="siteInfo.name" placeholder="请输入源站名" />
@@ -94,6 +105,9 @@
<el-form-item label="下载接口" prop='download'>
<el-input v-model="siteInfo.download" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入Download接口地址可以空着"/>
</el-form-item>
<el-form-item label="解析接口" prop='jiexiUrl'>
<el-input v-model="siteInfo.jiexiUrl" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入解析接口地址默认源自带解析,若要调用应用默认解析接口请输入默认或default"/>
</el-form-item>
<el-form-item label="分组" prop='group'>
<el-select v-model="siteInfo.group" allow-create filterable default-first-option placeholder="请输入分组">
<el-option v-for="item in siteGroup" :key="item" :label="item" :value="item"></el-option>
@@ -109,16 +123,36 @@
</span>
</el-dialog>
</div>
<!-- 设置过滤关键词页面 -->
<div>
<el-dialog :visible.sync="filterKeywordsDialogVisible" v-if='filterKeywordsDialogVisible' :title="'分类过滤'" :append-to-body="true" @close="closeDialog">
<el-form>
<el-switch v-model="excludeRootClasses" active-text="开启主分类过滤">></el-switch>
<el-form-item>
<el-input v-model="rootClassFilterKeywords" :autosize="{ minRows: 3, maxRows: 6}" type="textarea" placeholder="请输入过滤关键词" />
</el-form-item>
</el-form>
<el-form>
<el-switch v-model="excludeR18Films" active-text="开启福利分类过滤">></el-switch>
<el-form-item>
<el-input v-model="r18ClassFilterKeywords" :autosize="{ minRows: 3, maxRows: 6}" type="textarea" placeholder="请输入过滤关键词" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="saveFilterKeywords">保存</el-button>
</span>
</el-dialog>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import { sites, setting } from '../lib/dexie'
import zy from '../lib/site/tools'
import { remote } from 'electron'
import { sites as defaultSites } from '../lib/dexie/initData'
import fs from 'fs'
import Sortable from 'sortablejs'
const remote = require('@electron/remote')
export default {
name: 'editSites',
@@ -127,15 +161,21 @@ export default {
show: false,
sites: [],
dialogType: 'new',
dialogVisible: false,
editSiteDialogVisible: false,
filterKeywordsDialogVisible: false,
siteInfo: {
key: '',
name: '',
api: '',
download: '',
jiexiUrl: '',
group: '',
isActive: true
},
excludeRootClasses: true,
excludeR18Films: true,
rootClassFilterKeywords: [],
r18ClassFilterKeywords: [],
siteGroup: [],
rules: {
name: [
@@ -169,9 +209,9 @@ export default {
},
getFilters () {
const groups = [...new Set(this.sites.map(site => site.group))]
var filters = []
const filters = []
groups.forEach(g => {
var doc = {
const doc = {
text: g,
value: g
}
@@ -201,12 +241,6 @@ export default {
},
methods: {
...mapMutations(['SET_SETTING']),
excludeR18FilmsChangeEvent () {
setting.find().then(res => {
res.excludeR18Films = this.setting.excludeR18Films
setting.update(res)
})
},
selectionCellClick (selection, row) {
if (this.shiftDown && this.selectionBegin !== '' && selection.includes(row)) {
this.selectionEnd = row.id
@@ -246,6 +280,11 @@ export default {
},
getSites () {
sites.all().then(res => {
res.forEach(element => {
if (element.reverseOrder === null || element.reverseOrder === undefined) {
element.reverseOrder = false
}
})
this.sites = res
})
},
@@ -258,6 +297,29 @@ export default {
}
this.siteGroup = arr
},
openFilterKeywordsDiag () {
this.excludeRootClasses = this.setting.excludeRootClasses
this.excludeR18Films = this.setting.excludeR18Films
this.rootClassFilterKeywords = this.setting.rootClassFilter?.join()
this.r18ClassFilterKeywords = this.setting.r18ClassFilter?.join()
this.filterKeywordsDialogVisible = true
},
saveFilterKeywords () {
// 移除空格,然后按逗号分开
this.setting.rootClassFilter = this.rootClassFilterKeywords?.replace(/\s/g, '').split(',')
this.setting.r18ClassFilter = this.r18ClassFilterKeywords?.replace(/\s/g, '').split(',')
this.setting.classFilter = []
this.setting.excludeRootClasses = this.excludeRootClasses
if (this.excludeRootClasses) {
this.setting.classFilter = this.setting.classFilter.concat(this.setting.rootClassFilter)
}
this.setting.excludeR18Films = this.excludeR18Films
if (this.excludeR18Films) {
this.setting.classFilter = this.setting.classFilter.concat(this.setting.r18ClassFilter)
}
setting.update(this.setting)
this.filterKeywordsDialogVisible = false
},
addSite () {
if (this.checkAllSitesLoading) {
this.$message.info('正在检测, 请勿操作.')
@@ -265,12 +327,13 @@ export default {
}
this.getSitesGroup()
this.dialogType = 'new'
this.dialogVisible = true
this.editSiteDialogVisible = true
this.siteInfo = {
key: '',
name: '',
api: '',
download: '',
jiexiUrl: '',
group: '',
isActive: true
}
@@ -282,12 +345,13 @@ export default {
}
this.getSitesGroup()
this.dialogType = 'edit'
this.dialogVisible = true
this.editSiteDialogVisible = true
this.siteInfo = siteInfo
this.editOldkey = siteInfo.key
},
closeDialog () {
this.dialogVisible = false
this.editSiteDialogVisible = false
this.filterKeywordsDialogVisible = false
this.getSites()
},
removeEvent (e) {
@@ -322,13 +386,14 @@ export default {
if (!this.checkSiteKey()) {
return false
}
var randomstring = require('randomstring')
var doc = {
const randomstring = require('randomstring')
const doc = {
key: this.dialogType === 'edit' ? this.siteInfo.key : this.siteInfo.key ? this.siteInfo.key : randomstring.generate(6),
id: this.dialogType === 'edit' ? this.siteInfo.id : this.sites.length ? this.sites[this.sites.length - 1].id + 1 : 1,
name: this.siteInfo.name,
api: this.siteInfo.api,
download: this.siteInfo.download,
jiexiUrl: this.siteInfo.jiexiUrl,
group: this.siteInfo.group,
isActive: this.siteInfo.isActive
}
@@ -339,10 +404,11 @@ export default {
name: '',
api: '',
download: '',
jiexiUrl: '',
group: ''
}
this.dialogType === 'edit' ? this.$message.success('修改成功!') : this.$message.success('新增源成功!')
this.dialogVisible = false
this.editSiteDialogVisible = false
this.getSites()
})
this.editOldkey = ''
@@ -373,43 +439,75 @@ export default {
}
const options = {
filters: [
{ name: 'JSON file', extensions: ['json'] }
{ name: '支持的文件格式', extensions: ['json', 'txt'] }
],
properties: ['openFile', 'multiSelections']
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const json = JSON.parse(str)
json.forEach(ele => {
if (ele.api && this.sites.filter(x => x.key === ele.key).length === 0 && this.sites.filter(x => x.name === ele.name && x.api === ele.api).length === 0) {
// 不含该key 同时也不含名字和url一样的
if (ele.isActive === undefined) {
ele.isActive = true
if (file.endsWith('json')) {
const str = fs.readFileSync(file)
const json = JSON.parse(str)
json.forEach(ele => {
if (ele.api && this.sites.filter(x => x.key === ele.key).length === 0 && this.sites.filter(x => x.name === ele.name && x.api === ele.api).length === 0) {
// 不含该key 同时也不含名字和url一样的
if (ele.isActive === undefined) {
ele.isActive = true
}
if (ele.group === undefined) {
ele.group = '导入'
}
this.sites.push(ele)
}
if (ele.group === undefined) {
ele.group = '导入'
}
this.sites.push(ele)
})
this.resetId(this.sites)
sites.clear().then(sites.bulkAdd(this.sites))
this.$message.success('导入成功')
this.getSites()
}
if (file.endsWith('txt')) {
try {
const txt = fs.readFileSync(file, 'utf8')
const json = JSON.parse(txt)
json.forEach(ele => {
if (ele.api && this.sites.filter(x => x.key === ele.key).length === 0 && this.sites.filter(x => x.name === ele.name && x.api === ele.api).length === 0) {
// 不含该key 同时也不含名字和url一样的
if (ele.isActive === undefined) {
ele.isActive = true
}
if (ele.group === undefined) {
ele.group = '导入'
}
this.sites.push(ele)
}
})
this.resetId(this.sites)
sites.clear().then(sites.bulkAdd(this.sites))
this.$message.success('导入成功')
this.getSites()
} catch (error) {
this.$message.warning('导入失败')
}
})
this.resetId(this.sites)
sites.clear().then(sites.bulkAdd(this.sites))
this.$message.success('导入成功')
this.getSites()
}
})
}
})
},
resetSitesEvent () {
this.stopFlag = true
if (this.checkAllSitesLoading) {
this.$message.info('部分检测还未完全终止, 请稍等...')
return
let url = this.setting.sitesDataURL
if (!url) {
url = 'https://raw.iqiq.io/Hunlongyu/ZY-Player-Resources/main/Sites/20220713.json'
}
sites.clear().then(sites.bulkAdd(defaultSites).then(this.getSites()))
this.$message.success('重置源成功')
zy.getDefaultSites(url).then(res => {
if (res.length > 0) {
sites.clear().then(sites.bulkAdd(res))
this.$message.success('重置源成功')
this.getSites()
}
}).catch(error => {
this.$message.error('导入云端源站失败. ' + error)
})
},
moveToTopEvent (i) {
if (this.checkAllSitesLoading) {
@@ -424,12 +522,13 @@ export default {
this.sites = this.$refs.editSitesTable.tableData
}
},
isActiveChangeEvent (row) {
propChangeEvent (row) {
sites.remove(row.id)
sites.add(row)
this.getSites()
},
resetId (inArray) {
var id = 1
let id = 1
inArray.forEach(ele => {
ele.id = id
id += 1
@@ -439,7 +538,7 @@ export default {
// 因为el-table的数据是单向绑定,我们先同步el-table里的数据和其绑定的数据
this.syncTableData()
sites.clear().then(res => {
var id = 1
let id = 1
this.sites.forEach(ele => {
ele.id = id
id += 1
@@ -460,7 +559,7 @@ export default {
return false
}
const tbody = document.getElementById('sites-table').querySelector('.el-table__body-wrapper tbody')
var _this = this
const _this = this
Sortable.create(tbody, {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.sites.splice(oldIndex, 1)[0]

View File

@@ -78,7 +78,7 @@
<el-input-number size="small" v-model="selectedYears.end" :min=0 :max="new Date().getFullYear()" controls-position="right" step-strictly @change="refreshFilteredList"></el-input-number>
</span>
</div>
<el-divider content-position="center">
<el-divider class="listpage-header-divider" content-position="right">
<el-button type="text" size="mini" @click="toggleViewMode">视图切换</el-button>
<el-button type="text" size="mini" @click='() => { showToolbar = !showToolbar; if (!showToolbar) this.refreshFilteredList() }' title="收起工具栏会重置筛选排序">{{ showToolbar ? '隐藏工具栏' : '显示工具栏' }}</el-button>
<el-button type="text" size="mini" @click="backTop">回到顶部</el-button>
@@ -281,7 +281,7 @@
animationEffect="fadeIn"
backgroundColor="rgba(0, 0, 0, 0)">
<template slot="item" slot-scope="props">
<div class="card" v-show="!setting.excludeR18Films || !containsR18Keywords(props.data.type)">
<div class="card" v-show="!setting.excludeR18Films || !containsClassFilterKeyword(props.data.type)">
<div class="img">
<div class="site">
<span>{{props.data.site.name}}</span>
@@ -322,7 +322,6 @@ export default {
data () {
return {
showFind: false,
showToolbar: false,
showTableLastColumn: false,
sites: [],
site: {},
@@ -332,6 +331,7 @@ export default {
selectedSiteName: '',
selectedClassName: '',
selectedSearchClassNames: [],
totalpagecount: 0,
pagecount: 0,
recordcount: 0,
list: [],
@@ -344,18 +344,19 @@ export default {
filteredSearchContents: [],
currentColumn: '',
searchGroup: '',
searchGroups: [],
// 福利片关键词
r18KeyWords: ['伦理', '论理', '倫理', '福利', '激情', '理论', '写真', '情色', '美女', '街拍', '赤足', '性感', '里番'],
searchGroups: ['站内', '组内', '全站'],
classFilterKeywords: [],
filteredList: [],
areas: [],
searchRunning: false,
siteSearchCount: 0,
infiniteHandlerCount: 0,
// Toolbar
showToolbar: false,
selectedAreas: [],
sortKeyword: '',
sortKeywords: ['按片名', '按上映年份', '按更新时间'],
selectedYears: { start: 0, end: new Date().getFullYear() },
searchRunning: false,
siteSearchCount: 0,
infiniteHandlerCount: 0
selectedYears: { start: 0, end: new Date().getFullYear() }
}
},
components: {
@@ -403,13 +404,22 @@ export default {
this.SET_SETTING(val)
}
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
},
filterSettings () {
return this.$store.getters.getSetting.excludeR18Films // 需要监听的数据
return this.$store.getters.getSetting.classFilter // 需要监听的数据
},
searchSites () {
if (this.searchGroup === '站内') return [this.site]
if (this.searchGroup === '组内') return this.sites.filter(site => site.group === this.site.group)
if (this.searchGroup === '全站') return this.sites
return this.sites.filter(site => site.group === this.searchGroup)
return this.sites.filter(site => site.isActive)
}
},
filters: {
@@ -434,7 +444,7 @@ export default {
}
},
filterSettings () {
this.siteClick(this.site.name)
this.refreshClass()
},
list: {
handler (list) {
@@ -446,8 +456,12 @@ export default {
siteSearchCount () {
if (this.siteSearchCount === this.searchSites.length) this.searchRunning = false
},
site () {
this.siteClick(this.site.name)
},
searchContents: {
handler (list) {
list = list.filter(res => !this.setting.excludeR18Films || !this.containsClassFilterKeyword(res.type))
this.areas = [...new Set(list.map(ele => ele.area))].filter(x => x)
this.searchClassList = [...new Set(list.map(ele => ele.type))].filter(x => x)
this.refreshFilteredList()
@@ -468,7 +482,7 @@ export default {
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING']),
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING', 'SET_DetailCache']),
backTop () {
const viewMode = this.showFind ? this.setting.searchViewMode : this.setting.view
if (viewMode === 'picture') {
@@ -489,14 +503,14 @@ export default {
let filteredData = this.showFind ? this.searchContents : this.list
if (this.showFind) filteredData = filteredData.filter(x => (this.selectedSearchClassNames.length === 0) || this.selectedSearchClassNames.includes(x.type))
filteredData = filteredData.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.area))
filteredData = filteredData.filter(res => !this.setting.excludeR18Films || !this.containsR18Keywords(res.type))
filteredData = filteredData.filter(res => !this.setting.excludeR18Films || !this.containsClassFilterKeyword(res.type))
filteredData = filteredData.filter(res => res.year >= this.selectedYears.start)
filteredData = filteredData.filter(res => res.year <= this.selectedYears.end)
if (!this.showFind) this.selectedClassName = this.type.name + ' ' + filteredData.length + '/' + this.recordcount
switch (this.sortKeyword) {
case '按上映年份':
filteredData.sort(function (a, b) {
return a.year - b.year
return b.year - a.year
})
break
case '按片名':
@@ -510,8 +524,14 @@ export default {
})
break
default:
filteredData.sort(function (a, b) {
return new Date(b.last) - new Date(a.last)
})
break
}
// Get unique film data
filteredData = Array.from(new Set(filteredData))
if (this.showFind) {
this.filteredSearchContents = filteredData
} else {
@@ -535,7 +555,7 @@ export default {
return a.localeCompare(b, 'zh')
},
dateFormat (row, column) {
var date = row[column.property]
const date = row[column.property]
if (date === undefined) {
return ''
}
@@ -581,6 +601,16 @@ export default {
})
}
},
refreshClass () {
this.getClass().then(res => {
this.classList = res
// cache classList data
FILM_DATA_CACHE[this.site.key] = {
classList: this.classList
}
this.classClick(this.type.name)
})
},
classClick (className) {
this.list = []
this.type = this.classList.find(x => x.name === className)
@@ -591,12 +621,14 @@ export default {
if (this.type.name.endsWith('剧')) this.selectedAreas = []
const cacheKey = this.site.key + '@' + this.type.tid
if (FILM_DATA_CACHE[cacheKey]) {
this.totalpagecount = FILM_DATA_CACHE[cacheKey].totalpagecount
this.pagecount = FILM_DATA_CACHE[cacheKey].pagecount
this.recordcount = FILM_DATA_CACHE[cacheKey].recordcount
this.list = FILM_DATA_CACHE[cacheKey].list
this.areas = FILM_DATA_CACHE[cacheKey].areas
} else {
zy.page(this.site.key, this.type.tid).then(res => {
this.totalpagecount = res.pagecount
this.pagecount = res.pagecount
this.recordcount = res.recordcount
this.infiniteId += 1
@@ -606,20 +638,11 @@ export default {
getClass () {
return new Promise((resolve, reject) => {
const key = this.site.key
// 屏蔽主分类
const classToHide = ['电影', '电影片', '电视剧', '连续剧', '综艺', '动漫']
zy.class(key).then(res => {
var allClass = [{ name: '最新', tid: 0 }]
const allClass = [{ name: '最新', tid: 0 }]
res.class.forEach(element => {
if (!this.setting.excludeRootClasses || !classToHide.includes(element.name)) {
if (this.setting.excludeR18Films) {
const containKeyWord = this.containsR18Keywords(element.name)
if (!containKeyWord) {
allClass.push(element)
}
} else {
allClass.push(element)
}
if (!this.containsClassFilterKeyword(element.name)) {
allClass.push(element)
}
})
resolve(allClass)
@@ -628,19 +651,30 @@ export default {
})
})
},
containsR18Keywords (name) {
var containKeyWord = false
if (!name) {
return containKeyWord
containsClassFilterKeyword (name) {
let ret = false
// 主分类过滤, 检测关键词是否包含分类名
if (this.setting.excludeRootClasses) {
ret = this.setting.rootClassFilter?.some(v => v.includes(name))
}
return this.r18KeyWords.some(v => name.includes(v))
// 福利过滤,检测分类名是否包含关键词
if (this.setting.excludeR18Films && !ret) {
ret = this.setting.r18ClassFilter?.some(v => name?.includes(v))
}
return ret
},
toFlipPagecount () {
return this.site.reverseOrder
},
infiniteHandler ($state) {
const key = this.site.key
const typeTid = this.type.tid
const page = this.pagecount
let page = this.pagecount
if (this.toFlipPagecount()) {
page = this.totalpagecount - this.pagecount + 1
}
this.statusText = ' '
if (key === undefined || page < 1 || typeTid === undefined) {
if (key === undefined || page < 1 || page > this.totalpagecount || typeTid === undefined) {
$state.complete()
this.statusText = '暂无数据'
return false
@@ -654,15 +688,20 @@ export default {
if (res) {
this.pagecount -= 1
const type = Object.prototype.toString.call(res)
if (type === '[object Undefined]') {
$state.complete()
}
if (type === '[object Array]') {
// zy.list 返回的是按时间从旧到新排列, 我门需要翻转为从新到旧
this.list.push(...res.reverse())
}
if (type === '[object Object]') {
this.list.push(res)
// 过滤掉无链接的项
res = res.filter(e => e.dl.dd && (e.dl.dd._t || (Object.prototype.toString.call(e.dl.dd) === '[object Array]' && e.dl.dd.some(i => i._t))))
if (!this.toFlipPagecount()) {
// zy.list 返回的是按时间从旧到新排列, 我门需要翻转为从新到旧
this.list.push(...res.reverse())
} else {
// 如果是需要解析的视频网站zy.list已经是按从新到旧排列
this.list.push(...res)
}
} else if (type === '[object Object]') {
if (res.dl.dd && (res.dl.dd._t || (Object.prototype.toString.call(res.dl.dd) === '[object Array]' && res.dl.dd.some(e => e._t)))) {
this.list.push(res)
}
}
$state.loaded()
// 更新缓存数据
@@ -691,9 +730,6 @@ export default {
} else {
this.video = { key: site.key, info: { id: e.id, name: e.name, index: 0, site: site } }
}
zy.detail(site.key, e.id).then(detailRes => {
this.video.detail = detailRes
})
this.view = 'Play'
},
async starEvent (site, e) {
@@ -701,17 +737,19 @@ export default {
if (db) {
this.$message.info('已存在')
} else {
zy.detail(site.key, e.id).then(detailRes => {
const docs = {
key: site.key,
ids: e.id,
site: site,
name: e.name,
detail: detailRes
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
})
const cacheKey = site.key + '@' + e.id
if (!this.DetailCache[cacheKey]) {
this.DetailCache[cacheKey] = await zy.detail(site.key, e.id)
}
const docs = {
key: site.key,
ids: e.id,
site: site,
name: e.name,
detail: this.DetailCache[cacheKey]
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
})
}
},
@@ -722,36 +760,20 @@ export default {
info: e
}
},
downloadEvent (site, row) {
const key = site.key
const id = row.id
zy.download(key, id).then(res => {
if (res && res.m3u8List) {
const list = res.m3u8List.split('#')
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
zy.detail(key, id).then(res => {
const list = [...res.m3u8List]
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
})
}
async downloadEvent (site, row) {
const db = await history.find({ site: site.key, ids: row.id })
let videoFlag
if (db) videoFlag = db.videoFlag
zy.download(site.key, row.id, videoFlag).then(res => {
clipboard.writeText(res.downloadUrls)
this.$message.success(res.info)
}).catch((err) => {
this.$message.error(err.info)
})
},
querySearch (queryString, cb) {
var searchList = this.searchList.slice(0, -1)
var results = queryString ? searchList.filter(this.createFilter(queryString)) : this.searchList
const searchList = this.searchList.slice(0, -1)
const results = queryString ? searchList.filter(this.createFilter(queryString)) : this.searchList
// 调用 callback 返回建议列表的数据
cb(results)
},
@@ -809,20 +831,24 @@ export default {
zy.detail(site.key, element.id).then(detailRes => {
if (id !== this.searchID || !this.searchRunning) return
detailRes.site = site
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
if (this.isValidSearchResult(detailRes)) {
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
}
}).finally(() => { count++; if (count === res.length) { this.siteSearchCount++; this.statusText = '暂无数据' } })
})
} else if (type === '[object Object]') {
zy.detail(site.key, res.id).then(detailRes => {
if (id !== this.searchID || !this.searchRunning) return
detailRes.site = site
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
if (this.isValidSearchResult(detailRes)) {
this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id
})
}
}).finally(() => { this.siteSearchCount++; this.statusText = '暂无数据' })
} else if (res === undefined) {
this.siteSearchCount++
@@ -832,6 +858,10 @@ export default {
}).catch(() => { this.siteSearchCount++; if (this.searchGroup === '站内') this.$message.error('本次查询状态异常,未获取到数据!') })
})
},
isValidSearchResult (detailRes) {
return detailRes.dl.dd && (detailRes.dl.dd._t || (Object.prototype.toString.call(detailRes.dl.dd) === '[object Array]' &&
detailRes.dl.dd.some(i => i._t)))
},
searchAndRecord () {
this.addSearchRecord()
this.searchEvent()
@@ -842,12 +872,34 @@ export default {
this.showFind = false
}
},
async getDefaultSites () {
const s = await setting.find()
zy.getDefaultSites(s.sitesDataURL).then(res => {
if (res && typeof res === 'string') {
const json = JSON.parse(res)
sites.clear().then(sites.bulkAdd(json))
}
if (res && typeof res === 'object') {
sites.clear().then(sites.bulkAdd(res))
}
sites.all().then(res => {
if (res) {
this.sites = res.filter(item => item.isActive)
if (this.site === undefined || !this.sites.some(x => x.key === this.site.key)) {
this.site = this.sites[0]
this.selectedSiteName = this.sites[0].name
}
}
})
}).catch(error => {
this.$message.error('获取云端源站失败. ' + error)
})
},
getAllSites () {
sites.all().then(res => {
if (res.length <= 0) {
this.site = {}
this.type = {}
this.list = []
this.$message.warning('检测到视频源未能正常加载, 即将重置源.')
this.getDefaultSites()
} else {
this.sites = res.filter(item => item.isActive)
if (this.site === undefined || !this.sites.some(x => x.key === this.site.key)) {
@@ -855,10 +907,6 @@ export default {
this.selectedSiteName = this.sites[0].name
}
}
this.searchGroups = [...new Set(this.sites.map(site => site.group))]
if (this.searchGroups.length === 1) this.searchGroups = []
this.searchGroups.unshift('站内')
this.searchGroups.push('全站')
this.searchGroup = this.setting.searchGroup
if (this.searchGroup === undefined) setting.find().then(res => { this.searchGroup = res.searchGroup })
})

View File

@@ -15,7 +15,7 @@
</div>
</template>
<script>
const { remote } = require('electron')
const remote = require('@electron/remote')
export default {
name: 'frame',
computed: {

View File

@@ -1,15 +1,55 @@
<template>
<div class="listpage" id="history">
<div class="listpage-header" id="history-header">
<el-switch v-model="setting.historyViewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateViewMode"></el-switch>
<el-button @click.stop="exportHistory" icon="el-icon-upload2" title="导出全部,自动添加扩展名">导出</el-button>
<el-button @click.stop="importHistory" icon="el-icon-download" title="支持同时导入多个文件">导入</el-button>
<el-button @click.stop="removeSelectedItems" icon="el-icon-delete-solid">{{ multipleSelection.length === 0 ? "清空" : "删除所选" }}</el-button>
<b-button-group>
<el-switch v-model="onlyShowItemsHasUpdate" active-text="有更新" inactive-text="全部" @change="refreshFilteredList"></el-switch>
<el-button @click.stop="updateAllEvent" icon="el-icon-refresh">检查更新</el-button>
</b-button-group>
</div>
<div class="toolbar" v-show="showToolbar">
<el-select v-model="selectedAreas" size="small" multiple placeholder="地区" popper-class="popper" :popper-append-to-body="false" @remove-tag="refreshFilteredList" @change="refreshFilteredList">
<el-option
v-for="item in areas"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="selectedTypes" size="small" multiple placeholder="类型" popper-class="popper" :popper-append-to-body="false" @remove-tag="refreshFilteredList" @change="refreshFilteredList">
<el-option
v-for="item in types"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="sortKeyword" size="small" placeholder="排序" popper-class="popper" :popper-append-to-body="false" @change="refreshFilteredList">
<el-option
v-for="item in sortKeywords"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<span>
上映区间
<el-input-number size="small" v-model="selectedYears.start" :min=0 :max="new Date().getFullYear()" controls-position="right" step-strictly @change="refreshFilteredList"></el-input-number>
<el-input-number size="small" v-model="selectedYears.end" :min=0 :max="new Date().getFullYear()" controls-position="right" step-strictly @change="refreshFilteredList"></el-input-number>
</span>
</div>
<el-divider class="listpage-header-divider" content-position="right">
<el-button type="text" size="mini" @click="toggleViewMode">视图切换</el-button>
<el-button type="text" size="mini" @click='() => { showToolbar = !showToolbar; if (!showToolbar) this.refreshFilteredList() }' title="收起工具栏会重置筛选排序">{{ showToolbar ? '隐藏工具栏' : '显示工具栏' }}</el-button>
<el-button type="text" size="mini" @click="backTop">回到顶部</el-button>
</el-divider>
<div class="listpage-body" id="history-body">
<div class="show-table" id="history-table" v-if="setting.historyViewMode === 'table'">
<el-table size="mini" fit height="100%"
:data="history"
:data="filteredList"
row-key="id"
ref="historyTable"
@select="selectionCellClick"
@@ -35,16 +75,17 @@
width="180"
label="观看至">
<template slot-scope="scope">
<span v-if="scope.row.detail && scope.row.detail.m3u8List && scope.row.detail.m3u8List.length > 1">
{{ scope.row.index + 1 }}({{scope.row.detail.m3u8List.length}})
<span v-if="scope.row.detail && scope.row.detail.fullList[0].list && scope.row.detail.fullList[0].list.length > 1">
{{ scope.row.index + 1 }}({{scope.row.detail.fullList[0].list.length}})
</span>
</template>
</el-table-column>
<el-table-column v-if="history.some(e => e.time)"
width="150"
<el-table-column v-if="list.some(e => e.time)"
width="200"
label="时间进度">
<template slot-scope="scope">
<span v-if="scope.row.time && scope.row.duration">{{fmtMSS(scope.row.time.toFixed(0))}}/{{fmtMSS(scope.row.duration.toFixed(0))}}</span>
<span v-if="scope.row.time && scope.row.duration">{{fmtMSS(scope.row.time.toFixed(0))}}/{{fmtMSS(scope.row.duration.toFixed(0))}} ({{progress(scope.row)}}%)</span>
<span v-if="scope.row.onlinePlay">在线解析</span>
</template>
</el-table-column>
<el-table-column
@@ -62,7 +103,7 @@
</el-table>
</div>
<div class="show-picture" id="star-picture" v-if="setting.historyViewMode === 'picture'">
<Waterfall ref="historyWaterfall" :list="history" :gutter="20" :width="240"
<Waterfall ref="historyWaterfall" :list="filteredList" :gutter="20" :width="240"
:breakpoints="{
1200: { //当屏幕宽度小于等于1200
rowPerView: 4,
@@ -79,6 +120,9 @@
<template slot="item" slot-scope="props">
<div class="card">
<div class="img">
<div class="update" v-if="props.data.hasUpdate">
<span>有更新</span>
</div>
<img v-if="props.data.detail && props.data.detail.pic" style="width: 100%" :src="props.data.detail.pic" alt="" @load="$refs.historyWaterfall.refresh()" @click="detailEvent(props.data)">
<div class="operate">
<div class="operate-wrap">
@@ -92,10 +136,11 @@
<div class="name" @click="detailEvent(props.data)">{{props.data.name}}</div>
<div class="info">
<span v-if="props.data.time && props.data.duration">
{{fmtMSS(props.data.time.toFixed(0))}}/{{fmtMSS(props.data.duration.toFixed(0))}}
{{fmtMSS(props.data.time.toFixed(0))}}/{{fmtMSS(props.data.duration.toFixed(0))}} ({{progress(props.data)}}%)
</span>
<span v-if="props.data.detail && props.data.detail.m3u8List !== undefined && props.data.detail.m3u8List.length > 1">
{{ props.data.index + 1 }}({{props.data.detail.m3u8List.length}})
<span v-if="props.data.onlinePlay">在线解析</span>
<span v-if="props.data.detail && props.data.detail.fullList[0].list !== undefined && props.data.detail.fullList[0].list.length > 1">
{{ props.data.index + 1 }}({{props.data.detail.fullList[0].list.length}})
</span>
</div>
</div>
@@ -109,22 +154,34 @@
import { mapMutations } from 'vuex'
import { history, sites, setting } from '../lib/dexie'
import zy from '../lib/site/tools'
import Sortable from 'sortablejs'
import { remote } from 'electron'
import fs from 'fs'
import Waterfall from 'vue-waterfall-plugin'
const remote = require('@electron/remote')
const { clipboard } = require('electron')
export default {
name: 'history',
data () {
return {
history: [],
list: [],
sites: [],
shiftDown: false,
selectionBegin: '',
selectionEnd: '',
multipleSelection: []
multipleSelection: [],
areas: [],
types: [],
filteredList: [],
// Update
numNoUpdate: 0,
// Toolbar
showToolbar: false,
selectedAreas: [],
selectedTypes: [],
sortKeyword: '',
sortKeywords: ['按片名', '按上映年份', '按更新时间', '按完成度'],
selectedYears: { start: 0, end: new Date().getFullYear() },
onlyShowItemsHasUpdate: false
}
},
components: {
@@ -170,6 +227,14 @@ export default {
set (val) {
this.SET_SETTING(val)
}
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
}
},
watch: {
@@ -179,19 +244,134 @@ export default {
this.getAllsites()
if (this.setting.historyViewMode === 'table') this.showShiftPrompt()
}
},
list: {
handler (list) {
this.areas = [...new Set(list.map(ele => ele.detail.area))].filter(x => x)
this.types = [...new Set(list.map(ele => ele.detail.type))].filter(x => x)
this.refreshFilteredList()
},
deep: true
},
numNoUpdate () {
// 如果所有历史都没有更新的话
if (this.numNoUpdate === this.list.length) {
this.numNoUpdate = 0
this.$message.warning('未查询到任何更新')
}
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING']),
updateAllEvent () {
this.numNoUpdate = 0
this.list.forEach(e => {
this.updateEvent(e)
})
},
async updateEvent (e) {
try {
if (!this.DetailCache[e.site + '@' + e.ids]) {
this.DetailCache[e.site + '@' + e.ids] = await zy.detail(e.site, e.ids)
}
const newDetail = this.DetailCache[e.site + '@' + e.ids]
history.get(e.id).then(res => {
if (!e.hasUpdate && e.detail.last !== newDetail.last) {
res.hasUpdate = true
res.detail = newDetail
const msg = `检查到"${e.name}"有更新。`
this.$message.success(msg)
} else {
this.numNoUpdate += 1
}
history.update(e.id, res)
this.getAllhistory()
})
} catch (err) {
const msg = `更新"${e.name}"失败, 请重试。`
this.$message.warning(msg, err)
}
},
toggleViewMode () {
this.setting.historyViewMode = this.setting.historyViewMode === 'picture' ? 'table' : 'picture'
if (this.setting.historyViewMode === 'table') {
this.showShiftPrompt()
} else {
setTimeout(() => { if (this.$refs.historyWaterfall) this.$refs.historyWaterfall.refresh() }, 700)
}
setting.find().then(res => {
res.historyViewMode = this.setting.historyViewMode
setting.update(res)
})
},
backTop () {
if (this.setting.starViewMode === 'picture') {
document.getElementById('history-body').scrollTop = 0
} else {
this.$refs.historyTable.bodyWrapper.scrollTop = 0
}
},
refreshFilteredList () {
if (!this.showToolbar) {
this.sortKeyword = ''
this.selectedAreas = []
this.selectedSearchClassNames = []
this.selectedYears.start = 0
this.selectedYears.end = new Date().getFullYear()
this.filteredList = this.list
} else {
let filteredData = this.list
filteredData = filteredData.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.detail.area))
filteredData = filteredData.filter(x => (this.selectedTypes.length === 0) || this.selectedTypes.includes(x.detail.type))
filteredData = filteredData.filter(res => res.detail.year >= this.selectedYears.start)
filteredData = filteredData.filter(res => res.detail.year <= this.selectedYears.end)
switch (this.sortKeyword) {
case '按上映年份':
filteredData.sort(function (a, b) {
return a.detail.year - b.detail.year
})
break
case '按片名':
filteredData.sort(function (a, b) {
return a.detail.name.localeCompare(b.detail.name, 'zh')
})
break
case '按更新时间':
filteredData.sort(function (a, b) {
return new Date(b.detail.last) - new Date(a.detail.last)
})
break
case '按完成度':
filteredData.sort(this.sortByProgress)
break
default:
break
}
this.filteredList = filteredData
}
if (this.onlyShowItemsHasUpdate) {
this.filteredList = this.filteredList.filter(x => x.hasUpdate)
}
},
progress (e) {
return e.duration > 0 ? ((e.time / e.duration) * 100).toFixed(0) : 0
},
sortByProgress (a, b) {
if (this.progress(a) < this.progress(b)) {
return -1
} else {
return 1
}
},
fmtMSS (s) {
return (s - (s %= 60)) / 60 + (s > 9 ? ':' : ':0') + s
},
selectionCellClick (selection, row) { // 历史id与顺序刚好相反大的反而在前面
if (this.shiftDown && this.selectionBegin !== '' && selection.includes(row)) {
this.selectionEnd = row.id
const start = this.history.findIndex(e => e.id === Math.max(this.selectionBegin, this.selectionEnd))
const end = this.history.findIndex(e => e.id === Math.min(this.selectionBegin, this.selectionEnd))
const selections = this.history.slice(start, end + 1)
const start = this.list.findIndex(e => e.id === Math.max(this.selectionBegin, this.selectionEnd))
const end = this.list.findIndex(e => e.id === Math.min(this.selectionBegin, this.selectionEnd))
const selections = this.list.slice(start, end + 1)
this.$nextTick(() => {
selections.forEach(e => this.$refs.historyTable.toggleRowSelection(e, true))
})
@@ -208,8 +388,9 @@ export default {
this.multipleSelection = rows
},
removeSelectedItems () {
if (!this.multipleSelection.length) this.multipleSelection = this.history
if (!this.multipleSelection.length) this.multipleSelection = this.list
this.multipleSelection.forEach(e => history.remove(e.id))
this.multipleSelection = []
this.getAllhistory()
this.updateDatabase()
},
@@ -222,6 +403,9 @@ export default {
name: e.name
}
}
if (e.hasUpdate) {
this.clearHasUpdateFlag(e)
}
},
async playEvent (e) {
const db = await history.find({ site: e.site, ids: e.ids })
@@ -230,48 +414,37 @@ export default {
} else {
this.video = { key: e.site, info: { id: e.ids, name: e.name, index: 0 } }
}
zy.detail(e.site, e.ids).then(detailRes => {
this.video.detail = detailRes
})
if (e.hasUpdate) {
this.clearHasUpdateFlag(e)
}
this.view = 'Play'
},
async clearHasUpdateFlag (e) {
const db = await history.find({ id: e.id })
if (db) {
db.hasUpdate = false
history.update(e.id, db)
this.getAllhistory()
}
},
shareEvent (e) {
this.share = {
show: true,
key: e.site,
info: e
info: e.detail
}
},
downloadEvent (e) {
const key = e.site
const id = e.ids
zy.download(key, id).then(res => {
if (res && res.m3u8List) {
const list = res.m3u8List.split('#')
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
zy.detail(key, id).then(res => {
const list = [...res.m3u8List]
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
})
}
zy.download(e.site, e.ids, e.videoFlag).then(res => {
clipboard.writeText(res.downloadUrls)
this.$message.success(res.info)
}).catch((err) => {
this.$message.error(err.info)
})
},
exportHistory () {
this.getAllhistory()
const arr = [...this.history]
const arr = [...this.list]
const str = JSON.stringify(arr, null, 2)
const options = {
filters: [
@@ -298,8 +471,14 @@ export default {
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const str = fs.readFileSync(file)
const json = JSON.parse(str)
json.forEach(record => {
if (record.detail && record.detail.m3u8List) {
record.detail.fullList = [].concat({ flag: 'm3u8', list: record.detail.m3u8List })
delete record.detail.m3u8List
}
})
history.bulkAdd(json).then(res => {
this.$message.success('导入成功')
this.getAllhistory()
@@ -310,7 +489,7 @@ export default {
},
getAllhistory () {
history.all().then(res => {
this.history = res.reverse()
this.list = res.reverse()
})
},
getAllsites () {
@@ -319,7 +498,7 @@ export default {
})
},
getSiteName (key) {
var site = this.sites.find(e => e.key === key)
const site = this.sites.find(e => e.key === key)
if (site) {
return site.name
}
@@ -333,37 +512,14 @@ export default {
},
updateDatabase () {
history.clear().then(res => {
var id = length
this.history.forEach(ele => {
let id = length
this.list.forEach(ele => {
ele.id = id
id -= 1
history.add(ele)
})
})
},
rowDrop () {
const tbody = document.getElementById('history-table').querySelector('.el-table__body-wrapper tbody')
const _this = this
Sortable.create(tbody, {
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.history.splice(oldIndex, 1)[0]
_this.history.splice(newIndex, 0, currRow)
_this.updateDatabase()
}
})
},
updateViewMode () {
if (this.setting.historyViewMode === 'table') {
setTimeout(() => { this.rowDrop() }, 100)
this.showShiftPrompt()
} else {
setTimeout(() => { if (this.$refs.historyWaterfall) this.$refs.historyWaterfall.refresh() }, 700)
}
setting.find().then(res => {
res.historyViewMode = this.setting.historyViewMode
setting.update(res)
})
},
showShiftPrompt () {
if (this.setting.shiftTooltipLimitTimes === undefined) this.setting.shiftTooltipLimitTimes = 5
if (this.setting.shiftTooltipLimitTimes) {
@@ -377,7 +533,6 @@ export default {
}
},
mounted () {
if (this.setting.historyViewMode === 'table') setTimeout(() => { this.rowDrop() }, 100)
addEventListener('keydown', code => { if (code.keyCode === 16) this.shiftDown = true })
addEventListener('keyup', code => { if (code.keyCode === 16) this.shiftDown = false })
addEventListener('resize', () => {

View File

@@ -110,9 +110,10 @@ import { mapMutations } from 'vuex'
import { iptv, channelList, setting } from '../lib/dexie'
import { iptv as defaultChannels } from '../lib/dexie/initData'
import zy from '../lib/site/tools'
import { remote } from 'electron'
import fs from 'fs'
import Sortable from 'sortablejs'
import axios from 'axios'
const remote = require('@electron/remote')
export default {
name: 'iptv',
data () {
@@ -168,9 +169,9 @@ export default {
},
getFilters () {
const groups = [...new Set(this.channelList.map(iptv => iptv.group))]
var filters = []
const filters = []
groups.forEach(g => {
var doc = {
const doc = {
text: g,
value: g
}
@@ -257,7 +258,7 @@ export default {
},
mergeChannel () {
if (this.inputContent && this.multipleSelection.length) {
var channels = []
let channels = []
const id = this.multipleSelection[0].id
this.multipleSelection.forEach(ele => {
channels = channels.concat(ele.channels)
@@ -274,8 +275,13 @@ export default {
if (e.url) {
this.video = { iptv: e }
} else {
const prefer = e.prefer ? e.channels.find(c => c.id === e.prefer) : e.channels.filter(c => c.isActive)[0]
if (!prefer) return
let prefer
if (e.prefer) prefer = e.channels.find(c => c.id === e.prefer)
if (!prefer) prefer = e.channels.filter(c => c.isActive)[0]
if (!prefer) {
this.$message.error('当前频道所有源已全部停用,不可播放!')
return
}
this.video = { iptv: prefer }
}
this.view = 'Play'
@@ -298,6 +304,7 @@ export default {
ele.channels.splice(ele.channels.findIndex(e => e.id === row.id), 1)
channelList.remove(row.channelID)
if (ele.channels.length) {
if (ele.prefer === row.id) delete ele.prefer
if (ele.channels.length === 1) ele.hasChildren = false
channelList.add(ele)
this.$set(this.$refs.iptvTable.store.states.lazyTreeNodeMap, ele.id, ele.channels)
@@ -320,7 +327,7 @@ export default {
remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) {
if (result.filePath.endsWith('m3u')) {
var writer = require('m3u').extendedWriter()
const writer = require('m3u').extendedWriter()
this.iptvList.forEach(e => {
writer.file(e.url, -1, e.name)
})
@@ -343,10 +350,55 @@ export default {
this.$message.info('正在检测, 请勿操作.')
return false
}
this.$msgbox.prompt('请输入网址', '提示', {
distinguishCancelAndClose: true,
inputValue: 'http://y.qibaobaike.com/nzy.txt',
confirmButtonText: '确定',
cancelButtonText: '本地文件'
}).then(({ value }) => {
this.importOnlineChannels(value)
}).catch(action => {
if (action === 'cancel') {
this.importLocalChannels()
}
})
},
async importOnlineChannels (url) {
try {
const docs = []
let id = this.channelList.length ? this.channelList.slice(-1)[0].id + 1 : 1
const res = await axios.get(url)
const result = res.data.split('\n')
const supportFormats = /\.(m3u8|flv)$/
for (const i of result) {
if (i.includes('http') && supportFormats.test(i)) {
const j = i.split(',')
const doc = {
id: id,
name: j[0],
url: j[1],
isActive: true
}
id += 1
docs.push(doc)
}
}
// 获取url不重复的列表
const uniqueList = [...new Map(docs.map(item => [item.url, item])).values()]
iptv.clear().then(res => {
iptv.bulkAdd(uniqueList).then(e => { // 支持导入同名频道,群里反馈
this.updateChannelList()
})
})
this.$message.success('导入成功')
} catch (error) {
this.$message.warning('导入失败')
}
},
importLocalChannels () {
const options = {
filters: [
{ name: 'm3u file', extensions: ['m3u', 'm3u8'] },
{ name: 'JSON file', extensions: ['json'] }
{ name: '支持的文件格式', extensions: ['m3u', 'm3u8', 'json', 'txt'] }
],
properties: ['openFile', 'multiSelections']
}
@@ -360,11 +412,12 @@ export default {
const parser = require('iptv-playlist-parser')
const playlist = fs.readFileSync(file, { encoding: 'utf-8' })
const result = parser.parse(playlist)
const supportFormats = /\.(m3u8|flv)$/
result.items.forEach(ele => {
const urls = ele.url.split('#').filter(e => e.startsWith('http')) // 网址带#时自动分割
urls.forEach(url => {
if (ele.name && url && new URL.URL(url).pathname.endsWith('.m3u8')) { // 网址可能带参数
var doc = {
if (ele.name && url && (supportFormats.test(url) || supportFormats.test(new URL.URL(url).pathname))) { // 网址可能带参数
const doc = {
id: id,
name: ele.name,
url: url,
@@ -382,8 +435,9 @@ export default {
this.updateChannelList()
})
})
} else {
// Import Json file
this.$message.success('导入成功')
}
if (file.endsWith('json')) {
const importedList = JSON.parse(fs.readFileSync(file))
importedList.forEach(ele => {
const commonEle = this.channelList.find(e => e.name === ele.name)
@@ -397,9 +451,41 @@ export default {
}
})
this.updateDatabase()
this.$message.success('导入成功')
}
if (file.endsWith('txt')) {
try {
const docs = []
let id = this.channelList.length ? this.channelList.slice(-1)[0].id + 1 : 1
const playlist = fs.readFileSync(file, 'utf8')
const result = playlist.split('\n')
const supportFormats = /\.(m3u8|flv)$/
for (const i of result) {
if (i.includes('http') && supportFormats.test(i)) {
const j = i.split(',')
const doc = {
id: id,
name: j[0],
url: j[1],
isActive: true
}
id += 1
docs.push(doc)
}
}
// 获取url不重复的列表
const uniqueList = [...new Map(docs.map(item => [item.url, item])).values()]
iptv.clear().then(res => {
iptv.bulkAdd(uniqueList).then(e => { // 支持导入同名频道,群里反馈
this.updateChannelList()
})
})
this.$message.success('导入成功')
} catch (error) {
this.$message.warning('导入失败')
}
}
})
this.$message.success('导入成功')
}
})
},
@@ -438,12 +524,12 @@ export default {
res = res.filter(o => !this.iptvList.find(e => o.url === e.url))
const resClone = JSON.parse(JSON.stringify(res))
const uniqueChannelName = {}
for (var i = 0; i < resClone.length; i++) {
var channelName = resClone[i].name.trim().replace(/[- ]?(1080p|蓝光|超清|高清|标清|hd|cq|4k)(\d{1,2})?$/i, '')
for (let i = 0; i < resClone.length; i++) {
let channelName = resClone[i].name.trim().replace(/[- ]?(1080p|蓝光|超清|高清|标清|hd|cq|4k)(\d{1,2})?$/i, '')
if (channelName.match(/cctv/i)) channelName = channelName.replace('-', '')
if (Object.keys(uniqueChannelName).some(name => channelName.match(new RegExp(`${name}(1080p|4k|(?!\\d))`, 'i')))) continue // 避免重复
const matchRule = new RegExp(`${channelName}(1080p|4k|(?!\\d))`, 'i')
for (var j = i; j < resClone.length; j++) {
for (let j = i; j < resClone.length; j++) {
if (resClone[j].name.match(/cctv/i)) {
resClone[j].name = resClone[j].name.replace('-', '')
}
@@ -513,7 +599,7 @@ export default {
})
},
resetId (channelList) {
var id = 1
let id = 1
channelList.forEach(ele => {
ele.id = id
id += 1
@@ -575,7 +661,7 @@ export default {
})
},
async checkChannelsBySite (channels) {
var siteList = {}
const siteList = {}
channels.forEach(channel => {
const site = channel.url.split('/')[2]
if (siteList[site]) {
@@ -592,44 +678,50 @@ export default {
await this.checkSingleChannel(c)
}
},
async checkSingleChannel (channel) {
if (this.setting.allowPassWhenIptvCheck && !channel.isActive) {
async checkSingleChannel (channel, force = false) {
if (this.stopFlag) {
this.checkProgress += 1
return
}
channel.status = ' '
const ele = this.channelList.find(e => e.id === channel.channelID)
if (this.stopFlag) {
this.checkProgress += 1
return channel.status
}
const flag = await zy.checkChannel(channel.url)
this.checkProgress += 1
ele.hasCheckedNum++
if (flag) {
channel.status = '可用'
if (!force && this.setting.allowPassWhenIptvCheck && (!channel.isActive || !ele.isActive)) {
if (!ele.isActive) {
ele.status = '跳过'
} else if (!channel.isActive) {
channel.status = '跳过'
}
} else {
channel.status = '失效'
channel.isActive = false
if (this.setting.autocleanWhenIptvCheck) {
ele.channels.splice(ele.channels.findIndex(e => e.id === channel.id), 1)
ele.hasCheckedNum--
channel.status = ' '
const flag = await zy.checkChannel(channel.url)
if (flag) {
channel.status = '可用'
} else {
channel.status = '失效'
channel.isActive = false
if (this.setting.autocleanWhenIptvCheck) {
if (ele.prefer === channel.id) delete ele.prefer
ele.channels.splice(ele.channels.findIndex(e => e.id === channel.id), 1)
ele.hasCheckedNum--
}
}
}
this.checkProgress += 1
ele.hasCheckedNum++
if (ele.hasCheckedNum === ele.channels.length) {
ele.status = ele.channels.some(channel => channel.status === '可用') ? '可用' : '失效'
if (ele.status === '失效') ele.isActive = false
if (ele.status === ' ') {
ele.status = ele.channels.some(channel => channel.status === '可用') ? '可用' : '失效'
if (ele.status === '失效') ele.isActive = false
}
channelList.remove(channel.channelID)
if (ele.channels.length === 1) ele.hasChildren = false
if (ele.channels.length) channelList.add(ele)
}
return channel.status
},
async checkChannel (row) {
if (row.channels) {
row.status = ' '
row.hasCheckedNum = 0
row.channels.forEach(e => this.checkSingleChannel(e))
row.channels.forEach(e => this.checkSingleChannel(e, true))
} else {
this.checkSingleChannel(row)
}

View File

@@ -3,12 +3,23 @@
<div class="box">
<div class="title">
<span v-if="this.right.list.length > 1"> {{(video.info.index + 1)}} </span>{{name}}
<span class="right" @click="() => { onlineUrl = ''; videoStop(); }">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="closeIconTitle">
<title id="closeIconTitle">关闭</title>
<path d="M6.34314575 6.34314575L17.6568542 17.6568542M6.34314575 17.6568542L17.6568542 6.34314575"></path>
</svg>
</span>
</div>
<div class="player">
<div class="player" v-show="!onlineUrl">
<div id="xgplayer"></div>
</div>
<div class="iframePlayer" v-if="onlineUrl" style='width:100%;height:100%;'>
<iframe v-bind:src="onlineUrl" width="100%" height="100%"
frameborder="0" scrolling="no" allow='autoplay;fullscreen'>
</iframe>
</div>
<div class="more" v-if="!video.iptv" :key="Boolean(video.iptv)">
<span class="zy-svg" @click="otherEvent" v-show="name !== ''">
<span class="zy-svg" @click="otherEvent" v-show="name !== ''" :class="right.type === 'other' ? 'active' : ''">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="coloursIconTitle">
<title id="coloursIconTitle">换源</title>
<circle cx="12" cy="9" r="5"></circle>
@@ -39,7 +50,7 @@
<polyline points="12 5 12 12 16 16"></polyline>
</svg>
</span>
<span class="zy-svg" @click="starEvent" :class="isStar ? 'active' : ''" v-show="right.list.length > 0">
<span class="zy-svg" @click="starEvent" :class="isStar ? 'active' : ''" v-show="right.list.length > 0 || isStar">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="favouriteIconTitle">
<title id="favouriteIconTitle">收藏</title>
<path d="M12,21 L10.55,19.7051771 C5.4,15.1242507 2,12.1029973 2,8.39509537 C2,5.37384196 4.42,3 7.5,3 C9.24,3 10.91,3.79455041 12,5.05013624 C13.09,3.79455041 14.76,3 16.5,3 C19.58,3 22,5.37384196 22,8.39509537 C22,12.1029973 18.6,15.1242507 13.45,19.7149864 L12,21 Z"></path>
@@ -54,13 +65,13 @@
<path d="M5 2h14a3 3 0 0 1 3 3v17H2V5a3 3 0 0 1 3-3z"></path>
</svg>
</span>
<span class="zy-svg" @click="miniEvent" v-show="right.list.length > 0">
<span class="zy-svg" @click="miniEvent" v-show="!onlineUrl && right.list.length > 0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-labelledby="diamondIconTitle">
<title id="diamondIconTitle">精简模式</title>
<path d="M12 20L3 11M12 20L21 11M12 20L8 11M12 20L16 11M3 11L7 5M3 11H8M7 5L8 11M7 5H12M17 5L21 11M17 5L16 11M17 5H12M21 11H16M8 11H16M8 11L12 5M16 11L12 5"></path>
</svg>
</span>
<span class="zy-svg" @click="playWithExternalPalyerEvent" v-show="right.list.length > 0">
<span class="zy-svg" @click="playWithExternalPalyerEvent" v-show="!onlineUrl && right.list.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="tvIconTitle">
<title id="tvIconTitle" >使用第三方播放器</title>
<polygon points="20 8 20 20 4 20 4 8"></polygon>
@@ -84,7 +95,7 @@
<rect x="17" y="6" width="1" height="1"></rect>
</svg>
</span>
<span class="zy-svg" @click="showShortcutEvent" v-show="right.list.length > 0">
<span class="zy-svg" @click="showShortcutEvent" :class="right.type === 'shortcut' ? 'active' : ''" v-show="!onlineUrl && right.list.length > 0">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="sendIconTitle">
<title id="sendIconTitle">快捷键指南</title>
<polygon points="21.368 12.001 3 21.609 3 14 11 12 3 9.794 3 2.394"></polygon>
@@ -98,7 +109,7 @@
<circle cx="12" cy="12" r="10"></circle>
</svg>
</span>
<span class="timespanSwitch" v-if="right.list.length > 1" title="跳过片头片尾,建议优先通过快捷键设置,更便捷更精准">
<span class="timespanSwitch" v-if="right.list.length > 1 && !onlineUrl" title="跳过片头片尾,建议优先通过快捷键设置,更便捷更精准">
<el-switch v-model="state.showTimespanSetting" active-text="手动跳略时长"></el-switch>
</span>
<span class="timespan" v-if="state.showTimespanSetting">
@@ -116,11 +127,14 @@
<span></span>
<input type="button" value="重置" @click="() => { startPosition.min = startPosition.sec = endPosition.min = endPosition.sec = '00'; this.clearPosition() }">
</span>
<span class="last-tip" v-if="!video.key && right.history.length > 0" @click="historyItemEvent(right.history[0])">
上次播放到:{{right.history[0].site}}{{right.history[0].name}} {{right.history[0].index+1}} {{fmtMSS(right.history[0].time.toFixed(0))}}/{{fmtMSS(right.history[0].duration.toFixed(0))}}</span>
<span class="last-tip" v-if="!video.key && right.history.length > 0 && right.history[0]" @click="historyItemEvent(right.history[0])">
<span>上次播放到:{{right.history[0].site}}{{right.history[0].name}} {{right.history[0].index+1}} </span>
<span v-if="right.history[0].time && right.history[0].duration">{{fmtMSS(right.history[0].time.toFixed(0))}}/{{fmtMSS(right.history[0].duration.toFixed(0))}}</span>
<span v-if="right.history[0].onlinePlay">在线解析</span>
</span>
</div>
<div class="more" v-if="video.iptv" :key="Boolean(video.iptv)">
<span class="zy-svg" @click="state.showChannelList = !state.showChannelList">
<span class="zy-svg" @click="state.showChannelList = !state.showChannelList" :class="state.showChannelList ? 'active' : ''">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="dashboardIconTitle">
<title id="dashboardIconTitle">频道列表</title>
<rect width="20" height="20" x="2" y="2"></rect>
@@ -130,7 +144,7 @@
<line x1="7" y1="17" x2="7" y2="17"></line>
</svg>
</span>
<span class="zy-svg" @click="otherEvent">
<span class="zy-svg" @click="otherEvent" :class="right.type === 'sources' ? 'active' : ''">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="coloursIconTitle">
<title id="coloursIconTitle">换源</title>
<circle cx="12" cy="9" r="5"></circle>
@@ -151,7 +165,7 @@
<polyline stroke-linejoin="round" points="8 4 12 7.917 16 4"></polyline>
</svg>
</span>
<span class="zy-svg" @click="showShortcutEvent">
<span class="zy-svg" @click="showShortcutEvent" :class="right.type === 'shortcut' ? 'active' : ''">
<svg role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-labelledby="sendIconTitle">
<title id="sendIconTitle">快捷键指南</title>
<polygon points="21.368 12.001 3 21.609 3 14 11 12 3 9.794 3 2.394"></polygon>
@@ -175,24 +189,24 @@
</span>
</div>
<div class="list-body zy-scroll" :style="{overflowY:scroll? 'auto' : 'hidden',paddingRight: scroll ? '0': '5px' }" @mouseenter="scroll = true" @mouseleave="scroll = false">
<ul v-if="right.type === 'list'" class="list-item" v-on-clickaway="closeListEvent">
<li v-if="right.list.length > 0" @click="exportM3u8">导出</li>
<ul v-if="right.type === 'list'" class="list-item" v-clickoutside="closeListEvent">
<li v-if="exportablePlaylist" @click="exportM3u8">导出</li>
<li v-if="right.list.length === 0">无数据</li>
<li @click="listItemEvent(j)" :class="video.info.index === j ? 'active' : ''" v-for="(i, j) in right.list" :key="j">{{i | ftName(j)}}</li>
</ul>
<ul v-if="right.type === 'history'" class="list-history" v-on-clickaway="closeListEvent">
<ul v-if="right.type === 'history'" class="list-history" v-clickoutside="closeListEvent">
<li v-if="right.history.length > 0" @click="clearAllHistory">清空</li>
<li v-if="right.history.length === 0">无数据</li>
<li @click="historyItemEvent(m)" :class="video.info.id === m.ids ? 'active' : ''" v-for="(m, n) in right.history" :key="n"><span class="title" :title="'【' + m.site + '】' + m.name + ' 第' + (m.index+1) + '集'">【{{m.site}}】{{m.name}} 第{{m.index+1}}集</span><span @click.stop="removeHistoryItem(m)" class="detail-delete">删除</span></li>
</ul>
<ul v-if="right.type === 'shortcut'" class="list-shortcut" v-on-clickaway="closeListEvent">
<ul v-if="right.type === 'shortcut'" class="list-shortcut" v-clickoutside="closeListEvent">
<li v-for="(m, n) in right.shortcut" :key="n"><span class="title">{{m.desc}} -- [ {{m.key}} ]</span></li>
</ul>
<ul v-if="right.type === 'other'" class="list-other" v-on-clickaway="closeListEvent">
<ul v-if="right.type === 'other'" class="list-other" v-clickoutside="closeListEvent">
<li v-if="right.other.length === 0">无数据</li>
<li @click="otherItemEvent(m)" v-for="(m, n) in right.other" :key="n"><span class="title">{{m.name}} - [{{m.site.name}}]</span></li>
</ul>
<ul v-if="right.type === 'sources'" class="list-channels" v-on-clickaway="closeListEvent">
<ul v-if="right.type === 'sources'" class="list-channels" v-clickoutside="closeListEvent">
<li v-if="right.sources.length === 0">当前频道已关闭</li>
<li v-for="(channel, index) in right.sources" :key="index">
<span @click="playChannel(channel)" class="title">{{ channel.id === video.iptv.id ? channel.name + '[当前]' : channel.name }}</span>
@@ -203,7 +217,7 @@
</div>
</transition>
<transition name="slideX">
<div v-if="state.showChannelList" class="list" v-on-clickaway="closeListEvent">
<div v-if="state.showChannelList && channelList && channelList.length > 0" class="list" v-clickoutside="closeListEvent">
<div class="list-top">
<span class="list-top-title">频道列表</span>
<span class="list-top-close zy-svg" @click="state.showChannelList = false">
@@ -238,15 +252,18 @@ import { mapMutations } from 'vuex'
import { star, history, setting, shortcut, mini, channelList, sites } from '../lib/dexie'
import zy from '../lib/site/tools'
import Player from 'xgplayer'
import 'xgplayer-mp4'
import HlsJsPlayer from 'xgplayer-hls.js'
import FlvJsPlayer from 'xgplayer-flv.js'
import mt from 'mousetrap'
import { directive as onClickaway } from 'vue-clickaway'
import Clickoutside from 'element-ui/src/utils/clickoutside'
import { exec, execFile } from 'child_process'
import PinyinMatch from 'pinyin-match'
const { remote, clipboard } = require('electron')
const { clipboard } = require('electron')
const remote = require('@electron/remote')
const win = remote.getCurrentWindow()
const PinyinMatch = require('pinyin-match')
const URL = require('url')
const VIDEO_DETAIL_CACHE = {}
const addPlayerBtn = function (event, svg, attrs) {
@@ -327,7 +344,7 @@ export default {
videoTitle: true,
airplay: true,
closeVideoTouch: true,
ignores: ['cssFullscreen', 'replay'],
ignores: ['replay', 'error'], // 为了切换播放器类型时避免显示错误刷新,暂时忽略错误
preloadTime: 300
},
state: {
@@ -341,7 +358,6 @@ export default {
timer: null,
scroll: false,
isStar: false,
isTop: false,
miniMode: false,
mainWindowBounds: {},
searchTxt: '',
@@ -355,7 +371,11 @@ export default {
startPosition: { min: '00', sec: '00' }, // 对应调略输入框
endPosition: { min: '00', sec: '00' },
skipendStatus: false, // 是否跳过了片尾
currentShortcutList: []
currentShortcutList: [],
onlineUrl: '',
playerType: 'hls',
exportablePlaylist: false,
changingIPTV: false
}
},
filters: {
@@ -369,7 +389,7 @@ export default {
}
},
directives: {
onClickaway: onClickaway
Clickoutside
},
computed: {
view: {
@@ -419,6 +439,17 @@ export default {
set (val) {
this.SET_SETTING(val)
}
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
},
VideoEssentialInfo () {
return this.video.key + '@' + this.video.info.id + '@' + this.video.info.index
}
},
watch: {
@@ -432,11 +463,11 @@ export default {
}
}
},
video: {
VideoEssentialInfo: {
handler () {
if (this.changingIPTV) return
this.getUrls()
},
deep: true
}
},
setting: {
handler () {
@@ -472,7 +503,7 @@ export default {
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_APPSTATE']),
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_APPSTATE', 'SET_DetailCache']),
fmtMSS (s) {
return (s - (s %= 60)) / 60 + (s > 9 ? ':' : ':0') + s
},
@@ -502,6 +533,7 @@ export default {
return false
}
this.name = ''
this.onlineUrl = ''
if (this.timer !== null) {
clearInterval(this.timer)
this.timer = null
@@ -518,14 +550,16 @@ export default {
const index = this.video.info.index || 0
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
const key = this.video.key + '@' + this.video.info.id
var time = this.video.info.time
let time = this.video.info.time
this.xg.removeAllProgressDot()
this.startPosition = this.endPosition = { min: '00', sec: '00' }
this.startPosition = { min: '00', sec: '00' }
this.endPosition = { min: '00', sec: '00' }
if (db) {
if (!time && db.index === index) { // 如果video.info.time没有设定的话从历史中读取时间进度
time = db.time
}
if (!VIDEO_DETAIL_CACHE[key]) VIDEO_DETAIL_CACHE[key] = {}
if (!this.video.info.videoFlag) this.video.info.videoFlag = db.videoFlag
if (db.startPosition) { // 数据库保存的时长通过快捷键设置时可能为小数, this.startPosition为object对应输入框分秒转化到数据库后肯定为整数
VIDEO_DETAIL_CACHE[key].startPosition = db.startPosition
this.startPosition = { min: '' + parseInt(db.startPosition / 60), sec: '' + parseInt(db.startPosition % 60) }
@@ -551,8 +585,11 @@ export default {
playChannel (channel) {
this.isLive = true
if (channel.channels) {
let prefer
this.right.sources = channel.channels.filter(e => e.isActive)
channel = channel.prefer ? channel.channels.find(e => e.id === channel.prefer) : channel.channels.filter(e => e.isActive)[0]
if (channel.prefer) prefer = channel.channels.find(e => e.id === channel.prefer)
if (!prefer) prefer = channel.channels.filter(e => e.isActive)[0]
channel = prefer
} else {
const ele = this.channelList.find(e => e.id === channel.channelID)
ele.prefer = channel.id
@@ -560,119 +597,176 @@ export default {
channelList.add(ele)
this.right.sources = ele.channels.filter(e => e.isActive)
}
this.changingIPTV = true // 避免二次执行playChannel
this.video.iptv = channel
this.name = channel.name
const supportFormats = /\.(m3u8|flv)$/
const extRE = channel.url.match(supportFormats) || new URL.URL(channel.url).pathname.match(supportFormats)
this.getPlayer(extRE[1])
if (extRE[1] === 'flv') this.xg.config.isLive = true
this.xg.src = channel.url
this.xg.play()
document.querySelector('xg-btn-showhistory').style.display = 'none'
document.querySelector('.xgplayer-playbackrate').style.display = 'none'
this.changingIPTV = false
setTimeout(() => {
if (!document.getElementById('xgplayer').querySelector('video')) {
this.getPlayer(this.playerType, true)
this.playChannel(channel)
}
}, 1000)
if (document.querySelector('xg-btn-showhistory')) document.querySelector('xg-btn-showhistory').style.display = 'none'
if (document.querySelector('.xgplayer-playbackrate')) document.querySelector('.xgplayer-playbackrate').style.display = 'none'
},
async getPlayer (playerType, force = false) {
if (!force && this.playerType === playerType) return
if (this.playerType !== 'flv') {
this.xg.src = '' // https://developers.google.com/web/updates/2017/06/play-request-was-interrupted#danger-zone
this.config.url = ''
}
try {
if (this.xg) this.xg.destroy()
} catch (err) { }
this.xg = null
switch (playerType) {
case 'mp4':
this.xg = new Player(this.config)
break
case 'flv':
this.xg = new FlvJsPlayer(this.config)
break
default:
this.xg = new HlsJsPlayer(this.config)
}
this.playerInstall()
this.bindEvent()
this.playerType = playerType
if (this.miniMode) { await this.saveMiniWindowState(); this.miniEvent() }
},
playVideo (index = 0, time = 0) {
this.isLive = false
document.querySelector('xg-btn-showhistory').style.display = 'block'
document.querySelector('.xgplayer-playbackrate').style.display = 'inline-block'
this.fetchM3u8List().then(m3u8Arr => {
const url = m3u8Arr[index]
if (!m3u8Arr[index].endsWith('.m3u8')) {
const onlineUrl = 'https://www.1717yun.com/jiexi/?url=' + url
const open = require('open')
open(onlineUrl)
} else {
this.xg.src = m3u8Arr[index]
const key = this.video.key + '@' + this.video.info.id
const startTime = VIDEO_DETAIL_CACHE[key].startPosition || 0
this.xg.play()
this.xg.once('playing', () => {
this.xg.currentTime = time > startTime ? time : startTime
if (VIDEO_DETAIL_CACHE[key].startPosition) this.xg.addProgressDot(VIDEO_DETAIL_CACHE[key].startPosition, '片头')
if (VIDEO_DETAIL_CACHE[key].endPosition) this.xg.addProgressDot(this.xg.duration - VIDEO_DETAIL_CACHE[key].endPosition, '片尾')
})
this.videoPlaying()
this.skipendStatus = false
this.xg.once('ended', () => {
if (m3u8Arr.length > 1 && (m3u8Arr.length - 1 > index)) {
this.video.info.time = 0
this.video.info.index++
}
this.xg.off('ended') // 明明是once为何会触发多次得注销掉以真正只执行一次
})
this.isStar = false
this.exportablePlaylist = false
this.fetchPlaylist().then(async (fullList) => {
let playlist = fullList[0].list // ZY支持的已移到首位
// 如果设定了特定的video flag, 获取该flag下的视频列表
const videoFlag = this.video.info.videoFlag
if (videoFlag) {
playlist = fullList.find(x => x.flag === videoFlag).list
}
this.right.list = playlist
const url = playlist[index].includes('$') ? playlist[index].split('$')[1] : playlist[index]
if (playlist.every(e => e.includes('$') ? e.split('$')[1].endsWith('.m3u8') : e.endsWith('.m3u8'))) this.exportablePlaylist = true
if (!url.endsWith('.m3u8') && !url.endsWith('.mp4')) {
const currentSite = await sites.find({ key: this.video.key })
this.$message.info('即将调用解析接口播放,请等待...')
if (currentSite.jiexiUrl) {
this.onlineUrl = currentSite.jiexiUrl + url
} else {
this.onlineUrl = this.setting.defaultParseURL + url
}
this.videoPlaying('online')
return
} else {
const ext = url.match(/\.\w+?$/)[0].slice(1)
this.getPlayer(ext)
}
this.xg.src = url
const key = this.video.key + '@' + this.video.info.id
const startTime = VIDEO_DETAIL_CACHE[key].startPosition || 0
this.xg.play()
setTimeout(() => {
if (!document.getElementById('xgplayer').querySelector('video')) {
this.getPlayer(this.playerType, true)
this.getUrls()
}
}, 1000)
if (document.querySelector('xg-btn-showhistory')) document.querySelector('xg-btn-showhistory').style.display = 'block'
if (document.querySelector('.xgplayer-playbackrate')) document.querySelector('.xgplayer-playbackrate').style.display = 'inline-block'
this.xg.once('playing', () => {
this.xg.currentTime = time > startTime ? time : startTime
if (VIDEO_DETAIL_CACHE[key].startPosition) this.xg.addProgressDot(VIDEO_DETAIL_CACHE[key].startPosition, '片头')
if (VIDEO_DETAIL_CACHE[key].endPosition) this.xg.addProgressDot(this.xg.duration - VIDEO_DETAIL_CACHE[key].endPosition, '片尾')
})
this.videoPlaying()
this.skipendStatus = false
this.xg.once('ended', () => {
if (playlist.length > 1 && (playlist.length - 1 > index)) {
this.video.info.time = 0
this.video.info.index++
}
this.xg.off('ended') // 明明是once为何会触发多次得注销掉以真正只执行一次
})
})
},
fetchM3u8List () {
fetchPlaylist () {
return new Promise((resolve) => {
const cacheKey = this.video.key + '@' + this.video.info.id
if (VIDEO_DETAIL_CACHE[cacheKey] && VIDEO_DETAIL_CACHE[cacheKey].list && VIDEO_DETAIL_CACHE[cacheKey].list.length) {
this.name = VIDEO_DETAIL_CACHE[cacheKey].name
resolve(VIDEO_DETAIL_CACHE[cacheKey].list)
}
zy.detail(this.video.key, this.video.info.id).then(res => {
let res
if (!this.DetailCache[cacheKey]) {
zy.detail(this.video.key, this.video.info.id).then(res => {
this.DetailCache[cacheKey] = res
res = this.DetailCache[cacheKey]
this.name = res.name
VIDEO_DETAIL_CACHE[cacheKey] = Object.assign(VIDEO_DETAIL_CACHE[cacheKey] || { }, {
list: res.fullList,
name: res.name
})
resolve(res.fullList)
}).catch(err => { this.$message.error('播放地址可能已失效,请换源并调整收藏', err); this.name = this.video.info.name; this.updateStar(); this.otherEvent() })
} else {
res = this.DetailCache[cacheKey]
this.name = res.name
const m3u8Txt = res.m3u8List
this.right.list = m3u8Txt
const m3u8Arr = []
for (const i of m3u8Txt) {
const j = i.split('$')
if (j.length > 1) {
for (let m = 0; m < j.length; m++) {
if (j[m].startsWith('http')) {
m3u8Arr.push(j[m])
break
}
}
} else {
m3u8Arr.push(j[0])
}
}
VIDEO_DETAIL_CACHE[cacheKey] = Object.assign(VIDEO_DETAIL_CACHE[cacheKey] || {}, {
list: m3u8Arr,
VIDEO_DETAIL_CACHE[cacheKey] = Object.assign(VIDEO_DETAIL_CACHE[cacheKey] || { }, {
list: res.fullList,
name: res.name
})
resolve(m3u8Arr)
})
resolve(res.fullList)
}
})
},
async videoPlaying () {
this.changeVideo()
async videoPlaying (isOnline) {
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
const videoFlag = this.video.info.videoFlag || ''
let time = this.xg.currentTime || 0
let duration = this.xg.duration || 0
let startPosition = 0
let endPosition = 0
if (db) {
const doc = {
site: db.site,
ids: db.ids,
name: db.name,
type: db.type,
year: db.year,
index: this.video.info.index,
time: db.time,
detail: this.video.detail
}
history.update(db.id, doc)
} else {
const doc = {
site: this.video.key,
ids: this.video.info.id,
name: this.video.info.name,
type: this.video.info.type,
year: this.video.info.year,
index: this.video.info.index,
time: '',
detail: this.video.detail
}
history.add(doc)
time = time || db.time
duration = duration || db.duration
startPosition = db.startPosition
endPosition = db.endPosition
await history.remove(db.id)
}
if (isOnline) {
time = duration = 0
}
const doc = {
site: this.video.key,
ids: this.video.info.id,
name: this.video.info.name,
type: this.video.info.type,
year: this.video.info.year,
index: this.video.info.index,
time: time,
duration: duration,
startPosition: startPosition,
endPosition: endPosition,
detail: this.DetailCache[this.video.key + '@' + this.video.info.id],
onlinePlay: isOnline,
videoFlag: videoFlag
}
await history.add(doc)
this.updateStar()
this.timerEvent()
},
changeVideo () {
win.setProgressBar(-1)
this.checkStar()
this.checkTop()
if (!isOnline) this.timerEvent()
},
async setProgressDotEvent (position, timespan, text) { // 根据跳略时长在进度条上添加标记, position为位置, timespan为时长text为标记文本(title)
const key = this.video.key + '@' + this.video.info.id
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
if (db && this.xg && VIDEO_DETAIL_CACHE[key].list.length > 1) {
if (db && this.xg && this.right.list.length > 1) {
this[position] = { min: '' + parseInt(timespan / 60), sec: '' + parseInt(timespan % 60) }
const positionTime = position === 'endPosition' ? this.xg.duration - timespan : timespan
if (db[position]) this.xg.removeProgressDot(position === 'endPosition' ? this.xg.duration - db[position] : db[position])
@@ -702,10 +796,6 @@ export default {
},
timerEvent () {
this.timer = setInterval(async () => {
const endTime = this.xg.duration
const currentTime = this.xg.currentTime
const progress = parseFloat((currentTime / endTime).toFixed(2))
win.setProgressBar(progress)
const db = await history.find({ site: this.video.key, ids: this.video.info.id })
if (db) {
const doc = { ...db }
@@ -718,13 +808,10 @@ export default {
},
prevEvent () {
if (this.video.iptv) {
var index = this.channelList.findIndex(obj => obj.id === this.video.iptv.channelID)
if (index >= 1) {
var channel = this.channelList[index - 1]
this.playChannel(channel)
} else {
this.$message.warning('这已经是第一个频道了。')
}
let index = this.channelList.findIndex(obj => obj.id === this.video.iptv.channelID)
index = index === 0 ? this.channelList.length - 1 : index - 1
const channel = this.channelList[index]
this.playChannel(channel)
} else {
if (this.video.info.index >= 1) {
this.video.info.index--
@@ -736,13 +823,10 @@ export default {
},
nextEvent () {
if (this.video.iptv) {
var index = this.channelList.findIndex(obj => obj.id === this.video.iptv.channelID)
if (index < (this.channelList.length - 1)) {
var channel = this.channelList[index + 1]
this.playChannel(channel)
} else {
this.$message.warning('这已经是最后一个频道了。')
}
let index = this.channelList.findIndex(obj => obj.id === this.video.iptv.channelID)
index = index === this.channelList.length - 1 ? 0 : index + 1
const channel = this.channelList[index]
this.playChannel(channel)
} else {
if (this.video.info.index < (this.right.list.length - 1)) {
this.video.info.index++
@@ -781,13 +865,15 @@ export default {
const info = this.video.info
const db = await star.find({ key: this.video.key, ids: info.id })
if (db) {
this.isStar = true
db.index = info.index
star.update(db.id, db)
} else {
this.isStar = false
}
},
async starEvent () {
const info = this.video.info
const db = await star.find({ key: this.video.key, ids: info.id })
const db = await star.find({ key: this.video.key, ids: this.video.info.id })
if (db) {
star.remove(db.id).then(res => {
if (res) {
@@ -798,18 +884,16 @@ export default {
}
})
} else {
zy.detail(this.video.key, info.id).then(detailRes => {
const docs = {
key: this.video.key,
ids: info.id,
name: info.name,
detail: detailRes,
index: info.index
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
this.isStar = true
})
const docs = {
key: this.video.key,
ids: this.video.info.id,
name: this.video.info.name,
detail: this.DetailCache[this.video.key + '@' + this.video.info.id],
index: this.video.info.index
}
star.add(docs).then(res => {
this.$message.success('收藏成功')
this.isStar = true
})
}
},
@@ -821,7 +905,7 @@ export default {
}
},
async miniEvent () {
this.mainWindowBounds = JSON.parse(JSON.stringify(win.getBounds()))
if (!this.miniMode) this.mainWindowBounds = JSON.parse(JSON.stringify(win.getBounds()))
let miniWindowBounds
await mini.find().then(res => { if (res) miniWindowBounds = res.bounds })
if (!miniWindowBounds) miniWindowBounds = { x: win.getPosition()[0], y: win.getPosition()[1], width: 550, height: 340 }
@@ -830,7 +914,7 @@ export default {
document.querySelector('xg-btn-quitMiniMode').style.display = 'block'
this.miniMode = true
},
async exitMiniEvent () {
async saveMiniWindowState () {
await mini.find().then(res => {
let doc = {}
doc = {
@@ -843,6 +927,9 @@ export default {
mini.add(doc)
}
})
},
async exitMiniEvent () {
await this.saveMiniWindowState()
win.setBounds(this.mainWindowBounds)
this.xg.exitCssFullscreen()
document.querySelector('xg-btn-quitMiniMode').style.display = 'none'
@@ -852,14 +939,16 @@ export default {
this.share = {
show: true,
key: this.video.key,
info: this.video.info
info: this.DetailCache[this.video.key + '@' + this.video.info.id],
index: this.video.info.index
}
},
issueEvent () {
async issueEvent () {
const currentSite = await sites.find({ key: this.video.key })
const info = {
video: this.video,
list: this.right.list,
m3u8List: VIDEO_DETAIL_CACHE[this.video.key + '@' + this.video.info.id] || [],
video: Object.assign({ site: currentSite, detail: this.DetailCache[this.video.key + '@' + this.video.info.id] }, this.video.info),
playlist: this.right.list.map(e => e.split('$')[1]),
playerType: this.onlineUrl ? '在线解析' : this.playerType,
playerError: this.xg.error || '',
playerState: this.xg.readyState || '',
networkState: this.xg.networkState || ''
@@ -869,8 +958,8 @@ export default {
},
playWithExternalPalyerEvent () {
const fs = require('fs')
const externalPlayer = this.setting.externalPlayer
if (this.video.iptv) {
var externalPlayer = this.setting.externalPlayer
if (!externalPlayer) {
this.$message.error('请设置第三方播放器路径')
return
@@ -880,35 +969,40 @@ export default {
} else {
exec(externalPlayer, [this.video.iptv.url])
}
return
}
this.fetchM3u8List().then(m3u8Arr => {
var externalPlayer = this.setting.externalPlayer
} else {
const playlistUrls = this.right.list.map(e => e.split('$')[1])
if (!externalPlayer) {
this.$message.error('请设置第三方播放器路径')
// 在线播放该视频
var link = 'https://www.m3u8play.com/?play=' + m3u8Arr[this.video.info.index]
const open = require('open')
open(link)
if (playlistUrls[this.video.info.index].endsWith('.m3u8')) {
const link = 'http://hunlongyu.gitee.io/zy-player-web?url=' + playlistUrls[this.video.info.index] + '&name=' + this.video.info.name
const open = require('open')
open(link)
}
} else {
var m3uFile = this.generateM3uFile(this.video.info.name, m3u8Arr, this.video.info.index)
if (fs.existsSync(externalPlayer)) {
execFile(externalPlayer, [m3uFile])
let playlist
if (playlistUrls.every(e => e.endsWith('.m3u8'))) {
playlist = this.generateM3uFile(this.video.info.name, playlistUrls, this.video.info.index)
} else {
exec(externalPlayer, [m3uFile])
playlist = playlistUrls[this.video.info.index]
}
if (fs.existsSync(externalPlayer)) {
execFile(externalPlayer, [playlist])
} else {
exec(externalPlayer, [playlist])
}
}
})
}
},
generateM3uFile (fileName, m3u8Arr, startIndex) {
const path = require('path')
const os = require('os')
const fs = require('fs')
var filePath = path.join(os.tmpdir(), fileName + '.m3u')
const filePath = path.join(os.tmpdir(), fileName + '.m3u')
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath)
}
var str = '#EXTM3U' + os.EOL
let str = '#EXTM3U' + os.EOL
for (let ind = startIndex; ind < m3u8Arr.length; ind++) {
str += `#EXTINF: -1, 第${ind + 1}` + os.EOL
str += m3u8Arr[ind] + os.EOL
@@ -917,21 +1011,18 @@ export default {
fs.writeFileSync(filePath, str)
return filePath
},
async checkStar () {
const db = await star.find({ key: this.video.key, ids: this.video.info.id })
if (db) {
this.isStar = true
} else {
this.isStar = false
}
},
checkTop () {
this.isTop = this.appState.windowIsOnTop
},
closeListEvent () {
this.right.show = false
this.right.type = ''
this.state.showChannelList = false
const lastRightType = this.right.type
const lastChannelListState = this.state.showChannelList
setTimeout(() => {
if (lastRightType === this.right.type) {
this.right.show = false
this.right.type = ''
}
if (lastChannelListState === this.state.showChannelList) {
this.state.showChannelList = false
}
}, 50)
},
exportM3u8 () {
const m3u8Arr = []
@@ -955,15 +1046,15 @@ export default {
link: link
})
}
let m3u8Content = '#EXTM3U'
let m3u8Content = '#EXTM3U\n'
for (const item of m3u8Arr) {
m3u8Content += `#EXTINF:-1, ${item.name}\n${item.link}`
m3u8Content += `#EXTINF:-1, ${item.name}\n${item.link}\n`
}
const blob = new Blob([m3u8Content], { type: 'application/vnd.apple.mpegurl' })
const downloadElement = document.createElement('a')
const href = window.URL.createObjectURL(blob)
downloadElement.href = href
downloadElement.download = `${this.name}.m3u8`
downloadElement.download = `${this.name}.m3u`
document.body.appendChild(downloadElement)
downloadElement.click()
document.body.removeChild(downloadElement)
@@ -976,7 +1067,7 @@ export default {
},
listItemEvent (n) {
if (this.video.iptv) {
var channel = this.channelList[n]
const channel = this.channelList[n]
// 是直播源,直接播放
this.playChannel(channel)
} else {
@@ -1040,7 +1131,6 @@ export default {
this.getOtherSites()
this.right.currentTime = this.xg.currentTime
} else {
this.state.showChannelList = false
this.right.type = 'sources'
}
this.right.show = true
@@ -1084,8 +1174,6 @@ export default {
if (this.xg) {
if (this.xg.paused) {
this.xg.play()
// 继续播放时,隐藏进度条
win.setProgressBar(-1)
} else {
this.xg.pause()
}
@@ -1341,7 +1429,7 @@ export default {
this.channelTree = []
const groups = [...new Set(this.channelList.map(iptv => iptv.group))]
groups.forEach(g => {
var doc = {
const doc = {
label: g,
children: this.channelList.filter(x => x.group === g).map(i => { return { label: i.name, channel: i } })
}
@@ -1350,16 +1438,45 @@ export default {
})
},
bindEvent () {
// 直播卡顿时换源换台
let stallIptvTimeout
let stallCount = 0
this.xg.on('waiting', () => {
if (this.isLive && this.setting.autoChangeSourceWhenIptvStalling) {
stallIptvTimeout = setTimeout(() => {
let index = this.right.sources.indexOf(this.video.iptv) + 1
if (index === this.right.sources.length) index = 0
stallCount++
if (stallCount >= this.right.sources.length) {
stallCount = 0
this.nextEvent()
} else {
this.playChannel(this.right.sources[index])
}
}, this.setting.waitingTimeInSec * 1000)
}
})
this.xg.on('canplay', () => {
stallCount = 0
clearTimeout(stallIptvTimeout)
})
this.xg.on('destroy', () => {
stallCount = 0
clearTimeout(stallIptvTimeout)
})
this.xg.on('exitFullscreen', () => {
if (this.miniMode) this.xg.getCssFullscreen()
})
this.xg.on('volumechange', () => {
this.config.volume = this.xg.volume.toFixed(2)
setting.find().then(res => { res.volume = this.config.volume; setting.update(res) })
const volume = this.config.volume
setTimeout(() => { if (volume === this.config.volume) setting.find().then(res => { res.volume = this.config.volume; setting.update(res) }) }, 500)
})
this.xg.on('timeupdate', () => {
if (this.isLive) return
const key = this.video.key + '@' + this.video.info.id
if (VIDEO_DETAIL_CACHE[key] && VIDEO_DETAIL_CACHE[key].endPosition) {
const time = this.xg.duration - VIDEO_DETAIL_CACHE[key].endPosition - this.xg.currentTime
@@ -1388,8 +1505,8 @@ export default {
this.toggleHistory()
})
this.xg.on('videoStop', () => {
if (this.miniMode) this.exitMiniEvent()
this.xg.on('videoStop', async () => {
if (this.miniMode) await this.exitMiniEvent()
this.videoStop()
})
@@ -1420,6 +1537,7 @@ export default {
})
this.xg.on('play', () => {
clearTimeout(stallIptvTimeout)
if (!this.video.key) {
if (!this.video.iptv && !this.video.info.ids) {
// 如果当前播放页面的播放信息没有被赋值,播放历史记录
@@ -1428,9 +1546,8 @@ export default {
this.videoStop()
return
}
var historyItem = this.right.history[0]
const historyItem = this.right.history[0]
this.video = { key: historyItem.site, info: { id: historyItem.ids, name: historyItem.name, index: historyItem.index } }
this.getUrls()
} else if (this.video.iptv && !this.isLive) {
this.playChannel(this.video.iptv)
this.state.showChannelList = false
@@ -1439,31 +1556,23 @@ export default {
})
},
videoStop () {
win.setProgressBar(-1)
if (this.xg.fullscreen) {
this.xg.exitFullscreen()
}
clearInterval(this.timer)
this.video.key = ''
this.xg.src = ''
this.config.url = ''
this.xg.destroy(false)
this.xg = null
this.name = ''
this.isLive = false
this.state.showChannelList = true
this.state.showTimespanSetting = false
this.right.list = []
this.getAllhistory()
setTimeout(() => {
this.xg = new HlsJsPlayer(this.config)
this.playerInstall()
this.bindEvent()
}, 100)
if (this.playerType === 'flv') this.xg.destroy()
this.getPlayer('hls', true)
},
minMaxEvent () {
win.on('minimize', () => {
if (this.xg && this.xg.hasStart) {
if (this.xg && this.xg.hasStart && this.setting.pauseWhenMinimize) {
this.xg.pause()
}
})
@@ -1505,11 +1614,16 @@ export default {
})
},
showShortcutEvent () {
this.right.show = !this.right.show
shortcut.all().then(res => {
this.right.type = 'shortcut'
this.right.shortcut = res
})
if (this.right.type === 'shortcut') {
this.right.show = false
this.right.type = ''
} else {
this.right.show = true
shortcut.all().then(res => {
this.right.type = 'shortcut'
this.right.shortcut = res
})
}
}
},
created () {
@@ -1554,6 +1668,9 @@ export default {
cursor: pointer;
margin-left: 3px;
}
.xgplayer-skin-default .xg-btn-playPrev {
margin-left: 50px;
}
.xgplayer-skin-default .xg-btn-quitMiniMode {
display: none;
}
@@ -1631,12 +1748,6 @@ export default {
.xgplayer-skin-default .xgplayer-playbackrate {
width: 40px !important;
}
.xgplayer-skin-default .xgplayer-playbackrate .name {
top: 10px !important;
}
.xgplayer-skin-default .xgplayer-playbackrate ul {
bottom: 25px;
}
.xgplayer-skin-default .xgplayer-playbackrate ul li {
font-size: 13px !important;
}
@@ -1685,6 +1796,7 @@ export default {
.right {
float: right;
svg {
display: inline-block;
margin-top: 8px;
cursor: pointer;
}

View File

@@ -1,39 +1,63 @@
<template>
<div class="listpage" id="recommendataions">
<div class="listpage-header" id="recommendataions-header">
<el-switch v-model="setting.recommendationViewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateViewMode"></el-switch>
<div class="listpage" id="recommendations">
<div class="listpage-header" id="recommendations-header">
<el-select v-model="selectedRecommendationType" size="small" slot="prepend"
:popper-append-to-body="false"
popper-class="popper"
default-first-option placeholder="请选择"
@change="changeRecommendationTypeEvent">
<el-option
v-for="item in recommendationTypes"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-button type="text">视频数{{ recommendations.length }}</el-button>
<el-select v-model="selectedAreas" size="small" multiple placeholder="地区" popper-class="popper" :popper-append-to-body="false">
<el-option
v-for="item in areas"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="selectedTypes" size="small" multiple placeholder="类型" popper-class="popper" :popper-append-to-body="false">
<el-option
v-for="item in types"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="sortKeyword" size="small" placeholder="排序" popper-class="popper" :popper-append-to-body="false">
<el-option
v-for="item in sortKeywords"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-button :loading="loading" @click.stop="updateEvent" icon="el-icon-refresh">更新推荐</el-button>
</div>
<div class="listpage-body" id="recommendataions-body" >
<div class="toolbar" v-show="showToolbar">
<el-select v-model="selectedAreas" size="small" multiple placeholder="地区" popper-class="popper" :popper-append-to-body="false" @remove-tag="refreshFilteredList" @change="refreshFilteredList">
<el-option
v-for="item in areas"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="selectedTypes" size="small" multiple placeholder="类型" popper-class="popper" :popper-append-to-body="false" @remove-tag="refreshFilteredList" @change="refreshFilteredList">
<el-option
v-for="item in types"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="sortKeyword" size="small" placeholder="排序" popper-class="popper" :popper-append-to-body="false" @change="refreshFilteredList">
<el-option
v-for="item in sortKeywords"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<span>
上映区间
<el-input-number size="small" v-model="selectedYears.start" :min=0 :max="new Date().getFullYear()" controls-position="right" step-strictly @change="refreshFilteredList"></el-input-number>
<el-input-number size="small" v-model="selectedYears.end" :min=0 :max="new Date().getFullYear()" controls-position="right" step-strictly @change="refreshFilteredList"></el-input-number>
</span>
</div>
<el-divider class="listpage-header-divider" content-position="right">
<el-button type="text" size="mini" @click="toggleViewMode">视图切换</el-button>
<el-button type="text" size="mini" @click='() => { showToolbar = !showToolbar; if (!showToolbar) this.refreshFilteredList() }' title="收起工具栏会重置筛选排序">{{ showToolbar ? '隐藏工具栏' : '显示工具栏' }}</el-button>
<el-button type="text" size="mini" @click="backTop">回到顶部</el-button>
</el-divider>
<div class="listpage-body" id="recommendations-body" >
<div class="show-table" id="star-table" v-if="setting.recommendationViewMode === 'table'">
<el-table size="mini" fit height="100%" row-key="id"
ref="recommendataionsTable"
:data="filteredRecommendations"
ref="recommendationsTable"
:data="filteredList"
@row-click="detailEvent">
<el-table-column
prop="name"
@@ -55,13 +79,13 @@
width="100"
align="center">
</el-table-column>
<el-table-column v-if="filteredRecommendations.some(e => e.rate)"
<el-table-column v-if="filteredList.some(e => e.rate)"
prop="rate"
align="center"
width="100"
label="豆瓣评分">
</el-table-column>
<el-table-column v-if="filteredRecommendations.some(e => e.detail.note)"
<el-table-column v-if="filteredList.some(e => e.detail.note)"
prop="detail.note"
label="备注">
</el-table-column>
@@ -74,13 +98,12 @@
<el-button @click.stop="playEvent(scope.row)" type="text">播放</el-button>
<el-button @click.stop="shareEvent(scope.row)" type="text">分享</el-button>
<el-button @click.stop="downloadEvent(scope.row)" type="text">下载</el-button>
<el-button @click.stop="deleteEvent(scope.row)" type="text">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="show-picture" id="star-picture" v-if="setting.recommendationViewMode === 'picture'">
<Waterfall ref="recommendataionsWaterfall" :list="filteredRecommendations" :gutter="20" :width="240"
<Waterfall ref="recommendationsWaterfall" :list="filteredList" :gutter="20" :width="240"
:breakpoints="{
1200: { //当屏幕宽度小于等于1200
rowPerView: 4,
@@ -100,13 +123,12 @@
<div class="rate" v-if="props.data.rate && props.data.rate !== '暂无评分'">
<span>{{props.data.rate}}</span>
</div>
<img style="width: 100%" :src="props.data.detail.pic" alt="" @load="$refs.recommendataionsWaterfall.refresh()" @click="detailEvent(props.data)">
<img style="width: 100%" :src="props.data.detail.pic" alt="" @load="$refs.recommendationsWaterfall.refresh()" @click="detailEvent(props.data)">
<div class="operate">
<div class="operate-wrap">
<span class="o-play" @click="playEvent(props.data)">播放</span>
<span class="o-share" @click="shareEvent(props.data)">分享</span>
<span class="o-star" @click="downloadEvent(props.data)">下载</span>
<span class="o-star" @click="deleteEvent(props.data)">删除</span>
</div>
</div>
</div>
@@ -126,10 +148,12 @@
</template>
<script>
import { mapMutations } from 'vuex'
import { history, recommendation, setting } from '../lib/dexie'
import { history, recommendation, setting, sites, cachedMovies } from '../lib/dexie'
import zy from '../lib/site/tools'
import Waterfall from 'vue-waterfall-plugin'
import axios from 'axios'
const { clipboard } = require('electron')
export default {
name: 'recommendations',
data () {
@@ -138,11 +162,49 @@ export default {
sites: [],
loading: false,
types: [],
selectedTypes: [],
areas: [],
filteredList: [],
// 不同推荐
recommendationsDefault: [],
recommendationTypes: ['豆瓣热门电影', '豆瓣高分电影', '豆瓣华语电影', '豆瓣冷门佳片', '豆瓣热门剧集', '豆瓣热门美剧', '豆瓣热门英剧', '豆瓣热门国产剧', '豆瓣热门综艺', '豆瓣热门动漫', '豆瓣热门纪录片', '豆瓣热门动画电影'],
selectedRecommendationType: '豆瓣热门电影',
// Toolbar
showToolbar: false,
selectedAreas: [],
selectedTypes: [],
sortKeyword: '',
sortKeywords: ['上映', '评分', '默认']
sortKeywords: ['按片名', '按上映年份', '按更新时间', '按评分'],
selectedYears: { start: 0, end: new Date().getFullYear() },
// 缓存数据
localCachedMovies: [],
// 豆瓣
douban: {
page_limit: 50,
hotMoviePageStart: 0,
hotmovie: [],
hotTVPageStart: 0,
hotTV: [],
highRateMoviePageStart: 0,
highRateMovie: [],
hotAnimePageStart: 0,
hotAnime: [],
hotDocumentaryPageStart: 0,
hotDocumentary: [],
hotTVShowPageStart: 0,
hotTVShow: [],
hotCartonMoviePageStart: 0,
hotCartonMovie: [],
hotAmericanTVSeriesPageStart: 0,
hotAmericanTVSeries: [],
hotBritishTVSeriesPageStart: 0,
hotBritishTVSeries: [],
hotChineseTVSeriesPageStart: 0,
hotChineseTVSeries: [],
goodButNotHotMoviesPageStart: 0,
goodButNotHotMovies: [],
chineseMoviesPageStart: 0,
chineseMovies: []
}
}
},
components: {
@@ -188,57 +250,150 @@ export default {
set (val) {
this.SET_SETTING(val)
}
},
filteredRecommendations () {
var filteredData = this.recommendations.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.detail.area))
filteredData = filteredData.filter(x => (this.selectedTypes.length === 0) || this.selectedTypes.includes(x.detail.type))
return filteredData
}
},
watch: {
view () {
if (this.view === 'Recommendation') {
if (this.$refs.recommendataionsWaterfall) this.$refs.recommendataionsWaterfall.resize()
if (this.$refs.recommendationsWaterfall) this.$refs.recommendationsWaterfall.resize()
}
},
sortKeyword () {
switch (this.sortKeyword) {
case '上映':
this.recommendations = this.recommendations.sort(function (a, b) {
return b.detail.year - a.detail.year
})
break
case '评分':
this.recommendations.sort(function (a, b) {
return b.rate - a.rate
})
break
case '默认':
this.recommendations.sort(function (a, b) {
return b.id - a.id
})
break
default:
break
}
recommendations: {
handler (recommendations) {
this.areas = [...new Set(recommendations.map(ele => ele.detail.area))].filter(x => x)
this.types = [...new Set(recommendations.map(ele => ele.detail.type))].filter(x => x)
this.refreshFilteredList()
},
deep: true
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING']),
detailEvent (e) {
this.detail = {
show: true,
key: e.key,
info: {
id: e.ids,
name: e.name
changeRecommendationTypeEvent () {
if (this.selectedRecommendationType === '作者推荐') {
this.recommendations = this.recommendationsDefault
} else {
if (this.selectedRecommendationType === '豆瓣热门电影') {
this.recommendations = [...this.douban.hotmovie]
}
if (this.selectedRecommendationType === '豆瓣高分电影') {
this.recommendations = [...this.douban.highRateMovie]
}
if (this.selectedRecommendationType === '豆瓣热门剧集') {
this.recommendations = [...this.douban.hotTV]
}
if (this.selectedRecommendationType === '豆瓣热门美剧') {
this.recommendations = [...this.douban.hotAmericanTVSeries]
}
if (this.selectedRecommendationType === '豆瓣热门英剧') {
this.recommendations = [...this.douban.hotBritishTVSeries]
}
if (this.selectedRecommendationType === '豆瓣热门国产剧') {
this.recommendations = [...this.douban.hotChineseTVSeries]
}
if (this.selectedRecommendationType === '豆瓣热门动漫') {
this.recommendations = [...this.douban.hotAnime]
}
if (this.selectedRecommendationType === '豆瓣热门纪录片') {
this.recommendations = [...this.douban.hotDocumentary]
}
if (this.selectedRecommendationType === '豆瓣热门综艺') {
this.recommendations = [...this.douban.hotTVShow]
}
if (this.selectedRecommendationType === '豆瓣热门动画电影') {
this.recommendations = [...this.douban.hotCartonMovie]
}
if (this.selectedRecommendationType === '豆瓣冷门佳片') {
this.recommendations = [...this.douban.goodButNotHotMovies]
}
if (this.selectedRecommendationType === '豆瓣华语电影') {
this.recommendations = [...this.douban.chineseMovies]
}
if (this.recommendations.length === 0) {
this.updateDoubanRecommendationsEvent()
}
}
},
getRecommendationsDoubanMovieOrTV (doubanUrl) {
axios.get(doubanUrl).then(res => {
if (res.data) {
res.data.subjects.forEach(element => {
const localCachedMovie = this.localCachedMovies.find(e => e.key === this.sites[0].key && e.name === element.title)
if (localCachedMovie) {
this.updateDoubanRecommendataions(localCachedMovie)
} else {
this.searchAndCacheMovie(element)
}
})
}
})
},
updateDoubanRecommendataions (movie) {
this.recommendations.push(movie)
if (this.selectedRecommendationType === '豆瓣热门电影') {
this.douban.hotmovie.push(movie)
}
if (this.selectedRecommendationType === '豆瓣高分电影') {
this.douban.highRateMovie.push(movie)
}
if (this.selectedRecommendationType === '豆瓣热门剧集') {
this.douban.hotTV.push(movie)
}
if (this.selectedRecommendationType === '豆瓣热门美剧') {
this.douban.hotAmericanTVSeries.push(movie)
}
if (this.selectedRecommendationType === '豆瓣热门英剧') {
this.douban.hotBritishTVSeries.push(movie)
}
if (this.selectedRecommendationType === '豆瓣热门国产剧') {
this.douban.hotChineseTVSeries.push(movie)
}
if (this.selectedRecommendationType === '豆瓣热门动漫') {
this.douban.hotAnime.push(movie)
}
if (this.selectedRecommendationType === '豆瓣热门纪录片') {
this.douban.hotDocumentary.push(movie)
}
if (this.selectedRecommendationType === '豆瓣热门综艺') {
this.douban.hotTVShow.push(movie)
}
if (this.selectedRecommendationType === '豆瓣热门动画电影') {
this.douban.hotCartonMovie.push(movie)
}
if (this.selectedRecommendationType === '豆瓣冷门佳片') {
this.douban.goodButNotHotMovies.push(movie)
}
if (this.selectedRecommendationType === '豆瓣华语电影') {
this.douban.chineseMovies.push(movie)
}
},
searchAndCacheMovie (element) {
zy.searchFirstDetail(this.sites[0].key, element.title).then(detailRes => {
if (detailRes) {
const doc = {
key: this.sites[0].key,
ids: detailRes.id,
site: this.sites[0],
name: detailRes.name,
detail: detailRes,
rate: element.rate
}
this.updateDoubanRecommendataions(doc)
this.localCachedMovies.push(doc)
cachedMovies.add(doc)
}
})
},
updateEvent () {
if (this.selectedRecommendationType === '作者推荐') {
this.updateAuthorRecommendataions()
} else {
this.updateDoubanRecommendationsEvent()
}
},
updateAuthorRecommendataions () {
const url = 'https://raw.githubusercontent.com/cuiocean/ZY-Player-Resources/main/Recommendations/Recommendations.json'
this.loading = true
const axios = require('axios')
axios.get(url).then(res => {
if (res.status === 200) {
if (res.data.length > 0) {
@@ -255,6 +410,128 @@ export default {
this.$message.warning('最新的推荐数据保存在Github上,请考虑使用代理或者等待下一版本内置数据更新.')
})
},
updateDoubanRecommendationsEvent () {
let doubanUrl = ''
if (this.selectedRecommendationType === '豆瓣热门电影') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=movie&tag=热门&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.hotMoviePageStart}`
this.douban.hotMoviePageStart = this.douban.hotMoviePageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣热门剧集') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=tv&tag=热门&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.hotTVPageStart}`
this.douban.hotTVPageStart = this.douban.hotTVPageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣热门美剧') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=tv&tag=美剧&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.hotCartonMoviePageStart}`
this.douban.hotCartonMoviePageStart = this.douban.hotCartonMoviePageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣热门英剧') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=tv&tag=英剧&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.hotBritishTVSeriesPageStart}`
this.douban.hotBritishTVSeriesPageStart = this.douban.hotBritishTVSeriesPageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣热门国产剧') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=tv&tag=国产剧&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.hotChineseTVSeriesPageStart}`
this.douban.hotChineseTVSeriesPageStart = this.douban.hotChineseTVSeriesPageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣高分电影') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=movie&tag=豆瓣高分&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.highRateMoviePageStart}`
this.douban.highRateMoviePageStart = this.douban.highRateMoviePageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣热门动漫') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=tv&tag=日本动画&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.hotAnimePageStart}`
this.douban.hotAnimePageStart = this.douban.hotAnimePageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣热门纪录片') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=tv&tag=纪录片&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.hotDocumentaryPageStart}`
this.douban.hotDocumentaryPageStart = this.douban.hotDocumentaryPageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣热门综艺') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=tv&tag=综艺&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.hotTVShowPageStart}`
this.douban.hotTVShowPageStart = this.douban.hotTVShowPageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣热门动画电影') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=movie&tag=动画&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.hotCartonMoviePageStart}`
this.douban.hotCartonMoviePageStart = this.douban.hotCartonMoviePageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣冷门佳片') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=movie&tag=冷门佳片&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.goodButNotHotMoviesPageStart}`
this.douban.goodButNotHotMoviesPageStart = this.douban.goodButNotHotMoviesPageStart + this.douban.page_limit
}
if (this.selectedRecommendationType === '豆瓣华语电影') {
doubanUrl = `https://movie.douban.com/j/search_subjects?type=movie&tag=华语&sort=recommend&page_limit=${this.douban.page_limit}&page_start=${this.douban.chineseMoviesPageStart}`
this.douban.chineseMoviesPageStart = this.douban.chineseMoviesPageStart + this.douban.page_limit
}
this.getRecommendationsDoubanMovieOrTV(doubanUrl)
},
toggleViewMode () {
this.setting.recommendationViewMode = this.setting.recommendationViewMode === 'picture' ? 'table' : 'picture'
if (this.setting.recommendationViewMode === 'table') {
setTimeout(() => { this.rowDrop() }, 100)
} else {
setTimeout(() => { if (this.$refs.recommendationsWaterfall) this.$refs.recommendationsWaterfall.refresh() }, 700)
}
setting.find().then(res => {
res.recommendationViewMode = this.setting.recommendationViewMode
setting.update(res)
})
},
backTop () {
if (this.setting.recommendationViewMode === 'picture') {
document.getElementById('recommendations-body').scrollTop = 0
} else {
this.$refs.recommendationsTable.bodyWrapper.scrollTop = 0
}
},
refreshFilteredList () {
if (!this.showToolbar) {
this.sortKeyword = ''
this.selectedAreas = []
this.selectedSearchClassNames = []
this.selectedYears.start = 0
this.selectedYears.end = new Date().getFullYear()
this.filteredList = this.recommendations
} else {
let filteredData = this.recommendations
filteredData = filteredData.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.detail.area))
filteredData = filteredData.filter(x => (this.selectedTypes.length === 0) || this.selectedTypes.includes(x.detail.type))
filteredData = filteredData.filter(res => res.detail.year >= this.selectedYears.start)
filteredData = filteredData.filter(res => res.detail.year <= this.selectedYears.end)
switch (this.sortKeyword) {
case '按上映年份':
filteredData.sort(function (a, b) {
return b.detail.year - a.detail.year
})
break
case '按片名':
filteredData.sort(function (a, b) {
return a.detail.name.localeCompare(b.detail.name, 'zh')
})
break
case '按更新时间':
filteredData.sort(function (a, b) {
return new Date(b.detail.last) - new Date(a.detail.last)
})
break
case '按评分':
filteredData.sort(function (a, b) {
return b.rate - a.rate
})
break
default:
break
}
this.filteredList = filteredData
}
},
detailEvent (e) {
this.detail = {
show: true,
key: e.key,
info: {
id: e.ids,
name: e.name
}
}
},
async playEvent (e) {
const db = await history.find({ site: e.key, ids: e.ids })
if (db) {
@@ -264,14 +541,6 @@ export default {
}
this.view = 'Play'
},
deleteEvent (e) {
recommendation.remove(e.id).then(res => {
if (res) {
this.$message.warning('删除失败')
}
this.getRecommendations()
})
},
shareEvent (e) {
this.share = {
show: true,
@@ -279,59 +548,54 @@ export default {
info: e.detail
}
},
downloadEvent (e) {
const key = e.key
const id = e.ids
zy.download(key, id).then(res => {
if (res && res.m3u8List) {
const list = res.m3u8List.split('#')
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
zy.detail(key, id).then(res => {
const list = [...res.m3u8List]
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
})
}
async downloadEvent (e) {
const db = await history.find({ site: e.key, ids: e.ids })
let videoFlag
if (db) videoFlag = db.videoFlag
zy.download(e.key, e.ids, videoFlag).then(res => {
clipboard.writeText(res.downloadUrls)
this.$message.success(res.info)
}).catch((err) => {
this.$message.error(err.info)
})
},
getRecommendations () {
recommendation.all().then(res => {
this.recommendations = res.sort(function (a, b) {
return b.id - a.id
})
this.getFilterData()
})
this.recommendationsDefault = []
this.changeRecommendationTypeEvent()
this.getFilterData()
},
getFilterData () {
this.types = [...new Set(this.recommendations.map(ele => ele.detail.type))].filter(x => x)
this.areas = [...new Set(this.recommendations.map(ele => ele.detail.area))].filter(x => x)
},
updateViewMode () {
setTimeout(() => { if (this.$refs.recommendataionsWaterfall) this.$refs.recommendataionsWaterfall.refresh() }, 700)
setTimeout(() => { if (this.$refs.recommendationsWaterfall) this.$refs.recommendationsWaterfall.refresh() }, 700)
setting.find().then(res => {
res.recommendationViewMode = this.setting.recommendationViewMode
setting.update(res)
})
},
getAllSites () {
sites.all().then(res => {
if (res.length > 0) {
this.sites = res.filter(item => item.isActive)
}
})
},
getCachedMovies () {
cachedMovies.all().then(res => {
this.localCachedMovies = res
})
}
},
created () {
this.getAllSites()
this.getRecommendations()
this.getCachedMovies()
},
mounted () {
addEventListener('resize', () => {
setTimeout(() => { if (this.$refs.recommendataionsWaterfall) this.$refs.recommendataionsWaterfall.resize() }, 500)
setTimeout(() => { if (this.$refs.recommendationsWaterfall) this.$refs.recommendationsWaterfall.resize() }, 500)
})
}
}

View File

@@ -5,9 +5,7 @@
<div class="info">
<a @click="linkOpen('http://zyplayer.fun/')">官网</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player')">Github</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/releases/tag/v' + pkg.version)">v{{pkg.version}}更新日志</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues/80')">常见问题</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues')">反馈建议</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/discussions/776')">软件完全免费如遇收费请立即给差评并退费</a>
<a style="color:#38dd77" @click="openUpdate()" v-show="update.find" >最新版本v{{update.version}}</a>
</div>
<div class="shortcut">
@@ -33,11 +31,11 @@
</div>
</div>
</div>
<div class="shortcut">
<div class="shortcut" title="清理缓存后图片资源需重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小">
<div class="title">缓存</div>
<div class="shortcut-box">
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="clearCache">清理视频缓存</div>
<div class="vs-placeholder vs-noAfter" @click="clearCache">清理缓存</div>
</div>
</div>
</div>
@@ -75,6 +73,10 @@
<div class="zy-input">
<input type="checkbox" v-model = "d.autocleanWhenIptvCheck" @change="updateSettingEvent"> 检测时自动清理无效源
</div>
<div class="zy-input">
<input type="checkbox" v-model = "d.autoChangeSourceWhenIptvStalling" @change="updateSettingEvent">
卡顿时自动换源换台:<input style="width:50px" type="number" min=0 v-model.number = "d.waitingTimeInSec" @change="updateSettingEvent">
</div>
</div>
</div>
<div class="site">
@@ -83,8 +85,11 @@
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="editSitesEvent">编辑源</div>
</div>
<div class="zy-input" @click="toggleExcludeRootClasses">
<input type="checkbox" v-model = "d.excludeRootClasses" @change="updateSettingEvent"> 屏蔽主分类
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="show.configDefaultParseUrlDialog = true">设置默认解析接口</div>
</div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="show.configSitesDataUrlDialog = true">设置源站接口文件</div>
</div>
</div>
</div>
@@ -103,6 +108,15 @@
</div>
</div>
</div>
<div class="site">
<div class="title">窗口及播放</div>
<div class="site-box">
<div class="zy-input">
<input type="checkbox" v-model = "d.restoreWindowPositionAndSize" @change="updateSettingEvent"> 记录并恢复窗口位置和大小
<input type="checkbox" v-model = "d.pauseWhenMinimize" @change="updateSettingEvent"> 最小化时暂停播放
</div>
</div>
</div>
<div class="theme">
<div class="title">主题</div>
<div class="theme-box">
@@ -132,13 +146,6 @@
</div>
</div>
</div>
<div class="qrcode">
<div class="title">请作者吃辣条</div>
<div class="qrcode-box">
<img class="qrcode-item" src="../assets/image/wepay-hunlongyu.png">
<img class="qrcode-item" src="../assets/image/wepay_cuiocean.jpg">
</div>
</div>
<div class="clearDB">
<span @click="clearDBEvent" class="clearBtn">软件重置</span>
<span @click="changePasswordEvent" class="clearBtn">设置密码</span>
@@ -148,6 +155,34 @@
<span>所有资源来自网上, 该软件不参与任何制作, 上传, 储存等内容, 禁止传播违法资源. 该软件仅供学习参考, 请于安装后24小时内删除.</span>
</div>
</div>
<div> <!-- 设置默认解析接口 -->
<el-dialog :visible.sync="show.configDefaultParseUrlDialog" v-if='show.configDefaultParseUrlDialog' title="设置默认解析接口" :append-to-body="true" @close="closeDialog">
<el-form label-width="45px" label-position="left">
<el-form-item label="URL:">
<el-input v-model="setting.defaultParseURL" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入解析接口地址,为空时会自动设置,重置时会自动更新默认接口地址"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="danger" @click="resetDefaultParseURL">重置</el-button>
<el-button type="primary" @click="configDefaultParseURL">确定</el-button>
</span>
</el-dialog>
</div>
<div> <!-- 设置源站接口文件 -->
<el-dialog :visible="show.configSitesDataUrlDialog" v-if='show.configSitesDataUrlDialog' title="设置源站接口文件" :append-to-body="true" @close="closeDialog">
<el-form label-width="45px" label-position="left">
<el-form-item label="URL:">
<el-input v-model="setting.sitesDataURL" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入解析接口地址,为空时会自动设置,重置时会自动更新默认接口地址"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="danger" @click="resetDefaultSitesDataURL">重置</el-button>
<el-button type="primary" @click="configSitesDataURL">确定</el-button>
</span>
</el-dialog>
</div>
<div> <!-- 输入密码页面 -->
<el-dialog :visible.sync="show.checkPasswordDialog" v-if='show.checkPasswordDialog' :append-to-body="true" @close="closeDialog" width="300px">
<el-form label-width="75px" label-position="left">
@@ -205,14 +240,11 @@
<div class="wrapper">
<div class="body">
<div class="content" v-html="update.html"></div>
<div class="progress" v-show="update.percent > 0">
<el-progress :percentage="update.percent"></el-progress>
<div class="size" style="font-size: 14px">更新包大小: {{update.size}} KB</div>
</div>
</div>
<div class="footer">
<el-button size="small" @click="cancelUpdate">取消</el-button>
<el-button size="small" v-show="!update.downloaded" @click="startUpdate">更新</el-button>
<el-button size="small" @click="closeUpdate">关闭</el-button>
<el-button size="small" v-show="update.showDownload" @click="startUpdate">更新</el-button>
<el-button size="small" v-show="!update.showDownload && !update.downloaded">正在更新...</el-button>
<el-button size="small" v-show="update.downloaded" @click="installUpdate">安装</el-button>
</div>
</div>
@@ -223,8 +255,9 @@
import { mapMutations } from 'vuex'
import pkg from '../../package.json'
import { setting, sites, shortcut } from '../lib/dexie'
import { sites as defaultSites, localKey as defaultShortcuts } from '../lib/dexie/initData'
import { shell, clipboard, remote, ipcRenderer } from 'electron'
import { localKey as defaultShortcuts } from '../lib/dexie/initData'
import { shell, clipboard, ipcRenderer } from 'electron'
const remote = require('@electron/remote')
import db from '../lib/dexie/dexie'
import zy from '../lib/site/tools'
export default {
@@ -241,7 +274,9 @@ export default {
checkPasswordDialog: false,
changePasswordDialog: false,
proxy: false,
proxyDialog: false
proxyDialog: false,
configDefaultParseUrlDialog: false,
configSitesDataUrlDialog: false
},
d: { },
latestVersion: pkg.version,
@@ -258,9 +293,8 @@ export default {
version: '',
show: false,
html: '',
percent: 0,
size: 0,
downloaded: false
downloaded: false,
showDownload: true
}
}
},
@@ -291,13 +325,29 @@ export default {
setting.find().then(res => {
this.d = res
this.setting = this.d
if (!this.setting.defaultParseURL) this.configDefaultParseURL()
if (!this.setting.sitesDataURL) this.resetDefaultSitesDataURL()
})
},
async getDefaultSites () {
const s = await setting.find()
zy.getDefaultSites(s.sitesDataURL).then(res => {
if (res && typeof res === 'string') {
const json = JSON.parse(res)
sites.clear().then(sites.bulkAdd(json))
}
if (res && typeof res === 'object') {
sites.clear().then(sites.bulkAdd(res))
}
}).catch(error => {
this.$message.error('获取云端源站失败. ' + error)
})
},
getSites () {
sites.all().then(res => {
if (res.length <= 0) {
this.$message.warning('检测到视频源未能正常加载, 即将重置源.')
sites.clear().then(sites.bulkAdd(defaultSites).then(this.getSites()))
this.getDefaultSites()
}
})
},
@@ -327,6 +377,24 @@ export default {
this.d.excludeRootClasses = !this.d.excludeRootClasses
this.updateSettingEvent()
},
async resetDefaultParseURL () {
this.setting.defaultParseURL = 'https://jx.bpba.cc/?v='
},
async configDefaultParseURL () {
if (!this.setting.defaultParseURL) await this.resetDefaultParseURL()
this.d.defaultParseURL = this.setting.defaultParseURL?.trim()
this.show.configDefaultParseUrlDialog = false
this.updateSettingEvent()
},
resetDefaultSitesDataURL () {
this.setting.sitesDataURL = 'https://raw.iqiq.io/Hunlongyu/ZY-Player-Resources/main/Sites/20220713.json'
},
configSitesDataURL () {
if (!this.setting.sitesDataURL) this.resetDefaultSitesDataURL()
this.d.sitesDataURL = this.setting.sitesDataURL
this.show.configSitesDataUrlDialog = false
this.updateSettingEvent()
},
selectLocalPlayer () {
const options = {
filters: [
@@ -337,7 +405,7 @@ export default {
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
var playerPath = result.filePaths[0].replace(/\\/g, '/')
const playerPath = result.filePaths[0].replace(/\\/g, '/')
this.$message.success('设定第三方播放器路径为:' + result.filePaths[0])
this.d.externalPlayer = playerPath
this.updateSettingEvent()
@@ -369,6 +437,8 @@ export default {
async closeDialog () {
this.show.checkPasswordDialog = false
this.show.changePasswordDialog = false
this.show.configDefaultParseUrlDialog = false
this.show.configSitesDataUrlDialog = false
if (this.show.proxyDialog) {
this.show.proxyDialog = false
this.setting.proxy.type = 'none'
@@ -498,17 +568,15 @@ export default {
openUpdate () {
this.update.show = true
},
cancelUpdate () {
closeUpdate () {
this.update.show = false
},
startUpdate () {
this.update.showDownload = false
ipcRenderer.send('downloadUpdate')
ipcRenderer.on('download-progress', (info, progress) => {
this.update.size = progress.total
this.update.percent = parseFloat(progress.percent).toFixed(1)
})
ipcRenderer.on('update-downloaded', () => {
this.update.downloaded = true
this.$message.success('更新已下载完成Mac用户须手动点击“安装”其它系统会在退出后自动安装')
})
},
installUpdate () {
@@ -527,7 +595,7 @@ export default {
}
},
created () {
this.getSites()
// this.getSites()
this.getSetting()
this.getShortcut()
this.checkUpdate()

View File

@@ -1,7 +1,7 @@
<template>
<div class="share" id="share" @click="shareClickEvent" v-on-clickaway="shareClickEvent">
<div class="share" id="share" @click="shareClickEvent" v-clickoutside="shareClickEvent">
<div class="left">
<img :src="pic" alt="" @load="picLoadEvent">
<img :src="share.info.pic" alt="">
</div>
<div class="right" id="right">
<div class="title">{{ share.info.name }}</div>
@@ -22,7 +22,7 @@ import { mapMutations } from 'vuex'
import QrcodeVue from 'qrcode.vue'
import html2canvas from 'html2canvas'
import zy from '../lib/site/tools'
import { directive as onClickaway } from 'vue-clickaway'
import Clickoutside from 'element-ui/src/utils/clickoutside'
const { clipboard, nativeImage } = require('electron')
export default {
name: 'share',
@@ -45,6 +45,14 @@ export default {
set (val) {
this.SET_SHARE(val)
}
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
}
},
watch: {
@@ -58,42 +66,43 @@ export default {
}
},
directives: {
onClickaway: onClickaway
Clickoutside
},
methods: {
...mapMutations(['SET_SHARE']),
...mapMutations(['SET_SHARE', 'SET_DetailCache']),
shareClickEvent () {
this.share = {
show: false,
info: {}
}
},
async getUrl (dl) {
const t = dl.dd._t
if (t) {
return t.split('#')[0].split('$')[1]
} else {
const id = this.share.info.ids || this.share.info.id
const res = await zy.detail(this.share.key, id)
if (res) {
return res.m3u8List[1]
}
async getUrl (index) {
const id = this.share.info.ids || this.share.info.id
const cacheKey = this.share.key + '@' + id
let res = this.DetailCache[cacheKey]
if (!this.DetailCache[cacheKey]) {
res = await zy.detail(this.share.key, id)
this.DetailCache[cacheKey] = res
}
if (res) {
const url = res.fullList[0].list[index]
return url.includes('$') ? url.split('$')[1] : url
}
},
async getDetail () {
this.loading = true
this.pic = this.share.info.pic
const url = await this.getUrl(this.share.info.dl)
const index = this.share.index || 0
const url = await this.getUrl(index)
this.link = 'http://hunlongyu.gitee.io/zy-player-web?url=' + url + '&name=' + this.share.info.name
this.loading = false
},
picLoadEvent () {
const dom = document.getElementById('share')
html2canvas(dom).then(res => {
const png = res.toDataURL('image/png')
const p = nativeImage.createFromDataURL(png)
clipboard.writeImage(p)
this.$message.success('已复制到剪贴板,快去分享吧~ 严禁传播违法资源!!!')
this.$nextTick(() => {
const dom = document.getElementById('share')
html2canvas(dom, { useCORS: true }).then(res => {
const png = res.toDataURL('image/png')
const p = nativeImage.createFromDataURL(png)
clipboard.writeImage(p)
this.$message.success('已复制到剪贴板,快去分享吧~ 严禁传播违法资源!!!')
})
})
}
},

View File

@@ -1,17 +1,56 @@
<template>
<div class="listpage" id="star">
<div class="listpage-header" id="star-header">
<el-switch v-model="setting.starViewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateViewMode"></el-switch>
<el-button @click.stop="exportFavoritesEvent" icon="el-icon-upload2" title="导出全部,自动添加扩展名">导出</el-button>
<el-button @click.stop="importFavoritesEvent" icon="el-icon-download" title="支持同时导入多个文件">导入</el-button>
<el-button @click.stop="removeSelectedItems" icon="el-icon-delete-solid">{{ multipleSelection.length === 0 ? "清空" : "删除所选" }}</el-button>
<el-button @click.stop="updateAllEvent" icon="el-icon-refresh">同步所有收藏</el-button>
<b-button-group>
<el-switch v-model="onlyShowItemsHasUpdate" active-text="有更新" inactive-text="全部" @change="refreshFilteredList"></el-switch>
<el-button @click.stop="updateAllEvent" icon="el-icon-refresh">检查更新</el-button>
</b-button-group>
</div>
<div class="toolbar" v-show="showToolbar">
<el-select v-model="selectedAreas" size="small" multiple placeholder="地区" popper-class="popper" :popper-append-to-body="false" @remove-tag="refreshFilteredList" @change="refreshFilteredList">
<el-option
v-for="item in areas"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="selectedTypes" size="small" multiple placeholder="类型" popper-class="popper" :popper-append-to-body="false" @remove-tag="refreshFilteredList" @change="refreshFilteredList">
<el-option
v-for="item in types"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-select v-model="sortKeyword" size="small" placeholder="排序" popper-class="popper" :popper-append-to-body="false" @change="refreshFilteredList">
<el-option
v-for="item in sortKeywords"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<span>
上映区间
<el-input-number size="small" v-model="selectedYears.start" :min=0 :max="new Date().getFullYear()" controls-position="right" step-strictly @change="refreshFilteredList"></el-input-number>
<el-input-number size="small" v-model="selectedYears.end" :min=0 :max="new Date().getFullYear()" controls-position="right" step-strictly @change="refreshFilteredList"></el-input-number>
</span>
</div>
<el-divider class="listpage-header-divider" content-position="right">
<el-button type="text" size="mini" @click="toggleViewMode">视图切换</el-button>
<el-button type="text" size="mini" @click='() => { showToolbar = !showToolbar; if (!showToolbar) this.refreshFilteredList() }' title="收起工具栏会重置筛选排序">{{ showToolbar ? '隐藏工具栏' : '显示工具栏' }}</el-button>
<el-button type="text" size="mini" @click="backTop">回到顶部</el-button>
</el-divider>
<div class="listpage-body" id="star-body">
<div class="show-table" id="star-table" v-if="setting.starViewMode === 'table'">
<el-table size="mini" fit height="100%" row-key="id"
ref="starTable"
:data="list"
:data="filteredList"
:cell-class-name="checkUpdate"
@row-click="detailEvent"
@sort-change="handleSortChange"
@@ -84,7 +123,7 @@
</el-table>
</div>
<div class="show-picture" id="star-picture" v-if="setting.starViewMode === 'picture'">
<Waterfall ref="starWaterfall" :list="list" :gutter="20" :width="240"
<Waterfall ref="starWaterfall" :list="filteredList" :gutter="20" :width="240"
:breakpoints="{
1200: { //当屏幕宽度小于等于1200
rowPerView: 4,
@@ -107,7 +146,7 @@
<div class="update" v-if="props.data.hasUpdate">
<span>有更新</span>
</div>
<div class="progress" v-if="props.data.index && props.data.detail && props.data.detail.m3u8List !== undefined && props.data.detail.m3u8List.length > 1">
<div class="progress" v-if="props.data.index && props.data.detail && props.data.detail.fullList[0].list !== undefined && props.data.detail.fullList[0].list.length > 1">
<span>
看至第{{ props.data.index + 1 }}集
</span>
@@ -138,9 +177,9 @@
</template>
<script>
import { mapMutations } from 'vuex'
import { star, sites, setting } from '../lib/dexie'
import { history, star, sites, setting } from '../lib/dexie'
import zy from '../lib/site/tools'
import { remote } from 'electron'
const remote = require('@electron/remote')
import fs from 'fs'
import Sortable from 'sortablejs'
import Waterfall from 'vue-waterfall-plugin'
@@ -155,7 +194,18 @@ export default {
shiftDown: false,
selectionBegin: '',
selectionEnd: '',
multipleSelection: []
multipleSelection: [],
filteredList: [],
areas: [],
types: [],
// Toolbar
showToolbar: false,
selectedAreas: [],
selectedTypes: [],
sortKeyword: '',
sortKeywords: ['按片名', '按上映年份', '按更新时间'],
selectedYears: { start: 0, end: new Date().getFullYear() },
onlyShowItemsHasUpdate: false
}
},
components: {
@@ -201,6 +251,14 @@ export default {
set (val) {
this.SET_SETTING(val)
}
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
}
},
watch: {
@@ -217,10 +275,77 @@ export default {
this.numNoUpdate = 0
this.$message.warning('未查询到任何更新')
}
},
list: {
handler (list) {
this.areas = [...new Set(list.map(ele => ele.detail.area))].filter(x => x)
this.types = [...new Set(list.map(ele => ele.detail.type))].filter(x => x)
this.refreshFilteredList()
},
deep: true
}
},
methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING']),
toggleViewMode () {
this.setting.starViewMode = this.setting.starViewMode === 'picture' ? 'table' : 'picture'
if (this.setting.starViewMode === 'table') {
setTimeout(() => { this.rowDrop() }, 100)
this.showShiftPrompt()
} else {
setTimeout(() => { if (this.$refs.starWaterfall) this.$refs.starWaterfall.refresh() }, 700)
}
setting.find().then(res => {
res.starViewMode = this.setting.starViewMode
setting.update(res)
})
},
backTop () {
if (this.setting.starViewMode === 'picture') {
document.getElementById('star-body').scrollTop = 0
} else {
this.$refs.starTable.bodyWrapper.scrollTop = 0
}
},
refreshFilteredList () {
if (!this.showToolbar) {
this.sortKeyword = ''
this.selectedAreas = []
this.selectedSearchClassNames = []
this.selectedYears.start = 0
this.selectedYears.end = new Date().getFullYear()
this.filteredList = this.list
} else {
let filteredData = this.list
filteredData = filteredData.filter(x => (this.selectedAreas.length === 0) || this.selectedAreas.includes(x.detail.area))
filteredData = filteredData.filter(x => (this.selectedTypes.length === 0) || this.selectedTypes.includes(x.detail.type))
filteredData = filteredData.filter(res => res.detail.year >= this.selectedYears.start)
filteredData = filteredData.filter(res => res.detail.year <= this.selectedYears.end)
switch (this.sortKeyword) {
case '按上映年份':
filteredData.sort(function (a, b) {
return b.detail.year - a.detail.year
})
break
case '按片名':
filteredData.sort(function (a, b) {
return a.detail.name.localeCompare(b.detail.name, 'zh')
})
break
case '按更新时间':
filteredData.sort(function (a, b) {
return new Date(b.detail.last) - new Date(a.detail.last)
})
break
default:
break
}
this.filteredList = filteredData
}
if (this.onlyShowItemsHasUpdate) {
this.filteredList = this.filteredList.filter(x => x.hasUpdate)
}
},
handleSortChange (column, prop, order) {
this.updateDatabase()
},
@@ -269,9 +394,9 @@ export default {
},
async playEvent (e) {
if (e.index) {
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: e.index }, detail: e.detail }
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: e.index } }
} else {
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: 0 }, detail: e.detail }
this.video = { key: e.key, info: { id: e.ids, name: e.name, index: 0 } }
}
if (e.hasUpdate) {
this.clearHasUpdateFlag(e)
@@ -306,21 +431,24 @@ export default {
this.getFavorites()
}
},
updateEvent (e) {
zy.detail(e.key, e.ids).then(detailRes => {
var doc = {
async updateEvent (e) {
try {
if (!this.DetailCache[e.key + '@' + e.ids]) {
this.DetailCache[e.key + '@' + e.ids] = await zy.detail(e.key, e.ids)
}
const doc = {
id: e.id,
key: e.key,
ids: e.ids,
site: e.site,
name: e.name,
detail: detailRes,
detail: this.DetailCache[e.key + '@' + e.ids],
index: e.index
}
star.get(e.id).then(resStar => {
if (!e.hasUpdate && e.detail.last !== detailRes.last) {
if (!e.hasUpdate && e.detail.last !== doc.detail.last) {
doc.hasUpdate = true
var msg = `同步"${e.name}"成功, 检查到更新。`
const msg = `检查到"${e.name}"更新。`
this.$message.success(msg)
} else {
this.numNoUpdate += 1
@@ -328,10 +456,10 @@ export default {
star.update(e.id, doc)
this.getFavorites()
})
}).catch(err => {
var msg = `同步"${e.name}"失败, 请重试`
} catch (err) {
const msg = `更新"${e.name}"失败, 请重试`
this.$message.warning(msg, err)
})
}
},
updateAllEvent () {
this.numNoUpdate = 0
@@ -339,38 +467,22 @@ export default {
this.updateEvent(e)
})
},
downloadEvent (e) {
const key = e.key
const id = e.id
zy.download(key, id).then(res => {
if (res && res.m3u8List) {
const list = res.m3u8List.split('#')
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
zy.detail(key, id).then(res => {
const list = [...res.m3u8List]
let downloadUrl = ''
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
})
}
async downloadEvent (e) {
const db = await history.find({ site: e.key, ids: e.ids })
let videoFlag
if (db) videoFlag = db.videoFlag
zy.download(e.key, e.ids, videoFlag).then(res => {
clipboard.writeText(res.downloadUrls)
this.$message.success(res.info)
}).catch((err) => {
this.$message.error(err.info)
})
},
getSiteName (row) {
if (row.site) {
return row.site.name
} else {
var site = this.sites.find(e => e.key === row.key)
const site = this.sites.find(e => e.key === row.key)
if (site) {
return site.name
}
@@ -422,15 +534,25 @@ export default {
}
remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) {
var starList = Array.from(this.list)
var id = this.list.length + 1
const starList = Array.from(this.list)
let id = this.list.length + 1
result.filePaths.forEach(file => {
var str = fs.readFileSync(file)
const str = fs.readFileSync(file)
const json = JSON.parse(str)
json.reverse().forEach(ele => {
const starExists = starList.some(x => x.key === ele.key && x.ids === ele.ids)
if (!starExists) {
var doc = {
const newDetail = {
director: ele.director,
actor: ele.actor,
type: ele.type,
area: ele.area,
lang: ele.lang,
year: ele.year,
last: ele.last,
note: ele.note
}
const doc = {
id: id,
key: ele.key,
ids: ele.ids,
@@ -439,16 +561,7 @@ export default {
hasUpdate: ele.hasUpdate,
index: ele.index,
rate: ele.rate,
detail: ele.detail === undefined ? {
director: ele.director,
actor: ele.actor,
type: ele.type,
area: ele.area,
lang: ele.lang,
year: ele.year,
last: ele.last,
note: ele.note
} : ele.detail
detail: ele.detail === undefined ? newDetail : ele.detail
}
id += 1
starList.push(doc)
@@ -472,7 +585,7 @@ export default {
updateDatabase () {
this.syncTableData()
star.clear().then(res => {
var id = this.list.length
let id = this.list.length
this.list.forEach(ele => {
ele.id = id
id -= 1
@@ -481,6 +594,7 @@ export default {
})
},
rowDrop () {
if (!document.getElementById('star-table')) return
const tbody = document.getElementById('star-table').querySelector('.el-table__body-wrapper tbody')
const _this = this
Sortable.create(tbody, {
@@ -491,18 +605,6 @@ export default {
}
})
},
updateViewMode () {
if (this.setting.starViewMode === 'table') {
setTimeout(() => { this.rowDrop() }, 100)
this.showShiftPrompt()
} else {
setTimeout(() => { if (this.$refs.starWaterfall) this.$refs.starWaterfall.refresh() }, 700)
}
setting.find().then(res => {
res.starViewMode = this.setting.starViewMode
setting.update(res)
})
},
showShiftPrompt () {
if (this.setting.shiftTooltipLimitTimes === undefined) this.setting.shiftTooltipLimitTimes = 5
if (this.setting.shiftTooltipLimitTimes) {

View File

@@ -10,7 +10,7 @@ import Share from './Share'
import History from './History'
import EditSites from './EditSites'
import IPTV from './IPTV'
import Recommendation from './Recommendation'
// import Recommendation from './Recommendation'
export default {
registerComponents () {
Vue.component('Aside', Aside)
@@ -24,6 +24,6 @@ export default {
Vue.component('History', History)
Vue.component('EditSites', EditSites)
Vue.component('IPTV', IPTV)
Vue.component('Recommendation', Recommendation)
// Vue.component('Recommendation', Recommendation)
}
}

View File

@@ -0,0 +1,28 @@
import db from './dexie'
const { cachedMovies } = db
export default {
async add (doc) {
return await cachedMovies.add(doc)
},
async bulkAdd (doc) {
return await cachedMovies.bulkAdd(doc)
},
async find (doc) {
return await cachedMovies.where(doc).first()
},
async update (id, docs) {
return await cachedMovies.update(id, docs)
},
async all () {
return await cachedMovies.toArray()
},
async remove (id) {
return await cachedMovies.delete(id)
},
async get (id) {
return await cachedMovies.get(id)
},
async clear () {
return await cachedMovies.clear()
}
}

View File

@@ -1,8 +1,7 @@
import Dexie from 'dexie'
import { setting, sites, localKey, iptv, recommendations } from './initData'
import { sites, localKey, iptv, recommendations, iniSetting } from './initData'
const db = new Dexie('zy')
db.version(4).stores({
search: '++id, keywords',
setting: 'id, theme, site, shortcut, view, volume, externalPlayer, searchGroup, excludeRootClasses, excludeR18Films, forwardTimeInSec, starViewMode, recommandationViewMode, searchViewMode, password, proxy, allowPassWhenIptvCheck, autocleanWhenIptvCheck',
@@ -16,6 +15,7 @@ db.version(4).stores({
channelList: '++id, name, prefer, channels, group, isActive'
})
// 开发和稳定版同一版本号会有不同的数据库
// 参考https://github.com/dfahlander/Dexie.js/releases/tag/v3.0.0-alpha.3 upgrade可以改变主键和表名了
// https://dexie.org/docs/Version/Version.stores()
// https://dexie.org/docs/Version/Version.upgrade()
@@ -30,8 +30,87 @@ db.version(6).stores({
await tx.shortcut.bulkAdd(localKey)
})
db.version(7).stores({
sites: '++id, key, name, api, download, jiexiUrl, isActive, group',
history: '++id, [site+ids], name, type, year, index, time, duration, detail, onlinePlay'
}).upgrade(trans => {
trans.sites.toCollection().modify(site => {
site.jiexiUrl = ''
})
trans.history.toCollection().modify(record => {
record.detail.fullList = [].concat(record.detail.m3u8List)
delete record.detail.m3u8List
})
trans.star.toCollection().modify(favorite => {
favorite.detail.fullList = [].concat(favorite.detail.m3u8List)
delete favorite.detail.m3u8List
})
})
db.version(8).stores({
}).upgrade(trans => {
trans.sites.toCollection().modify(site => {
if (site.api.includes('7kjx.com')) site.jiexiUrl = 'default'
})
trans.setting.toCollection().modify(setting => {
setting.waitingTimeInSec = 15
setting.autoChangeSourceWhenIptvStalling = true
})
})
db.version(9).stores({
history: '++id, [site+ids], name, type, year, index, time, duration, detail, onlinePlay, hasUpdate'
})
db.version(10).stores({
setting: 'id, theme, shortcut, view, volume, externalPlayer, searchGroup, excludeRootClasses, excludeR18Films, forwardTimeInSec, starViewMode, recommandationViewMode, searchViewMode, password, proxy, allowPassWhenIptvCheck, autocleanWhenIptvCheck, rootClassFilter, r18ClassFilter, classFilter'
}).upgrade(trans => {
trans.setting.toCollection().modify(setting => {
delete setting.site
setting.rootClassFilter = ['电影', '电影片', '电视剧', '连续剧', '综艺', '动漫']
setting.r18ClassFilter = ['伦理', '论理', '倫理', '福利', '激情', '理论', '写真', '情色', '美女', '街拍', '赤足', '性感', '里番', 'VIP']
setting.classFilter = ['电影', '电影片', '电视剧', '连续剧', '综艺', '动漫', '伦理', '论理', '倫理', '福利', '激情', '理论', '写真', '情色', '美女', '街拍', '赤足', '性感', '里番', 'VIP']
})
})
db.version(11).stores({
setting: 'id, theme, shortcut, view, volume, externalPlayer, searchGroup, excludeRootClasses, excludeR18Films, forwardTimeInSec, starViewMode, recommandationViewMode,' +
'searchViewMode, password, proxy, allowPassWhenIptvCheck, autocleanWhenIptvCheck, rootClassFilter, r18ClassFilter, classFilter, restoreWindowPositionAndSize, windowPositionAndSize, pauseWhenMinimize',
cachedMovies: '++id, [key+ids], site, name, detail, index, rate, hasUpdate'
}).upgrade(trans => {
trans.setting.toCollection().modify(setting => {
setting.restoreWindowPositionAndSize = false
setting.windowPositionAndSize = {
x: 0,
y: 0,
width: 1080,
height: 720
}
setting.pauseWhenMinimize = false
})
})
db.version(11).stores({
setting: 'id, theme, shortcut, view, volume, externalPlayer, searchGroup, excludeRootClasses, excludeR18Films, forwardTimeInSec, starViewMode, recommandationViewMode,' +
'searchViewMode, password, proxy, allowPassWhenIptvCheck, autocleanWhenIptvCheck, rootClassFilter, r18ClassFilter, classFilter, restoreWindowPositionAndSize,' +
'windowPositionAndSize, pauseWhenMinimize, sitesDataURL, defaultParseURL'
}).upgrade(trans => {
trans.setting.toCollection().modify(setting => {
setting.sitesDataURL = 'https://raw.iqiq.io/Hunlongyu/ZY-Player-Resources/main/Sites/20220713.json'
setting.defaultParseURL = 'https://jx.bpba.cc/?v='
})
})
db.version(12).stores({
sites: '++id, key, name, api, download, jiexiUrl, isActive, group, reverseOrder'
}).upgrade(trans => {
trans.sites.toCollection().modify(site => {
site.reverseOrder = false
})
})
db.on('populate', () => {
db.setting.bulkAdd(setting)
db.setting.bulkAdd(iniSetting)
db.sites.bulkAdd(sites)
db.shortcut.bulkAdd(localKey)
db.iptv.bulkAdd(iptv)

View File

@@ -19,6 +19,9 @@ export default {
async remove (id) {
return await history.delete(id)
},
async get (id) {
return await history.get(id)
},
async clear () {
return await history.clear()
}

View File

@@ -8,6 +8,7 @@ import search from './search'
import iptv from './iptv'
import channelList from './channelList'
import recommendation from './recommendation'
import cachedMovies from './cachedMovies'
export {
history,
@@ -19,5 +20,6 @@ export {
iptv,
channelList,
search,
recommendation
recommendation,
cachedMovies
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,162 +1 @@
[
{
"id": 1,
"key": "mahuazy",
"name": "麻花资源",
"api": "http://www.mhapi123.com/inc/ldg_api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 2,
"key": "1886zy",
"name": "1886 资源",
"api": "http://cj.1886zy.co/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 3,
"key": "123ku",
"name": "123 资源",
"api": "http://cj.123ku2.com:12315/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 4,
"key": "subo988",
"name": "速播资源站",
"api": "https://www.subo988.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 5,
"key": "88zyw",
"name": "88 影视资源站",
"api": "http://www.88zyw.net/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"key": "zuidazy",
"id": 6,
"name": "最大资源网",
"api": "http://www.zdziyuan.com/inc/ldg_sea.php",
"download": "http://www.zdziyuan.com/inc/apidown.php",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"key": "mbo",
"id": 7,
"name": "秒播资源",
"api": "http://caiji.mb77.vip/inc/seacmsapi.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 8,
"key": "apibdzy",
"name": "百度云资源",
"api": "https://api.apibdzy.com/api.php/provide/vod/at/xml",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 9,
"key": "okzy",
"name": "OK 资源网",
"api": "http://cj.okzy.tv/inc/api.php",
"download": "http://cj.okzy.tv/inc/apidown.php",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 10,
"key": "kuyunzy",
"name": "酷云资源",
"api": "http://caiji.kuyun98.com/inc/ldg_api.php",
"download": "http://caiji.kuyun98.com/inc/apidown.php",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 11,
"key": "kubozy",
"name": "酷播资源",
"api": "http://api.kbzyapi.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 12,
"key": "yongjiuzy",
"name": "永久资源",
"api": "http://cj.yongjiuzyw.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 13,
"key": "rrzy",
"name": "人人资源",
"api": "https://www.rrzyw.cc/api.php/provide/vod/from/rrm3u8/at/xml/",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 14,
"key": "bbkdj",
"name": "步步高顶尖资源网",
"api": "http://api.bbkdj.com/api",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 15,
"key": "zuixinzy",
"name": "最新资源",
"api": "http://api.zuixinapi.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 16,
"key": "209zy",
"name": "209 资源",
"api": "http://cj.1156zy.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
}
]
[]

View File

@@ -0,0 +1,82 @@
[{
"id": 0,
"theme": "light",
"shortcut": true,
"view": "picture",
"externalPlayer": "",
"searchGroup": "全站",
"excludeRootClasses": true,
"excludeR18Films": true,
"forwardTimeInSec": 5,
"waitingTimeInSec": 15,
"starViewMode": "picture",
"recommendationViewMode": "picture",
"historyViewMode": "picture",
"searchViewMode": "picture",
"password": "",
"proxy": {
"type": "none",
"scheme": "",
"url": "",
"port": ""
},
"allowPassWhenIptvCheck": true,
"autocleanWhenIptvCheck": false,
"autoChangeSourceWhenIptvStalling": true,
"shortcutModified": false,
"sitesDataURL": "https://raw.iqiq.io/Hunlongyu/ZY-Player-Resources/main/Sites/20220713.json",
"rootClassFilter": [
"电影",
"电影片",
"电视剧",
"连续剧",
"综艺",
"动漫"
],
"r18ClassFilter": [
"伦理",
"论理",
"倫理",
"福利",
"激情",
"理论",
"写真",
"情色",
"美女",
"街拍",
"赤足",
"性感",
"里番",
"VIP"
],
"classFilter": [
"电影",
"电影片",
"电视剧",
"连续剧",
"综艺",
"动漫",
"伦理",
"论理",
"倫理",
"福利",
"激情",
"理论",
"写真",
"情色",
"美女",
"街拍",
"赤足",
"性感",
"里番",
"VIP"
],
"restoreWindowPositionAndSize": false,
"windowPositionAndSize": {
"x": 0,
"y": 0,
"width": 1080,
"height": 720
},
"pauseWhenMinimize": false
}]

View File

@@ -0,0 +1,112 @@
[
{
"name": "playAndPause",
"desc": "播放或暂停",
"key": "space"
},
{
"name": "forward",
"desc": "快进",
"key": "right"
},
{
"name": "back",
"desc": "快退",
"key": "left"
},
{
"name": "volumeUp",
"desc": "音量调高",
"key": "up"
},
{
"name": "volumeDown",
"desc": "音量调低",
"key": "down"
},
{
"name": "mute",
"desc": "静音",
"key": "m"
},
{
"name": "top",
"desc": "置顶或退出置顶",
"key": "t"
},
{
"name": "fullscreen",
"desc": "进入或退出全屏",
"key": "f"
},
{
"name": "escape",
"desc": "退出全屏/精简模式",
"key": "esc"
},
{
"name": "next",
"desc": "下一集",
"key": "alt+right"
},
{
"name": "prev",
"desc": "上一集",
"key": "alt+left"
},
{
"name": "home",
"desc": "跳到视频开始位置",
"key": "home"
},
{
"name": "end",
"desc": "跳到视频结束位置",
"key": "end"
},
{
"name": "startPosition",
"desc": "标记片头",
"key": "ctrl+home"
},
{
"name": "endPosition",
"desc": "标记片尾",
"key": "ctrl+end"
},
{
"name": "clearPosition",
"desc": "清除标记",
"key": "ctrl+del"
},
{
"name": "opacityUp",
"desc": "透明度调高",
"key": "alt+up"
},
{
"name": "opacityDown",
"desc": "透明度调低",
"key": "alt+down"
},
{
"name": "playbackRateUp",
"desc": "播放倍速加快",
"key": "pageup"
},
{
"name": "playbackRateDown",
"desc": "播放倍速减慢",
"key": "pagedown"
},
{
"name": "mini",
"desc": "进入或退出mini模式",
"key": "alt+m"
},
{
"name": "resetMini",
"desc": "重置mini窗口",
"key": "ctrl+0"
}
]

View File

@@ -1,143 +1,3 @@
const setting = [
{
id: 0,
theme: 'light',
shortcut: true,
view: 'picture',
externalPlayer: '',
searchGroup: '全站',
excludeRootClasses: true,
excludeR18Films: true,
forwardTimeInSec: 5,
starViewMode: 'picture',
recommendationViewMode: 'picture',
historyViewMode: 'picture',
searchViewMode: 'picture',
password: '',
proxy: {
type: 'none',
scheme: '',
url: '',
port: ''
},
allowPassWhenIptvCheck: true,
autocleanWhenIptvCheck: false
}
]
const localKey = [
{
name: 'playAndPause',
desc: '播放或暂停',
key: 'space'
},
{
name: 'forward',
desc: '快进',
key: 'right'
},
{
name: 'back',
desc: '快退',
key: 'left'
},
{
name: 'volumeUp',
desc: '音量调高',
key: 'up'
},
{
name: 'volumeDown',
desc: '音量调低',
key: 'down'
},
{
name: 'mute',
desc: '静音',
key: 'm'
},
{
name: 'top',
desc: '置顶或退出置顶',
key: 't'
},
{
name: 'fullscreen',
desc: '进入或退出全屏',
key: 'f'
},
{
name: 'escape',
desc: '退出全屏/精简模式',
key: 'esc'
},
{
name: 'next',
desc: '下一集',
key: 'alt+right'
},
{
name: 'prev',
desc: '上一集',
key: 'alt+left'
},
{
name: 'home',
desc: '跳到视频开始位置',
key: 'home'
},
{
name: 'end',
desc: '跳到视频结束位置',
key: 'end'
},
{
name: 'startPosition',
desc: '标记片头',
key: 'ctrl+home'
},
{
name: 'endPosition',
desc: '标记片尾',
key: 'ctrl+end'
},
{
name: 'clearPosition',
desc: '清除标记',
key: 'ctrl+del'
},
{
name: 'opacityUp',
desc: '透明度调高',
key: 'alt+up'
},
{
name: 'opacityDown',
desc: '透明度调低',
key: 'alt+down'
},
{
name: 'playbackRateUp',
desc: '播放倍速加快',
key: 'pageup'
},
{
name: 'playbackRateDown',
desc: '播放倍速减慢',
key: 'pagedown'
},
{
name: 'mini',
desc: '进入或退出mini模式',
key: 'alt+m'
},
{
name: 'resetMini',
desc: '重置mini窗口',
key: 'ctrl+0'
}
]
const getSite = (key) => {
for (const i of sites) {
if (key === i.key) {
@@ -149,11 +9,13 @@ const getSite = (key) => {
const sites = require('./iniData/Sites.json')
const iptv = require('./iniData/Iptv.json')
const recommendations = require('./iniData/Recommendations.json')
const iniSetting = require('./iniData/iniSetting.json')
const localKey = require('./iniData/localKey.json')
export {
setting,
sites,
iptv,
recommendations,
iniSetting,
localKey,
getSite
}

View File

@@ -5,6 +5,12 @@ export default {
async find () {
return await setting.get({ id: 0 })
},
async bulkAdd (doc) {
return await setting.bulkAdd(doc)
},
async add (doc) {
return await setting.add(doc)
},
async update (docs) {
return await setting.update(0, docs)
}

View File

@@ -1,6 +1,8 @@
import Vue from 'vue'
import { Message, Button, Table, TableColumn, Tag, Input, InputNumber, Dialog, Form, FormItem, Switch, Select, Option, Checkbox, Autocomplete, Col, Tree, Divider, Progress } from 'element-ui'
import { Message, Button, Table, TableColumn, Tag, Input, InputNumber, Dialog, Form, FormItem, Switch, Select, Option, Checkbox, Autocomplete, Col, Tree, Divider, Progress, MessageBox } from 'element-ui'
import Plugin from 'v-fit-columns'
import { ButtonGroupPlugin } from 'bootstrap-vue'
Vue.use(ButtonGroupPlugin)
Vue.use(Button)
Vue.use(Col)
Vue.use(Table)
@@ -21,3 +23,4 @@ Vue.use(Tree)
Vue.use(Divider)
Vue.use(Progress)
Vue.prototype.$message = Message
Vue.prototype.$msgbox = MessageBox

View File

@@ -35,29 +35,29 @@ const onlineVideo = {
},
playVideoOnBde4 (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://bde4.com/search/${videoName}`
const url = `https://bde4.com/search/${videoName}`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('div.search-list')
var searchResult = $(e).find('div>div>div>div>a').toArray()
const e = $('div.search-list')
const searchResult = $(e).find('div>div>div>div>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
const detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).attr('title')
const title = $(searchResult[0]).attr('title')
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
var detailPageFullLink = 'https://bde4.com/' + detailPageLink
const detailPageFullLink = 'https://bde4.com/' + detailPageLink
// 解析详情页面
axios.get(detailPageFullLink).then(res => {
const $ = cheerio.load(res.data)
var e = $('div.info1')
var videoList = $(e).find('a').toArray()
var videoFullLink = detailPageFullLink
const d = $('div.info1')
const videoList = $(d).find('a').toArray()
let videoFullLink = detailPageFullLink
// 获取index视频链接
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
const indexVideoLink = $(videoList[videoIndex]).attr('href')
if (indexVideoLink.includes('.htm')) {
videoFullLink = 'https://bde4.com' + indexVideoLink
}
@@ -69,31 +69,31 @@ const onlineVideo = {
},
playVideoOnK1080 (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://k1080.net/vodsearch123/-------------.html?wd=${videoName}&submit=`
const url = `https://k1080.net/vodsearch123/-------------.html?wd=${videoName}&submit=`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('#searchList')
var searchResult = $(e).find('li>div>a').toArray()
const e = $('#searchList')
const searchResult = $(e).find('li>div>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
const detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).attr('title')
const title = $(searchResult[0]).attr('title')
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = 'https://k1080.net' + detailPageLink
const detailPageFullLink = 'https://k1080.net' + detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('#playlist1')
const d = $('#playlist1')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
var videoFullLink = detailPageFullLink
const videoList = $(d).find('div>ul>li>a').toArray()
let videoFullLink = detailPageFullLink
// 获取index视频链接
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
const indexVideoLink = $(videoList[videoIndex]).attr('href')
if (indexVideoLink.includes('.htm')) {
videoFullLink = 'https://k1080.net' + indexVideoLink
}
@@ -105,31 +105,31 @@ const onlineVideo = {
},
playVideoOnSubaibai (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://www.subaibai.com/xssearch?q=${videoName}`
const url = `https://www.subaibai.com/xssearch?q=${videoName}`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('div.search_list')
var searchResult = $(e).find('div>ul>li>h3>a').toArray()
const e = $('div.search_list')
const searchResult = $(e).find('div>ul>li>h3>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
const detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).text()
const title = $(searchResult[0]).text()
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = detailPageLink
const detailPageFullLink = detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('div.paly_list_btn')
const d = $('div.paly_list_btn')
// 获取所有视频链接
var videoList = $(e).find('a').toArray()
const videoList = $(d).find('a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
let videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
const indexVideoLink = $(videoList[videoIndex]).attr('href')
if (indexVideoLink.includes('.htm')) {
videoFullLink = indexVideoLink
}
@@ -141,31 +141,31 @@ const onlineVideo = {
},
playVideoOnYhdm (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `http://www.yhdm.tv/search/${videoName}`
const url = `http://www.yhdm.tv/search/${videoName}`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('div.lpic')
var searchResult = $(e).find('div>ul>li>h2>a').toArray()
const e = $('div.lpic')
const searchResult = $(e).find('div>ul>li>h2>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
const detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).attr('title')
const title = $(searchResult[0]).attr('title')
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = 'http://www.yhdm.tv/' + detailPageLink
const detailPageFullLink = 'http://www.yhdm.tv/' + detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('div.movurl')
const d = $('div.movurl')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
const videoList = $(d).find('div>ul>li>a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
let videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
const indexVideoLink = $(videoList[videoIndex]).attr('href')
if (indexVideoLink.includes('.htm')) {
videoFullLink = 'http://www.yhdm.tv/' + indexVideoLink
}
@@ -177,31 +177,31 @@ const onlineVideo = {
},
playVideoOndmdm2020 (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `http://www.dmdm2020.com/dongmansearch.html?wd=${videoName}&submit=`
const url = `http://www.dmdm2020.com/dongmansearch.html?wd=${videoName}&submit=`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('#searchList')
var searchResult = $(e).find('ul>li>div>h4>a').toArray()
const e = $('#searchList')
const searchResult = $(e).find('ul>li>div>h4>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
const detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).text()
const title = $(searchResult[0]).text()
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = 'http://www.dmdm2020.com' + detailPageLink
const detailPageFullLink = 'http://www.dmdm2020.com' + detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('#playlist1')
const d = $('#playlist1')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
const videoList = $(d).find('div>ul>li>a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
let videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
const indexVideoLink = $(videoList[videoIndex]).attr('href')
if (indexVideoLink.includes('.htm')) {
videoFullLink = 'http://www.dmdm2020.com' + indexVideoLink
}
@@ -213,31 +213,31 @@ const onlineVideo = {
},
playVideoOnSyrme (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://syrme.top/searchs?q=${videoName}`
const url = `https://syrme.top/searchs?q=${videoName}`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('ul.MovieList')
var searchResult = $(e).find('ul>li>article>a').toArray()
const e = $('ul.MovieList')
const searchResult = $(e).find('ul>li>article>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
const detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).find('a>h2').text()
const title = $(searchResult[0]).find('a>h2').text()
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = 'https://syrme.top' + detailPageLink
const detailPageFullLink = 'https://syrme.top' + detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('#categories-2')
const d = $('#categories-2')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
const videoList = $(d).find('div>ul>li>a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
let videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
const indexVideoLink = $(videoList[videoIndex]).attr('href')
videoFullLink = 'https://syrme.top' + indexVideoLink
}
open(videoFullLink)
@@ -247,31 +247,31 @@ const onlineVideo = {
},
playVideoOnJpysvip (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://www.jpysvip.net/vodsearch/-------------.html?wd=${videoName}&submit=`
const url = `https://www.jpysvip.net/vodsearch/-------------.html?wd=${videoName}&submit=`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('#searchList')
var searchResult = $(e).find('ul>li>div>a').toArray()
const e = $('#searchList')
const searchResult = $(e).find('ul>li>div>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
const detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).attr('title')
const title = $(searchResult[0]).attr('title')
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = 'https://www.jpysvip.net' + detailPageLink
const detailPageFullLink = 'https://www.jpysvip.net' + detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('#playlist1')
const d = $('#playlist1')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
const videoList = $(d).find('div>ul>li>a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
let videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
const indexVideoLink = $(videoList[videoIndex]).attr('href')
videoFullLink = 'https://www.jpysvip.net/' + indexVideoLink
}
open(videoFullLink)
@@ -281,31 +281,31 @@ const onlineVideo = {
},
playVideoOnXhkan (videoName, videoIndex) {
videoName = videoName.replace(/\s/g, '')
var url = `https://www.xhkan.com/vodsearch.html?wd=${videoName}&submit=`
const url = `https://www.xhkan.com/vodsearch.html?wd=${videoName}&submit=`
axios.get(url).then(res => {
const $ = cheerio.load(res.data)
var e = $('#searchList')
var searchResult = $(e).find('ul>li>div>a').toArray()
const e = $('#searchList')
const searchResult = $(e).find('ul>li>div>a').toArray()
// 获取第一个搜索结果的视频链接
var detailPageLink = $(searchResult[0]).attr('href')
const detailPageLink = $(searchResult[0]).attr('href')
// 获取第一个搜索结果的title
var title = $(searchResult[0]).attr('title')
const title = $(searchResult[0]).attr('title')
if (title === null || title === undefined || !title.replace(/\s/g, '').includes(videoName)) {
// 如果第一个搜索结果不符合,打开搜索页面
open(url)
} else {
// 解析详情页面
var detailPageFullLink = detailPageLink
const detailPageFullLink = detailPageLink
axios.get(detailPageFullLink).then(res2 => {
const $ = cheerio.load(res2.data)
// 获取playlist1
var e = $('#playlist1')
const d = $('#playlist1')
// 获取所有视频链接
var videoList = $(e).find('div>ul>li>a').toArray()
const videoList = $(d).find('div>ul>li>a').toArray()
// 获取index视频链接
var videoFullLink = detailPageFullLink
let videoFullLink = detailPageFullLink
if (videoIndex < videoList.length) {
var indexVideoLink = $(videoList[videoIndex]).attr('href')
const indexVideoLink = $(videoList[videoIndex]).attr('href')
videoFullLink = indexVideoLink
}
open(videoFullLink)

View File

@@ -3,36 +3,55 @@ import axios from 'axios'
import parser from 'fast-xml-parser'
import cheerio from 'cheerio'
import { Parser as M3u8Parser } from 'm3u8-parser'
// import FLVDemuxer from 'xgplayer-flv.js/src/flv/demux/flv-demuxer.js'
import SocksProxyAgent from 'socks-proxy-agent'
// axios使用系统代理 https://evandontje.com/2020/04/02/automatic-system-proxy-configuration-for-electron-applications/
// xgplayer使用chromium代理设置浏览器又默认使用系统代理 https://www.chromium.org/developers/design-documents/network-settings
// 要在设置中添加代理设置可参考https://stackoverflow.com/questions/37393248/how-connect-to-proxy-in-electron-webview
var http = require('http')
var https = require('http')
const { remote } = require('electron')
var win = remote.getCurrentWindow()
var session = win.webContents.session
var ElectronProxyAgent = require('electron-proxy-agent')
const http = require('http')
const https = require('http')
const remote = require('@electron/remote')
const win = remote.getCurrentWindow()
const session = win.webContents.session
const ElectronProxyAgent = require('electron-proxy-agent')
const URL = require('url')
const request = require('request')
let proxyURL
// 取消axios请求 浅析cancelToken https://juejin.cn/post/6844904168277147661 https://masteringjs.io/tutorials/axios/cancel
// const source = axios.CancelToken.source()
// const cancelToken = source.token
// 请求超时时限
axios.defaults.timeout = 10000 // 可能使用代理,增长超时
// axios.defaults.timeout = 10000 // 可能使用代理,增长超时
const TIMEOUT = 20000
// 重试次数,共请求3
axios.defaults.retry = 2
// 重试次数,共请求2
axios.defaults.retry = 1
// 请求的间隙
axios.defaults.retryDelay = 1000
// 使用请求拦截器动态调整超时
axios.interceptors.request.use(function (config) {
if (config.__retryCount === undefined) {
config.timeout = TIMEOUT
} else {
config.timeout = TIMEOUT * (config.__retryCount + 1)
}
return config
}, function (err) {
return Promise.reject(err)
})
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做些事
if (response.status && response.status === 200 && response.request.responseURL.includes('api.php') && !response.data.startsWith('<?xml')) {
}
return response
}, function (err) { // 请求错误时做些事
// 请求超时的之后,抛出 err.code = ECONNABORTED的错误..错误信息是 timeout of xxx ms exceeded
if (err.code === 'ECONNABORTED' && err.message.indexOf('timeout') !== -1) {
var config = err.config
const config = err.config
config.__retryCount = config.__retryCount || 0
if (config.__retryCount >= config.retry) {
@@ -42,7 +61,7 @@ axios.interceptors.response.use(function (response) {
config.__retryCount += 1
var backoff = new Promise(function (resolve) {
const backoff = new Promise(function (resolve) {
setTimeout(function () {
resolve()
}, config.retryDelay || 1)
@@ -89,16 +108,19 @@ const zy = {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const url = res.api
axios.post(url).then(res => {
axios.get(url).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
const jsondata = json?.rss === undefined ? json : json.rss
if (!jsondata?.class || !jsondata?.list) resolve()
const arr = []
if (jsondata.class) {
// 有些网站返回的分类名里会含有一串包含在{}内的字符串,移除掉
const regex = /\{.*\}/i
for (const i of jsondata.class.ty) {
const j = {
tid: i._id,
name: i._t
name: i._t.replace(regex, '')
}
arr.push(j)
}
@@ -134,12 +156,16 @@ const zy = {
} else {
url = `${site.api}?ac=videolist&pg=${pg}`
}
axios.post(url).then(async res => {
axios.get(url).then(async res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
const videoList = jsondata.list.video
resolve(videoList)
if (videoList && videoList.length) {
resolve(videoList)
} else {
resolve([])
}
}).catch(err => {
reject(err)
})
@@ -162,8 +188,8 @@ const zy = {
} else {
url = `${site.api}?ac=videolist`
}
axios.post(url).then(async res => {
const data = res.data
axios.get(url).then(async res => {
const data = res.data.match(/<list [^>]*>/)[0] + '</list>' // 某些源站不含页码时获取到的数据parser无法解析
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
const pg = {
@@ -189,15 +215,59 @@ const zy = {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const site = res
wd = encodeURI(wd)
var url = `${site.api}?wd=${wd}`
axios.post(url, { timeout: 3000 }).then(res => {
const url = `${site.api}?wd=${encodeURI(wd)}`
axios.get(url, { timeout: 3000 }).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
const jsondata = json?.rss === undefined ? json : json.rss
if (json && jsondata && jsondata.list) {
const videoList = jsondata.list.video
resolve(videoList)
let videoList = jsondata.list.video
if (Object.prototype.toString.call(videoList) === '[object Object]') videoList = [].concat(videoList)
videoList = videoList?.filter(e => e.name.toLowerCase().includes(wd.toLowerCase()))
if (videoList?.length) {
resolve(videoList)
} else {
resolve()
}
} else {
resolve()
}
}).catch(err => {
reject(err)
})
}).catch(err => {
reject(err)
})
})
},
/**
* 搜索资源详情
* @param {*} key 资源网 key
* @param {*} wd 搜索关键字
* @returns
*/
searchFirstDetail (key, wd) {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const site = res
const url = `${site.api}?wd=${encodeURI(wd)}`
axios.get(url, { timeout: 3000 }).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json?.rss === undefined ? json : json.rss
if (json && jsondata && jsondata.list) {
let videoList = jsondata.list.video
if (Object.prototype.toString.call(videoList) === '[object Object]') videoList = [].concat(videoList)
videoList = videoList?.filter(e => e.name.toLowerCase().includes(wd.toLowerCase()))
if (videoList?.length) {
this.detail(key, videoList[0].id).then(detailRes => {
resolve(detailRes)
})
} else {
resolve()
}
} else {
resolve()
}
}).catch(err => {
reject(err)
@@ -217,26 +287,56 @@ const zy = {
return new Promise((resolve, reject) => {
this.getSite(key).then(res => {
const url = `${res.api}?ac=videolist&ids=${id}`
axios.post(url).then(res => {
axios.get(url).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
const videoList = jsondata.list.video
// Parse m3u8List
var m3u8List = []
const jsondata = json?.rss === undefined ? json : json.rss
const videoList = jsondata?.list?.video
if (!videoList) resolve()
// Parse video lists
let fullList = []
let index = 0
const supportedFormats = ['m3u8', 'mp4']
const dd = videoList.dl.dd
const type = Object.prototype.toString.call(dd)
if (type === '[object Array]') {
for (const i of dd) {
// 如果含有多个视频列表的话, 仅获取m3u8列表
if (i._flag.includes('m3u8')) {
m3u8List = i._t.split('#')
i._t = i._t.replace(/\$+/g, '$')
const ext = Array.from(new Set(...i._t.split('#').map(e => e.includes('$') ? e.split('$')[1].match(/\.\w+?$/) : e.match(/\.\w+?$/)))).map(e => e.slice(1))
if (ext.length && ext.length <= supportedFormats.length && ext.every(e => supportedFormats.includes(e))) {
if (ext.length === 1) {
i._flag = ext[0]
} else {
i._flag = index ? 'ZY支持-' + index : 'ZY支持'
index++
}
}
fullList.push(
{
flag: i._flag,
list: i._t.split('#').filter(e => e && (e.startsWith('http') || (e.split('$')[1] && e.split('$')[1].startsWith('http'))))
}
)
}
} else {
m3u8List = dd._t.split('#')
fullList.push(
{
flag: dd._flag,
list: dd._t.replace(/\$+/g, '$').split('#').filter(e => e && (e.startsWith('http') || (e.split('$')[1] && e.split('$')[1].startsWith('http'))))
}
)
}
videoList.m3u8List = m3u8List
fullList.forEach(item => {
if (item.list.every(e => e.includes('$') && /^\s*\d+\s*$/.test(e.split('$')[0]))) item.list.sort((a, b) => { return a.split('$')[0] - b.split('$')[0] })
})
if (fullList.length > 1) { // 将ZY支持的播放列表前置
index = fullList.findIndex(e => supportedFormats.includes(e.flag) || e.flag.startsWith('ZY支持'))
if (index !== -1) {
const first = fullList.splice(index, 1)
fullList = first.concat(fullList)
}
}
videoList.fullList = fullList
resolve(videoList)
}).catch(err => {
reject(err)
@@ -252,35 +352,55 @@ const zy = {
* @param {*} id 资源唯一标识符 id
* @returns
*/
download (key, id) {
download (key, id, videoFlag) {
return new Promise((resolve, reject) => {
let info = ''
let downloadUrls = ''
this.getSite(key).then(res => {
const site = res
if (site.download) {
const url = `${site.download}?ac=videolist&ids=${id}&ct=1`
axios.post(url).then(res => {
axios.get(url).then(res => {
const data = res.data
const json = parser.parse(data, this.xmlConfig)
const jsondata = json.rss === undefined ? json : json.rss
const videoList = jsondata.list.video
// Parse m3u8List
var m3u8List = []
const dd = videoList.dl.dd
const type = Object.prototype.toString.call(dd)
if (type === '[object Array]') {
for (const i of dd) {
m3u8List = i._t.split('#')
downloadUrls = i._t.replace(/\$+/g, '$').split('#').map(e => encodeURI(e.includes('$') ? e.split('$')[1] : e)).join('\n')
}
} else {
m3u8List = dd._t.split('#')
downloadUrls = dd._t.replace(/\$+/g, '$').split('#').map(e => encodeURI(e.includes('$') ? e.split('$')[1] : e)).join('\n')
}
videoList.m3u8List = m3u8List
resolve(videoList)
}).catch(err => {
if (downloadUrls) {
info = '调用下载接口获取到的链接已复制, 快去下载吧!'
resolve({ downloadUrls: downloadUrls, info: info })
} else {
throw new Error()
}
}).catch((err) => {
err.info = '无法获取到下载链接,请通过播放页面点击“调试”按钮获取'
reject(err)
})
} else {
resolve([])
zy.detail(key, id).then(res => {
const dl = res.fullList.find(e => e.flag === videoFlag) || res.fullList[0]
for (const i of dl.list) {
const url = encodeURI(i.includes('$') ? i.split('$')[1] : i)
downloadUrls += (url + '\n')
}
if (downloadUrls) {
info = '视频源链接已复制, 快去下载吧!'
resolve({ downloadUrls: downloadUrls, info: info })
} else {
throw new Error()
}
}).catch((err) => {
err.info = '无法获取到下载链接,请通过播放页面点击“调试”按钮获取'
reject(err)
})
}
})
})
@@ -307,49 +427,72 @@ const zy = {
* @param {*} channel 直播频道 url
* @returns boolean
*/
async checkChannel (channel) {
checkChannel (url) {
return new Promise((resolve, reject) => {
axios.get(channel).then(res => {
const manifest = res.data
var parser = new M3u8Parser()
parser.push(manifest)
parser.end()
var parsedManifest = parser.manifest
if (parsedManifest.segments.length) {
resolve(true)
} else {
resolve(false)
const supportFormats = /\.(m3u8|flv)$/
const extRE = url.match(supportFormats) || new URL.URL(url).pathname.match(supportFormats)
if (extRE[1] === 'flv') {
const MAX_CONTENT_LENGTH = 2000 // axios配置maxContentLength不生效先用request凑合
let receivedLength = 0
let options = { uri: url, gzip: true, timeout: 10000 }
if (proxyURL) {
if (proxyURL.startsWith('http')) options = Object.assign({ proxy: proxyURL }, options)
if (proxyURL.startsWith('socks5')) options = Object.assign({ agent: new SocksProxyAgent(proxyURL) }, options)
}
}).catch(e => {
resolve(false)
})
const req = request.get(options)
.on('data', (str) => {
receivedLength += str.length
if (receivedLength > MAX_CONTENT_LENGTH) {
resolve(true) // 应该用FLVDemuxer.probe来检测先凑合
req.abort()
}
})
.on('error', function (err) {
resolve(false)
console.log(err)
})
.on('end', () => { resolve(false) })
} else if (extRE[1] === 'm3u8') {
axios.get(url).then(res => {
const manifest = res.data
const parser = new M3u8Parser()
parser.push(manifest)
parser.end()
const parsedManifest = parser.manifest
if (parsedManifest.segments.length) {
resolve(true)
} else {
resolve(false)
}
}).catch(e => {
resolve(false)
})
}
})
},
/**
* 获取豆瓣页面链接
* @param {*} name 视频名称
* @param {*} year 视频年份
* @returns 豆瓣页面链接,如果没有搜到该视频,返回搜索页面链接
*/
doubanLink (name) {
doubanLink (name, year) {
return new Promise((resolve, reject) => {
// 豆瓣搜索链接
var nameToSearch = name.replace(/\s/g, '')
var doubanSearchLink = 'https://www.douban.com/search?q=' + nameToSearch
const nameToSearch = name.replace(/\s/g, '')
const doubanSearchLink = 'https://www.douban.com/search?q=' + nameToSearch
axios.get(doubanSearchLink).then(res => {
const $ = cheerio.load(res.data)
// 比较第一和第二给豆瓣搜索结果, 看名字是否相符
var link = ''
var linkInDouban = $($('div.result')[0]).find('div>div>h3>a').first()
var nameInDouban = linkInDouban.text().replace(/\s/g, '')
if (nameToSearch === nameInDouban) {
link = linkInDouban.attr('href')
} else {
linkInDouban = $($('div.result')[1]).find('div>div>h3>a').first()
nameInDouban = linkInDouban.text().replace(/\s/g, '')
if (nameToSearch === nameInDouban) {
// 查询所有搜索结果, 看名字和年代是否相符
let link = ''
$('div.result').each(function () {
const linkInDouban = $(this).find('div>div>h3>a').first()
const nameInDouban = linkInDouban.text().replace(/\s/g, '')
const subjectCast = $(this).find('span.subject-cast').text()
if (nameToSearch === nameInDouban && subjectCast && subjectCast.includes(year)) {
link = linkInDouban.attr('href')
}
}
})
if (link) {
resolve(link)
} else {
@@ -364,18 +507,19 @@ const zy = {
/**
* 获取豆瓣评分
* @param {*} name 视频名称
* @param {*} year 视频年份
* @returns 豆瓣评分
*/
doubanRate (name) {
doubanRate (name, year) {
return new Promise((resolve, reject) => {
var nameToSearch = name.replace(/\s/g, '')
this.doubanLink(nameToSearch).then(link => {
const nameToSearch = name.replace(/\s/g, '')
this.doubanLink(nameToSearch, year).then(link => {
if (link.includes('https://www.douban.com/search')) {
resolve('暂无评分')
} else {
axios.get(link).then(response => {
const parsedHtml = cheerio.load(response.data)
var rating = parsedHtml('body').find('#interest_sectl').first().find('strong').first()
const rating = parsedHtml('body').find('#interest_sectl').first().find('strong').first()
if (rating.text()) {
resolve(rating.text().replace(/\s/g, ''))
} else {
@@ -390,16 +534,53 @@ const zy = {
})
})
},
async proxy () {
/**
* 获取豆瓣相关视频推荐列表
* @param {*} name 视频名称
* @param {*} year 视频年份
* @returns 豆瓣相关视频推荐列表
*/
doubanRecommendations (name, year) {
return new Promise((resolve, reject) => {
const nameToSearch = name.replace(/\s/g, '')
const recommendations = []
this.doubanLink(nameToSearch, year).then(link => {
if (link.includes('https://www.douban.com/search')) {
resolve(recommendations)
} else {
axios.get(link).then(response => {
const $ = cheerio.load(response.data)
$('div.recommendations-bd').find('div>dl>dd>a').each(function (index, element) {
recommendations.push($(element).text())
})
resolve(recommendations)
}).catch(err => {
reject(err)
})
}
}).catch(err => {
reject(err)
})
})
},
getDefaultSites (url) {
return new Promise((resolve, reject) => {
axios.get(url).then(res => {
resolve(res.data)
}).catch(err => { reject(err) })
})
},
proxy () {
return new Promise((resolve, reject) => {
setting.find().then(db => {
if (db && db.proxy && db.proxy.type === 'manual') {
if (db.proxy.scheme && db.proxy.url && db.proxy.port) {
const proxyURL = db.proxy.scheme + '://' + db.proxy.url.trim() + ':' + db.proxy.port.trim()
proxyURL = db.proxy.scheme + '://' + db.proxy.url.trim() + ':' + db.proxy.port.trim()
session.setProxy({ proxyRules: proxyURL })
http.globalAgent = https.globalAgent = new ElectronProxyAgent(session)
}
} else {
proxyURL = ''
session.setProxy({ proxyRules: 'direct://' })
http.globalAgent = https.globalAgent = new ElectronProxyAgent(session)
}

View File

@@ -1,9 +1,10 @@
import { BrowserWindow, ipcMain } from 'electron'
const { autoUpdater } = require('@imjs/electron-differential-updater')
const { autoUpdater } = require('electron-updater')
// electron-updater 增量更新时似乎无法显示进度
export function initUpdater (win = BrowserWindow) {
autoUpdater.autoDownload = false
autoUpdater.autoInstallOnAppQuit = false
autoUpdater.autoInstallOnAppQuit = true
// 主进程监听检查更新事件
ipcMain.on('checkForUpdate', () => {
@@ -41,8 +42,8 @@ export function initUpdater (win = BrowserWindow) {
})
// 下载更新进度
autoUpdater.on('download-progress', (info, progress) => {
win.webContents.send('download-progress', info, progress)
autoUpdater.on('download-progress', (progressObj) => {
win.webContents.send('download-progress', progressObj)
})
// 下载完成

View File

@@ -28,7 +28,8 @@ export default new Vuex.Store({
},
appState: {
windowIsOnTop: false
}
},
DetailCache: {}
},
getters: {
getView: state => {
@@ -48,6 +49,9 @@ export default new Vuex.Store({
},
getAppState: state => {
return state.appState
},
getDetailCache: state => {
return state.DetailCache
}
},
mutations: {
@@ -68,6 +72,9 @@ export default new Vuex.Store({
},
SET_APPSTATE: (state, payload) => {
state.appState = payload
},
set_DetailCache: (state, payload) => {
state.DetailCache = payload
}
}
})

17407
yarn.lock

File diff suppressed because it is too large Load Diff