PRがマージされたらマツケンサンバが流れてライトが光るシステムを作ってみた【Switchbot・Spotify】


# はじめに

今回は社内のLT大会用に開発をしたものを、備忘録としてQiita用にまとめた記事です。
Python・Node.jsの開発経験がなく、ChatGPTさんに協力いただきながらやっているため、より良い方法や楽しい方法があればぜひコメントやリプライでアドバイスをいただけますと幸いです。


# 何を作ったか

みなさん新卒の時にこんなことありませんでしたか？

---
先輩「もっちー、これを作って〜！」
もっちー「はい！わかりました！がんばります！！！」
〜1日後〜
もっちー「終わった！初めてコードレビューだ」ポチッ
先輩(コメントﾂﾗﾂﾗ…)
もっちー「一発じゃ通るわけがないか…悔しい……」
〜〜
もっちー「コメント修正終わった…今度こそ…」

![スクリーンショット 2025-05-18 16.25.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308698/ce5bfe8a-d511-4222-998a-1df1574f9b70.png)


この瞬間

<font color="red"><b>もっちー(うれし〜〜〜〜〜開発超たのし〜〜〜〜〜)</b></font>

---

こんなことありませんでしたか？私はよくありました。



脳内は踊り出しちゃうぐらい嬉しい気持ちでいっぱいでしたが、新卒のころは毎日出社をしているので、踊ることはできませんでした。

そんな当時の嬉しさを体現するものを今回作成しました。

最初はライトを光らせて踊れる楽しい空間を作ろうと思ったのですが、踊るなら音楽も必要でしょう！！と思い自宅にいて、いつも元気をいただいている松平健さんにもご協力をいただきました。

![スクリーンショット 2025-05-18 18.28.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308698/6ee4cc06-663e-4e31-812f-b8d1d78e20ef.png)



#### こちらの記事の対象者

* SwitchBotAPIを利用したい人
* SpotifyAPIを利用したい人
* マツケンサンバで踊りたい人

# 何を作るか

PRがマージされたらマツケンサンバを流してライトを光らせる
ぜひ音あり動画で見ていただきたいのでこちらをご覧ください

https://x.com/Motchy_1204/status/1924039101214531715


# 構成図

PRがマージされたらngrokを通して各機器を動かします

![スクリーンショット 2025-05-18 17.03.44 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308698/dc2f59a6-d152-4b93-a1e9-381ed3a73eb0.png)


# 最終的な階層

![スクリーンショット 2025-05-18 17.00.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308698/b1561e47-a916-4be5-bec2-49ec06ed58c6.png)


## 事前準備

事前に諸々登録やターミナルで動作テストを行っていきます。
筆者はMacBook Pro2017で開発しております

### Spotify


・以下でSpotify developerアカウントの作成
　今回は自由に音楽をかけたい(マツケンサンバをかけたい)ため、Premiumアカウントに登録しました

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

・以下サイトを参考にし、ターミナルでトークンの取得・Spotify再生など、疎通テスト

https://takagi.blog/get-and-refresh-access-tokens-for-the-spotify-web-api/

https://developer.spotify.com/documentation/web-api/reference/start-a-users-playback

### Switchbot

・SwitchBotハブミニ・プラグを準備、初期設定を行なっておく(今回は自宅にあるものを使用)

https://www.switchbot.jp/products/switchbot-hub-mini?_pos=1&_sid=f721c4186&_ss=r

https://www.switchbot.jp/products/switchbot-plugmini-homekit?_pos=5&_sid=0a6308e0d&_ss=r

・以下の記事などを参考にし、トークン・デバイスIDを確認

https://qiita.com/katta1024/items/6a5af91c986fe3c47f4d

・SwitchBotAPIの公式ドキュメントを確認し、自分の機器に対応するAPIをターミナルで呼び出し、動作確認

https://github.com/OpenWonderLabs/SwitchBotAPI

・Amazonで好みのディスコライト🕺を購入し、プラグとライトを繋げ、ターミナルからプラグを操作したら点灯することを確認


### ngrok

・ngrokをインストールし起動確認

https://ngrok.com/

### Github

・アカウントの作成・マージされたら踊り出したくなるようなプロジェクトを作成

・Webhookの準備・ngrokの情報を設定

https://docs.github.com/ja/webhooks/about-webhooks


## それぞれの動作を繋げていく

個々で動くことの準備や動作確認はできましたので、これを繋げていきました。
ChatGPTにおすすめの階層や書き方を教えてもらいながら作業を行なっていきました。


今回はwebhookHandler箇所と各機器を動かす箇所のコード一部を記載します



