Compare commits

...

433 Commits

Author SHA1 Message Date
Lee
7f935da4b4 add nerddetector command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m35s
2025-01-15 18:03:42 +00:00
Lee
f3aad3925a update ppize
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m7s
2025-01-09 21:46:16 +00:00
Lee
ae741e1f13 oops
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m40s
2025-01-09 21:40:11 +00:00
Lee
b78ef6e96c oops
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m5s
2025-01-06 05:20:05 +00:00
Lee
d3661127cf update help cmd
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m8s
2025-01-06 05:16:01 +00:00
Lee
fc1216be25 add how gay command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m44s
2025-01-06 05:03:24 +00:00
Lee
38089bdcd5 add how gay command 2025-01-06 05:03:10 +00:00
Lee
c424694068 update help cmd 2024-12-27 13:54:30 +00:00
Lee
b3a6284e40 cleanup
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m42s
2024-12-27 13:04:57 +00:00
Lee
5f099a97f0 remove some things and bump depends
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m14s
2024-12-27 12:50:13 +00:00
577c895169 Merge remote-tracking branch 'origin/master'
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 12m51s
2024-11-29 03:38:35 +00:00
6db2a01bb3 expire pastes after a month 2024-11-29 03:38:28 +00:00
Lee
4cc4334df4 Merge pull request 'Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.5' (#44) from renovate/spring-boot into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 7s
Reviewed-on: #44
2024-11-02 23:16:31 +00:00
Lee
a4acdc2ec1 Merge pull request 'Update dependency org.apache.httpcomponents.client5:httpclient5 to v5.4.1' (#45) from renovate/org.apache.httpcomponents.client5-httpclient5-5.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #45
2024-11-02 23:16:18 +00:00
Lee
7ca984e785 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.16.0' (#46) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 5s
Reviewed-on: #46
2024-11-02 23:16:13 +00:00
Lee
53730a82cf Merge pull request 'Update dependency io.mongock:mongock-bom to v5.5.0' (#47) from renovate/io.mongock-mongock-bom-5.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #47
2024-11-02 23:16:06 +00:00
Lee
6f4a787ccb Merge pull request 'Update dependency io.mongock:mongock-springboot-v3 to v5.5.0' (#48) from renovate/io.mongock-mongock-springboot-v3-5.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #48
2024-11-02 23:16:02 +00:00
Lee
2b588a5940 Merge pull request 'Update dependency io.mongock:mongodb-springdata-v4-driver to v5.5.0' (#49) from renovate/io.mongock-mongodb-springdata-v4-driver-5.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #49
2024-11-02 23:15:56 +00:00
Lee
fe4cf3bbf4 Merge pull request 'Update eclipse-temurin Docker tag to v17.0.13_11-jre-focal' (#50) from renovate/eclipse-temurin-17.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #50
2024-11-02 23:15:51 +00:00
ede931f994 Update dependency org.apache.httpcomponents.client5:httpclient5 to v5.4.1 2024-10-28 18:01:11 +00:00
d5ed2b0b13 Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.5 2024-10-24 14:01:29 +00:00
4479bce0a4 Update eclipse-temurin Docker tag to v17.0.13_11-jre-focal 2024-10-24 04:01:33 +00:00
3c80b3ca4b Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.16.0 2024-10-23 14:01:31 +00:00
65fd68f76c Update dependency io.mongock:mongodb-springdata-v4-driver to v5.5.0 2024-10-17 00:02:24 +00:00
6640dd4368 Update dependency io.mongock:mongock-springboot-v3 to v5.5.0 2024-10-16 23:00:45 +00:00
96cb634fd4 Update dependency io.mongock:mongock-bom to v5.5.0 2024-10-16 23:00:43 +00:00
Lee
0f9a23e4a2 Update pom.xml
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 55s
2024-09-02 08:25:19 +00:00
Lee
eb3d8eea29 Merge pull request 'Update dependency maven to v3.9.9' (#42) from renovate/maven-3.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #42
2024-09-02 08:24:54 +00:00
Lee
7e45b5d880 Merge pull request 'Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.3' (#43) from renovate/spring-boot into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m53s
Reviewed-on: #43
2024-09-01 01:50:49 +00:00
892b85ccb4 update msg log paste
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 2m6s
2024-08-25 01:58:07 +01:00
f291344c45 oops
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m25s
2024-08-22 20:47:11 +01:00
df9b0e2604 oopsie
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m16s
2024-08-22 20:31:22 +01:00
2695a2994a add coin flip command
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 1m54s
2024-08-22 20:23:11 +01:00
fa50eb9873 Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.3 2024-08-22 19:01:10 +00:00
58bb976f96 Update dependency maven to v3.9.9 2024-08-19 21:01:17 +00:00
3615ef4b92 Merge remote-tracking branch 'origin/master'
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 1m31s
2024-08-15 13:59:19 +01:00
5c78ec907f fix minecraft player lookup error handling 2024-08-15 13:59:13 +01:00
Lee
c380878039 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.14.0' (#40) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 2m4s
Reviewed-on: #40
2024-08-13 17:28:28 +00:00
cfaa501377 Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.14.0 2024-08-13 09:01:17 +00:00
Lee
6f12062311 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.13.0' (#39) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 2m19s
Reviewed-on: #39
2024-07-31 15:59:30 +00:00
3edfab3adb Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.13.0 2024-07-31 10:00:36 +00:00
Lee
25d4b247b3 Merge pull request 'Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.2' (#38) from renovate/org.springframework.boot-spring-boot-starter-parent-3.x into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 2m16s
Reviewed-on: #38
2024-07-30 20:52:04 +00:00
Lee
393c7741ca Merge pull request 'Update eclipse-temurin Docker tag to v17.0.12_7-jre-focal' (#18) from renovate/eclipse-temurin-17.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #18
2024-07-30 20:51:59 +00:00
Lee
001836c88b Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.12.1' (#16) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #16
2024-07-30 20:51:53 +00:00
Lee
be7e03d641 Merge pull request 'Update dependency se.michaelthelin.spotify:spotify-web-api-java to v8.4.1' (#19) from renovate/se.michaelthelin.spotify-spotify-web-api-java-8.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #19
2024-07-30 20:51:46 +00:00
db357a63f5 Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.2 2024-07-26 12:03:33 +00:00
e50fe57ee6 Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.12.1 2024-07-25 13:00:41 +00:00
2c017d0603 Update dependency se.michaelthelin.spotify:spotify-web-api-java to v8.4.1 2024-07-24 20:01:06 +00:00
f32d278a6c Update eclipse-temurin Docker tag to v17.0.12_7-jre-focal 2024-07-23 01:01:16 +00:00
b8ebc1de24 fix in dms
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m9s
2024-07-16 14:40:53 +01:00
2b2e10d994 oopsie
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 54s
2024-07-16 14:37:46 +01:00
8526036044 add scoresaber score summary
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m59s
2024-07-16 14:35:02 +01:00
f5c31195da fix log title
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m28s
2024-07-10 10:42:19 +01:00
d6175b3f92 oops
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 2m14s
2024-07-10 06:00:40 +01:00
96e7518f72 fix interactions
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m19s
2024-07-09 23:02:12 +01:00
f1bc2b2aaa add user validation to moderation and to user lookup
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m18s
2024-07-09 20:34:56 +01:00
4ec65c8d6e validate id when getting user
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m11s
2024-07-09 20:24:50 +01:00
38465f544d fix past tense
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m19s
2024-07-09 20:19:21 +01:00
a3f4e2b918 finish moderation
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 2m2s
2024-07-09 19:46:48 +01:00
bc57834366 fix spelling
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m14s
2024-07-07 07:02:49 +01:00
87a56700ec add stat channels feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m25s
2024-07-07 06:50:25 +01:00
6d98977198 Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m30s
2024-07-07 05:50:21 +01:00
1256d2ddf9 change bot motd 2024-07-07 05:46:48 +01:00
Lee
7cf970df77 Merge pull request 'Update dependency org.apache.maven.plugins:maven-shade-plugin to v3.6.0' (#14) from renovate/org.apache.maven.plugins-maven-shade-plugin-3.x into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m7s
Reviewed-on: #14
2024-07-07 02:35:48 +00:00
Lee
52557b5c65 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.11.0' (#13) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #13
2024-07-07 02:35:24 +00:00
898a39e99c change some command categories
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m26s
2024-07-07 03:01:36 +01:00
7a66ed4d87 Update dependency org.apache.maven.plugins:maven-shade-plugin to v3.6.0 2024-07-07 02:00:30 +00:00
5cc504d4ed Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.11.0 2024-07-07 02:00:28 +00:00
29b44edc6b Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m38s
2024-07-07 02:18:16 +01:00
2a7e2acc88 cleanup 2024-07-07 02:18:10 +01:00
Lee
0d92a49d95 Merge pull request 'Update dependency io.mongock:mongock-bom to v5.4.4' (#12) from renovate/io.mongock-mongock-bom-5.x into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 17s
Reviewed-on: #12
2024-07-07 01:00:59 +00:00
Lee
2d8779d8e0 Merge pull request 'Update dependency com.google.code.gson:gson to v2.11.0' (#11) from renovate/com.google.code.gson-gson-2.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #11
2024-07-07 01:00:50 +00:00
765805fa7a Update dependency io.mongock:mongock-bom to v5.4.4 2024-07-07 01:00:30 +00:00
1d36479a82 Update dependency com.google.code.gson:gson to v2.11.0 2024-07-07 01:00:28 +00:00
Lee
9d6f3b9fef Merge pull request 'Update dependency org.projectlombok:lombok to v1.18.34' (#10) from renovate/org.projectlombok-lombok-1.x into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m9s
Reviewed-on: #10
2024-07-07 00:44:25 +00:00
Lee
9be60fee4c Merge pull request 'Update dependency maven to v3.9.8' (#9) from renovate/maven-3.x into master
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #9
2024-07-07 00:44:19 +00:00
cb82716a17 Update dependency org.projectlombok:lombok to v1.18.34 2024-07-07 00:42:58 +00:00
80439a3329 Update dependency maven to v3.9.8 2024-07-07 00:42:57 +00:00
Lee
627062ef23 Update renovate.json
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m19s
2024-07-07 00:42:09 +00:00
25dd6cb0dc silly me
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 48s
2024-07-07 01:34:46 +01:00
217b284f14 move lookup to a sub command and add mem usage to the bot stats command
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
2024-07-07 01:34:27 +01:00
843bb34fb4 add paste command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m5s
2024-07-07 01:23:20 +01:00
8a01359f6e Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m44s
2024-07-07 01:19:31 +01:00
8f85ce91da oopsie!!!!!!!!! 2024-07-07 01:12:22 +01:00
Lee
93f232805e revert 585d3e079390a9dd5f24d87499b0da27556ca617
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 5s
revert add version number to the embeds
2024-07-06 23:58:09 +00:00
Lee
41f92400c7 Update src/main/java/cc/fascinated/bat/features/base/BaseFeature.java
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
2024-07-06 23:57:58 +00:00
585d3e0793 add version number to the embeds
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 1m4s
2024-07-07 00:28:58 +01:00
b5ac5d6a9b only allow sniped messages to be sniped for 1 hour after deletion
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m22s
2024-07-07 00:10:39 +01:00
d9dd175174 update bot invite url
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 52s
2024-07-06 21:17:23 +01:00
7a1ffc0538 update cache expiration policy
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m17s
2024-07-06 21:14:20 +01:00
04c4533c40 update command log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m13s
2024-07-06 20:08:26 +01:00
353962e569 enable spotify linking (even tho it doesn't work w/o me adding you rn)
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m17s
2024-07-06 20:02:20 +01:00
d0ba6e72a4 fix pp size for not in guild
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m11s
2024-07-06 19:41:16 +01:00
b63b25f8ab update pp size command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m11s
2024-07-06 19:37:26 +01:00
2627b80148 update pp size command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 50s
2024-07-06 19:36:27 +01:00
16bbda57f6 add ppsize command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m16s
2024-07-06 19:34:03 +01:00
e65b7b8232 Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m26s
2024-07-06 17:26:37 +01:00
d90bdfe3b6 cleanup bot admin 2024-07-06 17:26:31 +01:00
8def168ef7 bob
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m19s
2024-07-06 09:22:58 +01:00
9379ebb33d make the server watcher list command look better
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m13s
2024-07-06 07:35:06 +01:00
bbbbce557b oopsie
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m29s
2024-07-06 07:21:19 +01:00
6fef1d0092 check feature states
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m8s
2024-07-06 06:26:33 +01:00
f7aba7a49b lower retry limit 2024-07-06 06:24:53 +01:00
514f4757a9 impl server watcher for minecraft servers
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m27s
2024-07-06 06:21:05 +01:00
ee7e8b64c5 add minecraft server and player lookups
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m28s
2024-07-06 05:34:50 +01:00
6de1e8b2b1 fix access token refreshing?!??!
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 52s
2024-07-06 04:18:10 +01:00
98e9003129 update bot joining guild message
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m3s
2024-07-06 04:14:06 +01:00
9d78432211 add leveling feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m5s
2024-07-06 04:04:38 +01:00
60ce8df108 consistency
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m0s
2024-07-06 02:11:02 +01:00
ab5cec535b update sync cmd title
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 41s
2024-07-06 02:04:37 +01:00
ec9b0cc862 add auto role sync command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m56s
2024-07-06 02:01:20 +01:00
d45cd48ff6 no need for this, oops
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 5s
2024-07-06 00:42:14 +01:00
3126935057 add checks to the skip command
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
2024-07-06 00:41:31 +01:00
27244d0d98 use expiring map for users
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m2s
2024-07-06 00:34:53 +01:00
de1da2391d add currently playing cache to spotify
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m28s
2024-07-06 00:31:48 +01:00
9395ae73b9 use Sentry hints
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m5s
2024-07-06 00:24:06 +01:00
fa932b7fc2 oops
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-07-06 00:19:52 +01:00
bd340448a1 catch exceptions
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 42s
2024-07-06 00:18:06 +01:00
45460f8b90 update check mark emoji
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 56s
2024-07-06 00:10:31 +01:00
fbb292a591 oopsie
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m1s
2024-07-06 00:05:52 +01:00
31aad2744c oopsie
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m6s
2024-07-06 00:02:45 +01:00
35b8fff808 fix npe
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 44s
2024-07-06 00:00:53 +01:00
8cc465e53d clean spotify commands
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m17s
2024-07-05 23:59:27 +01:00
ec54d5427e update interaction error message
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m6s
2024-07-05 23:35:41 +01:00
07c5a7358b fix help cmd
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 54s
2024-07-05 23:24:36 +01:00
4aae21b594 update help cmd for running outside a guild
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 59s
2024-07-05 23:21:28 +01:00
650556079b fix help not working without a guild
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-07-05 23:15:29 +01:00
29d3fe3701 fix npe part 2
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m9s
2024-07-05 23:13:12 +01:00
4cc48412f7 don't log as much when getting scores for SS
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 56s
2024-07-05 23:02:27 +01:00
ccdeebdc1c fix npe
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m13s
2024-07-05 22:58:32 +01:00
6dc801dda6 yes!
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m5s
2024-07-05 22:38:14 +01:00
d01c7d4965 maybe fix this?
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 51s
2024-07-05 22:35:29 +01:00
0e33179260 maybe fix this?
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 57s
2024-07-05 22:33:00 +01:00
36d29caa7a change avatar url in the log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m26s
2024-07-05 22:29:25 +01:00
a605ab2dba fix some logging things
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m5s
2024-07-05 21:47:29 +01:00
863206bb9f change log color for voice channel switching
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 59s
2024-07-05 21:42:47 +01:00
a7af4108c6 switch voice channel log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m15s
2024-07-05 21:38:21 +01:00
fdd36d4385 fix markdown in the message breaking the log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m6s
2024-07-05 21:12:25 +01:00
0cfccc70d8 update log format for messages
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 44s
2024-07-05 21:03:56 +01:00
2791d6328e update depend
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m7s
2024-07-05 21:02:18 +01:00
b4f4a12da8 fix double interaction reply
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 57s
2024-07-05 20:49:22 +01:00
bb81c098b2 update scoresaber me/user command to show raw per global
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m12s
2024-07-05 20:44:43 +01:00
bef2b695f5 remove some debug and fix depends on for scoresaber service
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m4s
2024-07-05 19:49:16 +01:00
d575e0ec9e fix for real this time
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 50s
2024-07-05 19:46:06 +01:00
4f97e181a7 fix log error
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 56s
2024-07-05 19:43:18 +01:00
b5b306cc6d fix welcome msg
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m9s
2024-07-05 19:40:01 +01:00
5b917af1ca fix intent
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 45s
2024-07-05 19:35:33 +01:00
0b09b8ba5f default disable all features and add bot join guild message
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m1s
2024-07-05 19:33:10 +01:00
4e866895a0 impl counter feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 2m13s
2024-07-05 19:07:32 +01:00
f6a23e4888 update role log embed
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m3s
2024-07-04 18:15:05 +01:00
75da7a4b51 a lil cleanup and add role perms when adding and removing a role to the log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 59s
2024-07-04 18:04:15 +01:00
313f43172d who even cares about this
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 5s
2024-07-04 18:00:44 +01:00
6efb042f14 fix role log color
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
2024-07-04 18:00:16 +01:00
9be80a0f7e nvm to this too
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 5s
2024-07-04 17:56:56 +01:00
cd9563f77e nvm
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
2024-07-04 17:56:39 +01:00
0ba532a9b5 add channel position update log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 57s
2024-07-04 17:55:10 +01:00
96c4e03e81 add role update position log 2024-07-04 17:54:06 +01:00
3995bf9992 add thread archive event
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 58s
2024-07-04 17:50:56 +01:00
8946a18b0b update log message
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m10s
2024-07-04 17:45:39 +01:00
6ebfb4b289 fix log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 43s
2024-07-04 17:17:26 +01:00
35223c376a holy new events that are getting logged
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m3s
2024-07-04 17:15:34 +01:00
f637faf0b6 yes
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m5s
2024-07-04 15:59:16 +01:00
16e956d718 fix button interactions
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 49s
2024-07-04 15:53:49 +01:00
ee62eae519 fix button interactions
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m2s
2024-07-04 15:52:43 +01:00
f7aea851b2 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	src/main/java/cc/fascinated/bat/features/tmdb/command/MovieSubCommand.java
2024-07-04 15:52:17 +01:00
0e781f3d9a fix button interactions 2024-07-04 15:51:50 +01:00
0c6f3a0b4c silly resource leak nick
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m9s
2024-07-04 14:50:45 +00:00
c78033f409 fix cached footer
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 52s
2024-07-04 15:44:05 +01:00
9a5609f40e update scoresaber command messages
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m0s
2024-07-04 15:42:16 +01:00
7abdcd4d95 fix
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m2s
2024-07-04 15:36:52 +01:00
92a0c23b49 yes
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 47s
2024-07-04 15:32:24 +01:00
65ba1d91ac Update src/main/java/cc/fascinated/bat/command/InternalCommandInfo.java
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
2024-07-04 14:31:30 +00:00
4ae2e7566a ples bby gurl
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 48s
2024-07-04 15:27:08 +01:00
5e17f655a4 ples bby gurl
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m6s
2024-07-04 15:25:03 +01:00
11299b3ca7 fix?
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m5s
2024-07-04 15:20:26 +01:00
d023e21575 help menu use proper button ids
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m8s
2024-07-04 14:57:54 +01:00
d70a29d363 use a perm check for the list buttons
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m10s
2024-07-04 14:43:15 +01:00
7bf468b470 fix invite log channel
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m2s
2024-07-04 14:36:07 +01:00
0d9b6ed8f8 oops, cleanup imports
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 47s
2024-07-04 14:21:30 +01:00
69adc56e4e re-impl help cmd and fix cmd categories
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
2024-07-04 14:21:02 +01:00
57d5dc0c42 Merge pull request 'lol' (#8) from Rainnny/Bat:master into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 59s
Reviewed-on: #8
2024-07-04 13:02:29 +00:00
ee1b2e0ae0 lol 2024-07-04 09:01:56 -04:00
Lee
65af4c8c16 Merge pull request 'New command system' (#7) from Rainnny/Bat:master into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m9s
Reviewed-on: #7
2024-07-04 12:58:35 +00:00
47b4796695 fix subcomamnds not having options 2024-07-04 08:51:27 -04:00
4fdd00453a rework command system 2024-07-04 08:47:32 -04:00
e19334a9ef dont require sentry 2024-07-04 07:20:31 -04:00
66a2d8b2a9 ?????
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m10s
2024-07-04 12:05:37 +01:00
cbc28701c9 fix cmds??
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m11s
2024-07-04 12:02:12 +01:00
de6eabc1b7 user install commands
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m1s
2024-07-04 11:58:54 +01:00
26fddc0493 pls fix
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m11s
2024-07-04 11:53:25 +01:00
50796d00f0 MAYBE USER INSTALL COMMANDS PLEASE??~!?!?!?!?!?!?!!!?!?!!?!!?!?!?!!!?!??!??!?!?!?!?!?!?!?!?!?!?!?!?!
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m17s
2024-07-04 11:46:54 +01:00
bf2e046718 jda is wank!
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 11:16:25 +01:00
05bc0f0a41 jda is wank!
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 11:12:42 +01:00
21e7ddf549 slowmode update logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 11:00:32 +01:00
14ff692b87 add channel region changing logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 41s
2024-07-04 10:49:21 +01:00
26b7b7158a add role color and icon update, and add channel nsfw and user limit update logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-07-04 10:40:51 +01:00
626a9bd77d update log messages and add bitrate and topic change logs
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-07-04 10:22:19 +01:00
06ded19492 fix new avatar being broken if they remove their avatar
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 43s
2024-07-04 09:57:12 +01:00
6ba802912f add role create, delete, rename and update permissions logs
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-07-04 09:56:06 +01:00
c284bb8a7f update the log remove command to be the same design as the set one
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-07-04 09:41:21 +01:00
dbe6fb4762 add channel permission update logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 41s
2024-07-04 09:36:40 +01:00
a0e0b82f40 add user lookup command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 41s
2024-07-04 09:15:26 +01:00
96e675a6be update msg
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-04 07:53:03 +01:00
ed79a46af3 update format for messages in logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 07:51:04 +01:00
ee138cb5a9 update format for messages in logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 43s
2024-07-04 07:49:35 +01:00
45bfae281e update logging topics
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 07:43:53 +01:00
52d4b65d92 update message
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 07:09:01 +01:00
5b0f06efc2 add topics to logs
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-07-04 07:06:54 +01:00
43c5473a4f don't show old avatar url and make the thumbnail of the log the new avatar
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 07:03:48 +01:00
dc04a2f36a fix channel rename log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 45s
2024-07-04 07:01:46 +01:00
407ee6f1e9 log errors to a channel
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 06:55:56 +01:00
09d850146a add creation time for user and guild log 2024-07-04 06:47:19 +01:00
e8fa4c72dd disable user and guild load log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 06:46:33 +01:00
c31ec09e0f make the log embeds more fancy
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 06:44:55 +01:00
4148f6e05f check if the member is still boosting and update log set messages
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 06:41:17 +01:00
0120fa24ef find users in a voice channel when starting to make logs more accurate
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-04 06:24:51 +01:00
4a522c084d add emojis to the log category buttons and add a home button
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 06:21:31 +01:00
188124991e fix for losing data... LOL
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 45s
2024-07-04 05:54:13 +01:00
7f09c6d06c remove unneeded check
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-04 05:49:47 +01:00
e873988c47 use pages for the log list command and tell the user about the list command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 05:48:20 +01:00
79d20672c5 update log 2024-07-04 05:28:06 +01:00
772787a98e make log messages more consistent
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-04 05:25:19 +01:00
b6ba778ff2 fixes
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 05:14:47 +01:00
cdd953351a add emoji logs and fix log channels that are set from vanishing
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 05:10:35 +01:00
c1d775c9d0 maybe fix boost event??
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 04:43:53 +01:00
a4e3598986 update voice channel join/leave logs
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-04 04:38:02 +01:00
8cdc2a853b auto log names
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-04 04:34:20 +01:00
f940bd526c fix channel delete logging for non text channel channels
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 04:32:14 +01:00
490080b17a add seperate events for boosting
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-04 04:30:45 +01:00
595c61e789 impl boost logging????
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 04:28:42 +01:00
040c644ab1 use the util for getting channels
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 03:52:33 +01:00
fa1f18e11b don't log every channel find
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 03:42:56 +01:00
ece36f7f27 maybe fix loading log channels
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 03:40:12 +01:00
3b3ea2b3cc maybe fix some NPEs when calling events
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 50s
2024-07-04 03:33:06 +01:00
c98d8d7e26 log correct name in user creation
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 51s
2024-07-04 03:17:07 +01:00
49beb864d2 use generic embed for interaction errors
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 03:15:53 +01:00
750b8cbfea handle errors on interactions
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 03:07:08 +01:00
2fcc1b66c1 update original message
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 02:58:29 +01:00
040846ef0a fix shutdown db saving (fr this time) and fixed embed color parsing
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 02:47:36 +01:00
96486bb3a1 fix shutdown db saving
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 47s
2024-07-04 02:25:26 +01:00
78daf4531b log user name not id
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-04 01:58:06 +01:00
a9e4626dfd fix tmdb command errors
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 01:47:01 +01:00
dc7e44239f fix npe and maybe fix null users????????
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 01:33:24 +01:00
2349f10b35 fix message and fix category name
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-07-04 01:17:21 +01:00
bd9f4ef971 fix non null
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-04 01:11:26 +01:00
6698b7c656 Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m20s
2024-07-04 01:04:50 +01:00
0a80db8195 return a msg if it's not your cancel request 2024-07-04 01:04:38 +01:00
Lee
a3af3c3637 Merge pull request 'TMDB Support' (#6) from okNick/Bat:master into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
Reviewed-on: #6
2024-07-03 23:58:40 +00:00
34102e9b22 Merge branch 'master' of https://git.fascinated.cc/Fascinated/Bat
# Conflicts:
#	pom.xml
2024-07-03 18:55:48 -05:00
7083bebef1 impl drag feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 43s
2024-07-04 00:54:16 +01:00
c81835cb2d funny git merge 2024-07-03 18:46:42 -05:00
80e7afedea Silly extra space 2024-07-03 18:44:26 -05:00
285a0ca00a Merge branch 'master' of https://git.fascinated.cc/Fascinated/Bat
# Conflicts:
#	src/main/java/cc/fascinated/bat/command/Category.java
2024-07-03 18:44:09 -05:00
f07e30d843 Add support for TMDB movie + series lookup 2024-07-03 18:41:58 -05:00
bd9ac1e138 maybe fix npe??
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-03 23:23:23 +01:00
3878d3029b mention voice channel in the embed
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 23:09:28 +01:00
831bc934b4 cleanup voice logs
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-03 23:07:50 +01:00
938005f6d9 add null check
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 23:02:42 +01:00
5959b814a7 use proper embed color for voice channel logs
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-07-03 23:01:56 +01:00
5f75302f3a don't make accounts for bots
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 22:59:05 +01:00
a7a7bc784b impl voice join and leave logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 22:57:51 +01:00
2b4980fb10 change auto role log to include the guild
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 22:18:51 +01:00
655662c6f8 fix embed color 2024-07-03 22:18:35 +01:00
642185f8c5 fix welcomer serialization
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-03 22:12:56 +01:00
c2e447f416 fix welcomer placeholders and fix channel command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 22:10:35 +01:00
271a1cf88d fix npe
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 22:04:40 +01:00
11e7ca4aa6 impl purge command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-03 21:43:29 +01:00
f6834db9cb add 8ball command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-03 20:19:21 +01:00
90aaf5422f fix some messages
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-03 19:53:47 +01:00
e4183b4882 impl welcomer feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-03 19:49:19 +01:00
f62a022ed5 logging messages
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 16:51:36 +01:00
50b8b4b2c1 Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 46s
2024-07-03 16:35:40 +01:00
920755eae0 maybe fix member leave?? 2024-07-03 16:34:52 +01:00
Lee
2255b02a60 update vote cmd
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 01:09:50 +00:00
f30697d1a6 fix null on old avatar in logs
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 41s
2024-07-03 02:03:26 +01:00
83250d2c08 add check to see if the member is in the guild before logging some events
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m27s
2024-07-03 01:59:53 +01:00
d7916ad24a oopsie, log after command was ran
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 00:41:28 +01:00
295d673d06 fix cmd execution log
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
2024-07-03 00:40:50 +01:00
da06a01097 fix WebRequest#getAsEntity
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 51s
2024-07-03 00:39:20 +01:00
6202aa6691 update member join log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 41s
2024-07-03 00:35:58 +01:00
82a87c79b2 add execution time for commands
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-03 00:34:10 +01:00
e795d542b9 update max reminder time
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-03 00:33:07 +01:00
162d7af46b fix max reminder length
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-03 00:32:14 +01:00
821190a144 fix reminder message
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 42s
2024-07-03 00:30:27 +01:00
cb35182c6a update reminder message
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
2024-07-03 00:29:50 +01:00
ac499898e3 fix
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 43s
2024-07-03 00:23:46 +01:00
35596b720b maybe fix a NPE??
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 41s
2024-07-03 00:14:00 +01:00
048d2856f9 impl reminders
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 43s
2024-07-03 00:10:02 +01:00
5f654f9ca6 update vote command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 21:41:13 +01:00
4540bdef99 add tos and privacy policy to the help cmd
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-02 21:30:10 +01:00
eda4eb5973 temp tos and privacy policy
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-02 21:17:37 +01:00
68a9a6dc48 impl member avatar update logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-07-02 21:05:20 +01:00
ff23ea1d6c impl global name and username update logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 21:03:40 +01:00
982c038b07 don't show clickable channel on channel delete log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-02 20:07:34 +01:00
52223b5233 add avatar to member join log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-07-02 20:06:41 +01:00
45755503a7 show account age on member join log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 20:04:27 +01:00
194a5d8119 fix timeout millis
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 19:57:25 +01:00
120afee73b impl member timeout logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 19:55:50 +01:00
8b7340715c impl member ban and unban logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 43s
2024-07-02 19:47:51 +01:00
38bde93d16 update channel log messages
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 19:43:27 +01:00
a3ffaf1ab9 impl channel create and delete logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 19:24:48 +01:00
86c147f359 merge role add/remove into 1 update type and rename nickname logtype
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 19:09:01 +01:00
deb93e442c impl role add/remove logging
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 19:05:21 +01:00
6eca92b4cf add member nickname update log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 18:58:50 +01:00
3f93df131d looks too messy with this
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-02 18:47:28 +01:00
1028dca15a add more data to the logs
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-02 18:36:17 +01:00
e03aef0ad5 impl member join and leave logs
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 41s
2024-07-02 18:31:18 +01:00
8ce3a5d25c implement logging feature base 2024-07-02 18:21:24 +01:00
0b8caf3e25 add vote link
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-02 01:56:59 +01:00
317eaaec8a impl a EmbedDescriptionBuilder
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-02 01:51:13 +01:00
4f975ab07a use paste for messages longer than 512 and fix message sniping
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m10s
2024-07-02 01:20:41 +01:00
1a69bce9dd sort sniped messages
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-07-02 00:54:55 +01:00
37c69597be why angry??????
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-01 23:02:28 +01:00
3146ed7d6d fix dev commands
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-01 22:56:52 +01:00
69281d113c fix marked as non null err and removed profiles debug
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-01 21:51:51 +01:00
c6289d1c8e fix for npe??
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-01 21:49:02 +01:00
3082265ec6 add a new motd
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-01 21:27:20 +01:00
a057853cbd add feature disabled check
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 44s
2024-07-01 21:21:47 +01:00
8b451c6ee5 add message snipe feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-07-01 21:20:39 +01:00
727a4c9a6f set name when user joins guild 2024-07-01 19:41:13 +01:00
4bf099d25e Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-01 19:40:38 +01:00
20c5f71cd4 fix user leaving guild npe 2024-07-01 19:40:32 +01:00
a6e490dbe5 fix npe 2024-07-01 19:40:16 +01:00
Lee
87bf0b9f67 Merge pull request 'Configure Renovate' (#3) from renovate/configure into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 59s
Reviewed-on: #3
2024-07-01 15:01:23 +00:00
419bdf1fea Add renovate.json 2024-07-01 15:00:36 +00:00
f2b2dbc794 rename interaction to event
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-07-01 15:27:39 +01:00
8361f3c784 update member count command to look similar to botstats command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-07-01 15:23:19 +01:00
52349a17c3 cleanup imports 2024-07-01 15:21:09 +01:00
c1f9bfec6a don't remove birthdays for members that have left
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-07-01 15:20:49 +01:00
be7f8a9057 only check guilds that we're in
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-07-01 15:16:06 +01:00
c93e112ebf might fix, who knows
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-07-01 15:14:42 +01:00
7485bd2ec8 fix premium admin command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-07-01 01:46:56 +01:00
ed83175a39 add env for admin guild
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-07-01 01:44:22 +01:00
a001f2dd4c add valid guild and user id check
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-07-01 01:41:40 +01:00
b1785ce373 impl pre shutdown saving
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-07-01 01:33:52 +01:00
b1f5db9b2d maybe fix this?
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 45s
2024-07-01 01:26:10 +01:00
d372c41c98 big ass refactor to handle loading guilds and users without spring to make it more futureproof
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 44s
2024-07-01 01:12:32 +01:00
f566c3bcb5 add emojis to feature states
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-06-30 08:34:59 +01:00
6403c57db5 add checks for some events to see if the feature is enabled and more cleanup
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-06-30 08:24:14 +01:00
22d4558d84 use an enum for feature states
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-06-30 08:10:49 +01:00
ea546f02ca cleanup features and move all misc commands into a base feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 48s
2024-06-30 08:00:03 +01:00
5b1ddb145f make feature command less ugly and update feature disabled message
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-06-30 07:41:31 +01:00
5ce5ef6898 format numbers on botstats command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-06-30 07:34:49 +01:00
729e0b482b update number formatter
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-06-30 07:34:03 +01:00
5aa56c2955 rename feature command
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 5s
2024-06-30 05:16:00 +01:00
ee6456e4d8 add feature toggling
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Has been cancelled
2024-06-30 05:15:37 +01:00
93350f1506 remove useless config option
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-06-30 04:16:49 +01:00
702aead53a cleanup dev mode
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-06-30 04:13:54 +01:00
b7f2b6a3d7 fix birthday date validation
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-06-30 03:47:34 +01:00
b66114503c fix npe
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-06-30 03:39:38 +01:00
50391e5344 add name history tracking
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-06-30 03:36:00 +01:00
91ecc9882c cleanup
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-06-30 02:36:17 +01:00
06a2584e63 fix migrations
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m9s
2024-06-30 01:03:10 +01:00
29affe2f12 update birthdays to have a view command and a private command to be able to hide your birthday
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-06-30 00:35:52 +01:00
86c7afac42 many changes
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-29 22:38:53 +01:00
b0949d17e6 add skip spotify command and show song when pausing and resuming the song
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-06-29 21:36:45 +01:00
df44ae90b9 update member count command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-29 18:35:53 +01:00
4821e2a4fa add emojis to the spotify commands
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 44s
2024-06-29 16:54:39 +01:00
4cb34fbb9a add duck image command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-29 13:23:12 +01:00
d824f957fe add vote command 2024-06-29 13:23:05 +01:00
d2d898a5b8 disable spotify linking (until they accept out application)
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-29 13:08:13 +01:00
320eab34a3 Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m8s
2024-06-29 12:45:35 +01:00
433dfb4693 make linking and unlinking only viewable to the user for spotify commands 2024-06-29 12:45:16 +01:00
Lee
71158fd477 Merge pull request 'Fix NPE caused by default avatars + make MemberCountCommand message consistent' (#2) from okNick/Bat:master into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
Reviewed-on: #2
2024-06-28 20:38:26 +00:00
eb8408eb8f Fix NPE caused by default avatars + make MemberCountCommand message consistent 2024-06-28 15:32:46 -05:00
20905a7962 fix message
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 34s
2024-06-28 20:05:32 +01:00
827e1bed4f cleanup spotify
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-28 19:59:29 +01:00
f4d3752de7 fix image command category
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-28 19:53:49 +01:00
f737b7d571 merge cat, dog, and fox commands into 1
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-28 19:37:10 +01:00
dc18c9fe7a remove debug and change some command messages
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 34s
2024-06-28 19:23:37 +01:00
107ec43149 actually fix it
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 34s
2024-06-28 19:11:37 +01:00
e17c8a08c2 fix for sub commands since discord doesn't allow not showing them in dms
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 34s
2024-06-28 19:07:01 +01:00
fa205d7ff2 allow banner and avatar command to be global
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 34s
2024-06-28 19:03:02 +01:00
Lee
290c25ee43 Merge pull request 'master' (#1) from okNick/Bat:master into master
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 33s
Reviewed-on: #1
2024-06-28 17:56:18 +00:00
Lee
4fc3431213 Merge branch 'master' into master 2024-06-28 17:55:07 +00:00
ac7c94031a use name instead of ids for some logs
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 34s
2024-06-28 18:51:58 +01:00
f3e5116708 fix spotify command category
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-28 18:50:45 +01:00
087ab99b44 Merge branch 'master' into master 2024-06-28 09:26:34 +00:00
88b88633a7 fix botstats command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 44s
2024-06-28 04:05:41 +01:00
d77c937818 spotify is v silly!
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 40s
2024-06-28 04:04:50 +01:00
bf554933cc pls
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-28 04:01:30 +01:00
6af78c7da1 pls
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-28 04:00:19 +01:00
31e81363ac pls
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-28 03:56:34 +01:00
e892dade1c add log for token refresh in spotify
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-06-28 03:52:29 +01:00
eccd673db8 spotify debug
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-28 03:50:38 +01:00
7f58a84acf Merge branch 'master' into master 2024-06-28 02:27:31 +00:00
3af151d89a Revert quick fix 2024-06-27 21:24:55 -05:00
d088e2bf95 Add category to ChannelCommand 2024-06-27 21:15:27 -05:00
53b84a884c missing code param err
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-28 03:13:54 +01:00
d54d2ffe0d wording 2024-06-27 21:11:37 -05:00
bf6bd8080d add configurable redirect uri
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-28 03:05:36 +01:00
409e71a0aa Add FoxCommand 2024-06-27 21:04:21 -05:00
2dc04394fe cleanup 2024-06-28 03:01:41 +01:00
fa10cf2019 add spotify feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m10s
2024-06-28 03:01:21 +01:00
9a1d011ff2 Add ChannelCommand with topic sub commands 2024-06-27 20:41:38 -05:00
bd3774fab0 Remove ServerIconCommand 2024-06-27 19:59:59 -05:00
34d92d7bf8 Ember -> Bat 2024-06-27 19:58:09 -05:00
886755cd40 Add BannerCommand and add guild + user sub commands to AvatarCommand 2024-06-27 19:57:42 -05:00
306edf7017 Add MemberCountCommand 2024-06-27 19:25:33 -05:00
b34b628613 Add ServerIconCommand 2024-06-27 19:22:17 -05:00
5c7a067f7a use expiring map for guild and users
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 23:51:40 +01:00
63fbe27754 check if the guild is valid before getting it
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-27 21:34:31 +01:00
f0cc859585 oops! actually save the AFK status
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 45s
2024-06-27 21:30:09 +01:00
e05e08c291 fix bot not loading when admin guild is null
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 21:25:06 +01:00
ff5b83f531 fix help command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 42s
2024-06-27 21:21:56 +01:00
deb656086a fix admin cmds
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-27 21:14:45 +01:00
a7dc517a36 fix premiumadmin command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-06-27 21:12:31 +01:00
73e4b58695 add premium to guilds and re-enable the global commands that are supposed to be global
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-06-27 21:05:54 +01:00
2c6dcc08cd cleanup missing permission message and check if the user has permission to give the role when adding a new auto role
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-27 19:48:15 +01:00
f062fa21c3 cleanup commands
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-06-27 19:36:52 +01:00
3d39fd9784 fix auto role list title
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 44s
2024-06-27 17:40:21 +01:00
2c5c1ecc41 cleanup home command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 17:38:28 +01:00
406d6b7164 cleanup and acc country flag to ss me and user command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 16:30:47 +01:00
50b921c66d make the help command better
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 16:24:08 +01:00
a7c3e2d745 impl basic help command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 16:01:27 +01:00
175c8eba9f add cat and dog command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 14:45:08 +01:00
576f7b156d add afk feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 14:29:48 +01:00
0fc5e6514a add command execution log
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-06-27 13:36:03 +01:00
ecd688310f add fetch time for scoresaber me and user command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 39s
2024-06-27 13:33:38 +01:00
001ece7899 update botstats command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 35s
2024-06-27 13:25:12 +01:00
4a7e7de6a2 add rate limit check for scoresaber api
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 13:13:37 +01:00
b50e30ffc5 add invite command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-06-27 13:11:58 +01:00
84dfe5a119 cleanup
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 34s
2024-06-27 13:02:55 +01:00
b36bdae5de add guild, user and scoresaber account caching
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 13:00:45 +01:00
d5ee54d011 don't use embed fields for botstats
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-27 11:45:06 +01:00
99570d6f86 add botstats command
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m12s
2024-06-27 11:28:38 +01:00
6119da81bf in-mem cache
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-26 21:29:31 +01:00
52e5d50782 oops
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
2024-06-26 20:42:47 +01:00
455c3f0bf1 fix a bug
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 17s
2024-06-26 20:41:31 +01:00
4d891e1825 remove caching until i can find a good solution
Some checks failed
Deploy to Dokku / docker (ubuntu-latest) (push) Failing after 17s
2024-06-26 16:17:57 +01:00
805da78cad fix caching!
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 37s
2024-06-26 14:15:05 +01:00
3fc26583f2 re-add caching (featuring Redis!!!!!)
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 38s
2024-06-26 14:05:51 +01:00
b3a5a4c02e fix wording 2024-06-26 13:13:27 +01:00
256 changed files with 13907 additions and 2422 deletions

View File

@ -16,4 +16,4 @@
# under the License. # under the License.
wrapperVersion=3.3.2 wrapperVersion=3.3.2
distributionType=only-script distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

View File

@ -1,5 +1,5 @@
# Stage 1: Build the application # Stage 1: Build the application
FROM maven:3.9.6-eclipse-temurin-17-alpine AS builder FROM maven:3.9.9-eclipse-temurin-17-alpine AS builder
# Set the working directory # Set the working directory
WORKDIR /home/container WORKDIR /home/container
@ -11,7 +11,7 @@ COPY . .
RUN mvn package -q -Dmaven.test.skip -DskipTests -T2C RUN mvn package -q -Dmaven.test.skip -DskipTests -T2C
# Stage 2: Create the final lightweight image # Stage 2: Create the final lightweight image
FROM eclipse-temurin:17.0.11_9-jre-focal FROM eclipse-temurin:17.0.13_11-jre-focal
# Set the working directory # Set the working directory
WORKDIR /home/container WORKDIR /home/container
@ -19,5 +19,9 @@ WORKDIR /home/container
# Copy the built jar file from the builder stage # Copy the built jar file from the builder stage
COPY --from=builder /home/container/target/Bat.jar . COPY --from=builder /home/container/target/Bat.jar .
# Export the port
ENV PORT=8080
EXPOSE $PORT
# Run the jar file # Run the jar file
CMD java -jar Bat.jar -Djava.awt.headless=true CMD java -jar Bat.jar -Djava.awt.headless=true

66
pom.xml
View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.1</version> <version>3.4.1</version>
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
@ -41,7 +41,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version> <version>3.6.0</version>
<configuration> <configuration>
<createDependencyReducedPom>false</createDependencyReducedPom> <createDependencyReducedPom>false</createDependencyReducedPom>
</configuration> </configuration>
@ -70,6 +70,12 @@
</plugins> </plugins>
</build> </build>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies> <dependencies>
<!-- Spring --> <!-- Spring -->
@ -86,40 +92,68 @@
<artifactId>spring-boot-starter-websocket</artifactId> <artifactId>spring-boot-starter-websocket</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>io.sentry</groupId>
<artifactId>spring-boot-starter-cache</artifactId> <artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.16.0</version>
</dependency> </dependency>
<!-- Redis for caching -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- Libraries --> <!-- Libraries -->
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.32</version> <version>1.18.34</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- Dependencies -->
<dependency> <dependency>
<groupId>net.dv8tion</groupId> <groupId>io.github.freya022</groupId>
<artifactId>JDA</artifactId> <artifactId>JDA</artifactId>
<version>5.0.0-beta.24</version> <version>2ed819ad15</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.10.1</version> <version>2.11.0</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents.client5</groupId> <groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId> <artifactId>httpclient5</artifactId>
<version>5.3.1</version> <version>5.4.1</version>
<scope>compile</scope>
</dependency> </dependency>
<!-- Test Dependencies -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>net.jodah</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>expiringmap</artifactId>
<scope>test</scope> <version>0.5.11</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>com.github.Steppschuh</groupId>
<artifactId>Java-Markdown-Generator</artifactId>
<version>1.3.2</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

34
privacy-policy.txt Normal file
View File

@ -0,0 +1,34 @@
Privacy Policy
1. Introduction
This Privacy Policy explains how Bat ("we", "us", "our") collects, uses, and protects your information when you use our Discord bot (the "Service").
2. Information We Collect
User Data: When you interact with our bot, we may collect your Discord user ID, server ID, and any messages or commands you send to the bot.
Usage Data: We may collect data on how you interact with the bot, such as commands used and features accessed.
3. How We Use Your Information
Service Operation: To provide, maintain, and improve the Service.
Communication: To respond to your inquiries and provide customer support.
Analytics: To analyze usage trends and improve the Service.
4. Data Sharing
We do not sell or rent your information to third parties. We may share your information with third-party service providers who assist us in operating the Service, under confidentiality agreements.
5. Data Security
We implement appropriate technical and organizational measures to protect your information from unauthorized access, loss, or misuse.
6. Data Retention
We retain your information for as long as necessary to fulfill the purposes outlined in this Privacy Policy, unless a longer retention period is required or permitted by law.
7. Your Rights
You have the right to access, correct, or delete your personal information. To exercise these rights, please contact us at bat@fascinated.cc.
8. Changes to This Privacy Policy
We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on our https://discord.gg/yjj2U3ctEG.
9. Contact Us
If you have any questions about this Privacy Policy, please contact us at bat@fascinated.cc.

3
renovate.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": ["config:recommended", ":dependencyDashboard"]
}

View File

@ -1,5 +1,8 @@
package cc.fascinated.bat; package cc.fascinated.bat;
import cc.fascinated.bat.config.Config;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.service.EventService;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import lombok.NonNull; import lombok.NonNull;
@ -7,7 +10,7 @@ import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import java.io.File; import java.io.File;
@ -15,26 +18,39 @@ import java.nio.file.Files;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.Objects; import java.util.Objects;
@EnableCaching @EnableScheduling @EnableScheduling
@SpringBootApplication @SpringBootApplication
@Log4j2(topic = "Ember") @Log4j2(topic = "Bat")
public class BatApplication { public class BatApplication {
public static Gson GSON = new GsonBuilder().create(); public static Gson GSON = new GsonBuilder().create();
@SneakyThrows @SneakyThrows
public static void main(@NonNull String[] args) { public static void main(@NonNull String[] args) {
// Handle loading of our configuration file // Handle loading of our configuration file
File config = new File("application.yml"); File config = new File("application.yml");
if (!config.exists()) { // Saving the default config if it doesn't exist locally if (!config.exists()) { // Saving the default config if it doesn't exist locally
Files.copy(Objects.requireNonNull(BatApplication.class.getResourceAsStream("/application.yml")), config.toPath(), StandardCopyOption.REPLACE_EXISTING); Files.copy(Objects.requireNonNull(BatApplication.class.getResourceAsStream("/application.yml")), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
log.info("Saved the default configuration to '{}', please re-launch the application", // Log the default config being saved log.info("Saved the default configuration to '{}', please re-launch the application", // Log the default config being saved
config.getAbsolutePath() config.getAbsolutePath()
); );
return; return;
} }
log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config
// Start the app // Start the application
SpringApplication.run(BatApplication.class, args); SpringApplication app = new SpringApplication(BatApplication.class);
} app.setRegisterShutdownHook(false); // Disable the default shutdown hook
ConfigurableApplicationContext context = app.run(args);
// Register the shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("Shutting down...");
for (EventListener listener : EventService.LISTENERS) {
listener.onShutdown();
}
context.close();
}));
log.info("APP IS RUNNING IN %s MODE!!!!!!!!!".formatted(Config.isProduction() ? "PRODUCTION" : "DEVELOPMENT"));
}
} }

View File

@ -0,0 +1,12 @@
package cc.fascinated.bat;
/**
* @author Fascinated (fascinated7)
*/
public class Consts {
public static final String INVITE_URL = "https://dsc.gg/batbot";
public static final String SUPPORT_INVITE_URL = "https://discord.gg/invite/yjj2U3ctEG";
public static final String BOT_OWNER = "474221560031608833";
public static final String PRIVACY_POLICY_URL = "https://git.fascinated.cc/Fascinated/Bat/raw/branch/master/privacy-policy.txt";
public static final String TERMS_OF_SERVICE_URL = "https://git.fascinated.cc/Fascinated/Bat/raw/branch/master/terms-of-service.txt";
}

View File

@ -0,0 +1,35 @@
package cc.fascinated.bat;
import cc.fascinated.bat.service.DiscordService;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.emoji.Emoji;
/**
* @author Fascinated (fascinated7)
*/
@Log4j2(topic = "Emojis")
public class Emojis {
public static final Emoji SPOTIFY_EMOJI;
public static final Emoji CHECK_MARK_EMOJI;
public static final Emoji CROSS_MARK_EMOJI;
public static final Emoji SAD_FACE_EMOJI;
public static final Emoji PAUSE_EMOJI;
public static final Emoji PLAY_EMOJI;
public static final Emoji SKIP_EMOJI;
public static final Emoji HOME_EMOJI;
static {
log.info("Loading emojis...");
JDA jda = DiscordService.JDA;
SPOTIFY_EMOJI = jda.getEmojiById("1256629771975266479");
CHECK_MARK_EMOJI = jda.getEmojiById("1258922852669853817");
CROSS_MARK_EMOJI = jda.getEmojiById("1256634487429922897");
SAD_FACE_EMOJI = jda.getEmojiById("1256636078258131055");
PAUSE_EMOJI = Emoji.fromUnicode("");
PLAY_EMOJI = Emoji.fromUnicode("");
SKIP_EMOJI = Emoji.fromUnicode("");
HOME_EMOJI = Emoji.fromUnicode("🏠");
log.info("Loaded emojis!");
}
}

View File

@ -0,0 +1,21 @@
package cc.fascinated.bat.afk;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.afk.command.AfkCommand;
import cc.fascinated.bat.service.CommandService;
import lombok.NonNull;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class AfkFeature extends Feature {
public AfkFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("AFK", FeatureProfile.FeatureState.DISABLED, true);
registerCommand(commandService, context.getBean(AfkCommand.class));
}
}

View File

@ -0,0 +1,35 @@
package cc.fascinated.bat.afk;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.afk.profile.AfkProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class AfkMentionListener implements EventListener {
@Override
public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
Message message = event.getMessage();
List<User> mentionedUsers = message.getMentions().getUsers();
if (mentionedUsers.isEmpty()) {
return;
}
User mentionedUser = mentionedUsers.get(0);
AfkProfile profile = guild.getProfile(AfkProfile.class);
if (!profile.isAfk(mentionedUser.getId())) {
return;
}
event.getMessage().reply("%s is currently AFK: %s".formatted(mentionedUser.getAsMention(), profile.getAfkReason(mentionedUser.getId()))).queue();
}
}

View File

@ -0,0 +1,25 @@
package cc.fascinated.bat.afk;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.afk.profile.AfkProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class AfkReturnListener implements EventListener {
@Override
public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
AfkProfile profile = guild.getProfile(AfkProfile.class);
if (!profile.isAfk(user.getId())) {
return;
}
profile.removeAfkUser(guild, user.getId());
event.getMessage().reply("Welcome back, %s! You are no longer AFK.".formatted(user.getDiscordUser().getAsMention())).queue();
}
}

View File

@ -0,0 +1,57 @@
package cc.fascinated.bat.afk.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.MemberUtils;
import cc.fascinated.bat.afk.profile.AfkProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "afk", description = "Sets your AFK status", category = Category.GENERAL)
public class AfkCommand extends BatCommand {
public AfkCommand() {
super.addOptions(new OptionData(OptionType.STRING, "reason", "The reason for being AFK", false));
}
/**
* Fired when this command is executed.
*
* @param guild the guild the command was executed in, if any
* @param user the user who executed the command
* @param channel the channel the command was executed in
* @param member the member who executed the command, null if not a guild
* @param commandMessage
* @param arguments
* @param event the event that invoked this command
*/
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AfkProfile profile = guild.getProfile(AfkProfile.class);
String reason = null;
OptionMapping reasonOption = event.getOption("reason");
if (reasonOption != null) {
reason = reasonOption.getAsString();
}
profile.addAfkUser(guild, member.getId(), reason);
event.reply("You are now AFK: %s%s".formatted(
profile.getAfkReason(member.getId()),
MemberUtils.hasPermissionToEdit(guild, user) ? "" :
"\n\n*I do not have enough permissions to edit your user, and therefore cannot update your nickname*"
)).queue();
}
}

View File

@ -0,0 +1,122 @@
package cc.fascinated.bat.afk.profile;
import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.common.model.BatGuild;
import com.google.gson.Gson;
import lombok.NoArgsConstructor;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import org.bson.Document;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Component
@NoArgsConstructor
public class AfkProfile extends Serializable {
private static final String DEFAULT_REASON = "Away";
/**
* The AFK users in this guild
*/
private Map<String, String> afkUsers;
/**
* Adds a user to the AFK list
*
* @param guild the guild enable afk mode for
* @param userId the user ID to add
* @param reason the reason for being AFK
*/
public void addAfkUser(BatGuild guild, String userId, String reason) {
if (afkUsers == null) {
afkUsers = new HashMap<>();
}
afkUsers.put(userId, reason == null ? DEFAULT_REASON : reason);
Guild discordGuild = guild.getDiscordGuild();
Member member = discordGuild.getMemberById(userId);
if (member == null) {
return;
}
try {
member.modifyNickname("[AFK] " + member.getEffectiveName()).queue();
} catch (Exception ignored) {
}
}
/**
* Removes a user from the AFK list
*
* @param guild the guild to remove the user from
* @param userId the user ID to remove
*/
public void removeAfkUser(BatGuild guild, String userId) {
if (afkUsers == null) {
afkUsers = new HashMap<>();
}
afkUsers.remove(userId);
Guild discordGuild = guild.getDiscordGuild();
Member member = discordGuild.getMemberById(userId);
if (member == null) {
return;
}
try {
member.modifyNickname(member.getEffectiveName().replace("[AFK] ", "")).queue();
} catch (Exception ignored) {
}
}
/**
* Gets the reason for being AFK
*
* @param userId the user ID to get the reason for
* @return the reason for being AFK
*/
public String getAfkReason(String userId) {
if (afkUsers == null) {
afkUsers = new HashMap<>();
}
return afkUsers.get(userId);
}
/**
* Checks if a user is AFK
*
* @param userId the user ID to check
* @return if the user is AFK
*/
public boolean isAfk(String userId) {
if (afkUsers == null) {
afkUsers = new HashMap<>();
}
return afkUsers.containsKey(userId);
}
@Override
public void reset() {
afkUsers = new HashMap<>();
}
@Override
public void load(Document document, Gson gson) {
afkUsers = new HashMap<>();
for (String key : document.keySet()) {
afkUsers.put(key, document.getString(key));
}
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
for (String key : afkUsers.keySet()) {
document.put(key, afkUsers.get(key));
}
return document;
}
}

View File

@ -1,7 +1,8 @@
package cc.fascinated.bat.features.autorole; package cc.fascinated.bat.autorole;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.features.autorole.command.AutoRoleCommand; import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.autorole.command.AutoRoleCommand;
import cc.fascinated.bat.service.CommandService; import cc.fascinated.bat.service.CommandService;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -15,8 +16,8 @@ import org.springframework.stereotype.Component;
public class AutoRoleFeature extends Feature { public class AutoRoleFeature extends Feature {
@Autowired @Autowired
public AutoRoleFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) { public AutoRoleFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("AutoRole", Category.SERVER); super("Auto Role", FeatureProfile.FeatureState.DISABLED, true);
commandService.registerCommand(context.getBean(AutoRoleCommand.class)); registerCommand(commandService, context.getBean(AutoRoleCommand.class));
} }
} }

View File

@ -1,13 +1,15 @@
package cc.fascinated.bat.features.autorole; package cc.fascinated.bat.autorole;
import cc.fascinated.bat.event.EventListener; import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile; import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.ArrayList; import java.util.ArrayList;
@ -17,10 +19,22 @@ import java.util.List;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@Log4j2 @Log4j2(topic = "AutoRole Listener")
public class AutoRoleListener implements EventListener { public class AutoRoleListener implements EventListener {
private final FeatureService featureService;
@Autowired
public AutoRoleListener(@NonNull FeatureService featureService) {
this.featureService = featureService;
}
@Override @Override
public void onGuildMemberJoin(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberJoinEvent event) { public void onGuildMemberJoin(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberJoinEvent event) {
AutoRoleFeature autoRoleFeature = featureService.getFeature(AutoRoleFeature.class);
if (!guild.getFeatureProfile().isFeatureEnabled(autoRoleFeature)) { // Check if the feature is enabled
return;
}
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
if (profile.getRoles().isEmpty()) { if (profile.getRoles().isEmpty()) {
return; return;
@ -35,7 +49,11 @@ public class AutoRoleListener implements EventListener {
event.getGuild().addRoleToMember(event.getMember(), role).queue(); event.getGuild().addRoleToMember(event.getMember(), role).queue();
} }
toRemove.forEach(profile::removeRole); toRemove.forEach(profile::removeRole);
log.info("Gave user \"{}\" {} auto roles{}", user.getId(), profile.getRoles().size(), toRemove.isEmpty() ? "" log.info("Gave user \"{}\" {} auto roles in guild \"{}\"{}",
: " and removed %s invalid roles".formatted(toRemove.size())); user.getId(),
profile.getRoles().size(),
guild.getName(),
toRemove.isEmpty() ? "" : " and removed %s invalid roles from the profile".formatted(toRemove.size())
);
} }
} }

View File

@ -0,0 +1,80 @@
package cc.fascinated.bat.autorole.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.RoleUtils;
import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("autoroles:add.sub")
@CommandInfo(name = "add", description = "Adds a role to the auto roles list")
public class AddSubCommand extends BatCommand {
@Autowired
public AddSubCommand() {
super.addOptions(new OptionData(OptionType.ROLE, "role", "The role to add", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
// Check if the guild has reached the maximum auto roles count
int maxRoleSlots = AutoRoleProfile.getMaxRoleSlots(guild);
if (profile.getRoleSlotsInUse() >= maxRoleSlots) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The guild can only have a maximum of %d auto roles"
.formatted(maxRoleSlots))
.build()).queue();
return;
}
OptionMapping option = event.getOption("role");
assert option != null;
Role role = option.getAsRole();
// Check if the role is already in the auto roles list
if (profile.hasRole(role.getId())) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The role %s is already in the auto roles list".formatted(role.getAsMention()))
.build()).queue();
return;
}
// Check if the bot has permission to give the role
if (!RoleUtils.hasPermissionToGiveRole(guild, guild.getDiscordGuild().getSelfMember(), role)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("I do not have permission to give the role %s".formatted(role.getAsMention()))
.build()).queue();
return;
}
// Check if the role is higher than the user adding the role
if (!RoleUtils.hasPermissionToGiveRole(guild, member, role)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You cannot add a role that is higher than you")
.build()).queue();
return;
}
// Add the role to the auto roles list
profile.addRole(role.getId());
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You have added %s to the auto roles list".formatted(role.getAsMention()))
.build()).queue();
}
}

View File

@ -0,0 +1,31 @@
package cc.fascinated.bat.autorole.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("autoroles.command")
@CommandInfo(
name = "autorole",
description = "Set up the automatic role system for members on join",
requiredPermissions = Permission.MANAGE_SERVER,
category = Category.SERVER
)
public class AutoRoleCommand extends BatCommand {
public AutoRoleCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(ListSubCommand.class),
context.getBean(AddSubCommand.class),
context.getBean(RemoveSubCommand.class),
context.getBean(ClearSubCommand.class),
context.getBean(SyncSubCommand.class)
);
}
}

View File

@ -0,0 +1,31 @@
package cc.fascinated.bat.autorole.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("autoroles:clear.sub")
@CommandInfo(name = "clear", description = "Clears all auto roles")
public class ClearSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
profile.reset();
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully cleared all auto roles")
.build()).queue();
}
}

View File

@ -1,13 +1,15 @@
package cc.fascinated.bat.features.autorole.command; package cc.fascinated.bat.autorole.command;
import cc.fascinated.bat.command.BatSubCommand; import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile; import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -16,17 +18,13 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component("autoroles:list.sub") @Component("autoroles:list.sub")
public class ListSubCommand extends BatSubCommand { @CommandInfo(name = "list", description = "Lists all auto roles")
public class ListSubCommand extends BatCommand {
public ListSubCommand() {
super("list", "Lists all auto roles");
}
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
if (profile.getRoles().isEmpty()) { if (profile.getRoles().isEmpty()) {
interaction.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("There are no auto roles set") .setDescription("There are no auto roles set")
.build()).queue(); .build()).queue();
return; return;
@ -35,15 +33,15 @@ public class ListSubCommand extends BatSubCommand {
StringBuilder roles = new StringBuilder(); StringBuilder roles = new StringBuilder();
roles.append("There are %d/%d auto roles\n".formatted( roles.append("There are %d/%d auto roles\n".formatted(
profile.getRoleSlotsInUse(), profile.getRoleSlotsInUse(),
profile.getMaxRoles() AutoRoleProfile.getMaxRoleSlots(guild)
)); ));
for (int i = 0; i < profile.getRoles().size(); i++) { for (int i = 0; i < profile.getRoles().size(); i++) {
roles.append("%d. %s\n".formatted(i + 1, profile.getRoles().get(i).getAsMention())); roles.append("%d. %s\n".formatted(i + 1, profile.getRoles().get(i).getAsMention()));
} }
EmbedBuilder embed = EmbedUtils.genericEmbed(); EmbedBuilder embed = EmbedUtils.genericEmbed();
embed.setTitle("Auto Role List"); embed.setAuthor("Auto Role List");
embed.setDescription(roles.toString()); embed.setDescription(roles.toString());
interaction.replyEmbeds(embed.build()).queue(); event.replyEmbeds(embed.build()).queue();
} }
} }

