Revert "feat: 添加收藏夹选择和取消收藏功能"
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
/sign
|
/sign
|
||||||
/dist
|
/dist
|
||||||
/build
|
/build
|
||||||
# .husky/ — hooks are tracked in version control
|
.husky/
|
||||||
*/__pycache__
|
*/__pycache__
|
||||||
src/buildinfo.ts
|
src/buildinfo.ts
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
@@ -1 +0,0 @@
|
|||||||
npx --no -- commitlint --edit $1
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
npx lint-staged
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
# 代码修复计划 — Bug、风险与性能问题
|
|
||||||
|
|
||||||
## 修复优先级总览
|
|
||||||
|
|
||||||
| 阶段 | 问题数 | 描述 |
|
|
||||||
|------|--------|------|
|
|
||||||
| P0 紧急 | 8 | 功能完全不可用、崩溃、安全漏洞 |
|
|
||||||
| P1 高 | 8 | 数据损坏、启动失败、CI 失效 |
|
|
||||||
| P2 中 | 14 | 功能异常、性能退化、潜在隐患 |
|
|
||||||
| P3 低 | 11 | 代码质量、工具链现代化 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## P0 紧急修复(功能不可用/崩溃)
|
|
||||||
|
|
||||||
### 1. 回复功能 — API 调用交换 + 未声明变量
|
|
||||||
**文件**: `src/pages/reply/replys/replys.ux`
|
|
||||||
- **L64-77**: `GiveTreeReply` 和 `GiveSecReply` 调用语义反了,交换调用
|
|
||||||
- **L97**: `this.input_text` 未声明,在 `private` data 中添加 `input_text: { content: "" }`
|
|
||||||
|
|
||||||
### 2. 音乐播放 — `api` 未定义
|
|
||||||
**文件**: `src/pages/video/player/player.ux`
|
|
||||||
- **L782,822,1261,1461,1495**: `api.getSongUrl()` / `api.getLyric()` 调用但 `api` 未导入
|
|
||||||
- 需要创建 `src/bilibiliclient/music/music.ts` 模块(或找到正确的 API 来源),在文件顶部导入并赋值给 `api`
|
|
||||||
|
|
||||||
### 3. 动态详情 — `typeof` 比较恒为 true
|
|
||||||
**文件**: `src/pages/app/features/dynamic/detail/detail.ux`
|
|
||||||
- **L87**: `typeof this.dyn != Object` → `typeof this.dyn !== "object"`
|
|
||||||
|
|
||||||
### 4. 空回复检查 — 字符串与数字比较
|
|
||||||
**文件**: `src/pages/video/videodetail/videodetail.ux`
|
|
||||||
- **L202**: `evt.detail.content === 0` → `!evt.detail.content || evt.detail.content.length === 0`
|
|
||||||
|
|
||||||
### 5. Settings JSON.parse 崩溃
|
|
||||||
**文件**: `src/settings.ts`
|
|
||||||
- **L37**: 用 try-catch 包裹 `JSON.parse(data)`,catch 中保持默认 SETTINGS 并 log 警告
|
|
||||||
|
|
||||||
### 6. App 启动 Promise 无错误处理
|
|
||||||
**文件**: `src/app.ux`
|
|
||||||
- **L44-50**: 用 try-catch 包裹每个 await,或用 `Promise.allSettled` 并行执行非关键初始化
|
|
||||||
- 关键初始化(`savedcontent.initialize`)失败应显示错误页面
|
|
||||||
- 非关键初始化(`getNetworkType`)失败可降级为默认值
|
|
||||||
|
|
||||||
### 7. HTTP 追踪 URL(调试遗留)
|
|
||||||
**文件**: `src/usertracker.ts`
|
|
||||||
- **L5**: 取消注释 L4 的 HTTPS URL,删除或注释 L5 的 HTTP 内网地址
|
|
||||||
|
|
||||||
### 8. asyncapi `access()` 语义错误
|
|
||||||
**文件**: `src/asyncapi/file.ts`
|
|
||||||
- **L161**: `fail` 回调中 `reject(...)` → `resolve(false)`,使调用方能用布尔值判断文件是否存在
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## P1 高优先级修复
|
|
||||||
|
|
||||||
### 9. API 响应空检查链
|
|
||||||
**文件**: `src/bilibiliclient/api/request.ts`
|
|
||||||
- **L60, L91**: `return response.data` 前增加对 `response` 和 `response.data` 的 null 检查
|
|
||||||
- 创建 `safeData(response)` 辅助函数,统一处理 `response.data.code !== 0` 的错误情况
|
|
||||||
- 在所有 bilibiliclient 子模块中使用此辅助函数
|
|
||||||
|
|
||||||
### 10. 文章图片空引用
|
|
||||||
**文件**: `src/articletools.ts`
|
|
||||||
- **L39**: 在访问 `dom.attributes.src` 前增加 `dom.attributes && dom.attributes.src` 检查
|
|
||||||
- **L49**: 第二个 `parseInt(dom.attributes.height)` → `parseInt(dom.attributes.width)`
|
|
||||||
|
|
||||||
### 11. 页面返回键崩溃
|
|
||||||
**文件**: `src/ui/ui.ts`
|
|
||||||
- **L55**: 增加 `pageStack.length >= 2` 判断,不足时跳过返回动画
|
|
||||||
- **L57-58**: 增加 `global.vmPool[lastPageName]` 存在性检查
|
|
||||||
|
|
||||||
### 12. VmPool GC 逻辑错误
|
|
||||||
**文件**: `src/ui/ui.ts`
|
|
||||||
- **L21**: `return` → `continue`
|
|
||||||
|
|
||||||
### 13. 构建脚本不传播失败
|
|
||||||
**文件**: `scripts/build_s3s4.py`, `scripts/build_rw5.py`
|
|
||||||
- **L8**: `os.system("yarn run build")` → `subprocess.run(["yarn", "run", "build"], check=True)`
|
|
||||||
- 同时更新 import 语句添加 `subprocess`
|
|
||||||
|
|
||||||
### 14. buildtools.py 异常吞掉
|
|
||||||
**文件**: `scripts/buildtools.py`
|
|
||||||
- **L18**: `Exception(...)` → `raise Exception(...)`
|
|
||||||
- **L27-31**: 用 `with open(...) as f:` 替换裸 `open()`
|
|
||||||
|
|
||||||
### 15. ESLint 配置缺失
|
|
||||||
**文件**: 新建 `eslint.config.mjs`
|
|
||||||
- 创建 ESLint flat config 文件,兼容 ESLint 9
|
|
||||||
- 或将 `package.json` 中 ESLint 降级到 `^8.x` 并保留旧 `.eslintrc`
|
|
||||||
|
|
||||||
### 16. Husky hooks 未版本控制 + API 过时
|
|
||||||
**文件**: `.gitignore`, `husky.sh`
|
|
||||||
- 从 `.gitignore` 中删除 `.husky/` 行
|
|
||||||
- 用 Husky v9 API 重写 `husky.sh`(`husky init` + 直接写 `.husky/pre-commit` 和 `.husky/commit-msg` 脚本)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## P2 中优先级修复
|
|
||||||
|
|
||||||
### 17. bvid 默认值类型错误
|
|
||||||
**文件**: `src/pages/video/videodetail/videodetail.ux`
|
|
||||||
- **L84**: `bvid: 1` → `bvid: ""`
|
|
||||||
|
|
||||||
### 18. 数组初始化为对象
|
|
||||||
**文件**: `src/pages/user/user.ux` L53, `src/pages/search/search/search.ux` L32
|
|
||||||
- `masterpiece: {}` → `masterpiece: []`
|
|
||||||
- `hotwords: {}` → `hotwords: []`
|
|
||||||
|
|
||||||
### 19. 音频事件处理被覆盖
|
|
||||||
**文件**: `src/pages/video/player/player.ux`
|
|
||||||
- **L404-407**: 删除 `onInit` 中重复的 `audio.ontimeupdate` 赋值,或合并逻辑到 `bindAudioEvents`
|
|
||||||
|
|
||||||
### 20. fetchDefaultPlaylist 空函数
|
|
||||||
**文件**: `src/pages/video/player/player.ux`
|
|
||||||
- **L665-668**: 实现函数体或标记为 TODO 并添加用户提示
|
|
||||||
|
|
||||||
### 21. Settings 加载竞态
|
|
||||||
**文件**: `src/settings.ts`
|
|
||||||
- 重构 `loadSettings` 返回 `Promise`,在 `app.ux` 中 `await` 完成后再继续初始化
|
|
||||||
|
|
||||||
### 22. HTML 解析器嵌套标签问题
|
|
||||||
**文件**: `src/htmlparser.ts`
|
|
||||||
- **L38**: 对于嵌套标签,改用递归下降解析而非单正则匹配
|
|
||||||
- 或引入轻量 HTML 解析库
|
|
||||||
|
|
||||||
### 23. 收藏夹名称硬编码
|
|
||||||
**文件**: `src/bilibiliclient/video/action.ts` L23, L47
|
|
||||||
- 改用 `folder.id === 0`(B站默认收藏夹 ID 固定为 0)或通过 API 响应中的 `default` 字段判断
|
|
||||||
|
|
||||||
### 24. 存储索引无验证
|
|
||||||
**文件**: `src/savedcontent.ts`
|
|
||||||
- **L38**: JSON.parse 后增加 `Array.isArray()` 检查和元素结构验证
|
|
||||||
|
|
||||||
### 25. 日志无缓冲
|
|
||||||
**文件**: `src/logger/logger.ts`
|
|
||||||
- 实现内存缓冲区,批量写入(如每 10 条或每 5 秒 flush 一次)
|
|
||||||
- 添加日志文件轮转机制(保留最近 N 个文件)
|
|
||||||
|
|
||||||
### 26. VmPool GC 定时器优化
|
|
||||||
**文件**: `src/ui/ui.ts`
|
|
||||||
- 在页面销毁事件中触发 GC,替代 10 秒定时器
|
|
||||||
|
|
||||||
### 27. 动画引用泄漏
|
|
||||||
**文件**: `src/animation/engine.ts`
|
|
||||||
- 在 `SequenceAnim.stop()` 和 `DomAnim.stop()` 中从 `global.animations` 移除自身引用
|
|
||||||
|
|
||||||
### 28. request.ts 无超时
|
|
||||||
**文件**: `src/bilibiliclient/api/request.ts`
|
|
||||||
- `getRequest` 和 `postRequest` 增加 `timeout` 参数(默认 15s),传入 `fetch.fetch()`
|
|
||||||
|
|
||||||
### 29. Content-Length 冗余设置
|
|
||||||
**文件**: `src/bilibiliclient/message/message.ts`
|
|
||||||
- **L51**: 删除手动 `Content-Length` 设置,让 HTTP 客户端自动处理
|
|
||||||
|
|
||||||
### 30. Stylelint 废弃规则
|
|
||||||
**文件**: `.stylelintrc.js`
|
|
||||||
- **L10-11**: 删除 `color-hex-case` 和 `color-hex-length`,或安装 `stylelint-stylistic` 插件
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## P3 低优先级改进
|
|
||||||
|
|
||||||
### 31. formatNumber 边界处理
|
|
||||||
**文件**: `src/tools.ts` L3-13 — 增加 `NaN`/负数/undefined 守卫
|
|
||||||
|
|
||||||
### 32. funnytips 日期捕获时机
|
|
||||||
**文件**: `src/funnytips.ts` L1 — `new Date()` 移入 `getTips()` 函数内部
|
|
||||||
|
|
||||||
### 33. jumpcheck storage 无 fail 回调
|
|
||||||
**文件**: `src/jumpcheck.ts` — 增加 `fail` 回调,导航到错误页面
|
|
||||||
|
|
||||||
### 34. Object.assign 原型组装
|
|
||||||
**文件**: `src/bilibiliclient/client.ts` L55-68 — 长期重构为 class extends 或 mixin 模式(影响范围大,建议单独 PR)
|
|
||||||
|
|
||||||
### 35. quickapp.config.js 健壮性
|
|
||||||
**文件**: `quickapp.config.js`
|
|
||||||
- L7: `execSync` 包裹 try-catch
|
|
||||||
- L10: 相对路径 → `path.resolve(__dirname, ...)`
|
|
||||||
- L12-17: 模板字符串值转义引号和反斜杠
|
|
||||||
|
|
||||||
### 36. manifest.json 生产日志级别
|
|
||||||
**文件**: `src/manifest.json` L53 — `"log"` → `"warn"`(或在构建脚本中覆盖)
|
|
||||||
|
|
||||||
### 37. .prettierrc.js 废弃选项
|
|
||||||
**文件**: `.prettierrc.js` L10 — `jsxBracketSameLine` → `bracketSameLine`
|
|
||||||
|
|
||||||
### 38. buildInfoContent 注入风险
|
|
||||||
**文件**: `quickapp.config.js` L12-17 — 对插值值做 JSON.stringify 转义
|
|
||||||
|
|
||||||
### 39. PII 泄漏
|
|
||||||
**文件**: `quickapp.config.js` L8 — 移除 `os.userInfo().username`,改用 CI 环境变量或固定标识
|
|
||||||
|
|
||||||
### 40. logger %c 样式无效
|
|
||||||
**文件**: `src/logger/logger.ts` L61 — 移除 `%c` 和 style 参数
|
|
||||||
|
|
||||||
### 41. eula.ts 嵌套 `<p>` 标签
|
|
||||||
**文件**: `src/eula.ts` — 修正 HTML 结构,外层 `<p>` 改为 `<div>`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 实施建议
|
|
||||||
|
|
||||||
### 分批策略
|
|
||||||
1. **第 1 批 (P0)**: 8 个修复,每个独立可测,建议逐个 PR
|
|
||||||
2. **第 2 批 (P1)**: 8 个修复,工具链相关可合并为一个 PR
|
|
||||||
3. **第 3 批 (P2)**: 14 个修复,按模块分组(player 模块、bilibiliclient 模块、基础设施)
|
|
||||||
4. **第 4 批 (P3)**: 11 个改进,可在后续迭代中逐步完成
|
|
||||||
|
|
||||||
### 验证方式
|
|
||||||
- 每个修复在 NuttX 设备或模拟器上手动验证对应功能
|
|
||||||
- 构建脚本修复:运行 `python scripts/build_s3s4.py` 和 `python scripts/build_rw5.py` 确认退出码
|
|
||||||
- 工具链修复:运行 `yarn lint` 确认 ESLint 正常工作
|
|
||||||
- CI 修复:推送到 `next-gen` 分支确认 GitHub Actions 流水线绿色
|
|
||||||
|
|
||||||
### 关键文件清单
|
|
||||||
| 文件 | 修改类型 |
|
|
||||||
|------|---------|
|
|
||||||
| `src/pages/reply/replys/replys.ux` | Bug 修复 |
|
|
||||||
| `src/pages/video/player/player.ux` | Bug 修复 + 功能实现 |
|
|
||||||
| `src/pages/app/features/dynamic/detail/detail.ux` | Bug 修复 |
|
|
||||||
| `src/pages/video/videodetail/videodetail.ux` | Bug 修复 |
|
|
||||||
| `src/settings.ts` | 健壮性修复 |
|
|
||||||
| `src/app.ux` | 错误处理 |
|
|
||||||
| `src/usertracker.ts` | 安全修复 |
|
|
||||||
| `src/asyncapi/file.ts` | 语义修复 |
|
|
||||||
| `src/bilibiliclient/api/request.ts` | 健壮性修复 |
|
|
||||||
| `src/articletools.ts` | Bug 修复 |
|
|
||||||
| `src/ui/ui.ts` | Bug 修复 + 性能 |
|
|
||||||
| `src/tools.ts` | 边界处理 |
|
|
||||||
| `src/htmlparser.ts` | Bug 修复 |
|
|
||||||
| `src/logger/logger.ts` | 性能优化 |
|
|
||||||
| `src/savedcontent.ts` | 健壮性修复 |
|
|
||||||
| `src/animation/engine.ts` | 内存泄漏修复 |
|
|
||||||
| `src/funnytips.ts` | Bug 修复 |
|
|
||||||
| `src/jumpcheck.ts` | 健壮性修复 |
|
|
||||||
| `src/bilibiliclient/video/action.ts` | 兼容性修复 |
|
|
||||||
| `src/bilibiliclient/message/message.ts` | 代码清理 |
|
|
||||||
| `scripts/build_s3s4.py` | CI 修复 |
|
|
||||||
| `scripts/build_rw5.py` | CI 修复 |
|
|
||||||
| `scripts/buildtools.py` | Bug 修复 |
|
|
||||||
| `quickapp.config.js` | 健壮性 + 安全 |
|
|
||||||
| `package.json` | 依赖更新 |
|
|
||||||
| `.prettierrc.js` | 配置更新 |
|
|
||||||
| `.stylelintrc.js` | 配置更新 |
|
|
||||||
| `husky.sh` | 重写 |
|
|
||||||
| `.gitignore` | 配置修复 |
|
|
||||||
| `.github/workflows/main.yml` | CI 修复 |
|
|
||||||
| `eslint.config.mjs` | 新建 |
|
|
||||||
| `src/pages/user/user.ux` | Bug 修复 |
|
|
||||||
| `src/pages/search/search/search.ux` | Bug 修复 |
|
|
||||||
| `src/eula.ts` | HTML 修正 |
|
|
||||||
| `src/manifest.json` | 配置调整 |
|
|
||||||
+1
-1
@@ -7,7 +7,7 @@ module.exports = {
|
|||||||
quoteProps: "consistent", // 要求对象字面量属性是否使用引号包裹,(‘as-needed’: 没有特殊要求,禁止使用,'consistent': 保持一致 , preserve: 不限制,想用就用)
|
quoteProps: "consistent", // 要求对象字面量属性是否使用引号包裹,(‘as-needed’: 没有特殊要求,禁止使用,'consistent': 保持一致 , preserve: 不限制,想用就用)
|
||||||
trailingComma: "none", // 不添加对象和数组最后一个元素的逗号
|
trailingComma: "none", // 不添加对象和数组最后一个元素的逗号
|
||||||
bracketSpacing: false, // 对象中对空格和空行进行处理
|
bracketSpacing: false, // 对象中对空格和空行进行处理
|
||||||
bracketSameLine: false, // 在多行JSX元素的最后一行追加 >
|
jsxBracketSameLine: false, // 在多行JSX元素的最后一行追加 >
|
||||||
requirePragma: false, // 是否严格按照文件顶部的特殊注释格式化代码
|
requirePragma: false, // 是否严格按照文件顶部的特殊注释格式化代码
|
||||||
insertPragma: false, // 是否在格式化的文件顶部插入Pragma标记,以表明该文件被prettier格式化过了
|
insertPragma: false, // 是否在格式化的文件顶部插入Pragma标记,以表明该文件被prettier格式化过了
|
||||||
proseWrap: "preserve", // 按照文件原样折行
|
proseWrap: "preserve", // 按照文件原样折行
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ module.exports = {
|
|||||||
ignoreFiles: ["node_modules", "test", "dist", "**/*.js"],
|
ignoreFiles: ["node_modules", "test", "dist", "**/*.js"],
|
||||||
rules: {
|
rules: {
|
||||||
"no-descending-specificity": null,
|
"no-descending-specificity": null,
|
||||||
|
"color-hex-case": "lower",
|
||||||
|
"color-hex-length": "short",
|
||||||
"at-rule-no-unknown": null,
|
"at-rule-no-unknown": null,
|
||||||
"block-no-empty": null,
|
"block-no-empty": null,
|
||||||
"selector-pseudo-class-no-unknown": [
|
"selector-pseudo-class-no-unknown": [
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
# AGENTS.md
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
Third-party Bilibili client for Xiaomi Vela embedded watches, built with the **QuickApp** framework (快应用). Targets round watches (S3/S4 at 466px, RW5 at 432px design width).
|
|
||||||
|
|
||||||
## Key Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn # install deps (NEVER use npm — postinstall enforces this)
|
|
||||||
yarn start # dev server with watch + NuttX device
|
|
||||||
yarn build # production build (outputs dist/*.rpk)
|
|
||||||
yarn lint # eslint --fix on src/**/*.{ux,js}
|
|
||||||
python scripts/build_s3s4.py # build for S3/S4 watches (466px design width)
|
|
||||||
python scripts/build_rw5.py # build for RW5 watches (432px design width)
|
|
||||||
```
|
|
||||||
|
|
||||||
Build scripts mutate `src/manifest.json` `config.designWidth` before calling `yarn build`. The CI runs both sequentially.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
**Framework**: QuickApp (小米快应用) — NOT Vue, React, or standard web. Uses `.ux` single-file components with `<template>`, `<script>`, `<style>` blocks. Docs: https://iot.mi.com/vela/quickapp
|
|
||||||
|
|
||||||
**Build toolchain**: `aiot-toolkit` (rspack-based). TypeScript compiled via `builtin:swc-loader`, NOT tsc. The `tsconfig.json` exists for editor support only.
|
|
||||||
|
|
||||||
**Entry point**: `src/manifest.json` → router entry is `pages/app/entry/splash`
|
|
||||||
|
|
||||||
**Generated file**: `src/buildinfo.ts` is auto-generated at build time (gitignored). Do not create or edit it manually. Import it as `$buildinfo`.
|
|
||||||
|
|
||||||
**System API bridge**: `src/tsimports.js` re-exports QuickApp system APIs (`@system.fetch`, `@system.storage`, etc.) for use in TypeScript files. Import from `'../tsimports'`, not directly from `@system.*`.
|
|
||||||
|
|
||||||
**Path aliases** (defined in `quickapp.config.js`):
|
|
||||||
- `@src` → `src/`
|
|
||||||
- `@components` → `src/components/`
|
|
||||||
- `@less` → `src/less/`
|
|
||||||
- `@protobuf` → `src/protobuf/`
|
|
||||||
- `$buildinfo` → `src/buildinfo.ts`
|
|
||||||
|
|
||||||
**BilibiliClient**: API client assembled via `Object.assign` onto a single prototype from modules in `src/bilibiliclient/`. Each subdirectory (video, search, comment, etc.) exports a methods object merged in `client.ts`.
|
|
||||||
|
|
||||||
**Global state**: App-level singletons are attached to `global` in `src/app.ux` — `global.biliclient`, `global.logger`, `global.settings`, `global.ui`, `global.animengine`, `global.bgimg`, `global.savedcontent`. Access these from any page/component.
|
|
||||||
|
|
||||||
## File Conventions
|
|
||||||
|
|
||||||
- **`.ux` files**: QuickApp components. Use `<import>` to register child components (not ES modules). Template syntax is Vue-like but has differences (e.g., `if`/`for` directives, `@click`).
|
|
||||||
- **`.ts` files**: Business logic, API clients, utilities. Standard TypeScript.
|
|
||||||
- **`.less` files**: Styles, scoped per component. Stylelint uses `postcss-less` custom syntax.
|
|
||||||
- **`src/less/`**: Shared style variables/mixins.
|
|
||||||
- **`src/common/`**: Shared static assets (images, icons).
|
|
||||||
|
|
||||||
## Style & Lint
|
|
||||||
|
|
||||||
- **Prettier**: no semicolons, double quotes, no trailing commas, 100 char width, no bracket spacing. `.ux` files parsed as Vue.
|
|
||||||
- **ESLint**: extends prettier config. Lints `.ux` and `.js` in `src/`.
|
|
||||||
- **Stylelint**: standard + recess property order. Ignores `.js` files. Allows QuickApp-specific properties (`placeholder-color`, `gradient-*`, `caret-color`, etc.).
|
|
||||||
- **lint-staged**: runs on commit via husky — prettier + eslint for `*.{ux,js}`, prettier + stylelint for `*.{less,css}`.
|
|
||||||
- **Commitlint**: conventional commits. Allowed types: `bug`, `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `revert`, `merge`.
|
|
||||||
|
|
||||||
## Gotchas
|
|
||||||
|
|
||||||
- **No test suite exists.** There are no unit tests or integration tests. Verify changes manually on device or emulator.
|
|
||||||
- **`src/manifest.json` is mutable at build time.** Build scripts overwrite `config.designWidth`. Don't assume its value is stable.
|
|
||||||
- **QuickApp is not web.** DOM APIs, `window`, `document` do not exist. The runtime is a V8/JSC engine on embedded Linux. Use `system.*` APIs for device capabilities.
|
|
||||||
- **`.ux` template syntax diverges from Vue.** For example, `static` attribute on elements marks them as non-reactive for perf. `@system.folme` provides animation, not CSS transitions.
|
|
||||||
- **i18n**: strings in `src/i18n/{en,zh}.json`. Components reference keys like `"main.title"`.
|
|
||||||
- **CI**: GitHub Actions builds on push/PR to `next-gen` branch only. Produces two RPK artifacts (S3/S4 and RW5).
|
|
||||||
- **No `dist/` or `build/` in repo.** Both are gitignored. RPK output goes to `dist/`.
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import js from "@eslint/js"
|
|
||||||
import prettier from "eslint-config-prettier"
|
|
||||||
|
|
||||||
export default [
|
|
||||||
js.configs.recommended,
|
|
||||||
prettier,
|
|
||||||
{
|
|
||||||
files: ["src/**/*.{js,ux}"],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: 2022,
|
|
||||||
sourceType: "module",
|
|
||||||
globals: {
|
|
||||||
global: "readonly",
|
|
||||||
console: "readonly",
|
|
||||||
setTimeout: "readonly",
|
|
||||||
setInterval: "readonly",
|
|
||||||
clearTimeout: "readonly",
|
|
||||||
clearInterval: "readonly",
|
|
||||||
Promise: "readonly",
|
|
||||||
JSON: "readonly",
|
|
||||||
Math: "readonly",
|
|
||||||
Date: "readonly",
|
|
||||||
String: "readonly",
|
|
||||||
Number: "readonly",
|
|
||||||
Array: "readonly",
|
|
||||||
Object: "readonly",
|
|
||||||
parseInt: "readonly",
|
|
||||||
parseFloat: "readonly",
|
|
||||||
isNaN: "readonly",
|
|
||||||
undefined: "readonly",
|
|
||||||
Uint8Array: "readonly",
|
|
||||||
requestAnimationFrame: "readonly",
|
|
||||||
cancelAnimationFrame: "readonly"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
"no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
|
||||||
"no-console": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ignores: ["dist/", "build/", "node_modules/", "src/buildinfo.ts"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1 +1,3 @@
|
|||||||
npx husky init
|
npx husky install
|
||||||
|
npx husky add .husky/pre-commit 'npx lint-staged'
|
||||||
|
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit ${1}'
|
||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
"build": "aiot build --enable-jsc --enable-protobuf",
|
"build": "aiot build --enable-jsc --enable-protobuf",
|
||||||
"release": "aiot release --enable-jsc --enable-protobuf",
|
"release": "aiot release --enable-jsc --enable-protobuf",
|
||||||
"watch": "aiot watch --open-nuttx",
|
"watch": "aiot watch --open-nuttx",
|
||||||
"lint": "eslint --format codeframe --fix src/",
|
"lint": "eslint --format codeframe --fix --ext .ux,.js src/",
|
||||||
"postinstall": "node -e \"if (process.env.npm_execpath && !process.env.npm_execpath.includes('yarn')) { console.error('Please use Yarn to install dependencies!'); process.exit(1); }\""
|
"postinstall": "node -e \"if (process.env.npm_execpath && !process.env.npm_execpath.includes('yarn')) { console.error('Please use Yarn to install dependencies!'); process.exit(1); }\""
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|||||||
+6
-11
@@ -4,21 +4,16 @@ const os = require('os');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
// 注入buildinfo
|
// 注入buildinfo
|
||||||
let gitCommitHash = "unknown";
|
const gitCommitHash = childProcess.execSync('git rev-parse HEAD').toString().trim();
|
||||||
try {
|
const username = os.userInfo().username;
|
||||||
gitCommitHash = childProcess.execSync('git rev-parse HEAD').toString().trim();
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Failed to get git commit hash:', e.message);
|
|
||||||
}
|
|
||||||
const username = process.env.CI ? "ci" : os.userInfo().username;
|
|
||||||
const buildTime = new Date().toISOString();
|
const buildTime = new Date().toISOString();
|
||||||
const designWidth = JSON.parse(fs.readFileSync(path.resolve(__dirname, "src/manifest.json"))).config.designWidth
|
const designWidth = JSON.parse(fs.readFileSync("src/manifest.json")).config.designWidth
|
||||||
|
|
||||||
const buildInfoContent = `
|
const buildInfoContent = `
|
||||||
export const GIT_COMMIT_HASH = "${gitCommitHash.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}";
|
export const GIT_COMMIT_HASH = "${gitCommitHash}";
|
||||||
export const BUILD_TIME = "${buildTime}";
|
export const BUILD_TIME = "${buildTime}";
|
||||||
export const BUILD_USER = "${username.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}";
|
export const BUILD_USER = "${username}";
|
||||||
export const DESIGN_WIDTH = ${JSON.stringify(designWidth)};
|
export const DESIGN_WIDTH = ${designWidth};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const buildInfoPath = path.resolve(__dirname, 'src/buildinfo.ts');
|
const buildInfoPath = path.resolve(__dirname, 'src/buildinfo.ts');
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import subprocess
|
import os
|
||||||
import buildtools
|
import buildtools
|
||||||
|
|
||||||
DESIGN_WIDTH = 432
|
DESIGN_WIDTH = 432
|
||||||
|
|
||||||
buildtools.setManifestDesignWidth(DESIGN_WIDTH)
|
buildtools.setManifestDesignWidth(DESIGN_WIDTH)
|
||||||
|
|
||||||
subprocess.run(["yarn", "run", "build"], check=True)
|
os.system("yarn run build")
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import subprocess
|
import os
|
||||||
import buildtools
|
import buildtools
|
||||||
|
|
||||||
DESIGN_WIDTH = 466
|
DESIGN_WIDTH = 466
|
||||||
|
|
||||||
buildtools.setManifestDesignWidth(DESIGN_WIDTH)
|
buildtools.setManifestDesignWidth(DESIGN_WIDTH)
|
||||||
|
|
||||||
subprocess.run(["yarn", "run", "build"], check=True)
|
os.system("yarn run build")
|
||||||
@@ -15,7 +15,7 @@ def getGitCommitHash():
|
|||||||
return commit_hash
|
return commit_hash
|
||||||
else:
|
else:
|
||||||
print("Failed to get commit hash. Make sure you are in a Git repository.")
|
print("Failed to get commit hash. Make sure you are in a Git repository.")
|
||||||
raise Exception(f"Error: {result.stderr}")
|
Exception(f"Error: {result.stderr}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"An error occurred: {e}")
|
print(f"An error occurred: {e}")
|
||||||
|
|
||||||
@@ -25,8 +25,10 @@ def getBuildTime():
|
|||||||
return formatted_datetime
|
return formatted_datetime
|
||||||
|
|
||||||
def readFileToJson(path):
|
def readFileToJson(path):
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
f = open(path, "r")
|
||||||
return json.loads(f.read())
|
f = f.read()
|
||||||
|
f = json.loads(f)
|
||||||
|
return f
|
||||||
|
|
||||||
def writeJsonToFile(path, obj):
|
def writeJsonToFile(path, obj):
|
||||||
with open(path, "w+", encoding="utf-8") as f:
|
with open(path, "w+", encoding="utf-8") as f:
|
||||||
|
|||||||
+2
-18
@@ -63,15 +63,7 @@ class SequenceAnim extends Anim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stop(): void {
|
stop(): void {
|
||||||
if (this.intervalId) {
|
clearInterval(this.intervalId!);
|
||||||
clearInterval(this.intervalId);
|
|
||||||
this.intervalId = null;
|
|
||||||
}
|
|
||||||
// Remove self from global.animations to prevent memory leak
|
|
||||||
if (global.animations && global.animations[this.pageId]) {
|
|
||||||
const idx = global.animations[this.pageId].indexOf(this);
|
|
||||||
if (idx !== -1) global.animations[this.pageId].splice(idx, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,15 +124,7 @@ class DomAnim extends Anim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stop(): void {
|
stop(): void {
|
||||||
if (this.intervalId) {
|
clearInterval(this.intervalId!);
|
||||||
clearInterval(this.intervalId);
|
|
||||||
this.intervalId = null;
|
|
||||||
}
|
|
||||||
// Remove self from global.animations to prevent memory leak
|
|
||||||
if (global.animations && global.animations[this.pageId]) {
|
|
||||||
const idx = global.animations[this.pageId].indexOf(this);
|
|
||||||
if (idx !== -1) global.animations[this.pageId].splice(idx, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-28
@@ -42,39 +42,16 @@ export default {
|
|||||||
global.settings = settings
|
global.settings = settings
|
||||||
|
|
||||||
console.log("appinit > initSavedContentManager")
|
console.log("appinit > initSavedContentManager")
|
||||||
try {
|
await savedcontent.SavedContentManager.initialize()
|
||||||
await savedcontent.SavedContentManager.initialize()
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to init SavedContentManager:", e)
|
|
||||||
}
|
|
||||||
global.savedcontent = savedcontent
|
global.savedcontent = savedcontent
|
||||||
|
|
||||||
console.log("appinit > initDeviceInfo")
|
console.log("appinit > initDeviceInfo")
|
||||||
try {
|
global.DEVICE_INFO = await tools.getDeviceInformation()
|
||||||
global.DEVICE_INFO = await tools.getDeviceInformation()
|
global.DEVICE_SERIAL = await tools.getDeviceSerial()
|
||||||
} catch (e) {
|
global.DEVICE_NETWORK_TYPE = await tools.getNetworkType()
|
||||||
console.error("Failed to get device info:", e)
|
|
||||||
global.DEVICE_INFO = { screenShape: "round", product: "unknown" }
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
global.DEVICE_SERIAL = await tools.getDeviceSerial()
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to get device serial:", e)
|
|
||||||
global.DEVICE_SERIAL = "unknown"
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
global.DEVICE_NETWORK_TYPE = await tools.getNetworkType()
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to get network type:", e)
|
|
||||||
global.DEVICE_NETWORK_TYPE = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("appinit > initBgImg")
|
console.log("appinit > initBgImg")
|
||||||
try {
|
this.bgimg.Init()
|
||||||
this.bgimg.Init()
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to init bgimg:", e)
|
|
||||||
}
|
|
||||||
global.bgimg = this.bgimg
|
global.bgimg = this.bgimg
|
||||||
|
|
||||||
console.log("appinit > createBiliClient")
|
console.log("appinit > createBiliClient")
|
||||||
|
|||||||
+1
-2
@@ -35,7 +35,6 @@ export function PatchArticleContent(doms: any) {
|
|||||||
if (!dom) continue; // 避免空节点
|
if (!dom) continue; // 避免空节点
|
||||||
|
|
||||||
if (dom.type === "img") {
|
if (dom.type === "img") {
|
||||||
if (!dom.attributes || !dom.attributes.src) continue;
|
|
||||||
// 如果图片的 src 包含 .png 或 .jpg 扩展名
|
// 如果图片的 src 包含 .png 或 .jpg 扩展名
|
||||||
if (dom.attributes.src.includes(".png") || dom.attributes.src.includes(".jpg")) {
|
if (dom.attributes.src.includes(".png") || dom.attributes.src.includes(".jpg")) {
|
||||||
// 确保 src 包含 http:// 或 https:// 前缀
|
// 确保 src 包含 http:// 或 https:// 前缀
|
||||||
@@ -47,7 +46,7 @@ export function PatchArticleContent(doms: any) {
|
|||||||
|
|
||||||
if(dom.attributes){
|
if(dom.attributes){
|
||||||
// 如果图片过大,调整其尺寸
|
// 如果图片过大,调整其尺寸
|
||||||
if (parseInt(dom.attributes.height) > 500 || parseInt(dom.attributes.width) > 500) {
|
if (parseInt(dom.attributes.height) > 500 || parseInt(dom.attributes.height) > 500) {
|
||||||
dom.attributes.src += "@250h"; // 对应补丁操作
|
dom.attributes.src += "@250h"; // 对应补丁操作
|
||||||
dom.attributes._patched_sign = "large_picture_scaled"
|
dom.attributes._patched_sign = "large_picture_scaled"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class FileAPI {
|
|||||||
file.access({
|
file.access({
|
||||||
...options,
|
...options,
|
||||||
success: () => resolve(true),
|
success: () => resolve(true),
|
||||||
fail: () => resolve(false),
|
fail: (_, code: number) => reject(new Error(`Access failed with code ${code}`)),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ export const BilibiliClientAPIRequestMethods = {
|
|||||||
const response = await this.fetch.fetch({
|
const response = await this.fetch.fetch({
|
||||||
url,
|
url,
|
||||||
responseType: responseType,
|
responseType: responseType,
|
||||||
header: this.getHeaders(),
|
header: this.getHeaders()
|
||||||
timeout: 15000
|
|
||||||
});
|
});
|
||||||
if (!response || !response.data) return void 0;
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
global.logger.error(`GET请求失败,详细数据:`, error);
|
global.logger.error(`GET请求失败,详细数据:`, error);
|
||||||
@@ -88,10 +86,8 @@ export const BilibiliClientAPIRequestMethods = {
|
|||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data,
|
data,
|
||||||
header: headers,
|
header: headers
|
||||||
timeout: 15000
|
|
||||||
});
|
});
|
||||||
if (!response || !response.data) return void 0;
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
global.logger.error(`POST请求失败,详细数据:`, error);
|
global.logger.error(`POST请求失败,详细数据:`, error);
|
||||||
|
|||||||
@@ -7,14 +7,6 @@ export const BilibiliClientFavFolderMethods = {
|
|||||||
return response.data.data;
|
return response.data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取用户所有收藏夹及目标视频在各收藏夹中的收藏状态
|
|
||||||
// 返回的每个 folder 对象中包含 fav_state 字段:0=未收藏, 1=已收藏
|
|
||||||
async getUserFavouriteFoldersWithVideoState(this: any, mid: string, aid: string): Promise<any> {
|
|
||||||
const url = `https://api.bilibili.com/x/v3/fav/folder/created/list-all?up_mid=${mid}&type=2&rid=${aid}`;
|
|
||||||
const response = await this.getRequest(url);
|
|
||||||
return response.data.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 获取目标收藏夹元数据
|
// 获取目标收藏夹元数据
|
||||||
async getFavouriteFolderMetadata(this: any, mlid: string): Promise<any> {
|
async getFavouriteFolderMetadata(this: any, mlid: string): Promise<any> {
|
||||||
const url = `https://api.bilibili.com/x/v3/fav/folder/info?media_id=${mlid}`;
|
const url = `https://api.bilibili.com/x/v3/fav/folder/info?media_id=${mlid}`;
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ export const BilibiliClientMessageMethods = {
|
|||||||
headers["Host"] = "api.vc.bilibili.com"
|
headers["Host"] = "api.vc.bilibili.com"
|
||||||
headers["Origin"] = "https://message.bilibili.com"
|
headers["Origin"] = "https://message.bilibili.com"
|
||||||
headers["Referer"] = "https://message.bilibili.com/"
|
headers["Referer"] = "https://message.bilibili.com/"
|
||||||
|
headers["Content-Length"] = body.length.toString()
|
||||||
|
|
||||||
const response = await this.postRequestWbi(url, {
|
const response = await this.postRequestWbi(url, {
|
||||||
w_sender_uid: this.accountInfo.mid,
|
w_sender_uid: this.accountInfo.mid,
|
||||||
w_receiver_id: receiver_id,
|
w_receiver_id: receiver_id,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const BilibiliClientVideoActionMethods = {
|
|||||||
let defaultFolderID = 0;
|
let defaultFolderID = 0;
|
||||||
const folders = await this.getUserFavouriteFolders(this.accountInfo.mid);
|
const folders = await this.getUserFavouriteFolders(this.accountInfo.mid);
|
||||||
folders.list.forEach((folder: any) => {
|
folders.list.forEach((folder: any) => {
|
||||||
if (folder.attr === 1 || folder.id === 0) {
|
if (folder.title === "默认收藏夹") {
|
||||||
defaultFolderID = folder.id;
|
defaultFolderID = folder.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -38,56 +38,4 @@ export const BilibiliClientVideoActionMethods = {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 取消收藏视频从默认收藏夹
|
|
||||||
async unstarVideoFromDefaultFavFolderByBVID(this: any, bvid: string): Promise<any> {
|
|
||||||
let defaultFolderID = 0;
|
|
||||||
const folders = await this.getUserFavouriteFolders(this.accountInfo.mid);
|
|
||||||
folders.list.forEach((folder: any) => {
|
|
||||||
if (folder.attr === 1 || folder.id === 0) {
|
|
||||||
defaultFolderID = folder.id;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!defaultFolderID) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const videoInfo = await this.getVideoInfoByBVID(bvid);
|
|
||||||
const aid = videoInfo.aid;
|
|
||||||
const url = `https://api.bilibili.com/x/v3/fav/resource/deal`;
|
|
||||||
const data = `rid=${aid}&csrf=${this.biliJct}&type=2&add_media_ids=&del_media_ids=${defaultFolderID}`;
|
|
||||||
const response = await this.postRequest(url, data, "application/x-www-form-urlencoded");
|
|
||||||
return response.data.code;
|
|
||||||
} catch (error) {
|
|
||||||
global.logger.error("Error unstarring video: ", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 收藏视频至指定收藏夹(支持多个)
|
|
||||||
async starVideoToFavFolders(this: any, aid: string, folderIds: string[]): Promise<any> {
|
|
||||||
try {
|
|
||||||
const add_media_ids = folderIds.join(',');
|
|
||||||
const url = `https://api.bilibili.com/x/v3/fav/resource/deal`;
|
|
||||||
const data = `rid=${aid}&csrf=${this.biliJct}&type=2&add_media_ids=${add_media_ids}&del_media_ids=`;
|
|
||||||
const response = await this.postRequest(url, data, "application/x-www-form-urlencoded");
|
|
||||||
return response.data.code;
|
|
||||||
} catch (error) {
|
|
||||||
global.logger.error("Error starring video to folders: ", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 从指定收藏夹取消收藏视频(支持多个)
|
|
||||||
async unstarVideoFromFavFolders(this: any, aid: string, folderIds: string[]): Promise<any> {
|
|
||||||
try {
|
|
||||||
const del_media_ids = folderIds.join(',');
|
|
||||||
const url = `https://api.bilibili.com/x/v3/fav/resource/deal`;
|
|
||||||
const data = `rid=${aid}&csrf=${this.biliJct}&type=2&add_media_ids=&del_media_ids=${del_media_ids}`;
|
|
||||||
const response = await this.postRequest(url, data, "application/x-www-form-urlencoded");
|
|
||||||
return response.data.code;
|
|
||||||
} catch (error) {
|
|
||||||
global.logger.error("Error unstarring video from folders: ", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
+10
-10
@@ -5,24 +5,24 @@ export const eula = ` <h1>澎湃哔哩(Hyperbili)用户协议</h1>
|
|||||||
<p>本客户端是基于哔哩哔哩网页端API开发的第三方工具,旨在为用户提供在部分搭载Xiaomi Vela OS的设备上浏览哔哩哔哩提供的内容的能力。本客户端不具备独立的账号系统,用户需通过哔哩哔哩官方App进行扫码登录。所有的服务和内容均来自哔哩哔哩平台,我们不对其内容的准确性、合法性或其他方面负责。</p>
|
<p>本客户端是基于哔哩哔哩网页端API开发的第三方工具,旨在为用户提供在部分搭载Xiaomi Vela OS的设备上浏览哔哩哔哩提供的内容的能力。本客户端不具备独立的账号系统,用户需通过哔哩哔哩官方App进行扫码登录。所有的服务和内容均来自哔哩哔哩平台,我们不对其内容的准确性、合法性或其他方面负责。</p>
|
||||||
|
|
||||||
<h2>2. 免责声明</h2>
|
<h2>2. 免责声明</h2>
|
||||||
<div>
|
<p>
|
||||||
<p><strong>服务稳定性</strong>:我们将尽力维护客户端的正常运行,但不保证其无故障、无中断或随时可用。由于哔哩哔哩API变更、网络故障、服务器问题或其他不可抗力因素导致的服务中断、数据丢失或功能异常,AstralSight Studios不承担任何责任。</p>
|
<p><strong>服务稳定性</strong>:我们将尽力维护客户端的正常运行,但不保证其无故障、无中断或随时可用。由于哔哩哔哩API变更、网络故障、服务器问题或其他不可抗力因素导致的服务中断、数据丢失或功能异常,AstralSight Studios不承担任何责任。</p>
|
||||||
<p><strong>第三方内容</strong>:本客户端依赖哔哩哔哩平台提供的内容,AstralSight Studios不对任何第三方内容的准确性、合法性、完整性或适用性做出任何承诺或保证,用户需自行判断并承担使用风险。</p>
|
<p><strong>第三方内容</strong>:本客户端依赖哔哩哔哩平台提供的内容,AstralSight Studios不对任何第三方内容的准确性、合法性、完整性或适用性做出任何承诺或保证,用户需自行判断并承担使用风险。</p>
|
||||||
<p><strong>责任限制</strong>:在任何情况下,AstralSight Studios不对用户因使用或无法使用本客户端导致的任何间接、附带、特殊或惩罚性损害承担责任。</p>
|
<p><strong>责任限制</strong>:在任何情况下,AstralSight Studios不对用户因使用或无法使用本客户端导致的任何间接、附带、特殊或惩罚性损害承担责任。</p>
|
||||||
<p><strong>不可抗力</strong>:对于因自然灾害、战争、网络攻击、政府行为、哔哩哔哩API关闭或变更等不可抗力事件造成的服务中断或损失,AstralSight Studios不承担任何责任。</p>
|
<p><strong>不可抗力</strong>:对于因自然灾害、战争、网络攻击、政府行为、哔哩哔哩API关闭或变更等不可抗力事件造成的服务中断或损失,AstralSight Studios不承担任何责任。</p>
|
||||||
</div>
|
</p>
|
||||||
|
|
||||||
<h2>3. 账号安全与使用</h2>
|
<h2>3. 账号安全与使用</h2>
|
||||||
<p>用户通过哔哩哔哩官方App扫码登录并使用其哔哩哔哩账号。本客户端不会保存用户登录密码,但会保存用户的登录Token、与哔哩哔哩API交互产生的Cookies等,但不对其安全性(包括但不限于:存储安全性、传输安全性)负任何责任。用户需对其账号安全负责。如果因用户原因导致账号被盗或发生其他安全问题,AstralSight Studios不承担责任。</p>
|
<p>用户通过哔哩哔哩官方App扫码登录并使用其哔哩哔哩账号。本客户端不会保存用户登录密码,但会保存用户的登录Token、与哔哩哔哩API交互产生的Cookies等,但不对其安全性(包括但不限于:存储安全性、传输安全性)负任何责任。用户需对其账号安全负责。如果因用户原因导致账号被盗或发生其他安全问题,AstralSight Studios不承担责任。</p>
|
||||||
|
|
||||||
<h2>4. 用户行为规范</h2>
|
<h2>4. 用户行为规范</h2>
|
||||||
<p>用户在使用本客户端时应遵守国家法律法规、哔哩哔哩平台的相关规定,以及本协议中的条款。用户不得利用本客户端进行包括但不限于以下行为:</p>
|
<p>用户在使用本客户端时应遵守国家法律法规、哔哩哔哩平台的相关规定,以及本协议中的条款。用户不得利用本客户端进行包括但不限于以下行为:</p>
|
||||||
<div>
|
<p>
|
||||||
<p>传播非法、侵权、骚扰、诽谤、淫秽、欺诈性内容;</p>
|
<p>传播非法、侵权、骚扰、诽谤、淫秽、欺诈性内容;</p>
|
||||||
<p>侵犯他人知识产权、隐私权、名誉权等合法权益;</p>
|
<p>侵犯他人知识产权、隐私权、名誉权等合法权益;</p>
|
||||||
<p>进行批量数据抓取、自动化访问或未经授权的网络攻击;</p>
|
<p>进行批量数据抓取、自动化访问或未经授权的网络攻击;</p>
|
||||||
<p>任何其他违反哔哩哔哩平台规则或法律法规的行为。</p>
|
<p>任何其他违反哔哩哔哩平台规则或法律法规的行为。</p>
|
||||||
</div>
|
</p>
|
||||||
<p>对于上述行为,AstralSight Studios有权立即中止或终止用户的使用权限,且不承担任何法律责任。</p>
|
<p>对于上述行为,AstralSight Studios有权立即中止或终止用户的使用权限,且不承担任何法律责任。</p>
|
||||||
|
|
||||||
<h2>5. 开源许可</h2>
|
<h2>5. 开源许可</h2>
|
||||||
@@ -39,29 +39,29 @@ export const eula = ` <h1>澎湃哔哩(Hyperbili)用户协议</h1>
|
|||||||
|
|
||||||
<h2>1. 信息的收集</h2>
|
<h2>1. 信息的收集</h2>
|
||||||
<p>在使用本客户端的过程中,我们可能会收集但不限于以下信息:</p>
|
<p>在使用本客户端的过程中,我们可能会收集但不限于以下信息:</p>
|
||||||
<div>
|
<p>
|
||||||
<p><strong>设备信息</strong>:包括设备型号、操作系统、设备标识符等;</p>
|
<p><strong>设备信息</strong>:包括设备型号、操作系统、设备标识符等;</p>
|
||||||
<p><strong>网络信息</strong>:包括IP地址、网络提供商、连接时长等;</p>
|
<p><strong>网络信息</strong>:包括IP地址、网络提供商、连接时长等;</p>
|
||||||
<p><strong>操作日志</strong>:包括用户操作行为、功能使用情况、访问页面记录等;</p>
|
<p><strong>操作日志</strong>:包括用户操作行为、功能使用情况、访问页面记录等;</p>
|
||||||
<p><strong>其他数据</strong>:随着产品功能的扩展,我们可能收集更多种类的用户数据,具体收集内容将在未来版本的隐私政策中进行更新。</p>
|
<p><strong>其他数据</strong>:随着产品功能的扩展,我们可能收集更多种类的用户数据,具体收集内容将在未来版本的隐私政策中进行更新。</p>
|
||||||
</div>
|
</p>
|
||||||
|
|
||||||
<h2>2. 数据的使用</h2>
|
<h2>2. 数据的使用</h2>
|
||||||
<p>我们将收集到的用户数据用于以下目的:</p>
|
<p>我们将收集到的用户数据用于以下目的:</p>
|
||||||
<div>
|
<p>
|
||||||
<p>优化和改进客户端的功能与用户体验;</p>
|
<p>优化和改进客户端的功能与用户体验;</p>
|
||||||
<p>分析用户行为,提升产品的使用效果;</p>
|
<p>分析用户行为,提升产品的使用效果;</p>
|
||||||
<p>保障客户端的安全,防止恶意攻击和非法活动;</p>
|
<p>保障客户端的安全,防止恶意攻击和非法活动;</p>
|
||||||
<p>满足法律法规要求,或配合相关部门的合法请求。</p>
|
<p>满足法律法规要求,或配合相关部门的合法请求。</p>
|
||||||
</div>
|
</p>
|
||||||
|
|
||||||
<h2>3. 数据的共享</h2>
|
<h2>3. 数据的共享</h2>
|
||||||
<p>我们不会主动将用户的个人数据分享给任何第三方,除非在以下情形下:</p>
|
<p>我们不会主动将用户的个人数据分享给任何第三方,除非在以下情形下:</p>
|
||||||
<div>
|
<p>
|
||||||
<p>因任何不可抗力(包括但不限于 因资金短缺造成无法负担起数据存储费用、遭受网络攻击等)导致数据必须分享给第三方以进行包括但不限于数据存储、CDN分发、异地容灾等操作;</p>
|
<p>因任何不可抗力(包括但不限于 因资金短缺造成无法负担起数据存储费用、遭受网络攻击等)导致数据必须分享给第三方以进行包括但不限于数据存储、CDN分发、异地容灾等操作;</p>
|
||||||
<p>遵守法律法规的要求,或响应执法机关、法院的合法请求;</p>
|
<p>遵守法律法规的要求,或响应执法机关、法院的合法请求;</p>
|
||||||
<p>在维护客户端的合法权益时,为保护AstralSight Studios免受潜在侵害,可能会与相关合作伙伴或技术提供商共享必要数据。</p>
|
<p>在维护客户端的合法权益时,为保护AstralSight Studios免受潜在侵害,可能会与相关合作伙伴或技术提供商共享必要数据。</p>
|
||||||
</div>
|
</p>
|
||||||
|
|
||||||
<h2>4. 数据的保护</h2>
|
<h2>4. 数据的保护</h2>
|
||||||
<p>我们将采取合理的技术手段和管理措施,保护用户数据免受未经授权的访问、泄露、篡改或破坏。然而,互联网环境中无法完全消除所有风险,用户应知晓并承担这些潜在风险。AstralSight Studios不对因用户行为或外部因素导致的数据泄露或损失负责。</p>
|
<p>我们将采取合理的技术手段和管理措施,保护用户数据免受未经授权的访问、泄露、篡改或破坏。然而,互联网环境中无法完全消除所有风险,用户应知晓并承担这些潜在风险。AstralSight Studios不对因用户行为或外部因素导致的数据泄露或损失负责。</p>
|
||||||
|
|||||||
+2
-1
@@ -1,3 +1,5 @@
|
|||||||
|
const date = new Date()
|
||||||
|
|
||||||
const tips = [
|
const tips = [
|
||||||
"你所热爱的,就是你热爱的",
|
"你所热爱的,就是你热爱的",
|
||||||
"让我猜猜你现在使用的设备是...?",
|
"让我猜猜你现在使用的设备是...?",
|
||||||
@@ -30,7 +32,6 @@ const AprilFoolsDayTips = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export function getTips(): string{
|
export function getTips(): string{
|
||||||
const date = new Date()
|
|
||||||
// 愚人节特供
|
// 愚人节特供
|
||||||
if(date.getMonth() === 3 && date.getDate() === 1){
|
if(date.getMonth() === 3 && date.getDate() === 1){
|
||||||
return AprilFoolsDayTips[Math.floor(Math.random() * AprilFoolsDayTips.length)]
|
return AprilFoolsDayTips[Math.floor(Math.random() * AprilFoolsDayTips.length)]
|
||||||
|
|||||||
+1
-6
@@ -4,7 +4,7 @@ export async function Jump() {
|
|||||||
storage.get({
|
storage.get({
|
||||||
key: "bilibili_account",
|
key: "bilibili_account",
|
||||||
success: async (bilibili_account) => {
|
success: async (bilibili_account) => {
|
||||||
if (!bilibili_account || bilibili_account.length < 1) {
|
if (bilibili_account.length < 1) {
|
||||||
router.replace({
|
router.replace({
|
||||||
uri: "pages/app/entry/login"
|
uri: "pages/app/entry/login"
|
||||||
})
|
})
|
||||||
@@ -13,11 +13,6 @@ export async function Jump() {
|
|||||||
uri: "pages/app/entry/prepage"
|
uri: "pages/app/entry/prepage"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
|
||||||
fail: function () {
|
|
||||||
router.replace({
|
|
||||||
uri: "pages/app/entry/login"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class Logger {
|
|||||||
const message = args.map((arg) => this.safeStringify(arg)).join(' ');
|
const message = args.map((arg) => this.safeStringify(arg)).join(' ');
|
||||||
|
|
||||||
// 在控制台输出日志
|
// 在控制台输出日志
|
||||||
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
|
console.log(`%c[${timestamp}] [${level.toUpperCase()}] ${message}`, style);
|
||||||
|
|
||||||
// 同步日志到文件
|
// 同步日志到文件
|
||||||
this.flushLogToFile(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
|
this.flushLogToFile(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"config": {
|
"config": {
|
||||||
"logLevel": "warn",
|
"logLevel": "log",
|
||||||
"designWidth": "device-width",
|
"designWidth": "device-width",
|
||||||
"background": {
|
"background": {
|
||||||
"features": [
|
"features": [
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export default {
|
|||||||
onInit() {
|
onInit() {
|
||||||
// 来自子组件push的object传递会变成字符串
|
// 来自子组件push的object传递会变成字符串
|
||||||
// 因此需要进行类型检查和手动parse
|
// 因此需要进行类型检查和手动parse
|
||||||
if (typeof this.dyn !== "object") {
|
if (typeof this.dyn != Object) {
|
||||||
this.dyn = JSON.parse(this.dyn)
|
this.dyn = JSON.parse(this.dyn)
|
||||||
console.log(this.dyn)
|
console.log(this.dyn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ export default {
|
|||||||
},
|
},
|
||||||
replymode: false,
|
replymode: false,
|
||||||
reply_target: null,
|
reply_target: null,
|
||||||
scrclass: "",
|
scrclass: ""
|
||||||
input_text: { content: "" }
|
|
||||||
},
|
},
|
||||||
async SendReply(evt) {
|
async SendReply(evt) {
|
||||||
this.replymode = false
|
this.replymode = false
|
||||||
@@ -63,19 +62,19 @@ export default {
|
|||||||
let result
|
let result
|
||||||
try {
|
try {
|
||||||
result = this.secmode
|
result = this.secmode
|
||||||
? await global.biliclient.GiveSecReply(
|
? await global.biliclient.GiveTreeReply(
|
||||||
this.type,
|
|
||||||
this.oid,
|
|
||||||
this.reply_target,
|
|
||||||
content
|
|
||||||
)
|
|
||||||
: await global.biliclient.GiveTreeReply(
|
|
||||||
this.type,
|
this.type,
|
||||||
this.oid,
|
this.oid,
|
||||||
this.reply_target,
|
this.reply_target,
|
||||||
this.rootrpid,
|
this.rootrpid,
|
||||||
content
|
content
|
||||||
)
|
)
|
||||||
|
: await global.biliclient.GiveSecReply(
|
||||||
|
this.type,
|
||||||
|
this.oid,
|
||||||
|
this.reply_target,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
if (result.code === 0) {
|
if (result.code === 0) {
|
||||||
prompt.showToast({message: "发送成功"})
|
prompt.showToast({message: "发送成功"})
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
import router from "@system.router"
|
import router from "@system.router"
|
||||||
export default {
|
export default {
|
||||||
private: {
|
private: {
|
||||||
hotwords: [],
|
hotwords: {},
|
||||||
showHotwordsList: false,
|
showHotwordsList: false,
|
||||||
input_mode: false,
|
input_mode: false,
|
||||||
anims: {
|
anims: {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default {
|
|||||||
user: {},
|
user: {},
|
||||||
stat: {},
|
stat: {},
|
||||||
navnum: {},
|
navnum: {},
|
||||||
masterpiece: [],
|
masterpiece: {},
|
||||||
dynamiclist: {},
|
dynamiclist: {},
|
||||||
anims: {
|
anims: {
|
||||||
show_loading: false
|
show_loading: false
|
||||||
|
|||||||
@@ -138,18 +138,6 @@
|
|||||||
import prompt from "@system.prompt";
|
import prompt from "@system.prompt";
|
||||||
import request from "@system.request";
|
import request from "@system.request";
|
||||||
import { asyncFile } from "../../../asyncapi/file";
|
import { asyncFile } from "../../../asyncapi/file";
|
||||||
|
|
||||||
// Music API stub — getSongUrl/getLyric are not yet implemented.
|
|
||||||
// Wrapping in a safe object prevents ReferenceError crashes.
|
|
||||||
const api = {
|
|
||||||
getSongUrl(id, opts) {
|
|
||||||
if (opts && opts.fail) opts.fail('Music API not implemented');
|
|
||||||
},
|
|
||||||
getLyric(id, opts) {
|
|
||||||
if (opts && opts.fail) opts.fail('Music API not implemented');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: { playlistId: { default: null }, startIndex: { default: 0 }, tracks: { default: null }, bvid: { default: null }, cachedAudioUri: { default: null }, cachedTitle: { default: null }, cachedCoverUrl: { default: null }, cachedAuthor: { default: null } },
|
props: { playlistId: { default: null }, startIndex: { default: 0 }, tracks: { default: null }, bvid: { default: null }, cachedAudioUri: { default: null }, cachedTitle: { default: null }, cachedCoverUrl: { default: null }, cachedAuthor: { default: null } },
|
||||||
data: {
|
data: {
|
||||||
@@ -413,9 +401,10 @@
|
|||||||
that.audioReady = true;
|
that.audioReady = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// ontimeupdate is already set in bindAudioEvents() with pendingSongId guard.
|
audio.ontimeupdate = () => {
|
||||||
// Do NOT overwrite it here — the bindAudioEvents version handles both
|
that.progress = audio.percent;
|
||||||
// playerState.playDuration and updateLyric().
|
that.updateLyric();
|
||||||
|
};
|
||||||
if (hasNewPlaylist || !fromAppStart) {
|
if (hasNewPlaylist || !fromAppStart) {
|
||||||
if (that.videoMode) {
|
if (that.videoMode) {
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -52,73 +52,4 @@
|
|||||||
|
|
||||||
.vidtoolimg {
|
.vidtoolimg {
|
||||||
width: 62px;
|
width: 62px;
|
||||||
}
|
|
||||||
|
|
||||||
.fav-folder-overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fav-folder-picker {
|
|
||||||
width: 340px;
|
|
||||||
height: 380px;
|
|
||||||
background-color: #2a2a2a;
|
|
||||||
border-radius: 16px;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fav-folder-picker-title {
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #ffffff;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fav-folder-list {
|
|
||||||
width: 100%;
|
|
||||||
height: 260px;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fav-folder-item {
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px 8px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: #3a3a3a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fav-folder-item-selected {
|
|
||||||
background-color: #4a6fa5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fav-folder-checkmark {
|
|
||||||
font-size: 20px;
|
|
||||||
color: #ffffff;
|
|
||||||
margin-right: 10px;
|
|
||||||
width: 28px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fav-folder-name {
|
|
||||||
font-size: 18px;
|
|
||||||
color: #ffffff;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fav-folder-buttons {
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
}
|
||||||
@@ -54,22 +54,6 @@
|
|||||||
</scroll>
|
</scroll>
|
||||||
<full-screen-input style="position: absolute" if="{{replymode}}" @send="SendReply"
|
<full-screen-input style="position: absolute" if="{{replymode}}" @send="SendReply"
|
||||||
@exit="ExitInput"></full-screen-input>
|
@exit="ExitInput"></full-screen-input>
|
||||||
<div class="fav-folder-overlay" if="{{favFolderPickerVisible}}">
|
|
||||||
<div class="fav-folder-picker">
|
|
||||||
<text class="fav-folder-picker-title">选择收藏夹</text>
|
|
||||||
<scroll scroll-y="true" class="fav-folder-list">
|
|
||||||
<div class="fav-folder-item {{folder.selected ? 'fav-folder-item-selected' : ''}}"
|
|
||||||
for="folder in favFolders" @click="ToggleFolder(folder.id)">
|
|
||||||
<text class="fav-folder-checkmark">{{folder.selected ? '✓' : '○'}}</text>
|
|
||||||
<text class="fav-folder-name">{{folder.title}}</text>
|
|
||||||
</div>
|
|
||||||
</scroll>
|
|
||||||
<div class="fav-folder-buttons">
|
|
||||||
<default-button text="取消" @click="HideFavFolderPicker"></default-button>
|
|
||||||
<default-button text="确认" @click="ConfirmAddToFav"></default-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -81,7 +65,7 @@ import {kmeansCircleGradientImage} from "@src/image/jpegkmeans.ts"
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
public: {
|
public: {
|
||||||
bvid: "" // 视频BVID
|
bvid: 1 // 视频BVID
|
||||||
},
|
},
|
||||||
private: {
|
private: {
|
||||||
vid: {},
|
vid: {},
|
||||||
@@ -93,9 +77,7 @@ export default {
|
|||||||
starsrc: "/common/vidtool_star.png",
|
starsrc: "/common/vidtool_star.png",
|
||||||
replymode: false,
|
replymode: false,
|
||||||
renderingCover: false,
|
renderingCover: false,
|
||||||
scrclass: "",
|
scrclass: ""
|
||||||
favFolderPickerVisible: false,
|
|
||||||
favFolders: []
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
stat_view() {
|
stat_view() {
|
||||||
@@ -199,7 +181,7 @@ export default {
|
|||||||
},
|
},
|
||||||
async SendReply(evt) {
|
async SendReply(evt) {
|
||||||
this.replymode = false
|
this.replymode = false
|
||||||
if (!evt.detail.content || evt.detail.content.length === 0) {
|
if (evt.detail.content === 0) {
|
||||||
return prompt.showToast({
|
return prompt.showToast({
|
||||||
message: "评论内容不得为空!",
|
message: "评论内容不得为空!",
|
||||||
duration: 2000
|
duration: 2000
|
||||||
@@ -238,83 +220,11 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
async AddToFav() {
|
async AddToFav() {
|
||||||
if (this.stared) {
|
const result = await global.biliclient.starVideoToDefaultFavFolderByBVID(this.bvid)
|
||||||
// 已收藏:从所有包含此视频的收藏夹中删除
|
const message = result ? "收藏失败,请重试,或重新登录后再试" : "收藏成功"
|
||||||
await this.ConfirmUnstarAll()
|
prompt.showToast({message})
|
||||||
} else {
|
|
||||||
// 未收藏:弹出收藏夹选择窗口
|
|
||||||
await this.ShowFavFolderPicker()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async ShowFavFolderPicker() {
|
|
||||||
try {
|
|
||||||
var self = this
|
|
||||||
var data = await global.biliclient.getUserFavouriteFoldersWithVideoState(
|
|
||||||
global.biliclient.accountInfo.mid,
|
|
||||||
this.vid.aid
|
|
||||||
)
|
|
||||||
var list = data.list || []
|
|
||||||
this.favFolders = list.map(function(folder) {
|
|
||||||
return {
|
|
||||||
id: folder.id,
|
|
||||||
title: folder.title,
|
|
||||||
selected: folder.fav_state === 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.favFolderPickerVisible = true
|
|
||||||
} catch (e) {
|
|
||||||
global.logger.error("获取收藏夹列表失败: ", e)
|
|
||||||
prompt.showToast({message: "获取收藏夹列表失败,请重试"})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
HideFavFolderPicker() {
|
|
||||||
this.favFolderPickerVisible = false
|
|
||||||
},
|
|
||||||
ToggleFolder(folderId) {
|
|
||||||
this.favFolders = this.favFolders.map(function(f) {
|
|
||||||
if (f.id === folderId) {
|
|
||||||
return Object.assign({}, f, { selected: !f.selected })
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async ConfirmAddToFav() {
|
|
||||||
var selectedIds = this.favFolders
|
|
||||||
.filter(function(f) { return f.selected })
|
|
||||||
.map(function(f) { return f.id })
|
|
||||||
this.favFolderPickerVisible = false
|
|
||||||
if (selectedIds.length === 0) {
|
|
||||||
prompt.showToast({message: "未选择任何收藏夹"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var result = await global.biliclient.starVideoToFavFolders(this.vid.aid, selectedIds)
|
|
||||||
var message = result === 0 ? "收藏成功" : "收藏失败,请重试,或重新登录后再试"
|
|
||||||
prompt.showToast({message: message})
|
|
||||||
this.UpdateVideoToolbarStatus()
|
this.UpdateVideoToolbarStatus()
|
||||||
},
|
},
|
||||||
async ConfirmUnstarAll() {
|
|
||||||
try {
|
|
||||||
var data = await global.biliclient.getUserFavouriteFoldersWithVideoState(
|
|
||||||
global.biliclient.accountInfo.mid,
|
|
||||||
this.vid.aid
|
|
||||||
)
|
|
||||||
var list = data.list || []
|
|
||||||
var staredFolderIds = list
|
|
||||||
.filter(function(f) { return f.fav_state === 1 })
|
|
||||||
.map(function(f) { return f.id })
|
|
||||||
if (staredFolderIds.length === 0) {
|
|
||||||
prompt.showToast({message: "该视频未被收藏"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var result = await global.biliclient.unstarVideoFromFavFolders(this.vid.aid, staredFolderIds)
|
|
||||||
var message = result === 0 ? "取消收藏成功" : "取消收藏失败,请重试,或重新登录后再试"
|
|
||||||
prompt.showToast({message: message})
|
|
||||||
this.UpdateVideoToolbarStatus()
|
|
||||||
} catch (e) {
|
|
||||||
global.logger.error("取消收藏失败: ", e)
|
|
||||||
prompt.showToast({message: "取消收藏失败,请重试"})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async GenBlur(evt) {
|
async GenBlur(evt) {
|
||||||
// 该Feature在现在任何设备上运行都会卡死,故注释
|
// 该Feature在现在任何设备上运行都会卡死,故注释
|
||||||
/*
|
/*
|
||||||
|
|||||||
+1
-2
@@ -35,8 +35,7 @@ async function loadStorageIndex(): Promise<void> {
|
|||||||
const fileExists = await asyncFile.access({ uri: indexFileUri });
|
const fileExists = await asyncFile.access({ uri: indexFileUri });
|
||||||
if (fileExists) {
|
if (fileExists) {
|
||||||
const indexData = await asyncFile.readText({ uri: indexFileUri });
|
const indexData = await asyncFile.readText({ uri: indexFileUri });
|
||||||
const parsed = JSON.parse(indexData);
|
storageIndex = JSON.parse(indexData);
|
||||||
storageIndex = Array.isArray(parsed) ? parsed : [];
|
|
||||||
} else {
|
} else {
|
||||||
storageIndex = [];
|
storageIndex = [];
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-9
@@ -34,15 +34,11 @@ export function loadSettings(): void {
|
|||||||
key: 'settings',
|
key: 'settings',
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
try {
|
const storedSettings = JSON.parse(data);
|
||||||
const storedSettings = JSON.parse(data);
|
SETTINGS = {
|
||||||
SETTINGS = {
|
...SETTINGS,
|
||||||
...SETTINGS,
|
...storedSettings
|
||||||
...storedSettings
|
};
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
global.logger.warn('Failed to parse settings, using defaults:', e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
global.logger.log('Settings loaded:', SETTINGS);
|
global.logger.log('Settings loaded:', SETTINGS);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { device, network, router } from "./tsimports"
|
import { device, network, router } from "./tsimports"
|
||||||
|
|
||||||
export function formatNumber(num: number): string {
|
export function formatNumber(num: number): string {
|
||||||
if (num == null || isNaN(num)) return "0";
|
|
||||||
if (num < 0) return "0";
|
|
||||||
if (num < 1000) {
|
if (num < 1000) {
|
||||||
return num.toString();
|
return num.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-9
@@ -18,7 +18,7 @@ function runVmPoolGC(){
|
|||||||
for(var name in global.vmPool){
|
for(var name in global.vmPool){
|
||||||
if(global.vmPool[name]){
|
if(global.vmPool[name]){
|
||||||
if(global.vmPool[name].$valid){
|
if(global.vmPool[name].$valid){
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,20 +46,15 @@ export function InitPage(vm){
|
|||||||
|
|
||||||
export function OnBackPressTriggered(){
|
export function OnBackPressTriggered(){
|
||||||
GlobalActions.UpdateCurrentPageName()
|
GlobalActions.UpdateCurrentPageName()
|
||||||
if (global.vmPool[global.currentPageName]) {
|
global.vmPool[global.currentPageName].scrclass = "scroll-backanim"
|
||||||
global.vmPool[global.currentPageName].scrclass = "scroll-backanim"
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
GlobalActions.ClearCurrentVmSrcClass()
|
GlobalActions.ClearCurrentVmSrcClass()
|
||||||
router.back()
|
router.back()
|
||||||
|
|
||||||
var pageStack = router.getPages()
|
var pageStack = router.getPages()
|
||||||
if (pageStack.length < 2) return
|
|
||||||
var lastPageName = pageStack[pageStack.length - 2].name
|
var lastPageName = pageStack[pageStack.length - 2].name
|
||||||
global.logger.log("lastpage=", lastPageName, "currentPage=", global.currentPageName)
|
global.logger.log("lastpage=", lastPageName, "currentPage=", global.currentPageName)
|
||||||
if (global.vmPool[lastPageName]) {
|
global.vmPool[lastPageName].scrclass = ""
|
||||||
global.vmPool[lastPageName].scrclass = ""
|
global.vmPool[lastPageName].scrclass = "scroll-frombackanim"
|
||||||
global.vmPool[lastPageName].scrclass = "scroll-frombackanim"
|
|
||||||
}
|
|
||||||
}, 150)
|
}, 150)
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import { fetch } from "./tsimports"
|
import { fetch } from "./tsimports"
|
||||||
|
|
||||||
const TRACKER_URL: string = "https://tracker.hyperbili.astralsight.space/trackreport"
|
//const TRACKER_URL: string = "https://tracker.hyperbili.astralsight.space/trackreport"
|
||||||
// const TRACKER_URL: string = "http://192.168.1.247:4080/trackreport"
|
const TRACKER_URL: string = "http://192.168.1.247:4080/trackreport"
|
||||||
const TRACKER_SERVER_PROTOCOL_VERSION = "v1"
|
const TRACKER_SERVER_PROTOCOL_VERSION = "v1"
|
||||||
|
|
||||||
interface TrackedInfoUploadPacket {
|
interface TrackedInfoUploadPacket {
|
||||||
|
|||||||
Reference in New Issue
Block a user