2024-06-28 03:01:21 +01:00
|
|
|
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 java.net.URI;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @author Fascinated (fascinated7)
|
|
|
|
*/
|
|
|
|
@Service
|
|
|
|
@Getter
|
|
|
|
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")
|
|
|
|
.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();
|
|
|
|
}
|
|
|
|
}
|