Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27a86f0dc1 | ||
|
|
767ea23173 | ||
|
|
85707508ed | ||
|
|
e8bc59ff9e | ||
|
|
7f9e622a64 | ||
|
|
b22e37596b | ||
|
|
642e90b1c8 | ||
|
|
8402ab896a | ||
|
|
d6dc7edf66 | ||
|
|
8f6dbf70f7 | ||
|
|
bd14d9a71e | ||
|
|
61a0a38dd2 | ||
|
|
102f438a99 | ||
|
|
586a2d1c79 | ||
|
|
5b04c39107 | ||
|
|
d96e91ea70 | ||
|
|
68ac209aae | ||
|
|
90b961f900 | ||
|
|
9945fe0fd8 | ||
|
|
1346ce0472 | ||
|
|
e914aaa961 | ||
|
|
70e17250e8 | ||
|
|
8c134904ba | ||
|
|
bfa4b14c13 | ||
|
|
36c7820d78 | ||
|
|
92e6530d3e | ||
|
|
9e47af301e | ||
|
|
b59eb57dce | ||
|
|
158a557867 | ||
|
|
5a95e9b1ee | ||
|
|
98d3ca31d7 | ||
|
|
52bd5e7ae6 | ||
|
|
b883f0c9ef | ||
|
|
e61b808c3c | ||
|
|
7bc049de68 | ||
|
|
de3f859d98 | ||
|
|
ea06277649 | ||
|
|
9a6bd67e43 | ||
|
|
01baee5cf2 | ||
|
|
bc855bc089 | ||
|
|
4bf1210651 | ||
|
|
a4f4398765 | ||
|
|
0f343841a6 | ||
|
|
41d26799b2 | ||
|
|
a807d7647b | ||
|
|
7bcd2a6206 | ||
|
|
a3c2c1bb22 | ||
|
|
355e47ec11 | ||
|
|
a01bee8330 | ||
|
|
c4c020aaa6 | ||
|
|
35e92bebbd | ||
|
|
2bd3e9f556 | ||
|
|
60705d1558 | ||
|
|
2cbd08c470 | ||
|
|
51a501611d | ||
|
|
46bcfb5695 | ||
|
|
e44bd55df2 | ||
|
|
efaa090053 | ||
|
|
8be13fbc5c | ||
|
|
e0a1be1417 | ||
|
|
7f841f6862 | ||
|
|
d1804996d7 | ||
|
|
34084a7ee8 | ||
|
|
fedde8bb87 | ||
|
|
7439e80eec | ||
|
|
e388f361af | ||
|
|
2a9ed6bac7 | ||
|
|
5936a86159 | ||
|
|
3b22911fb1 | ||
|
|
0dd3762685 | ||
|
|
fd9106e97e | ||
|
|
5a3d709e4f | ||
|
|
c0c357dc39 | ||
|
|
e76bec3d4f | ||
|
|
384b3974f7 | ||
|
|
3c37044cd8 | ||
|
|
541112a9eb | ||
|
|
59d7613485 | ||
|
|
d1a579372d | ||
|
|
16cd247db8 | ||
|
|
872d6d9880 | ||
|
|
79fe322d69 | ||
|
|
fe6f1cb547 | ||
|
|
8d7fcd0b84 | ||
|
|
46b0c3cee2 | ||
|
|
1ad9c3017c | ||
|
|
5d320fa22f | ||
|
|
5e1d9b6463 | ||
|
|
175dfde0b8 | ||
|
|
87717ae214 | ||
|
|
111c8f3160 | ||
|
|
6c756352cf | ||
|
|
97a5d11c1f | ||
|
|
44415d824e | ||
|
|
286e8f3afe | ||
|
|
4c2e3db2d5 | ||
|
|
6faf699fcc | ||
|
|
80b765e56a | ||
|
|
3f9ebe5c8c | ||
|
|
481371fd03 | ||
|
|
36c2165fc3 | ||
|
|
36402626e0 | ||
|
|
40c39e61e1 | ||
|
|
4e1b13e776 | ||
|
|
7f355ff43a | ||
|
|
22df449013 | ||
|
|
68303f5124 | ||
|
|
33babfef6b | ||
|
|
910a9185c7 | ||
|
|
f5bd74dfa1 | ||
|
|
b982640c85 | ||
|
|
245bcc46ed | ||
|
|
08a346bfa1 | ||
|
|
0cb4dd6d63 | ||
|
|
1a7a78cc51 | ||
|
|
9b69875b78 | ||
|
|
999ae4782b | ||
|
|
3c61ca8c94 | ||
|
|
0790e0a0ef | ||
|
|
0a2d77b699 | ||
|
|
051ada3f21 | ||
|
|
faf8d053dd | ||
|
|
5ece56f9b0 | ||
|
|
c4d9474f34 | ||
|
|
3b8481ccac | ||
|
|
a31d0215f5 | ||
|
|
f6cce68553 | ||
|
|
636b100b9f | ||
|
|
7a3574eaa0 | ||
|
|
9cd6176e60 | ||
|
|
f2641029cb | ||
|
|
6350e191f8 | ||
|
|
c570ba3488 | ||
|
|
b64abd24c8 | ||
|
|
13c9ab3962 | ||
|
|
b9f1c6ee79 | ||
|
|
938170a957 | ||
|
|
ca0a28c0cf | ||
|
|
c3967173a1 | ||
|
|
9ebbd9a1fd | ||
|
|
16e343ca19 | ||
|
|
1de9132f16 | ||
|
|
dad54a8e47 | ||
|
|
7d73b96b78 | ||
|
|
1260fe34aa | ||
|
|
3b4678e059 | ||
|
|
ff8294b5db | ||
|
|
7e94e19f5d | ||
|
|
ae18e2575a | ||
|
|
a5c493a29c | ||
|
|
8ee89fe51c | ||
|
|
b4909847bf | ||
|
|
568be88127 | ||
|
|
67144c1d08 | ||
|
|
285c78d41a | ||
|
|
7d15735444 | ||
|
|
4191749564 | ||
|
|
9e68dbbf09 | ||
|
|
f02ad5b051 | ||
|
|
08e20a25ed | ||
|
|
ee0468038d | ||
|
|
04afc297d9 | ||
|
|
ed06ca76e0 | ||
|
|
a68510a814 | ||
|
|
4040efab01 | ||
|
|
f08cc1061e | ||
|
|
3b5b11891e | ||
|
|
72427c9309 | ||
|
|
befe2068fd | ||
|
|
c7a3ef32fa | ||
|
|
51d394f4ef | ||
|
|
8db341dd70 | ||
|
|
5cd7fec3ab | ||
|
|
f672ecbb45 | ||
|
|
9a12b2c9b6 | ||
|
|
c31407fd87 | ||
|
|
646c81f136 | ||
|
|
aca6232d2a | ||
|
|
c61da10523 | ||
|
|
1f53d65ac2 | ||
|
|
844ab1287e | ||
|
|
d6d318b23b | ||
|
|
7504ffd934 | ||
|
|
e8078908c9 | ||
|
|
431848280f | ||
|
|
2e0c575512 | ||
|
|
ebf0d99417 | ||
|
|
423d893fee | ||
|
|
56fe0d3b04 | ||
|
|
7172d9d203 | ||
|
|
bd770a6902 | ||
|
|
628508faed | ||
|
|
826634f061 | ||
|
|
c7ed959a71 | ||
|
|
aea6befc1e | ||
|
|
7bb3484788 | ||
|
|
4eafc67a7e | ||
|
|
b5197b6f20 | ||
|
|
d306304195 | ||
|
|
35f47dae4f | ||
|
|
f1be4810d5 | ||
|
|
d4f9406869 | ||
|
|
a41b81d724 | ||
|
|
da56b86102 | ||
|
|
f12d2c9be0 | ||
|
|
322b9b4c25 | ||
|
|
fb7fe410f9 | ||
|
|
ee392a079c | ||
|
|
0502579d50 | ||
|
|
f10f3175d9 | ||
|
|
f155af99f1 | ||
|
|
0fbf3575a6 | ||
|
|
f6b9b5f54e | ||
|
|
2dadd9d8c9 | ||
|
|
5786b3cdfe | ||
|
|
769fc30c0c | ||
|
|
fb2dad9c6f | ||
|
|
c43d308a2d | ||
|
|
742a12ce6a | ||
|
|
d953f27865 | ||
|
|
4719cded95 | ||
|
|
9d489bdb69 | ||
|
|
537c307550 | ||
|
|
d5575e0323 | ||
|
|
592962bbc8 | ||
|
|
7a371c86dc | ||
|
|
0e295bebde | ||
|
|
8d98759be6 | ||
|
|
924317b01b | ||
|
|
a08a601dc3 | ||
|
|
e652c15d6c | ||
|
|
4534eb60de | ||
|
|
54c6c0c24c | ||
|
|
393694ae96 | ||
|
|
818bb730e4 | ||
|
|
61bd35a192 | ||
|
|
81f9df000a | ||
|
|
0dcb31c108 | ||
|
|
cd4140fda9 | ||
|
|
117cc25cdb | ||
|
|
832ad26151 | ||
|
|
5a344cd8ad | ||
|
|
0b0a67cdad | ||
|
|
a326a240f0 | ||
|
|
dfdb033769 | ||
|
|
a31af45d97 | ||
|
|
cae955c483 | ||
|
|
0bfff5259e | ||
|
|
0fa83e603b | ||
|
|
906b03804f | ||
|
|
1ed7a61bea | ||
|
|
ad6ae7ab05 | ||
|
|
5e2de1d47d | ||
|
|
591ca51104 | ||
|
|
b210841158 | ||
|
|
a223a3985b | ||
|
|
8baa6a73cd | ||
|
|
508efac2ea | ||
|
|
b6eed9e228 | ||
|
|
b62cad07f0 | ||
|
|
6d552d27ac | ||
|
|
39d0f2a060 | ||
|
|
0b03abee91 | ||
|
|
b4fb69d0ee | ||
|
|
5b9ed029b3 | ||
|
|
c29397371b | ||
|
|
fe47e27788 | ||
|
|
a14c702c79 | ||
|
|
fe4246036d | ||
|
|
074063b814 | ||
|
|
0271a8b721 | ||
|
|
08617383be | ||
|
|
3840459b0a | ||
|
|
aee7ac28bc | ||
|
|
9e6cd39b2d | ||
|
|
8898429046 | ||
|
|
a74def6dbd | ||
|
|
9f783cd35e | ||
|
|
9d2f061d81 | ||
|
|
a6045b2d54 | ||
|
|
6142b2b564 | ||
|
|
ed53ccb919 | ||
|
|
1ab45c6cfd | ||
|
|
c2fdb090e1 | ||
|
|
a10b47eb7e | ||
|
|
7b234e2e37 | ||
|
|
90d45a4698 | ||
|
|
5e40b70e19 | ||
|
|
a27d0c3efb | ||
|
|
46ed506b35 | ||
|
|
0ad813a1fe | ||
|
|
e57e42f47a | ||
|
|
cc58157d37 | ||
|
|
9b5bb0a339 | ||
|
|
84ebbea287 | ||
|
|
1c2021f54b | ||
|
|
98a7f00d7d | ||
|
|
60d97d0e67 | ||
|
|
03f4a07cbe | ||
|
|
3fbe526793 | ||
|
|
a892f03382 | ||
|
|
8db69f6e69 | ||
|
|
f9037ff9fc | ||
|
|
366ddffa2b | ||
|
|
b8468588de | ||
|
|
62dd6f7f4a | ||
|
|
858202f1bb | ||
|
|
db7900dc26 | ||
|
|
aa87e437dc | ||
|
|
741e5e86f8 | ||
|
|
390a171891 | ||
|
|
1fc5e2b47c | ||
|
|
8926dc7cc6 | ||
|
|
5c3d3c0d70 | ||
|
|
41b29cbf60 | ||
|
|
44463c199a | ||
|
|
9071adcdcc | ||
|
|
21cd92ca13 | ||
|
|
d8a3e8dc43 | ||
|
|
3f5707952c | ||
|
|
38a9590be0 | ||
|
|
a7ce117b87 | ||
|
|
c3b24a243c | ||
|
|
cb2c11df43 | ||
|
|
98f88dcf68 | ||
|
|
9fb12e0e23 | ||
|
|
0ca106f16b | ||
|
|
33210c6374 | ||
|
|
045ab67f5d | ||
|
|
19259c93ea | ||
|
|
9b6784a018 | ||
|
|
2fe3706e9c | ||
|
|
a4e310a7f8 | ||
|
|
802da9f3b2 | ||
|
|
0692979b19 | ||
|
|
fb8b146bc4 | ||
|
|
fdbaa43ec9 | ||
|
|
4a807530c1 | ||
|
|
84784faa1e | ||
|
|
bd40978dee | ||
|
|
d4d94ffe0e | ||
|
|
d984382a33 | ||
|
|
02d05812d2 | ||
|
|
1dbf568bac | ||
|
|
6bc3aed348 | ||
|
|
c1e8e0f09c | ||
|
|
8f8bcfc474 | ||
|
|
b386df982d | ||
|
|
fa846ddf9b | ||
|
|
16685ed8ea | ||
|
|
4addccbd55 | ||
|
|
e9a20c4326 | ||
|
|
f073aeef13 | ||
|
|
eb08739eea | ||
|
|
aff3b01e39 | ||
|
|
2d64b26e86 | ||
|
|
7b7bd02503 | ||
|
|
7904516c97 | ||
|
|
6e0bafdd89 | ||
|
|
2b1e605e5f | ||
|
|
f454310a09 | ||
|
|
b6579b5b96 | ||
|
|
4ef06d7b92 | ||
|
|
e0c594a71b | ||
|
|
a48d386d8b | ||
|
|
d592858a90 | ||
|
|
a3cb3e983a | ||
|
|
4eb9029b2c | ||
|
|
b3835b0aa0 | ||
|
|
b2db58a922 | ||
|
|
acb6b8e0d3 | ||
|
|
21c6e1e860 | ||
|
|
571b65c423 | ||
|
|
1631a62abe | ||
|
|
e88a7ca8ce | ||
|
|
d54f64a9cc | ||
|
|
03dbd5db82 | ||
|
|
2446c292bf | ||
|
|
5ab99dfd69 | ||
|
|
605f46fda8 | ||
|
|
e53f1eafe8 | ||
|
|
1457185e84 | ||
|
|
ad43bec18d | ||
|
|
c0a4b223ea | ||
|
|
65917e1957 | ||
|
|
a19cede60d | ||
|
|
a3ead95ea4 | ||
|
|
c46bc1b923 | ||
|
|
b327212cbe | ||
|
|
4f348c7e48 | ||
|
|
f2d0001b11 | ||
|
|
66a224a0e3 | ||
|
|
1fa1206568 | ||
|
|
87eea28ac2 | ||
|
|
29ca0217b3 | ||
|
|
306f75ff3b | ||
|
|
6ecb203570 | ||
|
|
2e7a3a6b61 | ||
|
|
86bc4a92ba | ||
|
|
f652277e18 | ||
|
|
14a3535439 | ||
|
|
edce7424de | ||
|
|
0fa121bbe2 | ||
|
|
9c046a92be | ||
|
|
e4506e14da | ||
|
|
515a00346c | ||
|
|
399e27e24e | ||
|
|
9413376138 | ||
|
|
3caec14783 | ||
|
|
6e427a0a34 | ||
|
|
40c5e95d93 | ||
|
|
ab70e27266 | ||
|
|
301b6f57d3 | ||
|
|
4c9f5ed907 | ||
|
|
04d65150ed | ||
|
|
d29165c9a5 | ||
|
|
30db175d52 | ||
|
|
5c666c0dd4 | ||
|
|
3ccc0469c0 | ||
|
|
5ab3244d6a | ||
|
|
05bc8abbee | ||
|
|
8e9056a0cd | ||
|
|
ea75b357a7 | ||
|
|
2b607e9b44 | ||
|
|
57d3528a96 | ||
|
|
660a04aa52 | ||
|
|
d0a408001d | ||
|
|
48391533a6 | ||
|
|
f2113adbbc | ||
|
|
abfcbc7397 | ||
|
|
96f2982325 | ||
|
|
3a521bc06c | ||
|
|
6fbaec0083 | ||
|
|
cae94c7b5c | ||
|
|
bce012f692 | ||
|
|
b23b639a8b | ||
|
|
5a628907ed | ||
|
|
6da479c0d7 | ||
|
|
cbb321df6d | ||
|
|
0a7beb6909 | ||
|
|
2105094ac3 | ||
|
|
d8fee686f8 | ||
|
|
75fc1fd7e0 | ||
|
|
e718652491 | ||
|
|
bcb49f2be7 | ||
|
|
dab63e4060 | ||
|
|
3fdacbb6ac | ||
|
|
f02b62e620 | ||
|
|
51acce14a0 | ||
|
|
b63f6ac39e | ||
|
|
9e107a8a76 | ||
|
|
8c083242d3 | ||
|
|
2a068d561f | ||
|
|
ef96fbf8c5 | ||
|
|
2c381a0b9c | ||
|
|
fef2bcb6e2 | ||
|
|
2f6ed2fb58 | ||
|
|
4eb23d2c09 | ||
|
|
ec3de8dbcf | ||
|
|
f6f09747dc | ||
|
|
27a534a605 | ||
|
|
3cbd940843 | ||
|
|
e728eec01a | ||
|
|
d06d2b78a8 | ||
|
|
5878a9196e | ||
|
|
85dd061f96 | ||
|
|
8381c3e946 | ||
|
|
6ff8a9705d | ||
|
|
fb5819b966 | ||
|
|
5cc1af3f82 | ||
|
|
c279de694c | ||
|
|
f7a1787f1f | ||
|
|
5442fd6e93 | ||
|
|
b8c8ca7653 | ||
|
|
5ca7dbc84b | ||
|
|
d836c9706f | ||
|
|
318be43a4f | ||
|
|
95bebce731 | ||
|
|
27b8c9d058 | ||
|
|
10d4d24526 | ||
|
|
b19917bd3d | ||
|
|
4b6774b1db | ||
|
|
360e0a7951 | ||
|
|
171f516eee | ||
|
|
01943f6970 | ||
|
|
953d48ee1f | ||
|
|
2b6028bcb1 | ||
|
|
79b7b7d2a2 | ||
|
|
93a6a7b87f | ||
|
|
3b499d6af5 | ||
|
|
6481919597 | ||
|
|
228f6ec8c5 | ||
|
|
6723f201b7 | ||
|
|
c250bb49f0 | ||
|
|
3b607662e5 | ||
|
|
f2d7041cb0 | ||
|
|
afc06794c2 | ||
|
|
33eaafcff1 | ||
|
|
e5a8743b9a | ||
|
|
3322c4e2a5 | ||
|
|
aa39aaf489 | ||
|
|
a238581f44 | ||
|
|
00814ef64e | ||
|
|
6362d2966b | ||
|
|
4e181a82b6 | ||
|
|
374199905d | ||
|
|
642079ab1f | ||
|
|
215c9d96b9 | ||
|
|
5369eda705 | ||
|
|
5e42bbe1eb | ||
|
|
79dd2ee03e | ||
|
|
78d3eae44b | ||
|
|
a9ee68fe1a | ||
|
|
ee5678ccfb | ||
|
|
248a675c1a | ||
|
|
6b1d84cf38 | ||
|
|
c0977538ff | ||
|
|
c8727e8b2f | ||
|
|
55a41e19f4 | ||
|
|
6096c0b158 | ||
|
|
161c6dfd95 | ||
|
|
078bcf7652 | ||
|
|
57c6df3970 | ||
|
|
1130ee1b21 | ||
|
|
c9c0a29740 | ||
|
|
e60d972bff | ||
|
|
3c4a139f52 | ||
|
|
bf615efa96 | ||
|
|
2af3940be7 | ||
|
|
64e145909e | ||
|
|
e90f34cc96 | ||
|
|
a23cf2dd26 | ||
|
|
7b623d0faa | ||
|
|
14788b1d53 | ||
|
|
e1bf8cf735 | ||
|
|
2117731b46 | ||
|
|
74f8fe1caf | ||
|
|
9388a3b277 | ||
|
|
13e7a05e1b | ||
|
|
885fa25c0c | ||
|
|
966b35094f | ||
|
|
65607bf3b3 | ||
|
|
5fcba7a7db | ||
|
|
f9ec3cb753 | ||
|
|
bf6ffd65a9 | ||
|
|
d680b6b43a | ||
|
|
3ead3a0ba3 | ||
|
|
4c5c977310 | ||
|
|
89a9ce87da | ||
|
|
52e469f6ac | ||
|
|
4f275de8d4 | ||
|
|
d9de41e350 | ||
|
|
a4c279ba25 | ||
|
|
e68282a9e4 | ||
|
|
8613206c3a | ||
|
|
31b8cde8bf | ||
|
|
2f2f9520db | ||
|
|
2501ae836e | ||
|
|
2c8bf91e77 | ||
|
|
0ac063498e | ||
|
|
2bf672f0cd | ||
|
|
ca209adca0 | ||
|
|
dd10c82c5e | ||
|
|
c3ad2a0931 | ||
|
|
30aacc8946 | ||
|
|
723a644937 | ||
|
|
165eeb5bf2 | ||
|
|
f1776d4a26 | ||
|
|
2f2a1b3182 | ||
|
|
0477cf7db5 | ||
|
|
2d7c7986aa | ||
|
|
bf84ea8fca | ||
|
|
f3ef8a7f96 | ||
|
|
ec214f610a | ||
|
|
013fd46722 | ||
|
|
32d583b9ef | ||
|
|
df6e201ba5 | ||
|
|
789b613b82 | ||
|
|
b698342db4 | ||
|
|
d853f2fe99 | ||
|
|
20d66b5b31 | ||
|
|
da7264c5e8 | ||
|
|
f3299f78d9 | ||
|
|
7ef0805c91 | ||
|
|
ba1afa2569 | ||
|
|
936e2d0863 | ||
|
|
166410f66a | ||
|
|
f8c9298694 | ||
|
|
b0b642b6bb | ||
|
|
5fa20343da | ||
|
|
bbbf181011 | ||
|
|
6af547d2af | ||
|
|
d72b1822cb | ||
|
|
d34df18da8 | ||
|
|
27cb247760 | ||
|
|
7a66d6fccd | ||
|
|
f1cb372339 | ||
|
|
46d65e6fc0 | ||
|
|
6168298b5e | ||
|
|
d5fef413ca | ||
|
|
0802ee6106 | ||
|
|
65d0f87eeb | ||
|
|
488868d80e | ||
|
|
154965efcb | ||
|
|
f364f0b495 | ||
|
|
82e2f5bcb9 | ||
|
|
f7a2228925 | ||
|
|
dadc9d4efd | ||
|
|
df59867431 | ||
|
|
a7c6e9dc00 | ||
|
|
6b51cfd448 | ||
|
|
98b86ca527 | ||
|
|
9a2ef4c52a | ||
|
|
d69d4bd29d | ||
|
|
97cdc56c26 | ||
|
|
c2440b9c79 | ||
|
|
f0b69d089a | ||
|
|
ac79549934 | ||
|
|
e6b8b5e904 | ||
|
|
5a22e5b2b2 | ||
|
|
431d7bf9d9 | ||
|
|
865ea6e66b | ||
|
|
2f787d6a53 | ||
|
|
939ac28d00 | ||
|
|
76068add09 | ||
|
|
0730dd6756 | ||
|
|
64cce8e8be | ||
|
|
2d12dc0f3e | ||
|
|
82a60e99cc | ||
|
|
1cbd18db76 | ||
|
|
48ccd636cd | ||
|
|
573dd0da7e | ||
|
|
df8583801f | ||
|
|
ff2b434133 | ||
|
|
30cae74d50 | ||
|
|
a896d32d37 | ||
|
|
d9d4527894 | ||
|
|
e93caac34b | ||
|
|
cd5182f8ee | ||
|
|
b185c02252 | ||
|
|
58aa10be9c | ||
|
|
5d3d3bf03a | ||
|
|
11af1cb390 | ||
|
|
32057573b7 | ||
|
|
0b6058654e | ||
|
|
a7fe444166 | ||
|
|
f4f734d8ff |
No files matched your search
+7
-1
@@ -1,11 +1,17 @@
|
||||
Dockerfile
|
||||
tokens
|
||||
.*sessions
|
||||
files
|
||||
venv
|
||||
.wwebjs_auth
|
||||
docs
|
||||
examples
|
||||
.github
|
||||
.git
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
.yarn
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
||||
+33
-22
@@ -1,25 +1,36 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'prettier/@typescript-eslint',
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'prettier/@typescript-eslint',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-inferrable-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'lines-between-class-members': [
|
||||
'error',
|
||||
'always',
|
||||
{ exceptAfterSingleLine: true },
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: wa_http_api
|
||||
open_collective: # doks Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom:
|
||||
- https://boosty.to/wa-http-api
|
||||
- https://portal.devlike.pro/
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: '[BUG][WEBJS] - '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
> A clear and concise description of what the bug is. Feel free to remove
|
||||
> sections that you don't feel to make the text shorter!
|
||||
|
||||
**Version**
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2023.11.1",
|
||||
"engine": "NOWEB",
|
||||
"tier": "PLUS",
|
||||
"browser": "/usr/bin/google-chrome-stable"
|
||||
}
|
||||
```
|
||||
|
||||
> You can get the WAHA version by calling `GET /api/version` Try to update to
|
||||
> [the latest version](https://github.com/devlikeapro/waha/releases) before
|
||||
> creating an issue!
|
||||
|
||||
**To Reproduce** Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
> A clear and concise description of what you expected to happen.
|
||||
|
||||
**Engine** I'm using **WEBJS** engine.
|
||||
|
||||
> If you set `WHATSAPP_DEFAULT_ENGINE` explicitly to
|
||||
> [one of the engine](https://waha.devlike.pro/docs/how-to/engines/) - please
|
||||
> provide that information here and in the issue title.
|
||||
|
||||
**Requests - Responses**
|
||||
|
||||
> Please attach all sent requests, responses that you sent.
|
||||
|
||||
**Docker Logs**
|
||||
|
||||
> Collect and attach related docker logs if you have any
|
||||
> https://waha.devlike.pro/docs/how-to/deploy/#viewing-the-logs
|
||||
|
||||
**Screenshots**
|
||||
|
||||
> If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
|
||||
> Add any other context about the problem here.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: '[Feature Request]'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.** I'm always
|
||||
frustrated when [...]
|
||||
|
||||
> A clear and concise description of what the problem is. Ex.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
|
||||
> A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
> A clear and concise description of any alternative solutions or features
|
||||
> you've considered.
|
||||
|
||||
**Additional context** Add any other context or screenshots about the feature
|
||||
request here.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Idea from
|
||||
# https://stackoverflow.com/a/65539398/6753144
|
||||
#
|
||||
|
||||
OWNER=devlikeapro
|
||||
REPO=waha-plus
|
||||
|
||||
# list workflows
|
||||
gh api -X GET /repos/$OWNER/$REPO/actions/workflows | jq '.workflows[] | .name,.id'
|
||||
|
||||
# copy the ID of the workflow you want to clear and set it
|
||||
WORKFLOW_ID=40218610
|
||||
|
||||
# list runs
|
||||
gh api -X GET /repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/runs --paginate | jq '.workflow_runs[] | .id'
|
||||
|
||||
# delete runs
|
||||
gh api -X GET /repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/runs --paginate | jq '.workflow_runs[] | .id' | xargs -I{} gh api -X DELETE /repos/$OWNER/$REPO/actions/runs/{} --silent
|
||||
@@ -0,0 +1,10 @@
|
||||
teamLabels:
|
||||
- team: waha-patrons-pro
|
||||
label: patron:PRO
|
||||
comment: '[](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)'
|
||||
- team: waha-patrons-advanced
|
||||
label: patron:ADVANCED
|
||||
comment: '[](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)'
|
||||
- team: waha-patrons-plus
|
||||
label: patron:PLUS
|
||||
comment: '[](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)'
|
||||
@@ -3,16 +3,47 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
deploy-docker:
|
||||
runs-on: ubuntu-latest
|
||||
docker-build:
|
||||
runs-on: ${{ matrix.runner }}
|
||||
name: ${{ matrix.platform }} - ${{ matrix.browser }} - ${{ matrix.tag }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Chromium
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'latest'
|
||||
platform: 'amd64'
|
||||
browser: 'chromium'
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
tag: 'arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'chromium'
|
||||
# Chrome
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'chrome'
|
||||
platform: 'amd64'
|
||||
browser: 'chrome'
|
||||
# Chrome is not supported on arm64
|
||||
# - runner: "buildjet-4vcpu-ubuntu-2204-arm"
|
||||
# tag: "chrome-arm"
|
||||
# platform: "linux/arm64"
|
||||
# browser: "chrome"
|
||||
# No browser
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'noweb'
|
||||
platform: 'amd64'
|
||||
browser: 'none'
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
tag: 'noweb-arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'none'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
@@ -20,10 +51,11 @@ jobs:
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: allburov/whatsapp-http-api
|
||||
flavor: latest=true
|
||||
images: ${{ vars.DOCKER_IMAGE }}
|
||||
flavor: latest=false
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=raw,value=${{ matrix.tag }}
|
||||
type=raw,value=${{ matrix.tag }}-{{tag}}
|
||||
|
||||
- name: Login to image repository
|
||||
if: github.ref_type == 'tag'
|
||||
@@ -33,12 +65,13 @@ jobs:
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
build-args: |
|
||||
USE_BROWSER=${{ matrix.browser }}
|
||||
push: ${{ github.ref_type == 'tag' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
# cache-from: type=gha
|
||||
cache-to: type=gha
|
||||
@@ -0,0 +1,84 @@
|
||||
name: Manual push
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to push'
|
||||
required: true
|
||||
default: 'dev'
|
||||
push:
|
||||
description: 'Push to registry'
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
docker-build:
|
||||
runs-on: ${{ matrix.runner }}
|
||||
name: ${{ matrix.platform }} - ${{ matrix.browser }} - ${{ matrix.tag }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Chromium
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'latest'
|
||||
platform: 'amd64'
|
||||
browser: 'chromium'
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
tag: 'arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'chromium'
|
||||
# Chrome
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'chrome'
|
||||
platform: 'amd64'
|
||||
browser: 'chrome'
|
||||
# Chrome is not supported on arm64
|
||||
# - runner: "buildjet-4vcpu-ubuntu-2204-arm"
|
||||
# tag: "chrome-arm"
|
||||
# platform: "linux/arm64"
|
||||
# browser: "chrome"
|
||||
# No browser
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204'
|
||||
tag: 'noweb'
|
||||
platform: 'amd64'
|
||||
browser: 'none'
|
||||
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
|
||||
tag: 'noweb-arm'
|
||||
platform: 'linux/arm64'
|
||||
browser: 'none'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ vars.DOCKER_IMAGE }}
|
||||
flavor: latest=false
|
||||
tags: |
|
||||
type=raw,value=${{ inputs.tag }}-${{ matrix.tag }}
|
||||
|
||||
- name: Login to image repository
|
||||
if: github.ref_type == 'tag'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: ${{ matrix.platform }}
|
||||
build-args: |
|
||||
USE_BROWSER=${{ matrix.browser }}
|
||||
push: ${{ inputs.push }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -1,6 +1,12 @@
|
||||
.yarn
|
||||
example.ts
|
||||
.*sessions
|
||||
tokens
|
||||
files
|
||||
venv
|
||||
.wwebjs_auth
|
||||
test.js
|
||||
.wwebjs_cache
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
|
||||
@@ -1,5 +1,48 @@
|
||||
exclude: |
|
||||
(?x)^(
|
||||
^src/dashboard/.*|
|
||||
^src/core/engines/webjs/.*html|
|
||||
^src/plus/engines/webjs/.*html
|
||||
)$
|
||||
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: lint
|
||||
name: lint
|
||||
language: system
|
||||
entry: bash -c '$HOME/.nvm/nvm-exec npm run lint'
|
||||
pass_filenames: false
|
||||
files: |
|
||||
(?x)^(
|
||||
src/.*|
|
||||
)$
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: 'v3.1.0'
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: |
|
||||
(?x)^(
|
||||
docs/.*|
|
||||
README.md|
|
||||
)$
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs
|
||||
rev: v1.1.2
|
||||
hooks:
|
||||
- id: markdown-toc
|
||||
name: README.md
|
||||
files: ^README.md$
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: no-plus-in-core
|
||||
name: No "plus" in core
|
||||
language: pygrep
|
||||
entry: 'plus'
|
||||
files: |
|
||||
(?x)^(
|
||||
src/api/.*|
|
||||
src/core/.*|
|
||||
src/structures/.*|
|
||||
src/config.service.ts|
|
||||
)$
|
||||
+11
-2
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "auto",
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"quoteProps": "as-needed",
|
||||
"proseWrap": "always",
|
||||
"printWidth": 80
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.3.cjs
|
||||
@@ -0,0 +1,4 @@
|
||||
# Changelog
|
||||
|
||||
You can find changelog on
|
||||
[the changelog page](https://waha.devlike.pro/docs/help/changelog/)
|
||||
+69
-18
@@ -1,24 +1,75 @@
|
||||
FROM node:15
|
||||
# Chrome
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y wget gnupg \
|
||||
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
#
|
||||
# Build
|
||||
#
|
||||
ARG NODE_VERSION=20.12.2-bullseye
|
||||
FROM node:${NODE_VERSION} as build
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
|
||||
# npm packages
|
||||
WORKDIR /app
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y openssh-client
|
||||
RUN apt install openssh-client
|
||||
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
|
||||
WORKDIR /src
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
RUN npm install
|
||||
COPY yarn.lock .
|
||||
RUN yarn set version 3.6.3
|
||||
RUN --mount=type=ssh yarn install
|
||||
|
||||
# App
|
||||
WORKDIR /app
|
||||
ADD . /app
|
||||
RUN npm run build
|
||||
EXPOSE 3000
|
||||
WORKDIR /src
|
||||
ADD . /src
|
||||
RUN --mount=type=ssh yarn install
|
||||
RUN yarn build && find ./dist -name "*.d.ts" -delete
|
||||
|
||||
CMD npm run start:prod
|
||||
#
|
||||
# Final
|
||||
#
|
||||
FROM node:${NODE_VERSION} as release
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
# Quick fix for memory potential memory leaks
|
||||
# https://github.com/devlikeapro/waha/issues/347
|
||||
ENV NODE_OPTIONS="--max-old-space-size=16384"
|
||||
ARG USE_BROWSER=chromium
|
||||
|
||||
RUN echo "USE_BROWSER=$USE_BROWSER"
|
||||
|
||||
# Install fonts if using either chromium or chrome
|
||||
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
|
||||
apt-get update \
|
||||
&& apt-get install -y fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
# Install Chromium
|
||||
RUN if [ "$USE_BROWSER" = "chromium" ]; then \
|
||||
apt-get update \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y chromium \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
# Install Chrome
|
||||
# Available versions:
|
||||
# https://www.ubuntuupdates.org/package/google_chrome/stable/main/base/google-chrome-stable
|
||||
ARG CHROME_VERSION="122.0.6261.128-1"
|
||||
RUN if [ "$USE_BROWSER" = "chrome" ]; then \
|
||||
wget --no-verbose -O /tmp/chrome.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb \
|
||||
&& apt-get update \
|
||||
&& apt install -y /tmp/chrome.deb \
|
||||
&& rm /tmp/chrome.deb \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
# Attach sources, install packages
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
COPY --from=build /src/node_modules ./node_modules
|
||||
COPY --from=build /src/dist ./dist
|
||||
|
||||
# Run command, etc
|
||||
EXPOSE 3000
|
||||
CMD yarn start:prod
|
||||
@@ -1,8 +1,40 @@
|
||||
build:
|
||||
docker build . -t allburov/whatsapp-http-api
|
||||
docker build . -t devlikeapro/waha
|
||||
|
||||
run:
|
||||
docker run --rm -v `pwd`/tokens:/app/tokens -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api allburov/whatsapp-http-api
|
||||
build-chrome:
|
||||
docker build . -t devlikeapro/waha-plus:chrome --build-arg USE_BROWSER=chrome
|
||||
|
||||
build-noweb:
|
||||
docker build . -t devlikeapro/waha:noweb --build-arg USE_BROWSER=none
|
||||
|
||||
build-all: build build-chrome build-noweb
|
||||
|
||||
build-plus:
|
||||
docker build . -t devlikeapro/waha-plus
|
||||
|
||||
build-ssh:
|
||||
# check IMAGE provided
|
||||
@[ "${IMAGE}" ] || ( echo "Add APP: make build-ssh image=devlikeapro/waha"; exit 1 );
|
||||
eval $(ssh-agent) && \
|
||||
ssh-add ~/.ssh/id_rsa && \
|
||||
docker buildx build --ssh default=${SSH_AUTH_SOCK} . -t ${IMAGE} --build-arg USE_BROWSER=none
|
||||
|
||||
stop:
|
||||
docker stop waha
|
||||
|
||||
clean: stop
|
||||
sudo rm -rf .sessions
|
||||
|
||||
push:
|
||||
docker push allburov/whatsapp-http-api
|
||||
docker push devlikeapro/waha
|
||||
|
||||
for-swagger:
|
||||
WHATSAPP_SWAGGER_CONFIG_ADVANCED=true npm run start
|
||||
|
||||
update-dependencies:
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js#main-fork
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:WhiskeySockets/Baileys
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up venom-bot
|
||||
|
||||
start-proxy:
|
||||
docker run --rm -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta
|
||||
@@ -1,185 +1,197 @@
|
||||
# WhatsApp HTTP API Free
|
||||
# WAHA
|
||||
|
||||
WhatsApp HTTP API that you can configure in a click! It's really Free! :)
|
||||
<p align="center">
|
||||
<img src="./logo.png" style='border-radius: 50%' width='150'/>
|
||||
</p>
|
||||
|
||||
The project provides you HTTP API for Whatsapp that you can use to send and receive messages. You can
|
||||
go [through currently supported methods in Swagger](https://allburov.github.io/whatsapp-http-api/)
|
||||
**WAHA** - **W**hats**A**pp **H**TTP **A**PI (REST API) that you can install on your own server and run in less than 5 minutes!
|
||||
|
||||
> The project is an HTTP API wrapper around https://github.com/orkestral/venom
|
||||
[](https://hub.docker.com/r/devlikeapro/waha)
|
||||
|
||||
- Documentation: [https://waha.devlike.pro/](https://waha.devlike.pro/)
|
||||
- Swagger Example: [https://waha.devlike.pro/swagger](https://waha.devlike.pro/swagger)
|
||||
|
||||
# Tables of Contents
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [Installation](#installation)
|
||||
- [First Steps](#first-steps)
|
||||
* [Run WhatsAPP HTTP API](#run-whatsapp-http-api)
|
||||
* [Create a new session and login](#create-a-new-session-and-login)
|
||||
* [Send a text message](#send-a-text-message)
|
||||
- [Receive messages](#receive-messages)
|
||||
+ [Python echo server](#python-echo-server)
|
||||
- [Environment variables](#environment-variables)
|
||||
* [Common](#common)
|
||||
* [Webhooks](#webhooks)
|
||||
* [File storage](#file-storage)
|
||||
- [How to](#how-to)
|
||||
* [How to build the newest Docker image locally](#how-to-build-the-newest-docker-image-locally)
|
||||
- [Support](#support)
|
||||
- [Features](#features)
|
||||
* [Messages](#messages)
|
||||
* [Sessions](#sessions)
|
||||
* [Security](#security)
|
||||
* [Updates](#updates)
|
||||
* [Groups](#groups)
|
||||
* [Contacts](#contacts)
|
||||
- [Quick start](#quick-start)
|
||||
* [Requirements](#requirements)
|
||||
* [Send your first message](#send-your-first-message)
|
||||
+ [1. Download image](#1-download-image)
|
||||
+ [2. Run WhatsApp HTTP API](#2-run-whatsapp-http-api)
|
||||
+ [3. Start a new session](#3-start-a-new-session)
|
||||
+ [4. Get and scan QR](#4-get-and-scan-qr)
|
||||
+ [5. Get the screenshot](#5-get-the-screenshot)
|
||||
+ [6. Send a text message](#6-send-a-text-message)
|
||||
* [What is next?](#what-is-next)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
# Installation
|
||||
# Features
|
||||
|
||||
## Messages
|
||||
- Send messages
|
||||
- Receive messages
|
||||
- Message replies
|
||||
- Send location
|
||||
- Receive location
|
||||
- React to messages
|
||||
- Send media (images/documents/files)
|
||||
- Send voice messages
|
||||
- Receive media (images/audio/video/documents)
|
||||
- Receive messages - webhook retries
|
||||
|
||||
## Sessions
|
||||
- Multi Device
|
||||
- Get the screenshot
|
||||
- Session saving (don't have to scan QR on every restart)
|
||||
- Single WhatsApp account running inside one container
|
||||
- Multiple WhatsApp account running inside one container
|
||||
|
||||
## Security
|
||||
- Swagger panel authentication
|
||||
- API authentication
|
||||
|
||||
## Updates
|
||||
- Bug fixes and updates come as soon as they are implemented.
|
||||
|
||||
## Groups
|
||||
- Create a group
|
||||
- Get invite for group
|
||||
- Modify group info (subject, description)
|
||||
- Add group participants
|
||||
- Kick group participants
|
||||
- Promote/demote group participants
|
||||
|
||||
## Contacts
|
||||
- Mute/unmute chats
|
||||
- Block/unblock contacts
|
||||
- Get contact info
|
||||
- Get profile pictures
|
||||
- Get presence (online\offline\typing) status
|
||||
|
||||
# Quick start
|
||||
|
||||
## Requirements
|
||||
|
||||
Only thing that you must have - installed docker. Please follow the original
|
||||
instruction [how to install docker](https://docs.docker.com/get-docker/)
|
||||
instruction <a href="https://docs.docker.com/get-docker/" target="_blank" rel="noopener">how to install docker -></a>.
|
||||
|
||||
When you are ready - come back and follows the below steps to send the first text message to WhatsApp via HTTP API!
|
||||
|
||||
{{< details "Why Docker?" >}}
|
||||
Docker makes it easy to ship all-in-one solution with the runtime and dependencies. You don't have to worry about
|
||||
language-specific libraries or chrome installation.
|
||||
|
||||
Also Docker makes installation and update processes so simple, just one command!
|
||||
{{< /details >}}
|
||||
|
||||
## Send your first message
|
||||
|
||||
Let's go over steps that allow you to send your first text message via WhatsApp HTTP API!
|
||||
|
||||
### 1. Download image
|
||||
|
||||
Assuming you have installed [Docker](https://docs.docker.com/get-docker/), let's download the image.
|
||||
|
||||
|
||||
```bash
|
||||
docker pull allburov/whatsapp-http-api
|
||||
docker pull devlikeapro/waha
|
||||
```
|
||||
|
||||
# First Steps
|
||||
|
||||
We're going to go through basic steps and send a text message at the end!
|
||||
```bash
|
||||
docker login -u devlikeapro -p {KEY}
|
||||
docker pull devlikeapro/waha-plus
|
||||
docker logout
|
||||
```
|
||||
|
||||
## Run WhatsAPP HTTP API
|
||||
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
|
||||
|
||||
### 2. Run WhatsApp HTTP API
|
||||
|
||||
Run WhatsApp HTTP API:
|
||||
|
||||
```bash
|
||||
docker run -it --rm -v `pwd`/tokens:/app/tokens -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api allburov/whatsapp-http-api
|
||||
docker run -it --rm -p 3000:3000/tcp --name waha devlikeapro/waha
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
```
|
||||
|
||||
Open the link in your browser: http://localhost:3000/
|
||||
Open the link in your browser [http://localhost:3000/](http://localhost:3000/) and you'll see API documentation
|
||||
(Swagger).
|
||||
|
||||
Note: We don't recommend expose the API outside the world because it does not
|
||||
support ([yet](https://github.com/allburov/whatsapp-http-api/issues/4)) authorization!
|
||||
|
||||
## Create a new session and login
|
||||
### 3. Start a new session
|
||||
|
||||
1. To start a new session you should have your mobile phone with installed WhatsApp application close to you. Please go
|
||||
and read how what we'll need to a bit
|
||||
later: [How to log in - the instruction on WhatsApp site](https://faq.whatsapp.com/381777293328336/?helpref=hc_fnav)
|
||||
1. Open API documentation at http://localhost:3000/
|
||||
1. **Start a new session with a name** (you can use `default` for the start) - find `POST /api/session/start`, click
|
||||
on **Try it out**, then **Execute** a bit below.
|
||||

|
||||
1. **Scan QR Code** - find `GET /api/screenshot` and execute it, it'll show you QR code that you must scan with your
|
||||
device.
|
||||

|
||||
1. **Get a screenshot again** - it'll show you the screenshot of you Whatsapp instance. If you can get the actual
|
||||
screenshot - then you're ready to start sending messages!
|
||||

|
||||
To start a new session you should have your mobile phone with installed WhatsApp application close to you.
|
||||
|
||||
## Send a text message
|
||||
Please go and read how what we'll need to a bit
|
||||
later:
|
||||
<a href="https://faq.whatsapp.com/381777293328336/?helpref=hc_fnav" target="_blank">
|
||||
How to log in - the instruction on WhatsApp site
|
||||
</a>
|
||||
|
||||
Let's try to send a message - you can either find `POST /api/sendText` in swagger (http://localhost:3000/) or
|
||||
use `curl` or just open a link in a browser (change the phone
|
||||
before!) http://localhost:3000/api/sendText?phone=79776772457&text=Hello+from+WhatsApp+HTTP+API+Free!
|
||||
When your ready - find `POST /api/session/start`, click on **Try it out**, then **Execute** a bit below.
|
||||
|
||||
|
||||
The example payload:
|
||||
```json
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
By using the request with `name` values you can start multiple session (WhatsApp accounts) inside the single docker container in Plus
|
||||
|
||||
|
||||
### 4. Get and scan QR
|
||||
|
||||
Find `GET /api/screenshot` and execute it, it shows you QR code.
|
||||
|
||||
|
||||
**Scan the QR with your cell phone's WhatsApp app.**
|
||||
|
||||
|
||||
### 5. Get the screenshot
|
||||
|
||||
Execute `GET /api/screenshot` after a few seconds after scanning the QR - it'll show you the screenshot of you Whatsapp
|
||||
instance. If you can get the actual screenshot - then you're ready to start sending messages!
|
||||
|
||||
|
||||
### 6. Send a text message
|
||||
|
||||
Let's send a text message - find `POST /api/sendText` in [swagger](http://localhost:3000/) and change `chatId` this
|
||||
way: use a phone international phone number without `+` symbol and add `@c.us` at the end.
|
||||
|
||||
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
|
||||
|
||||
The example payload:
|
||||
```json
|
||||
{
|
||||
"chatId": "12132132130@c.us",
|
||||
"text": "Hi there!",
|
||||
"session": "default"
|
||||
}
|
||||
```
|
||||
|
||||
Also, you can use `curl` and send POST request like this:
|
||||
|
||||
```bash
|
||||
# Phone without +
|
||||
# Using GET
|
||||
curl "http://localhost:3000/api/sendText?phone=79776772457&text=Hello+from+WhatsApp+HTTP+API+Free!"
|
||||
|
||||
# Using POST
|
||||
export PHONE=79776772457
|
||||
curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP API Free\" }" -H "Content-Type: application/json" -X POST http://localhost:3000/api/sendText
|
||||
export PHONE=12132132130
|
||||
curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP API\" }" -H "Content-Type: application/json" -X POST http://localhost:3000/api/sendText
|
||||
```
|
||||
|
||||
# Receive messages
|
||||
|
||||
To show how to receive messages we'll create a simple "echo" server with two functions:
|
||||
|
||||
1. When we receive a text message - just send the text back
|
||||
2. When we receive a message with a file (an image, a voice message) - download it and send the path back
|
||||
|
||||
In order to send you messages we use **Webhooks** (look at them below) and configure them via environments variables. So
|
||||
what you need to
|
||||
create "echo" server is HTTP server that will receive JSON POST request and then call back WhatsApp HTTP API
|
||||
via `POST /api/sendText` endpoint with JSON body.
|
||||
|
||||
### Python echo server
|
||||
|
||||
We use Python. Feel free to create your favorite language example and contribute to the project!
|
||||
|
||||
Run "echo" server in one terminal and leave it working:
|
||||
|
||||
```bash
|
||||
# if you haven't already
|
||||
git clone https://github.com/allburov/whatsapp-http-api.git
|
||||
cd whatsapp-http-api
|
||||
python -mpip install -r examples/requirements.txt
|
||||
export FLASK_APP=examples/echo.py
|
||||
flask run
|
||||
```
|
||||
|
||||
Visit http://localhost:5000 and check that we are good to go further.
|
||||
|
||||
Let's start WhatsApp HTTP API and configure the "on message" webhook and point it on our "http://localhost:5000/message"
|
||||
endpoint:
|
||||
|
||||
```bash
|
||||
docker run -it -v `pwd`/tokens:/app/tokens --network=host -e WHATSAPP_HOOK_ONMESSAGE=http://localhost:5000/message allburov/whatsapp-http-api
|
||||
```
|
||||
|
||||
Now go ahead, open the second whatsapp and send to our WhatsApp HTTP API a text message! It must reply the same text.
|
||||
|
||||
If you try to send an image the "echo server" will send a path to the downloaded file.
|
||||
|
||||
# Environment variables
|
||||
|
||||
## Common
|
||||
|
||||
- `DEBUG` - show debug and verbose logs, set in any value
|
||||
- `WHATSAPP_API_PORT` - listen port for HTTP server (default: `3000`)
|
||||
- `WHATSAPP_API_HOSTNAME` - Hostname for HTTP server (default: `localhost`)
|
||||
- `WHATSAPP_START_SESSION` - start session with that name right after launching the app
|
||||
|
||||
## Webhooks
|
||||
|
||||
The description of [webhooks you can in Venom README.md, section Events](https://github.com/orkestral/venom#events)
|
||||
|
||||
All webhooks are disabled by default:
|
||||
|
||||
- `WHATSAPP_HOOK_ONANYMESSAGE=http://localhost/uri`
|
||||
- `WHATSAPP_HOOK_ONMESSAGE=http://localhost/uri`
|
||||
- `WHATSAPP_HOOK_ONSTATECHANGE=http://localhost/uri`
|
||||
- `WHATSAPP_HOOK_ONACK=http://localhost/uri`
|
||||
- `WHATSAPP_HOOK_ONADDEDTOGROUP=http://localhost/uri`
|
||||
|
||||
If you want to look at the payload of the webhooks - use the url for that `https://httpbin.org/post`.
|
||||
|
||||
```bash
|
||||
docker run -it -v `pwd`/tokens:/app/tokens --network=host -e WHATSAPP_HOOK_ONMESSAGE=https://httpbin.org/post allburov/whatsapp-http-api
|
||||
```
|
||||
|
||||
## File storage
|
||||
|
||||
- `WHATSAPP_FILES_FOLDER` - folder where will be stored files from chats (images, voice messages) (
|
||||
default: `/tmp/whatsapp-files`)
|
||||
- `WHATSAPP_FILES_MIMETYPES` - download only these mimetypes from messages (download all files be default). Mimetypes
|
||||
must be separated by a comma, without spaces: `audio,image/png,image/gif`. In order to choose type use prefix (
|
||||
like `audio,image`).
|
||||
- `WHATSAPP_FILES_LIFETIME`- to keep free space files will be removed after this time (default: `180`, in seconds)
|
||||
|
||||
# How to
|
||||
## How to build the newest Docker image locally
|
||||
|
||||
If the changes haven't been released yet from `main` branch - you can build your own version from the git repository
|
||||
```bash
|
||||
git clone https://github.com/allburov/whatsapp-http-api.git
|
||||
cd whatsapp-http-api
|
||||
docker build . -t allburov/whatsapp-http-api
|
||||
```
|
||||
|
||||
# Support
|
||||
|
||||
If you want to support the project - you can either:
|
||||
|
||||
1. Create a Pull Request for desired functionality
|
||||
2. Support [the project one time, every month or get access to telegram channel on Boosty](https://boosty.to/allburov)
|
||||
3. Looking for the personal support, have an important feature request for your business, need to develop a whatsapp bot? [Reach me out at Telegram](https://t.me/allburov)!
|
||||
|
||||
## What is next?
|
||||
[Go and read the full documentation!](https://waha.devlike.pro/docs/overview/introduction/)
|
||||
@@ -0,0 +1,80 @@
|
||||
version: '3'
|
||||
services:
|
||||
waha:
|
||||
# Check available Docker images:
|
||||
# https://waha.devlike.pro/docs/how-to/engines/#docker-images
|
||||
image: devlikeapro/waha
|
||||
container_name: waha
|
||||
ports:
|
||||
- '3000:3000/tcp'
|
||||
environment:
|
||||
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
|
||||
- WHATSAPP_HOOK_URL=https://httpbin.org/post
|
||||
- WHATSAPP_HOOK_EVENTS=message
|
||||
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
restart: always
|
||||
|
||||
waha-plus:
|
||||
# Check available Docker images:
|
||||
# https://waha.devlike.pro/docs/how-to/engines/#docker-images
|
||||
image: devlikeapro/waha-plus:chrome
|
||||
container_name: waha-plus
|
||||
ports:
|
||||
- '3000:3000/tcp'
|
||||
volumes:
|
||||
- './.sessions:/app/.sessions'
|
||||
environment:
|
||||
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
|
||||
# - WHATSAPP_RESTART_ALL_SESSIONS=True
|
||||
# - WHATSAPP_HOOK_URL=https://httpbin.org/post
|
||||
# - WHATSAPP_HOOK_EVENTS=message
|
||||
# - WHATSAPP_API_HOSTNAME=localhost
|
||||
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
- WHATSAPP_API_KEY=321
|
||||
# Username and password for Swagger
|
||||
- WHATSAPP_SWAGGER_USERNAME=swagger
|
||||
- WHATSAPP_SWAGGER_PASSWORD=admin
|
||||
# Username and password for WAHA Dashboard
|
||||
# you can use the same username and password for both Swagger and Dashboard
|
||||
# Default - waha / waha
|
||||
- WAHA_DASHBOARD_USERNAME=waha
|
||||
- WAHA_DASHBOARD_PASSWORD=waha
|
||||
# Engine specific environment variables
|
||||
# - WAHA_WEBJS_WEB_VERSION=2.2412.54-videofix
|
||||
restart: always
|
||||
|
||||
waha-plus--mongodb:
|
||||
# Check available Docker images:
|
||||
# https://waha.devlike.pro/docs/how-to/engines/#docker-images
|
||||
image: devlikeapro/waha-plus:chrome
|
||||
container_name: waha-plus--mongodb
|
||||
ports:
|
||||
- '3000:3000/tcp'
|
||||
volumes:
|
||||
- './.sessions:/app/.sessions'
|
||||
environment:
|
||||
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
|
||||
# - WHATSAPP_RESTART_ALL_SESSIONS=True
|
||||
# - WHATSAPP_HOOK_URL=https://httpbin.org/post
|
||||
# - WHATSAPP_HOOK_EVENTS=message
|
||||
# - WHATSAPP_API_KEY=321
|
||||
# - WHATSAPP_SWAGGER_USERNAME=admin
|
||||
# - WHATSAPP_SWAGGER_PASSWORD=123
|
||||
# - WAHA_DASHBOARD_USERNAME=admin
|
||||
# - WAHA_DASHBOARD_PASSWORD=123
|
||||
# - WHATSAPP_API_HOSTNAME=localhost
|
||||
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
- WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongodb:27017
|
||||
restart: always
|
||||
|
||||
mongodb:
|
||||
image: mongo
|
||||
container_name: mongodb
|
||||
ports:
|
||||
- '27017:27017/tcp'
|
||||
volumes:
|
||||
- mongodb_data:/data/db
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
mongodb_data: {}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 665 B |
Binary file not shown.
|
Before Width: | Height: | Size: 628 B |
Binary file not shown.
|
Before Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 139 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
@@ -1,57 +0,0 @@
|
||||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>allburov/whatsapp-http-api</title>
|
||||
<link href="./swagger-ui.css" rel="stylesheet" type="text/css">
|
||||
<link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/>
|
||||
<link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/>
|
||||
<style>
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script charset="UTF-8" src="./swagger-ui-bundle.js"></script>
|
||||
<script charset="UTF-8" src="./swagger-ui-standalone-preset.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "openapi.json",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,77 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
function run() {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v, i, _arr) {
|
||||
_arr[i] = '"' + v.replace('=', '":"') + '"';
|
||||
})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "[" + qp.error + "]: " +
|
||||
(qp.error_description ? qp.error_description + ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: " + qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
</script>
|
||||
@@ -1,908 +0,0 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/api/sessions/start": {
|
||||
"post": {
|
||||
"operationId": "SessionsController_start",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"sessions"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sessions/stop": {
|
||||
"post": {
|
||||
"operationId": "SessionsController_stop",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"sessions"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sessions": {
|
||||
"get": {
|
||||
"operationId": "SessionsController_list",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"sessions"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendContactVcard": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendContactVcard",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageContactVcard"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendText": {
|
||||
"get": {
|
||||
"operationId": "ChattingController_sendTextGet",
|
||||
"summary": "Send a text message",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "phone",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sessionName",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"default": "default",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendText",
|
||||
"summary": "Send a text message",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageText"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendLocation": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendLocation",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageLocation"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendLinkPreview": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendLinkPreview",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageLinkPreview"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendImage": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendImage",
|
||||
"summary": "NOT IMPLEMENTED YET",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageImage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendFile": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendFile",
|
||||
"summary": "NOT IMPLEMENTED YET",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageFile"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/reply": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_reply",
|
||||
"summary": "Reply to a text message",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageReply"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendSeen": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendSeen",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChatRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/startTyping": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_startTyping",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChatRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/stopTyping": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_stopTyping",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChatRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/killServiceWorker": {
|
||||
"post": {
|
||||
"operationId": "DeviceController_killServiceWorker",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/restartService": {
|
||||
"post": {
|
||||
"operationId": "DeviceController_restartService",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/getHostDevice": {
|
||||
"get": {
|
||||
"operationId": "DeviceController_getHostDevice",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/getConnectionState": {
|
||||
"get": {
|
||||
"operationId": "DeviceController_getConnectionState",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/getBatteryLevel": {
|
||||
"get": {
|
||||
"operationId": "DeviceController_getBatteryLevel",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/isConnected": {
|
||||
"get": {
|
||||
"operationId": "DeviceController_isConnected",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/getWAVersion": {
|
||||
"get": {
|
||||
"operationId": "DeviceController_getWAVersion",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/screenshot": {
|
||||
"get": {
|
||||
"operationId": "ScreenshotController_screenshot",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "sessionName",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"default": "default",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"screenshot"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"title": "WhatsApp HTTP API",
|
||||
"description": "WhatsApp HTTP API that you can configure in a click!",
|
||||
"version": "1.0",
|
||||
"contact": {}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "sessions",
|
||||
"description": "Control your WhatsApp sessions"
|
||||
},
|
||||
{
|
||||
"name": "screenshot",
|
||||
"description": "Get screenshot of WhatsApp and show QR code"
|
||||
},
|
||||
{
|
||||
"name": "chatting",
|
||||
"description": "Chat methods"
|
||||
},
|
||||
{
|
||||
"name": "device",
|
||||
"description": "Device information"
|
||||
}
|
||||
],
|
||||
"servers": [],
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"api_key": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "X-VENOM-TOKEN",
|
||||
"description": "Your secret key"
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"SessionRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionName": {
|
||||
"type": "string",
|
||||
"default": "default"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionName"
|
||||
]
|
||||
},
|
||||
"MessageContactVcard": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionName": {
|
||||
"type": "string",
|
||||
"default": "default"
|
||||
},
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"contactsId": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionName",
|
||||
"chatId",
|
||||
"contactsId",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"MessageText": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionName": {
|
||||
"type": "string",
|
||||
"default": "default"
|
||||
},
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionName",
|
||||
"chatId",
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"MessageLocation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionName": {
|
||||
"type": "string",
|
||||
"default": "default"
|
||||
},
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"latitude": {
|
||||
"type": "string"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionName",
|
||||
"chatId",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"title"
|
||||
]
|
||||
},
|
||||
"MessageLinkPreview": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionName": {
|
||||
"type": "string",
|
||||
"default": "default"
|
||||
},
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionName",
|
||||
"chatId",
|
||||
"url",
|
||||
"title"
|
||||
]
|
||||
},
|
||||
"MessageImage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionName": {
|
||||
"type": "string",
|
||||
"default": "default"
|
||||
},
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"caption": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionName",
|
||||
"chatId",
|
||||
"path",
|
||||
"filename",
|
||||
"caption"
|
||||
]
|
||||
},
|
||||
"MessageFile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionName": {
|
||||
"type": "string",
|
||||
"default": "default"
|
||||
},
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"caption": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionName",
|
||||
"chatId",
|
||||
"path",
|
||||
"filename",
|
||||
"caption"
|
||||
]
|
||||
},
|
||||
"MessageReply": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionName": {
|
||||
"type": "string",
|
||||
"default": "default"
|
||||
},
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"reply_to": {
|
||||
"type": "string",
|
||||
"example": "message.id"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionName",
|
||||
"chatId",
|
||||
"reply_to",
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"ChatRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionName": {
|
||||
"type": "string",
|
||||
"default": "default"
|
||||
},
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionName",
|
||||
"chatId"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Github WhatsApp HTTP API",
|
||||
"url": "https://github.com/allburov/whatsapp-http-api"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(function(){try{return require("esprima")}catch(e){}}()):"function"==typeof define&&define.amd?define(["esprima"],t):"object"==typeof exports?exports.SwaggerUIBundle=t(function(){try{return require("esprima")}catch(e){}}()):e.SwaggerUIBundle=t(e.esprima)}(this,(function(e){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=544)}([function(e,t,n){"use strict";e.exports=n(129)},function(e,t,n){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:J(e)}function r(e){return s(e)?e:K(e)}function o(e){return u(e)?e:Y(e)}function a(e){return i(e)&&!c(e)?e:G(e)}function i(e){return!(!e||!e[p])}function s(e){return!(!e||!e[f])}function u(e){return!(!e||!e[h])}function c(e){return s(e)||u(e)}function l(e){return!(!e||!e[d])}t(r,n),t(o,n),t(a,n),n.isIterable=i,n.isKeyed=s,n.isIndexed=u,n.isAssociative=c,n.isOrdered=l,n.Keyed=r,n.Indexed=o,n.Set=a;var p="@@__IMMUTABLE_ITERABLE__@@",f="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",v=5,g=1<<v,y=g-1,b={},_={value:!1},w={value:!1};function x(e){return e.value=!1,e}function E(e){e&&(e.value=!0)}function S(){}function C(e,t){t=t||0;for(var n=Math.max(0,e.length-t),r=new Array(n),o=0;o<n;o++)r[o]=e[o+t];return r}function A(e){return void 0===e.size&&(e.size=e.__iterate(O)),e.size}function k(e,t){if("number"!=typeof t){var n=t>>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?A(e)+t:t}function O(){return!0}function j(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function T(e,t){return N(e,t,0)}function I(e,t){return N(e,t,t)}function N(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var P=0,M=1,R=2,D="function"==typeof Symbol&&Symbol.iterator,L="@@iterator",B=D||L;function F(e){this.next=e}function U(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function q(){return{value:void 0,done:!0}}function z(e){return!!H(e)}function V(e){return e&&"function"==typeof e.next}function W(e){var t=H(e);return t&&t.call(e)}function H(e){var t=e&&(D&&e[D]||e[L]);if("function"==typeof t)return t}function $(e){return e&&"number"==typeof e.length}function J(e){return null==e?ie():i(e)?e.toSeq():ce(e)}function K(e){return null==e?ie().toKeyedSeq():i(e)?s(e)?e.toSeq():e.fromEntrySeq():se(e)}function Y(e){return null==e?ie():i(e)?s(e)?e.entrySeq():e.toIndexedSeq():ue(e)}function G(e){return(null==e?ie():i(e)?s(e)?e.entrySeq():e:ue(e)).toSetSeq()}F.prototype.toString=function(){return"[Iterator]"},F.KEYS=P,F.VALUES=M,F.ENTRIES=R,F.prototype.inspect=F.prototype.toSource=function(){return this.toString()},F.prototype[B]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return pe(this,e,t,!0)},J.prototype.__iterator=function(e,t){return fe(this,e,t,!0)},t(K,J),K.prototype.toKeyedSeq=function(){return this},t(Y,J),Y.of=function(){return Y(arguments)},Y.prototype.toIndexedSeq=function(){return this},Y.prototype.toString=function(){return this.__toString("Seq [","]")},Y.prototype.__iterate=function(e,t){return pe(this,e,t,!1)},Y.prototype.__iterator=function(e,t){return fe(this,e,t,!1)},t(G,J),G.of=function(){return G(arguments)},G.prototype.toSetSeq=function(){return this},J.isSeq=ae,J.Keyed=K,J.Set=G,J.Indexed=Y;var Z,X,Q,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function ae(e){return!(!e||!e[ee])}function ie(){return Z||(Z=new te([]))}function se(e){var t=Array.isArray(Line truncated
|
||||
//# sourceMappingURL=swagger-ui-bundle.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack://SwaggerUIBundle/webpack/universalModuleDefinition","webpack://SwaggerUIBundle/webpack/bootstrap","webpack://SwaggerUIBundle/./node_modules/react/react.js","webpack://SwaggerUIBundle/./node_modules/immutable/dist/immutable.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIBundle/./src/core/utils.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIBundle/./node_modules/prop-types/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIBundle/./node_modules/reselect/es/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUIBundle/./node_modules/fbjs/lib/invariant.js","webpack://SwaggerUIBundle/./src/core/window.js","webpack://SwaggerUIBundle/./node_modules/react-immutable-proptypes/dist/ImmutablePropTypes.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUIBundle/./node_modules/react-dom/lib/reactProdInvariant.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIBundle/./node_modules/fbjs/lib/warning.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/some.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUIBundle/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/object-assign/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIBundle/./node_modules/react-dom/lib/ReactDOMComponentTree.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIBundle/./node_modules/lodash/get.js","webpack://SwaggerUIBundle/./src/core/plugins/spec/actions.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/js-yaml/lib/js-yaml/type.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createForOfIteratorHelper.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/has.js","webpack://SwaggerUIBundle/./node_modules/lodash/isArray.js","webpack://SwaggerUIBundle/(webpack)/buildin/global.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectWithoutProperties.js","webpack://SwaggerUIBundle/./node_modules/fbjs/lib/ExecutionEnvironment.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3Line truncated
|
||||
@@ -1,3 +0,0 @@
|
||||
/*! For license information please see swagger-ui-es-bundle-core.js.LICENSE.txt */
|
||||
module.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=410)}([function(e,t){e.exports=require("react")},function(e,t){e.exports=require("immutable")},function(e,t,n){e.exports=n(449)},function(e,t,n){var r=n(191);e.exports=function(e,t,n){return t in e?r(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}},function(e,t,n){e.exports=n(452)},function(e,t,n){var r=n(191);function a(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),r(e,a.key,a)}}e.exports=function(e,t,n){return t&&a(e.prototype,t),n&&a(e,n),e}},function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,n){"use strict";(function(e){n.d(t,"t",(function(){return me})),n.d(t,"A",(function(){return ve})),n.d(t,"i",(function(){return ge})),n.d(t,"w",(function(){return ye})),n.d(t,"r",(function(){return be})),n.d(t,"u",(function(){return Ee})),n.d(t,"s",(function(){return xe})),n.d(t,"p",(function(){return Se})),n.d(t,"v",(function(){return we})),n.d(t,"y",(function(){return je})),n.d(t,"z",(function(){return Ce})),n.d(t,"K",(function(){return Oe})),n.d(t,"f",(function(){return _e})),n.d(t,"n",(function(){return Ae})),n.d(t,"h",(function(){return ke})),n.d(t,"E",(function(){return Ie})),n.d(t,"L",(function(){return Fe})),n.d(t,"o",(function(){return Ye})),n.d(t,"D",(function(){return Ke})),n.d(t,"a",(function(){return Ge})),n.d(t,"I",(function(){return Ze})),n.d(t,"b",(function(){return Xe})),n.d(t,"H",(function(){return Qe})),n.d(t,"G",(function(){return et})),n.d(t,"F",(function(){return tt})),n.d(t,"k",(function(){return nt})),n.d(t,"d",(function(){return rt})),n.d(t,"g",(function(){return at})),n.d(t,"m",(function(){return ot})),n.d(t,"l",(function(){return it})),n.d(t,"e",(function(){return ct})),n.d(t,"J",(function(){return st})),n.d(t,"x",(function(){return ut})),n.d(t,"B",(function(){return lt})),n.d(t,"C",(function(){return pt})),n.d(t,"j",(function(){return ft})),n.d(t,"c",(function(){return dt})),n.d(t,"q",(function(){return mt}));var r=n(82),a=n.n(r),o=n(130),i=n.n(o),c=n(50),s=n.n(c),u=n(13),l=n.n(u),p=n(30),f=n.n(p),d=n(17),h=n.n(d),m=(n(34),n(32)),v=n.n(m),g=(n(24),n(15),n(157)),y=n.n(g),b=n(21),E=n.n(b),x=n(22),S=n.n(x),w=(n(29),n(16)),j=n.n(w),C=n(12),O=n.n(C),_=n(18),A=n.n(_),k=n(2),I=n.n(k),P=n(45),T=n.n(P),N=n(71),R=n.n(N),M=n(4),D=n.n(M),q=n(14),L=n.n(q),B=n(20),U=n.n(B),V=n(1),z=n.n(V),F=n(375),J=n(376),W=n.n(J),H=n(207),$=n.n(H),Y=n(208),K=n.n(Y),G=n(158),Z=n.n(G),X=n(266),Q=n.n(X),ee=n(102),te=n.n(ee),ne=n(58),re=n.n(ne),ae=n(112),oe=n(26),ie=n(378),ce=n.n(ie),se=n(114),ue=n(379),le=n.n(ue),pe=n(380),fe=n.n(pe),de="default",he=function(e){return z.a.Iterable.isIterable(e)};function me(e){try{var t=JSON.parse(e);if(t&&"object"===U()(t))return t}catch(e){}return!1}function ve(e){return Ee(e)?he(e)?e.toJS():e:{}}function ge(e){var t,n;if(he(e))return e;if(e instanceof oe.a.File)return e;if(!Ee(e))return e;if(L()(e))return D()(n=z.a.Seq(e)).call(n,ge).toList();if(re()(R()(e))){var r,a=function(e){if(!re()(R()(e)))return e;var t,n={},r="_**[]",a={},o=T()(R()(e).call(e));try{for(o.s();!(t=o.n()).done;){var i=t.value;if(n[i[0]]||a[i[0]]&&a[i[0]].containsMultiple){var c,s,u,l;if(!a[i[0]])a[i[0]]={containsMultiple:!0,length:1},n[I()(u=I()(l="".concat(i[0])).call(l,r)).call(u,a[i[0]].length)]=n[i[0]],delete n[i[0]];a[i[0]].length+=1,n[I()(c=I()(s="".concat(i[0])).call(s,r)).call(c,a[i[0]].length)]=i[1]}else n[i[0]]=i[1]}}catch(e){o.e(e)}finally{o.f()}return n}(e);return D()(r=z.a.OrderedMap(a)).call(r,ge)}return D()(t=z.a.OrderedMap(e)).call(t,ge)}function ye(e){return L()(e)?e:[e]}function be(e){return"function"==typeof e}function Ee(e){return!!e&&"object"===U()(e)}function xe(e){return"function"==typeof e}function Se(e){return L()(e)}var we=K.a;function je(e,t){var n;return S()(n=j()(e)).call(n,(function(n,r){return n[r]=t(e[r],r),n}),{})}function Ce(e,t){var n;return S()(n=j()(e)).call(n,(function(n,r){var a=t(e[r],r);return a&&"object"===U()(a)&&E()(n,a),n}),{})}function Oe(e){return function(t){t.dispatch,t.getStateLine truncated
|
||||
//# sourceMappingURL=swagger-ui-es-bundle-core.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack://SwaggerUIBundle/webpack/bootstrap","webpack://SwaggerUIBundle/external \"react\"","webpack://SwaggerUIBundle/external \"immutable\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIBundle/./src/core/utils.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIBundle/external \"prop-types\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIBundle/external \"reselect\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUIBundle/./src/core/window.js","webpack://SwaggerUIBundle/external \"react-immutable-proptypes\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/some.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUIBundle/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIBundle/./node_modules/lodash/get.js","webpack://SwaggerUIBundle/./src/core/plugins/spec/actions.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createForOfIteratorHelper.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/has.js","webpack://SwaggerUIBundle/./node_modules/lodash/isArray.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectWithoutProperties.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js","webpack://SwaggerUIBundle/./src/core/plugins/oas3/actions.js","webpack://SwaggerUIBundle/./node_modules/lodash/isObject.js","webpack://SwaggerUIBundle/./node_modules/swagger-client/es/helpers.js","webpack://SwaggerUIBundle/./src/core/plugins/err/actions.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/object-define-property.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/array-method-uses-to-length.js","webpack://SwaggerUIBundle/external \"classnames\"","webpack://SwaggerUIBundle/./node_modules/lodash/isFunction.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/promise.js","webpack://SwagLine truncated
|
||||
@@ -1,3 +0,0 @@
|
||||
/*! For license information please see swagger-ui-es-bundle.js.LICENSE.txt */
|
||||
module.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=544)}([function(e,t,n){"use strict";e.exports=n(129)},function(e,t,n){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:J(e)}function r(e){return s(e)?e:K(e)}function o(e){return u(e)?e:Y(e)}function a(e){return i(e)&&!c(e)?e:G(e)}function i(e){return!(!e||!e[p])}function s(e){return!(!e||!e[f])}function u(e){return!(!e||!e[h])}function c(e){return s(e)||u(e)}function l(e){return!(!e||!e[d])}t(r,n),t(o,n),t(a,n),n.isIterable=i,n.isKeyed=s,n.isIndexed=u,n.isAssociative=c,n.isOrdered=l,n.Keyed=r,n.Indexed=o,n.Set=a;var p="@@__IMMUTABLE_ITERABLE__@@",f="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",v=5,g=1<<v,y=g-1,b={},_={value:!1},w={value:!1};function x(e){return e.value=!1,e}function E(e){e&&(e.value=!0)}function S(){}function C(e,t){t=t||0;for(var n=Math.max(0,e.length-t),r=new Array(n),o=0;o<n;o++)r[o]=e[o+t];return r}function A(e){return void 0===e.size&&(e.size=e.__iterate(O)),e.size}function k(e,t){if("number"!=typeof t){var n=t>>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?A(e)+t:t}function O(){return!0}function j(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function T(e,t){return N(e,t,0)}function I(e,t){return N(e,t,t)}function N(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var P=0,M=1,R=2,D="function"==typeof Symbol&&Symbol.iterator,L="@@iterator",B=D||L;function F(e){this.next=e}function U(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function q(){return{value:void 0,done:!0}}function z(e){return!!H(e)}function V(e){return e&&"function"==typeof e.next}function W(e){var t=H(e);return t&&t.call(e)}function H(e){var t=e&&(D&&e[D]||e[L]);if("function"==typeof t)return t}function $(e){return e&&"number"==typeof e.length}function J(e){return null==e?ie():i(e)?e.toSeq():ce(e)}function K(e){return null==e?ie().toKeyedSeq():i(e)?s(e)?e.toSeq():e.fromEntrySeq():se(e)}function Y(e){return null==e?ie():i(e)?s(e)?e.entrySeq():e.toIndexedSeq():ue(e)}function G(e){return(null==e?ie():i(e)?s(e)?e.entrySeq():e:ue(e)).toSetSeq()}F.prototype.toString=function(){return"[Iterator]"},F.KEYS=P,F.VALUES=M,F.ENTRIES=R,F.prototype.inspect=F.prototype.toSource=function(){return this.toString()},F.prototype[B]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return pe(this,e,t,!0)},J.prototype.__iterator=function(e,t){return fe(this,e,t,!0)},t(K,J),K.prototype.toKeyedSeq=function(){return this},t(Y,J),Y.of=function(){return Y(arguments)},Y.prototype.toIndexedSeq=function(){return this},Y.prototype.toString=function(){return this.__toString("Seq [","]")},Y.prototype.__iterate=function(e,t){return pe(this,e,t,!1)},Y.prototype.__iterator=function(e,t){return fe(this,e,t,!1)},t(G,J),G.of=function(){return G(arguments)},G.prototype.toSetSeq=function(){return this},J.isSeq=ae,J.Keyed=K,J.Set=G,J.Indexed=Y;var Z,X,Q,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function ae(e){return!(!e||!e[ee])}function ie(){return Z||(Z=new te([]))}function se(e){var t=Array.isArray(e)?new te(e).fromEntrySeq():V(e)?new oe(e).fromEntrySeq():z(e)?new re(e).fromEntrySeq():"object"==typeof e?new ne(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function ue(e){var t=le(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return tLine truncated
|
||||
//# sourceMappingURL=swagger-ui-es-bundle.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack://SwaggerUIBundle/webpack/bootstrap","webpack://SwaggerUIBundle/./node_modules/react/react.js","webpack://SwaggerUIBundle/./node_modules/immutable/dist/immutable.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIBundle/./src/core/utils.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIBundle/./node_modules/prop-types/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIBundle/./node_modules/reselect/es/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUIBundle/./node_modules/fbjs/lib/invariant.js","webpack://SwaggerUIBundle/./src/core/window.js","webpack://SwaggerUIBundle/./node_modules/react-immutable-proptypes/dist/ImmutablePropTypes.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUIBundle/./node_modules/react-dom/lib/reactProdInvariant.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIBundle/./node_modules/fbjs/lib/warning.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/some.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUIBundle/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/object-assign/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIBundle/./node_modules/react-dom/lib/ReactDOMComponentTree.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIBundle/./node_modules/lodash/get.js","webpack://SwaggerUIBundle/./src/core/plugins/spec/actions.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/js-yaml/lib/js-yaml/type.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createForOfIteratorHelper.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/has.js","webpack://SwaggerUIBundle/./node_modules/lodash/isArray.js","webpack://SwaggerUIBundle/(webpack)/buildin/global.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectWithoutProperties.js","webpack://SwaggerUIBundle/./node_modules/fbjs/lib/ExecutionEnvironment.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js","webpack://SwaggerUIBundle/.Line truncated
|
||||
@@ -1,3 +0,0 @@
|
||||
/*! For license information please see swagger-ui-standalone-preset.js.LICENSE.txt */
|
||||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(function(){try{return require("esprima")}catch(t){}}()):"function"==typeof define&&define.amd?define(["esprima"],e):"object"==typeof exports?exports.SwaggerUIStandalonePreset=e(function(){try{return require("esprima")}catch(t){}}()):t.SwaggerUIStandalonePreset=e(t.esprima)}(this,(function(t){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="/dist",n(n.s=241)}([function(t,e,n){t.exports=function(){"use strict";var t=Array.prototype.slice;function e(t,e){e&&(t.prototype=Object.create(e.prototype)),t.prototype.constructor=t}function n(t){return u(t)?t:J(t)}function r(t){return s(t)?t:Z(t)}function i(t){return a(t)?t:V(t)}function o(t){return u(t)&&!c(t)?t:X(t)}function u(t){return!(!t||!t[l])}function s(t){return!(!t||!t[p])}function a(t){return!(!t||!t[h])}function c(t){return s(t)||a(t)}function f(t){return!(!t||!t[d])}e(r,n),e(i,n),e(o,n),n.isIterable=u,n.isKeyed=s,n.isIndexed=a,n.isAssociative=c,n.isOrdered=f,n.Keyed=r,n.Indexed=i,n.Set=o;var l="@@__IMMUTABLE_ITERABLE__@@",p="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",y="delete",v=5,g=1<<v,w=g-1,M={},m={value:!1},_={value:!1};function L(t){return t.value=!1,t}function x(t){t&&(t.value=!0)}function b(){}function j(t,e){e=e||0;for(var n=Math.max(0,t.length-e),r=new Array(n),i=0;i<n;i++)r[i]=t[i+e];return r}function N(t){return void 0===t.size&&(t.size=t.__iterate(D)),t.size}function S(t,e){if("number"!=typeof e){var n=e>>>0;if(""+n!==e||4294967295===n)return NaN;e=n}return e<0?N(t)+e:e}function D(){return!0}function I(t,e,n){return(0===t||void 0!==n&&t<=-n)&&(void 0===e||void 0!==n&&e>=n)}function A(t,e){return C(t,e,0)}function E(t,e){return C(t,e,e)}function C(t,e,n){return void 0===t?n:t<0?Math.max(0,e+t):void 0===e?t:Math.min(e,t)}var O=0,T=1,z=2,k="function"==typeof Symbol&&Symbol.iterator,U="@@iterator",Y=k||U;function P(t){this.next=t}function R(t,e,n,r){var i=0===t?e:1===t?n:[e,n];return r?r.value=i:r={value:i,done:!1},r}function Q(){return{value:void 0,done:!0}}function F(t){return!!W(t)}function B(t){return t&&"function"==typeof t.next}function G(t){var e=W(t);return e&&e.call(t)}function W(t){var e=t&&(k&&t[k]||t[U]);if("function"==typeof e)return e}function q(t){return t&&"number"==typeof t.length}function J(t){return null==t?ut():u(t)?t.toSeq():ct(t)}function Z(t){return null==t?ut().toKeyedSeq():u(t)?s(t)?t.toSeq():t.fromEntrySeq():st(t)}function V(t){return null==t?ut():u(t)?s(t)?t.entrySeq():t.toIndexedSeq():at(t)}function X(t){return(null==t?ut():u(t)?s(t)?t.entrySeq():t:at(t)).toSetSeq()}P.prototype.toString=function(){return"[Iterator]"},P.KEYS=O,P.VALUES=T,P.ENTRIES=z,P.prototype.inspect=P.prototype.toSource=function(){return this.toString()},P.prototype[Y]=function(){return this},e(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(t,e){return lt(this,t,e,!0)},J.prototype.__iterator=function(t,e){return pt(this,t,e,!0)},e(Z,J),Z.prototype.toKeyedSeq=function(){return this},e(V,J),V.of=function(){return V(arguments)},V.prototype.toIndexedSeq=function(){return this},V.prototype.toString=function(){return this.__toString("Seq [","]")},V.prototype.__iterate=function(t,e){return lt(this,t,e,!1)},V.prototype.__iterator=function(t,e){return pt(this,t,e,!1)},e(X,J),X.of=function(){return X(arguments)},X.prototype.toSetSeq=function(){return this},J.isSeq=ot,J.Keyed=Z,J.Set=X,J.Indexed=V;var H,K,$,tt="@@__IMMUTABLE_SEQ__@@";function et(t){this._array=t,this.size=t.length}function nt(t){var e=Object.keys(t);this._object=t,this._keys=e,this.size=e.length}function rt(t){this._iterable=t,this.size=t.length||t.size}function it(t){this._iterator=t,this._iteratorCache=[]}function ot(t){return!(!t||!t[tt])}function ut(){return H||(H=new et([]))}function st(t){var e=Array.isArray(t)?new et(t).fromEntrySeq()Line truncated
|
||||
//# sourceMappingURL=swagger-ui-standalone-preset.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack://SwaggerUIStandalonePreset/webpack/universalModuleDefinition","webpack://SwaggerUIStandalonePreset/webpack/bootstrap","webpack://SwaggerUIStandalonePreset/./node_modules/immutable/dist/immutable.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIStandalonePreset/./node_modules/react/react.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIStandalonePreset/./node_modules/js-yaml/lib/js-yaml/type.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/inherits/inherits_browser.js","webpack://SwaggerUIStandalonePreset/./node_modules/safe-buffer/index.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/has.js","webpack://SwaggerUIStandalonePreset/./src/core/utils.js","webpack://SwaggerUIStandalonePreset/(webpack)/buildin/global.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/to-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/isArray.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/create-non-enumerable-property.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/array-method-uses-to-length.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/_root.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/es5-ext/object/valid-callable.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/to-indexed-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/object-define-property.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/to-length.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/iterators.js","webpack://SwaggerUIStandalonePreset/./node_modules/process/browser.js","webpack://SwaggerUIStandalonePreset/./node_modules/readable-stream/lib/_stream_duplex.js","webpack://SwaggerUIStandalonePreset/./node_modules/es5-ext/object/is-value.js","webpack://SwaggerUIStandalonePreset/./node_modules/es5-ext/number/to-pos-integer.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/require-object-coercible.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/classof.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/modules/web.dom-collections.iterator.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/array-iteration.js","webpack://SwaggerUIStandalonePreset/./node_modules/react/lib/ReactElement.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/_getNative.js","webpack://SwaggerUIStandalonePreset/./node_modules/es5-ext/object/valid-value.js","webpack://SwaggerUIStandalonePreset/./node_modules/sha.js/hash.js","webpack://SwaggerUIStandalonePreset/./node_modules/js-yaml/lib/js-yaml/common.js","webpack://SwaggerUIStandalonePreset/./node_modules/js-yaml/lib/js-yaml/schema.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/eq.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/isEmpty.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/create-property-descriptor.js","webpack://SwaggerUIStandalonePreset/./node_moduLine truncated
|
||||
-11057
File diff suppressed because it is too large.
Load diff
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack:///./src/style/main.scss","webpack:///./node_modules/tachyons-sass/scss/_normalize.scss","webpack:///./src/style/_type.scss","webpack:///./node_modules/tachyons-sass/scss/_debug-children.scss","webpack:///./node_modules/tachyons-sass/scss/_debug-grid.scss","webpack:///./node_modules/tachyons-sass/scss/_box-sizing.scss","webpack:///./node_modules/tachyons-sass/scss/_aspect-ratios.scss","webpack:///./node_modules/tachyons-sass/scss/_images.scss","webpack:///./node_modules/tachyons-sass/scss/_background-size.scss","webpack:///./node_modules/tachyons-sass/scss/_background-position.scss","webpack:///./node_modules/tachyons-sass/scss/_outlines.scss","webpack:///./node_modules/tachyons-sass/scss/_borders.scss","webpack:///./node_modules/tachyons-sass/scss/_border-colors.scss","webpack:///./node_modules/tachyons-sass/scss/_border-radius.scss","webpack:///./node_modules/tachyons-sass/scss/_border-style.scss","webpack:///./node_modules/tachyons-sass/scss/_border-widths.scss","webpack:///./node_modules/tachyons-sass/scss/_box-shadow.scss","webpack:///./node_modules/tachyons-sass/scss/_code.scss","webpack:///./node_modules/tachyons-sass/scss/_coordinates.scss","webpack:///./node_modules/tachyons-sass/scss/_clears.scss","webpack:///./node_modules/tachyons-sass/scss/_flexbox.scss","webpack:///./node_modules/tachyons-sass/scss/_display.scss","webpack:///./node_modules/tachyons-sass/scss/_floats.scss","webpack:///./node_modules/tachyons-sass/scss/_font-family.scss","webpack:///./node_modules/tachyons-sass/scss/_font-style.scss","webpack:///./node_modules/tachyons-sass/scss/_font-weight.scss","webpack:///./node_modules/tachyons-sass/scss/_forms.scss","webpack:///./node_modules/tachyons-sass/scss/_heights.scss","webpack:///./node_modules/tachyons-sass/scss/_letter-spacing.scss","webpack:///./node_modules/tachyons-sass/scss/_line-height.scss","webpack:///./node_modules/tachyons-sass/scss/_links.scss","webpack:///./node_modules/tachyons-sass/scss/_lists.scss","webpack:///./node_modules/tachyons-sass/scss/_max-widths.scss","webpack:///./node_modules/tachyons-sass/scss/_widths.scss","webpack:///./node_modules/tachyons-sass/scss/_overflow.scss","webpack:///./node_modules/tachyons-sass/scss/_position.scss","webpack:///./node_modules/tachyons-sass/scss/_opacity.scss","webpack:///./node_modules/tachyons-sass/scss/_rotations.scss","webpack:///./node_modules/tachyons-sass/scss/_skins.scss","webpack:///./node_modules/tachyons-sass/scss/_skins-pseudo.scss","webpack:///./node_modules/tachyons-sass/scss/_spacing.scss","webpack:///./node_modules/tachyons-sass/scss/_variables.scss","webpack:///./node_modules/tachyons-sass/scss/_negative-margins.scss","webpack:///./node_modules/tachyons-sass/scss/_tables.scss","webpack:///./node_modules/tachyons-sass/scss/_text-decoration.scss","webpack:///./node_modules/tachyons-sass/scss/_text-align.scss","webpack:///./node_modules/tachyons-sass/scss/_text-transform.scss","webpack:///./node_modules/tachyons-sass/scss/_type-scale.scss","webpack:///./node_modules/tachyons-sass/scss/_typography.scss","webpack:///./node_modules/tachyons-sass/scss/_utilities.scss","webpack:///./node_modules/tachyons-sass/scss/_visibility.scss","webpack:///./node_modules/tachyons-sass/scss/_white-space.scss","webpack:///./node_modules/tachyons-sass/scss/_vertical-align.scss","webpack:///./node_modules/tachyons-sass/scss/_hovers.scss","webpack:///./node_modules/tachyons-sass/scss/_z-index.scss","webpack:///./node_modules/tachyons-sass/scss/_nested.scss","webpack:///./src/style/_layout.scss","webpack:///./src/style/_variables.scss","webpack:///./src/style/_buttons.scss","webpack:///./src/style/_mixins.scss","webpack:///./src/style/_form.scss","webpack:///./src/style/_modal.scss","webpack:///./src/style/_models.scss","webpack:///./src/style/_servers.scss","webpack:///./src/style/_table.scss","webpack:///./src/style/_topbar.scss","webpack:///./src/style/_information.scss","webpack:///./src/style/_authorize.scss","webpack:///./src/style/_errors.scss","webpack:///./src/style/_split-pane-mode.scss","webpack:///./src/style/_markdown.scss"],"names":[],"mappings":"AAAA;ECOA,4ECLI,sBAAuB,CAEvB,aFmhCJ,CAvhCA,iBCmBE,gBAAiB,CACjB,yBAA0B,CAC1B,6BD2gCF,CAhiCA,iBCgCE,QDogCF,CApiCA,gHC6CE,aDggCF,CA7iCA,eCsDE,aAAc,CACd,cD2/BF,CAljCA,2DCqEE,aDo/BF,CAzjCA,mBC6EE,eDg/BF,CA7jCA,eCsFE,sBAAuB,CACvB,QAAS,CACT,gBD8+BF,CAtkCA,gBCiGE,+BAAiC,CACjC,aD2+BF,CA7kCA,cC8GE,4BAA6B,CAC7B,oCDq+BF,CAplCA,wBCwHE,kBAAmB,CACnB,yBAA0B,CAC1B,wCAAiC,CAAjC,gCDm+BF,CA7lCA,iCCmIE,mBAAoB,CASpB,kBDs9BF,CAlmCA,kDCuJE,+BAAiC,CACjC,aDw9BF,CAhnCA,gBCgKE,iBDo9BF,CApnCA,iBCwKE,qBAAsB,CACtB,UDg9BF,CAznCA,kBCiLE,aD48BF,CA7nCA,gCC2LE,aAAc,CACd,aAAc,CACd,iBAAkB,CAClB,uBDu8BF,CAroCA,gBCkME,aDu8BF,CAzoCA,gBCsME,SDu8BF,CA7oCA,oCCkNE,oBDg8BF,CAlpCA,kCC0NE,YAAa,CACb,QD47BF,CAvpCA,gBCmOE,iBDw7BF,CA3pCA,2BC2OE,eDo7BF,CA/pCA,kGC2PE,sBAAuB,CACvB,cAAe,CACf,gBAAiB,CACjB,QDg7BF,CA9qCA,qCCwQE,gBD46BF,CAprCA,sCCkRE,mBDw6BF,CA1rCA,qGC+RE,yBDm6BF,CAlsCA,wKC0SE,iBAAkB,CACLine truncated
|
||||
@@ -1,3 +0,0 @@
|
||||
/*! For license information please see swagger-ui.js.LICENSE.txt */
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUICore=t():e.SwaggerUICore=t()}(this,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=410)}([function(e,t){e.exports=require("react")},function(e,t){e.exports=require("immutable")},function(e,t,n){e.exports=n(449)},function(e,t,n){var r=n(191);e.exports=function(e,t,n){return t in e?r(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}},function(e,t,n){e.exports=n(452)},function(e,t,n){var r=n(191);function a(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),r(e,a.key,a)}}e.exports=function(e,t,n){return t&&a(e.prototype,t),n&&a(e,n),e}},function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,n){"use strict";(function(e){n.d(t,"t",(function(){return me})),n.d(t,"A",(function(){return ve})),n.d(t,"i",(function(){return ge})),n.d(t,"w",(function(){return ye})),n.d(t,"r",(function(){return be})),n.d(t,"u",(function(){return Ee})),n.d(t,"s",(function(){return xe})),n.d(t,"p",(function(){return Se})),n.d(t,"v",(function(){return we})),n.d(t,"y",(function(){return je})),n.d(t,"z",(function(){return Ce})),n.d(t,"K",(function(){return Oe})),n.d(t,"f",(function(){return _e})),n.d(t,"n",(function(){return Ae})),n.d(t,"h",(function(){return ke})),n.d(t,"E",(function(){return Ie})),n.d(t,"L",(function(){return Fe})),n.d(t,"o",(function(){return Ye})),n.d(t,"D",(function(){return Ke})),n.d(t,"a",(function(){return Ge})),n.d(t,"I",(function(){return Ze})),n.d(t,"b",(function(){return Xe})),n.d(t,"H",(function(){return Qe})),n.d(t,"G",(function(){return et})),n.d(t,"F",(function(){return tt})),n.d(t,"k",(function(){return nt})),n.d(t,"d",(function(){return rt})),n.d(t,"g",(function(){return at})),n.d(t,"m",(function(){return ot})),n.d(t,"l",(function(){return it})),n.d(t,"e",(function(){return ct})),n.d(t,"J",(function(){return st})),n.d(t,"x",(function(){return ut})),n.d(t,"B",(function(){return lt})),n.d(t,"C",(function(){return pt})),n.d(t,"j",(function(){return ft})),n.d(t,"c",(function(){return dt})),n.d(t,"q",(function(){return mt}));var r=n(82),a=n.n(r),o=n(130),i=n.n(o),c=n(50),s=n.n(c),u=n(13),l=n.n(u),p=n(30),f=n.n(p),d=n(17),h=n.n(d),m=(n(34),n(32)),v=n.n(m),g=(n(24),n(15),n(157)),y=n.n(g),b=n(21),E=n.n(b),x=n(22),S=n.n(x),w=(n(29),n(16)),j=n.n(w),C=n(12),O=n.n(C),_=n(18),A=n.n(_),k=n(2),I=n.n(k),P=n(45),T=n.n(P),N=n(71),R=n.n(N),M=n(4),D=n.n(M),q=n(14),L=n.n(q),B=n(20),U=n.n(B),V=n(1),z=n.n(V),F=n(375),J=n(376),W=n.n(J),H=n(207),$=n.n(H),Y=n(208),K=n.n(Y),G=n(158),Z=n.n(G),X=n(266),Q=n.n(X),ee=n(102),te=n.n(ee),ne=n(58),re=n.n(ne),ae=n(112),oe=n(26),ie=n(378),ce=n.n(ie),se=n(114),ue=n(379),le=n.n(ue),pe=n(380),fe=n.n(pe),de="default",he=function(e){return z.a.Iterable.isIterable(e)};function me(e){try{var t=JSON.parse(e);if(t&&"object"===U()(t))return t}catch(e){}return!1}function ve(e){return Ee(e)?he(e)?e.toJS():e:{}}function ge(e){var t,n;if(he(e))return e;if(e instanceof oe.a.File)return e;if(!Ee(e))return e;if(L()(e))return D()(n=z.a.Seq(e)).call(n,ge).toList();if(re()(R()(e))){var r,a=function(e){if(!re()(R()(e)))return e;var t,n={},r="_**[]",a={},o=T()(R()(e).call(e));try{for(o.s();!(t=o.n()).done;){var i=t.value;if(n[i[0]]||a[i[0]]&&a[i[0]].containsMultiple){var c,s,u,l;if(!a[i[0]])a[i[0]]={containsMultiple:!0,length:1},n[I()(u=I()(l="".concat(i[0])).call(l,r)).call(u,a[i[0]].length)]=n[i[0]],delete n[i[0]];a[i[0]].length+=1,n[I()(c=I()(s="".concat(i[0])).call(s,r)).call(c,a[i[0]].length)]=i[1]}else n[i[0]]=i[1]}}catch(e){o.e(e)}finally{o.f()}return n}(e);return D()(r=z.a.OrderedMap(a)).call(r,ge)}return D()(t=z.a.OrderedMap(e)).call(t,ge)}function ye(e){return L()(e)?e:[e]}function be(e){return"function"==typeof e}function Ee(e){return!!e&&"object"===U()(e)}function xe(e){return"function"==typeof e}function Se(e){return L()(e)}var we=K.a;function je(e,t){var n;return S()(n=j()(e)).call(n,(function(n,Line truncated
|
||||
//# sourceMappingURL=swagger-ui.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack://SwaggerUICore/webpack/universalModuleDefinition","webpack://SwaggerUICore/webpack/bootstrap","webpack://SwaggerUICore/external \"react\"","webpack://SwaggerUICore/external \"immutable\"","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUICore/./src/core/utils.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUICore/external \"prop-types\"","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUICore/external \"reselect\"","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUICore/./src/core/window.js","webpack://SwaggerUICore/external \"react-immutable-proptypes\"","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/some.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUICore/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUICore/./node_modules/lodash/get.js","webpack://SwaggerUICore/./src/core/plugins/spec/actions.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/createForOfIteratorHelper.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/has.js","webpack://SwaggerUICore/./node_modules/lodash/isArray.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/objectWithoutProperties.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js","webpack://SwaggerUICore/./src/core/plugins/oas3/actions.js","webpack://SwaggerUICore/./node_modules/lodash/isObject.js","webpack://SwaggerUICore/./node_modules/swagger-client/es/helpers.js","webpack://SwaggerUICore/./src/core/plugins/err/actions.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/object-define-property.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/array-method-uses-to-length.js","webpack://SwaggerUICore/external \"classnames\"","webpack://SwaggerUICore/./node_modules/lodash/isFunction.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/promise.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/to-indexed-objLine truncated
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
@@ -1,40 +0,0 @@
|
||||
from pprint import pprint
|
||||
|
||||
import requests
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def whatsapp_echo():
|
||||
return "WhatsApp HTTP API Echo server is ready!"
|
||||
|
||||
|
||||
@app.route("/message", methods=["GET", "POST"])
|
||||
def whatsapp_webhook():
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return "WhatsApp HTTP API Echo server is ready!"
|
||||
pprint(data)
|
||||
# The text
|
||||
text = data["body"]
|
||||
# Number in format 791111111@c.us
|
||||
from_ = data["from"]
|
||||
|
||||
if data.get("clientUrl", None):
|
||||
# Download file and set text to path
|
||||
client_url = data["clientUrl"]
|
||||
filename = client_url.split("/")[-1]
|
||||
path = "/tmp/" + filename
|
||||
r = requests.get(client_url)
|
||||
open(path, "wb").write(r.content)
|
||||
text = f"We have downloaded file here: {path}"
|
||||
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendText", json={"chatId": from_, "text": text}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return "OK"
|
||||
@@ -0,0 +1,128 @@
|
||||
# WAHA (WhatsApp HTTP API) Python examples
|
||||
|
||||
## Installation
|
||||
|
||||
We assume that you have installed software:
|
||||
|
||||
1. Python 3
|
||||
2. Docker
|
||||
|
||||
### Download and start image
|
||||
|
||||
First of all, you must run WhatsApp HTTP API locally (which under the hood it
|
||||
runs real WhatsApp Web instance and expose HTTP API for interaction).
|
||||
|
||||
Here are the steps from
|
||||
[Quick Start](https://waha.devlike.pro/docs/overview/quick-start/):
|
||||
|
||||
Download and start WhatsApp HTTP API docker container
|
||||
|
||||
```bash
|
||||
# Download the image
|
||||
docker pull devlikeapro/waha
|
||||
# Run the docker container
|
||||
docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bot -e "WHATSAPP_HOOK_EVENTS=*" --name waha devlikeapro/waha
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
```
|
||||
|
||||
#### Download image - ARM
|
||||
|
||||
If you're using ARM (like Apple Silicon, Apple M1, etc.) - use following
|
||||
commands to download the image
|
||||
|
||||
 For Core version the command is
|
||||
|
||||
```bash
|
||||
# Download the image
|
||||
docker pull devlikeapro/waha:arm
|
||||
# Rename it, so you can use devlikeapro/waha image in other place
|
||||
docker tag devlikeapro/waha:arm devlikeapro/waha
|
||||
# Run the docker container
|
||||
docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bot -e "WHATSAPP_HOOK_EVENTS=*" --name waha devlikeapro/waha
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
```
|
||||
|
||||
### Start session and scan QR
|
||||
|
||||
2. Open Swagger API in the browser http://localhost:3000/
|
||||
3. Start session and scan QR code in swagger
|
||||
1. Find `POST /api/session/start` request press **Try it out** and
|
||||
**Execute** it with `default` session name
|
||||
2. Find `GET /api/screenshot` and execute it - it must show QR code
|
||||
3. Scan QR code on your mobile WhatsApp application (that installed on your
|
||||
phone)
|
||||
4. Execute `GET /api/screenshot` once again - it must show the screenshot
|
||||
from WhatsApp Web.
|
||||
4. Send test text message - find `POST /api/sendText` and execute it with
|
||||
payload (change `12132132130` in the `chatId` to phone number that is
|
||||
registered in WhatsApp).
|
||||
|
||||
```json
|
||||
{
|
||||
"chatId": "12132132130@c.us",
|
||||
"text": "Hi there!",
|
||||
"session": "default"
|
||||
}
|
||||
```
|
||||
|
||||
If you see **Hi there!** message then you're ready to run bots!
|
||||
|
||||
## WhatsApp Echo Bot
|
||||
|
||||
The WhatsApp Echo Bot is a sample flask webhook server application that echoes
|
||||
back to you whatever you send it. It can serve as a basic reference for how to
|
||||
set up webhooks and reply to incoming messages.
|
||||
|
||||
```bash
|
||||
# Clone the git repository with example
|
||||
git clone https://github.com/devlikeapro/waha.git
|
||||
# Open python example folder
|
||||
cd waha/examples/python
|
||||
# Install requirements
|
||||
python -mpip install -r requirements.txt
|
||||
# Run the bot
|
||||
FLASK_APP=whatsapp_echo_bot.py flask run
|
||||
```
|
||||
|
||||
Open http://127.0.0.1:5000/bot - if you see **WhatsApp Echo Bot is ready!** then
|
||||
the bot is ready to receive message!
|
||||
|
||||
Send message to the WhatsApp (that you used to scan QR code) and it'll echo text
|
||||
back to you!
|
||||
|
||||
## WhatsApp Download Files Bot
|
||||
|
||||
The WhatsApp Download Image Bot downloads all files people send to your WhatsApp
|
||||
and log the path for the file.
|
||||
|
||||
**The bot works only with WAHA Plus version** available with donations. Visit
|
||||
[read more about difference between Core and Plus versions](https://waha.devlike.pro/docs/how-to/plus-version/).
|
||||
|
||||
Download **WAHA Plus** version:
|
||||
|
||||
```bash
|
||||
# Download the image
|
||||
docker pull devlikeapro/waha-plus
|
||||
# Run the docker container
|
||||
docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bot -e "WHATSAPP_HOOK_EVENTS=*" --name waha devlikeapro/waha-plus
|
||||
```
|
||||
|
||||
Run the WhatsApp Download Files bot:
|
||||
|
||||
```bash
|
||||
# Clone the git repository with example
|
||||
git clone https://github.com/devlikeapro/waha.git
|
||||
# Open python example folder
|
||||
cd waha/examples/python
|
||||
# Install requirements
|
||||
python -mpip install -r requirements.txt
|
||||
# Run the bot
|
||||
FLASK_APP=whatsapp_download_files_bot.py flask run
|
||||
```
|
||||
|
||||
Open http://127.0.0.1:5000/bot - if you see **WhatsApp Download Files Bot!**
|
||||
then the bot is ready to receive message with files!
|
||||
@@ -0,0 +1,3 @@
|
||||
flask~=1.0
|
||||
requests~=2.0
|
||||
markupsafe==2.0.1
|
||||
@@ -0,0 +1,84 @@
|
||||
from os.path import abspath
|
||||
from pprint import pprint
|
||||
|
||||
import requests
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def send_message(chat_id, text):
|
||||
"""
|
||||
Send message to chat_id.
|
||||
:param chat_id: Phone number + "@c.us" suffix - 1231231231@c.us
|
||||
:param text: Message for the recipient
|
||||
"""
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendText",
|
||||
json={
|
||||
"chatId": chat_id,
|
||||
"text": text,
|
||||
"session": "default",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def send_seen(chat_id, message_id, participant):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendSeen",
|
||||
json={
|
||||
"session": "default",
|
||||
"chatId": chat_id,
|
||||
"messageId": message_id,
|
||||
"participant": participant,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
@app.route("/")
|
||||
def whatsapp_echo():
|
||||
return "WhatsApp Download Files Bot is ready!"
|
||||
|
||||
|
||||
@app.route("/bot", methods=["GET", "POST"])
|
||||
def whatsapp_webhook():
|
||||
if request.method == "GET":
|
||||
return "WhatsApp Download Files Bot is ready!"
|
||||
|
||||
data = request.get_json()
|
||||
pprint(data)
|
||||
if data["event"] != "message":
|
||||
# We can't process other event yet
|
||||
return f"Unknown event {data['event']}"
|
||||
|
||||
payload = data["payload"]
|
||||
# Ignore messages without files
|
||||
if not payload.get("mediaUrl", None):
|
||||
return "No files in the message"
|
||||
|
||||
# Number in format 791111111@c.us
|
||||
chat_id = payload["from"]
|
||||
# Message ID - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
message_id = payload['id']
|
||||
# For groups - who sent the message
|
||||
participant = payload.get('participant')
|
||||
# IMPORTANT - Always send seen before sending new message
|
||||
send_seen(chat_id=chat_id, message_id=message_id, participant=participant)
|
||||
|
||||
# Download the file and download it to the current folder
|
||||
client_url = payload["mediaUrl"]
|
||||
filename = client_url.split("/")[-1]
|
||||
path = abspath("./" + filename)
|
||||
r = requests.get(client_url)
|
||||
with open(path, "wb") as f:
|
||||
f.write(r.content)
|
||||
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
text = f"We have downloaded file here: {path}"
|
||||
print(text)
|
||||
send_message(chat_id=chat_id, text=text)
|
||||
|
||||
# Send OK back
|
||||
return "OK"
|
||||
@@ -0,0 +1,123 @@
|
||||
import random
|
||||
from pprint import pprint
|
||||
from time import sleep
|
||||
|
||||
import requests
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def send_message(chat_id, text):
|
||||
"""
|
||||
Send message to chat_id.
|
||||
:param chat_id: Phone number + "@c.us" suffix - 1231231231@c.us
|
||||
:param text: Message for the recipient
|
||||
"""
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendText",
|
||||
json={
|
||||
"chatId": chat_id,
|
||||
"text": text,
|
||||
"session": "default",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def reply(chat_id, message_id, text):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/reply",
|
||||
json={
|
||||
"chatId": chat_id,
|
||||
"text": text,
|
||||
"reply_to": message_id,
|
||||
"session": "default",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def send_seen(chat_id, message_id, participant):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendSeen",
|
||||
json={
|
||||
"session": "default",
|
||||
"chatId": chat_id,
|
||||
"messageId": message_id,
|
||||
"participant": participant,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def start_typing(chat_id):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/startTyping",
|
||||
json={
|
||||
"session": "default",
|
||||
"chatId": chat_id,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def stop_typing(chat_id):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/stopTyping",
|
||||
json={
|
||||
"session": "default",
|
||||
"chatId": chat_id,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def typing(chat_id, seconds):
|
||||
start_typing(chat_id=chat_id)
|
||||
sleep(seconds)
|
||||
stop_typing(chat_id=chat_id)
|
||||
|
||||
@app.route("/")
|
||||
def whatsapp_echo():
|
||||
return "WhatsApp Echo Bot is ready!"
|
||||
|
||||
|
||||
@app.route("/bot", methods=["GET", "POST"])
|
||||
def whatsapp_webhook():
|
||||
if request.method == "GET":
|
||||
return "WhatsApp Echo Bot is ready!"
|
||||
|
||||
data = request.get_json()
|
||||
pprint(data)
|
||||
if data["event"] != "message":
|
||||
# We can't process other event yet
|
||||
return f"Unknown event {data['event']}"
|
||||
|
||||
# Payload that we've got
|
||||
payload = data["payload"]
|
||||
# The text
|
||||
text = payload.get("body")
|
||||
if not text:
|
||||
# We can't process non-text messages yet
|
||||
print("No text in message")
|
||||
print(payload)
|
||||
return "OK"
|
||||
# Number in format 1231231231@c.us or @g.us for group
|
||||
chat_id = payload["from"]
|
||||
# Message ID - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
message_id = payload['id']
|
||||
# For groups - who sent the message
|
||||
participant = payload.get('participant')
|
||||
# IMPORTANT - Always send seen before sending new message
|
||||
send_seen(chat_id=chat_id, message_id=message_id, participant=participant)
|
||||
|
||||
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
typing(chat_id=chat_id, seconds=random.random() * 3)
|
||||
send_message(chat_id=chat_id, text=text)
|
||||
|
||||
# OR reply on the message
|
||||
typing(chat_id=chat_id, seconds=random.random() * 3)
|
||||
reply(chat_id=chat_id, message_id=message_id, text=text)
|
||||
|
||||
# Send OK back
|
||||
return "OK"
|
||||
@@ -1,2 +0,0 @@
|
||||
flask
|
||||
requests
|
||||
Binary file not shown.
+4
-4
@@ -2,8 +2,8 @@
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"plugins": [
|
||||
"@nestjs/swagger/plugin"
|
||||
]
|
||||
"plugins": ["@nestjs/swagger"],
|
||||
"assets": ["dashboard/**", "core/engines/webjs/*", "plus/engines/webjs/*"],
|
||||
"watchAssets": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
-31599
File diff suppressed because it is too large.
Load diff
+36
-11
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "whatsapp-http-api",
|
||||
"name": "waha",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
@@ -12,9 +12,9 @@
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node --unhandled-rejections=none dist/main",
|
||||
"start:prod": "node dist/main",
|
||||
"start:prod-exit": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix --max-warnings=0",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
@@ -22,21 +22,44 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adiwajshing/baileys": "github:WhiskeySockets/Baileys",
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"@nestjs/axios": "^3.0.2",
|
||||
"@nestjs/common": "^9.0.9",
|
||||
"@nestjs/config": "^0.5.0",
|
||||
"@nestjs/core": "^9.0.9",
|
||||
"@nestjs/passport": "^9.0.0",
|
||||
"@nestjs/platform-express": "^9.0.9",
|
||||
"@nestjs/serve-static": "^2.1.3",
|
||||
"@nestjs/swagger": "^6.0.5",
|
||||
"@nestjs/swagger": "^7.1.11",
|
||||
"@nestjs/terminus": "^10.2.3",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/ws": "^8.5.4",
|
||||
"async-lock": "^1.4.1",
|
||||
"check-disk-space": "^3.4.0",
|
||||
"class-validator": "^0.12.2",
|
||||
"del": "^6.0.0",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"file-type": "16.5.4",
|
||||
"https-proxy-agent": "^7.0.0",
|
||||
"libphonenumber-js": "^1.10.36",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.27",
|
||||
"mongodb": "^6.3.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-headerapikey": "^1.2.2",
|
||||
"puppeteer": "^21.0.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"request": "^2.88.2",
|
||||
"requestretry": "^4.1.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.1.0",
|
||||
"swagger-ui-express": "^4.1.4",
|
||||
"venom-bot": "^4.3.0"
|
||||
"venom-bot": "5.0.1",
|
||||
"whatsapp-web.js": "https://github.com/devlikeapro/whatsapp-web.js#main-fork-update"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
@@ -44,21 +67,22 @@
|
||||
"@nestjs/testing": "^9.0.9",
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "26.0.10",
|
||||
"@types/node": "^13.9.1",
|
||||
"@types/node": "^18.14.2",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "3.9.1",
|
||||
"@typescript-eslint/parser": "3.9.1",
|
||||
"eslint": "7.7.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"jest": "26.4.2",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^1.19.1",
|
||||
"supertest": "^4.0.2",
|
||||
"ts-jest": "26.2.0",
|
||||
"ts-jest": "^29.1.3",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"typescript": "4.7.4"
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
@@ -67,11 +91,12 @@
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".spec.ts$",
|
||||
"testRegex": ".test.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@3.6.3"
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
StreamableFile,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { Base64File } from '../structures/files.dto';
|
||||
|
||||
@Injectable()
|
||||
/**
|
||||
* Interceptor to convert Buffer response to Base64File or StreamableFile
|
||||
* based on Accept header.
|
||||
* It should go together with @ApiFileAcceptHeader in controller method
|
||||
* So we have proper Swagger documentation
|
||||
*/
|
||||
export class BufferResponseInterceptor<T>
|
||||
implements NestInterceptor<Buffer, Base64File | StreamableFile>
|
||||
{
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
): Observable<Base64File | StreamableFile> {
|
||||
const ctx = context.switchToHttp();
|
||||
const request = ctx.getRequest<Request>();
|
||||
const response = ctx.getResponse<Response>();
|
||||
return next.handle().pipe(map(processBuffer));
|
||||
|
||||
function processBuffer(buffer: Buffer) {
|
||||
// Check buffer is Buffer
|
||||
if (!Buffer.isBuffer(buffer)) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const accept = request.headers['accept'];
|
||||
if (accept == 'application/json') {
|
||||
return {
|
||||
mimetype: 'image/png',
|
||||
data: buffer.toString('base64'),
|
||||
};
|
||||
}
|
||||
const file = new StreamableFile(buffer);
|
||||
response.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import {ApiProperty} from "@nestjs/swagger";
|
||||
import {IsNotEmpty, IsString} from "class-validator";
|
||||
|
||||
export const WHATSAPP_DEFAULT_SESSION_NAME = "default"
|
||||
|
||||
//
|
||||
// Queries
|
||||
//
|
||||
export class SessionQuery {
|
||||
@IsNotEmpty()
|
||||
sessionName: string = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
}
|
||||
|
||||
export class CheckNumberStatusQuery extends SessionQuery {
|
||||
@IsString()
|
||||
phone: string
|
||||
}
|
||||
|
||||
export class MessageTextQuery extends SessionQuery {
|
||||
@IsString()
|
||||
phone: string
|
||||
@IsString()
|
||||
text: string
|
||||
}
|
||||
|
||||
//
|
||||
// Requests
|
||||
//
|
||||
|
||||
const chatIdProperty = ApiProperty({
|
||||
example: '791231234567@c.us'
|
||||
})
|
||||
const sessionNameProperty = ApiProperty({
|
||||
default: WHATSAPP_DEFAULT_SESSION_NAME,
|
||||
})
|
||||
|
||||
export class SessionRequest {
|
||||
@sessionNameProperty
|
||||
sessionName: string;
|
||||
}
|
||||
|
||||
export class ChatRequest extends SessionRequest {
|
||||
@chatIdProperty
|
||||
chatId: string;
|
||||
}
|
||||
|
||||
export class MessageContactVcard extends ChatRequest {
|
||||
contactsId: string;
|
||||
name: string
|
||||
}
|
||||
|
||||
export class MessageText extends ChatRequest {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class MessageTextButtons extends ChatRequest {
|
||||
text: string;
|
||||
title: string;
|
||||
buttons: any[];
|
||||
}
|
||||
|
||||
export class MessageReply extends ChatRequest {
|
||||
text: string;
|
||||
@ApiProperty({
|
||||
example: 'message.id',
|
||||
})
|
||||
reply_to: string;
|
||||
}
|
||||
|
||||
export class MessageLocation extends ChatRequest {
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export class MessageImage extends ChatRequest {
|
||||
path: string;
|
||||
filename: string;
|
||||
caption: string;
|
||||
}
|
||||
|
||||
export class MessageFile extends ChatRequest {
|
||||
path: string;
|
||||
filename: string;
|
||||
caption: string;
|
||||
}
|
||||
|
||||
export class MessageLinkPreview extends ChatRequest {
|
||||
url: string;
|
||||
title: string;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Query,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
CaptchaBody,
|
||||
OTPRequest,
|
||||
QRCodeFormat,
|
||||
QRCodeQuery,
|
||||
QRCodeValue,
|
||||
RequestCodeRequest,
|
||||
} from '../structures/auth.dto';
|
||||
import { WAHASessionStatus } from '../structures/enums.dto';
|
||||
import { Base64File } from '../structures/files.dto';
|
||||
import { BufferResponseInterceptor } from './BufferResponseInterceptor';
|
||||
import { ApiFileAcceptHeader, SessionApiParam, SessionParam } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/auth')
|
||||
@ApiTags('auth')
|
||||
class AuthController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('qr')
|
||||
@ApiOperation({
|
||||
summary: 'Get QR code for pairing WhatsApp Web.',
|
||||
})
|
||||
@SessionApiParam
|
||||
@ApiFileAcceptHeader(Base64File, QRCodeValue)
|
||||
@UseInterceptors(new BufferResponseInterceptor())
|
||||
async getQR(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Query() query: QRCodeQuery,
|
||||
): Promise<Buffer | QRCodeValue> {
|
||||
if (session.status != WAHASessionStatus.SCAN_QR_CODE) {
|
||||
const err = `Can get QR code only in SCAN_QR_CODE status. The current status is '${session.status}'`;
|
||||
throw new UnprocessableEntityException(err);
|
||||
}
|
||||
const qr = session.getQR();
|
||||
if (query.format == QRCodeFormat.RAW) {
|
||||
return { value: qr.raw };
|
||||
}
|
||||
return qr.get();
|
||||
}
|
||||
|
||||
@Post('request-code')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Request authentication code.',
|
||||
})
|
||||
requestCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: RequestCodeRequest,
|
||||
) {
|
||||
return session.requestCode(request.phoneNumber, request.method, request);
|
||||
}
|
||||
|
||||
@Post('authorize-code')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Send OTP authentication code.',
|
||||
})
|
||||
authorizeCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: OTPRequest,
|
||||
) {
|
||||
return session.authorizeCode(request.code);
|
||||
}
|
||||
|
||||
@Get('captcha')
|
||||
@SessionApiParam
|
||||
@ApiFileAcceptHeader()
|
||||
@UseInterceptors(new BufferResponseInterceptor())
|
||||
@ApiOperation({
|
||||
summary: 'Get captcha image.',
|
||||
})
|
||||
async getCaptcha(@SessionParam session: WhatsappSession): Promise<Buffer> {
|
||||
const image = await session.getCaptcha();
|
||||
return image.get();
|
||||
}
|
||||
|
||||
@Post('captcha')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Enter captcha code.',
|
||||
})
|
||||
saveCaptcha(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() body: CaptchaBody,
|
||||
) {
|
||||
return session.saveCaptcha(body.code);
|
||||
}
|
||||
}
|
||||
|
||||
export { AuthController };
|
||||
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { parseBool } from '../helpers';
|
||||
import { GetChatMessagesQuery } from '../structures/chats.dto';
|
||||
import { EditMessageRequest } from '../structures/chatting.dto';
|
||||
import {
|
||||
ChatIdApiParam,
|
||||
MessageIdApiParam,
|
||||
SessionApiParam,
|
||||
SessionParam,
|
||||
} from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/chats')
|
||||
@ApiTags('chats')
|
||||
class ChatsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get all chats' })
|
||||
getAllChats(@SessionParam session: WhatsappSession) {
|
||||
return session.getChats();
|
||||
}
|
||||
|
||||
@Delete(':chatId')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Deletes the chat' })
|
||||
deleteChat(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
) {
|
||||
return session.deleteChat(chatId);
|
||||
}
|
||||
|
||||
@Get(':chatId/messages')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Gets messages in the chat' })
|
||||
getChatMessages(
|
||||
@Query() query: GetChatMessagesQuery,
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
) {
|
||||
const downloadMedia = parseBool(query.downloadMedia);
|
||||
return session.getChatMessages(chatId, query.limit, downloadMedia);
|
||||
}
|
||||
|
||||
@Delete(':chatId/messages')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Clears all messages from the chat' })
|
||||
clearMessages(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
) {
|
||||
return session.clearMessages(chatId);
|
||||
}
|
||||
|
||||
@Delete(':chatId/messages/:messageId')
|
||||
@SessionApiParam
|
||||
@ChatIdApiParam
|
||||
@MessageIdApiParam
|
||||
@ApiOperation({ summary: 'Deletes a message from the chat' })
|
||||
deleteMessage(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
@Param('messageId') messageId: string,
|
||||
) {
|
||||
return session.deleteMessage(chatId, messageId);
|
||||
}
|
||||
|
||||
@Put(':chatId/messages/:messageId')
|
||||
@SessionApiParam
|
||||
@ChatIdApiParam
|
||||
@MessageIdApiParam
|
||||
@ApiOperation({ summary: 'Edits a message in the chat' })
|
||||
editMessage(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
@Param('messageId') messageId: string,
|
||||
@Body() body: EditMessageRequest,
|
||||
) {
|
||||
return session.editMessage(chatId, messageId, body);
|
||||
}
|
||||
}
|
||||
|
||||
export { ChatsController };
|
||||
+157
-110
@@ -1,128 +1,175 @@
|
||||
import {Body, Controller, Get, NotImplementedException, Post, Query} from '@nestjs/common';
|
||||
import {ApiOperation, ApiTags} from "@nestjs/swagger";
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcard,
|
||||
MessageFile,
|
||||
MessageImage,
|
||||
MessageLinkPreview,
|
||||
MessageLocation,
|
||||
MessageReply,
|
||||
MessageText,
|
||||
MessageTextButtons,
|
||||
MessageTextQuery
|
||||
} from "./all.dto";
|
||||
import {ensureSuffix, WhatsappSessionManager} from "../whatsapp.service";
|
||||
import { Body, Controller, Get, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageStarRequest,
|
||||
MessageTextQuery,
|
||||
MessageTextRequest,
|
||||
MessageVideoRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
import { WAMessage } from '../structures/responses.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api')
|
||||
@ApiTags('chatting')
|
||||
export class ChattingController {
|
||||
constructor(private whatsappSessionManager: WhatsappSessionManager) {
|
||||
}
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/checkNumberStatus')
|
||||
@ApiOperation({summary: 'Check number status'})
|
||||
async checkNumberStatus(
|
||||
@Query() request: CheckNumberStatusQuery,
|
||||
) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(request.sessionName)
|
||||
try {
|
||||
const result = await whatsapp.checkNumberStatus(ensureSuffix(request.phone))
|
||||
return {numberExists: result['numberExists']}
|
||||
} catch (error) {
|
||||
// We need to "touch" the error in order to get unhandled rejections
|
||||
// It logs out the session and stop the app
|
||||
console.log(typeof error)
|
||||
console.log(error)
|
||||
return {numberExists: false}
|
||||
}
|
||||
}
|
||||
@Get('/checkNumberStatus')
|
||||
@ApiOperation({ summary: 'Check number status' })
|
||||
async checkNumberStatus(
|
||||
@Query() request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.checkNumberStatus(request);
|
||||
}
|
||||
|
||||
@Post('/sendContactVcard')
|
||||
sendContactVcard(@Body() message: MessageContactVcard) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
||||
return whatsapp.sendContactVcard(message.chatId, message.contactsId, message.name)
|
||||
}
|
||||
@Post('/sendContactVcard')
|
||||
sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendContactVCard(request);
|
||||
}
|
||||
|
||||
@Get('/sendText')
|
||||
@ApiOperation({summary: 'Send a text message'})
|
||||
sendTextGet(
|
||||
@Query() message: MessageTextQuery,
|
||||
) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
||||
return whatsapp.sendText(ensureSuffix(message.phone), message.text)
|
||||
}
|
||||
@Get('/sendText')
|
||||
@ApiOperation({ summary: 'Send a text message' })
|
||||
sendTextGet(@Query() query: MessageTextQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
const msg = new MessageTextRequest();
|
||||
msg.chatId = query.phone;
|
||||
msg.text = query.text;
|
||||
return whatsapp.sendText(msg);
|
||||
}
|
||||
|
||||
@Post('/sendText')
|
||||
@ApiOperation({summary: 'Send a text message'})
|
||||
sendText(@Body() message: MessageText) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
||||
return whatsapp.sendText(ensureSuffix(message.chatId), message.text)
|
||||
}
|
||||
@Post('/sendText')
|
||||
@ApiOperation({ summary: 'Send a text message' })
|
||||
sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendText(request);
|
||||
}
|
||||
|
||||
@Post('/sendTextButtons')
|
||||
@ApiOperation({summary: 'Send a text message with buttons'})
|
||||
sendTextButtons(@Body() message: MessageTextButtons) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
||||
return whatsapp.sendButtons(ensureSuffix(message.chatId), message.title, message.buttons, message.text)
|
||||
}
|
||||
@Post('/sendPoll')
|
||||
@ApiOperation({
|
||||
summary: 'Send a poll with options. You can use it as buttons replace',
|
||||
})
|
||||
sendPoll(@Body() request: MessagePollRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendPoll(request);
|
||||
}
|
||||
|
||||
@Post('/sendLocation')
|
||||
sendLocation(@Body() message: MessageLocation) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
||||
return whatsapp.sendLocation(message.chatId, message.latitude, message.longitude, message.title)
|
||||
}
|
||||
@Post('/sendLocation')
|
||||
sendLocation(@Body() request: MessageLocationRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendLocation(request);
|
||||
}
|
||||
|
||||
@Post('/sendLinkPreview')
|
||||
sendLinkPreview(@Body() message: MessageLinkPreview) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
||||
return whatsapp.sendLinkPreview(message.chatId, message.url, message.title)
|
||||
}
|
||||
@Post('/sendLinkPreview')
|
||||
sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendLinkPreview(request);
|
||||
}
|
||||
|
||||
@Post('/sendImage')
|
||||
@ApiOperation({summary: 'NOT IMPLEMENTED YET'})
|
||||
sendImage(@Body() message: MessageImage) {
|
||||
throw new NotImplementedException();
|
||||
// TODO: Accept image URL, download it and then send with path
|
||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
||||
return whatsapp.sendImage(message.chatId, message.path, message.filename, message.caption)
|
||||
}
|
||||
@Post('/sendImage')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send an image. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendImage(@Body() request: MessageImageRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendImage(request);
|
||||
}
|
||||
|
||||
@Post('/sendFile')
|
||||
@ApiOperation({summary: 'NOT IMPLEMENTED YET'})
|
||||
sendFile(@Body() message: MessageFile) {
|
||||
throw new NotImplementedException();
|
||||
// TODO: Accept File URL, download it and then send with path
|
||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
||||
return whatsapp.sendFile(message.chatId, message.path, message.filename, message.caption)
|
||||
}
|
||||
@Post('/sendFile')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send a file. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendFile(@Body() request: MessageFileRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendFile(request);
|
||||
}
|
||||
|
||||
@Post('/reply')
|
||||
@ApiOperation({summary: 'Reply to a text message'})
|
||||
reply(@Body() message: MessageReply) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
||||
return whatsapp.reply(message.chatId, message.text, message.reply_to)
|
||||
}
|
||||
@Post('/sendVoice')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send an voice message. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendVoice(@Body() request: MessageVoiceRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendVoice(request);
|
||||
}
|
||||
|
||||
@Post('/sendSeen')
|
||||
sendSeen(@Body() chat: ChatRequest) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(chat.sessionName)
|
||||
return whatsapp.sendSeen(chat.chatId)
|
||||
}
|
||||
@Post('/sendVideo')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send a video. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendVideo(@Body() request: MessageVideoRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendVideo(request);
|
||||
}
|
||||
|
||||
@Post('/startTyping')
|
||||
startTyping(@Body() chat: ChatRequest) {
|
||||
// It's infinitive action
|
||||
const whatsapp = this.whatsappSessionManager.getSession(chat.sessionName)
|
||||
whatsapp.startTyping(chat.chatId)
|
||||
return true
|
||||
}
|
||||
@Post('/reply')
|
||||
@ApiOperation({ summary: 'Reply to a text message' })
|
||||
reply(@Body() request: MessageReplyRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.reply(request);
|
||||
}
|
||||
|
||||
@Post('/stopTyping')
|
||||
stopTyping(@Body() chat: ChatRequest) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(chat.sessionName)
|
||||
whatsapp.stopTyping(chat.chatId)
|
||||
return true
|
||||
}
|
||||
@Post('/sendSeen')
|
||||
sendSeen(@Body() chat: SendSeenRequest) {
|
||||
const whatsapp = this.manager.getSession(chat.session);
|
||||
return whatsapp.sendSeen(chat);
|
||||
}
|
||||
|
||||
@Post('/startTyping')
|
||||
async startTyping(@Body() chat: ChatRequest) {
|
||||
// It's infinitive action
|
||||
const whatsapp = this.manager.getSession(chat.session);
|
||||
await whatsapp.startTyping(chat);
|
||||
return { result: true };
|
||||
}
|
||||
|
||||
@Post('/stopTyping')
|
||||
async stopTyping(@Body() chat: ChatRequest) {
|
||||
const whatsapp = this.manager.getSession(chat.session);
|
||||
await whatsapp.stopTyping(chat);
|
||||
return { result: true };
|
||||
}
|
||||
|
||||
@Put('/reaction')
|
||||
@ApiOperation({ summary: 'React to a message with an emoji' })
|
||||
setReaction(@Body() request: MessageReactionRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.setReaction(request);
|
||||
}
|
||||
|
||||
@Put('/star')
|
||||
@ApiOperation({ summary: 'Star or unstar a message' })
|
||||
async setStar(@Body() request: MessageStarRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
await whatsapp.setStar(request);
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('/messages')
|
||||
@ApiOperation({ summary: 'Get messages in a chat' })
|
||||
getMessages(@Query() query: GetMessageQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getMessages(query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { SessionQuery } from '../structures/base.dto';
|
||||
import {
|
||||
CheckNumberStatusQuery,
|
||||
WANumberExistResult,
|
||||
} from '../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/contacts')
|
||||
@ApiTags('contacts')
|
||||
export class ContactsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Get contact basic info. The method always return result, even if the phone number is not registered in WhatsApp. For that - use /check-exists endpoint below.',
|
||||
})
|
||||
get(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContact(query);
|
||||
}
|
||||
|
||||
@Get('/all')
|
||||
@ApiOperation({ summary: 'Get all contacts' })
|
||||
getAll(@Query() query: SessionQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContacts();
|
||||
}
|
||||
|
||||
@Get('/check-exists')
|
||||
@ApiOperation({ summary: 'Check phone number is registered in WhatsApp.' })
|
||||
async checkExists(
|
||||
@Query() request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.checkNumberStatus(request);
|
||||
}
|
||||
|
||||
@Get('/about')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Gets the Contact\'s current "about" info. Returns null if you don\'t have permission to read their status.',
|
||||
})
|
||||
getAbout(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContactAbout(query);
|
||||
}
|
||||
|
||||
@Get('/profile-picture')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Returns the contact's profile picture URL, if privacy settings allow it.",
|
||||
})
|
||||
getProfilePicture(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContactProfilePicture(query);
|
||||
}
|
||||
|
||||
@Post('/block')
|
||||
@ApiOperation({ summary: 'Block contact' })
|
||||
block(@Body() request: ContactRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.blockContact(request);
|
||||
}
|
||||
|
||||
@Post('/unblock')
|
||||
@ApiOperation({ summary: 'Unblock contact' })
|
||||
unblock(@Body() request: ContactRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.unblockContact(request);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import {Body, Controller, Get, Post} from '@nestjs/common';
|
||||
import {ApiTags} from "@nestjs/swagger";
|
||||
import {SessionRequest} from "./all.dto";
|
||||
import {WhatsappSessionManager} from "../whatsapp.service";
|
||||
|
||||
|
||||
@Controller('api')
|
||||
@ApiTags('device')
|
||||
export class DeviceController {
|
||||
|
||||
constructor(private whatsappSessionManager: WhatsappSessionManager) {
|
||||
}
|
||||
|
||||
|
||||
@Post('/killServiceWorker')
|
||||
killServiceWorker(@Body() request: SessionRequest) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(request.sessionName)
|
||||
return whatsapp.killServiceWorker()
|
||||
}
|
||||
|
||||
@Post('/restartService')
|
||||
restartService(@Body() request: SessionRequest) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(request.sessionName)
|
||||
return whatsapp.restartService()
|
||||
}
|
||||
|
||||
@Get('/getHostDevice')
|
||||
getHostDevice(@Body() request: SessionRequest) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(request.sessionName)
|
||||
return whatsapp.getHostDevice()
|
||||
}
|
||||
|
||||
@Get('/getConnectionState')
|
||||
getConnectionState(@Body() request: SessionRequest) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(request.sessionName)
|
||||
return whatsapp.getConnectionState()
|
||||
}
|
||||
|
||||
@Get('/getBatteryLevel')
|
||||
getBatteryLevel(@Body() request: SessionRequest) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(request.sessionName)
|
||||
return whatsapp.getBatteryLevel()
|
||||
}
|
||||
|
||||
@Get('/isConnected')
|
||||
isConnected(@Body() request: SessionRequest) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(request.sessionName)
|
||||
return whatsapp.isConnected()
|
||||
}
|
||||
|
||||
@Get('/getWAVersion')
|
||||
getWAVersion(@Body() request: SessionRequest) {
|
||||
const whatsapp = this.whatsappSessionManager.getSession(request.sessionName)
|
||||
return whatsapp.getWAVersion()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+70
-24
@@ -1,30 +1,76 @@
|
||||
import {ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus,} from '@nestjs/common';
|
||||
import {Request, Response} from 'express';
|
||||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
ExceptionFilter,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
import { VERSION } from '../version';
|
||||
|
||||
/**
|
||||
* Serializer error to JSON.
|
||||
* Because it's not possible to just pass Error class to JSON.stringify in the nestjs response
|
||||
* Credits: https://stackoverflow.com/a/72707578/6753144
|
||||
*/
|
||||
export function serializeError(err: unknown) {
|
||||
const properties = Object.getOwnPropertyNames(err);
|
||||
// getOwnPropertyNames does not get 'name', like "ReferenceError"
|
||||
properties.push('name');
|
||||
return JSON.parse(JSON.stringify(err, properties));
|
||||
}
|
||||
|
||||
@Catch()
|
||||
export class AllExceptionsFilter implements ExceptionFilter {
|
||||
catch(exception: HttpException | Error, host: ArgumentsHost): void {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
response
|
||||
.status(exception.getStatus())
|
||||
.json(exception.getResponse());
|
||||
return
|
||||
}
|
||||
|
||||
const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR
|
||||
response
|
||||
.status(httpStatus)
|
||||
.json({
|
||||
statusCode: httpStatus,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: request.url,
|
||||
error: exception.message,
|
||||
stack: exception.stack,
|
||||
});
|
||||
catch(exception: any | Error, host: ArgumentsHost): void {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
/**
|
||||
* If file not found - we get weird 500 error
|
||||
* So we convert that exception manually to 404 and send appropriate JSON response
|
||||
* @issue https://github.com/devlikeapro/waha/issues/134
|
||||
* @solution https://github.com/nestjs/serve-static/issues/139#issuecomment-612429557
|
||||
*/
|
||||
if (exception.code === 'ENOENT') {
|
||||
response.status(HttpStatus.NOT_FOUND).json({
|
||||
error: {
|
||||
code: 404,
|
||||
key: 'FILE_NOT_FOUND',
|
||||
message: 'File not found',
|
||||
details: 'File not found or no longer available',
|
||||
},
|
||||
});
|
||||
response.send();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If it's HttpException - pass it as is
|
||||
*/
|
||||
if (exception instanceof HttpException) {
|
||||
response.status(exception.getStatus()).json(exception.getResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If it's not HttpException - pass it as 500 error
|
||||
* And send JSON response with error details
|
||||
*/
|
||||
const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
response.status(httpStatus).json({
|
||||
statusCode: httpStatus,
|
||||
timestamp: new Date().toISOString(),
|
||||
exception: serializeError(exception),
|
||||
request: {
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
query: request.query,
|
||||
},
|
||||
version: VERSION,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
DescriptionRequest,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
SubjectRequest,
|
||||
} from '../structures/groups.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/groups')
|
||||
@ApiTags('groups')
|
||||
export class GroupsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Create a new group.' })
|
||||
createGroup(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: CreateGroupRequest,
|
||||
) {
|
||||
return session.createGroup(request);
|
||||
}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get all groups.' })
|
||||
getGroups(@SessionParam session: WhatsappSession) {
|
||||
return session.getGroups();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get the group.' })
|
||||
getGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.getGroup(id);
|
||||
}
|
||||
|
||||
@Put(':id/settings/security/info-admin-only')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group settings to only allow admins to edit group info (title, description, photo).',
|
||||
})
|
||||
setInfoAdminOnly(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: SettingsSecurityChangeInfo,
|
||||
) {
|
||||
return session.setInfoAdminsOnly(id, request.adminsOnly);
|
||||
}
|
||||
|
||||
@Get(':id/settings/security/info-admin-only')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Gets the group settings to only allow admins to edit group info (title, description, photo).',
|
||||
})
|
||||
getInfoAdminOnly(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
): Promise<SettingsSecurityChangeInfo> {
|
||||
return session.getInfoAdminsOnly(id);
|
||||
}
|
||||
|
||||
@Put(':id/settings/security/messages-admin-only')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group settings to only allow admins to send messages.',
|
||||
})
|
||||
setMessagesAdminOnly(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: SettingsSecurityChangeInfo,
|
||||
) {
|
||||
return session.setMessagesAdminsOnly(id, request.adminsOnly);
|
||||
}
|
||||
|
||||
@Get(':id/settings/security/messages-admin-only')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Gets the group settings to only allow admins to send messages.',
|
||||
})
|
||||
getMessagesAdminOnly(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
): Promise<SettingsSecurityChangeInfo> {
|
||||
return session.getMessagesAdminsOnly(id);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Delete the group.' })
|
||||
deleteGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.deleteGroup(id);
|
||||
}
|
||||
|
||||
@Post(':id/leave')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Leave the group.' })
|
||||
leaveGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.leaveGroup(id);
|
||||
}
|
||||
|
||||
@Put(':id/description')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group description.\n' +
|
||||
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
|
||||
})
|
||||
@SessionApiParam
|
||||
setDescription(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: DescriptionRequest,
|
||||
) {
|
||||
return session.setDescription(id, request.description);
|
||||
}
|
||||
|
||||
@Put(':id/subject')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group subject.\n' +
|
||||
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
|
||||
})
|
||||
setSubject(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: SubjectRequest,
|
||||
) {
|
||||
return session.setSubject(id, request.subject);
|
||||
}
|
||||
|
||||
@Get(':id/invite-code')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Gets the invite code for a specific group.' })
|
||||
getInviteCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
): Promise<string> {
|
||||
return session.getInviteCode(id);
|
||||
}
|
||||
|
||||
@Post(':id/invite-code/revoke')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Invalidates the current group invite code and generates a new one.',
|
||||
})
|
||||
revokeInviteCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
): Promise<string> {
|
||||
return session.revokeInviteCode(id);
|
||||
}
|
||||
|
||||
@Get(':id/participants/')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get a list of participants by in the group.' })
|
||||
getParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return session.getParticipants(id);
|
||||
}
|
||||
|
||||
@Post(':id/participants/add')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Adds a list of participants by ID to the group.' })
|
||||
addParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.addParticipants(id, request);
|
||||
}
|
||||
|
||||
@Post(':id/participants/remove')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Removes a list of participants by ID to the group.',
|
||||
})
|
||||
removeParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.removeParticipants(id, request);
|
||||
}
|
||||
|
||||
@Post(':id/admin/promote')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Promote participants to admin users.' })
|
||||
promoteToAdmin(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.promoteParticipantsToAdmin(id, request);
|
||||
}
|
||||
|
||||
@Post(':id/admin/demote')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Demotes participants by to regular users.' })
|
||||
demoteToAdmin(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.demoteParticipantsToUser(id, request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { HealthCheck } from '@nestjs/terminus';
|
||||
|
||||
import { WAHAHealthCheckService } from '../core/abc/WAHAHealthCheckService';
|
||||
|
||||
@Controller('health')
|
||||
@ApiTags('other')
|
||||
export class HealthController {
|
||||
constructor(private wahaHealth: WAHAHealthCheckService) {}
|
||||
|
||||
@Get()
|
||||
@HealthCheck()
|
||||
async check() {
|
||||
return this.wahaHealth.check();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
applyDecorators,
|
||||
Injectable,
|
||||
Param,
|
||||
PipeTransform,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiExtraModels,
|
||||
ApiParam,
|
||||
ApiResponse,
|
||||
getSchemaPath,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { Base64File } from '../structures/files.dto';
|
||||
|
||||
/**
|
||||
* Get session name and return Whatsapp session back (if exists)
|
||||
* use it as
|
||||
@Param('session', SessionPipe) session: WhatsappSession,
|
||||
*/
|
||||
@Injectable()
|
||||
export class SessionPipe implements PipeTransform<WhatsappSession> {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
async transform(value: any) {
|
||||
return this.manager.getSession(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator for a method that uses SessionPipe above
|
||||
*/
|
||||
export const SessionApiParam = ApiParam({
|
||||
name: 'session',
|
||||
required: true,
|
||||
type: 'string',
|
||||
schema: {
|
||||
default: 'default',
|
||||
},
|
||||
description: 'WhatsApp session name',
|
||||
});
|
||||
|
||||
/**
|
||||
* Session param
|
||||
@SessionParam session: WhatsappSession,
|
||||
*/
|
||||
export const SessionParam = Param('session', SessionPipe);
|
||||
|
||||
export const ChatIdApiParam = ApiParam({
|
||||
name: 'chatId',
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'Chat ID',
|
||||
example: '123456789@c.us',
|
||||
});
|
||||
|
||||
export const MessageIdApiParam = ApiParam({
|
||||
name: 'messageId',
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'Message ID',
|
||||
example: 'true_123456789@c.us_BAE6A33293978B16',
|
||||
});
|
||||
|
||||
function getRefSchemaPaths(models) {
|
||||
return models.map((model) => {
|
||||
return { $ref: getSchemaPath(model) };
|
||||
});
|
||||
}
|
||||
|
||||
export function ApiFileAcceptHeader(...models) {
|
||||
models = models.length ? models : [Base64File];
|
||||
return applyDecorators(
|
||||
// Add extra models, otherwise it'll give a error
|
||||
// $ref not found
|
||||
ApiExtraModels(...models),
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
content: {
|
||||
'image/png': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
format: 'binary',
|
||||
},
|
||||
},
|
||||
'application/json': {
|
||||
schema: {
|
||||
oneOf: getRefSchemaPaths(models),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { WAHAPresenceStatus } from '../structures/enums.dto';
|
||||
import {
|
||||
WAHAChatPresences,
|
||||
WAHASessionPresence,
|
||||
} from '../structures/presence.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/presence')
|
||||
@ApiTags('presence')
|
||||
export class PresenceController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Set session presence' })
|
||||
setPresence(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: WAHASessionPresence,
|
||||
) {
|
||||
// Validate request
|
||||
const presencesWithoutChatId = [
|
||||
WAHAPresenceStatus.ONLINE,
|
||||
WAHAPresenceStatus.OFFLINE,
|
||||
];
|
||||
const requiresNoChatId = presencesWithoutChatId.includes(request.presence);
|
||||
const requiresChatId = !requiresNoChatId;
|
||||
|
||||
if (requiresNoChatId && request.chatId) {
|
||||
const msg = {
|
||||
detail: `'${request.presence}' presence works on the global scope and doesn't require 'chatId' field.`,
|
||||
};
|
||||
throw new BadRequestException(msg);
|
||||
} else if (requiresChatId && !request.chatId) {
|
||||
const msg = {
|
||||
detail: `'${request.presence}' presence requires 'chatId' field.`,
|
||||
};
|
||||
throw new BadRequestException(msg);
|
||||
}
|
||||
|
||||
return session.setPresence(request.presence, request.chatId);
|
||||
}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get all subscribed presence information.' })
|
||||
getPresenceAll(
|
||||
@SessionParam session: WhatsappSession,
|
||||
): Promise<WAHAChatPresences[]> {
|
||||
return session.getPresences();
|
||||
}
|
||||
|
||||
@Get(':chatId')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Get the presence for the chat id. If it hasn't been subscribed - it also subscribes to it.",
|
||||
})
|
||||
getPresence(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
): Promise<WAHAChatPresences> {
|
||||
return session.getPresence(chatId);
|
||||
}
|
||||
|
||||
@Post(':chatId/subscribe')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Subscribe to presence events for the chat.',
|
||||
})
|
||||
subscribe(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
): Promise<void> {
|
||||
return session.subscribePresence(chatId);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,33 @@
|
||||
import {Controller, Get, Query, Res} from '@nestjs/common';
|
||||
import {ApiTags} from "@nestjs/swagger";
|
||||
import {Readable} from "stream";
|
||||
import {Response} from 'express';
|
||||
import {WhatsappSessionManager} from "../whatsapp.service";
|
||||
import {SessionQuery} from "./all.dto";
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Query,
|
||||
Res,
|
||||
StreamableFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { SessionQuery } from '../structures/base.dto';
|
||||
import { BufferResponseInterceptor } from './BufferResponseInterceptor';
|
||||
import { ApiFileAcceptHeader } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api')
|
||||
@ApiTags('screenshot')
|
||||
export class ScreenshotController {
|
||||
constructor(private whatsappSessionManager: WhatsappSessionManager) {
|
||||
}
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/screenshot')
|
||||
async screenshot(
|
||||
@Res() res: Response,
|
||||
@Query() sessionQuery: SessionQuery,
|
||||
) {
|
||||
const whatsappService = this.whatsappSessionManager.getService(sessionQuery.sessionName)
|
||||
const buffer = await whatsappService.getScreenshotOrQRCode();
|
||||
const stream = new Readable();
|
||||
stream.push(buffer);
|
||||
stream.push(null);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
stream.pipe(res)
|
||||
}
|
||||
@Get('/screenshot')
|
||||
@UseInterceptors(new BufferResponseInterceptor())
|
||||
@ApiFileAcceptHeader()
|
||||
async screenshot(
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
@Query() sessionQuery: SessionQuery,
|
||||
) {
|
||||
const whatsappService = this.manager.getSession(sessionQuery.session);
|
||||
return await whatsappService.getScreenshot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,95 @@
|
||||
import {Body, Controller, Get, Post} from '@nestjs/common';
|
||||
import {ApiTags} from "@nestjs/swagger";
|
||||
import {WhatsappSessionManager} from "../whatsapp.service";
|
||||
import {SessionRequest} from "./all.dto";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
NotFoundException,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiExtraModels,
|
||||
ApiOperation,
|
||||
ApiSecurity,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { parseBool } from '../helpers';
|
||||
import {
|
||||
ListSessionsQuery,
|
||||
MeInfo,
|
||||
SessionDTO,
|
||||
SessionInfo,
|
||||
SessionLogoutRequest,
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
} from '../structures/sessions.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/sessions')
|
||||
@ApiTags('sessions')
|
||||
export class SessionsController {
|
||||
constructor(private whatsappSessionManager: WhatsappSessionManager) {
|
||||
}
|
||||
class SessionsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('/start/')
|
||||
async start(@Body() request: SessionStartRequest): Promise<SessionDTO> {
|
||||
const result = await this.manager.start(request);
|
||||
await this.manager.sessionConfigRepository.save(
|
||||
request.name,
|
||||
request.config || null,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post('/start/')
|
||||
async start(@Body() request: SessionRequest) {
|
||||
// Do not wait it, because we get venom instance only after scanning QR code
|
||||
this.whatsappSessionManager.startSession(request.sessionName)
|
||||
@Post('/stop/')
|
||||
@ApiOperation({ summary: 'Stop session' })
|
||||
async stop(@Body() request: SessionStopRequest): Promise<void> {
|
||||
if (request.logout) {
|
||||
await this.manager.logout(request);
|
||||
} else {
|
||||
await this.manager.stop(request);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@Post('/stop/')
|
||||
async stop(@Body() request: SessionRequest) {
|
||||
await this.whatsappSessionManager.stopSession(request.sessionName)
|
||||
}
|
||||
@Post('/logout/')
|
||||
@ApiOperation({ summary: 'Logout from session.' })
|
||||
clean(@Body() request: SessionLogoutRequest): Promise<void> {
|
||||
return this.manager.logout(request);
|
||||
}
|
||||
|
||||
@Get('/')
|
||||
async list() {
|
||||
return this.whatsappSessionManager.getAllSessions()
|
||||
@Get('/')
|
||||
async list(@Query() query: ListSessionsQuery): Promise<SessionInfo[]> {
|
||||
const all = parseBool(query.all);
|
||||
return this.manager.getSessions(all);
|
||||
}
|
||||
|
||||
@Get('/:session')
|
||||
async get(@Param('session') name: string): Promise<SessionInfo> {
|
||||
const session = this.manager.getSessionInfo(name);
|
||||
if (session === null) {
|
||||
throw new NotFoundException('Session not found');
|
||||
}
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/sessions/:session')
|
||||
@ApiTags('sessions')
|
||||
class SessionController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('me')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get information about the authenticated account' })
|
||||
getMe(@SessionParam session: WhatsappSession): Promise<MeInfo | null> {
|
||||
return session.getSessionMeInfo();
|
||||
}
|
||||
}
|
||||
|
||||
export { SessionController, SessionsController };
|
||||
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
ImageStatus,
|
||||
TextStatus,
|
||||
VideoStatus,
|
||||
VoiceStatus,
|
||||
} from '../structures/status.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/status')
|
||||
@ApiTags('status')
|
||||
class StatusController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('text')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send text status.' })
|
||||
sendTextStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: TextStatus,
|
||||
) {
|
||||
return session.sendTextStatus(status);
|
||||
}
|
||||
|
||||
@Post('image')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send image status.' })
|
||||
sendImageStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: ImageStatus,
|
||||
) {
|
||||
return session.sendImageStatus(status);
|
||||
}
|
||||
|
||||
@Post('voice')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send voice status.' })
|
||||
sendVoiceStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: VoiceStatus,
|
||||
) {
|
||||
return session.sendVoiceStatus(status);
|
||||
}
|
||||
|
||||
@Post('video')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send video status.' })
|
||||
sendVideoStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: VideoStatus,
|
||||
) {
|
||||
return session.sendVideoStatus(status);
|
||||
}
|
||||
}
|
||||
|
||||
export { StatusController };
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiExtraModels, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { WAHAEnvironment } from '../structures/environment.dto';
|
||||
import { VERSION } from '../version';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/version')
|
||||
@ApiTags('other')
|
||||
export class VersionController {
|
||||
@Get('')
|
||||
get(): WAHAEnvironment {
|
||||
return VERSION;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import {ConsoleLogger, Module} from '@nestjs/common';
|
||||
import {WhatsappSessionManager} from "./whatsapp.service";
|
||||
import {ScreenshotController} from "./api/screenshot.controller";
|
||||
import {ConfigModule} from "@nestjs/config";
|
||||
import {WhatsappConfigService} from "./config.service";
|
||||
import {ServeStaticModule} from "@nestjs/serve-static";
|
||||
import {SessionsController} from "./api/sessions.controller";
|
||||
import {ChattingController} from "./api/chatting.controller";
|
||||
import {DeviceController} from "./api/device.controller";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
ServeStaticModule.forRootAsync({
|
||||
imports: [],
|
||||
extraProviders: [WhatsappConfigService],
|
||||
inject: [WhatsappConfigService],
|
||||
useFactory: (config: WhatsappConfigService) => {
|
||||
return [{
|
||||
rootPath: config.filesFolder,
|
||||
serveRoot: config.files_uri,
|
||||
}]
|
||||
},
|
||||
}),
|
||||
],
|
||||
controllers: [
|
||||
SessionsController,
|
||||
ChattingController,
|
||||
DeviceController,
|
||||
ScreenshotController,
|
||||
],
|
||||
providers: [WhatsappSessionManager, ConsoleLogger, WhatsappConfigService],
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
+128
-41
@@ -1,56 +1,143 @@
|
||||
import {Injectable} from "@nestjs/common";
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { parseBool } from './helpers';
|
||||
import { WAHAEngine } from './structures/enums.dto';
|
||||
import { WebhookConfig } from './structures/webhooks.config.dto';
|
||||
import { getEngineName } from './version';
|
||||
|
||||
@Injectable()
|
||||
export class WhatsappConfigService {
|
||||
public files_uri = '/api/files'
|
||||
public schema = "http"
|
||||
public filesUri = '/api/files';
|
||||
public schema = 'http';
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
}
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
get filesURL(): string {
|
||||
return `${this.schema}://${this.hostname}:${this.port}${this.files_uri}/`
|
||||
}
|
||||
get filesURL(): string {
|
||||
return `${this.schema}://${this.hostname}:${this.port}${this.filesUri}/`;
|
||||
}
|
||||
|
||||
get hostname(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_API_HOSTNAME',
|
||||
'localhost',
|
||||
)
|
||||
}
|
||||
get hostname(): string {
|
||||
return this.configService.get('WHATSAPP_API_HOSTNAME', 'localhost');
|
||||
}
|
||||
|
||||
get port(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_API_PORT',
|
||||
'3000',
|
||||
)
|
||||
}
|
||||
get port(): string {
|
||||
return this.configService.get('WHATSAPP_API_PORT', '3000');
|
||||
}
|
||||
|
||||
get filesFolder(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_FILES_FOLDER',
|
||||
'/tmp/whatsapp-files',
|
||||
)
|
||||
}
|
||||
get filesFolder(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_FILES_FOLDER',
|
||||
'/tmp/whatsapp-files',
|
||||
);
|
||||
}
|
||||
|
||||
get filesLifetime(): number {
|
||||
return this.configService.get<number>(
|
||||
'WHATSAPP_FILES_LIFETIME',
|
||||
180,
|
||||
)
|
||||
}
|
||||
get filesLifetime(): number {
|
||||
return this.configService.get<number>('WHATSAPP_FILES_LIFETIME', 180);
|
||||
}
|
||||
|
||||
get mimetypes(): string[] | null {
|
||||
const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', "")
|
||||
return types ? types.split(',') : null
|
||||
get mimetypes(): string[] {
|
||||
if (!this.shouldDownloadMedia) {
|
||||
return ['mimetype/ignore-all-media'];
|
||||
}
|
||||
const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', '');
|
||||
return types ? types.split(',') : [];
|
||||
}
|
||||
|
||||
get startSession(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_START_SESSION', undefined)
|
||||
}
|
||||
get shouldDownloadMedia(): boolean {
|
||||
const value = this.configService.get('WHATSAPP_DOWNLOAD_MEDIA', 'true');
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
get(name: string): any {
|
||||
return this.configService.get(name)
|
||||
get startSessions(): string[] {
|
||||
const value: string = this.configService.get('WHATSAPP_START_SESSION', '');
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
return value.split(',');
|
||||
}
|
||||
|
||||
get shouldRestartAllSessions(): boolean {
|
||||
const value: string = this.configService.get(
|
||||
'WHATSAPP_RESTART_ALL_SESSIONS',
|
||||
'false',
|
||||
);
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
get proxyServer(): string[] | string | undefined {
|
||||
const single = this.configService.get('WHATSAPP_PROXY_SERVER', undefined);
|
||||
const multipleValues = this.configService.get(
|
||||
'WHATSAPP_PROXY_SERVER_LIST',
|
||||
undefined,
|
||||
);
|
||||
const multiple = multipleValues ? multipleValues.split(',') : undefined;
|
||||
return single ? single : multiple;
|
||||
}
|
||||
|
||||
get proxyServerIndexPrefix(): string | undefined {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_PROXY_SERVER_INDEX_PREFIX',
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
|
||||
get proxyServerUsername(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_PROXY_SERVER_USERNAME', undefined);
|
||||
}
|
||||
|
||||
get proxyServerPassword(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_PROXY_SERVER_PASSWORD', undefined);
|
||||
}
|
||||
|
||||
getWebhookConfig(): WebhookConfig | undefined {
|
||||
const url = this.getWebhookUrl();
|
||||
const events = this.getWebhookEvents();
|
||||
if (!url || events.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return { url: url, events: events };
|
||||
}
|
||||
|
||||
private getWebhookUrl(): string | undefined {
|
||||
return this.get('WHATSAPP_HOOK_URL');
|
||||
}
|
||||
|
||||
private getWebhookEvents(): string[] {
|
||||
const value = this.get('WHATSAPP_HOOK_EVENTS', '');
|
||||
return value ? value.split(',') : [];
|
||||
}
|
||||
|
||||
getSessionMongoUrl(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_SESSIONS_MONGO_URL', undefined);
|
||||
}
|
||||
|
||||
get(name: string, defaultValue: any = undefined): any {
|
||||
return this.configService.get(name, defaultValue);
|
||||
}
|
||||
|
||||
getApiKey(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_API_KEY', '');
|
||||
}
|
||||
|
||||
getHealthMediaFilesThreshold(): number {
|
||||
return this.configService.get<number>(
|
||||
'WHATSAPP_HEALTH_MEDIA_FILES_THRESHOLD_MB',
|
||||
100,
|
||||
);
|
||||
}
|
||||
|
||||
getHealthSessionFilesThreshold(): number {
|
||||
return this.configService.get<number>(
|
||||
'WHATSAPP_HEALTH_SESSION_FILES_THRESHOLD_MB',
|
||||
100,
|
||||
);
|
||||
}
|
||||
|
||||
getHealthMongoTimeout(): number {
|
||||
return this.configService.get<number>(
|
||||
'WHATSAPP_HEALTH_MONGO_TIMEOUT_MS',
|
||||
3000,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export class QR {
|
||||
private base64: string;
|
||||
public raw?: string;
|
||||
|
||||
save(base64: string, raw?: string) {
|
||||
this.base64 = base64.replace(/^data:image\/png;base64,/, '');
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
get(): Buffer {
|
||||
return Buffer.from(this.base64, 'base64');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger';
|
||||
import { DECORATORS } from '@nestjs/swagger/dist/constants';
|
||||
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { VERSION } from '../version';
|
||||
import { SwaggerConfigServiceCore } from './config/SwaggerConfigServiceCore';
|
||||
|
||||
export class SwaggerConfiguratorCore {
|
||||
constructor(protected app: INestApplication) {}
|
||||
|
||||
get title() {
|
||||
return 'WAHA - WhatsApp HTTP API';
|
||||
}
|
||||
|
||||
get description() {
|
||||
return (
|
||||
'WhatsApp HTTP API that you can run in a click!<br/>' +
|
||||
'<br/>' +
|
||||
'Learn more:' +
|
||||
'<ul>' +
|
||||
'<li><a href="https://waha.devlike.pro/" target="_blank">Documentation</a></li>' +
|
||||
'<li><a href="https://waha.devlike.pro/docs/how-to/engines/#features" target="_blank">Supported features in engines</a></li>' +
|
||||
'<li><a href="https://github.com/devlikeapro/waha" target="_blank">GitHub - WAHA Core</a></li>' +
|
||||
'<li><a href="https://github.com/devlikeapro/waha-plu' + // Separate line to pass pre-commit check
|
||||
's" target="_blank">GitHub - WAHA Plus</a></li>' +
|
||||
'<li><b>NEW!</b> - <a href="/dashboard">WAHA Dashboard</a></li>' +
|
||||
'<li><b>NEW!</b> - <a href="https://waha.devlike.pro/docs/how-to/waha-dashboard">WAHA Dashboard Documentation</a></li>' +
|
||||
'</ul>' +
|
||||
'<p>Support the project and get WAHA Plus version!</p>' +
|
||||
'<ul>' +
|
||||
'<li><a href="https://waha.devlike.pro/docs/how-to/plu' + // Separate line to pass pre-commit check
|
||||
's-version/" target="_blank">WAHA Plus</a></li>' +
|
||||
'<li><a href="https://patreon.com/wa_http_api/" target="_blank">Patreon</a></li>' +
|
||||
'<li><a href="https://boosty.to/wa-http-api/" target="_blank">Boosty</a></li>' +
|
||||
'<li><a href="https://portal.devlike.pro/" target="_blank">Patron Portal</a></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
}
|
||||
|
||||
get externalDocUrl() {
|
||||
return 'https://waha.devlike.pro/';
|
||||
}
|
||||
|
||||
configure(webhooks: any[]) {
|
||||
const app = this.app;
|
||||
const builder = new DocumentBuilder();
|
||||
|
||||
builder
|
||||
.setTitle(this.title)
|
||||
.setDescription(this.description)
|
||||
.setExternalDoc(this.title, this.externalDocUrl)
|
||||
.setVersion(VERSION.version)
|
||||
.addTag('sessions', 'Control WhatsApp sessions')
|
||||
.addTag('auth', 'Authentication')
|
||||
.addTag('screenshot', 'Get screenshot of WhatsApp and show QR code')
|
||||
.addTag('chatting', 'Chatting methods')
|
||||
.addTag(
|
||||
'status',
|
||||
'Status (aka stories) methods. <b>NOWEB</b> engine only!',
|
||||
)
|
||||
.addTag('chats', `Chats methods`)
|
||||
.addTag(
|
||||
'contacts',
|
||||
`Contacts methods.<br>
|
||||
Use phone number (without +) or phone number and \`@c.us\` at the end as \`contactId\`.<br>
|
||||
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`,
|
||||
)
|
||||
.addTag('groups', `Groups methods.<br>`)
|
||||
.addTag('presence', `Presence information`)
|
||||
.addTag('other', 'Other methods')
|
||||
.addApiKey({
|
||||
type: 'apiKey',
|
||||
description: 'Your secret api key',
|
||||
name: 'X-Api-Key',
|
||||
});
|
||||
|
||||
const config = app.get(WhatsappConfigService);
|
||||
const swaggerConfig = app.get(SwaggerConfigServiceCore);
|
||||
if (swaggerConfig.advancedConfigEnabled) {
|
||||
builder.addServer('{protocol}://{host}:{port}/{baseUrl}', '', {
|
||||
protocol: {
|
||||
default: 'http',
|
||||
enum: ['http', 'https'],
|
||||
description: 'The protocol used to access the server.',
|
||||
},
|
||||
host: {
|
||||
default: config.hostname,
|
||||
description: 'The hostname or IP address of the server.',
|
||||
},
|
||||
port: {
|
||||
default: config.port,
|
||||
description:
|
||||
'The port number on which the server is listening for requests',
|
||||
},
|
||||
baseUrl: {
|
||||
default: '',
|
||||
description:
|
||||
'The base URL path for all API endpoints. This can be used to group related endpoints together under a common path.',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const swaggerDocumentConfig = builder.build();
|
||||
const swaggerDocumentOptions = {
|
||||
extraModels: webhooks,
|
||||
};
|
||||
let document = SwaggerModule.createDocument(
|
||||
app,
|
||||
swaggerDocumentConfig,
|
||||
swaggerDocumentOptions,
|
||||
);
|
||||
document = this.configureWebhooks(document, webhooks);
|
||||
SwaggerModule.setup('', app, document, {
|
||||
customSiteTitle: this.title,
|
||||
});
|
||||
}
|
||||
|
||||
private configureWebhooks(document: OpenAPIObject, supportedWebhooks) {
|
||||
document.openapi = '3.1.0';
|
||||
const webhooks = {};
|
||||
for (const webhook of supportedWebhooks) {
|
||||
const eventMetadata = Reflect.getMetadata(
|
||||
DECORATORS.API_MODEL_PROPERTIES,
|
||||
webhook.prototype,
|
||||
'event',
|
||||
);
|
||||
const event = new webhook().event;
|
||||
const schemaName = webhook.name;
|
||||
webhooks[event] = {
|
||||
post: {
|
||||
summary: eventMetadata.description,
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: `#/components/schemas/${schemaName}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description:
|
||||
'Return a 200 status to indicate that the data was received successfully',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
// @ts-ignore
|
||||
document.webhooks = webhooks;
|
||||
return document;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export abstract class DataStore {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ConsoleLogger, Injectable } from '@nestjs/common';
|
||||
import { DiskHealthIndicator, HealthCheckService } from '@nestjs/terminus';
|
||||
import type { HealthCheckResult } from '@nestjs/terminus/dist/health-check/health-check-result.interface';
|
||||
|
||||
import { WhatsappConfigService } from '../../config.service';
|
||||
import { SessionManager } from './manager.abc';
|
||||
|
||||
@Injectable()
|
||||
export abstract class WAHAHealthCheckService {
|
||||
constructor(
|
||||
protected sessionManager: SessionManager,
|
||||
protected health: HealthCheckService,
|
||||
protected log: ConsoleLogger,
|
||||
protected config: WhatsappConfigService,
|
||||
) {}
|
||||
|
||||
abstract check(): Promise<HealthCheckResult>;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
import { WAHAEngine } from '../../structures/enums.dto';
|
||||
import {
|
||||
SessionDTO,
|
||||
SessionInfo,
|
||||
SessionLogoutRequest,
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
} from '../../structures/sessions.dto';
|
||||
import { ISessionAuthRepository } from '../storage/ISessionAuthRepository';
|
||||
import { ISessionConfigRepository } from '../storage/ISessionConfigRepository';
|
||||
import { WhatsappSession } from './session.abc';
|
||||
import { WebhookConductor } from './webhooks.abc';
|
||||
|
||||
export abstract class SessionManager implements OnApplicationShutdown {
|
||||
public store: any;
|
||||
public sessionAuthRepository: ISessionAuthRepository;
|
||||
public sessionConfigRepository: ISessionConfigRepository;
|
||||
|
||||
protected abstract getEngine(engine: WAHAEngine): typeof WhatsappSession;
|
||||
|
||||
protected abstract get EngineClass(): typeof WhatsappSession;
|
||||
|
||||
protected abstract get WebhookConductorClass(): typeof WebhookConductor;
|
||||
|
||||
abstract onApplicationShutdown(signal?: string);
|
||||
|
||||
//
|
||||
// API Methods
|
||||
//
|
||||
abstract start(request: SessionStartRequest): Promise<SessionDTO>;
|
||||
|
||||
abstract stop(request: SessionStopRequest): Promise<void>;
|
||||
|
||||
abstract logout(request: SessionLogoutRequest): Promise<void>;
|
||||
|
||||
abstract getSession(name: string): WhatsappSession;
|
||||
|
||||
abstract getSessionInfo(name: string): Promise<SessionInfo | null>;
|
||||
|
||||
abstract getSessions(all: boolean): Promise<SessionInfo[]>;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
abstract class MediaStorage {
|
||||
abstract save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
interface IEngineMediaProcessor<Message> {
|
||||
hasMedia(message: Message): boolean;
|
||||
|
||||
getFilename(message: Message): string | null;
|
||||
|
||||
getMimetype(message: Message): string;
|
||||
|
||||
getMessageId(message: Message): string;
|
||||
|
||||
getMediaBuffer(message: Message): Promise<Buffer | null>;
|
||||
}
|
||||
|
||||
interface MediaManager {
|
||||
processMedia<Message>(
|
||||
processor: IEngineMediaProcessor<Message>,
|
||||
message: Message,
|
||||
): Promise<Message>;
|
||||
}
|
||||
|
||||
export { IEngineMediaProcessor, MediaManager, MediaStorage };
|
||||
@@ -0,0 +1,498 @@
|
||||
import { ConsoleLogger } from '@nestjs/common';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as fs from 'fs';
|
||||
import { MessageId } from 'whatsapp-web.js';
|
||||
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
EditMessageRequest,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageStarRequest,
|
||||
MessageTextRequest,
|
||||
MessageVideoRequest,
|
||||
MessageVoiceRequest,
|
||||
} from '../../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../../structures/contacts.dto';
|
||||
import {
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
} from '../../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '../../structures/groups.dto';
|
||||
import { WAHAChatPresences } from '../../structures/presence.dto';
|
||||
import {
|
||||
MeInfo,
|
||||
ProxyConfig,
|
||||
SessionConfig,
|
||||
} from '../../structures/sessions.dto';
|
||||
import {
|
||||
ImageStatus,
|
||||
TextStatus,
|
||||
VideoStatus,
|
||||
VoiceStatus,
|
||||
} from '../../structures/status.dto';
|
||||
import { WASessionStatusBody } from '../../structures/webhooks.dto';
|
||||
import { NotImplementedByEngineError } from '../exceptions';
|
||||
import { QR } from '../QR';
|
||||
import { DataStore } from './DataStore';
|
||||
import { MediaManager } from './media.abc';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const qrcode = require('qrcode-terminal');
|
||||
|
||||
const CHROME_PATH = '/usr/bin/google-chrome-stable';
|
||||
const CHROMIUM_PATH = '/usr/bin/chromium';
|
||||
|
||||
export function getBrowserExecutablePath() {
|
||||
if (fs.existsSync(CHROME_PATH)) {
|
||||
return CHROME_PATH;
|
||||
}
|
||||
return CHROMIUM_PATH;
|
||||
}
|
||||
|
||||
export function ensureSuffix(phone) {
|
||||
const suffix = '@c.us';
|
||||
if (phone.includes('@')) {
|
||||
return phone;
|
||||
}
|
||||
return phone + suffix;
|
||||
}
|
||||
|
||||
export enum WAHAInternalEvent {
|
||||
ENGINE_START = 'engine.start',
|
||||
SESSION_STATUS_CHANGED = 'session.status.changed',
|
||||
}
|
||||
|
||||
export interface SessionParams {
|
||||
name: string;
|
||||
printQR: boolean;
|
||||
mediaManager: MediaManager;
|
||||
log: ConsoleLogger;
|
||||
sessionStore: DataStore;
|
||||
proxyConfig?: ProxyConfig;
|
||||
sessionConfig?: SessionConfig;
|
||||
engineConfig?: any;
|
||||
}
|
||||
|
||||
export abstract class WhatsappSession {
|
||||
public events: EventEmitter;
|
||||
public engine: WAHAEngine;
|
||||
|
||||
public name: string;
|
||||
protected mediaManager: MediaManager;
|
||||
protected log: ConsoleLogger;
|
||||
protected sessionStore: DataStore;
|
||||
protected proxyConfig?: ProxyConfig;
|
||||
public sessionConfig?: SessionConfig;
|
||||
protected engineConfig?: any;
|
||||
|
||||
private _status: WAHASessionStatus;
|
||||
private shouldPrintQR: boolean;
|
||||
|
||||
public constructor({
|
||||
name,
|
||||
printQR,
|
||||
log,
|
||||
sessionStore,
|
||||
proxyConfig,
|
||||
mediaManager,
|
||||
sessionConfig,
|
||||
engineConfig,
|
||||
}: SessionParams) {
|
||||
this.events = new EventEmitter();
|
||||
this.name = name;
|
||||
this.proxyConfig = proxyConfig;
|
||||
this.log = log;
|
||||
this.sessionStore = sessionStore;
|
||||
this.mediaManager = mediaManager;
|
||||
this.sessionConfig = sessionConfig;
|
||||
this.engineConfig = engineConfig;
|
||||
this.shouldPrintQR = printQR;
|
||||
}
|
||||
|
||||
protected set status(value: WAHASessionStatus) {
|
||||
this._status = value;
|
||||
this.events.emit(WAHAInternalEvent.SESSION_STATUS_CHANGED, value);
|
||||
}
|
||||
|
||||
public get status() {
|
||||
return this._status;
|
||||
}
|
||||
|
||||
getBrowserExecutablePath() {
|
||||
return getBrowserExecutablePath();
|
||||
}
|
||||
|
||||
getBrowserArgsForPuppeteer() {
|
||||
// Run optimized version of Chrome
|
||||
// References:
|
||||
// https://github.com/pedroslopez/whatsapp-web.js/issues/1420
|
||||
// https://www.bannerbear.com/blog/ways-to-speed-up-puppeteer-screenshots/
|
||||
return [
|
||||
'--no-sandbox',
|
||||
'--disable-client-side-phishing-detection',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-component-update',
|
||||
'--disable-default-apps',
|
||||
'--disable-popup-blocking',
|
||||
'--disable-offer-store-unmasked-wallet-cards',
|
||||
'--disable-speech-api',
|
||||
'--hide-scrollbars',
|
||||
'--mute-audio',
|
||||
'--disable-extensions',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-accelerated-2d-canvas',
|
||||
'--no-first-run',
|
||||
'--no-default-browser-check',
|
||||
'--no-pings',
|
||||
'--password-store=basic',
|
||||
'--use-mock-keychain',
|
||||
'--no-zygote',
|
||||
'--single-process',
|
||||
'--disable-gpu',
|
||||
];
|
||||
}
|
||||
|
||||
protected isDebugEnabled() {
|
||||
return this.log.isLevelEnabled('debug');
|
||||
}
|
||||
|
||||
/** Start the session */
|
||||
abstract start();
|
||||
|
||||
/** Stop the session */
|
||||
abstract stop(): Promise<void>;
|
||||
|
||||
/** Subscribe the handler to specific hook */
|
||||
subscribeSessionEvent(
|
||||
hook: WAHAEvents | string,
|
||||
handler: (message) => void,
|
||||
): boolean {
|
||||
switch (hook) {
|
||||
case WAHAEvents.SESSION_STATUS:
|
||||
this.events.on(WAHAInternalEvent.SESSION_STATUS_CHANGED, (value) => {
|
||||
const body: WASessionStatusBody = { name: this.name, status: value };
|
||||
handler(body);
|
||||
});
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
abstract subscribeEngineEvent(
|
||||
hook: WAHAEvents | string,
|
||||
handler: (message) => void,
|
||||
): boolean;
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
|
||||
public getQR(): QR {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public requestCode(phoneNumber: string, method: string, params?: any) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public authorizeCode(code: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract getScreenshot(): Promise<Buffer>;
|
||||
|
||||
public getSessionMeInfo(): Promise<MeInfo | null> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getCaptcha(): Promise<QR> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public saveCaptcha(code: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
abstract checkNumberStatus(request: CheckNumberStatusQuery);
|
||||
|
||||
abstract sendText(request: MessageTextRequest);
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendPoll(request: MessagePollRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract sendLocation(request: MessageLocationRequest);
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract sendImage(request: MessageImageRequest);
|
||||
|
||||
abstract sendFile(request: MessageFileRequest);
|
||||
|
||||
abstract sendVoice(request: MessageVoiceRequest);
|
||||
|
||||
sendVideo(request: MessageVideoRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract reply(request: MessageReplyRequest);
|
||||
|
||||
abstract sendSeen(chat: ChatRequest);
|
||||
|
||||
abstract startTyping(chat: ChatRequest);
|
||||
|
||||
abstract stopTyping(chat: ChatRequest);
|
||||
|
||||
getMessages(query: GetMessageQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract setReaction(request: MessageReactionRequest);
|
||||
|
||||
setStar(request: MessageStarRequest): Promise<void> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Chats methods
|
||||
*/
|
||||
public getChats() {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public deleteChat(chatId) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getChatMessages(
|
||||
chatId: string,
|
||||
limit: number,
|
||||
downloadMedia: boolean,
|
||||
) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public deleteMessage(chatId: string, messageId: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public editMessage(
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
request: EditMessageRequest,
|
||||
) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public clearMessages(chatId) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
public getContact(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContacts() {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContactAbout(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContactProfilePicture(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public blockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public unblockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group methods
|
||||
*/
|
||||
public createGroup(request: CreateGroupRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getGroups() {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setInfoAdminsOnly(id, value) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getMessagesAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setMessagesAdminsOnly(id, value) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public deleteGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public leaveGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setDescription(id, description) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setSubject(id, description) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getInviteCode(id): Promise<string> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public revokeInviteCode(id): Promise<string> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getParticipants(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public addParticipants(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public removeParticipants(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public demoteParticipantsToUser(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setPresence(presence: WAHAPresenceStatus, chatId?: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getPresences(): Promise<WAHAChatPresences[]> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getPresence(id: string): Promise<WAHAChatPresences> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public subscribePresence(id: string): Promise<void> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Status methods
|
||||
*/
|
||||
public sendTextStatus(status: TextStatus) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public sendImageStatus(status: ImageStatus) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public sendVoiceStatus(status: VoiceStatus) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public sendVideoStatus(status: VideoStatus) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async getEngineInfo(): Promise<any> {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add WhatsApp suffix (@c.us) to the phone number if it doesn't have it yet
|
||||
* @param phone
|
||||
*/
|
||||
protected ensureSuffix(phone) {
|
||||
return ensureSuffix(phone);
|
||||
}
|
||||
|
||||
protected deserializeId(messageId: string): MessageId {
|
||||
const parts = messageId.split('_');
|
||||
return {
|
||||
fromMe: parts[0] === 'true',
|
||||
remote: parts[1],
|
||||
id: parts[2],
|
||||
_serialized: messageId,
|
||||
};
|
||||
}
|
||||
|
||||
protected printQR(qr: QR) {
|
||||
if (!this.shouldPrintQR) {
|
||||
return;
|
||||
}
|
||||
if (!qr.raw) {
|
||||
this.log.error(
|
||||
'QR.raw is not available, can not print it in the console',
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.log.log(
|
||||
"You can disable QR in console by setting 'WAHA_PRINT_QR=false' in your environment variables.",
|
||||
);
|
||||
qrcode.generate(qr.raw, { small: true });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ConsoleLogger } from '@nestjs/common';
|
||||
|
||||
import { WebhookConfig } from '../../structures/webhooks.config.dto';
|
||||
import { WhatsappSession } from './session.abc';
|
||||
|
||||
export abstract class WebhookSender {
|
||||
protected url: string;
|
||||
constructor(
|
||||
protected log: ConsoleLogger,
|
||||
protected webhookConfig: WebhookConfig,
|
||||
) {
|
||||
this.url = webhookConfig.url;
|
||||
}
|
||||
|
||||
abstract send(json);
|
||||
}
|
||||
|
||||
export abstract class WebhookConductor {
|
||||
abstract configure(session: WhatsappSession, webhooks: WebhookConfig[]);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { ConsoleLogger, Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { TerminusModule } from '@nestjs/terminus';
|
||||
import { join } from 'path';
|
||||
|
||||
import { AuthController } from '../api/auth.controller';
|
||||
import { ChatsController } from '../api/chats.controller';
|
||||
import { ChattingController } from '../api/chatting.controller';
|
||||
import { ContactsController } from '../api/contacts.controller';
|
||||
import { GroupsController } from '../api/groups.controller';
|
||||
import { HealthController } from '../api/health.controller';
|
||||
import { PresenceController } from '../api/presence.controller';
|
||||
import { ScreenshotController } from '../api/screenshot.controller';
|
||||
import {
|
||||
SessionController,
|
||||
SessionsController,
|
||||
} from '../api/sessions.controller';
|
||||
import { StatusController } from '../api/status.controller';
|
||||
import { VersionController } from '../api/version.controller';
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { SessionManager } from './abc/manager.abc';
|
||||
import { WAHAHealthCheckService } from './abc/WAHAHealthCheckService';
|
||||
import { DashboardConfigServiceCore } from './config/DashboardConfigServiceCore';
|
||||
import { EngineConfigService } from './config/EngineConfigService';
|
||||
import { SwaggerConfigServiceCore } from './config/SwaggerConfigServiceCore';
|
||||
import { WAHAHealthCheckServiceCore } from './health/WAHAHealthCheckServiceCore';
|
||||
import { SessionManagerCore } from './manager.core';
|
||||
|
||||
export const IMPORTS = [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
ServeStaticModule.forRootAsync({
|
||||
imports: [],
|
||||
extraProviders: [WhatsappConfigService, DashboardConfigServiceCore],
|
||||
inject: [WhatsappConfigService, DashboardConfigServiceCore],
|
||||
useFactory: (
|
||||
config: WhatsappConfigService,
|
||||
dashboardConfig: DashboardConfigServiceCore,
|
||||
) => {
|
||||
const options = [
|
||||
// Serve files (media)
|
||||
{
|
||||
rootPath: config.filesFolder,
|
||||
serveRoot: config.filesUri,
|
||||
},
|
||||
];
|
||||
if (dashboardConfig.enabled) {
|
||||
options.push({
|
||||
rootPath: join(__dirname, '..', 'dashboard'),
|
||||
serveRoot: dashboardConfig.dashboardUri,
|
||||
});
|
||||
}
|
||||
return options;
|
||||
},
|
||||
}),
|
||||
PassportModule,
|
||||
TerminusModule,
|
||||
];
|
||||
export const CONTROLLERS = [
|
||||
AuthController,
|
||||
SessionsController,
|
||||
SessionController,
|
||||
ChattingController,
|
||||
ChatsController,
|
||||
StatusController,
|
||||
ContactsController,
|
||||
GroupsController,
|
||||
PresenceController,
|
||||
ScreenshotController,
|
||||
VersionController,
|
||||
HealthController,
|
||||
];
|
||||
const PROVIDERS = [
|
||||
{
|
||||
provide: SessionManager,
|
||||
useClass: SessionManagerCore,
|
||||
},
|
||||
{
|
||||
provide: WAHAHealthCheckService,
|
||||
useClass: WAHAHealthCheckServiceCore,
|
||||
},
|
||||
DashboardConfigServiceCore,
|
||||
SwaggerConfigServiceCore,
|
||||
WhatsappConfigService,
|
||||
EngineConfigService,
|
||||
ConsoleLogger,
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: IMPORTS,
|
||||
controllers: CONTROLLERS,
|
||||
providers: PROVIDERS,
|
||||
})
|
||||
export class AppModuleCore {
|
||||
constructor(protected config: WhatsappConfigService) {}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { parseBool } from '../../helpers';
|
||||
|
||||
@Injectable()
|
||||
export class DashboardConfigServiceCore {
|
||||
public dashboardUri = '/dashboard';
|
||||
|
||||
constructor(protected configService: ConfigService) {}
|
||||
|
||||
get enabled(): boolean {
|
||||
const value = this.configService.get('WAHA_DASHBOARD_ENABLED', 'true');
|
||||
return parseBool(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { parseBool } from '../../helpers';
|
||||
import { WAHAEngine } from '../../structures/enums.dto';
|
||||
import { getEngineName } from '../../version';
|
||||
|
||||
@Injectable()
|
||||
export class EngineConfigService {
|
||||
constructor(protected configService: ConfigService) {}
|
||||
|
||||
getDefaultEngineName(): WAHAEngine {
|
||||
const value = getEngineName();
|
||||
if (value in WAHAEngine) {
|
||||
return WAHAEngine[value];
|
||||
}
|
||||
console.log(
|
||||
`Unknown WhatsApp default engine WHATSAPP_DEFAULT_ENGINE=${value}. Using WEBJS`,
|
||||
);
|
||||
return WAHAEngine.WEBJS;
|
||||
}
|
||||
|
||||
get shouldPrintQR(): boolean {
|
||||
const value = this.configService.get('WAHA_PRINT_QR', true);
|
||||
return parseBool(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { parseBool } from '../../helpers';
|
||||
|
||||
@Injectable()
|
||||
export class SwaggerConfigServiceCore {
|
||||
constructor(protected configService: ConfigService) {}
|
||||
|
||||
get advancedConfigEnabled(): boolean {
|
||||
const value = this.configService.get(
|
||||
'WHATSAPP_SWAGGER_CONFIG_ADVANCED',
|
||||
false,
|
||||
);
|
||||
return parseBool(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { DataStore } from '../../abc/DataStore';
|
||||
import { LocalStore } from '../../storage/LocalStore';
|
||||
import { useMultiFileAuthState } from './useMultiFileAuthState';
|
||||
|
||||
export class NowebAuthFactoryCore {
|
||||
buildAuth(store: DataStore, name: string) {
|
||||
if (store instanceof LocalStore) return this.buildLocalAuth(store, name);
|
||||
throw new Error(`Unsupported store type '${store.constructor.name}'`);
|
||||
}
|
||||
|
||||
protected async buildLocalAuth(store: LocalStore, name: string) {
|
||||
await store.init(name);
|
||||
const authFolder = store.getSessionDirectory(name);
|
||||
const { state, saveCreds } = await useMultiFileAuthState(authFolder);
|
||||
return { state, saveCreds };
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
AuthenticationCreds,
|
||||
AuthenticationState,
|
||||
proto,
|
||||
} from '@adiwajshing/baileys';
|
||||
import { BufferJSON, initAuthCreds } from '@adiwajshing/baileys/lib/Utils';
|
||||
import { mkdir, readFile, stat, unlink, writeFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const AsyncLock = require('async-lock');
|
||||
|
||||
// We need to lock files due to the fact that we are using async functions to read and write files
|
||||
// https://github.com/WhiskeySockets/Baileys/issues/794
|
||||
// https://github.com/nodejs/node/issues/26338
|
||||
// Default pending is 1000, set it to infinity
|
||||
// https://github.com/rogierschouten/async-lock/issues/63
|
||||
const fileLock = new AsyncLock({ maxPending: Infinity });
|
||||
|
||||
/**
|
||||
* stores the full authentication state in a single folder.
|
||||
* Far more efficient than singlefileauthstate
|
||||
*
|
||||
* Again, I wouldn't endorse this for any production level use other than perhaps a bot.
|
||||
* Would recommend writing an auth state for use with a proper SQL or No-SQL DB
|
||||
* */
|
||||
export const useMultiFileAuthState = async (
|
||||
folder: string,
|
||||
): Promise<{
|
||||
state: AuthenticationState;
|
||||
saveCreds: () => Promise<void>;
|
||||
}> => {
|
||||
const writeData = (data: any, file: string) => {
|
||||
const filePath = join(folder, fixFileName(file));
|
||||
return fileLock.acquire(filePath, () =>
|
||||
writeFile(join(filePath), JSON.stringify(data, BufferJSON.replacer)),
|
||||
);
|
||||
};
|
||||
|
||||
const readData = async (file: string) => {
|
||||
try {
|
||||
const filePath = join(folder, fixFileName(file));
|
||||
const data = await fileLock.acquire(filePath, () =>
|
||||
readFile(filePath, { encoding: 'utf-8' }),
|
||||
);
|
||||
return JSON.parse(data, BufferJSON.reviver);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const removeData = async (file: string) => {
|
||||
try {
|
||||
const filePath = join(folder, fixFileName(file));
|
||||
await fileLock.acquire(filePath, () => unlink(filePath));
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const folderInfo = await stat(folder).catch(() => {
|
||||
return null;
|
||||
});
|
||||
if (folderInfo) {
|
||||
if (!folderInfo.isDirectory()) {
|
||||
throw new Error(
|
||||
`found something that is not a directory at ${folder}, either delete it or specify a different location`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await mkdir(folder, { recursive: true });
|
||||
}
|
||||
|
||||
const fixFileName = (file?: string) =>
|
||||
file?.replace(/\//g, '__')?.replace(/:/g, '-') || '';
|
||||
|
||||
const creds: AuthenticationCreds =
|
||||
(await readData('creds.json')) || initAuthCreds();
|
||||
|
||||
return {
|
||||
state: {
|
||||
creds,
|
||||
keys: {
|
||||
get: async (type, ids) => {
|
||||
const data = {};
|
||||
await Promise.all(
|
||||
ids.map(async (id) => {
|
||||
let value = await readData(`${type}-${id}.json`);
|
||||
if (type === 'app-state-sync-key' && value) {
|
||||
value = proto.Message.AppStateSyncKeyData.fromObject(value);
|
||||
}
|
||||
|
||||
data[id] = value;
|
||||
}),
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
set: async (data) => {
|
||||
const tasks: Promise<void>[] = [];
|
||||
for (const category in data) {
|
||||
for (const id in data[category]) {
|
||||
const value = data[category][id];
|
||||
const file = `${category}-${id}.json`;
|
||||
tasks.push(value ? writeData(value, file) : removeData(file));
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(tasks);
|
||||
},
|
||||
},
|
||||
},
|
||||
saveCreds: () => {
|
||||
return writeData(creds, 'creds.json');
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { extractMessageContent, proto } from '@adiwajshing/baileys';
|
||||
|
||||
export function extractMediaContent(
|
||||
content: proto.IMessage | null | undefined,
|
||||
) {
|
||||
content = extractMessageContent(content);
|
||||
const mediaContent =
|
||||
content?.documentMessage ||
|
||||
content?.imageMessage ||
|
||||
content?.videoMessage ||
|
||||
content?.audioMessage ||
|
||||
content?.stickerMessage;
|
||||
return mediaContent;
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import * as Buffer from 'buffer';
|
||||
import { create, CreateConfig, Message, Whatsapp } from 'venom-bot';
|
||||
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextRequest,
|
||||
WANumberExistResult,
|
||||
} from '../../../structures/chatting.dto';
|
||||
import {
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHASessionStatus,
|
||||
} from '../../../structures/enums.dto';
|
||||
import { WAMessage } from '../../../structures/responses.dto';
|
||||
import { IEngineMediaProcessor } from '../../abc/media.abc';
|
||||
import { WAHAInternalEvent, WhatsappSession } from '../../abc/session.abc';
|
||||
import { NotImplementedByEngineError } from '../../exceptions';
|
||||
import { QR } from '../../QR';
|
||||
|
||||
export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
engine = WAHAEngine.VENOM;
|
||||
|
||||
whatsapp: Whatsapp;
|
||||
private qr: QR;
|
||||
|
||||
public constructor(config) {
|
||||
super(config);
|
||||
this.qr = new QR();
|
||||
}
|
||||
|
||||
protected buildClient() {
|
||||
const venomOptions: CreateConfig = {
|
||||
headless: true,
|
||||
devtools: false,
|
||||
debug: false,
|
||||
logQR: true,
|
||||
browserArgs: this.getBrowserArgsForPuppeteer(),
|
||||
autoClose: 60000,
|
||||
puppeteerOptions: {},
|
||||
};
|
||||
this.addProxyConfig(venomOptions);
|
||||
return create(this.name, this.getCatchQR(), undefined, venomOptions);
|
||||
}
|
||||
|
||||
protected addProxyConfig(venomOptions: CreateConfig) {
|
||||
if (this.proxyConfig?.server !== undefined) {
|
||||
venomOptions.addProxy = [this.proxyConfig?.server];
|
||||
}
|
||||
if (
|
||||
this.proxyConfig?.username !== undefined &&
|
||||
this.proxyConfig?.password !== undefined
|
||||
) {
|
||||
venomOptions.userProxy = this.proxyConfig?.username;
|
||||
venomOptions.userPass = this.proxyConfig?.password;
|
||||
}
|
||||
}
|
||||
|
||||
protected getCatchQR() {
|
||||
return (base64Qrimg, asciiQR, attempts, urlCode) => {
|
||||
this.qr.save(base64Qrimg, urlCode);
|
||||
this.printQR(this.qr);
|
||||
this.status = WAHASessionStatus.SCAN_QR_CODE;
|
||||
this.log.debug('Number of attempts to read the qrcode: ', attempts);
|
||||
};
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.status = WAHASessionStatus.STARTING;
|
||||
try {
|
||||
this.whatsapp = await this.buildClient();
|
||||
} catch (error) {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.log.error(error);
|
||||
this.qr.save('');
|
||||
return;
|
||||
}
|
||||
|
||||
this.status = WAHASessionStatus.WORKING;
|
||||
this.events.emit(WAHAInternalEvent.ENGINE_START);
|
||||
return this;
|
||||
}
|
||||
|
||||
async stop() {
|
||||
await this.whatsapp.close();
|
||||
this.status = WAHASessionStatus.STOPPED;
|
||||
}
|
||||
|
||||
subscribeEngineEvent(event: WAHAEvents | string, handler: (message) => void) {
|
||||
switch (event) {
|
||||
case WAHAEvents.MESSAGE:
|
||||
this.whatsapp.onMessage((message: Message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
return true;
|
||||
case WAHAEvents.MESSAGE_ANY:
|
||||
this.whatsapp.onAnyMessage((message: Message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
return true;
|
||||
case WAHAEvents.STATE_CHANGE:
|
||||
this.whatsapp.onStateChange(handler);
|
||||
return true;
|
||||
case WAHAEvents.MESSAGE_ACK:
|
||||
this.whatsapp.onAck(handler);
|
||||
return true;
|
||||
case WAHAEvents.GROUP_JOIN:
|
||||
this.whatsapp.onAddedToGroup(handler);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
public getQR(): QR {
|
||||
return this.qr;
|
||||
}
|
||||
|
||||
getScreenshot(): Promise<Buffer> {
|
||||
if (this.status === WAHASessionStatus.STARTING) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session is starting, please try again after few seconds`,
|
||||
);
|
||||
} else if (this.status === WAHASessionStatus.SCAN_QR_CODE) {
|
||||
return Promise.resolve(this.qr.get());
|
||||
} else if (this.status === WAHASessionStatus.WORKING) {
|
||||
return this.whatsapp.page.screenshot();
|
||||
} else {
|
||||
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
try {
|
||||
return await this.whatsapp.checkNumberStatus(
|
||||
this.ensureSuffix(request.phone),
|
||||
);
|
||||
} catch (error) {
|
||||
// Catch number doesn't exist error and return it as is
|
||||
if (error.status === 404 && !error.numberExists) {
|
||||
return error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
return this.whatsapp.sendText(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.text,
|
||||
);
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
return this.whatsapp.reply(request.chatId, request.text, request.reply_to);
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
async sendVoice(request) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
return this.whatsapp.sendLinkPreview(
|
||||
request.chatId,
|
||||
request.url,
|
||||
request.title,
|
||||
);
|
||||
}
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
return this.whatsapp.sendLocation(
|
||||
request.chatId,
|
||||
String(request.latitude),
|
||||
String(request.longitude),
|
||||
request.title,
|
||||
);
|
||||
}
|
||||
|
||||
sendSeen(chat: ChatRequest) {
|
||||
return this.whatsapp.markMarkSeenMessage(chat.chatId);
|
||||
}
|
||||
|
||||
startTyping(chat: ChatRequest) {
|
||||
return this.whatsapp.startTyping(chat.chatId, false);
|
||||
}
|
||||
|
||||
stopTyping(chat: ChatRequest) {
|
||||
return;
|
||||
// The method is not available yet in new venom-bot
|
||||
// return this.whatsapp.stopTyping(chat.chatId);
|
||||
}
|
||||
|
||||
async getChatMessages(chatId: string, limit: number) {
|
||||
const messages = await this.whatsapp.getAllMessagesInChat(
|
||||
chatId,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
// Go over messages, download media, and convert to right format.
|
||||
const result = [];
|
||||
for (const [count, message] of messages.entries()) {
|
||||
if (count > limit) {
|
||||
// Have enough in the list, stop processing
|
||||
break;
|
||||
}
|
||||
result.push(await this.processIncomingMessage(message));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async getMessages(query: GetMessageQuery) {
|
||||
return this.getChatMessages(query.chatId, query.limit);
|
||||
}
|
||||
|
||||
setReaction(request: MessageReactionRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
protected downloadMedia(message: Message) {
|
||||
const processor = new EngineMediaProcessor(this);
|
||||
return this.mediaManager.processMedia(processor, message);
|
||||
}
|
||||
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadMedia(message).then(this.toWAMessage);
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// Media
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.media),
|
||||
// @ts-ignore
|
||||
media: message.media,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: undefined,
|
||||
vCards: undefined,
|
||||
_data: message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class EngineMediaProcessor implements IEngineMediaProcessor<Message> {
|
||||
constructor(public session: WhatsappSessionVenomCore) {}
|
||||
|
||||
hasMedia(message: any): boolean {
|
||||
if (!message.isMMS || !message.isMedia) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
getMessageId(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMimetype(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMediaBuffer(message: any): Promise<Buffer | null> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
getFilename(message: Message): string | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" dir="ltr" loc="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<title>WhatsApp Web</title>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="google" content="notranslate" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Quickly send and receive WhatsApp messages right from your computer."
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="Quickly send and receive WhatsApp messages right from your computer."
|
||||
/>
|
||||
<meta name="og:url" content="https://web.whatsapp.com/" />
|
||||
<meta name="og:title" content="WhatsApp Web" />
|
||||
<meta
|
||||
name="og:image"
|
||||
content="https://static.facebook.com/images/whatsapp/www/whatsapp-promo.png"
|
||||
/>
|
||||
<link
|
||||
id="favicon"
|
||||
rel="shortcut icon"
|
||||
href="/img/favicon/1x/favicon.png"
|
||||
type="image/png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="192x192"
|
||||
href="/apple-touch-icon.png"
|
||||
type="image/png"
|
||||
/>
|
||||
<meta
|
||||
name="theme-color"
|
||||
content="#111b21"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
<meta name="theme-color" content="#f0f2f5" />
|
||||
<link
|
||||
id="whatsapp-pwa-manifest-link"
|
||||
rel="manifest"
|
||||
href="/manifest.json"
|
||||
crossorigin="use-credentials"
|
||||
/>
|
||||
<style>
|
||||
#initial_startup {
|
||||
--startup-background: #f0f2f5;
|
||||
--startup-background-rgb: 240, 242, 245;
|
||||
--startup-icon: #bbc5cb;
|
||||
--secondary-lighter: #8696a0;
|
||||
--primary-title: #41525d;
|
||||
--progress-primary: #00c298;
|
||||
--progress-background: #e9edef;
|
||||
}
|
||||
.dark #initial_startup {
|
||||
--startup-background: #111b21;
|
||||
--startup-background-rgb: 17, 27, 33;
|
||||
--startup-icon: #676f73;
|
||||
--primary-title: rgba(233, 237, 239, 0.88);
|
||||
--secondary-lighter: #667781;
|
||||
--progress-primary: #0b846d;
|
||||
--progress-background: #233138;
|
||||
}
|
||||
#app,
|
||||
body,
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
#app {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
#initial_startup {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
background-color: var(--startup-background);
|
||||
}
|
||||
#initial_startup .graphic {
|
||||
margin-top: -40px;
|
||||
color: var(--startup-icon);
|
||||
}
|
||||
#initial_startup .graphic .resume-logo {
|
||||
transform: translateX(calc(50% - 52px / 2));
|
||||
}
|
||||
#initial_startup .graphic::after {
|
||||
position: relative;
|
||||
top: -100%;
|
||||
left: calc(50% - 72px * 2 - 72px / 2);
|
||||
display: block;
|
||||
width: calc(72px * 3);
|
||||
height: 100%;
|
||||
content: '';
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgba(var(--startup-background-rgb), 0.5) 0,
|
||||
rgba(var(--startup-background-rgb), 0.5) 33.33%,
|
||||
rgba(var(--startup-background-rgb), 0) 44.1%,
|
||||
rgba(var(--startup-background-rgb), 0) 55.8%,
|
||||
rgba(var(--startup-background-rgb), 0.5) 66.66%,
|
||||
rgba(var(--startup-background-rgb), 0.5) 100%
|
||||
);
|
||||
opacity: 1;
|
||||
animation: shimmer 1.5s linear 0.6s infinite;
|
||||
}
|
||||
html[dir='rtl'] #initial_startup .graphic::after {
|
||||
animation-direction: reverse;
|
||||
}
|
||||
@keyframes shimmer {
|
||||
from {
|
||||
left: calc(50% - 72px * 2 - 72px / 2);
|
||||
}
|
||||
to {
|
||||
left: calc(50% - 72px / 2);
|
||||
}
|
||||
}
|
||||
#initial_startup .progress {
|
||||
position: relative;
|
||||
width: 420px;
|
||||
height: 3px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
#initial_startup .progress progress {
|
||||
vertical-align: top;
|
||||
}
|
||||
#initial_startup .main {
|
||||
margin-top: 40px;
|
||||
font-size: 17px;
|
||||
color: var(--primary-title);
|
||||
}
|
||||
#initial_startup .secondary {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
color: var(--secondary-lighter);
|
||||
}
|
||||
#initial_startup .secondary span {
|
||||
display: inline-block;
|
||||
margin-bottom: 2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
progress {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
margin: 0;
|
||||
color: var(--progress-primary);
|
||||
background-color: var(--progress-background);
|
||||
border: none;
|
||||
}
|
||||
progress[value]::-webkit-progress-bar {
|
||||
background-color: var(--progress-background);
|
||||
}
|
||||
progress[value]::-moz-progress-bar,
|
||||
progress[value]::-webkit-progress-value {
|
||||
background-color: var(--progress-primary);
|
||||
transition: width 0.45s ease;
|
||||
}
|
||||
</style>
|
||||
<link
|
||||
href="/stylex-f0601c57714fe972a8c4d2c2562f8082.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link href="/app-6113f1562d101247269b.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="web">
|
||||
<script
|
||||
data-binary-transparency-hash-key="inline-js-d11c72a06542fc3b43af600028cf0f996e4b9b178dfa047e18730efaf74aab0b"
|
||||
>
|
||||
try {
|
||||
var systemThemeDark,
|
||||
theme = window.localStorage.getItem('theme'),
|
||||
systemThemeMode = window.localStorage.getItem('system-theme-mode');
|
||||
if (('true' === systemThemeMode || !theme) && window.matchMedia) {
|
||||
var systemTheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
systemThemeDark = systemTheme && systemTheme.matches;
|
||||
}
|
||||
var darkTheme = '"dark"' === theme || Boolean(systemThemeDark);
|
||||
darkTheme && document.body.classList.add('dark');
|
||||
} catch (e) {}
|
||||
</script>
|
||||
<div id="app"></div>
|
||||
<div id="hard_expire_time" data-time="1719543128.405"></div>
|
||||
<div id="initial_startup">
|
||||
<div class="graphic">
|
||||
<span
|
||||
><svg width="250" height="52" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
class="resume-logo"
|
||||
d="M37.7 31.2c-.6-.4-3.8-2-4.4-2.1-.6-.2-1-.4-1.4.3l-2 2.5c-.4.4-.8.5-1.5.2-.6-.3-2.7-1-5.1-3.2-2-1.7-3.2-3.8-3.6-4.5-.4-.6 0-1 .3-1.3l1-1.1.6-1.1c.2-.4 0-.8 0-1.1l-2-4.8c-.6-1.3-1.1-1-1.5-1.1h-1.2c-.5 0-1.2.1-1.8.8-.5.6-2.2 2.2-2.2 5.3 0 3.2 2.3 6.3 2.6 6.7.3.4 4.6 7 11 9.7l3.7 1.4c1.5.5 3 .4 4 .2 1.3-.1 3.9-1.5 4.4-3 .5-1.5.5-2.8.4-3-.2-.4-.6-.5-1.3-.8M26 47.2c-3.9 0-7.6-1-11-3l-.7-.4-8.1 2L8.4 38l-.6-.8A21.4 21.4 0 0126 4.4a21.3 21.3 0 0121.4 21.4c0 11.8-9.6 21.4-21.4 21.4M44.2 7.6a25.8 25.8 0 00-40.6 31L0 52l13.7-3.6A25.8 25.8 0 0044.3 7.5"
|
||||
fill="currentColor"
|
||||
></path></svg
|
||||
></span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<progress value="0" max="100" dir="ltr"></progress>
|
||||
</div>
|
||||
<div class="main">WhatsApp</div>
|
||||
<div class="secondary">
|
||||
<span
|
||||
><svg width="10" height="12" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5 1.6c1.4 0 2.5 1 2.6 2.4v1.5h.2c.5 0 1 .4 1 1V10c0 .6-.5 1-1 1H2.3a1 1 0 01-1.1-1V6.5c0-.6.5-1 1-1h.2V4.2c0-1.4 1-2.5 2.4-2.6H5zm0 1.2c-.7 0-1.3.6-1.3 1.3v1.4h2.6V4.2c0-.7-.4-1.2-1-1.3H5z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg> </span
|
||||
> End-to-end encrypted
|
||||
</div>
|
||||
</div>
|
||||
<link
|
||||
rel="preload"
|
||||
crossorigin="anonymous"
|
||||
as="fetch"
|
||||
href="/binary-transparency-manifest-2.2412.54.json"
|
||||
id="binary-transparency-manifest-preload"
|
||||
/>
|
||||
<script src="/libsignal-protocol-ee5b8ba.min.js"></script>
|
||||
<script defer="defer" src="/runtime.295f0230b6dbd8983513.js"></script>
|
||||
<script
|
||||
data-binary-transparency-hash-key="inline-js-cdf06293c99d00ee0c1bc0fcfdbd76a7ae9aef6290926dbff0cba3a7994b7fca"
|
||||
>
|
||||
/*! Copyright (c) 2024 WhatsApp Inc. All Rights Reserved. */
|
||||
(self.webpackChunkwhatsapp_web_client =
|
||||
self.webpackChunkwhatsapp_web_client || []).push([
|
||||
[5617],
|
||||
{
|
||||
307914: (e) => {
|
||||
e.exports = function (e) {
|
||||
return e && e.__esModule ? e : { default: e };
|
||||
};
|
||||
},
|
||||
595318: (e) => {
|
||||
(e.exports = function (e) {
|
||||
return e && e.__esModule ? e : { default: e };
|
||||
}),
|
||||
(e.exports.default = e.exports),
|
||||
(e.exports.__esModule = !0);
|
||||
},
|
||||
415227: (e, t) => {
|
||||
'use strict';
|
||||
Object.defineProperty(t, '__esModule', { value: !0 }),
|
||||
(t.default = function (e) {
|
||||
const t = new Error(e);
|
||||
if (void 0 === t.stack)
|
||||
try {
|
||||
throw t;
|
||||
} catch (e) {}
|
||||
return t;
|
||||
});
|
||||
},
|
||||
670983: (e, t, r) => {
|
||||
'use strict';
|
||||
var n = r(307914);
|
||||
Object.defineProperty(t, '__esModule', { value: !0 }),
|
||||
(t.default = function (e) {
|
||||
let t =
|
||||
arguments.length > 1 && void 0 !== arguments[1]
|
||||
? arguments[1]
|
||||
: '?';
|
||||
if (null == e)
|
||||
throw (0, o.default)('Unexpected null or undefined: ' + t);
|
||||
return e;
|
||||
});
|
||||
var o = n(r(415227));
|
||||
},
|
||||
250473: (e, t, r) => {
|
||||
'use strict';
|
||||
Object.defineProperty(t, '__esModule', { value: !0 }),
|
||||
(t.UNIQUE_UPLOADS_ALLOWED_BEFORE_THROTTLE =
|
||||
t.TICKET_URL =
|
||||
t.CLB_URL =
|
||||
t.CLB_TOKEN =
|
||||
t.CLB_CHECK_URL =
|
||||
void 0);
|
||||
var n = r(508247);
|
||||
(t.CLB_CHECK_URL =
|
||||
'https://crashlogs.whatsapp.net/wa_fls_upload_check'),
|
||||
(t.CLB_TOKEN =
|
||||
'1063127757113399|745146ffa34413f9dbb5469f5370b7af'),
|
||||
(t.CLB_URL = 'https://crashlogs.whatsapp.net/wa_clb_data');
|
||||
const o = n.DYN_ORIGIN + 'web-contact-us';
|
||||
(t.TICKET_URL = o), (t.UNIQUE_UPLOADS_ALLOWED_BEFORE_THROTTLE = 0);
|
||||
},
|
||||
207024: (e, t, r) => {
|
||||
'use strict';
|
||||
var n = r(595318);
|
||||
Object.defineProperty(t, '__esModule', { value: !0 }),
|
||||
(t.getDistribution = function () {
|
||||
let e = 'unknown';
|
||||
return (e = 'prod'), 'web_prod';
|
||||
}),
|
||||
(t.getLogUserAgent = function (e) {
|
||||
let t,
|
||||
{ appVersion: r, browser: n, device: o } = e;
|
||||
return (t = 'Web/' + n), `WhatsApp/${r} ${t} Device/${o}`;
|
||||
}),
|
||||
n(r(472514)),
|
||||
n(r(556869));
|
||||
},
|
||||
794858: (e, t, r) => {
|
||||
'use strict';
|
||||
var n = r(595318),
|
||||
o = n(r(670983)),
|
||||
a = r(508247),
|
||||
s = r(250473),
|
||||
c = r(207024),
|
||||
_ = n(r(174285)),
|
||||
u = r(425017);
|
||||
function i(e) {
|
||||
!(function (e, t) {
|
||||
const r = window.navigator.userAgent;
|
||||
if (r === p && e.includes('getElementsByTagName')) return;
|
||||
const n = new FormData(),
|
||||
i = new Blob([e], { type: 'text/plain' });
|
||||
n.append(
|
||||
'from_jid',
|
||||
(function () {
|
||||
if (f) return f;
|
||||
try {
|
||||
(f = JSON.parse(
|
||||
(0, o.default)(_.default, 'localStorage').getItem(
|
||||
d.UNKNOWN_ID,
|
||||
),
|
||||
)),
|
||||
f && (f = f.replace('-', ''));
|
||||
} catch (e) {}
|
||||
if (!f) {
|
||||
f = 'unknown' + Math.floor(1e10 * Math.random());
|
||||
const e = f;
|
||||
try {
|
||||
(0, o.default)(_.default, 'localStorage').setItem(
|
||||
d.UNKNOWN_ID,
|
||||
JSON.stringify(e),
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
return (0, o.default)(f, 'id');
|
||||
})(),
|
||||
),
|
||||
n.append(
|
||||
'agent',
|
||||
(0, c.getLogUserAgent)(
|
||||
(0, u.parseUASimple)(r, a.VERSION_BASE),
|
||||
),
|
||||
),
|
||||
n.append('file', i, 'logs.txt'),
|
||||
n.append('tags', 'load');
|
||||
const l = new XMLHttpRequest(),
|
||||
I =
|
||||
s.CLB_URL +
|
||||
'?access_token=' +
|
||||
encodeURIComponent(s.CLB_TOKEN);
|
||||
l.open('POST', I, !0), l.send(n);
|
||||
})(e);
|
||||
}
|
||||
function l(e) {
|
||||
let { error: t, reason: r, stack: n } = e;
|
||||
const o = new Date().toISOString();
|
||||
return `${o}: error: ${t}\n${o}: reason for logs: ${r}\n${o}: userAgent: ${window.navigator.userAgent}\n${n}`;
|
||||
}
|
||||
null == window.onerror &&
|
||||
(window.onerror = function (e, t, r) {
|
||||
const n = t.split('?')[0];
|
||||
return "Uncaught SyntaxError: Unexpected token '<'" === e
|
||||
? ((function (e) {
|
||||
i(
|
||||
l({
|
||||
error: 'failed to load a js or css bundle',
|
||||
reason: `failed to load [${e
|
||||
.split('/')[3]
|
||||
.replace(/^\//, '')}]`,
|
||||
stack: '',
|
||||
}),
|
||||
);
|
||||
})(n),
|
||||
!0)
|
||||
: (i(
|
||||
l({
|
||||
error: e,
|
||||
reason: `Error at [${n}:${r}]`,
|
||||
stack: '',
|
||||
}),
|
||||
),
|
||||
!1);
|
||||
});
|
||||
const d = { UNKNOWN_ID: a.UNKNOWN_ID },
|
||||
p =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36';
|
||||
let f;
|
||||
},
|
||||
425017: (e, t) => {
|
||||
'use strict';
|
||||
function r(e) {
|
||||
return e.includes('windows')
|
||||
? 'Windows'
|
||||
: e.includes('mac')
|
||||
? 'Mac OS'
|
||||
: e.includes('linux')
|
||||
? 'Linux'
|
||||
: 'Unparsed';
|
||||
}
|
||||
Object.defineProperty(t, '__esModule', { value: !0 }),
|
||||
(t.parseUASimple = function (e, t) {
|
||||
const n = e.toLowerCase();
|
||||
return { browser: s(n), device: r(n), appVersion: t };
|
||||
});
|
||||
const n = /(chrome|firefox)\/([\w\.]+)/i,
|
||||
o = /(edge|opr)\/([\w\.]+)/i,
|
||||
a = {
|
||||
chrome: 'Chrome',
|
||||
edge: 'Edge',
|
||||
opr: 'Opera',
|
||||
firefox: 'Firefox',
|
||||
};
|
||||
function s(e) {
|
||||
const t = e.match(o) || e.match(n);
|
||||
return null == t ? 'Unparsed' : `${a[t[1]]} ${t[2]}`;
|
||||
}
|
||||
},
|
||||
174285: (e, t) => {
|
||||
'use strict';
|
||||
let r;
|
||||
Object.defineProperty(t, '__esModule', { value: !0 }),
|
||||
(t.default = void 0);
|
||||
try {
|
||||
r = window.localStorage;
|
||||
} catch (e) {}
|
||||
var n = r;
|
||||
t.default = n;
|
||||
},
|
||||
508247: (e, t) => {
|
||||
'use strict';
|
||||
Object.defineProperty(t, '__esModule', { value: !0 }),
|
||||
(t.WS_ORIGIN =
|
||||
t.WEB_PUBLIC_PATH =
|
||||
t.VERSION_TERTIARY =
|
||||
t.VERSION_STR =
|
||||
t.VERSION_SECONDARY =
|
||||
t.VERSION_PRIMARY =
|
||||
t.VERSION_BASE =
|
||||
t.USER_TIMINGS =
|
||||
t.UNKNOWN_ID =
|
||||
t.LOCALHOST_ENV_CONFIG =
|
||||
t.HASH_PLACEHOLDER =
|
||||
t.FLB_PLATFORM =
|
||||
t.FB_APP_ID =
|
||||
t.DYN_ORIGIN =
|
||||
t.DIFF_VERSION =
|
||||
t.DIFF_ID =
|
||||
t.BUILD_URL =
|
||||
t.BUILD_TIMESTAMP =
|
||||
t.BUILD_ID =
|
||||
void 0);
|
||||
t.DIFF_ID = '';
|
||||
(t.DIFF_VERSION = ''),
|
||||
(t.LOCALHOST_ENV_CONFIG = !1),
|
||||
(t.HASH_PLACEHOLDER = '507042ed75b89fb00e8ef35da3541122cbc261e6'),
|
||||
(t.VERSION_PRIMARY = '2'),
|
||||
(t.VERSION_SECONDARY = '2412'),
|
||||
(t.VERSION_TERTIARY = '54'),
|
||||
(t.VERSION_BASE = '2.2412.54'),
|
||||
(t.VERSION_STR = '2.2412.54'),
|
||||
(t.DYN_ORIGIN = 'https://web.whatsapp.com/'),
|
||||
(t.WS_ORIGIN = ''),
|
||||
(t.FLB_PLATFORM = 'web'),
|
||||
(t.FB_APP_ID = ''),
|
||||
(t.UNKNOWN_ID = 'WAUnknownID'),
|
||||
(t.BUILD_ID = '583059957'),
|
||||
(t.BUILD_TIMESTAMP = 'March 29, 2024'),
|
||||
(t.WEB_PUBLIC_PATH = '/'),
|
||||
(t.BUILD_URL = 'https://web.whatsapp.com/'),
|
||||
(t.USER_TIMINGS = !1);
|
||||
},
|
||||
472514: (e, t) => {
|
||||
'use strict';
|
||||
Object.defineProperty(t, '__esModule', { value: !0 }),
|
||||
(t.default = function (e) {
|
||||
return e.default;
|
||||
});
|
||||
},
|
||||
556869: (e, t) => {
|
||||
'use strict';
|
||||
Object.defineProperty(t, '__esModule', { value: !0 }),
|
||||
(t.default = function e(t) {
|
||||
const r = new Error(t);
|
||||
if (void 0 === r.stack)
|
||||
try {
|
||||
throw e;
|
||||
} catch (e) {}
|
||||
return r;
|
||||
});
|
||||
},
|
||||
},
|
||||
(e) => {
|
||||
'use strict';
|
||||
e((e.s = 794858));
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
<script defer="defer" src="/vendor1~app.264a01b6133c4a120327.js"></script>
|
||||
<script defer="defer" src="/app.2c7f8dd150ae88037cde.js"></script>
|
||||
<script
|
||||
data-binary-transparency-hash-key="inline-js-9c8cd3d0f4d5af8c7f5cd1c45208edd308bfe472e353a50e4cbacbf6cb58627c"
|
||||
>
|
||||
((i) => {
|
||||
const l = document.getElementById(i);
|
||||
l &&
|
||||
fetch(l.href)
|
||||
.then((b) => b.text())
|
||||
.then((code) => {
|
||||
const script = document.createElement('script');
|
||||
script.id = 'binary-transparency-manifest';
|
||||
script.type = 'application/json';
|
||||
script.innerHTML = code;
|
||||
document.body.append(script);
|
||||
});
|
||||
})('binary-transparency-manifest-preload');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,790 @@
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import {
|
||||
Chat,
|
||||
Client,
|
||||
ClientOptions,
|
||||
Contact,
|
||||
Events,
|
||||
GroupChat,
|
||||
Location,
|
||||
Message,
|
||||
Reaction,
|
||||
} from 'whatsapp-web.js';
|
||||
import { Message as MessageInstance } from 'whatsapp-web.js/src/structures';
|
||||
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
EditMessageRequest,
|
||||
GetMessageQuery,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageStarRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
WANumberExistResult,
|
||||
} from '../../../structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '../../../structures/contacts.dto';
|
||||
import {
|
||||
ACK_UNKNOWN,
|
||||
SECOND,
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from '../../../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '../../../structures/groups.dto';
|
||||
import {
|
||||
WAMessage,
|
||||
WAMessageReaction,
|
||||
} from '../../../structures/responses.dto';
|
||||
import { MeInfo } from '../../../structures/sessions.dto';
|
||||
import { WAMessageRevokedBody } from '../../../structures/webhooks.dto';
|
||||
import { IEngineMediaProcessor } from '../../abc/media.abc';
|
||||
import { WAHAInternalEvent, WhatsappSession } from '../../abc/session.abc';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} from '../../exceptions';
|
||||
import { QR } from '../../QR';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const qrcode = require('qrcode-terminal');
|
||||
|
||||
export interface WebJSConfig {
|
||||
webVersion?: string;
|
||||
}
|
||||
|
||||
export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
private START_ATTEMPT_DELAY_SECONDS = 2;
|
||||
|
||||
engine = WAHAEngine.WEBJS;
|
||||
protected engineConfig?: WebJSConfig;
|
||||
|
||||
private startTimeoutId: null | ReturnType<typeof setTimeout> = null;
|
||||
private shouldRestart: boolean;
|
||||
|
||||
whatsapp: Client;
|
||||
protected qr: QR;
|
||||
|
||||
public constructor(config) {
|
||||
super(config);
|
||||
this.qr = new QR();
|
||||
this.shouldRestart = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Folder with the current class
|
||||
*/
|
||||
protected getClassDirName() {
|
||||
return __dirname;
|
||||
}
|
||||
|
||||
protected getClientOptions(): ClientOptions {
|
||||
const path = this.getClassDirName();
|
||||
const webVersion = this.engineConfig?.webVersion || '2.2412.54';
|
||||
this.log.debug(`Using web version: '${webVersion}'`);
|
||||
return {
|
||||
puppeteer: {
|
||||
headless: true,
|
||||
executablePath: this.getBrowserExecutablePath(),
|
||||
args: this.getBrowserArgsForPuppeteer(),
|
||||
dumpio: this.isDebugEnabled(),
|
||||
},
|
||||
webVersion: webVersion,
|
||||
webVersionCache: {
|
||||
type: 'local',
|
||||
path: path,
|
||||
strict: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected async buildClient() {
|
||||
const clientOptions = this.getClientOptions();
|
||||
this.addProxyConfig(clientOptions);
|
||||
return new Client(clientOptions);
|
||||
}
|
||||
|
||||
private restartClient() {
|
||||
if (!this.shouldRestart) {
|
||||
this.log.debug("Shouldn't restart the client, ignoring restart request");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.startTimeoutId) {
|
||||
const msg =
|
||||
'Request to restart is already in progress, ignoring restart request';
|
||||
this.log.warn(msg);
|
||||
return;
|
||||
}
|
||||
this.log.log(
|
||||
`Setting up client start in ${this.START_ATTEMPT_DELAY_SECONDS} seconds...`,
|
||||
);
|
||||
this.startTimeoutId = setTimeout(() => {
|
||||
this.startTimeoutId = undefined;
|
||||
this.start();
|
||||
}, this.START_ATTEMPT_DELAY_SECONDS * SECOND);
|
||||
}
|
||||
|
||||
protected addProxyConfig(clientOptions: ClientOptions) {
|
||||
if (this.proxyConfig?.server !== undefined) {
|
||||
// push the proxy server to the args
|
||||
clientOptions.puppeteer.args.push(
|
||||
`--proxy-server=${this.proxyConfig?.server}`,
|
||||
);
|
||||
|
||||
// Authenticate
|
||||
if (this.proxyConfig?.username && this.proxyConfig?.password) {
|
||||
clientOptions.proxyAuthentication = {
|
||||
username: this.proxyConfig?.username,
|
||||
password: this.proxyConfig?.password,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
await this.end();
|
||||
this.shouldRestart = true;
|
||||
this.whatsapp = await this.buildClient();
|
||||
this.whatsapp
|
||||
.initialize()
|
||||
.then(() => {
|
||||
// Listen for browser disconnected event
|
||||
this.whatsapp.pupBrowser.on('disconnected', () => {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.log.error('The browser has been disconnected');
|
||||
this.restartClient();
|
||||
});
|
||||
// Listen for page close event
|
||||
this.whatsapp.pupPage.on('close', () => {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.log.error('The WhatsApp Web page has been closed');
|
||||
this.restartClient();
|
||||
});
|
||||
|
||||
// Listen for page error event
|
||||
if (this.isDebugEnabled()) {
|
||||
this.log.debug("Logging 'console' event for web page");
|
||||
this.whatsapp.pupPage.on('console', (msg) =>
|
||||
this.log.debug(`WEBJS page log: ${msg.text()}`),
|
||||
);
|
||||
this.whatsapp.pupPage.evaluate(() =>
|
||||
console.log(`url is ${location.href}`),
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.log.error(error);
|
||||
this.restartClient();
|
||||
return;
|
||||
});
|
||||
if (this.isDebugEnabled()) {
|
||||
this.listenEngineEventsInDebugMode();
|
||||
}
|
||||
this.listenConnectionEvents();
|
||||
this.events.emit(WAHAInternalEvent.ENGINE_START);
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.status = WAHASessionStatus.STARTING;
|
||||
await this.init();
|
||||
return this;
|
||||
}
|
||||
|
||||
async stop() {
|
||||
this.shouldRestart = false;
|
||||
this.status = WAHASessionStatus.STOPPED;
|
||||
await this.end();
|
||||
}
|
||||
|
||||
private async end() {
|
||||
try {
|
||||
this.whatsapp?.removeAllListeners();
|
||||
clearInterval(this.startTimeoutId);
|
||||
this.whatsapp?.destroy().catch((error) => {
|
||||
this.log.debug('Failed to destroy the client', error);
|
||||
});
|
||||
} catch (error) {
|
||||
this.log.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getSessionMeInfo(): Promise<MeInfo | null> {
|
||||
const clientInfo = this.whatsapp?.info;
|
||||
if (!clientInfo) {
|
||||
return null;
|
||||
}
|
||||
const wid = clientInfo.wid;
|
||||
const meInfo: MeInfo = {
|
||||
id: wid._serialized,
|
||||
pushName: clientInfo.pushname,
|
||||
};
|
||||
return meInfo;
|
||||
}
|
||||
|
||||
protected listenEngineEventsInDebugMode() {
|
||||
// Iterate over Events enum and log with debug level all incoming events
|
||||
// This is useful for debugging
|
||||
for (const key in Events) {
|
||||
const event = Events[key];
|
||||
this.whatsapp.on(event, (...data: any[]) => {
|
||||
const log = { event: event, data: data };
|
||||
this.log.debug(`WEBJS event: ${JSON.stringify(log)}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected listenConnectionEvents() {
|
||||
this.whatsapp.on(Events.QR_RECEIVED, async (qr) => {
|
||||
this.log.debug('QR received');
|
||||
// Convert to image and save
|
||||
const url = await QRCode.toDataURL(qr);
|
||||
this.qr.save(url, qr);
|
||||
this.printQR(this.qr);
|
||||
this.status = WAHASessionStatus.SCAN_QR_CODE;
|
||||
});
|
||||
|
||||
this.whatsapp.on(Events.READY, () => {
|
||||
this.status = WAHASessionStatus.WORKING;
|
||||
this.qr.save('');
|
||||
this.log.log(`Session '${this.name}' has been authenticated!`);
|
||||
});
|
||||
|
||||
this.whatsapp.on(Events.DISCONNECTED, () => {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.qr.save('');
|
||||
this.log.log(`Session '${this.name}' has been disconnected!`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
public getQR(): QR {
|
||||
return this.qr;
|
||||
}
|
||||
|
||||
async getScreenshot(): Promise<Buffer> {
|
||||
if (this.status === WAHASessionStatus.FAILED) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session under FAILED status. Please try to restart it.`,
|
||||
);
|
||||
}
|
||||
const screenshot = await this.whatsapp.pupPage.screenshot({
|
||||
encoding: 'binary',
|
||||
});
|
||||
return screenshot as Buffer;
|
||||
}
|
||||
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const phone = request.phone.split('@')[0];
|
||||
const result = await this.whatsapp.getNumberId(phone);
|
||||
if (!result) {
|
||||
return {
|
||||
numberExists: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
numberExists: true,
|
||||
chatId: result._serialized,
|
||||
};
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
const options = {
|
||||
// It's fine to sent just ids instead of Contact object
|
||||
mentions: request.mentions as unknown as string[],
|
||||
};
|
||||
return this.whatsapp.sendMessage(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.text,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
public deleteMessage(chatId: string, messageId: string) {
|
||||
const message = this.recreateMessage(messageId);
|
||||
return message.delete(true);
|
||||
}
|
||||
|
||||
public editMessage(
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
request: EditMessageRequest,
|
||||
) {
|
||||
const message = this.recreateMessage(messageId);
|
||||
const options = {
|
||||
// It's fine to sent just ids instead of Contact object
|
||||
mentions: request.mentions as unknown as string[],
|
||||
};
|
||||
return message.edit(request.text, options);
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
const options = {
|
||||
quotedMessageId: request.reply_to,
|
||||
// It's fine to sent just ids instead of Contact object
|
||||
mentions: request.mentions as unknown as string[],
|
||||
};
|
||||
return this.whatsapp.sendMessage(request.chatId, request.text, options);
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendVoice(request: MessageVoiceRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
async sendLocation(request: MessageLocationRequest) {
|
||||
const location = new Location(request.latitude, request.longitude, {
|
||||
name: request.title,
|
||||
});
|
||||
return this.whatsapp.sendMessage(request.chatId, location);
|
||||
}
|
||||
|
||||
async sendSeen(request: SendSeenRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
await chat.sendSeen();
|
||||
}
|
||||
|
||||
async startTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
await chat.sendStateTyping();
|
||||
}
|
||||
|
||||
async stopTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
await chat.clearState();
|
||||
}
|
||||
|
||||
async getMessages(query: GetMessageQuery) {
|
||||
return this.getChatMessages(query.chatId, query.limit, query.downloadMedia);
|
||||
}
|
||||
|
||||
async setReaction(request: MessageReactionRequest) {
|
||||
const message = this.recreateMessage(request.messageId);
|
||||
return message.react(request.reaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreate message instance from id
|
||||
*/
|
||||
private recreateMessage(msgId: string): MessageInstance {
|
||||
const messageId = this.deserializeId(msgId);
|
||||
const data = {
|
||||
id: messageId,
|
||||
};
|
||||
return new MessageInstance(this.whatsapp, data);
|
||||
}
|
||||
|
||||
async setStar(request: MessageStarRequest) {
|
||||
const message = this.recreateMessage(request.messageId);
|
||||
if (request.star) {
|
||||
await message.star();
|
||||
} else {
|
||||
await message.unstar();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Chats methods
|
||||
*/
|
||||
getChats() {
|
||||
return this.whatsapp.getChats();
|
||||
}
|
||||
|
||||
async getChatMessages(chatId: string, limit: number, downloadMedia: boolean) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(
|
||||
this.ensureSuffix(chatId),
|
||||
);
|
||||
const messages = await chat.fetchMessages({
|
||||
limit: limit,
|
||||
});
|
||||
// Go over messages, download media, and convert to right format.
|
||||
const result = [];
|
||||
for (const message of messages) {
|
||||
const msg = await this.processIncomingMessage(message, downloadMedia);
|
||||
result.push(msg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async deleteChat(chatId) {
|
||||
const chat = await this.whatsapp.getChatById(chatId);
|
||||
return chat.delete();
|
||||
}
|
||||
|
||||
async clearMessages(chatId) {
|
||||
const chat = await this.whatsapp.getChatById(chatId);
|
||||
return chat.clearMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
getContact(query: ContactQuery) {
|
||||
return this.whatsapp
|
||||
.getContactById(this.ensureSuffix(query.contactId))
|
||||
.then(this.toWAContact);
|
||||
}
|
||||
|
||||
getContacts() {
|
||||
return this.whatsapp
|
||||
.getContacts()
|
||||
.then((contacts) => contacts.map(this.toWAContact));
|
||||
}
|
||||
|
||||
public async getContactAbout(query: ContactQuery) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(query.contactId),
|
||||
);
|
||||
return { about: await contact.getAbout() };
|
||||
}
|
||||
|
||||
public async getContactProfilePicture(query: ContactQuery) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(query.contactId),
|
||||
);
|
||||
return { profilePictureURL: await contact.getProfilePicUrl() };
|
||||
}
|
||||
|
||||
public async blockContact(request: ContactRequest) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(request.contactId),
|
||||
);
|
||||
await contact.block();
|
||||
}
|
||||
|
||||
public async unblockContact(request: ContactRequest) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(request.contactId),
|
||||
);
|
||||
await contact.unblock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group methods
|
||||
*/
|
||||
public createGroup(request: CreateGroupRequest) {
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return this.whatsapp.createGroup(request.name, participantIds);
|
||||
}
|
||||
|
||||
public async getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return {
|
||||
// Undocumented property, can be changed in the future
|
||||
// @ts-ignore
|
||||
adminsOnly: groupChat.groupMetadata.restrict,
|
||||
};
|
||||
}
|
||||
|
||||
public async setInfoAdminsOnly(id, value) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.setInfoAdminsOnly(value);
|
||||
}
|
||||
|
||||
public async getMessagesAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
// @ts-ignore
|
||||
return {
|
||||
// Undocumented property, can be changed in the future
|
||||
// @ts-ignore
|
||||
adminsOnly: groupChat.groupMetadata.announce,
|
||||
};
|
||||
}
|
||||
|
||||
public async setMessagesAdminsOnly(id, value) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.setMessagesAdminsOnly(value);
|
||||
}
|
||||
|
||||
public getGroups() {
|
||||
return this.whatsapp
|
||||
.getChats()
|
||||
.then((chats) => chats.filter((chat) => chat.isGroup));
|
||||
}
|
||||
|
||||
public getGroup(id) {
|
||||
return this.whatsapp.getChatById(id);
|
||||
}
|
||||
|
||||
public async deleteGroup(id) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.delete();
|
||||
}
|
||||
|
||||
public async leaveGroup(id) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.leave();
|
||||
}
|
||||
|
||||
public async setDescription(id, description) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.setDescription(description);
|
||||
}
|
||||
|
||||
public async setSubject(id, subject) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.setSubject(subject);
|
||||
}
|
||||
|
||||
public async getInviteCode(id): Promise<string> {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.getInviteCode();
|
||||
}
|
||||
|
||||
public async revokeInviteCode(id): Promise<string> {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
await groupChat.revokeInvite();
|
||||
return groupChat.getInviteCode();
|
||||
}
|
||||
|
||||
public async getParticipants(id) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.participants;
|
||||
}
|
||||
|
||||
public async addParticipants(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.addParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async removeParticipants(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.removeParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.promoteParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.demoteParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async setPresence(presence: WAHAPresenceStatus, chatId?: string) {
|
||||
let chat: Chat;
|
||||
switch (presence) {
|
||||
case WAHAPresenceStatus.ONLINE:
|
||||
await this.whatsapp.sendPresenceAvailable();
|
||||
break;
|
||||
case WAHAPresenceStatus.OFFLINE:
|
||||
await this.whatsapp.sendPresenceUnavailable();
|
||||
break;
|
||||
case WAHAPresenceStatus.TYPING:
|
||||
chat = await this.whatsapp.getChatById(chatId);
|
||||
await chat.sendStateTyping();
|
||||
break;
|
||||
case WAHAPresenceStatus.RECORDING:
|
||||
chat = await this.whatsapp.getChatById(chatId);
|
||||
await chat.sendStateRecording();
|
||||
break;
|
||||
case WAHAPresenceStatus.PAUSED:
|
||||
chat = await this.whatsapp.getChatById(chatId);
|
||||
await chat.clearState();
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedByEngineError(
|
||||
`WEBJS engine doesn't support '${presence}' presence.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
subscribeEngineEvent(event, handler): boolean {
|
||||
switch (event) {
|
||||
case WAHAEvents.MESSAGE:
|
||||
this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
return true;
|
||||
case WAHAEvents.MESSAGE_REVOKED:
|
||||
this.whatsapp.on(
|
||||
Events.MESSAGE_REVOKED_EVERYONE,
|
||||
async (after, before) => {
|
||||
const afterMessage = after ? await this.toWAMessage(after) : null;
|
||||
const beforeMessage = before
|
||||
? await this.toWAMessage(before)
|
||||
: null;
|
||||
const body: WAMessageRevokedBody = {
|
||||
after: afterMessage,
|
||||
before: beforeMessage,
|
||||
};
|
||||
handler(body);
|
||||
},
|
||||
);
|
||||
return true;
|
||||
case WAHAEvents.MESSAGE_REACTION:
|
||||
this.whatsapp.on('message_reaction', (message) =>
|
||||
handler(this.processMessageReaction(message)),
|
||||
);
|
||||
return true;
|
||||
case WAHAEvents.MESSAGE_ANY:
|
||||
this.whatsapp.on(Events.MESSAGE_CREATE, (message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
return true;
|
||||
case WAHAEvents.STATE_CHANGE:
|
||||
this.whatsapp.on(Events.STATE_CHANGED, handler);
|
||||
return true;
|
||||
case WAHAEvents.MESSAGE_ACK:
|
||||
// We do not download media here
|
||||
this.whatsapp.on(Events.MESSAGE_ACK, (message) =>
|
||||
this.toWAMessage(message).then(handler),
|
||||
);
|
||||
return true;
|
||||
case WAHAEvents.GROUP_JOIN:
|
||||
this.whatsapp.on(Events.GROUP_JOIN, handler);
|
||||
return true;
|
||||
case WAHAEvents.GROUP_LEAVE:
|
||||
this.whatsapp.on(Events.GROUP_LEAVE, handler);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async processIncomingMessage(message: Message, downloadMedia = true) {
|
||||
if (downloadMedia) {
|
||||
try {
|
||||
message = await this.downloadMedia(message);
|
||||
} catch (e) {
|
||||
this.log.error('Failed when tried to download media for a message');
|
||||
this.log.error(e, e.stack);
|
||||
}
|
||||
}
|
||||
return await this.toWAMessage(message);
|
||||
}
|
||||
|
||||
private processMessageReaction(reaction: Reaction): WAMessageReaction {
|
||||
return {
|
||||
id: reaction.id._serialized,
|
||||
from: reaction.senderId,
|
||||
fromMe: reaction.id.fromMe,
|
||||
participant: reaction.senderId,
|
||||
to: reaction.id.remote,
|
||||
timestamp: reaction.timestamp,
|
||||
reaction: {
|
||||
text: reaction.reaction,
|
||||
messageId: reaction.msgId._serialized,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id._serialized,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// Media
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.media),
|
||||
// @ts-ignore
|
||||
media: message.media,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
ackName: WAMessageAck[message.ack] || ACK_UNKNOWN,
|
||||
location: message.location,
|
||||
vCards: message.vCards,
|
||||
_data: message.rawData,
|
||||
});
|
||||
}
|
||||
|
||||
public async getEngineInfo() {
|
||||
return {
|
||||
WWebVersion: await this.whatsapp?.getWWebVersion(),
|
||||
state: await this.whatsapp?.getState(),
|
||||
};
|
||||
}
|
||||
|
||||
protected toWAContact(contact: Contact) {
|
||||
// @ts-ignore
|
||||
contact.id = contact.id._serialized;
|
||||
return contact;
|
||||
}
|
||||
|
||||
protected downloadMedia(message: Message) {
|
||||
const processor = new EngineMediaProcessor();
|
||||
return this.mediaManager.processMedia(processor, message);
|
||||
}
|
||||
}
|
||||
|
||||
export class EngineMediaProcessor implements IEngineMediaProcessor<Message> {
|
||||
hasMedia(message: Message): boolean {
|
||||
if (!message.hasMedia) {
|
||||
return false;
|
||||
}
|
||||
// Can't get media for revoked messages
|
||||
return message.type !== 'revoked';
|
||||
}
|
||||
|
||||
getMessageId(message: Message): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMimetype(message: Message): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMediaBuffer(message: Message): Promise<Buffer | null> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
getFilename(message: Message): string | null {
|
||||
// @ts-ignore
|
||||
return message.rawData?.filename || null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
|
||||
export const DOCS_URL = 'https://waha.devlike.pro/';
|
||||
|
||||
export class NotImplementedByEngineError extends UnprocessableEntityException {
|
||||
constructor(msg = '') {
|
||||
let error = 'The method is not implemented by the engine.';
|
||||
if (msg) {
|
||||
error += ` ${msg}`;
|
||||
}
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
|
||||
export class AvailableInPlusVersion extends UnprocessableEntityException {
|
||||
constructor() {
|
||||
super(
|
||||
`The feature is available only in Plus version. Check this out: ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { HealthCheckResult } from '@nestjs/terminus';
|
||||
|
||||
import { WAHAHealthCheckService } from '../abc/WAHAHealthCheckService';
|
||||
import { AvailableInPlusVersion } from '../exceptions';
|
||||
|
||||
@Injectable()
|
||||
export class WAHAHealthCheckServiceCore extends WAHAHealthCheckService {
|
||||
check(): Promise<HealthCheckResult> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Agent } from 'https';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import { URL } from 'url';
|
||||
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { ProxyConfig } from '../structures/sessions.dto';
|
||||
import { WhatsappSession } from './abc/session.abc';
|
||||
|
||||
export function getProxyConfig(
|
||||
config: WhatsappConfigService,
|
||||
sessions: Record<string, WhatsappSession>,
|
||||
sessionName: string,
|
||||
): ProxyConfig | undefined {
|
||||
// Single proxy server configuration
|
||||
if (typeof config.proxyServer === 'string') {
|
||||
return {
|
||||
server: config.proxyServer,
|
||||
username: config.proxyServerUsername,
|
||||
password: config.proxyServerPassword,
|
||||
};
|
||||
}
|
||||
|
||||
// Multiple proxy server configuration
|
||||
if (Array.isArray(config.proxyServer)) {
|
||||
const prefix = config.proxyServerIndexPrefix;
|
||||
let indexToUse: number | undefined = undefined;
|
||||
if (prefix && sessionName.includes(prefix)) {
|
||||
// match numbers at the end of the string
|
||||
const matches = sessionName.match(/\d+$/);
|
||||
indexToUse = matches ? parseInt(matches[0], 10) : undefined;
|
||||
}
|
||||
let idx = 0;
|
||||
idx = Object.keys(sessions).length % config.proxyServer.length;
|
||||
const index = indexToUse ? indexToUse % config.proxyServer.length : idx;
|
||||
return {
|
||||
server: config.proxyServer[index],
|
||||
username: config.proxyServerUsername,
|
||||
password: config.proxyServerPassword,
|
||||
};
|
||||
}
|
||||
|
||||
// No proxy server configuration
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getAuthenticatedUrl(
|
||||
url: string,
|
||||
username: string,
|
||||
password: string,
|
||||
): string {
|
||||
const hasSchema = url.startsWith('http://') || url.startsWith('https://');
|
||||
if (!hasSchema) {
|
||||
url = `http://${url}`;
|
||||
}
|
||||
const parsedUrl = new URL(url);
|
||||
parsedUrl.protocol = parsedUrl.protocol || 'http';
|
||||
parsedUrl.username = username;
|
||||
parsedUrl.password = password;
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return https Agent proxy for the config
|
||||
* @param proxyConfig
|
||||
*/
|
||||
export function createAgentProxy(proxyConfig: ProxyConfig): Agent | undefined {
|
||||
const url = getAuthenticatedUrl(
|
||||
proxyConfig.server,
|
||||
proxyConfig.username || '',
|
||||
proxyConfig.password || '',
|
||||
);
|
||||
return new HttpsProxyAgent(url);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export function toVcard(data): string {
|
||||
if (data.vcard) {
|
||||
return data.vcard;
|
||||
}
|
||||
const parts = [];
|
||||
parts.push('BEGIN:VCARD');
|
||||
parts.push('VERSION:3.0');
|
||||
parts.push(`FN:${data.fullName}`);
|
||||
if (data.organization) {
|
||||
parts.push(`ORG:${data.organization};`);
|
||||
}
|
||||
if (data.whatsappId) {
|
||||
parts.push(
|
||||
`TEL;type=CELL;type=VOICE;waid=${data.whatsappId}:${data.phoneNumber}`,
|
||||
);
|
||||
} else {
|
||||
parts.push(`TEL;type=CELL;type=VOICE:${data.phoneNumber}`);
|
||||
}
|
||||
parts.push('END:VCARD');
|
||||
return parts.join('\n');
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
import {
|
||||
ConsoleLogger,
|
||||
Injectable,
|
||||
LogLevel,
|
||||
NotFoundException,
|
||||
UnprocessableEntityException,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { getLogLevels } from '../helpers';
|
||||
import { WAHAEngine, WAHASessionStatus } from '../structures/enums.dto';
|
||||
import {
|
||||
ProxyConfig,
|
||||
SessionDTO,
|
||||
SessionInfo,
|
||||
SessionLogoutRequest,
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
} from '../structures/sessions.dto';
|
||||
import { WebhookConfig } from '../structures/webhooks.config.dto';
|
||||
import { SessionManager } from './abc/manager.abc';
|
||||
import { SessionParams, WhatsappSession } from './abc/session.abc';
|
||||
import { EngineConfigService } from './config/EngineConfigService';
|
||||
import { WhatsappSessionNoWebCore } from './engines/noweb/session.noweb.core';
|
||||
import { WhatsappSessionVenomCore } from './engines/venom/session.venom.core';
|
||||
import { WhatsappSessionWebJSCore } from './engines/webjs/session.webjs.core';
|
||||
import { DOCS_URL } from './exceptions';
|
||||
import { getProxyConfig } from './helpers.proxy';
|
||||
import { CoreMediaManager, MediaStorageCore } from './media.core';
|
||||
import { LocalSessionAuthRepository } from './storage/LocalSessionAuthRepository';
|
||||
import { LocalSessionConfigRepository } from './storage/LocalSessionConfigRepository';
|
||||
import { LocalStoreCore } from './storage/LocalStoreCore';
|
||||
import { WebhookConductorCore } from './webhooks.core';
|
||||
|
||||
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
|
||||
constructor() {
|
||||
super(
|
||||
`WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function buildLogger(name: string, levels: LogLevel[]) {
|
||||
return new ConsoleLogger(name, { logLevels: levels });
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SessionManagerCore extends SessionManager {
|
||||
private session?: WhatsappSession;
|
||||
DEFAULT = 'default';
|
||||
|
||||
// @ts-ignore
|
||||
protected WebhookConductorClass = WebhookConductorCore;
|
||||
protected readonly EngineClass: typeof WhatsappSession;
|
||||
|
||||
constructor(
|
||||
private config: WhatsappConfigService,
|
||||
private log: ConsoleLogger,
|
||||
private engineConfigService: EngineConfigService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.log.setContext('SessionManager');
|
||||
this.session = undefined;
|
||||
const engineName = this.engineConfigService.getDefaultEngineName();
|
||||
this.EngineClass = this.getEngine(engineName);
|
||||
this.store = new LocalStoreCore(engineName.toLowerCase());
|
||||
this.sessionAuthRepository = new LocalSessionAuthRepository(this.store);
|
||||
this.sessionConfigRepository = new LocalSessionConfigRepository(this.store);
|
||||
this.startPredefinedSessions();
|
||||
}
|
||||
|
||||
protected startPredefinedSessions() {
|
||||
const startSessions = this.config.startSessions;
|
||||
startSessions.forEach((sessionName) => {
|
||||
this.start({ name: sessionName });
|
||||
});
|
||||
}
|
||||
|
||||
protected getEngine(engine: WAHAEngine): typeof WhatsappSession {
|
||||
if (engine === WAHAEngine.WEBJS) {
|
||||
return WhatsappSessionWebJSCore;
|
||||
} else if (engine === WAHAEngine.VENOM) {
|
||||
return WhatsappSessionVenomCore;
|
||||
} else if (engine === WAHAEngine.NOWEB) {
|
||||
return WhatsappSessionNoWebCore;
|
||||
} else {
|
||||
throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
private onlyDefault(name: string) {
|
||||
if (name !== this.DEFAULT) {
|
||||
throw new OnlyDefaultSessionIsAllowed();
|
||||
}
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal?: string) {
|
||||
if (!this.session) {
|
||||
return;
|
||||
}
|
||||
await this.stop({ name: this.DEFAULT, logout: false });
|
||||
}
|
||||
|
||||
//
|
||||
// API Methods
|
||||
//
|
||||
async start(request: SessionStartRequest): Promise<SessionDTO> {
|
||||
this.onlyDefault(request.name);
|
||||
if (this.session) {
|
||||
throw new UnprocessableEntityException(
|
||||
`Session '${this.DEFAULT}' is already started.`,
|
||||
);
|
||||
}
|
||||
|
||||
const name = request.name;
|
||||
this.log.log(`'${name}' - starting session...`);
|
||||
const levels = getLogLevels(request.config?.debug);
|
||||
const log = buildLogger(`WhatsappSession - ${name}`, levels);
|
||||
const mediaManager = new CoreMediaManager(
|
||||
new MediaStorageCore(),
|
||||
this.config.mimetypes,
|
||||
);
|
||||
const webhookLog = buildLogger(`Webhook - ${name}`, levels);
|
||||
const webhook = new this.WebhookConductorClass(webhookLog);
|
||||
const proxyConfig = this.getProxyConfig(request);
|
||||
const sessionConfig: SessionParams = {
|
||||
name,
|
||||
mediaManager,
|
||||
log,
|
||||
printQR: this.engineConfigService.shouldPrintQR,
|
||||
sessionStore: this.store,
|
||||
proxyConfig: proxyConfig,
|
||||
sessionConfig: request.config,
|
||||
};
|
||||
await this.sessionAuthRepository.init(name);
|
||||
// @ts-ignore
|
||||
const session = new this.EngineClass(sessionConfig);
|
||||
this.session = session;
|
||||
|
||||
// configure webhooks
|
||||
const webhooks = this.getWebhooks(request);
|
||||
webhook.configure(session, webhooks);
|
||||
|
||||
// start session
|
||||
await session.start();
|
||||
return {
|
||||
name: session.name,
|
||||
status: session.status,
|
||||
config: session.sessionConfig,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine per session and global webhooks
|
||||
*/
|
||||
private getWebhooks(request: SessionStartRequest) {
|
||||
let webhooks: WebhookConfig[] = [];
|
||||
if (request.config?.webhooks) {
|
||||
webhooks = webhooks.concat(request.config.webhooks);
|
||||
}
|
||||
const globalWebhookConfig = this.config.getWebhookConfig();
|
||||
if (globalWebhookConfig) {
|
||||
webhooks.push(globalWebhookConfig);
|
||||
}
|
||||
return webhooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get either session's or global proxy if defined
|
||||
*/
|
||||
protected getProxyConfig(
|
||||
request: SessionStartRequest,
|
||||
): ProxyConfig | undefined {
|
||||
if (request.config?.proxy) {
|
||||
return request.config.proxy;
|
||||
}
|
||||
if (!this.session) {
|
||||
return undefined;
|
||||
}
|
||||
const sessions = { [request.name]: this.session };
|
||||
return getProxyConfig(this.config, sessions, request.name);
|
||||
}
|
||||
|
||||
async stop(request: SessionStopRequest): Promise<void> {
|
||||
this.onlyDefault(request.name);
|
||||
|
||||
const name = request.name;
|
||||
this.log.log(`Stopping ${name} session...`);
|
||||
const session = this.getSession(name);
|
||||
await session.stop();
|
||||
this.log.log(`"${name}" has been stopped.`);
|
||||
this.session = undefined;
|
||||
}
|
||||
|
||||
async logout(request: SessionLogoutRequest) {
|
||||
const name = request.name;
|
||||
this.onlyDefault(request.name);
|
||||
this.stop({ name: name, logout: false })
|
||||
.then(() => {
|
||||
this.log.log(`Session '${name}' has been stopped.`);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.log.error(
|
||||
`Error while stopping session '${name}' while logging out`,
|
||||
err,
|
||||
);
|
||||
});
|
||||
await this.sessionAuthRepository.clean(request.name);
|
||||
}
|
||||
|
||||
getSession(name: string): WhatsappSession {
|
||||
this.onlyDefault(name);
|
||||
const session = this.session;
|
||||
if (!session) {
|
||||
throw new NotFoundException(
|
||||
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
|
||||
);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
async getSessions(all: boolean): Promise<SessionInfo[]> {
|
||||
if (!this.session) {
|
||||
if (!all) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
name: this.DEFAULT,
|
||||
status: WAHASessionStatus.STOPPED,
|
||||
config: undefined,
|
||||
me: null,
|
||||
},
|
||||
];
|
||||
}
|
||||
const me = await this.session.getSessionMeInfo().catch((err) => null);
|
||||
const engine = {
|
||||
engine: this.session?.engine,
|
||||
...(await this.session?.getEngineInfo().catch((err) => ({}))),
|
||||
};
|
||||
return [
|
||||
{
|
||||
name: this.session.name,
|
||||
status: this.session.status,
|
||||
config: this.session.sessionConfig,
|
||||
me: me,
|
||||
engine: engine,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async getSessionInfo(name: string): Promise<SessionInfo | null> {
|
||||
if (name !== this.DEFAULT) {
|
||||
return null;
|
||||
}
|
||||
return this.getSessions(true).then((sessions) => sessions[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { WAMedia } from '../structures/responses.dto';
|
||||
import {
|
||||
IEngineMediaProcessor,
|
||||
MediaManager,
|
||||
MediaStorage,
|
||||
} from './abc/media.abc';
|
||||
import { DOCS_URL } from './exceptions';
|
||||
|
||||
export class MediaStorageCore implements MediaStorage {
|
||||
async save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string> {
|
||||
return Promise.resolve(
|
||||
`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CoreMediaManager implements MediaManager {
|
||||
constructor(
|
||||
private storage: MediaStorage,
|
||||
private mimetypes: string[],
|
||||
) {}
|
||||
|
||||
async processMedia<Message>(
|
||||
processor: IEngineMediaProcessor<Message>,
|
||||
message: Message,
|
||||
): Promise<Message> {
|
||||
if (!processor.hasMedia(message)) {
|
||||
return message;
|
||||
}
|
||||
const mimetype = '';
|
||||
const filename = processor.getFilename(message);
|
||||
const url = await this.storage.save('', mimetype, Buffer.from(''));
|
||||
const media: WAMedia = {
|
||||
mimetype: mimetype,
|
||||
filename: filename,
|
||||
url: url,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
message.media = media;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export abstract class ISessionAuthRepository {
|
||||
abstract init(sessionName?: string): Promise<void>;
|
||||
|
||||
abstract clean(sessionName: string): Promise<void>;
|
||||
|
||||
abstract getAll(): Promise<string[]>;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { SessionConfig } from '../../structures/sessions.dto';
|
||||
|
||||
export abstract class ISessionConfigRepository {
|
||||
abstract save(
|
||||
sessionName: string,
|
||||
config: SessionConfig | null,
|
||||
): Promise<void>;
|
||||
|
||||
abstract get(sessionName: string): Promise<SessionConfig | null>;
|
||||
|
||||
abstract delete(sessionName: string): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import * as fs from 'fs/promises';
|
||||
|
||||
import { ISessionAuthRepository } from './ISessionAuthRepository';
|
||||
import { LocalStore } from './LocalStore';
|
||||
|
||||
export class LocalSessionAuthRepository extends ISessionAuthRepository {
|
||||
private store: LocalStore;
|
||||
|
||||
constructor(store: LocalStore) {
|
||||
super();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
async init(sessionName?: string) {
|
||||
await this.store.init(sessionName);
|
||||
}
|
||||
|
||||
async clean(sessionName: string) {
|
||||
const folder = this.store.getSessionDirectory(sessionName);
|
||||
await fs.rm(folder, { recursive: true });
|
||||
}
|
||||
|
||||
async getAll(): Promise<string[]> {
|
||||
await this.init();
|
||||
const content = await fs.readdir(this.store.getEngineDirectory(), {
|
||||
withFileTypes: true,
|
||||
});
|
||||
return content
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user