dsh-shlr-bilitoolkit 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -2
- package/README.md +99 -68
- package/dist/apis/dynamic.d.ts +7 -0
- package/dist/apis/dynamic.js +60 -0
- package/dist/apis/profile.d.ts +4 -0
- package/dist/apis/profile.js +34 -0
- package/dist/dsh.d.ts +2 -0
- package/dist/dsh.js +136 -64
- package/dist/endpoints.d.ts +2 -0
- package/dist/endpoints.js +2 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
package/README.md
CHANGED
|
@@ -1,98 +1,129 @@
|
|
|
1
1
|
# dsh-shlr-bilitoolkit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
0.1.15-0.1.19为工具懒加载版,
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
B 站(哔哩哔哩)接口封装的 **TypeScript** 移植版,作为 [dsh](https://dshmp.com)(DeepSeek Harness)插件提供「视频 / 搜索 / 空间」等读取能力。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> 本项目从原 Python 工具箱(`bili/`)完整移植而来,覆盖视频、搜索、空间、评论、私信、互动(点赞/收藏/投币/关注/三连)、动态、专栏、关注关系、消息中心、个人资料与扫码登录,并作为 [dsh](https://dshmp.com)(DeepSeek Harness)插件提供上述能力。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
# 已发布到 npm
|
|
11
|
-
dsh plugin --profile <name> add dsh-shlr-bilitoolkit@latest
|
|
9
|
+
## 目录结构
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
```
|
|
12
|
+
dsh-shlr-bilitoolkit/
|
|
13
|
+
├── src/
|
|
14
|
+
│ ├── client.ts # BiliClient:Session / Cookie / 请求 / 节流 / 重试 / 风控 / 表单编码
|
|
15
|
+
│ ├── wbi.ts # WbiSigner:w_rid / wts 签名(含官方 mixin_key 算法)
|
|
16
|
+
│ ├── errors.ts # BiliApiError / CaptchaRequired / CookieError
|
|
17
|
+
│ ├── models.ts # Video / Article / Comment / PrivateMessage / RelationUser /
|
|
18
|
+
│ │ # Dynamic / Notice / User / Page / PlayInfo / Subtitle / Collection
|
|
19
|
+
│ ├── formatting.ts # 播放量、时长、HTML 清理等展示格式化
|
|
20
|
+
│ ├── pagination.ts # iterPages:统一翻页异步生成器
|
|
21
|
+
│ ├── endpoints.ts # 所有接口 URL 常量
|
|
22
|
+
│ ├── apis/
|
|
23
|
+
│ │ ├── video.ts # 推荐流 / 详情 / 分P / 播放地址 / 字幕
|
|
24
|
+
│ │ ├── search.ts # 搜索视频 / 用户
|
|
25
|
+
│ │ ├── space.ts # 空间信息 / 投稿 / 合集
|
|
26
|
+
│ │ ├── comment.ts # 评论:视频/动态/专栏 的读取与发表/回复/删除
|
|
27
|
+
│ │ ├── message.ts # 私信:发送 / 撤回 / 记录 / 未读
|
|
28
|
+
│ │ ├── interact.ts # 点赞 / 收藏 / 投币 / 关注 / 三连
|
|
29
|
+
│ │ ├── dynamic.ts # 动态:关注流 / 空间 / 发布 / 转发 / 点赞 / 删除
|
|
30
|
+
│ │ ├── article.ts # 专栏:详情 / UP 主列表
|
|
31
|
+
│ │ ├── relation.ts # 关注 / 粉丝 / 统计
|
|
32
|
+
│ │ ├── notify.ts # 消息中心:未读 / 回复我的 / @我的 / 收到的赞
|
|
33
|
+
│ │ ├── profile.ts # 个人资料:读取 / 改签名 / 改昵称性别生日
|
|
34
|
+
│ │ ├── auth.ts # 扫码登录(generate / poll / wait / login)
|
|
35
|
+
│ │ └── download.ts # 下载与 ffmpeg 合并工具
|
|
36
|
+
│ ├── dsh.ts # dsh 插件入口:apply(ctx) + 工具列表
|
|
37
|
+
│ └── index.ts # 对外导出
|
|
38
|
+
├── tests/ # 离线单测(wbi 签名、格式化、模型)
|
|
39
|
+
├── cordis.patch.yml # dsh bundle 的配置补丁
|
|
40
|
+
├── dsh-manifest.json # 市场目录收录清单(schema v1)
|
|
41
|
+
├── package.json # 含 dsh.bundle.patch 声明
|
|
42
|
+
└── tsconfig.json
|
|
16
43
|
```
|
|
17
44
|
|
|
18
|
-
|
|
45
|
+
## 构建与测试
|
|
19
46
|
|
|
20
47
|
```bash
|
|
21
|
-
npm install
|
|
22
|
-
#
|
|
23
|
-
|
|
48
|
+
npm install
|
|
49
|
+
npm run typecheck # tsc --noEmit
|
|
50
|
+
npm test # 离线单测(node --test + tsx)
|
|
51
|
+
npm run build # 产出 dist/
|
|
24
52
|
```
|
|
25
53
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
对齐官方 cordis 规范:`src/dsh.ts` 导出 `name` / `inject = ["tools"]` / `apply(ctx)`,在 `apply` 内用官方 `ctx.tools.register(defineTool({ name, description, parameters, output, execute }))` 注册每个工具。`defineTool` 来自 `@deepseek-ai/dsh-tools`,由宿主提供、插件不打包(保证单实例),已在 `peerDependencies` 声明。`cordis.patch.yml` 为组合包配置层(`- insert: - id: bili name: dsh-shlr-bilitoolkit`,按包名解析到 `main`)。较旧、没有 `ctx.tools` 的 dsh 仍保留 `ctx.registerTool` / `ctx.services.tools.register` 回退。
|
|
54
|
+
> 说明:`tsconfig` 使用 `moduleResolution: Bundler`,相对导入不带扩展名,适合被 dsh 运行时(基于打包器的 cordis 应用)消费。若要在原生 Node ESM 下直接 `node dist/index.js`,需改用 `NodeNext` 并补 `.js` 扩展名。
|
|
29
55
|
|
|
30
|
-
##
|
|
56
|
+
## 当作库用
|
|
31
57
|
|
|
32
|
-
|
|
58
|
+
```ts
|
|
59
|
+
import { BiliClient } from "dsh-shlr-bilitoolkit";
|
|
33
60
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- **写操作**(均会真实修改账号状态,需登录):
|
|
37
|
-
`bili_like` · `bili_follow` · `bili_favorite` · `bili_send_message` · `bili_triple` · `bili_dynamic_publish` · `bili_dynamic_repost` · `bili_coin` · `bili_fav_folder_create` · `bili_fav_folder_delete` · `bili_dynamic_like` · `bili_dynamic_delete` · `bili_comment_send` · `bili_comment_reply` · `bili_comment_delete` · `bili_comment_like` · `bili_message_recall` · `bili_profile_update`
|
|
38
|
-
- **辅助**:`bili_login_status`(查登录态)· `bili_gen_qr`(生成登录二维码)· `bili_cache_clear`(清空本地缓存)
|
|
61
|
+
const client = new BiliClient({ cookieFile: "bili_cookies.json" });
|
|
62
|
+
await client.loadCookies();
|
|
39
63
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
| `bili_favorite` | `action=add\|remove` |
|
|
48
|
-
| `bili_video_subtitle` | `list=true` 时列字幕轨,否则取正文 |
|
|
49
|
-
| `bili_comment_*` | `target=video\|dynamic\|article`,`id`(bvid / 动态 id / 专栏 cv 号) |
|
|
50
|
-
| `bili_profile_update` | `uname` / `sex` / `birthday` / `sign`(至少一项) |
|
|
64
|
+
const videos = await client.video.recommend(2, 10);
|
|
65
|
+
const detail = await client.video.detail("BV1xx411c7mD");
|
|
66
|
+
const text = await client.video.subtitleText("BV1xx411c7mD"); // 字幕纯文本
|
|
67
|
+
const [videos2, total] = await client.search.videos("孤独摇滚");
|
|
68
|
+
const info = await client.space.info(574115689);
|
|
69
|
+
const [ups, _] = await client.space.videos(574115689, 1, 10);
|
|
70
|
+
```
|
|
51
71
|
|
|
52
|
-
##
|
|
72
|
+
## 作为 dsh 插件
|
|
53
73
|
|
|
54
|
-
|
|
74
|
+
- 包名 `dsh-shlr-bilitoolkit`(符合 `dsh-` 前缀约定),`package.json` 里声明了 `dsh.bundle.patch: ./cordis.patch.yml`,因此会被 dsh 识别为插件而非普通库。
|
|
75
|
+
- 入口 `src/dsh.ts` 导出 `apply(ctx)`,注册一组工具(读取为主,写操作明确标注「需登录 / 谨慎」):
|
|
76
|
+
- 读取:`bili_recommend` / `bili_video_detail` / `bili_video_subtitle` / `bili_search_video` / `bili_search_user` / `bili_space_info` / `bili_space_videos` / `bili_article_view` / `bili_article_list` / `bili_dynamic_feed` / `bili_dynamic_space` / `bili_comment_list` / `bili_relation_followings` / `bili_relation_followers` / `bili_notify_unread` / `bili_notify_replies` / `bili_fav_folders` / `bili_video_pagelist` / `bili_video_subtitles` / `bili_video_playurl` / `bili_video_status` / `bili_space_collections` / `bili_space_collection` / `bili_fav_folder_videos` / `bili_dynamic_detail` / `bili_notify_ats` / `bili_notify_likes` / `bili_message_unread` / `bili_message_history` / `bili_relation_stat` / `bili_user_following` / `bili_profile_info` / `bili_comment_replies`
|
|
77
|
+
- 写操作:`bili_like` / `bili_follow` / `bili_favorite` / `bili_send_comment` / `bili_send_message` / `bili_triple` / `bili_dynamic_publish` / `bili_dynamic_repost` / `bili_coin` / `bili_unfavorite` / `bili_fav_folder_create` / `bili_fav_folder_delete` / `bili_dynamic_like` / `bili_dynamic_delete` / `bili_comment_send` / `bili_comment_reply` / `bili_comment_delete` / `bili_comment_like` / `bili_message_recall` / `bili_profile_update_sign` / `bili_profile_update`(均会真实修改账号状态,需登录)
|
|
78
|
+
- 辅助:`bili_login_status`(查登录态)/ `bili_gen_qr`(生成扫码登录二维码)/ `bili_cache_clear`(清空本地缓存)
|
|
79
|
+
- 工具读取 `BILI_COOKIE_FILE`(默认 `bili_data/bili_cookies.json`)作为登录态。
|
|
80
|
+
- **数据目录**:Cookie / 缓存 / 二维码 / 设备号统一放在一个目录里,默认 `bili_data/`(相对 dsh 工作目录),避免散落污染工作目录。可用 `BILI_DATA_DIR` 改目录;`BILI_COOKIE_FILE` / `BILI_CACHE_FILE` / `BILI_QR_FILE` / `BILI_DEVICE_FILE` 可精确指定单个文件。设备号(私信 `dev_id`)会落盘到 `bili_data/bili_device.json`,跨重启保持稳定,避免被 B 站当成新设备触发风控。兼容旧版:若新目录还没有 Cookie、而旧位置(`./bili_cookies.json`)有,会继续沿用旧的。
|
|
81
|
+
- **扫码登录(独立获取 Cookie)**:运行 `npm run login`,终端会打印 ASCII 二维码,用「哔哩哔哩」App 扫码并确认后,Cookie 自动写入 `bili_data/bili_cookies.json`。也可在对话里先调 `bili_login_status` 查登录态、调 `bili_gen_qr` 拿二维码链接与可直接展示的 `ascii` 二维码。写操作(点赞 / 收藏 / 投币 / 关注 / 评论 / 私信 / 三连)都依赖该 Cookie 中的 `SESSDATA` / `bili_jct`。
|
|
82
|
+
- **缓存持久化**:除 Cookie 外,客户端还会把 WBI 签名密钥(`B 站每天轮换`)、`bvid→aid` 映射这类稳定 / 带 TTL 的数据写入本地 JSON 缓存(`BILI_CACHE_FILE`,默认 `bili_data/bili_cache.json`),用临时文件 + 原子替换落盘、文件损坏会自动自愈。冷启动或跨进程时直接复用,避免每次都请求 `nav` 接口;密钥过期或失效时 `refresh()` 会重新拉取并覆盖缓存。若签名反复报 `-403/-352`,可调用 `bili_cache_clear` 强制清空缓存后重试(不影响 Cookie / 登录态)。
|
|
83
|
+
- **计费 / 缓存与懒加载**:默认**稳定注册全部工具**,让 prompt 前缀保持不变、最大化 DeepSeek 前缀缓存命中(未命中价 ≈ 命中价 50 倍,中途改工具列表反而更贵)。列表类工具默认**精简返回**(`full=true` 取全量)。如确认提供商不做前缀缓存、想极致压缩每轮 schema,设 `BILI_LAZY=1` 开启懒加载(常驻核心 + `bili_open` 按需开组,下一轮生效)。
|
|
55
84
|
|
|
56
|
-
|
|
57
|
-
| --- | --- |
|
|
58
|
-
| `bili_cookies.json` | 登录 Cookie(`SESSDATA` / `bili_jct`) |
|
|
59
|
-
| `bili_cache.json` | WBI 签名密钥 + `bvid→aid` 缓存(原子写、损坏自愈) |
|
|
60
|
-
| `bili_qrcode.png` | 最近一次生成的登录二维码 |
|
|
61
|
-
| `bili_device.json` | 私信设备号 `dev_id`(跨重启稳定,降低风控) |
|
|
85
|
+
> **给 Agent 的约定**:仓库根目录的 [`AGENTS.md`](./AGENTS.md) 写明了登录流程——需要登录时必须调用 `bili_gen_qr` 并把二维码**图片**展示给用户,而不是只给链接或 ASCII。把这份 `AGENTS.md` 放到 dsh 的工作目录(或确保你的 dsh 会读取它),agent 就会按约定输出登录二维码。
|
|
62
86
|
|
|
63
|
-
|
|
87
|
+
安装到 dsh(任选其一,取决于你的发行方式):
|
|
64
88
|
|
|
65
|
-
|
|
89
|
+
```bash
|
|
90
|
+
dsh plugin --profile <name> add dsh-shlr-bilitoolkit # 已发布到 npm
|
|
91
|
+
dsh plugin --profile <name> add github:your-name/dsh-shlr-bilitoolkit#<ref>
|
|
92
|
+
```
|
|
66
93
|
|
|
67
|
-
|
|
94
|
+
> 插件入口已对齐 **官方 DeepSeek Harness(cordis 体系)** 的约定:模块导出 `name` / `inject = ["tools"]` / `apply(ctx)`,并在 `apply` 内用官方 `ctx.tools.register(defineTool({ name, description, parameters, output, execute }))` 注册每个工具(`defineTool` 来自 `@deepseek-ai/dsh-tools`,由宿主提供、插件不打包,保证单实例)。`parameters` 用官方简化 schema(`{ 参数名: { type, required, description, enum? } }`),工具返回规范 JSON 值,由 `output.render` 转成面向模型的文本。`cordis.patch.yml` 是官方组合包配置层(`- insert: - id: bili name: dsh-shlr-bilitoolkit`,按包名解析到 `main` 入口)。若你的 dsh 版本较旧、没有 `ctx.tools`,`apply` 仍保留了 `ctx.registerTool` / `ctx.services.tools.register` 的回退路径。
|
|
68
95
|
|
|
69
|
-
##
|
|
96
|
+
## 与原 Python 版的对应关系
|
|
70
97
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
98
|
+
| Python | TS |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| `bili/client.py` | `src/client.ts` |
|
|
101
|
+
| `bili/wbi.py` | `src/wbi.ts` |
|
|
102
|
+
| `bili/video.py` | `src/apis/video.ts` |
|
|
103
|
+
| `bili/search.py` | `src/apis/search.ts` |
|
|
104
|
+
| `bili/space.py` | `src/apis/space.ts` |
|
|
105
|
+
| `bili/comment.py` | `src/apis/comment.ts` |
|
|
106
|
+
| `bili/message.py` | `src/apis/message.ts` |
|
|
107
|
+
| `bili/interact.py` | `src/apis/interact.ts` |
|
|
108
|
+
| `bili/dynamic.py` | `src/apis/dynamic.ts` |
|
|
109
|
+
| `bili/article.py` | `src/apis/article.ts` |
|
|
110
|
+
| `bili/relation.py` | `src/apis/relation.ts` |
|
|
111
|
+
| `bili/notify.py` | `src/apis/notify.ts` |
|
|
112
|
+
| `bili/profile.py` | `src/apis/profile.ts` |
|
|
113
|
+
| `bili/auth.py` | `src/apis/auth.ts` |
|
|
114
|
+
| `bili/download.py` | `src/apis/download.ts` |
|
|
115
|
+
| `bili/models.py` | `src/models.ts` |
|
|
116
|
+
| `bili/formatting.py` | `src/formatting.ts` |
|
|
117
|
+
| `bili/pagination.py` | `src/pagination.ts` |
|
|
118
|
+
|
|
119
|
+
约定保持一致:`client.xxx.yyy()` 调用风格、`iter_*` 异步生成器、`toDict()` 导出、WBI 签名算法与密钥 6 小时缓存(已落地到 `bili_cache.json`,跨进程复用)。
|
|
89
120
|
|
|
90
121
|
## 与原 Python 版的差异 / 已知限制
|
|
91
122
|
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
- **下载 / ffmpeg**:`src/apis/download.ts`
|
|
123
|
+
- **写接口为表单编码**:原 Python 用 `requests` 的 `data=` 默认表单;本包的 `client.post` 默认把对象编码为 `application/x-www-form-urlencoded`,仅动态点赞等少数接口显式走 JSON(`jsonBody=true`)。
|
|
124
|
+
- **图文动态**:`bili_dynamic_publish` 支持「文字 + 本地图片」(先上传 B 站动态图床拿到 url/尺寸,再带 `pictures` 以 type=2 创建)。上传 / pictures 组装按社区通用接口实现,**未在真实账号验证**;若失败请把报错码反馈回来校准。
|
|
125
|
+
- **下载 / ffmpeg**:`src/apis/download.ts` 提供 `downloadUrl` / `mergeWithFfmpeg`,为纯工具函数,未接入 dsh 工具(下载行为在 dsh 里通常不合适)。
|
|
95
126
|
|
|
96
127
|
## 风控说明
|
|
97
128
|
|
|
98
|
-
|
|
129
|
+
本包内置与原 Python 版一致的基础降风控策略:请求节流(默认 0.6s + 0~0.4s 抖动)、GET 退避重试、写操作不重试、HTTP 412 / `-412` 立即停手、WBI 签名。但单账号操作量上限、避免短时间切 IP/UA 等仍需使用习惯配合。
|
package/dist/apis/dynamic.d.ts
CHANGED
|
@@ -15,6 +15,13 @@ export declare class DynamicAPI {
|
|
|
15
15
|
iterFeed(maxPages?: number, type?: string): AsyncGenerator<Dynamic>;
|
|
16
16
|
iterSpace(uid: number, maxPages?: number): AsyncGenerator<Dynamic>;
|
|
17
17
|
publish(text: string, atUids?: number[], ctrl?: any[], closeComment?: boolean): Promise<string>;
|
|
18
|
+
/** 上传单张图片到 B 站动态图床,返回 pictures 需要的一段信息。 */
|
|
19
|
+
private uploadImage;
|
|
20
|
+
/** 发布图文动态(文字 + 最多若干张图片)。图片可以是本地路径或 Buffer。 */
|
|
21
|
+
publishWithImages(text: string, images: Array<string | Buffer>, opts?: {
|
|
22
|
+
atUids?: number[];
|
|
23
|
+
closeComment?: boolean;
|
|
24
|
+
}): Promise<string>;
|
|
18
25
|
repost(dynamicId: string | number, text?: string): Promise<string>;
|
|
19
26
|
thumb(dynamicId: string | number, liked?: boolean): Promise<void>;
|
|
20
27
|
delete(dynamicId: string | number): Promise<void>;
|
package/dist/apis/dynamic.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
读取走网页版 polymer 接口,需要登录态。写操作(publish / repost / thumb /
|
|
4
4
|
delete)都是公开行为,受风控和人机验证限制,使用前请自行确认。
|
|
5
5
|
*/
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { basename } from "node:path";
|
|
6
8
|
import * as endpoints from "../endpoints.js";
|
|
7
9
|
import { BiliApiError } from "../errors.js";
|
|
8
10
|
import { Dynamic } from "../models.js";
|
|
@@ -106,6 +108,64 @@ export class DynamicAPI {
|
|
|
106
108
|
const payload = await this.client.post(endpoints.DYNAMIC_CREATE, form, { Referer: REFERER });
|
|
107
109
|
return DynamicAPI.dynamicId(payload);
|
|
108
110
|
}
|
|
111
|
+
/** 上传单张图片到 B 站动态图床,返回 pictures 需要的一段信息。 */
|
|
112
|
+
async uploadImage(img) {
|
|
113
|
+
let bytes;
|
|
114
|
+
let name = "image.png";
|
|
115
|
+
if (Buffer.isBuffer(img)) {
|
|
116
|
+
bytes = img;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
bytes = await readFile(String(img));
|
|
120
|
+
name = basename(String(img));
|
|
121
|
+
}
|
|
122
|
+
// 动态图床上传是 multipart,直接用底层 axios(client.post 会做表单编码,不适合二进制)
|
|
123
|
+
const form = new FormData();
|
|
124
|
+
form.append("biz", "draw");
|
|
125
|
+
form.append("csrf", this.client.csrf);
|
|
126
|
+
form.append("file_up", new Blob([bytes]), name);
|
|
127
|
+
const resp = await this.client.http.post(endpoints.DYNAMIC_IMAGE_UPLOAD, form, { headers: { Referer: REFERER } });
|
|
128
|
+
const data = resp?.data ?? {};
|
|
129
|
+
const d = data.data ?? {};
|
|
130
|
+
if (data.code !== 0 || !d.url) {
|
|
131
|
+
throw new BiliApiError(`图片上传失败: ${data.message ?? "接口未返回 url"}`, data.code, d);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
img_src: String(d.url),
|
|
135
|
+
img_width: Number(d.image_width ?? d.width ?? 0),
|
|
136
|
+
img_height: Number(d.image_height ?? d.height ?? 0),
|
|
137
|
+
// 接口有时不返回体积,用字节数兜底(KB)
|
|
138
|
+
img_size: Number(d.img_size ?? Math.round(bytes.length / 1024)),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** 发布图文动态(文字 + 最多若干张图片)。图片可以是本地路径或 Buffer。 */
|
|
142
|
+
async publishWithImages(text, images, opts = {}) {
|
|
143
|
+
text = (text || "").trim();
|
|
144
|
+
const pics = [];
|
|
145
|
+
for (const img of images ?? []) {
|
|
146
|
+
pics.push(await this.uploadImage(img));
|
|
147
|
+
}
|
|
148
|
+
if (!text && !pics.length)
|
|
149
|
+
throw new Error("动态内容不能为空");
|
|
150
|
+
const extension = { emoji_type: 1, from: { emoji_type: 1 }, flag_cfg: {} };
|
|
151
|
+
const form = {
|
|
152
|
+
dynamic_id: 0,
|
|
153
|
+
type: pics.length ? 2 : 4, // 2 = 图文,4 = 纯文字
|
|
154
|
+
rid: 0,
|
|
155
|
+
content: text,
|
|
156
|
+
up_choose_comment: 0,
|
|
157
|
+
up_close_comment: opts.closeComment ? 1 : 0,
|
|
158
|
+
extension: JSON.stringify(extension),
|
|
159
|
+
at_uids: (opts.atUids ?? []).map(String).join(","),
|
|
160
|
+
ctrl: JSON.stringify([]),
|
|
161
|
+
csrf: this.client.csrf,
|
|
162
|
+
csrf_token: this.client.csrf,
|
|
163
|
+
};
|
|
164
|
+
if (pics.length)
|
|
165
|
+
form["pictures"] = JSON.stringify(pics);
|
|
166
|
+
const payload = await this.client.post(endpoints.DYNAMIC_CREATE, form, { Referer: REFERER });
|
|
167
|
+
return DynamicAPI.dynamicId(payload);
|
|
168
|
+
}
|
|
109
169
|
async repost(dynamicId, text = "") {
|
|
110
170
|
const form = {
|
|
111
171
|
dynamic_id: String(Math.floor(Number(dynamicId))),
|
package/dist/apis/profile.d.ts
CHANGED
|
@@ -12,4 +12,8 @@ export declare class ProfileAPI {
|
|
|
12
12
|
avatarUrl(): Promise<string>;
|
|
13
13
|
updateSign(sign: string): Promise<void>;
|
|
14
14
|
updateProfile(uname?: string, sex?: string, birthday?: string): Promise<void>;
|
|
15
|
+
/** 上传头像图片到 B 站图床,返回可用的图片 URL。 */
|
|
16
|
+
private uploadFaceUrl;
|
|
17
|
+
/** 更换头像:先上传图片拿 URL,再调 face/update。img 为本地路径或 Buffer。 */
|
|
18
|
+
changeAvatar(img: string | Buffer): Promise<void>;
|
|
15
19
|
}
|
package/dist/apis/profile.js
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
- updateSign 用的是 x/member/web/sign/update,改完进入审核队列。
|
|
5
5
|
- updateProfile 用的是 x/member/web/update,只支持昵称、性别、生日。
|
|
6
6
|
*/
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import { basename } from "node:path";
|
|
7
9
|
import * as endpoints from "../endpoints.js";
|
|
10
|
+
import { BiliApiError } from "../errors.js";
|
|
8
11
|
const SEX_VALUES = ["男", "女", "保密"];
|
|
9
12
|
const MAX_SIGN_LENGTH = 70;
|
|
10
13
|
export class ProfileAPI {
|
|
@@ -56,4 +59,35 @@ export class ProfileAPI {
|
|
|
56
59
|
}
|
|
57
60
|
await this.client.post(endpoints.MEMBER_PROFILE_UPDATE, form);
|
|
58
61
|
}
|
|
62
|
+
/** 上传头像图片到 B 站图床,返回可用的图片 URL。 */
|
|
63
|
+
async uploadFaceUrl(img) {
|
|
64
|
+
let bytes;
|
|
65
|
+
let name = "avatar.png";
|
|
66
|
+
if (Buffer.isBuffer(img)) {
|
|
67
|
+
bytes = img;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
bytes = await readFile(String(img));
|
|
71
|
+
name = basename(String(img));
|
|
72
|
+
}
|
|
73
|
+
const form = new FormData();
|
|
74
|
+
form.append("biz", "draw");
|
|
75
|
+
form.append("csrf", this.client.csrf);
|
|
76
|
+
form.append("file_up", new Blob([bytes]), name);
|
|
77
|
+
const resp = await this.client.http.post(endpoints.DYNAMIC_IMAGE_UPLOAD, form, { headers: { Referer: "https://space.bilibili.com/" } });
|
|
78
|
+
const data = resp?.data ?? {};
|
|
79
|
+
if (data.code !== 0 || !data.data?.url) {
|
|
80
|
+
throw new BiliApiError(`头像图片上传失败: ${data.message ?? "接口未返回 url"}`, data.code, data.data);
|
|
81
|
+
}
|
|
82
|
+
return String(data.data.url);
|
|
83
|
+
}
|
|
84
|
+
/** 更换头像:先上传图片拿 URL,再调 face/update。img 为本地路径或 Buffer。 */
|
|
85
|
+
async changeAvatar(img) {
|
|
86
|
+
const face = await this.uploadFaceUrl(img);
|
|
87
|
+
await this.client.post(endpoints.MEMBER_FACE_UPDATE, {
|
|
88
|
+
face,
|
|
89
|
+
csrf: this.client.csrf,
|
|
90
|
+
csrf_token: this.client.csrf,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
59
93
|
}
|
package/dist/dsh.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface DshTool {
|
|
|
30
30
|
* 这里统一处理:丢 undefined、NaN/Infinity 转 null、bigint 转字符串。
|
|
31
31
|
*/
|
|
32
32
|
export declare function toLosslessJson(value: unknown): unknown;
|
|
33
|
+
/** 列表工具统一出口:full=true 返回完整 toDict,否则瘦身。 */
|
|
34
|
+
export declare function listPayload(items: any[], full: boolean, keep?: string[]): any[];
|
|
33
35
|
export declare const TOOLS: DshTool[];
|
|
34
36
|
export declare const CORE_IDS: string[];
|
|
35
37
|
export declare const GROUPS: Record<string, {
|
package/dist/dsh.js
CHANGED
|
@@ -109,21 +109,51 @@ export function toLosslessJson(value) {
|
|
|
109
109
|
}
|
|
110
110
|
return value; // string / boolean / null / undefined
|
|
111
111
|
}
|
|
112
|
+
// ---- 列表返回瘦身 ----
|
|
113
|
+
// 工具结果会进历史、之后每轮重发,所以列表默认只返回关键字段 + 更少条数;
|
|
114
|
+
// full=true 时返回完整 toDict。HEAVY_KEYS 是各类模型里普遍偏长 / 噪音的字段。
|
|
115
|
+
const HEAVY_KEYS = [
|
|
116
|
+
"cover", "cover_url", "description", "desc", "avatar", "avatar_url",
|
|
117
|
+
"face", "pic", "first_frame", "badge", "badge_url", "jump_url",
|
|
118
|
+
];
|
|
119
|
+
const VIDEO_KEEP = [
|
|
120
|
+
"bvid", "title", "author", "view", "danmaku", "duration", "pubdate", "link",
|
|
121
|
+
];
|
|
122
|
+
function slimDict(d, keep) {
|
|
123
|
+
if (keep) {
|
|
124
|
+
const out = {};
|
|
125
|
+
for (const k of keep)
|
|
126
|
+
if (k in d)
|
|
127
|
+
out[k] = d[k];
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
const out = {};
|
|
131
|
+
for (const [k, v] of Object.entries(d))
|
|
132
|
+
if (!HEAVY_KEYS.includes(k))
|
|
133
|
+
out[k] = v;
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
/** 列表工具统一出口:full=true 返回完整 toDict,否则瘦身。 */
|
|
137
|
+
export function listPayload(items, full, keep) {
|
|
138
|
+
const dicts = items.map((x) => (x && typeof x.toDict === "function" ? x.toDict() : x));
|
|
139
|
+
return full ? dicts : dicts.map((d) => slimDict(d, keep));
|
|
140
|
+
}
|
|
112
141
|
export const TOOLS = [
|
|
113
142
|
{
|
|
114
143
|
id: "bili_recommend",
|
|
115
|
-
description: "获取 B 站首页推荐视频流,返回视频列表(标题 / UP主 / 播放量 /
|
|
144
|
+
description: "获取 B 站首页推荐视频流,返回视频列表(标题 / UP主 / 播放量 / 链接)。默认精简字段,full=true 返回完整字段。",
|
|
116
145
|
parameters: {
|
|
117
146
|
type: "object",
|
|
118
147
|
properties: {
|
|
119
148
|
pages: { type: "integer", description: "抓取页数", default: 1 },
|
|
120
149
|
pageSize: { type: "integer", description: "每页条数", default: 10 },
|
|
150
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
121
151
|
},
|
|
122
152
|
},
|
|
123
153
|
handler: async (a) => {
|
|
124
154
|
const c = await getClient();
|
|
125
155
|
const videos = await c.video.recommend(Number(a.pages ?? 1), Number(a.pageSize ?? 10));
|
|
126
|
-
return videos.
|
|
156
|
+
return listPayload(videos, Boolean(a.full ?? false), VIDEO_KEEP);
|
|
127
157
|
},
|
|
128
158
|
},
|
|
129
159
|
{
|
|
@@ -162,7 +192,7 @@ export const TOOLS = [
|
|
|
162
192
|
},
|
|
163
193
|
{
|
|
164
194
|
id: "bili_search",
|
|
165
|
-
description: "搜索 B
|
|
195
|
+
description: "搜索 B 站视频或用户。默认精简字段,full=true 返回完整字段。",
|
|
166
196
|
parameters: {
|
|
167
197
|
type: "object",
|
|
168
198
|
properties: {
|
|
@@ -174,19 +204,21 @@ export const TOOLS = [
|
|
|
174
204
|
default: "totalrank",
|
|
175
205
|
description: "排序(仅视频)",
|
|
176
206
|
},
|
|
177
|
-
pageSize: { type: "integer", default:
|
|
207
|
+
pageSize: { type: "integer", default: 10 },
|
|
208
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
178
209
|
},
|
|
179
210
|
required: ["keyword"],
|
|
180
211
|
},
|
|
181
212
|
handler: async (a) => {
|
|
182
213
|
const c = await getClient();
|
|
183
|
-
const
|
|
214
|
+
const full = Boolean(a.full ?? false);
|
|
215
|
+
const ps = Number(a.pageSize ?? 10);
|
|
184
216
|
if (String(a.type ?? "video") === "user") {
|
|
185
217
|
const [users, total] = await c.search.users(a.keyword, 1, ps);
|
|
186
|
-
return { total, users: users
|
|
218
|
+
return { total, users: listPayload(users, full) };
|
|
187
219
|
}
|
|
188
220
|
const [videos, total] = await c.search.videos(a.keyword, 1, ps, a.order ?? "totalrank");
|
|
189
|
-
return { total, videos: videos
|
|
221
|
+
return { total, videos: listPayload(videos, full, VIDEO_KEEP) };
|
|
190
222
|
},
|
|
191
223
|
},
|
|
192
224
|
{
|
|
@@ -204,7 +236,7 @@ export const TOOLS = [
|
|
|
204
236
|
},
|
|
205
237
|
{
|
|
206
238
|
id: "bili_space_videos",
|
|
207
|
-
description: "获取某 UP
|
|
239
|
+
description: "获取某 UP 主的投稿视频列表。默认精简字段,full=true 返回完整字段。",
|
|
208
240
|
parameters: {
|
|
209
241
|
type: "object",
|
|
210
242
|
properties: {
|
|
@@ -214,14 +246,15 @@ export const TOOLS = [
|
|
|
214
246
|
enum: ["pubdate", "click", "stow"],
|
|
215
247
|
default: "pubdate",
|
|
216
248
|
},
|
|
217
|
-
pageSize: { type: "integer", default:
|
|
249
|
+
pageSize: { type: "integer", default: 10 },
|
|
250
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
218
251
|
},
|
|
219
252
|
required: ["uid"],
|
|
220
253
|
},
|
|
221
254
|
handler: async (a) => {
|
|
222
255
|
const c = await getClient();
|
|
223
|
-
const [videos, total] = await c.space.videos(Number(a.uid), 1, Number(a.pageSize ??
|
|
224
|
-
return { total, videos: videos.
|
|
256
|
+
const [videos, total] = await c.space.videos(Number(a.uid), 1, Number(a.pageSize ?? 10), a.order ?? "pubdate");
|
|
257
|
+
return { total, videos: listPayload(videos, Boolean(a.full ?? false), VIDEO_KEEP) };
|
|
225
258
|
},
|
|
226
259
|
},
|
|
227
260
|
// ---- 专栏 ----
|
|
@@ -237,70 +270,76 @@ export const TOOLS = [
|
|
|
237
270
|
},
|
|
238
271
|
{
|
|
239
272
|
id: "bili_article_list",
|
|
240
|
-
description: "获取某 UP
|
|
273
|
+
description: "获取某 UP 主的专栏列表。默认精简字段,full=true 返回完整字段。",
|
|
241
274
|
parameters: {
|
|
242
275
|
type: "object",
|
|
243
276
|
properties: {
|
|
244
277
|
mid: { type: "integer" },
|
|
245
|
-
pageSize: { type: "integer", default:
|
|
278
|
+
pageSize: { type: "integer", default: 10 },
|
|
279
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
246
280
|
},
|
|
247
281
|
required: ["mid"],
|
|
248
282
|
},
|
|
249
283
|
handler: async (a) => {
|
|
250
284
|
const c = await getClient();
|
|
251
|
-
const [articles, total] = await c.article.listByUser(Number(a.mid), 1, Number(a.pageSize ??
|
|
252
|
-
return { total, articles: articles.
|
|
285
|
+
const [articles, total] = await c.article.listByUser(Number(a.mid), 1, Number(a.pageSize ?? 10));
|
|
286
|
+
return { total, articles: listPayload(articles, Boolean(a.full ?? false)) };
|
|
253
287
|
},
|
|
254
288
|
},
|
|
255
289
|
// ---- 动态 ----
|
|
256
290
|
{
|
|
257
291
|
id: "bili_dynamic_feed",
|
|
258
|
-
description: "
|
|
292
|
+
description: "获取「关注」动态流(需登录),返回动态列表。默认精简字段,full=true 返回完整字段。",
|
|
259
293
|
parameters: {
|
|
260
294
|
type: "object",
|
|
261
295
|
properties: {
|
|
262
296
|
page: { type: "integer", default: 1 },
|
|
263
297
|
type: { type: "string", default: "all" },
|
|
298
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
264
299
|
},
|
|
265
300
|
},
|
|
266
301
|
handler: async (a) => {
|
|
267
302
|
const c = await getClient();
|
|
268
303
|
const [items, offset, hasMore] = await c.dynamic.feed(Number(a.page ?? 1), undefined, a.type ?? "all");
|
|
269
|
-
return { offset, hasMore, dynamics: items.
|
|
304
|
+
return { offset, hasMore, dynamics: listPayload(items, Boolean(a.full ?? false)) };
|
|
270
305
|
},
|
|
271
306
|
},
|
|
272
307
|
{
|
|
273
308
|
id: "bili_dynamic_space",
|
|
274
|
-
description: "
|
|
309
|
+
description: "获取某用户的动态列表。默认精简字段,full=true 返回完整字段。",
|
|
275
310
|
parameters: {
|
|
276
311
|
type: "object",
|
|
277
|
-
properties: {
|
|
312
|
+
properties: {
|
|
313
|
+
uid: { type: "integer" },
|
|
314
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
315
|
+
},
|
|
278
316
|
required: ["uid"],
|
|
279
317
|
},
|
|
280
318
|
handler: async (a) => {
|
|
281
319
|
const c = await getClient();
|
|
282
320
|
const [items, offset, hasMore, total] = await c.dynamic.space(Number(a.uid));
|
|
283
|
-
return { total, offset, hasMore, dynamics: items.
|
|
321
|
+
return { total, offset, hasMore, dynamics: listPayload(items, Boolean(a.full ?? false)) };
|
|
284
322
|
},
|
|
285
323
|
},
|
|
286
324
|
// ---- 评论 ----
|
|
287
325
|
{
|
|
288
326
|
id: "bili_comment_list",
|
|
289
|
-
description: "获取评论列表(视频 / 动态 /
|
|
327
|
+
description: "获取评论列表(视频 / 动态 / 专栏)。默认精简字段,full=true 返回完整字段。",
|
|
290
328
|
parameters: {
|
|
291
329
|
type: "object",
|
|
292
330
|
properties: {
|
|
293
331
|
target: { type: "string", enum: ["video", "dynamic", "article"], default: "video" },
|
|
294
332
|
id: { type: "string", description: "视频 bvid / 动态 id / 专栏 cv 号" },
|
|
295
333
|
sort: { type: "string", enum: ["time", "like", "reply"], default: "time" },
|
|
296
|
-
pageSize: { type: "integer", default:
|
|
334
|
+
pageSize: { type: "integer", default: 10 },
|
|
335
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
297
336
|
},
|
|
298
337
|
required: ["id"],
|
|
299
338
|
},
|
|
300
339
|
handler: async (a) => {
|
|
301
340
|
const c = await getClient();
|
|
302
341
|
const target = String(a.target ?? "video");
|
|
303
|
-
const ps = Number(a.pageSize ??
|
|
342
|
+
const ps = Number(a.pageSize ?? 10);
|
|
304
343
|
const sort = a.sort ?? "time";
|
|
305
344
|
let items;
|
|
306
345
|
if (target === "dynamic")
|
|
@@ -309,28 +348,29 @@ export const TOOLS = [
|
|
|
309
348
|
[items] = await c.comment.listArticle(a.id, 1, ps, sort);
|
|
310
349
|
else
|
|
311
350
|
[items] = await c.comment.listVideo(a.id, 1, ps, sort);
|
|
312
|
-
return { comments: items.
|
|
351
|
+
return { comments: listPayload(items, Boolean(a.full ?? false)) };
|
|
313
352
|
},
|
|
314
353
|
},
|
|
315
354
|
// ---- 关注关系 ----
|
|
316
355
|
{
|
|
317
356
|
id: "bili_relation_list",
|
|
318
|
-
description: "
|
|
357
|
+
description: "获取某用户的关注列表或粉丝列表。默认精简字段,full=true 返回完整字段。",
|
|
319
358
|
parameters: {
|
|
320
359
|
type: "object",
|
|
321
360
|
properties: {
|
|
322
361
|
uid: { type: "integer" },
|
|
323
362
|
type: { type: "string", enum: ["followings", "followers"], default: "followings" },
|
|
324
|
-
pageSize: { type: "integer", default:
|
|
363
|
+
pageSize: { type: "integer", default: 10 },
|
|
364
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
325
365
|
},
|
|
326
366
|
required: ["uid"],
|
|
327
367
|
},
|
|
328
368
|
handler: async (a) => {
|
|
329
369
|
const c = await getClient();
|
|
330
370
|
const [users, total] = String(a.type ?? "followings") === "followers"
|
|
331
|
-
? await c.relation.followers(Number(a.uid), 1, Number(a.pageSize ??
|
|
332
|
-
: await c.relation.followings(Number(a.uid), 1, Number(a.pageSize ??
|
|
333
|
-
return { total, users: users.
|
|
371
|
+
? await c.relation.followers(Number(a.uid), 1, Number(a.pageSize ?? 10))
|
|
372
|
+
: await c.relation.followings(Number(a.uid), 1, Number(a.pageSize ?? 10));
|
|
373
|
+
return { total, users: listPayload(users, Boolean(a.full ?? false)) };
|
|
334
374
|
},
|
|
335
375
|
},
|
|
336
376
|
// ---- 消息中心 ----
|
|
@@ -434,11 +474,15 @@ export const TOOLS = [
|
|
|
434
474
|
// ---- 动态(写操作,需登录,会公开展示在主页) ----
|
|
435
475
|
{
|
|
436
476
|
id: "bili_dynamic_publish",
|
|
437
|
-
description: "
|
|
477
|
+
description: "发布动态:纯文字,或文字+图片(写操作,需登录,会公开展示在你的主页)。图片传本地文件路径(多个用逗号 / 换行分隔)。返回动态 id 与链接。",
|
|
438
478
|
parameters: {
|
|
439
479
|
type: "object",
|
|
440
480
|
properties: {
|
|
441
|
-
content: { type: "string", description: "
|
|
481
|
+
content: { type: "string", description: "动态正文(可与图片同时,也可仅发图片)" },
|
|
482
|
+
images: {
|
|
483
|
+
type: "string",
|
|
484
|
+
description: "本地图片路径,多个用逗号 / 换行分隔(可选,带图即发图文动态)",
|
|
485
|
+
},
|
|
442
486
|
atUids: {
|
|
443
487
|
type: "string",
|
|
444
488
|
description: "要 @ 的用户 uid,多个用逗号分隔(可选)",
|
|
@@ -449,7 +493,6 @@ export const TOOLS = [
|
|
|
449
493
|
description: "是否关闭评论",
|
|
450
494
|
},
|
|
451
495
|
},
|
|
452
|
-
required: ["content"],
|
|
453
496
|
},
|
|
454
497
|
handler: async (a) => {
|
|
455
498
|
const c = await getClient();
|
|
@@ -457,7 +500,20 @@ export const TOOLS = [
|
|
|
457
500
|
.split(/[,,\s]+/)
|
|
458
501
|
.map((s) => Number(s.trim()))
|
|
459
502
|
.filter((n) => Number.isFinite(n) && n > 0);
|
|
460
|
-
const
|
|
503
|
+
const images = String(a.images ?? "")
|
|
504
|
+
.split(/[,,\n]+/)
|
|
505
|
+
.map((s) => s.trim())
|
|
506
|
+
.filter(Boolean);
|
|
507
|
+
const content = String(a.content ?? "");
|
|
508
|
+
if (!content.trim() && !images.length) {
|
|
509
|
+
throw new Error("动态内容不能为空:至少提供正文或图片");
|
|
510
|
+
}
|
|
511
|
+
const id = images.length
|
|
512
|
+
? await c.dynamic.publishWithImages(content, images, {
|
|
513
|
+
atUids,
|
|
514
|
+
closeComment: Boolean(a.closeComment ?? false),
|
|
515
|
+
})
|
|
516
|
+
: await c.dynamic.publish(content, atUids, undefined, Boolean(a.closeComment ?? false));
|
|
461
517
|
return { ok: true, dynamicId: id, url: `https://t.bilibili.com/${id}` };
|
|
462
518
|
},
|
|
463
519
|
},
|
|
@@ -545,37 +601,43 @@ export const TOOLS = [
|
|
|
545
601
|
},
|
|
546
602
|
{
|
|
547
603
|
id: "bili_space_collection",
|
|
548
|
-
description: "
|
|
604
|
+
description: "获取某个合集内的视频列表。默认精简字段,full=true 返回完整字段。",
|
|
549
605
|
parameters: {
|
|
550
606
|
type: "object",
|
|
551
607
|
properties: {
|
|
552
608
|
seasonId: { type: "integer" },
|
|
553
609
|
mid: { type: "integer", description: "UP 主 uid(可选,用于正确 Referer)" },
|
|
554
|
-
pageSize: { type: "integer", default:
|
|
610
|
+
pageSize: { type: "integer", default: 10 },
|
|
611
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
555
612
|
},
|
|
556
613
|
required: ["seasonId"],
|
|
557
614
|
},
|
|
558
615
|
handler: async (a) => {
|
|
559
|
-
const [col, videos, total] = await (await getClient()).space.collection(Number(a.seasonId), 1, Number(a.pageSize ??
|
|
560
|
-
return {
|
|
616
|
+
const [col, videos, total] = await (await getClient()).space.collection(Number(a.seasonId), 1, Number(a.pageSize ?? 10), a.mid != null ? Number(a.mid) : undefined);
|
|
617
|
+
return {
|
|
618
|
+
collection: col.toDict(),
|
|
619
|
+
total,
|
|
620
|
+
videos: listPayload(videos, Boolean(a.full ?? false), VIDEO_KEEP),
|
|
621
|
+
};
|
|
561
622
|
},
|
|
562
623
|
},
|
|
563
624
|
// ---- 收藏夹 ----
|
|
564
625
|
{
|
|
565
626
|
id: "bili_fav_folder_videos",
|
|
566
|
-
description: "
|
|
627
|
+
description: "获取某个收藏夹内的视频列表(需登录)。默认精简字段,full=true 返回完整字段。",
|
|
567
628
|
parameters: {
|
|
568
629
|
type: "object",
|
|
569
630
|
properties: {
|
|
570
631
|
folderId: { type: "integer" },
|
|
571
632
|
order: { type: "string", enum: ["mtime", "view", "pubtime"], default: "mtime" },
|
|
572
|
-
pageSize: { type: "integer", default:
|
|
633
|
+
pageSize: { type: "integer", default: 10 },
|
|
634
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
573
635
|
},
|
|
574
636
|
required: ["folderId"],
|
|
575
637
|
},
|
|
576
638
|
handler: async (a) => {
|
|
577
|
-
const [videos, total] = await (await getClient()).interact.folderVideos(Number(a.folderId), 1, Number(a.pageSize ??
|
|
578
|
-
return { total, videos: videos.
|
|
639
|
+
const [videos, total] = await (await getClient()).interact.folderVideos(Number(a.folderId), 1, Number(a.pageSize ?? 10), a.order ?? "mtime");
|
|
640
|
+
return { total, videos: listPayload(videos, Boolean(a.full ?? false), VIDEO_KEEP) };
|
|
579
641
|
},
|
|
580
642
|
},
|
|
581
643
|
{
|
|
@@ -708,14 +770,15 @@ export const TOOLS = [
|
|
|
708
770
|
},
|
|
709
771
|
{
|
|
710
772
|
id: "bili_comment_replies",
|
|
711
|
-
description: "
|
|
773
|
+
description: "获取某条评论下的楼中楼回复列表。默认精简字段,full=true 返回完整字段。",
|
|
712
774
|
parameters: {
|
|
713
775
|
type: "object",
|
|
714
776
|
properties: {
|
|
715
777
|
target: { type: "string", enum: ["video", "dynamic", "article"], default: "video" },
|
|
716
778
|
id: { type: "string" },
|
|
717
779
|
rootRpid: { type: "integer", description: "根评论 rpid" },
|
|
718
|
-
pageSize: { type: "integer", default:
|
|
780
|
+
pageSize: { type: "integer", default: 10 },
|
|
781
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
719
782
|
},
|
|
720
783
|
required: ["id", "rootRpid"],
|
|
721
784
|
},
|
|
@@ -723,7 +786,7 @@ export const TOOLS = [
|
|
|
723
786
|
const c = await getClient();
|
|
724
787
|
const target = String(a.target ?? "video");
|
|
725
788
|
const root = Number(a.rootRpid);
|
|
726
|
-
const ps = Number(a.pageSize ??
|
|
789
|
+
const ps = Number(a.pageSize ?? 10);
|
|
727
790
|
let items;
|
|
728
791
|
if (target === "video")
|
|
729
792
|
[items] = await c.comment.repliesVideo(a.id, root, 1, ps);
|
|
@@ -733,7 +796,7 @@ export const TOOLS = [
|
|
|
733
796
|
[items] = await c.comment.repliesArticle(a.id, root, 1, ps);
|
|
734
797
|
else
|
|
735
798
|
throw new Error("target 只支持 video / dynamic / article");
|
|
736
|
-
return { replies: items.
|
|
799
|
+
return { replies: listPayload(items, Boolean(a.full ?? false)) };
|
|
737
800
|
},
|
|
738
801
|
},
|
|
739
802
|
{
|
|
@@ -827,11 +890,12 @@ export const TOOLS = [
|
|
|
827
890
|
// ---- 消息中心补充 ----
|
|
828
891
|
{
|
|
829
892
|
id: "bili_notify_list",
|
|
830
|
-
description: "消息中心列表:回复我的 / @我的 /
|
|
893
|
+
description: "消息中心列表:回复我的 / @我的 / 收到的赞(需登录)。默认精简字段,full=true 返回完整字段。",
|
|
831
894
|
parameters: {
|
|
832
895
|
type: "object",
|
|
833
896
|
properties: {
|
|
834
897
|
type: { type: "string", enum: ["replies", "ats", "likes"], default: "replies" },
|
|
898
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
835
899
|
},
|
|
836
900
|
},
|
|
837
901
|
handler: async (a) => {
|
|
@@ -844,7 +908,7 @@ export const TOOLS = [
|
|
|
844
908
|
[items] = await c.notify.likes();
|
|
845
909
|
else
|
|
846
910
|
[items] = await c.notify.replies();
|
|
847
|
-
return { notices: items.
|
|
911
|
+
return { notices: listPayload(items, Boolean(a.full ?? false)) };
|
|
848
912
|
},
|
|
849
913
|
},
|
|
850
914
|
// ---- 私信补充 ----
|
|
@@ -856,18 +920,19 @@ export const TOOLS = [
|
|
|
856
920
|
},
|
|
857
921
|
{
|
|
858
922
|
id: "bili_message_history",
|
|
859
|
-
description: "
|
|
923
|
+
description: "获取与某用户的私信聊天记录(需登录)。默认精简字段,full=true 返回完整字段。",
|
|
860
924
|
parameters: {
|
|
861
925
|
type: "object",
|
|
862
926
|
properties: {
|
|
863
927
|
talkerUid: { type: "integer" },
|
|
864
|
-
size: { type: "integer", default:
|
|
928
|
+
size: { type: "integer", default: 10 },
|
|
929
|
+
full: { type: "boolean", default: false, description: "true 返回完整字段" },
|
|
865
930
|
},
|
|
866
931
|
required: ["talkerUid"],
|
|
867
932
|
},
|
|
868
933
|
handler: async (a) => {
|
|
869
|
-
const msgs = await (await getClient()).message.history(Number(a.talkerUid), Number(a.size ??
|
|
870
|
-
return { messages: msgs.
|
|
934
|
+
const msgs = await (await getClient()).message.history(Number(a.talkerUid), Number(a.size ?? 10));
|
|
935
|
+
return { messages: listPayload(msgs, Boolean(a.full ?? false)) };
|
|
871
936
|
},
|
|
872
937
|
},
|
|
873
938
|
{
|
|
@@ -906,7 +971,7 @@ export const TOOLS = [
|
|
|
906
971
|
},
|
|
907
972
|
{
|
|
908
973
|
id: "bili_profile_update",
|
|
909
|
-
description: "修改我的昵称 / 性别 / 生日 /
|
|
974
|
+
description: "修改我的昵称 / 性别 / 生日 / 签名 / 头像(写操作,需登录,至少改一项)。头像传本地图片路径。",
|
|
910
975
|
parameters: {
|
|
911
976
|
type: "object",
|
|
912
977
|
properties: {
|
|
@@ -914,19 +979,24 @@ export const TOOLS = [
|
|
|
914
979
|
sex: { type: "string", enum: ["男", "女", "保密"] },
|
|
915
980
|
birthday: { type: "string", description: "YYYY-MM-DD" },
|
|
916
981
|
sign: { type: "string", description: "个性签名(≤70 字)" },
|
|
982
|
+
avatar: { type: "string", description: "新头像的本地图片路径" },
|
|
917
983
|
},
|
|
918
984
|
},
|
|
919
985
|
handler: async (a) => {
|
|
920
986
|
const c = await getClient();
|
|
921
987
|
const hasProfile = a.uname != null || a.sex != null || a.birthday != null;
|
|
922
|
-
|
|
923
|
-
|
|
988
|
+
const hasSign = a.sign != null;
|
|
989
|
+
const hasAvatar = a.avatar != null && String(a.avatar).trim() !== "";
|
|
990
|
+
if (!hasSign && !hasProfile && !hasAvatar) {
|
|
991
|
+
throw new Error("至少修改一项:uname / sex / birthday / sign / avatar");
|
|
924
992
|
}
|
|
925
|
-
if (
|
|
993
|
+
if (hasSign)
|
|
926
994
|
await c.profile.updateSign(String(a.sign));
|
|
927
995
|
if (hasProfile) {
|
|
928
996
|
await c.profile.updateProfile(a.uname != null ? String(a.uname) : undefined, a.sex != null ? String(a.sex) : undefined, a.birthday != null ? String(a.birthday) : undefined);
|
|
929
997
|
}
|
|
998
|
+
if (hasAvatar)
|
|
999
|
+
await c.profile.changeAvatar(String(a.avatar));
|
|
930
1000
|
return { ok: true };
|
|
931
1001
|
},
|
|
932
1002
|
},
|
|
@@ -1227,21 +1297,23 @@ function registerOne(ctx, tool) {
|
|
|
1227
1297
|
}
|
|
1228
1298
|
}
|
|
1229
1299
|
export function apply(ctx) {
|
|
1230
|
-
//
|
|
1300
|
+
// 捕获工具注册表,供懒加载模式下的 bili_open 动态注册
|
|
1231
1301
|
toolsRegistry =
|
|
1232
1302
|
ctx?.tools && typeof ctx.tools.register === "function" ? ctx.tools : null;
|
|
1233
|
-
//
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
// BILI_LAZY=
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1303
|
+
// 默认:稳定注册全部工具——让 prompt 前缀保持不变、最大化 DeepSeek 前缀缓存命中。
|
|
1304
|
+
// (缓存未命中价 ≈ 命中价 50 倍;工具 schema 在前缀里是命中价、成本可忽略,
|
|
1305
|
+
// 中途改工具列表反而造成未命中、更贵。)只有确认提供商不做前缀缓存、
|
|
1306
|
+
// 想极致压缩每轮 schema 时,才用 BILI_LAZY=1 开启懒加载。
|
|
1307
|
+
const lazyEnabled = !!toolsRegistry &&
|
|
1308
|
+
(process.env.BILI_LAZY === "1" || process.env.BILI_LAZY === "true");
|
|
1309
|
+
if (!lazyEnabled) {
|
|
1240
1310
|
for (const t of TOOLS)
|
|
1241
1311
|
registerOne(ctx, t);
|
|
1242
1312
|
return;
|
|
1243
1313
|
}
|
|
1244
|
-
//
|
|
1314
|
+
// 懒加载模式:注册机制工具 + 常驻核心,其余按需 bili_open 打开
|
|
1315
|
+
for (const t of lazyTools)
|
|
1316
|
+
registerOne(ctx, t);
|
|
1245
1317
|
for (const id of CORE_IDS) {
|
|
1246
1318
|
const t = TOOL_BY_ID.get(id);
|
|
1247
1319
|
if (t)
|
package/dist/endpoints.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export declare const MEMBER_ACCOUNT = "https://api.bilibili.com/x/member/web/acc
|
|
|
39
39
|
export declare const MEMBER_MYINFO = "https://api.bilibili.com/x/space/myinfo";
|
|
40
40
|
export declare const MEMBER_SIGN_UPDATE = "https://api.bilibili.com/x/member/web/sign/update";
|
|
41
41
|
export declare const MEMBER_PROFILE_UPDATE = "https://api.bilibili.com/x/member/web/update";
|
|
42
|
+
export declare const MEMBER_FACE_UPDATE = "https://api.bilibili.com/x/member/web/face/update";
|
|
42
43
|
export declare const DYNAMIC_FEED_ALL = "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all";
|
|
43
44
|
export declare const DYNAMIC_FEED_SPACE = "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space";
|
|
44
45
|
export declare const DYNAMIC_DETAIL = "https://api.bilibili.com/x/polymer/web-dynamic/v1/detail";
|
|
@@ -46,6 +47,7 @@ export declare const DYNAMIC_CREATE = "https://api.vc.bilibili.com/dynamic_svr/v
|
|
|
46
47
|
export declare const DYNAMIC_REPUBLISH = "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/republish";
|
|
47
48
|
export declare const DYNAMIC_DELETE = "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/rm_dynamic";
|
|
48
49
|
export declare const DYNAMIC_THUMB = "https://api.bilibili.com/x/dynamic/feed/dyn/thumb";
|
|
50
|
+
export declare const DYNAMIC_IMAGE_UPLOAD = "https://api.bilibili.com/x/dynamic/feed/draw/upload_bfs";
|
|
49
51
|
export declare const REPLY_LIST = "https://api.bilibili.com/x/v2/reply";
|
|
50
52
|
export declare const REPLY_ADD = "https://api.bilibili.com/x/v2/reply/add";
|
|
51
53
|
export declare const REPLY_DEL = "https://api.bilibili.com/x/v2/reply/del";
|
package/dist/endpoints.js
CHANGED
|
@@ -50,6 +50,7 @@ export const MEMBER_ACCOUNT = `${API_BASE}/x/member/web/account`;
|
|
|
50
50
|
export const MEMBER_MYINFO = `${API_BASE}/x/space/myinfo`;
|
|
51
51
|
export const MEMBER_SIGN_UPDATE = `${API_BASE}/x/member/web/sign/update`;
|
|
52
52
|
export const MEMBER_PROFILE_UPDATE = `${API_BASE}/x/member/web/update`;
|
|
53
|
+
export const MEMBER_FACE_UPDATE = `${API_BASE}/x/member/web/face/update`;
|
|
53
54
|
// ---- 动态 ----
|
|
54
55
|
export const DYNAMIC_FEED_ALL = `${API_BASE}/x/polymer/web-dynamic/v1/feed/all`;
|
|
55
56
|
export const DYNAMIC_FEED_SPACE = `${API_BASE}/x/polymer/web-dynamic/v1/feed/space`;
|
|
@@ -58,6 +59,7 @@ export const DYNAMIC_CREATE = `${VC_BASE}/dynamic_svr/v1/dynamic_svr/create`;
|
|
|
58
59
|
export const DYNAMIC_REPUBLISH = `${VC_BASE}/dynamic_svr/v1/dynamic_svr/republish`;
|
|
59
60
|
export const DYNAMIC_DELETE = `${VC_BASE}/dynamic_svr/v1/dynamic_svr/rm_dynamic`;
|
|
60
61
|
export const DYNAMIC_THUMB = `${API_BASE}/x/dynamic/feed/dyn/thumb`;
|
|
62
|
+
export const DYNAMIC_IMAGE_UPLOAD = `${API_BASE}/x/dynamic/feed/draw/upload_bfs`;
|
|
61
63
|
// ---- 评论 ----
|
|
62
64
|
export const REPLY_LIST = `${API_BASE}/x/v2/reply`;
|
|
63
65
|
export const REPLY_ADD = `${API_BASE}/x/v2/reply/add`;
|