@zhin.js/adapter-napcat 0.1.13 → 0.1.14

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +121 -0
  3. package/client/NapCatManagement.tsx +113 -0
  4. package/client/index.tsx +11 -0
  5. package/client/tsconfig.json +7 -0
  6. package/client/utils/api.ts +30 -0
  7. package/dist/index.js +27 -0
  8. package/lib/agent-prompt.d.ts +3 -0
  9. package/lib/agent-prompt.d.ts.map +1 -0
  10. package/lib/agent-prompt.js +107 -0
  11. package/lib/agent-prompt.js.map +1 -0
  12. package/lib/bot-base.d.ts +6 -1
  13. package/lib/bot-base.d.ts.map +1 -1
  14. package/lib/bot-base.js +35 -1
  15. package/lib/bot-base.js.map +1 -1
  16. package/lib/bot-http.d.ts +1 -0
  17. package/lib/bot-http.d.ts.map +1 -1
  18. package/lib/bot-http.js +22 -0
  19. package/lib/bot-http.js.map +1 -1
  20. package/lib/bot-ws-client.d.ts +2 -0
  21. package/lib/bot-ws-client.d.ts.map +1 -1
  22. package/lib/bot-ws-client.js +32 -0
  23. package/lib/bot-ws-client.js.map +1 -1
  24. package/lib/bot-ws-server.d.ts +1 -0
  25. package/lib/bot-ws-server.d.ts.map +1 -1
  26. package/lib/bot-ws-server.js +14 -0
  27. package/lib/bot-ws-server.js.map +1 -1
  28. package/lib/index.d.ts +5 -1
  29. package/lib/index.d.ts.map +1 -1
  30. package/lib/index.js +22 -1
  31. package/lib/index.js.map +1 -1
  32. package/lib/napcat-inbound.d.ts +21 -0
  33. package/lib/napcat-inbound.d.ts.map +1 -0
  34. package/lib/napcat-inbound.js +55 -0
  35. package/lib/napcat-inbound.js.map +1 -0
  36. package/lib/onebot-get-msg.d.ts +3 -0
  37. package/lib/onebot-get-msg.d.ts.map +1 -0
  38. package/lib/onebot-get-msg.js +27 -0
  39. package/lib/onebot-get-msg.js.map +1 -0
  40. package/lib/routes.d.ts +4 -0
  41. package/lib/routes.d.ts.map +1 -0
  42. package/lib/routes.js +61 -0
  43. package/lib/routes.js.map +1 -0
  44. package/lib/types.d.ts +18 -0
  45. package/lib/types.d.ts.map +1 -1
  46. package/lib/typing-indicator.d.ts +46 -0
  47. package/lib/typing-indicator.d.ts.map +1 -0
  48. package/lib/typing-indicator.js +138 -0
  49. package/lib/typing-indicator.js.map +1 -0
  50. package/package.json +19 -5
  51. package/plugin.yml +1 -1
  52. package/src/agent-prompt.ts +115 -0
  53. package/src/bot-base.ts +36 -1
  54. package/src/bot-http.ts +18 -0
  55. package/src/bot-ws-client.ts +34 -0
  56. package/src/bot-ws-server.ts +15 -0
  57. package/src/index.ts +41 -2
  58. package/src/napcat-inbound.ts +57 -0
  59. package/src/onebot-get-msg.ts +33 -0
  60. package/src/routes.ts +61 -0
  61. package/src/types.ts +13 -0
  62. package/src/typing-indicator.ts +196 -0
@@ -6,6 +6,7 @@ import { formatCompact } from 'zhin.js';
6
6
  import { NapCatBotBase } from './bot-base.js';
7
7
  import type { NapCatWsClientConfig, ApiResponse } from './types.js';
8
8
  import type { NapCatAdapter } from './adapter.js';
9
+ import { enableTypingIndicator } from './typing-indicator.js';
9
10
 
10
11
  export class NapCatWsClient extends NapCatBotBase {
11
12
  private ws?: WebSocket;
@@ -43,6 +44,7 @@ export class NapCatWsClient extends NapCatBotBase {
43
44
  }
44
45
  this.logger.info(formatCompact({ bot: this.$id, mode: 'ws' }));
45
46
  this.startHeartbeat();
47
+ this.initTypingIndicator();
46
48
  resolve();
47
49
  });
48
50
 
