@zhin.js/adapter-napcat 1.0.0 → 1.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.
Files changed (159) hide show
  1. package/CHANGELOG.md +674 -0
  2. package/README.md +60 -95
  3. package/adapters/napcat.js +47 -0
  4. package/adapters/napcat.ts +64 -0
  5. package/agent/PERMITS.md +19 -0
  6. package/{skills/napcat/SKILL.md → agent/skills/napcat.md} +6 -6
  7. package/agent/tools/ai_tts.ts +19 -0
  8. package/agent/tools/del_group_notice.ts +20 -0
  9. package/agent/tools/delete_essence_msg.ts +19 -0
  10. package/agent/tools/delete_friend.ts +18 -0
  11. package/agent/tools/download_file.ts +17 -0
  12. package/agent/tools/forward_single_msg.ts +28 -0
  13. package/agent/tools/get_ai_characters.ts +17 -0
  14. package/agent/tools/get_essence_list.ts +17 -0
  15. package/agent/tools/get_friend_msg_history.ts +18 -0
  16. package/agent/tools/get_group_file_url.ts +19 -0
  17. package/agent/tools/get_group_info_ex.ts +17 -0
  18. package/agent/tools/get_group_msg_history.ts +19 -0
  19. package/agent/tools/get_group_notice.ts +17 -0
  20. package/agent/tools/get_group_root_files.ts +17 -0
  21. package/agent/tools/get_group_shut_list.ts +17 -0
  22. package/agent/tools/get_mini_app_ark.ts +20 -0
  23. package/agent/tools/get_user_status.ts +16 -0
  24. package/agent/tools/group_sign.ts +18 -0
  25. package/agent/tools/mark_msg_as_read.ts +17 -0
  26. package/agent/tools/ocr_image.ts +16 -0
  27. package/agent/tools/send_forward_msg.ts +27 -0
  28. package/agent/tools/send_group_notice.ts +21 -0
  29. package/agent/tools/send_like.ts +18 -0
  30. package/agent/tools/send_poke.ts +18 -0
  31. package/agent/tools/set_avatar.ts +17 -0
  32. package/agent/tools/set_emoji_reaction.ts +18 -0
  33. package/agent/tools/set_essence_msg.ts +19 -0
  34. package/agent/tools/set_group_portrait.ts +20 -0
  35. package/agent/tools/set_online_status.ts +18 -0
  36. package/agent/tools/set_profile.ts +21 -0
  37. package/agent/tools/set_signature.ts +17 -0
  38. package/agent/tools/set_title.ts +21 -0
  39. package/agent/tools/translate.ts +16 -0
  40. package/agent/tools/upload_group_file.ts +21 -0
  41. package/commands/endpoint/add/[id].js +3 -0
  42. package/commands/endpoint/add/[id].ts +3 -0
  43. package/commands/endpoint/list.js +3 -0
  44. package/commands/endpoint/list.ts +3 -0
  45. package/commands/endpoint/remove/[id].js +3 -0
  46. package/commands/endpoint/remove/[id].ts +3 -0
  47. package/lib/client.d.ts +52 -0
  48. package/lib/client.js +62 -0
  49. package/lib/endpoint-management.d.ts +15 -0
  50. package/lib/endpoint-management.js +47 -0
  51. package/lib/http-endpoint.d.ts +27 -0
  52. package/lib/http-endpoint.js +177 -0
  53. package/lib/index.d.ts +11 -22
  54. package/lib/index.js +10 -58
  55. package/lib/napcat-endpoint-commands.d.ts +1 -0
  56. package/lib/napcat-endpoint-commands.js +28 -0
  57. package/lib/napcat-inbound.d.ts +12 -5
  58. package/lib/napcat-inbound.js +28 -13
  59. package/lib/napcat-runtime-state.d.ts +1 -0
  60. package/lib/napcat-runtime-state.js +6 -0
  61. package/lib/onebot-get-msg.d.ts +16 -3
  62. package/lib/onebot-get-msg.js +53 -1
  63. package/lib/protocol.d.ts +165 -0
  64. package/lib/protocol.js +305 -0
  65. package/lib/side-event-dispatch.d.ts +7 -0
  66. package/lib/side-event-dispatch.js +36 -0
  67. package/lib/webhook.d.ts +2 -0
  68. package/lib/webhook.js +14 -0
  69. package/lib/ws-endpoint.d.ts +27 -0
  70. package/lib/ws-endpoint.js +241 -0
  71. package/lib/ws-transport.d.ts +13 -0
  72. package/lib/ws-transport.js +75 -0
  73. package/lib/ws-types.d.ts +17 -0
  74. package/lib/ws-types.js +1 -0
  75. package/lib/wss-auth.d.ts +2 -0
  76. package/lib/wss-auth.js +16 -0
  77. package/lib/wss-endpoint.d.ts +26 -0
  78. package/lib/wss-endpoint.js +187 -0
  79. package/package.json +62 -30
  80. package/plugin.js +18 -0
  81. package/schema.json +110 -0
  82. package/src/client.ts +79 -0
  83. package/src/endpoint-management.ts +72 -0
  84. package/src/http-endpoint.ts +234 -0
  85. package/src/index.ts +63 -85
  86. package/src/napcat-endpoint-commands.ts +26 -0
  87. package/src/napcat-inbound.ts +34 -10
  88. package/src/napcat-runtime-state.ts +7 -0
  89. package/src/onebot-get-msg.ts +69 -5
  90. package/src/protocol.ts +468 -0
  91. package/src/side-event-dispatch.ts +48 -0
  92. package/src/webhook.ts +16 -0
  93. package/src/ws-endpoint.ts +301 -0
  94. package/src/ws-transport.ts +105 -0
  95. package/src/ws-types.ts +20 -0
  96. package/src/wss-auth.ts +17 -0
  97. package/src/wss-endpoint.ts +242 -0
  98. package/client/NapCatManagement.tsx +0 -113
  99. package/client/index.tsx +0 -11
  100. package/client/tsconfig.json +0 -7
  101. package/client/utils/api.ts +0 -30
  102. package/dist/index.js +0 -27
  103. package/lib/adapter.d.ts +0 -27
  104. package/lib/adapter.d.ts.map +0 -1
  105. package/lib/adapter.js +0 -93
  106. package/lib/adapter.js.map +0 -1
  107. package/lib/agent-prompt.d.ts +0 -3
  108. package/lib/agent-prompt.d.ts.map +0 -1
  109. package/lib/agent-prompt.js +0 -105
  110. package/lib/agent-prompt.js.map +0 -1
  111. package/lib/bot-base.d.ts +0 -143
  112. package/lib/bot-base.d.ts.map +0 -1
  113. package/lib/bot-base.js +0 -334
  114. package/lib/bot-base.js.map +0 -1
  115. package/lib/bot-http.d.ts +0 -18
  116. package/lib/bot-http.d.ts.map +0 -1
  117. package/lib/bot-http.js +0 -119
  118. package/lib/bot-http.js.map +0 -1
  119. package/lib/bot-ws-client.d.ts +0 -21
  120. package/lib/bot-ws-client.d.ts.map +0 -1
  121. package/lib/bot-ws-client.js +0 -165
  122. package/lib/bot-ws-client.js.map +0 -1
  123. package/lib/bot-ws-server.d.ts +0 -21
  124. package/lib/bot-ws-server.d.ts.map +0 -1
  125. package/lib/bot-ws-server.js +0 -150
  126. package/lib/bot-ws-server.js.map +0 -1
  127. package/lib/index.d.ts.map +0 -1
  128. package/lib/index.js.map +0 -1
  129. package/lib/napcat-inbound.d.ts.map +0 -1
  130. package/lib/napcat-inbound.js.map +0 -1
  131. package/lib/onebot-get-msg.d.ts.map +0 -1
  132. package/lib/onebot-get-msg.js.map +0 -1
  133. package/lib/routes.d.ts +0 -4
  134. package/lib/routes.d.ts.map +0 -1
  135. package/lib/routes.js +0 -61
  136. package/lib/routes.js.map +0 -1
  137. package/lib/tools.d.ts +0 -8
  138. package/lib/tools.d.ts.map +0 -1
  139. package/lib/tools.js +0 -605
  140. package/lib/tools.js.map +0 -1
  141. package/lib/types.d.ts +0 -293
  142. package/lib/types.d.ts.map +0 -1
  143. package/lib/types.js +0 -6
  144. package/lib/types.js.map +0 -1
  145. package/lib/typing-indicator.d.ts +0 -45
  146. package/lib/typing-indicator.d.ts.map +0 -1
  147. package/lib/typing-indicator.js +0 -132
  148. package/lib/typing-indicator.js.map +0 -1
  149. package/plugin.yml +0 -3
  150. package/src/adapter.ts +0 -99
  151. package/src/agent-prompt.ts +0 -114
  152. package/src/bot-base.ts +0 -376
  153. package/src/bot-http.ts +0 -109
  154. package/src/bot-ws-client.ts +0 -164
  155. package/src/bot-ws-server.ts +0 -150
  156. package/src/routes.ts +0 -61
  157. package/src/tools.ts +0 -632
  158. package/src/types.ts +0 -279
  159. package/src/typing-indicator.ts +0 -184