View File

@ -1,18 +1,20 @@
package cc.fascinated.bat.features.autorole.command; package cc.fascinated.bat.autorole.command;
import cc.fascinated.bat.command.BatSubCommand; import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile; import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -20,38 +22,29 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component("autoroles:remove.sub") @Component("autoroles:remove.sub")
public class RemoveSubCommand extends BatSubCommand { @CommandInfo(name = "remove", description = "Removes a role from the auto roles list")
private final GuildService guildService; public class RemoveSubCommand extends BatCommand {
@Autowired @Autowired
public RemoveSubCommand(GuildService guildService) { public RemoveSubCommand() {
super("remove", "Removes a role from the auto roles list"); super.addOptions(new OptionData(OptionType.ROLE, "role", "The role to remove", true));
super.addOption(OptionType.ROLE, "role", "The role to remove", true);
this.guildService = guildService;
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
OptionMapping option = interaction.getOption("role"); OptionMapping option = event.getOption("role");
if (option == null) { assert option != null;
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Please provide a role to remove")
.build()).queue();
return;
}
Role role = option.getAsRole(); Role role = option.getAsRole();
if (!profile.hasRole(role.getId())) { if (!profile.hasRole(role.getId())) {
interaction.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The role %s is not in the auto roles list".formatted(role.getAsMention())) .setDescription("The role %s is not in the auto roles list".formatted(role.getAsMention()))
.build()).queue(); .build()).queue();
return; return;
} }
profile.removeRole(role.getId()); profile.removeRole(role.getId());
guildService.saveGuild(guild); event.replyEmbeds(EmbedUtils.successEmbed()
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully removed the role %s from the auto roles list".formatted(role.getAsMention())) .setDescription("Successfully removed the role %s from the auto roles list".formatted(role.getAsMention()))
.build()).queue(); .build()).queue();
} }

