n8n-nodes-whaapy 0.2.4 → 0.3.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.
@@ -1,6 +1,82 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Whaapy = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ // Helper function to convert string to slug (for auto-generating IDs)
6
+ function slugify(text) {
7
+ return text
8
+ .toString()
9
+ .toLowerCase()
10
+ .trim()
11
+ .replace(/\s+/g, '_')
12
+ .replace(/[^\w\-]+/g, '')
13
+ .replace(/\-\-+/g, '_')
14
+ .replace(/^-+/, '')
15
+ .replace(/-+$/, '')
16
+ .substring(0, 256);
17
+ }
18
+ // Build interactive message payload from structured fields
19
+ function buildInteractivePayload(params) {
20
+ // If using raw JSON, return it directly
21
+ if (params.useRawJson && params.rawJson) {
22
+ return params.rawJson;
23
+ }
24
+ const interactive = {
25
+ type: params.interactiveType,
26
+ body: {
27
+ text: params.bodyText,
28
+ },
29
+ };
30
+ // Add header if specified
31
+ if (params.headerType && params.headerType !== 'none') {
32
+ if (params.headerType === 'text' && params.headerText) {
33
+ interactive.header = {
34
+ type: 'text',
35
+ text: params.headerText,
36
+ };
37
+ }
38
+ else if (['image', 'video', 'document'].includes(params.headerType) && params.headerMediaUrl) {
39
+ interactive.header = {
40
+ type: params.headerType,
41
+ [params.headerType]: {
42
+ link: params.headerMediaUrl,
43
+ },
44
+ };
45
+ }
46
+ }
47
+ // Add footer if specified and not empty
48
+ if (params.footerText && params.footerText.trim()) {
49
+ interactive.footer = {
50
+ text: params.footerText.trim(),
51
+ };
52
+ }
53
+ // Build action based on type
54
+ if (params.interactiveType === 'button' && params.buttons && params.buttons.length > 0) {
55
+ interactive.action = {
56
+ buttons: params.buttons.map((btn) => ({
57
+ type: 'reply',
58
+ reply: {
59
+ id: btn.id || slugify(btn.title),
60
+ title: btn.title.substring(0, 20),
61
+ },
62
+ })),
63
+ };
64
+ }
65
+ else if (params.interactiveType === 'list' && params.sections && params.sections.length > 0) {
66
+ interactive.action = {
67
+ button: params.listButtonText || 'Ver Opciones',
68
+ sections: params.sections.map((section) => ({
69
+ title: section.title || undefined,
70
+ rows: section.rows.map((row) => ({
71
+ id: row.id || slugify(row.title),
72
+ title: row.title.substring(0, 24),
73
+ description: row.description ? row.description.substring(0, 72) : undefined,
74
+ })),
75
+ })),
76
+ };
77
+ }
78
+ return interactive;
79
+ }
4
80
  class Whaapy {
5
81
  constructor() {
6
82
  this.description = {
@@ -66,24 +142,12 @@ class Whaapy {
66
142
  value: 'send',
67
143
  action: 'Send a message',
68
144
  description: 'Send a WhatsApp message',
69
- routing: {
70
- request: {
71
- method: 'POST',
72
- url: '/messages/v1',
73
- },
74
- },
75
145
  },
76
146
  {
77
147
  name: 'Retry',
78
148
  value: 'retry',
79
149
  action: 'Retry a failed message',
80
150
  description: 'Retry sending a failed message',
81
- routing: {
82
- request: {
83
- method: 'POST',
84
- url: '=/messages/v1/{{$parameter.messageId}}/retry',
85
- },
86
- },
87
151
  },
88
152
  ],
89
153
  default: 'send',
@@ -100,9 +164,6 @@ class Whaapy {
100
164
  displayOptions: {
101
165
  show: { resource: ['message'], operation: ['send'] },
102
166
  },
103
- routing: {
104
- send: { type: 'body', property: 'to' },
105
- },
106
167
  },
107
168
  // Message: Send - Type
108
169
  {
@@ -127,9 +188,6 @@ class Whaapy {
127
188
  displayOptions: {
128
189
  show: { resource: ['message'], operation: ['send'] },
129
190
  },
130
- routing: {
131
- send: { type: 'body', property: 'type' },
132
- },
133
191
  },
134
192
  // Message: Send - Text content
135
193
  {
@@ -142,10 +200,7 @@ class Whaapy {
142
200
  description: 'The text content of the message',
143
201
  displayOptions: {
144
202
  show: { resource: ['message'], operation: ['send'], messageType: ['text'] },
145
- },
146
- routing: {
147
- send: { type: 'body', property: 'content' },
148
- },
203
+ }
149
204
  },
150
205
  // Message: Send - Media URL (for image, video, audio, document, sticker)
151
206
  {
@@ -163,9 +218,6 @@ class Whaapy {
163
218
  messageType: ['image', 'video', 'audio', 'document', 'sticker'],
164
219
  },
165
220
  },
166
- routing: {
167
- send: { type: 'body', property: '={{$parameter.messageType}}.link' },
168
- },
169
221
  },
170
222
  // Message: Send - Caption (for media)
171
223
  {
@@ -181,9 +233,6 @@ class Whaapy {
181
233
  messageType: ['image', 'video', 'document'],
182
234
  },
183
235
  },
184
- routing: {
185
- send: { type: 'body', property: '={{$parameter.messageType}}.caption' },
186
- },
187
236
  },
188
237
  // Message: Send - Template name
189
238
  {
@@ -196,10 +245,7 @@ class Whaapy {
196
245
  description: 'Exact name of the WhatsApp template as it appears in Meta Business Manager',
197
246
  displayOptions: {
198
247
  show: { resource: ['message'], operation: ['send'], messageType: ['template'] },
199
- },
200
- routing: {
201
- send: { type: 'body', property: 'templateName' },
202
- },
248
+ }
203
249
  },
204
250
  // Message: Send - Template Language
205
251
  {
@@ -221,10 +267,7 @@ class Whaapy {
221
267
  { name: 'French', value: 'fr' },
222
268
  { name: 'German', value: 'de' },
223
269
  { name: 'Italian', value: 'it' },
224
- ],
225
- routing: {
226
- send: { type: 'body', property: 'language' },
227
- },
270
+ ]
228
271
  },
229
272
  // Message: Send - Template Additional Options
230
273
  {
@@ -244,13 +287,6 @@ class Whaapy {
244
287
  default: '',
245
288
  placeholder: 'Juan Pérez, #ORD-12345, $1500',
246
289
  description: 'Comma-separated values for {{1}}, {{2}}, etc. placeholders in the template body',
247
- routing: {
248
- send: {
249
- type: 'body',
250
- property: 'template_parameters',
251
- value: '={{ $value ? $value.split(",").map(v => v.trim()) : undefined }}',
252
- },
253
- },
254
290
  },
255
291
  {
256
292
  displayName: 'Header Media Type',
@@ -261,10 +297,7 @@ class Whaapy {
261
297
  { name: 'Image', value: 'image' },
262
298
  { name: 'Video', value: 'video' },
263
299
  { name: 'Document', value: 'document' },
264
- ],
265
- routing: {
266
- send: { type: 'body', property: 'header_media.type' },
267
- },
300
+ ]
268
301
  },
269
302
  {
270
303
  displayName: 'Header Media URL',
@@ -272,26 +305,279 @@ class Whaapy {
272
305
  type: 'string',
273
306
  default: '',
274
307
  placeholder: 'https://example.com/image.jpg',
275
- description: 'Public URL of the media file for header',
276
- routing: {
277
- send: { type: 'body', property: 'header_media.url' },
278
- },
308
+ description: 'Public URL of the media file for header'
279
309
  },
280
310
  ],
281
311
  },
