@zhin.js/plugin-bilibili 0.1.0 → 0.1.2

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 ADDED
@@ -0,0 +1,22 @@
1
+ # @zhin.js/plugin-bilibili
2
+
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [6da2bad]
8
+ - Updated dependencies [3781f1c]
9
+ - @zhin.js/tool@1.1.9
10
+ - @zhin.js/skill@1.1.9
11
+
12
+ ## 0.1.1
13
+
14
+ ### Patch Changes
15
+
16
+ - c67e43d: 取消某 UP 主的最后一个订阅时,同步清理 watcher 中该 UP 的退避记录,避免已退订 UID 的条目残留。
17
+ - 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.
18
+ - 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.
19
+ - Updated dependencies [1a11207]
20
+ - Updated dependencies [9943df6]
21
+ - @zhin.js/tool@1.1.8
22
+ - @zhin.js/skill@1.1.8
@@ -1,9 +1,9 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { defineCommand } from 'zhin.js/command';
2
+ import { canManageConversation, defineCommand } from 'zhin.js/command';
3
3
  import { BiliApiError, parseUid } from "../../../lib/api.js";
4
4
  import { channelSourceError, extractChannelInfo, subscriptionScope } from "../../../lib/channel.js";
5
5
  import { bilibiliRuntimeToken } from "../../../lib/runtime.js";
6
- import { canManageSubscription, SUBSCRIPTION_PERMISSION_DENIED_MESSAGE } from "../../../lib/permission.js";
6
+ import { SUBSCRIPTION_PERMISSION_DENIED_MESSAGE } from "../../../lib/permission.js";
7
7
  import { getBiliSubs, parseKinds } from "../../../lib/store.js";
8
8
  const KIND_TEXT = { all: '开播 + 动态', live: '开播', dynamic: '动态' };
