Site cover image

Site icon imageおかしんワークス

ビジネステクノロジーエンジニア @okash1n のブログです

Microsoft Power AutomateのみでSlackのSlashコマンドを作る

こんにちはビジネステクノロジーユニットのおかしんです。

私の所属するビジネステクノロジーユニットでは、主に社内情報システム、ITデバイスの管理、セキュリティなどを担っており、私は最近は様々なシステム間連携の構築や自動化に取り組んでいます。

Microsoft Power Automateについて

Microsoft Power Automateをご存知でしょうか?以前はMicrosoft Flowという名前でしたが、いわゆるIPaaS(Integration Platform as a Service)というジャンルで、様々なWebサービスをつなぎ合わせて、ノーコードもしくはローコードで自動化を実現するサービスです。

toC向けだとIFTTTなどが有名でしょうか。

toB向けのプロダクトでは

などが有名です。

Power Automate(以下PA)は上記の中だとZapierなどと比べて最初から用意されているコネクタ(連携済みのWebサービス)はそこまで多いわけではないのですが、HTTPのリクエストを使ってAPIを叩いたり、JSONをパースするのは比較的得意だったりします。

今回はそんなPower Automateを使ってSlackのスラッシュコマンドを作成する手法を紹介したいと思います。

なお、今回のレシピにはPAの有料版が必要になります。

Slackのスラッシュコマンドとは?

スラッシュコマンドとは、Slack内のチャットボックスで / から始まるコマンドを実行することで特定の動作を行うSlackの機能のことです。

Slackにはいくつかの標準のスラッシュコマンドが用意されており、例えば /leave などを自分の参加しているチャンネル上で行うと、そのチャンネルから退出することが出来ます。

これだけだと、マウスでポチポチっとやったほうが楽なのですが、AWS LambdaやGoogle Apps Scriptなどを使い、SlackのAPIや他サービスのAPIを組み合わせることで、より複雑な動作を行うことができます。

例えば弊社開発部では kyafu というスラッシュコマンドで k8s 上に検証環境を立ち上げたり、ステージング環境にデプロイしたりといった操作をSlackだけで行えるようになっていたりします。

上記のコマンドは弊社のエンジニアがコードを書いてGitHub Actionsなどを利用することで実現しているのですが、私を含め情報システム部門に所属する人々はそんなに簡単にコードの実行環境を用意できるわけでもなく、そもそもコードを書くことに馴染みが少ない人も多いのでは無いでしょうか?

というわけで、今回は冒頭に紹介したPAを使って、ノーコードでスラッシュコマンドを実装する方法を紹介したいと思います。

今回作るコマンド

今回は、他人が作ったチャンネルの名前をAdmin権限が無くても変更できるコマンド、通称リネームちゃん(Rename Chan)を作成します。

こちらのTweetは一部誤りがあるのですが、SlackはAdmin権限以上の権限が無いと、自身が作ったチャンネル以外はオープンチャンネル、プライベートチャンネル問わずリネームすることが出来ません。

組織変更などの際に、部署チャンネルのリネームなどは比較的発生しやすいと思いますが、Admin権限を持った人でないとリネームできないので、Admin権限を持った人に作業が集中してしまうという問題がありました。

弊社では苦肉の策で、リーダー以上の役職者の方全員にAdmin権限を付与することで作業を分散していたのですが、「チャンネルをリネームしたい」という目的を達成するにはAdmin権限は権限が大きすぎるという問題がありました。

スラッシュコマンドを用いて、Admin権限を持った管理アカウントがリネームを代行することで、Admin多すぎ問題に終止符を打つことができます。

レシピ概要

Slack Appの設定

このアプリはAdmin権限を持ったユーザーのUser Tokenを使って実行する必要があるので、Admin権限を持ったユーザーで作成してください。

利用するAPIは conversations.rename です。ドキュメントにはRequired scopesに以下4つのBot tokens scopes が必要となっています。

  • channels:manage
  • groups:write
  • im:write
  • mpim:write

