Compare commits

..

360 Commits

Author SHA1 Message Date
Lee
7f935da4b4 add nerddetector command 2025-01-15 18:03:42 +00:00
Lee
f3aad3925a update ppize 2025-01-09 21:46:16 +00:00
Lee
ae741e1f13 oops 2025-01-09 21:40:11 +00:00
Lee
b78ef6e96c oops 2025-01-06 05:20:05 +00:00
Lee
d3661127cf update help cmd 2025-01-06 05:16:01 +00:00
Lee
fc1216be25 add how gay command 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 2024-12-27 13:04:57 +00:00
Lee
5f099a97f0 remove some things and bump depends 2024-12-27 12:50:13 +00:00
577c895169 Merge remote-tracking branch 'origin/master' 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' () from renovate/spring-boot into master
Reviewed-on: 
2024-11-02 23:16:31 +00:00
Lee
a4acdc2ec1 Merge pull request 'Update dependency org.apache.httpcomponents.client5:httpclient5 to v5.4.1' () from renovate/org.apache.httpcomponents.client5-httpclient5-5.x into master
Reviewed-on: 
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' () from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Reviewed-on: 
2024-11-02 23:16:13 +00:00
Lee
53730a82cf Merge pull request 'Update dependency io.mongock:mongock-bom to v5.5.0' () from renovate/io.mongock-mongock-bom-5.x into master
Reviewed-on: 
2024-11-02 23:16:06 +00:00
Lee
6f4a787ccb Merge pull request 'Update dependency io.mongock:mongock-springboot-v3 to v5.5.0' () from renovate/io.mongock-mongock-springboot-v3-5.x into master
Reviewed-on: 
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' () from renovate/io.mongock-mongodb-springdata-v4-driver-5.x into master
Reviewed-on: 
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' () from renovate/eclipse-temurin-17.x into master
Reviewed-on: 
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 2024-09-02 08:25:19 +00:00
Lee
eb3d8eea29 Merge pull request 'Update dependency maven to v3.9.9' () from renovate/maven-3.x into master
Reviewed-on: 
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' () from renovate/spring-boot into master
Reviewed-on: 
2024-09-01 01:50:49 +00:00
892b85ccb4 update msg log paste 2024-08-25 01:58:07 +01:00
f291344c45 oops 2024-08-22 20:47:11 +01:00
df9b0e2604 oopsie 2024-08-22 20:31:22 +01:00
2695a2994a add coin flip command 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' 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' () from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Reviewed-on: 
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' () from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Reviewed-on: 
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' () from renovate/org.springframework.boot-spring-boot-starter-parent-3.x into master
Reviewed-on: 
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' () from renovate/eclipse-temurin-17.x into master
Reviewed-on: 
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' () from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Reviewed-on: 
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' () from renovate/se.michaelthelin.spotify-spotify-web-api-java-8.x into master
Reviewed-on: 
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 2024-07-16 14:40:53 +01:00
2b2e10d994 oopsie 2024-07-16 14:37:46 +01:00
8526036044 add scoresaber score summary 2024-07-16 14:35:02 +01:00
f5c31195da fix log title 2024-07-10 10:42:19 +01:00
d6175b3f92 oops 2024-07-10 06:00:40 +01:00
96e7518f72 fix interactions 2024-07-09 23:02:12 +01:00
f1bc2b2aaa add user validation to moderation and to user lookup 2024-07-09 20:34:56 +01:00
4ec65c8d6e validate id when getting user 2024-07-09 20:24:50 +01:00
38465f544d fix past tense 2024-07-09 20:19:21 +01:00
a3f4e2b918 finish moderation 2024-07-09 19:46:48 +01:00
bc57834366 fix spelling 2024-07-07 07:02:49 +01:00
87a56700ec add stat channels feature 2024-07-07 06:50:25 +01:00
6d98977198 Merge remote-tracking branch 'origin/master' 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' () from renovate/org.apache.maven.plugins-maven-shade-plugin-3.x into master
Reviewed-on: 
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' () from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Reviewed-on: 
2024-07-07 02:35:24 +00:00
898a39e99c change some command categories 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' 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' () from renovate/io.mongock-mongock-bom-5.x into master
Reviewed-on: 
2024-07-07 01:00:59 +00:00
Lee
2d8779d8e0 Merge pull request 'Update dependency com.google.code.gson:gson to v2.11.0' () from renovate/com.google.code.gson-gson-2.x into master
Reviewed-on: 
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' () from renovate/org.projectlombok-lombok-1.x into master
Reviewed-on: 
2024-07-07 00:44:25 +00:00
Lee
9be60fee4c Merge pull request 'Update dependency maven to v3.9.8' () from renovate/maven-3.x into master
Reviewed-on: 
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 2024-07-07 00:42:09 +00:00
25dd6cb0dc silly me 2024-07-07 01:34:46 +01:00
217b284f14 move lookup to a sub command and add mem usage to the bot stats command 2024-07-07 01:34:27 +01:00
843bb34fb4 add paste command 2024-07-07 01:23:20 +01:00
8a01359f6e Merge remote-tracking branch 'origin/master' 2024-07-07 01:19:31 +01:00
8f85ce91da oopsie!!!!!!!!! 2024-07-07 01:12:22 +01:00
Lee
93f232805e revert 585d3e079390a9dd5f24d87499b0da27556ca617
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 2024-07-06 23:57:58 +00:00
585d3e0793 add version number to the embeds 2024-07-07 00:28:58 +01:00
b5ac5d6a9b only allow sniped messages to be sniped for 1 hour after deletion 2024-07-07 00:10:39 +01:00
d9dd175174 update bot invite url 2024-07-06 21:17:23 +01:00
7a1ffc0538 update cache expiration policy 2024-07-06 21:14:20 +01:00
04c4533c40 update command log 2024-07-06 20:08:26 +01:00
353962e569 enable spotify linking (even tho it doesn't work w/o me adding you rn) 2024-07-06 20:02:20 +01:00
d0ba6e72a4 fix pp size for not in guild 2024-07-06 19:41:16 +01:00
b63b25f8ab update pp size command 2024-07-06 19:37:26 +01:00
2627b80148 update pp size command 2024-07-06 19:36:27 +01:00
16bbda57f6 add ppsize command 2024-07-06 19:34:03 +01:00
e65b7b8232 Merge remote-tracking branch 'origin/master' 2024-07-06 17:26:37 +01:00
d90bdfe3b6 cleanup bot admin 2024-07-06 17:26:31 +01:00
8def168ef7 bob 2024-07-06 09:22:58 +01:00
9379ebb33d make the server watcher list command look better 2024-07-06 07:35:06 +01:00
bbbbce557b oopsie 2024-07-06 07:21:19 +01:00
6fef1d0092 check feature states 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 2024-07-06 06:21:05 +01:00
ee7e8b64c5 add minecraft server and player lookups 2024-07-06 05:34:50 +01:00
6de1e8b2b1 fix access token refreshing?!??! 2024-07-06 04:18:10 +01:00
98e9003129 update bot joining guild message 2024-07-06 04:14:06 +01:00
9d78432211 add leveling feature 2024-07-06 04:04:38 +01:00
60ce8df108 consistency 2024-07-06 02:11:02 +01:00
ab5cec535b update sync cmd title 2024-07-06 02:04:37 +01:00
ec9b0cc862 add auto role sync command 2024-07-06 02:01:20 +01:00
d45cd48ff6 no need for this, oops 2024-07-06 00:42:14 +01:00
3126935057 add checks to the skip command 2024-07-06 00:41:31 +01:00
27244d0d98 use expiring map for users 2024-07-06 00:34:53 +01:00
de1da2391d add currently playing cache to spotify 2024-07-06 00:31:48 +01:00
9395ae73b9 use Sentry hints 2024-07-06 00:24:06 +01:00
fa932b7fc2 oops 2024-07-06 00:19:52 +01:00
bd340448a1 catch exceptions 2024-07-06 00:18:06 +01:00
45460f8b90 update check mark emoji 2024-07-06 00:10:31 +01:00
fbb292a591 oopsie 2024-07-06 00:05:52 +01:00
31aad2744c oopsie 2024-07-06 00:02:45 +01:00
35b8fff808 fix npe 2024-07-06 00:00:53 +01:00
8cc465e53d clean spotify commands 2024-07-05 23:59:27 +01:00
ec54d5427e update interaction error message 2024-07-05 23:35:41 +01:00
07c5a7358b fix help cmd 2024-07-05 23:24:36 +01:00
4aae21b594 update help cmd for running outside a guild 2024-07-05 23:21:28 +01:00
650556079b fix help not working without a guild 2024-07-05 23:15:29 +01:00
29d3fe3701 fix npe part 2 2024-07-05 23:13:12 +01:00
4cc48412f7 don't log as much when getting scores for SS 2024-07-05 23:02:27 +01:00
ccdeebdc1c fix npe 2024-07-05 22:58:32 +01:00
6dc801dda6 yes! 2024-07-05 22:38:14 +01:00
d01c7d4965 maybe fix this? 2024-07-05 22:35:29 +01:00
0e33179260 maybe fix this? 2024-07-05 22:33:00 +01:00
36d29caa7a change avatar url in the log 2024-07-05 22:29:25 +01:00
a605ab2dba fix some logging things 2024-07-05 21:47:29 +01:00
863206bb9f change log color for voice channel switching 2024-07-05 21:42:47 +01:00
a7af4108c6 switch voice channel log 2024-07-05 21:38:21 +01:00
fdd36d4385 fix markdown in the message breaking the log 2024-07-05 21:12:25 +01:00
0cfccc70d8 update log format for messages 2024-07-05 21:03:56 +01:00
2791d6328e update depend 2024-07-05 21:02:18 +01:00
b4f4a12da8 fix double interaction reply 2024-07-05 20:49:22 +01:00
bb81c098b2 update scoresaber me/user command to show raw per global 2024-07-05 20:44:43 +01:00
bef2b695f5 remove some debug and fix depends on for scoresaber service 2024-07-05 19:49:16 +01:00
d575e0ec9e fix for real this time 2024-07-05 19:46:06 +01:00
4f97e181a7 fix log error 2024-07-05 19:43:18 +01:00
b5b306cc6d fix welcome msg 2024-07-05 19:40:01 +01:00
5b917af1ca fix intent 2024-07-05 19:35:33 +01:00
0b09b8ba5f default disable all features and add bot join guild message 2024-07-05 19:33:10 +01:00
4e866895a0 impl counter feature 2024-07-05 19:07:32 +01:00
f6a23e4888 update role log embed 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 2024-07-04 18:04:15 +01:00
313f43172d who even cares about this 2024-07-04 18:00:44 +01:00
6efb042f14 fix role log color 2024-07-04 18:00:16 +01:00
9be80a0f7e nvm to this too 2024-07-04 17:56:56 +01:00
cd9563f77e nvm 2024-07-04 17:56:39 +01:00
0ba532a9b5 add channel position update log 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 2024-07-04 17:50:56 +01:00
8946a18b0b update log message 2024-07-04 17:45:39 +01:00
6ebfb4b289 fix log 2024-07-04 17:17:26 +01:00
35223c376a holy new events that are getting logged 2024-07-04 17:15:34 +01:00
f637faf0b6 yes 2024-07-04 15:59:16 +01:00
16e956d718 fix button interactions 2024-07-04 15:53:49 +01:00
ee62eae519 fix button interactions 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 2024-07-04 14:50:45 +00:00
c78033f409 fix cached footer 2024-07-04 15:44:05 +01:00
9a5609f40e update scoresaber command messages 2024-07-04 15:42:16 +01:00
7abdcd4d95 fix 2024-07-04 15:36:52 +01:00
92a0c23b49 yes 2024-07-04 15:32:24 +01:00
65ba1d91ac Update src/main/java/cc/fascinated/bat/command/InternalCommandInfo.java 2024-07-04 14:31:30 +00:00
4ae2e7566a ples bby gurl 2024-07-04 15:27:08 +01:00
5e17f655a4 ples bby gurl 2024-07-04 15:25:03 +01:00
11299b3ca7 fix? 2024-07-04 15:20:26 +01:00
d023e21575 help menu use proper button ids 2024-07-04 14:57:54 +01:00
d70a29d363 use a perm check for the list buttons 2024-07-04 14:43:15 +01:00
7bf468b470 fix invite log channel 2024-07-04 14:36:07 +01:00
0d9b6ed8f8 oops, cleanup imports 2024-07-04 14:21:30 +01:00
69adc56e4e re-impl help cmd and fix cmd categories 2024-07-04 14:21:02 +01:00
57d5dc0c42 Merge pull request 'lol' () from Rainnny/Bat:master into master
Reviewed-on: 
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' () from Rainnny/Bat:master into master
Reviewed-on: 
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 ????? 2024-07-04 12:05:37 +01:00
cbc28701c9 fix cmds?? 2024-07-04 12:02:12 +01:00
de6eabc1b7 user install commands 2024-07-04 11:58:54 +01:00
26fddc0493 pls fix 2024-07-04 11:53:25 +01:00
50796d00f0 MAYBE USER INSTALL COMMANDS PLEASE??~!?!?!?!?!?!?!!!?!?!!?!!?!?!?!!!?!??!??!?!?!?!?!?!?!?!?!?!?!?!?! 2024-07-04 11:46:54 +01:00
bf2e046718 jda is wank! 2024-07-04 11:16:25 +01:00
05bc0f0a41 jda is wank! 2024-07-04 11:12:42 +01:00
21e7ddf549 slowmode update logging 2024-07-04 11:00:32 +01:00
14ff692b87 add channel region changing logging 2024-07-04 10:49:21 +01:00
26b7b7158a add role color and icon update, and add channel nsfw and user limit update logging 2024-07-04 10:40:51 +01:00
626a9bd77d update log messages and add bitrate and topic change logs 2024-07-04 10:22:19 +01:00
06ded19492 fix new avatar being broken if they remove their avatar 2024-07-04 09:57:12 +01:00
6ba802912f add role create, delete, rename and update permissions logs 2024-07-04 09:56:06 +01:00
c284bb8a7f update the log remove command to be the same design as the set one 2024-07-04 09:41:21 +01:00
dbe6fb4762 add channel permission update logging 2024-07-04 09:36:40 +01:00
a0e0b82f40 add user lookup command 2024-07-04 09:15:26 +01:00
96e675a6be update msg 2024-07-04 07:53:03 +01:00
ed79a46af3 update format for messages in logging 2024-07-04 07:51:04 +01:00
ee138cb5a9 update format for messages in logging 2024-07-04 07:49:35 +01:00
45bfae281e update logging topics 2024-07-04 07:43:53 +01:00
52d4b65d92 update message 2024-07-04 07:09:01 +01:00
5b0f06efc2 add topics to logs 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 2024-07-04 07:03:48 +01:00
dc04a2f36a fix channel rename log 2024-07-04 07:01:46 +01:00
407ee6f1e9 log errors to a channel 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 2024-07-04 06:46:33 +01:00
c31ec09e0f make the log embeds more fancy 2024-07-04 06:44:55 +01:00
4148f6e05f check if the member is still boosting and update log set messages 2024-07-04 06:41:17 +01:00
0120fa24ef find users in a voice channel when starting to make logs more accurate 2024-07-04 06:24:51 +01:00
4a522c084d add emojis to the log category buttons and add a home button 2024-07-04 06:21:31 +01:00
188124991e fix for losing data... LOL 2024-07-04 05:54:13 +01:00
7f09c6d06c remove unneeded check 2024-07-04 05:49:47 +01:00
e873988c47 use pages for the log list command and tell the user about the list command 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 2024-07-04 05:25:19 +01:00
b6ba778ff2 fixes 2024-07-04 05:14:47 +01:00
cdd953351a add emoji logs and fix log channels that are set from vanishing 2024-07-04 05:10:35 +01:00
c1d775c9d0 maybe fix boost event?? 2024-07-04 04:43:53 +01:00
a4e3598986 update voice channel join/leave logs 2024-07-04 04:38:02 +01:00
8cdc2a853b auto log names 2024-07-04 04:34:20 +01:00
f940bd526c fix channel delete logging for non text channel channels 2024-07-04 04:32:14 +01:00
490080b17a add seperate events for boosting 2024-07-04 04:30:45 +01:00
595c61e789 impl boost logging???? 2024-07-04 04:28:42 +01:00
040c644ab1 use the util for getting channels 2024-07-04 03:52:33 +01:00
fa1f18e11b don't log every channel find 2024-07-04 03:42:56 +01:00
ece36f7f27 maybe fix loading log channels 2024-07-04 03:40:12 +01:00
3b3ea2b3cc maybe fix some NPEs when calling events 2024-07-04 03:33:06 +01:00
c98d8d7e26 log correct name in user creation 2024-07-04 03:17:07 +01:00
49beb864d2 use generic embed for interaction errors 2024-07-04 03:15:53 +01:00
750b8cbfea handle errors on interactions 2024-07-04 03:07:08 +01:00
2fcc1b66c1 update original message 2024-07-04 02:58:29 +01:00
040846ef0a fix shutdown db saving (fr this time) and fixed embed color parsing 2024-07-04 02:47:36 +01:00
96486bb3a1 fix shutdown db saving 2024-07-04 02:25:26 +01:00
78daf4531b log user name not id 2024-07-04 01:58:06 +01:00
a9e4626dfd fix tmdb command errors 2024-07-04 01:47:01 +01:00
dc7e44239f fix npe and maybe fix null users???????? 2024-07-04 01:33:24 +01:00
2349f10b35 fix message and fix category name 2024-07-04 01:17:21 +01:00
bd9f4ef971 fix non null 2024-07-04 01:11:26 +01:00
6698b7c656 Merge remote-tracking branch 'origin/master' 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' () from okNick/Bat:master into master
Reviewed-on: 
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 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?? 2024-07-03 23:23:23 +01:00
3878d3029b mention voice channel in the embed 2024-07-03 23:09:28 +01:00
831bc934b4 cleanup voice logs 2024-07-03 23:07:50 +01:00
938005f6d9 add null check 2024-07-03 23:02:42 +01:00
5959b814a7 use proper embed color for voice channel logs 2024-07-03 23:01:56 +01:00
5f75302f3a don't make accounts for bots 2024-07-03 22:59:05 +01:00
a7a7bc784b impl voice join and leave logging 2024-07-03 22:57:51 +01:00
2b4980fb10 change auto role log to include the guild 2024-07-03 22:18:51 +01:00
655662c6f8 fix embed color 2024-07-03 22:18:35 +01:00
642185f8c5 fix welcomer serialization 2024-07-03 22:12:56 +01:00
c2e447f416 fix welcomer placeholders and fix channel command 2024-07-03 22:10:35 +01:00
271a1cf88d fix npe 2024-07-03 22:04:40 +01:00
11e7ca4aa6 impl purge command 2024-07-03 21:43:29 +01:00
f6834db9cb add 8ball command 2024-07-03 20:19:21 +01:00
90aaf5422f fix some messages 2024-07-03 19:53:47 +01:00
e4183b4882 impl welcomer feature 2024-07-03 19:49:19 +01:00
f62a022ed5 logging messages 2024-07-03 16:51:36 +01:00
50b8b4b2c1 Merge remote-tracking branch 'origin/master' 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 2024-07-03 01:09:50 +00:00
f30697d1a6 fix null on old avatar in logs 2024-07-03 02:03:26 +01:00
83250d2c08 add check to see if the member is in the guild before logging some events 2024-07-03 01:59:53 +01:00
d7916ad24a oopsie, log after command was ran 2024-07-03 00:41:28 +01:00
295d673d06 fix cmd execution log 2024-07-03 00:40:50 +01:00
da06a01097 fix WebRequest#getAsEntity 2024-07-03 00:39:20 +01:00
6202aa6691 update member join log 2024-07-03 00:35:58 +01:00
82a87c79b2 add execution time for commands 2024-07-03 00:34:10 +01:00
e795d542b9 update max reminder time 2024-07-03 00:33:07 +01:00
162d7af46b fix max reminder length 2024-07-03 00:32:14 +01:00
821190a144 fix reminder message 2024-07-03 00:30:27 +01:00
cb35182c6a update reminder message 2024-07-03 00:29:50 +01:00
ac499898e3 fix 2024-07-03 00:23:46 +01:00
35596b720b maybe fix a NPE?? 2024-07-03 00:14:00 +01:00
048d2856f9 impl reminders 2024-07-03 00:10:02 +01:00
5f654f9ca6 update vote command 2024-07-02 21:41:13 +01:00
4540bdef99 add tos and privacy policy to the help cmd 2024-07-02 21:30:10 +01:00
eda4eb5973 temp tos and privacy policy 2024-07-02 21:17:37 +01:00
68a9a6dc48 impl member avatar update logging 2024-07-02 21:05:20 +01:00
ff23ea1d6c impl global name and username update logging 2024-07-02 21:03:40 +01:00
982c038b07 don't show clickable channel on channel delete log 2024-07-02 20:07:34 +01:00
52223b5233 add avatar to member join log 2024-07-02 20:06:41 +01:00
45755503a7 show account age on member join log 2024-07-02 20:04:27 +01:00
194a5d8119 fix timeout millis 2024-07-02 19:57:25 +01:00
120afee73b impl member timeout logging 2024-07-02 19:55:50 +01:00
8b7340715c impl member ban and unban logging 2024-07-02 19:47:51 +01:00
38bde93d16 update channel log messages 2024-07-02 19:43:27 +01:00
a3ffaf1ab9 impl channel create and delete logging 2024-07-02 19:24:48 +01:00
86c147f359 merge role add/remove into 1 update type and rename nickname logtype 2024-07-02 19:09:01 +01:00
deb93e442c impl role add/remove logging 2024-07-02 19:05:21 +01:00
6eca92b4cf add member nickname update log 2024-07-02 18:58:50 +01:00
3f93df131d looks too messy with this 2024-07-02 18:47:28 +01:00
1028dca15a add more data to the logs 2024-07-02 18:36:17 +01:00
e03aef0ad5 impl member join and leave logs 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 2024-07-02 01:56:59 +01:00
317eaaec8a impl a EmbedDescriptionBuilder 2024-07-02 01:51:13 +01:00
4f975ab07a use paste for messages longer than 512 and fix message sniping 2024-07-02 01:20:41 +01:00
1a69bce9dd sort sniped messages 2024-07-02 00:54:55 +01:00
37c69597be why angry?????? 2024-07-01 23:02:28 +01:00
3146ed7d6d fix dev commands 2024-07-01 22:56:52 +01:00
69281d113c fix marked as non null err and removed profiles debug 2024-07-01 21:51:51 +01:00
c6289d1c8e fix for npe?? 2024-07-01 21:49:02 +01:00
3082265ec6 add a new motd 2024-07-01 21:27:20 +01:00
a057853cbd add feature disabled check 2024-07-01 21:21:47 +01:00
8b451c6ee5 add message snipe feature 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' 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' () from renovate/configure into master
Reviewed-on: 
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 2024-07-01 15:27:39 +01:00
8361f3c784 update member count command to look similar to botstats command 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 2024-07-01 15:20:49 +01:00
be7f8a9057 only check guilds that we're in 2024-07-01 15:16:06 +01:00
c93e112ebf might fix, who knows 2024-07-01 15:14:42 +01:00
7485bd2ec8 fix premium admin command 2024-07-01 01:46:56 +01:00
ed83175a39 add env for admin guild 2024-07-01 01:44:22 +01:00
a001f2dd4c add valid guild and user id check 2024-07-01 01:41:40 +01:00
b1785ce373 impl pre shutdown saving 2024-07-01 01:33:52 +01:00
b1f5db9b2d maybe fix this? 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 2024-07-01 01:12:32 +01:00
f566c3bcb5 add emojis to feature states 2024-06-30 08:34:59 +01:00
6403c57db5 add checks for some events to see if the feature is enabled and more cleanup 2024-06-30 08:24:14 +01:00
22d4558d84 use an enum for feature states 2024-06-30 08:10:49 +01:00
ea546f02ca cleanup features and move all misc commands into a base feature 2024-06-30 08:00:03 +01:00
5b1ddb145f make feature command less ugly and update feature disabled message 2024-06-30 07:41:31 +01:00
5ce5ef6898 format numbers on botstats command 2024-06-30 07:34:49 +01:00
729e0b482b update number formatter 2024-06-30 07:34:03 +01:00
5aa56c2955 rename feature command 2024-06-30 05:16:00 +01:00
ee6456e4d8 add feature toggling 2024-06-30 05:15:37 +01:00
93350f1506 remove useless config option 2024-06-30 04:16:49 +01:00
702aead53a cleanup dev mode 2024-06-30 04:13:54 +01:00
b7f2b6a3d7 fix birthday date validation 2024-06-30 03:47:34 +01:00
b66114503c fix npe 2024-06-30 03:39:38 +01:00
50391e5344 add name history tracking 2024-06-30 03:36:00 +01:00
91ecc9882c cleanup 2024-06-30 02:36:17 +01:00
06a2584e63 fix migrations 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 2024-06-30 00:35:52 +01:00
86c7afac42 many changes 2024-06-29 22:38:53 +01:00
b0949d17e6 add skip spotify command and show song when pausing and resuming the song 2024-06-29 21:36:45 +01:00
df44ae90b9 update member count command 2024-06-29 18:35:53 +01:00
4821e2a4fa add emojis to the spotify commands 2024-06-29 16:54:39 +01:00
4cb34fbb9a add duck image command 2024-06-29 13:23:12 +01:00
d824f957fe add vote command 2024-06-29 13:23:05 +01:00
266 changed files with 12622 additions and 3907 deletions
.mvn/wrapper
Dockerfilepom.xmlprivacy-policy.txtrenovate.json
src/main/java/cc/fascinated/bat
BatApplication.javaConsts.javaEmojis.java
afk
autorole
base
birthday
command
common

@ -16,4 +16,4 @@
# under the License.
wrapperVersion=3.3.2
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

@ -1,5 +1,5 @@
# 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
WORKDIR /home/container
@ -11,7 +11,7 @@ COPY . .
RUN mvn package -q -Dmaven.test.skip -DskipTests -T2C
# 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
WORKDIR /home/container

62
pom.xml

@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.1</version>
<version>3.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
@ -41,7 +41,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<version>3.6.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
@ -70,6 +70,12 @@
</plugins>
</build>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<!-- Spring -->
@ -85,47 +91,69 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.16.0</version>
</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 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<!-- Dependencies -->
<dependency>
<groupId>net.dv8tion</groupId>
<groupId>io.github.freya022</groupId>
<artifactId>JDA</artifactId>
<version>5.0.0-beta.24</version>
<version>2ed819ad15</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<version>2.11.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
<version>5.4.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.jodah</groupId>
<artifactId>expiringmap</artifactId>
<version>0.5.11</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>se.michaelthelin.spotify</groupId>
<artifactId>spotify-web-api-java</artifactId>
<version>8.4.0</version>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.12.0</version>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<groupId>com.github.Steppschuh</groupId>
<artifactId>Java-Markdown-Generator</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</project>

34
privacy-policy.txt Normal 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

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

@ -1,5 +1,8 @@
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.GsonBuilder;
import lombok.NonNull;
@ -7,6 +10,7 @@ import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.io.File;
@ -33,7 +37,20 @@ public class BatApplication {
}
log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config
// Start the app
SpringApplication.run(BatApplication.class, args);
// Start the application
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"));
}
}

