@kichat/n8n-nodes-kirimchat 1.0.5 → 1.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 CHANGED
@@ -1,139 +1,319 @@
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 `@kichat/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
+ - **List Templates** - List all WhatsApp message templates with filtering
9
+ - **Get Template** - Get a specific template by ID
10
+ - **Mark as Read** - Mark messages as read and send read receipts
11
+ - **Send Typing Indicator** - Show typing indicator to customers
12
+ - **Flexible Customer Lookup** - Find customers by ID, phone number, or Instagram username
13
+
14
+ ## Installation
15
+
16
+ ### Community Nodes (Recommended)
17
+
18
+ 1. Go to **Settings > Community Nodes**
19
+ 2. Select **Install**
20
+ 3. Enter `@kichat/n8n-nodes-kirimchat` and confirm
21
+
22
+ ## Credentials
23
+
24
+ You need a KirimChat API key to use this node:
25
+
26
+ 1. Log in to your KirimChat dashboard
27
+ 2. Go to **Settings > Developers > API Keys**
28
+ 3. Click **Create API Key** and copy the key (shown only once)
29
+ 4. In n8n, create new credentials for **KirimChat API**
30
+ 5. Paste your API key (starts with `kc_live_`)
31
+
32
+ ## Operations
33
+
34
+ ### Send Message
35
+
36
+ Send a message to a customer via WhatsApp, Instagram, or Messenger.
37
+
38
+ #### Customer Lookup Options
39
+
40
+ | Method | Description | Example |
41
+ |--------|-------------|---------|
42
+ | Customer ID | KirimChat customer ID | `cust_abc123` |
43
+ | Phone Number | Phone with country code | `+6281234567890` |
44
+ | Instagram Username | Instagram username | `johndoe` |
45
+
46
+ #### Channels & Message Types
47
+
48
+ | Channel | Message Types |
49
+ |---------|---------------|
50
+ | **WhatsApp** | `text`, `image`, `document`, `audio`, `video`, `template`, `interactive` |
51
+ | **Instagram** | `text`, `image`, `media_share` |
52
+ | **Messenger** | `text`, `image`, `video`, `audio`, `file` |
53
+
54
+ #### Parameters
55
+
56
+ | Parameter | Description |
57
+ |-----------|-------------|
58
+ | Customer Lookup | How to identify the customer |
59
+ | Channel | `whatsapp`, `instagram`, or `messenger` |
60
+ | Message Type | Type of message (varies by channel) |
61
+ | Content | Text content for text messages |
62
+ | Media URL | URL of media file for media messages |
63
+ | Caption | Optional caption for media messages |
64
+ | Filename | Filename for document/file messages |
65
+
66
+ #### WhatsApp Template Messages
67
+
68
+ | Parameter | Description |
69
+ |-----------|-------------|
70
+ | Template Name | Name of the approved template |
71
+ | Template Language | Language code (e.g., `en`, `id`) |
72
+ | Template Components | JSON array for variables and buttons |
73
+
74
+ **Example Template Components:**
75
+ ```json
76
+ [
77
+ {
78
+ "type": "body",
79
+ "parameters": [
80
+ { "type": "text", "text": "John" },
81
+ { "type": "text", "text": "12345" }
82
+ ]
83
+ }
84
+ ]
85
+ ```
86
+
87
+ #### WhatsApp Interactive Messages
88
+
89
+ Send buttons with your messages:
90
+
91
+ **CTA URL Button:**
92
+ | Parameter | Description |
93
+ |-----------|-------------|
94
+ | Interactive Type | `cta_url` |
95
+ | Body Text | Main message text (max 1024 chars) |
96
+ | Header Text | Optional header (max 60 chars) |
97
+ | Footer Text | Optional footer (max 60 chars) |
98
+ | Button Text | Text on button (max 20 chars) |
99
+ | Button URL | URL to open |
100
+
101
+ **Reply Buttons:**
102
+ | Parameter | Description |
103
+ |-----------|-------------|
104
+ | Interactive Type | `button` |
105
+ | Body Text | Main message text |
106
+ | Buttons (JSON) | Array of 1-3 buttons |
107
+
108
+ **Example Reply Buttons:**
109
+ ```json
110
+ [
111
+ { "id": "yes", "title": "Yes" },
112
+ { "id": "no", "title": "No" },
113
+ { "id": "maybe", "title": "Maybe" }
114
+ ]
115
+ ```
116
+
117
+ **Example Response:**
118
+ ```json
119
+ {
120
+ "success": true,
121
+ "data": {
122
+ "message_id": "msg_xyz789",
123
+ "status": "sent",
124
+ "channel": "whatsapp",
125
+ "timestamp": "2025-11-26T10:31:00.000Z"
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Mark as Read
131
+
132
+ Mark a message as read and send read receipt to the customer.
133
+
134
+ | Parameter | Description |
135
+ |-----------|-------------|
136
+ | Message ID | The ID of the message to mark as read (e.g., `msg_xyz789`) |
137
+
138
+ ### List Templates
139
+
140
+ List all WhatsApp message templates with optional filtering.
141
+
142
+ | Parameter | Description |
143
+ |-----------|-------------|
144
+ | Filter by Status | `All`, `APPROVED`, `PENDING`, or `REJECTED` |
145
+ | Filter by Category | `All`, `MARKETING`, `UTILITY`, or `AUTHENTICATION` |
146
+ | Limit | Maximum templates to return (1-500, default 100) |
147
+
148
+ **Example Response:**
149
+ ```json
150
+ {
151
+ "success": true,
152
+ "data": {
153
+ "templates": [
154
+ {
155
+ "id": "tpl_abc123",
156
+ "templateName": "order_confirmation",
157
+ "language": "en",
158
+ "status": "APPROVED",
159
+ "category": "UTILITY",
160
+ "content": "Your order {{1}} has been confirmed.",
161
+ "headerType": "TEXT",
162
+ "headerContent": "Order Update",
163
+ "footerContent": "Thank you for shopping!",
164
+ "variables": ["order_number"],
165
+ "createdAt": "2025-01-15T10:00:00.000Z"
166
+ }
167
+ ],
168
+ "total": 1
169
+ }
170
+ }
171
+ ```
172
+
173
+ ### Get Template
174
+
175
+ Get a specific WhatsApp message template by ID.
176
+
177
+ | Parameter | Description |
178
+ |-----------|-------------|
179
+ | Template ID | The ID of the template (e.g., `tpl_abc123`) |
180
+
181
+ **Example Response:**
182
+ ```json
183
+ {
184
+ "success": true,
185
+ "data": {
186
+ "id": "tpl_abc123",
187
+ "templateName": "order_confirmation",
188
+ "language": "en",
189
+ "status": "APPROVED",
190
+ "category": "UTILITY",
191
+ "content": "Your order {{1}} has been confirmed.",
192
+ "headerType": "TEXT",
193
+ "headerContent": "Order Update",
194
+ "footerContent": "Thank you for shopping!",
195
+ "buttons": [
196
+ { "type": "URL", "text": "Track Order", "url": "https://..." }
197
+ ],
198
+ "variables": ["order_number"],
199
+ "createdAt": "2025-01-15T10:00:00.000Z",
200
+ "updatedAt": "2025-01-15T10:00:00.000Z"
201
+ }
202
+ }
203
+ ```
204
+
205
+ ### Send Typing Indicator
206
+
207
+ Show typing indicator to a customer before sending a message.
208
+
209
+ | Parameter | Description |
210
+ |-----------|-------------|
211
+ | Customer Lookup | How to identify the customer |
212
+ | Customer ID/Phone/Username | Customer identifier |
213
+ | Channel | `whatsapp`, `instagram`, or auto-detect |
214
+
215
+ > **Note:** Rate limited to 1 request per customer per 3 seconds.
216
+
217
+ ## Webhook Trigger
218
+
219
+ KirimChat supports outbound webhooks for real-time event notifications. Configure webhooks in your KirimChat dashboard under **Settings > Developers > Webhooks**.
220
+
221
+ ### Supported Events
222
+
223
+ - `message.received` - New message from customer
224
+ - `message.sent` - Message sent to customer
225
+ - `message.delivered` - Message delivered
226
+ - `message.read` - Message read by customer
227
+ - `message.failed` - Message delivery failed
228
+
229
+ ### Webhook Payload Example
230
+
231
+ ```json
232
+ {
233
+ "event_type": "message.received",
234
+ "event_id": "evt_abc123",
235
+ "timestamp": "2025-11-26T10:30:00.000Z",
236
+ "data": {
237
+ "message_id": "msg_xyz789",
238
+ "customer_id": "cust_123",
239
+ "customer_phone": "+6281234567890",
240
+ "direction": "inbound",
241
+ "message_type": "text",
242
+ "content": "Hello!",
243
+ "channel": "whatsapp"
244
+ }
245
+ }
246
+ ```
247
+
248
+ ### Using with n8n Webhook Node
249
+
250
+ 1. Create a new workflow with **Webhook** trigger node
251
+ 2. Copy the webhook URL
252
+ 3. In KirimChat, create a webhook endpoint with this URL
253
+ 4. Select the events you want to receive
254
+ 5. Connect the webhook to your KirimChat node for automated responses
255
+
256
+ ## Rate Limits
257
+
258
+ | Limit Type | Rate |
259
+ |------------|------|
260
+ | Global | 100 requests per minute per API key |
261
+ | WhatsApp | 60 messages per minute |
262
+ | Instagram | 180 messages per hour |
263
+ | Messenger | 180 messages per hour |
264
+ | Typing Indicator | 1 request per customer per 3 seconds |
265
+
266
+ ## Error Handling
267
+
268
+ The node returns standard error responses:
269
+
270
+ | Code | Description |
271
+ |------|-------------|
272
+ | 401 | Invalid or expired API key |
273
+ | 403 | Account disconnected or forbidden |
274
+ | 404 | Resource not found |
275
+ | 400 | Invalid request or messaging window closed |
276
+ | 429 | Rate limit exceeded |
277
+
278
+ ### Common Error Codes
279
+
280
+ | Error Code | Description |
281
+ |------------|-------------|
282
+ | `ValidationError` | Invalid input parameters |
283
+ | `WindowClosed` | 24-hour messaging window expired |
284
+ | `ConfigurationError` | Channel not configured |
285
+ | `ConnectionError` | Account disconnected |
286
+ | `RateLimitExceeded` | Too many requests |
287
+
288
+ ## Changelog
289
+
290
+ ### v1.2.0 (Latest)
291
+ - ✅ Added **List Templates** operation - List all WhatsApp templates with filtering
292
+ - ✅ Added **Get Template** operation - Get template details by ID
293
+ - ✅ Filter templates by status (Approved, Pending, Rejected)
294
+ - ✅ Filter templates by category (Marketing, Utility, Authentication)
295
+
296
+ ### v1.1.0
297
+ - ✅ Added Messenger channel support
298
+ - ✅ Added customer lookup by phone number and Instagram username
299
+ - ✅ Added WhatsApp Interactive messages (CTA URL buttons, Reply buttons)
300
+ - ✅ Improved typing indicator with flexible customer lookup
301
+ - ✅ Enhanced error messages
302
+
303
+ ### v1.0.5
304
+ - Enhanced validation for media URLs
305
+ - Template components structure validation
306
+
307
+ ### v1.0.1
308
+ - Initial security improvements
309
+ - JSON validation for templates
310
+ - Base URL security warning
311
+
312
+ ## Resources
313
+
314
+ - [KirimChat Documentation](https://kirim.chat/developers)
315
+ - [n8n Community Nodes](https://docs.n8n.io/integrations/community-nodes/)
316
+
317
+ ## License
318
+
319
+ MIT
@@ -23,6 +23,24 @@ function validateTemplateComponents(components) {
23
23
  }
24
24
  return components.every((component) => typeof component === 'object' && component !== null);
25
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
+ }
26
44
  class KirimChat {
27
45
  constructor() {
28
46
  this.description = {
@@ -32,7 +50,7 @@ class KirimChat {
32
50
  group: ['transform'],
33
51
  version: 1,
34
52
  subtitle: '={{$parameter["operation"]}}',
35
- 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',
36
54
  defaults: {
37
55
  name: 'KirimChat',
38
56
  },
@@ -54,9 +72,21 @@ class KirimChat {
54
72
  {
55
73
  name: 'Send Message',
56
74
  value: 'sendMessage',
57
- description: 'Send a message to a customer via WhatsApp or Instagram',
75
+ description: 'Send a message to a customer via WhatsApp, Instagram, or Messenger',
58
76
  action: 'Send a message',
59
77
  },
78
+ {
79
+ name: 'List Templates',
80
+ value: 'listTemplates',
81
+ description: 'List WhatsApp message templates',
82
+ action: 'List templates',
83
+ },
84
+ {
85
+ name: 'Get Template',
86
+ value: 'getTemplate',
87
+ description: 'Get a specific WhatsApp message template by ID',
88
+ action: 'Get template',
89
+ },
60
90
  {
61
91
  name: 'Mark as Read',
62
92
  value: 'markAsRead',
@@ -72,7 +102,40 @@ class KirimChat {
72
102
  ],
73
103
  default: 'sendMessage',
74
104
  },
75
- // Send Message Fields
105
+ // ============================================
106
+ // SEND MESSAGE FIELDS
107
+ // ============================================
108
+ // Customer Lookup Method
109
+ {
110
+ displayName: 'Customer Lookup',
111
+ name: 'customerLookup',
112
+ type: 'options',
113
+ required: true,
114
+ displayOptions: {
115
+ show: {
116
+ operation: ['sendMessage'],
117
+ },
118
+ },
119
+ options: [
120
+ {
121
+ name: 'Customer ID',
122
+ value: 'customer_id',
123
+ description: 'Lookup by KirimChat customer ID (e.g., cust_abc123)',
124
+ },
125
+ {
126
+ name: 'Phone Number',
127
+ value: 'phone_number',
128
+ description: 'Lookup by phone number (e.g., +6281234567890)',
129
+ },
130
+ {
131
+ name: 'Instagram Username',
132
+ value: 'instagram_username',
133
+ description: 'Lookup by Instagram username',
134
+ },
135
+ ],
136
+ default: 'customer_id',
137
+ description: 'How to identify the customer',
138
+ },
76
139
  {
77
140
  displayName: 'Customer ID',
78
141
  name: 'customerId',
@@ -81,12 +144,44 @@ class KirimChat {
81
144
  displayOptions: {
82
145
  show: {
83
146
  operation: ['sendMessage'],
147
+ customerLookup: ['customer_id'],
84
148
  },
85
149
  },
86
150
  default: '',
87
151
  description: 'The ID of the customer to send the message to',
88
152
  placeholder: 'cust_abc123',
89
153
  },
154
+ {
155
+ displayName: 'Phone Number',
156
+ name: 'phoneNumber',
157
+ type: 'string',
158
+ required: true,
159
+ displayOptions: {
160
+ show: {
161
+ operation: ['sendMessage'],
162
+ customerLookup: ['phone_number'],
163
+ },
164
+ },
165
+ default: '',
166
+ description: 'The phone number of the customer (with country code)',
167
+ placeholder: '+6281234567890',
168
+ },
169
+ {
170
+ displayName: 'Instagram Username',
171
+ name: 'instagramUsername',
172
+ type: 'string',
173
+ required: true,
174
+ displayOptions: {
175
+ show: {
176
+ operation: ['sendMessage'],
177
+ customerLookup: ['instagram_username'],
178
+ },
179
+ },
180
+ default: '',
181
+ description: 'The Instagram username of the customer',
182
+ placeholder: 'username',
183
+ },
184
+ // Channel Selection
90
185
  {
91
186
  displayName: 'Channel',
92
187
  name: 'channel',
@@ -106,10 +201,15 @@ class KirimChat {
106
201
  name: 'Instagram',
107
202
  value: 'instagram',
108
203
  },
204
+ {
205
+ name: 'Messenger',
206
+ value: 'messenger',
207
+ },
109
208
  ],
110
209
  default: 'whatsapp',
111
210
  description: 'The messaging channel to use',
112
211
  },
212
+ // Message Type - WhatsApp
113
213
  {
114
214
  displayName: 'Message Type',
115
215
  name: 'messageType',
@@ -146,10 +246,15 @@ class KirimChat {
146
246
  name: 'Template',
147
247
  value: 'template',
148
248
  },
249
+ {
250
+ name: 'Interactive (Buttons)',
251
+ value: 'interactive',
252
+ },
149
253
  ],
150
254
  default: 'text',
151
255
  description: 'The type of message to send (WhatsApp)',
152
256
  },
257
+ // Message Type - Instagram
153
258
  {
154
259
  displayName: 'Message Type',
155
260
  name: 'messageType',
@@ -178,6 +283,44 @@ class KirimChat {
178
283
  default: 'text',
179
284
  description: 'The type of message to send (Instagram)',
180
285
  },
286
+ // Message Type - Messenger
287
+ {
288
+ displayName: 'Message Type',
289
+ name: 'messageType',
290
+ type: 'options',
291
+ required: true,
292
+ displayOptions: {
293
+ show: {
294
+ operation: ['sendMessage'],
295
+ channel: ['messenger'],
296
+ },
297
+ },
298
+ options: [
299
+ {
300
+ name: 'Text',
301
+ value: 'text',
302
+ },
303
+ {
304
+ name: 'Image',
305
+ value: 'image',
306
+ },
307
+ {
308
+ name: 'Video',
309
+ value: 'video',
310
+ },
311
+ {
312
+ name: 'Audio',
313
+ value: 'audio',
314
+ },
315
+ {
316
+ name: 'File',
317
+ value: 'file',
318
+ },
319
+ ],
320
+ default: 'text',
321
+ description: 'The type of message to send (Messenger)',
322
+ },
323
+ // Text Content
181
324
  {
182
325
  displayName: 'Message Content',
183
326
  name: 'content',
@@ -195,19 +338,23 @@ class KirimChat {
195
338
  rows: 4,
196
339
  },
197
340
  },
341
+ // Media URL (for all media types)
198
342
  {
199
343
  displayName: 'Media URL',
200
344
  name: 'mediaUrl',
201
345
  type: 'string',
346
+ required: true,
202
347
  displayOptions: {
203
348
  show: {
204
349
  operation: ['sendMessage'],
205
- messageType: ['image', 'document', 'audio', 'video', 'media_share'],
350
+ messageType: ['image', 'document', 'audio', 'video', 'media_share', 'file'],
206
351
  },
207
352
  },
208
353
  default: '',
209
- description: 'URL of the media file to send',
354
+ description: 'URL of the media file to send (must be publicly accessible)',
355
+ placeholder: 'https://example.com/image.jpg',
210
356
  },
357
+ // Caption (for media messages)
211
358
  {
212
359
  displayName: 'Caption',
213
360
  name: 'caption',
@@ -221,6 +368,7 @@ class KirimChat {
221
368
  default: '',
222
369
  description: 'Optional caption for media messages',
223
370
  },
371
+ // Filename (for documents)
224
372
  {
225
373
  displayName: 'Filename',
226
374
  name: 'filename',
@@ -228,14 +376,16 @@ class KirimChat {
228
376
  displayOptions: {
229
377
  show: {
230
378
  operation: ['sendMessage'],
231
- messageType: ['document'],
379
+ messageType: ['document', 'file'],
232
380
  },
233
381
  },
234
382
  default: '',
235
- description: 'Filename for document messages',
383
+ description: 'Filename for document/file messages',
236
384
  placeholder: 'invoice.pdf',
237
385
  },
238
- // Template fields (WhatsApp only)
386
+ // ============================================
387
+ // TEMPLATE FIELDS (WhatsApp only)
388
+ // ============================================
239
389
  {
240
390
  displayName: 'Template Name',
241
391
  name: 'templateName',
@@ -279,7 +429,130 @@ class KirimChat {
279
429
  default: '[]',
280
430
  description: 'Template components as JSON array (for variables, buttons, etc.)',
281
431
  },
282
- // Mark as Read Fields
432
+ // ============================================
433
+ // INTERACTIVE MESSAGE FIELDS (WhatsApp only)
434
+ // ============================================
435
+ {
436
+ displayName: 'Interactive Type',
437
+ name: 'interactiveType',
438
+ type: 'options',
439
+ required: true,
440
+ displayOptions: {
441
+ show: {
442
+ operation: ['sendMessage'],
443
+ messageType: ['interactive'],
444
+ },
445
+ },
446
+ options: [
447
+ {
448
+ name: 'CTA URL Button',
449
+ value: 'cta_url',
450
+ description: 'Call-to-action button that opens a URL',
451
+ },
452
+ {
453
+ name: 'Reply Buttons',
454
+ value: 'button',
455
+ description: 'Up to 3 reply buttons',
456
+ },
457
+ ],
458
+ default: 'cta_url',
459
+ description: 'Type of interactive message',
460
+ },
461
+ {
462
+ displayName: 'Body Text',
463
+ name: 'interactiveBody',
464
+ type: 'string',
465
+ required: true,
466
+ displayOptions: {
467
+ show: {
468
+ operation: ['sendMessage'],
469
+ messageType: ['interactive'],
470
+ },
471
+ },
472
+ default: '',
473
+ description: 'Main body text of the interactive message (max 1024 characters)',
474
+ typeOptions: {
475
+ rows: 3,
476
+ },
477
+ },
478
+ {
479
+ displayName: 'Header Text',
480
+ name: 'interactiveHeader',
481
+ type: 'string',
482
+ displayOptions: {
483
+ show: {
484
+ operation: ['sendMessage'],
485
+ messageType: ['interactive'],
486
+ },
487
+ },
488
+ default: '',
489
+ description: 'Optional header text (max 60 characters)',
490
+ },
491
+ {
492
+ displayName: 'Footer Text',
493
+ name: 'interactiveFooter',
494
+ type: 'string',
495
+ displayOptions: {
496
+ show: {
497
+ operation: ['sendMessage'],
498
+ messageType: ['interactive'],
499
+ },
500
+ },
501
+ default: '',
502
+ description: 'Optional footer text (max 60 characters)',
503
+ },
504
+ // CTA URL specific fields
505
+ {
506
+ displayName: 'Button Text',
507
+ name: 'ctaButtonText',
508
+ type: 'string',
509
+ required: true,
510
+ displayOptions: {
511
+ show: {
512
+ operation: ['sendMessage'],
513
+ messageType: ['interactive'],
514
+ interactiveType: ['cta_url'],
515
+ },
516
+ },
517
+ default: '',
518
+ description: 'Text displayed on the button (max 20 characters)',
519
+ placeholder: 'Visit Website',
520
+ },
521
+ {
522
+ displayName: 'Button URL',
523
+ name: 'ctaButtonUrl',
524
+ type: 'string',
525
+ required: true,
526
+ displayOptions: {
527
+ show: {
528
+ operation: ['sendMessage'],
529
+ messageType: ['interactive'],
530
+ interactiveType: ['cta_url'],
531
+ },
532
+ },
533
+ default: '',
534
+ description: 'URL to open when button is clicked',
535
+ placeholder: 'https://example.com',
536
+ },
537
+ // Reply buttons specific fields
538
+ {
539
+ displayName: 'Buttons (JSON)',
540
+ name: 'replyButtons',
541
+ type: 'json',
542
+ required: true,
543
+ displayOptions: {
544
+ show: {
545
+ operation: ['sendMessage'],
546
+ messageType: ['interactive'],
547
+ interactiveType: ['button'],
548
+ },
549
+ },
550
+ default: '[{"id": "btn1", "title": "Yes"}, {"id": "btn2", "title": "No"}]',
551
+ description: 'Array of buttons (1-3). Each button needs "id" (max 256 chars) and "title" (max 20 chars)',
552
+ },
553
+ // ============================================
554
+ // MARK AS READ FIELDS
555
+ // ============================================
283
556
  {
284
557
  displayName: 'Message ID',
285
558
  name: 'messageId',
@@ -294,32 +567,203 @@ class KirimChat {
294
567
  description: 'The ID of the message to mark as read',
295
568
  placeholder: 'msg_xyz789',
296
569
  },
297
- // Send Typing Fields
570
+ // ============================================
571
+ // LIST TEMPLATES FIELDS
572
+ // ============================================
573
+ {
574
+ displayName: 'Filter by Status',
575
+ name: 'templateStatus',
576
+ type: 'options',
577
+ displayOptions: {
578
+ show: {
579
+ operation: ['listTemplates'],
580
+ },
581
+ },
582
+ options: [
583
+ {
584
+ name: 'All',
585
+ value: '',
586
+ description: 'Return all templates',
587
+ },
588
+ {
589
+ name: 'Approved',
590
+ value: 'APPROVED',
591
+ description: 'Only approved templates (ready to use)',
592
+ },
593
+ {
594
+ name: 'Pending',
595
+ value: 'PENDING',
596
+ description: 'Templates pending approval',
597
+ },
598
+ {
599
+ name: 'Rejected',
600
+ value: 'REJECTED',
601
+ description: 'Rejected templates',
602
+ },
603
+ ],
604
+ default: '',
605
+ description: 'Filter templates by approval status',
606
+ },
607
+ {
608
+ displayName: 'Filter by Category',
609
+ name: 'templateCategory',
610
+ type: 'options',
611
+ displayOptions: {
612
+ show: {
613
+ operation: ['listTemplates'],
614
+ },
615
+ },
616
+ options: [
617
+ {
618
+ name: 'All',
619
+ value: '',
620
+ description: 'Return all categories',
621
+ },
622
+ {
623
+ name: 'Marketing',
624
+ value: 'MARKETING',
625
+ description: 'Marketing templates',
626
+ },
627
+ {
628
+ name: 'Utility',
629
+ value: 'UTILITY',
630
+ description: 'Utility templates (order updates, etc.)',
631
+ },
632
+ {
633
+ name: 'Authentication',
634
+ value: 'AUTHENTICATION',
635
+ description: 'Authentication templates (OTP, etc.)',
636
+ },
637
+ ],
638
+ default: '',
639
+ description: 'Filter templates by category',
640
+ },
641
+ {
642
+ displayName: 'Limit',
643
+ name: 'templateLimit',
644
+ type: 'number',
645
+ displayOptions: {
646
+ show: {
647
+ operation: ['listTemplates'],
648
+ },
649
+ },
650
+ default: 100,
651
+ description: 'Maximum number of templates to return (max 500)',
652
+ typeOptions: {
653
+ minValue: 1,
654
+ maxValue: 500,
655
+ },
656
+ },
657
+ // ============================================
658
+ // GET TEMPLATE FIELDS
659
+ // ============================================
660
+ {
661
+ displayName: 'Template ID',
662
+ name: 'templateId',
663
+ type: 'string',
664
+ required: true,
665
+ displayOptions: {
666
+ show: {
667
+ operation: ['getTemplate'],
668
+ },
669
+ },
670
+ default: '',
671
+ description: 'The ID of the template to retrieve',
672
+ placeholder: 'tpl_abc123',
673
+ },
674
+ // ============================================
675
+ // SEND TYPING FIELDS
676
+ // ============================================
677
+ {
678
+ displayName: 'Customer Lookup',
679
+ name: 'typingCustomerLookup',
680
+ type: 'options',
681
+ required: true,
682
+ displayOptions: {
683
+ show: {
684
+ operation: ['sendTyping'],
685
+ },
686
+ },
687
+ options: [
688
+ {
689
+ name: 'Customer ID',
690
+ value: 'customer_id',
691
+ description: 'Lookup by KirimChat customer ID',
692
+ },
693
+ {
694
+ name: 'Phone Number',
695
+ value: 'phone_number',
696
+ description: 'Lookup by phone number',
697
+ },
698
+ {
699
+ name: 'Instagram Username',
700
+ value: 'instagram_username',
701
+ description: 'Lookup by Instagram username',
702
+ },
703
+ ],
704
+ default: 'customer_id',
705
+ description: 'How to identify the customer',
706
+ },
298
707
  {
299
708
  displayName: 'Customer ID',
300
- name: 'customerId',
709
+ name: 'typingCustomerId',
301
710
  type: 'string',
302
711
  required: true,
303
712
  displayOptions: {
304
713
  show: {
305
714
  operation: ['sendTyping'],
715
+ typingCustomerLookup: ['customer_id'],
306
716
  },
307
717
  },
308
718
  default: '',
309
719
  description: 'The ID of the customer to send typing indicator to',
310
720
  placeholder: 'cust_abc123',
311
721
  },
722
+ {
723
+ displayName: 'Phone Number',
724
+ name: 'typingPhoneNumber',
725
+ type: 'string',
726
+ required: true,
727
+ displayOptions: {
728
+ show: {
729
+ operation: ['sendTyping'],
730
+ typingCustomerLookup: ['phone_number'],
731
+ },
732
+ },
733
+ default: '',
734
+ description: 'The phone number of the customer',
735
+ placeholder: '+6281234567890',
736
+ },
737
+ {
738
+ displayName: 'Instagram Username',
739
+ name: 'typingInstagramUsername',
740
+ type: 'string',
741
+ required: true,
742
+ displayOptions: {
743
+ show: {
744
+ operation: ['sendTyping'],
745
+ typingCustomerLookup: ['instagram_username'],
746
+ },
747
+ },
748
+ default: '',
749
+ description: 'The Instagram username of the customer',
750
+ placeholder: 'username',
751
+ },
312
752
  {
313
753
  displayName: 'Channel',
314
- name: 'channel',
754
+ name: 'typingChannel',
315
755
  type: 'options',
316
- required: true,
317
756
  displayOptions: {
318
757
  show: {
319
758
  operation: ['sendTyping'],
320
759
  },
321
760
  },
322
761
  options: [
762
+ {
763
+ name: 'Auto-detect',
764
+ value: '',
765
+ description: 'Automatically detect based on customer data',
766
+ },
323
767
  {
324
768
  name: 'WhatsApp',
325
769
  value: 'whatsapp',
@@ -329,8 +773,8 @@ class KirimChat {
329
773
  value: 'instagram',
330
774
  },
331
775
  ],
332
- default: 'whatsapp',
333
- description: 'The messaging channel to use',
776
+ default: '',
777
+ description: 'The messaging channel to use (leave empty for auto-detect)',
334
778
  },
335
779
  ],
336
780
  };
@@ -345,15 +789,27 @@ class KirimChat {
345
789
  const operation = this.getNodeParameter('operation', i);
346
790
  let responseData;
347
791
  if (operation === 'sendMessage') {
348
- // Send Message Operation
349
- const customerId = this.getNodeParameter('customerId', i);
792
+ // ============================================
793
+ // SEND MESSAGE OPERATION
794
+ // ============================================
795
+ const customerLookup = this.getNodeParameter('customerLookup', i);
350
796
  const channel = this.getNodeParameter('channel', i);
351
797
  const messageType = this.getNodeParameter('messageType', i);
352
798
  const body = {
353
- customer_id: customerId,
354
799
  channel,
355
800
  message_type: messageType,
356
801
  };
802
+ // Set customer identifier based on lookup method
803
+ if (customerLookup === 'customer_id') {
804
+ body.customer_id = this.getNodeParameter('customerId', i);
805
+ }
806
+ else if (customerLookup === 'phone_number') {
807
+ body.phone_number = this.getNodeParameter('phoneNumber', i);
808
+ }
809
+ else if (customerLookup === 'instagram_username') {
810
+ body.instagram_username = this.getNodeParameter('instagramUsername', i);
811
+ }
812
+ // Handle different message types
357
813
  if (messageType === 'text') {
358
814
  body.content = this.getNodeParameter('content', i);
359
815
  }
@@ -379,24 +835,90 @@ class KirimChat {
379
835
  components: templateComponents,
380
836
  };
381
837
  }
838
+ else if (messageType === 'interactive') {
839
+ // WhatsApp interactive message
840
+ const interactiveType = this.getNodeParameter('interactiveType', i);
841
+ const interactiveBody = this.getNodeParameter('interactiveBody', i);
842
+ const interactiveHeader = this.getNodeParameter('interactiveHeader', i, '');
843
+ const interactiveFooter = this.getNodeParameter('interactiveFooter', i, '');
844
+ const interactive = {
845
+ type: interactiveType,
846
+ body: { text: interactiveBody },
847
+ };
848
+ // Add optional header
849
+ if (interactiveHeader) {
850
+ interactive.header = {
851
+ type: 'text',
852
+ text: interactiveHeader,
853
+ };
854
+ }
855
+ // Add optional footer
856
+ if (interactiveFooter) {
857
+ interactive.footer = { text: interactiveFooter };
858
+ }
859
+ // Add action based on type
860
+ if (interactiveType === 'cta_url') {
861
+ const ctaButtonText = this.getNodeParameter('ctaButtonText', i);
862
+ const ctaButtonUrl = this.getNodeParameter('ctaButtonUrl', i);
863
+ if (!isValidUrl(ctaButtonUrl)) {
864
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid Button URL format: "${ctaButtonUrl}". URL must start with http:// or https://`, { itemIndex: i });
865
+ }
866
+ interactive.action = {
867
+ name: 'cta_url',
868
+ parameters: {
869
+ display_text: ctaButtonText,
870
+ url: ctaButtonUrl,
871
+ },
872
+ };
873
+ }
874
+ else if (interactiveType === 'button') {
875
+ const replyButtonsJson = this.getNodeParameter('replyButtons', i);
876
+ let replyButtons;
877
+ try {
878
+ replyButtons = JSON.parse(replyButtonsJson);
879
+ }
880
+ catch (error) {
881
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid JSON in Reply Buttons: ${error instanceof Error ? error.message : 'Parse failed'}`, { itemIndex: i });
882
+ }
883
+ if (!Array.isArray(replyButtons) || replyButtons.length === 0 || replyButtons.length > 3) {
884
+ 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 });
885
+ }
886
+ // Transform to n8n format
887
+ const formattedButtons = replyButtons.map((btn) => ({
888
+ type: 'reply',
889
+ reply: {
890
+ id: btn.id || '',
891
+ title: btn.title || '',
892
+ },
893
+ }));
894
+ interactive.action = {
895
+ buttons: formattedButtons,
896
+ };
897
+ }
898
+ if (!validateInteractiveMessage(interactive)) {
899
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid interactive message structure', { itemIndex: i });
900
+ }
901
+ body.interactive = interactive;
902
+ }
382
903
  else {
383
- // Media messages
384
- const mediaUrl = this.getNodeParameter('mediaUrl', i, '');
904
+ // Media messages (image, document, audio, video, media_share, file)
905
+ const mediaUrl = this.getNodeParameter('mediaUrl', i);
385
906
  // Validate media URL is provided
386
907
  if (!mediaUrl) {
387
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Media URL is required for media messages (image, document, audio, video, media_share)', { itemIndex: i });
908
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Media URL is required for ${messageType} messages`, { itemIndex: i });
388
909
  }
389
910
  // Validate media URL format
390
911
  if (!isValidUrl(mediaUrl)) {
391
912
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid Media URL format: "${mediaUrl}". URL must start with http:// or https://`, { itemIndex: i });
392
913
  }
393
914
  body.media_url = mediaUrl;
915
+ // Add caption if provided
394
916
  const caption = this.getNodeParameter('caption', i, '');
395
917
  if (caption) {
396
918
  body.caption = caption;
397
919
  }
398
- // Document filename
399
- if (messageType === 'document') {
920
+ // Add filename for document/file types
921
+ if (messageType === 'document' || messageType === 'file') {
400
922
  const filename = this.getNodeParameter('filename', i, '');
401
923
  if (filename) {
402
924
  body.filename = filename;
@@ -411,7 +933,9 @@ class KirimChat {
411
933
  });
412
934
  }
413
935
  else if (operation === 'markAsRead') {
414
- // Mark as Read Operation
936
+ // ============================================
937
+ // MARK AS READ OPERATION
938
+ // ============================================
415
939
  const messageId = this.getNodeParameter('messageId', i);
416
940
  responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'kirimChatApi', {
417
941
  method: 'POST',
@@ -419,14 +943,67 @@ class KirimChat {
419
943
  json: true,
420
944
  });
421
945
  }
946
+ else if (operation === 'listTemplates') {
947
+ // ============================================
948
+ // LIST TEMPLATES OPERATION
949
+ // ============================================
950
+ const templateStatus = this.getNodeParameter('templateStatus', i, '');
951
+ const templateCategory = this.getNodeParameter('templateCategory', i, '');
952
+ const templateLimit = this.getNodeParameter('templateLimit', i, 100);
953
+ // Build query string
954
+ const queryParams = [];
955
+ if (templateStatus) {
956
+ queryParams.push(`status=${templateStatus}`);
957
+ }
958
+ if (templateCategory) {
959
+ queryParams.push(`category=${templateCategory}`);
960
+ }
961
+ if (templateLimit) {
962
+ queryParams.push(`limit=${templateLimit}`);
963
+ }
964
+ const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
965
+ responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'kirimChatApi', {
966
+ method: 'GET',
967
+ url: `${baseUrl}/templates${queryString}`,
968
+ json: true,
969
+ });
970
+ }
971
+ else if (operation === 'getTemplate') {
972
+ // ============================================
973
+ // GET TEMPLATE OPERATION
974
+ // ============================================
975
+ const templateId = this.getNodeParameter('templateId', i);
976
+ responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'kirimChatApi', {
977
+ method: 'GET',
978
+ url: `${baseUrl}/templates/${templateId}`,
979
+ json: true,
980
+ });
981
+ }
422
982
  else if (operation === 'sendTyping') {
423
- // Send Typing Indicator Operation
424
- const customerId = this.getNodeParameter('customerId', i);
425
- const channel = this.getNodeParameter('channel', i);
983
+ // ============================================
984
+ // SEND TYPING INDICATOR OPERATION
985
+ // ============================================
986
+ const typingLookup = this.getNodeParameter('typingCustomerLookup', i);
987
+ const typingChannel = this.getNodeParameter('typingChannel', i, '');
988
+ // Get customer identifier based on lookup method
989
+ let customerIdentifier;
990
+ if (typingLookup === 'customer_id') {
991
+ customerIdentifier = this.getNodeParameter('typingCustomerId', i);
992
+ }
993
+ else if (typingLookup === 'phone_number') {
994
+ customerIdentifier = this.getNodeParameter('typingPhoneNumber', i);
995
+ }
996
+ else {
997
+ customerIdentifier = this.getNodeParameter('typingInstagramUsername', i);
998
+ }
999
+ const typingBody = {};
1000
+ if (typingChannel) {
1001
+ typingBody.channel = typingChannel;
1002
+ }
426
1003
  responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'kirimChatApi', {
427
1004
  method: 'POST',
428
- url: `${baseUrl}/conversations/${customerId}/typing`,
429
- body: { channel },
1005
+ url: `${baseUrl}/conversations/${customerIdentifier}/typing`,
1006
+ body: typingBody,
430
1007
  json: true,
431
1008
  });
432
1009
  }
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
- {
2
- "name": "@kichat/n8n-nodes-kirimchat",
3
- "version": "1.0.5",
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.2.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": {