```javascript:webhookHandler.js
// このモジュールを外部から使用できるようにエクスポート
module.exports = (req, res) => {
    // 受け取ったWebhookの内容をログに出力（デバッグ用）
    console.log('Webhook received:', req.body);

    // 以下の条件をすべて満たすときに処理を実行する：
    // - プルリクエストが「closed（クローズ）」された
    // - そのプルリクエストがマージされた（merged === true）
    // - プルリクエストの作成者（user.id）がもっちーである
    if (req.body.action === 'closed' &&
        req.body.pull_request.merged === true &&
        req.body.pull_request.user.id === [自分のuser.idを入力]) {

        // SwitchBotを操作するPythonスクリプトを実行
        exec('python3 scripts/play_switchbot.py', (error, stdout, stderr) => {
            if (error) {
                // 実行時エラーがあれば表示して処理を中断
                console.error(`Error: ${error.message}`);
                return;
            }
            if (stderr) {
                // 標準エラー出力があれば表示して処理を中断
                console.error(`Stderr: ${stderr}`);
                return;
            }
            // 正常終了した場合の出力を表示
            console.log(`Output: ${stdout}`);
        });

        //  松平健の『マツケンサンバ』を再生するPythonスクリプトを実行
        exec('python3 scripts/play_matsuken.py', (error, stdout, stderr) => {
            if (error) {
                console.error(`Error: ${error.message}`);
                return;
            }
            if (stderr) {
                console.error(`Stderr: ${stderr}`);
                return;
            }
            console.log(`Output: ${stdout}`);
        });
    }

    // Webhook受信に対して200 OKで応答
    res.status(200).send('Webhook received');
};
```


play_matsuken.pyの中身を一部抜粋

```python:play_matsuken.py
# SpotifyのクライアントID、クライアントシークレット、リダイレクトURLを設定
# SpotifyのAPI認証の設定を記載

def play_matsuken_samba():
        # 再生デバイスのIDを取得
        devices = sp.devices()
        device_id = None
        # 取得デバイス確認
        print(devices)
        for device in devices['devices']:
            if device['name'] == '流したい起動済み機器名を指定':  
                device_id = device['id']
                break
        if device_id:
            sp.volume(50, device_id=device_id)
            # 指定デバイスで曲を再生
            sp.start_playback(device_id=device_id, uris=[流したい音楽のuri])
        else:
            print('再生機器が見つかりませんでした')
```

play_switchbot.pyの中身を一部抜粋

```python:play_switchbot.py
# SwitchBot APIトークンとデバイスIDを設定
# DEVICE_IDを設定

# SwitchBot APIを使うためのリクエストヘッダー
headers = {
    'Authorization': API_TOKEN,
    'Content-Type': 'application/json' 
}


# 操作対象デバイスへのURLを作成（部屋の電気を消すためのハブミニ用）
hub_mini_url = f'https://api.switch-bot.com/v1.0/devices/{DEVICE_ID}/commands'
hub_mini_command = {
    'command': 'turnOn', 
    'parameter': 'default',
    'commandType': 'command'
}

# 操作対象デバイスへのURLを作成（プラグ(ディスコライト)用）
plug_url = f'https://api.switch-bot.com/v1.0/devices/{DEVICE_ID}/commands'
plug_command = {
    'command': 'turnOff', 
    'commandType': 'command'
}

# --- ハブミニを操作 ---
hub_response = requests.post(hub_mini_url, headers=headers, json=hub_mini_command)

# 結果を確認
if hub_response.status_code == 200:
    print('ハブミニの操作が成功しました！')
else:
    print(f'ハブミニの操作に失敗しました: {hub_response.status_code}, {hub_response.text}')

# --- プラグを操作 ---
plug_response = requests.post(plug_url, headers=headers, json=plug_command)

# 結果を確認
if plug_response.status_code == 200:
    print('プラグの操作が成功しました！')
else:
    print(f'プラグの操作に失敗しました: {plug_response.status_code}, {plug_response.text}')
```

# おわり

事前準備の時点で最初にやろうと思っていたIFTTTが動かなかったり、GoogleHomeで本当は流したかったけど制約があったりと準備時点から右往左往しましたが、なんとかやりたい形まで持っていくことができました。

最近はコードを書く業務から離れていたため、久々にものづくりをして非常に楽しかったです。
素敵なイベントの開催・きっかけをくれた社内メンバーには感謝いっぱいです。
閲覧ありがとうございました。


システム化のご相談がございましたら、ぜひお声がけくださいませ。

https://www.i-enter.co.jp/


# イラスト元・グッズご協力
* [いらすとや](https://www.irasutoya.com/)
* [松平健さん](https://www.ken-matsudaira.com/)