@open-wa/wa-automate 4.54.6 → 4.55.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,6 +18,7 @@ const __1 = require("../..");
18
18
  const axios_1 = __importDefault(require("axios"));
19
19
  const form_data_1 = __importDefault(require("form-data"));
20
20
  const mime_types_1 = __importDefault(require("mime-types"));
21
+ const tools_1 = require("../../utils/tools");
21
22
  const contactReg = {
22
23
  //WID : chatwoot contact ID
23
24
  "example@c.us": "1"
@@ -29,18 +30,53 @@ const convoReg = {
29
30
  const ignoreMap = {
30
31
  "example_message_id": true
31
32
  };
33
+ let chatwootClient;
32
34
  exports.chatwoot_webhook_check_event_name = "cli.integrations.chatwoot.check";
35
+ function parseIdAndScore(input) {
36
+ const regex = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}):([1-5])$/;
37
+ const match = regex.exec(input);
38
+ if (match) {
39
+ return {
40
+ id: match[1],
41
+ score: parseInt(match[2], 10)
42
+ };
43
+ }
44
+ else {
45
+ return null;
46
+ }
47
+ }
33
48
  const chatwootMiddleware = (cliConfig, client) => {
34
49
  return (req, res) => __awaiter(void 0, void 0, void 0, function* () {
35
50
  const processMesssage = () => __awaiter(void 0, void 0, void 0, function* () {
51
+ var _a, _b, _c, _d;
36
52
  const promises = [];
37
53
  const { body } = req;
38
54
  if (!body)
39
55
  return;
56
+ if (body.event == "conversation_status_changed" && body.status == "resolved") {
57
+ __1.log.info("Trying to send CSAT");
58
+ /**
59
+ * CSAT requested
60
+ */
61
+ let basicCsatMsgData = (_a = body.messages) === null || _a === void 0 ? void 0 : _a.find(m => (m === null || m === void 0 ? void 0 : m.content_type) === "input_csat");
62
+ if (!basicCsatMsgData) {
63
+ /**
64
+ * CSAT Missing from this webhook. Try to find it by getting all messages and filtering by csat and with a ts of more than the webhook event - 5s (just in case the csat was somehow sent before the convo was "resolved")
65
+ */
66
+ const msgs = yield chatwootClient.getAllInboxMessages(body.id);
67
+ basicCsatMsgData = msgs.find(m => m.content_type === 'input_csat' && m.created_at > (body.timestamp - 5));
68
+ }
69
+ if (!basicCsatMsgData)
70
+ return;
71
+ const _to = `${(((_b = body === null || body === void 0 ? void 0 : body.custom_attributes) === null || _b === void 0 ? void 0 : _b.wanumber) || (((_d = (_c = body.meta) === null || _c === void 0 ? void 0 : _c.sender) === null || _d === void 0 ? void 0 : _d.phone_number) || "").replace('+', '')).replace('@c.us', '')}@c.us`;
72
+ if (_to)
73
+ promises.push(chatwootClient.sendCSAT(basicCsatMsgData, _to));
74
+ }
40
75
  if (!body.conversation)
41
76
  return;
42
- const m = body.conversation.messages[0];
43
77
  const contact = (body.conversation.meta.sender.phone_number || "").replace('+', '');
78
+ const to = `${contact}@c.us`;
79
+ const m = body.conversation.messages[0];
44
80
  if (body.message_type === "incoming" ||
45
81
  body.private ||
46
82
  body.event !== "message_created" ||
@@ -48,7 +84,6 @@ const chatwootMiddleware = (cliConfig, client) => {
48
84
  !contact)
49
85
  return;
50
86
  const { attachments, content } = m;
51
- const to = `${contact}@c.us`;
52
87
  if (!convoReg[to])
53
88
  convoReg[to] = body.conversation.id;
54
89
  if ((attachments === null || attachments === void 0 ? void 0 : attachments.length) > 0) {
@@ -101,7 +136,6 @@ const chatwootMiddleware = (cliConfig, client) => {
101
136
  res.status(200).send(processAndSendResult);
102
137
  }
103
138
  catch (error) {
104
- console.log("🚀 ~ file: chatwoot.ts ~ line 62 ~ return ~ error", error);
105
139
  res.status(400).send(error);
106
140
  }
107
141
  return;
@@ -109,329 +143,508 @@ const chatwootMiddleware = (cliConfig, client) => {
109
143
  };
110
144
  exports.chatwootMiddleware = chatwootMiddleware;
111
145
  const setupChatwootOutgoingMessageHandler = (cliConfig, client) => __awaiter(void 0, void 0, void 0, function* () {
112
- __1.log.info(`Setting up chatwoot integration: ${cliConfig.chatwootUrl}`);
113
- const u = cliConfig.chatwootUrl; //e.g `"localhost:3000/api/v1/accounts/3"
114
- const api_access_token = cliConfig.chatwootApiAccessToken;
115
- const _u = new URL(u);
116
- const origin = _u.origin;
117
- const port = _u.port || 80;
118
- const accountNumber = yield client.getHostNumber();
119
- const proms = [];
120
- let expectedSelfWebhookUrl = cliConfig.apiHost ? `${cliConfig.apiHost}/chatwoot ` : `${cliConfig.host.includes('http') ? '' : `http${cliConfig.https || (cliConfig.cert && cliConfig.privkey) ? 's' : ''}://`}${cliConfig.host}:${cliConfig.port}/chatwoot `;
121
- expectedSelfWebhookUrl = expectedSelfWebhookUrl.trim();
122
- if (cliConfig.key)
123
- expectedSelfWebhookUrl = `${expectedSelfWebhookUrl}?api_key=${cliConfig.key}`;
124
- let [accountId, inboxId] = (u.match(/\/(app|(api\/v1))\/accounts\/\d*\/(inbox|inboxes)\/\d*/g) || [''])[0].split('/').filter(Number);
125
- inboxId = inboxId || u.match(/inboxes\/\d*/g) && u.match(/inboxes\/\d*/g)[0].replace('inboxes/', '');
126
- // const accountId = u.match(/accounts\/\d*/g) && u.match(/accounts\/\d*/g)[0].replace('accounts/', '')
146
+ chatwootClient = new ChatwootClient(cliConfig, client);
147
+ yield chatwootClient.init();
148
+ return;
149
+ });
150
+ exports.setupChatwootOutgoingMessageHandler = setupChatwootOutgoingMessageHandler;
151
+ class ChatwootClient {
152
+ constructor(cliConfig, client) {
153
+ const u = cliConfig.chatwootUrl; //e.g `"localhost:3000/api/v1/accounts/3"
154
+ this.api_access_token = cliConfig.chatwootApiAccessToken;
155
+ const _u = new URL(u);
156
+ this.origin = _u.origin;
157
+ this.port = Number(_u.port || 80);
158
+ this.client = client;
159
+ this.expectedSelfWebhookUrl = cliConfig.apiHost ? `${cliConfig.apiHost}/chatwoot ` : `${cliConfig.host.includes('http') ? '' : `http${cliConfig.https || (cliConfig.cert && cliConfig.privkey) ? 's' : ''}://`}${cliConfig.host}:${cliConfig.port}/chatwoot `;
160
+ this.expectedSelfWebhookUrl = this.expectedSelfWebhookUrl.trim();
161
+ this.key = cliConfig.key;
162
+ if (cliConfig.key)
163
+ this.expectedSelfWebhookUrl = `${this.expectedSelfWebhookUrl}?api_key=${cliConfig.key}`;
164
+ const [accountId, inboxId] = (u.match(/\/(app|(api\/v1))\/accounts\/\d*(\/(inbox|inboxes)\/\d*)?/g) || [''])[0].split('/').filter(Number);
165
+ this.inboxId = inboxId || u.match(/inboxes\/\d*/g) && u.match(/inboxes\/\d*/g)[0].replace('inboxes/', '');
166
+ this.accountId = accountId;
167
+ this.forceUpdateCwWebhook = cliConfig.forceUpdateCwWebhook;
168
+ }
169
+ cwReq(method, path, data, _headers) {
170
+ return __awaiter(this, void 0, void 0, function* () {
171
+ const { origin, accountId, api_access_token } = this;
172
+ const url = `${origin}/api/v1/accounts/${accountId}/${path}`.replace('app.bentonow.com', 'chat.bentonow.com');
173
+ const response = yield (0, axios_1.default)({
174
+ method,
175
+ data,
176
+ url,
177
+ headers: Object.assign({ api_access_token }, _headers)
178
+ }).catch(error => {
179
+ var _a, _b;
180
+ __1.log.error(`CW REQ ERROR: ${(_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status} ${(_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.message}`, error === null || error === void 0 ? void 0 : error.toJSON());
181
+ throw error;
182
+ });
183
+ __1.log.info(`CW REQUEST: ${response.status} ${method} ${url} ${JSON.stringify(data)}`);
184
+ return response;
185
+ });
186
+ }
127
187
  /**
128
- * Is the inbox and or account id undefined??
188
+ * Ensures the chatwoot integration is setup properly.
129
189
  */
130
- if (!accountId) {
131
- __1.log.info(`CHATWOOT INTEGRATION: account ID missing. Attempting to infer from access token....`);
132
- /**
133
- * If the account ID is undefined then get the account ID from the access_token
134
- */
135
- accountId = (yield axios_1.default.get(`${origin}/api/v1/profile`, { headers: { api_access_token } })).data.account_id;
136
- __1.log.info(`CHATWOOT INTEGRATION: Got account ID: ${accountId}`);
137
- }
138
- if (!inboxId) {
139
- __1.log.info(`CHATWOOT INTEGRATION: inbox ID missing. Attempting to find correct inbox....`);
140
- /**
141
- * Find the inbox with the correct setup.
142
- */
143
- const inboxArray = (yield axios_1.default.get(`${origin}/api/v1/accounts/${accountId}/inboxes`, { headers: { api_access_token } })).data.payload;
144
- const possibleInbox = inboxArray.find(inbox => { var _a; return ((_a = inbox === null || inbox === void 0 ? void 0 : inbox.additional_attributes) === null || _a === void 0 ? void 0 : _a.hostAccountNumber) === accountNumber; });
145
- if (possibleInbox) {
146
- __1.log.info(`CHATWOOT INTEGRATION: found inbox: ${JSON.stringify(possibleInbox)}`);
147
- __1.log.info(`CHATWOOT INTEGRATION: found inbox id: ${possibleInbox.channel_id}`);
148
- inboxId = possibleInbox.channel_id;
149
- }
150
- else {
151
- __1.log.info(`CHATWOOT INTEGRATION: inbox not found. Attempting to create inbox....`);
190
+ init() {
191
+ return __awaiter(this, void 0, void 0, function* () {
192
+ __1.log.info(`Setting up chatwoot integration: ${this.u}`);
193
+ const accountNumber = this.accountNumber = yield this.client.getHostNumber();
194
+ const { api_access_token, origin } = this;
195
+ let { inboxId, accountId } = this;
196
+ const proms = [];
197
+ // const accountId = u.match(/accounts\/\d*/g) && u.match(/accounts\/\d*/g)[0].replace('accounts/', '')
152
198
  /**
153
- * Create inbox
199
+ * Is the inbox and or account id undefined??
154
200
  */
155
- const { data: new_inbox } = (yield axios_1.default.post(`${origin}/api/v1/accounts/${accountId}/inboxes`, {
156
- "name": `open-wa-${accountNumber}`,
201
+ if (!accountId) {
202
+ __1.log.info(`CHATWOOT INTEGRATION: account ID missing. Attempting to infer from access token....`);
203
+ /**
204
+ * If the account ID is undefined then get the account ID from the access_token
205
+ */
206
+ accountId = (yield axios_1.default.get(`${origin}/api/v1/profile`, { headers: { api_access_token } })).data.account_id;
207
+ __1.log.info(`CHATWOOT INTEGRATION: Got account ID: ${accountId}`);
208
+ this.accountId = accountId;
209
+ }
210
+ if (!inboxId) {
211
+ __1.log.info(`CHATWOOT INTEGRATION: inbox ID missing. Attempting to find correct inbox....`);
212
+ /**
213
+ * Find the inbox with the correct setup.
214
+ */
215
+ const inboxArray = (yield axios_1.default.get(`${origin}/api/v1/accounts/${accountId}/inboxes`, { headers: { api_access_token } })).data.payload;
216
+ const possibleInbox = inboxArray.find(inbox => { var _a; return ((_a = inbox === null || inbox === void 0 ? void 0 : inbox.additional_attributes) === null || _a === void 0 ? void 0 : _a.hostAccountNumber) === accountNumber; });
217
+ if (possibleInbox) {
218
+ __1.log.info(`CHATWOOT INTEGRATION: found inbox: ${JSON.stringify(possibleInbox)}`);
219
+ __1.log.info(`CHATWOOT INTEGRATION: found inbox id: ${possibleInbox.id}`);
220
+ inboxId = possibleInbox.id;
221
+ this.inboxId = inboxId;
222
+ }
223
+ else {
224
+ __1.log.info(`CHATWOOT INTEGRATION: inbox not found. Attempting to create inbox....`);
225
+ /**
226
+ * Create inbox
227
+ */
228
+ const { data: new_inbox } = (yield axios_1.default.post(`${origin}/api/v1/accounts/${accountId}/inboxes`, {
229
+ "name": `open-wa-${accountNumber}`,
230
+ "channel": {
231
+ "phone_number": `${accountNumber}`,
232
+ "type": "api",
233
+ "webhook_url": this.expectedSelfWebhookUrl,
234
+ "additional_attributes": {
235
+ "sessionId": this.client.getSessionId(),
236
+ "hostAccountNumber": `${accountNumber}`
237
+ }
238
+ }
239
+ }, { headers: { api_access_token } }));
240
+ inboxId = new_inbox.id;
241
+ __1.log.info(`CHATWOOT INTEGRATION: inbox created. id: ${inboxId}`);
242
+ this.inboxId = inboxId;
243
+ }
244
+ }
245
+ let { data: get_inbox } = yield this.cwReq('get', `inboxes/${inboxId}`);
246
+ /**
247
+ * Update the webhook
248
+ */
249
+ const updatePayload = {
157
250
  "channel": {
158
- "phone_number": `${accountNumber}`,
159
- "type": "api",
160
- "webhook_url": expectedSelfWebhookUrl,
161
251
  "additional_attributes": {
162
- "sessionId": client.getSessionId(),
163
- "hostAccountNumber": `${accountNumber}`
252
+ "sessionId": this.client.getSessionId(),
253
+ "hostAccountNumber": `${this.accountNumber}`,
254
+ "instanceId": `${this.client.getInstanceId()}`
164
255
  }
165
256
  }
166
- }, { headers: { api_access_token } }));
167
- inboxId = new_inbox.id;
168
- __1.log.info(`CHATWOOT INTEGRATION: inbox created. id: ${inboxId}`);
169
- }
257
+ };
258
+ if (this.forceUpdateCwWebhook)
259
+ updatePayload.channel['webhook_url'] = this.expectedSelfWebhookUrl;
260
+ const updateInboxPromise = this.cwReq('patch', `inboxes/${this.inboxId}`, updatePayload);
261
+ if (this.forceUpdateCwWebhook)
262
+ get_inbox = (yield updateInboxPromise).data;
263
+ else
264
+ proms.push(updateInboxPromise);
265
+ /**
266
+ * Get the inbox and test it.
267
+ */
268
+ if (!((get_inbox === null || get_inbox === void 0 ? void 0 : get_inbox.webhook_url) || "").includes("/chatwoot"))
269
+ console.log("Please set the chatwoot inbox webhook to this sessions URL with path /chatwoot");
270
+ /**
271
+ * Check the webhook URL
272
+ */
273
+ const chatwootWebhookCheck = () => __awaiter(this, void 0, void 0, function* () {
274
+ let checkCodePromise;
275
+ const cancelCheckProm = () => (checkCodePromise.cancel && typeof checkCodePromise.cancel === "function") && checkCodePromise.cancel();
276
+ try {
277
+ const wUrl = get_inbox.webhook_url.split('?')[0].replace(/\/+$/, "").trim();
278
+ const checkWhURL = `${wUrl}${wUrl.endsWith("/") ? '' : `/`}checkWebhook${this.key ? `?api_key=${this.key}` : ''}`;
279
+ __1.log.info(`Verifying webhook url: ${checkWhURL}`);
280
+ const checkCode = uuid_apikey_1.default.create().apiKey; //random generated string
281
+ yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
282
+ var _a;
283
+ checkCodePromise = __1.ev.waitFor(exports.chatwoot_webhook_check_event_name, 5000).catch(reject);
284
+ yield axios_1.default.post(checkWhURL, {
285
+ checkCode
286
+ }, { headers: { api_key: this.key || '' } }).catch(reject);
287
+ const checkCodeResponse = yield checkCodePromise;
288
+ if (checkCodeResponse && ((_a = checkCodeResponse[0]) === null || _a === void 0 ? void 0 : _a.checkCode) == checkCode)
289
+ resolve(true);
290
+ else
291
+ reject(`Webhook check code is incorrect. Expected ${checkCode} - incoming ${((checkCodeResponse || [])[0] || {}).checkCode}`);
292
+ }));
293
+ __1.log.info('Chatwoot webhook verification successful');
294
+ }
295
+ catch (error) {
296
+ cancelCheckProm();
297
+ const e = `Unable to verify the chatwoot webhook URL on this inbox: ${error.message}`;
298
+ console.error(e);
299
+ __1.log.error(e);
300
+ }
301
+ finally {
302
+ cancelCheckProm();
303
+ }
304
+ });
305
+ proms.push(chatwootWebhookCheck());
306
+ const setOnMessageProm = this.client.onMessage(this.processWAMessage.bind(this));
307
+ const setOnAckProm = this.client.onAck((ackEvent) => __awaiter(this, void 0, void 0, function* () {
308
+ if (ignoreMap[ackEvent.id] && typeof ignoreMap[ackEvent.id] === 'number' && ackEvent.ack > ignoreMap[ackEvent.id]) {
309
+ // delete ignoreMap[ackEvent.id]
310
+ return;
311
+ }
312
+ if (ackEvent.ack >= 1 && ackEvent.isNewMsg && ackEvent.self === "in") {
313
+ if (ignoreMap[ackEvent.id])
314
+ return;
315
+ ignoreMap[ackEvent.id] = ackEvent.ack;
316
+ const _message = yield this.client.getMessageById(ackEvent.id);
317
+ return yield this.processWAMessage(_message);
318
+ }
319
+ return;
320
+ }));
321
+ proms.push(setOnMessageProm);
322
+ proms.push(setOnAckProm);
323
+ yield Promise.all(proms);
324
+ this.inboxId = inboxId;
325
+ this.accountId = accountId;
326
+ });
170
327
  }
171
- const cwReq = (method, path, data, _headers) => __awaiter(void 0, void 0, void 0, function* () {
172
- const url = `${origin}/api/v1/accounts/${accountId}/${path}`.replace('app.bentonow.com', 'chat.bentonow.com');
173
- // console.log(url,method,data)
174
- const response = yield (0, axios_1.default)({
175
- method,
176
- data,
177
- url,
178
- headers: Object.assign({ api_access_token }, _headers)
179
- }).catch(error => {
180
- var _a, _b;
181
- __1.log.error(`CW REQ ERROR: ${(_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status} ${(_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.message}`, error === null || error === void 0 ? void 0 : error.toJSON());
182
- throw error;
328
+ getAllInboxMessages(convoIdOrContactId) {
329
+ return __awaiter(this, void 0, void 0, function* () {
330
+ /**
331
+ * Check if it's a contact by traversing the convo reg.
332
+ */
333
+ const convoId = convoReg[convoIdOrContactId] || convoIdOrContactId;
334
+ const { data } = yield this.cwReq('get', `conversations/${convoId}/messages`);
335
+ const messages = data.payload;
336
+ return messages;
183
337
  });
184
- __1.log.info(`CW REQUEST: ${response.status} ${method} ${url} ${JSON.stringify(data)}`);
185
- return response;
186
- });
187
- let { data: get_inbox } = yield cwReq('get', `inboxes/${inboxId}`);
188
- /**
189
- * Update the webhook
190
- */
191
- const updatePayload = {
192
- "channel": {
193
- "additional_attributes": {
194
- "sessionId": client.getSessionId(),
195
- "hostAccountNumber": `${accountNumber}`,
196
- "instanceId": `${client.getInstanceId()}`
197
- }
198
- }
199
- };
200
- if (cliConfig.forceUpdateCwWebhook)
201
- updatePayload.channel['webhook_url'] = expectedSelfWebhookUrl;
202
- const updateInboxPromise = cwReq('patch', `inboxes/${inboxId}`, updatePayload);
203
- if (cliConfig.forceUpdateCwWebhook)
204
- get_inbox = (yield updateInboxPromise).data;
205
- else
206
- proms.push(updateInboxPromise);
207
- /**
208
- * Get the inbox and test it.
209
- */
210
- if (!((get_inbox === null || get_inbox === void 0 ? void 0 : get_inbox.webhook_url) || "").includes("/chatwoot"))
211
- console.log("Please set the chatwoot inbox webhook to this sessions URL with path /chatwoot");
338
+ }
212
339
  /**
213
- * Check the webhook URL
340
+ * Get the original chatwoot message object. Useful for getting CSAT full message details.
341
+ *
342
+ * @param convoIdOrContactId the owa contact ID or the convo ID. Better if you use convo ID.
343
+ * @param messageId
344
+ * @returns
214
345
  */
215
- const chatwootWebhookCheck = () => __awaiter(void 0, void 0, void 0, function* () {
216
- let checkCodePromise;
217
- const cancelCheckProm = () => (checkCodePromise.cancel && typeof checkCodePromise.cancel === "function") && checkCodePromise.cancel();
218
- try {
219
- const wUrl = get_inbox.webhook_url.split('?')[0].replace(/\/+$/, "").trim();
220
- const checkWhURL = `${wUrl}${wUrl.endsWith("/") ? '' : `/`}checkWebhook${cliConfig.key ? `?api_key=${cliConfig.key}` : ''}`;
221
- __1.log.info(`Verifying webhook url: ${checkWhURL}`);
222
- const checkCode = uuid_apikey_1.default.create().apiKey; //random generated string
223
- yield new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () {
224
- var _a;
225
- checkCodePromise = __1.ev.waitFor(exports.chatwoot_webhook_check_event_name, 5000).catch(reject);
226
- yield axios_1.default.post(checkWhURL, {
227
- checkCode
228
- }, { headers: { api_key: cliConfig.key || '' } }).catch(reject);
229
- const checkCodeResponse = yield checkCodePromise;
230
- if (checkCodeResponse && ((_a = checkCodeResponse[0]) === null || _a === void 0 ? void 0 : _a.checkCode) == checkCode)
231
- resolve(true);
232
- else
233
- reject(`Webhook check code is incorrect. Expected ${checkCode} - incoming ${((checkCodeResponse || [])[0] || {}).checkCode}`);
234
- }));
235
- __1.log.info('Chatwoot webhook verification successful');
236
- }
237
- catch (error) {
238
- cancelCheckProm();
239
- const e = `Unable to verify the chatwoot webhook URL on this inbox: ${error.message}`;
240
- console.error(e);
241
- __1.log.error(e);
242
- }
243
- finally {
244
- cancelCheckProm();
245
- }
246
- });
247
- proms.push(chatwootWebhookCheck());
346
+ getMessageObject(convoIdOrContactId, messageId) {
347
+ return __awaiter(this, void 0, void 0, function* () {
348
+ const msgs = yield this.getAllInboxMessages(`${convoIdOrContactId}`);
349
+ const foundMsg = msgs.find(x => x.id == messageId);
350
+ return foundMsg;
351
+ });
352
+ }
248
353
  /**
249
354
  * Get Contacts and conversations
250
355
  */
251
- const searchContact = (number) => __awaiter(void 0, void 0, void 0, function* () {
252
- try {
253
- const n = number.replace('@c.us', '');
254
- const { data } = yield cwReq('get', `contacts/search?q=${n}&sort=phone_number`);
255
- if (data.payload.length) {
256
- return data.payload.find(x => (x.phone_number || "").includes(n)) || false;
356
+ searchContact(number) {
357
+ return __awaiter(this, void 0, void 0, function* () {
358
+ try {
359
+ const n = number.replace('@c.us', '');
360
+ const { data } = yield this.cwReq('get', `contacts/search?q=${n}&sort=phone_number`);
361
+ if (data.payload.length) {
362
+ return data.payload.find(x => (x.phone_number || "").includes(n)) || false;
363
+ }
364
+ else
365
+ false;
257
366
  }
258
- else
259
- false;
260
- }
261
- catch (error) {
262
- return;
263
- }
264
- });
265
- const getContactConversation = (number) => __awaiter(void 0, void 0, void 0, function* () {
266
- try {
267
- const { data } = yield cwReq('get', `contacts/${contactReg[number]}/conversations`);
268
- const allContactConversations = data.payload.filter(c => c.inbox_id === inboxId).sort((a, b) => a.id - b.id);
269
- const [opened, notOpen] = [allContactConversations.filter(c => c.status === 'open'), allContactConversations.filter(c => c.status != 'open')];
270
- const hasOpenConvo = opened[0] ? true : false;
271
- const resolvedConversation = opened[0] || notOpen[0];
272
- if (!hasOpenConvo) {
273
- //reopen convo
274
- yield openConversation(resolvedConversation.id);
367
+ catch (error) {
368
+ return;
275
369
  }
276
- return resolvedConversation;
277
- }
278
- catch (error) {
279
- return;
280
- }
281
- });
282
- const createConversation = (contact_id) => __awaiter(void 0, void 0, void 0, function* () {
283
- try {
284
- const { data } = yield cwReq('post', `conversations`, {
285
- contact_id,
286
- "inbox_id": inboxId
287
- });
288
- return data;
289
- }
290
- catch (error) {
291
- return;
292
- }
293
- });
294
- const createContact = (contact) => __awaiter(void 0, void 0, void 0, function* () {
295
- try {
296
- const { data } = yield cwReq('post', `contacts`, {
297
- "identifier": contact.id,
298
- "name": contact.formattedName || contact.id,
299
- "phone_number": `+${contact.id.replace('@c.us', '')}`,
300
- "avatar_url": contact.profilePicThumbObj.eurl
301
- });
302
- return data.payload.contact;
303
- }
304
- catch (error) {
305
- return;
306
- }
307
- });
308
- const openConversation = (conversationId, status = "opened") => __awaiter(void 0, void 0, void 0, function* () {
309
- try {
310
- const { data } = yield cwReq('post', `conversations/${conversationId}/messages`, {
311
- status
312
- });
313
- return data;
314
- }
315
- catch (error) {
316
- return;
317
- }
318
- });
319
- const sendConversationMessage = (content, contactId, message) => __awaiter(void 0, void 0, void 0, function* () {
320
- __1.log.info(`INCOMING MESSAGE ${contactId}: ${content} ${message.id}`);
321
- try {
322
- const { data } = yield cwReq('post', `conversations/${convoReg[contactId]}/messages`, {
323
- content,
324
- "message_type": message.fromMe ? "outgoing" : "incoming",
325
- "private": false
326
- });
327
- return data;
328
- }
329
- catch (error) {
330
- return;
331
- }
332
- });
333
- const sendAttachmentMessage = (content, contactId, message) => __awaiter(void 0, void 0, void 0, function* () {
334
- // decrypt message
335
- const file = yield client.decryptMedia(message);
336
- __1.log.info(`INCOMING MESSAGE ATTACHMENT ${contactId}: ${content} ${message.id}`);
337
- let formData = new form_data_1.default();
338
- formData.append('attachments[]', Buffer.from(file.split(',')[1], 'base64'), {
339
- knownLength: 1,
340
- filename: `${message.t}.${mime_types_1.default.extension(message.mimetype)}`,
341
- contentType: (file.match(/[^:\s*]\w+\/[\w-+\d.]+(?=[;| ])/) || ["application/octet-stream"])[0]
342
370
  });
343
- formData.append('content', content);
344
- formData.append('message_type', 'incoming');
345
- try {
346
- const { data } = yield cwReq('post', `conversations/${convoReg[contactId]}/messages`, formData, formData.getHeaders());
347
- return data;
348
- }
349
- catch (error) {
350
- return;
351
- }
352
- });
353
- const processWAMessage = (message) => __awaiter(void 0, void 0, void 0, function* () {
354
- var _b;
355
- if (message.chatId.includes('g')) {
356
- //chatwoot integration does not support group chats
357
- return;
358
- }
359
- /**
360
- * Does the contact exist in chatwoot?
361
- */
362
- if (!contactReg[message.chatId]) {
363
- const contact = yield searchContact(message.chatId);
364
- if (contact) {
365
- contactReg[message.chatId] = contact.id;
371
+ }
372
+ getContactConversation(number) {
373
+ return __awaiter(this, void 0, void 0, function* () {
374
+ try {
375
+ const { data } = yield this.cwReq('get', `contacts/${contactReg[number]}/conversations`);
376
+ const allContactConversations = data.payload.filter(c => `${c.inbox_id}` == `${this.inboxId}`).sort((a, b) => a.id - b.id);
377
+ const [opened, notOpen] = [allContactConversations.filter(c => c.status === 'open'), allContactConversations.filter(c => c.status != 'open')];
378
+ const hasOpenConvo = opened[0] ? true : false;
379
+ const resolvedConversation = opened[0] || notOpen[0];
380
+ if (!hasOpenConvo) {
381
+ //reopen convo
382
+ yield this.openConversation(resolvedConversation.id);
383
+ }
384
+ return resolvedConversation;
366
385
  }
367
- else {
368
- //create the contact
369
- contactReg[message.chatId] = (yield createContact(message.sender)).id;
386
+ catch (error) {
387
+ return;
370
388
  }
371
- }
372
- if (!convoReg[message.chatId]) {
373
- const conversation = yield getContactConversation(message.chatId);
374
- if (conversation) {
375
- convoReg[message.chatId] = conversation.id;
389
+ });
390
+ }
391
+ createConversation(contact_id) {
392
+ return __awaiter(this, void 0, void 0, function* () {
393
+ try {
394
+ const { data } = yield this.cwReq('post', `conversations`, {
395
+ contact_id,
396
+ "inbox_id": this.inboxId
397
+ });
398
+ return data;
376
399
  }
377
- else {
378
- //create the conversation
379
- convoReg[message.chatId] = (yield createConversation(contactReg[message.chatId])).id;
400
+ catch (error) {
401
+ return;
380
402
  }
381
- }
382
- /**
383
- * Does the conversation exist in
384
- */
385
- let text = message.body;
386
- let hasAttachments = false;
387
- switch (message.type) {
388
- case 'location':
389
- text = `Location Message:\n\n${message.loc}\n\nhttps://www.google.com/maps?q=${message.lat},${message.lng}`;
390
- break;
391
- case 'buttons_response':
392
- text = message.selectedButtonId;
393
- break;
394
- case 'document':
395
- case 'image':
396
- case 'audio':
397
- case 'ptt':
398
- case 'video':
399
- if (message.cloudUrl) {
400
- text = `FILE:\t${message.cloudUrl}\n\nMESSAGE:\t${message.text}`;
403
+ });
404
+ }
405
+ createContact(contact) {
406
+ return __awaiter(this, void 0, void 0, function* () {
407
+ try {
408
+ const { data } = yield this.cwReq('post', `contacts`, {
409
+ "identifier": contact.id,
410
+ "name": contact.formattedName || contact.id,
411
+ "phone_number": `+${contact.id.replace('@c.us', '')}`,
412
+ "avatar_url": contact.profilePicThumbObj.eurl,
413
+ "custom_attributes": Object.assign({ "wa:number": `${contact.id.replace('@c.us', '')}` }, contact)
414
+ });
415
+ return data.payload.contact;
416
+ }
417
+ catch (error) {
418
+ return;
419
+ }
420
+ });
421
+ }
422
+ openConversation(conversationId, status = "opened") {
423
+ return __awaiter(this, void 0, void 0, function* () {
424
+ try {
425
+ const { data } = yield this.cwReq('post', `conversations/${conversationId}/messages`, {
426
+ status
427
+ });
428
+ return data;
429
+ }
430
+ catch (error) {
431
+ return;
432
+ }
433
+ });
434
+ }
435
+ sendConversationMessage(content, contactId, message) {
436
+ return __awaiter(this, void 0, void 0, function* () {
437
+ __1.log.info(`WA=>CW ${contactId}: ${content} ${message.id}`);
438
+ try {
439
+ const { data } = yield this.cwReq('post', `conversations/${convoReg[contactId]}/messages`, {
440
+ content,
441
+ "message_type": message.fromMe ? "outgoing" : "incoming",
442
+ "private": false,
443
+ echo_id: message.id
444
+ });
445
+ return data;
446
+ }
447
+ catch (error) {
448
+ return;
449
+ }
450
+ });
451
+ }
452
+ sendAttachmentMessage(content, contactId, message) {
453
+ return __awaiter(this, void 0, void 0, function* () {
454
+ // decrypt message
455
+ const file = yield this.client.decryptMedia(message);
456
+ __1.log.info(`INCOMING MESSAGE ATTACHMENT ${contactId}: ${content} ${message.id}`);
457
+ const formData = new form_data_1.default();
458
+ formData.append('attachments[]', Buffer.from(file.split(',')[1], 'base64'), {
459
+ knownLength: 1,
460
+ filename: `${message.t}.${mime_types_1.default.extension(message.mimetype)}`,
461
+ contentType: (file.match(/[^:\s*]\w+\/[\w-+\d.]+(?=[;| ])/) || ["application/octet-stream"])[0]
462
+ });
463
+ formData.append('content', content);
464
+ formData.append('message_type', 'incoming');
465
+ try {
466
+ const { data } = yield this.cwReq('post', `conversations/${convoReg[contactId]}/messages`, formData, formData.getHeaders());
467
+ return data;
468
+ }
469
+ catch (error) {
470
+ return;
471
+ }
472
+ });
473
+ }
474
+ processWAMessage(message) {
475
+ var _a;
476
+ return __awaiter(this, void 0, void 0, function* () {
477
+ let isNewConversation = false;
478
+ if (message.chatId.includes('g')) {
479
+ //chatwoot integration does not support group chats
480
+ return;
481
+ }
482
+ /**
483
+ * Does the contact exist in chatwoot?
484
+ */
485
+ if (!contactReg[message.chatId]) {
486
+ const contact = yield this.searchContact(message.chatId);
487
+ if (contact) {
488
+ contactReg[message.chatId] = contact.id;
401
489
  }
402
490
  else {
403
- text = message.text;
404
- hasAttachments = true;
491
+ //create the contact
492
+ contactReg[message.chatId] = (yield this.createContact(message.sender)).id;
405
493
  }
406
- break;
407
- default:
408
- text = ((_b = message === null || message === void 0 ? void 0 : message.ctwaContext) === null || _b === void 0 ? void 0 : _b.sourceUrl) ? `${message.body}\n\n${message.ctwaContext.sourceUrl}` : message.body || "__UNHANDLED__";
409
- break;
410
- }
411
- if (hasAttachments)
412
- yield sendAttachmentMessage(text, message.chatId, message);
413
- else
414
- yield sendConversationMessage(text, message.chatId, message);
415
- });
416
- // const inboxId = s.match(/conversations\/\d*/g) && s.match(/conversations\/\d*/g)[0].replace('conversations/','')
417
- /**
418
- * Update the chatwoot contact and conversation registries
419
- */
420
- const setOnMessageProm = client.onMessage(processWAMessage);
421
- const setOnAckProm = client.onAck((ackEvent) => __awaiter(void 0, void 0, void 0, function* () {
422
- if (ackEvent.ack == 1 && ackEvent.isNewMsg && ackEvent.self === "in") {
423
- if (ignoreMap[ackEvent.id]) {
424
- delete ignoreMap[ackEvent.id];
494
+ }
495
+ if (!convoReg[message.chatId]) {
496
+ const conversation = yield this.getContactConversation(message.chatId);
497
+ if (conversation) {
498
+ convoReg[message.chatId] = conversation.id;
499
+ }
500
+ else {
501
+ //create the conversation
502
+ convoReg[message.chatId] = (yield this.createConversation(contactReg[message.chatId])).id;
503
+ isNewConversation = convoReg[message.chatId];
504
+ }
505
+ }
506
+ /**
507
+ * Does the conversation exist in
508
+ */
509
+ let text = message.body;
510
+ let hasAttachments = false;
511
+ switch (message.type) {
512
+ case 'list_response':
513
+ /**
514
+ * Possible CSAT response:
515
+ */
516
+ yield this.processCSATResponse(message);
517
+ break;
518
+ case 'location':
519
+ text = `Location Message:\n\n${message.loc}\n\nhttps://www.google.com/maps?q=${message.lat},${message.lng}`;
520
+ break;
521
+ case 'buttons_response':
522
+ text = message.selectedButtonId;
523
+ break;
524
+ case 'document':
525
+ case 'image':
526
+ case 'audio':
527
+ case 'ptt':
528
+ case 'video':
529
+ if (message.cloudUrl) {
530
+ text = `FILE:\t${message.cloudUrl}\n\nMESSAGE:\t${message.text}`;
531
+ }
532
+ else {
533
+ text = message.text;
534
+ hasAttachments = true;
535
+ }
536
+ break;
537
+ default:
538
+ text = ((_a = message === null || message === void 0 ? void 0 : message.ctwaContext) === null || _a === void 0 ? void 0 : _a.sourceUrl) ? `${message.body}\n\n${message.ctwaContext.sourceUrl}` : message.body || "__UNHANDLED__";
539
+ break;
540
+ }
541
+ const newCWMessage = hasAttachments ? yield this.sendAttachmentMessage(text, message.chatId, message) : yield this.sendConversationMessage(text, message.chatId, message);
542
+ if (isNewConversation !== false) {
543
+ /**
544
+ * Wait 3 seconds before trying to check for an automated message
545
+ */
546
+ yield (0, tools_1.timeout)(3000);
547
+ /**
548
+ * Check the messages to see if a message_type: 3 comes through after the initial message;
549
+ */
550
+ const msgs = yield this.getAllInboxMessages(`${isNewConversation}`);
551
+ if (!msgs)
552
+ return;
553
+ /**
554
+ * Message IDs are numbers (for now)
555
+ */
556
+ const possibleWelcomeMessage = msgs.filter(m => m.id > newCWMessage.id).find(m => m.message_type === 3 && m.content_type !== 'input_csat');
557
+ if (!possibleWelcomeMessage)
558
+ return;
559
+ /**
560
+ * Ok reply with the welcome message now
561
+ */
562
+ yield this.client.sendText(message.chatId, possibleWelcomeMessage.content || "...");
563
+ }
564
+ });
565
+ }
566
+ processCSATResponse(message) {
567
+ return __awaiter(this, void 0, void 0, function* () {
568
+ const csatResponse = parseIdAndScore(message.listResponse.rowId);
569
+ if (!csatResponse)
425
570
  return;
571
+ if (csatResponse.id && csatResponse.score) {
572
+ __1.log.info(`CW:CSAT RESPONSE: ${csatResponse.id} ${csatResponse.score}`);
573
+ /**
574
+ * PUT request to report CSAT response
575
+ */
576
+ yield axios_1.default.put(`https://app.chatwoot.com/public/api/v1/csat_survey/${csatResponse.id}`, {
577
+ "message": {
578
+ "submitted_values": {
579
+ "csat_survey_response": {
580
+ "rating": csatResponse.score
581
+ }
582
+ }
583
+ }
584
+ }).catch(e => { });
426
585
  }
427
- const _message = yield client.getMessageById(ackEvent.id);
428
- return yield processWAMessage(_message);
429
- }
430
- return;
431
- }));
432
- proms.push(setOnMessageProm);
433
- proms.push(setOnAckProm);
434
- yield Promise.all(proms);
435
- return;
436
- });
437
- exports.setupChatwootOutgoingMessageHandler = setupChatwootOutgoingMessageHandler;
586
+ });
587
+ }
588
+ sendCSAT(incomlpeteCsatMessage, to) {
589
+ return __awaiter(this, void 0, void 0, function* () {
590
+ const extractCsatLink = str => (str.match(/(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])/gi) || [])[0];
591
+ /**
592
+ * First check if the given csat message object has the link, if not, get the whole csat object
593
+ */
594
+ let csatMessage;
595
+ if (!extractCsatLink(incomlpeteCsatMessage === null || incomlpeteCsatMessage === void 0 ? void 0 : incomlpeteCsatMessage.content)) {
596
+ csatMessage = yield chatwootClient.getMessageObject(incomlpeteCsatMessage.conversation_id, incomlpeteCsatMessage.id);
597
+ }
598
+ else {
599
+ csatMessage = incomlpeteCsatMessage;
600
+ }
601
+ if (!csatMessage)
602
+ return;
603
+ const lic = false; //this.client.getLicenseType()
604
+ const link = extractCsatLink(csatMessage.content);
605
+ const u = new URL(link);
606
+ const csatID = u.pathname.replace('/survey/responses/', '');
607
+ __1.log.info(`SENDING CSAT ${to} ${csatMessage.content}`);
608
+ if (!lic) {
609
+ /**
610
+ * Send as a normal text message with the link
611
+ */
612
+ yield this.client.sendLinkWithAutoPreview(to, link, csatMessage.content);
613
+ }
614
+ else {
615
+ yield this.client.sendListMessage(to, [
616
+ {
617
+ title: "Please rate from 1 - 5",
618
+ rows: [
619
+ {
620
+ "title": "😞",
621
+ "description": "1",
622
+ rowId: `${csatID}:1`
623
+ },
624
+ {
625
+ "title": "😑",
626
+ "description": "2",
627
+ rowId: `${csatID}:2`
628
+ },
629
+ {
630
+ "title": "😐",
631
+ "description": "3",
632
+ rowId: `${csatID}:3`
633
+ },
634
+ {
635
+ "title": "😀",
636
+ "description": "4",
637
+ rowId: `${csatID}:4`
638
+ },
639
+ {
640
+ "title": "😍",
641
+ "description": "5",
642
+ rowId: `${csatID}:5`
643
+ }
644
+ ]
645
+ }
646
+ ], "Customer Survey", "Please rate this conversation", 'Help Us Improve');
647
+ }
648
+ });
649
+ }
650
+ }