Initial commit

This commit is contained in:
2020-09-23 11:30:59 +02:00
commit 9df2ffe291
16 changed files with 523 additions and 0 deletions

22
LICENSE.md Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2020 David Álvarez González
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

50
README.md Normal file
View File

@@ -0,0 +1,50 @@
# MusicAdvisor
CLI app using Spotify API to create a personal music advisor that makes preference-based suggestions and even shares links to new releases and featured playlists.
## How to run
In root of the project:
> gradle run -q --console=plain
## Parameters
`-access`
Authorization server path. If it isn't set, default server is https://accounts.spotify.com
`-resource`
API server path. If it isn't set, default server is https://api.spotify.com
`-page`
Number of entries that should be shown on a page. If it isn't set, default value is 5.
## Commands
`auth` Creates a link to confirm access of the app.
![](src/advisor/resources/auth.png)
`featured` Gets a paginated list of Spotify-featured playlists with their links fetched from API.
![](src/advisor/resources/featured.png)
`new` Gets a paginated list of new albums with artists and links on Spotify.
![](src/advisor/resources/new.png)
`categories` Gets a paginated list of all available categories on Spotify (just their names)
![](src/advisor/resources/categories.png)
`playlists C_NAME` Gets a paginated list containing playlists of this category (where C_NAME is the name of category) and their links on Spotify.
![](src/advisor/resources/playlists.png)
`next` Goes to next page.
`prev` Goes to previous page.
`exit` Exits the program.
![](src/advisor/resources/exit.png)

29
build.gradle Normal file
View File

@@ -0,0 +1,29 @@
apply plugin: 'java'
apply plugin: 'application'
group 'advisor'
version '1.0-SNAPSHOT'
sourceCompatibility = 11
mainClassName = 'advisor.Main'
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.code.gson:gson:+'
}
jar {
manifest {
attributes 'Main-Class' : 'advisor.Main'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
run{
standardInput = System.in
}

97
src/advisor/Main.java Normal file
View File

@@ -0,0 +1,97 @@
package advisor;
import advisor.auth.Authorization;
import advisor.auth.LocalServer;
import advisor.config.Params;
import advisor.controller.AdvisorController;
import advisor.model.AdvisorModel;
import advisor.view.AdvisorView;
import java.util.List;
import java.util.Scanner;
import java.util.StringJoiner;
public class Main {
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
String accessServer = Params.ACCESS_SERVER;
String resourceServer = Params.RESOURCE_SERVER;
boolean userAuth = false, exit = false, access = false, resource = false, page = false;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-access") && !access) {
accessServer = args[i + 1];
access = true;
i++;
}
if (args[i].equals("-resource") && !resource) {
resourceServer = args[i + 1];
resource = true;
i++;
}
if (args[i].equals("-page") && !page) {
Params.RESULTS_PER_PAGE = Integer.parseInt(args[i + 1]);
page = true;
i++;
}
}
AdvisorController advisorController = new AdvisorController(resourceServer);
while (!exit) {
String[] input = sc.nextLine().split("\\s+");
if (!userAuth) {
if (input[0].equals("auth")) {
LocalServer localServer = new LocalServer(accessServer);
localServer.startServer();
localServer.getCode();
localServer.stopServer();
Authorization authorization = new Authorization(accessServer);
authorization.getToken();
userAuth = true;
AdvisorView.printMessage(Params.SUCCESS);
} else {
AdvisorView.printMessage(Params.ANSWER_DENIED_ACCESS);
}
} else {
switch (input[0]) {
case "featured":
AdvisorView.print(advisorController.getFeaturedPlaylists());
break;
case "new":
AdvisorView.print(advisorController.getNewReleases());
break;
case "categories":
AdvisorView.print(advisorController.getCategories());
break;
case "playlists":
StringJoiner sj = new StringJoiner(" ");
for (int i = 1; i < input.length; i++) {
sj.add(input[i]);
}
String categoryName = sj.toString();
List<AdvisorModel> categoryPlaylists = advisorController.getCategoryPlaylists(categoryName);
if (categoryPlaylists != null) {
AdvisorView.print(categoryPlaylists);
} else {
AdvisorView.printMessage(Params.UNKNOWN_CATEGORY_NAME);
}
break;
case "next":
AdvisorView.printNextPage();
break;
case "prev":
AdvisorView.printPrevPage();
break;
case "exit":
AdvisorView.printMessage(Params.GOODBYE);
exit = true;
break;
default:
AdvisorView.printMessage(Params.INCORRECT_COMMAND);
}
}
}
}
}

View File