View File

@ -0,0 +1,113 @@
package cc.fascinated.bat.autorole.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Log4j2(topic = "AutoRole Sync SubCommand")
@Component("autoroles:sync.sub")
@CommandInfo(name = "sync", description = "Gives everyone their missing auto roles")
public class SyncSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
if (profile.getRoles().isEmpty()) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("There are no auto roles set")
.build()).queue();
return;
}
Guild discordGuild = guild.getDiscordGuild();
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("Finding members that are missing auto roles...")
.build())
.queue(message -> {
log.info("Finding members that are missing auto roles in guild \"{}\"", discordGuild.getName());
List<Member> members = discordGuild.loadMembers().get();
Map<Role, Integer> rolesGiven = new HashMap<>();
// Find members that are missing the auto roles
members.removeIf(foundMember -> {
if (foundMember.getUser().isBot() || foundMember.getId().equals(discordGuild.getSelfMember().getId())) {
return true;
}
return new HashSet<>(foundMember.getRoles()).containsAll(profile.getRoles());
});
log.info("Found {} members that are missing auto roles in guild \"{}\"", members.size(), discordGuild.getName());
// No members were missing roles
if (members.isEmpty()) {
message.editOriginalEmbeds(EmbedUtils.successEmbed()
.setDescription("There are no members missing auto roles")
.build()).queue();
return;
}
int finished = 0;
for (Member foundMember : members) {
// Check if the user doesn't have the role, so we can
// show the incremented count when we're done
for (Role role : profile.getRoles()) {
if (foundMember.getRoles().contains(role)) {
continue;
}
rolesGiven.put(role, rolesGiven.getOrDefault(role, 0) + 1);
}
// Give the user the roles
discordGuild.modifyMemberRoles(foundMember, profile.getRoles(), null).queue();
finished++;
// Update the message every 10 members
if (finished % 10 == 0 || finished == 1) {
message.editOriginalEmbeds(EmbedUtils.genericEmbed()
.setDescription("""
Giving auto roles...
Completed: `%s`/`%s`
""".formatted(finished, members.size()))
.build()).queue();
}
// We're finished giving all the roles
if (finished == members.size()) {
DescriptionBuilder description = new DescriptionBuilder(
"Successfully gave auto roles to `%s` member%s".formatted(
members.size(),
members.size() == 1 ? "" : "s"
));
description.emptyLine();
description.appendLine("**Auto Roles Given:**", false);
for (Map.Entry<Role, Integer> entry : rolesGiven.entrySet()) {
description.appendLine("%s: %s member%s".formatted(
entry.getKey().getAsMention(),
entry.getValue(),
entry.getValue() == 1 ? "" : "s"
), true);
}
message.editOriginalEmbeds(EmbedUtils.successEmbed().setDescription(description.build()).build()).queue();
}
}
});
}
}

