Rust で Spotify Web API を叩いて遊んでみた

はじめに

テントの中から失礼します、CX事業本部のてんとタカハシです!

Spotify が完全に仕事のお供になっています。というのと、Rust 書きたいな〜という気分から、Rust で Spotify Web API を叩いて遊んでみましたので、記事にしようと思います。

今回は、生で API を叩くのではなく、Rust で Spotify Web API をラップした rspotify という ライブラリを使用します。こちらのライブラリですが、ドキュメントに各エンドポイントのサンプルコードが置いてあり、とても親切です。

Developers.IO では、過去に Spotify Web APIの使い方 という記事が公開されていますので、併せて参考にして頂けると良いのかなと思います。Spotify for Developers にてアプリの新規作成を行う手順については、UI が新しくなっていたりするので、改めてこの記事でご紹介しようと思います。

また、下記のリポジトリにソースコード一式を置いていますので、併せて参考にして頂ければと思います。

GitHub - iam326/rust-spotify

前提

  • Spotify のアカウントが作成済みであること
  • Spotify のプレミアムプランが登録済みであること
  • Spotify のアプリが PC (Mac) にインストールされていること
  • Rust がインストール済みであること

環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.6
BuildVersion:   19G2021

$ cargo version
cargo 1.46.0 (149022b1d 2020-07-17)

$ rustc --version
rustc 1.46.0 (04488afe3 2020-08-24)

Spotify Web API で何ができるの

下記ができます。詳細は API の ドキュメント をご参照ください。

  • 楽曲の検索
  • プレイヤーの操作(楽曲の再生や停止、スキップなど)
  • プレイリストの操作(作成や楽曲の追加など)
  • ユーザーの試聴傾向に基づいた情報の取得
  • などなど

尚、エンドポイントの中には、Beta 版のものが存在しています。例えば、プレイヤーの操作に関するものは全て Beta 版です。今回ご紹介する実装でも、Beta 版のエンドポイントを使用していたりするので、ある日突然動作しなくなる可能性があります。ご注意ください。

Spotify Web API を使用するための準備

Spotify Web API を叩くために必要な Client ID を取得したり、Redirect URI の設定を行います。

Spotify for Developers にログインする

Spotify for Developers にログインします。

アプリを新規作成する

ログインするとダッシュボードが表示されます。「CREATE AN APP」ボタンをクリックしてください。

※ 私の環境では既に2つアプリを作成済みのため、下記のような画面となっております

ダイアログが表示されますので、App name、App description を入力した後、チェックボックスにチェックを入れて、「CREATE」ボタンをクリックしてください。

すると、アプリが作成されて、下記の画面に遷移します。画面左側に Client ID が表示されます。また、その下の「SHOW CLIENT SECRET」をクリックすることで、Client Secret が表示されます。これら2つの情報は、API を叩く際に必要となります。

Redirect URI を設定する

API を叩くために1つだけ設定が必要になります。画面右上にある「EDIT SETTINGS」ボタンをクリックしてください。すると、設定ダイアログが表示されますので、Redirect URIs の入力欄に http://127.0.0.1:8080/ と入力した後、「ADD」ボタンをクリックしてください。その後、ダイアログの下部までスクロールして、「SAVE」ボタンをクリックしてください。

これで、Spotify for Developers 画面上での設定は完了となります。

実装前の準備

Rust のプロジェクトを作成する

ここでは、rust-spotify という名前にします。

$ cargo new rust-spotify

使用するクレートを設定する

Cargo.toml 内の dependencies を下記に変更してください。

Cargo.toml

[package]

...

[dependencies]
rspotify = { version = "0.10"}
tokio = { version = "0.2", features = ["full"] }

環境変数を設定する

先ほど、Spotify for Developers にて取得した Client ID、Client Secret と Redirect URI を環境変数に設定します。ここで設定した環境変数を rspotify が読み込んで、API を叩く際に使用されます。環境変数ではなく、.env ファイルを読み込ませる方法もありますが、今回は説明を省きます。

export CLIENT_ID="<SPOTIFY_CLIENT_ID>"
export CLIENT_SECRET="<SPOTIFY_CLIENT_SECRET>"
export REDIRECT_URI="http://127.0.0.1:8080/"

実装

今回は Spotify Web API を使用して、下記をやってみようと思います。

  • アーティストの情報を検索する
  • 指定したアーティストの楽曲人気 TOP10 を表示する
  • 指定した楽曲を再生する

それぞれを1ファイルとして src/bin の中に実装していきます。

アーティストの情報を検索する

実装は下記になります。引数で検索するアーティスト名を受け取るようにしています。