@@ -0,0 +1,42 @@
package advisor.auth;
import advisor.config.Params;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Authorization {
private static String accessServer;
public Authorization(String accessServer) {
Authorization.accessServer = accessServer;
}
public void getToken() throws IOException, InterruptedException {
System.out.println(Params.MAKING_HTTP_REQUEST_FOR_TOKEN);
HttpRequest request = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(
"client_id=" + Params.CLIENT_ID
+ "&client_secret=" + Params.CLIENT_SECRET
+ "&grant_type=" + Params.GRANT_TYPE
+ "&code=" + Params.AUTH_CODE
+ "&redirect_uri=" + Params.REDIRECT_URI))
.header("Content-Type", "application/x-www-form-urlencoded")
.uri(URI.create(accessServer + Params.TOKEN_PART))
.build();
HttpClient client = HttpClient.newBuilder().build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JsonObject responseJson = JsonParser.parseString(response.body()).getAsJsonObject();
Params.TOKEN_CODE = responseJson.get("access_token").getAsString();
}
}

View File

@@ -0,0 +1,61 @@
package advisor.auth;
import advisor.config.Params;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
public class LocalServer {
private static String accessServer;
private static HttpServer httpServer;
public LocalServer(String accessServer) {
LocalServer.accessServer = accessServer;
}
public void startServer() throws IOException {
httpServer = HttpServer.create();
httpServer.bind(new InetSocketAddress(8080), 0);
httpServer.start();
}
public void getCode() throws InterruptedException {
System.out.println("use this link to request the access code:");
System.out.println(accessServer + Params.AUTHORIZE_PART
+ "?client_id=" + Params.CLIENT_ID
+ "&redirect_uri=" + Params.REDIRECT_URI
+ "&response_type=" + Params.RESPONSE_TYPE);
System.out.println("waiting for code...");
httpServer.createContext("/",
exchange -> {
String code = exchange.getRequestURI().getQuery();
String result, answer;
if (code != null && code.contains("code")) {
Params.AUTH_CODE = code.substring(5);
result = "Got the code. Return back to your program.";
answer = "code received";
} else {
result = "Not found authorization code. Try again.";
answer = "code not received";
}
exchange.sendResponseHeaders(200, result.length());
exchange.getResponseBody().write(result.getBytes());
exchange.getResponseBody().close();
System.out.println(answer);
}
);
while (Params.AUTH_CODE.equals("")) {
Thread.sleep(10);
}
}
public void stopServer() {
httpServer.stop(10);
}
}

View File

@@ -0,0 +1,27 @@
package advisor.config;
public class Params {
public static final String CLIENT_ID = "dd6e7d19d2624ee48db69174ac093a2f";
public static final String CLIENT_SECRET = "84fcf5eab40a4854a722bd95802cec5c";
public static final String AUTHORIZE_PART = "/authorize";
public static final String RESPONSE_TYPE = "code";
public static final String TOKEN_PART = "/api/token";
public static final String GRANT_TYPE = "authorization_code";
public static final String ACCESS_SERVER = "https://accounts.spotify.com";
public static final String RESOURCE_SERVER = "https://api.spotify.com";
public static final String NEW_RELEASES= "/v1/browse/new-releases";
public static final String MAKING_HTTP_REQUEST_FOR_TOKEN= "making http request for access_token...";
public static final String FEATURED_PLAYLISTS= "/v1/browse/featured-playlists";
public static final String CATEGORIES = "/v1/browse/categories";
public static final String PLAYLISTS= "/playlists";
public static final String REDIRECT_URI = "http://localhost:8080";
public static final String SUCCESS = "---SUCCESS---";
public static final String ANSWER_DENIED_ACCESS = "Please, provide access for application.";
public static final String UNKNOWN_CATEGORY_NAME = "Unknown category name.";
public static final String GOODBYE = "---GOODBYE!---";
public static final String INCORRECT_COMMAND = "Incorrect command. Try again.";
public static final String NO_MORE_PAGES = "No more pages.";
public static String AUTH_CODE = "";
public static String TOKEN_CODE = "";
public static int RESULTS_PER_PAGE = 5;
}

View File