しかし、実際にはこのAPIはBot tokenで叩くことはできないので、これらのScopeは不要です。

Image in a image block

Scope

Slashコマンドの設定は以下のような感じです。コマンドの文字列は好きに決めてください。

Image in a image block

PAのフローの概要

Image in a image block

レシピ詳細 Step by Step

SlackのスラッシュコマンドはHTTPのリクエストを設定したRequest URLに対して投げます。そのリクエストを受けるためにPAでは 「HTTP 要求の受信時」というトリガーから作成します。

Image in a image block
Image in a image block

このトリガーは、一度フロー全体を保存しないとURLが発行されません。トリガーを保存するには、アクションが最低一つ必要なので、「JSONの解析」というアクションを追加し、以下のようにコンテンツとスキーマを設定して、一旦フローを保存します。

Image in a image block

フローを保存すると、URLが発行されるので、スラッシュコマンドのRequest URLに設定します

Image in a image block
Image in a image block

JSONの解析

これで、フローはとりあえずリクエストを受けれる状態になるので、アプリをワークスペースにインストールして、コマンドを実行してみてください。

Image in a image block

フローの実行ログには「失敗」のログが出ます。これは、JSONの解析の中で設定しているスキーマ {} が間違っているからなのですが、では実際どういうリクエストが飛んできていたのか、実行ログから確認しましょう。

Image in a image block

リクエストの本文に合致するスキーマを設定するために、本文内をコピーし、フローを再度編集します

PAの「JSONの解析」アクションにはサンプルのJSONを入力するとスキーマを自動で生成してくれる素敵な機能があります。

Image in a image block

サンプルから生成をクリックして、先程コピーした本文を貼り付けて「完了」します

Image in a image block

すると、いい感じのスキーマを作ってくれます

Image in a image block

これで、JSONをうまく解析してくれるはずです。再度スラッシュコマンドを実行してみましょう。

Image in a image block

なんと、スキーマを正しく設定したはずが、またエラーが出てしまいます。

BadRequest. The property ‘content’ must be of type JSON in the ‘ParseJson’ action inputs, but was of type ‘application/x-www-form-urlencoded’.

「JSON型じゃないといけないのにapplication/x-www-form-urlencoded型が届いてるよ」というエラーが出ています。

application/x-www-form-urlencoded に / が入ってること、もしくはプロパティ名に $ が入っていることが原因じゃないかと疑っているのですが、とにかく、PAの「JSONの解析」はスラッシュコマンドのリクエストをそのままでは処理出来ないようなのです。

四苦八苦した結果、 $formdata のプロパティだけに絞れば処理できることがわかったので、「JSONの解析」に全文を渡すのではなく、少しプロパティを減らしてから渡すことにします。

オブジェクトのプロパティを減らしてくれる removeProperty 関数

Slackのスラッシュコマンドから送られてくるリクエストは必ず以下のようなJSONになっています。

今回のフローでこの中で使いたい要素は $formdataという配列の中の以下の3つの値だけです

  • channel_id(リネームを実行する対象)
  • user_id (誰がリネームを実施したのかを通知するのに利用)
  • text(変更後のチャンネル名をコマンドの引数から取得)

$content-type と $content はプロパティそのものが不要かつ、エラーの元になっているので、「本文」のオブジェクトからこの2つのプロパティを減らします。

PAにはオブジェクトのプロパティを減らしてくれる removeProperty という関数があります。

removeProperty(<object>, '<property>')

という書式で、 <object> から <property> を削除してくれます。

よって今回は「本文」というオブジェクトから$content-type と $content を削除するので、2回 removeProperty を行えば良いということになります。(もっと良いやり方があったら教えて下さい)

removeProperty(removeProperty(triggerBody(),'$content-type'),'$content')

Image in a image block

このようになります。

triggerBody() は「動的なコンテンツ」から「本文」を選択することで自動入力することも出来ます。

プロパティを2つ減らしたので、設定するスキーマは以下のようになります

Image in a image block

これで、一度保存して再度コマンドを実行してみましょう。

