@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/lib/wbi.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * WBI request signing used by Bilibili web APIs (e.g. the space dynamic feed).
3
+ *
4
+ * The permutation below is the published WBI mixin-key order: the browser
5
+ * concatenates the file names of `wbi_img.img_url` and `wbi_img.sub_url`
6
+ * from `/x/web-interface/nav`, reorders the 64 characters with this table and
7
+ * keeps the first 32 as the salt for an MD5 over the sorted query string.
8
+ */
9
+ import { createHash } from 'node:crypto';
10
+ const MIXIN_KEY_ORDER = [
11
+ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35,
12
+ 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13,
13
+ 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4,
14
+ 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52,
15
+ ];
16
+ /** `https://i0.hdslb.com/bfs/wbi/7cd0...077c.png` → `7cd0...077c`. */
17
+ export function wbiKeyFromUrl(url) {
18
+ const file = url.slice(url.lastIndexOf('/') + 1);
19
+ const dot = file.indexOf('.');
20
+ return dot === -1 ? file : file.slice(0, dot);
21
+ }
22
+ export function mixinKey(imgKey, subKey) {
23
+ const raw = imgKey + subKey;
24
+ if (raw.length < 64)
25
+ throw new Error('WBI 密钥格式异常');
26
+ let key = '';
27
+ for (const index of MIXIN_KEY_ORDER)
28
+ key += raw[index] ?? '';
29
+ return key.slice(0, 32);
30
+ }
31
+ function stripReserved(value) {
32
+ let out = '';
33
+ for (const char of value)
34
+ if (!"!'()*".includes(char))
35
+ out += char;
36
+ return out;
37
+ }
38
+ /** Returns the signed query string (`a=1&wts=...&w_rid=...`). */
39
+ export function signWbi(params, key, nowSeconds = Math.floor(Date.now() / 1000)) {
40
+ const all = { wts: String(nowSeconds) };
41
+ for (const [name, value] of Object.entries(params))
42
+ all[name] = stripReserved(String(value));
43
+ const query = Object.keys(all)
44
+ .sort()
45
+ .map((name) => `${encodeURIComponent(name)}=${encodeURIComponent(all[name])}`)
46
+ .join('&');
47
+ const wRid = createHash('md5').update(query + key).digest('hex');
48
+ return `${query}&w_rid=${wRid}`;
49
+ }
50
+ //# sourceMappingURL=wbi.js.map
package/lib/wbi.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wbi.js","sourceRoot":"","sources":["../src/wbi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WBI request signing used by Bilibili web APIs (e.g. the space dynamic feed).\n *\n * The permutation below is the published WBI mixin-key order: the browser\n * concatenates the file names of `wbi_img.img_url` and `wbi_img.sub_url`\n * from `/x/web-interface/nav`, reorders the 64 characters with this table and\n * keeps the first 32 as the salt for an MD5 over the sorted query string.\n */\nimport { createHash } from 'node:crypto';\n\nconst MIXIN_KEY_ORDER: readonly number[] = [\n 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35,\n 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13,\n 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4,\n 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52,\n];\n\n/** `https://i0.hdslb.com/bfs/wbi/7cd0...077c.png` → `7cd0...077c`. */\nexport function wbiKeyFromUrl(url: string): string {\n const file = url.slice(url.lastIndexOf('/') + 1);\n const dot = file.indexOf('.');\n return dot === -1 ? file : file.slice(0, dot);\n}\n\nexport function mixinKey(imgKey: string, subKey: string): string {\n const raw = imgKey + subKey;\n if (raw.length < 64) throw new Error('WBI 密钥格式异常');\n let key = '';\n for (const index of MIXIN_KEY_ORDER) key += raw[index] ?? '';\n return key.slice(0, 32);\n}\n\nfunction stripReserved(value: string): string {\n let out = '';\n for (const char of value) if (!\"!'()*\".includes(char)) out += char;\n return out;\n}\n\n/** Returns the signed query string (`a=1&wts=...&w_rid=...`). */\nexport function signWbi(\n params: Readonly<Record<string, string | number>>,\n key: string,\n nowSeconds: number = Math.floor(Date.now() / 1000),\n): string {\n const all: Record<string, string> = { wts: String(nowSeconds) };\n for (const [name, value] of Object.entries(params)) all[name] = stripReserved(String(value));\n const query = Object.keys(all)\n .sort()\n .map((name) => `${encodeURIComponent(name)}=${encodeURIComponent(all[name]!)}`)\n .join('&');\n const wRid = createHash('md5').update(query + key).digest('hex');\n return `${query}&w_rid=${wRid}`;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,107 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-bilibili",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
4
+ "description": "Bilibili live-start and UP dynamic subscriptions for Zhin.js (Plugin Runtime)",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "types": "./lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "import": "./lib/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "lib",
17
+ "plugin.js",
18
+ "schema.json",
19
+ "commands",
20
+ "!commands/**/*.ts",
21
+ "skills",
22
+ "!skills/**/*.ts",
23
+ "README.md",
24
+ "CHANGELOG.md"
25
+ ],
26
+ "keywords": [
27
+ "zhin",
28
+ "zhin.js",
29
+ "bot",
30
+ "plugin",
31
+ "bilibili",
32
+ "live",
33
+ "dynamic",
34
+ "subscription",
35
+ "push"
36
+ ],
37
+ "author": {
38
+ "name": "lc-cn",
39
+ "email": "admin@liucl.cn",
40
+ "url": "https://github.com/lc-cn"
41
+ },
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "@zhin.js/skill": "1.1.8",
45
+ "@zhin.js/tool": "1.1.8"
46
+ },
47
+ "devDependencies": {
48
+ "typescript": "^6.0.3",
49
+ "vitest": "^4.1.10",
50
+ "zod": "^4.4.3",
51
+ "@zhin.js/command": "1.1.9",
52
+ "zhin.js": "1.1.13"
53
+ },
54
+ "peerDependencies": {
55
+ "zod": "^4.0.0",
56
+ "@zhin.js/command": "^1.1.9",
57
+ "zhin.js": "^1.1.13"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "@zhin.js/command": {
61
+ "optional": true
62
+ },
63
+ "zod": {
64
+ "optional": true
65
+ }
66
+ },
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "git+https://github.com/zhinjs/zhin.git",
70
+ "directory": "plugins/utils/bilibili"
71
+ },
72
+ "publishConfig": {
73
+ "access": "public",
74
+ "registry": "https://registry.npmjs.org"
75
+ },
76
+ "engines": {
77
+ "node": ">=22.19.0"
78
+ },
79
+ "zhin": {
80
+ "protocol": 1,
81
+ "type": "plugin",
82
+ "entry": "./plugin.js",
83
+ "engine": "^1.0.0",
84
+ "runtime": "trusted",
85
+ "features": [
86
+ {
87
+ "package": "@zhin.js/command",
88
+ "api": "^1.0.0"
89
+ },
90
+ {
91
+ "package": "@zhin.js/tool",
92
+ "api": "^1.0.0"
93
+ },
94
+ {
95
+ "package": "@zhin.js/skill",
96
+ "api": "^1.0.0"
97
+ }
98
+ ],
99
+ "plugins": []
100
+ },
101
+ "scripts": {
102
+ "build": "tsc",
103
+ "check": "tsc -p tsconfig.check.json",
104
+ "clean": "rimraf lib",
105
+ "test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/utils/bilibili/tests"
106
+ }
6
107
  }
