mirror of
https://github.com/veganhacktivists/arabot.git
synced 2025-12-02 10:50:02 +01:00
Compare commits
707 Commits
github-dep
...
feat/clear
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c76a514a2c | ||
|
|
f77758c039 | ||
|
|
325dc0d0d0 | ||
|
|
71a065d3ca | ||
|
|
613f53491b | ||
|
|
19721c10ea | ||
|
|
bd87a8b6c6 | ||
|
|
46ef2fd8e2 | ||
|
|
d8c91fd39b | ||
|
|
fabd381051 | ||
|
|
a5758dc6ef | ||
|
|
9ff5b78aff | ||
|
|
f4655829e2 | ||
|
|
c82d256be4 | ||
|
|
a9039572d1 | ||
|
|
2cf7998cd9 | ||
|
|
63a4d651af | ||
|
|
a2dba859f2 | ||
|
|
a7b772f77a | ||
|
|
1fa87b8a4a | ||
|
|
4e99a5456f | ||
|
|
62d941dfcb | ||
|
|
e6b1463a1a | ||
|
|
5793bbb461 | ||
|
|
cf8142b86a | ||
|
|
502d5c5cdf | ||
|
|
8f071b8043 | ||
|
|
36ce086532 | ||
|
|
617834833a | ||
|
|
4d92250500 | ||
|
|
f898dada56 | ||
|
|
9e2f2c7558 | ||
|
|
e15e5da5aa | ||
|
|
e89f056b94 | ||
|
|
6acd012e7a | ||
|
|
75174f711d | ||
|
|
5e69ea6126 | ||
|
|
3d8aba5577 | ||
|
|
e7839552f8 | ||
|
|
3b0666e80d | ||
|
|
3d8a9be7f2 | ||
|
|
a7f608c1f0 | ||
|
|
cb457136d4 | ||
|
|
7e984c4857 | ||
|
|
0ea9ea3f64 | ||
|
|
19e70ebdbc | ||
|
|
5409be3d75 | ||
|
|
edd3caf9c0 | ||
|
|
5a87f97a74 | ||
|
|
feab05c1ea | ||
|
|
9b505fbece | ||
|
|
a5ba493372 | ||
|
|
98e514b5e9 | ||
|
|
172508c741 | ||
|
|
730f3e6a28 | ||
|
|
fbc2944b92 | ||
|
|
6172dc6ac6 | ||
|
|
b762ae3bc8 | ||
|
|
c8eb8299dd | ||
|
|
785e844da8 | ||
|
|
b3afe3f162 | ||
|
|
5bbc5057fc | ||
|
|
7943a2d1b8 | ||
|
|
3a4f8dba78 | ||
|
|
f4eac3cbe7 | ||
|
|
fe655ea0dc | ||
|
|
a95b1fde6e | ||
|
|
aad5066ba9 | ||
|
|
8302e11436 | ||
|
|
8c48473ef3 | ||
|
|
d90c985cec | ||
|
|
b6b50ea450 | ||
|
|
d00fddd51a | ||
|
|
e5f2c9436e | ||
|
|
0bb10b55ed | ||
|
|
d9c4f54299 | ||
|
|
77bbe97c6a | ||
|
|
a498cde933 | ||
|
|
750b10062f | ||
|
|
e348b09f80 | ||
|
|
4dc37ab31c | ||
|
|
396d69db06 | ||
|
|
94036984d3 | ||
|
|
0068fb5bdd | ||
|
|
1f3610a89d | ||
|
|
02298f2089 | ||
|
|
bb6ac8aef0 | ||
|
|
3f82f87317 | ||
|
|
fc2574e8e1 | ||
|
|
619aeb533b | ||
|
|
255ca62c33 | ||
|
|
ef0cf08bf8 | ||
|
|
113ebbbaee | ||
|
|
4a03675256 | ||
|
|
7fa844c24f | ||
|
|
5a42926c13 | ||
|
|
54f1c23c69 | ||
|
|
b3e1d11e72 | ||
|
|
d42332f290 | ||
|
|
534453fcc0 | ||
|
|
6fbb547ca7 | ||
|
|
a1beb4d347 | ||
|
|
48b30bfcb9 | ||
|
|
165a34ba0c | ||
|
|
c9a987a2a9 | ||
|
|
7aebbbc2fd | ||
|
|
779f8de43f | ||
|
|
0e77867010 | ||
|
|
c58360d563 | ||
|
|
e936fe49b1 | ||
|
|
b39cf0b44d | ||
|
|
81f5db4b0a | ||
|
|
8bc3b2dd4f | ||
|
|
cba2115080 | ||
|
|
f7b1512935 | ||
|
|
5d93db6365 | ||
|
|
1a8b6eb0c1 | ||
|
|
c2b0753232 | ||
|
|
07c5d85b15 | ||
|
|
dfd4ab7d26 | ||
|
|
81c01aede8 | ||
|
|
e06bde4540 | ||
|
|
f8c7267f26 | ||
|
|
7aaffda339 | ||
|
|
3752f57af0 | ||
|
|
c56a9521a5 | ||
|
|
e529cfd98f | ||
|
|
dd7c975db7 | ||
|
|
c1a35eeb89 | ||
|
|
39d0043937 | ||
|
|
4b999f0a5c | ||
|
|
95d6093ae6 | ||
|
|
768ac13f8b | ||
|
|
d27871e0f7 | ||
|
|
871696bf89 | ||
|
|
73da43ab0a | ||
|
|
3bf351e472 | ||
|
|
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 |
@@ -1,6 +1,6 @@
|
||||
.idea
|
||||
dist
|
||||
node_modules
|
||||
tsconfig.tsbuildinfo
|
||||
npm-debug.log
|
||||
.env
|
||||
*.md
|
||||
|
||||
@@ -10,5 +10,8 @@ POSTGRES_USER=USERNAME
|
||||
POSTGRES_PASSWORD=PASSWORD
|
||||
POSTGRES_DB=DB
|
||||
|
||||
# Redis
|
||||
REDIS_URL= # URL to redis database (if running everything within docker compose, use "redis")
|
||||
|
||||
# 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@v3
|
||||
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@v3
|
||||
|
||||
# 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@v3
|
||||
|
||||
10
.github/workflows/eslint.yml
vendored
10
.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,12 +38,12 @@ 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
|
||||
|
||||
- name: Upload analysis results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: eslint-results.sarif
|
||||
wait-for-processing: 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
|
||||
3
.npmrc
Normal file
3
.npmrc
Normal file
@@ -0,0 +1,3 @@
|
||||
node-linker=hoisted
|
||||
shamefully-hoist=true
|
||||
public-hoist-pattern[]=@sapphire/*
|
||||
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
node_modules
|
||||
tsconfig.json
|
||||
pnpm-lock.yaml
|
||||
3
.prettierrc.json
Normal file
3
.prettierrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
32
Dockerfile
32
Dockerfile
@@ -1,20 +1,26 @@
|
||||
FROM node:18-buster
|
||||
FROM node:22 AS base
|
||||
# PNPM
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /opt/app
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
|
||||
COPY --chown=node:node package.json .
|
||||
COPY --chown=node:node package-lock.json .
|
||||
COPY --chown=node:node tsconfig.json .
|
||||
COPY --chown=node:node prisma ./prisma/
|
||||
FROM base AS prod-deps
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
|
||||
RUN npm install
|
||||
FROM base AS build
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
RUN pnpm exec prisma generate
|
||||
RUN pnpm run build
|
||||
|
||||
COPY . .
|
||||
FROM base
|
||||
COPY --from=prod-deps /app/node_modules /app/node_modules
|
||||
COPY --from=build /app/node_modules/.prisma /app/node_modules/.prisma
|
||||
COPY --from=build /app/dist /app/dist
|
||||
|
||||
RUN npm run build
|
||||
|
||||
RUN chown node:node /opt/app/
|
||||
RUN chown node:node .
|
||||
|
||||
USER node
|
||||
|
||||
CMD [ "npm", "run", "start:migrate"]
|
||||
CMD [ "pnpm", "run", "start:migrate"]
|
||||
|
||||
@@ -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:16
|
||||
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.
|
||||
32
docs/commands/GENERAL.md
Normal file
32
docs/commands/GENERAL.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 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
|
||||
|
||||
## 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
|
||||
5736
package-lock.json
generated
5736
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
59
package.json
59
package.json
@@ -1,13 +1,17 @@
|
||||
{
|
||||
"name": "arabot",
|
||||
"version": "0.0.1",
|
||||
"version": "0.4.1",
|
||||
"description": "A Discord bot for Animal Rights Advocates",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"build": "tsc",
|
||||
"cleanBuild": "rm -rf ./dist && tsc",
|
||||
"start": "node dist/index.js",
|
||||
"start:migrate": "prisma migrate deploy && npm run start"
|
||||
"start:migrate": "prisma migrate deploy && pnpm run start"
|
||||
},
|
||||
"imports": {
|
||||
"#utils/*": "./dist/utils/*.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,32 +29,35 @@
|
||||
"url": "https://github.com/veganhacktivists/arabot/issues"
|
||||
},
|
||||
"homepage": "https://github.com/veganhacktivists/arabot#readme",
|
||||
"engines": {
|
||||
"node": ">=20",
|
||||
"pnpm": ">=9"
|
||||
},
|
||||
"packageManager": "pnpm@9.6.0",
|
||||
"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.17.0",
|
||||
"@sapphire/discord.js-utilities": "^7.3.0",
|
||||
"@sapphire/framework": "^5.2.1",
|
||||
"@sapphire/plugin-logger": "^4.0.2",
|
||||
"@sapphire/plugin-scheduled-tasks": "^10.0.1",
|
||||
"@sapphire/plugin-subcommands": "^6.0.3",
|
||||
"@sapphire/stopwatch": "^1.5.2",
|
||||
"@sapphire/time-utilities": "^1.7.12",
|
||||
"@sapphire/ts-config": "^5.0.1",
|
||||
"@sapphire/utilities": "^3.17.0",
|
||||
"bullmq": "^5.12.0",
|
||||
"discord.js": "^14.15.3",
|
||||
"ioredis": "^5.4.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "~5.4.5"
|
||||
},
|
||||
"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/node": "^20.14.14",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"prettier": "3.2.4",
|
||||
"prisma": "^5.17.0"
|
||||
}
|
||||
}
|
||||
|
||||
1923
pnpm-lock.yaml
generated
Normal file
1923
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
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;
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `active` on the `Warning` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Warning" DROP COLUMN "active";
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `channelId` to the `StatRole` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "StatRole" ADD COLUMN "channelId" TEXT NOT NULL;
|
||||
@@ -26,45 +26,220 @@ 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")
|
||||
ClearCommandMod ClearCommand[]
|
||||
}
|
||||
|
||||
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
|
||||
channelId 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 +251,16 @@ 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())
|
||||
note String
|
||||
}
|
||||
|
||||
model Restrict {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("restUser", fields: [userId], references: [id])
|
||||
@@ -83,19 +268,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 +296,28 @@ 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?
|
||||
}
|
||||
|
||||
model ClearCommand {
|
||||
id Int @id @default(autoincrement())
|
||||
mod User @relation(fields: [modId], references: [id])
|
||||
modId String
|
||||
messages Int
|
||||
time DateTime @default(now())
|
||||
}
|
||||
|
||||
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.LoaderContext, 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.LoaderContext, 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('✅');
|
||||
}
|
||||
}
|
||||
110
src/commands/coordinators/clear.ts
Normal file
110
src/commands/coordinators/clear.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
// 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 { PermissionFlagsBits } from 'discord.js';
|
||||
import type { Message } from 'discord.js';
|
||||
|
||||
export class ClearCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'clear',
|
||||
description: 'Deletes 1-100 messages in bulk',
|
||||
preconditions: [['CoordinatorOnly', 'ModOnly']],
|
||||
requiredUserPermissions: [PermissionFlagsBits.ManageMessages]
|
||||
});
|
||||
}
|
||||
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
397
src/commands/coordinators/private.ts
Normal file
397
src/commands/coordinators/private.ts
Normal file
@@ -0,0 +1,397 @@
|
||||
// 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.LoaderContext,
|
||||
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 if (user.roles.cache.has(IDs.roles.staff.mediaCoordinator)) {
|
||||
name = 'media';
|
||||
id = IDs.roles.staff.mediaCoordinator;
|
||||
} else if (user.roles.cache.has(IDs.roles.staff.hrCoordinator)) {
|
||||
name = 'hr';
|
||||
id = IDs.roles.staff.hrCoordinator;
|
||||
} 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.LoaderContext, 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.LoaderContext, 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.LoaderContext, 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;
|
||||
}
|
||||
}
|
||||
@@ -18,25 +18,24 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { N1984 } from '../../utils/gifs';
|
||||
import { EmbedBuilder, GuildMember } from 'discord.js';
|
||||
import { N1984 } from '#utils/gifs';
|
||||
import { addFunLog, countTotal } from '#utils/database/fun';
|
||||
|
||||
class N1984Command extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
export class N1984Command extends Command {
|
||||
public constructor(context: Command.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: '1984',
|
||||
description: 'this is literally 1984',
|
||||
preconditions: ['ModOnly'],
|
||||
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),
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -44,24 +43,42 @@ 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 { member } = interaction;
|
||||
|
||||
// Type checks
|
||||
if (!(member instanceof GuildMember)) {
|
||||
await interaction.reply({
|
||||
ephemeral: true,
|
||||
content: 'Failed to fetch your user on the bot!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await addFunLog(member.id, '1984');
|
||||
const count = await countTotal(member.id, '1984');
|
||||
|
||||
let embedFooter: string;
|
||||
if (count === 1) {
|
||||
embedFooter = `${member.displayName} 1984'd the server for the first time!`;
|
||||
} else {
|
||||
embedFooter = `${member.displayName} 1984'd the server ${count} times!`;
|
||||
}
|
||||
|
||||
// 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(`${member.displayName} is happy!`)
|
||||
.setImage(random1984)
|
||||
.setFooter({ text: embedFooter });
|
||||
|
||||
// Send the embed
|
||||
await interaction.reply({ embeds: [n1984Embed], fetchReply: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default N1984Command;
|
||||
|
||||
79
src/commands/fun/cringe.ts
Normal file
79
src/commands/fun/cringe.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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, GuildMember } from 'discord.js';
|
||||
import { Cringe } from '#utils/gifs';
|
||||
import { addFunLog, countTotal } from '#utils/database/fun';
|
||||
|
||||
export class CringeCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, 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
|
||||
const { member } = interaction;
|
||||
|
||||
// Type check
|
||||
if (!(member instanceof GuildMember)) {
|
||||
await interaction.reply({
|
||||
ephemeral: true,
|
||||
content: 'Failed to fetch your user on the bot!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await addFunLog(member.id, 'cringe');
|
||||
const count = await countTotal(member.id, 'cringe');
|
||||
|
||||
let embedFooter: string;
|
||||
if (count === 1) {
|
||||
embedFooter = `${member.displayName} cringed for the first time!`;
|
||||
} else {
|
||||
embedFooter = `${member.displayName} cringed ${count} times!`;
|
||||
}
|
||||
|
||||
// Creates the embed for the cringe reaction
|
||||
const randomCringe = Cringe[Math.floor(Math.random() * Cringe.length)];
|
||||
const cringeEmbed = new EmbedBuilder()
|
||||
.setColor('#001148')
|
||||
.setTitle(`${member.displayName} feels immense cringe...`)
|
||||
.setImage(randomCringe)
|
||||
.setFooter({ text: embedFooter });
|
||||
|
||||
// Send the embed
|
||||
await interaction.reply({ embeds: [cringeEmbed], fetchReply: true });
|
||||
}
|
||||
}
|
||||
@@ -18,25 +18,22 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Happy } from '../../utils/gifs';
|
||||
import { EmbedBuilder, GuildMember } from 'discord.js';
|
||||
import { Happy } from '#utils/gifs';
|
||||
|
||||
class HappyCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
export class HappyCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'happy',
|
||||
description: 'Express your happiness',
|
||||
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),
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -44,22 +41,27 @@ 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;
|
||||
const memberGuildMember = interaction.guild!.members.cache.get(member.id)!;
|
||||
const { member } = interaction;
|
||||
|
||||
// Type checks
|
||||
if (!(member instanceof GuildMember)) {
|
||||
await interaction.reply({
|
||||
ephemeral: true,
|
||||
content: 'Failed to fetch your user on the bot!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 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!`)
|
||||
.setTitle(`${member.displayName} is happy!`)
|
||||
.setImage(randomHappy);
|
||||
|
||||
// Send the embed
|
||||
await interaction.reply({ embeds: [happyEmbed], fetchReply: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default HappyCommand;
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Hugs } from '../../utils/gifs';
|
||||
import { EmbedBuilder, GuildMember } from 'discord.js';
|
||||
import { Hugs } from '#utils/gifs';
|
||||
import { addFunLog, countTotal } from '#utils/database/fun';
|
||||
|
||||
class HugCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
export class HugCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'hug',
|
||||
@@ -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,52 @@ 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.member;
|
||||
|
||||
// Type Checks
|
||||
|
||||
if (!(hugger instanceof GuildMember)) {
|
||||
await interaction.reply({
|
||||
ephemeral: true,
|
||||
content: 'Failed to fetch your user on the bot!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await addFunLog(hugger.id, 'hug', user.id);
|
||||
const count = await countTotal(hugger.id, 'hug', user.id);
|
||||
|
||||
let embedFooter: string;
|
||||
if (hugger.id === user.id) {
|
||||
if (count === 1) {
|
||||
embedFooter = `You hugged yourself for the first time!`;
|
||||
} else {
|
||||
embedFooter = `You hugged yourself ${count} times!`;
|
||||
}
|
||||
} else {
|
||||
if (count === 1) {
|
||||
embedFooter = `${hugger.displayName} hugged you for the first time!`;
|
||||
} else {
|
||||
embedFooter = `${hugger.displayName} hugged you ${count} times!`;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.displayName}`)
|
||||
.setImage(randomHug)
|
||||
.setFooter({ text: embedFooter });
|
||||
|
||||
// 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,11 +18,12 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Kill } from '../../utils/gifs';
|
||||
import { EmbedBuilder, GuildMember } from 'discord.js';
|
||||
import { Kill } from '#utils/gifs';
|
||||
import { addFunLog, countTotal } from '#utils/database/fun';
|
||||
|
||||
class KillCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
export class KillCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'kill',
|
||||
@@ -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,48 @@ 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.member;
|
||||
|
||||
// Type checks
|
||||
if (!(sender instanceof GuildMember)) {
|
||||
await interaction.reply({
|
||||
ephemeral: true,
|
||||
content: 'Failed to fetch your user on the bot!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let embedFooter: string;
|
||||
if (count === 1) {
|
||||
embedFooter = `${sender.displayName} killed you for the first time!`;
|
||||
} else {
|
||||
embedFooter = `${sender.displayName} killed you ${count} times!`;
|
||||
}
|
||||
|
||||
// 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.displayName}`)
|
||||
.setImage(randomKill)
|
||||
.setFooter({ text: embedFooter });
|
||||
|
||||
// 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, GuildMember } from 'discord.js';
|
||||
import { Poke } from '#utils/gifs';
|
||||
import { addFunLog, countTotal } from '#utils/database/fun';
|
||||
|
||||
class PokeCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
export class PokeCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, 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,51 @@ 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.member;
|
||||
|
||||
// Type checks
|
||||
if (!(sender instanceof GuildMember)) {
|
||||
await interaction.reply({
|
||||
ephemeral: true,
|
||||
content: 'Failed to fetch your user on the bot!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await addFunLog(sender.id, 'poke', user.id);
|
||||
const count = await countTotal(sender.id, 'poke', user.id);
|
||||
|
||||
let embedFooter: string;
|
||||
if (sender.id === user.id) {
|
||||
if (count === 1) {
|
||||
embedFooter = `You poked yourself for the first time!`;
|
||||
} else {
|
||||
embedFooter = `You poked yourself ${count} times!`;
|
||||
}
|
||||
} else {
|
||||
if (count === 1) {
|
||||
embedFooter = `${sender.displayName} poked you for the first time!`;
|
||||
} else {
|
||||
embedFooter = `${sender.displayName} poked you ${count} times!`;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.displayName}`)
|
||||
.setImage(randomPoke)
|
||||
.setFooter({ text: embedFooter });
|
||||
|
||||
// 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,25 +18,22 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Sad } from '../../utils/gifs';
|
||||
import { EmbedBuilder, GuildMember } from 'discord.js';
|
||||
import { Sad } from '#utils/gifs';
|
||||
|
||||
class SadCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
export class SadCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'sad',
|
||||
description: 'Express your sadness',
|
||||
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),
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -44,22 +41,27 @@ 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;
|
||||
const memberGuildMember = interaction.guild!.members.cache.get(member.id)!;
|
||||
const { member } = interaction;
|
||||
|
||||
// Type checks
|
||||
if (!(member instanceof GuildMember)) {
|
||||
await interaction.reply({
|
||||
ephemeral: true,
|
||||
content: 'Failed to fetch your user on the bot!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 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...`)
|
||||
.setTitle(`${member.displayName} is sad...`)
|
||||
.setImage(randomSad);
|
||||
|
||||
// Send the embed
|
||||
await interaction.reply({ embeds: [sadEmbed], fetchReply: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default SadCommand;
|
||||
|
||||
@@ -18,25 +18,22 @@
|
||||
*/
|
||||
|
||||
import { Command, RegisterBehavior } from '@sapphire/framework';
|
||||
import { MessageEmbed } from 'discord.js';
|
||||
import { Shrug } from '../../utils/gifs';
|
||||
import { EmbedBuilder, GuildMember } from 'discord.js';
|
||||
import { Shrug } from '#utils/gifs';
|
||||
|
||||
class ShrugCommand extends Command {
|
||||
public constructor(context: Command.Context, options: Command.Options) {
|
||||
export class ShrugCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'shrug',
|
||||
description: 'Ugh... whatever... idk...',
|
||||
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),
|
||||
(builder) => builder.setName(this.name).setDescription(this.description),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
@@ -44,22 +41,27 @@ 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;
|
||||
const memberGuildMember = interaction.guild!.members.cache.get(member.id)!;
|
||||
const { member } = interaction;
|
||||
|
||||
// Type checks
|
||||
if (!(member instanceof GuildMember)) {
|
||||
await interaction.reply({
|
||||
ephemeral: true,
|
||||
content: 'Failed to fetch your user on the bot!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 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`)
|
||||
.setTitle(`${member.displayName} shrugs`)
|
||||
.setImage(randomShrug);
|
||||
|
||||
// Send the embed
|
||||
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.LoaderContext, 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;
|
||||
}
|
||||
}
|
||||
314
src/commands/mod/ban/tempBan.ts
Normal file
314
src/commands/mod/ban/tempBan.ts
Normal file
@@ -0,0 +1,314 @@
|
||||
// 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.LoaderContext, 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
|
||||
await this.container.tasks.create(
|
||||
{
|
||||
name: 'tempBan',
|
||||
payload: {
|
||||
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.LoaderContext, 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;
|
||||
}
|
||||
}
|
||||
288
src/commands/mod/diversity.ts
Normal file
288
src/commands/mod/diversity.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
// 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.LoaderContext,
|
||||
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.LoaderContext, 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.LoaderContext, 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.LoaderContext, 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 ? '✅' : '❌');
|
||||
}
|
||||
}
|
||||
242
src/commands/mod/restriction/restrictLogs.ts
Normal file
242
src/commands/mod/restriction/restrictLogs.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
// 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.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'restrictlogs',
|
||||
description: 'Shows restriction history for 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;
|
||||
}
|
||||
|
||||
const staffChannel = checkStaff(channel);
|
||||
if (staffChannel) {
|
||||
channel = channel as TextChannel;
|
||||
|
||||
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.LoaderContext, 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 ? '✅' : '❌');
|
||||
}
|
||||
}
|
||||
181
src/commands/mod/restriction/restrictTools.ts
Normal file
181
src/commands/mod/restriction/restrictTools.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
// 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.LoaderContext,
|
||||
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.LoaderContext, 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.LoaderContext, 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.LoaderContext, 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,287 @@
|
||||
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,
|
||||
EmbedBuilder,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonInteraction,
|
||||
ButtonStyle,
|
||||
User,
|
||||
Guild,
|
||||
TextChannel,
|
||||
GuildMember,
|
||||
Snowflake,
|
||||
} from 'discord.js';
|
||||
import type { Message, GuildMember } from 'discord.js';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import type { Message } from 'discord.js';
|
||||
import { isMessageInstance } from '@sapphire/discord.js-utilities';
|
||||
import { addExistingUser, userExists } from '../../utils/dbExistingUser';
|
||||
import IDs from '../../utils/ids';
|
||||
import {
|
||||
addSusNoteDB,
|
||||
findNotes,
|
||||
getNote,
|
||||
deactivateNote,
|
||||
deactivateAllNotes,
|
||||
} from '#utils/database/sus';
|
||||
import { checkStaff } from '#utils/checker';
|
||||
import IDs from '#utils/ids';
|
||||
import { createSusLogEmbed } from '#utils/embeds';
|
||||
|
||||
// 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.LoaderContext,
|
||||
options: Subcommand.Options,
|
||||
) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'sus',
|
||||
subcommands: [
|
||||
{
|
||||
name: 'add',
|
||||
default: true,
|
||||
chatInputRun: 'addNoteChatInput',
|
||||
messageRun: 'addNoteMessage',
|
||||
},
|
||||
{
|
||||
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 addNoteChatInput(
|
||||
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 instanceof Guild)) {
|
||||
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) {
|
||||
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 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);
|
||||
}
|
||||
const info = await this.addNote(user, mod, note, guild);
|
||||
|
||||
await interaction.reply({
|
||||
content: `${user} note: ${note}`,
|
||||
content: info.message,
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
}
|
||||
|
||||
private async listNote(interaction: Command.ChatInputInteraction) {
|
||||
// Non Application Command method of adding a sus note
|
||||
public async addNoteMessage(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 note = args.finished ? null : await args.rest('string');
|
||||
const mod = message.author;
|
||||
|
||||
if (note === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('No sus note was provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const guild = message.guild;
|
||||
|
||||
if (!(guild instanceof Guild)) {
|
||||
await message.react('❌');
|
||||
await message.reply(
|
||||
'Could not find guild! Make sure you run this command in a server.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.addNote(user, mod, note, guild);
|
||||
|
||||
if (!info.success) {
|
||||
await message.react('❌');
|
||||
return;
|
||||
}
|
||||
|
||||
await message.react('✅');
|
||||
}
|
||||
|
||||
private async addNote(user: User, mod: User, note: string, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
success: false,
|
||||
};
|
||||
|
||||
// Add the data to the database
|
||||
await addSusNoteDB(user.id, mod.id, note);
|
||||
|
||||
// Gives the sus role to the user
|
||||
await this.addSusRole(user, guild);
|
||||
|
||||
info.message = `Added the sus note for ${user}: ${note}`;
|
||||
info.success = true;
|
||||
|
||||
// Log the sus note
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.sus) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(IDs.channels.logs.sus)) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
if (logChannel === undefined) {
|
||||
this.container.logger.error('Sus Error: Could not fetch log channel');
|
||||
info.message = `Added a sus note for ${user} but could not find the log channel. This has been logged to the database.`;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
const message = new EmbedBuilder()
|
||||
.setColor('#0099ff')
|
||||
.setAuthor({
|
||||
name: `Added sus note for ${user.tag}`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'User', value: `${user}`, inline: true },
|
||||
{ name: 'Moderator', value: `${mod}`, inline: true },
|
||||
{ name: 'Note', value: note },
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${user.id}` });
|
||||
|
||||
await logChannel.send({ embeds: [message] });
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private async addSusRole(user: User, guild: Guild) {
|
||||
// Get GuildMember for user to add a sus note for
|
||||
let member = guild.members.cache.get(user.id);
|
||||
|
||||
// Checks if Member was not found in cache
|
||||
if (member === undefined) {
|
||||
// Fetches Member from API call to Discord
|
||||
member = await guild.members.fetch(user.id).catch(() => undefined);
|
||||
}
|
||||
|
||||
if (member === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Give the user the sus role they don't already have the sus note
|
||||
if (!member.roles.cache.has(IDs.roles.restrictions.sus)) {
|
||||
await member.roles.add(IDs.roles.restrictions.sus);
|
||||
}
|
||||
}
|
||||
|
||||
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!;
|
||||
const staffChannel = checkStaff(interaction.channel);
|
||||
|
||||
// Gets the sus notes from the database
|
||||
const notes = await findNotes(user.id, true);
|
||||
@@ -299,52 +313,32 @@ class SusCommand extends Command {
|
||||
}
|
||||
|
||||
// Creates the embed to display the sus note
|
||||
const noteEmbed = new MessageEmbed()
|
||||
.setColor('#0099ff')
|
||||
.setTitle(`${notes.length} sus notes for ${user.username}`)
|
||||
.setThumbnail(user.avatarURL()!);
|
||||
|
||||
// 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) {
|
||||
// Get mod name
|
||||
const modGuildMember = guild!.members.cache.get(notes[i].modId);
|
||||
let mod = notes[i].modId;
|
||||
if (modGuildMember !== undefined) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
const noteEmbed = createSusLogEmbed(notes, user, guild);
|
||||
|
||||
// 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 mod = interaction.user;
|
||||
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);
|
||||
|
||||
@@ -358,44 +352,56 @@ class SusCommand extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 userId = note.userId;
|
||||
const modId = note.modId;
|
||||
|
||||
// Get user's name
|
||||
let userName = note!.userId;
|
||||
if (user !== undefined) {
|
||||
userName = user!.displayName;
|
||||
// Get user GuildMembers for user and mod and person who ran command
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
if (!(user instanceof User)) {
|
||||
user = await guild.client.users.fetch(userId).catch(() => undefined);
|
||||
}
|
||||
if (user === undefined) {
|
||||
await interaction.reply({
|
||||
content: 'Error fetching user!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get mod name
|
||||
let modName = note!.modId;
|
||||
if (mod !== undefined) {
|
||||
modName = mod!.displayName;
|
||||
let modCreator = guild.client.users.cache.get(modId);
|
||||
if (!(modCreator instanceof User)) {
|
||||
modCreator = await guild.client.users.fetch(modId).catch(() => undefined);
|
||||
}
|
||||
|
||||
let modCreatorDisplay = modId;
|
||||
if (modCreator instanceof User) {
|
||||
modCreatorDisplay = modCreator.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,
|
||||
);
|
||||
.setTitle(`Sus note for ${user.tag}`)
|
||||
.setThumbnail(user.displayAvatarURL())
|
||||
.addFields({
|
||||
name: `ID: ${noteId} | Moderator: ${modCreatorDisplay} | 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 +418,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,20 +426,30 @@ 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: `${user}'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(userId, 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);
|
||||
let member = guild.members.cache.get(userId);
|
||||
if (!(member instanceof GuildMember)) {
|
||||
member = await guild.members.fetch(userId).catch(() => undefined);
|
||||
}
|
||||
|
||||
if (member instanceof GuildMember) {
|
||||
await member.roles.remove(IDs.roles.restrictions.sus);
|
||||
}
|
||||
}
|
||||
|
||||
// Logs the removal of the sus note
|
||||
await this.deleteNoteLogger(userId, mod, noteId, guild);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -445,25 +461,74 @@ class SusCommand extends Command {
|
||||
});
|
||||
}
|
||||
|
||||
private async removeAllNotes(interaction: Command.ChatInputInteraction) {
|
||||
// Logs removal of 1 sus note
|
||||
private async deleteNoteLogger(
|
||||
userId: Snowflake,
|
||||
mod: User,
|
||||
noteId: number,
|
||||
guild: Guild,
|
||||
) {
|
||||
// Find user
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
if (!(user instanceof User)) {
|
||||
user = await guild.client.users.fetch(userId).catch(() => undefined);
|
||||
}
|
||||
if (!(user instanceof User)) return;
|
||||
|
||||
// Log the sus note
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.sus) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(IDs.channels.logs.sus)) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
if (logChannel === undefined) {
|
||||
this.container.logger.error('Sus Error: Could not fetch log channel');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#28A745')
|
||||
.setAuthor({
|
||||
name: `Removed sus note for ${user.tag}`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'User', value: `${user}`, inline: true },
|
||||
{ name: 'Moderator', value: `${mod}`, inline: true },
|
||||
{ name: 'Note ID', value: `${noteId}`, inline: true },
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${user.id}` });
|
||||
|
||||
await logChannel.send({ embeds: [embed] });
|
||||
}
|
||||
|
||||
public async removeAllNotes(
|
||||
interaction: Subcommand.ChatInputCommandInteraction,
|
||||
) {
|
||||
// Get the arguments
|
||||
const user = interaction.options.getUser('user');
|
||||
const user = interaction.options.getUser('user', true);
|
||||
const mod = interaction.user;
|
||||
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 +539,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 +552,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,21 +607,23 @@ 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: [],
|
||||
});
|
||||
}
|
||||
|
||||
await this.deleteAllNotesLogger(user, mod, guild);
|
||||
});
|
||||
|
||||
// Remove the buttons after they have been clicked
|
||||
@@ -560,57 +634,39 @@ 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) {
|
||||
// Get arguments
|
||||
let user: GuildMember;
|
||||
try {
|
||||
user = await args.pick('member');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
const note = args.finished ? null : await args.rest('string');
|
||||
const mod = message.member;
|
||||
// Logs removal of 1 sus note
|
||||
private async deleteAllNotesLogger(user: User, mod: User, guild: Guild) {
|
||||
// Log the sus note
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.sus) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (note === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('No sus note was provided!');
|
||||
return;
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(IDs.channels.logs.sus)) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
if (logChannel === undefined) {
|
||||
this.container.logger.error('Sus Error: Could not fetch log channel');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mod === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Moderator not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#28A745')
|
||||
.setAuthor({
|
||||
name: `Purged all sus notes for ${user.tag}`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'User', value: `${user}`, inline: true },
|
||||
{ name: 'Moderator', value: `${mod}`, inline: true },
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${user.id}` });
|
||||
|
||||
// Check if user and mod are on the database
|
||||
if (!await userExists(user)) {
|
||||
await addExistingUser(user);
|
||||
}
|
||||
if (!await userExists(mod!)) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Checks if the user is xlevra to send a very kind message
|
||||
if (mod.id === '259624904746467329') {
|
||||
await message.reply('Fuck you for making me add this feature 🤬');
|
||||
}
|
||||
|
||||
await message.react('✅');
|
||||
await logChannel.send({ embeds: [embed] });
|
||||
}
|
||||
}
|
||||
|
||||
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.LoaderContext, 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('✅');
|
||||
}
|
||||
}
|
||||
183
src/commands/mod/warning/deleteWarning.ts
Normal file
183
src/commands/mod/warning/deleteWarning.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2024 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 { EmbedBuilder, TextChannel } from 'discord.js';
|
||||
import type { Message, Guild, User } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { deleteWarning, fetchWarning } from '#utils/database/warnings';
|
||||
import { checkStaff } from '#utils/checker';
|
||||
|
||||
export class DeleteWarningCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'deletewarning',
|
||||
aliases: ['delwarn', 'removewarning'],
|
||||
description: 'Deletes a warning',
|
||||
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)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('id')
|
||||
.setDescription('ID for the warning')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// Get the arguments
|
||||
const warningId = interaction.options.getInteger('id', 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 staffChannel = checkStaff(interaction.channel);
|
||||
|
||||
await interaction.deferReply({ ephemeral: !staffChannel });
|
||||
|
||||
const info = await this.deleteWarning(warningId, mod, guild);
|
||||
|
||||
await interaction.editReply({
|
||||
content: info.message,
|
||||
embeds: info.embeds,
|
||||
});
|
||||
}
|
||||
|
||||
// Non Application Command method for removing a warning
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let warningId: number;
|
||||
try {
|
||||
warningId = await args.pick('integer');
|
||||
} catch {
|
||||
await message.react('❌');
|
||||
await message.react('Correct warning ID 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 info = await this.deleteWarning(warningId, mod, guild);
|
||||
|
||||
await message.reply({ content: info.message, embeds: info.embeds });
|
||||
if (!info.success) {
|
||||
await message.react('❌');
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteWarning(warningId: number, mod: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
embeds: [] as EmbedBuilder[],
|
||||
success: false,
|
||||
};
|
||||
|
||||
const warning = await fetchWarning(warningId);
|
||||
|
||||
if (warning === null) {
|
||||
info.message = `Warning ID \`${warningId}\` not found!`;
|
||||
return info;
|
||||
}
|
||||
|
||||
await deleteWarning(warningId);
|
||||
info.success = true;
|
||||
|
||||
const userId = warning.userId;
|
||||
let user = guild.client.users.cache.get(userId);
|
||||
|
||||
if (user === undefined) {
|
||||
user = await guild.client.users.fetch(userId);
|
||||
if (user === undefined) {
|
||||
info.message = `Deleted warning ID \`${warningId}\`, but the user could not be found!`;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
// Log the warnings deletion
|
||||
let logChannel = guild.channels.cache.get(IDs.channels.logs.sus) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(IDs.channels.logs.sus)) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
if (logChannel === undefined) {
|
||||
this.container.logger.error(
|
||||
'Delete Warning Error: Could not fetch log channel',
|
||||
);
|
||||
info.message =
|
||||
`Deleted warning for ${user} (Warning ID: ${warningId} but ` +
|
||||
'could not find the log channel.';
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
const message = new EmbedBuilder()
|
||||
.setColor('#28A745')
|
||||
.setAuthor({
|
||||
name: `Removed warning for ${user.tag}`,
|
||||
iconURL: `${user.displayAvatarURL()}`,
|
||||
})
|
||||
.addFields(
|
||||
{ name: 'User', value: `${user}`, inline: true },
|
||||
{ name: 'Moderator', value: `${mod}`, inline: true },
|
||||
{ name: 'Warning ID', value: `${warningId}`, inline: true },
|
||||
)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${userId}` });
|
||||
|
||||
await logChannel.send({ embeds: [message] });
|
||||
|
||||
info.message = `Deleted warning for ${user}`;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
226
src/commands/mod/warning/warn.ts
Normal file
226
src/commands/mod/warning/warn.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2023, 2024 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,
|
||||
container,
|
||||
RegisterBehavior,
|
||||
} from '@sapphire/framework';
|
||||
import type { User, Message, Snowflake, Guild, TextChannel } from 'discord.js';
|
||||
import { updateUser } from '#utils/database/dbExistingUser';
|
||||
import { addWarn } from '#utils/database/warnings';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
|
||||
export class WarnCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'warn',
|
||||
description: 'Warns a 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 warn')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('Reason for the warning')
|
||||
.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();
|
||||
|
||||
const info = await this.warn(user.id, mod.id, reason, guild);
|
||||
|
||||
await interaction.editReply({
|
||||
content: info.message,
|
||||
});
|
||||
}
|
||||
|
||||
// Non Application Command method for warning 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.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('❌');
|
||||
return;
|
||||
}
|
||||
|
||||
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 User for person being restricted
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
await addWarn(userId, modId, reason);
|
||||
|
||||
info.message = `Warned ${user}`;
|
||||
info.success = true;
|
||||
|
||||
// DM the reason
|
||||
|
||||
const dmEmbed = new EmbedBuilder()
|
||||
.setColor('#FF6700')
|
||||
.setAuthor({
|
||||
name: "You've been warned!",
|
||||
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.sus) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
|
||||
if (logChannel === undefined) {
|
||||
logChannel = (await guild.channels.fetch(IDs.channels.logs.sus)) as
|
||||
| TextChannel
|
||||
| undefined;
|
||||
if (logChannel === undefined) {
|
||||
container.logger.error('Warn Error: Could not fetch log channel');
|
||||
info.message = `Warned ${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: `Warned ${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;
|
||||
}
|
||||
}
|
||||
161
src/commands/mod/warning/warnings.ts
Normal file
161
src/commands/mod/warning/warnings.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2024 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, Guild, User } from 'discord.js';
|
||||
import IDs from '#utils/ids';
|
||||
import { fetchWarnings } from '#utils/database/warnings';
|
||||
import { checkStaff } from '#utils/checker';
|
||||
import { createWarningsEmbed } from '#utils/embeds';
|
||||
|
||||
export class WarningsCommand extends Command {
|
||||
public constructor(context: Command.LoaderContext, options: Command.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
name: 'warnings',
|
||||
aliases: ['warninglog', 'warnlog'],
|
||||
description: 'Shows all the warnings for the 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 the warnings for')
|
||||
.setRequired(true),
|
||||
),
|
||||
{
|
||||
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Command run
|
||||
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
|
||||
// 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 guild!',
|
||||
ephemeral: true,
|
||||
fetchReply: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const staffChannel = checkStaff(interaction.channel);
|
||||
|
||||
await interaction.deferReply({ ephemeral: !staffChannel });
|
||||
|
||||
const info = await this.warnings(user, guild);
|
||||
|
||||
await interaction.editReply({
|
||||
content: info.message,
|
||||
embeds: info.embeds,
|
||||
});
|
||||
}
|
||||
|
||||
// Non Application Command method for fetching warnings
|
||||
public async messageRun(message: Message, args: Args) {
|
||||
// Get arguments
|
||||
let user: User | undefined;
|
||||
try {
|
||||
user = await args.pick('user');
|
||||
} catch {
|
||||
user = undefined;
|
||||
}
|
||||
|
||||
const { guild } = message;
|
||||
|
||||
if (guild === null) {
|
||||
await message.react('❌');
|
||||
await message.reply('Guild not found! Try again or contact a developer!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (user === undefined) {
|
||||
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
|
||||
const userId = topic[2];
|
||||
|
||||
user = guild.client.users.cache.get(userId);
|
||||
|
||||
if (user === undefined) {
|
||||
user = await guild.client.users.fetch(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (user === undefined) {
|
||||
await message.react('❌');
|
||||
await message.reply('User was not provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await this.warnings(user, guild);
|
||||
|
||||
await message.reply({ content: info.message, embeds: info.embeds });
|
||||
}
|
||||
|
||||
private async warnings(user: User, guild: Guild) {
|
||||
const info = {
|
||||
message: '',
|
||||
embeds: [] as EmbedBuilder[],
|
||||
};
|
||||
|
||||
const warnings = await fetchWarnings(user.id);
|
||||
|
||||
if (warnings.length === 0) {
|
||||
info.message = `${user} user has no warnings.`;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Creates an embed to display the warnings
|
||||
const embed = createWarningsEmbed(warnings, user, guild);
|
||||
|
||||
info.embeds.push(embed);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
596
src/commands/outreach/outreach.ts
Normal file
596
src/commands/outreach/outreach.ts
Normal file
@@ -0,0 +1,596 @@
|
||||
// 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 { ChannelType, PermissionsBitField, 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.LoaderContext,
|
||||
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' },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// 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: 'Outreach Leader was not found!',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mod.roles.cache.has(IDs.roles.staff.outreachLeader)) {
|
||||
await interaction.reply({
|
||||
content: 'You need to be an Outreach Leader 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.outreachLeader)) {
|
||||
await interaction.reply({
|
||||
content: 'You need to be an Outreach Leader 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); // Delete role
|
||||
guild.channels.delete(role.channelId); // Delete VC
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
// Create role for group
|
||||
const role = await guild.roles.create({
|
||||
name: `Outreach Group ${groupNo}`,
|
||||
mentionable: true,
|
||||
});
|
||||
|
||||
// Create a voice channel for group
|
||||
const channel = await guild.channels.create({
|
||||
name: `Outreach Group ${groupNo}`,
|
||||
type: ChannelType.GuildVoice,
|
||||
parent: IDs.categories.activism,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
deny: [
|
||||
PermissionsBitField.Flags.SendMessages,
|
||||
PermissionsBitField.Flags.Connect,
|
||||
PermissionsBitField.Flags.ViewChannel,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.activist,
|
||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
||||
},
|
||||
{
|
||||
id: role.id, // Permissions for the specific group
|
||||
allow: [
|
||||
PermissionsBitField.Flags.SendMessages,
|
||||
PermissionsBitField.Flags.Connect,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.staff.outreachLeader,
|
||||
allow: [
|
||||
PermissionsBitField.Flags.SendMessages,
|
||||
PermissionsBitField.Flags.Connect,
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Create stats in database
|
||||
await createStat(event.id, leader.id, role.id, channel.id);
|
||||
|
||||
// Give group leader role
|
||||
await leaderMember.roles.add(role);
|
||||
|
||||
// Send message in VC with a welcome and reminder
|
||||
await channel.send(
|
||||
`Welcome ${role}, ${leaderMember} is going to be the leader of your group!\n\n` +
|
||||
'Remember to keep track of stats during activism with `/outreach group update` and' +
|
||||
'to have these questions in mind whilst doing activism:\n' +
|
||||
'- How many said would go vegan?\n' +
|
||||
'- How many seriously considered being vegan?\n' +
|
||||
'- How many people had anti-vegan viewpoints?\n' +
|
||||
'- How many thanked you for the conversation?\n' +
|
||||
'- How many said they would watch a vegan documentary?\n' +
|
||||
'- How many got educated on veganism or the animal industry?',
|
||||
);
|
||||
|
||||
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.outreachLeader)
|
||||
) {
|
||||
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.LoaderContext, 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.LoaderContext, 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.LoaderContext, 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.LoaderContext, 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.LoaderContext, 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.LoaderContext, 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.LoaderContext, 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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user