View File

@ -1,10 +1,14 @@
package cc.fascinated.bat.features.autorole.profile; package cc.fascinated.bat.autorole.profile;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.service.DiscordService; import cc.fascinated.bat.service.DiscordService;
import com.google.gson.Gson;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import org.bson.Document;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -12,9 +16,12 @@ import java.util.List;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Setter @Getter @Setter
public class AutoRoleProfile extends Profile { @Getter
@NoArgsConstructor
public class AutoRoleProfile extends Serializable {
private static final int DEFAULT_MAX_ROLES = 10; private static final int DEFAULT_MAX_ROLES = 10;
private static final int PREMIUM_MAX_ROLES = 25;
/** /**
* The roles to assign when a user joins * The roles to assign when a user joins
@ -22,21 +29,16 @@ public class AutoRoleProfile extends Profile {
private List<String> roleIds; private List<String> roleIds;
/** /**
* The maximum amount of roles that can be set * Gets the maximum amount of roles that can be set in the guild
*/
private int maxRoles = 10;
public AutoRoleProfile() {
super("auto-role");
}
/**
* Gets the amount of role slots left
* *
* @return the amount * @param guild the guild to check
* @return the amount of role slots
*/ */
public int getRoleSlotsLeft() { public static int getMaxRoleSlots(BatGuild guild) {
return maxRoles - getRoles().size(); if (guild.getPremiumProfile().hasPremium()) {
return PREMIUM_MAX_ROLES;
}
return DEFAULT_MAX_ROLES;
} }
/** /**
@ -107,6 +109,17 @@ public class AutoRoleProfile extends Profile {
@Override @Override
public void reset() { public void reset() {
roleIds.clear(); roleIds.clear();
maxRoles = DEFAULT_MAX_ROLES; }
@Override
public void load(Document document, Gson gson) {
roleIds = document.getList("roleIds", String.class);
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("roleIds", roleIds);
return document;
} }
} }

View File

@ -0,0 +1,53 @@
package cc.fascinated.bat.base;
import cc.fascinated.bat.base.commands.fun.*;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.base.commands.botadmin.BotAdminCommand;
import cc.fascinated.bat.base.commands.fun.image.ImageCommand;
import cc.fascinated.bat.base.commands.general.*;
import cc.fascinated.bat.base.commands.general.avatar.AvatarCommand;
import cc.fascinated.bat.base.commands.general.banner.BannerCommand;
import cc.fascinated.bat.base.commands.server.MemberCountCommand;
import cc.fascinated.bat.base.commands.server.PremiumCommand;
import cc.fascinated.bat.base.commands.server.channel.ChannelCommand;
import cc.fascinated.bat.base.commands.server.feature.FeatureCommand;
import cc.fascinated.bat.base.commands.utility.PastebinCommand;
import cc.fascinated.bat.base.commands.utility.lookup.LookupCommand;
import cc.fascinated.bat.service.CommandService;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class BaseFeature extends Feature {
@Autowired
public BaseFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Base", FeatureProfile.FeatureState.ENABLED, false);
super.registerCommand(commandService, context.getBean(PremiumCommand.class));
super.registerCommand(commandService, context.getBean(BotAdminCommand.class));
super.registerCommand(commandService, context.getBean(MemberCountCommand.class));
super.registerCommand(commandService, context.getBean(ChannelCommand.class));
super.registerCommand(commandService, context.getBean(VoteCommand.class));
super.registerCommand(commandService, context.getBean(PingCommand.class));
super.registerCommand(commandService, context.getBean(InviteCommand.class));
super.registerCommand(commandService, context.getBean(HelpCommand.class));
super.registerCommand(commandService, context.getBean(BotStatsCommand.class));
super.registerCommand(commandService, context.getBean(BannerCommand.class));
super.registerCommand(commandService, context.getBean(AvatarCommand.class));
super.registerCommand(commandService, context.getBean(ImageCommand.class));
super.registerCommand(commandService, context.getBean(FeatureCommand.class));
super.registerCommand(commandService, context.getBean(EightBallCommand.class));
super.registerCommand(commandService, context.getBean(LookupCommand.class));
super.registerCommand(commandService, context.getBean(PPSizeCommand.class));
super.registerCommand(commandService, context.getBean(PastebinCommand.class));
super.registerCommand(commandService, context.getBean(CoinFlipCommand.class));
super.registerCommand(commandService, context.getBean(HowGayCommand.class));
super.registerCommand(commandService, context.getBean(NerdDetectorCommand.class));
}
}

View File

@ -0,0 +1,25 @@
package cc.fascinated.bat.base.commands.botadmin;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.base.commands.botadmin.premium.PremiumRemoveSubCommand;
import cc.fascinated.bat.base.commands.botadmin.premium.PremiumSetSubCommand;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "bot-admin", description = "Bot admin commands", botOwnerOnly = true)
public class BotAdminCommand extends BatCommand {
@Autowired
public BotAdminCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(PremiumSetSubCommand.class),
context.getBean(PremiumRemoveSubCommand.class)
);
}
}

View File

@ -0,0 +1,56 @@
package cc.fascinated.bat.base.commands.botadmin.premium;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "premium-remove", description = "Remove premium from a guild")
public class PremiumRemoveSubCommand extends BatCommand {
private final GuildService guildService;
@Autowired
public PremiumRemoveSubCommand(GuildService guildService) {
this.guildService = guildService;
super.addOptions(new OptionData(OptionType.STRING, "guild", "The guild id to set as premium", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping guildOption = event.getOption("guild");
if (guildOption == null) {
event.reply("Please provide a guild id").queue();
return;
}
String guildId = guildOption.getAsString();
BatGuild targetGuild = guildService.getGuild(guildId);
if (targetGuild == null) {
event.reply("The guild with the id %s does not exist".formatted(guildId)).queue();
return;
}
PremiumProfile premium = targetGuild.getPremiumProfile();
if (!premium.hasPremium()) {
event.reply("The guild does not have premium").queue();
return;
}
premium.removePremium();
event.reply("The guild **%s** has had its premium removed".formatted(targetGuild.getName())).queue();
}
}

View File

@ -0,0 +1,69 @@
package cc.fascinated.bat.base.commands.botadmin.premium;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "premium-set", description = "Adds premium to a guild")
public class PremiumSetSubCommand extends BatCommand {
private final GuildService guildService;
@Autowired
public PremiumSetSubCommand(@NonNull GuildService guildService) {
this.guildService = guildService;
super.addOptions(
new OptionData(OptionType.STRING, "guild", "The guild id to set as premium", true),
new OptionData(OptionType.BOOLEAN, "infinite", "Whether the premium length should be infinite", true)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping guildOption = event.getOption("guild");
if (guildOption == null) {
event.reply("Please provide a guild id").queue();
return;
}
String guildId = guildOption.getAsString();
OptionMapping infiniteOption = event.getOption("infinite");
if (infiniteOption == null) {
event.reply("Please provide whether the premium length should be infinite").queue();
return;
}
boolean infinite = infiniteOption.getAsBoolean();
BatGuild targetGuild = guildService.getGuild(guildId);
if (targetGuild == null) {
event.reply("The guild with the id %s does not exist".formatted(guildId)).queue();
return;
}
PremiumProfile premium = targetGuild.getPremiumProfile();
if (!infinite) {
premium.addTime();
} else {
premium.addInfiniteTime();
}
if (!infinite) {
event.reply("The guild **%s** has been set as premium until <t:%s>".formatted(targetGuild.getName(), premium.getExpiresAt().toInstant().toEpochMilli() / 1000)).queue();
} else {
event.reply("The guild **%s** has been set as premium indefinitely".formatted(targetGuild.getName())).queue();
}
}
}

View File

@ -0,0 +1,40 @@
package cc.fascinated.bat.base.commands.fun;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
import java.util.Random;
/**
* @author Fascinated (fascinated7)
*/
@CommandInfo(
name = "coinflip",
description = "Flips a coin",
userInstall = true
)
@Component
public class CoinFlipCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member,
Message commandMessage, String[] arguments, SlashCommandInteraction event) {
event.reply("%s, you flipped a coin and got **%s**!".formatted(user.getDiscordUser().getAsMention(), flip())).queue();
}
/**
* Flips a coin
*
* @return The result of the coin flip
*/
public String flip() {
return new Random().nextBoolean() ? "heads" : "tails";
}
}

View File

@ -0,0 +1,72 @@
package cc.fascinated.bat.base.commands.fun;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "8ball", description = "Ask the magic 8ball a question",
guildOnly = false,
userInstall = true,
prefixAllowed = true,
category = Category.FUN
)
public class EightBallCommand extends BatCommand {
private final String[] responses = new String[]{
"It is certain",
"It is decidedly so",
"Without a doubt",
"Yes, definitely",
"You may rely on it",
"As I see it, yes",
"Most likely",
"Outlook good",
"Yes",
"Signs point to yes",
"Reply hazy, try again",
"Ask again later",
"Better not tell you now",
"Cannot predict now",
"Concentrate and ask again",
"Don't count on it",
"My reply is no",
"My sources say no",
"Outlook not so good",
"Very doubtful"
};
public EightBallCommand() {
super.addOptions(new OptionData(OptionType.STRING, "question", "The question you want to ask the 8ball", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
String question = super.getArgument("question", arguments, 0, true, event).getAsString();
if (question == null) {
super.replyEmbed(commandMessage, event, EmbedUtils.errorEmbed()
.setDescription("You need to provide a question to ask the 8ball")
);
return;
}
String response = responses[(int) (Math.random() * responses.length)];
super.replyEmbed(commandMessage, event, EmbedUtils.successEmbed()
.setDescription("You asked: `%s`\n\n:8ball: The magic 8ball says: `%s`".formatted(question, response))
);
}
}

View File

@ -0,0 +1,52 @@
package cc.fascinated.bat.base.commands.fun;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "howgay",
description = "Checks how gay someone is",
userInstall = true,
category = Category.FUN
)
public class HowGayCommand extends BatCommand {
public HowGayCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user you want to check the gayness of", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping userOption = event.getOption("user");
assert userOption != null; // This should never be null
User target = userOption.getAsUser();
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new DescriptionBuilder("How Gay?")
.appendLine("%s is %s%% gay".formatted(
target.getAsMention(),
(int) MathUtils.random(0, 100)
), false)
.build())
.build()).queue();
}
}

View File

@ -0,0 +1,52 @@
package cc.fascinated.bat.base.commands.fun;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "nerddetector",
description = "Checks how much of a nerd someone is",
userInstall = true,
category = Category.FUN
)
public class NerdDetectorCommand extends BatCommand {
public NerdDetectorCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user you want to check how much of a nerd they are", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping userOption = event.getOption("user");
assert userOption != null; // This should never be null
User target = userOption.getAsUser();
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new DescriptionBuilder("Nerd Detector")
.appendLine("%s is %s%% nerd 🤓".formatted(
target.getAsMention(),
(int) MathUtils.random(0, 100)
), false)
.build())
.build()).queue();
}
}

View File

@ -0,0 +1,60 @@
package cc.fascinated.bat.base.commands.fun;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "ppsize",
description = "Get the size of someone's pp",
userInstall = true,
category = Category.FUN
)
public class PPSizeCommand extends BatCommand {
public PPSizeCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user you want to get the pp size of", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping userOption = event.getOption("user");
assert userOption != null; // This should never be null
User target = userOption.getAsUser();
int maxSize = 12;
int size = (int) MathUtils.random(1, maxSize);
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new DescriptionBuilder("PP Size")
.appendLine("""
The size of %s's pp is %s inches%s
**8%sD**
""".formatted(
target.getAsMention(),
size,
size == maxSize ? " 😳" : "",
"=".repeat(size)
), false)
.build())
.build()).queue();
}
}

View File

@ -0,0 +1,36 @@
package cc.fascinated.bat.base.commands.fun.image;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.common.model.token.thecatapi.CatImageToken;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "cat", description = "Get a random cat image")
public class CatSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
CatImageToken[] responseEntity = WebRequest.getAsEntity("https://api.thecatapi.com/v1/images/search", CatImageToken[].class);
if (responseEntity == null || responseEntity.length == 0) {
event.reply("Failed to get a cat image!").queue();
return;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("Here's a random cat image!")
.setImage(responseEntity[0].getUrl())
.build()).queue();
}
}

View File

@ -0,0 +1,36 @@
package cc.fascinated.bat.base.commands.fun.image;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.common.model.token.dogceo.RandomImage;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "dog", description = "Get a random dog image")
public class DogSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
RandomImage responseEntity = WebRequest.getAsEntity("https://dog.ceo/api/breeds/image/random", RandomImage.class);
if (responseEntity == null) {
event.reply("Failed to get a dog image!").queue();
return;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("Here's a random dog image!")
.setImage(responseEntity.getMessage())
.build()).queue();
}
}

View File

