@kichat/n8n-nodes-kirimchat 1.0.4 → 1.1.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 CHANGED
@@ -1,139 +1,244 @@
1
- # n8n-nodes-kirimchat
2
-
3
- This is an n8n community node for [KirimChat](https://kirim.chat) - a messaging platform that integrates WhatsApp Business API and Instagram DM.
4
-
5
- ## Features
6
-
7
- - **Send Message** - Send text, image, document, audio, video, or template messages via WhatsApp or Instagram
8
- - **Mark as Read** - Mark messages as read and send read receipts
9
- - **Send Typing Indicator** - Show typing indicator to customers
10
-
11
- ## Installation
12
-
13
- ### Community Nodes (Recommended)
14
-
15
- 1. Go to **Settings > Community Nodes**
16
- 2. Select **Install**
17
- 3. Enter `@n8n-nodes-kirimchat` and confirm
18
-
19
- ## Credentials
20
-
21
- You need a KirimChat API key to use this node:
22
-
23
- 1. Log in to your KirimChat dashboard
24
- 2. Go to **Settings > Developers > API Keys**
25
- 3. Click **Create API Key** and copy the key (shown only once)
26
- 4. In n8n, create new credentials for **KirimChat API**
27
- 5. Paste your API key (starts with `kc_live_`)
28
-
29
- ## Operations
30
-
31
- ### Send Message
32
-
33
- Send a message to a customer via WhatsApp or Instagram.
34
-
35
- | Parameter | Description |
36
- |-----------|-------------|
37
- | Customer ID | The ID of the customer (e.g., `cust_abc123`) |
38
- | Channel | `whatsapp` or `instagram` |
39
- | Message Type | `text`, `image`, `document`, `audio`, `video`, `template` (WhatsApp) or `text`, `image`, `media_share` (Instagram) |
40
- | Content | Text content for text messages |
41
- | Media URL | URL of media file for media messages |
42
- | Caption | Optional caption for media messages |
43
-
44
- **Example Response:**
45
- ```json
46
- {
47
- "success": true,
48
- "data": {
49
- "message_id": "msg_xyz789",
50
- "status": "sent",
51
- "channel": "whatsapp",
52
- "timestamp": "2025-11-26T10:31:00.000Z"
53
- }
54
- }
55
- ```
56
-
57
- ### Mark as Read
58
-
59
- Mark a message as read and send read receipt to the customer.
60
-
61
- | Parameter | Description |
62
- |-----------|-------------|
63
- | Message ID | The ID of the message to mark as read (e.g., `msg_xyz789`) |
64
-
65
- ### Send Typing Indicator
66
-
67
- Show typing indicator to a customer before sending a message.
68
-
69
- | Parameter | Description |
70
- |-----------|-------------|
71
- | Customer ID | The ID of the customer |
72
- | Channel | `whatsapp` or `instagram` |
73
-
74
- > **Note:** Rate limited to 1 request per customer per 3 seconds.
75
-
76
- ## Webhook Trigger
77
-
78
- KirimChat supports outbound webhooks for real-time event notifications. Configure webhooks in your KirimChat dashboard under **Settings > Developers > Webhooks**.
79
-
80
- ### Supported Events
81
-
82
- - `message.received` - New message from customer
83
- - `message.sent` - Message sent to customer
84
- - `message.delivered` - Message delivered
85
- - `message.read` - Message read by customer
86
- - `message.failed` - Message delivery failed
87
-
88
- ### Webhook Payload Example
89
-
90
- ```json
91
- {
92
- "event_type": "message.received",
93
- "event_id": "evt_abc123",
94
- "timestamp": "2025-11-26T10:30:00.000Z",
95
- "data": {
96
- "message_id": "msg_xyz789",
97
- "customer_id": "cust_123",
98
- "customer_phone": "+6281234567890",
99
- "direction": "inbound",
100
- "message_type": "text",
101
- "content": "Hello!",
102
- "channel": "whatsapp"
103
- }
104
- }
105
- ```
106
-
107
- ### Using with n8n Webhook Node
108
-
109
- 1. Create a new workflow with **Webhook** trigger node
110
- 2. Copy the webhook URL
111
- 3. In KirimChat, create a webhook endpoint with this URL
112
- 4. Select the events you want to receive
113
- 5. Connect the webhook to your KirimChat node for automated responses
114
-
115
- ## Rate Limits
116
-
117
- - **Global:** 100 requests per minute per API key
118
- - **Send Message:** 60 messages per minute per API key
119
- - **Typing Indicator:** 1 request per customer per 3 seconds
120
-
121
- ## Error Handling
122
-
123
- The node returns standard error responses:
124
-
125
- | Code | Description |
126
- |------|-------------|
127
- | 401 | Invalid or expired API key |
128
- | 404 | Resource not found |
129
- | 400 | Invalid request or messaging window closed |
130
- | 429 | Rate limit exceeded |
131
-
132
- ## Resources
133
-
134
- - [KirimChat Documentation](https://kirim.chat/developers)
135
- - [n8n Community Nodes](https://docs.n8n.io/integrations/community-nodes/)
136
-
137
- ## License
138
-
139
- MIT
1
+ # n8n-nodes-kirimchat
2
+
3
+ This is an n8n community node for [KirimChat](https://kirim.chat) - a messaging platform that integrates WhatsApp Business API, Instagram DM, and Facebook Messenger.
4
+
5
+ ## Features
6
+
7
+ - **Send Message** - Send text, image, document, audio, video, template, or interactive messages via WhatsApp, Instagram, or Messenger
8
+ - **Mark as Read** - Mark messages as read and send read receipts
9
+ - **Send Typing Indicator** - Show typing indicator to customers
10
+ - **Flexible Customer Lookup** - Find customers by ID, phone number, or Instagram username
11
+
12
+ ## Installation
13
+
14
+ ### Community Nodes (Recommended)
15
+
16
+ 1. Go to **Settings > Community Nodes**
17
+ 2. Select **Install**
18
+ 3. Enter `@kichat/n8n-nodes-kirimchat` and confirm
19
+
20
+ ## Credentials
21
+
22
+ You need a KirimChat API key to use this node:
23
+
24
+ 1. Log in to your KirimChat dashboard
25
+ 2. Go to **Settings > Developers > API Keys**
26
+ 3. Click **Create API Key** and copy the key (shown only once)
27
+ 4. In n8n, create new credentials for **KirimChat API**
28
+ 5. Paste your API key (starts with `kc_live_`)
29
+
30
+ ## Operations
31
+
32
+ ### Send Message
33
+
34
+ Send a message to a customer via WhatsApp, Instagram, or Messenger.
35
+
36
+ #### Customer Lookup Options
37
+
38
+ | Method | Description | Example |
39
+ |--------|-------------|---------|
40
+ | Customer ID | KirimChat customer ID | `cust_abc123` |
41
+ | Phone Number | Phone with country code | `+6281234567890` |
42
+ | Instagram Username | Instagram username | `johndoe` |
43
+
44
+ #### Channels & Message Types
45
+
46
+ | Channel | Message Types |
47
+ |---------|---------------|
48
+ | **WhatsApp** | `text`, `image`, `document`, `audio`, `video`, `template`, `interactive` |
49
+ | **Instagram** | `text`, `image`, `media_share` |
50
+ | **Messenger** | `text`, `image`, `video`, `audio`, `file` |
51
+
52
+ #### Parameters
53
+
54
+ | Parameter | Description |
55
+ |-----------|-------------|
56
+ | Customer Lookup | How to identify the customer |
57
+ | Channel | `whatsapp`, `instagram`, or `messenger` |
58
+ | Message Type | Type of message (varies by channel) |
59
+ | Content | Text content for text messages |
60
+ | Media URL | URL of media file for media messages |
61
+ | Caption | Optional caption for media messages |
62
+ | Filename | Filename for document/file messages |
63
+
64
+ #### WhatsApp Template Messages
65
+
66
+ | Parameter | Description |
67
+ |-----------|-------------|
68
+ | Template Name | Name of the approved template |
69
+ | Template Language | Language code (e.g., `en`, `id`) |
70
+ | Template Components | JSON array for variables and buttons |
71
+
72
+ **Example Template Components:**
73
+ ```json
74
+ [
75
+ {
76
+ "type": "body",
77
+ "parameters": [
78
+ { "type": "text", "text": "John" },
79
+ { "type": "text", "text": "12345" }
80
+ ]
81
+ }
82
+ ]
83
+ ```
84
+
85
+ #### WhatsApp Interactive Messages
86
+
87
+ Send buttons with your messages:
88
+
89
+ **CTA URL Button:**
90
+ | Parameter | Description |
91
+ |-----------|-------------|
92
+ | Interactive Type | `cta_url` |
93
+ | Body Text | Main message text (max 1024 chars) |
94
+ | Header Text | Optional header (max 60 chars) |
95
+ | Footer Text | Optional footer (max 60 chars) |
96
+ | Button Text | Text on button (max 20 chars) |
97
+ | Button URL | URL to open |
98
+
99
+ **Reply Buttons:**
100
+ | Parameter | Description |
101
+ |-----------|-------------|
102
+ | Interactive Type | `button` |
103
+ | Body Text | Main message text |
104
+ | Buttons (JSON) | Array of 1-3 buttons |
105
+
106
+ **Example Reply Buttons:**
107
+ ```json
108
+ [
109
+ { "id": "yes", "title": "Yes" },
110
+ { "id": "no", "title": "No" },
111
+ { "id": "maybe", "title": "Maybe" }
112
+ ]
113
+ ```
114
+
115
+ **Example Response:**
116
+ ```json
117
+ {
118
+ "success": true,
119
+ "data": {
120
+ "message_id": "msg_xyz789",
121
+ "status": "sent",
122
+ "channel": "whatsapp",
123
+ "timestamp": "2025-11-26T10:31:00.000Z"
124
+ }
125
+ }
126
+ ```
127
+
128
+ ### Mark as Read
129
+
130
+ Mark a message as read and send read receipt to the customer.
131
+
132
+ | Parameter | Description |
133
+ |-----------|-------------|
134
+ | Message ID | The ID of the message to mark as read (e.g., `msg_xyz789`) |
135
+
136
+ ### Send Typing Indicator
137
+
138
+ Show typing indicator to a customer before sending a message.
139
+
140
+ | Parameter | Description |
141
+ |-----------|-------------|
142
+ | Customer Lookup | How to identify the customer |
143
+ | Customer ID/Phone/Username | Customer identifier |
144
+ | Channel | `whatsapp`, `instagram`, or auto-detect |
145
+
146
+ > **Note:** Rate limited to 1 request per customer per 3 seconds.
147
+
148
+ ## Webhook Trigger
149
+
150
+ KirimChat supports outbound webhooks for real-time event notifications. Configure webhooks in your KirimChat dashboard under **Settings > Developers > Webhooks**.
151
+
152
+ ### Supported Events
153
+
154
+ - `message.received` - New message from customer
155
+ - `message.sent` - Message sent to customer
156
+ - `message.delivered` - Message delivered
157
+ - `message.read` - Message read by customer
158
+ - `message.failed` - Message delivery failed
159
+
160
+ ### Webhook Payload Example
161
+
162
+ ```json
163
+ {
164
+ "event_type": "message.received",
165
+ "event_id": "evt_abc123",
166
+ "timestamp": "2025-11-26T10:30:00.000Z",
167
+ "data": {
168
+ "message_id": "msg_xyz789",
169
+ "customer_id": "cust_123",
170
+ "customer_phone": "+6281234567890",
171
+ "direction": "inbound",
172
+ "message_type": "text",
173
+ "content": "Hello!",
174
+ "channel": "whatsapp"
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### Using with n8n Webhook Node
180
+
181
+ 1. Create a new workflow with **Webhook** trigger node
182
+ 2. Copy the webhook URL
183
+ 3. In KirimChat, create a webhook endpoint with this URL
184
+ 4. Select the events you want to receive
185
+ 5. Connect the webhook to your KirimChat node for automated responses
186
+
187
+ ## Rate Limits
188
+
189
+ | Limit Type | Rate |
190
+ |------------|------|
191
+ | Global | 100 requests per minute per API key |
192
+ | WhatsApp | 60 messages per minute |
193
+ | Instagram | 180 messages per hour |
194
+ | Messenger | 180 messages per hour |
195
+ | Typing Indicator | 1 request per customer per 3 seconds |
196
+
197
+ ## Error Handling
198
+
199
+ The node returns standard error responses:
200
+
201
+ | Code | Description |
202
+ |------|-------------|
203
+ | 401 | Invalid or expired API key |
204
+ | 403 | Account disconnected or forbidden |
205
+ | 404 | Resource not found |
206
+ | 400 | Invalid request or messaging window closed |
207
+ | 429 | Rate limit exceeded |
208
+
209
+ ### Common Error Codes
210
+
211
+ | Error Code | Description |
212
+ |------------|-------------|
213
+ | `ValidationError` | Invalid input parameters |
214
+ | `WindowClosed` | 24-hour messaging window expired |
215
+ | `ConfigurationError` | Channel not configured |
216
+ | `ConnectionError` | Account disconnected |
217
+ | `RateLimitExceeded` | Too many requests |
218
+
219
+ ## Changelog
220
+
221
+ ### v1.1.0 (Latest)
222
+ - ✅ Added Messenger channel support
223
+ - ✅ Added customer lookup by phone number and Instagram username
224
+ - ✅ Added WhatsApp Interactive messages (CTA URL buttons, Reply buttons)
225
+ - ✅ Improved typing indicator with flexible customer lookup
226
+ - ✅ Enhanced error messages
227
+
228
+ ### v1.0.5
229
+ - Enhanced validation for media URLs
230
+ - Template components structure validation
231
+
232
+ ### v1.0.1
233
+ - Initial security improvements
234
+ - JSON validation for templates
235
+ - Base URL security warning
236
+
237
+ ## Resources
238
+
239
+ - [KirimChat Documentation](https://kirim.chat/developers)
240
+ - [n8n Community Nodes](https://docs.n8n.io/integrations/community-nodes/)
241
+
242
+ ## License
243
+
244
+ MIT
@@ -2,6 +2,45 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KirimChat = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
+ /**
6
+ * Validates if a string is a valid URL
7
+ */
8
+ function isValidUrl(urlString) {
9
+ try {
10
+ const url = new URL(urlString);
11
+ return url.protocol === 'http:' || url.protocol === 'https:';
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ /**
18
+ * Validates template components structure
19
+ */
20
+ function validateTemplateComponents(components) {
21
+ if (!Array.isArray(components)) {
22
+ return false;
23
+ }
24
+ return components.every((component) => typeof component === 'object' && component !== null);
25
+ }
26
+ /**
27
+ * Validates interactive message structure
28
+ */
29
+ function validateInteractiveMessage(interactive) {
30
+ if (typeof interactive !== 'object' || interactive === null) {
31
+ return false;
32
+ }
33
+ const obj = interactive;
34
+ // Must have type and body
35
+ if (!obj.type || !obj.body) {
36
+ return false;
37
+ }
38
+ // type must be 'cta_url' or 'button'
39
+ if (obj.type !== 'cta_url' && obj.type !== 'button') {
40
+ return false;
41
+ }
42
+ return true;
43
+ }
5
44
  class KirimChat {
6
45
  constructor() {
7
46
  this.description = {
@@ -11,7 +50,7 @@ class KirimChat {
11
50
  group: ['transform'],
12
51
  version: 1,
13
52
  subtitle: '={{$parameter["operation"]}}',
14
- description: 'Send WhatsApp & Instagram messages, mark as read, and send typing indicators',
53
+ description: 'Send WhatsApp, Instagram & Messenger messages, mark as read, and send typing indicators',
15
54
  defaults: {
16
55
  name: 'KirimChat',
17
56
  },
@@ -33,7 +72,7 @@ class KirimChat {
33
72
  {
34
73
  name: 'Send Message',
35
74
  value: 'sendMessage',
36
- description: 'Send a message to a customer via WhatsApp or Instagram',
75
+ description: 'Send a message to a customer via WhatsApp, Instagram, or Messenger',
37
76
  action: 'Send a message',
38
77
  },
39
78
  {
@@ -51,7 +90,40 @@ class KirimChat {
51
90
  ],
52
91
  default: 'sendMessage',
53
92
  },
54
- // Send Message Fields
93
+ // ============================================
94
+ // SEND MESSAGE FIELDS
95
+ // ============================================
96
+ // Customer Lookup Method
97
+ {
98
+ displayName: 'Customer Lookup',
99
+ name: 'customerLookup',
100
+ type: 'options',
101
+ required: true,
102
+ displayOptions: {
103
+ show: {
104
+ operation: ['sendMessage'],
105
+ },
106
+ },
107
+ options: [
108
+ {
109
+ name: 'Customer ID',
110
+ value: 'customer_id',
111
+ description: 'Lookup by KirimChat customer ID (e.g., cust_abc123)',
112
+ },
113
+ {
114
+ name: 'Phone Number',
115
+ value: 'phone_number',
116
+ description: 'Lookup by phone number (e.g., +6281234567890)',
117
+ },
118
+ {
119
+ name: 'Instagram Username',
120
+ value: 'instagram_username',
121
+ description: 'Lookup by Instagram username',
122
+ },
123
+ ],
124
+ default: 'customer_id',
125
+ description: 'How to identify the customer',
126
+ },
55
127
  {
56
128
  displayName: 'Customer ID',
57
129
  name: 'customerId',
@@ -60,12 +132,44 @@ class KirimChat {
60
132
  displayOptions: {
61
133
  show: {
62
134
  operation: ['sendMessage'],
135
+ customerLookup: ['customer_id'],
63
136
  },
64
137
  },
65
138
  default: '',
66
139
  description: 'The ID of the customer to send the message to',
67
140
  placeholder: 'cust_abc123',
68
141
  },
142
+ {
143
+ displayName: 'Phone Number',
144
+ name: 'phoneNumber',
145
+ type: 'string',
146
+ required: true,
147
+ displayOptions: {
148
+ show: {
149
+ operation: ['sendMessage'],
150
+ customerLookup: ['phone_number'],
151
+ },
152
+ },
153
+ default: '',
154
+ description: 'The phone number of the customer (with country code)',
155
+ placeholder: '+6281234567890',
156
+ },
157
+ {
158
+ displayName: 'Instagram Username',
159
+ name: 'instagramUsername',
160
+ type: 'string',
161
+ required: true,
162
+ displayOptions: {
163
+ show: {
164
+ operation: ['sendMessage'],
165
+ customerLookup: ['instagram_username'],
166
+ },
167
+ },
168
+ default: '',
169
+ description: 'The Instagram username of the customer',
170
+ placeholder: 'username',
171
+ },
172
+ // Channel Selection
69
173
  {
70
174
  displayName: 'Channel',
71
175
  name: 'channel',
@@ -85,10 +189,15 @@ class KirimChat {
85
189
  name: 'Instagram',
86
190
  value: 'instagram',
87
191
  },
192
+ {
193
+ name: 'Messenger',
194
+ value: 'messenger',
195
+ },
88
196
  ],
89
197
  default: 'whatsapp',
90
198
  description: 'The messaging channel to use',
91
199
  },
200
+ // Message Type - WhatsApp
92
201
  {
93
202
  displayName: 'Message Type',
94
203
  name: 'messageType',
@@ -125,10 +234,15 @@ class KirimChat {
125
234
  name: 'Template',
126
235
  value: 'template',
127
236
  },
237
+ {
238
+ name: 'Interactive (Buttons)',
239
+ value: 'interactive',
240
+ },
128
241
  ],
129
242
  default: 'text',
130
243
  description: 'The type of message to send (WhatsApp)',
131
244
  },
245
+ // Message Type - Instagram
132
246
  {
133
247
  displayName: 'Message Type',
134
248
  name: 'messageType',
@@ -157,6 +271,44 @@ class KirimChat {
157
271
  default: 'text',
158
272
  description: 'The type of message to send (Instagram)',
159
273
  },
274
+ // Message Type - Messenger
275
+ {
276
+ displayName: 'Message Type',
277
+ name: 'messageType',
278
+ type: 'options',
279
+ required: true,
280
+ displayOptions: {
281
+ show: {
282
+ operation: ['sendMessage'],
283
+ channel: ['messenger'],
284
+ },
285
+ },
286
+ options: [
287
+ {
288
+ name: 'Text',
289
+ value: 'text',
290
+ },
291
+ {
292
+ name: 'Image',
293
+ value: 'image',
294
+ },
295
+ {
296
+ name: 'Video',
297
+ value: 'video',
298
+ },
299
+ {
300
+ name: 'Audio',
301
+ value: 'audio',
302
+ },
303
+ {
304
+ name: 'File',
305
+ value: 'file',
306
+ },
307
+ ],
308
+ default: 'text',
309
+ description: 'The type of message to send (Messenger)',
310
+ },
311
+ // Text Content
160
312
  {
161
313
  displayName: 'Message Content',
162
314
  name: 'content',
@@ -174,19 +326,23 @@ class KirimChat {
174
326
  rows: 4,
175
327
  },
176
328
  },
329
+ // Media URL (for all media types)
177
330
  {
178
331
  displayName: 'Media URL',
179
332
  name: 'mediaUrl',
180
333
  type: 'string',
334
+ required: true,
181
335
  displayOptions: {
182
336
  show: {
183
337
  operation: ['sendMessage'],
184
- messageType: ['image', 'document', 'audio', 'video', 'media_share'],
338
+ messageType: ['image', 'document', 'audio', 'video', 'media_share', 'file'],
185
339
  },
186
340
  },
187
341
  default: '',
188
- description: 'URL of the media file to send',
342
+ description: 'URL of the media file to send (must be publicly accessible)',
343
+ placeholder: 'https://example.com/image.jpg',
189
344
  },
345
+ // Caption (for media messages)
190
346
  {
191
347
  displayName: 'Caption',
192
348
  name: 'caption',
@@ -200,6 +356,7 @@ class KirimChat {
200
356
  default: '',
201
357
  description: 'Optional caption for media messages',
202
358
  },
359
+ // Filename (for documents)
203
360
  {
204
361
  displayName: 'Filename',
205
362
  name: 'filename',
@@ -207,14 +364,16 @@ class KirimChat {
207
364
  displayOptions: {
208
365
  show: {
209
366
  operation: ['sendMessage'],
210
- messageType: ['document'],
367
+ messageType: ['document', 'file'],
211
368
  },
212
369
  },
213
370
  default: '',
214
- description: 'Filename for document messages',
371
+ description: 'Filename for document/file messages',
215
372
  placeholder: 'invoice.pdf',
216
373
  },
217
- // Template fields (WhatsApp only)
374
+ // ============================================
375
+ // TEMPLATE FIELDS (WhatsApp only)
376
+ // ============================================
218
377
  {
219
378
  displayName: 'Template Name',
220
379
  name: 'templateName',
@@ -258,7 +417,130 @@ class KirimChat {
258
417
  default: '[]',
259
418
  description: 'Template components as JSON array (for variables, buttons, etc.)',
260
419
  },
261
- // Mark as Read Fields
420
+ // ============================================
421
+ // INTERACTIVE MESSAGE FIELDS (WhatsApp only)
422
+ // ============================================
423
+ {
424
+ displayName: 'Interactive Type',
425
+ name: 'interactiveType',
426
+ type: 'options',
427
+ required: true,
428
+ displayOptions: {
429
+ show: {
430
+ operation: ['sendMessage'],
431
+ messageType: ['interactive'],
432
+ },
433
+ },
434
+ options: [
435
+ {
436
+ name: 'CTA URL Button',
437
+ value: 'cta_url',
438
+ description: 'Call-to-action button that opens a URL',
439
+ },
440
+ {
441
+ name: 'Reply Buttons',
442
+ value: 'button',
443
+ description: 'Up to 3 reply buttons',
444
+ },
445
+ ],
446
+ default: 'cta_url',
447
+ description: 'Type of interactive message',
448
+ },
449
+ {
450
+ displayName: 'Body Text',
451
+ name: 'interactiveBody',
452
+ type: 'string',
453
+ required: true,
454
+ displayOptions: {
455
+ show: {
456
+ operation: ['sendMessage'],
457
+ messageType: ['interactive'],
458
+ },
459
+ },
460
+ default: '',
461
+ description: 'Main body text of the interactive message (max 1024 characters)',
462
+ typeOptions: {
463
+ rows: 3,
464
+ },
465
+ },
466
+ {
467
+ displayName: 'Header Text',
468
+ name: 'interactiveHeader',
469
+ type: 'string',
470
+ displayOptions: {
471
+ show: {
472
+ operation: ['sendMessage'],
473
+ messageType: ['interactive'],
474
+ },
475
+ },
476
+ default: '',
477
+ description: 'Optional header text (max 60 characters)',
478
+ },
479
+ {
480
+ displayName: 'Footer Text',
481
+ name: 'interactiveFooter',
482
+ type: 'string',
483
+ displayOptions: {
484
+ show: {
485
+ operation: ['sendMessage'],
486
+ messageType: ['interactive'],
487
+ },
488
+ },
489
+ default: '',
490
+ description: 'Optional footer text (max 60 characters)',
491
+ },
492
+ // CTA URL specific fields
493
+ {
494
+ displayName: 'Button Text',
495
+ name: 'ctaButtonText',
496
+ type: 'string',
497
+ required: true,
498
+ displayOptions: {
499
+ show: {
500
+ operation: ['sendMessage'],
501
+ messageType: ['interactive'],
502
+ interactiveType: ['cta_url'],
503
+ },
504
+ },
505
+ default: '',
506
+ description: 'Text displayed on the button (max 20 characters)',
507
+ placeholder: 'Visit Website',
508
+ },
509
+ {
510
+ displayName: 'Button URL',
511
+ name: 'ctaButtonUrl',
512
+ type: 'string',
513
+ required: true,
514
+ displayOptions: {
515
+ show: {
516
+ operation: ['sendMessage'],
517
+ messageType: ['interactive'],
518
+ interactiveType: ['cta_url'],
519
+ },
520
+ },
521
+ default: '',
522
+ description: 'URL to open when button is clicked',
523
+ placeholder: 'https://example.com',
524
+ },
525
+ // Reply buttons specific fields
526
+ {
527
+ displayName: 'Buttons (JSON)',
528
+ name: 'replyButtons',
529
+ type: 'json',
530
+ required: true,
531
+ displayOptions: {
532
+ show: {
533
+ operation: ['sendMessage'],
534
+ messageType: ['interactive'],
535
+ interactiveType: ['button'],
536
+ },
537
+ },
538
+ default: '[{"id": "btn1", "title": "Yes"}, {"id": "btn2", "title": "No"}]',
539
+ description: 'Array of buttons (1-3). Each button needs "id" (max 256 chars) and "title" (max 20 chars)',
540
+ },
541
+ // ============================================
542
+ // MARK AS READ FIELDS
543
+ // ============================================
262
544
  {
263
545
  displayName: 'Message ID',
264
546
  name: 'messageId',
@@ -273,32 +555,99 @@ class KirimChat {
273
555
  description: 'The ID of the message to mark as read',
274
556
  placeholder: 'msg_xyz789',
275
557
  },
276
- // Send Typing Fields
558
+ // ============================================
559
+ // SEND TYPING FIELDS
560
+ // ============================================
561
+ {
562
+ displayName: 'Customer Lookup',
563
+ name: 'typingCustomerLookup',
564
+ type: 'options',
565
+ required: true,
566
+ displayOptions: {
567
+ show: {
568
+ operation: ['sendTyping'],
569
+ },
570
+ },
571
+ options: [
572
+ {
573
+ name: 'Customer ID',
574
+ value: 'customer_id',
575
+ description: 'Lookup by KirimChat customer ID',
576
+ },
577
+ {
578
+ name: 'Phone Number',
579
+ value: 'phone_number',
580
+ description: 'Lookup by phone number',
581
+ },
582
+ {
583
+ name: 'Instagram Username',
584
+ value: 'instagram_username',
585
+ description: 'Lookup by Instagram username',
586
+ },
587
+ ],
588
+ default: 'customer_id',
589
+ description: 'How to identify the customer',
590
+ },
277
591
  {
278
592
  displayName: 'Customer ID',
279
- name: 'customerId',
593
+ name: 'typingCustomerId',
280
594
  type: 'string',
281
595
  required: true,
282
596
  displayOptions: {
283
597
  show: {
284
598
  operation: ['sendTyping'],
599
+ typingCustomerLookup: ['customer_id'],
285
600
  },
286
601
  },
287
602
  default: '',
288
603
  description: 'The ID of the customer to send typing indicator to',
289
604
  placeholder: 'cust_abc123',
290
605
  },
606
+ {
607
+ displayName: 'Phone Number',
608
+ name: 'typingPhoneNumber',
609
+ type: 'string',
610
+ required: true,
611
+ displayOptions: {
612
+ show: {
613
+ operation: ['sendTyping'],
614
+ typingCustomerLookup: ['phone_number'],
615
+ },
616
+ },
617
+ default: '',
618
+ description: 'The phone number of the customer',
619
+ placeholder: '+6281234567890',
620
+ },
621
+ {
622
+ displayName: 'Instagram Username',
623
+ name: 'typingInstagramUsername',
624
+ type: 'string',
625
+ required: true,
626
+ displayOptions: {
627
+ show: {
628
+ operation: ['sendTyping'],
629
+ typingCustomerLookup: ['instagram_username'],
630
+ },
631
+ },
632
+ default: '',
633
+ description: 'The Instagram username of the customer',
634
+ placeholder: 'username',
635
+ },
291
636
  {
292
637
  displayName: 'Channel',
293
- name: 'channel',
638
+ name: 'typingChannel',
294
639
  type: 'options',
295
- required: true,
296
640
  displayOptions: {
297
641
  show: {
298
642
  operation: ['sendTyping'],
299
643
  },
300
644
  },
301
645
  options: [
646
+ {
647
+ name: 'Auto-detect',
648
+ value: '',
649
+ description: 'Automatically detect based on customer data',
650
+ },
302
651
  {
303
652
  name: 'WhatsApp',
304
653
  value: 'whatsapp',
@@ -308,8 +657,8 @@ class KirimChat {
308
657
  value: 'instagram',
309
658
  },
310
659
  ],
311
- default: 'whatsapp',
312
- description: 'The messaging channel to use',
660
+ default: '',
661
+ description: 'The messaging channel to use (leave empty for auto-detect)',
313
662
  },
314
663
  ],
315
664
  };
@@ -324,15 +673,27 @@ class KirimChat {
324
673
  const operation = this.getNodeParameter('operation', i);
325
674
  let responseData;
326
675
  if (operation === 'sendMessage') {
327
- // Send Message Operation
328
- const customerId = this.getNodeParameter('customerId', i);
676
+ // ============================================
677
+ // SEND MESSAGE OPERATION
678
+ // ============================================
679
+ const customerLookup = this.getNodeParameter('customerLookup', i);
329
680
  const channel = this.getNodeParameter('channel', i);
330
681
  const messageType = this.getNodeParameter('messageType', i);
331
682
  const body = {
332
- customer_id: customerId,
333
683
  channel,
334
684
  message_type: messageType,
335
685
  };
686
+ // Set customer identifier based on lookup method
687
+ if (customerLookup === 'customer_id') {
688
+ body.customer_id = this.getNodeParameter('customerId', i);
689
+ }
690
+ else if (customerLookup === 'phone_number') {
691
+ body.phone_number = this.getNodeParameter('phoneNumber', i);
692
+ }
693
+ else if (customerLookup === 'instagram_username') {
694
+ body.instagram_username = this.getNodeParameter('instagramUsername', i);
695
+ }
696
+ // Handle different message types
336
697
  if (messageType === 'text') {
337
698
  body.content = this.getNodeParameter('content', i);
338
699
  }
@@ -341,12 +702,16 @@ class KirimChat {
341
702
  const templateName = this.getNodeParameter('templateName', i);
342
703
  const templateLanguage = this.getNodeParameter('templateLanguage', i);
343
704
  const templateComponentsJson = this.getNodeParameter('templateComponents', i, '[]');
344
- let templateComponents = [];
705
+ let templateComponents;
345
706
  try {
346
707
  templateComponents = JSON.parse(templateComponentsJson);
347
708
  }
348
709
  catch (error) {
349
- throw new Error(`Invalid JSON in Template Components: ${error instanceof Error ? error.message : 'Parse failed'}`);
710
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid JSON in Template Components: ${error instanceof Error ? error.message : 'Parse failed'}`, { itemIndex: i });
711
+ }
712
+ // Validate template components structure
713
+ if (!validateTemplateComponents(templateComponents)) {
714
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Template Components must be a JSON array of objects. Example: [{"type": "body", "parameters": [...]}]', { itemIndex: i });
350
715
  }
351
716
  body.template = {
352
717
  name: templateName,
@@ -354,15 +719,90 @@ class KirimChat {
354
719
  components: templateComponents,
355
720
  };
356
721
  }
722
+ else if (messageType === 'interactive') {
723
+ // WhatsApp interactive message
724
+ const interactiveType = this.getNodeParameter('interactiveType', i);
725
+ const interactiveBody = this.getNodeParameter('interactiveBody', i);
726
+ const interactiveHeader = this.getNodeParameter('interactiveHeader', i, '');
727
+ const interactiveFooter = this.getNodeParameter('interactiveFooter', i, '');
728
+ const interactive = {
729
+ type: interactiveType,
730
+ body: { text: interactiveBody },
731
+ };
732
+ // Add optional header
733
+ if (interactiveHeader) {
734
+ interactive.header = {
735
+ type: 'text',
736
+ text: interactiveHeader,
737
+ };
738
+ }
739
+ // Add optional footer
740
+ if (interactiveFooter) {
741
+ interactive.footer = { text: interactiveFooter };
742
+ }
743
+ // Add action based on type
744
+ if (interactiveType === 'cta_url') {
745
+ const ctaButtonText = this.getNodeParameter('ctaButtonText', i);
746
+ const ctaButtonUrl = this.getNodeParameter('ctaButtonUrl', i);
747
+ if (!isValidUrl(ctaButtonUrl)) {
748
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid Button URL format: "${ctaButtonUrl}". URL must start with http:// or https://`, { itemIndex: i });
749
+ }
750
+ interactive.action = {
751
+ name: 'cta_url',
752
+ parameters: {
753
+ display_text: ctaButtonText,
754
+ url: ctaButtonUrl,
755
+ },
756
+ };
757
+ }
758
+ else if (interactiveType === 'button') {
759
+ const replyButtonsJson = this.getNodeParameter('replyButtons', i);
760
+ let replyButtons;
761
+ try {
762
+ replyButtons = JSON.parse(replyButtonsJson);
763
+ }
764
+ catch (error) {
765
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid JSON in Reply Buttons: ${error instanceof Error ? error.message : 'Parse failed'}`, { itemIndex: i });
766
+ }
767
+ if (!Array.isArray(replyButtons) || replyButtons.length === 0 || replyButtons.length > 3) {
768
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Reply Buttons must be a JSON array with 1-3 buttons. Example: [{"id": "btn1", "title": "Yes"}]', { itemIndex: i });
769
+ }
770
+ // Transform to n8n format
771
+ const formattedButtons = replyButtons.map((btn) => ({
772
+ type: 'reply',
773
+ reply: {
774
+ id: btn.id || '',
775
+ title: btn.title || '',
776
+ },
777
+ }));
778
+ interactive.action = {
779
+ buttons: formattedButtons,
780
+ };
781
+ }
782
+ if (!validateInteractiveMessage(interactive)) {
783
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid interactive message structure', { itemIndex: i });
784
+ }
785
+ body.interactive = interactive;
786
+ }
357
787
  else {
358
- // Media messages
359
- body.media_url = this.getNodeParameter('mediaUrl', i, '');
788
+ // Media messages (image, document, audio, video, media_share, file)
789
+ const mediaUrl = this.getNodeParameter('mediaUrl', i);
790
+ // Validate media URL is provided
791
+ if (!mediaUrl) {
792
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Media URL is required for ${messageType} messages`, { itemIndex: i });
793
+ }
794
+ // Validate media URL format
795
+ if (!isValidUrl(mediaUrl)) {
796
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid Media URL format: "${mediaUrl}". URL must start with http:// or https://`, { itemIndex: i });
797
+ }
798
+ body.media_url = mediaUrl;
799
+ // Add caption if provided
360
800
  const caption = this.getNodeParameter('caption', i, '');
361
801
  if (caption) {
362
802
  body.caption = caption;
363
803
  }
364
- // Document filename
365
- if (messageType === 'document') {
804
+ // Add filename for document/file types
805
+ if (messageType === 'document' || messageType === 'file') {
366
806
  const filename = this.getNodeParameter('filename', i, '');
367
807
  if (filename) {
368
808
  body.filename = filename;
@@ -377,7 +817,9 @@ class KirimChat {
377
817
  });
378
818
  }
379
819
  else if (operation === 'markAsRead') {
380
- // Mark as Read Operation
820
+ // ============================================
821
+ // MARK AS READ OPERATION
822
+ // ============================================
381
823
  const messageId = this.getNodeParameter('messageId', i);
382
824
  responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'kirimChatApi', {
383
825
  method: 'POST',
@@ -386,13 +828,30 @@ class KirimChat {
386
828
  });
387
829
  }
388
830
  else if (operation === 'sendTyping') {
389
- // Send Typing Indicator Operation
390
- const customerId = this.getNodeParameter('customerId', i);
391
- const channel = this.getNodeParameter('channel', i);
831
+ // ============================================
832
+ // SEND TYPING INDICATOR OPERATION
833
+ // ============================================
834
+ const typingLookup = this.getNodeParameter('typingCustomerLookup', i);
835
+ const typingChannel = this.getNodeParameter('typingChannel', i, '');
836
+ // Get customer identifier based on lookup method
837
+ let customerIdentifier;
838
+ if (typingLookup === 'customer_id') {
839
+ customerIdentifier = this.getNodeParameter('typingCustomerId', i);
840
+ }
841
+ else if (typingLookup === 'phone_number') {
842
+ customerIdentifier = this.getNodeParameter('typingPhoneNumber', i);
843
+ }
844
+ else {
845
+ customerIdentifier = this.getNodeParameter('typingInstagramUsername', i);
846
+ }
847
+ const typingBody = {};
848
+ if (typingChannel) {
849
+ typingBody.channel = typingChannel;
850
+ }
392
851
  responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'kirimChatApi', {
393
852
  method: 'POST',
394
- url: `${baseUrl}/conversations/${customerId}/typing`,
395
- body: { channel },
853
+ url: `${baseUrl}/conversations/${customerIdentifier}/typing`,
854
+ body: typingBody,
396
855
  json: true,
397
856
  });
398
857
  }
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
- {
2
- "name": "@kichat/n8n-nodes-kirimchat",
3
- "version": "1.0.4",
4
- "description": "n8n community node for KirimChat - Send WhatsApp & Instagram messages, mark as read, and send typing indicators",
5
- "keywords": [
6
- "n8n-community-node-package",
7
- "n8n",
8
- "kirimchat",
9
- "whatsapp",
10
- "instagram",
11
- "messaging",
12
- "chat"
13
- ],
1
+ {
2
+ "name": "@kichat/n8n-nodes-kirimchat",
3
+ "version": "1.1.0",
4
+ "description": "n8n community node for KirimChat - Send WhatsApp, Instagram & Messenger messages with interactive buttons, flexible customer lookup, and typing indicators",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "n8n",
8
+ "kirimchat",
9
+ "whatsapp",
10
+ "instagram",
11
+ "messenger",
12
+ "facebook",
13
+ "messaging",
14
+ "chat",
15
+ "automation"
16
+ ],
14
17
  "license": "MIT",
15
18
  "homepage": "https://kirim.chat",
16
19
  "author": {
@@ -53,4 +56,4 @@
53
56
  "peerDependencies": {
54
57
  "n8n-workflow": "*"
55
58
  }
56
- }
59
+ }