n8n-nodes-wavy 1.0.2 → 1.0.4

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.
@@ -41,6 +41,18 @@ class Wavy {
41
41
  name: 'Tool',
42
42
  value: 'tool',
43
43
  },
44
+ {
45
+ name: 'Group',
46
+ value: 'group',
47
+ },
48
+ {
49
+ name: 'Chat',
50
+ value: 'chat',
51
+ },
52
+ {
53
+ name: 'Contact',
54
+ value: 'contact',
55
+ },
44
56
  ],
45
57
  default: 'message',
46
58
  },
@@ -60,6 +72,16 @@ class Wavy {
60
72
  value: 'delete',
61
73
  action: 'Delete a message',
62
74
  },
75
+ {
76
+ name: 'Edit Message',
77
+ value: 'editMessage',
78
+ action: 'Edit a sent message',
79
+ },
80
+ {
81
+ name: 'React',
82
+ value: 'reactMessage',
83
+ action: 'React to a message',
84
+ },
63
85
  {
64
86
  name: 'Send Carousel',
65
87
  value: 'sendCarousel',
@@ -105,6 +127,16 @@ class Wavy {
105
127
  value: 'sendSticker',
106
128
  action: 'Send a sticker message',
107
129
  },
130
+ {
131
+ name: 'Send View Once',
132
+ value: 'sendViewOnce',
133
+ action: 'Send a view once media message',
134
+ },
135
+ {
136
+ name: 'Send Product',
137
+ value: 'sendProduct',
138
+ action: 'Send a product message',
139
+ },
108
140
  {
109
141
  name: 'Send Text',
110
142
  value: 'sendText',
@@ -226,10 +258,36 @@ class Wavy {
226
258
  displayOptions: {
227
259
  show: {
228
260
  resource: ['message'],
229
- operation: ['sendMedia', 'sendSticker'],
261
+ operation: ['sendMedia', 'sendSticker', 'sendViewOnce'],
230
262
  },
231
263
  },
232
264
  },
265
+ {
266
+ displayName: 'View Once Type',
267
+ name: 'viewOnceType',
268
+ type: 'options',
269
+ displayOptions: {
270
+ show: {
271
+ resource: ['message'],
272
+ operation: ['sendViewOnce'],
273
+ },
274
+ },
275
+ options: [
276
+ {
277
+ name: 'Image',
278
+ value: 'viewOnceImage',
279
+ },
280
+ {
281
+ name: 'Video',
282
+ value: 'viewOnceVideo',
283
+ },
284
+ {
285
+ name: 'Audio',
286
+ value: 'viewOnceAudio',
287
+ },
288
+ ],
289
+ default: 'viewOnceImage',
290
+ },
233
291
  {
234
292
  displayName: 'Caption',
235
293
  name: 'caption',
@@ -238,7 +296,7 @@ class Wavy {
238
296
  displayOptions: {
239
297
  show: {
240
298
  resource: ['message'],
241
- operation: ['sendMedia'],
299
+ operation: ['sendMedia', 'sendViewOnce'],
242
300
  },
243
301
  },
244
302
  },
@@ -454,6 +512,57 @@ class Wavy {
454
512
  },
455
513
  ],
456
514
  },
515
+ {
516
+ displayName: 'Product ID',
517
+ name: 'productId',
518
+ type: 'string',
519
+ required: true,
520
+ default: '',
521
+ displayOptions: {
522
+ show: {
523
+ resource: ['message'],
524
+ operation: ['sendProduct'],
525
+ },
526
+ },
527
+ },
528
+ {
529
+ displayName: 'Business Owner JID',
530
+ name: 'businessOwnerJid',
531
+ type: 'string',
532
+ required: false,
533
+ default: '',
534
+ description: 'JID of the business owner (optional if same as sender)',
535
+ displayOptions: {
536
+ show: {
537
+ resource: ['message'],
538
+ operation: ['sendProduct'],
539
+ },
540
+ },
541
+ },
542
+ {
543
+ displayName: 'Body Message',
544
+ name: 'text',
545
+ type: 'string',
546
+ default: '',
547
+ displayOptions: {
548
+ show: {
549
+ resource: ['message'],
550
+ operation: ['sendProduct'],
551
+ },
552
+ },
553
+ },
554
+ {
555
+ displayName: 'Footer',
556
+ name: 'footer',
557
+ type: 'string',
558
+ default: '',
559
+ displayOptions: {
560
+ show: {
561
+ resource: ['message'],
562
+ operation: ['sendProduct'],
563
+ },
564
+ },
565
+ },
457
566
  {
458
567
  displayName: 'Question',
459
568
  name: 'question',
@@ -572,6 +681,218 @@ class Wavy {
572
681
  },
573
682
  },
574
683
  },
