692 Commits

Author SHA1 Message Date
Anthony
2cf7998cd9 feat(arabot): add hr and mentor coordinators private channels 2024-02-07 15:03:26 +00:00
Anthony Berg
63a4d651af build: change ioredis to dev dep 2024-02-03 22:45:39 +00:00
Anthony Berg
a2dba859f2 build: fix prisma generate in Dockerfile 2024-02-03 22:39:26 +00:00
Anthony Berg
a7b772f77a build: update deps 2024-02-03 22:18:11 +00:00
Anthony Berg
1fa87b8a4a Merge remote-tracking branch 'origin/main'
# Conflicts:
#	package-lock.json
#	package.json
2024-02-03 22:14:50 +00:00
Anthony Berg
4e99a5456f build: change to pnpm 2024-02-03 22:13:35 +00:00
Anthony
62d941dfcb feat(arabot): add better checks for types for temp bans 2024-01-27 19:46:31 +01:00
Anthony
e6b1463a1a refactor: run prettier 2024-01-27 19:46:17 +01:00
Anthony
5793bbb461 fix(arabot): catch conditions for fetching guild and user 2024-01-27 19:41:22 +01:00
Anthony
cf8142b86a fix(arabot): deleted channel errors 2024-01-27 19:38:59 +01:00
Anthony
502d5c5cdf ci: update deps 2024-01-27 19:27:19 +01:00
Anthony Berg
8f071b8043 Merge pull request #187 from veganhacktivists/renovate/node-20.x-lockfile
fix(deps): update dependency @types/node to v20.11.8
2024-01-27 18:04:39 +00:00
Anthony Berg
36ce086532 Merge pull request #188 from veganhacktivists/renovate/bullmq-5.x-lockfile
fix(deps): update dependency bullmq to v5.1.5
2024-01-27 18:04:24 +00:00
Anthony Berg
617834833a Merge pull request #189 from veganhacktivists/renovate/prisma-monorepo
fix(deps): update prisma monorepo to v5.8.1
2024-01-27 18:04:15 +00:00
renovate[bot]
4d92250500 fix(deps): update prisma monorepo to v5.8.1 2024-01-27 17:59:09 +00:00
renovate[bot]
f898dada56 fix(deps): update dependency @types/node to v20.11.8 2024-01-27 17:58:51 +00:00
renovate[bot]
9e2f2c7558 fix(deps): update dependency bullmq to v5.1.5 2024-01-27 14:03:25 +00:00
Anthony Berg
e15e5da5aa Merge pull request #186 from veganhacktivists/renovate/sapphire-time-utilities-1.x-lockfile
fix(deps): update dependency @sapphire/time-utilities to v1.7.12
2024-01-27 14:02:47 +00:00
Anthony Berg
e89f056b94 Merge pull request #185 from veganhacktivists/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v6.19.1
2024-01-27 14:02:27 +00:00
renovate[bot]
6acd012e7a fix(deps): update dependency @sapphire/time-utilities to v1.7.12 2024-01-27 13:50:08 +00:00
renovate[bot]
75174f711d chore(deps): update typescript-eslint monorepo to v6.19.1 2024-01-27 13:49:56 +00:00
Anthony
5e69ea6126 fix(arabot): grammar in welcome message 2024-01-24 15:43:45 +00:00
Anthony Berg
3d8aba5577 Merge pull request #182 from veganhacktivists/renovate/sapphire-plugin-logger-4.x-lockfile
fix(deps): update dependency @sapphire/plugin-logger to v4.0.2
2024-01-21 23:36:36 +00:00
Anthony Berg
e7839552f8 Merge pull request #183 from veganhacktivists/renovate/sapphire-plugin-scheduled-tasks-10.x-lockfile
fix(deps): update dependency @sapphire/plugin-scheduled-tasks to v10.0.1
2024-01-21 23:36:26 +00:00
Anthony Berg
3b0666e80d Merge pull request #184 from veganhacktivists/renovate/sapphire-plugin-subcommands-6.x-lockfile
fix(deps): update dependency @sapphire/plugin-subcommands to v6.0.3
2024-01-21 23:36:17 +00:00
Anthony Berg
3d8a9be7f2 Merge pull request #180 from veganhacktivists/renovate/node-20.x-lockfile
fix(deps): update dependency @types/node to v20.11.5
2024-01-21 23:35:58 +00:00
Anthony Berg
a7f608c1f0 Merge pull request #179 from veganhacktivists/renovate/prettier-3.x
chore(deps): update dependency prettier to v3.2.4
2024-01-21 23:35:48 +00:00
Anthony Berg
cb457136d4 Merge pull request #178 from veganhacktivists/renovate/sapphire-framework-5.x-lockfile
fix(deps): update dependency @sapphire/framework to v5.0.7
2024-01-21 23:35:40 +00:00
Anthony Berg
7e984c4857 Merge pull request #177 from veganhacktivists/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v6.19.0
2024-01-21 23:35:32 +00:00
Anthony Berg
0ea9ea3f64 feat(arabot): remove Patreon precondition 2024-01-21 20:11:21 +00:00
renovate[bot]
19e70ebdbc fix(deps): update dependency @sapphire/plugin-subcommands to v6.0.3 2024-01-20 16:17:44 +00:00
renovate[bot]
5409be3d75 fix(deps): update dependency @sapphire/plugin-scheduled-tasks to v10.0.1 2024-01-20 16:17:37 +00:00
renovate[bot]
edd3caf9c0 fix(deps): update dependency @sapphire/plugin-logger to v4.0.2 2024-01-20 14:29:26 +00:00
renovate[bot]
5a87f97a74 fix(deps): update dependency @sapphire/framework to v5.0.7 2024-01-19 21:34:24 +00:00
renovate[bot]
feab05c1ea chore(deps): update dependency prettier to v3.2.4 2024-01-17 11:41:50 +00:00
renovate[bot]
9b505fbece fix(deps): update dependency @types/node to v20.11.5 2024-01-17 07:25:22 +00:00
renovate[bot]
a5ba493372 chore(deps): update typescript-eslint monorepo to v6.19.0 2024-01-15 18:20:21 +00:00
Anthony Berg
98e514b5e9 fix(arabot): not giving roles back to server boosters 2024-01-13 02:06:25 +00:00
Anthony Berg
172508c741 feat(arabot): remove unholy fun command 2024-01-13 01:36:43 +00:00
Anthony Berg
730f3e6a28 feat(arabot): add fun listener for "bad" words 2024-01-13 01:17:34 +00:00
Anthony Berg
fbc2944b92 fix(arabot): 1984 preconditions 2024-01-13 01:07:20 +00:00
Anthony Berg
6172dc6ac6 feat(arabot): add logging for sus note purges 2024-01-13 00:57:14 +00:00
Anthony Berg
b762ae3bc8 feat(arabot): add logging for one sus note removal 2024-01-13 00:52:54 +00:00
Anthony Berg
c8eb8299dd refactor(arabot): change removing sus notes to have more checks for types 2024-01-13 00:29:57 +00:00
Anthony Berg
785e844da8 feat(arabot): sus note logging for added sus note 2024-01-13 00:11:06 +00:00
Anthony
b3afe3f162 refactor(arabot): allow coordinators to run 1984 command 2024-01-09 19:12:10 +00:00
Anthony
5bbc5057fc feat(arabot): add type checking and made footers for existing embeds better 2024-01-09 19:10:29 +00:00
Anthony Berg
7943a2d1b8 Merge pull request #155 from veganhacktivists/renovate/postgres-16.x
chore(deps): update postgres docker tag to v16
2024-01-06 19:33:04 +00:00
Anthony Berg
3a4f8dba78 Merge pull request #176 from veganhacktivists/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v6.18.0
2024-01-06 18:54:40 +00:00
renovate[bot]
f4eac3cbe7 chore(deps): update typescript-eslint monorepo to v6.18.0 2024-01-06 17:20:06 +00:00
Anthony Berg
fe655ea0dc Merge pull request #175 from veganhacktivists/renovate/github-codeql-action-3.x
chore(deps): update github/codeql-action action to v3
2024-01-06 12:17:14 +00:00
Anthony Berg
a95b1fde6e Merge pull request #174 from veganhacktivists/renovate/sapphire-utilities-3.x-lockfile
fix(deps): update dependency @sapphire/utilities to v3.15.2
2024-01-06 12:16:38 +00:00
renovate[bot]
aad5066ba9 chore(deps): update github/codeql-action action to v3 2024-01-06 12:15:42 +00:00
renovate[bot]
8302e11436 fix(deps): update dependency @sapphire/utilities to v3.15.2 2024-01-06 12:15:39 +00:00
Anthony Berg
8c48473ef3 refactor(arabot): ran prettier 2024-01-06 02:32:52 +00:00
Anthony Berg
d90c985cec refactor(arabot): insert awaits to schedule creators 2024-01-06 02:32:52 +00:00
Anthony Berg
b6b50ea450 fix(arabot): broken tempban schedule creation 2024-01-06 02:32:52 +00:00
Anthony Berg
d00fddd51a Merge pull request #173 from veganhacktivists/feat/warnings
feat(arabot): add warning commands
2024-01-04 22:12:46 +00:00
Anthony Berg
e5f2c9436e feat(arabot): move embeds to separate file and add warnings to ModMail 2024-01-04 17:57:15 +00:00
Anthony Berg
0bb10b55ed refactor(arabot): change ephemeral for warnings 2024-01-04 12:49:35 +00:00
Anthony Berg
d9c4f54299 feat(arabot): add delete warnings command 2024-01-04 12:47:13 +00:00
Anthony Berg
77bbe97c6a refactor(db): remove active column in Warning table 2024-01-04 12:44:52 +00:00
Anthony Berg
a498cde933 feat(arabot): add type checking to checkStaff command 2024-01-04 12:40:24 +00:00
Anthony Berg
750b10062f fix(arabot): change restrict log channel to sus log channel 2024-01-04 12:22:13 +00:00
Anthony Berg
e348b09f80 refactor(arabot): change comments to reflect the command 2024-01-04 12:14:10 +00:00
Anthony Berg
4dc37ab31c refactor(arabot): change class name 2024-01-04 12:01:34 +00:00
Anthony Berg
396d69db06 feat(arabot): add warnings command 2024-01-04 11:56:15 +00:00
Anthony Berg
94036984d3 feat(arabot): update copyright for warn command 2024-01-04 11:27:30 +00:00
Anthony Berg
0068fb5bdd fix(arabot): description for restrictLogs command to be reflective of the command 2024-01-04 11:26:57 +00:00
Anthony Berg
1f3610a89d refactor(arabot): move warn command to dedicated warning directory 2024-01-04 11:25:08 +00:00
Anthony Berg
02298f2089 feat(arabot): completed warn command 2024-01-04 11:23:32 +00:00
Anthony Berg
bb6ac8aef0 refactor(arabot): updated deprecated commands 2024-01-04 10:38:39 +00:00
Anthony Berg
3f82f87317 ci: update dependencies 2024-01-04 09:21:42 +00:00
Anthony Berg
fc2574e8e1 fix(arabot): new sapphire interaction handler 2024-01-02 23:44:12 +00:00
Anthony Berg
619aeb533b ci: ran npm audit fix 2024-01-02 23:29:25 +00:00
Anthony Berg
255ca62c33 Merge pull request #167 from veganhacktivists/renovate/sapphire-framework-4.x-lockfile
fix(deps): update dependency @sapphire/framework to v4.8.5
2024-01-02 23:28:10 +00:00
Anthony Berg
ef0cf08bf8 Merge pull request #168 from veganhacktivists/renovate/eslint-8.x
chore(deps): update dependency eslint to v8.56.0
2024-01-02 23:28:02 +00:00
Anthony Berg
113ebbbaee Merge pull request #169 from veganhacktivists/renovate/eslint-config-prettier-9.x-lockfile
chore(deps): update dependency eslint-config-prettier to v9.1.0
2024-01-02 23:27:53 +00:00
Anthony Berg
4a03675256 Merge pull request #170 from veganhacktivists/renovate/sapphire-plugin-scheduled-tasks-8.x-lockfile
fix(deps): update dependency @sapphire/plugin-scheduled-tasks to v8.1.0
2024-01-02 23:27:44 +00:00
Anthony Berg
7fa844c24f Merge pull request #171 from veganhacktivists/renovate/node-20.x-lockfile
fix(deps): update dependency @types/node to v20.10.6
2024-01-02 23:27:33 +00:00
renovate[bot]
5a42926c13 fix(deps): update dependency @sapphire/framework to v4.8.5 2024-01-02 23:27:28 +00:00
Anthony Berg
54f1c23c69 Merge pull request #172 from veganhacktivists/renovate/sapphire-stopwatch-1.x-lockfile
fix(deps): update dependency @sapphire/stopwatch to v1.5.1
2024-01-02 23:27:26 +00:00
Anthony Berg
b3e1d11e72 Merge pull request #166 from veganhacktivists/renovate/sapphire-discord.js-utilities-7.x-lockfile
fix(deps): update dependency @sapphire/discord.js-utilities to v7.1.5
2024-01-02 23:27:07 +00:00
Anthony Berg
d42332f290 Merge pull request #165 from veganhacktivists/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v6.17.0
2024-01-02 23:26:57 +00:00
Anthony Berg
534453fcc0 Merge pull request #164 from veganhacktivists/renovate/redis-4.x-lockfile
fix(deps): update dependency redis to v4.6.12
2024-01-02 23:26:48 +00:00
Stefanie Merceron
6fbb547ca7 feat(arabot): add new KIll gifs thanks to Lithium 2024-01-02 23:25:52 +00:00
renovate[bot]
a1beb4d347 chore(deps): update typescript-eslint monorepo to v6.17.0 2024-01-01 19:24:08 +00:00
renovate[bot]
48b30bfcb9 fix(deps): update dependency @types/node to v20.10.6 2023-12-30 04:25:07 +00:00
renovate[bot]
165a34ba0c fix(deps): update dependency @sapphire/discord.js-utilities to v7.1.5 2023-12-28 01:23:04 +00:00
renovate[bot]
c9a987a2a9 fix(deps): update dependency redis to v4.6.12 2023-12-19 01:40:59 +00:00
renovate[bot]
7aebbbc2fd chore(deps): update dependency eslint to v8.56.0 2023-12-16 01:22:14 +00:00
renovate[bot]
779f8de43f fix(deps): update dependency @sapphire/stopwatch to v1.5.1 2023-12-09 12:03:58 +00:00
renovate[bot]
0e77867010 fix(deps): update dependency @sapphire/plugin-scheduled-tasks to v8.1.0 2023-12-02 15:47:37 +00:00
renovate[bot]
c58360d563 chore(deps): update dependency eslint-config-prettier to v9.1.0 2023-12-02 12:52:34 +00:00
Anthony Berg
e936fe49b1 refactor(arabot): remove debug logger message for slowmode 2023-11-18 23:19:54 +00:00
Anthony Berg
b39cf0b44d refactor(arabot): add string type to isNumber function 2023-11-18 23:19:21 +00:00
Anthony Berg
81f5db4b0a refactor(arabot): remove old eslint ignores 2023-11-18 23:09:00 +00:00
Anthony Berg
8bc3b2dd4f ci(build): remove dotenv 2023-11-18 23:07:44 +00:00
Anthony Berg
cba2115080 refactor(config): fix prettier problem with eslintrc 2023-11-18 22:59:35 +00:00
Anthony Berg
f7b1512935 ci(build): removed airbnb eslint config 2023-11-18 22:58:00 +00:00
Anthony Berg
5d93db6365 refactor: run prettier 2023-11-18 22:01:33 +00:00
Anthony Berg
1a8b6eb0c1 ci(build): add prettier github action 2023-11-18 21:57:45 +00:00
Anthony Berg
c2b0753232 ci(build): add prettier 2023-11-18 21:44:26 +00:00
Anthony Berg
07c5d85b15 Merge pull request #162 from veganhacktivists/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v6.11.0
2023-11-18 21:21:38 +00:00
Anthony Berg
dfd4ab7d26 Merge pull request #161 from veganhacktivists/renovate/eslint-8.x
chore(deps): update dependency eslint to v8.54.0
2023-11-18 21:21:25 +00:00
Anthony Berg
81c01aede8 Merge pull request #160 from veganhacktivists/renovate/node-20.x-lockfile
fix(deps): update dependency @types/node to v20.9.1
2023-11-18 21:21:11 +00:00
Anthony Berg
e06bde4540 Merge pull request #159 from veganhacktivists/renovate/sapphire-plugin-logger-3.x-lockfile
fix(deps): update dependency @sapphire/plugin-logger to v3.0.7
2023-11-18 21:21:01 +00:00
renovate[bot]
f8c7267f26 chore(deps): update typescript-eslint monorepo to v6.11.0 2023-11-18 15:38:17 +00:00
renovate[bot]
7aaffda339 chore(deps): update dependency eslint to v8.54.0 2023-11-18 15:38:01 +00:00
renovate[bot]
3752f57af0 fix(deps): update dependency @types/node to v20.9.1 2023-11-18 13:01:38 +00:00
renovate[bot]
c56a9521a5 fix(deps): update dependency @sapphire/plugin-logger to v3.0.7 2023-11-18 13:01:23 +00:00
Anthony Berg
e529cfd98f Merge pull request #144 from MercStef/main
feat(arabot): add fun cringe command
2023-11-13 22:05:50 +00:00
Anthony Berg
dd7c975db7 Merge branch 'main' into main 2023-11-13 22:00:24 +00:00
Anthony
c1a35eeb89 feat(arabot): add counting channel ID 2023-11-11 21:29:17 +01:00
Stefanie Merceron
39d0043937 feat(arabot): add fun cringe command
Adds a new feature that introduces a fun and cringe-worthy command.
2023-11-11 14:45:42 -05:00
renovate[bot]
4b999f0a5c fix(deps): update dependency redis to v4.6.10 2023-11-11 14:24:10 -05:00
renovate[bot]
95d6093ae6 fix(deps): update dependency bullmq to v4.12.4 2023-11-11 14:24:10 -05:00
Anthony
768ac13f8b feat(db): add counting listener for a counting channel 2023-11-11 19:23:31 +01:00
Anthony
d27871e0f7 feat(db): add counting table 2023-11-11 15:49:58 +01:00
Anthony Berg
871696bf89 Merge pull request #152 from veganhacktivists/renovate/eslint-8.x
chore(deps): update dependency eslint to v8.53.0
2023-11-11 15:41:02 +01:00
Anthony Berg
73da43ab0a Merge pull request #151 from veganhacktivists/renovate/bullmq-4.x-lockfile
fix(deps): update dependency bullmq to v4.13.2
2023-11-11 15:40:52 +01:00
renovate[bot]
3bf351e472 chore(deps): update postgres docker tag to v16 2023-11-11 14:40:43 +00:00
Anthony Berg
2b1a06f315 Merge pull request #150 from veganhacktivists/renovate/node-20.x-lockfile
fix(deps): update dependency @types/node to v20.9.0
2023-11-11 15:40:43 +01:00
Anthony Berg
74f910385a Merge pull request #149 from veganhacktivists/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v6.10.0
2023-11-11 15:40:34 +01:00
Anthony Berg
2d2c01180a Merge pull request #153 from veganhacktivists/renovate/actions-checkout-4.x
chore(deps): update actions/checkout action to v4
2023-11-11 15:40:22 +01:00
renovate[bot]
860d203c16 chore(deps): update actions/checkout action to v4 2023-11-11 13:47:11 +00:00
renovate[bot]
dda946ebf3 fix(deps): update dependency bullmq to v4.13.2 2023-11-09 15:37:53 +00:00
renovate[bot]
7027fe6431 fix(deps): update dependency @types/node to v20.9.0 2023-11-07 23:05:10 +00:00
renovate[bot]
bc88d5c41c chore(deps): update typescript-eslint monorepo to v6.10.0 2023-11-06 18:59:19 +00:00
renovate[bot]
eb1cf81d64 chore(deps): update dependency eslint to v8.53.0 2023-11-04 15:07:28 +00:00
Anthony Berg
584126cacb ci: update dependencies 2023-10-29 14:56:03 +00:00
Anthony Berg
6d9e25164e Merge pull request #148 from veganhacktivists/renovate/typescript-5.x-lockfile
fix(deps): update dependency typescript to v5.2.2
2023-10-29 14:50:11 +00:00
Anthony Berg
c7e66ea9da Merge pull request #147 from veganhacktivists/renovate/sapphire-plugin-subcommands-4.x-lockfile
fix(deps): update dependency @sapphire/plugin-subcommands to v4.3.0
2023-10-29 14:50:00 +00:00
Anthony Berg
6a99e00a36 Merge pull request #146 from veganhacktivists/renovate/sapphire-framework-4.x-lockfile
fix(deps): update dependency @sapphire/framework to v4.7.2
2023-10-29 14:49:28 +00:00
Anthony Berg
a6f05a3235 Merge pull request #145 from veganhacktivists/renovate/eslint-plugin-import-2.x-lockfile
chore(deps): update dependency eslint-plugin-import to v2.29.0
2023-10-29 14:48:49 +00:00
Anthony Berg
598d7559df Merge pull request #143 from veganhacktivists/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v6.9.0
2023-10-29 14:48:39 +00:00
Anthony Berg
6f64baba7b Merge pull request #142 from veganhacktivists/renovate/eslint-8.x
chore(deps): update dependency eslint to v8.52.0
2023-10-29 14:48:31 +00:00
Anthony Berg
88321cc8fb Merge pull request #141 from veganhacktivists/renovate/bullmq-4.x-lockfile
fix(deps): update dependency bullmq to v4.12.7
2023-10-29 14:48:23 +00:00
Anthony Berg
9d84045226 Merge pull request #140 from veganhacktivists/renovate/node-20.x-lockfile
fix(deps): update dependency @types/node to v20.8.9
2023-10-29 14:48:15 +00:00
renovate[bot]
96dad137ab fix(deps): update dependency bullmq to v4.12.7 2023-10-29 07:32:14 +00:00
renovate[bot]
004e49829d fix(deps): update dependency typescript to v5.2.2 2023-10-28 15:17:16 +00:00
renovate[bot]
1c2fc11e47 fix(deps): update dependency @sapphire/plugin-subcommands to v4.3.0 2023-10-28 15:17:03 +00:00
renovate[bot]
c2b4b014dc fix(deps): update dependency @sapphire/framework to v4.7.2 2023-10-28 12:04:49 +00:00
renovate[bot]
37527d46fc chore(deps): update dependency eslint-plugin-import to v2.29.0 2023-10-28 12:04:32 +00:00
renovate[bot]
a2deb04cf9 fix(deps): update dependency @types/node to v20.8.9 2023-10-25 20:04:06 +00:00
renovate[bot]
0475929e84 chore(deps): update typescript-eslint monorepo to v6.9.0 2023-10-23 19:13:23 +00:00
renovate[bot]
f0ec5712f9 chore(deps): update dependency eslint to v8.52.0 2023-10-21 15:46:36 +00:00
Anthony Berg
f5c9392b5e Merge pull request #139 from veganhacktivists/renovate/redis-4.x-lockfile
fix(deps): update dependency redis to v4.6.10
2023-10-14 16:03:21 +01:00
Anthony Berg
89defb9390 Merge pull request #138 from veganhacktivists/renovate/bullmq-4.x-lockfile
fix(deps): update dependency bullmq to v4.12.4
2023-10-14 16:03:12 +01:00
renovate[bot]
e42d2b5537 fix(deps): update dependency redis to v4.6.10 2023-10-14 15:02:55 +00:00
renovate[bot]
c2bace00b3 fix(deps): update dependency bullmq to v4.12.4 2023-10-14 15:02:41 +00:00
Anthony Berg
7250b4cf2b Merge pull request #137 from veganhacktivists/renovate/node-20.x-lockfile
fix(deps): update dependency @types/node to v20.8.6
2023-10-14 16:02:24 +01:00
Anthony Berg
9d7f7fa542 Merge pull request #136 from veganhacktivists/renovate/sapphire-plugin-scheduled-tasks-7.x-lockfile
fix(deps): update dependency @sapphire/plugin-scheduled-tasks to v7.1.2
2023-10-14 16:02:14 +01:00
renovate[bot]
0f8af4bee8 fix(deps): update dependency @types/node to v20.8.6 2023-10-14 13:40:41 +00:00
renovate[bot]
2aaf0fddf0 fix(deps): update dependency @sapphire/plugin-scheduled-tasks to v7.1.2 2023-10-14 13:40:27 +00:00
Anthony Berg
8c825d5132 docs(arabot): add info on ARA Vegan role command 2023-10-14 02:06:42 +01:00
Anthony Berg
9434185659 feat(arabot): add command to manage ARA Vegan role 2023-10-14 02:05:20 +01:00
Anthony Berg
ece946b9a8 fix(arabot): add space to a message 2023-10-12 18:08:02 +01:00
Anthony Berg
19226bab08 fix(arabot): bug that breaks the economy 2023-10-12 18:04:16 +01:00
Anthony Berg
a2a351e2c3 feat(arabot): allow moderators to use /moveall 2023-10-12 00:49:41 +01:00
Anthony Berg
22efb5c40d Merge pull request #131 from veganhacktivists/renovate/discord.js-14.x-lockfile
fix(deps): update dependency discord.js to v14.13.0
2023-10-12 00:45:29 +01:00
renovate[bot]
9bf61807b4 fix(deps): update dependency discord.js to v14.13.0 2023-10-11 23:44:47 +00:00
Anthony Berg
312eeba01f Merge pull request #133 from veganhacktivists/renovate/eslint-import-resolver-typescript-3.x-lockfile
chore(deps): update dependency eslint-import-resolver-typescript to v3.6.1
2023-10-12 00:43:53 +01:00
renovate[bot]
49237c1f92 chore(deps): update dependency eslint-import-resolver-typescript to v3.6.1 2023-10-11 23:43:40 +00:00
Anthony Berg
868121767c Merge pull request #135 from veganhacktivists/renovate/sapphire-plugin-logger-3.x-lockfile
fix(deps): update dependency @sapphire/plugin-logger to v3.0.6
2023-10-12 00:43:39 +01:00
Anthony Berg
4ed3a08dac Merge pull request #134 from veganhacktivists/renovate/sapphire-framework-4.x-lockfile
fix(deps): update dependency @sapphire/framework to v4.6.1
2023-10-12 00:43:30 +01:00
Anthony Berg
b64ba697c3 Merge pull request #132 from veganhacktivists/renovate/prisma-monorepo
fix(deps): update prisma monorepo to v5.4.2
2023-10-12 00:43:15 +01:00
Anthony Berg
184853c363 Merge pull request #130 from veganhacktivists/renovate/eslint-plugin-import-2.x-lockfile
chore(deps): update dependency eslint-plugin-import to v2.28.1
2023-10-12 00:42:58 +01:00
Anthony Berg
48f678dbbd Merge pull request #129 from veganhacktivists/renovate/bullmq-4.x-lockfile
fix(deps): update dependency bullmq to v4.12.3
2023-10-12 00:42:46 +01:00
renovate[bot]
6fab3822bb fix(deps): update dependency bullmq to v4.12.3 2023-10-11 23:42:33 +00:00
Anthony Berg
15f34d68ab Merge pull request #128 from veganhacktivists/renovate/node-20.x-lockfile
fix(deps): update dependency @types/node to v20.8.4
2023-10-12 00:42:22 +01:00
Anthony Berg
c0f3b657bc Merge pull request #117 from veganhacktivists/renovate/eslint-8.x
chore(deps): update dependency eslint to v8.51.0
2023-10-12 00:42:12 +01:00
Anthony Berg
190f927df3 Merge pull request #115 from veganhacktivists/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v6.7.5
2023-10-12 00:41:55 +01:00
renovate[bot]
fe9f04dd68 fix(deps): update prisma monorepo to v5.4.2 2023-10-09 19:10:09 +00:00
renovate[bot]
f19545b988 fix(deps): update dependency @types/node to v20.8.4 2023-10-09 19:09:52 +00:00
renovate[bot]
fd17c38b08 chore(deps): update typescript-eslint monorepo to v6.7.5 2023-10-09 19:09:36 +00:00
renovate[bot]
3b25622730 chore(deps): update dependency eslint to v8.51.0 2023-10-06 22:24:48 +00:00
renovate[bot]
88f94bc3fc fix(deps): update dependency @sapphire/framework to v4.6.1 2023-10-05 14:21:11 +00:00
renovate[bot]
544f14b175 fix(deps): update dependency @sapphire/plugin-logger to v3.0.6 2023-08-26 12:43:00 +00:00
renovate[bot]
d15e46b770 chore(deps): update dependency eslint-plugin-import to v2.28.1 2023-08-18 23:16:27 +00:00
Anthony Berg
09135a0f61 docs(database): add JSDoc for basic user functions 2023-07-27 12:18:57 +01:00
Anthony Berg
29df8bf2c4 docs(database): add JSDoc for ban functions 2023-07-27 12:07:15 +01:00
Anthony Berg
d42cc6be0b fix(arabot): remove ability for non coordinator to give vegans veg curious 2023-07-27 11:55:24 +01:00
Anthony Berg
7ec4fc2909 build(upgrade): update npm packages 2023-07-23 01:33:10 +01:00
Anthony Berg
97e3ca2bc3 fix: add bullmq env variable 2023-05-27 07:26:17 +01:00
Anthony Berg
1dbfff38bc feat: add restriction for verification reminder if less than 100 messages since last reminder 2023-05-27 07:19:47 +01:00
Anthony Berg
a2404ae404 feat: add counter for messages sent in general 2023-05-27 07:17:54 +01:00
Anthony Berg
592c1c05cc feat: add redis client 2023-05-27 07:17:35 +01:00
Anthony Berg
49a5430cc0 refactor: move redis host to environment variable 2023-05-27 06:18:48 +01:00
Anthony Berg
fb5d963af8 build: update package.json 2023-05-27 06:18:26 +01:00
Anthony Berg
c9a39ecfc2 build(upgrade): update nodejs version to 20 2023-05-13 14:43:10 +01:00
Anthony Berg
1cd27444d8 feat(arabot): update @sapphire/plugin-scheduled-tasks 2023-05-13 14:42:31 +01:00
Anthony Berg
9a469c269c fix(arabot): fix imports of types 2023-05-13 14:41:37 +01:00
Anthony Berg
d493947579 feat(config): change commonjs modules to node16 2023-05-13 14:41:05 +01:00
Anthony Berg
9019116973 build(upgrade): update all packages 2023-05-13 14:40:14 +01:00
Anthony Berg
63e6d64ba2 Merge pull request #114 from veganhacktivists/renovate/bullmq-3.x-lockfile
fix(deps): update dependency bullmq to v3.13.0
2023-05-07 14:32:29 +01:00
Anthony Berg
e08127e4c2 Merge pull request #113 from veganhacktivists/renovate/sapphire-discord.js-utilities-6.x-lockfile
fix(deps): update dependency @sapphire/discord.js-utilities to v6.1.0
2023-05-07 14:31:56 +01:00
Anthony Berg
c4f33edcf5 Merge pull request #112 from veganhacktivists/renovate/eslint-8.x
chore(deps): update dependency eslint to v8.40.0
2023-05-07 14:31:20 +01:00
Anthony Berg
da76ba0477 Merge pull request #111 from veganhacktivists/renovate/node-18.x-lockfile
fix(deps): update dependency @types/node to v18.16.5
2023-05-07 14:30:39 +01:00
Anthony Berg
d6f590d245 Merge pull request #110 from veganhacktivists/renovate/sapphire-plugin-logger-3.x-lockfile
fix(deps): update dependency @sapphire/plugin-logger to v3.0.4
2023-05-07 14:29:43 +01:00
Anthony Berg
652d57614f Merge pull request #109 from veganhacktivists/renovate/sapphire-ts-config-4.x
fix(deps): update dependency @sapphire/ts-config to v4
2023-05-07 14:28:07 +01:00
Anthony Berg
9713487496 Merge pull request #107 from veganhacktivists/renovate/sapphire-framework-4.x-lockfile
fix(deps): update dependency @sapphire/framework to v4.4.3
2023-05-07 14:27:19 +01:00
Anthony Berg
8940498bb2 Merge pull request #106 from veganhacktivists/renovate/typescript-5.x-lockfile
fix(deps): update dependency typescript to v5.0.4
2023-05-07 14:26:57 +01:00
Anthony Berg
bc5a5e979e Merge pull request #105 from veganhacktivists/renovate/sapphire-plugin-subcommands-4.x-lockfile
fix(deps): update dependency @sapphire/plugin-subcommands to v4.0.1
2023-05-07 14:26:20 +01:00
Anthony
541e4c49e6 Merge remote-tracking branch 'origin/main' 2023-05-07 14:25:47 +01:00
Anthony
d6c88744a9 ci: change renovate to only run on saturday 2023-05-07 14:25:29 +01:00
renovate[bot]
186ce8dfef fix(deps): update dependency bullmq to v3.13.0 2023-05-06 11:59:07 +00:00
renovate[bot]
ac61cd8b15 fix(deps): update dependency @types/node to v18.16.5 2023-05-06 00:48:44 +00:00
renovate[bot]
19d7bd8db7 fix(deps): update dependency @sapphire/discord.js-utilities to v6.1.0 2023-05-05 21:28:35 +00:00
renovate[bot]
258af69cd1 chore(deps): update dependency eslint to v8.40.0 2023-05-05 21:28:18 +00:00
renovate[bot]
234b12cbb6 fix(deps): update dependency @sapphire/plugin-logger to v3.0.4 2023-05-03 21:07:56 +00:00
renovate[bot]
0c2f8ef655 fix(deps): update dependency @sapphire/ts-config to v4 2023-05-03 10:01:36 +00:00
renovate[bot]
0680897431 fix(deps): update dependency @sapphire/framework to v4.4.3 2023-05-03 05:24:51 +00:00
renovate[bot]
ac798c1c70 fix(deps): update dependency typescript to v5.0.4 2023-05-03 03:51:57 +00:00
renovate[bot]
8558a8b2a4 fix(deps): update dependency @sapphire/plugin-subcommands to v4.0.1 2023-05-03 03:51:37 +00:00
Anthony Berg
fdf13e91d2 Merge pull request #104 from veganhacktivists/renovate/sapphire-plugin-scheduled-tasks-6.x-lockfile
fix(deps): update dependency @sapphire/plugin-scheduled-tasks to v6.0.1
2023-05-03 01:26:41 +01:00
Anthony Berg
dc779bcbad Merge pull request #103 from veganhacktivists/renovate/sapphire-discord.js-utilities-6.x-lockfile
fix(deps): update dependency @sapphire/discord.js-utilities to v6.0.7
2023-05-03 01:26:33 +01:00
Anthony Berg
bdb0b9e1d1 Merge pull request #94 from veganhacktivists/renovate/typescript-5.x
fix(deps): update dependency typescript to v5
2023-05-03 01:26:24 +01:00
Anthony Berg
68ba798150 Merge pull request #95 from veganhacktivists/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v5.59.2
2023-05-03 01:26:08 +01:00
Anthony Berg
c5ad86409b Merge pull request #96 from veganhacktivists/renovate/node-18.x-lockfile
fix(deps): update dependency @types/node to v18.16.3
2023-05-03 01:25:59 +01:00
Anthony Berg
21080ac92c Merge pull request #97 from veganhacktivists/renovate/bullmq-3.x-lockfile
fix(deps): update dependency bullmq to v3.12.0
2023-05-03 01:25:51 +01:00
renovate[bot]
2829ebfc3b fix(deps): update dependency @sapphire/plugin-scheduled-tasks to v6.0.1 2023-05-03 00:25:46 +00:00
Anthony Berg
86ba0d537d Merge pull request #98 from veganhacktivists/renovate/prisma-monorepo
fix(deps): update prisma monorepo to v4.13.0
2023-05-03 01:25:43 +01:00
Anthony Berg
9e96e73e09 Merge pull request #99 from veganhacktivists/renovate/eslint-8.x
chore(deps): update dependency eslint to v8.39.0
2023-05-03 01:25:34 +01:00
Anthony Berg
6efa57734a Merge pull request #100 from veganhacktivists/renovate/eslint-import-resolver-typescript-3.x-lockfile
chore(deps): update dependency eslint-import-resolver-typescript to v3.5.5
2023-05-03 01:25:25 +01:00
Anthony Berg
53438a5c1f Merge pull request #101 from veganhacktivists/renovate/discord.js-14.x-lockfile
fix(deps): update dependency discord.js to v14.10.2
2023-05-03 01:25:17 +01:00
Anthony Berg
91221dccda Merge pull request #102 from veganhacktivists/renovate/sapphire-plugin-logger-3.x-lockfile
fix(deps): update dependency @sapphire/plugin-logger to v3.0.3
2023-05-03 01:25:09 +01:00
renovate[bot]
1752dc37b4 fix(deps): update dependency @sapphire/discord.js-utilities to v6.0.7 2023-05-03 00:24:53 +00:00
Anthony Berg
7348404582 Merge pull request #93 from veganhacktivists/renovate/sapphire-time-utilities-1.x-lockfile
fix(deps): update dependency @sapphire/time-utilities to v1.7.9
2023-05-03 01:24:21 +01:00
renovate[bot]
760cbc6ecd fix(deps): update dependency discord.js to v14.10.2 2023-05-02 00:58:06 +00:00
renovate[bot]
3dc82f7d47 chore(deps): update typescript-eslint monorepo to v5.59.2 2023-05-01 20:52:14 +00:00
Anthony
0b68a7c524 fix(arabot): fix no reply for voice channel updates 2023-04-30 23:06:49 +01:00
Anthony
90207acaae refactor: add check run in guild and comments 2023-04-30 23:05:13 +01:00
renovate[bot]
098e6de85f fix(deps): update dependency @types/node to v18.16.3 2023-04-29 08:38:42 +00:00
renovate[bot]
4830c0498a chore(deps): update dependency eslint to v8.39.0 2023-04-21 21:49:22 +00:00
renovate[bot]
e0a8e7161b fix(deps): update dependency bullmq to v3.12.0 2023-04-20 07:16:35 +00:00
renovate[bot]
3fda2d7aec fix(deps): update prisma monorepo to v4.13.0 2023-04-18 17:06:03 +00:00
Anthony
137e3d4738 fix(arabot): add proper check to see if user is currently restricted when opening modmail 2023-04-11 22:26:55 +01:00
Anthony
4e75cffe5d fix(arabot): remove modmail listener only running once and add a second delay to send embeds 2023-04-11 22:15:22 +01:00
Anthony
65ee043ea4 feat(arabot): add automatic restrict and sus logs for restricted users in modmail 2023-04-11 21:52:37 +01:00
renovate[bot]
cbffb884c3 fix(deps): update dependency @sapphire/plugin-logger to v3.0.3 2023-04-11 00:12:38 +00:00
renovate[bot]
1c46c84ad6 chore(deps): update dependency eslint-import-resolver-typescript to v3.5.5 2023-04-05 20:46:23 +00:00
smyalygames
797d9dbff1 revert: it's no longer april
This reverts commit afb5e8f013.