282
- // Message: Send - Interactive content
312
+ // ===========================================
313
+ // INTERACTIVE MESSAGE FIELDS (Structured)
314
+ // ===========================================
315
+ // Interactive: Type selector (button or list)
283
316
  {
284
- displayName: 'Interactive Content',
285
- name: 'interactiveContent',
286
- type: 'json',
317
+ displayName: 'Interactive Type',
318
+ name: 'interactiveType',
319
+ type: 'options',
287
320
  required: true,
288
- default: '{}',
289
- description: 'Interactive message content (buttons, lists)',
321
+ options: [
322
+ { name: 'Buttons (Reply Buttons)', value: 'button' },
323
+ { name: 'List (Menu)', value: 'list' },
324
+ ],
325
+ default: 'button',
326
+ description: 'Type of interactive message. Buttons show up to 3 options, Lists show a menu with sections.',
327
+ displayOptions: {
328
+ show: { resource: ['message'], operation: ['send'], messageType: ['interactive'], interactiveUseRawJson: [false] },
329
+ },
330
+ },
331
+ // Interactive: Body text (required)
332
+ {
333
+ displayName: 'Body Text',
334
+ name: 'interactiveBodyText',
335
+ type: 'string',
336
+ typeOptions: { rows: 3 },
337
+ required: true,
338
+ default: '',
339
+ placeholder: '¿Cómo podemos ayudarte hoy?',
340
+ description: 'Main text of the message. Max 1024 characters.',
341
+ displayOptions: {
342
+ show: { resource: ['message'], operation: ['send'], messageType: ['interactive'], interactiveUseRawJson: [false] },
343
+ },
344
+ },
345
+ // Interactive: Header type (optional)
346
+ {
347
+ displayName: 'Header Type',
348
+ name: 'interactiveHeaderType',
349
+ type: 'options',
350
+ default: 'none',
351
+ options: [
352
+ { name: 'None', value: 'none' },
353
+ { name: 'Text', value: 'text' },
354
+ { name: 'Image', value: 'image' },
355
+ { name: 'Video', value: 'video' },
356
+ { name: 'Document', value: 'document' },
357
+ ],
358
+ description: 'Optional header for the message',
359
+ displayOptions: {
360
+ show: { resource: ['message'], operation: ['send'], messageType: ['interactive'], interactiveUseRawJson: [false] },
361
+ },
362
+ },
363
+ // Interactive: Header text (if type=text)
364
+ {
365
+ displayName: 'Header Text',
366
+ name: 'interactiveHeaderText',
367
+ type: 'string',
368
+ default: '',
369
+ placeholder: '🍕 Pizzería Whaapy',
370
+ description: 'Header text. Max 60 characters.',
371
+ displayOptions: {
372
+ show: {
373
+ resource: ['message'],
374
+ operation: ['send'],
375
+ messageType: ['interactive'],
376
+ interactiveHeaderType: ['text'],
377
+ interactiveUseRawJson: [false],
378
+ },
379
+ },
380
+ },
381
+ // Interactive: Header media URL (if type=image|video|document)
382
+ {
383
+ displayName: 'Header Media URL',
384
+ name: 'interactiveHeaderMediaUrl',
385
+ type: 'string',
386
+ default: '',
387
+ placeholder: 'https://example.com/image.jpg',
388
+ description: 'Public URL of the media file for header',
389
+ displayOptions: {
390
+ show: {
391
+ resource: ['message'],
392
+ operation: ['send'],
393
+ messageType: ['interactive'],
394
+ interactiveHeaderType: ['image', 'video', 'document'],
395
+ interactiveUseRawJson: [false],
396
+ },
397
+ },
398
+ },
399
+ // Interactive: Footer text (optional)
400
+ {
401
+ displayName: 'Footer Text',
402
+ name: 'interactiveFooterText',
403
+ type: 'string',
404
+ default: '',
405
+ placeholder: 'Responde con una opción',
406
+ description: 'Optional footer text in gray. Max 60 characters. Leave empty to omit.',
407
+ displayOptions: {
408
+ show: { resource: ['message'], operation: ['send'], messageType: ['interactive'], interactiveUseRawJson: [false] },
409
+ },
410
+ },
411
+ // Interactive: Buttons (if type=button)
412
+ {
413
+ displayName: 'Buttons',
414
+ name: 'interactiveButtons',
415
+ type: 'fixedCollection',
416
+ typeOptions: {
417
+ multipleValues: true,
418
+ maxValue: 3,
419
+ },
420
+ default: { buttonValues: [] },
421
+ description: 'Reply buttons (1-3). Users tap to respond.',
422
+ displayOptions: {
423
+ show: {
424
+ resource: ['message'],
425
+ operation: ['send'],
426
+ messageType: ['interactive'],
427
+ interactiveType: ['button'],
428
+ interactiveUseRawJson: [false],
429
+ },
430
+ },
431
+ options: [
432
+ {
433
+ displayName: 'Button',
434
+ name: 'buttonValues',
435
+ values: [
436
+ {
437
+ displayName: 'Title',
438
+ name: 'title',
439
+ type: 'string',
440
+ required: true,
441
+ default: '',
442
+ placeholder: 'Ver Menú',
443
+ description: 'Button text visible to user. Max 20 characters.',
444
+ },
445
+ {
446
+ displayName: 'ID',
447
+ name: 'id',
448
+ type: 'string',
449
+ default: '',
450
+ placeholder: 'ver_menu (optional, auto-generated if empty)',
451
+ description: 'Unique ID returned in webhook when user clicks. If empty, generated from title.',
452
+ },
453
+ ],
454
+ },
455
+ ],
456
+ },
457
+ // Interactive: List button text (if type=list)
458
+ {
459
+ displayName: 'List Button Text',
460
+ name: 'interactiveListButtonText',
461
+ type: 'string',
462
+ required: true,
463
+ default: 'Ver Opciones',
464
+ placeholder: 'Ver Menú',
465
+ description: 'Text for the button that opens the list menu. Max 20 characters.',
466
+ displayOptions: {
467
+ show: {
468
+ resource: ['message'],
469
+ operation: ['send'],
470
+ messageType: ['interactive'],
471
+ interactiveType: ['list'],
472
+ interactiveUseRawJson: [false],
473
+ },
474
+ },
475
+ },
476
+ // Interactive: Sections (if type=list)
477
+ {
478
+ displayName: 'Sections',
479
+ name: 'interactiveSections',
480
+ type: 'fixedCollection',
481
+ typeOptions: {
482
+ multipleValues: true,
483
+ maxValue: 10,
484
+ },
485
+ default: { sectionValues: [] },
486
+ description: 'Menu sections. Each section has a title and rows (options).',
487
+ displayOptions: {
488
+ show: {
489
+ resource: ['message'],
490
+ operation: ['send'],
491
+ messageType: ['interactive'],
492
+ interactiveType: ['list'],
493
+ interactiveUseRawJson: [false],
494
+ },
495
+ },
496
+ options: [
497
+ {
498
+ displayName: 'Section',
499
+ name: 'sectionValues',
500
+ values: [
501
+ {
502
+ displayName: 'Section Title',
503
+ name: 'title',
504
+ type: 'string',
505
+ default: '',
506
+ placeholder: 'Pizzas',
507
+ description: 'Section title. Required if more than 1 section. Max 24 characters.',
508
+ },
509
+ {
510
+ displayName: 'Rows',
511
+ name: 'rows',
512
+ type: 'fixedCollection',
513
+ typeOptions: {
514
+ multipleValues: true,
515
+ maxValue: 10,
516
+ },
517
+ default: { rowValues: [] },
518
+ description: 'Options in this section',
519
+ options: [
520
+ {
521
+ displayName: 'Row',
522
+ name: 'rowValues',
523
+ values: [
524
+ {
525
+ displayName: 'Title',
526
+ name: 'title',
527
+ type: 'string',
528
+ required: true,
529
+ default: '',
530
+ placeholder: 'Margarita',
531
+ description: 'Row title. Max 24 characters.',
532
+ },
533
+ {
534
+ displayName: 'Description',
535
+ name: 'description',
536
+ type: 'string',
537
+ default: '',
538
+ placeholder: 'Tomate, mozzarella - $150',
539
+ description: 'Row description. Optional. Max 72 characters.',
540
+ },
541
+ {
542
+ displayName: 'ID',
543
+ name: 'id',
544
+ type: 'string',
545
+ default: '',
546
+ placeholder: 'pizza_margarita (optional)',
547
+ description: 'Unique ID returned in webhook. If empty, generated from title.',
548
+ },
549
+ ],
550
+ },
551
+ ],
552
+ },
553
+ ],
554
+ },
555
+ ],
556
+ },
557
+ // Interactive: Advanced JSON (fallback for complex cases)
558
+ {
559
+ displayName: 'Use Raw JSON',
560
+ name: 'interactiveUseRawJson',
561
+ type: 'boolean',
562
+ default: false,
563
+ description: 'Use raw JSON instead of structured fields (for advanced use cases)',
290
564
  displayOptions: {
291
565
  show: { resource: ['message'], operation: ['send'], messageType: ['interactive'] },
292
566
  },
293
- routing: {
294
- send: { type: 'body', property: 'interactive' },
567
+ },
568
+ {
569
+ displayName: 'Interactive JSON',
570
+ name: 'interactiveRawJson',
571
+ type: 'json',
572
+ default: '{}',
573
+ description: 'Raw interactive content JSON. See docs.whaapy.com for structure.',
574
+ displayOptions: {
575
+ show: {
576
+ resource: ['message'],
577
+ operation: ['send'],
578
+ messageType: ['interactive'],
579
+ interactiveUseRawJson: [true],
580
+ },
295
581
  },
296
582
  },
297
583
  // Message: Send - Location
@@ -303,10 +589,7 @@ class Whaapy {
303
589
  default: 0,
304
590
  displayOptions: {
305
591
  show: { resource: ['message'], operation: ['send'], messageType: ['location'] },
306
- },
307
- routing: {
308
- send: { type: 'body', property: 'location.latitude' },
309
- },
592
+ }
310
593
  },
311
594
  {
312
595
  displayName: 'Longitude',
@@ -316,10 +599,7 @@ class Whaapy {
316
599
  default: 0,
317
600
  displayOptions: {
318
601
  show: { resource: ['message'], operation: ['send'], messageType: ['location'] },
319
- },
320
- routing: {
321
- send: { type: 'body', property: 'location.longitude' },
322
- },
602
+ }
323
603
  },
324
604
  {
325
605
  displayName: 'Location Name',
@@ -328,10 +608,7 @@ class Whaapy {
328
608
  default: '',
329
609
  displayOptions: {
330
610
  show: { resource: ['message'], operation: ['send'], messageType: ['location'] },
331
- },
332
- routing: {
333
- send: { type: 'body', property: 'location.name' },
334
- },
611
+ }
335
612
  },
336
613
  // Message: Send - Contacts
337
614
  {
@@ -361,10 +638,7 @@ class Whaapy {
361
638
  description: 'Array of contact cards to send. Each contact needs: name.formatted_name (required), phones[].phone (required). Optional: emails, org, addresses.',
362
639
  displayOptions: {
363
640
  show: { resource: ['message'], operation: ['send'], messageType: ['contacts'] },
364
- },
365
- routing: {
366
- send: { type: 'body', property: 'contacts' },
367
- },
641
+ }
368
642
  },
369
643
  // Message: Send - Reaction
370
644
  {
@@ -375,10 +649,7 @@ class Whaapy {
375
649
  default: '',
376
650
  displayOptions: {
377
651
  show: { resource: ['message'], operation: ['send'], messageType: ['reaction'] },
378
- },
379
- routing: {
380
- send: { type: 'body', property: 'reaction.message_id' },
381
- },
652
+ }
382
653
  },
383
654
  {
384
655
  displayName: 'Emoji',
@@ -388,10 +659,7 @@ class Whaapy {
388
659
  default: '👍',
389
660
  displayOptions: {
390
661
  show: { resource: ['message'], operation: ['send'], messageType: ['reaction'] },
391
- },
392
- routing: {
393
- send: { type: 'body', property: 'reaction.emoji' },
394
- },
662
+ }
395
663
  },
396
664
  // Message: Send - Additional Fields