684
+ {
685
+ displayName: 'Participants',
686
+ name: 'participants',
687
+ type: 'string',
688
+ default: '',
689
+ placeholder: 'number1,number2',
690
+ description: 'Comma-separated list of phone numbers',
691
+ displayOptions: {
692
+ show: {
693
+ resource: ['group'],
694
+ operation: ['create', 'updateParticipants'],
695
+ },
696
+ },
697
+ },
698
+ {
699
+ displayName: 'Subject',
700
+ name: 'subject',
701
+ type: 'string',
702
+ default: '',
703
+ displayOptions: {
704
+ show: {
705
+ resource: ['group'],
706
+ operation: ['create', 'updateSubject'],
707
+ },
708
+ },
709
+ },
710
+ {
711
+ displayName: 'Description',
712
+ name: 'description',
713
+ type: 'string',
714
+ default: '',
715
+ displayOptions: {
716
+ show: {
717
+ resource: ['group'],
718
+ operation: ['updateDescription'],
719
+ },
720
+ },
721
+ },
722
+ {
723
+ displayName: 'Action',
724
+ name: 'participantAction',
725
+ type: 'options',
726
+ options: [
727
+ { name: 'Add', value: 'add' },
728
+ { name: 'Remove', value: 'remove' },
729
+ { name: 'Promote', value: 'promote' },
730
+ { name: 'Demote', value: 'demote' },
731
+ ],
732
+ default: 'add',
733
+ displayOptions: {
734
+ show: {
735
+ resource: ['group'],
736
+ operation: ['updateParticipants'],
737
+ },
738
+ },
739
+ },
740
+ {
741
+ displayName: 'Setting',
742
+ name: 'groupSetting',
743
+ type: 'options',
744
+ options: [
745
+ { name: 'Announcement (Admins only)', value: 'announcement' },
746
+ { name: 'Not Announcement (All)', value: 'not_announcement' },
747
+ { name: 'Locked (Admins only)', value: 'locked' },
748
+ { name: 'Unlocked (All)', value: 'unlocked' },
749
+ ],
750
+ default: 'announcement',
751
+ displayOptions: {
752
+ show: {
753
+ resource: ['group'],
754
+ operation: ['updateSetting'],
755
+ },
756
+ },
757
+ },
758
+ {
759
+ displayName: 'Operation',
760
+ name: 'operation',
761
+ type: 'options',
762
+ noDataExpression: true,
763
+ displayOptions: {
764
+ show: {
765
+ resource: ['chat'],
766
+ },
767
+ },
768
+ options: [
769
+ { name: 'Archive', value: 'archive', action: 'Archive a chat' },
770
+ { name: 'Unarchive', value: 'unarchive', action: 'Unarchive a chat' },
771
+ { name: 'Pin', value: 'pin', action: 'Pin a chat' },
772
+ { name: 'Unpin', value: 'unpin', action: 'Unpin a chat' },
773
+ { name: 'Mute', value: 'mute', action: 'Mute a chat' },
774
+ { name: 'Unmute', value: 'unmute', action: 'Unmute a chat' },
775
+ { name: 'Mark Read', value: 'markRead', action: 'Mark chat as read' },
776
+ { name: 'Mark Unread', value: 'markUnread', action: 'Mark chat as unread' },
777
+ { name: 'Clear Chat', value: 'clear', action: 'Clear all messages in chat' },
778
+ { name: 'Delete Chat', value: 'delete', action: 'Delete entire chat' },
779
+ ],
780
+ default: 'archive',
781
+ },
782
+ {
783
+ displayName: 'JID',
784
+ name: 'jid',
785
+ type: 'string',
786
+ default: '',
787
+ required: true,
788
+ displayOptions: {
789
+ show: {
790
+ resource: ['chat', 'group', 'contact'],
791
+ },
792
+ },
793
+ description: 'The JID of the chat, group, or contact',
794
+ },
795
+ {
796
+ displayName: 'Mute Duration (ms)',
797
+ name: 'muteDuration',
798
+ type: 'number',
799
+ default: 28800000,
800
+ displayOptions: {
801
+ show: {
802
+ resource: ['chat'],
803
+ operation: ['mute'],
804
+ },
805
+ },
806
+ },
807
+ {
808
+ displayName: 'Operation',
809
+ name: 'operation',
810
+ type: 'options',
811
+ noDataExpression: true,
812
+ displayOptions: {
813
+ show: {
814
+ resource: ['contact'],
815
+ },
816
+ },
817
+ options: [
818
+ { name: 'Block', value: 'block', action: 'Block a contact' },
819
+ { name: 'Unblock', value: 'unblock', action: 'Unblock a contact' },
820
+ { name: 'Get Profile Picture', value: 'getProfilePicture', action: 'Get profile picture URL' },
821
+ { name: 'Update Status', value: 'updateStatus', action: 'Update your profile status (About)' },
822
+ ],
823
+ default: 'getProfilePicture',
824
+ },
825
+ {
826
+ displayName: 'Status',
827
+ name: 'status',
828
+ type: 'string',
829
+ default: '',
830
+ displayOptions: {
831
+ show: {
832
+ resource: ['contact'],
833
+ operation: ['updateStatus'],
834
+ },
835
+ },
836
+ },
837
+ {
838
+ displayName: 'Message Key (JSON)',
839
+ name: 'messageKey',
840
+ type: 'json',
841
+ default: '{}',
842
+ displayOptions: {
843
+ show: {
844
+ resource: ['message'],
845
+ operation: ['editMessage', 'reactMessage'],
846
+ },
847
+ },
848
+ description: 'The key object of the message to edit or react to',
849
+ },
850
+ {
851
+ displayName: 'New Text',
852
+ name: 'newText',
853
+ type: 'string',
854
+ default: '',
855
+ displayOptions: {
856
+ show: {
857
+ resource: ['message'],
858
+ operation: ['editMessage'],
859
+ },
860
+ },
861
+ },
862
+ {
863
+ displayName: 'Emoji',
864
+ name: 'emoji',
865
+ type: 'string',
866
+ default: '👍',
867
+ displayOptions: {
868
+ show: {
869
+ resource: ['message'],
870
+ operation: ['reactMessage'],
871
+ },
872
+ },
873
+ },
874
+ {
875
+ displayName: 'Operation',
876
+ name: 'operation',
877
+ type: 'options',
878
+ noDataExpression: true,
879
+ displayOptions: {
880
+ show: {
881
+ resource: ['group'],
882
+ },
883
+ },
884
+ options: [
885
+ { name: 'Create', value: 'create', action: 'Create a new group' },
886
+ { name: 'Leave', value: 'leave', action: 'Leave a group' },
887
+ { name: 'Get Invite Code', value: 'getInviteCode', action: 'Get group invite code' },
888
+ { name: 'Get Metadata', value: 'getMetadata', action: 'Get group metadata' },
889
+ { name: 'Update Description', value: 'updateDescription', action: 'Update group description' },
890
+ { name: 'Update Participants', value: 'updateParticipants', action: 'Add, remove, promote, or demote participants' },
891
+ { name: 'Update Settings', value: 'updateSetting', action: 'Update group settings' },
892
+ { name: 'Update Subject', value: 'updateSubject', action: 'Update group subject' },
893
+ ],
894
+ default: 'getMetadata',
895
+ },
575
896
  {
576
897
  displayName: 'Phone Numbers',
577
898
  name: 'phoneNumbers',
@@ -589,10 +910,43 @@ class Wavy {
589
910
  ],
590
911
  };
