@theotherwillembotha/node-red-whatsapp 0.0.55
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/LICENSE +15 -0
- package/README.md +147 -0
- package/build/GenerateNodes.js +21 -0
- package/build/Nodes.html +1814 -0
- package/build/Nodes.js +24 -0
- package/build/Plugins.js +70 -0
- package/build/icons/whatsapp.png +0 -0
- package/build/index.js +21 -0
- package/build/whatsapp/node/WhatsappAccountConfigNode.js +36 -0
- package/build/whatsapp/node/WhatsappGroupConfigNode.js +45 -0
- package/build/whatsapp/node/WhatsappReceiveMessageNode.js +82 -0
- package/build/whatsapp/node/WhatsappSendMessageNode.js +95 -0
- package/build/whatsapp/service/WhatsappClient.js +869 -0
- package/build/whatsapp/service/WhatsappService.js +212 -0
- package/build/whatsapp/service/WhatsappStore.js +408 -0
- package/icons/whatsapp.png +0 -0
- package/package.json +71 -0
package/build/Nodes.html
ADDED
|
@@ -0,0 +1,1814 @@
|
|
|
1
|
+
|
|
2
|
+
<!--
|
|
3
|
+
|
|
4
|
+
This file was automatically generated using the NODERED Core utility.
|
|
5
|
+
Any modifications to his file will be overwritten the next time the code is regenerated.
|
|
6
|
+
|
|
7
|
+
You have been warned.
|
|
8
|
+
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
<!-- WhatsappAccountConfigNode -->
|
|
12
|
+
<style>
|
|
13
|
+
.theotherwillembotha-compact-table li {
|
|
14
|
+
padding-top: 4px;
|
|
15
|
+
padding-bottom: 4px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
</style>
|
|
19
|
+
<script type="text/javascript">
|
|
20
|
+
// a simple client for interfaceing with the WhatsappService on the backend. Note: this needs to be moved to its own file.
|
|
21
|
+
let waService = {
|
|
22
|
+
unlinkAccount: function(nodeID, localConnectionId) {
|
|
23
|
+
return new Promise((resolve, fail) => {
|
|
24
|
+
let request = JSON.stringify({
|
|
25
|
+
nodeID: nodeID,
|
|
26
|
+
localConnectionId: localConnectionId
|
|
27
|
+
});
|
|
28
|
+
$.ajax({
|
|
29
|
+
url: "whatsapp/unlinkaccount",
|
|
30
|
+
type: "POST",
|
|
31
|
+
data: request,
|
|
32
|
+
contentType: "application/json; charset=utf-8",
|
|
33
|
+
success: (response) => {
|
|
34
|
+
resolve(response);
|
|
35
|
+
},
|
|
36
|
+
error: (jqXHR, textStatus, errorThrown) => {
|
|
37
|
+
fail(errorThrown);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
getConnectionDetails: function(nodeID, localConnectionId) {
|
|
43
|
+
return new Promise((resolve, fail) => {
|
|
44
|
+
let request = JSON.stringify({
|
|
45
|
+
nodeID: nodeID,
|
|
46
|
+
localConnectionId: localConnectionId
|
|
47
|
+
});
|
|
48
|
+
$.ajax({
|
|
49
|
+
url: "whatsapp/connectiondetails",
|
|
50
|
+
type: "POST",
|
|
51
|
+
data: request,
|
|
52
|
+
contentType: "application/json; charset=utf-8",
|
|
53
|
+
success: (response) => {
|
|
54
|
+
resolve(response);
|
|
55
|
+
},
|
|
56
|
+
error: (jqXHR, textStatus, errorThrown) => {
|
|
57
|
+
fail(errorThrown);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
createGroup: function(nodeID, localConnectionId, name) {
|
|
63
|
+
console.log("create group! " + name);
|
|
64
|
+
return new Promise((resolve, fail) => {
|
|
65
|
+
let request = JSON.stringify({
|
|
66
|
+
nodeID: nodeID,
|
|
67
|
+
localConnectionId: localConnectionId,
|
|
68
|
+
groupName: name
|
|
69
|
+
});
|
|
70
|
+
$.ajax({
|
|
71
|
+
url: "whatsapp/creategroup",
|
|
72
|
+
type: "POST",
|
|
73
|
+
data: request,
|
|
74
|
+
contentType: "application/json; charset=utf-8",
|
|
75
|
+
success: (response) => {
|
|
76
|
+
resolve(response);
|
|
77
|
+
},
|
|
78
|
+
error: (jqXHR, textStatus, errorThrown) => {
|
|
79
|
+
fail(errorThrown);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
getGroups: function(nodeID, localConnectionId) {
|
|
85
|
+
return new Promise((resolve, fail) => {
|
|
86
|
+
let request = JSON.stringify({
|
|
87
|
+
nodeID: nodeID,
|
|
88
|
+
localConnectionId: localConnectionId
|
|
89
|
+
});
|
|
90
|
+
$.ajax({
|
|
91
|
+
url: "whatsapp/getgroups",
|
|
92
|
+
type: "POST",
|
|
93
|
+
data: request,
|
|
94
|
+
contentType: "application/json; charset=utf-8",
|
|
95
|
+
success: (response) => {
|
|
96
|
+
resolve(response);
|
|
97
|
+
},
|
|
98
|
+
error: (jqXHR, textStatus, errorThrown) => {
|
|
99
|
+
fail(errorThrown);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
addUserToGroup: function(nodeID, localConnectionId, userId, groupId, role) {
|
|
105
|
+
return new Promise((resolve, fail) => {
|
|
106
|
+
let request = JSON.stringify({
|
|
107
|
+
nodeID: nodeID,
|
|
108
|
+
localConnectionId: localConnectionId,
|
|
109
|
+
userId: userId,
|
|
110
|
+
groupId: groupId,
|
|
111
|
+
role: role
|
|
112
|
+
});
|
|
113
|
+
$.ajax({
|
|
114
|
+
url: "whatsapp/groupadduser",
|
|
115
|
+
type: "POST",
|
|
116
|
+
data: request,
|
|
117
|
+
contentType: "application/json; charset=utf-8",
|
|
118
|
+
success: (response) => {
|
|
119
|
+
resolve(response);
|
|
120
|
+
},
|
|
121
|
+
error: (jqXHR, textStatus, errorThrown) => {
|
|
122
|
+
fail(errorThrown);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
updateUserInGroup: function(nodeID, localConnectionId, userId, groupId, role) {
|
|
128
|
+
console.log("update user in group.");
|
|
129
|
+
return new Promise((resolve, fail) => {
|
|
130
|
+
let request = JSON.stringify({
|
|
131
|
+
nodeID: nodeID,
|
|
132
|
+
localConnectionId: localConnectionId,
|
|
133
|
+
userId: userId,
|
|
134
|
+
groupId: groupId,
|
|
135
|
+
role: role
|
|
136
|
+
});
|
|
137
|
+
$.ajax({
|
|
138
|
+
url: "whatsapp/groupupdateuser",
|
|
139
|
+
type: "POST",
|
|
140
|
+
data: request,
|
|
141
|
+
contentType: "application/json; charset=utf-8",
|
|
142
|
+
success: (response) => {
|
|
143
|
+
resolve(response);
|
|
144
|
+
},
|
|
145
|
+
error: (jqXHR, textStatus, errorThrown) => {
|
|
146
|
+
fail(errorThrown);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
removeUserFromGroup: function(nodeID, localConnectionId, userId, groupId) {
|
|
152
|
+
console.log("removeUserFromGroup", nodeID, localConnectionId, userId, groupId);
|
|
153
|
+
return new Promise((resolve, fail) => {
|
|
154
|
+
let request = JSON.stringify({
|
|
155
|
+
nodeID: nodeID,
|
|
156
|
+
localConnectionId: localConnectionId,
|
|
157
|
+
userId: userId,
|
|
158
|
+
groupId: groupId
|
|
159
|
+
});
|
|
160
|
+
$.ajax({
|
|
161
|
+
url: "whatsapp/groupremoveuser",
|
|
162
|
+
type: "POST",
|
|
163
|
+
data: request,
|
|
164
|
+
contentType: "application/json; charset=utf-8",
|
|
165
|
+
success: (response) => {
|
|
166
|
+
resolve(response);
|
|
167
|
+
},
|
|
168
|
+
error: (jqXHR, textStatus, errorThrown) => {
|
|
169
|
+
fail(errorThrown);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
</script>
|
|
177
|
+
<script type="text/javascript">
|
|
178
|
+
RED.nodes.registerType('WhatsappAccountConfigNode', {
|
|
179
|
+
category: 'config',
|
|
180
|
+
label: function() {
|
|
181
|
+
return this.name
|
|
182
|
+
},
|
|
183
|
+
paletteLabel: 'WhatsApp Account',
|
|
184
|
+
defaults: {
|
|
185
|
+
name: {
|
|
186
|
+
value: 'WhatsApp Account',
|
|
187
|
+
required: true
|
|
188
|
+
},
|
|
189
|
+
accountLinked: {
|
|
190
|
+
value: false,
|
|
191
|
+
required: true
|
|
192
|
+
},
|
|
193
|
+
localConnectionId: {
|
|
194
|
+
required: true
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
oneditprepare: function() {
|
|
198
|
+
// **** WhatsappAccountConfigNode **** //
|
|
199
|
+
{
|
|
200
|
+
let node = this;
|
|
201
|
+
let $btn = $("#wa-account-link-btn");
|
|
202
|
+
// if the account is already linked, show the Unlink state
|
|
203
|
+
if (document.getElementById("node-config-input-accountLinked").checked) {
|
|
204
|
+
$btn.html('<i class="fa fa-unlink"></i> Unlink Account');
|
|
205
|
+
}
|
|
206
|
+
// if we don't have a local connectionId for this node, generate one.
|
|
207
|
+
if ($("#node-config-input-localConnectionId").val() === "") {
|
|
208
|
+
$("#node-config-input-localConnectionId").val(crypto.randomUUID());
|
|
209
|
+
}
|
|
210
|
+
let localConnectionId = $("#node-config-input-localConnectionId").val();
|
|
211
|
+
$btn.on("click", () => {
|
|
212
|
+
$btn.prop("disabled", true);
|
|
213
|
+
if (document.getElementById("node-config-input-accountLinked").checked) {
|
|
214
|
+
waService.unlinkAccount(node.id, localConnectionId).then(response => {
|
|
215
|
+
$btn.prop("disabled", false);
|
|
216
|
+
$btn.html('<i class="fa fa-link"></i> Link Account');
|
|
217
|
+
document.getElementById("node-config-input-accountLinked").checked = false;
|
|
218
|
+
$("#wa-account-qrcode").html("<p>Account unlinked. Remember to remove this device from WhatsApp on your phone.</p>");
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// register an event handler for QR codes and connection state updates.
|
|
223
|
+
let eventHandler = function(topic, payload) {
|
|
224
|
+
if (payload.type === "qrcode") {
|
|
225
|
+
let qrcode = "<p>Open WhatsApp on your phone, select <strong>Linked Devices</strong> and scan the QR code below.</p><br>" + payload.qrcode;
|
|
226
|
+
$("#wa-account-qrcode").html(qrcode);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (payload.type === "account_linked") {
|
|
230
|
+
$("#wa-account-qrcode").html("<p>\u2714 Account linked successfully.</p>");
|
|
231
|
+
RED.comms.unsubscribe("notification/whatsapp/" + localConnectionId, eventHandler);
|
|
232
|
+
$btn.prop("disabled", false);
|
|
233
|
+
$btn.html('<i class="fa fa-unlink"></i> Unlink Account');
|
|
234
|
+
document.getElementById("node-config-input-accountLinked").checked = true;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (payload.type === "account_link_timeout") {
|
|
238
|
+
$("#wa-account-qrcode").html("<p>\u2718 Link timed out. Click <em>Link Account</em> to try again.</p>");
|
|
239
|
+
$btn.prop("disabled", false);
|
|
240
|
+
RED.comms.unsubscribe("notification/whatsapp/" + localConnectionId, eventHandler);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
console.log("unhandled payload type in WhatsappConnectionConfig " + payload.type);
|
|
244
|
+
};
|
|
245
|
+
RED.comms.subscribe("notification/whatsapp/" + localConnectionId, eventHandler);
|
|
246
|
+
$.ajax({
|
|
247
|
+
url: "whatsapp/linkaccount",
|
|
248
|
+
type: "POST",
|
|
249
|
+
data: JSON.stringify({
|
|
250
|
+
nodeID: node.id,
|
|
251
|
+
localConnectionId: localConnectionId
|
|
252
|
+
}),
|
|
253
|
+
contentType: "application/json; charset=utf-8",
|
|
254
|
+
error: (jqXHR) => {
|
|
255
|
+
console.log("linkaccount error", jqXHR);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
oneditsave: function() {
|
|
262
|
+
// **** WhatsappAccountConfigNode **** //
|
|
263
|
+
{
|
|
264
|
+
let node = this;
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
oneditcancel: function() {},
|
|
268
|
+
oneditdelete: function() {},
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
</script>
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
<script type="text/html" data-template-name='WhatsappAccountConfigNode'>
|
|
275
|
+
<div id='section_WhatsappAccountConfigNode'>
|
|
276
|
+
<div class="form-row">
|
|
277
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
278
|
+
<input type="text" id="node-config-input-name" />
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<div style="display:none">
|
|
282
|
+
<input type="text" id="node-config-input-localConnectionId">
|
|
283
|
+
<input type="checkbox" id="node-config-input-accountLinked">
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<div class="form-row">
|
|
287
|
+
<label></label>
|
|
288
|
+
<button type="button" id="wa-account-link-btn" class="red-ui-button">
|
|
289
|
+
<i class="fa fa-link"></i> Link Account
|
|
290
|
+
</button>
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
<div class="form-row">
|
|
294
|
+
<label></label>
|
|
295
|
+
<div id="wa-account-qrcode" style="max-width: 450px;"></div>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
</div>
|
|
300
|
+
</script>
|
|
301
|
+
|
|
302
|
+
<script type="text/markdown" data-help-name='WhatsappAccountConfigNode'>
|
|
303
|
+
> **Early development** — this package is still maturing. Some features may be incomplete and diagnostic log output is intentionally verbose for now.
|
|
304
|
+
|
|
305
|
+
Manages a WhatsApp Web session. Handles QR-code pairing and persists session credentials so the connection survives Node-RED restarts.
|
|
306
|
+
|
|
307
|
+
### Properties
|
|
308
|
+
|
|
309
|
+
: *name* (string) : Display label for this config node.
|
|
310
|
+
|
|
311
|
+
### Linking an account
|
|
312
|
+
|
|
313
|
+
Open the node editor and click **Link Account**. A QR code is displayed — scan it using WhatsApp on your phone (Linked Devices). Once scanned, the session is saved and reconnected automatically on each restart.
|
|
314
|
+
|
|
315
|
+
To unlink the device, open the editor and click **Unlink Account**, then remove the linked device from WhatsApp on your phone.
|
|
316
|
+
|
|
317
|
+
### Session storage
|
|
318
|
+
|
|
319
|
+
Credentials are persisted to `/data/whatsapp/<localConnectionId>/` if the `/data` directory exists (standard Node-RED Docker image), otherwise to `./whatsapp/<localConnectionId>/` relative to the Node-RED working directory.
|
|
320
|
+
</script>
|
|
321
|
+
<!-- WhatsappGroupConfigNode -->
|
|
322
|
+
<style>
|
|
323
|
+
</style>
|
|
324
|
+
<script type="text/javascript">
|
|
325
|
+
RED.nodes.registerType('WhatsappGroupConfigNode', {
|
|
326
|
+
category: 'config',
|
|
327
|
+
label: function() {
|
|
328
|
+
return this.name
|
|
329
|
+
},
|
|
330
|
+
paletteLabel: 'WhatsApp Group',
|
|
331
|
+
defaults: {
|
|
332
|
+
name: {
|
|
333
|
+
value: 'WhatsApp Group',
|
|
334
|
+
required: true
|
|
335
|
+
},
|
|
336
|
+
accountConfig: {
|
|
337
|
+
type: 'WhatsappAccountConfigNode',
|
|
338
|
+
required: true
|
|
339
|
+
},
|
|
340
|
+
groupId: {
|
|
341
|
+
value: '',
|
|
342
|
+
required: true
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
oneditprepare: function() {
|
|
346
|
+
// **** WhatsappGroupConfigNode **** //
|
|
347
|
+
{
|
|
348
|
+
let node = this;
|
|
349
|
+
let groupList = [];
|
|
350
|
+
let onGroupChanged = () => {
|
|
351
|
+
let selectedId = groupSelectionControl.typedInput("value");
|
|
352
|
+
let selectedGroup = groupList.find(g => g.id === selectedId);
|
|
353
|
+
if (selectedGroup) {
|
|
354
|
+
let accountNode;
|
|
355
|
+
RED.nodes.eachConfig(cfg => {
|
|
356
|
+
if (cfg.id === accountConfigInput.val()) accountNode = cfg;
|
|
357
|
+
});
|
|
358
|
+
let accountName = accountNode ? accountNode.name : "";
|
|
359
|
+
$("#node-config-input-name").val(accountName ? accountName + " - " + selectedGroup.subject : selectedGroup.subject);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
let onGroupMembersLoad = () => {
|
|
363
|
+
let selectedId = groupSelectionControl.typedInput("value");
|
|
364
|
+
updateMemberList(selectedId);
|
|
365
|
+
};
|
|
366
|
+
let updateGroupList = (accountNodeId) => {
|
|
367
|
+
// map controls.
|
|
368
|
+
let groupSelectionControl = $("#node-config-input-groupId");
|
|
369
|
+
let newGroupButton = document.getElementById("wa-group-new-btn");
|
|
370
|
+
// get the localConnectionId.
|
|
371
|
+
let localConnectionId;
|
|
372
|
+
RED.nodes.eachConfig(cfg => {
|
|
373
|
+
if (cfg.id === accountNodeId) {
|
|
374
|
+
localConnectionId = cfg.localConnectionId;
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
// if we couldnt find a localConnectionId, it means that the connection does not exist or was not set.
|
|
378
|
+
if (!localConnectionId) {
|
|
379
|
+
newGroupButton.disabled = true;
|
|
380
|
+
groupList = [];
|
|
381
|
+
groupSelectionControl.typedInput('types', [{
|
|
382
|
+
value: "group",
|
|
383
|
+
options: []
|
|
384
|
+
}]);
|
|
385
|
+
}
|
|
386
|
+
if (localConnectionId) {
|
|
387
|
+
newGroupButton.disabled = false;
|
|
388
|
+
// issue request to backend to get the list of groups
|
|
389
|
+
waService.getGroups(node.id, localConnectionId).then(groupListResponse => {
|
|
390
|
+
groupList = groupListResponse;
|
|
391
|
+
// rebuild the list of groups...
|
|
392
|
+
let groups = groupList.map(groupMeta => {
|
|
393
|
+
return {
|
|
394
|
+
value: groupMeta.id,
|
|
395
|
+
label: groupMeta.subject
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
// detach change handlers before restoring saved value so the name field is not overwritten on open
|
|
399
|
+
groupSelectionControl.off("change", onGroupChanged);
|
|
400
|
+
groupSelectionControl.off("change", onGroupMembersLoad);
|
|
401
|
+
groupSelectionControl.typedInput('types', [{
|
|
402
|
+
value: "group",
|
|
403
|
+
options: groups
|
|
404
|
+
}]);
|
|
405
|
+
groupSelectionControl.typedInput('value', node.groupId);
|
|
406
|
+
groupSelectionControl.on("change", onGroupChanged);
|
|
407
|
+
groupSelectionControl.on("change", onGroupMembersLoad);
|
|
408
|
+
onGroupMembersLoad();
|
|
409
|
+
}).catch(error => {
|
|
410
|
+
console.log(`Error gettting groups: ${name}`, error);
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
let updateMemberList = (selectedGroupId) => {
|
|
415
|
+
let selectedGroup = groupList.find(g => g.id === selectedGroupId);
|
|
416
|
+
if (!selectedGroup) {
|
|
417
|
+
//console.log("no group selected");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
console.log("updating member list", selectedGroup);
|
|
421
|
+
let members = selectedGroup.participants ? selectedGroup.participants : [];
|
|
422
|
+
// determine if the linked account can manage roles (must be Admin or SuperAdmin in this group)
|
|
423
|
+
let me = members.find(m => m.isMe);
|
|
424
|
+
let canManage = me && (me.role === "Admin" || me.role === "SuperAdmin");
|
|
425
|
+
let groupMemberList = $("#node-input-groupMembers-container");
|
|
426
|
+
groupMemberList.editableList({
|
|
427
|
+
sortable: false,
|
|
428
|
+
removable: true,
|
|
429
|
+
height: 'auto',
|
|
430
|
+
scrollOnAdd: true,
|
|
431
|
+
addButton: 'Add Member',
|
|
432
|
+
header: $('<div>').append($.parseHTML(`
|
|
433
|
+
<div style='width:130px; margin-left:8px; display:inline-block;'>Number</div>
|
|
434
|
+
<div style='width:140px; display:inline-block;'>Name</div>
|
|
435
|
+
<div style='width:100px; display:inline-block;'>Role</div>
|
|
436
|
+
`)),
|
|
437
|
+
addItem: function(container, i, _member) {
|
|
438
|
+
let member = _member;
|
|
439
|
+
console.log("ADD MEMBER: ", member);
|
|
440
|
+
let saveMember = (number, role) => {
|
|
441
|
+
// get the localConnectionId from the selected account config.
|
|
442
|
+
let localConnectionId;
|
|
443
|
+
RED.nodes.eachConfig(cfg => {
|
|
444
|
+
if (cfg.id === accountConfigInput.val()) {
|
|
445
|
+
localConnectionId = cfg.localConnectionId;
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
if (!localConnectionId) return;
|
|
449
|
+
// format the phone number as a WhatsApp JID: strip leading + and append @s.whatsapp.net
|
|
450
|
+
let userId = number.replace(/^\+/, "") + "@s.whatsapp.net";
|
|
451
|
+
let groupId = groupSelectionControl.typedInput("value");
|
|
452
|
+
saveButton.prop("disabled", true);
|
|
453
|
+
waService.addUserToGroup(node.id, localConnectionId, userId, groupId, role).then(() => {
|
|
454
|
+
idField.val(userId);
|
|
455
|
+
member.id = userId;
|
|
456
|
+
phoneNumberField.prop("disabled", true);
|
|
457
|
+
saveButton.hide();
|
|
458
|
+
}).catch(err => {
|
|
459
|
+
console.log("Error adding member to group:", err);
|
|
460
|
+
}).finally(() => {
|
|
461
|
+
saveButton.prop("disabled", false);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
let onRoleChanged = (role) => {
|
|
465
|
+
if (!member.id) return; // new unsaved member — role is submitted with the save button
|
|
466
|
+
let localConnectionId;
|
|
467
|
+
RED.nodes.eachConfig(cfg => {
|
|
468
|
+
if (cfg.id === accountConfigInput.val()) {
|
|
469
|
+
localConnectionId = cfg.localConnectionId;
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
if (!localConnectionId) return;
|
|
473
|
+
let groupId = groupSelectionControl.typedInput("value");
|
|
474
|
+
roleField.prop("disabled", true);
|
|
475
|
+
waService.updateUserInGroup(node.id, localConnectionId, member.id, groupId, role).catch(err => {
|
|
476
|
+
console.log("Error updating member role:", err);
|
|
477
|
+
}).finally(() => {
|
|
478
|
+
roleField.prop("disabled", false);
|
|
479
|
+
});
|
|
480
|
+
};
|
|
481
|
+
let row = $('<div/>', {
|
|
482
|
+
class: 'form-row',
|
|
483
|
+
style: 'display: flex; margin-bottom: 0;'
|
|
484
|
+
}).appendTo(container);
|
|
485
|
+
// Add the id column
|
|
486
|
+
let idField = $('<input/>', {
|
|
487
|
+
type: 'text',
|
|
488
|
+
name: i,
|
|
489
|
+
required: true,
|
|
490
|
+
style: 'display:none;',
|
|
491
|
+
class: 'node-input-member-id-value'
|
|
492
|
+
}).appendTo(row);
|
|
493
|
+
idField.val(member.id);
|
|
494
|
+
// Add the Number column
|
|
495
|
+
let phoneNumberField = $('<input/>', {
|
|
496
|
+
type: 'text',
|
|
497
|
+
name: i,
|
|
498
|
+
required: true,
|
|
499
|
+
disabled: (member.id),
|
|
500
|
+
style: 'margin-left: 5px; margin-bottom: 0; width:130px',
|
|
501
|
+
class: 'node-input-member-phonenumber-value'
|
|
502
|
+
}).appendTo(row);
|
|
503
|
+
if (member.id) {
|
|
504
|
+
phoneNumberField.val(member.id.substring(0, member.id.indexOf("@")));
|
|
505
|
+
}
|
|
506
|
+
// Add the Name column (read-only)
|
|
507
|
+
let nameField = $('<span/>', {
|
|
508
|
+
style: 'margin-left: 5px; margin-bottom: 0; width:140px; display:inline-block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; line-height:34px;',
|
|
509
|
+
class: 'node-input-member-name-value'
|
|
510
|
+
}).appendTo(row);
|
|
511
|
+
nameField.text(member.name ?? "");
|
|
512
|
+
let isSuperAdmin = member.role === "SuperAdmin";
|
|
513
|
+
let roleField = $('<select />', {
|
|
514
|
+
name: i,
|
|
515
|
+
required: false,
|
|
516
|
+
disabled: isSuperAdmin || !canManage,
|
|
517
|
+
style: 'margin-left: 5px; margin-bottom: 0; width:100px',
|
|
518
|
+
class: 'node-input-member-role-value'
|
|
519
|
+
});
|
|
520
|
+
// SuperAdmin is a fixed role — show it only for the current SuperAdmin, not as an assignable option
|
|
521
|
+
if (isSuperAdmin) {
|
|
522
|
+
roleField.append($("<option>").attr('value', "SuperAdmin").text("Super Admin"));
|
|
523
|
+
} else {
|
|
524
|
+
Object.entries({
|
|
525
|
+
Admin: "Admin",
|
|
526
|
+
Member: "Member"
|
|
527
|
+
}).forEach(([key, value]) => {
|
|
528
|
+
roleField.append($("<option>").attr('value', key).text(value));
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
roleField.val(member.role);
|
|
532
|
+
roleField.on("change", () => onRoleChanged(roleField.val()));
|
|
533
|
+
roleField.appendTo(row);
|
|
534
|
+
// column for the save button.
|
|
535
|
+
let saveButtonHolder = $(`<span id="middlePlaceholder" style="display: inline-block; width: 18px;"></span>`).appendTo(row)
|
|
536
|
+
let saveButton = $(`<a
|
|
537
|
+
href="#"
|
|
538
|
+
class="red-ui-editableList-item-approve red-ui-button red-ui-button-small"
|
|
539
|
+
style="margin-left: 5px; margin-bottom: 0;margin-top: 8px;">
|
|
540
|
+
<i class="fa fa-save"></i>
|
|
541
|
+
</a>`).appendTo(saveButtonHolder);
|
|
542
|
+
// if there is no member.id, show the save button because its a new member.
|
|
543
|
+
if (member.id) {
|
|
544
|
+
saveButton.hide();
|
|
545
|
+
} else {
|
|
546
|
+
saveButton.show();
|
|
547
|
+
}
|
|
548
|
+
saveButton.on("click", () => {
|
|
549
|
+
saveMember(phoneNumberField.val(), roleField.val())
|
|
550
|
+
})
|
|
551
|
+
},
|
|
552
|
+
removeItem: function(member) {
|
|
553
|
+
if (member.id) {
|
|
554
|
+
console.log("member removed", member);
|
|
555
|
+
// get the localConnectionId from the selected account config.
|
|
556
|
+
let localConnectionId;
|
|
557
|
+
RED.nodes.eachConfig(cfg => {
|
|
558
|
+
if (cfg.id === accountConfigInput.val()) {
|
|
559
|
+
localConnectionId = cfg.localConnectionId;
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
// get the groupId
|
|
563
|
+
let groupId = groupSelectionControl.typedInput("value");
|
|
564
|
+
// remove the user from the group.
|
|
565
|
+
waService.removeUserFromGroup(node.id, localConnectionId, member.id, groupId);
|
|
566
|
+
} else {
|
|
567
|
+
console.log("member id was not available when trying to remove member");
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
groupMemberList.empty();
|
|
572
|
+
groupMemberList.editableList('addItems', members);
|
|
573
|
+
}
|
|
574
|
+
let setCreateButtonEnabled = (accountNodeId) => {
|
|
575
|
+
let hasAccount = accountNodeId && accountNodeId !== "_ADD_";
|
|
576
|
+
$("#wa-group-new-btn").prop("disabled", !hasAccount);
|
|
577
|
+
};
|
|
578
|
+
// when the accountConfig changes, update the group list and button state.
|
|
579
|
+
let accountConfigInput = $("#node-config-input-accountConfig");
|
|
580
|
+
accountConfigInput.on("change", () => {
|
|
581
|
+
updateGroupList(accountConfigInput.val());
|
|
582
|
+
setCreateButtonEnabled(accountConfigInput.val());
|
|
583
|
+
});
|
|
584
|
+
// when the group changes, update the name field and member list separately
|
|
585
|
+
let groupSelectionControl = $("#node-config-input-groupId");
|
|
586
|
+
groupSelectionControl.on("change", onGroupChanged);
|
|
587
|
+
groupSelectionControl.on("change", onGroupMembersLoad);
|
|
588
|
+
groupSelectionControl.typedInput({
|
|
589
|
+
types: [{
|
|
590
|
+
value: "group",
|
|
591
|
+
options: []
|
|
592
|
+
}]
|
|
593
|
+
});
|
|
594
|
+
// set initial button state
|
|
595
|
+
setCreateButtonEnabled(accountConfigInput.val());
|
|
596
|
+
// show inline create section
|
|
597
|
+
$("#wa-group-new-btn").on("click", () => {
|
|
598
|
+
$("#wa-group-new-row").hide();
|
|
599
|
+
$("#wa-group-create-section").show();
|
|
600
|
+
$("#wa-new-group-name").val("").focus();
|
|
601
|
+
});
|
|
602
|
+
// cancel
|
|
603
|
+
$("#wa-group-create-cancel-btn").on("click", () => {
|
|
604
|
+
$("#wa-group-create-section").hide();
|
|
605
|
+
$("#wa-group-new-row").show();
|
|
606
|
+
$("#wa-new-group-name").val("");
|
|
607
|
+
});
|
|
608
|
+
// create
|
|
609
|
+
$("#wa-group-create-confirm-btn").on("click", () => {
|
|
610
|
+
let groupName = $("#wa-new-group-name").val().trim();
|
|
611
|
+
if (!groupName) return;
|
|
612
|
+
let localConnectionId;
|
|
613
|
+
RED.nodes.eachConfig(cfg => {
|
|
614
|
+
if (cfg.id === accountConfigInput.val()) localConnectionId = cfg.localConnectionId;
|
|
615
|
+
});
|
|
616
|
+
if (!localConnectionId) return;
|
|
617
|
+
let $btn = $("#wa-group-create-confirm-btn");
|
|
618
|
+
$btn.prop("disabled", true);
|
|
619
|
+
waService.createGroup(node.id, localConnectionId, groupName).then(newGroup => {
|
|
620
|
+
return waService.getGroups(node.id, localConnectionId).then(groupListResponse => {
|
|
621
|
+
groupList = groupListResponse;
|
|
622
|
+
let groups = groupList.map(g => ({
|
|
623
|
+
value: g.id,
|
|
624
|
+
label: g.subject
|
|
625
|
+
}));
|
|
626
|
+
groupSelectionControl.typedInput("types", [{
|
|
627
|
+
value: "group",
|
|
628
|
+
options: groups
|
|
629
|
+
}]);
|
|
630
|
+
groupSelectionControl.typedInput("value", newGroup.id);
|
|
631
|
+
$("#wa-group-create-section").hide();
|
|
632
|
+
$("#wa-group-new-row").show();
|
|
633
|
+
$("#wa-new-group-name").val("");
|
|
634
|
+
});
|
|
635
|
+
}).catch(err => {
|
|
636
|
+
console.log("Error creating group:", err);
|
|
637
|
+
}).finally(() => {
|
|
638
|
+
$btn.prop("disabled", false);
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
oneditsave: function() {
|
|
644
|
+
// **** WhatsappGroupConfigNode **** //
|
|
645
|
+
{
|
|
646
|
+
let node = this;
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
oneditcancel: function() {},
|
|
650
|
+
oneditdelete: function() {},
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
</script>
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
<script type="text/html" data-template-name='WhatsappGroupConfigNode'>
|
|
657
|
+
<div id='section_WhatsappGroupConfigNode'>
|
|
658
|
+
<div class="form-row">
|
|
659
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
660
|
+
<input type="text" id="node-config-input-name" />
|
|
661
|
+
</div>
|
|
662
|
+
|
|
663
|
+
<div class="editorgroupborder">
|
|
664
|
+
|
|
665
|
+
<div class="form-row nomargin">
|
|
666
|
+
<label for="node-config-input-accountConfig"><i class="fa fa-user"></i> Account</label>
|
|
667
|
+
<input type="text" id="node-config-input-accountConfig" placeholder="config">
|
|
668
|
+
</div>
|
|
669
|
+
|
|
670
|
+
<div class="form-row">
|
|
671
|
+
<label for="node-config-input-groupId"><i class="fa fa-users"></i> Group</label>
|
|
672
|
+
<input type="text" id="node-config-input-groupId">
|
|
673
|
+
</div>
|
|
674
|
+
|
|
675
|
+
<div class="form-row" id="wa-group-new-row">
|
|
676
|
+
<label></label>
|
|
677
|
+
<button type="button" id="wa-group-new-btn" class="red-ui-button" disabled>
|
|
678
|
+
<i class="fa fa-plus"></i> Create New Group
|
|
679
|
+
</button>
|
|
680
|
+
</div>
|
|
681
|
+
|
|
682
|
+
<div id="wa-group-create-section" style="display:none;">
|
|
683
|
+
<div class="form-row nomargin">
|
|
684
|
+
<label style="width:120px;"><i class="fa fa-tag"></i> Group Name</label>
|
|
685
|
+
<input type="text" id="wa-new-group-name" style="width:calc(100% - 200px);">
|
|
686
|
+
</div>
|
|
687
|
+
<div class="form-row">
|
|
688
|
+
<label></label>
|
|
689
|
+
<button type="button" id="wa-group-create-confirm-btn" class="red-ui-button">
|
|
690
|
+
<i class="fa fa-check"></i> Create
|
|
691
|
+
</button>
|
|
692
|
+
<button type="button" id="wa-group-create-cancel-btn" class="red-ui-button" style="margin-left:4px;">
|
|
693
|
+
<i class="fa fa-times"></i> Cancel
|
|
694
|
+
</button>
|
|
695
|
+
</div>
|
|
696
|
+
</div>
|
|
697
|
+
|
|
698
|
+
<div class="form-row" style="font-weight: 600;">
|
|
699
|
+
<i class="w-16 fa fa-eye"></i> <span>Group Members</span>
|
|
700
|
+
</div>
|
|
701
|
+
<div class="form-row">
|
|
702
|
+
<span>
|
|
703
|
+
<ol id="node-input-groupMembers-container"></ol>
|
|
704
|
+
</span>
|
|
705
|
+
</div>
|
|
706
|
+
</div>
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
</div>
|
|
711
|
+
</script>
|
|
712
|
+
|
|
713
|
+
<script type="text/markdown" data-help-name='WhatsappGroupConfigNode'>
|
|
714
|
+
> **Early development** — this package is still maturing. Some features may be incomplete and diagnostic log output is intentionally verbose for now.
|
|
715
|
+
|
|
716
|
+
References a WhatsApp group within a linked account. Used as the target for Send Message nodes and the source for Receive Message nodes.
|
|
717
|
+
|
|
718
|
+
### Properties
|
|
719
|
+
|
|
720
|
+
: *name* (string) : Display label for this config node.
|
|
721
|
+
: *account* (config) : The WhatsApp Account config node that owns this group.
|
|
722
|
+
: *group* (select) : The WhatsApp group to use. The dropdown is populated from the groups available on the selected account.
|
|
723
|
+
|
|
724
|
+
### Creating a new group
|
|
725
|
+
|
|
726
|
+
Click **Create New Group** to create a new WhatsApp group from within Node-RED. The new group will appear in the group dropdown once created.
|
|
727
|
+
</script>
|
|
728
|
+
<!-- WhatsappSendMessageNode -->
|
|
729
|
+
<style>
|
|
730
|
+
</style>
|
|
731
|
+
<script>
|
|
732
|
+
const WA_SEND_FIELDS = [{
|
|
733
|
+
id: "text",
|
|
734
|
+
icon: "fa-font",
|
|
735
|
+
label: "Send Text",
|
|
736
|
+
types: ["msg", "flow", "global", "str"]
|
|
737
|
+
}, {
|
|
738
|
+
id: "image",
|
|
739
|
+
icon: "fa-image",
|
|
740
|
+
label: "Send Image",
|
|
741
|
+
types: ["msg", "flow", "global"]
|
|
742
|
+
}, ];
|
|
743
|
+
|
|
744
|
+
</script>
|
|
745
|
+
<!-- logger -->
|
|
746
|
+
<style>
|
|
747
|
+
.editorgroupborder {
|
|
748
|
+
border-width: 1px;
|
|
749
|
+
border-style: solid;
|
|
750
|
+
border-color: lightgray;
|
|
751
|
+
padding: 3px;
|
|
752
|
+
margin-top: 2px;
|
|
753
|
+
margin-bottom: 2px;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.editorsectionheading {
|
|
757
|
+
font-weight: 600;
|
|
758
|
+
margin-bottom: 1px !important;
|
|
759
|
+
margin-top: 6px !important;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.nomargin {
|
|
763
|
+
margin-bottom: 1px !important;
|
|
764
|
+
margin-top: 1px !important;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
.slidecontainer {
|
|
768
|
+
width: 100%;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.slider {
|
|
772
|
+
-webkit-appearance: none;
|
|
773
|
+
appearance: none;
|
|
774
|
+
width: 100%;
|
|
775
|
+
height: 25px;
|
|
776
|
+
background: #d3d3d3;
|
|
777
|
+
outline: none;
|
|
778
|
+
opacity: 0.7;
|
|
779
|
+
-webkit-transition: .2s;
|
|
780
|
+
transition: opacity .2s;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.slider:hover {
|
|
784
|
+
opacity: 1;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.slider::-webkit-slider-thumb {
|
|
788
|
+
-webkit-appearance: none;
|
|
789
|
+
appearance: none;
|
|
790
|
+
width: 25px;
|
|
791
|
+
height: 25px;
|
|
792
|
+
background: #04AA6D;
|
|
793
|
+
cursor: pointer;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
.slider::-moz-range-thumb {
|
|
797
|
+
width: 25px;
|
|
798
|
+
height: 25px;
|
|
799
|
+
background: #04AA6D;
|
|
800
|
+
cursor: pointer;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.towb_editorlabel {
|
|
804
|
+
width: 120px !important;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
.towb_editorfield {
|
|
808
|
+
width: 70% !important;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
.towb_editorfield_short {
|
|
812
|
+
width: 30% !important;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
</style>
|
|
816
|
+
<script type="text/javascript">
|
|
817
|
+
// CSS
|
|
818
|
+
dropdownStyles = `
|
|
819
|
+
.loggertype-dropdown {
|
|
820
|
+
position: absolute;
|
|
821
|
+
background: white;
|
|
822
|
+
border: 1px solid #ccc;
|
|
823
|
+
border-radius: 4px;
|
|
824
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
825
|
+
max-height: 150px;
|
|
826
|
+
overflow-y: auto;
|
|
827
|
+
z-index: 1000;
|
|
828
|
+
min-width: 150px;
|
|
829
|
+
}
|
|
830
|
+
.loggertype-dropdown .dropdown-item {
|
|
831
|
+
padding: 6px 12px;
|
|
832
|
+
cursor: pointer;
|
|
833
|
+
}
|
|
834
|
+
.loggertype-dropdown .dropdown-item:hover {
|
|
835
|
+
background: #f0f0f0;
|
|
836
|
+
}
|
|
837
|
+
`;
|
|
838
|
+
$('<style>').text(dropdownStyles).appendTo('head');
|
|
839
|
+
|
|
840
|
+
</script>
|
|
841
|
+
<script type="text/javascript">
|
|
842
|
+
RED.nodes.registerType('WhatsappSendMessageNode', {
|
|
843
|
+
category: 'whatsapp',
|
|
844
|
+
icon: 'whatsapp.png',
|
|
845
|
+
color: '#E6FFDA',
|
|
846
|
+
label: function() {
|
|
847
|
+
return this.name
|
|
848
|
+
},
|
|
849
|
+
paletteLabel: 'WhatsApp Send Message',
|
|
850
|
+
inputs: 1,
|
|
851
|
+
inputLabels: (i) => 'message',
|
|
852
|
+
outputs: 0,
|
|
853
|
+
outputLabels: (i) => [][i],
|
|
854
|
+
defaults: {
|
|
855
|
+
name: {
|
|
856
|
+
value: 'Send WhatsApp Message',
|
|
857
|
+
required: true
|
|
858
|
+
},
|
|
859
|
+
groupConfig: {
|
|
860
|
+
type: 'WhatsappGroupConfigNode',
|
|
861
|
+
required: true
|
|
862
|
+
},
|
|
863
|
+
payloads: {
|
|
864
|
+
value: '[]',
|
|
865
|
+
required: false,
|
|
866
|
+
validate: function(value) {
|
|
867
|
+
try {
|
|
868
|
+
var payloads = JSON.parse(value || "[]");
|
|
869
|
+
return payloads.every(function(p) {
|
|
870
|
+
return !p.enabled || (p.value !== undefined && p.value !== "");
|
|
871
|
+
});
|
|
872
|
+
} catch (e) {
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
logEnabled: {
|
|
878
|
+
value: false,
|
|
879
|
+
required: true
|
|
880
|
+
},
|
|
881
|
+
logger: {
|
|
882
|
+
required: false,
|
|
883
|
+
value: '',
|
|
884
|
+
type: 'DelegatedConfigReferenceNode'
|
|
885
|
+
},
|
|
886
|
+
logTemplateOverrideEnabled: {
|
|
887
|
+
value: false
|
|
888
|
+
},
|
|
889
|
+
logTemplateOverride: {
|
|
890
|
+
value: 'message:{{msg}}'
|
|
891
|
+
},
|
|
892
|
+
metricsEnabled: {
|
|
893
|
+
value: false,
|
|
894
|
+
required: true
|
|
895
|
+
},
|
|
896
|
+
metricsReference: {
|
|
897
|
+
type: 'MetricsConfigNode',
|
|
898
|
+
required: false
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
oneditprepare: function() {
|
|
902
|
+
// **** WhatsappSendMessageNode **** //
|
|
903
|
+
{
|
|
904
|
+
let node = this;
|
|
905
|
+
let payloads = [];
|
|
906
|
+
try {
|
|
907
|
+
payloads = JSON.parse(node.payloads || "[]");
|
|
908
|
+
} catch (e) {}
|
|
909
|
+
WA_SEND_FIELDS.forEach(function(field) {
|
|
910
|
+
let saved = payloads.find(function(p) {
|
|
911
|
+
return p.id === field.id;
|
|
912
|
+
}) || {
|
|
913
|
+
enabled: false,
|
|
914
|
+
type: field.types[0],
|
|
915
|
+
value: ""
|
|
916
|
+
};
|
|
917
|
+
let row = $('<div class="form-row nomargin">' + '<label style="width:120px;"><i class="fa ' + field.icon + '"></i> ' + field.label + '</label>' + '<div id="wa-send-row-' + field.id + '" style="display:inline-flex; align-items:center; gap:6px; vertical-align:middle; width:calc(100% - 128px);">' + '<input type="checkbox" id="wa-send-enabled-' + field.id + '" style="width:auto; margin:0; flex-shrink:0;">' + '<input type="text" id="wa-send-value-' + field.id + '">' + '</div>' + '</div>');
|
|
918
|
+
$("#wa-send-payload-rows").append(row);
|
|
919
|
+
$("#wa-send-value-" + field.id).typedInput({
|
|
920
|
+
types: field.types
|
|
921
|
+
}).typedInput("type", saved.type).typedInput("value", saved.value);
|
|
922
|
+
// typedInput inserts its container as a sibling of the (now hidden) input, making it
|
|
923
|
+
// a direct flex child — apply flex:1 so it expands to fill the remaining row width.
|
|
924
|
+
$("#wa-send-row-" + field.id + " .red-ui-typedInput-container").css({
|
|
925
|
+
"flex": "1",
|
|
926
|
+
"min-width": "0",
|
|
927
|
+
"width": ""
|
|
928
|
+
});
|
|
929
|
+
$("#wa-send-enabled-" + field.id).prop("checked", saved.enabled);
|
|
930
|
+
// live validation: highlight the typedInput when checkbox is checked but value is empty.
|
|
931
|
+
var checkFieldValid = function() {
|
|
932
|
+
var enabled = $("#wa-send-enabled-" + field.id).prop("checked");
|
|
933
|
+
var value = $("#wa-send-value-" + field.id).typedInput("value");
|
|
934
|
+
var invalid = enabled && (!value || value === "");
|
|
935
|
+
$("#wa-send-row-" + field.id + " .red-ui-typedInput-container").toggleClass("input-error", invalid);
|
|
936
|
+
};
|
|
937
|
+
$("#wa-send-enabled-" + field.id).on("change", checkFieldValid);
|
|
938
|
+
$("#wa-send-value-" + field.id).on("change", checkFieldValid);
|
|
939
|
+
checkFieldValid();
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
// **** logger **** //
|
|
943
|
+
{
|
|
944
|
+
let node = this;
|
|
945
|
+
// controls.
|
|
946
|
+
let logEnabled = $("#node-input-logEnabled");
|
|
947
|
+
let logTemplateEnabled = $("#node-input-logTemplateOverrideEnabled");
|
|
948
|
+
let logTemplateEnabledSection = $("#section_logTemplateOverrideEnabled");
|
|
949
|
+
let logEnabledSection = $("#section_logEnabled");
|
|
950
|
+
// functions.
|
|
951
|
+
let getLoggerTypes = async function() {
|
|
952
|
+
let types = [];
|
|
953
|
+
await $.ajax({
|
|
954
|
+
url: "nodetypeservice/find?tag=LoggerType",
|
|
955
|
+
type: "GET",
|
|
956
|
+
contentType: "application/json; charset=utf-8",
|
|
957
|
+
data: JSON.stringify({
|
|
958
|
+
id: node.id
|
|
959
|
+
}),
|
|
960
|
+
success: (response) => {
|
|
961
|
+
types = response;
|
|
962
|
+
},
|
|
963
|
+
error: (jqXHR, textStatus, errorThrown) => {
|
|
964
|
+
console.log(jqXHR, textStatus, errorThrown);
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
return types;
|
|
968
|
+
}
|
|
969
|
+
let getLoggers = async () => {
|
|
970
|
+
let loggers = [];
|
|
971
|
+
// get the list of types again.
|
|
972
|
+
let loggerTypes = (await getLoggerTypes()).map(type => type.id);
|
|
973
|
+
// add all of the known loggers.
|
|
974
|
+
RED.nodes.eachConfig(cfg => {
|
|
975
|
+
if (loggerTypes.includes(cfg.type)) {
|
|
976
|
+
loggers.push({
|
|
977
|
+
id: cfg.id,
|
|
978
|
+
name: cfg.label(),
|
|
979
|
+
type: cfg.type
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
return loggers;
|
|
984
|
+
};
|
|
985
|
+
let updateLoggerSelectorList = async () => {
|
|
986
|
+
loggerSelector.empty();
|
|
987
|
+
(await getLoggers()).forEach(logger => {
|
|
988
|
+
$('<option value="' + logger.id + '">' + logger.name + '</option>').appendTo(loggerSelector);
|
|
989
|
+
});
|
|
990
|
+
$('<option value="_ADD_">none</option>').appendTo(loggerSelector);
|
|
991
|
+
}
|
|
992
|
+
logEnabled.on('change', function() {
|
|
993
|
+
if (logEnabled.prop("checked")) {
|
|
994
|
+
logEnabledSection.show();
|
|
995
|
+
node._def.defaults.logger.required = true;
|
|
996
|
+
} else {
|
|
997
|
+
logEnabledSection.hide()
|
|
998
|
+
node._def.defaults.logger.required = false;
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
logTemplateEnabled.on('change', function() {
|
|
1002
|
+
if (logTemplateEnabled.prop("checked")) {
|
|
1003
|
+
logTemplateEnabledSection.show();
|
|
1004
|
+
node._def.defaults.logTemplateOverride.required = true;
|
|
1005
|
+
} else {
|
|
1006
|
+
logTemplateEnabledSection.hide()
|
|
1007
|
+
node._def.defaults.logTemplateOverride.required = false;
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
node.logTemplateOverrideEditor = RED.editor.createEditor({
|
|
1011
|
+
id: 'node-input-logTemplateOverrideEditor',
|
|
1012
|
+
mode: 'ace/mode/handlebars',
|
|
1013
|
+
value: node.logTemplateOverride
|
|
1014
|
+
});
|
|
1015
|
+
let loggerSelector = $("#logger-selector");
|
|
1016
|
+
let loggerEditButton = $("#logger-edit-btn");
|
|
1017
|
+
let loggerAddButton = $("#logger-add-btn");
|
|
1018
|
+
loggerEditButton.on('click', async function(event) {
|
|
1019
|
+
event.stopPropagation();
|
|
1020
|
+
if (loggerEditButton.hasClass("disabled")) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
// figure out what type of logger this is.
|
|
1024
|
+
let selectedLogger = (await getLoggers()).find(logger => logger.id === loggerSelector.val());
|
|
1025
|
+
RED.editor.editConfig("#logger-selector", selectedLogger.type, selectedLogger.id);
|
|
1026
|
+
});
|
|
1027
|
+
loggerAddButton.on('click', function(event) {
|
|
1028
|
+
event.stopPropagation();
|
|
1029
|
+
let $btn = $(event.target);
|
|
1030
|
+
// Remove any existing dropdown
|
|
1031
|
+
$('.loggertype-dropdown').remove();
|
|
1032
|
+
getLoggerTypes().then(loggerTypes => {
|
|
1033
|
+
let $dropdown = $('<div class="loggertype-dropdown red-ui-editor"></div>');
|
|
1034
|
+
loggerTypes.forEach(loggerType => {
|
|
1035
|
+
$('<div class="dropdown-item"></div>').text(loggerType.name).data('value', loggerType.id).css({
|
|
1036
|
+
padding: '4px 8px',
|
|
1037
|
+
cursor: 'pointer'
|
|
1038
|
+
}).appendTo($dropdown);
|
|
1039
|
+
});
|
|
1040
|
+
// Append first so outerWidth() is measurable, then position so the
|
|
1041
|
+
// right edge of the dropdown aligns with the right edge of the button.
|
|
1042
|
+
$('body').append($dropdown);
|
|
1043
|
+
$dropdown.css({
|
|
1044
|
+
top: $btn.offset().top + $btn.outerHeight(),
|
|
1045
|
+
left: $btn.offset().left + $btn.outerWidth() - $dropdown.outerWidth()
|
|
1046
|
+
});
|
|
1047
|
+
$(document).on('mousedown.dropdown', function(e) {
|
|
1048
|
+
if (!$(e.target).closest('.loggertype-dropdown').length) {
|
|
1049
|
+
$dropdown.remove();
|
|
1050
|
+
$(document).off('mousedown.dropdown'); // Clean up the listener
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
// Handle item selection
|
|
1054
|
+
$dropdown.on('click', '.dropdown-item', function(e) {
|
|
1055
|
+
e.stopPropagation();
|
|
1056
|
+
const selectedValue = $(this).data('value');
|
|
1057
|
+
const selectedText = $(this).text();
|
|
1058
|
+
// Do whatever you need with the selection
|
|
1059
|
+
console.log('Selected:', selectedValue, selectedText);
|
|
1060
|
+
$dropdown.remove();
|
|
1061
|
+
RED.editor.editConfig("#logger-selector", selectedValue, "_ADD_");
|
|
1062
|
+
});
|
|
1063
|
+
})
|
|
1064
|
+
});
|
|
1065
|
+
loggerSelector.on("focus", updateLoggerSelectorList);
|
|
1066
|
+
loggerSelector.on('change', function(event) {
|
|
1067
|
+
if ("_ADD_" === loggerSelector.val()) {
|
|
1068
|
+
loggerEditButton.addClass("disabled")
|
|
1069
|
+
} else {
|
|
1070
|
+
loggerEditButton.removeClass("disabled")
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
// lastly, make sure that we set the correct value on the logger selector.
|
|
1074
|
+
updateLoggerSelectorList().then(() => {
|
|
1075
|
+
loggerSelector.val(node.logger);
|
|
1076
|
+
loggerSelector.trigger('change');
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
// **** metrics **** //
|
|
1080
|
+
{
|
|
1081
|
+
let node = this;
|
|
1082
|
+
let metricsEnabled = $("#node-input-metricsEnabled");
|
|
1083
|
+
let metricsEnabledSection = $("#section_metricsEnabled");
|
|
1084
|
+
metricsEnabled.on('change', function() {
|
|
1085
|
+
if (metricsEnabled.prop("checked")) {
|
|
1086
|
+
metricsEnabledSection.show();
|
|
1087
|
+
node._def.defaults.metricsReference.required = true;
|
|
1088
|
+
} else {
|
|
1089
|
+
metricsEnabledSection.hide()
|
|
1090
|
+
node._def.defaults.metricsReference.required = false;
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
},
|
|
1095
|
+
oneditsave: function() {
|
|
1096
|
+
// **** WhatsappSendMessageNode **** //
|
|
1097
|
+
{
|
|
1098
|
+
let payloads = WA_SEND_FIELDS.map(function(field) {
|
|
1099
|
+
return {
|
|
1100
|
+
id: field.id,
|
|
1101
|
+
enabled: $("#wa-send-enabled-" + field.id).prop("checked"),
|
|
1102
|
+
type: $("#wa-send-value-" + field.id).typedInput("type"),
|
|
1103
|
+
value: $("#wa-send-value-" + field.id).typedInput("value"),
|
|
1104
|
+
};
|
|
1105
|
+
});
|
|
1106
|
+
$("#node-input-payloads").val(JSON.stringify(payloads));
|
|
1107
|
+
}
|
|
1108
|
+
// **** logger **** //
|
|
1109
|
+
{
|
|
1110
|
+
node = this;
|
|
1111
|
+
var selectedLogger = $("#logger-selector").val();
|
|
1112
|
+
node.logger = (selectedLogger && selectedLogger !== "_ADD_") ? selectedLogger : '';
|
|
1113
|
+
node.logTemplateOverride = node.logTemplateOverrideEditor.getValue();
|
|
1114
|
+
node.logTemplateOverrideEditor.destroy();
|
|
1115
|
+
delete node.logTemplateOverrideEditor;
|
|
1116
|
+
}
|
|
1117
|
+
// **** metrics **** //
|
|
1118
|
+
{}
|
|
1119
|
+
},
|
|
1120
|
+
oneditcancel: function() {},
|
|
1121
|
+
oneditdelete: function() {},
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
</script>
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
<script type="text/html" data-template-name='WhatsappSendMessageNode'>
|
|
1128
|
+
<div id='section_WhatsappSendMessageNode'>
|
|
1129
|
+
<div class="form-row">
|
|
1130
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
1131
|
+
<input type="text" id="node-input-name" />
|
|
1132
|
+
</div>
|
|
1133
|
+
|
|
1134
|
+
<div class="form-row nomargin">
|
|
1135
|
+
<label for="node-input-groupConfig" style="width:120px;"><i class="fa fa-users"></i> Group</label>
|
|
1136
|
+
<input type="text" id="node-input-groupConfig" placeholder="config">
|
|
1137
|
+
</div>
|
|
1138
|
+
|
|
1139
|
+
<div class="form-row editorsectionheading">
|
|
1140
|
+
<i class="w-16 fa fa-paper-plane"></i> <span>Send</span>
|
|
1141
|
+
</div>
|
|
1142
|
+
|
|
1143
|
+
<div class="editorgroupborder" id="wa-send-payload-rows">
|
|
1144
|
+
</div>
|
|
1145
|
+
|
|
1146
|
+
<input type="hidden" id="node-input-payloads">
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
</div>
|
|
1150
|
+
|
|
1151
|
+
<div id='section_logger'>
|
|
1152
|
+
<div id="section_loggerconfig">
|
|
1153
|
+
<div class="form-row editorsectionheading">
|
|
1154
|
+
<i class="w-16 fa fa-eye"></i> <span>Logging</span>
|
|
1155
|
+
</div>
|
|
1156
|
+
<div class="editorgroupborder">
|
|
1157
|
+
<div class="form-row nomargin logEnableCheck">
|
|
1158
|
+
<input type="checkbox" id="node-input-logEnabled" placeholder="logEnabled" value="false" style="margin:8px 0 10px 102px; width:20px;">
|
|
1159
|
+
<label style="width:auto" for="node-input-logEnabled"><span>Enable Logging</span></label>
|
|
1160
|
+
</div>
|
|
1161
|
+
|
|
1162
|
+
<div id="section_logEnabled">
|
|
1163
|
+
<div class="form-row nomargin">
|
|
1164
|
+
<label for="logger-selector">Logger</label>
|
|
1165
|
+
<div style="width: 70%; display: inline-flex;">
|
|
1166
|
+
<select id="logger-selector" style="flex-grow: 1;" class="">
|
|
1167
|
+
<option value="_ADD_">none</option>
|
|
1168
|
+
</select>
|
|
1169
|
+
<a id="logger-edit-btn" class="red-ui-button disabled" style="margin-left: 10px;">
|
|
1170
|
+
<i class="fa fa-pencil"></i>
|
|
1171
|
+
</a>
|
|
1172
|
+
<a id="logger-add-btn" class="red-ui-button" style="margin-left: 10px;">
|
|
1173
|
+
<i class="fa fa-plus"></i>
|
|
1174
|
+
</a>
|
|
1175
|
+
</div>
|
|
1176
|
+
</div>
|
|
1177
|
+
|
|
1178
|
+
<div class="form-row nomargin">
|
|
1179
|
+
<input type="checkbox" id="node-input-logTemplateOverrideEnabled" placeholder="" value="false" style="margin:8px 0 10px 102px; width:20px;">
|
|
1180
|
+
<label style="width:auto" for="node-input-logTemplateOverrideEnabled"><span>Override Template</span></label>
|
|
1181
|
+
</div>
|
|
1182
|
+
|
|
1183
|
+
<div id="section_logTemplateOverrideEnabled">
|
|
1184
|
+
<div class="form-row">
|
|
1185
|
+
<label class="towb_editorlabel" for="node-input-logTemplateOverrideEditor"><i class="fa fa-code"></i> Template</label>
|
|
1186
|
+
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-logTemplateOverrideEditor"></div>
|
|
1187
|
+
</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
</div>
|
|
1190
|
+
</div>
|
|
1191
|
+
</div>
|
|
1192
|
+
|
|
1193
|
+
</div>
|
|
1194
|
+
|
|
1195
|
+
<div id='section_metrics'>
|
|
1196
|
+
<div id="section_metricsconfig">
|
|
1197
|
+
<div class="form-row editorsectionheading">
|
|
1198
|
+
<i class="w-16 fa fa-eye"></i> <span>Metrics</span>
|
|
1199
|
+
</div>
|
|
1200
|
+
<div class="editorgroupborder">
|
|
1201
|
+
<div class="form-row nomargin metricsEnableCheck">
|
|
1202
|
+
<input type="checkbox" id="node-input-metricsEnabled" value="false" style="margin:8px 0 10px 102px; width:20px;" />
|
|
1203
|
+
<label style="width:auto" for="node-input-metricsEnabled"><span>Enable Metrics</span></label>
|
|
1204
|
+
</div>
|
|
1205
|
+
|
|
1206
|
+
<div id="section_metricsEnabled">
|
|
1207
|
+
<div class="form-row nomargin">
|
|
1208
|
+
<label class="towb_editorlabel" for="node-input-metricsReference">Metric Collector</label>
|
|
1209
|
+
<input id="node-input-metricsReference" placeholder="metrics" />
|
|
1210
|
+
</div>
|
|
1211
|
+
</div>
|
|
1212
|
+
</div>
|
|
1213
|
+
</div>
|
|
1214
|
+
|
|
1215
|
+
</div>
|
|
1216
|
+
</script>
|
|
1217
|
+
|
|
1218
|
+
<script type="text/markdown" data-help-name='WhatsappSendMessageNode'>
|
|
1219
|
+
> **Early development** — this package is still maturing. Some features may be incomplete and diagnostic log output is intentionally verbose for now.
|
|
1220
|
+
|
|
1221
|
+
Sends a message to a WhatsApp group.
|
|
1222
|
+
|
|
1223
|
+
### Properties
|
|
1224
|
+
|
|
1225
|
+
: *name* (string) : Display label for this node.
|
|
1226
|
+
: *group* (config) : The WhatsApp Group config node to send to.
|
|
1227
|
+
|
|
1228
|
+
### Send fields
|
|
1229
|
+
|
|
1230
|
+
Each field has a checkbox to enable it and a typed value. Supported value types depend on the field:
|
|
1231
|
+
|
|
1232
|
+
- **Send Text** — `msg`, `flow`, `global`, `str`
|
|
1233
|
+
- **Send Image** — `msg`, `flow`, `global`
|
|
1234
|
+
|
|
1235
|
+
### Inputs
|
|
1236
|
+
|
|
1237
|
+
: *msg* : Trigger message. Field values are resolved against this message (for `msg` type) or from context.
|
|
1238
|
+
|
|
1239
|
+
### Logging
|
|
1240
|
+
: *enable logging* (boolean) : if checked, then the node will produce logging output to the specified logger.
|
|
1241
|
+
: *logger* (logconfig) : the endppoint that the node will log events to.
|
|
1242
|
+
: *override template* (logconfig) : if checked, a custom logging template can be provided for the log message.
|
|
1243
|
+
: *logger template* (mustache) : a template in mustache format to generate a message for logging purposes (see LoggerConfig node for more information)
|
|
1244
|
+
|
|
1245
|
+
### Metrics
|
|
1246
|
+
: *enable metrics* (boolean) : if checked, then the node will produce metrics output to the specified metrics provider.
|
|
1247
|
+
: *metric collector* (metricconfig) : the collection point or grouping where this particular metric will be attached.
|
|
1248
|
+
</script>
|
|
1249
|
+
<!-- WhatsappReceiveMessageNode -->
|
|
1250
|
+
<style>
|
|
1251
|
+
</style>
|
|
1252
|
+
<script>
|
|
1253
|
+
const WA_ACCEPT_FIELDS = [{
|
|
1254
|
+
id: "Text",
|
|
1255
|
+
icon: "fa-font",
|
|
1256
|
+
label: "Text",
|
|
1257
|
+
defaultEnabled: true
|
|
1258
|
+
}, {
|
|
1259
|
+
id: "ExtendedText",
|
|
1260
|
+
icon: "fa-align-left",
|
|
1261
|
+
label: "Extended Text",
|
|
1262
|
+
defaultEnabled: true
|
|
1263
|
+
}, {
|
|
1264
|
+
id: "Image",
|
|
1265
|
+
icon: "fa-image",
|
|
1266
|
+
label: "Image",
|
|
1267
|
+
defaultEnabled: true
|
|
1268
|
+
}, {
|
|
1269
|
+
id: "Video",
|
|
1270
|
+
icon: "fa-video-camera",
|
|
1271
|
+
label: "Video",
|
|
1272
|
+
defaultEnabled: true
|
|
1273
|
+
}, {
|
|
1274
|
+
id: "Album",
|
|
1275
|
+
icon: "fa-th-large",
|
|
1276
|
+
label: "Album",
|
|
1277
|
+
defaultEnabled: true
|
|
1278
|
+
}, {
|
|
1279
|
+
id: "Document",
|
|
1280
|
+
icon: "fa-file",
|
|
1281
|
+
label: "Document",
|
|
1282
|
+
defaultEnabled: false
|
|
1283
|
+
}, {
|
|
1284
|
+
id: "Contact",
|
|
1285
|
+
icon: "fa-address-card",
|
|
1286
|
+
label: "Contact",
|
|
1287
|
+
defaultEnabled: false
|
|
1288
|
+
}, {
|
|
1289
|
+
id: "Template",
|
|
1290
|
+
icon: "fa-th-list",
|
|
1291
|
+
label: "Template",
|
|
1292
|
+
defaultEnabled: false
|
|
1293
|
+
}, {
|
|
1294
|
+
id: "Interactive",
|
|
1295
|
+
icon: "fa-hand-o-up",
|
|
1296
|
+
label: "Interactive",
|
|
1297
|
+
defaultEnabled: false
|
|
1298
|
+
}, {
|
|
1299
|
+
id: "Location",
|
|
1300
|
+
icon: "fa-map-marker",
|
|
1301
|
+
label: "Location",
|
|
1302
|
+
defaultEnabled: false
|
|
1303
|
+
}, {
|
|
1304
|
+
id: "LiveLocation",
|
|
1305
|
+
icon: "fa-location-arrow",
|
|
1306
|
+
label: "Live Location",
|
|
1307
|
+
defaultEnabled: false
|
|
1308
|
+
}, ];
|
|
1309
|
+
|
|
1310
|
+
</script>
|
|
1311
|
+
<!-- logger -->
|
|
1312
|
+
<style>
|
|
1313
|
+
.editorgroupborder {
|
|
1314
|
+
border-width: 1px;
|
|
1315
|
+
border-style: solid;
|
|
1316
|
+
border-color: lightgray;
|
|
1317
|
+
padding: 3px;
|
|
1318
|
+
margin-top: 2px;
|
|
1319
|
+
margin-bottom: 2px;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
.editorsectionheading {
|
|
1323
|
+
font-weight: 600;
|
|
1324
|
+
margin-bottom: 1px !important;
|
|
1325
|
+
margin-top: 6px !important;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
.nomargin {
|
|
1329
|
+
margin-bottom: 1px !important;
|
|
1330
|
+
margin-top: 1px !important;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
.slidecontainer {
|
|
1334
|
+
width: 100%;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
.slider {
|
|
1338
|
+
-webkit-appearance: none;
|
|
1339
|
+
appearance: none;
|
|
1340
|
+
width: 100%;
|
|
1341
|
+
height: 25px;
|
|
1342
|
+
background: #d3d3d3;
|
|
1343
|
+
outline: none;
|
|
1344
|
+
opacity: 0.7;
|
|
1345
|
+
-webkit-transition: .2s;
|
|
1346
|
+
transition: opacity .2s;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.slider:hover {
|
|
1350
|
+
opacity: 1;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
.slider::-webkit-slider-thumb {
|
|
1354
|
+
-webkit-appearance: none;
|
|
1355
|
+
appearance: none;
|
|
1356
|
+
width: 25px;
|
|
1357
|
+
height: 25px;
|
|
1358
|
+
background: #04AA6D;
|
|
1359
|
+
cursor: pointer;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.slider::-moz-range-thumb {
|
|
1363
|
+
width: 25px;
|
|
1364
|
+
height: 25px;
|
|
1365
|
+
background: #04AA6D;
|
|
1366
|
+
cursor: pointer;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
.towb_editorlabel {
|
|
1370
|
+
width: 120px !important;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.towb_editorfield {
|
|
1374
|
+
width: 70% !important;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
.towb_editorfield_short {
|
|
1378
|
+
width: 30% !important;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
</style>
|
|
1382
|
+
<script type="text/javascript">
|
|
1383
|
+
// CSS
|
|
1384
|
+
dropdownStyles = `
|
|
1385
|
+
.loggertype-dropdown {
|
|
1386
|
+
position: absolute;
|
|
1387
|
+
background: white;
|
|
1388
|
+
border: 1px solid #ccc;
|
|
1389
|
+
border-radius: 4px;
|
|
1390
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
1391
|
+
max-height: 150px;
|
|
1392
|
+
overflow-y: auto;
|
|
1393
|
+
z-index: 1000;
|
|
1394
|
+
min-width: 150px;
|
|
1395
|
+
}
|
|
1396
|
+
.loggertype-dropdown .dropdown-item {
|
|
1397
|
+
padding: 6px 12px;
|
|
1398
|
+
cursor: pointer;
|
|
1399
|
+
}
|
|
1400
|
+
.loggertype-dropdown .dropdown-item:hover {
|
|
1401
|
+
background: #f0f0f0;
|
|
1402
|
+
}
|
|
1403
|
+
`;
|
|
1404
|
+
$('<style>').text(dropdownStyles).appendTo('head');
|
|
1405
|
+
|
|
1406
|
+
</script>
|
|
1407
|
+
<script type="text/javascript">
|
|
1408
|
+
RED.nodes.registerType('WhatsappReceiveMessageNode', {
|
|
1409
|
+
category: 'whatsapp',
|
|
1410
|
+
icon: 'whatsapp.png',
|
|
1411
|
+
color: '#E6FFDA',
|
|
1412
|
+
label: function() {
|
|
1413
|
+
return this.name || "WhatsappReceiveMessage";
|
|
1414
|
+
},
|
|
1415
|
+
paletteLabel: 'WhatsApp Receive Message',
|
|
1416
|
+
inputs: 0,
|
|
1417
|
+
inputLabels: (i) => 'undefined',
|
|
1418
|
+
outputs: 1,
|
|
1419
|
+
outputLabels: (i) => ['message'][i],
|
|
1420
|
+
defaults: {
|
|
1421
|
+
name: {
|
|
1422
|
+
value: 'Receive Whatsapp Message',
|
|
1423
|
+
required: true
|
|
1424
|
+
},
|
|
1425
|
+
groupConfig: {
|
|
1426
|
+
type: 'WhatsappGroupConfigNode',
|
|
1427
|
+
required: true
|
|
1428
|
+
},
|
|
1429
|
+
eventOutputKey: {
|
|
1430
|
+
value: 'payload'
|
|
1431
|
+
},
|
|
1432
|
+
eventOutputType: {
|
|
1433
|
+
value: 'msg'
|
|
1434
|
+
},
|
|
1435
|
+
acceptOwnMessages: {
|
|
1436
|
+
value: false
|
|
1437
|
+
},
|
|
1438
|
+
accepts: {
|
|
1439
|
+
value: '[]'
|
|
1440
|
+
},
|
|
1441
|
+
logEnabled: {
|
|
1442
|
+
value: false,
|
|
1443
|
+
required: true
|
|
1444
|
+
},
|
|
1445
|
+
logger: {
|
|
1446
|
+
required: false,
|
|
1447
|
+
value: '',
|
|
1448
|
+
type: 'DelegatedConfigReferenceNode'
|
|
1449
|
+
},
|
|
1450
|
+
logTemplateOverrideEnabled: {
|
|
1451
|
+
value: false
|
|
1452
|
+
},
|
|
1453
|
+
logTemplateOverride: {
|
|
1454
|
+
value: 'message:{{msg}}'
|
|
1455
|
+
},
|
|
1456
|
+
metricsEnabled: {
|
|
1457
|
+
value: false,
|
|
1458
|
+
required: true
|
|
1459
|
+
},
|
|
1460
|
+
metricsReference: {
|
|
1461
|
+
type: 'MetricsConfigNode',
|
|
1462
|
+
required: false
|
|
1463
|
+
},
|
|
1464
|
+
},
|
|
1465
|
+
oneditprepare: function() {
|
|
1466
|
+
// **** WhatsappReceiveMessageNode **** //
|
|
1467
|
+
{
|
|
1468
|
+
let node = this;
|
|
1469
|
+
$("#node-input-eventOutputKey").typedInput({
|
|
1470
|
+
types: ["msg", "flow", "global"],
|
|
1471
|
+
typeField: "#node-input-eventOutputType"
|
|
1472
|
+
});
|
|
1473
|
+
let accepts = [];
|
|
1474
|
+
try {
|
|
1475
|
+
accepts = JSON.parse(node.accepts || "[]");
|
|
1476
|
+
} catch (e) {}
|
|
1477
|
+
let grid = $('<div style="display:grid; grid-template-columns:1fr 1fr; gap:4px 8px; padding:4px 0;"></div>');
|
|
1478
|
+
$("#wa-accept-rows").append(grid);
|
|
1479
|
+
WA_ACCEPT_FIELDS.forEach(function(field) {
|
|
1480
|
+
let saved = accepts.find(function(a) {
|
|
1481
|
+
return a.id === field.id;
|
|
1482
|
+
}) || {
|
|
1483
|
+
enabled: field.defaultEnabled
|
|
1484
|
+
};
|
|
1485
|
+
let cell = $('<div style="display:flex; align-items:center; gap:6px;">' + '<input type="checkbox" id="wa-accept-' + field.id + '" style="width:auto; margin:0; flex-shrink:0;">' + '<label for="wa-accept-' + field.id + '" style="margin:0; width:auto;"><i class="fa ' + field.icon + '"></i> ' + field.label + '</label>' + '</div>');
|
|
1486
|
+
grid.append(cell);
|
|
1487
|
+
$("#wa-accept-" + field.id).prop("checked", saved.enabled);
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
// **** logger **** //
|
|
1491
|
+
{
|
|
1492
|
+
let node = this;
|
|
1493
|
+
// controls.
|
|
1494
|
+
let logEnabled = $("#node-input-logEnabled");
|
|
1495
|
+
let logTemplateEnabled = $("#node-input-logTemplateOverrideEnabled");
|
|
1496
|
+
let logTemplateEnabledSection = $("#section_logTemplateOverrideEnabled");
|
|
1497
|
+
let logEnabledSection = $("#section_logEnabled");
|
|
1498
|
+
// functions.
|
|
1499
|
+
let getLoggerTypes = async function() {
|
|
1500
|
+
let types = [];
|
|
1501
|
+
await $.ajax({
|
|
1502
|
+
url: "nodetypeservice/find?tag=LoggerType",
|
|
1503
|
+
type: "GET",
|
|
1504
|
+
contentType: "application/json; charset=utf-8",
|
|
1505
|
+
data: JSON.stringify({
|
|
1506
|
+
id: node.id
|
|
1507
|
+
}),
|
|
1508
|
+
success: (response) => {
|
|
1509
|
+
types = response;
|
|
1510
|
+
},
|
|
1511
|
+
error: (jqXHR, textStatus, errorThrown) => {
|
|
1512
|
+
console.log(jqXHR, textStatus, errorThrown);
|
|
1513
|
+
},
|
|
1514
|
+
});
|
|
1515
|
+
return types;
|
|
1516
|
+
}
|
|
1517
|
+
let getLoggers = async () => {
|
|
1518
|
+
let loggers = [];
|
|
1519
|
+
// get the list of types again.
|
|
1520
|
+
let loggerTypes = (await getLoggerTypes()).map(type => type.id);
|
|
1521
|
+
// add all of the known loggers.
|
|
1522
|
+
RED.nodes.eachConfig(cfg => {
|
|
1523
|
+
if (loggerTypes.includes(cfg.type)) {
|
|
1524
|
+
loggers.push({
|
|
1525
|
+
id: cfg.id,
|
|
1526
|
+
name: cfg.label(),
|
|
1527
|
+
type: cfg.type
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
return loggers;
|
|
1532
|
+
};
|
|
1533
|
+
let updateLoggerSelectorList = async () => {
|
|
1534
|
+
loggerSelector.empty();
|
|
1535
|
+
(await getLoggers()).forEach(logger => {
|
|
1536
|
+
$('<option value="' + logger.id + '">' + logger.name + '</option>').appendTo(loggerSelector);
|
|
1537
|
+
});
|
|
1538
|
+
$('<option value="_ADD_">none</option>').appendTo(loggerSelector);
|
|
1539
|
+
}
|
|
1540
|
+
logEnabled.on('change', function() {
|
|
1541
|
+
if (logEnabled.prop("checked")) {
|
|
1542
|
+
logEnabledSection.show();
|
|
1543
|
+
node._def.defaults.logger.required = true;
|
|
1544
|
+
} else {
|
|
1545
|
+
logEnabledSection.hide()
|
|
1546
|
+
node._def.defaults.logger.required = false;
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
logTemplateEnabled.on('change', function() {
|
|
1550
|
+
if (logTemplateEnabled.prop("checked")) {
|
|
1551
|
+
logTemplateEnabledSection.show();
|
|
1552
|
+
node._def.defaults.logTemplateOverride.required = true;
|
|
1553
|
+
} else {
|
|
1554
|
+
logTemplateEnabledSection.hide()
|
|
1555
|
+
node._def.defaults.logTemplateOverride.required = false;
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
node.logTemplateOverrideEditor = RED.editor.createEditor({
|
|
1559
|
+
id: 'node-input-logTemplateOverrideEditor',
|
|
1560
|
+
mode: 'ace/mode/handlebars',
|
|
1561
|
+
value: node.logTemplateOverride
|
|
1562
|
+
});
|
|
1563
|
+
let loggerSelector = $("#logger-selector");
|
|
1564
|
+
let loggerEditButton = $("#logger-edit-btn");
|
|
1565
|
+
let loggerAddButton = $("#logger-add-btn");
|
|
1566
|
+
loggerEditButton.on('click', async function(event) {
|
|
1567
|
+
event.stopPropagation();
|
|
1568
|
+
if (loggerEditButton.hasClass("disabled")) {
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
// figure out what type of logger this is.
|
|
1572
|
+
let selectedLogger = (await getLoggers()).find(logger => logger.id === loggerSelector.val());
|
|
1573
|
+
RED.editor.editConfig("#logger-selector", selectedLogger.type, selectedLogger.id);
|
|
1574
|
+
});
|
|
1575
|
+
loggerAddButton.on('click', function(event) {
|
|
1576
|
+
event.stopPropagation();
|
|
1577
|
+
let $btn = $(event.target);
|
|
1578
|
+
// Remove any existing dropdown
|
|
1579
|
+
$('.loggertype-dropdown').remove();
|
|
1580
|
+
getLoggerTypes().then(loggerTypes => {
|
|
1581
|
+
let $dropdown = $('<div class="loggertype-dropdown red-ui-editor"></div>');
|
|
1582
|
+
loggerTypes.forEach(loggerType => {
|
|
1583
|
+
$('<div class="dropdown-item"></div>').text(loggerType.name).data('value', loggerType.id).css({
|
|
1584
|
+
padding: '4px 8px',
|
|
1585
|
+
cursor: 'pointer'
|
|
1586
|
+
}).appendTo($dropdown);
|
|
1587
|
+
});
|
|
1588
|
+
// Append first so outerWidth() is measurable, then position so the
|
|
1589
|
+
// right edge of the dropdown aligns with the right edge of the button.
|
|
1590
|
+
$('body').append($dropdown);
|
|
1591
|
+
$dropdown.css({
|
|
1592
|
+
top: $btn.offset().top + $btn.outerHeight(),
|
|
1593
|
+
left: $btn.offset().left + $btn.outerWidth() - $dropdown.outerWidth()
|
|
1594
|
+
});
|
|
1595
|
+
$(document).on('mousedown.dropdown', function(e) {
|
|
1596
|
+
if (!$(e.target).closest('.loggertype-dropdown').length) {
|
|
1597
|
+
$dropdown.remove();
|
|
1598
|
+
$(document).off('mousedown.dropdown'); // Clean up the listener
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
// Handle item selection
|
|
1602
|
+
$dropdown.on('click', '.dropdown-item', function(e) {
|
|
1603
|
+
e.stopPropagation();
|
|
1604
|
+
const selectedValue = $(this).data('value');
|
|
1605
|
+
const selectedText = $(this).text();
|
|
1606
|
+
// Do whatever you need with the selection
|
|
1607
|
+
console.log('Selected:', selectedValue, selectedText);
|
|
1608
|
+
$dropdown.remove();
|
|
1609
|
+
RED.editor.editConfig("#logger-selector", selectedValue, "_ADD_");
|
|
1610
|
+
});
|
|
1611
|
+
})
|
|
1612
|
+
});
|
|
1613
|
+
loggerSelector.on("focus", updateLoggerSelectorList);
|
|
1614
|
+
loggerSelector.on('change', function(event) {
|
|
1615
|
+
if ("_ADD_" === loggerSelector.val()) {
|
|
1616
|
+
loggerEditButton.addClass("disabled")
|
|
1617
|
+
} else {
|
|
1618
|
+
loggerEditButton.removeClass("disabled")
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
// lastly, make sure that we set the correct value on the logger selector.
|
|
1622
|
+
updateLoggerSelectorList().then(() => {
|
|
1623
|
+
loggerSelector.val(node.logger);
|
|
1624
|
+
loggerSelector.trigger('change');
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
// **** metrics **** //
|
|
1628
|
+
{
|
|
1629
|
+
let node = this;
|
|
1630
|
+
let metricsEnabled = $("#node-input-metricsEnabled");
|
|
1631
|
+
let metricsEnabledSection = $("#section_metricsEnabled");
|
|
1632
|
+
metricsEnabled.on('change', function() {
|
|
1633
|
+
if (metricsEnabled.prop("checked")) {
|
|
1634
|
+
metricsEnabledSection.show();
|
|
1635
|
+
node._def.defaults.metricsReference.required = true;
|
|
1636
|
+
} else {
|
|
1637
|
+
metricsEnabledSection.hide()
|
|
1638
|
+
node._def.defaults.metricsReference.required = false;
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
},
|
|
1643
|
+
oneditsave: function() {
|
|
1644
|
+
// **** WhatsappReceiveMessageNode **** //
|
|
1645
|
+
{
|
|
1646
|
+
let accepts = WA_ACCEPT_FIELDS.map(function(field) {
|
|
1647
|
+
return {
|
|
1648
|
+
id: field.id,
|
|
1649
|
+
enabled: $("#wa-accept-" + field.id).prop("checked"),
|
|
1650
|
+
};
|
|
1651
|
+
});
|
|
1652
|
+
$("#node-input-accepts").val(JSON.stringify(accepts));
|
|
1653
|
+
}
|
|
1654
|
+
// **** logger **** //
|
|
1655
|
+
{
|
|
1656
|
+
node = this;
|
|
1657
|
+
var selectedLogger = $("#logger-selector").val();
|
|
1658
|
+
node.logger = (selectedLogger && selectedLogger !== "_ADD_") ? selectedLogger : '';
|
|
1659
|
+
node.logTemplateOverride = node.logTemplateOverrideEditor.getValue();
|
|
1660
|
+
node.logTemplateOverrideEditor.destroy();
|
|
1661
|
+
delete node.logTemplateOverrideEditor;
|
|
1662
|
+
}
|
|
1663
|
+
// **** metrics **** //
|
|
1664
|
+
{}
|
|
1665
|
+
},
|
|
1666
|
+
oneditcancel: function() {},
|
|
1667
|
+
oneditdelete: function() {},
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
</script>
|
|
1671
|
+
|
|
1672
|
+
|
|
1673
|
+
<script type="text/html" data-template-name='WhatsappReceiveMessageNode'>
|
|
1674
|
+
<div id='section_WhatsappReceiveMessageNode'>
|
|
1675
|
+
<div class="form-row">
|
|
1676
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
1677
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
1678
|
+
</div>
|
|
1679
|
+
|
|
1680
|
+
<div class="form-row editorsectionheading">
|
|
1681
|
+
<i class="w-16 fa fa-envelope"></i> <span>WhatsApp Message</span>
|
|
1682
|
+
</div>
|
|
1683
|
+
|
|
1684
|
+
<div class="editorgroupborder">
|
|
1685
|
+
|
|
1686
|
+
<div class="form-row nomargin">
|
|
1687
|
+
<label for="node-input-groupConfig" style="width:120px;"><i class="fa fa-users"></i> Group</label>
|
|
1688
|
+
<input type="text" id="node-input-groupConfig" placeholder="config">
|
|
1689
|
+
</div>
|
|
1690
|
+
|
|
1691
|
+
<div class="form-row nomargin">
|
|
1692
|
+
<label for="node-input-acceptOwnMessages" style="width:120px;"><i class="fa fa-reply"></i> Own Messages</label>
|
|
1693
|
+
<input type="checkbox" id="node-input-acceptOwnMessages" style="width:auto; margin:0; vertical-align:middle;">
|
|
1694
|
+
</div>
|
|
1695
|
+
|
|
1696
|
+
<div class="form-row nomargin">
|
|
1697
|
+
<label for="node-input-eventOutputKey" style="width:120px;"><i class="fa fa-arrow-right"></i> Output Path</label>
|
|
1698
|
+
<input type="text" id="node-input-eventOutputKey" placeholder="payload">
|
|
1699
|
+
<input type="hidden" id="node-input-eventOutputType">
|
|
1700
|
+
</div>
|
|
1701
|
+
|
|
1702
|
+
</div>
|
|
1703
|
+
|
|
1704
|
+
<div class="form-row editorsectionheading">
|
|
1705
|
+
<i class="w-16 fa fa-filter"></i> <span>Accept</span>
|
|
1706
|
+
</div>
|
|
1707
|
+
|
|
1708
|
+
<div class="editorgroupborder" id="wa-accept-rows">
|
|
1709
|
+
</div>
|
|
1710
|
+
|
|
1711
|
+
<input type="hidden" id="node-input-accepts">
|
|
1712
|
+
|
|
1713
|
+
|
|
1714
|
+
</div>
|
|
1715
|
+
|
|
1716
|
+
<div id='section_logger'>
|
|
1717
|
+
<div id="section_loggerconfig">
|
|
1718
|
+
<div class="form-row editorsectionheading">
|
|
1719
|
+
<i class="w-16 fa fa-eye"></i> <span>Logging</span>
|
|
1720
|
+
</div>
|
|
1721
|
+
<div class="editorgroupborder">
|
|
1722
|
+
<div class="form-row nomargin logEnableCheck">
|
|
1723
|
+
<input type="checkbox" id="node-input-logEnabled" placeholder="logEnabled" value="false" style="margin:8px 0 10px 102px; width:20px;">
|
|
1724
|
+
<label style="width:auto" for="node-input-logEnabled"><span>Enable Logging</span></label>
|
|
1725
|
+
</div>
|
|
1726
|
+
|
|
1727
|
+
<div id="section_logEnabled">
|
|
1728
|
+
<div class="form-row nomargin">
|
|
1729
|
+
<label for="logger-selector">Logger</label>
|
|
1730
|
+
<div style="width: 70%; display: inline-flex;">
|
|
1731
|
+
<select id="logger-selector" style="flex-grow: 1;" class="">
|
|
1732
|
+
<option value="_ADD_">none</option>
|
|
1733
|
+
</select>
|
|
1734
|
+
<a id="logger-edit-btn" class="red-ui-button disabled" style="margin-left: 10px;">
|
|
1735
|
+
<i class="fa fa-pencil"></i>
|
|
1736
|
+
</a>
|
|
1737
|
+
<a id="logger-add-btn" class="red-ui-button" style="margin-left: 10px;">
|
|
1738
|
+
<i class="fa fa-plus"></i>
|
|
1739
|
+
</a>
|
|
1740
|
+
</div>
|
|
1741
|
+
</div>
|
|
1742
|
+
|
|
1743
|
+
<div class="form-row nomargin">
|
|
1744
|
+
<input type="checkbox" id="node-input-logTemplateOverrideEnabled" placeholder="" value="false" style="margin:8px 0 10px 102px; width:20px;">
|
|
1745
|
+
<label style="width:auto" for="node-input-logTemplateOverrideEnabled"><span>Override Template</span></label>
|
|
1746
|
+
</div>
|
|
1747
|
+
|
|
1748
|
+
<div id="section_logTemplateOverrideEnabled">
|
|
1749
|
+
<div class="form-row">
|
|
1750
|
+
<label class="towb_editorlabel" for="node-input-logTemplateOverrideEditor"><i class="fa fa-code"></i> Template</label>
|
|
1751
|
+
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-logTemplateOverrideEditor"></div>
|
|
1752
|
+
</div>
|
|
1753
|
+
</div>
|
|
1754
|
+
</div>
|
|
1755
|
+
</div>
|
|
1756
|
+
</div>
|
|
1757
|
+
|
|
1758
|
+
</div>
|
|
1759
|
+
|
|
1760
|
+
<div id='section_metrics'>
|
|
1761
|
+
<div id="section_metricsconfig">
|
|
1762
|
+
<div class="form-row editorsectionheading">
|
|
1763
|
+
<i class="w-16 fa fa-eye"></i> <span>Metrics</span>
|
|
1764
|
+
</div>
|
|
1765
|
+
<div class="editorgroupborder">
|
|
1766
|
+
<div class="form-row nomargin metricsEnableCheck">
|
|
1767
|
+
<input type="checkbox" id="node-input-metricsEnabled" value="false" style="margin:8px 0 10px 102px; width:20px;" />
|
|
1768
|
+
<label style="width:auto" for="node-input-metricsEnabled"><span>Enable Metrics</span></label>
|
|
1769
|
+
</div>
|
|
1770
|
+
|
|
1771
|
+
<div id="section_metricsEnabled">
|
|
1772
|
+
<div class="form-row nomargin">
|
|
1773
|
+
<label class="towb_editorlabel" for="node-input-metricsReference">Metric Collector</label>
|
|
1774
|
+
<input id="node-input-metricsReference" placeholder="metrics" />
|
|
1775
|
+
</div>
|
|
1776
|
+
</div>
|
|
1777
|
+
</div>
|
|
1778
|
+
</div>
|
|
1779
|
+
|
|
1780
|
+
</div>
|
|
1781
|
+
</script>
|
|
1782
|
+
|
|
1783
|
+
<script type="text/markdown" data-help-name='WhatsappReceiveMessageNode'>
|
|
1784
|
+
> **Early development** — this package is still maturing. Some features may be incomplete and diagnostic log output is intentionally verbose for now.
|
|
1785
|
+
|
|
1786
|
+
Receives messages from a WhatsApp group and outputs them as Node-RED messages.
|
|
1787
|
+
|
|
1788
|
+
### Properties
|
|
1789
|
+
|
|
1790
|
+
: *name* (string) : Display label for this node.
|
|
1791
|
+
: *group* (config) : The WhatsApp Group config node to receive from.
|
|
1792
|
+
: *own messages* (bool) : Whether to forward messages sent by this account.
|
|
1793
|
+
: *output path* (typed) : Where in the output `msg` to place the WhatsApp message, or which context variable to write to.
|
|
1794
|
+
|
|
1795
|
+
### Accept filters
|
|
1796
|
+
|
|
1797
|
+
Each message type can be individually enabled or disabled. By default Text, Extended Text, Image, Video, and Album messages are accepted.
|
|
1798
|
+
|
|
1799
|
+
### Outputs
|
|
1800
|
+
|
|
1801
|
+
: *msg* : Output message. The WhatsApp message is placed at the configured output path.
|
|
1802
|
+
|
|
1803
|
+
### Logging
|
|
1804
|
+
: *enable logging* (boolean) : if checked, then the node will produce logging output to the specified logger.
|
|
1805
|
+
: *logger* (logconfig) : the endppoint that the node will log events to.
|
|
1806
|
+
: *override template* (logconfig) : if checked, a custom logging template can be provided for the log message.
|
|
1807
|
+
: *logger template* (mustache) : a template in mustache format to generate a message for logging purposes (see LoggerConfig node for more information)
|
|
1808
|
+
|
|
1809
|
+
### Metrics
|
|
1810
|
+
: *enable metrics* (boolean) : if checked, then the node will produce metrics output to the specified metrics provider.
|
|
1811
|
+
: *metric collector* (metricconfig) : the collection point or grouping where this particular metric will be attached.
|
|
1812
|
+
</script>
|
|
1813
|
+
|
|
1814
|
+
|