@ -0,0 +1,36 @@
package cc.fascinated.bat.base.commands.fun.image;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.common.model.token.randomd.RandomDuck;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "duck", description = "Get a random duck image")
public class DuckSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
RandomDuck responseEntity = WebRequest.getAsEntity("https://random-d.uk/api/v2/random", RandomDuck.class);
if (responseEntity == null) {
event.reply("Failed to get a duck image!").queue();
return;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("Here's a random duck image!")
.setImage(responseEntity.getUrl())
.build()).queue();
}
}

View File

@ -0,0 +1,36 @@
package cc.fascinated.bat.base.commands.fun.image;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.common.model.token.randomfox.RandomFoxToken;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component
@CommandInfo(name = "fox", description = "Get a random fox image")
public class FoxSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
RandomFoxToken responseEntity = WebRequest.getAsEntity("https://randomfox.ca/floof/", RandomFoxToken.class);
if (responseEntity == null) {
event.reply("Failed to get a fox image!").queue();
return;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("Here's a random fox image!")
.setImage(responseEntity.getImage())
.build()).queue();
}
}

View File

@ -0,0 +1,26 @@
package cc.fascinated.bat.base.commands.fun.image;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "image", description = "View a random image", guildOnly = false, userInstall = true, category = Category.FUN)
public class ImageCommand extends BatCommand {
@Autowired
public ImageCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(CatSubCommand.class),
context.getBean(DogSubCommand.class),
context.getBean(FoxSubCommand.class),
context.getBean(DuckSubCommand.class)
);
}
}

View File

@ -0,0 +1,60 @@
package cc.fascinated.bat.base.commands.general;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.*;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.DiscordService;
import cc.fascinated.bat.service.GuildService;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "botstats", description = "Shows the bot statistics", guildOnly = false, userInstall = true, category = Category.GENERAL)
public class BotStatsCommand extends BatCommand {
private final GuildService guildService;
private final UserService userService;
private final RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();
private final Runtime runtime = Runtime.getRuntime();
@Autowired
public BotStatsCommand(@NonNull GuildService guildService, @NonNull UserService userService) {
this.guildService = guildService;
this.userService = userService;
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
JDA jda = DiscordService.JDA;
long memoryUsed = (runtime.totalMemory() - runtime.freeMemory());
event.replyEmbeds(EmbedUtils.genericEmbed().setDescription(
new DescriptionBuilder("Bat Statistics")
.appendLine("Guilds: **%s**".formatted(NumberFormatter.format(jda.getGuilds().size())), true)
.appendLine("Users: **%s**".formatted(NumberFormatter.format(jda.getUsers().size())), true)
.appendLine("Gateway Ping: **%sms**".formatted(jda.getGatewayPing()), true)
.emptyLine()
.appendSubtitle("Bot Statistics")
.appendLine("Uptime: **%s**".formatted(TimeUtils.format(bean.getUptime())), true)
.appendLine("Memory Usage: **%s**".formatted(StringUtils.formatBytes(memoryUsed)), true)
.appendLine("Cached Guilds: **%s**".formatted(NumberFormatter.format(guildService.getGuilds().size())), true)
.appendLine("Cached Users: **%s**".formatted(NumberFormatter.format(userService.getUsers().size())), true)
.build()
).build()).queue();
}
}

View File

@ -0,0 +1,132 @@
package cc.fascinated.bat.base.commands.general;
import cc.fascinated.bat.Consts;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.InteractionBuilder;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.CommandService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "help",
description = "View the bots command categories",
userInstall = true,
category = Category.GENERAL
)
public class HelpCommand extends BatCommand implements EventListener {
private final CommandService commandService;
@Autowired
public HelpCommand(@NonNull CommandService commandService) {
this.commandService = commandService;
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
InteractionBuilder interactionBuilder = new InteractionBuilder();
interactionBuilder.addUrlButton("Invite Me", Consts.INVITE_URL, null);
interactionBuilder.addUrlButton("Support Server", Consts.SUPPORT_INVITE_URL, null);
interactionBuilder.addStringSelect("Home", "Return Home", Emojis.HOME_EMOJI, (buttonEvent) -> {
buttonEvent.editMessageEmbeds(createHomeEmbed(event.isFromGuild())).queue();
});
for (Category category : Category.getSortedByName()) {
List<BatCommand> categoryCommands = commandService.getCommandsByCategory(category, event.isFromGuild());
if (categoryCommands.isEmpty()) {
continue;
}
interactionBuilder.addStringSelect(
category.getName(),
"View commands in the %s category".formatted(category.getName()),
category.getEmoji(),
(buttonEvent) -> {
DescriptionBuilder description = new DescriptionBuilder(null);
description.appendLine("Commands in the **%s** Category".formatted(category.getName()), false);
description.emptyLine();
for (BatCommand command : categoryCommands) {
if (!command.getSubCommands().isEmpty()) {
for (BatCommand subCommand : command.getSubCommands().values()) {
description.appendLine("`%s %s` - %s".formatted(
command.getInfo().getName(),
subCommand.getInfo().getName(),
subCommand.getInfo().getDescription()
), true);
}
continue;
}
description.appendLine("`%s` - %s".formatted(
command.getInfo().getName(),
command.getInfo().getDescription()
), true);
}
buttonEvent.editMessageEmbeds(EmbedUtils.genericEmbed()
.setDescription(description.build())
.build()).queue();
});
}
event.replyEmbeds(createHomeEmbed(event.isFromGuild()))
.addComponents(interactionBuilder.build())
.setEphemeral(true)
.queue();
}
/**
* Creates the home embed for the help command
*
* @return The home embed
*/
private MessageEmbed createHomeEmbed(boolean ranInsideGuild) {
StringBuilder categories = new StringBuilder();
for (Category category : Category.values()) {
if (commandService.getCommandsByCategory(category, ranInsideGuild).isEmpty()) {
continue;
}
long commandCount = commandService.getCommandsByCategory(category, ranInsideGuild).size();
categories.append("- %s: **%s Command%s**\n".formatted(
category.getName(),
commandCount,
commandCount == 1 ? "" : "s"
));
}
return EmbedUtils.genericEmbed()
.setDescription("""
**Welcome to the Bat Help Menu!**
Bat is a multi-purpose bot that has a variety of features to help you with your server. You can change the category be clicking on the category name in the list below.
%s%s
*View our [TOS](%s) and [Privacy Policy](%s) for more information.*
""".formatted(
categories.toString(),
!ranInsideGuild ? "\n*guild only commands won't be shown here*" : "",
Consts.TERMS_OF_SERVICE_URL,
Consts.PRIVACY_POLICY_URL
))
.build();
}
}

View File

@ -0,0 +1,31 @@
package cc.fascinated.bat.base.commands.general;
import cc.fascinated.bat.Consts;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "invite", description = "Invite the bot to your server!", guildOnly = false, category = Category.GENERAL)
public class InviteCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("You can invite the bot to your server by clicking [here](%s)".formatted(Consts.INVITE_URL))
.build())
.setEphemeral(true)
.queue();
}
}

View File

@ -0,0 +1,38 @@
package cc.fascinated.bat.base.commands.general;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.DiscordService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "ping",
description = "Gets the ping of the bot",
guildOnly = false,
userInstall = true,
category = Category.GENERAL
)
public class PingCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
long time = System.currentTimeMillis();
event.reply("Pinging...").queue(response -> {
response.editOriginal("Gateway response time: `%sms`\nAPI response time `%sms`".formatted(
DiscordService.JDA.getGatewayPing(),
System.currentTimeMillis() - time
)).queue();
});
}
}

View File

@ -0,0 +1,40 @@
package cc.fascinated.bat.base.commands.general;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "vote", description = "Vote for the bot", guildOnly = false, userInstall = true, category = Category.GENERAL)
public class VoteCommand extends BatCommand {
private static final String[] VOTE_LINKS = new String[]{
"https://top.gg/bot/1254161119975833652/vote",
"https://discordbotlist.com/bots/bat/upvote"
};
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
DescriptionBuilder description = new DescriptionBuilder("Vote Links");
description.appendLine("Vote for the bot on the following websites to support us!", false);
for (String link : VOTE_LINKS) {
description.appendLine(link, true);
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(description.build())
.build()
).queue();
}
}

View File

@ -0,0 +1,24 @@
package cc.fascinated.bat.base.commands.general.avatar;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component
@CommandInfo(name = "avatar", description = "View the avatar of the guild or a user", guildOnly = false, category = Category.GENERAL)
public class AvatarCommand extends BatCommand {
@Autowired
public AvatarCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(GuildSubCommand.class),
context.getBean(UserSubCommand.class)
);
}
}

View File

@ -0,0 +1,40 @@
package cc.fascinated.bat.base.commands.general.avatar;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.utils.ImageProxy;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component("avatar:guild.sub")
@CommandInfo(name = "guild", description = "View the avatar of the guild", category = Category.GENERAL)
public class GuildSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
ImageProxy icon = guild.getDiscordGuild().getIcon();
if (icon == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("**%s** does not have an avatar!".formatted(guild.getName()))
.build())
.queue();
return;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s's Avatar".formatted(guild.getName()), null, guild.getDiscordGuild().getIconUrl())
.setImage(icon.getUrl(4096))
.build()
).queue();
}
}

View File

@ -0,0 +1,42 @@
package cc.fascinated.bat.base.commands.general.avatar;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component("avatar:user.sub")
@CommandInfo(name = "user", description = "View the avatar of a user", guildOnly = false, category = Category.GENERAL)
public class UserSubCommand extends BatCommand {
public UserSubCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user to view the avatar of", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping userOption = event.getOption("user");
assert userOption != null;
User target = userOption.getAsUser();
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s's Avatar".formatted(target.getName()), null, target.getEffectiveAvatarUrl())
.setImage(target.getEffectiveAvatarUrl())
.build()
).queue();
}
}

View File

@ -0,0 +1,24 @@
package cc.fascinated.bat.base.commands.general.banner;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component
@CommandInfo(name = "banner", description = "View the banner of the guild or a user", guildOnly = false, category = Category.GENERAL)
public class BannerCommand extends BatCommand {
@Autowired
public BannerCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(GuildSubCommand.class),
context.getBean(UserSubCommand.class)
);
}
}

View File

@ -0,0 +1,40 @@
package cc.fascinated.bat.base.commands.general.banner;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.utils.ImageProxy;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component("banner:guild.sub")
@CommandInfo(name = "guild", description = "View the banner of the guild", category = Category.GENERAL)
public class GuildSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
ImageProxy banner = guild.getDiscordGuild().getBanner();
if (banner == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("**%s** does not have a banner!".formatted(guild.getName()))
.build())
.queue();
return;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s's Banner".formatted(guild.getName()))
.setImage(banner.getUrl(512))
.build()
).queue();
}
}

View File

@ -0,0 +1,51 @@
package cc.fascinated.bat.base.commands.general.banner;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.utils.ImageProxy;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component("banner:user.sub")
@CommandInfo(name = "user", description = "View the banner of a user", guildOnly = false, category = Category.GENERAL)
public class UserSubCommand extends BatCommand {
public UserSubCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user to view the banner of", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping userOption = event.getOption("user");
assert userOption != null;
User target = userOption.getAsUser();
ImageProxy banner = target.retrieveProfile().complete().getBanner();
if (banner == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("**%s** does not have a banner!".formatted(target.getName()))
.build())
.queue();
return;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s's Banner".formatted(target.getName()))
.setImage(banner.getUrl(512))
.build()
).queue();
}
}

View File

@ -0,0 +1,47 @@
package cc.fascinated.bat.base.commands.server;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component
@CommandInfo(name = "membercount", description = "View the member count of the server!")
public class MemberCountCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
Guild discordGuild = guild.getDiscordGuild();
int totalMembers = 0, totalUsers = 0, totalBots = 0;
for (Member guildMember : discordGuild.getMembers()) {
if (guildMember.getUser().isBot()) {
totalBots++;
} else {
totalUsers++;
}
totalMembers++;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("""
**Member Count**
Total Members: `%s`
Total Users: `%s`
Total Bots: `%s`
""".formatted(
NumberFormatter.format(totalMembers),
NumberFormatter.format(totalUsers),
NumberFormatter.format(totalBots)
)).build()).queue();
}
}

View File

@ -0,0 +1,38 @@
package cc.fascinated.bat.base.commands.server;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.premium.PremiumProfile;
import lombok.NonNull;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "premium", description = "View the premium information for the guild", requiredPermissions = Permission.ADMINISTRATOR)
public class PremiumCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
PremiumProfile premium = guild.getPremiumProfile();
EmbedBuilder embed = EmbedUtils.genericEmbed().setAuthor("Premium Information");
if (premium.hasPremium()) {
embed.addField("Premium", premium.hasPremium() ? "Yes" : "No", true);
embed.addField("Started", "<t:%d>".formatted(premium.getActivatedAt().toInstant().getEpochSecond()), true);
embed.addField("Expires", premium.isInfinite() ? "Never" : "<t:%d>"
.formatted(premium.getExpiresAt().toInstant().toEpochMilli() / 1000), true);
} else {
embed.setDescription("The guild does not have premium");
}
event.replyEmbeds(embed.build()).queue();
}
}

View File

@ -0,0 +1,25 @@
package cc.fascinated.bat.base.commands.server.channel;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component
@CommandInfo(name = "channel", description = "View or set information about a channel", category = Category.SERVER)
public class ChannelCommand extends BatCommand {
@Autowired
public ChannelCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(ViewTopicSubCommand.class),
context.getBean(SetTopicSubCommand.class),
context.getBean(RemoveTopicSubCommand.class)
);
}
}

View File

@ -0,0 +1,55 @@
package cc.fascinated.bat.base.commands.server.channel;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component
@CommandInfo(name = "removetopic", description = "Remove the topic of a channel", requiredPermissions = Permission.MANAGE_CHANNEL)
public class RemoveTopicSubCommand extends BatCommand {
public RemoveTopicSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to remove the topic of", false));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
Channel target = event.getOption("channel") == null ? channel : event.getOption("channel").getAsChannel();
if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> is not a text channel!".formatted(target.getId()))
.build())
.queue();
return;
}
if (textChannel.getTopic() == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> does not have a topic!".formatted(textChannel.getId()))
.build())
.queue();
return;
}
textChannel.getManager().setTopic(null).queue();
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully removed the topic of <#%s>".formatted(textChannel.getId()))
.build()
).queue();
}
}

View File

@ -0,0 +1,58 @@
package cc.fascinated.bat.base.commands.server.channel;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* @author Nick (okNick)
*/
@Component
@CommandInfo(name = "settopic", description = "Set the topic of a channel", requiredPermissions = Permission.MANAGE_CHANNEL)
public class SetTopicSubCommand extends BatCommand {
public SetTopicSubCommand() {
super.addOptions(
new OptionData(OptionType.STRING, "topic", "The topic to set", true),
new OptionData(OptionType.CHANNEL, "channel", "The channel to set the topic of", false)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
Channel target = event.getOption("channel") == null ? channel : Objects.requireNonNull(event.getOption("channel")).getAsChannel();
if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> is not a text channel!".formatted(target.getId()))
.build())
.queue();
return;
}
String topic = Objects.requireNonNull(event.getOption("topic")).getAsString();
if (topic.length() > 1024) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The topic must be 1024 characters or less!")
.build())
.queue();
return;
}
event.deferReply().queue(hook -> textChannel.getManager().setTopic(topic).queue((voidd) -> hook.editOriginalEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully set the topic of <#%s> to: \"%s\"".formatted(textChannel.getId(), topic))
.build()).queue()));
}
}

View File

@ -0,0 +1,54 @@
package cc.fascinated.bat.base.commands.server.channel;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component
@CommandInfo(name = "viewtopic", description = "View the topic of a channel")
public class ViewTopicSubCommand extends BatCommand {
public ViewTopicSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to view the topic of", false));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
Channel target = event.getOption("channel") == null ? channel : event.getOption("channel").getAsChannel();
if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> is not a text channel!".formatted(target.getId()))
.build())
.queue();
return;
}
String topic = textChannel.getTopic();
if (topic == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> does not have a topic!".formatted(textChannel.getId()))
.build())
.queue();
return;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("The topic of <#%s> is: \"%s\"".formatted(textChannel.getId(), topic))
.build()
).queue();
}
}

View File

@ -0,0 +1,63 @@
package cc.fascinated.bat.base.commands.server.feature;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("feature:disable.sub")
@CommandInfo(name = "disable", description = "Disables a feature")
public class DisableSubCommand extends BatCommand {
@Autowired
public DisableSubCommand() {
super.addOptions(new OptionData(OptionType.STRING, "feature", "The feature to disable", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
FeatureProfile featureProfile = guild.getFeatureProfile();
OptionMapping featureOption = event.getOption("feature");
if (featureOption == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a feature to enabled")
.build()).queue();
return;
}
String featureName = featureOption.getAsString();
if (!FeatureService.INSTANCE.isFeature(featureName)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("That feature does not exist")
.build()).queue();
return;
}
Feature feature = FeatureService.INSTANCE.getFeature(featureName);
if (featureProfile.isFeatureDisabled(feature)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The feature `%s` is already disabled".formatted(feature.getName()))
.build()).queue();
return;
}
featureProfile.disableFeature(feature);
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Successfully disabled the `%s` feature".formatted(feature.getName()))
.build()).queue();
}
}

View File

@ -0,0 +1,63 @@
package cc.fascinated.bat.base.commands.server.feature;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("feature:enable.sub")
@CommandInfo(name = "enable", description = "Enables a feature")
public class EnableSubCommand extends BatCommand {
@Autowired
public EnableSubCommand() {
super.addOptions(new OptionData(OptionType.STRING, "feature", "The feature to enable", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
FeatureProfile featureProfile = guild.getFeatureProfile();
OptionMapping featureOption = event.getOption("feature");
if (featureOption == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a feature to enabled")
.build()).queue();
return;
}
String featureName = featureOption.getAsString();
if (!FeatureService.INSTANCE.isFeature(featureName)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("That feature does not exist")
.build()).queue();
return;
}
Feature feature = FeatureService.INSTANCE.getFeature(featureName);
if (featureProfile.isFeatureEnabled(feature)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The feature `%s` is already enabled".formatted(feature.getName()))
.build()).queue();
return;
}
featureProfile.enableFeature(feature);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully enabled the `%s` feature".formatted(feature.getName()))
.build()).queue();
}
}

View File

@ -0,0 +1,26 @@
package cc.fascinated.bat.base.commands.server.feature;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "feature", description = "Configure features in your guild", requiredPermissions = Permission.ADMINISTRATOR, category = Category.SERVER)
public class FeatureCommand extends BatCommand {
@Autowired
public FeatureCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(EnableSubCommand.class),
context.getBean(DisableSubCommand.class),
context.getBean(ListSubCommand.class)
);
}
}

View File

@ -0,0 +1,40 @@
package cc.fascinated.bat.base.commands.server.feature;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("feature:list.sub")
@CommandInfo(name = "list", description = "Lists the features and their states")
public class ListSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
StringBuilder featureStates = new StringBuilder();
for (Feature feature : FeatureService.INSTANCE.getFeaturesSorted()) {
FeatureProfile featureProfile = guild.getFeatureProfile();
featureStates.append("%s `%s`\n".formatted(
featureProfile.getFeatureState(feature).getEmoji(),
feature.getName()
));
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setTitle("Feature List")
.setDescription(featureStates.toString())
.build()
).queue();
}
}

View File

@ -0,0 +1,54 @@
package cc.fascinated.bat.base.commands.utility;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.PasteUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "pastebin",
description = "Uploads the given text to Paste",
userInstall = true,
category = Category.UTILITY
)
public class PastebinCommand extends BatCommand {
public PastebinCommand() {
super.addOptions(
new OptionData(OptionType.STRING, "text", "The text to upload to Paste", true)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping textOption = event.getOption("text");
assert textOption != null;
String text = textOption.getAsString();
// Upload the text to pastebin
String url = PasteUtils.uploadPaste(text).getUrl();
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription(new DescriptionBuilder("The text has been uploaded to Paste!")
.appendLine("URL: %s".formatted(url), true)
.build())
.build())
.setEphemeral(true)
.queue();
}
}

View File

