@zhin.js/plugin-bilibili 0.0.0-stage → 0.1.1
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/CHANGELOG.md +13 -0
- package/README.md +136 -2
- package/commands/bili/list/index.js +26 -0
- package/commands/bili/live/index.js +31 -0
- package/commands/bili/sub/index.js +68 -0
- package/commands/bili/unsub/index.js +37 -0
- package/lib/api.d.ts +68 -0
- package/lib/api.js +298 -0
- package/lib/api.js.map +1 -0
- package/lib/channel.d.ts +15 -0
- package/lib/channel.js +47 -0
- package/lib/channel.js.map +1 -0
- package/lib/config.d.ts +15 -0
- package/lib/config.js +35 -0
- package/lib/config.js.map +1 -0
- package/lib/format.d.ts +5 -0
- package/lib/format.js +42 -0
- package/lib/format.js.map +1 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -0
- package/lib/permission.d.ts +10 -0
- package/lib/permission.js +11 -0
- package/lib/permission.js.map +1 -0
- package/lib/poll.d.ts +47 -0
- package/lib/poll.js +264 -0
- package/lib/poll.js.map +1 -0
- package/lib/runtime.d.ts +22 -0
- package/lib/runtime.js +3 -0
- package/lib/runtime.js.map +1 -0
- package/lib/store.d.ts +52 -0
- package/lib/store.js +141 -0
- package/lib/store.js.map +1 -0
- package/lib/wbi.d.ts +5 -0
- package/lib/wbi.js +50 -0
- package/lib/wbi.js.map +1 -0
- package/package.json +104 -3
- package/plugin.js +65 -0
- package/schema.json +62 -0
- package/skills/bilibili/SKILL.md +46 -0
- package/skills/bilibili/tools/bili_list_subscriptions/index.js +34 -0
- package/skills/bilibili/tools/bili_live_status/index.js +39 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @zhin.js/plugin-bilibili
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c67e43d: 取消某 UP 主的最后一个订阅时,同步清理 watcher 中该 UP 的退避记录,避免已退订 UID 的条目残留。
|
|
8
|
+
- e40899f: Adapters now project group owners and admins onto the canonical `sender.roles` names `owner` / `admin` (Telegram creator/administrator, KOOK guild owner and admin-permission roles, Lark chat owner/managers, QQ official guild owner/admin identities). KOOK numeric role ids moved from `roles` to `permissions` (`role:<id>`). `@zhin.js/command` exports `canManageConversation` ("private chat or owner/admin/master"), now shared by the Bilibili and earthquake subscription commands; Roulette lets owners and admins toggle the mute penalty and cancel a game.
|
|
9
|
+
- 66fc5dd: Add `@zhin.js/plugin-bilibili`: per-conversation Bilibili live-start and UP dynamic subscriptions (`bili sub|unsub|list|live`), a conservative scheduled watcher with jitter, exponential backoff, baseline-first dedupe and WBI signing, database persistence with an in-memory fallback, and read-only agent tools `bili_live_status` / `bili_list_subscriptions`. In groups and channels `bili sub` / `bili unsub` are limited to the group owner, admins and the bot master; private chats can manage their own subscriptions.
|
|
10
|
+
- Updated dependencies [1a11207]
|
|
11
|
+
- Updated dependencies [9943df6]
|
|
12
|
+
- @zhin.js/tool@1.1.8
|
|
13
|
+
- @zhin.js/skill@1.1.8
|
package/README.md
CHANGED
|
@@ -1,3 +1,137 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @zhin.js/plugin-bilibili
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
订阅 B 站(哔哩哔哩)UP 主的**开播提醒**和**新动态**,推送到订阅所在的私聊、群聊或频道。与平台无关:任何接入了 OutboundHost 的适配器都可以用。
|
|
4
|
+
|
|
5
|
+
Subscribe to Bilibili live starts and UP dynamics per conversation. Platform-agnostic: works with any adapter that exposes the OutboundHost. English below.
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm exec zhin install @zhin.js/plugin-bilibili
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
对应的 `zhin.config.yml`:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
configVersion: 2
|
|
17
|
+
plugins:
|
|
18
|
+
bilibili: "@zhin.js/plugin-bilibili"
|
|
19
|
+
bilibili:
|
|
20
|
+
pollCron: "0 * * * * *" # 每 60 秒一轮
|
|
21
|
+
jitterMs: 15000 # 每轮随机延迟 0–15 秒
|
|
22
|
+
# cookie: ${BILIBILI_COOKIE} # 可选,风控严格时再配
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
推送需要 Runtime 提供 Schedule(定时任务)和 Outbound(主动发消息);订阅持久化需要配置数据库,没有数据库时订阅只保存在内存里,重启即丢失。
|
|
26
|
+
|
|
27
|
+
## 配置
|
|
28
|
+
|
|
29
|
+
| 字段 | 默认值 | 说明 |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| `pollCron` | `0 * * * * *` | 轮询频率(6 段 Cron),默认每 60 秒 |
|
|
32
|
+
| `jitterMs` | `15000` | 每轮开始前的随机延迟上限,避免整点并发 |
|
|
33
|
+
| `requestGapMs` | `1500` | 同一轮里相邻两个 UP 主的动态请求间隔 |
|
|
34
|
+
| `maxBackoffMinutes` | `30` | 失败后指数退避(1、2、4… 分钟)的上限;风控响应退避更久 |
|
|
35
|
+
| `maxPerChannel` | `20` | 每个会话最多订阅多少个 UP 主 |
|
|
36
|
+
| `timeout` | `10000` | 单次 HTTP 请求超时(毫秒) |
|
|
37
|
+
| `live` / `dynamic` | `true` | 分别开关开播轮询和动态轮询 |
|
|
38
|
+
| `cookie` | `""` | 可选 Cookie(如 `SESSDATA=...`)。写成 `${BILIBILI_COOKIE}`,把值放进 `.env`,不要提交到仓库 |
|
|
39
|
+
|
|
40
|
+
## 命令
|
|
41
|
+
|
|
42
|
+
| 命令 | 说明 |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `bili sub <uid> [all\|live\|dynamic]` | 订阅 UP 主,`uid` 也可以是空间链接;类型默认 `all`(开播 + 动态),也可写 `直播` / `动态`。对已订阅的 UP 主再执行一次可修改类型 |
|
|
45
|
+
| `bili unsub <uid>` | 取消当前会话的订阅 |
|
|
46
|
+
| `bili list` | 查看当前会话的订阅,以及正在退避的请求 |
|
|
47
|
+
| `bili live <房间号>` | 查询直播间状态,支持 `https://live.bilibili.com/...` 链接 |
|
|
48
|
+
|
|
49
|
+
订阅按「适配器 + 机器人账号 + 会话」隔离,不同群互不影响。
|
|
50
|
+
|
|
51
|
+
### 权限
|
|
52
|
+
|
|
53
|
+
- 群聊和频道里,`bili sub` / `bili unsub` 只有**群主、群管理员或机器人主人**(`role(owner,admin,master)`)能用,其他成员会收到提示,订阅不会改动。
|
|
54
|
+
- 私聊里任何人都可以订阅、取消自己这个私聊会话。
|
|
55
|
+
- `bili list`、`bili live` 是只读命令,不限制。
|
|
56
|
+
|
|
57
|
+
角色来自适配器给出的平台无关身份 `sender.role`(如 icqq / OneBot 的 `owner`、`admin`,以及主人账号的 `master`),判断用的是命令路由同一套内置 permit 检查。适配器拿不到群角色时,群里只有机器人主人能管理订阅。
|
|
58
|
+
|
|
59
|
+
## AI 工具
|
|
60
|
+
|
|
61
|
+
技能 `bilibili` 提供两个只读工具:
|
|
62
|
+
|
|
63
|
+
- `bili_live_status`:按直播间号或 UID 查询是否在播。
|
|
64
|
+
- `bili_list_subscriptions`:列出全部订阅。
|
|
65
|
+
|
|
66
|
+
订阅的增删只走聊天命令。
|
|
67
|
+
|
|
68
|
+
## 工作原理
|
|
69
|
+
|
|
70
|
+
- **开播**:所有被订阅的 UP 主合并成一次批量请求(`get_status_info_by_uids`,无需登录)。从「未开播 / 轮播」变为「直播中」,或者开播时间变化(说明是新的一场)时推送一次。
|
|
71
|
+
- **动态**:逐个请求 UP 主空间动态(`x/polymer/web-dynamic/v1/feed/space`,带 WBI 签名和匿名 `buvid3`),按发布时间和已推送 ID 双重去重;置顶的旧动态不会推送,单个 UP 主每轮最多推 3 条。直播推荐类动态交给开播提醒处理。
|
|
72
|
+
- **首次观察只记基线**:新订阅或重启后的第一轮不推送历史内容。
|
|
73
|
+
- **降级**:接口返回格式变了、风控(`-352`、`-412`、`-799`、HTTP 412)或网络错误时,不会让定时任务失败,只在日志里提示并对该来源指数退避,`bili list` 里能看到恢复时间。风控时会丢弃缓存的 WBI 密钥和设备 ID,下次重新获取。
|
|
74
|
+
|
|
75
|
+
## 数据库
|
|
76
|
+
|
|
77
|
+
| 表 | 用途 |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `bilibili_subscriptions` | 订阅:`uid`、`uname`、`kinds`、会话地址、创建人 |
|
|
80
|
+
| `bilibili_watch_state` | 每个 UP 主的开播状态、动态游标、最近推送过的动态 ID |
|
|
81
|
+
|
|
82
|
+
取消最后一个订阅时会清掉对应 UP 主的游标。
|
|
83
|
+
|
|
84
|
+
## 注意事项
|
|
85
|
+
|
|
86
|
+
- 这些都是 B 站网页端的非公开接口,随时可能变化。动态接口对匿名请求和机房 IP 风控很严:可能返回 412 / -352,也可能悄悄返回空列表。插件把空列表当成「本轮没拿到」,不会据此建立基线或推送旧内容;超过 6 小时的动态只记为已读、不推送。长期拿不到动态时,可以配置登录后的 `cookie`,或只开 `live`。
|
|
87
|
+
- 请保持保守的轮询频率,不要把 `pollCron` 调到几秒一次。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## English
|
|
92
|
+
|
|
93
|
+
### Install
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pnpm exec zhin install @zhin.js/plugin-bilibili
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
configVersion: 2
|
|
101
|
+
plugins:
|
|
102
|
+
bilibili: "@zhin.js/plugin-bilibili"
|
|
103
|
+
bilibili:
|
|
104
|
+
pollCron: "0 * * * * *"
|
|
105
|
+
# cookie: ${BILIBILI_COOKIE}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Pushing needs the Schedule and Outbound hosts; persistence needs a configured database (otherwise subscriptions are kept in memory).
|
|
109
|
+
|
|
110
|
+
### Commands
|
|
111
|
+
|
|
112
|
+
- `bili sub <uid> [all|live|dynamic]`: subscribe the current conversation (uid or space URL). Running it again changes the kind.
|
|
113
|
+
- `bili unsub <uid>`: unsubscribe.
|
|
114
|
+
- `bili list`: list subscriptions and sources currently backing off.
|
|
115
|
+
- `bili live <room>`: show a live room's status (room id or URL).
|
|
116
|
+
|
|
117
|
+
**Permissions:** in groups and channels, `bili sub` / `bili unsub` are limited to the group owner, group admins and the bot master (`role(owner,admin,master)`); other members get a notice and nothing changes. In private chat anyone can manage their own conversation. `bili list` and `bili live` are read-only and unrestricted. Roles come from the adapter's platform-agnostic `sender.role` and are checked with the same built-in permit checker the command router uses; if an adapter does not report group roles, only the bot master can manage group subscriptions.
|
|
118
|
+
|
|
119
|
+
### Configuration
|
|
120
|
+
|
|
121
|
+
`pollCron` (default every 60s), `jitterMs` (random delay before each poll), `requestGapMs`, `maxBackoffMinutes`, `maxPerChannel`, `timeout`, `live`, `dynamic`, and an optional `cookie` that should be referenced as `${BILIBILI_COOKIE}` from `.env`.
|
|
122
|
+
|
|
123
|
+
### How it works
|
|
124
|
+
|
|
125
|
+
Live status is polled in one batched, unauthenticated request; a push happens on the offline→live transition or when a new session start time appears. Dynamics are fetched per UP from the WBI-signed space feed with an anonymous `buvid3` cookie, deduplicated by publish time and pushed IDs; old pinned items are ignored and at most 3 items per UP are pushed per tick. The first observation only records a baseline. Payload changes, risk control (`-352`, `-412`, `-799`, HTTP 412) and network errors never fail the scheduled job: the affected source backs off exponentially and `bili list` shows when it will retry.
|
|
126
|
+
|
|
127
|
+
### Caveats
|
|
128
|
+
|
|
129
|
+
These are undocumented Bilibili web endpoints and may change. The dynamic feed is aggressively risk-controlled for anonymous and datacenter clients: it may answer 412 / -352 or silently return an empty list. Empty answers are never used as a baseline, and dynamics older than 6 hours are marked seen without being pushed. Configure a logged-in `cookie`, or enable `live` only, if dynamics keep failing.
|
|
130
|
+
|
|
131
|
+
## AI tools
|
|
132
|
+
|
|
133
|
+
`bili_live_status` and `bili_list_subscriptions` (skill `bilibili`), both read-only.
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from 'zhin.js/command';
|
|
3
|
+
import { channelSourceError, extractChannelInfo, subscriptionScope } from "../../../lib/channel.js";
|
|
4
|
+
import { bilibiliRuntimeToken } from "../../../lib/runtime.js";
|
|
5
|
+
import { getBiliSubs } from "../../../lib/store.js";
|
|
6
|
+
const KIND_TEXT = { all: '开播+动态', live: '开播', dynamic: '动态' };
|
|
7
|
+
export default defineCommand({
|
|
8
|
+
description: '查看当前会话的 B 站订阅',
|
|
9
|
+
async execute({ input, use }) {
|
|
10
|
+
const runtime = use(bilibiliRuntimeToken);
|
|
11
|
+
const Subs = getBiliSubs(runtime.db);
|
|
12
|
+
if (!Subs)
|
|
13
|
+
return 'B 站订阅数据库尚未就绪,请稍后重试';
|
|
14
|
+
const channel = extractChannelInfo(input);
|
|
15
|
+
if (!channel)
|
|
16
|
+
return channelSourceError(input);
|
|
17
|
+
const rows = await Subs.select().where(subscriptionScope(channel));
|
|
18
|
+
if (rows.length === 0)
|
|
19
|
+
return '当前会话没有订阅任何 UP 主\n使用 bili sub <uid> 添加订阅';
|
|
20
|
+
const lines = rows.map((row, index) => `${index + 1}. ${String(row.uname || '(未知)')}(UID ${String(row.uid)})· ${KIND_TEXT[String(row.kinds)] ?? '开播+动态'}`);
|
|
21
|
+
const status = runtime.watcher.backoffStatus();
|
|
22
|
+
if (status.length > 0)
|
|
23
|
+
lines.push('', '暂停中的请求:', ...status);
|
|
24
|
+
return `当前订阅 (${rows.length}/${runtime.config.maxPerChannel}):\n${lines.join('\n')}`;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from 'zhin.js/command';
|
|
3
|
+
import { BiliApiError, parseRoomId } from "../../../lib/api.js";
|
|
4
|
+
import { formatRoom } from "../../../lib/format.js";
|
|
5
|
+
import { bilibiliRuntimeToken } from "../../../lib/runtime.js";
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: '查询 B 站直播间状态',
|
|
8
|
+
params: { room: { type: 'string', description: '直播间号或直播间链接' } },
|
|
9
|
+
async execute({ params, use }) {
|
|
10
|
+
const roomId = parseRoomId(String(params.room ?? ''));
|
|
11
|
+
if (!roomId)
|
|
12
|
+
return '请提供直播间号或链接,格式:bili live <房间号>';
|
|
13
|
+
const { client } = use(bilibiliRuntimeToken);
|
|
14
|
+
try {
|
|
15
|
+
const room = await client.getRoomInfo(roomId);
|
|
16
|
+
let uname = '';
|
|
17
|
+
let liveTime = 0;
|
|
18
|
+
if (room.uid) {
|
|
19
|
+
const info = (await client.getLiveStatus([room.uid]).catch(() => new Map())).get(room.uid);
|
|
20
|
+
uname = info?.uname ?? '';
|
|
21
|
+
liveTime = info?.liveTime ?? 0;
|
|
22
|
+
}
|
|
23
|
+
return formatRoom(room, uname, liveTime);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof BiliApiError && error.kind === 'not_found')
|
|
27
|
+
return `直播间 ${roomId} 不存在`;
|
|
28
|
+
return `查询失败:${error.message}`;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { canManageConversation, defineCommand } from 'zhin.js/command';
|
|
3
|
+
import { BiliApiError, parseUid } from "../../../lib/api.js";
|
|
4
|
+
import { channelSourceError, extractChannelInfo, subscriptionScope } from "../../../lib/channel.js";
|
|
5
|
+
import { bilibiliRuntimeToken } from "../../../lib/runtime.js";
|
|
6
|
+
import { SUBSCRIPTION_PERMISSION_DENIED_MESSAGE } from "../../../lib/permission.js";
|
|
7
|
+
import { getBiliSubs, parseKinds } from "../../../lib/store.js";
|
|
8
|
+
const KIND_TEXT = { all: '开播 + 动态', live: '开播', dynamic: '动态' };
|
|
9
|
+
export default defineCommand({
|
|
10
|
+
description: '订阅 B 站 UP 主的开播和动态推送(群聊限群主、管理员或机器人主人)',
|
|
11
|
+
params: {
|
|
12
|
+
uid: { type: 'string', description: 'UP 主 UID 或空间链接' },
|
|
13
|
+
kind: { type: 'string', default: 'all', description: 'all(默认)/ live / dynamic' },
|
|
14
|
+
},
|
|
15
|
+
async execute({ params, input, use, adapter, scene, sender }) {
|
|
16
|
+
const runtime = use(bilibiliRuntimeToken);
|
|
17
|
+
const Subs = getBiliSubs(runtime.db);
|
|
18
|
+
if (!Subs)
|
|
19
|
+
return 'B 站订阅数据库尚未就绪,请稍后重试';
|
|
20
|
+
const uid = parseUid(String(params.uid ?? ''));
|
|
21
|
+
if (!uid)
|
|
22
|
+
return '请提供 UP 主 UID 或空间链接,格式:bili sub <uid> [all|live|dynamic]';
|
|
23
|
+
const kinds = parseKinds(String(params.kind ?? 'all'));
|
|
24
|
+
if (!kinds)
|
|
25
|
+
return '订阅类型只能是 all、live 或 dynamic';
|
|
26
|
+
const channel = extractChannelInfo(input);
|
|
27
|
+
if (!channel)
|
|
28
|
+
return channelSourceError(input);
|
|
29
|
+
if (!canManageConversation({ adapter, scene, sender }))
|
|
30
|
+
return SUBSCRIPTION_PERMISSION_DENIED_MESSAGE;
|
|
31
|
+
const scope = subscriptionScope(channel);
|
|
32
|
+
const existing = await Subs.select().where({ uid, ...scope });
|
|
33
|
+
if (existing.length > 0) {
|
|
34
|
+
if (existing[0].kinds === kinds)
|
|
35
|
+
return '当前会话已订阅该 UP 主,无需重复添加';
|
|
36
|
+
await Subs.update({ kinds }).where({ uid, ...scope });
|
|
37
|
+
return `已将 ${String(existing[0].uname || uid)} 的订阅改为:${KIND_TEXT[kinds]}`;
|
|
38
|
+
}
|
|
39
|
+
const current = await Subs.select().where(scope);
|
|
40
|
+
if (current.length >= runtime.config.maxPerChannel) {
|
|
41
|
+
return `当前会话订阅数已达上限 (${runtime.config.maxPerChannel}),请先取消一些`;
|
|
42
|
+
}
|
|
43
|
+
let uname = '';
|
|
44
|
+
try {
|
|
45
|
+
uname = (await runtime.client.getUserCard(uid)).name;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error instanceof BiliApiError && error.kind === 'not_found')
|
|
49
|
+
return `找不到 UID 为 ${uid} 的用户`;
|
|
50
|
+
// Lookup is cosmetic; subscribe anyway and let the watcher fill gaps.
|
|
51
|
+
}
|
|
52
|
+
await Subs.insert({
|
|
53
|
+
uid,
|
|
54
|
+
uname,
|
|
55
|
+
kinds,
|
|
56
|
+
...scope,
|
|
57
|
+
creator_id: channel.senderId,
|
|
58
|
+
created_at: new Date().toISOString(),
|
|
59
|
+
});
|
|
60
|
+
await runtime.watcher.baseline(uid).catch(() => undefined);
|
|
61
|
+
const notes = [];
|
|
62
|
+
if (!runtime.outbound)
|
|
63
|
+
notes.push('⚠ 未接入 OutboundHost,暂时无法主动推送');
|
|
64
|
+
if (!runtime.persistent)
|
|
65
|
+
notes.push('⚠ 未配置数据库,订阅在重启后会丢失');
|
|
66
|
+
return [`订阅成功:${uname || uid}(UID ${uid}),类型:${KIND_TEXT[kinds]}`, ...notes].join('\n');
|
|
67
|
+
},
|
|
68
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { canManageConversation, defineCommand } from 'zhin.js/command';
|
|
3
|
+
import { parseUid } from "../../../lib/api.js";
|
|
4
|
+
import { channelSourceError, extractChannelInfo, subscriptionScope } from "../../../lib/channel.js";
|
|
5
|
+
import { bilibiliRuntimeToken } from "../../../lib/runtime.js";
|
|
6
|
+
import { SUBSCRIPTION_PERMISSION_DENIED_MESSAGE } from "../../../lib/permission.js";
|
|
7
|
+
import { getBiliState, getBiliSubs } from "../../../lib/store.js";
|
|
8
|
+
export default defineCommand({
|
|
9
|
+
description: '取消当前会话对某个 UP 主的订阅(群聊限群主、管理员或机器人主人)',
|
|
10
|
+
params: { uid: { type: 'string', description: 'UP 主 UID 或空间链接' } },
|
|
11
|
+
async execute({ params, input, use, adapter, scene, sender }) {
|
|
12
|
+
const runtime = use(bilibiliRuntimeToken);
|
|
13
|
+
const Subs = getBiliSubs(runtime.db);
|
|
14
|
+
if (!Subs)
|
|
15
|
+
return 'B 站订阅数据库尚未就绪,请稍后重试';
|
|
16
|
+
const uid = parseUid(String(params.uid ?? ''));
|
|
17
|
+
if (!uid)
|
|
18
|
+
return '请提供 UP 主 UID,格式:bili unsub <uid>';
|
|
19
|
+
const channel = extractChannelInfo(input);
|
|
20
|
+
if (!channel)
|
|
21
|
+
return channelSourceError(input);
|
|
22
|
+
if (!canManageConversation({ adapter, scene, sender }))
|
|
23
|
+
return SUBSCRIPTION_PERMISSION_DENIED_MESSAGE;
|
|
24
|
+
const scope = subscriptionScope(channel);
|
|
25
|
+
const existing = await Subs.select().where({ uid, ...scope });
|
|
26
|
+
if (existing.length === 0)
|
|
27
|
+
return '当前会话没有订阅该 UP 主';
|
|
28
|
+
await Subs.delete().where({ uid, ...scope });
|
|
29
|
+
// Drop the watcher cursor and backoff once nobody watches this UP any more.
|
|
30
|
+
const remaining = await Subs.select().where({ uid });
|
|
31
|
+
if (remaining.length === 0) {
|
|
32
|
+
await getBiliState(runtime.db)?.delete().where({ uid });
|
|
33
|
+
runtime.watcher.forget(uid);
|
|
34
|
+
}
|
|
35
|
+
return `已取消订阅:${String(existing[0].uname || uid)}`;
|
|
36
|
+
},
|
|
37
|
+
});
|
package/lib/api.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36";
|
|
2
|
+
export type BiliErrorKind = 'risk' | 'not_found' | 'http' | 'shape' | 'network';
|
|
3
|
+
export declare class BiliApiError extends Error {
|
|
4
|
+
readonly kind: BiliErrorKind;
|
|
5
|
+
readonly code: number | undefined;
|
|
6
|
+
constructor(kind: BiliErrorKind, message: string, code?: number);
|
|
7
|
+
}
|
|
8
|
+
export interface LiveInfo {
|
|
9
|
+
readonly uid: string;
|
|
10
|
+
readonly uname: string;
|
|
11
|
+
readonly roomId: string;
|
|
12
|
+
readonly title: string;
|
|
13
|
+
/** 0 = offline, 1 = live, 2 = replay carousel. */
|
|
14
|
+
readonly liveStatus: number;
|
|
15
|
+
/** Unix seconds of the current live start, 0 when unknown. */
|
|
16
|
+
readonly liveTime: number;
|
|
17
|
+
readonly cover: string;
|
|
18
|
+
readonly area: string;
|
|
19
|
+
readonly url: string;
|
|
20
|
+
}
|
|
21
|
+
export interface RoomInfo {
|
|
22
|
+
readonly uid: string;
|
|
23
|
+
readonly roomId: string;
|
|
24
|
+
readonly title: string;
|
|
25
|
+
readonly liveStatus: number;
|
|
26
|
+
readonly area: string;
|
|
27
|
+
readonly online: number;
|
|
28
|
+
readonly url: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DynamicItem {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly author: string;
|
|
33
|
+
/** Unix seconds. */
|
|
34
|
+
readonly pubTs: number;
|
|
35
|
+
readonly type: string;
|
|
36
|
+
readonly pinned: boolean;
|
|
37
|
+
readonly summary: string;
|
|
38
|
+
readonly url: string;
|
|
39
|
+
}
|
|
40
|
+
export interface UserCard {
|
|
41
|
+
readonly uid: string;
|
|
42
|
+
readonly name: string;
|
|
43
|
+
}
|
|
44
|
+
export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
|
|
45
|
+
export interface BiliClientOptions {
|
|
46
|
+
readonly timeout?: number;
|
|
47
|
+
readonly cookie?: string;
|
|
48
|
+
readonly fetch?: FetchLike;
|
|
49
|
+
readonly now?: () => number;
|
|
50
|
+
}
|
|
51
|
+
export declare class BiliClient {
|
|
52
|
+
#private;
|
|
53
|
+
constructor(options?: BiliClientOptions);
|
|
54
|
+
/** Forget cached device id and WBI key, e.g. after a risk-control response. */
|
|
55
|
+
resetSession(): void;
|
|
56
|
+
/** Batch live status by UP uid (no auth). */
|
|
57
|
+
getLiveStatus(uids: readonly string[]): Promise<Map<string, LiveInfo>>;
|
|
58
|
+
getRoomInfo(roomId: string): Promise<RoomInfo>;
|
|
59
|
+
getUserCard(uid: string): Promise<UserCard>;
|
|
60
|
+
/** Latest space dynamics of one UP (WBI signed). Newest first, as returned. */
|
|
61
|
+
getDynamics(uid: string): Promise<DynamicItem[]>;
|
|
62
|
+
}
|
|
63
|
+
/** Turn one raw feed item into a compact, push-ready record (undefined = skip). */
|
|
64
|
+
export declare function parseDynamic(raw: unknown): DynamicItem | undefined;
|
|
65
|
+
/** Accepts `123`, `UID:123`, `https://space.bilibili.com/123/...`. */
|
|
66
|
+
export declare function parseUid(input: string): string | undefined;
|
|
67
|
+
/** Accepts `21452505` or `https://live.bilibili.com/21452505`. */
|
|
68
|
+
export declare function parseRoomId(input: string): string | undefined;
|