591
912
  }
913
+ sanitizeBaseUrl(url) {
914
+ let sanitized = url.trim();
915
+ if (sanitized.endsWith('/')) {
916
+ sanitized = sanitized.slice(0, -1);
917
+ }
918
+ if (sanitized.endsWith('/api')) {
919
+ sanitized = sanitized.slice(0, -4);
920
+ }
921
+ return sanitized;
922
+ }
923
+ async checkSessionConnected(credentials, sessionId) {
924
+ const options = {
925
+ method: 'GET',
926
+ uri: `${credentials.baseUrl}/api/sessions/${sessionId}`,
927
+ headers: {
928
+ 'X-API-Key': credentials.apiKey,
929
+ },
930
+ json: true,
931
+ };
932
+ try {
933
+ const response = await this.helpers.request(options);
934
+ if (!response.isConnected) {
935
+ throw new Error(`Session "${sessionId}" is not connected. Please connect it in the Wavy dashboard first.`);
936
+ }
937
+ }
938
+ catch (error) {
939
+ if (error.message.includes('not connected'))
940
+ throw error;
941
+ throw new Error(`Failed to verify session connectivity: ${error.message}`);
942
+ }
943
+ }
592
944
  async execute() {
593
945
  const items = this.getInputData();
594
946
  const returnData = [];
595
- const credentials = await this.getCredentials('wavyApi');
947
+ const rawCredentials = await this.getCredentials('wavyApi');
948
+ const baseUrl = this.sanitizeBaseUrl(rawCredentials.baseUrl);
949
+ const credentials = Object.assign(Object.assign({}, rawCredentials), { baseUrl });
596
950
  for (let i = 0; i < items.length; i++) {
597
951
  try {
598
952
  const resource = this.getNodeParameter('resource', i);
@@ -606,15 +960,28 @@ class Wavy {
606
960
  const sessionId = this.getNodeParameter('sessionId', i);
607
961
  body.to = to;
608
962
  if (operation === 'sendText') {
963
+ await this.checkSessionConnected(credentials, sessionId);
609
964
  endpoint = `/sessions/${sessionId}/send`;
610
965
  body.message = this.getNodeParameter('message', i);
611
966
  }
612
967
  else if (operation === 'sendMedia') {
968
+ await this.checkSessionConnected(credentials, sessionId);
613
969
  endpoint = `/send-media`;
614
970
  body.sender = sessionId;
615
971
  body.mediaUrl = this.getNodeParameter('mediaUrl', i);
616
972
  body.message = this.getNodeParameter('caption', i);
617
973
  }
974
+ else if (operation === 'sendViewOnce') {
975
+ await this.checkSessionConnected(credentials, sessionId);
976
+ endpoint = `/send-interactive`;
977
+ body.sender = sessionId;
978
+ const type = this.getNodeParameter('viewOnceType', i);
979
+ body.messageType = type;
980
+ body.data = {
981
+ mediaUrl: this.getNodeParameter('mediaUrl', i),
982
+ caption: this.getNodeParameter('caption', i),
983
+ };
984
+ }
618
985
  else if (operation === 'sendSticker') {
619
986
  endpoint = `/send-sticker`;
620
987
  body.sender = sessionId;
@@ -682,19 +1049,128 @@ class Wavy {
682
1049
  }
683
1050
  }
684
1051
  else if (operation === 'sendCarousel') {
685
- endpoint = `/sessions/${sessionId}/send-interactive`;
1052
+ endpoint = `/send-interactive`;
686
1053
  const payload = this.getNodeParameter('payload', i);
687
1054
  body = Object.assign(Object.assign({}, body), payload);
688
1055
  }
1056
+ else if (operation === 'sendProduct') {
1057
+ endpoint = `/send-product`;
1058
+ body.sender = sessionId;
1059
+ body.data = {
1060
+ productId: this.getNodeParameter('productId', i),
1061
+ businessOwnerJid: this.getNodeParameter('businessOwnerJid', i),
1062
+ text: this.getNodeParameter('text', i),
1063
+ footer: this.getNodeParameter('footer', i),
1064
+ };
1065
+ }
689
1066
  else if (operation === 'delete') {
690
1067
  endpoint = `/delete-message`;
691
1068
  body.sender = sessionId;
692
1069
  body.key = this.getNodeParameter('messageKey', i);
693
1070
  }
1071
+ else if (operation === 'reactMessage') {
1072
+ await this.checkSessionConnected(credentials, sessionId);
1073
+ endpoint = `/messages/react`;
1074
+ body.jid = to;
1075
+ body.key = this.getNodeParameter('messageKey', i);
1076
+ body.emoji = this.getNodeParameter('emoji', i);
1077
+ }
1078
+ else if (operation === 'editMessage') {
1079
+ await this.checkSessionConnected(credentials, sessionId);
1080
+ endpoint = `/messages/edit`;
1081
+ body.jid = to;
1082
+ body.key = this.getNodeParameter('messageKey', i);
1083
+ body.newText = this.getNodeParameter('newText', i);
1084
+ }
1085
+ }
1086
+ else if (resource === 'group') {
1087
+ const sessionId = this.getNodeParameter('sessionId', i);
1088
+ await this.checkSessionConnected(credentials, sessionId);
1089
+ if (operation === 'create') {
1090
+ endpoint = `/groups/create`;
1091
+ body.subject = this.getNodeParameter('subject', i);
1092
+ body.participants = this.getNodeParameter('participants', i).split(',').map(p => p.trim());
1093
+ }
1094
+ else {
1095
+ const jid = this.getNodeParameter('jid', i);
1096
+ if (operation === 'leave') {
1097
+ endpoint = `/groups/${jid}/leave`;
1098
+ }
1099
+ else if (operation === 'getInviteCode') {
1100
+ endpoint = `/groups/${jid}/invite-code`;
1101
+ method = 'GET';
1102
+ }
1103
+ else if (operation === 'getMetadata') {
1104
+ endpoint = `/groups/${jid}`;
1105
+ method = 'GET';
1106
+ }
1107
+ else if (operation === 'updateDescription') {
1108
+ endpoint = `/groups/${jid}/description`;
1109
+ body.description = this.getNodeParameter('description', i);
1110
+ }
1111
+ else if (operation === 'updateParticipants') {
1112
+ endpoint = `/groups/${jid}/participants`;
1113
+ body.participants = this.getNodeParameter('participants', i).split(',').map(p => p.trim());
1114
+ body.action = this.getNodeParameter('participantAction', i);
1115
+ }
1116
+ else if (operation === 'updateSetting') {
1117
+ endpoint = `/groups/${jid}/settings`;
1118
+ body.setting = this.getNodeParameter('groupSetting', i);
1119
+ }
1120
+ else if (operation === 'updateSubject') {
1121
+ endpoint = `/groups/${jid}/update`;
1122
+ body.subject = this.getNodeParameter('subject', i);
1123
+ }
1124
+ }
1125
+ }
1126
+ else if (resource === 'chat') {
1127
+ const sessionId = this.getNodeParameter('sessionId', i);
1128
+ const jid = this.getNodeParameter('jid', i);
1129
+ await this.checkSessionConnected(credentials, sessionId);
1130
+ endpoint = `/chats/${jid}/modify`;
1131
+ const op = operation;
1132
+ if (op === 'archive') body.action = 'archive';
1133
+ else if (op === 'unarchive') body.action = 'unarchive';
1134
+ else if (op === 'pin') body.action = 'pin';
1135
+ else if (op === 'unpin') body.action = 'unpin';
1136
+ else if (op === 'mute') {
1137
+ body.action = 'mute';
1138
+ body.muteDuration = this.getNodeParameter('muteDuration', i);
1139
+ }
1140
+ else if (op === 'unmute') body.action = 'unmute';
1141
+ else if (op === 'markRead') body.action = 'markRead';
1142
+ else if (op === 'markUnread') body.action = 'markUnread';
1143
+ else if (op === 'clear') body.action = 'clear';
1144
+ else if (op === 'delete') body.action = 'delete';
1145
+ }
1146
+ else if (resource === 'contact') {
1147
+ const sessionId = this.getNodeParameter('sessionId', i);
1148
+ await this.checkSessionConnected(credentials, sessionId);
1149
+ if (operation === 'updateStatus') {
1150
+ endpoint = `/profile/status`;
1151
+ body.status = this.getNodeParameter('status', i);
1152
+ }
1153
+ else {
1154
+ const jid = this.getNodeParameter('jid', i);
1155
+ if (operation === 'block') {
1156
+ endpoint = `/contacts/block`;
1157
+ body.jid = jid;
1158
+ body.action = 'block';
1159
+ }
1160
+ else if (operation === 'unblock') {
1161
+ endpoint = `/contacts/block`;
1162
+ body.jid = jid;
1163
+ body.action = 'unblock';
1164
+ }
1165
+ else if (operation === 'getProfilePicture') {
1166
+ endpoint = `/contacts/${jid}/picture`;
1167
+ method = 'GET';
1168
+ }
1169
+ }
694
1170
  }
695
1171
  else if (resource === 'session') {
696
1172
  if (operation === 'getAll') {
697
- endpoint = '/sessions';
1173
+ endpoint = '/sessions/list';
698
1174
  method = 'GET';
699
1175
  }
700
1176
  else if (operation === 'create') {
@@ -66,12 +66,24 @@ class WavyTrigger {
66
66
  },
67
67
  ],
68
68
  };
69
+ this.sanitizeBaseUrl = (url) => {
70
+ let sanitized = url.trim();
71
+ if (sanitized.endsWith('/')) {
72
+ sanitized = sanitized.slice(0, -1);
73
+ }
74
+ if (sanitized.endsWith('/api')) {
75
+ sanitized = sanitized.slice(0, -4);
76
+ }
77
+ return sanitized;
78
+ };
69
79
  this.webhookMethods = {
70
80
  default: {
71
81
  async checkExists() {
72
82
  const webhookUrl = this.getNodeWebhookUrl('default');
73
83
  const sessionId = this.getNodeParameter('sessionId');
74
- const credentials = await this.getCredentials('wavyApi');
84
+ const rawCredentials = await this.getCredentials('wavyApi');
85
+ const baseUrl = this.nodeType.sanitizeBaseUrl(rawCredentials.baseUrl);
86
+ const credentials = Object.assign(Object.assign({}, rawCredentials), { baseUrl });
75
87
  const options = {
76
88
  method: 'GET',
77
89
  uri: `${credentials.baseUrl}/api/sessions/${sessionId}`,
@@ -91,7 +103,9 @@ class WavyTrigger {
91
103
  async create() {
92
104
  const webhookUrl = this.getNodeWebhookUrl('default');
93
105
  const sessionId = this.getNodeParameter('sessionId');
94
- const credentials = await this.getCredentials('wavyApi');
106
+ const rawCredentials = await this.getCredentials('wavyApi');
107
+ const baseUrl = this.nodeType.sanitizeBaseUrl(rawCredentials.baseUrl);
108
+ const credentials = Object.assign(Object.assign({}, rawCredentials), { baseUrl });
95
109
  const options = {
96
110
  method: 'POST',
97
111
  uri: `${credentials.baseUrl}/api/sessions/${sessionId}/webhook`,
@@ -110,7 +124,9 @@ class WavyTrigger {
110
124
  },
111
125
  async delete() {
112
126
  const sessionId = this.getNodeParameter('sessionId');
113
- const credentials = await this.getCredentials('wavyApi');
127
+ const rawCredentials = await this.getCredentials('wavyApi');
128
+ const baseUrl = this.nodeType.sanitizeBaseUrl(rawCredentials.baseUrl);
129
+ const credentials = Object.assign(Object.assign({}, rawCredentials), { baseUrl });
114
130
  const options = {
115
131
  method: 'POST',
116
132
  uri: `${credentials.baseUrl}/api/sessions/${sessionId}/webhook`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-wavy",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Node untuk Wavy gess",
5
5
  "files": [
6
6
  "dist",