397
665
  {
@@ -409,60 +677,42 @@ class Whaapy {
409
677
  name: 'pauseAi',
410
678
  type: 'boolean',
411
679
  default: false,
412
- description: 'Pause AI after sending this message',
413
- routing: {
414
- send: { type: 'body', property: 'ai.pause' },
415
- },
680
+ description: 'Pause AI after sending this message'
416
681
  },
417
682
  {
418
683
  displayName: 'Pause Duration (Minutes)',
419
684
  name: 'pauseDuration',
420
685
  type: 'number',
421
686
  default: 5,
422
- description: 'How long to pause AI (1-1440 minutes)',
423
- routing: {
424
- send: { type: 'body', property: 'ai.pauseDuration' },
425
- },
687
+ description: 'How long to pause AI (1-1440 minutes)'
426
688
  },
427
689
  {
428
690
  displayName: 'Disable AI',
429
691
  name: 'disableAi',
430
692
  type: 'boolean',
431
693
  default: false,
432
- description: 'Permanently disable AI for this conversation',
433
- routing: {
434
- send: { type: 'body', property: 'ai.disable' },
435
- },
694
+ description: 'Permanently disable AI for this conversation'
436
695
  },
437
696
  {
438
697
  displayName: 'Reply To Message ID',
439
698
  name: 'replyTo',
440
699
  type: 'string',
441
700
  default: '',
442
- description: 'Message ID to reply to',
443
- routing: {
444
- send: { type: 'body', property: 'context.message_id' },
445
- },
701
+ description: 'Message ID to reply to'
446
702
  },
447
703
  {
448
704
  displayName: 'Create Conversation',
449
705
  name: 'createConversation',
450
706
  type: 'boolean',
451
707
  default: true,
452
- description: 'Create a conversation if it doesn\'t exist',
453
- routing: {
454
- send: { type: 'body', property: 'createConversation' },
455
- },
708
+ description: 'Create a conversation if it doesn\'t exist'
456
709
  },
457
710
  {
458
711
  displayName: 'Metadata',
459
712
  name: 'metadata',
460
713
  type: 'json',
461
714
  default: '{}',
462
- description: 'Custom metadata to attach to the message',
463
- routing: {
464
- send: { type: 'body', property: 'metadata' },
465
- },
715
+ description: 'Custom metadata to attach to the message'
466
716
  },
467
717
  ],
468
718
  },
@@ -494,13 +744,7 @@ class Whaapy {
494
744
  name: 'Upload',
495
745
  value: 'upload',
496
746
  action: 'Upload media',
497
- description: 'Upload media to WhatsApp CDN',
498
- routing: {
499
- request: {
500
- method: 'POST',
501
- url: '/media/v1',
502
- },
503
- },
747
+ description: 'Upload media to WhatsApp CDN'
504
748
  },
505
749
  ],
506
750
  default: 'upload',
@@ -521,10 +765,7 @@ class Whaapy {
521
765
  default: 'image',
522
766
  displayOptions: {
523
767
  show: { resource: ['media'], operation: ['upload'] },
524
- },
525
- routing: {
526
- send: { type: 'body', property: 'type' },
527
- },
768
+ }
528
769
  },
529
770
  // Media: Upload - Binary property
530
771
  {
@@ -554,121 +795,61 @@ class Whaapy {
554
795
  name: 'List',
555
796
  value: 'list',
556
797
  action: 'List conversations',
557
- description: 'Get all conversations',
558
- routing: {
559
- request: {
560
- method: 'GET',
561
- url: '/conversations/v1',
562
- },
563
- },
798
+ description: 'Get all conversations'
564
799
  },
565
800
  {
566
801
  name: 'Get',
567
802
  value: 'get',
568
803
  action: 'Get a conversation',
569
804
  description: 'Get a specific conversation',
570
- routing: {
571
- request: {
572
- method: 'GET',
573
- url: '=/conversations/v1/{{$parameter.conversationId}}',
574
- },
575
- },
576
805
  },
577
806
  {
578
807
  name: 'Get by Phone',
579
808
  value: 'getByPhone',
580
809
  action: 'Get conversation by phone',
581
810
  description: 'Find conversation by phone number',
582
- routing: {
583
- request: {
584
- method: 'GET',
585
- url: '=/conversations/v1/by-phone/{{$parameter.phoneNumber}}',
586
- },
587
- },
588
811
  },
589
812
  {
590
813
  name: 'Get Messages',
591
814
  value: 'getMessages',
592
815
  action: 'Get conversation messages',
593
816
  description: 'Get message history of a conversation',
594
- routing: {
595
- request: {
596
- method: 'GET',
597
- url: '=/conversations/v1/{{$parameter.conversationId}}/messages',
598
- },
599
- },
600
817
  },
601
818
  {
602
819
  name: 'Close',
603
820
  value: 'close',
604
821
  action: 'Close a conversation',
605
822
  description: 'Close a conversation',
606
- routing: {
607
- request: {
608
- method: 'POST',
609
- url: '=/conversations/v1/{{$parameter.conversationId}}/close',
610
- },
611
- },
612
823
  },
613
824
  {
614
825
  name: 'Archive',
615
826
  value: 'archive',
616
827
  action: 'Archive a conversation',
617
828
  description: 'Archive a conversation',
618
- routing: {
619
- request: {
620
- method: 'POST',
621
- url: '=/conversations/v1/{{$parameter.conversationId}}/archive',
622
- },
623
- },
624
829
  },
625
830
  {
626
831
  name: 'Mark Read',
627
832
  value: 'markRead',
628
833
  action: 'Mark conversation as read',
629
834
  description: 'Mark a conversation as read',
630
- routing: {
631
- request: {
632
- method: 'PATCH',
633
- url: '=/conversations/v1/{{$parameter.conversationId}}/mark-read',
634
- },
635
- },
636
835
  },
637
836
  {
638
837
  name: 'Set AI',
639
838
  value: 'setAi',
640
839
  action: 'Enable/disable AI',
641
840
  description: 'Enable or disable AI for a conversation',
642
- routing: {
643
- request: {
644
- method: 'PATCH',
645
- url: '=/conversations/v1/{{$parameter.conversationId}}/ai',
646
- },
647
- },
648
841
  },
649
842
  {
650
843
  name: 'Pause AI',
651
844
  value: 'pauseAi',
652
845
  action: 'Pause AI temporarily',
653
846
  description: 'Pause AI for a conversation',
654
- routing: {
655
- request: {
656
- method: 'POST',
657
- url: '=/conversations/v1/{{$parameter.conversationId}}/ai/pause',
658
- },
659
- },
660
847
  },
661
848
  {
662
849
  name: 'AI Suggest',
663
850
  value: 'aiSuggest',
664
851
  action: 'Get AI suggestion',
665
852
  description: 'Get an AI suggestion without sending',
666
- routing: {
667
- request: {
668
- method: 'POST',
669
- url: '=/conversations/v1/{{$parameter.conversationId}}/ai-suggest',
670
- },
671
- },
672
853
  },
673
854
  ],
674
855
  default: 'list',
@@ -708,10 +889,7 @@ class Whaapy {
708
889
  default: true,
709
890
  displayOptions: {
710
891
  show: { resource: ['conversation'], operation: ['setAi'] },
711
- },
712
- routing: {
713
- send: { type: 'body', property: 'aiEnabled' },
714
- },
892
+ }
715
893
  },
716
894
  // Conversation: Pause AI - Duration
717
895
  {
@@ -723,10 +901,7 @@ class Whaapy {
723
901
  description: 'How long to pause AI (1-1440 minutes)',
724
902
  displayOptions: {
725
903
  show: { resource: ['conversation'], operation: ['pauseAi'] },
726
- },
727
- routing: {
728
- send: { type: 'body', property: 'duration' },
729
- },
904
+ }
730
905
  },
731
906
  // Conversation: List - Filters
732
907
  {
@@ -744,10 +919,7 @@ class Whaapy {
744
919
  name: 'search',
745
920
  type: 'string',
746
921
  default: '',
747
- description: 'Search by name or phone',
748
- routing: {
749
- send: { type: 'query', property: 'search' },
750
- },
922
+ description: 'Search by name or phone'
751
923
  },
752
924
  {
753
925
  displayName: 'Status',
@@ -759,29 +931,20 @@ class Whaapy {
759
931
  { name: 'Closed', value: 'closed' },
760
932
  { name: 'Archived', value: 'archived' },
761
933
  ],
762
- default: 'all',
763
- routing: {
764
- send: { type: 'query', property: 'status' },
765
- },
934
+ default: 'all'
766
935
  },
767
936
  {
768
937
  displayName: 'Limit',
769
938
  name: 'limit',
770
939
  type: 'number',
771
940
  default: 20,
772
- description: 'Max results (1-100)',
773
- routing: {
774
- send: { type: 'query', property: 'limit' },
775
- },
941
+ description: 'Max results (1-100)'
776
942
  },
777
943
  {
778
944
  displayName: 'Offset',
779
945
  name: 'offset',
780
946
  type: 'number',
781
- default: 0,
782
- routing: {
783
- send: { type: 'query', property: 'offset' },
784
- },
947
+ default: 0
785
948
  },
786
949
  ],
787
950
  },
@@ -800,20 +963,14 @@ class Whaapy {
800
963
  displayName: 'Limit',
801
964
  name: 'limit',
802
965
  type: 'number',
803
- default: 50,
804
- routing: {
805
- send: { type: 'query', property: 'limit' },
806
- },
966
+ default: 50
807
967
  },
808
968
  {
809
969
  displayName: 'Cursor',
810
970
  name: 'cursor',
811
971
  type: 'string',
812
972
  default: '',
813
- description: 'Pagination cursor',
814
- routing: {
815
- send: { type: 'query', property: 'cursor' },
816
- },
973
+ description: 'Pagination cursor'
817
974
  },
818
975
  ],
819
976
  },
@@ -833,25 +990,13 @@ class Whaapy {
833
990
  name: 'Toggle',
834
991
  value: 'toggle',
835
992
  action: 'Toggle AI globally',
836
- description: 'Enable or disable AI globally',
837
- routing: {
838
- request: {
839
- method: 'POST',
840
- url: '/agent/v1/toggle',
841
- },
842
- },
993
+ description: 'Enable or disable AI globally'
843
994
  },
844
995
  {
845
996
  name: 'Pause',
846
997
  value: 'pause',
847
998
  action: 'Pause AI globally',
848
- description: 'Pause AI globally for X minutes',
849
- routing: {
850
- request: {
851
- method: 'POST',
852
- url: '/agent/v1/pause',
853
- },
854
- },
999
+ description: 'Pause AI globally for X minutes'
855
1000
  },