9
9
  export default defineCommand({
@@ -26,7 +26,7 @@ export default defineCommand({
26
26
  const channel = extractChannelInfo(input);
27
27
  if (!channel)
28
28
  return channelSourceError(input);
29
- if (!canManageSubscription({ adapter, scene, sender }))
29
+ if (!canManageConversation({ adapter, scene, sender }))
30
30
  return SUBSCRIPTION_PERMISSION_DENIED_MESSAGE;
31
31
  const scope = subscriptionScope(channel);
32
32
  const existing = await Subs.select().where({ uid, ...scope });
@@ -1,9 +1,9 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { defineCommand } from 'zhin.js/command';
2
+ import { canManageConversation, defineCommand } from 'zhin.js/command';
3
3
  import { parseUid } from "../../../lib/api.js";
4
4
  import { channelSourceError, extractChannelInfo, subscriptionScope } from "../../../lib/channel.js";
5
5
  import { bilibiliRuntimeToken } from "../../../lib/runtime.js";
6
- import { canManageSubscription, SUBSCRIPTION_PERMISSION_DENIED_MESSAGE } from "../../../lib/permission.js";
6
+ import { SUBSCRIPTION_PERMISSION_DENIED_MESSAGE } from "../../../lib/permission.js";
7
7
  import { getBiliState, getBiliSubs } from "../../../lib/store.js";
8
8
  export default defineCommand({
9
9
  description: '取消当前会话对某个 UP 主的订阅(群聊限群主、管理员或机器人主人)',
@@ -19,17 +19,19 @@ export default defineCommand({
19
19
  const channel = extractChannelInfo(input);
20
20
  if (!channel)
21
21
  return channelSourceError(input);
22
- if (!canManageSubscription({ adapter, scene, sender }))
22
+ if (!canManageConversation({ adapter, scene, sender }))
23
23
  return SUBSCRIPTION_PERMISSION_DENIED_MESSAGE;
24
24
  const scope = subscriptionScope(channel);
25
25
  const existing = await Subs.select().where({ uid, ...scope });
26
26
  if (existing.length === 0)
27
27
  return '当前会话没有订阅该 UP 主';
28
28
  await Subs.delete().where({ uid, ...scope });
29
- // Drop the watcher cursor once nobody watches this UP any more.
29
+ // Drop the watcher cursor and backoff once nobody watches this UP any more.
30
30
  const remaining = await Subs.select().where({ uid });
31
- if (remaining.length === 0)
31
+ if (remaining.length === 0) {
32
32
  await getBiliState(runtime.db)?.delete().where({ uid });
33
+ runtime.watcher.forget(uid);
34
+ }
33
35
  return `已取消订阅:${String(existing[0].uname || uid)}`;
34
36
  },
35
37
  });
@@ -1,15 +1,10 @@
1
- export declare const SUBSCRIPTION_MANAGER_ROLES_PERMIT = "role(owner,admin,master)";
2
- export declare const PRIVATE_CHAT_PERMIT = "private()";
1
+ /**
2
+ * Who may change a conversation's Bilibili subscriptions.
3
+ *
4
+ * - Private chat: the user manages their own conversation, no restriction.
5
+ * - Group or channel: only the group owner, group admins or the bot master.
6
+ *
7
+ * The rule itself lives in `canManageConversation` (`zhin.js/command`); adapters project
8
+ * the canonical `owner` / `admin` role names that it checks.
9
+ */
3
10
  export declare const SUBSCRIPTION_PERMISSION_DENIED_MESSAGE = "\u53EA\u6709\u7FA4\u4E3B\u3001\u7FA4\u7BA1\u7406\u5458\u6216\u673A\u5668\u4EBA\u4E3B\u4EBA\u53EF\u4EE5\u5728\u7FA4\u804A\u4E2D\u4FEE\u6539 B \u7AD9\u8BA2\u9605\uFF1B\u79C1\u804A\u4E2D\u53EF\u4EE5\u76F4\u63A5\u8BA2\u9605\u672C\u4F1A\u8BDD\u3002";
4
- export interface SubscriptionPermissionSubject {
5
- readonly adapter?: string;
6
- readonly scene?: {
7
- readonly id: string;
8
- readonly type: string;
9
- };
10
- readonly sender?: {
11
- readonly id: string;
12
- readonly role: readonly string[];
13
- };
14
- }
15
- export declare function canManageSubscription(subject: SubscriptionPermissionSubject): boolean;
package/lib/permission.js CHANGED
@@ -4,17 +4,8 @@
4
4
  * - Private chat: the user manages their own conversation, no restriction.
5
5
  * - Group or channel: only the group owner, group admins or the bot master.
6
6
  *
7
- * `permit` lists are AND-only, so this "private OR role(...)" rule is evaluated
8
- * with the same built-in permit checker the command router uses, against the
9
- * platform-agnostic command session (`scene` / `sender.role`) that adapters project.
7
+ * The rule itself lives in `canManageConversation` (`zhin.js/command`); adapters project
8
+ * the canonical `owner` / `admin` role names that it checks.
10
9
  */
11
- import { checkBuiltinPermit } from 'zhin.js/command';
12
- export const SUBSCRIPTION_MANAGER_ROLES_PERMIT = 'role(owner,admin,master)';
13
- export const PRIVATE_CHAT_PERMIT = 'private()';
14
10
  export const SUBSCRIPTION_PERMISSION_DENIED_MESSAGE = '只有群主、群管理员或机器人主人可以在群聊中修改 B 站订阅;私聊中可以直接订阅本会话。';
15
- export function canManageSubscription(subject) {
16
- if (checkBuiltinPermit(PRIVATE_CHAT_PERMIT, subject))
17
- return true;
18
- return checkBuiltinPermit(SUBSCRIPTION_MANAGER_ROLES_PERMIT, subject);
19
- }
20
11
  //# sourceMappingURL=permission.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"permission.js","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,CAAC,MAAM,iCAAiC,GAAG,0BAA0B,CAAC;AAC5E,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAC;AAE/C,MAAM,CAAC,MAAM,sCAAsC,GACjD,6CAA6C,CAAC;AAQhD,MAAM,UAAU,qBAAqB,CAAC,OAAsC;IAC1E,IAAI,kBAAkB,CAAC,mBAAmB,EAAE,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAClE,OAAO,kBAAkB,CAAC,iCAAiC,EAAE,OAAO,CAAC,CAAC;AACxE,CAAC","sourcesContent":["/**\n * Who may change a conversation's Bilibili subscriptions.\n *\n * - Private chat: the user manages their own conversation, no restriction.\n * - Group or channel: only the group owner, group admins or the bot master.\n *\n * `permit` lists are AND-only, so this \"private OR role(...)\" rule is evaluated\n * with the same built-in permit checker the command router uses, against the\n * platform-agnostic command session (`scene` / `sender.role`) that adapters project.\n */\nimport { checkBuiltinPermit } from 'zhin.js/command';\n\nexport const SUBSCRIPTION_MANAGER_ROLES_PERMIT = 'role(owner,admin,master)';\nexport const PRIVATE_CHAT_PERMIT = 'private()';\n\nexport const SUBSCRIPTION_PERMISSION_DENIED_MESSAGE =\n '只有群主、群管理员或机器人主人可以在群聊中修改 B 站订阅;私聊中可以直接订阅本会话。';\n\nexport interface SubscriptionPermissionSubject {\n readonly adapter?: string;\n readonly scene?: { readonly id: string; readonly type: string };\n readonly sender?: { readonly id: string; readonly role: readonly string[] };\n}\n\nexport function canManageSubscription(subject: SubscriptionPermissionSubject): boolean {\n if (checkBuiltinPermit(PRIVATE_CHAT_PERMIT, subject)) return true;\n return checkBuiltinPermit(SUBSCRIPTION_MANAGER_ROLES_PERMIT, subject);\n}\n"]}
1
+ {"version":3,"file":"permission.js","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,sCAAsC,GACjD,6CAA6C,CAAC","sourcesContent":["/**\n * Who may change a conversation's Bilibili subscriptions.\n *\n * - Private chat: the user manages their own conversation, no restriction.\n * - Group or channel: only the group owner, group admins or the bot master.\n *\n * The rule itself lives in `canManageConversation` (`zhin.js/command`); adapters project\n * the canonical `owner` / `admin` role names that it checks.\n */\nexport const SUBSCRIPTION_PERMISSION_DENIED_MESSAGE =\n '只有群主、群管理员或机器人主人可以在群聊中修改 B 站订阅;私聊中可以直接订阅本会话。';\n"]}
package/lib/poll.d.ts CHANGED
@@ -32,6 +32,8 @@ export declare class BilibiliWatcher {
32
32
  } | undefined;
33
33
  lastSuccessAt: number | undefined;
34
34
  constructor(options: WatcherOptions);
35
+ /** Drop per-UP state once nobody subscribes to that UP any more. */
36
+ forget(uid: string): void;
35
37
  /** Human readable list of sources currently backing off. */
36
38
  backoffStatus(): string[];
37
39
  /** Record the current live / dynamic position of one UP without pushing. */
package/lib/poll.js CHANGED
@@ -63,6 +63,10 @@ export class BilibiliWatcher {
63
63
  #ok(key) {
64
64
  this.#backoff.delete(key);
65
65
  }
66
+ /** Drop per-UP state once nobody subscribes to that UP any more. */
67
+ forget(uid) {
68
+ this.#backoff.delete(`dynamic:${uid}`);
69
+ }
66
70
  /** Human readable list of sources currently backing off. */
67
71
  backoffStatus() {
68
72
  const now = this.#now;
package/lib/poll.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"poll.js","sourceRoot":"","sources":["../src/poll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Conservative live / dynamic watcher.\n *\n * - One poll per schedule tick (default every 60s) after a random jitter.\n * - Live status is fetched in one batched request for all watched UPs.\n * - Dynamics are fetched per UP with a gap between requests.\n * - Failures back off exponentially per source (live batch or one UP);\n * risk-control responses back off harder. Nothing throws to the scheduler.\n * - The first observation of an UP only records a baseline, so restarts and\n * new subscriptions never flood a conversation with old items.\n */\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { BiliApiError, type BiliClient, type DynamicItem, type LiveInfo } from './api.js';\nimport type { ResolvedBilibiliConfig } from './config.js';\nimport { formatDynamic, formatLiveStart } from './format.js';\nimport type { BiliOutboundPush } from './runtime.js';\nimport {\n emptyState,\n getBiliState,\n getBiliSubs,\n loadState,\n saveState,\n wantsDynamic,\n wantsLive,\n type BiliDb,\n type BiliRow,\n type WatchState,\n} from './store.js';\n\n/** At most this many new dynamics per UP are pushed in one tick. */\nexport const MAX_DYNAMICS_PER_TICK = 3;\n/** Dynamics older than this are recorded as seen but never pushed. */\nexport const MAX_DYNAMIC_AGE_SECONDS = 6 * 60 * 60;\n\nexport interface WatcherOptions {\n readonly db: BiliDb;\n readonly config: Readonly<ResolvedBilibiliConfig>;\n readonly client: BiliClient;\n readonly outbound: BiliOutboundPush | null;\n readonly signal?: AbortSignal;\n readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;\n readonly random?: () => number;\n readonly now?: () => number;\n readonly warn?: (message: string) => void;\n}\n\nexport interface TickResult {\n readonly skipped?: 'busy' | 'aborted';\n readonly liveChecked: number;\n readonly dynamicsChecked: number;\n readonly pushed: number;\n readonly errors: readonly string[];\n}\n\ninterface Backoff {\n failures: number;\n until: number;\n reason: string;\n}\n\nasync function defaultSleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return;\n try {\n await delay(ms, undefined, signal ? { signal } : undefined);\n } catch {\n // Aborted: the caller checks the signal.\n }\n}\n\nexport class BilibiliWatcher {\n readonly #options: WatcherOptions;\n readonly #backoff = new Map<string, Backoff>();\n #running = false;\n lastError: { readonly at: number; readonly message: string } | undefined;\n lastSuccessAt: number | undefined;\n\n constructor(options: WatcherOptions) {\n this.#options = options;\n }\n\n get #now(): number {\n return (this.#options.now ?? Date.now)();\n }\n\n #sleep(ms: number): Promise<void> {\n return (this.#options.sleep ?? defaultSleep)(ms, this.#options.signal);\n }\n\n #aborted(): boolean {\n return this.#options.signal?.aborted === true;\n }\n\n #blocked(key: string): boolean {\n const entry = this.#backoff.get(key);\n return entry !== undefined && entry.until > this.#now;\n }\n\n #fail(key: string, error: unknown): string {\n const previous = this.#backoff.get(key);\n const risk = error instanceof BiliApiError && error.kind === 'risk';\n const failures = (previous?.failures ?? 0) + (risk ? 2 : 1);\n const minutes = Math.min(this.#options.config.maxBackoffMinutes, 2 ** Math.min(failures - 1, 10));\n const message = error instanceof Error ? error.message : String(error);\n this.#backoff.set(key, { failures, until: this.#now + minutes * 60_000, reason: message });\n this.lastError = { at: this.#now, message: `${key}: ${message}` };\n (this.#options.warn ?? console.warn)(`[bilibili] ${key} 请求失败,${minutes} 分钟后重试:${message}`);\n return `${key}: ${message}`;\n }\n\n #ok(key: string): void {\n this.#backoff.delete(key);\n }\n\n /** Human readable list of sources currently backing off. */\n backoffStatus(): string[] {\n const now = this.#now;\n return [...this.#backoff.entries()]\n .filter(([, entry]) => entry.until > now)\n .map(([key, entry]) => `${key}:${Math.ceil((entry.until - now) / 60_000)} 分钟后重试(${entry.reason})`);\n }\n\n async #push(rows: readonly BiliRow[], content: string): Promise<number> {\n const push = this.#options.outbound;\n if (!push) return 0;\n let pushed = 0;\n for (const row of rows) {\n const { adapter_name: adapterName, endpoint_id: endpointKey, channel_type: channelType, channel_id: channelId } = row;\n if (typeof adapterName !== 'string' || !adapterName || typeof endpointKey !== 'string' || !endpointKey\n || typeof channelId !== 'string' || !channelId\n || (channelType !== 'private' && channelType !== 'group' && channelType !== 'channel')) continue;\n try {\n await push({ adapterName, endpointKey, channelType, channelId, content });\n pushed += 1;\n } catch {\n // OutboundHost already logs; keep delivering to other subscribers.\n }\n }\n return pushed;\n }\n\n /** Record the current live / dynamic position of one UP without pushing. */\n async baseline(uid: string): Promise<void> {\n const State = getBiliState(this.#options.db);\n if (!State) return;\n const state = (await loadState(State, uid)) ?? emptyState(uid);\n const { config, client } = this.#options;\n if (config.live) {\n try {\n const info = (await client.getLiveStatus([uid])).get(uid);\n if (info) {\n state.liveStatus = info.liveStatus;\n state.liveTime = info.liveTime;\n }\n } catch { /* the next tick records the baseline */ }\n }\n if (config.dynamic && !state.dynamicReady) {\n try {\n const items = await client.getDynamics(uid);\n if (items.length > 0) markBaseline(state, items);\n } catch { /* the next tick records the baseline */ }\n }\n await saveState(State, state);\n }\n\n async tick(): Promise<TickResult> {\n if (this.#running) return { skipped: 'busy', liveChecked: 0, dynamicsChecked: 0, pushed: 0, errors: [] };\n this.#running = true;\n try {\n return await this.#tick();\n } finally {\n this.#running = false;\n }\n }\n\n async #tick(): Promise<TickResult> {\n const { db, config, client } = this.#options;\n const Subs = getBiliSubs(db);\n const State = getBiliState(db);\n const errors: string[] = [];\n let pushed = 0;\n let liveChecked = 0;\n let dynamicsChecked = 0;\n if (!Subs || !State) return { liveChecked, dynamicsChecked, pushed, errors: ['数据库尚未就绪'] };\n\n const rows = await Subs.select();\n if (rows.length === 0) return { liveChecked, dynamicsChecked, pushed, errors };\n const byUid = new Map<string, BiliRow[]>();\n for (const row of rows) {\n if (typeof row.uid !== 'string' || !row.uid) continue;\n const list = byUid.get(row.uid) ?? [];\n list.push(row);\n byUid.set(row.uid, list);\n }\n\n if (config.jitterMs > 0) await this.#sleep(Math.floor((this.#options.random ?? Math.random)() * config.jitterMs));\n if (this.#aborted()) return { skipped: 'aborted', liveChecked, dynamicsChecked, pushed, errors };\n\n if (config.live) {\n const liveUids = [...byUid.entries()].filter(([, subs]) => subs.some((s) => wantsLive(s.kinds))).map(([uid]) => uid);\n if (liveUids.length > 0 && !this.#blocked('live')) {\n try {\n const statuses = await client.getLiveStatus(liveUids);\n this.#ok('live');\n for (const uid of liveUids) {\n const info = statuses.get(uid);\n if (!info) continue;\n liveChecked += 1;\n const state = (await loadState(State, uid)) ?? emptyState(uid);\n if (shouldPushLive(state, info)) {\n pushed += await this.#push(byUid.get(uid)!.filter((s) => wantsLive(s.kinds)), formatLiveStart(info));\n }\n if (state.liveStatus !== info.liveStatus || state.liveTime !== info.liveTime) {\n state.liveStatus = info.liveStatus;\n state.liveTime = info.liveTime;\n await saveState(State, state);\n }\n }\n } catch (error) {\n errors.push(this.#fail('live', error));\n }\n }\n }\n\n if (config.dynamic) {\n let first = true;\n for (const [uid, subs] of byUid) {\n const targets = subs.filter((s) => wantsDynamic(s.kinds));\n const key = `dynamic:${uid}`;\n if (targets.length === 0 || this.#blocked(key)) continue;\n if (!first && config.requestGapMs > 0) await this.#sleep(config.requestGapMs);\n first = false;\n if (this.#aborted()) return { skipped: 'aborted', liveChecked, dynamicsChecked, pushed, errors };\n try {\n const items = await client.getDynamics(uid);\n this.#ok(key);\n dynamicsChecked += 1;\n const state = (await loadState(State, uid)) ?? emptyState(uid);\n // An empty feed is also what a soft risk-control block looks like:\n // never treat it as a baseline, or the next real answer would flood.\n if (items.length === 0) continue;\n if (!state.dynamicReady) {\n markBaseline(state, items);\n } else {\n const fresh = selectFreshDynamics(state, items);\n const uname = typeof subs[0]?.uname === 'string' ? subs[0].uname : uid;\n const recent = fresh.filter((item) => item.pubTs >= Math.floor(this.#now / 1000) - MAX_DYNAMIC_AGE_SECONDS);\n for (const item of recent.slice(-MAX_DYNAMICS_PER_TICK)) {\n pushed += await this.#push(targets, formatDynamic(uname, item));\n }\n for (const item of fresh) {\n state.seenDynamics.push(item.id);\n state.lastDynamicTs = Math.max(state.lastDynamicTs, item.pubTs);\n }\n if (fresh.length === 0) continue;\n }\n await saveState(State, state);\n } catch (error) {\n errors.push(this.#fail(key, error));\n // A risk-control answer applies to the whole client; stop this round.\n if (error instanceof BiliApiError && error.kind === 'risk') break;\n }\n }\n }\n if (errors.length === 0) this.lastSuccessAt = this.#now;\n return { liveChecked, dynamicsChecked, pushed, errors };\n }\n}\n\n/** Live start = known previous state, now live, and either was offline or a new session began. */\nexport function shouldPushLive(state: WatchState, info: LiveInfo): boolean {\n if (info.liveStatus !== 1 || state.liveStatus === -1) return false;\n if (state.liveStatus !== 1) return true;\n return info.liveTime > 0 && state.liveTime > 0 && info.liveTime !== state.liveTime;\n}\n\nexport function markBaseline(state: WatchState, items: readonly DynamicItem[]): void {\n for (const item of items) {\n if (!state.seenDynamics.includes(item.id)) state.seenDynamics.push(item.id);\n state.lastDynamicTs = Math.max(state.lastDynamicTs, item.pubTs);\n }\n state.dynamicReady = true;\n}\n\n/** New, unseen, timestamped items newer than the cursor, oldest first. */\nexport function selectFreshDynamics(state: WatchState, items: readonly DynamicItem[]): DynamicItem[] {\n const seen = new Set(state.seenDynamics);\n return items\n .filter((item) => item.pubTs > 0 && item.pubTs > state.lastDynamicTs && !seen.has(item.id))\n .sort((a, b) => a.pubTs - b.pubTs);\n}\n"]}
1
+ {"version":3,"file":"poll.js","sourceRoot":"","sources":["../src/poll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Conservative live / dynamic watcher.\n *\n * - One poll per schedule tick (default every 60s) after a random jitter.\n * - Live status is fetched in one batched request for all watched UPs.\n * - Dynamics are fetched per UP with a gap between requests.\n * - Failures back off exponentially per source (live batch or one UP);\n * risk-control responses back off harder. Nothing throws to the scheduler.\n * - The first observation of an UP only records a baseline, so restarts and\n * new subscriptions never flood a conversation with old items.\n */\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { BiliApiError, type BiliClient, type DynamicItem, type LiveInfo } from './api.js';\nimport type { ResolvedBilibiliConfig } from './config.js';\nimport { formatDynamic, formatLiveStart } from './format.js';\nimport type { BiliOutboundPush } from './runtime.js';\nimport {\n emptyState,\n getBiliState,\n getBiliSubs,\n loadState,\n saveState,\n wantsDynamic,\n wantsLive,\n type BiliDb,\n type BiliRow,\n type WatchState,\n} from './store.js';\n\n/** At most this many new dynamics per UP are pushed in one tick. */\nexport const MAX_DYNAMICS_PER_TICK = 3;\n/** Dynamics older than this are recorded as seen but never pushed. */\nexport const MAX_DYNAMIC_AGE_SECONDS = 6 * 60 * 60;\n\nexport interface WatcherOptions {\n readonly db: BiliDb;\n readonly config: Readonly<ResolvedBilibiliConfig>;\n readonly client: BiliClient;\n readonly outbound: BiliOutboundPush | null;\n readonly signal?: AbortSignal;\n readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;\n readonly random?: () => number;\n readonly now?: () => number;\n readonly warn?: (message: string) => void;\n}\n\nexport interface TickResult {\n readonly skipped?: 'busy' | 'aborted';\n readonly liveChecked: number;\n readonly dynamicsChecked: number;\n readonly pushed: number;\n readonly errors: readonly string[];\n}\n\ninterface Backoff {\n failures: number;\n until: number;\n reason: string;\n}\n\nasync function defaultSleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return;\n try {\n await delay(ms, undefined, signal ? { signal } : undefined);\n } catch {\n // Aborted: the caller checks the signal.\n }\n}\n\nexport class BilibiliWatcher {\n readonly #options: WatcherOptions;\n readonly #backoff = new Map<string, Backoff>();\n #running = false;\n lastError: { readonly at: number; readonly message: string } | undefined;\n lastSuccessAt: number | undefined;\n\n constructor(options: WatcherOptions) {\n this.#options = options;\n }\n\n get #now(): number {\n return (this.#options.now ?? Date.now)();\n }\n\n #sleep(ms: number): Promise<void> {\n return (this.#options.sleep ?? defaultSleep)(ms, this.#options.signal);\n }\n\n #aborted(): boolean {\n return this.#options.signal?.aborted === true;\n }\n\n #blocked(key: string): boolean {\n const entry = this.#backoff.get(key);\n return entry !== undefined && entry.until > this.#now;\n }\n\n #fail(key: string, error: unknown): string {\n const previous = this.#backoff.get(key);\n const risk = error instanceof BiliApiError && error.kind === 'risk';\n const failures = (previous?.failures ?? 0) + (risk ? 2 : 1);\n const minutes = Math.min(this.#options.config.maxBackoffMinutes, 2 ** Math.min(failures - 1, 10));\n const message = error instanceof Error ? error.message : String(error);\n this.#backoff.set(key, { failures, until: this.#now + minutes * 60_000, reason: message });\n this.lastError = { at: this.#now, message: `${key}: ${message}` };\n (this.#options.warn ?? console.warn)(`[bilibili] ${key} 请求失败,${minutes} 分钟后重试:${message}`);\n return `${key}: ${message}`;\n }\n\n #ok(key: string): void {\n this.#backoff.delete(key);\n }\n\n /** Drop per-UP state once nobody subscribes to that UP any more. */\n forget(uid: string): void {\n this.#backoff.delete(`dynamic:${uid}`);\n }\n\n /** Human readable list of sources currently backing off. */\n backoffStatus(): string[] {\n const now = this.#now;\n return [...this.#backoff.entries()]\n .filter(([, entry]) => entry.until > now)\n .map(([key, entry]) => `${key}:${Math.ceil((entry.until - now) / 60_000)} 分钟后重试(${entry.reason})`);\n }\n\n async #push(rows: readonly BiliRow[], content: string): Promise<number> {\n const push = this.#options.outbound;\n if (!push) return 0;\n let pushed = 0;\n for (const row of rows) {\n const { adapter_name: adapterName, endpoint_id: endpointKey, channel_type: channelType, channel_id: channelId } = row;\n if (typeof adapterName !== 'string' || !adapterName || typeof endpointKey !== 'string' || !endpointKey\n || typeof channelId !== 'string' || !channelId\n || (channelType !== 'private' && channelType !== 'group' && channelType !== 'channel')) continue;\n try {\n await push({ adapterName, endpointKey, channelType, channelId, content });\n pushed += 1;\n } catch {\n // OutboundHost already logs; keep delivering to other subscribers.\n }\n }\n return pushed;\n }\n\n /** Record the current live / dynamic position of one UP without pushing. */\n async baseline(uid: string): Promise<void> {\n const State = getBiliState(this.#options.db);\n if (!State) return;\n const state = (await loadState(State, uid)) ?? emptyState(uid);\n const { config, client } = this.#options;\n if (config.live) {\n try {\n const info = (await client.getLiveStatus([uid])).get(uid);\n if (info) {\n state.liveStatus = info.liveStatus;\n state.liveTime = info.liveTime;\n }\n } catch { /* the next tick records the baseline */ }\n }\n if (config.dynamic && !state.dynamicReady) {\n try {\n const items = await client.getDynamics(uid);\n if (items.length > 0) markBaseline(state, items);\n } catch { /* the next tick records the baseline */ }\n }\n await saveState(State, state);\n }\n\n async tick(): Promise<TickResult> {\n if (this.#running) return { skipped: 'busy', liveChecked: 0, dynamicsChecked: 0, pushed: 0, errors: [] };\n this.#running = true;\n try {\n return await this.#tick();\n } finally {\n this.#running = false;\n }\n }\n\n async #tick(): Promise<TickResult> {\n const { db, config, client } = this.#options;\n const Subs = getBiliSubs(db);\n const State = getBiliState(db);\n const errors: string[] = [];\n let pushed = 0;\n let liveChecked = 0;\n let dynamicsChecked = 0;\n if (!Subs || !State) return { liveChecked, dynamicsChecked, pushed, errors: ['数据库尚未就绪'] };\n\n const rows = await Subs.select();\n if (rows.length === 0) return { liveChecked, dynamicsChecked, pushed, errors };\n const byUid = new Map<string, BiliRow[]>();\n for (const row of rows) {\n if (typeof row.uid !== 'string' || !row.uid) continue;\n const list = byUid.get(row.uid) ?? [];\n list.push(row);\n byUid.set(row.uid, list);\n }\n\n if (config.jitterMs > 0) await this.#sleep(Math.floor((this.#options.random ?? Math.random)() * config.jitterMs));\n if (this.#aborted()) return { skipped: 'aborted', liveChecked, dynamicsChecked, pushed, errors };\n\n if (config.live) {\n const liveUids = [...byUid.entries()].filter(([, subs]) => subs.some((s) => wantsLive(s.kinds))).map(([uid]) => uid);\n if (liveUids.length > 0 && !this.#blocked('live')) {\n try {\n const statuses = await client.getLiveStatus(liveUids);\n this.#ok('live');\n for (const uid of liveUids) {\n const info = statuses.get(uid);\n if (!info) continue;\n liveChecked += 1;\n const state = (await loadState(State, uid)) ?? emptyState(uid);\n if (shouldPushLive(state, info)) {\n pushed += await this.#push(byUid.get(uid)!.filter((s) => wantsLive(s.kinds)), formatLiveStart(info));\n }\n if (state.liveStatus !== info.liveStatus || state.liveTime !== info.liveTime) {\n state.liveStatus = info.liveStatus;\n state.liveTime = info.liveTime;\n await saveState(State, state);\n }\n }\n } catch (error) {\n errors.push(this.#fail('live', error));\n }\n }\n }\n\n if (config.dynamic) {\n let first = true;\n for (const [uid, subs] of byUid) {\n const targets = subs.filter((s) => wantsDynamic(s.kinds));\n const key = `dynamic:${uid}`;\n if (targets.length === 0 || this.#blocked(key)) continue;\n if (!first && config.requestGapMs > 0) await this.#sleep(config.requestGapMs);\n first = false;\n if (this.#aborted()) return { skipped: 'aborted', liveChecked, dynamicsChecked, pushed, errors };\n try {\n const items = await client.getDynamics(uid);\n this.#ok(key);\n dynamicsChecked += 1;\n const state = (await loadState(State, uid)) ?? emptyState(uid);\n // An empty feed is also what a soft risk-control block looks like:\n // never treat it as a baseline, or the next real answer would flood.\n if (items.length === 0) continue;\n if (!state.dynamicReady) {\n markBaseline(state, items);\n } else {\n const fresh = selectFreshDynamics(state, items);\n const uname = typeof subs[0]?.uname === 'string' ? subs[0].uname : uid;\n const recent = fresh.filter((item) => item.pubTs >= Math.floor(this.#now / 1000) - MAX_DYNAMIC_AGE_SECONDS);\n for (const item of recent.slice(-MAX_DYNAMICS_PER_TICK)) {\n pushed += await this.#push(targets, formatDynamic(uname, item));\n }\n for (const item of fresh) {\n state.seenDynamics.push(item.id);\n state.lastDynamicTs = Math.max(state.lastDynamicTs, item.pubTs);\n }\n if (fresh.length === 0) continue;\n }\n await saveState(State, state);\n } catch (error) {\n errors.push(this.#fail(key, error));\n // A risk-control answer applies to the whole client; stop this round.\n if (error instanceof BiliApiError && error.kind === 'risk') break;\n }\n }\n }\n if (errors.length === 0) this.lastSuccessAt = this.#now;\n return { liveChecked, dynamicsChecked, pushed, errors };\n }\n}\n\n/** Live start = known previous state, now live, and either was offline or a new session began. */\nexport function shouldPushLive(state: WatchState, info: LiveInfo): boolean {\n if (info.liveStatus !== 1 || state.liveStatus === -1) return false;\n if (state.liveStatus !== 1) return true;\n return info.liveTime > 0 && state.liveTime > 0 && info.liveTime !== state.liveTime;\n}\n\nexport function markBaseline(state: WatchState, items: readonly DynamicItem[]): void {\n for (const item of items) {\n if (!state.seenDynamics.includes(item.id)) state.seenDynamics.push(item.id);\n state.lastDynamicTs = Math.max(state.lastDynamicTs, item.pubTs);\n }\n state.dynamicReady = true;\n}\n\n/** New, unseen, timestamped items newer than the cursor, oldest first. */\nexport function selectFreshDynamics(state: WatchState, items: readonly DynamicItem[]): DynamicItem[] {\n const seen = new Set(state.seenDynamics);\n return items\n .filter((item) => item.pubTs > 0 && item.pubTs > state.lastDynamicTs && !seen.has(item.id))\n .sort((a, b) => a.pubTs - b.pubTs);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-bilibili",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Bilibili live-start and UP dynamic subscriptions for Zhin.js (Plugin Runtime)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -41,20 +41,20 @@
41
41
  },
42
42
  "license": "MIT",
43
43
  "dependencies": {
44
- "@zhin.js/tool": "1.1.7",
45
- "@zhin.js/skill": "1.1.7"
44
+ "@zhin.js/skill": "1.1.9",
45
+ "@zhin.js/tool": "1.1.9"
46
46
  },
47
47
  "devDependencies": {
48
48
  "typescript": "^6.0.3",
49
49
  "vitest": "^4.1.10",
50
50
  "zod": "^4.4.3",
51
- "zhin.js": "1.1.11",
52
- "@zhin.js/command": "1.1.8"
51
+ "@zhin.js/command": "1.1.10",
52
+ "zhin.js": "1.1.15"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "zod": "^4.0.0",
56
- "@zhin.js/command": "^1.1.8",
57
- "zhin.js": "^1.1.11"
56
+ "@zhin.js/command": "^1.1.10",
57
+ "zhin.js": "^1.1.15"
58
58
  },
59
59
  "peerDependenciesMeta": {
60
60
  "@zhin.js/command": {