157 Commits

Author SHA1 Message Date
Anthony Berg
132e3cc62b refactor(arabot): create separate functions for fetching and checking types of users, channels, etc
Some checks failed
Prettier / Run prettier scanning (push) Successful in 3m4s
ESLint / Run eslint scanning (push) Failing after 3m47s
CodeQL / Analyze (javascript) (push) Failing after 11m2s
2025-01-19 16:56:07 +01:00
Anthony Berg
db9204c115 build(arabot): update dependencies 2025-01-19 16:48:27 +01:00
Anthony Berg
0419488b6f refactor(arabot): change deprecated reply options 2025-01-19 13:21:33 +01:00
Anthony Berg
2207d996a1 docs(arabot): add TypeScript docs returns for dbExistingUser
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
ESLint / Run eslint scanning (push) Waiting to run
Prettier / Run prettier scanning (push) Waiting to run
2025-01-18 13:07:14 +01:00
Anthony Berg
8f60c3eac9 feat(arabot): add user to database when they join the server 2025-01-18 12:38:34 +01:00
Anthony Berg
c976905104 refactor(arabot): make checks in WelcomeButtonHandler more thorough 2025-01-18 12:29:28 +01:00
Anthony Berg
cd4526bfb1 refactor(arabot): remove deprecated options in nonVeganAccess 2025-01-18 12:00:50 +01:00
Anthony Berg
e44c0c2037 revert(arabot): undo the fixRoles command being only for giving nonvegan roles 2025-01-18 11:57:54 +01:00
Anthony Berg
9b3908cdc4 fix(arabot): add delay to skipping in fixRoles
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
ESLint / Run eslint scanning (push) Waiting to run
Prettier / Run prettier scanning (push) Waiting to run
2025-01-17 22:55:47 +01:00
Anthony Berg
513d3ec581 fix(arabot): prevent readding nonvegan role in fixRoles 2025-01-17 22:54:35 +01:00
Anthony Berg
2518376f3b fix(arabot): prevent readding nonvegan role in fixRoles 2025-01-17 22:51:14 +01:00
Anthony Berg
f4f83e51b2 refactor(arabot): change timings for fixRoles 2025-01-17 22:48:19 +01:00
Anthony Berg
616334f123 fix(arabot): counting for fixRoles
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
ESLint / Run eslint scanning (push) Waiting to run
Prettier / Run prettier scanning (push) Waiting to run
2025-01-17 16:16:08 +01:00
Anthony Berg
cd319609b0 fix(arabot): add snowflake for channel logs 2025-01-17 16:14:09 +01:00
Anthony Berg
a1469e0596 feat(arabot): process non-vegan roles 2025-01-17 16:07:05 +01:00
Anthony Berg
ca0e43a70e feat(arabot): add role for restricted vegan 2025-01-17 15:59:54 +01:00
Anthony Berg
dc16dee92c fix(arabot): typo in fixRoles 2025-01-17 15:34:22 +01:00
Anthony Berg
71f0ee9f01 refactor(arabot): cleanup sus command from deprecated functions
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
ESLint / Run eslint scanning (push) Has been cancelled
Prettier / Run prettier scanning (push) Has been cancelled
2025-01-16 00:58:11 +01:00
Anthony Berg
881f9bfc24 fix(arabot): new sus role was put in the wrong place 2025-01-16 00:57:25 +01:00
Anthony Berg
98b9ac6fde feat(arabot): add a check if the user is restricted when trying to gain access to the server 2025-01-15 20:46:25 +01:00
Anthony Berg
1f92bf5d68 fix: update the role snowflake to new ones 2025-01-15 20:35:47 +01:00
Anthony Berg
d9f04e8d49 Revert "fix: remove accidentally given nv roles from vegans"
This reverts commit b4c8f0785c.
2025-01-15 20:31:08 +01:00
Anthony Berg
b4c8f0785c fix: remove accidentally given nv roles from vegans 2025-01-15 20:26:49 +01:00
Anthony Berg
7918f73e7d feat: turn off the fixer for the roles reassignment 2025-01-15 20:06:22 +01:00
Anthony Berg
ea211a9111 feat: add fixer for when roles get recreated 2025-01-15 19:20:07 +01:00
Anthony Berg
32776a2311 feat: add log on Discord when bot has started
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
ESLint / Run eslint scanning (push) Waiting to run
Prettier / Run prettier scanning (push) Waiting to run
2025-01-15 17:42:54 +01:00
Anthony Berg
d72b66f988 refactor: update activist role to new snowflake 2025-01-15 16:47:48 +01:00
Anthony Berg
e03bd6e85e refactor: update roles to new snowflake 2025-01-15 16:22:31 +01:00
Anthony Berg
a400cf9507 refactor: update roles to new snowflake 2025-01-15 16:13:19 +01:00
Anthony Berg
2fbb6c9265 feat: update deps and breaking changes 2025-01-15 16:07:21 +01:00
Anthony Berg
fc8c12b346 Merge pull request #212 from veganhacktivists/coolify
Coolify support
2025-01-15 15:25:03 +01:00
Joaquín Triñanes
9ebf8a6938 Allow redis to use password auth 2024-10-25 10:58:10 +02:00
Joaquín Triñanes
bc7f2ffcfd Add nixpacks config 2024-10-25 10:30:56 +02:00
Joaquín Triñanes
86f391e131 Enable corepack 2024-10-25 10:23:20 +02:00
Anthony Berg
63c3b14b1c feat(interaction): update info for welcome message 2024-09-03 17:48:28 +02:00
Anthony Berg
a5187ec567 feat(utils): update info for verification message 2024-09-02 22:36:43 +02:00
Anthony Berg
222c3cb81a feat(utils): add non-vegan vc text channel to IDs 2024-09-02 22:36:29 +02:00
Anthony Berg
8f8580398e refactor(arabot): ran prettier 2024-09-02 22:24:24 +02:00
Anthony Berg
fe88e9f87b feat(arabot): add information that you can use apply command for verification 2024-09-02 22:24:12 +02:00
Anthony Berg
4ad35f5b57 fix(arabot): add type safety to deleting text channels 2024-08-26 23:11:30 +02:00
Anthony Berg
1c9f6612a3 build: update deps 2024-08-26 22:43:31 +02:00
Anthony Berg
88dd678bdc feat(arabot): make rules on trusted shorter and clearer 2024-08-11 02:03:30 +01:00
Anthony Berg
9c51be9ab6 feat(arabot): make the information message for trusted more blunt 2024-08-11 01:54:15 +01:00
Anthony Berg
128b15f18f feat(arabot): nerf autotruster to level 7 2024-08-11 01:48:03 +01:00
Anthony Berg
dba9aa970e refactor(arabot): run prettier 2024-08-07 01:39:21 +02:00
Anthony Berg
0ac0ff7f5c feat(arabot): add automatic trusted role at level 5 2024-08-07 01:39:02 +02:00
Anthony Berg
ae0afa02db feat(db): add checking previous warns/restrictions 2024-08-07 01:38:38 +02:00
Anthony Berg
3009a0f923 feat(arabot): add emitter when user levels up 2024-08-07 01:38:16 +02:00
Anthony Berg
a09b007831 refactor(db): move db functions to separate folders to aid distinguishing functions 2024-08-07 00:48:40 +02:00
Anthony Berg
325dc0d0d0 refactor(arabot): run prettier 2024-08-04 20:14:44 +02:00
Anthony Berg
71a065d3ca feat(arabot): mention that vegans can access extra channels if verified 2024-08-04 20:05:24 +02:00
Anthony Berg
613f53491b build: update pnpm deps 2024-08-04 20:04:40 +02:00
Anthony
19721c10ea build(arabot): update deps 2024-06-27 11:23:09 +02:00
Anthony Berg
bd87a8b6c6 ci: add tsconfig and pnpm-lock to .prettierignore 2024-03-12 21:37:52 +00:00
Anthony Berg
46ef2fd8e2 fix(arabot): adding sus note to user who left server 2024-03-12 21:35:19 +00:00
Anthony Berg
d8c91fd39b refactor: run prettier 2024-03-12 21:34:51 +00:00
Anthony Berg
fabd381051 deps: update packages 2024-03-12 21:34:18 +00:00
Anthony Berg
a5758dc6ef feat(arabot): add vegan check to plus command 2024-02-16 21:28:18 +00:00
Anthony Berg
9ff5b78aff feat(arabot): allow verifiers to use plus command 2024-02-15 19:57:51 +00:00
Anthony Berg
f4655829e2 feat(arabot): make vcs when creating groups in outreach 2024-02-13 21:36:41 +00:00
Anthony Berg
c82d256be4 refactor(arabot): change outreach command for outreach leader only 2024-02-13 19:31:49 +00:00
Anthony Berg
a9039572d1 build: update deps 2024-02-07 20:39:39 +00:00
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
renovate[bot]
3bf351e472 chore(deps): update postgres docker tag to v16 2023-11-11 14:40:43 +00:00
135 changed files with 5682 additions and 6415 deletions

View File

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

View File

@@ -3,16 +3,18 @@ DISCORD_TOKEN= # Bot token from: https://discord.com/developers/
# Configuration # Configuration
DEFAULT_PREFIX= # Prefix used to run commands in Discord DEFAULT_PREFIX= # Prefix used to run commands in Discord
DEVELOPMENT= # (true/false) Enables developer mode DEVELOPMENT= # (true/false) Enables developer mode
# Docker # Docker
POSTGRES_USER=USERNAME POSTGRES_USER=USERNAME
POSTGRES_PASSWORD=PASSWORD POSTGRES_PASSWORD=PASSWORD
POSTGRES_DB=DB POSTGRES_DB=DB
# Redis # Redis (if running everything within docker compose, use "redis" for the host and leave the rest empty)
REDIS_URL= # URL to redis database (if running everything within docker compose, use "redis") REDIS_HOST= # URL to redis database
BULLMQ_URL # URL for redis database, but without redis:// and credentials REDIS_USER= # redis database user
REDIS_PASSWORD= # redis database password
REDIS_PORT= # redis database port
# Database URL (designed for Postgres, but designed on Prisma) # Database URL (designed for Postgres, but designed on Prisma)
DATABASE_URL= # "postgresql://USERNAME:PASSWORD@postgres:5432/DB?schema=ara&sslmode=prefer" DATABASE_URL= # "postgresql://USERNAME:PASSWORD@postgres:5432/DB?schema=ara&sslmode=prefer"

View File

@@ -42,7 +42,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -55,7 +55,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell. # 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 # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -68,4 +68,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v3

View File

@@ -43,7 +43,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: Upload analysis results to GitHub - name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v2 uses: github/codeql-action/upload-sarif@v3
with: with:
sarif_file: eslint-results.sarif sarif_file: eslint-results.sarif
wait-for-processing: true wait-for-processing: true

3
.npmrc Normal file
View File

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

View File

@@ -1,2 +1,4 @@
dist dist
node_modules node_modules
tsconfig.json
pnpm-lock.yaml

View File

@@ -1,24 +1,26 @@
FROM node:20 FROM node:22 AS base
# PNPM
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /opt/app COPY . /app
WORKDIR /app
ENV NODE_ENV=production FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
COPY --chown=node:node package.json . FROM base AS build
COPY --chown=node:node package-lock.json . RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
COPY --chown=node:node tsconfig.json . RUN pnpm exec prisma generate
COPY --chown=node:node prisma ./prisma/ RUN pnpm run build
RUN npm install 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
COPY . . RUN chown node:node .
RUN npx prisma generate
RUN npm run build
RUN chown node:node /opt/app/
USER node USER node
CMD [ "pnpm", "run", "start:migrate"]
CMD [ "npm", "run", "start:migrate"]

View File

@@ -1,7 +1,7 @@
version: '3.7' version: '3.7'
services: services:
postgres: postgres:
image: postgres:15 image: postgres:16
container_name: postgres container_name: postgres
restart: always restart: always
env_file: env_file:

5
nixpacks.toml Normal file
View File

@@ -0,0 +1,5 @@
[phases.build]
cmds = ["pnpm prisma generate", "..."]
[start]
cmd = 'pnpm run start:migrate'

4436
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,10 +4,11 @@
"description": "A Discord bot for Animal Rights Advocates", "description": "A Discord bot for Animal Rights Advocates",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm",
"build": "tsc", "build": "tsc",
"cleanBuild": "rm -rf ./dist && tsc", "cleanBuild": "rm -rf ./dist && tsc",
"start": "node dist/index.js", "start": "node dist/index.js",
"start:migrate": "prisma migrate deploy && npm run start" "start:migrate": "prisma migrate deploy && pnpm run start"
}, },
"imports": { "imports": {
"#utils/*": "./dist/utils/*.js" "#utils/*": "./dist/utils/*.js"
@@ -28,31 +29,35 @@
"url": "https://github.com/veganhacktivists/arabot/issues" "url": "https://github.com/veganhacktivists/arabot/issues"
}, },
"homepage": "https://github.com/veganhacktivists/arabot#readme", "homepage": "https://github.com/veganhacktivists/arabot#readme",
"engines": {
"node": ">=20",
"pnpm": ">=9"
},
"dependencies": { "dependencies": {
"@prisma/client": "^5.5.2", "@prisma/client": "^6.2.1",
"@sapphire/discord.js-utilities": "^7.0.2", "@sapphire/discord.js-utilities": "^7.3.2",
"@sapphire/framework": "^4.7.2", "@sapphire/framework": "^5.3.2",
"@sapphire/plugin-logger": "^3.0.6", "@sapphire/plugin-logger": "^4.0.2",
"@sapphire/plugin-scheduled-tasks": "^8.0.0", "@sapphire/plugin-scheduled-tasks": "^10.0.2",
"@sapphire/plugin-subcommands": "^5.0.0", "@sapphire/plugin-subcommands": "^7.0.1",
"@sapphire/stopwatch": "^1.5.0", "@sapphire/stopwatch": "^1.5.4",
"@sapphire/time-utilities": "^1.7.10", "@sapphire/time-utilities": "^1.7.14",
"@sapphire/ts-config": "^5.0.0", "@sapphire/ts-config": "^5.0.1",
"@sapphire/utilities": "^3.13.0", "@sapphire/utilities": "^3.18.1",
"@types/node": "^20.8.9", "bullmq": "^5.34.10",
"bullmq": "^4.12.7", "discord.js": "^14.17.3",
"discord.js": "^14.13.0", "ioredis": "^5.4.2",
"redis": "^4.6.10", "ts-node": "^10.9.2",
"ts-node": "^10.9.1", "typescript": "~5.4.5"
"typescript": "^5.2.2"
}, },
"devDependencies": { "devDependencies": {
"@types/ioredis": "^5.0.0", "@types/node": "^22.10.7",
"@typescript-eslint/eslint-plugin": "^6.9.0", "@typescript-eslint/eslint-plugin": "^8.20.0",
"@typescript-eslint/parser": "^6.9.0", "@typescript-eslint/parser": "^8.20.0",
"eslint": "8.54.0", "eslint": "9.18.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^10.0.1",
"prettier": "3.1.0", "prettier": "3.4.2",
"prisma": "^5.5.2" "prisma": "^6.2.1"
} },
"packageManager": "pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228"
} }