@ -0,0 +1,28 @@
package cc.fascinated.bat.base.commands.utility.lookup;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component
@CommandInfo(
name = "lookup",
description = "View the banner of the guild or a user",
userInstall = true,
category = Category.UTILITY
)
public class LookupCommand extends BatCommand {
@Autowired
public LookupCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(UserSubCommand.class)
);
}
}

View File

@ -0,0 +1,75 @@
package cc.fascinated.bat.base.commands.utility.lookup;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("lookup.user:sub")
@CommandInfo(name = "user", description = "Lookup a user")
public class UserSubCommand extends BatCommand {
private final UserService userService;
@Autowired
public UserSubCommand(@NonNull UserService userService) {
this.userService = userService;
super.addOptions(new OptionData(OptionType.STRING, "id", "The id of the user", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping idOption = event.getOption("id");
if (idOption == null) {
return;
}
User target = userService.getUser(idOption.getAsString()).getDiscordUser();
if (target == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("User `%s` not found".formatted(idOption.getAsString()))
.build())
.setEphemeral(true)
.queue();
return;
}
// The flags of the user (eg. Discord Partner, Hypesquad Events, etc.)
StringBuilder flags = new StringBuilder();
for (User.UserFlag flag : target.getFlags()) {
flags.append("`").append(flag.getName()).append("`, ");
}
String name = target.getGlobalName() == null ? target.getName() : target.getGlobalName().replaceAll("`", "");
target.retrieveProfile().queue(profile -> event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new DescriptionBuilder("User Lookup")
.appendLine("Name: `%s`".formatted(name), true)
.appendLine("Username: `%s`".formatted(target.getName()), true)
.appendLine("ID: `%s`".formatted(target.getId()), true)
.appendLine("Flags: %s".formatted(flags.toString().isEmpty() ? "None" : flags.substring(0, flags.length() - 2)), true)
.appendLine("Joined Discord: <t:%s:R>".formatted(target.getTimeCreated().toEpochSecond()), true)
.appendLine("Avatar: %s".formatted(target.getAvatar() == null ? "None"
: "[click here](%s)".formatted(target.getAvatar().getUrl(4096))), true)
.appendLine("Banner: %s".formatted(profile.getBanner() == null ? "None"
: "[click here](%s)".formatted(profile.getBanner().getUrl(4096))), true)
.build())
.setThumbnail(target.getAvatar() == null ? null : target.getAvatar().getUrl(4096))
.build())
.setEphemeral(true)
.queue());
}
}

View File

@ -0,0 +1,46 @@
package cc.fascinated.bat.birthday;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.birthday.command.BirthdayCommand;
import cc.fascinated.bat.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.service.CommandService;
import cc.fascinated.bat.service.DiscordService;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class BirthdayFeature extends Feature implements EventListener {
private final GuildService guildService;
public BirthdayFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService, @NonNull GuildService guildService) {
super("Birthday", FeatureProfile.FeatureState.DISABLED, true);
this.guildService = guildService;
registerCommand(commandService, context.getBean(BirthdayCommand.class));
}
/**
* Check birthdays every day at midnight
*/
@Scheduled(cron = "0 1 0 * * *")
private void checkBirthdays() {
for (Guild guild : DiscordService.JDA.getGuilds()) {
BatGuild batGuild = guildService.getGuild(guild.getId());
if (batGuild.getFeatureProfile().isFeatureDisabled(this)) { // Check if the feature is disabled
continue;
}
BirthdayProfile profile = batGuild.getBirthdayProfile();
profile.checkBirthdays(batGuild);
}
}
}

View File

@ -0,0 +1,62 @@
package cc.fascinated.bat.birthday;
import cc.fascinated.bat.common.Serializable;
import com.google.gson.Gson;
import lombok.Getter;
import lombok.Setter;
import org.bson.Document;
import java.util.Calendar;
import java.util.Date;
/**
* @author Fascinated (fascinated7)
*/
@Getter
@Setter
public class UserBirthday extends Serializable {
/**
* The user's birthday
*/
private Date birthday;
/**
* If the birthday should be hidden
*/
private boolean hidden;
/**
* Calculates the age of the user
*
* @return the age of the user
*/
public int calculateAge() {
Calendar birthdayCalendar = Calendar.getInstance();
birthdayCalendar.setTime(this.getBirthday());
Calendar today = Calendar.getInstance();
int age = today.get(Calendar.YEAR) - birthdayCalendar.get(Calendar.YEAR);
// Check if the birthday hasn't occurred yet this year
if (today.get(Calendar.DAY_OF_YEAR) < birthdayCalendar.get(Calendar.DAY_OF_YEAR)) {
age--;
}
return age;
}
@Override
public void load(Document document, Gson gson) {
this.birthday = document.getDate("birthday");
this.hidden = document.getBoolean("hidden", false);
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("birthday", this.birthday);
document.put("hidden", this.hidden);
return document;
}
@Override
public void reset() {}
}

View File

@ -0,0 +1,28 @@
package cc.fascinated.bat.birthday.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "birthday", description = "Modify your birthday settings.", category = Category.UTILITY)
public class BirthdayCommand extends BatCommand {
@Autowired
public BirthdayCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(SetSubCommand.class),
context.getBean(RemoveSubCommand.class),
context.getBean(ChannelSubCommand.class),
context.getBean(MessageSubCommand.class),
context.getBean(ViewSubCommand.class),
context.getBean(PrivateSubCommand.class)
);
}
}

View File

@ -1,21 +1,23 @@
package cc.fascinated.bat.features.birthday.command; package cc.fascinated.bat.birthday.command;
import cc.fascinated.bat.command.BatSubCommand; import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.TextChannelUtils; import cc.fascinated.bat.common.TextChannelUtils;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile; import cc.fascinated.bat.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -23,28 +25,25 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component("birthday:channel.sub") @Component("birthday:channel.sub")
public class ChannelSubCommand extends BatSubCommand { @CommandInfo(name = "channel", description = "Sets the birthday notification channel", requiredPermissions = Permission.MANAGE_SERVER)
private final GuildService guildService; public class ChannelSubCommand extends BatCommand {
@Autowired @Autowired
public ChannelSubCommand(GuildService guildService) { public ChannelSubCommand() {
super("channel", "Sets the birthday notification channel", Permission.MANAGE_SERVER); super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel birthdays will be sent in", false));
super.addOption(OptionType.CHANNEL, "channel", "The channel birthdays will be sent in", false);
this.guildService = guildService;
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class); BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping option = interaction.getOption("channel"); OptionMapping option = event.getOption("channel");
if (option == null) { if (option == null) {
if (!TextChannelUtils.isValidChannel(profile.getChannelId())) { if (!TextChannelUtils.isValidChannel(profile.getChannelId())) {
interaction.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("There is no channel set for birthday notifications. Please provide a channel to set the birthday channel to") .setDescription("There is no channel set for birthday notifications. Please provide a channel to set the birthday channel to")
.build()).queue(); .build()).queue();
return; return;
} }
interaction.replyEmbeds(EmbedUtils.genericEmbed() event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("The current birthday channel is %s".formatted(TextChannelUtils.getChannelMention(profile.getChannelId()))) .setDescription("The current birthday channel is %s".formatted(TextChannelUtils.getChannelMention(profile.getChannelId())))
.build()).queue(); .build()).queue();
return; return;
@ -52,16 +51,14 @@ public class ChannelSubCommand extends BatSubCommand {
GuildChannelUnion targetChannel = option.getAsChannel(); GuildChannelUnion targetChannel = option.getAsChannel();
if (targetChannel.getType() != ChannelType.TEXT) { if (targetChannel.getType() != ChannelType.TEXT) {
interaction.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Invalid channel type, please provide a text channel") .setDescription("Invalid channel type, please provide a text channel")
.build()).queue(); .build()).queue();
return; return;
} }
profile.setChannelId(targetChannel.getId()); profile.setChannelId(targetChannel.getId());
guildService.saveGuild(guild); event.replyEmbeds(EmbedUtils.successEmbed()
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully set the birthday channel to %s".formatted(targetChannel.asTextChannel().getAsMention())) .setDescription("Successfully set the birthday channel to %s".formatted(targetChannel.asTextChannel().getAsMention()))
.build()).queue(); .build()).queue();
} }

View File

@ -0,0 +1,58 @@
package cc.fascinated.bat.birthday.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("birthday:message.sub")
@CommandInfo(name = "message", description = "Changes the message that is sent when it is a user's birthday", requiredPermissions = Permission.MANAGE_SERVER)
public class MessageSubCommand extends BatCommand {
@Autowired
public MessageSubCommand() {
super.addOptions(new OptionData(OptionType.STRING, "message", "The message that is sent. (Placeholders: {user}, {age})", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping messageOption = event.getOption("message");
assert messageOption != null;
String message = messageOption.getAsString();
if (message.length() > 2000) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The message must be less than 2000 characters")
.build()).queue();
return;
}
if (!message.contains("{user}") || !message.contains("{age}")) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The message must contain the placeholders {user} and {age}")
.build()).queue();
return;
}
profile.setMessage(message);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You have updated the birthday message!\n\n**Message:** %s".formatted(profile.getBirthdayMessage(user.getDiscordUser())))
.build()).queue();
}
}

View File

@ -0,0 +1,53 @@
package cc.fascinated.bat.birthday.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.birthday.UserBirthday;
import cc.fascinated.bat.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("birthday:private.sub")
@CommandInfo(name = "private", description = "Changes whether your birthday is private or not")
public class PrivateSubCommand extends BatCommand {
@Autowired
public PrivateSubCommand() {
super.addOptions(new OptionData(OptionType.BOOLEAN, "enabled", "Whether your birthday is private or not", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping enabledOption = event.getOption("enabled");
assert enabledOption != null;
boolean enabled = enabledOption.getAsBoolean();
UserBirthday birthday = profile.getBirthday(user.getId());
if (birthday == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You have not set your birthday yet")
.build()).queue();
return;
}
birthday.setHidden(enabled);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Your birthday privacy settings have been updated\n\n**Private:** " + (enabled ? "Yes" : "No"))
.build()).queue();
}
}

View File

@ -0,0 +1,32 @@
package cc.fascinated.bat.birthday.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("birthday:remove.sub")
@CommandInfo(name = "remove", description = "Remove your birthday from this guild")
public class RemoveSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile();
profile.removeBirthday(user.getId());
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Your birthday has been removed from this guild")
.build()).queue();
}
}

View File

@ -0,0 +1,93 @@
package cc.fascinated.bat.birthday.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.birthday.UserBirthday;
import cc.fascinated.bat.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author Fascinated (fascinated7)
*/
@Component("birthday:add.sub")
@CommandInfo(name = "set", description = "Add your birthday to this guild")
public class SetSubCommand extends BatCommand {
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("dd/MM/yyyy");
@Autowired
public SetSubCommand() {
super.addOptions(new OptionData(OptionType.STRING, "birthday", "Your birthday (format: DAY/MONTH/YEAR - 01/05/2004)", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile();
if (!profile.hasChannelSetup()) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Birthdays have not been enabled in this guild. Please ask an administrator to enable them.")
.build()).queue();
return;
}
OptionMapping birthdayOption = event.getOption("birthday");
assert birthdayOption != null;
String birthdayString = birthdayOption.getAsString();
Date birthday = parseBirthday(birthdayString);
if (birthday == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("""
Invalid birthday format. Please use the following format:
DAY/MONTH/YEAR - 01/05/2004
""")
.build()).queue();
return;
}
UserBirthday userBirthday = new UserBirthday();
userBirthday.setBirthday(birthday);
userBirthday.setHidden(false);
profile.addBirthday(member.getId(), userBirthday);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You have updated your birthday!")
.build()).queue();
}
/**
* Parses a birthday from the string
*
* @param birthday the date to parse
* @return the birthday
*/
private Date parseBirthday(String birthday) {
try {
Date date = FORMATTER.parse(birthday);
if (date.after(new Date())) {
return null;
}
if (date.toInstant().toEpochMilli() < 0) {
return null;
}
return date;
} catch (ParseException ignored) {
return null;
}
}
}

View File

@ -0,0 +1,76 @@
package cc.fascinated.bat.birthday.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.birthday.UserBirthday;
import cc.fascinated.bat.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("birthday:view.sub")
@CommandInfo(name = "view", description = "Add your birthday to this guild")
public class ViewSubCommand extends BatCommand {
private final UserService userService;
@Autowired
public ViewSubCommand(@NonNull UserService userService) {
this.userService = userService;
super.addOptions(new OptionData(OptionType.USER, "user", "The user to view the birthday of", false));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile();
if (!profile.hasChannelSetup()) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Birthdays have not been enabled in this guild. Please ask an administrator to enable them.")
.build()).queue();
return;
}
OptionMapping birthdayOption = event.getOption("user");
BatUser target = birthdayOption == null ? user : userService.getUser(birthdayOption.getAsUser().getId());
if (target == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a valid user")
.build()).queue();
return;
}
UserBirthday birthday = profile.getBirthday(target.getId());
if (birthday == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s does not have a birthday set".formatted(target.getDiscordUser().getAsMention()))
.build()).queue();
return;
}
if (birthday.isHidden() && !user.getId().equals(target.getId())) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s has their birthday set to private".formatted(target.getDiscordUser().getAsMention()))
.build()).queue();
return;
}
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("%s was born on <t:%s:D> they are `%s` years old!".formatted(
target.getDiscordUser().getAsMention(), birthday.getBirthday().toInstant().toEpochMilli()/1000,
birthday.calculateAge()
))
.build()).queue();
}
}

View File