@ -4,8 +4,9 @@ package cc.fascinated.bat;
* @author Fascinated (fascinated7)
*/
public class Consts {
public static final String INVITE_URL = "https://discord.com/oauth2/authorize?client_id=1254161119975833652&permissions=8&integration_type=0&scope=bot+applications.commands";
public static final String INVITE_URL = "https://dsc.gg/batbot";
public static final String SUPPORT_INVITE_URL = "https://discord.gg/invite/yjj2U3ctEG";
public static String BOT_OWNER = "474221560031608833";
public static String ADMIN_GUILD = "1203163422498361404";
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";
}

@ -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!");
}
}

@ -1,8 +1,8 @@
package cc.fascinated.bat.features.afk;
package cc.fascinated.bat.afk;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.afk.command.AfkCommand;
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;
@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
@Component
public class AfkFeature extends Feature {
public AfkFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("AFK", Category.GENERAL);
super("AFK", FeatureProfile.FeatureState.DISABLED, true);
registerCommand(commandService, context.getBean(AfkCommand.class));
}

@ -1,9 +1,9 @@
package cc.fascinated.bat.features.afk;
package cc.fascinated.bat.afk;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.afk.profile.AfkProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;

@ -1,13 +1,11 @@
package cc.fascinated.bat.features.afk;
package cc.fascinated.bat.afk;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.afk.profile.AfkProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
@ -15,13 +13,6 @@ import org.springframework.stereotype.Component;
*/
@Component
public class AfkReturnListener implements EventListener {
private final GuildService guildService;
@Autowired
public AfkReturnListener(@NonNull GuildService guildService) {
this.guildService = guildService;
}
@Override
public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
AfkProfile profile = guild.getProfile(AfkProfile.class);
@ -29,7 +20,6 @@ public class AfkReturnListener implements EventListener {
return;
}
profile.removeAfkUser(guild, user.getId());
guildService.saveGuild(guild);
event.getMessage().reply("Welcome back, %s! You are no longer AFK.".formatted(user.getDiscordUser().getAsMention())).queue();
}
}

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

