Revert "feat: 添加收藏夹选择和取消收藏功能"
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@
|
||||
/sign
|
||||
/dist
|
||||
/build
|
||||
# .husky/ — hooks are tracked in version control
|
||||
.husky/
|
||||
*/__pycache__
|
||||
src/buildinfo.ts
|
||||
**/.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: 不限制,想用就用)
|
||||
trailingComma: "none", // 不添加对象和数组最后一个元素的逗号
|
||||
bracketSpacing: false, // 对象中对空格和空行进行处理
|
||||
bracketSameLine: false, // 在多行JSX元素的最后一行追加 >
|
||||
jsxBracketSameLine: false, // 在多行JSX元素的最后一行追加 >
|
||||
requirePragma: false, // 是否严格按照文件顶部的特殊注释格式化代码
|
||||
insertPragma: false, // 是否在格式化的文件顶部插入Pragma标记,以表明该文件被prettier格式化过了
|
||||
proseWrap: "preserve", // 按照文件原样折行
|
||||
|
||||
@@ -7,6 +7,8 @@ module.exports = {
|
||||
ignoreFiles: ["node_modules", "test", "dist", "**/*.js"],
|
||||
rules: {
|
||||
"no-descending-specificity": null,
|
||||
"color-hex-case": "lower",
|
||||
"color-hex-length": "short",
|
||||
"at-rule-no-unknown": null,
|
||||
"block-no-empty": null,
|
||||
"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",
|
||||
"release": "aiot release --enable-jsc --enable-protobuf",
|
||||
"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); }\""
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
+6
-11
@@ -4,21 +4,16 @@ const os = require('os');
|
||||
const fs = require('fs');
|
||||
|
||||
// 注入buildinfo
|
||||
let gitCommitHash = "unknown";
|
||||
try {
|
||||
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 gitCommitHash = childProcess.execSync('git rev-parse HEAD').toString().trim();
|
||||
const username = os.userInfo().username;
|
||||
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 = `
|
||||
export const GIT_COMMIT_HASH = "${gitCommitHash.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}";
|
||||
export const GIT_COMMIT_HASH = "${gitCommitHash}";
|
||||
export const BUILD_TIME = "${buildTime}";
|
||||
export const BUILD_USER = "${username.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}";
|
||||
export const DESIGN_WIDTH = ${JSON.stringify(designWidth)};
|
||||
export const BUILD_USER = "${username}";
|
||||
export const DESIGN_WIDTH = ${designWidth};
|
||||
`;
|
||||
|
||||
const buildInfoPath = path.resolve(__dirname, 'src/buildinfo.ts');
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import subprocess
|
||||
import os
|
||||
import buildtools
|
||||
|
||||
DESIGN_WIDTH = 432
|
||||
|
||||
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
|
||||
|
||||
DESIGN_WIDTH = 466
|
||||
|
||||
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
|
||||
else:
|
||||
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:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
@@ -25,8 +25,10 @@ def getBuildTime():
|
||||
return formatted_datetime
|
||||
|
||||
def readFileToJson(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.loads(f.read())
|
||||
f = open(path, "r")
|
||||
f = f.read()
|
||||
f = json.loads(f)
|
||||
return f
|
||||
|
||||
def writeJsonToFile(path, obj):
|
||||
with open(path, "w+", encoding="utf-8") as f:
|
||||
|
||||
+2
-18
@@ -63,15 +63,7 @@ class SequenceAnim extends Anim {
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (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);
|
||||
}
|
||||
clearInterval(this.intervalId!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,15 +124,7 @@ class DomAnim extends Anim {
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (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);
|
||||
}
|
||||
clearInterval(this.intervalId!);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-23
@@ -42,39 +42,16 @@ export default {
|
||||
global.settings = settings
|
||||
|
||||
console.log("appinit > initSavedContentManager")
|
||||
try {
|
||||
await savedcontent.SavedContentManager.initialize()
|
||||
} catch (e) {
|
||||
console.error("Failed to init SavedContentManager:", e)
|
||||
}
|
||||
global.savedcontent = savedcontent
|
||||
|
||||
console.log("appinit > initDeviceInfo")
|
||||
try {
|
||||
global.DEVICE_INFO = await tools.getDeviceInformation()
|
||||
} catch (e) {
|
||||
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")
|
||||
try {
|
||||
this.bgimg.Init()
|
||||
} catch (e) {
|
||||
console.error("Failed to init bgimg:", e)
|
||||
}
|
||||
global.bgimg = this.bgimg
|
||||
|
||||
console.log("appinit > createBiliClient")
|
||||
|
||||
+1
-2
@@ -35,7 +35,6 @@ export function PatchArticleContent(doms: any) {
|
||||
if (!dom) continue; // 避免空节点
|
||||
|
||||
if (dom.type === "img") {
|
||||
if (!dom.attributes || !dom.attributes.src) continue;
|
||||
// 如果图片的 src 包含 .png 或 .jpg 扩展名
|
||||
if (dom.attributes.src.includes(".png") || dom.attributes.src.includes(".jpg")) {
|
||||
// 确保 src 包含 http:// 或 https:// 前缀
|
||||
@@ -47,7 +46,7 @@ export function PatchArticleContent(doms: any) {
|
||||
|
||||
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._patched_sign = "large_picture_scaled"
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ class FileAPI {
|
||||
file.access({
|
||||
...options,
|
||||
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({
|
||||
url,
|
||||
responseType: responseType,
|
||||
header: this.getHeaders(),
|
||||
timeout: 15000
|
||||
header: this.getHeaders()
|
||||
});
|
||||
if (!response || !response.data) return void 0;
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
global.logger.error(`GET请求失败,详细数据:`, error);
|
||||
@@ -88,10 +86,8 @@ export const BilibiliClientAPIRequestMethods = {
|
||||
responseType: 'json',
|
||||
method: 'POST',
|
||||
data,
|
||||
header: headers,
|
||||
timeout: 15000
|
||||
header: headers
|
||||
});
|
||||
if (!response || !response.data) return void 0;
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
global.logger.error(`POST请求失败,详细数据:`, error);
|
||||
|
||||
@@ -7,14 +7,6 @@ export const BilibiliClientFavFolderMethods = {
|
||||
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> {
|
||||
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["Origin"] = "https://message.bilibili.com"
|
||||
headers["Referer"] = "https://message.bilibili.com/"
|
||||
headers["Content-Length"] = body.length.toString()
|
||||
|
||||
const response = await this.postRequestWbi(url, {
|
||||
w_sender_uid: this.accountInfo.mid,
|
||||
w_receiver_id: receiver_id,
|
||||
|
||||
@@ -20,7 +20,7 @@ export const BilibiliClientVideoActionMethods = {
|
||||
let defaultFolderID = 0;
|
||||
const folders = await this.getUserFavouriteFolders(this.accountInfo.mid);
|
||||
folders.list.forEach((folder: any) => {
|
||||
if (folder.attr === 1 || folder.id === 0) {
|
||||
if (folder.title === "默认收藏夹") {
|
||||
defaultFolderID = folder.id;
|
||||
}
|
||||
});
|
||||
@@ -38,56 +38,4 @@ export const BilibiliClientVideoActionMethods = {
|
||||
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>
|
||||
|
||||
<h2>2. 免责声明</h2>
|
||||
<div>
|
||||
<p>
|
||||
<p><strong>服务稳定性</strong>:我们将尽力维护客户端的正常运行,但不保证其无故障、无中断或随时可用。由于哔哩哔哩API变更、网络故障、服务器问题或其他不可抗力因素导致的服务中断、数据丢失或功能异常,AstralSight Studios不承担任何责任。</p>
|
||||
<p><strong>第三方内容</strong>:本客户端依赖哔哩哔哩平台提供的内容,AstralSight Studios不对任何第三方内容的准确性、合法性、完整性或适用性做出任何承诺或保证,用户需自行判断并承担使用风险。</p>
|
||||
<p><strong>责任限制</strong>:在任何情况下,AstralSight Studios不对用户因使用或无法使用本客户端导致的任何间接、附带、特殊或惩罚性损害承担责任。</p>
|
||||
<p><strong>不可抗力</strong>:对于因自然灾害、战争、网络攻击、政府行为、哔哩哔哩API关闭或变更等不可抗力事件造成的服务中断或损失,AstralSight Studios不承担任何责任。</p>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<h2>3. 账号安全与使用</h2>
|
||||
<p>用户通过哔哩哔哩官方App扫码登录并使用其哔哩哔哩账号。本客户端不会保存用户登录密码,但会保存用户的登录Token、与哔哩哔哩API交互产生的Cookies等,但不对其安全性(包括但不限于:存储安全性、传输安全性)负任何责任。用户需对其账号安全负责。如果因用户原因导致账号被盗或发生其他安全问题,AstralSight Studios不承担责任。</p>
|
||||
|
||||
<h2>4. 用户行为规范</h2>
|
||||
<p>用户在使用本客户端时应遵守国家法律法规、哔哩哔哩平台的相关规定,以及本协议中的条款。用户不得利用本客户端进行包括但不限于以下行为:</p>
|
||||
<div>
|
||||
<p>
|
||||
<p>传播非法、侵权、骚扰、诽谤、淫秽、欺诈性内容;</p>
|
||||
<p>侵犯他人知识产权、隐私权、名誉权等合法权益;</p>
|
||||
<p>进行批量数据抓取、自动化访问或未经授权的网络攻击;</p>
|
||||
<p>任何其他违反哔哩哔哩平台规则或法律法规的行为。</p>
|
||||
</div>
|
||||
</p>
|
||||
<p>对于上述行为,AstralSight Studios有权立即中止或终止用户的使用权限,且不承担任何法律责任。</p>
|
||||
|
||||
<h2>5. 开源许可</h2>
|
||||
@@ -39,29 +39,29 @@ export const eula = ` <h1>澎湃哔哩(Hyperbili)用户协议</h1>
|
||||
|
||||
<h2>1. 信息的收集</h2>
|
||||
<p>在使用本客户端的过程中,我们可能会收集但不限于以下信息:</p>
|
||||
<div>
|
||||
<p>
|
||||
<p><strong>设备信息</strong>:包括设备型号、操作系统、设备标识符等;</p>
|
||||
<p><strong>网络信息</strong>:包括IP地址、网络提供商、连接时长等;</p>
|
||||
<p><strong>操作日志</strong>:包括用户操作行为、功能使用情况、访问页面记录等;</p>
|
||||
<p><strong>其他数据</strong>:随着产品功能的扩展,我们可能收集更多种类的用户数据,具体收集内容将在未来版本的隐私政策中进行更新。</p>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<h2>2. 数据的使用</h2>
|
||||
<p>我们将收集到的用户数据用于以下目的:</p>
|
||||
<div>
|
||||
<p>
|
||||
<p>优化和改进客户端的功能与用户体验;</p>
|
||||
<p>分析用户行为,提升产品的使用效果;</p>
|
||||
<p>保障客户端的安全,防止恶意攻击和非法活动;</p>
|
||||
<p>满足法律法规要求,或配合相关部门的合法请求。</p>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<h2>3. 数据的共享</h2>
|
||||
<p>我们不会主动将用户的个人数据分享给任何第三方,除非在以下情形下:</p>
|
||||
<div>
|
||||
<p>
|
||||
<p>因任何不可抗力(包括但不限于 因资金短缺造成无法负担起数据存储费用、遭受网络攻击等)导致数据必须分享给第三方以进行包括但不限于数据存储、CDN分发、异地容灾等操作;</p>
|
||||
<p>遵守法律法规的要求,或响应执法机关、法院的合法请求;</p>
|
||||
<p>在维护客户端的合法权益时,为保护AstralSight Studios免受潜在侵害,可能会与相关合作伙伴或技术提供商共享必要数据。</p>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<h2>4. 数据的保护</h2>
|
||||
<p>我们将采取合理的技术手段和管理措施,保护用户数据免受未经授权的访问、泄露、篡改或破坏。然而,互联网环境中无法完全消除所有风险,用户应知晓并承担这些潜在风险。AstralSight Studios不对因用户行为或外部因素导致的数据泄露或损失负责。</p>
|
||||
|
||||
+2
-1
@@ -1,3 +1,5 @@
|
||||
const date = new Date()
|
||||
|
||||
const tips = [
|
||||
"你所热爱的,就是你热爱的",
|
||||
"让我猜猜你现在使用的设备是...?",
|
||||
@@ -30,7 +32,6 @@ const AprilFoolsDayTips = [
|
||||
]
|
||||
|
||||
export function getTips(): string{
|
||||
const date = new Date()
|
||||
// 愚人节特供
|
||||
if(date.getMonth() === 3 && date.getDate() === 1){
|
||||
return AprilFoolsDayTips[Math.floor(Math.random() * AprilFoolsDayTips.length)]
|
||||
|
||||
+1
-6
@@ -4,7 +4,7 @@ export async function Jump() {
|
||||
storage.get({
|
||||
key: "bilibili_account",
|
||||
success: async (bilibili_account) => {
|
||||
if (!bilibili_account || bilibili_account.length < 1) {
|
||||
if (bilibili_account.length < 1) {
|
||||
router.replace({
|
||||
uri: "pages/app/entry/login"
|
||||
})
|
||||
@@ -13,11 +13,6 @@ export async function Jump() {
|
||||
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(' ');
|
||||
|
||||
// 在控制台输出日志
|
||||
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
|
||||
console.log(`%c[${timestamp}] [${level.toUpperCase()}] ${message}`, style);
|
||||
|
||||
// 同步日志到文件
|
||||
this.flushLogToFile(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"logLevel": "warn",
|
||||
"logLevel": "log",
|
||||
"designWidth": "device-width",
|
||||
"background": {
|
||||
"features": [
|
||||
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
onInit() {
|
||||
// 来自子组件push的object传递会变成字符串
|
||||
// 因此需要进行类型检查和手动parse
|
||||
if (typeof this.dyn !== "object") {
|
||||
if (typeof this.dyn != Object) {
|
||||
this.dyn = JSON.parse(this.dyn)
|
||||
console.log(this.dyn)
|
||||
}
|
||||
|
||||
@@ -48,8 +48,7 @@ export default {
|
||||
},
|
||||
replymode: false,
|
||||
reply_target: null,
|
||||
scrclass: "",
|
||||
input_text: { content: "" }
|
||||
scrclass: ""
|
||||
},
|
||||
async SendReply(evt) {
|
||||
this.replymode = false
|
||||
@@ -63,19 +62,19 @@ export default {
|
||||
let result
|
||||
try {
|
||||
result = this.secmode
|
||||
? await global.biliclient.GiveSecReply(
|
||||
this.type,
|
||||
this.oid,
|
||||
this.reply_target,
|
||||
content
|
||||
)
|
||||
: await global.biliclient.GiveTreeReply(
|
||||
? await global.biliclient.GiveTreeReply(
|
||||
this.type,
|
||||
this.oid,
|
||||
this.reply_target,
|
||||
this.rootrpid,
|
||||
content
|
||||
)
|
||||
: await global.biliclient.GiveSecReply(
|
||||
this.type,
|
||||
this.oid,
|
||||
this.reply_target,
|
||||
content
|
||||
)
|
||||
|
||||
if (result.code === 0) {
|
||||
prompt.showToast({message: "发送成功"})
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
import router from "@system.router"
|
||||
export default {
|
||||
private: {
|
||||
hotwords: [],
|
||||
hotwords: {},
|
||||
showHotwordsList: false,
|
||||
input_mode: false,
|
||||
anims: {
|
||||
|
||||
@@ -50,7 +50,7 @@ export default {
|
||||
user: {},
|
||||
stat: {},
|
||||
navnum: {},
|
||||
masterpiece: [],
|
||||
masterpiece: {},
|
||||
dynamiclist: {},
|
||||
anims: {
|
||||
show_loading: false
|
||||
|
||||
@@ -138,18 +138,6 @@
|
||||
import prompt from "@system.prompt";
|
||||
import request from "@system.request";
|
||||
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 {
|
||||
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: {
|
||||
@@ -413,9 +401,10 @@
|
||||
that.audioReady = true;
|
||||
}
|
||||
};
|
||||
// ontimeupdate is already set in bindAudioEvents() with pendingSongId guard.
|
||||
// Do NOT overwrite it here — the bindAudioEvents version handles both
|
||||
// playerState.playDuration and updateLyric().
|
||||
audio.ontimeupdate = () => {
|
||||
that.progress = audio.percent;
|
||||
that.updateLyric();
|
||||
};
|
||||
if (hasNewPlaylist || !fromAppStart) {
|
||||
if (that.videoMode) {
|
||||
} else {
|
||||
|
||||
@@ -53,72 +53,3 @@
|
||||
.vidtoolimg {
|
||||
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>
|
||||
<full-screen-input style="position: absolute" if="{{replymode}}" @send="SendReply"
|
||||
@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>
|
||||
</template>
|
||||
|
||||
@@ -81,7 +65,7 @@ import {kmeansCircleGradientImage} from "@src/image/jpegkmeans.ts"
|
||||
|
||||
export default {
|
||||
public: {
|
||||
bvid: "" // 视频BVID
|
||||
bvid: 1 // 视频BVID
|
||||
},
|
||||
private: {
|
||||
vid: {},
|
||||
@@ -93,9 +77,7 @@ export default {
|
||||
starsrc: "/common/vidtool_star.png",
|
||||
replymode: false,
|
||||
renderingCover: false,
|
||||
scrclass: "",
|
||||
favFolderPickerVisible: false,
|
||||
favFolders: []
|
||||
scrclass: ""
|
||||
},
|
||||
computed: {
|
||||
stat_view() {
|
||||
@@ -199,7 +181,7 @@ export default {
|
||||
},
|
||||
async SendReply(evt) {
|
||||
this.replymode = false
|
||||
if (!evt.detail.content || evt.detail.content.length === 0) {
|
||||
if (evt.detail.content === 0) {
|
||||
return prompt.showToast({
|
||||
message: "评论内容不得为空!",
|
||||
duration: 2000
|
||||
@@ -238,83 +220,11 @@ export default {
|
||||
})
|
||||
},
|
||||
async AddToFav() {
|
||||
if (this.stared) {
|
||||
// 已收藏:从所有包含此视频的收藏夹中删除
|
||||
await this.ConfirmUnstarAll()
|
||||
} 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})
|
||||
const result = await global.biliclient.starVideoToDefaultFavFolderByBVID(this.bvid)
|
||||
const message = result ? "收藏失败,请重试,或重新登录后再试" : "收藏成功"
|
||||
prompt.showToast({message})
|
||||
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) {
|
||||
// 该Feature在现在任何设备上运行都会卡死,故注释
|
||||
/*
|
||||
|
||||
+1
-2
@@ -35,8 +35,7 @@ async function loadStorageIndex(): Promise<void> {
|
||||
const fileExists = await asyncFile.access({ uri: indexFileUri });
|
||||
if (fileExists) {
|
||||
const indexData = await asyncFile.readText({ uri: indexFileUri });
|
||||
const parsed = JSON.parse(indexData);
|
||||
storageIndex = Array.isArray(parsed) ? parsed : [];
|
||||
storageIndex = JSON.parse(indexData);
|
||||
} else {
|
||||
storageIndex = [];
|
||||
}
|
||||
|
||||
@@ -34,15 +34,11 @@ export function loadSettings(): void {
|
||||
key: 'settings',
|
||||
success: function (data) {
|
||||
if (data) {
|
||||
try {
|
||||
const storedSettings = JSON.parse(data);
|
||||
SETTINGS = {
|
||||
...SETTINGS,
|
||||
...storedSettings
|
||||
};
|
||||
} catch (e) {
|
||||
global.logger.warn('Failed to parse settings, using defaults:', e);
|
||||
}
|
||||
}
|
||||
global.logger.log('Settings loaded:', SETTINGS);
|
||||
},
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { device, network, router } from "./tsimports"
|
||||
|
||||
export function formatNumber(num: number): string {
|
||||
if (num == null || isNaN(num)) return "0";
|
||||
if (num < 0) return "0";
|
||||
if (num < 1000) {
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
+1
-6
@@ -18,7 +18,7 @@ function runVmPoolGC(){
|
||||
for(var name in global.vmPool){
|
||||
if(global.vmPool[name]){
|
||||
if(global.vmPool[name].$valid){
|
||||
continue
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,20 +46,15 @@ export function InitPage(vm){
|
||||
|
||||
export function OnBackPressTriggered(){
|
||||
GlobalActions.UpdateCurrentPageName()
|
||||
if (global.vmPool[global.currentPageName]) {
|
||||
global.vmPool[global.currentPageName].scrclass = "scroll-backanim"
|
||||
}
|
||||
setTimeout(() => {
|
||||
GlobalActions.ClearCurrentVmSrcClass()
|
||||
router.back()
|
||||
|
||||
var pageStack = router.getPages()
|
||||
if (pageStack.length < 2) return
|
||||
var lastPageName = pageStack[pageStack.length - 2].name
|
||||
global.logger.log("lastpage=", lastPageName, "currentPage=", global.currentPageName)
|
||||
if (global.vmPool[lastPageName]) {
|
||||
global.vmPool[lastPageName].scrclass = ""
|
||||
global.vmPool[lastPageName].scrclass = "scroll-frombackanim"
|
||||
}
|
||||
}, 150)
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import dayjs from "dayjs"
|
||||
import { fetch } from "./tsimports"
|
||||
|
||||
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 = "https://tracker.hyperbili.astralsight.space/trackreport"
|
||||
const TRACKER_URL: string = "http://192.168.1.247:4080/trackreport"
|
||||
const TRACKER_SERVER_PROTOCOL_VERSION = "v1"
|
||||
|
||||
interface TrackedInfoUploadPacket {
|
||||
|
||||
Reference in New Issue
Block a user