diff --git a/pom.xml b/pom.xml
index 0e7e70c..5f06c01 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- space.b00tload
+ space.b00tload.spotifyutils
SpotifyDedupe
- 1.0.0.alpha1
+ 1.0.0
SpotifyDeduper
Removes duplicates from spotify playlists.
@@ -89,10 +89,20 @@
2.11.0
- com.electronwill.night-config
- toml
- 3.6.0
-
+ com.electronwill.night-config
+ toml
+ 3.6.0
+
+
+ io.javalin
+ javalin
+ 5.3.1
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.6
+
\ No newline at end of file
diff --git a/src/main/java/space/b00tload/bsu/dedupe/DuplicateTrack.java b/src/main/java/space/b00tload/bsu/dedupe/DuplicateTrack.java
new file mode 100644
index 0000000..fafb513
--- /dev/null
+++ b/src/main/java/space/b00tload/bsu/dedupe/DuplicateTrack.java
@@ -0,0 +1,7 @@
+package space.b00tload.bsu.dedupe;
+
+import se.michaelthelin.spotify.model_objects.specification.Track;
+
+public record DuplicateTrack(Track track, int location) {
+
+}
diff --git a/src/main/java/space/b00tload/bsu/dedupe/SpotifyDedupe.java b/src/main/java/space/b00tload/bsu/dedupe/SpotifyDedupe.java
index 7db5a74..7d1072e 100644
--- a/src/main/java/space/b00tload/bsu/dedupe/SpotifyDedupe.java
+++ b/src/main/java/space/b00tload/bsu/dedupe/SpotifyDedupe.java
@@ -1,23 +1,46 @@
package space.b00tload.bsu.dedupe;
-import space.b00tload.bsu.dedupe.util.TomlConfiguration;
-import space.b00tload.bsu.dedupe.util.VersionChecker;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import io.javalin.Javalin;
+import io.javalin.http.ContentType;
+import io.javalin.http.HttpStatus;
+import org.apache.hc.core5.http.ParseException;
+import se.michaelthelin.spotify.SpotifyApi;
+import se.michaelthelin.spotify.enums.ModelObjectType;
+import se.michaelthelin.spotify.exceptions.SpotifyWebApiException;
+import se.michaelthelin.spotify.model_objects.specification.*;
+import space.b00tload.bsu.dedupe.util.*;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.List;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
public class SpotifyDedupe {
public static final String LINE_SEPERATOR = System.getProperty("line.separator");
public static final String USER_HOME = System.getProperty("user.home");
+ public static String SOFTWARE_VERSION = SpotifyDedupe.class.getPackage().getImplementationVersion();
+ public static String USER_AGENT = "SpotifyDedupe/%VERSION% (" + System.getProperty("os.name") + "; " + System.getProperty("os.arch") + ") Java/" + System.getProperty("java.version");
public static final String CONFIG_BASE = Paths.get(USER_HOME, ".bsu", "dedupe").toString();
public static final Path CREDENTIAL_LOCATION = Paths.get(CONFIG_BASE, "spotify.bsucred");
- private static List requiredConfig = List.of("spotify.client", "spotify.secret");
+ private static final List requiredConfig = List.of("spotify.client", "spotify.secret");
- public static void main(String[] args) {
+ public static void main(String[] args) throws IOException, ParseException, SpotifyWebApiException {
+ if(Objects.isNull(SOFTWARE_VERSION)){
+ SOFTWARE_VERSION = "0.1.1-indev";
+ }
+ USER_AGENT = USER_AGENT.replace("%VERSION%", SOFTWARE_VERSION);
Runtime.getRuntime().addShutdownHook(new Thread(TomlConfiguration::close));
VersionChecker.checkVerion();
@@ -26,7 +49,143 @@ public class SpotifyDedupe {
TomlConfiguration.validate(requiredConfig);
+ SpotifyApi.Builder builder = SpotifyApi.builder();
+ builder.setClientId(TomlConfiguration.getString("spotify.client"));
+ builder.setClientSecret(TomlConfiguration.getString("spotify.secret"));
+ builder.setRedirectUri(URI.create("http://localhost:9876/callback/spotify/"));
+ SpotifyApi api = builder.build();
+ AtomicBoolean waiting = new AtomicBoolean(true);
+ if (TomlConfiguration.getBoolean("spotify.caching.enabled") && TokenHelper.existsTokens()) {
+ System.out.println("Cached credentials have been found.");
+ System.out.println("Fetching credentials from cache.");
+ SpotifyCredentials cred = TokenHelper.fetchTokens();
+ api.setRefreshToken(cred.getRefreshToken());
+
+ if(!cred.isValid()){
+ System.out.println("Cached credentials are invalid due to age. Refreshing and saving to cache");
+ cred.refreshCredentials(api.authorizationCodeRefresh().build().execute());
+ TokenHelper.saveTokens(cred);
+ }
+ api.setAccessToken(cred.getAccessToken());
+ } else {
+ try (Javalin webserver = Javalin.create().start(9876)) {
+ if (TomlConfiguration.getBoolean("spotify.caching.enabled")) System.out.println("No cached credentials have been found.");
+ System.out.println("Starting webserver to initiate web based authentication.");
+ Runtime.getRuntime().addShutdownHook(new Thread(webserver::stop));
+ webserver.exception(Exception.class, (exception, ctx) -> {
+ ctx.result(exception.getMessage());
+ });
+ webserver.get("/callback/spotify", ctx -> {
+ if(ctx.queryParamMap().containsKey("code")) {
+ System.out.println("Received spotify authentication code. Requesting credentials.");
+ SpotifyCredentials cred = new SpotifyCredentials(api.authorizationCode(ctx.queryParam("code")).setHeader("User-Agent", USER_AGENT).build().execute());
+ api.setAccessToken(cred.getAccessToken());
+ if(TomlConfiguration.getBoolean("spotify.caching.enabled")) {
+ System.out.println("Saving credentials to cache.");
+ TokenHelper.saveTokens(cred);
+ }
+ ctx.result("success. ").contentType(ContentType.TEXT_HTML).status(HttpStatus.OK);
+ waiting.set(false);
+ } else {
+ System.out.println("Error: Spotify authorization failed."+LINE_SEPERATOR+ctx.queryParam("error"));
+ System.exit(500);
+ }
+ });
+ System.out.println("Waiting for Spotify authorization.");
+
+
+ String authPage = "https://accounts.spotify.com/authorize?client_id="
+ + api.getClientId()
+ + "&response_type=code&state=state" +
+ "&redirect_uri=" + URLEncoder.encode(api.getRedirectURI().toString(), StandardCharsets.UTF_8)
+ + "&scope=user-read-private%20playlist-modify-private%20playlist-modify-public%20playlist-read-private%20playlist-read-collaborative";
+ BrowserHelper.openInBrowser(authPage);
+
+ while (waiting.get());
+ }
+ }
+ String id = null;
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
+ System.out.println("Enter playlist link: ");
+ String s = br.readLine();
+ id = s.split("://")[1].replace("open.spotify.com/playlist/", "").split("\\?")[0];
+ }
+ if(Objects.isNull(id)){
+ System.out.println("Invalid link");
+ return;
+ }
+ Paging listPage = api.getPlaylistsItems(id).setHeader("User-Agent", USER_AGENT).build().execute();
+ List playlist = new LinkedList<>(List.of(listPage.getItems()));
+ for(int offset = 100; offset < listPage.getTotal(); offset += 100){
+ Paging nextListPage = api.getPlaylistsItems(id).offset(offset).setHeader("User-Agent", USER_AGENT).build().execute();
+ playlist.addAll(List.of(nextListPage.getItems()));
+ }
+
+ List