"
+ .formatted(premium.getExpiresAt().toInstant().toEpochMilli() / 1000), true);
} else {
embed.setDescription("The guild does not have premium");
}
diff --git a/src/main/java/cc/fascinated/bat/common/MathUtils.java b/src/main/java/cc/fascinated/bat/common/MathUtils.java
index 4dfbd8e..e977726 100644
--- a/src/main/java/cc/fascinated/bat/common/MathUtils.java
+++ b/src/main/java/cc/fascinated/bat/common/MathUtils.java
@@ -13,7 +13,7 @@ public final class MathUtils {
/**
* Format a number to a specific amount of decimal places.
*
- * @param number the number to format
+ * @param number the number to format
* @param additional the additional decimal places to format
* @return the formatted number
*/
diff --git a/src/main/java/cc/fascinated/bat/common/MemberUtils.java b/src/main/java/cc/fascinated/bat/common/MemberUtils.java
index 85c96dc..287e288 100644
--- a/src/main/java/cc/fascinated/bat/common/MemberUtils.java
+++ b/src/main/java/cc/fascinated/bat/common/MemberUtils.java
@@ -15,7 +15,7 @@ public class MemberUtils {
* Checks if a user has permission to edit another user
*
* @param guild the guild to check
- * @param user the user to check
+ * @param user the user to check
* @return if the user has permission to edit another user
*/
public static boolean hasPermissionToEdit(BatGuild guild, BatUser user) {
diff --git a/src/main/java/cc/fascinated/bat/common/NumberUtils.java b/src/main/java/cc/fascinated/bat/common/NumberUtils.java
index 5784e62..21770d1 100644
--- a/src/main/java/cc/fascinated/bat/common/NumberUtils.java
+++ b/src/main/java/cc/fascinated/bat/common/NumberUtils.java
@@ -12,7 +12,7 @@ public class NumberUtils {
/**
* Formats a number with commas.
*
- * Example: 1000 -> 1,000 | Example: 1000.5 -> 1,000.5
+ * Example: 1000 -> 1,000 | Example: 1000.5 -> 1,000.5
*
*
* @param number the number to format
diff --git a/src/main/java/cc/fascinated/bat/common/Profile.java b/src/main/java/cc/fascinated/bat/common/Profile.java
index 42e089b..79e0624 100644
--- a/src/main/java/cc/fascinated/bat/common/Profile.java
+++ b/src/main/java/cc/fascinated/bat/common/Profile.java
@@ -8,14 +8,16 @@ import lombok.Setter;
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
-@Getter @Setter
+@Getter
+@Setter
public abstract class Profile {
/**
* The key of the profile.
*/
private String profileKey;
- public Profile() {}
+ public Profile() {
+ }
/**
* Resets the profile
diff --git a/src/main/java/cc/fascinated/bat/common/ProfileHolder.java b/src/main/java/cc/fascinated/bat/common/ProfileHolder.java
index 33aaa5b..75e490c 100644
--- a/src/main/java/cc/fascinated/bat/common/ProfileHolder.java
+++ b/src/main/java/cc/fascinated/bat/common/ProfileHolder.java
@@ -19,7 +19,7 @@ public class ProfileHolder {
* Gets a profile for the holder
*
* @param clazz The class of the profile
- * @param The type of the profile
+ * @param The type of the profile
* @return The profile
*/
public T getProfile(Class clazz) {
@@ -30,7 +30,7 @@ public class ProfileHolder {
Profile profile = profiles.values().stream().filter(p -> p.getClass().equals(clazz)).findFirst().orElse(null);
if (profile == null) {
try {
- profile = (Profile) clazz.newInstance();
+ profile = clazz.newInstance();
profiles.put(profile.getProfileKey(), profile);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
diff --git a/src/main/java/cc/fascinated/bat/common/RoleUtils.java b/src/main/java/cc/fascinated/bat/common/RoleUtils.java
index fbe0626..95294ad 100644
--- a/src/main/java/cc/fascinated/bat/common/RoleUtils.java
+++ b/src/main/java/cc/fascinated/bat/common/RoleUtils.java
@@ -11,9 +11,9 @@ public class RoleUtils {
/**
* Checks if a member has permission to give the role to another member
*
- * @param guild the guild to check
+ * @param guild the guild to check
* @param member the member to check
- * @param role the role to check
+ * @param role the role to check
* @return if the member has permission to give the role
*/
public static boolean hasPermissionToGiveRole(BatGuild guild, Member member, Role role) {
diff --git a/src/main/java/cc/fascinated/bat/common/StringUtils.java b/src/main/java/cc/fascinated/bat/common/StringUtils.java
new file mode 100644
index 0000000..ad3af10
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/common/StringUtils.java
@@ -0,0 +1,20 @@
+package cc.fascinated.bat.common;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+public class StringUtils {
+ /**
+ * Generates a random string
+ *
+ * @param length the length of the string
+ * @return the random string
+ */
+ public static String randomString(int length) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ stringBuilder.append((char) (Math.random() * 26 + 'a'));
+ }
+ return stringBuilder.toString();
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/common/TimeUtils.java b/src/main/java/cc/fascinated/bat/common/TimeUtils.java
index f3b902e..736f1aa 100644
--- a/src/main/java/cc/fascinated/bat/common/TimeUtils.java
+++ b/src/main/java/cc/fascinated/bat/common/TimeUtils.java
@@ -20,49 +20,49 @@ public final class TimeUtils {
* @return the formatted time
*/
public static String format(long millis) {
- return format(millis, WildTimeUnit.FIT);
+ return format(millis, BatTimeFormat.FIT);
}
/**
* Format a time in millis to a readable time format.
*
- * @param millis the millis to format
+ * @param millis the millis to format
* @param timeUnit the time unit to format the millis to
* @return the formatted time
*/
- public static String format(long millis, WildTimeUnit timeUnit) {
+ public static String format(long millis, BatTimeFormat timeUnit) {
return format(millis, timeUnit, false);
}
/**
* Format a time in millis to a readable time format.
*
- * @param millis the millis to format
+ * @param millis the millis to format
* @param timeUnit the time unit to format the millis to
- * @param compact whether to use a compact display
+ * @param compact whether to use a compact display
* @return the formatted time
*/
- public static String format(long millis, WildTimeUnit timeUnit, boolean compact) {
+ public static String format(long millis, BatTimeFormat timeUnit, boolean compact) {
return format(millis, timeUnit, true, compact);
}
/**
* Format a time in millis to a readable time format.
*
- * @param millis the millis to format
+ * @param millis the millis to format
* @param timeUnit the time unit to format the millis to
* @param decimals whether to include decimals
- * @param compact whether to use a compact display
+ * @param compact whether to use a compact display
* @return the formatted time
*/
- public static String format(long millis, WildTimeUnit timeUnit, boolean decimals, boolean compact) {
+ public static String format(long millis, BatTimeFormat timeUnit, boolean decimals, boolean compact) {
if (millis == -1L) { // Format permanent
return "Perm" + (compact ? "" : "anent");
}
// Format the time to the best fitting time unit
- if (timeUnit == WildTimeUnit.FIT) {
- for (WildTimeUnit otherTimeUnit : WildTimeUnit.VALUES) {
- if (otherTimeUnit != WildTimeUnit.FIT && millis >= otherTimeUnit.getMillis()) {
+ if (timeUnit == BatTimeFormat.FIT) {
+ for (BatTimeFormat otherTimeUnit : BatTimeFormat.VALUES) {
+ if (otherTimeUnit != BatTimeFormat.FIT && millis >= otherTimeUnit.getMillis()) {
timeUnit = otherTimeUnit;
break;
}
@@ -74,7 +74,7 @@ public final class TimeUtils {
}
String formatted = time + (compact ? timeUnit.getSuffix() : " " + timeUnit.getDisplay()); // Append the time unit
if (time != 1.0 && !compact) { // Pluralize the time unit
- formatted+= "s";
+ formatted += "s";
}
return formatted;
}
@@ -89,16 +89,16 @@ public final class TimeUtils {
* @return the time in millis
*/
public static long fromString(String input) {
- Matcher matcher = WildTimeUnit.SUFFIX_PATTERN.matcher(input); // Match the given input
+ Matcher matcher = BatTimeFormat.SUFFIX_PATTERN.matcher(input); // Match the given input
long millis = 0; // The total millis
// Match corresponding suffixes and add up the total millis
while (matcher.find()) {
int amount = Integer.parseInt(matcher.group(1)); // The amount of time to add
String suffix = matcher.group(2); // The unit suffix
- WildTimeUnit timeUnit = WildTimeUnit.fromSuffix(suffix); // The time unit to add
+ BatTimeFormat timeUnit = BatTimeFormat.fromSuffix(suffix); // The time unit to add
if (timeUnit != null) { // Increment the total millis
- millis+= amount * timeUnit.getMillis();
+ millis += amount * timeUnit.getMillis();
}
}
return millis;
@@ -107,9 +107,11 @@ public final class TimeUtils {
/**
* Represents a unit of time.
*/
- @NoArgsConstructor @AllArgsConstructor
- @Getter(AccessLevel.PRIVATE) @ToString
- public enum WildTimeUnit {
+ @NoArgsConstructor
+ @AllArgsConstructor
+ @Getter(AccessLevel.PRIVATE)
+ @ToString
+ public enum BatTimeFormat {
FIT,
YEARS("Year", "y", TimeUnit.DAYS.toMillis(365L)),
MONTHS("Month", "mo", TimeUnit.DAYS.toMillis(30L)),
@@ -123,7 +125,7 @@ public final class TimeUtils {
/**
* Our cached unit values.
*/
- public static final WildTimeUnit[] VALUES = values();
+ public static final BatTimeFormat[] VALUES = values();
/**
* Our cached suffix pattern.
@@ -152,8 +154,8 @@ public final class TimeUtils {
* @return the time unit, null if not found
*/
@Nullable
- public static WildTimeUnit fromSuffix(String suffix) {
- for (WildTimeUnit unit : VALUES) {
+ public static BatTimeFormat fromSuffix(String suffix) {
+ for (BatTimeFormat unit : VALUES) {
if (unit != FIT && unit.getSuffix().equals(suffix)) {
return unit;
}
diff --git a/src/main/java/cc/fascinated/bat/common/TimerUtils.java b/src/main/java/cc/fascinated/bat/common/TimerUtils.java
index 4d1d997..ad37ffe 100644
--- a/src/main/java/cc/fascinated/bat/common/TimerUtils.java
+++ b/src/main/java/cc/fascinated/bat/common/TimerUtils.java
@@ -14,7 +14,7 @@ public class TimerUtils {
* Runs a repeating task on a schedule
*
* @param runnable the task to run
- * @param delay the delay before the task runs
+ * @param delay the delay before the task runs
*/
public static void scheduleRepeating(Runnable runnable, long delay, long period) {
new Timer().scheduleAtFixedRate(new TimerTask() {
diff --git a/src/main/java/cc/fascinated/bat/common/WebRequest.java b/src/main/java/cc/fascinated/bat/common/WebRequest.java
index f59dd98..5ff5841 100644
--- a/src/main/java/cc/fascinated/bat/common/WebRequest.java
+++ b/src/main/java/cc/fascinated/bat/common/WebRequest.java
@@ -27,14 +27,15 @@ public class WebRequest {
* Gets a response from the given URL.
*
* @param url the url
- * @return the response
* @param the type of the response
+ * @return the response
*/
public static T getAsEntity(String url, Class clazz) throws RateLimitException {
ResponseEntity responseEntity = CLIENT.get()
.uri(url)
.retrieve()
- .onStatus(HttpStatusCode::isError, (request, response) -> {}) // Don't throw exceptions on error
+ .onStatus(HttpStatusCode::isError, (request, response) -> {
+ }) // Don't throw exceptions on error
.toEntity(clazz);
if (responseEntity.getStatusCode().isError()) {
@@ -56,7 +57,8 @@ public class WebRequest {
return CLIENT.get()
.uri(url)
.retrieve()
- .onStatus(HttpStatusCode::isError, (request, response) -> {}) // Don't throw exceptions on error
+ .onStatus(HttpStatusCode::isError, (request, response) -> {
+ }) // Don't throw exceptions on error
.toEntity(clazz);
}
@@ -70,7 +72,8 @@ public class WebRequest {
return CLIENT.head()
.uri(url)
.retrieve()
- .onStatus(HttpStatusCode::isError, (request, response) -> {}) // Don't throw exceptions on error
+ .onStatus(HttpStatusCode::isError, (request, response) -> {
+ }) // Don't throw exceptions on error
.toEntity(clazz);
}
}
diff --git a/src/main/java/cc/fascinated/bat/config/AppConfig.java b/src/main/java/cc/fascinated/bat/config/AppConfig.java
index 58ba464..8409e30 100644
--- a/src/main/java/cc/fascinated/bat/config/AppConfig.java
+++ b/src/main/java/cc/fascinated/bat/config/AppConfig.java
@@ -8,4 +8,5 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration
@ComponentScan(basePackages = "cc.fascinated.bat")
-public class AppConfig { }
\ No newline at end of file
+public class AppConfig {
+}
\ No newline at end of file
diff --git a/src/main/java/cc/fascinated/bat/controller/SpotifyController.java b/src/main/java/cc/fascinated/bat/controller/SpotifyController.java
new file mode 100644
index 0000000..4f23bf7
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/controller/SpotifyController.java
@@ -0,0 +1,33 @@
+package cc.fascinated.bat.controller;
+
+import cc.fascinated.bat.service.SpotifyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@RestController
+@RequestMapping(value = "/spotify")
+public class SpotifyController {
+ private final SpotifyService spotifyService;
+
+ @Autowired
+ public SpotifyController(SpotifyService spotifyService) {
+ this.spotifyService = spotifyService;
+ }
+
+ /**
+ * A GET request to authorize the user with Spotify.
+ *
+ * @return the response entity
+ */
+ @GetMapping(value = "/callback")
+ public ResponseEntity authorizationCallback(@RequestParam String code) {
+ return ResponseEntity.ok(spotifyService.authorize(code));
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/event/EventListener.java b/src/main/java/cc/fascinated/bat/event/EventListener.java
index 6a9c3ff..e6caa6f 100644
--- a/src/main/java/cc/fascinated/bat/event/EventListener.java
+++ b/src/main/java/cc/fascinated/bat/event/EventListener.java
@@ -8,6 +8,8 @@ import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberScoreToken;
import lombok.NonNull;
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
+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.StringSelectInteractionEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
@@ -18,42 +20,65 @@ public interface EventListener {
/**
* Called when a ScoreSaber score is received
*
- * @param score the score that was set
+ * @param score the score that was set
* @param leaderboard the leaderboard that the score was set on
- * @param player the player that set the score
+ * @param player the player that set the score
*/
default void onScoresaberScoreReceived(@NonNull ScoreSaberPlayerScoreToken score, @NonNull ScoreSaberLeaderboardToken leaderboard,
- @NonNull ScoreSaberScoreToken.LeaderboardPlayerInfo player) {}
+ @NonNull ScoreSaberScoreToken.LeaderboardPlayerInfo player) {
+ }
/**
* Called when a user joins a guild
*
* @param guild the guild the user joined
- * @param user the user that joined the guild
+ * @param user the user that joined the guild
*/
- default void onGuildMemberJoin(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberJoinEvent event) {}
+ default void onGuildMemberJoin(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberJoinEvent event) {
+ }
/**
* Called when a user leaves a guild
*
* @param guild the guild the user left
- * @param user the user that left the guild
+ * @param user the user that left the guild
*/
- default void onGuildMemberLeave(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberRemoveEvent event) {}
+ default void onGuildMemberLeave(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberRemoveEvent event) {
+ }
/**
* Called when a user types a message
*
* @param guild the guild that the message was sent in
- * @param user the user that sent the message
+ * @param user the user that sent the message
*/
- default void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {}
+ default void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
+ }
/**
* Called when a user selects a string
*
* @param guild the guild that the string was selected in
- * @param user the user that selected the string
+ * @param user the user that selected the string
*/
- default void onStringSelectInteraction(BatGuild guild, @NonNull BatUser user, @NonNull StringSelectInteractionEvent event) {}
+ default void onStringSelectInteraction(BatGuild guild, @NonNull BatUser user, @NonNull StringSelectInteractionEvent event) {
+ }
+
+ /**
+ * Called when a user interacts with a button
+ *
+ * @param guild the guild that the button was interacted with in
+ * @param user the user that interacted with the button
+ */
+ default void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) {
+ }
+
+ /**
+ * Called when a user interacts with a modal
+ *
+ * @param guild the guild that the modal was interacted with in
+ * @param user the user that interacted with the modal
+ */
+ default void onModalInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ModalInteractionEvent event) {
+ }
}
diff --git a/src/main/java/cc/fascinated/bat/exception/BadRequestException.java b/src/main/java/cc/fascinated/bat/exception/BadRequestException.java
index f629d9f..966f467 100644
--- a/src/main/java/cc/fascinated/bat/exception/BadRequestException.java
+++ b/src/main/java/cc/fascinated/bat/exception/BadRequestException.java
@@ -6,4 +6,5 @@ import org.springframework.web.bind.annotation.ResponseStatus;
@StandardException
@ResponseStatus(HttpStatus.BAD_REQUEST)
-public class BadRequestException extends RuntimeException { }
+public class BadRequestException extends RuntimeException {
+}
diff --git a/src/main/java/cc/fascinated/bat/exception/ResourceNotFoundException.java b/src/main/java/cc/fascinated/bat/exception/ResourceNotFoundException.java
index f864cef..3dd3a76 100644
--- a/src/main/java/cc/fascinated/bat/exception/ResourceNotFoundException.java
+++ b/src/main/java/cc/fascinated/bat/exception/ResourceNotFoundException.java
@@ -6,4 +6,5 @@ import org.springframework.web.bind.annotation.ResponseStatus;
@StandardException
@ResponseStatus(HttpStatus.NOT_FOUND)
-public class ResourceNotFoundException extends RuntimeException { }
+public class ResourceNotFoundException extends RuntimeException {
+}
diff --git a/src/main/java/cc/fascinated/bat/features/Feature.java b/src/main/java/cc/fascinated/bat/features/Feature.java
index 0fc395c..ecdcfd7 100644
--- a/src/main/java/cc/fascinated/bat/features/Feature.java
+++ b/src/main/java/cc/fascinated/bat/features/Feature.java
@@ -29,7 +29,7 @@ public abstract class Feature {
* Registers the command for the feature
*
* @param commandService The command service
- * @param command The command to register
+ * @param command The command to register
*/
public void registerCommand(@NonNull CommandService commandService, @NonNull BatCommand command) {
command.setCategory(category);
diff --git a/src/main/java/cc/fascinated/bat/features/afk/profile/AfkProfile.java b/src/main/java/cc/fascinated/bat/features/afk/profile/AfkProfile.java
index 02ce819..20aeecb 100644
--- a/src/main/java/cc/fascinated/bat/features/afk/profile/AfkProfile.java
+++ b/src/main/java/cc/fascinated/bat/features/afk/profile/AfkProfile.java
@@ -24,7 +24,7 @@ public class AfkProfile extends Profile {
/**
* Adds a user to the AFK list
*
- * @param guild the guild enable afk mode for
+ * @param guild the guild enable afk mode for
* @param userId the user ID to add
* @param reason the reason for being AFK
*/
@@ -41,13 +41,14 @@ public class AfkProfile extends Profile {
}
try {
member.modifyNickname("[AFK] " + member.getEffectiveName()).queue();
- } catch (Exception ignored) {}
+ } catch (Exception ignored) {
+ }
}
/**
* Removes a user from the AFK list
*
- * @param guild the guild to remove the user from
+ * @param guild the guild to remove the user from
* @param userId the user ID to remove
*/
public void removeAfkUser(BatGuild guild, String userId) {
@@ -63,7 +64,8 @@ public class AfkProfile extends Profile {
}
try {
member.modifyNickname(member.getEffectiveName().replace("[AFK] ", "")).queue();
- } catch (Exception ignored) {}
+ } catch (Exception ignored) {
+ }
}
/**
diff --git a/src/main/java/cc/fascinated/bat/features/autorole/command/AddSubCommand.java b/src/main/java/cc/fascinated/bat/features/autorole/command/AddSubCommand.java
index 07600cf..fd3586d 100644
--- a/src/main/java/cc/fascinated/bat/features/autorole/command/AddSubCommand.java
+++ b/src/main/java/cc/fascinated/bat/features/autorole/command/AddSubCommand.java
@@ -40,7 +40,7 @@ public class AddSubCommand extends BatSubCommand {
if (profile.getRoleSlotsInUse() >= maxRoleSlots) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The guild can only have a maximum of %d auto roles"
- .formatted(maxRoleSlots))
+ .formatted(maxRoleSlots))
.build()).queue();
return;
}
diff --git a/src/main/java/cc/fascinated/bat/features/autorole/profile/AutoRoleProfile.java b/src/main/java/cc/fascinated/bat/features/autorole/profile/AutoRoleProfile.java
index d7e4544..80608dd 100644
--- a/src/main/java/cc/fascinated/bat/features/autorole/profile/AutoRoleProfile.java
+++ b/src/main/java/cc/fascinated/bat/features/autorole/profile/AutoRoleProfile.java
@@ -13,7 +13,8 @@ import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
-@Setter @Getter
+@Setter
+@Getter
public class AutoRoleProfile extends Profile {
private static final int DEFAULT_MAX_ROLES = 10;
private static final int PREMIUM_MAX_ROLES = 25;
@@ -27,6 +28,19 @@ public class AutoRoleProfile extends Profile {
super("auto-role");
}
+ /**
+ * Gets the maximum amount of roles that can be set in the guild
+ *
+ * @param guild the guild to check
+ * @return the amount of role slots
+ */
+ public static int getMaxRoleSlots(BatGuild guild) {
+ if (guild.getPremium().hasPremium()) {
+ return PREMIUM_MAX_ROLES;
+ }
+ return DEFAULT_MAX_ROLES;
+ }
+
/**
* Gets the amount of role slots in use
*
@@ -92,19 +106,6 @@ public class AutoRoleProfile extends Profile {
return roles;
}
- /**
- * Gets the maximum amount of roles that can be set in the guild
- *
- * @param guild the guild to check
- * @return the amount of role slots
- */
- public static int getMaxRoleSlots(BatGuild guild) {
- if (guild.getPremium().hasPremium()) {
- return PREMIUM_MAX_ROLES;
- }
- return DEFAULT_MAX_ROLES;
- }
-
@Override
public void reset() {
roleIds.clear();
diff --git a/src/main/java/cc/fascinated/bat/features/birthday/command/MessageSubCommand.java b/src/main/java/cc/fascinated/bat/features/birthday/command/MessageSubCommand.java
index 1ae7295..216b0d3 100644
--- a/src/main/java/cc/fascinated/bat/features/birthday/command/MessageSubCommand.java
+++ b/src/main/java/cc/fascinated/bat/features/birthday/command/MessageSubCommand.java
@@ -80,7 +80,8 @@ public class MessageSubCommand extends BatSubCommand {
private Date parseBirthday(String birthday) {
try {
return FORMATTER.parse(birthday);
- } catch (ParseException ignored) {}
+ } catch (ParseException ignored) {
+ }
return null;
}
}
diff --git a/src/main/java/cc/fascinated/bat/features/birthday/command/SetSubCommand.java b/src/main/java/cc/fascinated/bat/features/birthday/command/SetSubCommand.java
index 5d3de41..ecf6a46 100644
--- a/src/main/java/cc/fascinated/bat/features/birthday/command/SetSubCommand.java
+++ b/src/main/java/cc/fascinated/bat/features/birthday/command/SetSubCommand.java
@@ -80,7 +80,8 @@ public class SetSubCommand extends BatSubCommand {
private Date parseBirthday(String birthday) {
try {
return FORMATTER.parse(birthday);
- } catch (ParseException ignored) {}
+ } catch (ParseException ignored) {
+ }
return null;
}
}
diff --git a/src/main/java/cc/fascinated/bat/features/scoresaber/NumberOneScoreFeedListener.java b/src/main/java/cc/fascinated/bat/features/scoresaber/NumberOneScoreFeedListener.java
index 041050b..b20744f 100644
--- a/src/main/java/cc/fascinated/bat/features/scoresaber/NumberOneScoreFeedListener.java
+++ b/src/main/java/cc/fascinated/bat/features/scoresaber/NumberOneScoreFeedListener.java
@@ -19,7 +19,8 @@ import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
-@Component @Log4j2
+@Component
+@Log4j2
public class NumberOneScoreFeedListener implements EventListener {
private final GuildService guildService;
diff --git a/src/main/java/cc/fascinated/bat/features/scoresaber/UserScoreFeedListener.java b/src/main/java/cc/fascinated/bat/features/scoresaber/UserScoreFeedListener.java
index e20cb1d..0af493f 100644
--- a/src/main/java/cc/fascinated/bat/features/scoresaber/UserScoreFeedListener.java
+++ b/src/main/java/cc/fascinated/bat/features/scoresaber/UserScoreFeedListener.java
@@ -18,7 +18,8 @@ import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
-@Component @Log4j2
+@Component
+@Log4j2
public class UserScoreFeedListener implements EventListener {
private final GuildService guildService;
diff --git a/src/main/java/cc/fascinated/bat/features/scoresaber/command/numberone/ChannelSubCommand.java b/src/main/java/cc/fascinated/bat/features/scoresaber/command/numberone/ChannelSubCommand.java
index 85d79b2..984e583 100644
--- a/src/main/java/cc/fascinated/bat/features/scoresaber/command/numberone/ChannelSubCommand.java
+++ b/src/main/java/cc/fascinated/bat/features/scoresaber/command/numberone/ChannelSubCommand.java
@@ -40,7 +40,7 @@ public class ChannelSubCommand extends BatSubCommand {
if (option == null) {
if (!TextChannelUtils.isValidChannel(profile.getChannelId())) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
- .setDescription("There is no channel set for the feed notifications. Please provide a channel to set the feed channel to")
+ .setDescription("There is no channel set for the feed notifications.")
.build()).queue();
return;
}
diff --git a/src/main/java/cc/fascinated/bat/features/scoresaber/command/scoresaber/LinkSubCommand.java b/src/main/java/cc/fascinated/bat/features/scoresaber/command/scoresaber/LinkSubCommand.java
index 6dd81b6..491428d 100644
--- a/src/main/java/cc/fascinated/bat/features/scoresaber/command/scoresaber/LinkSubCommand.java
+++ b/src/main/java/cc/fascinated/bat/features/scoresaber/command/scoresaber/LinkSubCommand.java
@@ -65,7 +65,7 @@ public class LinkSubCommand extends BatSubCommand {
return;
}
- ((UserScoreSaberProfile) user.getProfile(UserScoreSaberProfile.class)).setSteamId(id);
+ user.getProfile(UserScoreSaberProfile.class).setSteamId(id);
userService.saveUser(user);
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully linked your [ScoreSaber](%s) profile".formatted("https://scoresaber.com/u/%s".formatted(id)))
diff --git a/src/main/java/cc/fascinated/bat/features/scoresaber/command/scoresaber/ScoreSaberCommand.java b/src/main/java/cc/fascinated/bat/features/scoresaber/command/scoresaber/ScoreSaberCommand.java
index 3a084e8..27106ec 100644
--- a/src/main/java/cc/fascinated/bat/features/scoresaber/command/scoresaber/ScoreSaberCommand.java
+++ b/src/main/java/cc/fascinated/bat/features/scoresaber/command/scoresaber/ScoreSaberCommand.java
@@ -41,11 +41,6 @@ public class ScoreSaberCommand extends BatCommand {
super.addSubCommand(context.getBean(ResetSubCommand.class));
}
- @Override
- public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
- sendProfileEmbed(true, user, scoreSaberService, interaction);
- }
-
/**
* Builds the profile embed for the ScoreSaber profile
*
@@ -103,4 +98,9 @@ public class ScoreSaberCommand extends BatCommand {
.build()).queue();
}
}
+
+ @Override
+ public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
+ sendProfileEmbed(true, user, scoreSaberService, interaction);
+ }
}
diff --git a/src/main/java/cc/fascinated/bat/features/scoresaber/command/userfeed/ChannelSubCommand.java b/src/main/java/cc/fascinated/bat/features/scoresaber/command/userfeed/ChannelSubCommand.java
index 42ab983..7af65ac 100644
--- a/src/main/java/cc/fascinated/bat/features/scoresaber/command/userfeed/ChannelSubCommand.java
+++ b/src/main/java/cc/fascinated/bat/features/scoresaber/command/userfeed/ChannelSubCommand.java
@@ -40,7 +40,7 @@ public class ChannelSubCommand extends BatSubCommand {
if (option == null) {
if (!TextChannelUtils.isValidChannel(profile.getChannelId())) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
- .setDescription("There is no channel set for the feed notifications. Please provide a channel to set the feed channel to")
+ .setDescription("There is no channel set for the feed notifications.")
.build()).queue();
return;
}
diff --git a/src/main/java/cc/fascinated/bat/features/scoresaber/command/userfeed/UserSubCommand.java b/src/main/java/cc/fascinated/bat/features/scoresaber/command/userfeed/UserSubCommand.java
index 8b90d8d..789d6ab 100644
--- a/src/main/java/cc/fascinated/bat/features/scoresaber/command/userfeed/UserSubCommand.java
+++ b/src/main/java/cc/fascinated/bat/features/scoresaber/command/userfeed/UserSubCommand.java
@@ -39,7 +39,7 @@ public class UserSubCommand extends BatSubCommand {
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
GuildUserScoreFeedProfile profile = guild.getProfile(GuildUserScoreFeedProfile.class);
OptionMapping option = interaction.getOption("user");
- if (option == null){
+ if (option == null) {
if (profile.getTrackedUsers().isEmpty()) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("There are no users being tracked in the feed")
diff --git a/src/main/java/cc/fascinated/bat/features/scoresaber/profile/GuildNumberOneScoreFeedProfile.java b/src/main/java/cc/fascinated/bat/features/scoresaber/profile/GuildNumberOneScoreFeedProfile.java
index 873dfc3..1291bee 100644
--- a/src/main/java/cc/fascinated/bat/features/scoresaber/profile/GuildNumberOneScoreFeedProfile.java
+++ b/src/main/java/cc/fascinated/bat/features/scoresaber/profile/GuildNumberOneScoreFeedProfile.java
@@ -9,7 +9,8 @@ import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
/**
* @author Fascinated (fascinated7)
*/
-@Getter @Setter
+@Getter
+@Setter
public class GuildNumberOneScoreFeedProfile extends Profile {
/**
* The channel ID of the score feed
diff --git a/src/main/java/cc/fascinated/bat/features/scoresaber/profile/GuildUserScoreFeedProfile.java b/src/main/java/cc/fascinated/bat/features/scoresaber/profile/GuildUserScoreFeedProfile.java
index e68b2de..3d6ade3 100644
--- a/src/main/java/cc/fascinated/bat/features/scoresaber/profile/GuildUserScoreFeedProfile.java
+++ b/src/main/java/cc/fascinated/bat/features/scoresaber/profile/GuildUserScoreFeedProfile.java
@@ -12,7 +12,8 @@ import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
-@Getter @Setter
+@Getter
+@Setter
public class GuildUserScoreFeedProfile extends Profile {
/**
* The channel ID of the score feed
diff --git a/src/main/java/cc/fascinated/bat/features/scoresaber/profile/UserScoreSaberProfile.java b/src/main/java/cc/fascinated/bat/features/scoresaber/profile/UserScoreSaberProfile.java
index 1a4fa04..799faf1 100644
--- a/src/main/java/cc/fascinated/bat/features/scoresaber/profile/UserScoreSaberProfile.java
+++ b/src/main/java/cc/fascinated/bat/features/scoresaber/profile/UserScoreSaberProfile.java
@@ -7,7 +7,8 @@ import lombok.Setter;
/**
* @author Fascinated (fascinated7)
*/
-@Setter @Getter
+@Setter
+@Getter
public class UserScoreSaberProfile extends Profile {
/**
* The Account ID of the ScoreSaber profile
diff --git a/src/main/java/cc/fascinated/bat/features/spotify/SpotifyFeature.java b/src/main/java/cc/fascinated/bat/features/spotify/SpotifyFeature.java
new file mode 100644
index 0000000..ed38a38
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/spotify/SpotifyFeature.java
@@ -0,0 +1,33 @@
+package cc.fascinated.bat.features.spotify;
+
+import cc.fascinated.bat.command.Category;
+import cc.fascinated.bat.common.EmbedUtils;
+import cc.fascinated.bat.features.Feature;
+import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
+import cc.fascinated.bat.model.BatGuild;
+import cc.fascinated.bat.model.BatUser;
+import net.dv8tion.jda.api.entities.MessageEmbed;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Component
+public class SpotifyFeature extends Feature {
+ @Autowired
+ public SpotifyFeature() {
+ super("Spotify", Category.MUSIC);
+ }
+
+ /**
+ * The embed for when a user needs to link their Spotify account.
+ *
+ * @return The embed.
+ */
+ public static MessageEmbed linkAccountEmbed() {
+ return EmbedUtils.genericEmbed()
+ .setDescription("You need to link your Spotify account before you can use this command.")
+ .build();
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/features/spotify/command/CurrentSubCommand.java b/src/main/java/cc/fascinated/bat/features/spotify/command/CurrentSubCommand.java
new file mode 100644
index 0000000..d54bead
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/spotify/command/CurrentSubCommand.java
@@ -0,0 +1,87 @@
+package cc.fascinated.bat.features.spotify.command;
+
+import cc.fascinated.bat.command.BatSubCommand;
+import cc.fascinated.bat.command.CommandInfo;
+import cc.fascinated.bat.common.EmbedUtils;
+import cc.fascinated.bat.features.spotify.SpotifyFeature;
+import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
+import cc.fascinated.bat.model.BatGuild;
+import cc.fascinated.bat.model.BatUser;
+import cc.fascinated.bat.service.SpotifyService;
+import lombok.NonNull;
+import lombok.extern.log4j.Log4j2;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;
+import se.michaelthelin.spotify.model_objects.specification.AlbumSimplified;
+import se.michaelthelin.spotify.model_objects.specification.Image;
+import se.michaelthelin.spotify.model_objects.specification.Track;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Component
+@Log4j2
+@CommandInfo(name = "current", description = "Gets the currently playing Spotify track")
+public class CurrentSubCommand extends BatSubCommand {
+ private final SpotifyService spotifyService;
+
+ @Autowired
+ public CurrentSubCommand(@NonNull SpotifyService spotifyService) {
+ this.spotifyService = spotifyService;
+ }
+
+ @Override
+ public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
+ SpotifyProfile profile = user.getProfile(SpotifyProfile.class);
+ if (!profile.hasLinkedAccount()) {
+ interaction.replyEmbeds(SpotifyFeature.linkAccountEmbed()).queue();
+ return;
+ }
+
+ if (!spotifyService.hasTrackPlaying(user)) {
+ interaction.replyEmbeds(EmbedUtils.genericEmbed()
+ .setDescription("You are not currently playing a track.")
+ .build())
+ .queue();
+ return;
+ }
+ CurrentlyPlaying currentlyPlaying = spotifyService.getCurrentlyPlayingTrack(user);
+ Track track = (Track) currentlyPlaying.getItem();
+ AlbumSimplified album = track.getAlbum();
+ String trackUrl = "https://open.spotify.com/track/" + track.getId();
+ String albumUrl = "https://open.spotify.com/album/" + album.getId();
+
+ String description =
+ "➜ Song: **[%s](%s)**\n".formatted(track.getName(), trackUrl) +
+ "➜ Album: **[%s](%s)**\n".formatted(album.getName(), albumUrl) +
+ "➜ Position: %s\n".formatted(getFormattedTime(currentlyPlaying));
+
+ Image albumCover = album.getImages()[0];
+ interaction.replyEmbeds(EmbedUtils.genericEmbed()
+ .setAuthor("Listening to %s".formatted(track.getName()), trackUrl)
+ .setThumbnail(albumCover.getUrl())
+ .setDescription(description)
+ .build()).queue();
+ }
+
+
+ /**
+ * Gets the formatted time of the currently playing track
+ *
+ * @param currentlyPlaying the currently playing track
+ * @return the formatted time
+ */
+ private String getFormattedTime(@NonNull CurrentlyPlaying currentlyPlaying) {
+ Track track = (Track) currentlyPlaying.getItem();
+ int currentMinutes = currentlyPlaying.getProgress_ms() / 1000 / 60;
+ int currentSeconds = currentlyPlaying.getProgress_ms() / 1000 % 60;
+ int totalMinutes = track.getDurationMs() / 1000 / 60;
+ int totalSeconds = track.getDurationMs() / 1000 % 60;
+
+ return "`%02d:%02d`/`%02d:%02d`".formatted(currentMinutes, currentSeconds, totalMinutes, totalSeconds);
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/features/spotify/command/LinkSubCommand.java b/src/main/java/cc/fascinated/bat/features/spotify/command/LinkSubCommand.java
new file mode 100644
index 0000000..f41a9fb
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/spotify/command/LinkSubCommand.java
@@ -0,0 +1,83 @@
+package cc.fascinated.bat.features.spotify.command;
+
+import cc.fascinated.bat.command.BatSubCommand;
+import cc.fascinated.bat.command.CommandInfo;
+import cc.fascinated.bat.common.EmbedUtils;
+import cc.fascinated.bat.event.EventListener;
+import cc.fascinated.bat.model.BatGuild;
+import cc.fascinated.bat.model.BatUser;
+import cc.fascinated.bat.service.SpotifyService;
+import lombok.NonNull;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
+import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
+import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
+import net.dv8tion.jda.api.interactions.components.ActionRow;
+import net.dv8tion.jda.api.interactions.components.buttons.Button;
+import net.dv8tion.jda.api.interactions.components.text.TextInput;
+import net.dv8tion.jda.api.interactions.components.text.TextInputStyle;
+import net.dv8tion.jda.api.interactions.modals.Modal;
+import net.dv8tion.jda.api.interactions.modals.ModalMapping;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Component
+@CommandInfo(name = "link", description = "Link your Spotify account")
+public class LinkSubCommand extends BatSubCommand implements EventListener {
+ private final SpotifyService spotifyService;
+
+ @Autowired
+ public LinkSubCommand(@NonNull SpotifyService spotifyService) {
+ this.spotifyService = spotifyService;
+ }
+
+ @Override
+ public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
+ interaction.replyEmbeds(EmbedUtils.genericEmbed()
+ .setDescription("You can link your Spotify account by clicking [here](%s)".formatted(spotifyService.getAuthorizationUrl()))
+ .build())
+ .addComponents(ActionRow.of(Button.primary("spotify_link", "Link Spotify")))
+ .queue();
+ }
+
+ @Override
+ public void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) {
+ if (!event.getComponentId().equals("spotify_link")) {
+ return;
+ }
+
+ TextInput code = TextInput.create("code", "Link Code", TextInputStyle.SHORT)
+ .setPlaceholder("Your link code")
+ .setMinLength(0)
+ .setMaxLength(16)
+ .build();
+
+ Modal modal = Modal.create("link_modal", "Link Spotify Account")
+ .addComponents(ActionRow.of(code))
+ .build();
+
+ event.replyModal(modal).queue();
+ }
+
+ @Override
+ public void onModalInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ModalInteractionEvent event) {
+ if (!event.getModalId().equals("link_modal")) {
+ return;
+ }
+
+ ModalMapping codeMapping = event.getValue("code");
+ if (codeMapping == null) {
+ return;
+ }
+ String code = codeMapping.getAsString();
+ spotifyService.linkAccount(user, code);
+ event.replyEmbeds(EmbedUtils.successEmbed()
+ .setDescription("Successfully linked your Spotify account!")
+ .build())
+ .queue();
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/features/spotify/command/PauseSubCommand.java b/src/main/java/cc/fascinated/bat/features/spotify/command/PauseSubCommand.java
new file mode 100644
index 0000000..b1b948c
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/spotify/command/PauseSubCommand.java
@@ -0,0 +1,62 @@
+package cc.fascinated.bat.features.spotify.command;
+
+import cc.fascinated.bat.command.BatSubCommand;
+import cc.fascinated.bat.command.CommandInfo;
+import cc.fascinated.bat.common.EmbedUtils;
+import cc.fascinated.bat.event.EventListener;
+import cc.fascinated.bat.features.spotify.SpotifyFeature;
+import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
+import cc.fascinated.bat.model.BatGuild;
+import cc.fascinated.bat.model.BatUser;
+import cc.fascinated.bat.service.SpotifyService;
+import lombok.NonNull;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
+import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
+import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
+import net.dv8tion.jda.api.interactions.components.ActionRow;
+import net.dv8tion.jda.api.interactions.components.buttons.Button;
+import net.dv8tion.jda.api.interactions.components.text.TextInput;
+import net.dv8tion.jda.api.interactions.components.text.TextInputStyle;
+import net.dv8tion.jda.api.interactions.modals.Modal;
+import net.dv8tion.jda.api.interactions.modals.ModalMapping;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Component
+@CommandInfo(name = "pause", description = "Pause the current Spotify track")
+public class PauseSubCommand extends BatSubCommand {
+ private final SpotifyService spotifyService;
+
+ @Autowired
+ public PauseSubCommand(@NonNull SpotifyService spotifyService) {
+ this.spotifyService = spotifyService;
+ }
+
+ @Override
+ public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
+ SpotifyProfile profile = user.getProfile(SpotifyProfile.class);
+ if (!profile.hasLinkedAccount()) {
+ interaction.replyEmbeds(SpotifyFeature.linkAccountEmbed()).queue();
+ return;
+ }
+
+ if (!spotifyService.hasTrackPlaying(user)) {
+ interaction.replyEmbeds(EmbedUtils.genericEmbed()
+ .setDescription("You need to be playing a track to pause it.")
+ .build())
+ .queue();
+ return;
+ }
+
+ boolean didPause = spotifyService.pausePlayback(user);
+ interaction.replyEmbeds(EmbedUtils.genericEmbed()
+ .setDescription(didPause ? "Paused the current track." : "The current track is already paused.")
+ .build())
+ .queue();
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/features/spotify/command/ResumeSubCommand.java b/src/main/java/cc/fascinated/bat/features/spotify/command/ResumeSubCommand.java
new file mode 100644
index 0000000..492a407
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/spotify/command/ResumeSubCommand.java
@@ -0,0 +1,53 @@
+package cc.fascinated.bat.features.spotify.command;
+
+import cc.fascinated.bat.command.BatSubCommand;
+import cc.fascinated.bat.command.CommandInfo;
+import cc.fascinated.bat.common.EmbedUtils;
+import cc.fascinated.bat.features.spotify.SpotifyFeature;
+import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
+import cc.fascinated.bat.model.BatGuild;
+import cc.fascinated.bat.model.BatUser;
+import cc.fascinated.bat.service.SpotifyService;
+import lombok.NonNull;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Component
+@CommandInfo(name = "resume", description = "Resume the current Spotify track")
+public class ResumeSubCommand extends BatSubCommand {
+ private final SpotifyService spotifyService;
+
+ @Autowired
+ public ResumeSubCommand(@NonNull SpotifyService spotifyService) {
+ this.spotifyService = spotifyService;
+ }
+
+ @Override
+ public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
+ SpotifyProfile profile = user.getProfile(SpotifyProfile.class);
+ if (!profile.hasLinkedAccount()) {
+ interaction.replyEmbeds(SpotifyFeature.linkAccountEmbed()).queue();
+ return;
+ }
+
+ if (!spotifyService.hasTrackPlaying(user)) {
+ interaction.replyEmbeds(EmbedUtils.genericEmbed()
+ .setDescription("You need to be playing a track to pause it.")
+ .build())
+ .queue();
+ return;
+ }
+
+ boolean didPause = spotifyService.resumePlayback(user);
+ interaction.replyEmbeds(EmbedUtils.genericEmbed()
+ .setDescription(didPause ? "Resumed the current track." : "The current track is already playing.")
+ .build())
+ .queue();
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/features/spotify/command/SpotifyCommand.java b/src/main/java/cc/fascinated/bat/features/spotify/command/SpotifyCommand.java
new file mode 100644
index 0000000..3d743ab
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/spotify/command/SpotifyCommand.java
@@ -0,0 +1,24 @@
+package cc.fascinated.bat.features.spotify.command;
+
+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 = "spotify", description = "Change your Spotify settings", guildOnly = false)
+public class SpotifyCommand extends BatCommand {
+ @Autowired
+ public SpotifyCommand(@NonNull ApplicationContext context) {
+ super.addSubCommand(context.getBean(LinkSubCommand.class));
+ super.addSubCommand(context.getBean(UnlinkSubCommand.class));
+ super.addSubCommand(context.getBean(PauseSubCommand.class));
+ super.addSubCommand(context.getBean(ResumeSubCommand.class));
+ super.addSubCommand(context.getBean(CurrentSubCommand.class));
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/features/spotify/command/UnlinkSubCommand.java b/src/main/java/cc/fascinated/bat/features/spotify/command/UnlinkSubCommand.java
new file mode 100644
index 0000000..9bbc911
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/spotify/command/UnlinkSubCommand.java
@@ -0,0 +1,58 @@
+package cc.fascinated.bat.features.spotify.command;
+
+import cc.fascinated.bat.command.BatSubCommand;
+import cc.fascinated.bat.command.CommandInfo;
+import cc.fascinated.bat.common.EmbedUtils;
+import cc.fascinated.bat.event.EventListener;
+import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
+import cc.fascinated.bat.model.BatGuild;
+import cc.fascinated.bat.model.BatUser;
+import cc.fascinated.bat.service.SpotifyService;
+import cc.fascinated.bat.service.UserService;
+import lombok.NonNull;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
+import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
+import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
+import net.dv8tion.jda.api.interactions.components.ActionRow;
+import net.dv8tion.jda.api.interactions.components.buttons.Button;
+import net.dv8tion.jda.api.interactions.components.text.TextInput;
+import net.dv8tion.jda.api.interactions.components.text.TextInputStyle;
+import net.dv8tion.jda.api.interactions.modals.Modal;
+import net.dv8tion.jda.api.interactions.modals.ModalMapping;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Component
+@CommandInfo(name = "unlink", description = "Unlink your Spotify account")
+public class UnlinkSubCommand extends BatSubCommand implements EventListener {
+ private final UserService userService;
+
+ @Autowired
+ public UnlinkSubCommand(@NonNull UserService userService) {
+ this.userService = userService;
+ }
+
+ @Override
+ public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
+ SpotifyProfile profile = user.getProfile(SpotifyProfile.class);
+ if (!profile.hasLinkedAccount()) {
+ interaction.replyEmbeds(EmbedUtils.genericEmbed()
+ .setDescription("You do not have a linked Spotify account.")
+ .build())
+ .queue();
+ return;
+ }
+
+ profile.reset();
+ userService.saveUser(user);
+ interaction.replyEmbeds(EmbedUtils.genericEmbed()
+ .setDescription("Successfully unlinked your Spotify account.")
+ .build())
+ .queue();
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/features/spotify/profile/SpotifyProfile.java b/src/main/java/cc/fascinated/bat/features/spotify/profile/SpotifyProfile.java
new file mode 100644
index 0000000..03e3e0f
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/spotify/profile/SpotifyProfile.java
@@ -0,0 +1,40 @@
+package cc.fascinated.bat.features.spotify.profile;
+
+import cc.fascinated.bat.common.Profile;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Getter @Setter
+public class SpotifyProfile extends Profile {
+ /**
+ * The access token
+ */
+ private String accessToken;
+
+ /**
+ * The refresh token
+ */
+ private String refreshToken;
+
+ public SpotifyProfile() {
+ super("spotify");
+ }
+
+ /**
+ * Checks if the account has a linked account
+ *
+ * @return if the account has a linked account
+ */
+ public boolean hasLinkedAccount() {
+ return this.accessToken != null && this.refreshToken != null;
+ }
+
+ @Override
+ public void reset() {
+ this.accessToken = null;
+ this.refreshToken = null;
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/model/BatGuild.java b/src/main/java/cc/fascinated/bat/model/BatGuild.java
index fe8b769..c80a008 100644
--- a/src/main/java/cc/fascinated/bat/model/BatGuild.java
+++ b/src/main/java/cc/fascinated/bat/model/BatGuild.java
@@ -2,12 +2,10 @@ package cc.fascinated.bat.model;
import cc.fascinated.bat.common.ProfileHolder;
import cc.fascinated.bat.service.DiscordService;
-import jakarta.annotation.PostConstruct;
import lombok.*;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
-import org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.Date;
@@ -16,13 +14,16 @@ import java.util.Date;
* @author Fascinated (fascinated7)
*/
@RequiredArgsConstructor
-@Getter @Setter
+@Getter
+@Setter
@Document(collection = "guilds")
public class BatGuild extends ProfileHolder {
/**
* The ID of the guild
*/
- @NonNull @Id private final String id;
+ @NonNull
+ @Id
+ private final String id;
/**
* The time this guild was joined
@@ -64,7 +65,9 @@ public class BatGuild extends ProfileHolder {
return DiscordService.JDA.getGuildById(id);
}
- @AllArgsConstructor @Getter @Setter
+ @AllArgsConstructor
+ @Getter
+ @Setter
public static class Premium {
/**
* The time the premium was activated
diff --git a/src/main/java/cc/fascinated/bat/model/BatUser.java b/src/main/java/cc/fascinated/bat/model/BatUser.java
index 72d0242..9347bce 100644
--- a/src/main/java/cc/fascinated/bat/model/BatUser.java
+++ b/src/main/java/cc/fascinated/bat/model/BatUser.java
@@ -16,19 +16,29 @@ import java.util.Date;
* @author Fascinated (fascinated7)
*/
@RequiredArgsConstructor
-@Getter @Setter
+@Getter
+@Setter
@Document(collection = "users")
public class BatUser extends ProfileHolder {
/**
* The ID of the user
*/
- @NonNull @Id private final String id;
+ @NonNull
+ @Id
+ private final String id;
/**
* The time this user was created
*/
private Date createdAt = new Date();
+ /**
+ * The name of the user
+ */
+ public String getName() {
+ return getDiscordUser().getName();
+ }
+
/**
* Gets the guild as the JDA Guild
*
diff --git a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberAccountToken.java b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberAccountToken.java
index f470417..a9e2584 100644
--- a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberAccountToken.java
+++ b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberAccountToken.java
@@ -88,7 +88,8 @@ public class ScoreSaberAccountToken {
/**
* The badge for this account.
*/
- @AllArgsConstructor @Getter
+ @AllArgsConstructor
+ @Getter
public static class Badge {
/**
* The image for this badge.
@@ -104,7 +105,8 @@ public class ScoreSaberAccountToken {
/**
* The score stats for this account.
*/
- @AllArgsConstructor @Getter
+ @AllArgsConstructor
+ @Getter
public static class ScoreStats {
/**
* The total score for this account.
diff --git a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberLeaderboardToken.java b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberLeaderboardToken.java
index 4d5dd49..e8ca838 100644
--- a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberLeaderboardToken.java
+++ b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberLeaderboardToken.java
@@ -5,7 +5,8 @@ import lombok.ToString;
import java.util.List;
-@Getter @ToString
+@Getter
+@ToString
public class ScoreSaberLeaderboardToken {
/**
* The ID of the leaderboard.
diff --git a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberPageMetadataToken.java b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberPageMetadataToken.java
index 7698a19..7782d36 100644
--- a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberPageMetadataToken.java
+++ b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberPageMetadataToken.java
@@ -3,7 +3,8 @@ package cc.fascinated.bat.model.token.beatsaber.scoresaber;
import lombok.Getter;
import lombok.ToString;
-@Getter @ToString
+@Getter
+@ToString
public class ScoreSaberPageMetadataToken {
/**
* The total amount of scores.
diff --git a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberPlayerScoreToken.java b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberPlayerScoreToken.java
index 83d7873..b090ce4 100644
--- a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberPlayerScoreToken.java
+++ b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberPlayerScoreToken.java
@@ -3,7 +3,8 @@ package cc.fascinated.bat.model.token.beatsaber.scoresaber;
import lombok.Getter;
import lombok.ToString;
-@Getter @ToString
+@Getter
+@ToString
public class ScoreSaberPlayerScoreToken {
/**
* The score that was set.
diff --git a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberScoreToken.java b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberScoreToken.java
index b1afa3b..12f69c0 100644
--- a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberScoreToken.java
+++ b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberScoreToken.java
@@ -3,7 +3,8 @@ package cc.fascinated.bat.model.token.beatsaber.scoresaber;
import lombok.Getter;
import lombok.ToString;
-@Getter @ToString
+@Getter
+@ToString
public class ScoreSaberScoreToken {
/**
* The id for this score.
diff --git a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberScoresPageToken.java b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberScoresPageToken.java
index f552fff..712cd77 100644
--- a/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberScoresPageToken.java
+++ b/src/main/java/cc/fascinated/bat/model/token/beatsaber/scoresaber/ScoreSaberScoresPageToken.java
@@ -4,7 +4,8 @@ import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
-@Getter @Setter
+@Getter
+@Setter
@ToString
public class ScoreSaberScoresPageToken {
/**
diff --git a/src/main/java/cc/fascinated/bat/model/token/dogceo/RandomImage.java b/src/main/java/cc/fascinated/bat/model/token/dogceo/RandomImage.java
index e70243a..f89af7b 100644
--- a/src/main/java/cc/fascinated/bat/model/token/dogceo/RandomImage.java
+++ b/src/main/java/cc/fascinated/bat/model/token/dogceo/RandomImage.java
@@ -6,7 +6,8 @@ import lombok.Getter;
/**
* @author Fascinated (fascinated7)
*/
-@Getter @AllArgsConstructor
+@Getter
+@AllArgsConstructor
public class RandomImage {
/**
* The URL of the image.
diff --git a/src/main/java/cc/fascinated/bat/model/token/thecatapi/CatImageToken.java b/src/main/java/cc/fascinated/bat/model/token/thecatapi/CatImageToken.java
index 55b5846..f4b4dbb 100644
--- a/src/main/java/cc/fascinated/bat/model/token/thecatapi/CatImageToken.java
+++ b/src/main/java/cc/fascinated/bat/model/token/thecatapi/CatImageToken.java
@@ -6,7 +6,8 @@ import lombok.Getter;
/**
* @author Fascinated (fascinated7)
*/
-@Getter @AllArgsConstructor
+@Getter
+@AllArgsConstructor
public class CatImageToken {
/**
* The ID of the image.
diff --git a/src/main/java/cc/fascinated/bat/repository/GuildRepository.java b/src/main/java/cc/fascinated/bat/repository/GuildRepository.java
index 415891d..c072a1c 100644
--- a/src/main/java/cc/fascinated/bat/repository/GuildRepository.java
+++ b/src/main/java/cc/fascinated/bat/repository/GuildRepository.java
@@ -6,4 +6,5 @@ import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Fascinated (fascinated7)
*/
-public interface GuildRepository extends MongoRepository { }
\ No newline at end of file
+public interface GuildRepository extends MongoRepository {
+}
\ No newline at end of file
diff --git a/src/main/java/cc/fascinated/bat/repository/UserRepository.java b/src/main/java/cc/fascinated/bat/repository/UserRepository.java
index 4637764..959ec3b 100644
--- a/src/main/java/cc/fascinated/bat/repository/UserRepository.java
+++ b/src/main/java/cc/fascinated/bat/repository/UserRepository.java
@@ -6,4 +6,5 @@ import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Fascinated (fascinated7)
*/
-public interface UserRepository extends MongoRepository { }
\ No newline at end of file
+public interface UserRepository extends MongoRepository {
+}
\ No newline at end of file
diff --git a/src/main/java/cc/fascinated/bat/service/DiscordService.java b/src/main/java/cc/fascinated/bat/service/DiscordService.java
index 58b2871..fa0d4c8 100644
--- a/src/main/java/cc/fascinated/bat/service/DiscordService.java
+++ b/src/main/java/cc/fascinated/bat/service/DiscordService.java
@@ -17,7 +17,8 @@ import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
-@Service @Getter
+@Service
+@Getter
public class DiscordService {
/**
* The JDA instance
diff --git a/src/main/java/cc/fascinated/bat/service/EventService.java b/src/main/java/cc/fascinated/bat/service/EventService.java
index 4e80322..77a104c 100644
--- a/src/main/java/cc/fascinated/bat/service/EventService.java
+++ b/src/main/java/cc/fascinated/bat/service/EventService.java
@@ -7,6 +7,8 @@ import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
+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.StringSelectInteractionEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
@@ -22,16 +24,16 @@ import java.util.Set;
/**
* @author Fascinated (fascinated7)
*/
-@Service @Log4j2
+@Service
+@Log4j2
@DependsOn("discordService")
public class EventService extends ListenerAdapter {
- private final GuildService guildService;
- private final UserService userService;
-
/**
* The list of listeners registered
*/
public static final Set LISTENERS = new HashSet<>();
+ private final GuildService guildService;
+ private final UserService userService;
@Autowired
public EventService(@NonNull GuildService guildService, @NonNull UserService userService, @NonNull ApplicationContext context) {
@@ -103,4 +105,30 @@ public class EventService extends ListenerAdapter {
listener.onStringSelectInteraction(guild, user, event);
}
}
+
+ @Override
+ public void onButtonInteraction(ButtonInteractionEvent event) {
+ if (event.getUser().isBot()) {
+ return;
+ }
+ BatGuild guild = event.getGuild() != null ? guildService.getGuild(event.getGuild().getId()) : null;
+ BatUser user = userService.getUser(event.getUser().getId());
+
+ for (EventListener listener : LISTENERS) {
+ listener.onButtonInteraction(guild, user, event);
+ }
+ }
+
+ @Override
+ public void onModalInteraction(ModalInteractionEvent event) {
+ if (event.getUser().isBot()) {
+ return;
+ }
+ BatGuild guild = event.getGuild() != null ? guildService.getGuild(event.getGuild().getId()) : null;
+ BatUser user = userService.getUser(event.getUser().getId());
+
+ for (EventListener listener : LISTENERS) {
+ listener.onModalInteraction(guild, user, event);
+ }
+ }
}
diff --git a/src/main/java/cc/fascinated/bat/service/FeatureService.java b/src/main/java/cc/fascinated/bat/service/FeatureService.java
index 2a82066..4eb7627 100644
--- a/src/main/java/cc/fascinated/bat/service/FeatureService.java
+++ b/src/main/java/cc/fascinated/bat/service/FeatureService.java
@@ -16,7 +16,9 @@ import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
-@Service @Getter @Log4j2
+@Service
+@Getter
+@Log4j2
@DependsOn("commandService")
public class FeatureService {
/**
diff --git a/src/main/java/cc/fascinated/bat/service/GuildService.java b/src/main/java/cc/fascinated/bat/service/GuildService.java
index 2ae2132..e239341 100644
--- a/src/main/java/cc/fascinated/bat/service/GuildService.java
+++ b/src/main/java/cc/fascinated/bat/service/GuildService.java
@@ -20,7 +20,9 @@ import java.util.concurrent.TimeUnit;
/**
* @author Fascinated (fascinated7)
*/
-@Service @Log4j2 @Getter
+@Service
+@Log4j2
+@Getter
@DependsOn("discordService")
public class GuildService extends ListenerAdapter {
/**
diff --git a/src/main/java/cc/fascinated/bat/service/ScoreSaberService.java b/src/main/java/cc/fascinated/bat/service/ScoreSaberService.java
index c4c1635..f2ea628 100644
--- a/src/main/java/cc/fascinated/bat/service/ScoreSaberService.java
+++ b/src/main/java/cc/fascinated/bat/service/ScoreSaberService.java
@@ -27,7 +27,8 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
-@Service @Log4j2(topic = "ScoreSaber Service")
+@Service
+@Log4j2(topic = "ScoreSaber Service")
public class ScoreSaberService extends TextWebSocketHandler {
private static final String SCORESABER_API = "https://scoresaber.com/api/";
private static final String GET_PLAYER_ENDPOINT = SCORESABER_API + "player/%s/full";
@@ -50,7 +51,7 @@ public class ScoreSaberService extends TextWebSocketHandler {
*
* @param id The id of the account.
* @return The account.
- * @throws ResourceNotFoundException If the account is not found.
+ * @throws ResourceNotFoundException If the account is not found.
* @throws cc.fascinated.bat.exception.RateLimitException If the ScoreSaber rate limit is reached.
*/
public ScoreSaberAccountToken getAccount(String id) {
@@ -77,7 +78,7 @@ public class ScoreSaberService extends TextWebSocketHandler {
* Gets the scores for the account.
*
* @param profile The profile.
- * @param page The page to get the scores from.
+ * @param page The page to get the scores from.
* @return The scores.
*/
public ScoreSaberScoresPageToken getPageScores(UserScoreSaberProfile profile, int page) {
@@ -131,7 +132,8 @@ public class ScoreSaberService extends TextWebSocketHandler {
connectWebSocket(); // Reconnect to the WebSocket.
}
- @Override @SneakyThrows
+ @Override
+ @SneakyThrows
protected void handleTextMessage(@NonNull WebSocketSession session, @NonNull TextMessage message) {
// Ignore the connection message.
if (message.getPayload().equals("Connected to the ScoreSaber WSS")) {
diff --git a/src/main/java/cc/fascinated/bat/service/SpotifyService.java b/src/main/java/cc/fascinated/bat/service/SpotifyService.java
new file mode 100644
index 0000000..6ae5544
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/service/SpotifyService.java
@@ -0,0 +1,188 @@
+package cc.fascinated.bat.service;
+
+import cc.fascinated.bat.common.StringUtils;
+import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
+import cc.fascinated.bat.model.BatUser;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.SneakyThrows;
+import net.jodah.expiringmap.ExpiringMap;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import se.michaelthelin.spotify.SpotifyApi;
+import se.michaelthelin.spotify.enums.AuthorizationScope;
+import se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials;
+import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;
+import se.michaelthelin.spotify.requests.data.player.GetUsersCurrentlyPlayingTrackRequest;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Service
+@Getter
+public class SpotifyService {
+ private static final String REDIRECT_URI = "http://localhost:8080/spotify/callback";
+
+ /**
+ * The access token map.
+ */
+ private final Map accessToken = ExpiringMap.builder()
+ .expiration(30, TimeUnit.MINUTES)
+ .build();
+
+ /**
+ * The client ID.
+ */
+ private final String clientId;
+
+ /**
+ * The client secret.
+ */
+ private final String clientSecret;
+
+ /**
+ * The Spotify API instance.
+ */
+ private final SpotifyApi spotifyApi;
+
+ /**
+ * The user service.
+ */
+ private final UserService userService;
+
+ /**
+ * The authorization URL.
+ */
+ private final String authorizationUrl;
+
+ public SpotifyService(@Value("${spotify.client-id}") String clientId, @Value("${spotify.client-secret}") String clientSecret, @NonNull UserService userService) {
+ this.clientId = clientId;
+ this.clientSecret = clientSecret;
+ this.userService = userService;
+
+ this.spotifyApi = new SpotifyApi.Builder()
+ .setClientId(clientId)
+ .setClientSecret(clientSecret)
+ .setRedirectUri(URI.create(REDIRECT_URI))
+ .build();
+ this.authorizationUrl = spotifyApi.authorizationCodeUri()
+ .response_type("code")
+ .scope(
+ AuthorizationScope.APP_REMOTE_CONTROL,
+ AuthorizationScope.USER_READ_PLAYBACK_POSITION,
+ AuthorizationScope.USER_READ_PLAYBACK_STATE,
+ AuthorizationScope.USER_MODIFY_PLAYBACK_STATE,
+ AuthorizationScope.USER_READ_CURRENTLY_PLAYING,
+ AuthorizationScope.APP_REMOTE_CONTROL,
+ AuthorizationScope.STREAMING
+ )
+ .build().execute().toString();
+ }
+
+ /**
+ * Gets the currently playing track for the user.
+ *
+ * @param user the user to check
+ * @return the currently playing track
+ */
+ public CurrentlyPlaying getCurrentlyPlayingTrack(BatUser user) {
+ try {
+ return getSpotifyApi(user).getUsersCurrentlyPlayingTrack().build().execute();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Checks if the user has a track playing.
+ *
+ * @param user the user to check
+ * @return whether a track is playing
+ */
+ public boolean hasTrackPlaying(BatUser user) {
+ return getCurrentlyPlayingTrack(user) != null;
+ }
+
+ /**
+ * Pauses playback for the user.
+ *
+ * @param user the user to start playback for
+ * @return if the playback was paused
+ */
+ @SneakyThrows
+ public boolean pausePlayback(BatUser user) {
+ try {
+ getSpotifyApi(user).pauseUsersPlayback().build().execute();
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Pauses playback for the user.
+ *
+ * @param user the user to start playback for
+ * @return if the playback was paused
+ */
+ @SneakyThrows
+ public boolean resumePlayback(BatUser user) {
+ try {
+ getSpotifyApi(user).startResumeUsersPlayback().build().execute();
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Gets the authorization key to link the user's
+ * Spotify account with their Discord account.
+ *
+ * @param code the code to authorize with
+ * @return the authorization details
+ */
+ @SneakyThrows
+ public String authorize(String code) {
+ AuthorizationCodeCredentials credentials = spotifyApi.authorizationCode(code).build().execute();
+ String key = StringUtils.randomString(16);
+ accessToken.put(key, credentials);
+ return "Authorization key: " + key;
+ }
+
+ /**
+ * Links the user's Spotify account with their Discord account.
+ *
+ * @param user the user to link the account with
+ * @param key the key to link the account with
+ */
+ public void linkAccount(BatUser user, String key) {
+ AuthorizationCodeCredentials credentials = accessToken.get(key);
+ if (credentials == null) {
+ return;
+ }
+ // Link the user's Spotify account
+ SpotifyProfile profile = user.getProfile(SpotifyProfile.class);
+ profile.setAccessToken(credentials.getAccessToken());
+ profile.setRefreshToken(credentials.getRefreshToken());
+ userService.saveUser(user);
+ }
+
+ /**
+ * Gets a new Spotify API instance.
+ *
+ * @return the Spotify API
+ */
+ public SpotifyApi getSpotifyApi(BatUser user) {
+ SpotifyProfile profile = user.getProfile(SpotifyProfile.class);
+ return new SpotifyApi.Builder()
+ .setAccessToken(profile.getAccessToken())
+ .setClientSecret(clientSecret)
+ .build();
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/service/UserService.java b/src/main/java/cc/fascinated/bat/service/UserService.java
index 1297c7d..1a66c7d 100644
--- a/src/main/java/cc/fascinated/bat/service/UserService.java
+++ b/src/main/java/cc/fascinated/bat/service/UserService.java
@@ -10,7 +10,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
-import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@@ -18,7 +17,9 @@ import java.util.concurrent.TimeUnit;
/**
* @author Fascinated (fascinated7)
*/
-@Service @Log4j2 @Getter
+@Service
+@Log4j2
+@Getter
@DependsOn("discordService")
public class UserService {
/**
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index a75ff70..dbae21a 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,11 +1,14 @@
+# Discord Configuration
discord:
token: "oh my goodnesssssssssss"
+# Spotify Configuration
+spotify:
+ client-id: "spotify-client-id"
+ client-secret: "spotify-client-secret"
+
# Spring Configuration
spring:
- # Disable the Spring Web Server
- main:
- web-application-type: none
data:
# MongoDB Configuration
mongodb: