mirror of
https://github.com/veganhacktivists/arabot.git
synced 2025-12-02 10:50:02 +01:00
Compare commits
606 Commits
github-dep
...
topbal
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ada16c485e | ||
|
|
285a3fefbc | ||
|
|
9758e91701 | ||
|
|
330b4bd8a6 | ||
|
|
ab66a8b5c0 | ||
|
|
72a9593c85 | ||
|
|
573fbe0c09 | ||
|
|
e936fe49b1 | ||
|
|
b39cf0b44d | ||
|
|
940c25a5ed | ||
|
|
81f5db4b0a | ||
|
|
8bc3b2dd4f | ||
|
|
cba2115080 | ||
|
|
f7b1512935 | ||
|
|
2aab5a514e | ||
|
|
5d93db6365 | ||
|
|
1a8b6eb0c1 | ||
|
|
c2b0753232 | ||
|
|
07c5d85b15 | ||
|
|
dfd4ab7d26 | ||
|
|
81c01aede8 | ||
|
|
e06bde4540 | ||
|
|
f8c7267f26 | ||
|
|
7aaffda339 | ||
|
|
3752f57af0 | ||
|
|
c56a9521a5 | ||
|
|
e529cfd98f | ||
|
|
dd7c975db7 | ||
|
|
c1a35eeb89 | ||
|
|
39d0043937 | ||
|
|
4b999f0a5c | ||
|
|
95d6093ae6 | ||
|
|
768ac13f8b | ||
|
|
d27871e0f7 | ||
|
|
871696bf89 | ||
|
|
73da43ab0a | ||
|
|
2b1a06f315 | ||
|
|
74f910385a | ||
|
|
2d2c01180a | ||
|
|
860d203c16 | ||
|
|
dda946ebf3 | ||
|
|
7027fe6431 | ||
|
|
bc88d5c41c | ||
|
|
eb1cf81d64 | ||
|
|
584126cacb | ||
|
|
6d9e25164e | ||
|
|
c7e66ea9da | ||
|
|
6a99e00a36 | ||
|
|
a6f05a3235 | ||
|
|
598d7559df | ||
|
|
6f64baba7b | ||
|
|
88321cc8fb | ||
|
|
9d84045226 | ||
|
|
96dad137ab | ||
|
|
004e49829d | ||
|
|
1c2fc11e47 | ||
|
|
c2b4b014dc | ||
|
|
37527d46fc | ||
|
|
a2deb04cf9 | ||
|
|
0475929e84 | ||
|
|
f0ec5712f9 | ||
|
|
f5c9392b5e | ||
|
|
89defb9390 | ||
|
|
e42d2b5537 | ||
|
|
c2bace00b3 | ||
|
|
7250b4cf2b | ||
|
|
9d7f7fa542 | ||
|
|
0f8af4bee8 | ||
|
|
2aaf0fddf0 | ||
|
|
8c825d5132 | ||
|
|
9434185659 | ||
|
|
ece946b9a8 | ||
|
|
19226bab08 | ||
|
|
a2a351e2c3 | ||
|
|
22efb5c40d | ||
|
|
9bf61807b4 | ||
|
|
312eeba01f | ||
|
|
49237c1f92 | ||
|
|
868121767c | ||
|
|
4ed3a08dac | ||
|
|
b64ba697c3 | ||
|
|
184853c363 | ||
|
|
48f678dbbd | ||
|
|
6fab3822bb | ||
|
|
15f34d68ab | ||
|
|
c0f3b657bc | ||
|
|
190f927df3 | ||
|
|
fe9f04dd68 | ||
|
|
f19545b988 | ||
|
|
fd17c38b08 | ||
|
|
3b25622730 | ||
|
|
88f94bc3fc | ||
|
|
544f14b175 | ||
|
|
d15e46b770 | ||
|
|
09135a0f61 | ||
|
|
29df8bf2c4 | ||
|
|
d42cc6be0b | ||
|
|
7ec4fc2909 | ||
|
|
97e3ca2bc3 | ||
|
|
1dbfff38bc | ||
|
|
a2404ae404 | ||
|
|
592c1c05cc | ||
|
|
49a5430cc0 | ||
|
|
fb5d963af8 | ||
|
|
c9a39ecfc2 | ||
|
|
1cd27444d8 | ||
|
|
9a469c269c | ||
|
|
d493947579 | ||
|
|
9019116973 | ||
|
|
63e6d64ba2 | ||
|
|
e08127e4c2 | ||
|
|
c4f33edcf5 | ||
|
|
da76ba0477 | ||
|
|
d6f590d245 | ||
|
|
652d57614f | ||
|
|
9713487496 | ||
|
|
8940498bb2 | ||
|
|
bc5a5e979e | ||
|
|
541e4c49e6 | ||
|
|
d6c88744a9 | ||
|
|
186ce8dfef | ||
|
|
ac61cd8b15 | ||
|
|
19d7bd8db7 | ||
|
|
258af69cd1 | ||
|
|
234b12cbb6 | ||
|
|
0c2f8ef655 | ||
|
|
0680897431 | ||
|
|
ac798c1c70 | ||
|
|
8558a8b2a4 | ||
|
|
fdf13e91d2 | ||
|
|
dc779bcbad | ||
|
|
bdb0b9e1d1 | ||
|
|
68ba798150 | ||
|
|
c5ad86409b | ||
|
|
21080ac92c | ||
|
|
2829ebfc3b | ||
|
|
86ba0d537d | ||
|
|
9e96e73e09 | ||
|
|
6efa57734a | ||
|
|
53438a5c1f | ||
|
|
91221dccda | ||
|
|
1752dc37b4 | ||
|
|
7348404582 | ||
|
|
760cbc6ecd | ||
|
|
3dc82f7d47 | ||
|
|
0b68a7c524 | ||
|
|
90207acaae | ||
|
|
098e6de85f | ||
|
|
4830c0498a | ||
|
|
e0a8e7161b | ||
|
|
3fda2d7aec | ||
|
|
137e3d4738 | ||
|
|
4e75cffe5d | ||
|
|
65ee043ea4 | ||
|
|
cbffb884c3 | ||
|
|
1c46c84ad6 | ||
|
|
797d9dbff1 | ||
|
|
126c7445db | ||
|
|
2cc0e5572b | ||
|
|
afb5e8f013 | ||
|
|
fbae131e4e | ||
|
|
92b6c2da38 | ||
|
|
26fe5a79eb | ||
|
|
634752a472 | ||
|
|
4f7dc865b0 | ||
|
|
7b74595bbe | ||
|
|
53b474c36a | ||
|
|
c2469433ed | ||
|
|
9dea07ccd3 | ||
|
|
ade11e5abd | ||
|
|
dad062d69a | ||
|
|
78418308dd | ||
|
|
44215af3c1 | ||
|
|
b32c7954bd | ||
|
|
857319e136 | ||
|
|
d3594be3ef | ||
|
|
d43e05b82a | ||
|
|
707134a12e | ||
|
|
0e0b8b2d02 | ||
|
|
c6628140ac | ||
|
|
d7fd0753a5 | ||
|
|
528f592733 | ||
|
|
f1453d5691 | ||
|
|
f304b9ecac | ||
|
|
f5be80807e | ||
|
|
649d7ebde7 | ||
|
|
e7084017b7 | ||
|
|
f49857125b | ||
|
|
f76cc38df0 | ||
|
|
304f5996fb | ||
|
|
e48db66f0c | ||
|
|
d6a796eebd | ||
|
|
6f276d5083 | ||
|
|
da0e231760 | ||
|
|
dcacffe908 | ||
|
|
390af56fa5 | ||
|
|
21a53a1a75 | ||
|
|
60c4b8f0f3 | ||
|
|
c8521d0c06 | ||
|
|
3c3f74e0ee | ||
|
|
e7432ae777 | ||
|
|
82e7f0f699 | ||
|
|
0736bb8e38 | ||
|
|
267d44d535 | ||
|
|
a42819ea86 | ||
|
|
41255f66c2 | ||
|
|
0a1ce0fbc8 | ||
|
|
80f38b2ca5 | ||
|
|
15f2f710a9 | ||
|
|
12b8f7016e | ||
|
|
45c07dbb11 | ||
|
|
3fe499174e | ||
|
|
c7af73cec2 | ||
|
|
1870f75517 | ||
|
|
ed693f9597 | ||
|
|
ddb826c84e | ||
|
|
22a19da926 | ||
|
|
232cbc4b67 | ||
|
|
873afcd4d6 | ||
|
|
6bfe57c135 | ||
|
|
d98f714477 | ||
|
|
c7fe49b4e3 | ||
|
|
de94b88be1 | ||
|
|
b404a26bdc | ||
|
|
80d90256d5 | ||
|
|
086d1d50c6 | ||
|
|
bede3cadbe | ||
|
|
50c823380c | ||
|
|
6700ad8674 | ||
|
|
ad594aa63f | ||
|
|
97d6d14d8a | ||
|
|
e39812649a | ||
|
|
005c190582 | ||
|
|
158c76c17e | ||
|
|
74ae558b0f | ||
|
|
62647c769c | ||
|
|
c77a6141a5 | ||
|
|
e4791f3d22 | ||
|
|
aa28f7a4cf | ||
|
|
6a96772c25 | ||
|
|
cd3c209ede | ||
|
|
1ee75d684f | ||
|
|
ab56b5192a | ||
|
|
dec6de8398 | ||
|
|
4e815c2d31 | ||
|
|
7aa9ca8b44 | ||
|
|
c55d5ffe93 | ||
|
|
26ea2ba996 | ||
|
|
76f8aa369c | ||
|
|
8070430976 | ||
|
|
da8088e157 | ||
|
|
3baa0e2081 | ||
|
|
e07871cb30 | ||
|
|
843b8cdfb9 | ||
|
|
e3e72ce483 | ||
|
|
5b5994eb47 | ||
|
|
68cff288e0 | ||
|
|
2909d5cc8d | ||
|
|
00cd92ac82 | ||
|
|
e1fd0eed83 | ||
|
|
b6bd3c6e80 | ||
|
|
7421052f44 | ||
|
|
87eca4f572 | ||
|
|
1067572441 | ||
|
|
0ebf9ade2f | ||
|
|
14ba1b27a3 | ||
|
|
a812712706 | ||
|
|
d872e964b5 | ||
|
|
7f62103c9b | ||
|
|
20ae7ce153 | ||
|
|
d5a3562f4f | ||
|
|
7196637b8a | ||
|
|
83724288d3 | ||
|
|
7a1cf06715 | ||
|
|
b859e4b617 | ||
|
|
9fa412b15e | ||
|
|
37c25e925e | ||
|
|
3e40b81d11 | ||
|
|
a5792ce8a2 | ||
|
|
ed85099d37 | ||
|
|
5fb9ae39a0 | ||
|
|
d681d7c183 | ||
|
|
4d0ffe5dcd | ||
|
|
039b84c6b5 | ||
|
|
db8871d205 | ||
|
|
8e7cc9037d | ||
|
|
e465c201b5 | ||
|
|
b87c28061a | ||
|
|
e0c609cf69 | ||
|
|
f8e99ef4f2 | ||
|
|
4770bf0307 | ||
|
|
07b5a6fcd8 | ||
|
|
cfc29fc384 | ||
|
|
edf0d0817a | ||
|
|
80f2107d32 | ||
|
|
9b402a435b | ||
|
|
905d9f4811 | ||
|
|
4c5e0238b8 | ||
|
|
ed3a2719f2 | ||
|
|
ab1cea2729 | ||
|
|
1533fdeeb5 | ||
|
|
23b4250e0a | ||
|
|
141d07c01c | ||
|
|
4f37da9a71 | ||
|
|
0fb0f96498 | ||
|
|
f76cfcec0d | ||
|
|
8cd8128723 | ||
|
|
969616301d | ||
|
|
ced9851574 | ||
|
|
dbd25b796b | ||
|
|
a8691c0587 | ||
|
|
cd00be8147 | ||
|
|
c60d8807a2 | ||
|
|
72c27f5b72 | ||
|
|
e21bf55fc7 | ||
|
|
a979c85079 | ||
|
|
a134dbccc4 | ||
|
|
0ef4025d00 | ||
|
|
7de8b22e0e | ||
|
|
4bb38415bd | ||
|
|
dbc359d11b | ||
|
|
9990db84b2 | ||
|
|
f6149a60f5 | ||
|
|
ff6f22005f | ||
|
|
c4075fe9f9 | ||
|
|
6979a0e79c | ||
|
|
a7c2541d89 | ||
|
|
f8a120b135 | ||
|
|
e57fa26f6b | ||
|
|
946b8e4440 | ||
|
|
12d4445428 | ||
|
|
a45a297d2f | ||
|
|
7260e9c32b | ||
|
|
06a05d4240 | ||
|
|
a38677c4e3 | ||
|
|
ea58ecc261 | ||
|
|
62e3c698fb | ||
|
|
6cb73b2a4b | ||
|
|
5503edd70b | ||
|
|
dda77483eb | ||
|
|
4d75f72989 | ||
|
|
de0fc48ba8 | ||
|
|
0b3d57d2da | ||
|
|
ef164125ea | ||
|
|
2833c54c75 | ||
|
|
922dd7556e | ||
|
|
94f88d882d | ||
|
|
cd8ecd527b | ||
|
|
48125ba896 | ||
|
|
e8fac57b72 | ||
|
|
62fd98bbe6 | ||
|
|
cc642708aa | ||
|
|
dc523b2db1 | ||
|
|
319049bc2b | ||
|
|
f3fa1398e8 | ||
|
|
dadaacd636 | ||
|
|
204090c39d | ||
|
|
64c9f75714 | ||
|
|
6dc3c1a2f9 | ||
|
|
77f3e4666f | ||
|
|
3cd0756d88 | ||
|
|
ec075f8927 | ||
|
|
63a320535c | ||
|
|
dab4f1767c | ||
|
|
daa427482a | ||
|
|
eb345d1866 | ||
|
|
d0d9dbacfd | ||
|
|
58f81eaac2 | ||
|
|
b4a3816649 | ||
|
|
c3dbd42d36 | ||
|
|
dd008c1ea5 | ||
|
|
ba5cebc78a | ||
|
|
be3dacd344 | ||
|
|
0a161122d7 | ||
|
|
36bd0eb7a9 | ||
|
|
7dde12f456 | ||
|
|
6e802f8dcd | ||
|
|
39da42541f | ||
|
|
ca1511a87b | ||
|
|
e6e4a288ba | ||
|
|
71cc4d461c | ||
|
|
7d54976cfd | ||
|
|
c60ba056ff | ||
|
|
eba68015db | ||
|
|
e544fa2eae | ||
|
|
9e833b87fd | ||
|
|
3632aaa276 | ||
|
|
383e644318 | ||
|
|
b57a25454b | ||
|
|
9ed1141d09 | ||
|
|
ba99a6be4a | ||
|
|
a259d8a067 | ||
|
|
de1d800341 | ||
|
|
51678a5701 | ||
|
|
a79cc496d8 | ||
|
|
733a6609a8 | ||
|
|
63f161b2d1 | ||
|
|
a8250eb514 | ||
|
|
c0da7cb1fa | ||
|
|
1bacef1f08 | ||
|
|
e89fcb5314 | ||
|
|
5ed15e6206 | ||
|
|
c0c1c16c37 | ||
|
|
cf0078ad25 | ||
|
|
dedd72c731 | ||
|
|
d7aea2c6c1 | ||
|
|
c735509f57 | ||
|
|
ecf75b63e7 | ||
|
|
0e31061d99 | ||
|
|
b14de47934 | ||
|
|
4b6dde3af2 | ||
|
|
2339ae6784 | ||
|
|
9a1a2a6852 | ||
|
|
80025b54ab | ||
|
|
fa0d9c2a60 | ||
|
|
91631995a9 | ||
|
|
6f31b21feb | ||
|
|
8d1276ab2c | ||
|
|
a44c804097 | ||
|
|
fcc10c392e | ||
|
|
80f201a1ad | ||
|
|
8d83365c8a | ||
|
|
4f628bb59e | ||
|
|
a047ade28f | ||
|
|
6e4607161b | ||
|
|
96233914a2 | ||
|
|
d8e7a48ee7 | ||
|
|
f728e14163 | ||
|
|
8e7ed8442c | ||
|
|
879c8bde77 | ||
|
|
937e874d66 | ||
|
|
45c19824c5 | ||
|
|
a78403278a | ||
|
|
0bc49cb8f1 | ||
|
|
09bb3f24f8 | ||
|
|
9a91d081e6 | ||
|
|
0e33019a98 | ||
|
|
67eb01781f | ||
|
|
7127d1dfe8 | ||
|
|
479db1a1c6 | ||
|
|
f2952d15e9 | ||
|
|
8c395784d0 | ||
|
|
2880283e9a | ||
|
|
c6d44083e7 | ||
|
|
4fe6420cb2 | ||
|
|
8dfa7a7072 | ||
|
|
d4c11febc1 | ||
|
|
db3c16b2b2 | ||
|
|
9d487fcbf6 | ||
|
|
abca50ff8e | ||
|
|
be0b57890d | ||
|
|
5879c1a610 | ||
|
|
03f6310caf | ||
|
|
1114190012 | ||
|
|
bf16170871 | ||
|
|
1e62fdf540 | ||
|
|
3b8b478a4c | ||
|
|
7187ce2f6a | ||
|
|
505c105a6a | ||
|
|
f044b6f23d | ||
|
|
6fe75b197d | ||
|
|
4db0d8aaf1 | ||
|
|
38390f7e59 | ||
|
|
f4135d0fcb | ||
|
|
9f01fcd542 | ||
|
|
b1a756aea6 | ||
|
|
9ada008da5 | ||
|
|
2b9cb907cc | ||
|
|
83a5fdee8d | ||
|
|
3fd2e8acdd | ||
|
|
eef7c9f953 | ||
|
|
58a3a28e4d | ||
|
|
442822dcf2 | ||
|
|
88b7ef269d | ||
|
|
3771c037a6 | ||
|
|
1ddda61bd0 | ||
|
|
24b0ca274a | ||
|
|
9496e838f4 | ||
|
|
9aa669e4c5 | ||
|
|
e5dfa51cc9 | ||
|
|
f6b321e454 | ||
|
|
341fb22833 | ||
|
|
9dc3efd0ef | ||
|
|
26ed6cb6a8 | ||
|
|
cec0d64109 | ||
|
|
203dc6bfdd | ||
|
|
357322c3b8 | ||
|
|
4f8cec5331 | ||
|
|
275da768dd | ||
|
|
d90b89d4ac | ||
|
|
c42d612ed4 | ||
|
|
270d6ca91f | ||
|
|
a6cf9db523 | ||
|
|
37e37011ad | ||
|
|
baaa52aa14 | ||
|
|
990d0f0dfa | ||
|
|
a943d5798e | ||
|
|
1c5ec53b3b | ||
|
|
534b45f5db | ||
|
|
9d3f687473 | ||
|
|
556038ee80 | ||
|
|
722f0a53f1 | ||
|
|
d42c4ce1cb | ||
|
|
7df01742be | ||
|
|
c408dab5a5 | ||
|
|
51082db025 | ||
|
|
a40925e9e9 | ||
|
|
16fb194cd3 | ||
|
|
b1df7b4001 | ||
|
|
c55f2cd077 | ||
|
|
488e256f30 | ||
|
|
222ec5b8b3 | ||
|
|
28e06a3b88 | ||
|
|
ac50b96ba8 | ||
|
|
ecd5a692c1 | ||
|
|
4e041f102e | ||
|
|
8c06bb6b88 | ||
|
|
4d819d87d2 | ||
|
|
f389479e9a | ||
|
|
0d4e498d79 | ||
|
|
d2e4e7c167 | ||
|
|
d771827b6d | ||
|
|
e5218d9c25 | ||
|
|
f80fd422c7 | ||
|
|
fa476f06be | ||
|
|
ab0e6c5adf | ||
|
|
15fe1a38a1 | ||
|
|
6e69039767 | ||
|
|
bcf72388ab | ||
|
|
eddf0bcec6 | ||
|
|
0873e4ad40 | ||
|
|
c0e5bae1e7 | ||
|
|
a545a52ba7 | ||
|
|
8a66fa39be | ||
|
|
4331f1a644 | ||
|
|
49f18bd10c | ||
|
|
57b17d6e28 | ||
|
|
bb117f3c23 | ||
|
|
d460826c43 | ||
|
|
92ebf948f0 | ||
|
|
a8efff5871 | ||
|
|
989915b796 | ||
|
|
3c0472eccc | ||
|
|
6f694d94fb | ||
|
|
4314e3a083 | ||
|
|
44d913b863 | ||
|
|
7f3f31d9f0 | ||
|
|
e0207fdd8d | ||
|
|
9cc7e53ea9 | ||
|
|
1993682484 | ||
|
|
24afe3c4b2 | ||
|
|
bb19a17522 | ||
|
|
70854915af | ||
|
|
ee27bb7582 | ||
|
|
79c6614f8d | ||
|
|
34dc689f94 | ||
|
|
0749d81586 | ||
|
|
8ee0b924fa | ||
|
|
effedbdfaf | ||
|
|
8dc1c99995 | ||
|
|
915f7d7048 | ||
|
|
0829e7c996 | ||
|
|
a893e13d57 | ||
|
|
cae9091aa1 | ||
|
|
35de24399a | ||
|
|
5a5afbfb02 | ||
|
|
f89d6c0730 | ||
|
|
63bce0d405 | ||
|
|
544d6bafd8 | ||
|
|
0043fdddfb | ||
|
|
b71a7ef2ea | ||
|
|
a4d15f67ab | ||
|
|
6af8373f2b | ||
|
|
801297c3a1 | ||
|
|
4deb7efc56 | ||
|
|
41c23ebb3c | ||
|
|
39c32101c5 | ||
|
|
3dd219ae04 | ||
|
|
75b768046d | ||
|
|
2b96d507eb | ||
|
|
c71b787c8b | ||
|
|
b567559438 | ||
|
|
e0f1bb6853 | ||
|
|
2d9fddba9e | ||
|
|
4e326e3e6d | ||
|
|
b2d11b6627 | ||
|
|
4883ec12a1 | ||
|
|
300ad9a903 | ||
|
|
ba638bb6e2 | ||
|
|
092dcce2fe | ||
|
|
8d39539fef | ||
|
|
0d571dd923 | ||
|
|
30729a565f | ||
|
|
e0893a2bab | ||
|
|
2abc957284 | ||
|
|
f0b0408a71 | ||
|
|
ed5327aae2 | ||
|
|
31199d9cd5 | ||
|
|
4e856913e9 | ||
|
|
1c4cf5a5f6 | ||
|
|
e4cb0bdfdc | ||
|
|
0ebf6af089 | ||
|
|
c0a0e10547 | ||
|
|
7a2543cea1 | ||
|
|
ac5860d10c | ||
|
|
5539277196 |
@@ -10,5 +10,9 @@ POSTGRES_USER=USERNAME
|
||||
POSTGRES_PASSWORD=PASSWORD
|
||||
POSTGRES_DB=DB
|
||||
|
||||
# Redis
|
||||
REDIS_URL= # URL to redis database (if running everything within docker compose, use "redis")
|
||||
BULLMQ_URL # URL for redis database, but without redis:// and credentials
|
||||
|
||||
# Database URL (designed for Postgres, but designed on Prisma)
|
||||
DATABASE_URL= # "postgresql://USERNAME:PASSWORD@postgres:5432/DB?schema=ara&sslmode=prefer"
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"airbnb-typescript/base"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module",
|
||||
"project": "tsconfig.json"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"class-methods-use-this": "off"
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module",
|
||||
"project": "tsconfig.json"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"class-methods-use-this": "off"
|
||||
},
|
||||
"settings": {
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".ts", ".tsx"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -4,7 +4,6 @@ about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
@@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -4,7 +4,6 @@ about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
5
.github/renovate.json
vendored
Normal file
5
.github/renovate.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base"],
|
||||
"schedule": ["after 12pm and before 6pm on Saturday"]
|
||||
}
|
||||
61
.github/workflows/codeql-analysis.yml
vendored
61
.github/workflows/codeql-analysis.yml
vendored
@@ -9,14 +9,14 @@
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
name: 'CodeQL'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ['main']
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "main" ]
|
||||
branches: ['main']
|
||||
schedule:
|
||||
- cron: '37 11 * * 2'
|
||||
|
||||
@@ -32,41 +32,40 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
language: ['javascript']
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
8
.github/workflows/eslint.yml
vendored
8
.github/workflows/eslint.yml
vendored
@@ -11,10 +11,10 @@ name: ESLint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ['main']
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "main" ]
|
||||
branches: ['main']
|
||||
schedule:
|
||||
- cron: '27 13 * * 1'
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install ESLint
|
||||
run: |
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
run: npx eslint .
|
||||
--config .eslintrc.json
|
||||
--ext .js,.jsx,.ts,.tsx
|
||||
--format @microsoft/eslint-formatter-sarif
|
||||
--format @microsoft/eslint-formatter-sarif
|
||||
--output-file eslint-results.sarif
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
28
.github/workflows/prettier.yml
vendored
Normal file
28
.github/workflows/prettier.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Prettier
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: ['main']
|
||||
schedule:
|
||||
- cron: '27 13 * * 1'
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
name: Run prettier scanning
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Prettier
|
||||
run: |
|
||||
npm install prettier@3.1.0
|
||||
|
||||
- name: Run Prettier
|
||||
run: npx prettier . --check
|
||||
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
dist
|
||||
node_modules
|
||||
3
.prettierrc.json
Normal file
3
.prettierrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
FROM node:18-buster
|
||||
FROM node:20
|
||||
|
||||
WORKDIR /opt/app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
COPY --chown=node:node package.json .
|
||||
COPY --chown=node:node package-lock.json .
|
||||
COPY --chown=node:node tsconfig.json .
|
||||
@@ -11,6 +13,8 @@ RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npx prisma generate
|
||||
|
||||
RUN npm run build
|
||||
|
||||
RUN chown node:node /opt/app/
|
||||
|
||||
@@ -14,7 +14,6 @@ Make sure to create the .env file, which you can use the [.env.example](.env.exa
|
||||
|
||||
There are 2 options for running this bot, one using docker-compose and the other, less desirable npm.
|
||||
|
||||
|
||||
### Docker
|
||||
|
||||
Running the bot Dockerised makes everything easier. To run the bot, run:
|
||||
@@ -28,11 +27,13 @@ docker-compose up -d
|
||||
Make sure to run `npm install` if you just cloned the repo.
|
||||
|
||||
Then make sure to compile the TypeScript files using
|
||||
|
||||
```shell
|
||||
npm run build
|
||||
```
|
||||
|
||||
If you are running the code for the first time with a new database, make sure to run `npm run start:migrate`, otherwise run:
|
||||
|
||||
```shell
|
||||
npm start
|
||||
```
|
||||
@@ -66,4 +67,4 @@ For support, feel free to send an email to anthony@aramovement.org or reach out
|
||||
|
||||
This bot is free and open source. It licensed using [GPL v3](LICENSE).
|
||||
|
||||
Well done on making it to the bottom of the README file :)
|
||||
Well done on making it to the bottom of the README file :)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
version: "3.7"
|
||||
version: '3.7'
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14
|
||||
image: postgres:15
|
||||
container_name: postgres
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
networks:
|
||||
- arabot
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
@@ -17,19 +19,28 @@ services:
|
||||
- .env
|
||||
volumes:
|
||||
- redis:/data
|
||||
networks:
|
||||
- arabot
|
||||
|
||||
node:
|
||||
arabot:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: arabot
|
||||
restart: always
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
- arabot
|
||||
|
||||
volumes:
|
||||
postgres:
|
||||
name: arabot-db
|
||||
redis:
|
||||
name: arabot-redis
|
||||
|
||||
networks:
|
||||
arabot:
|
||||
|
||||
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
|
||||
12
docs/COMMANDS.md
Normal file
12
docs/COMMANDS.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Commands for the bot
|
||||
|
||||
These are all the commands that you can use for the bot. Some of these are for staff only, but everyone can use the
|
||||
general commands
|
||||
|
||||
## Contents
|
||||
|
||||
- [General](commands/GENERAL.md)
|
||||
- [Moderator](commands/MOD.md)
|
||||
- [Verifier](commands/VERIFIER.md)
|
||||
- [Mentor](commands/MENTOR.md)
|
||||
- [Coordinator](commands/COORDINATOR.md)
|
||||
@@ -1,13 +1,12 @@
|
||||
# Commit Message Format
|
||||
# Commit Message Format
|
||||
|
||||
*This specification is a modified version of the [AngularJS commit message format](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#-commit-message-format).*
|
||||
_This specification is a modified version of the [AngularJS commit message format](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#-commit-message-format)._
|
||||
|
||||
We have very precise rules over how our Git commit messages must be formatted.
|
||||
This format leads to **easier to read commit history**.
|
||||
|
||||
Each commit message consists of a **header**, a **body**, and a **footer**.
|
||||
|
||||
|
||||
```
|
||||
<header>
|
||||
<BLANK LINE>
|
||||
@@ -23,7 +22,6 @@ When the body is present it must be at least 20 characters long and must conform
|
||||
|
||||
The `footer` is optional. The [Commit Message Footer](#commit-footer) format describes what the footer is used for and the structure it must have.
|
||||
|
||||
|
||||
#### <a name="commit-header"></a>Commit Message Header
|
||||
|
||||
```
|
||||
@@ -38,42 +36,39 @@ The `footer` is optional. The [Commit Message Footer](#commit-footer) format des
|
||||
|
||||
The `<type>` and `<summary>` fields are mandatory, the `(<scope>)` field is optional.
|
||||
|
||||
|
||||
##### Type
|
||||
|
||||
Must be one of the following:
|
||||
|
||||
* **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
* **ci**: Changes to our CI configuration files and scripts
|
||||
* **docs**: Documentation only changes
|
||||
* **feat**: A new feature
|
||||
* **fix**: A bug fix
|
||||
* **perf**: A code change that improves performance
|
||||
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
* **test**: Adding missing tests or correcting existing tests
|
||||
|
||||
- **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
- **ci**: Changes to our CI configuration files and scripts
|
||||
- **docs**: Documentation only changes
|
||||
- **feat**: A new feature
|
||||
- **fix**: A bug fix
|
||||
- **perf**: A code change that improves performance
|
||||
- **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
- **test**: Adding missing tests or correcting existing tests
|
||||
|
||||
##### Scope
|
||||
|
||||
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages).
|
||||
|
||||
The following is the list of supported scopes:
|
||||
|
||||
* `arabot`
|
||||
* `config`
|
||||
* `compiler`
|
||||
* `database`
|
||||
* `docs`
|
||||
* `upgrade`
|
||||
|
||||
- `arabot`
|
||||
- `config`
|
||||
- `compiler`
|
||||
- `database`
|
||||
- `docs`
|
||||
- `upgrade`
|
||||
|
||||
##### Summary
|
||||
|
||||
Use the summary field to provide a succinct description of the change:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize the first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
- use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
- don't capitalize the first letter
|
||||
- no dot (.) at the end
|
||||
|
||||
#### <a name="commit-body"></a>Commit Message Body
|
||||
|
||||
@@ -82,7 +77,6 @@ Just as in the summary, use the imperative, present tense: "fix" not "fixed" nor
|
||||
Explain the motivation for the change in the commit message body. This commit message should explain _why_ you are making the change.
|
||||
You can include a comparison of the previous behavior with the new behavior in order to illustrate the impact of the change.
|
||||
|
||||
|
||||
#### <a name="commit-footer"></a>Commit Message Footer
|
||||
|
||||
The footer can contain information about breaking changes and deprecations and is also the place to reference GitHub issues, Jira tickets, and other PRs that this commit closes or is related to.
|
||||
@@ -112,7 +106,6 @@ Breaking Change section should start with the phrase "BREAKING CHANGE: " followe
|
||||
|
||||
Similarly, a Deprecation section should start with "DEPRECATED: " followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path.
|
||||
|
||||
|
||||
### Revert commits
|
||||
|
||||
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit.
|
||||
|
||||
47
docs/commands/COORDINATOR.md
Normal file
47
docs/commands/COORDINATOR.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Coordinator Commands
|
||||
|
||||
## General Coordinator Commands
|
||||
|
||||
- `/access <permission> <channel> <user/role>` - Gives/removes specific access to a user/role from a channel in
|
||||
ModMail/Private/Restricted categories.
|
||||
- `/anonymous <message> <optional: channel>`/`?anon <channel> <message>` - Sends a message via the bot (optionally if
|
||||
channel has been defined, to that channel).
|
||||
- `/clear <messages>`/`?clear <messages>` - Bulk deletes 1-100 messages.
|
||||
- `/moveall <channel>`/`?mvall <channel>` - Moves everyone in the current voice channel to the specified voice channel.
|
||||
- `/plus <user>`/`?plus <user>` - Gives/removes the Plus role from the user. This role is for members that are 18+.
|
||||
|
||||
## Private Channels
|
||||
|
||||
- `/private create <user>` - Creates a private channel for that user for the specific coordinator team you are on.
|
||||
- `/private delete <optional: user>` - Deletes the private channel, either the channel you are currently in or if
|
||||
specified the optional channel, the channel you declared. You can only delete your specific coordinator team's private
|
||||
channels.
|
||||
|
||||
## Diversity Team
|
||||
|
||||
- `/diversity <user>`/`?div <user>` - Gives/removes the Diversity Team role from the user.
|
||||
- `/diversity toggleopen` - toggles the diversity chat open or closed.
|
||||
|
||||
## Events Team
|
||||
|
||||
- `/stagehost <user>`/`?stagehost <user>` - Gives/removes the Stage Host role from the user.
|
||||
- `/bookclub <user>`/`?bookclub <user>` - Gives/removes the Book Club role from the user.
|
||||
- `/debatehost <user>`/`?debatehost <user>` - Gives/removes the Debate Host role from the user.
|
||||
- `/gamenight <user>`/`?gamenight <user>` - Gives/removes the Game Night Host role from the user.
|
||||
- `/guest <user>`/`?guest <user>` - Gives/removes the Guest role from the user.
|
||||
|
||||
## Mentor Team
|
||||
|
||||
- `/mentor <user>`/`?mentor <user>` - Gives/removes the Mentor role from the user.
|
||||
|
||||
## Verification Team
|
||||
|
||||
- `/vegan <user>`/`?v <user>` - Gives/removes the Vegan role from the user.
|
||||
- `/activist <user>`/`?a <user>` - Gives/removes the Activist role from the user.
|
||||
- `/verifier <user>`/`?verifier <user>` - Gives/removes the Verifier role from the user.
|
||||
- `/trialverifier <user>`/`?trialverifier <user>` - Gives/removes the Trial Verifier role from the user.
|
||||
|
||||
## Mod Team
|
||||
|
||||
- `/mod <user>`/`?mod <user>` - Gives/removes the Mod role from the user.
|
||||
- `/restrictedaccess <user>`/`?ra <user>` - Gives/removes the Restricted Access role from the user.
|
||||
33
docs/commands/GENERAL.md
Normal file
33
docs/commands/GENERAL.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# General commands
|
||||
|
||||
## Utilities
|
||||
|
||||
- `/ping`/`?ping` - Checks if the bot is alive and the ping of the bot.
|
||||
- `/apply`/`?apply` - Gives you the link to where you can apply to be a Moderator or Verifier.
|
||||
- `/count`/`?count` - Tells you how many vegans and non-vegans there are on the server.
|
||||
- `/info <info> <optional: visible>` - Gives you information based on what you pick in `<info>`. If you make `visible`
|
||||
true, it will make the information visible to everyone.
|
||||
- `/help`/`?help` - Gives a link (here) to all the commands.
|
||||
|
||||
## Economy
|
||||
|
||||
- `/daily`/`?daily` - Gives you a daily reward of ARAs
|
||||
- `/pay <user> <amount> <reason>`/`?pay <user> <amount> <reason>` - Give a user an amount of ARAs
|
||||
- `/balance`/`?balance` - Checks how many ARAs you have
|
||||
- `/topbalances`/`?topbalances` - Displays the richest server members
|
||||
|
||||
## XP
|
||||
|
||||
- `/rank <optional: user>`/`?rank <optional: user>` - Shows your rank based on the amount of XP you have. If you provide
|
||||
a user, it will display that user's rank instead.
|
||||
|
||||
## Fun Commands
|
||||
|
||||
- `/1984`
|
||||
- `/happy`
|
||||
- `/hug`
|
||||
- `/kill`
|
||||
- `/poke`
|
||||
- `/sad`
|
||||
- `/shrug`
|
||||
- `/cringe`
|
||||
4
docs/commands/MENTOR.md
Normal file
4
docs/commands/MENTOR.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Mentor Commands
|
||||
|
||||
- `/vegcurious <user>`/`?veg <user>` - Gives/removes the Veg Curious role from the user (only usable on non-vegans).
|
||||
- `/convinced <user>`/`?conv <user>` - Gives/removes the Convinced role from the user.
|
||||
50
docs/commands/MOD.md
Normal file
50
docs/commands/MOD.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Moderation Commands
|
||||
|
||||
## General Moderation
|
||||
|
||||
- `/rename <user> <optional: nickname>`/`?ru <user> <optional: nickname>` - Renames that user to the specified nickname,
|
||||
if nickname is left blank it will reset the nickname to their original username.
|
||||
- `/slowmode <duration>`/`?slowmode <duration>` - changes the slowmode for a chat. Duration uses a time which can be set by providing a number
|
||||
followed by s/d/m/y or just writing `off`. These can be combined.
|
||||
For example to set the slowmode for 1 minute and 30 seconds, you would write: `1m 30s`.
|
||||
- `/softmute <user>`/`?sm/softmute <user>` - Prevents the user from reacting to messages.
|
||||
- `/vcmute <user>`/`?vcmute <user>` - Adds a persistent VC mute if the user has left the VC or leaves the server to
|
||||
circumvent VC mutes.
|
||||
- `?warn <user> <reason>` - Gives a warning to the user.
|
||||
|
||||
## Roles
|
||||
|
||||
These are roles you can give/take away from users.
|
||||
|
||||
- `/trusted <user>`/`?t <user>` - Gives/removes the Trusted role from the user.
|
||||
- `/vegcurious <user>`/`?veg <user>` - Gives/removes the Veg Curious role from the user (only usable on non-vegans).
|
||||
- `/convinced <user>`/`?conv <user>` - Gives/removes the Convinced role from the user.
|
||||
|
||||
## Sus
|
||||
|
||||
This command stores notes on users that could be information on the user
|
||||
or anything that is not serious enough for a warning.
|
||||
|
||||
- `/sus add <user> <note>`/`?sus <user> <note>` - Add a note to the user
|
||||
- `/sus view <user>` - View notes made on the user
|
||||
- `/sus remove <id>` - Remove a specific note from the sus note id, which you can get from using `/sus view`
|
||||
- `/sus purge <user>` - Remove all sus notes from the user
|
||||
|
||||
## Restrictions
|
||||
|
||||
These are used for users that have broken rules severe enough that takes away their access to the server.
|
||||
|
||||
- `/restrict <user> <reason>`/`?r/restrict <user> <reason>` - Restricts the user to the restricted section
|
||||
- `/unrestrict <user>`/`?ur <user>` - Unrestricts the user
|
||||
- `/restrictlogs <optional: user>` - Shows the logs of when the user has been restricted. The need to provide the user
|
||||
is optional depending on if the command is run in the ModMail category.
|
||||
- `/restricttools channel delete <optional: user>` - Deletes the vegan restricted channel for the user. Providing a user
|
||||
is only optional if the command is run in the channel that is to be deleted.
|
||||
|
||||
## Bans
|
||||
|
||||
- `/tempban <user> <duration> <reason>`/`?tempban <user> <duration <reason>` - Bans the user for a specific amount of
|
||||
time. Duration uses a time which can be set by providing a number followed by s/d/m/y. These can be combined.
|
||||
For example to ban someone for 1 week and 3 days, you would write: `1w 3d`.
|
||||
- `/ban <user> <reason>`/`?ban <user> <reason>` - Permanently bans that user.
|
||||
- `/unban <user>`/`?unban <user>` - Unbans that user.
|
||||
32
docs/commands/VERIFIER.md
Normal file
32
docs/commands/VERIFIER.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Verifier Commands
|
||||
|
||||
## Verification
|
||||
|
||||
- `/verify <user> <roles>`/`?ver <user> <roles>` - This is a manual verification to give roles to a user. This should
|
||||
not be used if you are verifying the user in the voice channel. Roles available (you can write multiple in one command
|
||||
such as `v a t`):
|
||||
|
||||
- `v` - Vegan
|
||||
- `a` - Activist
|
||||
- `t` - Trusted
|
||||
- `x` - ARA Vegan (went vegan because of ARA)
|
||||
- `nv` - Not Vegan
|
||||
- `conv` - Convinced
|
||||
- `veg` - Veg Curious
|
||||
|
||||
- `/verifytimeoutremove <user>` - Removes a verification timeout if the user has been timed out as a verifier was not
|
||||
available. This cannot be used for users that have been verified.
|
||||
|
||||
## Roles
|
||||
|
||||
These are roles you can give/take away from users.
|
||||
|
||||
- `/trusted <user>`/`?t <user>` - Gives/removes the Trusted role from the user.
|
||||
- `/vegcurious <user>`/`?veg <user>` - Gives/removes the Veg Curious role from the user (only usable on non-vegans).
|
||||
- `/convinced <user>`/`?conv <user>` - Gives/removes the Convinced role from the user.
|
||||
|
||||
### Roles you can only give
|
||||
|
||||
- `/vegan <user>`/`?v <user>` - Give the vegan role
|
||||
- `/activist <user>`/`?a <user>` - Gives the activist role
|
||||
- `/aravegan <user`/`?aravegan <user>` - Gives the ARA vegan role
|
||||
4110
package-lock.json
generated
4110
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "arabot",
|
||||
"version": "0.0.1",
|
||||
"version": "0.4.1",
|
||||
"description": "A Discord bot for Animal Rights Advocates",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
@@ -9,6 +9,9 @@
|
||||
"start": "node dist/index.js",
|
||||
"start:migrate": "prisma migrate deploy && npm run start"
|
||||
},
|
||||
"imports": {
|
||||
"#utils/*": "./dist/utils/*.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/veganhacktivists/arabot.git"
|
||||
@@ -26,31 +29,30 @@
|
||||
},
|
||||
"homepage": "https://github.com/veganhacktivists/arabot#readme",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^1.2.0",
|
||||
"@prisma/client": "^4.0.0",
|
||||
"@sapphire/discord.js-utilities": "^5.0.0",
|
||||
"@sapphire/framework": "^3.1.1",
|
||||
"@sapphire/plugin-scheduled-tasks": "^4.0.0",
|
||||
"@sapphire/plugin-subcommands": "^3.0.0",
|
||||
"@sapphire/stopwatch": "^1.4.1",
|
||||
"@sapphire/ts-config": "^3.3.4",
|
||||
"@sapphire/utilities": "^3.9.2",
|
||||
"@types/node": "^18.0.3",
|
||||
"bullmq": "^1.89.1",
|
||||
"discord-api-types": "^0.33.3",
|
||||
"discord.js": "^13.10.3",
|
||||
"dotenv": "^16.0.1",
|
||||
"prisma": "^4.2.1",
|
||||
"ts-node": "^10.8.2",
|
||||
"typescript": "^4.7.4"
|
||||
"@prisma/client": "^5.5.2",
|
||||
"@sapphire/discord.js-utilities": "^7.0.2",
|
||||
"@sapphire/framework": "^4.7.2",
|
||||
"@sapphire/plugin-logger": "^3.0.6",
|
||||
"@sapphire/plugin-scheduled-tasks": "^8.0.0",
|
||||
"@sapphire/plugin-subcommands": "^5.0.0",
|
||||
"@sapphire/stopwatch": "^1.5.0",
|
||||
"@sapphire/time-utilities": "^1.7.10",
|
||||
"@sapphire/ts-config": "^5.0.0",
|
||||
"@sapphire/utilities": "^3.13.0",
|
||||
"@types/node": "^20.8.9",
|
||||
"bullmq": "^4.12.7",
|
||||
"discord.js": "^14.13.0",
|
||||
"redis": "^4.6.10",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
||||
"@typescript-eslint/parser": "^5.30.7",
|
||||
"eslint": "8.22.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.26.0"
|
||||
"@types/ioredis": "^5.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.9.0",
|
||||
"@typescript-eslint/parser": "^6.9.0",
|
||||
"eslint": "8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"prettier": "3.1.0",
|
||||
"prisma": "^5.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
14
prisma/migrations/20220806153942_/migration.sql
Normal file
14
prisma/migrations/20220806153942_/migration.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `balance` on the `User` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `lastDaily` on the `User` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `level` on the `User` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `xp` on the `User` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" DROP COLUMN "balance",
|
||||
DROP COLUMN "lastDaily",
|
||||
DROP COLUMN "level",
|
||||
DROP COLUMN "xp";
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `channelId` to the `VerifyUnblock` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ADD COLUMN "channelId" TEXT NOT NULL;
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `time` on the `VerifyUnblock` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" DROP COLUMN "time",
|
||||
ADD COLUMN "finishTime" TIMESTAMP(3),
|
||||
ADD COLUMN "startTime" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ADD COLUMN "joinTime" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ALTER COLUMN "startTime" DROP NOT NULL;
|
||||
14
prisma/migrations/20220806202004_change_id/migration.sql
Normal file
14
prisma/migrations/20220806202004_change_id/migration.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `VerifyUnblock` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- You are about to drop the column `channelId` on the `VerifyUnblock` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" DROP CONSTRAINT "Verify_pkey",
|
||||
DROP COLUMN "channelId",
|
||||
ALTER COLUMN "id" DROP DEFAULT,
|
||||
ALTER COLUMN "id" SET DATA TYPE TEXT,
|
||||
ADD CONSTRAINT "Verify_pkey" PRIMARY KEY ("id");
|
||||
DROP SEQUENCE "Verify_id_seq";
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ALTER COLUMN "startTime" DROP DEFAULT;
|
||||
@@ -0,0 +1,10 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ADD COLUMN "activist" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "food" INTEGER,
|
||||
ADD COLUMN "length" INTEGER,
|
||||
ADD COLUMN "life" INTEGER,
|
||||
ADD COLUMN "reason" INTEGER,
|
||||
ADD COLUMN "reasoning" INTEGER,
|
||||
ADD COLUMN "trusted" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "vegCurious" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "where" INTEGER;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ADD COLUMN "convinced" BOOLEAN NOT NULL DEFAULT false;
|
||||
12
prisma/migrations/20221028003502_unban/migration.sql
Normal file
12
prisma/migrations/20221028003502_unban/migration.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `endModId` to the `Ban` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Ban" ADD COLUMN "endModId" TEXT NOT NULL,
|
||||
ADD COLUMN "endTime" TIMESTAMP(3);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Ban" ADD CONSTRAINT "Ban_endModId_fkey" FOREIGN KEY ("endModId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
8
prisma/migrations/20221028011419_unban_fix/migration.sql
Normal file
8
prisma/migrations/20221028011419_unban_fix/migration.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Ban" DROP CONSTRAINT "Ban_endModId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Ban" ALTER COLUMN "endModId" DROP NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Ban" ADD CONSTRAINT "Ban_endModId_fkey" FOREIGN KEY ("endModId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
17
prisma/migrations/20230211001033_vcmute/migration.sql
Normal file
17
prisma/migrations/20230211001033_vcmute/migration.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "VCMute" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"modId" TEXT NOT NULL,
|
||||
"time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"endTime" TIMESTAMP(3),
|
||||
"reason" TEXT,
|
||||
|
||||
CONSTRAINT "VCMute_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VCMute" ADD CONSTRAINT "VCMute_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VCMute" ADD CONSTRAINT "VCMute_modId_fkey" FOREIGN KEY ("modId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
5
prisma/migrations/20230211163443_restrict/migration.sql
Normal file
5
prisma/migrations/20230211163443_restrict/migration.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Restrict" ADD COLUMN "endModId" TEXT;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Restrict" ADD CONSTRAINT "Restrict_endModId_fkey" FOREIGN KEY ("endModId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
15
prisma/migrations/20230211163734_fix_typo/migration.sql
Normal file
15
prisma/migrations/20230211163734_fix_typo/migration.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `endedTime` on the `Restrict` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `endedTime` on the `TempBan` table. All the data in the column will be lost.
|
||||
- Added the required column `endTime` to the `TempBan` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Restrict" DROP COLUMN "endedTime",
|
||||
ADD COLUMN "endTime" TIMESTAMP(3);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "TempBan" DROP COLUMN "endedTime",
|
||||
ADD COLUMN "endTime" TIMESTAMP(3) NOT NULL;
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `section` to the `Restrict` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Restrict" ADD COLUMN "section" INTEGER NOT NULL;
|
||||
17
prisma/migrations/20230214100026_warnings/migration.sql
Normal file
17
prisma/migrations/20230214100026_warnings/migration.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Warnings" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"modId" TEXT NOT NULL,
|
||||
"time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"note" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Warnings_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Warnings" ADD CONSTRAINT "Warnings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Warnings" ADD CONSTRAINT "Warnings_modId_fkey" FOREIGN KEY ("modId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `Warnings` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Warnings" DROP CONSTRAINT "Warnings_modId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Warnings" DROP CONSTRAINT "Warnings_userId_fkey";
|
||||
|
||||
-- RenameTable
|
||||
ALTER TABLE "Warnings" RENAME TO "Warning";
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Warning" ADD CONSTRAINT "Warning_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Warning" ADD CONSTRAINT "Warning_modId_fkey" FOREIGN KEY ("modId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Warning" RENAME CONSTRAINT "Warnings_pkey" TO "Warning_pkey";
|
||||
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "TempBan" ADD COLUMN "endModId" TEXT;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TempBan" ADD CONSTRAINT "TempBan_endModId_fkey" FOREIGN KEY ("endModId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ADD COLUMN "manual" BOOLEAN NOT NULL DEFAULT false;
|
||||
58
prisma/migrations/20230227164244_outreach/migration.sql
Normal file
58
prisma/migrations/20230227164244_outreach/migration.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Event" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"leaderId" TEXT NOT NULL,
|
||||
"startTime" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"endTime" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "Event_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "EventType" (
|
||||
"type" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "EventType_pkey" PRIMARY KEY ("type")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Stat" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"eventId" INTEGER NOT NULL,
|
||||
"leaderId" TEXT NOT NULL,
|
||||
"vegan" INTEGER NOT NULL DEFAULT 0,
|
||||
"considered" INTEGER NOT NULL DEFAULT 0,
|
||||
"antivegan" INTEGER NOT NULL DEFAULT 0,
|
||||
"thanked" INTEGER NOT NULL DEFAULT 0,
|
||||
"documentary" INTEGER NOT NULL DEFAULT 0,
|
||||
"educated" INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT "Stat_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ParticipantStat" (
|
||||
"statId" INTEGER NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "ParticipantStat_pkey" PRIMARY KEY ("statId","userId")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Event" ADD CONSTRAINT "Event_eventType_fkey" FOREIGN KEY ("eventType") REFERENCES "EventType"("type") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Event" ADD CONSTRAINT "Event_leaderId_fkey" FOREIGN KEY ("leaderId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Stat" ADD CONSTRAINT "Stat_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Stat" ADD CONSTRAINT "Stat_leaderId_fkey" FOREIGN KEY ("leaderId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ParticipantStat" ADD CONSTRAINT "ParticipantStat_statId_fkey" FOREIGN KEY ("statId") REFERENCES "Stat"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ParticipantStat" ADD CONSTRAINT "ParticipantStat_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
10
prisma/migrations/20230228200640_stat_role/migration.sql
Normal file
10
prisma/migrations/20230228200640_stat_role/migration.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "StatRole" (
|
||||
"statId" INTEGER NOT NULL,
|
||||
"roleId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "StatRole_pkey" PRIMARY KEY ("statId")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StatRole" ADD CONSTRAINT "StatRole_statId_fkey" FOREIGN KEY ("statId") REFERENCES "Stat"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
12
prisma/migrations/20230301144406_leave_logs/migration.sql
Normal file
12
prisma/migrations/20230301144406_leave_logs/migration.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "LeaveLog" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"roles" TEXT[],
|
||||
|
||||
CONSTRAINT "LeaveLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "LeaveLog" ADD CONSTRAINT "LeaveLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
41
prisma/migrations/20230302130212_economy/migration.sql
Normal file
41
prisma/migrations/20230302130212_economy/migration.sql
Normal file
@@ -0,0 +1,41 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Balance" (
|
||||
"userId" TEXT NOT NULL,
|
||||
"balance" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Balance_pkey" PRIMARY KEY ("userId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Daily" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Daily_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Payment" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"senderId" TEXT NOT NULL,
|
||||
"recipientId" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"reason" TEXT NOT NULL,
|
||||
"time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Payment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Balance" ADD CONSTRAINT "Balance_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Daily" ADD CONSTRAINT "Daily_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
29
prisma/migrations/20230303182446_role_logs/migration.sql
Normal file
29
prisma/migrations/20230303182446_role_logs/migration.sql
Normal file
@@ -0,0 +1,29 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "RoleLog" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"modId" TEXT NOT NULL,
|
||||
"roleId" TEXT NOT NULL,
|
||||
"add" BOOLEAN NOT NULL DEFAULT false,
|
||||
"time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "RoleLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Role" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"staff" BOOLEAN NOT NULL,
|
||||
|
||||
CONSTRAINT "Role_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RoleLog" ADD CONSTRAINT "RoleLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RoleLog" ADD CONSTRAINT "RoleLog_modId_fkey" FOREIGN KEY ("modId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RoleLog" ADD CONSTRAINT "RoleLog_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
13
prisma/migrations/20230304234222_xp/migration.sql
Normal file
13
prisma/migrations/20230304234222_xp/migration.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Xp" (
|
||||
"userId" TEXT NOT NULL,
|
||||
"level" INTEGER NOT NULL DEFAULT 0,
|
||||
"xp" INTEGER NOT NULL DEFAULT 0,
|
||||
"messageCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastMessage" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Xp_pkey" PRIMARY KEY ("userId")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Xp" ADD CONSTRAINT "Xp_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
29
prisma/migrations/20230307144946_fun_logs/migration.sql
Normal file
29
prisma/migrations/20230307144946_fun_logs/migration.sql
Normal file
@@ -0,0 +1,29 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "FunLog" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"sendUserId" TEXT NOT NULL,
|
||||
"receiveUserId" TEXT,
|
||||
"typeId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "FunLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FunType" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "FunType_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FunType_name_key" ON "FunType"("name");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FunLog" ADD CONSTRAINT "FunLog_sendUserId_fkey" FOREIGN KEY ("sendUserId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FunLog" ADD CONSTRAINT "FunLog_receiveUserId_fkey" FOREIGN KEY ("receiveUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FunLog" ADD CONSTRAINT "FunLog_typeId_fkey" FOREIGN KEY ("typeId") REFERENCES "FunType"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Xp" ADD COLUMN "xpToNextLevel" INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `xpToNextLevel` on the `Xp` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Xp" DROP COLUMN "xpToNextLevel",
|
||||
ADD COLUMN "xpForNextLevel" INTEGER NOT NULL DEFAULT 0;
|
||||
11
prisma/migrations/20231111144714_counting/migration.sql
Normal file
11
prisma/migrations/20231111144714_counting/migration.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Counting" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"number" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Counting_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Counting" ADD CONSTRAINT "Counting_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -26,45 +26,218 @@ datasource db {
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @db.VarChar(255)
|
||||
level Int @default(0)
|
||||
xp Int @default(0)
|
||||
balance Int @default(0)
|
||||
lastDaily DateTime?
|
||||
vegan Boolean @default(false)
|
||||
trusted Boolean @default(false)
|
||||
activist Boolean @default(false)
|
||||
plus Boolean @default(false)
|
||||
notVegan Boolean @default(false)
|
||||
vegCurious Boolean @default(false)
|
||||
convinced Boolean @default(false)
|
||||
muted Boolean @default(false)
|
||||
VerifyUser Verify[] @relation("verUser")
|
||||
VerifyVerifier Verify[] @relation("verVerifier")
|
||||
SusUser Sus[] @relation("susUser")
|
||||
SusMod Sus[] @relation("susMod")
|
||||
RestrictUser Restrict[] @relation("restUser")
|
||||
RestrictMod Restrict[] @relation("restMod")
|
||||
BanUser Ban[] @relation("banUser")
|
||||
BanMod Ban[] @relation("banMod")
|
||||
TempBanUser TempBan[] @relation("tbanUser")
|
||||
TempBanMod TempBan[] @relation("tbanMod")
|
||||
id String @id @db.VarChar(255)
|
||||
vegan Boolean @default(false)
|
||||
trusted Boolean @default(false)
|
||||
activist Boolean @default(false)
|
||||
plus Boolean @default(false)
|
||||
notVegan Boolean @default(false)
|
||||
vegCurious Boolean @default(false)
|
||||
convinced Boolean @default(false)
|
||||
muted Boolean @default(false)
|
||||
VerifyUser Verify[] @relation("verUser")
|
||||
VerifyVerifier Verify[] @relation("verVerifier")
|
||||
Xp Xp?
|
||||
Balance Balance?
|
||||
Daily Daily[]
|
||||
SendPayment Payment[] @relation("sendPayment")
|
||||
RecievePayment Payment[] @relation("recievePayment")
|
||||
LeaveLog LeaveLog[]
|
||||
UserRoleLog RoleLog[] @relation("userRoleLog")
|
||||
ModRoleLog RoleLog[] @relation("modRoleLog")
|
||||
FunLogSender FunLog[] @relation("sendFunLog")
|
||||
FunLogReciever FunLog[] @relation("receiveFunLog")
|
||||
Counting Counting[] @relation("counting")
|
||||
EventLeader Event[] @relation("eventLeader")
|
||||
StatLeader Stat[] @relation("statLeader")
|
||||
OutreachParticipation ParticipantStat[] @relation("participantUser")
|
||||
SusUser Sus[] @relation("susUser")
|
||||
SusMod Sus[] @relation("susMod")
|
||||
WarnUser Warning[] @relation("warnUser")
|
||||
WarnMod Warning[] @relation("warnMod")
|
||||
RestrictUser Restrict[] @relation("restUser")
|
||||
RestrictMod Restrict[] @relation("restMod")
|
||||
RestrictEndMod Restrict[] @relation("endRestMod")
|
||||
BanUser Ban[] @relation("banUser")
|
||||
BanMod Ban[] @relation("banMod")
|
||||
BanEndMod Ban[] @relation("endBanMod")
|
||||
TempBanUser TempBan[] @relation("tbanUser")
|
||||
TempBanMod TempBan[] @relation("tbanMod")
|
||||
TempBanEndMod TempBan[] @relation("endTbanMod")
|
||||
VCMuteUser VCMute[] @relation("vcMuteUser")
|
||||
VCMuteMod VCMute[] @relation("vcMuteMod")
|
||||
}
|
||||
|
||||
model Verify {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("verUser", fields: [userId], references: [id])
|
||||
id String @id
|
||||
user User @relation("verUser", fields: [userId], references: [id])
|
||||
userId String
|
||||
verifier User? @relation("verVerifier", fields: [verifierId], references: [id])
|
||||
verifier User? @relation("verVerifier", fields: [verifierId], references: [id])
|
||||
verifierId String?
|
||||
time DateTime @default(now())
|
||||
timedOut Boolean @default(false) // If they got kicked out of verification because they timed out
|
||||
vegan Boolean @default(false) // If they got verified as a vegan
|
||||
text Boolean @default(false) // If they used text verification
|
||||
serverVegan Boolean @default(false) // People that went vegan on the server
|
||||
joinTime DateTime @default(now())
|
||||
startTime DateTime?
|
||||
finishTime DateTime?
|
||||
manual Boolean @default(false) // If they were verified with the verify command
|
||||
timedOut Boolean @default(false) // If they got kicked out of verification because they timed out
|
||||
// complete Boolean @default(false) // If the verification was incomplete
|
||||
// Roles they got from verification
|
||||
vegan Boolean @default(false) // If they got verified as a vegan
|
||||
activist Boolean @default(false) // If they got the activist role when they verified
|
||||
trusted Boolean @default(false) // If they got the trusted role when they verified
|
||||
vegCurious Boolean @default(false) // If they got the Veg Curious role
|
||||
convinced Boolean @default(false)
|
||||
text Boolean @default(false) // If they used text verification
|
||||
serverVegan Boolean @default(false) // People that went vegan on the server
|
||||
// Stats on verification
|
||||
reason Int?
|
||||
where Int?
|
||||
length Int?
|
||||
reasoning Int?
|
||||
life Int?
|
||||
food Int?
|
||||
notes String?
|
||||
}
|
||||
|
||||
model Xp {
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String @id
|
||||
level Int @default(0)
|
||||
xp Int @default(0)
|
||||
xpForNextLevel Int @default(0)
|
||||
messageCount Int @default(0)
|
||||
lastMessage DateTime @default(now())
|
||||
}
|
||||
|
||||
// Economy
|
||||
|
||||
model Balance {
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String @id
|
||||
balance Int
|
||||
}
|
||||
|
||||
model Daily {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
amount Int
|
||||
time DateTime @default(now())
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id Int @id @default(autoincrement())
|
||||
sender User @relation("sendPayment", fields: [senderId], references: [id])
|
||||
senderId String
|
||||
recipient User @relation("recievePayment", fields: [recipientId], references: [id])
|
||||
recipientId String
|
||||
amount Int
|
||||
reason String
|
||||
time DateTime @default(now())
|
||||
}
|
||||
|
||||
// Tracking roles for leaving the server
|
||||
|
||||
model LeaveLog {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
time DateTime @default(now())
|
||||
roles String[]
|
||||
}
|
||||
|
||||
model RoleLog {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("userRoleLog", fields: [userId], references: [id])
|
||||
userId String
|
||||
mod User @relation("modRoleLog", fields: [modId], references: [id])
|
||||
modId String
|
||||
role Role @relation(fields: [roleId], references: [id])
|
||||
roleId String
|
||||
add Boolean @default(false)
|
||||
time DateTime @default(now())
|
||||
}
|
||||
|
||||
model Role {
|
||||
id String @id
|
||||
name String
|
||||
staff Boolean
|
||||
RoleLog RoleLog[]
|
||||
}
|
||||
|
||||
model FunLog {
|
||||
id Int @id @default(autoincrement())
|
||||
sendUser User @relation("sendFunLog", fields: [sendUserId], references: [id])
|
||||
sendUserId String
|
||||
receiveUser User? @relation("receiveFunLog", fields: [receiveUserId], references: [id])
|
||||
receiveUserId String?
|
||||
type FunType @relation(fields: [typeId], references: [id])
|
||||
typeId Int
|
||||
}
|
||||
|
||||
model FunType {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
FunLog FunLog[]
|
||||
}
|
||||
|
||||
model Counting {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("counting", fields: [userId], references: [id])
|
||||
userId String
|
||||
number Int // This is the number that the user has counted, if the count failed, the number should be 0
|
||||
}
|
||||
|
||||
// Outreach
|
||||
|
||||
model Event {
|
||||
id Int @id @default(autoincrement())
|
||||
type EventType @relation(fields: [eventType], references: [type])
|
||||
eventType String
|
||||
leader User @relation("eventLeader", fields: [leaderId], references: [id]) // Not sure if this will stay
|
||||
leaderId String
|
||||
startTime DateTime @default(now())
|
||||
endTime DateTime?
|
||||
stats Stat[]
|
||||
}
|
||||
|
||||
model EventType {
|
||||
type String @id
|
||||
Event Event[]
|
||||
}
|
||||
|
||||
model Stat {
|
||||
id Int @id @default(autoincrement())
|
||||
event Event @relation(fields: [eventId], references: [id])
|
||||
eventId Int
|
||||
leader User @relation("statLeader", fields: [leaderId], references: [id]) // Not sure if this will stay
|
||||
leaderId String
|
||||
vegan Int @default(0)
|
||||
considered Int @default(0)
|
||||
antivegan Int @default(0)
|
||||
thanked Int @default(0)
|
||||
documentary Int @default(0)
|
||||
educated Int @default(0)
|
||||
participants ParticipantStat[]
|
||||
role StatRole?
|
||||
}
|
||||
|
||||
model StatRole {
|
||||
stat Stat @relation(fields: [statId], references: [id])
|
||||
statId Int @id
|
||||
roleId String
|
||||
}
|
||||
|
||||
model ParticipantStat {
|
||||
stat Stat @relation(fields: [statId], references: [id])
|
||||
statId Int
|
||||
user User @relation("participantUser", fields: [userId], references: [id])
|
||||
userId String
|
||||
|
||||
@@id([statId, userId])
|
||||
}
|
||||
|
||||
// Moderation
|
||||
|
||||
model Sus {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("susUser", fields: [userId], references: [id])
|
||||
@@ -76,6 +249,17 @@ model Sus {
|
||||
note String
|
||||
}
|
||||
|
||||
model Warning {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("warnUser", fields: [userId], references: [id])
|
||||
userId String
|
||||
mod User @relation("warnMod", fields: [modId], references: [id])
|
||||
modId String
|
||||
time DateTime @default(now())
|
||||
active Boolean @default(true)
|
||||
note String
|
||||
}
|
||||
|
||||
model Restrict {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("restUser", fields: [userId], references: [id])
|
||||
@@ -83,19 +267,25 @@ model Restrict {
|
||||
mod User @relation("restMod", fields: [modId], references: [id])
|
||||
modId String
|
||||
startTime DateTime @default(now())
|
||||
endedTime DateTime?
|
||||
endMod User? @relation("endRestMod", fields: [endModId], references: [id])
|
||||
endModId String?
|
||||
endTime DateTime?
|
||||
reason String
|
||||
section Int
|
||||
}
|
||||
|
||||
model Ban {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("banUser", fields: [userId], references: [id])
|
||||
userId String
|
||||
mod User @relation("banMod", fields: [modId], references: [id])
|
||||
modId String
|
||||
time DateTime @default(now())
|
||||
active Boolean @default(true)
|
||||
reason String
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("banUser", fields: [userId], references: [id])
|
||||
userId String
|
||||
mod User @relation("banMod", fields: [modId], references: [id])
|
||||
modId String
|
||||
time DateTime @default(now())
|
||||
endMod User? @relation("endBanMod", fields: [endModId], references: [id])
|
||||
endModId String?
|
||||
endTime DateTime?
|
||||
active Boolean @default(true)
|
||||
reason String
|
||||
}
|
||||
|
||||
model TempBan {
|
||||
@@ -105,7 +295,20 @@ model TempBan {
|
||||
mod User @relation("tbanMod", fields: [modId], references: [id])
|
||||
modId String
|
||||
startTime DateTime @default(now())
|
||||
endedTime DateTime
|
||||
endMod User? @relation("endTbanMod", fields: [endModId], references: [id])
|
||||
endModId String?
|
||||
endTime DateTime
|
||||
active Boolean @default(true)
|
||||
reason String
|
||||
}
|
||||
|
||||
model VCMute {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("vcMuteUser", fields: [userId], references: [id])
|
||||
userId String
|
||||
mod User @relation("vcMuteMod", fields: [modId], references: [id])
|
||||
modId String
|
||||
time DateTime @default(now())
|
||||
endTime DateTime?
|
||||
reason String?
|
||||
}
|
||||
|
||||
231
src/commands/coordinators/access.ts
Normal file
231
src/commands/coordinators/access.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { ChannelType } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
|
||||
export class AccessCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'access',
|
||||
description:
|
||||
'Manages channel permissions for ModMails, Private channels, and restricted channels',
|
||||
preconditions: ['CoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('permission')
|
||||
.setDescription('Select permissions for the user/role')
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
{ name: 'Add', value: 'add' },
|
||||
{ name: 'Read', value: 'read' },
|
||||
{ name: 'Remove', value: 'remove' },
|
||||
{ name: 'Reset', value: 'reset' },
|
||||
),
|
||||
)
|
||||
.addChannelOption((option) =>
|
||||
option
|
||||
.setName('channel')
|
||||
.setDescription('Channel to change these permissions on')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to set these permissions for'),
|
||||
)
|
||||
.addRoleOption((option) =>
|
||||
option
|
||||
.setName('role')
|
||||
.setDescription('Role to set these permissions for'),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Check that the command was run in the Guild
|
||||
if (!interaction.inCachedGuild()) {
|
||||
await interaction.reply({
|
||||
content: 'This command can only be run in a server!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the arguments
|
||||
const permission = interaction.options.getString('permission', true);
|
||||
const channel = interaction.options.getChannel('channel', true);
|
||||
const user = interaction.options.getUser('user');
|
||||
const role = interaction.options.getRole('role');
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (user === null && role === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching slash command data!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If user and role is provided, the return an error
|
||||
if (user !== null && role !== null) {
|
||||
await interaction.reply({
|
||||
content:
|
||||
'You have entered a user and a role at the same time! Please only enter one at a time.',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Checks that the channel is a GuildText or GuildVoice, otherwise, return error
|
||||
if (
|
||||
channel.type !== ChannelType.GuildText &&
|
||||
channel.type !== ChannelType.GuildVoice
|
||||
) {
|
||||
await interaction.reply({
|
||||
content: 'Please only select a text or voice channel!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If the channel is not in the categories ModMail, Private, Restricted, the return error
|
||||
if (
|
||||
channel.parentId !== IDs.categories.modMail &&
|
||||
channel.parentId !== IDs.categories.private &&
|
||||
channel.parentId !== IDs.categories.restricted
|
||||
) {
|
||||
await interaction.reply({
|
||||
content: 'Channel is not in ModMail/Private/Restricted category!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Create variable for either User or Role to update permissions for
|
||||
let permId: string;
|
||||
if (user !== null) {
|
||||
permId = user.id;
|
||||
} else if (role !== null) {
|
||||
permId = role.id;
|
||||
} else {
|
||||
await interaction.reply({
|
||||
content: 'Could not find the role to edit permissions!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Set permissions of voice channel
|
||||
if (channel.type === ChannelType.GuildVoice) {
|
||||
switch (permission) {
|
||||
case 'add':
|
||||
await channel.permissionOverwrites.create(permId, {
|
||||
ViewChannel: true,
|
||||
Connect: true,
|
||||
Speak: true,
|
||||
SendMessages: true,
|
||||
ReadMessageHistory: true,
|
||||
});
|
||||
break;
|
||||
case 'view':
|
||||
await channel.permissionOverwrites.create(permId, {
|
||||
ViewChannel: true,
|
||||
Connect: true,
|
||||
Speak: false,
|
||||
SendMessages: false,
|
||||
AddReactions: false,
|
||||
ReadMessageHistory: true,
|
||||
});
|
||||
break;
|
||||
case 'remove':
|
||||
await channel.permissionOverwrites.create(permId, {
|
||||
ViewChannel: false,
|
||||
Connect: false,
|
||||
Speak: false,
|
||||
SendMessages: false,
|
||||
ReadMessageHistory: false,
|
||||
});
|
||||
break;
|
||||
case 'reset':
|
||||
await channel.permissionOverwrites.delete(permId);
|
||||
break;
|
||||
default:
|
||||
await interaction.reply({
|
||||
content: 'Incorrect permission option!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Set permissions of text channel
|
||||
switch (permission) {
|
||||
case 'add':
|
||||
await channel.permissionOverwrites.create(permId, {
|
||||
ViewChannel: true,
|
||||
SendMessages: true,
|
||||
ReadMessageHistory: true,
|
||||
});
|
||||
break;
|
||||
case 'view':
|
||||
await channel.permissionOverwrites.create(permId, {
|
||||
ViewChannel: true,
|
||||
SendMessages: false,
|
||||
AddReactions: false,
|
||||
ReadMessageHistory: true,
|
||||
});
|
||||
break;
|
||||
case 'remove':
|
||||
await channel.permissionOverwrites.create(permId, {
|
||||
ViewChannel: false,
|
||||
SendMessages: false,
|
||||
ReadMessageHistory: false,
|
||||
});
|
||||
break;
|
||||
case 'reset':
|
||||
await channel.permissionOverwrites.delete(permId);
|
||||
break;
|
||||
default:
|
||||
await interaction.reply({
|
||||
content: 'Incorrect permission option!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await interaction.reply(`Successfully updated permissions for ${channel}`);
|
||||
}
|
||||
}
|
||||
134
src/commands/coordinators/anonymous.ts
Normal file
134
src/commands/coordinators/anonymous.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Message } from 'discord.js';
|
||||
import { ChannelType, TextChannel } from 'discord.js';
|
||||
|
||||
export class AnonymousCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'anonymous',
|
||||
aliases: ['anon'],
|
||||
description: 'Bot sends a message for you',
|
||||
preconditions: ['CoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('message')
|
||||
.setDescription('The message the bot will send')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addChannelOption((option) =>
|
||||
option
|
||||
.setName('channel')
|
||||
.setDescription('The channel the bot will send the message'),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const message = interaction.options.getString('message', true);
|
||||
let channel = interaction.options.getChannel('channel');
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel === null) {
|
||||
if (interaction.channel === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error getting the channel!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.channel.send(message);
|
||||
await interaction.reply({
|
||||
content: 'Sent the message',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel.type !== ChannelType.GuildText) {
|
||||
await interaction.reply({
|
||||
content: 'Could not send, unsupported text channel!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
|
||||
channel = channel as TextChannel;
|
||||
await channel.send(message);
|
||||
|
||||
await interaction.reply({
|
||||
content: 'Sent the message',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
const channel = await args.pick('channel');
|
||||
const text = args.finished ? null : await args.rest('string');
|
||||
|
||||
if (text === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('No message was provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel.isTextBased()) {
|
||||
await channel.send(text);
|
||||
} else {
|
||||
await message.react('❌');
|
||||
await message.reply('No channel was provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
await message.react('✅');
|
||||
}
|
||||
}
|
||||
108
src/commands/coordinators/clear.ts
Normal file
108
src/commands/coordinators/clear.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Message } from 'discord.js';
|
||||
|
||||
export class ClearCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'clear',
|
||||
description: 'Deletes 1-100 messages in bulk',
|
||||
preconditions: ['CoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('messages')
|
||||
.setDescription('Number of messages to clear')
|
||||
.setRequired(true)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(100),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const messages = interaction.options.getInteger('messages', true);
|
||||
const { channel } = interaction;
|
||||
|
||||
if (channel === null || channel.isDMBased()) {
|
||||
await interaction.reply({
|
||||
content: 'Could not fetch channel!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await channel.bulkDelete(messages);
|
||||
|
||||
await interaction.reply({
|
||||
content: `Successfully deleted ${messages} messages!`,
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
const messages = await args.pick('integer');
|
||||
|
||||
if (messages > 100) {
|
||||
await message.react('❌');
|
||||
await message.reply('You can only clear up to 100 messages at a time!');
|
||||
return;
|
||||
}
|
||||
if (messages < 1) {
|
||||
await message.react('❌');
|
||||
await message.reply('You need to at least clear 1 message!');
|
||||
return;
|
||||
}
|
||||
|
||||
const { channel } = message;
|
||||
|
||||
if (!channel.isTextBased() || channel.isDMBased()) {
|
||||
await message.react('❌');
|
||||
await message.reply('Unsupported channel type!');
|
||||
return;
|
||||
}
|
||||
|
||||
await channel.bulkDelete(messages);
|
||||
|
||||
await message.reply({
|
||||
content: `Successfully deleted ${messages} messages!`,
|
||||
failIfNotExists: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
388
src/commands/coordinators/private.ts
Normal file
388
src/commands/coordinators/private.ts
Normal file
@@ -0,0 +1,388 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { RegisterBehavior } from '@sapphire/framework';
|
||||
import { Subcommand } from '@sapphire/plugin-subcommands';
|
||||
import type { Guild, TextChannel, Snowflake } from 'discord.js';
|
||||
import {
|
||||
CategoryChannel,
|
||||
ChannelType,
|
||||
EmbedBuilder,
|
||||
GuildMember,
|
||||
PermissionsBitField,
|
||||
time,
|
||||
} from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
|
||||
export class PrivateCommand extends Subcommand {
|
||||
public constructor(context: Subcommand.Context, options: Subcommand.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'private',
|
||||
subcommands: [
|
||||
{
|
||||
name: 'create',
|
||||
chatInputRun: 'create',
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
chatInputRun: 'delete',
|
||||
},
|
||||
],
|
||||
description: 'Creates/deletes private channels for a user',
|
||||
preconditions: ['CoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Subcommand.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('create')
|
||||
.setDescription('Create a private channel')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to create a private channel with')
|
||||
.setRequired(true),
|
||||
),
|
||||
)
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('delete')
|
||||
.setDescription('Delete a private channel')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to delete a private channel from'),
|
||||
),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public async create(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const modUser = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.editReply({
|
||||
content: 'Error fetching mod!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const mod = guild.members.cache.get(modUser.id);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (member === undefined || mod === undefined) {
|
||||
await interaction.editReply({
|
||||
content: 'Error fetching users!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const [name, coordinator] = this.getCoordinator(mod);
|
||||
|
||||
if (this.checkPrivate(member.id, coordinator, guild)) {
|
||||
await interaction.editReply({
|
||||
content: 'A private channel already exists!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const voiceChannel = await guild.channels.create({
|
||||
name: 'Private Voice Channel',
|
||||
type: ChannelType.GuildVoice,
|
||||
parent: IDs.categories.private,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
deny: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: user.id,
|
||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: coordinator,
|
||||
allow: [
|
||||
PermissionsBitField.Flags.SendMessages,
|
||||
PermissionsBitField.Flags.ViewChannel,
|
||||
PermissionsBitField.Flags.Connect,
|
||||
PermissionsBitField.Flags.MuteMembers,
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let privateChannel: TextChannel;
|
||||
let bannedName = false;
|
||||
try {
|
||||
privateChannel = await guild.channels.create({
|
||||
name: `🍂┃${member.user.username}-private-${name}`,
|
||||
type: ChannelType.GuildText,
|
||||
topic: `Private channel. ${user.id} ${coordinator} ${voiceChannel.id} (Please do not change this)`,
|
||||
parent: IDs.categories.private,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
allow: [PermissionsBitField.Flags.ReadMessageHistory],
|
||||
deny: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: user.id,
|
||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: coordinator,
|
||||
allow: [
|
||||
PermissionsBitField.Flags.SendMessages,
|
||||
PermissionsBitField.Flags.ViewChannel,
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
} catch {
|
||||
privateChannel = await guild.channels.create({
|
||||
name: `🍂┃${member.user.id}-private-${name}`,
|
||||
type: ChannelType.GuildText,
|
||||
topic: `Private channel. ${user.id} ${coordinator} ${voiceChannel.id} (Please do not change this)`,
|
||||
parent: IDs.categories.private,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
allow: [PermissionsBitField.Flags.ReadMessageHistory],
|
||||
deny: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: user.id,
|
||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: coordinator,
|
||||
allow: [
|
||||
PermissionsBitField.Flags.SendMessages,
|
||||
PermissionsBitField.Flags.ViewChannel,
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
bannedName = true;
|
||||
}
|
||||
|
||||
if (!bannedName) {
|
||||
await voiceChannel.setName(`${member.user.username}-private-${name}`);
|
||||
} else {
|
||||
await voiceChannel.setName(`${member.user.id}-private-${name}`);
|
||||
}
|
||||
|
||||
const joinTime = time(member.joinedAt!);
|
||||
const registerTime = time(member.user.createdAt);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(member.displayHexColor)
|
||||
.setTitle(`Private channel for ${member.user.username}`)
|
||||
.setDescription(`${member}`)
|
||||
.setThumbnail(member.user.displayAvatarURL())
|
||||
.addFields(
|
||||
{ name: 'Joined:', value: `${joinTime}`, inline: true },
|
||||
{ name: 'Created:', value: `${registerTime}`, inline: true },
|
||||
);
|
||||
|
||||
await privateChannel.send({ embeds: [embed] });
|
||||
|
||||
await interaction.editReply({
|
||||
content: `Created the private channel: ${privateChannel}`,
|
||||
});
|
||||
}
|
||||
|
||||
public async delete(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user');
|
||||
const modUser = interaction.user;
|
||||
const { guild, channel } = interaction;
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null || channel === null) {
|
||||
await interaction.editReply({
|
||||
content: 'Error fetching user!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = guild.members.cache.get(modUser.id);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (mod === undefined) {
|
||||
await interaction.editReply({
|
||||
content: 'Error fetching users!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const coordinatorInfo = this.getCoordinator(mod);
|
||||
const coordinator = coordinatorInfo[1];
|
||||
let topic: string[];
|
||||
|
||||
if (user === null) {
|
||||
if (channel.type !== ChannelType.GuildText) {
|
||||
await interaction.editReply({
|
||||
content:
|
||||
'Please make sure you ran this command in the original private text channel!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel.parentId !== IDs.categories.private) {
|
||||
await interaction.editReply({
|
||||
content:
|
||||
'Please make sure you ran this command in the original private text channel!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel.topic === null) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error with this channel's topic!",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
topic = channel.topic.split(' ');
|
||||
await channel.delete();
|
||||
|
||||
const vcId = topic[topic.indexOf(coordinator) + 1];
|
||||
const voiceChannel = guild.channels.cache.get(vcId);
|
||||
|
||||
if (
|
||||
voiceChannel !== undefined &&
|
||||
voiceChannel.parentId === IDs.categories.private
|
||||
) {
|
||||
await voiceChannel.delete();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
const category = guild.channels.cache.get(IDs.categories.private) as
|
||||
| CategoryChannel
|
||||
| undefined;
|
||||
|
||||
if (category === undefined) {
|
||||
await interaction.editReply({
|
||||
content: 'Could not find category!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const textChannels = category.children.cache.filter(
|
||||
(c) => c.type === ChannelType.GuildText,
|
||||
);
|
||||
textChannels.forEach((c) => {
|
||||
const textChannel = c as TextChannel;
|
||||
// Checks if the channel topic has the user's snowflake
|
||||
if (textChannel.topic?.includes(user?.id)) {
|
||||
topic = textChannel.topic.split(' ');
|
||||
const vcId = topic[topic.indexOf(coordinator) + 1];
|
||||
const voiceChannel = guild.channels.cache.get(vcId);
|
||||
|
||||
if (
|
||||
voiceChannel !== undefined &&
|
||||
voiceChannel.parentId === IDs.categories.private
|
||||
) {
|
||||
voiceChannel.delete();
|
||||
}
|
||||
textChannel.delete();
|
||||
}
|
||||
});
|
||||
|
||||
await interaction.editReply({
|
||||
content: `Successfully deleted the channel for ${user}`,
|
||||
});
|
||||
}
|
||||
|
||||
private getCoordinator(user: GuildMember) {
|
||||
let name: string;
|
||||
let id: string;
|
||||
if (user.roles.cache.has(IDs.roles.staff.devCoordinator)) {
|
||||
name = 'dev';
|
||||
id = IDs.roles.staff.devCoordinator;
|
||||
} else if (user.roles.cache.has(IDs.roles.staff.modCoordinator)) {
|
||||
name = 'mod';
|
||||
id = IDs.roles.staff.modCoordinator;
|
||||
} else if (user.roles.cache.has(IDs.roles.staff.diversityCoordinator)) {
|
||||
name = 'diversity';
|
||||
id = IDs.roles.staff.diversityCoordinator;
|
||||
} else if (user.roles.cache.has(IDs.roles.staff.mentorCoordinator)) {
|
||||
name = 'mentor';
|
||||
id = IDs.roles.staff.mentorCoordinator;
|
||||
} else if (user.roles.cache.has(IDs.roles.staff.verifierCoordinator)) {
|
||||
name = 'verifier';
|
||||
id = IDs.roles.staff.verifierCoordinator;
|
||||
} else if (user.roles.cache.has(IDs.roles.staff.eventCoordinator)) {
|
||||
name = 'event';
|
||||
id = IDs.roles.staff.eventCoordinator;
|
||||
} else {
|
||||
name = 'coordinator';
|
||||
id = IDs.roles.staff.coordinator;
|
||||
}
|
||||
return [name, id];
|
||||
}
|
||||
|
||||
private checkPrivate(user: Snowflake, coordinator: string, guild: Guild) {
|
||||
const category = guild.channels.cache.get(IDs.categories.private) as
|
||||
| CategoryChannel
|
||||
| undefined;
|
||||
|
||||
if (category === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const textChannels = category.children.cache.filter(
|
||||
(c) => c.type === ChannelType.GuildText,
|
||||
);
|
||||
let exists = false;
|
||||
textChannels.forEach((c) => {
|
||||
const textChannel = c as TextChannel;
|
||||
// Checks if the channel topic has the user's snowflake
|
||||
if (
|
||||
textChannel.topic?.includes(user) &&
|
||||
textChannel.topic?.includes(coordinator)
|
||||
) {
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
return exists;
|
||||
}
|
||||
}
|
||||
125
src/commands/economy/balance.ts
Normal file
125
src/commands/economy/balance.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { User, Guild, Message } from 'discord.js';
|
||||
import { updateUser } from '#utils/database/dbExistingUser';
|
||||
import { getBalance } from '#utils/database/economy';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
|
||||
export class BalanceCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'balance',
|
||||
aliases: ['bal'],
|
||||
description: 'Gets the amount of ARAs you have',
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
const { user, guild } = interaction;
|
||||
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Could not find the guild!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const info = await this.balance(user, guild);
|
||||
|
||||
await interaction.editReply({
|
||||
content: info.message,
|
||||
embeds: info.embeds,
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message) {
|
||||
const user = message.member?.user;
|
||||
const { guild } = message;
|
||||
|
||||
if (user === undefined) {
|
||||
await message.react('❌');
|
||||
await message.reply('Could not find your user!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Could not find the guild!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.balance(user, guild);
|
||||
|
||||
await message.reply({
|
||||
content: info.message,
|
||||
embeds: info.embeds,
|
||||
});
|
||||
if (!info.success) {
|
||||
await message.react('❌');
|
||||
}
|
||||
}
|
||||
|
||||
private async balance(user: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
embeds: [] as EmbedBuilder[],
|
||||
success: false,
|
||||
};
|
||||
|
||||
const member = guild.members.cache.get(user.id);
|
||||
|
||||
if (member === undefined) {
|
||||
info.message = 'Could not find your guild member!';
|
||||
return info;
|
||||
}
|
||||
|
||||
await updateUser(member);
|
||||
|
||||
const balance = await getBalance(user.id);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#00ff7d')
|
||||
.setAuthor({
|
||||
name: `${member.displayName}'s Account`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields({ name: 'Balance', value: `${balance.balance} ARA` });
|
||||
|
||||
info.success = true;
|
||||
info.embeds.push(embed);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
187
src/commands/economy/daily.ts
Normal file
187
src/commands/economy/daily.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { Time } from '@sapphire/time-utilities';
|
||||
import type { User, Guild, GuildMember, Message } from 'discord.js';
|
||||
import { updateUser } from '#utils/database/dbExistingUser';
|
||||
import { daily, getLastDaily } from '#utils/database/economy';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
|
||||
export class DailyCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'daily',
|
||||
description: 'Get given an amount of money once a day',
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
const { user, guild } = interaction;
|
||||
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Could not find the guild!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const info = await this.runDaily(user, guild);
|
||||
|
||||
await interaction.editReply({
|
||||
content: info.message,
|
||||
embeds: info.embeds,
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message) {
|
||||
const user = message.member?.user;
|
||||
const { guild } = message;
|
||||
|
||||
if (user === undefined) {
|
||||
await message.react('❌');
|
||||
await message.reply('Could not find your user!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Could not find the guild!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.runDaily(user, guild);
|
||||
|
||||
await message.reply({
|
||||
content: info.message,
|
||||
embeds: info.embeds,
|
||||
});
|
||||
|
||||
if (!info.success) {
|
||||
await message.react('❌');
|
||||
}
|
||||
}
|
||||
|
||||
private async runDaily(user: User, guild: Guild) {
|
||||
const amount = 10;
|
||||
const time = Time.Hour * 18;
|
||||
const info = {
|
||||
message: '',
|
||||
embeds: [] as EmbedBuilder[],
|
||||
success: false,
|
||||
};
|
||||
|
||||
const lastDaily = await getLastDaily(user.id);
|
||||
|
||||
if (
|
||||
lastDaily !== null &&
|
||||
new Date().getTime() - lastDaily.time.getTime() < time
|
||||
) {
|
||||
info.message =
|
||||
'You have already claimed your daily, come back later to claim it!';
|
||||
return info;
|
||||
}
|
||||
|
||||
const member = guild.members.cache.get(user.id);
|
||||
|
||||
if (member === undefined) {
|
||||
info.message = 'Could not find your guild member!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Give bonus for the user
|
||||
const bonus = await this.giveBonus(member);
|
||||
|
||||
await updateUser(member);
|
||||
|
||||
const [db] = await Promise.all([daily(user.id, amount + bonus)]);
|
||||
|
||||
const balance = db.Balance?.balance;
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#00ff7d')
|
||||
.setAuthor({
|
||||
name: 'Daily Reward',
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields({
|
||||
name: 'Collected',
|
||||
value: `${amount} ARA`,
|
||||
inline: bonus > 0,
|
||||
});
|
||||
|
||||
if (bonus > 0) {
|
||||
embed.addFields(
|
||||
{ name: 'Bonus', value: `${bonus} ARA`, inline: true },
|
||||
{ name: '\u200B', value: 'Thank you for contributing to ARA! :D' },
|
||||
);
|
||||
}
|
||||
|
||||
if (balance !== undefined) {
|
||||
embed.setFooter({ text: `New Balance: ${balance}` });
|
||||
}
|
||||
|
||||
info.success = true;
|
||||
info.embeds.push(embed);
|
||||
return info;
|
||||
}
|
||||
|
||||
private async giveBonus(member: GuildMember) {
|
||||
let bonus = 0;
|
||||
|
||||
const amount = [
|
||||
{ role: member.roles.premiumSubscriberRole?.id, amount: 5 },
|
||||
{ role: IDs.roles.staff.coordinator, amount: 2 },
|
||||
{ role: IDs.roles.staff.moderator, amount: 2 },
|
||||
{ role: IDs.roles.staff.trialModerator, amount: 2 },
|
||||
{ role: IDs.roles.staff.restricted, amount: 1 },
|
||||
{ role: IDs.roles.staff.verifier, amount: 2 },
|
||||
{ role: IDs.roles.staff.trialVerifier, amount: 2 },
|
||||
{ role: IDs.roles.staff.developer, amount: 2 },
|
||||
{ role: IDs.roles.staff.mentor, amount: 2 },
|
||||
{ role: IDs.roles.stageHost, amount: 1 },
|
||||
];
|
||||
|
||||
member.roles.cache.forEach((role) => {
|
||||
amount.forEach((check) => {
|
||||
if (role.id === check.role) {
|
||||
bonus += check.amount;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return bonus;
|
||||
}
|
||||
}
|
||||
228
src/commands/economy/pay.ts
Normal file
228
src/commands/economy/pay.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { User, Guild, Message } from 'discord.js';
|
||||
import { updateUser } from '#utils/database/dbExistingUser';
|
||||
import { getBalance, transfer } from '#utils/database/economy';
|
||||
import { EmbedBuilder, TextChannel } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
|
||||
export class BalanceCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'pay',
|
||||
description: 'Give a user an amount of money',
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('The user to give the money to')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('amount')
|
||||
.setDescription('The amount to give to the user')
|
||||
.setMinValue(1)
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('The reason/reference for the transaction')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
const recipient = interaction.options.getUser('user', true);
|
||||
const amount = interaction.options.getInteger('amount', true);
|
||||
const reason = interaction.options.getString('reason', true);
|
||||
const { user, guild } = interaction;
|
||||
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Could not find the guild!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const info = await this.pay(user, recipient, amount, reason, guild);
|
||||
|
||||
await interaction.editReply({
|
||||
content: info.message,
|
||||
embeds: info.embeds,
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
let recipient: User;
|
||||
try {
|
||||
recipient = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
let amount: number;
|
||||
try {
|
||||
amount = await args.pick('integer');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('Amount was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = args.finished ? null : await args.rest('string');
|
||||
|
||||
if (reason === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Reason/reference was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const user = message.member?.user;
|
||||
const { guild } = message;
|
||||
|
||||
if (user === undefined) {
|
||||
await message.react('❌');
|
||||
await message.reply('Could not find your user!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Could not find the guild!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.pay(user, recipient, amount, reason, guild);
|
||||
|
||||
await message.reply({
|
||||
content: info.message,
|
||||
embeds: info.embeds,
|
||||
});
|
||||
if (!info.success) {
|
||||
await message.react('❌');
|
||||
}
|
||||
}
|
||||
|
||||
private async pay(
|
||||
user: User,
|
||||
recipient: User,
|
||||
amount: number,
|
||||
reason: string,
|
||||
guild: Guild,
|
||||
) {
|
||||
const info = {
|
||||
message: '',
|
||||
embeds: [] as EmbedBuilder[],
|
||||
success: false,
|
||||
};
|
||||
|
||||
// Check the amount to be paid is greater than 0
|
||||
if (amount < 1) {
|
||||
info.message =
|
||||
"You need to actually give money, you can't send nothing or try to break the " +
|
||||
'economy 😭';
|
||||
return info;
|
||||
}
|
||||
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const recipientMember = guild.members.cache.get(recipient.id);
|
||||
|
||||
if (member === undefined) {
|
||||
info.message = 'Could not find your guild member!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (recipientMember === undefined) {
|
||||
info.message = 'Could not find the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
await updateUser(member);
|
||||
await updateUser(recipientMember);
|
||||
|
||||
const balance = await getBalance(user.id);
|
||||
|
||||
if (balance.balance < amount) {
|
||||
info.message = "You don't have enough money to send!";
|
||||
return info;
|
||||
}
|
||||
|
||||
await transfer(user.id, recipient.id, amount, reason);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#00ff7d')
|
||||
.setAuthor({
|
||||
name: `Transfer to ${recipientMember.displayName}`,
|
||||
iconURL: `${recipientMember.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'From', value: `${user}`, inline: true },
|
||||
{ name: 'To', value: `${recipient}`, inline: true },
|
||||
{ name: 'Amount', value: `${amount} ARA` },
|
||||
{ name: 'Reason', value: reason },
|
||||
);
|
||||
|
||||
info.success = true;
|
||||
info.embeds.push(embed);
|
||||
|
||||
// Log the payment in the server
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.economy) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(IDs.channels.logs.economy)) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
if (logChannel === undefined) {
|
||||
this.container.logger.error('Pay Error: Could not fetch log channel');
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
const logEmbed = new EmbedBuilder(embed.data);
|
||||
logEmbed.setTimestamp().setFooter({ text: `ID: ${user.id}` });
|
||||
await logChannel.send({ embeds: [logEmbed] });
|
||||
return info;
|
||||
}
|
||||
}
|
||||
147
src/commands/economy/topbalances.ts
Normal file
147
src/commands/economy/topbalances.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Stefanie Merceron
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { Guild, GuildMember, Message, EmbedBuilder } from 'discord.js';
|
||||
import { getTopBalances } from '#utils/database/economy';
|
||||
|
||||
export class TopBalancesCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'topbalances',
|
||||
aliases: ['topbal', 'leaderboard'],
|
||||
description: 'Shows the top 5 largest balances on the server',
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
const { guild } = interaction;
|
||||
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Could not find the guild!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const info = await this.showTopBalances(guild);
|
||||
|
||||
await interaction.editReply({
|
||||
content: info.message,
|
||||
embeds: info.embeds,
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message) {
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Could not find the guild!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.showTopBalances(guild);
|
||||
|
||||
await message.reply({
|
||||
content: info.message,
|
||||
embeds: info.embeds,
|
||||
});
|
||||
if (!info.success) {
|
||||
await message.react('❌');
|
||||
}
|
||||
}
|
||||
|
||||
private async showTopBalances(guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
embeds: [] as EmbedBuilder[],
|
||||
success: false,
|
||||
};
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#cc802c')
|
||||
.setTitle('Top Balances on the Server')
|
||||
.setAuthor({
|
||||
name: 'ARA',
|
||||
iconURL:
|
||||
'https://github.com/veganhacktivists/arabot/blob/main/docs/images/logo.png?raw=true',
|
||||
});
|
||||
|
||||
const leaders = await getTopBalances(5);
|
||||
const fetchMemberPromises: Promise<GuildMember | null>[] = [];
|
||||
|
||||
for (const leader of leaders) {
|
||||
fetchMemberPromises.push(
|
||||
guild.members.fetch(leader.userId).catch(() => null),
|
||||
);
|
||||
}
|
||||
|
||||
const members = await Promise.all(fetchMemberPromises);
|
||||
|
||||
for (let i = 0; i < leaders.length; i += 1) {
|
||||
const leader = leaders[i];
|
||||
const member = members[i];
|
||||
|
||||
// Server Members Display on The Leaderboard
|
||||
if (member) {
|
||||
embed.addFields(
|
||||
{
|
||||
name: i + 1 + '.',
|
||||
value:
|
||||
'[' +
|
||||
member.displayName +
|
||||
'](<https://discord.com/users/' +
|
||||
leader.userId +
|
||||
'>)',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Balance',
|
||||
value: leader.balance + " ARA's",
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: '\u200b',
|
||||
value: '\u200b',
|
||||
inline: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
info.success = true;
|
||||
info.embeds.push(embed);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,11 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { N1984 } from '../../utils/gifs';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { N1984 } from '#utils/gifs';
|
||||
import { addFunLog, countTotal } from '#utils/database/fun';
|
||||
|
||||
class N1984Command extends Command {
|
||||
export class N1984Command extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
@@ -34,9 +35,7 @@ class N1984Command extends Command {
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description),
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -44,24 +43,26 @@ class N1984Command extends Command {
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the user
|
||||
// TODO exception handling
|
||||
const member = interaction.member!.user;
|
||||
const memberGuildMember = interaction.guild!.members.cache.get(member.id)!;
|
||||
const { user } = interaction;
|
||||
|
||||
await addFunLog(user.id, '1984');
|
||||
const count = await countTotal(user.id, '1984');
|
||||
|
||||
// Creates the embed for the 1984 reaction
|
||||
// Add a 1 in 1000 chance of Dantas literally making ARA 1984
|
||||
const random1984 = Math.random() < 0.001 ? 'https://c.tenor.com/0BwU0BjWYX4AAAAC/arthuria-dantas.gif'
|
||||
: N1984[Math.floor(Math.random() * N1984.length)];
|
||||
const n1984Embed = new MessageEmbed()
|
||||
const random1984 =
|
||||
Math.random() < 0.001
|
||||
? 'https://c.tenor.com/0BwU0BjWYX4AAAAC/arthuria-dantas.gif'
|
||||
: N1984[Math.floor(Math.random() * N1984.length)];
|
||||
const n1984Embed = new EmbedBuilder()
|
||||
.setColor('#ffffff')
|
||||
.setTitle(`${memberGuildMember.displayName} is happy!`)
|
||||
.setImage(random1984);
|
||||
.setTitle(`${user.username} is happy!`)
|
||||
.setImage(random1984)
|
||||
.setFooter({ text: `${user.username}'s 1984 count: ${count}` });
|
||||
|
||||
// Send the embed
|
||||
await interaction.reply({ embeds: [n1984Embed], fetchReply: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default N1984Command;
|
||||
|
||||
64
src/commands/fun/cringe.ts
Normal file
64
src/commands/fun/cringe.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg, Stefanie Merceron
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { Cringe } from '#utils/gifs';
|
||||
import { addFunLog, countTotal } from '#utils/database/fun';
|
||||
|
||||
export class CringeCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'cringe',
|
||||
description: 'Express your cringe',
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the user
|
||||
// TODO exception handling
|
||||
const { user } = interaction;
|
||||
|
||||
await addFunLog(user.id, 'cringe');
|
||||
const count = await countTotal(user.id, 'cringe');
|
||||
|
||||
// Creates the embed for the cringe reaction
|
||||
const randomCringe = Cringe[Math.floor(Math.random() * Cringe.length)];
|
||||
const cringeEmbed = new EmbedBuilder()
|
||||
.setColor('#001148')
|
||||
.setTitle(`${user.username} feels immense cringe...`)
|
||||
.setImage(randomCringe)
|
||||
.setFooter({ text: `${user.username}'s cringe count: ${count}` });
|
||||
|
||||
// Send the embed
|
||||
await interaction.reply({ embeds: [cringeEmbed], fetchReply: true });
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,10 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Happy } from '../../utils/gifs';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { Happy } from '#utils/gifs';
|
||||
|
||||
class HappyCommand extends Command {
|
||||
export class HappyCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
@@ -34,9 +34,7 @@ class HappyCommand extends Command {
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description),
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -44,7 +42,7 @@ class HappyCommand extends Command {
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the user
|
||||
// TODO exception handling
|
||||
const member = interaction.member!.user;
|
||||
@@ -52,7 +50,7 @@ class HappyCommand extends Command {
|
||||
|
||||
// Creates the embed for the happy reaction
|
||||
const randomHappy = Happy[Math.floor(Math.random() * Happy.length)];
|
||||
const happyEmbed = new MessageEmbed()
|
||||
const happyEmbed = new EmbedBuilder()
|
||||
.setColor('#40ff00')
|
||||
.setTitle(`${memberGuildMember.displayName} is happy!`)
|
||||
.setImage(randomHappy);
|
||||
@@ -61,5 +59,3 @@ class HappyCommand extends Command {
|
||||
await interaction.reply({ embeds: [happyEmbed], fetchReply: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default HappyCommand;
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Hugs } from '../../utils/gifs';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { Hugs } from '#utils/gifs';
|
||||
import { addFunLog, countTotal } from '#utils/database/fun';
|
||||
|
||||
class HugCommand extends Command {
|
||||
export class HugCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
@@ -33,12 +34,16 @@ class HugCommand extends Command {
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) => option.setName('user')
|
||||
.setDescription('User you want to hug')
|
||||
.setRequired(true)),
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User you want to hug')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -46,23 +51,29 @@ class HugCommand extends Command {
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the users
|
||||
// TODO exception handling
|
||||
const user = interaction.options.getUser('user')!;
|
||||
const hugger = interaction.member!.user;
|
||||
const huggerGuildMember = interaction.guild!.members.cache.get(hugger.id)!;
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const hugger = interaction.user;
|
||||
|
||||
await addFunLog(hugger.id, 'hug', user.id);
|
||||
const count = await countTotal(hugger.id, 'hug', user.id);
|
||||
|
||||
// Creates the embed for the hug
|
||||
const randomHug = Hugs[Math.floor(Math.random() * Hugs.length)];
|
||||
const hugEmbed = new MessageEmbed()
|
||||
const hugEmbed = new EmbedBuilder()
|
||||
.setColor('#0099ff')
|
||||
.setTitle(`Hug from ${huggerGuildMember.displayName}`)
|
||||
.setImage(randomHug);
|
||||
.setTitle(`Hug from ${hugger.username}`)
|
||||
.setImage(randomHug)
|
||||
.setFooter({
|
||||
text: `Amount of hugs given from ${hugger.username} to you: ${count}`,
|
||||
});
|
||||
|
||||
// Send the hug
|
||||
await interaction.reply({ content: `<@${user.id}>`, embeds: [hugEmbed], fetchReply: true });
|
||||
await interaction.reply({
|
||||
content: `${user}`,
|
||||
embeds: [hugEmbed],
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default HugCommand;
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Kill } from '../../utils/gifs';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { Kill } from '#utils/gifs';
|
||||
import { addFunLog, countTotal } from '#utils/database/fun';
|
||||
|
||||
class KillCommand extends Command {
|
||||
export class KillCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
@@ -33,12 +34,16 @@ class KillCommand extends Command {
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) => option.setName('user')
|
||||
.setDescription('User you want to kill')
|
||||
.setRequired(true)),
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User you want to kill')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -46,23 +51,34 @@ class KillCommand extends Command {
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the users
|
||||
// TODO exception handling
|
||||
const user = interaction.options.getUser('user')!;
|
||||
const killer = interaction.member!.user;
|
||||
const killerGuildMember = interaction.guild!.members.cache.get(killer.id)!;
|
||||
const user = interaction.options.getUser('user', true)!;
|
||||
const sender = interaction.user;
|
||||
|
||||
if (user.id === sender.id) {
|
||||
await interaction.reply('You changed your mind');
|
||||
return;
|
||||
}
|
||||
|
||||
await addFunLog(sender.id, 'kill', user.id);
|
||||
const count = await countTotal(sender.id, 'kill', user.id);
|
||||
|
||||
// Creates the embed for the kill
|
||||
const randomKill = Kill[Math.floor(Math.random() * Kill.length)];
|
||||
const killEmbed = new MessageEmbed()
|
||||
const killEmbed = new EmbedBuilder()
|
||||
.setColor('#ff0000')
|
||||
.setTitle(`Kill from ${killerGuildMember.displayName}`)
|
||||
.setImage(randomKill);
|
||||
.setTitle(`Kill from ${sender.username}`)
|
||||
.setImage(randomKill)
|
||||
.setFooter({
|
||||
text: `Amount of kills from ${sender.username} to you: ${count}`,
|
||||
});
|
||||
|
||||
// Send the kill
|
||||
await interaction.reply({ content: `<@${user.id}>`, embeds: [killEmbed], fetchReply: true });
|
||||
await interaction.reply({
|
||||
content: `${user}`,
|
||||
embeds: [killEmbed],
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default KillCommand;
|
||||
|
||||
@@ -18,28 +18,32 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Poke } from '../../utils/gifs';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { Poke } from '#utils/gifs';
|
||||
import { addFunLog, countTotal } from '#utils/database/fun';
|
||||
|
||||
class PokeCommand extends Command {
|
||||
export class PokeCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'poke',
|
||||
description: 'Poke a user',
|
||||
preconditions: [['CoordinatorOnly', 'PatreonOnly']],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) => option.setName('user')
|
||||
.setDescription('User you want to poke')
|
||||
.setRequired(true)),
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User you want to poke')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -47,23 +51,29 @@ class PokeCommand extends Command {
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the users
|
||||
// TODO exception handling
|
||||
const user = interaction.options.getUser('user')!;
|
||||
const poker = interaction.member!.user;
|
||||
const pokerGuildMember = interaction.guild!.members.cache.get(poker.id)!;
|
||||
const user = interaction.options.getUser('user', true)!;
|
||||
const sender = interaction.user;
|
||||
|
||||
await addFunLog(sender.id, 'poke', user.id);
|
||||
const count = await countTotal(sender.id, 'poke', user.id);
|
||||
|
||||
// Creates the embed for the poke
|
||||
const randomPoke = Poke[Math.floor(Math.random() * Poke.length)];
|
||||
const pokeEmbed = new MessageEmbed()
|
||||
const pokeEmbed = new EmbedBuilder()
|
||||
.setColor('#0099ff')
|
||||
.setTitle(`Poke from ${pokerGuildMember.displayName}`)
|
||||
.setImage(randomPoke);
|
||||
.setTitle(`Poke from ${sender.username}`)
|
||||
.setImage(randomPoke)
|
||||
.setFooter({
|
||||
text: `Amount of pokes from ${sender.username} to you: ${count}`,
|
||||
});
|
||||
|
||||
// Send the poke
|
||||
await interaction.reply({ content: `<@${user.id}>`, embeds: [pokeEmbed], fetchReply: true });
|
||||
await interaction.reply({
|
||||
content: `${user}`,
|
||||
embeds: [pokeEmbed],
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default PokeCommand;
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Sad } from '../../utils/gifs';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { Sad } from '#utils/gifs';
|
||||
|
||||
class SadCommand extends Command {
|
||||
export class SadCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
@@ -34,9 +34,7 @@ class SadCommand extends Command {
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description),
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -44,7 +42,7 @@ class SadCommand extends Command {
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the user
|
||||
// TODO exception handling
|
||||
const member = interaction.member!.user;
|
||||
@@ -52,7 +50,7 @@ class SadCommand extends Command {
|
||||
|
||||
// Creates the embed for the sad reaction
|
||||
const randomSad = Sad[Math.floor(Math.random() * Sad.length)];
|
||||
const sadEmbed = new MessageEmbed()
|
||||
const sadEmbed = new EmbedBuilder()
|
||||
.setColor('#001148')
|
||||
.setTitle(`${memberGuildMember.displayName} is sad...`)
|
||||
.setImage(randomSad);
|
||||
@@ -61,5 +59,3 @@ class SadCommand extends Command {
|
||||
await interaction.reply({ embeds: [sadEmbed], fetchReply: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default SadCommand;
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Shrug } from '../../utils/gifs';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { Shrug } from '#utils/gifs';
|
||||
|
||||
class ShrugCommand extends Command {
|
||||
export class ShrugCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
@@ -34,9 +34,7 @@ class ShrugCommand extends Command {
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description),
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -44,7 +42,7 @@ class ShrugCommand extends Command {
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the user
|
||||
// TODO exception handling
|
||||
const member = interaction.member!.user;
|
||||
@@ -52,7 +50,7 @@ class ShrugCommand extends Command {
|
||||
|
||||
// Creates the embed for the shrug reaction
|
||||
const randomShrug = Shrug[Math.floor(Math.random() * Shrug.length)];
|
||||
const shrugEmbed = new MessageEmbed()
|
||||
const shrugEmbed = new EmbedBuilder()
|
||||
.setColor('#001980')
|
||||
.setTitle(`${memberGuildMember.displayName} shrugs`)
|
||||
.setImage(randomShrug);
|
||||
@@ -61,5 +59,3 @@ class ShrugCommand extends Command {
|
||||
await interaction.reply({ embeds: [shrugEmbed], fetchReply: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default ShrugCommand;
|
||||
|
||||
239
src/commands/mod/ban/ban.ts
Normal file
239
src/commands/mod/ban/ban.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { User, Message, Snowflake, TextChannel, Guild } from 'discord.js';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { addBan, checkBan } from '#utils/database/ban';
|
||||
import { addEmptyUser, updateUser } from '#utils/database/dbExistingUser';
|
||||
import { checkTempBan, removeTempBan } from '#utils/database/tempBan';
|
||||
|
||||
export class BanCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'ban',
|
||||
description: 'Bans a user',
|
||||
preconditions: ['RestrictedAccessOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to ban')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('Note about the user')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const reason = interaction.options.getString('reason', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const ban = await this.ban(user.id, mod.id, reason, guild);
|
||||
|
||||
await interaction.editReply({ content: ban.message });
|
||||
}
|
||||
|
||||
// Non Application Command method of banning a user
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
const reason = args.finished ? null : await args.rest('string');
|
||||
const mod = message.author;
|
||||
|
||||
if (reason === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Ban reason was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.channel.id !== IDs.channels.restricted.moderators) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
`You can only run this command in <#${IDs.channels.restricted.moderators}> ` +
|
||||
'or alternatively use the slash command!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const ban = await this.ban(user.id, mod.id, reason, guild);
|
||||
|
||||
await message.reply(ban.message);
|
||||
await message.react(ban.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async ban(
|
||||
userId: Snowflake,
|
||||
modId: Snowflake,
|
||||
reason: string,
|
||||
guild: Guild,
|
||||
) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
|
||||
if (user === undefined) {
|
||||
user = (await guild.client.users.fetch(userId)) as User;
|
||||
}
|
||||
|
||||
// Gets mod's GuildMember
|
||||
const mod = guild.members.cache.get(modId);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (mod === undefined) {
|
||||
info.message = 'Error fetching mod!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (await checkBan(userId)) {
|
||||
info.message = `${user} is already banned!`;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check if mod is in database
|
||||
await updateUser(mod);
|
||||
|
||||
// Gets guildMember
|
||||
let member = guild.members.cache.get(userId);
|
||||
|
||||
if (member === undefined) {
|
||||
member = await guild.members.fetch(userId).catch(() => undefined);
|
||||
}
|
||||
|
||||
if (member !== undefined) {
|
||||
// Checks if the user is not restricted
|
||||
if (member.roles.cache.has(IDs.roles.vegan.vegan)) {
|
||||
info.message = 'You need to restrict the user first!';
|
||||
return info;
|
||||
}
|
||||
|
||||
await updateUser(member);
|
||||
|
||||
// Send DM for reason of ban
|
||||
await member
|
||||
.send(
|
||||
`You have been banned from ARA for: ${reason}` +
|
||||
'\n\nhttps://vbcamp.org/ARA',
|
||||
)
|
||||
.catch(() => {});
|
||||
|
||||
// Ban the user
|
||||
await member.ban({ reason });
|
||||
} else {
|
||||
await addEmptyUser(userId);
|
||||
}
|
||||
|
||||
// Add ban to database
|
||||
await addBan(userId, modId, reason);
|
||||
|
||||
if (await checkTempBan(userId)) {
|
||||
await removeTempBan(userId);
|
||||
}
|
||||
|
||||
info.message = `${user} has been banned.`;
|
||||
info.success = true;
|
||||
|
||||
// Log the ban
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(
|
||||
IDs.channels.logs.restricted,
|
||||
)) as TextChannel | undefined;
|
||||
if (logChannel === undefined) {
|
||||
this.container.logger.error('Ban Error: Could not fetch log channel');
|
||||
info.message = `${user} has been banned. This hasn't been logged in a text channel as log channel could not be found`;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
const log = new EmbedBuilder()
|
||||
.setColor('#FF0000')
|
||||
.setAuthor({
|
||||
name: `Banned ${user.tag}`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'User', value: `${user}`, inline: true },
|
||||
{ name: 'Moderator', value: `${mod}`, inline: true },
|
||||
{ name: 'Reason', value: reason },
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${user.id}` });
|
||||
|
||||
await logChannel.send({ embeds: [log] });
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
312
src/commands/mod/ban/tempBan.ts
Normal file
312
src/commands/mod/ban/tempBan.ts
Normal file
@@ -0,0 +1,312 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { Duration, DurationFormatter } from '@sapphire/time-utilities';
|
||||
import type { User, Snowflake, TextChannel, Guild } from 'discord.js';
|
||||
import { EmbedBuilder, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { addTempBan, checkTempBan } from '#utils/database/tempBan';
|
||||
import { addEmptyUser, updateUser } from '#utils/database/dbExistingUser';
|
||||
|
||||
export class TempBanCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'tempban',
|
||||
description: 'Bans a user for a certain amount of time',
|
||||
preconditions: ['RestrictedAccessOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to ban')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('duration')
|
||||
.setDescription('How long to ban the user for')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('Note about the user')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const duration = interaction.options.getString('duration', true);
|
||||
const reason = interaction.options.getString('reason', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const time = new Duration(duration);
|
||||
|
||||
if (Number.isNaN(time.offset)) {
|
||||
await interaction.reply({
|
||||
content: 'Invalid ban duration input',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const ban = await this.ban(user.id, mod.id, time, reason, guild);
|
||||
|
||||
await interaction.editReply({ content: ban.message });
|
||||
}
|
||||
|
||||
// Non Application Command method of banning a user
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
const arg = args.finished ? null : await args.rest('string');
|
||||
const mod = message.author;
|
||||
|
||||
if (arg === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Ban reason was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const { duration, reason } = this.findTimeAndReason(arg);
|
||||
|
||||
if (Number.isNaN(duration.offset)) {
|
||||
await message.react('❌');
|
||||
await message.reply('Invalid time length for ban!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (reason.length === 0) {
|
||||
await message.react('❌');
|
||||
await message.reply('Reason was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.channel.id !== IDs.channels.restricted.moderators) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
`You can only run this command in <#${IDs.channels.restricted.moderators}> ` +
|
||||
'or alternatively use the slash command!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const ban = await this.ban(user.id, mod.id, duration, reason, guild);
|
||||
|
||||
await message.reply(ban.message);
|
||||
await message.react(ban.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private findTimeAndReason(args: string) {
|
||||
const info = {
|
||||
duration: new Duration(''),
|
||||
reason: '',
|
||||
};
|
||||
|
||||
if (Number.isNaN(new Duration(args).offset)) {
|
||||
return info;
|
||||
}
|
||||
|
||||
const spliced = args.split(' ');
|
||||
let time = '';
|
||||
for (let i = 0; i < spliced.length; i += 1) {
|
||||
if (!Number.isNaN(new Duration(spliced[i]).offset)) {
|
||||
time += spliced[i];
|
||||
} else {
|
||||
info.reason = args.slice(args.indexOf(spliced[i]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (time.length === 0) {
|
||||
return info;
|
||||
}
|
||||
|
||||
info.duration = new Duration(time);
|
||||
return info;
|
||||
}
|
||||
|
||||
private async ban(
|
||||
userId: Snowflake,
|
||||
modId: Snowflake,
|
||||
time: Duration,
|
||||
reason: string,
|
||||
guild: Guild,
|
||||
) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
|
||||
const banLength = new DurationFormatter().format(time.offset);
|
||||
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
|
||||
if (user === undefined) {
|
||||
user = (await guild.client.users.fetch(userId)) as User;
|
||||
}
|
||||
|
||||
// Gets mod's GuildMember
|
||||
const mod = guild.members.cache.get(modId);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (mod === undefined) {
|
||||
info.message = 'Error fetching mod!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (await checkTempBan(userId)) {
|
||||
info.message = `${user} is already temp banned!`;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check if mod is in database
|
||||
await updateUser(mod);
|
||||
|
||||
// Gets guildMember
|
||||
let member = guild.members.cache.get(userId);
|
||||
|
||||
if (member === undefined) {
|
||||
member = await guild.members.fetch(userId).catch(() => undefined);
|
||||
}
|
||||
|
||||
if (member !== undefined) {
|
||||
// Checks if the user is not restricted
|
||||
if (member.roles.cache.has(IDs.roles.vegan.vegan)) {
|
||||
info.message = 'You need to restrict the user first!';
|
||||
return info;
|
||||
}
|
||||
|
||||
await updateUser(member);
|
||||
|
||||
// Send DM for reason of ban
|
||||
await member
|
||||
.send(
|
||||
`You have been temporarily banned from ARA for ${banLength}. Reason: ${reason}` +
|
||||
'\n\nhttps://vbcamp.org/ARA',
|
||||
)
|
||||
.catch(() => {});
|
||||
|
||||
// Ban the user
|
||||
await member.ban({ reason });
|
||||
} else {
|
||||
await addEmptyUser(userId);
|
||||
}
|
||||
|
||||
// Add ban to database
|
||||
await addTempBan(userId, modId, time.fromNow, reason);
|
||||
|
||||
// Create scheduled task to unban
|
||||
this.container.tasks.create(
|
||||
'tempBan',
|
||||
{
|
||||
userId: user.id,
|
||||
guildId: guild.id,
|
||||
},
|
||||
time.offset,
|
||||
);
|
||||
|
||||
info.message = `${user} has been temporarily banned for ${banLength}.`;
|
||||
info.success = true;
|
||||
|
||||
// Log the ban
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(
|
||||
IDs.channels.logs.restricted,
|
||||
)) as TextChannel | undefined;
|
||||
if (logChannel === undefined) {
|
||||
this.container.logger.error(
|
||||
'Temp Ban Error: Could not fetch log channel',
|
||||
);
|
||||
info.message =
|
||||
`${user} has been temporarily banned for ${banLength}. ` +
|
||||
"This hasn't been logged in a text channel as log channel could not be found";
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
const log = new EmbedBuilder()
|
||||
.setColor('#FF0000')
|
||||
.setAuthor({
|
||||
name: `Temp Banned ${user.tag}`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'User', value: `${user}`, inline: true },
|
||||
{ name: 'Moderator', value: `${mod}`, inline: true },
|
||||
{ name: 'Duration', value: banLength },
|
||||
{ name: 'Reason', value: reason },
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${user.id}` });
|
||||
|
||||
await logChannel.send({ embeds: [log] });
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
223
src/commands/mod/ban/unban.ts
Normal file
223
src/commands/mod/ban/unban.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type {
|
||||
User,
|
||||
Message,
|
||||
Snowflake,
|
||||
TextChannel,
|
||||
Guild,
|
||||
GuildBan,
|
||||
} from 'discord.js';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { removeBan, checkBan, addBan } from '#utils/database/ban';
|
||||
import { checkTempBan, removeTempBan } from '#utils/database/tempBan';
|
||||
import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser';
|
||||
|
||||
export class UnbanCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'unban',
|
||||
description: 'Unbans a user',
|
||||
preconditions: ['RestrictedAccessOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to unban')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const unban = await this.unban(user.id, mod.id, guild);
|
||||
|
||||
await interaction.editReply({ content: unban.message });
|
||||
}
|
||||
|
||||
// Non Application Command method of banning a user
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const unban = await this.unban(user.id, mod.id, guild);
|
||||
|
||||
await message.reply(unban.message);
|
||||
await message.react(unban.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async unban(userId: Snowflake, modId: Snowflake, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
|
||||
// Gets mod's GuildMember
|
||||
const mod = guild.members.cache.get(modId);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (mod === undefined) {
|
||||
info.message = 'Error fetching mod!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check if mod is in database
|
||||
await addExistingUser(mod);
|
||||
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
|
||||
if (user === undefined) {
|
||||
user = await guild.client.users.fetch(userId);
|
||||
if (user === undefined) {
|
||||
info.message = 'Could not fetch the user!';
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
let dbBan = await checkBan(userId);
|
||||
const dbTempBan = await checkTempBan(userId);
|
||||
|
||||
if (!dbBan && !dbTempBan) {
|
||||
let ban: GuildBan;
|
||||
try {
|
||||
ban = await guild.bans.fetch(userId);
|
||||
} catch {
|
||||
try {
|
||||
ban = await guild.bans.fetch({ user, force: true });
|
||||
} catch {
|
||||
info.message = `${user} is not banned.`;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
let { reason } = ban;
|
||||
|
||||
if (reason === null || reason === undefined) {
|
||||
reason = '';
|
||||
}
|
||||
|
||||
// Check if user and mod are on the database
|
||||
await addEmptyUser(user.id);
|
||||
|
||||
// Add missing ban
|
||||
await addBan(
|
||||
userId,
|
||||
modId,
|
||||
`(Mod who banned is not accurate) - ${reason}`,
|
||||
);
|
||||
dbBan = true;
|
||||
}
|
||||
|
||||
// Unban the user
|
||||
await guild.members.unban(user).catch(() => {});
|
||||
|
||||
if (dbBan) {
|
||||
// Add unban to database
|
||||
await removeBan(user.id, mod.user.id);
|
||||
} else if (dbTempBan) {
|
||||
await removeTempBan(user.id, mod.user.id);
|
||||
}
|
||||
|
||||
info.message = `${user} has been unbanned.`;
|
||||
info.success = true;
|
||||
|
||||
// Log unban
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(
|
||||
IDs.channels.logs.restricted,
|
||||
)) as TextChannel | undefined;
|
||||
if (logChannel === undefined) {
|
||||
this.container.logger.error('Ban Error: Could not fetch log channel');
|
||||
info.message = `${user} has been banned. This hasn't been logged in a text channel as log channel could not be found`;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
const log = new EmbedBuilder()
|
||||
.setColor('#28A745')
|
||||
.setAuthor({
|
||||
name: `Unbanned ${user.tag}`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'User', value: `${user}`, inline: true },
|
||||
{ name: 'Moderator', value: `${mod}`, inline: true },
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${user.id}` });
|
||||
|
||||
await logChannel.send({ embeds: [log] });
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
285
src/commands/mod/diversity.ts
Normal file
285
src/commands/mod/diversity.ts
Normal file
@@ -0,0 +1,285 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// TODO This file needs a MASSIVE refactor
|
||||
|
||||
import { Args, container, RegisterBehavior } from '@sapphire/framework';
|
||||
import { Subcommand } from '@sapphire/plugin-subcommands';
|
||||
import {
|
||||
ChannelType,
|
||||
GuildMember,
|
||||
Message,
|
||||
PermissionsBitField,
|
||||
} from 'discord.js';
|
||||
import type { TextChannel, Snowflake } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
|
||||
export class DiversityCommand extends Subcommand {
|
||||
public constructor(context: Subcommand.Context, options: Subcommand.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'diversity',
|
||||
aliases: ['di', 'div'],
|
||||
subcommands: [
|
||||
{
|
||||
name: 'role',
|
||||
default: true,
|
||||
chatInputRun: 'roleCommand',
|
||||
messageRun: 'roleMessage',
|
||||
},
|
||||
{
|
||||
name: 'toggleopen',
|
||||
chatInputRun: 'toggleOpen',
|
||||
},
|
||||
],
|
||||
description: 'Commands for the Diversity Coordinators',
|
||||
preconditions: ['DiversityCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Subcommand.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('role')
|
||||
.setDescription('Gives/removes the diversity role')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give/remove diversity to')
|
||||
.setRequired(true),
|
||||
),
|
||||
)
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('toggleopen')
|
||||
.setDescription(
|
||||
'Toggles read-only for vegans in diversity section',
|
||||
),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async toggleOpen(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
// Check if guild is not null
|
||||
if (interaction.guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Guild not found!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the channel
|
||||
const channel = interaction.guild.channels.cache.get(interaction.channelId);
|
||||
// Check if channel is not undefined
|
||||
if (channel === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Channel not found!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if channel is text
|
||||
if (channel.type !== ChannelType.GuildText) {
|
||||
await interaction.reply({
|
||||
content: 'Channel is not a text channel!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Converts GuildBasedChannel to TextChannel
|
||||
const channelText = channel as TextChannel;
|
||||
|
||||
// Check if the command was run in the diversity section
|
||||
if (channel.parentId !== IDs.categories.diversity) {
|
||||
await interaction.reply({
|
||||
content: 'Command was not run in the Diversity section!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Checks if the channel is open
|
||||
const open = channel
|
||||
.permissionsFor(IDs.roles.vegan.vegan)!
|
||||
.has([PermissionsBitField.Flags.SendMessages]);
|
||||
|
||||
// Toggle send message in channel
|
||||
await channelText.permissionOverwrites.edit(IDs.roles.vegan.vegan, {
|
||||
SendMessages: !open,
|
||||
});
|
||||
|
||||
await interaction.reply({
|
||||
content: `${!open ? 'Opened' : 'Closed'} this channel.`,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
|
||||
public async roleCommand(
|
||||
interaction: Subcommand.ChatInputCommandInteraction,
|
||||
) {
|
||||
// TODO add database updates
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user');
|
||||
const mod = interaction.member;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (user === null || guild === null || mod === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Gets guildMember whilst removing the ability of each other variables being null
|
||||
const guildMember = guild.members.cache.get(user.id);
|
||||
const diversity = guild.roles.cache.get(IDs.roles.staff.diversity);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (guildMember === undefined || diversity === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Checks if the user has Diversity and to give them or remove them based on if they have it
|
||||
if (guildMember.roles.cache.has(IDs.roles.staff.diversity)) {
|
||||
// Remove the Diversity role from the user
|
||||
await guildMember.roles.remove(diversity);
|
||||
await this.threadManager(guildMember.id, false);
|
||||
await interaction.reply({
|
||||
content: `Removed the ${diversity.name} role from ${user}`,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Add Diversity Team role to the user
|
||||
await guildMember.roles.add(diversity);
|
||||
await this.threadManager(guildMember.id, true);
|
||||
await interaction.reply({
|
||||
content: `Gave ${user} the ${diversity.name} role!`,
|
||||
fetchReply: true,
|
||||
});
|
||||
await user
|
||||
.send(`You have been given the ${diversity.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
public async roleMessage(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: GuildMember;
|
||||
try {
|
||||
user = await args.pick('member');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.member;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Diversity coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const diversity = guild.roles.cache.get(IDs.roles.staff.diversity);
|
||||
|
||||
if (diversity === undefined) {
|
||||
await message.react('❌');
|
||||
await message.reply('Role not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Checks if the user has Diversity and to give them or remove them based on if they have it
|
||||
if (user.roles.cache.has(IDs.roles.staff.diversity)) {
|
||||
// Remove the Diversity Team role from the user
|
||||
await user.roles.remove(diversity);
|
||||
await this.threadManager(user.id, false);
|
||||
await message.reply({
|
||||
content: `Removed the ${diversity.name} role from ${user}`,
|
||||
});
|
||||
} else {
|
||||
// Give Diversity Team role to the user
|
||||
await user.roles.add(diversity);
|
||||
await this.threadManager(user.id, true);
|
||||
await message.reply({
|
||||
content: `Gave ${user} the ${diversity.name} role!`,
|
||||
});
|
||||
await user
|
||||
.send(`You have been given the ${diversity.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
await message.react('✅');
|
||||
}
|
||||
|
||||
private async threadManager(member: Snowflake, add: boolean) {
|
||||
const thread = await container.client.channels.fetch(
|
||||
IDs.channels.diversity.diversity,
|
||||
);
|
||||
if (thread === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!thread.isThread()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (add) {
|
||||
await thread.members.add(member);
|
||||
return;
|
||||
}
|
||||
|
||||
await thread.members.remove(member);
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { TextChannel } from 'discord.js';
|
||||
import IDs from '../../utils/ids';
|
||||
|
||||
class ToggleOpenCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'diversity',
|
||||
description: 'Commands for the Diversity Coordinators',
|
||||
preconditions: ['DiversityCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addSubcommand((command) => command.setName('toggleopen')
|
||||
.setDescription('Toggles read-only for vegans in diversity section')),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
const subcommand = interaction.options.getSubcommand(true);
|
||||
|
||||
// Checks what subcommand was run
|
||||
switch (subcommand) {
|
||||
case 'toggleopen': {
|
||||
await this.toggleOpen(interaction);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
// If subcommand is invalid
|
||||
await interaction.reply({
|
||||
content: 'Invalid sub command!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async toggleOpen(interaction: Command.ChatInputInteraction) {
|
||||
// Check if guild is not null
|
||||
if (interaction.guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Guild not found!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the channel
|
||||
const channel = interaction.guild.channels.cache.get(interaction.channelId);
|
||||
// Check if channel is not undefined
|
||||
if (channel === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Channel not found!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if channel is text
|
||||
if (!channel.isText()) {
|
||||
await interaction.reply({
|
||||
content: 'Channel is not a text channel!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Converts GuildBasedChannel to TextChannel
|
||||
const channelText = channel as TextChannel;
|
||||
|
||||
// Check if the command was run in the diversity section
|
||||
if (channel.parentId !== IDs.categories.diversity) {
|
||||
await interaction.reply({
|
||||
content: 'Command was not run in the Diversity section!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Checks if the channel is open
|
||||
const open = channel.permissionsFor(IDs.roles.vegan.vegan)!.has(['SEND_MESSAGES']);
|
||||
|
||||
// Toggle send message in channel
|
||||
await channelText.permissionOverwrites.edit(IDs.roles.vegan.vegan, { SEND_MESSAGES: !open });
|
||||
|
||||
await interaction.reply({
|
||||
content: `${!open ? 'Opened' : 'Closed'} this channel.`,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ToggleOpenCommand;
|
||||
176
src/commands/mod/moveall.ts
Normal file
176
src/commands/mod/moveall.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
I created this command on the 13:30 Newcastle - Kings Cross train.
|
||||
Idk why I wanted to say that, but I felt like it was a cool fact
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Message } from 'discord.js';
|
||||
import { ChannelType } from 'discord.js';
|
||||
|
||||
export class MoveAllCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'moveall',
|
||||
aliases: ['mvall'],
|
||||
description: 'Moves everyone from one voice channel to the specified one',
|
||||
preconditions: [['CoordinatorOnly', 'ModOnly']],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addChannelOption((option) =>
|
||||
option
|
||||
.setName('channel')
|
||||
.setDescription('The channel to move everyone to')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const channel = interaction.options.getChannel('channel', true);
|
||||
const { member } = interaction;
|
||||
const { guild } = interaction;
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
if (
|
||||
channel.type !== ChannelType.GuildVoice &&
|
||||
channel.type !== ChannelType.GuildStageVoice
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: 'The channel you provided is not a voice channel!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.editReply({
|
||||
content: 'Error fetching guild!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (member === null) {
|
||||
await interaction.editReply({
|
||||
content: 'Error fetching your user',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = guild.members.cache.get(member.user.id);
|
||||
|
||||
if (mod === undefined) {
|
||||
await interaction.editReply({
|
||||
content: 'Error fetching user from guild',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (mod.voice.channelId === null) {
|
||||
await interaction.editReply({
|
||||
content: 'You need to be in a voice channel to run this command!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const voice = guild.channels.cache.get(mod.voice.channelId);
|
||||
|
||||
if (voice === undefined || !voice.isVoiceBased()) {
|
||||
await interaction.editReply({
|
||||
content: 'Error fetching your current voice channel!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
voice.members.forEach((memberVC) => {
|
||||
memberVC.voice.setChannel(channel.id);
|
||||
});
|
||||
|
||||
await interaction.editReply({
|
||||
content: `Successfully moved ${voice.members.size} members to <#${channel.id}>!`,
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
const channel = await args.pick('channel');
|
||||
|
||||
if (!channel.isVoiceBased()) {
|
||||
await message.react('❌');
|
||||
await message.reply('You did not provide a voice based channel!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.member;
|
||||
const { guild } = message;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Could not find your user!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (mod.voice.channelId === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'You need to be in a voice channel to run this command!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Could not find guild!');
|
||||
return;
|
||||
}
|
||||
|
||||
const voice = guild.channels.cache.get(mod.voice.channelId);
|
||||
|
||||
if (voice === undefined || !voice.isVoiceBased()) {
|
||||
await message.react('❌');
|
||||
await message.reply('Could not fetch current voice channel!');
|
||||
return;
|
||||
}
|
||||
|
||||
voice.members.forEach((member) => {
|
||||
member.voice.setChannel(channel.id);
|
||||
});
|
||||
|
||||
await message.reply(
|
||||
`Successfully moved ${voice.members.size} members to <#${channel.id}>!`,
|
||||
);
|
||||
|
||||
await message.react('✅');
|
||||
}
|
||||
}
|
||||
139
src/commands/mod/rename.ts
Normal file
139
src/commands/mod/rename.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { GuildMember, Message } from 'discord.js';
|
||||
|
||||
export class RenameUserCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'rename',
|
||||
aliases: ['ru', 'nick'],
|
||||
description: 'Changes the nickname for the user',
|
||||
preconditions: [['CoordinatorOnly', 'ModOnly']],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to change nickname')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('nickname')
|
||||
.setDescription('The nickname to give the user')
|
||||
.setMaxLength(32),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// TODO add database updates
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const nickname = interaction.options.getString('nickname');
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Gets guildMember whilst removing the ability of each other variables being null
|
||||
const member = guild.members.cache.get(user.id);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (member === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Change nickname
|
||||
try {
|
||||
await member.setNickname(nickname);
|
||||
} catch {
|
||||
await interaction.reply({
|
||||
content: "Bot doesn't have permission to change the user's name!",
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
await interaction.reply({
|
||||
content: `Changed ${user}'s nickname`,
|
||||
fetchReply: true,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let member: GuildMember;
|
||||
try {
|
||||
member = await args.pick('member');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const nickname = args.finished ? null : await args.rest('string');
|
||||
|
||||
if (nickname != null && nickname.length > 32) {
|
||||
await message.react('❌');
|
||||
await message.reply('Nickname is too long!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await member.setNickname(nickname);
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
"Bot doesn't have permission to change the user's name!",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await message.react('✅');
|
||||
}
|
||||
}
|
||||
375
src/commands/mod/restriction/restrict.ts
Normal file
375
src/commands/mod/restriction/restrict.ts
Normal file
@@ -0,0 +1,375 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
Args,
|
||||
Command,
|
||||
RegisterBehavior,
|
||||
container,
|
||||
} from '@sapphire/framework';
|
||||
import {
|
||||
ChannelType,
|
||||
EmbedBuilder,
|
||||
PermissionsBitField,
|
||||
time,
|
||||
} from 'discord.js';
|
||||
import type { User, Message, TextChannel, Guild, Snowflake } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import {
|
||||
addEmptyUser,
|
||||
updateUser,
|
||||
fetchRoles,
|
||||
} from '#utils/database/dbExistingUser';
|
||||
import { restrict, checkActive } from '#utils/database/restriction';
|
||||
import { randint } from '#utils/maths';
|
||||
import { blockedRolesAfterRestricted } from '#utils/blockedRoles';
|
||||
|
||||
export async function restrictRun(
|
||||
userId: Snowflake,
|
||||
modId: Snowflake,
|
||||
reason: string,
|
||||
guild: Guild,
|
||||
tolerance = false,
|
||||
) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
|
||||
if (user === undefined) {
|
||||
user = await guild.client.users.fetch(userId);
|
||||
if (user === undefined) {
|
||||
info.message = 'Error fetching user';
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets mod's GuildMember
|
||||
const mod = guild.members.cache.get(modId);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (mod === undefined) {
|
||||
info.message = 'Error fetching mod';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check if mod is in database
|
||||
await updateUser(mod);
|
||||
|
||||
if (await checkActive(userId)) {
|
||||
info.message = `<@${userId}> is already restricted!`;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Gets guildMember
|
||||
let member = guild.members.cache.get(userId);
|
||||
|
||||
if (member === undefined) {
|
||||
member = await guild.members.fetch(userId).catch(() => undefined);
|
||||
}
|
||||
|
||||
const restrictRoles = IDs.roles.restrictions.restricted;
|
||||
|
||||
let section = tolerance ? randint(3, 4) : randint(1, 2);
|
||||
|
||||
if (member !== undefined) {
|
||||
// Checks if the user is not restricted
|
||||
if (member.roles.cache.hasAny(...restrictRoles)) {
|
||||
info.message = `${member} is already restricted!`;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check if user and mod are on the database
|
||||
await updateUser(member);
|
||||
|
||||
if (member.roles.cache.has(IDs.roles.vegan.vegan)) {
|
||||
section = 5;
|
||||
}
|
||||
|
||||
await member.roles.add(restrictRoles[section - 1]);
|
||||
|
||||
if (member.roles.cache.has(IDs.roles.vegan.vegan)) {
|
||||
const voiceChannel = await guild.channels.create({
|
||||
name: 'Restricted Voice Channel',
|
||||
type: ChannelType.GuildVoice,
|
||||
parent: IDs.categories.restricted,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
deny: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: member.id,
|
||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.staff.restricted,
|
||||
allow: [
|
||||
PermissionsBitField.Flags.SendMessages,
|
||||
PermissionsBitField.Flags.ViewChannel,
|
||||
PermissionsBitField.Flags.Connect,
|
||||
PermissionsBitField.Flags.MuteMembers,
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let restrictedChannel: TextChannel;
|
||||
let bannedName = false;
|
||||
try {
|
||||
restrictedChannel = await guild.channels.create({
|
||||
name: `⛔┃${member.user.username}-restricted`,
|
||||
type: ChannelType.GuildText,
|
||||
topic: `Restricted channel. ${member.id} ${voiceChannel.id} (Please do not change this)`,
|
||||
parent: IDs.categories.restricted,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
allow: [PermissionsBitField.Flags.ReadMessageHistory],
|
||||
deny: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: member.id,
|
||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.staff.restricted,
|
||||
allow: [
|
||||
PermissionsBitField.Flags.SendMessages,
|
||||
PermissionsBitField.Flags.ViewChannel,
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
} catch {
|
||||
restrictedChannel = await guild.channels.create({
|
||||
name: `⛔┃${member.user.id}-restricted`,
|
||||
type: ChannelType.GuildText,
|
||||
topic: `Restricted channel. ${member.id} ${voiceChannel.id} (Please do not change this)`,
|
||||
parent: IDs.categories.restricted,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
allow: [PermissionsBitField.Flags.ReadMessageHistory],
|
||||
deny: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: member.id,
|
||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.staff.restricted,
|
||||
allow: [
|
||||
PermissionsBitField.Flags.SendMessages,
|
||||
PermissionsBitField.Flags.ViewChannel,
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
bannedName = true;
|
||||
}
|
||||
|
||||
if (!bannedName) {
|
||||
await voiceChannel.setName(`${member.user.username}-restricted`);
|
||||
} else {
|
||||
await voiceChannel.setName(`${member.user.id}-restricted`);
|
||||
}
|
||||
|
||||
const joinTime = time(member.joinedAt!);
|
||||
const registerTime = time(member.user.createdAt);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(member.displayHexColor)
|
||||
.setTitle(`Restricted channel for ${member.user.username}`)
|
||||
.setDescription(`${member}`)
|
||||
.setThumbnail(member.user.displayAvatarURL())
|
||||
.addFields(
|
||||
{ name: 'Joined:', value: `${joinTime}`, inline: true },
|
||||
{ name: 'Created:', value: `${registerTime}`, inline: true },
|
||||
);
|
||||
|
||||
await restrictedChannel.send({ embeds: [embed] });
|
||||
}
|
||||
|
||||
await member.roles.remove(blockedRolesAfterRestricted);
|
||||
} else {
|
||||
await addEmptyUser(userId);
|
||||
const dbRoles = await fetchRoles(userId);
|
||||
if (dbRoles.includes(IDs.roles.vegan.vegan)) {
|
||||
section = 5;
|
||||
}
|
||||
}
|
||||
|
||||
if (member !== undefined && member.voice.channelId !== null) {
|
||||
await member.voice.disconnect();
|
||||
}
|
||||
|
||||
// Restrict the user on the database
|
||||
await restrict(userId, modId, reason, section);
|
||||
|
||||
info.message = `Restricted ${user}`;
|
||||
info.success = true;
|
||||
|
||||
// DM the reason
|
||||
|
||||
const dmEmbed = new EmbedBuilder()
|
||||
.setColor('#FF6700')
|
||||
.setAuthor({
|
||||
name: "You've been restricted!",
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields({ name: 'Reason', value: reason })
|
||||
.setTimestamp();
|
||||
|
||||
await user.send({ embeds: [dmEmbed] }).catch(() => {});
|
||||
|
||||
// Log the ban
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(IDs.channels.logs.restricted)) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
if (logChannel === undefined) {
|
||||
container.logger.error('Restrict Error: Could not fetch log channel');
|
||||
info.message = `Restricted ${user} but could not find the log channel. This has been logged to the database.`;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
const message = new EmbedBuilder()
|
||||
.setColor('#FF6700')
|
||||
.setAuthor({
|
||||
name: `Restricted ${user.tag}`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'User', value: `${user}`, inline: true },
|
||||
{ name: 'Moderator', value: `${mod}`, inline: true },
|
||||
{ name: 'Reason', value: reason },
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${userId}` });
|
||||
|
||||
await logChannel.send({ embeds: [message] });
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
export class RestrictCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'restrict',
|
||||
aliases: ['r', 'rest', 'rr', 'rv'],
|
||||
description: 'Restricts a user',
|
||||
preconditions: ['ModOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to restrict')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('Reason for restricting the user')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const reason = interaction.options.getString('reason', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await restrictRun(user?.id, mod.id, reason, guild);
|
||||
|
||||
await interaction.editReply({
|
||||
content: info.message,
|
||||
});
|
||||
}
|
||||
|
||||
// Non Application Command method of banning a user
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
const reason = args.finished ? null : await args.rest('string');
|
||||
const mod = message.author;
|
||||
|
||||
if (reason === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Restrict reason was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await restrictRun(user?.id, mod.id, reason, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
}
|
||||
243
src/commands/mod/restriction/restrictLogs.ts
Normal file
243
src/commands/mod/restriction/restrictLogs.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { ChannelType, EmbedBuilder } from 'discord.js';
|
||||
import type { Message, TextChannel, Guild, Snowflake } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { getRestrictions } from '#utils/database/restriction';
|
||||
import { checkStaff } from '#utils/checker';
|
||||
|
||||
export class RestrictLogsCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'restrictlogs',
|
||||
description: 'Unrestricts a user',
|
||||
preconditions: ['ModOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to check restriction logs for'),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user');
|
||||
let { channel } = interaction;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null || channel === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild or channel!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let userId: Snowflake | null = null;
|
||||
|
||||
if (user !== undefined && user !== null) {
|
||||
userId = user.id;
|
||||
}
|
||||
|
||||
let staffChannel = false;
|
||||
if (channel.type === ChannelType.GuildText) {
|
||||
channel = channel as TextChannel;
|
||||
staffChannel = checkStaff(channel);
|
||||
|
||||
if (userId === null) {
|
||||
let topic: string[];
|
||||
|
||||
if (channel.parentId === IDs.categories.modMail) {
|
||||
// Checks if the channel topic has the user's snowflake
|
||||
if (channel.topic !== null) {
|
||||
topic = channel.topic.split(' ');
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
userId = topic[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (userId === null) {
|
||||
await interaction.reply({
|
||||
content: 'User could not be found or was not provided!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.unRestrictRun(userId, guild);
|
||||
|
||||
await interaction.reply({
|
||||
embeds: info.embeds,
|
||||
content: info.message,
|
||||
fetchReply: true,
|
||||
ephemeral: !staffChannel,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Non Application Command method of banning a user
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let userId: Snowflake | null;
|
||||
try {
|
||||
const user = await args.pick('user');
|
||||
userId = user.id;
|
||||
} catch {
|
||||
userId = null;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (userId === null) {
|
||||
const { channel } = message;
|
||||
|
||||
if (channel.type !== ChannelType.GuildText) {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
let topic: string[];
|
||||
|
||||
if (channel.parentId === IDs.categories.modMail) {
|
||||
// Checks if the channel topic has the user's snowflake
|
||||
if (channel.topic !== null) {
|
||||
topic = channel.topic.split(' ');
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
userId = topic[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (userId === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.unRestrictRun(userId, guild);
|
||||
|
||||
await message.reply({ content: info.message, embeds: info.embeds });
|
||||
if (!info.success) {
|
||||
await message.react('❌');
|
||||
}
|
||||
}
|
||||
|
||||
private async unRestrictRun(userId: Snowflake, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
embeds: [] as EmbedBuilder[],
|
||||
success: false,
|
||||
};
|
||||
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
|
||||
if (user === undefined) {
|
||||
user = await guild.client.users.fetch(userId);
|
||||
if (user === undefined) {
|
||||
info.message = 'Error fetching user';
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
const restrictions = await getRestrictions(userId);
|
||||
|
||||
if (restrictions.length === 0) {
|
||||
info.message = `${user} user has no restrict logs on them.`;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Creates the embed to display the restrictions
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#FF6700')
|
||||
.setTitle(`${restrictions.length} restrictions for ${user.tag}`)
|
||||
.setThumbnail(user.displayAvatarURL())
|
||||
.setFooter({ text: `ID: ${userId}` });
|
||||
|
||||
// Add up to 10 of the latest restrictions to the embed
|
||||
for (
|
||||
let i = restrictions.length > 10 ? restrictions.length - 10 : 0;
|
||||
i < restrictions.length;
|
||||
i += 1
|
||||
) {
|
||||
// Get mod names
|
||||
let restMod = restrictions[i].modId;
|
||||
const restModMember = guild.members.cache.get(restMod);
|
||||
if (restModMember !== undefined) {
|
||||
restMod = restModMember.displayName;
|
||||
}
|
||||
let endRestMod = restrictions[i].endModId;
|
||||
if (endRestMod !== null) {
|
||||
const endRestModMember = guild.members.cache.get(endRestMod);
|
||||
if (endRestModMember !== undefined) {
|
||||
endRestMod = endRestModMember.displayName;
|
||||
}
|
||||
}
|
||||
|
||||
let restTitle = `Restriction: ${i + 1} | Restricted by: ${restMod} | `;
|
||||
|
||||
if (endRestMod !== null) {
|
||||
restTitle += `Unrestricted by: ${endRestMod} | `;
|
||||
} else {
|
||||
restTitle += 'Currently Restricted | ';
|
||||
}
|
||||
|
||||
restTitle += `Date: <t:${Math.floor(
|
||||
restrictions[i].startTime.getTime() / 1000,
|
||||
)}>`;
|
||||
|
||||
embed.addFields({
|
||||
name: restTitle,
|
||||
value: restrictions[i].reason,
|
||||
});
|
||||
}
|
||||
|
||||
info.embeds.push(embed);
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
120
src/commands/mod/restriction/restrictTolerance.ts
Normal file
120
src/commands/mod/restriction/restrictTolerance.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { User, Message } from 'discord.js';
|
||||
import { restrictRun } from './restrict';
|
||||
|
||||
export class RestrictToleranceCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'restricttolerance',
|
||||
aliases: ['rt'],
|
||||
description: 'Restricts a user for bigoted reasons',
|
||||
preconditions: ['ModOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to restrict')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('Reason for restricting the user')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const reason = interaction.options.getString('reason', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await restrictRun(user?.id, mod.id, reason, guild, true);
|
||||
|
||||
await interaction.editReply({
|
||||
content: info.message,
|
||||
});
|
||||
}
|
||||
|
||||
// Non Application Command method of banning a user
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
const reason = args.finished ? null : await args.rest('string');
|
||||
const mod = message.author;
|
||||
|
||||
if (reason === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Restrict reason was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await restrictRun(user?.id, mod.id, reason, guild, true);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
}
|
||||
178
src/commands/mod/restriction/restrictTools.ts
Normal file
178
src/commands/mod/restriction/restrictTools.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { RegisterBehavior } from '@sapphire/framework';
|
||||
import { Subcommand } from '@sapphire/plugin-subcommands';
|
||||
import type { TextChannel } from 'discord.js';
|
||||
import { CategoryChannel, ChannelType } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
|
||||
export class RestrictToolsCommand extends Subcommand {
|
||||
public constructor(context: Subcommand.Context, options: Subcommand.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'restricttools',
|
||||
subcommands: [
|
||||
{
|
||||
name: 'channel',
|
||||
type: 'group',
|
||||
entries: [{ name: 'delete', chatInputRun: 'deleteChannel' }],
|
||||
},
|
||||
],
|
||||
description: 'Tools for managing restrictions',
|
||||
preconditions: ['RestrictedAccessOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Subcommand.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addSubcommandGroup((group) =>
|
||||
group
|
||||
.setName('channel')
|
||||
.setDescription('Manages restricted channels')
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('delete')
|
||||
.setDescription('Deletes a restricted channel')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription("The user's channel to delete"),
|
||||
),
|
||||
),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public async deleteChannel(
|
||||
interaction: Subcommand.ChatInputCommandInteraction,
|
||||
) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user');
|
||||
const { guild, channel } = interaction;
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null || channel === null) {
|
||||
await interaction.editReply({
|
||||
content: 'Error fetching user!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let topic: string[];
|
||||
|
||||
if (user === null) {
|
||||
if (channel.type !== ChannelType.GuildText) {
|
||||
await interaction.editReply({
|
||||
content:
|
||||
'Please make sure you ran this command in the original restricted text channel!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel.parentId !== IDs.categories.restricted) {
|
||||
await interaction.editReply({
|
||||
content:
|
||||
'Please make sure you ran this command in the original restricted text channel!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
channel.id === IDs.channels.restricted.welcome ||
|
||||
channel.id === IDs.channels.restricted.moderators ||
|
||||
channel.id === IDs.channels.restricted.restricted ||
|
||||
channel.id === IDs.channels.restricted.tolerance
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: "You can't run this command these channels!",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel.topic === null) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error with this channel's topic!",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
topic = channel.topic.split(' ');
|
||||
await channel.delete();
|
||||
|
||||
const vcId = topic[3];
|
||||
const voiceChannel = guild.channels.cache.get(vcId);
|
||||
|
||||
if (
|
||||
voiceChannel !== undefined &&
|
||||
voiceChannel.parentId === IDs.categories.restricted
|
||||
) {
|
||||
await voiceChannel.delete();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const category = guild.channels.cache.get(IDs.categories.restricted) as
|
||||
| CategoryChannel
|
||||
| undefined;
|
||||
|
||||
if (category === undefined) {
|
||||
await interaction.editReply({
|
||||
content: 'Could not find category!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const textChannels = category.children.cache.filter(
|
||||
(c) => c.type === ChannelType.GuildText,
|
||||
);
|
||||
textChannels.forEach((c) => {
|
||||
const textChannel = c as TextChannel;
|
||||
// Checks if the channel topic has the user's snowflake
|
||||
if (textChannel.topic?.includes(user?.id)) {
|
||||
topic = textChannel.topic.split(' ');
|
||||
const vcId = topic[topic.indexOf(user?.id) + 1];
|
||||
const voiceChannel = guild.channels.cache.get(vcId);
|
||||
|
||||
if (
|
||||
voiceChannel !== undefined &&
|
||||
voiceChannel.parentId === IDs.categories.restricted
|
||||
) {
|
||||
voiceChannel.delete();
|
||||
}
|
||||
textChannel.delete();
|
||||
}
|
||||
});
|
||||
|
||||
await interaction.editReply({
|
||||
content: `Successfully deleted the channel for ${user}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
272
src/commands/mod/restriction/unrestrict.ts
Normal file
272
src/commands/mod/restriction/unrestrict.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { CategoryChannel, ChannelType, EmbedBuilder } from 'discord.js';
|
||||
import type { User, Message, TextChannel, Guild, Snowflake } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { fetchRoles, addExistingUser } from '#utils/database/dbExistingUser';
|
||||
import {
|
||||
unRestrict,
|
||||
checkActive,
|
||||
unRestrictLegacy,
|
||||
} from '#utils/database/restriction';
|
||||
|
||||
export class UnRestrictCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'unrestrict',
|
||||
aliases: ['ur', 'urv'],
|
||||
description: 'Unrestricts a user',
|
||||
preconditions: ['ModOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to unrestrict')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const info = await this.unRestrictRun(user?.id, mod.id, guild);
|
||||
|
||||
await interaction.editReply({
|
||||
content: info.message,
|
||||
});
|
||||
}
|
||||
|
||||
// Non Application Command method of banning a user
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const channelRun = message.channel;
|
||||
|
||||
const info = await this.unRestrictRun(
|
||||
user?.id,
|
||||
mod.id,
|
||||
guild,
|
||||
channelRun.id,
|
||||
);
|
||||
|
||||
if (!info.runInVeganRestrict) {
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
}
|
||||
|
||||
private async unRestrictRun(
|
||||
userId: Snowflake,
|
||||
modId: Snowflake,
|
||||
guild: Guild,
|
||||
channelRun: Snowflake | null = null,
|
||||
) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
runInVeganRestrict: false,
|
||||
};
|
||||
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
|
||||
if (user === undefined) {
|
||||
user = await guild.client.users.fetch(userId);
|
||||
if (user === undefined) {
|
||||
info.message = 'Error fetching user';
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets mod's GuildMember
|
||||
const mod = guild.members.cache.get(modId);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (mod === undefined) {
|
||||
info.message = 'Error fetching mod';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check if mod is in database
|
||||
await addExistingUser(mod);
|
||||
|
||||
// Gets guildMember
|
||||
let member = guild.members.cache.get(userId);
|
||||
|
||||
if (member === undefined) {
|
||||
member = await guild.members.fetch(userId).catch(() => undefined);
|
||||
}
|
||||
|
||||
if (member === undefined) {
|
||||
info.message = "Can't unrestrict the user as they are not on this server";
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check if user is in database
|
||||
await addExistingUser(member);
|
||||
|
||||
const restrictRoles = IDs.roles.restrictions.restricted;
|
||||
|
||||
// Checks if the user is not restricted
|
||||
if (!member.roles.cache.hasAny(...restrictRoles)) {
|
||||
info.message = `${user} is not restricted!`;
|
||||
return info;
|
||||
}
|
||||
|
||||
if (await checkActive(userId)) {
|
||||
const roles = await fetchRoles(userId);
|
||||
await member.roles.add(roles);
|
||||
// Unrestricts the user on the database
|
||||
await unRestrict(userId, modId);
|
||||
} else {
|
||||
let section = 1;
|
||||
for (let i = 0; i < restrictRoles.length; i += 1) {
|
||||
if (member.roles.cache.has(restrictRoles[i])) {
|
||||
section = i + 1;
|
||||
}
|
||||
}
|
||||
await member.roles.add(IDs.roles.nonvegan.nonvegan);
|
||||
// Unrestricts the user on the database but for restricts done on the old bot
|
||||
await unRestrictLegacy(userId, modId, section);
|
||||
}
|
||||
|
||||
await member.roles.remove(restrictRoles);
|
||||
|
||||
// Remove vegan restrict channels
|
||||
if (member.roles.cache.has(IDs.roles.vegan.vegan)) {
|
||||
const category = guild.channels.cache.get(IDs.categories.restricted) as
|
||||
| CategoryChannel
|
||||
| undefined;
|
||||
|
||||
let topic: string[];
|
||||
|
||||
if (category !== undefined) {
|
||||
const textChannels = category.children.cache.filter(
|
||||
(c) => c.type === ChannelType.GuildText,
|
||||
);
|
||||
textChannels.forEach((c) => {
|
||||
const textChannel = c as TextChannel;
|
||||
// Checks if the channel topic has the user's snowflake
|
||||
if (textChannel.topic?.includes(userId)) {
|
||||
if (textChannel.id === channelRun) {
|
||||
info.runInVeganRestrict = true;
|
||||
}
|
||||
topic = textChannel.topic.split(' ');
|
||||
const vcId = topic[topic.indexOf(userId) + 1];
|
||||
const voiceChannel = guild.channels.cache.get(vcId);
|
||||
|
||||
if (
|
||||
voiceChannel !== undefined &&
|
||||
voiceChannel.parentId === IDs.categories.restricted
|
||||
) {
|
||||
voiceChannel.delete();
|
||||
}
|
||||
textChannel.delete();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
info.success = true;
|
||||
|
||||
// Log the ban
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(
|
||||
IDs.channels.logs.restricted,
|
||||
)) as TextChannel | undefined;
|
||||
if (logChannel === undefined) {
|
||||
this.container.logger.error(
|
||||
'Restrict Error: Could not fetch log channel',
|
||||
);
|
||||
info.message = `Unrestricted ${user} but could not find the log channel. This has been logged to the database.`;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
const message = new EmbedBuilder()
|
||||
.setColor('#28A745')
|
||||
.setAuthor({
|
||||
name: `Unrestricted ${user.tag}`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'User', value: `${user}`, inline: true },
|
||||
{ name: 'Moderator', value: `${mod}`, inline: true },
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${userId}` });
|
||||
|
||||
await logChannel.send({ embeds: [message] });
|
||||
|
||||
info.message = `Unrestricted ${user}`;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
134
src/commands/mod/slowmode.ts
Normal file
134
src/commands/mod/slowmode.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Message, TextBasedChannel } from 'discord.js';
|
||||
import { ChannelType } from 'discord.js';
|
||||
import { Duration, DurationFormatter } from '@sapphire/time-utilities';
|
||||
import { isNumber } from '#utils/maths';
|
||||
|
||||
export class SlowmodeCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'slowmode',
|
||||
description: 'Sets slowmode for a channel',
|
||||
preconditions: ['ModOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('duration')
|
||||
.setDescription('Set the slowmode time')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const duration = interaction.options.getString('duration', true);
|
||||
const { channel } = interaction;
|
||||
|
||||
if (channel === null) {
|
||||
await interaction.reply({
|
||||
content: 'Could not fetch channel!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const slowmode = await this.slowmode(duration, channel);
|
||||
|
||||
await interaction.reply({ content: slowmode.message });
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
const duration = args.finished ? null : await args.rest('string');
|
||||
const { channel } = message;
|
||||
|
||||
if (duration === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Slowmode length was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const slowmode = await this.slowmode(duration, channel);
|
||||
|
||||
await message.reply(slowmode.message);
|
||||
await message.react(slowmode.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async slowmode(duration: string, channel: TextBasedChannel) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
if (channel.type !== ChannelType.GuildText) {
|
||||
info.message = 'Channel is not a text channel!';
|
||||
return info;
|
||||
}
|
||||
|
||||
let durationCheck = duration;
|
||||
|
||||
if (isNumber(durationCheck)) {
|
||||
durationCheck += 's';
|
||||
}
|
||||
|
||||
const durationParsed = new Duration(durationCheck);
|
||||
let time = 0;
|
||||
|
||||
if (Number.isNaN(durationParsed.offset)) {
|
||||
if (duration !== 'off') {
|
||||
info.message = 'Invalid time format!';
|
||||
return info;
|
||||
}
|
||||
time = 0;
|
||||
} else {
|
||||
time = durationParsed.offset;
|
||||
}
|
||||
|
||||
await channel.setRateLimitPerUser(time / 1000);
|
||||
|
||||
info.success = true;
|
||||
if (time === 0) {
|
||||
info.message = `${channel} is no longer in slowmode.`;
|
||||
return info;
|
||||
}
|
||||
|
||||
info.message = `${channel} has now been set to a post every ${new DurationFormatter().format(
|
||||
time,
|
||||
)}.`;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
124
src/commands/mod/softMute.ts
Normal file
124
src/commands/mod/softMute.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { GuildMember, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
|
||||
export class SoftMuteCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'softmute',
|
||||
aliases: ['sm'],
|
||||
description:
|
||||
'Prevent a user from reacting to a message by giving ' +
|
||||
'the soft mute role',
|
||||
preconditions: ['ModOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to soft mute')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// TODO add database updates
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching the guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Gets guildMember whilst removing the ability of each other variables being null
|
||||
const guildMember = guild.members.cache.get(user.id);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (guildMember === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (guildMember.roles.cache.has(IDs.roles.restrictions.softMute)) {
|
||||
await guildMember.roles.remove(IDs.roles.restrictions.softMute);
|
||||
await interaction.reply({
|
||||
content: `Removed soft muted for ${user}`,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await guildMember.roles.add(IDs.roles.restrictions.softMute);
|
||||
|
||||
await interaction.reply({
|
||||
content: `Soft muted ${user}`,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: GuildMember;
|
||||
try {
|
||||
user = await args.pick('member');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.roles.cache.has(IDs.roles.restrictions.softMute)) {
|
||||
await user.roles.remove(IDs.roles.restrictions.softMute);
|
||||
await message.reply(`Removed soft mute for ${user}`);
|
||||
} else {
|
||||
await user.roles.add(IDs.roles.restrictions.softMute);
|
||||
await message.reply(`Soft muted ${user}`);
|
||||
}
|
||||
|
||||
await message.react('✅');
|
||||
}
|
||||
}
|
||||
@@ -17,273 +17,198 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior, Args } from '@sapphire/framework';
|
||||
import { RegisterBehavior, Args } from '@sapphire/framework';
|
||||
import { Subcommand } from '@sapphire/plugin-subcommands';
|
||||
import {
|
||||
MessageEmbed, MessageActionRow, MessageButton, Constants, ButtonInteraction,
|
||||
ChannelType,
|
||||
EmbedBuilder,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonInteraction,
|
||||
ButtonStyle,
|
||||
} from 'discord.js';
|
||||
import type { Message, GuildMember } from 'discord.js';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import type { Message, GuildMember, TextChannel } from 'discord.js';
|
||||
import { isMessageInstance } from '@sapphire/discord.js-utilities';
|
||||
import { addExistingUser, userExists } from '../../utils/dbExistingUser';
|
||||
import IDs from '../../utils/ids';
|
||||
import { addExistingUser } from '#utils/database/dbExistingUser';
|
||||
import {
|
||||
addToDatabase,
|
||||
findNotes,
|
||||
getNote,
|
||||
deactivateNote,
|
||||
deactivateAllNotes,
|
||||
} from '#utils/database/sus';
|
||||
import { checkStaff } from '#utils/checker';
|
||||
import IDs from '#utils/ids';
|
||||
|
||||
// TODO add a check when they join the server to give the user the sus role again
|
||||
|
||||
async function addToDatabase(userId: string, modId: string, message: string) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Add the user to the database
|
||||
await prisma.sus.create({
|
||||
data: {
|
||||
user: {
|
||||
connect: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
mod: {
|
||||
connect: {
|
||||
id: modId,
|
||||
},
|
||||
},
|
||||
note: message,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
// Get a list of sus notes from the user
|
||||
async function findNotes(userId: string, active: boolean) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to get the specific user's sus notes
|
||||
const note = await prisma.sus.findMany({
|
||||
where: {
|
||||
userId,
|
||||
active,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
return note;
|
||||
}
|
||||
|
||||
// Get one note from the id
|
||||
async function getNote(noteId: number) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to get the specific user's sus notes
|
||||
const note = await prisma.sus.findUnique({
|
||||
where: {
|
||||
id: noteId,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
return note;
|
||||
}
|
||||
|
||||
async function deactivateNote(noteId: number) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to deactivate the specific sus note
|
||||
await prisma.sus.update({
|
||||
where: {
|
||||
id: noteId,
|
||||
},
|
||||
data: {
|
||||
active: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
async function deactivateAllNotes(userId: string) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to deactivate the specific user's sus notes
|
||||
await prisma.sus.updateMany({
|
||||
where: {
|
||||
userId: {
|
||||
contains: userId,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
active: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
// Main command
|
||||
class SusCommand extends Command {
|
||||
public constructor(context: Command.Context) {
|
||||
export class SusCommand extends Subcommand {
|
||||
public constructor(context: Subcommand.Context, options: Subcommand.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'sus',
|
||||
subcommands: [
|
||||
{
|
||||
name: 'add',
|
||||
default: true,
|
||||
chatInputRun: 'addNote',
|
||||
messageRun: 'addMessage',
|
||||
},
|
||||
{
|
||||
name: 'view',
|
||||
chatInputRun: 'listNote',
|
||||
},
|
||||
{
|
||||
name: 'remove',
|
||||
chatInputRun: 'removeNote',
|
||||
},
|
||||
{
|
||||
name: 'purge',
|
||||
chatInputRun: 'removeAllNotes',
|
||||
},
|
||||
],
|
||||
description: 'Notes about users that are sus',
|
||||
preconditions: [['VerifierOnly', 'ModOnly']],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
public override registerApplicationCommands(registry: Subcommand.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
// Subcommand to add a sus note
|
||||
.addSubcommand((command) => command.setName('add')
|
||||
.setDescription('Add a sus note about a user')
|
||||
.addUserOption((option) => option.setName('user')
|
||||
.setDescription('User to add the note')
|
||||
.setRequired(true))
|
||||
.addStringOption((option) => option.setName('note')
|
||||
.setDescription('Note about the user')
|
||||
.setRequired(true)))
|
||||
// Subcommand to list sus notes
|
||||
.addSubcommand((command) => command.setName('view')
|
||||
.setDescription('View a sus note for a user')
|
||||
.addUserOption((option) => option.setName('user')
|
||||
.setDescription('User to view the note of')
|
||||
.setRequired(true)))
|
||||
// Subcommand to remove a specific sus note
|
||||
.addSubcommand((command) => command.setName('remove')
|
||||
.setDescription('Remove a specific sus note')
|
||||
.addIntegerOption((option) => option.setName('id')
|
||||
.setDescription('Sus note ID')
|
||||
.setRequired(true)))
|
||||
// Subcommand to remove all sus notes
|
||||
.addSubcommand((command) => command.setName('purge')
|
||||
.setDescription('Remove all sus notes from a user')
|
||||
.addUserOption((option) => option.setName('user')
|
||||
.setDescription('User to remove the note from')
|
||||
.setRequired(true))),
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
// Subcommand to add a sus note
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('add')
|
||||
.setDescription('Add a sus note about a user')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to add the note')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('note')
|
||||
.setDescription('Note about the user')
|
||||
.setRequired(true),
|
||||
),
|
||||
)
|
||||
// Subcommand to list sus notes
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('view')
|
||||
.setDescription('View a sus note for a user')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to view the note of')
|
||||
.setRequired(true),
|
||||
),
|
||||
)
|
||||
// Subcommand to remove a specific sus note
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('remove')
|
||||
.setDescription('Remove a specific sus note')
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('id')
|
||||
.setDescription('Sus note ID')
|
||||
.setRequired(true),
|
||||
),
|
||||
)
|
||||
// Subcommand to remove all sus notes
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('purge')
|
||||
.setDescription('Remove all sus notes from a user')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to remove the note from')
|
||||
.setRequired(true),
|
||||
),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
const subcommand = interaction.options.getSubcommand(true);
|
||||
|
||||
// Checks what subcommand was run
|
||||
switch (subcommand) {
|
||||
case 'add': {
|
||||
await this.addNote(interaction);
|
||||
return;
|
||||
}
|
||||
case 'view': {
|
||||
await this.listNote(interaction);
|
||||
return;
|
||||
}
|
||||
case 'remove': {
|
||||
await this.removeNote(interaction);
|
||||
return;
|
||||
}
|
||||
case 'purge': {
|
||||
await this.removeAllNotes(interaction);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
// If subcommand is invalid
|
||||
await interaction.reply({
|
||||
content: 'Invalid sub command!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subcommand to add sus note
|
||||
private async addNote(interaction: Command.ChatInputInteraction) {
|
||||
public async addNote(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
let user = interaction.options.getUser('user');
|
||||
let note = interaction.options.getString('note');
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const note = interaction.options.getString('note', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (user === null || interaction.member === null || note === null || guild === null) {
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove possibility of null from variables
|
||||
user = user!;
|
||||
const mod = interaction.member!.user;
|
||||
note = note!;
|
||||
|
||||
// Add the data to the database
|
||||
|
||||
// Check if the user exists on the database
|
||||
const userGuildMember = guild!.members.cache.get(user.id);
|
||||
const modGuildMember = guild!.members.cache.get(mod.id);
|
||||
// TODO potentially add a method to add user by Snowflake
|
||||
if (userGuildMember === undefined || modGuildMember === undefined) {
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const modMember = guild.members.cache.get(mod.id);
|
||||
|
||||
if (member === undefined || modMember === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching users!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user and mod are on the database
|
||||
if (!await userExists(userGuildMember!)) {
|
||||
await addExistingUser(userGuildMember!);
|
||||
}
|
||||
if (!await userExists(modGuildMember!)) {
|
||||
await addExistingUser(modGuildMember!);
|
||||
}
|
||||
await addExistingUser(member);
|
||||
await addExistingUser(modMember);
|
||||
|
||||
await addToDatabase(user.id, mod.id, note);
|
||||
|
||||
// Give the user the sus role they don't already have the sus note
|
||||
if (!userGuildMember.roles.cache.has(IDs.roles.restrictions.sus)) {
|
||||
await userGuildMember!.roles.add(IDs.roles.restrictions.sus);
|
||||
if (!member.roles.cache.has(IDs.roles.restrictions.sus)) {
|
||||
await member.roles.add(IDs.roles.restrictions.sus);
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
content: `${user} note: ${note}`,
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
|
||||
private async listNote(interaction: Command.ChatInputInteraction) {
|
||||
public async listNote(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
let user = interaction.options.getUser('user');
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (user === null || guild == null) {
|
||||
if (guild == null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove possibility of null from variables
|
||||
user = user!;
|
||||
let staffChannel = false;
|
||||
let { channel } = interaction;
|
||||
if (channel !== null) {
|
||||
if (channel.type === ChannelType.GuildText) {
|
||||
channel = channel as TextChannel;
|
||||
staffChannel = checkStaff(channel);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the sus notes from the database
|
||||
const notes = await findNotes(user.id, true);
|
||||
@@ -299,52 +224,58 @@ class SusCommand extends Command {
|
||||
}
|
||||
|
||||
// Creates the embed to display the sus note
|
||||
const noteEmbed = new MessageEmbed()
|
||||
const noteEmbed = new EmbedBuilder()
|
||||
.setColor('#0099ff')
|
||||
.setTitle(`${notes.length} sus notes for ${user.username}`)
|
||||
.setThumbnail(user.avatarURL()!);
|
||||
.setThumbnail(user.displayAvatarURL());
|
||||
|
||||
// Add up to 10 of the latest sus notes to the embed
|
||||
for (let i = notes.length > 10 ? notes.length - 10 : 0; i < notes.length; i += 1) {
|
||||
for (
|
||||
let i = notes.length > 10 ? notes.length - 10 : 0;
|
||||
i < notes.length;
|
||||
i += 1
|
||||
) {
|
||||
// Get mod name
|
||||
const modGuildMember = guild!.members.cache.get(notes[i].modId);
|
||||
let mod = notes[i].modId;
|
||||
if (modGuildMember !== undefined) {
|
||||
mod = modGuildMember!.displayName;
|
||||
const modMember = guild.members.cache.get(mod);
|
||||
if (modMember !== undefined) {
|
||||
mod = modMember.displayName;
|
||||
}
|
||||
|
||||
// Add sus note to embed
|
||||
noteEmbed.addField(
|
||||
`Sus ID: ${notes[i].id} | Moderator: ${mod} | Date: <t:${Math.floor(notes[i].time.getTime() / 1000)}>`,
|
||||
notes[i].note,
|
||||
);
|
||||
noteEmbed.addFields({
|
||||
name: `Sus ID: ${
|
||||
notes[i].id
|
||||
} | Moderator: ${mod} | Date: <t:${Math.floor(
|
||||
notes[i].time.getTime() / 1000,
|
||||
)}>`,
|
||||
value: notes[i].note,
|
||||
});
|
||||
}
|
||||
|
||||
// Sends the notes to the user
|
||||
await interaction.reply({
|
||||
embeds: [noteEmbed],
|
||||
ephemeral: true,
|
||||
ephemeral: !staffChannel,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
|
||||
private async removeNote(interaction: Command.ChatInputInteraction) {
|
||||
public async removeNote(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
let noteId = interaction.options.getInteger('id');
|
||||
const noteId = interaction.options.getInteger('id', true);
|
||||
const { guild, channel } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (noteId === null || guild === null || channel === null || interaction.member === null) {
|
||||
if (guild === null || channel === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching id from Discord!',
|
||||
content: 'Error fetching guild or channel!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove possibility of null from variables
|
||||
noteId = noteId!;
|
||||
|
||||
// Get the note to be deleted
|
||||
const note = await getNote(noteId);
|
||||
|
||||
@@ -359,43 +290,54 @@ class SusCommand extends Command {
|
||||
}
|
||||
|
||||
// Get user GuildMembers for user and mod and person who ran command
|
||||
const user = await guild!.members.cache.get(note!.userId);
|
||||
const mod = await guild!.members.cache.get(note!.modId);
|
||||
const member = await guild.members.cache.get(note.userId);
|
||||
const mod = await guild.members.cache.get(note.modId);
|
||||
|
||||
// TODO fix if user left the server
|
||||
if (member === undefined || mod === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching users from Discord!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get user's name
|
||||
let userName = note!.userId;
|
||||
if (user !== undefined) {
|
||||
userName = user!.displayName;
|
||||
let userName = note.userId;
|
||||
if (member !== undefined) {
|
||||
userName = member.displayName;
|
||||
}
|
||||
|
||||
// Get mod name
|
||||
let modName = note!.modId;
|
||||
let modName = note.modId;
|
||||
if (mod !== undefined) {
|
||||
modName = mod!.displayName;
|
||||
modName = mod.displayName;
|
||||
}
|
||||
|
||||
// Create an embed for the note
|
||||
const noteEmbed = new MessageEmbed()
|
||||
const noteEmbed = new EmbedBuilder()
|
||||
.setColor('#ff0000')
|
||||
.setTitle(`Sus note for ${userName}`)
|
||||
.setThumbnail(user!.avatarURL()!) // TODO avatar does not show when run
|
||||
.addField(
|
||||
`ID: ${noteId} | Moderator: ${modName} | Date: <t:${Math.floor(note!.time.getTime() / 1000)}>`,
|
||||
note!.note,
|
||||
);
|
||||
.setThumbnail(member.displayAvatarURL())
|
||||
.addFields({
|
||||
name: `ID: ${noteId} | Moderator: ${modName} | Date: <t:${Math.floor(
|
||||
note.time.getTime() / 1000,
|
||||
)}>`,
|
||||
value: note.note,
|
||||
});
|
||||
|
||||
// Create buttons to delete or cancel the deletion
|
||||
const buttons = new MessageActionRow<MessageButton>()
|
||||
.addComponents(
|
||||
new MessageButton()
|
||||
.setCustomId(`delete${noteId}`)
|
||||
.setLabel('Delete')
|
||||
.setStyle(Constants.MessageButtonStyles.DANGER),
|
||||
new MessageButton()
|
||||
.setCustomId(`cancel${noteId}`)
|
||||
.setLabel('Cancel')
|
||||
.setStyle(Constants.MessageButtonStyles.SECONDARY),
|
||||
);
|
||||
const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`delete${noteId}`)
|
||||
.setLabel('Delete')
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`cancel${noteId}`)
|
||||
.setLabel('Cancel')
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
);
|
||||
|
||||
// Sends the note to verify this note is to be deleted
|
||||
const message = await interaction.reply({
|
||||
@@ -412,7 +354,7 @@ class SusCommand extends Command {
|
||||
}
|
||||
|
||||
// Listen for the button presses
|
||||
const collector = channel!.createMessageComponentCollector({
|
||||
const collector = channel.createMessageComponentCollector({
|
||||
max: 1, // Maximum of 1 button press
|
||||
time: 15000, // 15 seconds
|
||||
});
|
||||
@@ -420,19 +362,19 @@ class SusCommand extends Command {
|
||||
// Button pressed
|
||||
collector.on('collect', async (button: ButtonInteraction) => {
|
||||
if (button.customId === `delete${noteId}`) {
|
||||
await deactivateNote(noteId!);
|
||||
await deactivateNote(noteId);
|
||||
await interaction.editReply({
|
||||
content: `${user!}'s sus note (ID: ${noteId}) has been successfully removed`,
|
||||
content: `${member}'s sus note (ID: ${noteId}) has been successfully removed`,
|
||||
embeds: [],
|
||||
});
|
||||
|
||||
// TODO create a new Prisma function to only count and not to get a whole list of sus notes
|
||||
// Check how many notes the user has and if 0, then remove sus note
|
||||
const notes = await findNotes(user!.id, true);
|
||||
const notes = await findNotes(member.id, true);
|
||||
|
||||
// Checks if there are no notes on the user and if there's none, remove the sus role
|
||||
if (notes.length === 0) {
|
||||
await user!.roles.remove(IDs.roles.restrictions.sus);
|
||||
await member.roles.remove(IDs.roles.restrictions.sus);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -445,25 +387,27 @@ class SusCommand extends Command {
|
||||
});
|
||||
}
|
||||
|
||||
private async removeAllNotes(interaction: Command.ChatInputInteraction) {
|
||||
public async removeAllNotes(
|
||||
interaction: Subcommand.ChatInputCommandInteraction,
|
||||
) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user');
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const { guild, channel } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (user === null || guild === null || channel === null) {
|
||||
if (guild === null || channel === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
content: 'Error fetching guild or channel!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const userGuildMember = guild!.members.cache.get(user!.id);
|
||||
const member = guild.members.cache.get(user.id);
|
||||
|
||||
// Checks if managed to find GuildMember for the user
|
||||
if (userGuildMember === undefined) {
|
||||
if (member === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
@@ -474,12 +418,12 @@ class SusCommand extends Command {
|
||||
|
||||
// Check if the user had sus notes before trying to remove them
|
||||
// Gets the sus notes from the database
|
||||
const notes = await findNotes(user!.id, true);
|
||||
const notes = await findNotes(user.id, true);
|
||||
|
||||
// Checks if there are no notes on the user
|
||||
if (notes.length === 0) {
|
||||
await interaction.reply({
|
||||
content: `${user!} had no notes!`,
|
||||
content: `${user} had no notes!`,
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
@@ -487,38 +431,45 @@ class SusCommand extends Command {
|
||||
}
|
||||
|
||||
// Creates the embed to display the sus note
|
||||
const noteEmbed = new MessageEmbed()
|
||||
const noteEmbed = new EmbedBuilder()
|
||||
.setColor('#ff0000')
|
||||
.setTitle(`Delete ${notes.length} sus notes for ${user!.username}?`)
|
||||
.setThumbnail(user!.avatarURL()!);
|
||||
.setTitle(`Delete ${notes.length} sus notes for ${user.username}?`)
|
||||
.setThumbnail(user.displayAvatarURL());
|
||||
|
||||
// Add up to 10 of the latest sus notes to the embed
|
||||
for (let i = notes.length > 10 ? notes.length - 10 : 0; i < notes.length; i += 1) {
|
||||
for (
|
||||
let i = notes.length > 10 ? notes.length - 10 : 0;
|
||||
i < notes.length;
|
||||
i += 1
|
||||
) {
|
||||
// Get mod name
|
||||
const modGuildMember = guild!.members.cache.get(notes[i].modId);
|
||||
let mod = notes[i].modId;
|
||||
const modGuildMember = guild.members.cache.get(mod);
|
||||
if (modGuildMember !== undefined) {
|
||||
mod = modGuildMember!.displayName;
|
||||
mod = modGuildMember.displayName;
|
||||
}
|
||||
// Add sus note to embed
|
||||
noteEmbed.addField(
|
||||
`Sus ID: ${notes[i].id} | Moderator: ${mod} | Date: <t:${Math.floor(notes[i].time.getTime() / 1000)}>`,
|
||||
notes[i].note,
|
||||
);
|
||||
noteEmbed.addFields({
|
||||
name: `Sus ID: ${
|
||||
notes[i].id
|
||||
} | Moderator: ${mod} | Date: <t:${Math.floor(
|
||||
notes[i].time.getTime() / 1000,
|
||||
)}>`,
|
||||
value: notes[i].note,
|
||||
});
|
||||
}
|
||||
|
||||
// Create buttons to delete or cancel the deletion
|
||||
const buttons = new MessageActionRow<MessageButton>()
|
||||
.addComponents(
|
||||
new MessageButton()
|
||||
.setCustomId(`delete${user!.id}`)
|
||||
.setLabel('Delete')
|
||||
.setStyle(Constants.MessageButtonStyles.DANGER),
|
||||
new MessageButton()
|
||||
.setCustomId(`cancel${user!.id}`)
|
||||
.setLabel('Cancel')
|
||||
.setStyle(Constants.MessageButtonStyles.SECONDARY),
|
||||
);
|
||||
const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`delete${user.id}`)
|
||||
.setLabel('Delete')
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`cancel${user.id}`)
|
||||
.setLabel('Cancel')
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
);
|
||||
|
||||
// Sends the note to verify this note is to be deleted
|
||||
const message = await interaction.reply({
|
||||
@@ -535,18 +486,18 @@ class SusCommand extends Command {
|
||||
}
|
||||
|
||||
// Listen for the button presses
|
||||
const collector = channel!.createMessageComponentCollector({
|
||||
const collector = channel.createMessageComponentCollector({
|
||||
max: 1, // Maximum of 1 button press
|
||||
time: 15000, // 15 seconds
|
||||
});
|
||||
|
||||
// Button pressed
|
||||
collector.on('collect', async (button: ButtonInteraction) => {
|
||||
if (button.customId === `delete${user!.id}`) {
|
||||
if (button.customId === `delete${user.id}`) {
|
||||
// Remove sus note from database
|
||||
await deactivateAllNotes(user!.id);
|
||||
await deactivateAllNotes(user.id);
|
||||
await interaction.editReply({
|
||||
content: `Removed all of ${userGuildMember!}'s sus notes successfully`,
|
||||
content: `Removed all of ${member}'s sus notes successfully`,
|
||||
embeds: [],
|
||||
});
|
||||
}
|
||||
@@ -560,12 +511,12 @@ class SusCommand extends Command {
|
||||
});
|
||||
|
||||
// Remove sus role from the user
|
||||
await userGuildMember!.roles.remove(IDs.roles.restrictions.sus);
|
||||
await member.roles.remove(IDs.roles.restrictions.sus);
|
||||
}
|
||||
|
||||
// Non Application Command method of adding a sus note
|
||||
// xlevra begged me to add this... so I guess here it is
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
public async addMessage(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: GuildMember;
|
||||
try {
|
||||
@@ -586,22 +537,21 @@ class SusCommand extends Command {
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Moderator not found! Try again or contact a developer!');
|
||||
await message.reply(
|
||||
'Moderator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user and mod are on the database
|
||||
if (!await userExists(user)) {
|
||||
await addExistingUser(user);
|
||||
}
|
||||
if (!await userExists(mod!)) {
|
||||
await addExistingUser(mod!);
|
||||
}
|
||||
await addExistingUser(user);
|
||||
await addExistingUser(mod);
|
||||
|
||||
await addToDatabase(user.id, mod.id, note);
|
||||
|
||||
// Give the user the sus role they don't already have the sus note
|
||||
if (!user.roles.cache.has(IDs.roles.restrictions.sus)) {
|
||||
await user!.roles.add(IDs.roles.restrictions.sus);
|
||||
await user.roles.add(IDs.roles.restrictions.sus);
|
||||
}
|
||||
|
||||
// Checks if the user is xlevra to send a very kind message
|
||||
@@ -612,5 +562,3 @@ class SusCommand extends Command {
|
||||
await message.react('✅');
|
||||
}
|
||||
}
|
||||
|
||||
export default SusCommand;
|
||||
|
||||
168
src/commands/mod/vcMute.ts
Normal file
168
src/commands/mod/vcMute.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { GuildMember, Message } from 'discord.js';
|
||||
import { addMute, removeMute, checkActive } from '#utils/database/vcMute';
|
||||
import { addExistingUser } from '#utils/database/dbExistingUser';
|
||||
|
||||
export class VCMuteCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'vcmute',
|
||||
aliases: ['vmute'],
|
||||
description: 'Persists a server mute if a user is trying to bypass mute',
|
||||
preconditions: [['CoordinatorOnly', 'ModOnly']],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to persistently mute')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('Reason for persistently muting the user'),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const reason = interaction.options.getString('reason');
|
||||
const modUser = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Gets guildMember whilst removing the ability of each other variables being null
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const mod = guild.members.cache.get(modUser.id);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (member === undefined || mod === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if removing VC Mute
|
||||
if (await checkActive(member.id)) {
|
||||
await removeMute(member.id);
|
||||
if (member.voice.channel !== null) {
|
||||
await member.voice.setMute(false, reason === null ? undefined : reason);
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
content: `Removed server mute from ${user}`,
|
||||
fetchReply: true,
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if mod is in database
|
||||
await addExistingUser(mod);
|
||||
|
||||
// Add VC Mute
|
||||
if (member.voice.channel !== null) {
|
||||
await member.voice.setMute(true, reason === null ? undefined : reason);
|
||||
}
|
||||
await addMute(member.id, mod.id, reason);
|
||||
await interaction.reply({
|
||||
content: `Server muted ${user}`,
|
||||
fetchReply: true,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let member: GuildMember;
|
||||
try {
|
||||
member = await args.pick('member');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = args.finished ? null : await args.rest('string');
|
||||
const mod = message.member;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Moderator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if removing VC Mute
|
||||
if (await checkActive(member.id)) {
|
||||
await removeMute(member.id);
|
||||
if (member.voice.channel !== null) {
|
||||
await member.voice.setMute(false, reason === null ? undefined : reason);
|
||||
}
|
||||
|
||||
await message.reply(`Removed server mute from ${member}`);
|
||||
await message.react('✅');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if mod is in database
|
||||
await addExistingUser(mod);
|
||||
|
||||
// Add VC Mute
|
||||
if (member.voice.channel !== null) {
|
||||
await member.voice.setMute(true, reason === null ? undefined : reason);
|
||||
}
|
||||
await addMute(member.id, mod.id, reason);
|
||||
await message.reply(`Server muted ${member}`);
|
||||
|
||||
await message.react('✅');
|
||||
}
|
||||
}
|
||||
127
src/commands/mod/warn.ts
Normal file
127
src/commands/mod/warn.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command } from '@sapphire/framework';
|
||||
import type { User, Message, Snowflake, Guild } from 'discord.js';
|
||||
import { addExistingUser, updateUser } from '#utils/database/dbExistingUser';
|
||||
import { addWarn } from '#utils/database/warnings';
|
||||
|
||||
/*
|
||||
This command is not intended to be functional for now, this is purely to log
|
||||
warnings onto a database, so if we were to switch purely to ARA Bot, it would
|
||||
mean we would have a lot of the warns already in the database.
|
||||
*/
|
||||
export class WarnCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'warn',
|
||||
description: 'Warns a user (only used for logging to a database for now)',
|
||||
preconditions: [['CoordinatorOnly', 'ModOnly']],
|
||||
});
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
const reason = args.finished ? null : await args.rest('string');
|
||||
const mod = message.member;
|
||||
|
||||
if (reason === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Warn reason was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Moderator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const warn = await this.warn(user.id, mod.id, reason, guild);
|
||||
|
||||
if (!warn.success) {
|
||||
await message.react('❌');
|
||||
}
|
||||
|
||||
// await message.react('✅');
|
||||
}
|
||||
|
||||
private async warn(
|
||||
userId: Snowflake,
|
||||
modId: Snowflake,
|
||||
reason: string,
|
||||
guild: Guild,
|
||||
) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
|
||||
// Gets mod's GuildMember
|
||||
const mod = guild.members.cache.get(modId);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (mod === undefined) {
|
||||
info.message = 'Error fetching mod!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check if mod is in database
|
||||
await updateUser(mod);
|
||||
|
||||
// Gets guildMember
|
||||
let member = guild.members.cache.get(userId);
|
||||
|
||||
if (member === undefined) {
|
||||
member = await guild.members.fetch(userId).catch(() => undefined);
|
||||
}
|
||||
|
||||
if (member === undefined) {
|
||||
info.message = 'User is not on this server';
|
||||
return info;
|
||||
}
|
||||
|
||||
await addExistingUser(member);
|
||||
|
||||
await addWarn(userId, modId, reason);
|
||||
|
||||
info.message = `Warned ${member}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
541
src/commands/outreach/outreach.ts
Normal file
541
src/commands/outreach/outreach.ts
Normal file
@@ -0,0 +1,541 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Subcommand } from '@sapphire/plugin-subcommands';
|
||||
import { RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Snowflake } from 'discord.js';
|
||||
import { updateUser } from '#utils/database/dbExistingUser';
|
||||
import {
|
||||
addStatUser,
|
||||
checkActiveEvent,
|
||||
createEvent,
|
||||
createStat,
|
||||
endEvent,
|
||||
getCurrentEvent,
|
||||
getStatFromLeader,
|
||||
getStatFromRole,
|
||||
getStatGroups,
|
||||
updateStats,
|
||||
userInStats,
|
||||
} from '#utils/database/outreach';
|
||||
import IDs from '#utils/ids';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
|
||||
export class OutreachCommand extends Subcommand {
|
||||
public constructor(context: Subcommand.Context, options: Subcommand.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'outreach',
|
||||
description: 'Tools for doing outreach',
|
||||
subcommands: [
|
||||
{
|
||||
name: 'event',
|
||||
type: 'group',
|
||||
entries: [
|
||||
{ name: 'create', chatInputRun: 'eventCreate' },
|
||||
// { name: 'start', chatInputRun: 'eventStart' },
|
||||
{ name: 'end', chatInputRun: 'eventEnd' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
entries: [
|
||||
{ name: 'create', chatInputRun: 'groupCreate' },
|
||||
{ name: 'add', chatInputRun: 'groupAdd' },
|
||||
{ name: 'update', chatInputRun: 'groupUpdate' },
|
||||
],
|
||||
},
|
||||
],
|
||||
preconditions: ['ModOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Subcommand.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addSubcommandGroup((group) =>
|
||||
group
|
||||
.setName('event')
|
||||
.setDescription('Commands to do with outreach events')
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('create')
|
||||
.setDescription('Start an outreach event'),
|
||||
)
|
||||
/*
|
||||
TODO add this back at a later date
|
||||
|
||||
.addBooleanOption((option) => option.setName('start')
|
||||
.setDescription('Start the event immediately'))
|
||||
.addSubcommand((command) => command.setName('start')
|
||||
.setDescription('Start an outreach event'))
|
||||
*/
|
||||
.addSubcommand((command) =>
|
||||
command.setName('end').setDescription('End an outreach event'),
|
||||
),
|
||||
)
|
||||
.addSubcommandGroup((group) =>
|
||||
group
|
||||
.setName('group')
|
||||
.setDescription('Commands to do with groups')
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('create')
|
||||
.setDescription('Create a group for people doing activism')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('leader')
|
||||
.setDescription('This is the person leading the group')
|
||||
.setRequired(true),
|
||||
),
|
||||
)
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('add')
|
||||
.setDescription('Add a person to the group')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to add to the group')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addRoleOption((option) =>
|
||||
option
|
||||
.setName('group')
|
||||
.setDescription('Group to add the user to'),
|
||||
),
|
||||
)
|
||||
.addSubcommand((command) =>
|
||||
command
|
||||
.setName('update')
|
||||
.setDescription('Update the statistics for the group')
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('vegan')
|
||||
.setDescription('How many said would go vegan?'),
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('considered')
|
||||
.setDescription(
|
||||
'How many seriously considered being vegan?',
|
||||
),
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('anti-vegan')
|
||||
.setDescription(
|
||||
'How many people had anti-vegan viewpoints?',
|
||||
),
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('thanked')
|
||||
.setDescription(
|
||||
'How many thanked you for the conversation?',
|
||||
),
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('documentary')
|
||||
.setDescription(
|
||||
'How many said they would watch a vegan documentary?',
|
||||
),
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('educated')
|
||||
.setDescription(
|
||||
'How many got educated on veganism or the animal industry?',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public async eventCreate(
|
||||
interaction: Subcommand.ChatInputCommandInteraction,
|
||||
) {
|
||||
// const start = interaction.options.getBoolean('start');
|
||||
const modUser = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Mod or guild was not found!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = guild.members.cache.get(modUser.id);
|
||||
|
||||
if (mod === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Mod was not found!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mod.roles.cache.has(IDs.roles.staff.outreachCoordinator)) {
|
||||
await interaction.reply({
|
||||
content: 'You need to be an Outreach Coordinator to run this command!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (await checkActiveEvent()) {
|
||||
await interaction.reply({
|
||||
content: 'There is already an active event!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await updateUser(mod);
|
||||
|
||||
await createEvent(modUser.id);
|
||||
|
||||
await interaction.reply({
|
||||
content: 'Created the event!',
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
public async eventEnd(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
const modUser = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Guild not found!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = guild.members.cache.get(modUser.id);
|
||||
|
||||
if (mod === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Your guild member was not found!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mod.roles.cache.has(IDs.roles.staff.outreachCoordinator)) {
|
||||
await interaction.reply({
|
||||
content: 'You need to be an Outreach Coordinator to run this command!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const event = await getCurrentEvent();
|
||||
|
||||
if (event === null) {
|
||||
await interaction.editReply('There is currently no event!');
|
||||
return;
|
||||
}
|
||||
|
||||
const [stat] = await Promise.all([getStatGroups(event.id)]);
|
||||
|
||||
stat.forEach(({ role }) => {
|
||||
if (role !== null) {
|
||||
guild.roles.delete(role.roleId);
|
||||
}
|
||||
});
|
||||
|
||||
await endEvent(event.id);
|
||||
|
||||
// Statistics shown at the end
|
||||
|
||||
let vegan = 0;
|
||||
let considered = 0;
|
||||
let antiVegan = 0;
|
||||
let thanked = 0;
|
||||
let documentary = 0;
|
||||
let educated = 0;
|
||||
|
||||
stat.forEach((group) => {
|
||||
vegan += group.vegan;
|
||||
considered += group.considered;
|
||||
antiVegan += group.antivegan;
|
||||
thanked += group.thanked;
|
||||
documentary += group.documentary;
|
||||
educated += group.educated;
|
||||
});
|
||||
|
||||
const activist = guild.channels.cache.get(IDs.channels.activism.activism);
|
||||
|
||||
if (activist === undefined || !activist.isTextBased()) {
|
||||
await interaction.editReply(
|
||||
'Event has now ended, but could not post statistics!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#0099FF')
|
||||
.setAuthor({ name: 'Stats for Discord Outreach' })
|
||||
.addFields(
|
||||
{ name: 'How many said would go vegan?', value: `${vegan}` },
|
||||
{
|
||||
name: 'How many seriously considered being vegan?',
|
||||
value: `${considered}`,
|
||||
},
|
||||
{
|
||||
name: 'How many people had anti-vegan viewpoints?',
|
||||
value: `${antiVegan}`,
|
||||
},
|
||||
{
|
||||
name: 'How many thanked you for the conversation?',
|
||||
value: `${thanked}`,
|
||||
},
|
||||
{
|
||||
name: 'How many said they would watch a vegan documentary?',
|
||||
value: `${documentary}`,
|
||||
},
|
||||
{
|
||||
name: 'How many got educated on veganism or the animal industry?',
|
||||
value: `${educated}`,
|
||||
},
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `Outreach Event: ${event.id}` });
|
||||
|
||||
await activist.send({ embeds: [embed] });
|
||||
|
||||
await interaction.editReply('Event has now ended!');
|
||||
}
|
||||
|
||||
public async groupCreate(
|
||||
interaction: Subcommand.ChatInputCommandInteraction,
|
||||
) {
|
||||
const leader = interaction.options.getUser('leader', true);
|
||||
const { guild } = interaction;
|
||||
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Guild not found!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
if ((await getStatFromLeader(leader.id)) !== null) {
|
||||
await interaction.editReply(
|
||||
`${leader} is already a leader for another group!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const event = await getCurrentEvent();
|
||||
|
||||
if (event === null) {
|
||||
await interaction.editReply({
|
||||
content: 'There is no current event!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const statGroups = await getStatGroups(event.id);
|
||||
const groupNo = statGroups.length + 1;
|
||||
|
||||
const leaderMember = await guild.members.cache.get(leader.id);
|
||||
|
||||
if (leaderMember === undefined) {
|
||||
await interaction.editReply({
|
||||
content: `Could not find ${leader}'s guild member.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await updateUser(leaderMember);
|
||||
|
||||
const role = await guild.roles.create({
|
||||
name: `Outreach Group ${groupNo}`,
|
||||
});
|
||||
|
||||
await createStat(event.id, leader.id, role.id);
|
||||
|
||||
await leaderMember.roles.add(role);
|
||||
|
||||
await interaction.editReply({
|
||||
content: `Created a group with the leader being ${leader}`,
|
||||
});
|
||||
}
|
||||
|
||||
public async groupAdd(interaction: Subcommand.ChatInputCommandInteraction) {
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const group = interaction.options.getRole('group');
|
||||
const leader = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Could not find guild!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
let statId: number;
|
||||
let roleId: Snowflake | undefined;
|
||||
|
||||
// Find group from role
|
||||
if (group !== null) {
|
||||
const [stat] = await Promise.all([getStatFromRole(group.id)]);
|
||||
|
||||
if (stat === null) {
|
||||
await interaction.editReply({
|
||||
content: `Could not find the group for role ${group}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const leaderMember = guild.members.cache.get(leader.id);
|
||||
|
||||
if (leaderMember === undefined) {
|
||||
await interaction.editReply({
|
||||
content: 'Could not find your GuildMember in cache!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
leader.id !== stat.stat.leaderId &&
|
||||
!leaderMember.roles.cache.has(IDs.roles.staff.outreachCoordinator)
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: `You are not the leader for ${group}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
statId = stat.statId;
|
||||
roleId = stat.roleId;
|
||||
} else {
|
||||
// Find group from person who ran the command
|
||||
const [stat] = await Promise.all([getStatFromLeader(leader.id)]);
|
||||
|
||||
if (stat === null) {
|
||||
await interaction.editReply({
|
||||
content: "You're not a group leader!",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
statId = stat.id;
|
||||
roleId = stat.role?.roleId;
|
||||
}
|
||||
|
||||
if (await userInStats(statId, user.id)) {
|
||||
await interaction.editReply({
|
||||
content: `${user} is already in this group!`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const member = guild.members.cache.get(user.id);
|
||||
|
||||
if (member === undefined) {
|
||||
await interaction.editReply({
|
||||
content: 'Could not fetch the member!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await updateUser(member);
|
||||
|
||||
await addStatUser(statId, user.id);
|
||||
|
||||
if (roleId !== undefined) {
|
||||
await member.roles.add(roleId);
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
content: `Added ${user} to the group!`,
|
||||
});
|
||||
}
|
||||
|
||||
public async groupUpdate(
|
||||
interaction: Subcommand.ChatInputCommandInteraction,
|
||||
) {
|
||||
const vegan = interaction.options.getInteger('vegan');
|
||||
const considered = interaction.options.getInteger('considered');
|
||||
const antiVegan = interaction.options.getInteger('anti-vegan');
|
||||
const thanked = interaction.options.getInteger('thanked');
|
||||
const documentary = interaction.options.getInteger('documentary');
|
||||
const educated = interaction.options.getInteger('educated');
|
||||
const leader = interaction.user;
|
||||
|
||||
const stats = {
|
||||
vegan: vegan !== null ? vegan : 0,
|
||||
considered: considered !== null ? considered : 0,
|
||||
antiVegan: antiVegan !== null ? antiVegan : 0,
|
||||
thanked: thanked !== null ? thanked : 0,
|
||||
documentary: documentary !== null ? documentary : 0,
|
||||
educated: educated !== null ? educated : 0,
|
||||
};
|
||||
|
||||
if (leader === null) {
|
||||
await interaction.reply({
|
||||
content: 'Could not find your user!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const stat = await getStatFromLeader(leader.id);
|
||||
|
||||
if (stat === null) {
|
||||
await interaction.editReply({
|
||||
content: "You're not the leader of a group!",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await updateStats(stat.id, stats);
|
||||
|
||||
await interaction.editReply({
|
||||
content: 'Updated the database with the stats!',
|
||||
});
|
||||
}
|
||||
}
|
||||
152
src/commands/roles/bookClub.ts
Normal file
152
src/commands/roles/bookClub.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class BookClubCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'bookclub',
|
||||
description: 'Gives the Book Club role',
|
||||
preconditions: ['EventCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give Book Club to')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageBookClub(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Event coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageBookClub(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageBookClub(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const bookClub = guild.roles.cache.get(IDs.roles.bookClub);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (bookClub === undefined) {
|
||||
info.message = 'Error fetching book club role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has Book Club and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.bookClub)) {
|
||||
// Remove the Book Club role from the user
|
||||
await member.roles.remove(bookClub);
|
||||
await roleRemoveLog(user.id, mod.id, bookClub);
|
||||
info.message = `Removed the ${bookClub.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Book Club role to the user
|
||||
await member.roles.add(bookClub);
|
||||
await roleAddLog(user.id, mod.id, bookClub);
|
||||
info.message = `Gave ${user} the ${bookClub.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${bookClub.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
153
src/commands/roles/debateHost.ts
Normal file
153
src/commands/roles/debateHost.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class DebateHostCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'debatehost',
|
||||
description: 'Gives the Debate Host role',
|
||||
preconditions: ['EventCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give Debate Host role to')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// TODO add database updates
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageDebateHost(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Event coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageDebateHost(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageDebateHost(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const debateHost = guild.roles.cache.get(IDs.roles.debateHost);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (debateHost === undefined) {
|
||||
info.message = 'Error fetching debate host role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has Debate Host and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.debateHost)) {
|
||||
// Remove the Debate Host role from the user
|
||||
await member.roles.remove(debateHost);
|
||||
await roleRemoveLog(user.id, mod.id, debateHost);
|
||||
info.message = `Removed the ${debateHost.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Debate Host role to the user
|
||||
await member.roles.add(debateHost);
|
||||
await roleAddLog(user.id, mod.id, debateHost);
|
||||
info.message = `Gave ${user} the ${debateHost.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${debateHost.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
152
src/commands/roles/gameNightHost.ts
Normal file
152
src/commands/roles/gameNightHost.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class GameNightHostCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'gamenight',
|
||||
description: 'Gives the Game Night Host role',
|
||||
preconditions: ['EventCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give Game Night Host role to')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageGameNight(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Event coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageGameNight(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageGameNight(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const gameNightHost = guild.roles.cache.get(IDs.roles.gameNightHost);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (gameNightHost === undefined) {
|
||||
info.message = 'Error fetching game night host role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has Game Night and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.gameNightHost)) {
|
||||
// Remove the Game Night Host role from the user
|
||||
await member.roles.remove(gameNightHost);
|
||||
await roleRemoveLog(user.id, mod.id, gameNightHost);
|
||||
info.message = `Removed the ${gameNightHost.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Game Night Host role to the user
|
||||
await member.roles.add(gameNightHost);
|
||||
await roleAddLog(user.id, mod.id, gameNightHost);
|
||||
info.message = `Gave ${user} the ${gameNightHost.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${gameNightHost.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
152
src/commands/roles/guest.ts
Normal file
152
src/commands/roles/guest.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class GuestCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'guest',
|
||||
description: 'Gives the Guest role',
|
||||
preconditions: ['EventCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give Guest role to')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageGuest(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Event coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageGuest(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageGuest(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const guest = guild.roles.cache.get(IDs.roles.guest);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (guest === undefined) {
|
||||
info.message = 'Error fetching guest role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has Guest and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.guest)) {
|
||||
// Remove the Guest role from the user
|
||||
await member.roles.remove(guest);
|
||||
await roleRemoveLog(user.id, mod.id, guest);
|
||||
info.message = `Removed the ${guest.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Guest role to the user
|
||||
await member.roles.add(guest);
|
||||
await roleAddLog(user.id, mod.id, guest);
|
||||
info.message = `Gave ${user} the ${guest.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${guest.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
153
src/commands/roles/staff/mentor.ts
Normal file
153
src/commands/roles/staff/mentor.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class MentorCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'mentor',
|
||||
aliases: ['vegs'],
|
||||
description: 'Gives/removes the mentor role',
|
||||
preconditions: ['MentorCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give/remove mentor role')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageMentor(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Mentor coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageMentor(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageMentor(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const mentor = guild.roles.cache.get(IDs.roles.staff.mentor);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (mentor === undefined) {
|
||||
info.message = 'Error fetching mentor role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has Mentor and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.staff.mentor)) {
|
||||
// Remove the Mentor role from the user
|
||||
await member.roles.remove(mentor);
|
||||
await roleRemoveLog(user.id, mod.id, mentor, true);
|
||||
info.message = `Removed the ${mentor.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Mentor role to the user
|
||||
await member.roles.add(mentor);
|
||||
await roleAddLog(user.id, mod.id, mentor, true);
|
||||
info.message = `Gave ${user} the ${mentor.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${mentor.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
152
src/commands/roles/staff/mod.ts
Normal file
152
src/commands/roles/staff/mod.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class ModCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'mod',
|
||||
description: 'Gives/removes the mod role',
|
||||
preconditions: ['ModCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give/remove mod role')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageMod(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Mod coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageMod(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageMod(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const moderator = guild.roles.cache.get(IDs.roles.staff.moderator);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (moderator === undefined) {
|
||||
info.message = 'Error fetching the moderator role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has Mod and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.staff.moderator)) {
|
||||
// Remove the Mod role from the user
|
||||
await member.roles.remove(moderator);
|
||||
await roleRemoveLog(user.id, mod.id, moderator, true);
|
||||
info.message = `Removed the ${moderator.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Mod role to the user
|
||||
await member.roles.add(moderator);
|
||||
await roleAddLog(user.id, mod.id, moderator, true);
|
||||
info.message = `Gave ${user} the ${moderator.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${moderator.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
153
src/commands/roles/staff/restrictedAccess.ts
Normal file
153
src/commands/roles/staff/restrictedAccess.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class RestrictedAccessCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'restrictedaccess',
|
||||
aliases: ['ra'],
|
||||
description: 'Gives/removes the restricted access role',
|
||||
preconditions: ['ModCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give/remove restricted access role')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageRestrictedAccess(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Mod coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageRestrictedAccess(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageRestrictedAccess(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const restricted = guild.roles.cache.get(IDs.roles.staff.restricted);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (restricted === undefined) {
|
||||
info.message = 'Error fetching the restricted access role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has RA and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.staff.restricted)) {
|
||||
// Remove the Restricted Access role from the user
|
||||
await member.roles.remove(restricted);
|
||||
await roleRemoveLog(user.id, mod.id, restricted, true);
|
||||
info.message = `Removed the ${restricted.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Restricted Access role to the user
|
||||
await member.roles.add(restricted);
|
||||
await roleAddLog(user.id, mod.id, restricted, true);
|
||||
info.message = `Gave ${user} the ${restricted.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${restricted.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
152
src/commands/roles/staff/stagehost.ts
Normal file
152
src/commands/roles/staff/stagehost.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022, 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class StageHostCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'stagehost',
|
||||
description: 'Gives the Stage Host role',
|
||||
preconditions: ['EventCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give Stage Host to')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageStageHost(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Event coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageStageHost(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageStageHost(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const stageHost = guild.roles.cache.get(IDs.roles.stageHost);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (stageHost === undefined) {
|
||||
info.message = 'Error fetching stage host role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has Stage Host and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.stageHost)) {
|
||||
// Remove the Stage Host role from the user
|
||||
await member.roles.remove(stageHost);
|
||||
await roleRemoveLog(user.id, mod.id, stageHost, true);
|
||||
info.message = `Removed the ${stageHost.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Stage Host role to the user
|
||||
await member.roles.add(stageHost);
|
||||
await roleAddLog(user.id, mod.id, stageHost, true);
|
||||
info.message = `Gave ${user} the ${stageHost.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${stageHost.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
153
src/commands/roles/staff/trialMod.ts
Normal file
153
src/commands/roles/staff/trialMod.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class TrialModCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'trialmod',
|
||||
aliases: ['tm'],
|
||||
description: 'Gives/removes the trial mod role',
|
||||
preconditions: ['ModCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give/remove trial mod role')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageMod(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Mod coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageMod(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageMod(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const moderator = guild.roles.cache.get(IDs.roles.staff.trialModerator);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (moderator === undefined) {
|
||||
info.message = 'Error fetching the trial moderator role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has Trial Mod and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.staff.trialModerator)) {
|
||||
// Remove the Trial Mod role from the user
|
||||
await member.roles.remove(moderator);
|
||||
await roleRemoveLog(user.id, mod.id, moderator, true);
|
||||
info.message = `Removed the ${moderator.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Trial Mod role to the user
|
||||
await member.roles.add(moderator);
|
||||
await roleAddLog(user.id, mod.id, moderator, true);
|
||||
info.message = `Gave ${user} the ${moderator.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${moderator.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
152
src/commands/roles/staff/trialVerifier.ts
Normal file
152
src/commands/roles/staff/trialVerifier.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class TrialVerifierCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'trialverifier',
|
||||
description: 'Gives/removes the trial verifier role',
|
||||
preconditions: ['VerifierCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give/remove trial verifier role')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageTrialVerifier(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Verifier coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageTrialVerifier(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageTrialVerifier(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const trialVerifier = guild.roles.cache.get(IDs.roles.staff.trialVerifier);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (trialVerifier === undefined) {
|
||||
info.message = 'Error fetching the trial verifier role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has TV and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.staff.trialVerifier)) {
|
||||
// Remove the Trial Verifier role from the user
|
||||
await member.roles.remove(trialVerifier);
|
||||
await roleRemoveLog(user.id, mod.id, trialVerifier, true);
|
||||
info.message = `Removed the ${trialVerifier.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Trial Verifier role to the user
|
||||
await member.roles.add(trialVerifier);
|
||||
await roleAddLog(user.id, mod.id, trialVerifier, true);
|
||||
info.message = `Gave ${user} the ${trialVerifier.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${trialVerifier.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
152
src/commands/roles/staff/verifier.ts
Normal file
152
src/commands/roles/staff/verifier.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import type { Guild, User, Message } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
|
||||
|
||||
export class VerifierCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'verifier',
|
||||
description: 'Gives/removes the verifier role',
|
||||
preconditions: ['VerifierCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) =>
|
||||
builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('User to give/remove verifier role')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const info = await this.manageVerifier(user, mod, guild);
|
||||
|
||||
await interaction.editReply(info.message);
|
||||
}
|
||||
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = message.author;
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Verifier coordinator not found! Try again or contact a developer!',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.manageVerifier(user, mod, guild);
|
||||
|
||||
await message.reply(info.message);
|
||||
await message.react(info.success ? '✅' : '❌');
|
||||
}
|
||||
|
||||
private async manageVerifier(user: User, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
const member = guild.members.cache.get(user.id);
|
||||
const verifier = guild.roles.cache.get(IDs.roles.staff.verifier);
|
||||
|
||||
// Checks if user's GuildMember was found in cache
|
||||
if (member === undefined) {
|
||||
info.message = 'Error fetching guild member for the user!';
|
||||
return info;
|
||||
}
|
||||
|
||||
if (verifier === undefined) {
|
||||
info.message = 'Error fetching verifier role from cache!';
|
||||
return info;
|
||||
}
|
||||
|
||||
// Checks if the user has Verifier and to give them or remove them based on if they have it
|
||||
if (member.roles.cache.has(IDs.roles.staff.verifier)) {
|
||||
// Remove the Verifier role from the user
|
||||
await member.roles.remove(verifier);
|
||||
await roleRemoveLog(user.id, mod.id, verifier, true);
|
||||
info.message = `Removed the ${verifier.name} role from ${user}`;
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
// Add Verifier role to the user
|
||||
await member.roles.add(verifier);
|
||||
await roleAddLog(user.id, mod.id, verifier, true);
|
||||
info.message = `Gave ${user} the ${verifier.name} role!`;
|
||||
|
||||
await user
|
||||
.send(`You have been given the ${verifier.name} role by ${mod}!`)
|
||||
.catch(() => {});
|
||||
info.success = true;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import IDs from '../../utils/ids';
|
||||
|
||||
class StageHostCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'stagehost',
|
||||
description: 'Gives the Stage Host role',
|
||||
preconditions: ['EventCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) => option.setName('user')
|
||||
.setDescription('User to give Stage Host to')
|
||||
.setRequired(true)),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
// TODO add database updates
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user');
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (user === null || guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Gets guildMember whilst removing the ability of each other variables being null
|
||||
let guildMember = guild!.members.cache.get(user!.id);
|
||||
let stageHost = guild!.roles.cache.get(IDs.roles.stageHost);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (guildMember === null || stageHost === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Removes the possibility of guildMember being null
|
||||
guildMember = guildMember!;
|
||||
stageHost = stageHost!;
|
||||
|
||||
// Checks if the user has Veg Curious and to give them or remove them based on if they have it
|
||||
if (guildMember.roles.cache.has(IDs.roles.stageHost)) {
|
||||
// Remove the Veg Curious role from the user
|
||||
await guildMember.roles.remove(stageHost);
|
||||
await interaction.reply({
|
||||
content: `Removed the ${stageHost.name} role from ${user!}`,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Add Veg Curious role to the user
|
||||
await guildMember.roles.add(stageHost);
|
||||
await interaction.reply({
|
||||
content: `Gave ${user!} the ${stageHost.name} role!`,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default StageHostCommand;
|
||||
@@ -1,113 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import IDs from '../../utils/ids';
|
||||
|
||||
class VegCuriousCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'vegancurious',
|
||||
description: 'Gives the veg curious role for vegans only',
|
||||
preconditions: ['MentorCoordinatorOnly'],
|
||||
});
|
||||
}
|
||||
|
||||
// Registers that this is a slash command
|
||||
public override registerApplicationCommands(registry: Command.Registry) {
|
||||
registry.registerChatInputCommand(
|
||||
(builder) => builder
|
||||
.setName(this.name)
|
||||
.setDescription(this.description)
|
||||
.addUserOption((option) => option.setName('user')
|
||||
.setDescription('User to give veg curious to')
|
||||
.setRequired(true)),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputInteraction) {
|
||||
// TODO add database updates
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user');
|
||||
const { guild } = interaction;
|
||||
|
||||
// Checks if all the variables are of the right type
|
||||
if (user === null || guild === null) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Gets guildMember whilst removing the ability of each other variables being null
|
||||
let guildMember = guild!.members.cache.get(user!.id);
|
||||
let vegCurious = guild!.roles.cache.get(IDs.roles.nonvegan.vegCurious);
|
||||
|
||||
// Checks if guildMember is null
|
||||
if (guildMember === null || vegCurious === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Removes the possibility of guildMember being null
|
||||
guildMember = guildMember!;
|
||||
vegCurious = vegCurious!;
|
||||
|
||||
// Checks if the user is vegan
|
||||
if (!guildMember.roles.cache.has(IDs.roles.vegan.vegan)) {
|
||||
await interaction.reply({
|
||||
content: `${user!} is not vegan!`,
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Checks if the user has Veg Curious and to give them or remove them based on if they have it
|
||||
if (guildMember.roles.cache.has(IDs.roles.nonvegan.vegCurious)) {
|
||||
// Remove the Veg Curious role from the user
|
||||
await guildMember.roles.remove(vegCurious);
|
||||
await interaction.reply({
|
||||
content: `Removed the ${vegCurious.name} role from ${user!}`,
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Add Veg Curious role to the user
|
||||
await guildMember.roles.add(vegCurious);
|
||||
await interaction.reply({
|
||||
content: `Gave ${user!} the ${vegCurious.name} role!`,
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default VegCuriousCommand;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user