Image in a image block

今度は正しく処理されています。入力されたオブジェクトの一番最初のプロパティが $formdata に変わっていることが分かりますね。

$formdata は Array型なので、まずは $formdata から formdata というArray変数を作成します。

ちなみに変数を作成することはマストではありません。 $formdata のまま後続の処理をすることもできますが、フローの視認性が悪くなるので私は他の人が見ても直感的に理解しやすいように変数を作成するステップを挟むようにしています。

変数の初期化

「変数の初期化」は新しく変数を作成するアクションです。プログラミングで言うところの変数の宣言ですね。Typescriptだと let arr: string[] = ['text1', 'text2', 'text3'] といった具合です。

今回は配列変数として初期化します。

Image in a image block

初期化と同時に、 $formdata という配列を初期値として設定しておきます。

Image in a image block

ここで、一つ前のステップで行った「JSONの解析」の解析結果を次のアクションである「変数の初期化」で利用できていることが分かります。

JSONから特定の要素を取り出し、後続のアクションで利用することができる「JSONの解析」はPAでAPIを叩いたり、Webhookを受け取って処理を行う際に最も重要になってくるアクションの1つです。これが無いと生きていけません。

さて、ここで formdata という変数に格納した $formdata の中身をおさらいしましょう。

$formdata の中には Key, Value のプロパティを持つオブジェクトが13個入っていることが分かりますね。

この中でも channel_id , user_id , text の key に対応する value こそが後続のアクションで使いたい部品です。

formdata という配列変数から4番目の要素を取り出しただけでは以下のようなオブジェクトが取り出されるだけなので、そのオブジェクトから更に value だけ取り出す必要があります。

