Bat/src/main/java/cc/fascinated/bat/service/SpotifyService.java

206 lines
6.7 KiB
Java
Raw Normal View History

2024-06-28 03:01:21 +01:00
package cc.fascinated.bat.service;
import cc.fascinated.bat.common.StringUtils;
2024-06-29 16:54:39 +01:00
import cc.fascinated.bat.exception.spotify.SpotifyTokenRefreshException;
2024-06-28 03:01:21 +01:00
import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
import cc.fascinated.bat.model.BatUser;
import lombok.Getter;
import lombok.NonNull;
import lombok.SneakyThrows;
2024-06-28 19:59:29 +01:00
import lombok.extern.log4j.Log4j2;
2024-06-28 03:01:21 +01:00
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;
2024-06-29 16:54:39 +01:00
import se.michaelthelin.spotify.exceptions.SpotifyWebApiException;
2024-06-28 03:01:21 +01:00
import se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials;
import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author Fascinated (fascinated7)
*/
@Service
@Getter
2024-06-28 19:59:29 +01:00
@Log4j2
2024-06-28 03:01:21 +01:00
public class SpotifyService {
/**
* The access token map.
*/
private final Map<String, AuthorizationCodeCredentials> 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;
2024-06-28 03:05:36 +01:00
public SpotifyService(@Value("${spotify.client-id}") String clientId, @Value("${spotify.client-secret}") String clientSecret,
@Value("${spotify.redirect-uri}") String redirectUri, @NonNull UserService userService) {
2024-06-28 03:01:21 +01:00
this.clientId = clientId;
this.clientSecret = clientSecret;
this.userService = userService;
this.spotifyApi = new SpotifyApi.Builder()
.setClientId(clientId)
.setClientSecret(clientSecret)
2024-06-28 03:05:36 +01:00
.setRedirectUri(URI.create(redirectUri))
2024-06-28 03:01:21 +01:00
.build();
this.authorizationUrl = spotifyApi.authorizationCodeUri()
.response_type("code")
2024-06-28 03:56:34 +01:00
.scope(AuthorizationScope.values())
2024-06-28 03:01:21 +01:00
.build().execute().toString();
}
/**
* Gets the currently playing track for the user.
*
* @param user the user to check
* @return the currently playing track
*/
2024-06-29 16:54:39 +01:00
@SneakyThrows
2024-06-28 03:01:21 +01:00
public CurrentlyPlaying getCurrentlyPlayingTrack(BatUser user) {
2024-06-29 16:54:39 +01:00
return getSpotifyApi(user).getUsersCurrentlyPlayingTrack().build().execute();
2024-06-28 03:01:21 +01:00
}
/**
* 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) {
2024-06-28 19:59:29 +01:00
getSpotifyApi(user).pauseUsersPlayback().build().execute();
return true;
2024-06-28 03:01:21 +01:00
}
/**
* Pauses playback for the user.
*
* @param user the user to start playback for
* @return if the playback was paused
*/
2024-06-29 16:54:39 +01:00
@SneakyThrows
2024-06-28 03:01:21 +01:00
public boolean resumePlayback(BatUser user) {
2024-06-29 16:54:39 +01:00
getSpotifyApi(user).startResumeUsersPlayback().build().execute();
return true;
2024-06-28 03:01:21 +01:00
}
/**
* 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) {
2024-06-28 03:13:54 +01:00
if (code == null) {
return """
<a href="%s">Click here to authorize your Spotify account</a>
""".formatted(authorizationUrl);
2024-06-28 03:13:54 +01:00
}
2024-06-28 03:01:21 +01:00
AuthorizationCodeCredentials credentials = spotifyApi.authorizationCode(code).build().execute();
String key = StringUtils.randomString(16);
accessToken.put(key, credentials);
return """
<p>Successfully authorized your Spotify account!</p>
<p>Your key is: <strong>%s</strong></p>
""".formatted(key);
2024-06-28 03:01:21 +01:00
}
/**
* Links the user's Spotify account with their Discord account.
*
* @param user the user to link the account with
2024-06-28 19:59:29 +01:00
* @param key the key to link the account with
2024-06-28 03:01:21 +01:00
*/
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());
2024-06-28 19:59:29 +01:00
profile.setExpiresAt(System.currentTimeMillis() + (credentials.getExpiresIn() * 1000));
2024-06-28 03:01:21 +01:00
userService.saveUser(user);
}
/**
* Gets a new Spotify API instance.
*
* @return the Spotify API
*/
2024-06-28 03:50:38 +01:00
@SneakyThrows
2024-06-28 03:01:21 +01:00
public SpotifyApi getSpotifyApi(BatUser user) {
SpotifyProfile profile = user.getProfile(SpotifyProfile.class);
2024-06-28 19:59:29 +01:00
ensureValidToken(profile, user);
2024-06-29 16:54:39 +01:00
return new SpotifyApi.Builder().setAccessToken(profile.getAccessToken()).build();
2024-06-28 19:59:29 +01:00
}
2024-06-28 03:50:38 +01:00
2024-06-28 19:59:29 +01:00
/**
* Ensures the user has a valid Spotify access token.
*
* @param user the user to get the token for
*/
@SneakyThrows
public void ensureValidToken(SpotifyProfile profile, BatUser user) {
if (profile.getExpiresAt() == null || profile.getExpiresAt() > System.currentTimeMillis()) {
return;
2024-06-28 03:50:38 +01:00
}
2024-06-29 16:54:39 +01:00
SpotifyApi api = new SpotifyApi.Builder()
.setClientId(clientId)
.setClientSecret(clientSecret)
.setAccessToken(profile.getAccessToken())
.setRefreshToken(profile.getRefreshToken())
.build();
try {
AuthorizationCodeCredentials credentials = api.authorizationCodeRefresh().build().execute();
profile.setAccessToken(credentials.getAccessToken());
profile.setExpiresAt(System.currentTimeMillis() + (credentials.getExpiresIn() * 1000));
userService.saveUser(user);
} catch (SpotifyWebApiException ex) {
log.error("Failed to refresh Spotify token", ex);
throw new SpotifyTokenRefreshException("Failed to refresh Spotify token", ex);
}
2024-06-28 03:01:21 +01:00
}
}