1783
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,8 @@
/*
Warnings:
- Added the required column `channelId` to the `StatRole` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "StatRole" ADD COLUMN "channelId" TEXT NOT NULL;

View File

@@ -222,9 +222,10 @@ model Stat {
} }
model StatRole { model StatRole {
stat Stat @relation(fields: [statId], references: [id]) stat Stat @relation(fields: [statId], references: [id])
statId Int @id statId Int @id
roleId String roleId String
channelId String
} }
model ParticipantStat { model ParticipantStat {
@@ -256,7 +257,6 @@ model Warning {
mod User @relation("warnMod", fields: [modId], references: [id]) mod User @relation("warnMod", fields: [modId], references: [id])
modId String modId String
time DateTime @default(now()) time DateTime @default(now())
active Boolean @default(true)
note String note String
} }

View File

@@ -18,11 +18,13 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { ChannelType } from 'discord.js'; import { MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { isRole, isUser } from '#utils/typeChecking';
import { isTextChannel } from '@sapphire/discord.js-utilities';
export class AccessCommand extends Command { export class AccessCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'access', name: 'access',
@@ -79,7 +81,7 @@ export class AccessCommand extends Command {
if (!interaction.inCachedGuild()) { if (!interaction.inCachedGuild()) {
await interaction.reply({ await interaction.reply({
content: 'This command can only be run in a server!', content: 'This command can only be run in a server!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
@@ -91,32 +93,29 @@ export class AccessCommand extends Command {
const role = interaction.options.getRole('role'); const role = interaction.options.getRole('role');
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (user === null && role === null) { if (!isUser(user) && !isRole(role)) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching slash command data!', content: 'Error fetching slash command data!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
// If user and role is provided, the return an error // If user and role is provided, the return an error
if (user !== null && role !== null) { if (isUser(user) && isRole(role)) {
await interaction.reply({ await interaction.reply({
content: content:
'You have entered a user and a role at the same time! Please only enter one at a time.', 'You have entered a user and a role at the same time! Please only enter one at a time.',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
// Checks that the channel is a GuildText or GuildVoice, otherwise, return error // Checks that the channel is a GuildText or GuildVoice, otherwise, return error
if ( if (!isTextChannel(channel) && !channel.isVoiceBased()) {
channel.type !== ChannelType.GuildText &&
channel.type !== ChannelType.GuildVoice
) {
await interaction.reply({ await interaction.reply({
content: 'Please only select a text or voice channel!', content: 'Please only select a text or voice channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
@@ -129,7 +128,7 @@ export class AccessCommand extends Command {
) { ) {
await interaction.reply({ await interaction.reply({
content: 'Channel is not in ModMail/Private/Restricted category!', content: 'Channel is not in ModMail/Private/Restricted category!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
@@ -143,13 +142,13 @@ export class AccessCommand extends Command {
} else { } else {
await interaction.reply({ await interaction.reply({
content: 'Could not find the role to edit permissions!', content: 'Could not find the role to edit permissions!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
// Set permissions of voice channel // Set permissions of voice channel
if (channel.type === ChannelType.GuildVoice) { if (channel.isVoiceBased()) {
switch (permission) { switch (permission) {
case 'add': case 'add':
await channel.permissionOverwrites.create(permId, { await channel.permissionOverwrites.create(permId, {
@@ -185,7 +184,7 @@ export class AccessCommand extends Command {
default: default:
await interaction.reply({ await interaction.reply({
content: 'Incorrect permission option!', content: 'Incorrect permission option!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
@@ -220,7 +219,7 @@ export class AccessCommand extends Command {
default: default:
await interaction.reply({ await interaction.reply({
content: 'Incorrect permission option!', content: 'Incorrect permission option!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }

View File

@@ -18,11 +18,12 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Message } from 'discord.js'; import { Message, MessageFlagsBitField } from 'discord.js';
import { ChannelType, TextChannel } from 'discord.js'; import { ChannelType, TextChannel } from 'discord.js';
import { isTextChannel } from '@sapphire/discord.js-utilities';
export class AnonymousCommand extends Command { export class AnonymousCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'anonymous', name: 'anonymous',
@@ -67,18 +68,27 @@ export class AnonymousCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
if (channel === null) { if (channel === null) {
if (interaction.channel === null) { if (!isTextChannel(interaction.channel)) {
await interaction.reply({ await interaction.reply({
content: 'Error getting the channel!', content: 'Error getting the channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
});
return;
}
if (!interaction.channel.isSendable()) {
await interaction.reply({
content: `I do not have sufficient permissions to send a message in ${interaction.channel}!`,
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
}); });
return; return;
} }
@@ -86,8 +96,8 @@ export class AnonymousCommand extends Command {
await interaction.channel.send(message); await interaction.channel.send(message);
await interaction.reply({ await interaction.reply({
content: 'Sent the message', content: 'Sent the message',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -95,8 +105,8 @@ export class AnonymousCommand extends Command {
if (channel.type !== ChannelType.GuildText) { if (channel.type !== ChannelType.GuildText) {
await interaction.reply({ await interaction.reply({
content: 'Could not send, unsupported text channel!', content: 'Could not send, unsupported text channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
} }
@@ -105,8 +115,8 @@ export class AnonymousCommand extends Command {
await interaction.reply({ await interaction.reply({
content: 'Sent the message', content: 'Sent the message',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
} }
@@ -121,7 +131,7 @@ export class AnonymousCommand extends Command {
return; return;
} }
if (channel.isTextBased()) { if (channel.isSendable()) {
await channel.send(text); await channel.send(text);
} else { } else {
await message.react('❌'); await message.react('❌');

View File

@@ -18,10 +18,11 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Message } from 'discord.js'; import { Message, MessageFlagsBitField } from 'discord.js';
import { isGuildBasedChannel } from '@sapphire/discord.js-utilities';
export class ClearCommand extends Command { export class ClearCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'clear', name: 'clear',
@@ -57,11 +58,11 @@ export class ClearCommand extends Command {
const messages = interaction.options.getInteger('messages', true); const messages = interaction.options.getInteger('messages', true);
const { channel } = interaction; const { channel } = interaction;
if (channel === null || channel.isDMBased()) { if (!isGuildBasedChannel(channel)) {
await interaction.reply({ await interaction.reply({
content: 'Could not fetch channel!', content: 'Could not fetch channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -70,8 +71,8 @@ export class ClearCommand extends Command {
await interaction.reply({ await interaction.reply({
content: `Successfully deleted ${messages} messages!`, content: `Successfully deleted ${messages} messages!`,
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
} }
@@ -92,7 +93,7 @@ export class ClearCommand extends Command {
const { channel } = message; const { channel } = message;
if (!channel.isTextBased() || channel.isDMBased()) { if (!isGuildBasedChannel(channel)) {
await message.react('❌'); await message.react('❌');
await message.reply('Unsupported channel type!'); await message.reply('Unsupported channel type!');
return; return;

View File

@@ -19,9 +19,8 @@
import { RegisterBehavior } from '@sapphire/framework'; import { RegisterBehavior } from '@sapphire/framework';
import { Subcommand } from '@sapphire/plugin-subcommands'; import { Subcommand } from '@sapphire/plugin-subcommands';
import type { Guild, TextChannel, Snowflake } from 'discord.js'; import { TextChannel, Snowflake, MessageFlagsBitField } from 'discord.js';
import { import {
CategoryChannel,
ChannelType, ChannelType,
EmbedBuilder, EmbedBuilder,
GuildMember, GuildMember,
@@ -29,9 +28,25 @@ import {
time, time,
} from 'discord.js'; } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import {
isCategoryChannel,
isGuildBasedChannel,
isGuildMember,
isTextChannel,
isVoiceChannel,
} from '@sapphire/discord.js-utilities';
import {
getCategoryChannel,
getGuildMember,
getVoiceChannel,
} from '#utils/fetcher';
import { isUser } from '#utils/typeChecking';
export class PrivateCommand extends Subcommand { export class PrivateCommand extends Subcommand {
public constructor(context: Subcommand.Context, options: Subcommand.Options) { public constructor(
context: Subcommand.LoaderContext,
options: Subcommand.Options,
) {
super(context, { super(context, {
...options, ...options,
name: 'private', name: 'private',
@@ -90,7 +105,9 @@ export class PrivateCommand extends Subcommand {
const modUser = interaction.user; const modUser = interaction.user;
const { guild } = interaction; const { guild } = interaction;
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (guild === null) { if (guild === null) {
@@ -100,11 +117,11 @@ export class PrivateCommand extends Subcommand {
return; return;
} }
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const mod = guild.members.cache.get(modUser.id); const mod = await getGuildMember(modUser.id, guild);
// Checks if guildMember is null // Checks if guildMember is null
if (member === undefined || mod === undefined) { if (!isGuildMember(member) || !isGuildMember(mod)) {
await interaction.editReply({ await interaction.editReply({
content: 'Error fetching users!', content: 'Error fetching users!',
}); });
@@ -113,7 +130,7 @@ export class PrivateCommand extends Subcommand {
const [name, coordinator] = this.getCoordinator(mod); const [name, coordinator] = this.getCoordinator(mod);
if (this.checkPrivate(member.id, coordinator, guild)) { if (await this.checkPrivate(member.id, coordinator)) {
await interaction.editReply({ await interaction.editReply({
content: 'A private channel already exists!', content: 'A private channel already exists!',
}); });
@@ -232,20 +249,22 @@ export class PrivateCommand extends Subcommand {
const modUser = interaction.user; const modUser = interaction.user;
const { guild, channel } = interaction; const { guild, channel } = interaction;
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (guild === null || channel === null) { if (guild === null || !isGuildBasedChannel(channel)) {
await interaction.editReply({ await interaction.editReply({
content: 'Error fetching user!', content: 'Error fetching user!',
}); });
return; return;
} }
const mod = guild.members.cache.get(modUser.id); const mod = await getGuildMember(modUser.id, guild);
// Checks if guildMember is null // Checks if guildMember is null
if (mod === undefined) { if (!isGuildMember(mod)) {
await interaction.editReply({ await interaction.editReply({
content: 'Error fetching users!', content: 'Error fetching users!',
}); });
@@ -256,8 +275,8 @@ export class PrivateCommand extends Subcommand {
const coordinator = coordinatorInfo[1]; const coordinator = coordinatorInfo[1];
let topic: string[]; let topic: string[];
if (user === null) { if (!isUser(user)) {
if (channel.type !== ChannelType.GuildText) { if (!isTextChannel(channel)) {
await interaction.editReply({ await interaction.editReply({
content: content:
'Please make sure you ran this command in the original private text channel!', 'Please make sure you ran this command in the original private text channel!',
@@ -284,10 +303,10 @@ export class PrivateCommand extends Subcommand {
await channel.delete(); await channel.delete();
const vcId = topic[topic.indexOf(coordinator) + 1]; const vcId = topic[topic.indexOf(coordinator) + 1];
const voiceChannel = guild.channels.cache.get(vcId); const voiceChannel = await getVoiceChannel(vcId);
if ( if (
voiceChannel !== undefined && isVoiceChannel(voiceChannel) &&
voiceChannel.parentId === IDs.categories.private voiceChannel.parentId === IDs.categories.private
) { ) {
await voiceChannel.delete(); await voiceChannel.delete();
@@ -295,9 +314,7 @@ export class PrivateCommand extends Subcommand {
return; return;
} }
const category = guild.channels.cache.get(IDs.categories.private) as const category = await getCategoryChannel(IDs.categories.private);
| CategoryChannel
| undefined;
if (category === undefined) { if (category === undefined) {
await interaction.editReply({ await interaction.editReply({
@@ -306,26 +323,32 @@ export class PrivateCommand extends Subcommand {
return; return;
} }
const textChannels = category.children.cache.filter( const textChannels = category.children.cache.filter((channel) =>
(c) => c.type === ChannelType.GuildText, isTextChannel(channel),
); );
textChannels.forEach((c) => {
const textChannel = c as TextChannel; for (const c of textChannels) {
const channel = c[1];
if (!isTextChannel(channel)) {
continue;
}
// Checks if the channel topic has the user's snowflake // Checks if the channel topic has the user's snowflake
if (textChannel.topic?.includes(user?.id)) { if (channel.topic !== null && channel.topic.includes(user.id)) {
topic = textChannel.topic.split(' '); topic = channel.topic.split(' ');
const vcId = topic[topic.indexOf(coordinator) + 1]; const vcId = topic[topic.indexOf(coordinator) + 1];
const voiceChannel = guild.channels.cache.get(vcId); const voiceChannel = await getVoiceChannel(vcId);
if ( if (
voiceChannel !== undefined && isVoiceChannel(voiceChannel) &&
voiceChannel.parentId === IDs.categories.private voiceChannel.parentId === IDs.categories.private
) { ) {
voiceChannel.delete(); await voiceChannel.delete();
} }
textChannel.delete(); await channel.delete();
} }
}); }
await interaction.editReply({ await interaction.editReply({
content: `Successfully deleted the channel for ${user}`, content: `Successfully deleted the channel for ${user}`,
@@ -353,6 +376,12 @@ export class PrivateCommand extends Subcommand {
} else if (user.roles.cache.has(IDs.roles.staff.eventCoordinator)) { } else if (user.roles.cache.has(IDs.roles.staff.eventCoordinator)) {
name = 'event'; name = 'event';
id = IDs.roles.staff.eventCoordinator; 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 { } else {
name = 'coordinator'; name = 'coordinator';
id = IDs.roles.staff.coordinator; id = IDs.roles.staff.coordinator;
@@ -360,29 +389,35 @@ export class PrivateCommand extends Subcommand {
return [name, id]; return [name, id];
} }
private checkPrivate(user: Snowflake, coordinator: string, guild: Guild) { private async checkPrivate(user: Snowflake, coordinator: string) {
const category = guild.channels.cache.get(IDs.categories.private) as const category = await getCategoryChannel(IDs.categories.private);
| CategoryChannel
| undefined;
if (category === undefined) { if (!isCategoryChannel(category)) {
return true; return true;
} }
const textChannels = category.children.cache.filter( const textChannels = category.children.cache.filter(
(c) => c.type === ChannelType.GuildText, (c) => c.type === ChannelType.GuildText,
); );
let exists = false; let exists = false;
textChannels.forEach((c) => {
const textChannel = c as TextChannel; for (const c of textChannels) {
const channel = c[1];
if (!isTextChannel(channel)) {
continue;
}
// Checks if the channel topic has the user's snowflake // Checks if the channel topic has the user's snowflake
if ( if (
textChannel.topic?.includes(user) && channel.topic !== null &&
textChannel.topic?.includes(coordinator) channel.topic.includes(user) &&
channel.topic.includes(coordinator)
) { ) {
exists = true; exists = true;
} }
}); }
return exists; return exists;
} }
} }

View File

@@ -18,13 +18,15 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Guild, Message } from 'discord.js'; import { User, Guild, Message, MessageFlagsBitField } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser'; import { updateUser } from '#utils/database/dbExistingUser';
import { getBalance } from '#utils/database/economy'; import { getBalance } from '#utils/database/fun/economy';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder } from 'discord.js';
import { getGuildMember } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class BalanceCommand extends Command { export class BalanceCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'balance', name: 'balance',
@@ -50,7 +52,7 @@ export class BalanceCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Could not find the guild!', content: 'Could not find the guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
@@ -66,15 +68,9 @@ export class BalanceCommand extends Command {
} }
public async messageRun(message: Message) { public async messageRun(message: Message) {
const user = message.member?.user; const user = message.author;
const { guild } = message; const { guild } = message;
if (user === undefined) {
await message.react('❌');
await message.reply('Could not find your user!');
return;
}
if (guild === null) { if (guild === null) {
await message.react('❌'); await message.react('❌');
await message.reply('Could not find the guild!'); await message.reply('Could not find the guild!');
@@ -99,9 +95,9 @@ export class BalanceCommand extends Command {
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Could not find your guild member!'; info.message = 'Could not find your guild member!';
return info; return info;
} }

View File

@@ -19,14 +19,22 @@
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { Time } from '@sapphire/time-utilities'; import { Time } from '@sapphire/time-utilities';
import type { User, Guild, GuildMember, Message } from 'discord.js'; import {
User,
Guild,
GuildMember,
Message,
MessageFlagsBitField,
} from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser'; import { updateUser } from '#utils/database/dbExistingUser';
import { daily, getLastDaily } from '#utils/database/economy'; import { daily, getLastDaily } from '#utils/database/fun/economy';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getGuildMember } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class DailyCommand extends Command { export class DailyCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'daily', name: 'daily',
@@ -51,7 +59,7 @@ export class DailyCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Could not find the guild!', content: 'Could not find the guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
@@ -67,15 +75,9 @@ export class DailyCommand extends Command {
} }
public async messageRun(message: Message) { public async messageRun(message: Message) {
const user = message.member?.user; const user = message.author;
const { guild } = message; const { guild } = message;
if (user === undefined) {
await message.react('❌');
await message.reply('Could not find your user!');
return;
}
if (guild === null) { if (guild === null) {
await message.react('❌'); await message.react('❌');
await message.reply('Could not find the guild!'); await message.reply('Could not find the guild!');
@@ -114,9 +116,9 @@ export class DailyCommand extends Command {
return info; return info;
} }
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Could not find your guild member!'; info.message = 'Could not find your guild member!';
return info; return info;
} }

View File

@@ -18,14 +18,16 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Guild, Message } from 'discord.js'; import { User, Guild, Message, MessageFlagsBitField } from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser'; import { updateUser } from '#utils/database/dbExistingUser';
import { getBalance, transfer } from '#utils/database/economy'; import { getBalance, transfer } from '#utils/database/fun/economy';
import { EmbedBuilder, TextChannel } from 'discord.js'; import { EmbedBuilder } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getGuildMember, getTextBasedChannel } from '#utils/fetcher';
import { isGuildMember, isTextChannel } from '@sapphire/discord.js-utilities';
export class BalanceCommand extends Command { export class BalanceCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'pay', name: 'pay',
@@ -75,7 +77,7 @@ export class BalanceCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Could not find the guild!', content: 'Could not find the guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
@@ -117,15 +119,9 @@ export class BalanceCommand extends Command {
return; return;
} }
const user = message.member?.user; const user = message.author;
const { guild } = message; const { guild } = message;
if (user === undefined) {
await message.react('❌');
await message.reply('Could not find your user!');
return;
}
if (guild === null) { if (guild === null) {
await message.react('❌'); await message.react('❌');
await message.reply('Could not find the guild!'); await message.reply('Could not find the guild!');
@@ -164,15 +160,15 @@ export class BalanceCommand extends Command {
return info; return info;
} }
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const recipientMember = guild.members.cache.get(recipient.id); const recipientMember = await getGuildMember(recipient.id, guild);
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Could not find your guild member!'; info.message = 'Could not find your guild member!';
return info; return info;
} }
if (recipientMember === undefined) { if (!isGuildMember(recipientMember)) {
info.message = 'Could not find the user!'; info.message = 'Could not find the user!';
return info; return info;
} }
@@ -206,18 +202,16 @@ export class BalanceCommand extends Command {
info.embeds.push(embed); info.embeds.push(embed);
// Log the payment in the server // Log the payment in the server
let logChannel = guild.channels.cache.get(IDs.channels.logs.economy) as const logChannel = await getTextBasedChannel(IDs.channels.logs.economy);
| TextChannel
| undefined;
if (logChannel === undefined) { if (!isTextChannel(logChannel)) {
logChannel = (await guild.channels.fetch(IDs.channels.logs.economy)) as this.container.logger.error('Pay: Could not fetch log channel');
| TextChannel return info;
| undefined; } else if (!logChannel.isSendable()) {
if (logChannel === undefined) { this.container.logger.error(
this.container.logger.error('Pay Error: Could not fetch log channel'); 'Pay: the bot does not have permission to send in the log channel',
return info; );
} return info;
} }
const logEmbed = new EmbedBuilder(embed.data); const logEmbed = new EmbedBuilder(embed.data);

View File

@@ -18,17 +18,18 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder, MessageFlagsBitField } from 'discord.js';
import { N1984 } from '#utils/gifs'; import { N1984 } from '#utils/gifs';
import { addFunLog, countTotal } from '#utils/database/fun'; import { addFunLog, countTotal } from '#utils/database/fun/fun';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class N1984Command extends Command { export class N1984Command extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: '1984', name: '1984',
description: 'this is literally 1984', description: 'this is literally 1984',
preconditions: ['ModOnly'], preconditions: [['CoordinatorOnly', 'ModOnly']],
}); });
} }
@@ -45,10 +46,26 @@ export class N1984Command extends Command {
// Command run // Command run
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the user // Get the user
const { user } = interaction; const { member } = interaction;
await addFunLog(user.id, '1984'); // Type checks
const count = await countTotal(user.id, '1984'); if (!isGuildMember(member)) {
await interaction.reply({
content: 'Failed to fetch your user on the bot!',
flags: MessageFlagsBitField.Flags.Ephemeral,
});
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 // Creates the embed for the 1984 reaction
// Add a 1 in 1000 chance of Dantas literally making ARA 1984 // Add a 1 in 1000 chance of Dantas literally making ARA 1984
@@ -58,11 +75,11 @@ export class N1984Command extends Command {
: N1984[Math.floor(Math.random() * N1984.length)]; : N1984[Math.floor(Math.random() * N1984.length)];
const n1984Embed = new EmbedBuilder() const n1984Embed = new EmbedBuilder()
.setColor('#ffffff') .setColor('#ffffff')
.setTitle(`${user.username} is happy!`) .setTitle(`${member.displayName} is happy!`)
.setImage(random1984) .setImage(random1984)
.setFooter({ text: `${user.username}'s 1984 count: ${count}` }); .setFooter({ text: embedFooter });
// Send the embed // Send the embed
await interaction.reply({ embeds: [n1984Embed], fetchReply: true }); await interaction.reply({ embeds: [n1984Embed], withResponse: true });
} }
} }

View File

@@ -18,12 +18,13 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder, MessageFlagsBitField } from 'discord.js';
import { Cringe } from '#utils/gifs'; import { Cringe } from '#utils/gifs';
import { addFunLog, countTotal } from '#utils/database/fun'; import { addFunLog, countTotal } from '#utils/database/fun/fun';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class CringeCommand extends Command { export class CringeCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'cringe', name: 'cringe',
@@ -44,21 +45,36 @@ export class CringeCommand extends Command {
// Command run // Command run
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the user // Get the user
// TODO exception handling const { member } = interaction;
const { user } = interaction;
await addFunLog(user.id, 'cringe'); // Type check
const count = await countTotal(user.id, 'cringe'); if (!isGuildMember(member)) {
await interaction.reply({
content: 'Failed to fetch your user on the bot!',
flags: MessageFlagsBitField.Flags.Ephemeral,
});
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 // Creates the embed for the cringe reaction
const randomCringe = Cringe[Math.floor(Math.random() * Cringe.length)]; const randomCringe = Cringe[Math.floor(Math.random() * Cringe.length)];
const cringeEmbed = new EmbedBuilder() const cringeEmbed = new EmbedBuilder()
.setColor('#001148') .setColor('#001148')
.setTitle(`${user.username} feels immense cringe...`) .setTitle(`${member.displayName} feels immense cringe...`)
.setImage(randomCringe) .setImage(randomCringe)
.setFooter({ text: `${user.username}'s cringe count: ${count}` }); .setFooter({ text: embedFooter });
// Send the embed // Send the embed
await interaction.reply({ embeds: [cringeEmbed], fetchReply: true }); await interaction.reply({ embeds: [cringeEmbed], withResponse: true });
} }
} }

View File

@@ -18,16 +18,16 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder, MessageFlagsBitField } from 'discord.js';
import { Happy } from '#utils/gifs'; import { Happy } from '#utils/gifs';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class HappyCommand extends Command { export class HappyCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'happy', name: 'happy',
description: 'Express your happiness', description: 'Express your happiness',
preconditions: [['CoordinatorOnly', 'PatreonOnly']],
}); });
} }
@@ -44,18 +44,25 @@ export class HappyCommand extends Command {
// Command run // Command run
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the user // Get the user
// TODO exception handling const { member } = interaction;
const member = interaction.member!.user;
const memberGuildMember = interaction.guild!.members.cache.get(member.id)!; // Type checks
if (!isGuildMember(member)) {
await interaction.reply({
content: 'Failed to fetch your user on the bot!',
flags: MessageFlagsBitField.Flags.Ephemeral,
});
return;
}
// Creates the embed for the happy reaction // Creates the embed for the happy reaction
const randomHappy = Happy[Math.floor(Math.random() * Happy.length)]; const randomHappy = Happy[Math.floor(Math.random() * Happy.length)];
const happyEmbed = new EmbedBuilder() const happyEmbed = new EmbedBuilder()
.setColor('#40ff00') .setColor('#40ff00')
.setTitle(`${memberGuildMember.displayName} is happy!`) .setTitle(`${member.displayName} is happy!`)
.setImage(randomHappy); .setImage(randomHappy);
// Send the embed // Send the embed
await interaction.reply({ embeds: [happyEmbed], fetchReply: true }); await interaction.reply({ embeds: [happyEmbed], withResponse: true });
} }
} }

View File

@@ -18,12 +18,13 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder, MessageFlagsBitField } from 'discord.js';
import { Hugs } from '#utils/gifs'; import { Hugs } from '#utils/gifs';
import { addFunLog, countTotal } from '#utils/database/fun'; import { addFunLog, countTotal } from '#utils/database/fun/fun';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class HugCommand extends Command { export class HugCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'hug', name: 'hug',
@@ -54,26 +55,49 @@ export class HugCommand extends Command {
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the users // Get the users
const user = interaction.options.getUser('user', true); const user = interaction.options.getUser('user', true);
const hugger = interaction.user; const hugger = interaction.member;
// Type Checks
if (!isGuildMember(hugger)) {
await interaction.reply({
content: 'Failed to fetch your user on the bot!',
flags: MessageFlagsBitField.Flags.Ephemeral,
});
return;
}
await addFunLog(hugger.id, 'hug', user.id); await addFunLog(hugger.id, 'hug', user.id);
const count = await countTotal(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 // Creates the embed for the hug
const randomHug = Hugs[Math.floor(Math.random() * Hugs.length)]; const randomHug = Hugs[Math.floor(Math.random() * Hugs.length)];
const hugEmbed = new EmbedBuilder() const hugEmbed = new EmbedBuilder()
.setColor('#0099ff') .setColor('#0099ff')
.setTitle(`Hug from ${hugger.username}`) .setTitle(`Hug from ${hugger.displayName}`)
.setImage(randomHug) .setImage(randomHug)
.setFooter({ .setFooter({ text: embedFooter });
text: `Amount of hugs given from ${hugger.username} to you: ${count}`,
});
// Send the hug // Send the hug
await interaction.reply({ await interaction.reply({
content: `${user}`, content: `${user}`,
embeds: [hugEmbed], embeds: [hugEmbed],
fetchReply: true, withResponse: true,
}); });
} }
} }

View File

@@ -18,12 +18,13 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder, MessageFlagsBitField } from 'discord.js';
import { Kill } from '#utils/gifs'; import { Kill } from '#utils/gifs';
import { addFunLog, countTotal } from '#utils/database/fun'; import { addFunLog, countTotal } from '#utils/database/fun/fun';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class KillCommand extends Command { export class KillCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'kill', name: 'kill',
@@ -54,7 +55,16 @@ export class KillCommand extends Command {
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the users // Get the users
const user = interaction.options.getUser('user', true)!; const user = interaction.options.getUser('user', true)!;
const sender = interaction.user; const sender = interaction.member;
// Type checks
if (!isGuildMember(sender)) {
await interaction.reply({
content: 'Failed to fetch your user on the bot!',
flags: MessageFlagsBitField.Flags.Ephemeral,
});
return;
}
if (user.id === sender.id) { if (user.id === sender.id) {
await interaction.reply('You changed your mind'); await interaction.reply('You changed your mind');
@@ -64,21 +74,26 @@ export class KillCommand extends Command {
await addFunLog(sender.id, 'kill', user.id); await addFunLog(sender.id, 'kill', user.id);
const count = await countTotal(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 // Creates the embed for the kill
const randomKill = Kill[Math.floor(Math.random() * Kill.length)]; const randomKill = Kill[Math.floor(Math.random() * Kill.length)];
const killEmbed = new EmbedBuilder() const killEmbed = new EmbedBuilder()
.setColor('#ff0000') .setColor('#ff0000')
.setTitle(`Kill from ${sender.username}`) .setTitle(`Kill from ${sender.displayName}`)
.setImage(randomKill) .setImage(randomKill)
.setFooter({ .setFooter({ text: embedFooter });
text: `Amount of kills from ${sender.username} to you: ${count}`,
});
// Send the kill // Send the kill
await interaction.reply({ await interaction.reply({
content: `${user}`, content: `${user}`,
embeds: [killEmbed], embeds: [killEmbed],
fetchReply: true, withResponse: true,
}); });
} }
} }

View File

@@ -18,12 +18,13 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder, MessageFlagsBitField } from 'discord.js';
import { Poke } from '#utils/gifs'; import { Poke } from '#utils/gifs';
import { addFunLog, countTotal } from '#utils/database/fun'; import { addFunLog, countTotal } from '#utils/database/fun/fun';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class PokeCommand extends Command { export class PokeCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'poke', name: 'poke',
@@ -54,26 +55,48 @@ export class PokeCommand extends Command {
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the users // Get the users
const user = interaction.options.getUser('user', true)!; const user = interaction.options.getUser('user', true)!;
const sender = interaction.user; const sender = interaction.member;
// Type checks
if (!isGuildMember(sender)) {
await interaction.reply({
content: 'Failed to fetch your user on the bot!',
flags: MessageFlagsBitField.Flags.Ephemeral,
});
return;
}
await addFunLog(sender.id, 'poke', user.id); await addFunLog(sender.id, 'poke', user.id);
const count = await countTotal(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 // Creates the embed for the poke
const randomPoke = Poke[Math.floor(Math.random() * Poke.length)]; const randomPoke = Poke[Math.floor(Math.random() * Poke.length)];
const pokeEmbed = new EmbedBuilder() const pokeEmbed = new EmbedBuilder()
.setColor('#0099ff') .setColor('#0099ff')
.setTitle(`Poke from ${sender.username}`) .setTitle(`Poke from ${sender.displayName}`)
.setImage(randomPoke) .setImage(randomPoke)
.setFooter({ .setFooter({ text: embedFooter });
text: `Amount of pokes from ${sender.username} to you: ${count}`,
});
// Send the poke // Send the poke
await interaction.reply({ await interaction.reply({
content: `${user}`, content: `${user}`,
embeds: [pokeEmbed], embeds: [pokeEmbed],
fetchReply: true, withResponse: true,
}); });
} }
} }

View File

@@ -18,16 +18,16 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder, MessageFlagsBitField } from 'discord.js';
import { Sad } from '#utils/gifs'; import { Sad } from '#utils/gifs';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class SadCommand extends Command { export class SadCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'sad', name: 'sad',
description: 'Express your sadness', description: 'Express your sadness',
preconditions: [['CoordinatorOnly', 'PatreonOnly']],
}); });
} }
@@ -44,18 +44,25 @@ export class SadCommand extends Command {
// Command run // Command run
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the user // Get the user
// TODO exception handling const { member } = interaction;
const member = interaction.member!.user;
const memberGuildMember = interaction.guild!.members.cache.get(member.id)!; // Type checks
if (!isGuildMember(member)) {
await interaction.reply({
content: 'Failed to fetch your user on the bot!',
flags: MessageFlagsBitField.Flags.Ephemeral,
});
return;
}
// Creates the embed for the sad reaction // Creates the embed for the sad reaction
const randomSad = Sad[Math.floor(Math.random() * Sad.length)]; const randomSad = Sad[Math.floor(Math.random() * Sad.length)];
const sadEmbed = new EmbedBuilder() const sadEmbed = new EmbedBuilder()
.setColor('#001148') .setColor('#001148')
.setTitle(`${memberGuildMember.displayName} is sad...`) .setTitle(`${member.displayName} is sad...`)
.setImage(randomSad); .setImage(randomSad);
// Send the embed // Send the embed
await interaction.reply({ embeds: [sadEmbed], fetchReply: true }); await interaction.reply({ embeds: [sadEmbed], withResponse: true });
} }
} }

View File

@@ -18,16 +18,16 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder, MessageFlagsBitField } from 'discord.js';
import { Shrug } from '#utils/gifs'; import { Shrug } from '#utils/gifs';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class ShrugCommand extends Command { export class ShrugCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'shrug', name: 'shrug',
description: 'Ugh... whatever... idk...', description: 'Ugh... whatever... idk...',
preconditions: [['CoordinatorOnly', 'PatreonOnly']],
}); });
} }
@@ -44,18 +44,25 @@ export class ShrugCommand extends Command {
// Command run // Command run
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the user // Get the user
// TODO exception handling const { member } = interaction;
const member = interaction.member!.user;
const memberGuildMember = interaction.guild!.members.cache.get(member.id)!; // Type checks
if (!isGuildMember(member)) {
await interaction.reply({
content: 'Failed to fetch your user on the bot!',
flags: MessageFlagsBitField.Flags.Ephemeral,
});
return;
}
// Creates the embed for the shrug reaction // Creates the embed for the shrug reaction
const randomShrug = Shrug[Math.floor(Math.random() * Shrug.length)]; const randomShrug = Shrug[Math.floor(Math.random() * Shrug.length)];
const shrugEmbed = new EmbedBuilder() const shrugEmbed = new EmbedBuilder()
.setColor('#001980') .setColor('#001980')
.setTitle(`${memberGuildMember.displayName} shrugs`) .setTitle(`${member.displayName} shrugs`)
.setImage(randomShrug); .setImage(randomShrug);
// Send the embed // Send the embed
await interaction.reply({ embeds: [shrugEmbed], fetchReply: true }); await interaction.reply({ embeds: [shrugEmbed], withResponse: true });
} }
} }

View File

@@ -18,15 +18,30 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Message, Snowflake, TextChannel, Guild } from 'discord.js'; import {
User,
Message,
Snowflake,
Guild,
MessageFlagsBitField,
} from 'discord.js';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { addBan, checkBan } from '#utils/database/ban'; import { addBan, checkBan } from '#utils/database/moderation/ban';
import { addEmptyUser, updateUser } from '#utils/database/dbExistingUser'; import { addEmptyUser, updateUser } from '#utils/database/dbExistingUser';
import { checkTempBan, removeTempBan } from '#utils/database/tempBan'; import {
checkTempBan,
removeTempBan,
} from '#utils/database/moderation/tempBan';
import { getGuildMember, getTextBasedChannel, getUser } from '#utils/fetcher';
import { isUser } from '#utils/typeChecking';
import {
isGuildMember,
isTextBasedChannel,
} from '@sapphire/discord.js-utilities';
export class BanCommand extends Command { export class BanCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'ban', name: 'ban',
@@ -72,13 +87,15 @@ export class BanCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const ban = await this.ban(user.id, mod.id, reason, guild); const ban = await this.ban(user.id, mod.id, reason, guild);
@@ -139,17 +156,19 @@ export class BanCommand extends Command {
success: false, success: false,
}; };
let user = guild.client.users.cache.get(userId); const user = await getUser(userId);
if (user === undefined) { if (!isUser(user)) {
user = (await guild.client.users.fetch(userId)) as User; info.message =
'The user does not exist! (The user provided is probably wrong, or their account has been deleted.)';
return info;
} }
// Gets mod's GuildMember // Gets mod's GuildMember
const mod = guild.members.cache.get(modId); const mod = await getGuildMember(modId, guild);
// Checks if guildMember is null // Checks if guildMember is null
if (mod === undefined) { if (!isGuildMember(mod)) {
info.message = 'Error fetching mod!'; info.message = 'Error fetching mod!';
return info; return info;
} }
@@ -163,13 +182,9 @@ export class BanCommand extends Command {
await updateUser(mod); await updateUser(mod);
// Gets guildMember // Gets guildMember
let member = guild.members.cache.get(userId); const member = await getGuildMember(userId, guild);
if (member === undefined) { if (isGuildMember(member)) {
member = await guild.members.fetch(userId).catch(() => undefined);
}
if (member !== undefined) {
// Checks if the user is not restricted // Checks if the user is not restricted
if (member.roles.cache.has(IDs.roles.vegan.vegan)) { if (member.roles.cache.has(IDs.roles.vegan.vegan)) {
info.message = 'You need to restrict the user first!'; info.message = 'You need to restrict the user first!';
@@ -203,19 +218,20 @@ export class BanCommand extends Command {
info.success = true; info.success = true;
// Log the ban // Log the ban
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as const logChannel = await getTextBasedChannel(IDs.channels.logs.restricted);
| TextChannel
| undefined;
if (logChannel === undefined) { if (!isTextBasedChannel(logChannel)) {
logChannel = (await guild.channels.fetch( this.container.logger.error('Ban: Could not fetch log channel');
IDs.channels.logs.restricted, info.message = `${user} has been banned. This hasn't been logged in a text channel as log channel could not be found`;
)) as TextChannel | undefined;
if (logChannel === undefined) { return info;
this.container.logger.error('Ban Error: Could not fetch log channel'); } else if (!logChannel.isSendable()) {
info.message = `${user} has been banned. This hasn't been logged in a text channel as log channel could not be found`; this.container.logger.error(
return info; 'Ban: The bot does not have permission to send in the logs channel!',
} );
info.message = `${user} has been banned. This hasn't been logged in a text channel as the bot does not have permission to send logs!`;
return info;
} }
const log = new EmbedBuilder() const log = new EmbedBuilder()

View File

@@ -19,14 +19,20 @@
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import { Duration, DurationFormatter } from '@sapphire/time-utilities'; import { Duration, DurationFormatter } from '@sapphire/time-utilities';
import type { User, Snowflake, TextChannel, Guild } from 'discord.js'; import { User, Snowflake, Guild, MessageFlagsBitField } from 'discord.js';
import { EmbedBuilder, Message } from 'discord.js'; import { EmbedBuilder, Message } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { addTempBan, checkTempBan } from '#utils/database/tempBan'; import { addTempBan, checkTempBan } from '#utils/database/moderation/tempBan';
import { addEmptyUser, updateUser } from '#utils/database/dbExistingUser'; import { addEmptyUser, updateUser } from '#utils/database/dbExistingUser';
import { getGuildMember, getTextBasedChannel, getUser } from '#utils/fetcher';
import { isUser } from '#utils/typeChecking';
import {
isGuildMember,
isTextBasedChannel,
} from '@sapphire/discord.js-utilities';
export class TempBanCommand extends Command { export class TempBanCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'tempban', name: 'tempban',
@@ -79,8 +85,8 @@ export class TempBanCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -94,7 +100,9 @@ export class TempBanCommand extends Command {
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const ban = await this.ban(user.id, mod.id, time, reason, guild); const ban = await this.ban(user.id, mod.id, time, reason, guild);
@@ -201,17 +209,19 @@ export class TempBanCommand extends Command {
const banLength = new DurationFormatter().format(time.offset); const banLength = new DurationFormatter().format(time.offset);
let user = guild.client.users.cache.get(userId); const user = await getUser(userId);
if (user === undefined) { if (!isUser(user)) {
user = (await guild.client.users.fetch(userId)) as User; info.message =
'The user does not exist! (The user provided is probably wrong, or their account has been deleted.)';
return info;
} }
// Gets mod's GuildMember // Gets mod's GuildMember
const mod = guild.members.cache.get(modId); const mod = await getGuildMember(modId, guild);
// Checks if guildMember is null // Checks if guildMember is null
if (mod === undefined) { if (!isGuildMember(mod)) {
info.message = 'Error fetching mod!'; info.message = 'Error fetching mod!';
return info; return info;
} }
@@ -225,13 +235,9 @@ export class TempBanCommand extends Command {
await updateUser(mod); await updateUser(mod);
// Gets guildMember // Gets guildMember
let member = guild.members.cache.get(userId); const member = await getGuildMember(userId, guild);
if (member === undefined) { if (isGuildMember(member)) {
member = await guild.members.fetch(userId).catch(() => undefined);
}
if (member !== undefined) {
// Checks if the user is not restricted // Checks if the user is not restricted
if (member.roles.cache.has(IDs.roles.vegan.vegan)) { if (member.roles.cache.has(IDs.roles.vegan.vegan)) {
info.message = 'You need to restrict the user first!'; info.message = 'You need to restrict the user first!';
@@ -258,11 +264,13 @@ export class TempBanCommand extends Command {
await addTempBan(userId, modId, time.fromNow, reason); await addTempBan(userId, modId, time.fromNow, reason);
// Create scheduled task to unban // Create scheduled task to unban
this.container.tasks.create( await this.container.tasks.create(
'tempBan',
{ {
userId: user.id, name: 'tempBan',
guildId: guild.id, payload: {
userId: user.id,
guildId: guild.id,
},
}, },
time.offset, time.offset,
); );
@@ -271,23 +279,25 @@ export class TempBanCommand extends Command {
info.success = true; info.success = true;
// Log the ban // Log the ban
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as const logChannel = await getTextBasedChannel(IDs.channels.logs.restricted);
| TextChannel
| undefined;
if (logChannel === undefined) { if (!isTextBasedChannel(logChannel)) {
logChannel = (await guild.channels.fetch( this.container.logger.error('Temp Ban: Could not fetch log channel');
IDs.channels.logs.restricted,
)) as TextChannel | undefined; info.message =
if (logChannel === undefined) { `${user} has been temporarily banned for ${banLength}. ` +
this.container.logger.error( "This hasn't been logged in a text channel as log channel could not be found";
'Temp Ban Error: Could not fetch log channel',
); return info;
info.message = } else if (!logChannel.isSendable()) {
`${user} has been temporarily banned for ${banLength}. ` + this.container.logger.error(
"This hasn't been logged in a text channel as log channel could not be found"; 'Temp Ban: The bot does not have permission to send in the logs channel!',
return info; );
} info.message =
`${user} has been temporarily banned for ${banLength}. ` +
"This hasn't been logged in a text channel as the bot does not have permission to send logs!";
return info;
} }
const log = new EmbedBuilder() const log = new EmbedBuilder()

View File

@@ -18,22 +18,31 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { import {
User, User,
Message, Message,
Snowflake, Snowflake,
TextChannel,
Guild, Guild,
GuildBan, GuildBan,
MessageFlagsBitField,
} from 'discord.js'; } from 'discord.js';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { removeBan, checkBan, addBan } from '#utils/database/ban'; import { removeBan, checkBan, addBan } from '#utils/database/moderation/ban';
import { checkTempBan, removeTempBan } from '#utils/database/tempBan'; import {
checkTempBan,
removeTempBan,
} from '#utils/database/moderation/tempBan';
import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser'; import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser';
import { getGuildMember, getTextBasedChannel, getUser } from '#utils/fetcher';
import {
isGuildMember,
isTextBasedChannel,
} from '@sapphire/discord.js-utilities';
import { isNullish } from '@sapphire/utilities';
export class UnbanCommand extends Command { export class UnbanCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'unban', name: 'unban',
@@ -72,8 +81,8 @@ export class UnbanCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -120,10 +129,10 @@ export class UnbanCommand extends Command {
}; };
// Gets mod's GuildMember // Gets mod's GuildMember
const mod = guild.members.cache.get(modId); const mod = await getGuildMember(modId, guild);
// Checks if guildMember is null // Checks if guildMember is null
if (mod === undefined) { if (!isGuildMember(mod)) {
info.message = 'Error fetching mod!'; info.message = 'Error fetching mod!';
return info; return info;
} }
@@ -131,14 +140,11 @@ export class UnbanCommand extends Command {
// Check if mod is in database // Check if mod is in database
await addExistingUser(mod); await addExistingUser(mod);
let user = guild.client.users.cache.get(userId); const user = await getUser(userId);
if (user === undefined) { if (user === undefined) {
user = await guild.client.users.fetch(userId); info.message = 'Could not fetch the user!';
if (user === undefined) { return info;
info.message = 'Could not fetch the user!';
return info;
}
} }
let dbBan = await checkBan(userId); let dbBan = await checkBan(userId);
@@ -158,7 +164,7 @@ export class UnbanCommand extends Command {
} }
let { reason } = ban; let { reason } = ban;
if (reason === null || reason === undefined) { if (isNullish(reason)) {
reason = ''; reason = '';
} }
@@ -188,19 +194,21 @@ export class UnbanCommand extends Command {
info.success = true; info.success = true;
// Log unban // Log unban
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as let logChannel = await getTextBasedChannel(IDs.channels.logs.restricted);
| TextChannel
| undefined;
if (logChannel === undefined) { if (!isTextBasedChannel(logChannel)) {
logChannel = (await guild.channels.fetch( this.container.logger.error('Unban Error: Could not fetch log channel');
IDs.channels.logs.restricted, info.message = `${user} has been unbanned. This hasn't been logged in a text channel as log channel could not be found`;
)) as TextChannel | undefined;
if (logChannel === undefined) { return info;
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`; if (!logChannel.isSendable()) {
return info; this.container.logger.error(
} 'Unban: The bot does not have permission to send in the logs channel!',
);
info.message = `${user} has been unbanned. This hasn't been logged in a text channel as the bot does not have permission to send logs!`;
return info;
} }
const log = new EmbedBuilder() const log = new EmbedBuilder()

View File

@@ -22,16 +22,26 @@
import { Args, container, RegisterBehavior } from '@sapphire/framework'; import { Args, container, RegisterBehavior } from '@sapphire/framework';
import { Subcommand } from '@sapphire/plugin-subcommands'; import { Subcommand } from '@sapphire/plugin-subcommands';
import { import {
ChannelType,
GuildMember, GuildMember,
Message, Message,
MessageFlagsBitField,
PermissionsBitField, PermissionsBitField,
} from 'discord.js'; } from 'discord.js';
import type { TextChannel, Snowflake } from 'discord.js'; import type { Snowflake } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getGuildMember, getRole, getTextBasedChannel } from '#utils/fetcher';
import {
isGuildMember,
isTextChannel,
isThreadChannel,
} from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class DiversityCommand extends Subcommand { export class DiversityCommand extends Subcommand {
public constructor(context: Subcommand.Context, options: Subcommand.Options) { public constructor(
context: Subcommand.LoaderContext,
options: Subcommand.Options,
) {
super(context, { super(context, {
...options, ...options,
name: 'diversity', name: 'diversity',
@@ -86,47 +96,25 @@ export class DiversityCommand extends Subcommand {
// Command run // Command run
public async toggleOpen(interaction: Subcommand.ChatInputCommandInteraction) { 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 // Get the channel
const channel = interaction.guild.channels.cache.get(interaction.channelId); const channel = await getTextBasedChannel(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 // Check if channel is text
if (channel.type !== ChannelType.GuildText) { if (!isTextChannel(channel)) {
await interaction.reply({ await interaction.reply({
content: 'Channel is not a text channel!', content: 'Channel is not a text channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
// Converts GuildBasedChannel to TextChannel
const channelText = channel as TextChannel;
// Check if the command was run in the diversity section // Check if the command was run in the diversity section
if (channel.parentId !== IDs.categories.diversity) { if (channel.parentId !== IDs.categories.diversity) {
await interaction.reply({ await interaction.reply({
content: 'Command was not run in the Diversity section!', content: 'Command was not run in the Diversity section!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -137,13 +125,13 @@ export class DiversityCommand extends Subcommand {
.has([PermissionsBitField.Flags.SendMessages]); .has([PermissionsBitField.Flags.SendMessages]);
// Toggle send message in channel // Toggle send message in channel
await channelText.permissionOverwrites.edit(IDs.roles.vegan.vegan, { await channel.permissionOverwrites.edit(IDs.roles.vegan.vegan, {
SendMessages: !open, SendMessages: !open,
}); });
await interaction.reply({ await interaction.reply({
content: `${!open ? 'Opened' : 'Closed'} this channel.`, content: `${!open ? 'Opened' : 'Closed'} this channel.`,
fetchReply: true, withResponse: true,
}); });
} }
@@ -152,51 +140,69 @@ export class DiversityCommand extends Subcommand {
) { ) {
// TODO add database updates // TODO add database updates
// Get the arguments // Get the arguments
const user = interaction.options.getUser('user'); const user = interaction.options.getUser('user', true);
const mod = interaction.member; const mod = interaction.member;
const { guild } = interaction; const { guild } = interaction;
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (user === null || guild === null || mod === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching user!', content: 'Error fetching the guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
// Gets guildMember whilst removing the ability of each other variables being null if (!isGuildMember(mod)) {
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({ await interaction.reply({
content: 'Error fetching user!', content: 'Error fetching your user!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
});
return;
}
const member = await getGuildMember(user.id, guild);
const diversity = await getRole(IDs.roles.staff.diversity, guild);
// Checks if the member was found
if (!isGuildMember(member)) {
await interaction.reply({
content: 'Error fetching the user!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
});
return;
}
// Checks if the role was found
if (!isRole(diversity)) {
await interaction.reply({
content: 'Error fetching the diversity role!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
}); });
return; return;
} }
// Checks if the user has Diversity and to give them or remove them based on if they have it // 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)) { if (member.roles.cache.has(IDs.roles.staff.diversity)) {
// Remove the Diversity role from the user // Remove the Diversity role from the user
await guildMember.roles.remove(diversity); await member.roles.remove(diversity);
await this.threadManager(guildMember.id, false); await this.threadManager(member.id, false);
await interaction.reply({ await interaction.reply({
content: `Removed the ${diversity.name} role from ${user}`, content: `Removed the ${diversity.name} role from ${user}`,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
// Add Diversity Team role to the user // Add Diversity Team role to the user
await guildMember.roles.add(diversity); await member.roles.add(diversity);
await this.threadManager(guildMember.id, true); await this.threadManager(member.id, true);
await interaction.reply({ await interaction.reply({
content: `Gave ${user} the ${diversity.name} role!`, content: `Gave ${user} the ${diversity.name} role!`,
fetchReply: true, withResponse: true,
}); });
await user await user
.send(`You have been given the ${diversity.name} role by ${mod}!`) .send(`You have been given the ${diversity.name} role by ${mod}!`)
@@ -216,7 +222,7 @@ export class DiversityCommand extends Subcommand {
const mod = message.member; const mod = message.member;
if (mod === null) { if (!isGuildMember(mod)) {
await message.react('❌'); await message.react('❌');
await message.reply( await message.reply(
'Diversity coordinator not found! Try again or contact a developer!', 'Diversity coordinator not found! Try again or contact a developer!',
@@ -232,9 +238,9 @@ export class DiversityCommand extends Subcommand {
return; return;
} }
const diversity = guild.roles.cache.get(IDs.roles.staff.diversity); const diversity = await getRole(IDs.roles.staff.diversity, guild);
if (diversity === undefined) { if (!isRole(diversity)) {
await message.react('❌'); await message.react('❌');
await message.reply('Role not found! Try again or contact a developer!'); await message.reply('Role not found! Try again or contact a developer!');
return; return;
@@ -267,11 +273,8 @@ export class DiversityCommand extends Subcommand {
const thread = await container.client.channels.fetch( const thread = await container.client.channels.fetch(
IDs.channels.diversity.diversity, IDs.channels.diversity.diversity,
); );
if (thread === null) {
return;
}
if (!thread.isThread()) { if (!isThreadChannel(thread)) {
return; return;
} }

View File

@@ -21,11 +21,16 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Message } from 'discord.js'; import { Message, MessageFlagsBitField } from 'discord.js';
import { ChannelType } from 'discord.js'; import { ChannelType } from 'discord.js';
import {
isGuildMember,
isVoiceBasedChannel,
} from '@sapphire/discord.js-utilities';
import { getVoiceBasedChannel } from '#utils/fetcher';
export class MoveAllCommand extends Command { export class MoveAllCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'moveall', name: 'moveall',
@@ -61,7 +66,9 @@ export class MoveAllCommand extends Command {
const { member } = interaction; const { member } = interaction;
const { guild } = interaction; const { guild } = interaction;
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
if ( if (
channel.type !== ChannelType.GuildVoice && channel.type !== ChannelType.GuildVoice &&
@@ -81,40 +88,31 @@ export class MoveAllCommand extends Command {
return; return;
} }
if (member === null) { if (!isGuildMember(member)) {
await interaction.editReply({ await interaction.editReply({
content: 'Error fetching your user', content: 'Error fetching your user',
}); });
return; return;
} }
const mod = guild.members.cache.get(member.user.id); if (member.voice.channelId === null) {
if (mod === undefined) {
await interaction.editReply({
content: 'Error fetching user from guild',
});
return;
}
if (mod.voice.channelId === null) {
await interaction.editReply({ await interaction.editReply({
content: 'You need to be in a voice channel to run this command!', content: 'You need to be in a voice channel to run this command!',
}); });
return; return;
} }
const voice = guild.channels.cache.get(mod.voice.channelId); const voice = await getVoiceBasedChannel(member.voice.channelId);
if (voice === undefined || !voice.isVoiceBased()) { if (!isVoiceBasedChannel(voice)) {
await interaction.editReply({ await interaction.editReply({
content: 'Error fetching your current voice channel!', content: 'Error fetching your current voice channel!',
}); });
return; return;
} }
voice.members.forEach((memberVC) => { voice.members.forEach((vcMember) => {
memberVC.voice.setChannel(channel.id); vcMember.voice.setChannel(channel.id);
}); });
await interaction.editReply({ await interaction.editReply({
@@ -135,7 +133,7 @@ export class MoveAllCommand extends Command {
const mod = message.member; const mod = message.member;
const { guild } = message; const { guild } = message;
if (mod === null) { if (!isGuildMember(mod)) {
await message.react('❌'); await message.react('❌');
await message.reply('Could not find your user!'); await message.reply('Could not find your user!');
return; return;
@@ -155,9 +153,9 @@ export class MoveAllCommand extends Command {
return; return;
} }
const voice = guild.channels.cache.get(mod.voice.channelId); const voice = await getVoiceBasedChannel(mod.voice.channelId);
if (voice === undefined || !voice.isVoiceBased()) { if (!isVoiceBasedChannel(voice)) {
await message.react('❌'); await message.react('❌');
await message.reply('Could not fetch current voice channel!'); await message.reply('Could not fetch current voice channel!');
return; return;

View File

@@ -18,10 +18,12 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { GuildMember, Message } from 'discord.js'; import { GuildMember, Message, MessageFlagsBitField } from 'discord.js';
import { getGuildMember } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class RenameUserCommand extends Command { export class RenameUserCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'rename', name: 'rename',
@@ -68,21 +70,21 @@ export class RenameUserCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
// Gets guildMember whilst removing the ability of each other variables being null // Gets guildMember whilst removing the ability of each other variables being null
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
// Checks if guildMember is null // Checks if guildMember is null
if (member === undefined) { if (!isGuildMember(member)) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching user!', content: 'Error fetching user!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -93,15 +95,15 @@ export class RenameUserCommand extends Command {
} catch { } catch {
await interaction.reply({ await interaction.reply({
content: "Bot doesn't have permission to change the user's name!", content: "Bot doesn't have permission to change the user's name!",
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.reply({ await interaction.reply({
content: `Changed ${user}'s nickname`, content: `Changed ${user}'s nickname`,
fetchReply: true, flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true, withResponse: true,
}); });
} }

View File

@@ -26,6 +26,7 @@ import {
import { import {
ChannelType, ChannelType,
EmbedBuilder, EmbedBuilder,
MessageFlagsBitField,
PermissionsBitField, PermissionsBitField,
time, time,
} from 'discord.js'; } from 'discord.js';
@@ -36,9 +37,14 @@ import {
updateUser, updateUser,
fetchRoles, fetchRoles,
} from '#utils/database/dbExistingUser'; } from '#utils/database/dbExistingUser';
import { restrict, checkActive } from '#utils/database/restriction'; import { restrict, checkActive } from '#utils/database/moderation/restriction';
import { randint } from '#utils/maths'; import { randint } from '#utils/maths';
import { blockedRolesAfterRestricted } from '#utils/blockedRoles'; import { blockedRolesAfterRestricted } from '#utils/blockedRoles';
import { getGuildMember, getTextBasedChannel, getUser } from '#utils/fetcher';
import {
isGuildMember,
isTextBasedChannel,
} from '@sapphire/discord.js-utilities';
export async function restrictRun( export async function restrictRun(
userId: Snowflake, userId: Snowflake,
@@ -52,21 +58,18 @@ export async function restrictRun(
success: false, success: false,
}; };
let user = guild.client.users.cache.get(userId); const user = await getUser(userId);
if (user === undefined) { if (user === undefined) {
user = await guild.client.users.fetch(userId); info.message = 'Error fetching user';
if (user === undefined) { return info;
info.message = 'Error fetching user';
return info;
}
} }
// Gets mod's GuildMember // Gets mod's GuildMember
const mod = guild.members.cache.get(modId); const mod = await getGuildMember(modId, guild);
// Checks if guildMember is null // Checks if guildMember is null
if (mod === undefined) { if (!isGuildMember(mod)) {
info.message = 'Error fetching mod'; info.message = 'Error fetching mod';
return info; return info;
} }
@@ -80,17 +83,13 @@ export async function restrictRun(
} }
// Gets guildMember // Gets guildMember
let member = guild.members.cache.get(userId); const member = await getGuildMember(userId, guild);
if (member === undefined) {
member = await guild.members.fetch(userId).catch(() => undefined);
}
const restrictRoles = IDs.roles.restrictions.restricted; const restrictRoles = IDs.roles.restrictions.restricted;
let section = tolerance ? randint(3, 4) : randint(1, 2); let section = tolerance ? randint(3, 4) : randint(1, 2);
if (member !== undefined) { if (isGuildMember(member)) {
// Checks if the user is not restricted // Checks if the user is not restricted
if (member.roles.cache.hasAny(...restrictRoles)) { if (member.roles.cache.hasAny(...restrictRoles)) {
info.message = `${member} is already restricted!`; info.message = `${member} is already restricted!`;
@@ -218,7 +217,7 @@ export async function restrictRun(
} }
} }
if (member !== undefined && member.voice.channelId !== null) { if (isGuildMember(member) && member.voice.channelId !== null) {
await member.voice.disconnect(); await member.voice.disconnect();
} }
@@ -242,19 +241,20 @@ export async function restrictRun(
await user.send({ embeds: [dmEmbed] }).catch(() => {}); await user.send({ embeds: [dmEmbed] }).catch(() => {});
// Log the ban // Log the ban
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as const logChannel = await getTextBasedChannel(IDs.channels.logs.restricted);
| TextChannel
| undefined;
if (logChannel === undefined) { if (!isTextBasedChannel(logChannel)) {
logChannel = (await guild.channels.fetch(IDs.channels.logs.restricted)) as container.logger.error('Restrict: Could not fetch log channel');
| TextChannel info.message = `Restricted ${user} but could not find the log channel. This has been logged to the database.`;
| undefined;
if (logChannel === undefined) { return info;
container.logger.error('Restrict Error: Could not fetch log channel'); } else if (!logChannel.isSendable()) {
info.message = `Restricted ${user} but could not find the log channel. This has been logged to the database.`; container.logger.error(
return info; 'Restrict: The bot does not have permission to send in the logs channel!',
} );
info.message = `${user} has been restricted. This hasn't been logged in a text channel as the bot does not have permission to send logs!`;
return info;
} }
const message = new EmbedBuilder() const message = new EmbedBuilder()
@@ -277,7 +277,7 @@ export async function restrictRun(
} }
export class RestrictCommand extends Command { export class RestrictCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'restrict', name: 'restrict',
@@ -324,15 +324,17 @@ export class RestrictCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await restrictRun(user?.id, mod.id, reason, guild); const info = await restrictRun(user.id, mod.id, reason, guild);
await interaction.editReply({ await interaction.editReply({
content: info.message, content: info.message,
@@ -367,7 +369,7 @@ export class RestrictCommand extends Command {
return; return;
} }
const info = await restrictRun(user?.id, mod.id, reason, guild); const info = await restrictRun(user.id, mod.id, reason, guild);
await message.reply(info.message); await message.reply(info.message);
await message.react(info.success ? '✅' : '❌'); await message.react(info.success ? '✅' : '❌');

View File

@@ -18,18 +18,21 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import { ChannelType, EmbedBuilder } from 'discord.js'; import { EmbedBuilder, MessageFlagsBitField } from 'discord.js';
import type { Message, TextChannel, Guild, Snowflake } from 'discord.js'; import type { Message, Guild, Snowflake } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getRestrictions } from '#utils/database/restriction'; import { getRestrictions } from '#utils/database/moderation/restriction';
import { checkStaff } from '#utils/checker'; import { checkStaff } from '#utils/checker';
import { isUser } from '#utils/typeChecking';
import { isGuildMember, isTextChannel } from '@sapphire/discord.js-utilities';
import { getGuildMember, getUser } from '#utils/fetcher';
export class RestrictLogsCommand extends Command { export class RestrictLogsCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'restrictlogs', name: 'restrictlogs',
description: 'Unrestricts a user', description: 'Shows restriction history for a user',
preconditions: ['ModOnly'], preconditions: ['ModOnly'],
}); });
} }
@@ -56,48 +59,48 @@ export class RestrictLogsCommand extends Command {
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
// Get the arguments // Get the arguments
const user = interaction.options.getUser('user'); const user = interaction.options.getUser('user');
let { channel } = interaction; const { channel } = interaction;
const { guild } = interaction; const { guild } = interaction;
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (guild === null || channel === null) { if (guild === null || channel === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild or channel!', content: 'Error fetching guild or channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
let userId: Snowflake | null = null; let userId: Snowflake | null = null;
if (user !== undefined && user !== null) { if (isUser(user)) {
userId = user.id; userId = user.id;
} }
let staffChannel = false; const staffChannel = checkStaff(channel);
if (channel.type === ChannelType.GuildText) { if (staffChannel) {
channel = channel as TextChannel; // Checking Channel topic for Snowflake
staffChannel = checkStaff(channel);
if (userId === null) { if (userId === null) {
let topic: string[]; let topic: string[];
if (channel.parentId === IDs.categories.modMail) { if (
isTextChannel(channel) &&
channel.parentId === IDs.categories.modMail &&
channel.topic !== null
) {
// Checks if the channel topic has the user's snowflake // Checks if the channel topic has the user's snowflake
if (channel.topic !== null) { topic = channel.topic.split(' ');
topic = channel.topic.split(' '); userId = topic[2];
// eslint-disable-next-line prefer-destructuring
userId = topic[2];
}
} }
} }
// If no Snowflake was provided/found
if (userId === null) { if (userId === null) {
await interaction.reply({ await interaction.reply({
content: 'User could not be found or was not provided!', content: 'User could not be found or was not provided!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -107,8 +110,8 @@ export class RestrictLogsCommand extends Command {
await interaction.reply({ await interaction.reply({
embeds: info.embeds, embeds: info.embeds,
content: info.message, content: info.message,
fetchReply: true, withResponse: true,
ephemeral: !staffChannel, flags: staffChannel ? undefined : MessageFlagsBitField.Flags.Ephemeral,
}); });
} }
} }
@@ -132,10 +135,11 @@ export class RestrictLogsCommand extends Command {
return; return;
} }
// Attempting to get the user's Snowflake from the channel topic.
if (userId === null) { if (userId === null) {
const { channel } = message; const { channel } = message;
if (channel.type !== ChannelType.GuildText) { if (!isTextChannel(channel)) {
await message.react('❌'); await message.react('❌');
await message.reply('User was not provided!'); await message.reply('User was not provided!');
return; return;
@@ -143,13 +147,13 @@ export class RestrictLogsCommand extends Command {
let topic: string[]; let topic: string[];
if (channel.parentId === IDs.categories.modMail) { // Checks if the channel topic has the user's snowflake
// Checks if the channel topic has the user's snowflake if (
if (channel.topic !== null) { channel.parentId === IDs.categories.modMail &&
topic = channel.topic.split(' '); channel.topic !== null
// eslint-disable-next-line prefer-destructuring ) {
userId = topic[2]; topic = channel.topic.split(' ');
} userId = topic[2];
} }
} }
@@ -174,14 +178,12 @@ export class RestrictLogsCommand extends Command {
success: false, success: false,
}; };
let user = guild.client.users.cache.get(userId); const user = await getUser(userId);
if (user === undefined) { if (!isUser(user)) {
user = await guild.client.users.fetch(userId); info.message =
if (user === undefined) { 'Error fetching user. (You probably provided an incorrect user.)';
info.message = 'Error fetching user'; return info;
return info;
}
} }
const restrictions = await getRestrictions(userId); const restrictions = await getRestrictions(userId);
@@ -206,14 +208,15 @@ export class RestrictLogsCommand extends Command {
) { ) {
// Get mod names // Get mod names
let restMod = restrictions[i].modId; let restMod = restrictions[i].modId;
const restModMember = guild.members.cache.get(restMod); const restModMember = await getGuildMember(restMod, guild);
if (restModMember !== undefined) { if (isGuildMember(restModMember)) {
restMod = restModMember.displayName; restMod = restModMember.displayName;
} }
let endRestMod = restrictions[i].endModId; let endRestMod = restrictions[i].endModId;
if (endRestMod !== null) { if (endRestMod !== null) {
const endRestModMember = guild.members.cache.get(endRestMod); const endRestModMember = await getGuildMember(endRestMod, guild);
if (endRestModMember !== undefined) { if (isGuildMember(endRestModMember)) {
endRestMod = endRestModMember.displayName; endRestMod = endRestModMember.displayName;
} }
} }

View File

@@ -18,11 +18,11 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Message } from 'discord.js'; import { User, Message, MessageFlagsBitField } from 'discord.js';
import { restrictRun } from './restrict'; import { restrictRun } from './restrict';
export class RestrictToleranceCommand extends Command { export class RestrictToleranceCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'restricttolerance', name: 'restricttolerance',
@@ -69,15 +69,17 @@ export class RestrictToleranceCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await restrictRun(user?.id, mod.id, reason, guild, true); const info = await restrictRun(user.id, mod.id, reason, guild, true);
await interaction.editReply({ await interaction.editReply({
content: info.message, content: info.message,
@@ -112,7 +114,7 @@ export class RestrictToleranceCommand extends Command {
return; return;
} }
const info = await restrictRun(user?.id, mod.id, reason, guild, true); const info = await restrictRun(user.id, mod.id, reason, guild, true);
await message.reply(info.message); await message.reply(info.message);
await message.react(info.success ? '✅' : '❌'); await message.react(info.success ? '✅' : '❌');

View File

@@ -19,12 +19,21 @@
import { RegisterBehavior } from '@sapphire/framework'; import { RegisterBehavior } from '@sapphire/framework';
import { Subcommand } from '@sapphire/plugin-subcommands'; import { Subcommand } from '@sapphire/plugin-subcommands';
import type { TextChannel } from 'discord.js'; import { MessageFlagsBitField } from 'discord.js';
import { CategoryChannel, ChannelType } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { isUser } from '#utils/typeChecking';
import {
isCategoryChannel,
isTextChannel,
isVoiceChannel,
} from '@sapphire/discord.js-utilities';
import { getCategoryChannel, getVoiceChannel } from '#utils/fetcher';
export class RestrictToolsCommand extends Subcommand { export class RestrictToolsCommand extends Subcommand {
public constructor(context: Subcommand.Context, options: Subcommand.Options) { public constructor(
context: Subcommand.LoaderContext,
options: Subcommand.Options,
) {
super(context, { super(context, {
...options, ...options,
name: 'restricttools', name: 'restricttools',
@@ -75,7 +84,9 @@ export class RestrictToolsCommand extends Subcommand {
const user = interaction.options.getUser('user'); const user = interaction.options.getUser('user');
const { guild, channel } = interaction; const { guild, channel } = interaction;
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (guild === null || channel === null) { if (guild === null || channel === null) {
@@ -87,8 +98,8 @@ export class RestrictToolsCommand extends Subcommand {
let topic: string[]; let topic: string[];
if (user === null) { if (!isUser(user)) {
if (channel.type !== ChannelType.GuildText) { if (!isTextChannel(channel)) {
await interaction.editReply({ await interaction.editReply({
content: content:
'Please make sure you ran this command in the original restricted text channel!', 'Please make sure you ran this command in the original restricted text channel!',
@@ -127,10 +138,10 @@ export class RestrictToolsCommand extends Subcommand {
await channel.delete(); await channel.delete();
const vcId = topic[3]; const vcId = topic[3];
const voiceChannel = guild.channels.cache.get(vcId); const voiceChannel = await getVoiceChannel(vcId);
if ( if (
voiceChannel !== undefined && isVoiceChannel(voiceChannel) &&
voiceChannel.parentId === IDs.categories.restricted voiceChannel.parentId === IDs.categories.restricted
) { ) {
await voiceChannel.delete(); await voiceChannel.delete();
@@ -139,37 +150,47 @@ export class RestrictToolsCommand extends Subcommand {
return; return;
} }
const category = guild.channels.cache.get(IDs.categories.restricted) as const category = await getCategoryChannel(IDs.categories.restricted);
| CategoryChannel
| undefined;
if (category === undefined) { if (!isCategoryChannel(category)) {
await interaction.editReply({ await interaction.editReply({
content: 'Could not find category!', content: 'Could not find category!',
}); });
return; return;
} }
const textChannels = category.children.cache.filter( const textChannels = category.children.cache.filter((channel) =>
(c) => c.type === ChannelType.GuildText, isTextChannel(channel),
); );
textChannels.forEach((c) => {
const textChannel = c as TextChannel; for (const c of textChannels) {
const channel = c[1];
// Checks that the channel is a text channel
if (!isTextChannel(channel)) {
continue;
}
// Checks that the channel has a topic
if (channel.topic === null) {
continue;
}
// Checks if the channel topic has the user's snowflake // Checks if the channel topic has the user's snowflake
if (textChannel.topic?.includes(user?.id)) { if (channel.topic.includes(user.id)) {
topic = textChannel.topic.split(' '); topic = channel.topic.split(' ');
const vcId = topic[topic.indexOf(user?.id) + 1]; const vcId = topic[topic.indexOf(user.id) + 1];
const voiceChannel = guild.channels.cache.get(vcId); const voiceChannel = await getVoiceChannel(vcId);
if ( if (
voiceChannel !== undefined && isVoiceChannel(voiceChannel) &&
voiceChannel.parentId === IDs.categories.restricted voiceChannel.parentId === IDs.categories.restricted
) { ) {
voiceChannel.delete(); await voiceChannel.delete();
} }
textChannel.delete(); await channel.delete();
} }
}); }
await interaction.editReply({ await interaction.editReply({
content: `Successfully deleted the channel for ${user}`, content: `Successfully deleted the channel for ${user}`,

View File

@@ -18,18 +18,33 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import { CategoryChannel, ChannelType, EmbedBuilder } from 'discord.js'; import type { Guild, Message, Snowflake, User } from 'discord.js';
import type { User, Message, TextChannel, Guild, Snowflake } from 'discord.js'; import { EmbedBuilder, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { fetchRoles, addExistingUser } from '#utils/database/dbExistingUser'; import { addExistingUser, fetchRoles } from '#utils/database/dbExistingUser';
import { import {
unRestrict,
checkActive, checkActive,
unRestrict,
unRestrictLegacy, unRestrictLegacy,
} from '#utils/database/restriction'; } from '#utils/database/moderation/restriction';
import {
getCategoryChannel,
getGuildMember,
getTextBasedChannel,
getUser,
getVoiceChannel,
} from '#utils/fetcher';
import { isUser } from '#utils/typeChecking';
import {
isCategoryChannel,
isGuildMember,
isTextBasedChannel,
isTextChannel,
isVoiceChannel,
} from '@sapphire/discord.js-utilities';
export class UnRestrictCommand extends Command { export class UnRestrictCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'unrestrict', name: 'unrestrict',
@@ -69,14 +84,14 @@ export class UnRestrictCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
await interaction.deferReply(); await interaction.deferReply();
const info = await this.unRestrictRun(user?.id, mod.id, guild); const info = await this.unRestrictRun(user.id, mod.id, guild);
await interaction.editReply({ await interaction.editReply({
content: info.message, content: info.message,
@@ -108,7 +123,7 @@ export class UnRestrictCommand extends Command {
const channelRun = message.channel; const channelRun = message.channel;
const info = await this.unRestrictRun( const info = await this.unRestrictRun(
user?.id, user.id,
mod.id, mod.id,
guild, guild,
channelRun.id, channelRun.id,
@@ -132,21 +147,18 @@ export class UnRestrictCommand extends Command {
runInVeganRestrict: false, runInVeganRestrict: false,
}; };
let user = guild.client.users.cache.get(userId); const user = await getUser(userId);
if (user === undefined) { if (!isUser(user)) {
user = await guild.client.users.fetch(userId); info.message = 'Error fetching user';
if (user === undefined) { return info;
info.message = 'Error fetching user';
return info;
}
} }
// Gets mod's GuildMember // Gets mod's GuildMember
const mod = guild.members.cache.get(modId); const mod = await getGuildMember(modId, guild);
// Checks if guildMember is null // Checks if guildMember is null
if (mod === undefined) { if (!isGuildMember(mod)) {
info.message = 'Error fetching mod'; info.message = 'Error fetching mod';
return info; return info;
} }
@@ -155,13 +167,9 @@ export class UnRestrictCommand extends Command {
await addExistingUser(mod); await addExistingUser(mod);
// Gets guildMember // Gets guildMember
let member = guild.members.cache.get(userId); const member = await getGuildMember(userId, guild);
if (member === undefined) { if (!isGuildMember(member)) {
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"; info.message = "Can't unrestrict the user as they are not on this server";
return info; return info;
} }
@@ -180,16 +188,20 @@ export class UnRestrictCommand extends Command {
if (await checkActive(userId)) { if (await checkActive(userId)) {
const roles = await fetchRoles(userId); const roles = await fetchRoles(userId);
await member.roles.add(roles); await member.roles.add(roles);
// Unrestricts the user on the database // Unrestricts the user on the database
await unRestrict(userId, modId); await unRestrict(userId, modId);
} else { } else {
let section = 1; let section = 1;
for (let i = 0; i < restrictRoles.length; i += 1) { for (let i = 0; i < restrictRoles.length; i += 1) {
if (member.roles.cache.has(restrictRoles[i])) { if (member.roles.cache.has(restrictRoles[i])) {
section = i + 1; section = i + 1;
} }
} }
await member.roles.add(IDs.roles.nonvegan.nonvegan); await member.roles.add(IDs.roles.nonvegan.nonvegan);
// Unrestricts the user on the database but for restricts done on the old bot // Unrestricts the user on the database but for restricts done on the old bot
await unRestrictLegacy(userId, modId, section); await unRestrictLegacy(userId, modId, section);
} }
@@ -198,57 +210,73 @@ export class UnRestrictCommand extends Command {
// Remove vegan restrict channels // Remove vegan restrict channels
if (member.roles.cache.has(IDs.roles.vegan.vegan)) { if (member.roles.cache.has(IDs.roles.vegan.vegan)) {
const category = guild.channels.cache.get(IDs.categories.restricted) as const category = await getCategoryChannel(IDs.categories.restricted);
| CategoryChannel
| undefined; if (!isCategoryChannel(category)) {
info.message =
'Could not find the restricted category! The channels will have to be deleted manually.';
return info;
}
let topic: string[]; let topic: string[];
if (category !== undefined) { const textChannels = category.children.cache.filter((channel) =>
const textChannels = category.children.cache.filter( isTextChannel(channel),
(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 ( for (const c of textChannels) {
voiceChannel !== undefined && const channel = c[1];
voiceChannel.parentId === IDs.categories.restricted
) { // Checks that the channel is a text channel
voiceChannel.delete(); if (!isTextChannel(channel)) {
} continue;
textChannel.delete(); }
// Checks that the channel has a topic
if (channel.topic === null) {
continue;
}
// Checks if the channel topic has the user's snowflake
if (channel.topic.includes(userId)) {
if (channel.id === channelRun) {
info.runInVeganRestrict = true;
} }
});
topic = channel.topic.split(' ');
const vcId = topic[topic.indexOf(user.id) + 1];
const voiceChannel = await getVoiceChannel(vcId);
if (
isVoiceChannel(voiceChannel) &&
// Used for sanitising the channel topic, so another voice channel does not get deleted
voiceChannel.parentId === IDs.categories.restricted
) {
await voiceChannel.delete();
}
await channel.delete();
}
} }
} }
info.success = true; info.success = true;
// Log the ban // Log the ban
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as const logChannel = await getTextBasedChannel(IDs.channels.logs.restricted);
| TextChannel
| undefined;
if (logChannel === undefined) { if (!isTextBasedChannel(logChannel)) {
logChannel = (await guild.channels.fetch( this.container.logger.error('Unrestrict: Could not fetch log channel');
IDs.channels.logs.restricted, info.message = `Unrestricted ${user} but could not find the log channel. This has been logged to the database.`;
)) as TextChannel | undefined;
if (logChannel === undefined) { return info;
this.container.logger.error( } else if (!logChannel.isSendable()) {
'Restrict Error: Could not fetch log channel', this.container.logger.error(
); 'Unrestrict: The bot does not have permission to send in the logs channel!',
info.message = `Unrestricted ${user} but could not find the log channel. This has been logged to the database.`; );
return info; info.message = `Unrestricted ${user} but could not find the log channel. This hasn't been logged in a text channel as the bot does not have permission to send logs!`;
}
return info;
} }
const message = new EmbedBuilder() const message = new EmbedBuilder()

View File

@@ -18,13 +18,16 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Message, TextBasedChannel } from 'discord.js'; import { Message, MessageFlagsBitField, TextBasedChannel } from 'discord.js';
import { ChannelType } from 'discord.js';
import { Duration, DurationFormatter } from '@sapphire/time-utilities'; import { Duration, DurationFormatter } from '@sapphire/time-utilities';
import { isNumber } from '#utils/maths'; import { isNumber } from '#utils/maths';
import {
isTextBasedChannel,
isTextChannel,
} from '@sapphire/discord.js-utilities';
export class SlowmodeCommand extends Command { export class SlowmodeCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'slowmode', name: 'slowmode',
@@ -58,11 +61,11 @@ export class SlowmodeCommand extends Command {
const duration = interaction.options.getString('duration', true); const duration = interaction.options.getString('duration', true);
const { channel } = interaction; const { channel } = interaction;
if (channel === null) { if (!isTextBasedChannel(channel)) {
await interaction.reply({ await interaction.reply({
content: 'Could not fetch channel!', content: 'Could not fetch channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -94,7 +97,7 @@ export class SlowmodeCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
if (channel.type !== ChannelType.GuildText) { if (!isTextChannel(channel)) {
info.message = 'Channel is not a text channel!'; info.message = 'Channel is not a text channel!';
return info; return info;
} }

View File

@@ -18,11 +18,13 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { GuildMember, Message } from 'discord.js'; import { GuildMember, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getGuildMember } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class SoftMuteCommand extends Command { export class SoftMuteCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'softmute', name: 'softmute',
@@ -64,39 +66,39 @@ export class SoftMuteCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching the guild!', content: 'Error fetching the guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
// Gets guildMember whilst removing the ability of each other variables being null // Gets GuildMember whilst removing the ability of each other variables being null
const guildMember = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
// Checks if guildMember is null // Checks if guildMember is null
if (guildMember === undefined) { if (!isGuildMember(member)) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching user!', content: 'Error fetching user!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
if (guildMember.roles.cache.has(IDs.roles.restrictions.softMute)) { if (member.roles.cache.has(IDs.roles.restrictions.softMute)) {
await guildMember.roles.remove(IDs.roles.restrictions.softMute); await member.roles.remove(IDs.roles.restrictions.softMute);
await interaction.reply({ await interaction.reply({
content: `Removed soft muted for ${user}`, content: `Removed soft muted for ${user}`,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await guildMember.roles.add(IDs.roles.restrictions.softMute); await member.roles.add(IDs.roles.restrictions.softMute);
await interaction.reply({ await interaction.reply({
content: `Soft muted ${user}`, content: `Soft muted ${user}`,
fetchReply: true, withResponse: true,
}); });
} }

View File

@@ -20,30 +20,42 @@
import { RegisterBehavior, Args } from '@sapphire/framework'; import { RegisterBehavior, Args } from '@sapphire/framework';
import { Subcommand } from '@sapphire/plugin-subcommands'; import { Subcommand } from '@sapphire/plugin-subcommands';
import { import {
ChannelType,
EmbedBuilder, EmbedBuilder,
ActionRowBuilder, ActionRowBuilder,
ButtonBuilder, ButtonBuilder,
ButtonInteraction, ButtonInteraction,
ButtonStyle, ButtonStyle,
User,
Guild,
Snowflake,
MessageFlagsBitField,
} from 'discord.js'; } from 'discord.js';
import type { Message, GuildMember, TextChannel } from 'discord.js'; import type { Message } from 'discord.js';
import { isMessageInstance } from '@sapphire/discord.js-utilities';
import { addExistingUser } from '#utils/database/dbExistingUser';
import { import {
addToDatabase, addSusNoteDB,
findNotes, findNotes,
getNote, getNote,
deactivateNote, deactivateNote,
deactivateAllNotes, deactivateAllNotes,
} from '#utils/database/sus'; } from '#utils/database/moderation/sus';
import { checkStaff } from '#utils/checker'; import { checkStaff } from '#utils/checker';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { createSusLogEmbed } from '#utils/embeds';
import { getGuildMember, getTextBasedChannel, getUser } from '#utils/fetcher';
import { isUser } from '#utils/typeChecking';
import {
isGuildMember,
isTextBasedChannel,
isTextChannel,
} from '@sapphire/discord.js-utilities';
// TODO add a check when they join the server to give the user the sus role again // TODO add a check when they join the server to give the user the sus role again
export class SusCommand extends Subcommand { export class SusCommand extends Subcommand {
public constructor(context: Subcommand.Context, options: Subcommand.Options) { public constructor(
context: Subcommand.LoaderContext,
options: Subcommand.Options,
) {
super(context, { super(context, {
...options, ...options,
name: 'sus', name: 'sus',
@@ -51,8 +63,8 @@ export class SusCommand extends Subcommand {
{ {
name: 'add', name: 'add',
default: true, default: true,
chatInputRun: 'addNote', chatInputRun: 'addNoteChatInput',
messageRun: 'addMessage', messageRun: 'addNoteMessage',
}, },
{ {
name: 'view', name: 'view',
@@ -140,7 +152,9 @@ export class SusCommand extends Subcommand {
} }
// Subcommand to add sus note // Subcommand to add sus note
public async addNote(interaction: Subcommand.ChatInputCommandInteraction) { public async addNoteChatInput(
interaction: Subcommand.ChatInputCommandInteraction,
) {
// Get the arguments // Get the arguments
const user = interaction.options.getUser('user', true); const user = interaction.options.getUser('user', true);
const note = interaction.options.getString('note', true); const note = interaction.options.getString('note', true);
@@ -151,40 +165,122 @@ export class SusCommand extends Subcommand {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
const info = await this.addNote(user, mod, note, guild);
await interaction.reply({
content: info.message,
flags: MessageFlagsBitField.Flags.Ephemeral,
});
}
// 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 === null) {
await message.react('❌');
await message.reply(
'Could not find guild! Make sure you run this command in a server.',
);
return;
}
const info = await this.addNote(user, mod, note, guild);
if (!info.success) {
await message.react('❌');
return;
}
await message.react('✅');
}
private async addNote(user: User, mod: User, note: string, guild: Guild) {
const info = {
message: '',
success: false,
};
// Add the data to the database // Add the data to the database
await addSusNoteDB(user.id, mod.id, note);
// Check if the user exists on the database // Gives the sus role to the user
const member = guild.members.cache.get(user.id); await this.addSusRole(user, guild);
const modMember = guild.members.cache.get(mod.id);
if (member === undefined || modMember === undefined) { info.message = `Added the sus note for ${user}: ${note}`;
await interaction.reply({ info.success = true;
content: 'Error fetching users!',
ephemeral: true, // Log the sus note
}); const logChannel = await getTextBasedChannel(IDs.channels.logs.sus);
return;
if (!isTextBasedChannel(logChannel)) {
this.container.logger.error('Sus: 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;
} else if (!logChannel.isSendable()) {
this.container.logger.error(
'Sus: Does not have permission to message in the log channel.',
);
info.message = `Added a sus note for ${user} but could not send in the logs channel. This has been logged to the database.`;
return info;
} }
// Check if user and mod are on the database const message = new EmbedBuilder()
await addExistingUser(member); .setColor('#0099ff')
await addExistingUser(modMember); .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 addToDatabase(user.id, mod.id, note); await logChannel.send({ embeds: [message] });
return info;
}
private async addSusRole(user: User, guild: Guild) {
// Get GuildMember for user to add a sus note for
const member = await getGuildMember(user.id, guild);
if (!isGuildMember(member)) {
return;
}
// Give the user the sus role they don't already have the sus note // Give the user the sus role they don't already have the sus note
if (!member.roles.cache.has(IDs.roles.restrictions.sus)) { if (!member.roles.cache.has(IDs.roles.restrictions.sus)) {
await member.roles.add(IDs.roles.restrictions.sus); await member.roles.add(IDs.roles.restrictions.sus);
} }
await interaction.reply({
content: `${user} note: ${note}`,
ephemeral: true,
});
} }
public async listNote(interaction: Subcommand.ChatInputCommandInteraction) { public async listNote(interaction: Subcommand.ChatInputCommandInteraction) {
@@ -193,22 +289,15 @@ export class SusCommand extends Subcommand {
const { guild } = interaction; const { guild } = interaction;
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (guild == null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
let staffChannel = false; const staffChannel = checkStaff(interaction.channel);
let { channel } = interaction;
if (channel !== null) {
if (channel.type === ChannelType.GuildText) {
channel = channel as TextChannel;
staffChannel = checkStaff(channel);
}
}
// Gets the sus notes from the database // Gets the sus notes from the database
const notes = await findNotes(user.id, true); const notes = await findNotes(user.id, true);
@@ -217,61 +306,35 @@ export class SusCommand extends Subcommand {
if (notes.length === 0) { if (notes.length === 0) {
await interaction.reply({ await interaction.reply({
content: `${user} has no sus notes!`, content: `${user} has no sus notes!`,
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
// Creates the embed to display the sus note // Creates the embed to display the sus note
const noteEmbed = new EmbedBuilder() const noteEmbed = createSusLogEmbed(notes, user, guild);
.setColor('#0099ff')
.setTitle(`${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
) {
// Get mod name
let mod = notes[i].modId;
const modMember = guild.members.cache.get(mod);
if (modMember !== undefined) {
mod = modMember.displayName;
}
// Add sus note to embed
noteEmbed.addFields({
name: `Sus ID: ${
notes[i].id
} | Moderator: ${mod} | Date: <t:${Math.floor(
notes[i].time.getTime() / 1000,
)}>`,
value: notes[i].note,
});
}
// Sends the notes to the user // Sends the notes to the user
await interaction.reply({ await interaction.reply({
embeds: [noteEmbed], embeds: [noteEmbed],
ephemeral: !staffChannel, flags: staffChannel ? undefined : MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
} }
public async removeNote(interaction: Subcommand.ChatInputCommandInteraction) { public async removeNote(interaction: Subcommand.ChatInputCommandInteraction) {
// Get the arguments // Get the arguments
const noteId = interaction.options.getInteger('id', true); const noteId = interaction.options.getInteger('id', true);
const mod = interaction.user;
const { guild, channel } = interaction; const { guild, channel } = interaction;
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (guild === null || channel === null) { if (guild === null || !isTextBasedChannel(channel)) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild or channel!', content: 'Error fetching guild or channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -283,45 +346,39 @@ export class SusCommand extends Subcommand {
if (note === null) { if (note === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching note from database!', content: 'Error fetching note from database!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
const userId = note.userId;
const modId = note.modId;
// Get user GuildMembers for user and mod and person who ran command // Get user GuildMembers for user and mod and person who ran command
const member = await guild.members.cache.get(note.userId); const user = await getUser(userId);
const mod = await guild.members.cache.get(note.modId); if (!isUser(user)) {
// TODO fix if user left the server
if (member === undefined || mod === undefined) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching users from Discord!', content: 'Error fetching user!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
// Get user's name const modCreator = await getUser(modId);
let userName = note.userId;
if (member !== undefined) {
userName = member.displayName;
}
// Get mod name const modCreatorDisplay = isUser(modCreator)
let modName = note.modId; ? modCreator.displayName
if (mod !== undefined) { : modId;
modName = mod.displayName;
}
// Create an embed for the note // Create an embed for the note
const noteEmbed = new EmbedBuilder() const noteEmbed = new EmbedBuilder()
.setColor('#ff0000') .setColor('#ff0000')
.setTitle(`Sus note for ${userName}`) .setTitle(`Sus note for ${user.tag}`)
.setThumbnail(member.displayAvatarURL()) .setThumbnail(user.displayAvatarURL())
.addFields({ .addFields({
name: `ID: ${noteId} | Moderator: ${modName} | Date: <t:${Math.floor( name: `ID: ${noteId} | Moderator: ${modCreatorDisplay} | Date: <t:${Math.floor(
note.time.getTime() / 1000, note.time.getTime() / 1000,
)}>`, )}>`,
value: note.note, value: note.note,
@@ -343,16 +400,21 @@ export class SusCommand extends Subcommand {
const message = await interaction.reply({ const message = await interaction.reply({
embeds: [noteEmbed], embeds: [noteEmbed],
components: [buttons], components: [buttons],
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
// Checks if the message is not an APIMessage // Checks if the message is not an APIMessage
if (!isMessageInstance(message)) { if (message.resource === null) {
await interaction.editReply('Failed to retrieve the message :('); await interaction.editReply('Failed to retrieve the message :(');
return; return;
} }
if (!channel.isSendable()) {
await interaction.editReply('Cannot send messages in this channel!');
return;
}
// Listen for the button presses // Listen for the button presses
const collector = channel.createMessageComponentCollector({ const collector = channel.createMessageComponentCollector({
max: 1, // Maximum of 1 button press max: 1, // Maximum of 1 button press
@@ -364,18 +426,25 @@ export class SusCommand extends Subcommand {
if (button.customId === `delete${noteId}`) { if (button.customId === `delete${noteId}`) {
await deactivateNote(noteId); await deactivateNote(noteId);
await interaction.editReply({ await interaction.editReply({
content: `${member}'s sus note (ID: ${noteId}) has been successfully removed`, content: `${user}'s sus note (ID: ${noteId}) has been successfully removed`,
embeds: [], embeds: [],
}); });
// TODO create a new Prisma function to only count and not to get a whole list of sus notes // 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 // Check how many notes the user has and if 0, then remove sus note
const notes = await findNotes(member.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 // Checks if there are no notes on the user and if there's none, remove the sus role
if (notes.length === 0) { if (notes.length === 0) {
await member.roles.remove(IDs.roles.restrictions.sus); const member = guild.members.cache.get(userId);
if (isGuildMember(member)) {
await member.roles.remove(IDs.roles.restrictions.sus);
}
} }
// Logs the removal of the sus note
await this.deleteNoteLogger(userId, mod, noteId);
} }
}); });
@@ -387,31 +456,68 @@ export class SusCommand extends Subcommand {
}); });
} }
// Logs removal of 1 sus note
private async deleteNoteLogger(userId: Snowflake, mod: User, noteId: number) {
// Find user
const user = await getUser(userId);
if (!isUser(user)) return;
// Log the sus note
const logChannel = await getTextBasedChannel(IDs.channels.logs.sus);
if (!isTextBasedChannel(logChannel)) {
this.container.logger.error('Sus: Could not fetch log channel.');
return;
} else if (!logChannel.isSendable()) {
this.container.logger.error(
'Sus: The bot does not have permission to send in the 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( public async removeAllNotes(
interaction: Subcommand.ChatInputCommandInteraction, interaction: Subcommand.ChatInputCommandInteraction,
) { ) {
// Get the arguments // Get the arguments
const user = interaction.options.getUser('user', true); const user = interaction.options.getUser('user', true);
const mod = interaction.user;
const { guild, channel } = interaction; const { guild, channel } = interaction;
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (guild === null || channel === null) { if (guild === null || !isTextBasedChannel(channel)) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild or channel!', content: 'Error fetching guild or channel!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
// Checks if managed to find GuildMember for the user // Checks if managed to find GuildMember for the user
if (member === undefined) { if (!isGuildMember(member)) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching user!', content: 'Error fetching user!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -424,8 +530,8 @@ export class SusCommand extends Subcommand {
if (notes.length === 0) { if (notes.length === 0) {
await interaction.reply({ await interaction.reply({
content: `${user} had no notes!`, content: `${user} had no notes!`,
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -444,10 +550,11 @@ export class SusCommand extends Subcommand {
) { ) {
// Get mod name // Get mod name
let mod = notes[i].modId; let mod = notes[i].modId;
const modGuildMember = guild.members.cache.get(mod); const modGuildMember = await getGuildMember(mod, guild);
if (modGuildMember !== undefined) { if (isGuildMember(modGuildMember)) {
mod = modGuildMember.displayName; mod = modGuildMember.displayName;
} }
// Add sus note to embed // Add sus note to embed
noteEmbed.addFields({ noteEmbed.addFields({
name: `Sus ID: ${ name: `Sus ID: ${
@@ -475,16 +582,21 @@ export class SusCommand extends Subcommand {
const message = await interaction.reply({ const message = await interaction.reply({
embeds: [noteEmbed], embeds: [noteEmbed],
components: [buttons], components: [buttons],
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
// Checks if the message is not an APIMessage // Checks if the message is not an APIMessage
if (!isMessageInstance(message)) { if (message.resource === null) {
await interaction.editReply('Failed to retrieve the message :('); await interaction.editReply('Failed to retrieve the message :(');
return; return;
} }
if (!channel.isSendable()) {
await interaction.editReply('Cannot send messages in this channel!');
return;
}
// Listen for the button presses // Listen for the button presses
const collector = channel.createMessageComponentCollector({ const collector = channel.createMessageComponentCollector({
max: 1, // Maximum of 1 button press max: 1, // Maximum of 1 button press
@@ -501,6 +613,8 @@ export class SusCommand extends Subcommand {
embeds: [], embeds: [],
}); });
} }
await this.deleteAllNotesLogger(user, mod);
}); });
// Remove the buttons after they have been clicked // Remove the buttons after they have been clicked
@@ -514,51 +628,34 @@ export class SusCommand extends Subcommand {
await member.roles.remove(IDs.roles.restrictions.sus); await member.roles.remove(IDs.roles.restrictions.sus);
} }
// Non Application Command method of adding a sus note // Logs removal of 1 sus note
// xlevra begged me to add this... so I guess here it is private async deleteAllNotesLogger(user: User, mod: User) {
public async addMessage(message: Message, args: Args) { // Log the sus note
// Get arguments const logChannel = await getTextBasedChannel(IDs.channels.logs.sus);
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;
if (note === null) { if (!isTextChannel(logChannel)) {
await message.react(''); this.container.logger.error('Sus: Could not fetch log channel.');
await message.reply('No sus note was provided!');
return; return;
} } else if (!logChannel.isSendable()) {
this.container.logger.error(
if (mod === null) { 'Sus: Could not not send in the log channel.',
await message.react('❌');
await message.reply(
'Moderator not found! Try again or contact a developer!',
); );
return; return;
} }
// Check if user and mod are on the database const embed = new EmbedBuilder()
await addExistingUser(user); .setColor('#28A745')
await addExistingUser(mod); .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}` });
await addToDatabase(user.id, mod.id, note); await logChannel.send({ embeds: [embed] });
// 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('✅');
} }
} }

View File

@@ -18,12 +18,18 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { GuildMember, Message } from 'discord.js'; import { GuildMember, Message, MessageFlagsBitField } from 'discord.js';
import { addMute, removeMute, checkActive } from '#utils/database/vcMute'; import {
addMute,
removeMute,
checkActive,
} from '#utils/database/moderation/vcMute';
import { addExistingUser } from '#utils/database/dbExistingUser'; import { addExistingUser } from '#utils/database/dbExistingUser';
import { getGuildMember } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class VCMuteCommand extends Command { export class VCMuteCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'vcmute', name: 'vcmute',
@@ -62,29 +68,38 @@ export class VCMuteCommand extends Command {
// Get the arguments // Get the arguments
const user = interaction.options.getUser('user', true); const user = interaction.options.getUser('user', true);
const reason = interaction.options.getString('reason'); const reason = interaction.options.getString('reason');
const modUser = interaction.user; const mod = interaction.member;
const { guild } = interaction; const { guild } = interaction;
// Checks if all the variables are of the right type // Checks if all the variables are of the right type
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
// Gets guildMember whilst removing the ability of each other variables being null // Gets guildMember whilst removing the ability of each other variables being null
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const mod = guild.members.cache.get(modUser.id);
// Checks if guildMember is null // Checks if `member` was found
if (member === undefined || mod === undefined) { if (!isGuildMember(member)) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching user!', content: 'Error fetching user!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
});
return;
}
// Checks if `mod` was found
if (!isGuildMember(mod)) {
await interaction.reply({
content: 'Error fetching your user!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
}); });
return; return;
} }
@@ -98,8 +113,8 @@ export class VCMuteCommand extends Command {
await interaction.reply({ await interaction.reply({
content: `Removed server mute from ${user}`, content: `Removed server mute from ${user}`,
fetchReply: true, flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true, withResponse: true,
}); });
return; return;
} }
@@ -114,8 +129,8 @@ export class VCMuteCommand extends Command {
await addMute(member.id, mod.id, reason); await addMute(member.id, mod.id, reason);
await interaction.reply({ await interaction.reply({
content: `Server muted ${user}`, content: `Server muted ${user}`,
fetchReply: true, flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true, withResponse: true,
}); });
} }
@@ -133,7 +148,7 @@ export class VCMuteCommand extends Command {
const reason = args.finished ? null : await args.rest('string'); const reason = args.finished ? null : await args.rest('string');
const mod = message.member; const mod = message.member;
if (mod === null) { if (!isGuildMember(mod)) {
await message.react('❌'); await message.react('❌');
await message.reply( await message.reply(
'Moderator not found! Try again or contact a developer!', 'Moderator not found! Try again or contact a developer!',

View File

@@ -1,127 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/*
Animal Rights Advocates Discord Bot
Copyright (C) 2023 Anthony Berg
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Args, Command } from '@sapphire/framework';
import type { User, Message, Snowflake, Guild } from 'discord.js';
import { addExistingUser, updateUser } from '#utils/database/dbExistingUser';
import { addWarn } from '#utils/database/warnings';
/*
This command is not intended to be functional for now, this is purely to log
warnings onto a database, so if we were to switch purely to ARA Bot, it would
mean we would have a lot of the warns already in the database.
*/
export class WarnCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) {
super(context, {
...options,
name: 'warn',
description: 'Warns a user (only used for logging to a database for now)',
preconditions: [['CoordinatorOnly', 'ModOnly']],
});
}
public async messageRun(message: Message, args: Args) {
// Get arguments
let user: User;
try {
user = await args.pick('user');
} catch {
await message.react('❌');
await message.reply('User was not provided!');
return;
}
const reason = args.finished ? null : await args.rest('string');
const mod = message.member;
if (reason === null) {
await message.react('❌');
await message.reply('Warn reason was not provided!');
return;
}
if (mod === null) {
await message.react('❌');
await message.reply(
'Moderator not found! Try again or contact a developer!',
);
return;
}
const { guild } = message;
if (guild === null) {
await message.react('❌');
await message.reply('Guild not found! Try again or contact a developer!');
return;
}
const warn = await this.warn(user.id, mod.id, reason, guild);
if (!warn.success) {
await message.react('❌');
}
// await message.react('✅');
}
private async warn(
userId: Snowflake,
modId: Snowflake,
reason: string,
guild: Guild,
) {
const info = {
message: '',
success: false,
};
// Gets mod's GuildMember
const mod = guild.members.cache.get(modId);
// Checks if guildMember is null
if (mod === undefined) {
info.message = 'Error fetching mod!';
return info;
}
// Check if mod is in database
await updateUser(mod);
// Gets guildMember
let member = guild.members.cache.get(userId);
if (member === undefined) {
member = await guild.members.fetch(userId).catch(() => undefined);
}
if (member === undefined) {
info.message = 'User is not on this server';
return info;
}
await addExistingUser(member);
await addWarn(userId, modId, reason);
info.message = `Warned ${member}`;
info.success = true;
return info;
}
}

