@swalha1999/whatsapp 0.1.3

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/README.md ADDED
@@ -0,0 +1,638 @@
1
+ # @swalha1999/whatsapp
2
+
3
+ A lightweight, type-safe WhatsApp Business API client for Node.js.
4
+
5
+ ## Features
6
+
7
+ - Send text messages and template messages
8
+ - Send media: images, videos, audio, documents, stickers
9
+ - Send interactive messages: buttons, lists
10
+ - Send location, reactions, and contact cards
11
+ - Message categories: marketing, utility, authentication, service
12
+ - Parse incoming webhook payloads (text, media, location, reactions, stickers, contacts)
13
+ - Verify webhook subscriptions
14
+ - Fluent template builder API
15
+ - Full TypeScript support
16
+ - Zero dependencies (uses native fetch)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ # Using pnpm
22
+ pnpm add @swalha1999/whatsapp
23
+
24
+ # Using npm
25
+ npm install @swalha1999/whatsapp
26
+
27
+ # Using yarn
28
+ yarn add @swalha1999/whatsapp
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```typescript
34
+ import { createWhatsApp } from '@swalha1999/whatsapp'
35
+
36
+ const whatsapp = createWhatsApp({
37
+ apiToken: process.env.WHATSAPP_TOKEN!,
38
+ phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
39
+ })
40
+
41
+ // Send a message
42
+ const result = await whatsapp.sendText({
43
+ to: '1234567890',
44
+ body: 'Hello!',
45
+ })
46
+
47
+ console.log(result.success ? 'Sent!' : result.error?.message)
48
+ ```
49
+
50
+ ## API Reference
51
+
52
+ ### `createWhatsApp(config)`
53
+
54
+ Creates a WhatsApp client instance.
55
+
56
+ ```typescript
57
+ import { createWhatsApp } from '@swalha1999/whatsapp'
58
+
59
+ const whatsapp = createWhatsApp({
60
+ apiToken: 'your_token',
61
+ phoneNumberId: 'your_phone_id',
62
+ apiVersion: 'v22.0', // optional, default: 'v22.0'
63
+ baseUrl: 'https://...', // optional, default: 'https://graph.facebook.com'
64
+ })
65
+ ```
66
+
67
+ ### `whatsapp.sendText(params)`
68
+
69
+ Send a text message.
70
+
71
+ ```typescript
72
+ const result = await whatsapp.sendText({
73
+ to: '1234567890', // recipient phone number
74
+ body: 'Hello!', // message text
75
+ previewUrl: true, // optional: enable URL preview
76
+ })
77
+
78
+ // Result
79
+ {
80
+ success: true,
81
+ messageId: 'wamid.xxx'
82
+ }
83
+
84
+ // Or on error
85
+ {
86
+ success: false,
87
+ messageId: '',
88
+ error: { code: 131047, message: 'Re-engagement message' }
89
+ }
90
+ ```
91
+
92
+ ### `whatsapp.sendTemplate(params)`
93
+
94
+ Send a template message.
95
+
96
+ ```typescript
97
+ const result = await whatsapp.sendTemplate({
98
+ to: '1234567890',
99
+ templateName: 'order_confirmation',
100
+ languageCode: 'en',
101
+ components: [
102
+ {
103
+ type: 'header',
104
+ parameters: [{ type: 'image', image: { link: 'https://...' } }]
105
+ },
106
+ {
107
+ type: 'body',
108
+ parameters: [
109
+ { type: 'text', text: 'John' },
110
+ { type: 'text', text: 'Order #123' }
111
+ ]
112
+ }
113
+ ]
114
+ })
115
+ ```
116
+
117
+ ### `whatsapp.sendImage(params)`
118
+
119
+ Send an image message.
120
+
121
+ ```typescript
122
+ const result = await whatsapp.sendImage({
123
+ to: '1234567890',
124
+ image: { link: 'https://example.com/image.jpg' },
125
+ // Or use media ID: image: { id: 'media_id' },
126
+ caption: 'Check this out!', // optional
127
+ })
128
+ ```
129
+
130
+ ### `whatsapp.sendVideo(params)`
131
+
132
+ Send a video message.
133
+
134
+ ```typescript
135
+ const result = await whatsapp.sendVideo({
136
+ to: '1234567890',
137
+ video: { link: 'https://example.com/video.mp4' },
138
+ caption: 'Watch this!', // optional
139
+ })
140
+ ```
141
+
142
+ ### `whatsapp.sendAudio(params)`
143
+
144
+ Send an audio message.
145
+
146
+ ```typescript
147
+ const result = await whatsapp.sendAudio({
148
+ to: '1234567890',
149
+ audio: { link: 'https://example.com/audio.mp3' },
150
+ })
151
+ ```
152
+
153
+ ### `whatsapp.sendDocument(params)`
154
+
155
+ Send a document.
156
+
157
+ ```typescript
158
+ const result = await whatsapp.sendDocument({
159
+ to: '1234567890',
160
+ document: { link: 'https://example.com/file.pdf' },
161
+ filename: 'report.pdf', // optional
162
+ caption: 'Here is the report', // optional
163
+ })
164
+ ```
165
+
166
+ ### `whatsapp.sendSticker(params)`
167
+
168
+ Send a sticker.
169
+
170
+ ```typescript
171
+ const result = await whatsapp.sendSticker({
172
+ to: '1234567890',
173
+ sticker: { id: 'sticker_media_id' },
174
+ })
175
+ ```
176
+
177
+ ### `whatsapp.sendLocation(params)`
178
+
179
+ Send a location.
180
+
181
+ ```typescript
182
+ const result = await whatsapp.sendLocation({
183
+ to: '1234567890',
184
+ latitude: 37.7749,
185
+ longitude: -122.4194,
186
+ name: 'San Francisco', // optional
187
+ address: '123 Main St, San Francisco, CA', // optional
188
+ })
189
+ ```
190
+
191
+ ### `whatsapp.sendReaction(params)`
192
+
193
+ Send a reaction to a message.
194
+
195
+ ```typescript
196
+ const result = await whatsapp.sendReaction({
197
+ to: '1234567890',
198
+ messageId: 'wamid.xxx',
199
+ emoji: '👍',
200
+ })
201
+ ```
202
+
203
+ ### `whatsapp.sendContacts(params)`
204
+
205
+ Send contact cards.
206
+
207
+ ```typescript
208
+ const result = await whatsapp.sendContacts({
209
+ to: '1234567890',
210
+ contacts: [
211
+ {
212
+ name: { formatted_name: 'John Doe', first_name: 'John', last_name: 'Doe' },
213
+ phones: [{ phone: '+1234567890', type: 'CELL' }],
214
+ },
215
+ ],
216
+ })
217
+ ```
218
+
219
+ ### `whatsapp.sendInteractiveButtons(params)`
220
+
221
+ Send an interactive button message.
222
+
223
+ ```typescript
224
+ const result = await whatsapp.sendInteractiveButtons({
225
+ to: '1234567890',
226
+ body: 'Please choose an option:',
227
+ buttons: [
228
+ { id: 'yes', title: 'Yes' },
229
+ { id: 'no', title: 'No' },
230
+ { id: 'maybe', title: 'Maybe' },
231
+ ],
232
+ header: 'Confirmation', // optional
233
+ footer: 'Reply within 24 hours', // optional
234
+ })
235
+ ```
236
+
237
+ ### `whatsapp.sendInteractiveList(params)`
238
+
239
+ Send an interactive list message.
240
+
241
+ ```typescript
242
+ const result = await whatsapp.sendInteractiveList({
243
+ to: '1234567890',
244
+ body: 'Select a product:',
245
+ buttonText: 'View Products',
246
+ sections: [
247
+ {
248
+ title: 'Electronics',
249
+ rows: [
250
+ { id: 'phone', title: 'Smartphone', description: 'Latest model' },
251
+ { id: 'laptop', title: 'Laptop', description: 'High performance' },
252
+ ],
253
+ },
254
+ {
255
+ title: 'Accessories',
256
+ rows: [
257
+ { id: 'case', title: 'Phone Case' },
258
+ { id: 'charger', title: 'Fast Charger' },
259
+ ],
260
+ },
261
+ ],
262
+ header: 'Our Products', // optional
263
+ footer: 'Prices may vary', // optional
264
+ })
265
+ ```
266
+
267
+ ### `whatsapp.markAsRead(messageId)`
268
+
269
+ Mark a message as read.
270
+
271
+ ```typescript
272
+ const success = await whatsapp.markAsRead('wamid.xxx')
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Template Builder
278
+
279
+ Use the fluent builder API to construct template components easily:
280
+
281
+ ```typescript
282
+ import { createTemplateBuilder } from '@swalha1999/whatsapp'
283
+
284
+ const components = createTemplateBuilder()
285
+ // Add header with media
286
+ .addHeader('image', 'https://example.com/image.jpg')
287
+ // Or text header
288
+ // .addTextHeader('Welcome!')
289
+
290
+ // Add body parameters ({{1}}, {{2}}, etc.)
291
+ .addBodyParam('John Doe')
292
+ .addBodyParam('Order #12345')
293
+ .addBodyParam('$99.99')
294
+
295
+ // Add quick reply buttons
296
+ .addQuickReplyButton(0, 'confirm_payload')
297
+ .addQuickReplyButton(1, 'cancel_payload')
298
+
299
+ .build()
300
+
301
+ await whatsapp.sendTemplate({
302
+ to: '1234567890',
303
+ templateName: 'order_update',
304
+ languageCode: 'en',
305
+ components,
306
+ })
307
+ ```
308
+
309
+ ### Builder Methods
310
+
311
+ | Method | Description |
312
+ |--------|-------------|
313
+ | `addHeader(type, url)` | Add media header (image, video, document) |
314
+ | `addTextHeader(text)` | Add text header |
315
+ | `addBodyParam(text)` | Add body parameter |
316
+ | `addQuickReplyButton(index, payload)` | Add quick reply button |
317
+ | `addUrlButton(index, dynamicSuffix)` | Add URL button with dynamic suffix |
318
+ | `build()` | Build and return components array |
319
+
320
+ ### URL Buttons Example
321
+
322
+ For templates with URL buttons that have dynamic suffixes:
323
+
324
+ ```typescript
325
+ const components = createTemplateBuilder()
326
+ .addHeader('image', 'https://example.com/image.jpg')
327
+ .addBodyParam('John')
328
+ .addQuickReplyButton(0, 'rsvp_yes')
329
+ .addUrlButton(1, 'https://instagram.com/user')
330
+ .build()
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Webhook Handling
336
+
337
+ ### Verify Webhook Subscription
338
+
339
+ ```typescript
340
+ import { verifyWebhook } from '@swalha1999/whatsapp'
341
+
342
+ // Express example
343
+ app.get('/webhook', (req, res) => {
344
+ const result = verifyWebhook(
345
+ {
346
+ mode: req.query['hub.mode'],
347
+ token: req.query['hub.verify_token'],
348
+ challenge: req.query['hub.challenge'],
349
+ },
350
+ process.env.VERIFY_TOKEN!
351
+ )
352
+
353
+ if (result.valid) {
354
+ res.send(result.challenge)
355
+ } else {
356
+ res.sendStatus(403)
357
+ }
358
+ })
359
+ ```
360
+
361
+ ### Verify Webhook Signature (HMAC-SHA256)
362
+
363
+ For enhanced security, verify the webhook signature using your app secret:
364
+
365
+ ```typescript
366
+ import { verifyWebhookSignature, parseWebhookPayload } from '@swalha1999/whatsapp'
367
+
368
+ export async function POST(request: Request) {
369
+ const signature = request.headers.get('x-hub-signature-256') || ''
370
+ const rawBody = await request.text()
371
+
372
+ if (!verifyWebhookSignature(rawBody, signature, process.env.APP_SECRET!)) {
373
+ return new Response('Invalid signature', { status: 401 })
374
+ }
375
+
376
+ const payload = JSON.parse(rawBody)
377
+ const events = parseWebhookPayload(payload)
378
+ // ... handle events
379
+ }
380
+ ```
381
+
382
+ ### Parse Incoming Messages
383
+
384
+ ```typescript
385
+ import { parseWebhookPayload } from '@swalha1999/whatsapp'
386
+
387
+ app.post('/webhook', (req, res) => {
388
+ const events = parseWebhookPayload(req.body)
389
+
390
+ for (const event of events) {
391
+ // Handle incoming messages
392
+ if (event.type === 'message' && event.message) {
393
+ const { id, from, timestamp, content } = event.message
394
+
395
+ switch (content.type) {
396
+ case 'text':
397
+ console.log(`Text from ${from}: ${content.body}`)
398
+ break
399
+
400
+ case 'button':
401
+ console.log(`Button clicked: ${content.payload}`)
402
+ break
403
+
404
+ case 'interactive':
405
+ console.log(`Reply: ${content.replyId} - ${content.replyTitle}`)
406
+ break
407
+
408
+ case 'media':
409
+ console.log(`Media: ${content.mediaId} (${content.mimeType})`)
410
+ break
411
+
412
+ case 'location':
413
+ console.log(`Location: ${content.latitude}, ${content.longitude}`)
414
+ break
415
+
416
+ case 'sticker':
417
+ console.log(`Sticker: ${content.stickerId}`)
418
+ break
419
+
420
+ case 'reaction':
421
+ console.log(`Reaction: ${content.emoji} to ${content.messageId}`)
422
+ break
423
+
424
+ case 'contacts':
425
+ console.log(`Contacts: ${content.contacts.map(c => c.formattedName).join(', ')}`)
426
+ break
427
+ }
428
+ }
429
+
430
+ // Handle status updates
431
+ if (event.type === 'status' && event.status) {
432
+ const { messageId, status, recipientId, error } = event.status
433
+ console.log(`Message ${messageId} is now ${status}`)
434
+
435
+ if (error) {
436
+ console.error(`Error: ${error.code} - ${error.message}`)
437
+ }
438
+ }
439
+ }
440
+
441
+ res.sendStatus(200)
442
+ })
443
+ ```
444
+
445
+ ---
446
+
447
+ ## Types
448
+
449
+ ### Message Content Types
450
+
451
+ ```typescript
452
+ type MessageContent =
453
+ | { type: 'text'; body: string }
454
+ | { type: 'button'; payload: string; text: string }
455
+ | { type: 'interactive'; replyId: string; replyTitle: string; replyDescription?: string }
456
+ | { type: 'media'; mediaType: 'image' | 'video' | 'audio' | 'document'; mediaId: string; mimeType: string; caption?: string; filename?: string }
457
+ | { type: 'location'; latitude: number; longitude: number; name?: string; address?: string }
458
+ | { type: 'sticker'; stickerId: string; mimeType: string; animated?: boolean }
459
+ | { type: 'reaction'; emoji: string; messageId: string }
460
+ | { type: 'contacts'; contacts: ParsedContactCard[] }
461
+ | { type: 'unknown' }
462
+
463
+ interface ParsedContactCard {
464
+ formattedName: string
465
+ firstName?: string
466
+ lastName?: string
467
+ phones?: Array<{ phone: string; type?: string }>
468
+ }
469
+ ```
470
+
471
+ ### Status Types
472
+
473
+ ```typescript
474
+ type StatusType = 'sent' | 'delivered' | 'read' | 'failed'
475
+ ```
476
+
477
+ ### Parsed Webhook
478
+
479
+ ```typescript
480
+ interface ParsedWebhook {
481
+ type: 'message' | 'status' | 'unknown'
482
+ phoneNumberId: string
483
+ message?: ParsedMessage
484
+ status?: ParsedStatus
485
+ contact?: ParsedContact // Contact info for incoming messages
486
+ }
487
+
488
+ interface ParsedContact {
489
+ name: string // Profile name
490
+ waId: string // WhatsApp ID
491
+ }
492
+
493
+ interface ParsedMessage {
494
+ id: string
495
+ from: string
496
+ timestamp: Date
497
+ type: string
498
+ content: MessageContent
499
+ }
500
+
501
+ interface ParsedStatus {
502
+ messageId: string
503
+ status: 'sent' | 'delivered' | 'read' | 'failed'
504
+ recipientId: string
505
+ timestamp: Date
506
+ error?: { code: number; message: string }
507
+ conversation?: {
508
+ id: string
509
+ origin: 'user_initiated' | 'business_initiated' | 'referral_conversion'
510
+ expiresAt?: Date
511
+ }
512
+ pricing?: {
513
+ billable: boolean
514
+ category: string
515
+ }
516
+ }
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Error Handling
522
+
523
+ ```typescript
524
+ import { createWhatsApp, WhatsAppError } from '@swalha1999/whatsapp'
525
+
526
+ const whatsapp = createWhatsApp({ ... })
527
+
528
+ const result = await whatsapp.sendText({
529
+ to: '1234567890',
530
+ body: 'Hello!',
531
+ })
532
+
533
+ if (!result.success) {
534
+ console.error(`Error ${result.error?.code}: ${result.error?.message}`)
535
+
536
+ // Common error codes:
537
+ // 131047 - Re-engagement message required (24h window expired)
538
+ // 131051 - Invalid recipient
539
+ // 132000 - Template not found
540
+ // 100 - Invalid parameter
541
+ }
542
+ ```
543
+
544
+ ---
545
+
546
+ ## Batch Sending
547
+
548
+ Send messages in batches with automatic delays to avoid rate limits:
549
+
550
+ ```typescript
551
+ import { createWhatsApp, batchSend, createTemplateBuilder } from '@swalha1999/whatsapp'
552
+
553
+ const whatsapp = createWhatsApp({
554
+ apiToken: process.env.WHATSAPP_TOKEN!,
555
+ phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
556
+ })
557
+
558
+ const contacts = [
559
+ { phone: '1234567890', name: 'John' },
560
+ { phone: '0987654321', name: 'Jane' },
561
+ // ... more contacts
562
+ ]
563
+
564
+ const result = await batchSend(
565
+ contacts,
566
+ (contact) => whatsapp.sendTemplate({
567
+ to: contact.phone,
568
+ templateName: 'invitation',
569
+ languageCode: 'en',
570
+ components: createTemplateBuilder().addBodyParam(contact.name).build(),
571
+ }),
572
+ {
573
+ batchSize: 70, // Send 70 messages per batch (default: 50)
574
+ delayMs: 1000, // Wait 1 second between batches (default: 1000)
575
+ onProgress: (done, total) => console.log(`Progress: ${done}/${total}`),
576
+ onError: (error, contact, index) => console.error(`Failed for ${contact.phone}`),
577
+ }
578
+ )
579
+
580
+ console.log(`Sent ${result.successful}/${result.total} messages`)
581
+ console.log(`Failed: ${result.failed}`)
582
+ ```
583
+
584
+ ### BatchResult
585
+
586
+ ```typescript
587
+ interface BatchResult {
588
+ total: number // Total items processed
589
+ successful: number // Successfully sent
590
+ failed: number // Failed to send
591
+ results: SendResult[] // All results
592
+ }
593
+ ```
594
+
595
+ ---
596
+
597
+ ## Next.js App Router Example
598
+
599
+ ```typescript
600
+ // app/api/whatsapp/webhook/route.ts
601
+ import { parseWebhookPayload, verifyWebhook } from '@swalha1999/whatsapp'
602
+
603
+ export async function GET(request: Request) {
604
+ const url = new URL(request.url)
605
+
606
+ const result = verifyWebhook(
607
+ {
608
+ mode: url.searchParams.get('hub.mode') || '',
609
+ token: url.searchParams.get('hub.verify_token') || '',
610
+ challenge: url.searchParams.get('hub.challenge') || '',
611
+ },
612
+ process.env.WHATSAPP_VERIFY_TOKEN!
613
+ )
614
+
615
+ return result.valid
616
+ ? new Response(result.challenge)
617
+ : new Response('Forbidden', { status: 403 })
618
+ }
619
+
620
+ export async function POST(request: Request) {
621
+ const payload = await request.json()
622
+ const events = parseWebhookPayload(payload)
623
+
624
+ for (const event of events) {
625
+ if (event.type === 'message') {
626
+ // Handle message
627
+ }
628
+ }
629
+
630
+ return new Response('OK')
631
+ }
632
+ ```
633
+
634
+ ---
635
+
636
+ ## License
637
+
638
+ MIT
@@ -0,0 +1,22 @@
1
+ import { TemplateComponent } from "./types-DwL3dbmb.mjs";
2
+
3
+ //#region src/templates/types.d.ts
4
+ interface TemplateBuilder {
5
+ addHeader(type: 'image' | 'video' | 'document', url: string): TemplateBuilder;
6
+ addTextHeader(text: string): TemplateBuilder;
7
+ addBodyParam(text: string): TemplateBuilder;
8
+ addQuickReplyButton(index: number, payload: string): TemplateBuilder;
9
+ addUrlButton(index: number, dynamicSuffix: string): TemplateBuilder;
10
+ build(): TemplateComponent[];
11
+ }
12
+
13
+ //#endregion
14
+ //#region src/templates/builders.d.ts
15
+ //# sourceMappingURL=types.d.ts.map
16
+ declare function createTemplateBuilder(): TemplateBuilder;
17
+
18
+ //#endregion
19
+ //# sourceMappingURL=builders.d.ts.map
20
+
21
+ export { TemplateBuilder, createTemplateBuilder as createTemplateBuilder$1 };
22
+ //# sourceMappingURL=index-9aM5qSPE.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-9aM5qSPE.d.mts","names":[],"sources":["../src/templates/types.ts","../src/templates/builders.ts"],"sourcesContent":null,"mappings":";;;UAEiB,eAAA;gEAC+C;EAD/C,aAAA,CAAA,IAAe,EAAA,MAAA,CAAA,EAED,eAFC;EAAA,YAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EAGF,eAHE;EAAA,mBACgC,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAGT,eAHS;EAAe,YAChD,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,EAAA,MAAA,CAAA,EAGuB,eAHvB;EAAe,KAChB,EAAA,EAGnB,iBAHmB,EAAA;;;;;;iBCFd,qBAAA,CAAA,GAAyB"}
@@ -0,0 +1,22 @@
1
+ import { TemplateComponent } from "./types-D8Ot82od.js";
2
+
3
+ //#region src/templates/types.d.ts
4
+ interface TemplateBuilder {
5
+ addHeader(type: 'image' | 'video' | 'document', url: string): TemplateBuilder;
6
+ addTextHeader(text: string): TemplateBuilder;
7
+ addBodyParam(text: string): TemplateBuilder;
8
+ addQuickReplyButton(index: number, payload: string): TemplateBuilder;
9
+ addUrlButton(index: number, dynamicSuffix: string): TemplateBuilder;
10
+ build(): TemplateComponent[];
11
+ }
12
+
13
+ //#endregion
14
+ //#region src/templates/builders.d.ts
15
+ //# sourceMappingURL=types.d.ts.map
16
+ declare function createTemplateBuilder(): TemplateBuilder;
17
+
18
+ //#endregion
19
+ //# sourceMappingURL=builders.d.ts.map
20
+
21
+ export { TemplateBuilder, createTemplateBuilder };
22
+ //# sourceMappingURL=index-CTZd2C9y.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-CTZd2C9y.d.ts","names":[],"sources":["../src/templates/types.ts","../src/templates/builders.ts"],"sourcesContent":null,"mappings":";;;UAEiB,eAAA;gEAC+C;EAD/C,aAAA,CAAA,IAAe,EAAA,MAAA,CAAA,EAED,eAFC;EAAA,YAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EAGF,eAHE;EAAA,mBACgC,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAGT,eAHS;EAAe,YAChD,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,EAAA,MAAA,CAAA,EAGuB,eAHvB;EAAe,KAChB,EAAA,EAGnB,iBAHmB,EAAA;;;;;;iBCFd,qBAAA,CAAA,GAAyB"}