@ -1,9 +1,12 @@
package cc.fascinated.bat.features.afk.profile;
package cc.fascinated.bat.afk.profile;
import cc.fascinated.bat.common.Profile;
import cc.fascinated.bat.model.BatGuild;
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;
@ -13,7 +16,8 @@ import java.util.Map;
* @author Fascinated (fascinated7)
*/
@Component
public class AfkProfile extends Profile {
@NoArgsConstructor
public class AfkProfile extends Serializable {
private static final String DEFAULT_REASON = "Away";
/**
@ -98,4 +102,21 @@ public class AfkProfile extends Profile {
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;
}
}

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

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

@ -1,20 +1,21 @@
package cc.fascinated.bat.features.autorole.command;
package cc.fascinated.bat.autorole.command;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
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.features.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
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;
@ -23,40 +24,32 @@ import org.springframework.stereotype.Component;
*/
@Component("autoroles:add.sub")
@CommandInfo(name = "add", description = "Adds a role to the auto roles list")
public class AddSubCommand extends BatSubCommand {
private final GuildService guildService;
public class AddSubCommand extends BatCommand {
@Autowired
public AddSubCommand(@NonNull GuildService guildService) {
super.addOption(OptionType.ROLE, "role", "The role to add", true);
this.guildService = guildService;
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, @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);
// Check if the guild has reached the maximum auto roles count
int maxRoleSlots = AutoRoleProfile.getMaxRoleSlots(guild);
if (profile.getRoleSlotsInUse() >= maxRoleSlots) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The guild can only have a maximum of %d auto roles"
.formatted(maxRoleSlots))
.build()).queue();
return;
}
OptionMapping option = interaction.getOption("role");
if (option == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Please provide a role to add")
.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())) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The role %s is already in the auto roles list".formatted(role.getAsMention()))
.build()).queue();
return;
@ -64,7 +57,7 @@ public class AddSubCommand extends BatSubCommand {
// Check if the bot has permission to give the role
if (!RoleUtils.hasPermissionToGiveRole(guild, guild.getDiscordGuild().getSelfMember(), role)) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("I do not have permission to give the role %s".formatted(role.getAsMention()))
.build()).queue();
return;
@ -72,7 +65,7 @@ public class AddSubCommand extends BatSubCommand {
// Check if the role is higher than the user adding the role
if (!RoleUtils.hasPermissionToGiveRole(guild, member, role)) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You cannot add a role that is higher than you")
.build()).queue();
return;
@ -80,8 +73,7 @@ public class AddSubCommand extends BatSubCommand {
// Add the role to the auto roles list
profile.addRole(role.getId());
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed()
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You have added %s to the auto roles list".formatted(role.getAsMention()))
.build()).queue();
}

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

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

