Compare commits

...

396 Commits

Author SHA1 Message Date
Hunlongyu
68fd0d0c6a Merge pull request #789 from Hunlongyu/dependabot/npm_and_yarn/ejs-3.1.8
Bump ejs from 3.1.3 to 3.1.8
2022-09-27 08:47:03 +08:00
dependabot[bot]
a95403849d Bump ejs from 3.1.3 to 3.1.8
Bumps [ejs](https://github.com/mde/ejs) from 3.1.3 to 3.1.8.
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.3...v3.1.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-26 07:05:21 +00:00
Hunlongyu
554f8d87bc Merge pull request #763 from Hunlongyu/dependabot/npm_and_yarn/postcss-7.0.39
Bump postcss from 7.0.32 to 7.0.39
2022-09-26 15:04:03 +08:00
dependabot[bot]
895619d194 Bump postcss from 7.0.32 to 7.0.39
Bumps [postcss](https://github.com/postcss/postcss) from 7.0.32 to 7.0.39.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/7.0.39/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/7.0.32...7.0.39)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-20 09:08:10 +00:00
Hunlongyu
fd77157f72 Merge pull request #788 from Hunlongyu/dependabot/npm_and_yarn/ejs-and-vue/cli-plugin-babel-and-vue/cli-plugin-vuex-and-vue/cli-service-3.1.8
Bump ejs, @vue/cli-plugin-babel, @vue/cli-plugin-vuex and @vue/cli-service
2022-09-20 17:06:41 +08:00
dependabot[bot]
0d8786db21 Bump ejs, @vue/cli-plugin-babel, @vue/cli-plugin-vuex and @vue/cli-service
Bumps [ejs](https://github.com/mde/ejs), [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel), [@vue/cli-plugin-vuex](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-vuex) and [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service). These dependencies needed to be updated together.

Updates `ejs` from 3.1.3 to 3.1.8
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.3...v3.1.8)

Updates `@vue/cli-plugin-babel` from 4.5.11 to 5.0.8
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v5.0.8/packages/@vue/cli-plugin-babel)

Updates `@vue/cli-plugin-vuex` from 4.5.11 to 5.0.8
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v5.0.8/packages/@vue/cli-plugin-vuex)

Updates `@vue/cli-service` from 4.5.11 to 5.0.8
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v5.0.8/packages/@vue/cli-service)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
- dependency-name: "@vue/cli-plugin-babel"
  dependency-type: direct:development
- dependency-name: "@vue/cli-plugin-vuex"
  dependency-type: direct:development
