add skip spotify command and show song when pausing and resuming the song

This commit is contained in:
Lee 2024-06-29 21:36:45 +01:00
parent df44ae90b9
commit b0949d17e6
8 changed files with 188 additions and 22 deletions

@ -85,6 +85,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.10.0</version>
</dependency>
<!-- Libraries -->
<dependency>

@ -99,6 +99,14 @@ public class LinkSubCommand extends BatSubCommand implements EventListener {
return;
}
String code = codeMapping.getAsString();
if (!spotifyService.isValidLinkCode(code)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The link code you provided is invalid.".formatted(Emojis.CROSS_MARK_EMOJI))
.build())
.queue();
return;
}
spotifyService.linkAccount(user, code);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("%s You have linked your Spotify account!".formatted(Emojis.CHECK_MARK_EMOJI.getFormatted()))

@ -15,6 +15,8 @@ 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.Track;
/**
* @author Fascinated (fascinated7)
@ -46,8 +48,14 @@ public class PauseSubCommand extends BatSubCommand {
}
boolean didPause = spotifyService.pausePlayback(user);
CurrentlyPlaying currentlyPlaying = spotifyService.getCurrentlyPlayingTrack(user);
Track track = (Track) currentlyPlaying.getItem();
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription(didPause ? ":pause_button: Paused the current track."
.setDescription(didPause ? ":pause_button: Paused the track **[%s | %s](%s)**".formatted(
track.getName(),
track.getArtists()[0].getName(),
"https://open.spotify.com/track/%s".formatted(track.getId())
)
: "%s The current track is already paused.".formatted(Emojis.CROSS_MARK_EMOJI))
.build())
.queue();

@ -15,6 +15,8 @@ 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.Track;
/**
* @author Fascinated (fascinated7)
@ -46,9 +48,16 @@ public class ResumeSubCommand extends BatSubCommand {
}
boolean didPause = spotifyService.resumePlayback(user);
CurrentlyPlaying currentlyPlaying = spotifyService.getCurrentlyPlayingTrack(user);
Track track = (Track) currentlyPlaying.getItem();
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription(didPause ? "%s Resumed the current track.".formatted(Emojis.CHECK_MARK_EMOJI) :
"%s The current track is already playing.".formatted(Emojis.CROSS_MARK_EMOJI))
.setDescription(didPause ? "%s Resumed the track **[%s | %s](%s)**".formatted(
Emojis.CHECK_MARK_EMOJI,
track.getName(),
track.getArtists()[0].getName(),
"https://open.spotify.com/track/%s".formatted(track.getId())
)
: "%s The current track is already playing.".formatted(Emojis.CROSS_MARK_EMOJI))
.build())
.queue();
}

@ -0,0 +1,103 @@
package cc.fascinated.bat.features.spotify.command;
import cc.fascinated.bat.Emojis;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Track;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "skip", description = "Skip the current Spotify track")
public class SkipSubCommand extends BatSubCommand {
private static final Logger log = LoggerFactory.getLogger(SkipSubCommand.class);
private final SpotifyService spotifyService;
@Autowired
public SkipSubCommand(@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.errorEmbed()
.setDescription("%s You need to be playing a track to skip a track.".formatted(Emojis.CROSS_MARK_EMOJI))
.build())
.queue();
return;
}
CurrentlyPlaying currentlyPlaying = spotifyService.getCurrentlyPlayingTrack(user);
Track track = (Track) currentlyPlaying.getItem();
String trackName = track.getName();
spotifyService.skipTrack(user);
Track newTrack = getNewTrack(user, trackName);
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("""
:track_next: Skipped the track: **[%s | %s](%s)**
%s New Track: **[%s | %s](%s)**
""".formatted(
trackName,
track.getArtists()[0].getName(),
"https://open.spotify.com/track/" + track.getId(),
Emojis.CHECK_MARK_EMOJI,
newTrack.getName(),
newTrack.getArtists()[0].getName(),
"https://open.spotify.com/track/" + newTrack.getId()
)).build())
.queue();
}
/**
* Get the next track that is playing
*
* @param user The user to get the track for
* @param oldName The name of the old track
* @return The new track
*/
public Track getNewTrack(BatUser user, String oldName) {
int checks = 0;
try {
Thread.sleep(150);
while (checks < 10) {
CurrentlyPlaying currentlyPlaying = spotifyService.getCurrentlyPlayingTrack(user);
Track track = (Track) currentlyPlaying.getItem();
if (track.getName().equals(oldName)) {
Thread.sleep(250);
checks++;
} else {
log.info("Found new track {} in {} checks", track.getName(), checks);
return (Track) currentlyPlaying.getItem();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}

@ -21,5 +21,6 @@ public class SpotifyCommand extends BatCommand {
super.addSubCommand(context.getBean(PauseSubCommand.class));
super.addSubCommand(context.getBean(ResumeSubCommand.class));
super.addSubCommand(context.getBean(CurrentSubCommand.class));
super.addSubCommand(context.getBean(SkipSubCommand.class));
}
}

@ -9,6 +9,7 @@ import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import net.jodah.expiringmap.ExpiringMap;
import org.apache.hc.core5.http.ParseException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import se.michaelthelin.spotify.SpotifyApi;
@ -17,6 +18,7 @@ import se.michaelthelin.spotify.exceptions.SpotifyWebApiException;
import se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials;
import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -77,6 +79,18 @@ public class SpotifyService {
.build().execute().toString();
}
/**
* Starts playback for the user.
*
* @param user the user to start playback for
* @return if the playback was started
*/
@SneakyThrows
public boolean skipTrack(BatUser user) {
getSpotifyApi(user).skipUsersPlaybackToNextTrack().build().execute();
return true;
}
/**
* Gets the currently playing track for the user.
*
@ -145,25 +159,6 @@ public class SpotifyService {
""".formatted(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());
profile.setExpiresAt(System.currentTimeMillis() + (credentials.getExpiresIn() * 1000));
userService.saveUser(user);
}
/**
* Gets a new Spotify API instance.
*
@ -197,9 +192,40 @@ public class SpotifyService {
profile.setAccessToken(credentials.getAccessToken());
profile.setExpiresAt(System.currentTimeMillis() + (credentials.getExpiresIn() * 1000));
userService.saveUser(user);
log.info("Refreshed Spotify token for user {}", user.getName());
} catch (SpotifyWebApiException ex) {
log.error("Failed to refresh Spotify token", ex);
throw new SpotifyTokenRefreshException("Failed to refresh Spotify token", ex);
}
}
/**
* 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());
profile.setExpiresAt(System.currentTimeMillis() + (credentials.getExpiresIn() * 1000));
userService.saveUser(user);
log.info("Linked Spotify account for user {}", user.getName());
}
/**
* Checks if the link code is valid.
*
* @param code the code to check
* @return if the code is valid
*/
public boolean isValidLinkCode(String code) {
return accessToken.containsKey(code);
}
}

@ -2,6 +2,12 @@
discord:
token: "oh my goodnesssssssssss"
# Sentry Configuration
sentry:
dsn: "CHANGE_ME"
tracesSampleRate: 1.0
environment: "development"
# Spotify Configuration
spotify:
redirect-uri: "http://localhost:8080/spotify/callback"