package/plugin.js ADDED
@@ -0,0 +1,65 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { definePlugin, databaseHostToken, outboundHostToken, scheduleHostToken, } from 'zhin.js';
3
+ import { BiliClient } from "./lib/api.js";
4
+ import { resolveBilibiliConfig } from "./lib/config.js";
5
+ import { BilibiliWatcher } from "./lib/poll.js";
6
+ import { bilibiliRuntimeToken } from "./lib/runtime.js";
7
+ import { createInMemoryBiliDb, defineBiliTables } from "./lib/store.js";
8
+ /**
9
+ * Plugin Runtime:
10
+ * - Commands: `bili sub|unsub|list|live`.
11
+ * - DB: `databaseHostToken` when configured, otherwise in-memory.
12
+ * - Outbound: `outboundHostToken` pushes live starts / new dynamics.
13
+ * - Schedule: `scheduleHostToken` drives the conservative watcher.
14
+ */
15
+ export default definePlugin({
16
+ name: 'bilibili',
17
+ metadata: {
18
+ displayName: '哔哩哔哩订阅',
19
+ },
20
+ setup(context) {
21
+ const config = Object.freeze(resolveBilibiliConfig(context.config.get()));
22
+ const database = context.resources.has(databaseHostToken) ? context.resources.use(databaseHostToken) : undefined;
23
+ let db;
24
+ if (database) {
25
+ defineBiliTables(database);
26
+ db = database;
27
+ }
28
+ else {
29
+ db = createInMemoryBiliDb();
30
+ }
31
+ const outbound = context.resources.has(outboundHostToken) ? context.resources.use(outboundHostToken) : undefined;
32
+ const push = outbound
33
+ ? async (input) => {
34
+ await outbound.send({
35
+ adapter: input.adapterName,
36
+ endpointKey: input.endpointKey,
37
+ conversation: { kind: input.channelType, id: input.channelId },
38
+ content: input.content,
39
+ });
40
+ }
41
+ : null;
42
+ const client = new BiliClient({ timeout: config.timeout, cookie: config.cookie });
43
+ const watcher = new BilibiliWatcher({ db, config, client, outbound: push, signal: context.signal });
44
+ context.resources.provide(bilibiliRuntimeToken, Object.freeze({
45
+ db,
46
+ config,
47
+ client,
48
+ outbound: push,
49
+ watcher,
50
+ persistent: database !== undefined,
51
+ }));
52
+ if (!context.resources.has(scheduleHostToken) || (!config.live && !config.dynamic))
53
+ return;
54
+ const dispose = context.resources.use(scheduleHostToken).register({
55
+ id: 'bilibili/watch',
56
+ cron: config.pollCron,
57
+ description: 'Poll Bilibili live status and UP dynamics',
58
+ async execute() {
59
+ // The watcher records and backs off on its own; never fail the job.
60
+ await watcher.tick();
61
+ },
62
+ });
63
+ context.lifecycle.add(dispose);
64
+ },
65
+ });
package/schema.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "properties": {
6
+ "pollCron": {
7
+ "type": "string",
8
+ "default": "0 * * * * *",
9
+ "description": "轮询频率(6 段 Cron,默认每 60 秒) / Poll schedule (6-field cron, default every 60s)"
10
+ },
11
+ "jitterMs": {
12
+ "type": "number",
13
+ "default": 15000,
14
+ "minimum": 0,
15
+ "maximum": 120000,
16
+ "description": "每轮轮询前的随机延迟上限(毫秒) / Max random delay before each poll (ms)"
17
+ },
18
+ "requestGapMs": {
19
+ "type": "number",
20
+ "default": 1500,
21
+ "minimum": 0,
22
+ "maximum": 30000,
23
+ "description": "同一轮内相邻动态请求的间隔(毫秒) / Gap between dynamic requests within one poll (ms)"
24
+ },
25
+ "maxBackoffMinutes": {
26
+ "type": "number",
27
+ "default": 30,
28
+ "minimum": 1,
29
+ "maximum": 720,
30
+ "description": "接口失败或风控时指数退避的上限(分钟) / Upper bound of exponential backoff (minutes)"
31
+ },
32
+ "maxPerChannel": {
33
+ "type": "number",
34
+ "default": 20,
35
+ "minimum": 1,
36
+ "maximum": 200,
37
+ "description": "每个会话最多订阅的 UP 主数量 / Max UP subscriptions per conversation"
38
+ },
39
+ "timeout": {
40
+ "type": "number",
41
+ "default": 10000,
42
+ "minimum": 1000,
43
+ "maximum": 60000,
44
+ "description": "单次 HTTP 请求超时(毫秒) / HTTP timeout (ms)"
45
+ },
46
+ "live": {
47
+ "type": "boolean",
48
+ "default": true,
49
+ "description": "是否轮询开播状态 / Poll live status"
50
+ },
51
+ "dynamic": {
52
+ "type": "boolean",
53
+ "default": true,
54
+ "description": "是否轮询 UP 主动态 / Poll UP dynamics"
55
+ },
56
+ "cookie": {
57
+ "type": "string",
58
+ "default": "",
59
+ "description": "可选的 B 站 Cookie(如 SESSDATA=...),风控严格时使用,请写成 ${BILIBILI_COOKIE} / Optional cookie, reference it as ${BILIBILI_COOKIE}"
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: bilibili
3
+ description: >-
4
+ B 站(哔哩哔哩)直播与 UP 主订阅查询能力。当用户想知道某个直播间是否在播、
5
+ 某个 UP 主有没有开播,或想看看当前订阅了哪些 UP 主时使用。
6
+ 订阅的增删由聊天命令 bili sub / bili unsub 管理,推送由后台定时任务完成。
7
+ keywords:
8
+ - bilibili
9
+ - b站
10
+ - 哔哩哔哩
11
+ - 直播
12
+ - 开播
13
+ - up主
14
+ - 动态
15
+ - 订阅
16
+ tags:
17
+ - bilibili
18
+ - live
19
+ - subscription
20
+ tools:
21
+ - bili_live_status
22
+ - bili_list_subscriptions
23
+ ---
24
+
25
+ # B 站订阅技能
26
+
27
+ ## 工具
28
+
29
+ | 工具 | 用途 | 说明 |
30
+ |------|------|------|
31
+ | `bili_live_status` | 查询开播状态 | 传直播间号(`room`)或 UP 主 UID(`uid`),返回是否在播、标题、链接 |
32
+ | `bili_list_subscriptions` | 查看订阅 | 列出本插件实例的全部 UP 主订阅,按会话分组 |
33
+
34
+ **Example:**
35
+ ```
36
+ 用户: 嘉然现在在播吗?直播间 22637261
37
+ → bili_live_status(room="22637261")
38
+
39
+ 用户: 我们订阅了哪些 UP 主?
40
+ → bili_list_subscriptions()
41
+ ```
42
+
43
+ ## 注意
44
+
45
+ - 订阅的增加 / 删除走聊天命令 `bili sub <uid> [all|live|dynamic]`、`bili unsub <uid>`,不是 AI 工具。
46
+ - 推送有随机延迟和退避,B 站风控时会暂停一段时间,属于正常现象。
@@ -0,0 +1,34 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { z } from 'zod';
4
+ import { bilibiliRuntimeToken } from "../../../../lib/runtime.js";
5
+ import { getBiliSubs } from "../../../../lib/store.js";
6
+ export default defineAgentTool({
7
+ description: '列出所有 B 站 UP 主订阅(按会话分组)',
8
+ requiresApproval: 'never',
9
+ tags: ['bilibili'],
10
+ keywords: ['b站订阅', 'up主', 'bilibili subscriptions'],
11
+ inputSchema: z.object({}),
12
+ async execute(_input, context) {
13
+ const Subs = getBiliSubs(context.use(bilibiliRuntimeToken).db);
14
+ if (!Subs)
15
+ return 'B 站订阅数据库尚未就绪';
16
+ const rows = await Subs.select();
17
+ if (rows.length === 0)
18
+ return '暂无任何 B 站订阅';
19
+ const grouped = new Map();
20
+ for (const row of rows) {
21
+ const key = `${String(row.channel_type)}:${String(row.channel_id)}`;
22
+ const list = grouped.get(key) ?? [];
23
+ list.push(`${String(row.uname || row.uid)}(UID ${String(row.uid)},${String(row.kinds || 'all')})`);
24
+ grouped.set(key, list);
25
+ }
26
+ const lines = [];
27
+ for (const [channel, subs] of grouped) {
28
+ lines.push(`[${channel}]`);
29
+ for (const sub of subs)
30
+ lines.push(` - ${sub}`);
31
+ }
32
+ return `B 站订阅总览 (${rows.length} 条):\n${lines.join('\n')}`;
33
+ },
34
+ });
@@ -0,0 +1,39 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { z } from 'zod';
4
+ import { parseRoomId, parseUid } from "../../../../lib/api.js";
5
+ import { formatRoom, liveStatusText } from "../../../../lib/format.js";
6
+ import { bilibiliRuntimeToken } from "../../../../lib/runtime.js";
7
+ export default defineAgentTool({
8
+ description: '查询 B 站直播间或 UP 主当前是否在直播',
9
+ requiresApproval: 'never',
10
+ tags: ['bilibili'],
11
+ keywords: ['b站', '直播', '开播', 'bilibili live'],
12
+ inputSchema: z.object({
13
+ room: z.string().optional().describe('直播间号或直播间链接'),
14
+ uid: z.string().optional().describe('UP 主 UID 或空间链接'),
15
+ }),
16
+ async execute({ room, uid }, context) {
17
+ const { client } = context.use(bilibiliRuntimeToken);
18
+ try {
19
+ const roomId = room ? parseRoomId(room) : undefined;
20
+ if (roomId) {
21
+ const info = await client.getRoomInfo(roomId);
22
+ const live = info.uid ? (await client.getLiveStatus([info.uid])).get(info.uid) : undefined;
23
+ return formatRoom(info, live?.uname ?? '', live?.liveTime ?? 0);
24
+ }
25
+ const upUid = uid ? parseUid(uid) : undefined;
26
+ if (!upUid)
27
+ return '请提供直播间号(room)或 UP 主 UID(uid)';
28
+ const live = (await client.getLiveStatus([upUid])).get(upUid);
29
+ if (!live)
30
+ return `UID ${upUid} 没有开通直播间`;
31
+ return [`${live.uname || upUid}:${liveStatusText(live.liveStatus)}`, live.title && `标题:${live.title}`, live.url]
32
+ .filter(Boolean)
33
+ .join('\n');
34
+ }
35
+ catch (error) {
36
+ return `查询失败:${error.message}`;
37
+ }
38
+ },
39
+ });