- dependency-name: "@vue/cli-service"
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-19 03:29:18 +00:00
Hunlongyu
e9e86c12bd Merge pull request #787 from Hunlongyu/dependabot/npm_and_yarn/jszip-3.10.1
Bump jszip from 3.5.0 to 3.10.1
2022-09-19 11:26:10 +08:00
hunlongyu
c61a3e2f1d fix: 蓝奏云外链被劫持跳转黄色网站 2022-09-13 08:51:58 +08:00
dependabot[bot]
9f36969e81 Bump jszip from 3.5.0 to 3.10.1
Bumps [jszip](https://github.com/Stuk/jszip) from 3.5.0 to 3.10.1.
- [Release notes](https://github.com/Stuk/jszip/releases)
- [Changelog](https://github.com/Stuk/jszip/blob/main/CHANGES.md)
- [Commits](https://github.com/Stuk/jszip/compare/v3.5.0...v3.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-08 15:03:20 +00:00
Hunlongyu
b8c970cefe Merge pull request #762 from Hunlongyu/dependabot/npm_and_yarn/node-fetch-2.6.7
Bump node-fetch from 2.6.1 to 2.6.7
2022-08-30 15:04:39 +08:00
Hunlongyu
da1ee461db Merge pull request #761 from Hunlongyu/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.20 to 4.17.21
2022-08-27 13:04:28 +08:00
Hunlongyu
c691a96d26 Merge pull request #774 from Hunlongyu/dependabot/npm_and_yarn/shell-quote-1.7.3
Bump shell-quote from 1.7.2 to 1.7.3
2022-08-25 16:22:54 +08:00
dependabot[bot]
90d2bb3e47 Bump lodash from 4.17.20 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-17 09:02:18 +00:00
dependabot[bot]
2bfaad5500 Bump node-fetch from 2.6.1 to 2.6.7
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-17 09:02:16 +00:00
dependabot[bot]
c186ba5e4e Bump shell-quote from 1.7.2 to 1.7.3
Bumps [shell-quote](https://github.com/substack/node-shell-quote) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/substack/node-shell-quote/releases)
- [Changelog](https://github.com/substack/node-shell-quote/blob/master/CHANGELOG.md)
- [Commits](https://github.com/substack/node-shell-quote/compare/v1.7.2...1.7.3)

---
updated-dependencies:
- dependency-name: shell-quote
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-17 09:02:12 +00:00
Hunlongyu
38378794ea Merge pull request #784 from Hunlongyu/dependabot/npm_and_yarn/terser-4.8.1
Bump terser from 4.8.0 to 4.8.1
2022-08-17 17:00:40 +08:00
dependabot[bot]
841f3424f9 Bump terser from 4.8.0 to 4.8.1
Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-21 04:22:32 +00:00
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
buvta
f2f58fb888 调整版本号到v2.6.10 2020-12-18 18:03:35 +08:00
buvta
5286568801 允许证书错误v2.7.0rc 2020-12-18 17:16:23 +08:00
buvta
2a9f8ed0dc 避免改动数据库时重置软件 2020-12-18 16:25:04 +08:00
buvta
eb3d064cb5 改动shortcut数据库,避免快捷键指南太过杂乱 2020-12-17 20:33:56 +08:00
buvta
a0ec282cc2 fix:改动快捷键设置时未及时生效 2020-12-17 20:15:16 +08:00
buvta
ea61dca27b film下el-select的visible-change事件改为change 2020-12-17 16:54:21 +08:00
buvta
fd0be9e96b film搜索调整隐藏列 2020-12-17 16:49:49 +08:00
buvta
775247e28b 设置快捷键添加重置功能 2020-12-17 16:47:57 +08:00
buvta
ded5c39790 Play频道列表添加clickaway事件 2020-12-17 16:36:40 +08:00
buvta
16e44d71bd 海报模式下不提醒支持shift 2020-12-17 16:22:20 +08:00
buvta
ae0858319f 完善导入导出的title提示 2020-12-15 20:35:50 +08:00
buvta
3c6733648b 再次调整resize事件 2020-12-15 20:22:01 +08:00
buvta
06bd915964 调大el-select下拉框高度 2020-12-15 17:15:25 +08:00
buvta
dfcd786f53 fix:收藏批量删除 2020-12-15 16:24:12 +08:00
buvta
d0f6282b81 批量检测完成时提示 2020-12-15 15:42:13 +08:00
buvta
ab3a6e1fd2 各页面完善按钮提示 2020-12-15 15:20:55 +08:00
buvta
0cb540d3e5 历史收藏添加支持shift提示 2020-12-15 14:34:14 +08:00
buvta
1d0123d69f fix:历史收藏列表模式拖拽调序 2020-12-15 14:23:31 +08:00
buvta
6dbcb8e621 历史与收藏添加批量删除 2020-12-15 14:17:13 +08:00
buvta
b1a6d58974 清除iptv的watch view 2020-12-15 14:12:02 +08:00
haiyangcui
bb45006ff7 Revert "删除跳过片头片尾里的无用代码"
This reverts commit e9f81faf70.
2020-12-14 17:11:47 +01:00
haiyangcui
e9f81faf70 删除跳过片头片尾里的无用代码 2020-12-14 16:26:47 +01:00
cuiocean
b864c6ca15 Merge pull request #383 from Hunlongyu/dependabot/npm_and_yarn/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8
2020-12-14 16:12:49 +01:00
buvta
9f98231eed 添加提示次数限制 2020-12-14 22:27:21 +08:00
buvta
3bcf5b1618 添加多选支持shift提示 2020-12-14 21:21:29 +08:00
buvta
d7a6245f8a 导出为json格式时自动添加扩展名 2020-12-14 21:19:32 +08:00
buvta
2abf2aeda7 设置页添加更新日志与常见问题链接 2020-12-14 17:18:19 +08:00
buvta
c3129c7cee 调整Play页面某些变量的命名 2020-12-14 17:08:38 +08:00
buvta
3b0ec94d2b 直播停止时亦可弹出频道列表 2020-12-14 17:03:24 +08:00
buvta
9902d98342 调整海报模式延时间隔 2020-12-13 23:17:28 +08:00
buvta
b5f5baeb8f 点击播放历史记录按钮时隐藏频道列表 2020-12-13 22:59:17 +08:00
buvta
f396d5ea05 停止时隐藏跳过片头片尾 2020-12-13 22:51:46 +08:00
buvta
e616aa5e2a 播放器初始状态点播放,历史记录为空时直接停止重置 2020-12-13 22:25:58 +08:00
buvta
f041093fe9 清除播放窗口还原时hasStart变量 2020-12-13 22:25:58 +08:00
buvta
e66c056283 稍微调整跳过片头片尾,并添加注释 2020-12-13 22:25:52 +08:00
haiyangcui
d33bbcff06 设置片头片尾按钮的文字提示改为tooltips提示 2020-12-13 14:49:11 +01:00
buvta
992c2f152f 推荐页切换页面时没必要重新读取数据库,并修正换行符 2020-12-13 18:36:31 +08:00
buvta
118bc5058c 调整历史播放页面watch监测view的事件 2020-12-13 18:21:38 +08:00
buvta
19c11f2694 fix:海报模式film页面加载图片时来回切换页面偶尔有卡片被部分遮掩,其它页面是重新加载数据故而不用 2020-12-13 18:12:47 +08:00
buvta
0a812ff6c4 el-select多选时显示全部标签 2020-12-13 16:30:12 +08:00
buvta
1e6a98df88 海报模式重新加上resize事件 2020-12-13 16:08:57 +08:00
buvta
b1b4e61244 fix:改造时调整rowDrop触发时机 2020-12-13 15:38:36 +08:00
buvta
2a05b2abdb 推荐、历史、收藏页面进行类film改造 2020-12-13 15:37:10 +08:00
buvta
74beb47ad5 fix: film搜索切换回海报模式时部分卡片被遮掩显示不全的bug 2020-12-13 15:08:32 +08:00
buvta
325a7f3b0d 调小播放器重置延时 2020-12-13 13:10:52 +08:00
buvta
9b5b5a1334 修复播放器初始状态时点击播放时的bug,并给直播加上对应功能 2020-12-13 13:09:36 +08:00
buvta
74dee7a892 调整film窗口resize事件触发机制 2020-12-13 11:53:35 +08:00
dependabot[bot]
d07436d876 Bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-13 01:04:59 +00:00
buvta
e283152a89 去除多余代码,Waterfall在数据及窗口变动时会自动刷新 2020-12-12 23:02:20 +08:00
buvta
bdeeccedb9 film列表模式窗口宽度足够时显示“最近更新” 2020-12-12 23:01:31 +08:00
haiyangcui
5931266770 播放页面,显示历史时间信息 2020-12-12 13:51:58 +01:00
haiyangcui
bbeb75631d 打开ZY,直接播放的话,或者是视频停止后再点击播放的话,播放最近的历史记录 2020-12-12 13:39:44 +01:00
haiyangcui
c0f4940289 设置quitMiniMode按钮默认不显示,这样每次播放视频的时候就无需设置'xg-btn-quitMiniMode'的样式 2020-12-12 13:01:27 +01:00
buvta
db1b81243a 修复“站内”时切换站点可搜索 2020-12-12 12:56:55 +08:00
buvta
97b6a8259f 继续调整 2020-12-12 12:52:27 +08:00
buvta
4c92ff9e70 fix:优化film停止搜索时的自动切换searchRunning问题 2020-12-12 12:25:04 +08:00
buvta
b8dc7f4526 film工具栏分割线添加"回到顶部" 2020-12-11 23:42:28 +08:00
buvta
0d6e0d6e9f film停止搜索优化 2020-12-11 23:07:13 +08:00
buvta
1a5bac68ad 调整film搜索表格过滤器设置 2020-12-11 16:11:12 +08:00
buvta
83e302aeb7 film搜索使用工具栏实现过滤(功能实现) 2020-12-11 15:45:36 +08:00
buvta
2196eaa68c 视图模式切换转移到工具栏分割线,搜索亦可用工具栏(功能待实现) 2020-12-11 15:41:28 +08:00
haiyangcui
40aae02af6 修复收藏页的共享功能 2020-12-10 17:17:28 +01:00
haiyangcui
f078df6f8e 修复推荐页的分享功能 2020-12-10 17:14:49 +01:00
haiyangcui
ee44bfe0a8 支持停止搜索,虽然实际上是停止把搜索结果加入searchContents,但对于用户来说应该是足够了. 2020-12-10 16:59:07 +01:00
hunlongyu
9d9a808226 修复分享二维码地址为空的bug 2020-12-10 23:17:55 +08:00
buvta
d78784c0d9 fix:精简模式切换内容时quitMini图标消失 2020-12-10 16:10:51 +08:00
buvta
1ead8a5594 开启过滤切换分类数据过少时加大请求间隔 2020-12-10 16:10:51 +08:00
Hunlongyu
b63c8f4daf 优化自动更新功能, 增加进度条 2020-12-10 14:59:49 +08:00
Hunlongyu
011400f714 删除 server.js 文件 2020-12-10 10:00:20 +08:00
Hunlongyu
1d725579de Merge branch 'master' of https://github.com/Hunlongyu/ZY-Player into master 2020-12-10 09:59:07 +08:00
Hunlongyu
93749c3841 移除 node 代理 2020-12-10 09:59:01 +08:00
haiyangcui
4b0c67b8fa 添加"跳略设置"按钮,避免一直显示片头片尾设置 2020-12-08 18:08:03 +01:00
buvta
165040872c fix:工具栏收起时重置selectedArea 2020-12-08 22:06:09 +08:00
buvta
27773be1c7 v2.6.9 2020-12-08 18:26:05 +08:00
buvta
fadb50a28b film调整areas刷新策略 2020-12-08 18:14:21 +08:00
buvta
518a9c3492 播放视频开始时重置片头片尾输入框数据 2020-12-08 17:36:33 +08:00
buvta
d5eb4dc49f film搜索不加载工具栏开关 2020-12-08 16:48:27 +08:00
buvta
e76bee574f film避免列表模式下加载图片 2020-12-08 16:46:02 +08:00
buvta
b7779a3e6f 疑似el-select弹窗bug,临时性修补 2020-12-08 15:45:25 +08:00
buvta
4cb5b48985 收起工具栏时重置列表 2020-12-08 15:37:21 +08:00
buvta
b2128113ba 调整工具栏refreshFilteredList触发方式 2020-12-08 15:24:36 +08:00
buvta
58d0ae6da1 工具栏"语言"替换为"排序" 2020-12-08 13:03:58 +08:00
buvta
2bd821f59d 调整工具栏打开方式 2020-12-08 12:20:12 +08:00
haiyangcui
8d002225fd 添加'过滤器'按钮,控制过滤选项按钮组的显示 2020-12-07 16:47:45 +01:00
haiyangcui
0243c2f0fe Fix: 切换类型时,总是调用refreshFilteredList 2020-12-07 16:28:30 +01:00
buvta
b8edd7f440 Revert "fix:海报模式切换分类时偶尔空白页"
This reverts commit 1df6726a3b.
2020-12-07 20:22:22 +08:00
buvta
4683aecf7b fix:上层弹窗被过滤工具栏遮蔽 2020-12-07 20:22:22 +08:00
buvta
16d38ba2b4 调整相应样式 2020-12-07 19:43:36 +08:00
buvta
fe7fe06d48 film添加过滤工具栏 2020-12-07 19:43:36 +08:00
buvta
c1220ef752 手动设置跳过片头片尾时间长度 2020-12-07 07:01:22 +08:00
buvta
f0c221a863 修复福利屏蔽 2020-12-06 18:40:18 +08:00
buvta
1df6726a3b fix:海报模式切换分类时偶尔空白页 2020-12-06 18:29:45 +08:00
buvta
1a9e939f9c 更新直播源 2020-12-06 15:53:55 +08:00
buvta
39cb188604 调整iptv分组命名规则 2020-12-06 15:53:13 +08:00
buvta
aba4d12302 确保不会意外引入代理 2020-12-05 23:54:29 +08:00
buvta
17c77fd48a 禁止xgplayer显示重播 2020-12-05 23:48:24 +08:00
buvta
1cfc12ec19 fix:确保不会跳过多集 2020-12-05 23:46:03 +08:00
buvta
b0aa1dc28a 视频多集时可通过快捷键设置跳过片头片尾,保存在相应的历史记录中 2020-12-05 19:10:05 +08:00
haiyangcui
0c12b394a7 视频列表页,支持按地区过滤数据 2020-12-04 14:06:30 +01:00
haiyangcui
9e468bc82e 在类型框内显示视频数信息 2020-12-04 13:48:31 +01:00
haiyangcui
973a15b593 统一downloadEvent函数 2020-12-02 15:19:50 +01:00
Hunlongyu
253c1e7723 分享的网址改为 gitee, 国内用户打开更快 2020-12-02 10:21:54 +08:00
Hunlongyu
7b44051190 更新官网截图,, 修改部分描述 2020-12-02 10:19:12 +08:00
Hunlongyu
a25ac77ebc 更新 README 2020-12-02 10:02:42 +08:00
haiyangcui
d187167fbe 解决换源时视频类型不正常更新的问题 2020-12-01 16:01:47 +01:00
haiyangcui
b34347cf43 添加视频数信息 2020-12-01 13:20:50 +01:00
haiyangcui
829d9447a4 删除重复和不可用的源站 2020-12-01 13:07:11 +01:00
haiyangcui
39067c6a35 缓存视频列表信息 2020-12-01 00:24:38 +01:00
haiyangcui
6dbb64a4f2 重构 2020-11-30 23:58:12 +01:00
haiyangcui
f8041290d2 无需定义getPage函数 2020-11-30 23:49:56 +01:00
haiyangcui
f772ac2e9d 从搜索界面返回时,无需再重新加载数据 2020-11-30 17:13:10 +01:00
haiyangcui
ef485ef64a 删除无用代码 2020-11-30 16:48:43 +01:00
haiyangcui
b164d5e83e Revert "降级xgplayer回2.13.1,新版居然不支持缓冲了."
This reverts commit 8cd2b920c8.
2020-11-30 15:48:13 +01:00
64 changed files with 48698 additions and 10552 deletions

View File

@@ -12,6 +12,7 @@ module.exports = {
}, },
rules: { rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', '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: 创建报告以帮助我们改进 about: 创建报告以帮助我们改进
title: '(未回答的问题请删除)' title: '(未回答的问题请删除,减少多余信息干扰'
labels: bug labels: bug
assignees: '' assignees: ''

View File

@@ -1,4 +1,4 @@
name: release-build name: x86-release-build
on: on:
push: push:
@@ -25,11 +25,3 @@ jobs:
shell: pwsh shell: pwsh
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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,19 @@
### 🌴 下载 ### 🌴 下载
- 🍓 [Github -- 官方下载](https://github.com/Hunlongyu/ZY-Player/releases) - 🎃 软件暂时关闭下载通道. 请大家支持正版.
- 🍉 [蓝奏云 -- 快速下载](https://www.lanzoux.com/b04s6a3re) 密码:95px - 🎭 所有资源来自网上, 该软件不参与任何制作, 上传, 储存等内容, 禁止传播违法资源. 该软件仅供学习参考, 请于安装后24小时内删除.
- 🍒 适用于32位操作系统的x86软件,在蓝奏云网盘里, 后缀名: ZY Player * 32位.exe
### 🎠 平台
| 平台 | 链接 |
| :------------------------------------ | :---------------------------------------------------------- |
| 🖥️ 电脑端 ( Windows & Mac & Linux ) | [ZY Player](https://github.com/Hunlongyu/ZY-Player) |
| 📱 手机端 ( Android & IOS ) | [ZY Player APP](https://github.com/Hunlongyu/ZY-Player-APP) |
| 📺 电视端 ( Android & Mac ) ( 进行中 ) | [ZY Player TV](https://github.com/cuiocean/ZY-Player-TV) |
| 🌐 浏览器 ( Web ) | [ZY Player Web](https://github.com/Hunlongyu/ZY-Player-Web) |
### 🚀 快捷键 ### 🚀 快捷键
@@ -81,7 +91,8 @@
### 🍭 开发者 ### 🍭 开发者
| [Hunlongyu](https://github.com/Hunlongyu) | [cuiocean](https://github.com/cuiocean) | | [Hunlongyu](https://github.com/Hunlongyu) | [cuiocean](https://github.com/cuiocean) | [buvta](https://github.com/buvta) |
| :----------------------------------------------------------: | :----------------------------------------------------------: | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
| <img width="120" src="https://avatars2.githubusercontent.com/u/15273630?s=460&u=48cf3299e2a842c0252233d8be42ef4c5d792138&v=4"/> | <img width="120" src="https://avatars0.githubusercontent.com/u/5760235?s=460&u=9d969dd8d83f069ce7ebd60516770c93ac07a330&v=4" /> | | <img width="120" src="https://avatars2.githubusercontent.com/u/15273630?s=460&u=48cf3299e2a842c0252233d8be42ef4c5d792138&v=4"/> | <img width="120" src="https://avatars0.githubusercontent.com/u/5760235?s=460&u=9d969dd8d83f069ce7ebd60516770c93ac07a330&v=4" /> | <img width="120" src="https://avatars3.githubusercontent.com/u/12312540?s=400&v=4" /> |
| 💻 🎨 🐛 | 💻 🐛 | | 💻 🎨 🐛 | 💻 🐛 | 💻 🐛 |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -70,7 +70,7 @@
<h1>ZY Player 资源播放器</h1> <h1>ZY Player 资源播放器</h1>
<h2>跨平台视频资源播放器, 简洁免费无广告.</h2> <h2>跨平台视频资源播放器, 简洁免费无广告.</h2>
<a href="https://github.com/Hunlongyu/ZY-Player/releases" target="_blank" class="download-btn"><i class="icofont-home"></i></i> Github 下载</a> <a href="https://github.com/Hunlongyu/ZY-Player/releases" target="_blank" class="download-btn"><i class="icofont-home"></i></i> Github 下载</a>
<a href="https://www.lanzous.com/b04s6a3re" target="_blank" class="download-btn"><i class="icofont-cloud"></i> 蓝奏下载 (密码:95px)</a> <a href="https://hly.lanzoul.com/b04s6a3re" target="_blank" class="download-btn"><i class="icofont-cloud"></i> 蓝奏下载 (密码:95px)</a>
</div> </div>
</div> </div>
<div class="col-lg-6 d-lg-flex flex-lg-column align-items-stretch order-1 order-lg-2 hero-img" data-aos="fade-up"> <div class="col-lg-6 d-lg-flex flex-lg-column align-items-stretch order-1 order-lg-2 hero-img" data-aos="fade-up">
@@ -89,7 +89,7 @@
<div class="section-title"> <div class="section-title">
<h2>软件特色</h2> <h2>软件特色</h2>
<p>经过三个大版本更迭, 软件功能丰富, 操作简单.</p> <p>软件功能丰富, 操作简单.</p>
</div> </div>
<div class="row no-gutters"> <div class="row no-gutters">
@@ -99,7 +99,7 @@
<div class="col-md-6 icon-box" data-aos="fade-up"> <div class="col-md-6 icon-box" data-aos="fade-up">
<i class="bx bx-receipt"></i> <i class="bx bx-receipt"></i>
<h4>浏览</h4> <h4>浏览</h4>
<p>浏览全网热门视频, 支持切换视频源. 详细的电影分类.支持搜索电影名和演员名称. </p> <p>浏览全网热门视频, 支持切换视频源. 详细的电影分类. </p>
</div> </div>
<div class="col-md-6 icon-box" data-aos="fade-up" data-aos-delay="100"> <div class="col-md-6 icon-box" data-aos="fade-up" data-aos-delay="100">
<i class="icofont-play-alt-3"></i> <i class="icofont-play-alt-3"></i>
@@ -124,7 +124,7 @@
<div class="col-md-6 icon-box" data-aos="fade-up" data-aos-delay="500"> <div class="col-md-6 icon-box" data-aos="fade-up" data-aos-delay="500">
<i class="icofont-cubes"></i> <i class="icofont-cubes"></i>
<h4>其他</h4> <h4>其他</h4>
<p>多主题, 多语言, 自动更新</p> <p>多主题, 自动更新</p>
</div> </div>
</div> </div>
</div> </div>
@@ -146,14 +146,14 @@
</div> </div>
<div class="owl-carousel gallery-carousel" data-aos="fade-up"> <div class="owl-carousel gallery-carousel" data-aos="fade-up">
<a href="assets/img/gallery/001.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/001.png" alt=""></a> <a href="assets/img/gallery/01.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/01.png" alt=""></a>
<a href="assets/img/gallery/002.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/002.png" alt=""></a> <a href="assets/img/gallery/02.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/02.png" alt=""></a>
<a href="assets/img/gallery/003.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/003.png" alt=""></a> <a href="assets/img/gallery/03.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/03.png" alt=""></a>
<a href="assets/img/gallery/004.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/004.png" alt=""></a> <a href="assets/img/gallery/04.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/04.png" alt=""></a>
<a href="assets/img/gallery/005.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/005.png" alt=""></a> <a href="assets/img/gallery/05.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/05.png" alt=""></a>
<a href="assets/img/gallery/006.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/006.png" alt=""></a> <a href="assets/img/gallery/06.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/06.png" alt=""></a>
<a href="assets/img/gallery/007.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/007.png" alt=""></a> <a href="assets/img/gallery/07.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/07.png" alt=""></a>
<a href="assets/img/gallery/008.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/008.png" alt=""></a> <a href="assets/img/gallery/08.png" class="venobox" data-gall="gallery-carousel"><img src="assets/img/gallery/08.png" alt=""></a>
</div> </div>
</div> </div>
@@ -193,7 +193,7 @@
<i class="bx bx-help-circle icon-help"></i> <a data-toggle="collapse" href="#accordion-list-3" class="collapsed">跨平台<i class="bx bx-chevron-down icon-show"></i><i class="bx bx-chevron-up icon-close"></i></a> <i class="bx bx-help-circle icon-help"></i> <a data-toggle="collapse" href="#accordion-list-3" class="collapsed">跨平台<i class="bx bx-chevron-down icon-show"></i><i class="bx bx-chevron-up icon-close"></i></a>
<div id="accordion-list-3" class="collapse" data-parent=".accordion-list"> <div id="accordion-list-3" class="collapse" data-parent=".accordion-list">
<p> <p>
目前支持 Windows, Mac, Linux 桌面系统. 暂不支持手机端或者电视端. 未来会考虑实现全平台. 目前支持 Windows, Mac, Linux, Android, IOS, TV, Web 全平台.
</p> </p>
</div> </div>
</li> </li>
@@ -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="#">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.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://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> </ul>
</div> </div>

34209
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "zy", "name": "zy",
"version": "2.6.8", "version": "2.8.8",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
@@ -17,58 +17,50 @@
}, },
"main": "background.js", "main": "background.js",
"dependencies": { "dependencies": {
"axios": "^0.21.0", "@electron/remote": "^2.0.8",
"cheerio": "^1.0.0-rc.3", "axios": "^0.21.2",
"core-js": "^3.8.0", "bootstrap-vue": "^2.22.0",
"cors": "^2.8.5", "cheerio": "1.0.0-rc.6",
"dexie": "^3.0.3", "core-js": "^3.10.2",
"dexie": "^3.2.2",
"electron-localshortcut": "^3.2.1", "electron-localshortcut": "^3.2.1",
"electron-proxy-agent": "^1.2.0", "electron-proxy-agent": "^1.2.0",
"electron-updater": "^4.3.5", "electron-updater": "^4.3.8",
"element-ui": "^2.14.1", "element-ui": "^2.15.9",
"express": "^4.17.1", "fast-xml-parser": "^3.19.0",
"fast-xml-parser": "^3.17.4",
"html2canvas": "^1.0.0-rc.7", "html2canvas": "^1.0.0-rc.7",
"iptv-playlist-parser": "^0.5.0", "iptv-playlist-parser": "^0.6.0",
"m3u": "0.0.2", "m3u": "0.0.2",
"m3u8-parser": "^4.5.0", "m3u8-parser": "^4.6.0",
"memcached": "^2.2.2", "memcached": "^2.2.2",
"modern-normalize": "^1.0.0", "modern-normalize": "^1.0.0",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"pinyin-match": "^1.1.1", "pinyin-match": "^1.2.1",
"qrcode.vue": "^1.7.0", "qrcode.vue": "^1.7.0",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",
"session": "^0.1.0", "session": "^0.1.0",
"sortablejs": "^1.12.0", "sortablejs": "^1.13.0",
"v-fit-columns": "^0.2.0", "v-fit-columns": "^0.2.0",
"vue": "^2.6.12", "vue": "^2.6.14",
"vue-clickaway": "^2.2.2",
"vue-infinite-loading": "^2.4.5", "vue-infinite-loading": "^2.4.5",
"vue-waterfall-plugin": "^1.1.0", "vue-waterfall-plugin": "^1.1.0",
"vuedraggable": "^2.24.3", "vuedraggable": "^2.24.3",
"vuex": "^3.6.0", "vuex": "^3.6.2",
"xgplayer": "^2.13.1", "xgplayer": "2.19.1",
"xgplayer-hls.js": "^2.2.5" "xgplayer-flv.js": "^2.3.0",
"xgplayer-hls.js": "^2.4.2",
"xgplayer-mp4": "^1.2.2"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.5.9", "@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-eslint": "~4.5.9", "@vue/cli-plugin-vuex": "~5.0.8",
"@vue/cli-plugin-vuex": "~4.5.9", "@vue/cli-service": "~5.0.8",
"@vue/cli-service": "~4.5.9",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.1", "babel-plugin-component": "^1.1.1",
"electron": "^11.0.3", "electron": "^13.0.0",
"electron-devtools-installer": "^3.1.1", "electron-devtools-installer": "^3.1.1",
"eslint": "^7.14.0", "sass": "^1.30.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-loader": "^10.1.0", "sass-loader": "^10.1.0",
"vue-cli-plugin-electron-builder": "2.0.0-rc.5", "vue-cli-plugin-electron-builder": "2.0.0-rc.6",
"vue-template-compiler": "^2.6.12" "vue-template-compiler": "^2.6.14"
} }
} }

View File

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

View File

@@ -10,7 +10,6 @@
<Setting v-show="view === 'Setting'" /> <Setting v-show="view === 'Setting'" />
<IPTV v-show="view === 'IPTV'" /> <IPTV v-show="view === 'IPTV'" />
<EditSites v-if="view === 'EditSites'"/> <EditSites v-if="view === 'EditSites'"/>
<Recommendation v-show="view === 'Recommendation'" />
</div> </div>
<transition name="slide"> <transition name="slide">
<Detail v-if="detail.show"/> <Detail v-if="detail.show"/>
@@ -22,11 +21,62 @@
</template> </template>
<script> <script>
import { setting } from './lib/dexie'
const remote = require('@electron/remote')
export default { export default {
name: 'App', name: 'App',
data () { data () {
return { 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: { computed: {

View File

@@ -123,7 +123,7 @@
border-radius: 5px; border-radius: 5px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.listpage-header{ .listpage-header, .toolbar{
height: 60px; height: 60px;
width: 100%; width: 100%;
display: flex; display: flex;
@@ -175,7 +175,38 @@
font-size: 1rem; font-size: 1rem;
border: none; border: none;
} }
.el-select-dropdown__item.selected.hover{ //是上游的bug吗临时性修补
background-color: transparent;
}
.el-select-dropdown__wrap{
max-height: 574px
}
} }
> span{
.el-input-number{
width:120px;
.el-input{
width: 100px;
}
.el-input__inner{
padding-left: 5px;
padding-right: 5px;
width: 88px;
}
.el-input-number__increase, .el-input-number__decrease {
background-color: inherit;
border: none;
}
}
}
}
.el-divider{
.el-divider--horizontal{
margin: 12px 0;
}
}
.toolbar{
z-index: 5;
} }
.listpage-body{ .listpage-body{
height: calc(100% - 60px); height: calc(100% - 60px);
@@ -252,7 +283,8 @@
cursor: pointer; cursor: pointer;
transition: 0.2s; transition: 0.2s;
&:hover { &:hover {
top: -3px; width: 102%;
height: 102%
} }
.img{ .img{
position: relative; position: relative;
@@ -409,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-fc-3: #823aa0;
--l-bgc-1: #ffffff; --l-bgc-1: #ffffff;
--l-bgc-2: #f2f6f9; --l-bgc-2: #f2f6f9;
--l-bgc-3: #F9FBFC;
--l-bsc: 0 1px 3px #8e8da233, 0 1px 2px #8e8da244; --l-bsc: 0 1px 3px #8e8da233, 0 1px 2px #8e8da244;
--l-bsc-hover: 0 14px 28px #8e8da255, 0 10px 10px #8e8da244; --l-bsc-hover: 0 14px 28px #8e8da255, 0 10px 10px #8e8da244;
--l-bsc-2: 0 -4px 23px 0 #8e8da233; --l-bsc-2: 0 -4px 23px 0 #8e8da233;
@@ -32,6 +33,7 @@
--d-fc-3: #38dd77; --d-fc-3: #38dd77;
--d-bgc-1: #222222; --d-bgc-1: #222222;
--d-bgc-2: #2f2f2f; --d-bgc-2: #2f2f2f;
--d-bgc-3: #292929;
--d-bsc: 0 1px 3px #38dd7733, 0 1px 2px #38dd7744; --d-bsc: 0 1px 3px #38dd7733, 0 1px 2px #38dd7744;
--d-bsc-hover: 0 14px 28px #38dd7755, 0 10px 10px #38dd7744; --d-bsc-hover: 0 14px 28px #38dd7755, 0 10px 10px #38dd7744;
--d-bsc-2: 0 -4px 23px 0 #38dd7733; --d-bsc-2: 0 -4px 23px 0 #38dd7733;
@@ -50,6 +52,7 @@
--g-fc-3: #C1D95C; --g-fc-3: #C1D95C;
--g-bgc-1: #4baea0; --g-bgc-1: #4baea0;
--g-bgc-2: #74b4ac; --g-bgc-2: #74b4ac;
--g-bgc-3: #60B1A6;
--g-bsc: 0 1px 3px #e1ebe033, 0 1px 2px #e1ebe044; --g-bsc: 0 1px 3px #e1ebe033, 0 1px 2px #e1ebe044;
--g-bsc-hover: 0 14px 28px #e1ebe055, 0 10px 10px #e1ebe044; --g-bsc-hover: 0 14px 28px #e1ebe055, 0 10px 10px #e1ebe044;
--g-bsc-2: 0 -4px 23px 0 #e1ebe033; --g-bsc-2: 0 -4px 23px 0 #e1ebe033;
@@ -68,6 +71,7 @@
--p-fc-3: #177ea7; --p-fc-3: #177ea7;
--p-bgc-1: #ff8499; --p-bgc-1: #ff8499;
--p-bgc-2: #fea1b2; --p-bgc-2: #fea1b2;
--p-bgc-3: #FF93A6;
--p-bsc: 0 1px 3px #ef528533, 0 1px 2px #ef528544; --p-bsc: 0 1px 3px #ef528533, 0 1px 2px #ef528544;
--p-bsc-hover: 0 14px 28px #ef528555, 0 10px 10px #ef528544; --p-bsc-hover: 0 14px 28px #ef528555, 0 10px 10px #ef528544;
--p-bsc-2: 0 -4px 23px 0 #ef528533; --p-bsc-2: 0 -4px 23px 0 #ef528533;

View File

@@ -131,6 +131,23 @@
border-color: var(--d-c-8); 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{ .play{
background-color: var(--d-bgc-1); background-color: var(--d-bgc-1);
box-shadow: var(--d-bsc); box-shadow: var(--d-bsc);
.el-switch__label {
color: var(--d-fc-2)
}
.el-switch__label.is-active {
color: #409EFF
}
.el-input{ .el-input{
input{ input{
background-color: var(--d-bgc-1); background-color: var(--d-bgc-1);
@@ -155,6 +178,21 @@
} }
} }
.box{ .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{ .more{
span{ span{
svg{ svg{
@@ -319,13 +357,32 @@
// Page of list using table and picture // Page of list using table and picture
.listpage{ .listpage{
color: var(--d-fc-2); color: var(--d-fc-2);
.listpage-header{ .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); border-bottom-color: var(--d-c-3);
.btn{ .btn{
&:hover{ &:hover{
color: var(--d-fc-3) color: var(--d-fc-3)
} }
} }
.el-switch__label {
color: var(--d-fc-2)
}
.el-switch__label.is-active {
color: #409EFF
}
.el-button{ .el-button{
background-color: var(--d-bgc-2); background-color: var(--d-bgc-2);
color: var(--d-fc-2); color: var(--d-fc-2);
@@ -415,7 +472,7 @@
.show-picture{ .show-picture{
color: var(--d-fc-1); color: var(--d-fc-1);
.card{ .card{
background-color: var(--d-bgc-1); background-color: var(--d-bgc-3);
box-shadow: var(--d-bsc); box-shadow: var(--d-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1); transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{ &:hover{

View File

@@ -131,6 +131,23 @@
border-color: var(--g-c-8); 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{ .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{ .more{
span{ span{
svg{ svg{
@@ -319,7 +351,20 @@
// Page of list using table and picture // Page of list using table and picture
.listpage{ .listpage{
color: var(--g-fc-2); color: var(--g-fc-2);
.listpage-header{ .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); border-bottom-color: var(--g-c-3);
.btn{ .btn{
&:hover{ &:hover{
@@ -415,7 +460,7 @@
.show-picture{ .show-picture{
color: var(--g-fc-1); color: var(--g-fc-1);
.card{ .card{
background-color: var(--g-bgc-1); background-color: var(--g-bgc-3);
box-shadow: var(--g-bsc); box-shadow: var(--g-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1); transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{ &:hover{

View File

@@ -1,5 +1,6 @@
.theme-light{ .theme-light{
background-color: var(--l-bgc-1); // background-color: var(--l-bgc-1);
background: rgba(0, 0, 0, 0);
.zy-select{ .zy-select{
color: var(--l-fc-1); color: var(--l-fc-1);
background-color: var(--l-bgc-1); background-color: var(--l-bgc-1);
@@ -131,6 +132,23 @@
border-color: var(--l-c-8); 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{ .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{ .more{
span{ span{
svg{ svg{
@@ -319,7 +352,20 @@
// Page of list using table and picture // Page of list using table and picture
.listpage{ .listpage{
color: var(--l-fc-2); color: var(--l-fc-2);
.listpage-header{ .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); border-bottom-color: var(--l-c-3);
.btn{ .btn{
&:hover{ &:hover{
@@ -415,7 +461,7 @@
.show-picture{ .show-picture{
color: var(--l-fc-1); color: var(--l-fc-1);
.card{ .card{
background-color: var(--l-bgc-1); background-color: var(--l-bgc-3);
box-shadow: var(--l-bsc); box-shadow: var(--l-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1); transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{ &:hover{

View File

@@ -131,6 +131,23 @@
border-color: var(--p-c-8); 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{ .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{ .more{
span{ span{
svg{ svg{
@@ -319,7 +351,20 @@
// Page of list using table and picture // Page of list using table and picture
.listpage{ .listpage{
color: var(--p-fc-2); color: var(--p-fc-2);
.listpage-header{ .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); border-bottom-color: var(--p-c-3);
.btn{ .btn{
&:hover{ &:hover{
@@ -415,7 +460,7 @@
.show-picture{ .show-picture{
color: var(--p-fc-1); color: var(--p-fc-1);
.card{ .card{
background-color: var(--p-bgc-1); background-color: var(--p-bgc-3);
box-shadow: var(--p-bsc); box-shadow: var(--p-bsc);
transition: all 0.3s cubic-bezier(.25,.8,.25,1); transition: all 0.3s cubic-bezier(.25,.8,.25,1);
&:hover{ &:hover{

View File

@@ -1,14 +1,17 @@
'use strict' 'use strict'
import './lib/site/server'
import { app, protocol, BrowserWindow, globalShortcut } from 'electron' import { app, protocol, BrowserWindow, globalShortcut } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib' import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import { initUpdater } from './lib/update/update' import { initUpdater } from './lib/update/update'
require('@electron/remote/main').initialize()
const isDevelopment = process.env.NODE_ENV !== 'production' const isDevelopment = process.env.NODE_ENV !== 'production'
// const log = require('electron-log') // 用于调试主程序
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors') // 允许跨域 app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors') // 允许跨域
// app.commandLine.appendSwitch('--ignore-certificate-errors', 'true') // 忽略证书相关错误 app.commandLine.appendSwitch('--ignore-certificate-errors', 'true') // 忽略证书相关错误
let win let win
@@ -23,7 +26,9 @@ function createWindow () {
webPreferences: { webPreferences: {
webSecurity: false, webSecurity: false,
enableRemoteModule: true, enableRemoteModule: true,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION nodeIntegration: true,
contextIsolation: false,
allowRunningInsecureContent: false
} }
}) })
@@ -34,6 +39,24 @@ function createWindow () {
createProtocol('app') createProtocol('app')
win.loadURL('app://./index.html') 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) initUpdater(win)
@@ -45,7 +68,7 @@ function createWindow () {
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
app.dock.show() app.dock.show()
} }
if (process.platform === 'Linux') { if (process.platform === 'linux') {
app.disableHardwareAcceleration() app.disableHardwareAcceleration()
app.commandLine.appendSwitch('--no-sandbox') // linux 关闭沙盒模式 app.commandLine.appendSwitch('--no-sandbox') // linux 关闭沙盒模式
} }

View File

@@ -14,13 +14,13 @@
</g> </g>
</svg> </svg>
</span> </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"> <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> <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="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"/> <path d="M4,18 L4,9"/>
</svg> </svg>
</span> </span> -->
<span :class="[view === 'Play' ? 'active ': ''] + 'zy-svg'" @click="changeView('Play')"> <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"> <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> <title id="playIconTitle">播放</title>
@@ -53,6 +53,12 @@
import { mapMutations } from 'vuex' import { mapMutations } from 'vuex'
export default { export default {
name: 'Aside', name: 'Aside',
data () {
return {
lastViewOpenDetail: '',
savedDetail: {}
}
},
computed: { computed: {
view: { view: {
get () { get () {
@@ -74,9 +80,19 @@ export default {
methods: { methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL']), ...mapMutations(['SET_VIEW', 'SET_DETAIL']),
changeView (e) { changeView (e) {
// 记录打开detail的view
if (this.detail.show === true) {
this.lastViewOpenDetail = this.view
this.savedDetail = this.detail
}
this.view = e this.view = e
this.detail = { // 如果回到上一次打开detail的试图页面,恢复detail页面
show: false if (e === this.lastViewOpenDetail) {
this.detail = this.savedDetail
} else {
this.detail = {
show: false
}
} }
} }
} }

View File

@@ -17,7 +17,7 @@
</svg> </svg>
</span> </span>
</div> </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">
<div class="info-left"> <div class="info-left">
<img :src="info.pic" alt=""> <img :src="info.pic" alt="">
@@ -36,10 +36,10 @@
</div> </div>
</div> </div>
<div class="operate"> <div class="operate">
<span @click="playEvent(0)">播放</span> <span @click="playEvent(selectedEpisode)">播放</span>
<span @click="starEvent">收藏</span> <span @click="starEvent(info)">收藏</span>
<span @click="downloadEvent">下载</span> <span @click="downloadEvent">下载</span>
<span @click="shareEvent">分享</span> <span @click="shareEvent(info,selectedEpisode)">分享</span>
<span @click="doubanLinkEvent">豆瓣</span> <span @click="doubanLinkEvent">豆瓣</span>
<span @click="togglePlayOnlineEvent"> <span @click="togglePlayOnlineEvent">
<input type="checkbox" v-model="playOnline"> 播放在线高清视频 <input type="checkbox" v-model="playOnline"> 播放在线高清视频
@@ -52,10 +52,57 @@
</span> </span>
</div> </div>
<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="m3u8">
<div class="box"> <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> </div>
</div> </div>
@@ -67,6 +114,7 @@
</template> </template>
<script> <script>
import { mapMutations } from 'vuex' import { mapMutations } from 'vuex'
import Waterfall from 'vue-waterfall-plugin'
import zy from '../lib/site/tools' import zy from '../lib/site/tools'
import onlineVideo from '../lib/site/onlineVideo' import onlineVideo from '../lib/site/onlineVideo'
import { star, history } from '../lib/dexie' import { star, history } from '../lib/dexie'
@@ -76,17 +124,26 @@ export default {
data () { data () {
return { return {
loading: true, loading: true,
m3u8List: [], videoFlag: '',
videoList: [],
videoFullList: [],
key: '',
site: {},
info: {}, info: {},
playOnline: false, playOnline: false,
selectedEpisode: 0, // 选定集数
selectedOnlineSite: '哔嘀', selectedOnlineSite: '哔嘀',
onlineSites: ['哔嘀', '素白白', '简影', '极品', '喜欢看', '1080影视'] onlineSites: ['哔嘀', '素白白', '简影', '极品', '喜欢看', '1080影视']
} }
}, },
filters: { filters: {
ftName (e) { ftName (e, n) {
const name = e.split('$')[0] const num = e.split('$')
return name if (num.length > 1) {
return e.split('$')[0]
} else {
return `${(n + 1)}`
}
} }
}, },
computed: { computed: {
@@ -121,26 +178,66 @@ export default {
set (val) { set (val) {
this.SET_SHARE(val) this.SET_SHARE(val)
} }
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
} }
}, },
components: {
Waterfall
},
methods: { 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 () { close () {
this.detail.show = false 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) { async playEvent (n) {
if (!this.playOnline) { if (!this.playOnline) {
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.detail.info.id })
if (db) { 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 { } 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.video.detail = this.info
this.view = 'Play' this.view = 'Play'
this.detail.show = false this.detail.show = false
} else { } 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) { if (db) {
db.index = n db.index = n
db.detail = this.info db.detail = this.info
@@ -161,15 +258,15 @@ export default {
onlineVideo.playVideoOnline(this.selectedOnlineSite, this.detail.info.name, n) onlineVideo.playVideoOnline(this.selectedOnlineSite, this.detail.info.name, n)
} }
}, },
async starEvent () { async starEvent (info) {
const db = await star.find({ key: this.detail.key, ids: this.info.id }) const db = await star.find({ key: this.detail.key, ids: info.id })
const doc = { const doc = {
key: this.detail.key, key: this.detail.key,
ids: this.info.id, ids: info.id,
site: this.detail.site, site: this.detail.site,
name: this.info.name, name: info.name,
detail: this.info, detail: info,
rate: this.info.rate rate: info.rate
} }
if (db) { if (db) {
star.update(db.id, doc) star.update(db.id, doc)
@@ -180,6 +277,10 @@ export default {
}) })
} }
}, },
detailEvent (info) {
this.detail.info = info
this.getDetailInfo()
},
togglePlayOnlineEvent () { togglePlayOnlineEvent () {
this.playOnline = !this.playOnline this.playOnline = !this.playOnline
}, },
@@ -211,64 +312,70 @@ export default {
} }
}, },
downloadEvent () { downloadEvent () {
zy.download(this.detail.key, this.info.id).then(res => { zy.download(this.detail.key, this.info.id, this.videoFlag).then(res => {
if (res && res.dl && res.dl.dd) { clipboard.writeText(res.downloadUrls)
const text = res.dl.dd._t this.$message.success(res.info)
if (text) { }).catch((err) => {
const list = text.split('#') this.$message.error(err.info)
let downloadUrl = res.name + '\n'
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
this.$message.warning('没有查询到下载链接.')
}
} else {
const list = [...this.m3u8List]
let downloadUrl = this.detail.info.name + '\n'
for (const i of list) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
}
}) })
}, },
shareEvent () { shareEvent (info, selectedEpisode) {
this.share = { this.share = {
show: true, show: true,
key: this.detail.key, key: this.detail.key,
info: this.detail.info info: info,
index: selectedEpisode
} }
}, },
doubanLinkEvent () { doubanLinkEvent () {
const name = this.detail.info.name.trim() const name = this.info.name.trim()
zy.doubanLink(name).then(link => { const year = this.info.year
zy.doubanLink(name, year).then(link => {
const open = require('open') const open = require('open')
open(link) open(link)
}) })
}, },
getDoubanRate () { async getDoubanRate () {
const name = this.detail.info.name.trim() const name = this.info.name.trim()
zy.doubanRate(name).then(res => { const year = this.info.year
this.info.rate = res 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 const id = this.detail.info.ids || this.detail.info.id
zy.detail(this.detail.key, id).then(res => { const cacheKey = this.detail.key + '@' + id
if (res) { const db = await history.find({ site: this.detail.key, ids: id })
this.info = res if (db) {
this.$set(this.info, 'rate', '') this.videoFlag = db.videoFlag
this.m3u8List = res.m3u8List this.selectedEpisode = db.index
this.getDoubanRate() }
this.loading = false 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 () { created () {
@@ -390,6 +497,15 @@ export default {
margin: 6px 10px 0px 0px; margin: 6px 10px 0px 0px;
padding: 8px 22px; 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,18 +2,18 @@
<div class="listpage" id="sites"> <div class="listpage" id="sites">
<div class="listpage-header" v-show="!enableBatchEdit"> <div class="listpage-header" v-show="!enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理分组">></el-switch> <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="addSite" icon="el-icon-document-add">新增</el-button>
<el-button @click="exportSites" icon="el-icon-upload2" >导出</el-button> <el-button @click="exportSites" icon="el-icon-upload2" title="导出全部,自动添加扩展名">导出</el-button>
<el-button @click="importSites" icon="el-icon-download">导入</el-button> <el-button @click="importSites" icon="el-icon-download" title="支持同时导入多个文件">导入</el-button>
<el-button @click="checkAllSite" icon="el-icon-refresh" :loading="checkAllSitesLoading">检测{{ this.checkAllSitesLoading ? this.checkProgress + '/' + this.sites.length : '' }}</el-button> <el-button @click="checkAllSite" icon="el-icon-refresh" :loading="checkAllSitesLoading" title="可在后台运行">检测{{ this.checkAllSitesLoading ? this.checkProgress + '/' + this.sites.length : '' }}</el-button>
<el-button @click="resetSitesEvent" icon="el-icon-refresh-left">重置</el-button> <el-button @click="resetSitesEvent" icon="el-icon-refresh-left">重置</el-button>
</div> </div>
<div class="listpage-header" v-show="enableBatchEdit"> <div class="listpage-header" v-show="enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理分组"></el-switch> <el-switch v-model="enableBatchEdit" active-text="批处理分组"></el-switch>
<el-input placeholder="新组名" v-model="batchGroupName"></el-input> <el-input placeholder="新组名" v-model="batchGroupName"></el-input>
<el-switch v-model="batchIsActive" active-text="启用"></el-switch> <el-switch v-model="batchIsActive" active-text="启用"></el-switch>
<el-button type="primary" icon="el-icon-edit" @click.stop="saveBatchEdit">保存</el-button> <el-button type="primary" icon="el-icon-edit" @click.stop="saveBatchEdit" title="输入框组名为空时仅保存开关状态">保存分组与开关状态</el-button>
<el-button @click="removeSelectedSites" icon="el-icon-delete-solid">删除</el-button> <el-button @click="removeSelectedSites" icon="el-icon-delete-solid">删除</el-button>
</div> </div>
<div class="listpage-body" id="sites-body"> <div class="listpage-body" id="sites-body">
@@ -41,7 +41,18 @@
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <el-switch
v-model="scope.row.isActive" 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> </el-switch>
</template> </template>
</el-table-column> </el-table-column>
@@ -83,7 +94,7 @@
</div> </div>
<!-- 编辑页面 --> <!-- 编辑页面 -->
<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 :model="siteInfo" ref='siteInfo' label-width="75px" label-position="left" :rules="rules">
<el-form-item label="源站名" prop='name'> <el-form-item label="源站名" prop='name'>
<el-input v-model="siteInfo.name" placeholder="请输入源站名" /> <el-input v-model="siteInfo.name" placeholder="请输入源站名" />
@@ -94,6 +105,9 @@
<el-form-item label="下载接口" prop='download'> <el-form-item label="下载接口" prop='download'>
<el-input v-model="siteInfo.download" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入Download接口地址可以空着"/> <el-input v-model="siteInfo.download" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="请输入Download接口地址可以空着"/>
</el-form-item> </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-form-item label="分组" prop='group'>
<el-select v-model="siteInfo.group" allow-create filterable default-first-option placeholder="请输入分组"> <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> <el-option v-for="item in siteGroup" :key="item" :label="item" :value="item"></el-option>
@@ -109,16 +123,36 @@
</span> </span>
</el-dialog> </el-dialog>
</div> </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> </div>
</template> </template>
<script> <script>
import { mapMutations } from 'vuex' import { mapMutations } from 'vuex'
import { sites, setting } from '../lib/dexie' import { sites, setting } from '../lib/dexie'
import zy from '../lib/site/tools' import zy from '../lib/site/tools'
import { remote } from 'electron'
import { sites as defaultSites } from '../lib/dexie/initData'
import fs from 'fs' import fs from 'fs'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
const remote = require('@electron/remote')
export default { export default {
name: 'editSites', name: 'editSites',
@@ -127,15 +161,21 @@ export default {
show: false, show: false,
sites: [], sites: [],
dialogType: 'new', dialogType: 'new',
dialogVisible: false, editSiteDialogVisible: false,
filterKeywordsDialogVisible: false,
siteInfo: { siteInfo: {
key: '', key: '',
name: '', name: '',
api: '', api: '',
download: '', download: '',
jiexiUrl: '',
group: '', group: '',
isActive: true isActive: true
}, },
excludeRootClasses: true,
excludeR18Films: true,
rootClassFilterKeywords: [],
r18ClassFilterKeywords: [],
siteGroup: [], siteGroup: [],
rules: { rules: {
name: [ name: [
@@ -169,9 +209,9 @@ export default {
}, },
getFilters () { getFilters () {
const groups = [...new Set(this.sites.map(site => site.group))] const groups = [...new Set(this.sites.map(site => site.group))]
var filters = [] const filters = []
groups.forEach(g => { groups.forEach(g => {
var doc = { const doc = {
text: g, text: g,
value: g value: g
} }
@@ -186,16 +226,21 @@ export default {
this.$message.info('正在检测, 请勿操作.') this.$message.info('正在检测, 请勿操作.')
this.enableBatchEdit = false this.enableBatchEdit = false
} }
if (this.enableBatchEdit) {
if (this.setting.shiftTooltipLimitTimes === undefined) this.setting.shiftTooltipLimitTimes = 5
if (this.setting.shiftTooltipLimitTimes) {
this.$message.info('多选时支持shift快捷键')
this.setting.shiftTooltipLimitTimes--
setting.find().then(res => {
res.shiftTooltipLimitTimes = this.setting.shiftTooltipLimitTimes
setting.update(res)
})
}
}
} }
}, },
methods: { methods: {
...mapMutations(['SET_SETTING']), ...mapMutations(['SET_SETTING']),
excludeR18FilmsChangeEvent () {
setting.find().then(res => {
res.excludeR18Films = this.setting.excludeR18Films
setting.update(res)
})
},
selectionCellClick (selection, row) { selectionCellClick (selection, row) {
if (this.shiftDown && this.selectionBegin !== '' && selection.includes(row)) { if (this.shiftDown && this.selectionBegin !== '' && selection.includes(row)) {
this.selectionEnd = row.id this.selectionEnd = row.id
@@ -235,6 +280,11 @@ export default {
}, },
getSites () { getSites () {
sites.all().then(res => { sites.all().then(res => {
res.forEach(element => {
if (element.reverseOrder === null || element.reverseOrder === undefined) {
element.reverseOrder = false
}
})
this.sites = res this.sites = res
}) })
}, },
@@ -247,6 +297,29 @@ export default {
} }
this.siteGroup = arr 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 () { addSite () {
if (this.checkAllSitesLoading) { if (this.checkAllSitesLoading) {
this.$message.info('正在检测, 请勿操作.') this.$message.info('正在检测, 请勿操作.')
@@ -254,12 +327,13 @@ export default {
} }
this.getSitesGroup() this.getSitesGroup()
this.dialogType = 'new' this.dialogType = 'new'
this.dialogVisible = true this.editSiteDialogVisible = true
this.siteInfo = { this.siteInfo = {
key: '', key: '',
name: '', name: '',
api: '', api: '',
download: '', download: '',
jiexiUrl: '',
group: '', group: '',
isActive: true isActive: true
} }
@@ -271,12 +345,13 @@ export default {
} }
this.getSitesGroup() this.getSitesGroup()
this.dialogType = 'edit' this.dialogType = 'edit'
this.dialogVisible = true this.editSiteDialogVisible = true
this.siteInfo = siteInfo this.siteInfo = siteInfo
this.editOldkey = siteInfo.key this.editOldkey = siteInfo.key
}, },
closeDialog () { closeDialog () {
this.dialogVisible = false this.editSiteDialogVisible = false
this.filterKeywordsDialogVisible = false
this.getSites() this.getSites()
}, },
removeEvent (e) { removeEvent (e) {
@@ -311,13 +386,14 @@ export default {
if (!this.checkSiteKey()) { if (!this.checkSiteKey()) {
return false return false
} }
var randomstring = require('randomstring') const randomstring = require('randomstring')
var doc = { const doc = {
key: this.dialogType === 'edit' ? this.siteInfo.key : this.siteInfo.key ? this.siteInfo.key : randomstring.generate(6), 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, id: this.dialogType === 'edit' ? this.siteInfo.id : this.sites.length ? this.sites[this.sites.length - 1].id + 1 : 1,
name: this.siteInfo.name, name: this.siteInfo.name,
api: this.siteInfo.api, api: this.siteInfo.api,
download: this.siteInfo.download, download: this.siteInfo.download,
jiexiUrl: this.siteInfo.jiexiUrl,
group: this.siteInfo.group, group: this.siteInfo.group,
isActive: this.siteInfo.isActive isActive: this.siteInfo.isActive
} }
@@ -328,10 +404,11 @@ export default {
name: '', name: '',
api: '', api: '',
download: '', download: '',
jiexiUrl: '',
group: '' group: ''
} }
this.dialogType === 'edit' ? this.$message.success('修改成功!') : this.$message.success('新增源成功!') this.dialogType === 'edit' ? this.$message.success('修改成功!') : this.$message.success('新增源成功!')
this.dialogVisible = false this.editSiteDialogVisible = false
this.getSites() this.getSites()
}) })
this.editOldkey = '' this.editOldkey = ''
@@ -342,13 +419,12 @@ export default {
const str = JSON.stringify(arr, null, 2) const str = JSON.stringify(arr, null, 2)
const options = { const options = {
filters: [ filters: [
{ name: 'JSON file', extensions: ['json'] }, { name: 'JSON file', extensions: ['json'] }
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
] ]
} }
remote.dialog.showSaveDialog(options).then(result => { remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) { if (!result.canceled) {
if (!result.filePath.endsWith('.json')) result.filePath += '.json'
fs.writeFileSync(result.filePath, str) fs.writeFileSync(result.filePath, str)
this.$message.success('已保存成功') this.$message.success('已保存成功')
} }
@@ -363,45 +439,75 @@ export default {
} }
const options = { const options = {
filters: [ filters: [
{ name: 'JSON file', extensions: ['json'] }, { name: '支持的文件格式', extensions: ['json', 'txt'] }
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
], ],
properties: ['openFile', 'multiSelections'] properties: ['openFile', 'multiSelections']
} }
remote.dialog.showOpenDialog(options).then(result => { remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) { if (!result.canceled) {
result.filePaths.forEach(file => { result.filePaths.forEach(file => {
var str = fs.readFileSync(file) if (file.endsWith('json')) {
const json = JSON.parse(str) const str = fs.readFileSync(file)
json.forEach(ele => { const json = JSON.parse(str)
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) { json.forEach(ele => {
// 不含该key 同时也不含名字和url一样的 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) {
if (ele.isActive === undefined) { // 不含该key 同时也不含名字和url一样的
ele.isActive = true if (ele.isActive === undefined) {
ele.isActive = true
}
if (ele.group === undefined) {
ele.group = '导入'
}
this.sites.push(ele)
} }
if (ele.group === undefined) { })
ele.group = '导入' this.resetId(this.sites)
} sites.clear().then(sites.bulkAdd(this.sites))
this.sites.push(ele) 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 () { resetSitesEvent () {
this.stopFlag = true let url = this.setting.sitesDataURL
if (this.checkAllSitesLoading) { if (!url) {
this.$message.info('部分检测还未完全终止, 请稍等...') url = 'https://raw.iqiq.io/Hunlongyu/ZY-Player-Resources/main/Sites/20220713.json'
return
} }
sites.clear().then(sites.bulkAdd(defaultSites).then(this.getSites())) zy.getDefaultSites(url).then(res => {
this.$message.success('重置源成功') if (res.length > 0) {
sites.clear().then(sites.bulkAdd(res))
this.$message.success('重置源成功')
this.getSites()
}
}).catch(error => {
this.$message.error('导入云端源站失败. ' + error)
})
}, },
moveToTopEvent (i) { moveToTopEvent (i) {
if (this.checkAllSitesLoading) { if (this.checkAllSitesLoading) {
@@ -416,12 +522,13 @@ export default {
this.sites = this.$refs.editSitesTable.tableData this.sites = this.$refs.editSitesTable.tableData
} }
}, },
isActiveChangeEvent (row) { propChangeEvent (row) {
sites.remove(row.id) sites.remove(row.id)
sites.add(row) sites.add(row)
this.getSites()
}, },
resetId (inArray) { resetId (inArray) {
var id = 1 let id = 1
inArray.forEach(ele => { inArray.forEach(ele => {
ele.id = id ele.id = id
id += 1 id += 1
@@ -431,7 +538,7 @@ export default {
// 因为el-table的数据是单向绑定,我们先同步el-table里的数据和其绑定的数据 // 因为el-table的数据是单向绑定,我们先同步el-table里的数据和其绑定的数据
this.syncTableData() this.syncTableData()
sites.clear().then(res => { sites.clear().then(res => {
var id = 1 let id = 1
this.sites.forEach(ele => { this.sites.forEach(ele => {
ele.id = id ele.id = id
id += 1 id += 1
@@ -452,7 +559,7 @@ export default {
return false return false
} }
const tbody = document.getElementById('sites-table').querySelector('.el-table__body-wrapper tbody') const tbody = document.getElementById('sites-table').querySelector('.el-table__body-wrapper tbody')
var _this = this const _this = this
Sortable.create(tbody, { Sortable.create(tbody, {
onEnd ({ newIndex, oldIndex }) { onEnd ({ newIndex, oldIndex }) {
const currRow = _this.sites.splice(oldIndex, 1)[0] const currRow = _this.sites.splice(oldIndex, 1)[0]
@@ -462,6 +569,7 @@ export default {
}) })
}, },
async checkAllSite () { async checkAllSite () {
if (this.checkAllSitesLoading) return
this.checkAllSitesLoading = true this.checkAllSitesLoading = true
this.stopFlag = false this.stopFlag = false
this.checkProgress = 0 this.checkProgress = 0
@@ -471,6 +579,7 @@ export default {
await Promise.all(other.map(site => this.checkSingleSite(site))).then(res => { await Promise.all(other.map(site => this.checkSingleSite(site))).then(res => {
this.checkAllSitesLoading = false this.checkAllSitesLoading = false
this.getSites() this.getSites()
if (!this.stopFlag) this.$message.success('视频点播源站批量检测已完成!')
}) })
}, },
async checkSingleSite (row) { async checkSingleSite (row) {

View File

@@ -9,9 +9,7 @@
:value="item.name"> :value="item.name">
</el-option> </el-option>
</el-select> </el-select>
<el-switch v-model="searchViewMode" active-text="海报" active-value="picture" inactive-text="列表" inactive-value="table" @change="updateSearchViewMode" <el-select v-model="selectedClassName" size="small" placeholder="类型" :popper-append-to-body="false" popper-class="popper" @change="classClick" v-if="classList && classList.length" v-show="!showFind">
v-if="show.find"></el-switch>
<el-select v-model="selectedClassName" size="small" placeholder="类型" :popper-append-to-body="false" popper-class="popper" @change="classClick" v-show="show.class">
<el-option <el-option
v-for="item in classList" v-for="item in classList"
:key="item.tid" :key="item.tid"
@@ -19,6 +17,14 @@
:value="item.name"> :value="item.name">
</el-option> </el-option>
</el-select> </el-select>
<el-select v-model="selectedSearchClassNames" size="small" multiple placeholder="类型" :popper-append-to-body="false" popper-class="popper" v-if="searchClassList && searchClassList.length" v-show="showFind && showToolbar" @remove-tag="refreshFilteredList" @change="refreshFilteredList">
<el-option
v-for="(item, index) in searchClassList"
:key='index'
:label="item"
:value="item">
</el-option>
</el-select>
<el-autocomplete <el-autocomplete
clearable clearable
size="small" size="small"
@@ -44,12 +50,42 @@
</el-option> </el-option>
</el-select> </el-select>
<!--方便触屏--> <!--方便触屏-->
<el-button icon="el-icon-search" @click.stop="searchEvent" slot="append" /> <el-button icon="el-icon-search" @click.stop="searchEvent" slot="append" v-if="!searchRunning"/>
<el-button icon="el-icon-loading" @click.stop="stopSearchEvent" slot="append" v-if="searchRunning" title='点击可停止搜索'/>
</el-autocomplete> </el-autocomplete>
</div> </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="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="film-body" infinite-wrapper> <div class="listpage-body" id="film-body" infinite-wrapper>
<div class="show-picture" v-if="setting.view === 'picture' && !show.find"> <div class="show-picture" v-if="setting.view === 'picture' && !showFind">
<Waterfall ref="filmWaterfall" :list="list" :gutter="20" :width="240" <Waterfall ref="filmWaterfall" :list="filteredList" :gutter="20" :width="240"
:breakpoints="{ :breakpoints="{
1200: { //当屏幕宽度小于等于1200 1200: { //当屏幕宽度小于等于1200
rowPerView: 4, rowPerView: 4,
@@ -64,7 +100,7 @@
animationEffect="fadeIn" animationEffect="fadeIn"
backgroundColor="rgba(0, 0, 0, 0)"> backgroundColor="rgba(0, 0, 0, 0)">
<template slot="item" slot-scope="props"> <template slot="item" slot-scope="props">
<div class="card" v-show="!setting.excludeR18Films || !containsR18Keywords(props.data.type)"> <div class="card">
<div class="img"> <div class="img">
<img style="width: 100%" :src="props.data.pic" alt="" @load="$refs.filmWaterfall.refresh()" @click="detailEvent(site, props.data)"> <img style="width: 100%" :src="props.data.pic" alt="" @load="$refs.filmWaterfall.refresh()" @click="detailEvent(site, props.data)">
<div class="operate"> <div class="operate">
@@ -87,10 +123,11 @@
</Waterfall> </Waterfall>
<infinite-loading force-use-infinite-wrapper :identifier="infiniteId" @infinite="infiniteHandler"></infinite-loading> <infinite-loading force-use-infinite-wrapper :identifier="infiniteId" @infinite="infiniteHandler"></infinite-loading>
</div> </div>
<div class="show-table" v-if="setting.view === 'table' && !show.find"> <div class="show-table" v-if="setting.view === 'table' && !showFind">
<el-table <el-table
size="mini" size="mini"
:data="list.filter(res => !setting.excludeR18Films || !containsR18Keywords(res.type))" :data="filteredList"
ref="filmTable"
height="100%" height="100%"
:empty-text="statusText" :empty-text="statusText"
@row-click="(row) => detailEvent(site, row)" @row-click="(row) => detailEvent(site, row)"
@@ -99,7 +136,7 @@
prop="name" prop="name"
label="片名"> label="片名">
</el-table-column> </el-table-column>
<el-table-column <el-table-column v-if="type.name === '最新'"
prop="type" prop="type"
label="类型" label="类型"
width="100"> width="100">
@@ -120,16 +157,16 @@
label="语言" label="语言"
width="100"> width="100">
</el-table-column> </el-table-column>
<el-table-column <el-table-column v-if="showTableLastColumn"
prop="note"
label="备注"
width="120">
</el-table-column>
<el-table-column
prop="last" prop="last"
label="最近更新" label="最近更新"
:formatter="dateFormat" :formatter="dateFormat"
align="left"> align="left"
width="120">
</el-table-column>
<el-table-column
prop="note"
label="备注">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
label="操作" label="操作"
@@ -152,10 +189,10 @@
</infinite-loading> </infinite-loading>
</el-table> </el-table>
</div> </div>
<div class="show-table" v-show="searchViewMode=== 'table' && show.find"> <div class="show-table" v-if="setting.searchViewMode === 'table' && showFind">
<el-table size="mini" <el-table size="mini"
ref="searchResultTable" ref="searchResultTable"
:data="searchContents.filter(res => !setting.excludeR18Films || (res.type !== undefined && !containsR18Keywords(res.type)))" :data="filteredSearchContents"
height="100%" height="100%"
:empty-text="statusText" :empty-text="statusText"
@filter-change="filterChange" @filter-change="filterChange"
@@ -181,36 +218,38 @@
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="type" prop="type"
:filters="getFilters('type')"
:filter-method="(value, row, column) => { this.currentColumn = column; return value === row.type }"
label="类型" label="类型"
width="90"> width="100">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
sortable sortable
prop="year" prop="year"
label="上映" label="上映"
width="90"> width="100">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="area" prop="area"
:filters="getFilters('area')"
:filter-method="(value, row, column) => { this.currentColumn = column; return value === row.area }"
label="地区" label="地区"
width="90"> width="100">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
:filters="getFilters('lang')" :filters="getFilters('lang')"
:filter-method="(value, row, column) => { this.currentColumn = column; return value === row.lang }" :filter-method="(value, row, column) => { this.currentColumn = column; return value === row.lang }"
prop="lang" prop="lang"
label="语言" label="语言"
width="70"> width="100">
</el-table-column>
<el-table-column v-if="showTableLastColumn"
prop="last"
label="最近更新"
:formatter="dateFormat"
align="left"
width="120">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
sortable sortable
prop="note" prop="note"
label="备注" label="备注">
width="120">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
label="操作" label="操作"
@@ -226,8 +265,8 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
<div class="show-picture" v-show="searchViewMode === 'picture' && show.find"> <div class="show-picture" v-if="setting.searchViewMode === 'picture' && showFind">
<Waterfall ref="filmSearchWaterfall" :list="searchContents.filter(res => !setting.excludeR18Films || (res.type !== undefined && !containsR18Keywords(res.type)))" :gutter="20" :width="240" <Waterfall ref="filmSearchWaterfall" :list="filteredSearchContents" :gutter="20" :width="240"
:breakpoints="{ :breakpoints="{
1200: { //当屏幕宽度小于等于1200 1200: { //当屏幕宽度小于等于1200
rowPerView: 4, rowPerView: 4,
@@ -242,7 +281,7 @@
animationEffect="fadeIn" animationEffect="fadeIn"
backgroundColor="rgba(0, 0, 0, 0)"> backgroundColor="rgba(0, 0, 0, 0)">
<template slot="item" slot-scope="props"> <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="img">
<div class="site"> <div class="site">
<span>{{props.data.site.name}}</span> <span>{{props.data.site.name}}</span>
@@ -277,24 +316,24 @@ import zy from '../lib/site/tools'
import Waterfall from 'vue-waterfall-plugin' import Waterfall from 'vue-waterfall-plugin'
import InfiniteLoading from 'vue-infinite-loading' import InfiniteLoading from 'vue-infinite-loading'
const { clipboard } = require('electron') const { clipboard } = require('electron')
const FILM_DATA_CACHE = {} // key = site.key, value = classList; key = site.key + '@' + type.tid, value = {list, pageCount}
export default { export default {
name: 'film', name: 'film',
data () { data () {
return { return {
show: { showFind: false,
body: false, showTableLastColumn: false,
site: false,
class: false,
classList: false,
find: false
},
sites: [], sites: [],
site: {}, site: {},
classList: [], classList: [],
searchClassList: [],
type: {}, type: {},
selectedClassName: '最新',
selectedSiteName: '', selectedSiteName: '',
selectedClassName: '',
selectedSearchClassNames: [],
totalpagecount: 0,
pagecount: 0, pagecount: 0,
recordcount: 0,
list: [], list: [],
statusText: ' ', statusText: ' ',
infiniteId: +new Date(), infiniteId: +new Date(),
@@ -302,12 +341,22 @@ export default {
searchList: [], searchList: [],
searchTxt: '', searchTxt: '',
searchContents: [], searchContents: [],
filteredSearchContents: [],
currentColumn: '', currentColumn: '',
searchGroup: '', searchGroup: '',
searchGroups: [], searchGroups: ['站内', '组内', '全站'],
// 福利片关键词 classFilterKeywords: [],
r18KeyWords: ['伦理', '论理', '倫理', '福利', '激情', '理论', '写真', '情色', '美女', '街拍', '赤足', '性感', '里番'], filteredList: [],
searchViewMode: 'picture' areas: [],
searchRunning: false,
siteSearchCount: 0,
infiniteHandlerCount: 0,
// Toolbar
showToolbar: false,
selectedAreas: [],
sortKeyword: '',
sortKeywords: ['按片名', '按上映年份', '按更新时间'],
selectedYears: { start: 0, end: new Date().getFullYear() }
} }
}, },
components: { components: {
@@ -355,8 +404,22 @@ export default {
this.SET_SETTING(val) this.SET_SETTING(val)
} }
}, },
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
},
filterSettings () { 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.isActive)
} }
}, },
filters: { filters: {
@@ -367,7 +430,11 @@ export default {
}, },
watch: { watch: {
view () { view () {
this.changeView() if (this.view === 'Film') {
this.getAllSites()
if (this.$refs.filmWaterfall) this.$refs.filmWaterfall.resize() // 瀑布插件resize和refresh功能相同只是延时不同
if (this.$refs.filmSearchWaterfall) this.$refs.filmSearchWaterfall.resize()
}
}, },
searchTxt () { searchTxt () {
if (this.searchTxt === '清除历史记录...') { if (this.searchTxt === '清除历史记录...') {
@@ -377,14 +444,110 @@ export default {
} }
}, },
filterSettings () { filterSettings () {
this.refreshClass()
},
list: {
handler (list) {
this.areas = [...new Set(list.map(ele => ele.area))].filter(x => x)
this.refreshFilteredList()
},
deep: true
},
siteSearchCount () {
if (this.siteSearchCount === this.searchSites.length) this.searchRunning = false
},
site () {
this.siteClick(this.site.name) 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()
},
deep: true
},
selectedAreas: {
handler () {
this.infiniteHandlerCount = 0
},
deep: true
},
selectedYears: {
handler () {
this.infiniteHandlerCount = 0
},
deep: true
} }
}, },
methods: { 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']),
updateSearchViewMode () { backTop () {
const viewMode = this.showFind ? this.setting.searchViewMode : this.setting.view
if (viewMode === 'picture') {
document.getElementById('film-body').scrollTop = 0
} else {
const table = this.showFind ? this.$refs.searchResultTable : this.$refs.filmTable
table.bodyWrapper.scrollTop = 0
}
},
refreshFilteredList () {
if (!this.showToolbar) {
this.sortKeyword = ''
this.selectedAreas = []
this.selectedSearchClassNames = []
this.selectedYears.start = 0
this.selectedYears.end = new Date().getFullYear()
}
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.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 b.year - a.year
})
break
case '按片名':
filteredData.sort(function (a, b) {
return a.name.localeCompare(b.name, 'zh')
})
break
case '按更新时间':
filteredData.sort(function (a, b) {
return new Date(b.last) - new Date(a.last)
})
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 {
this.filteredList = filteredData
}
},
toggleViewMode () {
if (this.showFind) {
this.setting.searchViewMode = this.setting.searchViewMode === 'picture' ? 'table' : 'picture'
setTimeout(() => { if (this.$refs.filmSearchWaterfall) this.$refs.filmSearchWaterfall.refresh() }, 700)
} else {
this.setting.view = this.setting.view === 'picture' ? 'table' : 'picture'
}
setting.find().then(res => { setting.find().then(res => {
res.searchViewMode = this.searchViewMode res.searchViewMode = this.setting.searchViewMode
res.view = this.setting.view
setting.update(res) setting.update(res)
}) })
}, },
@@ -392,16 +555,15 @@ export default {
return a.localeCompare(b, 'zh') return a.localeCompare(b, 'zh')
}, },
dateFormat (row, column) { dateFormat (row, column) {
var date = row[column.property] const date = row[column.property]
if (date === undefined) { if (date === undefined) {
return '' return ''
} }
return date.split(/\s/)[0] return date.split(/\s/)[0]
}, },
getFilters (column) { getFilters (column) {
const searchContents = this.searchContents.filter(res => !this.setting.excludeR18Films || (res.type !== undefined && !this.containsR18Keywords(res.type))) if (column === 'siteName') return [...new Set(this.filteredSearchContents.map(row => row.site.name))].map(e => { return { text: e, value: e } }) // 有方法合并这两行吗?
if (column === 'siteName') return [...new Set(searchContents.map(row => row.site.name))].map(e => { return { text: e, value: e } }) // 有方法合并这两行吗? return [...new Set(this.filteredSearchContents.map(row => row[column]))].map(e => { return { text: e, value: e } })
return [...new Set(searchContents.map(row => row[column]))].map(e => { return { text: e, value: e } })
}, },
filterChange (filters) { filterChange (filters) {
// 一次只能一列 // 一次只能一列
@@ -409,7 +571,7 @@ export default {
const otherColumns = this.$refs.searchResultTable.columns.filter(col => col.id !== this.currentColumn.id) const otherColumns = this.$refs.searchResultTable.columns.filter(col => col.id !== this.currentColumn.id)
otherColumns.forEach(col => { col.filterable = false }) otherColumns.forEach(col => { col.filterable = false })
} else { } else {
const filterLabels = ['源站', '类型', '地区', '语言'] const filterLabels = ['源站', '语言']
const columns = this.$refs.searchResultTable.columns.filter(col => filterLabels.includes(col.label)) const columns = this.$refs.searchResultTable.columns.filter(col => filterLabels.includes(col.label))
columns.forEach(col => { col.filterable = true }) columns.forEach(col => { col.filterable = true })
} }
@@ -417,111 +579,141 @@ export default {
siteClick (siteName) { siteClick (siteName) {
this.list = [] this.list = []
this.site = this.sites.find(x => x.name === siteName) this.site = this.sites.find(x => x.name === siteName)
if (this.searchTxt.length > 0 && this.searchGroup === '站内') { if (this.searchGroup === '站内' && this.searchTxt) {
this.searchEvent() this.searchEvent()
return
} else { } else {
this.searchTxt = '' this.searchTxt = ''
this.show.find = false }
this.classList = [] this.showFind = false
this.type = {} this.classList = []
if (FILM_DATA_CACHE[this.site.key]) {
this.classList = FILM_DATA_CACHE[this.site.key].classList
this.classClick(this.type.name)
} else {
this.getClass().then(res => { this.getClass().then(res => {
this.classClick(this.classList[0].name) this.classList = res
// cache classList data
FILM_DATA_CACHE[this.site.key] = {
classList: this.classList
}
this.classClick(this.type.name)
}) })
} }
}, },
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) { classClick (className) {
this.show.classList = false
this.list = [] this.list = []
this.type = this.classList.find(x => x.name === className) this.type = this.classList.find(x => x.name === className)
this.getPage().then(res => { this.infiniteHandlerCount = 0
if (res) { if (!this.type) {
this.type = this.classList[0]
}
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 this.infiniteId += 1
} })
}) }
}, },
getClass () { getClass () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const key = this.site.key const key = this.site.key
// 屏蔽主分类
const classToHide = ['电影', '电影片', '电视剧', '连续剧', '综艺', '动漫']
zy.class(key).then(res => { zy.class(key).then(res => {
var allClass = [{ name: '最新', tid: 0 }] const allClass = [{ name: '最新', tid: 0 }]
res.class.forEach(element => { res.class.forEach(element => {
if (!this.setting.excludeRootClasses || !classToHide.includes(element.name)) { if (!this.containsClassFilterKeyword(element.name)) {
if (this.setting.excludeR18Films) { allClass.push(element)
const containKeyWord = this.containsR18Keywords(element.name)
if (!containKeyWord) {
allClass.push(element)
}
} else {
allClass.push(element)
}
} }
}) })
this.classList = allClass resolve(allClass)
this.show.class = true
this.pagecount = res.pagecount
this.type = this.classList[0]
resolve(true)
}).catch(err => { }).catch(err => {
reject(err) reject(err)
}) })
}) })
}, },
containsR18Keywords (name) { containsClassFilterKeyword (name) {
var containKeyWord = false let ret = false
if (!name) { // 主分类过滤, 检测关键词是否包含分类名
return containKeyWord 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
}, },
getPage () { toFlipPagecount () {
return new Promise((resolve, reject) => { return this.site.reverseOrder
const key = this.site.key
const type = this.type.tid
zy.page(key, type).then(res => {
this.pagecount = res.pagecount
this.show.body = true
resolve(true)
}).catch(err => {
reject(err)
})
})
}, },
infiniteHandler ($state) { infiniteHandler ($state) {
const key = this.site.key const key = this.site.key
const type = this.type.tid const typeTid = this.type.tid
const page = this.pagecount let page = this.pagecount
if (this.toFlipPagecount()) {
page = this.totalpagecount - this.pagecount + 1
}
this.statusText = ' ' this.statusText = ' '
if (key === undefined || page < 1 || type === undefined) { // OK资源前几类硬是去不掉 if (key === undefined || page < 1 || page > this.totalpagecount || typeTid === undefined) {
$state.complete() $state.complete()
this.statusText = '暂无数据' this.statusText = '暂无数据'
return false return false
} }
zy.list(key, page, type).then(res => { if (this.showToolbar && this.filteredList.length && this.filteredList.length < 10) {
if (res) { this.infiniteHandlerCount++
this.pagecount -= 1 }
const type = Object.prototype.toString.call(res) const interval = this.setting.view === 'picture' ? 1200 : 300
if (type === '[object Undefined]') { setTimeout(() => {
$state.complete() zy.list(key, page, typeTid).then(res => {
if (res) {
this.pagecount -= 1
const type = Object.prototype.toString.call(res)
if (type === '[object Array]') {
// 过滤掉无链接的项
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()
// 更新缓存数据
const cacheKey = this.site.key + '@' + typeTid
FILM_DATA_CACHE[cacheKey] = {
pagecount: this.pagecount,
recordcount: this.recordcount,
list: this.list
}
} }
if (type === '[object Array]') { })
// zy.list 返回的是按时间从旧到新排列, 我门需要翻转为从新到旧 }, (this.infiniteHandlerCount <= 1 ? 0 : this.infiniteHandlerCount - 1) * interval)
this.list.push(...res.reverse())
}
if (type === '[object Object]') {
this.list.push(res)
}
$state.loaded()
// 数据更新后,刷新页面
if (this.$refs.filmWaterfall) {
this.$refs.filmWaterfall.refresh()
}
} else {
$state.complete()
this.statusText = '暂无数据'
}
})
}, },
detailEvent (site, e) { detailEvent (site, e) {
this.detail = { this.detail = {
@@ -538,9 +730,6 @@ export default {
} else { } else {
this.video = { key: site.key, info: { id: e.id, name: e.name, index: 0, site: site } } 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' this.view = 'Play'
}, },
async starEvent (site, e) { async starEvent (site, e) {
@@ -548,17 +737,19 @@ export default {
if (db) { if (db) {
this.$message.info('已存在') this.$message.info('已存在')
} else { } else {
zy.detail(site.key, e.id).then(detailRes => { const cacheKey = site.key + '@' + e.id
const docs = { if (!this.DetailCache[cacheKey]) {
key: site.key, this.DetailCache[cacheKey] = await zy.detail(site.key, e.id)
ids: e.id, }
site: site, const docs = {
name: e.name, key: site.key,
detail: detailRes ids: e.id,
} site: site,
star.add(docs).then(res => { name: e.name,
this.$message.success('收藏成功') detail: this.DetailCache[cacheKey]
}) }
star.add(docs).then(res => {
this.$message.success('收藏成功')
}) })
} }
}, },
@@ -569,58 +760,20 @@ export default {
info: e info: e
} }
}, },
downloadEvent (site, row) { async downloadEvent (site, row) {
zy.download(site.key, row.id).then(res => { const db = await history.find({ site: site.key, ids: row.id })
if (res && res.length > 0) { let videoFlag
const text = res.m3u8List if (db) videoFlag = db.videoFlag
if (text) { zy.download(site.key, row.id, videoFlag).then(res => {
const list = text.split('#') clipboard.writeText(res.downloadUrls)
let downloadUrl = res.name + '\n' this.$message.success(res.info)
for (const i of list) { }).catch((err) => {
const url = encodeURI(i.split('$')[1]) this.$message.error(err.info)
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
this.$message.warning('没有查询到下载链接.')
}
} else {
let m3u8List = []
const dd = row.dl.dd
const type = Object.prototype.toString.call(dd)
if (type === '[object Array]') {
for (const i of dd) {
if (i._flag.indexOf('m3u8') >= 0) {
m3u8List = i._t.split('#')
}
}
} else {
m3u8List = dd._t.split('#')
}
let downloadUrl = row.name + '\n'
for (const i of m3u8List) {
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『M3U8』格式的链接已复制, 快去下载吧!')
}
}) })
}, },
changeView () {
if (this.view === 'Film') {
this.getAllSites()
if (this.setting.view === 'picture') {
if (this.$refs.filmWaterfall) {
this.$refs.filmWaterfall.refresh()
}
}
}
},
querySearch (queryString, cb) { querySearch (queryString, cb) {
var searchList = this.searchList.slice(0, -1) const searchList = this.searchList.slice(0, -1)
var results = queryString ? searchList.filter(this.createFilter(queryString)) : this.searchList const results = queryString ? searchList.filter(this.createFilter(queryString)) : this.searchList
// 调用 callback 返回建议列表的数据 // 调用 callback 返回建议列表的数据
cb(results) cb(results)
}, },
@@ -651,6 +804,9 @@ export default {
this.searchList.push({ id: this.searchList.length + 1, keywords: '清除历史记录...' }) this.searchList.push({ id: this.searchList.length + 1, keywords: '清除历史记录...' })
}) })
}, },
stopSearchEvent () {
this.searchRunning = false
},
searchEvent () { searchEvent () {
const wd = this.searchTxt const wd = this.searchTxt
if (this.setting.searchGroup !== this.searchGroup) { if (this.setting.searchGroup !== this.searchGroup) {
@@ -659,79 +815,91 @@ export default {
} }
if (!wd) return if (!wd) return
this.searchID += 1 this.searchID += 1
var searchSites = []
if (this.searchGroup === '站内') searchSites.push(this.site)
if (this.searchGroup === '全站') searchSites = this.sites
if (!searchSites.length) {
searchSites = this.sites.filter(site => site.group === this.searchGroup)
}
this.searchContents = [] this.searchContents = []
this.pagecount = 0 this.showFind = true
this.show.find = true
this.show.class = false
this.statusText = ' ' this.statusText = ' '
if (wd) { this.searchRunning = true
searchSites.forEach(site => { this.siteSearchCount = 0
const id = this.searchID this.searchSites.forEach(site => {
zy.search(site.key, wd).then(res => { const id = this.searchID
if (id !== this.searchID) return zy.search(site.key, wd).then(res => {
const type = Object.prototype.toString.call(res) if (id !== this.searchID || !this.searchRunning) return
if (type === '[object Array]') { const type = Object.prototype.toString.call(res)
res.forEach(element => { if (type === '[object Array]') {
zy.detail(site.key, element.id).then(detailRes => { let count = 0
detailRes.site = site res.forEach(element => {
if (id !== this.searchID) return zy.detail(site.key, element.id).then(detailRes => {
if (id !== this.searchID || !this.searchRunning) return
detailRes.site = site
if (this.isValidSearchResult(detailRes)) {
this.searchContents.push(detailRes) this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) { this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id return a.site.id - b.site.id
}) })
this.statusText = '暂无数据' }
}) }).finally(() => { count++; if (count === res.length) { this.siteSearchCount++; this.statusText = '暂无数据' } })
}) })
} } else if (type === '[object Object]') {
if (type === '[object Object]') { zy.detail(site.key, res.id).then(detailRes => {
zy.detail(site.key, res.id).then(detailRes => { if (id !== this.searchID || !this.searchRunning) return
detailRes.site = site detailRes.site = site
if (id !== this.searchID) return if (this.isValidSearchResult(detailRes)) {
this.searchContents.push(detailRes) this.searchContents.push(detailRes)
this.searchContents.sort(function (a, b) { this.searchContents.sort(function (a, b) {
return a.site.id - b.site.id return a.site.id - b.site.id
}) })
this.statusText = '暂无数据' }
}) }).finally(() => { this.siteSearchCount++; this.statusText = '暂无数据' })
} } else if (res === undefined) {
}) this.siteSearchCount++
}) this.statusText = '暂无数据'
} if (this.searchGroup === '站内') this.$message.info('没有查询到数据!')
}
}).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 () { searchAndRecord () {
this.addSearchRecord() this.addSearchRecord()
this.searchEvent() this.searchEvent()
}, },
searchChangeEvent () { searchChangeEvent () {
if (this.searchTxt.length >= 1) { if (!this.searchTxt.length) {
this.show.class = false
} else {
this.show.class = true
this.searchContents = [] this.searchContents = []
this.show.find = false this.showFind = false
if (this.setting.view === 'picture' && this.$refs.filmWaterfall) {
this.$refs.filmWaterfall.refresh()
} else {
this.getClass().then(res => {
if (res) {
this.infiniteId += 1
}
})
}
} }
}, },
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 () { getAllSites () {
sites.all().then(res => { sites.all().then(res => {
if (res.length <= 0) { if (res.length <= 0) {
this.site = {} this.$message.warning('检测到视频源未能正常加载, 即将重置源.')
this.type = {} this.getDefaultSites()
this.list = []
} else { } else {
this.sites = res.filter(item => item.isActive) this.sites = res.filter(item => item.isActive)
if (this.site === undefined || !this.sites.some(x => x.key === this.site.key)) { if (this.site === undefined || !this.sites.some(x => x.key === this.site.key)) {
@@ -739,35 +907,23 @@ export default {
this.selectedSiteName = this.sites[0].name 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 this.searchGroup = this.setting.searchGroup
if (this.searchGroup === undefined) setting.find().then(res => { this.searchGroup = res.searchGroup }) if (this.searchGroup === undefined) setting.find().then(res => { this.searchGroup = res.searchGroup })
}) })
},
getSearchViewMode () {
setting.find().then(res => {
this.searchViewMode = res.searchViewMode === undefined ? 'picture' : res.searchViewMode
})
} }
}, },
created () { created () {
this.getAllSites() this.getAllSites()
this.getSearchHistory() this.getSearchHistory()
this.getSearchViewMode()
}, },
mounted () { mounted () {
window.addEventListener('resize', () => { addEventListener('resize', () => {
if (this.$refs.filmWaterfall && this.view === 'Film') { setTimeout(() => {
this.$refs.filmWaterfall.resize() this.showTableLastColumn = window.outerWidth >= 1200
this.$refs.filmWaterfall.refresh() if (this.$refs.filmWaterfall) this.$refs.filmWaterfall.resize()
setTimeout(() => { if (this.$refs.filmSearchWaterfall) this.$refs.filmSearchWaterfall.resize()
this.$refs.filmWaterfall.refresh() }, 500)
}, 500) })
}
}, false)
} }
} }
</script> </script>

View File

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

View File

@@ -1,14 +1,63 @@
<template> <template>
<div class="listpage" id="history"> <div class="listpage" id="history">
<div class="listpage-header" id="history-header"> <div class="listpage-header" id="history-header">
<el-switch v-model="viewMode" 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="exportHistory" icon="el-icon-upload2"></el-button> <el-button @click.stop="importHistory" icon="el-icon-download" title="支持同时导入多个文件"></el-button>
<el-button @click.stop="importHistory" icon="el-icon-download">导入</el-button> <el-button @click.stop="removeSelectedItems" icon="el-icon-delete-solid">{{ multipleSelection.length === 0 ? "清空" : "删除所选" }}</el-button>
<el-button @click.stop="clearAllHistory" icon="el-icon-delete-solid">清空</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>
<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="listpage-body" id="history-body">
<div class="show-table" id="history-table" v-show="viewMode === 'table'"> <div class="show-table" id="history-table" v-if="setting.historyViewMode === 'table'">
<el-table size="mini" fit height="100%" :data="history" row-key="id" @row-click="detailEvent"> <el-table size="mini" fit height="100%"
:data="filteredList"
row-key="id"
ref="historyTable"
@select="selectionCellClick"
@selection-change="handleSelectionChange"
@row-click="detailEvent">
<el-table-column
type="selection">
</el-table-column>
<el-table-column <el-table-column
prop="name" prop="name"
label="片名"> label="片名">
@@ -26,16 +75,17 @@
width="180" width="180"
label="观看至"> label="观看至">
<template slot-scope="scope"> <template slot-scope="scope">
<span v-if="scope.row.detail && scope.row.detail.m3u8List && scope.row.detail.m3u8List.length > 1"> <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.m3u8List.length}}) {{ scope.row.index + 1 }}({{scope.row.detail.fullList[0].list.length}})
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-if="history.some(e => e.time)" <el-table-column v-if="list.some(e => e.time)"
width="150" width="200"
label="时间进度"> label="时间进度">
<template slot-scope="scope"> <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> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@@ -52,8 +102,8 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
<div class="show-picture" id="star-picture" v-show="viewMode === 'picture'"> <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="{ :breakpoints="{
1200: { //当屏幕宽度小于等于1200 1200: { //当屏幕宽度小于等于1200
rowPerView: 4, rowPerView: 4,
@@ -70,6 +120,9 @@
<template slot="item" slot-scope="props"> <template slot="item" slot-scope="props">
<div class="card"> <div class="card">
<div class="img"> <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)"> <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">
<div class="operate-wrap"> <div class="operate-wrap">
@@ -83,10 +136,11 @@
<div class="name" @click="detailEvent(props.data)">{{props.data.name}}</div> <div class="name" @click="detailEvent(props.data)">{{props.data.name}}</div>
<div class="info"> <div class="info">
<span v-if="props.data.time && props.data.duration"> <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>
<span v-if="props.data.detail && props.data.detail.m3u8List !== undefined && props.data.detail.m3u8List.length > 1"> <span v-if="props.data.onlinePlay">在线解析</span>
{{ props.data.index + 1 }}({{props.data.detail.m3u8List.length}}) <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> </span>
</div> </div>
</div> </div>
@@ -100,19 +154,34 @@
import { mapMutations } from 'vuex' import { mapMutations } from 'vuex'
import { history, sites, setting } from '../lib/dexie' import { history, sites, setting } from '../lib/dexie'
import zy from '../lib/site/tools' import zy from '../lib/site/tools'
import Sortable from 'sortablejs'
import { remote } from 'electron'
import fs from 'fs' import fs from 'fs'
import Waterfall from 'vue-waterfall-plugin' import Waterfall from 'vue-waterfall-plugin'
const remote = require('@electron/remote')
const { clipboard } = require('electron') const { clipboard } = require('electron')
export default { export default {
name: 'history', name: 'history',
data () { data () {
return { return {
history: [], list: [],
sites: [], sites: [],
viewMode: setting.historyViewMode shiftDown: false,
selectionBegin: '',
selectionEnd: '',
multipleSelection: [],
areas: [],
types: [],
filteredList: [],
// Update
numNoUpdate: 0,
// Toolbar
showToolbar: false,
selectedAreas: [],
selectedTypes: [],
sortKeyword: '',
sortKeywords: ['按片名', '按上映年份', '按更新时间', '按完成度'],
selectedYears: { start: 0, end: new Date().getFullYear() },
onlyShowItemsHasUpdate: false
} }
}, },
components: { components: {
@@ -150,22 +219,181 @@ export default {
set (val) { set (val) {
this.SET_SHARE(val) this.SET_SHARE(val)
} }
},
setting: {
get () {
return this.$store.getters.getSetting
},
set (val) {
this.SET_SETTING(val)
}
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
} }
}, },
watch: { watch: {
view () { view () {
this.getAllhistory() if (this.view === 'History') {
this.getAllsites() this.getAllhistory()
if (this.$refs.historyWaterfall) { this.getAllsites()
this.$refs.historyWaterfall.refresh() 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: { methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']), ...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) { fmtMSS (s) {
return (s - (s %= 60)) / 60 + (s > 9 ? ':' : ':0') + 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.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))
})
this.selectionBegin = this.selectionEnd = ''
return
}
if (selection.includes(row)) {
this.selectionBegin = row.id
} else {
this.selectionBegin = ''
}
},
handleSelectionChange (rows) {
this.multipleSelection = rows
},
removeSelectedItems () {
if (!this.multipleSelection.length) this.multipleSelection = this.list
this.multipleSelection.forEach(e => history.remove(e.id))
this.multipleSelection = []
this.getAllhistory()
this.updateDatabase()
},
detailEvent (e) { detailEvent (e) {
this.detail = { this.detail = {
show: true, show: true,
@@ -175,6 +403,9 @@ export default {
name: e.name name: e.name
} }
} }
if (e.hasUpdate) {
this.clearHasUpdateFlag(e)
}
}, },
async playEvent (e) { async playEvent (e) {
const db = await history.find({ site: e.site, ids: e.ids }) const db = await history.find({ site: e.site, ids: e.ids })
@@ -183,46 +414,37 @@ export default {
} else { } else {
this.video = { key: e.site, info: { id: e.ids, name: e.name, index: 0 } } this.video = { key: e.site, info: { id: e.ids, name: e.name, index: 0 } }
} }
zy.detail(e.site, e.ids).then(detailRes => { if (e.hasUpdate) {
this.video.detail = detailRes this.clearHasUpdateFlag(e)
}) }
this.view = 'Play' 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) { shareEvent (e) {
this.share = { this.share = {
show: true, show: true,
key: e.site, key: e.site,
info: e info: e.detail
} }
}, },
downloadEvent (e) { downloadEvent (e) {
zy.download(e.site, e.ids).then(res => { zy.download(e.site, e.ids, e.videoFlag).then(res => {
if (res && res.m3u8List) { clipboard.writeText(res.downloadUrls)
const list = res.m3u8List.split('#') this.$message.success(res.info)
let downloadUrl = '' }).catch((err) => {
for (const i of list) { this.$message.error(err.info)
const url = encodeURI(i.split('$')[1])
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
zy.detail(e.site, e.ids).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』格式的链接已复制, 快去下载吧!')
})
}
}) })
}, },
exportHistory () { exportHistory () {
this.getAllhistory() this.getAllhistory()
const arr = [...this.history] const arr = [...this.list]
const str = JSON.stringify(arr, null, 2) const str = JSON.stringify(arr, null, 2)
const options = { const options = {
filters: [ filters: [
@@ -231,6 +453,7 @@ export default {
} }
remote.dialog.showSaveDialog(options).then(result => { remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) { if (!result.canceled) {
if (!result.filePath.endsWith('.json')) result.filePath += '.json'
fs.writeFileSync(result.filePath, str) fs.writeFileSync(result.filePath, str)
this.$message.success('已保存成功') this.$message.success('已保存成功')
} }
@@ -248,8 +471,14 @@ export default {
remote.dialog.showOpenDialog(options).then(result => { remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) { if (!result.canceled) {
result.filePaths.forEach(file => { result.filePaths.forEach(file => {
var str = fs.readFileSync(file) const str = fs.readFileSync(file)
const json = JSON.parse(str) 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 => { history.bulkAdd(json).then(res => {
this.$message.success('导入成功') this.$message.success('导入成功')
this.getAllhistory() this.getAllhistory()
@@ -258,14 +487,9 @@ export default {
} }
}) })
}, },
clearAllHistory () {
history.clear().then(res => {
this.history = []
})
},
getAllhistory () { getAllhistory () {
history.all().then(res => { history.all().then(res => {
this.history = res.reverse() this.list = res.reverse()
}) })
}, },
getAllsites () { getAllsites () {
@@ -274,7 +498,7 @@ export default {
}) })
}, },
getSiteName (key) { getSiteName (key) {
var site = this.sites.find(e => e.key === key) const site = this.sites.find(e => e.key === key)
if (site) { if (site) {
return site.name return site.name
} }
@@ -286,54 +510,37 @@ export default {
this.$message.warning('删除历史记录失败, 错误信息: ' + err) this.$message.warning('删除历史记录失败, 错误信息: ' + err)
}) })
}, },
updateDatabase (data) { updateDatabase () {
history.clear().then(res => { history.clear().then(res => {
var id = length let id = length
data.forEach(ele => { this.list.forEach(ele => {
ele.id = id ele.id = id
id -= 1 id -= 1
history.add(ele) history.add(ele)
}) })
}) })
}, },
rowDrop () { showShiftPrompt () {
const tbody = document.getElementById('history-table').querySelector('.el-table__body-wrapper tbody') if (this.setting.shiftTooltipLimitTimes === undefined) this.setting.shiftTooltipLimitTimes = 5
const _this = this if (this.setting.shiftTooltipLimitTimes) {
Sortable.create(tbody, { this.$message.info('多选时支持shift快捷键')
onEnd ({ newIndex, oldIndex }) { this.setting.shiftTooltipLimitTimes--
const currRow = _this.history.splice(oldIndex, 1)[0] setting.find().then(res => {
_this.history.splice(newIndex, 0, currRow) res.shiftTooltipLimitTimes = this.setting.shiftTooltipLimitTimes
_this.updateDatabase(_this.history) setting.update(res)
} })
}) }
},
getViewMode () {
setting.find().then(res => {
this.viewMode = res.historyViewMode
})
},
updateViewMode () {
setting.find().then(res => {
res.historyViewMode = this.viewMode
setting.update(res)
})
} }
}, },
mounted () { mounted () {
this.rowDrop() addEventListener('keydown', code => { if (code.keyCode === 16) this.shiftDown = true })
window.addEventListener('resize', () => { addEventListener('keyup', code => { if (code.keyCode === 16) this.shiftDown = false })
if (this.$refs.historyWaterfall && this.view === 'History') { addEventListener('resize', () => {
this.$refs.historyWaterfall.resize() setTimeout(() => { if (this.$refs.historyWaterfall) this.$refs.historyWaterfall.resize() }, 500)
this.$refs.historyWaterfall.refresh() })
setTimeout(() => {
this.$refs.historyWaterfall.refresh()
}, 500)
}
}, false)
}, },
created () { created () {
this.getAllhistory() this.getAllhistory()
this.getViewMode()
} }
} }
</script> </script>

View File

@@ -2,17 +2,17 @@
<div class="listpage" id="iptv"> <div class="listpage" id="iptv">
<div class="listpage-header" id="iptv-header" v-show="!enableBatchEdit"> <div class="listpage-header" id="iptv-header" v-show="!enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理及频道调整"></el-switch> <el-switch v-model="enableBatchEdit" active-text="批处理及频道调整"></el-switch>
<el-button @click.stop="exportChannels" icon="el-icon-upload2" >导出</el-button> <el-button @click.stop="exportChannels" icon="el-icon-upload2" title="导出m3u时必须手动添加扩展名要保存频道配置信息请选择json格式">导出</el-button>
<el-button @click.stop="importChannels" icon="el-icon-download">导入</el-button> <el-button @click.stop="importChannels" icon="el-icon-download" title='支持同时导入多个文件,导入m3u时网址可带参数、含有"#"号时自动分割'>导入</el-button>
<el-button @click="checkAllChannels" icon="el-icon-refresh" :loading="checkAllChannelsLoading">检测{{ this.checkAllChannelsLoading ? this.checkProgress + '/' + this.iptvList.length : '' }}</el-button> <el-button @click="checkAllChannels" icon="el-icon-refresh" :loading="checkAllChannelsLoading" title="可在后台运行">检测{{ this.checkAllChannelsLoading ? this.checkProgress + '/' + this.iptvList.length : '' }}</el-button>
<el-button @click.stop="resetChannelsEvent" icon="el-icon-refresh-left">重置</el-button> <el-button @click.stop="resetChannelsEvent" icon="el-icon-refresh-left">重置</el-button>
</div> </div>
<div class="listpage-header" id="iptv-header" v-show="enableBatchEdit"> <div class="listpage-header" id="iptv-header" v-show="enableBatchEdit">
<el-switch v-model="enableBatchEdit" active-text="批处理及频道调整"></el-switch> <el-switch v-model="enableBatchEdit" active-text="批处理及频道调整"></el-switch>
<el-input placeholder="新组名/新频道名" v-model="inputContent"></el-input> <el-input placeholder="新组名/新频道名" v-model="inputContent"></el-input>
<el-switch v-model="batchIsActive" active-text="启用"></el-switch> <el-switch v-model="batchIsActive" active-text="启用"></el-switch>
<el-button type="primary" icon="el-icon-edit" @click.stop="saveBatchEdit">保存分组与开关状态</el-button> <el-button type="primary" icon="el-icon-edit" @click.stop="saveBatchEdit" title="输入框组名为空时仅保存开关状态">保存分组与开关状态</el-button>
<el-button type="primary" icon="el-icon-film" @click.stop="mergeChannel">{{ this.multipleSelection.length === 1 ? '频道重命名' : '频道合并' }}</el-button> <el-button type="primary" icon="el-icon-film" @click.stop="mergeChannel" title="勾选单个时可重命名频道">{{ this.multipleSelection.length === 1 ? '频道重命名' : '频道合并' }}</el-button>
<el-button @click.stop="removeSelectedChannels" icon="el-icon-delete-solid">删除</el-button> <el-button @click.stop="removeSelectedChannels" icon="el-icon-delete-solid">删除</el-button>
</div> </div>
<div class="listpage-body" id="iptv-table"> <div class="listpage-body" id="iptv-table">
@@ -107,12 +107,13 @@
</template> </template>
<script> <script>
import { mapMutations } from 'vuex' import { mapMutations } from 'vuex'
import { iptv, channelList } from '../lib/dexie' import { iptv, channelList, setting } from '../lib/dexie'
import { iptv as defaultChannels } from '../lib/dexie/initData' import { iptv as defaultChannels } from '../lib/dexie/initData'
import zy from '../lib/site/tools' import zy from '../lib/site/tools'
import { remote } from 'electron'
import fs from 'fs' import fs from 'fs'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import axios from 'axios'
const remote = require('@electron/remote')
export default { export default {
name: 'iptv', name: 'iptv',
data () { data () {
@@ -143,8 +144,13 @@ export default {
this.SET_VIEW(val) this.SET_VIEW(val)
} }
}, },
setting () { setting: {
return this.$store.getters.getSetting get () {
return this.$store.getters.getSetting
},
set (val) {
this.SET_SETTING(val)
}
}, },
video: { video: {
get () { get () {
@@ -163,9 +169,9 @@ export default {
}, },
getFilters () { getFilters () {
const groups = [...new Set(this.channelList.map(iptv => iptv.group))] const groups = [...new Set(this.channelList.map(iptv => iptv.group))]
var filters = [] const filters = []
groups.forEach(g => { groups.forEach(g => {
var doc = { const doc = {
text: g, text: g,
value: g value: g
} }
@@ -175,11 +181,6 @@ export default {
} }
}, },
watch: { watch: {
view () {
if (this.view === 'IPTV' && !this.checkAllChannelsLoading) {
this.getChannelList()
}
},
enableBatchEdit () { enableBatchEdit () {
if (this.checkAllChannelsLoading) { if (this.checkAllChannelsLoading) {
this.$message.info('正在检测, 请勿操作.') this.$message.info('正在检测, 请勿操作.')
@@ -187,6 +188,15 @@ export default {
return return
} }
if (this.enableBatchEdit) { if (this.enableBatchEdit) {
if (this.setting.shiftTooltipLimitTimes === undefined) this.setting.shiftTooltipLimitTimes = 5
if (this.setting.shiftTooltipLimitTimes) {
this.$message.info('多选时支持shift快捷键')
this.setting.shiftTooltipLimitTimes--
setting.find().then(res => {
res.shiftTooltipLimitTimes = this.setting.shiftTooltipLimitTimes
setting.update(res)
})
}
this.$nextTick(() => { this.$nextTick(() => {
this.expandedRows.forEach(e => this.$refs.iptvTable.toggleRowExpansion(e, false)) this.expandedRows.forEach(e => this.$refs.iptvTable.toggleRowExpansion(e, false))
}) })
@@ -197,7 +207,7 @@ export default {
} }
}, },
methods: { methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']), ...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE', 'SET_SETTING']),
sortByLocaleCompare (a, b) { sortByLocaleCompare (a, b) {
return a.localeCompare(b, 'zh') return a.localeCompare(b, 'zh')
}, },
@@ -248,7 +258,7 @@ export default {
}, },
mergeChannel () { mergeChannel () {
if (this.inputContent && this.multipleSelection.length) { if (this.inputContent && this.multipleSelection.length) {
var channels = [] let channels = []
const id = this.multipleSelection[0].id const id = this.multipleSelection[0].id
this.multipleSelection.forEach(ele => { this.multipleSelection.forEach(ele => {
channels = channels.concat(ele.channels) channels = channels.concat(ele.channels)
@@ -265,8 +275,13 @@ export default {
if (e.url) { if (e.url) {
this.video = { iptv: e } this.video = { iptv: e }
} else { } else {
const prefer = e.prefer ? e.channels.find(c => c.id === e.prefer) : e.channels.filter(c => c.isActive)[0] let prefer
if (!prefer) return 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.video = { iptv: prefer }
} }
this.view = 'Play' this.view = 'Play'
@@ -289,6 +304,7 @@ export default {
ele.channels.splice(ele.channels.findIndex(e => e.id === row.id), 1) ele.channels.splice(ele.channels.findIndex(e => e.id === row.id), 1)
channelList.remove(row.channelID) channelList.remove(row.channelID)
if (ele.channels.length) { if (ele.channels.length) {
if (ele.prefer === row.id) delete ele.prefer
if (ele.channels.length === 1) ele.hasChildren = false if (ele.channels.length === 1) ele.hasChildren = false
channelList.add(ele) channelList.add(ele)
this.$set(this.$refs.iptvTable.store.states.lazyTreeNodeMap, ele.id, ele.channels) this.$set(this.$refs.iptvTable.store.states.lazyTreeNodeMap, ele.id, ele.channels)
@@ -311,13 +327,14 @@ export default {
remote.dialog.showSaveDialog(options).then(result => { remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) { if (!result.canceled) {
if (result.filePath.endsWith('m3u')) { if (result.filePath.endsWith('m3u')) {
var writer = require('m3u').extendedWriter() const writer = require('m3u').extendedWriter()
this.iptvList.forEach(e => { this.iptvList.forEach(e => {
writer.file(e.url, -1, e.name) writer.file(e.url, -1, e.name)
}) })
fs.writeFileSync(result.filePath, writer.toString()) fs.writeFileSync(result.filePath, writer.toString())
this.$message.success('已保存成功') this.$message.success('已保存成功')
} else { } else {
if (!result.filePath.endsWith('.json')) result.filePath += '.json'
const arr = [...this.channelList] // 要保存channelList必须选json const arr = [...this.channelList] // 要保存channelList必须选json
const str = JSON.stringify(arr, null, 2) const str = JSON.stringify(arr, null, 2)
fs.writeFileSync(result.filePath, str) fs.writeFileSync(result.filePath, str)
@@ -333,10 +350,55 @@ export default {
this.$message.info('正在检测, 请勿操作.') this.$message.info('正在检测, 请勿操作.')
return false 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 = { const options = {
filters: [ filters: [
{ name: 'm3u file', extensions: ['m3u', 'm3u8'] }, { name: '支持的文件格式', extensions: ['m3u', 'm3u8', 'json', 'txt'] }
{ name: 'JSON file', extensions: ['json'] }
], ],
properties: ['openFile', 'multiSelections'] properties: ['openFile', 'multiSelections']
} }
@@ -350,11 +412,12 @@ export default {
const parser = require('iptv-playlist-parser') const parser = require('iptv-playlist-parser')
const playlist = fs.readFileSync(file, { encoding: 'utf-8' }) const playlist = fs.readFileSync(file, { encoding: 'utf-8' })
const result = parser.parse(playlist) const result = parser.parse(playlist)
const supportFormats = /\.(m3u8|flv)$/
result.items.forEach(ele => { result.items.forEach(ele => {
const urls = ele.url.split('#').filter(e => e.startsWith('http')) // 网址带#时自动分割 const urls = ele.url.split('#').filter(e => e.startsWith('http')) // 网址带#时自动分割
urls.forEach(url => { urls.forEach(url => {
if (ele.name && url && new URL.URL(url).pathname.endsWith('.m3u8')) { // 网址可能带参数 if (ele.name && url && (supportFormats.test(url) || supportFormats.test(new URL.URL(url).pathname))) { // 网址可能带参数
var doc = { const doc = {
id: id, id: id,
name: ele.name, name: ele.name,
url: url, url: url,
@@ -372,8 +435,9 @@ export default {
this.updateChannelList() this.updateChannelList()
}) })
}) })
} else { this.$message.success('导入成功')
// Import Json file }
if (file.endsWith('json')) {
const importedList = JSON.parse(fs.readFileSync(file)) const importedList = JSON.parse(fs.readFileSync(file))
importedList.forEach(ele => { importedList.forEach(ele => {
const commonEle = this.channelList.find(e => e.name === ele.name) const commonEle = this.channelList.find(e => e.name === ele.name)
@@ -387,21 +451,51 @@ export default {
} }
}) })
this.updateDatabase() 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('导入成功')
} }
}) })
}, },
determineGroup (name) { determineGroup (name) {
if (name.toLowerCase().includes('cctv') && (name.includes('蓝光') || name.includes('高清'))) { if (name.toLowerCase().includes('cctv') || name.toLowerCase().includes('cgtn')) {
return '央视高清'
} else if (name.toLowerCase().includes('cctv')) {
return '央视' return '央视'
} else if (name.includes('香港') || name.includes('澳门') || name.includes('台湾') || name.includes('凤凰') || name.includes('翡翠')) {
return '港澳台'
} else if (name.includes('卫视')) { } else if (name.includes('卫视')) {
return '卫视' return '卫视'
} else if (name.includes('香港') || name.includes('澳门') || name.includes('台湾') || name.includes('凤凰')) {
return '港澳台'
} else if (name.includes('高清') || name.includes('蓝光') || name.includes('1080P')) { } else if (name.includes('高清') || name.includes('蓝光') || name.includes('1080P')) {
return '高清' return '高清'
} else { } else {
@@ -430,12 +524,12 @@ export default {
res = res.filter(o => !this.iptvList.find(e => o.url === e.url)) res = res.filter(o => !this.iptvList.find(e => o.url === e.url))
const resClone = JSON.parse(JSON.stringify(res)) const resClone = JSON.parse(JSON.stringify(res))
const uniqueChannelName = {} const uniqueChannelName = {}
for (var i = 0; i < resClone.length; i++) { for (let i = 0; i < resClone.length; i++) {
var channelName = resClone[i].name.trim().replace(/[- ]?(1080p|蓝光|超清|高清|标清|hd|cq|4k)(\d{1,2})?$/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 (channelName.match(/cctv/i)) channelName = channelName.replace('-', '')
if (Object.keys(uniqueChannelName).some(name => channelName.match(new RegExp(`${name}(1080p|4k|(?!\\d))`, 'i')))) continue // 避免重复 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') 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)) { if (resClone[j].name.match(/cctv/i)) {
resClone[j].name = resClone[j].name.replace('-', '') resClone[j].name = resClone[j].name.replace('-', '')
} }
@@ -505,7 +599,7 @@ export default {
}) })
}, },
resetId (channelList) { resetId (channelList) {
var id = 1 let id = 1
channelList.forEach(ele => { channelList.forEach(ele => {
ele.id = id ele.id = id
id += 1 id += 1
@@ -552,6 +646,7 @@ export default {
} }
}, },
async checkAllChannels () { async checkAllChannels () {
if (this.checkAllChannelsLoading) return
this.checkAllChannelsLoading = true this.checkAllChannelsLoading = true
this.stopFlag = false this.stopFlag = false
this.checkProgress = 0 this.checkProgress = 0
@@ -562,10 +657,11 @@ export default {
await this.checkChannelsBySite(other).then(res => { await this.checkChannelsBySite(other).then(res => {
this.checkAllChannelsLoading = false this.checkAllChannelsLoading = false
this.getChannelList() this.getChannelList()
if (!this.stopFlag) this.$message.success('直播频道批量检测已完成!')
}) })
}, },
async checkChannelsBySite (channels) { async checkChannelsBySite (channels) {
var siteList = {} const siteList = {}
channels.forEach(channel => { channels.forEach(channel => {
const site = channel.url.split('/')[2] const site = channel.url.split('/')[2]
if (siteList[site]) { if (siteList[site]) {
@@ -582,44 +678,50 @@ export default {
await this.checkSingleChannel(c) await this.checkSingleChannel(c)
} }
}, },
async checkSingleChannel (channel) { async checkSingleChannel (channel, force = false) {
if (this.setting.allowPassWhenIptvCheck && !channel.isActive) { if (this.stopFlag) {
this.checkProgress += 1 this.checkProgress += 1
return return
} }
channel.status = ' '
const ele = this.channelList.find(e => e.id === channel.channelID) const ele = this.channelList.find(e => e.id === channel.channelID)
if (this.stopFlag) { if (!force && this.setting.allowPassWhenIptvCheck && (!channel.isActive || !ele.isActive)) {
this.checkProgress += 1 if (!ele.isActive) {
return channel.status ele.status = '跳过'
} } else if (!channel.isActive) {
const flag = await zy.checkChannel(channel.url) channel.status = '跳过'
this.checkProgress += 1 }
ele.hasCheckedNum++
if (flag) {
channel.status = '可用'
} else { } else {
channel.status = '失效' channel.status = ' '
channel.isActive = false const flag = await zy.checkChannel(channel.url)
if (this.setting.autocleanWhenIptvCheck) { if (flag) {
ele.channels.splice(ele.channels.findIndex(e => e.id === channel.id), 1) channel.status = '可用'
ele.hasCheckedNum-- } 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) { if (ele.hasCheckedNum === ele.channels.length) {
ele.status = ele.channels.some(channel => channel.status === '可用') ? '可用' : '失效' if (ele.status === ' ') {
if (ele.status === '失效') ele.isActive = false ele.status = ele.channels.some(channel => channel.status === '可用') ? '可用' : '失效'
if (ele.status === '失效') ele.isActive = false
}
channelList.remove(channel.channelID) channelList.remove(channel.channelID)
if (ele.channels.length === 1) ele.hasChildren = false if (ele.channels.length === 1) ele.hasChildren = false
if (ele.channels.length) channelList.add(ele) if (ele.channels.length) channelList.add(ele)
} }
return channel.status
}, },
async checkChannel (row) { async checkChannel (row) {
if (row.channels) { if (row.channels) {
row.status = ' ' row.status = ' '
row.hasCheckedNum = 0 row.hasCheckedNum = 0
row.channels.forEach(e => this.checkSingleChannel(e)) row.channels.forEach(e => this.checkSingleChannel(e, true))
} else { } else {
this.checkSingleChannel(row) this.checkSingleChannel(row)
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -5,22 +5,8 @@
<div class="info"> <div class="info">
<a @click="linkOpen('http://zyplayer.fun/')">官网</a> <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')">Github</a>
<a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/issues')">当前版本v{{pkg.version}} 反馈</a> <a @click="linkOpen('https://github.com/Hunlongyu/ZY-Player/discussions/776')">软件完全免费如遇收费请立即给差评并退费</a>
<a style="color:#38dd77" @click="quitAndInstall()" v-show="latestVersion !== pkg.version" >最新版本v{{latestVersion}}</a> <a style="color:#38dd77" @click="openUpdate()" v-show="update.find" >最新版本v{{update.version}}</a>
</div>
<div class="view">
<div class="title">视图</div>
<div class="view-box">
<div class="zy-select" @mouseleave="show.view = false">
<div class="vs-placeholder" @click="show.view = true">默认视图</div>
<div class="vs-options" v-show="show.view">
<ul class="zy-scroll">
<li :class="d.view === 'picture' ? 'active' : ''" @click="changeView('picture')">海报</li>
<li :class="d.view === 'table' ? 'active' : ''" @click="changeView('table')">列表</li>
</ul>
</div>
</div>
</div>
</div> </div>
<div class="shortcut"> <div class="shortcut">
<div class="title">快捷键</div> <div class="title">快捷键</div>
@@ -40,13 +26,16 @@
<div class="zy-select"> <div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="impShortcut">导入</div> <div class="vs-placeholder vs-noAfter" @click="impShortcut">导入</div>
</div> </div>
<div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="resetShortcut">重置</div>
</div>
</div> </div>
</div> </div>
<div class="shortcut"> <div class="shortcut" title="清理缓存后图片资源需重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小">
<div class="title">缓存</div> <div class="title">缓存</div>
<div class="shortcut-box"> <div class="shortcut-box">
<div class="zy-select"> <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> </div>
</div> </div>
@@ -84,6 +73,10 @@
<div class="zy-input"> <div class="zy-input">
<input type="checkbox" v-model = "d.autocleanWhenIptvCheck" @change="updateSettingEvent"> 检测时自动清理无效源 <input type="checkbox" v-model = "d.autocleanWhenIptvCheck" @change="updateSettingEvent"> 检测时自动清理无效源
</div> </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> </div>
<div class="site"> <div class="site">
@@ -92,8 +85,11 @@
<div class="zy-select"> <div class="zy-select">
<div class="vs-placeholder vs-noAfter" @click="editSitesEvent">编辑源</div> <div class="vs-placeholder vs-noAfter" @click="editSitesEvent">编辑源</div>
</div> </div>
<div class="zy-input" @click="toggleExcludeRootClasses"> <div class="zy-select">
<input type="checkbox" v-model = "d.excludeRootClasses" @change="updateSettingEvent"> 屏蔽主分类 <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> </div>
</div> </div>
@@ -112,6 +108,15 @@
</div> </div>
</div> </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="theme">
<div class="title">主题</div> <div class="title">主题</div>
<div class="theme-box"> <div class="theme-box">
@@ -141,13 +146,6 @@
</div> </div>
</div> </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"> <div class="clearDB">
<span @click="clearDBEvent" class="clearBtn">软件重置</span> <span @click="clearDBEvent" class="clearBtn">软件重置</span>
<span @click="changePasswordEvent" class="clearBtn">设置密码</span> <span @click="changePasswordEvent" class="clearBtn">设置密码</span>
@@ -157,6 +155,34 @@
<span>所有资源来自网上, 该软件不参与任何制作, 上传, 储存等内容, 禁止传播违法资源. 该软件仅供学习参考, 请于安装后24小时内删除.</span> <span>所有资源来自网上, 该软件不参与任何制作, 上传, 储存等内容, 禁止传播违法资源. 该软件仅供学习参考, 请于安装后24小时内删除.</span>
</div> </div>
</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> <!-- 输入密码页面 --> <div> <!-- 输入密码页面 -->
<el-dialog :visible.sync="show.checkPasswordDialog" v-if='show.checkPasswordDialog' :append-to-body="true" @close="closeDialog" width="300px"> <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"> <el-form label-width="75px" label-position="left">
@@ -210,14 +236,28 @@
</span> </span>
</el-dialog> </el-dialog>
</div> </div>
<div class="update" v-if="update.show">
<div class="wrapper">
<div class="body">
<div class="content" v-html="update.html"></div>
</div>
<div class="footer">
<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>
</div>
</div> </div>
</template> </template>
<script> <script>
import { mapMutations } from 'vuex' import { mapMutations } from 'vuex'
import pkg from '../../package.json' import pkg from '../../package.json'
import { setting, sites, shortcut } from '../lib/dexie' import { setting, sites, shortcut } from '../lib/dexie'
import { sites as defaultSites } from '../lib/dexie/initData' import { localKey as defaultShortcuts } from '../lib/dexie/initData'
import { shell, clipboard, remote, ipcRenderer } from 'electron' import { shell, clipboard, ipcRenderer } from 'electron'
const remote = require('@electron/remote')
import db from '../lib/dexie/dexie' import db from '../lib/dexie/dexie'
import zy from '../lib/site/tools' import zy from '../lib/site/tools'
export default { export default {
@@ -234,7 +274,9 @@ export default {
checkPasswordDialog: false, checkPasswordDialog: false,
changePasswordDialog: false, changePasswordDialog: false,
proxy: false, proxy: false,
proxyDialog: false proxyDialog: false,
configDefaultParseUrlDialog: false,
configSitesDataUrlDialog: false
}, },
d: { }, d: { },
latestVersion: pkg.version, latestVersion: pkg.version,
@@ -245,6 +287,14 @@ export default {
scheme: '', scheme: '',
url: '', url: '',
port: '' port: ''
},
update: {
find: false,
version: '',
show: false,
html: '',
downloaded: false,
showDownload: true
} }
} }
}, },
@@ -275,13 +325,29 @@ export default {
setting.find().then(res => { setting.find().then(res => {
this.d = res this.d = res
this.setting = this.d 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 () { getSites () {
sites.all().then(res => { sites.all().then(res => {
if (res.length <= 0) { if (res.length <= 0) {
this.$message.warning('检测到视频源未能正常加载, 即将重置源.') this.$message.warning('检测到视频源未能正常加载, 即将重置源.')
sites.clear().then(sites.bulkAdd(defaultSites).then(this.getSites())) this.getDefaultSites()
} }
}) })
}, },
@@ -290,11 +356,6 @@ export default {
this.shortcutList = res this.shortcutList = res
}) })
}, },
changeView (e) {
this.d.view = e
this.updateSettingEvent()
this.show.view = false
},
async clearCache () { async clearCache () {
const win = remote.getCurrentWindow() const win = remote.getCurrentWindow()
const ses = win.webContents.session const ses = win.webContents.session
@@ -316,6 +377,24 @@ export default {
this.d.excludeRootClasses = !this.d.excludeRootClasses this.d.excludeRootClasses = !this.d.excludeRootClasses
this.updateSettingEvent() 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 () { selectLocalPlayer () {
const options = { const options = {
filters: [ filters: [
@@ -326,7 +405,7 @@ export default {
} }
remote.dialog.showOpenDialog(options).then(result => { remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) { if (!result.canceled) {
var playerPath = result.filePaths[0].replace(/\\/g, '/') const playerPath = result.filePaths[0].replace(/\\/g, '/')
this.$message.success('设定第三方播放器路径为:' + result.filePaths[0]) this.$message.success('设定第三方播放器路径为:' + result.filePaths[0])
this.d.externalPlayer = playerPath this.d.externalPlayer = playerPath
this.updateSettingEvent() this.updateSettingEvent()
@@ -358,6 +437,8 @@ export default {
async closeDialog () { async closeDialog () {
this.show.checkPasswordDialog = false this.show.checkPasswordDialog = false
this.show.changePasswordDialog = false this.show.changePasswordDialog = false
this.show.configDefaultParseUrlDialog = false
this.show.configSitesDataUrlDialog = false
if (this.show.proxyDialog) { if (this.show.proxyDialog) {
this.show.proxyDialog = false this.show.proxyDialog = false
this.setting.proxy.type = 'none' this.setting.proxy.type = 'none'
@@ -416,10 +497,20 @@ export default {
this.$message.info('已清空原数据') this.$message.info('已清空原数据')
shortcut.add(json).then(e => { shortcut.add(json).then(e => {
this.$message.success('已添加成功') this.$message.success('已添加成功')
this.getSites() this.getShortcut()
this.d.shortcutModified = true
this.updateSettingEvent()
}) })
}) })
}, },
resetShortcut () {
shortcut.clear().then(shortcut.add(defaultShortcuts)).then(res => {
this.getShortcut()
this.$message.success('快捷键已重置')
this.d.shortcutModified = true
this.updateSettingEvent()
})
},
async changeProxyType (e) { async changeProxyType (e) {
this.d.proxy.type = e this.d.proxy.type = e
if (e === 'manual') { if (e === 'manual') {
@@ -466,20 +557,29 @@ export default {
return false return false
} }
}, },
getLatestVersion () { checkUpdate () {
ipcRenderer.send('checkForUpdate') ipcRenderer.send('checkForUpdate')
ipcRenderer.on('update-available', (e, info) => { ipcRenderer.on('update-available', (e, info) => {
this.latestVersion = info.version this.update.find = true
}) this.update.version = info.version
ipcRenderer.on('update-error', () => { this.update.html = info.releaseNotes
this.$message.warning = '更新出错.'
})
ipcRenderer.on('update-downloaded', () => {
this.$message.info = '下载完毕, 退出安装'
}) })
}, },
quitAndInstall () { openUpdate () {
this.$message.success('已开始下载更新,下载完毕后,将自动退出安装。') this.update.show = true
},
closeUpdate () {
this.update.show = false
},
startUpdate () {
this.update.showDownload = false
ipcRenderer.send('downloadUpdate')
ipcRenderer.on('update-downloaded', () => {
this.update.downloaded = true
this.$message.success('更新已下载完成Mac用户须手动点击“安装”其它系统会在退出后自动安装')
})
},
installUpdate () {
ipcRenderer.send('quitAndInstall') ipcRenderer.send('quitAndInstall')
}, },
createContextMenu () { createContextMenu () {
@@ -495,10 +595,10 @@ export default {
} }
}, },
created () { created () {
this.getSites() // this.getSites()
this.getSetting() this.getSetting()
this.getShortcut() this.getShortcut()
this.getLatestVersion() this.checkUpdate()
this.createContextMenu() this.createContextMenu()
} }
} }
@@ -536,17 +636,6 @@ export default {
cursor: pointer; cursor: pointer;
} }
} }
.view{
width: 100%;
padding: 20px;
margin-top: 20px;
.view-box{
margin-top: 10px;
.zy-select{
margin-right: 20px;
}
}
}
.site{ .site{
width: 100%; width: 100%;
padding: 20px; padding: 20px;
@@ -641,5 +730,28 @@ export default {
font-size: 12px; font-size: 12px;
color: #ff000066; color: #ff000066;
} }
.update{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(7, 17, 27, 0.7);
display: flex;
align-items: center;
justify-content: center;
.wrapper{
background-color: #fff;
padding: 20px 50px 40px;
border-radius: 4px;
max-width: 500px;
max-height: 90%;
overflow: auto;
.footer{
display: flex;
justify-content: flex-end;
}
}
}
} }
</style> </style>

View File

@@ -1,11 +1,11 @@
<template> <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"> <div class="left">
<img :src="pic" alt="" @load="picLoadEvent"> <img :src="share.info.pic" alt="">
</div> </div>
<div class="right" id="right"> <div class="right" id="right">
<div class="title">{{ share.info.name }}</div> <div class="title">{{ share.info.name }}</div>
<qrcode-vue id="qr" :value="link" :size="160" level="L" /> <qrcode-vue v-if="link !== ''" id="qr" :value="link" :size="160" level="L" />
<div class="tips"> <div class="tips">
<p>长按二维码识别播放</p> <p>长按二维码识别播放</p>
<p><img src="@/assets/image/logo.png"></p> <p><img src="@/assets/image/logo.png"></p>
@@ -22,7 +22,7 @@ import { mapMutations } from 'vuex'
import QrcodeVue from 'qrcode.vue' import QrcodeVue from 'qrcode.vue'
import html2canvas from 'html2canvas' import html2canvas from 'html2canvas'
import zy from '../lib/site/tools' 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') const { clipboard, nativeImage } = require('electron')
export default { export default {
name: 'share', name: 'share',
@@ -45,6 +45,14 @@ export default {
set (val) { set (val) {
this.SET_SHARE(val) this.SET_SHARE(val)
} }
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
} }
}, },
watch: { watch: {
@@ -58,36 +66,43 @@ export default {
} }
}, },
directives: { directives: {
onClickaway: onClickaway Clickoutside
}, },
methods: { methods: {
...mapMutations(['SET_SHARE']), ...mapMutations(['SET_SHARE', 'SET_DetailCache']),
shareClickEvent () { shareClickEvent () {
this.share = { this.share = {
show: false, show: false,
info: {} info: {}
} }
}, },
getDetail () { async getUrl (index) {
this.loading = true
const id = this.share.info.ids || this.share.info.id const id = this.share.info.ids || this.share.info.id
zy.detail(this.share.key, id).then(res => { const cacheKey = this.share.key + '@' + id
if (res) { let res = this.DetailCache[cacheKey]
this.pic = res.pic if (!this.DetailCache[cacheKey]) {
var m3u8List = res.m3u8List res = await zy.detail(this.share.key, id)
const url = m3u8List[1] this.DetailCache[cacheKey] = res
this.link = 'http://zyplayer.fun/player/player.html?url=' + url + '&title=' + this.share.info.name }
} if (res) {
this.loading = false const url = res.fullList[0].list[index]
}) return url.includes('$') ? url.split('$')[1] : url
}
}, },
picLoadEvent () { async getDetail () {
const dom = document.getElementById('share') this.loading = true
html2canvas(dom, { useCORS: true, allowTaint: true }).then(res => { const index = this.share.index || 0
const png = res.toDataURL('image/png') const url = await this.getUrl(index)
const p = nativeImage.createFromDataURL(png) this.link = 'http://hunlongyu.gitee.io/zy-player-web?url=' + url + '&name=' + this.share.info.name
clipboard.writeImage(p) this.loading = false
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,20 +1,64 @@
<template> <template>
<div class="listpage" id="star"> <div class="listpage" id="star">
<div class="listpage-header" id="star-header"> <div class="listpage-header" id="star-header">
<el-switch v-model="viewMode" 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="exportFavoritesEvent" icon="el-icon-upload2"></el-button> <el-button @click.stop="importFavoritesEvent" icon="el-icon-download" title="支持同时导入多个文件"></el-button>
<el-button @click.stop="importFavoritesEvent" icon="el-icon-download">导入</el-button> <el-button @click.stop="removeSelectedItems" icon="el-icon-delete-solid">{{ multipleSelection.length === 0 ? "清空" : "删除所选" }}</el-button>
<el-button @click.stop="clearFavoritesEvent" icon="el-icon-delete-solid">清空</el-button> <b-button-group>
<el-button @click.stop="updateAllEvent" icon="el-icon-refresh">同步所有收藏</el-button> <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>
<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="listpage-body" id="star-body">
<div class="show-table" id="star-table" v-show="viewMode === 'table'"> <div class="show-table" id="star-table" v-if="setting.starViewMode === 'table'">
<el-table size="mini" fit height="100%" row-key="id" <el-table size="mini" fit height="100%" row-key="id"
ref="starTable" ref="starTable"
:data="list" :data="filteredList"
:cell-class-name="checkUpdate" :cell-class-name="checkUpdate"
@row-click="detailEvent" @row-click="detailEvent"
@sort-change="handleSortChange"> @sort-change="handleSortChange"
@select="selectionCellClick"
@selection-change="handleSelectionChange">
<el-table-column
type="selection">
</el-table-column>
<el-table-column <el-table-column
sortable sortable
:sort-method="(a , b) => sortByLocaleCompare(a.name, b.name)" :sort-method="(a , b) => sortByLocaleCompare(a.name, b.name)"
@@ -78,8 +122,8 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
<div class="show-picture" id="star-picture" v-show="viewMode === 'picture'"> <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="{ :breakpoints="{
1200: { //当屏幕宽度小于等于1200 1200: { //当屏幕宽度小于等于1200
rowPerView: 4, rowPerView: 4,
@@ -102,7 +146,7 @@
<div class="update" v-if="props.data.hasUpdate"> <div class="update" v-if="props.data.hasUpdate">
<span>有更新</span> <span>有更新</span>
</div> </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> <span>
看至第{{ props.data.index + 1 }}集 看至第{{ props.data.index + 1 }}集
</span> </span>
@@ -133,9 +177,9 @@
</template> </template>
<script> <script>
import { mapMutations } from 'vuex' 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 zy from '../lib/site/tools'
import { remote } from 'electron' const remote = require('@electron/remote')
import fs from 'fs' import fs from 'fs'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import Waterfall from 'vue-waterfall-plugin' import Waterfall from 'vue-waterfall-plugin'
@@ -146,8 +190,22 @@ export default {
return { return {
list: [], list: [],
sites: [], sites: [],
viewMode: 'picture', numNoUpdate: 0,
numNoUpdate: 0 shiftDown: false,
selectionBegin: '',
selectionEnd: '',
multipleSelection: [],
filteredList: [],
areas: [],
types: [],
// Toolbar
showToolbar: false,
selectedAreas: [],
selectedTypes: [],
sortKeyword: '',
sortKeywords: ['按片名', '按上映年份', '按更新时间'],
selectedYears: { start: 0, end: new Date().getFullYear() },
onlyShowItemsHasUpdate: false
} }
}, },
components: { components: {
@@ -185,6 +243,22 @@ export default {
set (val) { set (val) {
this.SET_SHARE(val) this.SET_SHARE(val)
} }
},
setting: {
get () {
return this.$store.getters.getSetting
},
set (val) {
this.SET_SETTING(val)
}
},
DetailCache: {
get () {
return this.$store.getters.getDetailCache
},
set (val) {
this.SET_DetailCache(val)
}
} }
}, },
watch: { watch: {
@@ -192,9 +266,7 @@ export default {
if (this.view === 'Star') { if (this.view === 'Star') {
this.getAllsites() this.getAllsites()
this.getFavorites() this.getFavorites()
if (this.$refs.starWaterfall) { if (this.setting.starViewMode === 'table') this.showShiftPrompt()
this.$refs.starWaterfall.refresh()
}
} }
}, },
numNoUpdate () { numNoUpdate () {
@@ -203,16 +275,110 @@ export default {
this.numNoUpdate = 0 this.numNoUpdate = 0
this.$message.warning('未查询到任何更新') 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: { methods: {
...mapMutations(['SET_VIEW', 'SET_DETAIL', 'SET_VIDEO', 'SET_SHARE']), ...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) { handleSortChange (column, prop, order) {
this.updateDatabase() this.updateDatabase()
}, },
sortByLocaleCompare (a, b) { sortByLocaleCompare (a, b) {
return a.localeCompare(b, 'zh') return a.localeCompare(b, 'zh')
}, },
selectionCellClick (selection, row) { // 同history一样逆序
if (this.shiftDown && this.selectionBegin !== '' && selection.includes(row)) {
this.selectionEnd = row.id
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.starTable.toggleRowSelection(e, true))
})
this.selectionBegin = this.selectionEnd = ''
return
}
if (selection.includes(row)) {
this.selectionBegin = row.id
} else {
this.selectionBegin = ''
}
},
handleSelectionChange (rows) {
this.multipleSelection = rows
},
removeSelectedItems () {
if (!this.multipleSelection.length) this.multipleSelection = this.list
this.multipleSelection.forEach(e => star.remove(e.id))
this.getFavorites()
this.updateDatabase()
},
detailEvent (e) { detailEvent (e) {
this.detail = { this.detail = {
show: true, show: true,
@@ -228,9 +394,9 @@ export default {
}, },
async playEvent (e) { async playEvent (e) {
if (e.index) { 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 { } 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) { if (e.hasUpdate) {
this.clearHasUpdateFlag(e) this.clearHasUpdateFlag(e)
@@ -249,7 +415,7 @@ export default {
this.share = { this.share = {
show: true, show: true,
key: e.key, key: e.key,
info: e info: e.detail
} }
}, },
checkUpdate ({ row, rowIndex }) { checkUpdate ({ row, rowIndex }) {
@@ -265,21 +431,24 @@ export default {
this.getFavorites() this.getFavorites()
} }
}, },
updateEvent (e) { async updateEvent (e) {
zy.detail(e.key, e.ids).then(detailRes => { try {
var doc = { 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, id: e.id,
key: e.key, key: e.key,
ids: e.ids, ids: e.ids,
site: e.site, site: e.site,
name: e.name, name: e.name,
detail: detailRes, detail: this.DetailCache[e.key + '@' + e.ids],
index: e.index index: e.index
} }
star.get(e.id).then(resStar => { 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 doc.hasUpdate = true
var msg = `同步"${e.name}"成功, 检查到更新。` const msg = `检查到"${e.name}"更新。`
this.$message.success(msg) this.$message.success(msg)
} else { } else {
this.numNoUpdate += 1 this.numNoUpdate += 1
@@ -287,10 +456,10 @@ export default {
star.update(e.id, doc) star.update(e.id, doc)
this.getFavorites() this.getFavorites()
}) })
}).catch(err => { } catch (err) {
var msg = `同步"${e.name}"失败, 请重试` const msg = `更新"${e.name}"失败, 请重试`
this.$message.warning(msg, err) this.$message.warning(msg, err)
}) }
}, },
updateAllEvent () { updateAllEvent () {
this.numNoUpdate = 0 this.numNoUpdate = 0
@@ -298,41 +467,22 @@ export default {
this.updateEvent(e) this.updateEvent(e)
}) })
}, },
downloadEvent (e) { async downloadEvent (e) {
zy.download(e.key, e.ids).then(res => { const db = await history.find({ site: e.key, ids: e.ids })
if (res) { let videoFlag
const text = res.m3u8List if (db) videoFlag = db.videoFlag
if (text) { zy.download(e.key, e.ids, videoFlag).then(res => {
const list = text.split('#') clipboard.writeText(res.downloadUrls)
let downloadUrl = '' this.$message.success(res.info)
for (const i of list) { }).catch((err) => {
const url = encodeURI(i.split('$')[1]) this.$message.error(err.info)
downloadUrl += (url + '\n')
}
clipboard.writeText(downloadUrl)
this.$message.success('『MP4』格式的链接已复制, 快去下载吧!')
} else {
this.$message.warning('没有查询到下载链接.')
}
} else {
zy.detail(e.key, e.ids).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』格式的链接已复制, 快去下载吧!')
})
}
}) })
}, },
getSiteName (row) { getSiteName (row) {
if (row.site) { if (row.site) {
return row.site.name return row.site.name
} else { } else {
var site = this.sites.find(e => e.key === row.key) const site = this.sites.find(e => e.key === row.key)
if (site) { if (site) {
return site.name return site.name
} }
@@ -362,13 +512,12 @@ export default {
const str = JSON.stringify(arr, null, 2) const str = JSON.stringify(arr, null, 2)
const options = { const options = {
filters: [ filters: [
{ name: 'JSON file', extensions: ['json'] }, { name: 'JSON file', extensions: ['json'] }
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
] ]
} }
remote.dialog.showSaveDialog(options).then(result => { remote.dialog.showSaveDialog(options).then(result => {
if (!result.canceled) { if (!result.canceled) {
if (!result.filePath.endsWith('.json')) result.filePath += '.json'
fs.writeFileSync(result.filePath, str) fs.writeFileSync(result.filePath, str)
this.$message.success('导出收藏成功') this.$message.success('导出收藏成功')
} }
@@ -379,23 +528,31 @@ export default {
importFavoritesEvent () { importFavoritesEvent () {
const options = { const options = {
filters: [ filters: [
{ name: 'JSON file', extensions: ['json'] }, { name: 'JSON file', extensions: ['json'] }
{ name: 'Normal text file', extensions: ['txt'] },
{ name: 'All types', extensions: ['*'] }
], ],
properties: ['openFile', 'multiSelections'] properties: ['openFile', 'multiSelections']
} }
remote.dialog.showOpenDialog(options).then(result => { remote.dialog.showOpenDialog(options).then(result => {
if (!result.canceled) { if (!result.canceled) {
var starList = Array.from(this.list) const starList = Array.from(this.list)
var id = this.list.length + 1 let id = this.list.length + 1
result.filePaths.forEach(file => { result.filePaths.forEach(file => {
var str = fs.readFileSync(file) const str = fs.readFileSync(file)
const json = JSON.parse(str) const json = JSON.parse(str)
json.reverse().forEach(ele => { json.reverse().forEach(ele => {
const starExists = starList.some(x => x.key === ele.key && x.ids === ele.ids) const starExists = starList.some(x => x.key === ele.key && x.ids === ele.ids)
if (!starExists) { 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, id: id,
key: ele.key, key: ele.key,
ids: ele.ids, ids: ele.ids,
@@ -404,16 +561,7 @@ export default {
hasUpdate: ele.hasUpdate, hasUpdate: ele.hasUpdate,
index: ele.index, index: ele.index,
rate: ele.rate, rate: ele.rate,
detail: ele.detail === undefined ? { detail: ele.detail === undefined ? newDetail : ele.detail
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
} }
id += 1 id += 1
starList.push(doc) starList.push(doc)
@@ -429,11 +577,6 @@ export default {
this.$message.error(err) this.$message.error(err)
}) })
}, },
clearFavoritesEvent () {
star.clear().then(e => {
this.getFavorites()
})
},
syncTableData () { syncTableData () {
if (this.$refs.starTable.tableData && this.$refs.starTable.tableData.length === this.list.length) { if (this.$refs.starTable.tableData && this.$refs.starTable.tableData.length === this.list.length) {
this.list = this.$refs.starTable.tableData this.list = this.$refs.starTable.tableData
@@ -442,7 +585,7 @@ export default {
updateDatabase () { updateDatabase () {
this.syncTableData() this.syncTableData()
star.clear().then(res => { star.clear().then(res => {
var id = this.list.length let id = this.list.length
this.list.forEach(ele => { this.list.forEach(ele => {
ele.id = id ele.id = id
id -= 1 id -= 1
@@ -451,6 +594,7 @@ export default {
}) })
}, },
rowDrop () { rowDrop () {
if (!document.getElementById('star-table')) return
const tbody = document.getElementById('star-table').querySelector('.el-table__body-wrapper tbody') const tbody = document.getElementById('star-table').querySelector('.el-table__body-wrapper tbody')
const _this = this const _this = this
Sortable.create(tbody, { Sortable.create(tbody, {
@@ -461,33 +605,28 @@ export default {
} }
}) })
}, },
getViewMode () { showShiftPrompt () {
setting.find().then(res => { if (this.setting.shiftTooltipLimitTimes === undefined) this.setting.shiftTooltipLimitTimes = 5
this.viewMode = res.starViewMode if (this.setting.shiftTooltipLimitTimes) {
}) this.$message.info('多选时支持shift快捷键')
}, this.setting.shiftTooltipLimitTimes--
updateViewMode () { setting.find().then(res => {
setting.find().then(res => { res.shiftTooltipLimitTimes = this.setting.shiftTooltipLimitTimes
res.starViewMode = this.viewMode setting.update(res)
setting.update(res) })
}) }
} }
}, },
created () { created () {
this.getFavorites() this.getFavorites()
this.getViewMode()
}, },
mounted () { mounted () {
this.rowDrop() if (this.setting.starViewMode === 'table') setTimeout(() => { this.rowDrop() }, 100)
window.addEventListener('resize', () => { addEventListener('keydown', code => { if (code.keyCode === 16) this.shiftDown = true })
if (this.$refs.starWaterfall && this.view === 'Star') { addEventListener('keyup', code => { if (code.keyCode === 16) this.shiftDown = false })
this.$refs.starWaterfall.resize() addEventListener('resize', () => {
this.$refs.starWaterfall.refresh() setTimeout(() => { if (this.$refs.starWaterfall) this.$refs.starWaterfall.resize() }, 500)
setTimeout(() => { })
this.$refs.starWaterfall.refresh()
}, 500)
}
}, false)
} }
} }
</script> </script>

View File

@@ -10,7 +10,7 @@ import Share from './Share'
import History from './History' import History from './History'
import EditSites from './EditSites' import EditSites from './EditSites'
import IPTV from './IPTV' import IPTV from './IPTV'
import Recommendation from './Recommendation' // import Recommendation from './Recommendation'
export default { export default {
registerComponents () { registerComponents () {
Vue.component('Aside', Aside) Vue.component('Aside', Aside)
@@ -24,6 +24,6 @@ export default {
Vue.component('History', History) Vue.component('History', History)
Vue.component('EditSites', EditSites) Vue.component('EditSites', EditSites)
Vue.component('IPTV', IPTV) 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 Dexie from 'dexie'
import { setting, sites, localKey, iptv, recommendations } from './initData' import { sites, localKey, iptv, recommendations, iniSetting } from './initData'
const db = new Dexie('zy') const db = new Dexie('zy')
db.version(4).stores({ db.version(4).stores({
search: '++id, keywords', search: '++id, keywords',
setting: 'id, theme, site, shortcut, view, volume, externalPlayer, searchGroup, excludeRootClasses, excludeR18Films, forwardTimeInSec, starViewMode, recommandationViewMode, searchViewMode, password, proxy, allowPassWhenIptvCheck, autocleanWhenIptvCheck', setting: 'id, theme, site, shortcut, view, volume, externalPlayer, searchGroup, excludeRootClasses, excludeR18Films, forwardTimeInSec, starViewMode, recommandationViewMode, searchViewMode, password, proxy, allowPassWhenIptvCheck, autocleanWhenIptvCheck',
@@ -16,8 +15,102 @@ db.version(4).stores({
channelList: '++id, name, prefer, channels, group, isActive' 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()
// https://ahuigo.github.io/b/ria/js-indexedDB#/ 比较旧,适当参考
db.version(5).stores({
shortcut: null
})
db.version(6).stores({
shortcut: '++id, name, key, desc'
}).upgrade(async tx => {
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.on('populate', () => {
db.setting.bulkAdd(setting) db.setting.bulkAdd(iniSetting)
db.sites.bulkAdd(sites) db.sites.bulkAdd(sites)
db.shortcut.bulkAdd(localKey) db.shortcut.bulkAdd(localKey)
db.iptv.bulkAdd(iptv) db.iptv.bulkAdd(iptv)

View File

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

View File

@@ -8,6 +8,7 @@ import search from './search'
import iptv from './iptv' import iptv from './iptv'
import channelList from './channelList' import channelList from './channelList'
import recommendation from './recommendation' import recommendation from './recommendation'
import cachedMovies from './cachedMovies'
export { export {
history, history,
@@ -19,5 +20,6 @@ export {
iptv, iptv,
channelList, channelList,
search, 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,259 +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": "可用"
},
{
"key": "wolongzy",
"id": 3,
"name": "卧龙资源网",
"api": "https://www.mhapi123.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true
},
{
"id": 4,
"key": "123ku",
"name": "123 资源",
"api": "http://cj.123ku2.com:12315/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 5,
"key": "subo988",
"name": "速播资源站",
"api": "https://www.subo988.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 6,
"key": "88zyw",
"name": "88 影视资源站",
"api": "http://www.88zyw.net/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"key": "zuidazy",
"id": 7,
"name": "最大资源网",
"api": "http://www.zdziyuan.com/inc/ldg_sea.php",
"download": "http://www.zdziyuan.com/inc/apidown.php",
"group": "默认",
"isActive": true
},
{
"key": "mbo",
"id": 8,
"name": "秒播资源",
"api": "http://caiji.mb77.vip/inc/seacmsapi.php",
"download": "",
"group": "默认",
"isActive": true
},
{
"id": 9,
"key": "apibdzy",
"name": "百度云资源",
"api": "https://api.apibdzy.com/api.php/provide/vod/at/xml",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 10,
"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": 11,
"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": 12,
"key": "kubozy",
"name": "酷播资源",
"api": "http://api.kbzyapi.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 13,
"key": "yongjiuzy",
"name": "永久资源",
"api": "http://cj.yongjiuzyw.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 14,
"key": "rrzy",
"name": "人人资源",
"api": "https://www.rrzyw.cc/api.php/provide/vod/from/rrm3u8/at/xml/",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 15,
"key": "bbkdj",
"name": "步步高顶尖资源网",
"api": "http://api.bbkdj.com/api",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 16,
"key": "solezy",
"name": "搜乐资源网",
"api": "https://www.caijizy.vip/api.php/provide/vod/at/xml/",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 17,
"key": "zuixinzy",
"name": "最新资源",
"api": "http://api.zuixinapi.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 18,
"key": "605zy",
"name": "605资源",
"api": "http://www.605zy.net/inc/seacmsapi.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 19,
"key": "doubanzy",
"name": "豆瓣电影资源",
"api": "http://v.1988cj.com/inc/api.php",
"download": "http://v.1988cj.com/inc/apidown.php",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 20,
"key": "135zy",
"name": "135 资源网",
"api": "http://cj.zycjw1.com/inc/api.php",
"download": "http://cj.zycjw1.com/inc/apidown.php",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 21,
"key": "mgtvzy",
"name": "芒果 TV 资源网",
"api": "https://api.shijiapi.com/api.php/provide/vod/at/xml/",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 22,
"key": "209zy",
"name": "209 资源",
"api": "http://cj.1156zy.com/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 23,
"key": "kkzy",
"name": "快快资源",
"api": "https://api.kkzy.tv/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 24,
"key": "mokazy",
"name": "魔卡资源网",
"api": "https://cj.heiyap.com/api.php/provide/vod/at/xml/",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 25,
"key": "158zy",
"name": "壹伍捌资源网",
"api": "http://cj.158zyz.net:158/inc/api.php",
"download": "",
"group": "默认",
"isActive": true,
"status": "可用"
},
{
"id": 26,
"key": "kyzy",
"name": "快影资源站",
"api": "https://www.kyzy.tv/api.php/kyyun/vod/at/xml/",
"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,128 +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: '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) => { const getSite = (key) => {
for (const i of sites) { for (const i of sites) {
if (key === i.key) { if (key === i.key) {
@@ -134,11 +9,13 @@ const getSite = (key) => {
const sites = require('./iniData/Sites.json') const sites = require('./iniData/Sites.json')
const iptv = require('./iniData/Iptv.json') const iptv = require('./iniData/Iptv.json')
const recommendations = require('./iniData/Recommendations.json') const recommendations = require('./iniData/Recommendations.json')
const iniSetting = require('./iniData/iniSetting.json')
const localKey = require('./iniData/localKey.json')
export { export {
setting,
sites, sites,
iptv, iptv,
recommendations, recommendations,
iniSetting,
localKey, localKey,
getSite getSite
} }

View File

@@ -5,6 +5,12 @@ export default {
async find () { async find () {
return await setting.get({ id: 0 }) 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) { async update (docs) {
return await setting.update(0, docs) return await setting.update(0, docs)
} }

View File

@@ -1,12 +1,15 @@
import Vue from 'vue' import Vue from 'vue'
import { Message, Button, Table, TableColumn, Tag, Input, Dialog, Form, FormItem, Switch, Select, Option, Checkbox, Autocomplete, Col, Tree } 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 Plugin from 'v-fit-columns'
import { ButtonGroupPlugin } from 'bootstrap-vue'
Vue.use(ButtonGroupPlugin)
Vue.use(Button) Vue.use(Button)
Vue.use(Col) Vue.use(Col)
Vue.use(Table) Vue.use(Table)
Vue.use(TableColumn) Vue.use(TableColumn)
Vue.use(Tag) Vue.use(Tag)
Vue.use(Input) Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Dialog) Vue.use(Dialog)
Vue.use(Form) Vue.use(Form)
Vue.use(FormItem) Vue.use(FormItem)
@@ -17,4 +20,7 @@ Vue.use(Option)
Vue.use(Checkbox) Vue.use(Checkbox)
Vue.use(Autocomplete) Vue.use(Autocomplete)
Vue.use(Tree) Vue.use(Tree)
Vue.use(Divider)
Vue.use(Progress)
Vue.prototype.$message = Message Vue.prototype.$message = Message
Vue.prototype.$msgbox = MessageBox

View File

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

View File

@@ -1,18 +0,0 @@
import express from 'express'
import cors from 'cors'
const Axios = require('axios')
const app = express()
app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.post('/api', async (req, res) => {
const result = await Axios.get(req.body.url)
res.json({
code: 1,
info: result.data
})
})
app.listen(44444)

View File

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

View File

@@ -1,18 +1,24 @@
import { BrowserWindow, ipcMain } from 'electron' import { BrowserWindow, ipcMain } from 'electron'
import { autoUpdater } from 'electron-updater' const { autoUpdater } = require('electron-updater')
// electron-updater 增量更新时似乎无法显示进度
export function initUpdater (win = BrowserWindow) { export function initUpdater (win = BrowserWindow) {
autoUpdater.autoDownload = false autoUpdater.autoDownload = false
autoUpdater.autoInstallOnAppQuit = false autoUpdater.autoInstallOnAppQuit = true
// 主进程监听检查更新事件 // 主进程监听检查更新事件
ipcMain.on('checkForUpdate', () => { ipcMain.on('checkForUpdate', () => {
autoUpdater.checkForUpdates() autoUpdater.checkForUpdates()
}) })
// 主进程监听开始下载事件
ipcMain.on('downloadUpdate', () => {
autoUpdater.downloadUpdate()
})
// 主进程监听退出并安装事件 // 主进程监听退出并安装事件
ipcMain.on('quitAndInstall', () => { ipcMain.on('quitAndInstall', () => {
autoUpdater.downloadUpdate() autoUpdater.quitAndInstall()
}) })
// 开始检测是否有更新 // 开始检测是否有更新
@@ -40,9 +46,8 @@ export function initUpdater (win = BrowserWindow) {
win.webContents.send('download-progress', progressObj) win.webContents.send('download-progress', progressObj)
}) })
// 下载完成并退出安装 // 下载完成
autoUpdater.on('update-downloaded', () => { autoUpdater.on('update-downloaded', () => {
win.webContents.send('update-downloaded') win.webContents.send('update-downloaded')
autoUpdater.quitAndInstall()
}) })
} }

View File

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

10027
yarn.lock

File diff suppressed because it is too large Load Diff