Revert "fix(arabot): fix april with bad number comparison"

This reverts commit 2cc0e5572b.

Revert "fix(arabot): fix april with bad number comparison"

This reverts commit 126c7445db.
2023-04-02 11:36:59 +01:00
smyalygames
126c7445db fix(arabot): fix april with bad number comparison 2023-04-02 00:39:58 +01:00
smyalygames
2cc0e5572b fix(arabot): fix april with bad number comparison 2023-04-01 23:45:47 +01:00
smyalygames
afb5e8f013 feat(arabot): it's april 2023-04-01 23:41:04 +01:00
smyalygames
fbae131e4e ci(arabot): change restrict and ban to be ephemeral 2023-04-01 23:30:43 +01:00
smyalygames
92b6c2da38 ci(arabot): change plus commands to allow mods to run it 2023-04-01 23:27:04 +01:00
smyalygames
26fe5a79eb feat(arabot): change help message to an embed 2023-03-23 23:20:35 +00:00
smyalygames
634752a472 feat(docs): add help command 2023-03-23 23:11:43 +00:00
smyalygames
4f7dc865b0 feat(arabot): add help command 2023-03-23 23:09:24 +00:00
smyalygames
7b74595bbe fix(docs): add optional parameter for info 2023-03-23 22:58:43 +00:00
smyalygames
53b474c36a feat(docs): add info command to docs 2023-03-23 22:57:28 +00:00
smyalygames
c2469433ed feat(docs): add documentation and split contents of commands to different files 2023-03-23 22:54:13 +00:00
smyalygames
9dea07ccd3 fix(arabot): change punctuation in veg curious info command 2023-03-18 02:20:05 +00:00
smyalygames
ade11e5abd feat(arabot): add TODO in rolesJoinServer.ts 2023-03-18 02:19:16 +00:00
smyalygames
dad062d69a feat(arabot): add ModMail to info command 2023-03-18 02:15:43 +00:00
smyalygames
78418308dd feat(arabot): change wording for veg curious info command 2023-03-18 02:15:29 +00:00
smyalygames
44215af3c1 fix(arabot): fix handling nv access for vegan role command 2023-03-18 01:33:46 +00:00
smyalygames
b32c7954bd fix(arabot): fix embeds being linked to each other 2023-03-17 01:02:59 +00:00
smyalygames
857319e136 feat(arabot): add log to log channel for payments 2023-03-17 00:54:19 +00:00
smyalygames
d3594be3ef feat(arabot): add command to remove verify timeout 2023-03-17 00:31:10 +00:00
renovate[bot]
d43e05b82a fix(deps): update dependency typescript to v5 2023-03-16 17:27:22 +00:00
renovate[bot]
707134a12e fix(deps): update dependency @sapphire/time-utilities to v1.7.9 2023-03-15 23:57:47 +00:00
smyalygames
0e0b8b2d02 refactor(arabot): remove unnecessary checks for finding users 2023-03-15 00:48:27 +00:00
smyalygames
c6628140ac fix(arabot): fix certain gifs links not directly being links 2023-03-15 00:24:28 +00:00
smyalygames
d7fd0753a5 fix(arabot): fix message count information 2023-03-15 00:18:21 +00:00
smyalygames
528f592733 feat(arabot): add more information on ranks 2023-03-15 00:12:49 +00:00
smyalygames
f1453d5691 feat(arabot): add ability to check other user for ranks 2023-03-15 00:12:27 +00:00
smyalygames
f304b9ecac ci(build): update dependencies (with my contributions to sapphire :D) 2023-03-14 12:21:24 +00:00
smyalygames
f5be80807e ci(build): update dependencies 2023-03-12 22:18:47 +00:00
Anthony Berg
649d7ebde7 Merge pull request #90 from veganhacktivists/renovate/eslint-8.x
chore(deps): update dependency eslint to v8.36.0
2023-03-12 21:26:40 +00:00
renovate[bot]
e7084017b7 chore(deps): update dependency eslint to v8.36.0 2023-03-11 01:53:03 +00:00
smyalygames
f49857125b feat(arabot): add restricted access role to blocked roles 2023-03-11 01:47:13 +00:00
smyalygames
f76cc38df0 refactor(arabot): change naming for xp to next level 2023-03-10 22:57:35 +00:00
smyalygames
304f5996fb fix(arabot): add xp to next level to fix levels being broken 2023-03-10 21:50:43 +00:00
smyalygames
e48db66f0c fix(arabot): add attachments URL if there is message content 2023-03-09 19:26:33 +00:00
smyalygames
d6a796eebd feat(arabot): add attachments to suggestions 2023-03-09 19:19:45 +00:00
smyalygames
6f276d5083 fix(arabot): increase character limit for suggestions 2023-03-08 18:52:37 +00:00
smyalygames
da0e231760 fix(arabot): fix sending pings with fun commands 2023-03-08 16:57:00 +00:00
smyalygames
dcacffe908 refactor(arabot): add blockedRolesAfterRestricted to restricted command 2023-03-08 00:26:51 +00:00
smyalygames
390af56fa5 feat(arabot): add better checking for giving roles to restricted users 2023-03-08 00:25:01 +00:00
smyalygames
21a53a1a75 feat(arabot): add logging for users using these commands to other users 2023-03-07 15:10:18 +00:00
smyalygames
60c4b8f0f3 feat(database): add fun logging to database 2023-03-07 15:04:26 +00:00
smyalygames
c8521d0c06 fix(arabot): fix check for updating roles on user on leave 2023-03-06 23:59:39 +00:00
smyalygames
3c3f74e0ee feat(arabot): add check for verify block, and private and restricted channels 2023-03-06 23:49:18 +00:00
smyalygames
e7432ae777 feat(arabot): add info command 2023-03-06 22:46:58 +00:00
smyalygames
82e7f0f699 ci(build): update dependencies 2023-03-06 19:22:17 +00:00
smyalygames
0736bb8e38 feat(arabot): add catch for finding guild members 2023-03-06 19:21:28 +00:00
smyalygames
267d44d535 fix(arabot): add success to removing roles 2023-03-06 11:42:16 +00:00
smyalygames
a42819ea86 feat(arabot): add ping message command 2023-03-05 16:09:26 +00:00
smyalygames
41255f66c2 feat(arabot): add trial mod role command 2023-03-05 16:01:01 +00:00
smyalygames
0a1ce0fbc8 feat(arabot): add rank command 2023-03-05 00:55:03 +00:00
smyalygames
80f38b2ca5 feat(arabot): add xp system 2023-03-04 23:48:45 +00:00
smyalygames
15f2f710a9 feat(arabot): remove unnecessary success react to daily 2023-03-04 01:33:35 +00:00
smyalygames
12b8f7016e feat(arabot): add bonus to daily rewards 2023-03-04 01:28:34 +00:00
smyalygames
45c07dbb11 ci(build): update bot version number 2023-03-04 00:40:48 +00:00
smyalygames
3fe499174e perf(database): only get id from userExists 2023-03-04 00:25:42 +00:00
smyalygames
c7af73cec2 perf(arabot): remove unnecessary userExists checks 2023-03-04 00:17:03 +00:00
smyalygames
1870f75517 refactor(arabot): change variable names for users and remove some checks 2023-03-04 00:03:27 +00:00
smyalygames
ed693f9597 perf(database): rely less on count functions 2023-03-03 23:27:54 +00:00
smyalygames
ddb826c84e feat(arabot): add logging for giving/removal of roles 2023-03-03 18:37:53 +00:00
smyalygames
22a19da926 feat(arabot): add logging for giving/removal of roles 2023-03-03 18:37:06 +00:00
smyalygames
232cbc4b67 feat(db): add role log table to database 2023-03-03 18:28:08 +00:00
smyalygames
873afcd4d6 refactor(arabot): change role command structures 2023-03-03 17:45:42 +00:00
smyalygames
6bfe57c135 feat(arabot): create payment command 2023-03-02 16:48:57 +00:00
smyalygames
d98f714477 feat(arabot): add balance command 2023-03-02 15:36:56 +00:00
smyalygames
c7fe49b4e3 feat(arabot): add daily command 2023-03-02 15:36:33 +00:00
smyalygames
de94b88be1 feat(database): add economy 2023-03-02 14:42:00 +00:00
Anthony Berg
b404a26bdc Merge pull request #88 from veganhacktivists/renovate/postgres-15.x
chore(deps): update postgres docker tag to v15
2023-03-02 10:57:02 +00:00
smyalygames
80d90256d5 refactor(arabot): change StatRole to one to one relationship in Prisma 2023-03-02 00:30:48 +00:00
smyalygames
086d1d50c6 feat(arabot): add disconnect from vc for restrictions 2023-03-01 23:45:16 +00:00
smyalygames
bede3cadbe feat(arabot): add dm for restrict reasons 2023-03-01 23:41:20 +00:00
smyalygames
50c823380c feat(arabot): remove open ports 2023-03-01 20:04:48 +00:00
smyalygames
6700ad8674 fix(arabot): fix if statement for userExists 2023-03-01 19:45:20 +00:00
smyalygames
ad594aa63f feat(arabot): fix @everyone in roles for role viewer 2023-03-01 16:20:28 +00:00
smyalygames
97d6d14d8a ci(build): update bot version number 2023-03-01 15:52:30 +00:00
smyalygames
e39812649a feat(build): add leave logs and giving roles on join from that 2023-03-01 15:50:00 +00:00
smyalygames
005c190582 feat(build): add leave logs 2023-03-01 15:28:01 +00:00
smyalygames
158c76c17e Merge remote-tracking branch 'origin/main' 2023-03-01 14:41:30 +00:00
smyalygames
74ae558b0f ci(build): update prisma 2023-03-01 14:41:14 +00:00
Anthony Berg
62647c769c Merge pull request #87 from veganhacktivists/renovate/node-19.x
chore(deps): update node.js to v19
2023-03-01 14:28:20 +00:00
smyalygames
c77a6141a5 fix(arabot): revert merge problem for tempBan 2023-03-01 14:27:50 +00:00
smyalygames
e4791f3d22 Merge remote-tracking branch 'origin/main' 2023-03-01 14:20:23 +00:00
smyalygames
aa28f7a4cf refactor(arabot): comment out subcommands to add at a later date 2023-03-01 14:19:04 +00:00
smyalygames
6a96772c25 feat(arabot): add check so there can only be one leader of a group 2023-03-01 14:16:00 +00:00
smyalygames
cd3c209ede feat(arabot): add check for outreach coordinator only for event create 2023-03-01 14:13:26 +00:00
smyalygames
1ee75d684f feat(arabot): add embed for the end of outreach 2023-03-01 14:08:51 +00:00
smyalygames
ab56b5192a feat(arabot): add ending outreach event 2023-03-01 13:49:48 +00:00
smyalygames
dec6de8398 feat(arabot): add updating statistics for outreach groups 2023-03-01 13:03:17 +00:00
renovate[bot]
4e815c2d31 chore(deps): update postgres docker tag to v15 2023-03-01 12:29:05 +00:00
renovate[bot]
7aa9ca8b44 chore(deps): update node.js to v19 2023-03-01 12:28:54 +00:00
smyalygames
c55d5ffe93 fix(arabot): change reply to editReply in deferred replies 2023-03-01 11:09:17 +00:00
smyalygames
26ea2ba996 fix(arabot): add check for deleted channel when replying 2023-03-01 11:06:26 +00:00
smyalygames
76f8aa369c fix(arabot): add failsafe for clear command reply on completion 2023-03-01 10:51:08 +00:00
smyalygames
8070430976 feat(arabot): add command to add users to outreach group 2023-03-01 04:00:08 +00:00
smyalygames
da8088e157 feat(arabot): add stat role to separate table 2023-02-28 20:11:58 +00:00
renovate[bot]
3baa0e2081 chore(deps): add renovate.json 2023-02-28 19:18:55 +00:00
smyalygames
e07871cb30 feat(arabot): remove update db roles on ban 2023-02-28 19:18:55 +00:00
smyalygames
843b8cdfb9 ci(build): update packages 2023-02-28 19:18:55 +00:00
smyalygames
e3e72ce483 feat(arabot): add deferred replies 2023-02-28 19:18:55 +00:00
smyalygames
5b5994eb47 feat(build): add env variable for node production 2023-02-28 19:18:42 +00:00
smyalygames
68cff288e0 fix(build): add env variable for node production 2023-02-28 19:17:59 +00:00
smyalygames
2909d5cc8d fix(arabot): change avatarURL to displayAvatarURL 2023-02-28 19:17:59 +00:00
smyalygames
00cd92ac82 feat(build): add networking and restart always for bot 2023-02-28 19:17:59 +00:00
smyalygames
e1fd0eed83 fix(arabot): make adding roles more efficient and stable 2023-02-28 19:17:59 +00:00
smyalygames
b6bd3c6e80 feat(arabot): add message command for temp ban 2023-02-28 19:17:59 +00:00
smyalygames
7421052f44 feat(arabot): add command to remove vegan restrict channels 2023-02-28 19:17:59 +00:00
smyalygames
87eca4f572 feat(arabot): add welcome restrict channel to IDs 2023-02-28 19:17:59 +00:00
smyalygames
1067572441 fix(arabot): add end time to legacy unrestricts 2023-02-28 19:17:59 +00:00
smyalygames
0ebf9ade2f Merge remote-tracking branch 'origin/main' 2023-02-28 19:10:09 +00:00
smyalygames
14ba1b27a3 feat(arabot): remove update db roles on ban 2023-02-28 19:09:58 +00:00
Anthony Berg
a812712706 Merge pull request #47 from veganhacktivists/renovate/configure
Configure Renovate
2023-02-28 12:07:31 +00:00
smyalygames
d872e964b5 ci(build): update packages 2023-02-28 05:44:53 +00:00
smyalygames
7f62103c9b feat(arabot): add deferred replies 2023-02-28 02:44:32 +00:00
smyalygames
20ae7ce153 feat(build): add env variable for node production 2023-02-27 22:10:17 +00:00
smyalygames
d5a3562f4f fix(build): add env variable for node production 2023-02-27 22:03:58 +00:00
smyalygames
7196637b8a fix(arabot): change avatarURL to displayAvatarURL 2023-02-27 21:43:48 +00:00
smyalygames
83724288d3 feat(build): add networking and restart always for bot 2023-02-27 21:42:42 +00:00
smyalygames
7a1cf06715 feat(arabot): add basic group creation 2023-02-27 18:03:38 +00:00
smyalygames
b859e4b617 feat(db): add outreach to database 2023-02-27 16:43:08 +00:00
smyalygames
9fa412b15e Merge remote-tracking branch 'origin/outreach' into outreach 2023-02-27 16:11:45 +00:00
smyalygames
37c25e925e ci(build): update packages 2023-02-27 16:11:23 +00:00
smyalygames
3e40b81d11 feat(build): add networking 2023-02-27 16:09:36 +00:00
smyalygames
a5792ce8a2 fix(arabot): make adding roles more efficient and stable 2023-02-26 01:57:32 +00:00
smyalygames
ed85099d37 feat(arabot): remove EventStat link table 2023-02-26 01:53:58 +00:00
smyalygames
5fb9ae39a0 feat(arabot): add message command for temp ban 2023-02-23 02:03:50 +00:00
smyalygames
d681d7c183 feat(arabot): add command to remove vegan restrict channels 2023-02-23 01:08:04 +00:00
smyalygames
4d0ffe5dcd feat(arabot): add welcome restrict channel to IDs 2023-02-23 01:07:47 +00:00
smyalygames
039b84c6b5 fix(arabot): add end time to legacy unrestricts 2023-02-21 23:07:17 +00:00
Anthony
db8871d205 feat(arabot): registered group update command 2023-02-21 14:21:18 +00:00
smyalygames
8e7cc9037d feat(arabot): register group command 2023-02-21 13:20:48 +00:00
smyalygames
e465c201b5 Merge remote-tracking branch 'origin/outreach' into outreach
# Conflicts:
#	prisma/schema.prisma
2023-02-20 18:04:01 +00:00
smyalygames
b87c28061a feat(arabot): add create event to outreach command 2023-02-20 18:03:19 +00:00
smyalygames
e0c609cf69 feat(arabot): add database commands 2023-02-20 18:02:42 +00:00
smyalygames
f8e99ef4f2 feat(db): finished database schema for outreach 2023-02-20 18:02:22 +00:00
smyalygames
4770bf0307 feat(db): finished database schema for outreach 2023-02-20 17:11:39 +00:00
Anthony Berg
07b5a6fcd8 Merge pull request #85 from veganhacktivists/main
update outreach after verification command
2023-02-20 16:13:46 +00:00
smyalygames
cfc29fc384 build(arabot): increment arabot version 2023-02-20 13:13:10 +00:00
smyalygames
edf0d0817a refactor(arabot): move role commands to do with verification into directory 2023-02-20 13:11:53 +00:00
smyalygames
80f2107d32 feat(arabot): add manual verification command 2023-02-20 13:10:55 +00:00
smyalygames
9b402a435b feat(arabot): add manual verification function to database 2023-02-20 13:09:37 +00:00
smyalygames
905d9f4811 feat(db): add manual verification 2023-02-20 13:09:24 +00:00
smyalygames
4c5e0238b8 feat(arabot): add nvAccess to fetchRoles 2023-02-20 13:09:11 +00:00
smyalygames
ed3a2719f2 fix(arabot): fix nvAccess text role for menu 2023-02-20 13:06:11 +00:00
smyalygames
ab1cea2729 refactor(arabot): change parameter to follow new style 2023-02-20 12:31:23 +00:00
smyalygames
1533fdeeb5 refactor(arabot): change types to Snowflake 2023-02-20 12:30:37 +00:00
smyalygames
23b4250e0a refactor(arabot): change function name to make giving roles clearer 2023-02-20 12:27:12 +00:00
smyalygames
141d07c01c feat(arabot): add ara vegan role to verification 2023-02-20 12:23:28 +00:00
smyalygames
4f37da9a71 refactor(arabot): move some verify functions to verification utils 2023-02-20 12:17:10 +00:00
Anthony
0fb0f96498 feat(arabot): started verification command 2023-02-20 10:31:31 +00:00
Anthony
f76cfcec0d feat(arabot): add ara vegan to roles 2023-02-20 10:30:59 +00:00
Anthony
8cd8128723 build(arabot): update version number for arabot 2023-02-20 09:45:12 +00:00
Anthony Berg
969616301d Merge pull request #84 from veganhacktivists/main
Update outreach
2023-02-20 09:42:06 +00:00
smyalygames
ced9851574 fix(arabot): incremented i in for loop for restriction number to start at 1 2023-02-20 01:29:45 +00:00
smyalygames
dbd25b796b feat(arabot): add nv access for vegan unrestricts 2023-02-20 01:26:43 +00:00
smyalygames
a8691c0587 feat(arabot): add restrict logs command to show all restrictions 2023-02-20 01:09:52 +00:00
smyalygames
cd00be8147 feat(arabot): add fetching restricts from database 2023-02-20 01:09:38 +00:00
smyalygames
c60d8807a2 fix(arabot): had fetchReply be attached to staffChannel 2023-02-20 00:12:34 +00:00
smyalygames
72c27f5b72 feat(arabot): add order to order by to findNotes 2023-02-20 00:08:32 +00:00
smyalygames
e21bf55fc7 feat(arabot): add nvAccess on join if user is vegan 2023-02-20 00:00:44 +00:00
smyalygames
a979c85079 fix(arabot): add check if roles list is not empty before giving roles 2023-02-19 23:55:45 +00:00
smyalygames
a134dbccc4 fix(arabot): remove adding empty string to fetchRoles 2023-02-19 23:50:33 +00:00
Anthony Berg
0ef4025d00 Merge pull request #83 from veganhacktivists/main
update outreah
2023-02-19 21:31:08 +00:00
smyalygames
7de8b22e0e feat(arabot): dealt with nv restrict roles in other commands 2023-02-19 21:28:30 +00:00
smyalygames
4bb38415bd feat(arabot): enable non vegan access for roles 2023-02-19 20:33:57 +00:00
smyalygames
dbc359d11b feat(arabot): add nv access to verifications 2023-02-19 20:33:30 +00:00
smyalygames
9990db84b2 fix(arabot): add potential fix for restricted roles 2023-02-19 20:32:51 +00:00
Anthony Berg
f6149a60f5 Merge pull request #82 from veganhacktivists/nonvegan_access
feat(arabot): non-vegan access
2023-02-19 16:35:27 +00:00
smyalygames
ff6f22005f feat(arabot): create command for nvaccess 2023-02-19 16:34:54 +00:00
smyalygames
c4075fe9f9 feat(arabot): create custom error for catch 2023-02-19 16:34:43 +00:00
smyalygames
6979a0e79c feat(arabot): disable role creation for now 2023-02-19 16:34:25 +00:00
smyalygames
a7c2541d89 feat(arabot): change role for non vegan access 2023-02-19 13:35:18 +00:00
smyalygames
f8a120b135 refactor(arabot): change wording 2023-02-19 13:35:01 +00:00
Anthony Berg
e57fa26f6b Merge pull request #81 from veganhacktivists/main
Update nonvegan_access commits
2023-02-19 13:29:39 +00:00
smyalygames
946b8e4440 feat(arabot): enable give roles back on join 2023-02-18 15:25:23 +00:00
smyalygames
12d4445428 refactor(arabot): remove duplicate code for creating channels 2023-02-18 13:31:58 +00:00
smyalygames
a45a297d2f refactor(arabot): remove duplicate code for creating channels 2023-02-18 13:19:28 +00:00
smyalygames
7260e9c32b refactor(arabot): run prisma format 2023-02-18 12:24:16 +00:00
Anthony Berg
06a05d4240 Merge pull request #80 from veganhacktivists/main
Update branch
2023-02-18 12:15:45 +00:00
Anthony Berg
a38677c4e3 Merge pull request #79 from veganhacktivists/main
Update branch
2023-02-18 12:14:24 +00:00
smyalygames
ea58ecc261 fix(arabot): fixed issues with using GuildMember instead of User leading to undefined instances 2023-02-18 03:08:38 +00:00
smyalygames
62e3c698fb ci(config): change arabot version number for slowmode 2023-02-17 03:05:38 +00:00
smyalygames
6cb73b2a4b feat(arabot): add slowmode command 2023-02-17 02:59:19 +00:00
smyalygames
5503edd70b feat(utils): add isNumber 2023-02-17 02:53:55 +00:00
smyalygames
dda77483eb refactor(utils): move fibonacci and randint to one file 2023-02-17 02:53:30 +00:00
smyalygames
4d75f72989 ci(config): change arabot version number 2023-02-17 01:53:39 +00:00
smyalygames
de0fc48ba8 fix(arabot): fix problem for member not in server 2023-02-17 01:52:49 +00:00
smyalygames
0b3d57d2da feat(arabot): add restrictions for vegans 2023-02-17 01:37:55 +00:00
smyalygames
ef164125ea feat(arabot): add bans on join for temp ban 2023-02-17 01:26:33 +00:00
smyalygames
2833c54c75 feat(arabot): remove temp ban if ban command is run 2023-02-17 01:04:44 +00:00
smyalygames
922dd7556e feat(arabot): remove temp ban if unban command is run 2023-02-17 01:04:26 +00:00
smyalygames
94f88d882d feat(arabot): add temp ban task to remove temp ban 2023-02-17 01:04:12 +00:00
smyalygames
cd8ecd527b feat(arabot): add temp ban command 2023-02-17 01:03:51 +00:00
smyalygames
48125ba896 feat(arabot): add temp ban database utilities 2023-02-17 01:03:42 +00:00
smyalygames
e8fac57b72 refactor(arabot): remove ts-ignore for tasks as it has been fixed 2023-02-17 00:58:57 +00:00
smyalygames
62fd98bbe6 fix(arabot): fix name in ScheduledTasks interface 2023-02-17 00:58:11 +00:00
smyalygames
cc642708aa build(arabot): update @sapphire/plugin-scheduled-tasks 2023-02-17 00:57:29 +00:00
smyalygames
dc523b2db1 feat(db): add check extra check when fetching user 2023-02-16 16:36:33 +00:00
smyalygames
319049bc2b refactor(db): rename functions for better readability 2023-02-16 16:09:56 +00:00
smyalygames
f3fa1398e8 feat(db): add end mod for temp bans 2023-02-16 15:26:22 +00:00
smyalygames
dadaacd636 build(arabot): add @sapphire/time-utilities 2023-02-16 15:21:06 +00:00
smyalygames
204090c39d refactor(arabot): move ban commands to its own directory 2023-02-16 15:20:33 +00:00
smyalygames
64c9f75714 feat(arabot): add listener for bans not done via bot 2023-02-16 01:43:37 +00:00
smyalygames
6dc3c1a2f9 refactor(arabot): move ban related listeners to ban directory 2023-02-16 01:21:23 +00:00
smyalygames
77f3e4666f feat(arabot): add listener for logging bans that weren't done on bot 2023-02-16 01:20:05 +00:00
smyalygames
3cd0756d88 refactor(arabot): rename ban on join listener 2023-02-16 01:19:23 +00:00
smyalygames
ec075f8927 build(upgrade): update package.json 2023-02-16 00:42:25 +00:00
smyalygames
63a320535c fix(arabot): add check for restricted user on database for unrestrictions 2023-02-15 20:32:20 +00:00
smyalygames
dab4f1767c fix(arabot): fix infinite for loop when finding restricted role 2023-02-15 20:26:26 +00:00
smyalygames
daa427482a feat(db): start schema for events and stats 2023-02-15 19:57:26 +00:00
smyalygames
eb345d1866 refactor(db): fix warning table to follow prisma naming convention 2023-02-15 13:01:09 +00:00
smyalygames
d0d9dbacfd feat(arabot): remove unrestrict vegan from unrestrict command 2023-02-14 22:00:36 +00:00
smyalygames
58f81eaac2 feat(arabot): add restrict, verify block roles on join and remove db roles 2023-02-14 21:22:36 +00:00
smyalygames
b4a3816649 feat(arabot): temporarily remove vegan restrict ID 2023-02-14 14:32:58 +00:00
Anthony Berg
c3dbd42d36 Merge pull request #78 from veganhacktivists/restrict
feat(arabot): new restrict system
2023-02-14 14:28:12 +00:00
smyalygames
dd008c1ea5 feat(arabot): remove vegan restricts and restrict after left 2023-02-14 14:26:52 +00:00
smyalygames
ba5cebc78a feat(arabot): let arabot appreciate the appreciation it gets 2023-02-14 14:10:05 +00:00
smyalygames
be3dacd344 feat(docker): add container name to arabot 2023-02-14 14:09:44 +00:00
smyalygames
0a161122d7 feat(arabot): add ability to warn user 2023-02-14 10:19:49 +00:00
smyalygames
36bd0eb7a9 feat(db): add warnings table 2023-02-14 10:00:44 +00:00
smyalygames
7dde12f456 feat(db): add restrict section to restrict table 2023-02-14 09:57:11 +00:00
smyalygames
6e802f8dcd refactor(db): fix typos in column names 2023-02-14 09:56:58 +00:00
smyalygames
39da42541f feat(db): add mod who unrestricted 2023-02-14 09:56:38 +00:00
smyalygames
ca1511a87b refactor(arabot): change variable names 2023-02-14 09:48:14 +00:00
smyalygames
e6e4a288ba feat(arabot): add error handling for missing perms for renaming 2023-02-14 09:47:07 +00:00
smyalygames
71cc4d461c refactor(arabot): change restricted roles array to array from IDs 2023-02-14 01:31:11 +00:00
smyalygames
7d54976cfd feat(arabot): add specific restrict role on joining server 2023-02-14 01:30:24 +00:00
smyalygames
c60ba056ff feat(arabot): add an array for restricted ids 2023-02-14 01:29:20 +00:00
smyalygames
eba68015db feat(arabot): add database logs for legacy restrictions 2023-02-14 01:20:08 +00:00
smyalygames
e544fa2eae feat(arabot): add selecting a restricted section to database 2023-02-14 01:08:10 +00:00
smyalygames
9e833b87fd feat(arabot): add unrestrict legacy restrictions to database 2023-02-14 01:07:32 +00:00
smyalygames
3632aaa276 feat(arabot): create random integer function 2023-02-14 01:07:11 +00:00
smyalygames
383e644318 refactor(db): change variable naming convention and types 2023-02-14 00:59:19 +00:00
smyalygames
b57a25454b feat(db): add restrict section to restrict table 2023-02-14 00:01:58 +00:00
smyalygames
9ed1141d09 feat(arabot): log to database the roles a user had when leaving 2023-02-13 23:44:49 +00:00
smyalygames
ba99a6be4a refactor(arabot): add function to remove duplicate code and add embeds for logs 2023-02-12 00:27:42 +00:00
smyalygames
a259d8a067 feat(arabot): add roles when user joins server 2023-02-11 23:02:45 +00:00
smyalygames
de1d800341 feat(arabot): log to database the roles a user had when leaving 2023-02-11 23:02:30 +00:00
smyalygames
51678a5701 feat(arabot): add restrict tolerance command 2023-02-11 21:23:21 +00:00
smyalygames
a79cc496d8 refactor(arabot): make restrictRun exported function 2023-02-11 21:23:02 +00:00
smyalygames
733a6609a8 fix(arabot): change wording for vegan restrict channel embed 2023-02-11 21:14:13 +00:00
smyalygames
63f161b2d1 feat(arabot): add remove vegan restrict channel 2023-02-11 21:13:38 +00:00
smyalygames
a8250eb514 refactor(arabot): change adding user to database 2023-02-11 21:13:25 +00:00
smyalygames
c0da7cb1fa fix(arabot): change restrict category from private to restrict in vegan restrict 2023-02-11 20:53:11 +00:00
smyalygames
1bacef1f08 feat(arabot): add unrestrict command 2023-02-11 20:52:55 +00:00
smyalygames
e89fcb5314 feat(arabot): change user and mod in embed to inline 2023-02-11 20:50:32 +00:00
smyalygames
5ed15e6206 feat(arabot): add restrict command 2023-02-11 20:25:14 +00:00
smyalygames
c0c1c16c37 refactor(db): fix typos in column names 2023-02-11 18:17:23 +00:00
Anthony Berg
cf0078ad25 Merge pull request #77 from veganhacktivists/main
update restrict branch
2023-02-11 16:13:48 +00:00
smyalygames
dedd72c731 feat(arabot): add listener to mute user on join or remove vcmute on unmute 2023-02-11 01:54:47 +00:00
smyalygames
d7aea2c6c1 fix(arabot): add checkers if user is in vc when server muting 2023-02-11 01:54:10 +00:00
smyalygames
c735509f57 fix(arabot): fix logic for checkActive 2023-02-11 01:53:24 +00:00
smyalygames
ecf75b63e7 feat(arabot): add vcmute command 2023-02-11 00:36:02 +00:00
smyalygames
0e31061d99 feat(arabot): add vcmute database utils 2023-02-11 00:35:51 +00:00
smyalygames
b14de47934 feat(db): add vcmute database 2023-02-11 00:35:35 +00:00
smyalygames
4b6dde3af2 build(db): update prisma 2023-02-10 23:55:11 +00:00
smyalygames
2339ae6784 feat(db): add mod who unrestricted 2023-02-10 23:39:18 +00:00
smyalygames
9a1a2a6852 feat(arabot): add button for managing access to non vegan section 2023-02-10 13:20:07 +00:00
Anthony Berg
80025b54ab Merge pull request #75 from veganhacktivists/softmute
feat(arabot): add soft mute command
2023-02-10 01:21:15 +00:00
smyalygames
fa0d9c2a60 feat(arabot): add soft mute command 2023-02-10 01:17:52 +00:00
smyalygames
91631995a9 feat(arabot): start restriction command 2023-02-10 00:59:10 +00:00
smyalygames
6f31b21feb fix(arabot): fix api error on fetching undefined user 2023-02-09 20:42:03 +00:00
smyalygames
8d1276ab2c feat(arabot): add soft mute basics 2023-02-09 17:56:53 +00:00
smyalygames
a44c804097 feat(arabot): add soft mute role snowflake 2023-02-09 17:51:08 +00:00
smyalygames
fcc10c392e refactor(arabot): change import for dotenv 2023-02-09 13:50:23 +00:00
smyalygames
80f201a1ad refactor(arabot): change subcommand logic to sapphire's plugin 2023-02-09 13:37:43 +00:00
smyalygames
8d83365c8a refactor(arabot): change wording for dms 2023-02-07 23:51:56 +00:00
smyalygames
4f628bb59e feat(arabot): add dm acknowledgement for suggestion 2023-02-07 23:44:40 +00:00
smyalygames
a047ade28f feat(arabot): add suggestions handler 2023-02-07 23:35:30 +00:00
Anthony Berg
6e4607161b Merge pull request #74 from veganhacktivists/database_refactor
perf(arabot): change database connection to global instance
2023-02-07 22:49:36 +00:00
smyalygames
96233914a2 perf(arabot): change database connection to global instance 2023-02-07 22:48:57 +00:00
smyalygames
d8e7a48ee7 fix(arabot): fix not banning if not in cache 2023-02-07 21:25:41 +00:00
Anthony
f728e14163 refactor(arabot): change console to sapphire-logger 2023-02-07 14:17:48 +00:00
Anthony
8e7ed8442c refactor(arabot): change eslint for export default 2023-02-07 13:57:27 +00:00
Anthony Berg
879c8bde77 Merge pull request #73 from veganhacktivists/absolute_imports
refactor(arabot): change to absolute imports
2023-02-07 01:00:38 +00:00
smyalygames
937e874d66 refactor(arabot): use absolute paths 2023-02-07 00:59:37 +00:00
Anthony
45c19824c5 refactor(arabot): change to absolute imports 2023-02-06 23:20:52 +00:00
smyalygames
a78403278a fix(arabot): add/remove non vegan roles 2023-02-06 00:25:14 +00:00
smyalygames
0bc49cb8f1 fix(arabot): fix permissions for verifier coordinator 2023-02-03 22:36:26 +00:00
smyalygames
09bb3f24f8 feat(arabot): add user management in thread 2023-01-29 00:32:17 +00:00
Anthony
9a91d081e6 fix(arabot): remove unnecessary once in events 2023-01-27 14:42:58 +00:00
Anthony
0e33019a98 docs(arabot): update with multiple more new commands 2023-01-19 22:36:55 +00:00
Anthony
67eb01781f feat(arabot): add clear command 2023-01-19 18:53:34 +00:00
Anthony
7127d1dfe8 fix(arabot): fix class naming 2023-01-19 18:28:32 +00:00
Anthony
479db1a1c6 feat(arabot): create moveall command 2023-01-19 16:27:03 +00:00
smyalygames
f2952d15e9 feat(arabot): create anonymous command 2023-01-16 00:37:19 +00:00
smyalygames
8c395784d0 fix(arabot): check private channel already exists 2023-01-15 19:22:35 +00:00
smyalygames
2880283e9a feat(arabot): add access command 2023-01-15 19:00:10 +00:00
smyalygames
c6d44083e7 fix(arabot): change class name 2023-01-15 18:10:40 +00:00
smyalygames
4fe6420cb2 feat(arabot): add private channels 2023-01-15 17:56:01 +00:00
smyalygames
8dfa7a7072 feat(arabot): add create private channels 2023-01-15 16:51:05 +00:00
smyalygames
d4c11febc1 feat(arabot): add apply command 2023-01-15 15:53:34 +00:00
smyalygames
db3c16b2b2 refactor(arabot): remove a few comments 2023-01-15 15:11:19 +00:00
smyalygames
9d487fcbf6 feat(arabot): add count command 2023-01-15 15:10:49 +00:00
smyalygames
abca50ff8e feat(arabot): add rename command 2023-01-15 14:25:16 +00:00
smyalygames
be0b57890d feat(arabot): add DMs when given role 2023-01-15 14:00:39 +00:00
smyalygames
5879c1a610 feat(arabot): add guest command 2023-01-15 13:57:56 +00:00
smyalygames
03f6310caf feat(arabot): add game night host command 2023-01-15 13:52:34 +00:00
smyalygames
1114190012 feat(arabot): add debate host command 2023-01-15 13:48:49 +00:00
smyalygames
bf16170871 feat(arabot): add book club command 2023-01-15 13:44:00 +00:00
smyalygames
1e62fdf540 feat(arabot): add message command for stagehost 2023-01-15 13:39:38 +00:00
smyalygames
3b8b478a4c feat(arabot): add mentor command 2023-01-15 13:34:37 +00:00
smyalygames
7187ce2f6a feat(arabot): add trial verifier command 2023-01-15 13:30:36 +00:00
smyalygames
505c105a6a fix(arabot): change command name 2023-01-15 13:28:20 +00:00
smyalygames
f044b6f23d fix(arabot): change command name 2023-01-15 13:27:51 +00:00
smyalygames
6fe75b197d fix(arabot): change copyright year to 2023 2023-01-15 13:26:49 +00:00
smyalygames
4db0d8aaf1 feat(arabot): add verifier command 2023-01-15 13:25:22 +00:00
smyalygames
38390f7e59 feat(arabot): add restricted access command 2023-01-15 13:20:35 +00:00
smyalygames
f4135d0fcb feat(arabot): add mod command 2023-01-15 13:17:46 +00:00
smyalygames
9f01fcd542 feat(arabot): add plus command 2023-01-15 13:12:42 +00:00
smyalygames
b1a756aea6 feat(arabot): add activist command 2023-01-15 12:42:31 +00:00
smyalygames
9ada008da5 feat(arabot): add close DM catch 2023-01-15 12:42:08 +00:00
smyalygames
2b9cb907cc feat(arabot): change activist message now send in DM 2023-01-15 12:36:10 +00:00
smyalygames
83a5fdee8d fix(arabot): change class name 2023-01-15 12:31:43 +00:00
smyalygames
3fd2e8acdd feat(arabot): add vegan role 2023-01-15 12:31:08 +00:00
smyalygames
eef7c9f953 refactor(arabot): move certain role commands to directory 2023-01-15 12:30:49 +00:00
smyalygames
58a3a28e4d fix(arabot): change class name 2023-01-15 12:10:33 +00:00
smyalygames
442822dcf2 feat(arabot): add diversity command 2023-01-15 12:09:38 +00:00
smyalygames
88b7ef269d feat(arabot): add trusted command 2023-01-15 11:59:08 +00:00
smyalygames
3771c037a6 fix(arabot): fix check if user is vegan 2023-01-15 11:32:46 +00:00
smyalygames
1ddda61bd0 Revert "refactor(arabot): defined import paths"
This reverts commit 24b0ca274a.
2023-01-15 11:13:41 +00:00
smyalygames
24b0ca274a refactor(arabot): defined import paths 2023-01-15 10:45:46 +00:00
smyalygames
9496e838f4 feat(arabot): add veg curious command 2023-01-15 10:26:33 +00:00
smyalygames
9aa669e4c5 fix(arabot): fix intents for message commands 2023-01-15 02:18:05 +00:00
smyalygames
e5dfa51cc9 upgrade(arabot): upgrade sapphire to v4 2023-01-14 22:21:42 +00:00
smyalygames
f6b321e454 build(arabot): update all packages 2022-12-11 01:04:57 +00:00
smyalygames
341fb22833 feat(arabot): add diversity section rules message 2022-11-16 13:43:57 +00:00
smyalygames
9dc3efd0ef feat(arabot): disable camera in verifications 2022-11-14 18:00:40 +00:00
smyalygames
26ed6cb6a8 fix(arabot): spacing issue 2022-11-14 17:27:27 +00:00
smyalygames
cec0d64109 feat(arabot): add message in restricted section 2022-11-14 15:40:27 +00:00
smyalygames
203dc6bfdd feat(arabot): add modmail to staff checker 2022-11-06 00:01:42 +00:00
smyalygames
357322c3b8 fix(arabot): add staff ephemeral if no sus notes 2022-11-05 23:56:24 +00:00
smyalygames
4f8cec5331 refactor(arabot): change addField to addFields 2022-11-05 23:55:29 +00:00
smyalygames
275da768dd feat(arabot): make ephemeral in list based on staff channel 2022-11-05 23:49:07 +00:00
smyalygames
d90b89d4ac feat(arabot): add check if in staff category 2022-11-05 23:41:30 +00:00
smyalygames
c42d612ed4 feat(arabot): remove restrict step for non-vegans 2022-11-05 21:42:02 +00:00
smyalygames
270d6ca91f feat(arabot): add fix for bad word API error 2022-11-03 21:35:18 +00:00
smyalygames
a6cf9db523 feat(arabot): add fix for bad word API error 2022-11-03 19:38:30 +00:00
smyalygames
37e37011ad feat(arabot): add more db checks 2022-10-28 14:28:41 +01:00
smyalygames
baaa52aa14 refactor(arabot): remove unnecessary not nulls 2022-10-28 05:31:13 +01:00
smyalygames
990d0f0dfa feat(arabot): extra checks if been banned outside bot 2022-10-28 05:14:27 +01:00
smyalygames
a943d5798e fix(arabot): fix broken catch 2022-10-28 03:08:21 +01:00
smyalygames
1c5ec53b3b feat(arabot): add check if user is already banned 2022-10-28 03:07:31 +01:00
smyalygames
534b45f5db fix(arabot): fix broken catches 2022-10-28 03:03:44 +01:00
smyalygames
9d3f687473 fix(arabot): check if user is banned 2022-10-28 03:03:01 +01:00
smyalygames
556038ee80 feat(arabot): add ability to ban when user is not in server 2022-10-28 02:47:59 +01:00
smyalygames
722f0a53f1 fix(arabot): add catch if user is not banned 2022-10-28 02:38:58 +01:00
smyalygames
d42c4ce1cb fix(arabot): change preconditions for restricted access only 2022-10-28 02:33:05 +01:00
smyalygames
7df01742be feat(arabot): add restricted access precondition 2022-10-28 02:32:59 +01:00
smyalygames
c408dab5a5 feat(arabot): add check on join if user is banned 2022-10-28 02:25:27 +01:00
smyalygames
51082db025 feat(arabot): add database logging for bans 2022-10-28 02:25:18 +01:00
smyalygames
a40925e9e9 feat(arabot): add database for bans 2022-10-28 02:24:44 +01:00
smyalygames
16fb194cd3 feat(arabot): add unban command 2022-10-28 01:16:08 +01:00
smyalygames
b1df7b4001 feat(arabot): add ban command 2022-10-28 01:08:17 +01:00
smyalygames
c55f2cd077 fix(arabot): stop command from being run if vegan 2022-10-27 02:39:00 +01:00
smyalygames
488e256f30 feat(arabot): add alias to convinced command 2022-10-27 02:38:01 +01:00
smyalygames
222ec5b8b3 fix(arabot): fix preconditions not being in a list 2022-10-27 02:35:14 +01:00
smyalygames
28e06a3b88 feat(arabot): add convinced command 2022-10-27 02:29:14 +01:00
smyalygames
ac50b96ba8 refactor(arabot): remove forcing types as its unnecessary 2022-10-27 01:14:02 +01:00
smyalygames
ecd5a692c1 refactor(arabot): remove forcing types as its unnecessary 2022-10-27 01:12:12 +01:00
smyalygames
4e041f102e fix(arabot): add failsafe for user leaving guild 2022-10-26 03:38:40 +01:00
smyalygames
8c06bb6b88 refactor(arabot): remove the need for container 2022-10-26 03:34:54 +01:00
smyalygames
4d819d87d2 fix(arabot): add error handling for undefined errors 2022-10-25 03:47:00 +01:00
smyalygames
f389479e9a ci(arabot): change version number 2022-10-23 02:45:54 +01:00
smyalygames
0d4e498d79 refactor(arabot): change order of welcome message 2022-10-23 02:38:50 +01:00
smyalygames
d2e4e7c167 feat(arabot): remove giving roles other than block verification on join 2022-10-22 21:19:57 +01:00
smyalygames
d771827b6d feat(arabot): add mute perms for verifiers 2022-10-22 04:25:20 +01:00
smyalygames
e5218d9c25 feat(arabot): add a reminder about the new verification system 2022-10-21 20:39:10 +01:00
smyalygames
f80fd422c7 build(arabot): update all packages 2022-10-21 20:31:52 +01:00
smyalygames
fa476f06be fix(arabot): checks for wrong channel if cache doesn't work 2022-10-21 19:40:03 +01:00
smyalygames
ab0e6c5adf feat(arabot): add join button in welcome 2022-10-21 19:35:42 +01:00
smyalygames
15fe1a38a1 refactor(arabot): change verify walkthrough question/answer 2022-10-21 18:13:35 +01:00
smyalygames
6e69039767 feat(arabot): add welcome channel to ids 2022-10-21 15:50:28 +01:00
smyalygames
bcf72388ab feat(arabot): remove purgeVerifying as verify-as-vegan no longer exists 2022-10-21 04:44:28 +01:00
smyalygames
eddf0bcec6 feat(arabot): remove verify-as-vegan role 2022-10-21 04:44:10 +01:00
smyalygames
0873e4ad40 fix(arabot): remove useless comparison 2022-10-21 04:18:07 +01:00
smyalygames
c0e5bae1e7 fix(arabot): fix verification vc being removed/not created 2022-10-21 04:14:19 +01:00
smyalygames
a545a52ba7 fix(arabot): fix permission issues for vcs 2022-10-21 03:36:39 +01:00
smyalygames
8a66fa39be fix(arabot): fix permission issues for vcs 2022-10-21 03:27:40 +01:00
smyalygames
4331f1a644 fix(arabot): fix permission issues for vcs 2022-10-21 03:25:29 +01:00
smyalygames
49f18bd10c fix(arabot): fix import merge conflicts 2022-10-20 22:46:01 +01:00
Anthony Berg
57b17d6e28 Merge pull request #68 from veganhacktivists/verification
feat(arabot): new verification system
2022-10-20 22:38:20 +01:00
Anthony Berg
bb117f3c23 Merge branch 'main' into verification 2022-10-20 22:35:42 +01:00
smyalygames
d460826c43 feat(verify): delete apple warning 2022-10-20 22:25:04 +01:00
smyalygames
92ebf948f0 feat(verify): add verification block role id 2022-10-20 22:23:58 +01:00
smyalygames
a8efff5871 refactor(verify): change timeout values for production 2022-10-20 22:18:57 +01:00
smyalygames
989915b796 feat(verify): add cancel for roles to give in verification 2022-10-20 22:14:31 +01:00
smyalygames
3c0472eccc feat(verify): add DM msg after verification 2022-10-20 18:45:38 +01:00
smyalygames
6f694d94fb build(arabot): update all packages 2022-10-20 16:45:09 +01:00
smyalygames
4314e3a083 fix(verify): add a catch for closed DMs 2022-10-20 16:44:51 +01:00
smyalygames
44d913b863 fix(verify): change getMillisecond to getTime 2022-10-20 16:44:26 +01:00
smyalygames
7f3f31d9f0 feat(verify): add give roles on join 2022-10-20 13:43:29 +01:00
smyalygames
e0207fdd8d feat(verify): add welcome message after verify 2022-10-19 01:59:49 +01:00
smyalygames
9cc7e53ea9 feat(verify): add verify block dm for timeout/leave 2022-10-16 22:55:11 +01:00
smyalygames
1993682484 build(config): update packages 2022-10-16 22:05:46 +01:00
smyalygames
24afe3c4b2 feat(verify): add checks on startup 2022-10-16 16:08:51 +01:00
smyalygames
bb19a17522 fix(arabot): wrong variables in payload 2022-10-14 18:11:52 +01:00
Anthony
70854915af feat(verification): add 15 minute timeout logic 2022-10-14 11:11:05 +01:00
smyalygames
ee27bb7582 feat(arabot): add sus notes to verification 2022-09-30 14:42:59 +01:00
Anthony
79c6614f8d Merge remote-tracking branch 'origin/verification' into verification 2022-09-23 19:14:51 +01:00
Anthony
34dc689f94 feat(db): add previous migrations to git 2022-09-23 19:14:45 +01:00
smyalygames
0749d81586 refactor(arabot): move db functions to utils 2022-09-23 18:22:11 +01:00
smyalygames
8ee0b924fa refactor(arabot): move db functions to utils 2022-09-23 18:21:59 +01:00
smyalygames
effedbdfaf docs(arabot): fix sus view 2022-09-22 14:14:12 +01:00
smyalygames
8dc1c99995 docs(arabot): list all commands 2022-09-22 14:04:27 +01:00
Anthony
915f7d7048 feat(verification): add verification block after verify 2022-09-02 01:20:07 +01:00
Anthony
0829e7c996 feat(verification): add information on user in text channel 2022-09-01 03:37:28 +01:00
Anthony
a893e13d57 feat(verification): add giving all roles back after leaving vc 2022-08-31 03:12:40 +01:00
Anthony
cae9091aa1 feat(arabot): add fibonacci verification ban lengths 2022-08-31 02:51:52 +01:00
Anthony
35de24399a feat(arabot): add extra check if guild/user not in cache 2022-08-31 00:40:26 +01:00
Anthony
5a5afbfb02 feat(arabot): add fibonacci sequence 2022-08-30 00:39:47 +01:00
Anthony
f89d6c0730 feat(verification): add schedule with verify block 2022-08-28 02:42:04 +01:00
Anthony
63bce0d405 feat(verification): remove roles on join for non vegan 2022-08-27 03:52:32 +01:00
Anthony
544d6bafd8 feat(verification): add verifier to database after confirmation 2022-08-27 03:41:06 +01:00
Anthony
0043fdddfb feat(verification): confirm add roles 2022-08-27 02:48:08 +01:00
Anthony
b71a7ef2ea ci(config): update docker node debian version 2022-08-24 15:34:57 +01:00
Anthony
a4d15f67ab ci(compiler): update target from es2016 to es2021 2022-08-24 15:12:27 +01:00
Anthony
6af8373f2b fix(verification): isNaN eslint issue 2022-08-24 15:01:01 +01:00
Anthony
801297c3a1 feat(verification): last few questions are dynamically added 2022-08-24 14:55:55 +01:00
Anthony
4deb7efc56 feat(verification): add ability for up to 25 buttons 2022-08-24 02:58:45 +01:00
Anthony
41c23ebb3c feat(verification): add logging button presses 2022-08-24 02:05:35 +01:00
Anthony
39c32101c5 build(config): add prisma as dev dependency 2022-08-23 04:32:07 +01:00
Anthony
3dd219ae04 feat(verification): add being able to go through questions 2022-08-23 04:31:03 +01:00
Anthony
75b768046d feat(verification): add util config fixes 2022-08-23 02:13:35 +01:00
Anthony
2b96d507eb fix(verification): fix multiple eslint issues 2022-08-23 02:05:13 +01:00
Anthony
c71b787c8b feat(verification): add logic to creating embed and buttons dynamically 2022-08-23 01:59:51 +01:00
Anthony
b567559438 feat(config): update dev dependencies and fix tsconfig 2022-08-22 22:57:37 +01:00
Anthony
e0f1bb6853 Merge remote-tracking branch 'origin/main' 2022-08-20 22:55:20 +01:00
Anthony
2d9fddba9e Merge remote-tracking branch 'origin/main'
# Conflicts:
#	package-lock.json
#	package.json
#	src/utils/devIDs.ts
2022-08-20 22:09:27 +01:00
Anthony
4e326e3e6d Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/commands/mod/sus.ts
#	src/preconditions/CoordinatorOnly.ts
#	src/preconditions/DevCoordinatorOnly.ts
#	src/preconditions/DiversityCoordinatorOnly.ts
#	src/preconditions/MentorCoordinatorOnly.ts
#	src/preconditions/ModOnly.ts
#	src/preconditions/PatreonOnly.ts
#	src/preconditions/VerifierCoordinatorOnly.ts
#	src/preconditions/VerifierOnly.ts
#	src/utils/dbExistingUser.ts
#	src/utils/devIDs.ts
#	src/utils/verificationConfig.ts
2022-08-19 05:20:01 +01:00
Anthony
b2d11b6627 feat(arabot): remove old schedule manager 2022-08-19 03:52:30 +01:00
Anthony
4883ec12a1 feat(verification): add info to be collected 2022-08-19 03:51:42 +01:00
Anthony
300ad9a903 refactor(arabot): move database functions 2022-08-19 03:51:11 +01:00
Anthony
ba638bb6e2 feat(arabot): add new scheduler plugin 2022-08-19 03:36:40 +01:00
Anthony
092dcce2fe feat(arabot): add beginning of verification walkthrough 2022-08-18 05:47:30 +01:00
Anthony
8d39539fef refactor(arabot): add proper null checks 2022-08-18 03:52:35 +01:00
Anthony
0d571dd923 feat(arabot): add text channel to verification 2022-08-18 03:20:49 +01:00
Anthony
30729a565f Merge remote-tracking branch 'origin/main' 2022-08-13 17:08:42 +01:00
Anthony
e0893a2bab Merge remote-tracking branch 'origin/main' 2022-08-12 00:50:30 +01:00
Anthony
2abc957284 feat(eslint): add airbnb-typescript to eslint 2022-08-12 00:50:12 +01:00
Anthony
f0b0408a71 feat(arabot): update IDs to be able to switch between development IDs and normal IDs 2022-08-10 05:38:18 +01:00
Anthony
ed5327aae2 feat(verification): add IDs from utils 2022-08-10 05:34:26 +01:00
Anthony
31199d9cd5 feat(arabot): add dev ids in development mode 2022-08-10 05:20:04 +01:00
Anthony
4e856913e9 feat(arabot): dantas is literally 1984 2022-08-10 04:37:28 +01:00
Anthony
1c4cf5a5f6 Merge remote-tracking branch 'origin/main' 2022-08-09 23:56:33 +01:00
Anthony
e4cb0bdfdc feat(verification): add a check if the joinable vc will be deleted 2022-08-08 01:59:07 +01:00
Anthony
0ebf6af089 ci(docker): remove .env file from being in the container 2022-08-08 01:45:34 +01:00
Anthony
c0a0e10547 feat(database): add update user function 2022-08-06 16:46:34 +01:00
Anthony
7a2543cea1 feat(docker): add automatic restart on failure to bot 2022-08-06 15:08:18 +01:00
Anthony
ac5860d10c feat(arabot): add create a new vc for joining and delete vc when leaving and give the user non vegan role 2022-08-06 14:54:26 +01:00
renovate[bot]
5539277196 chore(deps): add renovate.json 2022-07-29 04:08:57 +00:00
190 changed files with 18588 additions and 7121 deletions