@@ -1,164 +0,0 @@
1
- /**
2
- * NapCat 正向 WebSocket 连接
3
- */
4
- import WebSocket from 'ws';
5
- import { formatCompact } from 'zhin.js';
6
- import { NapCatBotBase } from './bot-base.js';
7
- import type { NapCatWsClientConfig, ApiResponse } from './types.js';
8
- import type { NapCatAdapter } from './adapter.js';
9
- import { enableTypingIndicator } from './typing-indicator.js';
10
-
11
- export class NapCatWsClient extends NapCatBotBase {
12
- private ws?: WebSocket;
13
- private reconnectTimer?: NodeJS.Timeout;
14
- private heartbeatTimer?: NodeJS.Timeout;
15
- private requestId = 0;
16
- private pendingRequests = new Map<string, {
17
- resolve: (value: any) => void;
18
- reject: (error: Error) => void;
19
- timeout: NodeJS.Timeout;
20
- }>();
21
-
22
- declare $config: NapCatWsClientConfig;
23
-
24
- constructor(adapter: NapCatAdapter, config: NapCatWsClientConfig) {
25
- super(adapter, config);
26
- }
27
-
28
- async $connect(): Promise<void> {
29
- return new Promise((resolve, reject) => {
30
- const headers: Record<string, string> = {};
31
- let url = this.$config.url;
32
- if (this.$config.access_token) {
33
- headers['Authorization'] = `Bearer ${this.$config.access_token}`;
34
- const u = new URL(url);
35
- u.searchParams.set('access_token', this.$config.access_token);
36
- url = u.toString();
37
- }
38
- this.ws = new WebSocket(url, { headers });
39
-
40
- this.ws.on('open', () => {
41
- this.$connected = true;
42
- if (!this.$config.access_token) {
43
- this.logger.warn(formatCompact({ bot: this.$id, ok: false, error: 'missing access_token' }));
44
- }
45
- this.logger.info(formatCompact({ bot: this.$id, mode: 'ws' }));
46
- this.startHeartbeat();
47
- this.initTypingIndicator();
48
- resolve();
49
- });
50
-
51
- this.ws.on('message', (data) => {
52
- try {
53
- this.handleWsMessage(JSON.parse(data.toString()));
54
- } catch (error) {
55
- this.emit('error', error);
56
- }
57
- });
58
-
59
- this.ws.on('close', (code, reason) => {
60
- this.$connected = false;
61
- const reasonStr = reason?.toString?.() || '';
62
- const codeHint = code === 1005 ? ' [no status]' : code === 1006 ? ' [abnormal]' : '';
63
- this.logger.warn(formatCompact( {
64
- op: 'disconnect',
65
- bot: this.$id,
66
- code,
67
- error: reasonStr || 'closed',
68
- reconnect_ms: this.$config.reconnect_interval || 5000,
69
- }));
70
- reject({ code, reason });
71
- this.scheduleReconnect();
72
- });
73
-
74
- this.ws.on('error', (error) => {
75
- this.logger.warn(formatCompact( {
76
- op: 'ws_error',
77
- bot: this.$id,
78
- ok: false,
79
- error: error instanceof Error ? error.message : String(error),
80
- }));
81
- reject(error);
82
- });
83
- });
84
- }
85
-
86
- async $disconnect(): Promise<void> {
87
- if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = undefined; }
88
- if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = undefined; }
89
- for (const [, req] of this.pendingRequests) { clearTimeout(req.timeout); req.reject(new Error('Connection closed')); }
90
- this.pendingRequests.clear();
91
- if (this.ws) { this.ws.close(); this.ws = undefined; }
92
- this.inboundDeduper.clear();
93
- this.$connected = false;
94
- }
95
-
96
- async callApi<T = any>(action: string, params: Record<string, any> = {}): Promise<T> {
97
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error('WebSocket is not connected');
98
- const echo = `req_${++this.requestId}`;
99
- return new Promise((resolve, reject) => {
100
- const timeout = setTimeout(() => { this.pendingRequests.delete(echo); reject(new Error(`API call timeout: ${action}`)); }, 30000);
101
- this.pendingRequests.set(echo, { resolve, reject, timeout });
102
- this.ws!.send(JSON.stringify({ action, params, echo }));
103
- });
104
- }
105
-
106
- private handleWsMessage(message: any): void {
107
- if (message.echo && this.pendingRequests.has(message.echo)) {
108
- const req = this.pendingRequests.get(message.echo)!;
109
- this.pendingRequests.delete(message.echo);
110
- clearTimeout(req.timeout);
111
- const resp = message as ApiResponse;
112
- if (resp.status === 'ok') return req.resolve(resp.data);
113
- return req.reject(new Error(`API error [${resp.retcode}]: ${resp.message || resp.wording || 'unknown'}`));
114
- }
115
- this.dispatchEvent(message);
116
- }
117
-
118
- private startHeartbeat(): void {
119
- const interval = this.$config.heartbeat_interval || 30000;
120
- this.heartbeatTimer = setInterval(() => {
121
- if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.ping();
122
- }, interval);
123
- }
124
-
125
- private scheduleReconnect(): void {
126
- if (this.reconnectTimer) return;
127
- const interval = this.$config.reconnect_interval || 5000;
128
- this.reconnectTimer = setTimeout(async () => {
129
- this.reconnectTimer = undefined;
130
- try { await this.$connect(); } catch { this.scheduleReconnect(); }
131
- }, interval);
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
- }
164
- }
@@ -1,150 +0,0 @@
1
- /**
2
- * NapCat 反向 WebSocket 连接
3
- */
4
- import WebSocket, { WebSocketServer } from 'ws';
5
- import { formatCompact } from 'zhin.js';
6
- import type { IncomingMessage } from 'http';
7
- import { NapCatBotBase } from './bot-base.js';
8
- import type { NapCatWsServerConfig, ApiResponse } from './types.js';
9
- import type { NapCatAdapter } from './adapter.js';
10
- import type { Router } from '@zhin.js/host-router';
11
- import { enableTypingIndicator } from './typing-indicator.js';
12
-
13
- export class NapCatWsServer extends NapCatBotBase {
14
- #wss?: WebSocketServer;
15
- #clientMap = new Map<string, WebSocket>();
16
- private heartbeatTimer?: NodeJS.Timeout;
17
- private requestId = 0;
18
- private pendingRequests = new Map<string, {
19
- resolve: (value: any) => void;
20
- reject: (error: Error) => void;
21
- timeout: NodeJS.Timeout;
22
- }>();
23
-
24
- declare $config: NapCatWsServerConfig;
25
-
26
- constructor(adapter: NapCatAdapter, public router: Router, config: NapCatWsServerConfig) {
27
- super(adapter, config);
28
- }
29
-
30
- async $connect(): Promise<void> {
31
- if (!this.$config.access_token) {
32
- this.logger.warn(formatCompact({ bot: this.$id, ok: false, error: 'missing access_token' }));
33
- }
34
- this.#wss = this.router.ws(this.$config.path, {
35
- verifyClient: (info: { origin: string; secure: boolean; req: IncomingMessage }) => {
36
- const authorization = info.req.headers['authorization'] || '';
37
- if (this.$config.access_token && authorization !== `Bearer ${this.$config.access_token}`) {
38
- this.logger.error(`[${this.$id}] auth failed`);
39
- return false;
40
- }
41
- return true;
42
- },
43
- });
44
- this.logger.info(formatCompact( { op: 'listen', bot: this.$id, mode: 'wss', path: this.$config.path }));
45
-
46
- this.#wss.on('connection', (client, req) => {
47
- this.startHeartbeat();
48
- this.logger.info(formatCompact({ bot: this.$id, peer: req.socket.remoteAddress }));
49
-
50
- client.on('error', (err) => this.logger.warn(formatCompact( {
51
- op: 'ws_error',
52
- bot: this.$id,
53
- ok: false,
54
- error: err instanceof Error ? err.message : String(err),
55
- })));
56
- client.on('close', (code, reason) => {
57
- const reasonStr = reason?.toString?.() || '';
58
- this.logger.warn(formatCompact( {
59
- op: 'disconnect',
60
- bot: this.$id,
61
- code,
62
- error: reasonStr || undefined,
63
- }));
64
- for (const [key, val] of this.#clientMap) {
65
- if (val === client) this.#clientMap.delete(key);
66
- }
67
- if (this.#clientMap.size === 0) this.$connected = false;
68
- });
69
- client.on('message', (data) => {
70
- try { this.handleWsMessage(client, JSON.parse(data.toString())); } catch (e) { this.emit('error', e); }
71
- });
72
- });
73
- }
74
-
75
- async $disconnect(): Promise<void> {
76
- this.#wss?.close();
77
- if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = undefined; }
78
- for (const [, req] of this.pendingRequests) { clearTimeout(req.timeout); req.reject(new Error('Connection closed')); }
79
- this.pendingRequests.clear();
80
- this.inboundDeduper.clear();
81
- this.$connected = false;
82
- }
83
-
84
- async callApi<T = any>(action: string, params: Record<string, any> = {}): Promise<T> {
85
- const selfId = this.getFirstSelfId();
86
- const client = this.#clientMap.get(selfId);
87
- if (!client || client.readyState !== WebSocket.OPEN) throw new Error('WebSocket is not connected');
88
- const echo = `req_${++this.requestId}`;
89
- return new Promise((resolve, reject) => {
90
- const timeout = setTimeout(() => { this.pendingRequests.delete(echo); reject(new Error(`API call timeout: ${action}`)); }, 30000);
91
- this.pendingRequests.set(echo, { resolve, reject, timeout });
92
- client.send(JSON.stringify({ action, params, echo }));
93
- });
94
- }
95
-
96
- private getFirstSelfId(): string {
97
- const first = this.#clientMap.keys().next().value;
98
- if (!first) throw new Error('No NapCat client connected to reverse WS');
99
- return first;
100
- }
101
-
102
- private handleWsMessage(client: WebSocket, message: any): void {
103
- if (message.self_id != null) {
104
- const selfIdStr = String(message.self_id);
105
- if (!this.#clientMap.has(selfIdStr) || this.#clientMap.get(selfIdStr) !== client) {
106
- this.#clientMap.set(selfIdStr, client);
107
- if (!this.$connected) this.$connected = true;
108
- }
109
- }
110
- if (message.echo && this.pendingRequests.has(message.echo)) {
111
- const req = this.pendingRequests.get(message.echo)!;
112
- this.pendingRequests.delete(message.echo);
113
- clearTimeout(req.timeout);
114
- const resp = message as ApiResponse;
115
- if (resp.status === 'ok') return req.resolve(resp.data);
116
- return req.reject(new Error(`API error [${resp.retcode}]: ${resp.message || resp.wording || 'unknown'}`));
117
- }
118
-
119
- if (message.post_type === 'meta_event' && message.sub_type === 'connect') {
120
- this.#clientMap.set(String(message.self_id), client);
121
- this.$connected = true;
122
- this.logger.info(formatCompact({ bot: this.$id, self_id: message.self_id }));
123
- this.initTypingIndicator();
124
- return;
125
- }
126
- this.dispatchEvent(message);
127
- }
128
-
129
- private startHeartbeat(): void {
130
- if (this.heartbeatTimer) return;
131
- const interval = this.$config.heartbeat_interval || 30000;
132
- this.heartbeatTimer = setInterval(() => {
133
- for (const client of this.#wss?.clients || []) {
134
- if (client.readyState === WebSocket.OPEN) client.ping();
135
- }
136
- }, interval);
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
- }
150
- }
package/src/routes.ts DELETED
@@ -1,61 +0,0 @@
1
- import type { Router } from '@zhin.js/host-router';
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
- }