JavaでSpotify Web API叩いてみた

# 本記事について
この記事では、Javaを使用しSpotify Web APIを叩いて、楽曲情報の取得やプレイリストへの追加をしてみた方法を紹介します。

:::note info
【 Spotify Web APIとは 】
音楽ストリーミングサービスで有名なSpotifyが提供しているAPIです。
Spotify Web APIによって下記の情報等が取得できます。

- アルバム情報
- アーティスト情報
- トラック(楽曲)情報
- プレイリスト情報
:::

>**Spotify Web API**
>You can use Spotify's Web API to discover music and podcasts, manage your Spotify library, control audio playback, and much more.

## サンプルコード

今回紹介する機能を実装したサンプルとして、GithubのURLを下記に記載します。

https://github.com/9-sho-5/sys_proj_B01

## 実装について

<dl>
  <dt>言語</dt>
  <dd>Java SE</dd>
  <dt>エディタ</dt>
  <dd>VSCode</dd>
  <dt>Webサーバー(サーブレットコンテナ)</dt>
  <dd>Jetty</dd>
</dl>

# 認証

APIを使用するにあたってSpotify Web APIの認証周りを順に見ていきます。

https://developer.spotify.com/documentation/general/guides/authorization/code-flow/

## 1.1 codeの取得
`アクセストークン`を生成するための`code`の取得方法について解説します。

下記のクエリを持ったURLにアクセスして、持っているSpotify アカウントでログインします。
```
// AUTHORIZATION URL
https://accounts.spotify.com/authorize

// QUERY PARAMETER
client_id="生成したクライアントID"
response_type="code"
redirect_uri="ログイン後に遷移したいURI"
state="SecureRandom()を使用"
scope="スコープの指定"
```
Javaでクエリを持たせたAUTHORIZATION URLを返すメソッドはこんな感じで作りました。
```getAuthorizationUrl.java
// ※それぞれURLEncoderでutf-8にエンコードしています。

// 環境変数の読み込み準備
Dotenv dotenv = Dotenv.configure().load();
// 環境変数からの読み込み
private final String clientId = dotenv.get("CLIENT_ID");
private final String clientSecret = dotenv.get("CLIENT_SECRET");
private final String redirectUri = dotenv.get("REDIRECT_URI");
private final String authorizeUrl = dotenv.get("AUTHORIZE_URL");
private final String[] scope = {
    "playlist-read-private",
    "playlist-modify-private",
    "playlist-read-collaborative",
    "playlist-modify-public",
    "user-read-email",
    "user-read-private",
    "user-modify-playback-state",
    "user-read-playback-state",
    "user-read-currently-playing",
    "user-read-recently-played",
    "user-read-playback-position",
    "user-top-read"
};

public String getAuthorizationUrl() throws UnsupportedEncodingException {
    return String.format("%s?client_id=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s", this.authorizeUrl,
            this.clientId, URLEncoder.encode(this.redirectUri, "utf-8"), String.join(" ", this.scope),
            new SecureRandom());
}
```

URLにアクセスして、Spotifyアカウントでログインすると`アクセストークン`を生成するための`code`が取得できます。
```
Ex.)
https://my-domain.com/callback?code=NApCCg..BkWtQ&state=34fFs29kd09
```

※リダイレクトURIを`http://localhost:8080/〇〇/callback`そして設定した場合、サーブレットでは下記の記述で`code`の取得が可能です。
```getCode.java
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.mashape.unirest.http.exceptions.UnirestException;

@WebServlet("/〇〇/callback")
public final class CallbackServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        // callbackからのレスポンス(code)を取得
        String code = (request.getParameter("code") == null) ? "" : (String) request.getParameter("code");
    }
}
```

## 1.2 アクセストークンの取得
楽曲情報の取得やプレイリストの作成等に必要な`アクセストークン`の取得方法について解説します。

下記のヘッダパラメーター、リクエストボディを持たせて、POSTリクエストを送るとアクセストークンが取得できます。
```
// API ENDPOINT
https://api.spotify.com/v1

// HEADER PARAMETER
Authorization: Basic <base64 encoded クライアントID:クライアントシークレットID>
Content-Type: application/x-www-form-urlencoded

// REQUEST BODY PARAMETER
grant_type="authorization_code"
code="先ほど取得したcode"
redirect_uri="ログイン後に遷移したいURI"
```

Javaでアクセストークンを生成するメソッドはこんな感じで作りました。

```crateAccessToken.java
// ※リクエストボディはそれぞれURLEncoderでutf-8にエンコードしています。

private String accessToken;

public void crateAccessToken() throws UnirestException, UnsupportedEncodingException {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://accounts.spotify.com/api/token"))
        .setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString((clientId + ':' + clientSecret).getBytes(StandardCharsets.UTF_8)))
        .setHeader("Content-Type", "application/x-www-form-urlencoded")
        .POST(BodyPublishers.ofString(String.format("grant_type=%s&code=%s&redirect_uri=%s", URLEncoder.encode("authorization_code", "UTF-8"), URLEncoder.encode(getCode(), "UTF-8"), URLEncoder.encode(redirectUri, "UTF-8"))))
        .build();
    try {
        // リクエストを送信
        var response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
        // レスポンスボディからaccess_tokenの取得
        JSONObject json = new JSONObject(response.body());

        // アクセストークンの設定
        accessToken = json.getString("access_token")
        
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    }
}
```

