@open-wa/wa-automate 4.44.11 → 4.45.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.
@@ -29,6 +29,11 @@ exports.optionList = [{
29
29
  type: String,
30
30
  typeLabel: '{blue {underline mEEwUGEEML2ZThMm252rLg1M}}',
31
31
  description: "The access token of the specific Chatwoot inbox you set up for this session"
32
+ }, {
33
+ name: 'force-update-cw-webhook',
34
+ type: Boolean,
35
+ default: false,
36
+ description: "Updates the chatwoot inbox webhook with the --api-host value on every launch"
32
37
  },
33
38
  {
34
39
  name: 'port',
package/dist/cli/index.js CHANGED
@@ -21,6 +21,7 @@ const setup_1 = require("./setup");
21
21
  const collections_1 = require("./collections");
22
22
  const server_1 = require("./server");
23
23
  const localtunnel_1 = __importDefault(require("localtunnel"));
24
+ const chatwoot_1 = require("./integrations/chatwoot");
24
25
  let checkUrl = (s) => (typeof s === "string") && (0, is_url_superb_1.default)(s);
25
26
  const ready = (config) => __awaiter(void 0, void 0, void 0, function* () {
26
27
  (0, index_1.processSend)('ready');
@@ -199,7 +200,7 @@ function start() {
199
200
  if (cliConfig.tunnel) {
200
201
  spinner.info(`\n• Setting up external tunnel`);
201
202
  const tunnel = yield (0, localtunnel_1.default)({ port: PORT });
202
- cliConfig.tunnel = tunnel.url;
203
+ cliConfig.apiHost = cliConfig.tunnel = tunnel.url;
203
204
  spinner.succeed(`\n\t${(0, terminal_link_1.default)('External address', tunnel.url)}`);
204
205
  }
205
206
  const apiDocsUrl = cliConfig.apiHost ? `${cliConfig.apiHost}/api-docs/ ` : `${cliConfig.host.includes('http') ? '' : 'http://'}${cliConfig.host}:${PORT}/api-docs/ `;
@@ -211,6 +212,7 @@ function start() {
211
212
  const statsLink = (0, terminal_link_1.default)('API Stats', swaggerStatsUrl);
212
213
  spinner.succeed(`\n\t${statsLink}`);
213
214
  }
215
+ yield (0, chatwoot_1.setupChatwootOutgoingMessageHandler)(cliConfig, client);
214
216
  }
215
217
  yield ready(Object.assign(Object.assign(Object.assign(Object.assign({}, createConfig), cliConfig), client.getSessionInfo()), { hostAccountNumber: yield client.getHostNumber() }));
216
218
  if (cliConfig.emitUnread) {
@@ -1,6 +1,7 @@
1
1
  import { Client } from '../..';
2
2
  import { cliFlags } from '../server';
3
3
  import { Request, Response } from "express";
4
+ export declare const chatwoot_webhook_check_event_name = "cli.integrations.chatwoot.check";
4
5
  export declare type expressMiddleware = (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
5
6
  export declare const chatwootMiddleware: (cliConfig: cliFlags, client: Client) => expressMiddleware;
6
7
  export declare const setupChatwootOutgoingMessageHandler: (cliConfig: cliFlags, client: Client) => Promise<void>;
@@ -12,7 +12,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.setupChatwootOutgoingMessageHandler = exports.chatwootMiddleware = void 0;
15
+ exports.setupChatwootOutgoingMessageHandler = exports.chatwootMiddleware = exports.chatwoot_webhook_check_event_name = void 0;
16
+ const uuid_apikey_1 = __importDefault(require("uuid-apikey"));
17
+ const __1 = require("../..");
16
18
  const axios_1 = __importDefault(require("axios"));
17
19
  const form_data_1 = __importDefault(require("form-data"));
18
20
  const mime_types_1 = __importDefault(require("mime-types"));
@@ -24,6 +26,7 @@ const convoReg = {
24
26
  //WID : chatwoot conversation ID
25
27
  "example@c.us": "1"
26
28
  };
29
+ exports.chatwoot_webhook_check_event_name = "cli.integrations.chatwoot.check";
27
30
  const chatwootMiddleware = (cliConfig, client) => {
28
31
  return (req, res) => __awaiter(void 0, void 0, void 0, function* () {
29
32
  const processMesssage = () => __awaiter(void 0, void 0, void 0, function* () {
@@ -48,10 +51,10 @@ const chatwootMiddleware = (cliConfig, client) => {
48
51
  if ((attachments === null || attachments === void 0 ? void 0 : attachments.length) > 0) {
49
52
  //has attachments
50
53
  const [firstAttachment, ...restAttachments] = attachments;
51
- const sendAttachment = (attachment, c) => __awaiter(void 0, void 0, void 0, function* () { return client.sendImage(to, attachment.data_url, attachment.data_url.substring(attachment.data_url.lastIndexOf('/') + 1), c || '', null, true); });
54
+ const sendAttachment = (attachment, c) => __awaiter(void 0, void 0, void 0, function* () { return attachment && client.sendImage(to, attachment.data_url, attachment.data_url.substring(attachment.data_url.lastIndexOf('/') + 1), c || '', null, true); });
52
55
  //send the text as the caption with the first message only
53
56
  promises.push(sendAttachment(firstAttachment, content));
54
- (restAttachments || []).map(sendAttachment).map(promises.push);
57
+ ((restAttachments || []).map(attachment => sendAttachment(attachment)) || []).map(p => promises.push(p));
55
58
  }
56
59
  else {
57
60
  //no attachments
@@ -71,7 +74,15 @@ const chatwootMiddleware = (cliConfig, client) => {
71
74
  }
72
75
  else {
73
76
  //not a location message
74
- promises.push(client.sendText(to, content));
77
+ /**
78
+ * Check for url
79
+ */
80
+ const urlregex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
81
+ if (content.match(urlregex) && content.match(urlregex)[0]) {
82
+ promises.push(client.sendLinkWithAutoPreview(to, content.match(urlregex)[0], content));
83
+ }
84
+ else
85
+ promises.push(client.sendText(to, content));
75
86
  }
76
87
  }
77
88
  return yield Promise.all(promises);
@@ -89,15 +100,66 @@ const chatwootMiddleware = (cliConfig, client) => {
89
100
  };
90
101
  exports.chatwootMiddleware = chatwootMiddleware;
91
102
  const setupChatwootOutgoingMessageHandler = (cliConfig, client) => __awaiter(void 0, void 0, void 0, function* () {
103
+ __1.log.info(`Setting up chatwoot integration: ${cliConfig.chatwootUrl}`);
92
104
  const u = cliConfig.chatwootUrl; //e.g `"localhost:3000/api/v1/accounts/3"
93
105
  const api_access_token = cliConfig.chatwootApiAccessToken;
94
106
  const _u = new URL(u);
95
107
  const origin = _u.origin;
96
108
  const port = _u.port || 80;
97
- const [accountId, inboxId] = u.match(/\/(app|(api\/v1))\/accounts\/\d*\/(inbox|inboxes)\/\d*/g)[0].split('/').filter(Number);
109
+ const accountNumber = yield client.getHostNumber();
110
+ const proms = [];
111
+ let expectedSelfWebhookUrl = cliConfig.apiHost ? `${cliConfig.apiHost}/chatwoot ` : `${cliConfig.host.includes('http') ? '' : 'http://'}${cliConfig.host}:${cliConfig.port}/chatwoot `;
112
+ expectedSelfWebhookUrl = expectedSelfWebhookUrl.trim();
113
+ if (cliConfig.key)
114
+ expectedSelfWebhookUrl = `${expectedSelfWebhookUrl}?api_key=${cliConfig.key}`;
115
+ let [accountId, inboxId] = (u.match(/\/(app|(api\/v1))\/accounts\/\d*\/(inbox|inboxes)\/\d*/g) || [''])[0].split('/').filter(Number);
116
+ inboxId = inboxId || u.match(/inboxes\/\d*/g) && u.match(/inboxes\/\d*/g)[0].replace('inboxes/', '');
98
117
  // const accountId = u.match(/accounts\/\d*/g) && u.match(/accounts\/\d*/g)[0].replace('accounts/', '')
99
- const resolvedInbox = inboxId || u.match(/inboxes\/\d*/g) && u.match(/inboxes\/\d*/g)[0].replace('inboxes/', '');
100
- const cwReq = (path, method, data, _headers) => {
118
+ /**
119
+ * Is the inbox and or account id undefined??
120
+ */
121
+ if (!accountId) {
122
+ __1.log.info(`CHATWOOT INTEGRATION: account ID missing. Attempting to infer from access token....`);
123
+ /**
124
+ * If the account ID is undefined then get the account ID from the access_token
125
+ */
126
+ accountId = (yield axios_1.default.get(`${origin}/api/v1/profile`, { headers: { api_access_token } })).data.account_id;
127
+ __1.log.info(`CHATWOOT INTEGRATION: Got account ID: ${accountId}`);
128
+ }
129
+ if (!inboxId) {
130
+ __1.log.info(`CHATWOOT INTEGRATION: inbox ID missing. Attempting to find correct inbox....`);
131
+ /**
132
+ * Find the inbox with the correct setup.
133
+ */
134
+ const inboxArray = (yield axios_1.default.get(`${origin}/api/v1/accounts/${accountId}/inboxes`, { headers: { api_access_token } })).data.payload;
135
+ 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; });
136
+ if (possibleInbox) {
137
+ __1.log.info(`CHATWOOT INTEGRATION: found inbox: ${JSON.stringify(possibleInbox)}`);
138
+ __1.log.info(`CHATWOOT INTEGRATION: found inbox id: ${possibleInbox.channel_id}`);
139
+ inboxId = possibleInbox.channel_id;
140
+ }
141
+ else {
142
+ __1.log.info(`CHATWOOT INTEGRATION: inbox not found. Attempting to create inbox....`);
143
+ /**
144
+ * Create inbox
145
+ */
146
+ const { data: new_inbox } = (yield axios_1.default.post(`${origin}/api/v1/accounts/${accountId}/inboxes`, {
147
+ "name": `open-wa-${accountNumber}`,
148
+ "channel": {
149
+ "phone_number": `${accountNumber}`,
150
+ "type": "api",
151
+ "webhook_url": expectedSelfWebhookUrl,
152
+ "additional_attributes": {
153
+ "sessionId": client.getSessionId(),
154
+ "hostAccountNumber": `${accountNumber}`
155
+ }
156
+ }
157
+ }, { headers: { api_access_token } }));
158
+ inboxId = new_inbox.id;
159
+ __1.log.info(`CHATWOOT INTEGRATION: inbox created. id: ${inboxId}`);
160
+ }
161
+ }
162
+ const cwReq = (method, path, data, _headers) => {
101
163
  const url = `${origin}/api/v1/accounts/${accountId}/${path}`.replace('app.bentonow.com', 'chat.bentonow.com');
102
164
  // console.log(url,method,data)
103
165
  return (0, axios_1.default)({
@@ -107,20 +169,74 @@ const setupChatwootOutgoingMessageHandler = (cliConfig, client) => __awaiter(voi
107
169
  headers: Object.assign({ api_access_token }, _headers)
108
170
  });
109
171
  };
110
- const { data: get_inbox } = yield cwReq(`inboxes/${resolvedInbox}`, 'get');
111
- // const inboxId = `openwa_${sessionId}`
172
+ let { data: get_inbox } = yield cwReq('get', `inboxes/${inboxId}`);
173
+ /**
174
+ * Update the webhook
175
+ */
176
+ const updatePayload = {
177
+ "channel": {
178
+ "additional_attributes": {
179
+ "sessionId": client.getSessionId(),
180
+ "hostAccountNumber": `${accountNumber}`,
181
+ "instanceId": `${client.getInstanceId()}`
182
+ }
183
+ }
184
+ };
185
+ if (cliConfig.forceUpdateCwWebhook)
186
+ updatePayload.channel['webhook_url'] = expectedSelfWebhookUrl;
187
+ const updateInboxPromise = cwReq('patch', `inboxes/${inboxId}`, updatePayload);
188
+ if (cliConfig.forceUpdateCwWebhook)
189
+ get_inbox = (yield updateInboxPromise).data;
190
+ else
191
+ proms.push(updateInboxPromise);
112
192
  /**
113
193
  * Get the inbox and test it.
114
194
  */
115
195
  if (!((get_inbox === null || get_inbox === void 0 ? void 0 : get_inbox.webhook_url) || "").includes("/chatwoot"))
116
196
  console.log("Please set the chatwoot inbox webhook to this sessions URL with path /chatwoot");
197
+ /**
198
+ * Check the webhook URL
199
+ */
200
+ const chatwootWebhookCheck = () => __awaiter(void 0, void 0, void 0, function* () {
201
+ let checkCodePromise;
202
+ const cancelCheckProm = () => (checkCodePromise.cancel && typeof checkCodePromise.cancel === "function") && checkCodePromise.cancel();
203
+ try {
204
+ const wUrl = get_inbox.webhook_url.split('?')[0].replace(/\/+$/, "").trim();
205
+ const checkWhURL = `${wUrl}${wUrl.endsWith("/") ? '' : `/`}checkWebhook${cliConfig.key ? `?api_key=${cliConfig.key}` : ''}`;
206
+ __1.log.info(`Verifying webhook url: ${checkWhURL}`);
207
+ const checkCode = uuid_apikey_1.default.create().apiKey; //random generated string
208
+ yield new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () {
209
+ var _a;
210
+ checkCodePromise = __1.ev.waitFor(exports.chatwoot_webhook_check_event_name, 5000).catch(reject);
211
+ yield axios_1.default.post(checkWhURL, {
212
+ checkCode
213
+ }, { headers: { api_key: cliConfig.key || '' } }).catch(reject);
214
+ const checkCodeResponse = yield checkCodePromise;
215
+ if (checkCodeResponse && ((_a = checkCodeResponse[0]) === null || _a === void 0 ? void 0 : _a.checkCode) == checkCode)
216
+ resolve(true);
217
+ else
218
+ reject(`Webhook check code is incorrect. Expected ${checkCode} - incoming ${((checkCodeResponse || [])[0] || {}).checkCode}`);
219
+ }));
220
+ __1.log.info('Chatwoot webhook verification successful');
221
+ }
222
+ catch (error) {
223
+ cancelCheckProm();
224
+ const e = `Unable to verify the chatwoot webhook URL on this inbox: ${error.message}`;
225
+ console.error(e);
226
+ __1.log.error(e);
227
+ }
228
+ finally {
229
+ cancelCheckProm();
230
+ }
231
+ });
232
+ proms.push(chatwootWebhookCheck());
117
233
  /**
118
234
  * Get Contacts and conversations
119
235
  */
120
236
  const searchContact = (number) => __awaiter(void 0, void 0, void 0, function* () {
121
237
  try {
122
238
  const n = number.replace('@c.us', '');
123
- const { data } = yield cwReq(`contacts/search?q=${n}&sort=phone_number`, 'get');
239
+ const { data } = yield cwReq('get', `contacts/search?q=${n}&sort=phone_number`);
124
240
  if (data.payload.length) {
125
241
  return data.payload.find(x => (x.phone_number || "").includes(n)) || false;
126
242
  }
@@ -133,8 +249,8 @@ const setupChatwootOutgoingMessageHandler = (cliConfig, client) => __awaiter(voi
133
249
  });
134
250
  const getContactConversation = (number) => __awaiter(void 0, void 0, void 0, function* () {
135
251
  try {
136
- const { data } = yield cwReq(`contacts/${contactReg[number]}/conversations`, 'get');
137
- const allContactConversations = data.payload.filter(c => c.inbox_id === resolvedInbox).sort((a, b) => a.id - b.id);
252
+ const { data } = yield cwReq('get', `contacts/${contactReg[number]}/conversations`);
253
+ const allContactConversations = data.payload.filter(c => c.inbox_id === inboxId).sort((a, b) => a.id - b.id);
138
254
  const [opened, notOpen] = [allContactConversations.filter(c => c.status === 'open'), allContactConversations.filter(c => c.status != 'open')];
139
255
  return opened[0] || notOpen[0];
140
256
  }
@@ -144,9 +260,9 @@ const setupChatwootOutgoingMessageHandler = (cliConfig, client) => __awaiter(voi
144
260
  });
145
261
  const createConversation = (contact_id) => __awaiter(void 0, void 0, void 0, function* () {
146
262
  try {
147
- const { data } = yield cwReq(`conversations`, 'post', {
263
+ const { data } = yield cwReq('post', `conversations`, {
148
264
  contact_id,
149
- "inbox_id": resolvedInbox
265
+ "inbox_id": inboxId
150
266
  });
151
267
  return data;
152
268
  }
@@ -156,7 +272,7 @@ const setupChatwootOutgoingMessageHandler = (cliConfig, client) => __awaiter(voi
156
272
  });
157
273
  const createContact = (contact) => __awaiter(void 0, void 0, void 0, function* () {
158
274
  try {
159
- const { data } = yield cwReq(`contacts`, 'post', {
275
+ const { data } = yield cwReq('post', `contacts`, {
160
276
  "identifier": contact.id,
161
277
  "name": contact.formattedName || contact.id,
162
278
  "phone_number": `+${contact.id.replace('@c.us', '')}`,
@@ -170,7 +286,7 @@ const setupChatwootOutgoingMessageHandler = (cliConfig, client) => __awaiter(voi
170
286
  });
171
287
  const sendConversationMessage = (content, contactId, message) => __awaiter(void 0, void 0, void 0, function* () {
172
288
  try {
173
- const { data } = yield cwReq(`conversations/${convoReg[contactId]}/messages`, 'post', {
289
+ const { data } = yield cwReq('post', `conversations/${convoReg[contactId]}/messages`, {
174
290
  content,
175
291
  "message_type": 0,
176
292
  "private": false
@@ -193,7 +309,7 @@ const setupChatwootOutgoingMessageHandler = (cliConfig, client) => __awaiter(voi
193
309
  formData.append('content', content);
194
310
  formData.append('message_type', 'incoming');
195
311
  try {
196
- const { data } = yield cwReq(`conversations/${convoReg[contactId]}/messages`, 'post', formData, formData.getHeaders());
312
+ const { data } = yield cwReq('post', `conversations/${convoReg[contactId]}/messages`, formData, formData.getHeaders());
197
313
  return data;
198
314
  }
199
315
  catch (error) {
@@ -204,7 +320,7 @@ const setupChatwootOutgoingMessageHandler = (cliConfig, client) => __awaiter(voi
204
320
  /**
205
321
  * Update the chatwoot contact and conversation registries
206
322
  */
207
- client.onMessage((message) => __awaiter(void 0, void 0, void 0, function* () {
323
+ const setOnMessageProm = client.onMessage((message) => __awaiter(void 0, void 0, void 0, function* () {
208
324
  if (message.from.includes('g')) {
209
325
  //chatwoot integration does not support group chats
210
326
  return;
@@ -266,5 +382,8 @@ const setupChatwootOutgoingMessageHandler = (cliConfig, client) => __awaiter(voi
266
382
  else
267
383
  yield sendConversationMessage(text, message.from, message);
268
384
  }));
385
+ proms.push(setOnMessageProm);
386
+ yield Promise.all(proms);
387
+ return;
269
388
  });
270
389
  exports.setupChatwootOutgoingMessageHandler = setupChatwootOutgoingMessageHandler;
@@ -298,7 +298,13 @@ const setupTwilioCompatibleWebhook = (cliConfig, client) => {
298
298
  exports.setupTwilioCompatibleWebhook = setupTwilioCompatibleWebhook;
299
299
  const setupChatwoot = (cliConfig, client) => __awaiter(void 0, void 0, void 0, function* () {
300
300
  exports.app.post('/chatwoot', (0, chatwoot_1.chatwootMiddleware)(cliConfig, client));
301
- yield (0, chatwoot_1.setupChatwootOutgoingMessageHandler)(cliConfig, client);
301
+ exports.app.post(`/chatwoot/checkWebhook`, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
302
+ const { body } = req;
303
+ __1.log.info(`chatwoot webhook check request received: ${body.checkCode}`);
304
+ yield __1.ev.emitAsync(chatwoot_1.chatwoot_webhook_check_event_name, body);
305
+ return res.send({});
306
+ }));
307
+ // await setupChatwootOutgoingMessageHandler(cliConfig, client);
302
308
  });
303
309
  exports.setupChatwoot = setupChatwoot;
304
310
  const setupBotPressHandler = (cliConfig, client) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-wa/wa-automate",
3
- "version": "4.44.11",
3
+ "version": "4.45.0",
4
4
  "licenseCheckUrl": "https://funcs.openwa.dev/license-check",
5
5
  "brokenMethodReportUrl": "https://funcs.openwa.dev/report-bm",
6
6
  "patches": "https://cdn.openwa.dev/patches.json",
@@ -123,7 +123,7 @@
123
123
  "cross-spawn": "^7.0.3",
124
124
  "datauri": "^4.0.1",
125
125
  "death": "^1.1.0",
126
- "eventemitter2": "^6.4.4",
126
+ "eventemitter2": "^6.4.7",
127
127
  "express": "^4.17.1",
128
128
  "express-ipfilter": "^1.3.1",
129
129
  "express-robots-txt": "^1.0.0",