@@ -0,0 +1,112 @@
package advisor.controller;
import advisor.model.AdvisorModel;
import advisor.config.Params;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
public class AdvisorController {
private static String resourceServer;
public AdvisorController(String resourceServer) {
AdvisorController.resourceServer = resourceServer;
}
public List<AdvisorModel> getNewReleases() throws IOException, InterruptedException {
List<AdvisorModel> newReleases = new ArrayList<>();
HttpRequest newReleasesRequest = createRequest(resourceServer + Params.NEW_RELEASES);
for (JsonElement item: Objects.requireNonNull(getJsonItems(newReleasesRequest, "albums"))) {
AdvisorModel album = new AdvisorModel();
album.setAlbum(item.getAsJsonObject().get("name").toString());
StringJoiner joiner = new StringJoiner(", ", "[", "]");
for (JsonElement artist: item.getAsJsonObject().getAsJsonArray("artists")) {
String artistName = artist.getAsJsonObject().get("name").getAsString();
joiner.add(artistName);
}
album.setArtists(String.valueOf(joiner));
album.setLink(item.getAsJsonObject().get("external_urls").getAsJsonObject().get("spotify").toString());
newReleases.add(album);
}
return newReleases;
}
public List<AdvisorModel> getFeaturedPlaylists() throws IOException, InterruptedException {
List<AdvisorModel> featuredPlaylists = new ArrayList<>();
HttpRequest featuredPlaylistsRequest = createRequest(resourceServer + Params.FEATURED_PLAYLISTS);
return getPlaylists(featuredPlaylists, featuredPlaylistsRequest);
}
private List<AdvisorModel> getPlaylists(List<AdvisorModel> categoryPlaylists, HttpRequest categoryPlaylistsRequest) throws IOException, InterruptedException {
for (JsonElement item: Objects.requireNonNull(getJsonItems(categoryPlaylistsRequest, "playlists"))) {
AdvisorModel categoryPlaylist = new AdvisorModel();
categoryPlaylist.setAlbum(item.getAsJsonObject().get("name").toString());
categoryPlaylist.setLink(item.getAsJsonObject().get("external_urls").getAsJsonObject().get("spotify").toString());
categoryPlaylists.add(categoryPlaylist);
}
return categoryPlaylists;
}
public List<AdvisorModel> getCategories() throws IOException, InterruptedException {
List<AdvisorModel> categories = new ArrayList<>();
HttpRequest categoriesRequest = createRequest(resourceServer + Params.CATEGORIES);
for (JsonElement item: Objects.requireNonNull(getJsonItems(categoriesRequest, "categories"))) {
AdvisorModel category = new AdvisorModel();
category.setAlbum(item.getAsJsonObject().get("name").toString());
categories.add(category);
}
return categories;
}
public List<AdvisorModel> getCategoryPlaylists(String categoryName) throws IOException, InterruptedException {
List<AdvisorModel> categoryPlaylists = new ArrayList<>();
String categoryID = getCategoryIdByCategoryName(categoryName);
HttpRequest categoryPlaylistsRequest =
createRequest(resourceServer + Params.CATEGORIES + "/" + categoryID + Params.PLAYLISTS);
return (categoryID == null) ? null : getPlaylists(categoryPlaylists, categoryPlaylistsRequest);
}
private String getCategoryIdByCategoryName(String categoryName) throws IOException, InterruptedException {
HttpRequest categories = createRequest(resourceServer + Params.CATEGORIES);
JsonArray items = getJsonItems(categories, "categories");
assert items != null;
for(JsonElement item: items){
String name = item.getAsJsonObject().get("name").getAsString();
if(categoryName.equalsIgnoreCase(name)){
return item.getAsJsonObject().get("id").getAsString();
}
}
return null;
}
private JsonArray getJsonItems(HttpRequest request, String element) throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder().build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JsonObject responseJson = JsonParser.parseString(response.body()).getAsJsonObject();
if (responseJson.get("error") == null){
JsonObject featured = responseJson.getAsJsonObject(element);
return featured.getAsJsonArray("items");
}
System.out.println(responseJson.getAsJsonObject("error").get("message").getAsString());
return null;
}
private static HttpRequest createRequest(String requestedFeatureURL) {
return HttpRequest.newBuilder()
.header("Authorization", "Bearer " + Params.TOKEN_CODE)
.uri(URI.create(requestedFeatureURL))
.GET()
.build();
}
}

View File

@@ -0,0 +1,28 @@
package advisor.model;
public class AdvisorModel {
String album = null;
String artists = null;
String link = null;
public void setAlbum(String album) {
this.album = album;
}
public void setArtists(String authors) {
this.artists = authors;
}
public void setLink(String link) {
this.link = link;
}
@Override
public String toString() {
StringBuilder info = new StringBuilder();
if (album != null) { info.append(album).append("\n"); }
if (artists != null) { info.append(artists).append("\n"); }
if (link != null) { info.append(link).append("\n"); }
return info.toString().replaceAll("\"", "");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,55 @@
package advisor.view;
import advisor.model.AdvisorModel;
import advisor.config.Params;
import java.util.List;
public class AdvisorView {
static int elem;
static int page;
static List<AdvisorModel> data;
static int pagesCount;
public static void printMessage(String message) {
System.out.println(message);
}
public static void print(List<AdvisorModel> data) {
elem -= Params.RESULTS_PER_PAGE;
page = 0;
AdvisorView.data = data;
pagesCount = data.size() / Params.RESULTS_PER_PAGE;
pagesCount += data.size() % Params.RESULTS_PER_PAGE != 0 ? 1 : 0;
printNextPage();
}
public static void printNextPage() {
if (page >= pagesCount) {
System.out.println(Params.NO_MORE_PAGES);
} else {
elem += Params.RESULTS_PER_PAGE;
page++;
print();
}
}
public static void printPrevPage() {
if (page == 1) {
System.out.println(Params.NO_MORE_PAGES);
} else {
elem -= Params.RESULTS_PER_PAGE;
page--;
print();
}
}
public static void print() {
data.stream()
.skip(elem)
.limit(Params.RESULTS_PER_PAGE)
.forEach(System.out::println);
System.out.printf("---PAGE %d OF %d---\n", page, pagesCount);
}
}