# 楽曲検索
キーワードを使用して、実際に楽曲検索する方法を解説します。

下記のクエリで検索を行います。
```
// URL
https://api.spotify.com/v1/search

// Query
q: String (検索キーワード)
type: track(楽曲)

Ex.)
https://api.spotify.com/v1/search?q=キーワード＆type＝＆type＝track
```
Javaで楽曲検索するメソッドはこんな感じで作りました。
```search.java
// ※このメソッドでは、検索結果を{アルバム画像URL、アルバム名、アーティスト名、トラック名、トラックID}のJSONで返しています。
// ※クエリはそれぞれURLEncoderでutf-8にエンコードしています。

public String search(String keyword) throws UnsupportedEncodingException {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(apiEndpoint
                    + String.format("/search?q=%s&type=%s&limit=5", keyword, URLEncoder.encode("track", "utf-8"))))
            .setHeader("Authorization", "Bearer " + this.accessToken)
            .setHeader("Content-Type", "application/json")
            .GET()
            .build();

    // レスポンスの格納変数
    StringBuilder builder = null;
    try {
        // リクエストを送信
        var response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
        // レスポンスボディからaccess_tokenの取得
        JSONObject json = new JSONObject(response.body());
        // tracksの取得
        JSONObject tracks = json.getJSONObject("tracks");
        // tracksの中の楽曲データの取得
        JSONArray data = tracks.getJSONArray("items");

        // レスポンスの生成準備
        builder = new StringBuilder();
        // JSON形式でレスポンスデータを構成
        builder.append("{\"data\":");
        builder.append('[');
        for (int i = 0; i < data.length(); i++) {
            builder.append('{');
            builder.append("\"album_image_url\":\"").append(
                    data.getJSONObject(i).getJSONObject("album").getJSONArray("images").getJSONObject(0).get("url"))
                    .append("\",");
            builder.append("\"album_name\":\"").append(data.getJSONObject(i).getJSONObject("album").get("name"))
                    .append("\",");
            builder.append("\"artist_name\":\"")
                    .append(data.getJSONObject(i).getJSONArray("artists").getJSONObject(0).get("name"))
                    .append("\",");
            builder.append("\"track_name\":\"").append(data.getJSONObject(i).get("name")).append("\",");
            builder.append("\"track_id\":\"").append(data.getJSONObject(i).get("uri")).append("\"");
            builder.append("}");
            if (i != data.length() - 1) {
                builder.append(",");
            }
        }
        builder.append(']');
        builder.append('}');

    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    }
    // JSON形式作ったレスポンスデータを返す
    return builder.toString();
}
```

# プレイリストへの追加

指定のプレイリストのIDを使用して、プレイリストへの楽曲追加する方法を解説します。

下記のクエリで検索を行います。
```
// URL
https://api.spotify.com/v1/playlists/指定のプレイリストID/tracks

// HEADER PARAMETER
Authorization: Bearer アクセストークン
Content-Type: application/json

// Query
uris: JSONObject (指定のプレイリストへ追加したいトラックのIDの入ったJSON配列を持つJSONオブジェクト)
=> {
    "uris": [
    "string"
    ]}
```

Javaでプレイリストに楽曲追加するメソッドはこんな感じで作りました。

```addTrack.java
public String addTrack(JSONObject uris) throws UnsupportedEncodingException {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(apiEndpoint + String.format("/playlists/%s/tracks", dotenv.get("PLAYLIST_ID"))))
            .setHeader("Authorization", "Bearer " + this.accessToken)
            .setHeader("Content-Type", "application/json")
            .POST(BodyPublishers.ofString(uris.toString()))
            .build();

    StringBuilder builder = null;
    try {
        // リクエストを送信
        var response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
        // レスポンスボディからレスポンスの取得
        JSONObject json = new JSONObject(response.body());

        builder = new StringBuilder();
        builder.append('{');
        builder.append("\"snapshot\":").append(json).append("");
        builder.append('}');

    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    }
    return builder.toString();
}
```

# 最後に
本記事では、JavaでSpotify Web APIを使用して楽曲情報とプレイリストへの楽曲追加方法を紹介しました。
JavaでAPIを叩くことがなかった私からするとなかなか興味深いものでした。

アウトプットがてらに別言語でも実装してみて見るものいいかもしれないですね。

## 参考文献

https://developer.spotify.com/documentation/web-api/reference/#/

https://qiita.com/suema0331/items/93a8e1c5bda955d3ef6b

https://www.baeldung.com/java-http-client-custom-header

https://hensa40.cutegirl.jp/archives/6542

※Servlet等についてはここでは詳しく述べてませんが、サンプルコードを参考にしていただければと思います。