Compare commits
1185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfb003fd72 | ||
|
|
5239e431c2 | ||
|
|
53cc6132f5 | ||
|
|
0bc1b78db9 | ||
|
|
2a9412c81a | ||
|
|
f707cf4109 | ||
|
|
410cfc8bcb | ||
|
|
f1a3fd872f | ||
|
|
fff0ca5c79 | ||
|
|
bd069200ea | ||
|
|
c898f1e62a | ||
|
|
2002e1c38d | ||
|
|
1f817df5f6 | ||
|
|
8eced6c575 | ||
|
|
afcf6eab71 | ||
|
|
9633412ef6 | ||
|
|
8034e7f587 | ||
|
|
5be01e1c18 | ||
|
|
6371773416 | ||
|
|
52230edc5c | ||
|
|
dd123c6a99 | ||
|
|
5b2a0fdcb1 | ||
|
|
9354af3bc7 | ||
|
|
f48f331d43 | ||
|
|
696261d749 | ||
|
|
0e9d036fe0 | ||
|
|
8e19fe2fb5 | ||
|
|
861b690217 | ||
|
|
f7c9541f5e | ||
|
|
05b5ae8a84 | ||
|
|
2e9c14a0a8 | ||
|
|
3350cec589 | ||
|
|
bd7a42646b | ||
|
|
2ae4ddee18 | ||
|
|
2fb6e2b209 | ||
|
|
91f869b136 | ||
|
|
1284a97625 | ||
|
|
3a5cfbf36f | ||
|
|
47b8ecd43b | ||
|
|
25ce1a4689 | ||
|
|
a5156709ab | ||
|
|
fd2d37d862 | ||
|
|
87bf433e9a | ||
|
|
b5ec79daae | ||
|
|
3b19200997 | ||
|
|
0ab040080c | ||
|
|
5719896d18 | ||
|
|
e9e1cb6ebc | ||
|
|
ceee6a7e74 | ||
|
|
ebfc6d4fa5 | ||
|
|
0d62557a15 | ||
|
|
097c09328f | ||
|
|
732103292b | ||
|
|
3191e9b450 | ||
|
|
bf88fdbb31 | ||
|
|
4c3fb5e762 | ||
|
|
e4b6f4ff0d | ||
|
|
ca0b0d4602 | ||
|
|
d7c3779ff7 | ||
|
|
f0d40eea15 | ||
|
|
68dcc1c86a | ||
|
|
f7dcab3736 | ||
|
|
2fcb476c50 | ||
|
|
44a7cd62c8 | ||
|
|
f7f0f8251b | ||
|
|
395b81a6ac | ||
|
|
da06ed1185 | ||
|
|
67afbd6a77 | ||
|
|
8fce53b4af | ||
|
|
65bba23519 | ||
|
|
283b497788 | ||
|
|
8cc1b8daa8 | ||
|
|
9ccdb45a7a | ||
|
|
f78d360c38 | ||
|
|
6144fbe856 | ||
|
|
8446be7646 | ||
|
|
8875ab1e93 | ||
|
|
7c207a50e1 | ||
|
|
dcc32479ff | ||
|
|
09911c472d | ||
|
|
7a0149ee23 | ||
|
|
3ae8cf32b0 | ||
|
|
11cf947bbb | ||
|
|
60a20f61af | ||
|
|
e65c7b6bcf | ||
|
|
80c892aca3 | ||
|
|
39ee266598 | ||
|
|
aa58d7744e | ||
|
|
ea3b0b3712 | ||
|
|
d9d8707123 | ||
|
|
8e9a1e2ba5 | ||
|
|
e29b4865e8 | ||
|
|
4b60ca175d | ||
|
|
2fbbc7b5a9 | ||
|
|
633dbb82d3 | ||
|
|
95907b3cea | ||
|
|
14c210c771 | ||
|
|
00e7fcc46b | ||
|
|
720efcbcbf | ||
|
|
d95791cc18 | ||
|
|
d45b7af3b6 | ||
|
|
0ad3acaf07 | ||
|
|
e27818ecda | ||
|
|
ff21cf4d4c | ||
|
|
dc19c7fdec | ||
|
|
6c8ffd8ec6 | ||
|
|
96fdb210be | ||
|
|
84ad8e0d6e | ||
|
|
53361682f4 | ||
|
|
0ee243f284 | ||
|
|
26ff0b634f | ||
|
|
f44ab0f678 | ||
|
|
1f128747bb | ||
|
|
3bf975d90f | ||
|
|
92f8951be4 | ||
|
|
e071f56767 | ||
|
|
c85619efcf | ||
|
|
27f9ae1e56 | ||
|
|
7449102d95 | ||
|
|
234a2c71b5 | ||
|
|
4dd5533202 | ||
|
|
a4d1740754 | ||
|
|
1a2ea1c38a | ||
|
|
371ec9b8d5 | ||
|
|
5e288d57ea | ||
|
|
61bd5b3484 | ||
|
|
bb974e10f5 | ||
|
|
4ed1335f89 | ||
|
|
4274c7afdb | ||
|
|
27f67142c8 | ||
|
|
ac8aba6ba4 | ||
|
|
07ff61c070 | ||
|
|
3645ac6704 | ||
|
|
9764719245 | ||
|
|
b3aeed7fc1 | ||
|
|
5ae5d8546e | ||
|
|
d190d8b1af | ||
|
|
c45538684d | ||
|
|
575e7bf4c4 | ||
|
|
a4338d8472 | ||
|
|
7d542cf115 | ||
|
|
9c9a542bbe | ||
|
|
b6c56551bc | ||
|
|
1379228196 | ||
|
|
d6e19b9273 | ||
|
|
5e0e4051dc | ||
|
|
8caf3a0a7b | ||
|
|
96ae97664c | ||
|
|
901b121695 | ||
|
|
3e88c9f0c7 | ||
|
|
94f5c130bf | ||
|
|
eb04c3f113 | ||
|
|
8ece6fb998 | ||
|
|
794213b5c6 | ||
|
|
ef4a9ab66b | ||
|
|
86e978faad | ||
|
|
d5d8f2a318 | ||
|
|
4ca1fad335 | ||
|
|
0edf6bdcb1 | ||
|
|
c2839ddd56 | ||
|
|
28581f88b2 | ||
|
|
833c625d18 | ||
|
|
6e2a3a410a | ||
|
|
3e85af9589 | ||
|
|
589dec52a3 | ||
|
|
a5477509bd | ||
|
|
5d951a96b5 | ||
|
|
76552f6ae5 | ||
|
|
e153515847 | ||
|
|
3bc692d894 | ||
|
|
73360d66cd | ||
|
|
04575d8051 | ||
|
|
9aba51cb45 | ||
|
|
1172b6c871 | ||
|
|
240f01f378 | ||
|
|
5975117636 | ||
|
|
51b285f665 | ||
|
|
b3958d4735 | ||
|
|
128119d494 | ||
|
|
324dc01699 | ||
|
|
f11d468e31 | ||
|
|
aa5ed13752 | ||
|
|
10ce7da18d | ||
|
|
73bd55dfac | ||
|
|
db10f81d53 | ||
|
|
717aac4438 | ||
|
|
077a464481 | ||
|
|
9bf18592fc | ||
|
|
677e196c96 | ||
|
|
19e6896f0d | ||
|
|
ba537baab2 | ||
|
|
2b8b6b086b | ||
|
|
cf3ec2b601 | ||
|
|
35eaa7852c | ||
|
|
b87558d301 | ||
|
|
9f1003e94e | ||
|
|
73c003907b | ||
|
|
b65d292af4 | ||
|
|
43bdbf1018 | ||
|
|
ec7986420d | ||
|
|
0b23153316 | ||
|
|
ad4f5e5e65 | ||
|
|
1d48532146 | ||
|
|
950803b2aa | ||
|
|
a41d92f4da | ||
|
|
70d4eb393f | ||
|
|
9f162127d0 | ||
|
|
4cd30dabc4 | ||
|
|
8f78728863 | ||
|
|
e7c5d33d50 | ||
|
|
5400f31acb | ||
|
|
e58f1d778e | ||
|
|
5f5db2011e | ||
|
|
aea4d89f62 | ||
|
|
ccda17d704 | ||
|
|
4ab6c438cf | ||
|
|
219e63c226 | ||
|
|
ee1e4623a3 | ||
|
|
5982212903 | ||
|
|
32e6ecb12d | ||
|
|
3b774558f4 | ||
|
|
072171da18 | ||
|
|
8292221790 | ||
|
|
901954de33 | ||
|
|
286fe03500 | ||
|
|
9c4f02634b | ||
|
|
434f39de1f | ||
|
|
e72b543f38 | ||
|
|
826e818802 | ||
|
|
cbf846d32f | ||
|
|
55811a39a5 | ||
|
|
a81f5953fc | ||
|
|
bd09328ec2 | ||
|
|
17ecc88f80 | ||
|
|
7bc4c7b36e | ||
|
|
79189ac5d7 | ||
|
|
060c4e6e72 | ||
|
|
8f7c518487 | ||
|
|
12761cbfce | ||
|
|
2b30c273a4 | ||
|
|
03684a893d | ||
|
|
fef27111b9 | ||
|
|
b04ed66686 | ||
|
|
f5df8bca20 | ||
|
|
060af66c09 | ||
|
|
a6adbd61db | ||
|
|
bab58054f7 | ||
|
|
e27e990cd6 | ||
|
|
196c2e0ed8 | ||
|
|
499bd4328a | ||
|
|
e389047aaf | ||
|
|
da04ff284b | ||
|
|
10b48aed97 | ||
|
|
ac6e9ae994 | ||
|
|
7dd589fb6c | ||
|
|
6b930058d1 | ||
|
|
aef3495a79 | ||
|
|
da341a95c6 | ||
|
|
bca5ec6482 | ||
|
|
f0a0cb7269 | ||
|
|
238b7618b4 | ||
|
|
4f206f67c9 | ||
|
|
fa513bf784 | ||
|
|
a68b0b3878 | ||
|
|
249489e697 | ||
|
|
e2c67d7dae | ||
|
|
703bc310a7 | ||
|
|
4caecfa680 | ||
|
|
fd4fde2543 | ||
|
|
763c5de03f | ||
|
|
4e160a46dd | ||
|
|
56aaf18663 | ||
|
|
df841aed27 | ||
|
|
d4372a0332 | ||
|
|
1595da789d | ||
|
|
7e65cb1d19 | ||
|
|
a931ee7afb | ||
|
|
bc6944736e | ||
|
|
16c2e28e9c | ||
|
|
ab89ef8db3 | ||
|
|
9ef14d11f8 | ||
|
|
e753990da3 | ||
|
|
f469c2e65d | ||
|
|
0525501b87 | ||
|
|
3a37fd9d32 | ||
|
|
b095c9dfb9 | ||
|
|
cd00effcfe | ||
|
|
1cd0334ccd | ||
|
|
3b1a16844e | ||
|
|
6995e8a451 | ||
|
|
1dd0f319fc | ||
|
|
5ce96369cf | ||
|
|
0791d78e28 | ||
|
|
cecbb7c34e | ||
|
|
525daff5fe | ||
|
|
b2c51b4b8c | ||
|
|
ffddb05ba3 | ||
|
|
4bb81b9a41 | ||
|
|
3df4e8d2ba | ||
|
|
3edef873bc | ||
|
|
c04b5cb851 | ||
|
|
fabd717d92 | ||
|
|
945bcf5fad | ||
|
|
d35d755379 | ||
|
|
a2622cb38e | ||
|
|
b58fd78450 | ||
|
|
e8d32066b4 | ||
|
|
6d3779eb83 | ||
|
|
c7bed04c80 | ||
|
|
b0e956cfa9 | ||
|
|
8608b7cded | ||
|
|
23f1b4ac03 | ||
|
|
27900c214f | ||
|
|
704701e251 | ||
|
|
34c53d352a | ||
|
|
32026d1fd4 | ||
|
|
35cdce0d52 | ||
|
|
e59098cf61 | ||
|
|
d8ca480b19 | ||
|
|
803b123ade | ||
|
|
cffb673fba | ||
|
|
1f6535d61b | ||
|
|
8a5ebe83a3 | ||
|
|
cc17d61016 | ||
|
|
0547ff719c | ||
|
|
c130846fe8 | ||
|
|
b3adde3a7a | ||
|
|
54603002a6 | ||
|
|
b995cdfc32 | ||
|
|
b09546577a | ||
|
|
6e84c1dc4c | ||
|
|
f41f3aaba8 | ||
|
|
8fa61e4632 | ||
|
|
7dacd752d3 | ||
|
|
7439d2401d | ||
|
|
dfb1ee0c56 | ||
|
|
3da73b821d | ||
|
|
66b82ac10a | ||
|
|
058acc5042 | ||
|
|
cdf822291f | ||
|
|
3f9e872e1c | ||
|
|
94aa3067f6 | ||
|
|
bff8064597 | ||
|
|
6b2d4e2585 | ||
|
|
56dfc2ae82 | ||
|
|
535d5ee47f | ||
|
|
838905f3dd | ||
|
|
59f5208c5c | ||
|
|
32a1a715ea | ||
|
|
3755d3870e | ||
|
|
0edc8a9284 | ||
|
|
5449d63602 | ||
|
|
dfa72fd6af | ||
|
|
0e50da324b | ||
|
|
679f8118f6 | ||
|
|
804d177782 | ||
|
|
eb96c9fece | ||
|
|
ed6c50621c | ||
|
|
7f74de07ed | ||
|
|
9e5bf93580 | ||
|
|
9d685da12d | ||
|
|
6bb40eb502 | ||
|
|
1ceee572cf | ||
|
|
391ffea4ab | ||
|
|
5292e569d9 | ||
|
|
82e111f1be | ||
|
|
1e9b3a1e42 | ||
|
|
35520d85a2 | ||
|
|
e19e37eef4 | ||
|
|
814795f566 | ||
|
|
1a633dcf10 | ||
|
|
c9b0e66641 | ||
|
|
440ff2f3ea | ||
|
|
96be63f50b | ||
|
|
69a323691f | ||
|
|
4357fcf7ef | ||
|
|
5f79513617 | ||
|
|
ba3ccbbc9a | ||
|
|
f182436673 | ||
|
|
793b907fe6 | ||
|
|
c75dfcd499 | ||
|
|
29a48f7914 | ||
|
|
095a8aa9dd | ||
|
|
2d8b5f04e9 | ||
|
|
ba9f97bc3e | ||
|
|
ed51f44ee8 | ||
|
|
933d787108 | ||
|
|
0bc12733a3 | ||
|
|
8cfd9c44ab | ||
|
|
3ddff84be0 | ||
|
|
afb5361a1b | ||
|
|
f376047632 | ||
|
|
7373eea842 | ||
|
|
a446df4620 | ||
|
|
306c6dd21d | ||
|
|
380ba8860c | ||
|
|
6c8c8ddcfc | ||
|
|
0c09c5e140 | ||
|
|
b761fba8cb | ||
|
|
dfceda3942 | ||
|
|
85d1825236 | ||
|
|
8f8c7e26c7 | ||
|
|
181768d91f | ||
|
|
2dcd4d8fd3 | ||
|
|
d909550134 | ||
|
|
c564ec41e2 | ||
|
|
af94a0e174 | ||
|
|
fcd8815fca | ||
|
|
5bd3f28117 | ||
|
|
384bde333e | ||
|
|
d06ec604b9 | ||
|
|
d8ce23f2a0 | ||
|
|
3ccb983377 | ||
|
|
19fb9fcd31 | ||
|
|
5f4a1b96ce | ||
|
|
6983f385fc | ||
|
|
2aadd1cac5 | ||
|
|
bfa7d429bd | ||
|
|
3238150b92 | ||
|
|
3603571967 | ||
|
|
7c2a8c0abb | ||
|
|
8b5f73badd | ||
|
|
ff57fd3d23 | ||
|
|
49aa1ea17c | ||
|
|
7430897085 | ||
|
|
82894a1c4f | ||
|
|
3bb4217a45 | ||
|
|
dfc8330035 | ||
|
|
45e03d87c7 | ||
|
|
3e358e5d26 | ||
|
|
dc5dae04eb | ||
|
|
9128b1f47d | ||
|
|
16a8226ba7 | ||
|
|
9ecaf3199d | ||
|
|
a44cd0373e | ||
|
|
2c0b629302 | ||
|
|
89a37a1771 | ||
|
|
cadc038966 | ||
|
|
07e8449379 | ||
|
|
71e908ad1a | ||
|
|
035c85b775 | ||
|
|
a87b753151 | ||
|
|
edaa4aff7e | ||
|
|
be02610349 | ||
|
|
1f65731165 | ||
|
|
fb61fb7849 | ||
|
|
060a945aea | ||
|
|
2797250f34 | ||
|
|
97b3f9b3c7 | ||
|
|
9363301d2a | ||
|
|
d93a826e28 | ||
|
|
244fe0835e | ||
|
|
5f1a5d6589 | ||
|
|
4e27c22292 | ||
|
|
53ee270096 | ||
|
|
a00ad20c08 | ||
|
|
e8764dd1c6 | ||
|
|
a869d38499 | ||
|
|
7bf7c96587 | ||
|
|
72b857a92f | ||
|
|
380d6a43a5 | ||
|
|
076f2b492e | ||
|
|
547f981c47 | ||
|
|
8bfc62a3b2 | ||
|
|
d39776a314 | ||
|
|
35641d0543 | ||
|
|
038cd6f149 | ||
|
|
38978dd447 | ||
|
|
fb24b7eaa7 | ||
|
|
b4ce45bc4b | ||
|
|
8d04198309 | ||
|
|
da796347c4 | ||
|
|
2d6a29664a | ||
|
|
4ba5cfceaf | ||
|
|
7cc324e1c0 | ||
|
|
cf89601269 | ||
|
|
c07e23bf8d | ||
|
|
5aa89d85f3 | ||
|
|
4ed1edf53d | ||
|
|
1be1326b52 | ||
|
|
42ae7d1568 | ||
|
|
b781c83545 | ||
|
|
e48cea18e7 | ||
|
|
ff82987144 | ||
|
|
f612a45550 | ||
|
|
182dce4840 | ||
|
|
4e41e072d6 | ||
|
|
a369c16db8 | ||
|
|
1fc820787a | ||
|
|
d3a83ba89e | ||
|
|
20fb66e2f7 | ||
|
|
a44646161b | ||
|
|
87027ea2d0 | ||
|
|
c9757cbb4b | ||
|
|
9a8f4aefe0 | ||
|
|
3545b80050 | ||
|
|
379855714e | ||
|
|
ff06cd7643 | ||
|
|
ade3952016 | ||
|
|
f246516a6e | ||
|
|
c296bf4178 | ||
|
|
1568554a1c | ||
|
|
ae66be197e | ||
|
|
3e904aa160 | ||
|
|
64c1440c46 | ||
|
|
f069a41390 | ||
|
|
d4a33e2290 | ||
|
|
7ee5bcecff | ||
|
|
48f6ee8846 | ||
|
|
7a24f52782 | ||
|
|
324d46120b | ||
|
|
87baec5ff8 | ||
|
|
b2e144f35c | ||
|
|
87a8e25662 | ||
|
|
8e88f00fb2 | ||
|
|
d14505d59a | ||
|
|
1e6d4347fa | ||
|
|
b8d9a8c072 | ||
|
|
fd15ae5e8c | ||
|
|
fb6377414b | ||
|
|
9a5dbe055e | ||
|
|
42dd280aca | ||
|
|
41b2946cdc | ||
|
|
a90f0f2c59 | ||
|
|
4222c0e53b | ||
|
|
ee0f0f0be0 | ||
|
|
f8d874453c | ||
|
|
aa891489f0 | ||
|
|
4c69b059d4 | ||
|
|
359bd9f762 | ||
|
|
2de0b61726 | ||
|
|
d75163aa57 | ||
|
|
e49f30641e | ||
|
|
1631c2c342 | ||
|
|
cf7de369b2 | ||
|
|
78c03d8f2f | ||
|
|
876320b849 | ||
|
|
a1d13f8ff3 | ||
|
|
3c19bdfaa9 | ||
|
|
4fa895086e | ||
|
|
9945d8debb | ||
|
|
4362de2198 | ||
|
|
a5c5879e4f | ||
|
|
1a57f4f33d | ||
|
|
a99e173168 | ||
|
|
e02a28f61e | ||
|
|
26d3ff97ce | ||
|
|
57fb3c9785 | ||
|
|
edeb970a82 | ||
|
|
e17baddf01 | ||
|
|
a277d36696 | ||
|
|
6c9e86e17a | ||
|
|
e75ef21eb6 | ||
|
|
04e5443b82 | ||
|
|
8b4cdf3b9b | ||
|
|
37f1620f7c | ||
|
|
b0a0e805cf | ||
|
|
c619e253a2 | ||
|
|
2bd111f1e2 | ||
|
|
40174b50eb | ||
|
|
e0fe28717f | ||
|
|
ac5fc22043 | ||
|
|
e30f196dad | ||
|
|
9e4e1ce8ec | ||
|
|
f710898844 | ||
|
|
94633484ca | ||
|
|
0817c2589f | ||
|
|
8a99386b33 | ||
|
|
52d6a563d6 | ||
|
|
d0a5ae1da4 | ||
|
|
783c00a1d9 | ||
|
|
bc70ec8b07 | ||
|
|
50e1efe5d7 | ||
|
|
d8629e53f1 | ||
|
|
cc9df1dabb | ||
|
|
cd6cb8182e | ||
|
|
5b0e90e5b9 | ||
|
|
3c3bbc84b3 | ||
|
|
23615cff4f | ||
|
|
daadc6cb68 | ||
|
|
e157a2a36b | ||
|
|
d8d7debfee | ||
|
|
a82b206fe6 | ||
|
|
a4416214c8 | ||
|
|
8fe75cd210 | ||
|
|
303effebbc | ||
|
|
f32a34190d | ||
|
|
8588ef1d8a | ||
|
|
51ec4821f3 | ||
|
|
957033a7bb | ||
|
|
62c74deac3 | ||
|
|
29fd448998 | ||
|
|
e55cb08a6a | ||
|
|
047359e8dc | ||
|
|
eb814e181a | ||
|
|
857031ff5a | ||
|
|
d5eeb68714 | ||
|
|
966b287026 | ||
|
|
a348729109 | ||
|
|
1f29b7733e | ||
|
|
e26ae30f6f | ||
|
|
547943a05c | ||
|
|
46aa229531 | ||
|
|
28bd796289 | ||
|
|
6a3f82ed7e | ||
|
|
523f3301c0 | ||
|
|
f085343a99 | ||
|
|
f76a924700 | ||
|
|
f8e3b76a4a | ||
|
|
b8f1e8a7ef | ||
|
|
d007fc49d8 | ||
|
|
f6d8ebd8d3 | ||
|
|
6ff9c4578a | ||
|
|
33acfe1464 | ||
|
|
f5eeb16bb1 | ||
|
|
c35c5faaa4 | ||
|
|
8425ebc13f | ||
|
|
6fc37a4298 | ||
|
|
99f3e77c12 | ||
|
|
a9c087c45f | ||
|
|
3f4333087f | ||
|
|
e1ac29683d | ||
|
|
5c74cbfe19 | ||
|
|
3fdb3fa673 | ||
|
|
ba584974cb | ||
|
|
bddd6408ac | ||
|
|
413ad66a07 | ||
|
|
0ef5d884cc | ||
|
|
f6b6d23e93 | ||
|
|
50be69f3d3 | ||
|
|
5a75e4d5e6 | ||
|
|
ec463df9d6 | ||
|
|
b648334323 | ||
|
|
916972aeb1 | ||
|
|
3893e01af9 | ||
|
|
7835f32c8b | ||
|
|
37302244ee | ||
|
|
f1be7ddb83 | ||
|
|
7447a65a83 | ||
|
|
129009d602 | ||
|
|
c656bd6f4b | ||
|
|
36528d7484 | ||
|
|
f9c85b6a67 | ||
|
|
4dfe6bdbe8 | ||
|
|
ee343f2fa1 | ||
|
|
1a1f5f85b2 | ||
|
|
bd64b0c884 | ||
|
|
e1c8928ed9 | ||
|
|
1f98940445 | ||
|
|
707aa22a6d | ||
|
|
32da15fa8a | ||
|
|
97cd6e289a | ||
|
|
def6576fb9 | ||
|
|
196c10b253 | ||
|
|
cfdca38d59 | ||
|
|
ccd90a69ee | ||
|
|
c16f962d2b | ||
|
|
8fccf69ceb | ||
|
|
adc8833670 | ||
|
|
ecbf90ded8 | ||
|
|
c5d2d7782a | ||
|
|
72dae22ef4 | ||
|
|
9a9cd41009 | ||
|
|
0c4c8162c2 | ||
|
|
d47cc5d5f4 | ||
|
|
cb942e512d | ||
|
|
bd0a479645 | ||
|
|
cd0da914f4 | ||
|
|
7a7e72897a | ||
|
|
c1542054c9 | ||
|
|
250e67e7ae | ||
|
|
cdbe839b35 | ||
|
|
240a77dcce | ||
|
|
1dc5bb8bd1 | ||
|
|
23534da27d | ||
|
|
8652d4031c | ||
|
|
384e311c7a | ||
|
|
2791f88b4c | ||
|
|
402d5af19a | ||
|
|
b554d8c19c | ||
|
|
7d9dd64303 | ||
|
|
41bea8931f | ||
|
|
f83d8de476 | ||
|
|
af2a652098 | ||
|
|
a3c911263d | ||
|
|
836c677837 | ||
|
|
7e4dbfdd7e | ||
|
|
6eda556242 | ||
|
|
3ea454c7ed | ||
|
|
9123d7014d | ||
|
|
3ea3abe81a | ||
|
|
7289c3d7f9 | ||
|
|
d00e1df29c | ||
|
|
16a18c4f22 | ||
|
|
30b156d92f | ||
|
|
931f9d33e4 | ||
|
|
e61fe4d092 | ||
|
|
5bc33ac654 | ||
|
|
878ba5a869 | ||
|
|
92632b2b96 | ||
|
|
d803a9e298 | ||
|
|
b9f67533dd | ||
|
|
7ade78bedf | ||
|
|
b4eca48f4d | ||
|
|
2cf8e317fa | ||
|
|
821a422ab5 | ||
|
|
91c7b4f2cd | ||
|
|
adb43ec5b3 | ||
|
|
c9721d7bc9 | ||
|
|
bbc2b8a396 | ||
|
|
54cfa67d52 | ||
|
|
9b72b3e332 | ||
|
|
c03919be2d | ||
|
|
706cc6f49c | ||
|
|
10c7e81e02 | ||
|
|
03637b2d4d | ||
|
|
b7218a05be | ||
|
|
a2cd57d9c6 | ||
|
|
001849eeaa | ||
|
|
bf09a70096 | ||
|
|
530aec92a9 | ||
|
|
deb8f2a0b7 | ||
|
|
5c247e3d2c | ||
|
|
04b9a070c4 | ||
|
|
dd2caf720c | ||
|
|
0d16a7aab0 | ||
|
|
31325d0999 | ||
|
|
6f99784224 | ||
|
|
201e6f7e7b | ||
|
|
c4d41134b8 | ||
|
|
680c92ecec | ||
|
|
deb07d2b7f | ||
|
|
3a14fc373a | ||
|
|
29e429a02e | ||
|
|
907a0ee135 | ||
|
|
22a03f77ab | ||
|
|
9761b10bf6 | ||
|
|
c364d3fdca | ||
|
|
07ad5756eb | ||
|
|
6e401eecde | ||
|
|
f32e259d2f | ||
|
|
83ed0e6454 | ||
|
|
da568e4ea5 | ||
|
|
ad819bf3ba | ||
|
|
b502ebd23a | ||
|
|
7c5d94c19e | ||
|
|
a16b5f4644 | ||
|
|
f3cb8c531b | ||
|
|
469e696ab7 | ||
|
|
d99ccd9df6 | ||
|
|
3b3118d764 | ||
|
|
84386847e2 | ||
|
|
0da3d100b3 | ||
|
|
56d621bab8 | ||
|
|
f1571b5f66 | ||
|
|
55f8e179af | ||
|
|
ab5289a136 | ||
|
|
e05c48979d | ||
|
|
24c880343b | ||
|
|
b3b4ee7a28 | ||
|
|
e26b440a66 | ||
|
|
d6194316e1 | ||
|
|
341148612f | ||
|
|
79864e97d6 | ||
|
|
ed5e66e430 | ||
|
|
074a861fb4 | ||
|
|
b88656829e | ||
|
|
aefe6a5943 | ||
|
|
3d743f8498 | ||
|
|
0186fff67d | ||
|
|
38f61cdf75 | ||
|
|
84366002db | ||
|
|
eff5bb74b5 | ||
|
|
2614088fae | ||
|
|
3699e04db9 | ||
|
|
0dca009c01 | ||
|
|
1b39eb1a23 | ||
|
|
2637aebb7f | ||
|
|
8afcfde078 | ||
|
|
9ea1eaf3ed | ||
|
|
66d06afaf7 | ||
|
|
cffcca9722 | ||
|
|
fe2b9774d8 | ||
|
|
3d02fabef4 | ||
|
|
f89c2b1f63 | ||
|
|
ee755d5a6c | ||
|
|
65e2ecf88e | ||
|
|
332ec69ee8 | ||
|
|
1ce30f8431 | ||
|
|
3ae6944307 | ||
|
|
52533d4b38 | ||
|
|
38409d9336 | ||
|
|
d344e513c4 | ||
|
|
9af7f67930 | ||
|
|
f95d938fb6 | ||
|
|
f74a7e87bd | ||
|
|
d3fce5fc89 | ||
|
|
14f3f3d2ac | ||
|
|
127d5b97c4 | ||
|
|
7ef1c097e8 | ||
|
|
80e3116cd8 | ||
|
|
46bac55bb3 | ||
|
|
2cadafd5c1 | ||
|
|
bb27dca21c | ||
|
|
86fc9fbffa | ||
|
|
9bdbfc6f4f | ||
|
|
28a7d9c62b | ||
|
|
2ca344b17c | ||
|
|
1bf2278f31 | ||
|
|
6188ab0d30 | ||
|
|
f016b53013 | ||
|
|
127cd3e843 | ||
|
|
457dbe5831 | ||
|
|
8d73fb1161 | ||
|
|
af5746bb39 | ||
|
|
3f27d018c7 | ||
|
|
68402393b5 | ||
|
|
2ad33857f4 | ||
|
|
d3c7677dc7 | ||
|
|
73e92f9ef5 | ||
|
|
fcc8748daf | ||
|
|
312ee249b6 | ||
|
|
e4548f6961 | ||
|
|
1b93aac8c5 | ||
|
|
e7ed666037 | ||
|
|
85ca0683ed | ||
|
|
78689a23e5 | ||
|
|
ba63a55883 | ||
|
|
dc3d59bae1 | ||
|
|
0a851b935e | ||
|
|
ddc75d710f | ||
|
|
5482601b41 | ||
|
|
249aecbc0d | ||
|
|
03f3020e9f | ||
|
|
e151eb85f0 | ||
|
|
348586553e | ||
|
|
fb6e58b3c4 | ||
|
|
67e98456bb | ||
|
|
3e47420534 | ||
|
|
1d3d557c43 | ||
|
|
3f41974a75 | ||
|
|
3e3c7397a5 | ||
|
|
dcb51702e7 | ||
|
|
de0c9a1eff | ||
|
|
dd0c1e20a7 | ||
|
|
4769d75dc3 | ||
|
|
6b926dc697 | ||
|
|
ecae077c6d | ||
|
|
78ab1bed35 | ||
|
|
2b6dbfde6b | ||
|
|
6bb1abd7f0 | ||
|
|
aef92240cc | ||
|
|
f0d8c2d095 | ||
|
|
14529f2c35 | ||
|
|
89f40d54d9 | ||
|
|
4c006970a2 | ||
|
|
de676041df | ||
|
|
82b1567ae5 | ||
|
|
1cd7291068 | ||
|
|
fdee1df5b3 | ||
|
|
c314d00ccd | ||
|
|
62e2a8a6e3 | ||
|
|
a12231a0aa | ||
|
|
183efd427a | ||
|
|
f95f3126c3 | ||
|
|
4d9ca4b451 | ||
|
|
c76334a68a | ||
|
|
f475391ba6 | ||
|
|
b77f22790b | ||
|
|
757a578c6e | ||
|
|
c5824767c8 | ||
|
|
84f3f07279 | ||
|
|
f8e1892eee | ||
|
|
036a8edca0 | ||
|
|
58ed6f395f | ||
|
|
ef4be6a612 | ||
|
|
3d8e6f4394 | ||
|
|
0cc1f18a7e | ||
|
|
8b6e577b8f | ||
|
|
cc91f2e5db | ||
|
|
8d1f2313ac | ||
|
|
f9abd90cc9 | ||
|
|
f7293255cf | ||
|
|
7d6a130cf9 | ||
|
|
be7bb2e39f | ||
|
|
1c30728880 | ||
|
|
8d91e7cb1d | ||
|
|
68d980795a | ||
|
|
45c11a5a8e | ||
|
|
4d00351db7 | ||
|
|
fff420b652 | ||
|
|
7103a95305 | ||
|
|
bcada5d553 | ||
|
|
c9b24ff612 | ||
|
|
854c7ed04d | ||
|
|
c0054959cd | ||
|
|
1aa837d220 | ||
|
|
95df402c4c | ||
|
|
2f3d6f7e63 | ||
|
|
ffe1523170 | ||
|
|
a73d5f4b4d | ||
|
|
f35b62ed12 | ||
|
|
20abdd2908 | ||
|
|
28c2c7285c | ||
|
|
3ca8ab12a4 | ||
|
|
fd82aa143c | ||
|
|
1fcbd4f9fd | ||
|
|
73d9cd62a5 | ||
|
|
be699d24a1 | ||
|
|
798eb90bed | ||
|
|
c252f5f8d9 | ||
|
|
76d77ad76f | ||
|
|
69f5cdd61a | ||
|
|
9f52f20660 | ||
|
|
90048afa9d | ||
|
|
1ec3ed32ee | ||
|
|
16ed5821e2 | ||
|
|
b681e33944 | ||
|
|
8f4d44a212 | ||
|
|
93a5d07f9a | ||
|
|
40c230c7db | ||
|
|
d0fa3b92f8 | ||
|
|
2a7727cf5f | ||
|
|
98722e7acf | ||
|
|
683fe4c3db | ||
|
|
e851696430 | ||
|
|
b2ccf965bb | ||
|
|
f847f38812 | ||
|
|
19039aa281 | ||
|
|
091b920a22 | ||
|
|
d7f264c1c2 | ||
|
|
897f8164b9 | ||
|
|
04a6f7c954 | ||
|
|
796287a776 | ||
|
|
763e30bd1d | ||
|
|
5121374d60 | ||
|
|
3e3a175bdc | ||
|
|
4a1aa9130b | ||
|
|
5a3f5f60b6 | ||
|
|
8c1600be55 | ||
|
|
7d3ae2347b | ||
|
|
27add47db4 | ||
|
|
836bcab036 | ||
|
|
2d20a07dfb | ||
|
|
24c5c70466 | ||
|
|
22ead22499 | ||
|
|
65e1620b43 | ||
|
|
acac09375e | ||
|
|
83cf859da3 | ||
|
|
956c391f13 | ||
|
|
7767a2607e | ||
|
|
429d1ac183 | ||
|
|
f42928f88f | ||
|
|
2d816ab92d | ||
|
|
aa75380662 | ||
|
|
3cf0ced62e | ||
|
|
41fa700fb3 | ||
|
|
a4ef9fb9b7 | ||
|
|
1db23b5277 | ||
|
|
584f0cbac0 | ||
|
|
dc432a19a3 | ||
|
|
6727b1cfca | ||
|
|
8c7b0698f3 | ||
|
|
69e1622e82 | ||
|
|
af7a5d3248 | ||
|
|
7b1a4554ad | ||
|
|
b3e213c133 | ||
|
|
a9fafec79d | ||
|
|
7cacf7bbaf | ||
|
|
44bf39f7b7 | ||
|
|
0db8f7295b | ||
|
|
c1226062b1 | ||
|
|
819ed168da | ||
|
|
19940953e2 | ||
|
|
f4af3eaf5d | ||
|
|
718563fe6a | ||
|
|
7e996ad6fa | ||
|
|
0b07fb6a49 | ||
|
|
0cb87e6ed9 | ||
|
|
71a3e75ae2 | ||
|
|
a2448ea4c1 | ||
|
|
8a14141021 | ||
|
|
69353892d9 | ||
|
|
7a2fcc3469 | ||
|
|
dfa6dd5011 | ||
|
|
654400c0cf | ||
|
|
3c15fc1b0d | ||
|
|
4b32485e3c | ||
|
|
b90736ed25 | ||
|
|
f28d5c7781 | ||
|
|
0654627478 | ||
|
|
2e91a2ecdb | ||
|
|
66a8ceb27a | ||
|
|
e6b0addb6c | ||
|
|
c00f132145 | ||
|
|
b9eb8d45b2 | ||
|
|
ec7ad70458 | ||
|
|
64e19699fb | ||
|
|
6d3e1b0cbe | ||
|
|
d3a53e1d3c | ||
|
|
23bbb3ee32 | ||
|
|
56bfcd52b7 | ||
|
|
1c19b6de1e | ||
|
|
85bed0bdf1 | ||
|
|
0102796769 | ||
|
|
50b2a72f1b | ||
|
|
04cc239756 | ||
|
|
0c20da2481 | ||
|
|
72d2a563f3 | ||
|
|
6f3f8c944d | ||
|
|
ed60fd2e82 | ||
|
|
636fef4693 | ||
|
|
77775e2f85 | ||
|
|
b260959a6e | ||
|
|
2a04f7b378 | ||
|
|
d4766f9c81 | ||
|
|
7f4c9b5b11 | ||
|
|
70f7c8ce89 | ||
|
|
f33a6aba39 | ||
|
|
236d717f10 | ||
|
|
0fc160f57c | ||
|
|
666023d89a | ||
|
|
8c4f2991c7 | ||
|
|
4453dff36d | ||
|
|
90b043561f | ||
|
|
43a8b34673 | ||
|
|
6005d33c94 | ||
|
|
cffb4736ce | ||
|
|
be54331d05 | ||
|
|
e19b8255d3 | ||
|
|
d680900f07 | ||
|
|
78bd4fd7de | ||
|
|
01d4a95d2d | ||
|
|
bb4490307d | ||
|
|
be492a3e3f | ||
|
|
a7b05f1e85 | ||
|
|
1c5ae4eb4d | ||
|
|
16d03d20ac | ||
|
|
8f062fab7d | ||
|
|
2565b934a5 | ||
|
|
7e88a9084d | ||
|
|
ef03292e35 | ||
|
|
d309ede623 | ||
|
|
834a80c35a | ||
|
|
99b0288d1b | ||
|
|
926f58f336 | ||
|
|
b7bfe99520 | ||
|
|
fa60b68425 | ||
|
|
8622e1e4ff | ||
|
|
3e13ae9740 | ||
|
|
e5cd577fbf | ||
|
|
86985231ff | ||
|
|
e64926a429 | ||
|
|
6232190cfe | ||
|
|
eb83d89307 | ||
|
|
b4a9941452 | ||
|
|
be782ba512 | ||
|
|
db54f247a2 | ||
|
|
0fc3b47c88 | ||
|
|
3eda2a1f2a | ||
|
|
c45b2adad6 | ||
|
|
702dbebfbe | ||
|
|
052303cc93 | ||
|
|
69380146e4 | ||
|
|
514fb56209 | ||
|
|
57b61070d9 | ||
|
|
86ce00365a | ||
|
|
a8bd32bef3 | ||
|
|
ae8801dd8a | ||
|
|
0a4e52e663 | ||
|
|
95045db74e | ||
|
|
19e7c0be0b | ||
|
|
3fcbf458b6 | ||
|
|
4580146cdb | ||
|
|
ea6a7c1c87 | ||
|
|
06cde721b3 | ||
|
|
31486e5963 | ||
|
|
1b52bdf425 | ||
|
|
9b59895ad2 | ||
|
|
c7600ff059 | ||
|
|
b1769edb65 | ||
|
|
14ad09e867 | ||
|
|
26a99d3696 | ||
|
|
c973730acc | ||
|
|
048bea376d | ||
|
|
eca4285ea8 | ||
|
|
437803da07 | ||
|
|
a7be7c3e19 | ||
|
|
5bd7dd3022 | ||
|
|
27aa0add4e | ||
|
|
24712c4c2d | ||
|
|
4bf4b4a045 | ||
|
|
9604f5c317 | ||
|
|
69c1059644 | ||
|
|
26b2903995 | ||
|
|
97abb256d5 | ||
|
|
a342911dae | ||
|
|
1f43563295 | ||
|
|
f23ebf1e99 | ||
|
|
d66b751c2e | ||
|
|
6a7c76a9ba | ||
|
|
2338787dbb | ||
|
|
a216c9cc37 | ||
|
|
b7da8d2193 | ||
|
|
41f191902b | ||
|
|
37397c7a69 | ||
|
|
153695288e | ||
|
|
a5e29758a4 | ||
|
|
964427e533 | ||
|
|
0a925df2a9 | ||
|
|
db95de6731 | ||
|
|
bc2191eae6 | ||
|
|
9fed844e59 | ||
|
|
a2c125ee90 | ||
|
|
a2779612be | ||
|
|
f51c3b6519 | ||
|
|
870968a7c5 | ||
|
|
c4f39ab85c | ||
|
|
8cb431ad40 | ||
|
|
77f98c2438 | ||
|
|
d7d0e5ec82 | ||
|
|
2519efb3ea | ||
|
|
b1c527cb71 | ||
|
|
b87f687e55 | ||
|
|
a62d27ffc2 | ||
|
|
a9a1c6c49b | ||
|
|
4989d6ddfa | ||
|
|
fd01b9de36 | ||
|
|
1017010e15 | ||
|
|
e0bd06489f | ||
|
|
4a6301c2af | ||
|
|
bca830dc54 | ||
|
|
77bde5325e | ||
|
|
bdca506dd4 | ||
|
|
34faaa28ca | ||
|
|
a08d433d0a | ||
|
|
6d08162012 | ||
|
|
2481650028 | ||
|
|
5e551fee41 | ||
|
|
e05690af4c | ||
|
|
8e65dd0546 | ||
|
|
509442a2f3 | ||
|
|
a5102d1b94 | ||
|
|
19ee2b3f34 | ||
|
|
5d29c2d881 | ||
|
|
a08bbab9dc | ||
|
|
84f6394f1f | ||
|
|
d359949310 | ||
|
|
30cd8a03eb | ||
|
|
1b7015c0df | ||
|
|
55b14641e0 | ||
|
|
4936d7fcc6 | ||
|
|
b8fa43296d | ||
|
|
631dd01c92 | ||
|
|
fdc72bc84e | ||
|
|
c63da9cd6e | ||
|
|
849d570bcb | ||
|
|
85e6efb8b0 | ||
|
|
485c8c3113 | ||
|
|
f2d0a8eb8c | ||
|
|
9201bc1022 | ||
|
|
2847a95c57 | ||
|
|
fc30bb9852 | ||
|
|
0f360d34e8 | ||
|
|
ec435e086f | ||
|
|
1efe7f7cde | ||
|
|
5759341c52 | ||
|
|
ea9ba27f22 | ||
|
|
a28bbce1f9 | ||
|
|
75b48aa8ac | ||
|
|
573e56cd8c | ||
|
|
ab28b4c0c5 | ||
|
|
55e36f24a5 | ||
|
|
ea8d6bf6c8 | ||
|
|
75b8bcb853 | ||
|
|
2b2cc2effc | ||
|
|
ae1c5908ae | ||
|
|
f067cb99c8 | ||
|
|
7e7d3a1659 | ||
|
|
c5f364f3e4 | ||
|
|
2a6366ef85 | ||
|
|
66fa855485 | ||
|
|
829b078b25 | ||
|
|
5cf1eacd1f | ||
|
|
7041cd7483 |
14
.eslintrc.js
@@ -3,10 +3,14 @@ module.exports = {
|
|||||||
parserOptions: {
|
parserOptions: {
|
||||||
sourceType: 'CommonJS',
|
sourceType: 'CommonJS',
|
||||||
},
|
},
|
||||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
'simple-import-sort',
|
||||||
|
'import'
|
||||||
|
],
|
||||||
extends: [
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:prettier/recommended',
|
|
||||||
'plugin:prettier/recommended'
|
'plugin:prettier/recommended'
|
||||||
],
|
],
|
||||||
globals: {
|
globals: {
|
||||||
@@ -26,7 +30,11 @@ module.exports = {
|
|||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
'@typescript-eslint/no-empty-function': 'off',
|
'@typescript-eslint/no-empty-function': 'off',
|
||||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
'@typescript-eslint/no-unused-vars': 'error',
|
||||||
|
'import/first': 'error',
|
||||||
|
'import/no-duplicates': 'error',
|
||||||
|
'simple-import-sort/imports': 'error',
|
||||||
|
'simple-import-sort/exports': 'error',
|
||||||
'@typescript-eslint/ban-types': [
|
'@typescript-eslint/ban-types': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
|||||||
107
.github/ISSUE_TEMPLATE/-en--bug-report.yaml
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Create a report to help us improve.
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
- en
|
||||||
|
# - help wanted
|
||||||
|
# Automatically assign the issue to:
|
||||||
|
# assignees: DavidsonGomes
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Welcome!
|
||||||
|
description: |
|
||||||
|
The issue tracker is only for reporting bugs and feature requests.
|
||||||
|
For user-related support questions, please visit:
|
||||||
|
- [Discord](https://evolution-api.com/discord)
|
||||||
|
- [WhatsApp Group](https://evolution-api.com/whatsapp)
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
**DO NOT OPEN AN ISSUE FOR GENERAL SUPPORT QUESTIONS.**
|
||||||
|
|
||||||
|
options:
|
||||||
|
- label: Yes, I have searched for similar issues on [GitHub](https://github.com/EvolutionAPI/evolution-api/issues) and found none.
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: What did you do?
|
||||||
|
description: |
|
||||||
|
How to write a good bug report?
|
||||||
|
|
||||||
|
- Respect the issue template as much as possible.
|
||||||
|
- The title should be short and descriptive.
|
||||||
|
- Explain the conditions that led you to report this issue: the context.
|
||||||
|
- The context should lead to something, an idea or problem you are facing.
|
||||||
|
- Be clear and concise.
|
||||||
|
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
|
||||||
|
placeholder: Describe the problem you encountered in detail.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: What did you expect?
|
||||||
|
placeholder: Describe what you expected to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: What did you observe instead of what you expected?
|
||||||
|
placeholder: Explain what actually happens when you follow the steps above.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Screenshots/Videos
|
||||||
|
placeholder: |
|
||||||
|
If possible, add screenshots or videos that illustrate the problem. This can be extremely helpful in understanding the issue better.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Which version of the API are you using?
|
||||||
|
description: |
|
||||||
|
Enter the version number found in the `version` property of the **package.json**.
|
||||||
|
placeholder: Paste the version here.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: select
|
||||||
|
attributes:
|
||||||
|
label: What is your environment?
|
||||||
|
options:
|
||||||
|
- Windows
|
||||||
|
- Linux
|
||||||
|
- Mac
|
||||||
|
- Docker
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Other environment specifications
|
||||||
|
placeholder: 'Hardware/Software: [e.g., CPU, GPU]'
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: If applicable, paste the log output
|
||||||
|
description: |
|
||||||
|
Please attach any logs that might be related to the issue.
|
||||||
|
If the logs contain sensitive information, consider sending them privately to one of the project maintainers.
|
||||||
|
placeholder: Paste the application log here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional Notes
|
||||||
|
description: Include any other information you think might be useful to understand or resolve the bug.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
86
.github/ISSUE_TEMPLATE/-en--feature-request.yaml
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Suggest ideas for the project.
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
- en
|
||||||
|
# Automatically assign the issue to:
|
||||||
|
# assignees: DavidsonGomes
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Welcome!
|
||||||
|
description: '**DO NOT OPEN FOR GENERAL SUPPORT QUESTIONS.**'
|
||||||
|
|
||||||
|
options:
|
||||||
|
- label: Yes, I have searched for similar requests on [GitHub](https://github.com/code-chat-br/whatsapp-api/issues) and found none.
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: What type of feature?
|
||||||
|
description: |
|
||||||
|
How to write a good feature request?
|
||||||
|
|
||||||
|
- Respect the issue template as much as possible.
|
||||||
|
- The title should be short and descriptive.
|
||||||
|
- Explain the conditions that led you to suggest this feature: the context.
|
||||||
|
- The context should lead to something, an idea or problem you are facing.
|
||||||
|
- Be clear and concise.
|
||||||
|
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
|
||||||
|
options:
|
||||||
|
- Integration
|
||||||
|
- Functionality
|
||||||
|
- Endpoint
|
||||||
|
- Database adjustment
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: What is the motivation for the request?
|
||||||
|
description: |
|
||||||
|
What problem does the feature seek to solve?
|
||||||
|
Clearly and in detail describe the functionality you want to be implemented.
|
||||||
|
Explain how it fits into the context of the project.
|
||||||
|
placeholder: Detailed description
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Usage Examples
|
||||||
|
description: |
|
||||||
|
Provide specific examples of how this functionality could be used.
|
||||||
|
This can include scenarios or use cases where the feature would be particularly beneficial.
|
||||||
|
placeholder: text - image - video - flowcharts
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: How should the feature be developed?
|
||||||
|
description: |
|
||||||
|
Should it be inserted directly into the code?
|
||||||
|
Should it be built as a different application that acts as an extension of the API?
|
||||||
|
For example: a `worker`?
|
||||||
|
|
||||||
|
Discuss how this new functionality could impact other parts of the project, if applicable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If you have ideas on how this functionality could be implemented, please share them here.
|
||||||
|
This is not mandatory, but it can be helpful for the development team.
|
||||||
|
|
||||||
|
placeholder: Insert feature ideas here
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional Notes
|
||||||
|
description: Any other information you believe is relevant to your request.
|
||||||
|
placeholder: Insert your observations here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
108
.github/ISSUE_TEMPLATE/-pt--reportar-bug.yaml
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
name: Relatório de bug
|
||||||
|
description: Crie um relatório para nos ajudar a melhorar.
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
- pt-br
|
||||||
|
# - help wanted
|
||||||
|
# Atrubuir automaticamente a issue a:
|
||||||
|
# assignees: DavidsonGomes
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: termos
|
||||||
|
attributes:
|
||||||
|
label: Bem-vido!
|
||||||
|
description: |
|
||||||
|
O rastreador de problemas é apenas para relatar bugs e solicitações de recursos.
|
||||||
|
Para perguntas de suporte relacionadas ao usuário final, acesse:
|
||||||
|
- [Discord](https://evolution-api.com/discord)
|
||||||
|
- [Grupo do WhatsApp](https://evolution-api.com/whatsapp)
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
**NÃO ABRA UM PROBLEMA PARA PERGUNTAS GERAIS DE SUPORTE.**
|
||||||
|
|
||||||
|
options:
|
||||||
|
- label: Sim, pesquisei problemas semelhantes no [GitHub](https://github.com/EvolutionAPI/evolution-api/issues) e não encontrei nenhum.
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: O que você fez?
|
||||||
|
description: |
|
||||||
|
Como escrever um bom relatório de bug?
|
||||||
|
|
||||||
|
- Respeite o modelo de problema tanto quanto possível.
|
||||||
|
- O título deve ser curto e descritivo.
|
||||||
|
- Explique as condições que o levaram a reportar este problema: o contexto.
|
||||||
|
- O contexto deve levar a algo, ideia ou problema que você está enfrentando.
|
||||||
|
- Seja claro e conciso.
|
||||||
|
- Formate suas mensagens para ajudar o leitor a se concentrar no que importa e entender a estrutura da sua mensagem, use [sintaxe Markdown](https://help.github.com/articles/github-flavored-markdown)
|
||||||
|
placeholder: Descreva detalhadamente o problema que você encontrou.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: O que você esperava?
|
||||||
|
placeholder: Descreva o que você esperava que acontecesse.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: O que vc observou ao invés do que esperava?
|
||||||
|
placeholder: Explique o que realmente acontece quando você segue os passos acima.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Capturas de Tela/Vídeos
|
||||||
|
placeholder: |
|
||||||
|
Se possível, adicione capturas de tela ou vídeos que ilustrem o problema. Isso pode ser extremamente útil para entender melhor o problema.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Qual versão da API você está usando?
|
||||||
|
description: |
|
||||||
|
Insira o número da sua versão que está na propriedade `version` do **package.json**.
|
||||||
|
placeholder: Cole aqui a versão.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: select
|
||||||
|
attributes:
|
||||||
|
label: Qual é o seu ambiente?
|
||||||
|
options:
|
||||||
|
- Windows
|
||||||
|
- Linux
|
||||||
|
- Mac
|
||||||
|
- Docker
|
||||||
|
- Outro
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Outras expecificações do ambiente
|
||||||
|
placeholder: 'Hardware/Software: [ex: CPU, GPU]'
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Se aplicável, cole a saída do log
|
||||||
|
description: |
|
||||||
|
Por favor, anexe os logs que possam estar relacionados ao problema.
|
||||||
|
Se os logs contiverem informações sensíveis, considere enviá-los de forma privada para um dos mantenedores do projeto.
|
||||||
|
placeholder: Cole aqui o log da aplicação
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Notas Adicionais
|
||||||
|
description: Inclua aqui qualquer outra informação que você ache que possa ser útil para entender ou resolver o bug.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
86
.github/ISSUE_TEMPLATE/-pt--solicitar-recurso.yaml
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
name: Solicitação de recursos
|
||||||
|
description: Sugira ideias para o projeto.
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
- pt-br
|
||||||
|
# Automatically assign the issue to:
|
||||||
|
# assignees: DavidsonGomes
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: termos
|
||||||
|
attributes:
|
||||||
|
label: Bem-vindo!
|
||||||
|
description: '**NÃO ABRA PARA PERGUNTAS GERAIS DE SUPORTE.**'
|
||||||
|
|
||||||
|
options:
|
||||||
|
- label: Sim, pesquisei solicitações semelhantes no [GitHub](https://github.com/code-chat-br/whatsapp-api/issues) e não encontrei nenhum.
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Qual tipo de recurso?
|
||||||
|
description: |
|
||||||
|
Como escrever uma boa solicitação de bug?
|
||||||
|
|
||||||
|
- Respeite o modelo de problema tanto quanto possível.
|
||||||
|
- O título deve ser curto e descritivo.
|
||||||
|
- Explique as condições que o levaram a reportar este problema: o contexto.
|
||||||
|
- O contexto deve levar a algo, ideia ou problema que você está enfrentando.
|
||||||
|
- Seja claro e conciso.
|
||||||
|
- Formate suas mensagens para ajudar o leitor a se concentrar no que importa e entender a estrutura da sua mensagem, use [sintaxe Markdown](https://help.github.com/articles/github-flavored-markdown)
|
||||||
|
options:
|
||||||
|
- Integração
|
||||||
|
- Funcionalidade
|
||||||
|
- Endpoint
|
||||||
|
- Ajuste de banco de dados
|
||||||
|
- Outro
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Qual a motivação para a solicitação?
|
||||||
|
description: |
|
||||||
|
Qual problema o recurso busca resolver?
|
||||||
|
Descreva claramente e em detalhes a funcionalidade que você deseja que seja implementada.
|
||||||
|
Explique como isso se encaixa no contexto do projeto.
|
||||||
|
placeholder: Descrição detalhada
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Exemplos de Uso
|
||||||
|
description: |
|
||||||
|
Forneça exemplos específicos de como essa funcionalidade poderia ser utilizada.
|
||||||
|
Isso pode incluir cenários ou casos de uso onde a funcionalidade seria particularmente benéfica.
|
||||||
|
placeholder: texto - imagem - video - fluxogramas
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Como o recurso deve ser desenvolvido?
|
||||||
|
description: |
|
||||||
|
Deve ser inserido diretamente no código?
|
||||||
|
Deve ser construído uma aplicação diferente que atuará como uma extenção da api?
|
||||||
|
Por exemplo: um `worker`?
|
||||||
|
|
||||||
|
Discuta como essa nova funcionalidade poderia impactar outras partes do projeto, se aplicável.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Se você tem ideias sobre como essa funcionalidade pode ser implementada, por favor, compartilhe-as aqui.
|
||||||
|
Isso não é obrigatório, mas pode ser útil para a equipe de desenvolvimento.
|
||||||
|
|
||||||
|
placeholder: Insira ideias para o recurso
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Notas Adicionais
|
||||||
|
description: Qualquer outra informação que você acredita ser relevante para a sua solicitação.
|
||||||
|
placeholder: Insira aqui as sua observções.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
48
.github/workflows/publish_docker_image.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: Build Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*.*.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_deploy:
|
||||||
|
name: Build and Deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: atendai/evolution-api
|
||||||
|
tags: type=semver,pattern=v{{version}}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Image digest
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||||
48
.github/workflows/publish_docker_image_homolog.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: Build Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_deploy:
|
||||||
|
name: Build and Deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: atendai/evolution-api
|
||||||
|
tags: homolog
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Image digest
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||||
48
.github/workflows/publish_docker_image_v2.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: Build Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- v2.0.0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_deploy:
|
||||||
|
name: Build and Deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: atendai/evolution-api
|
||||||
|
tags: v2.0.0-alpha
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Image digest
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||||
20
.gitignore
vendored
@@ -2,6 +2,10 @@
|
|||||||
/dist
|
/dist
|
||||||
/node_modules
|
/node_modules
|
||||||
|
|
||||||
|
/Docker/.env
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs/**.json
|
logs/**.json
|
||||||
*.log
|
*.log
|
||||||
@@ -11,16 +15,22 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
|
/docker-compose-data
|
||||||
|
/docker-data
|
||||||
|
|
||||||
|
docker-compose.yaml
|
||||||
|
|
||||||
# Package
|
# Package
|
||||||
/yarn.lock
|
/yarn.lock
|
||||||
/package-lock.json
|
/package-lock.json
|
||||||
|
|
||||||
# IDE - VSCode
|
# IDEs
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
.nova/*
|
||||||
|
|
||||||
# Prisma
|
# Prisma
|
||||||
/prisma/migrations
|
/prisma/migrations
|
||||||
@@ -30,3 +40,11 @@ lerna-debug.log*
|
|||||||
!/instances/.gitkeep
|
!/instances/.gitkeep
|
||||||
/test/
|
/test/
|
||||||
/src/env.yml
|
/src/env.yml
|
||||||
|
/store
|
||||||
|
*.env
|
||||||
|
|
||||||
|
/temp/*
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
*.DS_Store
|
||||||
|
.tool-versions
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ module.exports = {
|
|||||||
semi: true,
|
semi: true,
|
||||||
trailingComma: 'all',
|
trailingComma: 'all',
|
||||||
singleQuote: true,
|
singleQuote: true,
|
||||||
printWidth: 90,
|
printWidth: 120,
|
||||||
|
arrowParens: 'always',
|
||||||
tabWidth: 2,
|
tabWidth: 2,
|
||||||
bracketSameLine: true,
|
useTabs: false,
|
||||||
bracketSpacing: true
|
bracketSameLine: false,
|
||||||
|
bracketSpacing: true,
|
||||||
|
parser: 'typescript'
|
||||||
}
|
}
|
||||||
11
.vscode/settings.json
vendored
@@ -5,7 +5,12 @@
|
|||||||
"editor.smoothScrolling": true,
|
"editor.smoothScrolling": true,
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true,
|
"source.fixAll.eslint": "explicit",
|
||||||
"source.fixAll": true
|
"source.fixAll": "explicit"
|
||||||
}
|
},
|
||||||
|
"prisma-smart-formatter.typescript.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"prisma-smart-formatter.prisma.defaultFormatter": "Prisma.prisma",
|
||||||
|
"i18n-ally.localesPaths": [
|
||||||
|
"store/messages"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
559
CHANGELOG.md
@@ -1,3 +1,562 @@
|
|||||||
|
# 1.8.1 (2024-06-08 21:32)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* New method of saving sessions to a file using worker, made in partnership with [codechat](https://github.com/code-chat-br/whatsapp-api)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Correction of variables breaking lines in typebot
|
||||||
|
|
||||||
|
# 1.8.0 (2024-05-27 16:10)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Now in the manager, when logging in with the client's apikey, the listing only shows the instance corresponding to the provided apikey (only with MongoDB)
|
||||||
|
* New global mode for rabbitmq events
|
||||||
|
* Build in docker for linux/amd64, linux/arm64 platforms
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Correction in message formatting when generated by AI as markdown in typebot
|
||||||
|
* Security fix in fetch instance with client key when not connected to mongodb
|
||||||
|
|
||||||
|
# 1.7.5 (2024-05-21 08:50)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Add merge_brazil_contacts function to solve nine digit in brazilian numbers
|
||||||
|
* Optimize ChatwootService method for updating contact
|
||||||
|
* Fix swagger auth
|
||||||
|
* Update aws sdk v3
|
||||||
|
* Fix getOpenConversationByContact and init queries error
|
||||||
|
* Method to mark chat as unread
|
||||||
|
* Added environment variable to manually select the WhatsApp web version for the baileys lib (optional)
|
||||||
|
|
||||||
|
# 1.7.4 (2024-04-28 09:46)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Adjusts in proxy on fetchAgent
|
||||||
|
* Recovering messages lost with redis cache
|
||||||
|
* Log when init redis cache service
|
||||||
|
* Recovering messages lost with redis cache
|
||||||
|
* Chatwoot inbox name
|
||||||
|
* Update Baileys version
|
||||||
|
|
||||||
|
# 1.7.3 (2024-04-18 12:07)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Revert fix audio encoding
|
||||||
|
* Recovering messages lost with redis cache
|
||||||
|
* Adjusts in redis for save instances
|
||||||
|
* Adjusts in proxy
|
||||||
|
* Revert pull request #523
|
||||||
|
* Added instance name on logs
|
||||||
|
* Added support for Spanish
|
||||||
|
* Fix error: invalid operator. The allowed operators for identifier are equal_to,not_equal_to in chatwoot
|
||||||
|
|
||||||
|
# 1.7.2 (2024-04-12 17:31)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Mobile connection via sms (test)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Adjusts in redis
|
||||||
|
* Send global event in websocket
|
||||||
|
* Adjusts in proxy
|
||||||
|
* Fix audio encoding
|
||||||
|
* Fix conversation read on chatwoot version 3.7
|
||||||
|
* Fix when receiving/sending messages from whatsapp desktop with ephemeral messages enabled
|
||||||
|
* Changed returned sessions on typebot status change
|
||||||
|
* Reorganization of files and folders
|
||||||
|
|
||||||
|
# 1.7.1 (2024-04-03 10:19)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Correction when sending files with captions on Whatsapp Business
|
||||||
|
* Correction in receiving messages with response on WhatsApp Business
|
||||||
|
* Correction when sending a reaction to a message on WhatsApp Business
|
||||||
|
* Correction of receiving reactions on WhatsApp business
|
||||||
|
* Removed mandatory description of rows from sendList
|
||||||
|
* Feature to collect message type in typebot
|
||||||
|
|
||||||
|
# 1.7.0 (2024-03-11 18:23)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Added update message endpoint
|
||||||
|
* Add translate capabilities to QRMessages in CW
|
||||||
|
* Join in Group by Invite Code
|
||||||
|
* Read messages from whatsapp in chatwoot
|
||||||
|
* Add support to use use redis in cacheservice
|
||||||
|
* Add support for labels
|
||||||
|
* Command to clearcache from chatwoot inbox
|
||||||
|
* Whatsapp Cloud API Oficial
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Proxy configuration improvements
|
||||||
|
* Correction in sending lists
|
||||||
|
* Adjust in webhook_base64
|
||||||
|
* Correction in typebot text formatting
|
||||||
|
* Correction in chatwoot text formatting and render list message
|
||||||
|
* Only use a axios request to get file mimetype if necessary
|
||||||
|
* When possible use the original file extension
|
||||||
|
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
|
||||||
|
* Remove message ids cache in chatwoot to use chatwoot's api itself
|
||||||
|
* Adjusts the quoted message, now has contextInfo in the message Raw
|
||||||
|
* Collecting responses with text or numbers in Typebot
|
||||||
|
* Added sendList endpoint to swagger documentation
|
||||||
|
* Implemented a function to synchronize message deletions on WhatsApp, automatically reflecting in Chatwoot.
|
||||||
|
* Improvement on numbers validation
|
||||||
|
* Fix polls in message sending
|
||||||
|
* Sending status message
|
||||||
|
* Message 'connection successfully' spamming
|
||||||
|
* Invalidate the conversation cache if reopen_conversation is false and the conversation was resolved
|
||||||
|
* Fix looping when deleting a message in chatwoot
|
||||||
|
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
|
||||||
|
* Correction in the sendList Function
|
||||||
|
* Implement contact upsert in messaging-history.set
|
||||||
|
* Improve proxy error handling
|
||||||
|
* Refactor fetching participants for group in WhatsApp service
|
||||||
|
* Fixed problem where the typebot final keyword did not work
|
||||||
|
* Typebot's wait now pauses the flow and composing is defined by the delay_message parameter in set typebot
|
||||||
|
* Composing over 20s now loops until finished
|
||||||
|
|
||||||
|
# 1.6.1 (2023-12-22 11:43)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed Lid Messages
|
||||||
|
* Fixed sending variables to typebot
|
||||||
|
* Fixed sending variables from typebot
|
||||||
|
* Correction sending s3/minio media to chatwoot and typebot
|
||||||
|
* Fixed the problem with typebot closing at the end of the flow, now this is optional with the TYPEBOT_KEEP_OPEN variable
|
||||||
|
* Fixed chatwoot Bold, Italic and Underline formatting using Regex
|
||||||
|
* Added the sign_delimiter property to the Chatwoot configuration, allowing you to set a different delimiter for the signature. Default when not defined \n
|
||||||
|
* Include instance Id field in the instance configuration
|
||||||
|
* Fixed the pairing code
|
||||||
|
* Adjusts in typebot
|
||||||
|
* Fix the problem when disconnecting the instance and connecting again using mongodb
|
||||||
|
* Options to disable docs and manager
|
||||||
|
* When deleting a message in whatsapp, delete the message in chatwoot too
|
||||||
|
|
||||||
|
|
||||||
|
# 1.6.0 (2023-12-12 17:24)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Added AWS SQS Integration
|
||||||
|
* Added support for new typebot API
|
||||||
|
* Added endpoint sendPresence
|
||||||
|
* New Instance Manager
|
||||||
|
* Added auto_create to the chatwoot set to create the inbox automatically or not
|
||||||
|
* Added reply, delete and message reaction in chatwoot v3.3.1
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Adjusts in proxy
|
||||||
|
* Adjusts in start session for Typebot
|
||||||
|
* Added mimetype field when sending media
|
||||||
|
* Ajusts in validations to messages.upsert
|
||||||
|
* Fixed messages not received: error handling when updating contact in chatwoot
|
||||||
|
* Fix workaround to manage param data as an array in mongodb
|
||||||
|
* Removed await from webhook when sending a message
|
||||||
|
* Update typebot.service.ts - element.underline change ~ for *
|
||||||
|
* Removed api restart on receiving an error
|
||||||
|
* Fixes in mongodb and chatwoot
|
||||||
|
* Adjusted return from queries in mongodb
|
||||||
|
* Added restart instance when update profile picture
|
||||||
|
* Correction of chatwoot functioning with admin flows
|
||||||
|
* Fixed problem that did not generate qrcode with the chatwoot_conversation_pending option enabled
|
||||||
|
* Fixed issue where CSAT opened a new ticket when reopen_conversation was disabled
|
||||||
|
* Fixed issue sending contact to Chatwoot via iOS
|
||||||
|
|
||||||
|
### Integrations
|
||||||
|
|
||||||
|
* Chatwoot: v3.3.1
|
||||||
|
* Typebot: v2.20.0
|
||||||
|
|
||||||
|
# 1.5.4 (2023-10-09 20:43)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Baileys logger typing issue resolved
|
||||||
|
* Solved problem with duplicate messages in chatwoot
|
||||||
|
|
||||||
|
# 1.5.3 (2023-10-06 18:55)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Swagger documentation
|
||||||
|
* Added base 64 sending option via webhook
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Remove rabbitmq queues when delete instances
|
||||||
|
* Improvement in restart instance to completely redo the connection
|
||||||
|
* Update node version: v20
|
||||||
|
* Correction of messages sent by the api and typebot not appearing in chatwoot
|
||||||
|
* Adjustment to start typebot, added startSession parameter
|
||||||
|
* Chatwoot now receives messages sent via api and typebot
|
||||||
|
* Fixed problem with starting with an input in typebot
|
||||||
|
* Added check to ensure variables are not empty before executing foreach in start typebot
|
||||||
|
|
||||||
|
# 1.5.2 (2023-09-28 17:56)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fix chatwootSchema in chatwoot model to store reopen_conversation and conversation_pending options
|
||||||
|
* Problem resolved when sending files from minio to typebot
|
||||||
|
* Improvement in the "startTypebot" method to create persistent session when triggered
|
||||||
|
* New manager for Evo 1.5.2 - Set Typebot update
|
||||||
|
* Resolved problems when reading/querying instances
|
||||||
|
|
||||||
|
# 1.5.1 (2023-09-17 13:50)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Added listening_from_me option in Set Typebot
|
||||||
|
* Added variables options in Start Typebot
|
||||||
|
* Added webhooks for typebot events
|
||||||
|
* Added ChamaAI integration
|
||||||
|
* Added webhook to send errors
|
||||||
|
* Added support for messaging with ads on chatwoot
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fix looping connection messages in chatwoot
|
||||||
|
* Improved performance of fetch instances
|
||||||
|
|
||||||
|
# 1.5.0 (2023-08-18 12:47)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* New instance manager in /manager route
|
||||||
|
* Added extra files for chatwoot and appsmith
|
||||||
|
* Added Get Last Message and Archive for Chat
|
||||||
|
* Added env var QRCODE_COLOR
|
||||||
|
* Added websocket to send events
|
||||||
|
* Added rabbitmq to send events
|
||||||
|
* Added Typebot integration
|
||||||
|
* Added proxy endpoint
|
||||||
|
* Added send and date_time in webhook data
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Solved problem when disconnecting from the instance the instance was deleted
|
||||||
|
* Encoded spaces in chatwoot webhook
|
||||||
|
* Adjustment in the saving of contacts, saving the information of the number and Jid
|
||||||
|
* Update Dockerfile
|
||||||
|
* If you pass empty events in create instance and set webhook it is understood as all
|
||||||
|
* Fixed issue that did not output base64 averages
|
||||||
|
* Messages sent by the api now arrive in chatwoot
|
||||||
|
|
||||||
|
### Integrations
|
||||||
|
|
||||||
|
* Chatwoot: v2.18.0 - v3.0.0
|
||||||
|
* Typebot: v2.16.0
|
||||||
|
* Manager Evolution API
|
||||||
|
|
||||||
|
# 1.4.8 (2023-07-27 10:27)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed error return bug
|
||||||
|
|
||||||
|
# 1.4.7 (2023-07-27 08:47)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed error return bug
|
||||||
|
* Fixed problem of getting message when deleting message in chatwoot
|
||||||
|
* Change in error return pattern
|
||||||
|
|
||||||
|
# 1.4.6 (2023-07-26 17:54)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed bug of creating new inbox by chatwoot
|
||||||
|
* When conversation reopens is pending when conversation pending is true
|
||||||
|
* Added docker-compose file with dockerhub image
|
||||||
|
|
||||||
|
# 1.4.5 (2023-07-26 09:32)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed problems in localization template in chatwoot
|
||||||
|
* Fix mids going duplicated in chatwoot
|
||||||
|
|
||||||
|
# 1.4.4 (2023-07-25 15:24)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed chatwoot line wrap issue
|
||||||
|
* Solved receive location in chatwoot
|
||||||
|
* When requesting the pairing code, it also brings the qr code
|
||||||
|
* Option reopen_conversation in chatwoot endpoint
|
||||||
|
* Option conversation_pending in chatwoot endpoint
|
||||||
|
|
||||||
|
# 1.4.3 (2023-07-25 10:51)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Adjusts in settings with options always_online, read_messages and read_status
|
||||||
|
* Fixed send webhook for event CALL
|
||||||
|
* Create instance with settings
|
||||||
|
|
||||||
|
# 1.4.2 (2023-07-24 20:52)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed validation is set settings
|
||||||
|
* Adjusts in group validations
|
||||||
|
* Ajusts in sticker message to chatwoot
|
||||||
|
|
||||||
|
# 1.4.1 (2023-07-24 18:28)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed reconnect with pairing code or qrcode
|
||||||
|
* Fixed problem in createJid
|
||||||
|
|
||||||
|
# 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)
|
# 1.0.8 (2023-06-09)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
80
Docker/.env
@@ -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
|
|
||||||
174
Docker/.env.example
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# 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
|
||||||
|
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
|
||||||
|
|
||||||
|
# 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=false
|
||||||
|
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
|
||||||
|
|
||||||
|
RABBITMQ_ENABLED=false
|
||||||
|
RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
|
||||||
|
RABBITMQ_EXCHANGE_NAME=evolution_exchange
|
||||||
|
RABBITMQ_GLOBAL_ENABLED=false
|
||||||
|
RABBITMQ_EVENTS_APPLICATION_STARTUP=false
|
||||||
|
RABBITMQ_EVENTS_QRCODE_UPDATED=true
|
||||||
|
RABBITMQ_EVENTS_MESSAGES_SET=true
|
||||||
|
RABBITMQ_EVENTS_MESSAGES_UPSERT=true
|
||||||
|
RABBITMQ_EVENTS_MESSAGES_UPDATE=true
|
||||||
|
RABBITMQ_EVENTS_MESSAGES_DELETE=true
|
||||||
|
RABBITMQ_EVENTS_SEND_MESSAGE=true
|
||||||
|
RABBITMQ_EVENTS_CONTACTS_SET=true
|
||||||
|
RABBITMQ_EVENTS_CONTACTS_UPSERT=true
|
||||||
|
RABBITMQ_EVENTS_CONTACTS_UPDATE=true
|
||||||
|
RABBITMQ_EVENTS_PRESENCE_UPDATE=true
|
||||||
|
RABBITMQ_EVENTS_CHATS_SET=true
|
||||||
|
RABBITMQ_EVENTS_CHATS_UPSERT=true
|
||||||
|
RABBITMQ_EVENTS_CHATS_UPDATE=true
|
||||||
|
RABBITMQ_EVENTS_CHATS_DELETE=true
|
||||||
|
RABBITMQ_EVENTS_GROUPS_UPSERT=true
|
||||||
|
RABBITMQ_EVENTS_GROUPS_UPDATE=true
|
||||||
|
RABBITMQ_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||||
|
RABBITMQ_EVENTS_CONNECTION_UPDATE=true
|
||||||
|
RABBITMQ_EVENTS_LABELS_EDIT=true
|
||||||
|
RABBITMQ_EVENTS_LABELS_ASSOCIATION=true
|
||||||
|
RABBITMQ_EVENTS_CALL=true
|
||||||
|
RABBITMQ_EVENTS_TYPEBOT_START=false
|
||||||
|
RABBITMQ_EVENTS_TYPEBOT_CHANGE_STATUS=false
|
||||||
|
|
||||||
|
WEBSOCKET_ENABLED=false
|
||||||
|
WEBSOCKET_GLOBAL_EVENTS=false
|
||||||
|
|
||||||
|
WA_BUSINESS_TOKEN_WEBHOOK=evolution
|
||||||
|
WA_BUSINESS_URL=https://graph.facebook.com
|
||||||
|
WA_BUSINESS_VERSION=v18.0
|
||||||
|
WA_BUSINESS_LANGUAGE=pt_BR
|
||||||
|
|
||||||
|
SQS_ENABLED=false
|
||||||
|
SQS_ACCESS_KEY_ID=
|
||||||
|
SQS_SECRET_ACCESS_KEY=
|
||||||
|
SQS_ACCOUNT_ID=
|
||||||
|
SQS_REGION=
|
||||||
|
|
||||||
|
# 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
|
||||||
|
WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||||
|
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
|
||||||
|
WEBHOOK_EVENTS_CALL=true
|
||||||
|
# This event fires every time a new token is requested via the refresh route
|
||||||
|
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||||
|
# This events is used with Typebot
|
||||||
|
WEBHOOK_EVENTS_TYPEBOT_START=false
|
||||||
|
WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
|
||||||
|
# This event is used with Chama AI
|
||||||
|
WEBHOOK_EVENTS_CHAMA_AI_ACTION=false
|
||||||
|
# This event is used to send errors
|
||||||
|
WEBHOOK_EVENTS_ERRORS=false
|
||||||
|
WEBHOOK_EVENTS_ERRORS_WEBHOOK=
|
||||||
|
|
||||||
|
# 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
|
||||||
|
QRCODE_COLOR='#198754'
|
||||||
|
|
||||||
|
# old | latest
|
||||||
|
TYPEBOT_API_VERSION=latest
|
||||||
|
TYPEBOT_KEEP_OPEN=false
|
||||||
|
|
||||||
|
#Chatwoot
|
||||||
|
# If you leave this option as false, when deleting the message for everyone on WhatsApp, it will not be deleted on Chatwoot.
|
||||||
|
CHATWOOT_MESSAGE_DELETE=false # false | true
|
||||||
|
# If you leave this option as true, when sending a message in Chatwoot, the client's last message will be marked as read on WhatsApp.
|
||||||
|
CHATWOOT_MESSAGE_READ=false # false | true
|
||||||
|
# This db connection is used to import messages from whatsapp to chatwoot database
|
||||||
|
CHATWOOT_IMPORT_DATABASE_CONNECTION_URI=postgres://user:password@hostname:port/dbname?sslmode=disable
|
||||||
|
CHATWOOT_IMPORT_DATABASE_PLACEHOLDER_MEDIA_MESSAGE=true
|
||||||
|
|
||||||
|
CACHE_REDIS_ENABLED=false
|
||||||
|
CACHE_REDIS_URI=redis://redis:6379
|
||||||
|
CACHE_REDIS_PREFIX_KEY=evolution
|
||||||
|
CACHE_REDIS_TTL=604800
|
||||||
|
CACHE_REDIS_SAVE_INSTANCES=false
|
||||||
|
CACHE_LOCAL_ENABLED=false
|
||||||
|
CACHE_LOCAL_TTL=604800
|
||||||
|
|
||||||
|
# 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`'
|
||||||
|
|
||||||
|
LANGUAGE=en # pt-BR, en
|
||||||
22
Docker/docker-compose.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
api:
|
||||||
|
container_name: evolution_api
|
||||||
|
image: atendai/evolution-api
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- evolution_instances:/evolution/instances
|
||||||
|
- evolution_store:/evolution/store
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
command: ['node', './dist/src/main.js']
|
||||||
|
expose:
|
||||||
|
- 8080
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
evolution_instances:
|
||||||
|
evolution_store:
|
||||||
119
Docker/evolution-api-all-services/.env.example
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# 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
|
||||||
|
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
|
||||||
|
|
||||||
|
# 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=evolution
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||||
|
WEBHOOK_EVENTS_LABELS_ASSOCIATION=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='Evolution API'
|
||||||
|
# Browser Name = chrome | firefox | edge | opera | safari
|
||||||
|
CONFIG_SESSION_PHONE_NAME=chrome
|
||||||
|
|
||||||
|
# Set qrcode display limit
|
||||||
|
QRCODE_LIMIT=30
|
||||||
|
|
||||||
|
CACHE_REDIS_ENABLED=false
|
||||||
|
CACHE_REDIS_URI=redis://redis:6379
|
||||||
|
CACHE_REDIS_PREFIX_KEY=evolution
|
||||||
|
CACHE_REDIS_TTL=604800
|
||||||
|
CACHE_REDIS_SAVE_INSTANCES=false
|
||||||
|
CACHE_LOCAL_ENABLED=false
|
||||||
|
CACHE_LOCAL_TTL=604800
|
||||||
|
|
||||||
|
# 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='L0YWtjb2w554WFqPG'
|
||||||
|
# Set the instance name and webhook url to create an instance in init the application
|
||||||
|
# With this option activated, you work with a url per webhook event, respecting the local url and the name of each event
|
||||||
|
# container or server
|
||||||
|
AUTHENTICATION_INSTANCE_MODE=server
|
||||||
|
# if you are using container mode, set the container name and the webhook url to default instance
|
||||||
|
AUTHENTICATION_INSTANCE_NAME=evolution
|
||||||
|
AUTHENTICATION_INSTANCE_WEBHOOK_URL=''
|
||||||
|
AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1
|
||||||
|
AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456
|
||||||
|
AUTHENTICATION_INSTANCE_CHATWOOT_URL=''
|
||||||
91
Docker/evolution-api-all-services/docker-compose.yaml
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
mongodb:
|
||||||
|
container_name: mongodb
|
||||||
|
image: mongo
|
||||||
|
restart: on-failure
|
||||||
|
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:
|
||||||
|
container_name: mongodb-express
|
||||||
|
image: mongo-express
|
||||||
|
restart: on-failure
|
||||||
|
ports:
|
||||||
|
- 8081:8081
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
||||||
|
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
|
||||||
|
links:
|
||||||
|
- mongodb
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: redis
|
||||||
|
image: redis:latest
|
||||||
|
restart: on-failure
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
command: >
|
||||||
|
redis-server
|
||||||
|
--port 6379
|
||||||
|
--appendonly yes
|
||||||
|
volumes:
|
||||||
|
- evolution_redis:/data
|
||||||
|
|
||||||
|
rebrow:
|
||||||
|
container_name: rebrow
|
||||||
|
image: marian/rebrow
|
||||||
|
restart: on-failure
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
ports:
|
||||||
|
- 5001:5001
|
||||||
|
links:
|
||||||
|
- redis
|
||||||
|
|
||||||
|
api:
|
||||||
|
container_name: evolution_api
|
||||||
|
image: atendai/evolution-api
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
||||||
|
- redis
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- evolution_instances:/evolution/instances
|
||||||
|
- evolution_store:/evolution/store
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
command: ['node', './dist/src/main.js']
|
||||||
|
expose:
|
||||||
|
- 8080
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
evolution_mongodb_data:
|
||||||
|
evolution_mongodb_configdb:
|
||||||
|
evolution_redis:
|
||||||
|
evolution_instances:
|
||||||
|
evolution_store:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
evolution-net:
|
||||||
|
external: true
|
||||||
|
|
||||||
42
Docker/mongodb/docker-compose.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
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:
|
||||||
|
name: evolution-net
|
||||||
|
driver: bridge
|
||||||
21
Docker/redis/docker-compose.yaml
Normal 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:
|
||||||
|
name: evolution-net
|
||||||
|
driver: bridge
|
||||||
167
Dockerfile
@@ -1,51 +1,121 @@
|
|||||||
FROM node:16.18-alpine
|
FROM node:20.7.0-alpine AS builder
|
||||||
|
|
||||||
|
LABEL version="1.8.0" 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 && \
|
RUN apk update && apk upgrade && \
|
||||||
apk add --no-cache git
|
apk add --no-cache git tzdata ffmpeg wget curl
|
||||||
|
|
||||||
WORKDIR /evolution
|
WORKDIR /evolution
|
||||||
|
|
||||||
COPY ./package.json .
|
COPY ./package.json .
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:20.7.0-alpine AS final
|
||||||
|
|
||||||
|
ENV TZ=America/Sao_Paulo
|
||||||
ENV DOCKER_ENV=true
|
ENV DOCKER_ENV=true
|
||||||
|
|
||||||
ENV CORS_ORIGIN="*"
|
ENV SERVER_TYPE=http
|
||||||
ENV CORS_METHODS="POST,GET,PUT,DELETE"
|
ENV SERVER_PORT=8080
|
||||||
|
ENV SERVER_URL=http://localhost:8080
|
||||||
|
|
||||||
|
ENV CORS_ORIGIN=*
|
||||||
|
ENV CORS_METHODS=POST,GET,PUT,DELETE
|
||||||
ENV CORS_CREDENTIALS=true
|
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_COLOR=true
|
||||||
|
ENV LOG_BAILEYS=error
|
||||||
|
|
||||||
ENV DEL_INSTANCE=false
|
ENV DEL_INSTANCE=false
|
||||||
|
ENV DEL_TEMP_INSTANCES=true
|
||||||
|
|
||||||
ENV STORE_CLEANING_INTERVAL=7200
|
ENV STORE_MESSAGES=true
|
||||||
ENV STORE_MESSAGE=true
|
ENV STORE_MESSAGE_UP=true
|
||||||
ENV STORE_CONTACTS=true
|
ENV STORE_CONTACTS=true
|
||||||
ENV STORE_CHATS=true
|
ENV STORE_CHATS=true
|
||||||
|
|
||||||
ENV DATABASE_ENABLED=$DATABASE_ENABLED
|
ENV CLEAN_STORE_CLEANING_INTERVAL=7200
|
||||||
ENV DATABASE_CONNECTION_URI=$DATABASE_CONNECTION_URI
|
ENV CLEAN_STORE_MESSAGES=true
|
||||||
ENV DATABASE_CONNECTION_DB_PREFIX_NAME=$DATABASE_CONNECTION_DB_PREFIX_NAME
|
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_INSTANCE=false
|
||||||
ENV DATABASE_SAVE_DATA_OLD_MESSAGE=false
|
ENV DATABASE_SAVE_DATA_NEW_MESSAGE=false
|
||||||
ENV DATABASE_SAVE_DATA_NEW_MESSAGE=true
|
|
||||||
ENV DATABASE_SAVE_MESSAGE_UPDATE=false
|
ENV DATABASE_SAVE_MESSAGE_UPDATE=false
|
||||||
ENV DATABASE_SAVE_DATA_CONTACTS=true
|
ENV DATABASE_SAVE_DATA_CONTACTS=false
|
||||||
ENV DATABASE_SAVE_DATA_CHATS=true
|
ENV DATABASE_SAVE_DATA_CHATS=false
|
||||||
|
|
||||||
ENV REDIS_ENABLED=$REDIS_ENABLED
|
ENV RABBITMQ_ENABLED=false
|
||||||
ENV REDIS_URI=$REDIS_URI
|
ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
|
||||||
|
ENV RABBITMQ_EXCHANGE_NAME=evolution_exchange
|
||||||
|
ENV RABBITMQ_GLOBAL_ENABLED=false
|
||||||
|
ENV RABBITMQ_EVENTS_APPLICATION_STARTUP=false
|
||||||
|
ENV RABBITMQ_EVENTS_INSTANCE_CREATE=false
|
||||||
|
ENV RABBITMQ_EVENTS_INSTANCE_DELETE=false
|
||||||
|
ENV RABBITMQ_EVENTS_QRCODE_UPDATED=true
|
||||||
|
ENV RABBITMQ_EVENTS_MESSAGES_SET=true
|
||||||
|
ENV RABBITMQ_EVENTS_MESSAGES_UPSERT=true
|
||||||
|
ENV RABBITMQ_EVENTS_MESSAGES_UPDATE=true
|
||||||
|
ENV RABBITMQ_EVENTS_MESSAGES_DELETE=true
|
||||||
|
ENV RABBITMQ_EVENTS_SEND_MESSAGE=true
|
||||||
|
ENV RABBITMQ_EVENTS_CONTACTS_SET=true
|
||||||
|
ENV RABBITMQ_EVENTS_CONTACTS_UPSERT=true
|
||||||
|
ENV RABBITMQ_EVENTS_CONTACTS_UPDATE=true
|
||||||
|
ENV RABBITMQ_EVENTS_PRESENCE_UPDATE=true
|
||||||
|
ENV RABBITMQ_EVENTS_CHATS_SET=true
|
||||||
|
ENV RABBITMQ_EVENTS_CHATS_UPSERT=true
|
||||||
|
ENV RABBITMQ_EVENTS_CHATS_UPDATE=true
|
||||||
|
ENV RABBITMQ_EVENTS_CHATS_DELETE=true
|
||||||
|
ENV RABBITMQ_EVENTS_GROUPS_UPSERT=true
|
||||||
|
ENV RABBITMQ_EVENTS_GROUPS_UPDATE=true
|
||||||
|
ENV RABBITMQ_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||||
|
ENV RABBITMQ_EVENTS_CONNECTION_UPDATE=true
|
||||||
|
ENV RABBITMQ_EVENTS_LABELS_EDIT=true
|
||||||
|
ENV RABBITMQ_EVENTS_LABELS_ASSOCIATION=true
|
||||||
|
ENV RABBITMQ_EVENTS_CALL=true
|
||||||
|
ENV RABBITMQ_EVENTS_TYPEBOT_START=false
|
||||||
|
ENV RABBITMQ_EVENTS_TYPEBOT_CHANGE_STATUS=false
|
||||||
|
|
||||||
ENV WEBHOOK_GLOBAL_URL=$WEBHOOK_GLOBAL_URL
|
ENV WEBSOCKET_ENABLED=false
|
||||||
ENV WEBHOOK_GLOBAL_ENABLED=true
|
ENV WEBSOCKET_GLOBAL_EVENTS=false
|
||||||
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=$WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS
|
|
||||||
|
|
||||||
ENV WEBHOOK_EVENTS_STATUS_INSTANCE=true
|
ENV WA_BUSINESS_TOKEN_WEBHOOK=evolution
|
||||||
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=true
|
ENV WA_BUSINESS_URL=https://graph.facebook.com
|
||||||
|
ENV WA_BUSINESS_VERSION=v18.0
|
||||||
|
ENV WA_BUSINESS_LANGUAGE=pt_BR
|
||||||
|
|
||||||
|
ENV SQS_ENABLED=false
|
||||||
|
ENV SQS_ACCESS_KEY_ID=
|
||||||
|
ENV SQS_SECRET_ACCESS_KEY=
|
||||||
|
ENV SQS_ACCOUNT_ID=
|
||||||
|
ENV SQS_REGION=
|
||||||
|
|
||||||
|
ENV WEBHOOK_GLOBAL_URL=
|
||||||
|
ENV WEBHOOK_GLOBAL_ENABLED=false
|
||||||
|
|
||||||
|
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
|
||||||
|
|
||||||
|
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=false
|
||||||
|
ENV WEBHOOK_EVENTS_INSTANCE_CREATE=false
|
||||||
|
ENV WEBHOOK_EVENTS_INSTANCE_DELETE=false
|
||||||
ENV WEBHOOK_EVENTS_QRCODE_UPDATED=true
|
ENV WEBHOOK_EVENTS_QRCODE_UPDATED=true
|
||||||
ENV WEBHOOK_EVENTS_MESSAGES_SET=true
|
ENV WEBHOOK_EVENTS_MESSAGES_SET=true
|
||||||
ENV WEBHOOK_EVENTS_MESSAGES_UPDATE=true
|
|
||||||
ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=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_SEND_MESSAGE=true
|
||||||
ENV WEBHOOK_EVENTS_CONTACTS_SET=true
|
ENV WEBHOOK_EVENTS_CONTACTS_SET=true
|
||||||
ENV WEBHOOK_EVENTS_CONTACTS_UPSERT=true
|
ENV WEBHOOK_EVENTS_CONTACTS_UPSERT=true
|
||||||
@@ -54,34 +124,59 @@ ENV WEBHOOK_EVENTS_PRESENCE_UPDATE=true
|
|||||||
ENV WEBHOOK_EVENTS_CHATS_SET=true
|
ENV WEBHOOK_EVENTS_CHATS_SET=true
|
||||||
ENV WEBHOOK_EVENTS_CHATS_UPSERT=true
|
ENV WEBHOOK_EVENTS_CHATS_UPSERT=true
|
||||||
ENV WEBHOOK_EVENTS_CHATS_UPDATE=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_UPSERT=true
|
||||||
ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||||
ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||||
|
ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||||
|
ENV WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||||
|
ENV WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
|
||||||
|
ENV WEBHOOK_EVENTS_CALL=true
|
||||||
|
|
||||||
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=true
|
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||||
|
|
||||||
ENV CONFIG_SESSION_PHONE_CLIENT="Evolution API"
|
ENV WEBHOOK_EVENTS_TYPEBOT_START=false
|
||||||
ENV CONFIG_SESSION_PHONE_NAME="Chrome"
|
ENV WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
|
||||||
|
|
||||||
|
ENV WEBHOOK_EVENTS_CHAMA_AI_ACTION=false
|
||||||
|
|
||||||
|
ENV WEBHOOK_EVENTS_ERRORS=false
|
||||||
|
ENV WEBHOOK_EVENTS_ERRORS_WEBHOOK=
|
||||||
|
|
||||||
|
ENV CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI
|
||||||
|
ENV CONFIG_SESSION_PHONE_NAME=Chrome
|
||||||
|
|
||||||
ENV QRCODE_LIMIT=30
|
ENV QRCODE_LIMIT=30
|
||||||
|
ENV QRCODE_COLOR=#198754
|
||||||
|
|
||||||
ENV AUTHENTICATION_TYPE="apikey"
|
ENV TYPEBOT_API_VERSION=latest
|
||||||
|
|
||||||
ENV AUTHENTICATION_API_KEY=$AUTHENTICATION_API_KEY
|
ENV CACHE_REDIS_ENABLED=false
|
||||||
|
ENV CACHE_REDIS_URI=redis://redis:6379
|
||||||
|
ENV CACHE_REDIS_PREFIX_KEY=evolution
|
||||||
|
ENV CACHE_REDIS_TTL=604800
|
||||||
|
ENV CACHE_REDIS_SAVE_INSTANCES=false
|
||||||
|
ENV CACHE_LOCAL_ENABLED=false
|
||||||
|
ENV CACHE_LOCAL_TTL=604800
|
||||||
|
|
||||||
|
ENV AUTHENTICATION_TYPE=apikey
|
||||||
|
|
||||||
|
ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976
|
||||||
|
ENV AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
|
||||||
|
|
||||||
ENV AUTHENTICATION_JWT_EXPIRIN_IN=0
|
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_MODE=server
|
||||||
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
|
|
||||||
|
|
||||||
RUN npm install
|
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>
|
||||||
|
|
||||||
COPY . .
|
WORKDIR /evolution
|
||||||
|
|
||||||
RUN npm run build
|
COPY --from=builder /evolution .
|
||||||
|
|
||||||
CMD [ "node", "./dist/src/main.js" ]
|
CMD [ "node", "./dist/src/main.js" ]
|
||||||
|
|||||||
1
Extras/appsmith/manager.json
Normal file
241
Extras/chatwoot/configurar_admin.json
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
{
|
||||||
|
"name": "[Evolution] Configurar Admin",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"values": {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "CHATWOOT_ADMIN_USER_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_url",
|
||||||
|
"value": "https://CHATWOOT_URL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "n8n_url",
|
||||||
|
"value": "https://N8N_URL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "organization",
|
||||||
|
"value": "ORGANIZATION_NAME"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "logo",
|
||||||
|
"value": "ORGANIZATION_LOGO"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "7a89a538-2cae-4032-8896-09627c07bc68",
|
||||||
|
"name": "Info Base",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
620,
|
||||||
|
480
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/contacts/",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={\n \"inbox_id\": {{ $('Cria Inbox Start').item.json[\"id\"] }},\n \"name\": \"Bot {{ $('Info Base').item.json[\"organization\"] }}\",\n \"phone_number\": \"+123456\",\n \"avatar_url\": \"{{ $('Info Base').item.json[\"logo\"] }}\"\n}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "12a39df3-6b95-4f83-a0bc-50b25adaca7f",
|
||||||
|
"name": "Cria Contato Bot",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
1020,
|
||||||
|
480
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/inboxes/",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={\n \"name\": \"Start {{ $('Info Base').item.json[\"organization\"] }}\",\n \"channel\": {\n \"type\": \"api\",\n \"website_url\": \"\"\n }\n}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "bed7c54d-e232-4fe4-9584-0515e9679868",
|
||||||
|
"name": "Cria Inbox Start",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
480
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "36ada769-a757-4193-989b-0cc4ea504b80",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
420,
|
||||||
|
480
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/automation_rules/",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={\n \"name\": \"Create Company Chatwoot\",\n \"description\": \"Create Company Chatwoot\",\n \"event_name\": \"message_created\",\n \"active\": true,\n \"actions\": \n [\n {\n \"action_name\": \"send_webhook_event\",\n \"action_params\": [\"{{ $('Info Base').item.json[\"n8n_url\"] }}/webhook/criadorchatwoot\"]\n }\n ],\n \"conditions\": \n [\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"Tema Criador de Empresa:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"values\": [\"+123456\"]\n }\n ]\n}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "f5bbb285-71a8-4c58-a4d7-e56002d697f0",
|
||||||
|
"name": "Cria Automação Empresas",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
1220,
|
||||||
|
480
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/automation_rules/",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={\n \"name\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"description\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"event_name\": \"message_created\",\n \"active\": true,\n \"actions\": \n [\n {\n \"action_name\": \"send_webhook_event\",\n \"action_params\": [\"{{ $('Info Base').item.json[\"n8n_url\"] }}/webhook/inbox_whatsapp?utoken={{ $('Info Base').item.json[\"api_access_token\"] }}&organization={{ $('Info Base').item.json[\"organization\"] }}\"]\n }\n ],\n \"conditions\": \n [\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"start:\"]\n },\n \n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"query_operator\": \"or\",\n \"values\": [\"+123456\"]\n },\n\n\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"new_instance:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"values\": [\"+123456\"]\n }\n ]\n}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "a36bebdc-a318-40a2-8532-c7f476f8adb7",
|
||||||
|
"name": "Cria Automação Inboxes",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
1420,
|
||||||
|
480
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"content": "## Workflow Para Configurar admin\n**Aqui você prepara o Chatwoot Principal com um usuário (Superadmin) que poderá criar empresas e caixas de entrada**\n**Instruções**\n**No node Info Base, configure as variáveis de seu Chatwoot e N8N**\n**Obs: A variável api_access_token é o token do usuário que irá poder criar as empresas**",
|
||||||
|
"width": 894.6435495898575
|
||||||
|
},
|
||||||
|
"id": "db66e867-e9f4-452d-b521-725eeac652c8",
|
||||||
|
"name": "Sticky Note",
|
||||||
|
"type": "n8n-nodes-base.stickyNote",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
420,
|
||||||
|
280
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {},
|
||||||
|
"connections": {
|
||||||
|
"Info Base": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Cria Inbox Start",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Cria Contato Bot": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Cria Automação Empresas",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Cria Inbox Start": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Cria Contato Bot",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Info Base",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Cria Automação Empresas": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Cria Automação Inboxes",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {},
|
||||||
|
"versionId": "78f155dc-7809-4bfc-9282-63f49b07fc4d",
|
||||||
|
"id": "BSATyGpGWLR4ZwNm",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "4ff16e963c7f5197d7e99e6239192860914312fea0ce2a9a7fd14d74a0a0e906"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
456
Extras/chatwoot/criador_de_empresas.json
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
{
|
||||||
|
"name": "[Evolution] Criador de Empresas",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "criadorchatwoot",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "5a47c10a-e43c-4fa5-baad-4b6cc511bfcd",
|
||||||
|
"name": "Webhook",
|
||||||
|
"type": "n8n-nodes-base.webhook",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
1420,
|
||||||
|
860
|
||||||
|
],
|
||||||
|
"webhookId": "6fe428e3-1752-453c-9358-abf18b793387"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/platform/api/v1/accounts",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"bodyParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "={{ $json.name_company }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"value": "pt_BR"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "8295c119-3a96-424e-9386-43d75f6816f5",
|
||||||
|
"name": "Cria Conta",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
2020,
|
||||||
|
860
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/platform/api/v1/users",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"bodyParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "={{ $('Info Base').item.json.name_admin }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "email",
|
||||||
|
"value": "={{ $('Info Base').item.json[\"email\"] }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "password",
|
||||||
|
"value": "={{ $('Info Base').item.json[\"password\"] }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "4fe5007a-3a6b-490a-a446-e45cc168189f",
|
||||||
|
"name": "Cria Usuario",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
2220,
|
||||||
|
860
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/platform/api/v1/accounts/{{ $node[\"Cria Conta\"].json[\"id\"] }}/account_users",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"bodyParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"value": "={{ $node[\"Cria Usuario\"].json[\"id\"] }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "role",
|
||||||
|
"value": "administrator"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "848c55e2-5678-4291-9602-c94d994da95b",
|
||||||
|
"name": "Add Usuario a Conta",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
2420,
|
||||||
|
860
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"fromEmail": "={{ $('Info Base').item.json[\"from_email\"] }}",
|
||||||
|
"toEmail": "={{ $('LimpaDados').item.json.email }}",
|
||||||
|
"subject": "=Bem vindo à {{ $('Info Base').item.json[\"organization\"] }}",
|
||||||
|
"text": "=Olá seja bem vindo:\n\nAbaixo segue seus dados de acesso:\n\nURL: {{ $('Info Base').item.json[\"chatwoot_url\"] }}\n\nuser: {{ $('LimpaDados').item.json[\"email\"] }}\n\nSenha: {{ $('LimpaDados').item.json[\"password\"] }}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "27f3b24f-1cf2-4d0d-a354-ecba066059f6",
|
||||||
|
"name": "Send Email",
|
||||||
|
"type": "n8n-nodes-base.emailSend",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
3220,
|
||||||
|
860
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"smtp": {
|
||||||
|
"id": "6BxluEUV8zrXKoVG",
|
||||||
|
"name": "[Dgcode] SMTP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"values": {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "CHATWOOT_PLATFORM_TOKEN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_url",
|
||||||
|
"value": "https://CHATWOOT_URL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "n8n_url",
|
||||||
|
"value": "https://N8N_URL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "organization",
|
||||||
|
"value": "ORGANIZATION_NAME"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "logo",
|
||||||
|
"value": "ORGANIZATION_LOGO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "from_email",
|
||||||
|
"value": "FROM_EMAIL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "={{ $json.name_company }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "email",
|
||||||
|
"value": "={{ $json.email }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "password",
|
||||||
|
"value": "={{ $json.password }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name_company",
|
||||||
|
"value": "={{ $json.name_company }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "38b4069d-e51e-4db7-933f-941b1be6d124",
|
||||||
|
"name": "Info Base",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1820,
|
||||||
|
860
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"keepOnlySet": true,
|
||||||
|
"values": {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"name": "name_admin",
|
||||||
|
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Nome Usuario Administrador: ([^\\n]+)/)[1];}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name_company",
|
||||||
|
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Nome da Empresa: ([^\\n]+)/)[1];}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "email",
|
||||||
|
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Email: ([^\\s]+)/)[1];}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "password",
|
||||||
|
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Senha: ([^\\s]+)/)[1];}}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "28e29e73-aadc-49ca-bd6d-b57ee0160a21",
|
||||||
|
"name": "LimpaDados",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1620,
|
||||||
|
860
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Add Usuario a Conta').item.json.account_id }}/contacts/",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Cria Usuario').item.json.access_token }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={\n \"inbox_id\": {{ $('Cria Inbox Start').item.json[\"id\"] }},\n \"name\": \"Bot {{ $('Info Base').item.json[\"organization\"] }}\",\n \"phone_number\": \"+123456\",\n \"avatar_url\": \"{{ $('Info Base').item.json[\"logo\"] }}\"\n}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "bb671443-bdb4-4f56-99af-f0baef246a3e",
|
||||||
|
"name": "Cria Contato Bot",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
2820,
|
||||||
|
860
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Add Usuario a Conta').item.json.account_id }}/automation_rules/",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Cria Usuario').item.json.access_token }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={\n \"name\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"description\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"event_name\": \"message_created\",\n \"active\": true,\n \"actions\": \n [\n {\n \"action_name\": \"send_webhook_event\",\n \"action_params\": [\"{{ $('Info Base').item.json[\"n8n_url\"] }}/webhook/inbox_whatsapp?utoken={{ $('Cria Usuario').item.json.access_token }}&organization={{ $('Info Base').item.json[\"organization\"] }}\"]\n }\n ],\n \"conditions\": \n [\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"start:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"query_operator\": \"or\",\n \"values\": [\"+123456\"]\n },\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"new_instance:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"values\": [\"+123456\"]\n }\n ]\n}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "e016a2af-b212-4e00-a3ff-8cd03530aa06",
|
||||||
|
"name": "Cria Automação",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
3020,
|
||||||
|
860
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $json.account_id }}/inboxes/",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Cria Usuario').item.json.access_token }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={\n \"name\": \"Start {{ $('Info Base').item.json[\"organization\"] }}\",\n \"channel\": {\n \"type\": \"api\",\n \"website_url\": \"\"\n }\n}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "d3c42148-8920-4c98-a874-eb7113f2dd22",
|
||||||
|
"name": "Cria Inbox Start",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
2620,
|
||||||
|
860
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"content": "## Workflow Criador de Empresas\n**Cria Contas (Empresas) e Usuários através de tema**\n**Instruções**\n**No node Info Base, configure as variáveis de seu Chatwoot e N8N**\n**Obs: A variável api_access_token é o token PlatformApp encontrado no acesso ao Super Admin**\n**Tema para criar novas empresa:**\n\nTema Criador de Empresa:\n\nNome Usuario Administrador: Joao Linhares\nNome da Empresa: Oficina Linhates\nEmail: machineteste24@gmail.com\nSenha: Mfcd62!!",
|
||||||
|
"height": 304.02684563758396,
|
||||||
|
"width": 1129.7777777777778
|
||||||
|
},
|
||||||
|
"id": "d07516c0-4c8e-43ab-ba86-c8d063b09be5",
|
||||||
|
"name": "Sticky Note",
|
||||||
|
"type": "n8n-nodes-base.stickyNote",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
1420,
|
||||||
|
520
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {},
|
||||||
|
"connections": {
|
||||||
|
"Webhook": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "LimpaDados",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Cria Conta": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Cria Usuario",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Cria Usuario": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Add Usuario a Conta",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Add Usuario a Conta": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Cria Inbox Start",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Info Base": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Cria Conta",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"LimpaDados": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Info Base",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Cria Contato Bot": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Cria Automação",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Cria Automação": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Email",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Cria Inbox Start": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Cria Contato Bot",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": true,
|
||||||
|
"settings": {},
|
||||||
|
"versionId": "3ffd6d3f-6966-4de4-af8f-1fda464bc1b8",
|
||||||
|
"id": "79R6qQDtfyCwgYjJ",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "4ff16e963c7f5197d7e99e6239192860914312fea0ce2a9a7fd14d74a0a0e906"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
510
Extras/chatwoot/criador_de_inbox.json
Normal file
@@ -0,0 +1,510 @@
|
|||||||
|
{
|
||||||
|
"name": "[Evolution] Criador de Inbox",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "inbox_whatsapp",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "8205b929-73e9-456a-9b0d-e1474991663a",
|
||||||
|
"name": "Webhook",
|
||||||
|
"type": "n8n-nodes-base.webhook",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
320,
|
||||||
|
300
|
||||||
|
],
|
||||||
|
"webhookId": "cf37002d-3869-4bb1-af3a-739fdd3c1756"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $json.evolution_url }}/instance/create",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apikey",
|
||||||
|
"value": "={{ $json.global_api_key }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"bodyParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "instanceName",
|
||||||
|
"value": "={{ $json.instance_name }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "qrcode",
|
||||||
|
"value": "={{ $json.qrcode }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_account_id",
|
||||||
|
"value": "={{ $json.chatwoot_account_id }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_token",
|
||||||
|
"value": "={{ $json.chatwoot_token }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_url",
|
||||||
|
"value": "={{ $json.chatwoot_url }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_sign_msg",
|
||||||
|
"value": "={{ $json.chatwoot_sign_msg }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_reopen_conversation",
|
||||||
|
"value": "={{ $json.chatwoot_reopen_conversation }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_conversation_pending",
|
||||||
|
"value": "={{ $json.chatwoot_conversation_pending }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reject_call",
|
||||||
|
"value": "={{ $json.reject_call }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "msg_call",
|
||||||
|
"value": "={{ $json.msg_call }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "groups_ignore",
|
||||||
|
"value": "={{ $json.groups_ignore }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "always_online",
|
||||||
|
"value": "={{ $json.always_online }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "read_messages",
|
||||||
|
"value": "={{ $json.read_messages }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "read_status",
|
||||||
|
"value": "={{ $json.read_status }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "275aa370-2fdb-42f4-844a-2fb3051301bd",
|
||||||
|
"name": "Cria Instancia",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.1,
|
||||||
|
"position": [
|
||||||
|
760,
|
||||||
|
300
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Info Base').item.json[\"chatwoot_account_id\"] }}/inboxes/",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Info Base').item.json.chatwoot_token }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "e4650812-ba0a-4f72-8bd8-a235eca4b2de",
|
||||||
|
"name": "Lista Inboxes",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
980,
|
||||||
|
300
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"content": "## Workflow Para Criar Inbox\n**Aqui você configura a comunicação entre o chatwoot e a Evolution API para criar novas instâncias a partir do chatwoot**\n**Instruções**\n**No node Info Base, configure as variáveis de seu Chatwoot e Evolution API**",
|
||||||
|
"width": 1129.7777777777778
|
||||||
|
},
|
||||||
|
"id": "aa763d9e-d973-44fc-8399-277bb24718a5",
|
||||||
|
"name": "Sticky Note",
|
||||||
|
"type": "n8n-nodes-base.stickyNote",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
320,
|
||||||
|
80
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"keepOnlySet": true,
|
||||||
|
"values": {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"name": "chatwoot_url",
|
||||||
|
"value": "CHATWOOT_URL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "evolution_url",
|
||||||
|
"value": "EVOLUTION_URL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "global_api_key",
|
||||||
|
"value": "EVOLUTION_GLOBAL_API_KEY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "organization",
|
||||||
|
"value": "={{ $json.query.organization }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "instance_name",
|
||||||
|
"value": "={{ $json.body.messages[0].content.split(':')[1] }}-cwId-{{ $json.body.messages[0].account_id }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_token",
|
||||||
|
"value": "={{ $json.query.utoken }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "msg_call",
|
||||||
|
"value": "Não aceitamos chamadas, por favor deixe uma mensagem!"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"boolean": [
|
||||||
|
{
|
||||||
|
"name": "qrcode",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_sign_msg",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_reopen_conversation",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chatwoot_conversation_pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reject_call",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "groups_ignore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "always_online",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "read_messages",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "read_status"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"number": [
|
||||||
|
{
|
||||||
|
"name": "chatwoot_account_id",
|
||||||
|
"value": "={{ $json.body.messages[0].account_id }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "297df325-ecc4-4a34-817c-092d16d5753b",
|
||||||
|
"name": "Info Base",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
540,
|
||||||
|
300
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"conditions": {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"value1": "={{ $json.name }}",
|
||||||
|
"value2": "=Start {{ $('Info Base').item.json[\"organization\"] }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "a8d955e6-ac51-4316-aeec-09d4d65e943a",
|
||||||
|
"name": "é Start Inbox?",
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
1660,
|
||||||
|
200
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"batchSize": 1,
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "0d2d2194-aa4a-4241-9022-217d88bb581f",
|
||||||
|
"name": "Split In Batches",
|
||||||
|
"type": "n8n-nodes-base.splitInBatches",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1420,
|
||||||
|
300
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"conditions": {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"value1": "={{ $json.name }}",
|
||||||
|
"value2": "={{ $('Webhook').item.json[\"body\"][\"messages\"][0][\"content\"].split(':')[1] }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "0bfbc2cb-eff5-423c-bd3a-b266aaf6a943",
|
||||||
|
"name": "é_pre-existente?",
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
1900,
|
||||||
|
340
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "PATCH",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Info Base').item.json[\"chatwoot_account_id\"] }}/inboxes/{{ $json.id }}",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Info Base').item.json.chatwoot_token }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={\n\"channel\": {\n\"webhook_url\": \"{{ $('Info Base').item.json[\"evolution_url\"] }}/chatwoot/webhook/{{ encodeURIComponent($('Info Base').item.json[\"instance_name\"]) }}\"\n}\n}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "fb589456-5566-4a45-96a7-75986d0aa1d5",
|
||||||
|
"name": "Update_webhook_url",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
2120,
|
||||||
|
340
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "DELETE",
|
||||||
|
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Info Base').item.json[\"chatwoot_account_id\"] }}/inboxes/{{ $json.id }}",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "api_access_token",
|
||||||
|
"value": "={{ $('Info Base').item.json.chatwoot_token }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "e6094941-410f-496c-9c9c-7b95fd9349af",
|
||||||
|
"name": "Deleta Inbox Start",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 3,
|
||||||
|
"position": [
|
||||||
|
1900,
|
||||||
|
100
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "8cf9a78f-9e8a-4288-9d7b-801790af68d5",
|
||||||
|
"name": "No Operation, do nothing",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
1660,
|
||||||
|
400
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"fieldToSplitOut": "payload",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "9468896a-5f86-4598-9d20-e8f495cae859",
|
||||||
|
"name": "Ajusta lista",
|
||||||
|
"type": "n8n-nodes-base.itemLists",
|
||||||
|
"typeVersion": 2.2,
|
||||||
|
"position": [
|
||||||
|
1200,
|
||||||
|
300
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {},
|
||||||
|
"connections": {
|
||||||
|
"Webhook": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Info Base",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Lista Inboxes": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Ajusta lista",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Cria Instancia": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Lista Inboxes",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Info Base": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Cria Instancia",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"é Start Inbox?": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Deleta Inbox Start",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "é_pre-existente?",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Split In Batches": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "é Start Inbox?",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "No Operation, do nothing",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"é_pre-existente?": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Update_webhook_url",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Split In Batches",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Update_webhook_url": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Split In Batches",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Deleta Inbox Start": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Split In Batches",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Ajusta lista": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Split In Batches",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": true,
|
||||||
|
"settings": {},
|
||||||
|
"versionId": "ab910349-b559-4738-9ac6-de6b06d6bbce",
|
||||||
|
"id": "ByW2ccjR4XPrOyio",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "4ff16e963c7f5197d7e99e6239192860914312fea0ce2a9a7fd14d74a0a0e906"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
1
Extras/typebot/typebot-example.json
Normal file
256
README.md
@@ -1,247 +1,24 @@
|
|||||||
<h1 align="center">Evolution Api</h1>
|
<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">
|
||||||
|
|
||||||
<!-- [](#)
|
[](https://evolution-api.com/whatsapp)
|
||||||
[](#)
|
[](https://evolution-api.com/discord)
|
||||||
|
[](https://evolution-api.com/postman)
|
||||||
|
[](https://doc.evolution-api.com)
|
||||||
[](./LICENSE)
|
[](./LICENSE)
|
||||||
[](https://app.picpay.com/user/davidsongomes1998) -->
|
[](https://app.picpay.com/user/davidsongomes1998)
|
||||||
|
[](https://bmc.link/evolutionapi)
|
||||||
|
|
||||||
<!-- </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
|
## 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.
|
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
|
## SSL
|
||||||
|
|
||||||
To install the SSL certificate, follow the **[instructions](https://certbot.eff.org/instructions?ws=other&os=ubuntufocal)** below.
|
To install the SSL certificate, follow the **[instructions](https://certbot.eff.org/instructions?ws=other&os=ubuntufocal)** below.
|
||||||
@@ -254,14 +31,21 @@ This code was produced based on the baileys library and it is still under develo
|
|||||||
|
|
||||||
# Donate to the project.
|
# Donate to the project.
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
#### PicPay
|
#### PicPay
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://app.picpay.com/user/davidsongomes1998" target="_blank" rel="noopener noreferrer">
|
<a href="https://app.picpay.com/user/davidsongomes1998" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img src="./public/images/picpay-qr.jpeg" style="width: 50% !important;">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
#### Buy me coffe - PIX
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://bmc.link/evolutionapi" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img src="./public/images/qrcode-pix.png" style="width: 50% !important;">
|
||||||
|
</a>
|
||||||
|
<p><b>CHAVE PIX (Telefone):</b> (74)99987-9409</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</br>
|
</br>
|
||||||
@@ -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
|
|
||||||
29
docker-compose.yaml.example
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
container_name: evolution_api
|
||||||
|
image: evolution/api:local
|
||||||
|
build: .
|
||||||
|
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:
|
||||||
|
name: evolution-net
|
||||||
|
driver: bridge
|
||||||
80
docker-compose.yaml.example.complete
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
container_name: evolution_api
|
||||||
|
image: evolution/api:local
|
||||||
|
build: .
|
||||||
|
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:
|
||||||
|
name: evolution-net
|
||||||
|
driver: bridge
|
||||||
28
docker-compose.yaml.example.dockerhub
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
container_name: evolution_api
|
||||||
|
image: atendai/evolution-api:latest
|
||||||
|
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:
|
||||||
|
name: evolution-net
|
||||||
|
driver: bridge
|
||||||
@@ -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
|
|
||||||
56
package.json
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "evolution-api",
|
"name": "evolution-api",
|
||||||
"version": "1.2.0",
|
"version": "1.8.1",
|
||||||
"description": "Rest api for communication with WhatsApp",
|
"description": "Rest api for communication with WhatsApp",
|
||||||
"main": "index.js",
|
"main": "./dist/src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "ts-node --files --transpile-only ./src/main.ts",
|
"start": "ts-node --files --transpile-only ./src/main.ts",
|
||||||
"start:prod": "bash start.sh",
|
"start:prod": "bash start.sh",
|
||||||
"dev:server": "clear && tsnd --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts",
|
"dev:server": "clear && tsnd --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts",
|
||||||
"test": "clear && tsnd --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts"
|
"test": "clear && tsnd --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts",
|
||||||
|
"lint": "eslint --fix --ext .ts src"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/DavidsonGomes/evolution-api.git"
|
"url": "git+https://github.com/EvolutionAPI/evolution-api.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"chat",
|
"chat",
|
||||||
@@ -36,37 +37,58 @@
|
|||||||
},
|
},
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"bugs": {
|
"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": {
|
"dependencies": {
|
||||||
"@adiwajshing/keyed-db": "^0.2.4",
|
"@adiwajshing/keyed-db": "^0.2.4",
|
||||||
"@evolution/base": "github:WhiskeySockets/Baileys",
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||||
|
"@figuro/chatwoot-sdk": "^1.1.16",
|
||||||
"@hapi/boom": "^10.0.1",
|
"@hapi/boom": "^10.0.1",
|
||||||
"axios": "^1.3.5",
|
"@sentry/node": "^7.59.2",
|
||||||
"class-validator": "^0.13.2",
|
"amqplib": "^0.10.3",
|
||||||
|
"@aws-sdk/client-sqs": "^3.569.0",
|
||||||
|
"axios": "^1.6.5",
|
||||||
|
"@whiskeysockets/baileys": "6.7.5",
|
||||||
|
"class-validator": "^0.14.1",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"eventemitter2": "^6.4.9",
|
"eventemitter2": "^6.4.9",
|
||||||
|
"evolution-manager": "^0.4.13",
|
||||||
|
"exiftool-vendored": "^22.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-async-errors": "^3.1.1",
|
"express-async-errors": "^3.1.1",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
"hbs": "^4.2.0",
|
"hbs": "^4.2.0",
|
||||||
|
"https-proxy-agent": "^7.0.2",
|
||||||
|
"i18next": "^23.7.19",
|
||||||
"jimp": "^0.16.13",
|
"jimp": "^0.16.13",
|
||||||
"join": "^3.0.0",
|
"join": "^3.0.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"libphonenumber-js": "^1.10.39",
|
||||||
"link-preview-js": "^3.0.4",
|
"link-preview-js": "^3.0.4",
|
||||||
"mongoose": "^6.10.5",
|
"mongoose": "^6.10.5",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
"node-mime-types": "^1.1.0",
|
"node-mime-types": "^1.1.0",
|
||||||
|
"node-windows": "^1.0.0-beta.8",
|
||||||
|
"parse-bmfont-xml": "^1.1.4",
|
||||||
|
"pg": "^8.11.3",
|
||||||
"pino": "^8.11.0",
|
"pino": "^8.11.0",
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"redis": "^4.6.5",
|
"redis": "^4.6.5",
|
||||||
"uuid": "^9.0.0"
|
"sharp": "^0.32.2",
|
||||||
|
"socket.io": "^4.7.1",
|
||||||
|
"socks-proxy-agent": "^8.0.1",
|
||||||
|
"swagger-ui-express": "^5.0.0",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
|
"xml2js": "^0.6.2",
|
||||||
|
"yamljs": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/compression": "^1.7.2",
|
"@types/compression": "^1.7.2",
|
||||||
@@ -74,16 +96,20 @@
|
|||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/jsonwebtoken": "^8.5.9",
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
|
"@types/mime-types": "^2.1.1",
|
||||||
"@types/node": "^18.15.11",
|
"@types/node": "^18.15.11",
|
||||||
|
"@types/node-windows": "^0.1.2",
|
||||||
"@types/qrcode": "^1.5.0",
|
"@types/qrcode": "^1.5.0",
|
||||||
"@types/qrcode-terminal": "^0.12.0",
|
"@types/qrcode-terminal": "^0.12.0",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@typescript-eslint/parser": "^5.57.1",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
"eslint": "^8.38.0",
|
"eslint": "^8.45.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"prettier": "^2.8.7",
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"prettier": "^2.8.8",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
|
|||||||
2045
postman.json
BIN
public/images/bmc_qr.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 24 KiB |
BIN
public/images/evolution-logo.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 36 KiB |
BIN
public/images/picpay-qr.jpeg
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
public/images/qrcode-pix.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
19
src/api/abstract/abstract.cache.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export interface ICache {
|
||||||
|
get(key: string): Promise<any>;
|
||||||
|
|
||||||
|
hGet(key: string, field: string): Promise<any>;
|
||||||
|
|
||||||
|
set(key: string, value: any, ttl?: number): void;
|
||||||
|
|
||||||
|
hSet(key: string, field: string, value: any): Promise<void>;
|
||||||
|
|
||||||
|
has(key: string): Promise<boolean>;
|
||||||
|
|
||||||
|
keys(appendCriteria?: string): Promise<string[]>;
|
||||||
|
|
||||||
|
delete(key: string | string[]): Promise<number>;
|
||||||
|
|
||||||
|
hDelete(key: string, field: string): Promise<any>;
|
||||||
|
|
||||||
|
deleteAll(appendCriteria?: string): Promise<number>;
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
import { ConfigService, Database } from '../../config/env.config';
|
import { ConfigService, Database } from '../../config/env.config';
|
||||||
import { ROOT_DIR } from '../../config/path.config';
|
import { ROOT_DIR } from '../../config/path.config';
|
||||||
|
|
||||||
export type IInsert = { insertCount: number };
|
export type IInsert = { insertCount: number };
|
||||||
|
|
||||||
export interface IRepository {
|
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>;
|
find(query: any): Promise<any>;
|
||||||
delete(query: any, force?: boolean): Promise<any>;
|
delete(query: any, force?: boolean): Promise<any>;
|
||||||
|
|
||||||
@@ -33,11 +35,9 @@ export abstract class Repository implements IRepository {
|
|||||||
mkdirSync(create.path, { recursive: true });
|
mkdirSync(create.path, { recursive: true });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
writeFileSync(
|
writeFileSync(join(create.path, create.fileName + '.json'), JSON.stringify({ ...create.data }), {
|
||||||
join(create.path, create.fileName + '.json'),
|
encoding: 'utf-8',
|
||||||
JSON.stringify({ ...create.data }),
|
});
|
||||||
{ encoding: 'utf-8' },
|
|
||||||
);
|
|
||||||
|
|
||||||
return { message: 'create - success' };
|
return { message: 'create - success' };
|
||||||
} finally {
|
} finally {
|
||||||
@@ -45,13 +45,22 @@ export abstract class Repository implements IRepository {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public insert(data: any, saveDb = false): Promise<IInsert> {
|
// eslint-disable-next-line
|
||||||
|
public insert(data: any, instanceName: string, saveDb = false): Promise<IInsert> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
public update(data: any, instanceName: string, saveDb = false): Promise<IInsert> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
public find(query: any): Promise<any> {
|
public find(query: any): Promise<any> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
delete(query: any, force?: boolean): Promise<any> {
|
delete(query: any, force?: boolean): Promise<any> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import { InstanceDto } from '../dto/instance.dto';
|
|
||||||
import { JSONSchema7 } from 'json-schema';
|
|
||||||
import { Request } from 'express';
|
|
||||||
import { validate } from 'jsonschema';
|
|
||||||
import { BadRequestException } from '../../exceptions';
|
|
||||||
import 'express-async-errors';
|
import 'express-async-errors';
|
||||||
|
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { JSONSchema7 } from 'json-schema';
|
||||||
|
import { validate } from 'jsonschema';
|
||||||
|
|
||||||
import { Logger } from '../../config/logger.config';
|
import { Logger } from '../../config/logger.config';
|
||||||
import { GroupInvite, GroupJid } from '../dto/group.dto';
|
import { BadRequestException } from '../../exceptions';
|
||||||
|
import { GetParticipant, GroupInvite } from '../dto/group.dto';
|
||||||
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
|
|
||||||
type DataValidate<T> = {
|
type DataValidate<T> = {
|
||||||
request: Request;
|
request: Request;
|
||||||
@@ -19,7 +21,6 @@ const logger = new Logger('Validate');
|
|||||||
export abstract class RouterBroker {
|
export abstract class RouterBroker {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
public routerPath(path: string, param = true) {
|
public routerPath(path: string, param = true) {
|
||||||
// const route = param ? '/:instanceName/' + path : '/' + path;
|
|
||||||
let route = '/' + path;
|
let route = '/' + path;
|
||||||
param ? (route += '/:instanceName') : null;
|
param ? (route += '/:instanceName') : null;
|
||||||
|
|
||||||
@@ -45,6 +46,34 @@ export abstract class RouterBroker {
|
|||||||
|
|
||||||
const v = schema ? validate(ref, schema) : { valid: true, errors: [] };
|
const v = schema ? validate(ref, schema) : { valid: true, errors: [] };
|
||||||
|
|
||||||
|
if (!v.valid) {
|
||||||
|
const message: any[] = v.errors.map(({ stack, schema }) => {
|
||||||
|
let message: string;
|
||||||
|
if (schema['description']) {
|
||||||
|
message = schema['description'];
|
||||||
|
} else {
|
||||||
|
message = stack.replace('instance.', '');
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
logger.error(message);
|
||||||
|
throw new BadRequestException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
if (!v.valid) {
|
||||||
const message: any[] = v.errors.map(({ property, stack, schema }) => {
|
const message: any[] = v.errors.map(({ property, stack, schema }) => {
|
||||||
let message: string;
|
let message: string;
|
||||||
@@ -68,21 +97,29 @@ export abstract class RouterBroker {
|
|||||||
public async groupValidate<T>(args: DataValidate<T>) {
|
public async groupValidate<T>(args: DataValidate<T>) {
|
||||||
const { request, ClassRef, schema, execute } = args;
|
const { request, ClassRef, schema, execute } = args;
|
||||||
|
|
||||||
const groupJid = request.query as unknown as GroupJid;
|
|
||||||
|
|
||||||
if (!groupJid?.groupJid) {
|
|
||||||
throw new BadRequestException(
|
|
||||||
'The group id needs to be informed in the query',
|
|
||||||
'ex: "groupJid=120362@g.us"',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const instance = request.params as unknown as InstanceDto;
|
const instance = request.params as unknown as InstanceDto;
|
||||||
const body = request.body;
|
const body = request.body;
|
||||||
|
|
||||||
|
let groupJid = body?.groupJid;
|
||||||
|
|
||||||
|
if (!groupJid) {
|
||||||
|
if (request.query?.groupJid) {
|
||||||
|
groupJid = request.query.groupJid;
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException('The group id needs to be informed in the query', 'ex: "groupJid=120362@g.us"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groupJid.endsWith('@g.us')) {
|
||||||
|
groupJid = groupJid + '@g.us';
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(body, {
|
||||||
|
groupJid: groupJid,
|
||||||
|
});
|
||||||
|
|
||||||
const ref = new ClassRef();
|
const ref = new ClassRef();
|
||||||
|
|
||||||
Object.assign(body, groupJid);
|
|
||||||
Object.assign(ref, body);
|
Object.assign(ref, body);
|
||||||
|
|
||||||
const v = validate(ref, schema);
|
const v = validate(ref, schema);
|
||||||
@@ -129,7 +166,44 @@ export abstract class RouterBroker {
|
|||||||
|
|
||||||
const v = validate(ref, schema);
|
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) {
|
if (!v.valid) {
|
||||||
const message: any[] = v.errors.map(({ property, stack, schema }) => {
|
const message: any[] = v.errors.map(({ property, stack, schema }) => {
|
||||||
138
src/api/controllers/chat.controller.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { Logger } from '../../config/logger.config';
|
||||||
|
import {
|
||||||
|
ArchiveChatDto,
|
||||||
|
BlockUserDto,
|
||||||
|
DeleteMessage,
|
||||||
|
getBase64FromMediaMessageDto,
|
||||||
|
MarkChatUnreadDto,
|
||||||
|
NumberDto,
|
||||||
|
PrivacySettingDto,
|
||||||
|
ProfileNameDto,
|
||||||
|
ProfilePictureDto,
|
||||||
|
ProfileStatusDto,
|
||||||
|
ReadMessageDto,
|
||||||
|
SendPresenceDto,
|
||||||
|
UpdateMessageDto,
|
||||||
|
WhatsAppNumberDto,
|
||||||
|
} 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';
|
||||||
|
|
||||||
|
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 markChatUnread({ instanceName }: InstanceDto, data: MarkChatUnreadDto) {
|
||||||
|
logger.verbose('requested markChatUnread from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].markChatUnread(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, data: getBase64FromMediaMessageDto) {
|
||||||
|
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 sendPresence({ instanceName }: InstanceDto, data: SendPresenceDto) {
|
||||||
|
logger.verbose('requested sendPresence from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].sendPresence(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateProfileStatus({ instanceName }: InstanceDto, data: ProfileStatusDto) {
|
||||||
|
logger.verbose('requested updateProfileStatus from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].updateProfileStatus(data.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateProfilePicture({ instanceName }: InstanceDto, data: ProfilePictureDto) {
|
||||||
|
logger.verbose('requested updateProfilePicture from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].updateProfilePicture(data.picture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeProfilePicture({ instanceName }: InstanceDto) {
|
||||||
|
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].removeProfilePicture();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateMessage({ instanceName }: InstanceDto, data: UpdateMessageDto) {
|
||||||
|
logger.verbose('requested updateMessage from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].updateMessage(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async blockUser({ instanceName }: InstanceDto, data: BlockUserDto) {
|
||||||
|
logger.verbose('requested blockUser from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].blockUser(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/api/controllers/group.controller.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { Logger } from '../../config/logger.config';
|
||||||
|
import {
|
||||||
|
AcceptGroupInvite,
|
||||||
|
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';
|
||||||
|
|
||||||
|
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 acceptInviteCode(instance: InstanceDto, inviteCode: AcceptGroupInvite) {
|
||||||
|
logger.verbose('requested acceptInviteCode from ' + instance.instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instance.instanceName].acceptInviteCode(inviteCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateGParticipate(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
776
src/api/controllers/instance.controller.ts
Normal file
@@ -0,0 +1,776 @@
|
|||||||
|
import { delay } from '@whiskeysockets/baileys';
|
||||||
|
import { isURL } from 'class-validator';
|
||||||
|
import EventEmitter2 from 'eventemitter2';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { Auth, ConfigService, HttpServer, WaBusiness } from '../../config/env.config';
|
||||||
|
import { Logger } from '../../config/logger.config';
|
||||||
|
import { BadRequestException, InternalServerErrorException, UnauthorizedException } from '../../exceptions';
|
||||||
|
import { InstanceDto, SetPresenceDto } from '../dto/instance.dto';
|
||||||
|
import { ChatwootService } from '../integrations/chatwoot/services/chatwoot.service';
|
||||||
|
import { RabbitmqService } from '../integrations/rabbitmq/services/rabbitmq.service';
|
||||||
|
import { SqsService } from '../integrations/sqs/services/sqs.service';
|
||||||
|
import { TypebotService } from '../integrations/typebot/services/typebot.service';
|
||||||
|
import { WebsocketService } from '../integrations/websocket/services/websocket.service';
|
||||||
|
import { ProviderFiles } from '../provider/sessions';
|
||||||
|
import { RepositoryBroker } from '../repository/repository.manager';
|
||||||
|
import { AuthService, OldToken } from '../services/auth.service';
|
||||||
|
import { CacheService } from '../services/cache.service';
|
||||||
|
import { BaileysStartupService } from '../services/channels/whatsapp.baileys.service';
|
||||||
|
import { BusinessStartupService } from '../services/channels/whatsapp.business.service';
|
||||||
|
import { IntegrationService } from '../services/integration.service';
|
||||||
|
import { WAMonitoringService } from '../services/monitor.service';
|
||||||
|
import { SettingsService } from '../services/settings.service';
|
||||||
|
import { WebhookService } from '../services/webhook.service';
|
||||||
|
import { Events, Integration, wa } from '../types/wa.types';
|
||||||
|
import { ProxyController } from './proxy.controller';
|
||||||
|
|
||||||
|
export class InstanceController {
|
||||||
|
constructor(
|
||||||
|
private readonly waMonitor: WAMonitoringService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly repository: RepositoryBroker,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
private readonly webhookService: WebhookService,
|
||||||
|
private readonly chatwootService: ChatwootService,
|
||||||
|
private readonly settingsService: SettingsService,
|
||||||
|
private readonly websocketService: WebsocketService,
|
||||||
|
private readonly rabbitmqService: RabbitmqService,
|
||||||
|
private readonly sqsService: SqsService,
|
||||||
|
private readonly typebotService: TypebotService,
|
||||||
|
private readonly integrationService: IntegrationService,
|
||||||
|
private readonly proxyService: ProxyController,
|
||||||
|
private readonly cache: CacheService,
|
||||||
|
private readonly chatwootCache: CacheService,
|
||||||
|
private readonly baileysCache: CacheService,
|
||||||
|
private readonly providerFiles: ProviderFiles,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private readonly logger = new Logger(InstanceController.name);
|
||||||
|
|
||||||
|
public async createInstance({
|
||||||
|
instanceName,
|
||||||
|
webhook,
|
||||||
|
webhook_by_events,
|
||||||
|
webhook_base64,
|
||||||
|
events,
|
||||||
|
qrcode,
|
||||||
|
number,
|
||||||
|
mobile,
|
||||||
|
integration,
|
||||||
|
token,
|
||||||
|
chatwoot_account_id,
|
||||||
|
chatwoot_token,
|
||||||
|
chatwoot_url,
|
||||||
|
chatwoot_sign_msg,
|
||||||
|
chatwoot_reopen_conversation,
|
||||||
|
chatwoot_conversation_pending,
|
||||||
|
chatwoot_import_contacts,
|
||||||
|
chatwoot_name_inbox,
|
||||||
|
chatwoot_merge_brazil_contacts,
|
||||||
|
chatwoot_import_messages,
|
||||||
|
chatwoot_days_limit_import_messages,
|
||||||
|
reject_call,
|
||||||
|
msg_call,
|
||||||
|
groups_ignore,
|
||||||
|
always_online,
|
||||||
|
read_messages,
|
||||||
|
read_status,
|
||||||
|
sync_full_history,
|
||||||
|
websocket_enabled,
|
||||||
|
websocket_events,
|
||||||
|
rabbitmq_enabled,
|
||||||
|
rabbitmq_events,
|
||||||
|
sqs_enabled,
|
||||||
|
sqs_events,
|
||||||
|
typebot_url,
|
||||||
|
typebot,
|
||||||
|
typebot_expire,
|
||||||
|
typebot_keyword_finish,
|
||||||
|
typebot_delay_message,
|
||||||
|
typebot_unknown_message,
|
||||||
|
typebot_listening_from_me,
|
||||||
|
proxy,
|
||||||
|
}: InstanceDto) {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
|
||||||
|
|
||||||
|
this.logger.verbose('checking duplicate token');
|
||||||
|
await this.authService.checkDuplicateToken(token);
|
||||||
|
|
||||||
|
if (!token && integration === Integration.WHATSAPP_BUSINESS) {
|
||||||
|
throw new BadRequestException('token is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('creating instance');
|
||||||
|
let instance: BaileysStartupService | BusinessStartupService;
|
||||||
|
if (integration === Integration.WHATSAPP_BUSINESS) {
|
||||||
|
instance = new BusinessStartupService(
|
||||||
|
this.configService,
|
||||||
|
this.eventEmitter,
|
||||||
|
this.repository,
|
||||||
|
this.cache,
|
||||||
|
this.chatwootCache,
|
||||||
|
this.baileysCache,
|
||||||
|
this.providerFiles,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
instance = new BaileysStartupService(
|
||||||
|
this.configService,
|
||||||
|
this.eventEmitter,
|
||||||
|
this.repository,
|
||||||
|
this.cache,
|
||||||
|
this.chatwootCache,
|
||||||
|
this.baileysCache,
|
||||||
|
this.providerFiles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.waMonitor.saveInstance({ integration, instanceName, token, number, mobile });
|
||||||
|
|
||||||
|
instance.instanceName = instanceName;
|
||||||
|
|
||||||
|
const instanceId = v4();
|
||||||
|
|
||||||
|
instance.sendDataWebhook(Events.INSTANCE_CREATE, {
|
||||||
|
instanceName,
|
||||||
|
instanceId: instanceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
instanceId: instanceId,
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.logger.verbose('hash: ' + hash + ' generated');
|
||||||
|
|
||||||
|
let webhookEvents: string[];
|
||||||
|
|
||||||
|
if (webhook) {
|
||||||
|
if (!isURL(webhook, { require_tld: false })) {
|
||||||
|
throw new BadRequestException('Invalid "url" property in webhook');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('creating webhook');
|
||||||
|
try {
|
||||||
|
let newEvents: string[] = [];
|
||||||
|
if (events.length === 0) {
|
||||||
|
newEvents = [
|
||||||
|
'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',
|
||||||
|
'LABELS_EDIT',
|
||||||
|
'LABELS_ASSOCIATION',
|
||||||
|
'CALL',
|
||||||
|
'NEW_JWT_TOKEN',
|
||||||
|
'TYPEBOT_START',
|
||||||
|
'TYPEBOT_CHANGE_STATUS',
|
||||||
|
'CHAMA_AI_ACTION',
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
newEvents = events;
|
||||||
|
}
|
||||||
|
this.webhookService.create(instance, {
|
||||||
|
enabled: true,
|
||||||
|
url: webhook,
|
||||||
|
events: newEvents,
|
||||||
|
webhook_by_events,
|
||||||
|
webhook_base64,
|
||||||
|
});
|
||||||
|
|
||||||
|
webhookEvents = (await this.webhookService.find(instance)).events;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let websocketEvents: string[];
|
||||||
|
|
||||||
|
if (websocket_enabled) {
|
||||||
|
this.logger.verbose('creating websocket');
|
||||||
|
try {
|
||||||
|
let newEvents: string[] = [];
|
||||||
|
if (websocket_events.length === 0) {
|
||||||
|
newEvents = [
|
||||||
|
'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',
|
||||||
|
'LABELS_EDIT',
|
||||||
|
'LABELS_ASSOCIATION',
|
||||||
|
'CALL',
|
||||||
|
'NEW_JWT_TOKEN',
|
||||||
|
'TYPEBOT_START',
|
||||||
|
'TYPEBOT_CHANGE_STATUS',
|
||||||
|
'CHAMA_AI_ACTION',
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
newEvents = websocket_events;
|
||||||
|
}
|
||||||
|
this.websocketService.create(instance, {
|
||||||
|
enabled: true,
|
||||||
|
events: newEvents,
|
||||||
|
});
|
||||||
|
|
||||||
|
websocketEvents = (await this.websocketService.find(instance)).events;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rabbitmqEvents: string[];
|
||||||
|
|
||||||
|
if (rabbitmq_enabled) {
|
||||||
|
this.logger.verbose('creating rabbitmq');
|
||||||
|
try {
|
||||||
|
let newEvents: string[] = [];
|
||||||
|
if (rabbitmq_events.length === 0) {
|
||||||
|
newEvents = [
|
||||||
|
'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',
|
||||||
|
'LABELS_EDIT',
|
||||||
|
'LABELS_ASSOCIATION',
|
||||||
|
'CALL',
|
||||||
|
'NEW_JWT_TOKEN',
|
||||||
|
'TYPEBOT_START',
|
||||||
|
'TYPEBOT_CHANGE_STATUS',
|
||||||
|
'CHAMA_AI_ACTION',
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
newEvents = rabbitmq_events;
|
||||||
|
}
|
||||||
|
this.rabbitmqService.create(instance, {
|
||||||
|
enabled: true,
|
||||||
|
events: newEvents,
|
||||||
|
});
|
||||||
|
|
||||||
|
rabbitmqEvents = (await this.rabbitmqService.find(instance)).events;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sqsEvents: string[];
|
||||||
|
|
||||||
|
if (sqs_enabled) {
|
||||||
|
this.logger.verbose('creating sqs');
|
||||||
|
try {
|
||||||
|
let newEvents: string[] = [];
|
||||||
|
if (sqs_events.length === 0) {
|
||||||
|
newEvents = [
|
||||||
|
'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',
|
||||||
|
'LABELS_EDIT',
|
||||||
|
'LABELS_ASSOCIATION',
|
||||||
|
'CALL',
|
||||||
|
'NEW_JWT_TOKEN',
|
||||||
|
'TYPEBOT_START',
|
||||||
|
'TYPEBOT_CHANGE_STATUS',
|
||||||
|
'CHAMA_AI_ACTION',
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
newEvents = sqs_events;
|
||||||
|
}
|
||||||
|
this.sqsService.create(instance, {
|
||||||
|
enabled: true,
|
||||||
|
events: newEvents,
|
||||||
|
});
|
||||||
|
|
||||||
|
sqsEvents = (await this.sqsService.find(instance)).events;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proxy) {
|
||||||
|
const testProxy = await this.proxyService.testProxy(proxy);
|
||||||
|
if (!testProxy) {
|
||||||
|
throw new BadRequestException('Invalid proxy');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.proxyService.createProxy(instance, {
|
||||||
|
enabled: true,
|
||||||
|
proxy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typebot_url) {
|
||||||
|
try {
|
||||||
|
if (!isURL(typebot_url, { require_tld: false })) {
|
||||||
|
throw new BadRequestException('Invalid "url" property in typebot_url');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('creating typebot');
|
||||||
|
|
||||||
|
this.typebotService.create(instance, {
|
||||||
|
enabled: true,
|
||||||
|
url: typebot_url,
|
||||||
|
typebot: typebot,
|
||||||
|
expire: typebot_expire,
|
||||||
|
keyword_finish: typebot_keyword_finish,
|
||||||
|
delay_message: typebot_delay_message,
|
||||||
|
unknown_message: typebot_unknown_message,
|
||||||
|
listening_from_me: typebot_listening_from_me,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('creating settings');
|
||||||
|
const settings: wa.LocalSettings = {
|
||||||
|
reject_call: reject_call || false,
|
||||||
|
msg_call: msg_call || '',
|
||||||
|
groups_ignore: groups_ignore || true,
|
||||||
|
always_online: always_online || false,
|
||||||
|
read_messages: read_messages || false,
|
||||||
|
read_status: read_status || false,
|
||||||
|
sync_full_history: sync_full_history ?? false,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.logger.verbose('settings: ' + JSON.stringify(settings));
|
||||||
|
|
||||||
|
this.settingsService.create(instance, settings);
|
||||||
|
|
||||||
|
let webhook_wa_business = null,
|
||||||
|
access_token_wa_business = '';
|
||||||
|
|
||||||
|
if (integration === Integration.WHATSAPP_BUSINESS) {
|
||||||
|
if (!number) {
|
||||||
|
throw new BadRequestException('number is required');
|
||||||
|
}
|
||||||
|
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||||
|
webhook_wa_business = `${urlServer}/webhook/whatsapp/${encodeURIComponent(instance.instanceName)}`;
|
||||||
|
access_token_wa_business = this.configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.integrationService.create(instance, {
|
||||||
|
integration,
|
||||||
|
number,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
|
||||||
|
let getQrcode: wa.QrCode;
|
||||||
|
|
||||||
|
if (qrcode) {
|
||||||
|
this.logger.verbose('creating qrcode');
|
||||||
|
await instance.connectToWhatsapp(number, mobile);
|
||||||
|
await delay(5000);
|
||||||
|
getQrcode = instance.qrCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
instance: {
|
||||||
|
instanceName: instance.instanceName,
|
||||||
|
instanceId: instanceId,
|
||||||
|
integration: integration,
|
||||||
|
webhook_wa_business,
|
||||||
|
access_token_wa_business,
|
||||||
|
status: 'created',
|
||||||
|
},
|
||||||
|
hash,
|
||||||
|
webhook: {
|
||||||
|
webhook,
|
||||||
|
webhook_by_events,
|
||||||
|
webhook_base64,
|
||||||
|
events: webhookEvents,
|
||||||
|
},
|
||||||
|
websocket: {
|
||||||
|
enabled: websocket_enabled,
|
||||||
|
events: websocketEvents,
|
||||||
|
},
|
||||||
|
rabbitmq: {
|
||||||
|
enabled: rabbitmq_enabled,
|
||||||
|
events: rabbitmqEvents,
|
||||||
|
},
|
||||||
|
sqs: {
|
||||||
|
enabled: sqs_enabled,
|
||||||
|
events: sqsEvents,
|
||||||
|
},
|
||||||
|
typebot: {
|
||||||
|
enabled: typebot_url ? true : false,
|
||||||
|
url: typebot_url,
|
||||||
|
typebot,
|
||||||
|
expire: typebot_expire,
|
||||||
|
keyword_finish: typebot_keyword_finish,
|
||||||
|
delay_message: typebot_delay_message,
|
||||||
|
unknown_message: typebot_unknown_message,
|
||||||
|
listening_from_me: typebot_listening_from_me,
|
||||||
|
},
|
||||||
|
settings,
|
||||||
|
qrcode: getQrcode,
|
||||||
|
};
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chatwoot_sign_msg !== true && chatwoot_sign_msg !== false) {
|
||||||
|
throw new BadRequestException('sign_msg is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chatwoot_reopen_conversation !== true && chatwoot_reopen_conversation !== false) {
|
||||||
|
throw new BadRequestException('reopen_conversation is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chatwoot_conversation_pending !== true && chatwoot_conversation_pending !== false) {
|
||||||
|
throw new BadRequestException('conversation_pending is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
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: chatwoot_name_inbox ?? instance.instanceName.split('-cwId-')[0],
|
||||||
|
number,
|
||||||
|
reopen_conversation: chatwoot_reopen_conversation || false,
|
||||||
|
conversation_pending: chatwoot_conversation_pending || false,
|
||||||
|
import_contacts: chatwoot_import_contacts ?? true,
|
||||||
|
merge_brazil_contacts: chatwoot_merge_brazil_contacts ?? false,
|
||||||
|
import_messages: chatwoot_import_messages ?? true,
|
||||||
|
days_limit_import_messages: chatwoot_days_limit_import_messages ?? 60,
|
||||||
|
auto_create: true,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
instance: {
|
||||||
|
instanceName: instance.instanceName,
|
||||||
|
instanceId: instanceId,
|
||||||
|
integration: integration,
|
||||||
|
webhook_wa_business,
|
||||||
|
access_token_wa_business,
|
||||||
|
status: 'created',
|
||||||
|
},
|
||||||
|
hash,
|
||||||
|
webhook: {
|
||||||
|
webhook,
|
||||||
|
webhook_by_events,
|
||||||
|
webhook_base64,
|
||||||
|
events: webhookEvents,
|
||||||
|
},
|
||||||
|
websocket: {
|
||||||
|
enabled: websocket_enabled,
|
||||||
|
events: websocketEvents,
|
||||||
|
},
|
||||||
|
rabbitmq: {
|
||||||
|
enabled: rabbitmq_enabled,
|
||||||
|
events: rabbitmqEvents,
|
||||||
|
},
|
||||||
|
sqs: {
|
||||||
|
enabled: sqs_enabled,
|
||||||
|
events: sqsEvents,
|
||||||
|
},
|
||||||
|
typebot: {
|
||||||
|
enabled: typebot_url ? true : false,
|
||||||
|
url: typebot_url,
|
||||||
|
typebot,
|
||||||
|
expire: typebot_expire,
|
||||||
|
keyword_finish: typebot_keyword_finish,
|
||||||
|
delay_message: typebot_delay_message,
|
||||||
|
unknown_message: typebot_unknown_message,
|
||||||
|
listening_from_me: typebot_listening_from_me,
|
||||||
|
},
|
||||||
|
settings,
|
||||||
|
chatwoot: {
|
||||||
|
enabled: true,
|
||||||
|
account_id: chatwoot_account_id,
|
||||||
|
token: chatwoot_token,
|
||||||
|
url: chatwoot_url,
|
||||||
|
sign_msg: chatwoot_sign_msg || false,
|
||||||
|
reopen_conversation: chatwoot_reopen_conversation || false,
|
||||||
|
conversation_pending: chatwoot_conversation_pending || false,
|
||||||
|
merge_brazil_contacts: chatwoot_merge_brazil_contacts ?? false,
|
||||||
|
import_contacts: chatwoot_import_contacts ?? true,
|
||||||
|
import_messages: chatwoot_import_messages ?? true,
|
||||||
|
days_limit_import_messages: chatwoot_days_limit_import_messages || 60,
|
||||||
|
number,
|
||||||
|
name_inbox: chatwoot_name_inbox ?? instance.instanceName,
|
||||||
|
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error.message[0]);
|
||||||
|
throw new BadRequestException(error.message[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connectToWhatsapp({ instanceName, number = null, mobile = 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) {
|
||||||
|
throw new BadRequestException('The "' + instanceName + '" instance does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
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, mobile);
|
||||||
|
|
||||||
|
await delay(5000);
|
||||||
|
return instance.qrCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
instance: {
|
||||||
|
instanceName: instanceName,
|
||||||
|
status: state,
|
||||||
|
},
|
||||||
|
qrcode: instance?.qrCode,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async restartInstance({ instanceName }: InstanceDto) {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('requested restartInstance from ' + instanceName + ' instance');
|
||||||
|
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
const state = instance?.connectionStatus?.state;
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case 'open':
|
||||||
|
this.logger.verbose('logging out instance: ' + instanceName);
|
||||||
|
instance.clearCacheChatwoot();
|
||||||
|
await instance.reloadConnection();
|
||||||
|
await delay(2000);
|
||||||
|
|
||||||
|
return await this.connectionState({ instanceName });
|
||||||
|
default:
|
||||||
|
return await this.connectionState({ instanceName });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async registerMobileCode({ instanceName }: InstanceDto, { mobileCode }: any) {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('requested registerMobileCode from ' + instanceName + ' instance');
|
||||||
|
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
console.log('mobileCode', mobileCode);
|
||||||
|
await instance.receiveMobileCode(mobileCode);
|
||||||
|
return { status: 'SUCCESS', error: false, response: { message: 'Mobile code registered' } };
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connectionState({ instanceName }: InstanceDto) {
|
||||||
|
this.logger.verbose('requested connectionState from ' + instanceName + ' instance');
|
||||||
|
return {
|
||||||
|
instance: {
|
||||||
|
instanceName: instanceName,
|
||||||
|
state: this.waMonitor.waInstances[instanceName]?.connectionStatus?.state,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchInstances({ instanceName, instanceId, number }: InstanceDto, key: string) {
|
||||||
|
const env = this.configService.get<Auth>('AUTHENTICATION').API_KEY;
|
||||||
|
|
||||||
|
let name = instanceName;
|
||||||
|
let arrayReturn = false;
|
||||||
|
|
||||||
|
if (env.KEY !== key) {
|
||||||
|
const instanceByKey = await this.repository.auth.findByKey(key);
|
||||||
|
if (instanceByKey) {
|
||||||
|
name = instanceByKey._id;
|
||||||
|
arrayReturn = true;
|
||||||
|
} else {
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
this.logger.verbose('requested fetchInstances from ' + name + ' instance');
|
||||||
|
this.logger.verbose('instanceName: ' + name);
|
||||||
|
return this.waMonitor.instanceInfo(name, arrayReturn);
|
||||||
|
} else if (instanceId || number) {
|
||||||
|
return this.waMonitor.instanceInfoById(instanceId, number);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('requested fetchInstances (all instances)');
|
||||||
|
return this.waMonitor.instanceInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) {
|
||||||
|
this.logger.verbose('requested sendPresence from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].setPresence(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async logout({ instanceName }: InstanceDto) {
|
||||||
|
this.logger.verbose('requested logout from ' + instanceName + ' instance');
|
||||||
|
const { instance } = await this.connectionState({ instanceName });
|
||||||
|
|
||||||
|
if (instance.state === 'close') {
|
||||||
|
throw new BadRequestException('The "' + instanceName + '" instance is not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.waMonitor.waInstances[instanceName]?.logoutInstance();
|
||||||
|
|
||||||
|
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalServerErrorException(error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteInstance({ instanceName }: InstanceDto) {
|
||||||
|
this.logger.verbose('requested deleteInstance from ' + instanceName + ' instance');
|
||||||
|
const { instance } = await this.connectionState({ instanceName });
|
||||||
|
|
||||||
|
if (instance.state === 'open') {
|
||||||
|
throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
|
||||||
|
this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot();
|
||||||
|
|
||||||
|
if (instance.state === 'connecting') {
|
||||||
|
this.logger.verbose('logging out instance: ' + instanceName);
|
||||||
|
|
||||||
|
await this.logout({ instanceName });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('deleting instance: ' + instanceName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.waMonitor.waInstances[instanceName]?.sendDataWebhook(Events.INSTANCE_DELETE, {
|
||||||
|
instanceName,
|
||||||
|
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this.waMonitor.waInstances[instanceName];
|
||||||
|
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
|
||||||
|
return { status: 'SUCCESS', error: false, response: { 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/api/controllers/label.controller.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Logger } from '../../config/logger.config';
|
||||||
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
|
import { HandleLabelDto } from '../dto/label.dto';
|
||||||
|
import { WAMonitoringService } from '../services/monitor.service';
|
||||||
|
|
||||||
|
const logger = new Logger('LabelController');
|
||||||
|
|
||||||
|
export class LabelController {
|
||||||
|
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||||
|
|
||||||
|
public async fetchLabels({ instanceName }: InstanceDto) {
|
||||||
|
logger.verbose('requested fetchLabels from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].fetchLabels();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleLabel({ instanceName }: InstanceDto, data: HandleLabelDto) {
|
||||||
|
logger.verbose('requested chat label change from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].handleLabel(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/api/controllers/proxy.controller.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import { Logger } from '../../config/logger.config';
|
||||||
|
import { BadRequestException, NotFoundException } from '../../exceptions';
|
||||||
|
import { makeProxyAgent } from '../../utils/makeProxyAgent';
|
||||||
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
|
import { ProxyDto } from '../dto/proxy.dto';
|
||||||
|
import { WAMonitoringService } from '../services/monitor.service';
|
||||||
|
import { ProxyService } from '../services/proxy.service';
|
||||||
|
|
||||||
|
const logger = new Logger('ProxyController');
|
||||||
|
|
||||||
|
export class ProxyController {
|
||||||
|
constructor(private readonly proxyService: ProxyService, private readonly waMonitor: WAMonitoringService) {}
|
||||||
|
|
||||||
|
public async createProxy(instance: InstanceDto, data: ProxyDto) {
|
||||||
|
logger.verbose('requested createProxy from ' + instance.instanceName + ' instance');
|
||||||
|
|
||||||
|
if (!this.waMonitor.waInstances[instance.instanceName]) {
|
||||||
|
throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.enabled) {
|
||||||
|
logger.verbose('proxy disabled');
|
||||||
|
data.proxy = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.proxy) {
|
||||||
|
const testProxy = await this.testProxy(data.proxy);
|
||||||
|
if (!testProxy) {
|
||||||
|
throw new BadRequestException('Invalid proxy');
|
||||||
|
}
|
||||||
|
logger.verbose('proxy enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.proxyService.create(instance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findProxy(instance: InstanceDto) {
|
||||||
|
logger.verbose('requested findProxy from ' + instance.instanceName + ' instance');
|
||||||
|
|
||||||
|
if (!this.waMonitor.waInstances[instance.instanceName]) {
|
||||||
|
throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.proxyService.find(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async testProxy(proxy: ProxyDto['proxy']) {
|
||||||
|
logger.verbose('requested testProxy');
|
||||||
|
try {
|
||||||
|
const serverIp = await axios.get('https://icanhazip.com/');
|
||||||
|
const response = await axios.get('https://icanhazip.com/', {
|
||||||
|
httpsAgent: makeProxyAgent(proxy),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.verbose('[testProxy] from IP: ' + response?.data + ' To IP: ' + serverIp?.data);
|
||||||
|
return response?.data !== serverIp?.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isAxiosError(error) && error.response?.data) {
|
||||||
|
logger.error('testProxy error: ' + error.response.data);
|
||||||
|
} else if (axios.isAxiosError(error)) {
|
||||||
|
logger.error('testProxy error: ');
|
||||||
|
logger.verbose(error.cause ?? error.message);
|
||||||
|
} else {
|
||||||
|
logger.error('testProxy error: ');
|
||||||
|
logger.verbose(error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
src/api/controllers/sendMessage.controller.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { isBase64, isURL } from 'class-validator';
|
||||||
|
|
||||||
|
import { Logger } from '../../config/logger.config';
|
||||||
|
import { BadRequestException } from '../../exceptions';
|
||||||
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
|
import {
|
||||||
|
SendAudioDto,
|
||||||
|
SendButtonDto,
|
||||||
|
SendContactDto,
|
||||||
|
SendListDto,
|
||||||
|
SendLocationDto,
|
||||||
|
SendMediaDto,
|
||||||
|
SendPollDto,
|
||||||
|
SendReactionDto,
|
||||||
|
SendStatusDto,
|
||||||
|
SendStickerDto,
|
||||||
|
SendTemplateDto,
|
||||||
|
SendTextDto,
|
||||||
|
} from '../dto/sendMessage.dto';
|
||||||
|
import { WAMonitoringService } from '../services/monitor.service';
|
||||||
|
|
||||||
|
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 sendTemplate({ instanceName }: InstanceDto, data: SendTemplateDto) {
|
||||||
|
logger.verbose('requested sendList from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].templateMessage(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
throw new BadRequestException('Owned media must be a url or base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendButtons({ instanceName }: InstanceDto, data: SendButtonDto) {
|
||||||
|
logger.verbose('requested sendButtons from ' + instanceName + ' instance');
|
||||||
|
if (isBase64(data.buttonMessage.mediaMessage?.media) && !data.buttonMessage.mediaMessage?.fileName) {
|
||||||
|
throw new BadRequestException('For bse64 the file name must be informed.');
|
||||||
|
}
|
||||||
|
return await this.waMonitor.waInstances[instanceName].buttonMessage(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
return await this.waMonitor.waInstances[instanceName].reactionMessage(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendPoll({ instanceName }: InstanceDto, data: SendPollDto) {
|
||||||
|
logger.verbose('requested sendPoll from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].pollMessage(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto) {
|
||||||
|
logger.verbose('requested sendStatus from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].statusMessage(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/api/controllers/settings.controller.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Logger } from '../../config/logger.config';
|
||||||
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
|
import { SettingsDto } from '../dto/settings.dto';
|
||||||
|
import { SettingsService } from '../services/settings.service';
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
return this.settingsService.create(instance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findSettings(instance: InstanceDto) {
|
||||||
|
logger.verbose('requested findSettings from ' + instance.instanceName + ' instance');
|
||||||
|
const settings = this.settingsService.find(instance);
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/api/controllers/webhook.controller.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { isURL } from 'class-validator';
|
||||||
|
|
||||||
|
import { Logger } from '../../config/logger.config';
|
||||||
|
import { BadRequestException } from '../../exceptions';
|
||||||
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
|
import { WebhookDto } from '../dto/webhook.dto';
|
||||||
|
import { WAMonitoringService } from '../services/monitor.service';
|
||||||
|
import { WebhookService } from '../services/webhook.service';
|
||||||
|
|
||||||
|
const logger = new Logger('WebhookController');
|
||||||
|
|
||||||
|
export class WebhookController {
|
||||||
|
constructor(private readonly webhookService: WebhookService, private readonly waMonitor: WAMonitoringService) {}
|
||||||
|
|
||||||
|
public async createWebhook(instance: InstanceDto, data: WebhookDto) {
|
||||||
|
logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance');
|
||||||
|
|
||||||
|
if (!isURL(data.url, { require_tld: false })) {
|
||||||
|
throw new BadRequestException('Invalid "url" property');
|
||||||
|
}
|
||||||
|
|
||||||
|
data.enabled = data.enabled ?? true;
|
||||||
|
|
||||||
|
if (!data.enabled) {
|
||||||
|
logger.verbose('webhook disabled');
|
||||||
|
data.url = '';
|
||||||
|
data.events = [];
|
||||||
|
} else if (data.events.length === 0) {
|
||||||
|
logger.verbose('webhook events empty');
|
||||||
|
data.events = [
|
||||||
|
'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',
|
||||||
|
'LABELS_EDIT',
|
||||||
|
'LABELS_ASSOCIATION',
|
||||||
|
'CALL',
|
||||||
|
'NEW_JWT_TOKEN',
|
||||||
|
'TYPEBOT_START',
|
||||||
|
'TYPEBOT_CHANGE_STATUS',
|
||||||
|
'CHAMA_AI_ACTION',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.webhookService.create(instance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findWebhook(instance: InstanceDto) {
|
||||||
|
logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance');
|
||||||
|
return this.webhookService.find(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||||
|
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instance.instanceName].connectToWhatsapp(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
127
src/api/dto/chat.dto.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys';
|
||||||
|
|
||||||
|
export class OnWhatsAppDto {
|
||||||
|
constructor(
|
||||||
|
public readonly jid: string,
|
||||||
|
public readonly exists: boolean,
|
||||||
|
public readonly number: string,
|
||||||
|
public readonly name?: string,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class getBase64FromMediaMessageDto {
|
||||||
|
message: proto.WebMessageInfo;
|
||||||
|
convertToMp4?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WhatsAppNumberDto {
|
||||||
|
numbers: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NumberDto {
|
||||||
|
number: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NumberBusiness {
|
||||||
|
wid?: string;
|
||||||
|
jid?: string;
|
||||||
|
exists?: boolean;
|
||||||
|
isBusiness: boolean;
|
||||||
|
name?: string;
|
||||||
|
message?: string;
|
||||||
|
description?: string;
|
||||||
|
email?: string;
|
||||||
|
websites?: string[];
|
||||||
|
website?: string[];
|
||||||
|
address?: string;
|
||||||
|
about?: string;
|
||||||
|
vertical?: string;
|
||||||
|
profilehandle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProfileNameDto {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProfileStatusDto {
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProfilePictureDto {
|
||||||
|
number?: string;
|
||||||
|
// url or base64
|
||||||
|
picture?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Key {
|
||||||
|
id: string;
|
||||||
|
fromMe: boolean;
|
||||||
|
remoteJid: string;
|
||||||
|
}
|
||||||
|
export class ReadMessageDto {
|
||||||
|
read_messages: Key[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LastMessage {
|
||||||
|
key: Key;
|
||||||
|
messageTimestamp?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ArchiveChatDto {
|
||||||
|
lastMessage?: LastMessage;
|
||||||
|
chat?: string;
|
||||||
|
archive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MarkChatUnreadDto {
|
||||||
|
lastMessage?: LastMessage;
|
||||||
|
chat?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
remoteJid: string;
|
||||||
|
participant?: string;
|
||||||
|
}
|
||||||
|
export class Options {
|
||||||
|
delay?: number;
|
||||||
|
presence?: WAPresence;
|
||||||
|
}
|
||||||
|
class OptionsMessage {
|
||||||
|
options: Options;
|
||||||
|
}
|
||||||
|
export class Metadata extends OptionsMessage {
|
||||||
|
number: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SendPresenceDto extends Metadata {
|
||||||
|
options: {
|
||||||
|
presence: WAPresence;
|
||||||
|
delay: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateMessageDto extends Metadata {
|
||||||
|
number: string;
|
||||||
|
key: proto.IMessageKey;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BlockUserDto {
|
||||||
|
number: string;
|
||||||
|
status: 'block' | 'unblock';
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
export class CreateGroupDto {
|
export class CreateGroupDto {
|
||||||
subject: string;
|
subject: string;
|
||||||
description?: string;
|
|
||||||
participants: string[];
|
participants: string[];
|
||||||
|
description?: string;
|
||||||
|
promoteParticipants?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GroupPictureDto {
|
export class GroupPictureDto {
|
||||||
@@ -9,14 +10,38 @@ export class GroupPictureDto {
|
|||||||
image: string;
|
image: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GroupSubjectDto {
|
||||||
|
groupJid: string;
|
||||||
|
subject: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupDescriptionDto {
|
||||||
|
groupJid: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class GroupJid {
|
export class GroupJid {
|
||||||
groupJid: string;
|
groupJid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GetParticipant {
|
||||||
|
getParticipants: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class GroupInvite {
|
export class GroupInvite {
|
||||||
inviteCode: string;
|
inviteCode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AcceptGroupInvite {
|
||||||
|
inviteCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GroupSendInvite {
|
||||||
|
groupJid: string;
|
||||||
|
description: string;
|
||||||
|
numbers: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export class GroupUpdateParticipantDto extends GroupJid {
|
export class GroupUpdateParticipantDto extends GroupJid {
|
||||||
action: 'add' | 'remove' | 'promote' | 'demote';
|
action: 'add' | 'remove' | 'promote' | 'demote';
|
||||||
participants: string[];
|
participants: string[];
|
||||||
53
src/api/dto/instance.dto.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { WAPresence } from '@whiskeysockets/baileys';
|
||||||
|
|
||||||
|
import { ProxyDto } from './proxy.dto';
|
||||||
|
|
||||||
|
export class InstanceDto {
|
||||||
|
instanceName: string;
|
||||||
|
instanceId?: string;
|
||||||
|
qrcode?: boolean;
|
||||||
|
number?: string;
|
||||||
|
mobile?: boolean;
|
||||||
|
integration?: string;
|
||||||
|
token?: string;
|
||||||
|
webhook?: string;
|
||||||
|
webhook_by_events?: boolean;
|
||||||
|
webhook_base64?: boolean;
|
||||||
|
events?: string[];
|
||||||
|
reject_call?: boolean;
|
||||||
|
msg_call?: string;
|
||||||
|
groups_ignore?: boolean;
|
||||||
|
always_online?: boolean;
|
||||||
|
read_messages?: boolean;
|
||||||
|
read_status?: boolean;
|
||||||
|
sync_full_history?: boolean;
|
||||||
|
chatwoot_account_id?: string;
|
||||||
|
chatwoot_token?: string;
|
||||||
|
chatwoot_url?: string;
|
||||||
|
chatwoot_sign_msg?: boolean;
|
||||||
|
chatwoot_reopen_conversation?: boolean;
|
||||||
|
chatwoot_conversation_pending?: boolean;
|
||||||
|
chatwoot_merge_brazil_contacts?: boolean;
|
||||||
|
chatwoot_import_contacts?: boolean;
|
||||||
|
chatwoot_import_messages?: boolean;
|
||||||
|
chatwoot_days_limit_import_messages?: number;
|
||||||
|
chatwoot_name_inbox?: string;
|
||||||
|
websocket_enabled?: boolean;
|
||||||
|
websocket_events?: string[];
|
||||||
|
rabbitmq_enabled?: boolean;
|
||||||
|
rabbitmq_events?: string[];
|
||||||
|
sqs_enabled?: boolean;
|
||||||
|
sqs_events?: string[];
|
||||||
|
typebot_url?: string;
|
||||||
|
typebot?: string;
|
||||||
|
typebot_expire?: number;
|
||||||
|
typebot_keyword_finish?: string;
|
||||||
|
typebot_delay_message?: number;
|
||||||
|
typebot_unknown_message?: string;
|
||||||
|
typebot_listening_from_me?: boolean;
|
||||||
|
proxy?: ProxyDto['proxy'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SetPresenceDto {
|
||||||
|
presence: WAPresence;
|
||||||
|
}
|
||||||
5
src/api/dto/integration.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export class IntegrationDto {
|
||||||
|
integration: string;
|
||||||
|
number: string;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
12
src/api/dto/label.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export class LabelDto {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
color: number;
|
||||||
|
predefinedId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HandleLabelDto {
|
||||||
|
number: string;
|
||||||
|
labelId: string;
|
||||||
|
action: 'add' | 'remove';
|
||||||
|
}
|
||||||
12
src/api/dto/proxy.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
class Proxy {
|
||||||
|
host: string;
|
||||||
|
port: string;
|
||||||
|
protocol: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProxyDto {
|
||||||
|
enabled: boolean;
|
||||||
|
proxy: Proxy;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { proto, WAPresence } from '@evolution/base';
|
import { proto, WAPresence } from '@whiskeysockets/baileys';
|
||||||
|
|
||||||
export class Quoted {
|
export class Quoted {
|
||||||
key: proto.IMessageKey;
|
key: proto.IMessageKey;
|
||||||
@@ -15,6 +15,8 @@ export class Options {
|
|||||||
presence?: WAPresence;
|
presence?: WAPresence;
|
||||||
quoted?: Quoted;
|
quoted?: Quoted;
|
||||||
mentions?: Mentions;
|
mentions?: Mentions;
|
||||||
|
linkPreview?: boolean;
|
||||||
|
encoding?: boolean;
|
||||||
}
|
}
|
||||||
class OptionsMessage {
|
class OptionsMessage {
|
||||||
options: Options;
|
options: Options;
|
||||||
@@ -28,8 +30,14 @@ class TextMessage {
|
|||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class linkPreviewMessage {
|
export class StatusMessage {
|
||||||
text: string;
|
type: string;
|
||||||
|
content: string;
|
||||||
|
statusJidList?: string[];
|
||||||
|
allContacts?: boolean;
|
||||||
|
caption?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
font?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PollMessage {
|
class PollMessage {
|
||||||
@@ -38,12 +46,16 @@ class PollMessage {
|
|||||||
values: string[];
|
values: string[];
|
||||||
messageSecret?: Uint8Array;
|
messageSecret?: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SendTextDto extends Metadata {
|
export class SendTextDto extends Metadata {
|
||||||
textMessage: TextMessage;
|
textMessage: TextMessage;
|
||||||
}
|
}
|
||||||
|
export class SendPresence extends Metadata {
|
||||||
|
textMessage: TextMessage;
|
||||||
|
}
|
||||||
|
|
||||||
export class SendLinkPreviewDto extends Metadata {
|
export class SendStatusDto extends Metadata {
|
||||||
linkPreview: linkPreviewMessage;
|
statusMessage: StatusMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SendPollDto extends Metadata {
|
export class SendPollDto extends Metadata {
|
||||||
@@ -53,6 +65,7 @@ export class SendPollDto extends Metadata {
|
|||||||
export type MediaType = 'image' | 'document' | 'video' | 'audio';
|
export type MediaType = 'image' | 'document' | 'video' | 'audio';
|
||||||
export class MediaMessage {
|
export class MediaMessage {
|
||||||
mediatype: MediaType;
|
mediatype: MediaType;
|
||||||
|
mimetype?: string;
|
||||||
caption?: string;
|
caption?: string;
|
||||||
// for document
|
// for document
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
@@ -62,6 +75,12 @@ export class MediaMessage {
|
|||||||
export class SendMediaDto extends Metadata {
|
export class SendMediaDto extends Metadata {
|
||||||
mediaMessage: MediaMessage;
|
mediaMessage: MediaMessage;
|
||||||
}
|
}
|
||||||
|
class Sticker {
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
export class SendStickerDto extends Metadata {
|
||||||
|
stickerMessage: Sticker;
|
||||||
|
}
|
||||||
|
|
||||||
class Audio {
|
class Audio {
|
||||||
audio: string;
|
audio: string;
|
||||||
@@ -119,6 +138,19 @@ export class ContactMessage {
|
|||||||
fullName: string;
|
fullName: string;
|
||||||
wuid: string;
|
wuid: string;
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
|
organization?: string;
|
||||||
|
email?: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TemplateMessage {
|
||||||
|
name: string;
|
||||||
|
language: string;
|
||||||
|
components: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SendTemplateDto extends Metadata {
|
||||||
|
templateMessage: TemplateMessage;
|
||||||
}
|
}
|
||||||
export class SendContactDto extends Metadata {
|
export class SendContactDto extends Metadata {
|
||||||
contactMessage: ContactMessage[];
|
contactMessage: ContactMessage[];
|
||||||
9
src/api/dto/settings.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export class SettingsDto {
|
||||||
|
reject_call?: boolean;
|
||||||
|
msg_call?: string;
|
||||||
|
groups_ignore?: boolean;
|
||||||
|
always_online?: boolean;
|
||||||
|
read_messages?: boolean;
|
||||||
|
read_status?: boolean;
|
||||||
|
sync_full_history?: boolean;
|
||||||
|
}
|
||||||
7
src/api/dto/webhook.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export class WebhookDto {
|
||||||
|
enabled?: boolean;
|
||||||
|
url?: string;
|
||||||
|
events?: string[];
|
||||||
|
webhook_by_events?: boolean;
|
||||||
|
webhook_base64?: boolean;
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { isJWT } from 'class-validator';
|
import { isJWT } from 'class-validator';
|
||||||
import { NextFunction, Request, Response } from 'express';
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { Auth, configService } from '../../config/env.config';
|
|
||||||
import { Logger } from '../../config/logger.config';
|
|
||||||
import { name } from '../../../package.json';
|
import { name } from '../../../package.json';
|
||||||
import { InstanceDto } from '../dto/instance.dto';
|
import { Auth, configService, Database } from '../../config/env.config';
|
||||||
import { JwtPayload } from '../services/auth.service';
|
import { Logger } from '../../config/logger.config';
|
||||||
import { ForbiddenException, UnauthorizedException } from '../../exceptions';
|
import { ForbiddenException, UnauthorizedException } from '../../exceptions';
|
||||||
import { repository } from '../whatsapp.module';
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
|
import { repository } from '../server.module';
|
||||||
|
import { JwtPayload } from '../services/auth.service';
|
||||||
|
|
||||||
const logger = new Logger('GUARD');
|
const logger = new Logger('GUARD');
|
||||||
|
|
||||||
@@ -22,15 +23,8 @@ async function jwtGuard(req: Request, res: Response, next: NextFunction) {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {
|
||||||
(req.originalUrl.includes('/instance/create') ||
|
throw new ForbiddenException('Missing global api key', 'The global api key must be set');
|
||||||
req.originalUrl.includes('/instance/fetchInstances')) &&
|
|
||||||
!key
|
|
||||||
) {
|
|
||||||
throw new ForbiddenException(
|
|
||||||
'Missing global api key',
|
|
||||||
'The global api key must be set',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const jwtOpts = configService.get<Auth>('AUTHENTICATION').JWT;
|
const jwtOpts = configService.get<Auth>('AUTHENTICATION').JWT;
|
||||||
@@ -61,32 +55,41 @@ async function jwtGuard(req: Request, res: Response, next: NextFunction) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apikey(req: Request, res: Response, next: NextFunction) {
|
async function apikey(req: Request, _: Response, next: NextFunction) {
|
||||||
const env = configService.get<Auth>('AUTHENTICATION').API_KEY;
|
const env = configService.get<Auth>('AUTHENTICATION').API_KEY;
|
||||||
const key = req.get('apikey');
|
const key = req.get('apikey');
|
||||||
|
const db = configService.get<Database>('DATABASE');
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
|
||||||
if (env.KEY === key) {
|
if (env.KEY === key) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {
|
||||||
(req.originalUrl.includes('/instance/create') ||
|
throw new ForbiddenException('Missing global api key', 'The global api key must be set');
|
||||||
req.originalUrl.includes('/instance/fetchInstances')) &&
|
|
||||||
!key
|
|
||||||
) {
|
|
||||||
throw new ForbiddenException(
|
|
||||||
'Missing global api key',
|
|
||||||
'The global api key must be set',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
const param = req.params as unknown as InstanceDto;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const param = req.params as unknown as InstanceDto;
|
if (param?.instanceName) {
|
||||||
const instanceKey = await repository.auth.find(param.instanceName);
|
const instanceKey = await repository.auth.find(param.instanceName);
|
||||||
if (instanceKey.apikey === key) {
|
if (instanceKey?.apikey === key) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} else {
|
||||||
|
if (req.originalUrl.includes('/instance/fetchInstances') && db.ENABLED) {
|
||||||
|
const instanceByKey = await repository.auth.findByKey(key);
|
||||||
|
if (instanceByKey) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
75
src/api/guards/instance.guard.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { CacheConf, configService, Database } from '../../config/env.config';
|
||||||
|
import { INSTANCE_DIR } from '../../config/path.config';
|
||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
ForbiddenException,
|
||||||
|
InternalServerErrorException,
|
||||||
|
NotFoundException,
|
||||||
|
} from '../../exceptions';
|
||||||
|
import { dbserver } from '../../libs/db.connect';
|
||||||
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
|
import { cache, waMonitor } from '../server.module';
|
||||||
|
|
||||||
|
async function getInstance(instanceName: string) {
|
||||||
|
try {
|
||||||
|
const db = configService.get<Database>('DATABASE');
|
||||||
|
const cacheConf = configService.get<CacheConf>('CACHE');
|
||||||
|
|
||||||
|
const exists = !!waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) {
|
||||||
|
const keyExists = await cache.has(instanceName);
|
||||||
|
|
||||||
|
return exists || keyExists;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db.ENABLED) {
|
||||||
|
const collection = dbserver
|
||||||
|
.getClient()
|
||||||
|
.db(db.CONNECTION.DB_PREFIX_NAME + '-instances')
|
||||||
|
.collection(instanceName);
|
||||||
|
return exists || (await collection.find({}).toArray()).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return exists || existsSync(join(INSTANCE_DIR, instanceName));
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalServerErrorException(error?.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {
|
||||||
|
if (req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const param = req.params as unknown as InstanceDto;
|
||||||
|
if (!param?.instanceName) {
|
||||||
|
throw new BadRequestException('"instanceName" not provided.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await getInstance(param.instanceName))) {
|
||||||
|
throw new NotFoundException(`The "${param.instanceName}" instance does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) {
|
||||||
|
if (req.originalUrl.includes('/instance/create')) {
|
||||||
|
const instance = req.body as InstanceDto;
|
||||||
|
if (await getInstance(instance.instanceName)) {
|
||||||
|
throw new ForbiddenException(`This name "${instance.instanceName}" is already in use.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waMonitor.waInstances[instance.instanceName]) {
|
||||||
|
waMonitor.waInstances[instance.instanceName]?.removeRabbitmqQueues();
|
||||||
|
delete waMonitor.waInstances[instance.instanceName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { ChamaaiDto } from '../dto/chamaai.dto';
|
||||||
|
import { ChamaaiService } from '../services/chamaai.service';
|
||||||
|
|
||||||
|
const logger = new Logger('ChamaaiController');
|
||||||
|
|
||||||
|
export class ChamaaiController {
|
||||||
|
constructor(private readonly chamaaiService: ChamaaiService) {}
|
||||||
|
|
||||||
|
public async createChamaai(instance: InstanceDto, data: ChamaaiDto) {
|
||||||
|
logger.verbose('requested createChamaai from ' + instance.instanceName + ' instance');
|
||||||
|
|
||||||
|
if (!data.enabled) {
|
||||||
|
logger.verbose('chamaai disabled');
|
||||||
|
data.url = '';
|
||||||
|
data.token = '';
|
||||||
|
data.waNumber = '';
|
||||||
|
data.answerByAudio = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.chamaaiService.create(instance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findChamaai(instance: InstanceDto) {
|
||||||
|
logger.verbose('requested findChamaai from ' + instance.instanceName + ' instance');
|
||||||
|
return this.chamaaiService.find(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/integrations/chamaai/dto/chamaai.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export class ChamaaiDto {
|
||||||
|
enabled: boolean;
|
||||||
|
url: string;
|
||||||
|
token: string;
|
||||||
|
waNumber: string;
|
||||||
|
answerByAudio: boolean;
|
||||||
|
}
|
||||||
24
src/api/integrations/chamaai/models/chamaai.model.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Schema } from 'mongoose';
|
||||||
|
|
||||||
|
import { dbserver } from '../../../../libs/db.connect';
|
||||||
|
|
||||||
|
export class ChamaaiRaw {
|
||||||
|
_id?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
url?: string;
|
||||||
|
token?: string;
|
||||||
|
waNumber?: string;
|
||||||
|
answerByAudio?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chamaaiSchema = new Schema<ChamaaiRaw>({
|
||||||
|
_id: { type: String, _id: true },
|
||||||
|
enabled: { type: Boolean, required: true },
|
||||||
|
url: { type: String, required: true },
|
||||||
|
token: { type: String, required: true },
|
||||||
|
waNumber: { type: String, required: true },
|
||||||
|
answerByAudio: { type: Boolean, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ChamaaiModel = dbserver?.model(ChamaaiRaw.name, chamaaiSchema, 'chamaai');
|
||||||
|
export type IChamaaiModel = typeof ChamaaiModel;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { ConfigService } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { IInsert, Repository } from '../../../abstract/abstract.repository';
|
||||||
|
import { ChamaaiRaw, IChamaaiModel } from '../../../models';
|
||||||
|
|
||||||
|
export class ChamaaiRepository extends Repository {
|
||||||
|
constructor(private readonly chamaaiModel: IChamaaiModel, private readonly configService: ConfigService) {
|
||||||
|
super(configService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly logger = new Logger('ChamaaiRepository');
|
||||||
|
|
||||||
|
public async create(data: ChamaaiRaw, instance: string): Promise<IInsert> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('creating chamaai');
|
||||||
|
if (this.dbSettings.ENABLED) {
|
||||||
|
this.logger.verbose('saving chamaai to db');
|
||||||
|
const insert = await this.chamaaiModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||||
|
|
||||||
|
this.logger.verbose('chamaai saved to db: ' + insert.modifiedCount + ' chamaai');
|
||||||
|
return { insertCount: insert.modifiedCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('saving chamaai to store');
|
||||||
|
|
||||||
|
this.writeStore<ChamaaiRaw>({
|
||||||
|
path: join(this.storePath, 'chamaai'),
|
||||||
|
fileName: instance,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.verbose('chamaai saved to store in path: ' + join(this.storePath, 'chamaai') + '/' + instance);
|
||||||
|
|
||||||
|
this.logger.verbose('chamaai created');
|
||||||
|
return { insertCount: 1 };
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async find(instance: string): Promise<ChamaaiRaw> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('finding chamaai');
|
||||||
|
if (this.dbSettings.ENABLED) {
|
||||||
|
this.logger.verbose('finding chamaai in db');
|
||||||
|
return await this.chamaaiModel.findOne({ _id: instance });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('finding chamaai in store');
|
||||||
|
return JSON.parse(
|
||||||
|
readFileSync(join(this.storePath, 'chamaai', instance + '.json'), {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
}),
|
||||||
|
) as ChamaaiRaw;
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/api/integrations/chamaai/routes/chamaai.router.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { RequestHandler, Router } from 'express';
|
||||||
|
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { chamaaiSchema, instanceNameSchema } from '../../../../validate/validate.schema';
|
||||||
|
import { RouterBroker } from '../../../abstract/abstract.router';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { HttpStatus } from '../../../routes/index.router';
|
||||||
|
import { chamaaiController } from '../../../server.module';
|
||||||
|
import { ChamaaiDto } from '../dto/chamaai.dto';
|
||||||
|
|
||||||
|
const logger = new Logger('ChamaaiRouter');
|
||||||
|
|
||||||
|
export class ChamaaiRouter extends RouterBroker {
|
||||||
|
constructor(...guards: RequestHandler[]) {
|
||||||
|
super();
|
||||||
|
this.router
|
||||||
|
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||||
|
logger.verbose('request received in setChamaai');
|
||||||
|
logger.verbose('request body: ');
|
||||||
|
logger.verbose(req.body);
|
||||||
|
|
||||||
|
logger.verbose('request query: ');
|
||||||
|
logger.verbose(req.query);
|
||||||
|
const response = await this.dataValidate<ChamaaiDto>({
|
||||||
|
request: req,
|
||||||
|
schema: chamaaiSchema,
|
||||||
|
ClassRef: ChamaaiDto,
|
||||||
|
execute: (instance, data) => chamaaiController.createChamaai(instance, data),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.CREATED).json(response);
|
||||||
|
})
|
||||||
|
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||||
|
logger.verbose('request received in findChamaai');
|
||||||
|
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) => chamaaiController.findChamaai(instance),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly router = Router();
|
||||||
|
}
|
||||||
230
src/api/integrations/chamaai/services/chamaai.service.ts
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { writeFileSync } from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
import { ConfigService, HttpServer } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { ChamaaiRaw } from '../../../models';
|
||||||
|
import { WAMonitoringService } from '../../../services/monitor.service';
|
||||||
|
import { Events } from '../../../types/wa.types';
|
||||||
|
import { ChamaaiDto } from '../dto/chamaai.dto';
|
||||||
|
|
||||||
|
export class ChamaaiService {
|
||||||
|
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {}
|
||||||
|
|
||||||
|
private readonly logger = new Logger(ChamaaiService.name);
|
||||||
|
|
||||||
|
public create(instance: InstanceDto, data: ChamaaiDto) {
|
||||||
|
this.logger.verbose('create chamaai: ' + instance.instanceName);
|
||||||
|
this.waMonitor.waInstances[instance.instanceName].setChamaai(data);
|
||||||
|
|
||||||
|
return { chamaai: { ...instance, chamaai: data } };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async find(instance: InstanceDto): Promise<ChamaaiRaw> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('find chamaai: ' + instance.instanceName);
|
||||||
|
const result = await this.waMonitor.waInstances[instance.instanceName].findChamaai();
|
||||||
|
|
||||||
|
if (Object.keys(result).length === 0) {
|
||||||
|
throw new Error('Chamaai not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
return { enabled: false, url: '', token: '', waNumber: '', answerByAudio: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTypeMessage(msg: any) {
|
||||||
|
this.logger.verbose('get type message');
|
||||||
|
|
||||||
|
const types = {
|
||||||
|
conversation: msg.conversation,
|
||||||
|
extendedTextMessage: msg.extendedTextMessage?.text,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.logger.verbose('type message: ' + types);
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMessageContent(types: any) {
|
||||||
|
this.logger.verbose('get message content');
|
||||||
|
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
|
||||||
|
|
||||||
|
const result = typeKey ? types[typeKey] : undefined;
|
||||||
|
|
||||||
|
this.logger.verbose('message content: ' + result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getConversationMessage(msg: any) {
|
||||||
|
this.logger.verbose('get conversation message');
|
||||||
|
|
||||||
|
const types = this.getTypeMessage(msg);
|
||||||
|
|
||||||
|
const messageContent = this.getMessageContent(types);
|
||||||
|
|
||||||
|
this.logger.verbose('conversation message: ' + messageContent);
|
||||||
|
|
||||||
|
return messageContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateTypingTime(text: string) {
|
||||||
|
const wordsPerMinute = 100;
|
||||||
|
|
||||||
|
const wordCount = text.split(' ').length;
|
||||||
|
const typingTimeInMinutes = wordCount / wordsPerMinute;
|
||||||
|
const typingTimeInMilliseconds = typingTimeInMinutes * 60;
|
||||||
|
return typingTimeInMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertToMilliseconds(count: number) {
|
||||||
|
const averageCharactersPerSecond = 15;
|
||||||
|
const characterCount = count;
|
||||||
|
const speakingTimeInSeconds = characterCount / averageCharactersPerSecond;
|
||||||
|
return speakingTimeInSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRegexPatterns() {
|
||||||
|
const patternsToCheck = [
|
||||||
|
'.*atend.*humano.*',
|
||||||
|
'.*falar.*com.*um.*humano.*',
|
||||||
|
'.*fala.*humano.*',
|
||||||
|
'.*atend.*humano.*',
|
||||||
|
'.*fala.*atend.*',
|
||||||
|
'.*preciso.*ajuda.*',
|
||||||
|
'.*quero.*suporte.*',
|
||||||
|
'.*preciso.*assiste.*',
|
||||||
|
'.*ajuda.*atend.*',
|
||||||
|
'.*chama.*atendente.*',
|
||||||
|
'.*suporte.*urgente.*',
|
||||||
|
'.*atend.*por.*favor.*',
|
||||||
|
'.*quero.*falar.*com.*alguém.*',
|
||||||
|
'.*falar.*com.*um.*humano.*',
|
||||||
|
'.*transfer.*humano.*',
|
||||||
|
'.*transfer.*atend.*',
|
||||||
|
'.*equipe.*humano.*',
|
||||||
|
'.*suporte.*humano.*',
|
||||||
|
];
|
||||||
|
|
||||||
|
const regexPatterns = patternsToCheck.map((pattern) => new RegExp(pattern, 'iu'));
|
||||||
|
return regexPatterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendChamaai(instance: InstanceDto, remoteJid: string, msg: any) {
|
||||||
|
const content = this.getConversationMessage(msg.message);
|
||||||
|
const msgType = msg.messageType;
|
||||||
|
const find = await this.find(instance);
|
||||||
|
const url = find.url;
|
||||||
|
const token = find.token;
|
||||||
|
const waNumber = find.waNumber;
|
||||||
|
const answerByAudio = find.answerByAudio;
|
||||||
|
|
||||||
|
if (!content && msgType !== 'audioMessage') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data;
|
||||||
|
let endpoint;
|
||||||
|
|
||||||
|
if (msgType === 'audioMessage') {
|
||||||
|
const downloadBase64 = await this.waMonitor.waInstances[instance.instanceName].getBase64FromMediaMessage({
|
||||||
|
message: {
|
||||||
|
...msg,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const random = Math.random().toString(36).substring(7);
|
||||||
|
const nameFile = `${random}.ogg`;
|
||||||
|
|
||||||
|
const fileData = Buffer.from(downloadBase64.base64, 'base64');
|
||||||
|
|
||||||
|
const fileName = `${path.join(
|
||||||
|
this.waMonitor.waInstances[instance.instanceName].storePath,
|
||||||
|
'temp',
|
||||||
|
`${nameFile}`,
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
writeFileSync(fileName, fileData, 'utf8');
|
||||||
|
|
||||||
|
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||||
|
|
||||||
|
const url = `${urlServer}/store/temp/${nameFile}`;
|
||||||
|
|
||||||
|
data = {
|
||||||
|
waNumber: waNumber,
|
||||||
|
audioUrl: url,
|
||||||
|
queryNumber: remoteJid.split('@')[0],
|
||||||
|
answerByAudio: answerByAudio,
|
||||||
|
};
|
||||||
|
endpoint = 'processMessageAudio';
|
||||||
|
} else {
|
||||||
|
data = {
|
||||||
|
waNumber: waNumber,
|
||||||
|
question: content,
|
||||||
|
queryNumber: remoteJid.split('@')[0],
|
||||||
|
answerByAudio: answerByAudio,
|
||||||
|
};
|
||||||
|
endpoint = 'processMessageText';
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = await axios.post(`${url}/${endpoint}`, data, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const answer = request.data?.answer;
|
||||||
|
|
||||||
|
const type = request.data?.type;
|
||||||
|
|
||||||
|
const characterCount = request.data?.characterCount;
|
||||||
|
|
||||||
|
if (answer) {
|
||||||
|
if (type === 'text') {
|
||||||
|
this.waMonitor.waInstances[instance.instanceName].textMessage({
|
||||||
|
number: remoteJid.split('@')[0],
|
||||||
|
options: {
|
||||||
|
delay: this.calculateTypingTime(answer) * 1000 || 1000,
|
||||||
|
presence: 'composing',
|
||||||
|
linkPreview: false,
|
||||||
|
quoted: {
|
||||||
|
key: msg.key,
|
||||||
|
message: msg.message,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
textMessage: {
|
||||||
|
text: answer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'audio') {
|
||||||
|
this.waMonitor.waInstances[instance.instanceName].audioWhatsapp({
|
||||||
|
number: remoteJid.split('@')[0],
|
||||||
|
options: {
|
||||||
|
delay: characterCount ? this.convertToMilliseconds(characterCount) * 1000 || 1000 : 1000,
|
||||||
|
presence: 'recording',
|
||||||
|
encoding: true,
|
||||||
|
},
|
||||||
|
audioMessage: {
|
||||||
|
audio: answer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getRegexPatterns().some((pattern) => pattern.test(answer))) {
|
||||||
|
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.CHAMA_AI_ACTION, {
|
||||||
|
remoteJid: remoteJid,
|
||||||
|
message: msg,
|
||||||
|
answer: answer,
|
||||||
|
action: 'transfer',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/api/integrations/chamaai/validate/chamaai.schema.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { JSONSchema7 } from 'json-schema';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||||
|
const properties = {};
|
||||||
|
propertyNames.forEach(
|
||||||
|
(property) =>
|
||||||
|
(properties[property] = {
|
||||||
|
minLength: 1,
|
||||||
|
description: `The "${property}" cannot be empty`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
if: {
|
||||||
|
propertyNames: {
|
||||||
|
enum: [...propertyNames],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
then: { properties },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const chamaaiSchema: JSONSchema7 = {
|
||||||
|
$id: v4(),
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
enabled: { type: 'boolean', enum: [true, false] },
|
||||||
|
url: { type: 'string' },
|
||||||
|
token: { type: 'string' },
|
||||||
|
waNumber: { type: 'string' },
|
||||||
|
answerByAudio: { type: 'boolean', enum: [true, false] },
|
||||||
|
},
|
||||||
|
required: ['enabled', 'url', 'token', 'waNumber', 'answerByAudio'],
|
||||||
|
...isNotEmpty('enabled', 'url', 'token', 'waNumber', 'answerByAudio'),
|
||||||
|
};
|
||||||
112
src/api/integrations/chatwoot/controllers/chatwoot.controller.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { isURL } from 'class-validator';
|
||||||
|
|
||||||
|
import { CacheEngine } from '../../../../cache/cacheengine';
|
||||||
|
import { ConfigService, HttpServer } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { BadRequestException } from '../../../../exceptions';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { RepositoryBroker } from '../../../repository/repository.manager';
|
||||||
|
import { waMonitor } from '../../../server.module';
|
||||||
|
import { CacheService } from '../../../services/cache.service';
|
||||||
|
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||||
|
import { ChatwootService } from '../services/chatwoot.service';
|
||||||
|
|
||||||
|
const logger = new Logger('ChatwootController');
|
||||||
|
|
||||||
|
export class ChatwootController {
|
||||||
|
constructor(
|
||||||
|
private readonly chatwootService: ChatwootService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly repository: RepositoryBroker,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
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.sign_msg === false) data.sign_delimiter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.enabled) {
|
||||||
|
logger.verbose('chatwoot disabled');
|
||||||
|
data.account_id = '';
|
||||||
|
data.token = '';
|
||||||
|
data.url = '';
|
||||||
|
data.sign_msg = false;
|
||||||
|
data.sign_delimiter = null;
|
||||||
|
data.reopen_conversation = false;
|
||||||
|
data.conversation_pending = false;
|
||||||
|
data.import_contacts = false;
|
||||||
|
data.import_messages = false;
|
||||||
|
data.merge_brazil_contacts = false;
|
||||||
|
data.days_limit_import_messages = 0;
|
||||||
|
data.auto_create = false;
|
||||||
|
data.name_inbox = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.name_inbox || data.name_inbox === '') {
|
||||||
|
data.name_inbox = instance.instanceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.chatwootService.create(instance, data);
|
||||||
|
|
||||||
|
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
...result,
|
||||||
|
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(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/${encodeURIComponent(instance.instanceName)}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||||
|
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||||
|
|
||||||
|
const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine());
|
||||||
|
const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository, chatwootCache);
|
||||||
|
|
||||||
|
return chatwootService.receiveWebhook(instance, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/api/integrations/chatwoot/dto/chatwoot.dto.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export class ChatwootDto {
|
||||||
|
enabled?: boolean;
|
||||||
|
account_id?: string;
|
||||||
|
token?: string;
|
||||||
|
url?: string;
|
||||||
|
name_inbox?: string;
|
||||||
|
sign_msg?: boolean;
|
||||||
|
sign_delimiter?: string;
|
||||||
|
number?: string;
|
||||||
|
reopen_conversation?: boolean;
|
||||||
|
conversation_pending?: boolean;
|
||||||
|
merge_brazil_contacts?: boolean;
|
||||||
|
import_contacts?: boolean;
|
||||||
|
import_messages?: boolean;
|
||||||
|
days_limit_import_messages?: number;
|
||||||
|
auto_create?: boolean;
|
||||||
|
}
|
||||||
49
src/api/integrations/chatwoot/libs/postgres.client.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import postgresql from 'pg';
|
||||||
|
|
||||||
|
import { Chatwoot, configService } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
|
||||||
|
const { Pool } = postgresql;
|
||||||
|
|
||||||
|
class Postgres {
|
||||||
|
private logger = new Logger(Postgres.name);
|
||||||
|
private pool;
|
||||||
|
private connected = false;
|
||||||
|
|
||||||
|
getConnection(connectionString: string) {
|
||||||
|
if (this.connected) {
|
||||||
|
return this.pool;
|
||||||
|
} else {
|
||||||
|
this.pool = new Pool({
|
||||||
|
connectionString,
|
||||||
|
ssl: {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pool.on('error', () => {
|
||||||
|
this.logger.error('postgres disconnected');
|
||||||
|
this.connected = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.logger.verbose('connecting new postgres');
|
||||||
|
this.connected = true;
|
||||||
|
} catch (e) {
|
||||||
|
this.connected = false;
|
||||||
|
this.logger.error('postgres connect exception caught: ' + e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.pool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getChatwootConnection() {
|
||||||
|
const uri = configService.get<Chatwoot>('CHATWOOT').IMPORT.DATABASE.CONNECTION.URI;
|
||||||
|
|
||||||
|
return this.getConnection(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const postgresClient = new Postgres();
|
||||||
42
src/api/integrations/chatwoot/models/chatwoot.model.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Schema } from 'mongoose';
|
||||||
|
|
||||||
|
import { dbserver } from '../../../../libs/db.connect';
|
||||||
|
|
||||||
|
export class ChatwootRaw {
|
||||||
|
_id?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
account_id?: string;
|
||||||
|
token?: string;
|
||||||
|
url?: string;
|
||||||
|
name_inbox?: string;
|
||||||
|
sign_msg?: boolean;
|
||||||
|
sign_delimiter?: string;
|
||||||
|
number?: string;
|
||||||
|
reopen_conversation?: boolean;
|
||||||
|
conversation_pending?: boolean;
|
||||||
|
merge_brazil_contacts?: boolean;
|
||||||
|
import_contacts?: boolean;
|
||||||
|
import_messages?: boolean;
|
||||||
|
days_limit_import_messages?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 },
|
||||||
|
sign_delimiter: { type: String, required: false },
|
||||||
|
number: { type: String, required: true },
|
||||||
|
reopen_conversation: { type: Boolean, required: true },
|
||||||
|
conversation_pending: { type: Boolean, required: true },
|
||||||
|
merge_brazil_contacts: { type: Boolean, required: true },
|
||||||
|
import_contacts: { type: Boolean, required: true },
|
||||||
|
import_messages: { type: Boolean, required: true },
|
||||||
|
days_limit_import_messages: { type: Number, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ChatwootModel = dbserver?.model(ChatwootRaw.name, chatwootSchema, 'chatwoot');
|
||||||
|
export type IChatwootModel = typeof ChatwootModel;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { ConfigService } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { IInsert, Repository } from '../../../abstract/abstract.repository';
|
||||||
|
import { ChatwootRaw, IChatwootModel } from '../../../models';
|
||||||
|
|
||||||
|
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 {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/api/integrations/chatwoot/routes/chatwoot.router.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { RequestHandler, Router } from 'express';
|
||||||
|
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { chatwootSchema, instanceNameSchema } from '../../../../validate/validate.schema';
|
||||||
|
import { RouterBroker } from '../../../abstract/abstract.router';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { HttpStatus } from '../../../routes/index.router';
|
||||||
|
import { chatwootController } from '../../../server.module';
|
||||||
|
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
2410
src/api/integrations/chatwoot/services/chatwoot.service.ts
Normal file
472
src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
import { inbox } from '@figuro/chatwoot-sdk';
|
||||||
|
import { proto } from '@whiskeysockets/baileys';
|
||||||
|
|
||||||
|
import { InstanceDto } from '../../../../api/dto/instance.dto';
|
||||||
|
import { ChatwootRaw, ContactRaw, MessageRaw } from '../../../../api/models';
|
||||||
|
import { Chatwoot, configService } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { postgresClient } from '../libs/postgres.client';
|
||||||
|
import { ChatwootService } from '../services/chatwoot.service';
|
||||||
|
|
||||||
|
type ChatwootUser = {
|
||||||
|
user_type: string;
|
||||||
|
user_id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FksChatwoot = {
|
||||||
|
phone_number: string;
|
||||||
|
contact_id: string;
|
||||||
|
conversation_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type firstLastTimestamp = {
|
||||||
|
first: number;
|
||||||
|
last: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type IWebMessageInfo = Omit<proto.IWebMessageInfo, 'key'> & Partial<Pick<proto.IWebMessageInfo, 'key'>>;
|
||||||
|
|
||||||
|
class ChatwootImport {
|
||||||
|
private logger = new Logger(ChatwootImport.name);
|
||||||
|
private repositoryMessagesCache = new Map<string, Set<string>>();
|
||||||
|
private historyMessages = new Map<string, MessageRaw[]>();
|
||||||
|
private historyContacts = new Map<string, ContactRaw[]>();
|
||||||
|
|
||||||
|
public getRepositoryMessagesCache(instance: InstanceDto) {
|
||||||
|
return this.repositoryMessagesCache.has(instance.instanceName)
|
||||||
|
? this.repositoryMessagesCache.get(instance.instanceName)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setRepositoryMessagesCache(instance: InstanceDto, repositoryMessagesCache: Set<string>) {
|
||||||
|
this.repositoryMessagesCache.set(instance.instanceName, repositoryMessagesCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteRepositoryMessagesCache(instance: InstanceDto) {
|
||||||
|
this.repositoryMessagesCache.delete(instance.instanceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addHistoryMessages(instance: InstanceDto, messagesRaw: MessageRaw[]) {
|
||||||
|
const actualValue = this.historyMessages.has(instance.instanceName)
|
||||||
|
? this.historyMessages.get(instance.instanceName)
|
||||||
|
: [];
|
||||||
|
this.historyMessages.set(instance.instanceName, actualValue.concat(messagesRaw));
|
||||||
|
}
|
||||||
|
|
||||||
|
public addHistoryContacts(instance: InstanceDto, contactsRaw: ContactRaw[]) {
|
||||||
|
const actualValue = this.historyContacts.has(instance.instanceName)
|
||||||
|
? this.historyContacts.get(instance.instanceName)
|
||||||
|
: [];
|
||||||
|
this.historyContacts.set(instance.instanceName, actualValue.concat(contactsRaw));
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteHistoryMessages(instance: InstanceDto) {
|
||||||
|
this.historyMessages.delete(instance.instanceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteHistoryContacts(instance: InstanceDto) {
|
||||||
|
this.historyContacts.delete(instance.instanceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearAll(instance: InstanceDto) {
|
||||||
|
this.deleteRepositoryMessagesCache(instance);
|
||||||
|
this.deleteHistoryMessages(instance);
|
||||||
|
this.deleteHistoryContacts(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHistoryMessagesLenght(instance: InstanceDto) {
|
||||||
|
return this.historyMessages.get(instance.instanceName)?.length ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async importHistoryContacts(instance: InstanceDto, provider: ChatwootRaw) {
|
||||||
|
try {
|
||||||
|
if (this.getHistoryMessagesLenght(instance) > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pgClient = postgresClient.getChatwootConnection();
|
||||||
|
|
||||||
|
let totalContactsImported = 0;
|
||||||
|
|
||||||
|
const contacts = this.historyContacts.get(instance.instanceName) || [];
|
||||||
|
if (contacts.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contactsChunk: ContactRaw[] = this.sliceIntoChunks(contacts, 3000);
|
||||||
|
while (contactsChunk.length > 0) {
|
||||||
|
// inserting contacts in chatwoot db
|
||||||
|
let sqlInsert = `INSERT INTO contacts
|
||||||
|
(name, phone_number, account_id, identifier, created_at, updated_at) VALUES `;
|
||||||
|
const bindInsert = [provider.account_id];
|
||||||
|
|
||||||
|
for (const contact of contactsChunk) {
|
||||||
|
bindInsert.push(contact.pushName);
|
||||||
|
const bindName = `$${bindInsert.length}`;
|
||||||
|
|
||||||
|
bindInsert.push(`+${contact.id.split('@')[0]}`);
|
||||||
|
const bindPhoneNumber = `$${bindInsert.length}`;
|
||||||
|
|
||||||
|
bindInsert.push(contact.id);
|
||||||
|
const bindIdentifier = `$${bindInsert.length}`;
|
||||||
|
|
||||||
|
sqlInsert += `(${bindName}, ${bindPhoneNumber}, $1, ${bindIdentifier}, NOW(), NOW()),`;
|
||||||
|
}
|
||||||
|
if (sqlInsert.slice(-1) === ',') {
|
||||||
|
sqlInsert = sqlInsert.slice(0, -1);
|
||||||
|
}
|
||||||
|
sqlInsert += ` ON CONFLICT (identifier, account_id)
|
||||||
|
DO UPDATE SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
phone_number = EXCLUDED.phone_number,
|
||||||
|
identifier = EXCLUDED.identifier`;
|
||||||
|
|
||||||
|
totalContactsImported += (await pgClient.query(sqlInsert, bindInsert))?.rowCount ?? 0;
|
||||||
|
contactsChunk = this.sliceIntoChunks(contacts, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deleteHistoryContacts(instance);
|
||||||
|
|
||||||
|
return totalContactsImported;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error on import history contacts: ${error.toString()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async importHistoryMessages(
|
||||||
|
instance: InstanceDto,
|
||||||
|
chatwootService: ChatwootService,
|
||||||
|
inbox: inbox,
|
||||||
|
provider: ChatwootRaw,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const pgClient = postgresClient.getChatwootConnection();
|
||||||
|
|
||||||
|
const chatwootUser = await this.getChatwootUser(provider);
|
||||||
|
if (!chatwootUser) {
|
||||||
|
throw new Error('User not found to import messages.');
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalMessagesImported = 0;
|
||||||
|
|
||||||
|
const messagesOrdered = this.historyMessages.get(instance.instanceName) || [];
|
||||||
|
if (messagesOrdered.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ordering messages by number and timestamp asc
|
||||||
|
messagesOrdered.sort((a, b) => {
|
||||||
|
return (
|
||||||
|
parseInt(a.key.remoteJid) - parseInt(b.key.remoteJid) ||
|
||||||
|
(a.messageTimestamp as number) - (b.messageTimestamp as number)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const allMessagesMappedByPhoneNumber = this.createMessagesMapByPhoneNumber(messagesOrdered);
|
||||||
|
// Map structure: +552199999999 => { first message timestamp from number, last message timestamp from number}
|
||||||
|
const phoneNumbersWithTimestamp = new Map<string, firstLastTimestamp>();
|
||||||
|
allMessagesMappedByPhoneNumber.forEach((messages: MessageRaw[], phoneNumber: string) => {
|
||||||
|
phoneNumbersWithTimestamp.set(phoneNumber, {
|
||||||
|
first: messages[0]?.messageTimestamp as number,
|
||||||
|
last: messages[messages.length - 1]?.messageTimestamp as number,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// processing messages in batch
|
||||||
|
const batchSize = 4000;
|
||||||
|
let messagesChunk: MessageRaw[] = this.sliceIntoChunks(messagesOrdered, batchSize);
|
||||||
|
while (messagesChunk.length > 0) {
|
||||||
|
// Map structure: +552199999999 => MessageRaw[]
|
||||||
|
const messagesByPhoneNumber = this.createMessagesMapByPhoneNumber(messagesChunk);
|
||||||
|
|
||||||
|
if (messagesByPhoneNumber.size > 0) {
|
||||||
|
const fksByNumber = await this.selectOrCreateFksFromChatwoot(
|
||||||
|
provider,
|
||||||
|
inbox,
|
||||||
|
phoneNumbersWithTimestamp,
|
||||||
|
messagesByPhoneNumber,
|
||||||
|
);
|
||||||
|
|
||||||
|
// inserting messages in chatwoot db
|
||||||
|
let sqlInsertMsg = `INSERT INTO messages
|
||||||
|
(content, account_id, inbox_id, conversation_id, message_type, private, content_type,
|
||||||
|
sender_type, sender_id, created_at, updated_at) VALUES `;
|
||||||
|
const bindInsertMsg = [provider.account_id, inbox.id];
|
||||||
|
|
||||||
|
messagesByPhoneNumber.forEach((messages: MessageRaw[], phoneNumber: string) => {
|
||||||
|
const fksChatwoot = fksByNumber.get(phoneNumber);
|
||||||
|
|
||||||
|
messages.forEach((message) => {
|
||||||
|
if (!message.message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fksChatwoot?.conversation_id || !fksChatwoot?.contact_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentMessage = this.getContentMessage(chatwootService, message);
|
||||||
|
if (!contentMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bindInsertMsg.push(contentMessage);
|
||||||
|
const bindContent = `$${bindInsertMsg.length}`;
|
||||||
|
|
||||||
|
bindInsertMsg.push(fksChatwoot.conversation_id);
|
||||||
|
const bindConversationId = `$${bindInsertMsg.length}`;
|
||||||
|
|
||||||
|
bindInsertMsg.push(message.key.fromMe ? '1' : '0');
|
||||||
|
const bindMessageType = `$${bindInsertMsg.length}`;
|
||||||
|
|
||||||
|
bindInsertMsg.push(message.key.fromMe ? chatwootUser.user_type : 'Contact');
|
||||||
|
const bindSenderType = `$${bindInsertMsg.length}`;
|
||||||
|
|
||||||
|
bindInsertMsg.push(message.key.fromMe ? chatwootUser.user_id : fksChatwoot.contact_id);
|
||||||
|
const bindSenderId = `$${bindInsertMsg.length}`;
|
||||||
|
|
||||||
|
bindInsertMsg.push(message.messageTimestamp as number);
|
||||||
|
const bindmessageTimestamp = `$${bindInsertMsg.length}`;
|
||||||
|
|
||||||
|
sqlInsertMsg += `(${bindContent}, $1, $2, ${bindConversationId}, ${bindMessageType}, FALSE, 0,
|
||||||
|
${bindSenderType},${bindSenderId}, to_timestamp(${bindmessageTimestamp}), to_timestamp(${bindmessageTimestamp})),`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (bindInsertMsg.length > 2) {
|
||||||
|
if (sqlInsertMsg.slice(-1) === ',') {
|
||||||
|
sqlInsertMsg = sqlInsertMsg.slice(0, -1);
|
||||||
|
}
|
||||||
|
totalMessagesImported += (await pgClient.query(sqlInsertMsg, bindInsertMsg))?.rowCount ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messagesChunk = this.sliceIntoChunks(messagesOrdered, batchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deleteHistoryMessages(instance);
|
||||||
|
this.deleteRepositoryMessagesCache(instance);
|
||||||
|
|
||||||
|
this.importHistoryContacts(instance, provider);
|
||||||
|
|
||||||
|
return totalMessagesImported;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error on import history messages: ${error.toString()}`);
|
||||||
|
|
||||||
|
this.deleteHistoryMessages(instance);
|
||||||
|
this.deleteRepositoryMessagesCache(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async selectOrCreateFksFromChatwoot(
|
||||||
|
provider: ChatwootRaw,
|
||||||
|
inbox: inbox,
|
||||||
|
phoneNumbersWithTimestamp: Map<string, firstLastTimestamp>,
|
||||||
|
messagesByPhoneNumber: Map<string, MessageRaw[]>,
|
||||||
|
): Promise<Map<string, FksChatwoot>> {
|
||||||
|
const pgClient = postgresClient.getChatwootConnection();
|
||||||
|
|
||||||
|
const bindValues = [provider.account_id, inbox.id];
|
||||||
|
const phoneNumberBind = Array.from(messagesByPhoneNumber.keys())
|
||||||
|
.map((phoneNumber) => {
|
||||||
|
const phoneNumberTimestamp = phoneNumbersWithTimestamp.get(phoneNumber);
|
||||||
|
|
||||||
|
if (phoneNumberTimestamp) {
|
||||||
|
bindValues.push(phoneNumber);
|
||||||
|
let bindStr = `($${bindValues.length},`;
|
||||||
|
|
||||||
|
bindValues.push(phoneNumberTimestamp.first);
|
||||||
|
bindStr += `$${bindValues.length},`;
|
||||||
|
|
||||||
|
bindValues.push(phoneNumberTimestamp.last);
|
||||||
|
return `${bindStr}$${bindValues.length})`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
// select (or insert when necessary) data from tables contacts, contact_inboxes, conversations from chatwoot db
|
||||||
|
const sqlFromChatwoot = `WITH
|
||||||
|
phone_number AS (
|
||||||
|
SELECT phone_number, created_at::INTEGER, last_activity_at::INTEGER FROM (
|
||||||
|
VALUES
|
||||||
|
${phoneNumberBind}
|
||||||
|
) as t (phone_number, created_at, last_activity_at)
|
||||||
|
),
|
||||||
|
|
||||||
|
only_new_phone_number AS (
|
||||||
|
SELECT * FROM phone_number
|
||||||
|
WHERE phone_number NOT IN (
|
||||||
|
SELECT phone_number
|
||||||
|
FROM contacts
|
||||||
|
JOIN contact_inboxes ci ON ci.contact_id = contacts.id AND ci.inbox_id = $2
|
||||||
|
JOIN conversations con ON con.contact_inbox_id = ci.id
|
||||||
|
AND con.account_id = $1
|
||||||
|
AND con.inbox_id = $2
|
||||||
|
AND con.contact_id = contacts.id
|
||||||
|
WHERE contacts.account_id = $1
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
new_contact AS (
|
||||||
|
INSERT INTO contacts (name, phone_number, account_id, identifier, created_at, updated_at)
|
||||||
|
SELECT REPLACE(p.phone_number, '+', ''), p.phone_number, $1, CONCAT(REPLACE(p.phone_number, '+', ''),
|
||||||
|
'@s.whatsapp.net'), to_timestamp(p.created_at), to_timestamp(p.last_activity_at)
|
||||||
|
FROM only_new_phone_number AS p
|
||||||
|
ON CONFLICT(identifier, account_id) DO UPDATE SET updated_at = EXCLUDED.updated_at
|
||||||
|
RETURNING id, phone_number, created_at, updated_at
|
||||||
|
),
|
||||||
|
|
||||||
|
new_contact_inbox AS (
|
||||||
|
INSERT INTO contact_inboxes (contact_id, inbox_id, source_id, created_at, updated_at)
|
||||||
|
SELECT new_contact.id, $2, gen_random_uuid(), new_contact.created_at, new_contact.updated_at
|
||||||
|
FROM new_contact
|
||||||
|
RETURNING id, contact_id, created_at, updated_at
|
||||||
|
),
|
||||||
|
|
||||||
|
new_conversation AS (
|
||||||
|
INSERT INTO conversations (account_id, inbox_id, status, contact_id,
|
||||||
|
contact_inbox_id, uuid, last_activity_at, created_at, updated_at)
|
||||||
|
SELECT $1, $2, 0, new_contact_inbox.contact_id, new_contact_inbox.id, gen_random_uuid(),
|
||||||
|
new_contact_inbox.updated_at, new_contact_inbox.created_at, new_contact_inbox.updated_at
|
||||||
|
FROM new_contact_inbox
|
||||||
|
RETURNING id, contact_id
|
||||||
|
)
|
||||||
|
|
||||||
|
SELECT new_contact.phone_number, new_conversation.contact_id, new_conversation.id AS conversation_id
|
||||||
|
FROM new_conversation
|
||||||
|
JOIN new_contact ON new_conversation.contact_id = new_contact.id
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT p.phone_number, c.id contact_id, con.id conversation_id
|
||||||
|
FROM phone_number p
|
||||||
|
JOIN contacts c ON c.phone_number = p.phone_number
|
||||||
|
JOIN contact_inboxes ci ON ci.contact_id = c.id AND ci.inbox_id = $2
|
||||||
|
JOIN conversations con ON con.contact_inbox_id = ci.id AND con.account_id = $1
|
||||||
|
AND con.inbox_id = $2 AND con.contact_id = c.id`;
|
||||||
|
|
||||||
|
const fksFromChatwoot = await pgClient.query(sqlFromChatwoot, bindValues);
|
||||||
|
|
||||||
|
return new Map(fksFromChatwoot.rows.map((item: FksChatwoot) => [item.phone_number, item]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChatwootUser(provider: ChatwootRaw): Promise<ChatwootUser> {
|
||||||
|
try {
|
||||||
|
const pgClient = postgresClient.getChatwootConnection();
|
||||||
|
|
||||||
|
const sqlUser = `SELECT owner_type AS user_type, owner_id AS user_id
|
||||||
|
FROM access_tokens
|
||||||
|
WHERE token = $1`;
|
||||||
|
|
||||||
|
return (await pgClient.query(sqlUser, [provider.token]))?.rows[0] || false;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error on getChatwootUser: ${error.toString()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public createMessagesMapByPhoneNumber(messages: MessageRaw[]): Map<string, MessageRaw[]> {
|
||||||
|
return messages.reduce((acc: Map<string, MessageRaw[]>, message: MessageRaw) => {
|
||||||
|
if (!this.isIgnorePhoneNumber(message?.key?.remoteJid)) {
|
||||||
|
const phoneNumber = message?.key?.remoteJid?.split('@')[0];
|
||||||
|
if (phoneNumber) {
|
||||||
|
const phoneNumberPlus = `+${phoneNumber}`;
|
||||||
|
const messages = acc.has(phoneNumberPlus) ? acc.get(phoneNumberPlus) : [];
|
||||||
|
messages.push(message);
|
||||||
|
acc.set(phoneNumberPlus, messages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getContactsOrderByRecentConversations(
|
||||||
|
inbox: inbox,
|
||||||
|
provider: ChatwootRaw,
|
||||||
|
limit = 50,
|
||||||
|
): Promise<{ id: number; phone_number: string; identifier: string }[]> {
|
||||||
|
try {
|
||||||
|
const pgClient = postgresClient.getChatwootConnection();
|
||||||
|
|
||||||
|
const sql = `SELECT contacts.id, contacts.identifier, contacts.phone_number
|
||||||
|
FROM conversations
|
||||||
|
JOIN contacts ON contacts.id = conversations.contact_id
|
||||||
|
WHERE conversations.account_id = $1
|
||||||
|
AND inbox_id = $2
|
||||||
|
ORDER BY conversations.last_activity_at DESC
|
||||||
|
LIMIT $3`;
|
||||||
|
|
||||||
|
return (await pgClient.query(sql, [provider.account_id, inbox.id, limit]))?.rows;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error on get recent conversations: ${error.toString()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getContentMessage(chatwootService: ChatwootService, msg: IWebMessageInfo) {
|
||||||
|
const contentMessage = chatwootService.getConversationMessage(msg.message);
|
||||||
|
if (contentMessage) {
|
||||||
|
return contentMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configService.get<Chatwoot>('CHATWOOT').IMPORT.PLACEHOLDER_MEDIA_MESSAGE) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const types = {
|
||||||
|
documentMessage: msg.message.documentMessage,
|
||||||
|
documentWithCaptionMessage: msg.message.documentWithCaptionMessage?.message?.documentMessage,
|
||||||
|
imageMessage: msg.message.imageMessage,
|
||||||
|
videoMessage: msg.message.videoMessage,
|
||||||
|
audioMessage: msg.message.audioMessage,
|
||||||
|
stickerMessage: msg.message.stickerMessage,
|
||||||
|
templateMessage: msg.message.templateMessage?.hydratedTemplate?.hydratedContentText,
|
||||||
|
};
|
||||||
|
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
|
||||||
|
|
||||||
|
switch (typeKey) {
|
||||||
|
case 'documentMessage':
|
||||||
|
return `_<File: ${msg.message.documentMessage.fileName}${
|
||||||
|
msg.message.documentMessage.caption ? ` ${msg.message.documentMessage.caption}` : ''
|
||||||
|
}>_`;
|
||||||
|
|
||||||
|
case 'documentWithCaptionMessage':
|
||||||
|
return `_<File: ${msg.message.documentWithCaptionMessage.message.documentMessage.fileName}${
|
||||||
|
msg.message.documentWithCaptionMessage.message.documentMessage.caption
|
||||||
|
? ` ${msg.message.documentWithCaptionMessage.message.documentMessage.caption}`
|
||||||
|
: ''
|
||||||
|
}>_`;
|
||||||
|
|
||||||
|
case 'templateMessage':
|
||||||
|
return msg.message.templateMessage.hydratedTemplate.hydratedTitleText
|
||||||
|
? `*${msg.message.templateMessage.hydratedTemplate.hydratedTitleText}*\\n`
|
||||||
|
: '' + msg.message.templateMessage.hydratedTemplate.hydratedContentText;
|
||||||
|
|
||||||
|
case 'imageMessage':
|
||||||
|
return '_<Image Message>_';
|
||||||
|
|
||||||
|
case 'videoMessage':
|
||||||
|
return '_<Video Message>_';
|
||||||
|
|
||||||
|
case 'audioMessage':
|
||||||
|
return '_<Audio Message>_';
|
||||||
|
|
||||||
|
case 'stickerMessage':
|
||||||
|
return '_<Sticker Message>_';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sliceIntoChunks(arr: any[], chunkSize: number) {
|
||||||
|
return arr.splice(0, chunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isGroup(remoteJid: string) {
|
||||||
|
return remoteJid.includes('@g.us');
|
||||||
|
}
|
||||||
|
|
||||||
|
public isIgnorePhoneNumber(remoteJid: string) {
|
||||||
|
return this.isGroup(remoteJid) || remoteJid === 'status@broadcast' || remoteJid === '0@s.whatsapp.net';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const chatwootImport = new ChatwootImport();
|
||||||
44
src/api/integrations/chatwoot/validate/chatwoot.schema.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { JSONSchema7 } from 'json-schema';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||||
|
const properties = {};
|
||||||
|
propertyNames.forEach(
|
||||||
|
(property) =>
|
||||||
|
(properties[property] = {
|
||||||
|
minLength: 1,
|
||||||
|
description: `The "${property}" cannot be empty`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
if: {
|
||||||
|
propertyNames: {
|
||||||
|
enum: [...propertyNames],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
then: { properties },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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] },
|
||||||
|
sign_delimiter: { type: ['string', 'null'] },
|
||||||
|
name_inbox: { type: ['string', 'null'] },
|
||||||
|
reopen_conversation: { type: 'boolean', enum: [true, false] },
|
||||||
|
conversation_pending: { type: 'boolean', enum: [true, false] },
|
||||||
|
auto_create: { type: 'boolean', enum: [true, false] },
|
||||||
|
import_contacts: { type: 'boolean', enum: [true, false] },
|
||||||
|
merge_brazil_contacts: { type: 'boolean', enum: [true, false] },
|
||||||
|
import_messages: { type: 'boolean', enum: [true, false] },
|
||||||
|
days_limit_import_messages: { type: 'number' },
|
||||||
|
},
|
||||||
|
required: ['enabled', 'account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'],
|
||||||
|
...isNotEmpty('account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'),
|
||||||
|
};
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { RabbitmqDto } from '../dto/rabbitmq.dto';
|
||||||
|
import { RabbitmqService } from '../services/rabbitmq.service';
|
||||||
|
|
||||||
|
const logger = new Logger('RabbitmqController');
|
||||||
|
|
||||||
|
export class RabbitmqController {
|
||||||
|
constructor(private readonly rabbitmqService: RabbitmqService) {}
|
||||||
|
|
||||||
|
public async createRabbitmq(instance: InstanceDto, data: RabbitmqDto) {
|
||||||
|
logger.verbose('requested createRabbitmq from ' + instance.instanceName + ' instance');
|
||||||
|
|
||||||
|
if (!data.enabled) {
|
||||||
|
logger.verbose('rabbitmq disabled');
|
||||||
|
data.events = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.events.length === 0) {
|
||||||
|
logger.verbose('rabbitmq events empty');
|
||||||
|
data.events = [
|
||||||
|
'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',
|
||||||
|
'LABELS_EDIT',
|
||||||
|
'LABELS_ASSOCIATION',
|
||||||
|
'CALL',
|
||||||
|
'NEW_JWT_TOKEN',
|
||||||
|
'TYPEBOT_START',
|
||||||
|
'TYPEBOT_CHANGE_STATUS',
|
||||||
|
'CHAMA_AI_ACTION',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.rabbitmqService.create(instance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findRabbitmq(instance: InstanceDto) {
|
||||||
|
logger.verbose('requested findRabbitmq from ' + instance.instanceName + ' instance');
|
||||||
|
return this.rabbitmqService.find(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/api/integrations/rabbitmq/dto/rabbitmq.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export class RabbitmqDto {
|
||||||
|
enabled: boolean;
|
||||||
|
events?: string[];
|
||||||
|
}
|
||||||
135
src/api/integrations/rabbitmq/libs/amqp.server.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import * as amqp from 'amqplib/callback_api';
|
||||||
|
|
||||||
|
import { configService, Rabbitmq } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
|
||||||
|
const logger = new Logger('AMQP');
|
||||||
|
|
||||||
|
let amqpChannel: amqp.Channel | null = null;
|
||||||
|
|
||||||
|
export const initAMQP = () => {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const uri = configService.get<Rabbitmq>('RABBITMQ').URI;
|
||||||
|
amqp.connect(uri, (error, connection) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.createChannel((channelError, channel) => {
|
||||||
|
if (channelError) {
|
||||||
|
reject(channelError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exchangeName = 'evolution_exchange';
|
||||||
|
|
||||||
|
channel.assertExchange(exchangeName, 'topic', {
|
||||||
|
durable: true,
|
||||||
|
autoDelete: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
amqpChannel = channel;
|
||||||
|
|
||||||
|
logger.info('AMQP initialized');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAMQP = (): amqp.Channel | null => {
|
||||||
|
return amqpChannel;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initGlobalQueues = () => {
|
||||||
|
logger.info('Initializing global queues');
|
||||||
|
const events = configService.get<Rabbitmq>('RABBITMQ').EVENTS;
|
||||||
|
|
||||||
|
if (!events) {
|
||||||
|
logger.warn('No events to initialize on AMQP');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventKeys = Object.keys(events);
|
||||||
|
|
||||||
|
eventKeys.forEach((event) => {
|
||||||
|
if (events[event] === false) return;
|
||||||
|
|
||||||
|
const queueName = `${event.replace(/_/g, '.').toLowerCase()}`;
|
||||||
|
const amqp = getAMQP();
|
||||||
|
const exchangeName = 'evolution_exchange';
|
||||||
|
|
||||||
|
amqp.assertExchange(exchangeName, 'topic', {
|
||||||
|
durable: true,
|
||||||
|
autoDelete: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
amqp.assertQueue(queueName, {
|
||||||
|
durable: true,
|
||||||
|
autoDelete: false,
|
||||||
|
arguments: {
|
||||||
|
'x-queue-type': 'quorum',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
amqp.bindQueue(queueName, exchangeName, event);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initQueues = (instanceName: string, events: string[]) => {
|
||||||
|
if (!events || !events.length) return;
|
||||||
|
|
||||||
|
const queues = events.map((event) => {
|
||||||
|
return `${event.replace(/_/g, '.').toLowerCase()}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
queues.forEach((event) => {
|
||||||
|
const amqp = getAMQP();
|
||||||
|
const exchangeName = instanceName ?? 'evolution_exchange';
|
||||||
|
|
||||||
|
amqp.assertExchange(exchangeName, 'topic', {
|
||||||
|
durable: true,
|
||||||
|
autoDelete: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const queueName = `${instanceName}.${event}`;
|
||||||
|
|
||||||
|
amqp.assertQueue(queueName, {
|
||||||
|
durable: true,
|
||||||
|
autoDelete: false,
|
||||||
|
arguments: {
|
||||||
|
'x-queue-type': 'quorum',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
amqp.bindQueue(queueName, exchangeName, event);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeQueues = (instanceName: string, events: string[]) => {
|
||||||
|
if (!events || !events.length) return;
|
||||||
|
|
||||||
|
const channel = getAMQP();
|
||||||
|
|
||||||
|
const queues = events.map((event) => {
|
||||||
|
return `${event.replace(/_/g, '.').toLowerCase()}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const exchangeName = instanceName ?? 'evolution_exchange';
|
||||||
|
|
||||||
|
queues.forEach((event) => {
|
||||||
|
const amqp = getAMQP();
|
||||||
|
|
||||||
|
amqp.assertExchange(exchangeName, 'topic', {
|
||||||
|
durable: true,
|
||||||
|
autoDelete: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const queueName = `${instanceName}.${event}`;
|
||||||
|
|
||||||
|
amqp.deleteQueue(queueName);
|
||||||
|
});
|
||||||
|
|
||||||
|
channel.deleteExchange(exchangeName);
|
||||||
|
};
|
||||||
18
src/api/integrations/rabbitmq/models/rabbitmq.model.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Schema } from 'mongoose';
|
||||||
|
|
||||||
|
import { dbserver } from '../../../../libs/db.connect';
|
||||||
|
|
||||||
|
export class RabbitmqRaw {
|
||||||
|
_id?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
events?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const rabbitmqSchema = new Schema<RabbitmqRaw>({
|
||||||
|
_id: { type: String, _id: true },
|
||||||
|
enabled: { type: Boolean, required: true },
|
||||||
|
events: { type: [String], required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const RabbitmqModel = dbserver?.model(RabbitmqRaw.name, rabbitmqSchema, 'rabbitmq');
|
||||||
|
export type IRabbitmqModel = typeof RabbitmqModel;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { ConfigService } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { IInsert, Repository } from '../../../abstract/abstract.repository';
|
||||||
|
import { IRabbitmqModel, RabbitmqRaw } from '../../../models';
|
||||||
|
|
||||||
|
export class RabbitmqRepository extends Repository {
|
||||||
|
constructor(private readonly rabbitmqModel: IRabbitmqModel, private readonly configService: ConfigService) {
|
||||||
|
super(configService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly logger = new Logger('RabbitmqRepository');
|
||||||
|
|
||||||
|
public async create(data: RabbitmqRaw, instance: string): Promise<IInsert> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('creating rabbitmq');
|
||||||
|
if (this.dbSettings.ENABLED) {
|
||||||
|
this.logger.verbose('saving rabbitmq to db');
|
||||||
|
const insert = await this.rabbitmqModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||||
|
|
||||||
|
this.logger.verbose('rabbitmq saved to db: ' + insert.modifiedCount + ' rabbitmq');
|
||||||
|
return { insertCount: insert.modifiedCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('saving rabbitmq to store');
|
||||||
|
|
||||||
|
this.writeStore<RabbitmqRaw>({
|
||||||
|
path: join(this.storePath, 'rabbitmq'),
|
||||||
|
fileName: instance,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.verbose('rabbitmq saved to store in path: ' + join(this.storePath, 'rabbitmq') + '/' + instance);
|
||||||
|
|
||||||
|
this.logger.verbose('rabbitmq created');
|
||||||
|
return { insertCount: 1 };
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async find(instance: string): Promise<RabbitmqRaw> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('finding rabbitmq');
|
||||||
|
if (this.dbSettings.ENABLED) {
|
||||||
|
this.logger.verbose('finding rabbitmq in db');
|
||||||
|
return await this.rabbitmqModel.findOne({ _id: instance });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('finding rabbitmq in store');
|
||||||
|
return JSON.parse(
|
||||||
|
readFileSync(join(this.storePath, 'rabbitmq', instance + '.json'), {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
}),
|
||||||
|
) as RabbitmqRaw;
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/api/integrations/rabbitmq/routes/rabbitmq.router.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { RequestHandler, Router } from 'express';
|
||||||
|
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { instanceNameSchema, rabbitmqSchema } from '../../../../validate/validate.schema';
|
||||||
|
import { RouterBroker } from '../../../abstract/abstract.router';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { HttpStatus } from '../../../routes/index.router';
|
||||||
|
import { rabbitmqController } from '../../../server.module';
|
||||||
|
import { RabbitmqDto } from '../dto/rabbitmq.dto';
|
||||||
|
|
||||||
|
const logger = new Logger('RabbitmqRouter');
|
||||||
|
|
||||||
|
export class RabbitmqRouter extends RouterBroker {
|
||||||
|
constructor(...guards: RequestHandler[]) {
|
||||||
|
super();
|
||||||
|
this.router
|
||||||
|
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||||
|
logger.verbose('request received in setRabbitmq');
|
||||||
|
logger.verbose('request body: ');
|
||||||
|
logger.verbose(req.body);
|
||||||
|
|
||||||
|
logger.verbose('request query: ');
|
||||||
|
logger.verbose(req.query);
|
||||||
|
const response = await this.dataValidate<RabbitmqDto>({
|
||||||
|
request: req,
|
||||||
|
schema: rabbitmqSchema,
|
||||||
|
ClassRef: RabbitmqDto,
|
||||||
|
execute: (instance, data) => rabbitmqController.createRabbitmq(instance, data),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.CREATED).json(response);
|
||||||
|
})
|
||||||
|
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||||
|
logger.verbose('request received in findRabbitmq');
|
||||||
|
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) => rabbitmqController.findRabbitmq(instance),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly router = Router();
|
||||||
|
}
|
||||||
35
src/api/integrations/rabbitmq/services/rabbitmq.service.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { RabbitmqRaw } from '../../../models';
|
||||||
|
import { WAMonitoringService } from '../../../services/monitor.service';
|
||||||
|
import { RabbitmqDto } from '../dto/rabbitmq.dto';
|
||||||
|
import { initQueues } from '../libs/amqp.server';
|
||||||
|
|
||||||
|
export class RabbitmqService {
|
||||||
|
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||||
|
|
||||||
|
private readonly logger = new Logger(RabbitmqService.name);
|
||||||
|
|
||||||
|
public create(instance: InstanceDto, data: RabbitmqDto) {
|
||||||
|
this.logger.verbose('create rabbitmq: ' + instance.instanceName);
|
||||||
|
this.waMonitor.waInstances[instance.instanceName].setRabbitmq(data);
|
||||||
|
|
||||||
|
initQueues(instance.instanceName, data.events);
|
||||||
|
return { rabbitmq: { ...instance, rabbitmq: data } };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async find(instance: InstanceDto): Promise<RabbitmqRaw> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('find rabbitmq: ' + instance.instanceName);
|
||||||
|
const result = await this.waMonitor.waInstances[instance.instanceName].findRabbitmq();
|
||||||
|
|
||||||
|
if (Object.keys(result).length === 0) {
|
||||||
|
throw new Error('Rabbitmq not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
return { enabled: false, events: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/api/integrations/rabbitmq/validate/rabbitmq.schema.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { JSONSchema7 } from 'json-schema';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||||
|
const properties = {};
|
||||||
|
propertyNames.forEach(
|
||||||
|
(property) =>
|
||||||
|
(properties[property] = {
|
||||||
|
minLength: 1,
|
||||||
|
description: `The "${property}" cannot be empty`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
if: {
|
||||||
|
propertyNames: {
|
||||||
|
enum: [...propertyNames],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
then: { properties },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rabbitmqSchema: JSONSchema7 = {
|
||||||
|
$id: v4(),
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
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',
|
||||||
|
'LABELS_EDIT',
|
||||||
|
'LABELS_ASSOCIATION',
|
||||||
|
'CALL',
|
||||||
|
'NEW_JWT_TOKEN',
|
||||||
|
'TYPEBOT_START',
|
||||||
|
'TYPEBOT_CHANGE_STATUS',
|
||||||
|
'CHAMA_AI_ACTION',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['enabled'],
|
||||||
|
...isNotEmpty('enabled'),
|
||||||
|
};
|
||||||
58
src/api/integrations/sqs/controllers/sqs.controller.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { SqsDto } from '../dto/sqs.dto';
|
||||||
|
import { SqsService } from '../services/sqs.service';
|
||||||
|
|
||||||
|
const logger = new Logger('SqsController');
|
||||||
|
|
||||||
|
export class SqsController {
|
||||||
|
constructor(private readonly sqsService: SqsService) {}
|
||||||
|
|
||||||
|
public async createSqs(instance: InstanceDto, data: SqsDto) {
|
||||||
|
logger.verbose('requested createSqs from ' + instance.instanceName + ' instance');
|
||||||
|
|
||||||
|
if (!data.enabled) {
|
||||||
|
logger.verbose('sqs disabled');
|
||||||
|
data.events = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.events.length === 0) {
|
||||||
|
logger.verbose('sqs events empty');
|
||||||
|
data.events = [
|
||||||
|
'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',
|
||||||
|
'LABELS_EDIT',
|
||||||
|
'LABELS_ASSOCIATION',
|
||||||
|
'CALL',
|
||||||
|
'NEW_JWT_TOKEN',
|
||||||
|
'TYPEBOT_START',
|
||||||
|
'TYPEBOT_CHANGE_STATUS',
|
||||||
|
'CHAMA_AI_ACTION',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.sqsService.create(instance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findSqs(instance: InstanceDto) {
|
||||||
|
logger.verbose('requested findSqs from ' + instance.instanceName + ' instance');
|
||||||
|
return this.sqsService.find(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/api/integrations/sqs/dto/sqs.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export class SqsDto {
|
||||||
|
enabled: boolean;
|
||||||
|
events?: string[];
|
||||||
|
}
|
||||||
100
src/api/integrations/sqs/libs/sqs.server.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { SQS } from '@aws-sdk/client-sqs';
|
||||||
|
|
||||||
|
import { configService, Sqs } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
|
||||||
|
const logger = new Logger('SQS');
|
||||||
|
|
||||||
|
let sqs: SQS;
|
||||||
|
|
||||||
|
export const initSQS = () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const awsConfig = configService.get<Sqs>('SQS');
|
||||||
|
sqs = new SQS({
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: awsConfig.ACCESS_KEY_ID,
|
||||||
|
secretAccessKey: awsConfig.SECRET_ACCESS_KEY,
|
||||||
|
},
|
||||||
|
|
||||||
|
region: awsConfig.REGION,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('SQS initialized');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSQS = (): SQS => {
|
||||||
|
return sqs;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initQueues = (instanceName: string, events: string[]) => {
|
||||||
|
if (!events || !events.length) return;
|
||||||
|
|
||||||
|
const queues = events.map((event) => {
|
||||||
|
return `${event.replace(/_/g, '_').toLowerCase()}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const sqs = getSQS();
|
||||||
|
|
||||||
|
queues.forEach((event) => {
|
||||||
|
const queueName = `${instanceName}_${event}.fifo`;
|
||||||
|
|
||||||
|
sqs.createQueue(
|
||||||
|
{
|
||||||
|
QueueName: queueName,
|
||||||
|
Attributes: {
|
||||||
|
FifoQueue: 'true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(err, data) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(`Error creating queue ${queueName}: ${err.message}`);
|
||||||
|
} else {
|
||||||
|
logger.info(`Queue ${queueName} created: ${data.QueueUrl}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeQueues = (instanceName: string, events: string[]) => {
|
||||||
|
if (!events || !events.length) return;
|
||||||
|
|
||||||
|
const sqs = getSQS();
|
||||||
|
|
||||||
|
const queues = events.map((event) => {
|
||||||
|
return `${event.replace(/_/g, '_').toLowerCase()}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
queues.forEach((event) => {
|
||||||
|
const queueName = `${instanceName}_${event}.fifo`;
|
||||||
|
|
||||||
|
sqs.getQueueUrl(
|
||||||
|
{
|
||||||
|
QueueName: queueName,
|
||||||
|
},
|
||||||
|
(err, data) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(`Error getting queue URL for ${queueName}: ${err.message}`);
|
||||||
|
} else {
|
||||||
|
const queueUrl = data.QueueUrl;
|
||||||
|
|
||||||
|
sqs.deleteQueue(
|
||||||
|
{
|
||||||
|
QueueUrl: queueUrl,
|
||||||
|
},
|
||||||
|
(deleteErr) => {
|
||||||
|
if (deleteErr) {
|
||||||
|
logger.error(`Error deleting queue ${queueName}: ${deleteErr.message}`);
|
||||||
|
} else {
|
||||||
|
logger.info(`Queue ${queueName} deleted`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
18
src/api/integrations/sqs/models/sqs.model.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Schema } from 'mongoose';
|
||||||
|
|
||||||
|
import { dbserver } from '../../../../libs/db.connect';
|
||||||
|
|
||||||
|
export class SqsRaw {
|
||||||
|
_id?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
events?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqsSchema = new Schema<SqsRaw>({
|
||||||
|
_id: { type: String, _id: true },
|
||||||
|
enabled: { type: Boolean, required: true },
|
||||||
|
events: { type: [String], required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SqsModel = dbserver?.model(SqsRaw.name, sqsSchema, 'sqs');
|
||||||
|
export type ISqsModel = typeof SqsModel;
|
||||||
62
src/api/integrations/sqs/repository/sqs.repository.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { ConfigService } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { IInsert, Repository } from '../../../abstract/abstract.repository';
|
||||||
|
import { ISqsModel, SqsRaw } from '../../../models';
|
||||||
|
|
||||||
|
export class SqsRepository extends Repository {
|
||||||
|
constructor(private readonly sqsModel: ISqsModel, private readonly configService: ConfigService) {
|
||||||
|
super(configService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly logger = new Logger('SqsRepository');
|
||||||
|
|
||||||
|
public async create(data: SqsRaw, instance: string): Promise<IInsert> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('creating sqs');
|
||||||
|
if (this.dbSettings.ENABLED) {
|
||||||
|
this.logger.verbose('saving sqs to db');
|
||||||
|
const insert = await this.sqsModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||||
|
|
||||||
|
this.logger.verbose('sqs saved to db: ' + insert.modifiedCount + ' sqs');
|
||||||
|
return { insertCount: insert.modifiedCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('saving sqs to store');
|
||||||
|
|
||||||
|
this.writeStore<SqsRaw>({
|
||||||
|
path: join(this.storePath, 'sqs'),
|
||||||
|
fileName: instance,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.verbose('sqs saved to store in path: ' + join(this.storePath, 'sqs') + '/' + instance);
|
||||||
|
|
||||||
|
this.logger.verbose('sqs created');
|
||||||
|
return { insertCount: 1 };
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async find(instance: string): Promise<SqsRaw> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('finding sqs');
|
||||||
|
if (this.dbSettings.ENABLED) {
|
||||||
|
this.logger.verbose('finding sqs in db');
|
||||||
|
return await this.sqsModel.findOne({ _id: instance });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('finding sqs in store');
|
||||||
|
return JSON.parse(
|
||||||
|
readFileSync(join(this.storePath, 'sqs', instance + '.json'), {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
}),
|
||||||
|
) as SqsRaw;
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/api/integrations/sqs/routes/sqs.router.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { RequestHandler, Router } from 'express';
|
||||||
|
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { instanceNameSchema, sqsSchema } from '../../../../validate/validate.schema';
|
||||||
|
import { RouterBroker } from '../../../abstract/abstract.router';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { HttpStatus } from '../../../routes/index.router';
|
||||||
|
import { sqsController } from '../../../server.module';
|
||||||
|
import { SqsDto } from '../dto/sqs.dto';
|
||||||
|
|
||||||
|
const logger = new Logger('SqsRouter');
|
||||||
|
|
||||||
|
export class SqsRouter extends RouterBroker {
|
||||||
|
constructor(...guards: RequestHandler[]) {
|
||||||
|
super();
|
||||||
|
this.router
|
||||||
|
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||||
|
logger.verbose('request received in setSqs');
|
||||||
|
logger.verbose('request body: ');
|
||||||
|
logger.verbose(req.body);
|
||||||
|
|
||||||
|
logger.verbose('request query: ');
|
||||||
|
logger.verbose(req.query);
|
||||||
|
const response = await this.dataValidate<SqsDto>({
|
||||||
|
request: req,
|
||||||
|
schema: sqsSchema,
|
||||||
|
ClassRef: SqsDto,
|
||||||
|
execute: (instance, data) => sqsController.createSqs(instance, data),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.CREATED).json(response);
|
||||||
|
})
|
||||||
|
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||||
|
logger.verbose('request received in findSqs');
|
||||||
|
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) => sqsController.findSqs(instance),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly router = Router();
|
||||||
|
}
|
||||||
35
src/api/integrations/sqs/services/sqs.service.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { SqsRaw } from '../../../models';
|
||||||
|
import { WAMonitoringService } from '../../../services/monitor.service';
|
||||||
|
import { SqsDto } from '../dto/sqs.dto';
|
||||||
|
import { initQueues } from '../libs/sqs.server';
|
||||||
|
|
||||||
|
export class SqsService {
|
||||||
|
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||||
|
|
||||||
|
private readonly logger = new Logger(SqsService.name);
|
||||||
|
|
||||||
|
public create(instance: InstanceDto, data: SqsDto) {
|
||||||
|
this.logger.verbose('create sqs: ' + instance.instanceName);
|
||||||
|
this.waMonitor.waInstances[instance.instanceName].setSqs(data);
|
||||||
|
|
||||||
|
initQueues(instance.instanceName, data.events);
|
||||||
|
return { sqs: { ...instance, sqs: data } };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async find(instance: InstanceDto): Promise<SqsRaw> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('find sqs: ' + instance.instanceName);
|
||||||
|
const result = await this.waMonitor.waInstances[instance.instanceName].findSqs();
|
||||||
|
|
||||||
|
if (Object.keys(result).length === 0) {
|
||||||
|
throw new Error('Sqs not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
return { enabled: false, events: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/api/integrations/sqs/validate/sqs.schema.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { JSONSchema7 } from 'json-schema';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||||
|
const properties = {};
|
||||||
|
propertyNames.forEach(
|
||||||
|
(property) =>
|
||||||
|
(properties[property] = {
|
||||||
|
minLength: 1,
|
||||||
|
description: `The "${property}" cannot be empty`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
if: {
|
||||||
|
propertyNames: {
|
||||||
|
enum: [...propertyNames],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
then: { properties },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sqsSchema: JSONSchema7 = {
|
||||||
|
$id: v4(),
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
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',
|
||||||
|
'LABELS_EDIT',
|
||||||
|
'LABELS_ASSOCIATION',
|
||||||
|
'CALL',
|
||||||
|
'NEW_JWT_TOKEN',
|
||||||
|
'TYPEBOT_START',
|
||||||
|
'TYPEBOT_CHANGE_STATUS',
|
||||||
|
'CHAMA_AI_ACTION',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['enabled'],
|
||||||
|
...isNotEmpty('enabled'),
|
||||||
|
};
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { InstanceDto } from '../../../dto/instance.dto';
|
||||||
|
import { TypebotDto } from '../dto/typebot.dto';
|
||||||
|
import { TypebotService } from '../services/typebot.service';
|
||||||
|
|
||||||
|
const logger = new Logger('TypebotController');
|
||||||
|
|
||||||
|
export class TypebotController {
|
||||||
|
constructor(private readonly typebotService: TypebotService) {}
|
||||||
|
|
||||||
|
public async createTypebot(instance: InstanceDto, data: TypebotDto) {
|
||||||
|
logger.verbose('requested createTypebot from ' + instance.instanceName + ' instance');
|
||||||
|
|
||||||
|
if (!data.enabled) {
|
||||||
|
logger.verbose('typebot disabled');
|
||||||
|
data.url = '';
|
||||||
|
data.typebot = '';
|
||||||
|
data.expire = 0;
|
||||||
|
data.sessions = [];
|
||||||
|
} else {
|
||||||
|
const saveData = await this.typebotService.find(instance);
|
||||||
|
|
||||||
|
if (saveData.enabled) {
|
||||||
|
logger.verbose('typebot enabled');
|
||||||
|
data.sessions = saveData.sessions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.typebotService.create(instance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findTypebot(instance: InstanceDto) {
|
||||||
|
logger.verbose('requested findTypebot from ' + instance.instanceName + ' instance');
|
||||||
|
return this.typebotService.find(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async changeStatus(instance: InstanceDto, data: any) {
|
||||||
|
logger.verbose('requested changeStatus from ' + instance.instanceName + ' instance');
|
||||||
|
return this.typebotService.changeStatus(instance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async startTypebot(instance: InstanceDto, data: any) {
|
||||||
|
logger.verbose('requested startTypebot from ' + instance.instanceName + ' instance');
|
||||||
|
return this.typebotService.startTypebot(instance, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/api/integrations/typebot/dto/typebot.dto.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export class Session {
|
||||||
|
remoteJid?: string;
|
||||||
|
sessionId?: string;
|
||||||
|
status?: string;
|
||||||
|
createdAt?: number;
|
||||||
|
updateAt?: number;
|
||||||
|
prefilledVariables?: PrefilledVariables;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrefilledVariables {
|
||||||
|
remoteJid?: string;
|
||||||
|
pushName?: string;
|
||||||
|
messageType?: string;
|
||||||
|
additionalData?: { [key: string]: any };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypebotDto {
|
||||||
|
enabled?: boolean;
|
||||||
|
url: string;
|
||||||
|
typebot?: string;
|
||||||
|
expire?: number;
|
||||||
|
keyword_finish?: string;
|
||||||
|
delay_message?: number;
|
||||||
|
unknown_message?: string;
|
||||||
|
listening_from_me?: boolean;
|
||||||
|
sessions?: Session[];
|
||||||
|
}
|
||||||
58
src/api/integrations/typebot/models/typebot.model.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { Schema } from 'mongoose';
|
||||||
|
|
||||||
|
import { dbserver } from '../../../../libs/db.connect';
|
||||||
|
|
||||||
|
class Session {
|
||||||
|
remoteJid?: string;
|
||||||
|
sessionId?: string;
|
||||||
|
status?: string;
|
||||||
|
createdAt?: number;
|
||||||
|
updateAt?: number;
|
||||||
|
prefilledVariables?: {
|
||||||
|
remoteJid?: string;
|
||||||
|
pushName?: string;
|
||||||
|
additionalData?: { [key: string]: any };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypebotRaw {
|
||||||
|
_id?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
url: string;
|
||||||
|
typebot?: string;
|
||||||
|
expire?: number;
|
||||||
|
keyword_finish?: string;
|
||||||
|
delay_message?: number;
|
||||||
|
unknown_message?: string;
|
||||||
|
listening_from_me?: boolean;
|
||||||
|
sessions?: Session[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const typebotSchema = new Schema<TypebotRaw>({
|
||||||
|
_id: { type: String, _id: true },
|
||||||
|
enabled: { type: Boolean, required: true },
|
||||||
|
url: { type: String, required: true },
|
||||||
|
typebot: { type: String, required: true },
|
||||||
|
expire: { type: Number, required: true },
|
||||||
|
keyword_finish: { type: String, required: true },
|
||||||
|
delay_message: { type: Number, required: true },
|
||||||
|
unknown_message: { type: String, required: true },
|
||||||
|
listening_from_me: { type: Boolean, required: true },
|
||||||
|
sessions: [
|
||||||
|
{
|
||||||
|
remoteJid: { type: String, required: true },
|
||||||
|
sessionId: { type: String, required: true },
|
||||||
|
status: { type: String, required: true },
|
||||||
|
createdAt: { type: Number, required: true },
|
||||||
|
updateAt: { type: Number, required: true },
|
||||||
|
prefilledVariables: {
|
||||||
|
remoteJid: { type: String, required: false },
|
||||||
|
pushName: { type: String, required: false },
|
||||||
|
additionalData: { type: Schema.Types.Mixed, required: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const TypebotModel = dbserver?.model(TypebotRaw.name, typebotSchema, 'typebot');
|
||||||
|
export type ITypebotModel = typeof TypebotModel;
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { ConfigService } from '../../../../config/env.config';
|
||||||
|
import { Logger } from '../../../../config/logger.config';
|
||||||
|
import { IInsert, Repository } from '../../../abstract/abstract.repository';
|
||||||
|
import { ITypebotModel, TypebotRaw } from '../../../models';
|
||||||
|
|
||||||
|
export class TypebotRepository extends Repository {
|
||||||
|
constructor(private readonly typebotModel: ITypebotModel, private readonly configService: ConfigService) {
|
||||||
|
super(configService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly logger = new Logger('TypebotRepository');
|
||||||
|
|
||||||
|
public async create(data: TypebotRaw, instance: string): Promise<IInsert> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('creating typebot');
|
||||||
|
if (this.dbSettings.ENABLED) {
|
||||||
|
this.logger.verbose('saving typebot to db');
|
||||||
|
const insert = await this.typebotModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||||
|
|
||||||
|
this.logger.verbose('typebot saved to db: ' + insert.modifiedCount + ' typebot');
|
||||||
|
return { insertCount: insert.modifiedCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('saving typebot to store');
|
||||||
|
|
||||||
|
this.writeStore<TypebotRaw>({
|
||||||
|
path: join(this.storePath, 'typebot'),
|
||||||
|
fileName: instance,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.verbose('typebot saved to store in path: ' + join(this.storePath, 'typebot') + '/' + instance);
|
||||||
|
|
||||||
|
this.logger.verbose('typebot created');
|
||||||
|
return { insertCount: 1 };
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async find(instance: string): Promise<TypebotRaw> {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('finding typebot');
|
||||||
|
if (this.dbSettings.ENABLED) {
|
||||||
|
this.logger.verbose('finding typebot in db');
|
||||||
|
return await this.typebotModel.findOne({ _id: instance });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('finding typebot in store');
|
||||||
|
return JSON.parse(
|
||||||
|
readFileSync(join(this.storePath, 'typebot', instance + '.json'), {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
}),
|
||||||
|
) as TypebotRaw;
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
enabled: false,
|
||||||
|
url: '',
|
||||||
|
typebot: '',
|
||||||
|
expire: 0,
|
||||||
|
sessions: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||