856
1001
  ],
857
1002
  default: 'toggle',
@@ -865,10 +1010,7 @@ class Whaapy {
865
1010
  default: true,
866
1011
  displayOptions: {
867
1012
  show: { resource: ['agent'], operation: ['toggle'] },
868
- },
869
- routing: {
870
- send: { type: 'body', property: 'enabled' },
871
- },
1013
+ }
872
1014
  },
873
1015
  // Agent: Pause - Duration
874
1016
  {
@@ -880,10 +1022,7 @@ class Whaapy {
880
1022
  description: 'How long to pause AI globally',
881
1023
  displayOptions: {
882
1024
  show: { resource: ['agent'], operation: ['pause'] },
883
- },
884
- routing: {
885
- send: { type: 'body', property: 'duration' },
886
- },
1025
+ }
887
1026
  },
888
1027
  // ===========================================
889
1028
  // TEMPLATE OPERATIONS
@@ -901,49 +1040,25 @@ class Whaapy {
901
1040
  name: 'List',
902
1041
  value: 'list',
903
1042
  action: 'List templates',
904
- description: 'Get all WhatsApp templates',
905
- routing: {
906
- request: {
907
- method: 'GET',
908
- url: '/templates/v1',
909
- },
910
- },
1043
+ description: 'Get all WhatsApp templates'
911
1044
  },
912
1045
  {
913
1046
  name: 'Get',
914
1047
  value: 'get',
915
1048
  action: 'Get a template',
916
1049
  description: 'Get a specific template',
917
- routing: {
918
- request: {
919
- method: 'GET',
920
- url: '=/templates/v1/{{$parameter.templateId}}',
921
- },
922
- },
923
1050
  },
924
1051
  {
925
1052
  name: 'Get Variables',
926
1053
  value: 'getVariables',
927
1054
  action: 'Get template variables',
928
- description: 'Get available template variables',
929
- routing: {
930
- request: {
931
- method: 'GET',
932
- url: '/templates/v1/variables',
933
- },
934
- },
1055
+ description: 'Get available template variables'
935
1056
  },
936
1057
  {
937
1058
  name: 'Sync',
938
1059
  value: 'sync',
939
1060
  action: 'Sync templates',
940
- description: 'Sync templates from Meta',
941
- routing: {
942
- request: {
943
- method: 'POST',
944
- url: '/templates/v1/sync',
945
- },
946
- },
1061
+ description: 'Sync templates from Meta'
947
1062
  },
948
1063
  ],
949
1064
  default: 'list',
@@ -975,28 +1090,19 @@ class Whaapy {
975
1090
  name: 'status',
976
1091
  type: 'string',
977
1092
  default: '',
978
- description: 'Filter by template status',
979
- routing: {
980
- send: { type: 'query', property: 'status' },
981
- },
1093
+ description: 'Filter by template status'
982
1094
  },
983
1095
  {
984
1096
  displayName: 'Limit',
985
1097
  name: 'limit',
986
1098
  type: 'number',
987
- default: 20,
988
- routing: {
989
- send: { type: 'query', property: 'limit' },
990
- },
1099
+ default: 20
991
1100
  },
992
1101
  {
993
1102
  displayName: 'Offset',
994
1103
  name: 'offset',
995
1104
  type: 'number',
996
- default: 0,
997
- routing: {
998
- send: { type: 'query', property: 'offset' },
999
- },
1105
+ default: 0
1000
1106
  },
1001
1107
  ],
1002
1108
  },
@@ -1016,121 +1122,61 @@ class Whaapy {
1016
1122
  name: 'List',
1017
1123
  value: 'list',
1018
1124
  action: 'List contacts',
1019
- description: 'Get all contacts',
1020
- routing: {
1021
- request: {
1022
- method: 'GET',
1023
- url: '/contacts/v1',
1024
- },
1025
- },
1125
+ description: 'Get all contacts'
1026
1126
  },
1027
1127
  {
1028
1128
  name: 'Get',
1029
1129
  value: 'get',
1030
1130
  action: 'Get a contact',
1031
1131
  description: 'Get a specific contact',
1032
- routing: {
1033
- request: {
1034
- method: 'GET',
1035
- url: '=/contacts/v1/{{$parameter.contactId}}',
1036
- },
1037
- },
1038
1132
  },
1039
1133
  {
1040
1134
  name: 'Create',
1041
1135
  value: 'create',
1042
1136
  action: 'Create a contact',
1043
- description: 'Create a new contact',
1044
- routing: {
1045
- request: {
1046
- method: 'POST',
1047
- url: '/contacts/v1',
1048
- },
1049
- },
1137
+ description: 'Create a new contact'
1050
1138
  },
1051
1139
  {
1052
1140
  name: 'Update',
1053
1141
  value: 'update',
1054
1142
  action: 'Update a contact',
1055
1143
  description: 'Update an existing contact',
1056
- routing: {
1057
- request: {
1058
- method: 'PATCH',
1059
- url: '=/contacts/v1/{{$parameter.contactId}}',
1060
- },
1061
- },
1062
1144
  },
1063
1145
  {
1064
1146
  name: 'Delete',
1065
1147
  value: 'delete',
1066
1148
  action: 'Delete a contact',
1067
1149
  description: 'Delete a contact',
1068
- routing: {
1069
- request: {
1070
- method: 'DELETE',
1071
- url: '=/contacts/v1/{{$parameter.contactId}}',
1072
- },
1073
- },
1074
1150
  },
1075
1151
  {
1076
1152
  name: 'Search',
1077
1153
  value: 'search',
1078
1154
  action: 'Search contacts',
1079
- description: 'Advanced search for contacts',
1080
- routing: {
1081
- request: {
1082
- method: 'POST',
1083
- url: '/contacts/v1/search',
1084
- },
1085
- },
1155
+ description: 'Advanced search for contacts'
1086
1156
  },
1087
1157
  {
1088
1158
  name: 'Bulk',
1089
1159
  value: 'bulk',
1090
1160
  action: 'Bulk operations',
1091
- description: 'Perform bulk operations on contacts',
1092
- routing: {
1093
- request: {
1094
- method: 'POST',
1095
- url: '/contacts/v1/bulk',
1096
- },
1097
- },
1161
+ description: 'Perform bulk operations on contacts'
1098
1162
  },
1099
1163
  {
1100
1164
  name: 'Merge',
1101
1165
  value: 'merge',
1102
1166
  action: 'Merge contacts',
1103
1167
  description: 'Merge two contacts',
1104
- routing: {
1105
- request: {
1106
- method: 'POST',
1107
- url: '=/contacts/v1/{{$parameter.contactId}}/merge',
1108
- },
1109
- },
1110
1168
  },
1111
1169
  {
1112
1170
  name: 'Get Tags',
1113
1171
  value: 'getTags',
1114
1172
  action: 'Get all tags',
1115
- description: 'Get all available tags',
1116
- routing: {
1117
- request: {
1118
- method: 'GET',
1119
- url: '/contacts/v1/tags',
1120
- },
1121
- },
1173
+ description: 'Get all available tags'
1122
1174
  },
1123
1175
  {
1124
1176
  name: 'Get Fields',
1125
1177
  value: 'getFields',
1126
1178
  action: 'Get custom fields',
1127
- description: 'Get available custom fields',
1128
- routing: {
1129
- request: {
1130
- method: 'GET',
1131
- url: '/contacts/v1/fields',
1132
- },
1133
- },
1179
+ description: 'Get available custom fields'
1134
1180
  },
1135
1181
  ],
1136
1182
  default: 'list',
@@ -1158,10 +1204,7 @@ class Whaapy {
1158
1204
  default: '',
1159
1205
  displayOptions: {
1160
1206
  show: { resource: ['contact'], operation: ['create'] },
1161
- },
1162
- routing: {
1163
- send: { type: 'body', property: 'name' },
1164
- },
1207
+ }
1165
1208
  },
1166
1209
  {
1167
1210
  displayName: 'Phone Number',
@@ -1172,10 +1215,7 @@ class Whaapy {
1172
1215
  placeholder: '+5215512345678',
1173
1216
  displayOptions: {
1174
1217
  show: { resource: ['contact'], operation: ['create'] },
1175
- },
1176
- routing: {
1177
- send: { type: 'body', property: 'phoneNumber' },
1178
- },
1218
+ }
1179
1219
  },
1180
1220
  {
1181
1221
  displayName: 'Additional Fields',
@@ -1191,38 +1231,26 @@ class Whaapy {
1191
1231
  displayName: 'Email',
1192
1232
  name: 'email',
1193
1233
  type: 'string',
1194
- default: '',
1195
- routing: {
1196
- send: { type: 'body', property: 'email' },
1197
- },
1234
+ default: ''
1198
1235
  },
1199
1236
  {
1200
1237
  displayName: 'Tags',
1201
1238
  name: 'tags',
1202
1239
  type: 'string',
1203
1240
  default: '',
1204
- description: 'Comma-separated list of tags',
1205
- routing: {
1206
- send: { type: 'body', property: 'tags' },
1207
- },
1241
+ description: 'Comma-separated list of tags'
1208
1242
  },
1209
1243
  {
1210
1244
  displayName: 'Custom Fields',
1211
1245
  name: 'customFields',
1212
1246
  type: 'json',
1213
- default: '{}',
1214
- routing: {
1215
- send: { type: 'body', property: 'customFields' },
1216
- },
1247
+ default: '{}'
1217
1248
  },
1218
1249
  {
1219
1250
  displayName: 'Metadata',
1220
1251
  name: 'metadata',
1221
1252
  type: 'json',
1222
- default: '{}',
1223
- routing: {
1224
- send: { type: 'body', property: 'metadata' },
1225
- },
1253
+ default: '{}'
1226
1254
  },
1227
1255
  ],
1228
1256
  },
@@ -1241,47 +1269,32 @@ class Whaapy {
1241
1269
  displayName: 'Name',
1242
1270
  name: 'name',
1243
1271
  type: 'string',
1244
- default: '',
1245
- routing: {
1246
- send: { type: 'body', property: 'name' },
1247
- },
1272
+ default: ''
1248
1273
  },
