@zhin.js/adapter-onebot11 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,26 +1,57 @@
1
- import WebSocket from 'ws';
1
+ import WebSocket, {WebSocketServer} from 'ws';
2
2
  import {EventEmitter} from "events";
3
- import { Bot,Plugin,Adapter,BotConfig,Message,registerAdapter, User, Group, MessageSegment, SendOptions } from 'zhin.js';
4
-
5
- declare module '@zhin.js/types'{
6
- interface GlobalContext{
7
- onebot11:Adapter<OneBot11WsClient>
3
+ import {
4
+ Bot,
5
+ Plugin,
6
+ Adapter,
7
+ BotConfig,
8
+ Message,
9
+ registerAdapter,
10
+ User,
11
+ Group,
12
+ MessageSegment,
13
+ SendOptions,
14
+ MessageType, segment, useContext, SendContent
15
+ } from 'zhin.js';
16
+ import type {Router} from '@zhin.js/http'
17
+ import {IncomingMessage} from "http";
18
+ import {clearInterval} from "node:timers";
19
+
20
+ declare module 'zhin.js'{
21
+ interface RegisteredAdapters{
22
+ 'onebot11':Adapter<OneBot11WsClient>
23
+ 'onebot11.wss':Adapter<OneBot11WsServer>
8
24
  }
9
25
  }
10
26
  // ============================================================================
11
27
  // OneBot11 配置和类型
12
28
  // ============================================================================
13
29
 
14
- export interface OneBotV11Config extends BotConfig {
30
+ export interface OneBot11Config extends BotConfig {
15
31
  context: 'onebot11';
16
- url: string;
32
+ type:string
17
33
  access_token?: string;
34
+ }
35
+ export interface OneBot11WsClientConfig extends OneBot11Config{
36
+ type:'ws'
37
+ url: string;
18
38
  reconnect_interval?: number;
19
39
  heartbeat_interval?: number;
20
40
  }
41
+ export interface OneBot11WsServerConfig extends OneBot11Config{
42
+ type:'ws_reverse'
43
+ path:string
44
+ heartbeat_interval?: number;
45
+ }
46
+ export interface OneBot11HTTPConfig extends OneBot11Config{
47
+ type:'http_sse'
48
+ port:number
49
+ path:string
50
+ }
21
51
 
22
52
  interface OneBot11Message {
23
53
  post_type: string;
54
+ self_id:string
24
55
  message_type?: string;
25
56
  sub_type?: string;
26
57
  message_id: number;
@@ -45,8 +76,8 @@ interface ApiResponse<T = any> {
45
76
  // OneBot11 适配器实现
46
77
  // ============================================================================
47
78
 
48
- export class OneBot11WsClient extends EventEmitter implements Bot<OneBotV11Config> {
49
- connected?:boolean
79
+ export class OneBot11WsClient extends EventEmitter implements Bot<OneBot11Message,OneBot11WsClientConfig> {
80
+ $connected?:boolean
50
81
  private ws?: WebSocket;
51
82
  private reconnectTimer?: NodeJS.Timeout;
52
83
  private heartbeatTimer?: NodeJS.Timeout;
@@ -57,23 +88,25 @@ export class OneBot11WsClient extends EventEmitter implements Bot<OneBotV11Confi
57
88
  timeout: NodeJS.Timeout;
58
89
  }>();
59
90
 
60
- constructor(public plugin:Plugin,public config: OneBotV11Config) {
91
+ constructor(public plugin:Plugin,public $config: OneBot11WsClientConfig) {
61
92
  super();
93
+ this.$connected=false
62
94
  }
63
95
 
64
96
 
65
- async connect(): Promise<void> {
97
+ async $connect(): Promise<void> {
66
98
  return new Promise((resolve, reject) => {
67
- let wsUrl = this.config.url;
99
+ let wsUrl = this.$config.url;
68
100
  const headers: Record<string, string> = {};
69
101
 
70
- if (this.config.access_token) {
71
- headers['Authorization'] = `Bearer ${this.config.access_token}`;
102
+ if (this.$config.access_token) {
103
+ headers['Authorization'] = `Bearer ${this.$config.access_token}`;
72
104
  }
73
105
  this.ws = new WebSocket(wsUrl,{headers});
74
106
 
75
107
  this.ws.on('open', () => {
76
- this.connected=true;
108
+ this.$connected=true;
109
+ if(!this.$config.access_token) this.plugin.logger.warn(`missing 'access_token', your OneBot protocol is not safely`)
77
110
  this.startHeartbeat();
78
111
  resolve();
79
112
  });
@@ -88,7 +121,7 @@ export class OneBot11WsClient extends EventEmitter implements Bot<OneBotV11Confi
88
121
  });
89
122
 
90
123
  this.ws.on('close', (code,reason) => {
91
- this.connected=false
124
+ this.$connected=false
92
125
  reject({code,reason})
93
126
  this.scheduleReconnect();
94
127
  });
@@ -99,7 +132,7 @@ export class OneBot11WsClient extends EventEmitter implements Bot<OneBotV11Confi
99
132
  });
100
133
  }
101
134
 
102
- async disconnect(): Promise<void> {
135
+ async $disconnect(): Promise<void> {
103
136
  if (this.reconnectTimer) {
104
137
  clearTimeout(this.reconnectTimer);
105
138
  this.reconnectTimer = undefined;
@@ -122,53 +155,56 @@ export class OneBot11WsClient extends EventEmitter implements Bot<OneBotV11Confi
122
155
  this.ws = undefined;
123
156
  }
124
157
  }
158
+ $formatMessage(onebotMsg: OneBot11Message){
159
+ const message=Message.from(onebotMsg,{
160
+ $id: onebotMsg.message_id.toString(),
161
+ $adapter:'onebot11',
162
+ $bot:`${this.$config.name}`,
163
+ $sender:{
164
+ id:onebotMsg.user_id.toString(),
165
+ name:onebotMsg.user_id.toString()
166
+ },
167
+ $channel:{
168
+ id: (onebotMsg.group_id || onebotMsg.user_id).toString(),
169
+ type:onebotMsg.group_id?'group':'private'
170
+ },
171
+ $content: onebotMsg.message,
172
+ $raw: onebotMsg.raw_message,
173
+ $timestamp: onebotMsg.time,
174
+ $reply:async (content: MessageSegment[], quote?: boolean|string):Promise<void>=> {
175
+ if(quote) content.unshift({type:'reply',data:{message_id:message.$id}})
176
+ this.plugin.dispatch('message.send',{
177
+ ...message.$channel,
178
+ context:'onebot11',
179
+ bot:`${this.$config.name}`,
180
+ content
181
+ })
182
+ }
183
+ })
184
+ return message
185
+ }
125
186
 
126
- async sendMessage(options: SendOptions): Promise<void> {
187
+ async $sendMessage(options: SendOptions): Promise<void> {
127
188
  options=await this.plugin.app.handleBeforeSend(options)
128
- this.plugin.logger.info(`send ${options.type}(${options.id}):`,options.content)
129
189
  const messageData: any = {
130
190
  message: options.content
131
191
  };
132
-
133
192
  if (options.type==='group') {
134
193
  await this.callApi('send_group_msg', {
135
194
  group_id: parseInt(options.id),
136
195
  ...messageData
137
196
  });
197
+ this.plugin.logger.info(`send ${options.type}(${options.id}):${segment.raw(options.content)}`)
138
198
  } else if (options.type==='private') {
139
199
  await this.callApi('send_private_msg', {
140
200
  user_id: parseInt(options.id),
141
201
  ...messageData
142
202
  });
203
+ this.plugin.logger.info(`send ${options.type}(${options.id}):${segment.raw(options.content)}`)
143
204
  } else {
144
205
  throw new Error('Either group_id or user_id must be provided');
145
206
  }
146
207
  }
147
-
148
- async getUser(userId: string): Promise<User> {
149
- const response = await this.callApi('get_stranger_info', {
150
- user_id: parseInt(userId)
151
- });
152
-
153
- return {
154
- user_id: userId,
155
- nickname: response.nickname,
156
- card: response.card
157
- };
158
- }
159
-
160
- async getGroup(groupId: string): Promise<Group> {
161
- const response = await this.callApi('get_group_info', {
162
- group_id: parseInt(groupId)
163
- });
164
-
165
- return {
166
- group_id: groupId,
167
- group_name: response.group_name,
168
- member_count: response.member_count
169
- };
170
- }
171
-
172
208
  private async callApi(action: string, params: any = {}): Promise<any> {
173
209
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
174
210
  throw new Error('WebSocket is not connected');
@@ -215,38 +251,14 @@ export class OneBot11WsClient extends EventEmitter implements Bot<OneBotV11Confi
215
251
  }
216
252
 
217
253
  private handleOneBot11Message(onebotMsg: OneBot11Message): void {
218
- const message: Message = {
219
- id: onebotMsg.message_id.toString(),
220
- adapter:'onebot11',
221
- bot:`${this.config.name}`,
222
- sender:{
223
- id:onebotMsg.user_id.toString(),
224
- name:onebotMsg.user_id.toString()
225
- },
226
- channel:{
227
- id:(onebotMsg.group_id||onebotMsg.user_id).toString(),
228
- type:onebotMsg.group_id?'group':'private'
229
- },
230
- content: onebotMsg.message,
231
- raw: onebotMsg.raw_message,
232
- timestamp: onebotMsg.time,
233
- reply:async (content: MessageSegment[], quote?: boolean|string):Promise<void>=> {
234
- if(quote) content.unshift({type:'reply',data:{message_id:message.id}})
235
- this.plugin.dispatch('message.send',{
236
- ...message.channel,
237
- context:'onebot11',
238
- bot:`${this.config.name}`,
239
- content
240
- })
241
- }
242
- };
254
+ const message = this.$formatMessage(onebotMsg);
243
255
  this.plugin.dispatch('message.receive',message)
244
- this.plugin.logger.info(`recv ${message.channel.type}(${message.channel.id}):`,message.content)
245
- this.plugin.dispatch(`message.${message.channel.type}.receive`,message)
256
+ this.plugin.logger.info(`recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`)
257
+ this.plugin.dispatch(`message.${message.$channel.type}.receive`,message)
246
258
  }
247
259
 
248
260
  private startHeartbeat(): void {
249
- const interval = this.config.heartbeat_interval || 30000;
261
+ const interval = this.$config.heartbeat_interval || 30000;
250
262
  this.heartbeatTimer = setInterval(() => {
251
263
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
252
264
  this.ws.ping();
@@ -259,11 +271,11 @@ export class OneBot11WsClient extends EventEmitter implements Bot<OneBotV11Confi
259
271
  return;
260
272
  }
261
273
 
262
- const interval = this.config.reconnect_interval || 5000;
274
+ const interval = this.$config.reconnect_interval || 5000;
263
275
  this.reconnectTimer = setTimeout(async () => {
264
276
  this.reconnectTimer = undefined;
265
277
  try {
266
- await this.connect();
278
+ await this.$connect();
267
279
  } catch (error) {
268
280
  this.emit('error',new Error(`Reconnection failed: ${error}`));
269
281
  this.scheduleReconnect();
@@ -271,4 +283,198 @@ export class OneBot11WsClient extends EventEmitter implements Bot<OneBotV11Confi
271
283
  }, interval);
272
284
  }
273
285
  }
274
- registerAdapter(new Adapter('onebot11',OneBot11WsClient))
286
+ export class OneBot11WsServer extends EventEmitter implements Bot<OneBot11Message,OneBot11WsServerConfig> {
287
+ $connected?:boolean
288
+ #wss?:WebSocketServer
289
+ #clientMap:Map<string,WebSocket>=new Map<string,WebSocket>()
290
+ private heartbeatTimer?: NodeJS.Timeout;
291
+ private requestId = 0;
292
+ private pendingRequests = new Map<string, {
293
+ resolve: (value: any) => void;
294
+ reject: (error: Error) => void;
295
+ timeout: NodeJS.Timeout;
296
+ }>();
297
+
298
+ constructor(public plugin:Plugin,public router:Router,public $config: OneBot11WsServerConfig) {
299
+ super();
300
+ this.$connected=false
301
+ }
302
+ async $connect(): Promise<void> {
303
+ if(!this.$config.access_token) this.plugin.logger.warn(`missing 'access_token', your OneBot protocol is not safely`)
304
+ this.#wss=this.router.ws(this.$config.path,{verifyClient:(info:{ origin: string; secure: boolean; req: IncomingMessage })=>{
305
+ const {
306
+ req: { headers },
307
+ } = info;
308
+ const authorization = headers['authorization'] || '';
309
+ if (this.$config.access_token && authorization !== `Bearer ${this.$config.access_token}`) {
310
+ this.plugin.logger.error('鉴权失败');
311
+ return false;
312
+ }
313
+ return true;
314
+ }})
315
+ this.$connected=true;
316
+ this.plugin.logger.info(`ws server start at path:${this.$config.path}`)
317
+ this.#wss.on('connection', (client,req) => {
318
+ this.startHeartbeat();
319
+ this.plugin.logger.info(`已连接到协议端:${req.socket.remoteAddress}`);
320
+ client.on('error', err => {
321
+ this.plugin.logger.error('连接出错:', err);
322
+ });
323
+ client.on('close', code => {
324
+ this.plugin.logger.error(`与连接端(${req.socket.remoteAddress})断开,错误码:${code}`);
325
+ for(const [key,value] of this.#clientMap){
326
+ if(client===value) this.#clientMap.delete(key)
327
+ }
328
+ });
329
+ client.on('message',(data) => {
330
+ try {
331
+ const message = JSON.parse(data.toString());
332
+ this.handleWebSocketMessage(client,message);
333
+ } catch (error) {
334
+ this.emit('error',error)
335
+ }
336
+ })
337
+ });
338
+ }
339
+
340
+ async $disconnect(): Promise<void> {
341
+ this.#wss?.close();
342
+ if(this.heartbeatTimer){
343
+ clearInterval(this.heartbeatTimer)
344
+ delete this.heartbeatTimer;
345
+ }
346
+ }
347
+ $formatMessage(onebotMsg: OneBot11Message){
348
+ const message=Message.from(onebotMsg,{
349
+ $id: onebotMsg.message_id.toString(),
350
+ $adapter:'onebot11',
351
+ $bot:`${this.$config.name}`,
352
+ $sender:{
353
+ id:onebotMsg.user_id.toString(),
354
+ name:onebotMsg.user_id.toString()
355
+ },
356
+ $channel:{
357
+ id:[onebotMsg.self_id,(onebotMsg.group_id||onebotMsg.user_id)].join(':'),
358
+ type:onebotMsg.group_id?'group':'private'
359
+ },
360
+ $content: onebotMsg.message,
361
+ $raw: onebotMsg.raw_message,
362
+ $timestamp: onebotMsg.time,
363
+ $reply:async (content: SendContent, quote?: boolean|string):Promise<void>=> {
364
+ if(!Array.isArray(content)) content=[content];
365
+ if(quote) content.unshift({type:'reply',data:{message_id:message.$id}})
366
+ this.plugin.dispatch('message.send',{
367
+ ...message.$channel,
368
+ context:'onebot11',
369
+ bot:`${this.$config.name}`,
370
+ content
371
+ })
372
+ }
373
+ })
374
+ return message
375
+ }
376
+
377
+ async $sendMessage(options: SendOptions): Promise<void> {
378
+ options=await this.plugin.app.handleBeforeSend(options)
379
+ const messageData: any = {
380
+ message: options.content
381
+ };
382
+ if (options.type==='group') {
383
+ const [self_id,id]=options.id.split(':')
384
+ await this.callApi(self_id,'send_group_msg', {
385
+ group_id: parseInt(id),
386
+ ...messageData
387
+ });
388
+ this.plugin.logger.info(`send ${options.type}(${id}):${segment.raw(options.content)}`)
389
+ } else if (options.type==='private') {
390
+ const [self_id,id]=options.id.split(':')
391
+ await this.callApi(self_id,'send_private_msg', {
392
+ user_id: parseInt(id),
393
+ ...messageData
394
+ });
395
+ this.plugin.logger.info(`send ${options.type}(${id}):${segment.raw(options.content)}`)
396
+ } else {
397
+ throw new Error('Either group_id or user_id must be provided');
398
+ }
399
+ }
400
+ private async callApi(self_id:string,action: string, params: any = {}): Promise<any> {
401
+ const client=this.#clientMap.get(self_id)
402
+ if (!client || client.readyState !== WebSocket.OPEN) {
403
+ throw new Error('WebSocket is not connected');
404
+ }
405
+
406
+ const echo = `req_${++this.requestId}`;
407
+ const message = {
408
+ action,
409
+ params,
410
+ echo
411
+ };
412
+
413
+ return new Promise((resolve, reject) => {
414
+ const timeout = setTimeout(() => {
415
+ this.pendingRequests.delete(echo);
416
+ reject(new Error(`API call timeout: ${action}`));
417
+ }, 30000); // 30秒超时
418
+
419
+ this.pendingRequests.set(echo, { resolve, reject, timeout });
420
+ client.send(JSON.stringify(message));
421
+ });
422
+ }
423
+
424
+ private handleWebSocketMessage(client:WebSocket,message: any): void {
425
+ // 处理API响应
426
+ if (message.echo && this.pendingRequests.has(message.echo)) {
427
+ const request = this.pendingRequests.get(message.echo)!;
428
+ this.pendingRequests.delete(message.echo);
429
+ clearTimeout(request.timeout);
430
+
431
+ const response = message as ApiResponse;
432
+ if (response.status === 'ok') {
433
+ return request.resolve(response.data);
434
+ }
435
+ return request.reject(new Error(`API error: ${response.retcode}`));
436
+ }
437
+ switch (message.post_type){
438
+ case 'message':
439
+ return this.handleMessage(message);
440
+ case 'meta_event':
441
+ return this.handleMetaEvent(client,message)
442
+ }
443
+ // 处理事件消息
444
+ if (message.post_type === 'message') {
445
+ } else if (message.post_type === 'meta_event' && message.meta_event_type === 'heartbeat') {
446
+ // 心跳消息,暂时忽略
447
+ }
448
+ }
449
+ private handleMetaEvent(client:WebSocket,message:any){
450
+ switch (message.sub_type){
451
+ case 'heartbeat':
452
+ break;
453
+ case 'connect':
454
+ this.#clientMap.set(message.self_id,client);
455
+ this.plugin.logger.info(`client ${message.self_id} of ${this.$config.name} by ${this.$config.context} connected`)
456
+ break;
457
+ }
458
+ }
459
+ private handleMessage(onebotMsg: OneBot11Message): void {
460
+ const message = this.$formatMessage(onebotMsg);
461
+ this.plugin.dispatch('message.receive',message)
462
+ this.plugin.logger.info(`recv ${message.$channel.type}(${onebotMsg.group_id||onebotMsg.user_id}):${segment.raw(message.$content)}`)
463
+ this.plugin.dispatch(`message.${message.$channel.type}.receive`,message)
464
+ }
465
+
466
+ private startHeartbeat(): void {
467
+ const interval = this.$config.heartbeat_interval || 30000;
468
+ this.heartbeatTimer = setInterval(() => {
469
+ for(const client of this.#wss?.clients||[]){
470
+ if (client && client.readyState === WebSocket.OPEN) {
471
+ client.ping();
472
+ }
473
+ }
474
+ }, interval);
475
+ }
476
+ }
477
+ registerAdapter(new Adapter('onebot11',OneBot11WsClient))
478
+ useContext('router',(router)=>{
479
+ registerAdapter(new Adapter('onebot11.wss',(p,c:OneBot11WsServerConfig)=>new OneBot11WsServer(p,router,c)));
480
+ })
package/tsconfig.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
4
  "module": "ESNext",
5
- "moduleResolution": "node",
6
- "outDir": "./dist",
5
+ "moduleResolution": "bundler",
6
+ "outDir": "./lib",
7
7
  "rootDir": "./src",
8
8
  "strict": true,
9
9
  "esModuleInterop": true,
@@ -17,9 +17,8 @@
17
17
  "declaration": true,
18
18
  "declarationMap": true,
19
19
  "sourceMap": true,
20
- "composite": true,
21
20
  "verbatimModuleSyntax": false
22
21
  },
23
22
  "include": ["src/**/*"],
24
- "exclude": ["dist", "node_modules"]
23
+ "exclude": ["lib", "node_modules"]
25
24
  }
package/dist/index.d.ts DELETED
@@ -1,36 +0,0 @@
1
- import { EventEmitter } from "events";
2
- import { Bot, Plugin, Adapter, BotConfig, User, Group, SendOptions } from 'zhin.js';
3
- declare module '@zhin.js/types' {
4
- interface GlobalContext {
5
- onebot11: Adapter<OneBot11WsClient>;
6
- }
7
- }
8
- export interface OneBotV11Config extends BotConfig {
9
- context: 'onebot11';
10
- url: string;
11
- access_token?: string;
12
- reconnect_interval?: number;
13
- heartbeat_interval?: number;
14
- }
15
- export declare class OneBot11WsClient extends EventEmitter implements Bot<OneBotV11Config> {
16
- plugin: Plugin;
17
- config: OneBotV11Config;
18
- connected?: boolean;
19
- private ws?;
20
- private reconnectTimer?;
21
- private heartbeatTimer?;
22
- private requestId;
23
- private pendingRequests;
24
- constructor(plugin: Plugin, config: OneBotV11Config);
25
- connect(): Promise<void>;
26
- disconnect(): Promise<void>;
27
- sendMessage(options: SendOptions): Promise<void>;
28
- getUser(userId: string): Promise<User>;
29
- getGroup(groupId: string): Promise<Group>;
30
- private callApi;
31
- private handleWebSocketMessage;
32
- private handleOneBot11Message;
33
- private startHeartbeat;
34
- private scheduleReconnect;
35
- }
36
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,YAAY,EAAC,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,GAAG,EAAC,MAAM,EAAC,OAAO,EAAC,SAAS,EAA0B,IAAI,EAAE,KAAK,EAAkB,WAAW,EAAE,MAAM,SAAS,CAAC;AAEzH,OAAO,QAAQ,gBAAgB,CAAA;IAC7B,UAAU,aAAa;QACrB,QAAQ,EAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;KACnC;CACF;AAKD,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,OAAO,EAAE,UAAU,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AA4BD,qBAAa,gBAAiB,SAAQ,YAAa,YAAW,GAAG,CAAC,eAAe,CAAC;IAY7D,MAAM,EAAC,MAAM;IAAQ,MAAM,EAAE,eAAe;IAX/D,SAAS,CAAC,EAAC,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,CAAC,CAAY;IACvB,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,eAAe,CAIlB;gBAEc,MAAM,EAAC,MAAM,EAAQ,MAAM,EAAE,eAAe;IAKzD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAqCxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB3B,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBhD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;YAYjC,OAAO;IAuBrB,OAAO,CAAC,sBAAsB;IAsB9B,OAAO,CAAC,qBAAqB;IA+B7B,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,iBAAiB;CAgB1B"}