見出し画像

Slack × GAS:新しいチャンネルができたら色々お知らせするBOT

こんばんは、しがない情シスです。

今回はSlackAPIとGASで自動処理作ったよ、というお話です。
ふだん全然テック系のお話しせずにポエムばっかり書いてるマンなのですが、たまにはエンジニアっぽい記事も書いてみよう…と筆を取り、書きかけて、数ヶ月下書きで熟成されたものが発掘されたのが本記事です。

ものすごくn番煎じな内容です。
GASとSlack初学者の苦悩の記録的な感じで、ポップコーン感覚でお読みください🫠

というわけで、いってみよー。

やりたいこと

今回やりたい内容は以下の通りです。

  • チャンネルが新規作成されたら、そのチャンネルにアナウンス文を投稿する

  • チャンネルが通知されたよ!というのを別のチャンネルに通知する

正直、iPaaSでサクッと組めるじゃろう、みたいな処理だったのですが現時点で使っているiPaaSがSlackトリガーあるのに全然着火してくれなくて、「もういい!BOTとGASで組むもん!!!」と不貞腐れながら作りました。
(Zapierとかだったらもっとお手軽に作れます…)

参考にした情報

GASメッチャツヨイ情シスであることで有名なbarusu師範のアウトプットをものすごく参考にしました。

やりたいことの基本はこのコードでほとんど実現できちゃう。すごい。

BOTを準備する

アイコンを作る

まずはBOTの顔となるアイコンを作りましょう。
私個人の意見ですが、BOTはもう一人のユーザーだと思いますし、メッセージ投稿とかの機能があれば、なおさらお顔は必要じゃないですか。
あと作ってて愛着湧くし。

というわけで、商用利用可能なフリー素材で良いのでアイコンを用意してあげましょう。
SlackBOTのアイコンサイズは512x512以上の正方形に限定されるので、必要に応じて画像をトリミングしたりします。

アプリをつくる

こちらのURLの[Create New Apps]-[From Scratch]からアプリを作っていきます。

※作り方はググったらいっぱい出てくるので参考にしてみてください。

Displaynameをつける

BOTを作ったらそいつをワークスペース内にインストールするわけなのですが、Slack内のDisplaynameが無いと「誰やねん」ってなってインストールできないそうです。

Displaynameはデフォルト空っぽなので、[Features]-[App Home]-[App Display Name]画面から表示名を設定してあげます。

表示名がない=アイデンティティがない

ここの設定がないとワークスペースへのインストール時に謎エラーで怒られます。

権限スコープとかを設定する

BOTをワークスペースに招いてアレコレするわけですが、BOTがアレコレするための権限は必要になります。
以下のOAuth & Permissionsメニューから「ボットユーザーとしての権限」「ボットとしての権限」をそれぞれ付与してやります。

今回のケースだと「チャンネル参照、チャンネル招待、メッセージ投稿」あたりの権限をつけてやる必要がありますね。

やりたい挙動に対するスコープはここの「Required scopes」をみましょう。

権限が足りてなかったりするとエラーで動きませんので、つけ忘れに気をつけましょう。
「なんか動かないなー」というとき、疑うべきはまず権限不足だったりします。

BOTをワークスペースにインストールする

色々設定できたら[Install your app]メニューからBOTをワークスペースにインストールしてあげます。

これでBOTがワークスペース内で活動する準備ができました。

GASを書く

書いたコード

新規にチャンネルが追加されたらなんやかんや処理するGASを書きます。

//SlackBOTのトークンなどをプロパティから読み込む
const apiUrl = 'https://slack.com/api';
const botToken = PropertiesService.getScriptProperties().getProperty('SLACK_BOT_TOKEN');
const userToken = PropertiesService.getScriptProperties().getProperty('SLACK_USER_TOKEN');
const botUserID = PropertiesService.getScriptProperties().getProperty('SLACK_BOT_ID');
const botMemberID = PropertiesService.getScriptProperties().getProperty('SLACK_MEMBER_ID');

