Compare commits

..

78 Commits

Author SHA1 Message Date
BTMuli
17b0d75d21 🚀 v0.3.7 2023-12-11 19:14:32 +08:00
BTMuli
e973ac41fa 💚 调整 CI 2023-12-11 19:13:55 +08:00
BTMuli
30a97e9973 🚸 完善链接解析 2023-12-11 15:52:38 +08:00
BTMuli
07e944859c 🗑️ 增加几个 callback 处理 2023-12-11 15:49:41 +08:00
BTMuli
17744cc01d ♻️ 重构 pushPage、closePage 逻辑 2023-12-11 15:05:30 +08:00
BTMuli
cfc1f65e3e 🐛 完善链接识别 2023-12-11 01:08:44 +08:00
BTMuli
0965524003 ♻️ 优化切分正则
*PostID: 46252606
*thanks @Lightczx
2023-12-10 20:55:06 +08:00
BTMuli
d4818448b5 🚨 修复 qodana 报错 2023-12-10 20:47:34 +08:00
BTMuli
3ff37fcc83 📝 添加帖子获取说明 2023-12-10 16:38:55 +08:00
BTMuli
838fefac6a ️ 添加 hover 效果 2023-12-10 16:30:37 +08:00
BTMuli
0141012f55 🔥 移除侧边栏点击菜单,可通过新页面打开 2023-12-10 16:20:49 +08:00
BTMuli
347b3ed13d 🔧 完善链接识别 2023-12-10 16:16:46 +08:00
BTMuli
8c50da61b1 🗑️ loadMore 2023-12-10 16:03:07 +08:00
BTMuli
5395304431 💄 修复大别野卡片渲染错误,调整大别野卡片样式 2023-12-10 15:07:07 +08:00
BTMuli
39713bf5d3 添加帖子ID输入 2023-12-10 14:42:49 +08:00
BTMuli
771505b527 🔥 移除首页 emoji 下载 2023-12-10 13:20:59 +08:00
BTMuli
1a381916a5 👔 完善对于链接的判断逻辑 2023-12-10 13:20:12 +08:00
BTMuli
1d0b070451 🐛 修复表情包渲染错误,缺乏时自动下载 2023-12-10 13:12:48 +08:00
BTMuli
9a18fa8e42 🌱 添加应用“发现”接口 2023-12-09 21:18:31 +08:00
BTMuli
7164c7caf2 🔥 删除无用 css
close #64
2023-12-09 20:50:02 +08:00
BTMuli
aeb49dabb2 💄 修复描述过长导致的渲染错误 2023-12-09 20:40:03 +08:00
BTMuli
1ccb99dd1b 完成多游戏多频道多排序帖子获取
close #67
2023-12-09 17:29:13 +08:00
BTMuli
f998ba21b3 💄 增加深色模式下的辨识度 2023-12-09 17:27:53 +08:00
BTMuli
5406e10922 🐛 修复公告标题显示 <br> 的问题 2023-12-09 15:39:22 +08:00
BTMuli
527093b982 🚸 默认最新回复 2023-12-09 15:31:52 +08:00
BTMuli
7b34124cd2 Revert " 打包测试"
This reverts commit 6151d13b96.
2023-12-09 14:06:57 +08:00
BTMuli
3bf48561a1 ️ 避免搞混 2023-12-09 14:04:14 +08:00
BTMuli
c24ae38294 👷 增加 macOS arm 安装包
close #68
2023-12-09 14:00:00 +08:00
BTMuli
e94e3a6821 打包测试x3 2023-12-09 13:44:14 +08:00
BTMuli
ad55e56651 打包测试x2 2023-12-09 13:14:41 +08:00
BTMuli
6151d13b96 打包测试 2023-12-09 12:48:14 +08:00
BTMuli
dbf2ed4a56 💚 增加参数 2023-12-09 12:47:12 +08:00
BTMuli
624970aa57 🩹 MacOS → macOS 2023-12-09 12:42:33 +08:00
BTMuli
4c480d1fa2 👷 macOS 平台打包成 universal #68 2023-12-09 12:39:50 +08:00
BTMuli
4880e6bb8e 🌱 仿照咨讯页写了个酒馆页面 #67 2023-12-09 00:52:11 +08:00
BTMuli
7d142d02b3 ✏️ 适应 api 返回更新数据类型 2023-12-09 00:19:43 +08:00
BTMuli
df06d7aba0 💄 调整应用标识样式 2023-12-08 17:21:08 +08:00
BTMuli
b01e4e680f 💄 修复分享图生成错误 2023-12-08 17:15:26 +08:00
BTMuli
be3d49566d 🐛 插入 obj,自动转为 unknown 2023-12-08 16:58:43 +08:00
BTMuli
9aac81cbc4 🌱 video 缺乏数据源 2023-12-08 13:28:23 +08:00
BTMuli
e61f9519db 👷 调整参数 2023-12-08 13:19:19 +08:00
BTMuli
48f6d95c7a 🎨 修正一些小错误 2023-12-08 13:17:58 +08:00
BTMuli
0cb68dfe6f ✏️ 结构化数据类型分解 #51 2023-12-08 13:10:09 +08:00
BTMuli
1df568e26a 🔥 弃用旧渲染方式 #64 2023-12-08 13:03:42 +08:00
BTMuli
e249b5e956 🐛 修复默认画质错误,调整 aspect-ratio 2023-12-08 12:38:31 +08:00
BTMuli
c9370b0a22 📝 增加第三方组件说明 2023-12-07 15:25:20 +08:00
BTMuli
32691c6c92 🙈 删除过时 submodule 2023-12-07 15:24:46 +08:00
BTMuli
68edc6bcab 🙈 忽略图像文件 2023-12-07 15:23:42 +08:00
BTMuli
a800ed532d 💄 添加折叠框 icon 2023-12-07 15:08:35 +08:00
BTMuli
0bb730d2e3 💄 帖子增加应用标识 2023-12-07 14:08:04 +08:00
BTMuli
fcb5f94656 ✏️ 修正 vod.id 类型 2023-12-07 13:57:03 +08:00
BTMuli
3883f9880a 🐛 修复一些问题
*无法实例化多个Artplayer
*生成图 div 无实际占位
2023-12-07 13:51:08 +08:00
BTMuli
919baca46c 👷 适应 pnpm 进行切分 2023-12-07 13:19:29 +08:00
BTMuli
2e63d310af ♻️ 优化逻辑,完善大小计算 2023-12-07 12:57:37 +08:00
BTMuli
64ee8a36dc ️ 完善组件样式
*完善活动链接识别
*调整特殊文本偏移
*调整链接卡片图片宽度
*大别野卡片样式重构
2023-12-07 01:23:55 +08:00
BTMuli
71d7337384 完善 share 回调 2023-12-07 00:17:59 +08:00
BTMuli
389acf19fe ♻️ 优化代码 2023-12-06 23:35:34 +08:00
BTMuli
f55f4116d5 🐛 canvas 超过 80MB 时不予保存,超过 20MB 时给予提示 2023-12-06 23:31:31 +08:00
BTMuli
68b84d2bc7 ️ 全屏播放视频,默认最高清晰度 2023-12-06 23:28:51 +08:00
BTMuli
669dec73ba ️ 移除遮罩,自动聚焦 2023-12-06 22:15:53 +08:00
BTMuli
03b83ba2d1 ️ 完善组件样式 #64
*优化换行符渲染
*增加网页活动识别
*调整折叠文本样式
*限制最大宽度而非写死宽度
2023-12-06 20:30:14 +08:00
BTMuli
0f07f8c031 ️ 添加播放次数 2023-12-06 19:43:02 +08:00
BTMuli
3b08dd9a14 ✏️ 更新互动类型 2023-12-06 19:24:27 +08:00
BTMuli
f11c1ef984 🎨 默认采用新渲染方式 2023-12-06 18:11:50 +08:00
BTMuli
d271444610 采用 Artplayer #64 2023-12-06 18:10:40 +08:00
目棃
d9fa158076 Merge pull request #65 from BTMuli/dependabot/npm_and_yarn/vite-5.0.5
Bump vite from 5.0.3 to 5.0.5
2023-12-06 15:21:20 +08:00
dependabot[bot]
e08f37b89e Bump vite from 5.0.3 to 5.0.5
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.3 to 5.0.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.5/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-05 23:55:25 +00:00
BTMuli
7ba2011b35 🚨 修复 qodana 报错 2023-12-03 18:02:45 +08:00
BTMuli
cab0c9a9c8 大概完成渲染方式重构,点击标题切换
#64
2023-12-03 00:34:59 +08:00
BTMuli
885a8b22da ️ 完善 linkCard 解析样式 2023-12-03 00:23:06 +08:00
BTMuli
b5eec739e7 🌱 完成 mention,backupText,linkCard 类型的解析组件化 2023-12-03 00:06:05 +08:00
BTMuli
3640a43fa7 💄 完善样式 2023-12-02 23:54:38 +08:00
BTMuli
bb58e6ef03 🌱 完成 image,divider,vod,unknown 类型的解析组件化 2023-12-02 23:26:41 +08:00
BTMuli
9b27e8d955 🌱 完成 text,emoji,link 类型的解析组件化 2023-12-02 22:58:17 +08:00
BTMuli
7ae8b5ec46 ️ 副标题过长时进行折行 2023-12-02 22:36:33 +08:00
BTMuli
060382b3a7 🐛 修复 tag_list 字段 undefined 导致的解析错误
fix #63
2023-11-30 14:02:54 +08:00
BTMuli
747763601b ✏️ 修正大别野数据结构 2023-11-30 14:00:40 +08:00
BTMuli
5b8610ab5b ⬆️ 更新依赖 2023-11-29 16:57:57 +08:00
64 changed files with 3325 additions and 1748 deletions

View File

@@ -1,8 +1,6 @@
# Build
dist
src-tauri/target
# Submodules
TGAssistant
# Package files
pnpm-lock.yaml
# lint files

View File

@@ -35,7 +35,7 @@ body:
attributes:
label: 当前使用版本
description: 请填写当前使用版本
placeholder: 如 Beta v0.3.4
placeholder: 如 Beta v0.3.7
validations:
required: true
- type: textarea

View File

@@ -34,7 +34,7 @@ body:
attributes:
label: 当前使用版本
description: 请填写当前使用版本
placeholder: 如 Beta v0.3.4
placeholder: 如 Beta v0.3.7
validations:
required: true
- type: textarea

View File

@@ -33,7 +33,7 @@ body:
attributes:
label: 当前提交
description: 请填写当前提交
placeholder: 如 Beta v0.3.4
placeholder: 如 Beta v0.3.7
validations:
required: true
- type: textarea

View File