1249
1274
  {
1250
1275
  displayName: 'Phone Number',
1251
1276
  name: 'phoneNumber',
1252
1277
  type: 'string',
1253
- default: '',
1254
- routing: {
1255
- send: { type: 'body', property: 'phoneNumber' },
1256
- },
1278
+ default: ''
1257
1279
  },
1258
1280
  {
1259
1281
  displayName: 'Email',
1260
1282
  name: 'email',
1261
1283
  type: 'string',
1262
- default: '',
1263
- routing: {
1264
- send: { type: 'body', property: 'email' },
1265
- },
1284
+ default: ''
1266
1285
  },
1267
1286
  {
1268
1287
  displayName: 'Tags',
1269
1288
  name: 'tags',
1270
1289
  type: 'string',
1271
1290
  default: '',
1272
- description: 'Comma-separated list of tags',
1273
- routing: {
1274
- send: { type: 'body', property: 'tags' },
1275
- },
1291
+ description: 'Comma-separated list of tags'
1276
1292
  },
1277
1293
  {
1278
1294
  displayName: 'Custom Fields',
1279
1295
  name: 'customFields',
1280
1296
  type: 'json',
1281
- default: '{}',
1282
- routing: {
1283
- send: { type: 'body', property: 'customFields' },
1284
- },
1297
+ default: '{}'
1285
1298
  },
1286
1299
  ],
1287
1300
  },
@@ -1294,10 +1307,7 @@ class Whaapy {
1294
1307
  default: '',
1295
1308
  displayOptions: {
1296
1309
  show: { resource: ['contact'], operation: ['search'] },
1297
- },
1298
- routing: {
1299
- send: { type: 'body', property: 'query' },
1300
- },
1310
+ }
1301
1311
  },
1302
1312
  {
1303
1313
  displayName: 'Search Options',
@@ -1313,28 +1323,19 @@ class Whaapy {
1313
1323
  displayName: 'Filters',
1314
1324
  name: 'filters',
1315
1325
  type: 'json',
1316
- default: '{}',
1317
- routing: {
1318
- send: { type: 'body', property: 'filters' },
1319
- },
1326
+ default: '{}'
1320
1327
  },
1321
1328
  {
1322
1329
  displayName: 'Limit',
1323
1330
  name: 'limit',
1324
1331
  type: 'number',
1325
- default: 20,
1326
- routing: {
1327
- send: { type: 'body', property: 'limit' },
1328
- },
1332
+ default: 20
1329
1333
  },
1330
1334
  {
1331
1335
  displayName: 'Cursor',
1332
1336
  name: 'cursor',
1333
1337
  type: 'string',
1334
- default: '',
1335
- routing: {
1336
- send: { type: 'body', property: 'cursor' },
1337
- },
1338
+ default: ''
1338
1339
  },
1339
1340
  ],
1340
1341
  },
@@ -1354,10 +1355,7 @@ class Whaapy {
1354
1355
  default: 'create',
1355
1356
  displayOptions: {
1356
1357
  show: { resource: ['contact'], operation: ['bulk'] },
1357
- },
1358
- routing: {
1359
- send: { type: 'body', property: 'operation' },
1360
- },
1358
+ }
1361
1359
  },
1362
1360
  {
1363
1361
  displayName: 'Contacts Data',
@@ -1368,10 +1366,7 @@ class Whaapy {
1368
1366
  description: 'Array of contacts or contact IDs',
1369
1367
  displayOptions: {
1370
1368
  show: { resource: ['contact'], operation: ['bulk'] },
1371
- },
1372
- routing: {
1373
- send: { type: 'body', property: 'contacts' },
1374
- },
1369
+ }
1375
1370
  },
1376
1371
  {
1377
1372
  displayName: 'Operation Data',
@@ -1381,10 +1376,7 @@ class Whaapy {
1381
1376
  description: 'Additional data for the operation',
1382
1377
  displayOptions: {
1383
1378
  show: { resource: ['contact'], operation: ['bulk'] },
1384
- },
1385
- routing: {
1386
- send: { type: 'body', property: 'data' },
1387
- },
1379
+ }
1388
1380
  },
1389
1381
  // Contact: Merge - Merge With ID
1390
1382
  {
@@ -1396,10 +1388,7 @@ class Whaapy {
1396
1388
  description: 'ID of the contact to merge into the primary contact',
1397
1389
  displayOptions: {
1398
1390
  show: { resource: ['contact'], operation: ['merge'] },
1399
- },
1400
- routing: {
1401
- send: { type: 'body', property: 'mergeWith' },
1402
- },
1391
+ }
1403
1392
  },
1404
1393
  // Contact: List - Filters
1405
1394
  {
@@ -1416,38 +1405,26 @@ class Whaapy {
1416
1405
  displayName: 'Search',
1417
1406
  name: 'search',
1418
1407
  type: 'string',
1419
- default: '',
1420
- routing: {
1421
- send: { type: 'query', property: 'search' },
1422
- },
1408
+ default: ''
1423
1409
  },
1424
1410
  {
1425
1411
  displayName: 'Tags',
1426
1412
  name: 'tags',
1427
1413
  type: 'string',
1428
1414
  default: '',
1429
- description: 'Comma-separated list of tags',
1430
- routing: {
1431
- send: { type: 'query', property: 'tags' },
1432
- },
1415
+ description: 'Comma-separated list of tags'
1433
1416
  },
1434
1417
  {
1435
1418
  displayName: 'Funnel Stage ID',
1436
1419
  name: 'funnelStageId',
1437
1420
  type: 'string',
1438
- default: '',
1439
- routing: {
1440
- send: { type: 'query', property: 'funnel_stage_id' },
1441
- },
1421
+ default: ''
1442
1422
  },
1443
1423
  {
1444
1424
  displayName: 'Source',
1445
1425
  name: 'source',
1446
1426
  type: 'string',
1447
- default: '',
1448
- routing: {
1449
- send: { type: 'query', property: 'source' },
1450
- },
1427
+ default: ''
1451
1428
  },
1452
1429
  {
1453
1430
  displayName: 'Sort By',
@@ -1458,10 +1435,7 @@ class Whaapy {
1458
1435
  { name: 'Updated At', value: 'updated_at' },
1459
1436
  { name: 'Name', value: 'name' },
1460
1437
  ],
1461
- default: 'created_at',
1462
- routing: {
1463
- send: { type: 'query', property: 'sort_by' },
1464
- },
1438
+ default: 'created_at'
1465
1439
  },
1466
1440
  {
1467
1441
  displayName: 'Sort Order',
@@ -1471,28 +1445,19 @@ class Whaapy {
1471
1445
  { name: 'Ascending', value: 'asc' },
1472
1446
  { name: 'Descending', value: 'desc' },
1473
1447
  ],
1474
- default: 'desc',
1475
- routing: {
1476
- send: { type: 'query', property: 'sort_order' },
1477
- },
1448
+ default: 'desc'
1478
1449
  },
1479
1450
  {
1480
1451
  displayName: 'Limit',
1481
1452
  name: 'limit',
1482
1453
  type: 'number',
1483
- default: 20,
1484
- routing: {
1485
- send: { type: 'query', property: 'limit' },
1486
- },
1454
+ default: 20
1487
1455
  },
1488
1456
  {
1489
1457
  displayName: 'Cursor',
1490
1458
  name: 'cursor',
1491
1459
  type: 'string',
1492
- default: '',
1493
- routing: {
1494
- send: { type: 'query', property: 'cursor' },
1495
- },
1460
+ default: ''
1496
1461
  },
1497
1462
  ],
1498
1463
  },
@@ -1512,85 +1477,43 @@ class Whaapy {
1512
1477
  name: 'List Stages',
1513
1478
  value: 'listStages',
1514
1479
  action: 'List funnel stages',
1515
- description: 'Get all funnel stages',
1516
- routing: {
1517
- request: {
1518
- method: 'GET',
1519
- url: '/funnel/v1/stages',
1520
- },
1521
- },
1480
+ description: 'Get all funnel stages'
1522
1481
  },
1523
1482
  {
1524
1483
  name: 'Get Stage',
1525
1484
  value: 'getStage',
1526
1485
  action: 'Get a funnel stage',
1527
1486
  description: 'Get a specific funnel stage',
1528
- routing: {
1529
- request: {
1530
- method: 'GET',
1531
- url: '=/funnel/v1/stages/{{$parameter.stageId}}',
1532
- },
1533
- },
1534
1487
  },
1535
1488
  {
1536
1489
  name: 'Create Stage',
1537
1490
  value: 'createStage',
1538
1491
  action: 'Create a funnel stage',
1539
- description: 'Create a new funnel stage',
1540
- routing: {
1541
- request: {
1542
- method: 'POST',
1543
- url: '/funnel/v1/stages',
1544
- },
1545
- },
1492
+ description: 'Create a new funnel stage'
1546
1493
  },
1547
1494
  {
1548
1495
  name: 'Update Stage',
1549
1496
  value: 'updateStage',
1550
1497
  action: 'Update a funnel stage',
1551
1498
  description: 'Update an existing funnel stage',
1552
- routing: {
1553
- request: {
1554
- method: 'PATCH',
1555
- url: '=/funnel/v1/stages/{{$parameter.stageId}}',
1556
- },
1557
- },
1558
1499
  },
1559
1500
  {
1560
1501
  name: 'Delete Stage',
1561
1502
  value: 'deleteStage',
1562
1503
  action: 'Delete a funnel stage',
1563
1504
  description: 'Delete a funnel stage',
1564
- routing: {
1565
- request: {
1566
- method: 'DELETE',
1567
- url: '=/funnel/v1/stages/{{$parameter.stageId}}',
1568
- },
1569
- },
1570
1505
  },
1571
1506
  {
1572
1507
  name: 'Reorder Stages',
1573
1508
  value: 'reorderStages',
1574
1509
  action: 'Reorder funnel stages',
1575
- description: 'Reorder the funnel stages',
1576
- routing: {
1577
- request: {
1578
- method: 'PATCH',
1579
- url: '/funnel/v1/stages/reorder',
1580
- },
1581
- },
1510
+ description: 'Reorder the funnel stages'
1582
1511
  },
