@jep182/n8n-nodes-whatsthat 0.3.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ Use this node to:
|
|
|
27
27
|
When pairing is available, the node returns:
|
|
28
28
|
|
|
29
29
|
- `pairingCode`
|
|
30
|
+
- `qrCodeUrl`
|
|
30
31
|
- `qr`
|
|
31
32
|
- `qrDataUrl`
|
|
32
33
|
|
|
@@ -85,15 +86,19 @@ WhatsThat embeds Baileys directly in n8n.
|
|
|
85
86
|
```
|
|
86
87
|
|
|
87
88
|
3. Add `WhatsThat Session`.
|
|
88
|
-
4. Choose `
|
|
89
|
+
4. Choose `Ensure Session`, then provide:
|
|
89
90
|
- `Session ID (Internal)`: a stable unique ID such as `main-phone`
|
|
90
91
|
- `Label (Visible Name)`: a human-readable name such as `Luca personal phone`
|
|
91
92
|
- optional `Phone Number For Pairing`: full number with country code, digits only, without `00` or `+`
|
|
92
|
-
5.
|
|
93
|
-
6. Use the returned `pairingCode` or `qrDataUrl` to connect the device.
|
|
93
|
+
5. Set `Return When` to `Pairing Is Ready Or Connected` for first-time pairing.
|
|
94
|
+
6. Use the returned `pairingCode`, `qrCodeUrl`, or `qrDataUrl` to connect the device.
|
|
94
95
|
7. Use `WhatsThat Targets` to discover and link chats/groups.
|
|
95
96
|
8. Use `WhatsThat Message` to send messages by alias or raw JID.
|
|
96
97
|
|
|
98
|
+
Example workflow:
|
|
99
|
+
|
|
100
|
+
- [`examples/register-number.workflow.json`](./examples/register-number.workflow.json)
|
|
101
|
+
|
|
97
102
|
## Media Delivery
|
|
98
103
|
|
|
99
104
|
For `Image` and `Video` messages:
|
|
@@ -13,7 +13,7 @@ class WhatsThatSession {
|
|
|
13
13
|
icon: 'file:whatsthat.svg',
|
|
14
14
|
group: ['transform'],
|
|
15
15
|
version: 1,
|
|
16
|
-
description: '
|
|
16
|
+
description: 'Create, connect, and manage embedded Baileys sessions for WhatsThat',
|
|
17
17
|
defaults: { name: 'WhatsThat Session' },
|
|
18
18
|
inputs: ['main'],
|
|
19
19
|
outputs: ['main'],
|
|
@@ -23,10 +23,9 @@ class WhatsThatSession {
|
|
|
23
23
|
displayName: 'Operation',
|
|
24
24
|
name: 'operation',
|
|
25
25
|
type: 'options',
|
|
26
|
-
default: '
|
|
26
|
+
default: 'ensure',
|
|
27
27
|
options: [
|
|
28
|
-
{ name: '
|
|
29
|
-
{ name: 'Connect Session', value: 'connect' },
|
|
28
|
+
{ name: 'Ensure Session', value: 'ensure' },
|
|
30
29
|
{ name: 'List Sessions', value: 'list' },
|
|
31
30
|
{ name: 'Get Session Status', value: 'status' },
|
|
32
31
|
{ name: 'Disconnect Session', value: 'disconnect' },
|
|
@@ -51,7 +50,7 @@ class WhatsThatSession {
|
|
|
51
50
|
default: '',
|
|
52
51
|
description: 'Human-readable name shown in results. Example: "Luca personal phone" or "Support number".',
|
|
53
52
|
displayOptions: {
|
|
54
|
-
show: { operation: ['
|
|
53
|
+
show: { operation: ['ensure'] },
|
|
55
54
|
},
|
|
56
55
|
},
|
|
57
56
|
{
|
|
@@ -61,7 +60,34 @@ class WhatsThatSession {
|
|
|
61
60
|
default: '',
|
|
62
61
|
description: 'Optional. Full phone number with country code, digits only, without 00 or +. Example: 393331234567.',
|
|
63
62
|
displayOptions: {
|
|
64
|
-
show: { operation: ['
|
|
63
|
+
show: { operation: ['ensure'] },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
displayName: 'Return When',
|
|
68
|
+
name: 'waitFor',
|
|
69
|
+
type: 'options',
|
|
70
|
+
default: 'pairing_or_connected',
|
|
71
|
+
options: [
|
|
72
|
+
{ name: 'Pairing Is Ready Or Connected', value: 'pairing_or_connected' },
|
|
73
|
+
{ name: 'Connected', value: 'connected' },
|
|
74
|
+
],
|
|
75
|
+
description: 'Return as soon as pairing data is ready, or wait until the session is fully connected.',
|
|
76
|
+
displayOptions: {
|
|
77
|
+
show: { operation: ['ensure'] },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
displayName: 'Timeout Seconds',
|
|
82
|
+
name: 'timeoutSeconds',
|
|
83
|
+
type: 'number',
|
|
84
|
+
typeOptions: {
|
|
85
|
+
minValue: 1,
|
|
86
|
+
},
|
|
87
|
+
default: 20,
|
|
88
|
+
description: 'Maximum time to wait before returning the latest known session status.',
|
|
89
|
+
displayOptions: {
|
|
90
|
+
show: { operation: ['ensure'] },
|
|
65
91
|
},
|
|
66
92
|
},
|
|
67
93
|
],
|
|
@@ -77,19 +103,22 @@ class WhatsThatSession {
|
|
|
77
103
|
const rawSessionId = this.getNodeParameter('sessionId', itemIndex, '');
|
|
78
104
|
const sessionId = operation === 'list' ? (0, validation_1.normalizeSessionId)(rawSessionId) : (0, validation_1.requireSessionId)(rawSessionId);
|
|
79
105
|
let json;
|
|
80
|
-
const label = this.getNodeParameter('label', itemIndex, '').trim();
|
|
81
|
-
const phoneNumberForPairing = this.getNodeParameter('phoneNumberForPairing', itemIndex, '').trim();
|
|
82
106
|
switch (operation) {
|
|
83
|
-
case '
|
|
84
|
-
|
|
107
|
+
case 'ensure': {
|
|
108
|
+
const label = this.getNodeParameter('label', itemIndex, '').trim();
|
|
109
|
+
const phoneNumberForPairing = this.getNodeParameter('phoneNumberForPairing', itemIndex, '').trim();
|
|
110
|
+
const waitFor = this.getNodeParameter('waitFor', itemIndex);
|
|
111
|
+
const timeoutSeconds = this.getNodeParameter('timeoutSeconds', itemIndex, 20);
|
|
112
|
+
json = await runtime_1.registry.ensureConnectedSession(access.paths.root, access, {
|
|
85
113
|
sessionId,
|
|
86
114
|
label: label || sessionId,
|
|
87
115
|
phoneNumberForPairing,
|
|
116
|
+
}, {
|
|
117
|
+
waitFor,
|
|
118
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
88
119
|
});
|
|
89
120
|
break;
|
|
90
|
-
|
|
91
|
-
json = await runtime_1.registry.connectSession(access.paths.root, access, sessionId);
|
|
92
|
-
break;
|
|
121
|
+
}
|
|
93
122
|
case 'list':
|
|
94
123
|
json = await runtime_1.registry.listSessions(access);
|
|
95
124
|
break;
|
package/dist/shared/runtime.d.ts
CHANGED
|
@@ -46,6 +46,14 @@ declare class WhatsThatRegistry extends EventEmitter {
|
|
|
46
46
|
phoneNumberForPairing?: string;
|
|
47
47
|
}): Promise<SessionRecord>;
|
|
48
48
|
connectSession(storageRoot: string, access: DataAccess, sessionId: string): Promise<SessionRecord>;
|
|
49
|
+
ensureConnectedSession(storageRoot: string, access: DataAccess, input: {
|
|
50
|
+
sessionId: string;
|
|
51
|
+
label: string;
|
|
52
|
+
phoneNumberForPairing?: string;
|
|
53
|
+
}, options?: {
|
|
54
|
+
waitFor?: 'pairing_or_connected' | 'connected';
|
|
55
|
+
timeoutMs?: number;
|
|
56
|
+
}): Promise<SessionRecord>;
|
|
49
57
|
listSessions(access: DataAccess): Promise<SessionRecord[]>;
|
|
50
58
|
getSession(access: DataAccess, sessionId: string): Promise<SessionRecord | undefined>;
|
|
51
59
|
disconnectSession(access: DataAccess, sessionId: string): Promise<SessionRecord | undefined>;
|
|
@@ -61,7 +69,10 @@ declare class WhatsThatRegistry extends EventEmitter {
|
|
|
61
69
|
private upsertSession;
|
|
62
70
|
private upsertDiscoveredTarget;
|
|
63
71
|
private emitRuntime;
|
|
72
|
+
private waitForSessionState;
|
|
73
|
+
private matchesWaitTarget;
|
|
64
74
|
private required;
|
|
75
|
+
private buildQrCodeUrl;
|
|
65
76
|
}
|
|
66
77
|
export declare const registry: WhatsThatRegistry;
|
|
67
78
|
export declare function extractMessageText(message: proto.IMessage | null | undefined): string | undefined;
|
package/dist/shared/runtime.js
CHANGED
|
@@ -98,10 +98,12 @@ class WhatsThatRegistry extends node_events_1.EventEmitter {
|
|
|
98
98
|
if (update.qr) {
|
|
99
99
|
qrcode_terminal_1.default.generate(update.qr, { small: true });
|
|
100
100
|
const qrDataUrl = await qrcode_1.default.toDataURL(update.qr);
|
|
101
|
+
const qrCodeUrl = this.buildQrCodeUrl(update.qr);
|
|
101
102
|
const pairingRecord = {
|
|
102
103
|
...current,
|
|
103
104
|
status: 'pairing',
|
|
104
105
|
qr: update.qr,
|
|
106
|
+
qrCodeUrl,
|
|
105
107
|
qrDataUrl,
|
|
106
108
|
updatedAt: new Date().toISOString(),
|
|
107
109
|
lastSeenAt: new Date().toISOString(),
|
|
@@ -128,6 +130,7 @@ class WhatsThatRegistry extends node_events_1.EventEmitter {
|
|
|
128
130
|
status: 'connected',
|
|
129
131
|
phone: socket.user?.id,
|
|
130
132
|
qr: undefined,
|
|
133
|
+
qrCodeUrl: undefined,
|
|
131
134
|
qrDataUrl: undefined,
|
|
132
135
|
pairingCode: undefined,
|
|
133
136
|
updatedAt: new Date().toISOString(),
|
|
@@ -216,6 +219,17 @@ class WhatsThatRegistry extends node_events_1.EventEmitter {
|
|
|
216
219
|
this.sockets.set(sessionId, socket);
|
|
217
220
|
return (await this.getSession(access, sessionId)) ?? starting;
|
|
218
221
|
}
|
|
222
|
+
async ensureConnectedSession(storageRoot, access, input, options) {
|
|
223
|
+
await this.ensureSession(storageRoot, access, input);
|
|
224
|
+
const current = await this.getSession(access, input.sessionId);
|
|
225
|
+
if (!current) {
|
|
226
|
+
throw new Error(`Unknown session ${input.sessionId}`);
|
|
227
|
+
}
|
|
228
|
+
if (!this.sockets.has(input.sessionId) && current.status !== 'connected') {
|
|
229
|
+
await this.connectSession(storageRoot, access, input.sessionId);
|
|
230
|
+
}
|
|
231
|
+
return this.waitForSessionState(access, input.sessionId, options?.waitFor ?? 'pairing_or_connected', options?.timeoutMs ?? 20000);
|
|
232
|
+
}
|
|
219
233
|
async listSessions(access) {
|
|
220
234
|
const sessions = await access.listSessions();
|
|
221
235
|
return sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
@@ -448,12 +462,61 @@ class WhatsThatRegistry extends node_events_1.EventEmitter {
|
|
|
448
462
|
emitRuntime(event) {
|
|
449
463
|
this.emit('event', event);
|
|
450
464
|
}
|
|
465
|
+
async waitForSessionState(access, sessionId, waitFor, timeoutMs) {
|
|
466
|
+
const current = await this.getSession(access, sessionId);
|
|
467
|
+
if (!current) {
|
|
468
|
+
throw new Error(`Unknown session ${sessionId}`);
|
|
469
|
+
}
|
|
470
|
+
if (this.matchesWaitTarget(current, waitFor)) {
|
|
471
|
+
return current;
|
|
472
|
+
}
|
|
473
|
+
return new Promise((resolve) => {
|
|
474
|
+
let settled = false;
|
|
475
|
+
let timeout;
|
|
476
|
+
const cleanup = () => {
|
|
477
|
+
if (timeout)
|
|
478
|
+
clearTimeout(timeout);
|
|
479
|
+
this.off('event', handler);
|
|
480
|
+
};
|
|
481
|
+
const settle = async () => {
|
|
482
|
+
if (settled)
|
|
483
|
+
return;
|
|
484
|
+
settled = true;
|
|
485
|
+
cleanup();
|
|
486
|
+
resolve((await this.getSession(access, sessionId)) ?? current);
|
|
487
|
+
};
|
|
488
|
+
const handler = async (event) => {
|
|
489
|
+
if (event.sessionId !== sessionId)
|
|
490
|
+
return;
|
|
491
|
+
const latest = await this.getSession(access, sessionId);
|
|
492
|
+
if (latest && this.matchesWaitTarget(latest, waitFor)) {
|
|
493
|
+
await settle();
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
this.on('event', handler);
|
|
497
|
+
timeout = setTimeout(() => {
|
|
498
|
+
void settle();
|
|
499
|
+
}, timeoutMs);
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
matchesWaitTarget(record, waitFor) {
|
|
503
|
+
if (record.status === 'connected') {
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
if (waitFor === 'pairing_or_connected' && record.status === 'pairing') {
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
451
511
|
required(value, field) {
|
|
452
512
|
if (value === undefined || value === null || value === '') {
|
|
453
513
|
throw new Error(`Missing required field ${field}`);
|
|
454
514
|
}
|
|
455
515
|
return value;
|
|
456
516
|
}
|
|
517
|
+
buildQrCodeUrl(qr) {
|
|
518
|
+
return `https://quickchart.io/qr?text=${encodeURIComponent(qr)}`;
|
|
519
|
+
}
|
|
457
520
|
}
|
|
458
521
|
exports.registry = new WhatsThatRegistry();
|
|
459
522
|
function extractMessageText(message) {
|
package/dist/shared/types.d.ts
CHANGED