@zhin.js/plugin-bilibili 0.0.0-stage → 0.1.0

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.
@@ -0,0 +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"]}
@@ -0,0 +1,22 @@
1
+ import type { BiliClient } from './api.js';
2
+ import type { ChannelInfo } from './channel.js';
3
+ import type { ResolvedBilibiliConfig } from './config.js';
4
+ import type { BiliDb } from './store.js';
5
+ import type { BilibiliWatcher } from './poll.js';
6
+ export type BiliOutboundPush = (input: {
7
+ readonly adapterName: string;
8
+ readonly endpointKey: string;
9
+ readonly channelType: ChannelInfo['channelType'];
10
+ readonly channelId: string;
11
+ readonly content: string;
12
+ }) => Promise<void>;
13
+ export interface BilibiliRuntime {
14
+ readonly db: BiliDb;
15
+ readonly config: Readonly<ResolvedBilibiliConfig>;
16
+ readonly client: BiliClient;
17
+ readonly outbound: BiliOutboundPush | null;
18
+ readonly watcher: BilibiliWatcher;
19
+ /** False when no database is configured (subscriptions live in memory). */
20
+ readonly persistent: boolean;
21
+ }
22
+ export declare const bilibiliRuntimeToken: import("zhin.js").Token<BilibiliRuntime>;
package/lib/runtime.js ADDED
@@ -0,0 +1,3 @@
1
+ import { createToken } from 'zhin.js';
2
+ export const bilibiliRuntimeToken = createToken('zhin.bilibili.runtime', 'Owner-scoped Bilibili subscription runtime');
3
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAyBtC,MAAM,CAAC,MAAM,oBAAoB,GAAG,WAAW,CAC7C,uBAAuB,EACvB,4CAA4C,CAC7C,CAAC","sourcesContent":["import { createToken } from 'zhin.js';\nimport type { BiliClient } from './api.js';\nimport type { ChannelInfo } from './channel.js';\nimport type { ResolvedBilibiliConfig } from './config.js';\nimport type { BiliDb } from './store.js';\nimport type { BilibiliWatcher } from './poll.js';\n\nexport type BiliOutboundPush = (input: {\n readonly adapterName: string;\n readonly endpointKey: string;\n readonly channelType: ChannelInfo['channelType'];\n readonly channelId: string;\n readonly content: string;\n}) => Promise<void>;\n\nexport interface BilibiliRuntime {\n readonly db: BiliDb;\n readonly config: Readonly<ResolvedBilibiliConfig>;\n readonly client: BiliClient;\n readonly outbound: BiliOutboundPush | null;\n readonly watcher: BilibiliWatcher;\n /** False when no database is configured (subscriptions live in memory). */\n readonly persistent: boolean;\n}\n\nexport const bilibiliRuntimeToken = createToken<BilibiliRuntime>(\n 'zhin.bilibili.runtime',\n 'Owner-scoped Bilibili subscription runtime',\n);\n"]}
package/lib/store.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Subscription / watcher-state tables.
3
+ *
4
+ * The model surface matches the Plugin Runtime `DatabaseHostModel`, so the
5
+ * scoped `databaseHostToken` facade is used directly when a database is
6
+ * configured; otherwise an in-memory store keeps chat-only bots working
7
+ * (subscriptions are then lost on restart).
8
+ */
9
+ export type BiliRow = Record<string, unknown>;
10
+ export interface BiliModel {
11
+ select: (...fields: string[]) => {
12
+ where: (query: Record<string, unknown>) => PromiseLike<BiliRow[]>;
13
+ then: <TResult1 = BiliRow[], TResult2 = never>(onfulfilled?: ((value: BiliRow[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null) => Promise<TResult1 | TResult2>;
14
+ };
15
+ insert: (row: Record<string, unknown>) => Promise<unknown>;
16
+ delete: () => {
17
+ where: (query: Record<string, unknown>) => Promise<unknown>;
18
+ };
19
+ update: (patch: Record<string, unknown>) => {
20
+ where: (query: Record<string, unknown>) => Promise<unknown>;
21
+ };
22
+ }
23
+ export interface BiliDb {
24
+ models: {
25
+ get: (name: string) => BiliModel | undefined;
26
+ };
27
+ }
28
+ export declare const BILI_SUBS_TABLE = "bilibili_subscriptions";
29
+ export declare const BILI_STATE_TABLE = "bilibili_watch_state";
30
+ export declare const BILI_TABLE_DEFINITIONS: Readonly<Record<string, Record<string, unknown>>>;
31
+ export declare function defineBiliTables(db: {
32
+ define: (name: string, schema: Record<string, unknown>) => void;
33
+ }): void;
34
+ export declare function createInMemoryBiliDb(): BiliDb;
35
+ export declare function getBiliSubs(db: BiliDb): BiliModel | null;
36
+ export declare function getBiliState(db: BiliDb): BiliModel | null;
37
+ export type SubscriptionKinds = 'all' | 'live' | 'dynamic';
38
+ export declare function parseKinds(input: string): SubscriptionKinds | undefined;
39
+ export declare function wantsLive(kinds: unknown): boolean;
40
+ export declare function wantsDynamic(kinds: unknown): boolean;
41
+ export interface WatchState {
42
+ uid: string;
43
+ liveStatus: number;
44
+ liveTime: number;
45
+ lastDynamicTs: number;
46
+ seenDynamics: string[];
47
+ dynamicReady: boolean;
48
+ }
49
+ export declare function loadState(model: BiliModel, uid: string): Promise<WatchState | undefined>;
50
+ export declare const SEEN_DYNAMICS_LIMIT = 40;
51
+ export declare function saveState(model: BiliModel, state: WatchState): Promise<void>;
52
+ export declare function emptyState(uid: string): WatchState;
package/lib/store.js ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Subscription / watcher-state tables.
3
+ *
4
+ * The model surface matches the Plugin Runtime `DatabaseHostModel`, so the
5
+ * scoped `databaseHostToken` facade is used directly when a database is
6
+ * configured; otherwise an in-memory store keeps chat-only bots working
7
+ * (subscriptions are then lost on restart).
8
+ */
9
+ export const BILI_SUBS_TABLE = 'bilibili_subscriptions';
10
+ export const BILI_STATE_TABLE = 'bilibili_watch_state';
11
+ export const BILI_TABLE_DEFINITIONS = Object.freeze({
12
+ [BILI_SUBS_TABLE]: {
13
+ uid: { type: 'text', nullable: false },
14
+ uname: { type: 'text', default: '' },
15
+ /** `all` | `live` | `dynamic` */
16
+ kinds: { type: 'text', default: 'all' },
17
+ adapter_name: { type: 'text', nullable: false },
18
+ endpoint_id: { type: 'text', default: '' },
19
+ channel_type: { type: 'text', default: 'private' },
20
+ channel_id: { type: 'text', nullable: false },
21
+ creator_id: { type: 'text', default: '' },
22
+ created_at: { type: 'text', default: '' },
23
+ },
24
+ [BILI_STATE_TABLE]: {
25
+ uid: { type: 'text', nullable: false },
26
+ live_status: { type: 'integer', default: -1 },
27
+ live_time: { type: 'integer', default: 0 },
28
+ last_dynamic_ts: { type: 'integer', default: 0 },
29
+ /** JSON array of recently pushed dynamic ids (dedupe across restarts). */
30
+ seen_dynamics: { type: 'text', default: '[]' },
31
+ dynamic_ready: { type: 'integer', default: 0 },
32
+ updated_at: { type: 'text', default: '' },
33
+ },
34
+ });
35
+ export function defineBiliTables(db) {
36
+ for (const [name, definition] of Object.entries(BILI_TABLE_DEFINITIONS))
37
+ db.define(name, definition);
38
+ }
39
+ function createMemoryModel() {
40
+ const rows = [];
41
+ let nextId = 1;
42
+ const matches = (row, query) => Object.entries(query).every(([key, value]) => row[key] === value);
43
+ return {
44
+ select: () => {
45
+ const promise = Promise.resolve().then(() => rows.map((row) => ({ ...row })));
46
+ return {
47
+ where: async (query) => rows.filter((row) => matches(row, query)).map((row) => ({ ...row })),
48
+ then: (onfulfilled, onrejected) => promise.then(onfulfilled, onrejected),
49
+ };
50
+ },
51
+ insert: async (row) => {
52
+ const withId = { id: String(nextId++), ...row };
53
+ rows.push(withId);
54
+ return { ...withId };
55
+ },
56
+ delete: () => ({
57
+ where: async (query) => {
58
+ for (let index = rows.length - 1; index >= 0; index -= 1)
59
+ if (matches(rows[index], query))
60
+ rows.splice(index, 1);
61
+ },
62
+ }),
63
+ update: (patch) => ({
64
+ where: async (query) => {
65
+ for (const row of rows)
66
+ if (matches(row, query))
67
+ Object.assign(row, patch);
68
+ },
69
+ }),
70
+ };
71
+ }
72
+ export function createInMemoryBiliDb() {
73
+ const models = new Map(Object.keys(BILI_TABLE_DEFINITIONS).map((name) => [name, createMemoryModel()]));
74
+ return { models: { get: (name) => models.get(name) } };
75
+ }
76
+ export function getBiliSubs(db) {
77
+ return db.models.get(BILI_SUBS_TABLE) ?? null;
78
+ }
79
+ export function getBiliState(db) {
80
+ return db.models.get(BILI_STATE_TABLE) ?? null;
81
+ }
82
+ export function parseKinds(input) {
83
+ const text = input.trim().toLowerCase();
84
+ if (!text || text === 'all' || text === '全部')
85
+ return 'all';
86
+ if (text === 'live' || text === '直播' || text === '开播')
87
+ return 'live';
88
+ if (text === 'dynamic' || text === 'dyn' || text === '动态')
89
+ return 'dynamic';
90
+ return undefined;
91
+ }
92
+ export function wantsLive(kinds) {
93
+ return kinds === 'all' || kinds === 'live' || kinds === undefined || kinds === '';
94
+ }
95
+ export function wantsDynamic(kinds) {
96
+ return kinds === 'all' || kinds === 'dynamic' || kinds === undefined || kinds === '';
97
+ }
98
+ function intOf(value, fallback) {
99
+ const parsed = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : Number.NaN;
100
+ return Number.isFinite(parsed) ? parsed : fallback;
101
+ }
102
+ export async function loadState(model, uid) {
103
+ const [row] = await model.select().where({ uid });
104
+ if (!row)
105
+ return undefined;
106
+ let seen = [];
107
+ try {
108
+ const parsed = JSON.parse(typeof row.seen_dynamics === 'string' ? row.seen_dynamics : '[]');
109
+ if (Array.isArray(parsed))
110
+ seen = parsed.filter((id) => typeof id === 'string');
111
+ }
112
+ catch { /* corrupted cache: start over */ }
113
+ return {
114
+ uid,
115
+ liveStatus: intOf(row.live_status, -1),
116
+ liveTime: intOf(row.live_time, 0),
117
+ lastDynamicTs: intOf(row.last_dynamic_ts, 0),
118
+ seenDynamics: seen,
119
+ dynamicReady: intOf(row.dynamic_ready, 0) === 1,
120
+ };
121
+ }
122
+ export const SEEN_DYNAMICS_LIMIT = 40;
123
+ export async function saveState(model, state) {
124
+ const patch = {
125
+ live_status: state.liveStatus,
126
+ live_time: state.liveTime,
127
+ last_dynamic_ts: state.lastDynamicTs,
128
+ seen_dynamics: JSON.stringify(state.seenDynamics.slice(-SEEN_DYNAMICS_LIMIT)),
129
+ dynamic_ready: state.dynamicReady ? 1 : 0,
130
+ updated_at: new Date().toISOString(),
131
+ };
132
+ const existing = await model.select().where({ uid: state.uid });
133
+ if (existing.length > 0)
134
+ await model.update(patch).where({ uid: state.uid });
135
+ else
136
+ await model.insert({ uid: state.uid, ...patch });
137
+ }
138
+ export function emptyState(uid) {
139
+ return { uid, liveStatus: -1, liveTime: 0, lastDynamicTs: 0, seenDynamics: [], dynamicReady: false };
140
+ }
141
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Subscription / watcher-state tables.\n *\n * The model surface matches the Plugin Runtime `DatabaseHostModel`, so the\n * scoped `databaseHostToken` facade is used directly when a database is\n * configured; otherwise an in-memory store keeps chat-only bots working\n * (subscriptions are then lost on restart).\n */\n\nexport type BiliRow = Record<string, unknown>;\n\nexport interface BiliModel {\n select: (...fields: string[]) => {\n where: (query: Record<string, unknown>) => PromiseLike<BiliRow[]>;\n then: <TResult1 = BiliRow[], TResult2 = never>(\n onfulfilled?: ((value: BiliRow[]) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ) => Promise<TResult1 | TResult2>;\n };\n insert: (row: Record<string, unknown>) => Promise<unknown>;\n delete: () => { where: (query: Record<string, unknown>) => Promise<unknown> };\n update: (patch: Record<string, unknown>) => {\n where: (query: Record<string, unknown>) => Promise<unknown>;\n };\n}\n\nexport interface BiliDb {\n models: { get: (name: string) => BiliModel | undefined };\n}\n\nexport const BILI_SUBS_TABLE = 'bilibili_subscriptions';\nexport const BILI_STATE_TABLE = 'bilibili_watch_state';\n\nexport const BILI_TABLE_DEFINITIONS: Readonly<Record<string, Record<string, unknown>>> = Object.freeze({\n [BILI_SUBS_TABLE]: {\n uid: { type: 'text', nullable: false },\n uname: { type: 'text', default: '' },\n /** `all` | `live` | `dynamic` */\n kinds: { type: 'text', default: 'all' },\n adapter_name: { type: 'text', nullable: false },\n endpoint_id: { type: 'text', default: '' },\n channel_type: { type: 'text', default: 'private' },\n channel_id: { type: 'text', nullable: false },\n creator_id: { type: 'text', default: '' },\n created_at: { type: 'text', default: '' },\n },\n [BILI_STATE_TABLE]: {\n uid: { type: 'text', nullable: false },\n live_status: { type: 'integer', default: -1 },\n live_time: { type: 'integer', default: 0 },\n last_dynamic_ts: { type: 'integer', default: 0 },\n /** JSON array of recently pushed dynamic ids (dedupe across restarts). */\n seen_dynamics: { type: 'text', default: '[]' },\n dynamic_ready: { type: 'integer', default: 0 },\n updated_at: { type: 'text', default: '' },\n },\n});\n\nexport function defineBiliTables(db: { define: (name: string, schema: Record<string, unknown>) => void }): void {\n for (const [name, definition] of Object.entries(BILI_TABLE_DEFINITIONS)) db.define(name, definition);\n}\n\nfunction createMemoryModel(): BiliModel {\n const rows: BiliRow[] = [];\n let nextId = 1;\n const matches = (row: BiliRow, query: Record<string, unknown>): boolean =>\n Object.entries(query).every(([key, value]) => row[key] === value);\n return {\n select: () => {\n const promise = Promise.resolve().then(() => rows.map((row) => ({ ...row })));\n return {\n where: async (query: Record<string, unknown>) => rows.filter((row) => matches(row, query)).map((row) => ({ ...row })),\n then: (onfulfilled, onrejected) => promise.then(onfulfilled, onrejected),\n };\n },\n insert: async (row: Record<string, unknown>) => {\n const withId: BiliRow = { id: String(nextId++), ...row };\n rows.push(withId);\n return { ...withId };\n },\n delete: () => ({\n where: async (query: Record<string, unknown>) => {\n for (let index = rows.length - 1; index >= 0; index -= 1) if (matches(rows[index]!, query)) rows.splice(index, 1);\n },\n }),\n update: (patch: Record<string, unknown>) => ({\n where: async (query: Record<string, unknown>) => {\n for (const row of rows) if (matches(row, query)) Object.assign(row, patch);\n },\n }),\n };\n}\n\nexport function createInMemoryBiliDb(): BiliDb {\n const models = new Map<string, BiliModel>(Object.keys(BILI_TABLE_DEFINITIONS).map((name) => [name, createMemoryModel()]));\n return { models: { get: (name: string) => models.get(name) } };\n}\n\nexport function getBiliSubs(db: BiliDb): BiliModel | null {\n return db.models.get(BILI_SUBS_TABLE) ?? null;\n}\n\nexport function getBiliState(db: BiliDb): BiliModel | null {\n return db.models.get(BILI_STATE_TABLE) ?? null;\n}\n\nexport type SubscriptionKinds = 'all' | 'live' | 'dynamic';\n\nexport function parseKinds(input: string): SubscriptionKinds | undefined {\n const text = input.trim().toLowerCase();\n if (!text || text === 'all' || text === '全部') return 'all';\n if (text === 'live' || text === '直播' || text === '开播') return 'live';\n if (text === 'dynamic' || text === 'dyn' || text === '动态') return 'dynamic';\n return undefined;\n}\n\nexport function wantsLive(kinds: unknown): boolean {\n return kinds === 'all' || kinds === 'live' || kinds === undefined || kinds === '';\n}\n\nexport function wantsDynamic(kinds: unknown): boolean {\n return kinds === 'all' || kinds === 'dynamic' || kinds === undefined || kinds === '';\n}\n\nexport interface WatchState {\n uid: string;\n liveStatus: number;\n liveTime: number;\n lastDynamicTs: number;\n seenDynamics: string[];\n dynamicReady: boolean;\n}\n\nfunction intOf(value: unknown, fallback: number): number {\n const parsed = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : Number.NaN;\n return Number.isFinite(parsed) ? parsed : fallback;\n}\n\nexport async function loadState(model: BiliModel, uid: string): Promise<WatchState | undefined> {\n const [row] = await model.select().where({ uid });\n if (!row) return undefined;\n let seen: string[] = [];\n try {\n const parsed: unknown = JSON.parse(typeof row.seen_dynamics === 'string' ? row.seen_dynamics : '[]');\n if (Array.isArray(parsed)) seen = parsed.filter((id): id is string => typeof id === 'string');\n } catch { /* corrupted cache: start over */ }\n return {\n uid,\n liveStatus: intOf(row.live_status, -1),\n liveTime: intOf(row.live_time, 0),\n lastDynamicTs: intOf(row.last_dynamic_ts, 0),\n seenDynamics: seen,\n dynamicReady: intOf(row.dynamic_ready, 0) === 1,\n };\n}\n\nexport const SEEN_DYNAMICS_LIMIT = 40;\n\nexport async function saveState(model: BiliModel, state: WatchState): Promise<void> {\n const patch = {\n live_status: state.liveStatus,\n live_time: state.liveTime,\n last_dynamic_ts: state.lastDynamicTs,\n seen_dynamics: JSON.stringify(state.seenDynamics.slice(-SEEN_DYNAMICS_LIMIT)),\n dynamic_ready: state.dynamicReady ? 1 : 0,\n updated_at: new Date().toISOString(),\n };\n const existing = await model.select().where({ uid: state.uid });\n if (existing.length > 0) await model.update(patch).where({ uid: state.uid });\n else await model.insert({ uid: state.uid, ...patch });\n}\n\nexport function emptyState(uid: string): WatchState {\n return { uid, liveStatus: -1, liveTime: 0, lastDynamicTs: 0, seenDynamics: [], dynamicReady: false };\n}\n"]}
package/lib/wbi.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /** `https://i0.hdslb.com/bfs/wbi/7cd0...077c.png` → `7cd0...077c`. */
2
+ export declare function wbiKeyFromUrl(url: string): string;
3
+ export declare function mixinKey(imgKey: string, subKey: string): string;
4
+ /** Returns the signed query string (`a=1&wts=...&w_rid=...`). */
5
+ export declare function signWbi(params: Readonly<Record<string, string | number>>, key: string, nowSeconds?: number): string;
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.0",
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/tool": "1.1.7",
45
+ "@zhin.js/skill": "1.1.7"
46
+ },
47
+ "devDependencies": {
48
+ "typescript": "^6.0.3",
49
+ "vitest": "^4.1.10",
50
+ "zod": "^4.4.3",
51
+ "zhin.js": "1.1.11",
52
+ "@zhin.js/command": "1.1.8"
53
+ },
54
+ "peerDependencies": {
55
+ "zod": "^4.0.0",
56
+ "@zhin.js/command": "^1.1.8",
57
+ "zhin.js": "^1.1.11"
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
+ }