@ -1,14 +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.command.CommandInfo;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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.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.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
@ -18,12 +19,12 @@ import org.springframework.stereotype.Component;
*/
@Component("autoroles:list.sub")
@CommandInfo(name = "list", description = "Lists all auto roles")
public class ListSubCommand extends BatSubCommand {
public class ListSubCommand extends BatCommand {
@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);
if (profile.getRoles().isEmpty()) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("There are no auto roles set")
.build()).queue();
return;
@ -41,6 +42,6 @@ public class ListSubCommand extends BatSubCommand {
EmbedBuilder embed = EmbedUtils.genericEmbed();
embed.setAuthor("Auto Role List");
embed.setDescription(roles.toString());
interaction.replyEmbeds(embed.build()).queue();
event.replyEmbeds(embed.build()).queue();
}
}

@ -1,19 +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.command.CommandInfo;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
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;
@ -22,37 +23,28 @@ import org.springframework.stereotype.Component;
*/
@Component("autoroles:remove.sub")
@CommandInfo(name = "remove", description = "Removes a role from the auto roles list")
public class RemoveSubCommand extends BatSubCommand {
private final GuildService guildService;
public class RemoveSubCommand extends BatCommand {
@Autowired
public RemoveSubCommand(GuildService guildService) {
super.addOption(OptionType.ROLE, "role", "The role to remove", true);
this.guildService = guildService;
public RemoveSubCommand() {
super.addOptions(new OptionData(OptionType.ROLE, "role", "The role to remove", true));
}
@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);
OptionMapping option = interaction.getOption("role");
if (option == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Please provide a role to remove")
.build()).queue();
return;
}
OptionMapping option = event.getOption("role");
assert option != null;
Role role = option.getAsRole();
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()))
.build()).queue();
return;
}
profile.removeRole(role.getId());
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed()
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully removed the role %s from the auto roles list".formatted(role.getAsMention()))
.build()).queue();
}

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