@ -1,28 +1,37 @@
package cc.fascinated.bat.features.birthday.profile; package cc.fascinated.bat.birthday.profile;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.ChannelUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.birthday.UserBirthday;
import cc.fascinated.bat.common.model.BatGuild;
import com.google.gson.Gson;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.bson.Document;
import java.util.*; import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Getter @Getter
@Setter @Setter
public class BirthdayProfile extends Profile { @NoArgsConstructor
public class BirthdayProfile extends Serializable {
private static final String DEFAULT_MESSAGE = "Happy Birthday {user} :tada: :birthday: You are now {age} years old!"; private static final String DEFAULT_MESSAGE = "Happy Birthday {user} :tada: :birthday: You are now {age} years old!";
/** /**
* The list of birthdays that are being tracked * The list of birthdays that are being tracked
*/ */
private Map<String, Date> birthdays; private Map<String, UserBirthday> birthdays;
/** /**
* The channel ID of the birthday feed * The channel ID of the birthday feed
@ -34,17 +43,13 @@ public class BirthdayProfile extends Profile {
*/ */
private String message = DEFAULT_MESSAGE; private String message = DEFAULT_MESSAGE;
public BirthdayProfile() {
super("birthday");
}
/** /**
* Adds a birthday to be tracked * Adds a birthday to be tracked
* *
* @param userId the id of the user to track * @param userId the id of the user to track
* @param birthday the birthday of the user * @param birthday the birthday of the user
*/ */
public void addBirthday(String userId, Date birthday) { public void addBirthday(String userId, UserBirthday birthday) {
if (birthdays == null) { if (birthdays == null) {
birthdays = new HashMap<>(); birthdays = new HashMap<>();
} }
@ -69,7 +74,7 @@ public class BirthdayProfile extends Profile {
* @param userId the id of the user * @param userId the id of the user
* @return the birthday of the user * @return the birthday of the user
*/ */
public Date getBirthday(String userId) { public UserBirthday getBirthday(String userId) {
if (birthdays == null) { if (birthdays == null) {
birthdays = new HashMap<>(); birthdays = new HashMap<>();
} }
@ -85,33 +90,6 @@ public class BirthdayProfile extends Profile {
return channelId != null; return channelId != null;
} }
/**
* Calculates the age of a user
*
* @param userId the id of the user
* @return the age of the user
*/
public int calculateAge(String userId) {
Date birthday = getBirthday(userId);
if (birthday == null) {
return 0; // or throw an exception
}
Calendar birthdayCalendar = Calendar.getInstance();
birthdayCalendar.setTime(birthday);
Calendar today = Calendar.getInstance();
int age = today.get(Calendar.YEAR) - birthdayCalendar.get(Calendar.YEAR);
// Check if the birthday hasn't occurred yet this year
if (today.get(Calendar.DAY_OF_YEAR) < birthdayCalendar.get(Calendar.DAY_OF_YEAR)) {
age--;
}
return age;
}
/** /**
* Validates the profiles configuration * Validates the profiles configuration
* *
@ -122,33 +100,10 @@ public class BirthdayProfile extends Profile {
if (birthdays == null) { if (birthdays == null) {
birthdays = new HashMap<>(); birthdays = new HashMap<>();
} }
List<String> toRemove = new ArrayList<>();
Guild discordGuild = guild.getDiscordGuild();
for (Map.Entry<String, Date> entry : birthdays.entrySet()) {
String userId = entry.getKey();
Date birthday = entry.getValue();
if (userId == null || birthday == null) { // this should never happen
continue;
}
// Check if the user is still in the guild, if not remove them
Member member = discordGuild.getMemberById(userId);
if (member == null) {
toRemove.add(userId);
}
}
for (String userId : toRemove) {
birthdays.remove(userId);
}
if (channelId == null) { if (channelId == null) {
return false; return false;
} }
if (guild.getDiscordGuild().getTextChannelById(channelId) == null) {
if (discordGuild.getTextChannelById(channelId) == null) {
channelId = null; channelId = null;
return false; return false;
} }
@ -170,13 +125,10 @@ public class BirthdayProfile extends Profile {
int todayDay = today.get(Calendar.DAY_OF_MONTH); int todayDay = today.get(Calendar.DAY_OF_MONTH);
int todayMonth = today.get(Calendar.MONTH); // Note: January is 0 int todayMonth = today.get(Calendar.MONTH); // Note: January is 0
Iterator<Map.Entry<String, Date>> iterator = birthdays.entrySet().iterator(); for (Map.Entry<String, UserBirthday> entry : birthdays.entrySet()) {
while (iterator.hasNext()) { Date birthday = entry.getValue().getBirthday();
Map.Entry<String, Date> entry = iterator.next();
Date birthday = entry.getValue();
if (birthday == null) { if (birthday == null) {
iterator.remove();
continue; continue;
} }
@ -206,7 +158,7 @@ public class BirthdayProfile extends Profile {
return; return;
} }
TextChannel channel = discordGuild.getTextChannelById(channelId); TextChannel channel = ChannelUtils.getTextChannel(channelId);
if (channel == null) { // this should never happen if (channel == null) { // this should never happen
channelId = null; channelId = null;
return; return;
@ -223,7 +175,7 @@ public class BirthdayProfile extends Profile {
public String getBirthdayMessage(User user) { public String getBirthdayMessage(User user) {
return message return message
.replace("{user}", user.getAsMention()) .replace("{user}", user.getAsMention())
.replace("{age}", String.valueOf(calculateAge(user.getId()))); .replace("{age}", String.valueOf(birthdays.get(user.getId()).calculateAge()));
} }
@Override @Override
@ -231,4 +183,27 @@ public class BirthdayProfile extends Profile {
birthdays.clear(); birthdays.clear();
channelId = null; channelId = null;
} }
@Override
public void load(Document document, Gson gson) {
birthdays = new HashMap<>();
for (String key : document.keySet()) {
UserBirthday userBirthday = new UserBirthday();
userBirthday.load((Document) document.get(key), gson);
birthdays.put(key, userBirthday);
}
channelId = document.getString("channelId");
message = (String) document.getOrDefault("message", DEFAULT_MESSAGE);
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
for (String key : birthdays.keySet()) {
document.put(key, birthdays.get(key).serialize(gson));
}
document.put("channelId", channelId);
document.put("message", message);
return document;
}
} }

View File

@ -1,95 +0,0 @@
package cc.fascinated.bat.command;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Getter @Setter
public abstract class BatCommand implements BatCommandExecutor {
/**
* The name of the command
*/
private final String name;
/**
* The description of the command
*/
private final String description;
/**
* The command data for the slash command
*/
private final CommandDataImpl commandData;
/**
* The required permissions for the command
*/
private final List<Permission> requiredPermissions;
/**
* The sub commands of the command
*/
private final Map<String, BatSubCommand> subCommands = new HashMap<>();
public BatCommand(@NonNull String name, @NonNull String description, boolean guildOnly, Permission... permissions) {
this.name = name;
this.description = description;
this.requiredPermissions = List.of(permissions);
this.commandData = new CommandDataImpl(this.name, description)
.setGuildOnly(guildOnly);
}
public BatCommand(@NonNull String name) {
this(name, "No description provided.", false);
}
public BatCommand(@NonNull String name, @NonNull String description) {
this(name, description, false);
}
/**
* Adds a sub command to the command
*
* @param name The name of the sub command
* @param subCommand The sub command
*/
public void addSubCommand(@NonNull String name, @NonNull BatSubCommand subCommand) {
this.subCommands.put(name.toLowerCase(), subCommand);
this.commandData.addSubcommands(subCommand.getCommandData());
}
/**
* Adds an option to the sub command
*
* @param optionType the type of the option
* @param name the name of the option
* @param description the description of the option
* @param required whether the option is required
*/
protected void addOption(OptionType optionType, String name, String description, boolean required) {
this.commandData.addOption(optionType, name, description, required);
}
/**
* Gets all the options for the command
*
* @param interaction The slash command interaction
* @return The option strings
*/
public List<String> getOptions(SlashCommandInteraction interaction) {
return interaction.getOptions().stream().map(OptionMapping::getName).toList();
}
}

View File

@ -1,30 +0,0 @@
package cc.fascinated.bat.command;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
/**
* @author Fascinated (fascinated7)
*/
public interface BatCommandExecutor {
/**
* Executes the command using a slash command interaction.
*
* @param guild the bat guild the command was executed in (null if the command was executed in a DM)
* @param user the bat user that executed the command
* @param channel the channel the command was executed in
* @param member the member that executed the command
* @param interaction the slash command interaction
*/
default void execute(
BatGuild guild,
@NonNull BatUser user,
@NonNull MessageChannel channel,
Member member,
@NonNull SlashCommandInteraction interaction
) {}
}

View File

@ -1,41 +0,0 @@
package cc.fascinated.bat.command;
import lombok.Getter;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@Getter
public class BatSubCommand implements BatCommandExecutor {
/**
* The command data for the slash command
*/
private final SubcommandData commandData;
/**
* The required permissions for the command
*/
private final List<Permission> requiredPermissions;
public BatSubCommand(String name, String description, Permission... permissions) {
this.commandData = new SubcommandData(name, description);
this.requiredPermissions = List.of(permissions);
}
/**
* Adds an option to the sub command
*
* @param optionType the type of the option
* @param name the name of the option
* @param description the description of the option
* @param required whether the option is required
*/
public void addOption(OptionType optionType, String name, String description, boolean required) {
this.commandData.addOption(optionType, name, description, required);
}
}

View File

@ -1,41 +0,0 @@
package cc.fascinated.bat.command.impl;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class AvatarCommand extends BatCommand {
public AvatarCommand() {
super("avatar", "Gets the avatar of a user");
super.addOption(OptionType.USER, "user", "The user to get the avatar of", true);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
OptionMapping userOption = interaction.getOption("user");
if (userOption == null) {
interaction.reply("You must provide a user to get the avatar of!").queue();
return;
}
User target = userOption.getAsUser();
interaction.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s's Avatar".formatted(target.getName()), null, target.getEffectiveAvatarUrl())
.setImage(target.getAvatar().getUrl(4096))
.build()
).queue();
}
}

View File

@ -1,32 +0,0 @@
package cc.fascinated.bat.command.impl;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.DiscordService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class PingCommand extends BatCommand {
public PingCommand() {
super("ping", "Gets the ping of the bot");
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
long time = System.currentTimeMillis();
interaction.reply("Pinging...").queue(response -> {
response.editOriginal("Gateway response time: `%sms`\nAPI response time `%sms`".formatted(
DiscordService.JDA.getGatewayPing(),
System.currentTimeMillis() - time
)).queue();
});
}
}

View File

@ -0,0 +1,48 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.service.DiscordService;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
@Log4j2(topic = "ChannelUtils")
public class ChannelUtils {
/**
* Gets the user with the given id
*
* @param id the id of the user
* @param retries the amount of retries
* @return the user with the given id
*/
private static TextChannel getTextChannel(String id, int retries) {
if (retries >= 10) {
log.error("Failed to find user \"{}\" after {} retries.", id, retries);
return null;
}
if (id == null) {
return null;
}
TextChannel channel = DiscordService.JDA.getTextChannelById(id);
if (channel == null) {
return getTextChannel(id, retries + 1);
}
if (retries >= 5) {
log.info("Found text channel \"{}\" after {} retries.", channel.getName(), retries);
}
return channel;
}
/**
* Gets the user with the given id
*
* @param id the id of the user
* @return the user with the given id
*/
public static TextChannel getTextChannel(String id) {
return getTextChannel(id, 0);
}
}

View File

@ -3,12 +3,18 @@ package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Date; import java.util.Date;
import java.util.Locale;
@UtilityClass @UtilityClass
public class DateUtils { public class DateUtils {
private static final ZoneId ZONE_ID = ZoneId.of("Europe/London");
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_INSTANT; private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_INSTANT;
private static final DateTimeFormatter SIMPLE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
.withLocale(Locale.UK)
.withZone(ZONE_ID);
/** /**
* Gets the date from a string. * Gets the date from a string.
@ -19,4 +25,14 @@ public class DateUtils {
public static Date getDateFromString(String date) { public static Date getDateFromString(String date) {
return Date.from(Instant.from(FORMATTER.parse(date))); return Date.from(Instant.from(FORMATTER.parse(date)));
} }
/**
* Formats a date to a string.
*
* @param date The date to format.
* @return The formatted date.
*/
public String formatDate(Date date) {
return SIMPLE_FORMATTER.format(date.toInstant());
}
} }

View File

@ -0,0 +1,43 @@
package cc.fascinated.bat.common;
import lombok.NonNull;
/**
* @author Fascinated (fascinated7)
*/
public class DescriptionBuilder {
/**
* Where the description is stored
*/
private final StringBuilder builder = new StringBuilder();
public DescriptionBuilder(String title) {
if (title == null) {
return;
}
builder.append("**").append(title).append("**").append("\n");
}
@NonNull
public DescriptionBuilder appendLine(@NonNull String line, boolean arrow) {
builder.append(arrow ? "" : "").append(line).append("\n");
return this;
}
@NonNull
public DescriptionBuilder appendSubtitle(@NonNull String title) {
builder.append("**").append(title).append("**").append("\n");
return this;
}
@NonNull
public DescriptionBuilder emptyLine() {
builder.append("\n");
return this;
}
@NonNull
public String build() {
return builder.toString();
}
}

View File

@ -1,7 +1,9 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import cc.fascinated.bat.config.Config;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -42,4 +44,30 @@ public class EmbedUtils {
.setTimestamp(LocalDateTime.now()) .setTimestamp(LocalDateTime.now())
.setColor(Colors.SUCCESS); .setColor(Colors.SUCCESS);
} }
/**
* Builds a generic interaction error embed
*
* @param ex the exceptionk
* @return the embed builder
*/
public static EmbedBuilder genericInteractionError(Exception ex) {
TextChannel channel = ChannelUtils.getTextChannel(Config.INSTANCE.getLogsChannel());
EmbedBuilder embed = errorEmbed()
.setDescription("""
An error has occurred while processing %s interaction. If this issue persists, please contact the developers.
Cause: `%s`
```java
%s
```""".formatted(
channel == null ? "an" : "your",
ex.getStackTrace()[0].getClassName(),
ex.getLocalizedMessage()
));
if (channel != null) {
channel.sendMessageEmbeds(embed.build()).queue();
}
return embed;
}
} }

View File

@ -0,0 +1,24 @@
package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class EnumUtils {
/**
* Gets the name of the enum
*
* @param e the enum
* @return the name
*/
public static String getEnumName(Enum<?> e) {
String[] split = e.name().split("_");
StringBuilder builder = new StringBuilder();
for (String s : split) {
builder.append(s.substring(0, 1).toUpperCase()).append(s.substring(1).toLowerCase()).append(" ");
}
return builder.toString().trim();
}
}

View File

@ -0,0 +1,58 @@
package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
import java.awt.*;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class HexColorUtils {
/**
* Checks if the given string is a hex color
*
* @param hexColor the hex color to check
* @return if the given string is a hex color
*/
public static boolean isHexColor(String hexColor) {
return hexColor.matches("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
}
/**
* Converts a hex color to a Color object
*
* @param hex the hex color to convert
* @return the Color object
*/
public static Color hexToColor(String hex) {
if (hex == null || (!hex.matches("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"))) {
throw new IllegalArgumentException("Invalid hex color code");
}
// Remove the '#' character
hex = hex.substring(1);
// If the hex code is 3 characters long, expand it to 6 characters
if (hex.length() == 3) {
char r = hex.charAt(0);
char g = hex.charAt(1);
char b = hex.charAt(2);
hex = "" + r + r + g + g + b + b;
}
// Convert the hex string to an integer and create a Color object
int rgb = Integer.parseInt(hex, 16);
return new Color(rgb);
}
/**
* Converts a Color object to a hex color
*
* @param color the Color object to convert
* @return the hex color
*/
public static String colorToHex(Color color) {
return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
}
}

View File

@ -0,0 +1,105 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.service.InteractionService;
import lombok.Getter;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* @author Fascinated (fascinated7)
*/
@Getter
public class InteractionBuilder {
/**
* The button interactions
*/
private final Map<Button, Consumer<ButtonInteractionEvent>> buttonInteractions = new HashMap<>();
/**
* The select menu interactions
*/
private final Map<SelectOption, Consumer<StringSelectInteractionEvent>> selectMenuInteractions = new HashMap<>();
/**
* The select menu id
*/
private String selectMenuId;
public InteractionBuilder() {
InteractionService.INSTANCE.addInteractionBuilder(this);
}
/**
* Adds a button to the interaction builder
*
* @param display The display of the button
* @param onClick The consumer to run when the button is clicked
* @return - The interaction builder
*/
public InteractionBuilder addButton(String display, Consumer<ButtonInteractionEvent> onClick) {
String id = StringUtils.randomString(8);
this.buttonInteractions.put(Button.primary(id, display), onClick);
return this;
}
/**
* Adds a URL button to the interaction builder
*
* @param display The display of the button
* @param url The url to open when the button is clicked
* @return - The interaction builder
*/
public InteractionBuilder addUrlButton(String display, String url, Emoji emoji) {
this.buttonInteractions.put(Button.link(url, display).withEmoji(emoji), event -> {});
return this;
}
/**
* Adds a string selection to the interaction builder
*
* @param display the name of the selection
* @param description the description of the selection
* @param emoji the emoji of the selection
* @param onClick the consumer to run when the selection is clicked
* @return the interaction builder
*/
public InteractionBuilder addStringSelect(String display, String description, Emoji emoji, Consumer<StringSelectInteractionEvent> onClick) {
String id = StringUtils.randomString(8);
this.selectMenuInteractions.put(SelectOption.of(display, id).withDescription(description).withEmoji(emoji), onClick);
return this;
}
/**
* Builds the interactions into an action row
*
* @return The action row
*/
public List<ActionRow> build() {
List<ActionRow> components = new ArrayList<>();
if (!this.getButtonInteractions().isEmpty()) {
List<Button> buttons = new ArrayList<>(this.getButtonInteractions().keySet());
components.add(ActionRow.of(buttons));
}
if (!this.getSelectMenuInteractions().isEmpty()) {
List<SelectOption> options = new ArrayList<>(this.getSelectMenuInteractions().keySet());
String id = StringUtils.randomString(8);
this.selectMenuId = id;
components.add(ActionRow.of(StringSelectMenu.create(id)
.addOptions(options)
.build()));
}
return components;
}
}

View File

@ -0,0 +1,21 @@
package cc.fascinated.bat.common;
/**
* @author Fascinated (fascinated7)
*/
public class LongUtils {
/**
* Checks if a string is a long
*
* @param string the string to check
* @return if the string is a long
*/
public static boolean isLong(String string) {
try {
Long.parseLong(string);
return true;
} catch (NumberFormatException exception) {
return false;
}
}
}

View File

@ -0,0 +1,62 @@
package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public final class MathUtils {
/**
* Format a number to a specific amount of decimal places.
*
* @param number the number to format
* @param additional the additional decimal places to format
* @return the formatted number
*/
public static double format(double number, int additional) {
return Double.parseDouble(
new DecimalFormat("#.#" + "#".repeat(Math.max(0, additional - 1)),
new DecimalFormatSymbols()
).format(number)
);
}
/**
* Clamps a value between a minimum and maximum.
*
* @param value The value to clamp.
* @param min The minimum value.
* @param max The maximum value.
* @return The clamped value.
*/
public static double clamp(double value, double min, double max) {
return Math.max(min, Math.min(max, value));
}
/**
* Linearly interpolates between two values.
*
* @param a The first value.
* @param b The second value.
* @param t The interpolation value.
* @return The interpolated value.
*/
public static double lerp(double a, double b, double t) {
return a + t * (b - a);
}
/**
* Generates a random number between a minimum and maximum.
*
* @param min The minimum value.
* @param max The maximum value.
* @return The random value.
*/
public static double random(double min, double max) {
return Math.random() * (max - min) + min;
}
}

View File

@ -0,0 +1,42 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import java.util.Comparator;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class MemberUtils {
/**
* Checks if a user has permission to edit another user
*
* @param guild the guild to check
* @param user the user to check
* @return if the user has permission to edit another user
*/
public static boolean hasPermissionToEdit(BatGuild guild, BatUser user) {
Member botUser = guild.getDiscordGuild().getSelfMember();
Member member = guild.getDiscordGuild().getMemberById(user.getId());
if (member == null) {
return false;
}
return botUser.canInteract(member) && getHighestRole(botUser).getPosition() > getHighestRole(member).getPosition();
}
/**
* Gets the highest role of a member
*
* @param member the member to get the highest role of
* @return the highest role of the member
*/
public static Role getHighestRole(Member member) {
return member.getRoles().stream().max(Comparator.comparingInt(Role::getPosition)).orElse(null);
}
}

View File

@ -0,0 +1,86 @@
package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
import java.text.DecimalFormat;
import java.util.Locale;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class NumberFormatter {
/**
* The suffixes for the numbers
*/
private static final String[] SUFFIXES = new String[] { "K", "M", "B", "T", "Q", "QT", "S", "SP", "O", "N", "D", "UD", "DD", "TD" };
private static final DecimalFormat FORMAT = new DecimalFormat("#,##0.##");
/**
* Format the provided double
*
* @param input the value to format
* @return the formatted double, in the format of xx.xx[suffix]
*/
public static String format(double input) {
if (Double.isNaN(input)) {
return "ERROR";
}
if (Double.isInfinite(input) || input == Double.MAX_VALUE) {
return "";
}
if (1000 > input) {
return FORMAT.format(input);
}
double power = (int) Math.log10(input);
int index = (int) Math.floor(power / 3) - 1;
double factor = input / Math.pow(10, 3 + index * 3);
if (index >= SUFFIXES.length) {
return "ERROR";
}
return FORMAT.format(factor) + SUFFIXES[index];
}
/**
* Format the provided double with commas
*
* @param input the value to format
* @return the formatted double, in the format of xx,xxx,xxx
*/
public static String simpleFormat(double input) {
return FORMAT.format(input);
}
/**
* Turns a provided string into a double, for example 1M -> 1000000.00
* Accepts decimal and negative values and is not case-sensitive
*
* @param input the string to convert
* @return the value the string represents
*/
public static double fromString(String input) {
if ((input = input.trim()).isEmpty()) {
return -1D;
}
try {
double value = Double.parseDouble(input); // parse pure numbers
if (Double.isNaN(value) || Double.isInfinite(value)) {
return -1;
}
return value;
} catch (NumberFormatException ignored) {
input = input.toUpperCase(Locale.UK);
for (int i = SUFFIXES.length - 1; i > 0; i--) {
String suffix = SUFFIXES[i];
if (!input.endsWith(suffix)) {
continue;
}
String amount = input.substring(0, input.length() - suffix.length());
if (!amount.isEmpty()) {
return Double.parseDouble(amount) * Math.pow(10, 3 + i * 3);
}
}
}
return -1;
}
}

View File

@ -1,27 +1,20 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
import java.text.NumberFormat;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@UtilityClass
public class NumberUtils { public class NumberUtils {
/** /**
* Formats a number with commas. * Parses a string to an integer
* <p>
* Example: 1000 -> 1,000 | Example: 1000.5 -> 1,000.5
* </p>
* *
* @param number the number to format * @param input the string to parse
* @return the formatted number * @return the parsed integer, or -1 if invalid
*/ */
public static String formatNumberCommas(double number) { public static int parseInt(String input) {
NumberFormat format = NumberFormat.getNumberInstance(); try {
format.setGroupingUsed(true); return Integer.parseInt(input);
format.setMaximumFractionDigits(2); } catch (NumberFormatException exception) {
return format.format(number); return -1;
}
} }
} }

View File

@ -0,0 +1,41 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.BatApplication;
import cc.fascinated.bat.common.model.token.paste.PasteUploadToken;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
@Log4j2(topic = "PasteUtils")
public class PasteUtils {
private static final String PASTE_URL = "https://paste.fascinated.cc/";
private static final String PASTE_UPLOAD_URL = PASTE_URL + "api/upload?expires=" + 60 * 60 * 24 * 30; // 30 days
private static final HttpClient httpClient = HttpClient.newHttpClient();
/**
* Uploads a paste to the paste server
*
* @param content the content of the paste
* @return the paste upload token
*/
@SneakyThrows
public static PasteUploadToken uploadPaste(String content) {
HttpResponse<String> response = httpClient.send(HttpRequest.newBuilder()
.uri(URI.create(PASTE_UPLOAD_URL))
.POST(HttpRequest.BodyPublishers.ofString(content))
.build(), HttpResponse.BodyHandlers.ofString());
PasteUploadToken paste = BatApplication.GSON.fromJson(response.body(), PasteUploadToken.class);
paste.setUrl(PASTE_URL + paste.getKey());
log.info("Created paste with key \"{}\" ({})", paste.getKey(), paste.getUrl());
return paste;
}
}

View File

@ -1,24 +0,0 @@
package cc.fascinated.bat.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter @Setter
public abstract class Profile {
/**
* The key of the profile.
*/
private String profileKey;
public Profile() {}
/**
* Resets the profile
*/
public abstract void reset();
}

View File

@ -1,6 +1,11 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import cc.fascinated.bat.BatApplication;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -9,33 +14,40 @@ import java.util.Map;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Getter @Getter
public class ProfileHolder { public abstract class ProfileHolder {
private static final Logger log = LoggerFactory.getLogger(ProfileHolder.class);
/** /**
* The profiles for the holder * The profiles for the holder
*/ */
private Map<String, Profile> profiles; private final Map<String, Serializable> profiles = new HashMap<>();
/** /**
* Gets a profile for the holder * Gets a profile for the holder
* *
* @param clazz The class of the profile * @param clazz The class of the profile
* @param <T> The type of the profile * @param <T> The type of the profile
* @return The profile * @return The profile
*/ */
public <T extends Profile> T getProfile(Class<?> clazz) { public abstract <T extends Serializable> T getProfile(Class<T> clazz);
if (profiles == null) {
profiles = new HashMap<>();
}
Profile profile = profiles.values().stream().filter(p -> p.getClass().equals(clazz)).findFirst().orElse(null); /**
* Gets the profiles for the holder
* using the provided document
*
* @return the profiles
*/
@SneakyThrows
protected <T extends Serializable> T getProfileFromDocument(Class<T> clazz, Document document) {
Serializable profile = getProfiles().get(clazz.getSimpleName());
if (profile == null) { if (profile == null) {
try { T newProfile = clazz.cast(clazz.getDeclaredConstructors()[0].newInstance());
profile = (Profile) clazz.newInstance(); Document profiles = document.get("profiles", new org.bson.Document());
profiles.put(profile.getProfileKey(), profile); Document profileDocument = (Document) profiles.get(clazz.getSimpleName());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace(); newProfile.load(profileDocument == null ? new Document() : profileDocument, BatApplication.GSON);
} getProfiles().put(clazz.getSimpleName(), newProfile);
return newProfile;
} }
return (T) profile; return clazz.cast(profile);
} }
} }

View File

@ -1,22 +1,84 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.common.model.BatGuild;
import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import java.awt.*;
import java.util.EnumSet;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@UtilityClass
public class RoleUtils { public class RoleUtils {
/** /**
* Checks if a member has permission to give the role to another member * Checks if a member has permission to give the role to another member
* *
* @param guild the guild to check * @param guild the guild to check
* @param member the member to check * @param member the member to check
* @param role the role to check * @param role the role to check
* @return if the member has permission to give the role * @return if the member has permission to give the role
*/ */
public static boolean hasPermissionToGiveRole(BatGuild guild, Member member, Role role) { public static boolean hasPermissionToGiveRole(BatGuild guild, Member member, Role role) {
return member.getRoles().stream().anyMatch(r -> r.getPosition() > role.getPosition()); return member.getRoles().stream().anyMatch(r -> r.getPosition() > role.getPosition());
} }
/**
* Checks if a member has a higher role than the specified role
*
* @param member the member to check
* @param targetMember the member to check against
* @return if the member has a higher role
*/
public static boolean hasHigherRole(Member member, Member targetMember) {
return member.getRoles().stream().anyMatch(r -> targetMember.getRoles().stream().anyMatch(tr -> tr.getPosition() < r.getPosition()));
}
/**
* Gets the formatted permissions of a role
*
* @param role the role to get the formatted permissions of
* @return the formatted permissions
*/
public String getFormattedPermissions(Role role) {
StringBuilder formattedPermissions = new StringBuilder();
EnumSet<Permission> permissions = role.getPermissions();
if (permissions.isEmpty()) {
return "`None`";
}
for (Permission permission : permissions) {
formattedPermissions.append("`").append(permission.getName()).append("`, ");
}
return formattedPermissions.substring(0, formattedPermissions.length() - 2);
}
/**
* Gets the formatted color of a role
*
* @param color the color string to get the formatted color of
* @return the formatted color
*/
public String getFormattedColor(Color color) {
if (color == null) {
return "Default";
}
String colorHex = HexColorUtils.colorToHex(color);
return "`%s` *[(view)](%s)*".formatted(
colorHex,
"https://www.colorhexa.com/%s".formatted(colorHex.substring(1))
);
}
/**
* Gets the formatted color of a role
*
* @param role the role to get the formatted icon of
* @return the formatted icon
*/
public String getFormattedColor(Role role) {
return getFormattedColor(role.getColor());
}
} }

View File

@ -0,0 +1,36 @@
package cc.fascinated.bat.common;
import com.google.gson.Gson;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.bson.Document;
/**
* @author Fascinated (fascinated7)
*/
@Setter
@Getter
@NoArgsConstructor
public abstract class Serializable {
/**
* Load data from the provided document into this profile
*
* @param document the document to load data from
* @param gson the GSON instance to use
*/
public abstract void load(Document document, Gson gson);
/**
* Serialize this profile into a Bson document
*
* @param gson the GSON instance to use
* @return the serialized document
*/
public abstract Document serialize(Gson gson);
/**
* Resets the profile to its default state
*/
public abstract void reset();
}

View File

@ -0,0 +1,54 @@
package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class StringUtils {
/**
* Generates a random string
*
* @param length the length of the string
* @return the random string
*/
public static String randomString(int length) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < length; i++) {
stringBuilder.append((char) (Math.random() * 26 + 'a'));
}
return stringBuilder.toString();
}
/**
* Escapes meta characters in a string
*
* @param inputString the input string
* @return the string with escaped meta characters
*/
public static String escapeMetaCharacters(String inputString){
final String[] metaCharacters = {"\\","^","$","{","}","[","]","(",")",".","*","+","?","|","<",">","-","&","%"};
for (String metaCharacter : metaCharacters) {
if (inputString.contains(metaCharacter)) {
inputString = inputString.replace(metaCharacter, "\\" + metaCharacter);
}
}
return inputString;
}
/**
* Formats bytes into a human-readable format
*
* @param bytes the bytes
* @return the formatted bytes
*/
public static String formatBytes(long bytes) {
int unit = 1024;
if (bytes < unit) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(unit));
char pre = "KMGTPE".charAt(exp-1);
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
}