src/bin/search_artist.rs

extern crate rspotify;

use std::env;
use rspotify::client::Spotify;
use rspotify::oauth2::{SpotifyClientCredentials, SpotifyOAuth};
use rspotify::senum::{Country, SearchType};
use rspotify::util::get_token;

#[tokio::main]
async fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() == 1 {
        println!("Usage: cargo run --bin search_artist <ARTIST_NAME>");
        return;
    }
    let artist_name = &args[1];

    let mut oauth = SpotifyOAuth::default().scope("user-read-private").build();
    match get_token(&mut oauth).await {
        Some(token_info) => {
            let client_credential = SpotifyClientCredentials::default()
                .token_info(token_info)
                .build();
            let spotify = Spotify::default()
                .client_credentials_manager(client_credential)
                .build();

            // Reference: https://developer.spotify.com/documentation/web-api/reference/search/search/
            let result = spotify
                .search(
                    // q: 検索キーワード
                    artist_name,
                    // type: 検索タイプ(album, artist, playlist, track, etc.)
                    SearchType::Artist,
                    // limit: 取得するデータの最大数
                    1,
                    // offset: 取得するデータの開始位置
                    0,
                    // market: 指定した国名コードで再生可能なコンテンツのみを返す
                    Some(Country::Japan),
                    // include_external: audio を指定すると関連するオーディオコンテンツがレスポンスに含まれる
                    None,
                )
                .await
                .unwrap();

            match result {
                rspotify::model::search::SearchResult::Artists(artists) => {
                    let artist = &artists.items[0];
                    println!("artist: {}\nid: {}\npopularity: {}", artist.name, artist.id, artist.popularity);
                },
                err => println!("search error!{:?}", err),
            }
        }
        None => println!("auth failed"),
    };
}

下記で実行します。ここでは、DA PUMP を検索しようと思います。

$ cargo run --bin search_artist "DA PUMP"

すると、ブラウザが開いて下記のページが表示されるので、「同意する」ボタンをクリックします。

すると、ページが表示できなくなりますが、これでOKです。この状態でページの URL をコピーします。

ターミナルが下記のようになっているので、コピーした URL を貼り付けて Enter します。

Opened https://accounts.spotify.com/authorize?state=<STATE>&scope=user-read-private&response_type=code&client_id=<CLIENT_ID>&redirect_uri=http:%2F%2F127.0.0.1:8080%2F& in your browser
Enter the URL you were redirected to:

すると、検索した DA PUMP の ID と 人気度が表示されます(APIから様々な情報が取得されるので、ここでは絞って表示しています)。

artist: DA PUMP
id: 3NRXKeatDxKe4apH6XawKX
popularity: 53

この ID を使って、次は DA PUMP の楽曲人気 TOP10 を表示しましょう。

指定したアーティストの楽曲人気 TOP10 を表示する

実装は下記になります。先ほどと同様に、引数でアーティストの ID を受け取るようにしています。

src/bin/artist_top_tracks.rs

extern crate rspotify;

use std::env;
use rspotify::client::Spotify;
use rspotify::oauth2::SpotifyClientCredentials;
use rspotify::senum::Country;

#[tokio::main]
async fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() == 1 {
        println!("Usage: cargo run --bin artist_top_tracks <ARTIST_ID>");
        return;
    }
    let artist_id = &args[1];

    let client_credential = SpotifyClientCredentials::default().build();
    let spotify = Spotify::default()
        .client_credentials_manager(client_credential)
        .build();

    // Reference: https://developer.spotify.com/documentation/web-api/reference/artists/get-artists-top-tracks/
    let result = spotify
        .artist_top_tracks(
            // id: artist の Spotify ID
            artist_id,
            // country: 国名コード
            Country::Japan
        )
        .await
        .unwrap();
    
    println!("# {} Top Tracks", result.tracks[0].artists[0].name);
    for track in result.tracks.iter() {
        println!("{}: {} ({})", track.name, track.popularity, track.id.as_deref().unwrap_or("none"));
    }
}

下記で実行します。

$ cargo run --bin artist_top_tracks "3NRXKeatDxKe4apH6XawKX"

すると、下記が表示されます。少し見づらいですが、曲名と人気度、楽曲の ID を表示しています。やっぱり、if... と U.S.A. が人気なんですね。個人的には、Dragon Screamer 推しです。