{"key": "channel_id, "value":"[実行したチャンネルのID]"}

  • 配列変数からX番目の要素を取り出す
  • 取り出した要素(オブジェクト)から value だけを取り出す

この2STEPが必要ということになります。

配列変数からX番目の要素を取り出す

配列からX番目の要素を取り出すには variables 関数を使います。

variables(変数)[アドレス] という書式です。 formdata 内の4番目の要素 channel_id のアドレスは 3 になる(0から始めて4番目)ので、

variables('formdata')[3] で {"key": "channel_id, "value":"[実行したチャンネルのID]"} が取り出されることになります

取り出した要素(オブジェクト)から value だけを取り出す

さて、オブジェクトから特定のプロパティを取り出すにはどうすればよかったでしょうか?

オブジェクトはJSONでもあるので「JSONの解析」が使えますね。

Image in a image block

スキーマは以下のようになります

channel_id という key の value を channel_id という変数に格納する

「JSONの解析」によって フロー上で使えるようになった value を使って新しい変数を作ります。新しい変数を作るのは「変数の初期化」ですね。今度は文字列変数になります。

Image in a image block

同様にして、 userid と text も作成してしまいましょう。それぞれアドレスは 5 と 8 です。(6番目と9番目)

Image in a image block

Slackの conversations.rename APIを使ってチャンネルをリネーム

PAに最初から用意されているSlackコネクタにはチャンネル名を変更するアクションは用意されていません。よって、APIを叩いてチャンネル名変更を行う必要があります。レシピ概要にも書いたように利用するAPIは conversations.rename です。

PAからAPIを叩くには「HTTP」アクションを利用します。

conversations.renameのドキュメントを見ると https://slack.com/conversations.rename に対して POST のリクエストを送れば良いことが分かります。

Image in a image block

https://api.slack.com/methods/conversations.rename/test

こちらのURLからテストを手動で行うことができるので、冒頭で作成しておいた User OAuth Tokenを使って、APIを実行してみます。

Image in a image block

テスターでテストを行うと、リクエストのパラメータを含んだURLやヘッダーが表示されるので、それをそのままPAの「HTTP」アクションにコピペします。

Image in a image block

ただし、 channel= の部分と name= の部分には作成した変数を使います。

ヘッダーの左側には Authorization 、右側には Bearer xoxp-************************ を入力します。

ここまで出来ていれば、スラッシュコマンドを実行することでチャンネル名を変更できることが確認できます。なお、プライベートチャンネルをリネームしたい場合は、プライベートチャンネルにアプリを作成したユーザーを一度招待する必要があります。(このユーザーをシステムアカウントとして作っておくと良いでしょう)

リネーム通知とチャンネルからの退出

誰がリネームを行ったのか、の通知とリネームを実行したシステムアカウントのチャンネルからの退出のアクションを作成します。

Image in a image block

通知は標準のSlackコネクタの「メッセージの投稿(V2)」を利用します。メンションを行うために concat という関数を使い、 concat('<@',variables('user_id'),'>') とすることでメンションできます。

最後にHTTPで conversations.leave というAPIを叩くと完了です。

スラッシュコマンドを実行すると以下のような結果になります。

Image in a image block

今後の改善点

エラーハンドリングが全くできていないので、たとえば text が空だったり、プライベートチャンネルにAPIを叩くユーザーが招待されていなかったりした際に条件分岐して、コマンドをエラーを返すような処理を追加していきたいと思います。

まとめ

いかがでしたでしょうか?

スラッシュコマンドをPAで実行するには、おそらく一番最初の部分でリクエストのJSONから余計なプロパティを削除しなければいけないところがハマりポイントではないかと思います。

複数の引数を取るようなコマンドを作るさいには text を更に整形するような処理が必要になりますが、本記事のように user_id , channel_id , text の変数を作るところまで出来ていれば、色んな用途に応用できるのでは無いかと思います。

変数の初期化の部分については、私が知らないだけでもっとスマートに value を取り出す関数などがあるやもしれませんので、もしそういった方法を知ってる方がいましたら、@okash1n までご連絡いただければ幸いです。

今回作ったアプリのサンプルをGitHubにアップしているので、自分の環境で試してみたい方はZipファイルでダウンロードして、Power Automate にインポートしてみてください。

GitHub - Studist/sample-rename-chan: Slackのチャンネル名をAdmin権限なくてもリネーム出来るSlackアプリをMicrosoft Power…Slackのチャンネル名をAdmin権限なくてもリネーム出来るSlackアプリをMicrosoft Power Automateで作るサンプルです - GitHub - Studist/sample-rename-chan…
github.com

追記(エラーハンドリング)

エラーハンドリングを実装してみました。 conversations.rename の実行結果から ok プロパティと error プロパティの値を取得して条件分岐しています。

Image in a image block

エラーハンドリング

エラー処理に必要な値

conversations.rename の実行結果のうち、エラー処理に必要な値は

  • ok
  • is_channel
  • error

という3つの値です。

Image in a image block

実行結果を「JSONの解析」して、これらの値から変数を初期化しておきましょう。

※「条件」を作る際に、変数を作らずに直接「JSONの解析」の結果を条件式に入れてもうまくいきません

正常系

Image in a image block

リネームをしたチャンネルに通知を行って、終了です。

プライベートチャンネルの場合はBotユーザーを自分でチャンネルから退出させます。( conversations.leave を使用)

異常系

異常系は、今のところ以下の4つを想定してます

  • channel_not_found : プライベートチャンネルにフロー実行ユーザーを招待していないか、DMなどの場合
  • name_taken : すでに他のチャンネルで使われている名前を指定している
  • invalid_name_specials : チャンネル名に使えない文字や空白入っている(引数が2つ以上)
  • invalid_name_required : コマンドに引数が無い
Image in a image block

これらの場合、エラー通知はエラーコードとTeachme BizのマニュアルのURLを添えてコマンド実行者に対してDMで送るようにしています。

Image in a image block
Image in a image block

最後に、想定していないエラーが出た時を想定して、エラーコードと問い合わせへの誘導を行っています。

Image in a image block

フローの見通しを良くする

スコープというアクションを使うと、もう少しフローの見通しをよくできるそうなので、複雑な条件分岐になる場合はこういった機能を使うことを検討しても良いでしょう。