View File

@@ -0,0 +1,180 @@
// 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, MessageFlagsBitField } from 'discord.js';
import type { Message, User } from 'discord.js';
import IDs from '#utils/ids';
import {
deleteWarning,
fetchWarning,
} from '#utils/database/moderation/warnings';
import { checkStaff } from '#utils/checker';
import { getTextBasedChannel, getUser } from '#utils/fetcher';
import { isUser } from '#utils/typeChecking';
import { isTextChannel } from '@sapphire/discord.js-utilities';
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 staffChannel = checkStaff(interaction.channel);
await interaction.deferReply({
flags: staffChannel ? undefined : MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.deleteWarning(warningId, mod);
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);
await message.reply({ content: info.message, embeds: info.embeds });
if (!info.success) {
await message.react('❌');
}
}
private async deleteWarning(warningId: number, mod: User) {
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;
const user = await getUser(userId);
if (!isUser(user)) {
info.message = `Deleted warning ID \`${warningId}\`, but the user could not be found!`;
return info;
}
// Log the warnings deletion
const logChannel = await getTextBasedChannel(IDs.channels.logs.sus);
if (!isTextChannel(logChannel)) {
this.container.logger.error(
'Delete Warning: Could not fetch log channel',
);
info.message =
`Deleted warning for ${user} (Warning ID: ${warningId} but ` +
'could not find the log channel.';
return info;
} else if (!logChannel.isSendable()) {
this.container.logger.error(
'Delete Warning: The bot does not have permission to send in the logs channel!',
);
info.message =
`Deleted warning for ${user} (Warning ID: ${warningId} but ` +
"But this hasn't been logged in a text channel as the bot does not have permission to send logs!";
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,220 @@
// 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, RegisterBehavior } from '@sapphire/framework';
import {
EmbedBuilder,
Guild,
Message,
MessageFlagsBitField,
Snowflake,
User,
} from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser';
import { addWarn } from '#utils/database/moderation/warnings';
import IDs from '#utils/ids';
import { getGuildMember, getTextBasedChannel, getUser } from '#utils/fetcher';
import { isGuildMember, isTextChannel } from '@sapphire/discord.js-utilities';
import { isUser } from '#utils/typeChecking';
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!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: 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.author;
if (reason === null) {
await message.react('❌');
await message.reply('Warn 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 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 = await getGuildMember(modId, guild);
// Checks if guildMember is null
if (!isGuildMember(mod)) {
info.message = 'Error fetching mod!';
return info;
}
// Check if mod is in database
await updateUser(mod);
// Gets User for person being restricted
const user = await getUser(userId);
if (!isUser(user)) {
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
const logChannel = await getTextBasedChannel(IDs.channels.logs.sus);
if (!isTextChannel(logChannel)) {
this.container.logger.error('Warn: 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;
} else if (!logChannel.isSendable()) {
this.container.logger.error(
'Warn: The bot does not have permission to send in the logs channel!',
);
info.message = `Warned ${user}, but the bot does not have permission to send in the logs channel!`;
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,164 @@
// 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, MessageFlagsBitField } from 'discord.js';
import type { Message, Guild, User } from 'discord.js';
import IDs from '#utils/ids';
import { fetchWarnings } from '#utils/database/moderation/warnings';
import { checkStaff } from '#utils/checker';
import { createWarningsEmbed } from '#utils/embeds';
import { isUser } from '#utils/typeChecking';
import { isTextChannel } from '@sapphire/discord.js-utilities';
import { getUser } from '#utils/fetcher';
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!',
flags: MessageFlagsBitField.Flags.Ephemeral,
withResponse: true,
});
return;
}
const staffChannel = checkStaff(interaction.channel);
await interaction.deferReply({
flags: staffChannel ? undefined : MessageFlagsBitField.Flags.Ephemeral,
});
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 (!isUser(user)) {
const { channel } = message;
if (!isTextChannel(channel)) {
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 = await getUser(userId);
}
}
}
if (!isUser(user)) {
await message.react('❌');
await message.reply(
'User was not provided! (You most likely provided a user incorrectly.)',
);
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

@@ -19,7 +19,12 @@
import { Subcommand } from '@sapphire/plugin-subcommands'; import { Subcommand } from '@sapphire/plugin-subcommands';
import { RegisterBehavior } from '@sapphire/framework'; import { RegisterBehavior } from '@sapphire/framework';
import type { Snowflake } from 'discord.js'; import {
ChannelType,
MessageFlagsBitField,
PermissionsBitField,
Snowflake,
} from 'discord.js';
import { updateUser } from '#utils/database/dbExistingUser'; import { updateUser } from '#utils/database/dbExistingUser';
import { import {
addStatUser, addStatUser,
@@ -36,9 +41,17 @@ import {
} from '#utils/database/outreach'; } from '#utils/database/outreach';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder } from 'discord.js';
import {
isGuildMember,
isTextBasedChannel,
} from '@sapphire/discord.js-utilities';
import { getGuildMember, getTextBasedChannel } from '#utils/fetcher';
export class OutreachCommand extends Subcommand { export class OutreachCommand extends Subcommand {
public constructor(context: Subcommand.Context, options: Subcommand.Options) { public constructor(
context: Subcommand.LoaderContext,
options: Subcommand.Options,
) {
super(context, { super(context, {
...options, ...options,
name: 'outreach', name: 'outreach',
@@ -63,7 +76,6 @@ export class OutreachCommand extends Subcommand {
], ],
}, },
], ],
preconditions: ['ModOnly'],
}); });
} }
@@ -182,31 +194,29 @@ export class OutreachCommand extends Subcommand {
interaction: Subcommand.ChatInputCommandInteraction, interaction: Subcommand.ChatInputCommandInteraction,
) { ) {
// const start = interaction.options.getBoolean('start'); // const start = interaction.options.getBoolean('start');
const modUser = interaction.user; const mod = interaction.member;
const { guild } = interaction; const { guild } = interaction;
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Mod or guild was not found!', content: 'Mod or guild was not found!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
const mod = guild.members.cache.get(modUser.id); if (!isGuildMember(mod)) {
if (mod === undefined) {
await interaction.reply({ await interaction.reply({
content: 'Mod was not found!', content: 'Outreach Leader was not found!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
if (!mod.roles.cache.has(IDs.roles.staff.outreachCoordinator)) { if (!mod.roles.cache.has(IDs.roles.staff.outreachLeader)) {
await interaction.reply({ await interaction.reply({
content: 'You need to be an Outreach Coordinator to run this command!', content: 'You need to be an Outreach Leader to run this command!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
@@ -214,52 +224,52 @@ export class OutreachCommand extends Subcommand {
if (await checkActiveEvent()) { if (await checkActiveEvent()) {
await interaction.reply({ await interaction.reply({
content: 'There is already an active event!', content: 'There is already an active event!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
await updateUser(mod); await updateUser(mod);
await createEvent(modUser.id); await createEvent(mod.id);
await interaction.reply({ await interaction.reply({
content: 'Created the event!', content: 'Created the event!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
} }
public async eventEnd(interaction: Subcommand.ChatInputCommandInteraction) { public async eventEnd(interaction: Subcommand.ChatInputCommandInteraction) {
const modUser = interaction.user; const mod = interaction.member;
const { guild } = interaction; const { guild } = interaction;
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Guild not found!', content: 'Guild not found!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
const mod = guild.members.cache.get(modUser.id); if (!isGuildMember(mod)) {
if (mod === undefined) {
await interaction.reply({ await interaction.reply({
content: 'Your guild member was not found!', content: 'Your guild member was not found!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
if (!mod.roles.cache.has(IDs.roles.staff.outreachCoordinator)) { if (!mod.roles.cache.has(IDs.roles.staff.outreachLeader)) {
await interaction.reply({ await interaction.reply({
content: 'You need to be an Outreach Coordinator to run this command!', content: 'You need to be an Outreach Leader to run this command!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const event = await getCurrentEvent(); const event = await getCurrentEvent();
@@ -272,7 +282,8 @@ export class OutreachCommand extends Subcommand {
stat.forEach(({ role }) => { stat.forEach(({ role }) => {
if (role !== null) { if (role !== null) {
guild.roles.delete(role.roleId); guild.roles.delete(role.roleId); // Delete role
guild.channels.delete(role.channelId); // Delete VC
} }
}); });
@@ -296,9 +307,9 @@ export class OutreachCommand extends Subcommand {
educated += group.educated; educated += group.educated;
}); });
const activist = guild.channels.cache.get(IDs.channels.activism.activism); const activist = await getTextBasedChannel(IDs.channels.activism.activism);
if (activist === undefined || !activist.isTextBased()) { if (!isTextBasedChannel(activist)) {
await interaction.editReply( await interaction.editReply(
'Event has now ended, but could not post statistics!', 'Event has now ended, but could not post statistics!',
); );
@@ -348,12 +359,14 @@ export class OutreachCommand extends Subcommand {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Guild not found!', content: 'Guild not found!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
if ((await getStatFromLeader(leader.id)) !== null) { if ((await getStatFromLeader(leader.id)) !== null) {
await interaction.editReply( await interaction.editReply(
@@ -374,9 +387,9 @@ export class OutreachCommand extends Subcommand {
const statGroups = await getStatGroups(event.id); const statGroups = await getStatGroups(event.id);
const groupNo = statGroups.length + 1; const groupNo = statGroups.length + 1;
const leaderMember = await guild.members.cache.get(leader.id); const leaderMember = await getGuildMember(leader.id, guild);
if (leaderMember === undefined) { if (!isGuildMember(leaderMember)) {
await interaction.editReply({ await interaction.editReply({
content: `Could not find ${leader}'s guild member.`, content: `Could not find ${leader}'s guild member.`,
}); });
@@ -385,14 +398,66 @@ export class OutreachCommand extends Subcommand {
await updateUser(leaderMember); await updateUser(leaderMember);
// Create role for group
const role = await guild.roles.create({ const role = await guild.roles.create({
name: `Outreach Group ${groupNo}`, name: `Outreach Group ${groupNo}`,
mentionable: true,
}); });
await createStat(event.id, leader.id, role.id); // Create a voice channel for group
const channel = await guild.channels.create({
name: `Outreach Group ${groupNo}`,
type: ChannelType.GuildVoice,
parent: IDs.categories.activism,
permissionOverwrites: [
{
id: guild.roles.everyone,
deny: [
PermissionsBitField.Flags.SendMessages,
PermissionsBitField.Flags.Connect,
PermissionsBitField.Flags.ViewChannel,
],
},
{
id: IDs.roles.vegan.activist,
allow: [PermissionsBitField.Flags.ViewChannel],
},
{
id: role.id, // Permissions for the specific group
allow: [
PermissionsBitField.Flags.SendMessages,
PermissionsBitField.Flags.Connect,
],
},
{
id: IDs.roles.staff.outreachLeader,
allow: [
PermissionsBitField.Flags.SendMessages,
PermissionsBitField.Flags.Connect,
],
},
],
});
// Create stats in database
await createStat(event.id, leader.id, role.id, channel.id);
// Give group leader role
await leaderMember.roles.add(role); await leaderMember.roles.add(role);
// Send message in VC with a welcome and reminder
await channel.send(
`Welcome ${role}, ${leaderMember} is going to be the leader of your group!\n\n` +
'Remember to keep track of stats during activism with `/outreach group update` and' +
'to have these questions in mind whilst doing activism:\n' +
'- How many said would go vegan?\n' +
'- How many seriously considered being vegan?\n' +
'- How many people had anti-vegan viewpoints?\n' +
'- How many thanked you for the conversation?\n' +
'- How many said they would watch a vegan documentary?\n' +
'- How many got educated on veganism or the animal industry?',
);
await interaction.editReply({ await interaction.editReply({
content: `Created a group with the leader being ${leader}`, content: `Created a group with the leader being ${leader}`,
}); });
@@ -401,18 +466,27 @@ export class OutreachCommand extends Subcommand {
public async groupAdd(interaction: Subcommand.ChatInputCommandInteraction) { public async groupAdd(interaction: Subcommand.ChatInputCommandInteraction) {
const user = interaction.options.getUser('user', true); const user = interaction.options.getUser('user', true);
const group = interaction.options.getRole('group'); const group = interaction.options.getRole('group');
const leader = interaction.user; const leader = interaction.member;
const { guild } = interaction; const { guild } = interaction;
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Could not find guild!', content: 'Could not find guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); if (!isGuildMember(leader)) {
await interaction.editReply({
content: 'Could not find your GuildMember!',
});
return;
}
await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
let statId: number; let statId: number;
let roleId: Snowflake | undefined; let roleId: Snowflake | undefined;
@@ -428,18 +502,9 @@ export class OutreachCommand extends Subcommand {
return; 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 ( if (
leader.id !== stat.stat.leaderId && leader.id !== stat.stat.leaderId &&
!leaderMember.roles.cache.has(IDs.roles.staff.outreachCoordinator) !leader.roles.cache.has(IDs.roles.staff.outreachLeader)
) { ) {
await interaction.editReply({ await interaction.editReply({
content: `You are not the leader for ${group}`, content: `You are not the leader for ${group}`,
@@ -471,9 +536,9 @@ export class OutreachCommand extends Subcommand {
return; return;
} }
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
if (member === undefined) { if (!isGuildMember(member)) {
await interaction.editReply({ await interaction.editReply({
content: 'Could not fetch the member!', content: 'Could not fetch the member!',
}); });
@@ -513,15 +578,9 @@ export class OutreachCommand extends Subcommand {
educated: educated !== null ? educated : 0, educated: educated !== null ? educated : 0,
}; };
if (leader === null) { await interaction.deferReply({
await interaction.reply({ flags: MessageFlagsBitField.Flags.Ephemeral,
content: 'Could not find your user!', });
ephemeral: true,
});
return;
}
await interaction.deferReply({ ephemeral: true });
const stat = await getStatFromLeader(leader.id); const stat = await getStatFromLeader(leader.id);

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class BookClubCommand extends Command { export class BookClubCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'bookclub', name: 'bookclub',
@@ -62,13 +65,15 @@ export class BookClubCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageBookClub(user, mod, guild); const info = await this.manageBookClub(user, mod, guild);
@@ -88,14 +93,6 @@ export class BookClubCommand extends Command {
const mod = message.author; 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; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -115,16 +112,16 @@ export class BookClubCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const bookClub = guild.roles.cache.get(IDs.roles.bookClub); const bookClub = await getRole(IDs.roles.bookClub, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (bookClub === undefined) { if (!isRole(bookClub)) {
info.message = 'Error fetching book club role from cache!'; info.message = 'Error fetching book club role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class DebateHostCommand extends Command { export class DebateHostCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'debatehost', name: 'debatehost',
@@ -63,13 +66,15 @@ export class DebateHostCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageDebateHost(user, mod, guild); const info = await this.manageDebateHost(user, mod, guild);
@@ -89,14 +94,6 @@ export class DebateHostCommand extends Command {
const mod = message.author; 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; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -116,16 +113,16 @@ export class DebateHostCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const debateHost = guild.roles.cache.get(IDs.roles.debateHost); const debateHost = await getRole(IDs.roles.debateHost, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (debateHost === undefined) { if (!isRole(debateHost)) {
info.message = 'Error fetching debate host role from cache!'; info.message = 'Error fetching debate host role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class GameNightHostCommand extends Command { export class GameNightHostCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'gamenight', name: 'gamenight',
@@ -62,13 +65,15 @@ export class GameNightHostCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageGameNight(user, mod, guild); const info = await this.manageGameNight(user, mod, guild);
@@ -88,14 +93,6 @@ export class GameNightHostCommand extends Command {
const mod = message.author; 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; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -115,16 +112,16 @@ export class GameNightHostCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const gameNightHost = guild.roles.cache.get(IDs.roles.gameNightHost); const gameNightHost = await getRole(IDs.roles.gameNightHost, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (gameNightHost === undefined) { if (!isRole(gameNightHost)) {
info.message = 'Error fetching game night host role from cache!'; info.message = 'Error fetching game night host role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class GuestCommand extends Command { export class GuestCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'guest', name: 'guest',
@@ -62,13 +65,15 @@ export class GuestCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageGuest(user, mod, guild); const info = await this.manageGuest(user, mod, guild);
@@ -88,14 +93,6 @@ export class GuestCommand extends Command {
const mod = message.author; 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; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -115,16 +112,16 @@ export class GuestCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const guest = guild.roles.cache.get(IDs.roles.guest); const guest = await getRole(IDs.roles.guest, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (guest === undefined) { if (!isRole(guest)) {
info.message = 'Error fetching guest role from cache!'; info.message = 'Error fetching guest role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { isRole } from '#utils/typeChecking';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class MentorCommand extends Command { export class MentorCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'mentor', name: 'mentor',
@@ -63,13 +66,15 @@ export class MentorCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageMentor(user, mod, guild); const info = await this.manageMentor(user, mod, guild);
@@ -89,14 +94,6 @@ export class MentorCommand extends Command {
const mod = message.author; 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; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -116,16 +113,16 @@ export class MentorCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const mentor = guild.roles.cache.get(IDs.roles.staff.mentor); const mentor = await getRole(IDs.roles.staff.mentor, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (mentor === undefined) { if (!isRole(mentor)) {
info.message = 'Error fetching mentor role from cache!'; info.message = 'Error fetching mentor role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class ModCommand extends Command { export class ModCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'mod', name: 'mod',
@@ -62,13 +65,15 @@ export class ModCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageMod(user, mod, guild); const info = await this.manageMod(user, mod, guild);
@@ -88,14 +93,6 @@ export class ModCommand extends Command {
const mod = message.author; 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; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -115,16 +112,16 @@ export class ModCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const moderator = guild.roles.cache.get(IDs.roles.staff.moderator); const moderator = await getRole(IDs.roles.staff.moderator, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (moderator === undefined) { if (!isRole(moderator)) {
info.message = 'Error fetching the moderator role from cache!'; info.message = 'Error fetching the moderator role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class RestrictedAccessCommand extends Command { export class RestrictedAccessCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'restrictedaccess', name: 'restrictedaccess',
@@ -63,13 +66,15 @@ export class RestrictedAccessCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageRestrictedAccess(user, mod, guild); const info = await this.manageRestrictedAccess(user, mod, guild);
@@ -89,14 +94,6 @@ export class RestrictedAccessCommand extends Command {
const mod = message.author; 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; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -116,16 +113,16 @@ export class RestrictedAccessCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const restricted = guild.roles.cache.get(IDs.roles.staff.restricted); const restricted = await getRole(IDs.roles.staff.restricted, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (restricted === undefined) { if (!isRole(restricted)) {
info.message = 'Error fetching the restricted access role from cache!'; info.message = 'Error fetching the restricted access role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class StageHostCommand extends Command { export class StageHostCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'stagehost', name: 'stagehost',
@@ -62,13 +65,15 @@ export class StageHostCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageStageHost(user, mod, guild); const info = await this.manageStageHost(user, mod, guild);
@@ -88,14 +93,6 @@ export class StageHostCommand extends Command {
const mod = message.author; 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; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -115,16 +112,16 @@ export class StageHostCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const stageHost = guild.roles.cache.get(IDs.roles.stageHost); const stageHost = await getRole(IDs.roles.stageHost, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (stageHost === undefined) { if (!isRole(stageHost)) {
info.message = 'Error fetching stage host role from cache!'; info.message = 'Error fetching stage host role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class TrialModCommand extends Command { export class TrialModCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'trialmod', name: 'trialmod',
@@ -63,13 +66,15 @@ export class TrialModCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageMod(user, mod, guild); const info = await this.manageMod(user, mod, guild);
@@ -89,14 +94,6 @@ export class TrialModCommand extends Command {
const mod = message.author; 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; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -116,16 +113,16 @@ export class TrialModCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const moderator = guild.roles.cache.get(IDs.roles.staff.trialModerator); const moderator = await getRole(IDs.roles.staff.trialModerator, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (moderator === undefined) { if (!isRole(moderator)) {
info.message = 'Error fetching the trial moderator role from cache!'; info.message = 'Error fetching the trial moderator role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class TrialVerifierCommand extends Command { export class TrialVerifierCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'trialverifier', name: 'trialverifier',
@@ -62,13 +65,15 @@ export class TrialVerifierCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageTrialVerifier(user, mod, guild); const info = await this.manageTrialVerifier(user, mod, guild);
@@ -88,14 +93,6 @@ export class TrialVerifierCommand extends Command {
const mod = message.author; const mod = message.author;
if (mod === null) {
await message.react('❌');
await message.reply(
'Verifier coordinator not found! Try again or contact a developer!',
);
return;
}
const { guild } = message; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -115,16 +112,16 @@ export class TrialVerifierCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const trialVerifier = guild.roles.cache.get(IDs.roles.staff.trialVerifier); const trialVerifier = await getRole(IDs.roles.staff.trialVerifier, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (trialVerifier === undefined) { if (!isRole(trialVerifier)) {
info.message = 'Error fetching the trial verifier role from cache!'; info.message = 'Error fetching the trial verifier role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class VerifierCommand extends Command { export class VerifierCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'verifier', name: 'verifier',
@@ -62,13 +65,15 @@ export class VerifierCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageVerifier(user, mod, guild); const info = await this.manageVerifier(user, mod, guild);
@@ -88,14 +93,6 @@ export class VerifierCommand extends Command {
const mod = message.author; const mod = message.author;
if (mod === null) {
await message.react('❌');
await message.reply(
'Verifier coordinator not found! Try again or contact a developer!',
);
return;
}
const { guild } = message; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -115,16 +112,16 @@ export class VerifierCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const verifier = guild.roles.cache.get(IDs.roles.staff.verifier); const verifier = await getRole(IDs.roles.staff.verifier, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (verifier === undefined) { if (!isRole(verifier)) {
info.message = 'Error fetching verifier role from cache!'; info.message = 'Error fetching verifier role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class ActivistCommand extends Command { export class ActivistCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'activist', name: 'activist',
@@ -65,13 +68,15 @@ export class ActivistCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageActivist(user, mod, guild); const info = await this.manageActivist(user, mod, guild);
@@ -91,12 +96,6 @@ export class ActivistCommand extends Command {
const mod = message.author; const mod = message.author;
if (mod === null) {
await message.react('❌');
await message.reply('Staff not found! Try again or contact a developer!');
return;
}
const { guild } = message; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -116,22 +115,22 @@ export class ActivistCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const modMember = guild.members.cache.get(mod.id); const modMember = await getGuildMember(mod.id, guild);
const activist = guild.roles.cache.get(IDs.roles.vegan.activist); const activist = await getRole(IDs.roles.vegan.activist, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (modMember === undefined) { if (!isGuildMember(modMember)) {
info.message = "Error fetching the staff's guild member!"; info.message = "Error fetching the staff's guild member!";
return info; return info;
} }
if (activist === undefined) { if (!isRole(activist)) {
info.message = 'Error fetching activist role from cache!'; info.message = 'Error fetching activist role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class ARAVeganCommand extends Command { export class ARAVeganCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'aravegan', name: 'aravegan',
@@ -64,13 +67,15 @@ export class ARAVeganCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageVegan(user, mod, guild); const info = await this.manageVegan(user, mod, guild);
@@ -90,12 +95,6 @@ export class ARAVeganCommand extends Command {
const mod = message.author; const mod = message.author;
if (mod === null) {
await message.react('❌');
await message.reply('Staff not found! Try again or contact a developer!');
return;
}
const { guild } = message; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -115,22 +114,22 @@ export class ARAVeganCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const modMember = guild.members.cache.get(mod.id); const modMember = await getGuildMember(mod.id, guild);
const vegan = guild.roles.cache.get(IDs.roles.vegan.araVegan); const vegan = await getRole(IDs.roles.vegan.araVegan, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (modMember === undefined) { if (!isGuildMember(modMember)) {
info.message = "Error fetching the staff's guild member!"; info.message = "Error fetching the staff's guild member!";
return info; return info;
} }
if (vegan === undefined) { if (!isRole(vegan)) {
info.message = 'Error fetching vegan role from cache!'; info.message = 'Error fetching vegan role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class ConvincedCommand extends Command { export class ConvincedCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'convinced', name: 'convinced',
@@ -63,13 +66,15 @@ export class ConvincedCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageConvinced(user, mod, guild); const info = await this.manageConvinced(user, mod, guild);
@@ -89,12 +94,6 @@ export class ConvincedCommand extends Command {
const mod = message.author; const mod = message.author;
if (mod === null) {
await message.react('❌');
await message.reply('Mod not found! Try again or contact a developer!');
return;
}
const { guild } = message; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -114,16 +113,16 @@ export class ConvincedCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const convinced = guild.roles.cache.get(IDs.roles.nonvegan.convinced); const convinced = await getRole(IDs.roles.nonvegan.convinced, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (convinced === undefined) { if (!isRole(convinced)) {
info.message = 'Error fetching coordinator role from cache!'; info.message = 'Error fetching coordinator role from cache!';
return info; return info;
} }

View File

@@ -18,18 +18,21 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class PlusCommand extends Command { export class PlusCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'plus', name: 'plus',
aliases: ['+'], aliases: ['+'],
description: 'Give/remove the plus role', description: 'Give/remove the plus role',
preconditions: [['CoordinatorOnly', 'ModOnly']], preconditions: [['CoordinatorOnly', 'VerifierOnly', 'ModOnly']],
}); });
} }
@@ -63,7 +66,7 @@ export class PlusCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
@@ -88,14 +91,6 @@ export class PlusCommand extends Command {
const mod = message.author; const mod = message.author;
if (mod === null) {
await message.react('❌');
await message.reply(
'Coordinator not found! Try again or contact a developer!',
);
return;
}
const { guild } = message; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -115,16 +110,16 @@ export class PlusCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const plus = guild.roles.cache.get(IDs.roles.vegan.plus); const plus = await getRole(IDs.roles.vegan.plus, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (plus === undefined) { if (!isRole(plus)) {
info.message = 'Error fetching plus role from cache!'; info.message = 'Error fetching plus role from cache!';
return info; return info;
} }
@@ -138,6 +133,14 @@ export class PlusCommand extends Command {
info.success = true; info.success = true;
return info; return info;
} }
// Checks if the user is vegan before giving the plus role
// If not, stop from giving the plus role
if (!member.roles.cache.has(IDs.roles.vegan.vegan)) {
info.message = `Can't give ${user} the vegan role as they are not vegan!`;
return info;
}
// Add Plus role to the user // Add Plus role to the user
await member.roles.add(plus); await member.roles.add(plus);
await roleAddLog(user.id, mod.id, plus); await roleAddLog(user.id, mod.id, plus);

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class TrustedCommand extends Command { export class TrustedCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'trusted', name: 'trusted',
@@ -63,13 +66,15 @@ export class TrustedCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageTrusted(user, mod, guild); const info = await this.manageTrusted(user, mod, guild);
@@ -89,12 +94,6 @@ export class TrustedCommand extends Command {
const mod = message.author; const mod = message.author;
if (mod === null) {
await message.react('❌');
await message.reply('Mod not found! Try again or contact a developer!');
return;
}
const { guild } = message; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -114,16 +113,16 @@ export class TrustedCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const trusted = guild.roles.cache.get(IDs.roles.trusted); const trusted = await getRole(IDs.roles.trusted, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (trusted === undefined) { if (!isRole(trusted)) {
info.message = 'Error fetching trusted role from cache!'; info.message = 'Error fetching trusted role from cache!';
return info; return info;
} }
@@ -146,8 +145,8 @@ export class TrustedCommand extends Command {
.send( .send(
`You have been given the ${trusted.name} role by ${mod}!` + `You have been given the ${trusted.name} role by ${mod}!` +
'\n\nThis role allows you to post attachments to the server and stream in VCs.' + '\n\nThis role allows you to post attachments to the server and stream in VCs.' +
"\nMake sure that you follow the rules, and don't post anything NSFW, anything objectifying animals and follow Discord's ToS." + '\nMake sure that you follow the rules, especially by **not** posting anything **NSFW**, and **no animal products or consumption of animal products**.' +
`\nNot following these rules can result in the removal of the ${trusted.name} role.`, `\n\nNot following these rules will result in the **immediate removal** of the ${trusted.name} role.`,
) )
.catch(() => {}); .catch(() => {});
info.success = true; info.success = true;

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class VeganCommand extends Command { export class VeganCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'vegan', name: 'vegan',
@@ -65,13 +68,15 @@ export class VeganCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageVegan(user, mod, guild); const info = await this.manageVegan(user, mod, guild);
@@ -91,12 +96,6 @@ export class VeganCommand extends Command {
const mod = message.author; const mod = message.author;
if (mod === null) {
await message.react('❌');
await message.reply('Staff not found! Try again or contact a developer!');
return;
}
const { guild } = message; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -116,22 +115,22 @@ export class VeganCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const modMember = guild.members.cache.get(mod.id); const modMember = await getGuildMember(mod.id, guild);
const vegan = guild.roles.cache.get(IDs.roles.vegan.vegan); const vegan = await getRole(IDs.roles.vegan.vegan, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (modMember === undefined) { if (!isGuildMember(modMember)) {
info.message = "Error fetching the staff's guild member!"; info.message = "Error fetching the staff's guild member!";
return info; return info;
} }
if (vegan === undefined) { if (!isRole(vegan)) {
info.message = 'Error fetching vegan role from cache!'; info.message = 'Error fetching vegan role from cache!';
return info; return info;
} }

View File

@@ -18,12 +18,15 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Guild, User, Message } from 'discord.js'; import { Guild, User, Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { roleAddLog, roleRemoveLog } from '#utils/logging/role'; import { roleAddLog, roleRemoveLog } from '#utils/logging/role';
import { getGuildMember, getRole } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
import { isRole } from '#utils/typeChecking';
export class VegCuriousCommand extends Command { export class VegCuriousCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'vegcurious', name: 'vegcurious',
@@ -63,13 +66,15 @@ export class VegCuriousCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
const info = await this.manageVegCurious(user, mod, guild); const info = await this.manageVegCurious(user, mod, guild);
@@ -89,12 +94,6 @@ export class VegCuriousCommand extends Command {
const mod = message.author; const mod = message.author;
if (mod === null) {
await message.react('❌');
await message.reply('Staff not found! Try again or contact a developer!');
return;
}
const { guild } = message; const { guild } = message;
if (guild === null) { if (guild === null) {
@@ -114,22 +113,22 @@ export class VegCuriousCommand extends Command {
message: '', message: '',
success: false, success: false,
}; };
const member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
const modMember = guild.members.cache.get(mod.id); const modMember = await getGuildMember(mod.id, guild);
const vegCurious = guild.roles.cache.get(IDs.roles.nonvegan.vegCurious); const vegCurious = await getRole(IDs.roles.nonvegan.vegCurious, guild);
// Checks if user's GuildMember was found in cache // Checks if user's GuildMember was found in cache
if (member === undefined) { if (!isGuildMember(member)) {
info.message = 'Error fetching guild member for the user!'; info.message = 'Error fetching guild member for the user!';
return info; return info;
} }
if (modMember === undefined) { if (!isGuildMember(modMember)) {
info.message = "Error fetching the staff's guild member!"; info.message = "Error fetching the staff's guild member!";
return info; return info;
} }
if (vegCurious === undefined) { if (!isRole(vegCurious)) {
info.message = 'Error fetching veg curious role from cache!'; info.message = 'Error fetching veg curious role from cache!';
return info; return info;
} }

View File

@@ -18,10 +18,10 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import type { Message } from 'discord.js'; import { Message, MessageFlagsBitField } from 'discord.js';
export class ApplyCommand extends Command { export class ApplyCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'apply', name: 'apply',
@@ -47,8 +47,8 @@ export class ApplyCommand extends Command {
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
await interaction.reply({ await interaction.reply({
content: this.message, content: this.message,
fetchReply: true, flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true, withResponse: true,
}); });
} }

View File

@@ -18,11 +18,13 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import type { Message } from 'discord.js'; import { Message, MessageFlagsBitField } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getRole } from '#utils/fetcher';
import { isRole } from '#utils/typeChecking';
export class RenameUserCommand extends Command { export class RenameUserCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'count', name: 'count',
@@ -47,22 +49,22 @@ export class RenameUserCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await guild.members.fetch(); await guild.members.fetch();
const vegan = await guild.roles.cache.get(IDs.roles.vegan.vegan); const vegan = await getRole(IDs.roles.vegan.vegan, guild);
const notVegan = await guild.roles.cache.get(IDs.roles.nonvegan.nonvegan); const notVegan = await getRole(IDs.roles.nonvegan.nonvegan, guild);
if (vegan === undefined || notVegan === undefined) { if (!isRole(vegan) || !isRole(notVegan)) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching roles!', content: 'Error fetching roles!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -71,7 +73,7 @@ export class RenameUserCommand extends Command {
content: content:
`${vegan.name}s: \`${vegan.members.size}\`` + `${vegan.name}s: \`${vegan.members.size}\`` +
`\n${notVegan.name}s: \`${notVegan.members.size}\``, `\n${notVegan.name}s: \`${notVegan.members.size}\``,
fetchReply: true, withResponse: true,
}); });
} }
@@ -88,8 +90,8 @@ export class RenameUserCommand extends Command {
await guild.members.fetch(); await guild.members.fetch();
const vegan = await guild.roles.cache.get(IDs.roles.vegan.vegan); const vegan = guild.roles.cache.get(IDs.roles.vegan.vegan);
const notVegan = await guild.roles.cache.get(IDs.roles.nonvegan.nonvegan); const notVegan = guild.roles.cache.get(IDs.roles.nonvegan.nonvegan);
if (vegan === undefined || notVegan === undefined) { if (vegan === undefined || notVegan === undefined) {
await message.react('❌'); await message.react('❌');

View File

@@ -19,7 +19,7 @@ import { EmbedBuilder } from 'discord.js';
import type { Message } from 'discord.js'; import type { Message } from 'discord.js';
export class HelpCommand extends Command { export class HelpCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'help', name: 'help',

View File

@@ -18,9 +18,11 @@
*/ */
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import IDs from '#utils/ids';
import { MessageFlagsBitField } from 'discord.js';
export class InfoCommand extends Command { export class InfoCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'info', name: 'info',
@@ -65,11 +67,7 @@ export class InfoCommand extends Command {
const option = interaction.options.getString('info', true); const option = interaction.options.getString('info', true);
let ephemeral = interaction.options.getBoolean('visible'); let ephemeral = interaction.options.getBoolean('visible');
if (ephemeral === null) { ephemeral = ephemeral !== true;
ephemeral = true;
} else {
ephemeral = !ephemeral;
}
let message: string; let message: string;
@@ -94,7 +92,9 @@ export class InfoCommand extends Command {
message = message =
"If you want to have the vegan or activist role, you'll need to do a voice verification. " + "If you want to have the vegan or activist role, you'll need to do a voice verification. " +
"To do this, hop into the 'Verification' voice channel." + "To do this, hop into the 'Verification' voice channel." +
"\n\nIf there aren't any verifiers available, you'll be disconnected, and you can rejoin later."; "\n\nIf there aren't any verifiers available, you'll be disconnected, and you can rejoin later." +
`\n\nAlternatively if you would like text verification, you can use \`/apply\` in <#${IDs.channels.nonVegan.vcText}> ` +
'to be able fill out a Vegan Verification form through the Appy Bot.';
break; break;
case 'modMail': case 'modMail':
message = message =
@@ -112,7 +112,7 @@ export class InfoCommand extends Command {
await interaction.reply({ await interaction.reply({
content: message, content: message,
ephemeral, flags: ephemeral ? MessageFlagsBitField.Flags.Ephemeral : undefined,
}); });
} }
} }

View File

@@ -17,12 +17,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { isMessageInstance } from '@sapphire/discord.js-utilities';
import { Command } from '@sapphire/framework'; import { Command } from '@sapphire/framework';
import type { Message } from 'discord.js'; import { Message, MessageFlagsBitField } from 'discord.js';
export class PingCommand extends Command { export class PingCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'ping', name: 'ping',
@@ -41,12 +40,13 @@ export class PingCommand extends Command {
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
const msg = await interaction.reply({ const msg = await interaction.reply({
content: 'Ping?', content: 'Ping?',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
if (isMessageInstance(msg)) { if (msg.resource !== null && msg.resource.message !== null) {
const diff = msg.createdTimestamp - interaction.createdTimestamp; const diff =
msg.resource.message.createdTimestamp - interaction.createdTimestamp;
const ping = Math.round(this.container.client.ws.ping); const ping = Math.round(this.container.client.ws.ping);
return interaction.editReply( return interaction.editReply(
`Pong 🏓! (Round trip took: ${diff}ms. Heartbeat: ${ping}ms.)`, `Pong 🏓! (Round trip took: ${diff}ms. Heartbeat: ${ping}ms.)`,
@@ -57,6 +57,11 @@ export class PingCommand extends Command {
} }
public async messageRun(message: Message) { public async messageRun(message: Message) {
if (!message.channel.isSendable()) {
// TODO manage logging/errors properly
return;
}
const msg = await message.channel.send('Ping?'); const msg = await message.channel.send('Ping?');
const diff = msg.createdTimestamp - message.createdTimestamp; const diff = msg.createdTimestamp - message.createdTimestamp;

View File

@@ -18,16 +18,24 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { Message, User, Guild, Snowflake } from 'discord.js'; import {
Message,
User,
Guild,
Snowflake,
MessageFlagsBitField,
} from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { import {
finishVerifyMessages, finishVerifyMessages,
giveVerificationRoles, giveVerificationRoles,
} from '#utils/verification'; } from '#utils/verification';
import { manualVerification } from '#utils/database/verification'; import { manualVerification } from '#utils/database/verification';
import { getGuildMember } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class VerifyCommand extends Command { export class VerifyCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'verify', name: 'verify',
@@ -77,8 +85,8 @@ export class VerifyCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
@@ -93,7 +101,7 @@ export class VerifyCommand extends Command {
await interaction.reply({ await interaction.reply({
content: verify.message, content: verify.message,
fetchReply: true, withResponse: true,
}); });
} }
@@ -159,15 +167,12 @@ export class VerifyCommand extends Command {
convinced: false, convinced: false,
}; };
let member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
// Checks if member is null // Checks if member is null
if (member === undefined) { if (!isGuildMember(member)) {
member = await guild.members.fetch(user.id).catch(() => undefined); info.message = 'Failed to fetch member';
if (member === undefined) { return info;
info.message = 'Failed to fetch member';
return info;
}
} }
if (member.roles.cache.hasAny(...IDs.roles.restrictions.restricted)) { if (member.roles.cache.hasAny(...IDs.roles.restrictions.restricted)) {
@@ -175,15 +180,12 @@ export class VerifyCommand extends Command {
return info; return info;
} }
let verifier = guild.members.cache.get(verifierId); const verifier = await getGuildMember(verifierId, guild);
// Checks if verifier is null // Checks if verifier is null
if (verifier === undefined) { if (!isGuildMember(verifier)) {
verifier = await guild.members.fetch(user.id).catch(() => undefined); info.message = 'Failed to fetch verifier';
if (verifier === undefined) { return info;
info.message = 'Failed to fetch verifier';
return info;
}
} }
const roleArgs = rolesString.split(' '); const roleArgs = rolesString.split(' ');

View File

@@ -20,9 +20,12 @@
import { Command, RegisterBehavior } from '@sapphire/framework'; import { Command, RegisterBehavior } from '@sapphire/framework';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { checkVerificationFinish } from '#utils/database/verification'; import { checkVerificationFinish } from '#utils/database/verification';
import { MessageFlagsBitField } from 'discord.js';
import { getGuildMember } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class VerifyTimeoutRemoveCommand extends Command { export class VerifyTimeoutRemoveCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'verifytimeoutremove', name: 'verifytimeoutremove',
@@ -60,22 +63,21 @@ export class VerifyTimeoutRemoveCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Error fetching guild!', content: 'Error fetching guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
fetchReply: true, withResponse: true,
}); });
return; return;
} }
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({
flags: MessageFlagsBitField.Flags.Ephemeral,
});
let member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
if (member === undefined) { if (!isGuildMember(member)) {
member = await guild.members.fetch(user.id).catch(undefined); await interaction.editReply(`${user} is not on this server!`);
if (member === undefined) { return;
await interaction.editReply(`${user} is not on this server!`);
return;
}
} }
if (!member.roles.cache.has(IDs.roles.verifyBlock)) { if (!member.roles.cache.has(IDs.roles.verifyBlock)) {

View File

@@ -18,12 +18,14 @@
*/ */
import { Args, Command, RegisterBehavior } from '@sapphire/framework'; import { Args, Command, RegisterBehavior } from '@sapphire/framework';
import type { User, Guild, Message } from 'discord.js'; import { User, Guild, Message, MessageFlagsBitField } from 'discord.js';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder } from 'discord.js';
import { getRank, xpToNextLevel } from '#utils/database/xp'; import { getRank, xpToNextLevel } from '#utils/database/fun/xp';
import { getGuildMember } from '#utils/fetcher';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class RankCommand extends Command { export class RankCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) { public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, { super(context, {
...options, ...options,
name: 'rank', name: 'rank',
@@ -55,7 +57,7 @@ export class RankCommand extends Command {
if (guild === null) { if (guild === null) {
await interaction.reply({ await interaction.reply({
content: 'Could not find the guild!', content: 'Could not find the guild!',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
@@ -108,14 +110,11 @@ export class RankCommand extends Command {
success: false, success: false,
}; };
let member = guild.members.cache.get(user.id); const member = await getGuildMember(user.id, guild);
if (member === undefined) { if (!isGuildMember(member)) {
member = await guild.members.fetch(user.id).catch(() => undefined); info.message = 'The user is not on this server!';
if (member === undefined) { return info;
info.message = 'The user is not on this server!';
return info;
}
} }
const rank = await getRank(user.id); const rank = await getRank(user.id);

View File

@@ -25,8 +25,11 @@ import { LogLevel, SapphireClient, container } from '@sapphire/framework';
import '@sapphire/plugin-scheduled-tasks/register'; import '@sapphire/plugin-scheduled-tasks/register';
import '@sapphire/plugin-logger/register'; import '@sapphire/plugin-logger/register';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { createClient } from 'redis'; import { Redis } from 'ioredis';
import type { RedisClientType } from 'redis';
const REDIS_PORT = process.env.REDIS_PORT
? parseInt(process.env.REDIS_PORT)
: undefined;
// Setting up the Sapphire client // Setting up the Sapphire client
const client = new SapphireClient({ const client = new SapphireClient({
@@ -50,7 +53,10 @@ const client = new SapphireClient({
tasks: { tasks: {
bull: { bull: {
connection: { connection: {
host: process.env.BULLMQ_URL, host: process.env.REDIS_HOST,
username: process.env.REDIS_USER,
password: process.env.REDIS_PASSWORD,
port: REDIS_PORT,
}, },
}, },
}, },
@@ -63,11 +69,14 @@ const main = async () => {
client.logger.info('Logging in'); client.logger.info('Logging in');
// Create databases // Create databases
container.database = await new PrismaClient(); container.database = new PrismaClient();
container.redis = createClient({ container.redis = new Redis({
url: process.env.REDIS_URL, host: process.env.REDIS_HOST,
username: process.env.REDIS_USER,
password: process.env.REDIS_PASSWORD,
port: REDIS_PORT,
db: 1,
}); });
await container.redis.connect();
// Log the bot in to Discord // Log the bot in to Discord
await client.login(token); await client.login(token);
@@ -83,7 +92,7 @@ const main = async () => {
declare module '@sapphire/pieces' { declare module '@sapphire/pieces' {
interface Container { interface Container {
database: PrismaClient; database: PrismaClient;
redis: RedisClientType; redis: Redis;
} }
} }

View File

@@ -21,12 +21,15 @@ import {
InteractionHandler, InteractionHandler,
InteractionHandlerTypes, InteractionHandlerTypes,
} from '@sapphire/framework'; } from '@sapphire/framework';
import type { PieceContext } from '@sapphire/framework'; import { ButtonInteraction, MessageFlagsBitField } from 'discord.js';
import type { ButtonInteraction, GuildMember } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class NonVeganAccessButtonHandler extends InteractionHandler { export class NonVeganAccessButtonHandler extends InteractionHandler {
public constructor(ctx: PieceContext, options: InteractionHandler.Options) { public constructor(
ctx: InteractionHandler.LoaderContext,
options: InteractionHandler.Options,
) {
super(ctx, { super(ctx, {
...options, ...options,
interactionHandlerType: InteractionHandlerTypes.Button, interactionHandlerType: InteractionHandlerTypes.Button,
@@ -40,54 +43,42 @@ export class NonVeganAccessButtonHandler extends InteractionHandler {
} }
public async run(interaction: ButtonInteraction) { public async run(interaction: ButtonInteraction) {
let { member } = interaction; const { member } = interaction;
const errorMessage = const errorMessage =
'There was an error giving you the role, please try again later or contact ModMail/the developer ' + 'There was an error giving you the role, please try again later or contact ModMail/the developer ' +
'to sort out this problem.'; 'to sort out this problem.';
if (member === null) { if (!isGuildMember(member)) {
await interaction.reply({ await interaction.reply({
content: errorMessage, content: errorMessage,
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
try { if (!member.roles.cache.has(IDs.roles.vegan.vegan)) {
member = member as GuildMember; await interaction.reply({
content: 'You need to be vegan to use this button!',
if (!member.roles.cache.has(IDs.roles.vegan.vegan)) { flags: MessageFlagsBitField.Flags.Ephemeral,
await interaction.reply({ });
content: 'You need to be vegan to use this button!', return;
ephemeral: true, } else if (member.roles.cache.has(IDs.roles.vegan.nvAccess)) {
}); await member.roles.remove(IDs.roles.vegan.nvAccess);
return; await interaction.reply({
} content:
'Your access from the non vegan section has been removed. ' +
if (member.roles.cache.has(IDs.roles.vegan.nvAccess)) { 'If you want to gain access again, click this button again.',
await member.roles.remove(IDs.roles.vegan.nvAccess); flags: MessageFlagsBitField.Flags.Ephemeral,
await interaction.reply({ });
content: return;
'Your access from the non vegan section has been removed. ' + } else {
'If you want to gain access again, click this button again.',
ephemeral: true,
});
return;
}
await member.roles.add(IDs.roles.vegan.nvAccess); await member.roles.add(IDs.roles.vegan.nvAccess);
await interaction.reply({ await interaction.reply({
content: content:
'Your access to the non vegan section has been given back. ' + 'Your access to the non vegan section has been given back. ' +
'If you want to remove access again, click this button again.', 'If you want to remove access again, click this button again.',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
});
} catch (error) {
this.container.logger.error(`Non Vegan Access Interaction: ${error}`);
await interaction.reply({
content: errorMessage,
ephemeral: true,
}); });
} }
} }

View File

@@ -21,12 +21,18 @@ import {
InteractionHandler, InteractionHandler,
InteractionHandlerTypes, InteractionHandlerTypes,
} from '@sapphire/framework'; } from '@sapphire/framework';
import type { PieceContext } from '@sapphire/framework'; import { ButtonInteraction, MessageFlagsBitField } from 'discord.js';
import type { ButtonInteraction, GuildMember, TextChannel } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { checkActive } from '#utils/database/moderation/restriction';
import { addUser } from '#utils/database/dbExistingUser';
import { getTextBasedChannel } from '#utils/fetcher';
import { isGuildMember, isTextChannel } from '@sapphire/discord.js-utilities';
export class WelcomeButtonHandler extends InteractionHandler { export class WelcomeButtonHandler extends InteractionHandler {
public constructor(ctx: PieceContext, options: InteractionHandler.Options) { public constructor(
ctx: InteractionHandler.LoaderContext,
options: InteractionHandler.Options,
) {
super(ctx, { super(ctx, {
...options, ...options,
interactionHandlerType: InteractionHandlerTypes.Button, interactionHandlerType: InteractionHandlerTypes.Button,
@@ -40,49 +46,88 @@ export class WelcomeButtonHandler extends InteractionHandler {
} }
public async run(interaction: ButtonInteraction) { public async run(interaction: ButtonInteraction) {
let { member } = interaction; const { member } = interaction;
const general = this.container.client.channels.cache.get( const general = await getTextBasedChannel(IDs.channels.nonVegan.general);
IDs.channels.nonVegan.general,
) as TextChannel | undefined;
if (general === undefined) {
return;
}
if (member === null) { // Messages that are used multiple times
const roleErrorMessage =
'There was an error giving you the role, please try again later or contact ModMail to be let into this server.';
const welcomeMessage =
`${member} Welcome to ARA! :D Please check <#${IDs.channels.information.roles}> ` +
`and remember to follow the <#${IDs.channels.information.conduct}> and to respect ongoing discussions and debates.` +
`\n\nIf you are vegan, you can join the 'Verification' voice channel, or use \`/apply\` with the Appy bot in <#${IDs.channels.nonVegan.vcText}>, ` +
'to be verified and gain access to more channels.';
// Checks if general is not in the cache
if (!isTextChannel(general)) {
this.container.logger.error(
'WelcomeButtonHandler: Could not find and fetch the general channel!',
);
await interaction.reply({ await interaction.reply({
content: content:
'There was an error giving you the role, please try again later or contact ModMail to be let into this server.', 'Sorry there was a problem trying to give you access to the server. Please try again later.',
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
return; return;
} }
try { // If the member could not be found
member = member as GuildMember; if (!isGuildMember(member)) {
await interaction.reply({
content: roleErrorMessage,
flags: MessageFlagsBitField.Flags.Ephemeral,
});
// Give non-vegan role return;
if (!member.voice.channel) { }
await member.roles.add(IDs.roles.nonvegan.nonvegan);
await general.send( // Checks if the user is currently restricted
`${member} Welcome to ARA! :D Please check <#${IDs.channels.information.roles}> ` + if (await checkActive(member.id)) {
`and remember to follow the <#${IDs.channels.information.conduct}> and to respect ongoing discussion and debates.` + await interaction.reply({
"\n\nIf you would like to be verified as a vegan, join the 'Verification' voice channel.", content: `You are currently restricted from this server! Contact the moderators by sending a DM to <@${IDs.modMail}>.`,
); flags: MessageFlagsBitField.Flags.Ephemeral,
return; });
}
return;
}
// Give non-vegan role
if (member.voice.channel) {
await interaction.reply({ await interaction.reply({
content: content:
"You're currently in a verification, you'll have to leave the verification or get verified before being able to access the server again.", "You're currently in a verification, you'll have to leave the verification or get verified before being able to access the server again.",
ephemeral: true, flags: MessageFlagsBitField.Flags.Ephemeral,
}); });
} catch (error) {
return;
}
// Add the user to the database
await addUser(member.id);
// Give the role to the member
const role = await member.roles
.add(IDs.roles.nonvegan.nonvegan)
.catch(() => undefined);
// If the role could not be given
if (role === undefined) {
await interaction.reply({ await interaction.reply({
content: content: roleErrorMessage,
'There was an error giving you the role, please try again later or contact ModMail to be let into this server.', flags: MessageFlagsBitField.Flags.Ephemeral,
ephemeral: true,
}); });
return;
}
if (general.isSendable()) {
await general.send(welcomeMessage);
} else {
this.container.logger.error(
'WelcomeButtonHandler: The bot does not have permission to send in general!',
);
await member.send(welcomeMessage);
} }
} }
} }

View File

@@ -19,13 +19,18 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { GuildBan } from 'discord.js'; import type { GuildBan } from 'discord.js';
import { AuditLogEvent, EmbedBuilder, TextChannel } from 'discord.js'; import { AuditLogEvent, EmbedBuilder } from 'discord.js';
import { addBan, checkBan } from '#utils/database/ban'; import { addBan, checkBan } from '#utils/database/moderation/ban';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser'; import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser';
import { getGuildMember, getTextBasedChannel } from '#utils/fetcher';
import { isGuildMember, isTextChannel } from '@sapphire/discord.js-utilities';
export class BanListener extends Listener { export class BanListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'guildBanAdd', event: 'guildBanAdd',
@@ -79,17 +84,12 @@ export class BanListener extends Listener {
const { guild } = ban; const { guild } = ban;
// Gets mod's GuildMember // Gets mod's GuildMember
let mod = guild.members.cache.get(executor.id); const mod = await getGuildMember(executor.id, guild);
// Checks if GuildMember is null // Checks if GuildMember is null
if (mod === undefined) { if (!isGuildMember(mod)) {
mod = await guild.members.fetch(executor.id).catch(() => undefined); this.container.logger.error('UnbanListener: Could not fetch moderator.');
if (mod === undefined) { return;
this.container.logger.error(
'UnbanListener: Could not fetch moderator.',
);
return;
}
} }
// Check if mod is in database // Check if mod is in database
@@ -115,18 +115,11 @@ export class BanListener extends Listener {
await addBan(user.id, mod.id, `${reason}`); await addBan(user.id, mod.id, `${reason}`);
// Log the ban // Log the ban
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as const logChannel = await getTextBasedChannel(IDs.channels.logs.restricted);
| TextChannel
| undefined;
if (logChannel === undefined) { if (!isTextChannel(logChannel)) {
logChannel = (await guild.channels.fetch( this.container.logger.error('BanListener: Could not fetch log channel');
IDs.channels.logs.restricted, return;
)) as TextChannel | undefined;
if (logChannel === undefined) {
this.container.logger.error('BanListener: Could not fetch log channel');
return;
}
} }
const log = new EmbedBuilder() const log = new EmbedBuilder()

View File

@@ -19,11 +19,14 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { GuildMember } from 'discord.js'; import type { GuildMember } from 'discord.js';
import { checkBan, getBanReason } from '#utils/database/ban'; import { checkBan, getBanReason } from '#utils/database/moderation/ban';
import { checkTempBan } from '#utils/database/tempBan'; import { checkTempBan } from '#utils/database/moderation/tempBan';
export class BanJoinListener extends Listener { export class BanJoinListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'guildMemberAdd', event: 'guildMemberAdd',

View File

@@ -19,13 +19,18 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { GuildBan } from 'discord.js'; import type { GuildBan } from 'discord.js';
import { AuditLogEvent, EmbedBuilder, TextChannel } from 'discord.js'; import { AuditLogEvent, EmbedBuilder } from 'discord.js';
import { addBan, checkBan, removeBan } from '#utils/database/ban'; import { addBan, checkBan, removeBan } from '#utils/database/moderation/ban';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser'; import { addEmptyUser, addExistingUser } from '#utils/database/dbExistingUser';
import { getGuildMember, getTextBasedChannel } from '#utils/fetcher';
import { isTextBasedChannel } from '@sapphire/discord.js-utilities';
export class UnbanListener extends Listener { export class UnbanListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'guildBanRemove', event: 'guildBanRemove',
@@ -68,17 +73,12 @@ export class UnbanListener extends Listener {
const { guild } = ban; const { guild } = ban;
// Gets mod's GuildMember // Gets mod's GuildMember
let mod = guild.members.cache.get(executor.id); const mod = await getGuildMember(executor.id, guild);
// Checks if GuildMember is null // Checks if GuildMember is null
if (mod === undefined) { if (mod === undefined) {
mod = await guild.members.fetch(executor.id).catch(() => undefined); this.container.logger.error('UnbanListener: Could not fetch moderator.');
if (mod === undefined) { return;
this.container.logger.error(
'UnbanListener: Could not fetch moderator.',
);
return;
}
} }
// Check if mod is in database // Check if mod is in database
@@ -97,20 +97,11 @@ export class UnbanListener extends Listener {
await removeBan(user.id, mod.id); await removeBan(user.id, mod.id);
// Log the ban // Log the ban
let logChannel = guild.channels.cache.get(IDs.channels.logs.restricted) as const logChannel = await getTextBasedChannel(IDs.channels.logs.restricted);
| TextChannel
| undefined;
if (logChannel === undefined) { if (!isTextBasedChannel(logChannel)) {
logChannel = (await guild.channels.fetch( this.container.logger.error('UnbanListener: Could not fetch log channel');
IDs.channels.logs.restricted, return;
)) as TextChannel | undefined;
if (logChannel === undefined) {
this.container.logger.error(
'UnbanListener: Could not fetch log channel',
);
return;
}
} }
const log = new EmbedBuilder() const log = new EmbedBuilder()

View File

@@ -21,7 +21,10 @@ import { Listener } from '@sapphire/framework';
import type { Message } from 'discord.js'; import type { Message } from 'discord.js';
export class BotAppreciationListener extends Listener { export class BotAppreciationListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'messageCreate', event: 'messageCreate',

View File

@@ -24,7 +24,10 @@ import type {
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
export class OldCommandDeniedListener extends Listener { export class OldCommandDeniedListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'messageCommandDenied', event: 'messageCommandDenied',

View File

@@ -21,11 +21,14 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { Message } from 'discord.js'; import type { Message } from 'discord.js';
import { getLastCount, addCount } from '#utils/database/counting'; import { getLastCount, addCount } from '#utils/database/fun/counting';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
export class XpListener extends Listener { export class XpListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'messageCreate', event: 'messageCreate',
@@ -41,6 +44,13 @@ export class XpListener extends Listener {
return; return;
} }
if (!message.channel.isSendable()) {
this.container.logger.error(
'Counting: The bot does not have permission to send messages in the counting chat!',
);
return;
}
let lastCount = await getLastCount(); let lastCount = await getLastCount();
// If no counts exist on the database, then create the first count from the bot // If no counts exist on the database, then create the first count from the bot
@@ -49,6 +59,7 @@ export class XpListener extends Listener {
message.channel.send( message.channel.send(
'An unexpected error occurred trying to set up the counting channel, please contact a developer!', 'An unexpected error occurred trying to set up the counting channel, please contact a developer!',
); );
return; return;
} }
@@ -63,6 +74,7 @@ export class XpListener extends Listener {
message.channel.send( message.channel.send(
'An unexpected error occurred, please contact a developer!', 'An unexpected error occurred, please contact a developer!',
); );
return; return;
} }
} }

View File

@@ -27,7 +27,10 @@ import {
} from '#utils/database/dbExistingUser'; } from '#utils/database/dbExistingUser';
export class DbLeaveServerListener extends Listener { export class DbLeaveServerListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'guildMemberRemove', event: 'guildMemberRemove',

View File

@@ -24,7 +24,10 @@ import type {
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
export class CommandDeniedListener extends Listener { export class CommandDeniedListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'chatInputCommandDenied', event: 'chatInputCommandDenied',

View File

@@ -24,7 +24,10 @@ import { Listener } from '@sapphire/framework';
import { setupTypes } from '#utils/database/outreach'; import { setupTypes } from '#utils/database/outreach';
export class EventTypesReadyListener extends Listener { export class EventTypesReadyListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
once: true, once: true,

186
src/listeners/fixRoles.ts Normal file
View File

@@ -0,0 +1,186 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/*
Animal Rights Advocates Discord Bot
Copyright (C) 2025 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 used the Sapphire documentation and parts of the code from the Sapphire CLI to
create this file.
*/
import { Listener } from '@sapphire/framework';
import { DurationFormatter } from '@sapphire/time-utilities';
import IDs from '#utils/ids';
import { fetchRoles } from '#utils/database/dbExistingUser';
import { checkActive } from '#utils/database/moderation/restriction';
import { getUser } from '#utils/database/fun/xp';
import { getGuild, getTextBasedChannel } from '#utils/fetcher';
import { isTextBasedChannel } from '@sapphire/discord.js-utilities';
export class FixRolesOnReady extends Listener {
public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, {
...options,
once: true,
event: 'ready',
// !!!!!!!!!!!! WARNING !!!!!!!!!!!!
// THIS SHOULD BE DISABLED BY DEFAULT
// THIS IS ONLY USED FOR RESTORING ROLES TO THE SERVER!
// ENABLING THIS UNINTENTIONALLY WILL CAUSE SLOWDOWNS TO THE BOT DUE TO RATE LIMITING!
enabled: false,
});
}
public async run() {
this.container.logger.info(
'FixRolesOnReady: Preparation before starting to fix the roles for each user...',
);
// Fetching the Guild
const guild = await getGuild(IDs.guild);
if (guild === undefined) {
this.container.logger.error('FixRolesOnReady: Could not find the server');
return;
}
// Fetching the channel for the logs
// Leave the snowflake parameter empty for no logs
const logChannel = await getTextBasedChannel('');
const sendLogs = isTextBasedChannel(logChannel);
if (!sendLogs) {
this.container.logger.error(
'FixRolesOnReady: Could not find the channel for bot logs.',
);
} else if (sendLogs && !logChannel.isSendable()) {
this.container.logger.info(
'FixRolesOnReady: No permission to send in bots logs channel.',
);
return;
}
// Get all the current users
this.container.logger.info('FixRolesOnReady: Fetching all the members...');
if (sendLogs) {
logChannel.send('Fetching all the users in ARA!');
}
const members = await guild.members.fetch().catch(() => undefined);
if (members === undefined) {
this.container.logger.error(
'FixRolesOnReady: Could not fetch all the members, this function is stopping now.',
);
if (sendLogs) {
logChannel.send("Never mind, something went wrong :'(");
}
return;
}
const totalMembers = members.size;
this.container.logger.info(
`FixRolesOnReady: Done fetching ${totalMembers} members!`,
);
// Giving the roles to each user
let count = 0;
const startTime = new Date().getTime();
this.container.logger.info(
'FixRolesOnReady: Starting the process of fixing the roles for every member...',
);
for (const [userId, member] of members) {
// Update the counter for the total number of users processed
count += 1;
// Send a message with an update for every 50 completions
// Checks if `channelLog` has been set to null
// The RHS of the modulo should be around 100
if (sendLogs && count % 250 === 0) {
const currentTime = new Date().getTime();
const runningTime = currentTime - startTime;
const remaining = totalMembers - count;
// Basing this on the fact that
const eta = remaining * (runningTime / count);
const estimate = new DurationFormatter().format(eta);
logChannel.send(
`Given roles to ${count} out of ${totalMembers} members. Estimated time until completion: ${estimate}`,
);
}
// Checks if the user already has vegan or non-vegan role
// Checks if the user is restricted, and skips over them if they are
const restricted = await checkActive(userId);
if (
restricted ||
member.roles.cache.hasAny(...IDs.roles.restrictions.restricted)
) {
continue;
}
// Fetch the roles for the member in the database
const dbRoles = await fetchRoles(userId);
// Filters out the roles that the member does not have
const roles = dbRoles.filter((role) => !member.roles.cache.has(role));
if (!roles.includes(IDs.roles.nonvegan.nonvegan)) {
const xp = await getUser(userId);
if (xp !== null && xp.xp > 0) {
roles.push(IDs.roles.nonvegan.nonvegan);
}
}
// Give the roles to the member
if (roles.length > 0) {
await member.roles.add(roles);
}
// Log the completion
this.container.logger.info(
`FixRolesOnReady: Given roles to ${count}/${totalMembers}.`,
);
// Add a delay so that there's around 4 users processed a second
await this.delay(1000);
}
// Send the logs that the fix has finished.
const endTime = new Date().getTime();
const totalTime = endTime - startTime;
const totalTimeWritten = new DurationFormatter().format(totalTime);
const finishMessage = `Finished fixing roles for all ${totalMembers} members! It took ${totalTimeWritten} to complete.`;
this.container.logger.info(`FixRolesOnReady: ${finishMessage}`);
if (sendLogs) {
logChannel.send(finishMessage);
}
}
private delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}

View File

@@ -18,15 +18,29 @@
*/ */
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import { ChannelType, EmbedBuilder } from 'discord.js'; import type { GuildChannel, EmbedBuilder } from 'discord.js';
import type { GuildChannel } from 'discord.js';
import { setTimeout } from 'timers/promises'; import { setTimeout } from 'timers/promises';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { checkActive, getRestrictions } from '#utils/database/restriction'; import {
import { findNotes } from '#utils/database/sus'; checkActive,
getRestrictions,
} from '#utils/database/moderation/restriction';
import { findNotes } from '#utils/database/moderation/sus';
import {
createRestrictLogEmbed,
createSusLogEmbed,
createWarningsEmbed,
} from '#utils/embeds';
import { fetchWarnings } from '#utils/database/moderation/warnings';
import { isTextChannel } from '@sapphire/discord.js-utilities';
import { getUser } from '#utils/fetcher';
import { isUser } from '#utils/typeChecking';
export class ModMailCreateListener extends Listener { export class ModMailCreateListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'channelCreate', event: 'channelCreate',
@@ -38,8 +52,7 @@ export class ModMailCreateListener extends Listener {
if (channel.parentId !== IDs.categories.modMail) return; if (channel.parentId !== IDs.categories.modMail) return;
// Checks if the channel is not a text channel // Checks if the channel is not a text channel
if (!channel.isTextBased()) return; if (!isTextChannel(channel)) return;
if (channel.type !== ChannelType.GuildText) return;
// Gets the guild // Gets the guild
const { guild } = channel; const { guild } = channel;
@@ -51,6 +64,13 @@ export class ModMailCreateListener extends Listener {
// Get the user's ID // Get the user's ID
const userId = topic[2]; const userId = topic[2];
// Gets user who created ModMail
const user = await getUser(userId);
if (!isUser(user)) {
return;
}
// Check if the user is currently restricted on the database // Check if the user is currently restricted on the database
if (!(await checkActive(userId))) return; if (!(await checkActive(userId))) return;
@@ -60,81 +80,21 @@ export class ModMailCreateListener extends Listener {
// Creation of embeds // Creation of embeds
// Restriction Logs // Restriction Logs
const restrictEmbed = new EmbedBuilder() const embeds: EmbedBuilder[] = [];
.setColor('#FF6700') embeds.push(createRestrictLogEmbed(restrictions, user, guild));
.setTitle(`${restrictions.length} restrictions`)
.setFooter({ text: `ID: ${userId}` });
// Add up to 10 of the latest restrictions to the embed // Warnings
for ( const warnings = await fetchWarnings(userId);
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} | `; embeds.push(createWarningsEmbed(warnings, user, guild));
if (endRestMod !== null) {
restTitle += `Unrestricted by: ${endRestMod} | `;
} else {
restTitle += 'Currently Restricted | ';
}
restTitle += `Date: <t:${Math.floor(
restrictions[i].startTime.getTime() / 1000,
)}>`;
restrictEmbed.addFields({
name: restTitle,
value: restrictions[i].reason,
});
}
// Sus Notes // Sus Notes
const notes = await findNotes(userId, true); const notes = await findNotes(userId, true);
const susEmbed = new EmbedBuilder() embeds.push(createSusLogEmbed(notes, user, guild));
.setColor('#0099ff')
.setTitle(`${notes.length} sus notes`);
// 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
susEmbed.addFields({
name: `Sus ID: ${
notes[i].id
} | Moderator: ${mod} | Date: <t:${Math.floor(
notes[i].time.getTime() / 1000,
)}>`,
value: notes[i].note,
});
}
// Set a timeout for 1 second and then send the 2 embeds // Set a timeout for 1 second and then send the 2 embeds
await setTimeout(1000); await setTimeout(1000);
await channel.send({ embeds: [restrictEmbed, susEmbed] }); await channel.send({ embeds: embeds });
} }
} }

View File

@@ -20,11 +20,15 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import { ButtonStyle, ActionRowBuilder, ButtonBuilder } from 'discord.js'; import { ButtonStyle, ActionRowBuilder, ButtonBuilder } from 'discord.js';
import type { Client, TextChannel } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getTextBasedChannel } from '#utils/fetcher';
import { isTextBasedChannel } from '@sapphire/discord.js-utilities';
export class NonVeganAccessReady extends Listener { export class NonVeganAccessReady extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
once: true, once: true,
@@ -32,18 +36,12 @@ export class NonVeganAccessReady extends Listener {
}); });
} }
public async run(client: Client) { public async run() {
let roles = client.channels.cache.get(IDs.channels.information.roles) as const roles = await getTextBasedChannel(IDs.channels.information.roles);
| TextChannel
| undefined; if (!isTextBasedChannel(roles)) {
if (roles === undefined) { this.container.logger.error('nonVeganAccess: Roles not found');
roles = (await client.channels.fetch(IDs.channels.information.roles)) as return;
| TextChannel
| undefined;
if (roles === undefined) {
this.container.logger.error('nonVeganAccess: Roles not found');
return;
}
} }
const botId = this.container.client.id; const botId = this.container.client.id;

View File

@@ -22,9 +22,15 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { Client } from 'discord.js'; import type { Client } from 'discord.js';
import IDs from '#utils/ids';
import { getTextBasedChannel } from '#utils/fetcher';
import { isTextBasedChannel } from '@sapphire/discord.js-utilities';
export class ReadyListener extends Listener { export class ReadyListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
once: true, once: true,
@@ -32,8 +38,24 @@ export class ReadyListener extends Listener {
}); });
} }
public run(client: Client) { public async run(client: Client) {
const { username, id } = client.user!; const { username, id } = client.user!;
this.container.logger.info(`Successfully logged in as ${username} (${id})`); this.container.logger.info(`Successfully logged in as ${username} (${id})`);
const botLogChannel = await getTextBasedChannel(IDs.channels.logs.bot);
if (!isTextBasedChannel(botLogChannel)) {
this.container.logger.error(
'ReadyListener: Could not find the channel for bot logs.',
);
return;
} else if (!botLogChannel.isSendable()) {
this.container.logger.info(
'ReadyListener: No permission to send in bots logs channel.',
);
return;
}
botLogChannel.send('The bot has started up!');
} }
} }

View File

@@ -18,22 +18,27 @@
*/ */
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { import type { GuildMember, Snowflake, CategoryChannel } from 'discord.js';
GuildMember,
Snowflake,
CategoryChannel,
Guild,
TextChannel,
} from 'discord.js';
import { ChannelType } from 'discord.js';
import { fetchRoles, getLeaveRoles } from '#utils/database/dbExistingUser'; import { fetchRoles, getLeaveRoles } from '#utils/database/dbExistingUser';
import { blockTime } from '#utils/database/verification'; import { blockTime } from '#utils/database/verification';
import { checkActive, getSection } from '#utils/database/restriction'; import {
checkActive,
getSection,
} from '#utils/database/moderation/restriction';
import { blockedRoles, blockedRolesAfterRestricted } from '#utils/blockedRoles'; import { blockedRoles, blockedRolesAfterRestricted } from '#utils/blockedRoles';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getCategoryChannel, getVoiceChannel } from '#utils/fetcher';
import {
isCategoryChannel,
isTextChannel,
isVoiceChannel,
} from '@sapphire/discord.js-utilities';
export class RolesJoinServerListener extends Listener { export class RolesJoinServerListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'guildMemberAdd', event: 'guildMemberAdd',
@@ -71,14 +76,11 @@ export class RolesJoinServerListener extends Listener {
// Add user to the restricted vegan channel // Add user to the restricted vegan channel
if (section === 5) { if (section === 5) {
const restrictedCategory = member.guild.channels.cache.get( const restrictedCategory = await getCategoryChannel(
IDs.categories.restricted, IDs.categories.restricted,
); );
if ( if (isCategoryChannel(restrictedCategory)) {
restrictedCategory !== undefined && await this.restrictRun(member.id, restrictedCategory);
restrictedCategory.type === ChannelType.GuildCategory
) {
await this.restrictRun(member.id, restrictedCategory, member.guild);
} }
} }
} }
@@ -97,74 +99,77 @@ export class RolesJoinServerListener extends Listener {
await member.roles.add(roles); await member.roles.add(roles);
} }
const privateCategory = member.guild.channels.cache.get( const privateCategory = await getCategoryChannel(IDs.categories.private);
IDs.categories.private,
);
if ( if (isCategoryChannel(privateCategory)) {
privateCategory !== undefined && await this.privateRun(member.id, privateCategory);
privateCategory.type === ChannelType.GuildCategory
) {
await this.privateRun(member.id, privateCategory, member.guild);
} }
// TODO add access back to diversity team // TODO add access back to diversity team
} }
private async restrictRun( private async restrictRun(userId: Snowflake, category: CategoryChannel) {
userId: Snowflake, const textChannels = category.children.cache.filter((channel) =>
category: CategoryChannel, isTextChannel(channel),
guild: Guild,
) {
const textChannels = category.children.cache.filter(
(c) => c.type === ChannelType.GuildText,
); );
textChannels.forEach((c) => {
const textChannel = c as TextChannel; for (const c of textChannels) {
const channel = c[1];
if (!isTextChannel(channel)) {
continue;
}
// Checks if the channel topic has the user's snowflake // Checks if the channel topic has the user's snowflake
if (textChannel.topic?.includes(userId)) { if (channel.topic !== null && channel.topic.includes(userId)) {
const topic = textChannel.topic.split(' '); const topic = channel.topic.split(' ');
const vcId = topic[topic.indexOf(userId) + 1]; const vcId = topic[topic.indexOf(userId) + 1];
const voiceChannel = guild.channels.cache.get(vcId); const voiceChannel = await getVoiceChannel(vcId);
if ( if (
voiceChannel !== undefined && isVoiceChannel(voiceChannel) &&
voiceChannel.parentId === IDs.categories.restricted && voiceChannel.parentId === IDs.categories.restricted
voiceChannel.isVoiceBased()
) { ) {
voiceChannel.permissionOverwrites.edit(userId, { ViewChannel: true }); await voiceChannel.permissionOverwrites.edit(userId, {
ViewChannel: true,
});
} }
textChannel.permissionOverwrites.edit(userId, { ViewChannel: true });
await channel.permissionOverwrites.edit(userId, { ViewChannel: true });
} }
}); }
} }
private async privateRun( private async privateRun(userId: Snowflake, category: CategoryChannel) {
userId: Snowflake, const textChannels = category.children.cache.filter((channel) =>
category: CategoryChannel, isTextChannel(channel),
guild: Guild,
) {
const textChannels = category.children.cache.filter(
(c) => c.type === ChannelType.GuildText,
); );
textChannels.forEach((c) => {
const textChannel = c as TextChannel; for (const c of textChannels) {
const channel = c[1];
if (!isTextChannel(channel)) {
continue;
}
// Checks if the channel topic has the user's snowflake // Checks if the channel topic has the user's snowflake
if (textChannel.topic?.includes(userId)) { if (channel.topic !== null && channel.topic.includes(userId)) {
const topic = textChannel.topic.split(' '); const topic = channel.topic.split(' ');
const vcId = topic[topic.indexOf(userId) + 2]; const vcId = topic[topic.indexOf(userId) + 2];
const voiceChannel = guild.channels.cache.get(vcId); const voiceChannel = await getVoiceChannel(vcId);
if ( if (
voiceChannel !== undefined && isVoiceChannel(voiceChannel) &&
voiceChannel.parentId === IDs.categories.private && voiceChannel.parentId === IDs.categories.private
voiceChannel.isVoiceBased()
) { ) {
voiceChannel.permissionOverwrites.edit(userId, { ViewChannel: true }); await voiceChannel.permissionOverwrites.edit(userId, {
ViewChannel: true,
});
} }
textChannel.permissionOverwrites.edit(userId, { ViewChannel: true });
await channel.permissionOverwrites.edit(userId, { ViewChannel: true });
} }
}); }
} }
private blockedRole(role: Snowflake) { private blockedRole(role: Snowflake) {

View File

@@ -21,9 +21,14 @@ import { Listener } from '@sapphire/framework';
import { EmbedBuilder } from 'discord.js'; import { EmbedBuilder } from 'discord.js';
import type { Message } from 'discord.js'; import type { Message } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getTextBasedChannel } from '#utils/fetcher';
import { isTextChannel } from '@sapphire/discord.js-utilities';
export class Suggestions extends Listener { export class Suggestions extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'messageCreate', event: 'messageCreate',
@@ -35,14 +40,19 @@ export class Suggestions extends Listener {
return; return;
} }
const mailbox = await this.container.client.channels.cache.get( const mailbox = await getTextBasedChannel(IDs.channels.staff.mailbox);
IDs.channels.staff.mailbox,
);
if (mailbox === undefined || !mailbox.isTextBased()) { if (!isTextChannel(mailbox)) {
this.container.logger.error( this.container.logger.error(
'Mailbox is not a TextBased channel or is undefined', 'Mailbox is not a TextBased channel or is undefined',
); );
return;
} else if (!mailbox.isSendable()) {
this.container.logger.error(
'Suggestions: The bot does not have permissions to send messages in the mailbox!',
);
return; return;
} }

View File

@@ -19,10 +19,14 @@
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { VoiceState } from 'discord.js'; import type { VoiceState } from 'discord.js';
import { checkActive, removeMute } from '#utils/database/vcMute'; import { checkActive, removeMute } from '#utils/database/moderation/vcMute';
import { isGuildMember } from '@sapphire/discord.js-utilities';
export class VCMuteListener extends Listener { export class VCMuteListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'voiceStateUpdate', event: 'voiceStateUpdate',
@@ -34,7 +38,7 @@ export class VCMuteListener extends Listener {
if (oldState.channel === null && newState.channel !== null) { if (oldState.channel === null && newState.channel !== null) {
const { member } = newState; const { member } = newState;
if (member === null) { if (!isGuildMember(member)) {
this.container.logger.error( this.container.logger.error(
'VCMute Listener - GuildMember not found when joining', 'VCMute Listener - GuildMember not found when joining',
); );

View File

@@ -19,10 +19,8 @@
import { container, Listener } from '@sapphire/framework'; import { container, Listener } from '@sapphire/framework';
import type { import type {
CategoryChannel,
ColorResolvable, ColorResolvable,
TextChannel, TextChannel,
VoiceChannel,
VoiceState, VoiceState,
GuildMember, GuildMember,
Guild, Guild,
@@ -30,7 +28,6 @@ import type {
} from 'discord.js'; } from 'discord.js';
import { import {
time, time,
ChannelType,
PermissionsBitField, PermissionsBitField,
ButtonBuilder, ButtonBuilder,
ButtonInteraction, ButtonInteraction,
@@ -50,13 +47,26 @@ import {
startVerification, startVerification,
finishVerification, finishVerification,
} from '#utils/database/verification'; } from '#utils/database/verification';
import { findNotes } from '#utils/database/sus'; import { findNotes } from '#utils/database/moderation/sus';
import { addExistingUser } from '#utils/database/dbExistingUser'; import { addExistingUser } from '#utils/database/dbExistingUser';
import { rolesToString } from '#utils/formatter'; import { rolesToString } from '#utils/formatter';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import {
getCategoryChannel,
getGuildMember,
getVoiceChannel,
} from '#utils/fetcher';
import {
isCategoryChannel,
isGuildMember,
isVoiceChannel,
} from '@sapphire/discord.js-utilities';
export class VerificationJoinVCListener extends Listener { export class VerificationJoinVCListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'voiceStateUpdate', event: 'voiceStateUpdate',
@@ -81,20 +91,19 @@ export class VerificationJoinVCListener extends Listener {
const { client } = container; const { client } = container;
const guild = client.guilds.cache.get(newState.guild.id); const guild = client.guilds.cache.get(newState.guild.id);
if (channel === null || member === null || guild === undefined) { if (member === null || guild === undefined) {
this.container.logger.error('Verification channel not found'); this.container.logger.error('Verification channel not found');
return; return;
} }
// Get current category and channel // Get current category and channel
const categoryGuild = guild.channels.cache.get(IDs.categories.verification); const category = await getCategoryChannel(IDs.categories.verification);
const currentChannelGuild = guild.channels.cache.get(channel.id); const currentChannel = await getVoiceChannel(channel.id);
if (currentChannelGuild === undefined || categoryGuild === undefined) {
if (!isCategoryChannel(category) || !isVoiceChannel(currentChannel)) {
this.container.logger.error('Verification channel not found'); this.container.logger.error('Verification channel not found');
return; return;
} }
const currentChannel = currentChannelGuild as VoiceChannel;
const category = categoryGuild as CategoryChannel;
const roles: Snowflake[] = []; const roles: Snowflake[] = [];
@@ -151,19 +160,21 @@ export class VerificationJoinVCListener extends Listener {
]); ]);
// Start 15-minute timer if verifier does not join // Start 15-minute timer if verifier does not join
this.container.tasks.create( await this.container.tasks.create(
'verifyTimeout',
{ {
channelId: channel.id, name: 'verifyTimeout',
userId: member.id, payload: {
channelId: channel.id,
userId: member.id,
},
}, },
900_000, 900_000, // 15 minutes
); // 15 minutes );
} }
// Check how many voice channels there are // Check how many voice channels there are
const listVoiceChannels = category.children.cache.filter( const listVoiceChannels = category.children.cache.filter((channel) =>
(c) => c.type === ChannelType.GuildVoice, isVoiceChannel(channel),
); );
// Create a text channel for verifiers only // Create a text channel for verifiers only
@@ -295,11 +306,12 @@ export class VerificationJoinVCListener extends Listener {
i += 1 i += 1
) { ) {
// Get mod name // Get mod name
const modGuildMember = guild.members.cache.get(notes[i].modId); const modGuildMember = await getGuildMember(notes[i].modId, guild);
let mod = notes[i].modId; let mod = notes[i].modId;
if (modGuildMember !== undefined) { if (isGuildMember(modGuildMember)) {
mod = modGuildMember.displayName; mod = modGuildMember.displayName;
} }
// Add sus note to embed // Add sus note to embed
embed.addFields({ embed.addFields({
name: `Sus ID: ${ name: `Sus ID: ${
@@ -490,10 +502,8 @@ export class VerificationJoinVCListener extends Listener {
// Confirming and finishing the verification // Confirming and finishing the verification
if (button.customId === 'confirm' && info.page >= questionLength) { if (button.customId === 'confirm' && info.page >= questionLength) {
// Check verifier is on the database // Check verifier is on the database
const verifierGuildMember = await guild.members.cache.get( const verifierGuildMember = await getGuildMember(button.user.id, guild);
button.user.id, if (!isGuildMember(verifierGuildMember)) {
);
if (verifierGuildMember === undefined) {
await message.edit({ content: 'Verifier not found!' }); await message.edit({ content: 'Verifier not found!' });
return; return;
} }
@@ -506,11 +516,13 @@ export class VerificationJoinVCListener extends Listener {
await giveVerificationRoles(user, info.roles); await giveVerificationRoles(user, info.roles);
// Add timeout if they do not have activist role // Add timeout if they do not have activist role
if (!info.roles.activist) { if (!info.roles.activist) {
this.container.tasks.create( await this.container.tasks.create(
'verifyUnblock',
{ {
userId: user.id, name: 'verifyUnblock',
guildId: guild.id, payload: {
userId: user.id,
guildId: guild.id,
},
}, },
info.roles.vegan || info.roles.convinced ? 604800000 : 1814400000, info.roles.vegan || info.roles.convinced ? 604800000 : 1814400000,
); );

View File

@@ -18,13 +18,8 @@
*/ */
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { import type { VoiceState } from 'discord.js';
VoiceState, import { time } from 'discord.js';
CategoryChannel,
VoiceChannel,
TextChannel,
} from 'discord.js';
import { time, ChannelType } from 'discord.js';
import { createVerificationVoice } from '#utils/verification'; import { createVerificationVoice } from '#utils/verification';
import { maxVCs, leaveBan } from '#utils/verificationConfig'; import { maxVCs, leaveBan } from '#utils/verificationConfig';
import { import {
@@ -35,9 +30,19 @@ import {
import { fetchRoles } from '#utils/database/dbExistingUser'; import { fetchRoles } from '#utils/database/dbExistingUser';
import { fibonacci } from '#utils/maths'; import { fibonacci } from '#utils/maths';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import {
isCategoryChannel,
isGuildMember,
isTextChannel,
isVoiceChannel,
} from '@sapphire/discord.js-utilities';
import { getCategoryChannel, getGuildMember } from '#utils/fetcher';
export class VerificationLeaveVCListener extends Listener { export class VerificationLeaveVCListener extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'voiceStateUpdate', event: 'voiceStateUpdate',
@@ -60,59 +65,59 @@ export class VerificationLeaveVCListener extends Listener {
const { channel } = oldState; const { channel } = oldState;
const { guild } = newState; const { guild } = newState;
if (channel === null || guild === undefined) { if (!isVoiceChannel(channel) || guild === undefined) {
this.container.logger.error('Verification channel not found'); this.container.logger.error('Verification channel not found');
return; return;
} }
// Get the category // Get the category
const categoryGuild = guild.channels.cache.get(IDs.categories.verification); const category = await getCategoryChannel(IDs.categories.verification);
if (categoryGuild === null) { if (!isCategoryChannel(category)) {
this.container.logger.error('Verification channel not found'); this.container.logger.error('Verification channel not found');
return; return;
} }
const category = categoryGuild as CategoryChannel;
// Get the user that was being verified // Get the user that was being verified
const userSnowflake = await getUser(channel.id); const userSnowflake = await getUser(channel.id);
if (userSnowflake === null) { if (userSnowflake === null) {
verifier = true; verifier = true;
} } else {
// Allow more people to join VC if there are less than 10 VCs
// Allow more people to join VC if there are less than 10 VCs const member = await getGuildMember(userSnowflake, guild);
if (!verifier) {
const user = guild.members.cache.get(userSnowflake!);
// Remove verify as vegan and give non vegan role // Remove verify as vegan and give non vegan role
if (!(await checkFinish(channel.id)) && user !== undefined) { if (!(await checkFinish(channel.id)) && isGuildMember(member)) {
// Get roles to give back to the user // Get roles to give back to the user
const roles = await fetchRoles(user.id); const roles = await fetchRoles(member.id);
roles.push(IDs.roles.verifyBlock); roles.push(IDs.roles.verifyBlock);
await user.roles
await member.roles
.add(roles) .add(roles)
.catch(() => .catch(() =>
this.container.logger.error( this.container.logger.error(
'Verification: User left but bot still tried to add roles', 'Verification: User left but bot still tried to add roles',
), ),
); );
// Create timeout block for user // Create timeout block for user
// Counts the recent times they have incomplete verifications // Counts the recent times they have incomplete verifications
const incompleteCount = const incompleteCount =
(await countIncomplete(user.id)) % (leaveBan + 1); (await countIncomplete(member.id)) % (leaveBan + 1);
// Creates the length of the time for the ban // Creates the length of the time for the ban
const banLength = fibonacci(incompleteCount) * 3600_000; const banLength = fibonacci(incompleteCount) * 3600_000;
this.container.tasks.create( await this.container.tasks.create(
'verifyUnblock',
{ {
userId: user.id, name: 'verifyUnblock',
guildId: guild.id, payload: {
userId: member.id,
guildId: guild.id,
},
}, },
banLength, banLength,
); );
await user.user await member.user
.send( .send(
'You have been timed out as a verifier had not joined for 15 minutes or you disconnected from verification.\n\n' + 'You have been timed out as a verifier had not joined for 15 minutes or you disconnected from verification.\n\n' +
`You can verify again at: ${time( `You can verify again at: ${time(
@@ -124,8 +129,8 @@ export class VerificationLeaveVCListener extends Listener {
} }
// Check how many voice channels there are // Check how many voice channels there are
const listVoiceChannels = category.children.cache.filter( const listVoiceChannels = category.children.cache.filter((channel) =>
(c) => c.type === ChannelType.GuildVoice, isVoiceChannel(channel),
); );
// Check that it is not deleting the 'Verification' channel (in case bot crashes) // Check that it is not deleting the 'Verification' channel (in case bot crashes)
@@ -137,16 +142,22 @@ export class VerificationLeaveVCListener extends Listener {
// Delete text channel // Delete text channel
if (!verifier) { if (!verifier) {
// Gets a list of all the text channels in the verification category // Gets a list of all the text channels in the verification category
const listTextChannels = category.children.cache.filter( const listTextChannels = category.children.cache.filter((channel) =>
(c) => c.type === ChannelType.GuildText, isTextChannel(channel),
); );
listTextChannels.forEach((c) => {
const textChannel = c as TextChannel; for (const c of listTextChannels) {
// Checks if the channel topic has the user's snowflake const channel = c[1];
if (textChannel.topic!.includes(userSnowflake!)) {
textChannel.delete(); if (!isTextChannel(channel)) {
continue;
} }
});
// Checks if the channel topic has the user's snowflake
if (channel.topic !== null && channel.topic.includes(userSnowflake!)) {
await channel.delete();
}
}
} }
// If there are no VCs left in verification after having the channel deleted // If there are no VCs left in verification after having the channel deleted
@@ -160,9 +171,9 @@ export class VerificationLeaveVCListener extends Listener {
return; return;
} }
const verification = listVoiceChannels.last() as VoiceChannel | undefined; const verification = listVoiceChannels.last();
if (verification === undefined) { if (!isVoiceChannel(verification)) {
this.container.logger.error( this.container.logger.error(
'Verification: Verification channel not found.', 'Verification: Verification channel not found.',
); );

View File

@@ -22,7 +22,10 @@ import type { Message } from 'discord.js';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
export class VerificationMessageCounter extends Listener { export class VerificationMessageCounter extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
event: 'messageCreate', event: 'messageCreate',

View File

@@ -18,18 +18,21 @@
*/ */
import { Listener } from '@sapphire/framework'; import { Listener } from '@sapphire/framework';
import type { import type { VoiceChannel } from 'discord.js';
Client,
CategoryChannel,
TextChannel,
VoiceChannel,
} from 'discord.js';
import { ChannelType } from 'discord.js';
import { createVerificationVoice } from '#utils/verification'; import { createVerificationVoice } from '#utils/verification';
import IDs from '#utils/ids'; import IDs from '#utils/ids';
import { getCategoryChannel } from '#utils/fetcher';
import {
isCategoryChannel,
isTextChannel,
isVoiceChannel,
} from '@sapphire/discord.js-utilities';
export class VerificationReady extends Listener { export class VerificationReady extends Listener {
public constructor(context: Listener.Context, options: Listener.Options) { public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, { super(context, {
...options, ...options,
once: true, once: true,
@@ -37,59 +40,67 @@ export class VerificationReady extends Listener {
}); });
} }
public async run(client: Client) { public async run() {
// Get verification category // Get verification category
let category = client.channels.cache.get(IDs.categories.verification) as const category = await getCategoryChannel(IDs.categories.verification);
| CategoryChannel
| undefined; if (!isCategoryChannel(category)) {
if (category === undefined) { this.container.logger.error('verifyStart: Channel not found');
category = (await client.channels.fetch(IDs.categories.verification)) as return;
| CategoryChannel
| undefined;
if (category === undefined) {
this.container.logger.error('verifyStart: Channel not found');
return;
}
} }
// Check how many voice channels there are // Check how many voice channels there are
const voiceChannels = category.children.cache.filter( const voiceChannels = category.children.cache.filter((channel) =>
(c) => c.type === ChannelType.GuildVoice, isVoiceChannel(channel),
); );
const currentVCs: VoiceChannel[] = []; const currentVCs: VoiceChannel[] = [];
const emptyVC: string[] = []; const emptyVC: string[] = [];
// Delete voice channels // Delete voice channels
voiceChannels.forEach((c) => { for (const c of voiceChannels) {
const voiceChannel = c as VoiceChannel; const channel = c[1];
if (voiceChannel.members.size === 0) {
emptyVC.push(voiceChannel.id); if (!isVoiceChannel(channel)) {
voiceChannel.delete(); continue;
} else {
currentVCs.push(voiceChannel);
} }
});
if (channel.members.size === 0) {
emptyVC.push(channel.id);
await channel.delete();
} else {
currentVCs.push(channel);
}
}
// Delete text channels // Delete text channels
const textChannels = category.children.cache.filter( const textChannels = category.children.cache.filter((channel) =>
(c) => c.type === ChannelType.GuildText, isTextChannel(channel),
); );
textChannels.forEach((c) => {
const textChannel = c as TextChannel; for (const c of textChannels) {
const channel = c[1];
if (!isTextChannel(channel)) {
continue;
}
// Checks if the channel topic has the user's snowflake // Checks if the channel topic has the user's snowflake
emptyVC.forEach((snowflake) => { for (const snowflake in emptyVC) {
if (textChannel.topic!.includes(snowflake)) { if (channel.topic !== null && channel.topic.includes(snowflake)) {
textChannel.delete(); await channel.delete();
} }
}); }
}); }
// Check if there is no voice channels, create verification // Check if there is no voice channels, create verification
let verification = false; let verification = false;
currentVCs.forEach((c) => {
if (c.name === 'Verification') { currentVCs.forEach((channel) => {
if (channel.name === 'Verification') {
verification = true; verification = true;
} }
}); });
if (!verification) { if (!verification) {
await createVerificationVoice(category); await createVerificationVoice(category);
} }

View File

@@ -0,0 +1,90 @@
// 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 { Listener } from '@sapphire/framework';
import { GuildMember } from 'discord.js';
import IDs from '#utils/ids';
import { noModHistory, userPreviouslyHadRole } from '#utils/database/memberMod';
import { getRole } from '#utils/fetcher';
import { isRole } from '#utils/typeChecking';
/**
* Gives the trusted role to users who have levelled up to level 5
* and has not gotten any other warnings/restrictions prior.
*/
export class TrustedListener extends Listener {
public constructor(
context: Listener.LoaderContext,
options: Listener.Options,
) {
super(context, {
...options,
event: 'xpLevelUp',
});
}
public async run(member: GuildMember, level: number) {
// Checks if the member has gotten level 7
// Has been nefred. Should take around 1.5 hours to get the trusted role now
if (level !== 7) {
return;
}
// Checks if the user has been previously moderated
const noModerationHistory = await noModHistory(member.id);
if (!noModerationHistory) {
return;
}
const { guild } = member;
const trusted = await getRole(IDs.roles.trusted, guild);
if (!isRole(trusted)) {
this.container.logger.error(
'TrustedXP Listener: the Trusted role could not be found in the guild.',
);
return;
}
// Checks if the member has previously had the trusted role given/removed
const previouslyHadRole = await userPreviouslyHadRole(
member.id,
trusted.id,
);
if (previouslyHadRole) {
return;
}
// Checks if the user already has the trusted role
if (member.roles.cache.has(trusted.id)) {
return;
}
// Gives the trusted role to the member
await member.roles.add(trusted);
// Send a DM to inform the member that they have been given the trusted role
await member.user.send(
`Hi, you have been given the ${trusted.name} as you have been interacting in ARA for a long enough time!` +
'\n\nThis role allows you to post attachments to the server and stream in VCs.' +
'\nMake sure that you follow the rules, especially by **not** posting anything **NSFW**, and **no animal products or consumption of animal products**.' +
`\n\nNot following these rules will result in the **immediate removal** of the ${trusted.name} role.`,
);
}
}

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