Compare commits

..

170 Commits

Author SHA1 Message Date
Lee
4cc4334df4 Merge pull request 'Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.5' (#44) from renovate/spring-boot into master
Reviewed-on: Fascinated/Bat#44
2024-11-02 23:16:31 +00:00
Lee
a4acdc2ec1 Merge pull request 'Update dependency org.apache.httpcomponents.client5:httpclient5 to v5.4.1' (#45) from renovate/org.apache.httpcomponents.client5-httpclient5-5.x into master
Reviewed-on: Fascinated/Bat#45
2024-11-02 23:16:18 +00:00
Lee
7ca984e785 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.16.0' (#46) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Reviewed-on: Fascinated/Bat#46
2024-11-02 23:16:13 +00:00
Lee
53730a82cf Merge pull request 'Update dependency io.mongock:mongock-bom to v5.5.0' (#47) from renovate/io.mongock-mongock-bom-5.x into master
Reviewed-on: Fascinated/Bat#47
2024-11-02 23:16:06 +00:00
Lee
6f4a787ccb Merge pull request 'Update dependency io.mongock:mongock-springboot-v3 to v5.5.0' (#48) from renovate/io.mongock-mongock-springboot-v3-5.x into master
Reviewed-on: Fascinated/Bat#48
2024-11-02 23:16:02 +00:00
Lee
2b588a5940 Merge pull request 'Update dependency io.mongock:mongodb-springdata-v4-driver to v5.5.0' (#49) from renovate/io.mongock-mongodb-springdata-v4-driver-5.x into master
Reviewed-on: Fascinated/Bat#49
2024-11-02 23:15:56 +00:00
Lee
fe4cf3bbf4 Merge pull request 'Update eclipse-temurin Docker tag to v17.0.13_11-jre-focal' (#50) from renovate/eclipse-temurin-17.x into master
Reviewed-on: Fascinated/Bat#50
2024-11-02 23:15:51 +00:00
ede931f994 Update dependency org.apache.httpcomponents.client5:httpclient5 to v5.4.1 2024-10-28 18:01:11 +00:00
d5ed2b0b13 Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.5 2024-10-24 14:01:29 +00:00
4479bce0a4 Update eclipse-temurin Docker tag to v17.0.13_11-jre-focal 2024-10-24 04:01:33 +00:00
3c80b3ca4b Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.16.0 2024-10-23 14:01:31 +00:00
65fd68f76c Update dependency io.mongock:mongodb-springdata-v4-driver to v5.5.0 2024-10-17 00:02:24 +00:00
6640dd4368 Update dependency io.mongock:mongock-springboot-v3 to v5.5.0 2024-10-16 23:00:45 +00:00
96cb634fd4 Update dependency io.mongock:mongock-bom to v5.5.0 2024-10-16 23:00:43 +00:00
Lee
0f9a23e4a2 Update pom.xml 2024-09-02 08:25:19 +00:00
Lee
eb3d8eea29 Merge pull request 'Update dependency maven to v3.9.9' (#42) from renovate/maven-3.x into master
Reviewed-on: Fascinated/Bat#42
2024-09-02 08:24:54 +00:00
Lee
7e45b5d880 Merge pull request 'Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.3' (#43) from renovate/spring-boot into master
Reviewed-on: Fascinated/Bat#43
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' (#40) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Reviewed-on: Fascinated/Bat#40
2024-08-13 17:28:28 +00:00
cfaa501377 Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.14.0 2024-08-13 09:01:17 +00:00
Lee
6f12062311 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.13.0' (#39) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Reviewed-on: Fascinated/Bat#39
2024-07-31 15:59:30 +00:00
3edfab3adb Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.13.0 2024-07-31 10:00:36 +00:00
Lee
25d4b247b3 Merge pull request 'Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.2' (#38) from renovate/org.springframework.boot-spring-boot-starter-parent-3.x into master
Reviewed-on: Fascinated/Bat#38
2024-07-30 20:52:04 +00:00
Lee
393c7741ca Merge pull request 'Update eclipse-temurin Docker tag to v17.0.12_7-jre-focal' (#18) from renovate/eclipse-temurin-17.x into master
Reviewed-on: Fascinated/Bat#18
2024-07-30 20:51:59 +00:00
Lee
001836c88b Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.12.1' (#16) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Reviewed-on: Fascinated/Bat#16
2024-07-30 20:51:53 +00:00
Lee
be7e03d641 Merge pull request 'Update dependency se.michaelthelin.spotify:spotify-web-api-java to v8.4.1' (#19) from renovate/se.michaelthelin.spotify-spotify-web-api-java-8.x into master
Reviewed-on: Fascinated/Bat#19
2024-07-30 20:51:46 +00:00
db357a63f5 Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.2 2024-07-26 12:03:33 +00:00
e50fe57ee6 Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.12.1 2024-07-25 13:00:41 +00:00
2c017d0603 Update dependency se.michaelthelin.spotify:spotify-web-api-java to v8.4.1 2024-07-24 20:01:06 +00:00
f32d278a6c Update eclipse-temurin Docker tag to v17.0.12_7-jre-focal 2024-07-23 01:01:16 +00:00
b8ebc1de24 fix in dms 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' (#14) from renovate/org.apache.maven.plugins-maven-shade-plugin-3.x into master
Reviewed-on: Fascinated/Bat#14
2024-07-07 02:35:48 +00:00
Lee
52557b5c65 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.11.0' (#13) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Reviewed-on: Fascinated/Bat#13
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' (#12) from renovate/io.mongock-mongock-bom-5.x into master
Reviewed-on: Fascinated/Bat#12
2024-07-07 01:00:59 +00:00
Lee
2d8779d8e0 Merge pull request 'Update dependency com.google.code.gson:gson to v2.11.0' (#11) from renovate/com.google.code.gson-gson-2.x into master
Reviewed-on: Fascinated/Bat#11
2024-07-07 01:00:50 +00:00
765805fa7a Update dependency io.mongock:mongock-bom to v5.4.4 2024-07-07 01:00:30 +00:00
1d36479a82 Update dependency com.google.code.gson:gson to v2.11.0 2024-07-07 01:00:28 +00:00
Lee
9d6f3b9fef Merge pull request 'Update dependency org.projectlombok:lombok to v1.18.34' (#10) from renovate/org.projectlombok-lombok-1.x into master
Reviewed-on: Fascinated/Bat#10
2024-07-07 00:44:25 +00:00
Lee
9be60fee4c Merge pull request 'Update dependency maven to v3.9.8' (#9) from renovate/maven-3.x into master
Reviewed-on: Fascinated/Bat#9
2024-07-07 00:44:19 +00:00
cb82716a17 Update dependency org.projectlombok:lombok to v1.18.34 2024-07-07 00:42:58 +00:00
80439a3329 Update dependency maven to v3.9.8 2024-07-07 00:42:57 +00:00
Lee
627062ef23 Update renovate.json 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' (#8) from Rainnny/Bat:master into master
Reviewed-on: Fascinated/Bat#8
2024-07-04 13:02:29 +00:00
Lee
65af4c8c16 Merge pull request 'New command system' (#7) from Rainnny/Bat:master into master
Reviewed-on: Fascinated/Bat#7
2024-07-04 12:58:35 +00:00
208 changed files with 6544 additions and 1550 deletions

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

@ -1,5 +1,5 @@
# Stage 1: Build the application # Stage 1: Build the application
FROM maven:3.9.6-eclipse-temurin-17-alpine AS builder FROM maven:3.9.9-eclipse-temurin-17-alpine AS builder
# Set the working directory # Set the working directory
WORKDIR /home/container WORKDIR /home/container
@ -11,7 +11,7 @@ COPY . .
RUN mvn package -q -Dmaven.test.skip -DskipTests -T2C RUN mvn package -q -Dmaven.test.skip -DskipTests -T2C
# Stage 2: Create the final lightweight image # Stage 2: Create the final lightweight image
FROM eclipse-temurin:17.0.11_9-jre-focal FROM eclipse-temurin:17.0.13_11-jre-focal
# Set the working directory # Set the working directory
WORKDIR /home/container WORKDIR /home/container

42
pom.xml

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.1</version> <version>3.3.5</version>
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
@ -41,7 +41,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version> <version>3.6.0</version>
<configuration> <configuration>
<createDependencyReducedPom>false</createDependencyReducedPom> <createDependencyReducedPom>false</createDependencyReducedPom>
</configuration> </configuration>
@ -75,6 +75,11 @@
<id>jitpack.io</id> <id>jitpack.io</id>
<url>https://jitpack.io</url> <url>https://jitpack.io</url>
</repository> </repository>
<repository>
<id>fascinated-repo-public</id>
<name>Fascinated's Repository</name>
<url>https://repo.fascinated.cc/public</url>
</repository>
</repositories> </repositories>
<dependencies> <dependencies>
@ -94,25 +99,25 @@
<dependency> <dependency>
<groupId>io.sentry</groupId> <groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId> <artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.10.0</version> <version>7.16.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.mongock</groupId> <groupId>io.mongock</groupId>
<artifactId>mongock-bom</artifactId> <artifactId>mongock-bom</artifactId>
<version>5.2.4</version> <version>5.5.0</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.mongock</groupId> <groupId>io.mongock</groupId>
<artifactId>mongock-springboot-v3</artifactId> <artifactId>mongock-springboot-v3</artifactId>
<version>5.2.4</version> <version>5.5.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.mongock</groupId> <groupId>io.mongock</groupId>
<artifactId>mongodb-springdata-v4-driver</artifactId> <artifactId>mongodb-springdata-v4-driver</artifactId>
<version>5.2.4</version> <version>5.5.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@ -137,7 +142,7 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.32</version> <version>1.18.34</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -149,13 +154,13 @@
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.10.1</version> <version>2.11.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents.client5</groupId> <groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId> <artifactId>httpclient5</artifactId>
<version>5.3.1</version> <version>5.4.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -167,14 +172,23 @@
<dependency> <dependency>
<groupId>se.michaelthelin.spotify</groupId> <groupId>se.michaelthelin.spotify</groupId>
<artifactId>spotify-web-api-java</artifactId> <artifactId>spotify-web-api-java</artifactId>
<version>8.4.0</version> <version>8.4.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>uk.co.conoregan</groupId> <groupId>org.apache.commons</groupId>
<artifactId>themoviedbapi</artifactId> <artifactId>commons-text</artifactId>
<version>2.1.1</version> <version>1.12.0</version>
<scope>compile</scope> </dependency>
<dependency>
<groupId>xyz.mcutils</groupId>
<artifactId>mcutils-java-library</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>com.github.Steppschuh</groupId>
<artifactId>Java-Markdown-Generator</artifactId>
<version>1.3.2</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

@ -1,6 +1,3 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:recommended", ":dependencyDashboard"]
"extends": [ }
"local>Fascinated/renovate-config"
]
}

@ -4,7 +4,7 @@ package cc.fascinated.bat;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
public class Consts { 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 final String SUPPORT_INVITE_URL = "https://discord.gg/invite/yjj2U3ctEG";
public static final String BOT_OWNER = "474221560031608833"; 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 PRIVACY_POLICY_URL = "https://git.fascinated.cc/Fascinated/Bat/raw/branch/master/privacy-policy.txt";

@ -19,25 +19,13 @@ public class Emojis {
public static final Emoji SKIP_EMOJI; public static final Emoji SKIP_EMOJI;
public static final Emoji HOME_EMOJI; public static final Emoji HOME_EMOJI;
/**
* Presence Status Emojis
*/
public static final Emoji ONLINE_EMOJI;
public static final Emoji IDLE_EMOJI;
public static final Emoji DND_EMOJI;
public static final Emoji OFFLINE_EMOJI;
static { static {
log.info("Loading emojis..."); log.info("Loading emojis...");
JDA jda = DiscordService.JDA; JDA jda = DiscordService.JDA;
SPOTIFY_EMOJI = jda.getEmojiById("1256629771975266479"); SPOTIFY_EMOJI = jda.getEmojiById("1256629771975266479");
CHECK_MARK_EMOJI = jda.getEmojiById("1256633734065557676"); CHECK_MARK_EMOJI = jda.getEmojiById("1258922852669853817");
CROSS_MARK_EMOJI = jda.getEmojiById("1256634487429922897"); CROSS_MARK_EMOJI = jda.getEmojiById("1256634487429922897");
SAD_FACE_EMOJI = jda.getEmojiById("1256636078258131055"); SAD_FACE_EMOJI = jda.getEmojiById("1256636078258131055");
ONLINE_EMOJI = jda.getEmojiById("1256662465668710430");
IDLE_EMOJI = jda.getEmojiById("1256662632203685991");
DND_EMOJI = jda.getEmojiById("1256662572933845032");
OFFLINE_EMOJI = jda.getEmojiById("1256662679402053662");
PAUSE_EMOJI = Emoji.fromUnicode(""); PAUSE_EMOJI = Emoji.fromUnicode("");
PLAY_EMOJI = Emoji.fromUnicode(""); PLAY_EMOJI = Emoji.fromUnicode("");
SKIP_EMOJI = Emoji.fromUnicode(""); SKIP_EMOJI = Emoji.fromUnicode("");

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

@ -5,6 +5,10 @@ import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.entities.emoji.Emoji;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@ -16,10 +20,7 @@ public enum Category {
SERVER(Emoji.fromFormatted("U+1F5A5"), "Server"), SERVER(Emoji.fromFormatted("U+1F5A5"), "Server"),
MODERATION(Emoji.fromFormatted("U+1F6E0"), "Moderation"), MODERATION(Emoji.fromFormatted("U+1F6E0"), "Moderation"),
UTILITY(Emoji.fromFormatted("U+1F6E0"), "Utility"), UTILITY(Emoji.fromFormatted("U+1F6E0"), "Utility"),
MUSIC(Emoji.fromFormatted("U+1F3B5"), "Music"), MEDIA(Emoji.fromFormatted("U+1F3A5"), "Media"),
MOVIES_TV(Emoji.fromFormatted("U+1F3A5"), "Movies & TV"),
MESSAGES(Emoji.fromFormatted("U+1F4A3"), "Messages"),
LOGS(Emoji.fromFormatted("U+1F4D1"), "Logs"),
BEAT_SABER(Emoji.fromFormatted("U+1FA84"), "Beat Saber"); BEAT_SABER(Emoji.fromFormatted("U+1FA84"), "Beat Saber");
/** /**
@ -46,4 +47,13 @@ public enum Category {
} }
return null; return null;
} }
/**
* Gets the categories sorted by their name
*
* @return the sorted categories
*/
public static List<Category> getSortedByName() {
return Arrays.stream(Category.values()).sorted(Comparator.comparing(Category::getName)).toList();
}
} }

@ -44,6 +44,13 @@ public @interface CommandInfo {
*/ */
boolean userInstall() default false; boolean userInstall() default false;
/**
* If the command can be executed with a prefix
*
* @return if the command can be executed with a prefix
*/
boolean prefixAllowed() default false;
/** /**
* The required permissions for the command * The required permissions for the command
* *

@ -48,13 +48,19 @@ public class InternalCommandInfo {
*/ */
private boolean botOwnerOnly; private boolean botOwnerOnly;
/**
* Whether the command can be ran with a prefix.
*/
private boolean prefixAllowed;
protected InternalCommandInfo(@NonNull CommandInfo annotation) { protected InternalCommandInfo(@NonNull CommandInfo annotation) {
name = annotation.name(); name = annotation.name();
description = annotation.description(); description = annotation.description();
category = annotation.category(); category = annotation.category();
permissions = annotation.requiredPermissions(); permissions = annotation.requiredPermissions();
guildOnly = annotation.guildOnly(); guildOnly = annotation.guildOnly() && !annotation.userInstall();
userInstall = annotation.userInstall(); userInstall = annotation.userInstall();
botOwnerOnly = annotation.botOwnerOnly(); botOwnerOnly = annotation.botOwnerOnly();
prefixAllowed = annotation.prefixAllowed();
} }
} }

@ -19,7 +19,7 @@ public class ChannelUtils {
* @return the user with the given id * @return the user with the given id
*/ */
private static TextChannel getTextChannel(String id, int retries) { private static TextChannel getTextChannel(String id, int retries) {
if (retries >= 25) { if (retries >= 10) {
log.error("Failed to find user \"{}\" after {} retries.", id, retries); log.error("Failed to find user \"{}\" after {} retries.", id, retries);
return null; return null;
} }

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

@ -5,13 +5,13 @@ import lombok.NonNull;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
public class EmbedDescriptionBuilder { public class DescriptionBuilder {
/** /**
* Where the description is stored * Where the description is stored
*/ */
private final StringBuilder builder = new StringBuilder(); private final StringBuilder builder = new StringBuilder();
public EmbedDescriptionBuilder(String title) { public DescriptionBuilder(String title) {
if (title == null) { if (title == null) {
return; return;
} }
@ -19,19 +19,19 @@ public class EmbedDescriptionBuilder {
} }
@NonNull @NonNull
public EmbedDescriptionBuilder appendLine(@NonNull String line, boolean arrow) { public DescriptionBuilder appendLine(@NonNull String line, boolean arrow) {
builder.append(arrow ? "" : "").append(line).append("\n"); builder.append(arrow ? "" : "").append(line).append("\n");
return this; return this;
} }
@NonNull @NonNull
public EmbedDescriptionBuilder appendSubtitle(@NonNull String title) { public DescriptionBuilder appendSubtitle(@NonNull String title) {
builder.append("**").append(title).append("**").append("\n"); builder.append("**").append(title).append("**").append("\n");
return this; return this;
} }
@NonNull @NonNull
public EmbedDescriptionBuilder emptyLine() { public DescriptionBuilder emptyLine() {
builder.append("\n"); builder.append("\n");
return this; return this;
} }

@ -48,25 +48,26 @@ public class EmbedUtils {
/** /**
* Builds a generic interaction error embed * Builds a generic interaction error embed
* *
* @param ex the exception * @param ex the exceptionk
* @return the embed builder * @return the embed builder
*/ */
public static EmbedBuilder genericInteractionError(Exception ex) { public static EmbedBuilder genericInteractionError(Exception ex) {
TextChannel channel = ChannelUtils.getTextChannel(Config.INSTANCE.getLogsChannel()); TextChannel channel = ChannelUtils.getTextChannel(Config.INSTANCE.getLogsChannel());
if (channel != null) { EmbedBuilder embed = errorEmbed()
channel.sendMessageEmbeds(EmbedUtils.errorEmbed()
.setDescription("""
An error has occurred while processing an interaction. Please check the logs for more information.
```java
%s
```""".formatted(ex.getLocalizedMessage()))
.build()).queue();
}
return EmbedUtils.errorEmbed()
.setDescription(""" .setDescription("""
An error has occurred while processing your interaction. Please check the logs for more information. An error has occurred while processing %s interaction. If this issue persists, please contact the developers.
```java Cause: `%s`
%s ```java
```""".formatted(ex.getLocalizedMessage())); %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,118 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.InteractionService;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
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.ActionComponent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.ComponentInteraction;
import net.dv8tion.jda.api.interactions.components.ItemComponent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.selections.SelectMenu;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
import net.dv8tion.jda.api.utils.data.SerializableData;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import static net.dv8tion.jda.api.interactions.components.Component.Type.STRING_SELECT;
/**
* @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;
}
}

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

@ -14,7 +14,7 @@ public class NumberFormatter {
* The suffixes for the numbers * 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 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("###.##"); private static final DecimalFormat FORMAT = new DecimalFormat("#,##0.##");
/** /**
* Format the provided double * Format the provided double
@ -47,8 +47,8 @@ public class NumberFormatter {
* @param input the value to format * @param input the value to format
* @return the formatted double, in the format of xx,xxx,xxx * @return the formatted double, in the format of xx,xxx,xxx
*/ */
public static String formatCommas(double input) { public static String simpleFormat(double input) {
return String.format("%,.0f", input); return FORMAT.format(input);
} }
/** /**

@ -0,0 +1,20 @@
package cc.fascinated.bat.common;
/**
* @author Fascinated (fascinated7)
*/
public class NumberUtils {
/**
* Parses a string to an integer
*
* @param input the string to parse
* @return the parsed integer, or -1 if invalid
*/
public static int parseInt(String input) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException exception) {
return -1;
}
}
}

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

@ -37,4 +37,18 @@ public class StringUtils {
} }
return inputString; 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);
}
} }

@ -19,11 +19,11 @@ public class UserUtils {
* @return the user with the given id * @return the user with the given id
*/ */
private static User getUser(String id, int retries) { private static User getUser(String id, int retries) {
if (retries >= 25) { if (retries >= 10) {
log.error("Failed to find user \"{}\" after {} retries.", id, retries); log.error("Failed to find user \"{}\" after {} retries.", id, retries);
return null; return null;
} }
User user = DiscordService.JDA.getUserById(id); User user = DiscordService.JDA.retrieveUserById(id).complete();
if (user == null) { if (user == null) {
return getUser(id, retries + 1); return getUser(id, retries + 1);
} }

@ -0,0 +1,22 @@
package cc.fascinated.bat.common.beatsaber.leaderboard;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor @Getter
public class Leaderboard {
/**
* The name of the leaderboard.
*/
private final String name;
/**
* The version of the leaderboard.
*/
private int curveVersion;
/**
* The curve of the leaderboard.
*/
private final LeaderboardCurvePoint[] curve;
}

@ -0,0 +1,10 @@
package cc.fascinated.bat.common.beatsaber.leaderboard;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter @AllArgsConstructor
public class LeaderboardCurvePoint {
private final double a;
private final double b;
}

@ -0,0 +1,167 @@
package cc.fascinated.bat.common.beatsaber.leaderboard.impl;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.common.beatsaber.leaderboard.Leaderboard;
import cc.fascinated.bat.common.beatsaber.leaderboard.LeaderboardCurvePoint;
import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberScoreToken;
import lombok.extern.log4j.Log4j2;
import java.util.ArrayList;
import java.util.List;
@Log4j2(topic = "ScoreSaber Leaderboard")
public class ScoreSaberLeaderboard extends Leaderboard {
public static final ScoreSaberLeaderboard INSTANCE = new ScoreSaberLeaderboard();
/**
* The base multiplier for stars.
*/
private final double starMultiplier = 42.11;
/**
* no idea, ngl
*/
private final double weightCoefficient = 0.965;
public ScoreSaberLeaderboard() {
super("ScoreSaber", 1, new LeaderboardCurvePoint[] {
new LeaderboardCurvePoint(1.0, 5.367394282890631),
new LeaderboardCurvePoint(0.9995, 5.019543595874787),
new LeaderboardCurvePoint(0.999, 4.715470646416203),
new LeaderboardCurvePoint(0.99825, 4.325027383589547),
new LeaderboardCurvePoint(0.9975, 3.996793606763322),
new LeaderboardCurvePoint(0.99625, 3.5526145337555373),
new LeaderboardCurvePoint(0.995, 3.2022017597337955),
new LeaderboardCurvePoint(0.99375, 2.9190155639254955),
new LeaderboardCurvePoint(0.9925, 2.685667856592722),
new LeaderboardCurvePoint(0.99125, 2.4902905794106913),
new LeaderboardCurvePoint(0.99, 2.324506282149922),
new LeaderboardCurvePoint(0.9875, 2.058947159052738),
new LeaderboardCurvePoint(0.985, 1.8563887693647105),
new LeaderboardCurvePoint(0.9825, 1.697536248647543),
new LeaderboardCurvePoint(0.98, 1.5702410055532239),
new LeaderboardCurvePoint(0.9775, 1.4664726399289512),
new LeaderboardCurvePoint(0.975, 1.3807102743105126),
new LeaderboardCurvePoint(0.9725, 1.3090333065057616),
new LeaderboardCurvePoint(0.97, 1.2485807759957321),
new LeaderboardCurvePoint(0.965, 1.1552120359501035),
new LeaderboardCurvePoint(0.96, 1.0871883573850478),
new LeaderboardCurvePoint(0.955, 1.0388633331418984),
new LeaderboardCurvePoint(0.95, 1.0),
new LeaderboardCurvePoint(0.94, 0.9417362980580238),
new LeaderboardCurvePoint(0.93, 0.9039994071865736),
new LeaderboardCurvePoint(0.92, 0.8728710341448851),
new LeaderboardCurvePoint(0.91, 0.8488375988124467),
new LeaderboardCurvePoint(0.9, 0.825756123560842),
new LeaderboardCurvePoint(0.875, 0.7816934560296046),
new LeaderboardCurvePoint(0.85, 0.7462290664143185),
new LeaderboardCurvePoint(0.825, 0.7150465663454271),
new LeaderboardCurvePoint(0.8, 0.6872268862950283),
new LeaderboardCurvePoint(0.75, 0.6451808210101443),
new LeaderboardCurvePoint(0.7, 0.6125565959114954),
new LeaderboardCurvePoint(0.65, 0.5866010012767576),
new LeaderboardCurvePoint(0.6, 0.18223233667439062),
new LeaderboardCurvePoint(0.0, 0.0)
});
}
/**
* Gets the modifier for the given accuracy.
*
* @param accuracy The accuracy.
* @return The modifier.
*/
public double getModifier(double accuracy) {
accuracy = MathUtils.clamp(accuracy, 0, 100) / 100;
LeaderboardCurvePoint prev = this.getCurve()[1];
for (LeaderboardCurvePoint point : this.getCurve()) {
if (point.getA() <= accuracy) {
double distance = (prev.getA() - accuracy) / (prev.getA() - point.getA());
return MathUtils.lerp(prev.getB(), point.getB(), distance);
}
prev = point;
}
return 0;
}
/**
* Gets the pp for the given accuracy and stars.
*
* @param accuracy The accuracy.
* @param stars The stars.
* @return The pp.
*/
public double getPP(double accuracy, double stars) {
double pp = stars * this.starMultiplier;
double modifier = this.getModifier(accuracy);
return modifier * pp;
}
/**
* Gets the total pp for the given scores.
*
* @param scores The scores.
* @return The total pp.
*/
private double getTotalPP(List<ScoreSaberScoreToken> scores, int startIdx) {
double totalPP = 0;
for (int i = 0; i < scores.size(); i++) {
totalPP += Math.pow(this.weightCoefficient, i + startIdx) * scores.get(i).getPp();
}
return totalPP;
}
/**
* Gets the pp at the given index for the given scores.
*
* @param bottomScores The scores.
* @param idx The index.
* @return The pp.
*/
private double getRawPPAtIdx(List<ScoreSaberScoreToken> bottomScores, int idx, double expected) {
double oldBottomPP = this.getTotalPP(bottomScores, idx);
double newBottomPP = this.getTotalPP(bottomScores, idx + 1);
return (expected + oldBottomPP - newBottomPP) / Math.pow(this.weightCoefficient, idx);
}
/**
* Gets the raw pp per global pp for the given scores.
*
* @param scores The scores.
* @param expectedPP The expected pp.
* @return The raw pp per global pp.
*/
public double getRawPerGlobalPP(List<ScoreSaberScoreToken> scores, double expectedPP) {
int left = 0;
int right = scores.size() - 1;
int boundaryIdx = -1;
// Sort by PP
scores.sort((a, b) -> Double.compare(b.getPp(), a.getPp()));
while (left <= right) {
int mid = (left + right) / 2;
double bottomPP = this.getTotalPP(scores.subList(mid, scores.size()), mid);
List<ScoreSaberScoreToken> bottomSlice = new ArrayList<>(scores.subList(mid, scores.size()));
bottomSlice.add(0, scores.get(mid));
double modifiedBottomPP = this.getTotalPP(bottomSlice, mid);
double diff = modifiedBottomPP - bottomPP;
if (diff > expectedPP) {
boundaryIdx = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
if (boundaryIdx == -1) {
return this.getRawPPAtIdx(scores, 0, expectedPP);
} else {
return this.getRawPPAtIdx(scores.subList(boundaryIdx + 1, scores.size()), boundaryIdx + 1, expectedPP);
}
}
}

@ -7,9 +7,14 @@ import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberLeaderboardT
import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberPlayerScoreToken; import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberPlayerScoreToken;
import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberScoreToken; import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberScoreToken;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Invite; import net.dv8tion.jda.api.entities.Invite;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.entities.sticker.GuildSticker;
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent; import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent; import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent;
import net.dv8tion.jda.api.events.channel.update.*; import net.dv8tion.jda.api.events.channel.update.*;
@ -27,6 +32,7 @@ import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateBoostTime
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent; import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent;
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateTimeOutEvent; import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateTimeOutEvent;
import net.dv8tion.jda.api.events.guild.override.GenericPermissionOverrideEvent; import net.dv8tion.jda.api.events.guild.override.GenericPermissionOverrideEvent;
import net.dv8tion.jda.api.events.guild.update.*;
import net.dv8tion.jda.api.events.guild.voice.GenericGuildVoiceEvent; import net.dv8tion.jda.api.events.guild.voice.GenericGuildVoiceEvent;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
@ -36,13 +42,14 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.events.message.MessageUpdateEvent; import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
import net.dv8tion.jda.api.events.role.RoleCreateEvent; import net.dv8tion.jda.api.events.role.RoleCreateEvent;
import net.dv8tion.jda.api.events.role.RoleDeleteEvent; import net.dv8tion.jda.api.events.role.RoleDeleteEvent;
import net.dv8tion.jda.api.events.role.update.RoleUpdateColorEvent; import net.dv8tion.jda.api.events.role.update.*;
import net.dv8tion.jda.api.events.role.update.RoleUpdateIconEvent; import net.dv8tion.jda.api.events.sticker.GuildStickerAddedEvent;
import net.dv8tion.jda.api.events.role.update.RoleUpdateNameEvent; import net.dv8tion.jda.api.events.sticker.GuildStickerRemovedEvent;
import net.dv8tion.jda.api.events.role.update.RoleUpdatePermissionsEvent; import net.dv8tion.jda.api.events.sticker.update.GuildStickerUpdateNameEvent;
import net.dv8tion.jda.api.events.user.update.UserUpdateAvatarEvent; import net.dv8tion.jda.api.events.user.update.UserUpdateAvatarEvent;
import net.dv8tion.jda.api.events.user.update.UserUpdateGlobalNameEvent; import net.dv8tion.jda.api.events.user.update.UserUpdateGlobalNameEvent;
import net.dv8tion.jda.api.events.user.update.UserUpdateNameEvent; import net.dv8tion.jda.api.events.user.update.UserUpdateNameEvent;
import net.dv8tion.jda.api.interactions.DiscordLocale;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.List; import java.util.List;
@ -181,6 +188,93 @@ public interface EventListener {
default void onChannelUpdateSlowmode(@NonNull BatGuild guild, @NonNull ChannelUpdateSlowmodeEvent event) { default void onChannelUpdateSlowmode(@NonNull BatGuild guild, @NonNull ChannelUpdateSlowmodeEvent event) {
} }
default void onGuildUpdateName(@NonNull BatGuild guild, String oldName, String newName, @NonNull GuildUpdateNameEvent event) {
}
default void onGuildStickerAdd(@NonNull BatGuild guild, @NonNull GuildSticker sticker, @NonNull GuildStickerAddedEvent event) {
}
default void onGuildStickerRemove(@NonNull BatGuild guild, @NonNull GuildSticker sticker, @NonNull GuildStickerRemovedEvent event) {
}
default void onGuildStickerRename(@NonNull BatGuild guild, @NonNull GuildSticker sticker, @NonNull String oldName,
@NonNull String newName, @NonNull GuildStickerUpdateNameEvent event) {
}
default void onGuildUpdateAfkChannel(@NonNull BatGuild guild, VoiceChannel oldChannel, VoiceChannel newChannel, @NonNull GuildUpdateAfkChannelEvent event) {
}
default void onGuildUpdateAfkTimeout(@NonNull BatGuild guild, Guild.Timeout oldTimeout, Guild.Timeout newTimeout, @NonNull GuildUpdateAfkTimeoutEvent event) {
}
default void onGuildUpdateBanner(@NonNull BatGuild guild, String oldBannerUrl, String newBannerUrl, @NonNull GuildUpdateBannerEvent event) {
}
default void onGuildUpdateDescription(@NonNull BatGuild guild, String oldDescription, String newDescription, @NonNull GuildUpdateDescriptionEvent event) {
}
default void onGuildUpdateIcon(@NonNull BatGuild guild, String oldIconUrl, String newIconUrl, @NonNull GuildUpdateIconEvent event) {
}
default void onGuildUpdateLocale(@NonNull BatGuild guild, DiscordLocale oldLocale, DiscordLocale newLocale, @NonNull GuildUpdateLocaleEvent event) {
}
default void onGuildUpdateCommunityUpdatesChannel(@NonNull BatGuild guild, TextChannel oldChannel, TextChannel newChannel,
@NonNull GuildUpdateCommunityUpdatesChannelEvent event) {
}
default void onGuildUpdateBoostTier(@NonNull BatGuild guild, Guild.BoostTier oldTier, Guild.BoostTier newTier, @NonNull GuildUpdateBoostTierEvent event) {
}
default void onGuildUpdateMaxMembers(@NonNull BatGuild guild, int oldCount, int newCount, @NonNull GuildUpdateMaxMembersEvent event) {
}
default void onGuildUpdateExplicitContentLevel(@NonNull BatGuild guild, Guild.ExplicitContentLevel oldLevel, Guild.ExplicitContentLevel newLevel,
@NonNull GuildUpdateExplicitContentLevelEvent event) {
}
default void onGuildUpdateMFALevel(@NonNull BatGuild guild, Guild.MFALevel oldLevel, Guild.MFALevel newLevel, @NonNull GuildUpdateMFALevelEvent event) {
}
default void onGuildUpdateNotificationLevel(@NonNull BatGuild guild, Guild.NotificationLevel oldLevel, Guild.NotificationLevel newLevel,
@NonNull GuildUpdateNotificationLevelEvent event) {
}
default void onGuildUpdateNSFWLevel(@NonNull BatGuild guild, Guild.NSFWLevel oldLevel, Guild.NSFWLevel newLevel, @NonNull GuildUpdateNSFWLevelEvent event) {
}
default void onGuildUpdateOwner(@NonNull BatGuild guild, @NonNull BatUser oldOwner, @NonNull BatUser newOwner, @NonNull GuildUpdateOwnerEvent event) {
}
default void onGuildUpdateRulesChannel(@NonNull BatGuild guild, TextChannel oldChannel, TextChannel newChannel, @NonNull GuildUpdateRulesChannelEvent event) {
}
default void onGuildUpdateSystemChannel(@NonNull BatGuild guild, TextChannel oldChannel, TextChannel newChannel, @NonNull GuildUpdateSystemChannelEvent event) {
}
default void onGuildUpdateVanityCode(@NonNull BatGuild guild, String oldCode, String newCode, @NonNull GuildUpdateVanityCodeEvent event) {
}
default void onGuildUpdateVerificationLevel(@NonNull BatGuild guild, Guild.VerificationLevel oldLevel, Guild.VerificationLevel newLevel,
@NonNull GuildUpdateVerificationLevelEvent event) {
}
default void onGuildUpdateSplash(@NonNull BatGuild guild, String oldSplashUrl, String newSplashUrl, @NonNull GuildUpdateSplashEvent event) {
}
default void onChannelUpdateArchived(@NonNull BatGuild guild, @NonNull Channel channel, boolean isArchived, @NonNull ChannelUpdateArchivedEvent event) {
}
default void onRoleUpdatePosition(@NonNull BatGuild guild, @NonNull Role role, int oldPosition, int newPosition, @NonNull RoleUpdatePositionEvent event) {
}
default void onChannelUpdatePosition(@NonNull BatGuild guild, @NonNull Channel channel, int oldPosition, int newPosition,
@NonNull ChannelUpdatePositionEvent event) {
}
default void onRoleUpdateHoisted(@NonNull BatGuild guild, @NonNull Role role, boolean wasHoisted, boolean isHoisted, @NonNull RoleUpdateHoistedEvent event) {
}
default void onShutdown() { default void onShutdown() {
} }
} }

@ -1,10 +0,0 @@
package cc.fascinated.bat.exception;
/**
* @author Fascinated (fascinated7)
*/
public class BatException extends Exception {
public BatException(String message) {
super(message);
}
}

@ -1,13 +0,0 @@
package cc.fascinated.bat.exception.spotify;
import lombok.experimental.StandardException;
/**
* @author Fascinated (fascinated7)
*/
@StandardException
public class SpotifyTokenRefreshException extends RuntimeException {
public SpotifyTokenRefreshException(String message) {
super(message);
}
}

@ -1,7 +1,6 @@
package cc.fascinated.bat.features; package cc.fascinated.bat.features;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.service.CommandService; import cc.fascinated.bat.service.CommandService;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
@ -20,16 +19,16 @@ public abstract class Feature {
*/ */
private final String name; private final String name;
/**
* The default state of the feature
*/
private final FeatureProfile.FeatureState defaultState;
/** /**
* The description of the feature * The description of the feature
*/ */
public final boolean canBeDisabled; public final boolean canBeDisabled;
/**
* The category of the feature
*/
private final Category category;
/** /**
* Registers the command for the feature * Registers the command for the feature
* *

@ -1,11 +1,11 @@
package cc.fascinated.bat.features.base.profile; package cc.fascinated.bat.features;
import cc.fascinated.bat.common.Serializable; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.features.Feature;
import com.google.gson.Gson; import com.google.gson.Gson;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.NonNull;
import org.bson.Document; import org.bson.Document;
import java.util.HashMap; import java.util.HashMap;
@ -16,8 +16,6 @@ import java.util.Map;
*/ */
@NoArgsConstructor @NoArgsConstructor
public class FeatureProfile extends Serializable { public class FeatureProfile extends Serializable {
private static final FeatureState DEFAULT_STATE = FeatureState.ENABLED;
/** /**
* The feature states * The feature states
*/ */
@ -28,13 +26,13 @@ public class FeatureProfile extends Serializable {
* *
* @return the feature states * @return the feature states
*/ */
public FeatureState getFeatureState(Feature feature) { public FeatureState getFeatureState(@NonNull Feature feature) {
if (feature == null) { if (!feature.isCanBeDisabled()) {
return DEFAULT_STATE; return FeatureProfile.FeatureState.ENABLED;
} }
String featureName = feature.getName().toUpperCase(); String featureName = feature.getName().toUpperCase();
if (!this.featureStates.containsKey(featureName)) { if (!this.featureStates.containsKey(featureName)) {
this.featureStates.put(featureName, DEFAULT_STATE); this.featureStates.put(featureName, feature.getDefaultState());
} }
return this.featureStates.get(featureName); return this.featureStates.get(featureName);
} }

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

@ -1,6 +1,7 @@
package cc.fascinated.bat.features.afk.command; package cc.fascinated.bat.features.afk.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.MemberUtils; import cc.fascinated.bat.common.MemberUtils;
import cc.fascinated.bat.features.afk.profile.AfkProfile; import cc.fascinated.bat.features.afk.profile.AfkProfile;
@ -8,6 +9,7 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
@ -19,7 +21,7 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "afk", description = "Sets your AFK status") @CommandInfo(name = "afk", description = "Sets your AFK status", category = Category.GENERAL)
public class AfkCommand extends BatCommand { public class AfkCommand extends BatCommand {
public AfkCommand() { public AfkCommand() {
super.addOptions(new OptionData(OptionType.STRING, "reason", "The reason for being AFK", false)); super.addOptions(new OptionData(OptionType.STRING, "reason", "The reason for being AFK", false));
@ -28,14 +30,16 @@ public class AfkCommand extends BatCommand {
/** /**
* Fired when this command is executed. * Fired when this command is executed.
* *
* @param guild the guild the command was executed in, if any * @param guild the guild the command was executed in, if any
* @param user the user who executed the command * @param user the user who executed the command
* @param channel the channel the command was executed in * @param channel the channel the command was executed in
* @param member the member who executed the command, null if not a guild * @param member the member who executed the command, null if not a guild
* @param event the event that invoked this command * @param commandMessage
* @param arguments
* @param event the event that invoked this command
*/ */
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { 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); AfkProfile profile = guild.getProfile(AfkProfile.class);
String reason = null; String reason = null;
OptionMapping reasonOption = event.getOption("reason"); OptionMapping reasonOption = event.getOption("reason");

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

@ -9,6 +9,7 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
@ -30,7 +31,7 @@ public class AddSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
// Check if the guild has reached the maximum auto roles count // Check if the guild has reached the maximum auto roles count
int maxRoleSlots = AutoRoleProfile.getMaxRoleSlots(guild); int maxRoleSlots = AutoRoleProfile.getMaxRoleSlots(guild);
@ -43,12 +44,7 @@ public class AddSubCommand extends BatCommand {
} }
OptionMapping option = event.getOption("role"); OptionMapping option = event.getOption("role");
if (option == null) { assert option != null;
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Please provide a role to add")
.build()).queue();
return;
}
Role role = option.getAsRole(); Role role = option.getAsRole();
// Check if the role is already in the auto roles list // Check if the role is already in the auto roles list

@ -1,6 +1,7 @@
package cc.fascinated.bat.features.autorole.command; package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
@ -11,14 +12,20 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component("autoroles.command") @Component("autoroles.command")
@CommandInfo(name = "autorole", description = "Set up the automatic role system for members on join", requiredPermissions = Permission.MANAGE_SERVER) @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 class AutoRoleCommand extends BatCommand {
public AutoRoleCommand(@NonNull ApplicationContext context) { public AutoRoleCommand(@NonNull ApplicationContext context) {
super.addSubCommands( super.addSubCommands(
context.getBean(ListSubCommand.class), context.getBean(ListSubCommand.class),
context.getBean(AddSubCommand.class), context.getBean(AddSubCommand.class),
context.getBean(RemoveSubCommand.class), context.getBean(RemoveSubCommand.class),
context.getBean(ClearSubCommand.class) context.getBean(ClearSubCommand.class),
context.getBean(SyncSubCommand.class)
); );
} }
} }

@ -8,6 +8,7 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -19,7 +20,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "clear", description = "Clears all auto roles") @CommandInfo(name = "clear", description = "Clears all auto roles")
public class ClearSubCommand extends BatCommand { public class ClearSubCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
profile.reset(); profile.reset();

@ -9,6 +9,7 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -20,7 +21,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "list", description = "Lists all auto roles") @CommandInfo(name = "list", description = "Lists all auto roles")
public class ListSubCommand extends BatCommand { public class ListSubCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
if (profile.getRoles().isEmpty()) { if (profile.getRoles().isEmpty()) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -8,6 +8,7 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
@ -29,15 +30,10 @@ public class RemoveSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
OptionMapping option = event.getOption("role"); OptionMapping option = event.getOption("role");
if (option == null) { assert option != null;
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Please provide a role to remove")
.build()).queue();
return;
}
Role role = option.getAsRole(); Role role = option.getAsRole();
if (!profile.hasRole(role.getId())) { if (!profile.hasRole(role.getId())) {

@ -0,0 +1,113 @@
package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
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 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,10 +1,11 @@
package cc.fascinated.bat.features.base; package cc.fascinated.bat.features.base;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.base.commands.botadmin.premium.PremiumAdminCommand; import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.base.commands.discord.LookupUserCommand; import cc.fascinated.bat.features.base.commands.botadmin.BotAdminCommand;
import cc.fascinated.bat.features.base.commands.fun.CoinFlipCommand;
import cc.fascinated.bat.features.base.commands.fun.EightBallCommand; import cc.fascinated.bat.features.base.commands.fun.EightBallCommand;
import cc.fascinated.bat.features.base.commands.fun.PPSizeCommand;
import cc.fascinated.bat.features.base.commands.fun.image.ImageCommand; import cc.fascinated.bat.features.base.commands.fun.image.ImageCommand;
import cc.fascinated.bat.features.base.commands.general.*; import cc.fascinated.bat.features.base.commands.general.*;
import cc.fascinated.bat.features.base.commands.general.avatar.AvatarCommand; import cc.fascinated.bat.features.base.commands.general.avatar.AvatarCommand;
@ -13,6 +14,8 @@ import cc.fascinated.bat.features.base.commands.server.MemberCountCommand;
import cc.fascinated.bat.features.base.commands.server.PremiumCommand; import cc.fascinated.bat.features.base.commands.server.PremiumCommand;
import cc.fascinated.bat.features.base.commands.server.channel.ChannelCommand; import cc.fascinated.bat.features.base.commands.server.channel.ChannelCommand;
import cc.fascinated.bat.features.base.commands.server.feature.FeatureCommand; import cc.fascinated.bat.features.base.commands.server.feature.FeatureCommand;
import cc.fascinated.bat.features.base.commands.utility.PastebinCommand;
import cc.fascinated.bat.features.base.commands.utility.lookup.LookupCommand;
import cc.fascinated.bat.service.CommandService; import cc.fascinated.bat.service.CommandService;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -26,10 +29,10 @@ import org.springframework.stereotype.Component;
public class BaseFeature extends Feature { public class BaseFeature extends Feature {
@Autowired @Autowired
public BaseFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) { public BaseFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Base", false, Category.GENERAL); super("Base", FeatureProfile.FeatureState.ENABLED, false);
super.registerCommand(commandService, context.getBean(PremiumCommand.class)); super.registerCommand(commandService, context.getBean(PremiumCommand.class));
super.registerCommand(commandService, context.getBean(PremiumAdminCommand.class)); super.registerCommand(commandService, context.getBean(BotAdminCommand.class));
super.registerCommand(commandService, context.getBean(MemberCountCommand.class)); super.registerCommand(commandService, context.getBean(MemberCountCommand.class));
super.registerCommand(commandService, context.getBean(ChannelCommand.class)); super.registerCommand(commandService, context.getBean(ChannelCommand.class));
super.registerCommand(commandService, context.getBean(VoteCommand.class)); super.registerCommand(commandService, context.getBean(VoteCommand.class));
@ -42,6 +45,9 @@ public class BaseFeature extends Feature {
super.registerCommand(commandService, context.getBean(ImageCommand.class)); super.registerCommand(commandService, context.getBean(ImageCommand.class));
super.registerCommand(commandService, context.getBean(FeatureCommand.class)); super.registerCommand(commandService, context.getBean(FeatureCommand.class));
super.registerCommand(commandService, context.getBean(EightBallCommand.class)); super.registerCommand(commandService, context.getBean(EightBallCommand.class));
super.registerCommand(commandService, context.getBean(LookupUserCommand.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));
} }
} }

@ -0,0 +1,25 @@
package cc.fascinated.bat.features.base.commands.botadmin;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.features.base.commands.botadmin.premium.PremiumRemoveSubCommand;
import cc.fascinated.bat.features.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)
);
}
}

@ -1,23 +0,0 @@
package cc.fascinated.bat.features.base.commands.botadmin.premium;
import cc.fascinated.bat.command.BatCommand;
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)
public class PremiumAdminCommand extends BatCommand {
@Autowired
public PremiumAdminCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(SetSubCommand.class),
context.getBean(RemoveSubCommand.class)
);
}
}

@ -8,6 +8,7 @@ import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.GuildService; import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
@ -20,18 +21,18 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "remove", description = "Remove premium from a guild") @CommandInfo(name = "premium-remove", description = "Remove premium from a guild")
public class RemoveSubCommand extends BatCommand { public class PremiumRemoveSubCommand extends BatCommand {
private final GuildService guildService; private final GuildService guildService;
@Autowired @Autowired
public RemoveSubCommand(GuildService guildService) { public PremiumRemoveSubCommand(GuildService guildService) {
this.guildService = guildService; this.guildService = guildService;
super.addOptions(new OptionData(OptionType.STRING, "guild", "The guild id to set as premium", true)); super.addOptions(new OptionData(OptionType.STRING, "guild", "The guild id to set as premium", true));
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping guildOption = event.getOption("guild"); OptionMapping guildOption = event.getOption("guild");
if (guildOption == null) { if (guildOption == null) {
event.reply("Please provide a guild id").queue(); event.reply("Please provide a guild id").queue();

@ -8,6 +8,7 @@ import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.GuildService; import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
@ -20,12 +21,12 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "set", description = "Adds premium to a guild") @CommandInfo(name = "premium-set", description = "Adds premium to a guild")
public class SetSubCommand extends BatCommand { public class PremiumSetSubCommand extends BatCommand {
private final GuildService guildService; private final GuildService guildService;
@Autowired @Autowired
public SetSubCommand(@NonNull GuildService guildService) { public PremiumSetSubCommand(@NonNull GuildService guildService) {
this.guildService = guildService; this.guildService = guildService;
super.addOptions( super.addOptions(
new OptionData(OptionType.STRING, "guild", "The guild id to set as premium", true), new OptionData(OptionType.STRING, "guild", "The guild id to set as premium", true),
@ -34,7 +35,7 @@ public class SetSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping guildOption = event.getOption("guild"); OptionMapping guildOption = event.getOption("guild");
if (guildOption == null) { if (guildOption == null) {
event.reply("Please provide a guild id").queue(); event.reply("Please provide a guild id").queue();

@ -1,81 +0,0 @@
package cc.fascinated.bat.features.base.commands.discord;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.LongUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.DiscordService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.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 = "lookupuser", description = "Lookup a user", userInstall = true)
public class LookupUserCommand extends BatCommand {
public LookupUserCommand() {
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, @NonNull SlashCommandInteraction event) {
OptionMapping idOption = event.getOption("id");
if (idOption == null) {
return;
}
String id = idOption.getAsString();
if (!LongUtils.isLong(id)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You need to provide a valid user id")
.build())
.setEphemeral(true)
.queue();
return;
}
DiscordService.JDA.retrieveUserById(id).queue(target -> {
if (target == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("User `%s` not found".formatted(id))
.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(flag.getName()).append(", ");
}
target.retrieveProfile().queue(profile -> event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new EmbedDescriptionBuilder("User Lookup")
.appendLine("Name: `%s`".formatted(target.getGlobalName()), 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,40 @@
package cc.fascinated.bat.features.base.commands.fun;
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 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";
}
}

@ -1,14 +1,15 @@
package cc.fascinated.bat.features.base.commands.fun; package cc.fascinated.bat.features.base.commands.fun;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.interactions.commands.build.OptionData;
@ -18,7 +19,13 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "8ball", description = "Ask the magic 8ball a question", guildOnly = false, userInstall = true) @CommandInfo(
name = "8ball", description = "Ask the magic 8ball a question",
guildOnly = false,
userInstall = true,
prefixAllowed = true,
category = Category.FUN
)
public class EightBallCommand extends BatCommand { public class EightBallCommand extends BatCommand {
private final String[] responses = new String[]{ private final String[] responses = new String[]{
"It is certain", "It is certain",
@ -48,16 +55,18 @@ public class EightBallCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping questionOption = event.getOption("question"); String question = super.getArgument("question", arguments, 0, true, event).getAsString();
if (questionOption == null) { if (question == null) {
super.replyEmbed(commandMessage, event, EmbedUtils.errorEmbed()
.setDescription("You need to provide a question to ask the 8ball")
);
return; return;
} }
String question = questionOption.getAsString();
String response = responses[(int) (Math.random() * responses.length)]; String response = responses[(int) (Math.random() * responses.length)];
event.replyEmbeds(EmbedUtils.successEmbed() super.replyEmbed(commandMessage, event, EmbedUtils.successEmbed()
.setDescription("You asked: `%s`\n\n:8ball: The magic 8ball says: `%s`".formatted(question, response)) .setDescription("You asked: `%s`\n\n:8ball: The magic 8ball says: `%s`".formatted(question, response))
.build()) );
.queue();
} }
} }

@ -0,0 +1,57 @@
package cc.fascinated.bat.features.base.commands.fun;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.MathUtils;
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.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 size = (int) MathUtils.random(1, 12);
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new DescriptionBuilder("PP Size")
.appendLine("""
The size of %s's pp is %s inches
**8%sD**
""".formatted(
target.getAsMention(),
size,
"=".repeat(size)
), false)
.build())
.build()).queue();
}
}

@ -9,6 +9,7 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.thecatapi.CatImageToken; import cc.fascinated.bat.model.token.thecatapi.CatImageToken;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -20,7 +21,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "cat", description = "Get a random cat image") @CommandInfo(name = "cat", description = "Get a random cat image")
public class CatSubCommand extends BatCommand { public class CatSubCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { 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); CatImageToken[] responseEntity = WebRequest.getAsEntity("https://api.thecatapi.com/v1/images/search", CatImageToken[].class);
if (responseEntity == null || responseEntity.length == 0) { if (responseEntity == null || responseEntity.length == 0) {
event.reply("Failed to get a cat image!").queue(); event.reply("Failed to get a cat image!").queue();

@ -9,6 +9,7 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.dogceo.RandomImage; import cc.fascinated.bat.model.token.dogceo.RandomImage;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -20,7 +21,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "dog", description = "Get a random dog image") @CommandInfo(name = "dog", description = "Get a random dog image")
public class DogSubCommand extends BatCommand { public class DogSubCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { 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); RandomImage responseEntity = WebRequest.getAsEntity("https://dog.ceo/api/breeds/image/random", RandomImage.class);
if (responseEntity == null) { if (responseEntity == null) {
event.reply("Failed to get a dog image!").queue(); event.reply("Failed to get a dog image!").queue();

@ -9,6 +9,7 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.randomd.RandomDuck; import cc.fascinated.bat.model.token.randomd.RandomDuck;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -20,7 +21,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "duck", description = "Get a random duck image") @CommandInfo(name = "duck", description = "Get a random duck image")
public class DuckSubCommand extends BatCommand { public class DuckSubCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { 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); RandomDuck responseEntity = WebRequest.getAsEntity("https://random-d.uk/api/v2/random", RandomDuck.class);
if (responseEntity == null) { if (responseEntity == null) {
event.reply("Failed to get a duck image!").queue(); event.reply("Failed to get a duck image!").queue();

@ -9,6 +9,7 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.randomfox.RandomFoxToken; import cc.fascinated.bat.model.token.randomfox.RandomFoxToken;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -20,7 +21,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "fox", description = "Get a random fox image") @CommandInfo(name = "fox", description = "Get a random fox image")
public class FoxSubCommand extends BatCommand { public class FoxSubCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { 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); RandomFoxToken responseEntity = WebRequest.getAsEntity("https://randomfox.ca/floof/", RandomFoxToken.class);
if (responseEntity == null) { if (responseEntity == null) {
event.reply("Failed to get a fox image!").queue(); event.reply("Failed to get a fox image!").queue();

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

@ -1,38 +1,38 @@
package cc.fascinated.bat.features.base.commands.general; package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.Consts; import cc.fascinated.bat.Consts;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category; import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.InteractionBuilder;
import cc.fascinated.bat.event.EventListener; import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.CommandService; import cc.fascinated.bat.service.CommandService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; 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.SlashCommandInteraction;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "help", description = "View the bots command categories.", guildOnly = false) @CommandInfo(
name = "help",
description = "View the bots command categories",
userInstall = true,
category = Category.GENERAL
)
public class HelpCommand extends BatCommand implements EventListener { public class HelpCommand extends BatCommand implements EventListener {
private final CommandService commandService; private final CommandService commandService;
@ -42,62 +42,53 @@ public class HelpCommand extends BatCommand implements EventListener {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
event.replyEmbeds(createHomeEmbed()).addComponents(createHomeActions()).queue(); 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;
}
@Override interactionBuilder.addStringSelect(
public void onStringSelectInteraction(BatGuild guild, @NonNull BatUser user, @NonNull StringSelectInteractionEvent event) { category.getName(),
String item = event.getSelectedOptions().get(0).getValue(); "View commands in the %s category".formatted(category.getName()),
if (item.equalsIgnoreCase("home")) { category.getEmoji(),
event.editMessageEmbeds(createHomeEmbed()).queue(); (buttonEvent) -> {
return; 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();
});
} }
Category category = Category.getByName(item);
if (category == null) {
event.reply("Invalid category selected.").queue();
return;
}
event.reply("hello this doesnt work yet").queue();
// StringBuilder commands = new StringBuilder(); event.replyEmbeds(createHomeEmbed(event.isFromGuild())).addComponents(interactionBuilder.build()).queue();
// List<BatCommand> categoryCommands = commandService.getCommandsByCategory(category, true);
// if (categoryCommands.isEmpty()) {
// commands = new StringBuilder("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.append("</%s %s:%s> - %s\n".formatted(
// command.getCommandInfo().name(),
// commandData.getName(),
// subCommand.getCommandSnowflake(),
// commandData.getDescription()
// ));
// }
// continue;
// }
// commands.append("</%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.toString()
// )).build()).queue();
} }
/** /**
@ -105,10 +96,13 @@ public class HelpCommand extends BatCommand implements EventListener {
* *
* @return The home embed * @return The home embed
*/ */
private MessageEmbed createHomeEmbed() { private MessageEmbed createHomeEmbed(boolean ranInsideGuild) {
StringBuilder categories = new StringBuilder(); StringBuilder categories = new StringBuilder();
for (Category category : Category.values()) { for (Category category : Category.values()) {
long commandCount = commandService.getCommandsByCategory(category).size(); if (commandService.getCommandsByCategory(category, ranInsideGuild).isEmpty()) {
continue;
}
long commandCount = commandService.getCommandsByCategory(category, ranInsideGuild).size();
categories.append("➜ %s - **%s Command%s**\n".formatted( categories.append("➜ %s - **%s Command%s**\n".formatted(
category.getName(), category.getName(),
commandCount, commandCount,
@ -118,40 +112,16 @@ public class HelpCommand extends BatCommand implements EventListener {
return EmbedUtils.genericEmbed() return EmbedUtils.genericEmbed()
.setDescription(""" .setDescription("""
**Welcome to the Bat Help Menu!** **Welcome to the Bat Help Menu!**%s
%s %s
*View our [TOS](%s) and [Privacy Policy](%s) for more information.* *View our [TOS](%s) and [Privacy Policy](%s) for more information.*
""".formatted( """.formatted(
!ranInsideGuild ? "\n*guild only commands won't be shown here*" : "",
categories.toString(), categories.toString(),
Consts.TERMS_OF_SERVICE_URL, Consts.TERMS_OF_SERVICE_URL,
Consts.PRIVACY_POLICY_URL Consts.PRIVACY_POLICY_URL
)) ))
.build(); .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")));
for (Category category : Category.values()) {
options.add(SelectOption.of(category.getName(), category.getName()).withEmoji(category.getEmoji()));
}
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()
)
};
}
} }

@ -2,12 +2,14 @@ package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.Consts; import cc.fascinated.bat.Consts;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -16,10 +18,10 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @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 { public class InviteCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
event.replyEmbeds(EmbedUtils.genericEmbed() event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("You can invite the bot to your server by clicking [here](%s)".formatted(Consts.INVITE_URL)) .setDescription("You can invite the bot to your server by clicking [here](%s)".formatted(Consts.INVITE_URL))
.build()) .build())

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

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

@ -1,6 +1,7 @@
package cc.fascinated.bat.features.base.commands.general.avatar; package cc.fascinated.bat.features.base.commands.general.avatar;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -11,7 +12,7 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component @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 { public class AvatarCommand extends BatCommand {
@Autowired @Autowired
public AvatarCommand(@NonNull ApplicationContext context) { public AvatarCommand(@NonNull ApplicationContext context) {

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

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

@ -1,6 +1,7 @@
package cc.fascinated.bat.features.base.commands.general.banner; package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -11,7 +12,7 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component @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 { public class BannerCommand extends BatCommand {
@Autowired @Autowired
public BannerCommand(@NonNull ApplicationContext context) { public BannerCommand(@NonNull ApplicationContext context) {

@ -1,12 +1,14 @@
package cc.fascinated.bat.features.base.commands.general.banner; package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.utils.ImageProxy; import net.dv8tion.jda.api.utils.ImageProxy;
@ -16,10 +18,10 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component("banner:guild.sub") @Component("banner:guild.sub")
@CommandInfo(name = "guild", description = "View the banner of the guild") @CommandInfo(name = "guild", description = "View the banner of the guild", category = Category.GENERAL)
public class GuildSubCommand extends BatCommand { public class GuildSubCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
ImageProxy banner = guild.getDiscordGuild().getBanner(); ImageProxy banner = guild.getDiscordGuild().getBanner();
if (banner == null) { if (banner == null) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -1,12 +1,14 @@
package cc.fascinated.bat.features.base.commands.general.banner; package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
@ -20,23 +22,16 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component("banner:user.sub") @Component("banner:user.sub")
@CommandInfo(name = "user", description = "View the banner of a user", guildOnly = false) @CommandInfo(name = "user", description = "View the banner of a user", guildOnly = false, category = Category.GENERAL)
public class UserSubCommand extends BatCommand { public class UserSubCommand extends BatCommand {
public UserSubCommand() { public UserSubCommand() {
super.addOptions(new OptionData(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 @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping userOption = event.getOption("user"); OptionMapping userOption = event.getOption("user");
if (userOption == null) { assert userOption != null;
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a user to view the banner of!")
.build())
.queue();
return;
}
User target = userOption.getAsUser(); User target = userOption.getAsUser();
ImageProxy banner = target.retrieveProfile().complete().getBanner(); ImageProxy banner = target.retrieveProfile().complete().getBanner();
if (banner == null) { if (banner == null) {

@ -1,6 +1,5 @@
package cc.fascinated.bat.features.base.commands.server; package cc.fascinated.bat.features.base.commands.server;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
@ -8,16 +7,13 @@ import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/** /**
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@ -25,14 +21,10 @@ import java.util.Map;
@CommandInfo(name = "membercount", description = "View the member count of the server!") @CommandInfo(name = "membercount", description = "View the member count of the server!")
public class MemberCountCommand extends BatCommand { public class MemberCountCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
Guild discordGuild = guild.getDiscordGuild(); Guild discordGuild = guild.getDiscordGuild();
int totalMembers = 0, totalUsers = 0, totalBots = 0; int totalMembers = 0, totalUsers = 0, totalBots = 0;
Map<OnlineStatus, Integer> memberCounts = new HashMap<>();
for (Member guildMember : discordGuild.getMembers()) { for (Member guildMember : discordGuild.getMembers()) {
OnlineStatus status = guildMember.getOnlineStatus();
memberCounts.put(status, memberCounts.getOrDefault(status, 0) + 1);
if (guildMember.getUser().isBot()) { if (guildMember.getUser().isBot()) {
totalBots++; totalBots++;
} else { } else {
@ -46,23 +38,10 @@ public class MemberCountCommand extends BatCommand {
Total Members: `%s` Total Members: `%s`
Total Users: `%s` Total Users: `%s`
Total Bots: `%s` Total Bots: `%s`
\s """.formatted(
**Member Presence**
%s Online: `%s`
%s Idle: `%s`
%s Do Not Disturb: `%s`
%s Offline: `%s`""".formatted(
NumberFormatter.format(totalMembers), NumberFormatter.format(totalMembers),
NumberFormatter.format(totalUsers), NumberFormatter.format(totalUsers),
NumberFormatter.format(totalBots), NumberFormatter.format(totalBots)
Emojis.ONLINE_EMOJI, )).build()).queue();
NumberFormatter.format(memberCounts.getOrDefault(OnlineStatus.ONLINE, 0)),
Emojis.IDLE_EMOJI,
NumberFormatter.format(memberCounts.getOrDefault(OnlineStatus.IDLE, 0)),
Emojis.DND_EMOJI,
NumberFormatter.format(memberCounts.getOrDefault(OnlineStatus.DO_NOT_DISTURB, 0)),
Emojis.OFFLINE_EMOJI,
NumberFormatter.format(memberCounts.getOrDefault(OnlineStatus.OFFLINE, 0))))
.build()).queue();
} }
} }

@ -10,6 +10,7 @@ import lombok.NonNull;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -21,7 +22,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "premium", description = "View the premium information for the guild", requiredPermissions = Permission.ADMINISTRATOR) @CommandInfo(name = "premium", description = "View the premium information for the guild", requiredPermissions = Permission.ADMINISTRATOR)
public class PremiumCommand extends BatCommand { public class PremiumCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
PremiumProfile premium = guild.getPremiumProfile(); PremiumProfile premium = guild.getPremiumProfile();
EmbedBuilder embed = EmbedUtils.genericEmbed().setAuthor("Premium Information"); EmbedBuilder embed = EmbedUtils.genericEmbed().setAuthor("Premium Information");
if (premium.hasPremium()) { if (premium.hasPremium()) {

@ -8,6 +8,7 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -27,7 +28,7 @@ public class RemoveTopicSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { 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(); Channel target = event.getOption("channel") == null ? channel : event.getOption("channel").getAsChannel();
if (!(target instanceof TextChannel textChannel)) { if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -8,6 +8,7 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -32,7 +33,7 @@ public class SetTopicSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { 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(); Channel target = event.getOption("channel") == null ? channel : Objects.requireNonNull(event.getOption("channel")).getAsChannel();
if (!(target instanceof TextChannel textChannel)) { if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -7,6 +7,7 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -26,7 +27,7 @@ public class ViewTopicSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { 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(); Channel target = event.getOption("channel") == null ? channel : event.getOption("channel").getAsChannel();
if (!(target instanceof TextChannel textChannel)) { if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -4,12 +4,13 @@ import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.base.profile.FeatureProfile; import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService; import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
@ -30,7 +31,7 @@ public class DisableSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
FeatureProfile featureProfile = guild.getFeatureProfile(); FeatureProfile featureProfile = guild.getFeatureProfile();
OptionMapping featureOption = event.getOption("feature"); OptionMapping featureOption = event.getOption("feature");
if (featureOption == null) { if (featureOption == null) {

@ -4,12 +4,13 @@ import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.base.profile.FeatureProfile; import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService; import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
@ -30,7 +31,7 @@ public class EnableSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
FeatureProfile featureProfile = guild.getFeatureProfile(); FeatureProfile featureProfile = guild.getFeatureProfile();
OptionMapping featureOption = event.getOption("feature"); OptionMapping featureOption = event.getOption("feature");
if (featureOption == null) { if (featureOption == null) {

@ -4,12 +4,13 @@ import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.base.profile.FeatureProfile; import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService; import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -21,7 +22,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "list", description = "Lists the features and their states") @CommandInfo(name = "list", description = "Lists the features and their states")
public class ListSubCommand extends BatCommand { public class ListSubCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
StringBuilder featureStates = new StringBuilder(); StringBuilder featureStates = new StringBuilder();
for (Feature feature : FeatureService.INSTANCE.getFeaturesSorted()) { for (Feature feature : FeatureService.INSTANCE.getFeaturesSorted()) {
FeatureProfile featureProfile = guild.getFeatureProfile(); FeatureProfile featureProfile = guild.getFeatureProfile();

@ -0,0 +1,54 @@
package cc.fascinated.bat.features.base.commands.utility;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.PasteUtils;
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.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();
}
}

@ -1,4 +1,4 @@
package cc.fascinated.bat.features.tmdb.command; package cc.fascinated.bat.features.base.commands.utility.lookup;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category; import cc.fascinated.bat.command.Category;
@ -12,13 +12,17 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component @Component
@CommandInfo(name = "tmdb", description = "Get information about movies and TV shows", guildOnly = false, userInstall = true, category = Category.MOVIES_TV) @CommandInfo(
public class TMDBCommand extends BatCommand { name = "lookup",
description = "View the banner of the guild or a user",
userInstall = true,
category = Category.UTILITY
)
public class LookupCommand extends BatCommand {
@Autowired @Autowired
public TMDBCommand(@NonNull ApplicationContext context) { public LookupCommand(@NonNull ApplicationContext context) {
super.addSubCommands( super.addSubCommands(
context.getBean(MovieSubCommand.class), context.getBean(UserSubCommand.class)
context.getBean(SeriesSubCommand.class)
); );
} }
} }

@ -0,0 +1,75 @@
package cc.fascinated.bat.features.base.commands.utility.lookup;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.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());
}
}

@ -1,8 +1,8 @@
package cc.fascinated.bat.features.birthday; package cc.fascinated.bat.features.birthday;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.event.EventListener; import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.birthday.command.BirthdayCommand; import cc.fascinated.bat.features.birthday.command.BirthdayCommand;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile; import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
@ -23,7 +23,7 @@ public class BirthdayFeature extends Feature implements EventListener {
private final GuildService guildService; private final GuildService guildService;
public BirthdayFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService, @NonNull GuildService guildService) { public BirthdayFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService, @NonNull GuildService guildService) {
super("Birthday", true, Category.UTILITY); super("Birthday", FeatureProfile.FeatureState.DISABLED, true);
this.guildService = guildService; this.guildService = guildService;
registerCommand(commandService, context.getBean(BirthdayCommand.class)); registerCommand(commandService, context.getBean(BirthdayCommand.class));
@ -36,7 +36,7 @@ public class BirthdayFeature extends Feature implements EventListener {
private void checkBirthdays() { private void checkBirthdays() {
for (Guild guild : DiscordService.JDA.getGuilds()) { for (Guild guild : DiscordService.JDA.getGuilds()) {
BatGuild batGuild = guildService.getGuild(guild.getId()); BatGuild batGuild = guildService.getGuild(guild.getId());
if (batGuild == null) { if (batGuild.getFeatureProfile().isFeatureDisabled(this)) { // Check if the feature is disabled
continue; continue;
} }
BirthdayProfile profile = batGuild.getBirthdayProfile(); BirthdayProfile profile = batGuild.getBirthdayProfile();

@ -58,7 +58,5 @@ public class UserBirthday extends Serializable {
} }
@Override @Override
public void reset() { public void reset() {}
}
} }

@ -1,6 +1,7 @@
package cc.fascinated.bat.features.birthday.command; package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -11,7 +12,7 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "birthday", description = "Modify your birthday settings.") @CommandInfo(name = "birthday", description = "Modify your birthday settings.", category = Category.UTILITY)
public class BirthdayCommand extends BatCommand { public class BirthdayCommand extends BatCommand {
@Autowired @Autowired
public BirthdayCommand(@NonNull ApplicationContext context) { public BirthdayCommand(@NonNull ApplicationContext context) {

@ -10,6 +10,7 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
@ -32,7 +33,7 @@ public class ChannelSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping option = event.getOption("channel"); OptionMapping option = event.getOption("channel");
if (option == null) { if (option == null) {

@ -9,6 +9,7 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
@ -30,17 +31,12 @@ public class MessageSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping messageOption = event.getOption("message"); OptionMapping messageOption = event.getOption("message");
if (messageOption == null) { assert messageOption != null;
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a message")
.build()).queue();
return;
}
String message = messageOption.getAsString(); String message = messageOption.getAsString();
if (message.length() > 2000) { if (message.length() > 2000) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The message must be less than 2000 characters") .setDescription("The message must be less than 2000 characters")

@ -9,6 +9,7 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
@ -30,17 +31,12 @@ public class PrivateSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping enabledOption = event.getOption("enabled"); OptionMapping enabledOption = event.getOption("enabled");
if (enabledOption == null) { assert enabledOption != null;
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide whether your birthday is private or not")
.build()).queue();
return;
}
boolean enabled = enabledOption.getAsBoolean(); boolean enabled = enabledOption.getAsBoolean();
UserBirthday birthday = profile.getBirthday(user.getId()); UserBirthday birthday = profile.getBirthday(user.getId());
if (birthday == null) { if (birthday == null) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -8,6 +8,7 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -20,7 +21,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "remove", description = "Remove your birthday from this guild") @CommandInfo(name = "remove", description = "Remove your birthday from this guild")
public class RemoveSubCommand extends BatCommand { public class RemoveSubCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
profile.removeBirthday(user.getId()); profile.removeBirthday(user.getId());

@ -9,6 +9,7 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
@ -36,7 +37,7 @@ public class SetSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
if (!profile.hasChannelSetup()) { if (!profile.hasChannelSetup()) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
@ -46,13 +47,9 @@ public class SetSubCommand extends BatCommand {
} }
OptionMapping birthdayOption = event.getOption("birthday"); OptionMapping birthdayOption = event.getOption("birthday");
if (birthdayOption == null) { assert birthdayOption != null;
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a birthday")
.build()).queue();
return;
}
String birthdayString = birthdayOption.getAsString(); String birthdayString = birthdayOption.getAsString();
Date birthday = parseBirthday(birthdayString); Date birthday = parseBirthday(birthdayString);
if (birthday == null) { if (birthday == null) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -10,6 +10,7 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService; import cc.fascinated.bat.service.UserService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
@ -34,7 +35,7 @@ public class ViewSubCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
if (!profile.hasChannelSetup()) { if (!profile.hasChannelSetup()) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -0,0 +1,81 @@
package cc.fascinated.bat.features.counter;
import cc.fascinated.bat.common.ChannelUtils;
import cc.fascinated.bat.common.NumberUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
@Setter
public class CounterChannel {
/**
* The channel id of this counter channel
*/
private final String channelId;
/**
* The current count of this counter channel
*/
private int currentCount;
/**
* Whether the counter should reset when a user
* inputs the wrong number or a non number
*/
private boolean breakable;
/**
* Increment the counter
*
* @param input the input to increment the counter with
* @return the return type of the increment
*/
public CounterResult incrementChannel(String input) {
int number = NumberUtils.parseInt(input);
// The number is invalid
if (number == -1 || number != this.nextNumber()) {
// The counter was broken
if (this.isBreakable()) {
this.reset();
return CounterResult.BROKEN;
}
return CounterResult.INVALID_NUMBER;
}
this.currentCount++;
return CounterResult.SUCCESS;
}
/**
* Gets the next number for this counter
*/
public int nextNumber() {
return currentCount + 1;
}
/**
* Resets the current count
* <p>
* This usually happens if the counter is broken
* </p>
*/
public void reset() {
currentCount = 0;
}
/**
* Gets the channel
*
* @return the channel
*/
public TextChannel getChannel() {
return ChannelUtils.getTextChannel(channelId);
}
}

@ -0,0 +1,23 @@
package cc.fascinated.bat.features.counter;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.counter.command.CounterCommand;
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 CounterFeature extends Feature {
@Autowired
public CounterFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Counter", FeatureProfile.FeatureState.DISABLED, true);
super.registerCommand(commandService, context.getBean(CounterCommand.class));
}
}

@ -0,0 +1,89 @@
package cc.fascinated.bat.features.counter;
import cc.fascinated.bat.common.Serializable;
import com.google.gson.Gson;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.bson.Document;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class CounterProfile extends Serializable {
/**
* The counter channels in this guild
*/
private final List<CounterChannel> counters = new ArrayList<>();
/**
* Gets the counter channel by the channel id
*
* @param channelId the id of the channel
* @return the counter channel
*/
public CounterChannel getCounterChannel(String channelId) {
for (CounterChannel counter : counters) {
if (counter.getChannelId().equals(channelId)) {
return counter;
}
}
return null;
}
/**
* Removes the channel from the counter channels
*
* @param channel the channel to remove
*/
public void removeChannel(TextChannel channel) {
CounterChannel counterChannel = getCounterChannel(channel.getId());
if (counterChannel != null) {
counters.remove(counterChannel);
}
}
/**
* Sets up the channel as a counter channel
*
* @param channel the channel to set as a counter channel
* @param breakable whether the counter should reset when a user inputs the wrong number or a non number
*/
public void setupChannel(TextChannel channel, boolean breakable) {
counters.add(new CounterChannel(channel.getId(), 0, breakable));
}
@Override
public void load(Document document, Gson gson) {
for (Document counterDocument : document.getList("counters", Document.class, new ArrayList<>())) {
counters.add(new CounterChannel(
counterDocument.getString("channelId"),
counterDocument.getInteger("currentCount"),
counterDocument.getBoolean("breakable")
));
}
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
List<Document> counterDocuments = new ArrayList<>();
for (CounterChannel counter : counters) {
Document counterDocument = new Document();
counterDocument.append("channelId", counter.getChannelId());
counterDocument.append("currentCount", counter.getCurrentCount());
counterDocument.append("breakable", counter.isBreakable());
counterDocuments.add(counterDocument);
}
document.append("counters", counterDocuments);
return document;
}
@Override
public void reset() {
counters.clear();
}
}

@ -0,0 +1,10 @@
package cc.fascinated.bat.features.counter;
/**
* @author Fascinated (fascinated7)
*/
public enum CounterResult {
SUCCESS,
BROKEN,
INVALID_NUMBER,
}

@ -0,0 +1,53 @@
package cc.fascinated.bat.features.counter;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class CountingListener implements EventListener {
private final CounterFeature counterFeature;
@Autowired
public CountingListener(@NonNull CounterFeature counterFeature) {
this.counterFeature = counterFeature;
}
@Override
public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
FeatureProfile featureProfile = guild.getFeatureProfile();
if (featureProfile.isFeatureDisabled(counterFeature)) {
return;
}
CounterProfile counterProfile = guild.getCounterProfile();
if (counterProfile == null) {
return;
}
CounterChannel counterChannel = counterProfile.getCounterChannel(event.getChannel().getId());
if (counterChannel == null) {
return;
}
CounterResult result = counterChannel.incrementChannel(event.getMessage().getContentRaw());
if (result == null) {
return;
}
switch (result) {
case SUCCESS -> event.getMessage().addReaction(Emoji.fromUnicode("")).queue();
case INVALID_NUMBER -> event.getMessage().addReaction(Emoji.fromUnicode("")).queue();
case BROKEN -> {
event.getMessage().addReaction(Emoji.fromUnicode("")).queue();
event.getMessage().reply("%s, you have broken the counter!".formatted(event.getAuthor().getAsMention())).queue();
}
}
}
}

@ -0,0 +1,26 @@
package cc.fascinated.bat.features.counter.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.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 = "counter", description = "View or configure the counter settings", requiredPermissions = Permission.MANAGE_CHANNEL)
public class CounterCommand extends BatCommand {
@Autowired
public CounterCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(SetupSubCommand.class),
context.getBean(SetSubCommand.class),
context.getBean(RemoveSubCommand.class),
context.getBean(SetBreakingSubCommand.class)
);
}
}

@ -0,0 +1,76 @@
package cc.fascinated.bat.features.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.features.counter.CounterChannel;
import cc.fascinated.bat.features.counter.CounterProfile;
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.Message;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
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.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("counter.remove:sub")
@CommandInfo(name = "remove", description = "Remove a counter channel")
public class RemoveSubCommand extends BatCommand {
public RemoveSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to set the count in", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping channelOption = event.getOption("channel");
assert channelOption != null;
GuildChannelUnion targetChannel = channelOption.getAsChannel();
if (targetChannel.getType() != ChannelType.TEXT) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel must be a text channel".formatted(Emojis.CROSS_MARK_EMOJI))
.build()).queue();
return;
}
TextChannel textChannel = targetChannel.asTextChannel();
CounterProfile profile = guild.getCounterProfile();
if (profile == null) {
return;
}
// Check if the channel is a counter channel
CounterChannel counterChannel = profile.getCounterChannel(textChannel.getId());
if (counterChannel == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel %s is not a counter channel".formatted(
Emojis.CROSS_MARK_EMOJI,
textChannel.getAsMention()
))
.build()).queue();
return;
}
profile.removeChannel(textChannel);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("""
%s Successfully removed the counter channel %s
The count was `%s`
""".formatted(
Emojis.CHECK_MARK_EMOJI,
textChannel.getAsMention(),
NumberFormatter.simpleFormat(counterChannel.getCurrentCount())
))
.build()).queue();
}
}

@ -0,0 +1,84 @@
package cc.fascinated.bat.features.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.counter.CounterChannel;
import cc.fascinated.bat.features.counter.CounterProfile;
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.Message;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
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.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("counter.setbreaking:sub")
@CommandInfo(name = "setbreaking", description = "Updates if the counter should break")
public class SetBreakingSubCommand extends BatCommand {
public SetBreakingSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to set the count in", true));
super.addOptions(new OptionData(OptionType.BOOLEAN, "breakable", "Should the channel break on a invalid or wrong number", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping channelOption = event.getOption("channel");
assert channelOption != null;
OptionMapping breakableOption = event.getOption("breakable");
assert breakableOption != null;
GuildChannelUnion targetChannel = channelOption.getAsChannel();
if (targetChannel.getType() != ChannelType.TEXT) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel must be a text channel".formatted(Emojis.CROSS_MARK_EMOJI))
.build()).queue();
return;
}
TextChannel textChannel = targetChannel.asTextChannel();
boolean breakable = breakableOption.getAsBoolean();
CounterProfile profile = guild.getCounterProfile();
if (profile == null) {
return;
}
// Check if the channel is a counter channel
CounterChannel counterChannel = profile.getCounterChannel(textChannel.getId());
if (counterChannel == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel %s is not a counter channel".formatted(
Emojis.CROSS_MARK_EMOJI,
textChannel.getAsMention()
))
.build()).queue();
return;
}
counterChannel.setBreakable(breakable);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("%s The counter in %s will %s break on an invalid or wrong number".formatted(
Emojis.CHECK_MARK_EMOJI,
textChannel.getAsMention(),
breakable ? "now" : "no longer"
))
.build()).queue();
if (channel.getId().equals(textChannel.getId())) { // Don't send in the same channel
return;
}
counterChannel.getChannel().sendMessage("The counter will %s break on an invalid or wrong number".formatted(
breakable ? "now" : "no longer"
)).queue();
}
}

@ -0,0 +1,81 @@
package cc.fascinated.bat.features.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.counter.CounterChannel;
import cc.fascinated.bat.features.counter.CounterProfile;
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.Message;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
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.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("counter.set:sub")
@CommandInfo(name = "set", description = "Set the current count in a channel")
public class SetSubCommand extends BatCommand {
public SetSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to set the count in", true));
super.addOptions(new OptionData(OptionType.INTEGER, "count", "The count to set", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping channelOption = event.getOption("channel");
assert channelOption != null;
OptionMapping countOption = event.getOption("count");
assert countOption != null;
GuildChannelUnion targetChannel = channelOption.getAsChannel();
if (targetChannel.getType() != ChannelType.TEXT) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel must be a text channel".formatted(Emojis.CROSS_MARK_EMOJI))
.build()).queue();
return;
}
TextChannel textChannel = targetChannel.asTextChannel();
int count = countOption.getAsInt();
CounterProfile profile = guild.getCounterProfile();
if (profile == null) {
return;
}
// Check if the channel is a counter channel
CounterChannel counterChannel = profile.getCounterChannel(textChannel.getId());
if (counterChannel == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel %s is not a counter channel".formatted(
Emojis.CROSS_MARK_EMOJI,
textChannel.getAsMention()
))
.build()).queue();
return;
}
counterChannel.setCurrentCount(count);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("%s Successfully set the count in %s to %d".formatted(
Emojis.CHECK_MARK_EMOJI,
textChannel.getAsMention(),
count
))
.build()).queue();
counterChannel.getChannel().sendMessage("The count has been set to `%s`".formatted(
count
)).queue();
}
}

@ -0,0 +1,67 @@
package cc.fascinated.bat.features.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.counter.CounterProfile;
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.Message;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
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.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("counter.setup:sub")
@CommandInfo(name = "setup", description = "Setup a counter for a channel")
public class SetupSubCommand extends BatCommand {
public SetupSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to setup the counter in", true));
super.addOptions(new OptionData(OptionType.BOOLEAN, "breakable", "Should the channel break on a invalid or wrong number", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping channelOption = event.getOption("channel");
assert channelOption != null;
OptionMapping breakableOption = event.getOption("breakable");
assert breakableOption != null;
GuildChannelUnion targetChannel = channelOption.getAsChannel();
if (targetChannel.getType() != ChannelType.TEXT) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel must be a text channel".formatted(Emojis.CROSS_MARK_EMOJI))
.build()).queue();
return;
}
TextChannel textChannel = targetChannel.asTextChannel();
CounterProfile profile = guild.getCounterProfile();
if (profile.getCounterChannel(textChannel.getId()) != null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s This channel is already a counter channel".formatted(Emojis.CROSS_MARK_EMOJI))
.build()).queue();
return;
}
boolean breakable = breakableOption.getAsBoolean();
profile.setupChannel(textChannel, breakable);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("%s Successfully setup the counter in %s".formatted(
Emojis.CHECK_MARK_EMOJI,
textChannel.getAsMention()
))
.build()).queue();
}
}

@ -1,7 +1,7 @@
package cc.fascinated.bat.features.drag; package cc.fascinated.bat.features.drag;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.drag.command.DragCommand; import cc.fascinated.bat.features.drag.command.DragCommand;
import cc.fascinated.bat.service.CommandService; import cc.fascinated.bat.service.CommandService;
import lombok.NonNull; import lombok.NonNull;
@ -16,7 +16,7 @@ import org.springframework.stereotype.Component;
public class DragFeature extends Feature { public class DragFeature extends Feature {
@Autowired @Autowired
public DragFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) { public DragFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Drag", true,Category.GENERAL); super("Drag", FeatureProfile.FeatureState.DISABLED, true);
super.registerCommand(commandService, context.getBean(DragCommand.class)); super.registerCommand(commandService, context.getBean(DragCommand.class));
} }

@ -1,6 +1,7 @@
package cc.fascinated.bat.features.drag.command; package cc.fascinated.bat.features.drag.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -11,7 +12,7 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "drag", description = "Drag command") @CommandInfo(name = "drag", description = "Drag command", category = Category.GENERAL)
public class DragCommand extends BatCommand { public class DragCommand extends BatCommand {
@Autowired @Autowired
public DragCommand(@NonNull ApplicationContext context) { public DragCommand(@NonNull ApplicationContext context) {

@ -11,6 +11,7 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.GuildVoiceState; import net.dv8tion.jda.api.entities.GuildVoiceState;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
@ -64,7 +65,7 @@ public class RequestSubCommand extends BatCommand implements EventListener {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
GuildVoiceState voiceState = member.getVoiceState(); GuildVoiceState voiceState = member.getVoiceState();
// Check if the user is in a voice channel // Check if the user is in a voice channel
if (voiceState == null || voiceState.getChannel() == null) { if (voiceState == null || voiceState.getChannel() == null) {
@ -77,7 +78,7 @@ public class RequestSubCommand extends BatCommand implements EventListener {
} }
OptionMapping userOption = event.getOption("user"); OptionMapping userOption = event.getOption("user");
if (userOption == null) return; assert userOption != null;
// Check if the user is in a voice channel // Check if the user is in a voice channel
Member target = userOption.getAsMember(); Member target = userOption.getAsMember();

@ -20,6 +20,9 @@ import java.util.Optional;
public class RequestListener implements EventListener { public class RequestListener implements EventListener {
@Override @Override
public void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) { public void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) {
if (guild == null) {
return;
}
if (!event.getComponentId().equals("drag-request-cancel")) { if (!event.getComponentId().equals("drag-request-cancel")) {
return; return;
} }

@ -19,6 +19,9 @@ import org.springframework.stereotype.Component;
public class TargetChannelListener implements EventListener { public class TargetChannelListener implements EventListener {
@Override @Override
public void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) { public void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) {
if (guild == null) {
return;
}
User buttonUser = event.getUser(); User buttonUser = event.getUser();
Member member = guild.getDiscordGuild().getMember(buttonUser); Member member = guild.getDiscordGuild().getMember(buttonUser);
if (member == null) return; if (member == null) return;

@ -0,0 +1,52 @@
package cc.fascinated.bat.features.leveling;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.leveling.command.LevelingCommand;
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;
import java.util.HashMap;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class LevelingFeature extends Feature {
/**
* The cache of the required XP for each level.
*/
public static final Map<Integer, Double> xpRequiredCache = new HashMap<>();
@Autowired
public LevelingFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Leveling", FeatureProfile.FeatureState.DISABLED, true);
super.registerCommand(commandService, context.getBean(LevelingCommand.class));
}
/**
* Gets the amount of XP needed to level up.
*
* @param level The level.
* @param currentXp The current XP.
* @return The needed XP.
*/
public static double getNeededXP(int level, double currentXp) {
return xpRequiredCache.computeIfAbsent(level, integer -> getBaseXP(level) - currentXp);
}
/**
* Gets the base XP for a level.
*
* @param level The level.
* @return The base XP.
*/
public static double getBaseXP(int level) {
return 5 * (Math.pow(level, 2)) + (50 * level) + 100;
}
}

@ -0,0 +1,54 @@
package cc.fascinated.bat.features.leveling;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@Log4j2(topic = "Leveling Listener")
public class LevelingListener implements EventListener {
private final LevelingFeature levelingFeature;
@Autowired
public LevelingListener(@NonNull LevelingFeature levelingFeature) {
this.levelingFeature = levelingFeature;
}
@Override
public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
if (guild.getFeatureProfile().isFeatureDisabled(levelingFeature)) { // The feature is disabled
return;
}
LevelingProfile profile = guild.getLevelingProfile();
if (profile == null) {
return;
}
UserLevel userLevel = profile.getUserLevel(user.getId());
userLevel.addXp((int) MathUtils.random(15, 25));
if (!userLevel.canLevelUp()) {
return;
}
userLevel.levelup();
TextChannel notificationChannel = profile.getNotificationChannel();
if (notificationChannel == null) {
return;
}
notificationChannel.sendMessage("Congratulations %s, you have leveled up to `%s`! :tada:".formatted(
event.getAuthor().getAsMention(),
userLevel.getLevel()
)).queue();
}
}

@ -0,0 +1,76 @@
package cc.fascinated.bat.features.leveling;
import cc.fascinated.bat.common.ChannelUtils;
import cc.fascinated.bat.common.Serializable;
import com.google.gson.Gson;
import lombok.Setter;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.bson.Document;
import java.util.HashMap;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Setter
public class LevelingProfile extends Serializable {
/**
* The levels of each user.
*/
private final Map<String, UserLevel> userLevels = new HashMap<>();
/**
* The id of the level up notification channel.
*/
private String notificationChannelId;
/**
* Gets the user level of a user.
*
* @param userId The user ID.
* @return The user level.
*/
public UserLevel getUserLevel(String userId) {
return userLevels.computeIfAbsent(userId, s -> new UserLevel(1, 0, -1));
}
/**
* Gets the notification channel.
*
* @return The notification channel.
*/
public TextChannel getNotificationChannel() {
return ChannelUtils.getTextChannel(notificationChannelId);
}
@Override
public void load(Document document, Gson gson) {
for (String userId : document.keySet()) {
Document userDocument = document.get(userId, Document.class);
userLevels.put(userId, new UserLevel(
userDocument.getInteger("level"),
userDocument.getDouble("currentXp"),
(long) userDocument.getOrDefault("lastXpTimestamp", (long) -1)
));
}
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
for (Map.Entry<String, UserLevel> entry : userLevels.entrySet()) {
document.put(entry.getKey(), new Document()
.append("level", entry.getValue().getLevel())
.append("currentXp", entry.getValue().getCurrentXp())
.append("lastXpTimestamp", entry.getValue().getLastXpTimestamp())
);
}
return document;
}
@Override
public void reset() {
userLevels.clear();
}
}

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