Compare commits

..

300 Commits
1.0.8 ... 1.4.0

Author SHA1 Message Date
Davidson Gomes
be7bb2e39f Merge branch 'release/1.4.0' 2023-07-24 17:05:33 -03:00
Davidson Gomes
1c30728880 version: 1.4.0 2023-07-24 17:05:18 -03:00
Davidson Gomes
8d91e7cb1d feat: Added encoding option in endpoint sendWhatsAppAudio 2023-07-24 16:21:29 -03:00
Davidson Gomes
68d980795a fix: fix in chatwoot set, sign msg can now be disabled 2023-07-24 15:13:46 -03:00
Davidson Gomes
45c11a5a8e fix: fix in chatwoot set, sign msg can now be disabled 2023-07-24 15:12:13 -03:00
Davidson Gomes
4d00351db7 fix: command to create new instances set to /new_instance:<NAME>:<NUMBER> 2023-07-24 15:05:26 -03:00
Davidson Gomes
fff420b652 fix: command to create new instances set to /new_instance:<NAME>:<NUMBER> 2023-07-24 15:05:03 -03:00
Davidson Gomes
7103a95305 feat: Added connection with pairing code in chatwoot 2023-07-24 13:43:18 -03:00
Davidson Gomes
bcada5d553 fix: Now accepts all chatwoot inbox templates 2023-07-24 13:03:40 -03:00
Davidson Gomes
c9b24ff612 fix: adjusts for chatwoot v3 2023-07-24 12:55:27 -03:00
Davidson Gomes
854c7ed04d fix: Improvement for Validation of Numbers and Groups 2023-07-24 12:02:38 -03:00
Davidson Gomes
c0054959cd fix: Improvement for Validation of Numbers and Groups
Improvement for Validation of Numbers and Groups
2023-07-24 12:00:33 -03:00
Davidson Gomes
1aa837d220 fix: Adjusts in chatwoot integration 2023-07-24 11:58:52 -03:00
Alan Mosko
95df402c4c wip 2023-07-24 11:57:15 -03:00
Davidson Gomes
2f3d6f7e63 fix: Fixed problem with fileSha256 appearing when sending a sticker in chatwoot 2023-07-24 11:48:10 -03:00
Alan Mosko
ffe1523170 Update whatsapp.service.ts 2023-07-24 11:42:06 -03:00
Davidson Gomes
a73d5f4b4d Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-07-24 11:18:12 -03:00
Davidson Gomes
f35b62ed12 fix: Adjusts in createJid 2023-07-24 11:18:08 -03:00
Davidson Gomes
20abdd2908 fix: Adjusts in createJid to sendContact
Adjusts in createJid to sendContact
2023-07-24 11:17:35 -03:00
Alan Mosko
28c2c7285c Gerar Wuid no SendContact 2023-07-24 11:15:58 -03:00
Davidson Gomes
3ca8ab12a4 fix: Adjusts in createJid
Adjusts in createJid
2023-07-24 11:13:30 -03:00
Alan Mosko
fd82aa143c wip 2023-07-24 11:10:32 -03:00
Davidson Gomes
1fcbd4f9fd feat: Added reject call and send text message when receiving a call and Added setting to ignore group messages 2023-07-24 10:18:16 -03:00
Davidson Gomes
73d9cd62a5 feat: Created settings Controller 2023-07-24 09:42:29 -03:00
Davidson Gomes
be699d24a1 feat: Added fetch profile endpoint in chat controller and link preview option in send text message 2023-07-23 22:26:38 -03:00
Davidson Gomes
798eb90bed feat: Added fetch profile endpoint in chat controller and link preview option in send text message 2023-07-23 22:24:21 -03:00
Davidson Gomes
c252f5f8d9 Merge pull request #20 from EvolutionAPI/revert-18-fetch-profile
Revert "Adição de FetchProfile"
2023-07-23 22:05:38 -03:00
Davidson Gomes
76d77ad76f Revert "Adição de FetchProfile" 2023-07-23 22:05:21 -03:00
Davidson Gomes
69f5cdd61a feat: added fetchProfile endpoint in chat controller
Added fetchProfile endpoint in chat controller
2023-07-23 21:30:11 -03:00
Davidson Gomes
9f52f20660 feat: Add LinkPreview Option
Add LinkPreview Option
2023-07-23 21:25:20 -03:00
Alan Mosko
90048afa9d Add LinkPreview Option 2023-07-23 11:24:16 -03:00
Alan Mosko
1ec3ed32ee Melhorias 2023-07-23 10:18:00 -03:00
Davidson Gomes
16ed5821e2 Added connection functionality via pairing code 2023-07-21 20:58:37 -03:00
Davidson Gomes
b681e33944 Added connection functionality via pairing code 2023-07-21 20:38:10 -03:00
Davidson Gomes
8f4d44a212 Added connection functionality via pairing code 2023-07-21 20:37:58 -03:00
Davidson Gomes
93a5d07f9a Merge tag '1.3.2' into develop
* Fix in update settings that needed to restart after updated
* Correction in the use of the api with mongodb
* Adjustments to search endpoint for contacts, chats, messages and Status messages
* Now when deleting the instance, the data referring to it in mongodb is also deleted
* It is now validated if the instance name contains uppercase and special characters
* For compatibility reasons, container mode has been removed
* Added docker-compose files example
2023-07-21 17:20:19 -03:00
Davidson Gomes
40c230c7db Merge branch 'release/1.3.2' 2023-07-21 17:20:08 -03:00
Davidson Gomes
d0fa3b92f8 version: 1.3.2 2023-07-21 17:19:59 -03:00
Davidson Gomes
2a7727cf5f Merge tag '1.3.2' into develop
* Fix in update settings that needed to restart after updated
* Correction in the use of the api with mongodb
* Adjustments to search endpoint for contacts, chats, messages and Status messages
* Now when deleting the instance, the data referring to it in mongodb is also deleted
* It is now validated if the instance name contains uppercase and special characters
* For compatibility reasons, container mode has been removed
* Added docker-compose files example
2023-07-21 17:18:50 -03:00
Davidson Gomes
98722e7acf Merge branch 'release/1.3.2' 2023-07-21 17:18:40 -03:00
Davidson Gomes
683fe4c3db fix: added docker-compose files example 2023-07-21 16:00:58 -03:00
Alan Mosko
e851696430 Adição de Profile Route 2023-07-21 15:32:28 -03:00
Davidson Gomes
b2ccf965bb fix: added docker-compose files example 2023-07-21 15:14:46 -03:00
Davidson Gomes
f847f38812 fix: added docker-compose files example 2023-07-21 15:13:49 -03:00
Davidson Gomes
19039aa281 fix: For compatibility reasons, container mode has been removed 2023-07-21 13:12:55 -03:00
Davidson Gomes
091b920a22 fix: It is now validated if the instance name contains uppercase and special characters 2023-07-21 12:40:58 -03:00
Davidson Gomes
d7f264c1c2 fix: It is now validated if the instance name contains uppercase and special characters 2023-07-21 12:26:43 -03:00
Davidson Gomes
897f8164b9 fix: Now when deleting the instance, the data referring to it in mongodb is also deleted 2023-07-21 12:13:03 -03:00
Davidson Gomes
796287a776 fix: Adjustments to search endpoint for contacts, chats, messages and Status messages 2023-07-21 11:38:36 -03:00
Davidson Gomes
763e30bd1d fix: fix in update settings that needed to restart after updated 2023-07-21 09:34:39 -03:00
Davidson Gomes
5121374d60 Merge tag '1.3.1' into develop
version: 1.3.1
2023-07-21 08:26:24 -03:00
Davidson Gomes
3e3a175bdc Merge branch 'release/1.3.1' 2023-07-21 08:26:15 -03:00
Davidson Gomes
4a1aa9130b version: 1.3.1 2023-07-21 08:26:05 -03:00
Davidson Gomes
5a3f5f60b6 test: container mode with chatwoot 2023-07-20 10:08:57 -03:00
Davidson Gomes
8c1600be55 Merge tag '1.3.1' into develop
* Adjust in create store files
2023-07-20 07:49:54 -03:00
Davidson Gomes
7d3ae2347b Merge branch 'release/1.3.1' 2023-07-20 07:49:43 -03:00
Davidson Gomes
27add47db4 fix: Adjust in create store files 2023-07-20 07:49:32 -03:00
Davidson Gomes
836bcab036 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-07-20 07:15:36 -03:00
Davidson Gomes
2d20a07dfb fix: Adjust in create store files 2023-07-20 07:14:07 -03:00
Davidson Gomes
24c5c70466 fix: remove comments in .env to docker
fix: remove comments in .env to docker
2023-07-19 12:32:47 -03:00
Davidson Gomes
22ead22499 Merge branch 'develop' into patch-1 2023-07-19 12:32:06 -03:00
Davidson Gomes
65e1620b43 Merge tag '1.3.0' into develop
* Added messages.delete event
* Added restart instance endpoint
* Created automation for creating instances in the chatwoot bot with the command '#inbox_whatsapp:<INSTANCE_NAME>'
* Change Baileys version to: 6.4.0
* Send contact in chatwoot
* Send contact array in chatwoot
* Added apiKey in webhook and serverUrl in fetchInstance if EXPOSE_IN_FETCH_INSTANCES: true
* Translation set to default (english) in chatwoot

* Fixed error to send message in large groups
* Docker files adjusted
* Fixed in the postman collection the webhookByEvent parameter by webhook_by_events
* Added validations in create instance
* Removed link preview endpoint, now it's done automatically from sending conventional text
* Added group membership validation before sending message to groups
* Adjusts in docker files
* Adjusts in returns in endpoints chatwoot and webhook
* Fixed ghost mentions in send text message
* Fixed bug that saved contacts from groups came without number in chatwoot
* Fixed problem to receive csat in chatwoot
* Fixed require fileName for document only in base64 for send media message
* Bug fix when sending mobile message change contact name to number in chatwoot
* Bug fix when connecting whatsapp does not send confirmation message
* Fixed quoted message with id or message directly
* Adjust in validation for mexican and argentine numbers
* Adjust in create store files

- Chatwoot: v2.18.0
2023-07-19 11:33:42 -03:00
Davidson Gomes
acac09375e Merge branch 'release/1.3.0' 2023-07-19 11:33:32 -03:00
Davidson Gomes
83cf859da3 version: 1.3.0 2023-07-19 11:33:23 -03:00
Alan Mosko
956c391f13 Remoção de Comentários dos Valores
Ao subir um container de testes, verifiquei que os comentários subiram como valores dos campos, o que invalidou algumas conigurações.
2023-07-19 10:15:05 -03:00
Davidson Gomes
7767a2607e test: chatwoot 2023-07-19 08:18:07 -03:00
Davidson Gomes
429d1ac183 fix: Adjust in create store files 2023-07-19 08:14:11 -03:00
Davidson Gomes
f42928f88f fix: Adjust in create store files 2023-07-19 08:13:59 -03:00
Davidson Gomes
2d816ab92d Merge pull request #5 from w3nder/develop
fix: create store folder
2023-07-19 08:11:16 -03:00
Davidson Gomes
aa75380662 fix: Adjust in validation for mexican and argentine numbers 2023-07-19 08:02:24 -03:00
Davidson Gomes
3cf0ced62e test: status/stories 2023-07-18 20:23:18 -03:00
Davidson Gomes
41fa700fb3 feat: translation set to default (english) in chatwoot 2023-07-18 16:01:05 -03:00
Davidson Gomes
a4ef9fb9b7 feat: translation set to default (english) in chatwoot 2023-07-18 15:55:24 -03:00
Davidson Gomes
1db23b5277 fix: Adjusts to receive contact with no whatsapp 2023-07-18 15:53:53 -03:00
Davidson Gomes
584f0cbac0 fix: Adjusts to receive contact with more numbers 2023-07-18 15:13:41 -03:00
Davidson Gomes
dc432a19a3 fix: Adjusts to receive contact with more numbers 2023-07-18 14:57:18 -03:00
Davidson Gomes
6727b1cfca fix: Adjusts to receive contact with more numbers 2023-07-18 14:49:38 -03:00
Davidson Gomes
8c7b0698f3 feat: Added apiKey in webhook and serverUrl in fetchInstance if EXPOSE_IN_FETCH_INSTANCES: true 2023-07-18 14:30:40 -03:00
Davidson Gomes
69e1622e82 feat: Send contact array in chatwoot 2023-07-18 13:17:03 -03:00
w3nder
af7a5d3248 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-07-18 13:08:59 -03:00
w3nder
7b1a4554ad fix: create folder store 2023-07-18 13:08:53 -03:00
Davidson Gomes
b3e213c133 fix: Added replace special caracters and espaces in instanceName 2023-07-18 12:58:53 -03:00
Davidson Gomes
a9fafec79d fix: Bug fix when connecting whatsapp does not send confirmation message 2023-07-18 12:50:18 -03:00
Davidson Gomes
7cacf7bbaf fix: Bug fix when connecting whatsapp does not send confirmation message 2023-07-18 12:47:03 -03:00
Davidson Gomes
44bf39f7b7 feat: Send contact in chatwoot 2023-07-18 12:36:26 -03:00
Davidson Gomes
0db8f7295b feat: Send contact in chatwoot 2023-07-18 12:33:55 -03:00
Davidson Gomes
c1226062b1 test: contacts update 2023-07-18 11:25:34 -03:00
Davidson Gomes
819ed168da fix: Fixed require fileName for document only in base64 for send media message 2023-07-18 11:23:40 -03:00
Davidson Gomes
19940953e2 fix: Fixed require fileName for document only in base64 for send media message 2023-07-18 11:21:55 -03:00
Davidson Gomes
f4af3eaf5d fix: adjusts in docker files 2023-07-18 11:00:40 -03:00
Davidson Gomes
718563fe6a fix: adjusts in docker files 2023-07-18 10:58:53 -03:00
Davidson Gomes
7e996ad6fa fix: change Baileys version, fixed problem to receive csat in chatwoot 2023-07-18 10:56:26 -03:00
Davidson Gomes
0b07fb6a49 fix: fixed bug that saved contacts from groups came without number in chatwoot 2023-07-17 15:40:46 -03:00
Davidson Gomes
0cb87e6ed9 feat: Created automation for creating instances in the chatwoot bot with the command #inbox_whatsapp:<INSTANCE_NAME> 2023-07-17 15:37:49 -03:00
Davidson Gomes
71a3e75ae2 fix: Fixed ghost mentions in send text message 2023-07-17 14:57:52 -03:00
Davidson Gomes
a2448ea4c1 fix: adjusts in returns in endpoints chatwoot and webhook 2023-07-17 11:33:18 -03:00
Davidson Gomes
8a14141021 fix: adjusts in returns in endpoints chatwoot and webhook 2023-07-17 11:33:08 -03:00
Davidson Gomes
69353892d9 feat: Added restart instance endpoint 2023-07-16 21:55:12 -03:00
Davidson Gomes
7a2fcc3469 fix: adjusts in dockerfile 2023-07-16 19:21:17 -03:00
Davidson Gomes
dfa6dd5011 fix: adjusts in dockerfile 2023-07-16 19:15:39 -03:00
Davidson Gomes
654400c0cf fix: adjusts in dockerfile 2023-07-16 19:14:00 -03:00
Davidson Gomes
3c15fc1b0d fix: adjusts in dockerfile 2023-07-16 19:05:17 -03:00
Davidson Gomes
4b32485e3c fix: adjusts in dockerfile 2023-07-16 18:52:21 -03:00
Davidson Gomes
b90736ed25 fix: adjusts in dockerfile 2023-07-16 18:51:03 -03:00
Davidson Gomes
f28d5c7781 fix: adjusts in dockerfile 2023-07-16 18:44:10 -03:00
Davidson Gomes
0654627478 fix: adjusts in dockerfile 2023-07-16 18:35:48 -03:00
Davidson Gomes
2e91a2ecdb fix: adjusts in env files 2023-07-16 18:26:53 -03:00
Davidson Gomes
66a8ceb27a feat: added messages.delete event 2023-07-16 18:16:51 -03:00
Davidson Gomes
e6b0addb6c feat: added messages.delete event 2023-07-16 18:16:05 -03:00
Davidson Gomes
c00f132145 fix: Added group membership validation before sending message to groups 2023-07-16 18:01:43 -03:00
Davidson Gomes
b9eb8d45b2 fix: removed link preview endpoint, now it's done automatically from sending conventional text 2023-07-16 17:57:37 -03:00
Davidson Gomes
ec7ad70458 fix: Now it's getting the API URL directly in the request, no longer need the variable in the env file 2023-07-16 17:38:01 -03:00
Davidson Gomes
64e19699fb fix: Docker files adjusted 2023-07-16 15:05:02 -03:00
Davidson Gomes
6d3e1b0cbe fix: Fixed error to send message in large groups 2023-07-16 15:01:38 -03:00
Davidson Gomes
d3a53e1d3c Merge tag '1.2.2' into develop
* Tweak in route "/" with version info
* Adjusts chatwoot version

- Chatwoot: v2.18.0
2023-07-15 09:37:39 -03:00
Davidson Gomes
23bbb3ee32 Merge branch 'release/1.2.2' 2023-07-15 09:37:25 -03:00
Davidson Gomes
56bfcd52b7 fix: Adjusts chatwoot version 2023-07-15 09:37:17 -03:00
Davidson Gomes
1c19b6de1e fix: Adjusts chatwoot version 2023-07-15 09:37:03 -03:00
Davidson Gomes
85bed0bdf1 Merge tag '1.2.1-3' into develop
* Adjusts in docker files
* Save picture url groups in chatwoot
2023-07-14 19:05:19 -03:00
Davidson Gomes
0102796769 Merge branch 'release/1.2.1-3' 2023-07-14 19:04:43 -03:00
Davidson Gomes
50b2a72f1b fix: change Baileys fork 2023-07-14 19:04:34 -03:00
Davidson Gomes
04cc239756 Merge tag '1.2.1-2' into develop
v
2023-07-14 19:04:00 -03:00
Davidson Gomes
0c20da2481 Merge branch 'release/1.2.1-2' 2023-07-14 19:03:51 -03:00
Davidson Gomes
72d2a563f3 fix: change Baileys fork 2023-07-14 19:03:41 -03:00
Davidson Gomes
6f3f8c944d Merge tag '1.2.1-1' into develop
* Adjusts in docker files
* Save picture url groups in chatwoot
2023-07-14 18:38:18 -03:00
Davidson Gomes
ed60fd2e82 Merge branch 'release/1.2.1-1' 2023-07-14 18:38:07 -03:00
Davidson Gomes
636fef4693 fix: change Baileys fork 2023-07-14 18:36:44 -03:00
Davidson Gomes
77775e2f85 Merge branch 'release/1.2.1' 2023-07-14 18:04:32 -03:00
Davidson Gomes
b260959a6e fix: Save picture url groups in chatwoot 2023-07-14 18:04:20 -03:00
Davidson Gomes
2a04f7b378 feat: Save picture url groups in chatwoot 2023-07-14 17:54:48 -03:00
Davidson Gomes
d4766f9c81 feat: Save picture url groups in chatwoot 2023-07-14 17:22:30 -03:00
Davidson Gomes
7f4c9b5b11 feat: Save picture url groups in chatwoot 2023-07-14 17:04:39 -03:00
Davidson Gomes
70f7c8ce89 feat: Save picture url groups in chatwoot 2023-07-14 17:04:15 -03:00
Davidson Gomes
f33a6aba39 fix: Adjusts in docker files 2023-07-14 15:29:04 -03:00
Davidson Gomes
236d717f10 Merge branch 'helioelias-feature/mongo-express-rebrow' into develop 2023-07-14 15:27:49 -03:00
Davidson Gomes
0fc160f57c fix: Adjusts in docker files 2023-07-14 15:27:33 -03:00
Helio Elias
666023d89a docker-compose full services 2023-07-14 18:04:36 +00:00
Helio Elias
8c4f2991c7 Change network in docker-compose(api, mongo and redis), add mongo-express and rebrow, tools for maintenance and visualize data 2023-07-14 17:59:29 +00:00
Helio Elias
4453dff36d Change network in docker-compose(api, mongo and redis), add mongo-express and rebrow, tools for maintenance and visualize data 2023-07-14 17:35:42 +00:00
Davidson Gomes
90b043561f Merge tag '1.2.0-1' into develop
v
2023-07-14 14:18:01 -03:00
Davidson Gomes
43a8b34673 Merge branch 'release/1.2.0-1' 2023-07-14 14:17:57 -03:00
Davidson Gomes
6005d33c94 feat: Added verbose logs and format chatwoot service 2023-07-14 14:17:48 -03:00
Davidson Gomes
cffb4736ce Merge tag '1.2.0' into develop
* Native integration with chatwoot
* Added returning or non-returning participants option in fetchAllGroups
* Added group integration to chatwoot
* Added automation on create instance to chatwoot
* Added verbose logs and format chatwoot service

* Adjusts in docker-compose files
* Adjusts in number validation for AR and MX numbers
* Adjusts in env files, removed save old_messages
* Fix when sending a message to a group I don't belong returns a bad request
* Fits the format on return from the fetchAllGroups endpoint
* Adjust in send document with caption from chatwoot
* Fixed message with undefind in chatwoot
* Changed message in path /
* Test duplicate message media in groups chatwoot
* Optimize send message from group with mentions
* Fixed name of the profile status in fetchInstances
* Fixed error 500 when logout in instance with status = close
2023-07-14 13:17:44 -03:00
Davidson Gomes
be54331d05 Merge branch 'release/1.2.0' 2023-07-14 13:17:18 -03:00
Davidson Gomes
e19b8255d3 feat: Added verbose logs and format chatwoot service 2023-07-14 13:16:54 -03:00
Davidson Gomes
d680900f07 feat: Added verbose logs and format chatwoot service 2023-07-14 11:56:42 -03:00
Davidson Gomes
78bd4fd7de fix: Fixed error 500 when logout in instance with status = close 2023-07-14 09:43:04 -03:00
Davidson Gomes
01d4a95d2d fix: Fixed error 500 when logout in instance with status = close 2023-07-14 09:42:56 -03:00
Davidson Gomes
bb4490307d fix: Fixed name of the profile status in fetchInstances 2023-07-14 08:42:26 -03:00
Davidson Gomes
be492a3e3f fix: optimize send message from group with mentions 2023-07-14 08:31:43 -03:00
Davidson Gomes
a7b05f1e85 fix: optimize send message from group with mentions 2023-07-14 06:40:33 -03:00
Davidson Gomes
1c5ae4eb4d fix: optimize send message from group with mentions 2023-07-13 20:12:29 -03:00
Davidson Gomes
16d03d20ac fix: optimize send message from group with mentions 2023-07-13 20:05:38 -03:00
Davidson Gomes
8f062fab7d fix: optimize send message from group with mentions 2023-07-13 20:05:26 -03:00
Davidson Gomes
2565b934a5 fix: test duplicate message media in groups chatwoot 2023-07-13 19:55:23 -03:00
Davidson Gomes
7e88a9084d fix: test duplicate message media in groups chatwoot 2023-07-13 19:23:59 -03:00
Davidson Gomes
ef03292e35 fix: fixed message with undefind in chatwoot 2023-07-13 17:31:32 -03:00
Davidson Gomes
d309ede623 fix: fixed message with undefind in chatwoot 2023-07-13 17:31:15 -03:00
Davidson Gomes
834a80c35a feat: automation chatwoot 2023-07-13 16:34:16 -03:00
Davidson Gomes
99b0288d1b feat: automation chatwoot 2023-07-13 16:26:01 -03:00
Davidson Gomes
926f58f336 feat: automation chatwoot 2023-07-13 16:19:31 -03:00
Davidson Gomes
b7bfe99520 feat: automation chatwoot 2023-07-13 15:53:40 -03:00
Davidson Gomes
fa60b68425 feat: automation chatwoot 2023-07-13 15:45:14 -03:00
Davidson Gomes
8622e1e4ff feat: automation chatwoot 2023-07-13 15:39:40 -03:00
Davidson Gomes
3e13ae9740 feat: added group integration to chatwoot 2023-07-13 12:41:29 -03:00
Davidson Gomes
e5cd577fbf feat: added group integration to chatwoot 2023-07-13 12:32:24 -03:00
Davidson Gomes
86985231ff feat: added group integration to chatwoot 2023-07-13 12:32:10 -03:00
Davidson Gomes
e64926a429 fix: Adjust in send document with caption from chatwoot 2023-07-13 11:35:42 -03:00
Davidson Gomes
6232190cfe fix: Adjust in send document with caption from chatwoot 2023-07-13 11:35:30 -03:00
Davidson Gomes
eb83d89307 feat: Show webhook_url for chatwoot 2023-07-13 11:19:48 -03:00
Davidson Gomes
b4a9941452 feat: Native integration with chatwoot 2023-07-13 08:29:08 -03:00
Davidson Gomes
be782ba512 feat: Added returning or non-returning participants option in fetchAllGroups 2023-07-13 07:19:32 -03:00
Davidson Gomes
db54f247a2 feat: chatwoot integration completed 2023-07-13 00:34:34 -03:00
Davidson Gomes
0fc3b47c88 feat: chatwoot integration completed 2023-07-13 00:27:18 -03:00
Davidson Gomes
3eda2a1f2a feat: chatwoot integration completed 2023-07-12 21:01:06 -03:00
Davidson Gomes
c45b2adad6 Merge branch 'feature/connect-chatwoot' into develop 2023-07-12 20:58:38 -03:00
Davidson Gomes
702dbebfbe feat: chatwoot integration completed 2023-07-12 20:58:18 -03:00
Davidson Gomes
052303cc93 feat: chatwoot integration completed 2023-07-12 20:28:51 -03:00
Davidson Gomes
69380146e4 fix: Adjusts in env files, removed save old_messages 2023-07-12 16:39:02 -03:00
Davidson Gomes
514fb56209 feat: chatwoot service and webhook endpoint 2023-07-12 16:37:21 -03:00
Davidson Gomes
57b61070d9 feat: Save chatwoot creds 2023-07-12 15:41:07 -03:00
Davidson Gomes
86ce00365a fix: Adjusts in env files, removed save old_messages 2023-07-12 14:39:43 -03:00
Davidson Gomes
a8bd32bef3 fix: Adjusts in env files, removed save old_messages 2023-07-12 14:31:05 -03:00
Davidson Gomes
ae8801dd8a fix: Adjusts in env files, removed save old_messages 2023-07-12 14:10:24 -03:00
Davidson Gomes
0a4e52e663 fix: Adjusts in env files, removed save old_messages 2023-07-12 11:52:00 -03:00
Davidson Gomes
95045db74e fix: Adjusts in number validation for AR and MX numbers 2023-07-12 11:15:45 -03:00
Davidson Gomes
19e7c0be0b Merge tag '1.1.5' into develop
v
2023-07-12 07:21:04 -03:00
Davidson Gomes
3fcbf458b6 Merge branch 'release/1.1.5' 2023-07-12 07:21:01 -03:00
Davidson Gomes
4580146cdb fix: adjusts in temp folder and return with event send_messages 2023-07-12 07:20:25 -03:00
Davidson Gomes
ea6a7c1c87 Merge branch 'release/1.1.4-1' 2023-07-12 07:17:05 -03:00
Davidson Gomes
06cde721b3 fix: adjusts in temp folder and return with event send_messages 2023-07-12 07:16:45 -03:00
Davidson Gomes
31486e5963 fix: adjusts in temp folder and return with event send_messages 2023-07-12 07:05:18 -03:00
Davidson Gomes
1b52bdf425 Merge tag '1.1.4' into develop
* Route to send status broadcast
* Added verbose logs
* Insert allContacts in payload of endpoint sendStatus

