想定読者
- GASやSlack APIをちょっと触ったことがある人
- できれば無料で済ませたい人(Vercelや有料DBは使いたくない)
- 今回は 自分のみ のステータス更新をしています
- 放置で自動化したい人
やりたいこと
Spotifyで今再生している曲名・アーティスト名をSlackのステータスメッセージに自動表示したい。
Slackを開いているだけで「この人、今これ聴いてるんだ」がわかる感じにしたかった。
- 更新頻度は5分でおk(そんな叩いてもな...感)
- GASを使って自動更新
- 貧乏なので無料でなんとかしたかった
技術構成
項目 | 使用技術 |
---|---|
曲情報取得 | Spotify Web API |
ステータス更新 | Slack Web API |
スクリプト実行 | Google Apps Script(GAS) |
トークン取得 | Flask + spotipy + ngrok(※refresh_token を1回だけ取得するため) |
やること
この記事で紹介する .env
や スプレッドシート
に保存する情報は個人のアクセストークンを含みます。
GitHubなどの公開リポジトリには絶対にアップロードしないよう注意してください。
ステップ 1:Spotify Developer の準備
- Spotify Developer Dashboard にログイン
- 新しいアプリを作成する
-
Client ID
とClient Secret
を控える
ステップ 2:Slack API アプリの準備
- Slack API Console にアクセスし、新しいアプリを作成
- 「OAuth & Permissions」→ users.profile:write を User Token Scopes に追加
- 「Install to Workspace」でワークスペースに追加
- 発行された xoxp-... のトークンを控える
Botトークン(xoxb-...)では変更できない。Userトークンが必須
ステップ 3:Spotifyの refresh_token を取得(初回1回だけ)
Spotifyのアクセストークンは1時間で失効するため、定期更新に使う refresh_token
を事前に取得しておく必要がある。これが自動化のキモ
-
ngrok http 8888
を実行-
Forwarding https://xxxx-xxx-xxx-xxx-xxx.ngrok-free.app
の形で出てくるので、これをメモる - Spotifyで作成したアプリに
https://xxxx-xxx-xxx-xxx-xxx.ngrok-free.app/callback
のredirect uriを設定する
-
- spotipy + Flask のコードをローカルに作成(コードは以下)
- .env を設定(client_id など)
-
SPOTIPY_REDIRECT_URI
はhttps://xxxx-xxx-xxx-xxx-xxx.ngrok-free.app/callback
を設定する
-
- コードを実行
- ブラウザでngrokの
https://xxxx-xxx-xxx-xxx-xxx.ngrok-free.app
へアクセス - 認可後、ターミナルに表示された
refresh_token
を控える
Spotify側の仕様として、HTTPSリダイレクトが必須なため、今回は ngrok
を使って一時的にHTTPSで公開している
import os
from flask import Flask, redirect, request
from dotenv import load_dotenv
from spotipy.oauth2 import SpotifyOAuth
load_dotenv()
app = Flask(__name__)
sp_oauth = SpotifyOAuth(
client_id=os.getenv("SPOTIPY_CLIENT_ID"),
client_secret=os.getenv("SPOTIPY_CLIENT_SECRET"),
redirect_uri=os.getenv("SPOTIPY_REDIRECT_URI"),
scope="user-read-currently-playing user-read-playback-state",
cache_path=None
)
@app.route("/")
def login():
return redirect(sp_oauth.get_authorize_url())
@app.route("/callback")
def callback():
code = request.args.get("code")
token_info = sp_oauth.get_access_token(code)
return f"Refresh Token: {token_info.get('refresh_token')}"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8888)
ステップ 4:スプレッドシートの作成
Secretな情報を直接スプシに書き込むので、他人への共有設定はしないように
- Google スプレッドシートを新規作成
- 以下のキーを左列(A列)、値を右列(B列)に入力
spotify_client_id
spotify_client_secret
spotify_refresh_token
spotify_access_token(後で自動で入ってくるので空でOK)
slack_token
ステップ 5:GASスクリプトの設定
- スプレッドシート上で「拡張機能」→「Apps Script」を開く
- 記事の
updateSlackStatusWithSpotify()
以下のスクリプトを貼り付け
function updateSlackStatusWithSpotify() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const rows = sheet.getDataRange().getValues();
const config = Object.fromEntries(rows);
// 必要な情報を取得
const clientId = config["spotify_client_id"];
const clientSecret = config["spotify_client_secret"];
const refreshToken = config["spotify_refresh_token"];
let accessToken = config["spotify_access_token"];
const slackToken = config["slack_token"];
// アクセストークンをリフレッシュ(毎回)
accessToken = refreshSpotifyToken(clientId, clientSecret, refreshToken);
updateCell(sheet, "spotify_access_token", accessToken); // 更新
const nowPlaying = getNowPlayingTrack(accessToken);
if (nowPlaying) {
setSlackStatus(slackToken, nowPlaying);
} else {
const currentStatus = getCurrentSlackStatus(slackToken);
if (currentStatus?.status_emoji === ":musical_note:") {
clearSlackStatus(slackToken);
}
}
}
function refreshSpotifyToken(clientId, clientSecret, refreshToken) {
const token = Utilities.base64Encode(`${clientId}:${clientSecret}`);
const payload = {
grant_type: "refresh_token",
refresh_token: refreshToken
};
const res = UrlFetchApp.fetch("https://accounts.spotify.com/api/token", {
method: "post",
headers: {
Authorization: `Basic ${token}`,
"Content-Type": "application/x-www-form-urlencoded"
},
payload: Object.entries(payload).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&"),
muteHttpExceptions: true
});
const json = JSON.parse(res.getContentText());
return json.access_token;
}
function getNowPlayingTrack(token) {
const res = UrlFetchApp.fetch("https://api.spotify.com/v1/me/player/currently-playing", {
method: "get",
headers: {
Authorization: `Bearer ${token}`
},
muteHttpExceptions: true
});
if (res.getResponseCode() !== 200) return null;
const data = JSON.parse(res.getContentText());
if (!data?.item) return null;
const track = data.item;
const name = track.name;
const artists = track.artists.map(a => a.name).join(", ");
return `${name} - ${artists}`;
}
function setSlackStatus(token, text) {
const payload = {
profile: {
status_text: text,
status_emoji: ":musical_note:",
status_expiration: 0
}
};
UrlFetchApp.fetch("https://slack.com/api/users.profile.set", {
method: "post",
contentType: "application/json",
headers: {
Authorization: `Bearer ${token}`
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
}
function updateCell(sheet, key, value) {
const range = sheet.getDataRange();
const values = range.getValues();
for (let i = 0; i < values.length; i++) {
if (values[i][0] === key) {
sheet.getRange(i + 1, 2).setValue(value);
return;
}
}
}
function getCurrentSlackStatus(token) {
const res = UrlFetchApp.fetch("https://slack.com/api/users.profile.get", {
method: "get",
headers: {
Authorization: `Bearer ${token}`
},
muteHttpExceptions: true
});
if (res.getResponseCode() !== 200) return null;
const data = JSON.parse(res.getContentText());
return {
status_text: data.profile?.status_text || "",
status_emoji: data.profile?.status_emoji || ""
};
}
function clearSlackStatus(token) {
const payload = {
profile: {
status_text: "",
status_emoji: "",
status_expiration: 0
}
};
UrlFetchApp.fetch("https://slack.com/api/users.profile.set", {
method: "post",
contentType: "application/json",
headers: {
Authorization: `Bearer ${token}`
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
}
ステップ 6:トリガー設定(自動更新)
- GASエディタの「トリガー」アイコンをクリック
- 関数:updateSlackStatusWithSpotify を選択
- イベントの種類:「時間主導型」→「5分おき」を設定
いい感じ!
これを他人にもスケールさせるなら?
今回の構成はあくまで「自分用の自動化」
アクセスキーとかもベタ書きですし、例えば非エンジニア含めてステータス表示を行いたい!と思ったときにスケールが効かない形になっている
なので、気軽に広めるのであれば、Web UI+バックエンド化して、OAuthトークンを一元管理とかするといいのかもなあと思っていたりしておる
最後に
らぷりえーるはいいぞ