View File

@@ -1,6 +1,6 @@
.idea
dist
node_modules
tsconfig.tsbuildinfo
npm-debug.log
.env
*.md

View File

@@ -10,5 +10,9 @@ POSTGRES_USER=USERNAME
POSTGRES_PASSWORD=PASSWORD
POSTGRES_DB=DB
# Redis
REDIS_URL= # URL to redis database (if running everything within docker compose, use "redis")
BULLMQ_URL # URL for redis database, but without redis:// and credentials
# Database URL (designed for Postgres, but designed on Prisma)
DATABASE_URL= # "postgresql://USERNAME:PASSWORD@postgres:5432/DB?schema=ara&sslmode=prefer"

View File

@@ -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"]
}
}
}

View File

@@ -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.

View File

@@ -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
View File

@@ -0,0 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"schedule": ["after 12pm and before 6pm on Saturday"]
}

View File

@@ -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

View File

@@ -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
View 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
View File

@@ -0,0 +1,3 @@
node-linker=hoisted
shamefully-hoist=true
public-hoist-pattern[]=@sapphire/*

2
.prettierignore Normal file
View File

@@ -0,0 +1,2 @@
dist
node_modules

3
.prettierrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

@@ -1,20 +1,26 @@
FROM node:18-buster
FROM node:20 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"]

View File

@@ -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 :)

View 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:

View File

@@ -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
View 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)

View File

@@ -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.

View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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": ">=8"
},
"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.9.1",
"@sapphire/discord.js-utilities": "^7.1.6",
"@sapphire/framework": "^5.0.7",
"@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.0",
"@sapphire/utilities": "^3.15.3",
"@types/node": "^20.11.16",
"bullmq": "^5.1.8",
"discord.js": "^14.14.1",
"redis": "^4.6.12",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"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"
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
"eslint": "8.56.0",
"eslint-config-prettier": "^9.1.0",
"ioredis": "^5.3.2",
"prettier": "3.2.4",
"prisma": "^5.9.1"
}
}

1744
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View 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";

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "Verify" ADD COLUMN "joinTime" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ALTER COLUMN "startTime" DROP NOT NULL;

View 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";

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Verify" ALTER COLUMN "startTime" DROP DEFAULT;

View File

@@ -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;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Verify" ADD COLUMN "convinced" BOOLEAN NOT NULL DEFAULT false;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -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;

View 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;

View File

@@ -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;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Warning" RENAME CONSTRAINT "Warnings_pkey" TO "Warning_pkey";

View File

@@ -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;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Verify" ADD COLUMN "manual" BOOLEAN NOT NULL DEFAULT false;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Xp" ADD COLUMN "xpToNextLevel" INTEGER NOT NULL DEFAULT 0;

View File

@@ -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;

View 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;

View File

@@ -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";

View File

@@ -26,45 +26,218 @@ datasource db {
}
model User {
id String @id @db.VarChar(255)
level Int @default(0)
xp Int @default(0)
balance Int @default(0)
lastDaily DateTime?
vegan Boolean @default(false)
trusted Boolean @default(false)
activist Boolean @default(false)
plus Boolean @default(false)
notVegan Boolean @default(false)
vegCurious Boolean @default(false)
convinced Boolean @default(false)
muted Boolean @default(false)
VerifyUser Verify[] @relation("verUser")
VerifyVerifier Verify[] @relation("verVerifier")
SusUser Sus[] @relation("susUser")
SusMod Sus[] @relation("susMod")
RestrictUser Restrict[] @relation("restUser")
RestrictMod Restrict[] @relation("restMod")
BanUser Ban[] @relation("banUser")
BanMod Ban[] @relation("banMod")
TempBanUser TempBan[] @relation("tbanUser")
TempBanMod TempBan[] @relation("tbanMod")
id String @id @db.VarChar(255)
vegan Boolean @default(false)
trusted Boolean @default(false)
activist Boolean @default(false)
plus Boolean @default(false)
notVegan Boolean @default(false)
vegCurious Boolean @default(false)
convinced Boolean @default(false)
muted Boolean @default(false)
VerifyUser Verify[] @relation("verUser")
VerifyVerifier Verify[] @relation("verVerifier")
Xp Xp?
Balance Balance?
Daily Daily[]
SendPayment Payment[] @relation("sendPayment")
RecievePayment Payment[] @relation("recievePayment")
LeaveLog LeaveLog[]
UserRoleLog RoleLog[] @relation("userRoleLog")
ModRoleLog RoleLog[] @relation("modRoleLog")
FunLogSender FunLog[] @relation("sendFunLog")
FunLogReciever FunLog[] @relation("receiveFunLog")
Counting Counting[] @relation("counting")
EventLeader Event[] @relation("eventLeader")
StatLeader Stat[] @relation("statLeader")
OutreachParticipation ParticipantStat[] @relation("participantUser")
SusUser Sus[] @relation("susUser")
SusMod Sus[] @relation("susMod")
WarnUser Warning[] @relation("warnUser")
WarnMod Warning[] @relation("warnMod")
RestrictUser Restrict[] @relation("restUser")
RestrictMod Restrict[] @relation("restMod")
RestrictEndMod Restrict[] @relation("endRestMod")
BanUser Ban[] @relation("banUser")
BanMod Ban[] @relation("banMod")
BanEndMod Ban[] @relation("endBanMod")
TempBanUser TempBan[] @relation("tbanUser")
TempBanMod TempBan[] @relation("tbanMod")
TempBanEndMod TempBan[] @relation("endTbanMod")
VCMuteUser VCMute[] @relation("vcMuteUser")
VCMuteMod VCMute[] @relation("vcMuteMod")
}
model Verify {
id Int @id @default(autoincrement())
user User @relation("verUser", fields: [userId], references: [id])
id String @id
user User @relation("verUser", fields: [userId], references: [id])
userId String
verifier User? @relation("verVerifier", fields: [verifierId], references: [id])
verifier User? @relation("verVerifier", fields: [verifierId], references: [id])
verifierId String?
time DateTime @default(now())
timedOut Boolean @default(false) // If they got kicked out of verification because they timed out
vegan Boolean @default(false) // If they got verified as a vegan
text Boolean @default(false) // If they used text verification
serverVegan Boolean @default(false) // People that went vegan on the server
joinTime DateTime @default(now())
startTime DateTime?
finishTime DateTime?
manual Boolean @default(false) // If they were verified with the verify command
timedOut Boolean @default(false) // If they got kicked out of verification because they timed out
// complete Boolean @default(false) // If the verification was incomplete
// Roles they got from verification
vegan Boolean @default(false) // If they got verified as a vegan
activist Boolean @default(false) // If they got the activist role when they verified
trusted Boolean @default(false) // If they got the trusted role when they verified
vegCurious Boolean @default(false) // If they got the Veg Curious role
convinced Boolean @default(false)
text Boolean @default(false) // If they used text verification
serverVegan Boolean @default(false) // People that went vegan on the server
// Stats on verification
reason Int?
where Int?
length Int?
reasoning Int?
life Int?
food Int?
notes String?
}
model Xp {
user User @relation(fields: [userId], references: [id])
userId String @id
level Int @default(0)
xp Int @default(0)
xpForNextLevel Int @default(0)
messageCount Int @default(0)
lastMessage DateTime @default(now())
}
// Economy
model Balance {
user User @relation(fields: [userId], references: [id])
userId String @id
balance Int
}
model Daily {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId String
amount Int
time DateTime @default(now())
}
model Payment {
id Int @id @default(autoincrement())
sender User @relation("sendPayment", fields: [senderId], references: [id])
senderId String
recipient User @relation("recievePayment", fields: [recipientId], references: [id])
recipientId String
amount Int
reason String
time DateTime @default(now())
}
// Tracking roles for leaving the server
model LeaveLog {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId String
time DateTime @default(now())
roles String[]
}
model RoleLog {
id Int @id @default(autoincrement())
user User @relation("userRoleLog", fields: [userId], references: [id])
userId String
mod User @relation("modRoleLog", fields: [modId], references: [id])
modId String
role Role @relation(fields: [roleId], references: [id])
roleId String
add Boolean @default(false)
time DateTime @default(now())
}
model Role {
id String @id
name String
staff Boolean
RoleLog RoleLog[]
}
model FunLog {
id Int @id @default(autoincrement())
sendUser User @relation("sendFunLog", fields: [sendUserId], references: [id])
sendUserId String
receiveUser User? @relation("receiveFunLog", fields: [receiveUserId], references: [id])
receiveUserId String?
type FunType @relation(fields: [typeId], references: [id])
typeId Int
}
model FunType {
id Int @id @default(autoincrement())
name String @unique
FunLog FunLog[]
}
model Counting {
id Int @id @default(autoincrement())
user User @relation("counting", fields: [userId], references: [id])
userId String
number Int // This is the number that the user has counted, if the count failed, the number should be 0
}
// Outreach
model Event {
id Int @id @default(autoincrement())
type EventType @relation(fields: [eventType], references: [type])
eventType String
leader User @relation("eventLeader", fields: [leaderId], references: [id]) // Not sure if this will stay
leaderId String
startTime DateTime @default(now())
endTime DateTime?
stats Stat[]
}
model EventType {
type String @id
Event Event[]
}
model Stat {
id Int @id @default(autoincrement())
event Event @relation(fields: [eventId], references: [id])
eventId Int
leader User @relation("statLeader", fields: [leaderId], references: [id]) // Not sure if this will stay
leaderId String
vegan Int @default(0)
considered Int @default(0)
antivegan Int @default(0)
thanked Int @default(0)
documentary Int @default(0)
educated Int @default(0)
participants ParticipantStat[]
role StatRole?
}
model StatRole {
stat Stat @relation(fields: [statId], references: [id])
statId Int @id
roleId String
}
model ParticipantStat {
stat Stat @relation(fields: [statId], references: [id])
statId Int
user User @relation("participantUser", fields: [userId], references: [id])
userId String
@@id([statId, userId])
}
// Moderation
model Sus {
id Int @id @default(autoincrement())
user User @relation("susUser", fields: [userId], references: [id])
@@ -76,6 +249,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 +266,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 +294,20 @@ model TempBan {
mod User @relation("tbanMod", fields: [modId], references: [id])
modId String
startTime DateTime @default(now())
endedTime DateTime
endMod User? @relation("endTbanMod", fields: [endModId], references: [id])
endModId String?
endTime DateTime
active Boolean @default(true)
reason String
}
model VCMute {
id Int @id @default(autoincrement())
user User @relation("vcMuteUser", fields: [userId], references: [id])
userId String
mod User @relation("vcMuteMod", fields: [modId], references: [id])
modId String
time DateTime @default(now())
endTime DateTime?
reason String?
}

View 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}`);
}
}

View 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('✅');
}
}

View File

@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/*
Animal Rights Advocates Discord Bot
Copyright (C) 2023 Anthony Berg
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Message } from 'discord.js';
export class ClearCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, {
...options,
name: 'clear',
description: 'Deletes 1-100 messages in bulk',
preconditions: ['CoordinatorOnly'],
});
}
// Registers that this is a slash command
public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand(
(builder) =>
builder
.setName(this.name)
.setDescription(this.description)
.addIntegerOption((option) =>
option
.setName('messages')
.setDescription('Number of messages to clear')
.setRequired(true)
.setMinValue(1)
.setMaxValue(100),
),
{
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
},
);
}
// Command run
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the arguments
const messages = interaction.options.getInteger('messages', true);
const { channel } = interaction;
if (channel === null || channel.isDMBased()) {
await interaction.reply({
content: 'Could not fetch channel!',
ephemeral: true,
fetchReply: true,
});
return;
}
await channel.bulkDelete(messages);
await interaction.reply({
content: `Successfully deleted ${messages} messages!`,
ephemeral: true,
fetchReply: true,
});
}
public async messageRun(message: Message, args: Args) {
// Get arguments
const messages = await args.pick('integer');
if (messages > 100) {
await message.react('❌');
await message.reply('You can only clear up to 100 messages at a time!');
return;
}
if (messages < 1) {
await message.react('❌');
await message.reply('You need to at least clear 1 message!');
return;
}
const { channel } = message;
if (!channel.isTextBased() || channel.isDMBased()) {
await message.react('❌');
await message.reply('Unsupported channel type!');
return;
}
await channel.bulkDelete(messages);
await message.reply({
content: `Successfully deleted ${messages} messages!`,
failIfNotExists: false,
});
}
}

View 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;
}
}

View 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;
}
}

View 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
View 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;
}
}

View File

@@ -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;

View 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 });
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View File

@@ -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
View 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
View 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('✅');
}
}

View 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 ? '✅' : '❌');
}
}

View 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;
}
}

View 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 ? '✅' : '❌');
}
}

View 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}`,
});
}
}

View 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;
}
}

View 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;
}
}

View 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('✅');
}
}

View File

@@ -17,273 +17,282 @@
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,
};
// 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);
if (member === undefined) {
info.message = 'Error fetching user';
return info;
}
}
// Add the data to the database
await addSusNoteDB(user.id, mod.id, note);
// 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);
}
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;
}
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 +308,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 +347,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 +413,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 +421,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 +456,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 +534,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 +547,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 +602,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 +629,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
View 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('✅');
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -0,0 +1,544 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/*
Animal Rights Advocates Discord Bot
Copyright (C) 2023 Anthony Berg
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Subcommand } from '@sapphire/plugin-subcommands';
import { RegisterBehavior } from '@sapphire/framework';
import type { Snowflake } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser';
import {
addStatUser,
checkActiveEvent,
createEvent,
createStat,
endEvent,
getCurrentEvent,
getStatFromLeader,
getStatFromRole,
getStatGroups,
updateStats,
userInStats,
} from '#utils/database/outreach';
import IDs from '#utils/ids';
import { EmbedBuilder } from 'discord.js';
export class OutreachCommand extends Subcommand {
public constructor(
context: Subcommand.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' },
],
},
],
preconditions: ['ModOnly'],
});
}
// Registers that this is a slash command
public override registerApplicationCommands(registry: Subcommand.Registry) {
registry.registerChatInputCommand(
(builder) =>
builder
.setName(this.name)
.setDescription(this.description)
.addSubcommandGroup((group) =>
group
.setName('event')
.setDescription('Commands to do with outreach events')
.addSubcommand((command) =>
command
.setName('create')
.setDescription('Start an outreach event'),
)
/*
TODO add this back at a later date
.addBooleanOption((option) => option.setName('start')
.setDescription('Start the event immediately'))
.addSubcommand((command) => command.setName('start')
.setDescription('Start an outreach event'))
*/
.addSubcommand((command) =>
command.setName('end').setDescription('End an outreach event'),
),
)
.addSubcommandGroup((group) =>
group
.setName('group')
.setDescription('Commands to do with groups')
.addSubcommand((command) =>
command
.setName('create')
.setDescription('Create a group for people doing activism')
.addUserOption((option) =>
option
.setName('leader')
.setDescription('This is the person leading the group')
.setRequired(true),
),
)
.addSubcommand((command) =>
command
.setName('add')
.setDescription('Add a person to the group')
.addUserOption((option) =>
option
.setName('user')
.setDescription('User to add to the group')
.setRequired(true),
)
.addRoleOption((option) =>
option
.setName('group')
.setDescription('Group to add the user to'),
),
)
.addSubcommand((command) =>
command
.setName('update')
.setDescription('Update the statistics for the group')
.addIntegerOption((option) =>
option
.setName('vegan')
.setDescription('How many said would go vegan?'),
)
.addIntegerOption((option) =>
option
.setName('considered')
.setDescription(
'How many seriously considered being vegan?',
),
)
.addIntegerOption((option) =>
option
.setName('anti-vegan')
.setDescription(
'How many people had anti-vegan viewpoints?',
),
)
.addIntegerOption((option) =>
option
.setName('thanked')
.setDescription(
'How many thanked you for the conversation?',
),
)
.addIntegerOption((option) =>
option
.setName('documentary')
.setDescription(
'How many said they would watch a vegan documentary?',
),
)
.addIntegerOption((option) =>
option
.setName('educated')
.setDescription(
'How many got educated on veganism or the animal industry?',
),
),
),
),
{
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
},
);
}
public async eventCreate(
interaction: Subcommand.ChatInputCommandInteraction,
) {
// const start = interaction.options.getBoolean('start');
const modUser = interaction.user;
const { guild } = interaction;
if (guild === null) {
await interaction.reply({
content: 'Mod or guild was not found!',
ephemeral: true,
});
return;
}
const mod = guild.members.cache.get(modUser.id);
if (mod === undefined) {
await interaction.reply({
content: 'Mod was not found!',
ephemeral: true,
});
return;
}
if (!mod.roles.cache.has(IDs.roles.staff.outreachCoordinator)) {
await interaction.reply({
content: 'You need to be an Outreach Coordinator to run this command!',
ephemeral: true,
});
return;
}
if (await checkActiveEvent()) {
await interaction.reply({
content: 'There is already an active event!',
ephemeral: true,
});
return;
}
await updateUser(mod);
await createEvent(modUser.id);
await interaction.reply({
content: 'Created the event!',
ephemeral: true,
});
}
public async eventEnd(interaction: Subcommand.ChatInputCommandInteraction) {
const modUser = interaction.user;
const { guild } = interaction;
if (guild === null) {
await interaction.reply({
content: 'Guild not found!',
ephemeral: true,
});
return;
}
const mod = guild.members.cache.get(modUser.id);
if (mod === undefined) {
await interaction.reply({
content: 'Your guild member was not found!',
ephemeral: true,
});
return;
}
if (!mod.roles.cache.has(IDs.roles.staff.outreachCoordinator)) {
await interaction.reply({
content: 'You need to be an Outreach Coordinator to run this command!',
ephemeral: true,
});
return;
}
await interaction.deferReply({ ephemeral: true });
const event = await getCurrentEvent();
if (event === null) {
await interaction.editReply('There is currently no event!');
return;
}
const [stat] = await Promise.all([getStatGroups(event.id)]);
stat.forEach(({ role }) => {
if (role !== null) {
guild.roles.delete(role.roleId);
}
});
await endEvent(event.id);
// Statistics shown at the end
let vegan = 0;
let considered = 0;
let antiVegan = 0;
let thanked = 0;
let documentary = 0;
let educated = 0;
stat.forEach((group) => {
vegan += group.vegan;
considered += group.considered;
antiVegan += group.antivegan;
thanked += group.thanked;
documentary += group.documentary;
educated += group.educated;
});
const activist = guild.channels.cache.get(IDs.channels.activism.activism);
if (activist === undefined || !activist.isTextBased()) {
await interaction.editReply(
'Event has now ended, but could not post statistics!',
);
return;
}
const embed = new EmbedBuilder()
.setColor('#0099FF')
.setAuthor({ name: 'Stats for Discord Outreach' })
.addFields(
{ name: 'How many said would go vegan?', value: `${vegan}` },
{
name: 'How many seriously considered being vegan?',
value: `${considered}`,
},
{
name: 'How many people had anti-vegan viewpoints?',
value: `${antiVegan}`,
},
{
name: 'How many thanked you for the conversation?',
value: `${thanked}`,
},
{
name: 'How many said they would watch a vegan documentary?',
value: `${documentary}`,
},
{
name: 'How many got educated on veganism or the animal industry?',
value: `${educated}`,
},
)
.setTimestamp()
.setFooter({ text: `Outreach Event: ${event.id}` });
await activist.send({ embeds: [embed] });
await interaction.editReply('Event has now ended!');
}
public async groupCreate(
interaction: Subcommand.ChatInputCommandInteraction,
) {
const leader = interaction.options.getUser('leader', true);
const { guild } = interaction;
if (guild === null) {
await interaction.reply({
content: 'Guild not found!',
ephemeral: true,
});
return;
}
await interaction.deferReply({ ephemeral: true });
if ((await getStatFromLeader(leader.id)) !== null) {
await interaction.editReply(
`${leader} is already a leader for another group!`,
);
return;
}
const event = await getCurrentEvent();
if (event === null) {
await interaction.editReply({
content: 'There is no current event!',
});
return;
}
const statGroups = await getStatGroups(event.id);
const groupNo = statGroups.length + 1;
const leaderMember = await guild.members.cache.get(leader.id);
if (leaderMember === undefined) {
await interaction.editReply({
content: `Could not find ${leader}'s guild member.`,
});
return;
}
await updateUser(leaderMember);
const role = await guild.roles.create({
name: `Outreach Group ${groupNo}`,
});
await createStat(event.id, leader.id, role.id);
await leaderMember.roles.add(role);
await interaction.editReply({
content: `Created a group with the leader being ${leader}`,
});
}
public async groupAdd(interaction: Subcommand.ChatInputCommandInteraction) {
const user = interaction.options.getUser('user', true);
const group = interaction.options.getRole('group');
const leader = interaction.user;
const { guild } = interaction;
if (guild === null) {
await interaction.reply({
content: 'Could not find guild!',
ephemeral: true,
});
return;
}
await interaction.deferReply({ ephemeral: true });
let statId: number;
let roleId: Snowflake | undefined;
// Find group from role
if (group !== null) {
const [stat] = await Promise.all([getStatFromRole(group.id)]);
if (stat === null) {
await interaction.editReply({
content: `Could not find the group for role ${group}`,
});
return;
}
const leaderMember = guild.members.cache.get(leader.id);
if (leaderMember === undefined) {
await interaction.editReply({
content: 'Could not find your GuildMember in cache!',
});
return;
}
if (
leader.id !== stat.stat.leaderId &&
!leaderMember.roles.cache.has(IDs.roles.staff.outreachCoordinator)
) {
await interaction.editReply({
content: `You are not the leader for ${group}`,
});
return;
}
statId = stat.statId;
roleId = stat.roleId;
} else {
// Find group from person who ran the command
const [stat] = await Promise.all([getStatFromLeader(leader.id)]);
if (stat === null) {
await interaction.editReply({
content: "You're not a group leader!",
});
return;
}
statId = stat.id;
roleId = stat.role?.roleId;
}
if (await userInStats(statId, user.id)) {
await interaction.editReply({
content: `${user} is already in this group!`,
});
return;
}
const member = guild.members.cache.get(user.id);
if (member === undefined) {
await interaction.editReply({
content: 'Could not fetch the member!',
});
return;
}
await updateUser(member);
await addStatUser(statId, user.id);
if (roleId !== undefined) {
await member.roles.add(roleId);
}
await interaction.editReply({
content: `Added ${user} to the group!`,
});
}
public async groupUpdate(
interaction: Subcommand.ChatInputCommandInteraction,
) {
const vegan = interaction.options.getInteger('vegan');
const considered = interaction.options.getInteger('considered');
const antiVegan = interaction.options.getInteger('anti-vegan');
const thanked = interaction.options.getInteger('thanked');
const documentary = interaction.options.getInteger('documentary');
const educated = interaction.options.getInteger('educated');
const leader = interaction.user;
const stats = {
vegan: vegan !== null ? vegan : 0,
considered: considered !== null ? considered : 0,
antiVegan: antiVegan !== null ? antiVegan : 0,
thanked: thanked !== null ? thanked : 0,
documentary: documentary !== null ? documentary : 0,
educated: educated !== null ? educated : 0,
};
if (leader === null) {
await interaction.reply({
content: 'Could not find your user!',
ephemeral: true,
});
return;
}
await interaction.deferReply({ ephemeral: true });
const stat = await getStatFromLeader(leader.id);
if (stat === null) {
await interaction.editReply({
content: "You're not the leader of a group!",
});
return;
}
await updateStats(stat.id, stats);
await interaction.editReply({
content: 'Updated the database with the stats!',
});
}
}

