はじめに
最近実装された Spotify の Blend 機能はご存知でしょうか?2人のユーザーの嗜好を加味してしてお互いに気に入りそうなプレイリストを自動で生成してくれるサービス、みたいな感じです。ひょんなところからそのプレイリストを公開しようとしたところ、Blend で生成されたプレイリストはその2人のユーザーしか閲覧できないような仕様になっているようでした。本記事はそこをなんとかして公開できるようにしよう、ということでいろいろやった結果のメモです。ざっくりやったこととしては
- 公開可能な空のプレイリストを別途作成
- Blend で生成されたプレイリストの内容を取得し、前述の公開可能プレイリストに全部コピーするプログラムを実装
- そのスクリプトを定期的に実行するように設定
という感じです。
できたもの
以下のプレイリストが Blend で生成されたプレイリストの内容に追従して定期的に更新されています。よろしければ記事閲覧のお供にお聞きください。
使用したツール
- Google Apps Script
- 以前に使用したことがあり定時で実行とかを割とカジュアルにできるので使いやすいかなと思い選定しました。
-
clasp
+ TypeScript- GAS のブラウザエディタでの開発はかなり厳しいのでローカルで、しかも TypeScript で開発ができる
clasp
を採用しました。TypeScript で書いたソースをclasp push
するだけでいい感じに .gs ナイズしたコードに変換してくれます。
- GAS のブラウザエディタでの開発はかなり厳しいのでローカルで、しかも TypeScript で開発ができる
-
spotify-types
- せっかくTypeScriptで書くので投げるオブジェクトやレスポンスにも型推定させたい、ということでSpotifyのAPIで使える型ライブラリを探し、
spotify-types
を採用しました。なんか他にもいろいろありそうだけど違いはよく見てないです。spotify types - npm search
- せっかくTypeScriptで書くので投げるオブジェクトやレスポンスにも型推定させたい、ということでSpotifyのAPIで使える型ライブラリを探し、
書いたプログラム
全体ソースは以下を参照ください。
SyncSpotifyPlaylist/index.ts at master · Dance-At-Kamogawa/SyncSpotifyPlaylist
ハマったポイント
fetch
が使えない
これはGAS側の制約なんですがREST APIを叩くときに JavaScript ネイティブの fetch
が使えないみたいです。(デプロイして走らせてからエラーが出て気がつきました)。API コールにはGAS上で使える UrlFetchApp.fetch(url, option)
を使用するといいっぽいです。fetch
と違って特にawaitとか書かずに同期処理的に書けるのでこれはこれで便利ですね。
/**
* HTTP リクエストを実施
*
* @param fetchApiUrl URL
* @returns レスポンスのJSON
*/
const requestHttp = (
fetchApiUrl: string,
options?: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions
) => {
const commonOptions: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + accessToken,
},
};
const option = Object.assign({}, { ...commonOptions }, { ...options });
const response = UrlFetchApp.fetch(fetchApiUrl, option);
return JSON.parse(response.getContentText());
};
API のコールで body に生のオブジェクトを渡すとコケる
前述の UrlFetchApp.fetch
の options
の payload
にbody相当のオブジェクトを入れるのですがその場合生のobjectをそのまま入れるとパースの仕方が変になってBad Requestが帰ってきてしまいました。payloadに入れるときに object を JSON.stringify()
してあげればOKでした。
/**
* プレイリストのトラックを更新
* @param playlist プレイリスト
*/
const updateTargetPlaylist = (playlist: Paging<PlaylistTrack>) => {
const updateApiUrl = API_BASE_URL + Env.TARGET_PLAYLIST_ID + "/tracks";
const tracks = {
uris: playlist.items.map((el) => {
return el.track.uri;
}),
};
const options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = {
method: "post",
payload: JSON.stringify(tracks),
};
requestHttp(updateApiUrl, options);
};
Google Apps Script の定期実行設定
デプロイして動くことを確認したら、あとはそれを定期的に実行するように設定します。元のプレイリストが更新されるタイミングに応じて適度に設定してあげます。以下の画像はとりあえず一日一回更新される感じにしてますが、元のプレイリストの更新タイミングと微妙に噛み合ってないのでもう少し更新してもいい気がしてます。
ソースコード
ソースは以下に公開しています。そんなに複雑なことはしていませんが参考になれば。必要なAPIキーやプレイリストの値等各種環境変数はsrc/env.ts
の中の値を埋めてもらえれば使えるはずです。
Dance-At-Kamogawa/SyncSpotifyPlaylist: A Google Apps Script to sync one Spotify playlist to another.
参考にしたサイト
-
Spotify APIを使ってアプリケーションを作ろう - Qiita
- このサイトの手法を参考に RefreshToken を手作業で取得してそれを
env.ts
の中に持たせてます
- このサイトの手法を参考に RefreshToken を手作業で取得してそれを