@jep182/n8n-nodes-whatsthat 0.2.0

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 ADDED
@@ -0,0 +1,121 @@
1
+ # @jep182/n8n-nodes-whatsthat
2
+
3
+ WhatsThat is an n8n community package for sending WhatsApp messages, with multiple sessions.
4
+
5
+ It lets you:
6
+
7
+ - manage multiple sessions and numbers
8
+ - connect with pairing code or QR
9
+ - discover chats and groups
10
+ - link chats/groups to friendly aliases
11
+ - send text, media, documents, reactions, contacts, locations, and polls
12
+ - receive inbound events through a trigger node
13
+
14
+ ## Included Nodes
15
+
16
+ ### `WhatsThat Session`
17
+
18
+ Use this node to:
19
+
20
+ - create a session
21
+ - connect a session
22
+ - list sessions
23
+ - inspect session status
24
+ - disconnect a session
25
+ - remove a session
26
+
27
+ When pairing is available, the node returns:
28
+
29
+ - `pairingCode`
30
+ - `qr`
31
+ - `qrDataUrl`
32
+
33
+ ### `WhatsThat Targets`
34
+
35
+ Use this node to:
36
+
37
+ - list discovered chats and groups
38
+ - list linked aliases
39
+ - link a target to an alias
40
+ - unlink an alias
41
+
42
+ ### `WhatsThat Message`
43
+
44
+ Use this node to send:
45
+
46
+ - text
47
+ - image
48
+ - video
49
+ - audio
50
+ - document
51
+ - reaction
52
+ - location
53
+ - contact
54
+ - poll
55
+
56
+ For images and videos, the node can decide internally whether to send them as:
57
+
58
+ - native media
59
+ - file/document attachment
60
+
61
+ ### `WhatsThat Trigger`
62
+
63
+ Use this node to listen for:
64
+
65
+ - incoming messages
66
+ - your own sent messages
67
+ - session pairing events
68
+ - session connected/disconnected events
69
+ - group updates
70
+
71
+ ## How It Works
72
+
73
+ WhatsThat embeds Baileys directly in n8n.
74
+
75
+ - session auth files are stored on disk
76
+ - session metadata and linked targets can use n8n Data Tables when available
77
+ - if Data Tables are not available, WhatsThat falls back to local JSON storage
78
+
79
+ ## Quick Start
80
+
81
+ 1. Create `WhatsThat Runtime` credentials.
82
+ 2. Set a storage path, for example:
83
+
84
+ ```text
85
+ /home/node/.n8n/whatsthat
86
+ ```
87
+
88
+ 3. Add `WhatsThat Session`.
89
+ 4. Choose `Create Session`, then provide:
90
+ - `Session ID`
91
+ - `Label`
92
+ - optional `Phone Number For Pairing`
93
+ 5. Run `Connect Session`.
94
+ 6. Use the returned `pairingCode` or `qrDataUrl` to connect the device.
95
+ 7. Use `WhatsThat Targets` to discover and link chats/groups.
96
+ 8. Use `WhatsThat Message` to send messages by alias or raw JID.
97
+
98
+ ## Media Delivery
99
+
100
+ For `Image` and `Video` messages:
101
+
102
+ - `Native Media` sends them as normal media with preview
103
+ - `As File` sends them as a document/file attachment
104
+
105
+ ## Notes
106
+
107
+ - This package runs an active WebSocket client inside n8n.
108
+ - For production, use persistent storage for the auth directory.
109
+ - Use one n8n instance as the runtime owner for these sessions.
110
+
111
+ ## Thanks
112
+
113
+ This project relies on [Baileys](https://github.com/WhiskeySockets/Baileys), the open-source TypeScript/Node.js library that powers the messaging client layer.
114
+
115
+ Please review the Baileys project, its license, and its usage notes before running it in production:
116
+
117
+ - [Baileys repository](https://github.com/WhiskeySockets/Baileys)
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,7 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class WhatsThatRuntime implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WhatsThatRuntime = void 0;
4
+ class WhatsThatRuntime {
5
+ constructor() {
6
+ this.name = 'whatsThatRuntime';
7
+ this.displayName = 'WhatsThat Runtime';
8
+ this.documentationUrl = 'https://github.com/jep182/whatsapp-bot';
9
+ this.properties = [
10
+ {
11
+ displayName: 'Storage Path',
12
+ name: 'storagePath',
13
+ type: 'string',
14
+ default: '=/home/node/.n8n/whatsthat',
15
+ required: true,
16
+ description: 'Root folder used for session files and local fallback data',
17
+ },
18
+ {
19
+ displayName: 'Use Data Tables',
20
+ name: 'useDataTables',
21
+ type: 'boolean',
22
+ default: true,
23
+ description: 'Use n8n Data Tables when available, with filesystem fallback',
24
+ },
25
+ ];
26
+ }
27
+ }
28
+ exports.WhatsThatRuntime = WhatsThatRuntime;
@@ -0,0 +1,5 @@
1
+ export * from './credentials/WhatsThatRuntime.credentials';
2
+ export * from './nodes/WhatsThatSession/WhatsThatSession.node';
3
+ export * from './nodes/WhatsThatTargets/WhatsThatTargets.node';
4
+ export * from './nodes/WhatsThatMessage/WhatsThatMessage.node';
5
+ export * from './nodes/WhatsThatTrigger/WhatsThatTrigger.node';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./credentials/WhatsThatRuntime.credentials"), exports);
18
+ __exportStar(require("./nodes/WhatsThatSession/WhatsThatSession.node"), exports);
19
+ __exportStar(require("./nodes/WhatsThatTargets/WhatsThatTargets.node"), exports);
20
+ __exportStar(require("./nodes/WhatsThatMessage/WhatsThatMessage.node"), exports);
21
+ __exportStar(require("./nodes/WhatsThatTrigger/WhatsThatTrigger.node"), exports);
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class WhatsThatMessage implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WhatsThatMessage = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const access_1 = require("../../shared/access");
6
+ const runtime_1 = require("../../shared/runtime");
7
+ function buildPayload(context, itemIndex) {
8
+ const messageType = context.getNodeParameter('messageType', itemIndex);
9
+ const targetMode = context.getNodeParameter('targetMode', itemIndex);
10
+ const deliveryMode = context.getNodeParameter('deliveryMode', itemIndex, 'native');
11
+ const payload = {
12
+ sessionId: context.getNodeParameter('sessionId', itemIndex),
13
+ type: messageType,
14
+ };
15
+ if (targetMode === 'alias') {
16
+ payload.channelAlias = context.getNodeParameter('alias', itemIndex);
17
+ }
18
+ else {
19
+ payload.jid = context.getNodeParameter('jid', itemIndex);
20
+ }
21
+ payload.message = context.getNodeParameter('message', itemIndex, '');
22
+ payload.replyToMessageId = context.getNodeParameter('replyToMessageId', itemIndex, '');
23
+ if (['image', 'video', 'audio', 'document'].includes(messageType)) {
24
+ payload.mediaUrl = context.getNodeParameter('mediaUrl', itemIndex);
25
+ payload.mimetype = context.getNodeParameter('mimetype', itemIndex, '');
26
+ payload.fileName = context.getNodeParameter('fileName', itemIndex, '');
27
+ }
28
+ if (['image', 'video'].includes(messageType)) {
29
+ payload.sendAsDocument = deliveryMode === 'document';
30
+ }
31
+ if (['image', 'video', 'document'].includes(messageType)) {
32
+ payload.caption = context.getNodeParameter('caption', itemIndex, '');
33
+ }
34
+ if (messageType === 'reaction') {
35
+ payload.reactionText = context.getNodeParameter('reactionText', itemIndex, '👍');
36
+ }
37
+ if (messageType === 'location') {
38
+ payload.location = {
39
+ degreesLatitude: context.getNodeParameter('latitude', itemIndex),
40
+ degreesLongitude: context.getNodeParameter('longitude', itemIndex),
41
+ name: context.getNodeParameter('locationName', itemIndex, ''),
42
+ address: context.getNodeParameter('locationAddress', itemIndex, ''),
43
+ };
44
+ }
45
+ if (messageType === 'contact') {
46
+ payload.contact = {
47
+ displayName: context.getNodeParameter('contactName', itemIndex),
48
+ vcard: context.getNodeParameter('contactVcard', itemIndex),
49
+ };
50
+ }
51
+ if (messageType === 'poll') {
52
+ const raw = context.getNodeParameter('pollOptions', itemIndex);
53
+ payload.poll = {
54
+ name: context.getNodeParameter('pollName', itemIndex),
55
+ values: raw.split(',').map((value) => value.trim()).filter(Boolean),
56
+ selectableCount: context.getNodeParameter('pollSelectableCount', itemIndex, 1),
57
+ };
58
+ }
59
+ return payload;
60
+ }
61
+ class WhatsThatMessage {
62
+ constructor() {
63
+ this.description = {
64
+ displayName: 'WhatsThat Message',
65
+ name: 'whatsThatMessage',
66
+ icon: 'file:../WhatsThatSession/whatsthat.svg',
67
+ group: ['transform'],
68
+ version: 1,
69
+ description: 'Send messages and media through an embedded WhatsThat session',
70
+ defaults: { name: 'WhatsThat Message' },
71
+ inputs: ['main'],
72
+ outputs: ['main'],
73
+ credentials: [{ name: 'whatsThatRuntime', required: true }],
74
+ properties: [
75
+ { displayName: 'Session ID', name: 'sessionId', type: 'string', default: '' },
76
+ {
77
+ displayName: 'Target Mode',
78
+ name: 'targetMode',
79
+ type: 'options',
80
+ default: 'alias',
81
+ options: [
82
+ { name: 'Linked Alias', value: 'alias' },
83
+ { name: 'Raw JID', value: 'jid' },
84
+ ],
85
+ },
86
+ {
87
+ displayName: 'Alias',
88
+ name: 'alias',
89
+ type: 'string',
90
+ default: '',
91
+ displayOptions: { show: { targetMode: ['alias'] } },
92
+ },
93
+ {
94
+ displayName: 'JID',
95
+ name: 'jid',
96
+ type: 'string',
97
+ default: '',
98
+ displayOptions: { show: { targetMode: ['jid'] } },
99
+ },
100
+ {
101
+ displayName: 'Message Type',
102
+ name: 'messageType',
103
+ type: 'options',
104
+ default: 'text',
105
+ options: [
106
+ { name: 'Text', value: 'text' },
107
+ { name: 'Image', value: 'image' },
108
+ { name: 'Video', value: 'video' },
109
+ { name: 'Audio', value: 'audio' },
110
+ { name: 'Document', value: 'document' },
111
+ { name: 'Reaction', value: 'reaction' },
112
+ { name: 'Location', value: 'location' },
113
+ { name: 'Contact', value: 'contact' },
114
+ { name: 'Poll', value: 'poll' },
115
+ ],
116
+ },
117
+ {
118
+ displayName: 'Delivery Mode',
119
+ name: 'deliveryMode',
120
+ type: 'options',
121
+ default: 'native',
122
+ options: [
123
+ { name: 'Native Media', value: 'native' },
124
+ { name: 'As File', value: 'document' },
125
+ ],
126
+ displayOptions: { show: { messageType: ['image', 'video'] } },
127
+ },
128
+ {
129
+ displayName: 'Message',
130
+ name: 'message',
131
+ type: 'string',
132
+ typeOptions: { rows: 4 },
133
+ default: '',
134
+ displayOptions: { hide: { messageType: ['location', 'contact', 'poll'] } },
135
+ },
136
+ {
137
+ displayName: 'Media URL',
138
+ name: 'mediaUrl',
139
+ type: 'string',
140
+ default: '',
141
+ displayOptions: { show: { messageType: ['image', 'video', 'audio', 'document'] } },
142
+ },
143
+ {
144
+ displayName: 'Caption',
145
+ name: 'caption',
146
+ type: 'string',
147
+ default: '',
148
+ displayOptions: { show: { messageType: ['image', 'video', 'document'] } },
149
+ },
150
+ {
151
+ displayName: 'Mimetype',
152
+ name: 'mimetype',
153
+ type: 'string',
154
+ default: '',
155
+ displayOptions: { show: { messageType: ['image', 'video', 'audio', 'document'] } },
156
+ },
157
+ {
158
+ displayName: 'File Name',
159
+ name: 'fileName',
160
+ type: 'string',
161
+ default: '',
162
+ displayOptions: { show: { messageType: ['image', 'video', 'document'] } },
163
+ },
164
+ { displayName: 'Reply To Message ID', name: 'replyToMessageId', type: 'string', default: '' },
165
+ {
166
+ displayName: 'Reaction Text',
167
+ name: 'reactionText',
168
+ type: 'string',
169
+ default: '👍',
170
+ displayOptions: { show: { messageType: ['reaction'] } },
171
+ },
172
+ {
173
+ displayName: 'Latitude',
174
+ name: 'latitude',
175
+ type: 'number',
176
+ default: 0,
177
+ displayOptions: { show: { messageType: ['location'] } },
178
+ },
179
+ {
180
+ displayName: 'Longitude',
181
+ name: 'longitude',
182
+ type: 'number',
183
+ default: 0,
184
+ displayOptions: { show: { messageType: ['location'] } },
185
+ },
186
+ {
187
+ displayName: 'Location Name',
188
+ name: 'locationName',
189
+ type: 'string',
190
+ default: '',
191
+ displayOptions: { show: { messageType: ['location'] } },
192
+ },
193
+ {
194
+ displayName: 'Location Address',
195
+ name: 'locationAddress',
196
+ type: 'string',
197
+ default: '',
198
+ displayOptions: { show: { messageType: ['location'] } },
199
+ },
200
+ {
201
+ displayName: 'Contact Name',
202
+ name: 'contactName',
203
+ type: 'string',
204
+ default: '',
205
+ displayOptions: { show: { messageType: ['contact'] } },
206
+ },
207
+ {
208
+ displayName: 'Contact VCard',
209
+ name: 'contactVcard',
210
+ type: 'string',
211
+ typeOptions: { rows: 4 },
212
+ default: '',
213
+ displayOptions: { show: { messageType: ['contact'] } },
214
+ },
215
+ {
216
+ displayName: 'Poll Name',
217
+ name: 'pollName',
218
+ type: 'string',
219
+ default: '',
220
+ displayOptions: { show: { messageType: ['poll'] } },
221
+ },
222
+ {
223
+ displayName: 'Poll Options',
224
+ name: 'pollOptions',
225
+ type: 'string',
226
+ default: '',
227
+ displayOptions: { show: { messageType: ['poll'] } },
228
+ },
229
+ {
230
+ displayName: 'Selectable Count',
231
+ name: 'pollSelectableCount',
232
+ type: 'number',
233
+ default: 1,
234
+ displayOptions: { show: { messageType: ['poll'] } },
235
+ }
236
+ ],
237
+ };
238
+ }
239
+ async execute() {
240
+ const items = this.getInputData();
241
+ const access = await (0, access_1.buildAccess)(this);
242
+ const returnData = [];
243
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
244
+ try {
245
+ const result = await runtime_1.registry.sendMessage(access, buildPayload(this, itemIndex));
246
+ returnData.push({ json: result, pairedItem: itemIndex });
247
+ }
248
+ catch (error) {
249
+ if (this.continueOnFail()) {
250
+ returnData.push({ json: { error: error.message }, pairedItem: itemIndex });
251
+ continue;
252
+ }
253
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex });
254
+ }
255
+ }
256
+ return [returnData];
257
+ }
258
+ }
259
+ exports.WhatsThatMessage = WhatsThatMessage;
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class WhatsThatSession implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WhatsThatSession = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const access_1 = require("../../shared/access");
6
+ const runtime_1 = require("../../shared/runtime");
7
+ class WhatsThatSession {
8
+ constructor() {
9
+ this.description = {
10
+ displayName: 'WhatsThat Session',
11
+ name: 'whatsThatSession',
12
+ icon: 'file:whatsthat.svg',
13
+ group: ['transform'],
14
+ version: 1,
15
+ description: 'Manage embedded Baileys sessions for WhatsThat',
16
+ defaults: { name: 'WhatsThat Session' },
17
+ inputs: ['main'],
18
+ outputs: ['main'],
19
+ credentials: [{ name: 'whatsThatRuntime', required: true }],
20
+ properties: [
21
+ {
22
+ displayName: 'Operation',
23
+ name: 'operation',
24
+ type: 'options',
25
+ default: 'create',
26
+ options: [
27
+ { name: 'Create Session', value: 'create' },
28
+ { name: 'Connect Session', value: 'connect' },
29
+ { name: 'List Sessions', value: 'list' },
30
+ { name: 'Get Session Status', value: 'status' },
31
+ { name: 'Disconnect Session', value: 'disconnect' },
32
+ { name: 'Remove Session', value: 'remove' },
33
+ ],
34
+ },
35
+ {
36
+ displayName: 'Session ID',
37
+ name: 'sessionId',
38
+ type: 'string',
39
+ default: '',
40
+ displayOptions: {
41
+ hide: { operation: ['list'] },
42
+ },
43
+ },
44
+ {
45
+ displayName: 'Label',
46
+ name: 'label',
47
+ type: 'string',
48
+ default: '',
49
+ displayOptions: {
50
+ show: { operation: ['create'] },
51
+ },
52
+ },
53
+ {
54
+ displayName: 'Phone Number For Pairing',
55
+ name: 'phoneNumberForPairing',
56
+ type: 'string',
57
+ default: '',
58
+ description: 'Optional. When provided, WhatsThat will request a pairing code when possible',
59
+ displayOptions: {
60
+ show: { operation: ['create'] },
61
+ },
62
+ },
63
+ ],
64
+ };
65
+ }
66
+ async execute() {
67
+ const items = this.getInputData();
68
+ const returnData = [];
69
+ const access = await (0, access_1.buildAccess)(this);
70
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
71
+ try {
72
+ const operation = this.getNodeParameter('operation', itemIndex);
73
+ const sessionId = this.getNodeParameter('sessionId', itemIndex, '');
74
+ let json;
75
+ switch (operation) {
76
+ case 'create':
77
+ json = await runtime_1.registry.ensureSession(access.paths.root, access, {
78
+ sessionId,
79
+ label: this.getNodeParameter('label', itemIndex) || sessionId,
80
+ phoneNumberForPairing: this.getNodeParameter('phoneNumberForPairing', itemIndex, ''),
81
+ });
82
+ break;
83
+ case 'connect':
84
+ json = await runtime_1.registry.connectSession(access.paths.root, access, sessionId);
85
+ break;
86
+ case 'list':
87
+ json = await runtime_1.registry.listSessions(access);
88
+ break;
89
+ case 'status':
90
+ json = (await runtime_1.registry.getSession(access, sessionId)) ?? null;
91
+ break;
92
+ case 'disconnect':
93
+ json = (await runtime_1.registry.disconnectSession(access, sessionId)) ?? null;
94
+ break;
95
+ case 'remove':
96
+ json = { removed: await runtime_1.registry.removeSession(access, sessionId) };
97
+ break;
98
+ default:
99
+ throw new Error(`Unsupported operation ${operation}`);
100
+ }
101
+ returnData.push({ json: json, pairedItem: itemIndex });
102
+ }
103
+ catch (error) {
104
+ if (this.continueOnFail()) {
105
+ returnData.push({ json: { error: error.message }, pairedItem: itemIndex });
106
+ continue;
107
+ }
108
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex });
109
+ }
110
+ }
111
+ return [returnData];
112
+ }
113
+ }
114
+ exports.WhatsThatSession = WhatsThatSession;
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="8" fill="#111827"/>
3
+ <path d="M9 10h14v2H9zm0 5h14v2H9zm0 5h9v2H9z" fill="#f9fafb"/>
4
+ <circle cx="23" cy="21" r="3" fill="#22c55e"/>
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class WhatsThatTargets implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }