n8n-nodes-chatflow 0.4.1 → 1.1.1

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
@@ -1,43 +1,61 @@
1
1
  # n8n-nodes-chatflow
2
2
 
3
- n8n community node to send WhatsApp messages via Chatflow API
3
+ Community n8n nodes for [Chatflow](https://app.chatflow.kz) send and receive WhatsApp & Telegram messages.
4
4
 
5
- ## Installation
5
+ ## Nodes
6
6
 
7
- ```bash
8
- npm install n8n-nodes-chatflow
9
- ```
7
+ ### Chatflow Message
8
+ Send messages via WhatsApp or Telegram through Chatflow API.
9
+
10
+ **Operations:**
11
+ - Send Text
12
+ - Send Image (with optional caption)
13
+ - Send Video (with optional caption)
14
+ - Send Audio
15
+ - Send Document (with optional caption)
10
16
 
11
- ## Operations
17
+ ### Chatflow Trigger
18
+ Webhook-based trigger that starts your workflow when a new message arrives.
12
19
 
13
- - **Text**: Send text messages
14
- - **Audio**: Send audio files via URL
15
- - **Video**: Send video files with optional caption
16
- - **Document**: Send documents with optional caption
17
- - **Image**: Send images with optional caption
20
+ **Supported events:**
21
+ - Incoming message
22
+ - Delivery status updates
23
+ - Authorization status changes
24
+ - Incoming calls
25
+ - Outgoing messages (API / Phone)
18
26
 
19
27
  ## Credentials
20
28
 
21
- To use this node, you need to obtain an API token from [Chatflow](https://app.chatflow.kz):
29
+ Each credential is bound to one instance:
22
30
 
23
- 1. Go to Settings → API Access
24
- 2. Generate your API token
25
- 3. Add the token to your n8n credentials
31
+ | Field | Description |
32
+ |---|---|
33
+ | **Server URL** | Your Chatflow server (default: `https://app.chatflow.kz`) |
34
+ | **Token** | API token from Chatflow → Settings → API Access |
35
+ | **Instance ID** | Instance ID from Chatflow → Instances |
36
+ | **Platform** | `WhatsApp` or `Telegram` |
26
37
 
27
- ## Parameters
38
+ > **Tip:** Create separate credentials for each instance you want to use.
28
39
 
29
- - **Instance ID**: Your WhatsApp instance identifier
30
- - **JID**: Recipient's WhatsApp JID (phone number with country code)
31
- - **Continue On Fail**: Continue workflow execution if request fails
40
+ ## Installation
41
+
42
+ ### In n8n (Community Nodes)
43
+ 1. Go to **Settings → Community Nodes**
44
+ 2. Enter `n8n-nodes-chatflow`
45
+ 3. Click **Install**
46
+
47
+ ### Manual
48
+ ```bash
49
+ cd ~/.n8n
50
+ npm install n8n-nodes-chatflow
51
+ ```
32
52
 
33
- ## Usage Example
53
+ ## Usage
34
54
 
35
- 1. Add the Chatflow node to your workflow
36
- 2. Configure your Chatflow API credentials
37
- 3. Set the operation type (Text, Audio, Video, Document, Image)
38
- 4. Provide the required parameters
39
- 5. Connect to other nodes as needed
55
+ 1. Add your Chatflow credentials (token + instance ID + platform)
56
+ 2. **To send messages:** Add a "Chatflow Message" node, choose operation, set recipient
57
+ 3. **To receive messages:** Add a "Chatflow Trigger" node, select event types, activate workflow
40
58
 
41
- ## Support
59
+ ## License
42
60
 
43
- For issues and questions, please visit [Chatflow Support](https://app.chatflow.kz)
61
+ MIT
@@ -1,29 +1,46 @@
1
1
  class ChatflowApi {
2
- constructor() {
3
- this.name = 'chatflowApi';
4
- this.displayName = 'Chatflow API';
5
- this.documentationUrl = 'https://app.chatflow.kz';
6
- this.properties = [
7
- {
8
- displayName: 'Token',
9
- name: 'token',
10
- type: 'string',
11
- typeOptions: { password: true },
12
- default: '',
13
- required: true,
14
- description: 'API token from Chatflow → Settings → API Access',
15
- },
16
- ];
17
-
18
- // Basic test to ensure base URL is reachable
19
- this.test = {
20
- request: {
21
- baseURL: 'https://app.chatflow.kz/',
22
- url: 'ping',
23
- method: 'GET',
24
- },
25
- };
26
- }
2
+ constructor() {
3
+ this.name = 'chatflowApi';
4
+ this.displayName = 'Chatflow API';
5
+ this.documentationUrl = 'https://app.chatflow.kz';
6
+ this.properties = [
7
+ {
8
+ displayName: 'Server URL',
9
+ name: 'serverUrl',
10
+ type: 'string',
11
+ default: 'https://app.chatflow.kz',
12
+ required: true,
13
+ description: 'Your Chatflow server URL (e.g. https://app.chatflow.kz)',
14
+ },
15
+ {
16
+ displayName: 'Token',
17
+ name: 'token',
18
+ type: 'string',
19
+ typeOptions: { password: true },
20
+ default: '',
21
+ required: true,
22
+ description: 'API token from Chatflow → Settings → API Access',
23
+ },
24
+ {
25
+ displayName: 'Flow ID',
26
+ name: 'flowId',
27
+ type: 'string',
28
+ default: '',
29
+ required: true,
30
+ description: 'Flow ID copied from your Chatflow Flow Builder',
31
+ },
32
+ ];
33
+ this.test = {
34
+ request: {
35
+ baseURL: '={{$credentials.serverUrl}}',
36
+ url: '/api/v1/n8n/ping',
37
+ method: 'GET',
38
+ qs: {
39
+ token: '={{$credentials.token}}',
40
+ flow_id: '={{$credentials.flowId}}',
41
+ },
42
+ },
43
+ };
44
+ }
27
45
  }
28
-
29
- module.exports = ChatflowApi;
46
+ module.exports = { ChatflowApi };
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
- module.exports = {
2
- nodes: ['n8n-nodes-chatflow/dist/nodes/ChatflowMessage.node.js'],
3
- credentials: ['n8n-nodes-chatflow/dist/credentials/ChatflowApi.credentials.js']
4
- };
1
+ // Точка входа: экспортируем классы, а не пути. Относительные пути
2
+ // будут корректно работать как из исходников, так и после сборки в dist.
3
+ const { ChatflowMessage } = require('./nodes/ChatflowMessage.node.js');
4
+ const { ChatflowApi } = require('./credentials/ChatflowApi.credentials.js');
5
+ exports.nodes = [ChatflowMessage];
6
+ exports.credentials = [ChatflowApi];
7
+ module.exports = { nodes: exports.nodes, credentials: exports.credentials };
@@ -1,122 +1,259 @@
1
1
  class ChatflowMessage {
2
- constructor() {
3
- this.description = {
4
- displayName: 'Chatflow',
5
- name: 'chatflowMessage',
6
- icon: 'file:chatflow.png',
7
- group: ['output'],
8
- version: 1,
9
- subtitle: '={{$parameter["operation"]}}',
10
- description: 'Send WhatsApp messages via Chatflow API',
11
- defaults: { name: 'Chatflow Message' },
12
- inputs: [{ type: 'main' }],
13
- outputs: [{ type: 'main' }],
14
- credentials: [
15
- { name: 'chatflowApi', required: true },
16
- ],
17
- properties: [
18
- {
19
- displayName: 'Operation',
20
- name: 'operation',
21
- type: 'options',
22
- options: [
23
- { name: 'Text', value: 'text' },
24
- { name: 'Audio', value: 'audio' },
25
- { name: 'Video', value: 'video' },
26
- { name: 'Document', value: 'document' },
27
- { name: 'Image', value: 'image' },
28
- ],
29
- default: 'text',
30
- },
31
- { displayName: 'Instance ID', name: 'instance_id', type: 'string', default: '', required: true },
32
- { displayName: 'JID', name: 'jid', type: 'string', default: '', required: true },
33
-
34
- // Text
35
- { displayName: 'Message', name: 'msg', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['text'] } } },
36
-
37
- // Audio
38
- { displayName: 'Audio URL', name: 'audiourl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['audio'] } } },
39
-
40
- // Video
41
- { displayName: 'Video URL', name: 'videourl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['video'] } } },
42
- { displayName: 'Caption', name: 'caption', type: 'string', default: '', displayOptions: { show: { operation: ['video'] } } },
43
-
44
- // Document
45
- { displayName: 'Document URL', name: 'docurl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['document'] } } },
46
- { displayName: 'Caption', name: 'captionDoc', type: 'string', default: '', displayOptions: { show: { operation: ['document'] } } },
47
-
48
- // Image
49
- { displayName: 'Image URL', name: 'imageurl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['image'] } } },
50
- { displayName: 'Caption', name: 'captionImg', type: 'string', default: '', displayOptions: { show: { operation: ['image'] } } },
2
+ constructor() {
3
+ this.description = ChatflowMessage.description;
4
+ }
51
5
 
52
- { displayName: 'Continue On Fail', name: 'continueOnFail', type: 'boolean', default: true },
53
- ]
6
+ methods = {
7
+ loadOptions: {
8
+ async getPlatform() {
9
+ try {
10
+ const credentials = await this.getCredentials('chatflowApi');
11
+ const serverUrl = (credentials.serverUrl || 'https://app.chatflow.kz').replace(/\/+$/, '');
12
+ if (!credentials.flowId) return [];
13
+
14
+ const response = await this.helpers.httpRequest({
15
+ method: 'GET',
16
+ url: `${serverUrl}/api/v1/n8n/flow-info/${credentials.flowId}`,
17
+ qs: { token: credentials.token },
18
+ json: true,
19
+ });
20
+
21
+ if (response && response.platform) {
22
+ const platName = response.platform === 'telegram' ? 'Telegram' : 'WhatsApp';
23
+ return [{ name: platName, value: response.platform }];
24
+ }
25
+ } catch(e) {
26
+ console.error('Failed to load platform:', e);
27
+ }
28
+
29
+ return [
30
+ { name: 'WhatsApp', value: 'whatsapp' },
31
+ { name: 'Telegram', value: 'telegram' }
32
+ ];
33
+ }
34
+ }
54
35
  };
55
- }
56
36
 
57
- async execute() {
58
- const items = this.getInputData();
59
- const out = [];
37
+ async execute() {
38
+ const items = this.getInputData();
39
+ const out = [];
40
+ const credentials = await this.getCredentials('chatflowApi');
41
+ const token = credentials.token;
42
+ const flowId = credentials.flowId;
43
+ const serverUrl = (credentials.serverUrl || 'https://app.chatflow.kz').replace(/\/+$/, '');
44
+ const platform = this.getNodeParameter('platform', 0, 'whatsapp');
60
45
 
61
- const credentials = await this.getCredentials('chatflowApi');
62
- const token = credentials.token;
63
- const operation = this.getNodeParameter('operation', 0);
64
- const continueOnFail = this.getNodeParameter('continueOnFail', 0);
46
+ const operation = this.getNodeParameter('operation', 0);
47
+ const continueOnFail = this.getNodeParameter('continueOnFail', 0);
65
48
 
66
- const endpointMap = {
67
- text: 'api/v1/send-text',
68
- audio: 'api/v1/send-audio',
69
- video: 'api/v1/send-video',
70
- document: 'api/v1/send-doc',
71
- image: 'api/v1/send-image',
72
- };
49
+ for (let i = 0; i < items.length; i++) {
50
+ try {
51
+ const recipient = this.getNodeParameter('recipient', i);
73
52
 
74
- const urlJoin = (a, b) => a.replace(/\/+$/, '') + '/' + b.replace(/^\/+/, '');
75
- const baseUrl = 'https://app.chatflow.kz/';
53
+ // Build query string params
54
+ const qs = { token, flow_id: flowId };
76
55
 
77
- for (let i = 0; i < items.length; i++) {
78
- try {
79
- const instance_id = this.getNodeParameter('instance_id', i);
80
- const jid = this.getNodeParameter('jid', i);
56
+ // Set recipient field based on platform
57
+ if (platform === 'telegram') {
58
+ qs.chat_id = recipient;
59
+ } else {
60
+ qs.jid = recipient;
61
+ }
81
62
 
82
- const qs = { token, instance_id, jid };
83
- if (operation === 'text') qs.msg = this.getNodeParameter('msg', i);
84
- if (operation === 'audio') qs.audiourl = this.getNodeParameter('audiourl', i);
85
- if (operation === 'video') {
86
- qs.videourl = this.getNodeParameter('videourl', i);
87
- const capV = this.getNodeParameter('caption', i, ''); if (capV) qs.caption = capV;
88
- }
89
- if (operation === 'document') {
90
- qs.docurl = this.getNodeParameter('docurl', i);
91
- const capD = this.getNodeParameter('captionDoc', i, ''); if (capD) qs.caption = capD;
92
- }
93
- if (operation === 'image') {
94
- qs.imageurl = this.getNodeParameter('imageurl', i);
95
- const capI = this.getNodeParameter('captionImg', i, ''); if (capI) qs.caption = capI;
96
- }
63
+ // Operation-specific params
64
+ if (operation === 'text') {
65
+ qs.msg = this.getNodeParameter('msg', i);
66
+ }
67
+ if (operation === 'audio') {
68
+ qs.audiourl = this.getNodeParameter('audiourl', i);
69
+ }
70
+ if (operation === 'video') {
71
+ qs.videourl = this.getNodeParameter('videourl', i);
72
+ const cap = this.getNodeParameter('captionVideo', i, '');
73
+ if (cap) qs.caption = cap;
74
+ }
75
+ if (operation === 'document') {
76
+ qs.docurl = this.getNodeParameter('docurl', i);
77
+ const cap = this.getNodeParameter('captionDoc', i, '');
78
+ if (cap) qs.caption = cap;
79
+ }
80
+ if (operation === 'image') {
81
+ qs.imageurl = this.getNodeParameter('imageurl', i);
82
+ const cap = this.getNodeParameter('captionImg', i, '');
83
+ if (cap) qs.caption = cap;
84
+ }
97
85
 
98
- const options = {
99
- method: 'GET',
100
- url: urlJoin(baseUrl, endpointMap[operation]),
101
- qs,
102
- headers: { Accept: 'application/json' },
103
- timeout: 10000,
104
- retry: { times: 1, interval: 500 },
105
- };
86
+ const options = {
87
+ method: 'GET',
88
+ url: `${serverUrl}/api/v1/n8n/action/${operation}`,
89
+ qs,
90
+ headers: { Accept: 'application/json' },
91
+ timeout: 15000,
92
+ };
106
93
 
107
- const responseData = await this.helpers.httpRequest(options);
108
- out.push({ json: responseData });
109
- } catch (error) {
110
- if (continueOnFail) {
111
- out.push({ json: { success: false, message: (error && error.message) || 'Request failed' } });
112
- continue;
94
+ const responseData = await this.helpers.httpRequest(options);
95
+ out.push({ json: responseData });
96
+ } catch (error) {
97
+ if (continueOnFail) {
98
+ out.push({
99
+ json: {
100
+ success: false,
101
+ message: (error && error.message) || 'Request failed',
102
+ },
103
+ });
104
+ continue;
105
+ }
106
+ throw error;
107
+ }
113
108
  }
114
- throw error;
115
- }
109
+ return [out];
116
110
  }
117
-
118
- return [out];
119
- }
120
111
  }
121
112
 
122
- module.exports = ChatflowMessage;
113
+ ChatflowMessage.description = {
114
+ displayName: 'Chatflow Message',
115
+ name: 'chatflowMessage',
116
+ icon: {
117
+ light: 'file:black.svg',
118
+ dark: 'file:white.svg'
119
+ },
120
+ group: ['output'],
121
+ version: 2,
122
+ subtitle: '={{$parameter["operation"]}} ({{$parameter["platform"]}})',
123
+ description: 'Send WhatsApp & Telegram messages via Chatflow API',
124
+ defaults: { name: 'Chatflow Message' },
125
+ inputs: [{ type: 'main' }],
126
+ outputs: [{ type: 'main' }],
127
+ credentials: [
128
+ { name: 'chatflowApi', required: true },
129
+ ],
130
+ properties: [
131
+ {
132
+ displayName: 'Messaging Platform',
133
+ name: 'platform',
134
+ type: 'options',
135
+ typeOptions: { loadOptionsMethod: 'getPlatform' },
136
+ default: '',
137
+ required: true,
138
+ description: 'Automatically determined from your Chatflow credentials (Flow ID)',
139
+ },
140
+ {
141
+ displayName: 'Operation',
142
+ name: 'operation',
143
+ type: 'options',
144
+ noDataExpression: true,
145
+ options: [
146
+ { name: 'Send Text', value: 'text', description: 'Send a text message' },
147
+ { name: 'Send Image', value: 'image', description: 'Send an image with optional caption' },
148
+ { name: 'Send Video', value: 'video', description: 'Send a video with optional caption' },
149
+ { name: 'Send Audio', value: 'audio', description: 'Send an audio file' },
150
+ { name: 'Send Document', value: 'document', description: 'Send a document with optional caption' },
151
+ ],
152
+ default: 'text',
153
+ },
154
+ {
155
+ displayName: 'Recipient Number (WhatsApp)',
156
+ name: 'recipient',
157
+ type: 'string',
158
+ default: '',
159
+ required: true,
160
+ displayOptions: { show: { platform: ['whatsapp'] } },
161
+ description: 'WhatsApp phone number (e.g. 77001234567)',
162
+ placeholder: '77001234567',
163
+ },
164
+ {
165
+ displayName: 'Chat ID (Telegram)',
166
+ name: 'recipient',
167
+ type: 'string',
168
+ default: '',
169
+ required: true,
170
+ displayOptions: { show: { platform: ['telegram'] } },
171
+ description: 'Telegram numeric Chat ID',
172
+ },
173
+ // --- Text ---
174
+ {
175
+ displayName: 'Message',
176
+ name: 'msg',
177
+ type: 'string',
178
+ typeOptions: { rows: 4 },
179
+ default: '',
180
+ required: true,
181
+ displayOptions: { show: { operation: ['text'] } },
182
+ description: 'Text of the message to send',
183
+ },
184
+ // --- Image ---
185
+ {
186
+ displayName: 'Image URL',
187
+ name: 'imageurl',
188
+ type: 'string',
189
+ default: '',
190
+ required: true,
191
+ displayOptions: { show: { operation: ['image'] } },
192
+ description: 'Public URL of the image file',
193
+ },
194
+ {
195
+ displayName: 'Caption',
196
+ name: 'captionImg',
197
+ type: 'string',
198
+ default: '',
199
+ displayOptions: { show: { operation: ['image'] } },
200
+ description: 'Optional caption for the image',
201
+ },
202
+ // --- Video ---
203
+ {
204
+ displayName: 'Video URL',
205
+ name: 'videourl',
206
+ type: 'string',
207
+ default: '',
208
+ required: true,
209
+ displayOptions: { show: { operation: ['video'] } },
210
+ description: 'Public URL of the video file',
211
+ },
212
+ {
213
+ displayName: 'Caption',
214
+ name: 'captionVideo',
215
+ type: 'string',
216
+ default: '',
217
+ displayOptions: { show: { operation: ['video'] } },
218
+ description: 'Optional caption for the video',
219
+ },
220
+ // --- Audio ---
221
+ {
222
+ displayName: 'Audio URL',
223
+ name: 'audiourl',
224
+ type: 'string',
225
+ default: '',
226
+ required: true,
227
+ displayOptions: { show: { operation: ['audio'] } },
228
+ description: 'Public URL of the audio file',
229
+ },
230
+ // --- Document ---
231
+ {
232
+ displayName: 'Document URL',
233
+ name: 'docurl',
234
+ type: 'string',
235
+ default: '',
236
+ required: true,
237
+ displayOptions: { show: { operation: ['document'] } },
238
+ description: 'Public URL of the document file',
239
+ },
240
+ {
241
+ displayName: 'Caption',
242
+ name: 'captionDoc',
243
+ type: 'string',
244
+ default: '',
245
+ displayOptions: { show: { operation: ['document'] } },
246
+ description: 'Optional caption for the document',
247
+ },
248
+ // --- Options ---
249
+ {
250
+ displayName: 'Continue On Fail',
251
+ name: 'continueOnFail',
252
+ type: 'boolean',
253
+ default: true,
254
+ description: 'Whether to continue workflow execution on send failure',
255
+ },
256
+ ],
257
+ };
258
+
259
+ module.exports = { ChatflowMessage };
@@ -0,0 +1,270 @@
1
+ class ChatflowTrigger {
2
+ constructor() {
3
+ this.description = ChatflowTrigger.description;
4
+ }
5
+
6
+ methods = {
7
+ loadOptions: {
8
+ async getPlatform() {
9
+ try {
10
+ const credentials = await this.getCredentials('chatflowApi');
11
+ const serverUrl = (credentials.serverUrl || 'https://app.chatflow.kz').replace(/\/+$/, '');
12
+ if (!credentials.flowId) return [];
13
+
14
+ const response = await this.helpers.httpRequest({
15
+ method: 'GET',
16
+ url: `${serverUrl}/api/v1/n8n/flow-info/${credentials.flowId}`,
17
+ qs: { token: credentials.token },
18
+ json: true,
19
+ });
20
+
21
+ if (response && response.platform) {
22
+ const platName = response.platform === 'telegram' ? 'Telegram' : 'WhatsApp';
23
+ return [{ name: platName, value: response.platform }];
24
+ }
25
+ } catch(e) {
26
+ console.error('Failed to load platform:', e);
27
+ }
28
+
29
+ return [
30
+ { name: 'WhatsApp', value: 'whatsapp' },
31
+ { name: 'Telegram', value: 'telegram' }
32
+ ];
33
+ }
34
+ }
35
+ };
36
+
37
+ webhookMethods = {
38
+ default: {
39
+ async checkExists() {
40
+ const webhookData = this.getWorkflowStaticData('node');
41
+ const webhookUrl = this.getNodeWebhookUrl('default');
42
+
43
+ if (webhookData.webhookId) {
44
+ // Check if webhook is still registered on the server
45
+ try {
46
+ const credentials = await this.getCredentials('chatflowApi');
47
+ const serverUrl = (credentials.serverUrl || 'https://app.chatflow.kz').replace(/\/+$/, '');
48
+
49
+ const response = await this.helpers.httpRequest({
50
+ method: 'GET',
51
+ url: `${serverUrl}/api/v1/n8n/webhook/${webhookData.webhookId}`,
52
+ qs: { token: credentials.token },
53
+ headers: { Accept: 'application/json' },
54
+ timeout: 10000,
55
+ });
56
+
57
+ if (response && response.success && response.webhook) {
58
+ return true;
59
+ }
60
+ } catch (e) {
61
+ // Webhook not found, needs to be re-created
62
+ }
63
+ }
64
+ return false;
65
+ },
66
+
67
+ async create() {
68
+ const webhookUrl = this.getNodeWebhookUrl('default');
69
+ const credentials = await this.getCredentials('chatflowApi');
70
+ const serverUrl = (credentials.serverUrl || 'https://app.chatflow.kz').replace(/\/+$/, '');
71
+ const flowId = credentials.flowId;
72
+ const token = credentials.token;
73
+
74
+ // Get event filters from node parameters
75
+ const events = this.getNodeParameter('events', {});
76
+
77
+ const body = {
78
+ token,
79
+ flow_id: flowId,
80
+ webhook_url: webhookUrl,
81
+ events: events || { incoming_message: true },
82
+ };
83
+
84
+ const response = await this.helpers.httpRequest({
85
+ method: 'POST',
86
+ url: `${serverUrl}/api/v1/n8n/register-webhook`,
87
+ body,
88
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
89
+ timeout: 10000,
90
+ });
91
+
92
+ if (!response || !response.success || !response.webhookId) {
93
+ throw new Error(
94
+ `Failed to register webhook: ${(response && response.message) || 'Unknown error'}`
95
+ );
96
+ }
97
+
98
+ const webhookData = this.getWorkflowStaticData('node');
99
+ webhookData.webhookId = response.webhookId;
100
+
101
+ return true;
102
+ },
103
+
104
+ async delete() {
105
+ const webhookData = this.getWorkflowStaticData('node');
106
+ const credentials = await this.getCredentials('chatflowApi');
107
+ const serverUrl = (credentials.serverUrl || 'https://app.chatflow.kz').replace(/\/+$/, '');
108
+
109
+ if (webhookData.webhookId) {
110
+ try {
111
+ await this.helpers.httpRequest({
112
+ method: 'DELETE',
113
+ url: `${serverUrl}/api/v1/n8n/register-webhook`,
114
+ body: {
115
+ token: credentials.token,
116
+ webhookId: webhookData.webhookId,
117
+ },
118
+ headers: { 'Content-Type': 'application/json' },
119
+ timeout: 10000,
120
+ });
121
+ } catch (e) {
122
+ // Best-effort cleanup
123
+ }
124
+ delete webhookData.webhookId;
125
+ }
126
+ return true;
127
+ },
128
+ },
129
+ };
130
+
131
+ async webhook() {
132
+ const bodyData = this.getBodyData();
133
+ const headerData = this.getHeaderData();
134
+
135
+ // Verify the source (optional: check token header)
136
+ // const credentials = await this.getCredentials('chatflowApi');
137
+
138
+ // Normalize: Chatflow sends { messages: [...] }
139
+ const messages = bodyData.messages || [bodyData];
140
+ const returnData = [];
141
+
142
+ for (const message of messages) {
143
+ returnData.push({
144
+ json: {
145
+ // Core message fields
146
+ platform: message.platform || 'whatsapp',
147
+ event_type: message.wh_type || 'incoming_message',
148
+ // Sender info
149
+ from: message.chat_id || message.from || '',
150
+ sender_name: message.contact?.FullName || message.contact?.FirstName || message.pushName || '',
151
+ // Message content
152
+ message_id: message.id || message.task_id || '',
153
+ text: message.body || message.text || '',
154
+ type: message.type || 'text',
155
+ // Media (if any)
156
+ media_url: message.media_url || message.body_file || '',
157
+ caption: message.caption || '',
158
+ // Metadata
159
+ timestamp: message.time || Date.now(),
160
+ profile_id: message.profile_id || '',
161
+ instance_id: message.instance_id || '',
162
+ // Full raw payload for advanced use
163
+ _raw: message,
164
+ },
165
+ });
166
+ }
167
+
168
+ return {
169
+ workflowData: [returnData],
170
+ };
171
+ }
172
+ }
173
+
174
+ ChatflowTrigger.description = {
175
+ displayName: 'Chatflow Trigger',
176
+ name: 'chatflowTrigger',
177
+ icon: {
178
+ light: 'file:black.svg',
179
+ dark: 'file:white.svg'
180
+ },
181
+ group: ['trigger'],
182
+ version: 1,
183
+ subtitle: '={{$parameter["platform"]}}',
184
+ description: 'Triggers workflow on incoming messages via Chatflow',
185
+ defaults: { name: 'Chatflow Trigger' },
186
+ inputs: [],
187
+ outputs: [{ type: 'main' }],
188
+ credentials: [
189
+ { name: 'chatflowApi', required: true },
190
+ ],
191
+ webhooks: [
192
+ {
193
+ name: 'default',
194
+ httpMethod: 'POST',
195
+ responseMode: 'onReceived',
196
+ path: 'webhook',
197
+ },
198
+ ],
199
+ properties: [
200
+ {
201
+ displayName: 'Messaging Platform',
202
+ name: 'platform',
203
+ type: 'options',
204
+ typeOptions: { loadOptionsMethod: 'getPlatform' },
205
+ default: '',
206
+ required: true,
207
+ description: 'Automatically determined from your Chatflow credentials (Flow ID)',
208
+ },
209
+ {
210
+ displayName: 'Events',
211
+ name: 'events',
212
+ type: 'fixedCollection',
213
+ typeOptions: { multipleValues: false },
214
+ default: {},
215
+ description: 'Filter which events trigger this workflow',
216
+ options: [
217
+ {
218
+ displayName: 'Event Filters',
219
+ name: 'filters',
220
+ values: [
221
+ {
222
+ displayName: 'Incoming Message',
223
+ name: 'incoming_message',
224
+ type: 'boolean',
225
+ default: true,
226
+ description: 'Trigger on new incoming messages',
227
+ },
228
+ {
229
+ displayName: 'Delivery Status',
230
+ name: 'delivery_status',
231
+ type: 'boolean',
232
+ default: false,
233
+ description: 'Trigger on message delivery status updates',
234
+ },
235
+ {
236
+ displayName: 'Authorization Status',
237
+ name: 'authorization_status',
238
+ type: 'boolean',
239
+ default: false,
240
+ description: 'Trigger on instance authorization changes',
241
+ },
242
+ {
243
+ displayName: 'Incoming Call',
244
+ name: 'incoming_call',
245
+ type: 'boolean',
246
+ default: false,
247
+ description: 'Trigger on incoming voice/video calls',
248
+ },
249
+ {
250
+ displayName: 'Outgoing Message (API)',
251
+ name: 'outgoing_message_api',
252
+ type: 'boolean',
253
+ default: false,
254
+ description: 'Trigger on messages sent via API',
255
+ },
256
+ {
257
+ displayName: 'Outgoing Message (Phone)',
258
+ name: 'outgoing_message_phone',
259
+ type: 'boolean',
260
+ default: false,
261
+ description: 'Trigger on messages sent from phone',
262
+ },
263
+ ],
264
+ },
265
+ ],
266
+ },
267
+ ],
268
+ };
269
+
270
+ module.exports = { ChatflowTrigger };
@@ -0,0 +1,10 @@
1
+ <svg width="144" height="130" viewBox="0 0 144 130" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g transform="translate(72 65) scale(1.15) translate(-72 -65)">
3
+ <rect x="39.7617" y="19.8789" width="64.1613" height="15.3626" fill="black"/>
4
+ <rect x="64.1621" y="93.0762" width="39.762" height="15.3626" fill="black"/>
5
+ <rect x="64.1621" y="100.309" width="67.7761" height="15.3626" transform="rotate(-90 64.1621 100.309)" fill="black"/>
6
+ <rect x="93.9844" width="49.7025" height="56.0282" rx="4.29273" fill="black"/>
7
+ <rect width="49.7025" height="56.0282" rx="4.29273" fill="black"/>
8
+ <rect x="93.9844" y="73.1992" width="49.7025" height="56.0282" rx="4.29273" fill="black"/>
9
+ </g>
10
+ </svg>
Binary file
@@ -0,0 +1,18 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 868 780" width="60" height="60" preserveAspectRatio="xMidYMid meet">
2
+ <g fill="currentColor">
3
+ <!-- Top-left bracket -->
4
+ <path d="M 100 118 L 100 0 L 300 0 C 320 0 338 18 338 38 L 338 300 C 338 320 320 338 300 338 L 180 338 L 180 213 L 258 213 L 258 120 L 100 118 Z" />
5
+
6
+ <!-- Top-right bracket -->
7
+ <path d="M 568 0 L 828 0 L 828 118 L 610 120 L 610 213 L 748 213 L 748 338 L 568 338 C 548 338 530 320 530 300 L 530 38 C 530 18 548 0 568 0 Z" />
8
+
9
+ <!-- Bottom-right bracket (L-shape connection) -->
10
+ <path d="M 530 442 L 748 442 L 748 502 L 610 502 L 610 667 L 828 667 L 828 780 L 568 780 C 548 780 530 762 530 742 L 530 462 C 530 448 544 442 560 442 L 530 442 Z" />
11
+
12
+ <!-- Vertical connecting bar (stem) -->
13
+ <rect x="300" y="213" width="88" height="354" rx="0"/>
14
+
15
+ <!-- Horizontal connecting bar (bottom horizontal) -->
16
+ <rect x="388" y="654" width="180" height="88" rx="0"/>
17
+ </g>
18
+ </svg>
@@ -0,0 +1,10 @@
1
+ <svg width="144" height="130" viewBox="0 0 144 130" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g transform="translate(72 65) scale(1.15) translate(-72 -65)">
3
+ <rect x="39.7617" y="19.8789" width="64.1613" height="15.3626" fill="white"/>
4
+ <rect x="64.1621" y="93.0762" width="39.762" height="15.3626" fill="white"/>
5
+ <rect x="64.1621" y="100.309" width="67.7761" height="15.3626" transform="rotate(-90 64.1621 100.309)" fill="white"/>
6
+ <rect x="93.9844" width="49.7025" height="56.0282" rx="4.29273" fill="white"/>
7
+ <rect width="49.7025" height="56.0282" rx="4.29273" fill="white"/>
8
+ <rect x="93.9844" y="73.1992" width="49.7025" height="56.0282" rx="4.29273" fill="white"/>
9
+ </g>
10
+ </svg>
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "name": "n8n-nodes-chatflow",
3
- "version": "0.4.1",
4
- "description": "n8n community node to send WhatsApp messages via Chatflow API (text, audio, video, document, image)",
3
+ "version": "1.1.1",
4
+ "description": "n8n community nodes for Chatflow — send WhatsApp & Telegram messages and receive triggers",
5
5
  "author": "Chatflow",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "n8n-community-node-package",
9
+ "n8n",
10
+ "community-node",
9
11
  "chatflow",
10
12
  "whatsapp",
11
- "messaging"
13
+ "telegram",
14
+ "messaging",
15
+ "trigger"
12
16
  ],
13
17
  "repository": {
14
18
  "type": "git",
@@ -16,7 +20,8 @@
16
20
  },
17
21
  "n8n": {
18
22
  "nodes": [
19
- "dist/nodes/ChatflowMessage.node.js"
23
+ "dist/nodes/ChatflowMessage.node.js",
24
+ "dist/nodes/ChatflowTrigger.node.js"
20
25
  ],
21
26
  "credentials": [
22
27
  "dist/credentials/ChatflowApi.credentials.js"
@@ -25,24 +30,20 @@
25
30
  "main": "dist/index.js",
26
31
  "files": [
27
32
  "dist",
28
- "nodes",
29
- "credentials",
30
- "index.js",
31
33
  "README.md",
32
34
  "LICENSE"
33
35
  ],
34
36
  "scripts": {
35
- "build": "rimraf dist && cpx \"nodes/**/*\" dist/nodes && cpx \"credentials/**/*\" dist/credentials && cpx \"index.js\" dist/",
36
- "clean": "rimraf dist",
37
- "test": "node test-package.js",
38
- "prepublishOnly": "npm run build"
37
+ "test": "node test-package.js"
39
38
  },
40
- "dependencies": {
41
- "n8n-core": "^1.105.0",
42
- "n8n-workflow": "^1.105.0"
39
+ "peerDependencies": {
40
+ "n8n-core": "*",
41
+ "n8n-workflow": "*"
43
42
  },
44
43
  "devDependencies": {
45
44
  "cpx": "^1.5.0",
46
- "rimraf": "^5.0.5"
45
+ "eslint-plugin-n8n-nodes-base": "^1.16.3",
46
+ "rimraf": "^5.0.5",
47
+ "typescript": "^5.0.0"
47
48
  }
48
49
  }
@@ -1,29 +0,0 @@
1
- class ChatflowApi {
2
- constructor() {
3
- this.name = 'chatflowApi';
4
- this.displayName = 'Chatflow API';
5
- this.documentationUrl = 'https://app.chatflow.kz';
6
- this.properties = [
7
- {
8
- displayName: 'Token',
9
- name: 'token',
10
- type: 'string',
11
- typeOptions: { password: true },
12
- default: '',
13
- required: true,
14
- description: 'API token from Chatflow → Settings → API Access',
15
- },
16
- ];
17
-
18
- // Basic test to ensure base URL is reachable
19
- this.test = {
20
- request: {
21
- baseURL: 'https://app.chatflow.kz/',
22
- url: 'ping',
23
- method: 'GET',
24
- },
25
- };
26
- }
27
- }
28
-
29
- module.exports = ChatflowApi;
package/index.js DELETED
@@ -1,4 +0,0 @@
1
- module.exports = {
2
- nodes: ['n8n-nodes-chatflow/dist/nodes/ChatflowMessage.node.js'],
3
- credentials: ['n8n-nodes-chatflow/dist/credentials/ChatflowApi.credentials.js']
4
- };
@@ -1,122 +0,0 @@
1
- class ChatflowMessage {
2
- constructor() {
3
- this.description = {
4
- displayName: 'Chatflow',
5
- name: 'chatflowMessage',
6
- icon: 'file:chatflow.png',
7
- group: ['output'],
8
- version: 1,
9
- subtitle: '={{$parameter["operation"]}}',
10
- description: 'Send WhatsApp messages via Chatflow API',
11
- defaults: { name: 'Chatflow Message' },
12
- inputs: [{ type: 'main' }],
13
- outputs: [{ type: 'main' }],
14
- credentials: [
15
- { name: 'chatflowApi', required: true },
16
- ],
17
- properties: [
18
- {
19
- displayName: 'Operation',
20
- name: 'operation',
21
- type: 'options',
22
- options: [
23
- { name: 'Text', value: 'text' },
24
- { name: 'Audio', value: 'audio' },
25
- { name: 'Video', value: 'video' },
26
- { name: 'Document', value: 'document' },
27
- { name: 'Image', value: 'image' },
28
- ],
29
- default: 'text',
30
- },
31
- { displayName: 'Instance ID', name: 'instance_id', type: 'string', default: '', required: true },
32
- { displayName: 'JID', name: 'jid', type: 'string', default: '', required: true },
33
-
34
- // Text
35
- { displayName: 'Message', name: 'msg', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['text'] } } },
36
-
37
- // Audio
38
- { displayName: 'Audio URL', name: 'audiourl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['audio'] } } },
39
-
40
- // Video
41
- { displayName: 'Video URL', name: 'videourl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['video'] } } },
42
- { displayName: 'Caption', name: 'caption', type: 'string', default: '', displayOptions: { show: { operation: ['video'] } } },
43
-
44
- // Document
45
- { displayName: 'Document URL', name: 'docurl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['document'] } } },
46
- { displayName: 'Caption', name: 'captionDoc', type: 'string', default: '', displayOptions: { show: { operation: ['document'] } } },
47
-
48
- // Image
49
- { displayName: 'Image URL', name: 'imageurl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['image'] } } },
50
- { displayName: 'Caption', name: 'captionImg', type: 'string', default: '', displayOptions: { show: { operation: ['image'] } } },
51
-
52
- { displayName: 'Continue On Fail', name: 'continueOnFail', type: 'boolean', default: true },
53
- ]
54
- };
55
- }
56
-
57
- async execute() {
58
- const items = this.getInputData();
59
- const out = [];
60
-
61
- const credentials = await this.getCredentials('chatflowApi');
62
- const token = credentials.token;
63
- const operation = this.getNodeParameter('operation', 0);
64
- const continueOnFail = this.getNodeParameter('continueOnFail', 0);
65
-
66
- const endpointMap = {
67
- text: 'api/v1/send-text',
68
- audio: 'api/v1/send-audio',
69
- video: 'api/v1/send-video',
70
- document: 'api/v1/send-doc',
71
- image: 'api/v1/send-image',
72
- };
73
-
74
- const urlJoin = (a, b) => a.replace(/\/+$/, '') + '/' + b.replace(/^\/+/, '');
75
- const baseUrl = 'https://app.chatflow.kz/';
76
-
77
- for (let i = 0; i < items.length; i++) {
78
- try {
79
- const instance_id = this.getNodeParameter('instance_id', i);
80
- const jid = this.getNodeParameter('jid', i);
81
-
82
- const qs = { token, instance_id, jid };
83
- if (operation === 'text') qs.msg = this.getNodeParameter('msg', i);
84
- if (operation === 'audio') qs.audiourl = this.getNodeParameter('audiourl', i);
85
- if (operation === 'video') {
86
- qs.videourl = this.getNodeParameter('videourl', i);
87
- const capV = this.getNodeParameter('caption', i, ''); if (capV) qs.caption = capV;
88
- }
89
- if (operation === 'document') {
90
- qs.docurl = this.getNodeParameter('docurl', i);
91
- const capD = this.getNodeParameter('captionDoc', i, ''); if (capD) qs.caption = capD;
92
- }
93
- if (operation === 'image') {
94
- qs.imageurl = this.getNodeParameter('imageurl', i);
95
- const capI = this.getNodeParameter('captionImg', i, ''); if (capI) qs.caption = capI;
96
- }
97
-
98
- const options = {
99
- method: 'GET',
100
- url: urlJoin(baseUrl, endpointMap[operation]),
101
- qs,
102
- headers: { Accept: 'application/json' },
103
- timeout: 10000,
104
- retry: { times: 1, interval: 500 },
105
- };
106
-
107
- const responseData = await this.helpers.httpRequest(options);
108
- out.push({ json: responseData });
109
- } catch (error) {
110
- if (continueOnFail) {
111
- out.push({ json: { success: false, message: (error && error.message) || 'Request failed' } });
112
- continue;
113
- }
114
- throw error;
115
- }
116
- }
117
-
118
- return [out];
119
- }
120
- }
121
-
122
- module.exports = ChatflowMessage;
Binary file