n8n-nodes-chatflow 0.1.2 → 0.1.4

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 ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Chatflow
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # n8n-nodes-chatflow
2
+
3
+ Chatflow node for n8n — send WhatsApp messages via Chatflow API (text, audio, video, document, image).
4
+
5
+ ## Install
6
+
7
+ ```
8
+ npm i n8n-nodes-chatflow --save
9
+ ```
10
+
11
+ If you use custom extensions for n8n, make sure `N8N_CUSTOM_EXTENSIONS` points to the package folder (e.g. `node_modules/n8n-nodes-chatflow`) or copy this package to your custom dir.
12
+
13
+ Restart n8n after installation.
14
+
15
+ ## Usage
16
+ 1. Create credentials "Chatflow API" and paste your token (from Chatflow → Api access).
17
+ 2. Add node "Chatflow" and select the credential.
18
+ 3. Choose Operation and fill required parameters:
19
+ - Text: `msg`, `instance_id`, `jid`
20
+ - Audio: `audiourl`, `instance_id`, `jid`
21
+ - Video: `videourl`, `caption?`, `instance_id`, `jid`
22
+ - Document: `docurl`, `caption?`, `instance_id`, `jid`
23
+ - Image: `imageurl`, `caption?`, `instance_id`, `jid`
24
+
25
+ The node performs GET requests to `https://app.chatflow.kz/` using endpoints:
26
+ - `api/v1/send-text`, `api/v1/send-audio`, `api/v1/send-video`, `api/v1/send-doc`, `api/v1/send-image`.
27
+
28
+ ## License
29
+ MIT
@@ -1,4 +1,4 @@
1
- module.exports = class ChatflowApi {
1
+ class ChatflowApi {
2
2
  constructor() {
3
3
  this.name = 'chatflowApi';
4
4
  this.displayName = 'Chatflow API';
@@ -18,10 +18,13 @@ module.exports = class ChatflowApi {
18
18
  // simple test (no token needed)
19
19
  this.test = {
20
20
  request: {
21
- baseURL: 'https://app.chatflow.kz',
21
+ baseURL: 'https://app.chatflow.kz/',
22
22
  url: 'ping',
23
23
  method: 'GET',
24
24
  },
25
25
  };
26
26
  }
27
- };
27
+ }
28
+
29
+ exports.ChatflowApi = ChatflowApi;
30
+
@@ -0,0 +1,30 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+
3
+ export class ChatflowApi implements ICredentialType {
4
+ name = 'chatflowApi';
5
+ displayName = 'Chatflow API';
6
+ documentationUrl = '';
7
+
8
+ properties: INodeProperties[] = [
9
+ {
10
+ displayName: 'Token',
11
+ name: 'token',
12
+ type: 'string',
13
+ typeOptions: { password: true },
14
+ default: '',
15
+ required: true,
16
+ description: 'API token from Chatflow → Api access',
17
+ },
18
+ ];
19
+
20
+ // Optional basic test just to ensure base URL is reachable
21
+ // Token не проверяем, так как для ping он не нужен
22
+ test = {
23
+ request: {
24
+ baseURL: 'https://app.chatflow.kz/',
25
+ url: 'ping',
26
+ method: 'GET',
27
+ },
28
+ };
29
+ }
30
+
@@ -1,4 +1,4 @@
1
- module.exports = class ChatflowApi {
1
+ class ChatflowApi {
2
2
  constructor() {
3
3
  this.name = 'chatflowApi';
4
4
  this.displayName = 'Chatflow API';
@@ -18,10 +18,13 @@ module.exports = class ChatflowApi {
18
18
  // simple test (no token needed)
19
19
  this.test = {
20
20
  request: {
21
- baseURL: 'https://app.chatflow.kz',
21
+ baseURL: 'https://app.chatflow.kz/',
22
22
  url: 'ping',
23
23
  method: 'GET',
24
24
  },
25
25
  };
26
26
  }
27
- };
27
+ }
28
+
29
+ exports.ChatflowApi = ChatflowApi;
30
+
@@ -0,0 +1,30 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+
3
+ export class ChatflowApi implements ICredentialType {
4
+ name = 'chatflowApi';
5
+ displayName = 'Chatflow API';
6
+ documentationUrl = '';
7
+
8
+ properties: INodeProperties[] = [
9
+ {
10
+ displayName: 'Token',
11
+ name: 'token',
12
+ type: 'string',
13
+ typeOptions: { password: true },
14
+ default: '',
15
+ required: true,
16
+ description: 'API token from Chatflow → Api access',
17
+ },
18
+ ];
19
+
20
+ // Optional basic test just to ensure base URL is reachable
21
+ // Token не проверяем, так как для ping он не нужен
22
+ test = {
23
+ request: {
24
+ baseURL: 'https://app.chatflow.kz/',
25
+ url: 'ping',
26
+ method: 'GET',
27
+ },
28
+ };
29
+ }
30
+
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- // This file is required by n8n to properly load the nodes
1
+ // Entry file is not required by n8n, but kept for completeness
2
2
  module.exports = {};
3
+
@@ -1,4 +1,4 @@
1
- module.exports = class ChatflowMessage {
1
+ class Chatflow {
2
2
  constructor() {
3
3
  this.description = {
4
4
  displayName: 'Chatflow',
@@ -72,7 +72,7 @@ module.exports = class ChatflowMessage {
72
72
  };
73
73
 
74
74
  const urlJoin = (a, b) => a.replace(/\/+$/, '') + '/' + b.replace(/^\/+/, '');
75
- const baseUrl = 'https://app.chatflow.kz';
75
+ const baseUrl = 'https://app.chatflow.kz/';
76
76
 
77
77
  for (let i = 0; i < items.length; i++) {
78
78
  try {
@@ -117,4 +117,7 @@ module.exports = class ChatflowMessage {
117
117
 
118
118
  return [out];
119
119
  }
120
- };
120
+ }
121
+
122
+ exports.Chatflow = Chatflow;
123
+
@@ -0,0 +1,135 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ import { NodeOperationError } from 'n8n-workflow';
3
+
4
+ type MessageType = 'text' | 'audio' | 'video' | 'document' | 'image';
5
+
6
+ export class ChatflowMessage implements INodeType {
7
+ description: INodeTypeDescription = {
8
+ displayName: 'Chatflow',
9
+ name: 'chatflow',
10
+ icon: 'file:chatflow.png',
11
+ group: ['output'],
12
+ version: 1,
13
+ subtitle: '={{$parameter["operation"]}}',
14
+ description: 'Send WhatsApp messages via Chatflow API',
15
+ defaults: { name: 'Chatflow Message' },
16
+ inputs: [
17
+ { type: 'main' as any },
18
+ ],
19
+ outputs: [
20
+ { type: 'main' as any },
21
+ ],
22
+ credentials: [
23
+ {
24
+ name: 'chatflowApi',
25
+ required: true,
26
+ },
27
+ ],
28
+ properties: [
29
+ {
30
+ displayName: 'Operation',
31
+ name: 'operation',
32
+ type: 'options',
33
+ options: [
34
+ { name: 'Text', value: 'text' },
35
+ { name: 'Audio', value: 'audio' },
36
+ { name: 'Video', value: 'video' },
37
+ { name: 'Document', value: 'document' },
38
+ { name: 'Image', value: 'image' },
39
+ ],
40
+ default: 'text',
41
+ },
42
+ { displayName: 'Instance ID', name: 'instance_id', type: 'string', default: '', required: true },
43
+ { displayName: 'JID', name: 'jid', type: 'string', default: '', required: true },
44
+
45
+ // Text
46
+ { displayName: 'Message', name: 'msg', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['text'] } } },
47
+
48
+ // Audio
49
+ { displayName: 'Audio URL', name: 'audiourl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['audio'] } } },
50
+
51
+ // Video
52
+ { displayName: 'Video URL', name: 'videourl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['video'] } } },
53
+ { displayName: 'Caption', name: 'caption', type: 'string', default: '', displayOptions: { show: { operation: ['video'] } } },
54
+
55
+ // Document
56
+ { displayName: 'Document URL', name: 'docurl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['document'] } } },
57
+ { displayName: 'Caption', name: 'captionDoc', type: 'string', default: '', displayOptions: { show: { operation: ['document'] } } },
58
+
59
+ // Image
60
+ { displayName: 'Image URL', name: 'imageurl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['image'] } } },
61
+ { displayName: 'Caption', name: 'captionImg', type: 'string', default: '', displayOptions: { show: { operation: ['image'] } } },
62
+
63
+ {
64
+ displayName: 'Continue On Fail',
65
+ name: 'continueOnFail',
66
+ type: 'boolean',
67
+ default: true,
68
+ },
69
+ ],
70
+ };
71
+
72
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
73
+ const items = this.getInputData();
74
+ const out: INodeExecutionData[] = [];
75
+
76
+ const baseUrl = 'https://app.chatflow.kz/';
77
+ const credentials = await this.getCredentials('chatflowApi');
78
+ const token = (credentials as any).token as string;
79
+ const operation = this.getNodeParameter('operation', 0) as MessageType;
80
+ const continueOnFail = this.getNodeParameter('continueOnFail', 0) as boolean;
81
+
82
+ const endpointMap: Record<MessageType, string> = {
83
+ text: 'api/v1/send-text',
84
+ audio: 'api/v1/send-audio',
85
+ video: 'api/v1/send-video',
86
+ document: 'api/v1/send-doc',
87
+ image: 'api/v1/send-image',
88
+ };
89
+
90
+ const urlJoin = (a: string, b: string) => a.replace(/\/+$/, '') + '/' + b.replace(/^\/+/, '');
91
+
92
+ for (let i = 0; i < items.length; i++) {
93
+ try {
94
+ const instance_id = this.getNodeParameter('instance_id', i) as string;
95
+ const jid = this.getNodeParameter('jid', i) as string;
96
+
97
+ const qs: Record<string, string> = { token, instance_id, jid };
98
+ switch (operation) {
99
+ case 'text': qs.msg = this.getNodeParameter('msg', i) as string; break;
100
+ case 'audio': qs.audiourl = this.getNodeParameter('audiourl', i) as string; break;
101
+ case 'video':
102
+ qs.videourl = this.getNodeParameter('videourl', i) as string;
103
+ const capV = this.getNodeParameter('caption', i, '') as string; if (capV) qs.caption = capV; break;
104
+ case 'document':
105
+ qs.docurl = this.getNodeParameter('docurl', i) as string;
106
+ const capD = this.getNodeParameter('captionDoc', i, '') as string; if (capD) qs.caption = capD; break;
107
+ case 'image':
108
+ qs.imageurl = this.getNodeParameter('imageurl', i) as string;
109
+ const capI = this.getNodeParameter('captionImg', i, '') as string; if (capI) qs.caption = capI; break;
110
+ }
111
+
112
+ const options = {
113
+ method: 'GET',
114
+ url: urlJoin(baseUrl, endpointMap[operation]),
115
+ qs,
116
+ headers: { Accept: 'application/json' },
117
+ timeout: 10000,
118
+ retry: { times: 1, interval: 500 },
119
+ } as unknown as any;
120
+
121
+ const responseData = await this.helpers.httpRequest(options);
122
+ out.push({ json: responseData });
123
+ } catch (error) {
124
+ if (continueOnFail) {
125
+ out.push({ json: { success: false, message: (error as Error).message || 'Request failed' } });
126
+ continue;
127
+ }
128
+ throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i });
129
+ }
130
+ }
131
+
132
+ return [out];
133
+ }
134
+ }
135
+
package/index.js CHANGED
@@ -1,2 +1,3 @@
1
- // This file is required by n8n to properly load the nodes
1
+ // Entry file is not required by n8n, but kept for completeness
2
2
  module.exports = {};
3
+
@@ -1,4 +1,4 @@
1
- module.exports = class ChatflowMessage {
1
+ class Chatflow {
2
2
  constructor() {
3
3
  this.description = {
4
4
  displayName: 'Chatflow',
@@ -72,7 +72,7 @@ module.exports = class ChatflowMessage {
72
72
  };
73
73
 
74
74
  const urlJoin = (a, b) => a.replace(/\/+$/, '') + '/' + b.replace(/^\/+/, '');
75
- const baseUrl = 'https://app.chatflow.kz';
75
+ const baseUrl = 'https://app.chatflow.kz/';
76
76
 
77
77
  for (let i = 0; i < items.length; i++) {
78
78
  try {
@@ -117,4 +117,7 @@ module.exports = class ChatflowMessage {
117
117
 
118
118
  return [out];
119
119
  }
120
- };
120
+ }
121
+
122
+ exports.Chatflow = Chatflow;
123
+
@@ -0,0 +1,135 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ import { NodeOperationError } from 'n8n-workflow';
3
+
4
+ type MessageType = 'text' | 'audio' | 'video' | 'document' | 'image';
5
+
6
+ export class ChatflowMessage implements INodeType {
7
+ description: INodeTypeDescription = {
8
+ displayName: 'Chatflow',
9
+ name: 'chatflow',
10
+ icon: 'file:chatflow.png',
11
+ group: ['output'],
12
+ version: 1,
13
+ subtitle: '={{$parameter["operation"]}}',
14
+ description: 'Send WhatsApp messages via Chatflow API',
15
+ defaults: { name: 'Chatflow Message' },
16
+ inputs: [
17
+ { type: 'main' as any },
18
+ ],
19
+ outputs: [
20
+ { type: 'main' as any },
21
+ ],
22
+ credentials: [
23
+ {
24
+ name: 'chatflowApi',
25
+ required: true,
26
+ },
27
+ ],
28
+ properties: [
29
+ {
30
+ displayName: 'Operation',
31
+ name: 'operation',
32
+ type: 'options',
33
+ options: [
34
+ { name: 'Text', value: 'text' },
35
+ { name: 'Audio', value: 'audio' },
36
+ { name: 'Video', value: 'video' },
37
+ { name: 'Document', value: 'document' },
38
+ { name: 'Image', value: 'image' },
39
+ ],
40
+ default: 'text',
41
+ },
42
+ { displayName: 'Instance ID', name: 'instance_id', type: 'string', default: '', required: true },
43
+ { displayName: 'JID', name: 'jid', type: 'string', default: '', required: true },
44
+
45
+ // Text
46
+ { displayName: 'Message', name: 'msg', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['text'] } } },
47
+
48
+ // Audio
49
+ { displayName: 'Audio URL', name: 'audiourl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['audio'] } } },
50
+
51
+ // Video
52
+ { displayName: 'Video URL', name: 'videourl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['video'] } } },
53
+ { displayName: 'Caption', name: 'caption', type: 'string', default: '', displayOptions: { show: { operation: ['video'] } } },
54
+
55
+ // Document
56
+ { displayName: 'Document URL', name: 'docurl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['document'] } } },
57
+ { displayName: 'Caption', name: 'captionDoc', type: 'string', default: '', displayOptions: { show: { operation: ['document'] } } },
58
+
59
+ // Image
60
+ { displayName: 'Image URL', name: 'imageurl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['image'] } } },
61
+ { displayName: 'Caption', name: 'captionImg', type: 'string', default: '', displayOptions: { show: { operation: ['image'] } } },
62
+
63
+ {
64
+ displayName: 'Continue On Fail',
65
+ name: 'continueOnFail',
66
+ type: 'boolean',
67
+ default: true,
68
+ },
69
+ ],
70
+ };
71
+
72
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
73
+ const items = this.getInputData();
74
+ const out: INodeExecutionData[] = [];
75
+
76
+ const baseUrl = 'https://app.chatflow.kz/';
77
+ const credentials = await this.getCredentials('chatflowApi');
78
+ const token = (credentials as any).token as string;
79
+ const operation = this.getNodeParameter('operation', 0) as MessageType;
80
+ const continueOnFail = this.getNodeParameter('continueOnFail', 0) as boolean;
81
+
82
+ const endpointMap: Record<MessageType, string> = {
83
+ text: 'api/v1/send-text',
84
+ audio: 'api/v1/send-audio',
85
+ video: 'api/v1/send-video',
86
+ document: 'api/v1/send-doc',
87
+ image: 'api/v1/send-image',
88
+ };
89
+
90
+ const urlJoin = (a: string, b: string) => a.replace(/\/+$/, '') + '/' + b.replace(/^\/+/, '');
91
+
92
+ for (let i = 0; i < items.length; i++) {
93
+ try {
94
+ const instance_id = this.getNodeParameter('instance_id', i) as string;
95
+ const jid = this.getNodeParameter('jid', i) as string;
96
+
97
+ const qs: Record<string, string> = { token, instance_id, jid };
98
+ switch (operation) {
99
+ case 'text': qs.msg = this.getNodeParameter('msg', i) as string; break;
100
+ case 'audio': qs.audiourl = this.getNodeParameter('audiourl', i) as string; break;
101
+ case 'video':
102
+ qs.videourl = this.getNodeParameter('videourl', i) as string;
103
+ const capV = this.getNodeParameter('caption', i, '') as string; if (capV) qs.caption = capV; break;
104
+ case 'document':
105
+ qs.docurl = this.getNodeParameter('docurl', i) as string;
106
+ const capD = this.getNodeParameter('captionDoc', i, '') as string; if (capD) qs.caption = capD; break;
107
+ case 'image':
108
+ qs.imageurl = this.getNodeParameter('imageurl', i) as string;
109
+ const capI = this.getNodeParameter('captionImg', i, '') as string; if (capI) qs.caption = capI; break;
110
+ }
111
+
112
+ const options = {
113
+ method: 'GET',
114
+ url: urlJoin(baseUrl, endpointMap[operation]),
115
+ qs,
116
+ headers: { Accept: 'application/json' },
117
+ timeout: 10000,
118
+ retry: { times: 1, interval: 500 },
119
+ } as unknown as any;
120
+
121
+ const responseData = await this.helpers.httpRequest(options);
122
+ out.push({ json: responseData });
123
+ } catch (error) {
124
+ if (continueOnFail) {
125
+ out.push({ json: { success: false, message: (error as Error).message || 'Request failed' } });
126
+ continue;
127
+ }
128
+ throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i });
129
+ }
130
+ }
131
+
132
+ return [out];
133
+ }
134
+ }
135
+
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "n8n-nodes-chatflow",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "n8n community node to send WhatsApp messages via Chatflow (text, audio, video, document, image)",
5
5
  "author": "Chatflow",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "n8n-community-node-package",
9
- "n8n-nodes",
10
9
  "chatflow",
11
10
  "whatsapp",
12
11
  "messaging"
@@ -24,8 +23,20 @@
24
23
  ]
25
24
  },
26
25
  "main": "dist/index.js",
26
+ "files": [
27
+ "dist",
28
+ "nodes",
29
+ "credentials",
30
+ "index.js",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
27
34
  "scripts": {
28
- "build": "mkdir -p dist && cp -r nodes credentials index.js dist/ || true",
29
- "clean": "rm -rf dist"
35
+ "build": "rimraf dist && cpx \"nodes/**/*\" dist/nodes && cpx \"credentials/**/*\" dist/credentials && cpx \"index.js\" dist/",
36
+ "clean": "rimraf dist"
37
+ },
38
+ "devDependencies": {
39
+ "cpx": "^1.5.0",
40
+ "rimraf": "^5.0.5"
30
41
  }
31
42
  }
Binary file
Binary file