# DA PUMP Top Tracks
if...: 53 (78LQTUp1f8APWgFNZNOD5y)
U.S.A.: 53 (4qZyuyqLoZ3CaH79rGvJFR)
P.A.R.T.Y. 〜ユニバース・フェスティバル〜: 51 (7Avl3ZmZf5B80YSzfmdYuT)
Dragon Screamer: 43 (623pmkD6sclgLBQrrPqyz4)
Heart on Fire: 42 (5PLlnPQROKe1P9Vjrlxjvq)
Fantasista〜ファンタジスタ〜: 42 (2MMVhnZEo1ldxODbqqm96K)
桜: 42 (5B36byNsxLer31rJz1jlKT)
Feelin' Good 〜It's PARADISE〜: 40 (0g1NHilvp8AHX4HN8Ua26i)
if...: 38 (156j6p6VwT2OYzEmeDKjUG)
ごきげんだぜっ!〜Nothing But Something〜: 37 (0co9GJXSCrqVfIYjaihBjj)

次は楽曲の ID を使用して、Spotify アプリ上で楽曲を再生してみましょう。

指定した楽曲を再生する

実装は下記になります。これまでと同様に、引数で楽曲の ID を受け取るようにしています。

src/bin/play_track.rs

extern crate rspotify;

use std::env;
use rspotify::client::Spotify;
use rspotify::model::offset::for_position;
use rspotify::oauth2::{SpotifyClientCredentials, SpotifyOAuth};
use rspotify::util::get_token;

#[tokio::main]
async fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() == 1 {
        println!("Usage: cargo run --bin play_track <TRACK_ID>");
        return;
    }
    let track_id = &args[1];
    let track_uri = "spotify:track:".to_string() + track_id;

    let mut oauth = SpotifyOAuth::default()
        .scope("user-modify-playback-state,user-read-playback-state")
        .build();

    match get_token(&mut oauth).await {
        Some(token_info) => {
            let client_credential = SpotifyClientCredentials::default()
                .token_info(token_info)
                .build();
            let spotify = Spotify::default()
                .client_credentials_manager(client_credential)
                .build();

            // Reference: https://developer.spotify.com/documentation/web-api/reference/player/get-a-users-available-devices/
            let result = spotify.device().await.unwrap();
            let mut device_id = "";
            for device in result.devices.iter() {
                if let rspotify::senum::DeviceType::Computer = device._type {
                    if device.is_active == true {
                        device_id = &device.id;
                    }
                }
            }

            // Reference: https://developer.spotify.com/documentation/web-api/reference/player/start-a-users-playback/
            match spotify
                .start_playback(
                    // device_id: 対象とするデバイスのID
                    Some(device_id.to_string()),
                    // context_uri: 再生する album, artist, playlist の指定
                    None,
                    // uris: 再生する track のリストを指定
                    Some(vec![track_uri]),
                    // offset: album, playlist, track の再生を開始する位置
                    for_position(0),
                    // position_ms: track の再生を開始する位置
                    None
                )
                .await
            {
                Ok(_) => println!("start playback successful"),
                Err(_) => eprintln!("start playback failed"),
            }
        }
        None => println!("auth failed"),
    }
}

楽曲を再生するには、Spotify のアプリをアクティブな状態にしておく必要があります。Spotify のアプリを開いて、テキトーな曲を再生 & 停止してください。

その後で、プログラムを実行します。

$ cargo run --bin play_track "623pmkD6sclgLBQrrPqyz4"

先ほどと同様に、ブラウザが開いてページが表示されるので、「同意する」ボタンをクリックします。

ページが遷移したら、URL をコピーして、ターミナルに貼り付けて Enter します。

Opened https://accounts.spotify.com/authorize?client_id=<CLIENT_ID>&response_type=code&redirect_uri=http:%2F%2F127.0.0.1:8080%2F&scope=user-modify-playback-state,user-read-playback-state&state=<STATE>& in your browser
Enter the URL you were redirected to:

成功すると、start playback successful の表示と共に、僕が推している Dragon Screamer が流れます。

start playback failed と表示された場合は、再度 Spotify のアプリがアクティブになっているか確認してから、プログラムを実行してみてください。

以上でお遊びは終わりになります。

おわりに

楽しかった。

Python で spotipy を使って Spotify Web API を叩いたことはあるのですが、今回は不慣れな Rust ということで苦戦しました。少しおかしな実装になっているかもしれませんが、ご了承ください(勉強します)。

今回はお試しで API を叩いてみましたが、Raspberry Pi とボタンとかの部品を組み合わせて、それっぽいものを作るのが次の目標です。とはいえ、現状仕事では全然使わない知識なので、完成がいつになるかは不明です。何かできたら記事にしようと思います。

今回は以上になります。最後まで読んで頂きありがとうございました!