@@ -87,6 +89,7 @@ export class NapCatWsClient extends NapCatBotBase {
87
89
  for (const [, req] of this.pendingRequests) { clearTimeout(req.timeout); req.reject(new Error('Connection closed')); }
88
90
  this.pendingRequests.clear();
89
91
  if (this.ws) { this.ws.close(); this.ws = undefined; }
92
+ this.inboundDeduper.clear();
90
93
  this.$connected = false;
91
94
  }
92
95
 
@@ -127,4 +130,35 @@ export class NapCatWsClient extends NapCatBotBase {
127
130
  try { await this.$connect(); } catch { this.scheduleReconnect(); }
128
131
  }, interval);
129
132
  }
133
+
134
+ protected handleMeta(event: any): void {
135
+ if (event.meta_event_type === 'lifecycle' && event.sub_type === 'connect') {
136
+ this.logger.info(formatCompact({ bot: this.$id, lifecycle: 'connect', self_id: event.self_id }));
137
+ }
138
+ }
139
+
140
+ private initTypingIndicator(): void {
141
+ const tiConfig = this.$config.typingIndicator;
142
+ if (tiConfig && tiConfig.enabled !== false) {
143
+ enableTypingIndicator(this, {
144
+ enabled: tiConfig.enabled ?? true,
145
+ defaultEmoji: tiConfig.defaultEmoji || '128516',
146
+ autoRemove: true,
147
+ removeDelay: 5000,
148
+ privateConfig: tiConfig.privateConfig ? {
149
+ type: tiConfig.privateConfig.type || 'message',
150
+ message: tiConfig.privateConfig.message || '正在思考中...',
151
+ autoRemove: true,
152
+ removeDelay: 3000,
153
+ } : undefined,
154
+ groupConfig: tiConfig.groupConfig ? {
155
+ type: tiConfig.groupConfig.type || 'reaction',
156
+ emoji: tiConfig.groupConfig.emoji || '128516',
157
+ autoRemove: true,
158
+ removeDelay: 5000,
159
+ } : undefined,
160
+ });
161
+ this.logger.info(formatCompact({ bot: this.$id, typingIndicator: 'enabled' }));
162
+ }
163
+ }
130
164
  }
@@ -8,6 +8,7 @@ import { NapCatBotBase } from './bot-base.js';
8
8
  import type { NapCatWsServerConfig, ApiResponse } from './types.js';
9
9
  import type { NapCatAdapter } from './adapter.js';
10
10
  import type { Router } from '@zhin.js/http';
11
+ import { enableTypingIndicator } from './typing-indicator.js';
11
12
 
12
13
  export class NapCatWsServer extends NapCatBotBase {
13
14
  #wss?: WebSocketServer;
@@ -76,6 +77,7 @@ export class NapCatWsServer extends NapCatBotBase {
76
77
  if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = undefined; }
77
78
  for (const [, req] of this.pendingRequests) { clearTimeout(req.timeout); req.reject(new Error('Connection closed')); }
78
79
  this.pendingRequests.clear();
80
+ this.inboundDeduper.clear();
79
81
  this.$connected = false;
80
82
  }
81
83
 
@@ -118,6 +120,7 @@ export class NapCatWsServer extends NapCatBotBase {
118
120
  this.#clientMap.set(String(message.self_id), client);
119
121
  this.$connected = true;
120
122
  this.logger.info(formatCompact({ bot: this.$id, self_id: message.self_id }));
123
+ this.initTypingIndicator();
121
124
  return;
122
125
  }
123
126
  this.dispatchEvent(message);
@@ -132,4 +135,16 @@ export class NapCatWsServer extends NapCatBotBase {
132
135
  }
133
136
  }, interval);
134
137
  }
138
+
139
+ private initTypingIndicator(): void {
140
+ const tiConfig = (this.$config as any).typingIndicator;
141
+ if (tiConfig && tiConfig.enabled !== false) {
142
+ enableTypingIndicator(this, {
143
+ enabled: true,
144
+ defaultEmoji: tiConfig.defaultEmoji || '128516',
145
+ autoRemove: true,
146
+ removeDelay: 5000,
147
+ });
148
+ }
149
+ }
135
150
  }
package/src/index.ts CHANGED
@@ -3,9 +3,23 @@
3
3
  * 支持 OneBot11 标准 + go-cqhttp 扩展 + NapCat 独有 API
4
4
  * 连接方式:正向 WS / 反向 WS / HTTP
5
5
  */
