LoginSignup
0
1

More than 1 year has passed since last update.

公開不可な Spotify プレイリストの内容をなんとかして公開した話

Last updated at Posted at 2021-11-08

はじめに

最近実装された Spotify の Blend 機能はご存知でしょうか?2人のユーザーの嗜好を加味してしてお互いに気に入りそうなプレイリストを自動で生成してくれるサービス、みたいな感じです。ひょんなところからそのプレイリストを公開しようとしたところ、Blend で生成されたプレイリストはその2人のユーザーしか閲覧できないような仕様になっているようでした。本記事はそこをなんとかして公開できるようにしよう、ということでいろいろやった結果のメモです。ざっくりやったこととしては

  1. 公開可能な空のプレイリストを別途作成
  2. Blend で生成されたプレイリストの内容を取得し、前述の公開可能プレイリストに全部コピーするプログラムを実装
  3. そのスクリプトを定期的に実行するように設定

という感じです。

できたもの

以下のプレイリストが Blend で生成されたプレイリストの内容に追従して定期的に更新されています。よろしければ記事閲覧のお供にお聞きください。


使用したツール

  • Google Apps Script
    • 以前に使用したことがあり定時で実行とかを割とカジュアルにできるので使いやすいかなと思い選定しました。
  • clasp + TypeScript
    • GAS のブラウザエディタでの開発はかなり厳しいのでローカルで、しかも TypeScript で開発ができる clasp を採用しました。TypeScript で書いたソースを clasp push するだけでいい感じに .gs ナイズしたコードに変換してくれます。
  • spotify-types
    • せっかくTypeScriptで書くので投げるオブジェクトやレスポンスにも型推定させたい、ということでSpotifyのAPIで使える型ライブラリを探し、spotify-typesを採用しました。なんか他にもいろいろありそうだけど違いはよく見てないです。spotify types - npm search

書いたプログラム

全体ソースは以下を参照ください。
SyncSpotifyPlaylist/index.ts at master · Dance-At-Kamogawa/SyncSpotifyPlaylist

ハマったポイント

fetch が使えない

これはGAS側の制約なんですがREST APIを叩くときに JavaScript ネイティブの fetch が使えないみたいです。(デプロイして走らせてからエラーが出て気がつきました)。API コールにはGAS上で使える UrlFetchApp.fetch(url, option) を使用するといいっぽいです。fetchと違って特にawaitとか書かずに同期処理的に書けるのでこれはこれで便利ですね。

httpリクエスト部分.ts
/**
 * 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.fetchoptionspayload にbody相当のオブジェクトを入れるのですがその場合生のobjectをそのまま入れるとパースの仕方が変になってBad Requestが帰ってきてしまいました。payloadに入れるときに object を JSON.stringify() してあげればOKでした。

HTTPリクエスト処理にオブジェクトを渡す部分.ts
/**
 * プレイリストのトラックを更新
 * @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 の定期実行設定

デプロイして動くことを確認したら、あとはそれを定期的に実行するように設定します。元のプレイリストが更新されるタイミングに応じて適度に設定してあげます。以下の画像はとりあえず一日一回更新される感じにしてますが、元のプレイリストの更新タイミングと微妙に噛み合ってないのでもう少し更新してもいい気がしてます。

script.google.com_u_6_home_projects_1i2VmZE7-8UxmveNNGYQ03UGzRjn9qTLYr6QJr7zWItonABag_dYLjqlm_triggers.png

ソースコード

ソースは以下に公開しています。そんなに複雑なことはしていませんが参考になれば。必要なAPIキーやプレイリストの値等各種環境変数はsrc/env.tsの中の値を埋めてもらえれば使えるはずです。
Dance-At-Kamogawa/SyncSpotifyPlaylist: A Google Apps Script to sync one Spotify playlist to another.


参考にしたサイト

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1