GoでSlackのボットを作った話

Posted by johtani on Friday, December 8, 2023

目次

PySpaアドベントカレンダーの12/08のエントリーです。昨日は渋川さんのアンラーニングで失敗した話でした。

私は、今年は昨年の続きっぽい話を。

音楽の再生とか停止とか

今年もリモートで仕事を継続しています。 昨年、OwnToneという音楽サーバーを導入しましたが、ミーティング開始時や終了後に音楽の停止や再生をするのに、ブラウザで操作するのはちょっと面倒です。 加えて、ミーティングする際には実際には

  • 音楽を止める
  • 電気をつける

といったことをっやります。 音楽再生についても

  • 音楽を再生する
  • アンプの音は音楽再生用の音量にする

といった複数のことを行います。

基本的にPCの前にいるし、Slackを常に開いているからSlackのBotを作ると便利になるのでは? ということで、自分専用のSlack Botを作ってみました。

GitHubリポジトリ

全く誰の役にも立たないけど、公開しています。

Goで実装

知り合いに勧められたということもあり、Goをあまり触ったことがなかったので、Goでやってみました。時々新しい言語を触ってみたくなるので。。。

コマンドパターンのような作りで、コマンド(操作)を増やしていく感じで実装してはみたのですが、この書き方がいいのかはまだよくわかっていないです。

Actionという単体の操作(例:OwnToneの一時停止)を、操作したい機器の操作ごとに作成しています。 インターフェースとしてはRunだけを定義しています。

type Action interface {
	Run() (string, error)
}

それらの操作(Action)をひとまとめにしたものをSubcommandという形で定義して、SlackのBotにはSubcommandの名前を送って処理を実行する形にしています。

type Subcommand struct {
	Definition
	actions     []action.Action
	ignoreError bool
}

やりたいことを追加するタイミングで時々リファクタしたりもしていますが、基本はこんな感じです。 細かな実装はさておき、今回ボットを作るときの観点をいくつか。

宅内ネットワーク

音楽サーバーのOwnToneもですが、YAMAHAのアンプのAPIも見つけたのでこれらの操作をボットから行っています。どちらも自宅のネットワークにあるので、今回作成したボットも同じネットワークで動作します。

外部に公開されたHTTPのエンドポイントがあればよいのですが、今回は用意していません。 SlackのAPIではこれに対応したアプリを作るときのためにSocket Modeを用意してくれています。

ドキュメントがしっかりしているのもあり、チュートリアルなどを見つつ特に迷うことなく構築できました。

基本的には「特定のチャンネルでボットがメンションされたら動作する」という挙動だけしか実装していないので、特に今回はこれで困ることもありませんでした(世の中の人たちはもっとすごいSlackの使い方をしていそうだけど。。。)。

対象となるものたち

ボットから操作しているのは今のところ3種類です。

  • SwitchBot
  • OwnTone
  • YAMAHAのアンプ

SwitchBot

SwitchBotについては、go-siwtchbotを利用しています。 電気、エアコン、空気清浄機、サーキュレーターのオンオフをSwitchBot経由でやっています。

室内灯の明るさのために、SwitchBot側でシーンを設定している(一番暗い明るさで電気をつける)ので、そこが少しと特殊でしょうか?

あとは、SwitchBotの温湿度計を3つ(室内2つ、室外1つ)おいてあるので、これらの温度を表示するコマンドも用意しました。

それぞれの機器の電池残量(電池の絵文字の右側)を表示してます(書斎が2つあるように見えますが、温湿度計の名前なので、同じ部屋においてあります。。。温度が高いほうがデスクトップPCの近くにある)。 防水の温湿度計があるので、外の気温もわかるのは便利です。

OwnTone

JSONのAPIが用意されていますが、特にクライアントライブラリなどは用意されていません。 今回は、用意されているAPIのごく一部だけを利用するので、net/httpのClientでJSONを投げています。 必要になったらowntone/clientにメソッドを追加していく感じです。

再生、一時停止、プレイリストの取得、音量指定あたりを実装しています。 音の出力先はAirPlay(次に出てくるYamahaのアンプ)になっています。

あと、JSON APIのサンプルをChatGPTに渡して、「このJSONをレスポンスに受け取る処理をGoで書いてください」と聞いて処理を書いたりしました(細かいのはどう聞いたか覚えてないけど)。

YAMAHAのアンプ

ググって探してきましたが、利用しているYAMAHAのアンプにYXC(Yamaha Extented Control)と呼ばれるAPIが用意されていました。 PDFでAPIの仕様が公開されていたので、この資料とにらめっこしながら、必要そうなAPIを実行するクライアントを実装しました。 こちらは、OwnToneのAPIの処理をすでに書いた後だったので、それをもとに必要な実装を追加しただけです。

ちなみに、アンプの電源をONにするAPIは実装していません。

  • OwnToneで音楽を再生する(AirPlayで信号が送られるみたい)
  • シーン選択(Nintendo SwitchやPS5のシーンを用意している)のAPIを呼び出す

という処理をすると自動で電源がONになりました。スタンバイしている状態でもAPIはリクエストを受け付ける状態になっているようです。 また、PS5のシーンを呼び出したら、PS5自体も起動するのが便利だったりします。HDMI経由で信号が飛んでるのかな? 「ミーティング開始」や「音楽停止」コマンドではアンプの電源をオフするAPIを呼び出すようにしています。

タイポ対応

「入力間違いしちゃうんです、人間だもの。。。」

ということで、10月にタイポ対応の処理を入れてみました。 タイポ対応前の実装では、入力された文字列がコマンドの文字列と完全一致した場合にだけ、コマンドが実行される形になっていました。 結構タイポしちゃうので、完全一致で見つからない場合の処理として、入力された文字列とコマンド名のレーベンシュタイン距離が2以下のものを探し、その中で距離が一番小さな値のコマンドを実行する形にしてみました。 コマンド数はたかが知れているのでコマンドのリストを全部チェックする形にしています。 タイポするとこんな感じ。

レーベンシュタイン距離の計算にはGo-edlibを利用しています。OSS便利ですね。

やってよかったこと

ミーティング開始時などのいくつかの手順が簡素化できたので満足しています。 SwitchBotの操作もスマホをわざわざ操作しなくてもいいですし(仕事してるとキーボードとSlackは常に使ってるし)。 また、副次的な効果として、部屋の外から電源を切り忘れていたり、エアコンの切り忘れなどの場合に、スマホから遠隔でまとめた処理ができるのが便利です。

今後やってみたいこと

こういうのがあるともうちょっと便利かもなぁ?というのもあるので今後も気が向いたらコマンドなどを追加していく予定です。 今のところこんなことやりたいなというのがいくつかあります。

  • キーワードに関連する曲の再生
    • OwnTone自体に検索の仕組みがついているので、キーワードを受け付けてそれに関する曲を流す
  • Flexispotの操作
    • ミーティングの時は立ち上がっているので、机をスタンディングの状態にしたい
    • 電子工作しないとだめかも?
  • Webカメラ用ライトの点灯
    • ミーティング時に明かりが必要なのですが、物理スイッチのOn/Off
    • これも電子工作かな?
  • 定期的に温度をBotに表示させるとと面白いかも?

まとめ

ということで、新しいプログラミング言語にも触れたし、作業環境も便利になって一石二鳥でした。 新しいプログラミング言語触るのはやっぱたのしいですね。


comments powered by Disqus

See Also by Hugo


Related by prelims-cli