* Adjusted set in webhook to go empty when enabled false
* Adjust in store files
* Fixed the problem when do not save contacts when receive messages
* Changed owner of the jid for instanceName
* Create .env for installation in docker
2023-07-08 11:01:28 -03:00
Davidson Gomes
9b59895ad2 Merge branch 'release/1.1.4' 2023-07-08 11:01:21 -03:00
Davidson Gomes
c7600ff059 fix: Create .env for installation in docker 2023-07-08 11:01:10 -03:00
Davidson Gomes
b1769edb65 fix: Create .env for installation in docker 2023-07-08 11:00:33 -03:00
Davidson Gomes
14ad09e867 fix: Create .env for installation in docker 2023-07-08 10:56:57 -03:00
Davidson Gomes
26a99d3696 fix: Create .env for installation in docker 2023-07-08 10:53:48 -03:00
Davidson Gomes
c973730acc fix: Create .env for installation in docker 2023-07-08 10:50:20 -03:00
Davidson Gomes
048bea376d fix: Changed owner of the jid for instanceName 2023-07-08 07:47:06 -03:00
Davidson Gomes
eca4285ea8 fix: Fixed the problem when do not save contacts when receive messages 2023-07-08 07:15:34 -03:00
Davidson Gomes
437803da07 feat: Added verbose logs 2023-07-08 06:52:56 -03:00
Davidson Gomes
a7be7c3e19 fix: Adjust in store files 2023-07-07 15:55:33 -03:00
Davidson Gomes
5bd7dd3022 fix: Adjusted set in webhook to go empty when enabled false 2023-07-07 13:14:53 -03:00
Davidson Gomes
27aa0add4e fix: Adjusted set in webhook to go empty when enabled false 2023-07-07 13:12:51 -03:00
Davidson Gomes
24712c4c2d feat: Route to send status broadcast 2023-07-06 16:39:58 -03:00
Davidson Gomes
4bf4b4a045 feat: Route to send status broadcast 2023-07-06 16:03:48 -03:00
Davidson Gomes
9604f5c317 feat: Route to send status broadcast 2023-07-06 13:57:13 -03:00
Davidson Gomes
69c1059644 feat: Route to send status broadcast 2023-07-06 13:55:14 -03:00
Davidson Gomes
26b2903995 Merge tag '1.1.3' into develop
* Added configuration for Baileys log level in env
* Added audio to mp4 converter in optionally get Base64 From MediaMessage
* Added organization name in vcard
* Added email in vcard
* Added url in vcard
* Added verbose logs

* Added timestamp internally in urls to avoid caching
* Correction in decryption of poll votes
* Change in the way the api sent and saved the sent messages, now it goes in the messages.upsert event
* Fixed cash when sending stickers via url
* Improved how Redis works for instances
* Fixed problem when disconnecting the instance it removes the instance
* Fixed problem sending ack when preview is done by me
* Adjust in store files
2023-07-06 11:44:58 -03:00
Davidson Gomes
97abb256d5 Merge branch 'release/1.1.3' 2023-07-06 11:44:47 -03:00
Davidson Gomes
a342911dae changelog to release 1.1.3 2023-07-06 11:44:20 -03:00
Davidson Gomes
1f43563295 file postman removed 2023-07-06 11:38:18 -03:00
Davidson Gomes
f23ebf1e99 feat: Added verbose logs 2023-07-06 11:37:30 -03:00
Davidson Gomes
d66b751c2e doc: adjusts in readme 2023-07-06 09:34:48 -03:00
Davidson Gomes
6a7c76a9ba added url in vcard 2023-07-05 21:20:13 -03:00
Davidson Gomes
2338787dbb added url in vcard 2023-07-05 21:20:04 -03:00
Davidson Gomes
a216c9cc37 change changelog file 2023-07-05 21:15:01 -03:00
Davidson Gomes
b7da8d2193 added email in vcard 2023-07-05 21:14:44 -03:00
Davidson Gomes
41f191902b added organization name in vcard 2023-07-05 20:56:42 -03:00
Davidson Gomes
37397c7a69 change changelog file 2023-07-05 20:16:39 -03:00
Davidson Gomes
153695288e change changelog file 2023-07-05 20:06:45 -03:00
Davidson Gomes
a5e29758a4 added organization name in vcard 2023-07-05 20:05:24 -03:00
Davidson Gomes
964427e533 change changelog file 2023-07-05 19:17:06 -03:00
Davidson Gomes
0a925df2a9 adjust in store files 2023-07-05 19:16:47 -03:00
Davidson Gomes
db95de6731 log verbose in file redis 2023-07-05 15:58:46 -03:00
Davidson Gomes
bc2191eae6 log verbose in file redis 2023-07-05 15:49:46 -03:00
Davidson Gomes
9fed844e59 log verbose in file redis 2023-07-05 15:48:02 -03:00
Davidson Gomes
a2c125ee90 log verbose in file redis 2023-07-05 15:45:41 -03:00
Davidson Gomes
a2779612be log verbose in file redis 2023-07-05 12:54:19 -03:00
Davidson Gomes
f51c3b6519 feat: Added audio to mp4 converter in optionally get Base64 From MediaMessage 2023-07-04 17:16:31 -03:00
Davidson Gomes
870968a7c5 fix: Fixed problem when disconnecting the instance it removes the instance 2023-07-04 14:27:38 -03:00
Davidson Gomes
c4f39ab85c fix: Improved how Redis works for instances 2023-07-04 12:38:29 -03:00
Davidson Gomes
8cb431ad40 Fixed cash when sending stickers via url 2023-07-02 21:31:10 -03:00
Davidson Gomes
77f98c2438 Added destructor in redis connection 2023-07-02 20:17:13 -03:00
Davidson Gomes
d7d0e5ec82 Correction in decryption of poll votes 2023-07-02 19:58:05 -03:00
Davidson Gomes
2519efb3ea Added timestamp internally in urls to avoid caching 2023-07-01 10:17:29 -03:00
Davidson Gomes
b1c527cb71 Merge tag '1.1.2' into develop
* Fixed baileys version in package.json
* Fixed problem that did not validate if the token passed in create instance already existed
* Fixed problem that does not delete instance files in server mode
2023-06-28 13:45:14 -03:00
Davidson Gomes
b87f687e55 Merge branch 'release/1.1.2' 2023-06-28 13:45:04 -03:00
Davidson Gomes
a62d27ffc2 fix: fix problems in create instance and delete instance files 2023-06-28 13:44:58 -03:00
Davidson Gomes
a9a1c6c49b Merge tag '1.1.2' into develop
* Fixed problem that did not validate if the token passed in create instance already existed
* Fixed problem that does not delete instance files in server mode
2023-06-28 13:44:07 -03:00
Davidson Gomes
4989d6ddfa Merge branch 'release/1.1.2' 2023-06-28 13:43:48 -03:00
Davidson Gomes
fd01b9de36 fix: fix problems in create instance and delete instance files 2023-06-28 13:43:36 -03:00
Davidson Gomes
1017010e15 fix: fix problems in create instance and delete instance files 2023-06-28 13:42:35 -03:00
Davidson Gomes
e0bd06489f Merge tag '1.1.1' into develop
* Added group invitation sending
* Added webhook configuration per event in the individual instance registration
2023-06-28 10:29:48 -03:00
Davidson Gomes
4a6301c2af Merge branch 'release/1.1.1' 2023-06-28 10:29:37 -03:00
Davidson Gomes
bca830dc54 feat: Added group invitation sending and Added webhook configuration per event in the individual instance registration 2023-06-28 10:29:24 -03:00
Davidson Gomes
77bde5325e Merge tag '1.1.0-2' into develop
Adjusts reame.md
2023-06-23 08:39:41 -03:00
Davidson Gomes
bdca506dd4 Merge branch 'release/1.1.0-2' 2023-06-23 08:39:28 -03:00
Davidson Gomes
34faaa28ca Adjusts readme.md 2023-06-23 08:39:12 -03:00
Davidson Gomes
a08d433d0a Merge tag '1.1.0-1' into develop
v
2023-06-21 16:53:16 -03:00
Davidson Gomes
6d08162012 Merge branch 'release/1.1.0-1' 2023-06-21 16:53:13 -03:00
Davidson Gomes
2481650028 Remove recording of old messages on sync 2023-06-21 16:52:50 -03:00
Davidson Gomes
5e551fee41 Merge tag '1.1.0' into develop
* Improved fetch instances endpoint, now it also fetch other instances even if they are not connected
* Added conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg
* Route to fetch all groups that the connection is part of
* Route to fetch all privacy settings
* Route to update the privacy settings
* Route to update group subject
* Route to update group description
* Route to accept invite code
* Added configuration of events by webhook of instances
* Now the api key can be exposed in fetch instances if the EXPOSE_IN_FETCH_INSTANCES variable is set to true
* Added option to generate qrcode as soon as the instance is created
* The created instance token can now also be optionally defined manually in the creation endpoint
* Route to send Sticker

* Adjust dockerfile variables
* tweaks in docker-compose to pass variables
* Adjust the route getProfileBusiness to fetchProfileBusiness
* fix error after logout and try to get status or to connect again
* fix sending narrated audio on whatsapp android and ios
* fixed the problem of not disabling the global webhook by the variable
* Adjustment in the recording of temporary files and periodic cleaning
* Fix for container mode also work only with files
2023-06-21 11:18:43 -03:00
Davidson Gomes
e05690af4c Merge branch 'release/1.1.0' 2023-06-21 11:18:25 -03:00
Davidson Gomes
8e65dd0546 changelog release 1.1.0 2023-06-21 11:18:11 -03:00
Davidson Gomes
509442a2f3 changelog release 1.1.0 2023-06-21 11:17:40 -03:00
Davidson Gomes
a5102d1b94 new postman file 2023-06-21 11:16:44 -03:00
Davidson Gomes
19ee2b3f34 fix: fix for container mode also work only with files 2023-06-20 19:31:39 -03:00
Davidson Gomes
5d29c2d881 fix: fix for container mode also work only with files 2023-06-20 19:29:47 -03:00
Davidson Gomes
a08bbab9dc feat: route to send Sticker 2023-06-20 16:47:26 -03:00
Davidson Gomes
84f6394f1f fix: Adjustment in the recording of temporary files and periodic cleaning 2023-06-20 14:57:30 -03:00
Davidson Gomes
d359949310 fix: Adjustment in the recording of temporary files and periodic cleaning 2023-06-20 14:57:19 -03:00
Davidson Gomes
30cd8a03eb feat: the created instance token can now also be optionally defined manually in the creation endpoint 2023-06-20 09:07:26 -03:00
Davidson Gomes
1b7015c0df conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg 2023-06-19 22:37:42 -03:00
Davidson Gomes
55b14641e0 conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg 2023-06-19 22:30:59 -03:00
Davidson Gomes
4936d7fcc6 conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg 2023-06-19 22:29:07 -03:00
Davidson Gomes
b8fa43296d conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg 2023-06-19 22:28:39 -03:00
Davidson Gomes
631dd01c92 feat: added option to generate qrcode as soon as the instance is created 2023-06-13 17:42:30 -03:00
Davidson Gomes
fdc72bc84e feat: added option to generate qrcode as soon as the instance is created 2023-06-13 17:35:13 -03:00
Davidson Gomes
c63da9cd6e feat: added option to generate qrcode as soon as the instance is created 2023-06-13 17:23:52 -03:00
Davidson Gomes
849d570bcb fixed docker files and quoted message option 2023-06-13 14:08:26 -03:00
Davidson Gomes
85e6efb8b0 fixed docker files and quoted message option 2023-06-13 13:14:53 -03:00
Davidson Gomes
485c8c3113 fixed docker files and quoted message option 2023-06-13 13:11:24 -03:00
Davidson Gomes
f2d0a8eb8c fixed the problem of not disabling the global webhook by the variable and apikey only appears if it is enabled to be exposed 2023-06-13 11:52:30 -03:00
Davidson Gomes
9201bc1022 fix sending narrated audio on whatsapp android and ios 2023-06-13 11:41:17 -03:00
Davidson Gomes
2847a95c57 feat: expose api key in fetch instances 2023-06-12 16:07:38 -03:00
Davidson Gomes
fc30bb9852 feat: Added configuration of events by webhook of instances 2023-06-12 14:40:26 -03:00
Davidson Gomes
0f360d34e8 fix: readme 2023-06-12 13:45:51 -03:00
Davidson Gomes
ec435e086f fix: readme 2023-06-12 13:41:33 -03:00
Davidson Gomes
1efe7f7cde fix: readme 2023-06-12 13:35:47 -03:00
Davidson Gomes
5759341c52 feat: accept invite code 2023-06-12 12:08:53 -03:00
Davidson Gomes
ea9ba27f22 feat: Route to update group description 2023-06-12 12:05:52 -03:00
Davidson Gomes
a28bbce1f9 feat: Route to update group subject 2023-06-12 12:02:10 -03:00
Davidson Gomes
75b48aa8ac fix error after logout and try to get status or to connect again 2023-06-12 10:32:47 -03:00
Davidson Gomes
573e56cd8c feat: Route to update the privacy settings 2023-06-11 19:24:19 -03:00
Davidson Gomes
ab28b4c0c5 feat: Route to update the privacy settings 2023-06-11 11:38:26 -03:00
Davidson Gomes
55e36f24a5 feat: Route to fetch all privacy settings 2023-06-11 11:04:52 -03:00
Davidson Gomes
ea8d6bf6c8 feat: Route to fetch all groups that the connection is part of 2023-06-11 11:00:40 -03:00
Davidson Gomes
75b8bcb853 feat: Added conversion of audios for sending recorded audio 2023-06-11 10:36:59 -03:00
Davidson Gomes
2b2cc2effc feat: Added conversion of audios for sending recorded audio 2023-06-11 10:29:49 -03:00
Davidson Gomes
ae1c5908ae feat: Improved fetch instances endpoint and prepare changelog to version 1.1.0 in homolog 2023-06-10 18:56:11 -03:00
Davidson Gomes
f067cb99c8 fix: adjust in docker-compose file and added all variables in dockerfile 2023-06-10 18:11:28 -03:00
Davidson Gomes
7e7d3a1659 fix: adjust in docker-compose file and added all variables in dockerfile 2023-06-10 17:46:45 -03:00
Davidson Gomes
c5f364f3e4 fix: adjust in docker-compose file and added all variables in dockerfile 2023-06-10 15:48:28 -03:00
Davidson Gomes
2a6366ef85 Merge tag '1.0.9' into develop
* Adjust dockerfile variables
2023-06-10 12:06:13 -03:00
Davidson Gomes
66fa855485 Merge branch 'release/1.0.9' 2023-06-10 12:06:03 -03:00
Davidson Gomes
829b078b25 fix: adjust dockerfile variables 2023-06-10 12:05:54 -03:00
Davidson Gomes
5cf1eacd1f fix: adjust dockerfile variables 2023-06-10 12:04:33 -03:00
Davidson Gomes
7041cd7483 Merge tag '1.0.8' into develop
* Added Docker compose file
* Added ChangeLog file
2023-06-09 10:21:22 -03:00
85 changed files with 6581 additions and 3036 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

10
.gitignore vendored
View File