@@ -11,12 +11,24 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [windows-latest, macos-latest]
runs-on: ${{ matrix.platform }}
settings:
- platform: windows-latest
args: "--verbose"
target: "windows"
- platform: macos-latest
args: "--target x86_64-apple-darwin"
target: "macos-intel"
- platform: macos-latest
args: "--target aarch64-apple-darwin"
target: "macos-arm"
runs-on: ${{ matrix.settings.platform }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Add Rust targets(macOS)
if: matrix.settings.target == 'macos-arm'
run: rustup target add aarch64-apple-darwin
- name: Rust setup
uses: dtolnay/rust-toolchain@stable
@@ -28,11 +40,11 @@ jobs:
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18.16.0
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8.10.5
version: 8.11.0
- name: Install frontend dependencies
run: pnpm install
@@ -43,10 +55,11 @@ jobs:
# TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
# TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
args: ${{ matrix.settings.args }}
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version
releaseName: v__VERSION__-beta
releaseBody: |
> Windows 平台用户建议通过微软应用商店下载,MacOS 平台仅在此发布Linux 平台暂不支持。
> Windows 平台用户建议通过微软应用商店下载,macOS 平台仅在此发布Linux 平台暂不支持。
<a href="https://apps.microsoft.com/store/detail/9NLBNNNBNSJN?launch=true&cid=BTMuli&mode=mini">
<img src="https://get.microsoft.com/images/zh-cn%20dark.svg" alt="download"/>

View File

@@ -13,11 +13,11 @@ jobs:
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18.16.0
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8.6.7
version: 8.11.0
- name: Install frontend dependencies
run: pnpm install --frozen-lockfile
- name: "Qodana Scan"

View File

@@ -5,9 +5,11 @@
# Tauri build
dist
src-tauri/target
# submodules
TGAssistant
# Pnpm
pnpm-lock.yaml
# Qodana
qodana.yaml
# sourse
*.webp
*.png
*.svg

3
.vscode/launch.json vendored
View File

@@ -1,7 +1,4 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{

View File

@@ -2,12 +2,35 @@
Author: 目棃
Description: CHANGELOG
Date: 2023-09-08
Update: 2023-11-28
Update: 2023-12-11
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-09-08 09:45:17 `
>
> 更新于 `2023-11-28 15:18:27`
> 更新于 `2023-12-11 19:01:09`
## [0.3.7](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.7) (2023-12-11)
### Feat
- 帖子:渲染方式迭代,由 `v-html` 改为组件渲染 [`#64`](https://github.com/BTMuli/TeyvatGuide/issues/64)
- 帖子:分享图增加应用标识
- 应用:新增帖子页面,支持浏览各游戏分区各版块按不同排序方式的帖子 [`#67`](https://github.com/BTMuli/TeyvatGuide/issues/67)
- 应用macOS 新增 arm64 架构支持 [`#68`](https://github.com/BTMuli/TeyvatGuide/issues/68)
- JSBridge: 部分原先存在遮罩的页面将去除遮罩
- JSBridge: 新增部分回调的处理
### Fix
- 帖子:修复大别野卡片渲染错误 [`#63`](https://github.com/BTMuli/TeyvatGuide/issues/63)
- 公告:修复标题错误显示 `<br>` 的问题
- 帖子:修复表情包渲染错误,移除首页表情包下载
### Change
- 帖子:分享图大小超过 80M 时不予保存,超过 20M 时可选保存至文件
- 应用:移除侧边栏点击菜单,可以通过帖子页面触发各功能入口
- JSBridge重构 `pushPage``closePage` 逻辑
## [0.3.6](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.6) (2023-11-25)

View File

@@ -2,12 +2,12 @@
Author: 目棃
Description: 说明文档
Date: 2023-03-05
Update: 2023-11-15
Update: 2023-12-10
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
>
> 更新于 `2023-11-15 21:01:51`
> 更新于 `2023-12-10 16:38:32`
![](https://img.shields.io/github/last-commit/BTMuli/TeyvatGuide?style=for-the-badge) ![](https://img.shields.io/github/commits-since/BTMuli/TeyvatGuide/latest?include_prereleases&style=for-the-badge)
@@ -19,9 +19,9 @@ Update: 2023-11-15
# Teyvat Guide
基于 Tauri 的原神工具应用,支持 Windows 和 MacOS 平台。
基于 Tauri 的原神工具应用,支持 Windows 和 macOS 平台。
Game Tool for Genshin Impact player, supports Windows and MacOS.
Game Tool for Genshin Impact player, supports Windows and macOS.
## 下载 / Download
@@ -31,7 +31,7 @@ Game Tool for Genshin Impact player, supports Windows and MacOS.
<img src="https://get.microsoft.com/images/zh-cn%20dark.svg" alt="download"/>
</a>
> MacOS 用户可以通过 Github Release 下载
> macOS 用户可以通过 Github Release 下载
[![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/BTMuli/TeyvatGuide?include_prereleases&style=for-the-badge)](https://github.com/BTMuli/TeyvatGuide/releases/latest)
@@ -46,6 +46,7 @@ Game Tool for Genshin Impact player, supports Windows and MacOS.
- [x] 当前卡池、近期活动、素材日历
- [x] 游戏内公告&活动获取
- [x] 米游社官方帖获取(支持通过 ID 获取)
- [x] 米游社各分区帖子获取(支持通过 ID 获取)
- [x] 成就管理UIAF支持 [`YaeAchievement`](https://github.com/HolographicHat/YaeAchievement) 导入
- [x] 祈愿管理UIGF
@@ -100,7 +101,11 @@ Game Tool for Genshin Impact player, supports Windows and MacOS.
- [Vue3](https://github.com/vuejs/core)
- [Vite](https://github.com/vitejs/vite)
- [Vuetify](https://github.com/vuetifyjs/vuetify)
- [Echarts](https://echarts.apache.org/zh/index.html)
## 第三方组件 / Plugins
- [Echarts](https://echarts.apache.org/zh/index.html):用于祈愿概览图生成
- [Artplayer](https://artplayer.org/):用于米游社帖子视频播放解析
## 协议 / License

View File

@@ -1,9 +1,9 @@
{
"name": "TeyvatGuide",
"version": "0.3.6",
"version": "0.3.7",
"description": "Game Tool for Genshin Impact player",
"private": true,
"packageManager": "pnpm@8.10.5",
"packageManager": "pnpm@8.11.0",
"scripts": {
"build": "tauri build",
"debug": "tauri build --debug",
@@ -68,6 +68,7 @@
"dependencies": {
"@mdi/font": "7.3.67",
"@tauri-apps/api": "^1.5.1",
"artplayer": "^5.0.9",
"clipboard": "^2.0.11",
"color-convert": "^2.0.1",
"echarts": "^5.4.3",
@@ -78,27 +79,27 @@
"qrcode.vue": "^3.4.1",
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
"uuid": "^9.0.1",
"vue": "^3.3.8",
"vue": "^3.3.9",
"vue-echarts": "^6.6.1",
"vue-json-viewer": "^3.0.4",
"vue-router": "^4.2.5",
"vuetify": "^3.4.2",
"vuetify": "^3.4.4",
"wcag-color": "^1.1.1"
},
"devDependencies": {
"@tauri-apps/cli": "^1.5.6",
"@types/color-convert": "^2.0.3",
"@types/js-md5": "^0.7.2",
"@types/node": "^20.9.1",
"@types/node": "^20.10.0",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.13.1",
"@vitejs/plugin-vue": "^4.5.0",
"@vue/devtools": "^6.5.1",
"concurrently": "^8.2.2",
"eslint": "^8.53.0",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-standard-with-typescript": "^39.1.1",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsonc": "^2.10.0",
"eslint-plugin-n": "^16.3.1",
@@ -116,9 +117,9 @@
"stylelint-declaration-block-no-ignored-properties": "^2.7.0",
"stylelint-high-performance-animation": "^1.9.0",
"stylelint-order": "^6.0.3",
"stylelint-prettier": "^4.0.2",
"typescript": "^5.2.2",
"vite": "^5.0.0",
"stylelint-prettier": "^4.1.0",
"typescript": "^5.3.2",
"vite": "^5.0.5",
"vite-plugin-vuetify": "^1.0.2",
"yaml-eslint-parser": "^1.2.2"
}

564
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
public/source/UI/posts.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

312
src-tauri/Cargo.lock generated
View File

@@ -4,7 +4,7 @@ version = 3
[[package]]
name = "TeyvatGuide"
version = "0.3.6"
version = "0.3.7"
dependencies = [
"serde",
"serde_json",
@@ -129,6 +129,16 @@ dependencies = [
"num-traits",
]
[[package]]
name = "atomic-write-file"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae364a6c1301604bbc6dfbf8c385c47ff82301dd01eef506195a029196d8d04"
dependencies = [
"nix",
"rand 0.8.5",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -495,6 +505,30 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.8"
@@ -762,12 +796,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.6"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -874,9 +908,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
@@ -1117,9 +1151,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.28.0"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "gio"
@@ -1204,15 +1238,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
"regex-automata 0.4.3",
"regex-syntax 0.8.2",
]
[[package]]
@@ -1283,9 +1317,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.21"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
dependencies = [
"bytes",
"fnv",
@@ -1293,7 +1327,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap 1.9.3",
"indexmap 2.1.0",
"slab",
"tokio",
"tokio-util",
@@ -1308,9 +1342,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.2"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
"ahash",
"allocator-api2",
@@ -1322,7 +1356,7 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown 0.14.2",
"hashbrown 0.14.3",
]
[[package]]
@@ -1412,9 +1446,9 @@ dependencies = [
[[package]]
name = "http"
version = "0.2.10"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150"
checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
dependencies = [
"bytes",
"fnv",
@@ -1528,9 +1562,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
@@ -1538,17 +1572,16 @@ dependencies = [
[[package]]
name = "ignore"
version = "0.4.20"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
dependencies = [
"crossbeam-deque",
"globset",
"lazy_static",
"log",
"memchr",
"regex",
"regex-automata 0.4.3",
"same-file",
"thread_local",
"walkdir",
"winapi-util",
]
@@ -1584,7 +1617,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown 0.14.2",
"hashbrown 0.14.3",
"serde",
]
@@ -1691,9 +1724,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "js-sys"
version = "0.3.65"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
dependencies = [
"wasm-bindgen",
]
@@ -1769,9 +1802,9 @@ dependencies = [
[[package]]
name = "libsqlite3-sys"
version = "0.26.0"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326"
checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716"
dependencies = [
"cc",
"pkg-config",
@@ -1992,6 +2025,17 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nix"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags 2.4.1",
"cfg-if",
"libc",
]
[[package]]
name = "nodrop"
version = "0.1.14"
@@ -2196,9 +2240,9 @@ dependencies = [
[[package]]
name = "openssl"
version = "0.10.59"
version = "0.10.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33"
checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
dependencies = [
"bitflags 2.4.1",
"cfg-if",
@@ -2228,9 +2272,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.95"
version = "0.9.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9"
checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
dependencies = [
"cc",
"libc",
@@ -2332,9 +2376,9 @@ dependencies = [
[[package]]
name = "percent-encoding"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "phf"
@@ -2570,9 +2614,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.69"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-ident",
]
@@ -2821,9 +2865,9 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.9.3"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d"
checksum = "af6c4b23d99685a1408194da11270ef8e9809aff951cc70ec9b17350b087e474"
dependencies = [
"const-oid",
"digest",
@@ -2856,9 +2900,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.21"
version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [
"bitflags 2.4.1",
"errno",
@@ -2969,18 +3013,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.192"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.192"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
@@ -3124,9 +3168,9 @@ dependencies = [
[[package]]
name = "signature"
version = "2.1.0"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"rand_core 0.6.4",
@@ -3224,9 +3268,9 @@ dependencies = [
[[package]]
name = "spki"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
@@ -3245,9 +3289,9 @@ dependencies = [
[[package]]
name = "sqlx"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33"
checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf"
dependencies = [
"sqlx-core",
"sqlx-macros",
@@ -3258,9 +3302,9 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d"
checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
dependencies = [
"ahash",
"atoi",
@@ -3299,9 +3343,9 @@ dependencies = [
[[package]]
name = "sqlx-macros"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec"
checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5"
dependencies = [
"proc-macro2",
"quote",
@@ -3312,10 +3356,11 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc"
checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841"
dependencies = [
"atomic-write-file",
"dotenvy",
"either",
"heck 0.4.1",
@@ -3338,9 +3383,9 @@ dependencies = [
[[package]]
name = "sqlx-mysql"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db"
checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4"
dependencies = [
"atoi",
"base64 0.21.5",
@@ -3381,9 +3426,9 @@ dependencies = [
[[package]]
name = "sqlx-postgres"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624"
checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24"
dependencies = [
"atoi",
"base64 0.21.5",
@@ -3421,9 +3466,9 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f"
checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490"
dependencies = [
"atoi",
"flume",
@@ -3440,6 +3485,7 @@ dependencies = [
"time",
"tracing",
"url",
"urlencoding",
]
[[package]]
@@ -3793,7 +3839,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-sql"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#84a2a11c4dc6a6b5569ef2e38bb6c2a4e04a7219"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#3db81b4fe30d2ddb9cc109852b90b1e37da9e4cd"
dependencies = [
"futures-core",
"log",
@@ -4157,9 +4203,9 @@ dependencies = [
[[package]]
name = "tracing-log"
version = "0.1.4"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
@@ -4168,9 +4214,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"matchers",
"nu-ansi-term",
@@ -4240,9 +4286,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "url"
version = "2.4.1"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
dependencies = [
"form_urlencoded",
"idna",
@@ -4250,6 +4296,12 @@ dependencies = [
"serde",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf-8"
version = "0.7.6"
@@ -4258,9 +4310,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "uuid"
version = "1.5.0"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
dependencies = [
"getrandom 0.2.11",
]
@@ -4348,9 +4400,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -4358,9 +4410,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
dependencies = [
"bumpalo",
"log",
@@ -4373,9 +4425,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.38"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
dependencies = [
"cfg-if",
"js-sys",
@@ -4385,9 +4437,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -4395,9 +4447,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
@@ -4408,9 +4460,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.88"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
[[package]]
name = "wasm-streams"
@@ -4427,9 +4479,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.65"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -4661,6 +4713,15 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
@@ -4691,6 +4752,21 @@ dependencies = [
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows-tokens"
version = "0.39.0"
@@ -4709,6 +4785,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.37.0"
@@ -4733,6 +4815,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.37.0"
@@ -4757,6 +4845,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.37.0"
@@ -4781,6 +4875,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.37.0"
@@ -4805,6 +4905,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
@@ -4817,6 +4923,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.37.0"
@@ -4841,6 +4953,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.19"
@@ -4872,9 +4990,9 @@ dependencies = [
[[package]]
name = "wry"
version = "0.24.4"
version = "0.24.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ef04bdad49eba2e01f06e53688c8413bd6a87b0bc14b72284465cf96e3578e"
checksum = "64a70547e8f9d85da0f5af609143f7bde3ac7457a6e1073104d9b73d6c5ac744"
dependencies = [
"base64 0.13.1",
"block",
@@ -4940,18 +5058,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.25"
version = "0.7.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557"
checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.25"
version = "0.7.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b"
checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f"
dependencies = [
"proc-macro2",
"quote",
@@ -4960,6 +5078,6 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.6.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"

View File

@@ -1,6 +1,6 @@
[package]
name = "TeyvatGuide"
version = "0.3.6"
version = "0.3.7"
description = "Game Tool for Genshin Impact player"
authors = ["BTMuli <bt-muli@outlook.com>"]
license = "MIT"
@@ -13,7 +13,7 @@ edition = "2021"
tauri-build = { version = "1.4", features = [] }
[dependencies]
tauri = { version = "1.4", features = [ "dialog-message", "process-exit", "fs-read-dir", "window-hide", "os-all", "clipboard-all", "dialog-open", "dialog-save", "fs-create-dir", "fs-remove-dir", "fs-write-file", "fs-remove-file", "fs-read-file", "path-all", "fs-exists", "window-close", "window-set-title", "window-unminimize", "window-show", "window-set-focus", "http-request", "shell-open"] }
tauri = { version = "1.4", features = [ "window-set-fullscreen", "dialog-message", "process-exit", "fs-read-dir", "window-hide", "os-all", "clipboard-all", "dialog-open", "dialog-save", "fs-create-dir", "fs-remove-dir", "fs-write-file", "fs-remove-file", "fs-read-file", "path-all", "fs-exists", "window-close", "window-set-title", "window-unminimize", "window-show", "window-set-focus", "http-request", "shell-open"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
url = "2.4.1"

View File

@@ -34,6 +34,7 @@ pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
mhy_window_config.url = get_mhy_client_url(func.clone());
}
if func == "birthday"
|| func == "web_act"
|| url.starts_with("https://webstatic.mihoyo.com/ys/event/e20220303-birthday/index.html")
{
mhy_window_config.width = 1280.0;

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "TeyvatGuide",
"version": "0.3.6"
"version": "0.3.7"
},
"tauri": {
"allowlist": {
@@ -47,11 +47,12 @@
},
"window": {
"all": false,
"setFocus": true,
"setTitle": true,
"setFullscreen": true,
"unminimize": true,
"show": true,
"close": true,
"setFocus": true,
"hide": true
},
"os": {
@@ -102,11 +103,21 @@
"windows": ["mhy_client"],
"enableTauriAPI": true
},
{
"domain": "qaa.miyoushe.com",
"windows": ["mhy_client"],
"enableTauriAPI": true
},
{
"domain": "webstatic.mihoyo.com",
"windows": ["mhy_client"],
"enableTauriAPI": true
},
{
"domain": "bbs.mihoyo.com",
"windows": ["mhy_client"],
"enableTauriAPI": true
},
{
"domain": "api-takumi-record.mihoyo.com",
"windows": ["mhy_client"],

View File

@@ -19,7 +19,6 @@ import TBackTop from "./components/app/t-backTop.vue";
import TSidebar from "./components/app/t-sidebar.vue";
import showConfirm from "./components/func/confirm";
import showSnackbar from "./components/func/snackbar";
import { getEmojis } from "./plugins/Mys/request/getEmojis";
import TGSqlite from "./plugins/Sqlite";
import { useAppStore } from "./store/modules/app";
import { useUserStore } from "./store/modules/user";
@@ -64,7 +63,6 @@ async function listenOnInit(): Promise<void> {
await event.listen("initApp", async () => {
await tauri.invoke("register_deep_link");
await getDeepLink();
await emojiLoad();
await checkAppLoad();
await checkUserLoad();
await checkUpdate();
@@ -72,20 +70,6 @@ async function listenOnInit(): Promise<void> {
return;
}
async function emojiLoad(): Promise<void> {
const res = await getEmojis();
if ("retcode" in res) {
console.error(res);
showSnackbar({
text: "表情包加载失败!",
color: "error",
timeout: 3000,
});
} else {
localStorage.setItem("emojis", JSON.stringify(res));
}
}
async function checkAppLoad(): Promise<void> {
if (appStore.loading) {
console.info("数据已加载!");

View File

@@ -1,304 +0,0 @@
/*
* @file assets/css/post-parser.css
* @description 米游社解析 css
* @todo 需要完善
* @since Beta v0.3.4
*/
:deep(.mys-post-div) {
position: relative;
margin: 10px auto;
}
:deep(.mys-post-span) {
line-height: 2;
}
:deep(.mys-post-link) {
color: #00c3ff;
text-decoration: none;
}
:deep(.mys-post-details) {
padding: 10px;
border: #35acce 2px solid;
border-radius: 10px;
}
:deep(.mys-post-details) ::marker {
color: #35acce;
content: "✧";
}
:deep(.mys-post-divider) {
margin: 10px auto;
}
:deep(.mys-post-divider) img {
width: 800px;
height: auto;
}
:deep(.mys-post-img) {
width: 800px;
max-width: 100%;
height: auto;
border-radius: 10px;
}
:deep(.mys-post-vod) {
width: 800px;
height: 450px;
border-radius: 10px;
}
:deep(.mys-post-vod-cover-div) {
position: absolute;
z-index: -1;
top: 0;
left: 0;
display: flex;
overflow: hidden;
width: 800px;
height: 450px;
align-items: center;
justify-content: center;
border: 1px solid var(--common-shadow-2);
border-radius: 10px;
}
:deep(.mys-post-vod-cover) {
max-width: 800px;
max-height: 450px;
object-fit: contain;
}
:deep(.mys-post-vod-icon) {
position: absolute;
top: calc(50% - 40px);
left: calc(50% - 40px);
width: 80px;
height: 80px;
font-size: 50px;
line-height: 80px;
text-align: center;
}
:deep(.mys-post-vod-time) {
position: absolute;
right: 10px;
bottom: 10px;
padding: 0 5px;
border-radius: 5px;
background: rgba(0, 0, 0, 0.5);
color: #ffffff;
font-family: var(--font-title);
font-size: 12px;
}
:deep(.mys-post-iframe) {
overflow: hidden;
width: 800px;
height: 450px;
border: 0;
border-radius: 10px;
}
:deep(.mys-post-link-card) {
display: flex;
width: 800px;
height: 200px;
padding: 10px;
border: 1px solid var(--common-shadow-1);
border-radius: 10px;
background: var(--box-bg-1);
gap: 10px;
}
:deep(.mys-post-link-card-cover) {
width: auto;
height: 180px;
border-radius: 10px;
}
:deep(.mys-post-link-card-cover) img {
width: auto;
max-width: 400px;
height: 180px;
border-radius: 10px;
}
:deep(.mys-post-link-card-content) {
display: flex;
width: 100%;
height: 180px;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
:deep(.mys-post-link-card-title) {
width: 100%;
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
text-align: left;
}
:deep(.mys-post-link-card-price) {
display: inline-block;
color: #ff6d6d;
font-size: 20px;
font-weight: bold;
}
:deep(.mys-post-link-card-btn) {
display: inline-block;
margin-left: auto;
color: #00c3ff;
text-align: right;
text-decoration: none;
}
/* 表情包 */
:deep(.mys-post-emoji) {
width: 45px;
height: 45px;
margin: 0 5px;
}
/* 大别野卡片 */
:deep(.mys-post-villa-card) {
position: relative;
overflow: hidden;
width: 800px;
border: 1px solid var(--common-shadow-2);
border-radius: 10px;
}
:deep(.mys-post-villa-card-bg) {
position: absolute;
top: -40px;
right: 0;
width: 100%;
border-radius: 10px;
object-fit: cover;
}
:deep(.mys-post-villa-card-bg-before) {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
background: linear-gradient(rgba(255, 255, 255, 0) 40px, var(--box-bg-1) 140px);
}
:deep(.mys-post-villa-card-flex) {
position: relative;
display: flex;
width: 100%;
flex-direction: column;
justify-content: space-between;
padding: 10px;
border-radius: 10px;
gap: 10px;
}
:deep(.mys-post-villa-card-top) {
display: flex;
width: 100%;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
}
:deep(.mys-post-villa-card-icon) {
width: 80px;
height: 80px;
border-radius: 5px;
}
:deep(.mys-post-villa-card-content) {
display: flex;
height: 80px;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
}
:deep(.mys-post-villa-card-title) {
padding: 0 5px;
border-radius: 5px;
backdrop-filter: blur(5px);
background: var(--common-shadow-t-4);
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
:deep(.mys-post-villa-card-desc) {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
padding: 0 5px;
border-radius: 5px;
backdrop-filter: blur(5px);
background: var(--common-shadow-t-2);
gap: 5px;
}
:deep(.mys-post-villa-card-desc-icon) {
width: 30px;
height: 30px;
border-radius: 50%;
}
:deep(.mys-post-villa-card-desc-text) {
color: var(--common-text-content);
font-family: var(--font-text);
font-size: 14px;
}
:deep(.mys-post-villa-card-mid) {
display: flex;
width: 100%;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
}
:deep(.mys-post-villa-card-tag) {
padding: 2px 5px;
border-radius: 5px;
background: var(--box-bg-2);
color: var(--box-text-2);
font-family: var(--font-text);
font-size: 12px;
}
:deep(.mys-post-villa-card-bottom) {
width: 100%;
font-family: var(--font-title);
font-size: 20px;
text-align: left;
}
/* 未知类型 */
:deep(.mys-post-unknown) {
width: 800px;
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 10px;
background: var(--box-bg-1);
color: var(--common-text-content);
}
:deep(.mys-post-unknown-code) {
font-family: var(--font-text);
white-space: pre-wrap;
word-break: break-all;
}

View File

@@ -30,6 +30,11 @@
<img src="/platforms/mhy/mys.webp" alt="mihoyo" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="帖子" value="posts" :link="true" href="/posts">
<template #prepend>
<img src="/source/UI/posts.png" alt="posts" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="成就" value="achievements" :link="true" href="/achievements">
<template #prepend>
<img src="../../assets/icons/achievements.svg" alt="achievementsIcon" class="side-icon" />
@@ -93,47 +98,11 @@
</v-list-item>
</v-list-group>
<div class="bottom-menu">
<v-menu open-on-click location="end">
<template #activator="{ props }">
<v-list-item :title="userInfo.nickname" v-bind="props">
<template #prepend>
<img :src="userInfo.avatar" alt="userIcon" class="side-icon" />
</template>
</v-list-item>
<v-list-item :title="userInfo.nickname">
<template #prepend>
<img :src="userInfo.avatar" alt="userIcon" class="side-icon" />
</template>
<v-list class="side-list-user" density="compact" :nav="true">
<v-list-item class="side-item-user" title="签到" @click="openClient('sign_in')">
<template #prepend>
<img src="/source/UI/userGacha.webp" class="side-icon-user" alt="sing_in" />
</template>
</v-list-item>
<v-list-item class="side-item-user" title="战绩" @click="openClient('game_record')">
<template #prepend>
<img src="/source/UI/userRecord.webp" class="side-icon-user" alt="game_record" />
</template>
</v-list-item>
<v-list-item class="side-item-user" title="酒馆" @click="openClient('tavern')">
<template #prepend>
<img src="/platforms/mhy/mys.webp" alt="酒馆" class="side-icon-user" />
</template>
</v-list-item>
<v-list-item class="side-item-user" title="工具箱" @click="openClient('toolbox')">
<template #prepend>
<img src="/source/UI/toolbox.webp" alt="工具箱" class="side-icon-user" />
</template>
</v-list-item>
<v-list-item
class="side-item-user"
title="登录"
@click="login"
v-show="userStore.cookie?.game_token === ''"
>
<template #prepend>
<img src="/source/UI/defaultUser.webp" class="side-icon-user" alt="login" />
</template>
</v-list-item>
</v-list>
</v-menu>
</v-list-item>
<v-list-item :title="themeTitle" @click="switchTheme()">
<template #prepend>
<v-icon>
@@ -158,7 +127,6 @@ import { computed, onMounted, ref } from "vue";
import { useAppStore } from "../../store/modules/app";
import { useUserStore } from "../../store/modules/user";
import mhyClient from "../../utils/TGClient";
import showSnackbar from "../func/snackbar";
const appStore = useAppStore();
const userStore = useUserStore();
@@ -222,20 +190,6 @@ async function listenOnTheme(): Promise<void> {
async function switchTheme(): Promise<void> {
await event.emit("readTheme", themeGet.value === "default" ? "dark" : "default");
}
async function openClient(func: string): Promise<void> {
if (appStore.isLogin) {
await mhyClient.open(func);
} else {
login();
}
}
function login(): void {
showSnackbar({
text: "请前往设置页面扫码登录",
});
}
</script>
<style lang="css" scoped>
@@ -261,22 +215,4 @@ function login(): void {
border-radius: 5px;
margin-right: 32px;
}
.side-list-user {
background: var(--app-side-bg) !important;
color: var(--app-side-content) !important;
font-family: var(--font-title);
}
.side-item-user {
border: 1px solid var(--common-shadow-2);
background: var(--box-bg-1);
}
.side-icon-user {
width: 20px;
height: 20px;
border-radius: 5px;
margin-right: 10px;
}
</style>

View File

@@ -223,7 +223,7 @@ defineExpose({
font-size: 20px;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
white-space: normal;
word-break: break-all;
}

View File

@@ -0,0 +1,105 @@
<template>
<div
v-if="props.data.insert.lottery"
@click="toLottery()"
class="tp-backup-lottery"
:title="`ID: ${props.data.insert.lottery.id}`"
>
<v-icon size="small">mdi-gift</v-icon>
<span>{{ props.data.insert.lottery.toast }}</span>
</div>
<details v-else-if="props.data.insert.fold" class="tp-backup-fold">
<summary class="tp-backup-summary">
<img alt="marker" src="/source/post/fold_marker.webp" class="tp-backup-marker" />
<TpParser :data="JSON.parse(props.data.insert.fold.title)" />
</summary>
<div class="tp-backup-details">
<TpParser :data="JSON.parse(props.data.insert.fold.content)" />
</div>
</details>
<TpUnknown v-else :data="<TGApp.Plugins.Mys.SctPost.Empty>props.data" />
</template>
<script lang="ts" setup>
import { toRaw } from "vue";
import { useRouter } from "vue-router";
import TpParser from "./tp-parser.vue";
import TpUnknown from "./tp-unknown.vue";
interface TpBackupText {
insert: {
backup_text: string;
fold?: {
title: string;
content: string;
};
lottery?: {
id: string;
toast: string;
};
};
}
interface TpBackupTextProps {
data: TpBackupText;
}
const props = defineProps<TpBackupTextProps>();
const router = useRouter();
console.log("tpBackupText", props.data.insert.backup_text, toRaw(props.data));
async function toLottery() {
if (!props.data.insert.lottery) return;
await router.push({
name: "抽奖详情",
params: {
lottery_id: props.data.insert.lottery.id,
},
});
}
</script>
<style lang="css" scoped>
.tp-backup-lottery {
display: inline-flex;
align-items: center;
justify-content: center;
color: #00c3ff;
column-gap: 5px;
cursor: pointer;
}
.tp-backup-fold {
position: relative;
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 10px;
margin: 10px auto;
}
.tp-backup-fold summary {
list-style: none;
}
.tp-backup-fold ::marker {
color: var(--common-shadow-4);
content: "";
}
.tp-backup-summary {
display: flex;
margin-left: 5px;
font-family: var(--font-title);
}
.tp-backup-marker {
position: relative;
display: inline;
width: 20px;
height: 20px;
}
.tp-backup-details {
padding-left: 20px;
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<div class="tp-divider-box" v-if="isInclude">
<img
alt="divider"
:src="`/source/post/divider_${props.data.insert.divider}.webp`"
:title="props.data.insert.divider"
/>
</div>
<TpUnknown v-else :data="emptyData" />
</template>
<script lang="ts" setup>
import { computed } from "vue";
import TpUnknown from "./tp-unknown.vue";
interface TpDivider {
insert: {
divider: string;
};
}
interface TpDividerProps {
data: TpDivider;
}
const props = defineProps<TpDividerProps>();
const emptyData: TGApp.Plugins.Mys.SctPost.Empty = <TGApp.Plugins.Mys.SctPost.Empty>props.data;
console.log("tpDivider", props.data.insert.divider);
const isInclude = computed(() => {
const list = ["line_1", "line_2", "line_3", "line_4"];
return list.includes(props.data.insert.divider);
});
</script>
<style lang="css" scoped>
.tp-divider-box {
margin: 10px auto;
}
.tp-divider-box img {
max-width: 100%;
}
</style>

View File

@@ -0,0 +1,72 @@
<template>
<div class="tp-image-box">
<img
:style="getImageStyle()"
:src="props.data.insert.image"
:alt="props.data.insert.image"
:title="getImageTitle()"
/>
</div>
</template>
<script lang="ts" setup>
import { StyleValue, toRaw } from "vue";
import { bytesToSize } from "../../utils/toolFunc";
interface TpImage {
insert: {
image: string;
};
attributes?: {
width: number;
height: number;
size?: number;
ext?: "png" | "jpg"; // 待补充
align?: "center"; // 待补充
};
}
interface TpImageProps {
data: TpImage;
}
const props = defineProps<TpImageProps>();
console.log("tp-image", props.data.insert.image, toRaw(props.data).attributes);
function getImageStyle(): StyleValue {
let style: StyleValue = <Array<StyleValue>>[];
if (props.data.attributes == undefined) return style;
if (props.data.attributes.width < 800) {
const widthFullRule: StyleValue = "width: 100%";
style.push(widthFullRule);
}
return style;
}
function getImageTitle(): string {
if (props.data.attributes == undefined) return "";
const res: string[] = [];
res.push(`宽度:${props.data.attributes.width}px`);
res.push(`高度:${props.data.attributes.height}px`);
if (props.data.attributes.size) {
const size = bytesToSize(props.data.attributes.size);
res.push(`大小:${size}`);
}
if (props.data.attributes.ext) {
res.push(`格式:${props.data.attributes.ext}`);
}
return res.join("\n");
}
</script>
<style lang="css" scoped>
.tp-image-box {
margin: 10px auto;
}
.tp-image-box img {
max-width: 100%;
height: auto;
border-radius: 10px;
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<div class="tp-link-card-box">
<img :src="props.data.insert.link_card.cover" alt="cover" @click="toLink()" />
<div class="tp-link-card-content">
<span>{{ props.data.insert.link_card.title }}</span>
<div v-if="props.data.insert.link_card.price" class="tp-link-card-price">
{{ props.data.insert.link_card.price }}
</div>
<div @click="toLink()" class="tp-link-card-btn">
{{ props.data.insert.link_card.button_text ?? "详情" }} >
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { toRaw } from "vue";
import { useRouter } from "vue-router";
import { isMysPost } from "../../utils/toolFunc";
interface TpLinkCard {
insert: {
link_card: {
link_type: number;
origin_url: string;
landing_url: string;
cover: string;
title: string;
card_id: string;
card_status: number;
market_price: string;
price?: string;
button_text?: string;
landing_url_type: number;
};
};
}
interface TpLinkCardProps {
data: TpLinkCard;
}
const props = defineProps<TpLinkCardProps>();
const router = useRouter();
console.log("tpLinkCard", props.data.insert.link_card.card_id, toRaw(props.data).insert.link_card);
async function toLink() {
const link = props.data.insert.link_card.landing_url;
if (isMysPost(link)) {
await router.push({
name: "帖子详情",
params: {
post_id: link.split("/").pop(),
},
});
} else {
window.open(link);
}
}
</script>
<style lang="css" scoped>
.tp-link-card-box {
display: flex;
max-width: 100%;
padding: 10px;
border: 1px solid var(--common-shadow-1);
border-radius: 10px;
background: var(--app-side-bg);
column-gap: 10px;
}
.tp-link-card-box img {
max-width: 50%;
max-height: 180px;
border-radius: 10px;
cursor: pointer;
transition: all 0.5s;
}
.tp-link-card-box img:hover {
scale: 0.9;
}
.tp-link-card-content {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
justify-content: space-between;
font-family: var(--font-title);
}
.tp-link-card-content :nth-child(1) {
width: 100%;
color: var(--common-text-title);
font-size: 20px;
text-align: left;
}
.tp-link-card-price {
display: inline-block;
color: #ff6d6d;
font-size: 20px;
}
.tp-link-card-btn {
display: inline-block;
margin-left: auto;
color: #00c3ff;
cursor: pointer;
text-align: right;
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<span class="tp-mention-box" @click="toLink()">
<v-icon size="small">mdi-account-circle-outline</v-icon>
<span>{{ props.data.insert.mention.nickname }}</span>
</span>
</template>
<script lang="ts" setup>
import { toRaw } from "vue";
import TGClient from "../../utils/TGClient";
import showConfirm from "../func/confirm";
interface TpMention {
insert: {
mention: {
uid: string;
nickname: string;
};
};
}
interface TpMentionProps {
data: TpMention;
}
const props = defineProps<TpMentionProps>();
console.log("tpMention", props.data.insert.mention.uid, toRaw(props.data).insert.mention);
async function toLink(): Promise<void> {
const uid = props.data.insert.mention.uid;
const confirm = await showConfirm({
title: "跳转提示",
text: "是否采用内置 JSBridge 跳转?",
});
if (confirm) {
const prefix = "https://m.miyoushe.com/ys/#/accountCenter/0?id=";
await TGClient.open("mention", `${prefix}${uid}`);
} else {
const prefix = "https://www.miyoushe.com/ys/accountCenter/postList?id=";
window.open(`${prefix}${uid}`);
}
}
</script>
<style lang="css" scoped>
.tp-mention-box {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 5px;
border: 1px solid var(--common-shadow-1);
border-radius: 5px;
margin: 0 2px;
color: #00c3ff;
cursor: pointer;
transform: translateY(2px);
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<component v-for="(tp, index) in props.data" :key="index" :is="getTpName(tp)" :data="tp" />
</template>
<script lang="ts" setup>
import TpBackupText from "./tp-backupText.vue";
import TpDivider from "./tp-divider.vue";
import TpImage from "./tp-image.vue";
import TpLinkCard from "./tp-linkCard.vue";
import TpMention from "./tp-mention.vue";
import TpText from "./tp-text.vue";
import TpUnknown from "./tp-unknown.vue";
import TpVillaCard from "./tp-villaCard.vue";
import TpVod from "./tp-vod.vue";
interface TpParserProps {
data: TGApp.Plugins.Mys.SctPost.Base[];
}
const props = defineProps<TpParserProps>();
function getTpName(tp: TGApp.Plugins.Mys.SctPost.Base) {
if (typeof tp.insert === "string") {
return TpText;
} else if ("image" in tp.insert) {
return TpImage;
} else if ("vod" in tp.insert) {
return TpVod;
// } else if ("video" in tp.insert) {
// return TpVideo;
} else if ("backup_text" in tp.insert) {
return TpBackupText;
} else if ("link_card" in tp.insert) {
return TpLinkCard;
} else if ("divider" in tp.insert) {
return TpDivider;
} else if ("mention" in tp.insert) {
return TpMention;
} else if ("villa_card" in tp.insert) {
return TpVillaCard;
}
return TpUnknown;
}
</script>

View File

@@ -0,0 +1,220 @@
<template>
<span
v-if="mode == 'link'"
class="tp-text-link"
@click="toLink()"
:title="props.data.attributes?.link"
>
<v-icon size="small">mdi-link-variant</v-icon>{{ props.data.insert }}
</span>
<span v-else-if="mode == 'emoji'" class="tp-text-emoji">
<img :src="getEmojiUrl()" :alt="getEmojiName()" :title="getEmojiName()" />
</span>
<TpText
v-else-if="mode == 'emojis'"
v-for="(emoji, index) in emojis"
:data="emoji"
:key="index"
/>
<span v-else :style="getTextStyle()" class="tp-text-span">
{{ props.data.insert }}
</span>
</template>
<script lang="ts" setup>
import { onMounted, ref, StyleValue, toRaw } from "vue";
import { useRouter } from "vue-router";
import { getEmojis } from "../../plugins/Mys/request/getEmojis";
import TGClient from "../../utils/TGClient";
import { isColorSimilar, isMysPost } from "../../utils/toolFunc";
import showConfirm from "../func/confirm";
import showSnackbar from "../func/snackbar";
interface TpText {
insert: string;
attributes?: {
link?: string;
bold?: boolean;
color?: string;
align?: string;
};
}
interface TpTextProps {
data: TpText;
}
const props = defineProps<TpTextProps>();
const mode = ref<string>("text");
const router = useRouter();
const localEmojis = ref(localStorage.getItem("emojis"));
const emojis = ref<TpText[]>([]);
console.log("tpText", JSON.stringify(props.data.insert), toRaw(props.data)?.attributes);
onMounted(async () => {
if (props.data.attributes && "link" in props.data.attributes) {
mode.value = "link";
return;
}
const count = countEmoji(props.data.insert);
if (count == 1) {
mode.value = "emoji";
} else if (count > 1) {
mode.value = "emojis";
emojis.value = parseEmojis(props.data);
} else {
mode.value = "text";
}
});
// 获取可能的 emoji 数量
function countEmoji(text: string): number {
const reg = /_\((.*?)\)/g;
const res = text.match(reg);
if (res) {
return res.length;
}
return 0;
}
// 解析表情
function parseEmojis(text: TpText): TpText[] {
// thanks, @Lightczx & webstorm
const reg = /(?<=\n|.+?|^)(_\(.+?\)(?=\n|.+?|$))/g;
return text.insert.split(reg).map((item) => {
return {
insert: item,
attributes: text.attributes,
};
});
}
// 解析文本样式
function getTextStyle(): StyleValue {
const style = <Array<StyleValue>>[];
const data: TpText = <TpText>props.data;
if (data.attributes) {
if (data.attributes.bold) {
const ruleBold: StyleValue = "fontFamily: var(--font-title)";
style.push(ruleBold);
}
if (data.attributes.color) {
let colorGet = data.attributes.color;
if (isColorSimilar("#000000", data.attributes.color)) {
colorGet = "var(--app-page-content)";
}
const ruleColor: StyleValue = `color: ${colorGet}`;
style.push(ruleColor);
}
// todo 这边效果不是很好
// refer: 45245869
if (data.attributes.align) {
const ruleAlign: StyleValue = `textAlign: ${data.attributes.align}`;
style.push(ruleAlign);
}
}
return style;
}
// 解析链接目标
async function toLink() {
if (!props.data.attributes) return;
if (!props.data.attributes.link) return;
const link = props.data.attributes.link;
if (isMysPost(link)) {
await router.push({
name: "帖子详情",
params: {
post_id: link.split("/").pop(),
},
});
} else if (isMysAct(link)) {
const resOpen = await showConfirm({
title: "采用内置 JSBridge",
text: "取消则使用外部浏览器打开",
});
if (resOpen) {
const resType = await showConfirm({
title: "采用宽屏模式?",
text: "取消则使用默认竖屏",
});
if (resType) {
await TGClient.open("web_act", link);
} else {
await TGClient.open("web_act_thin", link);
}
} else {
window.open(link);
}
} else {
window.open(props.data.attributes.link);
}
}
function isMysAct(url: string): boolean {
const link = new URL(url);
const prefix = ["act.mihoyo.com", "mhyurl.cn", "webstatic.mihoyo.com", "qaa.miyoushe.com"];
if (prefix.includes(link.hostname)) {
if (link.hostname == "webstatic.mihoyo.com") {
return link.pathname.includes("event");
}
return true;
}
return false;
}
// 解析表情链接
function getEmojiUrl(): string {
if (localEmojis.value == null || !JSON.parse(localEmojis.value)[getEmojiName()]) {
console.warn("tpEmoji unknown", getEmojiName());
getEmojis().then((res) => {
if ("retcode" in res) {
console.error(res);
showSnackbar({
text: "获取表情包失败!",
color: "error",
});
mode.value = "text";
return "";
} else {
localEmojis.value = JSON.stringify(res);
localStorage.setItem("emojis", localEmojis.value);
}
});
}
const emojiName = getEmojiName();
return JSON.parse(<string>localEmojis.value)[emojiName];
}
function getEmojiName() {
return props.data.insert.slice(2, -1);
}
</script>
<style lang="css" scoped>
.tp-text-link {
display: inline-flex;
align-items: center;
justify-content: center;
color: #00c3ff;
cursor: pointer;
transform: translateY(2px);
}
.tp-text-emoji {
display: inline-flex;
align-items: flex-end;
justify-content: center;
transform: translateY(5px);
}
.tp-text-emoji img {
width: 45px;
height: 45px;
margin: 0 5px;
}
.tp-text-span {
white-space: pre-wrap;
}
</style>

View File

@@ -0,0 +1,12 @@
<template>
<div class="mys-post-unknown">
<code class="mys-post-unknown-code">{{ JSON.stringify(props.data, null, 2) }}</code>
</div>
</template>
<script lang="ts" setup>
interface TpUnknownProps {
data: TGApp.Plugins.Mys.SctPost.Empty;
}
const props = defineProps<TpUnknownProps>();
</script>

View File

@@ -0,0 +1,32 @@
<template>
<div class="tp-video-box">
{{ props.data }}
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
// todo https://zhuanlan.zhihu.com/p/450632587
interface TpVideo {
insert: {
video: string;
};
}
interface TpVideoProps {
data: TpVideo;
}
const props = defineProps<TpVideoProps>();
const videoAspectRatio = ref<number>(16 / 9);
console.log("tpVideo", props.data.insert.video);
</script>
<style lang="css" scoped>
.tp-video-box {
position: relative;
max-width: 100%;
margin: 10px auto;
aspect-ratio: v-bind(videoAspectRatio);
}
</style>

View File

@@ -0,0 +1,184 @@
<template>
<div
class="tp-villa-card-box"
:style="{
backgroundImage: 'url(' + props.data.insert.villa_card.villa_cover + ')',
}"
>
<div class="tp-villa-card-content">
<img alt="cardIcon" :src="props.data.insert.villa_card.villa_avatar_url" />
<div class="tp-villa-card-info">
<span class="tp-villa-card-name">{{ props.data.insert.villa_card.villa_name }}</span>
<span class="tp-villa-card-owner">
<img alt="topIcon" :src="props.data.insert.villa_card.owner_avatar_url" />
<span>{{ props.data.insert.villa_card.owner_nickname }} 创建</span>
</span>
</div>
</div>
<div class="tp-villa-card-tags">
<div class="tp-villa-card-tag">
{{ props.data.insert.villa_card.villa_member_num }} 人在聊
</div>
<div
v-for="(tag, index) in props.data.insert.villa_card?.tag_list"
:key="index"
class="tp-villa-card-tag"
>
{{ tag }}
</div>
</div>
<div class="tp-villa-card-desc">
{{ props.data.insert.villa_card.villa_introduce }}
</div>
</div>
</template>
<script lang="ts" setup>
import { toRaw } from "vue";
interface VillaRoom {
room_id: string;
room_name: string;
sender_avatar_list: string[];
sender_num: string;
}
interface TpVillaCard {
insert: {
villa_card: {
villa_id: string;
villa_name: string;
villa_avatar_url: string;
villa_cover: string;
owner_uid: string;
owner_nickname: string;
owner_avatar_url: string;
villa_introduce: string;
tag_list?: string[];
villa_member_num: string;
is_official: boolean;
is_available: boolean;
hot_member_avatar: string[];
hot_room: VillaRoom;
};
};
}
interface TpVillaCardProps {
data: TpVillaCard;
}
const props = defineProps<TpVillaCardProps>();
console.log(
"tpVillaCard",
props.data.insert.villa_card.villa_id,
toRaw(props.data).insert.villa_card,
);
</script>
<style lang="css" scoped>
.tp-villa-card-box {
position: relative;
display: flex;
overflow: hidden;
flex-direction: column;
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 10px;
margin: 10px auto;
background-position: top center;
background-repeat: no-repeat;
background-size: cover;
row-gap: 10px;
}
.tp-villa-card-content {
display: flex;
align-items: flex-start;
justify-content: flex-start;
column-gap: 10px;
}
.tp-villa-card-content img {
width: 80px;
height: 80px;
border-radius: 5px;
}
.tp-villa-card-info {
display: flex;
height: 80px;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
row-gap: 5px;
}
.tp-villa-card-name {
padding: 0 5px;
border-radius: 5px;
backdrop-filter: blur(20px);
background: var(--common-shadow-t-2);
box-shadow: 0 0 5px var(--common-shadow-8);
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
.tp-villa-card-owner {
display: flex;
align-items: center;
padding: 5px;
border-radius: 20px 5px 5px 20px;
backdrop-filter: blur(20px);
background: var(--common-shadow-t-2);
box-shadow: 0 0 5px var(--common-shadow-8);
color: var(--common-text-title);
column-gap: 5px;
}
.tp-villa-card-owner img {
width: 30px;
height: 30px;
border-radius: 50%;
}
.tp-villa-card-owner span {
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-title);
}
.tp-villa-card-tags {
display: flex;
flex-wrap: wrap;
column-gap: 10px;
}
.tp-villa-card-tag {
display: flex;
align-items: center;
justify-content: center;
padding: 5px 10px;
border-radius: 5px;
backdrop-filter: blur(20px);
background: var(--common-shadow-t-2);
box-shadow: 0 0 5px var(--common-shadow-8);
color: var(--tgc-pink-1);
font-family: var(--font-title);
font-size: 12px;
}
.tp-villa-card-desc {
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
border-radius: 5px;
margin-right: auto;
backdrop-filter: blur(20px);
background: var(--common-shadow-t-2);
box-shadow: 0 0 5px var(--common-shadow-8);
color: var(--box-text-1);
}
</style>

View File

@@ -0,0 +1,173 @@
<template>
<div class="tp-vod-box">
<div
class="tp-vod-container"
data-html2canvas-ignore
:id="`tp-vod-${props.data.insert.vod.id}`"
></div>
<div class="tp-vod-cover">
<img alt="cover" :src="props.data.insert.vod.cover" />
<img src="/source/UI/video_play.svg" alt="icon" />
<span>{{ getVodTime() }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { window as TauriWindow } from "@tauri-apps/api";
import Artplayer from "artplayer";
import type { Option } from "artplayer/types/option";
import { onMounted, ref, toRaw } from "vue";
interface TpVod {
insert: {
vod: {
id: string;
duration: number;
cover: string;
resolutions: Array<{
url: string;
definition: "480P" | "720P" | "1080P" | "2K"; // 待补充
height: number;
width: number;
bitrate: number;
size: number;
format: "MP4"; // 待补充
label: "480P" | "720P" | "1080P" | "2K"; // 待补充
}>;
view_num: number;
transcode_status: number;
review_status: number;
};
};
}
interface TpVodProps {
data: TpVod;
}
const props = defineProps<TpVodProps>();
const container = ref<Artplayer | null>(null);
const vodAspectRatio = ref<number>(16 / 9);
console.log("tpVod", props.data.insert.vod.id, toRaw(props.data).insert.vod);
onMounted(async () => {
const resolutions = props.data.insert.vod.resolutions;
const highestResolution = resolutions.reduce((prev, curr) => {
return prev.size > curr.size ? prev : curr;
});
const width = highestResolution.width;
const height = highestResolution.height;
if (width && height) {
width > height
? (vodAspectRatio.value = width / height)
: (vodAspectRatio.value = height / width);
}
const option: Option = {
id: props.data.insert.vod.id,
container: `#tp-vod-${props.data.insert.vod.id}`,
url: highestResolution.url,
poster: props.data.insert.vod.cover,
type: highestResolution.format,
playbackRate: true,
aspectRatio: true,
setting: true,
hotkey: true,
pip: true,
quality: resolutions.map((resolution) => {
return {
default: resolution.label == highestResolution.label,
html: resolution.label,
url: resolution.url,
};
}),
fullscreen: true,
icons: {
// eslint-disable-next-line @typescript-eslint/quotes
state: `<img src="/source/UI/video_play.svg" alt="icon" />`,
},
lang: "zh-cn",
airplay: true,
controls: [
{
name: "subtitle",
index: 100,
position: "left",
html: `<i class="mdi mdi-eye"></i><span style="padding-left: 5px">${props.data.insert.vod.view_num}</span>`,
tooltip: `播放数:${props.data.insert.vod.view_num}`,
},
],
};
container.value = new Artplayer(option);
container.value?.on("fullscreen", async (state) => {
await TauriWindow.getCurrent().setFullscreen(state);
});
});
function getVodTime(): string {
const duration = props.data.insert.vod.duration;
const secTotal = Math.floor(duration / 1000);
const seconds = secTotal % 60;
const minutes = Math.floor(secTotal / 60) % 60;
const hours = Math.floor(secTotal / 3600);
let result = "";
if (hours > 0) {
result += `${hours.toString().padStart(2, "0")}:`;
}
result += `${minutes.toString().padStart(2, "0")}:`;
result += `${seconds.toString().padStart(2, "0")}`;
return result;
}
</script>
<style lang="css" scoped>
.tp-vod-box {
position: relative;
max-width: 100%;
margin: 10px auto;
aspect-ratio: v-bind(vodAspectRatio);
}
.tp-vod-container {
overflow: hidden;
max-width: 100%;
border-radius: 10px;
aspect-ratio: v-bind(vodAspectRatio);
}
.tp-vod-cover {
position: absolute;
z-index: -1;
top: 0;
left: 0;
display: flex;
overflow: hidden;
align-items: center;
justify-content: center;
border-radius: 10px;
aspect-ratio: v-bind(vodAspectRatio);
}
.tp-vod-cover :nth-child(1) {
max-width: 100%;
}
.tp-vod-cover :nth-child(2) {
position: absolute;
top: calc(50% - 40px);
left: calc(50% - 40px);
width: 80px;
height: 80px;
}
.tp-vod-cover :nth-child(3) {
position: absolute;
right: 10px;
bottom: 10px;
padding: 0 5px;
border-radius: 5px;
background: rgb(0 0 0/50%);
color: var(--tgc-white-4);
font-family: var(--font-title);
font-size: 12px;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="props.modelValue === undefined">暂无数据</div>
<div v-if="!props.modelValue">暂无数据</div>
<div v-else class="tur-ag-box">
<TibUrAvatar v-for="avatar in data" :key="avatar.id" :model-value="avatar" />
</div>

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="props.modelValue === undefined">暂无数据</div>
<div v-if="!props.modelValue">暂无数据</div>
<div v-else class="tur-hg-box">
<TurHomeSub v-for="(home, index) in homes" :key="index" :data="home" />
</div>

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="props.modelValue === undefined">暂无数据</div>
<div v-if="!props.modelValue">暂无数据</div>
<div v-else class="tur-og-box">
<TurOverviewSub title="活跃天数" :text="data.activeDays" />
<TurOverviewSub title="成就达成数" :text="data.achievementNumber" />

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="props.modelValue === undefined">暂无数据</div>
<div v-if="!props.modelValue">暂无数据</div>
<div v-else class="tur-wg-box">
<TurWorldSub v-for="(area, index) in getData()" :key="index" :data="area" :theme="theme" />
</div>

View File

@@ -11,7 +11,7 @@
v-model="search"
append-icon="mdi-magnify"
label="搜索"
single-line
:single-line="true"
hide-details
@click:append="searchCard"
@keyup.enter="searchCard"

View File

@@ -26,7 +26,9 @@
</div>
</div>
</div>
<v-card-title class="anno-title" :title="item.title">{{ item.subtitle }}</v-card-title>
<v-card-title class="anno-title" :title="item.title">{{
parseTitle(item.title)
}}</v-card-title>
<div class="anno-label" :title="`标签:${item.tagLabel}`">
<img :src="item.tagIcon" alt="tag" />
<span>{{ item.tagLabel }}</span>
@@ -89,6 +91,12 @@ onMounted(async () => {
});
});
function parseTitle(title: string): string {
const div = document.createElement("div");
div.innerHTML = title;
return div.innerText;
}
async function switchNews(): Promise<void> {
await router.push("/news/2");
}

View File

@@ -13,7 +13,7 @@
class="news-search"
append-icon="mdi-magnify"
label="请输入米游社帖子 ID"
single-line
:single-line="true"
hide-details
@click:append="searchPost"
@keyup.enter="searchPost"
@@ -264,6 +264,10 @@ function searchPost(): void {
font-family: var(--font-title);
}
.dark .news-switch-btn {
border: 1px solid var(--common-shadow-2);
}
.news-search {
margin-left: 10px;
color: var(--box-text-1);

621
src/pages/common/Posts.vue Normal file
View File

@@ -0,0 +1,621 @@
<template>
<ToLoading v-model="loading" :title="loadingTitle" />
<div class="posts-box">
<div class="posts-switch">
<v-select
v-model="curGameLabel"
class="post-switch-item"
:items="gameItem"
:theme="vuetifyTheme"
variant="outlined"
label="游戏"
/>
<v-select
v-model="curForumLabel"
class="post-switch-item"
:items="forumItem"
:theme="vuetifyTheme"
variant="outlined"
label="频道"
/>
<v-select
v-model="curSortLabel"
class="post-switch-item"
:items="sortItem"
:theme="vuetifyTheme"
variant="outlined"
label="排序"
/>
<v-text-field
v-model="search"
class="post-switch-item"
append-inner-icon="mdi-magnify"
label="请输入帖子 ID"
variant="outlined"
:single-line="true"
hide-details
@click:append="searchPost"
@keyup.enter="searchPost"
/>
<v-btn class="post-fresh-btn" @click="freshPostData(false)">
<v-icon>mdi-refresh</v-icon>
<span>刷新</span>
</v-btn>
</div>
<!-- todo: hover效果本来是只有 iconhover之后显示 title -->
<div class="posts-nav">
<div
v-for="navItem in nav"
:key="navItem.id"
class="post-nav"
@click="toNav(navItem.app_path)"
>
<img alt="navIcon" :src="navItem.icon" />
<span>{{ navItem.name }}</span>
</div>
</div>
<!-- todo 无限加载 -->
<div class="posts-grid">
<v-card v-for="post in posts" :key="post.postId" class="post-card">
<div class="post-cover" @click="createPost(post)">
<img :src="post.cover" alt="cover" />
</div>
<div class="post-content">
<div class="post-card-title" :title="post.title">{{ post.title }}</div>
<div class="post-card-user">
<div class="pcu-left">
<div class="pcu-icon">
<img :src="post.user.icon" alt="userIcon" />
</div>
<div v-if="post.user.pendant !== ''" class="pcu-pendent">
<img :src="post.user.pendant" alt="userPendant" />
</div>
</div>
<div class="pcu-right">
<span>{{ post.user.nickname }}</span>
<span :title="post.user.label">{{ post.user.label }}</span>
</div>
</div>
<div class="post-card-data">
<div class="pcd-item" :title="`浏览数:${post.data.view}`">
<v-icon>mdi-eye</v-icon>
<span>{{ post.data.view }}</span>
</div>
<div class="pcd-item" :title="`收藏数:${post.data.mark}`">
<v-icon>mdi-star</v-icon>
<span>{{ post.data.mark }}</span>
</div>
<div class="pcd-item" :title="`回复数:${post.data.reply}`">
<v-icon>mdi-comment</v-icon>
<span>{{ post.data.reply }}</span>
</div>
<div class="pcd-item" :title="`点赞数:${post.data.like}`">
<v-icon>mdi-thumb-up</v-icon>
<span>{{ post.data.like }}</span>
</div>
<div class="pcd-item" :title="`转发数:${post.data.forward}`">
<v-icon>mdi-share-variant</v-icon>
<span>{{ post.data.forward }}</span>
</div>
</div>
</div>
<div class="post-card-forum" :title="`频道: ${post.forum.name}`">
<img :src="post.forum.icon" :alt="post.forum.name" />
<span>{{ post.forum.name }}</span>
</div>
</v-card>
</div>
<div class="load-more">
<v-btn :loading="loading" @click="freshPostData(true)">
{{ rawData.page }}已加载{{ posts.length }}加载更多
</v-btn>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, ref, watch } from "vue";
import showConfirm from "../../components/func/confirm";
import showSnackbar from "../../components/func/snackbar";
import ToLoading from "../../components/overlay/to-loading.vue";
import Mys from "../../plugins/Mys";
import { useAppStore } from "../../store/modules/app";
import TGClient from "../../utils/TGClient";
import { createPost } from "../../utils/TGWindow";
const loading = ref<boolean>(true);
const loadingTitle = ref<string>("正在加载数据");
const appStore = useAppStore();
// 常量
const sortList = {
默认排序: 0,
最新回复: 1,
最新发布: 2,
};
const forumGenshin = {
酒馆: 26,
攻略: 43,
同人图: 29,
COS: 49,
硬核: 50,
};
const forumSr = {
候车室: 52,
攻略: 61,
同人图: 56,
COS: 62,
};
const forumBh3 = {
甲板: 1,
攻略: 14,
同人图: 4,
同人文: 41,
};
const forumBh2 = {
学园: 30,
攻略: 51,
同人图: 40,
};
const forumWd = {
律所: 37,
攻略: 60,
同人文: 42,
同人图: 38,
};
const forumZzz = {
咖啡馆: 57,
同人图: 59,
};
const forumDby = {
校园: 54,
ACG: 35,
生活: 34,
同人图: 39,
COS: 47,
脑洞: 48,
科技: 55,
公告: 36,
};
const forumList = {
原神: forumGenshin,
"崩坏:星穹铁道": forumSr,
崩坏3: forumBh3,
崩坏2: forumBh2,
未定事件簿: forumWd,
绝区零: forumZzz,
大别野: forumDby,
};
const gameList = {
原神: 2,
"崩坏:星穹铁道": 6,
崩坏3: 1,
崩坏2: 3,
未定事件簿: 4,
绝区零: 8,
大别野: 5,
};
// 主题
const vuetifyTheme = computed(() => {
return appStore.theme === "dark" ? "dark" : "light";
});
// 渲染参数
const curForumLabel = ref<string>("酒馆");
const forumItem = ref<string[]>(["酒馆", "攻略", "同人图", "COS", "硬核"]);
const curForum = ref<number>(26);
const rawData = ref({ page: 1, is_last: false });
// 游戏相关
const curGameLabel = ref<keyof typeof gameList>("原神");
const gameItem = ref<string[]>([
"原神",
"崩坏:星穹铁道",
"崩坏3",
"崩坏2",
"未定事件簿",
"绝区零",
"大别野",
]);
const curGid = ref<number>(2);
// 排序相关
const curSortLabel = ref<keyof typeof sortList>("默认排序");
const sortItem = ref<string[]>(["默认排序", "最新回复", "最新发布"]);
const curSortType = ref<number>(0);
// 渲染数据
const posts = ref<TGApp.Plugins.Mys.Forum.RenderCard[]>([]);
const nav = ref<TGApp.BBS.Navigator.Navigator[]>([]);
const search = ref<string>();
onMounted(async () => {
loading.value = true;
await freshNavData();
await freshPostData(false);
loading.value = false;
});
// 监听游戏变化
watch(curGameLabel, async (newVal) => {
curGid.value = gameList[newVal];
forumItem.value = Object.keys(forumList[newVal]);
curForumLabel.value = forumItem.value[0];
freshCurForum(forumItem.value[0]);
await freshNavData();
});
// 监听论坛变化
watch(curForumLabel, async (newVal) => {
freshCurForum(newVal);
await freshPostData(false);
});
// 监听排序变化
watch(curSortLabel, async (newVal) => {
curSortType.value = sortList[newVal];
await freshPostData(false);
});
async function toNav(path: string): Promise<void> {
const link = new URL(path);
const mysList = [
"https://act.mihoyo.com",
"https://webstatic.mihoyo.com",
"https://bbs.mihoyo.com",
"https://qaa.miyoushe.com",
];
if (link.protocol != "https:") {
toBBS(link);
return;
}
// 如果不在上面的域名里面,就直接打开
if (!mysList.includes(link.origin)) {
window.open(path);
return;
}
// todo 记忆宽屏竖屏
const modeConfirm = await showConfirm({
title: "是否采用宽屏模式打开?",
text: "取消则采用竖屏模式打开",
});
if (modeConfirm) await TGClient.open("web_act", path);
else await TGClient.open("web_act_thin", path);
}
// 处理 protocol
function toBBS(link: URL): void {
if (link.protocol == "mihoyobbs:") {
if (link.pathname.startsWith("//article")) {
const postId = link.pathname.split("/").pop();
createPost(<string>postId);
return;
}
if (link.pathname.startsWith("//forum")) {
const forumId = link.pathname.split("/").pop();
const url = `https://www.miyoushe.com/ys/home/${forumId}`;
window.open(url);
return;
}
}
showSnackbar({
text: `不支持的链接:${link.href}`,
color: "warn",
});
}
async function freshNavData(): Promise<void> {
nav.value = await Mys.Posts.nav(curGid.value);
}
async function freshPostData(more: boolean = false): Promise<void> {
loading.value = true;
loadingTitle.value = `正在加载 ${curGameLabel.value}-${curForumLabel.value}-${curSortLabel.value} 的数据`;
if (more) {
const postsGet = await Mys.Posts.get(
curForum.value,
curGid.value,
curSortType.value,
rawData.value.page,
);
if (rawData.value.is_last) {
showSnackbar({
text: "已经是最后一页了",
color: "warn",
});
loading.value = false;
return;
}
posts.value = posts.value.concat(Mys.Posts.card(postsGet));
rawData.value.is_last = postsGet.is_last;
rawData.value.page = postsGet.page;
} else {
const postsGet = await Mys.Posts.get(curForum.value, curGid.value, curSortType.value);
posts.value = Mys.Posts.card(postsGet);
rawData.value.is_last = false;
rawData.value.page = 1;
}
await nextTick();
loading.value = false;
}
function freshCurForum(newVal: string): void {
const forum = forumList[curGameLabel.value];
// @ts-ignore
curForum.value = forum[newVal];
}
// 查询帖子
function searchPost(): void {
if (search.value === undefined || search.value === "") {
showSnackbar({
text: "请输入搜索内容",
color: "error",
});
return;
}
if (!isNaN(Number(search.value))) {
createPost(search.value);
} else {
showSnackbar({
text: "请输入搜索内容",
color: "error",
});
}
}
</script>
<style lang="css" scoped>
.posts-box {
display: flex;
flex-direction: column;
row-gap: 10px;
}
.posts-nav {
display: flex;
flex-wrap: wrap;
padding: 5px;
gap: 10px 10px;
}
.post-nav {
display: flex;
align-items: center;
justify-content: center;
padding: 5px;
border-radius: 5px;
backdrop-filter: blur(20px);
background: var(--common-shadow-t-4);
box-shadow: 0 0 5px var(--common-shadow-4);
color: var(--tgc-white-1);
cursor: pointer;
}
.post-nav img {
width: 25px;
height: 25px;
}
.posts-nav span {
display: none;
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 16px;
}
.post-nav:hover span {
display: block;
}
.posts-switch {
display: flex;
align-items: flex-end;
justify-content: flex-start;
padding: 5px;
column-gap: 10px;
}
.post-switch-item {
max-width: 200px;
height: 50px;
}
.post-fresh-btn {
height: 40px;
background: var(--btn-bg-1);
color: var(--btn-text-1);
font-family: var(--font-title);
}
.dark .post-fresh-btn {
border: 1px solid var(--common-shadow-2);
}
.posts-grid {
display: grid;
padding: 5px;
font-family: var(--font-title);
grid-gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
.post-card {
border-radius: 5px;
background: var(--app-page-bg);
color: var(--box-text-1);
}
.dark .post-card {
border: 1px solid var(--common-shadow-2);
}
.post-cover {
position: relative;
display: flex;
overflow: hidden;
width: 100%;
align-items: center;
justify-content: center;
aspect-ratio: 36 / 13;
}
.post-cover img {
min-width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
transition: all 0.3s linear;
}
.post-content {
position: relative;
display: flex;
width: 100%;
flex-direction: column;
padding: 10px;
gap: 10px;
}
.post-card-title {
overflow: hidden;
width: 100%;
font-size: 18px;
text-overflow: ellipsis;
white-space: nowrap;
}
.post-card-user {
display: flex;
}
.pcu-left {
position: relative;
width: 50px;
height: 50px;
}
.pcu-icon {
position: absolute;
top: 5px;
left: 5px;
overflow: hidden;
width: 40px;
height: 40px;
border-radius: 50%;
}
.pcu-icon img {
width: 100%;
height: 100%;
object-fit: cover;
}
.pcu-pendent {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
width: 50px;
height: 50px;
border-radius: 50%;
}
.pcu-pendent img {
width: 100%;
height: 100%;
object-fit: cover;
}
.pcu-right {
position: relative;
display: flex;
max-width: calc(100% - 50px);
height: 50px;
flex-direction: column;
align-items: start;
color: var(--box-text-4);
}
.pcu-right :nth-child(1) {
display: flex;
height: 30px;
align-items: center;
justify-content: start;
font-size: 16px;
}
.pcu-right :nth-child(2) {
overflow: hidden;
width: 100%;
height: 20px;
border-top: 2px solid var(--common-shadow-2);
font-size: 14px;
opacity: 0.7;
text-overflow: ellipsis;
white-space: nowrap;
}
.post-card-forum {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 5px;
backdrop-filter: blur(20px);
background: rgb(0 0 0/20%);
border-bottom-left-radius: 5px;
border-top-right-radius: 5px;
box-shadow: 0 0 10px var(--tgc-dark-1);
color: var(--tgc-white-1);
}
.post-card-forum img {
width: 20px;
height: 20px;
margin-right: 5px;
}
.post-cover img:hover {
cursor: pointer;
transform: scale(1.1);
transition: all 0.3s linear;
}
.post-card-data {
display: flex;
width: 100%;
height: 20px;
align-items: center;
justify-content: flex-end;
padding: 5px;
column-gap: 10px;
}
.pcd-item {
display: flex;
align-items: center;
justify-content: flex-start;
color: var(--box-text-7);
font-size: 12px;
gap: 5px;
opacity: 0.6;
}
.load-more {
display: flex;
align-items: center;
justify-content: center;
margin: 10px;
font-family: var(--font-title);
transition: all 0.3s linear;
}
.load-more button {
border-radius: 5px;
background: var(--tgc-btn-1);
color: var(--btn-text);
}
</style>

View File

@@ -14,8 +14,18 @@
</div>
</div>
</div>
<TpVideo :data="mock" />
</template>
<script lang="ts" setup></script>
<script lang="ts" setup>
import TpVideo from "../../components/post/tp-video.vue";
const mock = {
insert: {
// todo数据也可能是 BV1qb4y1L7mD缺乏数据来源
video: "https://www.bilibili.com/video/BV1qb4y1L7mD",
},
};
</script>
<style lang="css" scoped>
.test-box {
display: flex;

View File

@@ -1,8 +1,7 @@
/**
* @file plugins Mys api index.ts
* @file plugins/Mys/api/index.ts
* @description Mys API
* @author BTMuli <bt-muli@outlook.com>
* @since Alpha v0.2.1
* @since Beta v0.3.7
*/
const MysApi = {
@@ -10,6 +9,10 @@ const MysApi = {
Gacha: "https://api-takumi.mihoyo.com/common/blackboard/ys_obc/v1/gacha_pool?app_sn=ys_obc",
Lottery: "https://bbs-api.miyoushe.com/painter/wapi/lottery/user/show?id={lotteryId}",
News: "https://bbs-api.mihoyo.com/post/wapi/getNewsList?gids={gid}&page_size={pageSize}&type={newsType}&last_id={lastId}",
Forum:
"https://bbs-api.miyoushe.com/post/wapi/getForumPostList?forum_id={forum}&gids={gid}&sort_type={type}&page={page}&page_size=20",
Feed: "https://bbs-api.miyoushe.com/post/api/feeds/posts?gids={gid}",
Navigator: "https://bbs-api.miyoushe.com/apihub/api/home/new?gids={gid}",
Position: "https://api-static.mihoyo.com/common/blackboard/ys_obc/v1/home/position?app_sn=ys_obc",
Post: {
Api: "https://bbs-api.mihoyo.com/post/wapi/getPostFull?post_id={postId}",

View File

@@ -1,27 +1,33 @@
/**
* @file plugins Mys index.ts
* @file plugins/Mys/index.ts
* @description Mys plugin index
* @since Beta v0.3.0
* @since Beta v0.3.7
*/
import MysApi from "./api";
import { getLoginQr, getLoginStatus } from "./request/doGameLogin";
import getForumList from "./request/getForumList";
import getGachaData from "./request/getGachaData";
import getHomeNavigator from "./request/getHomeNavigator";
import getLotteryData from "./request/getLotteryData";
import getNewsList from "./request/getNewsList";
import getPositionData from "./request/getPositionData";
import getPostData from "./request/getPostData";
import getGachaCard from "./utils/getGachaCard";
import getLotteryCard from "./utils/getLotteryCard";
import { getNoticeCard, getActivityCard, getNewsCard } from "./utils/getNewsCard";
import { getActivityCard, getNewsCard, getNoticeCard } from "./utils/getNewsCard";
import getPositionCard from "./utils/getPositionCard";
import parsePost from "./utils/parsePost";
import { getPostsCard } from "./utils/getPostsCard";
const Mys = {
Api: MysApi,
Post: {
get: getPostData,
parser: parsePost,
},
Posts: {
get: getForumList,
card: getPostsCard,
nav: getHomeNavigator,
},
Gacha: {
get: getGachaData,

View File

@@ -0,0 +1,33 @@
/**
* @file plugins/Mys/request/getForumList.ts
* @description Mys 插件特定论坛请求
* @since Beta v0.3.7
*/
import { http } from "@tauri-apps/api";
import MysApi from "../api";
/**
* @description 获取特定论坛列表
* @since Beta v0.3.7
* @param {number} forumId 特定论坛 ID
* @param {number} gid GID
* @param {number} type 排序方式: 0-按热度排序1-最新回复2-按时间排序
* @param {number} page 页码
* @return {Promise<TGApp.Plugins.Mys.Forum.FullData>}
*/
async function getForumList(
forumId: number,
gid: number = 2,
type: number = 0,
page: number = 1,
): Promise<TGApp.Plugins.Mys.Forum.FullData> {
const url = MysApi.Forum.replace("{forum}", forumId.toString())
.replace("{gid}", gid.toString())
.replace("{type}", type.toString())
.replace("{page}", page.toString());
return await http.fetch<TGApp.Plugins.Mys.Forum.Response>(url).then((res) => res.data.data);
}
export default getForumList;

View File

@@ -0,0 +1,24 @@
/**
* @file plugins/Mys/request/getHomeNavigator.ts
* @description Mys 插件首页导航请求
* @since Beta v0.3.7
*/
import { http } from "@tauri-apps/api";
import MysApi from "../api";
/**
* @description 获取首页导航列表
* @since Beta v0.3.7
* @param {number} gid GID
* @return {Promise<TGApp.BBS.Navigator.Navigator[]>}
*/
async function getHomeNavigator(gid: number = 2): Promise<TGApp.BBS.Navigator.Navigator[]> {
const url = MysApi.Navigator.replace("{gid}", gid.toString());
return await http
.fetch<TGApp.BBS.Navigator.HomeResponse>(url)
.then((res) => res.data.data.navigator);
}
export default getHomeNavigator;

95
src/plugins/Mys/types/Forum.d.ts vendored Normal file
View File

@@ -0,0 +1,95 @@
/**
* @file plugins/Mys/types/Forum.d.ts
* @description Mys 插件论坛类型定义文件
* @since Beta v0.3.7
*/
/**
* @description Mys 插件论坛类型
* @since Beta v0.3.7
* @namespace TGApp.Plugins.Mys.Forum
* @memberof TGApp.Plugins.Mys
*/
declare namespace TGApp.Plugins.Mys.Forum {
/**
* @description 特定论坛返回数据
* @since Beta v0.3.7
* @interface Response
* @extends TGApp.BBS.Response.BaseWithData
* @property {FullData} data 特定论坛数据
* @return Response
*/
interface Response extends TGApp.BBS.Response.BaseWithData {
data: FullData;
}
/**
* @description 特定论坛数据
* @since Beta v0.3.7
* @interface FullData
* @property {number} last_id 最后一条帖子 ID
* @property {boolean} is_last 是否最后一页
* @property {boolean} is_origin 是否原创
* @property {number} page 页码
* @property {unknown} databox 数据盒子
* @property {TGApp.Plugins.Mys.News.Item[]} list 帖子列表
* @return FullData
*/
interface FullData {
last_id: number;
is_last: boolean;
is_origin: boolean;
page: number;
databox: unknown;
list: TGApp.Plugins.Mys.News.Item[];
}
/**
* @description 用于渲染的咨讯卡片
* @since Beta v0.3.7
* @interface RenderCard
* @property {string} title 标题
* @property {string} cover 封面图片 URL
* @property {string} postId 帖子 ID
* @property {string} subtitle 副标题
* @property user 发帖用户
* @property {string} user.nickname 用户昵称
* @property {string} user.pendant 用户头像挂件
* @property {string} user.icon 用户头像
* @property {string} user.label 用户标签
* @property forum 版块
* @property {string} forum.name 版块名称
* @property {string} forum.icon 版块图标
* @property {RenderStatus} status 活动状态,仅活动咨讯有
* @property data 帖子统计
* @property {number} data.mark 帖子收藏数
* @property {number} data.forward 帖子转发数
* @property {number} data.like 帖子点赞数
* @property {number} data.reply 帖子回复数
* @property {number} data.view 帖子浏览数
* @return RenderCard
*/
interface RenderCard {
title: string;
cover: string;
postId: string;
subtitle: string;
user: {
nickname: string;
pendant: string;
icon: string;
label: string;
};
forum: {
name: string;
icon: string;
};
data: {
mark: number;
forward: number;
like: number;
reply: number;
view: number;
};
}
}

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Mys/types/news.d.ts
* @description Mys 插件咨讯类型定义文件
* @since Alpha v0.2.1
* @since Beta v0.3.7
*/
/**
@@ -40,7 +40,7 @@ declare namespace TGApp.Plugins.Mys.News {
/**
* @description 咨讯列表项
* @since Alpha v0.2.1
* @since Beta v0.3.7
* @interface Item
* @property {TGApp.Plugins.Mys.Post.Post} post 帖子
* @property {TGApp.Plugins.Mys.Post.Forum} forum 版块
@@ -58,11 +58,19 @@ declare namespace TGApp.Plugins.Mys.News {
* @property {number} last_modify_time 最后修改时间
* @property {string} recommend_type 推荐类型
* @property {unknown} collection 合集, 可能为 null
* @property {unknown[]} vod_list 视频列表
* @property {TGApp.Plugins.Mys.Post.Vod[]} vod_list 视频列表
* @property {boolean} is_block_on 是否屏蔽
* @property {unknown} forum_rank_info 版块排名信息,可能为 null
* @property {unknown[]} link_card_list 链接卡片列表,可能为 null
* @property {Meta} news_meta 元数据
* @property {TGApp.Plugins.Mys.Post.RecommendReason|null} recommend_reason 推荐理由,可能为 null
* @property {unknown} villa_card 别墅卡片,可能为 null
* @property {boolean} is_mentor 是否为导师
* @property {unknown} villa_room_card 别墅房间卡片,可能为 null
* @property {unknown} reply_avatar_action_info 回复头像动作信息,可能为 null
* @property {TGApp.Plugins.Mys.Post.Challenge} challenge 挑战信息,可能为 null
* @property {unknown[]} hot_reply_list 热门回复列表
* @property {unknown[]} villa_msg_image_list 别墅消息图片列表
* @returns Item
*/
interface Item {
@@ -82,11 +90,19 @@ declare namespace TGApp.Plugins.Mys.News {
last_modify_time: number;
recommend_type: string;
collection: unknown;
vod_list: unknown[];
vod_list: TGApp.Plugins.Mys.Post.Vod[];
is_block_on: boolean;
forum_rank_info: unknown;
link_card_list: unknown[];
news_meta: Meta;
recommend_reason: TGApp.Plugins.Mys.Post.RecommendReason | null;
villa_card: unknown | null;
is_mentor: boolean;
villa_room_card: unknown | null;
reply_avatar_action_info: unknown | null;
challenge: TGApp.Plugins.Mys.Post.Challenge | null;
hot_reply_list: unknown[];
villa_msg_image_list: unknown[];
}
/**

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Mys/types/post.d.ts
* @description Mys 插件帖子类型定义文件
* @since Beta v0.3.4
* @since Beta v0.3.7
*/
/**
@@ -69,16 +69,24 @@ declare namespace TGApp.Plugins.Mys.Post {
last_modify_time: number;
recommend_type: string;
collection: unknown | null;
vod_list: unknown[];
vod_list: Vod[];
is_block_on: boolean;
forum_rank_info: unknown | null;
link_card_list: unknown[];
news_meta: TGApp.Plugins.Mys.News.Meta | null;
recommend_reason: unknown | null;
villa_card: unknown | null;
is_mentor: boolean;
villa_room_card: unknown | null;
reply_avatar_action_info: unknown | null;
challenge: unknown | null;
hot_reply_list: unknown[];
villa_msg_image_list: unknown[];
}
/**
* @description 帖子信息
* @since Alpha v0.2.1
* @since Beta v0.3.7
* @interface Post
* @property {number} game_id 游戏 ID // 2 为原神
* @property {string} post_id 帖子 ID
@@ -94,6 +102,7 @@ declare namespace TGApp.Plugins.Mys.Post {
* @property {boolean} post_status.is_top 是否置顶
* @property {boolean} post_status.is_good 是否加精
* @property {boolean} post_status.is_official 是否官方
* @property {number} post_status.post_status 帖子状态
* @property {number[]} topic_ids 所属话题 ID 列表
* @property {number} view_status 浏览状态
* @property {number} max_floor 最大楼层
@@ -119,6 +128,7 @@ declare namespace TGApp.Plugins.Mys.Post {
* @property {boolean} is_showing_missing 是否显示缺失
* @property {number} block_latest_reply_time 是否屏蔽最新回复时间
* @property {number} selected_comment 是否选择评论
* @property {boolean} is_mentor 是否为导师
* @return Post
*/
interface Post {
@@ -136,6 +146,7 @@ declare namespace TGApp.Plugins.Mys.Post {
is_top: boolean;
is_good: boolean;
is_official: boolean;
post_status: number;
};
topic_ids: number[];
view_status: number;
@@ -162,6 +173,7 @@ declare namespace TGApp.Plugins.Mys.Post {
is_showing_missing: boolean;
block_latest_reply_time: number;
selected_comment: number;
is_mentor: boolean;
}
/**
@@ -210,13 +222,16 @@ declare namespace TGApp.Plugins.Mys.Post {
/**
* @description 帖子状态
* @since Alpha v0.2.1
* @since Beta v0.3.7
* @interface Stat
* @property {number} view_num 浏览数
* @property {number} reply_num 回复数
* @property {number} like_num 点赞数
* @property {number} bookmark_num 收藏数
* @property {number} forward_num 转发数
* @property post_upvote_stat 互动
* @property {number} post_upvote_stat[].upvote_type 互动类型
* @property {number} post_upvote_stat[].upvote_cnt 互动数量
* @return Stat
*/
interface Stat {
@@ -225,6 +240,11 @@ declare namespace TGApp.Plugins.Mys.Post {
like_num: number;
bookmark_num: number;
forward_num: number;
original_like_num: number;
post_upvote_stat: Array<{
upvote_type: number;
upvote_cnt: number;
}>;
}
/**
@@ -285,16 +305,81 @@ declare namespace TGApp.Plugins.Mys.Post {
}
/**
* @description 帖子内容-结构化
* @description 当用户发帖时,解析内容用这个,为 post.content 的反序列化
* @since Alpha v0.2.1
* @interface Content
* @property {string} describe 描述
* @property {string[]} images 图片 URL
* @return Content
* @description 视频信息
* @since Beta v0.3.7
* @interface Vod
* @property {string} id 视频 ID
* @property {number} duration 视频时长
* @property {string} cover 视频封面图 URL
* @property {string} resolutions[].url 视频 URL
* @property {string} resolutions[].definition 视频清晰度
* @property {number} resolutions[].width 视频宽度
* @property {number} resolutions[].height 视频高度
* @property {number} resolutions[].bitrate 视频码率
* @property {string} resolutions[].size 视频大小
* @property {string} resolutions[].format 视频格式
* @property {string} resolutions[].label 视频标签
* @property {number} view_num 观看数
* @property {number} transcoding_status 转码状态
* @property {number} review_status 审核状态
* @property {string} brief_info 视频简介
* @return Vod
*/
interface PostContent {
describe: string;
images?: string[];
interface Vod {
id: string;
duration: number;
cover: string;
resolutions: Array<{
url: string;
definition: string;
width: number;
height: number;
bitrate: number;
size: string;
format: string;
label: string;
}>;
view_num: number;
transcoding_status: number;
review_status: number;
brief_info: string;
}
/**
* @description 推荐理由
* @since Beta v0.3.7
* @interface RecommendReason
* @property {string[]} tags 标签
* @property {boolean} is_mentor_rec_block 是否为导师推荐
* @return RecommendReason
*/
interface RecommendReason {
tags: string[];
is_mentor_rec_block: boolean;
}
/**
* @description 挑战
* @since Beta v0.3.7
* @interface Challenge
* @property {string} id 挑战 ID
* @property {string} title 挑战标题
* @property {number} participant_amount 参与人数
* @property {TGApp.Plugins.Mys.User.Challenge} sponsor 发起人
* @property {TGApp.Plugins.Mys.User.Challenge[]} participants 参与人
* @property {boolean} is_sandbox 是否为沙盒
* @property {string} header_url 头像 URL
* @property {string} post_id 帖子 ID
* @return Challenge
*/
interface Challenge {
id: string;
title: string;
participant_amount: number;
sponsor: TGApp.Plugins.Mys.User.Challenge;
participants: TGApp.Plugins.Mys.User.Challenge[];
is_sandbox: boolean;
header_url: string;
post_id: string;
}
}

View File

@@ -1,13 +1,12 @@
/**
* @file plugins/Mys/types/SctPost.d.ts
* @description Mys 插件 结构化帖子类型声明文件
* @todo 完善类型
* @since Beta v0.3.4
* @since Beta v0.3.7
*/
/**
* @description 结构化帖子类型命名空间
* @since Beta v0.3.4
* @since Beta v0.3.7
* @namespace TGApp.Plugins.Mys.SctPost
* @memberof TGApp.Plugins.Mys
*/
@@ -38,25 +37,6 @@ declare namespace TGApp.Plugins.Mys.SctPost {
attributes?: never;
}
/**
* @description 帖子结构化数据-联合类型
* @since Beta v0.3.4
* @interface Common
* @return Common
*/
type Common =
| Backup
| Divider
| Image
| Link
| LinkCard
| Mention
| Text
| Video
| VillaCard
| Vod
| Empty;
/**
* @description 帖子结构化数据-其他类型
* @since Beta v0.3.4
@@ -67,267 +47,7 @@ declare namespace TGApp.Plugins.Mys.SctPost {
interface Other {
describe: string;
imgs: string[];
[key: string]: unknown;
}
/**
* @description 帖子结构化数据-折叠文本
* @since Beta v0.3.4
* @interface Backup
* @extends Base
* @property {string} insert.backup_text - 折叠文本
* @property {string} insert.fold.title - 折叠标题
* @property {string} insert.fold.content - 折叠内容
* @return Backup
*/
interface Backup extends Base {
insert: {
backup_text: string;
fold: {
title: string;
content: string;
};
};
}
/**
* @description 帖子结构化数据-分割线
* @since Beta v0.3.4
* @interface Divider
* @extends Base
* @property {string} insert.divider - 分割线
* @return Divider
*/
interface Divider extends Base {
insert: {
divider: string;
};
}
/**
* @description 帖子结构化数据-图片类型
* @since Beta v0.3.4
* @interface Image
* @extends Base
* @property {string} insert.image - 图片链接
* @property {number} attributes.width - 图片宽度
* @property {number} attributes.height - 图片高度
* @property {number} [attributes.size] - 图片大小
* @property {string} [attributes.ext] - 图片格式
* @return Image
*/
interface Image extends Base {
insert: {
image: string;
};
attributes?: {
width: number;
height: number;
size: number | undefined;
ext: string | undefined;
};
}
/**
* @description 帖子结构化数据-文本链接
* @since Beta v0.3.4
* @interface Link
* @extends Base
* @property {string} insert - 帖子内容
* @property {string} attributes.link - 链接
* @return Link
*/
interface Link extends Base {
insert: string;
attributes: {
link: string;
};
}
/**
* @description 帖子结构化数据-链接卡片
* @since Beta v0.3.4
* @interface LinkCard
* @extends Base
* @property {number} insert.link_card.link_type - 链接类型
* @property {string} insert.link_card.origin_url - 原始链接
* @property {string} insert.link_card.landing_url - 落地页链接
* @property {string} insert.link_card.cover - 封面
* @property {string} insert.link_card.title - 标题
* @property {string} insert.link_card.card_id - 卡片ID
* @property {number} insert.link_card.card_status - 卡片状态
* @property {string} insert.link_card.market_price - 市场价
* @property {string} insert.link_card.price - 价格
* @property {string} insert.link_card.button_text - 按钮文本
* @property {number} insert.link_card.landing_url_type - 落地页类型
* @return LinkCard
*/
interface LinkCard extends Base {
insert: {
link_card: {
link_type: number;
origin_url: string;
landing_url: string;
cover: string;
title: string;
card_id: string;
card_status: number;
market_price: string;
price?: string;
button_text?: string;
landing_url_type: number;
};
};
}
/**
* @description 帖子结构化数据-抽奖
* @since Beta v0.3.4
* @interface Lottery
* @extends Base
* @property {"[抽奖]"} insert.backup_text - 抽奖文本
* @property {string} insert.lottery.id - 抽奖ID
* @property {string} insert.lottery.toast - 抽奖提示
* @return Lottery
*/
interface Lottery extends Base {
insert: {
backup_text: "[抽奖]";
lottery: {
id: string;
toast: string;
};
};
}
/**
* @description 帖子结构化数据-提及用户
* @since Beta v0.3.4
* @interface Mention
* @extends Base
* @property {string} insert.mention.uid - 用户ID
* @property {string} insert.mention.nickname - 用户昵称
* @return Mention
*/
interface Mention extends Base {
insert: {
mention: {
uid: string;
nickname: string;
};
};
}
/**
* @description 帖子结构化数据-文本类型
* @since Beta v0.3.4
* @interface Text
* @extends Base
* @property {string} insert - 帖子内容
* @property {boolean} [attributes.bold] - 是否加粗
* @property {string} [attributes.color] - 文本颜色
* @property {string} [attributes.link] - 链接
* @return Text
*/
interface Text extends Base {
insert: string;
attributes?: {
bold?: boolean;
color?: string;
align?: string;
};
}
/**
* @description 帖子结构化数据-视频类型-站外视频
* @since Beta v0.3.4
* @interface Video
* @extends Base
* @property {string} insert.video - 视频链接
* @return Video
*/
interface Video extends Base {
insert: {
video: string;
};
}
/**
* @description 帖子结构化数据-大别野卡片
* @since Beta v0.3.4
* @interface VillaCard
* @extends Base
* @property {string} insert.villa_card.villa_id - 别墅ID
* @property {string} insert.villa_card.villa_name - 别墅名称
* @property {string} insert.villa_card.villa_avatar_url - 别墅头像
* @property {string} insert.villa_card.villa_cover - 别墅封面
* @property {string} insert.villa_card.owner_uid - 别墅主人ID
* @property {string} insert.villa_card.owner_nickname - 别墅主人昵称
* @property {string} insert.villa_card.owner_avatar_url - 别墅主人头像
* @property {string} insert.villa_card.villa_introduce - 别墅介绍
* @property {string[]} insert.villa_card.tag_list - 别墅标签
* @property {string} insert.villa_card.villa_member_num - 别墅成员数量
* @return VillaCard
*/
interface VillaCard extends Base {
insert: {
villa_card: {
villa_id: string;
villa_name: string;
villa_avatar_url: string;
villa_cover: string;
owner_uid: string;
owner_nickname: string;
owner_avatar_url: string;
villa_introduce: string;
tag_list: string[];
villa_member_num: string;
};
};
}
/**
* @description 帖子结构化数据-视频类型-站内视频
* @since Beta v0.3.4
* @interface Vod
* @extends Base
* @property {number} insert.vod.id - 视频ID
* @property {number} insert.vod.duration - 视频时长
* @property {string} insert.vod.cover - 视频封面
* @property {Array} insert.vod.resolutions - 视频分辨率
* @property {string} insert.vod.resolutions.url - 视频链接
* @property {string} insert.vod.resolutions.definition - 视频清晰度
* @property {number} insert.vod.resolutions.height - 视频高度
* @property {number} insert.vod.resolutions.width - 视频宽度
* @property {number} insert.vod.resolutions.bitrate - 视频码率
* @property {number} insert.vod.resolutions.size - 视频大小
* @property {string} insert.vod.resolutions.format - 视频格式
* @property {string} insert.vod.resolutions.label - 视频标签
* @property {number} insert.vod.view_num - 观看次数
* @property {number} insert.vod.transcode_status - 转码状态
* @property {number} insert.vod.review_status - 审核状态
* @return Vod
*/
interface Vod extends Base {
insert: {
vod: {
id: number;
duration: number;
cover: string;
resolutions: Array<{
url: string;
definition: string;
height: number;
width: number;
bitrate: number;
size: number;
format: string;
label: string;
}>;
view_num: number;
transcode_status: number;
review_status: number;
};
};
}
}

View File

@@ -1,12 +1,12 @@
/**
* @file plugins/Mys/types/user.ts
* @description Mys 插件用户类型定义文件
* @since Alpha v0.2.2
* @since Beta v0.3.7
*/
/**
* @description Mys 插件用户类型
* @since Alpha v0.2.2
* @since Beta v0.3.7
* @namespace TGApp.Plugins.Mys.User
* @memberof TGApp.Plugins.Mys
*/
@@ -226,7 +226,7 @@ declare namespace TGApp.Plugins.Mys.User {
/**
* @description post中的用户信息
* @since Alpha v0.2.1
* @since Beta v0.3.7
* @interface Post
* @property {string} uid 用户 ID
* @property {string} nickname 用户昵称
@@ -238,9 +238,11 @@ declare namespace TGApp.Plugins.Mys.User {
* @property {number} level_exp.level 用户等级
* @property {number} level_exp.exp 用户经验
* @property {boolean} is_following 是否关注
* @property {boolean} is_follower 是否被关注
* @property {boolean} is_followed 是否被关注
* @property {string} avatar_url 用户头像链接
* @property {string} pendant 用户挂件 URL可能为 ""
* @property {boolean} is_creator 是否是创作者
* @property {AvatarExt} avatar_ext 用户头像扩展信息
* @return Post
*/
interface Post {
@@ -255,21 +257,57 @@ declare namespace TGApp.Plugins.Mys.User {
exp: number;
};
is_following: boolean;
is_follower: boolean;
is_followed: boolean;
avatar_url: string;
pendant: string;
is_creator: boolean;
avatar_ext: AvatarExt;
}
/**
* @description 用户操作
* @since Alpha v0.2.1
* @since Beta v0.3.7
* @interface SelfOperation
* @property {number} attitude 操作类型
* @property {boolean} is_collected 是否收藏
* @property {number} upvote_type 互动类型
* @returns {SelfOperation}
*/
interface SelfOperation {
attitude: number;
is_collected: boolean;
upvote_type: number;
}
/**
* @description 用户头像扩展信息
* @since Beta v0.3.7
* @interface AvatarExt
* @property {number} avatar_type 头像类型
* @property {string} avatar_assets_id 头像资源 ID
* @property {unknown[]} resources 资源
* @property {unknown[]} hd_resources 高清资源
* @return AvatarExt
*/
interface AvatarExt {
avatar_type: number;
avatar_assets_id: string;
resources: unknown[];
hd_resources: unknown[];
}
/**
* @description post.challenge 用户挑战信息
* @since Beta v0.3.7
* @interface Challenge
* @property {string} uid 用户 ID
* @property {string} nickname 用户昵称
* @property {string} avatar_url 用户头像链接
* @return Challenge
*/
interface Challenge {
uid: string;
nickname: string;
avatar_url: string;
}
}

View File

@@ -0,0 +1,70 @@
/**
* @file plugins/Mys/utils/getPostsCard.ts
* @description Mys 插件帖子渲染
* @since Beta v0.3.7
*/
const defaultCover = "/source/UI/defaultCover.webp";
/**
* @description 解析单个帖子
* @since Beta v0.3.7
* @param {TGApp.Plugins.Mys.News.Item} post 帖子
* @returns {TGApp.Plugins.Mys.Forum.RenderCard} 渲染用帖子
*/
function getPostCard(post: TGApp.Plugins.Mys.News.Item): TGApp.Plugins.Mys.Forum.RenderCard {
const postCover = post.cover?.url || post.post.cover || post.post.images[0] || defaultCover;
const userLabel = getUserLabel(post);
return {
title: post.post.subject,
cover: postCover,
postId: post.post.post_id,
subtitle: post.post.post_id,
user: {
nickname: post.user.nickname,
pendant: post.user.pendant,
icon: post.user.avatar_url,
label: userLabel,
},
forum: {
name: post.forum.name,
icon: post.forum.icon,
},
data: {
mark: post.stat.bookmark_num,
forward: post.stat.forward_num,
like: post.stat.like_num,
reply: post.stat.reply_num,
view: post.stat.view_num,
},
};
}
/**
* @description 获取用户描述
* @since Beta v0.3.7
* @param {TGApp.Plugins.Mys.News.Item} post 帖子
* @returns {string} 描述
*/
function getUserLabel(post: TGApp.Plugins.Mys.News.Item): string {
if (post.user.certification.label !== "") {
return post.user.certification.label;
}
return post.user.introduce;
}
/**
* @description 获取渲染用帖子数据
* @since Beta v0.3.7
* @param {TGApp.Plugins.Mys.Forum.FullData} posts
* @returns {TGApp.Plugins.Mys.Forum.RenderCard[]}
*/
export function getPostsCard(
posts: TGApp.Plugins.Mys.Forum.FullData,
): TGApp.Plugins.Mys.Forum.RenderCard[] {
const postsCard: TGApp.Plugins.Mys.Forum.RenderCard[] = [];
posts.list.map((post) => {
return postsCard.push(getPostCard(post));
});
return postsCard;
}

View File

@@ -1,536 +0,0 @@
/**
* @file plugins Mys utils parsePost.ts
* @description 用于解析Mys数据的工具
* @since Beta v0.3.5
*/
import * as colorConvert from "color-convert";
import type { KEYWORD } from "color-convert/conversions";
import { score } from "wcag-color";
/**
* @description 给定两个16进制颜色值确认两者是否相近
* @since Beta v0.3.4
* @param {string} colorBg 背景颜色
* @param {string} colorFg 前景颜色
* @returns {boolean} 是否相近
*/
function isColorSimilar(colorBg: string, colorFg: string): boolean {
let hexBg, hexFg;
if (colorBg.startsWith("#")) hexBg = colorBg;
else hexBg = colorConvert.keyword.hex(<KEYWORD>colorBg);
if (colorFg.startsWith("#")) hexFg = colorFg;
else hexFg = colorConvert.keyword.hex(<KEYWORD>colorFg);
const contrast = score(hexFg, hexBg);
return contrast === "Fail";
}
/**
* @description 检测链接是否是米游社帖子
* @since Alpha v0.1.2
* @param {string} url 链接
* @returns {boolean} 是否是米游社帖子
*/
function isMysPost(url: string): boolean {
const regBBS = /^https:\/\/bbs\.mihoyo\.com\/\w+\/article\/\d+$/;
const regMys = /^https:\/\/www\.miyoushe\.com\/\w+\/article\/\d+$/;
return regBBS.test(url) || regMys.test(url);
}
/**
* @description 根据 url 获取帖子 id
* @since Beta v0.3.0
* @param {string} url 链接
* @returns {string} 帖子 id
*/
function getPostId(url: string): string {
const postId: string | undefined = url.split("/").pop();
if (postId === undefined) {
throw new Error(`Can't get postId from ${url}`);
}
return postId;
}
/**
* @description 获取视频时长
* @since Beta v0.3.3
* @param {number} duration 视频时长
* @returns {string} 视频时长
*/
function getVodTime(duration: number): string {
const secTotal = Math.floor(duration / 1000);
const seconds = secTotal % 60;
const minutes = Math.floor(secTotal / 60) % 60;
const hours = Math.floor(secTotal / 3600);
let result = "";
if (hours > 0) {
result += `${hours.toString().padStart(2, "0")}:`;
}
result += `${minutes.toString().padStart(2, "0")}:`;
result += `${seconds.toString().padStart(2, "0")}`;
return result;
}
/**
* @description 解析用户帖子,将其转换为 StructContent
* @since Beta v0.3.4
* @see PostContent
* @param {string} content 帖子内容
* @returns {string} 解析后的内容
*/
function parseContent(content: string): string {
const data: TGApp.Plugins.Mys.SctPost.Other = JSON.parse(content);
const result: TGApp.Plugins.Mys.SctPost.Common[] = [];
const keys = Object.keys(data);
keys.forEach((key) => {
switch (key) {
case "describe":
result.push({
insert: data.describe,
});
break;
case "imgs":
data.imgs.forEach((item) => {
result.push({
insert: {
image: item,
},
});
});
break;
default:
console.warn(`[MysPostParser] Unknown key: ${key}`);
result.push({
insert: JSON.stringify(data[key]),
});
break;
}
});
return JSON.stringify(result);
}
/**
* @description 解析Mys数据
* @since Alpha v0.1.2
* @param {TGApp.Plugins.Mys.Post.FullData} post Mys数据
* @description 为了安全考虑,不会解析所有的属性,只会解析几个常用的属性
* @returns {string} 解析后的HTML可作为 v-html 使用
*/
function parsePost(post: TGApp.Plugins.Mys.Post.FullData): string {
const postContent = post.post.content;
let parserData;
if (postContent.startsWith("<")) {
parserData = post.post.structured_content;
} else {
try {
parserData = parseContent(post.post.content);
} catch (error) {
parserData = post.post.structured_content;
}
}
const jsonData: TGApp.Plugins.Mys.SctPost.Common[] = JSON.parse(parserData);
const doc = document.createElement("div");
jsonData.forEach((item: any) => {
const parsed = transferParser(item);
doc.appendChild(parsed);
});
return doc.innerHTML;
}
/**
* @description 解析中转
* @since Beta v0.3.3
* @param {TGApp.Plugins.Mys.SctPost.Common} data Mys数据
* @returns {HTMLDivElement | HTMLSpanElement} 解析后的中转
*/
function transferParser(data: TGApp.Plugins.Mys.SctPost.Common): HTMLDivElement | HTMLSpanElement {
if (typeof data.insert === "string") {
return parseText(<TGApp.Plugins.Mys.SctPost.Text | TGApp.Plugins.Mys.SctPost.Link>data);
} else if ("image" in data.insert) {
return parseImage(<TGApp.Plugins.Mys.SctPost.Image>data);
} else if ("vod" in data.insert) {
return parseVideo(<TGApp.Plugins.Mys.SctPost.Vod>data);
} else if ("video" in data.insert) {
return parseVideo(<TGApp.Plugins.Mys.SctPost.Video>data);
} else if ("backup_text" in data.insert) {
return parseBackup(<TGApp.Plugins.Mys.SctPost.Backup>data);
} else if ("link_card" in data.insert) {
return parseLinkCard(<TGApp.Plugins.Mys.SctPost.LinkCard>data);
} else if ("divider" in data.insert) {
return parseDivider(<TGApp.Plugins.Mys.SctPost.Divider>data);
} else if ("mention" in data.insert) {
return parseMention(<TGApp.Plugins.Mys.SctPost.Mention>data);
} else if ("villa_card" in data.insert) {
return parseVillaCard(<TGApp.Plugins.Mys.SctPost.VillaCard>data);
}
return parseUnknown(<TGApp.Plugins.Mys.SctPost.Empty>data);
}
/**
* @description 解析未知数据
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.Empty} data Mys数据
* @returns {HTMLDivElement} 解析后的未知数据
*/
function parseUnknown(data: TGApp.Plugins.Mys.SctPost.Empty): HTMLDivElement {
const div = document.createElement("div");
div.classList.add("mys-post-unknown");
const code = document.createElement("code");
code.innerText = JSON.stringify(data, null, 2);
code.classList.add("mys-post-unknown-code");
div.appendChild(code);
return div;
}
/**
* @description 解析文本
* @since Beta v0.3.5
* @param {TGApp.Plugins.Mys.SctPost.Text |TGApp.Plugins.Mys.SctPost.Link} data Mys数据
* @returns {HTMLSpanElement} 解析后的文本
*/
function parseText(
data: TGApp.Plugins.Mys.SctPost.Text | TGApp.Plugins.Mys.SctPost.Link,
): HTMLSpanElement {
if (data.attributes && "link" in data.attributes) {
return LinkTextParser(<TGApp.Plugins.Mys.SctPost.Link>data);
}
const text = document.createElement("span");
if (data.attributes) {
if (data.attributes.bold) text.style.fontWeight = "bold";
if (data.attributes.color) {
let colorGet = data.attributes.color;
if (isColorSimilar("#000000", colorGet)) {
colorGet = "var(--app-page-content)";
}
text.style.color = colorGet;
}
}
if (data.insert.startsWith("_(") && data.insert.endsWith(")")) {
return emojiParser(<TGApp.Plugins.Mys.SctPost.Text>data);
}
text.classList.add("mys-post-span");
text.innerText = data.insert;
return text;
}
/**
* @description 解析链接
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.Link} data Mys数据
* @returns {HTMLSpanElement} 解析后的链接
*/
function LinkTextParser(data: TGApp.Plugins.Mys.SctPost.Link): HTMLSpanElement {
const icon = document.createElement("i");
icon.classList.add("mdi", "mdi-link-variant");
const link = document.createElement("a");
const linkUrl = data.attributes.link;
link.classList.add("mys-post-link");
if (isMysPost(linkUrl)) {
const postId = getPostId(linkUrl);
link.href = `/post_detail/${postId}`;
link.target = "_self";
} else {
link.href = linkUrl;
link.target = "view_window";
}
link.innerText = data.insert;
link.prepend(icon);
return link;
}
/**
* @description 解析分割线
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.Divider} data Mys数据
* @returns {HTMLDivElement} 解析后的分割线
*/
function parseDivider(data: TGApp.Plugins.Mys.SctPost.Divider): HTMLDivElement {
const div = document.createElement("div");
div.classList.add("mys-post-divider");
const img = document.createElement("img");
const dividerList = ["line_1", "line_2", "line_3", "line_4"];
if (!dividerList.includes(data.insert.divider)) {
console.error("Unknown divider type", data);
return parseUnknown(<TGApp.Plugins.Mys.SctPost.Empty>data);
}
img.src = `/source/post/divider_${data.insert.divider}.webp`;
div.appendChild(img);
return div;
}
/**
* @description 解析图片
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.Image} data Mys数据
* @returns {HTMLDivElement} 解析后的图片
*/
function parseImage(data: TGApp.Plugins.Mys.SctPost.Image): HTMLDivElement {
const div = document.createElement("div");
const img = document.createElement("img");
img.src = data.insert.image;
img.classList.add("mys-post-img");
div.appendChild(img);
div.classList.add("mys-post-div");
return div;
}
/**
* @description 解析视频
* @since Beta v0.3.3
* @todo 分开解析 Vod 和 Video
* @param {TGApp.Plugins.Mys.SctPost.Vod|TGApp.Plugins.Mys.SctPost.Video} data Mys数据
* @returns {HTMLDivElement} 解析后的视频
*/
function parseVideo(
data: TGApp.Plugins.Mys.SctPost.Vod | TGApp.Plugins.Mys.SctPost.Video,
): HTMLDivElement {
const div = document.createElement("div");
div.classList.add("mys-post-div");
if ("vod" in data.insert) {
const video = document.createElement("video");
video.classList.add("mys-post-vod");
const resolution = data.insert.vod.resolutions.reduce((prev: any, curr: any) => {
if (prev.size > curr.size) return prev;
return curr;
});
video.poster = data.insert.vod.cover;
video.controls = true;
const source = document.createElement("source");
source.src = resolution.url;
source.type = resolution.format === ".mp4" ? "video/mp4" : "video/webm";
video.appendChild(source);
div.appendChild(video);
const coverDiv = document.createElement("div");
const cover = document.createElement("img");
cover.classList.add("mys-post-vod-cover");
cover.src = video.poster;
coverDiv.appendChild(cover);
const playIcon = document.createElement("img");
playIcon.classList.add("mys-post-vod-icon");
playIcon.src = "/source/UI/video_play.svg";
coverDiv.appendChild(playIcon);
const playTime = document.createElement("div");
playTime.classList.add("mys-post-vod-time");
playTime.innerText = getVodTime(data.insert.vod.duration);
coverDiv.appendChild(playTime);
coverDiv.classList.add("mys-post-vod-cover-div");
div.appendChild(coverDiv);
} else {
const video = document.createElement("iframe");
video.classList.add("mys-post-iframe");
video.src = data.insert.video;
video.allowFullscreen = true;
video.sandbox.add("allow-top-navigation", "allow-same-origin", "allow-forms", "allow-scripts");
div.appendChild(video);
}
return div;
}
/**
* @description 解析折叠内容
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.Backup} data Mys数据
* @returns {HTMLDivElement} 解析后的折叠内容
*/
function parseBackup(
data: TGApp.Plugins.Mys.SctPost.Backup | TGApp.Plugins.Mys.SctPost.Lottery,
): HTMLDivElement {
if ("lottery" in data.insert) {
return LotteryParser(<TGApp.Plugins.Mys.SctPost.Lottery>data);
}
const titleJson: TGApp.Plugins.Mys.SctPost.Base[] = JSON.parse(data.insert.fold.title);
const contentJson: TGApp.Plugins.Mys.SctPost.Base[] = JSON.parse(data.insert.fold.content);
const div = document.createElement("div");
div.classList.add("mys-post-div");
const details = document.createElement("details");
details.classList.add("mys-post-details");
const title = document.createElement("summary");
titleJson.forEach((item) => {
const parsed = transferParser(item);
title.appendChild(parsed);
});
const content = document.createElement("div");
contentJson.forEach((item) => {
const parsed = transferParser(item);
content.appendChild(parsed);
});
details.appendChild(title);
details.appendChild(content);
div.appendChild(details);
return div;
}
/**
* @description 解析抽奖
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.Lottery} data Mys数据
* @returns {HTMLDivElement} 解析后的抽奖
*/
function LotteryParser(data: TGApp.Plugins.Mys.SctPost.Lottery): HTMLDivElement {
const div = document.createElement("div");
const icon = document.createElement("i");
icon.classList.add("mdi", "mdi-gift");
const title = document.createElement("a");
title.classList.add("mys-post-link");
title.href = `/lottery/${data.insert.lottery.id}`;
title.innerText = data.insert.lottery.toast;
title.prepend(icon);
div.appendChild(title);
return div;
}
/**
* @description 解析链接卡片
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.LinkCard} data Mys数据
* @returns {HTMLDivElement} 解析后的链接卡片
*/
function parseLinkCard(data: TGApp.Plugins.Mys.SctPost.LinkCard): HTMLDivElement {
const div = document.createElement("div");
const cover = document.createElement("div");
cover.classList.add("mys-post-link-card-cover");
const img = document.createElement("img");
img.src = data.insert.link_card.cover;
cover.appendChild(img);
div.appendChild(cover);
const content = document.createElement("div");
content.classList.add("mys-post-link-card-content");
const title = document.createElement("div");
title.classList.add("mys-post-link-card-title");
title.innerHTML = data.insert.link_card.title;
content.appendChild(title);
if (data.insert.link_card.price) {
const price = document.createElement("div");
price.classList.add("mys-post-link-card-price");
price.innerHTML = data.insert.link_card.price;
content.appendChild(price);
}
const button = document.createElement("a");
button.classList.add("mys-post-link-card-btn");
button.innerHTML = (data.insert.link_card.button_text ?? "详情") + " >";
const linkUrl = data.insert.link_card.origin_url;
if (isMysPost(linkUrl)) {
const postId = getPostId(linkUrl);
button.href = `/post_detail/${postId}`;
button.target = "_self";
} else {
button.href = linkUrl;
button.target = "view_window";
}
content.appendChild(button);
div.appendChild(content);
div.classList.add("mys-post-link-card");
return div;
}
/**
* @description 解析 Mention
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.Mention} data Mys数据
* @returns {HTMLAnchorElement} 解析后的 Mention
*/
function parseMention(data: TGApp.Plugins.Mys.SctPost.Mention): HTMLAnchorElement {
const icon = document.createElement("i");
icon.classList.add("mdi", "mdi-account-circle-outline");
const link = document.createElement("a");
link.classList.add("mys-post-link");
link.href = `https://www.miyoushe.com/ys/accountCenter/postList?id=${data.insert.mention.uid}`;
link.target = "_blank";
link.innerText = data.insert.mention.nickname;
link.prepend(icon);
return link;
}
/**
* @description 解析 Emoji
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.Text} data Mys数据
* @returns {HTMLSpanElement} 解析后的 Emoji
*/
function emojiParser(data: TGApp.Plugins.Mys.SctPost.Text): HTMLImageElement {
const emojis = localStorage.getItem("emojis");
if (!emojis) {
throw new Error("[EmojiParser] emojis is not defined");
}
const emojiList: Record<string, string> = JSON.parse(emojis);
const emojiName = data.insert.slice(2, -1);
const emoji = emojiList[emojiName];
if (!emoji) {
throw new Error(`[EmojiParser] emoji is not defined: ${emojiName}`);
}
const img = document.createElement("img");
img.classList.add("mys-post-emoji");
img.src = emoji;
img.alt = emojiName;
img.title = emojiName;
return img;
}
/**
* @description 解析大别野房间的卡片
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.VillaCard} data Mys数据
* @returns {HTMLDivElement} 解析后的大别野房间的卡片
*/
function parseVillaCard(data: TGApp.Plugins.Mys.SctPost.VillaCard): HTMLDivElement {
const div = document.createElement("div");
div.classList.add("mys-post-div");
const villaCard = document.createElement("div");
villaCard.classList.add("mys-post-villa-card");
const bgImg = document.createElement("img");
bgImg.classList.add("mys-post-villa-card-bg");
bgImg.src = data.insert.villa_card.villa_cover;
villaCard.appendChild(bgImg);
const bgBefore = document.createElement("div");
bgBefore.classList.add("mys-post-villa-card-bg-before");
villaCard.appendChild(bgBefore);
const flexDiv = document.createElement("div");
flexDiv.classList.add("mys-post-villa-card-flex");
const topDiv = document.createElement("div");
topDiv.classList.add("mys-post-villa-card-top");
const topIcon = document.createElement("img");
topIcon.classList.add("mys-post-villa-card-icon");
topIcon.src = data.insert.villa_card.villa_avatar_url;
const topContent = document.createElement("div");
topContent.classList.add("mys-post-villa-card-content");
const topTitle = document.createElement("div");
topTitle.classList.add("mys-post-villa-card-title");
topTitle.innerText = data.insert.villa_card.villa_name;
topContent.appendChild(topTitle);
const topDesc = document.createElement("div");
topDesc.classList.add("mys-post-villa-card-desc");
const topDescIcon = document.createElement("img");
topDescIcon.src = data.insert.villa_card.owner_avatar_url;
topDescIcon.classList.add("mys-post-villa-card-desc-icon");
const topDescText = document.createElement("span");
topDescText.classList.add("mys-post-villa-card-desc-text");
topDescText.innerText = `${data.insert.villa_card.owner_nickname} 创建`;
topDesc.appendChild(topDescIcon);
topDesc.appendChild(topDescText);
topContent.appendChild(topDesc);
topDiv.appendChild(topIcon);
topDiv.appendChild(topContent);
flexDiv.appendChild(topDiv);
const midDiv = document.createElement("div");
midDiv.classList.add("mys-post-villa-card-mid");
const numberDiv = document.createElement("div");
numberDiv.classList.add("mys-post-villa-card-tag");
numberDiv.innerText = `${data.insert.villa_card.villa_member_num}人在聊`;
midDiv.appendChild(numberDiv);
data.insert.villa_card.tag_list.forEach((tag) => {
const tagDiv = document.createElement("div");
tagDiv.classList.add("mys-post-villa-card-tag");
tagDiv.innerText = tag;
midDiv.appendChild(tagDiv);
});
flexDiv.appendChild(midDiv);
const bottomDiv = document.createElement("div");
bottomDiv.classList.add("mys-post-villa-card-bottom");
bottomDiv.innerText = data.insert.villa_card.villa_introduce;
flexDiv.appendChild(bottomDiv);
villaCard.appendChild(flexDiv);
div.appendChild(villaCard);
return div;
}
export default parsePost;

View File

@@ -1,7 +1,7 @@
/**
* @file router modules main.ts
* @file router/modules/main.ts
* @description 主路由模块
* @since Beta v0.3.3
* @since Beta v0.3.7
*/
const mainRoutes = [
@@ -20,6 +20,11 @@ const mainRoutes = [
name: "咨讯",
component: async () => await import("../../pages/common/News.vue"),
},
{
path: "/posts",
name: "酒馆",
component: async () => await import("../../pages/common/Posts.vue"),
},
{
path: "/achievements/:app?",
name: "成就",

267
src/types/BBS/Navigator.d.ts vendored Normal file
View File

@@ -0,0 +1,267 @@
/**
* @file types/BBS/Navigator.d.ts
* @description BBS 导航相关类型定义文件
* @since Beta v0.3.7
*/
/**
* @description 导航相关类型定义
* @since Beta v0.3.7
* @namespace TGApp.BBS.Navigator
* @memberof TGApp.BBS
*/
declare namespace TGApp.BBS.Navigator {
/**
* @description 导航列表返回响应类型
* @interface HomeResponse
* @since Beta v0.3.7
* @extends TGApp.BBS.Response.BaseWithData
* @property {HomeData} data - 导航列表数据
* @return HomeResponse
*/
interface HomeResponse extends TGApp.BBS.Response.BaseWithData {
data: HomeData;
}
/**
* @description 导航列表数据
* @interface HomeData
* @since Beta v0.3.7
* @property {Navigator[]} navigator - 导航列表
* @property {Disscussion} discussion - 讨论区
* @property {Background} background - 背景
* @property {Official} official - 官方
* @property {Carousels} carousels - 轮播图
* @property {HotTopic[]} hot_topics - 热门话题
* @property {GameReception[]} game_receptions - 游戏接待
* @property {unknown[]} posts - 帖子
* @property {unknown[]} lives - 直播
* @property {unknown} recommend_villa - 推荐别墅
* @property {unknown[]} image_post_card - 图片帖子卡片
* @return HomeData
*/
interface HomeData {
navigator: Navigator[];
discussion: Disscussion;
background: Background;
official: Official;
carousels: Carousels;
hot_topics: HotTopic[];
game_receptions: GameReception[];
posts: unknown[];
lives: unknown[];
recommend_villa: unknown;
image_post_card: unknown[];
}
/**
* @description 导航列表
* @interface Navigator
* @since Beta v0.3.7
* @property {number} id - 导航 id
* @property {string} name - 导航名称
* @property {string} icon - 导航图标
* @property {string} app_path - 导航路径
* @property {number} reddot_online_item - 红点在线项目
* @return Navigator
*/
interface Navigator {
id: number;
name: string;
icon: string;
app_path: string;
reddot_online_item: number;
}
/**
* @description 讨论区
* @interface Disscussion
* @since Beta v0.3.7
* @property {string} icon - 讨论区图标
* @property {string} title - 讨论区标题
* @property {string} prompt - 讨论区提示
* @return Disscussion
*/
interface Disscussion {
icon: string;
title: string;
prompt: string;
}
/**
* @description 背景
* @interface Background
* @since Beta v0.3.7
* @property {string} image - 背景图片
* @property {string} color - 背景颜色
* @return Background
*/
interface Background {
image: string;
color: string;
}
/**
* @description 官方
* @interface Official
* @since Beta v0.3.7
* @property {number} position - 官方位置
* @property {number} forum_id - 官方论坛 id
* @property {string} data[].post_id - 官方帖子 id
* @property {string} data[].title - 官方帖子标题
* @property {string} data[].date - 官方帖子日期(时间戳,单位:秒)
* @property {string} data[].label - 官方帖子标签
* @property {boolean} data[].is_top - 官方帖子是否置顶
* @property {number} data[].view_type - 官方帖子视图类型
* @property {string} data[].image_url - 官方帖子图片 url
* @return Official
*/
interface Official {
position: number;
forum_id: number;
data: Array<{
post_id: string;
title: string;
date: string;
label: string;
is_top: boolean;
view_type: number;
image_url: string;
}>;
}
/**
* @description 轮播图
* @interface Carousels
* @since Beta v0.3.7
* @property {number} position - 轮播图位置
* @property {string} data[].cover - 轮播图封面
* @property {string} data[].app_path - 轮播图路径
* @return Carousels
*/
interface Carousels {
position: number;
data: Array<{
cover: string;
app_path: string;
}>;
}
/**
* @description 热门话题
* @interface HotTopic
* @since Beta v0.3.7
* @property {number} position - 热门话题位置
* @property {number} data[].topic_id - 热门话题 id
* @property {string} data[].image - 热门话题图片
* @property {string} data[].topic_name - 热门话题名称
* @property {string} data[].post_name - 热门话题帖子名称
* @property {number} data[].count.view - 热门话题浏览数
* @property {number} data[].count.discuss - 热门话题讨论数
* @return HotTopic
*/
interface HotTopic {
position: number;
data: Array<{
topic_id: number;
image: string;
topic_name: string;
post_name: string;
count: {
view: number;
discuss: number;
};
}>;
}
/**
* @description 游戏接待
* @interface GameReception
* @since Beta v0.3.7
* @property {number} position - 游戏接待位置
* @property {number} data.config.id - 游戏接待 id
* @property {number} data.config.game_id - 游戏接待游戏 id
* @property {string} data.config.name - 游戏接待名称
* @property {string} data.config.description - 游戏接待描述
* @property {string} data.config.icon - 游戏接待图标
* @property {string} data.config.status - 游戏接待状态
* @property {number} data.config.rules.game_level - 游戏接待规则游戏等级
* @property {number} data.config.rules.community_level - 游戏接待规则社区等级
* @property {number} data.config.questionnaire.questionnaire_type - 游戏接待问卷类型
* @property {string} data.config.questionnaire.questionnaire_url - 游戏接待问卷 url
* @property {number} data.config.questionnaire.questionnaire_status - 游戏接待问卷状态
* @property {string} data.config.pkg.android_url - 游戏接待安卓包 url
* @property {string} data.config.pkg.pkg_name - 游戏接待安卓包名称
* @property {string} data.config.pkg.pkg_version - 游戏接待安卓包版本
* @property {string} data.config.pkg.ios_url - 游戏接待 ios 包 url
* @property {string} data.config.pkg.pkg_length - 游戏接待 ios 包大小
* @property {string} data.config.pkg.pkg_md5 - 游戏接待 ios 包 md5
* @property {string} data.config.pkg.pkg_version_code - 游戏接待 ios 包版本
* @property {string} data.config.pkg.ios_version - 游戏接待 ios 版本
* @property {string} data.config.pkg.new_filename - 游戏接待 ios 包新文件名
* @property {string} data.config.pkg.filename - 游戏接待 ios 包文件名
* @property {string} data.config.pkg.pkg_version_name - 游戏接待 ios 包版本名称
* @property {number} data.config.detail_servlet.type - 游戏接待详情类型
* @property {unknown} data.config.detail_servlet.normal_servlet - 游戏接待详情普通类型
* @property {unknown} data.config.detail_servlet.customize_detail - 游戏接待详情自定义类型
* @property {boolean} data.config.pre_register_count.show_count - 游戏接待预注册是否显示数量
* @property {string} data.config.pre_register_count.count - 游戏接待预注册数量
* @property {boolean} data.config.is_top - 游戏接待是否置顶
* @property {string} data.config.last_update_time - 游戏接待最后更新时间
* @property {number} data.config.app_store_switch - 游戏接待 app store 开关
* @property {number} data.config.download_switch - 游戏接待下载开关
* @property {string} data.config.developer - 游戏接待开发商
* @property {unknown} data.user_status - 游戏接待用户状态
* @return GameReception
*/
interface GameReception {
position: number;
data: {
config: {
id: number;
game_id: number;
name: string;
description: string;
icon: string;
status: string;
rules: {
game_level: number;
community_level: number;
};
questionnaire: {
questionnaire_type: number;
questionnaire_url: string;
questionnaire_status: number;
};
pkg: {
android_url: string;
pkg_name: string;
pkg_version: string;
ios_url: string;
pkg_length: string;
pkg_md5: string;
pkg_version_code: string;
ios_version: string;
new_filename: string;
filename: string;
pkg_version_name: string;
};
detail_servlet: {
type: number;
normal_servlet: unknown;
customize_detail: unknown;
};
pre_register_count: {
show_count: boolean;
count: string;
};
is_top: boolean;
last_update_time: string;
app_store_switch: number;
download_switch: number;
developer: string;
};
user_status: unknown;
};
}
}

View File

@@ -1,7 +1,7 @@
/**
* @file utils/TGClient.ts
* @desc 负责米游社客户端的 callback 处理
* @since Beta v0.3.6
* @since Beta v0.3.7
*/
import { event, invoke } from "@tauri-apps/api";
@@ -29,7 +29,7 @@ interface InvokeArg {
/**
* @class TGClient
* @since Beta v0.3.4
* @since Beta v0.3.7
* @description 米游社客户端
*/
class TGClient {
@@ -87,6 +87,22 @@ class TGClient {
}
}
/**
* @func loadJSBridge
* @since Beta v0.3.7
* @desc 加载 JSBridge
* @returns {void} - 无返回值
*/
async loadJSBridge(): Promise<void> {
const executeJS = `javascript:(function(){
window.MiHoYoJSInterface = {
postMessage: function(arg) { window.__TAURI__.event.emit('post_mhy_client', arg) },
closePage: function() { this.postMessage('{"method":"closePage"}') },
};
})();`;
await invoke("execute_js", { label: "mhy_client", js: executeJS });
}
/**
* @func hideSideBar
* @since Beta v0.3.5
@@ -107,6 +123,22 @@ class TGClient {
await invoke("execute_js", { label: "mhy_client", js: executeJS });
}
/**
* @func hideOverlay
* @since Beta v0.3.7
* @desc 隐藏遮罩
* @returns {void}
*/
async hideOverlay(): Promise<void> {
const executeJS = `javascript:(function(){
if (document.getElementById('mihoyo_landscape') !== null) {
let box = document.getElementById('mihoyo_landscape');
box.remove();
}
})();`;
await invoke("execute_js", { label: "mhy_client", js: executeJS });
}
/**
* @func getUrl
* @since Beta v0.3.6
@@ -135,7 +167,7 @@ class TGClient {
/**
* @func open
* @since Beta v0.3.6
* @since Beta v0.3.7
* @desc 打开米游社客户端
* @param {string} func - 方法名
* @param {string} url - url
@@ -146,6 +178,7 @@ class TGClient {
try {
await this.window.close();
} catch (e) {
console.error(e);
await invoke<InvokeArg>("create_mhy_client", {
func: "default",
url: "https://api-static.mihoyo.com/",
@@ -153,21 +186,18 @@ class TGClient {
await this.open(func, url);
}
}
if (url === undefined) {
url = this.getUrl(func);
this.route = [url];
} else if (func !== "closePage") {
this.route.push(url);
}
if (url === undefined) url = this.getUrl(func);
this.route = [url];
console.log(`[open] ${url}`);
await invoke<InvokeArg>("create_mhy_client", { func, url });
this.window = WebviewWindow.getByLabel("mhy_client");
await this.window?.show();
await this.window?.setFocus();
}
/**
* @func handleCallback
* @since Beta v0.3.5
* @since Beta v0.3.7
* @desc 处理米游社客户端的 callback
* @param {Event<string>} arg - 事件参数
* @returns {any} - 返回值
@@ -175,11 +205,15 @@ class TGClient {
async handleCallback(arg: Event<string>): Promise<any> {
console.log(`[${arg.windowLabel}] ${arg.payload}`);
await this.hideSideBar();
await this.hideOverlay();
const { method, payload, callback } = <NormalArg>JSON.parse(arg.payload);
switch (method) {
case "getStatusBarHeight":
await this.getStatusBarHeight(callback);
break;
case "genAuthKey":
await this.genAuthKey(payload, callback);
break;
case "getCookieInfo":
await this.getCookieInfo(payload, callback);
break;
@@ -218,6 +252,13 @@ class TGClient {
case "share":
await this.share(payload, callback);
break;
case "share2":
await this.nullCallback(arg);
break;
// 监听滚动事件? payload:{direction: 0|1} 0:向上滚动 1:向下滚动
case "onBeginDragging":
await this.nullCallback(arg);
break;
// getNotificationSettings
default:
console.warn(`[${arg.windowLabel}] ${arg.payload}`);
@@ -258,6 +299,24 @@ class TGClient {
await this.callback(callback, data);
}
/**
* @func genAuthKey
* @since Beta v0.3.7
* @desc 获取米游社客户端的 authkey
* @param {Record<string, string>} payload - 请求参数
* @param {string} callback - 回调函数名
* @returns {void} - 无返回值
*/
async genAuthKey(payload: Record<string, string>, callback: string): Promise<void> {
const userStore = useUserStore();
const cookie = {
mid: userStore.cookie.mid,
stoken: userStore.cookie.stoken,
};
const res = await TGRequest.User.getAuthkey2(cookie, payload);
await this.callback(callback, res.data);
}
/**
* @func getCookieInfo
* @since Beta v0.3.4
@@ -398,7 +457,7 @@ class TGClient {
/**
* @func pushPage
* @since Beta v0.3.4
* @since Beta v0.3.7
* @desc 打开米游社客户端的页面
* @param {unknown} payload - 请求参数
* @returns {void} - 无返回值
@@ -407,22 +466,29 @@ class TGClient {
const url: string = payload.page;
if (url.startsWith("mihoyobbs://article/")) {
const urlBBS = url.replace("mihoyobbs://article/", "https://m.miyoushe.com/ys/#/article/");
await this.open("pushPage", urlBBS);
await this.pushPage({ page: urlBBS });
return;
} else if (url.startsWith("mihoyobbs://webview?link=")) {
const urlWv = url.replace("mihoyobbs://webview?link=", "");
// 解析经过编码作为参数的链接
const urlReal = decodeURIComponent(urlWv);
await this.open("pushPage", urlReal);
await this.pushPage({ page: urlReal });
return;
}
await this.open("pushPage", url);
this.route.push(url);
console.log(`[pushPage] ${url}`);
const executeJS = `javascript:(function(){
window.location.href = '${url}';
})();`;
await invoke("execute_js", { label: "mhy_client", js: executeJS });
await this.loadJSBridge();
await this.hideSideBar();
await this.hideOverlay();
}
/**
* @func closePage
* @since Beta v0.3.6
* @since Beta v0.3.7
* @desc 关闭米游社客户端的页面
* @returns {void} - 无返回值
*/
@@ -433,7 +499,11 @@ class TGClient {
return;
}
const url = this.route[this.route.length - 1];
await this.open("closePage", url);
const executeJS = `javascript:(function(){
window.location.href = '${url}';
})();`;
await invoke("execute_js", { label: "mhy_client", js: executeJS });
await this.loadJSBridge();
}
/**
@@ -451,23 +521,36 @@ class TGClient {
/**
* @func onClickImg
* @since Beta v0.3.6
* @since Beta v0.3.7
* @desc 点击图片,下载到本地
* @param {unknown} payload - 请求参数
* @returns {void} - 无返回值
*/
async onClickImg(payload: any): Promise<void> {
const image = payload.image_list[0];
const executeJS = `javascript:(async function() {
const executeJS = this.getSaveImgJS(image.url, image.format);
await invoke("execute_js", { label: "mhy_client", js: executeJS });
}
/**
* @func getSaveImgJS
* @since Beta v0.3.7
* @desc 获取保存图片的 JS
* @param {string} url - 图片链接
* @param {string} format - 图片格式
* @returns {string} - JS
*/
getSaveImgJS(url: string, format: string): string {
return `javascript:(async function() {
const _t = window.__TAURI__;
const defaultPath = await _t.path.downloadDir() + Date.now() + '.${image.format}';
const defaultPath = await _t.path.downloadDir() + Date.now() + '.${format}';
const savePath = await _t.dialog.save({
title: '保存图片',
filters: [{ name: '图片', extensions: ['png'] }],
defaultPath: defaultPath,
});
if (savePath) {
const resBlob = await _t.http.fetch('${image.url}',{
const resBlob = await _t.http.fetch('${url}',{
method: 'GET',
responseType: _t.http.ResponseType.Binary
});
@@ -479,19 +562,26 @@ class TGClient {
alert('保存成功');
}
})();`;
await invoke("execute_js", { label: "mhy_client", js: executeJS });
}
/**
* @func share
* @since Beta v0.3.5
* @since Beta v0.3.7
* @desc 分享
* @todo 实现图片获取
* @param {unknown} payload - 请求参数
* @param {string} callback - 回调函数名
* @returns {void} - 无返回值
*/
async share(payload: any, callback: string): Promise<void> {
// 如果有数据
if (payload?.content.image_url !== undefined) {
const image = payload.content.image_url;
const format = image.split(".").pop();
const executeJS = this.getSaveImgJS(image, format);
await invoke("execute_js", { label: "mhy_client", js: executeJS });
await this.callback(callback, {});
return;
}
// 延时 3s
setTimeout(async () => {
await this.callback(callback, {});

View File

@@ -1,27 +1,24 @@
/**
* @file utils TGShare.ts
* @file utils/TGShare.ts
* @description 生成分享截图并保存到本地
* @since Beta v0.3.6
* @since Beta v0.3.7
*/
import { dialog, fs, http, path } from "@tauri-apps/api";
import html2canvas from "html2canvas";
import { bytesToSize } from "./toolFunc";
import showConfirm from "../components/func/confirm";
import showSnackbar from "../components/func/snackbar";
/**
* @description 保存图片-canvas
* @since Beta v0.3.4
* @param {HTMLCanvasElement} canvas - canvas元素
* @since Beta v0.3.7
* @param {Uint8Array} buffer - 图片数据
* @param {string} filename - 文件名
* @returns {Promise<void>} 无返回值
*/
async function saveCanvasImg(canvas: HTMLCanvasElement, filename: string): Promise<void> {
const buffer = new Uint8Array(
atob(canvas.toDataURL("image/png").split(",")[1])
.split("")
.map((item) => item.charCodeAt(0)),
);
async function saveCanvasImg(buffer: Uint8Array, filename: string): Promise<void> {
await dialog
.save({
title: "保存图片",
@@ -75,7 +72,7 @@ function getShareImgBgColor(): string {
/**
* @description 生成分享截图
* @since Beta v0.3.6
* @since Beta v0.3.7
* @param {string} fileName - 文件名
* @param {HTMLElement} element - 元素
* @param {number} scale - 缩放比例
@@ -104,28 +101,54 @@ export async function generateShareImg(
dpi: 350,
};
const canvasData = await html2canvas(element, opts);
try {
await copyToClipboard(canvasData);
const buffer = new Uint8Array(
atob(canvasData.toDataURL("image/png").split(",")[1])
.split("")
.map((item) => item.charCodeAt(0)),
);
const size = buffer.length;
const sizeStr = bytesToSize(size);
if (size > 80000000) {
showSnackbar({
text: `已将 ${fileName} 复制到剪贴板`,
text: `图像大小为 ${sizeStr},过大,无法保存`,
color: "warn",
timeout: 3000,
});
return;
}
if (size > 20000000) {
const sizeStr = bytesToSize(size);
const saveFile = await showConfirm({
title: "图像过大",
text: `图像大小为 ${sizeStr},是否保存到文件?`,
});
if (!saveFile) {
showSnackbar({
color: "cancel",
text: "已取消",
});
} else {
await saveCanvasImg(buffer, fileName);
}
return;
}
try {
await copyToClipboard(buffer);
showSnackbar({
text: `已将 ${fileName} 复制到剪贴板,大小为 ${sizeStr}`,
});
} catch (e) {
await saveCanvasImg(canvasData, fileName);
await saveCanvasImg(buffer, fileName);
}
}
/**
* @description 复制到剪贴板
* @since Beta v0.3.2
* @param {HTMLCanvasElement} canvas - canvas元素
* @since Beta v0.3.7
* @param {Uint8Array} buffer - 图片数据
* @returns {Promise<void>} 无返回值
*/
async function copyToClipboard(canvas: HTMLCanvasElement): Promise<void> {
const buffer = new Uint8Array(
atob(canvas.toDataURL("image/png").split(",")[1])
.split("")
.map((item) => item.charCodeAt(0)),
);
async function copyToClipboard(buffer: Uint8Array): Promise<void> {
const blob = new Blob([buffer], { type: "image/png" });
const url = URL.createObjectURL(blob);
await navigator.clipboard.write([

View File

@@ -1,7 +1,7 @@
/**
* @file utils TGWindow.ts
* @file utils/TGWindow.ts
* @description 窗口创建相关工具函数
* @since Beta v0.3.4
* @since Beta v0.3.7
*/
import { invoke, window as TauriWindow } from "@tauri-apps/api";
@@ -73,13 +73,13 @@ export function createTGWindow(
/**
* @description 打开帖子
* @since Beta v0.3.3
* @param {TGApp.Plugins.Mys.News.RenderCard|string|number} item 帖子内容或ID
* @since Beta v0.3.7
* @param {TGApp.Plugins.Mys.News.RenderCard | string | number | TGApp.Plugins.Mys.Forum.RenderCard} item 帖子内容或ID
* @param {string} title 帖子标题
* @returns {void}
*/
export function createPost(
item: TGApp.Plugins.Mys.News.RenderCard | string | number,
item: TGApp.Plugins.Mys.News.RenderCard | string | number | TGApp.Plugins.Mys.Forum.RenderCard,
title?: string,
): void {
let postId, postTitle, jsonTitle;

View File

@@ -5,7 +5,10 @@
*/
import { os, path } from "@tauri-apps/api";
import colorConvert from "color-convert";
import type { KEYWORD } from "color-convert/conversions";
import { v4 } from "uuid";
import { score } from "wcag-color";
/**
* @description 时间戳转换为时间字符串
@@ -159,3 +162,30 @@ export function getRandomString(length: number, type: string = "all"): string {
}
return res;
}
/**
* @description 判断颜色是否相似
* @since Beta v0.3.7
* @param {string} colorBg - 背景颜色
* @param {string} colorText - 文本颜色
* @returns {boolean} 是否相似
*/
export function isColorSimilar(colorBg: string, colorText: string): boolean {
const hexBg = colorBg.startsWith("#") ? colorBg : colorConvert.keyword.hex(<KEYWORD>colorBg);
const hexText = colorText.startsWith("#")
? colorText
: colorConvert.keyword.hex(<KEYWORD>colorText);
return score(hexText, hexBg) === "Fail";
}
/**
* @description 判断是否为 Mys 帖子
* @since Beta v0.3.7
* @param {string} url - 网址
* @returns {boolean} 是否为 Mys 帖子
*/
export function isMysPost(url: string): boolean {
const regBBS = /^https:\/\/bbs\.mihoyo\.com\/\w+\/article\/\d+$/;
const regMys = /^https:\/\/www\.miyoushe\.com\/\w+\/article\/\d+$/;
return regBBS.test(url) || regMys.test(url);
}

View File

@@ -7,9 +7,12 @@
:title="shareTitle"
/>
<ToLoading v-model="loading" :empty="loadingEmpty" :title="loadingTitle" :subtitle="loadingSub" />
<div class="mys-post-body">
<div class="mys-post-info">
<div class="mys-post-meta">
<div class="tp-post-body">
<div class="tp-post-info">
<div class="tp-post-version">
PostID{{ postId }} | Render by TeyvatGuide v{{ appVersion }}
</div>
<div class="tp-post-meta">
<div class="mpm-forum" v-if="postRender.forum !== null">
<img :src="postRender.forum.icon" alt="forumIcon" />
<span>{{ postRender.forum?.name }}</span>
@@ -35,14 +38,10 @@
<span>{{ postRender.metadata.forward_num }}</span>
</div>
</div>
<div class="mys-post-author">
<div class="tp-post-author">
<div class="mpa-left">
<span>{{ postRender.author.nickname }}</span>
<span>{{
postRender.author.certification?.label === ""
? postRender.author.introduce
: postRender.author.certification?.label
}}</span>
<span :title="getMpaLeftDesc()">{{ getMpaLeftDesc() }}</span>
</div>
<div class="mpa-right">
<div class="mpa-icon">
@@ -54,19 +53,19 @@
</div>
</div>
</div>
<div class="mys-post-title">
<div class="tp-post-title">
<span class="mpt-official" v-if="postRender.isOfficial"></span>
<span>{{ postRender.title }}</span>
</div>
<div class="mys-post-subtitle">
<div class="tp-post-subtitle">
<span>创建时间{{ postRender.created }}&emsp;</span>
<span>更新时间{{ postRender.updated }}</span>
</div>
<!-- eslint-disable-nextline vue/no-v-html -->
<div class="mys-post-content" v-html="postHtml" />
<TpParser v-model:data="renderPost" />
</div>
</template>
<script lang="ts" setup>
import { app } from "@tauri-apps/api";
import { appWindow } from "@tauri-apps/api/window";
import { nextTick, onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
@@ -74,6 +73,7 @@ import { useRoute } from "vue-router";
import TSwitchTheme from "../components/app/t-switchTheme.vue";
import TShareBtn from "../components/main/t-shareBtn.vue";
import ToLoading from "../components/overlay/to-loading.vue";
import TpParser from "../components/post/tp-parser.vue";
import Mys from "../plugins/Mys";
interface PostRender {
@@ -98,8 +98,9 @@ const postRef = ref<HTMLElement>(<HTMLElement>{});
const shareTitle = ref<string>("");
// 数据
const appVersion = ref<string>();
const postId = Number(useRoute().params.post_id);
const postHtml = ref<string>("");
const renderPost = ref<TGApp.Plugins.Mys.SctPost.Base[]>([]);
const postRender = ref<PostRender>({
title: "",
isOfficial: false,
@@ -119,11 +120,14 @@ const postRender = ref<PostRender>({
reply_num: 0,
like_num: 0,
forward_num: 0,
original_like_num: 0,
post_upvote_stat: [],
},
});
onMounted(async () => {
await appWindow.show();
appVersion.value = await app.getVersion();
// 检查数据
if (!postId) {
loadingEmpty.value = true;
@@ -136,7 +140,7 @@ onMounted(async () => {
try {
const postData = await Mys.Post.get(postId);
loadingTitle.value = "正在渲染数据...";
postHtml.value = Mys.Post.parser(postData);
renderPost.value = getRenderPost(postData);
postRender.value = {
title: postData.post.subject,
isOfficial: postData.post.post_status.is_official,
@@ -147,7 +151,7 @@ onMounted(async () => {
metadata: postData.stat,
};
shareTitle.value = `Post_${postId}`;
postRef.value = <HTMLElement>document.querySelector(".mys-post-body");
postRef.value = <HTMLElement>document.querySelector(".tp-post-body");
await appWindow.setTitle(`Post_${postId} ${postData.post.subject}`);
} catch (error) {
console.error(error);
@@ -172,17 +176,68 @@ watch(loadShare, (value) => {
loading.value = false;
}
});
function getMpaLeftDesc(): string {
return postRender.value.author.certification?.label === ""
? postRender.value.author.introduce ?? ""
: postRender.value.author.certification?.label ?? "";
}
function getRenderPost(data: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys.SctPost.Base[] {
const postContent = data.post.content;
let jsonParse: string;
if (postContent.startsWith("<")) {
jsonParse = data.post.structured_content;
} else {
try {
jsonParse = parseContent(data.post.content);
} catch (e) {
jsonParse = data.post.structured_content;
}
}
return JSON.parse(jsonParse);
}
function parseContent(content: string): string {
const data: TGApp.Plugins.Mys.SctPost.Other = JSON.parse(content);
const result: TGApp.Plugins.Mys.SctPost.Base[] = [];
const keys = Object.keys(data);
keys.forEach((key) => {
switch (key) {
case "describe":
result.push({
insert: data.describe,
});
break;
case "imgs":
data.imgs.forEach((item) => {
result.push({
insert: {
image: item,
},
});
});
break;
default:
console.warn(`[MysPostParser] Unknown key: ${key}`);
result.push({
insert: data[key],
});
break;
}
});
return JSON.stringify(result);
}
</script>
<style lang="css" scoped src="../assets/css/post-parser.css" />
<style lang="css" scoped>
.mys-post-body {
.tp-post-body {
width: 800px;
margin: 0 auto;
font-family: var(--font-text);
}
/* title */
.mys-post-title {
.tp-post-title {
margin: 10px auto;
color: var(--common-text-title);
font-family: var(--font-title);
@@ -202,23 +257,33 @@ watch(loadShare, (value) => {
}
/* subtitle */
.mys-post-subtitle {
.tp-post-subtitle {
font-size: 16px;
opacity: 0.6;
}
/* info */
.mys-post-info {
.tp-post-info {
position: relative;
display: flex;
width: 100%;
align-items: end;
justify-content: space-between;
padding-bottom: 10px;
border-bottom: 1px dashed var(--common-shadow-2);
}
.tp-post-version {
position: absolute;
top: 0;
left: 0;
color: var(--box-text-4);
font-family: var(--font-title);
font-size: 14px;
opacity: 0.6;
}
/* author */
.mys-post-author {
.tp-post-author {
display: flex;
}
@@ -232,22 +297,18 @@ watch(loadShare, (value) => {
}
.mpa-left :nth-child(1) {
display: flex;
height: 30px;
align-items: center;
justify-content: start;
font-size: 16px;
}
.mpa-left :nth-child(2) {
display: flex;
width: 100%;
overflow: hidden;
height: 20px;
align-items: center;
justify-content: end;
border-top: 2px solid var(--common-shadow-2);
font-size: 14px;
opacity: 0.7;
text-align: right;
text-overflow: ellipsis;
}
.mpa-right {
@@ -289,7 +350,7 @@ watch(loadShare, (value) => {
}
/* meta */
.mys-post-meta {
.tp-post-meta {
display: flex;
align-items: center;
justify-content: start;
@@ -310,6 +371,12 @@ watch(loadShare, (value) => {
object-fit: cover;
}
.mpm-forum span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mpm-item {
display: flex;
align-items: center;
@@ -318,9 +385,4 @@ watch(loadShare, (value) => {
column-gap: 2px;
opacity: 0.8;
}
/* content */
.mys-post-content {
line-height: 2;
}
</style>

View File

@@ -4,7 +4,7 @@
* @since Beta v0.3.6
*/
import { genAuthkey } from "./genAuthkey";
import { genAuthkey, genAuthkey2 } from "./genAuthkey";
import { getAbyss } from "./getAbyss";
import { getActionTicketBySToken } from "./getActionTicket";
import { getAnnoContent, getAnnoList } from "./getAnno";
@@ -33,6 +33,7 @@ const TGRequest = {
},
User: {
getAuthkey: genAuthkey,
getAuthkey2: genAuthkey2,
getGachaLog,
getRecord: getGameRecord,
byLoginTicket: {

View File

@@ -1,7 +1,7 @@
/**
* @file web/request/genAuthkey.ts
* @description 生成 authkey
* @since Beta v0.3.0
* @since Beta v0.3.7
*/
import { http } from "@tauri-apps/api";
@@ -39,3 +39,25 @@ export async function genAuthkey(
return res.data;
});
}
/**
* @description 生成 authkey
* @since Beta v0.3.0
* @param {Record<string, string>} cookie cookie // stoken_v2 & mid
* @param {object} payload payload
* @returns {Promise<string|TGApp.BBS.Response.Base>} authkey
*/
export async function genAuthkey2(
cookie: Record<string, string>,
payload: Record<string, string>,
): Promise<TGApp.BBS.Response.Base> {
const url = "https://api-takumi.mihoyo.com/binding/api/genAuthKey";
const header = TGUtils.User.getHeader(cookie, "POST", JSON.stringify(payload), "lk2", true);
return await http
.fetch<TGApp.BBS.Response.Base>(url, {
method: "POST",
headers: header,
body: http.Body.json(payload),
})
.then((res) => res.data);
}

View File

@@ -1,7 +1,7 @@
/**
* @file vite.config.ts
* @description vite 配置文件
* @since Beta v0.3.4
* @since Beta v0.3.7
*/
import vue from "@vitejs/plugin-vue";
@@ -42,8 +42,10 @@ export default defineConfig({
// chunking
output: {
manualChunks(id) {
// pnpm 依赖包路径格式为 本地路径/node_modules/.pnpm/包名@版本号/node_modules/依赖包名/文件路径
if (id.includes("node_modules")) {
return id.toString().split("node_modules/")[1].split("/")[0].toString();
const arr = id.split("node_modules/");
return arr[2].split("/")[0];
}
},
},