// メイン処理
function doPost(e) {
  // Event API Verification (初回存在確認)時のコード
  let json = JSON.parse(e.postData.getDataAsString());
  if (json.type == 'url_verification') {
    return ContentService.createTextOutput(json.challenge);        
  }

  // チャンネルID取得
  let createdChannelId = JSON.parse(e.postData.getDataAsString()).event.channel.id;

  // 追加された新規チャンネルにBOTを招待する
  const invited = inviteBotToChannel(createdChannelId);
  Logger.log(`  [${json.name}] invited: ${invited}`);
  
  // 新規追加チャンネルにガイダンスを投稿する
  const guidance = newChannelCreatedGuidance(createdChannelId);
  Logger.log(`  [${json.name}] guidance: ${guidance}`);

  // チャンネル通知チャンネルに情報を流す
  const notification = newChannelNotification(createdChannelId);
  Logger.log(`  [${json.name}] notification: ${notification}`); 
}

// 新規チャンネルが出来た時にチャンネル自体にガイダンス文章を投げる処理
function newChannelCreatedGuidance(channelId) {
  // 新規チャンネルに投げるPOSTの内容
  let text = 'おめでとうございます🎉 新しいチャンネルができました!!!\n';
  text += '以下、改めてご確認をお願いします。\n';
  text += '- prefixが正しくついていますか?\n';
  text += '- チャンネルマネージャーの設定をお願いします!\n';
  text += '- 誰が見ても分かるように、概要欄には「何のためのチャンネルか」を記載してください!\n';
  
  // リファレンス:https://api.slack.com/methods/chat.postMessage
  const url = `${apiUrl}/chat.postMessage`;
  const options = {
    'method' : 'post',
    'payload' : { 
      'token': botToken,
      'channel' : channelId,
      'text' : text
    }
  }
  // POST処理実行
  let response = UrlFetchApp.fetch(url, options);
  Logger.log(JSON.parse(response));
}

// 新規チャンネルが出来た時に通知チャンネルに情報を流す処理
function newChannelNotification(channelId) {
  const notificationID = PropertiesService.getScriptProperties().getProperty('NOTIFICATION_CHANNEL');
  
  // チャンネル追加通知チャンネルにPOSTする内容
  let text = 'みなさ〜ん!新しいパブリックチャンネルができましたよ〜!!🎉\n';
  text += '気になる方はチェックしてみてくださいね〜!\n';
  text += '<#' + channelId + ">"

  // リファレンス:https://api.slack.com/methods/chat.postMessage
  const url = `${apiUrl}/chat.postMessage`;
  const options = {
    'method' : 'post',
    'payload' : { 
      'token': botToken,
      'channel' : notificationID,
      'text' : text
    }
  }
  let response = UrlFetchApp.fetch(url, options);
  Logger.log(JSON.parse(response));
}

// 引数のチャネルにBotユーザーを招待 @return 成功したらtrue
function inviteBotToChannel(channelId) {
  // リファレンス:https://api.slack.com/methods/conversations.invite
  const url = `${apiUrl}/conversations.invite`;
  const body = {
    token: userToken,
    channel: channelId,
    users: botMemberID,
  };
  const headers = {
    Authorization: `Bearer ${userToken}`,
  };
  const res = UrlFetchApp.fetch(url, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(body),
    headers: headers,
    muteHttpExceptions: true,
  });

  const json = JSON.parse(res.getContentText());
  return json.ok;
}

汚い箇所はご容赦くだされ…。

トークンとかはあらかじめ作ったBOTのページからスクリプトのプロパティにコピペしてあります。

BOTは該当チャンネルに招待してあげないと動かなかったりするので、新規チャンネルが作られたら勝手に招待して動く形にしてます。
(招待処理もコードで実装)

「新規チャンネルが作られた!」というようなイベントをトリガーとするEventAPIがあるので、それを利用します。

EventAPIから受け取れる情報はここで確認できます。

「どんなイベントからどんな情報取れるかな?じゃあ処理はこうできるね」ということが組み立てられるので、書き始める前にざっと読んどきましょう。
(私はすっ飛ばして書き始めたので後から慌てて調べました。公式ドキュメントはちゃんと読もう。お約束です)

EventAPIを設定する

GASをデプロイする