@@ -2,6 +2,8 @@
/dist
/node_modules
/Docker/.env
# Logs
logs/**.json
*.log
@@ -11,6 +13,11 @@ yarn-debug.log*
yarn-error.log*
lerna-debug.log*
/docker-compose-data
/docker-data
docker-compose.yaml
# Package
/yarn.lock
/package-lock.json
@@ -30,3 +37,6 @@ lerna-debug.log*
!/instances/.gitkeep
/test/
/src/env.yml
/store
/temp/*

View File

@@ -1,3 +1,235 @@
# 1.4.0 (2023-07-24 17:03)
### Features
* Added connection functionality via pairing code
* Added fetch profile endpoint in chat controller
* Created settings controller
* Added reject call and send text message when receiving a call
* Added setting to ignore group messages
* Added connection with pairing code in chatwoot with command /init:{NUMBER}
* Added encoding option in endpoint sendWhatsAppAudio
### Fixed
* Added link preview option in send text message
* Fixed problem with fileSha256 appearing when sending a sticker in chatwoot
* Fixed issue where it was not possible to open a conversation when sent at first by me on my cell phone in chatwoot
* Now it only updates the contact name if it is the same as the phone number in chatwoot
* Now accepts all chatwoot inbox templates
* Command to create new instances set to /new_instance:{NAME}:{NUMBER}
* Fix in chatwoot set, sign msg can now be disabled
### Integrations
- Chatwoot: v2.18.0 - v3.0.0 (Beta)
# 1.3.2 (2023-07-21 17:19)
### Fixed
* Fix in update settings that needed to restart after updated
* Correction in the use of the api with mongodb
* Adjustments to search endpoint for contacts, chats, messages and Status messages
* Now when deleting the instance, the data referring to it in mongodb is also deleted
* It is now validated if the instance name contains uppercase and special characters
* For compatibility reasons, container mode has been removed
* Added docker-compose files example
### Integrations
- Chatwoot: v2.18.0
# 1.3.1 (2023-07-20 07:48)
### Fixed
* Adjust in create store files
### Integrations
- Chatwoot: v2.18.0
# 1.3.0 (2023-07-19 11:33)
### Features
* Added messages.delete event
* Added restart instance endpoint
* Created automation for creating instances in the chatwoot bot with the command '#inbox_whatsapp:{INSTANCE_NAME}
* Change Baileys version to: 6.4.0
* Send contact in chatwoot
* Send contact array in chatwoot
* Added apiKey in webhook and serverUrl in fetchInstance if EXPOSE_IN_FETCH_INSTANCES: true
* Translation set to default (english) in chatwoot
### Fixed
* Fixed error to send message in large groups
* Docker files adjusted
* Fixed in the postman collection the webhookByEvent parameter by webhook_by_events
* Added validations in create instance
* Removed link preview endpoint, now it's done automatically from sending conventional text
* Added group membership validation before sending message to groups
* Adjusts in docker files
* Adjusts in returns in endpoints chatwoot and webhook
* Fixed ghost mentions in send text message
* Fixed bug that saved contacts from groups came without number in chatwoot
* Fixed problem to receive csat in chatwoot
* Fixed require fileName for document only in base64 for send media message
* Bug fix when sending mobile message change contact name to number in chatwoot
* Bug fix when connecting whatsapp does not send confirmation message
* Fixed quoted message with id or message directly
* Adjust in validation for mexican and argentine numbers
* Adjust in create store files
### Integrations
- Chatwoot: v2.18.0
# 1.2.2 (2023-07-15 09:36)
### Fixed
* Tweak in route "/" with version info
* Adjusts chatwoot version
### Integrations
- Chatwoot: v2.18.0
# 1.2.1 (2023-07-14 19:04)
### Fixed
* Adjusts in docker files
* Save picture url groups in chatwoot
# 1.2.0 (2023-07-14 15:28)
### Features
* Native integration with chatwoot
* Added returning or non-returning participants option in fetchAllGroups
* Added group integration to chatwoot
* Added automation on create instance to chatwoot
* Added verbose logs and format chatwoot service
### Fixed
* Adjusts in docker-compose files
* Adjusts in number validation for AR and MX numbers
* Adjusts in env files, removed save old_messages
* Fix when sending a message to a group I don't belong returns a bad request
* Fits the format on return from the fetchAllGroups endpoint
* Adjust in send document with caption from chatwoot
* Fixed message with undefind in chatwoot
* Changed message in path /
* Test duplicate message media in groups chatwoot
* Optimize send message from group with mentions
* Fixed name of the profile status in fetchInstances
* Fixed error 500 when logout in instance with status = close
# 1.1.5 (2023-07-12 07:17)
### Fixed
* Adjusts in temp folder
* Return with event send_message
# 1.1.4 (2023-07-08 11:01)
### Features
* Route to send status broadcast
* Added verbose logs
* Insert allContacts in payload of endpoint sendStatus
### Fixed
* Adjusted set in webhook to go empty when enabled false
* Adjust in store files
* Fixed the problem when do not save contacts when receive messages
* Changed owner of the jid for instanceName
* Create .env for installation in docker
# 1.1.3 (2023-07-06 11:43)
### Features
* Added configuration for Baileys log level in env
* Added audio to mp4 converter in optionally get Base64 From MediaMessage
* Added organization name in vcard
* Added email in vcard
* Added url in vcard
* Added verbose logs
### Fixed
* Added timestamp internally in urls to avoid caching
* Correction in decryption of poll votes
* Change in the way the api sent and saved the sent messages, now it goes in the messages.upsert event
* Fixed cash when sending stickers via url
* Improved how Redis works for instances
* Fixed problem when disconnecting the instance it removes the instance
* Fixed problem sending ack when preview is done by me
* Adjust in store files
# 1.1.2 (2023-06-28 13:43)
### Fixed
* Fixed baileys version in package.json
* Fixed problem that did not validate if the token passed in create instance already existed
* Fixed problem that does not delete instance files in server mode
# 1.1.1 (2023-06-28 10:27)
### Features
* Added group invitation sending
* Added webhook configuration per event in the individual instance registration
### Fixed
* Adjust dockerfile variables
# 1.1.0 (2023-06-21 11:17)
### Features
* Improved fetch instances endpoint, now it also fetch other instances even if they are not connected
* Added conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg
* Route to fetch all groups that the connection is part of
* Route to fetch all privacy settings
* Route to update the privacy settings
* Route to update group subject
* Route to update group description
* Route to accept invite code
* Added configuration of events by webhook of instances
* Now the api key can be exposed in fetch instances if the EXPOSE_IN_FETCH_INSTANCES variable is set to true
* Added option to generate qrcode as soon as the instance is created
* The created instance token can now also be optionally defined manually in the creation endpoint
* Route to send Sticker
### Fixed
* Adjust dockerfile variables
* tweaks in docker-compose to pass variables
* Adjust the route getProfileBusiness to fetchProfileBusiness
* fix error after logout and try to get status or to connect again
* fix sending narrated audio on whatsapp android and ios
* fixed the problem of not disabling the global webhook by the variable
* Adjustment in the recording of temporary files and periodic cleaning
* Fix for container mode also work only with files
* Remove recording of old messages on sync
# 1.0.9 (2023-06-10)
### Fixed
* Adjust dockerfile variables
# 1.0.8 (2023-06-09)
### Features

View File

@@ -1,80 +0,0 @@
CORS_ORIGIN='*' # Or separate by commas - ex.: 'yourdomain1.com, yourdomain2.com'
CORS_METHODS='POST,GET,PUT,DELETE'
CORS_CREDENTIALS=true
# Determine the logs to be displayed
LOG_LEVEL='ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK'
LOG_COLOR=true
# Determine how long the instance should be deleted from memory in case of no connection.
# Default time: 5 minutes
# If you don't even want an expiration, enter the value false
DEL_INSTANCE=5
# Temporary data storage
STORE_CLEANING_INTERVAL=7200 # seconds ===2h
STORE_MESSAGE=true
STORE_CONTACTS=false
STORE_CHATS=false
# Permanent data storage
DATABASE_ENABLED=false
DATABASE_CONNECTION_URI='<uri>'
DATABASE_CONNECTION_DB_PREFIX_NAME='evolution'
DATABASE_SAVE_DATA_INSTANCE=false
DATABASE_SAVE_DATA_OLD_MESSAGE=false
DATABASE_SAVE_DATA_NEW_MESSAGE=true
DATABASE_SAVE_MESSAGE_UPDATE=true
DATABASE_SAVE_DATA_CONTACTS=true
DATABASE_SAVE_DATA_CHATS=true
REDIS_ENABLED=true
REDIS_URI='<uri>/1'
REDIS_PREFIX_KEY='evolution'
# Webhook Settings
## Define a global webhook that will listen for enabled events from all instances
WEBHOOK_GLOBAL_URL='<url>'
WEBHOOK_GLOBAL_ENABLED=false
WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
## Set the events you want to hear
WEBHOOK_EVENTS_STATUS_INSTANCE=true
WEBHOOK_EVENTS_APPLICATION_STARTUP=true
WEBHOOK_EVENTS_QRCODE_UPDATED=true
WEBHOOK_EVENTS_MESSAGES_SET=true
WEBHOOK_EVENTS_MESSAGES_UPDATE=true
WEBHOOK_EVENTS_MESSAGES_UPSERT=true
WEBHOOK_EVENTS_SEND_MESSAGE=true
WEBHOOK_EVENTS_CONTACTS_SET=true
WEBHOOK_EVENTS_CONTACTS_UPSERT=true
WEBHOOK_EVENTS_CONTACTS_UPDATE=true
WEBHOOK_EVENTS_PRESENCE_UPDATE=true
WEBHOOK_EVENTS_CHATS_SET=true
WEBHOOK_EVENTS_CHATS_UPSERT=true
WEBHOOK_EVENTS_CHATS_UPDATE=true
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
WEBHOOK_EVENTS_GROUPS_UPSERT=false
WEBHOOK_EVENTS_GROUPS_UPDATE=false
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=false
## This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=true
CONFIG_SESSION_PHONE_CLIENT='Evolution API'
CONFIG_SESSION_PHONE_NAME='Chrome'
# Set qrcode display limit
QRCODE_LIMIT=6
# Defines an authentication type for the api
AUTHENTICATION_TYPE='jwt' # or 'apikey'
## Define a global apikey to access all instances.
### OBS: This key must be inserted in the request header to create an instance.
AUTHENTICATION_API_KEY='t8OOEeISKzpmc3jjcMqBWYSaJsafdefer'
## Set the secret key to encrypt and decrypt your token and its expiration time
AUTHENTICATION_JWT_EXPIRIN_IN=3600 # seconds - 3600s ===1h | zero (0) - never expires
AUTHENTICATION_JWT_SECRET='L0YWtjb2w554WFqPG'
AUTHENTICATION_INSTANCE_NAME='evolution'
AUTHENTICATION_INSTANCE_WEBHOOK_URL='<url>'
AUTHENTICATION_INSTANCE_MODE='container' # or 'server'
AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS=false

99
Docker/.env.example Normal file
View File

@@ -0,0 +1,99 @@
# Server URL - Set your application url
SERVER_URL=http://localhost:8080
# Cors - * for all or set separate by commas - ex.: 'yourdomain1.com, yourdomain2.com'
CORS_ORIGIN=*
CORS_METHODS=POST,GET,PUT,DELETE
CORS_CREDENTIALS=true
# Determine the logs to be displayed
LOG_LEVEL=ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS
LOG_COLOR=true
# Log Baileys - "fatal" | "error" | "warn" | "info" | "debug" | "trace"
LOG_BAILEYS=error
# Determine how long the instance should be deleted from memory in case of no connection.
# Default time: 5 minutes
# If you don't even want an expiration, enter the value false
DEL_INSTANCE=false
# Temporary data storage
STORE_MESSAGES=true
STORE_MESSAGE_UP=true
STORE_CONTACTS=true
STORE_CHATS=true
# Set Store Interval in Seconds (7200 = 2h)
CLEAN_STORE_CLEANING_INTERVAL=7200
CLEAN_STORE_MESSAGES=true
CLEAN_STORE_MESSAGE_UP=true
CLEAN_STORE_CONTACTS=true
CLEAN_STORE_CHATS=true
# Permanent data storage
DATABASE_ENABLED=true
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
DATABASE_CONNECTION_DB_PREFIX_NAME=evdocker
# Choose the data you want to save in the application's database or store
DATABASE_SAVE_DATA_INSTANCE=false
DATABASE_SAVE_DATA_NEW_MESSAGE=false
DATABASE_SAVE_MESSAGE_UPDATE=false
DATABASE_SAVE_DATA_CONTACTS=false
DATABASE_SAVE_DATA_CHATS=false
REDIS_ENABLED=true
REDIS_URI=redis://redis:6379
REDIS_PREFIX_KEY=evdocker
# Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created
## Define a global webhook that will listen for enabled events from all instances
WEBHOOK_GLOBAL_URL=''
WEBHOOK_GLOBAL_ENABLED=false
# With this option activated, you work with a url per webhook event, respecting the global url and the name of each event
WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
## Set the events you want to hear
WEBHOOK_EVENTS_APPLICATION_STARTUP=false
WEBHOOK_EVENTS_QRCODE_UPDATED=true
WEBHOOK_EVENTS_MESSAGES_SET=true
WEBHOOK_EVENTS_MESSAGES_UPSERT=true
WEBHOOK_EVENTS_MESSAGES_UPDATE=true
WEBHOOK_EVENTS_MESSAGES_DELETE=true
WEBHOOK_EVENTS_SEND_MESSAGE=true
WEBHOOK_EVENTS_CONTACTS_SET=true
WEBHOOK_EVENTS_CONTACTS_UPSERT=true
WEBHOOK_EVENTS_CONTACTS_UPDATE=true
WEBHOOK_EVENTS_PRESENCE_UPDATE=true
WEBHOOK_EVENTS_CHATS_SET=true
WEBHOOK_EVENTS_CHATS_UPSERT=true
WEBHOOK_EVENTS_CHATS_UPDATE=true
WEBHOOK_EVENTS_CHATS_DELETE=true
WEBHOOK_EVENTS_GROUPS_UPSERT=true
WEBHOOK_EVENTS_GROUPS_UPDATE=true
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
# This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
# Name that will be displayed on smartphone connection
CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI
# Browser Name = chrome | firefox | edge | opera | safari
CONFIG_SESSION_PHONE_NAME=chrome
# Set qrcode display limit
QRCODE_LIMIT=30
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
# jwt or 'apikey'
AUTHENTICATION_TYPE=apikey
## Define a global apikey to access all instances.
### OBS: This key must be inserted in the request header to create an instance.
AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976
AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
## Set the secret key to encrypt and decrypt your token and its expiration time
# seconds - 3600s ===1h | zero (0) - never expires
AUTHENTICATION_JWT_EXPIRIN_IN=0
AUTHENTICATION_JWT_SECRET='L=0YWt]b2w[WF>#>:&E`'

View File

@@ -0,0 +1,41 @@
version: '3.3'
services:
mongodb:
container_name: mongodb
image: mongo
restart: always
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root
- PUID=1000
- PGID=1000
volumes:
- evolution_mongodb_data:/data/db
- evolution_mongodb_configdb:/data/configdb
expose:
- 27017
mongo-express:
image: mongo-express
environment:
ME_CONFIG_BASICAUTH_USERNAME: root
ME_CONFIG_BASICAUTH_PASSWORD: root
ME_CONFIG_MONGODB_SERVER: mongodb
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: root
ports:
- 8081:8081
links:
- mongodb
volumes:
evolution_mongodb_data:
evolution_mongodb_configdb:
networks:
evolution-net:
external: true

View File

@@ -0,0 +1,21 @@
version: '3.3'
services:
redis:
image: redis:latest
container_name: redis
command: >
redis-server
--port 6379
--appendonly yes
volumes:
- evolution_redis:/data
ports:
- 6379:6379
volumes:
evolution_redis:
networks:
evolution-net:
external: true

View File

@@ -1,5 +1,9 @@
FROM node:16.18-alpine
LABEL version="1.1.3" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@agenciadgcode.com"
RUN apk update && apk upgrade && \
apk add --no-cache git
@@ -9,43 +13,54 @@ COPY ./package.json .
ENV DOCKER_ENV=true
ENV CORS_ORIGIN="*"
ENV CORS_METHODS="POST,GET,PUT,DELETE"
ENV SERVER_URL=http://localhost:8080
ENV CORS_ORIGIN=*
ENV CORS_METHODS=POST,GET,PUT,DELETE
ENV CORS_CREDENTIALS=true
ENV LOG_LEVEL="ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK"
ENV LOG_LEVEL=ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS
ENV LOG_COLOR=true
ENV LOG_BAILEYS=error
ENV DEL_INSTANCE=false
ENV STORE_CLEANING_INTERVAL=7200
ENV STORE_MESSAGE=true
ENV STORE_MESSAGES=true
ENV STORE_MESSAGE_UP=true
ENV STORE_CONTACTS=true
ENV STORE_CHATS=true
ENV DATABASE_ENABLED=$DATABASE_ENABLED
ENV DATABASE_CONNECTION_URI=$DATABASE_CONNECTION_URI
ENV DATABASE_CONNECTION_DB_PREFIX_NAME=$DATABASE_CONNECTION_DB_PREFIX_NAME
ENV CLEAN_STORE_CLEANING_INTERVAL=7200
ENV CLEAN_STORE_MESSAGES=true
ENV CLEAN_STORE_MESSAGE_UP=true
ENV CLEAN_STORE_CONTACTS=true
ENV CLEAN_STORE_CHATS=true
ENV DATABASE_ENABLED=false
ENV DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
ENV DATABASE_CONNECTION_DB_PREFIX_NAME=evolution
ENV DATABASE_SAVE_DATA_INSTANCE=false
ENV DATABASE_SAVE_DATA_OLD_MESSAGE=false
ENV DATABASE_SAVE_DATA_NEW_MESSAGE=true
ENV DATABASE_SAVE_DATA_NEW_MESSAGE=false
ENV DATABASE_SAVE_MESSAGE_UPDATE=false
ENV DATABASE_SAVE_DATA_CONTACTS=true
ENV DATABASE_SAVE_DATA_CHATS=true
ENV DATABASE_SAVE_DATA_CONTACTS=false
ENV DATABASE_SAVE_DATA_CHATS=false
ENV REDIS_ENABLED=$REDIS_ENABLED
ENV REDIS_URI=$REDIS_URI
ENV REDIS_ENABLED=false
ENV REDIS_URI=redis://redis:6379
ENV REDIS_PREFIX_KEY=evolution
ENV WEBHOOK_GLOBAL_URL=$WEBHOOK_GLOBAL_URL
ENV WEBHOOK_GLOBAL_ENABLED=true
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=$WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS
ENV WEBHOOK_GLOBAL_URL=<url>
ENV WEBHOOK_GLOBAL_ENABLED=false
ENV WEBHOOK_EVENTS_STATUS_INSTANCE=true
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=true
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=false
ENV WEBHOOK_EVENTS_QRCODE_UPDATED=true
ENV WEBHOOK_EVENTS_MESSAGES_SET=true
ENV WEBHOOK_EVENTS_MESSAGES_UPDATE=true
ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=true
ENV WEBHOOK_EVENTS_MESSAGES_UPDATE=true
ENV WEBHOOK_EVENTS_MESSAGES_DELETE=true
ENV WEBHOOK_EVENTS_SEND_MESSAGE=true
ENV WEBHOOK_EVENTS_CONTACTS_SET=true
ENV WEBHOOK_EVENTS_CONTACTS_UPSERT=true
@@ -54,29 +69,34 @@ ENV WEBHOOK_EVENTS_PRESENCE_UPDATE=true
ENV WEBHOOK_EVENTS_CHATS_SET=true
ENV WEBHOOK_EVENTS_CHATS_UPSERT=true
ENV WEBHOOK_EVENTS_CHATS_UPDATE=true
ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=true
ENV WEBHOOK_EVENTS_CHATS_DELETE=true
ENV WEBHOOK_EVENTS_GROUPS_UPSERT=true
ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true
ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=true
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=true
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
ENV CONFIG_SESSION_PHONE_CLIENT="Evolution API"
ENV CONFIG_SESSION_PHONE_NAME="Chrome"
ENV CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI
ENV CONFIG_SESSION_PHONE_NAME=chrome
ENV QRCODE_LIMIT=30
ENV AUTHENTICATION_TYPE="apikey"
ENV AUTHENTICATION_TYPE=apikey
ENV AUTHENTICATION_API_KEY=$AUTHENTICATION_API_KEY
ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976
ENV AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
ENV AUTHENTICATION_JWT_EXPIRIN_IN=0
ENV AUTHENTICATION_JWT_SECRET="L0YWtjb2w554WFqPG"
ENV AUTHENTICATION_JWT_SECRET='L=0YWt]b2w[WF>#>:&E`'
ENV AUTHENTICATION_INSTANCE_NAME=$AUTHENTICATION_INSTANCE_NAME
ENV AUTHENTICATION_INSTANCE_WEBHOOK_URL=$AUTHENTICATION_INSTANCE_WEBHOOK_URL
ENV AUTHENTICATION_INSTANCE_MODE=$AUTHENTICATION_INSTANCE_MODE
ENV AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS=$AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS
ENV AUTHENTICATION_INSTANCE_MODE=server
ENV AUTHENTICATION_INSTANCE_NAME=evolution
ENV AUTHENTICATION_INSTANCE_WEBHOOK_URL=<url>
ENV AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1
ENV AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456
ENV AUTHENTICATION_INSTANCE_CHATWOOT_URL=<url>
RUN npm install

244
README.md
View File

@@ -1,247 +1,23 @@
<h1 align="center">Evolution Api</h1>
<!--
</br>
<hr style="height: 5px;background: #007500;margin: 20px 0;box-shadow: 0px 3px 5px 0px rgb(204 204 204);">-->
<!-- <div align="center"> -->
<div align="center">
<!-- [![Telegram Group](https://img.shields.io/badge/Group-Telegram-%2333C1FF)](#)
[![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](#)
[![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](https://evolution-api.com/whatsapp)
[![Discord Community](https://img.shields.io/badge/Discord-Community-blue)](https://evolution-api.com/discord)
[![Postman Collection](https://img.shields.io/badge/Postman-Collection-orange)](https://evolution-api.com/postman)
[![Documentation](https://img.shields.io/badge/Documentation-Official-green)](https://doc.evolution-api.com)
[![License](https://img.shields.io/badge/license-GPL--3.0-orange)](./LICENSE)
[![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://app.picpay.com/user/davidsongomes1998) -->
[![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://app.picpay.com/user/davidsongomes1998)
<!-- </div> -->
</div>
<!-- <div align="center"><img src="./public/images/cover.png"></div>-> -->
<div align="center"><img src="./public/images/cover.png"></div>
## WhatsApp-Api-NodeJs
This project is based on the [CodeChat](https://github.com/code-chat-br/whatsapp-api). The original project is an implementation of [Baileys](https://github.com/adiwajshing/Baileys), serving as a Restful API service that controls WhatsApp functions.</br>
This project is based on the [CodeChat](https://github.com/code-chat-br/whatsapp-api). The original project is an implementation of [Baileys](https://github.com/WhiskeySockets/Baileys), serving as a Restful API service that controls WhatsApp functions.</br>
The code allows the creation of multiservice chats, service bots, or any other system that utilizes WhatsApp. The documentation provides instructions on how to set up and use the project, as well as additional information about its features and configuration options.
## Infrastructure
### Nvm installation
```sh
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
# or
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
```
>
> After finishing, restart the terminal to load the new information.
>
### Docker installation \[optional\]
```sh
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker ${USER}
```
### Nodejs installation
```sh
nvm install 16.17.0
```
### pm2 installation
```sh
npm i -g pm2
```
```sh
docker --version
node --version
```
## MongoDb [optional]
After installing docker and docker-compose, up the container.
- [compose from mongodb](./mongodb/docker-compose.yaml)
In the same directory where the file is located, run the following command:
```sh
bash docker.sh
```
Using the database is optional.
## Application startup
Cloning the Repository
```
git clone https://github.com/code-chat-br/whatsapp-api.git
```
Go to the project directory and install all dependencies.</br>
```sh
cd whatsapp-api
npm i
```
Finally, run the command below to start the application:
```sh
# Under development
npm run start
# In production
npm run start:prod
# pm2
pm2 start 'npm run start:prod' --name ApiCodechat
```
## Authentication
You can define two authentication **types** for the routes in the **[env file](./src/dev-env.yml)**.
Authentications must be inserted in the request header.
1. **apikey**
2. **jwt:** A JWT is a standard for authentication and information exchange defined with a signature.
> Authentications are generated at instance creation time.
**Note:** There is also the possibility to define a global api key, which can access and control all instances.
### Connection
#### Create an instance
##### HTTP
> *NOTE:* This key must be inserted in the request header to create an instance.
```http
POST /instance/create HTTP/1.1
Host: localhost:8080
Content-Type: application/json
apikey: t8OOEeISKzpmc3jjcMqBWYSaJH2PIxns
```
##### cURL
```bash
curl --location --request POST 'http://localhost:8080/instance/create' \
--header 'Content-Type: application/json' \
--header 'apikey: t8OOEeISKzpmc3jjcMqBWYSaJH2PIxns' \
--data-raw '{
"instanceName": "codechat"
}'
```
### Response
```ts
{
"instance": {
"instanceName": "codechat",
"status": "created"
},
"hash": {
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]"
// or
// "apikey": "88513847-1B0E-4188-8D76-4A2750C9B6C3"
}
}
```
#### Connection with qrcode
##### HTTP
```http
GET /instance/connect/codechat HTTP/1.1
Host: localhost:8080
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]
```
```http
GET /instance/connect/codechat HTTP/1.1
Host: localhost:8080
apikey: 88513847-1B0E-4188-8D76-4A2750C9B6C3
```
##### cURL
```bash
curl --location --request GET 'http://localhost:8080/instance/connect/codechat' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. [...]'
```
```bash
curl --location --request GET 'http://localhost:8080/instance/connect/codechat' \
--header 'apikey: 88513847-1B0E-4188-8D76-4A2750C9B6C3'
```
### Response
```ts
{
"code": "2@nXSUgRJSBY6T0XJmiFKZ0 [...] ,XsgJhJHYa+0MPpXANdPHHt6Ke/I7O2QyXT/Lsge0uSg=",
"base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhE [...] LkMtqAAAAABJRU5ErkJggg=="
}
```
### App in Docker
- [docker run](./docker.sh)
- [docker-compose](./docker-compose.yml)
- [env for docker](./Docker/.env)
- [DockerHub-codechat/api](https://hub.docker.com/r/codechat/api)
After building the application, in the same directory as the files above, run the following command:
```sh
docker-compose up
```
## Send Messages
| | |
|-----|---|
| Send Text | ✔ |
| Send Buttons | ✔ |
| Send Template | ❌ |
| Send Media: audio - video - image - document - gif <br></br>base64: ```true``` | ✔ |
| Send Media File | ❌ |
| Send Audio type WhatsApp | ✔ |
| Send Location | ✔ |
| Send List | ✔ |
| Send Link Preview | ✔ |
| Send Contact | ✔ |
| Send Reaction - emoji | ✔ |
| Send Poll Message | ✔ |
## Postman collections
- [Postman Json](./postman.json)
## Webhook Events
| Name | Event | TypeData | Description |
|------|-------|-----------|------------|
| APPLICATION_STARTUP | application.startup | json | Notifies you when a application startup |
| QRCODE_UPDATED | qrcode.updated | json | Sends the base64 of the qrcode for reading |
| CONNECTION_UPDATE | connection.update | json | Informs the status of the connection with whatsapp |
| MESSAGES_SET | message.set | json | Sends a list of all your messages uploaded on whatsapp</br>This event occurs only once |
| MESSAGES_UPSERT | message.upsert | json | Notifies you when a message is received |
| MESSAGES_UPDATE | message.update | json | Tells you when a message is updated |
| SEND_MESSAGE | send.message | json | Notifies when a message is sent |
| CONTACTS_SET | contacts.set | json | Performs initial loading of all contacts</br>This event occurs only once |
| CONTACTS_UPSERT | contacts.upsert | json | Reloads all contacts with additional information</br>This event occurs only once |
| CONTACTS_UPDATE | contacts.update | json | Informs you when the chat is updated |
| PRESENCE_UPDATE | presence.update | json | Informs if the user is online, if he is performing some action like writing or recording and his last seen</br>'unavailable' | 'available' | 'composing' | 'recording' | 'paused' |
| CHATS_SET | chats.set | json | Send a list of all loaded chats |
| CHATS_UPDATE | chats.update | json | Informs you when the chat is updated |
| CHATS_UPSERT | chats.upsert | json | Sends any new chat information |
| GROUPS_UPSERT | groups.upsert | JSON | Notifies when a group is created |
| GROUPS_UPDATE | groups.update | JSON | Notifies when a group has its information updated |
| GROUP_PARTICIPANTS_UPDATE | group-participants.update | JSON | Notifies when an action occurs involving a participant</br>'add' | 'remove' | 'promote' | 'demote' |
| NEW_TOKEN | new.jwt | JSON | Notifies when the token (jwt) is updated
## Env File
See additional settings that can be applied through the **env** file by clicking **[here](./src/dev-env.yml)**.
> **⚠Attention⚠:** rename the **dev-env.yml** file to **env.yml**.
## SSL
To install the SSL certificate, follow the **[instructions](https://certbot.eff.org/instructions?ws=other&os=ubuntufocal)** below.
## SSL
To install the SSL certificate, follow the **[instructions](https://certbot.eff.org/instructions?ws=other&os=ubuntufocal)** below.
@@ -254,8 +30,6 @@ This code was produced based on the baileys library and it is still under develo
# Donate to the project.
<div align="center">
#### PicPay
<div align="center">

View File

@@ -1,18 +0,0 @@
version: '3.3'
networks:
evolution-net:
driver: bridge
services:
base:
container_name: evolution_api
image: davidsongomes/evolution-api:latest
ports:
- 8080:8080
volumes:
- /data/instances:/evolution/instances
command: ['node', './dist/src/main.js']
networks:
- evolution-net

View File

@@ -0,0 +1,28 @@
version: '3.3'
services:
api:
container_name: evolution_api
image: evolution/api:local
restart: always
ports:
- 8080:8080
volumes:
- evolution_instances:/evolution/instances
- evolution_store:/evolution/store
networks:
- evolution-net
env_file:
- ./Docker/.env
command: ['node', './dist/src/main.js']
expose:
- 8080
volumes:
evolution_instances:
evolution_store:
networks:
evolution-net:
external: true

View File

@@ -0,0 +1,79 @@
version: '3.3'
services:
api:
container_name: evolution_api
image: evolution/api:local
restart: always
ports:
- 8080:8080
volumes:
- evolution_instances:/evolution/instances
- evolution_store:/evolution/store
networks:
- evolution-net
env_file:
- ./Docker/.env
command: ['node', './dist/src/main.js']
expose:
- 8080
mongodb:
container_name: mongodb
image: mongo
restart: always
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root
- PUID=1000
- PGID=1000
volumes:
- evolution_mongodb_data:/data/db
- evolution_mongodb_configdb:/data/configdb
networks:
- evolution-net
expose:
- 27017
mongo-express:
image: mongo-express
networks:
- evolution-net
environment:
ME_CONFIG_BASICAUTH_USERNAME: root
ME_CONFIG_BASICAUTH_PASSWORD: root
ME_CONFIG_MONGODB_SERVER: mongodb
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: root
ports:
- 8081:8081
links:
- mongodb
redis:
image: redis:latest
container_name: redis
command: >
redis-server
--port 6379
--appendonly yes
volumes:
- evolution_redis:/data
networks:
- evolution-net
ports:
- 6379:6379
volumes:
evolution_instances:
evolution_store:
evolution_mongodb_data:
evolution_mongodb_configdb:
evolution_redis:
networks:
evolution-net:
external: true

13
docker.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
NET='evolution-net'
IMAGE='evolution/api:local'
if !(docker network ls | grep ${NET} > /dev/null)
then
docker network create -d bridge ${NET}
fi
docker build -t ${IMAGE} .
docker compose up -d

View File

@@ -1,28 +0,0 @@
version: '3.8'
networks:
api-net:
driver: bridge
services:
mongodb:
container_name: mongodb
# This image already has a single replica set
image: mongo
restart: always
volumes:
# sudo mkdir -p /data/mongodb
- /data/mongodb:/data/db
ports:
- 26712:27017
environment:
MONGO_INITDB_ROOT_USERNAME: root
# Set a password to access the bank
MONGO_INITDB_ROOT_PASSWORD: <password>
networks:
- api-net
expose:
- 26712

View File

@@ -1,8 +1,8 @@
{
"name": "evolution-api",
"version": "1.2.0",
"version": "1.4.0",
"description": "Rest api for communication with WhatsApp",
"main": "index.js",
"main": "./dist/src/main.js",
"scripts": {
"build": "tsc",
"start": "ts-node --files --transpile-only ./src/main.ts",
@@ -12,7 +12,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/DavidsonGomes/evolution-api.git"
"url": "git+https://github.com/EvolutionAPI/evolution-api.git"
},
"keywords": [
"chat",
@@ -36,13 +36,16 @@
},
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/DavidsonGomes/evolution-api/issues"
"url": "https://github.com/EvolutionAPI/evolution-api/issues"
},
"homepage": "https://github.com/DavidsonGomes/evolution-api#readme",
"homepage": "https://github.com/EvolutionAPI/evolution-api#readme",
"dependencies": {
"@adiwajshing/keyed-db": "^0.2.4",
"@evolution/base": "github:WhiskeySockets/Baileys",
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@figuro/chatwoot-sdk": "^1.1.14",
"@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2",
"@whiskeysockets/baileys": "^6.4.0",
"axios": "^1.3.5",
"class-validator": "^0.13.2",
"compression": "^1.7.4",
@@ -50,6 +53,7 @@
"cross-env": "^7.0.3",
"dayjs": "^1.11.7",
"eventemitter2": "^6.4.9",
"exiftool-vendored": "^22.0.0",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"hbs": "^4.2.0",
@@ -63,9 +67,11 @@
"node-cache": "^5.1.2",
"node-mime-types": "^1.1.0",
"pino": "^8.11.0",
"proxy-agent": "^6.2.1",
"qrcode": "^1.5.1",
"qrcode-terminal": "^0.12.0",
"redis": "^4.6.5",
"sharp": "^0.30.7",
"uuid": "^9.0.0"
},
"devDependencies": {

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -1,9 +1,9 @@
import { readFileSync } from 'fs';
import { load } from 'js-yaml';
import { join } from 'path';
import { SRC_DIR } from './path.config';
import { isBooleanString } from 'class-validator';
export type HttpServer = { TYPE: 'http' | 'https'; PORT: number };
export type HttpServer = { TYPE: 'http' | 'https'; PORT: number; URL: string };
export type HttpMethods = 'POST' | 'GET' | 'PUT' | 'DELETE';
export type Cors = {
@@ -12,15 +12,26 @@ export type Cors = {
CREDENTIALS: boolean;
};
export type LogLevel = 'ERROR' | 'WARN' | 'DEBUG' | 'INFO' | 'LOG' | 'VERBOSE' | 'DARK';
export type LogBaileys = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
export type LogLevel =
| 'ERROR'
| 'WARN'
| 'DEBUG'
| 'INFO'
| 'LOG'
| 'VERBOSE'
| 'DARK'
| 'WEBHOOKS';
export type Log = {
LEVEL: LogLevel[];
COLOR: boolean;
BAILEYS: LogBaileys;
};
export type SaveData = {
INSTANCE: boolean;
OLD_MESSAGE: boolean;
NEW_MESSAGE: boolean;
MESSAGE_UPDATE: boolean;
CONTACTS: boolean;
@@ -28,8 +39,16 @@ export type SaveData = {
};
export type StoreConf = {
MESSAGES: boolean;
MESSAGE_UP: boolean;
CONTACTS: boolean;
CHATS: boolean;
};
export type CleanStoreConf = {
CLEANING_INTERVAL: number;
MESSAGES: boolean;
MESSAGE_UP: boolean;
CONTACTS: boolean;
CHATS: boolean;
};
@@ -56,6 +75,7 @@ export type EventsWebhook = {
MESSAGES_SET: boolean;
MESSAGES_UPSERT: boolean;
MESSAGES_UPDATE: boolean;
MESSAGES_DELETE: boolean;
SEND_MESSAGE: boolean;
CONTACTS_SET: boolean;
CONTACTS_UPDATE: boolean;
@@ -74,17 +94,12 @@ export type EventsWebhook = {
export type ApiKey = { KEY: string };
export type Jwt = { EXPIRIN_IN: number; SECRET: string };
export type Instance = {
NAME: string;
WEBHOOK_URL: string;
MODE: string;
WEBHOOK_BY_EVENTS: boolean;
};
export type Auth = {
API_KEY: ApiKey;
EXPOSE_IN_FETCH_INSTANCES: boolean;
JWT: Jwt;
TYPE: 'jwt' | 'apikey';
INSTANCE: Instance;
};
export type DelInstance = number | boolean;
@@ -105,6 +120,7 @@ export interface Env {
CORS: Cors;
SSL_CONF: SslConf;
STORE: StoreConf;
CLEAN_STORE: CleanStoreConf;
DATABASE: Database;
REDIS: Redis;
LOG: Log;
@@ -134,12 +150,14 @@ export class ConfigService {
this.env.PRODUCTION = process.env?.NODE_ENV === 'PROD';
if (process.env?.DOCKER_ENV === 'true') {
this.env.SERVER.TYPE = 'http';
this.env.SERVER.PORT = Number.parseInt(process.env?.SERVER_PORT ?? '8080');
this.env.SERVER.PORT = 8080;
}
}
private envYaml(): Env {
return load(readFileSync(join(SRC_DIR, 'env.yml'), { encoding: 'utf-8' })) as Env;
return load(
readFileSync(join(process.cwd(), 'src', 'env.yml'), { encoding: 'utf-8' }),
) as Env;
}
private envProcess(): Env {
@@ -147,6 +165,7 @@ export class ConfigService {
SERVER: {
TYPE: process.env.SERVER_TYPE as 'http' | 'https',
PORT: Number.parseInt(process.env.SERVER_PORT),
URL: process.env.SERVER_URL,
},
CORS: {
ORIGIN: process.env.CORS_ORIGIN.split(','),
@@ -158,13 +177,20 @@ export class ConfigService {
FULLCHAIN: process.env?.SSL_CONF_FULLCHAIN,
},
STORE: {
CLEANING_INTERVAL: Number.isInteger(process.env?.STORE_CLEANING_TERMINAL)
? Number.parseInt(process.env.STORE_CLEANING_TERMINAL)
: undefined,
MESSAGES: process.env?.STORE_MESSAGE === 'true',
MESSAGES: process.env?.STORE_MESSAGES === 'true',
MESSAGE_UP: process.env?.STORE_MESSAGE_UP === 'true',
CONTACTS: process.env?.STORE_CONTACTS === 'true',
CHATS: process.env?.STORE_CHATS === 'true',
},
CLEAN_STORE: {
CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_TERMINAL)
? Number.parseInt(process.env.CLEAN_STORE_CLEANING_TERMINAL)
: 7200,
MESSAGES: process.env?.CLEAN_STORE_MESSAGES === 'true',
MESSAGE_UP: process.env?.CLEAN_STORE_MESSAGE_UP === 'true',
CONTACTS: process.env?.CLEAN_STORE_CONTACTS === 'true',
CHATS: process.env?.CLEAN_STORE_CHATS === 'true',
},
DATABASE: {
CONNECTION: {
URI: process.env.DATABASE_CONNECTION_URI,
@@ -173,7 +199,6 @@ export class ConfigService {
ENABLED: process.env?.DATABASE_ENABLED === 'true',
SAVE_DATA: {
INSTANCE: process.env?.DATABASE_SAVE_DATA_INSTANCE === 'true',
OLD_MESSAGE: process.env?.DATABASE_SAVE_DATA_OLD_MESSAGE === 'true',
NEW_MESSAGE: process.env?.DATABASE_SAVE_DATA_NEW_MESSAGE === 'true',
MESSAGE_UPDATE: process.env?.DATABASE_SAVE_MESSAGE_UPDATE === 'true',
CONTACTS: process.env?.DATABASE_SAVE_DATA_CONTACTS === 'true',
@@ -188,11 +213,11 @@ export class ConfigService {
LOG: {
LEVEL: process.env?.LOG_LEVEL.split(',') as LogLevel[],
COLOR: process.env?.LOG_COLOR === 'true',
BAILEYS: (process.env?.LOG_BAILEYS as LogBaileys) || 'error',
},
DEL_INSTANCE:
typeof process.env?.DEL_INSTANCE === 'boolean'
? process.env.DEL_INSTANCE === 'true'
: Number.parseInt(process.env.DEL_INSTANCE),
DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE)
? process.env.DEL_INSTANCE === 'true'
: Number.parseInt(process.env.DEL_INSTANCE) || false,
WEBHOOK: {
GLOBAL: {
URL: process.env?.WEBHOOK_GLOBAL_URL,
@@ -205,6 +230,7 @@ export class ConfigService {
MESSAGES_SET: process.env?.WEBHOOK_EVENTS_MESSAGES_SET === 'true',
MESSAGES_UPSERT: process.env?.WEBHOOK_EVENTS_MESSAGES_UPSERT === 'true',
MESSAGES_UPDATE: process.env?.WEBHOOK_EVENTS_MESSAGES_UPDATE === 'true',
MESSAGES_DELETE: process.env?.WEBHOOK_EVENTS_MESSAGES_DELETE === 'true',
SEND_MESSAGE: process.env?.WEBHOOK_EVENTS_SEND_MESSAGE === 'true',
CONTACTS_SET: process.env?.WEBHOOK_EVENTS_CONTACTS_SET === 'true',
CONTACTS_UPDATE: process.env?.WEBHOOK_EVENTS_CONTACTS_UPDATE === 'true',
@@ -223,30 +249,25 @@ export class ConfigService {
},
},
CONFIG_SESSION_PHONE: {
CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT,
NAME: process.env?.CONFIG_SESSION_PHONE_NAME,
CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API',
NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'chrome',
},
QRCODE: {
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT),
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
},
AUTHENTICATION: {
TYPE: process.env.AUTHENTICATION_TYPE as 'jwt',
API_KEY: {
KEY: process.env.AUTHENTICATION_API_KEY,
},
EXPOSE_IN_FETCH_INSTANCES:
process.env?.AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES === 'true',
JWT: {
EXPIRIN_IN: Number.isInteger(process.env?.AUTHENTICATION_JWT_EXPIRIN_IN)
? Number.parseInt(process.env.AUTHENTICATION_JWT_EXPIRIN_IN)
: 3600,
SECRET: process.env.AUTHENTICATION_JWT_SECRET,
},
INSTANCE: {
NAME: process.env.AUTHENTICATION_INSTANCE_NAME,
WEBHOOK_URL: process.env.AUTHENTICATION_INSTANCE_WEBHOOK_URL,
MODE: process.env.AUTHENTICATION_INSTANCE_MODE,
WEBHOOK_BY_EVENTS:
process.env.AUTHENTICATION_INSTANCE_WEBHOOK_BY_EVENTS === 'true',
},
},
};
}

View File

@@ -67,7 +67,6 @@ export class Logger {
this.configService.get<Log>('LOG').LEVEL.forEach((level) => types.push(Type[level]));
const typeValue = typeof value;
if (types.includes(type)) {
if (configService.get<Log>('LOG').COLOR) {
console.log(

View File

@@ -4,3 +4,4 @@ export const ROOT_DIR = process.cwd();
export const INSTANCE_DIR = join(ROOT_DIR, 'instances');
export const SRC_DIR = join(ROOT_DIR, 'src');
export const AUTH_DIR = join(ROOT_DIR, 'store', 'auth');
export const STORE_DIR = join(ROOT_DIR, 'store');

View File

@@ -2,13 +2,23 @@ import mongoose from 'mongoose';
import { configService, Database } from '../config/env.config';
import { Logger } from '../config/logger.config';
const logger = new Logger('Db Connection');
const logger = new Logger('MongoDB');
export const db = configService.get<Database>('DATABASE');
export const dbserver = db.ENABLED
? mongoose.createConnection(db.CONNECTION.URI, {
const db = configService.get<Database>('DATABASE');
export const dbserver = (() => {
if (db.ENABLED) {
logger.verbose('connecting');
const dbs = mongoose.createConnection(db.CONNECTION.URI, {
dbName: db.CONNECTION.DB_PREFIX_NAME + '-whatsapp-api',
})
: null;
});
logger.verbose('connected in ' + db.CONNECTION.URI);
logger.info('ON - dbName: ' + dbs['$dbName']);
db.ENABLED ? logger.info('ON - dbName: ' + dbserver['$dbName']) : null;
process.on('beforeExit', () => {
logger.verbose('instance destroyed');
dbserver.destroy(true, (error) => logger.error(error));
});
return dbs;
}
})();

View File

@@ -1,33 +1,64 @@
import { createClient, RedisClientType } from '@redis/client';
import { Logger } from '../config/logger.config';
import { BufferJSON } from '@evolution/base';
import { BufferJSON } from '@whiskeysockets/baileys';
import { Redis } from '../config/env.config';
export class RedisCache {
constructor(private readonly redisEnv: Partial<Redis>, private instanceName?: string) {
this.client = createClient({ url: this.redisEnv.URI });
this.client.connect();
constructor() {
this.logger.verbose('instance created');
process.on('beforeExit', async () => {
this.logger.verbose('instance destroyed');
if (this.statusConnection) {
this.logger.verbose('instance disconnect');
await this.client.disconnect();
}
});
}
private statusConnection = false;
private instanceName: string;
private redisEnv: Redis;
public set reference(reference: string) {
this.logger.verbose('set reference: ' + reference);
this.instanceName = reference;
}
public async connect(redisEnv: Redis) {
this.logger.verbose('connecting');
this.client = createClient({ url: redisEnv.URI });
this.logger.verbose('connected in ' + redisEnv.URI);
await this.client.connect();
this.statusConnection = true;
this.redisEnv = redisEnv;
}
private readonly logger = new Logger(RedisCache.name);
private client: RedisClientType;
public async instanceKeys(): Promise<string[]> {
try {
this.logger.verbose('instance keys: ' + this.redisEnv.PREFIX_KEY + ':*');
return await this.client.sendCommand(['keys', this.redisEnv.PREFIX_KEY + ':*']);
} catch (error) {
this.logger.error(error);
}
}
public async keyExists(key?: string) {
if (key) {
this.logger.verbose('keyExists: ' + key);
return !!(await this.instanceKeys()).find((i) => i === key);
}
this.logger.verbose('keyExists: ' + this.instanceName);
return !!(await this.instanceKeys()).find((i) => i === this.instanceName);
}
public async writeData(field: string, data: any) {
try {
this.logger.verbose('writeData: ' + field);
const json = JSON.stringify(data, BufferJSON.replacer);
return await this.client.hSet(
this.redisEnv.PREFIX_KEY + ':' + this.instanceName,
field,
@@ -40,13 +71,19 @@ export class RedisCache {
public async readData(field: string) {
try {
this.logger.verbose('readData: ' + field);
const data = await this.client.hGet(
this.redisEnv.PREFIX_KEY + ':' + this.instanceName,
field,
);
if (data) {
this.logger.verbose('readData: ' + field + ' success');
return JSON.parse(data, BufferJSON.reviver);
}
this.logger.verbose('readData: ' + field + ' not found');
return null;
} catch (error) {
this.logger.error(error);
}
@@ -54,6 +91,7 @@ export class RedisCache {
public async removeData(field: string) {
try {
this.logger.verbose('removeData: ' + field);
return await this.client.hDel(
this.redisEnv.PREFIX_KEY + ':' + this.instanceName,
field,
@@ -65,9 +103,12 @@ export class RedisCache {
public async delAll(hash?: string) {
try {
return await this.client.del(
this.logger.verbose('instance delAll: ' + hash);
const result = await this.client.del(
hash || this.redisEnv.PREFIX_KEY + ':' + this.instanceName,
);
return result;
} catch (error) {
this.logger.error(error);
}

View File

@@ -7,11 +7,12 @@
# Choose the server type for the application
SERVER:
TYPE: http # https
PORT: 8083 # 443
PORT: 8080 # 443
URL: localhost
CORS:
ORIGIN:
- '*'
- "*"
# - yourdomain.com
METHODS:
- POST
@@ -36,7 +37,9 @@ LOG:
- LOG
- VERBOSE
- DARK
- WEBHOOKS
COLOR: true
BAILEYS: error # fatal | error | warn | info | debug | trace
# Determine how long the instance should be deleted from memory in case of no connection.
# Default time: 5 minutes
@@ -45,47 +48,55 @@ DEL_INSTANCE: false # or false
# Temporary data storage
STORE:
CLEANING_INTERVAL: 7200 # seconds === 2h
MESSAGE: true
MESSAGES: true
MESSAGE_UP: true
CONTACTS: true
CHATS: true
CLEAN_STORE:
CLEANING_INTERVAL: 7200 # 7200 seconds === 2h
MESSAGES: true
MESSAGE_UP: true
CONTACTS: true
CHATS: true
# Permanent data storage
DATABASE:
ENABLED: true
ENABLED: false
CONNECTION:
URI: 'mongodb://root:root@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true'
URI: "mongodb://root:root@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true"
DB_PREFIX_NAME: evolution
# Choose the data you want to save in the application's database or store
SAVE_DATA:
INSTANCE: false
OLD_MESSAGE: false
NEW_MESSAGE: true
MESSAGE_UPDATE: true
CONTACTS: true
CHATS: true
NEW_MESSAGE: false
MESSAGE_UPDATE: false
CONTACTS: false
CHATS: false
REDIS:
ENABLED: true
URI: 'redis://localhost:6379/1'
PREFIX_KEY: 'evolution'
ENABLED: false
URI: "redis://localhost:6379"
PREFIX_KEY: "evolution"
# Webhook Settings
# Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created
WEBHOOK:
# Define a global webhook that will listen for enabled events from all instances
GLOBAL:
URL: <url>
ENABLED: true
ENABLED: false
# With this option activated, you work with a url per webhook event, respecting the global url and the name of each event
WEBHOOK_BY_EVENTS: false
# Automatically maps webhook paths
# Set the events you want to hear
# Set the events you want to hear
EVENTS:
APPLICATION_STARTUP: true
APPLICATION_STARTUP: false
QRCODE_UPDATED: true
MESSAGES_SET: true
MESSAGES_UPSERT: true
MESSAGES_UPDATE: true
MESSAGES_DELETE: true
SEND_MESSAGE: true
CONTACTS_SET: true
CONTACTS_UPSERT: true
@@ -100,33 +111,29 @@ WEBHOOK:
GROUP_PARTICIPANTS_UPDATE: true
CONNECTION_UPDATE: true
# This event fires every time a new token is requested via the refresh route
NEW_JWT_TOKEN: true
NEW_JWT_TOKEN: false
CONFIG_SESSION_PHONE:
# Name that will be displayed on smartphone connection
CLIENT: 'Evolution API'
NAME: Chrome # firefox | edge | opera | safari
CLIENT: "Evolution API"
NAME: chrome # chrome | firefox | edge | opera | safari
# Set qrcode display limit
QRCODE:
LIMIT: 30
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
AUTHENTICATION:
TYPE: apikey # or jwt apikey
TYPE: apikey # jwt or apikey
# Define a global apikey to access all instances
API_KEY:
# OBS: This key must be inserted in the request header to create an instance.
KEY: B6D711FC-DE4D-4FD5-9365-44120E713976
KEY: B6D711FCDE4D4FD5936544120E713976
# Expose the api key on return from fetch instances
EXPOSE_IN_FETCH_INSTANCES: true
# Set the secret key to encrypt and decrypt your token and its expiration time.
JWT:
EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires
SECRET: L=0YWt]b2w[WF>#>:&E`
# Set the instance name and webhook url to create an instance in init the application
INSTANCE:
# With this option activated, you work with a url per webhook event, respecting the local url and the name of each event
WEBHOOK_BY_EVENTS: false
MODE: server # container or server
# if you are using container mode, set the container name and the webhook url to default instance
NAME: evolution
WEBHOOK_URL: <url>
SECRET: L=0YWt]b2w[WF>#>:&E`

View File

@@ -10,17 +10,37 @@ import { waMonitor } from './whatsapp/whatsapp.module';
import { HttpStatus, router } from './whatsapp/routers/index.router';
import 'express-async-errors';
import { ServerUP } from './utils/server-up';
import * as Sentry from '@sentry/node';
function initWA() {
waMonitor.loadInstance();
}
function bootstrap() {
initWA();
const logger = new Logger('SERVER');
const app = express();
// Sentry.init({
// dsn: '',
// integrations: [
// // enable HTTP calls tracing
// new Sentry.Integrations.Http({ tracing: true }),
// // enable Express.js middleware tracing
// new Sentry.Integrations.Express({ app }),
// // Automatically instrument Node.js libraries and frameworks
// ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(),
// ],
// // Set tracesSampleRate to 1.0 to capture 100%
// // of transactions for performance monitoring.
// // We recommend adjusting this value in production
// tracesSampleRate: 1.0,
// });
// app.use(Sentry.Handlers.requestHandler());
// app.use(Sentry.Handlers.tracingHandler());
app.use(
cors({
origin(requestOrigin, callback) {
@@ -34,8 +54,8 @@ function bootstrap() {
methods: [...configService.get<Cors>('CORS').METHODS],
credentials: configService.get<Cors>('CORS').CREDENTIALS,
}),
urlencoded({ extended: true, limit: '50mb' }),
json({ limit: '50mb' }),
urlencoded({ extended: true, limit: '136mb' }),
json({ limit: '136mb' }),
compression(),
);
@@ -45,6 +65,13 @@ function bootstrap() {
app.use('/', router);
// app.use(Sentry.Handlers.errorHandler());
// app.use(function onError(err, req, res, next) {
// res.statusCode = 500;
// res.end(res.sentry + '\n');
// });
app.use(
(err: Error, req: Request, res: Response, next: NextFunction) => {
if (err) {
@@ -73,6 +100,8 @@ function bootstrap() {
logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT),
);
initWA();
onUnexpectedError();
}

View File

@@ -5,7 +5,7 @@ import {
initAuthCreds,
proto,
SignalDataTypeMap,
} from '@evolution/base';
} from '@whiskeysockets/baileys';
import { configService, Database } from '../config/env.config';
import { Logger } from '../config/logger.config';
import { dbserver } from '../db/db.connect';

View File

@@ -4,22 +4,17 @@ import {
initAuthCreds,
proto,
SignalDataTypeMap,
} from '@evolution/base';
} from '@whiskeysockets/baileys';
import { RedisCache } from '../db/redis.client';
import { Logger } from '../config/logger.config';
import { Redis } from '../config/env.config';
export async function useMultiFileAuthStateRedisDb(
redisEnv: Partial<Redis>,
instanceName: string,
): Promise<{
export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{
state: AuthenticationState;
saveCreds: () => Promise<void>;
}> {
const logger = new Logger(useMultiFileAuthStateRedisDb.name);
const cache = new RedisCache(redisEnv, instanceName);
const writeData = async (data: any, key: string): Promise<any> => {
try {
return await cache.writeData(key, data);

View File

@@ -27,6 +27,39 @@ export const instanceNameSchema: JSONSchema7 = {
properties: {
instanceName: { type: 'string' },
webhook: { type: 'string' },
webhook_by_events: { type: 'boolean' },
events: {
type: 'array',
minItems: 0,
items: {
type: 'string',
enum: [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'NEW_JWT_TOKEN',
],
},
},
qrcode: { type: 'boolean', enum: [true, false] },
number: { type: 'string', pattern: '^\\d+[\\.@\\w-]+' },
token: { type: 'string' },
},
...isNotEmpty('instanceName'),
};
@@ -50,8 +83,8 @@ const quotedOptionsSchema: JSONSchema7 = {
remoteJid: { type: 'string' },
fromMe: { type: 'boolean', enum: [true, false] },
},
required: ['id', 'remoteJid', 'fromMe'],
...isNotEmpty('id', 'remoteJid'),
required: ['id'],
...isNotEmpty('id'),
},
message: { type: 'object' },
},
@@ -91,7 +124,7 @@ const optionsSchema: JSONSchema7 = {
const numberDefinition: JSONSchema7Definition = {
type: 'string',
pattern: '^\\d+[\\.@\\w-]+',
// pattern: '^\\d+[\\.@\\w-]+',
description: 'Invalid format',
};
@@ -113,24 +146,6 @@ export const textMessageSchema: JSONSchema7 = {
required: ['textMessage', 'number'],
};
export const linkPreviewSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
number: { ...numberDefinition },
options: { ...optionsSchema },
linkPreview: {
type: 'object',
properties: {
text: { type: 'string' },
},
required: ['text'],
...isNotEmpty('text'),
},
},
required: ['linkPreview', 'number'],
};
export const pollMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -159,6 +174,37 @@ export const pollMessageSchema: JSONSchema7 = {
required: ['pollMessage', 'number'],
};
export const statusMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
statusMessage: {
type: 'object',
properties: {
type: { type: 'string', enum: ['text', 'image', 'audio', 'video'] },
content: { type: 'string' },
caption: { type: 'string' },
backgroundColor: { type: 'string' },
font: { type: 'integer', minimum: 0, maximum: 5 },
statusJidList: {
type: 'array',
minItems: 1,
uniqueItems: true,
items: {
type: 'string',
pattern: '^\\d+',
description: '"statusJidList" must be an array of numeric strings',
},
},
allContacts: { type: 'boolean', enum: [true, false] },
},
required: ['type', 'content'],
...isNotEmpty('type', 'content'),
},
},
required: ['statusMessage'],
};
export const mediaMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -180,6 +226,24 @@ export const mediaMessageSchema: JSONSchema7 = {
required: ['mediaMessage', 'number'],
};
export const stickerMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
number: { ...numberDefinition },
options: { ...optionsSchema },
stickerMessage: {
type: 'object',
properties: {
image: { type: 'string' },
},
required: ['image'],
...isNotEmpty('image'),
},
},
required: ['stickerMessage', 'number'],
};
export const audioMessageSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -331,8 +395,11 @@ export const contactMessageSchema: JSONSchema7 = {
description: '"wuid" must be a numeric string',
},
phoneNumber: { type: 'string', minLength: 10 },
organization: { type: 'string' },
email: { type: 'string' },
url: { type: 'string' },
},
required: ['fullName', 'wuid', 'phoneNumber'],
required: ['fullName', 'phoneNumber'],
...isNotEmpty('fullName'),
},
minItems: 1,
@@ -379,7 +446,7 @@ export const whatsappNumberSchema: JSONSchema7 = {
uniqueItems: true,
items: {
type: 'string',
pattern: '^\\d+',
// pattern: '^\\d+',
description: '"numbers" must be an array of numeric strings',
},
},
@@ -408,6 +475,36 @@ export const readMessageSchema: JSONSchema7 = {
required: ['readMessages'],
};
export const privacySettingsSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
privacySettings: {
type: 'object',
properties: {
readreceipts: { type: 'string', enum: ['all', 'none'] },
profile: {
type: 'string',
enum: ['all', 'contacts', 'contact_blacklist', 'none'],
},
status: {
type: 'string',
enum: ['all', 'contacts', 'contact_blacklist', 'none'],
},
online: { type: 'string', enum: ['all', 'match_last_seen'] },
last: { type: 'string', enum: ['all', 'contacts', 'contact_blacklist', 'none'] },
groupadd: {
type: 'string',
enum: ['all', 'contacts', 'contact_blacklist', 'none'],
},
},
required: ['readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'],
...isNotEmpty('readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'),
},
},
required: ['privacySettings'],
};
export const archiveChatSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -491,6 +588,17 @@ export const profilePictureSchema: JSONSchema7 = {
},
};
export const profileSchema: JSONSchema7 = {
type: 'object',
properties: {
wuid: { type: 'string' },
name: { type: 'string' },
picture: { type: 'string' },
status: { type: 'string' },
isBusiness: { type: 'boolean' },
},
};
export const messageValidateSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -587,6 +695,38 @@ export const groupJidSchema: JSONSchema7 = {
...isNotEmpty('groupJid'),
};
export const getParticipantsSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
getParticipants: { type: 'string', enum: ['true', 'false'] },
},
required: ['getParticipants'],
...isNotEmpty('getParticipants'),
};
export const groupSendInviteSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
groupJid: { type: 'string' },
description: { type: 'string' },
numbers: {
type: 'array',
minItems: 1,
uniqueItems: true,
items: {
type: 'string',
minLength: 10,
pattern: '\\d+',
description: '"numbers" must be an array of numeric strings',
},
},
},
required: ['groupJid', 'description', 'numbers'],
...isNotEmpty('groupJid', 'description', 'numbers'),
};
export const groupInviteSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
@@ -650,7 +790,7 @@ export const toggleEphemeralSchema: JSONSchema7 = {
...isNotEmpty('groupJid', 'expiration'),
};
export const updateGroupPicture: JSONSchema7 = {
export const updateGroupPictureSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
@@ -661,6 +801,28 @@ export const updateGroupPicture: JSONSchema7 = {
...isNotEmpty('groupJid', 'image'),
};
export const updateGroupSubjectSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
groupJid: { type: 'string' },
subject: { type: 'string' },
},
required: ['groupJid', 'subject'],
...isNotEmpty('groupJid', 'subject'),
};
export const updateGroupDescriptionSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
groupJid: { type: 'string' },
description: { type: 'string' },
},
required: ['groupJid', 'description'],
...isNotEmpty('groupJid', 'description'),
};
// Webhook Schema
export const webhookSchema: JSONSchema7 = {
$id: v4(),
@@ -668,7 +830,62 @@ export const webhookSchema: JSONSchema7 = {
properties: {
url: { type: 'string' },
enabled: { type: 'boolean', enum: [true, false] },
events: {
type: 'array',
minItems: 0,
items: {
type: 'string',
enum: [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'NEW_JWT_TOKEN',
],
},
},
},
required: ['url', 'enabled'],
...isNotEmpty('url'),
};
export const chatwootSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
account_id: { type: 'string' },
token: { type: 'string' },
url: { type: 'string' },
sign_msg: { type: 'boolean', enum: [true, false] },
},
required: ['enabled', 'account_id', 'token', 'url', 'sign_msg'],
...isNotEmpty('account_id', 'token', 'url', 'sign_msg'),
};
export const settingsSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
reject_call: { type: 'boolean', enum: [true, false] },
msg_call: { type: 'string' },
groups_ignore: { type: 'boolean', enum: [true, false] },
},
required: ['reject_call'],
...isNotEmpty('reject_call'),
};

View File

@@ -6,7 +6,8 @@ import { ROOT_DIR } from '../../config/path.config';
export type IInsert = { insertCount: number };
export interface IRepository {
insert(data: any, saveDb?: boolean): Promise<IInsert>;
insert(data: any, instanceName: string, saveDb?: boolean): Promise<IInsert>;
update(data: any, instanceName: string, saveDb?: boolean): Promise<IInsert>;
find(query: any): Promise<any>;
delete(query: any, force?: boolean): Promise<any>;
@@ -45,9 +46,14 @@ export abstract class Repository implements IRepository {
}
};
public insert(data: any, saveDb = false): Promise<IInsert> {
public insert(data: any, instanceName: string, saveDb = false): Promise<IInsert> {
throw new Error('Method not implemented.');
}
public update(data: any, instanceName: string, saveDb = false): Promise<IInsert> {
throw new Error('Method not implemented.');
}
public find(query: any): Promise<any> {
throw new Error('Method not implemented.');
}

View File

@@ -5,7 +5,7 @@ import { validate } from 'jsonschema';
import { BadRequestException } from '../../exceptions';
import 'express-async-errors';
import { Logger } from '../../config/logger.config';
import { GroupInvite, GroupJid } from '../dto/group.dto';
import { GetParticipant, GroupInvite, GroupJid } from '../dto/group.dto';
type DataValidate<T> = {
request: Request;
@@ -65,6 +65,37 @@ export abstract class RouterBroker {
return await execute(instance, ref);
}
public async groupNoValidate<T>(args: DataValidate<T>) {
const { request, ClassRef, schema, execute } = args;
const instance = request.params as unknown as InstanceDto;
const ref = new ClassRef();
Object.assign(ref, request.body);
const v = validate(ref, schema);
if (!v.valid) {
const message: any[] = v.errors.map(({ property, stack, schema }) => {
let message: string;
if (schema['description']) {
message = schema['description'];
} else {
message = stack.replace('instance.', '');
}
return {
property: property.replace('instance.', ''),
message,
};
});
logger.error([...message]);
throw new BadRequestException(...message);
}
return await execute(instance, ref);
}
public async groupValidate<T>(args: DataValidate<T>) {
const { request, ClassRef, schema, execute } = args;
@@ -129,7 +160,46 @@ export abstract class RouterBroker {
const v = validate(ref, schema);
console.log(v, '@checkei aqui');
if (!v.valid) {
const message: any[] = v.errors.map(({ property, stack, schema }) => {
let message: string;
if (schema['description']) {
message = schema['description'];
} else {
message = stack.replace('instance.', '');
}
return {
property: property.replace('instance.', ''),
message,
};
});
logger.error([...message]);
throw new BadRequestException(...message);
}
return await execute(instance, ref);
}
public async getParticipantsValidate<T>(args: DataValidate<T>) {
const { request, ClassRef, schema, execute } = args;
const getParticipants = request.query as unknown as GetParticipant;
if (!getParticipants?.getParticipants) {
throw new BadRequestException(
'The getParticipants needs to be informed in the query',
);
}
const instance = request.params as unknown as InstanceDto;
const body = request.body;
const ref = new ClassRef();
Object.assign(body, getParticipants);
Object.assign(ref, body);
const v = validate(ref, schema);
if (!v.valid) {
const message: any[] = v.errors.map(({ property, stack, schema }) => {

View File

@@ -1,76 +1,116 @@
import { proto } from '@evolution/base';
import { proto } from '@whiskeysockets/baileys';
import {
ArchiveChatDto,
DeleteMessage,
NumberDto,
PrivacySettingDto,
ProfileNameDto,
ProfilePictureDto,
ProfileStatusDto,
ReadMessageDto,
WhatsAppNumberDto,
getBase64FromMediaMessageDto,
} from '../dto/chat.dto';
import { InstanceDto } from '../dto/instance.dto';
import { ContactQuery } from '../repository/contact.repository';
import { MessageQuery } from '../repository/message.repository';
import { MessageUpQuery } from '../repository/messageUp.repository';
import { WAMonitoringService } from '../services/monitor.service';
import { Logger } from '../../config/logger.config';
const logger = new Logger('ChatController');
export class ChatController {
constructor(private readonly waMonitor: WAMonitoringService) {}
public async whatsappNumber({ instanceName }: InstanceDto, data: WhatsAppNumberDto) {
logger.verbose('requested whatsappNumber from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].whatsappNumber(data);
}
public async readMessage({ instanceName }: InstanceDto, data: ReadMessageDto) {
logger.verbose('requested readMessage from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].markMessageAsRead(data);
}
public async archiveChat({ instanceName }: InstanceDto, data: ArchiveChatDto) {
logger.verbose('requested archiveChat from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].archiveChat(data);
}
public async deleteMessage({ instanceName }: InstanceDto, data: DeleteMessage) {
logger.verbose('requested deleteMessage from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].deleteMessage(data);
}
public async fetchProfilePicture({ instanceName }: InstanceDto, data: NumberDto) {
logger.verbose('requested fetchProfilePicture from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].profilePicture(data.number);
}
public async fetchProfile({ instanceName }: InstanceDto, data: NumberDto) {
logger.verbose('requested fetchProfile from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchProfile(
instanceName,
data.number,
);
}
public async fetchContacts({ instanceName }: InstanceDto, query: ContactQuery) {
logger.verbose('requested fetchContacts from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchContacts(query);
}
public async getBase64FromMediaMessage(
{ instanceName }: InstanceDto,
message: proto.IWebMessageInfo,
data: getBase64FromMediaMessageDto,
) {
return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage(
message,
logger.verbose(
'requested getBase64FromMediaMessage from ' + instanceName + ' instance',
);
return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage(data);
}
public async fetchMessages({ instanceName }: InstanceDto, query: MessageQuery) {
logger.verbose('requested fetchMessages from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchMessages(query);
}
public async fetchStatusMessage({ instanceName }: InstanceDto, query: MessageUpQuery) {
logger.verbose('requested fetchStatusMessage from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchStatusMessage(query);
}
public async fetchChats({ instanceName }: InstanceDto) {
logger.verbose('requested fetchChats from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchChats();
}
public async getBusinessProfile(
public async fetchPrivacySettings({ instanceName }: InstanceDto) {
logger.verbose('requested fetchPrivacySettings from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchPrivacySettings();
}
public async updatePrivacySettings(
{ instanceName }: InstanceDto,
data: PrivacySettingDto,
) {
logger.verbose('requested updatePrivacySettings from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].updatePrivacySettings(data);
}
public async fetchBusinessProfile(
{ instanceName }: InstanceDto,
data: ProfilePictureDto,
) {
return await this.waMonitor.waInstances[instanceName].getBusinessProfile(data.number);
logger.verbose('requested fetchBusinessProfile from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchBusinessProfile(
data.number,
);
}
public async updateProfileName({ instanceName }: InstanceDto, data: ProfileNameDto) {
logger.verbose('requested updateProfileName from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].updateProfileName(data.name);
}
@@ -78,6 +118,7 @@ export class ChatController {
{ instanceName }: InstanceDto,
data: ProfileStatusDto,
) {
logger.verbose('requested updateProfileStatus from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].updateProfileStatus(
data.status,
);
@@ -87,6 +128,7 @@ export class ChatController {
{ instanceName }: InstanceDto,
data: ProfilePictureDto,
) {
logger.verbose('requested updateProfilePicture from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].updateProfilePicture(
data.picture,
);
@@ -96,6 +138,7 @@ export class ChatController {
{ instanceName }: InstanceDto,
data: ProfilePictureDto,
) {
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].removeProfilePicture();
}
}

View File

@@ -0,0 +1,103 @@
import { isURL } from 'class-validator';
import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { ChatwootService } from '../services/chatwoot.service';
import { Logger } from '../../config/logger.config';
import { waMonitor } from '../whatsapp.module';
import { ConfigService, HttpServer } from '../../config/env.config';
const logger = new Logger('ChatwootController');
export class ChatwootController {
constructor(
private readonly chatwootService: ChatwootService,
private readonly configService: ConfigService,
) {}
public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
logger.verbose(
'requested createChatwoot from ' + instance.instanceName + ' instance',
);
if (data.enabled) {
if (!isURL(data.url, { require_tld: false })) {
throw new BadRequestException('url is not valid');
}
if (!data.account_id) {
throw new BadRequestException('account_id is required');
}
if (!data.token) {
throw new BadRequestException('token is required');
}
if (data.sign_msg !== true && data.sign_msg !== false) {
throw new BadRequestException('sign_msg is required');
}
}
if (!data.enabled) {
logger.verbose('chatwoot disabled');
data.account_id = '';
data.token = '';
data.url = '';
data.sign_msg = false;
}
data.name_inbox = instance.instanceName;
const result = this.chatwootService.create(instance, data);
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const response = {
...result,
webhook_url: `${urlServer}/chatwoot/webhook/${instance.instanceName}`,
};
return response;
}
public async findChatwoot(instance: InstanceDto) {
logger.verbose('requested findChatwoot from ' + instance.instanceName + ' instance');
const result = await this.chatwootService.find(instance);
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
if (Object.keys(result).length === 0) {
return {
enabled: false,
url: '',
account_id: '',
token: '',
sign_msg: false,
name_inbox: '',
webhook_url: '',
};
}
const response = {
...result,
webhook_url: `${urlServer}/chatwoot/webhook/${instance.instanceName}`,
};
return response;
}
public async receiveWebhook(instance: InstanceDto, data: any) {
logger.verbose(
'requested receiveWebhook from ' + instance.instanceName + ' instance',
);
const chatwootService = new ChatwootService(waMonitor, this.configService);
return chatwootService.receiveWebhook(instance, data);
}
public async newInstance(data: any) {
const chatwootService = new ChatwootService(waMonitor, this.configService);
return chatwootService.newInstance(data);
}
}

View File

@@ -1,47 +1,102 @@
import {
CreateGroupDto,
GetParticipant,
GroupDescriptionDto,
GroupInvite,
GroupJid,
GroupPictureDto,
GroupSendInvite,
GroupSubjectDto,
GroupToggleEphemeralDto,
GroupUpdateParticipantDto,
GroupUpdateSettingDto,
} from '../dto/group.dto';
import { InstanceDto } from '../dto/instance.dto';
import { WAMonitoringService } from '../services/monitor.service';
import { Logger } from '../../config/logger.config';
const logger = new Logger('ChatController');
export class GroupController {
constructor(private readonly waMonitor: WAMonitoringService) {}
public async createGroup(instance: InstanceDto, create: CreateGroupDto) {
logger.verbose('requested createGroup from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].createGroup(create);
}
public async updateGroupPicture(instance: InstanceDto, update: GroupPictureDto) {
logger.verbose(
'requested updateGroupPicture from ' + instance.instanceName + ' instance',
);
return await this.waMonitor.waInstances[instance.instanceName].updateGroupPicture(
update,
);
}
public async updateGroupSubject(instance: InstanceDto, update: GroupSubjectDto) {
logger.verbose(
'requested updateGroupSubject from ' + instance.instanceName + ' instance',
);
return await this.waMonitor.waInstances[instance.instanceName].updateGroupSubject(
update,
);
}
public async updateGroupDescription(
instance: InstanceDto,
update: GroupDescriptionDto,
) {
logger.verbose(
'requested updateGroupDescription from ' + instance.instanceName + ' instance',
);
return await this.waMonitor.waInstances[instance.instanceName].updateGroupDescription(
update,
);
}
public async findGroupInfo(instance: InstanceDto, groupJid: GroupJid) {
logger.verbose('requested findGroupInfo from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].findGroup(groupJid);
}
public async fetchAllGroups(instance: InstanceDto, getPaticipants: GetParticipant) {
logger.verbose(
'requested fetchAllGroups from ' + instance.instanceName + ' instance',
);
return await this.waMonitor.waInstances[instance.instanceName].fetchAllGroups(
getPaticipants,
);
}
public async inviteCode(instance: InstanceDto, groupJid: GroupJid) {
logger.verbose('requested inviteCode from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].inviteCode(groupJid);
}
public async inviteInfo(instance: InstanceDto, inviteCode: GroupInvite) {
logger.verbose('requested inviteInfo from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].inviteInfo(inviteCode);
}
public async sendInvite(instance: InstanceDto, data: GroupSendInvite) {
logger.verbose('requested sendInvite from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data);
}
public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {
logger.verbose(
'requested revokeInviteCode from ' + instance.instanceName + ' instance',
);
return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(
groupJid,
);
}
public async findParticipants(instance: InstanceDto, groupJid: GroupJid) {
logger.verbose(
'requested findParticipants from ' + instance.instanceName + ' instance',
);
return await this.waMonitor.waInstances[instance.instanceName].findParticipants(
groupJid,
);
@@ -51,22 +106,32 @@ export class GroupController {
instance: InstanceDto,
update: GroupUpdateParticipantDto,
) {
logger.verbose(
'requested updateGParticipate from ' + instance.instanceName + ' instance',
);
return await this.waMonitor.waInstances[instance.instanceName].updateGParticipant(
update,
);
}
public async updateGSetting(instance: InstanceDto, update: GroupUpdateSettingDto) {
logger.verbose(
'requested updateGSetting from ' + instance.instanceName + ' instance',
);
return await this.waMonitor.waInstances[instance.instanceName].updateGSetting(update);
}
public async toggleEphemeral(instance: InstanceDto, update: GroupToggleEphemeralDto) {
logger.verbose(
'requested toggleEphemeral from ' + instance.instanceName + ' instance',
);
return await this.waMonitor.waInstances[instance.instanceName].toggleEphemeral(
update,
);
}
public async leaveGroup(instance: InstanceDto, groupJid: GroupJid) {
logger.verbose('requested leaveGroup from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].leaveGroup(groupJid);
}
}

View File

@@ -1,6 +1,6 @@
import { delay } from '@evolution/base';
import { delay } from '@whiskeysockets/baileys';
import EventEmitter2 from 'eventemitter2';
import { Auth, ConfigService } from '../../config/env.config';
import { Auth, ConfigService, HttpServer } from '../../config/env.config';
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { RepositoryBroker } from '../repository/repository.manager';
@@ -8,7 +8,11 @@ import { AuthService, OldToken } from '../services/auth.service';
import { WAMonitoringService } from '../services/monitor.service';
import { WAStartupService } from '../services/whatsapp.service';
import { WebhookService } from '../services/webhook.service';
import { ChatwootService } from '../services/chatwoot.service';
import { Logger } from '../../config/logger.config';
import { wa } from '../types/wa.types';
import { RedisCache } from '../../db/redis.client';
import { isURL } from 'class-validator';
export class InstanceController {
constructor(
@@ -18,111 +22,236 @@ export class InstanceController {
private readonly eventEmitter: EventEmitter2,
private readonly authService: AuthService,
private readonly webhookService: WebhookService,
private readonly chatwootService: ChatwootService,
private readonly cache: RedisCache,
) {}
private readonly logger = new Logger(InstanceController.name);
public async createInstance({ instanceName, webhook }: InstanceDto) {
//verifica se modo da instancia é container
const mode = this.configService.get<Auth>('AUTHENTICATION').INSTANCE.MODE;
public async createInstance({
instanceName,
webhook,
webhook_by_events,
events,
qrcode,
number,
token,
chatwoot_account_id,
chatwoot_token,
chatwoot_url,
chatwoot_sign_msg,
}: InstanceDto) {
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
if (mode === 'container') {
//verifica se ja existe uma instancia criada com qualquer nome
if (Object.keys(this.waMonitor.waInstances).length > 0) {
throw new BadRequestException([
'Instance already created',
'Only one instance can be created',
]);
}
const instance = new WAStartupService(
this.configService,
this.eventEmitter,
this.repository,
if (instanceName !== instanceName.toLowerCase().replace(/[^a-z0-9]/g, '')) {
throw new BadRequestException(
'The instance name must be lowercase and without special characters',
);
instance.instanceName = instanceName;
this.waMonitor.waInstances[instance.instanceName] = instance;
this.waMonitor.delInstanceTime(instance.instanceName);
}
const hash = await this.authService.generateHash({
this.logger.verbose('checking duplicate token');
await this.authService.checkDuplicateToken(token);
this.logger.verbose('creating instance');
const instance = new WAStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
);
instance.instanceName = instanceName
.toLowerCase()
.replace(/[^a-z0-9]/g, '')
.replace(' ', '');
this.logger.verbose('instance: ' + instance.instanceName + ' created');
this.waMonitor.waInstances[instance.instanceName] = instance;
this.waMonitor.delInstanceTime(instance.instanceName);
this.logger.verbose('generating hash');
const hash = await this.authService.generateHash(
{
instanceName: instance.instanceName,
});
},
token,
);
if (webhook) {
try {
this.webhookService.create(instance, { enabled: true, url: webhook });
} catch (error) {
this.logger.log(error);
}
this.logger.verbose('hash: ' + hash + ' generated');
let getEvents: string[];
if (webhook) {
if (!isURL(webhook, { require_tld: false })) {
throw new BadRequestException('Invalid "url" property in webhook');
}
return {
this.logger.verbose('creating webhook');
try {
this.webhookService.create(instance, {
enabled: true,
url: webhook,
events,
webhook_by_events,
});
getEvents = (await this.webhookService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
let getQrcode: wa.QrCode;
if (qrcode) {
this.logger.verbose('creating qrcode');
await instance.connectToWhatsapp(number);
await delay(3000);
getQrcode = instance.qrCode;
}
const result = {
instance: {
instanceName: instance.instanceName,
status: 'created',
},
hash,
webhook,
webhook_by_events,
events: getEvents,
qrcode: getQrcode,
};
} else {
const instance = new WAStartupService(
this.configService,
this.eventEmitter,
this.repository,
);
instance.instanceName = instanceName;
this.waMonitor.waInstances[instance.instanceName] = instance;
this.waMonitor.delInstanceTime(instance.instanceName);
const hash = await this.authService.generateHash({
instanceName: instance.instanceName,
this.logger.verbose('instance created');
this.logger.verbose(result);
return result;
}
if (!chatwoot_account_id) {
throw new BadRequestException('account_id is required');
}
if (!chatwoot_token) {
throw new BadRequestException('token is required');
}
if (!chatwoot_url) {
throw new BadRequestException('url is required');
}
if (!isURL(chatwoot_url, { require_tld: false })) {
throw new BadRequestException('Invalid "url" property in chatwoot');
}
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
try {
this.chatwootService.create(instance, {
enabled: true,
account_id: chatwoot_account_id,
token: chatwoot_token,
url: chatwoot_url,
sign_msg: chatwoot_sign_msg || false,
name_inbox: instance.instanceName,
number,
});
if (webhook) {
try {
this.webhookService.create(instance, { enabled: true, url: webhook });
} catch (error) {
this.logger.log(error);
}
this.chatwootService.initInstanceChatwoot(
instance,
instance.instanceName,
`${urlServer}/chatwoot/webhook/${instance.instanceName}`,
qrcode,
number,
);
} catch (error) {
this.logger.log(error);
}
return {
instance: {
instanceName: instance.instanceName,
status: 'created',
},
hash,
webhook,
webhook_by_events,
events: getEvents,
chatwoot: {
enabled: true,
account_id: chatwoot_account_id,
token: chatwoot_token,
url: chatwoot_url,
sign_msg: chatwoot_sign_msg || false,
number,
name_inbox: instance.instanceName,
webhook_url: `${urlServer}/chatwoot/webhook/${instance.instanceName}`,
},
};
}
public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) {
try {
this.logger.verbose(
'requested connectToWhatsapp from ' + instanceName + ' instance',
);
const instance = this.waMonitor.waInstances[instanceName];
const state = instance?.connectionStatus?.state;
this.logger.verbose('state: ' + state);
if (state == 'open') {
return await this.connectionState({ instanceName });
}
if (state == 'connecting') {
return instance.qrCode;
}
if (state == 'close') {
this.logger.verbose('connecting');
await instance.connectToWhatsapp(number);
await delay(2000);
return instance.qrCode;
}
return {
instance: {
instanceName: instance.instanceName,
status: 'created',
instanceName: instanceName,
status: state,
},
hash,
webhook,
qrcode: instance?.qrCode,
};
} catch (error) {
this.logger.error(error);
}
}
public async connectToWhatsapp({ instanceName }: InstanceDto) {
public async restartInstance({ instanceName }: InstanceDto) {
try {
const instance = this.waMonitor.waInstances[instanceName];
const state = instance.connectionStatus?.state;
this.logger.verbose('requested restartInstance from ' + instanceName + ' instance');
switch (state) {
case 'close':
await instance.connectToWhatsapp();
await delay(2000);
return instance.qrCode;
case 'connecting':
return instance.qrCode;
default:
return await this.connectionState({ instanceName });
}
this.logger.verbose('logging out instance: ' + instanceName);
this.waMonitor.waInstances[instanceName]?.client?.ws?.close();
return { error: false, message: 'Instance restarted' };
} catch (error) {
this.logger.log(error);
this.logger.error(error);
}
}
public async connectionState({ instanceName }: InstanceDto) {
return this.waMonitor.waInstances[instanceName].connectionStatus;
this.logger.verbose('requested connectionState from ' + instanceName + ' instance');
return this.waMonitor.waInstances[instanceName]?.connectionStatus;
}
public async fetchInstances({ instanceName }: InstanceDto) {
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
if (instanceName) {
this.logger.verbose('instanceName: ' + instanceName);
return this.waMonitor.instanceInfo(instanceName);
}
@@ -130,13 +259,23 @@ export class InstanceController {
}
public async logout({ instanceName }: InstanceDto) {
this.logger.verbose('requested logout from ' + instanceName + ' instance');
const stateConn = await this.connectionState({ instanceName });
if (stateConn.state === 'close') {
throw new BadRequestException(
'The "' + instanceName + '" instance is not connected',
);
}
try {
this.logger.verbose('logging out instance: ' + instanceName);
await this.waMonitor.waInstances[instanceName]?.client?.logout(
'Log out instance: ' + instanceName,
);
this.logger.verbose('close connection instance: ' + instanceName);
this.waMonitor.waInstances[instanceName]?.client?.ws?.close();
this.waMonitor.waInstances[instanceName]?.client?.end(undefined);
return { error: false, message: 'Instance logged out' };
} catch (error) {
@@ -145,22 +284,35 @@ export class InstanceController {
}
public async deleteInstance({ instanceName }: InstanceDto) {
this.logger.verbose('requested deleteInstance from ' + instanceName + ' instance');
const stateConn = await this.connectionState({ instanceName });
if (stateConn.state === 'open') {
throw new BadRequestException([
'Deletion failed',
'The instance needs to be disconnected',
]);
throw new BadRequestException(
'The "' + instanceName + '" instance needs to be disconnected',
);
}
try {
delete this.waMonitor.waInstances[instanceName];
return { error: false, message: 'Instance deleted' };
if (stateConn.state === 'connecting') {
this.logger.verbose('logging out instance: ' + instanceName);
await this.logout({ instanceName });
delete this.waMonitor.waInstances[instanceName];
return { error: false, message: 'Instance deleted' };
} else {
this.logger.verbose('deleting instance: ' + instanceName);
delete this.waMonitor.waInstances[instanceName];
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
return { error: false, message: 'Instance deleted' };
}
} catch (error) {
throw new BadRequestException(error.toString());
}
}
public async refreshToken(_: InstanceDto, oldToken: OldToken) {
this.logger.verbose('requested refreshToken');
return await this.authService.refreshToken(oldToken);
}
}

View File

@@ -5,34 +5,76 @@ import {
SendAudioDto,
SendButtonDto,
SendContactDto,
SendLinkPreviewDto,
SendListDto,
SendLocationDto,
SendMediaDto,
SendPollDto,
SendReactionDto,
SendStatusDto,
SendStickerDto,
SendTextDto,
} from '../dto/sendMessage.dto';
import { WAMonitoringService } from '../services/monitor.service';
import { Logger } from '../../config/logger.config';
const logger = new Logger('MessageRouter');
export class SendMessageController {
constructor(private readonly waMonitor: WAMonitoringService) {}
public async sendText({ instanceName }: InstanceDto, data: SendTextDto) {
logger.verbose('requested sendText from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].textMessage(data);
}
public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) {
if (isBase64(data?.mediaMessage?.media) && !data?.mediaMessage?.fileName) {
throw new BadRequestException('For bse64 the file name must be informed.');
logger.verbose('requested sendMedia from ' + instanceName + ' instance');
if (
isBase64(data?.mediaMessage?.media) &&
!data?.mediaMessage?.fileName &&
data?.mediaMessage?.mediatype === 'document'
) {
throw new BadRequestException('For base64 the file name must be informed.');
}
logger.verbose(
'isURL: ' +
isURL(data?.mediaMessage?.media) +
', isBase64: ' +
isBase64(data?.mediaMessage?.media),
);
if (isURL(data?.mediaMessage?.media) || isBase64(data?.mediaMessage?.media)) {
return await this.waMonitor.waInstances[instanceName].mediaMessage(data);
}
throw new BadRequestException('Owned media must be a url or base64');
}
public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto) {
logger.verbose('requested sendSticker from ' + instanceName + ' instance');
logger.verbose(
'isURL: ' +
isURL(data?.stickerMessage?.image) +
', isBase64: ' +
isBase64(data?.stickerMessage?.image),
);
if (isURL(data.stickerMessage.image) || isBase64(data.stickerMessage.image)) {
return await this.waMonitor.waInstances[instanceName].mediaSticker(data);
}
throw new BadRequestException('Owned media must be a url or base64');
}
public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto) {
logger.verbose('requested sendWhatsAppAudio from ' + instanceName + ' instance');
logger.verbose(
'isURL: ' +
isURL(data?.audioMessage?.audio) +
', isBase64: ' +
isBase64(data?.audioMessage?.audio),
);
if (isURL(data.audioMessage.audio) || isBase64(data.audioMessage.audio)) {
return await this.waMonitor.waInstances[instanceName].audioWhatsapp(data);
}
@@ -40,6 +82,7 @@ export class SendMessageController {
}
public async sendButtons({ instanceName }: InstanceDto, data: SendButtonDto) {
logger.verbose('requested sendButtons from ' + instanceName + ' instance');
if (
isBase64(data.buttonMessage.mediaMessage?.media) &&
!data.buttonMessage.mediaMessage?.fileName
@@ -50,18 +93,22 @@ export class SendMessageController {
}
public async sendLocation({ instanceName }: InstanceDto, data: SendLocationDto) {
logger.verbose('requested sendLocation from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].locationMessage(data);
}
public async sendList({ instanceName }: InstanceDto, data: SendListDto) {
logger.verbose('requested sendList from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].listMessage(data);
}
public async sendContact({ instanceName }: InstanceDto, data: SendContactDto) {
logger.verbose('requested sendContact from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].contactMessage(data);
}
public async sendReaction({ instanceName }: InstanceDto, data: SendReactionDto) {
logger.verbose('requested sendReaction from ' + instanceName + ' instance');
if (!data.reactionMessage.reaction.match(/[^\(\)\w\sà-ú"-\+]+/)) {
throw new BadRequestException('"reaction" must be an emoji');
}
@@ -69,10 +116,12 @@ export class SendMessageController {
}
public async sendPoll({ instanceName }: InstanceDto, data: SendPollDto) {
logger.verbose('requested sendPoll from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].pollMessage(data);
}
public async sendLinkPreview({ instanceName }: InstanceDto, data: SendLinkPreviewDto) {
return await this.waMonitor.waInstances[instanceName].linkPreview(data);
public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto) {
logger.verbose('requested sendStatus from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].statusMessage(data);
}
}

View File

@@ -0,0 +1,29 @@
import { isURL } from 'class-validator';
import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { SettingsDto } from '../dto/settings.dto';
import { SettingsService } from '../services/settings.service';
import { Logger } from '../../config/logger.config';
const logger = new Logger('SettingsController');
export class SettingsController {
constructor(private readonly settingsService: SettingsService) {}
public async createSettings(instance: InstanceDto, data: SettingsDto) {
logger.verbose(
'requested createSettings from ' + instance.instanceName + ' instance',
);
if (data.reject_call && data.msg_call.trim() == '') {
throw new BadRequestException('msg_call is required');
}
return this.settingsService.create(instance, data);
}
public async findSettings(instance: InstanceDto) {
logger.verbose('requested findSettings from ' + instance.instanceName + ' instance');
return this.settingsService.find(instance);
}
}

View File

@@ -3,18 +3,31 @@ import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { WebhookDto } from '../dto/webhook.dto';
import { WebhookService } from '../services/webhook.service';
import { Logger } from '../../config/logger.config';
const logger = new Logger('WebhookController');
export class WebhookController {
constructor(private readonly webhookService: WebhookService) {}
public async createWebhook(instance: InstanceDto, data: WebhookDto) {
if (!isURL(data.url, { require_tld: false })) {
logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance');
if (data.enabled && !isURL(data.url, { require_tld: false })) {
throw new BadRequestException('Invalid "url" property');
}
if (!data.enabled) {
logger.verbose('webhook disabled');
data.url = '';
data.events = [];
}
return this.webhookService.create(instance, data);
}
public async findWebhook(instance: InstanceDto) {
logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance');
return this.webhookService.find(instance);
}
}

View File

@@ -1,3 +1,10 @@
import {
WAPrivacyOnlineValue,
WAPrivacyValue,
WAReadReceiptsValue,
proto,
} from '@whiskeysockets/baileys';
export class OnWhatsAppDto {
constructor(
public readonly jid: string,
@@ -6,6 +13,11 @@ export class OnWhatsAppDto {
) {}
}
export class getBase64FromMediaMessageDto {
message: proto.WebMessageInfo;
convertToMp4?: boolean;
}
export class WhatsAppNumberDto {
numbers: string[];
}
@@ -14,6 +26,19 @@ export class NumberDto {
number: string;
}
export class NumberBusiness {
wid?: string;
jid?: string;
exists?: boolean;
isBusiness: boolean;
name?: string;
message?: string;
description?: string;
email?: string;
website?: string[];
address?: string;
}
export class ProfileNameDto {
name: string;
}
@@ -47,6 +72,19 @@ export class ArchiveChatDto {
archive: boolean;
}
class PrivacySetting {
readreceipts: WAReadReceiptsValue;
profile: WAPrivacyValue;
status: WAPrivacyValue;
online: WAPrivacyOnlineValue;
last: WAPrivacyValue;
groupadd: WAPrivacyValue;
}
export class PrivacySettingDto {
privacySettings: PrivacySetting;
}
export class DeleteMessage {
id: string;
fromMe: boolean;

View File

@@ -0,0 +1,9 @@
export class ChatwootDto {
enabled?: boolean;
account_id?: string;
token?: string;
url?: string;
name_inbox?: string;
sign_msg?: boolean;
number?: string;
}

View File

@@ -9,14 +9,34 @@ export class GroupPictureDto {
image: string;
}
export class GroupSubjectDto {
groupJid: string;
subject: string;
}
export class GroupDescriptionDto {
groupJid: string;
description: string;
}
export class GroupJid {
groupJid: string;
}
export class GetParticipant {
getParticipants: string;
}
export class GroupInvite {
inviteCode: string;
}
export class GroupSendInvite {
groupJid: string;
description: string;
numbers: string[];
}
export class GroupUpdateParticipantDto extends GroupJid {
action: 'add' | 'remove' | 'promote' | 'demote';
participants: string[];

View File

@@ -1,4 +1,13 @@
export class InstanceDto {
instanceName: string;
webhook?: string;
webhook_by_events?: boolean;
events?: string[];
qrcode?: boolean;
number?: string;
token?: string;
chatwoot_account_id?: string;
chatwoot_token?: string;
chatwoot_url?: string;
chatwoot_sign_msg?: boolean;
}

View File

@@ -1,4 +1,4 @@
import { proto, WAPresence } from '@evolution/base';
import { proto, WAPresence } from '@whiskeysockets/baileys';
export class Quoted {
key: proto.IMessageKey;
@@ -15,6 +15,8 @@ export class Options {
presence?: WAPresence;
quoted?: Quoted;
mentions?: Mentions;
linkPreview?: boolean;
encoding?: boolean;
}
class OptionsMessage {
options: Options;
@@ -28,8 +30,14 @@ class TextMessage {
text: string;
}
class linkPreviewMessage {
text: string;
export class StatusMessage {
type: string;
content: string;
statusJidList?: string[];
allContacts?: boolean;
caption?: string;
backgroundColor?: string;
font?: number;
}
class PollMessage {
@@ -42,8 +50,8 @@ export class SendTextDto extends Metadata {
textMessage: TextMessage;
}
export class SendLinkPreviewDto extends Metadata {
linkPreview: linkPreviewMessage;
export class SendStatusDto extends Metadata {
statusMessage: StatusMessage;
}
export class SendPollDto extends Metadata {
@@ -62,6 +70,12 @@ export class MediaMessage {
export class SendMediaDto extends Metadata {
mediaMessage: MediaMessage;
}
class Sticker {
image: string;
}
export class SendStickerDto extends Metadata {
stickerMessage: Sticker;
}
class Audio {
audio: string;
@@ -119,6 +133,9 @@ export class ContactMessage {
fullName: string;
wuid: string;
phoneNumber: string;
organization?: string;
email?: string;
url?: string;
}
export class SendContactDto extends Metadata {
contactMessage: ContactMessage[];

View File

@@ -0,0 +1,5 @@
export class SettingsDto {
reject_call?: boolean;
msg_call?: string;
groups_ignore?: boolean;
}

View File

@@ -1,4 +1,6 @@
export class WebhookDto {
enabled?: boolean;
url?: string;
events?: string[];
webhook_by_events?: boolean;
}

View File

@@ -2,17 +2,26 @@ import { NextFunction, Request, Response } from 'express';
import { existsSync } from 'fs';
import { join } from 'path';
import { INSTANCE_DIR } from '../../config/path.config';
import { db, dbserver } from '../../db/db.connect';
import { dbserver } from '../../db/db.connect';
import {
BadRequestException,
ForbiddenException,
NotFoundException,
} from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { waMonitor } from '../whatsapp.module';
import { cache, waMonitor } from '../whatsapp.module';
import { Database, Redis, configService } from '../../config/env.config';
async function getInstance(instanceName: string) {
const exists = waMonitor.waInstances[instanceName];
const db = configService.get<Database>('DATABASE');
const redisConf = configService.get<Redis>('REDIS');
const exists = !!waMonitor.waInstances[instanceName];
if (redisConf.ENABLED) {
const keyExists = await cache.keyExists();
return exists || keyExists;
}
if (db.ENABLED) {
const collection = dbserver

View File

@@ -0,0 +1,31 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../db/db.connect';
export class ChatwootRaw {
_id?: string;
enabled?: boolean;
account_id?: string;
token?: string;
url?: string;
name_inbox?: string;
sign_msg?: boolean;
number?: string;
}
const chatwootSchema = new Schema<ChatwootRaw>({
_id: { type: String, _id: true },
enabled: { type: Boolean, required: true },
account_id: { type: String, required: true },
token: { type: String, required: true },
url: { type: String, required: true },
name_inbox: { type: String, required: true },
sign_msg: { type: Boolean, required: true },
number: { type: String, required: true },
});
export const ChatwootModel = dbserver?.model(
ChatwootRaw.name,
chatwootSchema,
'chatwoot',
);
export type IChatwootModel = typeof ChatwootModel;

View File

@@ -3,3 +3,5 @@ export * from './contact.model';
export * from './message.model';
export * from './auth.model';
export * from './webhook.model';
export * from './chatwoot.model';
export * from './settings.model';

View File

@@ -50,6 +50,7 @@ export class MessageUpdateRaw {
datetime?: number;
status?: wa.StatusMessage;
owner: string;
pollUpdates?: any;
}
const messageUpdateSchema = new Schema<MessageUpdateRaw>({

View File

@@ -0,0 +1,23 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../db/db.connect';
export class SettingsRaw {
_id?: string;
reject_call?: boolean;
msg_call?: string;
groups_ignore?: boolean;
}
const settingsSchema = new Schema<SettingsRaw>({
_id: { type: String, _id: true },
reject_call: { type: Boolean, required: true },
msg_call: { type: String, required: true },
groups_ignore: { type: Boolean, required: true },
});
export const SettingsModel = dbserver?.model(
SettingsRaw.name,
settingsSchema,
'settings',
);
export type ISettingsModel = typeof SettingsModel;

View File

@@ -5,12 +5,16 @@ export class WebhookRaw {
_id?: string;
url?: string;
enabled?: boolean;
events?: string[];
webhook_by_events?: boolean;
}
const webhookSchema = new Schema<WebhookRaw>({
_id: { type: String, _id: true },
url: { type: String, required: true },
enabled: { type: Boolean, required: true },
events: { type: [String], required: true },
webhook_by_events: { type: Boolean, required: true },
});
export const WebhookModel = dbserver?.model(WebhookRaw.name, webhookSchema, 'webhook');

View File

@@ -1,9 +1,10 @@
import { join } from 'path';
import { Auth, ConfigService } from '../../config/env.config';
import { Auth, ConfigService, Database } from '../../config/env.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { IAuthModel, AuthRaw } from '../models';
import { readFileSync } from 'fs';
import { AUTH_DIR } from '../../config/path.config';
import { Logger } from '../../config/logger.config';
export class AuthRepository extends Repository {
constructor(
@@ -15,24 +16,35 @@ export class AuthRepository extends Repository {
}
private readonly auth: Auth;
private readonly logger = new Logger('AuthRepository');
public async create(data: AuthRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating auth');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving auth to db');
const insert = await this.authModel.replaceOne(
{ _id: instance },
{ ...data },
{ upsert: true },
);
this.logger.verbose('auth saved to db: ' + insert.modifiedCount + ' auth');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving auth to store');
this.writeStore<AuthRaw>({
path: join(AUTH_DIR, this.auth.TYPE),
fileName: instance,
data,
});
this.logger.verbose(
'auth saved to store in path: ' + join(AUTH_DIR, this.auth.TYPE) + '/' + instance,
);
this.logger.verbose('auth created');
return { insertCount: 1 };
} catch (error) {
return { error } as any;
@@ -41,10 +53,14 @@ export class AuthRepository extends Repository {
public async find(instance: string): Promise<AuthRaw> {
try {
this.logger.verbose('finding auth');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding auth in db');
return await this.authModel.findOne({ _id: instance });
}
this.logger.verbose('finding auth in store');
return JSON.parse(
readFileSync(join(AUTH_DIR, this.auth.TYPE, instance + '.json'), {
encoding: 'utf-8',

View File

@@ -1,8 +1,9 @@
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { ConfigService, StoreConf } from '../../config/env.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { opendirSync, readFileSync, rmSync } from 'fs';
import { ChatRaw, IChatModel } from '../models';
import { Logger } from '../../config/logger.config';
export class ChatQuery {
where: ChatRaw;
@@ -16,26 +17,55 @@ export class ChatRepository extends Repository {
super(configService);
}
public async insert(data: ChatRaw[], saveDb = false): Promise<IInsert> {
private readonly logger = new Logger('ChatRepository');
public async insert(
data: ChatRaw[],
instanceName: string,
saveDb = false,
): Promise<IInsert> {
this.logger.verbose('inserting chats');
if (data.length === 0) {
this.logger.verbose('no chats to insert');
return;
}
try {
this.logger.verbose('saving chats to store');
if (this.dbSettings.ENABLED && saveDb) {
this.logger.verbose('saving chats to db');
const insert = await this.chatModel.insertMany([...data]);
this.logger.verbose('chats saved to db: ' + insert.length + ' chats');
return { insertCount: insert.length };
}
data.forEach((chat) => {
this.writeStore<ChatRaw>({
path: join(this.storePath, 'chats', chat.owner),
fileName: chat.id,
data: chat,
});
});
this.logger.verbose('saving chats to store');
return { insertCount: data.length };
const store = this.configService.get<StoreConf>('STORE');
if (store.CHATS) {
this.logger.verbose('saving chats to store');
data.forEach((chat) => {
this.writeStore<ChatRaw>({
path: join(this.storePath, 'chats', instanceName),
fileName: chat.id,
data: chat,
});
this.logger.verbose(
'chats saved to store in path: ' +
join(this.storePath, 'chats', instanceName) +
'/' +
chat.id,
);
});
this.logger.verbose('chats saved to store');
return { insertCount: data.length };
}
this.logger.verbose('chats not saved to store');
return { insertCount: 0 };
} catch (error) {
return error;
} finally {
@@ -45,10 +75,14 @@ export class ChatRepository extends Repository {
public async find(query: ChatQuery): Promise<ChatRaw[]> {
try {
this.logger.verbose('finding chats');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding chats in db');
return await this.chatModel.find({ owner: query.where.owner });
}
this.logger.verbose('finding chats in store');
const chats: ChatRaw[] = [];
const openDir = opendirSync(join(this.storePath, 'chats', query.where.owner));
for await (const dirent of openDir) {
@@ -64,6 +98,7 @@ export class ChatRepository extends Repository {
}
}
this.logger.verbose('chats found in store: ' + chats.length + ' chats');
return chats;
} catch (error) {
return [];
@@ -72,10 +107,13 @@ export class ChatRepository extends Repository {
public async delete(query: ChatQuery) {
try {
this.logger.verbose('deleting chats');
if (this.dbSettings.ENABLED) {
this.logger.verbose('deleting chats in db');
return await this.chatModel.deleteOne({ ...query.where });
}
this.logger.verbose('deleting chats in store');
rmSync(join(this.storePath, 'chats', query.where.owner, query.where.id + '.josn'), {
force: true,
recursive: true,

View File

@@ -0,0 +1,75 @@
import { IInsert, Repository } from '../abstract/abstract.repository';
import { ConfigService } from '../../config/env.config';
import { join } from 'path';
import { readFileSync } from 'fs';
import { IChatwootModel, ChatwootRaw } from '../models';
import { Logger } from '../../config/logger.config';
export class ChatwootRepository extends Repository {
constructor(
private readonly chatwootModel: IChatwootModel,
private readonly configService: ConfigService,
) {
super(configService);
}
private readonly logger = new Logger('ChatwootRepository');
public async create(data: ChatwootRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating chatwoot');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving chatwoot to db');
const insert = await this.chatwootModel.replaceOne(
{ _id: instance },
{ ...data },
{ upsert: true },
);
this.logger.verbose(
'chatwoot saved to db: ' + insert.modifiedCount + ' chatwoot',
);
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving chatwoot to store');
this.writeStore<ChatwootRaw>({
path: join(this.storePath, 'chatwoot'),
fileName: instance,
data,
});
this.logger.verbose(
'chatwoot saved to store in path: ' +
join(this.storePath, 'chatwoot') +
'/' +
instance,
);
this.logger.verbose('chatwoot created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<ChatwootRaw> {
try {
this.logger.verbose('finding chatwoot');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding chatwoot in db');
return await this.chatwootModel.findOne({ _id: instance });
}
this.logger.verbose('finding chatwoot in store');
return JSON.parse(
readFileSync(join(this.storePath, 'chatwoot', instance + '.json'), {
encoding: 'utf-8',
}),
) as ChatwootRaw;
} catch (error) {
return {};
}
}
}

View File

@@ -1,8 +1,9 @@
import { opendirSync, readFileSync } from 'fs';
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { ConfigService, StoreConf } from '../../config/env.config';
import { ContactRaw, IContactModel } from '../models';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { Logger } from '../../config/logger.config';
export class ContactQuery {
where: ContactRaw;
@@ -16,26 +17,122 @@ export class ContactRepository extends Repository {
super(configService);
}
public async insert(data: ContactRaw[], saveDb = false): Promise<IInsert> {
private readonly logger = new Logger('ContactRepository');
public async insert(
data: ContactRaw[],
instanceName: string,
saveDb = false,
): Promise<IInsert> {
this.logger.verbose('inserting contacts');
if (data.length === 0) {
this.logger.verbose('no contacts to insert');
return;
}
try {
if (this.dbSettings.ENABLED && saveDb) {
this.logger.verbose('saving contacts to db');
const insert = await this.contactModel.insertMany([...data]);
this.logger.verbose('contacts saved to db: ' + insert.length + ' contacts');
return { insertCount: insert.length };
}
data.forEach((contact) => {
this.writeStore({
path: join(this.storePath, 'contacts', contact.owner),
fileName: contact.id,
data: contact,
});
});
this.logger.verbose('saving contacts to store');
return { insertCount: data.length };
const store = this.configService.get<StoreConf>('STORE');
if (store.CONTACTS) {
this.logger.verbose('saving contacts to store');
data.forEach((contact) => {
this.writeStore({
path: join(this.storePath, 'contacts', instanceName),
fileName: contact.id,
data: contact,
});
this.logger.verbose(
'contacts saved to store in path: ' +
join(this.storePath, 'contacts', instanceName) +
'/' +
contact.id,
);
});
this.logger.verbose('contacts saved to store: ' + data.length + ' contacts');
return { insertCount: data.length };
}
this.logger.verbose('contacts not saved');
return { insertCount: 0 };
} catch (error) {
return error;
} finally {
data = undefined;
}
}
public async update(
data: ContactRaw[],
instanceName: string,
saveDb = false,
): Promise<IInsert> {
try {
this.logger.verbose('updating contacts');
if (data.length === 0) {
this.logger.verbose('no contacts to update');
return;
}
if (this.dbSettings.ENABLED && saveDb) {
this.logger.verbose('updating contacts in db');
const contacts = data.map((contact) => {
return {
updateOne: {
filter: { id: contact.id },
update: { ...contact },
upsert: true,
},
};
});
const { nModified } = await this.contactModel.bulkWrite(contacts);
this.logger.verbose('contacts updated in db: ' + nModified + ' contacts');
return { insertCount: nModified };
}
this.logger.verbose('updating contacts in store');
const store = this.configService.get<StoreConf>('STORE');
if (store.CONTACTS) {
this.logger.verbose('updating contacts in store');
data.forEach((contact) => {
this.writeStore({
path: join(this.storePath, 'contacts', instanceName),
fileName: contact.id,
data: contact,
});
this.logger.verbose(
'contacts updated in store in path: ' +
join(this.storePath, 'contacts', instanceName) +
'/' +
contact.id,
);
});
this.logger.verbose('contacts updated in store: ' + data.length + ' contacts');
return { insertCount: data.length };
}
this.logger.verbose('contacts not updated');
return { insertCount: 0 };
} catch (error) {
return error;
} finally {
@@ -45,11 +142,16 @@ export class ContactRepository extends Repository {
public async find(query: ContactQuery): Promise<ContactRaw[]> {
try {
this.logger.verbose('finding contacts');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding contacts in db');
return await this.contactModel.find({ ...query.where });
}
this.logger.verbose('finding contacts in store');
const contacts: ContactRaw[] = [];
if (query?.where?.id) {
this.logger.verbose('finding contacts in store by id');
contacts.push(
JSON.parse(
readFileSync(
@@ -64,6 +166,8 @@ export class ContactRepository extends Repository {
),
);
} else {
this.logger.verbose('finding contacts in store by owner');
const openDir = opendirSync(join(this.storePath, 'contacts', query.where.owner), {
encoding: 'utf-8',
});
@@ -80,6 +184,8 @@ export class ContactRepository extends Repository {
}
}
}
this.logger.verbose('contacts found in store: ' + contacts.length + ' contacts');
return contacts;
} catch (error) {
return [];

View File

@@ -1,8 +1,9 @@
import { ConfigService } from '../../config/env.config';
import { ConfigService, StoreConf } from '../../config/env.config';
import { join } from 'path';
import { IMessageModel, MessageRaw } from '../models';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { opendirSync, readFileSync } from 'fs';
import { Logger } from '../../config/logger.config';
export class MessageQuery {
where: MessageRaw;
@@ -17,13 +18,23 @@ export class MessageRepository extends Repository {
super(configService);
}
public async insert(data: MessageRaw[], saveDb = false): Promise<IInsert> {
private readonly logger = new Logger('MessageRepository');
public async insert(
data: MessageRaw[],
instanceName: string,
saveDb = false,
): Promise<IInsert> {
this.logger.verbose('inserting messages');
if (!Array.isArray(data) || data.length === 0) {
this.logger.verbose('no messages to insert');
return;
}
try {
if (this.dbSettings.ENABLED && saveDb) {
this.logger.verbose('saving messages to db');
const cleanedData = data.map((obj) => {
const cleanedObj = { ...obj };
if ('extendedTextMessage' in obj.message) {
@@ -44,21 +55,37 @@ export class MessageRepository extends Repository {
});
const insert = await this.messageModel.insertMany([...cleanedData]);
this.logger.verbose('messages saved to db: ' + insert.length + ' messages');
return { insertCount: insert.length };
}
if (saveDb) {
data.forEach((msg) =>
this.writeStore<MessageRaw>({
path: join(this.storePath, 'messages', msg.owner),
fileName: msg.key.id,
data: msg,
}),
);
this.logger.verbose('saving messages to store');
const store = this.configService.get<StoreConf>('STORE');
if (store.MESSAGES) {
this.logger.verbose('saving messages to store');
data.forEach((message) => {
this.writeStore({
path: join(this.storePath, 'messages', instanceName),
fileName: message.key.id,
data: message,
});
this.logger.verbose(
'messages saved to store in path: ' +
join(this.storePath, 'messages', instanceName) +
'/' +
message.key.id,
);
});
this.logger.verbose('messages saved to store: ' + data.length + ' messages');
return { insertCount: data.length };
}
this.logger.verbose('messages not saved to store');
return { insertCount: 0 };
} catch (error) {
console.log('ERROR: ', error);
@@ -70,21 +97,26 @@ export class MessageRepository extends Repository {
public async find(query: MessageQuery) {
try {
this.logger.verbose('finding messages');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding messages in db');
if (query?.where?.key) {
for (const [k, v] of Object.entries(query.where.key)) {
query.where['key.' + k] = v;
}
delete query?.where?.key;
}
return await this.messageModel
.find({ ...query.where })
.sort({ messageTimestamp: -1 })
.limit(query?.limit ?? 0);
}
this.logger.verbose('finding messages in store');
const messages: MessageRaw[] = [];
if (query?.where?.key?.id) {
this.logger.verbose('finding messages in store by id');
messages.push(
JSON.parse(
readFileSync(
@@ -99,6 +131,7 @@ export class MessageRepository extends Repository {
),
);
} else {
this.logger.verbose('finding messages in store by owner');
const openDir = opendirSync(join(this.storePath, 'messages', query.where.owner), {
encoding: 'utf-8',
});
@@ -117,6 +150,7 @@ export class MessageRepository extends Repository {
}
}
this.logger.verbose('messages found in store: ' + messages.length + ' messages');
return messages
.sort((x, y) => {
return (y.messageTimestamp as number) - (x.messageTimestamp as number);

View File

@@ -1,8 +1,9 @@
import { ConfigService } from '../../config/env.config';
import { ConfigService, StoreConf } from '../../config/env.config';
import { IMessageUpModel, MessageUpdateRaw } from '../models';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { join } from 'path';
import { opendirSync, readFileSync } from 'fs';
import { Logger } from '../../config/logger.config';
export class MessageUpQuery {
where: MessageUpdateRaw;
@@ -17,24 +18,55 @@ export class MessageUpRepository extends Repository {
super(configService);
}
public async insert(data: MessageUpdateRaw[], saveDb?: boolean): Promise<IInsert> {
private readonly logger = new Logger('MessageUpRepository');
public async insert(
data: MessageUpdateRaw[],
instanceName: string,
saveDb?: boolean,
): Promise<IInsert> {
this.logger.verbose('inserting message up');
if (data.length === 0) {
this.logger.verbose('no message up to insert');
return;
}
try {
if (this.dbSettings.ENABLED && saveDb) {
this.logger.verbose('saving message up to db');
const insert = await this.messageUpModel.insertMany([...data]);
this.logger.verbose('message up saved to db: ' + insert.length + ' message up');
return { insertCount: insert.length };
}
data.forEach((update) => {
this.writeStore<MessageUpdateRaw>({
path: join(this.storePath, 'message-up', update.owner),
fileName: update.id,
data: update,
this.logger.verbose('saving message up to store');
const store = this.configService.get<StoreConf>('STORE');
if (store.MESSAGE_UP) {
this.logger.verbose('saving message up to store');
data.forEach((update) => {
this.writeStore<MessageUpdateRaw>({
path: join(this.storePath, 'message-up', instanceName),
fileName: update.id,
data: update,
});
this.logger.verbose(
'message up saved to store in path: ' +
join(this.storePath, 'message-up', instanceName) +
'/' +
update.id,
);
});
});
this.logger.verbose('message up saved to store: ' + data.length + ' message up');
return { insertCount: data.length };
}
this.logger.verbose('message up not saved to store');
return { insertCount: 0 };
} catch (error) {
return error;
}
@@ -42,15 +74,21 @@ export class MessageUpRepository extends Repository {
public async find(query: MessageUpQuery) {
try {
this.logger.verbose('finding message up');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding message up in db');
return await this.messageUpModel
.find({ ...query.where })
.sort({ datetime: -1 })
.limit(query?.limit ?? 0);
}
this.logger.verbose('finding message up in store');
const messageUpdate: MessageUpdateRaw[] = [];
if (query?.where?.id) {
this.logger.verbose('finding message up in store by id');
messageUpdate.push(
JSON.parse(
readFileSync(
@@ -65,6 +103,8 @@ export class MessageUpRepository extends Repository {
),
);
} else {
this.logger.verbose('finding message up in store by owner');
const openDir = opendirSync(
join(this.storePath, 'message-up', query.where.owner),
{ encoding: 'utf-8' },
@@ -84,6 +124,9 @@ export class MessageUpRepository extends Repository {
}
}
this.logger.verbose(
'message up found in store: ' + messageUpdate.length + ' message up',
);
return messageUpdate
.sort((x, y) => {
return y.datetime - x.datetime;

View File

@@ -4,8 +4,14 @@ import { ContactRepository } from './contact.repository';
import { MessageUpRepository } from './messageUp.repository';
import { MongoClient } from 'mongodb';
import { WebhookRepository } from './webhook.repository';
import { AuthRepository } from './auth.repository';
import { ChatwootRepository } from './chatwoot.repository';
import { SettingsRepository } from './settings.repository';
import { AuthRepository } from './auth.repository';
import { Auth, ConfigService, Database } from '../../config/env.config';
import { join } from 'path';
import fs from 'fs';
import { Logger } from '../../config/logger.config';
export class RepositoryBroker {
constructor(
public readonly message: MessageRepository,
@@ -13,15 +19,98 @@ export class RepositoryBroker {
public readonly contact: ContactRepository,
public readonly messageUpdate: MessageUpRepository,
public readonly webhook: WebhookRepository,
public readonly chatwoot: ChatwootRepository,
public readonly settings: SettingsRepository,
public readonly auth: AuthRepository,
private configService: ConfigService,
dbServer?: MongoClient,
) {
this.dbClient = dbServer;
this.__init_repo_without_db__();
}
private dbClient?: MongoClient;
private readonly logger = new Logger('RepositoryBroker');
public get dbServer() {
return this.dbClient;
}
private __init_repo_without_db__() {
this.logger.verbose('initializing repository without db');
if (!this.configService.get<Database>('DATABASE').ENABLED) {
const storePath = join(process.cwd(), 'store');
this.logger.verbose('creating store path: ' + storePath);
try {
const authDir = join(
storePath,
'auth',
this.configService.get<Auth>('AUTHENTICATION').TYPE,
);
const chatsDir = join(storePath, 'chats');
const contactsDir = join(storePath, 'contacts');
const messagesDir = join(storePath, 'messages');
const messageUpDir = join(storePath, 'message-up');
const webhookDir = join(storePath, 'webhook');
const chatwootDir = join(storePath, 'chatwoot');
const settingsDir = join(storePath, 'settings');
const tempDir = join(storePath, 'temp');
if (!fs.existsSync(authDir)) {
this.logger.verbose('creating auth dir: ' + authDir);
fs.mkdirSync(authDir, { recursive: true });
}
if (!fs.existsSync(chatsDir)) {
this.logger.verbose('creating chats dir: ' + chatsDir);
fs.mkdirSync(chatsDir, { recursive: true });
}
if (!fs.existsSync(contactsDir)) {
this.logger.verbose('creating contacts dir: ' + contactsDir);
fs.mkdirSync(contactsDir, { recursive: true });
}
if (!fs.existsSync(messagesDir)) {
this.logger.verbose('creating messages dir: ' + messagesDir);
fs.mkdirSync(messagesDir, { recursive: true });
}
if (!fs.existsSync(messageUpDir)) {
this.logger.verbose('creating message-up dir: ' + messageUpDir);
fs.mkdirSync(messageUpDir, { recursive: true });
}
if (!fs.existsSync(webhookDir)) {
this.logger.verbose('creating webhook dir: ' + webhookDir);
fs.mkdirSync(webhookDir, { recursive: true });
}
if (!fs.existsSync(chatwootDir)) {
this.logger.verbose('creating chatwoot dir: ' + chatwootDir);
fs.mkdirSync(chatwootDir, { recursive: true });
}
if (!fs.existsSync(settingsDir)) {
this.logger.verbose('creating settings dir: ' + settingsDir);
fs.mkdirSync(settingsDir, { recursive: true });
}
if (!fs.existsSync(tempDir)) {
this.logger.verbose('creating temp dir: ' + tempDir);
fs.mkdirSync(tempDir, { recursive: true });
}
} catch (error) {
this.logger.error(error);
}
} else {
const storePath = join(process.cwd(), 'store');
this.logger.verbose('creating store path: ' + storePath);
const tempDir = join(storePath, 'temp');
if (!fs.existsSync(tempDir)) {
this.logger.verbose('creating temp dir: ' + tempDir);
fs.mkdirSync(tempDir, { recursive: true });
}
try {
} catch (error) {
this.logger.error(error);
}
}
}
}

View File

@@ -0,0 +1,75 @@
import { IInsert, Repository } from '../abstract/abstract.repository';
import { ConfigService } from '../../config/env.config';
import { join } from 'path';
import { readFileSync } from 'fs';
import { ISettingsModel, SettingsRaw } from '../models';
import { Logger } from '../../config/logger.config';
export class SettingsRepository extends Repository {
constructor(
private readonly settingsModel: ISettingsModel,
private readonly configService: ConfigService,
) {
super(configService);
}
private readonly logger = new Logger('SettingsRepository');
public async create(data: SettingsRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating settings');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving settings to db');
const insert = await this.settingsModel.replaceOne(
{ _id: instance },
{ ...data },
{ upsert: true },
);
this.logger.verbose(
'settings saved to db: ' + insert.modifiedCount + ' settings',
);
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving settings to store');
this.writeStore<SettingsRaw>({
path: join(this.storePath, 'settings'),
fileName: instance,
data,
});
this.logger.verbose(
'settings saved to store in path: ' +
join(this.storePath, 'settings') +
'/' +
instance,
);
this.logger.verbose('settings created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<SettingsRaw> {
try {
this.logger.verbose('finding settings');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding settings in db');
return await this.settingsModel.findOne({ _id: instance });
}
this.logger.verbose('finding settings in store');
return JSON.parse(
readFileSync(join(this.storePath, 'settings', instance + '.json'), {
encoding: 'utf-8',
}),
) as SettingsRaw;
} catch (error) {
return {};
}
}
}

View File

@@ -3,6 +3,7 @@ import { ConfigService } from '../../config/env.config';
import { join } from 'path';
import { readFileSync } from 'fs';
import { IWebhookModel, WebhookRaw } from '../models';
import { Logger } from '../../config/logger.config';
export class WebhookRepository extends Repository {
constructor(
@@ -12,23 +13,39 @@ export class WebhookRepository extends Repository {
super(configService);
}
private readonly logger = new Logger('WebhookRepository');
public async create(data: WebhookRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating webhook');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving webhook to db');
const insert = await this.webhookModel.replaceOne(
{ _id: instance },
{ ...data },
{ upsert: true },
);
this.logger.verbose('webhook saved to db: ' + insert.modifiedCount + ' webhook');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving webhook to store');
this.writeStore<WebhookRaw>({
path: join(this.storePath, 'webhook'),
fileName: instance,
data,
});
this.logger.verbose(
'webhook saved to store in path: ' +
join(this.storePath, 'webhook') +
'/' +
instance,
);
this.logger.verbose('webhook created');
return { insertCount: 1 };
} catch (error) {
return error;
@@ -37,10 +54,13 @@ export class WebhookRepository extends Repository {
public async find(instance: string): Promise<WebhookRaw> {
try {
this.logger.verbose('finding webhook');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding webhook in db');
return await this.webhookModel.findOne({ _id: instance });
}
this.logger.verbose('finding webhook in store');
return JSON.parse(
readFileSync(join(this.storePath, 'webhook', instance + '.json'), {
encoding: 'utf-8',

View File

@@ -5,8 +5,10 @@ import {
deleteMessageSchema,
messageUpSchema,
messageValidateSchema,
privacySettingsSchema,
profileNameSchema,
profilePictureSchema,
profileSchema,
profileStatusSchema,
readMessageSchema,
whatsappNumberSchema,
@@ -15,11 +17,13 @@ import {
ArchiveChatDto,
DeleteMessage,
NumberDto,
PrivacySettingDto,
ProfileNameDto,
ProfilePictureDto,
ProfileStatusDto,
ReadMessageDto,
WhatsAppNumberDto,
getBase64FromMediaMessageDto,
} from '../dto/chat.dto';
import { ContactQuery } from '../repository/contact.repository';
import { MessageQuery } from '../repository/message.repository';
@@ -27,14 +31,24 @@ import { chatController } from '../whatsapp.module';
import { RouterBroker } from '../abstract/abstract.router';
import { HttpStatus } from './index.router';
import { MessageUpQuery } from '../repository/messageUp.repository';
import { proto } from '@evolution/base';
import { proto } from '@whiskeysockets/baileys';
import { InstanceDto } from '../dto/instance.dto';
import { Logger } from '../../config/logger.config';
const logger = new Logger('ChatRouter');
export class ChatRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('whatsappNumbers'), ...guards, async (req, res) => {
logger.verbose('request received in whatsappNumbers');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<WhatsAppNumberDto>({
request: req,
schema: whatsappNumberSchema,
@@ -45,6 +59,13 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => {
logger.verbose('request received in markMessageAsRead');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ReadMessageDto>({
request: req,
schema: readMessageSchema,
@@ -55,6 +76,13 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('archiveChat'), ...guards, async (req, res) => {
logger.verbose('request received in archiveChat');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ArchiveChatDto>({
request: req,
schema: archiveChatSchema,
@@ -68,6 +96,13 @@ export class ChatRouter extends RouterBroker {
this.routerPath('deleteMessageForEveryone'),
...guards,
async (req, res) => {
logger.verbose('request received in deleteMessageForEveryone');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<DeleteMessage>({
request: req,
schema: deleteMessageSchema,
@@ -79,6 +114,13 @@ export class ChatRouter extends RouterBroker {
},
)
.post(this.routerPath('fetchProfilePictureUrl'), ...guards, async (req, res) => {
logger.verbose('request received in fetchProfilePictureUrl');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<NumberDto>({
request: req,
schema: profilePictureSchema,
@@ -88,7 +130,31 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('fetchProfile'), ...guards, async (req, res) => {
logger.verbose('request received in fetchProfile');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<NumberDto>({
request: req,
schema: profileSchema,
ClassRef: NumberDto,
execute: (instance, data) => chatController.fetchProfile(instance, data),
});
return res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('findContacts'), ...guards, async (req, res) => {
logger.verbose('request received in findContacts');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ContactQuery>({
request: req,
schema: contactValidateSchema,
@@ -99,10 +165,17 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => {
const response = await this.dataValidate<proto.IWebMessageInfo>({
logger.verbose('request received in getBase64FromMediaMessage');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<getBase64FromMediaMessageDto>({
request: req,
schema: null,
ClassRef: Object,
ClassRef: getBase64FromMediaMessageDto,
execute: (instance, data) =>
chatController.getBase64FromMediaMessage(instance, data),
});
@@ -110,6 +183,13 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('findMessages'), ...guards, async (req, res) => {
logger.verbose('request received in findMessages');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<MessageQuery>({
request: req,
schema: messageValidateSchema,
@@ -120,6 +200,13 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('findStatusMessage'), ...guards, async (req, res) => {
logger.verbose('request received in findStatusMessage');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<MessageUpQuery>({
request: req,
schema: messageUpSchema,
@@ -130,6 +217,13 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('findChats'), ...guards, async (req, res) => {
logger.verbose('request received in findChats');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: null,
@@ -140,17 +234,67 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
// Profile routes
.post(this.routerPath('getBusinessProfile'), ...guards, async (req, res) => {
.get(this.routerPath('fetchPrivacySettings'), ...guards, async (req, res) => {
logger.verbose('request received in fetchPrivacySettings');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: null,
ClassRef: InstanceDto,
execute: (instance) => chatController.fetchPrivacySettings(instance),
});
return res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('updatePrivacySettings'), ...guards, async (req, res) => {
logger.verbose('request received in updatePrivacySettings');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<PrivacySettingDto>({
request: req,
schema: privacySettingsSchema,
ClassRef: PrivacySettingDto,
execute: (instance, data) =>
chatController.updatePrivacySettings(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('fetchBusinessProfile'), ...guards, async (req, res) => {
logger.verbose('request received in fetchBusinessProfile');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ProfilePictureDto>({
request: req,
schema: profilePictureSchema,
ClassRef: ProfilePictureDto,
execute: (instance, data) => chatController.getBusinessProfile(instance, data),
execute: (instance, data) =>
chatController.fetchBusinessProfile(instance, data),
});
return res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('updateProfileName'), ...guards, async (req, res) => {
logger.verbose('request received in updateProfileName');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ProfileNameDto>({
request: req,
schema: profileNameSchema,
@@ -161,6 +305,13 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('updateProfileStatus'), ...guards, async (req, res) => {
logger.verbose('request received in updateProfileStatus');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ProfileStatusDto>({
request: req,
schema: profileStatusSchema,
@@ -171,6 +322,13 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('updateProfilePicture'), ...guards, async (req, res) => {
logger.verbose('request received in updateProfilePicture');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ProfilePictureDto>({
request: req,
schema: profilePictureSchema,
@@ -182,6 +340,13 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.delete(this.routerPath('removeProfilePicture'), ...guards, async (req, res) => {
logger.verbose('request received in removeProfilePicture');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ProfilePictureDto>({
request: req,
schema: profilePictureSchema,

View File

@@ -0,0 +1,68 @@
import { RequestHandler, Router } from 'express';
import { instanceNameSchema, chatwootSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { chatwootController } from '../whatsapp.module';
import { ChatwootService } from '../services/chatwoot.service';
import { HttpStatus } from './index.router';
import { Logger } from '../../config/logger.config';
const logger = new Logger('ChatwootRouter');
export class ChatwootRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setChatwoot');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ChatwootDto>({
request: req,
schema: chatwootSchema,
ClassRef: ChatwootDto,
execute: (instance, data) => chatwootController.createChatwoot(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findChatwoot');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance) => chatwootController.findChatwoot(instance),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('webhook'), async (req, res) => {
logger.verbose('request received in findChatwoot');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance, data) => chatwootController.receiveWebhook(instance, data),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@@ -5,8 +5,12 @@ import {
updateParticipantsSchema,
updateSettingsSchema,
toggleEphemeralSchema,
updateGroupPicture,
updateGroupPictureSchema,
updateGroupSubjectSchema,
updateGroupDescriptionSchema,
groupInviteSchema,
groupSendInviteSchema,
getParticipantsSchema,
} from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import {
@@ -14,18 +18,31 @@ import {
GroupInvite,
GroupJid,
GroupPictureDto,
GroupSubjectDto,
GroupDescriptionDto,
GroupUpdateParticipantDto,
GroupUpdateSettingDto,
GroupToggleEphemeralDto,
GroupSendInvite,
GetParticipant,
} from '../dto/group.dto';
import { groupController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
import { Logger } from '../../config/logger.config';
const logger = new Logger('GroupRouter');
export class GroupRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('create'), ...guards, async (req, res) => {
logger.verbose('request received in createGroup');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<CreateGroupDto>({
request: req,
schema: createGroupSchema,
@@ -35,17 +52,63 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('updateGroupSubject'), ...guards, async (req, res) => {
logger.verbose('request received in updateGroupSubject');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupSubjectDto>({
request: req,
schema: updateGroupSubjectSchema,
ClassRef: GroupSubjectDto,
execute: (instance, data) => groupController.updateGroupSubject(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('updateGroupPicture'), ...guards, async (req, res) => {
logger.verbose('request received in updateGroupPicture');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupPictureDto>({
request: req,
schema: updateGroupPicture,
schema: updateGroupPictureSchema,
ClassRef: GroupPictureDto,
execute: (instance, data) => groupController.updateGroupPicture(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('updateGroupDescription'), ...guards, async (req, res) => {
logger.verbose('request received in updateGroupDescription');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupDescriptionDto>({
request: req,
schema: updateGroupDescriptionSchema,
ClassRef: GroupDescriptionDto,
execute: (instance, data) =>
groupController.updateGroupDescription(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('findGroupInfos'), ...guards, async (req, res) => {
logger.verbose('request received in findGroupInfos');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupJid>({
request: req,
schema: groupJidSchema,
@@ -55,7 +118,29 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetchAllGroups'), ...guards, async (req, res) => {
logger.verbose('request received in fetchAllGroups');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.getParticipantsValidate<GetParticipant>({
request: req,
schema: getParticipantsSchema,
ClassRef: GetParticipant,
execute: (instance, data) => groupController.fetchAllGroups(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('participants'), ...guards, async (req, res) => {
logger.verbose('request received in participants');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupJid>({
request: req,
schema: groupJidSchema,
@@ -66,6 +151,12 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('inviteCode'), ...guards, async (req, res) => {
logger.verbose('request received in inviteCode');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupJid>({
request: req,
schema: groupJidSchema,
@@ -76,6 +167,12 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('inviteInfo'), ...guards, async (req, res) => {
logger.verbose('request received in inviteInfo');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.inviteCodeValidate<GroupInvite>({
request: req,
schema: groupInviteSchema,
@@ -85,7 +182,29 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('sendInvite'), ...guards, async (req, res) => {
logger.verbose('request received in sendInvite');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupNoValidate<GroupSendInvite>({
request: req,
schema: groupSendInviteSchema,
ClassRef: GroupSendInvite,
execute: (instance, data) => groupController.sendInvite(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.put(this.routerPath('revokeInviteCode'), ...guards, async (req, res) => {
logger.verbose('request received in revokeInviteCode');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupJid>({
request: req,
schema: groupJidSchema,
@@ -96,6 +215,12 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('updateParticipant'), ...guards, async (req, res) => {
logger.verbose('request received in updateParticipant');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupUpdateParticipantDto>({
request: req,
schema: updateParticipantsSchema,
@@ -106,6 +231,12 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('updateSetting'), ...guards, async (req, res) => {
logger.verbose('request received in updateSetting');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupUpdateSettingDto>({
request: req,
schema: updateSettingsSchema,
@@ -116,6 +247,12 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('toggleEphemeral'), ...guards, async (req, res) => {
logger.verbose('request received in toggleEphemeral');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupToggleEphemeralDto>({
request: req,
schema: toggleEphemeralSchema,
@@ -126,6 +263,12 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.CREATED).json(response);
})
.delete(this.routerPath('leaveGroup'), ...guards, async (req, res) => {
logger.verbose('request received in leaveGroup');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.groupValidate<GroupJid>({
request: req,
schema: {},

View File

@@ -8,6 +8,9 @@ import { InstanceRouter } from './instance.router';
import { MessageRouter } from './sendMessage.router';
import { ViewsRouter } from './view.router';
import { WebhookRouter } from './webhook.router';
import { ChatwootRouter } from './chatwoot.router';
import fs from 'fs';
import { SettingsRouter } from './settings.router';
enum HttpStatus {
OK = 200,
@@ -23,7 +26,16 @@ const router = Router();
const authType = configService.get<Auth>('AUTHENTICATION').TYPE;
const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard[authType]];
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
router
.get('/', (req, res) => {
res.status(HttpStatus.OK).json({
status: HttpStatus.OK,
message: 'Welcome to the Evolution API, it is working!',
version: packageJson.version,
});
})
.use(
'/instance',
new InstanceRouter(configService, ...guards).router,
@@ -32,6 +44,8 @@ router
.use('/message', new MessageRouter(...guards).router)
.use('/chat', new ChatRouter(...guards).router)
.use('/group', new GroupRouter(...guards).router)
.use('/webhook', new WebhookRouter(...guards).router);
.use('/webhook', new WebhookRouter(...guards).router)
.use('/chatwoot', new ChatwootRouter(...guards).router)
.use('/settings', new SettingsRouter(...guards).router);
export { router, HttpStatus };

View File

@@ -7,7 +7,9 @@ import { HttpStatus } from './index.router';
import { OldToken } from '../services/auth.service';
import { Auth, ConfigService, Database } from '../../config/env.config';
import { dbserver } from '../../db/db.connect';
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
import { Logger } from '../../config/logger.config';
const logger = new Logger('InstanceRouter');
export class InstanceRouter extends RouterBroker {
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) {
@@ -15,6 +17,13 @@ export class InstanceRouter extends RouterBroker {
const auth = configService.get<Auth>('AUTHENTICATION');
this.router
.post('/create', ...guards, async (req, res) => {
logger.verbose('request received in createInstance');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
@@ -24,7 +33,29 @@ export class InstanceRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('restart'), ...guards, async (req, res) => {
logger.verbose('request received in restartInstance');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance) => instanceController.restartInstance(instance),
});
return res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('connect'), ...guards, async (req, res) => {
logger.verbose('request received in connectInstance');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
@@ -35,6 +66,12 @@ export class InstanceRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('connectionState'), ...guards, async (req, res) => {
logger.verbose('request received in connectionState');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
@@ -45,6 +82,12 @@ export class InstanceRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetchInstances', false), ...guards, async (req, res) => {
logger.verbose('request received in fetchInstances');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: null,
@@ -55,6 +98,12 @@ export class InstanceRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.delete(this.routerPath('logout'), ...guards, async (req, res) => {
logger.verbose('request received in logoutInstances');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
@@ -65,6 +114,12 @@ export class InstanceRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.delete(this.routerPath('delete'), ...guards, async (req, res) => {
logger.verbose('request received in deleteInstances');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
@@ -77,6 +132,12 @@ export class InstanceRouter extends RouterBroker {
if (auth.TYPE === 'jwt') {
this.router.put('/refreshToken', async (req, res) => {
logger.verbose('request received in refreshToken');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<OldToken>({
request: req,
schema: oldTokenSchema,
@@ -89,6 +150,12 @@ export class InstanceRouter extends RouterBroker {
}
this.router.delete('/deleteDatabase', async (req, res) => {
logger.verbose('request received in deleteDatabase');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const db = this.configService.get<Database>('DATABASE');
if (db.ENABLED) {
try {

View File

@@ -3,35 +3,46 @@ import {
audioMessageSchema,
buttonMessageSchema,
contactMessageSchema,
linkPreviewSchema,
listMessageSchema,
locationMessageSchema,
mediaMessageSchema,
pollMessageSchema,
reactionMessageSchema,
statusMessageSchema,
stickerMessageSchema,
textMessageSchema,
} from '../../validate/validate.schema';
import {
SendAudioDto,
SendButtonDto,
SendContactDto,
SendLinkPreviewDto,
SendListDto,
SendLocationDto,
SendMediaDto,
SendPollDto,
SendReactionDto,
SendStatusDto,
SendStickerDto,
SendTextDto,
} from '../dto/sendMessage.dto';
import { sendMessageController } from '../whatsapp.module';
import { RouterBroker } from '../abstract/abstract.router';
import { HttpStatus } from './index.router';
import { Logger } from '../../config/logger.config';
const logger = new Logger('MessageRouter');
export class MessageRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('sendText'), ...guards, async (req, res) => {
logger.verbose('request received in sendText');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendTextDto>({
request: req,
schema: textMessageSchema,
@@ -42,6 +53,12 @@ export class MessageRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendMedia'), ...guards, async (req, res) => {
logger.verbose('request received in sendMedia');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendMediaDto>({
request: req,
schema: mediaMessageSchema,
@@ -52,6 +69,12 @@ export class MessageRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendWhatsAppAudio'), ...guards, async (req, res) => {
logger.verbose('request received in sendWhatsAppAudio');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendAudioDto>({
request: req,
schema: audioMessageSchema,
@@ -63,6 +86,12 @@ export class MessageRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendButtons'), ...guards, async (req, res) => {
logger.verbose('request received in sendButtons');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendButtonDto>({
request: req,
schema: buttonMessageSchema,
@@ -73,6 +102,12 @@ export class MessageRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendLocation'), ...guards, async (req, res) => {
logger.verbose('request received in sendLocation');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendLocationDto>({
request: req,
schema: locationMessageSchema,
@@ -83,6 +118,12 @@ export class MessageRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendList'), ...guards, async (req, res) => {
logger.verbose('request received in sendList');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendListDto>({
request: req,
schema: listMessageSchema,
@@ -93,6 +134,12 @@ export class MessageRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendContact'), ...guards, async (req, res) => {
logger.verbose('request received in sendContact');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendContactDto>({
request: req,
schema: contactMessageSchema,
@@ -103,6 +150,12 @@ export class MessageRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendReaction'), ...guards, async (req, res) => {
logger.verbose('request received in sendReaction');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendReactionDto>({
request: req,
schema: reactionMessageSchema,
@@ -113,6 +166,12 @@ export class MessageRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendPoll'), ...guards, async (req, res) => {
logger.verbose('request received in sendPoll');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendPollDto>({
request: req,
schema: pollMessageSchema,
@@ -122,13 +181,34 @@ export class MessageRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendLinkPreview'), ...guards, async (req, res) => {
const response = await this.dataValidate<SendLinkPreviewDto>({
.post(this.routerPath('sendStatus'), ...guards, async (req, res) => {
logger.verbose('request received in sendStatus');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendStatusDto>({
request: req,
schema: linkPreviewSchema,
ClassRef: SendLinkPreviewDto,
execute: (instance, data) =>
sendMessageController.sendLinkPreview(instance, data),
schema: statusMessageSchema,
ClassRef: SendStatusDto,
execute: (instance, data) => sendMessageController.sendStatus(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('sendSticker'), ...guards, async (req, res) => {
logger.verbose('request received in sendSticker');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SendStickerDto>({
request: req,
schema: stickerMessageSchema,
ClassRef: SendStickerDto,
execute: (instance, data) => sendMessageController.sendSticker(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);

View File

@@ -0,0 +1,52 @@
import { RequestHandler, Router } from 'express';
import { instanceNameSchema, settingsSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';
import { SettingsDto } from '../dto/settings.dto';
import { settingsController } from '../whatsapp.module';
import { SettingsService } from '../services/settings.service';
import { HttpStatus } from './index.router';
import { Logger } from '../../config/logger.config';
const logger = new Logger('SettingsRouter');
export class SettingsRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setSettings');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<SettingsDto>({
request: req,
schema: settingsSchema,
ClassRef: SettingsDto,
execute: (instance, data) => settingsController.createSettings(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findSettings');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance) => settingsController.findSettings(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@@ -5,12 +5,21 @@ import { InstanceDto } from '../dto/instance.dto';
import { WebhookDto } from '../dto/webhook.dto';
import { webhookController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
import { Logger } from '../../config/logger.config';
const logger = new Logger('WebhookRouter');
export class WebhookRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setWebhook');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<WebhookDto>({
request: req,
schema: webhookSchema,
@@ -21,6 +30,12 @@ export class WebhookRouter extends RouterBroker {
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findWebhook');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,

View File

@@ -43,8 +43,12 @@ export class AuthService {
{ expiresIn: jwtOpts.EXPIRIN_IN, encoding: 'utf8', subject: 'g-t' },
);
this.logger.verbose('JWT token created: ' + token);
const auth = await this.repository.auth.create({ jwt: token }, instance.instanceName);
this.logger.verbose('JWT token saved in database');
if (auth['error']) {
this.logger.error({
localError: AuthService.name + '.jwt',
@@ -56,14 +60,20 @@ export class AuthService {
return { jwt: token };
}
private async apikey(instance: InstanceDto) {
const apikey = v4().toUpperCase();
private async apikey(instance: InstanceDto, token?: string) {
const apikey = token ? token : v4().toUpperCase();
this.logger.verbose(
token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey,
);
const auth = await this.repository.auth.create({ apikey }, instance.instanceName);
this.logger.verbose('APIKEY saved in database');
if (auth['error']) {
this.logger.error({
localError: AuthService.name + '.jwt',
localError: AuthService.name + '.apikey',
error: auth['error'],
});
throw new BadRequestException('Authentication error', auth['error']?.toString());
@@ -72,43 +82,80 @@ export class AuthService {
return { apikey };
}
public async generateHash(instance: InstanceDto) {
public async checkDuplicateToken(token: string) {
const instances = await this.waMonitor.instanceInfo();
this.logger.verbose('checking duplicate token');
const instance = instances.find((instance) => instance.instance.apikey === token);
if (instance) {
throw new BadRequestException('Token already exists');
}
this.logger.verbose('available token');
return true;
}
public async generateHash(instance: InstanceDto, token?: string) {
const options = this.configService.get<Auth>('AUTHENTICATION');
return (await this[options.TYPE](instance)) as { jwt: string } | { apikey: string };
this.logger.verbose(
'generating hash ' + options.TYPE + ' to instance: ' + instance.instanceName,
);
return (await this[options.TYPE](instance, token)) as
| { jwt: string }
| { apikey: string };
}
public async refreshToken({ oldToken }: OldToken) {
this.logger.verbose('refreshing token');
if (!isJWT(oldToken)) {
throw new BadRequestException('Invalid "oldToken"');
}
try {
const jwtOpts = this.configService.get<Auth>('AUTHENTICATION').JWT;
this.logger.verbose('checking oldToken');
const decode = verify(oldToken, jwtOpts.SECRET, {
ignoreExpiration: true,
}) as Pick<JwtPayload, 'apiName' | 'instanceName' | 'tokenId'>;
this.logger.verbose('checking token in database');
const tokenStore = await this.repository.auth.find(decode.instanceName);
const decodeTokenStore = verify(tokenStore.jwt, jwtOpts.SECRET, {
ignoreExpiration: true,
}) as Pick<JwtPayload, 'apiName' | 'instanceName' | 'tokenId'>;
this.logger.verbose('checking tokenId');
if (decode.tokenId !== decodeTokenStore.tokenId) {
throw new BadRequestException('Invalid "oldToken"');
}
this.logger.verbose('generating new token');
const token = {
jwt: (await this.jwt({ instanceName: decode.instanceName })).jwt,
instanceName: decode.instanceName,
};
try {
this.logger.verbose('checking webhook');
const webhook = await this.repository.webhook.find(decode.instanceName);
if (
webhook?.enabled &&
this.configService.get<Webhook>('WEBHOOK').EVENTS.NEW_JWT_TOKEN
) {
this.logger.verbose('sending webhook');
const httpService = axios.create({ baseURL: webhook.url });
await httpService.post(
'',
@@ -124,6 +171,8 @@ export class AuthService {
this.logger.error(error);
}
this.logger.verbose('token refreshed');
return token;
} catch (error) {
this.logger.error({

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,43 @@
import { opendirSync, readdirSync, rmSync } from 'fs';
import { WAStartupService } from './whatsapp.service';
import { INSTANCE_DIR } from '../../config/path.config';
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
import EventEmitter2 from 'eventemitter2';
import { join } from 'path';
import { Logger } from '../../config/logger.config';
import { ConfigService, Database, DelInstance, Redis } from '../../config/env.config';
import {
Auth,
ConfigService,
Database,
DelInstance,
HttpServer,
Redis,
} from '../../config/env.config';
import { RepositoryBroker } from '../repository/repository.manager';
import { NotFoundException } from '../../exceptions';
import { Db } from 'mongodb';
import { RedisCache } from '../../db/redis.client';
import { initInstance } from '../whatsapp.module';
import { ValidationError } from 'class-validator';
import { execSync } from 'child_process';
import { dbserver } from '../../db/db.connect';
import mongoose from 'mongoose';
import {
AuthModel,
ChatwootModel,
ContactModel,
MessageModel,
MessageUpModel,
SettingsModel,
WebhookModel,
} from '../models';
export class WAMonitoringService {
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly configService: ConfigService,
private readonly repository: RepositoryBroker,
private readonly cache: RedisCache,
) {
this.logger.verbose('instance created');
this.removeInstance();
this.noConnection();
this.delInstanceFiles();
@@ -28,15 +48,14 @@ export class WAMonitoringService {
this.dbInstance = this.db.ENABLED
? this.repository.dbServer?.db(this.db.CONNECTION.DB_PREFIX_NAME + '-instances')
: undefined;
this.redisCache = this.redis.ENABLED ? new RedisCache(this.redis) : undefined;
}
private readonly db: Partial<Database> = {};
private readonly redis: Partial<Redis> = {};
private dbInstance: Db;
private redisCache: RedisCache;
private dbStore = dbserver;
private readonly logger = new Logger(WAMonitoringService.name);
public readonly waInstances: Record<string, WAStartupService> = {};
@@ -44,15 +63,30 @@ export class WAMonitoringService {
public delInstanceTime(instance: string) {
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
if (typeof time === 'number' && time > 0) {
setTimeout(() => {
this.logger.verbose(
`Instance "${instance}" don't have connection, will be removed in ${time} minutes`,
);
setTimeout(async () => {
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
delete this.waInstances[instance];
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
await this.waInstances[instance]?.client?.logout(
'Log out instance: ' + instance,
);
this.waInstances[instance]?.client?.ws?.close();
this.waInstances[instance]?.client?.end(undefined);
delete this.waInstances[instance];
} else {
delete this.waInstances[instance];
this.eventEmitter.emit('remove.instance', instance, 'inner');
}
}
}, 1000 * 60 * time);
}
}
public async instanceInfo(instanceName?: string) {
this.logger.verbose('get instance info');
if (instanceName && !this.waInstances[instanceName]) {
throw new NotFoundException(`Instance "${instanceName}" not found`);
}
@@ -60,23 +94,82 @@ export class WAMonitoringService {
const instances: any[] = [];
for await (const [key, value] of Object.entries(this.waInstances)) {
if (value && value.connectionStatus.state === 'open') {
instances.push({
instance: {
instanceName: key,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
status: (await value.getProfileStatus()) || '',
},
});
if (value) {
this.logger.verbose('get instance info: ' + key);
let chatwoot: any;
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const findChatwoot = await this.waInstances[key].findChatwoot();
if (findChatwoot && findChatwoot.enabled) {
chatwoot = {
...findChatwoot,
webhook_url: `${urlServer}/chatwoot/webhook/${key}`,
};
}
if (value.connectionStatus.state === 'open') {
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
const instanceData = {
instance: {
instanceName: key,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
profileStatus: (await value.getProfileStatus()) || '',
status: value.connectionStatus.state,
},
};
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance['serverUrl'] =
this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['apikey'] = (
await this.repository.auth.find(key)
).apikey;
instanceData.instance['chatwoot'] = chatwoot;
}
instances.push(instanceData);
} else {
this.logger.verbose(
'instance: ' + key + ' - connectionStatus: ' + value.connectionStatus.state,
);
const instanceData = {
instance: {
instanceName: key,
status: value.connectionStatus.state,
},
};
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance['serverUrl'] =
this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['apikey'] = (
await this.repository.auth.find(key)
).apikey;
instanceData.instance['chatwoot'] = chatwoot;
}
instances.push(instanceData);
}
}
}
this.logger.verbose('return instance info: ' + instances.length);
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
}
private delInstanceFiles() {
this.logger.verbose('cron to delete instance files started');
setInterval(async () => {
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
const collections = await this.dbInstance.collections();
@@ -88,7 +181,9 @@ export class WAMonitoringService {
{ _id: { $regex: /^session-.*/ } },
],
});
this.logger.verbose('instance files deleted: ' + name);
});
} else if (this.redis.ENABLED) {
} else {
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
for await (const dirent of dir) {
@@ -104,14 +199,17 @@ export class WAMonitoringService {
});
}
});
this.logger.verbose('instance files deleted: ' + dirent.name);
}
}
}
}, 3600 * 1000 * 2);
}
private async cleaningUp(instanceName: string) {
public async cleaningUp(instanceName: string) {
this.logger.verbose('cleaning up instance: ' + instanceName);
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
this.logger.verbose('cleaning up instance in database: ' + instanceName);
await this.repository.dbServer.connect();
const collections: any[] = await this.dbInstance.collections();
if (collections.length > 0) {
@@ -121,52 +219,100 @@ export class WAMonitoringService {
}
if (this.redis.ENABLED) {
this.redisCache.reference = instanceName;
await this.redisCache.delAll();
this.logger.verbose('cleaning up instance in redis: ' + instanceName);
this.cache.reference = instanceName;
await this.cache.delAll();
return;
}
this.logger.verbose('cleaning up instance in files: ' + instanceName);
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
}
public async cleaningStoreFiles(instanceName: string) {
if (!this.db.ENABLED) {
this.logger.verbose('cleaning store files instance: ' + instanceName);
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
execSync(`rm -rf ${join(STORE_DIR, 'chats', instanceName)}`);
execSync(`rm -rf ${join(STORE_DIR, 'contacts', instanceName)}`);
execSync(`rm -rf ${join(STORE_DIR, 'message-up', instanceName)}`);
execSync(`rm -rf ${join(STORE_DIR, 'messages', instanceName)}`);
execSync(`rm -rf ${join(STORE_DIR, 'auth', 'apikey', instanceName + '.json')}`);
execSync(`rm -rf ${join(STORE_DIR, 'webhook', instanceName + '.json')}`);
execSync(`rm -rf ${join(STORE_DIR, 'chatwoot', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`);
return;
}
this.logger.verbose('cleaning store database instance: ' + instanceName);
await AuthModel.deleteMany({ owner: instanceName });
await ContactModel.deleteMany({ owner: instanceName });
await MessageModel.deleteMany({ owner: instanceName });
await MessageUpModel.deleteMany({ owner: instanceName });
await AuthModel.deleteMany({ _id: instanceName });
await WebhookModel.deleteMany({ _id: instanceName });
await ChatwootModel.deleteMany({ _id: instanceName });
await SettingsModel.deleteMany({ _id: instanceName });
return;
}
public async loadInstance() {
this.logger.verbose('load instances');
const set = async (name: string) => {
const instance = new WAStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
);
instance.instanceName = name;
this.logger.verbose('instance loaded: ' + name);
await instance.connectToWhatsapp();
this.logger.verbose('connectToWhatsapp: ' + name);
this.waInstances[name] = instance;
};
try {
if (this.redis.ENABLED) {
const keys = await this.redisCache.instanceKeys();
this.logger.verbose('redis enabled');
await this.cache.connect(this.redis as Redis);
const keys = await this.cache.instanceKeys();
if (keys?.length > 0) {
this.logger.verbose('reading instance keys and setting instances');
keys.forEach(async (k) => await set(k.split(':')[1]));
} else {
initInstance();
this.logger.verbose('no instance keys found');
}
return;
}
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
this.logger.verbose('database enabled');
await this.repository.dbServer.connect();
const collections: any[] = await this.dbInstance.collections();
if (collections.length > 0) {
this.logger.verbose('reading collections and setting instances');
collections.forEach(
async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, '')),
);
} else {
initInstance();
this.logger.verbose('no collections found');
}
return;
}
this.logger.verbose('store in files enabled');
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
for await (const dirent of dir) {
if (dirent.isDirectory()) {
this.logger.verbose('reading instance files and setting instances');
const files = readdirSync(join(INSTANCE_DIR, dirent.name), {
encoding: 'utf-8',
});
@@ -176,6 +322,8 @@ export class WAMonitoringService {
}
await set(dirent.name);
} else {
this.logger.verbose('no instance files found');
}
}
} catch (error) {
@@ -185,22 +333,39 @@ export class WAMonitoringService {
private removeInstance() {
this.eventEmitter.on('remove.instance', async (instanceName: string) => {
this.logger.verbose('remove instance: ' + instanceName);
try {
this.logger.verbose('instance: ' + instanceName + ' - removing from memory');
this.waInstances[instanceName] = undefined;
} catch {}
try {
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName);
this.cleaningStoreFiles(instanceName);
} finally {
this.logger.warn(`Instance "${instanceName}" - REMOVED`);
}
});
this.eventEmitter.on('logout.instance', async (instanceName: string) => {
this.logger.verbose('logout instance: ' + instanceName);
try {
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName);
} finally {
this.logger.warn(`Instance "${instanceName}" - LOGOUT`);
}
});
}
private noConnection() {
this.logger.verbose('checking instances without connection');
this.eventEmitter.on('no.connection', async (instanceName) => {
try {
this.logger.verbose('instance: ' + instanceName + ' - removing from memory');
this.waInstances[instanceName] = undefined;
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName);
} catch (error) {
this.logger.error({

View File

@@ -0,0 +1,34 @@
import { InstanceDto } from '../dto/instance.dto';
import { SettingsDto } from '../dto/settings.dto';
import { WAMonitoringService } from './monitor.service';
import { Logger } from '../../config/logger.config';
export class SettingsService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(SettingsService.name);
public create(instance: InstanceDto, data: SettingsDto) {
this.logger.verbose('create settings: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setSettings(data);
return { settings: { ...instance, settings: data } };
}
public async find(instance: InstanceDto): Promise<SettingsDto> {
try {
this.logger.verbose('find settings: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[
instance.instanceName
].findSettings();
if (Object.keys(result).length === 0) {
throw new Error('Settings not found');
}
return result;
} catch (error) {
return { reject_call: false, msg_call: '', groups_ignore: false };
}
}
}

View File

@@ -1,11 +1,15 @@
import { InstanceDto } from '../dto/instance.dto';
import { WebhookDto } from '../dto/webhook.dto';
import { WAMonitoringService } from './monitor.service';
import { Logger } from '../../config/logger.config';
export class WebhookService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(WebhookService.name);
public create(instance: InstanceDto, data: WebhookDto) {
this.logger.verbose('create webhook: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setWebhook(data);
return { webhook: { ...instance, webhook: data } };
@@ -13,9 +17,18 @@ export class WebhookService {
public async find(instance: InstanceDto): Promise<WebhookDto> {
try {
return await this.waMonitor.waInstances[instance.instanceName].findWebhook();
this.logger.verbose('find webhook: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[
instance.instanceName
].findWebhook();
if (Object.keys(result).length === 0) {
throw new Error('Webhook not found');
}
return result;
} catch (error) {
return { enabled: null, url: '' };
return { enabled: false, url: '', events: [], webhook_by_events: false };
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-namespace */
import { AuthenticationState, WAConnectionState } from '@evolution/base';
import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys';
export enum Events {
APPLICATION_STARTUP = 'application.startup',
@@ -9,6 +9,7 @@ export enum Events {
MESSAGES_SET = 'messages.set',
MESSAGES_UPSERT = 'messages.upsert',
MESSAGES_UPDATE = 'messages.update',
MESSAGES_DELETE = 'messages.delete',
SEND_MESSAGE = 'send.message',
CONTACTS_SET = 'contacts.set',
CONTACTS_UPSERT = 'contacts.upsert',
@@ -24,9 +25,15 @@ export enum Events {
}
export declare namespace wa {
export type QrCode = { count?: number; base64?: string; code?: string };
export type QrCode = {
count?: number;
pairingCode?: string;
base64?: string;
code?: string;
};
export type Instance = {
qrcode?: QrCode;
pairingCode?: string;
authState?: { state: AuthenticationState; saveCreds: () => void };
name?: string;
wuid?: string;
@@ -34,7 +41,27 @@ export declare namespace wa {
profilePictureUrl?: string;
};
export type LocalWebHook = { enabled?: boolean; url?: string };
export type LocalWebHook = {
enabled?: boolean;
url?: string;
events?: string[];
webhook_by_events?: boolean;
};
export type LocalChatwoot = {
enabled?: boolean;
account_id?: string;
token?: string;
url?: string;
name_inbox?: string;
sign_msg?: boolean;
};
export type LocalSettings = {
reject_call?: boolean;
msg_call?: string;
groups_ignore?: boolean;
};
export type StateConnection = {
instance?: string;
@@ -48,6 +75,7 @@ export declare namespace wa {
| 'SERVER_ACK'
| 'DELIVERY_ACK'
| 'READ'
| 'DELETED'
| 'PLAYED';
}

View File

@@ -14,6 +14,8 @@ import { GroupController } from './controllers/group.controller';
import { ViewsController } from './controllers/views.controller';
import { WebhookService } from './services/webhook.service';
import { WebhookController } from './controllers/webhook.controller';
import { ChatwootService } from './services/chatwoot.service';
import { ChatwootController } from './controllers/chatwoot.controller';
import { RepositoryBroker } from './repository/repository.manager';
import {
AuthModel,
@@ -21,14 +23,21 @@ import {
ContactModel,
MessageModel,
MessageUpModel,
ChatwootModel,
WebhookModel,
SettingsModel,
} from './models';
import { dbserver } from '../db/db.connect';
import { WebhookRepository } from './repository/webhook.repository';
import { WebhookModel } from './models/webhook.model';
import { ChatwootRepository } from './repository/chatwoot.repository';
import { AuthRepository } from './repository/auth.repository';
import { WAStartupService } from './services/whatsapp.service';
import { delay } from '@evolution/base';
import { delay } from '@whiskeysockets/baileys';
import { Events } from './types/wa.types';
import { RedisCache } from '../db/redis.client';
import { SettingsRepository } from './repository/settings.repository';
import { SettingsService } from './services/settings.service';
import { SettingsController } from './controllers/settings.controller';
const logger = new Logger('WA MODULE');
@@ -37,6 +46,8 @@ const chatRepository = new ChatRepository(ChatModel, configService);
const contactRepository = new ContactRepository(ContactModel, configService);
const messageUpdateRepository = new MessageUpRepository(MessageUpModel, configService);
const webhookRepository = new WebhookRepository(WebhookModel, configService);
const chatwootRepository = new ChatwootRepository(ChatwootModel, configService);
const settingsRepository = new SettingsRepository(SettingsModel, configService);
const authRepository = new AuthRepository(AuthModel, configService);
export const repository = new RepositoryBroker(
@@ -45,11 +56,21 @@ export const repository = new RepositoryBroker(
contactRepository,
messageUpdateRepository,
webhookRepository,
chatwootRepository,
settingsRepository,
authRepository,
configService,
dbserver?.getClient(),
);
export const waMonitor = new WAMonitoringService(eventEmitter, configService, repository);
export const cache = new RedisCache();
export const waMonitor = new WAMonitoringService(
eventEmitter,
configService,
repository,
cache,
);
const authService = new AuthService(configService, waMonitor, repository);
@@ -57,6 +78,14 @@ const webhookService = new WebhookService(waMonitor);
export const webhookController = new WebhookController(webhookService);
const chatwootService = new ChatwootService(waMonitor, configService);
export const chatwootController = new ChatwootController(chatwootService, configService);
const settingsService = new SettingsService(waMonitor);
export const settingsController = new SettingsController(settingsService);
export const instanceController = new InstanceController(
waMonitor,
configService,
@@ -64,80 +93,12 @@ export const instanceController = new InstanceController(
eventEmitter,
authService,
webhookService,
chatwootService,
cache,
);
export const viewsController = new ViewsController(waMonitor, configService);
export const sendMessageController = new SendMessageController(waMonitor);
export const chatController = new ChatController(waMonitor);
export const groupController = new GroupController(waMonitor);
export async function initInstance() {
const instance = new WAStartupService(configService, eventEmitter, repository);
const mode = configService.get<Auth>('AUTHENTICATION').INSTANCE.MODE;
instance.sendDataWebhook(
Events.APPLICATION_STARTUP,
{
message: 'Application startup',
mode,
},
false,
);
if (mode === 'container') {
const instanceName = configService.get<Auth>('AUTHENTICATION').INSTANCE.NAME;
const instanceWebhook =
configService.get<Auth>('AUTHENTICATION').INSTANCE.WEBHOOK_URL;
instance.instanceName = instanceName;
waMonitor.waInstances[instance.instanceName] = instance;
waMonitor.delInstanceTime(instance.instanceName);
const hash = await authService.generateHash({
instanceName: instance.instanceName,
});
if (instanceWebhook) {
try {
webhookService.create(instance, { enabled: true, url: instanceWebhook });
} catch (error) {
this.logger.log(error);
}
}
try {
const state = instance.connectionStatus?.state;
switch (state) {
case 'close':
await instance.connectToWhatsapp();
await delay(2000);
return instance.qrCode;
case 'connecting':
return instance.qrCode;
default:
return await this.connectionState({ instanceName });
}
} catch (error) {
this.logger.log(error);
}
const result = {
instance: {
instanceName: instance.instanceName,
status: 'created',
},
hash,
webhook: instanceWebhook,
};
logger.info(result);
return result;
}
return null;
}
logger.info('Module - ON');

25
start.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/sh
if [ "$DOCKER_ENV" = "true" ];
then
echo "Enabling environment variables for Docker"
echo "DOCKER_ENV=$DOCKER_ENV"
echo
else
mkdir -p ./dist/src
cp ./src/env.yml ./dist/src
fi
echo "> removing dist"
rm -rf ./dist
echo
echo "> transpiling..."
npm run build
echo
echo "> Successfully build "
echo
echo "> Starting application..."
echo
node ./dist/src/main.js

View File

View File