@builderbot/provider-evolution-api 1.2.7 → 1.2.8

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/dist/index.cjs CHANGED
@@ -3,19 +3,69 @@
3
3
  var bot = require('@builderbot/bot');
4
4
  var require$$1 = require('util');
5
5
  var stream = require('stream');
6
- var require$$1$1 = require('path');
6
+ var path$1 = require('path');
7
7
  var require$$3 = require('http');
8
8
  var require$$4 = require('https');
9
9
  var require$$0$1 = require('url');
10
- var require$$6 = require('fs');
10
+ var fs$1 = require('fs');
11
11
  var crypto = require('crypto');
12
12
  var require$$4$1 = require('assert');
13
- var require$$1$2 = require('tty');
13
+ var require$$1$1 = require('tty');
14
14
  var require$$0$2 = require('os');
15
15
  var zlib = require('zlib');
16
16
  var require$$0$3 = require('events');
17
+ var promises = require('fs/promises');
18
+ var node_crypto = require('node:crypto');
17
19
  var EventEmitter = require('node:events');
18
20
 
21
+ function getDefaultExportFromCjs (x) {
22
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
23
+ }
24
+
25
+ var json = function (opts={}) {
26
+ const limit = opts.limit || 100 * 1024; // 100kb
27
+ const type = opts.type || 'application/json';
28
+
29
+ return function (req, res, next) {
30
+ if (req._body) return next();
31
+ req.body = req.body || {};
32
+
33
+ const head = req.headers;
34
+ const ctype = head['content-type'];
35
+ const clength = parseInt(head['content-length'], 10);
36
+
37
+ if (isNaN(clength) && head['transfer-encoding'] == null) return next(); // no body
38
+ if (ctype && !ctype.includes(type)) return next(); // not json
39
+ if (clength === 0) return next(); // is empty
40
+
41
+ let bits = [];
42
+ let length = 0;
43
+ req.on('data', x => {
44
+ length += Buffer.byteLength(x);
45
+ if (length <= limit) {
46
+ bits.push(x);
47
+ } else {
48
+ next({
49
+ code: 413,
50
+ details: 'Exceeded JSON limit'
51
+ });
52
+ req.destroy();
53
+ }
54
+ }).on('end', () => {
55
+ try {
56
+ req.body = JSON.parse(bits);
57
+ req._body = true;
58
+ next();
59
+ } catch (err) {
60
+ err.code = 422;
61
+ err.details = err.message;
62
+ err.message = 'Invalid JSON';
63
+ next(err);
64
+ }
65
+ }).on('error', next);
66
+ };
67
+ };
68
+
19
69
  function bind$1(fn, thisArg) {
20
70
  return function wrap() {
21
71
  return fn.apply(thisArg, arguments);
@@ -861,10 +911,6 @@ AxiosError$1.from = (error, code, config, request, response, customProps) => {
861
911
  return axiosError;
862
912
  };
863
913
 
864
- function getDefaultExportFromCjs (x) {
865
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
866
- }
867
-
868
914
  var Stream$2 = stream.Stream;
869
915
  var util$2 = require$$1;
870
916
 
@@ -11915,7 +11961,7 @@ var mimeDb = require$$0;
11915
11961
  */
11916
11962
 
11917
11963
  var db = mimeDb;
11918
- var extname = require$$1$1.extname;
11964
+ var extname = path$1.extname;
11919
11965
 
11920
11966
  /**
11921
11967
  * Module variables.
@@ -13385,11 +13431,11 @@ var populate$1 = function(dst, src) {
13385
13431
 
13386
13432
  var CombinedStream = combined_stream;
13387
13433
  var util = require$$1;
13388
- var path = require$$1$1;
13389
- var http$1 = require$$3;
13390
- var https$1 = require$$4;
13434
+ var path = path$1;
13435
+ var http$2 = require$$3;
13436
+ var https$2 = require$$4;
13391
13437
  var parseUrl$2 = require$$0$1.parse;
13392
- var fs = require$$6;
13438
+ var fs = fs$1;
13393
13439
  var Stream = stream.Stream;
13394
13440
  var mime = mimeTypes;
13395
13441
  var asynckit = asynckit$1;
@@ -13836,9 +13882,9 @@ FormData$1.prototype.submit = function(params, cb) {
13836
13882
 
13837
13883
  // https if specified, fallback to http in any other case
13838
13884
  if (options.protocol == 'https:') {
13839
- request = https$1.request(options);
13885
+ request = https$2.request(options);
13840
13886
  } else {
13841
- request = http$1.request(options);
13887
+ request = http$2.request(options);
13842
13888
  }
13843
13889
 
13844
13890
  // get content length and fire away
@@ -14372,12 +14418,12 @@ const hasStandardBrowserWebWorkerEnv = (() => {
14372
14418
  const origin = hasBrowserEnv && window.location.href || 'http://localhost';
14373
14419
 
14374
14420
  var utils = /*#__PURE__*/Object.freeze({
14375
- __proto__: null,
14376
- hasBrowserEnv: hasBrowserEnv,
14377
- hasStandardBrowserEnv: hasStandardBrowserEnv,
14378
- hasStandardBrowserWebWorkerEnv: hasStandardBrowserWebWorkerEnv,
14379
- navigator: _navigator,
14380
- origin: origin
14421
+ __proto__: null,
14422
+ hasBrowserEnv: hasBrowserEnv,
14423
+ hasStandardBrowserEnv: hasStandardBrowserEnv,
14424
+ hasStandardBrowserWebWorkerEnv: hasStandardBrowserWebWorkerEnv,
14425
+ navigator: _navigator,
14426
+ origin: origin
14381
14427
  });
14382
14428
 
14383
14429
  var platform = {
@@ -16007,7 +16053,7 @@ function requireSupportsColor () {
16007
16053
  if (hasRequiredSupportsColor) return supportsColor_1;
16008
16054
  hasRequiredSupportsColor = 1;
16009
16055
  const os = require$$0$2;
16010
- const tty = require$$1$2;
16056
+ const tty = require$$1$1;
16011
16057
  const hasFlag = requireHasFlag();
16012
16058
 
16013
16059
  const {env} = process;
@@ -16153,7 +16199,7 @@ function requireNode () {
16153
16199
  if (hasRequiredNode) return node.exports;
16154
16200
  hasRequiredNode = 1;
16155
16201
  (function (module, exports) {
16156
- const tty = require$$1$2;
16202
+ const tty = require$$1$1;
16157
16203
  const util = require$$1;
16158
16204
 
16159
16205
  /**
@@ -16452,8 +16498,8 @@ var debug_1 = function () {
16452
16498
 
16453
16499
  var url = require$$0$1;
16454
16500
  var URL$1 = url.URL;
16455
- var http = require$$3;
16456
- var https = require$$4;
16501
+ var http$1 = require$$3;
16502
+ var https$1 = require$$4;
16457
16503
  var Writable = stream.Writable;
16458
16504
  var assert = require$$4$1;
16459
16505
  var debug = debug_1;
@@ -17134,7 +17180,7 @@ function isURL(value) {
17134
17180
  }
17135
17181
 
17136
17182
  // Exports
17137
- followRedirects$1.exports = wrap({ http: http, https: https });
17183
+ followRedirects$1.exports = wrap({ http: http$1, https: https$1 });
17138
17184
  followRedirects$1.exports.wrap = wrap;
17139
17185
 
17140
17186
  var followRedirectsExports = followRedirects$1.exports;
@@ -19830,302 +19876,258 @@ var _events=_interopRequireDefault(require$$0$3);Object.defineProperty(exports,"
19830
19876
  var distExports = dist.exports;
19831
19877
  var Queue = /*@__PURE__*/getDefaultExportFromCjs(distExports);
19832
19878
 
19833
- async function getMediaUrl(version, IdMedia, numberId, Token) {
19834
- try {
19835
- const response = await axios.get(`https://graph.facebook.com/${version}/${IdMedia}?phone_number_id=${numberId}`, {
19836
- headers: {
19837
- Authorization: `Bearer ${Token}`,
19838
- },
19839
- maxBodyLength: Infinity,
19840
- });
19841
- return response.data?.url;
19842
- }
19843
- catch (error) {
19844
- console.error(error.message);
19845
- }
19846
- }
19847
-
19848
- const processIncomingMessage = async ({ messageId, messageTimestamp, pushName, message, to, jwtToken, version, numberId, fileData, }) => {
19849
- let responseObj;
19850
- switch (message.type) {
19851
- case 'text': {
19852
- responseObj = {
19853
- type: message.type,
19854
- from: message.from,
19855
- to,
19856
- body: message.text?.body,
19857
- name: pushName,
19858
- pushName,
19859
- };
19860
- break;
19861
- }
19862
- case 'interactive': {
19863
- responseObj = {
19864
- type: 'interactive',
19865
- from: message.from,
19866
- to,
19867
- body: message.interactive?.button_reply?.title ??
19868
- message.interactive?.list_reply?.id ??
19869
- message.interactive?.nfm_reply.response_json,
19870
- title_button_reply: message.interactive?.button_reply?.title,
19871
- title_list_reply: message.interactive?.list_reply?.title,
19872
- nfm_reply: message.interactive?.nfm_reply?.response_json
19873
- ? JSON.parse(message.interactive?.nfm_reply?.response_json)
19874
- : undefined,
19875
- pushName,
19876
- name: pushName,
19877
- };
19878
- break;
19879
- }
19880
- case 'button': {
19881
- responseObj = {
19882
- type: 'button',
19883
- from: message.from,
19884
- to,
19885
- body: message.button?.text,
19886
- payload: message.button?.payload,
19887
- title_button_reply: message.button?.payload,
19888
- pushName,
19889
- name: pushName,
19890
- };
19891
- break;
19892
- }
19893
- case 'image': {
19894
- const imageUrl = await getMediaUrl(version, message.image?.id, numberId, jwtToken);
19895
- responseObj = {
19896
- type: message.type,
19897
- from: message.from,
19898
- url: imageUrl,
19899
- fileData,
19900
- caption: message?.image?.caption,
19901
- to,
19902
- body: bot.utils.generateRefProvider('_event_media_'),
19903
- pushName,
19904
- name: pushName,
19905
- };
19906
- break;
19907
- }
19908
- case 'document': {
19909
- const documentUrl = await getMediaUrl(version, message.document?.id, numberId, jwtToken);
19910
- responseObj = {
19911
- type: message.type,
19912
- from: message.from,
19913
- url: documentUrl,
19914
- fileData,
19915
- to,
19916
- body: bot.utils.generateRefProvider('_event_document_'),
19917
- pushName,
19918
- name: pushName,
19919
- };
19920
- break;
19921
- }
19922
- case 'video': {
19923
- const videoUrl = await getMediaUrl(version, message.video?.id, numberId, jwtToken);
19924
- responseObj = {
19925
- type: message.type,
19926
- from: message.from,
19927
- url: videoUrl,
19928
- fileData,
19929
- caption: message?.video?.caption,
19930
- to,
19931
- body: bot.utils.generateRefProvider('_event_media_'),
19932
- pushName,
19933
- name: pushName,
19934
- };
19935
- break;
19936
- }
19937
- case 'location': {
19938
- responseObj = {
19939
- type: message.type,
19940
- from: message.from,
19941
- to,
19942
- latitude: message.location.latitude,
19943
- longitude: message.location.longitude,
19944
- body: bot.utils.generateRefProvider('_event_location_'),
19945
- pushName,
19946
- name: pushName,
19947
- };
19948
- break;
19949
- }
19950
- case 'audio': {
19951
- const audioUrl = await getMediaUrl(version, message.audio?.id, numberId, jwtToken);
19952
- responseObj = {
19953
- type: message.type,
19954
- from: message.from,
19955
- url: audioUrl,
19956
- fileData,
19957
- to,
19958
- body: bot.utils.generateRefProvider('_event_voice_note_'),
19959
- pushName,
19960
- name: pushName,
19961
- };
19962
- break;
19963
- }
19964
- case 'sticker': {
19965
- responseObj = {
19966
- type: message.type,
19967
- from: message.from,
19968
- to,
19969
- id: message.sticker.id,
19970
- body: bot.utils.generateRefProvider('_event_media_'),
19971
- pushName,
19972
- name: pushName,
19973
- };
19974
- break;
19975
- }
19976
- case 'contacts': {
19977
- responseObj = {
19978
- type: message.type,
19979
- from: message.from,
19980
- contacts: [
19981
- {
19982
- name: message.contacts[0].name,
19983
- phones: message.contacts[0].phones,
19984
- },
19985
- ],
19986
- to,
19987
- body: bot.utils.generateRefProvider('_event_contacts_'),
19988
- pushName,
19989
- name: pushName,
19990
- };
19991
- break;
19879
+ const { http, https } = followRedirects;
19880
+ /**
19881
+ * Extraer el mimetype from buffer
19882
+ * @param response - La respuesta HTTP
19883
+ * @returns Un objeto con el tipo y la extensión del archivo
19884
+ */
19885
+ const fileTypeFromFile = async (response) => {
19886
+ const type = response.headers['content-type'] ?? '';
19887
+ const ext = mime$1.extension(type);
19888
+ return {
19889
+ type,
19890
+ ext,
19891
+ };
19892
+ };
19893
+ /**
19894
+ * Descargar archivo binario en tmp
19895
+ * @param url - La URL del archivo a descargar
19896
+ * @returns La ruta al archivo descargado
19897
+ */
19898
+ const generalDownload = async (url, pathToSave, headers) => {
19899
+ const checkIsLocal = fs$1.existsSync(url);
19900
+ const handleDownload = () => {
19901
+ try {
19902
+ const checkProtocol = url.startsWith('http');
19903
+ const handleHttp = checkProtocol ? https : http;
19904
+ const fileName = path$1.basename(checkProtocol ? new URL(url).pathname : url);
19905
+ const name = path$1.parse(fileName).name;
19906
+ const fullPath = path$1.join(pathToSave ?? require$$0$2.tmpdir(), name);
19907
+ const file = fs$1.createWriteStream(fullPath);
19908
+ if (checkIsLocal) {
19909
+ /**
19910
+ * From Local
19911
+ */
19912
+ return new Promise((res) => {
19913
+ const response = {
19914
+ headers: {
19915
+ 'content-type': mime$1.contentType(path$1.extname(url)) || '',
19916
+ },
19917
+ };
19918
+ res({ response, fullPath: url });
19919
+ });
19920
+ }
19921
+ else {
19922
+ /**
19923
+ * From URL
19924
+ */
19925
+ return new Promise((res, rej) => {
19926
+ const options = {
19927
+ headers: headers ?? {},
19928
+ };
19929
+ handleHttp.get(url, options, function (response) {
19930
+ response.pipe(file);
19931
+ file.on('finish', async function () {
19932
+ file.close();
19933
+ res({ response, fullPath });
19934
+ });
19935
+ file.on('error', function () {
19936
+ file.close();
19937
+ rej(new Error('Error downloading file'));
19938
+ });
19939
+ });
19940
+ });
19941
+ }
19992
19942
  }
19993
- case 'order': {
19994
- responseObj = {
19995
- type: message.type,
19996
- from: message.from,
19997
- to,
19998
- order: {
19999
- catalog_id: message.order.catalog_id,
20000
- product_items: message.order.product_items,
20001
- },
20002
- body: bot.utils.generateRefProvider('_event_order_'),
20003
- pushName,
20004
- name: pushName,
20005
- };
20006
- break;
19943
+ catch (err) {
19944
+ console.error('Error downloading file', err);
19945
+ return;
20007
19946
  }
20008
- }
20009
- return {
20010
- ...responseObj,
20011
- message_id: messageId,
20012
- timestamp: messageTimestamp,
20013
19947
  };
19948
+ const handleFile = (pathInput, ext) => {
19949
+ return new Promise((resolve, reject) => {
19950
+ if (!ext) {
19951
+ reject(new Error('No extension found for the file'));
19952
+ return;
19953
+ }
19954
+ const fullPath = checkIsLocal ? `${pathInput}` : `${pathInput}.${ext}`;
19955
+ fs$1.rename(pathInput, fullPath, (err) => {
19956
+ if (err)
19957
+ reject(err);
19958
+ resolve(fullPath);
19959
+ });
19960
+ });
19961
+ };
19962
+ const httpResponse = await handleDownload();
19963
+ const { ext } = await fileTypeFromFile(httpResponse.response);
19964
+ if (!ext)
19965
+ throw new Error('Unable to determine file extension');
19966
+ const getPath = await handleFile(httpResponse.fullPath, ext);
19967
+ return getPath;
20014
19968
  };
20015
19969
 
20016
19970
  /**
20017
- * Class representing MetaCoreVendor, a vendor class for meta core functionality.
19971
+ * Genera un UUID único con un prefijo opcional.
19972
+ * @param prefix - Prefijo opcional para el UUID.
19973
+ * @returns Un identificador único (UUID v4).
19974
+ */
19975
+ const generateRefProvider = (prefix) => {
19976
+ const id = node_crypto.randomUUID();
19977
+ return prefix ? `${prefix}_${id}` : id;
19978
+ };
19979
+ /**
19980
+ * Elimina el dominio del JID de WhatsApp (e.g., "@s.whatsapp.net").
19981
+ * @param jid - JID completo.
19982
+ * @returns El número limpio.
19983
+ */
19984
+ const cleanJid = (jid) => {
19985
+ return jid?.split('@')[0] ?? '';
19986
+ };
19987
+ /**
19988
+ * Class representing EvolutionCoreVendor, a vendor class for WhatsApp Business API integration.
19989
+ * Handles webhook validation, message reception, and processing through Meta's Cloud API.
20018
19990
  * @extends EventEmitter
20019
19991
  */
20020
19992
  class EvolutionCoreVendor extends EventEmitter {
20021
19993
  /**
20022
- * Create a MetaCoreVendor.
20023
- * @param {Queue} _queue - The queue instance.
19994
+ * Creates an instance of EvolutionCoreVendor.
19995
+ * @param {Queue} _queue - The queue instance for managing message processing.
20024
19996
  */
20025
19997
  constructor(_queue) {
20026
19998
  super();
20027
19999
  /**
20028
- * Middleware function for indexing home.
20000
+ * Middleware function for health check endpoint.
20001
+ * Returns a simple response to verify the service is running.
20029
20002
  * @type {polka.Middleware}
20030
20003
  */
20031
20004
  this.indexHome = (_, res) => {
20032
- res.end('running ok');
20033
- };
20034
- /**
20035
- * Middleware function for verifying token.
20036
- * @type {polka.Middleware}
20037
- */
20038
- this.verifyToken = async (req, res) => {
20039
- const { query } = req;
20040
- const mode = query?.['hub.mode'];
20041
- const token = query?.['hub.verify_token'];
20042
- const challenge = query?.['hub.challenge'];
20043
- const globalVendorArgs = req['globalVendorArgs'] ?? null;
20044
- if (!mode || !token) {
20045
- res.statusCode = 403;
20046
- res.end('No token!');
20047
- return;
20005
+ try {
20006
+ res.end('ok');
20048
20007
  }
20049
- if (this.tokenIsValid(mode, token, globalVendorArgs?.verifyToken)) {
20050
- this.emit('ready');
20051
- res.statusCode = 200;
20052
- res.end(challenge);
20053
- return;
20008
+ catch (error) {
20009
+ console.error('Error in indexHome middleware:', error);
20010
+ res.statusCode = 500;
20011
+ res.end('Internal server error');
20054
20012
  }
20055
- res.statusCode = 403;
20056
- res.end('Invalid token!');
20057
20013
  };
20058
20014
  /**
20059
- * Middleware function for handling incoming messages.
20015
+ * Middleware function for handling incoming webhook messages.
20016
+ * Processes incoming messages from WhatsApp and adds them to the processing queue.
20060
20017
  * @type {polka.Middleware}
20061
20018
  */
20062
20019
  this.incomingMsg = async (req, res) => {
20063
- const globalVendorArgs = req['globalVendorArgs'] ?? null;
20064
- const body = req?.body;
20065
- const { jwtToken, numberId, version } = globalVendorArgs;
20066
- const someErrors = this.extractStatus(body);
20067
- const findError = someErrors.find((s) => s.status === 'failed');
20068
- if (findError) {
20069
- this.emit('notice', {
20070
- title: '🔔 META ALERT 🔔',
20071
- instructions: [findError.reason],
20072
- });
20073
- res.writeHead(400, { 'Content-Type': 'application/json' });
20074
- return res.end(JSON.stringify(someErrors));
20075
- }
20076
- const messages = body?.entry?.[0]?.changes?.[0]?.value?.messages;
20077
- const contacts = body?.entry?.[0]?.changes?.[0]?.value?.contacts;
20078
- const messageId = body?.entry?.[0]?.changes?.[0]?.value?.messages?.[0]?.id;
20079
- const messageTimestamp = body?.entry?.[0]?.changes?.[0]?.value?.messages?.[0]?.timestamp;
20080
- if (!messages?.length) {
20081
- res.statusCode = 200;
20082
- res.end('empty endpoint');
20083
- return;
20084
- }
20085
20020
  try {
20086
- await Promise.all(messages.map(async (message) => {
20087
- let contact;
20088
- if (Array.isArray(contacts))
20089
- [contact] = contacts;
20090
- const to = body.entry[0].changes[0].value?.metadata?.display_phone_number;
20091
- const pushName = contact?.profile?.name ?? 'Unknown';
20092
- const fileData = message?.audio ??
20093
- message?.image ??
20094
- message?.video ??
20095
- message?.document ??
20096
- message?.sticker ??
20097
- null;
20098
- const response = await processIncomingMessage({
20099
- messageId,
20100
- messageTimestamp,
20101
- to,
20102
- pushName,
20103
- message,
20104
- jwtToken,
20105
- numberId,
20106
- version,
20107
- fileData,
20108
- });
20109
- if (response) {
20110
- await this.queue.enqueue(() => this.processMessage(response));
20111
- }
20112
- }));
20021
+ const globalVendorArgs = req['globalVendorArgs'] ?? null;
20022
+ if (!globalVendorArgs) {
20023
+ res.statusCode = 400;
20024
+ res.end('Missing vendor arguments');
20025
+ return;
20026
+ }
20027
+ console.log(' body', JSON.stringify(req.body, null, 2));
20028
+ const { event, data } = req.body;
20029
+ if (!req.body) {
20030
+ res.statusCode = 400;
20031
+ res.end('Invalid request body');
20032
+ return;
20033
+ }
20034
+ switch (event) {
20035
+ case 'messages.upsert':
20036
+ if (data.message) {
20037
+ const { message } = data;
20038
+ const from = cleanJid(data.key?.remoteJid);
20039
+ const name = data.pushName;
20040
+ let responseObj = null;
20041
+ if (message.documentMessage) {
20042
+ responseObj = {
20043
+ type: data.messageType,
20044
+ from,
20045
+ mimetype: message.documentMessage.mimetype,
20046
+ body: generateRefProvider('_event_document_'),
20047
+ name,
20048
+ caption: message.documentMessage.caption,
20049
+ base64: message.base64,
20050
+ };
20051
+ }
20052
+ else if (message.videoMessage) {
20053
+ responseObj = {
20054
+ type: data.messageType,
20055
+ from,
20056
+ mimetype: message.videoMessage.mimetype,
20057
+ body: generateRefProvider('_event_media_'),
20058
+ name,
20059
+ caption: message.videoMessage.caption || '',
20060
+ base64: message.base64,
20061
+ };
20062
+ }
20063
+ else if (message.imageMessage) {
20064
+ responseObj = {
20065
+ type: data.messageType,
20066
+ from,
20067
+ mimetype: message.imageMessage.mimetype,
20068
+ body: generateRefProvider('_event_media_'),
20069
+ name,
20070
+ caption: message.imageMessage.caption || '',
20071
+ base64: message.base64,
20072
+ };
20073
+ }
20074
+ else if (message.audioMessage) {
20075
+ responseObj = {
20076
+ type: data.messageType,
20077
+ from,
20078
+ mimetype: message.audioMessage.mimetype,
20079
+ body: generateRefProvider('_event_voice_note_'),
20080
+ name,
20081
+ caption: message.audioMessage.caption || '',
20082
+ base64: message.base64,
20083
+ };
20084
+ }
20085
+ else if (message.locationMessage || message.liveLocationMessage) {
20086
+ responseObj = {
20087
+ type: data.messageType,
20088
+ from,
20089
+ latitude: message.locationMessage?.degreesLatitude ??
20090
+ message.liveLocationMessage?.degreesLatitude,
20091
+ longitude: message.locationMessage?.degreesLongitude ??
20092
+ message.liveLocationMessage?.degreesLongitude,
20093
+ body: generateRefProvider('_event_location_'),
20094
+ name,
20095
+ };
20096
+ }
20097
+ else if (message.conversation) {
20098
+ responseObj = {
20099
+ type: data.messageType,
20100
+ from,
20101
+ body: message.conversation,
20102
+ name,
20103
+ };
20104
+ }
20105
+ if (responseObj) {
20106
+ const enrichedMessage = { ...data, ...responseObj };
20107
+ await this.queue.enqueue(() => this.processMessage(enrichedMessage));
20108
+ }
20109
+ }
20110
+ }
20113
20111
  res.statusCode = 200;
20114
- res.end('Messages enqueued');
20112
+ res.end('Message processed successfully');
20113
+ // Check for errors reported by Meta
20115
20114
  }
20116
20115
  catch (error) {
20116
+ console.error('Error processing incoming message:', error);
20117
20117
  this.emit('notice', {
20118
- title: '🔔 META ALERT 🔔',
20119
- instructions: [error.message || 'An error occurred while processing messages.'],
20118
+ title: '🔔 EVOLUTION API ALERT 🔔',
20119
+ instructions: [error.message || 'An error occurred while processing message.'],
20120
20120
  });
20121
- res.writeHead(400, { 'Content-Type': 'application/json' });
20122
- res.end(JSON.stringify({ error: error.message || 'An error occurred while processing messages.' }));
20121
+ res.writeHead(500, { 'Content-Type': 'application/json' });
20122
+ res.end(JSON.stringify({
20123
+ error: error.message || 'An error occurred while processing message.',
20124
+ stack: process.env.NODE_ENV === 'development' ? error.stack : undefined,
20125
+ }));
20123
20126
  }
20124
20127
  };
20125
20128
  /**
20126
- * Process incoming message.
20127
- * @param {Message} message - The message object.
20128
- * @returns {Promise<void>} Promise that resolves when processing is complete.
20129
+ * Procesa un mensaje entrante y lo emite al flujo del bot.
20130
+ * @param message - Objeto de mensaje enriquecido.
20129
20131
  */
20130
20132
  this.processMessage = (message) => {
20131
20133
  return new Promise((resolve, reject) => {
@@ -20138,41 +20140,22 @@ class EvolutionCoreVendor extends EventEmitter {
20138
20140
  }
20139
20141
  });
20140
20142
  };
20143
+ if (!_queue) {
20144
+ throw new Error('Queue instance is required');
20145
+ }
20141
20146
  this.queue = _queue;
20142
20147
  }
20143
- /**
20144
- * Check if the token is valid.
20145
- * @param {string} mode - The mode parameter.
20146
- * @param {string} token - The token parameter.
20147
- * @param {string} originToken - The origin token parameter.
20148
- * @returns {boolean} Returns true if token is valid, false otherwise.
20149
- */
20150
- tokenIsValid(mode, token, originToken) {
20151
- return mode === 'subscribe' && originToken === token;
20152
- }
20153
- extractStatus(obj) {
20154
- const entry = obj.entry || [];
20155
- const statusArray = [];
20156
- entry.forEach((entryItem) => {
20157
- const changes = entryItem.changes || [];
20158
- changes.forEach((change) => {
20159
- const values = change.value || {};
20160
- const statuses = values.statuses || [];
20161
- statuses.forEach((status) => {
20162
- const recipient_id = status.recipient_id || 'N/A';
20163
- const errorDetails = status.errors?.[0]?.error_data?.details || 'Unknown';
20164
- statusArray.push({
20165
- status: status.status || 'Unknown',
20166
- reason: `Number(${recipient_id}): ${errorDetails}`,
20167
- });
20168
- });
20169
- });
20170
- });
20171
- return statusArray;
20172
- }
20173
20148
  }
20174
20149
 
20150
+ /**
20151
+ * Evolution API Provider implementation
20152
+ * Handles all communication with Evolution API for sending messages, media, etc.
20153
+ */
20175
20154
  class EvolutionProvider extends bot.ProviderClass {
20155
+ /**
20156
+ * Creates an instance of Evolution Provider
20157
+ * @param args Provider configuration
20158
+ */
20176
20159
  constructor(args) {
20177
20160
  super();
20178
20161
  this.queue = new Queue();
@@ -20181,108 +20164,164 @@ class EvolutionProvider extends bot.ProviderClass {
20181
20164
  apiKey: '',
20182
20165
  baseURL: 'http://localhost:8080',
20183
20166
  instanceName: '',
20167
+ port: 3000,
20184
20168
  };
20185
- this.busEvents = () => [
20186
- {
20187
- event: 'auth_failure',
20188
- func: (payload) => this.emit('auth_failure', payload),
20189
- },
20190
- {
20191
- event: 'notice',
20192
- func: ({ instructions, title }) => this.emit('notice', { instructions, title }),
20193
- },
20194
- {
20195
- event: 'ready',
20196
- func: () => this.emit('ready', true),
20197
- },
20198
- {
20199
- event: 'message',
20200
- func: (payload) => {
20201
- this.emit('message', payload);
20169
+ /**
20170
+ * Punto de entrada para envío de archivos multimedia.
20171
+ * Detecta el tipo de archivo y redirige a la función correspondiente.
20172
+ *
20173
+ * @param number Número de destino
20174
+ * @param mediaUrl URL o path del archivo
20175
+ * @param caption Texto opcional
20176
+ */
20177
+ this.sendMedia = async (number, mediaUrl, caption) => {
20178
+ const fileDownloaded = await generalDownload(mediaUrl);
20179
+ const mimeType = mime$1.lookup(fileDownloaded);
20180
+ if (!mimeType)
20181
+ throw new Error('No se pudo determinar el tipo MIME');
20182
+ if (mimeType.includes('image')) {
20183
+ return this.sendImage(number, fileDownloaded, caption || '');
20184
+ }
20185
+ if (mimeType.includes('video')) {
20186
+ return this.sendVideo(number, fileDownloaded, caption || '');
20187
+ }
20188
+ if (mimeType.includes('audio')) {
20189
+ return this.sendAudio(number, fileDownloaded);
20190
+ }
20191
+ return this.sendFile(number, fileDownloaded, caption || '');
20192
+ };
20193
+ /**
20194
+ * Envía una imagen al número dado.
20195
+ * @param number Número destino
20196
+ * @param filePath Ruta local de la imagen
20197
+ * @param caption Texto opcional
20198
+ */
20199
+ this.sendImage = async (number, filePath, caption) => {
20200
+ const mediaBase64 = fs$1.readFileSync(filePath, { encoding: 'base64' });
20201
+ const mimeType = mime$1.lookup(filePath);
20202
+ const body = {
20203
+ number,
20204
+ media: mediaBase64,
20205
+ mimetype: mimeType,
20206
+ mediatype: 'image',
20207
+ caption: caption || path$1.basename(filePath),
20208
+ delay: 0,
20209
+ };
20210
+ return this.sendMessageEvoApi(body, '/message/sendMedia/');
20211
+ };
20212
+ /**
20213
+ * Envía un video al número dado.
20214
+ * @param number Número destino
20215
+ * @param filePath Ruta local del video
20216
+ * @param caption Texto opcional
20217
+ */
20218
+ this.sendVideo = async (number, filePath, caption) => {
20219
+ const mediaBase64 = fs$1.readFileSync(filePath, { encoding: 'base64' });
20220
+ const mimeType = mime$1.lookup(filePath);
20221
+ const body = {
20222
+ number,
20223
+ media: mediaBase64,
20224
+ mimetype: mimeType,
20225
+ mediatype: 'video',
20226
+ caption: caption || path$1.basename(filePath),
20227
+ delay: 0,
20228
+ };
20229
+ return this.sendMessageEvoApi(body, '/message/sendMedia/');
20230
+ };
20231
+ /**
20232
+ * Envía un archivo de audio en formato compatible (OPUS).
20233
+ * @param number Número destino
20234
+ * @param filePath Ruta local del archivo de audio
20235
+ */
20236
+ this.sendAudio = async (number, filePath) => {
20237
+ const mediaBase64 = fs$1.readFileSync(filePath, { encoding: 'base64' });
20238
+ const body = {
20239
+ number,
20240
+ media: mediaBase64,
20241
+ mimetype: 'audio/ogg; codecs=opus',
20242
+ mediatype: 'audio',
20243
+ delay: 0,
20244
+ };
20245
+ return this.sendMessageEvoApi(body, '/message/sendMedia/');
20246
+ };
20247
+ /**
20248
+ * Envía un documento genérico al número dado.
20249
+ * @param number Número destino
20250
+ * @param filePath Ruta local del archivo
20251
+ * @param caption Texto opcional
20252
+ */
20253
+ this.sendFile = async (number, filePath, caption) => {
20254
+ const mediaBase64 = fs$1.readFileSync(filePath, { encoding: 'base64' });
20255
+ const mimeType = mime$1.lookup(filePath);
20256
+ const fileName = path$1.basename(filePath);
20257
+ const body = {
20258
+ number,
20259
+ media: mediaBase64,
20260
+ mimetype: mimeType,
20261
+ mediatype: 'document',
20262
+ fileName,
20263
+ caption: caption || path$1.basename(filePath),
20264
+ delay: 0,
20265
+ };
20266
+ return this.sendMessageEvoApi(body, '/message/sendMedia/');
20267
+ };
20268
+ /**
20269
+ * Envía un mensaje de texto plano.
20270
+ * @param number Número destino
20271
+ * @param message Contenido del mensaje
20272
+ */
20273
+ this.sendText = async (number, message) => {
20274
+ const ruta = '/message/sendText/';
20275
+ const body = {
20276
+ number,
20277
+ text: message,
20278
+ delay: 0,
20279
+ };
20280
+ return this.sendMessageEvoApi(body, ruta);
20281
+ };
20282
+ /**
20283
+ * Función general para hacer peticiones POST a la API externa.
20284
+ * @param body Cuerpo de la petición
20285
+ * @param ruta Ruta relativa del endpoint (optional)
20286
+ */
20287
+ this.sendMessageToApi = async (body, ruta = '/message/') => {
20288
+ const { baseURL, instanceName, apiKey } = this.globalVendorArgs;
20289
+ const response = await fetch(`${baseURL}${ruta}${instanceName}`, {
20290
+ method: 'POST',
20291
+ headers: {
20292
+ 'Content-Type': 'application/json',
20293
+ apikey: apiKey,
20202
20294
  },
20203
- },
20204
- ];
20205
- // async sendLocation(to: string, lat: string, long: string): Promise<any> {
20206
- // const { baseURL, instanceName, apiKey } = this.globalVendorArgs
20207
- // try {
20208
- // return await axios.post(
20209
- // `${baseURL}/message/sendLocation/${instanceName}`,
20210
- // {
20211
- // number: to,
20212
- // lat,
20213
- // long
20214
- // },
20215
- // {
20216
- // headers: {
20217
- // 'apikey': apiKey
20218
- // }
20219
- // }
20220
- // )
20221
- // } catch (error) {
20222
- // console.error('Error sending location:', error)
20223
- // throw error
20224
- // }
20225
- // }
20226
- // async sendContact(to: string, contact: Contact): Promise<any> {
20227
- // const { baseURL, instanceName, apiKey } = this.globalVendorArgs
20228
- // try {
20229
- // return await axios.post(
20230
- // `${baseURL}/message/contact/${instanceName}`,
20231
- // {
20232
- // number: to,
20233
- // contact
20234
- // },
20235
- // {
20236
- // headers: {
20237
- // 'apikey': apiKey
20238
- // }
20239
- // }
20240
- // )
20241
- // } catch (error) {
20242
- // console.error('Error sending contact:', error)
20243
- // throw error
20244
- // }
20245
- // }
20246
- // async sendReaction(to: string, message: string): Promise<any> {
20247
- // const { baseURL, instanceName, apiKey } = this.globalVendorArgs
20248
- // try {
20249
- // return await axios.post(
20250
- // `${baseURL}/message/reaction/${instanceName}`,
20251
- // {
20252
- // number: to,
20253
- // reaction: message
20254
- // },
20255
- // {
20256
- // headers: {
20257
- // 'apikey': apiKey
20258
- // }
20259
- // }
20260
- // )
20261
- // } catch (error) {
20262
- // console.error('Error sending reaction:', error)
20263
- // throw error
20264
- // }
20265
- // }
20266
- // Métodos auxiliares requeridos por la interfaz
20267
- this.sendMessageMeta = (body) => {
20295
+ body: JSON.stringify(body),
20296
+ });
20297
+ if (!response.ok) {
20298
+ throw new Error(`Error sending message: ${response.statusText}`);
20299
+ }
20300
+ const data = await response.json();
20301
+ return data;
20302
+ };
20303
+ /**
20304
+ * Encola el envío de un mensaje para asegurar orden y evitar conflictos.
20305
+ * @param body Cuerpo del mensaje
20306
+ * @param ruta Ruta del endpoint
20307
+ */
20308
+ this.sendMessageEvoApi = (body, ruta) => {
20268
20309
  return new Promise((resolve) => this.queue.add(async () => {
20269
- const resp = await this.sendMessageToApi(body);
20310
+ const resp = await this.sendMessageToApi(body, ruta);
20270
20311
  resolve(resp);
20271
20312
  }));
20272
20313
  };
20273
- this.sendMessageToApi = async (body) => {
20274
- const { baseURL, instanceName, apiKey } = this.globalVendorArgs;
20314
+ this.saveFile = async (ctx, options = {}) => {
20275
20315
  try {
20276
- const response = await axios.post(`${baseURL}/message/sendText/${instanceName}`, body, {
20277
- headers: {
20278
- apikey: apiKey,
20279
- },
20280
- });
20281
- return response.data;
20316
+ const buffer = ctx.base64;
20317
+ const extension = mime$1.extension(ctx.mimetype);
20318
+ const fileName = `file-${Date.now()}.${extension}`;
20319
+ const pathFile = path$1.join(options?.path ?? require$$0$2.tmpdir(), fileName);
20320
+ await promises.writeFile(pathFile, buffer);
20321
+ return path$1.resolve(pathFile);
20282
20322
  }
20283
20323
  catch (error) {
20284
- console.error('Error sending message:', error);
20285
- throw error;
20324
+ return Promise.reject(error);
20286
20325
  }
20287
20326
  };
20288
20327
  this.globalVendorArgs = { ...this.globalVendorArgs, ...args };
@@ -20292,215 +20331,106 @@ class EvolutionProvider extends bot.ProviderClass {
20292
20331
  start: true,
20293
20332
  });
20294
20333
  }
20334
+ /**
20335
+ * Initialize HTTP server middleware
20336
+ */
20295
20337
  beforeHttpServerInit() {
20296
20338
  this.server = this.server
20339
+ .use(json())
20297
20340
  .use((req, _, next) => {
20298
20341
  req['globalVendorArgs'] = this.globalVendorArgs;
20299
20342
  return next();
20300
20343
  })
20301
- .post('/', this.vendor.indexHome);
20344
+ .post('/', this.vendor.indexHome)
20345
+ .post('/webhook', this.vendor.incomingMsg);
20302
20346
  }
20303
- async initVendor() {
20347
+ /**
20348
+ * Initialize vendor core
20349
+ */
20350
+ initVendor() {
20304
20351
  const vendor = new EvolutionCoreVendor(this.queue);
20305
20352
  this.vendor = vendor;
20306
20353
  return Promise.resolve(this.vendor);
20307
20354
  }
20308
- async afterHttpServerInit() {
20355
+ /**
20356
+ * Build standard headers for API requests
20357
+ * @param additionalHeaders Optional additional headers to include
20358
+ * @returns Headers object with apiKey
20359
+ */
20360
+ builderHeader(additionalHeaders = {}) {
20361
+ const { apiKey } = this.globalVendorArgs;
20362
+ return {
20363
+ apikey: apiKey,
20364
+ ...additionalHeaders,
20365
+ };
20366
+ }
20367
+ /**
20368
+ * Verify connection with Evolution API after HTTP server initialization
20369
+ */ async afterHttpServerInit() {
20309
20370
  try {
20310
- const { baseURL, instanceName, apiKey } = this.globalVendorArgs;
20311
- // Verificar conexión con Evolution API
20371
+ const { baseURL, instanceName } = this.globalVendorArgs;
20372
+ // Verify connection with Evolution API
20312
20373
  const response = await axios.get(`${baseURL}/instance/connectionState/${instanceName}`, {
20313
- headers: {
20314
- apikey: apiKey,
20315
- },
20374
+ headers: this.builderHeader(),
20316
20375
  });
20317
- if (response.data.state === 'open') {
20376
+ const state = response.data.state ?? response.data.instance.state ?? 'close';
20377
+ if (state === 'open') {
20318
20378
  this.emit('ready');
20319
20379
  }
20320
20380
  else {
20321
- throw new Error('Instance not connected');
20381
+ throw new Error(`Instance state: ${state}`);
20322
20382
  }
20323
20383
  }
20324
20384
  catch (err) {
20385
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
20325
20386
  this.emit('notice', {
20326
20387
  title: '🟠 ERROR AUTH 🟠',
20327
20388
  instructions: [
20328
20389
  'Error connecting to Evolution API, please check your credentials',
20329
20390
  'Make sure your instance is connected',
20391
+ `Details: ${errorMessage}`,
20330
20392
  ],
20331
20393
  });
20332
20394
  }
20333
20395
  }
20334
- async sendText(to, message) {
20335
- return this.sendMessage(to, message);
20336
- }
20337
- async sendMessage(to, message) {
20338
- try {
20339
- const { baseURL, instanceName, apiKey } = this.globalVendorArgs;
20340
- return await axios.post(`${baseURL}/message/sendText/${instanceName}`, {
20341
- number: to,
20342
- text: message,
20343
- }, {
20344
- headers: {
20345
- apikey: apiKey,
20346
- },
20347
- });
20348
- }
20349
- catch (error) {
20350
- console.error('Error sending message:', error);
20351
- throw error;
20352
- }
20353
- }
20354
- async sendImage(to, mediaUrl, mediaName, caption) {
20355
- try {
20356
- const { baseURL, instanceName, apiKey } = this.globalVendorArgs;
20357
- return await axios.post(`${baseURL}/message/sendMedia/${instanceName}`, {
20358
- number: to,
20359
- mediaType: 'image',
20360
- mimeType: mime$1.lookup(mediaUrl) || 'image/png',
20361
- caption: caption,
20362
- media: mediaUrl,
20363
- fileName: mediaName || 'image.png',
20364
- }, {
20365
- headers: {
20366
- apikey: apiKey,
20367
- },
20368
- });
20369
- }
20370
- catch (error) {
20371
- console.error('Error sending image:', error);
20372
- throw error;
20373
- }
20374
- }
20375
- async sendImageUrl(to, url, mediaName, caption) {
20376
- return this.sendImage(to, url, mediaName, caption);
20377
- }
20378
- async sendVideo(to, mediaUrl, mediaName, caption) {
20379
- try {
20380
- const { baseURL, instanceName, apiKey } = this.globalVendorArgs;
20381
- return await axios.post(`${baseURL}/message/video/${instanceName}`, {
20382
- number: to,
20383
- mediaType: 'video',
20384
- mimeType: 'video/mp4',
20385
- caption: caption,
20386
- media: mediaUrl,
20387
- fileName: mediaName || 'video.mp4',
20388
- }, {
20389
- headers: {
20390
- apikey: apiKey,
20391
- },
20392
- });
20393
- }
20394
- catch (error) {
20395
- console.error('Error sending video:', error);
20396
- throw error;
20397
- }
20398
- }
20399
- async sendVideoUrl(to, url, mediaName, caption) {
20400
- return this.sendVideo(to, url, mediaName, caption);
20401
- }
20402
- async sendAudio(to, mediaUrl, mediaName, caption) {
20403
- try {
20404
- const { baseURL, instanceName, apiKey } = this.globalVendorArgs;
20405
- return await axios.post(`${baseURL}/message/audio/${instanceName}`, {
20406
- number: to,
20407
- mediaType: 'audio',
20408
- mimeType: 'audio/mp3',
20409
- caption: caption,
20410
- media: mediaUrl,
20411
- fileName: mediaName || 'audio.mp3',
20412
- }, {
20413
- headers: {
20414
- apikey: apiKey,
20415
- },
20416
- });
20417
- }
20418
- catch (error) {
20419
- console.error('Error sending audio:', error);
20420
- throw error;
20421
- }
20422
- }
20423
- async sendAudioUrl(to, url, mediaName, caption) {
20424
- return this.sendAudio(to, url, mediaName, caption);
20425
- }
20426
- async sendMedia(to, file, type) {
20427
- const { baseURL, instanceName, apiKey } = this.globalVendorArgs;
20428
- try {
20429
- return await axios.post(`${baseURL}/message/${type}/${instanceName}`, {
20430
- number: to,
20431
- [type]: file,
20432
- }, {
20433
- headers: {
20434
- apikey: apiKey,
20435
- },
20436
- });
20437
- }
20438
- catch (error) {
20439
- console.error(`Error sending ${type}:`, error);
20440
- throw error;
20441
- }
20442
- }
20443
- // async sendButtons(to: string, buttons: Button[] = [], text: string): Promise<any> {
20444
- // try {
20445
- // const { baseURL, instanceName, apiKey } = this.globalVendorArgs
20446
- // return await axios.post(
20447
- // `${baseURL}/message/buttons/${instanceName}`,
20448
- // {
20449
- // number: to,
20450
- // buttons: buttons.map(btn => ({
20451
- // buttonText: btn.body,
20452
- // buttonId: btn.id
20453
- // })),
20454
- // text
20455
- // },
20456
- // {
20457
- // headers: {
20458
- // 'apikey': apiKey
20459
- // }
20460
- // }
20461
- // )
20462
- // } catch (error) {
20463
- // console.error('Error sending buttons:', error)
20464
- // throw error
20465
- // }
20466
- // }
20467
- async sendList(to, list) {
20468
- try {
20469
- const { baseURL, instanceName, apiKey } = this.globalVendorArgs;
20470
- return await axios.post(`${baseURL}/message/list/${instanceName}`, {
20471
- number: to,
20472
- list,
20473
- }, {
20474
- headers: {
20475
- apikey: apiKey,
20396
+ /**
20397
+ * Event bus configuration
20398
+ */
20399
+ busEvents() {
20400
+ return [
20401
+ {
20402
+ event: 'auth_failure',
20403
+ func: (payload) => this.emit('auth_failure', payload),
20404
+ },
20405
+ {
20406
+ event: 'notice',
20407
+ func: ({ instructions, title }) => this.emit('notice', { instructions, title }),
20408
+ },
20409
+ {
20410
+ event: 'ready',
20411
+ func: () => this.emit('ready', true),
20412
+ },
20413
+ {
20414
+ event: 'message',
20415
+ func: (payload) => {
20416
+ this.emit('message', payload);
20476
20417
  },
20477
- });
20478
- }
20479
- catch (error) {
20480
- console.error('Error sending list:', error);
20481
- throw error;
20482
- }
20483
- }
20484
- async sendListComplete(to, list) {
20485
- return this.sendList(to, list);
20418
+ },
20419
+ ];
20486
20420
  }
20487
- async saveFile(ctx, options = {}) {
20488
- // try {
20489
- // // Download the file from the URL
20490
- // const buffer = await utils.downloadFile(ctx.url)
20491
- // // Generate filename using timestamp and mime type from file data
20492
- // const fileName = `file-${Date.now()}.${ctx.fileData?.mime_type?.split('/')[1] || 'tmp'}`
20493
- // // Create full path by joining target directory with filename
20494
- // const pathFile = join(options?.path ?? tmpdir(), fileName)
20495
- // // Write buffer to file
20496
- // await writeFile(pathFile, buffer)
20497
- // // Return resolved absolute path
20498
- // return resolve(pathFile)
20499
- // } catch (err) {
20500
- // console.error(`[Error saving file]:`, err.message)
20501
- // return 'ERROR'
20502
- // }
20503
- return '';
20421
+ /**
20422
+ * Enrutador general para envío de mensajes.
20423
+ * Si incluye media, se envía como archivo. Si no, como texto plano.
20424
+ *
20425
+ * @param number Número destino
20426
+ * @param message Mensaje de texto
20427
+ * @param options Opciones adicionales (media, etc.)
20428
+ */
20429
+ async sendMessage(number, message, options) {
20430
+ options = { ...options, ...options['options'] };
20431
+ if (options.media)
20432
+ return this.sendMedia(number, options.media, message);
20433
+ return this.sendText(number, message);
20504
20434
  }
20505
20435
  }
20506
20436