Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03e9588581 | ||
|
|
393f5ef7da | ||
|
|
f778678514 | ||
|
|
e4f88771a8 | ||
|
|
e885ea8e95 | ||
|
|
f8a9ed7d06 | ||
|
|
d89e9105d0 | ||
|
|
3ba92dec04 | ||
|
|
441ebbd525 | ||
|
|
690c43c245 | ||
|
|
26d8591a6c | ||
|
|
ac63c097ec | ||
|
|
d29e4f8351 | ||
|
|
615ccda4c3 | ||
|
|
d52eb71c76 | ||
|
|
8edd81a765 | ||
|
|
5830cee384 | ||
|
|
20f15b4370 | ||
|
|
8432cd4f8d | ||
|
|
55870b88bf | ||
|
|
6acf4373f4 | ||
|
|
2381f0843f | ||
|
|
b4e6f5e722 | ||
|
|
415bee3e8e | ||
|
|
aad924be79 | ||
|
|
81083e7f82 | ||
|
|
f8e52959a4 | ||
|
|
8eb542f49e | ||
|
|
45ae263ab9 | ||
|
|
f066f982bf | ||
|
|
0888640741 | ||
|
|
bde3722f5c | ||
|
|
0cd3df117a | ||
|
|
4c9525cc9f | ||
|
|
c14fe1e67d | ||
|
|
d1e3b209e7 | ||
|
|
f37bf62a30 | ||
|
|
538bfe0e24 | ||
|
|
93cd5307d9 | ||
|
|
b697f37b1e | ||
|
|
96a9cd8cbb | ||
|
|
a21b9a11a9 | ||
|
|
213b51e598 | ||
|
|
faf3c77508 | ||
|
|
e6fdfb88ca | ||
|
|
99746df626 | ||
|
|
43aeeb55e8 | ||
|
|
4f89f49d44 | ||
|
|
be583e7e40 | ||
|
|
e8c8f89733 | ||
|
|
13b6d3ddf7 | ||
|
|
5e97fb74b1 | ||
|
|
cc47321cad | ||
|
|
607dff8f7a | ||
|
|
0a74f8dbcd | ||
|
|
796befcf87 | ||
|
|
2c5610dd01 | ||
|
|
43d110fa31 | ||
|
|
bf52dd4874 | ||
|
|
f8268180bf | ||
|
|
04cf9bc8c0 | ||
|
|
ff38cf7660 | ||
|
|
4c0b44c4b5 | ||
|
|
e75c29d57f | ||
|
|
f8329c29b1 | ||
|
|
52222a6b98 | ||
|
|
99a0a19d8c | ||
|
|
48ed33bc94 | ||
|
|
b234e134bf | ||
|
|
f6004fd294 | ||
|
|
91d541387b | ||
|
|
9dbd45da6e | ||
|
|
adc1c3960c | ||
|
|
ff742d2e84 | ||
|
|
10bd4d6271 | ||
|
|
874cafe114 | ||
|
|
0ce2c4b9fa | ||
|
|
a8a7988bfa | ||
|
|
2940d93b81 | ||
|
|
88707cf72b | ||
|
|
b0f883c6a8 | ||
|
|
4cddb766c2 | ||
|
|
6675492901 | ||
|
|
a9d030af2a | ||
|
|
2782a188a9 | ||
|
|
256688497e | ||
|
|
4def193c57 | ||
|
|
7f3a2e6aa5 | ||
|
|
60e4f13b67 | ||
|
|
2d6cfeb0fa | ||
|
|
03e85434df | ||
|
|
0ce4c01c48 | ||
|
|
0e95890ca4 | ||
|
|
072f4b2ab0 | ||
|
|
47a91ef669 | ||
|
|
5143bd1ef8 | ||
|
|
0efc0751c9 | ||
|
|
51dda739cb | ||
|
|
3b28826679 | ||
|
|
81646bbaa2 | ||
|
|
d8d84107fb | ||
|
|
58d917d374 | ||
|
|
f8f0063975 | ||
|
|
73faf330e3 | ||
|
|
5a1ebb186d | ||
|
|
54d6d715b0 | ||
|
|
5d9d6e29e2 | ||
|
|
cf3385f624 | ||
|
|
0346c5e7b9 | ||
|
|
443652b0a7 | ||
|
|
1e73c3dc6a | ||
|
|
afa9d4208f | ||
|
|
64eda25b5d | ||
|
|
cfc6c931a3 | ||
|
|
35bc56c948 | ||
|
|
371bde4c3e | ||
|
|
9497e2900e | ||
|
|
51bbc9998a | ||
|
|
057f7bce56 | ||
|
|
52412fb4cc | ||
|
|
a57f3443de | ||
|
|
9b2537e7c7 | ||
|
|
a378ad4c3d | ||
|
|
8f867e0b5d | ||
|
|
d4f6ec18fd | ||
|
|
ac05dd83e9 | ||
|
|
17c22f0cd4 | ||
|
|
971979c13f | ||
|
|
845e203637 | ||
|
|
7a2e1cd731 | ||
|
|
84ba011a4c | ||
|
|
50399f1192 | ||
|
|
a2517b248d | ||
|
|
f02d95331a | ||
|
|
cb1faff88c | ||
|
|
3ff1c06eea | ||
|
|
c91d2c59eb | ||
|
|
4cf5a74d23 | ||
|
|
13de42dbe0 | ||
|
|
f99b86ed18 | ||
|
|
07f6c78372 | ||
|
|
83678553e3 | ||
|
|
9d1bbd780e | ||
|
|
899f9f2e84 | ||
|
|
1d1a083464 | ||
|
|
fabc6753e6 | ||
|
|
a16f3b67fe | ||
|
|
c0c3381a36 | ||
|
|
212a27d9df | ||
|
|
a18c1d82c1 | ||
|
|
67349e4509 | ||
|
|
293267536b | ||
|
|
10d103a09a | ||
|
|
1a6ff4dbde | ||
|
|
321310934d | ||
|
|
22312d775a | ||
|
|
b71d83ce48 | ||
|
|
1a91e7d722 | ||
|
|
7a54b6b599 | ||
|
|
821c9f7b00 | ||
|
|
e7f300b4bc | ||
|
|
a5bff80b3a | ||
|
|
89dd3aa9e1 | ||
|
|
8fc5658e9d | ||
|
|
eb3879bf65 | ||
|
|
daf56433a3 | ||
|
|
e4cb5d5652 | ||
|
|
a2e0119e61 | ||
|
|
f8bcaebf4a | ||
|
|
935a2b515f | ||
|
|
2313915ade | ||
|
|
dfc1c6dde4 | ||
|
|
62d1b3dbf6 | ||
|
|
7272f0e669 | ||
|
|
96da96281d | ||
|
|
50e09c3680 | ||
|
|
1da6a16640 | ||
|
|
10f80132f8 | ||
|
|
750b2b86b5 | ||
|
|
2fc2caed85 | ||
|
|
1431e8c0f1 | ||
|
|
01c9c2ca56 | ||
|
|
f5d216df6f | ||
|
|
7614822d2b | ||
|
|
d2eb463d32 | ||
|
|
07b1e50534 | ||
|
|
41b6b3459e | ||
|
|
0f5fcf60f6 | ||
|
|
18d8330ab3 | ||
|
|
7de5f461aa | ||
|
|
caa055abed | ||
|
|
49af88dea3 | ||
|
|
05d856d8ae | ||
|
|
c5c99136fa | ||
|
|
a8b520012f | ||
|
|
449df53c19 | ||
|
|
69ac19b5c0 | ||
|
|
163b11aa36 | ||
|
|
f4171a9086 | ||
|
|
dfbae9c71e | ||
|
|
91caa7ba25 | ||
|
|
2ac57bb6c3 | ||
|
|
e01647eb48 | ||
|
|
90bc05ecf3 | ||
|
|
21998cbfae | ||
|
|
87c49a8766 | ||
|
|
4e78d559a8 | ||
|
|
18ce902c60 | ||
|
|
5784db8055 | ||
|
|
6862063654 | ||
|
|
4aea282356 | ||
|
|
82ff15ae56 | ||
|
|
6cbf8cded2 | ||
|
|
35e9418988 | ||
|
|
a58be84e56 | ||
|
|
2994e0a54d | ||
|
|
f16100e1d9 | ||
|
|
e57be58d48 | ||
|
|
7909910dc6 | ||
|
|
7f1778ceb0 | ||
|
|
aba90574d8 | ||
|
|
33c945bf95 | ||
|
|
d40e82eb02 | ||
|
|
6e82d6f40b | ||
|
|
e193eb5696 | ||
|
|
8c63c4b097 | ||
|
|
4127d609cf | ||
|
|
57f6db4252 | ||
|
|
68a91cb55c | ||
|
|
869a343e43 | ||
|
|
fd2b22b339 | ||
|
|
90d3a5b1ad | ||
|
|
b4a2ad6b96 | ||
|
|
27ff7e7a21 | ||
|
|
94ae624662 | ||
|
|
52e018aa93 | ||
|
|
8bd13a233e | ||
|
|
87f79c1dc5 | ||
|
|
31533b5d19 | ||
|
|
27cc9f4764 | ||
|
|
4fdfce3944 | ||
|
|
1e12a83cdf | ||
|
|
1c41b8be65 | ||
|
|
f72ee7c1f8 | ||
|
|
e75912435b | ||
|
|
da294a58a6 | ||
|
|
7689199e11 | ||
|
|
324ab11f09 | ||
|
|
b1f2e17e3d | ||
|
|
90822cd582 | ||
|
|
9eec72b641 | ||
|
|
050c652480 | ||
|
|
c44680c987 | ||
|
|
9394509a07 | ||
|
|
63890dc499 | ||
|
|
e2330901e4 | ||
|
|
58672fa1e1 | ||
|
|
3890071f77 | ||
|
|
d60dfba90a | ||
|
|
4e58713e7e | ||
|
|
6f0af3c7cc | ||
|
|
23bac2e7b1 | ||
|
|
e839203dbd | ||
|
|
304b0037ec | ||
|
|
482dbbeb0e | ||
|
|
8f4c79baf4 | ||
|
|
31038a9318 | ||
|
|
86fde1f670 | ||
|
|
984ef0c6a1 | ||
|
|
744ee1abb1 | ||
|
|
47f4f2670d | ||
|
|
f16eaed935 | ||
|
|
dfe9454a72 | ||
|
|
4aef38ff04 | ||
|
|
ab8de51022 | ||
|
|
c001d7fed6 | ||
|
|
cc056dca9c | ||
|
|
47f60a1626 | ||
|
|
98721051da | ||
|
|
5d7a6f4ff7 | ||
|
|
dcb7bf033f | ||
|
|
7d29be2d69 | ||
|
|
41b669a35c | ||
|
|
7b5ca3bc57 | ||
|
|
bb0c7fd22b | ||
|
|
c79958541e | ||
|
|
94ee8863d6 | ||
|
|
011477262d | ||
|
|
2e8e4cefb0 | ||
|
|
5e2802c098 | ||
|
|
9a6250a055 | ||
|
|
f8cb56525a | ||
|
|
b5b491cb7b | ||
|
|
7eb6be70f9 | ||
|
|
0de8a5fabc | ||
|
|
9fbc18fb70 | ||
|
|
e7ce3368b0 | ||
|
|
729600e9c6 | ||
|
|
54886f385b | ||
|
|
81854ad7ec | ||
|
|
5261d4f281 | ||
|
|
72d9d94342 | ||
|
|
b782ad27a2 | ||
|
|
d7dad5e6c2 | ||
|
|
8765bb4412 | ||
|
|
984be25d51 | ||
|
|
70620207dd | ||
|
|
a96200f981 | ||
|
|
4978abf3d8 | ||
|
|
a67d84f21e | ||
|
|
91f59b38fd | ||
|
|
d0ec351910 | ||
|
|
a7b12874fb | ||
|
|
cd2c7bdd86 | ||
|
|
045077143f | ||
|
|
bcd2199687 | ||
|
|
af108eede4 | ||
|
|
ee1cf838b8 | ||
|
|
0fc74895da | ||
|
|
394937c154 | ||
|
|
71f9adeff4 | ||
|
|
e13ccdab9c | ||
|
|
2074748a7a | ||
|
|
283d63394e | ||
|
|
5cfb53c367 | ||
|
|
9466407b8a | ||
|
|
4b65f0c462 | ||
|
|
23504b35c8 | ||
|
|
dd6ec4ca3a | ||
|
|
8235e5a111 | ||
|
|
55499a6144 | ||
|
|
9dbc4a6085 | ||
|
|
f7c6ce847c | ||
|
|
84eed00415 | ||
|
|
51aa0e3154 | ||
|
|
77893a0eb0 | ||
|
|
4a866d8a08 | ||
|
|
f912353a5c | ||
|
|
a6a1d8dc28 | ||
|
|
150e73aa74 | ||
|
|
a9300b3134 | ||
|
|
bbe3859085 | ||
|
|
b420548dd9 | ||
|
|
741483eec7 | ||
|
|
2acf7e4e17 | ||
|
|
9bca6d904d | ||
|
|
857c586df6 | ||
|
|
df8cb7cecb | ||
|
|
9d0050adb8 | ||
|
|
df927ab04a | ||
|
|
02e5934e8f | ||
|
|
825a4152db | ||
|
|
81b10099af | ||
|
|
3be5f328b0 | ||
|
|
5caa3b8b81 | ||
|
|
8231bf7d8e | ||
|
|
22d94808fb | ||
|
|
57daefa680 | ||
|
|
5cf8704973 | ||
|
|
28da9c34bb | ||
|
|
51ba5e3266 | ||
|
|
f696794ee1 | ||
|
|
c6eca9510b | ||
|
|
9a2fd2fccd | ||
|
|
c0a2d02f0d | ||
|
|
6493d16fd2 | ||
|
|
9f96c678dc | ||
|
|
a81f2ab553 | ||
|
|
8fbf7fa0e5 | ||
|
|
af0bba313a | ||
|
|
76307dec12 | ||
|
|
0b441054ae | ||
|
|
2e8b78aebc | ||
|
|
d01b221a24 | ||
|
|
61bb111768 | ||
|
|
de47d6e445 | ||
|
|
59fe4b30f0 | ||
|
|
166dae8e27 | ||
|
|
5ce64fd30d | ||
|
|
1a4513f437 | ||
|
|
59be4a780f | ||
|
|
74001c2b49 | ||
|
|
a9ce0fb2d9 | ||
|
|
4c3fd1c214 | ||
|
|
a9459fe746 | ||
|
|
5a3228f05b | ||
|
|
befc238db9 | ||
|
|
ae75a1f8d4 | ||
|
|
d1c50bfd73 | ||
|
|
6f540366f4 | ||
|
|
151fc760fe | ||
|
|
be4391a4b6 | ||
|
|
76ae3e0f6b | ||
|
|
f37b6edaa6 | ||
|
|
7095f77f93 | ||
|
|
5853b778f2 | ||
|
|
6405725ce6 | ||
|
|
111d6141aa | ||
|
|
5560b3c8e6 | ||
|
|
d528d541ea | ||
|
|
ab6b51cc1f | ||
|
|
35aa4f2658 | ||
|
|
36db3c37e8 | ||
|
|
b0c9232153 | ||
|
|
0070fb9e5c | ||
|
|
aa33ac4b1d | ||
|
|
1c2bc5b395 | ||
|
|
f2fe18a4c5 | ||
|
|
87fdcbebf6 | ||
|
|
68a4f651ea | ||
|
|
243caa9626 | ||
|
|
863684b916 | ||
|
|
2d543f3728 | ||
|
|
47088cfbee | ||
|
|
66c75cbf79 | ||
|
|
b2c68d88ae | ||
|
|
675e998cba | ||
|
|
db1d20ae36 | ||
|
|
59b40dda4e | ||
|
|
317fab4dfe | ||
|
|
6c83ab68b4 | ||
|
|
2194465757 | ||
|
|
a0e70f88ad | ||
|
|
9102a3d852 | ||
|
|
b05cba896e | ||
|
|
6716a31b6c | ||
|
|
2e42d0e7eb | ||
|
|
dee9864157 | ||
|
|
0293fc8c6f | ||
|
|
725fdc03a6 | ||
|
|
4557cb695f | ||
|
|
172403a061 | ||
|
|
d03c3c185e | ||
|
|
50a111343c | ||
|
|
f01693adc3 | ||
|
|
d2bbbb4448 | ||
|
|
38c0aadff2 | ||
|
|
79d593b155 | ||
|
|
ddc84cfecc | ||
|
|
b620383d94 | ||
|
|
2319dba68d | ||
|
|
0c388ee3a7 | ||
|
|
fb5b3e2964 | ||
|
|
d44eaa5b41 | ||
|
|
2eaf05df27 | ||
|
|
9e5431e205 | ||
|
|
26c3102af8 | ||
|
|
104f06ef2b | ||
|
|
93c8654e84 | ||
|
|
a7261790bd | ||
|
|
d8d6e521f6 | ||
|
|
02497bcd40 | ||
|
|
b498e15620 | ||
|
|
1288d312c0 | ||
|
|
84cd350c42 | ||
|
|
100bf89074 | ||
|
|
d66c80f95a | ||
|
|
26f04bbd94 | ||
|
|
6582c03823 | ||
|
|
abacaf2e02 | ||
|
|
c65d8b9856 | ||
|
|
8f94cf06de | ||
|
|
94c1c16de8 | ||
|
|
e69d992725 | ||
|
|
e6c5dd6e22 | ||
|
|
5486d4874a | ||
|
|
9d9d8e03fe | ||
|
|
6ee50243a2 | ||
|
|
3ec078f9c1 | ||
|
|
4c3209be6d | ||
|
|
7fe8f9a8dc | ||
|
|
7fa2b29418 | ||
|
|
dd4edd5a85 | ||
|
|
b85df5a0d3 | ||
|
|
1a7bec3d4e | ||
|
|
727c4b454f | ||
|
|
4ba86b34a9 | ||
|
|
277a35aed9 | ||
|
|
28c1ffa09f | ||
|
|
10595680a9 | ||
|
|
577298dbc5 | ||
|
|
49ca4dfbf7 | ||
|
|
89ec47deec | ||
|
|
a043a46457 | ||
|
|
eb8e3a0fd1 | ||
|
|
fc25df8104 | ||
|
|
d8ffd3e543 | ||
|
|
ea4ec9310d | ||
|
|
479ec85e0d | ||
|
|
3e4b450260 | ||
|
|
f6a81949d0 | ||
|
|
6896962606 | ||
|
|
9b6d6ab4b0 | ||
|
|
d5f399f7d5 | ||
|
|
dbd3bd024c | ||
|
|
9ba7e19da3 | ||
|
|
5f6d1412cf | ||
|
|
0b5af3145e | ||
|
|
ad10ea4e96 | ||
|
|
9de53a972a | ||
|
|
4043efbb9a | ||
|
|
42019644f0 | ||
|
|
bcc25ef42f | ||
|
|
602bbf3b60 | ||
|
|
585ac6074f | ||
|
|
5bd1a43df6 | ||
|
|
c9279a4b59 | ||
|
|
9ff8eed6ba | ||
|
|
96ffdd251f | ||
|
|
35b8b7d01c | ||
|
|
4e2e2b2b3c | ||
|
|
bfff0c8cf4 | ||
|
|
e253f445b9 | ||
|
|
73ed663c9c | ||
|
|
2236150b83 | ||
|
|
da24feb390 | ||
|
|
44a5ada162 | ||
|
|
e57cc2587c | ||
|
|
38bfb9baa5 | ||
|
|
540636d59b | ||
|
|
3cc784353e | ||
|
|
44946ee784 | ||
|
|
a9d77487c6 | ||
|
|
70d4f27abd | ||
|
|
9fca09661d | ||
|
|
2f0efd0223 | ||
|
|
5c31ed53df | ||
|
|
87640ea238 | ||
|
|
eb8f771b06 | ||
|
|
b080b5862e | ||
|
|
df50e7527e | ||
|
|
e2b43575c0 | ||
|
|
2059793562 | ||
|
|
e5df21e165 | ||
|
|
792029b13f | ||
|
|
0ef49d7116 | ||
|
|
600d3291eb | ||
|
|
b78413190c | ||
|
|
c9729af139 | ||
|
|
697f267fc3 | ||
|
|
ac49ce3cc5 | ||
|
|
555daf6888 | ||
|
|
8f0c2994a6 | ||
|
|
873e66db19 | ||
|
|
38b542def4 | ||
|
|
a646e5efc0 | ||
|
|
31590c9229 | ||
|
|
98c2172908 | ||
|
|
314b02daf1 | ||
|
|
d2ab78b65c | ||
|
|
db2c352fd1 | ||
|
|
6e79ad2793 | ||
|
|
77eaa287fe | ||
|
|
a64820048c | ||
|
|
4ff4a5a893 | ||
|
|
5ea5681d2b | ||
|
|
2bb327b747 | ||
|
|
4dcdf60309 | ||
|
|
009fd5b2ec | ||
|
|
091e7e5bc1 | ||
|
|
ea1202bb65 | ||
|
|
cb413febaa | ||
|
|
364e55d477 | ||
|
|
680a35a26d | ||
|
|
08ff59e463 | ||
|
|
9db4c8c333 | ||
|
|
2489c2da46 | ||
|
|
fbc58b0707 | ||
|
|
d5a525789e | ||
|
|
34066921e1 | ||
|
|
901c594311 | ||
|
|
5f619e608b | ||
|
|
49295090ad | ||
|
|
a6e43adb0a | ||
|
|
1f165bb6cb | ||
|
|
8fc9d88d2e | ||
|
|
d7e5a5ce05 | ||
|
|
09d446fdeb | ||
|
|
c0bbd60e15 | ||
|
|
af3fc137be | ||
|
|
0ee5e1bd88 | ||
|
|
f92ea18651 | ||
|
|
3cce2bb35f | ||
|
|
e080ce42ee | ||
|
|
5dbe41da78 | ||
|
|
eef3957d4f | ||
|
|
680f4596b9 | ||
|
|
8de344f220 | ||
|
|
8005846955 | ||
|
|
3edd60448b | ||
|
|
4a2fa5c076 | ||
|
|
96e9c2cc4e | ||
|
|
2089a1bb35 | ||
|
|
023c2718e2 | ||
|
|
f7aacb193a | ||
|
|
e710040904 | ||
|
|
fb12826801 | ||
|
|
833a70c6e0 | ||
|
|
c807f9021c | ||
|
|
46b66bcdf0 | ||
|
|
2ebade817e | ||
|
|
080342e619 | ||
|
|
75b06d3ba1 | ||
|
|
3b6fa3343d | ||
|
|
469cdc9b1d | ||
|
|
8a179e7194 | ||
|
|
c133795753 | ||
|
|
941ace7461 | ||
|
|
8589420f5d | ||
|
|
ece24e182f | ||
|
|
38e5bb7bdf | ||
|
|
d09e931827 | ||
|
|
3f3f1e1c20 | ||
|
|
32f2468ba8 | ||
|
|
1d6540e0d9 | ||
|
|
31da4bfca1 | ||
|
|
41d49894e2 | ||
|
|
6dcdd15b1c | ||
|
|
c2b275fd90 | ||
|
|
e4838ef3ef | ||
|
|
55ca4660d7 | ||
|
|
c16c903ec4 | ||
|
|
937cf38ff6 | ||
|
|
07f76e854d | ||
|
|
cde1d084c6 | ||
|
|
9362c5d6ae | ||
|
|
117a9108c0 | ||
|
|
5214e461f2 | ||
|
|
9bdb178b7d | ||
|
|
9c526ffdba | ||
|
|
f80a74edd1 | ||
|
|
7d8c6c0a62 | ||
|
|
17b26b869b | ||
|
|
dfbe54e92f | ||
|
|
2321de409b | ||
|
|
502287a258 | ||
|
|
052ff0ea6e | ||
|
|
2b76789cdf | ||
|
|
7bbf5df231 | ||
|
|
794a04e7d3 | ||
|
|
645467f13a | ||
|
|
7e87637341 | ||
|
|
b7abeed54a | ||
|
|
d53646eef2 | ||
|
|
c54743de6c | ||
|
|
e1fdc41132 | ||
|
|
060aa82294 | ||
|
|
2df08eac3f | ||
|
|
0ecdb9d3d0 | ||
|
|
6a7fe1bd1f | ||
|
|
87c69cb3d0 | ||
|
|
c654855c22 | ||
|
|
11bb006801 | ||
|
|
e5551f3f64 | ||
|
|
0de148005f | ||
|
|
27e4ce628e | ||
|
|
19b752bf15 | ||
|
|
6528e5bba9 | ||
|
|
37e1183e87 | ||
|
|
824512cf8e | ||
|
|
82f296b17d | ||
|
|
8302530954 | ||
|
|
6c271829aa | ||
|
|
3b30d339ee | ||
|
|
c5a268bdbd | ||
|
|
8457ea8e47 | ||
|
|
72d69eeab5 | ||
|
|
31885e41af | ||
|
|
e63726d18b | ||
|
|
3d2ab45d25 | ||
|
|
246478e1d8 | ||
|
|
9418de3b23 | ||
|
|
c6b68cad37 | ||
|
|
ef36e4d3ac | ||
|
|
ba2ddd62ad | ||
|
|
6a03e1488c | ||
|
|
cabd2ed562 | ||
|
|
4a9296276d | ||
|
|
1fd84d824c | ||
|
|
faa55ffe2d | ||
|
|
d45af4191f | ||
|
|
9f8dead8b8 | ||
|
|
564037cd53 | ||
|
|
434de6292c | ||
|
|
a12e6990e5 | ||
|
|
ce0c4a094b | ||
|
|
e36728ec08 | ||
|
|
2c1f3090a0 | ||
|
|
0c6d4b3581 | ||
|
|
1d5b9eb241 | ||
|
|
b0567dafe3 | ||
|
|
518f78f067 | ||
|
|
47a2c21824 | ||
|
|
aca5547965 | ||
|
|
0e07876630 | ||
|
|
a43901b4a4 | ||
|
|
6515fac16d | ||
|
|
fdaa7e9f48 | ||
|
|
cd779850de | ||
|
|
ef939f4357 | ||
|
|
bfb9dd7009 | ||
|
|
f282f14eff | ||
|
|
992b289500 | ||
|
|
9dcdebcab2 | ||
|
|
c0b8bacab0 | ||
|
|
f96e6420fc | ||
|
|
d7f97480cc | ||
|
|
26df41b571 | ||
|
|
4ca336017d | ||
|
|
94252ea54c | ||
|
|
f8220fec1f | ||
|
|
a5ded396c9 | ||
|
|
c457cf8064 | ||
|
|
3079f90118 | ||
|
|
4fbb331015 | ||
|
|
640c0d305c | ||
|
|
0b044c67bb | ||
|
|
7a37ba8145 | ||
|
|
b447863d9f | ||
|
|
6dd7e14f6f | ||
|
|
b09ef98dce | ||
|
|
ff1cf8a288 | ||
|
|
603793662a | ||
|
|
4cdab0dbc7 | ||
|
|
d745279fec | ||
|
|
df34c9f2fd | ||
|
|
37da9aabe3 | ||
|
|
3a2205147c | ||
|
|
5c6acaf10a | ||
|
|
d916f2dea5 | ||
|
|
254a1aedcb | ||
|
|
dce3175388 | ||
|
|
e2b6b45418 | ||
|
|
e210d1e9fc | ||
|
|
df0f449e13 | ||
|
|
895105c900 | ||
|
|
209538709b | ||
|
|
ef7c35286b | ||
|
|
70af0828cb | ||
|
|
ef18e311b1 | ||
|
|
68de84973a | ||
|
|
ef6d4f9998 | ||
|
|
9f5413963b | ||
|
|
ec7c0a5cb7 | ||
|
|
f82bd9eadf | ||
|
|
843c064ebe | ||
|
|
8a52e1d1d0 | ||
|
|
8ec220dc9a | ||
|
|
9c4b9f4433 | ||
|
|
6059a29660 | ||
|
|
e65e6764fa | ||
|
|
1e77cb234a | ||
|
|
4a357087d1 | ||
|
|
6a3823cdff | ||
|
|
c2372ce96e | ||
|
|
0f5e79b163 | ||
|
|
fa4026c83f | ||
|
|
3e4d66ca27 | ||
|
|
2ec94dc13c | ||
|
|
6b11e08f3d | ||
|
|
e884e56b47 | ||
|
|
2d74affed4 | ||
|
|
3572b17049 | ||
|
|
4ee7091cd1 | ||
|
|
ff5e89d97c | ||
|
|
c4e82afc0c | ||
|
|
52c5d85487 | ||
|
|
fd0438f80b | ||
|
|
cb5781ca65 | ||
|
|
4d8aad9dfe | ||
|
|
39429a2e44 | ||
|
|
0a3514d81e | ||
|
|
4e5e0a7c34 | ||
|
|
f6cc6a9bef | ||
|
|
2e545e9fb9 | ||
|
|
d49ef64e7e | ||
|
|
b1e6eaef90 | ||
|
|
3420d0199b | ||
|
|
671db35571 | ||
|
|
d724f87726 | ||
|
|
5ab8565205 | ||
|
|
660c567d1b | ||
|
|
2c68d13c56 | ||
|
|
b9fc155b4f | ||
|
|
8dd5aefc81 | ||
|
|
f9aaf5dcef | ||
|
|
34a6e773df | ||
|
|
f0beb03344 | ||
|
|
1ff9a81ab3 | ||
|
|
9b1771c693 | ||
|
|
823e604737 | ||
|
|
97a31bdf17 | ||
|
|
ee7a198ae6 | ||
|
|
f143a51010 | ||
|
|
e483185b2c | ||
|
|
40e1c75746 | ||
|
|
456bd4693c | ||
|
|
5ac9c0f760 | ||
|
|
f621d84b83 | ||
|
|
0dd001e95a | ||
|
|
69b5235db9 | ||
|
|
a673d63b33 | ||
|
|
36485e10f2 | ||
|
|
8a1a062127 | ||
|
|
5961e0de08 | ||
|
|
60bda521a0 | ||
|
|
49f63c0f0d | ||
|
|
33910aedb8 | ||
|
|
3359a81a14 | ||
|
|
e27f850e68 | ||
|
|
07340b8f53 | ||
|
|
2f9dd7faa8 | ||
|
|
3f81fd4a87 | ||
|
|
4dd961fc66 | ||
|
|
0bd70bfb32 | ||
|
|
3ab845d2d6 | ||
|
|
443f09d5a2 | ||
|
|
3500952bb7 | ||
|
|
e98182c4b5 | ||
|
|
2337827744 | ||
|
|
556dd66af3 | ||
|
|
8bdad3f4f9 | ||
|
|
6346d4143d | ||
|
|
fd8dc88760 | ||
|
|
b2810f390e | ||
|
|
23d3cd0f4b | ||
|
|
4a3c76773d | ||
|
|
d23e7604f0 | ||
|
|
0b3f6c6eff | ||
|
|
f2869c892b | ||
|
|
3755a9529b | ||
|
|
890f0593ee | ||
|
|
e6d666b5b0 | ||
|
|
f440acf145 | ||
|
|
1ad0e0c6dd | ||
|
|
bb8691ddc0 | ||
|
|
cf3041d07b | ||
|
|
313b3d5d6b | ||
|
|
122e7336be | ||
|
|
e954453f6b | ||
|
|
55ef3d09f3 | ||
|
|
88b9cc5ac9 | ||
|
|
f07cb7470d | ||
|
|
bd874a0232 | ||
|
|
7cc4cccb9c | ||
|
|
e85a3bacb7 | ||
|
|
530dfa758f | ||
|
|
8bc3489060 | ||
|
|
6914cb171a | ||
|
|
176c2bbd4c | ||
|
|
7771954455 | ||
|
|
a66743bb20 | ||
|
|
851c7b75f9 | ||
|
|
ed0b406027 | ||
|
|
71985972a2 | ||
|
|
2dbeef9290 | ||
|
|
a79453270d | ||
|
|
ae71808c61 | ||
|
|
5c195ba37b | ||
|
|
607ee17a86 | ||
|
|
a15aacc5b3 | ||
|
|
78fb4b1a65 | ||
|
|
8e566cba72 | ||
|
|
36184288de | ||
|
|
6fc5bf38a7 | ||
|
|
e146ecde70 | ||
|
|
72b796019a | ||
|
|
0f66d15ba8 | ||
|
|
4695e933b8 | ||
|
|
5e1da6cd6f | ||
|
|
0c3bbc0980 | ||
|
|
d2bb5bea63 | ||
|
|
c76e191d4d | ||
|
|
171594d0ae | ||
|
|
4e5f499b48 | ||
|
|
bb75ad8b8e | ||
|
|
482cc7028b | ||
|
|
735bdb3854 | ||
|
|
1ee1bd78af | ||
|
|
00463c729d | ||
|
|
dac636b3bb | ||
|
|
ad87121506 | ||
|
|
55d802f005 | ||
|
|
f17164a3d9 | ||
|
|
0b9f9b2335 | ||
|
|
71d3e24241 | ||
|
|
ccd4512e8f | ||
|
|
fe6b77d67d | ||
|
|
58d12f4e70 | ||
|
|
17303039b8 | ||
|
|
f70d5203a3 | ||
|
|
efd6b1db65 | ||
|
|
dcca0fa921 | ||
|
|
e5bc447807 | ||
|
|
cd6b4b5ab1 | ||
|
|
fb79ae6138 | ||
|
|
17801b0b13 | ||
|
|
9010388de0 | ||
|
|
2da901f994 | ||
|
|
7832cc7a5d | ||
|
|
13ab2e8d0e | ||
|
|
868d23a278 | ||
|
|
eaf2bca44e | ||
|
|
af8f537f58 | ||
|
|
bc7ff472be | ||
|
|
9b9a2b5203 | ||
|
|
347259394b | ||
|
|
ead61b5e37 | ||
|
|
c9f7814f5d | ||
|
|
3ee3355e81 | ||
|
|
ce8f8b0ce9 | ||
|
|
cae35cf3b9 | ||
|
|
900e3c23bc | ||
|
|
aaeffeb983 | ||
|
|
15600324be | ||
|
|
da4913edb5 | ||
|
|
e8cc1e4906 | ||
|
|
801fc3f9fd | ||
|
|
703b0b4cde | ||
|
|
d55895389a | ||
|
|
56009e482b | ||
|
|
767d1bb3e3 | ||
|
|
05132ac0f5 | ||
|
|
fb372f8c28 | ||
|
|
a2dc863b7a | ||
|
|
15671c6781 | ||
|
|
6296115adb | ||
|
|
0a099a91d0 | ||
|
|
5f060fe022 | ||
|
|
a4bbe1cf7b | ||
|
|
dce74ae7d5 | ||
|
|
871c509493 | ||
|
|
9333470e3b | ||
|
|
0c108f5232 | ||
|
|
39d298c867 | ||
|
|
376e804a28 | ||
|
|
79e5c0589d | ||
|
|
f6679b7368 | ||
|
|
a3c0b86b67 | ||
|
|
c8f48dd62b | ||
|
|
aed157697a | ||
|
|
913b8606d9 | ||
|
|
7cadbad36e | ||
|
|
abb5bdffcc | ||
|
|
6a44c15dd3 | ||
|
|
e669dfe533 | ||
|
|
be888f195d | ||
|
|
d87cdef311 | ||
|
|
ecf07cb520 | ||
|
|
10829d53b9 | ||
|
|
71093f2f31 | ||
|
|
e88192002a | ||
|
|
3e14282603 | ||
|
|
5f16f4ca77 | ||
|
|
7b4fa4331f | ||
|
|
3ad0a463b5 | ||
|
|
d28d640f77 | ||
|
|
594c0a6122 | ||
|
|
843a826437 | ||
|
|
c4660bc824 | ||
|
|
6053d38e4d | ||
|
|
3865254a1c | ||
|
|
65b61c09cb | ||
|
|
cce1618117 | ||
|
|
8b4b49a34b | ||
|
|
048c5dc2ec | ||
|
|
eb218da8fb | ||
|
|
65942a4ed4 | ||
|
|
1444155c09 | ||
|
|
84ac1d8cfc | ||
|
|
440e1e879a | ||
|
|
bcecf793d4 | ||
|
|
16c0d827e1 | ||
|
|
94f16a9583 | ||
|
|
4bdac3bbc2 | ||
|
|
2bc4a59aaf | ||
|
|
cf886de5ce | ||
|
|
9969966ac6 | ||
|
|
fb809338e3 | ||
|
|
c6284eb994 | ||
|
|
454bda4c82 | ||
|
|
26e21441a0 | ||
|
|
2edef0166e | ||
|
|
05cb89779b | ||
|
|
c0b2524952 | ||
|
|
f1bb445593 | ||
|
|
7f777f338f | ||
|
|
4449dfdbd6 | ||
|
|
6226e65e97 | ||
|
|
be1783c029 | ||
|
|
83a002ae5b | ||
|
|
f0fcb2b329 | ||
|
|
5ff695a5b3 | ||
|
|
43fe1f076e | ||
|
|
1afd7e042c | ||
|
|
ba6474f97e | ||
|
|
d8eed114aa | ||
|
|
e61ad6f79c | ||
|
|
9d27616003 | ||
|
|
0f2641909f | ||
|
|
9ca96acda2 | ||
|
|
77db6c5e98 | ||
|
|
c4aa9804d2 | ||
|
|
eb7bfe3a62 | ||
|
|
01fd7a8d56 | ||
|
|
81fb93e5d6 | ||
|
|
64716ec6e6 | ||
|
|
e0f787be32 | ||
|
|
138da79599 | ||
|
|
b8b290c2a5 | ||
|
|
f35dcc0ec6 | ||
|
|
62704e261a | ||
|
|
f16ace6169 | ||
|
|
35628aa93b | ||
|
|
64bf42dc1c | ||
|
|
55fc6a4282 | ||
|
|
1a59300cd7 | ||
|
|
9c7d6d835e | ||
|
|
2a20242654 | ||
|
|
171f9de073 | ||
|
|
3b4d896bcd | ||
|
|
4b602333bc | ||
|
|
347cd193dd | ||
|
|
964d8dde9b | ||
|
|
bf794ce67f | ||
|
|
51dc9ea8c1 | ||
|
|
791a1fd475 | ||
|
|
244eb5f692 | ||
|
|
003bc33c25 | ||
|
|
b488b9f1df | ||
|
|
ff4f0a59af | ||
|
|
10e2c4bbd4 | ||
|
|
18536330ce | ||
|
|
e894069d4e | ||
|
|
5be2ca0deb | ||
|
|
83e2405151 | ||
|
|
220c0dfb01 | ||
|
|
5474f1ec0e | ||
|
|
dbd01abde8 | ||
|
|
83bbab0969 | ||
|
|
cc6808a6cd | ||
|
|
191fd89cfb | ||
|
|
42e5b46365 | ||
|
|
f444ce86f9 | ||
|
|
ce05b3d087 | ||
|
|
ab4dd28c51 | ||
|
|
9e23b287ff | ||
|
|
135c1ab10b | ||
|
|
de680c93f6 | ||
|
|
57ac57824f | ||
|
|
38df64c191 | ||
|
|
575969f938 | ||
|
|
42c388e5c9 | ||
|
|
6346433d45 | ||
|
|
44e63e00c3 | ||
|
|
8648b44583 | ||
|
|
086d540ee2 | ||
|
|
adf6836ee5 | ||
|
|
62f4406c51 | ||
|
|
e4c201e105 | ||
|
|
b6b0b1e7b3 | ||
|
|
9d857d015f | ||
|
|
2055c595bb | ||
|
|
00e33ceac7 | ||
|
|
77ef1ecebf | ||
|
|
57672a2a9c | ||
|
|
0ae594b198 | ||
|
|
14a7e1c72d | ||
|
|
e570cbef12 | ||
|
|
c6d732a723 | ||
|
|
9cb0131a37 | ||
|
|
d72b665cfe | ||
|
|
d6b87585f7 | ||
|
|
2e01692e92 | ||
|
|
78ad79c08f | ||
|
|
725446a022 | ||
|
|
3bd7594b7f | ||
|
|
2bd314732a | ||
|
|
f4d7776bca | ||
|
|
80e13beb2b | ||
|
|
5c37826710 | ||
|
|
693a288839 | ||
|
|
ecc0e1b60d | ||
|
|
1d60a4ca42 | ||
|
|
7bf343d071 | ||
|
|
0430b0ed53 | ||
|
|
9147dd9925 | ||
|
|
b6e7fac619 | ||
|
|
c1d15e570d | ||
|
|
72b067c8a7 | ||
|
|
94df9a681d | ||
|
|
1d30bb7e8e | ||
|
|
0779e326d7 | ||
|
|
3ad5b67fef | ||
|
|
d747bf72fd | ||
|
|
8474cb5789 | ||
|
|
83650c7dc9 | ||
|
|
74bbbc4b1d | ||
|
|
743e4f3e31 | ||
|
|
d7a699c714 | ||
|
|
366ab8355a | ||
|
|
e721270343 | ||
|
|
2ba7d1a425 | ||
|
|
b1a5dd3b3c | ||
|
|
cbc2d1ef9b | ||
|
|
95f58df3c2 | ||
|
|
01da700458 | ||
|
|
62f9a000f2 | ||
|
|
90f32638be | ||
|
|
6aa141bb33 | ||
|
|
6d46ad44a1 | ||
|
|
afea71cfeb | ||
|
|
866b3c1f65 | ||
|
|
858f89a7b9 | ||
|
|
98d68d4f6e | ||
|
|
ea69e725eb | ||
|
|
7a27078532 | ||
|
|
2ef48bf6ce | ||
|
|
588a209a6e | ||
|
|
8103c4f50a | ||
|
|
cb74c63474 | ||
|
|
1e9711d58b | ||
|
|
48876d2726 | ||
|
|
56da84052c | ||
|
|
e2e5bd54a1 | ||
|
|
c071776215 | ||
|
|
a06361f246 | ||
|
|
3a7b68430c | ||
|
|
d85e2498fd | ||
|
|
507382b6d2 | ||
|
|
f343cf8549 | ||
|
|
832d5baf35 | ||
|
|
0253f32574 | ||
|
|
6173180272 | ||
|
|
2d21931fb1 | ||
|
|
bdaa7187fd | ||
|
|
e7ad6e06ec | ||
|
|
7c14483852 | ||
|
|
8b4b1f934e | ||
|
|
713eb3f169 | ||
|
|
48297c80d1 | ||
|
|
315ffdac92 | ||
|
|
7ddc17e6e5 | ||
|
|
e3b8073c82 | ||
|
|
80517e416c | ||
|
|
4b71aade9d | ||
|
|
1b52698c54 | ||
|
|
b8f372d83f | ||
|
|
7bc5f55fb2 | ||
|
|
7eeafd03b3 | ||
|
|
f88480d271 | ||
|
|
9b8cce85bd | ||
|
|
aa364b57e8 | ||
|
|
ea973d0de4 | ||
|
|
1a1b64cbb2 | ||
|
|
09c7e11dbc | ||
|
|
3450a56756 | ||
|
|
f37f4466ae | ||
|
|
451834b219 | ||
|
|
91924353e2 | ||
|
|
67cf94fc95 | ||
|
|
36a343c09f | ||
|
|
4a37aebbf1 | ||
|
|
d5fa9f8d72 | ||
|
|
08c244334b | ||
|
|
23dd1c2726 | ||
|
|
23a309d394 | ||
|
|
3c1bb4e145 | ||
|
|
87b320a3e3 | ||
|
|
e4fb9615c7 | ||
|
|
efad4de0af | ||
|
|
04f62700a2 | ||
|
|
08415f7931 | ||
|
|
8bc86af3c8 | ||
|
|
fa2c5e42dc | ||
|
|
e3d3952b81 | ||
|
|
a9171addde | ||
|
|
440ac087ad | ||
|
|
9dd5eb4eeb | ||
|
|
26c2c9abc9 | ||
|
|
663b07c07e | ||
|
|
5add6a0e73 | ||
|
|
5c4912feb4 | ||
|
|
75e115f390 | ||
|
|
8e443d23f4 | ||
|
|
c43c9c319b | ||
|
|
e32b128b0f | ||
|
|
5287421cea | ||
|
|
9bc95ef350 | ||
|
|
028de6addb | ||
|
|
a107605937 | ||
|
|
cec5d0147b | ||
|
|
e46b879fc4 | ||
|
|
b6764fbb29 | ||
|
|
e8899cc314 | ||
|
|
2b2054dd6b | ||
|
|
92705b0ae2 | ||
|
|
2ae8a36b7f | ||
|
|
899139dda1 | ||
|
|
b6b571e84b | ||
|
|
9e098fa997 | ||
|
|
13d00f954f | ||
|
|
fee75d0c8a | ||
|
|
abae3f14cd | ||
|
|
b8a98e2c6e | ||
|
|
d990572b5b | ||
|
|
2d37d392bc | ||
|
|
59015fc3bb | ||
|
|
c3be68f988 | ||
|
|
8610784958 | ||
|
|
f01d71c34a | ||
|
|
a06501e4b1 | ||
|
|
7c2c30fa1b | ||
|
|
903dc714f2 | ||
|
|
b1bd2c8805 | ||
|
|
0e4b4f6d8c | ||
|
|
d8d0eaf247 | ||
|
|
90096faef3 | ||
|
|
f794f75394 | ||
|
|
8de63e486c | ||
|
|
4308efca47 | ||
|
|
877fa292d6 | ||
|
|
df6ea6f497 | ||
|
|
5e9be391e1 | ||
|
|
49960b8234 | ||
|
|
d669dbe54b | ||
|
|
2d1859ac0c | ||
|
|
dc864055a3 | ||
|
|
bad9537aba | ||
|
|
303b1adc0a | ||
|
|
c582904d41 | ||
|
|
65eabf6153 | ||
|
|
cc9c9e2253 | ||
|
|
8747ae9564 | ||
|
|
9253e54958 | ||
|
|
fb774f2caa | ||
|
|
ba120024a6 | ||
|
|
1fe0e69914 | ||
|
|
0fe04e411c | ||
|
|
35074f959f | ||
|
|
0d698fbaf1 | ||
|
|
c9e2a52327 | ||
|
|
a58e06109b | ||
|
|
65375e4836 | ||
|
|
edc517d33f | ||
|
|
354c82dc34 | ||
|
|
fe2acd0665 | ||
|
|
19b5b1a90c | ||
|
|
e7281e2118 | ||
|
|
10f334cf15 | ||
|
|
a057fd6458 | ||
|
|
3bafabeb0a | ||
|
|
edefbe17da | ||
|
|
a65041ffcd | ||
|
|
0962c60844 | ||
|
|
3d72eceac0 | ||
|
|
57b5deff5b | ||
|
|
1163dee352 | ||
|
|
f7fe562bba | ||
|
|
3394f98a8a | ||
|
|
a575b35286 | ||
|
|
0f41e79e98 | ||
|
|
24bf22a31f | ||
|
|
05a1b8802e | ||
|
|
9c409d9507 | ||
|
|
4ee7161c36 | ||
|
|
4cd9ce0ff7 | ||
|
|
a2d0dfd8b2 | ||
|
|
d4164fe0ba | ||
|
|
6bbecdd7c0 | ||
|
|
0784c3ae05 | ||
|
|
679757ccfa | ||
|
|
63a4e693e3 | ||
|
|
b21041d9e0 | ||
|
|
f014bb4587 | ||
|
|
9030f7c4ce | ||
|
|
bde6c38bd4 | ||
|
|
0aefb6c6ec | ||
|
|
da650ccdef | ||
|
|
92d3e70c1f | ||
|
|
cb5dfaf066 | ||
|
|
cd75e5e4cc | ||
|
|
7bd6ab5ec0 | ||
|
|
92f9c89cf1 | ||
|
|
eb2632b23a | ||
|
|
8c90bc6808 | ||
|
|
66146532e1 | ||
|
|
dbf3a41c1d | ||
|
|
942bbf9c6c | ||
|
|
a9d7cc527c | ||
|
|
2d362722ed | ||
|
|
f81638e625 | ||
|
|
8ae77df0ff | ||
|
|
110bd0e5f7 | ||
|
|
1b3a366cb7 | ||
|
|
3b5d9a145f | ||
|
|
fc37fc067a | ||
|
|
bb18a28484 | ||
|
|
86ca08b9aa | ||
|
|
9522df7835 | ||
|
|
55e0fd0226 | ||
|
|
ff9a986a2e | ||
|
|
df31fe2a8b | ||
|
|
1aeaf996bc | ||
|
|
cb0e59e91a | ||
|
|
5f02f49e9d | ||
|
|
ce9f91fe2c | ||
|
|
ac37745721 | ||
|
|
e44aeefda8 | ||
|
|
dbff2b659a | ||
|
|
52ef8e8b76 | ||
|
|
b71567c754 | ||
|
|
d1670c743f | ||
|
|
009b38f471 | ||
|
|
0cf5f30c0d | ||
|
|
cd4e1f179d | ||
|
|
b89f9d6b35 | ||
|
|
c3ece96390 | ||
|
|
770739b44e | ||
|
|
84984c2cde | ||
|
|
86393c8674 | ||
|
|
d4aed3bc45 | ||
|
|
818cd77450 | ||
|
|
dc26c70400 | ||
|
|
6547d713ea | ||
|
|
e8dcb0c242 | ||
|
|
100d0be513 | ||
|
|
fc8db2d977 | ||
|
|
e5d98d7889 | ||
|
|
c7b1a05ed3 | ||
|
|
649a90bd5e | ||
|
|
0fc880cf21 | ||
|
|
0d82a4c2c6 | ||
|
|
fb3aa59683 | ||
|
|
b938792ca8 | ||
|
|
bb74946d54 | ||
|
|
3e78105509 | ||
|
|
c6adf0ac89 | ||
|
|
219b4dca94 | ||
|
|
acec292b0f | ||
|
|
7ba86cbd23 | ||
|
|
3002eaf479 | ||
|
|
15422ca504 | ||
|
|
7067f00bee | ||
|
|
a0616eb251 | ||
|
|
5d4da0e4c7 | ||
|
|
546e3475fb | ||
|
|
56c44ebb58 | ||
|
|
bd03bc730c | ||
|
|
e0e1e58104 | ||
|
|
940de662b6 | ||
|
|
d151c1192b | ||
|
|
15ae10579c | ||
|
|
359696408d | ||
|
|
d3766368f8 | ||
|
|
73af7451fc | ||
|
|
e27fcefe45 | ||
|
|
aa5ade5517 | ||
|
|
94b6a66ce4 | ||
|
|
a51e486fa6 | ||
|
|
2b608daea4 | ||
|
|
aa60c2afd1 | ||
|
|
cff22c74a9 | ||
|
|
b77b8ee0da | ||
|
|
610aef2f14 | ||
|
|
e15ca41d6e | ||
|
|
60e9e53af4 | ||
|
|
9812000724 | ||
|
|
d72401fc39 | ||
|
|
822eba7982 | ||
|
|
9327564e6e | ||
|
|
6e9fe42a83 | ||
|
|
a32cf28ccb | ||
|
|
55b101802a | ||
|
|
ecda8b4394 | ||
|
|
6d4f8d4c26 | ||
|
|
26aa53f3fa | ||
|
|
2f7a94ea3f | ||
|
|
2b622b4ee9 | ||
|
|
3e280f1e08 | ||
|
|
c54d52ad45 | ||
|
|
e54d3ea96b | ||
|
|
e07cb8257d | ||
|
|
74b6a8fdba | ||
|
|
e48c76397c | ||
|
|
64aff5d041 | ||
|
|
87b24e35d0 | ||
|
|
fb48c81207 | ||
|
|
cd9f302f8b | ||
|
|
df3bc97988 | ||
|
|
94191a2902 | ||
|
|
a0dd540075 | ||
|
|
5684665123 | ||
|
|
5db407d750 | ||
|
|
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 | ||
|
|
983e8dbbd3 | ||
|
|
b2150477d7 | ||
|
|
f83eeac33a | ||
|
|
37c4a85e76 | ||
|
|
8732af0f43 |
No files matched your search
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(yarn build:*)",
|
||||||
|
"Bash(cat:*)",
|
||||||
|
"Bash(node:*)",
|
||||||
|
"Bash(npm show:*)",
|
||||||
|
"Bash(python3:*)",
|
||||||
|
"Bash(yarn test:unit:*)",
|
||||||
|
"Bash(npx tsc:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,23 @@
|
|||||||
|
tmp/*
|
||||||
Dockerfile
|
Dockerfile
|
||||||
tokens
|
docker-compose
|
||||||
|
src/core/engines/gows/proto
|
||||||
|
.*sessions
|
||||||
|
sessions
|
||||||
files
|
files
|
||||||
venv
|
venv
|
||||||
|
.wwebjs_auth
|
||||||
|
docs
|
||||||
|
examples
|
||||||
|
.github
|
||||||
|
.git
|
||||||
|
.envs/*
|
||||||
|
.env
|
||||||
|
|
||||||
# compiled output
|
# compiled output
|
||||||
/dist
|
/dist
|
||||||
/node_modules
|
/node_modules
|
||||||
|
.yarn
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
# WAHA - WhatsApp HTTP API
|
||||||
|
#
|
||||||
|
# https://waha.devlike.pro/docs/how-to/config/
|
||||||
|
#
|
||||||
|
|
||||||
|
# ====================
|
||||||
|
# ===== SECURITY =====
|
||||||
|
# ====================
|
||||||
|
WAHA_API_KEY=00000000000000000000000000000000
|
||||||
|
WAHA_DASHBOARD_USERNAME=admin
|
||||||
|
WAHA_DASHBOARD_PASSWORD=11111111111111111111111111111111
|
||||||
|
WHATSAPP_SWAGGER_USERNAME=admin
|
||||||
|
WHATSAPP_SWAGGER_PASSWORD=11111111111111111111111111111111
|
||||||
|
|
||||||
|
# Disable Dashboard or Swagger
|
||||||
|
WAHA_DASHBOARD_ENABLED=True
|
||||||
|
WHATSAPP_SWAGGER_ENABLED=True
|
||||||
|
|
||||||
|
# ==================
|
||||||
|
# ===== COMMON =====
|
||||||
|
# ==================
|
||||||
|
# WhatsApp engine (WEBJS is default, GOWS or NOWEB for better performance)
|
||||||
|
WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||||
|
# So you don't have to create sessions/apps when switching between engines
|
||||||
|
WAHA_NAMESPACE=all
|
||||||
|
|
||||||
|
# "Firefox (YourApp)" in Linked Devices
|
||||||
|
# WAHA_CLIENT_DEVICE_NAME=YourApp
|
||||||
|
|
||||||
|
# Base URL for the API (used for webhooks, file URLs, etc.)
|
||||||
|
WAHA_BASE_URL=http://localhost:3000
|
||||||
|
# WAHA_PUBLIC_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Server configuration (if you need to customize hostname/port)
|
||||||
|
# WHATSAPP_API_SCHEMA=http
|
||||||
|
# WHATSAPP_API_PORT=3000
|
||||||
|
# WHATSAPP_API_HOSTNAME=localhost
|
||||||
|
|
||||||
|
|
||||||
|
# ===================
|
||||||
|
# ====== GOWS =======
|
||||||
|
# ===================
|
||||||
|
# By default, GOWS uses cgo (libc) DNS resolver
|
||||||
|
# You can ask to use golang one, but it's less reliable
|
||||||
|
# GODEBUG=netdns=go
|
||||||
|
|
||||||
|
# ===================
|
||||||
|
# ===== LOGGING =====
|
||||||
|
# ===================
|
||||||
|
# Log format: JSON (for log management systems) or PRETTY (for development)
|
||||||
|
WAHA_LOG_FORMAT=JSON
|
||||||
|
|
||||||
|
# Log level: info, debug, error, warn
|
||||||
|
WAHA_LOG_LEVEL=info
|
||||||
|
|
||||||
|
# Don't print QR codes in logs
|
||||||
|
WAHA_PRINT_QR=False
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# ===== MEDIA STORAGE =====
|
||||||
|
# =========================
|
||||||
|
# Local storage (default)
|
||||||
|
WAHA_MEDIA_STORAGE=LOCAL
|
||||||
|
WHATSAPP_FILES_LIFETIME=0
|
||||||
|
WHATSAPP_FILES_FOLDER=/app/.media
|
||||||
|
|
||||||
|
# Media download settings
|
||||||
|
# WHATSAPP_DOWNLOAD_MEDIA=true
|
||||||
|
# WHATSAPP_FILES_MIMETYPES=image/jpeg,image/png
|
||||||
|
|
||||||
|
# S3 storage (uncomment to use)
|
||||||
|
# WAHA_MEDIA_STORAGE=S3
|
||||||
|
# WAHA_S3_REGION=eu-west-2
|
||||||
|
# WAHA_S3_BUCKET=waha
|
||||||
|
# WAHA_S3_ACCESS_KEY_ID=minioadmin
|
||||||
|
# WAHA_S3_SECRET_ACCESS_KEY=minioadmin
|
||||||
|
# WAHA_S3_ENDPOINT=http://minio:9000
|
||||||
|
# WAHA_S3_FORCE_PATH_STYLE=True
|
||||||
|
# WAHA_S3_PROXY_FILES=True
|
||||||
|
|
||||||
|
# PostgreSQL storage (uncomment to use)
|
||||||
|
# WAHA_MEDIA_STORAGE=POSTGRESQL
|
||||||
|
# WAHA_MEDIA_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||||
|
|
||||||
|
# ===========================
|
||||||
|
# ===== SESSION STORAGE =====
|
||||||
|
# ===========================
|
||||||
|
# PostgreSQL for sessions (uncomment to use)
|
||||||
|
# WHATSAPP_SESSIONS_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||||
|
|
||||||
|
# MongoDB for sessions (uncomment to use)
|
||||||
|
# WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongouser:mongopassword@mongodb:27017
|
||||||
|
|
||||||
|
# =================
|
||||||
|
# ===== APPS ======
|
||||||
|
# =================
|
||||||
|
# WAHA_APPS_ENABLED=True
|
||||||
|
# REDIS_URL=redis://:redis@redis:6379
|
||||||
|
|
||||||
|
# Apps Jobs configuration - *_AGE in seconds
|
||||||
|
# WAHA_APPS_JOBS_REMOVE_ON_COMPLETE_AGE=
|
||||||
|
# WAHA_APPS_JOBS_REMOVE_ON_COMPLETE_COUNT=
|
||||||
|
# WAHA_APPS_JOBS_REMOVE_ON_FAIL_AGE=
|
||||||
|
# WAHA_APPS_JOBS_REMOVE_ON_FAIL_COUNT
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================
|
||||||
|
# ===== ADVANCED CONFIGURATION =====
|
||||||
|
# ==================================
|
||||||
|
|
||||||
|
# Timezone for screenshots and logs
|
||||||
|
# TZ=Europe/Dublin
|
||||||
|
|
||||||
|
# Session management
|
||||||
|
# WHATSAPP_START_SESSION=session1,session2
|
||||||
|
# WHATSAPP_RESTART_ALL_SESSIONS=False
|
||||||
|
|
||||||
|
# Webhooks
|
||||||
|
# WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
|
||||||
|
# WHATSAPP_HOOK_CUSTOM_HEADERS=custom_header_key:custom_header_value
|
||||||
|
# WHATSAPP_HOOK_EVENTS=session.status,message,message.reaction
|
||||||
|
|
||||||
|
# Proxy configuration
|
||||||
|
# WHATSAPP_PROXY_SERVER=proxy.example.com:3128
|
||||||
|
# WHATSAPP_PROXY_SERVER_USERNAME=user
|
||||||
|
# WHATSAPP_PROXY_SERVER_PASSWORD=pass
|
||||||
|
|
||||||
|
# HTTPS configuration
|
||||||
|
# !DEPRECATED!
|
||||||
|
# Setup nginx reverse proxy to handle TLS connection
|
||||||
|
# using Let's encrypt or self-issued certificate
|
||||||
|
# WAHA_HTTPS_ENABLED=true
|
||||||
|
# WAHA_HTTPS_PATH_KEY=/etc/letsencrypt/live/waha.example.pro/privkey.pem
|
||||||
|
# WAHA_HTTPS_PATH_CERT=/etc/letsencrypt/live/waha.example.pro/cert.pem
|
||||||
|
# WAHA_HTTPS_PATH_CA=/etc/letsencrypt/live/waha.example.pro/chain.pem
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
src/core/engines/gows/grpc/*
|
||||||
|
src/core/engines/gows/proto/*
|
||||||
|
src/core/engines/webjs/_lodash.js
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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',
|
|
||||||
],
|
|
||||||
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,59 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: '[WEBJS] - '
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
type: 'BUG'
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
Get the WAHA version by calling `GET /api/version`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "YYYY.MM.DD",
|
||||||
|
"engine": "ENGINE",
|
||||||
|
"tier": "TIER"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Try to update to
|
||||||
|
[the latest version](https://github.com/devlikeapro/waha/releases) before
|
||||||
|
creating an issue!
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
### 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,25 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
type: 'Feature'
|
||||||
|
---
|
||||||
|
|
||||||
|
**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,11 @@
|
|||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: I have a question about the project or feature
|
||||||
|
title: '[Question] '
|
||||||
|
labels: 'question'
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
👉 Kindly use
|
||||||
|
[GitHub Discussions](https://github.com/devlikeapro/waha/discussions) to ask
|
||||||
|
questions!
|
||||||
@@ -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,84 @@ name: Release
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- '*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-docker:
|
docker-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.runner }}
|
||||||
|
name:
|
||||||
|
${{ matrix.engine }} - ${{ matrix.browser }} - ${{ matrix.platform }} -
|
||||||
|
${{ matrix.tag }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# Chromium - x86
|
||||||
|
- runner: 'ubuntu-latest'
|
||||||
|
tag: 'latest'
|
||||||
|
platform: 'amd64'
|
||||||
|
browser: 'chromium'
|
||||||
|
engine: 'WEBJS'
|
||||||
|
goss: 'goss-linux-amd64'
|
||||||
|
|
||||||
|
# Chromium - ARM
|
||||||
|
- runner: 'ubuntu-24.04-arm'
|
||||||
|
tag: 'arm'
|
||||||
|
platform: 'linux/arm64'
|
||||||
|
browser: 'chromium'
|
||||||
|
engine: 'WEBJS'
|
||||||
|
goss: 'goss-linux-arm'
|
||||||
|
|
||||||
|
# Chrome - x86
|
||||||
|
- runner: 'ubuntu-latest'
|
||||||
|
tag: 'chrome'
|
||||||
|
platform: 'amd64'
|
||||||
|
browser: 'chrome'
|
||||||
|
engine: 'WEBJS'
|
||||||
|
goss: 'goss-linux-amd64'
|
||||||
|
|
||||||
|
# Chrome - ARM (Chrome is not available for ARM)
|
||||||
|
#- runner: "ubuntu-24.04-arm"
|
||||||
|
# tag: "chrome-arm"
|
||||||
|
# platform: "linux/arm64"
|
||||||
|
# browser: "chrome"
|
||||||
|
# goss: 'goss-linux-arm'
|
||||||
|
|
||||||
|
# NOWEB - x86
|
||||||
|
- runner: 'ubuntu-latest'
|
||||||
|
tag: 'noweb'
|
||||||
|
platform: 'amd64'
|
||||||
|
browser: 'none'
|
||||||
|
engine: 'NOWEB'
|
||||||
|
goss: 'goss-linux-amd64'
|
||||||
|
|
||||||
|
# NOWEB - ARM
|
||||||
|
- runner: 'ubuntu-24.04-arm'
|
||||||
|
tag: 'noweb-arm'
|
||||||
|
platform: 'linux/arm64'
|
||||||
|
browser: 'none'
|
||||||
|
engine: 'NOWEB'
|
||||||
|
goss: 'goss-linux-arm'
|
||||||
|
|
||||||
|
# GOWS - x86
|
||||||
|
- runner: 'ubuntu-latest'
|
||||||
|
tag: 'gows'
|
||||||
|
platform: 'amd64'
|
||||||
|
browser: 'none'
|
||||||
|
engine: 'GOWS'
|
||||||
|
goss: 'goss-linux-amd64'
|
||||||
|
|
||||||
|
# No browser - ARM
|
||||||
|
- runner: 'ubuntu-24.04-arm'
|
||||||
|
tag: 'gows-arm'
|
||||||
|
platform: 'linux/arm64'
|
||||||
|
browser: 'none'
|
||||||
|
engine: 'GOWS'
|
||||||
|
goss: 'goss-linux-arm'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
@@ -20,10 +88,11 @@ jobs:
|
|||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: allburov/whatsapp-http-api
|
images: ${{ vars.DOCKER_IMAGE }}
|
||||||
flavor: latest=true
|
flavor: latest=false
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=raw,value=${{ matrix.tag }}
|
||||||
|
type=raw,value=${{ matrix.tag }}-{{tag}}
|
||||||
|
|
||||||
- name: Login to image repository
|
- name: Login to image repository
|
||||||
if: github.ref_type == 'tag'
|
if: github.ref_type == 'tag'
|
||||||
@@ -32,13 +101,96 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build (attempt 1)
|
||||||
uses: docker/build-push-action@v3
|
id: build1
|
||||||
|
continue-on-error: true
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: Dockerfile
|
file: Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
build-args: |
|
||||||
|
USE_BROWSER=${{ matrix.browser }}
|
||||||
|
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Wait to retry build
|
||||||
|
if: steps.build1.outcome == 'failure'
|
||||||
|
run: |
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
- name: Build (attempt 2)
|
||||||
|
id: build2
|
||||||
|
if: steps.build1.outcome == 'failure'
|
||||||
|
continue-on-error: true
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
build-args: |
|
||||||
|
USE_BROWSER=${{ matrix.browser }}
|
||||||
|
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Wait to retry build
|
||||||
|
if: steps.build2.outcome == 'failure'
|
||||||
|
run: |
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
- name: Build (attempt 3)
|
||||||
|
if: steps.build2.outcome == 'failure'
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
build-args: |
|
||||||
|
USE_BROWSER=${{ matrix.browser }}
|
||||||
|
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Install goss
|
||||||
|
env:
|
||||||
|
GOSS_DOWNLOAD: 'https://github.com/goss-org/goss/releases/download'
|
||||||
|
GOSS_VERSION: 'v0.4.8'
|
||||||
|
run: |
|
||||||
|
curl -L https://github.com/goss-org/goss/releases/download/${{ env.GOSS_VERSION }}/${{ matrix.goss }} -o /usr/local/bin/goss
|
||||||
|
chmod +rx /usr/local/bin/goss
|
||||||
|
|
||||||
|
- name: Run smoke tests
|
||||||
|
working-directory: tests/smoke
|
||||||
|
timeout-minutes: 2
|
||||||
|
run: |
|
||||||
|
docker run -d \
|
||||||
|
--name smoke \
|
||||||
|
--rm -p3000:3000 \
|
||||||
|
-e WAHA_API_KEY=test \
|
||||||
|
${{ vars.DOCKER_IMAGE }}:${{ matrix.tag }}
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
docker logs smoke
|
||||||
|
goss validate --retry-timeout 30s --sleep 1s
|
||||||
|
docker rm --force smoke
|
||||||
|
|
||||||
|
- name: Push
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
build-args: |
|
||||||
|
USE_BROWSER=${{ matrix.browser }}
|
||||||
|
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
|
||||||
push: ${{ github.ref_type == 'tag' }}
|
push: ${{ github.ref_type == 'tag' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha
|
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
name: Dev
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# unit-tests:
|
||||||
|
# if: github.repository == 'devlikeapro/waha-plus'
|
||||||
|
# runs-on: ubuntu-22.04
|
||||||
|
# name: Unit tests
|
||||||
|
# steps:
|
||||||
|
# - name: Checkout
|
||||||
|
# uses: actions/checkout@v4
|
||||||
|
# with:
|
||||||
|
# fetch-depth: 0
|
||||||
|
#
|
||||||
|
# - name: Set up Node
|
||||||
|
# uses: actions/setup-node@v4
|
||||||
|
# with:
|
||||||
|
# node-version: 22
|
||||||
|
#
|
||||||
|
# - name: Enable Corepack
|
||||||
|
# run: |
|
||||||
|
# corepack enable
|
||||||
|
# corepack prepare yarn@4.9.2 --activate
|
||||||
|
#
|
||||||
|
# - name: Install dependencies
|
||||||
|
# run: |
|
||||||
|
# unset GIT_CONFIG_PARAMETERS
|
||||||
|
# yarn install --immutable
|
||||||
|
#
|
||||||
|
# - name: Run unit tests
|
||||||
|
# run: yarn test:unit
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
if: github.repository == 'devlikeapro/waha-plus'
|
||||||
|
# needs: unit-tests
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
name:
|
||||||
|
${{ matrix.engine }} - ${{ matrix.browser }} - ${{ matrix.platform }} -
|
||||||
|
${{ matrix.tag }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# Chromium - x86
|
||||||
|
- runner: 'ubuntu-latest'
|
||||||
|
tag: 'dev'
|
||||||
|
platform: 'amd64'
|
||||||
|
browser: 'chromium'
|
||||||
|
engine: 'WEBJS'
|
||||||
|
goss: 'goss-linux-amd64'
|
||||||
|
|
||||||
|
# Chrome - x86
|
||||||
|
- runner: 'ubuntu-latest'
|
||||||
|
tag: 'dev-chrome'
|
||||||
|
platform: 'amd64'
|
||||||
|
browser: 'chrome'
|
||||||
|
engine: 'WEBJS'
|
||||||
|
goss: 'goss-linux-amd64'
|
||||||
|
|
||||||
|
# Chromium - ARM
|
||||||
|
- runner: 'ubuntu-24.04-arm'
|
||||||
|
tag: 'dev-arm'
|
||||||
|
platform: 'linux/arm64'
|
||||||
|
browser: 'chromium'
|
||||||
|
engine: 'WEBJS'
|
||||||
|
goss: 'goss-linux-arm'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check recent changes
|
||||||
|
id: recent
|
||||||
|
run: |
|
||||||
|
count=$(git rev-list --after="24 hours ago" --count HEAD || true)
|
||||||
|
if [ "$count" -gt 0 ]; then
|
||||||
|
echo "has_recent=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "has_recent=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: No recent changes summary
|
||||||
|
if: ${{ steps.recent.outputs.has_recent != 'true' }}
|
||||||
|
run:
|
||||||
|
echo "No commits in the last 24 hours; skipping build." >>
|
||||||
|
"$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
if: ${{ steps.recent.outputs.has_recent == 'true' }}
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
if: ${{ steps.recent.outputs.has_recent == 'true' }}
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ vars.DOCKER_IMAGE }}
|
||||||
|
flavor: latest=false
|
||||||
|
tags: |
|
||||||
|
type=raw,value=${{ matrix.tag }}
|
||||||
|
|
||||||
|
- name: Login to image repository
|
||||||
|
if: ${{ steps.recent.outputs.has_recent == 'true' }}
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build (attempt 1)
|
||||||
|
id: build1
|
||||||
|
if: ${{ steps.recent.outputs.has_recent == 'true' }}
|
||||||
|
continue-on-error: true
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
build-args: |
|
||||||
|
USE_BROWSER=${{ matrix.browser }}
|
||||||
|
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Wait to retry build
|
||||||
|
if:
|
||||||
|
${{ steps.recent.outputs.has_recent == 'true' && steps.build1.outcome
|
||||||
|
== 'failure' }}
|
||||||
|
run: |
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
- name: Build (attempt 2)
|
||||||
|
id: build2
|
||||||
|
if:
|
||||||
|
${{ steps.recent.outputs.has_recent == 'true' && steps.build1.outcome
|
||||||
|
== 'failure' }}
|
||||||
|
continue-on-error: true
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
build-args: |
|
||||||
|
USE_BROWSER=${{ matrix.browser }}
|
||||||
|
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Wait to retry build
|
||||||
|
if:
|
||||||
|
${{ steps.recent.outputs.has_recent == 'true' && steps.build2.outcome
|
||||||
|
== 'failure' }}
|
||||||
|
run: |
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
- name: Build (attempt 3)
|
||||||
|
if:
|
||||||
|
${{ steps.recent.outputs.has_recent == 'true' && steps.build2.outcome
|
||||||
|
== 'failure' }}
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
build-args: |
|
||||||
|
USE_BROWSER=${{ matrix.browser }}
|
||||||
|
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Install goss
|
||||||
|
if: ${{ steps.recent.outputs.has_recent == 'true' }}
|
||||||
|
env:
|
||||||
|
GOSS_DOWNLOAD: 'https://github.com/goss-org/goss/releases/download'
|
||||||
|
GOSS_VERSION: 'v0.4.8'
|
||||||
|
run: |
|
||||||
|
curl -L https://github.com/goss-org/goss/releases/download/${{ env.GOSS_VERSION }}/${{ matrix.goss }} -o /usr/local/bin/goss
|
||||||
|
chmod +rx /usr/local/bin/goss
|
||||||
|
|
||||||
|
- name: Run smoke tests
|
||||||
|
if: ${{ steps.recent.outputs.has_recent == 'true' }}
|
||||||
|
working-directory: tests/smoke
|
||||||
|
timeout-minutes: 2
|
||||||
|
run: |
|
||||||
|
docker run -d \
|
||||||
|
--name smoke \
|
||||||
|
--rm -p3000:3000 \
|
||||||
|
-e WAHA_API_KEY=test \
|
||||||
|
${{ vars.DOCKER_IMAGE }}:${{ matrix.tag }}
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
docker logs smoke
|
||||||
|
goss validate --retry-timeout 30s --sleep 1s
|
||||||
|
docker rm --force smoke
|
||||||
|
|
||||||
|
- name: Push
|
||||||
|
if: ${{ steps.recent.outputs.has_recent == 'true' }}
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
build-args: |
|
||||||
|
USE_BROWSER=${{ matrix.browser }}
|
||||||
|
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
name: 'github-translate'
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
discussion:
|
||||||
|
types: [created]
|
||||||
|
discussion_comment:
|
||||||
|
types: [created]
|
||||||
|
# pull_request_target:
|
||||||
|
# types: [opened, edited]
|
||||||
|
# pull_request_review_comment:
|
||||||
|
# types: [created, edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
en:
|
||||||
|
if: github.repository == 'devlikeapro/waha'
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
discussions: write
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: devlikeapro/github-translate-action@main
|
||||||
|
env:
|
||||||
|
# GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
IS_MODIFY_TITLE: true
|
||||||
|
CUSTOM_BOT_NOTE: 'English'
|
||||||
|
TARGET_LANGUAGE: 'eng'
|
||||||
|
TRANSLATED_LANGUAGE: 'en'
|
||||||
|
# pt:
|
||||||
|
# permissions:
|
||||||
|
# issues: write
|
||||||
|
# discussions: write
|
||||||
|
# pull-requests: write
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@v4
|
||||||
|
# - uses: devlikeapro/github-translate-action@main
|
||||||
|
# env:
|
||||||
|
# # GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# with:
|
||||||
|
# IS_MODIFY_TITLE: false
|
||||||
|
# CUSTOM_BOT_NOTE: 'Portuguese'
|
||||||
|
# TARGET_LANGUAGE: 'por'
|
||||||
|
# TRANSLATED_LANGUAGE: 'pt'
|
||||||
@@ -1,6 +1,26 @@
|
|||||||
|
tmp/*
|
||||||
|
src/core/engines/gows/proto
|
||||||
|
.env
|
||||||
|
.envs/*
|
||||||
|
*.heapsnapshot
|
||||||
|
.yarn
|
||||||
|
example.ts
|
||||||
|
.*sessions
|
||||||
|
sessions
|
||||||
|
.secrets
|
||||||
|
.minio
|
||||||
|
.media
|
||||||
tokens
|
tokens
|
||||||
files
|
files
|
||||||
venv
|
venv
|
||||||
|
.wwebjs_auth
|
||||||
|
src/test.ts
|
||||||
|
src/test.js
|
||||||
|
test.js
|
||||||
|
.wwebjs_cache
|
||||||
|
src/dashboard
|
||||||
|
.eslintcache
|
||||||
|
__pycache__
|
||||||
|
|
||||||
# compiled output
|
# compiled output
|
||||||
/dist
|
/dist
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"plugins": ["typescript"],
|
||||||
|
"env": {
|
||||||
|
"builtin": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"@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-comment": "off",
|
||||||
|
"no-unused-vars": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,77 @@
|
|||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
^src/dashboard/.*|
|
||||||
|
^src/core/engines/webjs/.*html|
|
||||||
|
^src/core/engines/gows/grpc/.*|
|
||||||
|
^src/core/engines/gows/proto/.*|
|
||||||
|
^src/plus/engines/webjs/.*html|
|
||||||
|
^.yarn/.*
|
||||||
|
)$
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: commit-message-prefix
|
||||||
|
name: Validate Commit Message for src/plus Changes
|
||||||
|
entry: python ./.precommit/validate_commit_message.py
|
||||||
|
language: python
|
||||||
|
stages: [commit-msg]
|
||||||
|
|
||||||
|
- id: oxlint
|
||||||
|
name: oxlint
|
||||||
|
entry: npx oxlint --fix --deny-warnings
|
||||||
|
language: system
|
||||||
|
pass_filenames: true
|
||||||
|
files: \.(ts|tsx|js|jsx)$
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
rev: 'v3.1.0'
|
||||||
|
hooks:
|
||||||
|
- id: prettier
|
||||||
|
stages: [pre-commit]
|
||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
docs/.*|
|
||||||
|
README.md|
|
||||||
|
tests/smoke/goss.yaml|
|
||||||
|
)$
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs
|
- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs
|
||||||
rev: v1.1.2
|
rev: v1.1.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: markdown-toc
|
- id: markdown-toc
|
||||||
|
stages: [pre-commit]
|
||||||
|
name: README.md
|
||||||
|
files: ^README.md$
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: no-plus-in-core
|
||||||
|
stages: [pre-commit]
|
||||||
|
name: No "plus" in core
|
||||||
|
language: pygrep
|
||||||
|
entry: 'plus'
|
||||||
|
files: |
|
||||||
|
(?x)^(
|
||||||
|
src/api/.*|
|
||||||
|
src/core/.*|
|
||||||
|
src/structures/.*|
|
||||||
|
src/config.service.ts|
|
||||||
|
)$
|
||||||
|
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: no-console-log
|
||||||
|
stages: [pre-commit]
|
||||||
|
name: No console.log() calls
|
||||||
|
language: pygrep
|
||||||
|
entry: 'console\.log'
|
||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
entrypoint.sh|
|
||||||
|
AGENTS.md|
|
||||||
|
.pre-commit-config.yaml|
|
||||||
|
^src/core/engines/webjs/session.webjs.core.ts|
|
||||||
|
^src/core/engines/webjs/_lodash.js|
|
||||||
|
^tests/perf/.*|
|
||||||
|
^scripts/.*|
|
||||||
|
^scripts/init-waha.js|
|
||||||
|
)$
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def get_staged_files():
|
||||||
|
result = subprocess.run(["git", "diff", "--cached", "--name-only"], stdout=subprocess.PIPE, text=True)
|
||||||
|
files = result.stdout.splitlines()
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def get_commit_message(commit_msg_filepath):
|
||||||
|
with open(commit_msg_filepath, 'r') as f:
|
||||||
|
return f.readline().strip()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
commit_msg_filepath = sys.argv[1]
|
||||||
|
commit_message = get_commit_message(commit_msg_filepath)
|
||||||
|
staged_files = get_staged_files()
|
||||||
|
|
||||||
|
has_plus_changes = any(f.startswith('src/plus') for f in staged_files)
|
||||||
|
print(commit_message)
|
||||||
|
starts_with_plus = commit_message.startswith('[PLUS]')
|
||||||
|
|
||||||
|
if has_plus_changes and not starts_with_plus:
|
||||||
|
print("'[PLUS]' not found in commit message, but there's changes are from 'src/plus'.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if starts_with_plus and not all(f.startswith('src/plus') for f in staged_files):
|
||||||
|
print("'[PLUS]' found in commit message, but there's changes from other directories. \n"
|
||||||
|
"Changes MUST be only from 'src/plus'.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -1,4 +1,13 @@
|
|||||||
{
|
{
|
||||||
"singleQuote": true,
|
"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,5 @@
|
|||||||
|
compressionLevel: mixed
|
||||||
|
|
||||||
|
enableGlobalCache: false
|
||||||
|
|
||||||
|
nodeLinker: node-modules
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
# WAHA Agent Playbook
|
||||||
|
|
||||||
|
This guide summarizes how to explore, modify, and validate the WhatsApp HTTP API
|
||||||
|
(WAHA) codebase when assisting as an automation or coding agent.
|
||||||
|
|
||||||
|
- When you asked to refactor or "apply new lib" instead of current one - do not
|
||||||
|
change the behavior, make a minimum amount of changes possible. If you see
|
||||||
|
some edge case during that process that haven't been covered - tell it,
|
||||||
|
suggest fix, but don't change the code.
|
||||||
|
|
||||||
|
## Product & Variants
|
||||||
|
|
||||||
|
- WAHA ships in **Core** and **Plus** editions. Core lives under `src/core` and
|
||||||
|
only supports the default session plus minimal media features. Plus extends
|
||||||
|
core via `src/plus` to add multi-session orchestration, richer media handling,
|
||||||
|
and external storage integrations.
|
||||||
|
- Core code must remain free from Plus-only references. A pre-commit hook
|
||||||
|
rejects the word `plus` inside core files; reuse abstractions exposed through
|
||||||
|
`@waha/core/**` instead of importing Plus modules from Core.
|
||||||
|
- Commit subjects are validated: changes that touch `src/plus` require a
|
||||||
|
`[PLUS] …` prefix and must not include non-Plus files; everything else must
|
||||||
|
use `[core] …`. Verify against `./.precommit/validate_commit_message.py` if
|
||||||
|
unsure.
|
||||||
|
|
||||||
|
## Tech Stack Snapshot
|
||||||
|
|
||||||
|
- **Runtime**: Node.js 22.x, Yarn 3.6 (Berry). Always install packages and run
|
||||||
|
scripts with Yarn.
|
||||||
|
- **Framework**: NestJS v11 with dependency injection, modular controllers in
|
||||||
|
`src/api`, and Pino-based logging via `nestjs-pino`.
|
||||||
|
- **Engines**: WhatsApp engines are abstracted (`WEBJS`, `GOWS`, `NOWEB`). Core
|
||||||
|
uses `SessionManagerCore`; Plus swaps to `SessionManagerPlus` with extra
|
||||||
|
storage backends (Mongo/Postgres/SQLite).
|
||||||
|
- **ESM Bridge**: ESM-only dependencies (Baileys) load through
|
||||||
|
`src/vendor/esm.ts`. Add new ESM modules there to ensure they load exactly
|
||||||
|
once.
|
||||||
|
- **Utilities**: RxJS streams (`SwitchObservable`, `DefaultMap`) drive webhook
|
||||||
|
event fan-out. Prefer existing helpers in `src/utils` and `src/core/utils`
|
||||||
|
before adding bespoke logic.
|
||||||
|
|
||||||
|
## Repository Landmarks
|
||||||
|
|
||||||
|
- `src/main.ts`: runtime entry point; dynamically loads the correct AppModule
|
||||||
|
(Core vs Plus) and configures global interceptors/filters.
|
||||||
|
- `src/api/**`: REST controllers and WebSocket gateway. Keep handlers thin;
|
||||||
|
delegate to managers/services. HTTP routes follow `/api/{sessionName}/…`;
|
||||||
|
always thread the session name through request DTOs and guards instead of
|
||||||
|
hardcoding `default`.
|
||||||
|
- `src/core/**`: shared abstractions (config services, engine bootstrap,
|
||||||
|
storage, session management). Core only allows the `default` session and
|
||||||
|
cleans up storage on boot.
|
||||||
|
- `src/plus/**`: multi-session orchestration, advanced media services, and
|
||||||
|
external persistence layers (Mongo, Postgres). Reuse this layer when adding
|
||||||
|
Plus-only capabilities.
|
||||||
|
- `src/apps/**`: integrations (e.g., ChatWoot) and application-specific
|
||||||
|
services.
|
||||||
|
- `src/structures/**` and `src/utils/**`: DTOs, enums (event names follow
|
||||||
|
`domain.action`), helper utilities. Maintain naming consistency when
|
||||||
|
introducing new events.
|
||||||
|
- `tests/**`: Jest-based suites; do not add new tests unless explicitly asked,
|
||||||
|
but keep existing tests working.
|
||||||
|
|
||||||
|
## Coding Expectations
|
||||||
|
|
||||||
|
- Favor composable, long-lived solutions. If a helper already exists (e.g.,
|
||||||
|
`parseBool`, `DefaultMap`, media factories), extend it instead of reinventing
|
||||||
|
logic. Lodash ships with the project—prefer its utilities over hand-rolled
|
||||||
|
helpers. Import lodash as a namespace (`import * as lodash from 'lodash';`) so
|
||||||
|
helpers like `lodash.camelCase` stay consistent across files. When a new
|
||||||
|
third-party library seems necessary, confirm with the user, especially if the
|
||||||
|
package looks stale.
|
||||||
|
- Stick to NestJS patterns: inject dependencies through constructors, expose
|
||||||
|
provider tokens from modules, and keep controllers free from business logic.
|
||||||
|
- Logging goes through injected `PinoLogger` or helpers in
|
||||||
|
`src/utils/logging.ts`. `console.log` is blocked by pre-commit.
|
||||||
|
- Respect path aliases (`@waha/...`) defined in `tsconfig.json`; keep imports
|
||||||
|
consistent (use absolute aliases, not relative `../../../`).
|
||||||
|
- Prefer named function declarations over `const` arrow functions when possible.
|
||||||
|
- Avoid naming unused variables with a leading underscore; if a parameter is
|
||||||
|
required by a signature, explicitly `void` it instead.
|
||||||
|
- Do not write verbose ternaries like
|
||||||
|
`condition !== undefined ? condition : default`; use idiomatic helpers such as
|
||||||
|
`??` (nullish coalescing) or existing boolean parsers so flags stay readable.
|
||||||
|
- Do not place `await` or other async calls inside ternary expressions (`?:`);
|
||||||
|
use explicit `if/else` blocks instead.
|
||||||
|
- For configs, prefer runtime configurability over constants. Environment keys
|
||||||
|
follow `WAHA_*` for global values and `WAHA_SESSION_CONFIG_*` /
|
||||||
|
`session.config.*` for per-session overrides. If both env and config are
|
||||||
|
supported, honor both (`WAHA_WEBJS_CONFIG_*` vs. `session.webjs.config.*`).
|
||||||
|
|
||||||
|
## Language & Localization
|
||||||
|
|
||||||
|
- Keep identifiers (classes, methods, variables) and code comments in English so
|
||||||
|
the codebase stays consistent for the global team.
|
||||||
|
- Route user-visible strings through the existing i18n structure rather than
|
||||||
|
hardcoding text. ChatWoot copy belongs in `src/apps/chatwoot/i18n` with
|
||||||
|
English as the source locale before adding translations.
|
||||||
|
|
||||||
|
## ChatWoot Integration Notes
|
||||||
|
|
||||||
|
- Avoid introducing synthetic events; map onto existing webhook or engine events
|
||||||
|
whenever plausible. Always use the official ChatWoot API client located in
|
||||||
|
`src/apps/chatwoot/client`.
|
||||||
|
- When adding events, align consumer names with webhook/event identifiers (e.g.,
|
||||||
|
`message.any`).
|
||||||
|
|
||||||
|
## Workflow Checklist
|
||||||
|
|
||||||
|
1. Identify whether work targets Core, Plus, or shared layers. If Plus-only,
|
||||||
|
isolate changes to `src/plus` and ensure the commit prefix matches.
|
||||||
|
2. Lean on existing services/managers; extend the appropriate session manager
|
||||||
|
rather than branching logic inline.
|
||||||
|
3. After edits run:
|
||||||
|
- Do **not** run `pre-commit run --all-files` unless the user explicitly
|
||||||
|
asks.
|
||||||
|
- `yarn build`
|
||||||
|
- `yarn test --watchman=false`
|
||||||
|
4. Do **not** start the application yourself; ask the user to run it if runtime
|
||||||
|
validation is required.
|
||||||
|
5. Capture any assumptions or open questions for the user, especially when
|
||||||
|
touching configs, introducing dependencies, or modifying public APIs.
|
||||||
|
|
||||||
|
## Additional Tips
|
||||||
|
|
||||||
|
- Concurrency-sensitive sections (session start/stop) rely on `async-lock`. When
|
||||||
|
adding async flows, reuse `SessionManager.withLock` or existing retry
|
||||||
|
utilities (`promiseTimeout`, `waitUntil`).
|
||||||
|
- Media features centralize through `MediaManager` and `MediaStorageFactory`.
|
||||||
|
When altering media behavior, update both Core and Plus variants where
|
||||||
|
applicable.
|
||||||
|
- Keep docs and code ASCII unless a file already uses other characters. When
|
||||||
|
updating documentation, mirror the concise, actionable tone used here.
|
||||||
|
|
||||||
|
## Yes No
|
||||||
|
|
||||||
|
Always define key for objects (and in return too)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// NO
|
||||||
|
const variable = 123;
|
||||||
|
const b = { variable };
|
||||||
|
|
||||||
|
// YES
|
||||||
|
const variable = 123;
|
||||||
|
const b = { variable: variable };
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the three-line comment style for section headers inside files:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// NO
|
||||||
|
// ─── Section name ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// YES
|
||||||
|
//
|
||||||
|
// Section name
|
||||||
|
//
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related Sources Code
|
||||||
|
|
||||||
|
You can find and read related source code in the following paths:
|
||||||
|
|
||||||
|
- WEBJS: `../whatsapp-web.js`
|
||||||
|
- NOWEB: `../WhiskeySockets-Baileys`
|
||||||
|
- whatsapp-rust-bridge - `../whatsapp-rust-bridge`
|
||||||
|
- GOWS: `../gows`
|
||||||
|
- whatsmeow - `../whatsmeow`
|
||||||
|
- WPP: `../wa-js`, `../wppconnect`, `../wppconnect-server`
|
||||||
|
- ChatWoot: `../chatwoot`
|
||||||
|
|
||||||
|
Following this playbook keeps contributions aligned with WAHA’s structure,
|
||||||
|
automation hooks, and release process.
|
||||||
|
|
||||||
|
## How to run API
|
||||||
|
|
||||||
|
You can run the project outside of sandbox using the below command, then run
|
||||||
|
queries against `default` session (if not asked to do something different) using
|
||||||
|
`curl` and `X-Api-Key: 666` header.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export DEBUG=1
|
||||||
|
export WAHA_API_KEY=666
|
||||||
|
export WAHA_DASHBOARD_PASSWORD=666
|
||||||
|
export WAHA_DASHBOARD_USERNAME=admin
|
||||||
|
export WWHATSAPP_SWAGGER_USERNAME=admin
|
||||||
|
export WHATSAPP_SWAGGER_PASSWORD=666
|
||||||
|
export WHATSAPP_DEFAULT_ENGINE={WEBJS|WPP|NOWEB|GOWS}
|
||||||
|
export WAHA_DEBUG_MODE=True
|
||||||
|
export WAHA_HTTP_STRICT_MODE=1
|
||||||
|
export WAHA_MEDIA_STORAGE=LOCAL
|
||||||
|
export WHATSAPP_FILES_FOLDER=./.media
|
||||||
|
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
- Ask user before running the server.
|
||||||
|
- Before executing some queries make sure the session is in `WORKING` status.
|
||||||
|
- If it's `FAILED` or `SCAN_QR_CODE` ask user to scan QR code or fix failed
|
||||||
|
session.
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
### @Activity Decorator and Presence Tracking
|
||||||
|
|
||||||
|
Add `@Activity()` (from `src/core/abc/activity.ts`) to every engine method that
|
||||||
|
makes a network call to WhatsApp servers. It triggers `maintainPresenceOnline()`
|
||||||
|
before the method runs, keeping the session ONLINE during API activity and
|
||||||
|
scheduling an OFFLINE transition after an idle period. Skip it on methods that
|
||||||
|
only throw `NotImplementedByEngineError` / `AvailableInPlusVersion`.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
You can find changelog on
|
||||||
|
[the changelog page](https://waha.devlike.pro/docs/help/changelog/)
|
||||||
@@ -1,24 +1,241 @@
|
|||||||
FROM node:15
|
ARG NODE_IMAGE_TAG=24.11-bookworm-slim
|
||||||
# Chrome
|
ARG GOLANG_IMAGE_TAG=1.24-bookworm
|
||||||
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 - \
|
# Build
|
||||||
&& 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 \
|
FROM node:${NODE_IMAGE_TAG} AS build
|
||||||
&& apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
|
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||||
--no-install-recommends \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
# git + build toolchain for git deps
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends git python3 build-essential curl ca-certificates unzip && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# npm packages
|
# npm packages
|
||||||
WORKDIR /app
|
WORKDIR /git
|
||||||
COPY package.json .
|
COPY package.json .
|
||||||
COPY package-lock.json .
|
COPY yarn.lock .
|
||||||
RUN npm install
|
ENV YARN_CHECKSUM_BEHAVIOR=update
|
||||||
|
|
||||||
|
RUN npm install -g corepack && corepack enable
|
||||||
|
RUN yarn set version 4.9.2
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
# App
|
# App
|
||||||
WORKDIR /app
|
WORKDIR /git
|
||||||
ADD . /app
|
ADD . /git
|
||||||
RUN npm run build
|
RUN yarn install
|
||||||
EXPOSE 3000
|
RUN yarn build && find ./dist -name "*.d.ts" -delete
|
||||||
|
|
||||||
CMD npm run start:prod
|
#
|
||||||
|
# Dashboard
|
||||||
|
#
|
||||||
|
FROM node:${NODE_IMAGE_TAG} AS dashboard
|
||||||
|
|
||||||
|
# jq to parse json
|
||||||
|
RUN apt-get update && apt-get install -y jq && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# wget, unzip
|
||||||
|
RUN apt-get update && apt-get install -y wget unzip && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY waha.config.json /tmp/waha.config.json
|
||||||
|
RUN \
|
||||||
|
WAHA_DASHBOARD_GITHUB_REPO=$(jq -r '.waha.dashboard.repo' /tmp/waha.config.json) && \
|
||||||
|
WAHA_DASHBOARD_SHA=$(jq -r '.waha.dashboard.ref' /tmp/waha.config.json) && \
|
||||||
|
wget https://github.com/${WAHA_DASHBOARD_GITHUB_REPO}/archive/${WAHA_DASHBOARD_SHA}.zip \
|
||||||
|
&& unzip ${WAHA_DASHBOARD_SHA}.zip -d /tmp/dashboard \
|
||||||
|
&& mkdir -p /dashboard \
|
||||||
|
&& mv /tmp/dashboard/dashboard-${WAHA_DASHBOARD_SHA}/* /dashboard/ \
|
||||||
|
&& rm -rf ${WAHA_DASHBOARD_SHA}.zip \
|
||||||
|
&& rm -rf /tmp/dashboard/dashboard-${WAHA_DASHBOARD_SHA}
|
||||||
|
|
||||||
|
#
|
||||||
|
# GOWS
|
||||||
|
#
|
||||||
|
FROM golang:${GOLANG_IMAGE_TAG} AS gows
|
||||||
|
|
||||||
|
# jq to parse json
|
||||||
|
RUN apt-get update && apt-get install -y jq && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# install protoc
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install protobuf-compiler -y
|
||||||
|
|
||||||
|
# Image processing for thumbnails
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y libvips-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY waha.config.json /tmp/waha.config.json
|
||||||
|
WORKDIR /go/gows
|
||||||
|
RUN \
|
||||||
|
GOWS_GITHUB_REPO=$(jq -r '.waha.gows.repo' /tmp/waha.config.json) && \
|
||||||
|
GOWS_SHA=$(jq -r '.waha.gows.ref' /tmp/waha.config.json) && \
|
||||||
|
ARCH=$(uname -m) && \
|
||||||
|
if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; \
|
||||||
|
elif [ "$ARCH" = "aarch64" ]; then ARCH="arm64"; \
|
||||||
|
else echo "Unsupported architecture: $ARCH" && exit 1; fi && \
|
||||||
|
mkdir -p /go/gows/bin && \
|
||||||
|
wget -O /go/gows/bin/gows https://github.com/${GOWS_GITHUB_REPO}/releases/download/${GOWS_SHA}/gows-${ARCH} && \
|
||||||
|
chmod +x /go/gows/bin/gows
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Final
|
||||||
|
#
|
||||||
|
FROM node:${NODE_IMAGE_TAG} 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
|
||||||
|
ARG WHATSAPP_DEFAULT_ENGINE
|
||||||
|
|
||||||
|
RUN echo "USE_BROWSER=$USE_BROWSER"
|
||||||
|
|
||||||
|
# Install ffmpeg to generate previews for videos
|
||||||
|
RUN apt-get update && apt-get install -y ffmpeg --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Image processing for thumbnails
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y libvips \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install zip and unzip - either for chromium or chrome
|
||||||
|
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
|
||||||
|
apt-get update \
|
||||||
|
&& apt-get install -y zip unzip \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install wget - either for chromium or chrome
|
||||||
|
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
|
||||||
|
apt-get update \
|
||||||
|
&& apt-get install -y wget \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install fonts if using either chromium or chrome
|
||||||
|
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
|
||||||
|
apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
fontconfig \
|
||||||
|
fonts-freefont-ttf \
|
||||||
|
fonts-gfs-neohellenic \
|
||||||
|
fonts-indic \
|
||||||
|
fonts-ipafont-gothic \
|
||||||
|
fonts-kacst \
|
||||||
|
fonts-liberation \
|
||||||
|
fonts-noto-cjk \
|
||||||
|
fonts-noto-color-emoji \
|
||||||
|
fonts-roboto \
|
||||||
|
fonts-thai-tlwg \
|
||||||
|
fonts-wqy-zenhei \
|
||||||
|
fonts-open-sans \
|
||||||
|
--no-install-recommends \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install xvfb, xauth
|
||||||
|
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
|
||||||
|
apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
xvfb \
|
||||||
|
xauth \
|
||||||
|
libnss3 \
|
||||||
|
libxss1 \
|
||||||
|
libasound2 \
|
||||||
|
libatk-bridge2.0-0 \
|
||||||
|
libgtk-3-0 \
|
||||||
|
libdrm2 \
|
||||||
|
ca-certificates \
|
||||||
|
&& 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="140.0.7339.80-1"
|
||||||
|
ARG OPUSTAGS_VERSION="1.10.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
|
||||||
|
|
||||||
|
# curl
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Build and install opustags so audio metadata can be cleaned up inside the container
|
||||||
|
RUN set -eux; \
|
||||||
|
buildDeps='build-essential cmake pkg-config libogg-dev'; \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get install -y --no-install-recommends ${buildDeps}; \
|
||||||
|
mkdir -p /tmp/opustags; \
|
||||||
|
curl -L https://github.com/fmang/opustags/archive/refs/tags/${OPUSTAGS_VERSION}.tar.gz \
|
||||||
|
| tar -xz -C /tmp/opustags; \
|
||||||
|
cd /tmp/opustags/opustags-${OPUSTAGS_VERSION}; \
|
||||||
|
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release; \
|
||||||
|
cmake --build build --config Release; \
|
||||||
|
cmake --install build; \
|
||||||
|
rm -rf /tmp/opustags; \
|
||||||
|
apt-get purge -y --auto-remove ${buildDeps}; \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# GOWS requirements
|
||||||
|
# libc6
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y libc6 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install tini for proper init process
|
||||||
|
RUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set the ENV for docker image
|
||||||
|
ENV WHATSAPP_DEFAULT_ENGINE=$WHATSAPP_DEFAULT_ENGINE
|
||||||
|
|
||||||
|
# Attach sources, install packages
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json ./
|
||||||
|
COPY --from=build /git/node_modules ./node_modules
|
||||||
|
COPY --from=build /git/dist ./dist
|
||||||
|
COPY --from=dashboard /dashboard ./dist/dashboard
|
||||||
|
COPY --from=gows /go/gows/bin/gows /app/gows
|
||||||
|
COPY .env.example ./.env.example
|
||||||
|
COPY scripts/init-waha.js ./scripts/init-waha.js
|
||||||
|
RUN chmod +x ./scripts/init-waha.js \
|
||||||
|
&& printf '%s\n' '#!/bin/sh' 'exec node /app/scripts/init-waha.js "$@"' > /usr/local/bin/init-waha \
|
||||||
|
&& chmod +x /usr/local/bin/init-waha
|
||||||
|
ENV WAHA_GOWS_PATH=/app/gows
|
||||||
|
ENV WAHA_GOWS_SOCKET=/tmp/gows.sock
|
||||||
|
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
# Chokidar options to monitor file changes
|
||||||
|
ENV CHOKIDAR_USEPOLLING=1
|
||||||
|
ENV CHOKIDAR_INTERVAL=5000
|
||||||
|
|
||||||
|
# WAHA variables
|
||||||
|
ENV WAHA_ZIPPER=ZIPUNZIP
|
||||||
|
|
||||||
|
# GOWS - use libc DNS resolver
|
||||||
|
ENV GODEBUG netdns=cgo
|
||||||
|
|
||||||
|
# Run command, etc
|
||||||
|
EXPOSE 3000
|
||||||
|
# Use tini as init system to handle zombie processes properly
|
||||||
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
|
CMD ["/entrypoint.sh"]
|
||||||
@@ -1,8 +1,65 @@
|
|||||||
build:
|
build-all: build-plus build-chrome build-gows build-noweb
|
||||||
docker build . -t allburov/whatsapp-http-api
|
|
||||||
|
|
||||||
run:
|
build:
|
||||||
docker run --rm -v `pwd`/tokens:/app/tokens -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api allburov/whatsapp-http-api
|
docker build . -t devlikeapro/waha
|
||||||
|
|
||||||
|
build-plus:
|
||||||
|
docker build . -t devlikeapro/waha-plus
|
||||||
|
|
||||||
|
build-chrome:
|
||||||
|
docker build . -t devlikeapro/waha-plus:chrome --build-arg USE_BROWSER=chrome
|
||||||
|
|
||||||
|
build-noweb:
|
||||||
|
docker build . -t devlikeapro/waha-plus:noweb --build-arg USE_BROWSER=none --build-arg WHATSAPP_DEFAULT_ENGINE=NOWEB
|
||||||
|
|
||||||
|
build-gows:
|
||||||
|
docker build . -t devlikeapro/waha-plus:gows --build-arg USE_BROWSER=none --build-arg WHATSAPP_DEFAULT_ENGINE=GOWS
|
||||||
|
|
||||||
|
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:
|
push:
|
||||||
docker push allburov/whatsapp-http-api
|
docker push devlikeapro/waha
|
||||||
|
|
||||||
|
for-swagger:
|
||||||
|
export WHATSAPP_SWAGGER_CONFIG_ADVANCED=true && export WHATSAPP_SWAGGER_PASSWORD=666 && yarn start
|
||||||
|
|
||||||
|
up-noweb:
|
||||||
|
yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2026-02-11
|
||||||
|
|
||||||
|
up-noweb-libsignal:
|
||||||
|
yarn up libsignal@github:devlikeapro/libsignal-node#fork-master
|
||||||
|
|
||||||
|
up-webjs:
|
||||||
|
yarn up whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2026-02-18
|
||||||
|
|
||||||
|
up-wpp:
|
||||||
|
yarn up @wppconnect-team/wppconnect
|
||||||
|
|
||||||
|
up-rust-bridge:
|
||||||
|
yarn up -R whatsapp-rust-bridge
|
||||||
|
|
||||||
|
start-proxy:
|
||||||
|
docker run --rm -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta
|
||||||
|
|
||||||
|
proto-gows:
|
||||||
|
node scripts/gows-proto.js build --dir ../gows/proto
|
||||||
|
|
||||||
|
gows:
|
||||||
|
cd ../gows && \
|
||||||
|
export PATH=${HOME}/go/bin:${PATH} && \
|
||||||
|
make all
|
||||||
|
|
||||||
|
copy-dashboard:
|
||||||
|
cd ../waha-hub/ui && \
|
||||||
|
make copy-waha
|
||||||
@@ -1,177 +1,170 @@
|
|||||||
# 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
|
**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!
|
||||||
go [through currently supported methods in Swagger](https://allburov.github.io/whatsapp-http-api/)
|
|
||||||
|
|
||||||
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/)
|
||||||
|
- Dashboard Example: [https://waha.devlike.pro/dashboard](https://waha.devlike.pro/dashboard)
|
||||||
|
- Swagger Example: [https://waha.devlike.pro/swagger](https://waha.devlike.pro/swagger)
|
||||||
|
|
||||||
# Tables of Contents
|
# Tables of Contents
|
||||||
|
|
||||||
<!-- toc -->
|
<!-- toc -->
|
||||||
|
|
||||||
- [Installation](#installation)
|
- [Quick start](#quick-start)
|
||||||
- [First Steps](#first-steps)
|
* [Requirements](#requirements)
|
||||||
* [Run WhatsAPP HTTP API](#run-whatsapp-http-api)
|
* [Send your first message](#send-your-first-message)
|
||||||
* [Create a new session and login](#create-a-new-session-and-login)
|
+ [1. Download image](#1-download-image)
|
||||||
* [Send a text message](#send-a-text-message)
|
+ [2. Run WhatsApp HTTP API](#2-run-whatsapp-http-api)
|
||||||
- [Receive messages](#receive-messages)
|
+ [3. Start a new session](#3-start-a-new-session)
|
||||||
+ [Python echo server](#python-echo-server)
|
+ [4. Get and scan QR](#4-get-and-scan-qr)
|
||||||
- [Environment variables](#environment-variables)
|
+ [5. Get the screenshot](#5-get-the-screenshot)
|
||||||
* [Common](#common)
|
+ [6. Send a text message](#6-send-a-text-message)
|
||||||
* [Webhooks](#webhooks)
|
* [What is next?](#what-is-next)
|
||||||
* [File storage](#file-storage)
|
- [Development](#development)
|
||||||
- [How to](#how-to)
|
* [Start the project](#start-the-project)
|
||||||
* [How to build the newest Docker image locally](#how-to-build-the-newest-docker-image-locally)
|
|
||||||
- [Support](#support)
|
|
||||||
|
|
||||||
<!-- tocstop -->
|
<!-- tocstop -->
|
||||||
|
|
||||||
# Installation
|
# Quick start
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
Only thing that you must have - installed docker. Please follow the original
|
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!
|
||||||
|
|
||||||
|
## 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
|
```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 [**➕ WAHA Plus**](https://waha.devlike.pro/docs/how-to/waha-plus/)
|
||||||
|
|
||||||
|
### 2. Run WhatsApp HTTP API
|
||||||
|
|
||||||
Run WhatsApp HTTP API:
|
Run WhatsApp HTTP API:
|
||||||
|
|
||||||
```bash
|
```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
|
# 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
|
To start a new session you should have your mobile phone with installed WhatsApp application close to you.
|
||||||
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!
|
|
||||||

|
|
||||||
|
|
||||||
## 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
|
When your ready - find `POST /api/sessions`, click on **Try it out**, then **Execute** a bit below.
|
||||||
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!
|
|
||||||
|
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
|
```bash
|
||||||
# Phone without +
|
# Phone without +
|
||||||
# Using GET
|
export PHONE=12132132130
|
||||||
curl "http://localhost:3000/api/sendText?phone=79776772457&text=Hello+from+WhatsApp+HTTP+API+Free!"
|
curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP API\" }" -H "Content-Type: application/json" -X POST http://localhost:3000/api/sendText
|
||||||
|
|
||||||
# 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Receive messages
|
## What is next?
|
||||||
|
[Go and read the full documentation!](https://waha.devlike.pro/docs/overview/introduction/)
|
||||||
|
|
||||||
To show how to receive messages we'll create a simple "echo" server with two functions:
|
# Development
|
||||||
|
|
||||||
1. When we receive a text message - just send the text back
|
## Start the project
|
||||||
2. When we receive a message with a file (an image, a voice message) - download it and send the path back
|
1. Clone the repository
|
||||||
|
2. Make sure you're using node>=22 (check [.nvmrc](/.nvmrc) to get the version)
|
||||||
|
3. Install the **whatsapp-rust-bridge prerequisites**:
|
||||||
|
```bash
|
||||||
|
# Bun runtime used by whatsapp-rust-bridge prepare scripts
|
||||||
|
curl -fsSL https://bun.sh/install | bash -s -- bun-v1.3.9
|
||||||
|
|
||||||
In order to send you messages we use **Webhooks** (look at them below) and configure them via environments variables. So
|
# Rust nightly toolchain used for whatsapp-rust-bridge
|
||||||
what you need to
|
curl -fsSL https://sh.rustup.rs | bash -s -- -y --default-toolchain nightly-2026-01-30
|
||||||
create "echo" server is HTTP server that will receive JSON POST request and then call back WhatsApp HTTP API
|
rustup target add wasm32-unknown-unknown
|
||||||
via `POST /api/sendText` endpoint with JSON body.
|
cargo install wasm-pack --vers 0.14.0 --locked
|
||||||
|
```
|
||||||
### 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:
|
|
||||||
|
|
||||||
|
4. Run the following commands:
|
||||||
```bash
|
```bash
|
||||||
# if you haven't already
|
# Install dependencies
|
||||||
git clone https://github.com/allburov/whatsapp-http-api.git
|
yarn install
|
||||||
cd whatsapp-http-api
|
# Fetch and compile proto files
|
||||||
python -mpip install -r examples/requirements.txt
|
yarn gows:proto
|
||||||
export FLASK_APP=examples/echo.py
|
# Run
|
||||||
flask run
|
yarn start
|
||||||
```
|
# open http://localhost:3000
|
||||||
|
```
|
||||||
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_ONMESSAGE=http://localhost/uri`
|
|
||||||
- `WHATSAPP_HOOK_ONSTATECHANGE=http://localhost/uri`
|
|
||||||
- `WHATSAPP_HOOK_ONACK=http://localhost/uri`
|
|
||||||
- `WHATSAPP_HOOK_ONADDEDTOGROUP=http://localhost/uri`
|
|
||||||
|
|
||||||
## 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)
|
|
||||||
|
|
||||||
@@ -1,15 +1,104 @@
|
|||||||
version: '3.4'
|
# https://waha.devlike.pro/docs/how-to/install/
|
||||||
|
|
||||||
services:
|
services:
|
||||||
whatsapp-http-api:
|
waha:
|
||||||
build: .
|
|
||||||
image: allburov/whatsapp-http-api
|
|
||||||
restart: always
|
restart: always
|
||||||
|
# https://waha.devlike.pro/docs/how-to/engines/#docker-images
|
||||||
|
# https://portal.devlike.pro/docker-image
|
||||||
|
image: devlikeapro/waha-plus
|
||||||
|
# WAHA Core
|
||||||
|
# image: devlikeapro/waha:latest
|
||||||
|
|
||||||
|
# Add "dns" if you have a problem with resolving "web.whatsapp.com"
|
||||||
|
dns:
|
||||||
|
- 1.1.1.1
|
||||||
|
- 8.8.8.8
|
||||||
|
|
||||||
|
logging:
|
||||||
|
driver: 'json-file'
|
||||||
|
options:
|
||||||
|
max-size: '100m'
|
||||||
|
max-file: '10'
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- '127.0.0.1:3000:3000/tcp'
|
||||||
shm_size: "1gb"
|
|
||||||
environment:
|
|
||||||
- WHATSAPP_DOWNLOADS_FOLDER=/app/files/downloads
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./tokens:/app/tokens
|
# Store sessions in the .sessions folder (comment it if you're using MongoDB)
|
||||||
- ./files:/app/files
|
- './sessions:/app/.sessions'
|
||||||
|
|
||||||
|
# Save media files
|
||||||
|
# https://waha.devlike.pro/docs/how-to/storages/#save-media-files-between-the-container-restarts
|
||||||
|
- './media:/app/.media'
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
# NOTE: Only if you're using PostgreSQL to save sessions
|
||||||
|
# https://waha.devlike.pro/docs/how-to/storages/#media---postgresql
|
||||||
|
# postgres:
|
||||||
|
# image: postgres:17.6
|
||||||
|
# restart: always
|
||||||
|
# environment:
|
||||||
|
# POSTGRES_USER: postgres
|
||||||
|
# POSTGRES_PASSWORD: postgres
|
||||||
|
# POSTGRES_DB: postgres
|
||||||
|
# ports:
|
||||||
|
# - "127.0.0.1:5432:5432"
|
||||||
|
# volumes:
|
||||||
|
# - pg_data:/var/lib/postgresql/data
|
||||||
|
# command:
|
||||||
|
# - postgres
|
||||||
|
# - "-c"
|
||||||
|
# - "max_connections=3000"
|
||||||
|
# logging:
|
||||||
|
# driver: "json-file"
|
||||||
|
# options:
|
||||||
|
# max-size: "100m"
|
||||||
|
# max-file: "10"
|
||||||
|
|
||||||
|
# NOTE: Only if you're using MongoDB
|
||||||
|
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
|
||||||
|
# Uncomment this block if you're using MongoDB
|
||||||
|
# mongodb:
|
||||||
|
# image: mongo
|
||||||
|
# container_name: mongodb
|
||||||
|
# ports:
|
||||||
|
# - '127.0.0.1:27017:27017/tcp'
|
||||||
|
# volumes:
|
||||||
|
# - mongodb_data:/data/db
|
||||||
|
# environment:
|
||||||
|
# - MONGO_INITDB_ROOT_USERNAME=mongouser
|
||||||
|
# - MONGO_INITDB_ROOT_PASSWORD=mongopassword
|
||||||
|
# logging:
|
||||||
|
# driver: "json-file"
|
||||||
|
# options:
|
||||||
|
# max-size: "100m"
|
||||||
|
# max-file: "10"
|
||||||
|
|
||||||
|
# NOTE: Only if you're using S3 to save media files
|
||||||
|
# https://waha.devlike.pro/docs/how-to/storages/#media---s3
|
||||||
|
# Uncomment this block if you're using AWS S3
|
||||||
|
# minio:
|
||||||
|
# image: quay.io/minio/minio
|
||||||
|
# container_name: minio
|
||||||
|
# restart: always
|
||||||
|
# ports:
|
||||||
|
# - '127.0.0.1:9000:9000'
|
||||||
|
# - '127.0.0.1:9001:9001'
|
||||||
|
# environment:
|
||||||
|
# MINIO_REGION: 'eu-west-2'
|
||||||
|
# MINIO_ROOT_USER: 'minioadmin'
|
||||||
|
# MINIO_ROOT_PASSWORD: 'minioadmin'
|
||||||
|
# volumes:
|
||||||
|
# - minio_data:/data
|
||||||
|
# command: server /data --console-address ":9001"
|
||||||
|
# logging:
|
||||||
|
# driver: "json-file"
|
||||||
|
# options:
|
||||||
|
# max-size: "100m"
|
||||||
|
# max-file: "10"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongodb_data: {}
|
||||||
|
minio_data: {}
|
||||||
|
pg_data: {}
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
# Learn about the various environment variables at
|
||||||
|
# https://www.chatwoot.com/docs/self-hosted/configuration/environment-variables/#rails-production-variables
|
||||||
|
|
||||||
|
# Increase rack default timeout (30s) to avoid timeout errors
|
||||||
|
# Workaround for https://github.com/devlikeapro/waha/issues/1389
|
||||||
|
RACK_TIMEOUT_SERVICE_TIMEOUT=60
|
||||||
|
|
||||||
|
# Used to verify the integrity of signed cookies. so ensure a secure value is set
|
||||||
|
# SECRET_KEY_BASE should be alphanumeric. Avoid special characters or symbols.
|
||||||
|
# Use `rake secret` to generate this variable
|
||||||
|
SECRET_KEY_BASE=replace_with_lengthy_secure_hex
|
||||||
|
|
||||||
|
# Replace with the URL you are planning to use for your app
|
||||||
|
FRONTEND_URL=http://chatwoot:3009
|
||||||
|
# To use a dedicated URL for help center pages
|
||||||
|
# HELPCENTER_URL=http://0.0.0.0:3000
|
||||||
|
|
||||||
|
# If the variable is set, all non-authenticated pages would fallback to the default locale.
|
||||||
|
# Whenever a new account is created, the default language will be DEFAULT_LOCALE instead of en
|
||||||
|
# DEFAULT_LOCALE=en
|
||||||
|
|
||||||
|
# If you plan to use CDN for your assets, set Asset CDN Host
|
||||||
|
ASSET_CDN_HOST=
|
||||||
|
|
||||||
|
# Force all access to the app over SSL, default is set to false
|
||||||
|
FORCE_SSL=false
|
||||||
|
|
||||||
|
# This lets you control new sign ups on your chatwoot installation
|
||||||
|
# true : default option, allows sign ups
|
||||||
|
# false : disables all the end points related to sign ups
|
||||||
|
# api_only: disables the UI for signup, but you can create sign ups via the account apis
|
||||||
|
ENABLE_ACCOUNT_SIGNUP=false
|
||||||
|
|
||||||
|
# Redis config
|
||||||
|
# specify the configs via single URL or individual variables
|
||||||
|
# ref: https://www.iana.org/assignments/uri-schemes/prov/redis
|
||||||
|
# You can also use the following format for the URL: redis://:password@host:port/db_number
|
||||||
|
REDIS_URL=redis://redis:6379
|
||||||
|
# If you are using docker-compose, set this variable's value to be any string,
|
||||||
|
# which will be the password for the redis service running inside the docker-compose
|
||||||
|
# to make it secure
|
||||||
|
REDIS_PASSWORD=redis
|
||||||
|
# Redis Sentinel can be used by passing list of sentinel host and ports e,g. sentinel_host1:port1,sentinel_host2:port2
|
||||||
|
REDIS_SENTINELS=
|
||||||
|
# Redis sentinel master name is required when using sentinel, default value is "mymaster".
|
||||||
|
# You can find list of master using "SENTINEL masters" command
|
||||||
|
REDIS_SENTINEL_MASTER_NAME=
|
||||||
|
|
||||||
|
# By default Chatwoot will pass REDIS_PASSWORD as the password value for sentinels
|
||||||
|
# Use the following environment variable to customize passwords for sentinels.
|
||||||
|
# Use empty string if sentinels are configured with out passwords
|
||||||
|
# REDIS_SENTINEL_PASSWORD=
|
||||||
|
|
||||||
|
# Redis premium breakage in heroku fix
|
||||||
|
# enable the following configuration
|
||||||
|
# ref: https://github.com/chatwoot/chatwoot/issues/2420
|
||||||
|
# REDIS_OPENSSL_VERIFY_MODE=none
|
||||||
|
|
||||||
|
# Postgres Database config variables
|
||||||
|
# You can leave POSTGRES_DATABASE blank. The default name of
|
||||||
|
# the database in the production environment is chatwoot_production
|
||||||
|
POSTGRES_DATABASE=chatwoot
|
||||||
|
POSTGRES_HOST=postgres
|
||||||
|
POSTGRES_USERNAME=postgres
|
||||||
|
POSTGRES_PASSWORD=postgres
|
||||||
|
RAILS_ENV=development
|
||||||
|
# Changes the Postgres query timeout limit. The default is 14 seconds. Modify only when required.
|
||||||
|
# POSTGRES_STATEMENT_TIMEOUT=14s
|
||||||
|
RAILS_MAX_THREADS=5
|
||||||
|
|
||||||
|
# The email from which all outgoing emails are sent
|
||||||
|
# could user either `email@yourdomain.com` or `BrandName <email@yourdomain.com>`
|
||||||
|
MAILER_SENDER_EMAIL=Chatwoot <accounts@chatwoot.com>
|
||||||
|
|
||||||
|
#SMTP domain key is set up for HELO checking
|
||||||
|
SMTP_DOMAIN=chatwoot.com
|
||||||
|
# Set the value to "mailhog" if using docker-compose for development environments,
|
||||||
|
# Set the value as "localhost" or your SMTP address in other environments
|
||||||
|
# If SMTP_ADDRESS is empty, Chatwoot would try to use sendmail(postfix)
|
||||||
|
SMTP_ADDRESS=
|
||||||
|
SMTP_PORT=1025
|
||||||
|
SMTP_USERNAME=
|
||||||
|
SMTP_PASSWORD=
|
||||||
|
# plain,login,cram_md5
|
||||||
|
SMTP_AUTHENTICATION=
|
||||||
|
SMTP_ENABLE_STARTTLS_AUTO=true
|
||||||
|
# Can be: 'none', 'peer', 'client_once', 'fail_if_no_peer_cert', see http://api.rubyonrails.org/classes/ActionMailer/Base.html
|
||||||
|
SMTP_OPENSSL_VERIFY_MODE=peer
|
||||||
|
# Comment out the following environment variables if required by your SMTP server
|
||||||
|
# SMTP_TLS=
|
||||||
|
# SMTP_SSL=
|
||||||
|
# SMTP_OPEN_TIMEOUT
|
||||||
|
# SMTP_READ_TIMEOUT
|
||||||
|
|
||||||
|
# Mail Incoming
|
||||||
|
# This is the domain set for the reply emails when conversation continuity is enabled
|
||||||
|
MAILER_INBOUND_EMAIL_DOMAIN=
|
||||||
|
# Set this to the appropriate ingress channel with regards to incoming emails
|
||||||
|
# Possible values are :
|
||||||
|
# relay for Exim, Postfix, Qmail
|
||||||
|
# mailgun for Mailgun
|
||||||
|
# mandrill for Mandrill
|
||||||
|
# postmark for Postmark
|
||||||
|
# sendgrid for Sendgrid
|
||||||
|
RAILS_INBOUND_EMAIL_SERVICE=
|
||||||
|
# Use one of the following based on the email ingress service
|
||||||
|
# Ref: https://edgeguides.rubyonrails.org/action_mailbox_basics.html
|
||||||
|
# Set this to a password of your choice and use it in the Inbound webhook
|
||||||
|
RAILS_INBOUND_EMAIL_PASSWORD=
|
||||||
|
|
||||||
|
MAILGUN_INGRESS_SIGNING_KEY=
|
||||||
|
MANDRILL_INGRESS_API_KEY=
|
||||||
|
|
||||||
|
# Creating Your Inbound Webhook Instructions for Postmark and Sendgrid:
|
||||||
|
# Inbound webhook URL format:
|
||||||
|
# https://actionmailbox:[YOUR_RAILS_INBOUND_EMAIL_PASSWORD]@[YOUR_CHATWOOT_DOMAIN.COM]/rails/action_mailbox/[RAILS_INBOUND_EMAIL_SERVICE]/inbound_emails
|
||||||
|
# Note: Replace the values inside the brackets; do not include the brackets themselves.
|
||||||
|
# Example: https://actionmailbox:mYRandomPassword3@chatwoot.example.com/rails/action_mailbox/postmark/inbound_emails
|
||||||
|
# For Postmark
|
||||||
|
# Ensure the 'Include raw email content in JSON payload' checkbox is selected in the inbound webhook section.
|
||||||
|
|
||||||
|
# Storage
|
||||||
|
ACTIVE_STORAGE_SERVICE=local
|
||||||
|
|
||||||
|
# Amazon S3
|
||||||
|
# documentation: https://www.chatwoot.com/docs/configuring-s3-bucket-as-cloud-storage
|
||||||
|
S3_BUCKET_NAME=
|
||||||
|
AWS_ACCESS_KEY_ID=
|
||||||
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
AWS_REGION=
|
||||||
|
|
||||||
|
# Log settings
|
||||||
|
# Disable if you want to write logs to a file
|
||||||
|
RAILS_LOG_TO_STDOUT=true
|
||||||
|
LOG_LEVEL=info
|
||||||
|
LOG_SIZE=500
|
||||||
|
# Configure this environment variable if you want to use lograge instead of rails logger
|
||||||
|
#LOGRAGE_ENABLED=true
|
||||||
|
|
||||||
|
### This environment variables are only required if you are setting up social media channels
|
||||||
|
|
||||||
|
# Facebook
|
||||||
|
# documentation: https://www.chatwoot.com/docs/facebook-setup
|
||||||
|
FB_VERIFY_TOKEN=
|
||||||
|
FB_APP_SECRET=
|
||||||
|
FB_APP_ID=
|
||||||
|
|
||||||
|
# https://developers.facebook.com/docs/messenger-platform/instagram/get-started#app-dashboard
|
||||||
|
IG_VERIFY_TOKEN=
|
||||||
|
|
||||||
|
# Twitter
|
||||||
|
# documentation: https://www.chatwoot.com/docs/twitter-app-setup
|
||||||
|
TWITTER_APP_ID=
|
||||||
|
TWITTER_CONSUMER_KEY=
|
||||||
|
TWITTER_CONSUMER_SECRET=
|
||||||
|
TWITTER_ENVIRONMENT=
|
||||||
|
|
||||||
|
#slack integration
|
||||||
|
SLACK_CLIENT_ID=
|
||||||
|
SLACK_CLIENT_SECRET=
|
||||||
|
|
||||||
|
# Google OAuth
|
||||||
|
GOOGLE_OAUTH_CLIENT_ID=
|
||||||
|
GOOGLE_OAUTH_CLIENT_SECRET=
|
||||||
|
GOOGLE_OAUTH_CALLBACK_URL=
|
||||||
|
|
||||||
|
### Change this env variable only if you are using a custom build mobile app
|
||||||
|
## Mobile app env variables
|
||||||
|
IOS_APP_ID=L7YLMN4634.com.chatwoot.app
|
||||||
|
ANDROID_BUNDLE_ID=com.chatwoot.app
|
||||||
|
|
||||||
|
# https://developers.google.com/android/guides/client-auth (use keytool to print the fingerprint in the first section)
|
||||||
|
ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:D4:5D:D4:53:F8:3B:FB:D3:C6:28:64:1D:AA:08:1E:D8
|
||||||
|
|
||||||
|
### Smart App Banner
|
||||||
|
# https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html
|
||||||
|
# You can find your app-id in https://itunesconnect.apple.com
|
||||||
|
#IOS_APP_IDENTIFIER=1495796682
|
||||||
|
|
||||||
|
## Push Notification
|
||||||
|
## generate a new key value here : https://d3v.one/vapid-key-generator/
|
||||||
|
# VAPID_PUBLIC_KEY=
|
||||||
|
# VAPID_PRIVATE_KEY=
|
||||||
|
#
|
||||||
|
# for mobile apps
|
||||||
|
# FCM_SERVER_KEY=
|
||||||
|
|
||||||
|
### APM and Error Monitoring configurations
|
||||||
|
## Elastic APM
|
||||||
|
## https://www.elastic.co/guide/en/apm/agent/ruby/current/getting-started-rails.html
|
||||||
|
# ELASTIC_APM_SERVER_URL=
|
||||||
|
# ELASTIC_APM_SECRET_TOKEN=
|
||||||
|
|
||||||
|
## Sentry
|
||||||
|
# SENTRY_DSN=
|
||||||
|
|
||||||
|
|
||||||
|
## Scout
|
||||||
|
## https://scoutapm.com/docs/ruby/configuration
|
||||||
|
# SCOUT_KEY=YOURKEY
|
||||||
|
# SCOUT_NAME=YOURAPPNAME (Production)
|
||||||
|
# SCOUT_MONITOR=true
|
||||||
|
|
||||||
|
## NewRelic
|
||||||
|
# https://docs.newrelic.com/docs/agents/ruby-agent/configuration/ruby-agent-configuration/
|
||||||
|
# NEW_RELIC_LICENSE_KEY=
|
||||||
|
# Set this to true to allow newrelic apm to send logs.
|
||||||
|
# This is turned off by default.
|
||||||
|
# NEW_RELIC_APPLICATION_LOGGING_ENABLED=
|
||||||
|
|
||||||
|
## Datadog
|
||||||
|
## https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#environment-variables
|
||||||
|
# DD_TRACE_AGENT_URL=
|
||||||
|
|
||||||
|
# MaxMindDB API key to download GeoLite2 City database
|
||||||
|
# IP_LOOKUP_API_KEY=
|
||||||
|
|
||||||
|
## Rack Attack configuration
|
||||||
|
## To prevent and throttle abusive requests
|
||||||
|
# ENABLE_RACK_ATTACK=true
|
||||||
|
# RACK_ATTACK_LIMIT=300
|
||||||
|
# ENABLE_RACK_ATTACK_WIDGET_API=true
|
||||||
|
CONVERSATION_MESSAGE_PER_MINUTE_LIMIT=3000
|
||||||
|
|
||||||
|
## Running chatwoot as an API only server
|
||||||
|
## setting this value to true will disable the frontend dashboard endpoints
|
||||||
|
# CW_API_ONLY_SERVER=false
|
||||||
|
|
||||||
|
## Development Only Config
|
||||||
|
# if you want to use letter_opener for local emails
|
||||||
|
# LETTER_OPENER=true
|
||||||
|
# meant to be used in github codespaces
|
||||||
|
# WEBPACKER_DEV_SERVER_PUBLIC=
|
||||||
|
|
||||||
|
# If you want to use official mobile app,
|
||||||
|
# the notifications would be relayed via a Chatwoot server
|
||||||
|
ENABLE_PUSH_RELAY_SERVER=true
|
||||||
|
|
||||||
|
# Stripe API key
|
||||||
|
STRIPE_SECRET_KEY=
|
||||||
|
STRIPE_WEBHOOK_SECRET=
|
||||||
|
|
||||||
|
# Set to true if you want to upload files to cloud storage using the signed url
|
||||||
|
# Make sure to follow https://edgeguides.rubyonrails.org/active_storage_overview.html#cross-origin-resource-sharing-cors-configuration on the cloud storage after setting this to true.
|
||||||
|
DIRECT_UPLOADS_ENABLED=
|
||||||
|
|
||||||
|
#MS OAUTH creds
|
||||||
|
AZURE_APP_ID=
|
||||||
|
AZURE_APP_SECRET=
|
||||||
|
|
||||||
|
## Advanced configurations
|
||||||
|
## Change these values to fine tune performance
|
||||||
|
# control the concurrency setting of sidekiq
|
||||||
|
# SIDEKIQ_CONCURRENCY=10
|
||||||
|
|
||||||
|
|
||||||
|
# AI powered features
|
||||||
|
## OpenAI key
|
||||||
|
# OPENAI_API_KEY=
|
||||||
|
|
||||||
|
# Housekeeping/Performance related configurations
|
||||||
|
# Set to true if you want to remove stale contact inboxes
|
||||||
|
# contact_inboxes with no conversation older than 90 days will be removed
|
||||||
|
# REMOVE_STALE_CONTACT_INBOX_JOB_STATUS=false
|
||||||
|
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# WAHA - WhatsApp HTTP API
|
||||||
|
#
|
||||||
|
# https://waha.devlike.pro/docs/how-to/config/
|
||||||
|
#
|
||||||
|
|
||||||
|
# ====================
|
||||||
|
# ===== SECURITY =====
|
||||||
|
# ====================
|
||||||
|
WAHA_API_KEY_PLAIN=00000000000000000000000000000000
|
||||||
|
WAHA_API_KEY=00000000000000000000000000000000
|
||||||
|
WAHA_DASHBOARD_USERNAME=admin
|
||||||
|
WAHA_DASHBOARD_PASSWORD=11111111111111111111111111111111
|
||||||
|
WHATSAPP_SWAGGER_USERNAME=admin
|
||||||
|
WHATSAPP_SWAGGER_PASSWORD=11111111111111111111111111111111
|
||||||
|
|
||||||
|
# Disable Dashboard or Swagger
|
||||||
|
WAHA_DASHBOARD_ENABLED=True
|
||||||
|
WHATSAPP_SWAGGER_ENABLED=True
|
||||||
|
|
||||||
|
# ==================
|
||||||
|
# ===== COMMON =====
|
||||||
|
# ==================
|
||||||
|
# Base URL for the API (used for webhooks, file URLs, etc.)
|
||||||
|
WAHA_BASE_URL=http://waha:3000
|
||||||
|
WAHA_PUBLIC_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Server configuration (if you need to customize hostname/port)
|
||||||
|
# WHATSAPP_API_SCHEMA=http
|
||||||
|
# WHATSAPP_API_PORT=3000
|
||||||
|
# WHATSAPP_API_HOSTNAME=localhost
|
||||||
|
|
||||||
|
# WhatsApp engine (WEBJS is default, GOWS or NOWEB for better performance)
|
||||||
|
WHATSAPP_DEFAULT_ENGINE=GOWS
|
||||||
|
|
||||||
|
# WAHA_CLIENT_DEVICE_NAME=YourApp
|
||||||
|
|
||||||
|
# =================
|
||||||
|
# ===== APPS ======
|
||||||
|
# =================
|
||||||
|
WAHA_APPS_ENABLED=True
|
||||||
|
REDIS_URL=redis://:redis@redis:6379
|
||||||
|
|
||||||
|
# ===================
|
||||||
|
# ===== LOGGING =====
|
||||||
|
# ===================
|
||||||
|
# Log format: JSON (for log management systems) or PRETTY (for development)
|
||||||
|
WAHA_LOG_FORMAT=JSON
|
||||||
|
|
||||||
|
# Log level: info, debug, error, warn
|
||||||
|
WAHA_LOG_LEVEL=info
|
||||||
|
|
||||||
|
# Don't print QR codes in logs
|
||||||
|
WAHA_PRINT_QR=False
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# ===== MEDIA STORAGE =====
|
||||||
|
# =========================
|
||||||
|
# Local storage (default)
|
||||||
|
WAHA_MEDIA_STORAGE=LOCAL
|
||||||
|
WHATSAPP_FILES_LIFETIME=1800
|
||||||
|
WHATSAPP_FILES_FOLDER=/app/.media
|
||||||
|
|
||||||
|
# Media download settings
|
||||||
|
# WHATSAPP_DOWNLOAD_MEDIA=true
|
||||||
|
# WHATSAPP_FILES_MIMETYPES=image/jpeg,image/png
|
||||||
|
|
||||||
|
# S3 storage (uncomment to use)
|
||||||
|
# WAHA_MEDIA_STORAGE=S3
|
||||||
|
# WAHA_S3_REGION=eu-west-2
|
||||||
|
# WAHA_S3_BUCKET=waha
|
||||||
|
# WAHA_S3_ACCESS_KEY_ID=minioadmin
|
||||||
|
# WAHA_S3_SECRET_ACCESS_KEY=minioadmin
|
||||||
|
# WAHA_S3_ENDPOINT=http://minio:9000
|
||||||
|
# WAHA_S3_FORCE_PATH_STYLE=True
|
||||||
|
# WAHA_S3_PROXY_FILES=True
|
||||||
|
|
||||||
|
# PostgreSQL storage (uncomment to use)
|
||||||
|
# WAHA_MEDIA_STORAGE=POSTGRESQL
|
||||||
|
# WAHA_MEDIA_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||||
|
|
||||||
|
# ===========================
|
||||||
|
# ===== SESSION STORAGE =====
|
||||||
|
# ===========================
|
||||||
|
# PostgreSQL for sessions (uncomment to use)
|
||||||
|
WHATSAPP_SESSIONS_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||||
|
|
||||||
|
# MongoDB for sessions (uncomment to use)
|
||||||
|
# WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongouser:mongopassword@mongodb:27017
|
||||||
|
|
||||||
|
|
||||||
|
# ==================================
|
||||||
|
# ===== ADVANCED CONFIGURATION =====
|
||||||
|
# ==================================
|
||||||
|
|
||||||
|
# Timezone for screenshots and logs
|
||||||
|
# TZ=Europe/Dublin
|
||||||
|
|
||||||
|
# Session management
|
||||||
|
# WHATSAPP_START_SESSION=session1,session2
|
||||||
|
# WHATSAPP_RESTART_ALL_SESSIONS=False
|
||||||
|
|
||||||
|
# Webhooks
|
||||||
|
# WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
|
||||||
|
# WHATSAPP_HOOK_EVENTS=session.status,message,message.reaction
|
||||||
|
|
||||||
|
# Proxy configuration
|
||||||
|
# WHATSAPP_PROXY_SERVER=proxy.example.com:3128
|
||||||
|
# WHATSAPP_PROXY_SERVER_USERNAME=user
|
||||||
|
# WHATSAPP_PROXY_SERVER_PASSWORD=pass
|
||||||
|
|
||||||
|
# HTTPS configuration
|
||||||
|
# !DEPRECATED!
|
||||||
|
# Setup nginx reverse proxy to handle TLS connection
|
||||||
|
# using Let's encrypt or self-issued certificate
|
||||||
|
# WAHA_HTTPS_ENABLED=true
|
||||||
|
# WAHA_HTTPS_PATH_KEY=/etc/letsencrypt/live/waha.example.pro/privkey.pem
|
||||||
|
# WAHA_HTTPS_PATH_CERT=/etc/letsencrypt/live/waha.example.pro/cert.pem
|
||||||
|
# WAHA_HTTPS_PATH_CA=/etc/letsencrypt/live/waha.example.pro/chain.pem
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
services:
|
||||||
|
waha:
|
||||||
|
restart: always
|
||||||
|
image: devlikeapro/waha-plus
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:3000:3000/tcp'
|
||||||
|
volumes:
|
||||||
|
- './sessions:/app/.sessions' # required for WAHA Core only
|
||||||
|
- './media:/app/.media'
|
||||||
|
env_file:
|
||||||
|
- .waha.env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
- chatwoot
|
||||||
|
- chatwoot-sidekiq
|
||||||
|
logging:
|
||||||
|
driver: 'json-file'
|
||||||
|
options:
|
||||||
|
max-size: '100m'
|
||||||
|
max-file: '10'
|
||||||
|
dns:
|
||||||
|
- 1.1.1.1
|
||||||
|
- 8.8.8.8
|
||||||
|
|
||||||
|
base: &base
|
||||||
|
image: chatwoot/chatwoot:v4.7.0
|
||||||
|
env_file: .chatwoot.env ## Change this file for customized env variables
|
||||||
|
volumes:
|
||||||
|
- chatwoot_storage:/app/storage
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
|
logging:
|
||||||
|
driver: 'json-file'
|
||||||
|
options:
|
||||||
|
max-size: '100m'
|
||||||
|
max-file: '10'
|
||||||
|
|
||||||
|
chatwoot:
|
||||||
|
<<: *base
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:3009:3009'
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- RAILS_ENV=production
|
||||||
|
- INSTALLATION_ENV=docker
|
||||||
|
entrypoint: docker/entrypoints/rails.sh
|
||||||
|
command: ['bundle', 'exec', 'rails', 's', '-p', '3009', '-b', '0.0.0.0']
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
chatwoot-sidekiq:
|
||||||
|
<<: *base
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- RAILS_ENV=production
|
||||||
|
- INSTALLATION_ENV=docker
|
||||||
|
command: ['bundle', 'exec', 'sidekiq', '-C', 'config/sidekiq.yml']
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: pgvector/pgvector:pg16
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:5432:5432'
|
||||||
|
volumes:
|
||||||
|
- chatwoot_pg_data:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=chatwoot
|
||||||
|
- POSTGRES_USER=postgres
|
||||||
|
# Please provide your own password.
|
||||||
|
- POSTGRES_PASSWORD=postgres
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
restart: always
|
||||||
|
command:
|
||||||
|
- 'sh'
|
||||||
|
- '-c'
|
||||||
|
- >
|
||||||
|
redis-server --bind 0.0.0.0 --port 6379 --requirepass
|
||||||
|
${REDIS_PASSWORD:-redis} --timeout 300 --tcp-keepalive 60 --save 900 1
|
||||||
|
--save 300 10 --save 60 10000 --appendonly yes --appendfsync everysec
|
||||||
|
env_file: .chatwoot.env
|
||||||
|
volumes:
|
||||||
|
- chatwoot_redis:/data
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:6379:6379'
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
chatwoot_pg_data: {}
|
||||||
|
chatwoot_redis: {}
|
||||||
|
chatwoot_storage: {}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# https://waha.devlike.pro/docs/how-to/install/
|
||||||
|
services:
|
||||||
|
# Just to connect
|
||||||
|
waha-dashboard:
|
||||||
|
restart: unless-stopped
|
||||||
|
image: devlikeapro/waha-plus:latest
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:3000:3000/tcp'
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- WAHA_WORKER_ID=waha-dashboard
|
||||||
|
|
||||||
|
waha1:
|
||||||
|
restart: unless-stopped
|
||||||
|
image: devlikeapro/waha-plus:latest
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:3001:3000/tcp'
|
||||||
|
volumes:
|
||||||
|
- './media:/app/.media'
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- WAHA_WORKER_ID=waha1
|
||||||
|
- WAHA_BASE_URL=http://localhost:3001
|
||||||
|
- WHATSAPP_DEFAULT_ENGINE=NOWEB
|
||||||
|
# MongoDB
|
||||||
|
- WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongodb:27017
|
||||||
|
# S3
|
||||||
|
- WAHA_MEDIA_STORAGE=S3
|
||||||
|
- WAHA_S3_REGION=eu-west-2
|
||||||
|
- WAHA_S3_BUCKET=waha
|
||||||
|
- WAHA_S3_ACCESS_KEY_ID=minioadmin
|
||||||
|
- WAHA_S3_SECRET_ACCESS_KEY=minioadmin
|
||||||
|
- WAHA_S3_ENDPOINT=http://minio:9000 # Not required if you're using AWS S3
|
||||||
|
- WAHA_S3_FORCE_PATH_STYLE=True # Required for Minio
|
||||||
|
- WAHA_S3_PROXY_FILES=True # Required for docker-compose setup
|
||||||
|
|
||||||
|
waha2:
|
||||||
|
restart: unless-stopped
|
||||||
|
image: devlikeapro/waha-plus:latest
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:3002:3000/tcp'
|
||||||
|
volumes:
|
||||||
|
- './media:/app/.media'
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- WAHA_WORKER_ID=waha2
|
||||||
|
- WAHA_BASE_URL=http://localhost:3002
|
||||||
|
- WHATSAPP_DEFAULT_ENGINE=NOWEB
|
||||||
|
# MongoDB
|
||||||
|
- WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongodb:27017
|
||||||
|
# S3
|
||||||
|
- WAHA_MEDIA_STORAGE=S3
|
||||||
|
- WAHA_S3_REGION=eu-west-2
|
||||||
|
- WAHA_S3_BUCKET=waha
|
||||||
|
- WAHA_S3_ACCESS_KEY_ID=minioadmin
|
||||||
|
- WAHA_S3_SECRET_ACCESS_KEY=minioadmin
|
||||||
|
- WAHA_S3_ENDPOINT=http://minio:9000 # Not required if you're using AWS S3
|
||||||
|
- WAHA_S3_FORCE_PATH_STYLE=True # Required for Minio
|
||||||
|
- WAHA_S3_PROXY_FILES=True # Required for docker-compose setup
|
||||||
|
|
||||||
|
# NOTE: Only if you're using MongoDB
|
||||||
|
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
|
||||||
|
# Comment this block if you're not using MongoDB
|
||||||
|
mongodb:
|
||||||
|
image: mongo
|
||||||
|
container_name: mongodb
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:27017:27017/tcp'
|
||||||
|
volumes:
|
||||||
|
- mongodb_data:/data/db
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
# NOTE: Only if you're using S3 to save media files
|
||||||
|
# https://waha.devlike.pro/docs/how-to/storages/#media---s3
|
||||||
|
# Comment this block if you're using S3
|
||||||
|
minio:
|
||||||
|
image: quay.io/minio/minio
|
||||||
|
container_name: minio
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:9000:9000'
|
||||||
|
- '127.0.0.1:9001:9001'
|
||||||
|
environment:
|
||||||
|
MINIO_REGION: 'eu-west-2'
|
||||||
|
MINIO_ROOT_USER: 'minioadmin'
|
||||||
|
MINIO_ROOT_PASSWORD: 'minioadmin'
|
||||||
|
volumes:
|
||||||
|
- minio_data:/data
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
# NOTE: Only if you're using MongoDB
|
||||||
|
mongodb_data: {}
|
||||||
|
minio_data: {}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
services:
|
||||||
|
waha:
|
||||||
|
restart: always
|
||||||
|
container_name: waha
|
||||||
|
# image: devlikeapro/waha:latest
|
||||||
|
image: devlikeapro/waha-plus:latest
|
||||||
|
ports:
|
||||||
|
- '3000:3000/tcp'
|
||||||
|
volumes:
|
||||||
|
- './sessions:/app/.sessions'
|
||||||
|
- './media:/app/.media'
|
||||||
|
environment:
|
||||||
|
- WAHA_API_KEY=321
|
||||||
|
- WAHA_DASHBOARD_USERNAME=admin
|
||||||
|
- WAHA_DASHBOARD_PASSWORD=admin
|
||||||
|
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||||
|
- WAHA_PRINT_QR=False
|
||||||
|
- WAHA_MEDIA_STORAGE=LOCAL
|
||||||
|
- WHATSAPP_FILES_LIFETIME=0
|
||||||
|
- WHATSAPP_FILES_FOLDER=/app/.media
|
||||||
|
|
||||||
|
n8n:
|
||||||
|
image: docker.n8n.io/n8nio/n8n
|
||||||
|
container_name: n8n
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- WEBHOOK_URL=http://n8n:5678
|
||||||
|
ports:
|
||||||
|
- '5678:5678'
|
||||||
|
volumes:
|
||||||
|
- n8n_data:/home/node/.n8n
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
n8n_data: {}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
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/
|
||||||
|
# https://docs.webhook.site/
|
||||||
|
- WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
|
||||||
|
- 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_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
|
||||||
|
# - WHATSAPP_HOOK_EVENTS=message
|
||||||
|
# - WHATSAPP_API_HOSTNAME=localhost
|
||||||
|
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||||
|
- WAHA_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=admin
|
||||||
|
- WAHA_DASHBOARD_PASSWORD=admin
|
||||||
|
# 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_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
|
||||||
|
# - WHATSAPP_HOOK_EVENTS=message
|
||||||
|
# - WAHA_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
|
||||||
|
|
||||||
|
# Let's Encrypt + Certbot
|
||||||
|
# https://waha.devlike.pro/docs/how-to/security/#https
|
||||||
|
waha-https--certbot:
|
||||||
|
image: devlikeapro/waha-plus
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
volumes:
|
||||||
|
- './.sessions:/app/.sessions'
|
||||||
|
- '/etc/letsencrypt:/etc/letsencrypt'
|
||||||
|
environment:
|
||||||
|
- DOMAIN=waha.example.pro
|
||||||
|
- WHATSAPP_API_HOSTNAME=${DOMAIN}
|
||||||
|
- WAHA_HTTPS_ENABLED=true
|
||||||
|
- WAHA_HTTPS_PATH_KEY=/etc/letsencrypt/live/${DOMAIN}/privkey.pem
|
||||||
|
- WAHA_HTTPS_PATH_CERT=/etc/letsencrypt/live/${DOMAIN}/cert.pem
|
||||||
|
- WAHA_HTTPS_PATH_CA=/etc/letsencrypt/live/${DOMAIN}/chain.pem
|
||||||
|
|
||||||
|
# Self-signed certificates example
|
||||||
|
# https://waha.devlike.pro/docs/how-to/security/#https
|
||||||
|
waha-https--self-signed:
|
||||||
|
image: devlikeapro/waha-plus
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
volumes:
|
||||||
|
- './.sessions:/app/.sessions'
|
||||||
|
- './.secrets:/app/.secrets'
|
||||||
|
environment:
|
||||||
|
- WAHA_HTTPS_ENABLED=true
|
||||||
|
- WAHA_HTTPS_PATH_KEY=.secrets/privkey.pem
|
||||||
|
- WAHA_HTTPS_PATH_CERT=.secrets/cert.pem
|
||||||
|
- WAHA_HTTPS_PATH_CA=.secrets/chain.pem
|
||||||
|
|
||||||
|
mongodb:
|
||||||
|
image: mongo
|
||||||
|
container_name: mongodb
|
||||||
|
ports:
|
||||||
|
- '27017:27017/tcp'
|
||||||
|
volumes:
|
||||||
|
- mongodb_data:/data/db
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongodb_data: {}
|
||||||
|
Before Width: | Height: | Size: 665 B |
|
Before Width: | Height: | Size: 628 B |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
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
|
|
||||||
@@ -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
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Calculate UV_THREADPOOL_SIZE based on number of CPUs
|
||||||
|
#
|
||||||
|
# Try to get CPU count using Node.js, fallback to 1 if it fails
|
||||||
|
cpus=$(node -e "const os = require('os'); console.log(os.cpus().length);" 2>/dev/null) || cpus=1
|
||||||
|
# Make sure cpus is a number, default to 1 if not
|
||||||
|
case $cpus in
|
||||||
|
''|*[!0-9]*) cpus=1 ;;
|
||||||
|
esac
|
||||||
|
uv_threadpool_size=$(($cpus * 2))
|
||||||
|
|
||||||
|
# Check if uv_threadpool_size is less than 4 (default), set it to 4 if it is
|
||||||
|
if [ "$uv_threadpool_size" -lt 4 ]; then
|
||||||
|
uv_threadpool_size=4
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set UV_THREADPOOL_SIZE as an environment variable
|
||||||
|
export UV_THREADPOOL_SIZE="${UV_THREADPOOL_SIZE:-$uv_threadpool_size}"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Handle API key hashing
|
||||||
|
#
|
||||||
|
# Save WHATSAPP_API_KEY or WAHA_API_KEY in a variable (WHATSAPP_API_KEY has priority)
|
||||||
|
if [ -n "$WHATSAPP_API_KEY" ]; then
|
||||||
|
key="$WHATSAPP_API_KEY"
|
||||||
|
elif [ -n "$WAHA_API_KEY" ]; then
|
||||||
|
key="$WAHA_API_KEY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unset both environment variables
|
||||||
|
unset WHATSAPP_API_KEY
|
||||||
|
unset WAHA_API_KEY
|
||||||
|
|
||||||
|
# Process the key if it exists
|
||||||
|
if [ -n "$key" ]; then
|
||||||
|
# Check if key is already hashed
|
||||||
|
if echo "$key" | grep -q "^sha512:"; then
|
||||||
|
# If already hashed, use it as is
|
||||||
|
export WAHA_API_KEY="$key"
|
||||||
|
else
|
||||||
|
# Hash the key using sha512sum
|
||||||
|
HASHED_KEY=$(echo -n "$key" | sha512sum | awk '{print $1}')
|
||||||
|
export WAHA_API_KEY="sha512:$HASHED_KEY"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# xvfb-run
|
||||||
|
#
|
||||||
|
USE_XVFB=false
|
||||||
|
|
||||||
|
# Check WAHA_RUN_XVFB parameter - only test for "false" case, treat all others as True
|
||||||
|
if [ "$WAHA_RUN_XVFB" = "false" ] || [ "$WAHA_RUN_XVFB" = "False" ] || [ "$WAHA_RUN_XVFB" = "0" ]; then
|
||||||
|
# Explicitly disabled by user
|
||||||
|
echo "WAHA_RUN_XVFB value: $WAHA_RUN_XVFB - xvfb is disabled"
|
||||||
|
USE_XVFB=false
|
||||||
|
else
|
||||||
|
# Check engine and run test if it's WEBJS or not specified
|
||||||
|
if [ -z "$WHATSAPP_DEFAULT_ENGINE" ] || [ "$WHATSAPP_DEFAULT_ENGINE" = "WEBJS" ]; then
|
||||||
|
# Try to run xvfb-run with a test command
|
||||||
|
if xvfb-run --auto-servernum echo "xvfb-run is working!"; then
|
||||||
|
USE_XVFB=true
|
||||||
|
else
|
||||||
|
echo "xvfb-run test failed, do not run it"
|
||||||
|
USE_XVFB=false
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Start your application using node with exec to ensure proper signal handling
|
||||||
|
#
|
||||||
|
if [ "$USE_XVFB" = "true" ]; then
|
||||||
|
echo "Executing node with xvfb-run..."
|
||||||
|
exec xvfb-run --auto-servernum node dist/main
|
||||||
|
else
|
||||||
|
echo "Executing node without xvfb-run..."
|
||||||
|
exec node dist/main
|
||||||
|
fi
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -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/sessions/` 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
|
|
||||||
|
After Width: | Height: | Size: 17 KiB |
@@ -2,8 +2,14 @@
|
|||||||
"collection": "@nestjs/schematics",
|
"collection": "@nestjs/schematics",
|
||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"plugins": [
|
"plugins": ["@nestjs/swagger"],
|
||||||
"@nestjs/swagger/plugin"
|
"assets": [
|
||||||
]
|
"dashboard/**",
|
||||||
|
"core/engines/webjs/*",
|
||||||
|
"plus/engines/webjs/*",
|
||||||
|
"apps/chatwoot/i18n/locales/*.yaml",
|
||||||
|
"apps/chatwoot/i18n/locales/*.yml"
|
||||||
|
],
|
||||||
|
"watchAssets": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "whatsapp-http-api",
|
"name": "waha",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -12,66 +12,191 @@
|
|||||||
"start": "nest start",
|
"start": "nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "nest start --watch",
|
||||||
"start:debug": "nest start --debug --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",
|
"start:prod-exit": "node dist/main",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "oxlint --deny-warnings",
|
||||||
"test": "jest",
|
"lint-fix": "oxlint --fix --deny-warnings",
|
||||||
"test:watch": "jest --watch",
|
"test": "yarn test:unit",
|
||||||
"test:cov": "jest --coverage",
|
"test:watch": "jest --selectProjects unit --watch",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:cov": "jest --selectProjects unit --coverage",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --selectProjects unit --runInBand",
|
||||||
|
"test:unit": "jest --selectProjects unit",
|
||||||
|
"test:e2e": "jest --selectProjects e2e",
|
||||||
|
"test:e8e": "yarn test:e2e",
|
||||||
|
"test:all": "yarn test:unit && yarn test:e8e",
|
||||||
|
"gows:proto:fetch": "node scripts/gows-proto.js fetch",
|
||||||
|
"gows:proto:build": "node scripts/gows-proto.js build",
|
||||||
|
"gows:proto": "yarn gows:proto:fetch && yarn gows:proto:build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^9.0.9",
|
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2026-02-11",
|
||||||
"@nestjs/config": "^0.5.0",
|
"@adiwajshing/keyed-db": "^0.2.4",
|
||||||
"@nestjs/core": "^9.0.9",
|
"@aws-sdk/client-s3": "^3.633.0",
|
||||||
"@nestjs/platform-express": "^9.0.9",
|
"@aws-sdk/s3-request-presigner": "^3.633.0",
|
||||||
"@nestjs/serve-static": "^2.1.3",
|
"@bull-board/api": "^6.9.1",
|
||||||
"@nestjs/swagger": "^6.0.5",
|
"@bull-board/express": "^6.9.1",
|
||||||
"class-validator": "^0.12.2",
|
"@bull-board/nestjs": "^6.9.1",
|
||||||
"del": "^6.0.0",
|
"@casl/ability": "^6.8.0",
|
||||||
|
"@figuro/chatwoot-sdk": "^1.1.17",
|
||||||
|
"@liaoliaots/nestjs-redis": "^9",
|
||||||
|
"@nestjs/axios": "^3.0.2",
|
||||||
|
"@nestjs/bullmq": "^11.0.2",
|
||||||
|
"@nestjs/common": "^11.0.0",
|
||||||
|
"@nestjs/config": "^3.2.3",
|
||||||
|
"@nestjs/core": "^11.0.0",
|
||||||
|
"@nestjs/passport": "^11.0.0",
|
||||||
|
"@nestjs/platform-express": "^11.1.5",
|
||||||
|
"@nestjs/platform-ws": "^11.0.0",
|
||||||
|
"@nestjs/serve-static": "^5.0.0",
|
||||||
|
"@nestjs/swagger": "^7.1.11",
|
||||||
|
"@nestjs/terminus": "^10.2.3",
|
||||||
|
"@nestjs/websockets": "^11.0.0",
|
||||||
|
"@types/better-sqlite3": "^7.6.10",
|
||||||
|
"@types/lodash": "^4.14.194",
|
||||||
|
"@types/mustache": "^4.2.5",
|
||||||
|
"@types/passport": "^1.0.17",
|
||||||
|
"@types/sqlite3": "^5.1.0",
|
||||||
|
"@types/ws": "^8.5.4",
|
||||||
|
"@wppconnect-team/wppconnect": "^1.40.1",
|
||||||
|
"adm-zip": "0.5.10",
|
||||||
|
"agentkeepalive": "^4.5.0",
|
||||||
|
"async-lock": "^1.4.1",
|
||||||
|
"audio-decode": "^2.2.2",
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"axios-retry": "^4.5.0",
|
||||||
|
"better-sqlite3": "^12.4.1",
|
||||||
|
"bullmq": "^5.48.1",
|
||||||
|
"check-disk-space": "^3.4.0",
|
||||||
|
"chokidar": "^3.6.0",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "0.14.0",
|
||||||
|
"commander": "^14.0.1",
|
||||||
|
"del": "^8.0.0",
|
||||||
|
"express-basic-auth": "^1.2.1",
|
||||||
|
"file-type": "16.5.4",
|
||||||
|
"fs-extra": "^11.2.0",
|
||||||
|
"google-protobuf": "^3.21.4",
|
||||||
|
"https-proxy-agent": "^7.0.0",
|
||||||
|
"ical.js": "^2.2.1",
|
||||||
|
"ioredis": "^5",
|
||||||
|
"joi": "^17.13.3",
|
||||||
|
"knex": "^3.1.0",
|
||||||
|
"libphonenumber-js": "^1.10.36",
|
||||||
|
"libsignal": "github:WhiskeySockets/libsignal-node#master",
|
||||||
|
"link-preview-js": "^3.0.4",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"long": "^5.3.2",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
|
"mongodb": "6.9.0",
|
||||||
|
"ms": "^2.1.3",
|
||||||
|
"mustache": "^4.2.0",
|
||||||
|
"nestjs-pino": "^4.1.0",
|
||||||
|
"node-cache": "5.1.2",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-headerapikey": "^1.2.2",
|
||||||
|
"pg": "^8.13.1",
|
||||||
|
"pino-http": "^10.2.0",
|
||||||
|
"pino-pretty": "^11.2.1",
|
||||||
|
"pretty-bytes": "5.6.0",
|
||||||
|
"promise-retry": "^2.0.1",
|
||||||
|
"puppeteer": "^24.31.0",
|
||||||
|
"qrcode": "^1.5.1",
|
||||||
|
"qrcode-terminal": "^0.12.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"requestretry": "^4.1.1",
|
"rimraf": "^4.3.0",
|
||||||
"rimraf": "^3.0.2",
|
"rxjs": "^7.8.1",
|
||||||
"rxjs": "^7.1.0",
|
"sharp": "^0.33.4",
|
||||||
|
"shell-quote": "^1.8.3",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
"swagger-ui-express": "^4.1.4",
|
"swagger-ui-express": "^4.1.4",
|
||||||
"venom-bot": "^4.3.0"
|
"ulid": "^2.3.0",
|
||||||
|
"undici": "^7.16.0",
|
||||||
|
"uniqid": "^5.4.0",
|
||||||
|
"user-agents": "^1.1.669",
|
||||||
|
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-2026-02-18",
|
||||||
|
"write-file-atomic": "^6.0.0",
|
||||||
|
"yaml": "^2.7.1"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"bufferutil": "^4.0.8"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"typescript": "5.9.2",
|
||||||
|
"bufferutil": "^4.0.8",
|
||||||
|
"ws": "^8.18.0",
|
||||||
|
"puppeteer": "^24.31.0",
|
||||||
|
"whatwg-url": "13.0.0",
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"whatsapp-rust-bridge": "npm:@devlikeapro/whatsapp-rust-bridge@0.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^9.0.0",
|
"@grpc/grpc-js": "^1.14.1",
|
||||||
"@nestjs/schematics": "^9.0.1",
|
"@grpc/proto-loader": "^0.8.0",
|
||||||
"@nestjs/testing": "^9.0.9",
|
"@nestjs/cli": "^11.0.0",
|
||||||
|
"@nestjs/schematics": "^11.0.0",
|
||||||
|
"@nestjs/testing": "^11.0.0",
|
||||||
"@types/express": "^4.17.3",
|
"@types/express": "^4.17.3",
|
||||||
"@types/jest": "26.0.10",
|
"@types/jest": "26.0.10",
|
||||||
"@types/node": "^13.9.1",
|
"@types/node": "^18.14.2",
|
||||||
|
"@types/node-fetch": "^2.6.13",
|
||||||
|
"@types/semver": "^7.7.0",
|
||||||
"@types/supertest": "^2.0.8",
|
"@types/supertest": "^2.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "3.9.1",
|
"@types/user-agents": "^1",
|
||||||
"@typescript-eslint/parser": "3.9.1",
|
"dotenv": "^17.2.3",
|
||||||
"eslint": "7.7.0",
|
"grpc-tools": "^1.13.0",
|
||||||
"eslint-config-prettier": "^6.10.0",
|
"jest": "^29.7.0",
|
||||||
"eslint-plugin-import": "^2.20.1",
|
"oxlint": "^1.13.0",
|
||||||
"jest": "26.4.2",
|
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
|
"protoc-gen-ts": "^0.8.7",
|
||||||
"supertest": "^4.0.2",
|
"supertest": "^4.0.2",
|
||||||
"ts-jest": "26.2.0",
|
"ts-jest": "^29.1.3",
|
||||||
"ts-loader": "^6.2.1",
|
"ts-loader": "^6.2.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths": "^4.1.0",
|
"tsconfig-paths": "^4.1.0",
|
||||||
"typescript": "4.7.4"
|
"typescript": "5.9.2"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"projects": [
|
||||||
"js",
|
{
|
||||||
"json",
|
"displayName": "unit",
|
||||||
"ts"
|
"moduleFileExtensions": [
|
||||||
],
|
"js",
|
||||||
"rootDir": "src",
|
"json",
|
||||||
"testRegex": ".spec.ts$",
|
"ts"
|
||||||
"transform": {
|
],
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
"rootDir": "src",
|
||||||
},
|
"moduleNameMapper": {
|
||||||
"coverageDirectory": "../coverage",
|
"^@waha/(.*)$": "<rootDir>/$1"
|
||||||
"testEnvironment": "node"
|
},
|
||||||
}
|
"testRegex": ".test.ts$",
|
||||||
|
"testPathIgnorePatterns": [
|
||||||
|
"/__tests__/e2e/"
|
||||||
|
],
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"displayName": "e2e",
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": "src",
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"^@waha/(.*)$": "<rootDir>/$1"
|
||||||
|
},
|
||||||
|
"testRegex": "__tests__/e2e/.*\\.test\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@4.9.2"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Set source and output file
|
||||||
|
SOURCE_DIR="src"
|
||||||
|
OUTPUT_DIR="$HOME/Downloads/chatgpt"
|
||||||
|
OUTPUT_FILE="$OUTPUT_DIR/waha.ts"
|
||||||
|
|
||||||
|
# Create or clear the output file
|
||||||
|
rm -f "$OUTPUT_FILE"
|
||||||
|
touch "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
# Find all .md files in the source directory and combine them
|
||||||
|
find "$SOURCE_DIR" -type f -name "*.ts" | while read -r file; do
|
||||||
|
# Add file name to OUTPUT_FILE
|
||||||
|
echo "\n-------" >> "$OUTPUT_FILE"
|
||||||
|
echo "File: $file" >> "$OUTPUT_FILE"
|
||||||
|
echo "-------\n" >> "$OUTPUT_FILE"
|
||||||
|
# Append the content of the file to the output file
|
||||||
|
cat "$file" >> "$OUTPUT_FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "All .ts files combined into $OUTPUT_FILE with separators."
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const { readFileSync } = require('fs');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
const YAML = require('yaml');
|
||||||
|
|
||||||
|
const repoRoot = path.resolve(__dirname, '..');
|
||||||
|
const enPath = path.join(repoRoot, 'src/apps/chatwoot/i18n/locales/en-US.yaml');
|
||||||
|
const contents = readFileSync(enPath, 'utf8');
|
||||||
|
const document = YAML.parse(contents);
|
||||||
|
|
||||||
|
const flatten = (node, prefix = '') => {
|
||||||
|
if (node && typeof node === 'object' && !Array.isArray(node)) {
|
||||||
|
return Object.entries(node).reduce((acc, [key, value]) => {
|
||||||
|
const next = prefix ? `${prefix}.${key}` : key;
|
||||||
|
return acc.concat(flatten(value, next));
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [prefix];
|
||||||
|
};
|
||||||
|
|
||||||
|
const allKeys = flatten(document).filter(Boolean);
|
||||||
|
|
||||||
|
const ignoredPrefixes = ['datetime.', 'locale.'];
|
||||||
|
|
||||||
|
const ignored = new Set();
|
||||||
|
const unused = [];
|
||||||
|
|
||||||
|
const searchRoots = ['src', 'tests'];
|
||||||
|
|
||||||
|
for (const key of allKeys) {
|
||||||
|
if (
|
||||||
|
ignoredPrefixes.some((prefix) => key.startsWith(prefix)) ||
|
||||||
|
!key.includes('.')
|
||||||
|
) {
|
||||||
|
ignored.add(key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = spawnSync(
|
||||||
|
'rg',
|
||||||
|
[
|
||||||
|
'--files-with-matches',
|
||||||
|
'--fixed-strings',
|
||||||
|
key,
|
||||||
|
...searchRoots,
|
||||||
|
'--glob',
|
||||||
|
'!apps/chatwoot/i18n/locales/*.yaml',
|
||||||
|
],
|
||||||
|
{
|
||||||
|
cwd: repoRoot,
|
||||||
|
encoding: 'utf8',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.status !== 0 || !result.stdout.trim()) {
|
||||||
|
unused.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = [
|
||||||
|
`Scanned ${allKeys.length} keys.`,
|
||||||
|
`Ignored ${ignored.size} keys (datetime.*, locale.*, or without dot).`,
|
||||||
|
unused.length
|
||||||
|
? `Found ${unused.length} potentially unused keys:\n - ${unused.join(
|
||||||
|
'\n - ',
|
||||||
|
)}`
|
||||||
|
: 'No unused keys detected.',
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(message.join('\n'));
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const fs = require('fs');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const path = require('path');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const axios = require('axios');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const yargs = require('yargs');
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
const CONFIG_FILE = 'waha.config.json';
|
||||||
|
// Load defaults from package.json
|
||||||
|
gows = (() => {
|
||||||
|
try {
|
||||||
|
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
||||||
|
return config.waha.gows || {};
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const DEFAULT_REPO = gows.repo;
|
||||||
|
if (!DEFAULT_REPO) {
|
||||||
|
throw new Error(`Missing default repo in ${CONFIG_FILE}`);
|
||||||
|
}
|
||||||
|
const DEFAULT_REF = gows.ref;
|
||||||
|
if (!DEFAULT_REF) {
|
||||||
|
throw new Error('Missing default ref in ${CONFIG_FILE}');
|
||||||
|
}
|
||||||
|
const DEFAULT_DIR = './src/core/engines/gows/proto';
|
||||||
|
|
||||||
|
const PROTO_FILES = ['gows.proto'];
|
||||||
|
const PROTO_OUTPUT = './src/core/engines/gows/grpc';
|
||||||
|
|
||||||
|
// Helper function to clean directory
|
||||||
|
function cleanDirectory(directory, suffix) {
|
||||||
|
if (!fs.existsSync(directory)) {
|
||||||
|
fs.mkdirSync(directory, { recursive: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(directory);
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(directory, file);
|
||||||
|
if (file.endsWith(suffix)) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to download files
|
||||||
|
async function downloadFiles(repo, ref, directory) {
|
||||||
|
for (const file of PROTO_FILES) {
|
||||||
|
const url = `https://github.com/${repo}/releases/download/${ref}/${file}`;
|
||||||
|
const filePath = path.join(directory, file);
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, { responseType: 'arraybuffer' });
|
||||||
|
fs.writeFileSync(filePath, response.data);
|
||||||
|
console.log(`Downloaded: ${file}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to download ${file}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for fetch command
|
||||||
|
async function handleFetch(repo, ref, dir) {
|
||||||
|
console.log(`Fetching .proto files from ${repo}@${ref} to ${dir}...`);
|
||||||
|
cleanDirectory(dir, '.proto');
|
||||||
|
await downloadFiles(repo, ref, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for build command
|
||||||
|
function handleBuild(dir) {
|
||||||
|
console.log('Building gRPC files...');
|
||||||
|
cleanDirectory(PROTO_OUTPUT);
|
||||||
|
|
||||||
|
const command = `grpc_tools_node_protoc \
|
||||||
|
--plugin=protoc-gen-ts=node_modules/.bin/protoc-gen-ts \
|
||||||
|
--plugin=protoc-gen-grpc=node_modules/.bin/grpc_tools_node_protoc_plugin \
|
||||||
|
--js_out=import_style=commonjs,binary:${PROTO_OUTPUT} \
|
||||||
|
--grpc_out=grpc_js:${PROTO_OUTPUT} \
|
||||||
|
--ts_out=grpc_js:${PROTO_OUTPUT} \
|
||||||
|
-I ${dir} ${dir}/gows.proto`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
execSync(command, { stdio: 'inherit' });
|
||||||
|
console.log('gRPC files built successfully.');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to build gRPC files: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Commands
|
||||||
|
//
|
||||||
|
|
||||||
|
// fetch
|
||||||
|
yargs.command(
|
||||||
|
'fetch',
|
||||||
|
'Fetch .proto files from GitHub',
|
||||||
|
(yargs) => {
|
||||||
|
yargs
|
||||||
|
.option('repo', {
|
||||||
|
describe: 'GitHub repository (owner/repo)',
|
||||||
|
type: 'string',
|
||||||
|
default: DEFAULT_REPO,
|
||||||
|
})
|
||||||
|
.option('ref', {
|
||||||
|
describe: 'Git reference (branch or commit SHA)',
|
||||||
|
type: 'string',
|
||||||
|
default: DEFAULT_REF,
|
||||||
|
})
|
||||||
|
.option('dir', {
|
||||||
|
describe: 'Directory to output .proto files',
|
||||||
|
type: 'string',
|
||||||
|
default: DEFAULT_DIR,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async (argv) => {
|
||||||
|
await handleFetch(argv.repo, argv.ref, argv.dir);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// build
|
||||||
|
yargs.command(
|
||||||
|
'build',
|
||||||
|
'Build gRPC files from .proto files',
|
||||||
|
(yargs) => {
|
||||||
|
yargs.option('dir', {
|
||||||
|
describe: 'Directory containing .proto files',
|
||||||
|
type: 'string',
|
||||||
|
default: DEFAULT_DIR,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(argv) => {
|
||||||
|
handleBuild(argv.dir);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse arguments
|
||||||
|
yargs.parse();
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const projectRoot = path.resolve(__dirname, '..');
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const forceIndex = args.indexOf('--force');
|
||||||
|
const forceOverwrite = forceIndex !== -1;
|
||||||
|
|
||||||
|
if (forceOverwrite) {
|
||||||
|
args.splice(forceIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderArg = args[0] || '.';
|
||||||
|
const sourceArgRaw = args[1];
|
||||||
|
const targetArg = args[2] || '.env';
|
||||||
|
|
||||||
|
const envDir = path.resolve(process.cwd(), folderArg);
|
||||||
|
const sourceArg = sourceArgRaw || '.env.example';
|
||||||
|
const templateBaseDir = sourceArgRaw ? envDir : projectRoot;
|
||||||
|
const templatePath = path.isAbsolute(sourceArg)
|
||||||
|
? sourceArg
|
||||||
|
: path.resolve(templateBaseDir, sourceArg);
|
||||||
|
const targetPath = path.isAbsolute(targetArg)
|
||||||
|
? targetArg
|
||||||
|
: path.resolve(envDir, targetArg);
|
||||||
|
|
||||||
|
function exitWithMessage(message, code = 1) {
|
||||||
|
console.error(message);
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(envDir)) {
|
||||||
|
exitWithMessage(`Target directory does not exist: ${envDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(templatePath)) {
|
||||||
|
exitWithMessage(`Missing template file: ${templatePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!forceOverwrite && fs.existsSync(targetPath)) {
|
||||||
|
const stats = fs.statSync(targetPath);
|
||||||
|
if (stats.size > 0) {
|
||||||
|
exitWithMessage(
|
||||||
|
`${targetPath} already exists and is not empty. Remove it manually or rerun with --force to overwrite.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exampleContent = fs.readFileSync(templatePath, 'utf8');
|
||||||
|
|
||||||
|
function generateSecret() {
|
||||||
|
return crypto.randomUUID({ disableEntropyCache: true }).replace(/-/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKey = generateSecret();
|
||||||
|
const adminPassword = generateSecret();
|
||||||
|
|
||||||
|
const updatedContent = exampleContent
|
||||||
|
.replace(/^WAHA_API_KEY=.*/m, `WAHA_API_KEY=${apiKey}`)
|
||||||
|
.replace(/^WAHA_API_KEY_PLAIN=.*/m, `WAHA_API_KEY_PLAIN=${apiKey}`)
|
||||||
|
.replace(/^WAHA_DASHBOARD_USERNAME=.*/m, 'WAHA_DASHBOARD_USERNAME=admin')
|
||||||
|
.replace(
|
||||||
|
/^WAHA_DASHBOARD_PASSWORD=.*/m,
|
||||||
|
`WAHA_DASHBOARD_PASSWORD=${adminPassword}`,
|
||||||
|
)
|
||||||
|
.replace(/^WHATSAPP_SWAGGER_USERNAME=.*/m, 'WHATSAPP_SWAGGER_USERNAME=admin')
|
||||||
|
.replace(
|
||||||
|
/^WHATSAPP_SWAGGER_PASSWORD=.*/m,
|
||||||
|
`WHATSAPP_SWAGGER_PASSWORD=${adminPassword}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(targetPath, updatedContent, { encoding: 'utf8' });
|
||||||
|
|
||||||
|
const generatedEnvValues = [
|
||||||
|
`WAHA_API_KEY=${apiKey}`,
|
||||||
|
`WAHA_API_KEY_PLAIN=${apiKey}`,
|
||||||
|
'WAHA_DASHBOARD_USERNAME=admin',
|
||||||
|
`WAHA_DASHBOARD_PASSWORD=${adminPassword}`,
|
||||||
|
'WHATSAPP_SWAGGER_USERNAME=admin',
|
||||||
|
`WHATSAPP_SWAGGER_PASSWORD=${adminPassword}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
'Credentials generated.',
|
||||||
|
'',
|
||||||
|
'Generated env values:',
|
||||||
|
...generatedEnvValues.map((line) => ` - ${line}`),
|
||||||
|
'',
|
||||||
|
'Use these credentials to login in Dashboard or Swagger:',
|
||||||
|
' - Username: admin',
|
||||||
|
` - Password: ${adminPassword}`,
|
||||||
|
'',
|
||||||
|
'Use this API key in the x-api-key header:',
|
||||||
|
` - ${apiKey}`,
|
||||||
|
'',
|
||||||
|
'Read more:',
|
||||||
|
' - https://waha.devlike.pro/docs/how-to/dashboard/#api-key',
|
||||||
|
' - https://waha.devlike.pro/docs/how-to/security/',
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(lines.join('\n'));
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import WebSocket = require('ws');
|
||||||
|
|
||||||
|
const VALID_API_KEY = '666';
|
||||||
|
const VALID_USERNAME = 'admin';
|
||||||
|
const VALID_PASSWORD = '666';
|
||||||
|
const VALID_SESSION_API_KEY = 'key_0fQfI3n3rFVsczBbBfknLXKUreY2my6J';
|
||||||
|
|
||||||
|
const BASE_URL = (process.env.WAHA_BASE_URL ?? 'http://localhost:3000').replace(
|
||||||
|
/\/$/,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
const WS_BASE_URL = BASE_URL.replace(/^http/, 'ws');
|
||||||
|
|
||||||
|
const HTTP_OK = 200;
|
||||||
|
const HTTP_CREATED = 201;
|
||||||
|
const HTTP_UNAUTHORIZED = 401;
|
||||||
|
const HTTP_FORBIDDEN = 403;
|
||||||
|
const HTTP_NOT_FOUND = 404;
|
||||||
|
const HTTP_UNPROCESSABLE = 422;
|
||||||
|
const WS_POLICY_VIOLATION = 1008;
|
||||||
|
const WS_OK_CODES = [1000, 1005];
|
||||||
|
|
||||||
|
describe('admin - GET /api/sessions/{name}', () => {
|
||||||
|
test('no api key is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/sessions/default`, {
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('valid api key is ok', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/sessions/default`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid api key is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/sessions/default`, {
|
||||||
|
headers: { 'X-Api-Key': '123' },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('admin - POST /api/sessions', () => {
|
||||||
|
test('admin key can create a session without name and remove it', async () => {
|
||||||
|
const createResponse = await axios.post(
|
||||||
|
`${BASE_URL}/api/sessions`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: { 'X-Api-Key': VALID_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(createResponse.status).toBe(HTTP_CREATED);
|
||||||
|
expect(createResponse.data?.name).toBeTruthy();
|
||||||
|
|
||||||
|
const deleteResponse = await axios.delete(
|
||||||
|
`${BASE_URL}/api/sessions/${createResponse.data.name}`,
|
||||||
|
{
|
||||||
|
headers: { 'X-Api-Key': VALID_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(deleteResponse.status).toBe(HTTP_OK);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/sessions?all=true', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
const createResponse = await axios.post(
|
||||||
|
`${BASE_URL}/api/sessions`,
|
||||||
|
{ name: 'another' },
|
||||||
|
{
|
||||||
|
headers: { 'X-Api-Key': VALID_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (![HTTP_CREATED, HTTP_UNPROCESSABLE].includes(createResponse.status)) {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected status for creating "another" session: ${createResponse.status}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const deleteResponse = await axios.delete(
|
||||||
|
`${BASE_URL}/api/sessions/another`,
|
||||||
|
{
|
||||||
|
headers: { 'X-Api-Key': VALID_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (![HTTP_OK, HTTP_NOT_FOUND].includes(deleteResponse.status)) {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected status for deleting "another" session: ${deleteResponse.status}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin key returns two sessions', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/sessions?all=true`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
expect(response.data).toHaveLength(2);
|
||||||
|
expect(response.data.map((session) => session.name).sort()).toEqual([
|
||||||
|
'another',
|
||||||
|
'default',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no api key is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/sessions?all=true`, {
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('session key returns one session', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/sessions?all=true`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
expect(response.data).toHaveLength(1);
|
||||||
|
expect(response.data[0]?.name).toBe('default');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/server/version', () => {
|
||||||
|
test('no api key is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/server/version`, {
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin api key is ok', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/server/version`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('session api key is ok', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/server/version`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/server/environment', () => {
|
||||||
|
test('no api key is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/server/environment`, {
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin api key is ok', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/server/environment`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('session api key is forbidden', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/server/environment`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_FORBIDDEN);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('admin - GET /health', () => {
|
||||||
|
test('no api key is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/health`, {
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('valid api key is ok', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/health`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid api key is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/health`, {
|
||||||
|
headers: { 'X-Api-Key': '123' },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/ws', () => {
|
||||||
|
const buildWsUrl = (apiKey?: string) => {
|
||||||
|
const wsUrl = new URL('/ws', WS_BASE_URL);
|
||||||
|
wsUrl.searchParams.set('session', '*');
|
||||||
|
wsUrl.searchParams.set('events', '*');
|
||||||
|
if (apiKey) {
|
||||||
|
wsUrl.searchParams.set('x-api-key', apiKey);
|
||||||
|
}
|
||||||
|
return wsUrl.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const waitForOpen = (socket: WebSocket, timeoutMs = 5_000) =>
|
||||||
|
new Promise<void>((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
reject(new Error('WebSocket open timeout'));
|
||||||
|
}, timeoutMs);
|
||||||
|
socket.once('open', () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
socket.once('error', (error) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const waitForClose = (socket: WebSocket, timeoutMs = 5_000) =>
|
||||||
|
new Promise<{ code: number; reason: string }>((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
reject(new Error('WebSocket close timeout'));
|
||||||
|
}, timeoutMs);
|
||||||
|
socket.once('close', (code, reason) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve({ code, reason: reason.toString() });
|
||||||
|
});
|
||||||
|
socket.once('error', (error) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no api key is unauthorized', async () => {
|
||||||
|
const socket = new WebSocket(buildWsUrl());
|
||||||
|
const { code } = await waitForClose(socket);
|
||||||
|
expect(code).toBe(WS_POLICY_VIOLATION);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin api key is ok', async () => {
|
||||||
|
const socket = new WebSocket(buildWsUrl(VALID_API_KEY));
|
||||||
|
await waitForOpen(socket);
|
||||||
|
socket.close();
|
||||||
|
const { code } = await waitForClose(socket);
|
||||||
|
expect(WS_OK_CODES).toContain(code);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/files/test.txt', () => {
|
||||||
|
// create "test.txt" in current dir/.media/test.txt
|
||||||
|
let file = null;
|
||||||
|
beforeAll(() => {
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const mediaDir = path.resolve('./.media');
|
||||||
|
if (!fs.existsSync(mediaDir)) {
|
||||||
|
fs.mkdirSync(mediaDir);
|
||||||
|
}
|
||||||
|
const filePath = path.join(mediaDir, 'test.txt');
|
||||||
|
fs.writeFileSync(filePath, 'This is a test file.');
|
||||||
|
file = filePath;
|
||||||
|
});
|
||||||
|
test('no api key is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/files/test.txt`, {
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('valid api key is ok', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/files/test.txt`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid api key is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/files/test.txt`, {
|
||||||
|
headers: { 'X-Api-Key': '123' },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /', () => {
|
||||||
|
test('no auth is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/`, {
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('valid auth is ok', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/`, {
|
||||||
|
auth: { username: VALID_USERNAME, password: VALID_PASSWORD },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('wrong auth is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/`, {
|
||||||
|
auth: { username: VALID_USERNAME, password: 'password-another' },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /dashboard', () => {
|
||||||
|
test('no auth is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/dashboard/`, {
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('valid auth is ok', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/dashboard/`, {
|
||||||
|
auth: { username: VALID_USERNAME, password: VALID_PASSWORD },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('wrong auth is unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/dashboard/`, {
|
||||||
|
auth: { username: VALID_USERNAME, password: 'password-another' },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('session key - GET /api/sessions/{name}', () => {
|
||||||
|
test('default key - default session - is authorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/sessions/default`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default key - create a new sessions - forbidden', async () => {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${BASE_URL}/api/sessions`,
|
||||||
|
{ name: 'newone' },
|
||||||
|
{
|
||||||
|
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(response.status).toBe(HTTP_FORBIDDEN);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('another key - default session - unauthorized', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/sessions/default`, {
|
||||||
|
headers: { 'X-Api-Key': 'key_another' },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default key - another session - forbidden', async () => {
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/sessions/another`, {
|
||||||
|
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(HTTP_FORBIDDEN);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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,139 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
NotFoundException,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
UnprocessableEntityException,
|
||||||
|
UseGuards,
|
||||||
|
UsePipes,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
import { CanServer } from '@waha/core/auth/policies';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { ApiKey, CheckInvariant } from '@waha/core/storage/IApiKeyRepository';
|
||||||
|
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
|
||||||
|
import { ApiKeyDTO, ApiKeyRequest } from '@waha/structures/apikeys.dto';
|
||||||
|
import { generatePrefixedId, generateSecret } from '@waha/utils/ids';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/keys')
|
||||||
|
@ApiTags('🔑 Api Keys')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanServer(Action.Manage))
|
||||||
|
export class ApiKeysController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Post('/')
|
||||||
|
@ApiOperation({ summary: 'Create a new API key' })
|
||||||
|
@UsePipes(new WAHAValidationPipe())
|
||||||
|
async create(@Body() body: ApiKeyRequest): Promise<ApiKeyDTO> {
|
||||||
|
CheckInvariant(body);
|
||||||
|
if (body.session) {
|
||||||
|
const exists = await this.manager.exists(body.session);
|
||||||
|
if (!exists) {
|
||||||
|
throw new UnprocessableEntityException(
|
||||||
|
`Session "${body.session}" does not exist`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let apikey: ApiKey | null = null;
|
||||||
|
// Try 5 times to check there's no conflict on id and key
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
apikey = {
|
||||||
|
id: generatePrefixedId('key_id'),
|
||||||
|
key: `key_${generateSecret(32)}`,
|
||||||
|
isActive: body.isActive,
|
||||||
|
isAdmin: body.isAdmin,
|
||||||
|
session: body.session,
|
||||||
|
rules: null,
|
||||||
|
};
|
||||||
|
const idExists = await this.manager.apiKeyRepository.getById(apikey.id);
|
||||||
|
if (idExists) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const keyExists = await this.manager.apiKeyRepository.getByKey(
|
||||||
|
apikey.key,
|
||||||
|
);
|
||||||
|
if (keyExists) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!apikey) {
|
||||||
|
throw new UnprocessableEntityException(
|
||||||
|
`Failed to generate API key, try again`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
CheckInvariant(apikey);
|
||||||
|
await this.manager.apiKeyRepository.upsert(apikey);
|
||||||
|
return ApiKeyToDTO(apikey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/')
|
||||||
|
@ApiOperation({ summary: 'Get all API keys' })
|
||||||
|
async list(): Promise<ApiKeyDTO[]> {
|
||||||
|
const keys = await this.manager.apiKeyRepository.list();
|
||||||
|
return keys.map((key) => ApiKeyToDTO(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('/:id')
|
||||||
|
@ApiOperation({ summary: 'Update an API key' })
|
||||||
|
@UsePipes(new WAHAValidationPipe())
|
||||||
|
async update(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() body: ApiKeyRequest,
|
||||||
|
): Promise<ApiKeyDTO> {
|
||||||
|
const existing = await this.manager.apiKeyRepository.getById(id);
|
||||||
|
if (!existing) {
|
||||||
|
throw new NotFoundException('API key not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const apikey: ApiKey = {
|
||||||
|
...existing,
|
||||||
|
isActive: body.isActive,
|
||||||
|
isAdmin: body.isAdmin,
|
||||||
|
session: body.session,
|
||||||
|
};
|
||||||
|
CheckInvariant(apikey);
|
||||||
|
if (apikey.session) {
|
||||||
|
const exists = await this.manager.exists(apikey.session);
|
||||||
|
if (!exists) {
|
||||||
|
throw new UnprocessableEntityException(
|
||||||
|
`Session "${apikey.session}" does not exist`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckInvariant(apikey);
|
||||||
|
await this.manager.apiKeyRepository.upsert(apikey);
|
||||||
|
return ApiKeyToDTO(apikey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('/:id')
|
||||||
|
@ApiOperation({ summary: 'Delete an API key' })
|
||||||
|
async delete(@Param('id') id: string): Promise<{ result: true }> {
|
||||||
|
const existing = await this.manager.apiKeyRepository.getById(id);
|
||||||
|
if (!existing) {
|
||||||
|
throw new NotFoundException('API key not found');
|
||||||
|
}
|
||||||
|
await this.manager.apiKeyRepository.deleteById(id);
|
||||||
|
return { result: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApiKeyToDTO(apikey: ApiKey): ApiKeyDTO {
|
||||||
|
return {
|
||||||
|
id: apikey.id,
|
||||||
|
key: apikey.key,
|
||||||
|
isActive: apikey.isActive,
|
||||||
|
isAdmin: apikey.isAdmin,
|
||||||
|
session: apikey.session,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
UseInterceptors,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { ApiFileAcceptHeader } from '@waha/nestjs/ApiFileAcceptHeader';
|
||||||
|
import {
|
||||||
|
QRCodeSessionParam,
|
||||||
|
SessionApiParam,
|
||||||
|
SessionParam,
|
||||||
|
} from '@waha/nestjs/params/SessionApiParam';
|
||||||
|
|
||||||
|
import { SessionManager } from '../core/abc/manager.abc';
|
||||||
|
import { WhatsappSession } from '../core/abc/session.abc';
|
||||||
|
import { BufferResponseInterceptor } from '../nestjs/BufferResponseInterceptor';
|
||||||
|
import {
|
||||||
|
QRCodeFormat,
|
||||||
|
QRCodeQuery,
|
||||||
|
QRCodeValue,
|
||||||
|
RequestCodeRequest,
|
||||||
|
} from '../structures/auth.dto';
|
||||||
|
import { Base64File } from '../structures/files.dto';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/:session/auth')
|
||||||
|
@ApiTags('📱 Pairing')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
|
||||||
|
class AuthController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Get('qr')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Get QR code for pairing WhatsApp API.',
|
||||||
|
})
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiFileAcceptHeader('image/png', Base64File, QRCodeValue)
|
||||||
|
@UseInterceptors(new BufferResponseInterceptor('image/png'))
|
||||||
|
async getQR(
|
||||||
|
@QRCodeSessionParam session: WhatsappSession,
|
||||||
|
@Query() query: QRCodeQuery,
|
||||||
|
): Promise<Buffer | QRCodeValue> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { AuthController };
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Post,
|
||||||
|
UseGuards,
|
||||||
|
UsePipes,
|
||||||
|
ValidationPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
SessionApiParam,
|
||||||
|
WorkingSessionParam,
|
||||||
|
} from '@waha/nestjs/params/SessionApiParam';
|
||||||
|
|
||||||
|
import { SessionManager } from '../core/abc/manager.abc';
|
||||||
|
import { WhatsappSession } from '../core/abc/session.abc';
|
||||||
|
import { RejectCallRequest } from '../structures/calls.dto';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/:session/calls')
|
||||||
|
@ApiTags('📞 Calls')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
|
||||||
|
export class CallsController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Post('reject')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Reject incoming call' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
rejectCall(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() request: RejectCallRequest,
|
||||||
|
) {
|
||||||
|
return session.rejectCall(request.from, request.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
UsePipes,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiParam, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { ChannelsInfoServiceCore } from '@waha/core/services/ChannelsInfoServiceCore';
|
||||||
|
import { isJidNewsletter } from '@waha/core/utils/jids';
|
||||||
|
import {
|
||||||
|
SessionApiParam,
|
||||||
|
WorkingSessionParam,
|
||||||
|
} from '@waha/nestjs/params/SessionApiParam';
|
||||||
|
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
|
||||||
|
import {
|
||||||
|
Channel,
|
||||||
|
ChannelCategory,
|
||||||
|
ChannelCountry,
|
||||||
|
ChannelListResult,
|
||||||
|
ChannelMessage,
|
||||||
|
ChannelSearchByText,
|
||||||
|
ChannelSearchByView,
|
||||||
|
ChannelView,
|
||||||
|
CreateChannelRequest,
|
||||||
|
ListChannelsQuery,
|
||||||
|
NewsletterIdApiParam,
|
||||||
|
NewsletterIdOrInviteCodeApiParam,
|
||||||
|
PreviewChannelMessages,
|
||||||
|
} from '@waha/structures/channels.dto';
|
||||||
|
|
||||||
|
import { SessionManager } from '../core/abc/manager.abc';
|
||||||
|
import {
|
||||||
|
parseChannelInviteLink,
|
||||||
|
WhatsappSession,
|
||||||
|
} from '../core/abc/session.abc';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/:session/channels')
|
||||||
|
@ApiTags('📢 Channels')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
|
||||||
|
export class ChannelsController {
|
||||||
|
constructor(
|
||||||
|
private manager: SessionManager,
|
||||||
|
private channelsInfoService: ChannelsInfoServiceCore,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get list of know channels' })
|
||||||
|
async list(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Query() query: ListChannelsQuery,
|
||||||
|
): Promise<Channel[]> {
|
||||||
|
return session.channelsList(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Create a new channel.' })
|
||||||
|
create(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() request: CreateChannelRequest,
|
||||||
|
): Promise<Channel> {
|
||||||
|
return session.channelsCreateChannel(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@SessionApiParam
|
||||||
|
@NewsletterIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Delete the channel.' })
|
||||||
|
delete(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
return session.channelsDeleteChannel(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@SessionApiParam
|
||||||
|
@NewsletterIdOrInviteCodeApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Get the channel info',
|
||||||
|
description:
|
||||||
|
'You can use either id (123@newsletter) ' +
|
||||||
|
'OR invite code (https://www.whatsapp.com/channel/123)',
|
||||||
|
})
|
||||||
|
get(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<Channel> {
|
||||||
|
if (isJidNewsletter(id)) {
|
||||||
|
return session.channelsGetChannel(id);
|
||||||
|
} else {
|
||||||
|
const inviteCode = parseChannelInviteLink(id);
|
||||||
|
return session.channelsGetChannelByInviteCode(inviteCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/messages/preview')
|
||||||
|
@SessionApiParam
|
||||||
|
@UsePipes(new WAHAValidationPipe())
|
||||||
|
@ApiParam({
|
||||||
|
name: 'id',
|
||||||
|
description: 'Channel id or invite code',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
schema: {
|
||||||
|
default: '0029Va4K0PZ5a245NkngBA2M',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Preview channel messages',
|
||||||
|
description:
|
||||||
|
'You can use either ' +
|
||||||
|
'invite code (https://www.whatsapp.com/channel/123) or (123)' +
|
||||||
|
'OR' +
|
||||||
|
'Channel ID (123@newsletter).',
|
||||||
|
})
|
||||||
|
async previewChannelMessages(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') codeOrId: string,
|
||||||
|
@Query() query: PreviewChannelMessages,
|
||||||
|
): Promise<ChannelMessage[]> {
|
||||||
|
let code = null;
|
||||||
|
if (isJidNewsletter(codeOrId)) {
|
||||||
|
const channel = await session.channelsGetChannel(codeOrId);
|
||||||
|
code = parseChannelInviteLink(channel.invite);
|
||||||
|
}
|
||||||
|
const inviteCode = parseChannelInviteLink(code || codeOrId);
|
||||||
|
return session.previewChannelMessages(inviteCode, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/follow')
|
||||||
|
@SessionApiParam
|
||||||
|
@NewsletterIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Follow the channel.' })
|
||||||
|
follow(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<void> {
|
||||||
|
return session.channelsFollowChannel(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/unfollow')
|
||||||
|
@SessionApiParam
|
||||||
|
@NewsletterIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Unfollow the channel.' })
|
||||||
|
unfollow(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<void> {
|
||||||
|
return session.channelsUnfollowChannel(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/mute')
|
||||||
|
@SessionApiParam
|
||||||
|
@NewsletterIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Mute the channel.' })
|
||||||
|
mute(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<void> {
|
||||||
|
return session.channelsMuteChannel(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/unmute')
|
||||||
|
@SessionApiParam
|
||||||
|
@NewsletterIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Unmute the channel.' })
|
||||||
|
unmute(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<void> {
|
||||||
|
return session.channelsUnmuteChannel(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/search/by-view')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@SessionApiParam
|
||||||
|
@UsePipes(new WAHAValidationPipe())
|
||||||
|
@ApiOperation({ summary: 'Search for channels (by view)' })
|
||||||
|
async searchByView(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() request: ChannelSearchByView,
|
||||||
|
): Promise<ChannelListResult> {
|
||||||
|
return session.searchChannelsByView(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/search/by-text')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@SessionApiParam
|
||||||
|
@UsePipes(new WAHAValidationPipe())
|
||||||
|
@ApiOperation({ summary: 'Search for channels (by text)' })
|
||||||
|
async searchByText(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() request: ChannelSearchByText,
|
||||||
|
): Promise<ChannelListResult> {
|
||||||
|
return session.searchChannelsByText(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/search/views')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get list of views for channel search' })
|
||||||
|
getSearchViews(): Promise<ChannelView[]> {
|
||||||
|
return this.channelsInfoService.getViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/search/countries')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get list of countries for channel search' })
|
||||||
|
getSearchCountries(): Promise<ChannelCountry[]> {
|
||||||
|
return this.channelsInfoService.getCountries();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/search/categories')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get list of categories for channel search' })
|
||||||
|
getSearchCategories(): Promise<ChannelCategory[]> {
|
||||||
|
return this.channelsInfoService.getCategories();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
NotFoundException,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
UsePipes,
|
||||||
|
ValidationPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
|
||||||
|
import { MessageIdApiParam } from '@waha/nestjs/params/MessageIdApiParam';
|
||||||
|
import {
|
||||||
|
SessionApiParam,
|
||||||
|
WorkingSessionParam,
|
||||||
|
} from '@waha/nestjs/params/SessionApiParam';
|
||||||
|
|
||||||
|
import { SessionManager } from '../core/abc/manager.abc';
|
||||||
|
import { WhatsappSession } from '../core/abc/session.abc';
|
||||||
|
import {
|
||||||
|
ChatPictureQuery,
|
||||||
|
ChatPictureResponse,
|
||||||
|
ChatSummary,
|
||||||
|
GetChatMessageQuery,
|
||||||
|
GetChatMessagesFilter,
|
||||||
|
GetChatMessagesQuery,
|
||||||
|
GetChatsOverviewParams,
|
||||||
|
GetChatsParams,
|
||||||
|
MessageSortField,
|
||||||
|
OverviewBodyRequest,
|
||||||
|
OverviewFilter,
|
||||||
|
PinMessageRequest,
|
||||||
|
ReadChatMessagesQuery,
|
||||||
|
ReadChatMessagesResponse,
|
||||||
|
transformAck,
|
||||||
|
} from '../structures/chats.dto';
|
||||||
|
import { EditMessageRequest } from '../structures/chatting.dto';
|
||||||
|
import { SortOrder } from '@waha/structures/pagination.dto';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/:session/chats')
|
||||||
|
@ApiTags('💬 Chats')
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true }))
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
|
||||||
|
class ChatsController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Get('')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get chats' })
|
||||||
|
getChats(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Query() pagination: GetChatsParams,
|
||||||
|
) {
|
||||||
|
return session.getChats(pagination);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('overview')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary:
|
||||||
|
'Get chats overview. Includes all necessary things to build UI "your chats overview" page - chat id, name, picture, last message. Sorting by last message timestamp',
|
||||||
|
})
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
getChatsOverview(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Query() pagination: GetChatsOverviewParams,
|
||||||
|
@Query() filter: OverviewFilter,
|
||||||
|
): Promise<ChatSummary[]> {
|
||||||
|
return session.getChatsOverview(pagination, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('overview')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary:
|
||||||
|
'Get chats overview. Use POST if you have too many "ids" params - GET can limit it',
|
||||||
|
})
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
postChatsOverview(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() body: OverviewBodyRequest,
|
||||||
|
): Promise<ChatSummary[]> {
|
||||||
|
return session.getChatsOverview(body.pagination, body.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':chatId')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Deletes the chat' })
|
||||||
|
@ChatIdApiParam
|
||||||
|
deleteChat(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
) {
|
||||||
|
return session.deleteChat(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':chatId/picture')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Gets chat picture' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async getChatPicture(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
@Query() query: ChatPictureQuery,
|
||||||
|
): Promise<ChatPictureResponse> {
|
||||||
|
const url = await session.getContactProfilePicture(chatId, query.refresh);
|
||||||
|
return { url: url };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':chatId/messages')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Gets messages in the chat' })
|
||||||
|
@ChatIdApiParam
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
getChatMessages(
|
||||||
|
@Query() query: GetChatMessagesQuery,
|
||||||
|
@Query() filter: GetChatMessagesFilter,
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
) {
|
||||||
|
if (query.sortBy == MessageSortField.MESSAGE_TIMESTAMP) {
|
||||||
|
query.sortBy = MessageSortField.TIMESTAMP;
|
||||||
|
}
|
||||||
|
query.sortBy = query.sortBy || MessageSortField.TIMESTAMP;
|
||||||
|
query.sortOrder = query.sortOrder || SortOrder.DESC;
|
||||||
|
filter = transformAck(filter);
|
||||||
|
return session.getChatMessages(chatId, query, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':chatId/messages/read')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Read unread messages in the chat' })
|
||||||
|
@ChatIdApiParam
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
readChatMessages(
|
||||||
|
@Query() query: ReadChatMessagesQuery,
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
): Promise<ReadChatMessagesResponse> {
|
||||||
|
return session.readChatMessages(chatId, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':chatId/messages/:messageId')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Gets message by id' })
|
||||||
|
@ChatIdApiParam
|
||||||
|
async getChatMessage(
|
||||||
|
@Query() query: GetChatMessageQuery,
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
@Param('messageId') messageId: string,
|
||||||
|
) {
|
||||||
|
const message = await session.getChatMessage(chatId, messageId, query);
|
||||||
|
if (!message) {
|
||||||
|
throw new NotFoundException('Message not found');
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':chatId/messages/:messageId/pin')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Pins a message in the chat' })
|
||||||
|
@ChatIdApiParam
|
||||||
|
async pinMessage(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
@Param('messageId') messageId: string,
|
||||||
|
@Body() body: PinMessageRequest,
|
||||||
|
) {
|
||||||
|
const result = await session.pinMessage(chatId, messageId, body.duration);
|
||||||
|
return { success: result };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':chatId/messages/:messageId/unpin')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Unpins a message in the chat' })
|
||||||
|
@ChatIdApiParam
|
||||||
|
async unpinMessage(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
@Param('messageId') messageId: string,
|
||||||
|
) {
|
||||||
|
const result = await session.unpinMessage(chatId, messageId);
|
||||||
|
return { success: result };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':chatId/messages')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Clears all messages from the chat' })
|
||||||
|
@ChatIdApiParam
|
||||||
|
clearMessages(
|
||||||
|
@WorkingSessionParam 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(
|
||||||
|
@WorkingSessionParam 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(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
@Param('messageId') messageId: string,
|
||||||
|
@Body() body: EditMessageRequest,
|
||||||
|
) {
|
||||||
|
return session.editMessage(chatId, messageId, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':chatId/archive')
|
||||||
|
@SessionApiParam
|
||||||
|
@ChatIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Archive the chat' })
|
||||||
|
archiveChat(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
) {
|
||||||
|
return session.chatsArchiveChat(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':chatId/unarchive')
|
||||||
|
@SessionApiParam
|
||||||
|
@ChatIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Unarchive the chat' })
|
||||||
|
unarchiveChat(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
) {
|
||||||
|
return session.chatsUnarchiveChat(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':chatId/unread')
|
||||||
|
@SessionApiParam
|
||||||
|
@ChatIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Unread the chat' })
|
||||||
|
unreadChat(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
) {
|
||||||
|
return session.chatsUnreadChat(chatId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ChatsController };
|
||||||
@@ -1,128 +1,350 @@
|
|||||||
import {Body, Controller, Get, NotImplementedException, Post, Query} from '@nestjs/common';
|
|
||||||
import {ApiOperation, ApiTags} from "@nestjs/swagger";
|
|
||||||
import {
|
import {
|
||||||
ChatRequest,
|
Body,
|
||||||
CheckNumberStatusQuery,
|
Controller,
|
||||||
MessageContactVcard,
|
Get,
|
||||||
MessageFile,
|
Post,
|
||||||
MessageImage,
|
Put,
|
||||||
MessageLinkPreview,
|
Query,
|
||||||
MessageLocation,
|
UseGuards,
|
||||||
MessageReply,
|
UsePipes,
|
||||||
MessageText,
|
ValidationPipe,
|
||||||
MessageTextButtons,
|
} from '@nestjs/common';
|
||||||
MessageTextQuery
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
} from "./all.dto";
|
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
|
||||||
import {ensureSuffix, WhatsappSessionManager} from "../whatsapp.service";
|
import {
|
||||||
|
GetChatMessagesFilter,
|
||||||
|
ReadChatMessagesQuery,
|
||||||
|
transformAck,
|
||||||
|
} from '@waha/structures/chats.dto';
|
||||||
|
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
|
||||||
|
import { SendListRequest } from '@waha/structures/chatting.list.dto';
|
||||||
|
|
||||||
|
import { SessionManager } from '../core/abc/manager.abc';
|
||||||
|
import {
|
||||||
|
ChatRequest,
|
||||||
|
CheckNumberStatusQuery,
|
||||||
|
GetMessageQuery,
|
||||||
|
MessageButtonReply,
|
||||||
|
MessageContactVcardRequest,
|
||||||
|
MessageFileRequest,
|
||||||
|
MessageForwardRequest,
|
||||||
|
MessageImageRequest,
|
||||||
|
MessageLinkCustomPreviewRequest,
|
||||||
|
MessageLinkPreviewRequest,
|
||||||
|
MessageLocationRequest,
|
||||||
|
MessagePollRequest,
|
||||||
|
MessagePollVoteRequest,
|
||||||
|
MessageReactionRequest,
|
||||||
|
MessageReplyRequest,
|
||||||
|
MessageStarRequest,
|
||||||
|
MessageTextQuery,
|
||||||
|
MessageTextRequest,
|
||||||
|
MessageVideoRequest,
|
||||||
|
MessageVoiceRequest,
|
||||||
|
SendSeenRequest,
|
||||||
|
WANumberExistResult,
|
||||||
|
} from '../structures/chatting.dto';
|
||||||
|
import { WAMessage } from '../structures/responses.dto';
|
||||||
|
import {
|
||||||
|
mentionsAll,
|
||||||
|
validateRequestMentions,
|
||||||
|
} from '@waha/core/utils/mentions.all';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromBody, FromQuery } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
@Controller('api')
|
@Controller('api')
|
||||||
@ApiTags('chatting')
|
@ApiTags('📤 Chatting')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
export class ChattingController {
|
export class ChattingController {
|
||||||
constructor(private whatsappSessionManager: WhatsappSessionManager) {
|
constructor(private manager: SessionManager) {}
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/checkNumberStatus')
|
@Post('/sendText')
|
||||||
@ApiOperation({summary: 'Check number status'})
|
@ApiOperation({ summary: 'Send a text message' })
|
||||||
async checkNumberStatus(
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
@Query() request: CheckNumberStatusQuery,
|
async sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
|
||||||
) {
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(request.sessionName)
|
if (mentionsAll(request)) {
|
||||||
try {
|
validateRequestMentions(request);
|
||||||
const result = await whatsapp.checkNumberStatus(ensureSuffix(request.phone))
|
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
|
||||||
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}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return whatsapp.sendText(request);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/sendContactVcard')
|
@Post('/sendImage')
|
||||||
sendContactVcard(@Body() message: MessageContactVcard) {
|
@ApiOperation({
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
summary: 'Send an image',
|
||||||
return whatsapp.sendContactVcard(message.chatId, message.contactsId, message.name)
|
description:
|
||||||
|
'Either from an URL or base64 data - look at the request schemas for details.',
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async sendImage(@Body() request: MessageImageRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
if (mentionsAll(request)) {
|
||||||
|
validateRequestMentions(request);
|
||||||
|
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
|
||||||
}
|
}
|
||||||
|
return whatsapp.sendImage(request);
|
||||||
|
}
|
||||||
|
|
||||||
@Get('/sendText')
|
@Post('/sendFile')
|
||||||
@ApiOperation({summary: 'Send a text message'})
|
@ApiOperation({
|
||||||
sendTextGet(
|
summary: 'Send a file',
|
||||||
@Query() message: MessageTextQuery,
|
description:
|
||||||
) {
|
'Either from an URL or base64 data - look at the request schemas for details.',
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
})
|
||||||
return whatsapp.sendText(ensureSuffix(message.phone), message.text)
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async sendFile(@Body() request: MessageFileRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
if (mentionsAll(request)) {
|
||||||
|
validateRequestMentions(request);
|
||||||
|
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
|
||||||
}
|
}
|
||||||
|
return whatsapp.sendFile(request);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/sendText')
|
@Post('/sendVoice')
|
||||||
@ApiOperation({summary: 'Send a text message'})
|
@ApiOperation({
|
||||||
sendText(@Body() message: MessageText) {
|
summary: 'Send an voice message',
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
description:
|
||||||
return whatsapp.sendText(ensureSuffix(message.chatId), message.text)
|
'Either from an URL or base64 data - look at the request schemas for details.',
|
||||||
}
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async sendVoice(@Body() request: MessageVoiceRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.sendVoice(request);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/sendTextButtons')
|
@Post('/sendVideo')
|
||||||
@ApiOperation({summary: 'Send a text message with buttons'})
|
@ApiOperation({
|
||||||
sendTextButtons(@Body() message: MessageTextButtons) {
|
summary: 'Send a video',
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
description:
|
||||||
return whatsapp.sendButtons(ensureSuffix(message.chatId), message.title, message.buttons, message.text)
|
'Either from an URL or base64 data - look at the request schemas for details.',
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async sendVideo(@Body() request: MessageVideoRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
if (mentionsAll(request)) {
|
||||||
|
validateRequestMentions(request);
|
||||||
|
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
|
||||||
}
|
}
|
||||||
|
return whatsapp.sendVideo(request);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/sendLocation')
|
@Post('/send/link-custom-preview')
|
||||||
sendLocation(@Body() message: MessageLocation) {
|
@ApiOperation({
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
summary: 'Send a text message with a CUSTOM link preview.',
|
||||||
return whatsapp.sendLocation(message.chatId, message.latitude, message.longitude, message.title)
|
description:
|
||||||
|
'You can use regular /api/sendText if you wanna send auto-generated link preview.',
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
@UsePipes(new WAHAValidationPipe())
|
||||||
|
async sendLinkCustomPreview(
|
||||||
|
@Body() request: MessageLinkCustomPreviewRequest,
|
||||||
|
): Promise<any> {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
if (!request.text.includes(request.preview.url)) {
|
||||||
|
throw new Error(
|
||||||
|
'"text" must include the URL provided in the "preview.url"',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return whatsapp.sendLinkCustomPreview(request);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/sendLinkPreview')
|
@Post('/sendButtons')
|
||||||
sendLinkPreview(@Body() message: MessageLinkPreview) {
|
@ApiOperation({
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
summary: 'Send buttons message (interactive)',
|
||||||
return whatsapp.sendLinkPreview(message.chatId, message.url, message.title)
|
description: 'Send Buttons',
|
||||||
}
|
deprecated: true,
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
@UsePipes(new WAHAValidationPipe())
|
||||||
|
async sendButtons(@Body() request: SendButtonsRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.sendButtons(request);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/sendImage')
|
@Post('/sendList')
|
||||||
@ApiOperation({summary: 'NOT IMPLEMENTED YET'})
|
@ApiOperation({
|
||||||
sendImage(@Body() message: MessageImage) {
|
summary: 'Send a list message (interactive)',
|
||||||
throw new NotImplementedException();
|
description: 'Send a List message with sections and rows',
|
||||||
// TODO: Accept image URL, download it and then send with path
|
})
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
return whatsapp.sendImage(message.chatId, message.path, message.filename, message.caption)
|
@UsePipes(new WAHAValidationPipe())
|
||||||
}
|
async sendList(@Body() request: SendListRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.sendList(request);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/sendFile')
|
@Post('/forwardMessage')
|
||||||
@ApiOperation({summary: 'NOT IMPLEMENTED YET'})
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
sendFile(@Body() message: MessageFile) {
|
async forwardMessage(
|
||||||
throw new NotImplementedException();
|
@Body() request: MessageForwardRequest,
|
||||||
// TODO: Accept File URL, download it and then send with path
|
): Promise<WAMessage> {
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
return whatsapp.sendFile(message.chatId, message.path, message.filename, message.caption)
|
return await whatsapp.forwardMessage(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/reply')
|
@Post('/sendSeen')
|
||||||
@ApiOperation({summary: 'Reply to a text message'})
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
reply(@Body() message: MessageReply) {
|
async sendSeen(@Body() chat: SendSeenRequest) {
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(message.sessionName)
|
const hasMessageId = chat.messageIds?.length > 0 || Boolean(chat.messageId);
|
||||||
return whatsapp.reply(message.chatId, message.text, message.reply_to)
|
if (!hasMessageId) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(chat.session);
|
||||||
|
const query: ReadChatMessagesQuery = {
|
||||||
|
messages: null,
|
||||||
|
days: 7,
|
||||||
|
};
|
||||||
|
return whatsapp.readChatMessages(chat.chatId, query);
|
||||||
}
|
}
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(chat.session);
|
||||||
|
return whatsapp.sendSeen(chat);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/sendSeen')
|
@Post('/startTyping')
|
||||||
sendSeen(@Body() chat: ChatRequest) {
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(chat.sessionName)
|
async startTyping(@Body() chat: ChatRequest) {
|
||||||
return whatsapp.sendSeen(chat.chatId)
|
// It's infinitive action
|
||||||
}
|
const whatsapp = await this.manager.getWorkingSession(chat.session);
|
||||||
|
await whatsapp.startTyping(chat);
|
||||||
|
return { result: true };
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/startTyping')
|
@Post('/stopTyping')
|
||||||
startTyping(@Body() chat: ChatRequest) {
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
// It's infinitive action
|
async stopTyping(@Body() chat: ChatRequest) {
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(chat.sessionName)
|
const whatsapp = await this.manager.getWorkingSession(chat.session);
|
||||||
whatsapp.startTyping(chat.chatId)
|
await whatsapp.stopTyping(chat);
|
||||||
return true
|
return { result: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/stopTyping')
|
@Put('/reaction')
|
||||||
stopTyping(@Body() chat: ChatRequest) {
|
@ApiOperation({ summary: 'React to a message with an emoji' })
|
||||||
const whatsapp = this.whatsappSessionManager.getSession(chat.sessionName)
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
whatsapp.stopTyping(chat.chatId)
|
async setReaction(@Body() request: MessageReactionRequest) {
|
||||||
return true
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
}
|
return whatsapp.setReaction(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('/star')
|
||||||
|
@ApiOperation({ summary: 'Star or unstar a message' })
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async setStar(@Body() request: MessageStarRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
await whatsapp.setStar(request);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/sendPoll')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Send a poll with options',
|
||||||
|
description: 'You can use it as buttons or list replacement',
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async sendPoll(@Body() request: MessagePollRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.sendPoll(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/sendPollVote')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Vote on a poll',
|
||||||
|
description: 'Cast vote(s) on an existing poll message',
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
@UsePipes(new WAHAValidationPipe())
|
||||||
|
async sendPollVote(@Body() request: MessagePollVoteRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.sendPollVote(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/sendLocation')
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async sendLocation(@Body() request: MessageLocationRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.sendLocation(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/sendContactVcard')
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.sendContactVCard(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/send/buttons/reply')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Reply on a button message',
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async sendButtonsReply(@Body() request: MessageButtonReply) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.sendButtonsReply(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/sendText')
|
||||||
|
@ApiOperation({ summary: 'Send a text message', deprecated: true })
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
|
||||||
|
async sendTextGet(@Query() query: MessageTextQuery) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(query.session);
|
||||||
|
const msg = new MessageTextRequest();
|
||||||
|
msg.chatId = query.phone;
|
||||||
|
msg.text = query.text;
|
||||||
|
return whatsapp.sendText(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/messages')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Get messages in a chat',
|
||||||
|
description: 'DEPRECATED. Use "GET /api/chats/{id}/messages" instead',
|
||||||
|
deprecated: true,
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async getMessages(
|
||||||
|
@Query() query: GetMessageQuery,
|
||||||
|
@Query() filter: GetChatMessagesFilter,
|
||||||
|
) {
|
||||||
|
filter = transformAck(filter);
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(query.session);
|
||||||
|
return whatsapp.getChatMessages(query.chatId, query, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/checkNumberStatus')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Check number status',
|
||||||
|
description: 'DEPRECATED. Use "POST /contacts/check-exists" instead',
|
||||||
|
deprecated: true,
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
|
||||||
|
async DEPRECATED_checkNumberStatus(
|
||||||
|
@Query() request: CheckNumberStatusQuery,
|
||||||
|
): Promise<WANumberExistResult> {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.checkNumberStatus(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/reply')
|
||||||
|
@ApiOperation({
|
||||||
|
summary:
|
||||||
|
'DEPRECATED - you can set "reply_to" field when sending text, image, etc',
|
||||||
|
deprecated: true,
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async reply(@Body() request: MessageReplyRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.reply(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/sendLinkPreview')
|
||||||
|
@ApiOperation({ deprecated: true })
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async sendLinkPreview_DEPRECATED(@Body() request: MessageLinkPreviewRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.sendLinkPreview(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
UsePipes,
|
||||||
|
ValidationPipe,
|
||||||
|
} 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 {
|
||||||
|
ContactProfilePictureQuery,
|
||||||
|
ContactQuery,
|
||||||
|
ContactRequest,
|
||||||
|
ContactsPaginationParams,
|
||||||
|
} from '../structures/contacts.dto';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromBody, FromQuery } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/contacts')
|
||||||
|
@ApiTags('👤 Contacts')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
export class ContactsController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Get('/all')
|
||||||
|
@ApiOperation({ summary: 'Get all contacts' })
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async getAll(
|
||||||
|
@Query() query: SessionQuery,
|
||||||
|
@Query() pagination: ContactsPaginationParams,
|
||||||
|
) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(query.session);
|
||||||
|
return whatsapp.getContacts(pagination);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Get contact basic info',
|
||||||
|
description:
|
||||||
|
'The method always return result, even if the phone number is not registered in WhatsApp. For that - use /contacts/check-exists endpoint below.',
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
|
||||||
|
async get(@Query() query: ContactQuery) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(query.session);
|
||||||
|
return whatsapp.getContact(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/check-exists')
|
||||||
|
@ApiOperation({ summary: 'Check phone number is registered in WhatsApp.' })
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
|
||||||
|
async checkExists(
|
||||||
|
@Query() request: CheckNumberStatusQuery,
|
||||||
|
): Promise<WANumberExistResult> {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.checkNumberStatus(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/about')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Gets the Contact\'s "about" info',
|
||||||
|
description:
|
||||||
|
'Returns null if you do not have permission to read their status.',
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
|
||||||
|
async getAbout(@Query() query: ContactQuery) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(query.session);
|
||||||
|
return whatsapp.getContactAbout(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/profile-picture')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Get contact's profile picture URL",
|
||||||
|
description:
|
||||||
|
'If privacy settings do not allow to get the picture, the method will return null.',
|
||||||
|
})
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async getProfilePicture(@Query() query: ContactProfilePictureQuery) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(query.session);
|
||||||
|
const url = await whatsapp.getContactProfilePicture(
|
||||||
|
query.contactId,
|
||||||
|
query.refresh,
|
||||||
|
);
|
||||||
|
return { profilePictureURL: url };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/block')
|
||||||
|
@ApiOperation({ summary: 'Block contact' })
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async block(@Body() request: ContactRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.blockContact(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/unblock')
|
||||||
|
@ApiOperation({ summary: 'Unblock contact' })
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
|
||||||
|
async unblock(@Body() request: ContactRequest) {
|
||||||
|
const whatsapp = await this.manager.getWorkingSession(request.session);
|
||||||
|
return whatsapp.unblockContact(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Put,
|
||||||
|
UseGuards,
|
||||||
|
UsePipes,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiParam, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||||
|
import { WhatsappSession } from '@waha/core/abc/session.abc';
|
||||||
|
import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
|
||||||
|
import {
|
||||||
|
SessionApiParam,
|
||||||
|
WorkingSessionParam,
|
||||||
|
} from '@waha/nestjs/params/SessionApiParam';
|
||||||
|
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
|
||||||
|
|
||||||
|
import { SessionManager } from '../core/abc/manager.abc';
|
||||||
|
import { Result } from '../structures/base.dto';
|
||||||
|
import { ContactUpdateBody } from '../structures/contacts.dto';
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/:session/contacts')
|
||||||
|
@ApiTags('👤 Contacts')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
|
||||||
|
export class ContactsSessionController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Get('/:id')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiParam({
|
||||||
|
name: 'id',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
description: 'Contact ID',
|
||||||
|
example: '123456789@c.us',
|
||||||
|
})
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Get contact basic info',
|
||||||
|
description:
|
||||||
|
'The method always return result, even if the phone number is not registered in WhatsApp. For that - use /contacts/check-exists endpoint below.',
|
||||||
|
})
|
||||||
|
async get(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
return session.getContact({ session: session.name, contactId: id });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('/:chatId')
|
||||||
|
@SessionApiParam
|
||||||
|
@ChatIdApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Create or update contact',
|
||||||
|
description:
|
||||||
|
'Create or update contact on the phone address book. May not work if you have installed many WhatsApp apps on the same phone',
|
||||||
|
})
|
||||||
|
@UsePipes(new WAHAValidationPipe())
|
||||||
|
async put(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
@Body() body: ContactUpdateBody,
|
||||||
|
): Promise<Result> {
|
||||||
|
await session.upsertContact(chatId, body);
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
UseGuards,
|
||||||
|
UsePipes,
|
||||||
|
ValidationPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { WhatsappSession } from '@waha/core/abc/session.abc';
|
||||||
|
import {
|
||||||
|
SessionApiParam,
|
||||||
|
WorkingSessionParam,
|
||||||
|
} from '@waha/nestjs/params/SessionApiParam';
|
||||||
|
|
||||||
|
import { SessionManager } from '../core/abc/manager.abc';
|
||||||
|
import {
|
||||||
|
EventCancelRequest,
|
||||||
|
EventMessageRequest,
|
||||||
|
} from '../structures/events.dto';
|
||||||
|
import { WAMessage } from '../structures/responses.dto';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/:session/events')
|
||||||
|
@ApiTags('📅 Events')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
|
||||||
|
export class EventsController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: 'Send an event message' })
|
||||||
|
@UsePipes(new ValidationPipe())
|
||||||
|
@SessionApiParam
|
||||||
|
async sendEvent(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() request: EventMessageRequest,
|
||||||
|
): Promise<WAMessage> {
|
||||||
|
return session.sendEvent(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Post(':id/cancel')
|
||||||
|
// @ApiOperation({ summary: 'Cancel an event by ID' })
|
||||||
|
// @UsePipes(new ValidationPipe())
|
||||||
|
// @SessionApiParam
|
||||||
|
// async cancelEvent(
|
||||||
|
// @WorkingSessionParam session: WhatsappSession,
|
||||||
|
// @Param('id') id: string,
|
||||||
|
// ): Promise<WAMessage> {
|
||||||
|
// return session.cancelEvent(id);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import {ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus,} from '@nestjs/common';
|
|
||||||
import {Request, Response} from 'express';
|
|
||||||
|
|
||||||
@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,
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,396 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
UsePipes,
|
||||||
|
ValidationPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { GroupIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
|
||||||
|
import {
|
||||||
|
SessionApiParam,
|
||||||
|
WorkingSessionParam,
|
||||||
|
} from '@waha/nestjs/params/SessionApiParam';
|
||||||
|
import { CountResponse, Result } from '@waha/structures/base.dto';
|
||||||
|
import {
|
||||||
|
ChatPictureQuery,
|
||||||
|
ChatPictureResponse,
|
||||||
|
} from '@waha/structures/chats.dto';
|
||||||
|
import { ProfilePictureRequest } from '@waha/structures/profile.dto';
|
||||||
|
|
||||||
|
import { SessionManager } from '../core/abc/manager.abc';
|
||||||
|
import { parseGroupInviteLink, WhatsappSession } from '../core/abc/session.abc';
|
||||||
|
import {
|
||||||
|
CreateGroupRequest,
|
||||||
|
DescriptionRequest,
|
||||||
|
GroupField,
|
||||||
|
GroupParticipant,
|
||||||
|
GroupsListFields,
|
||||||
|
GroupsPaginationParams,
|
||||||
|
JoinGroupRequest,
|
||||||
|
JoinGroupResponse,
|
||||||
|
ParticipantsRequest,
|
||||||
|
SettingsSecurityChangeInfo,
|
||||||
|
SubjectRequest,
|
||||||
|
} from '../structures/groups.dto';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/:session/groups')
|
||||||
|
@ApiTags('👥 Groups')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
|
||||||
|
export class GroupsController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Post('')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Create a new group.' })
|
||||||
|
createGroup(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() request: CreateGroupRequest,
|
||||||
|
) {
|
||||||
|
return session.createGroup(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('join-info')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get info about the group before joining.' })
|
||||||
|
async joinInfoGroup(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Query() query: JoinGroupRequest,
|
||||||
|
): Promise<any> {
|
||||||
|
const code = parseGroupInviteLink(query.code);
|
||||||
|
return session.joinInfoGroup(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('join')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Join group via code' })
|
||||||
|
async joinGroup(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() request: JoinGroupRequest,
|
||||||
|
): Promise<JoinGroupResponse> {
|
||||||
|
const code = parseGroupInviteLink(request.code);
|
||||||
|
const id = await session.joinGroup(code);
|
||||||
|
return { id: id };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get all groups.' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async getGroups(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Query() pagination: GroupsPaginationParams,
|
||||||
|
@Query() fields: GroupsListFields,
|
||||||
|
) {
|
||||||
|
let groups: any = await session.getGroups(pagination);
|
||||||
|
groups = session.filterGroupsFields(groups, fields);
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/count')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get the number of groups.' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async getGroupsCount(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
): Promise<CountResponse> {
|
||||||
|
const data: any = await session.getGroups({});
|
||||||
|
const groups: any[] = Array.isArray(data) ? data : Object.values(data);
|
||||||
|
return {
|
||||||
|
count: groups.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('refresh')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Refresh groups from the server.' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async refreshGroups(@WorkingSessionParam session: WhatsappSession) {
|
||||||
|
return { success: await session.refreshGroups() };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@GroupIdApiParam
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get the group.' })
|
||||||
|
getGroup(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
return session.getGroup(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Delete the group.' })
|
||||||
|
deleteGroup(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
return session.deleteGroup(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/leave')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Leave the group.' })
|
||||||
|
leaveGroup(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
return session.leaveGroup(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/picture')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Get group picture' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async getChatPicture(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Query() query: ChatPictureQuery,
|
||||||
|
): Promise<ChatPictureResponse> {
|
||||||
|
const url = await session.getContactProfilePicture(id, query.refresh);
|
||||||
|
return { url: url };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id/picture')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Set group picture' })
|
||||||
|
async setPicture(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() request: ProfilePictureRequest,
|
||||||
|
): Promise<Result> {
|
||||||
|
const success = await session.updateGroupPicture(id, request.file);
|
||||||
|
return { success: success ?? true };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id/picture')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Delete group picture' })
|
||||||
|
async deletePicture(
|
||||||
|
@Param('id') id: string,
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
): Promise<Result> {
|
||||||
|
const success = await session.updateGroupPicture(id, null);
|
||||||
|
return { success: success || true };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id/description')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Updates the group description.',
|
||||||
|
description:
|
||||||
|
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
|
||||||
|
})
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
setDescription(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() request: DescriptionRequest,
|
||||||
|
) {
|
||||||
|
return session.setDescription(id, request.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id/subject')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Updates the group subject',
|
||||||
|
description:
|
||||||
|
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
|
||||||
|
})
|
||||||
|
setSubject(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() request: SubjectRequest,
|
||||||
|
) {
|
||||||
|
return session.setSubject(id, request.subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id/settings/security/info-admin-only')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Updates the group "info admin only" settings.',
|
||||||
|
description:
|
||||||
|
'You can allow only admins to edit group info (title, description, photo).',
|
||||||
|
})
|
||||||
|
setInfoAdminOnly(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() request: SettingsSecurityChangeInfo,
|
||||||
|
) {
|
||||||
|
return session.setInfoAdminsOnly(id, request.adminsOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/settings/security/info-admin-only')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Get the group's 'info admin only' settings.",
|
||||||
|
description:
|
||||||
|
'You can allow only admins to edit group info (title, description, photo).',
|
||||||
|
})
|
||||||
|
getInfoAdminOnly(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<SettingsSecurityChangeInfo> {
|
||||||
|
return session.getInfoAdminsOnly(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id/settings/security/messages-admin-only')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Update settings - who can send messages',
|
||||||
|
description:
|
||||||
|
'Updates the group settings to only allow admins to send messages.',
|
||||||
|
})
|
||||||
|
setMessagesAdminOnly(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() request: SettingsSecurityChangeInfo,
|
||||||
|
) {
|
||||||
|
return session.setMessagesAdminsOnly(id, request.adminsOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/settings/security/messages-admin-only')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Get settings - who can send messages',
|
||||||
|
description: 'The group settings to only allow admins to send messages.',
|
||||||
|
})
|
||||||
|
getMessagesAdminOnly(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<SettingsSecurityChangeInfo> {
|
||||||
|
return session.getMessagesAdminsOnly(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/invite-code')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Gets the invite code for the group.' })
|
||||||
|
getInviteCode(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<string> {
|
||||||
|
return session.getInviteCode(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/invite-code/revoke')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary:
|
||||||
|
'Invalidates the current group invite code and generates a new one.',
|
||||||
|
})
|
||||||
|
revokeInviteCode(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<string> {
|
||||||
|
return session.revokeInviteCode(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/participants/')
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Get participants' })
|
||||||
|
getParticipants(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
return session.getParticipants(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/participants/v2')
|
||||||
|
@GroupIdApiParam
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get group participants.' })
|
||||||
|
getGroupParticipants(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<GroupParticipant[]> {
|
||||||
|
return session.getGroupParticipants(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/participants/add')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Add participants' })
|
||||||
|
addParticipants(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() request: ParticipantsRequest,
|
||||||
|
) {
|
||||||
|
return session.addParticipants(id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/participants/remove')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Remove participants',
|
||||||
|
})
|
||||||
|
removeParticipants(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() request: ParticipantsRequest,
|
||||||
|
) {
|
||||||
|
return session.removeParticipants(id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/admin/promote')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Promote participants to admin users.' })
|
||||||
|
promoteToAdmin(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() request: ParticipantsRequest,
|
||||||
|
) {
|
||||||
|
return session.promoteParticipantsToAdmin(id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id/admin/demote')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@SessionApiParam
|
||||||
|
@GroupIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Demotes participants to regular users.' })
|
||||||
|
demoteToAdmin(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() request: ParticipantsRequest,
|
||||||
|
) {
|
||||||
|
return session.demoteParticipantsToUser(id, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { HealthCheck } from '@nestjs/terminus';
|
||||||
|
|
||||||
|
import { WAHAHealthCheckService } from '../core/abc/WAHAHealthCheckService';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanServer } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('health')
|
||||||
|
@ApiTags('🔍 Observability')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanServer(Action.Manage))
|
||||||
|
export class HealthController {
|
||||||
|
constructor(private wahaHealth: WAHAHealthCheckService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@HealthCheck()
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Check the health of the server',
|
||||||
|
description:
|
||||||
|
"Perform all health checks and return the server's health status.",
|
||||||
|
})
|
||||||
|
async check() {
|
||||||
|
return this.wahaHealth.check();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
NotFoundException,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
UnprocessableEntityException,
|
||||||
|
UseGuards,
|
||||||
|
UsePipes,
|
||||||
|
ValidationPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { WhatsappSession } from '@waha/core/abc/session.abc';
|
||||||
|
import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
|
||||||
|
import {
|
||||||
|
SessionApiParam,
|
||||||
|
WorkingSessionParam,
|
||||||
|
} from '@waha/nestjs/params/SessionApiParam';
|
||||||
|
import {
|
||||||
|
Label,
|
||||||
|
LabelBody,
|
||||||
|
SetLabelsRequest,
|
||||||
|
} from '@waha/structures/labels.dto';
|
||||||
|
import * as lodash from 'lodash';
|
||||||
|
|
||||||
|
import { SessionManager } from '../core/abc/manager.abc';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/:session/labels')
|
||||||
|
@ApiTags('🏷️ Labels')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
|
||||||
|
export class LabelsController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Get('/')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get all labels' })
|
||||||
|
getAll(@WorkingSessionParam session: WhatsappSession): Promise<Label[]> {
|
||||||
|
return session.getLabels();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Create a new label' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async create(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() body: LabelBody,
|
||||||
|
): Promise<Label> {
|
||||||
|
const labelDto = body.toDTO();
|
||||||
|
|
||||||
|
const labels = await session.getLabels();
|
||||||
|
if (labels.length >= 20) {
|
||||||
|
throw new UnprocessableEntityException('Maximum 20 labels allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelByName = lodash.find(labels, { name: labelDto.name });
|
||||||
|
if (labelByName) {
|
||||||
|
throw new UnprocessableEntityException(
|
||||||
|
`Label with '${labelByName.name}' already exists, id: ${labelByName.id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return session.createLabel(labelDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('/:labelId')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Update a label' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async update(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('labelId') labelId: string,
|
||||||
|
@Body() body: LabelBody,
|
||||||
|
): Promise<Label> {
|
||||||
|
const label = await session.getLabel(labelId);
|
||||||
|
if (!label) {
|
||||||
|
throw new NotFoundException('Label not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelDto = body.toDTO();
|
||||||
|
const labels = await session.getLabels();
|
||||||
|
|
||||||
|
const labelByName = lodash.find(labels, { name: labelDto.name });
|
||||||
|
if (labelByName) {
|
||||||
|
throw new UnprocessableEntityException(
|
||||||
|
`Label with '${labelByName.name}' already exists, id: ${labelByName.id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
label.name = labelDto.name;
|
||||||
|
label.color = labelDto.color;
|
||||||
|
label.colorHex = Label.toHex(label.color);
|
||||||
|
await session.updateLabel(label);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('/:labelId')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Delete a label' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async delete(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('labelId') labelId: string,
|
||||||
|
): Promise<any> {
|
||||||
|
const label = await session.getLabel(labelId);
|
||||||
|
if (!label) {
|
||||||
|
throw new NotFoundException('Label not found');
|
||||||
|
}
|
||||||
|
await session.deleteLabel(label);
|
||||||
|
return { result: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/chats/:chatId')
|
||||||
|
@SessionApiParam
|
||||||
|
@ChatIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Get labels for the chat' })
|
||||||
|
getChatLabels(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
): Promise<Label[]> {
|
||||||
|
return session.getChatLabels(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('/chats/:chatId')
|
||||||
|
@SessionApiParam
|
||||||
|
@ChatIdApiParam
|
||||||
|
@ApiOperation({ summary: 'Save labels for the chat' })
|
||||||
|
putChatLabels(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('chatId') chatId: string,
|
||||||
|
@Body() request: SetLabelsRequest,
|
||||||
|
) {
|
||||||
|
return session.putLabelsToChat(chatId, request.labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/:labelId/chats')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get chats by label' })
|
||||||
|
getChatsByLabel(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('labelId') labelId: string,
|
||||||
|
) {
|
||||||
|
return session.getChatsByLabelId(labelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Query,
|
||||||
|
UnprocessableEntityException,
|
||||||
|
UseGuards,
|
||||||
|
UsePipes,
|
||||||
|
ValidationPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { WhatsappSession } from '@waha/core/abc/session.abc';
|
||||||
|
import {
|
||||||
|
SessionApiParam,
|
||||||
|
WorkingSessionParam,
|
||||||
|
} from '@waha/nestjs/params/SessionApiParam';
|
||||||
|
import { CountResponse } from '@waha/structures/base.dto';
|
||||||
|
import {
|
||||||
|
LidsListQueryParams,
|
||||||
|
LidToPhoneNumber,
|
||||||
|
} from '@waha/structures/lids.dto';
|
||||||
|
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
|
||||||
|
|
||||||
|
import { SessionManager } from '../core/abc/manager.abc';
|
||||||
|
import { isLidUser } from '@waha/core/utils/jids';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/:session/lids')
|
||||||
|
@ApiTags('👤 Contacts')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
|
||||||
|
export class LidsController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Get('/')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get all known lids to phone number mapping' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async getAll(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Query() params: LidsListQueryParams,
|
||||||
|
): Promise<Array<LidToPhoneNumber>> {
|
||||||
|
// Always lid
|
||||||
|
const pagination: PaginationParams = params;
|
||||||
|
pagination.sortBy = 'lid';
|
||||||
|
pagination.sortOrder = SortOrder.ASC;
|
||||||
|
const lids = await session.getAllLids(pagination);
|
||||||
|
return lids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/count')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get the number of known lids' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async getLidsCount(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
): Promise<CountResponse> {
|
||||||
|
const count = await session.getLidsCount();
|
||||||
|
return {
|
||||||
|
count: count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/:lid')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get phone number by lid' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async findPNByLid(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('lid') lid: string,
|
||||||
|
): Promise<LidToPhoneNumber> {
|
||||||
|
if (!lid.includes('@')) {
|
||||||
|
lid = lid + '@lid';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLidUser(lid)) {
|
||||||
|
throw new UnprocessableEntityException(
|
||||||
|
'Invalid LID - it must end with @lid',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = await session.findPNByLid(lid);
|
||||||
|
result.pn = result.pn || null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/pn/:phoneNumber')
|
||||||
|
@SessionApiParam
|
||||||
|
@ApiOperation({ summary: 'Get lid by phone number (chat id)' })
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async findLIDByPhoneNumber(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Param('phoneNumber') phoneNumber: string,
|
||||||
|
): Promise<LidToPhoneNumber> {
|
||||||
|
if (isLidUser(phoneNumber)) {
|
||||||
|
return {
|
||||||
|
lid: phoneNumber,
|
||||||
|
pn: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await session.findLIDByPhoneNumber(phoneNumber);
|
||||||
|
result.lid = result.lid || null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Post,
|
||||||
|
UnprocessableEntityException,
|
||||||
|
UseGuards,
|
||||||
|
UseInterceptors,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||||
|
import { WAMimeType } from '@waha/core/media/WAMimeType';
|
||||||
|
import { ApiFileAcceptHeader } from '@waha/nestjs/ApiFileAcceptHeader';
|
||||||
|
import {
|
||||||
|
SessionApiParam,
|
||||||
|
WorkingSessionParam,
|
||||||
|
} from '@waha/nestjs/params/SessionApiParam';
|
||||||
|
import {
|
||||||
|
FileDTO,
|
||||||
|
VideoFileDTO,
|
||||||
|
VoiceFileDTO,
|
||||||
|
} from '@waha/structures/media.dto';
|
||||||
|
|
||||||
|
import { WhatsappSession } from '../core/abc/session.abc';
|
||||||
|
import { BufferResponseInterceptor } from '../nestjs/BufferResponseInterceptor';
|
||||||
|
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||||
|
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
|
||||||
|
import { CanSession, FromParam } from '@waha/core/auth/policies';
|
||||||
|
|
||||||
|
import { Action } from '@waha/core/auth/casl.types';
|
||||||
|
|
||||||
|
@ApiSecurity('api_key')
|
||||||
|
@Controller('api/:session/media')
|
||||||
|
@ApiTags('🖼️ Media')
|
||||||
|
@UseGuards(PoliciesGuard)
|
||||||
|
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
|
||||||
|
class MediaController {
|
||||||
|
constructor(private manager: SessionManager) {}
|
||||||
|
|
||||||
|
@Post('convert/voice')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Convert voice to WhatsApp format (opus)',
|
||||||
|
})
|
||||||
|
@SessionApiParam
|
||||||
|
@UseInterceptors(
|
||||||
|
new BufferResponseInterceptor(WAMimeType.VOICE, 'output.opus'),
|
||||||
|
)
|
||||||
|
@ApiFileAcceptHeader(WAMimeType.VOICE)
|
||||||
|
async convertVoice(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() file: VoiceFileDTO,
|
||||||
|
): Promise<Buffer> {
|
||||||
|
const data = await this.buffer(session, file);
|
||||||
|
const content = await session.mediaConverter.voice(data);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('convert/video')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Convert video to WhatsApp format (mp4)',
|
||||||
|
})
|
||||||
|
@SessionApiParam
|
||||||
|
@UseInterceptors(
|
||||||
|
new BufferResponseInterceptor(WAMimeType.VIDEO, 'output.mp4'),
|
||||||
|
)
|
||||||
|
@ApiFileAcceptHeader(WAMimeType.VIDEO)
|
||||||
|
async convertVideo(
|
||||||
|
@WorkingSessionParam session: WhatsappSession,
|
||||||
|
@Body() file: VideoFileDTO,
|
||||||
|
): Promise<Buffer> {
|
||||||
|
const data = await this.buffer(session, file);
|
||||||
|
const content = await session.mediaConverter.video(data);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async buffer(
|
||||||
|
session: WhatsappSession,
|
||||||
|
file: FileDTO,
|
||||||
|
): Promise<Buffer> {
|
||||||
|
if ('url' in file) {
|
||||||
|
return session.fetch(file.url);
|
||||||
|
} else if ('data' in file) {
|
||||||
|
return Buffer.from(file.data, 'base64');
|
||||||
|
} else {
|
||||||
|
throw new UnprocessableEntityException(
|
||||||
|
'Either "url" or "data" must be specified.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { MediaController };
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { PingResponse } from '@waha/structures/ping.dto';
|
||||||
|
|
||||||
|
@Controller('ping')
|
||||||
|
@ApiTags('🔍 Observability')
|
||||||
|
export class PingController {
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Ping the server',
|
||||||
|
description: 'Check if the server is alive and responding to requests.',
|
||||||
|
})
|
||||||
|
ping(): PingResponse {
|
||||||
|
return { message: 'pong' };
|
||||||
|
}
|
||||||
|
}
|
||||||