@jep182/n8n-nodes-whatsthat 0.3.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@ It lets you:
|
|
|
17
17
|
|
|
18
18
|
Use this node to:
|
|
19
19
|
|
|
20
|
-
- create a session
|
|
21
20
|
- connect a session
|
|
21
|
+
- wait for a session to become connected
|
|
22
22
|
- list sessions
|
|
23
23
|
- inspect session status
|
|
24
24
|
- disconnect a session
|
|
@@ -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,14 +86,19 @@ WhatsThat embeds Baileys directly in n8n.
|
|
|
85
86
|
```
|
|
86
87
|
|
|
87
88
|
3. Add `WhatsThat Session`.
|
|
88
|
-
4. Choose `
|
|
89
|
+
4. Choose `Connect 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. Run `
|
|
93
|
-
6.
|
|
94
|
-
7.
|
|
95
|
-
8. Use `WhatsThat
|
|
93
|
+
5. Run the node and use the returned `pairingCode`, `qrCodeUrl`, or `qrDataUrl` to connect the device.
|
|
94
|
+
6. Add another `WhatsThat Session` node with `Ensure Session`.
|
|
95
|
+
7. Set `Return When` to `Connected` so the workflow waits until the already-started session finishes pairing.
|
|
96
|
+
8. Use `WhatsThat Targets` to discover and link chats/groups.
|
|
97
|
+
9. Use `WhatsThat Message` to send messages by alias or raw JID.
|
|
98
|
+
|
|
99
|
+
Example workflow:
|
|
100
|
+
|
|
101
|
+
- [`examples/register-number.workflow.json`](./examples/register-number.workflow.json)
|
|
96
102
|
|
|
97
103
|
## Media Delivery
|
|
98
104
|
|
|
@@ -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,10 @@ class WhatsThatSession {
|
|
|
23
23
|
displayName: 'Operation',
|
|
24
24
|
name: 'operation',
|
|
25
25
|
type: 'options',
|
|
26
|
-
default: '
|
|
26
|
+
default: 'connect',
|
|
27
27
|
options: [
|
|
28
|
-
{ name: 'Create Session', value: 'create' },
|
|
29
28
|
{ name: 'Connect Session', value: 'connect' },
|
|
29
|
+
{ name: 'Ensure Session', value: 'ensure' },
|
|
30
30
|
{ name: 'List Sessions', value: 'list' },
|
|
31
31
|
{ name: 'Get Session Status', value: 'status' },
|
|
32
32
|
{ name: 'Disconnect Session', value: 'disconnect' },
|
|
@@ -51,7 +51,7 @@ class WhatsThatSession {
|
|
|
51
51
|
default: '',
|
|
52
52
|
description: 'Human-readable name shown in results. Example: "Luca personal phone" or "Support number".',
|
|
53
53
|
displayOptions: {
|
|
54
|
-
show: { operation: ['
|
|
54
|
+
show: { operation: ['connect', 'ensure'] },
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
57
|
{
|
|
@@ -61,7 +61,34 @@ class WhatsThatSession {
|
|
|
61
61
|
default: '',
|
|
62
62
|
description: 'Optional. Full phone number with country code, digits only, without 00 or +. Example: 393331234567.',
|
|
63
63
|
displayOptions: {
|
|
64
|
-
show: { operation: ['
|
|
64
|
+
show: { operation: ['connect', 'ensure'] },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
displayName: 'Return When',
|
|
69
|
+
name: 'waitFor',
|
|
70
|
+
type: 'options',
|
|
71
|
+
default: 'pairing_or_connected',
|
|
72
|
+
options: [
|
|
73
|
+
{ name: 'Pairing Is Ready Or Connected', value: 'pairing_or_connected' },
|
|
74
|
+
{ name: 'Connected', value: 'connected' },
|
|
75
|
+
],
|
|
76
|
+
description: 'Return as soon as pairing data is ready, or wait until the session is fully connected.',
|
|
77
|
+
displayOptions: {
|
|
78
|
+
show: { operation: ['ensure'] },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
displayName: 'Timeout Seconds',
|
|
83
|
+
name: 'timeoutSeconds',
|
|
84
|
+
type: 'number',
|
|
85
|
+
typeOptions: {
|
|
86
|
+
minValue: 1,
|
|
87
|
+
},
|
|
88
|
+
default: 20,
|
|
89
|
+
description: 'Maximum time to wait before returning the latest known session status.',
|
|
90
|
+
displayOptions: {
|
|
91
|
+
show: { operation: ['ensure'] },
|
|
65
92
|
},
|
|
66
93
|
},
|
|
67
94
|
],
|
|
@@ -77,19 +104,33 @@ class WhatsThatSession {
|
|
|
77
104
|
const rawSessionId = this.getNodeParameter('sessionId', itemIndex, '');
|
|
78
105
|
const sessionId = operation === 'list' ? (0, validation_1.normalizeSessionId)(rawSessionId) : (0, validation_1.requireSessionId)(rawSessionId);
|
|
79
106
|
let json;
|
|
80
|
-
const label = this.getNodeParameter('label', itemIndex, '').trim();
|
|
81
|
-
const phoneNumberForPairing = this.getNodeParameter('phoneNumberForPairing', itemIndex, '').trim();
|
|
82
107
|
switch (operation) {
|
|
83
|
-
case '
|
|
84
|
-
|
|
108
|
+
case 'connect': {
|
|
109
|
+
const label = this.getNodeParameter('label', itemIndex, '').trim();
|
|
110
|
+
const phoneNumberForPairing = this.getNodeParameter('phoneNumberForPairing', itemIndex, '').trim();
|
|
111
|
+
await runtime_1.registry.ensureSession(access.paths.root, access, {
|
|
85
112
|
sessionId,
|
|
86
113
|
label: label || sessionId,
|
|
87
114
|
phoneNumberForPairing,
|
|
88
115
|
});
|
|
89
|
-
break;
|
|
90
|
-
case 'connect':
|
|
91
116
|
json = await runtime_1.registry.connectSession(access.paths.root, access, sessionId);
|
|
92
117
|
break;
|
|
118
|
+
}
|
|
119
|
+
case 'ensure': {
|
|
120
|
+
const label = this.getNodeParameter('label', itemIndex, '').trim();
|
|
121
|
+
const phoneNumberForPairing = this.getNodeParameter('phoneNumberForPairing', itemIndex, '').trim();
|
|
122
|
+
const waitFor = this.getNodeParameter('waitFor', itemIndex);
|
|
123
|
+
const timeoutSeconds = this.getNodeParameter('timeoutSeconds', itemIndex, 20);
|
|
124
|
+
json = await runtime_1.registry.ensureConnectedSession(access.paths.root, access, {
|
|
125
|
+
sessionId,
|
|
126
|
+
label: label || sessionId,
|
|
127
|
+
phoneNumberForPairing,
|
|
128
|
+
}, {
|
|
129
|
+
waitFor,
|
|
130
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
131
|
+
});
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
93
134
|
case 'list':
|
|
94
135
|
json = await runtime_1.registry.listSessions(access);
|
|
95
136
|
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,11 @@ declare class WhatsThatRegistry extends EventEmitter {
|
|
|
61
69
|
private upsertSession;
|
|
62
70
|
private upsertDiscoveredTarget;
|
|
63
71
|
private emitRuntime;
|
|
72
|
+
private waitForSessionState;
|
|
73
|
+
private matchesWaitTarget;
|
|
74
|
+
waitForConnectedSession(access: DataAccess, sessionId: string, timeoutMs: number): Promise<SessionRecord>;
|
|
64
75
|
private required;
|
|
76
|
+
private buildQrCodeUrl;
|
|
65
77
|
}
|
|
66
78
|
export declare const registry: WhatsThatRegistry;
|
|
67
79
|
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
|
+
throw new Error(`Session ${input.sessionId} is not active. Run Connect Session first and keep the n8n runtime alive until pairing completes.`);
|
|
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,71 @@ 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
|
+
}
|
|
511
|
+
async waitForConnectedSession(access, sessionId, timeoutMs) {
|
|
512
|
+
const current = await this.getSession(access, sessionId);
|
|
513
|
+
if (!current) {
|
|
514
|
+
throw new Error(`Unknown session ${sessionId}`);
|
|
515
|
+
}
|
|
516
|
+
if (!this.sockets.has(sessionId) && current.status !== 'connected') {
|
|
517
|
+
throw new Error(`Session ${sessionId} is not active. Run Connect Session first and keep the n8n runtime alive until pairing completes.`);
|
|
518
|
+
}
|
|
519
|
+
return this.waitForSessionState(access, sessionId, 'connected', timeoutMs);
|
|
520
|
+
}
|
|
451
521
|
required(value, field) {
|
|
452
522
|
if (value === undefined || value === null || value === '') {
|
|
453
523
|
throw new Error(`Missing required field ${field}`);
|
|
454
524
|
}
|
|
455
525
|
return value;
|
|
456
526
|
}
|
|
527
|
+
buildQrCodeUrl(qr) {
|
|
528
|
+
return `https://quickchart.io/qr?text=${encodeURIComponent(qr)}`;
|
|
529
|
+
}
|
|
457
530
|
}
|
|
458
531
|
exports.registry = new WhatsThatRegistry();
|
|
459
532
|
function extractMessageText(message) {
|
package/dist/shared/types.d.ts
CHANGED