1583
1512
  {
1584
1513
  name: 'Move Contact',
1585
1514
  value: 'moveContact',
1586
1515
  action: 'Move contact to stage',
1587
1516
  description: 'Move a contact to a funnel stage',
1588
- routing: {
1589
- request: {
1590
- method: 'POST',
1591
- url: '=/funnel/v1/contacts/{{$parameter.contactIdFunnel}}/move',
1592
- },
1593
- },
1594
1517
  },
1595
1518
  ],
1596
1519
  default: 'listStages',
@@ -1618,10 +1541,7 @@ class Whaapy {
1618
1541
  default: '',
1619
1542
  displayOptions: {
1620
1543
  show: { resource: ['funnel'], operation: ['createStage'] },
1621
- },
1622
- routing: {
1623
- send: { type: 'body', property: 'name' },
1624
- },
1544
+ }
1625
1545
  },
1626
1546
  {
1627
1547
  displayName: 'Stage Options',
@@ -1637,29 +1557,20 @@ class Whaapy {
1637
1557
  displayName: 'Position',
1638
1558
  name: 'position',
1639
1559
  type: 'number',
1640
- default: 0,
1641
- routing: {
1642
- send: { type: 'body', property: 'position' },
1643
- },
1560
+ default: 0
1644
1561
  },
1645
1562
  {
1646
1563
  displayName: 'Color',
1647
1564
  name: 'color',
1648
1565
  type: 'string',
1649
1566
  default: '#3B82F6',
1650
- description: 'Hex color code',
1651
- routing: {
1652
- send: { type: 'body', property: 'color' },
1653
- },
1567
+ description: 'Hex color code'
1654
1568
  },
1655
1569
  {
1656
1570
  displayName: 'Description',
1657
1571
  name: 'description',
1658
1572
  type: 'string',
1659
- default: '',
1660
- routing: {
1661
- send: { type: 'body', property: 'description' },
1662
- },
1573
+ default: ''
1663
1574
  },
1664
1575
  ],
1665
1576
  },
@@ -1678,37 +1589,25 @@ class Whaapy {
1678
1589
  displayName: 'Name',
1679
1590
  name: 'name',
1680
1591
  type: 'string',
1681
- default: '',
1682
- routing: {
1683
- send: { type: 'body', property: 'name' },
1684
- },
1592
+ default: ''
1685
1593
  },
1686
1594
  {
1687
1595
  displayName: 'Position',
1688
1596
  name: 'position',
1689
1597
  type: 'number',
1690
- default: 0,
1691
- routing: {
1692
- send: { type: 'body', property: 'position' },
1693
- },
1598
+ default: 0
1694
1599
  },
1695
1600
  {
1696
1601
  displayName: 'Color',
1697
1602
  name: 'color',
1698
1603
  type: 'string',
1699
- default: '',
1700
- routing: {
1701
- send: { type: 'body', property: 'color' },
1702
- },
1604
+ default: ''
1703
1605
  },
1704
1606
  {
1705
1607
  displayName: 'Description',
1706
1608
  name: 'description',
1707
1609
  type: 'string',
1708
- default: '',
1709
- routing: {
1710
- send: { type: 'body', property: 'description' },
1711
- },
1610
+ default: ''
1712
1611
  },
1713
1612
  ],
1714
1613
  },
@@ -1722,10 +1621,7 @@ class Whaapy {
1722
1621
  description: 'Array of objects with id and position',
1723
1622
  displayOptions: {
1724
1623
  show: { resource: ['funnel'], operation: ['reorderStages'] },
1725
- },
1726
- routing: {
1727
- send: { type: 'body', property: 'stages' },
1728
- },
1624
+ }
1729
1625
  },
1730
1626
  // Funnel: Move Contact - Fields
1731
1627
  {
@@ -1746,10 +1642,7 @@ class Whaapy {
1746
1642
  default: '',
1747
1643
  displayOptions: {
1748
1644
  show: { resource: ['funnel'], operation: ['moveContact'] },
1749
- },
1750
- routing: {
1751
- send: { type: 'body', property: 'stageId' },
1752
- },
1645
+ }
1753
1646
  },
1754
1647
  // Funnel: List Stages - Filters
1755
1648
  {
@@ -1766,24 +1659,637 @@ class Whaapy {
1766
1659
  displayName: 'Limit',
1767
1660
  name: 'limit',
1768
1661
  type: 'number',
1769
- default: 20,
1770
- routing: {
1771
- send: { type: 'query', property: 'limit' },
1772
- },
1662
+ default: 20
1773
1663
  },
1774
1664
  {
1775
1665
  displayName: 'Offset',
1776
1666
  name: 'offset',
1777
1667
  type: 'number',
1778
- default: 0,
1779
- routing: {
1780
- send: { type: 'query', property: 'offset' },
1781
- },
1668
+ default: 0
1782
1669
  },
1783
1670
  ],
1784
1671
  },
1785
1672
  ],
1786
1673
  };
1787
1674
  }