6
- import { usePlugin, type Plugin, type Context, type IGroupManagement, createGroupManagementTools, type ToolFeature } from 'zhin.js';
6
+ import path from 'path';
7
+ import {
8
+ usePlugin,
9
+ type Plugin,
10
+ type Context,
11
+ type IGroupManagement,
12
+ createGroupManagementTools,
13
+ type ToolFeature,
14
+ registerAgentPromptContributor,
15
+ unregisterAgentPromptContributor,
16
+ } from 'zhin.js';
17
+ import type { Router } from '@zhin.js/http';
18
+ import { PageManager } from '@zhin.js/console';
7
19
  import { NapCatAdapter } from './adapter.js';
8
20
  import { createNapCatTools } from './tools.js';
21
+ import { createNapCatAgentPromptContributor } from './agent-prompt.js';
22
+ import { registerRoutes } from './routes.js';
9
23
 
10
24
  export * from './types.js';
11
25
  export { NapCatWsClient } from './bot-ws-client.js';
@@ -13,11 +27,17 @@ export { NapCatWsServer } from './bot-ws-server.js';
13
27
  export { NapCatHttpBot } from './bot-http.js';
14
28
  export { NapCatAdapter, type NapCatBot } from './adapter.js';
15
29
  export { NapCatBotBase } from './bot-base.js';
30
+ export {
31
+ NapCatTypingIndicatorManager,
32
+ enableTypingIndicator,
33
+ type NapCatTypingIndicatorConfig,
34
+ } from './typing-indicator.js';
16
35
 
