Bat/src/main/java/cc/fascinated/bat/service/SpotifyService.java
Liam d2d898a5b8
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 36s
disable spotify linking (until they accept out application)
2024-06-29 13:08:13 +01:00

203 lines
6.5 KiB
Java

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 lombok.extern.log4j.Log4j2;
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 java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author Fascinated (fascinated7)
*/
@Service
@Getter
@Log4j2
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;
public SpotifyService(@Value("${spotify.client-id}") String clientId, @Value("${spotify.client-secret}") String clientSecret,
@Value("${spotify.redirect-uri}") String redirectUri, @NonNull UserService userService) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.userService = userService;
this.spotifyApi = new SpotifyApi.Builder()
.setClientId(clientId)
.setClientSecret(clientSecret)
.setRedirectUri(URI.create(redirectUri))
.build();
this.authorizationUrl = spotifyApi.authorizationCodeUri()
.response_type("code")
.scope(AuthorizationScope.values())
.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) {
throw new RuntimeException("Failed to get currently playing track", e);
}
}
/**
* 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) {
getSpotifyApi(user).pauseUsersPlayback().build().execute();
return true;
}
/**
* Pauses playback for the user.
*
* @param user the user to start playback for
* @return if the playback was paused
*/
public boolean resumePlayback(BatUser user) {
try {
getSpotifyApi(user).startResumeUsersPlayback().build().execute();
return true;
} catch (Exception e) {
throw new RuntimeException("Failed to resume playback", e);
}
}
/**
* 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) {
if (code == null) {
return """
<a href="%s">Click here to authorize your Spotify account</a>
""".formatted(authorizationUrl);
}
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);
}
/**
* 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.
*
* @return the Spotify API
*/
@SneakyThrows
public SpotifyApi getSpotifyApi(BatUser user) {
SpotifyProfile profile = user.getProfile(SpotifyProfile.class);
ensureValidToken(profile, user);
return new SpotifyApi.Builder()
.setAccessToken(profile.getAccessToken())
.setRefreshToken(profile.getRefreshToken())
.build();
}
/**
* 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;
}
AuthorizationCodeCredentials credentials = spotifyApi.authorizationCodeRefresh().build().execute();
profile.setAccessToken(credentials.getAccessToken());
profile.setRefreshToken(credentials.getRefreshToken());
profile.setExpiresAt(System.currentTimeMillis() + (credentials.getExpiresIn() * 1000));
userService.saveUser(user);
}
}