1675
+ async execute() {
1676
+ const items = this.getInputData();
1677
+ const returnData = [];
1678
+ for (let i = 0; i < items.length; i++) {
1679
+ try {
1680
+ const resource = this.getNodeParameter('resource', i);
1681
+ const operation = this.getNodeParameter('operation', i);
1682
+ const credentials = await this.getCredentials('whaapyApi');
1683
+ const baseUrl = credentials.baseUrl;
1684
+ const apiKey = credentials.apiKey;
1685
+ let response;
1686
+ // ===========================================
1687
+ // MESSAGE RESOURCE
1688
+ // ===========================================
1689
+ if (resource === 'message') {
1690
+ if (operation === 'send') {
1691
+ const to = this.getNodeParameter('to', i);
1692
+ const messageType = this.getNodeParameter('messageType', i);
1693
+ const body = { to, type: messageType };
1694
+ // Handle different message types
1695
+ if (messageType === 'text') {
1696
+ body.content = this.getNodeParameter('textContent', i);
1697
+ }
1698
+ else if (['image', 'video', 'audio', 'document', 'sticker'].includes(messageType)) {
1699
+ const mediaUrl = this.getNodeParameter('mediaUrl', i);
1700
+ body[messageType] = { link: mediaUrl };
1701
+ if (['image', 'video', 'document'].includes(messageType)) {
1702
+ const caption = this.getNodeParameter('caption', i, '');
1703
+ if (caption)
1704
+ body[messageType].caption = caption;
1705
+ }
1706
+ }
1707
+ else if (messageType === 'template') {
1708
+ body.templateName = this.getNodeParameter('templateName', i);
1709
+ body.language = this.getNodeParameter('templateLanguage', i);
1710
+ const templateOptions = this.getNodeParameter('templateOptions', i, {});
1711
+ if (templateOptions.parameters) {
1712
+ body.template_parameters = templateOptions.parameters.split(',').map((v) => v.trim());
1713
+ }
1714
+ if (templateOptions.headerMediaType && templateOptions.headerMediaUrl) {
1715
+ body.header_media = {
1716
+ type: templateOptions.headerMediaType,
1717
+ url: templateOptions.headerMediaUrl,
1718
+ };
1719
+ }
1720
+ }
1721
+ else if (messageType === 'interactive') {
1722
+ // Build interactive message from structured fields
1723
+ const useRawJson = this.getNodeParameter('interactiveUseRawJson', i, false);
1724
+ if (useRawJson) {
1725
+ const rawJson = this.getNodeParameter('interactiveRawJson', i, '{}');
1726
+ body.interactive = typeof rawJson === 'string' ? JSON.parse(rawJson) : rawJson;
1727
+ }
1728
+ else {
1729
+ const interactiveType = this.getNodeParameter('interactiveType', i);
1730
+ const bodyText = this.getNodeParameter('interactiveBodyText', i);
1731
+ const headerType = this.getNodeParameter('interactiveHeaderType', i, 'none');
1732
+ const headerText = this.getNodeParameter('interactiveHeaderText', i, '');
1733
+ const headerMediaUrl = this.getNodeParameter('interactiveHeaderMediaUrl', i, '');
1734
+ const footerText = this.getNodeParameter('interactiveFooterText', i, '');
1735
+ // Build buttons array
1736
+ let buttons = [];
1737
+ if (interactiveType === 'button') {
1738
+ const buttonsData = this.getNodeParameter('interactiveButtons', i, { buttonValues: [] });
1739
+ buttons = buttonsData.buttonValues || [];
1740
+ }
1741
+ // Build sections array
1742
+ let sections = [];
1743
+ let listButtonText = '';
1744
+ if (interactiveType === 'list') {
1745
+ listButtonText = this.getNodeParameter('interactiveListButtonText', i, 'Ver Opciones');
1746
+ const sectionsData = this.getNodeParameter('interactiveSections', i, { sectionValues: [] });
1747
+ if (sectionsData.sectionValues) {
1748
+ sections = sectionsData.sectionValues.map((section) => {
1749
+ var _a;
1750
+ return ({
1751
+ title: section.title,
1752
+ rows: ((_a = section.rows) === null || _a === void 0 ? void 0 : _a.rowValues) || [],
1753
+ });
1754
+ });
1755
+ }
1756
+ }
1757
+ body.interactive = buildInteractivePayload({
1758
+ interactiveType,
1759
+ bodyText,
1760
+ headerType,
1761
+ headerText,
1762
+ headerMediaUrl,
1763
+ footerText,
1764
+ buttons,
1765
+ listButtonText,
1766
+ sections,
1767
+ });
1768
+ }
1769
+ }
1770
+ else if (messageType === 'location') {
1771
+ body.location = {
1772
+ latitude: this.getNodeParameter('latitude', i),
1773
+ longitude: this.getNodeParameter('longitude', i),
1774
+ name: this.getNodeParameter('locationName', i, '') || undefined,
1775
+ };
1776
+ }
1777
+ else if (messageType === 'contacts') {
1778
+ const contactsData = this.getNodeParameter('contactsData', i);
1779
+ body.contacts = typeof contactsData === 'string' ? JSON.parse(contactsData) : contactsData;
1780
+ }
1781
+ else if (messageType === 'reaction') {
1782
+ body.reaction = {
1783
+ message_id: this.getNodeParameter('reactionMessageId', i),
1784
+ emoji: this.getNodeParameter('reactionEmoji', i),
1785
+ };
1786
+ }
1787
+ // Add additional fields
1788
+ const additionalFields = this.getNodeParameter('additionalFields', i, {});
1789
+ if (additionalFields.pauseAi) {
1790
+ body.ai = body.ai || {};
1791
+ body.ai.pause = additionalFields.pauseAi;
1792
+ }
1793
+ if (additionalFields.pauseDuration) {
1794
+ body.ai = body.ai || {};
1795
+ body.ai.pauseDuration = additionalFields.pauseDuration;
1796
+ }
1797
+ if (additionalFields.disableAi) {
1798
+ body.ai = body.ai || {};
1799
+ body.ai.disable = additionalFields.disableAi;
1800
+ }
1801
+ if (additionalFields.replyTo) {
1802
+ body.context = { message_id: additionalFields.replyTo };
1803
+ }
1804
+ if (additionalFields.createConversation !== undefined) {
1805
+ body.createConversation = additionalFields.createConversation;
1806
+ }
1807
+ if (additionalFields.metadata) {
1808
+ body.metadata = typeof additionalFields.metadata === 'string'
1809
+ ? JSON.parse(additionalFields.metadata)
1810
+ : additionalFields.metadata;
1811
+ }
1812
+ response = await this.helpers.request({
1813
+ method: 'POST',
1814
+ url: `${baseUrl}/messages/v1`,
1815
+ headers: {
1816
+ 'Authorization': `Bearer ${apiKey}`,
1817
+ 'Content-Type': 'application/json',
1818
+ },
1819
+ body,
1820
+ json: true,
1821
+ });
1822
+ }
1823
+ else if (operation === 'retry') {
1824
+ const messageId = this.getNodeParameter('messageId', i);
1825
+ response = await this.helpers.request({
1826
+ method: 'POST',
1827
+ url: `${baseUrl}/messages/v1/${messageId}/retry`,
1828
+ headers: {
1829
+ 'Authorization': `Bearer ${apiKey}`,
1830
+ 'Content-Type': 'application/json',
1831
+ },
1832
+ json: true,
1833
+ });
1834
+ }
1835
+ }
1836
+ // ===========================================
1837
+ // CONVERSATION RESOURCE
1838
+ // ===========================================
1839
+ else if (resource === 'conversation') {
1840
+ if (operation === 'list') {
1841
+ const filters = this.getNodeParameter('conversationFilters', i, {});
1842
+ const qs = {};
1843
+ if (filters.search)
1844
+ qs.search = filters.search;
1845
+ if (filters.status && filters.status !== 'all')
1846
+ qs.status = filters.status;
1847
+ if (filters.limit)
1848
+ qs.limit = filters.limit;
1849
+ if (filters.offset)
1850
+ qs.offset = filters.offset;
1851
+ response = await this.helpers.request({
1852
+ method: 'GET',
1853
+ url: `${baseUrl}/conversations/v1`,
1854
+ headers: { 'Authorization': `Bearer ${apiKey}` },
1855
+ qs,
1856
+ json: true,
1857
+ });
1858
+ }
1859
+ else if (operation === 'get') {
1860
+ const conversationId = this.getNodeParameter('conversationId', i);
1861
+ response = await this.helpers.request({
1862
+ method: 'GET',
1863
+ url: `${baseUrl}/conversations/v1/${conversationId}`,
1864
+ headers: { 'Authorization': `Bearer ${apiKey}` },
1865
+ json: true,
1866
+ });
1867
+ }
1868
+ else if (operation === 'getByPhone') {
1869
+ const phoneNumber = this.getNodeParameter('phoneNumber', i);
1870
+ response = await this.helpers.request({
1871
+ method: 'GET',
1872
+ url: `${baseUrl}/conversations/v1/by-phone/${encodeURIComponent(phoneNumber)}`,
1873
+ headers: { 'Authorization': `Bearer ${apiKey}` },
1874
+ json: true,
1875
+ });
1876
+ }
1877
+ else if (operation === 'getMessages') {
1878
+ const conversationId = this.getNodeParameter('conversationId', i);
1879
+ const options = this.getNodeParameter('messagesOptions', i, {});
1880
+ const qs = {};
1881
+ if (options.limit)
1882
+ qs.limit = options.limit;
1883
+ if (options.cursor)
1884
+ qs.cursor = options.cursor;
1885
+ response = await this.helpers.request({
1886
+ method: 'GET',
1887
+ url: `${baseUrl}/conversations/v1/${conversationId}/messages`,
1888
+ headers: { 'Authorization': `Bearer ${apiKey}` },
1889
+ qs,
1890
+ json: true,
1891
+ });
1892
+ }
1893
+ else if (operation === 'close') {
1894
+ const conversationId = this.getNodeParameter('conversationId', i);
1895
+ response = await this.helpers.request({
1896
+ method: 'POST',
1897
+ url: `${baseUrl}/conversations/v1/${conversationId}/close`,
1898
+ headers: { 'Authorization': `Bearer ${apiKey}` },
1899
+ json: true,
1900
+ });
1901
+ }
1902
+ else if (operation === 'archive') {
1903
+ const conversationId = this.getNodeParameter('conversationId', i);
1904
+ response = await this.helpers.request({
1905
+ method: 'POST',
1906
+ url: `${baseUrl}/conversations/v1/${conversationId}/archive`,
1907
+ headers: { 'Authorization': `Bearer ${apiKey}` },
1908
+ json: true,
1909
+ });
1910
+ }
1911
+ else if (operation === 'markRead') {
1912
+ const conversationId = this.getNodeParameter('conversationId', i);
1913
+ response = await this.helpers.request({
1914
+ method: 'PATCH',
1915
+ url: `${baseUrl}/conversations/v1/${conversationId}/mark-read`,
1916
+ headers: { 'Authorization': `Bearer ${apiKey}` },
1917
+ json: true,
1918
+ });
1919
+ }
1920
+ else if (operation === 'setAi') {
1921
+ const conversationId = this.getNodeParameter('conversationId', i);
1922
+ const aiEnabled = this.getNodeParameter('aiEnabled', i);
1923
+ response = await this.helpers.request({
1924
+ method: 'PATCH',
1925
+ url: `${baseUrl}/conversations/v1/${conversationId}/ai`,
1926
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
1927
+ body: { aiEnabled },
1928
+ json: true,
1929
+ });
1930
+ }
1931
+ else if (operation === 'pauseAi') {
1932
+ const conversationId = this.getNodeParameter('conversationId', i);
1933
+ const duration = this.getNodeParameter('pauseDurationConv', i);
1934
+ response = await this.helpers.request({
1935
+ method: 'POST',
1936
+ url: `${baseUrl}/conversations/v1/${conversationId}/ai/pause`,
1937
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
1938
+ body: { duration },
1939
+ json: true,
1940
+ });
1941
+ }
1942
+ else if (operation === 'aiSuggest') {
1943
+ const conversationId = this.getNodeParameter('conversationId', i);
1944
+ response = await this.helpers.request({
1945
+ method: 'POST',
1946
+ url: `${baseUrl}/conversations/v1/${conversationId}/ai-suggest`,
1947
+ headers: { 'Authorization': `Bearer ${apiKey}` },
1948
+ json: true,
1949
+ });
1950
+ }
1951
+ }
1952
+ // ===========================================
1953
+ // AGENT RESOURCE
1954
+ // ===========================================
1955
+ else if (resource === 'agent') {
1956
+ if (operation === 'toggle') {
1957
+ const enabled = this.getNodeParameter('agentEnabled', i);
1958
+ response = await this.helpers.request({
1959
+ method: 'POST',
1960
+ url: `${baseUrl}/agent/v1/toggle`,
1961
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
1962
+ body: { enabled },
1963
+ json: true,
1964
+ });
1965
+ }
1966
+ else if (operation === 'pause') {
1967
+ const duration = this.getNodeParameter('agentPauseDuration', i);
1968
+ response = await this.helpers.request({
1969
+ method: 'POST',
1970
+ url: `${baseUrl}/agent/v1/pause`,
1971
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
1972
+ body: { duration },
1973
+ json: true,
1974
+ });
1975
+ }
1976
+ }
1977
+ // ===========================================
1978
+ // TEMPLATE RESOURCE
1979
+ // ===========================================
1980
+ else if (resource === 'template') {
1981
+ if (operation === 'list') {
1982
+ const filters = this.getNodeParameter('templateFilters', i, {});
1983
+ const qs = {};
1984
+ if (filters.status)
1985
+ qs.status = filters.status;
1986
+ if (filters.limit)
1987
+ qs.limit = filters.limit;
1988
+ if (filters.offset)
1989
+ qs.offset = filters.offset;
1990
+ response = await this.helpers.request({
1991
+ method: 'GET',
1992
+ url: `${baseUrl}/templates/v1`,
1993
+ headers: { 'Authorization': `Bearer ${apiKey}` },
1994
+ qs,
1995
+ json: true,
1996
+ });
1997
+ }
1998
+ else if (operation === 'get') {
1999
+ const templateId = this.getNodeParameter('templateId', i);
2000
+ response = await this.helpers.request({
2001
+ method: 'GET',
2002
+ url: `${baseUrl}/templates/v1/${templateId}`,
2003
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2004
+ json: true,
2005
+ });
2006
+ }
2007
+ else if (operation === 'getVariables') {
2008
+ response = await this.helpers.request({
2009
+ method: 'GET',
2010
+ url: `${baseUrl}/templates/v1/variables`,
2011
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2012
+ json: true,
2013
+ });
2014
+ }
2015
+ else if (operation === 'sync') {
2016
+ response = await this.helpers.request({
2017
+ method: 'POST',
2018
+ url: `${baseUrl}/templates/v1/sync`,
2019
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2020
+ json: true,
2021
+ });
2022
+ }
2023
+ }
2024
+ // ===========================================
2025
+ // MEDIA RESOURCE
2026
+ // ===========================================
2027
+ else if (resource === 'media') {
2028
+ if (operation === 'upload') {
2029
+ // Media upload requires special handling with binary data
2030
+ const mediaType = this.getNodeParameter('mediaType', i);
2031
+ const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
2032
+ // For now, just return an error - binary upload needs special handling
2033
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Media upload from n8n requires binary data handling. Use the HTTP Request node with binary data or upload via URL.', { itemIndex: i });
2034
+ }
2035
+ }
2036
+ // ===========================================
2037
+ // CONTACT RESOURCE
2038
+ // ===========================================
2039
+ else if (resource === 'contact') {
2040
+ if (operation === 'list') {
2041
+ const filters = this.getNodeParameter('contactFilters', i, {});
2042
+ const qs = {};
2043
+ Object.entries(filters).forEach(([key, value]) => {
2044
+ if (value !== undefined && value !== '') {
2045
+ qs[key === 'sortBy' ? 'sort_by' : key === 'sortOrder' ? 'sort_order' : key === 'funnelStageId' ? 'funnel_stage_id' : key] = value;
2046
+ }
2047
+ });
2048
+ response = await this.helpers.request({
2049
+ method: 'GET',
2050
+ url: `${baseUrl}/contacts/v1`,
2051
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2052
+ qs,
2053
+ json: true,
2054
+ });
2055
+ }
2056
+ else if (operation === 'get') {
2057
+ const contactId = this.getNodeParameter('contactId', i);
2058
+ response = await this.helpers.request({
2059
+ method: 'GET',
2060
+ url: `${baseUrl}/contacts/v1/${contactId}`,
2061
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2062
+ json: true,
2063
+ });
2064
+ }
2065
+ else if (operation === 'create') {
2066
+ const name = this.getNodeParameter('contactName', i);
2067
+ const phoneNumber = this.getNodeParameter('contactPhone', i);
2068
+ const additional = this.getNodeParameter('contactAdditional', i, {});
2069
+ const body = { name, phoneNumber };
2070
+ if (additional.email)
2071
+ body.email = additional.email;
2072
+ if (additional.tags)
2073
+ body.tags = additional.tags;
2074
+ if (additional.customFields) {
2075
+ body.customFields = typeof additional.customFields === 'string'
2076
+ ? JSON.parse(additional.customFields)
2077
+ : additional.customFields;
2078
+ }
2079
+ if (additional.metadata) {
2080
+ body.metadata = typeof additional.metadata === 'string'
2081
+ ? JSON.parse(additional.metadata)
2082
+ : additional.metadata;
2083
+ }
2084
+ response = await this.helpers.request({
2085
+ method: 'POST',
2086
+ url: `${baseUrl}/contacts/v1`,
2087
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
2088
+ body,
2089
+ json: true,
2090
+ });
2091
+ }
2092
+ else if (operation === 'update') {
2093
+ const contactId = this.getNodeParameter('contactId', i);
2094
+ const updateFields = this.getNodeParameter('contactUpdateFields', i, {});
2095
+ const body = {};
2096
+ Object.entries(updateFields).forEach(([key, value]) => {
2097
+ if (value !== undefined && value !== '') {
2098
+ if (key === 'customFields') {
2099
+ body[key] = typeof value === 'string' ? JSON.parse(value) : value;
2100
+ }
2101
+ else {
2102
+ body[key] = value;
2103
+ }
2104
+ }
2105
+ });
2106
+ response = await this.helpers.request({
2107
+ method: 'PATCH',
2108
+ url: `${baseUrl}/contacts/v1/${contactId}`,
2109
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
2110
+ body,
2111
+ json: true,
2112
+ });
2113
+ }
2114
+ else if (operation === 'delete') {
2115
+ const contactId = this.getNodeParameter('contactId', i);
2116
+ response = await this.helpers.request({
2117
+ method: 'DELETE',
2118
+ url: `${baseUrl}/contacts/v1/${contactId}`,
2119
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2120
+ json: true,
2121
+ });
2122
+ }
2123
+ else if (operation === 'search') {
2124
+ const query = this.getNodeParameter('searchQuery', i);
2125
+ const options = this.getNodeParameter('searchOptions', i, {});
2126
+ const body = { query };
2127
+ if (options.filters) {
2128
+ body.filters = typeof options.filters === 'string' ? JSON.parse(options.filters) : options.filters;
2129
+ }
2130
+ if (options.limit)
2131
+ body.limit = options.limit;
2132
+ if (options.cursor)
2133
+ body.cursor = options.cursor;
2134
+ response = await this.helpers.request({
2135
+ method: 'POST',
2136
+ url: `${baseUrl}/contacts/v1/search`,
2137
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
2138
+ body,
2139
+ json: true,
2140
+ });
2141
+ }
2142
+ else if (operation === 'bulk') {
2143
+ const bulkOperation = this.getNodeParameter('bulkOperation', i);
2144
+ const contacts = this.getNodeParameter('bulkContacts', i);
2145
+ const data = this.getNodeParameter('bulkData', i, '{}');
2146
+ response = await this.helpers.request({
2147
+ method: 'POST',
2148
+ url: `${baseUrl}/contacts/v1/bulk`,
2149
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
2150
+ body: {
2151
+ operation: bulkOperation,
2152
+ contacts: typeof contacts === 'string' ? JSON.parse(contacts) : contacts,
2153
+ data: typeof data === 'string' ? JSON.parse(data) : data,
2154
+ },
2155
+ json: true,
2156
+ });
2157
+ }
2158
+ else if (operation === 'merge') {
2159
+ const contactId = this.getNodeParameter('contactId', i);
2160
+ const mergeWith = this.getNodeParameter('mergeWithId', i);
2161
+ response = await this.helpers.request({
2162
+ method: 'POST',
2163
+ url: `${baseUrl}/contacts/v1/${contactId}/merge`,
2164
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
2165
+ body: { mergeWith },
2166
+ json: true,
2167
+ });
2168
+ }
2169
+ else if (operation === 'getTags') {
2170
+ response = await this.helpers.request({
2171
+ method: 'GET',
2172
+ url: `${baseUrl}/contacts/v1/tags`,
2173
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2174
+ json: true,
2175
+ });
2176
+ }
2177
+ else if (operation === 'getFields') {
2178
+ response = await this.helpers.request({
2179
+ method: 'GET',
2180
+ url: `${baseUrl}/contacts/v1/fields`,
2181
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2182
+ json: true,
2183
+ });
2184
+ }
2185
+ }
2186
+ // ===========================================
2187
+ // FUNNEL RESOURCE
2188
+ // ===========================================
2189
+ else if (resource === 'funnel') {
2190
+ if (operation === 'listStages') {
2191
+ const options = this.getNodeParameter('stageListOptions', i, {});
2192
+ const qs = {};
2193
+ if (options.limit)
2194
+ qs.limit = options.limit;
2195
+ if (options.offset)
2196
+ qs.offset = options.offset;
2197
+ response = await this.helpers.request({
2198
+ method: 'GET',
2199
+ url: `${baseUrl}/funnel/v1/stages`,
2200
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2201
+ qs,
2202
+ json: true,
2203
+ });
2204
+ }
2205
+ else if (operation === 'getStage') {
2206
+ const stageId = this.getNodeParameter('stageId', i);
2207
+ response = await this.helpers.request({
2208
+ method: 'GET',
2209
+ url: `${baseUrl}/funnel/v1/stages/${stageId}`,
2210
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2211
+ json: true,
2212
+ });
2213
+ }
2214
+ else if (operation === 'createStage') {
2215
+ const name = this.getNodeParameter('stageName', i);
2216
+ const options = this.getNodeParameter('stageOptions', i, {});
2217
+ const body = { name };
2218
+ if (options.position !== undefined)
2219
+ body.position = options.position;
2220
+ if (options.color)
2221
+ body.color = options.color;
2222
+ if (options.description)
2223
+ body.description = options.description;
2224
+ response = await this.helpers.request({
2225
+ method: 'POST',
2226
+ url: `${baseUrl}/funnel/v1/stages`,
2227
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
2228
+ body,
2229
+ json: true,
2230
+ });
2231
+ }
2232
+ else if (operation === 'updateStage') {
2233
+ const stageId = this.getNodeParameter('stageId', i);
2234
+ const updateFields = this.getNodeParameter('stageUpdateFields', i, {});
2235
+ const body = {};
2236
+ Object.entries(updateFields).forEach(([key, value]) => {
2237
+ if (value !== undefined && value !== '') {
2238
+ body[key] = value;
2239
+ }
2240
+ });
2241
+ response = await this.helpers.request({
2242
+ method: 'PATCH',
2243
+ url: `${baseUrl}/funnel/v1/stages/${stageId}`,
2244
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
2245
+ body,
2246
+ json: true,
2247
+ });
2248
+ }
2249
+ else if (operation === 'deleteStage') {
2250
+ const stageId = this.getNodeParameter('stageId', i);
2251
+ response = await this.helpers.request({
2252
+ method: 'DELETE',
2253
+ url: `${baseUrl}/funnel/v1/stages/${stageId}`,
2254
+ headers: { 'Authorization': `Bearer ${apiKey}` },
2255
+ json: true,
2256
+ });
2257
+ }
2258
+ else if (operation === 'reorderStages') {
2259
+ const stages = this.getNodeParameter('stagesOrder', i);
2260
+ response = await this.helpers.request({
2261
+ method: 'PATCH',
2262
+ url: `${baseUrl}/funnel/v1/stages/reorder`,
2263
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
2264
+ body: { stages: typeof stages === 'string' ? JSON.parse(stages) : stages },
2265
+ json: true,
2266
+ });
2267
+ }
2268
+ else if (operation === 'moveContact') {
2269
+ const contactId = this.getNodeParameter('contactIdFunnel', i);
2270
+ const stageId = this.getNodeParameter('targetStageId', i);
2271
+ response = await this.helpers.request({
2272
+ method: 'POST',
2273
+ url: `${baseUrl}/funnel/v1/contacts/${contactId}/move`,
2274
+ headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
2275
+ body: { stageId },
2276
+ json: true,
2277
+ });
2278
+ }
2279
+ }
2280
+ if (response) {
2281
+ returnData.push({ json: response });
2282
+ }
2283
+ }
2284
+ catch (error) {
2285
+ if (this.continueOnFail()) {
2286
+ returnData.push({ json: { error: error.message } });
2287
+ continue;
2288
+ }
2289
+ throw error;
2290
+ }
2291
+ }
2292
+ return [returnData];
2293
+ }
1788
2294
  }
1789
2295
  exports.Whaapy = Whaapy;