@ -1,11 +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.model.BatGuild;
import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.service.DiscordService;
import com.google.gson.Gson;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Role;
import org.bson.Document;
import java.util.ArrayList;
import java.util.List;
@ -15,7 +18,8 @@ import java.util.List;
*/
@Setter
@Getter
public class AutoRoleProfile extends Profile {
@NoArgsConstructor
public class AutoRoleProfile extends Serializable {
private static final int DEFAULT_MAX_ROLES = 10;
private static final int PREMIUM_MAX_ROLES = 25;
@ -24,10 +28,6 @@ public class AutoRoleProfile extends Profile {
*/
private List<String> roleIds;
public AutoRoleProfile() {
super("auto-role");
}
/**
* Gets the maximum amount of roles that can be set in the guild
*
@ -35,7 +35,7 @@ public class AutoRoleProfile extends Profile {
* @return the amount of role slots
*/
public static int getMaxRoleSlots(BatGuild guild) {
if (guild.getPremium().hasPremium()) {
if (guild.getPremiumProfile().hasPremium()) {
return PREMIUM_MAX_ROLES;
}
return DEFAULT_MAX_ROLES;
@ -110,4 +110,16 @@ public class AutoRoleProfile extends Profile {
public void reset() {
roleIds.clear();
}
@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;
}
}

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

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

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

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

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

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

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

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

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

@ -1,14 +1,15 @@
package cc.fascinated.bat.command.impl.fun.image;
package cc.fascinated.bat.base.commands.fun.image;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
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.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.thecatapi.CatImageToken;
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;
@ -18,16 +19,16 @@ import org.springframework.stereotype.Component;
*/
@Component
@CommandInfo(name = "cat", description = "Get a random cat image")
public class CatSubCommand extends BatSubCommand {
public class CatSubCommand extends BatCommand {
@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) {
CatImageToken[] responseEntity = WebRequest.getAsEntity("https://api.thecatapi.com/v1/images/search", CatImageToken[].class);
if (responseEntity == null || responseEntity.length == 0) {
interaction.reply("Failed to get a cat image!").queue();
event.reply("Failed to get a cat image!").queue();
return;
}
interaction.replyEmbeds(EmbedUtils.genericEmbed()
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("Here's a random cat image!")
.setImage(responseEntity[0].getUrl())
.build()).queue();

@ -1,14 +1,15 @@
package cc.fascinated.bat.command.impl.fun.image;
package cc.fascinated.bat.base.commands.fun.image;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
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.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.dogceo.RandomImage;
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;
@ -18,16 +19,16 @@ import org.springframework.stereotype.Component;
*/
@Component
@CommandInfo(name = "dog", description = "Get a random dog image")
public class DogSubCommand extends BatSubCommand {
public class DogSubCommand extends BatCommand {
@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) {
RandomImage responseEntity = WebRequest.getAsEntity("https://dog.ceo/api/breeds/image/random", RandomImage.class);
if (responseEntity == null) {
interaction.reply("Failed to get a dog image!").queue();
event.reply("Failed to get a dog image!").queue();
return;
}
interaction.replyEmbeds(EmbedUtils.genericEmbed()
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("Here's a random dog image!")
.setImage(responseEntity.getMessage())
.build()).queue();

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

@ -1,14 +1,15 @@
package cc.fascinated.bat.command.impl.fun.image;
package cc.fascinated.bat.base.commands.fun.image;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
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.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.randomfox.RandomFoxToken;
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;
@ -18,16 +19,16 @@ import org.springframework.stereotype.Component;
*/
@Component
@CommandInfo(name = "fox", description = "Get a random fox image")
public class FoxSubCommand extends BatSubCommand {
public class FoxSubCommand extends BatCommand {
@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) {
RandomFoxToken responseEntity = WebRequest.getAsEntity("https://randomfox.ca/floof/", RandomFoxToken.class);
if (responseEntity == null) {
interaction.reply("Failed to get a fox image!").queue();
event.reply("Failed to get a fox image!").queue();
return;
}
interaction.replyEmbeds(EmbedUtils.genericEmbed()
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("Here's a random fox image!")
.setImage(responseEntity.getImage())
.build()).queue();

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

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

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

@ -1,13 +1,15 @@
package cc.fascinated.bat.command.impl.general;
package cc.fascinated.bat.base.commands.general;
import cc.fascinated.bat.Consts;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
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.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
@ -16,11 +18,11 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "invite", description = "Invite the bot to your server!", guildOnly = false)
@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, @NonNull SlashCommandInteraction interaction) {
interaction.replyEmbeds(EmbedUtils.genericEmbed()
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)

@ -1,12 +1,14 @@
package cc.fascinated.bat.command.impl.general;
package cc.fascinated.bat.base.commands.general;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
@ -15,12 +17,18 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "ping", description = "Gets the ping of the bot", guildOnly = false)
@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, @NonNull SlashCommandInteraction interaction) {
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
long time = System.currentTimeMillis();
interaction.reply("Pinging...").queue(response -> {
event.reply("Pinging...").queue(response -> {
response.editOriginal("Gateway response time: `%sms`\nAPI response time `%sms`".formatted(
DiscordService.JDA.getGatewayPing(),
System.currentTimeMillis() - time

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

@ -1,7 +1,8 @@
package cc.fascinated.bat.command.impl.general.avatar;
package cc.fascinated.bat.base.commands.general.avatar;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
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;
@ -11,11 +12,13 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@Component
@CommandInfo(name = "avatar", description = "View the avatar of the guild or a user", guildOnly = false)
@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.addSubCommand(context.getBean(GuildSubCommand.class));
super.addSubCommand(context.getBean(UserSubCommand.class));
super.addSubCommands(
context.getBean(GuildSubCommand.class),
context.getBean(UserSubCommand.class)
);
}
}

@ -1,12 +1,14 @@
package cc.fascinated.bat.command.impl.general.avatar;
package cc.fascinated.bat.base.commands.general.avatar;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
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.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
@ -16,21 +18,20 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@Component("avatar:guild.sub")
@CommandInfo(name = "guild", description = "View the avatar of the guild")
public class GuildSubCommand extends BatSubCommand {
@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, @NonNull SlashCommandInteraction interaction) {
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) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("**%s** does not have an avatar!".formatted(guild.getName()))
.build())
.queue();
return;
}
interaction.replyEmbeds(EmbedUtils.genericEmbed()
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s's Avatar".formatted(guild.getName()), null, guild.getDiscordGuild().getIconUrl())
.setImage(icon.getUrl(4096))
.build()

@ -1,42 +1,39 @@
package cc.fascinated.bat.command.impl.general.avatar;
package cc.fascinated.bat.base.commands.general.avatar;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
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.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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)
public class UserSubCommand extends BatSubCommand {
@CommandInfo(name = "user", description = "View the avatar of a user", guildOnly = false, category = Category.GENERAL)
public class UserSubCommand extends BatCommand {
public UserSubCommand() {
super.addOption(OptionType.USER, "user", "The user to view the avatar of", true);
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, @NonNull SlashCommandInteraction interaction) {
OptionMapping userOption = interaction.getOption("user");
if (userOption == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a user to view the avatar of!")
.build())
.queue();
return;
}
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();
interaction.replyEmbeds(EmbedUtils.genericEmbed()
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s's Avatar".formatted(target.getName()), null, target.getEffectiveAvatarUrl())
.setImage(target.getEffectiveAvatarUrl())
.build()

@ -1,7 +1,8 @@
package cc.fascinated.bat.command.impl.general.banner;
package cc.fascinated.bat.base.commands.general.banner;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
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;
@ -11,11 +12,13 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@Component
@CommandInfo(name = "banner", description = "View the banner of the guild or a user", guildOnly = false)
@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.addSubCommand(context.getBean(GuildSubCommand.class));
super.addSubCommand(context.getBean(UserSubCommand.class));
super.addSubCommands(
context.getBean(GuildSubCommand.class),
context.getBean(UserSubCommand.class)
);
}
}

@ -1,12 +1,14 @@
package cc.fascinated.bat.command.impl.general.banner;
package cc.fascinated.bat.base.commands.general.banner;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
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.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
@ -16,20 +18,20 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@Component("banner:guild.sub")
@CommandInfo(name = "guild", description = "View the banner of the guild")
public class GuildSubCommand extends BatSubCommand {
@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, @NonNull SlashCommandInteraction interaction) {
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) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("**%s** does not have a banner!".formatted(guild.getName()))
.build())
.queue();
return;
}
interaction.replyEmbeds(EmbedUtils.genericEmbed()
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s's Banner".formatted(guild.getName()))
.setImage(banner.getUrl(512))
.build()

@ -1,17 +1,20 @@
package cc.fascinated.bat.command.impl.general.banner;
package cc.fascinated.bat.base.commands.general.banner;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
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.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
@ -19,34 +22,27 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@Component("banner:user.sub")
@CommandInfo(name = "user", description = "View the banner of a user", guildOnly = false)
public class UserSubCommand extends BatSubCommand {
@CommandInfo(name = "user", description = "View the banner of a user", guildOnly = false, category = Category.GENERAL)
public class UserSubCommand extends BatCommand {
public UserSubCommand() {
super.addOption(OptionType.USER, "user", "The user to view the banner of", true);
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, @NonNull SlashCommandInteraction interaction) {
OptionMapping userOption = interaction.getOption("user");
if (userOption == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a user to view the banner of!")
.build())
.queue();
return;
}
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) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("**%s** does not have a banner!".formatted(target.getName()))
.build())
.queue();
return;
}
interaction.replyEmbeds(EmbedUtils.genericEmbed()
event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s's Banner".formatted(target.getName()))
.setImage(banner.getUrl(512))
.build()

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

@ -1,14 +1,16 @@
package cc.fascinated.bat.command.impl.server;
package cc.fascinated.bat.base.commands.server;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
@ -20,17 +22,17 @@ import org.springframework.stereotype.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, @NonNull SlashCommandInteraction interaction) {
BatGuild.Premium premium = guild.getPremium();
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().toEpochMilli() / 1000), 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");
}
interaction.replyEmbeds(embed.build()).queue();
event.replyEmbeds(embed.build()).queue();
}
}

@ -1,8 +1,8 @@
package cc.fascinated.bat.command.impl.server.channel;
package cc.fascinated.bat.base.commands.server.channel;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
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;
@ -16,8 +16,10 @@ import org.springframework.stereotype.Component;
public class ChannelCommand extends BatCommand {
@Autowired
public ChannelCommand(@NonNull ApplicationContext context) {
super.addSubCommand(context.getBean(ViewTopicSubCommand.class));
super.addSubCommand(context.getBean(SetTopicSubCommand.class));
super.addSubCommand(context.getBean(RemoveTopicSubCommand.class));
super.addSubCommands(
context.getBean(ViewTopicSubCommand.class),
context.getBean(SetTopicSubCommand.class),
context.getBean(RemoveTopicSubCommand.class)
);
}
}

@ -1,18 +1,20 @@
package cc.fascinated.bat.command.impl.server.channel;
package cc.fascinated.bat.base.commands.server.channel;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
/**
@ -20,16 +22,16 @@ import org.springframework.stereotype.Component;
*/
@Component
@CommandInfo(name = "removetopic", description = "Remove the topic of a channel", requiredPermissions = Permission.MANAGE_CHANNEL)
public class RemoveTopicSubCommand extends BatSubCommand {
public class RemoveTopicSubCommand extends BatCommand {
public RemoveTopicSubCommand() {
super.addOption(OptionType.CHANNEL, "channel", "The channel to remove the topic of", false);
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, @NonNull SlashCommandInteraction interaction) {
Channel target = interaction.getOption("channel") == null ? channel : interaction.getOption("channel").getAsChannel();
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)) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> is not a text channel!".formatted(target.getId()))
.build())
.queue();
@ -37,7 +39,7 @@ public class RemoveTopicSubCommand extends BatSubCommand {
}
if (textChannel.getTopic() == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> does not have a topic!".formatted(textChannel.getId()))
.build())
.queue();
@ -45,7 +47,7 @@ public class RemoveTopicSubCommand extends BatSubCommand {
}
textChannel.getManager().setTopic(null).queue();
interaction.replyEmbeds(EmbedUtils.successEmbed()
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully removed the topic of <#%s>".formatted(textChannel.getId()))
.build()
).queue();

@ -1,55 +1,58 @@
package cc.fascinated.bat.command.impl.server.channel;
package cc.fascinated.bat.base.commands.server.channel;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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 BatSubCommand {
public class SetTopicSubCommand extends BatCommand {
public SetTopicSubCommand() {
super.addOption(OptionType.STRING, "topic", "The topic to set", true);
super.addOption(OptionType.CHANNEL, "channel", "The channel to set the topic of", false);
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, @NonNull SlashCommandInteraction interaction) {
Channel target = interaction.getOption("channel") == null ? channel : interaction.getOption("channel").getAsChannel();
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)) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> is not a text channel!".formatted(target.getId()))
.build())
.queue();
return;
}
String topic = interaction.getOption("topic").getAsString();
String topic = Objects.requireNonNull(event.getOption("topic")).getAsString();
if (topic.length() > 1024) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The topic must be 1024 characters or less!")
.build())
.queue();
return;
}
textChannel.getManager().setTopic(topic).queue();
interaction.replyEmbeds(EmbedUtils.successEmbed()
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();
.build()).queue()));
}
}