View 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;
}
}

View 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;
}
}

View 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
View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -0,0 +1,152 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/*
Animal Rights Advocates Discord Bot
Copyright (C) 2022, 2023 Anthony Berg
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js';
import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
export class StageHostCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, {
...options,
name: 'stagehost',
description: 'Gives the Stage Host role',
preconditions: ['EventCoordinatorOnly'],
});
}
// Registers that this is a slash command
public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand(
(builder) =>
builder
.setName(this.name)
.setDescription(this.description)
.addUserOption((option) =>
option
.setName('user')
.setDescription('User to give Stage Host to')
.setRequired(true),
),
{
behaviorWhenNotIdentical: RegisterBehavior.Overwrite,
},
);
}
// Command run
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the arguments
const user = interaction.options.getUser('user', true);
const mod = interaction.user;
const { guild } = interaction;
// Checks if all the variables are of the right type
if (guild === null) {
await interaction.reply({
content: 'Error fetching guild!',
ephemeral: true,
fetchReply: true,
});
return;
}
await interaction.deferReply({ ephemeral: true });
const info = await this.manageStageHost(user, mod, guild);
await interaction.editReply(info.message);
}
public async messageRun(message: Message, args: Args) {
// Get arguments
let user: User;
try {
user = await args.pick('user');
} catch {
await message.react('❌');
await message.reply('User was not provided!');
return;
}
const mod = message.author;
if (mod === null) {
await message.react('❌');
await message.reply(
'Event coordinator not found! Try again or contact a developer!',
);
return;
}
const { guild } = message;
if (guild === null) {
await message.react('❌');
await message.reply('Guild not found! Try again or contact a developer!');
return;
}
const info = await this.manageStageHost(user, mod, guild);
await message.reply(info.message);
await message.react(info.success ? '✅' : '❌');
}
private async manageStageHost(user: User, mod: User, guild: Guild) {
const info = {
message: '',
success: false,
};
const member = guild.members.cache.get(user.id);
const stageHost = guild.roles.cache.get(IDs.roles.stageHost);
// Checks if user's GuildMember was found in cache
if (member === undefined) {
info.message = 'Error fetching guild member for the user!';
return info;
}
if (stageHost === undefined) {
info.message = 'Error fetching stage host role from cache!';
return info;
}
// Checks if the user has Stage Host and to give them or remove them based on if they have it
if (member.roles.cache.has(IDs.roles.stageHost)) {
// Remove the Stage Host role from the user
await member.roles.remove(stageHost);
await roleRemoveLog(user.id, mod.id, stageHost, true);
info.message = `Removed the ${stageHost.name} role from ${user}`;
info.success = true;
return info;
}
// Add Stage Host role to the user
await member.roles.add(stageHost);
await roleAddLog(user.id, mod.id, stageHost, true);
info.message = `Gave ${user} the ${stageHost.name} role!`;
await user
.send(`You have been given the ${stageHost.name} role by ${mod}!`)
.catch(() => {});
info.success = true;
return info;
}
}

Some files were not shown because too many files have changed in this diff Show More