add spotify feature

This commit is contained in:
Lee
2024-06-28 03:01:21 +01:00
parent 5c7a067f7a
commit fa10cf2019
70 changed files with 966 additions and 170 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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