@ -1,17 +1,19 @@
package cc.fascinated.bat.command.impl.server.channel;
package cc.fascinated.bat.base.commands.server.channel;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
/**
@ -19,16 +21,16 @@ import org.springframework.stereotype.Component;
*/
@Component
@CommandInfo(name = "viewtopic", description = "View the topic of a channel")
public class ViewTopicSubCommand extends BatSubCommand {
public class ViewTopicSubCommand extends BatCommand {
public ViewTopicSubCommand() {
super.addOption(OptionType.CHANNEL, "channel", "The channel to view the topic of", false);
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, @NonNull SlashCommandInteraction interaction) {
Channel target = interaction.getOption("channel") == null ? channel : interaction.getOption("channel").getAsChannel();
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)) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> is not a text channel!".formatted(target.getId()))
.build())
.queue();
@ -37,14 +39,14 @@ public class ViewTopicSubCommand extends BatSubCommand {
String topic = textChannel.getTopic();
if (topic == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> does not have a topic!".formatted(textChannel.getId()))
.build())
.queue();
return;
}
interaction.replyEmbeds(EmbedUtils.genericEmbed()
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("The topic of <#%s> is: \"%s\"".formatted(textChannel.getId(), topic))
.build()
).queue();

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

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

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

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

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

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

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

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

@ -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() {}
}

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

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

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

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

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

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

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

@ -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.model.BatGuild;
import cc.fascinated.bat.common.ChannelUtils;
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.NoArgsConstructor;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
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)
*/
@Getter
@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!";
/**
* 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
@ -34,17 +43,13 @@ public class BirthdayProfile extends Profile {
*/
private String message = DEFAULT_MESSAGE;
public BirthdayProfile() {
super("birthday");
}
/**
* Adds a birthday to be tracked
*
* @param userId the id of the user to track
* @param birthday the birthday of the user
*/
public void addBirthday(String userId, Date birthday) {
public void addBirthday(String userId, UserBirthday birthday) {
if (birthdays == null) {
birthdays = new HashMap<>();
}
@ -69,7 +74,7 @@ public class BirthdayProfile extends Profile {
* @param userId the id of the user
* @return the birthday of the user
*/
public Date getBirthday(String userId) {
public UserBirthday getBirthday(String userId) {
if (birthdays == null) {
birthdays = new HashMap<>();
}
@ -85,33 +90,6 @@ public class BirthdayProfile extends Profile {
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
*
@ -122,33 +100,10 @@ public class BirthdayProfile extends Profile {
if (birthdays == null) {
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) {
return false;
}
if (discordGuild.getTextChannelById(channelId) == null) {
if (guild.getDiscordGuild().getTextChannelById(channelId) == null) {
channelId = null;
return false;
}
@ -170,13 +125,10 @@ public class BirthdayProfile extends Profile {
int todayDay = today.get(Calendar.DAY_OF_MONTH);
int todayMonth = today.get(Calendar.MONTH); // Note: January is 0
Iterator<Map.Entry<String, Date>> iterator = birthdays.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Date> entry = iterator.next();
Date birthday = entry.getValue();
for (Map.Entry<String, UserBirthday> entry : birthdays.entrySet()) {
Date birthday = entry.getValue().getBirthday();
if (birthday == null) {
iterator.remove();
continue;
}
@ -206,7 +158,7 @@ public class BirthdayProfile extends Profile {
return;
}
TextChannel channel = discordGuild.getTextChannelById(channelId);
TextChannel channel = ChannelUtils.getTextChannel(channelId);
if (channel == null) { // this should never happen
channelId = null;
return;
@ -223,7 +175,7 @@ public class BirthdayProfile extends Profile {
public String getBirthdayMessage(User user) {
return message
.replace("{user}", user.getAsMention())
.replace("{age}", String.valueOf(calculateAge(user.getId())));
.replace("{age}", String.valueOf(birthdays.get(user.getId()).calculateAge()));
}
@Override
@ -231,4 +183,27 @@ public class BirthdayProfile extends Profile {
birthdays.clear();
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;
}
}

@ -1,91 +0,0 @@
package cc.fascinated.bat.command;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
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 information about the command
*/
private final CommandInfo commandInfo;
/**
* The command data for the slash command
*/
private final CommandDataImpl commandData;
/**
* The sub commands of the command
*/
private final Map<String, BatSubCommand> subCommands = new HashMap<>();
/**
* The category of the command
*/
private Category category;
/**
* Whether the command can only be used by the bot owner
*/
private boolean botOwnerOnly;
/**
* The command snowflake from Discord
*/
private long commandSnowflake;
public BatCommand() {
this.commandInfo = getClass().getAnnotation(CommandInfo.class);
this.category = this.commandInfo.category();
this.botOwnerOnly = this.commandInfo.botOwnerOnly();
this.commandData = new CommandDataImpl(this.commandInfo.name(), this.commandInfo.description())
.setGuildOnly(this.commandInfo.guildOnly());
}
/**
* Adds a sub command to the command
*
* @param subCommand The sub command
*/
public void addSubCommand(@NonNull BatSubCommand subCommand) {
this.subCommands.put(subCommand.getCommandInfo().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();
}
}

@ -1,31 +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
) {
}
}

@ -1,45 +0,0 @@
package cc.fascinated.bat.command;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
/**
* @author Fascinated (fascinated7)
*/
@Getter
@Setter
public class BatSubCommand implements BatCommandExecutor {
/**
* The information about the sub command
*/
private final CommandInfo commandInfo;
/**
* The command data for the slash command
*/
private final SubcommandData commandData;
/**
* The commands snowflake from Discord
*/
private long commandSnowflake;
public BatSubCommand() {
this.commandInfo = getClass().getAnnotation(CommandInfo.class);
this.commandData = new SubcommandData(this.commandInfo.name(), this.commandInfo.description());
}
/**
* 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);
}
}

@ -1,62 +0,0 @@
package cc.fascinated.bat.command;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import java.util.Arrays;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
public enum Category {
GENERAL(Emoji.fromUnicode("U+2699"), "General", false),
FUN(Emoji.fromFormatted("U+1F973"), "Fun", false),
SERVER(Emoji.fromFormatted("U+1F5A5"), "Server", false),
UTILITY(Emoji.fromFormatted("U+1F6E0"), "Utility", false),
MUSIC(Emoji.fromFormatted("U+1F3B5"), "Music", false),
BEAT_SABER(Emoji.fromFormatted("U+1FA84"), "Beat Saber", false),
BOT_ADMIN(null, null, true);
/**
* The emoji for the category
*/
private final Emoji emoji;
/**
* The name of the category
*/
private final String name;
/**
* If the category is hidden
*/
private final boolean hidden;
/**
* 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 all the visible categories
*
* @return the visible categories
*/
public static List<Category> getCategories() {
return Arrays.stream(Category.values()).filter(category -> !category.isHidden()).toList();
}
}

@ -1,22 +0,0 @@
package cc.fascinated.bat.command.impl.botadmin.premium;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.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 = "premiumadmin", description = "Set a guild as premium", botOwnerOnly = true, category = Category.BOT_ADMIN)
public class PremiumAdminCommand extends BatCommand {
@Autowired
public PremiumAdminCommand(@NonNull ApplicationContext context) {
super.addSubCommand(context.getBean(SetSubCommand.class));
super.addSubCommand(context.getBean(RemoveSubCommand.class));
}
}

@ -1,54 +0,0 @@
package cc.fascinated.bat.command.impl.botadmin.premium;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
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.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "remove", description = "Remove premium from a guild")
public class RemoveSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired
public RemoveSubCommand(GuildService guildService) {
this.guildService = guildService;
super.addOption(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, @NonNull SlashCommandInteraction interaction) {
OptionMapping guildOption = interaction.getOption("guild");
if (guildOption == null) {
interaction.reply("Please provide a guild id").queue();
return;
}
String guildId = guildOption.getAsString();
BatGuild batGuild = guildService.getGuild(guildId);
if (batGuild == null) {
interaction.reply("The guild with the id %s does not exist".formatted(guildId)).queue();
return;
}
BatGuild.Premium premium = batGuild.getPremium();
if (!premium.hasPremium()) {
interaction.reply("The guild does not have premium").queue();
return;
}
premium.removePremium();
guildService.saveGuild(batGuild);
interaction.reply("The guild **%s** has had its premium removed".formatted(guild.getName())).queue();
}
}

@ -1,65 +0,0 @@
package cc.fascinated.bat.command.impl.botadmin.premium;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
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.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "set", description = "Adds premium to a guild")
public class SetSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired
public SetSubCommand(GuildService guildService) {
this.guildService = guildService;
super.addOption(OptionType.STRING, "guild", "The guild id to set as premium", true);
super.addOption(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, @NonNull SlashCommandInteraction interaction) {
OptionMapping guildOption = interaction.getOption("guild");
if (guildOption == null) {
interaction.reply("Please provide a guild id").queue();
return;
}
String guildId = guildOption.getAsString();
OptionMapping infiniteOption = interaction.getOption("infinite");
if (infiniteOption == null) {
interaction.reply("Please provide whether the premium length should be infinite").queue();
return;
}
boolean infinite = infiniteOption.getAsBoolean();
BatGuild batGuild = guildService.getGuild(guildId);
if (batGuild == null) {
interaction.reply("The guild with the id %s does not exist".formatted(guildId)).queue();
return;
}
BatGuild.Premium premium = batGuild.getPremium();
if (!infinite) {
premium.addTime();
} else {
premium.addInfiniteTime();
}
guildService.saveGuild(batGuild);
if (!infinite) {
interaction.reply("The guild **%s** has been set as premium until <t:%s>".formatted(guild.getName(), premium.getExpiresAt().toInstant().toEpochMilli() / 1000)).queue();
} else {
interaction.reply("The guild **%s** has been set as premium indefinitely".formatted(guild.getName())).queue();
}
}
}

@ -1,23 +0,0 @@
package cc.fascinated.bat.command.impl.fun.image;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.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, category = Category.FUN)
public class ImageCommand extends BatCommand {
@Autowired
public ImageCommand(@NonNull ApplicationContext context) {
super.addSubCommand(context.getBean(CatSubCommand.class));
super.addSubCommand(context.getBean(DogSubCommand.class));
super.addSubCommand(context.getBean(FoxSubCommand.class));
}
}

@ -1,55 +0,0 @@
package cc.fascinated.bat.command.impl.general;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.TimeUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.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.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)
public class BotStatsCommand extends BatCommand {
private final GuildService guildService;
private final UserService userService;
private final RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();
@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, @NonNull SlashCommandInteraction interaction) {
JDA jda = DiscordService.JDA;
interaction.replyEmbeds(EmbedUtils.genericEmbed().setDescription(
"**Bot Statistics**\n" +
"➜ Guilds: **%s**\n".formatted(jda.getGuilds().size()) +
"➜ Users: **%s**\n".formatted(jda.getUsers().size()) +
"➜ Gateway Ping: **%sms**\n".formatted(jda.getGatewayPing()) +
"\n" +
"**Bat Statistics**\n" +
"➜ Uptime: **%s**\n".formatted(TimeUtils.format(bean.getUptime())) +
"➜ Cached Guilds: **%s**\n".formatted(guildService.getGuilds().size()) +
"➜ Cached Users: **%s**".formatted(userService.getUsers().size())
).build()).queue();
}
}

@ -1,150 +0,0 @@
package cc.fascinated.bat.command.impl.general;
import cc.fascinated.bat.Consts;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.CommandService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "help", description = "View the bots command categories.", guildOnly = false)
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, @NonNull SlashCommandInteraction interaction) {
interaction.replyEmbeds(createHomeEmbed()).addComponents(createHomeActions()).queue();
}
@Override
public void onStringSelectInteraction(BatGuild guild, @NonNull BatUser user, @NonNull StringSelectInteractionEvent event) {
String item = event.getSelectedOptions().get(0).getValue();
if (item.equalsIgnoreCase("home")) {
event.editMessageEmbeds(createHomeEmbed()).queue();
return;
}
Category category = Category.getByName(item);
if (category == null) {
event.reply("Invalid category selected.").queue();
return;
}
String commands = "";
List<BatCommand> categoryCommands = commandService.getCommandsByCategory(category, true);
if (categoryCommands.isEmpty()) {
commands = "No commands available in this category.";
} else {
for (BatCommand command : categoryCommands) {
if (!command.getSubCommands().isEmpty()) {
for (Map.Entry<String, BatSubCommand> entry : command.getSubCommands().entrySet()) {
BatSubCommand subCommand = entry.getValue();
SubcommandData commandData = subCommand.getCommandData();
commands += "</%s %s:%s> - %s\n".formatted(
command.getCommandInfo().name(),
commandData.getName(),
subCommand.getCommandSnowflake(),
commandData.getDescription()
);
}
continue;
}
commands += "</%s:%s> - %s\n".formatted(
command.getCommandInfo().name(),
command.getCommandSnowflake(),
command.getCommandInfo().description()
);
}
}
int subCommands = categoryCommands.stream().mapToInt(command -> command.getSubCommands().size()).sum();
event.editMessageEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s Category".formatted(category.getName()))
.setDescription("%s command%s (with %s sub-command%s)\n\n**Commands:**\n%s".formatted(
categoryCommands.size(),
categoryCommands.size() == 1 ? "" : "s",
subCommands,
subCommands == 1 ? "" : "s",
commands
)).build()).queue();
}
/**
* Creates the home embed for the help command
*
* @return The home embed
*/
private MessageEmbed createHomeEmbed() {
String categories = "";
for (Category category : Category.getCategories()) {
long commandCount = commandService.getCommandsByCategory(category, true).size();
categories += "➜ %s - **%s Command%s**\n".formatted(
category.getName(),
commandCount,
commandCount == 1 ? "" : "s"
);
}
return EmbedUtils.genericEmbed()
.setDescription("Here are the available command categories: \n\n" + categories)
.build();
}
/**
* Creates the home actions for the help command
*
* @return The layout components
*/
private LayoutComponent[] createHomeActions() {
List<SelectOption> options = new ArrayList<>();
options.add(SelectOption.of("Home", "home").withEmoji(Emoji.fromUnicode("U+1F3E0")));
options.addAll(Category.getCategories().stream().map(category ->
SelectOption.of(category.getName(), category.getName()).withEmoji(category.getEmoji()))
.toList());
return new LayoutComponent[]{
ActionRow.of(
Button.of(ButtonStyle.LINK, Consts.INVITE_URL, "Invite"),
Button.of(ButtonStyle.LINK, Consts.SUPPORT_INVITE_URL, "Support")
),
ActionRow.of(
StringSelectMenu.create("help-menu")
.addOptions(options)
.build()
)
};
}
}