17
36
  declare module 'zhin.js' {
18
37
  namespace Plugin {
19
38
  interface Contexts {
20
- router: import('@zhin.js/http').Router;
39
+ web: PageManager;
40
+ router: Router;
21
41
  }
22
42
  }
23
43
  interface Adapters {
@@ -28,19 +48,23 @@ declare module 'zhin.js' {
28
48
  const plugin = usePlugin();
29
49
  const { provide, useContext } = plugin;
30
50
 
51
+ // ── 适配器注册 ─────────────────────────────────────────────────────
31
52
  provide({
32
53
  name: 'napcat',
33
54
  description: 'NapCatQQ 适配器(OneBot11 + go-cqhttp + NapCat 扩展,正向/反向 WS + HTTP)',
34
55
  mounted: async (p: Plugin) => {
56
+ registerAgentPromptContributor(createNapCatAgentPromptContributor());
35
57
  const adapter = new NapCatAdapter(p);
36
58
  await adapter.start();
37
59
  return adapter;
38
60
  },
39
61
  dispose: async (adapter: NapCatAdapter) => {
62
+ unregisterAgentPromptContributor('napcat');
40
63
  await adapter.stop();
41
64
  },
42
65
  } as unknown as Context<'napcat'>);
43
66
 
67
+ // ── AI 工具注册 ────────────────────────────────────────────────────
44
68
  useContext('tool', 'napcat', (toolService: ToolFeature, napcat: NapCatAdapter) => {
45
69
  const disposers: (() => void)[] = [];
46
70
 
@@ -55,3 +79,18 @@ useContext('tool', 'napcat', (toolService: ToolFeature, napcat: NapCatAdapter) =
55
79
 
56
80
  return () => disposers.forEach(d => d());
57
81
  });
82
+
83
+ // ── Web 控制台入口 ─────────────────────────────────────────────────
84
+ useContext('web', (pageManager) => {
85
+ pageManager.addEntry({
86
+ id: 'napcat',
87
+ development: path.resolve(import.meta.dirname, '../client/index.tsx'),
88
+ production: path.resolve(import.meta.dirname, '../dist/index.js'),
89
+ meta: { name: 'NapCat' },
90
+ });
91
+ });
92
+
93
+ // ── HTTP 路由 ──────────────────────────────────────────────────────
94
+ useContext('router', 'napcat', async (router: Router, napcat: NapCatAdapter) => {
95
+ registerRoutes(router, napcat);
96
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * NapCat 入站消息治理:去重、自发过滤、消息归一化
3
+ */
4
+ import type { NapCatMessageEvent, MessageSegment } from './types.js';
5
+
6
+ const DEDUPE_TTL_MS = 120_000;
7
+
8
+ export class InboundMessageDeduper {
9
+ private readonly seen = new Map<string, number>();
10
+
11
+ shouldProcess(messageId: string): boolean {
12
+ const now = Date.now();
13
+ for (const [id, t] of this.seen) {
14
+ if (now - t > DEDUPE_TTL_MS) this.seen.delete(id);
15
+ }
16
+ if (this.seen.has(messageId)) return false;
17
+ this.seen.set(messageId, now);
18
+ return true;
19
+ }
20
+
21
+ clear(): void {
22
+ this.seen.clear();
23
+ }
24
+ }
25
+
26
+ /** 判断是否为 bot 自身发出的消息 */
27
+ export function isSelfMessage(event: NapCatMessageEvent): boolean {
28
+ if (event.post_type === 'message_sent') return true;
29
+ if (event.self_id != null && event.user_id != null) {
30
+ return Number(event.self_id) === Number(event.user_id);
31
+ }
32
+ return false;
33
+ }
34
+
35
+ /**
36
+ * 将 message 字段归一化为 MessageSegment[]。
37
+ * NapCat 通常返回数组,但某些配置下可能返回 CQ 字符串。
38
+ */
39
+ export function normalizeMessage(message: MessageSegment[] | string): MessageSegment[] {
40
+ if (Array.isArray(message)) return message;
41
+ if (typeof message === 'string') {
42
+ return [{ type: 'text', data: { text: message } }];
43
+ }
44
+ return [];
45
+ }
46
+
47
+ /**
48
+ * 生成用于 notice / request 事件的去重 key
49
+ */
50
+ export function resolveSideEventDedupeKey(event: any, prefix: string): string {
51
+ const parts = [prefix, event.time, event.notice_type || event.request_type];
52
+ if (event.group_id) parts.push(event.group_id);
53
+ if (event.user_id) parts.push(event.user_id);
54
+ if (event.message_id) parts.push(event.message_id);
55
+ if (event.flag) parts.push(event.flag);
56
+ return parts.join(':');
57
+ }
@@ -0,0 +1,33 @@
1
+ import type { MessageSegment, QuotedMessagePayload } from 'zhin.js';
2
+
3
+ export function parseOneBotGetMsgResponse(
4
+ messageId: string,
5
+ data: unknown,
6
+ ): QuotedMessagePayload {
7
+ const record =
8
+ data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
9
+ let content: QuotedMessagePayload['content'] = [];
10
+ if (Array.isArray(record.message)) {
11
+ content = record.message as MessageSegment[];
12
+ } else if (typeof record.raw_message === 'string' && record.raw_message) {
13
+ content = [{ type: 'text', data: { text: record.raw_message } }];
14
+ }
15
+
16
+ const senderRaw = record.sender;
17
+ let sender: QuotedMessagePayload['sender'];
18
+ if (senderRaw && typeof senderRaw === 'object') {
19
+ const s = senderRaw as Record<string, unknown>;
20
+ sender = {
21
+ id: String(s.user_id ?? ''),
22
+ name: String(s.nickname ?? s.card ?? ''),
23
+ };
24
+ }
25
+
26
+ return {
27
+ messageId,
28
+ sender,
29
+ content,
30
+ raw: typeof record.raw_message === 'string' ? record.raw_message : undefined,
31
+ time: typeof record.time === 'number' ? record.time : undefined,
32
+ };
33
+ }
package/src/routes.ts ADDED
@@ -0,0 +1,61 @@
1
+ import type { Router } from '@zhin.js/http';
2
+ import type { NapCatAdapter } from './adapter.js';
3
+
4
+ export function registerRoutes(router: Router, napcat: NapCatAdapter): void {
5
+ router.get('/api/napcat/bots', async (ctx) => {
6
+ try {
7
+ const bots = Array.from(napcat.bots.values());
8
+ if (bots.length === 0) {
9
+ ctx.body = { success: true, data: [], message: '暂无 NapCat 机器人实例' };
10
+ return;
11
+ }
12
+ const result = await Promise.all(
13
+ bots.map(async (bot) => {
14
+ try {
15
+ const connection = (bot.$config as any).connection ?? 'ws';
16
+ const base: Record<string, unknown> = {
17
+ name: bot.$config.name,
18
+ connected: bot.$connected || false,
19
+ connection,
20
+ status: bot.$connected ? 'online' : 'offline',
21
+ lastActivity: new Date().toISOString(),
22
+ };
23
+ if (bot.$connected) {
24
+ try {
25
+ const friends = await bot.getFriendList();
26
+ const groups = await bot.getGroupList();
27
+ base.friendCount = Array.isArray(friends) ? friends.length : 0;
28
+ base.groupCount = Array.isArray(groups) ? groups.length : 0;
29
+ } catch {
30
+ base.friendCount = 0;
31
+ base.groupCount = 0;
32
+ }
33
+ } else {
34
+ base.friendCount = 0;
35
+ base.groupCount = 0;
36
+ }
37
+ return base;
38
+ } catch {
39
+ return {
40
+ name: bot.$config.name,
41
+ connected: false,
42
+ connection: 'unknown',
43
+ status: 'error',
44
+ friendCount: 0,
45
+ groupCount: 0,
46
+ };
47
+ }
48
+ }),
49
+ );
50
+ ctx.body = { success: true, data: result, timestamp: new Date().toISOString() };
51
+ } catch (error) {
52
+ ctx.status = 500;
53
+ ctx.body = {
54
+ success: false,
55
+ error: 'NAPCAT_API_ERROR',
56
+ message: '获取机器人数据失败',
57
+ details: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined,
58
+ };
59
+ }
60
+ });
61
+ }
package/src/types.ts CHANGED
@@ -5,10 +5,23 @@
5
5
 
6
6
  // ── 配置 ─────────────────────────────────────────────────────────────
7
7
 
8
+ export interface TypingIndicatorConfig {
9
+ /** 是否启用(默认 true) */
10
+ enabled?: boolean;
11
+ /** 默认表情 ID */
12
+ defaultEmoji?: string;
13
+ /** 私聊配置 */
14
+ privateConfig?: { type?: 'message' | 'typing'; message?: string };
15
+ /** 群聊配置 */
16
+ groupConfig?: { type?: 'reaction'; emoji?: string };
17
+ }
18
+
8
19
  export interface NapCatConfigBase {
9
20
  context: 'napcat';
10
21
  name: string;
11
22
  access_token?: string;
23
+ /** Typing Indicator 配置 */
24
+ typingIndicator?: TypingIndicatorConfig;
12
25
  }
13
26
 
14
27
  /** 正向 WebSocket */
@@ -0,0 +1,196 @@
1
+ /**
2
+ * NapCat Typing Indicator 集成
3
+ *
4
+ * 群聊:set_msg_emoji_like(表情回应)
5
+ * 私聊:set_input_status(输入状态)或短暂提示消息
6
+ */
7
+
8
+ import type { NapCatBotBase } from './bot-base.js';
9
+ import {
10
+ TypingIndicatorManager,
11
+ ReactionTypingIndicatorAdapter,
12
+ type TypingIndicatorOptions,
13
+ type TypingIndicatorConfig,
14
+ type TypingIndicator,
15
+ } from '@zhin.js/agent';
16
+
17
+ export interface NapCatTypingIndicatorConfig {
18
+ enabled: boolean;
19
+ defaultEmoji: string;
20
+ autoRemove: boolean;
21
+ removeDelay: number;
22
+ privateConfig?: Partial<TypingIndicatorConfig>;
23
+ groupConfig?: Partial<TypingIndicatorConfig>;
24
+ }
25
+
26
+ const DEFAULT_CONFIG: NapCatTypingIndicatorConfig = {
27
+ enabled: true,
28
+ defaultEmoji: '128516',
29
+ autoRemove: true,
30
+ removeDelay: 5000,
31
+ privateConfig: {
32
+ type: 'message',
33
+ message: '正在思考中...',
34
+ autoRemove: true,
35
+ removeDelay: 3000,
36
+ },
37
+ groupConfig: {
38
+ type: 'reaction',
39
+ emoji: '128516',
40
+ autoRemove: true,
41
+ removeDelay: 5000,
42
+ },
43
+ };
44
+
45
+ export class NapCatTypingIndicatorManager {
46
+ private manager: TypingIndicatorManager;
47
+ private config: NapCatTypingIndicatorConfig;
48
+ private bot: NapCatBotBase;
49
+
50
+ private currentOptions?: {
51
+ messageId?: string;
52
+ sessionId: string;
53
+ userId?: string;
54
+ groupId?: string;
55
+ sceneType: 'private' | 'group';
56
+ };
57
+
58
+ constructor(bot: NapCatBotBase, config: Partial<NapCatTypingIndicatorConfig> = {}) {
59
+ this.bot = bot;
60
+ this.config = { ...DEFAULT_CONFIG, ...config };
61
+ this.manager = new TypingIndicatorManager({
62
+ type: 'reaction',
63
+ emoji: this.config.defaultEmoji,
64
+ autoRemove: this.config.autoRemove,
65
+ removeDelay: this.config.removeDelay,
66
+ });
67
+ this.registerAdapter();
68
+ }
69
+
70
+ private registerAdapter(): void {
71
+ const adapter = new ReactionTypingIndicatorAdapter(
72
+ async (messageId: string, emoji: string) => {
73
+ return await this.bot.$addReaction(messageId, emoji);
74
+ },
75
+ async (messageId: string, reactionId: string) => {
76
+ await this.bot.$removeReaction(messageId, reactionId);
77
+ },
78
+ async (sessionId: string, content: string) => {
79
+ try {
80
+ const options = this.currentOptions;
81
+ if (!options) return null;
82
+ if (options.sceneType === 'group' && options.groupId) {
83
+ return await this.bot.$sendMessage({
84
+ type: 'group',
85
+ id: options.groupId,
86
+ context: 'napcat',
87
+ bot: this.bot.$id,
88
+ content: [{ type: 'text', data: { text: content } }],
89
+ });
90
+ } else if (options.userId) {
91
+ return await this.bot.$sendMessage({
92
+ type: 'private',
93
+ id: options.userId,
94
+ context: 'napcat',
95
+ bot: this.bot.$id,
96
+ content: [{ type: 'text', data: { text: content } }],
97
+ });
98
+ }
99
+ return null;
100
+ } catch {
101
+ return null;
102
+ }
103
+ },
104
+ async (messageId: string) => {
105
+ try {
106
+ await this.bot.$recallMessage(messageId);
107
+ } catch { /* ignore */ }
108
+ },
109
+ );
110
+ this.manager.registerAdapter(adapter);
111
+ }
112
+
113
+ async start(options: {
114
+ messageId?: string;
115
+ sessionId: string;
116
+ userId?: string;
117
+ groupId?: string;
118
+ sceneType: 'private' | 'group';
119
+ }): Promise<TypingIndicator> {
120
+ this.bot.logger.debug('[NapCat:TypingIndicator] start called', {
121
+ enabled: this.config.enabled,
122
+ sceneType: options.sceneType,
123
+ });
124
+
125
+ if (!this.config.enabled) {
126
+ return {
127
+ start: async () => {},
128
+ stop: async () => {},
129
+ isActive: () => false,
130
+ };
131
+ }
132
+
133
+ this.currentOptions = options;
134
+
135
+ const config = options.sceneType === 'group'
136
+ ? this.config.groupConfig
137
+ : this.config.privateConfig;
138
+
139
+ const typingOptions: TypingIndicatorOptions = {
140
+ messageId: options.messageId,
141
+ sessionId: options.sessionId,
142
+ userId: options.userId,
143
+ groupId: options.groupId,
144
+ platform: 'napcat',
145
+ botId: this.bot.$id,
146
+ sceneType: options.sceneType,
147
+ };
148
+
149
+ return await this.manager.start(typingOptions, config);
150
+ }
151
+
152
+ async stop(options: {
153
+ sessionId: string;
154
+ userId?: string;
155
+ groupId?: string;
156
+ }): Promise<void> {
157
+ const typingOptions: TypingIndicatorOptions = {
158
+ sessionId: options.sessionId,
159
+ userId: options.userId,
160
+ groupId: options.groupId,
161
+ platform: 'napcat',
162
+ botId: this.bot.$id,
163
+ sceneType: 'private',
164
+ };
165
+ await this.manager.stop(typingOptions);
166
+ this.currentOptions = undefined;
167
+ }
168
+
169
+ async stopAll(): Promise<void> {
170
+ await this.manager.stopAll();
171
+ }
172
+
173
+ updateConfig(config: Partial<NapCatTypingIndicatorConfig>): void {
174
+ this.config = { ...this.config, ...config };
175
+ }
176
+
177
+ getConfig(): NapCatTypingIndicatorConfig {
178
+ return { ...this.config };
179
+ }
180
+ }
181
+
182
+ declare module './bot-base.js' {
183
+ interface NapCatBotBase {
184
+ $typingIndicator?: NapCatTypingIndicatorManager;
185
+ }
186
+ }
187
+
188
+ export function enableTypingIndicator(
189
+ bot: NapCatBotBase,
190
+ config?: Partial<NapCatTypingIndicatorConfig>,
191
+ ): NapCatTypingIndicatorManager {
192
+ if (!(bot as any).$typingIndicator) {
193
+ (bot as any).$typingIndicator = new NapCatTypingIndicatorManager(bot, config);
194
+ }
195
+ return (bot as any).$typingIndicator;
196
+ }