View File

@ -1,6 +1,5 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import cc.fascinated.bat.service.DiscordService;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
/** /**
@ -18,7 +17,7 @@ public class TextChannelUtils {
if (id == null) { if (id == null) {
return false; return false;
} }
return DiscordService.JDA.getTextChannelById(id) != null; return ChannelUtils.getTextChannel(id) != null;
} }
/** /**

View File

@ -0,0 +1,166 @@
package cc.fascinated.bat.common;
import lombok.*;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public final class TimeUtils {
/**
* Format a time in millis to a readable time format.
*
* @param millis the millis to format
* @return the formatted time
*/
public static String format(long millis) {
return format(millis, BatTimeFormat.FIT);
}
/**
* Format a time in millis to a readable time format.
*
* @param millis the millis to format
* @param timeUnit the time unit to format the millis to
* @return the formatted time
*/
public static String format(long millis, BatTimeFormat timeUnit) {
return format(millis, timeUnit, false);
}
/**
* Format a time in millis to a readable time format.
*
* @param millis the millis to format
* @param timeUnit the time unit to format the millis to
* @param compact whether to use a compact display
* @return the formatted time
*/
public static String format(long millis, BatTimeFormat timeUnit, boolean compact) {
return format(millis, timeUnit, true, compact);
}
/**
* Format a time in millis to a readable time format.
*
* @param millis the millis to format
* @param timeUnit the time unit to format the millis to
* @param decimals whether to include decimals
* @param compact whether to use a compact display
* @return the formatted time
*/
public static String format(long millis, BatTimeFormat timeUnit, boolean decimals, boolean compact) {
if (millis == -1L) { // Format permanent
return "Perm" + (compact ? "" : "anent");
}
// Format the time to the best fitting time unit
if (timeUnit == BatTimeFormat.FIT) {
for (BatTimeFormat otherTimeUnit : BatTimeFormat.VALUES) {
if (otherTimeUnit != BatTimeFormat.FIT && millis >= otherTimeUnit.getMillis()) {
timeUnit = otherTimeUnit;
break;
}
}
}
double time = MathUtils.format((double) millis / timeUnit.getMillis(), 1); // Format the time
if (!decimals) { // Remove decimals
time = (int) time;
}
String formatted = time + (compact ? timeUnit.getSuffix() : " " + timeUnit.getDisplay()); // Append the time unit
if (time != 1.0 && !compact) { // Pluralize the time unit
formatted += "s";
}
return formatted;
}
/**
* Convert the given input into a time in millis.
* <p>
* E.g: 1d, 1h, 1d1h, etc
* </p>
*
* @param input the input to parse
* @return the time in millis
*/
public static long fromString(String input) {
Matcher matcher = BatTimeFormat.SUFFIX_PATTERN.matcher(input); // Match the given input
long millis = 0; // The total millis
// Match corresponding suffixes and add up the total millis
while (matcher.find()) {
int amount = Integer.parseInt(matcher.group(1)); // The amount of time to add
String suffix = matcher.group(2); // The unit suffix
BatTimeFormat timeUnit = BatTimeFormat.fromSuffix(suffix); // The time unit to add
if (timeUnit != null) { // Increment the total millis
millis += amount * timeUnit.getMillis();
}
}
return millis;
}
/**
* Represents a unit of time.
*/
@NoArgsConstructor
@AllArgsConstructor
@Getter(AccessLevel.PRIVATE)
@ToString
public enum BatTimeFormat {
FIT,
YEARS("Year", "y", TimeUnit.DAYS.toMillis(365L)),
MONTHS("Month", "mo", TimeUnit.DAYS.toMillis(30L)),
WEEKS("Week", "w", TimeUnit.DAYS.toMillis(7L)),
DAYS("Day", "d", TimeUnit.DAYS.toMillis(1L)),
HOURS("Hour", "h", TimeUnit.HOURS.toMillis(1L)),
MINUTES("Minute", "m", TimeUnit.MINUTES.toMillis(1L)),
SECONDS("Second", "s", TimeUnit.SECONDS.toMillis(1L)),
MILLISECONDS("Millisecond", "ms", 1L);
/**
* Our cached unit values.
*/
public static final BatTimeFormat[] VALUES = values();
/**
* Our cached suffix pattern.
*/
public static final Pattern SUFFIX_PATTERN = Pattern.compile("(\\d+)(mo|ms|[ywdhms])");
/**
* The display of this time unit.
*/
private String display;
/**
* The suffix of this time unit.
*/
private String suffix;
/**
* The amount of millis in this time unit.
*/
private long millis;
/**
* Get the time unit with the given suffix.
*
* @param suffix the time unit suffix
* @return the time unit, null if not found
*/
@Nullable
public static BatTimeFormat fromSuffix(String suffix) {
for (BatTimeFormat unit : VALUES) {
if (unit != FIT && unit.getSuffix().equals(suffix)) {
return unit;
}
}
return null;
}
}
}

View File

@ -14,7 +14,7 @@ public class TimerUtils {
* Runs a repeating task on a schedule * Runs a repeating task on a schedule
* *
* @param runnable the task to run * @param runnable the task to run
* @param delay the delay before the task runs * @param delay the delay before the task runs
*/ */
public static void scheduleRepeating(Runnable runnable, long delay, long period) { public static void scheduleRepeating(Runnable runnable, long delay, long period) {
new Timer().scheduleAtFixedRate(new TimerTask() { new Timer().scheduleAtFixedRate(new TimerTask() {

View File

@ -0,0 +1,45 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.service.DiscordService;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.User;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
@Log4j2(topic = "UserUtils")
public class UserUtils {
/**
* Gets the user with the given id
*
* @param id the id of the user
* @param retries the amount of retries
* @return the user with the given id
*/
private static User getUser(String id, int retries) {
if (retries >= 10) {
log.error("Failed to find user \"{}\" after {} retries.", id, retries);
return null;
}
User user = DiscordService.JDA.retrieveUserById(id).complete();
if (user == null) {
return getUser(id, retries + 1);
}
if (retries >= 5) {
log.info("Found user \"{}\" after {} retries.", user.getGlobalName(), retries);
}
return user;
}
/**
* Gets the user with the given id
*
* @param id the id of the user
* @return the user with the given id
*/
public static User getUser(String id) {
return getUser(id, 0);
}
}

View File

@ -27,23 +27,30 @@ public class WebRequest {
* Gets a response from the given URL. * Gets a response from the given URL.
* *
* @param url the url * @param url the url
* @return the response
* @param <T> the type of the response * @param <T> the type of the response
* @return the response
*/ */
public static <T> T getAsEntity(String url, Class<T> clazz) throws RateLimitException { public static <T> T getAsEntity(String url, Class<T> clazz) throws RateLimitException {
ResponseEntity<T> responseEntity = CLIENT.get() try {
.uri(url) ResponseEntity<T> responseEntity = CLIENT.get()
.retrieve() .uri(url)
.onStatus(HttpStatusCode::isError, (request, response) -> {}) // Don't throw exceptions on error .retrieve()
.toEntity(clazz); .onStatus(HttpStatusCode::isError, (request, response) -> {
}) // Don't throw exceptions on error
.toEntity(clazz);
if (responseEntity.getStatusCode().isError()) { if (responseEntity.getStatusCode().isError()) {
return null;
}
if (responseEntity.getStatusCode().isSameCodeAs(HttpStatus.TOO_MANY_REQUESTS)) {
throw new RateLimitException("Rate limit reached");
}
return responseEntity.getBody();
} catch (RateLimitException e) {
throw e;
} catch (Exception e) {
return null; return null;
} }
if (responseEntity.getStatusCode().isSameCodeAs(HttpStatus.TOO_MANY_REQUESTS)) {
throw new RateLimitException("Rate limit reached");
}
return responseEntity.getBody();
} }
/** /**
@ -56,7 +63,8 @@ public class WebRequest {
return CLIENT.get() return CLIENT.get()
.uri(url) .uri(url)
.retrieve() .retrieve()
.onStatus(HttpStatusCode::isError, (request, response) -> {}) // Don't throw exceptions on error .onStatus(HttpStatusCode::isError, (request, response) -> {
}) // Don't throw exceptions on error
.toEntity(clazz); .toEntity(clazz);
} }
@ -70,7 +78,8 @@ public class WebRequest {
return CLIENT.head() return CLIENT.head()
.uri(url) .uri(url)
.retrieve() .retrieve()
.onStatus(HttpStatusCode::isError, (request, response) -> {}) // Don't throw exceptions on error .onStatus(HttpStatusCode::isError, (request, response) -> {
}) // Don't throw exceptions on error
.toEntity(clazz); .toEntity(clazz);
} }
} }

View File

@ -0,0 +1,248 @@
package cc.fascinated.bat.common.command;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.*;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.IntegrationType;
import net.dv8tion.jda.api.interactions.InteractionContextType;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
/**
* @author Braydon
*/
@Getter
public abstract class BatCommand {
/**
* The info of this command.
*/
@NonNull
private final InternalCommandInfo info;
/**
* The feature this command belongs to.
*/
@Setter
private Feature feature;
/**
* The snowflake of this command, set when
* this command is registered with Discord.
*/
@Setter
private long snowflake;
/**
* The sub commands of this command, if any.
*/
private final Map<String, BatCommand> subCommands = Collections.synchronizedMap(new HashMap<>());
/**
* The internal data for this command.
*/
@Setter(AccessLevel.PRIVATE)
private CommandDataImpl commandData;
/**
* The internal subcommand data for this command.
*/
@Setter(AccessLevel.PRIVATE)
private SubcommandData subcommandData;
public BatCommand() {
if (!getClass().isAnnotationPresent(CommandInfo.class)) {
throw new IllegalStateException("Missing @CommandInfo annotation in " + getClass().getSimpleName());
}
info = new InternalCommandInfo(getClass().getAnnotation(CommandInfo.class));
commandData = new CommandDataImpl(info.getName(), info.getDescription())
// .setDefaultPermissions(DefaultMemberPermissions.enabledFor(info.getPermissions()))
.setIntegrationTypes(info.isUserInstall() ?
EnumSet.of(IntegrationType.USER_INSTALL) :
EnumSet.of(IntegrationType.GUILD_INSTALL)
).setContexts(info.isGuildOnly() ?
EnumSet.of(InteractionContextType.GUILD) :
EnumSet.of(InteractionContextType.GUILD, InteractionContextType.BOT_DM, InteractionContextType.PRIVATE_CHANNEL)
);
}
/**
* Fired when this command is executed.
*
* @param guild the guild the command was executed in, if any
* @param user the user who executed the command
* @param channel the channel the command was executed in
* @param member the member who executed the command, null if not a guild
* @param commandMessage the message that invoked this command, if any
* @param arguments the arguments of the command, if any
* @param event the event that invoked this command, if any
*/
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member,
Message commandMessage, String[] arguments, SlashCommandInteraction event) {
}
/**
* Register the given sub commands.
*
* @param commands the commands to register
*/
protected final void addSubCommands(@NonNull BatCommand... commands) {
for (BatCommand command : commands) {
// Copy info from the parent command
if (command.getInfo().getCategory() != info.getCategory()) {
command.getInfo().setCategory(info.getCategory());
}
if (command.getInfo().getPermissions().length == 0) {
command.getInfo().setPermissions(info.getPermissions());
}
if (command.getInfo().isGuildOnly() != info.isGuildOnly()) {
command.getInfo().setGuildOnly(info.isGuildOnly());
}
if (command.getInfo().isBotOwnerOnly() != info.isBotOwnerOnly()) {
command.getInfo().setBotOwnerOnly(info.isBotOwnerOnly());
}
command.setSubcommandData(new SubcommandData(command.getInfo().getName(), command.getInfo().getDescription()));
for (OptionData option : command.getCommandData().getOptions()) {
command.getSubcommandData().addOptions(option);
}
commandData.addSubcommands(command.getSubcommandData());
subCommands.put(command.getInfo().getName(), command);
}
}
/**
* Add the given options
* to this command.
*
* @param options the options to add
*/
protected final void addOptions(OptionData... options) {
commandData.addOptions(options);
}
/**
* Get the sub command by its class.
*
* @param clazz the class of the sub command
* @return the sub command
*/
public BatCommand getSubCommand(Class<? extends BatCommand> clazz) {
for (Map.Entry<String, BatCommand> entry : subCommands.entrySet()) {
if (entry.getValue().getClass().equals(clazz)) {
return entry.getValue();
}
}
return null;
}
/**
* Reply to the message or interaction with the given contents.
*
* @param message the message to reply to, null if interaction
* @param interaction the interaction to reply to, null if message
* @param contents the contents to reply with
*/
public void replyMessage(Message message, SlashCommandInteraction interaction, String contents) {
if (message != null) {
message.reply(contents).queue();
} else {
interaction.reply(contents).queue();
}
}
/**
* Reply to the message or interaction with the given embed.
*
* @param message the message to reply to, null if interaction
* @param interaction the interaction to reply to, null if message
* @param builder the embed builder to reply with
*/
public void replyEmbed(Message message, SlashCommandInteraction interaction, EmbedBuilder builder) {
if (message != null) {
message.replyEmbeds(builder.build()).queue();
} else {
interaction.replyEmbeds(builder.build()).queue();
}
}
/**
* Get the argument from the command.
*
* @param option the option to get from the slash command
* @param arguments the arguments of the invoked command
* @param argumentIndex the index of the argument in the command
* @param concatenateLeftover whether to concatenate the leftover arguments
* @param event the event that invoked the command
* @return the argument
*/
public Argument getArgument(String option, String[] arguments, int argumentIndex, boolean concatenateLeftover, SlashCommandInteraction event) {
return new Argument(option, arguments, argumentIndex, concatenateLeftover, event);
}
@AllArgsConstructor
public static class Argument {
/**
* The option to get from the slash command.
*/
private final String option;
/**
* The arguments of the invoked command.
*/
private final String[] arguments;
/**
* The index of the argument in the command.
*/
private final int argumentIndex;
/**
* Whether to concatenate the leftover arguments.
*/
private final boolean concatenateLeftover;
/**
* The event that invoked the command.
*/
private final SlashCommandInteraction event;
/**
* Get the argument from the command.
*
* @return the argument
*/
public String getAsString() {
if (event != null) { // Get the option from the event
OptionMapping option = event.getOption(this.option);
if (option == null) {
return null;
}
return option.getAsString();
}
if (arguments.length < argumentIndex) { // Check if the argument index is out of bounds
return null;
}
if (concatenateLeftover) { // Concatenate the leftover arguments
StringBuilder builder = new StringBuilder();
for (int i = argumentIndex; i < arguments.length; i++) {
builder.append(arguments[i]).append(" ");
}
return builder.toString().trim();
}
return arguments[argumentIndex]; // Get the argument at the index
}
}
}

View File

@ -0,0 +1,59 @@
package cc.fascinated.bat.common.command;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
public enum Category {
GENERAL(Emoji.fromUnicode("U+2699"), "General"),
FUN(Emoji.fromFormatted("U+1F973"), "Fun"),
SERVER(Emoji.fromFormatted("U+1F5A5"), "Server"),
MODERATION(Emoji.fromFormatted("U+1F6E0"), "Moderation"),
UTILITY(Emoji.fromFormatted("U+1F6E0"), "Utility"),
MEDIA(Emoji.fromFormatted("U+1F3A5"), "Media"),
BEAT_SABER(Emoji.fromFormatted("U+1FA84"), "Beat Saber");
/**
* The emoji for the category
*/
@NonNull private final Emoji emoji;
/**
* The name of the category
*/
@NonNull private final String name;
/**
* Gets a category by its name
*
* @param name the name of the category
* @return the category
*/
public static Category getByName(String name) {
for (Category category : Category.values()) {
if (category.getName().equalsIgnoreCase(name)) {
return category;
}
}
return null;
}
/**
* Gets the categories sorted by their name
*
* @return the sorted categories
*/
public static List<Category> getSortedByName() {
return Arrays.stream(Category.values()).sorted(Comparator.comparing(Category::getName)).toList();
}
}

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