@ -1,29 +0,0 @@
package cc.fascinated.bat.command.impl.server;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
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.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
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 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, @NonNull SlashCommandInteraction interaction) {
EmbedBuilder embed = EmbedUtils.genericEmbed().setAuthor("Member Count");
Guild discordGuild = guild.getDiscordGuild();
embed.setDescription("**%s** has a total of %s members.".formatted(discordGuild.getName(), discordGuild.getMembers().size()));
interaction.replyEmbeds(embed.build()).queue();
}
}

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

@ -3,12 +3,18 @@ package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
@UtilityClass
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 SIMPLE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
.withLocale(Locale.UK)
.withZone(ZONE_ID);
/**
* Gets the date from a string.
@ -19,4 +25,14 @@ public class DateUtils {
public static Date getDateFromString(String 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());
}
}

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

@ -1,7 +1,9 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.config.Config;
import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import java.time.LocalDateTime;
@ -42,4 +44,30 @@ public class EmbedUtils {
.setTimestamp(LocalDateTime.now())
.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;
}
}

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

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

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

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

@ -24,4 +24,39 @@ public final class MathUtils {
).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;
}
}

@ -1,7 +1,8 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
@ -10,6 +11,7 @@ import java.util.Comparator;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class MemberUtils {
/**
* Checks if a user has permission to edit another user

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

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

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

@ -1,26 +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();
}

@ -1,6 +1,11 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.BatApplication;
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.Map;
@ -9,11 +14,12 @@ import java.util.Map;
* @author Fascinated (fascinated7)
*/
@Getter
public class ProfileHolder {
public abstract class ProfileHolder {
private static final Logger log = LoggerFactory.getLogger(ProfileHolder.class);
/**
* The profiles for the holder
*/
private Map<String, Profile> profiles;
private final Map<String, Serializable> profiles = new HashMap<>();
/**
* Gets a profile for the holder
@ -22,19 +28,25 @@ public class ProfileHolder {
* @param <T> The type of the profile
* @return The profile
*/
public <T extends Profile> T getProfile(Class<T> clazz) {
if (profiles == null) {
profiles = new HashMap<>();
}
public abstract <T extends Serializable> T getProfile(Class<T> clazz);
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) {
try {
profile = clazz.newInstance();
profiles.put(profile.getProfileKey(), profile);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
T newProfile = clazz.cast(clazz.getDeclaredConstructors()[0].newInstance());
Document profiles = document.get("profiles", new org.bson.Document());
Document profileDocument = (Document) profiles.get(clazz.getSimpleName());
newProfile.load(profileDocument == null ? new Document() : profileDocument, BatApplication.GSON);
getProfiles().put(clazz.getSimpleName(), newProfile);
return newProfile;
}
return clazz.cast(profile);
}

@ -1,12 +1,18 @@
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.Role;
import java.awt.*;
import java.util.EnumSet;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class RoleUtils {
/**
* Checks if a member has permission to give the role to another member
@ -19,4 +25,60 @@ public class RoleUtils {
public static boolean hasPermissionToGiveRole(BatGuild guild, Member member, Role role) {
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());
}
}

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

@ -1,8 +1,11 @@
package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class StringUtils {
/**
* Generates a random string
@ -17,4 +20,35 @@ public class StringUtils {
}
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);
}
}

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

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