EventAPIを投げるためのURLが必要になるので、書いたGASをデプロイしてURLを取得します。
プロジェクトページのデプロイメニューから、Webアプリケーションとしてデプロイ。
外部からアクセスさせたいので、アクセスできるユーザーは「全員」を指定します。

URLの初回疎通(charrange)

EventAPIのページ(Event Subscriptions)に上記のデプロイされたURLをコピペします。
コピペすると初回の疎通確認charrangeが行われ、チャレンジが通る(URLが実在していることを確認される)とEventAPIをトリガーにしてGASのファンクションを起動できるようになります。

Subscribe to bot eventsメニューで、トリガーとなるイベントを指定しましょう。
今回はチャンネルできたら、という条件なので「channnel_created」ですね。

これであとは新規チャンネルを作るとGASが動くことになります。

動作結果

新しく作ったチャンネル
チャンネルが出来たことを通知するチャンネル

やった〜、うごいた〜😊

動作のステップとしては、

  1. Slack EventAPIがchannel_createdを検知してトリガー

  2. GAS APIから投げられた情報をdoPostファンクションが処理される

  3. BOTが自動的に招待される

  4. BOTのアナウンスがPOSTされる(チャンネルへのお知らせ)

  5. BOTのアナウンスがPOSTされる(新着通知)

ざっくりこんな流れで動くことになります。

その他

わかりづらいポイント

Slackを操作する時に「対応する権限のトークンを使ってBOTに働きかける」わけですが、

  • ユーザートークンでユーザーとして処理するもの(チャンネルへの招待など)

  • BOTトークンでBOTとして処理するもの(メッセージの投稿など)

  • BOTはあらかじめ招待しておかないとチャンネルに対して色々できない

  • BOTを招待するときはBOTIDじゃなくてSlack上のユーザーIDで招待する

あたりがわかりづらかったです。
よくよく考えたらわかるのですが、似たような権限やIDが複数あるので、「どれ使ったらいいんじゃー!うおおおおーーーー!!」ってなりました。

デバッグもよくわからないですよね。
実環境で動作テストしたらうんともすんとも言わなかったりしたので、ブレークポイント置いてステップ実行したり、エラーや変数・定数の中身をJSONから取得して、メッセージに吐き出したりして解析してみたりしました。

設定しておくこと

ここで一通り動いて稼働したと思うのですが、運用上の弱点も一部解消しておきます。
この仕組みをメンテできるメンバーがいる前提なのですが、

  • BOTのコラボレーターにチームメンバーを指定する

  • GASのソースを共有ドライブなどに保存する

などの設定を行っておきます。(わかんなくていいのであれば上司でも可)

最悪自分に何かあってコードをメンテできなくなっても「暴走した古代兵器を止められる人」には情報(コードの場所、仕様、挙動など)と権限(ドライブへのアクセスやコード編集)は共有しておきたいものですね。

おわりに

なんか2,3年ぶりくらいにお仕事でちゃんとコード書いた気がします。
(これ作るまではずーっとノーコードツール触ったりパワポ芸人やったりドキュメント図書館で司書もどきをやってた気がします…)

ここまでまとめておいた上で身も蓋もない話なんですが、iPaaSあるならiPaaSでフローとかレシピ組んじゃった方が、早いし簡単です。

私がSlackAPI初学者だったのもあるのですが、色々調べてお試ししながら組んで、動かすまで2日弱くらい掛かっちゃいました。
Zapierとかだったらそれこそ1時間くらいでおんなじものが出来てると思います。

みんなー!iPaaSを駆使していいかんじに自動化しようね!!ノーコードツールはべんりだぞ!!!


…なんか違う話になってきた。


Slackにはイベントを検知してトリガーにする手段が色々ありますし、イベントで発生した情報をGASで処理することができます。
そしてGASはコードなので、処理の仕方は自分次第で好きに書けます。
つまり、可能性は無限大!

コードであれiPaaSであれ、やりたいことを実現する色んな手段が用意されているので、ご自身・チーム・組織環境に合ったやりやすい方法で自動化や効率化を実現していったら良いかなって思います。

さあ、あなたもレッツ効率化!

この記事が気に入ったらサポートをしてみませんか?