LoginSignup
115
68

More than 3 years have passed since last update.

【出欠確認に疲れた若手必見】 3ステップでできる! slackで出欠確認コマンド

Last updated at Posted at 2020-12-03

この記事はモチベーションクラウドシリーズアドベントカレンダー2020の4日目の記事です。


どうも初めまして、リンクアンドモチベーションでエンジニアをしている伊藤と申します。
完全に別件の宣伝ですが、2020/12/18に弊社がスポンサーにもなっている【オンライン開催】銀座Rails#28 で「モチベーションクラウドを支える非同期処理の変遷」というテーマで発表します。
今回のゲストスピーカーはあのまつもとゆきひろ(@yukihiro_matz)さんなので興味があればぜひ!!

はじめに

業務効率化のために slack apigoogle app scriptを使ったslack appを作りました。

なぜ作ったか から作成の手順を3つのステップに分けて解説します。

こんなことありませんか?

〜オンライン会議にて〜

上司 「おい、若手!全員いるか確認しろ!」
自分 「はい!」
...
自分 「(顔だけじゃわかんないし、人数的には足りないけど誰いないかわかんないよ...)」
上司 「誰いない?いない人声かけて!」
自分 「...(いないフリ)」
上司 「ん???」

自分 🥺


online_kaigi_man.png

新しい生活様式になり、ガラリと変わった会議の形 = オンライン会議 での若手のあるあるイライラの一つである、

「オンライン会議、誰がいないか全然わからない問題」

を解決するために!

出欠を報告してもらう
slack用アプリ (スラッシュコマンド)

を作成しました!

若手でも作れます!!

何を作ったか

使い方は簡単!!

(「出欠確認して」と言われる前にやっておくこと!) ← こういうのが若手のうちはとっても大事

  1. 全員がいるslackチャンネルで /attendance と入力する
  2. 参加者がボタンをおす
  3. 名前が残っている人を読み上げて、参加しているか確認する

ほら、いない人が一目瞭然!!!

もうこれで

誰がいないか画面とにらめっこ

なんてしなくていいですね!!

今回はslack apiを活用しながら、Google Apps Script (以降 gas) で実装していきます。

いいね!と思った人はぜひLGTMお願いします!!

作り方

解説はいくつか飛ばしますがgasは活用記事が多かったり、本家のReferenceも整備されているのでそちらも合わせて参照ください。

全体像

/attendanceを実行してから、欠席者の名前が出るまでのフローは以下のようになっています

なので

  1. slack appの設定
  2. スラッシュコマンドの作成
  3. 参加ボタン用apiの作成

の3つに分けて説明していきます!

1. slack appの設定

まずはslack appを登録していきます

Slack APIへアクセスし下記の「Start Building」をおします

すると次のようなポップアップが出てくるので

  • App Nameはお好みで (出欠確認bot とか)
  • Development Slack Workspace は導入する予定のslackのworkspaceを選択

早速アプリが作成できました!

さらに、Settings > Basic Information > Display Information より、作ったアプリの見た目をいい感じにいじることができます

次にbotとして発信もさせたいので、botの登録をします

Features > App Home > Your App's Presence in Slackより Editをおします

ポップアップが出てくるのでいい感じに名前を付けます

これで完了です!

アプリのインストールとtokenの確認

Features > OAuth & Permissions > OAuth Tokens & Redirect URLs で 「Install to Workspace」を押して、許可するを押しましょう

するとFeatures > OAuth & Permissions > OAuth Tokens & Redirect URLs
OAuth Access Token
が表示されるので、メモしておいてください (あとで使用します)

(このtokenはセキュリティ的に重要なので、扱いには気をつけてください)

また、Settings > App Credentialsより
Verification Token
もメモしてください (こちらもあとで使用します)

これで一旦slack appの設定は完了です
このあとちょくちょくslack側での設定を行うので、このslack appのページは閉じないで下さい

2. スラッシュコマンドの作成

次はslackのスラッシュコマンドをgasで作成します

スラッシュコマンドって?

slack上で使える簡単なコマンドです

/remindとかは使ったことのあるひとも多いのではないでしょうか?

/remind ごみを捨てる tommorowと送信すると...

ってできるやつです!
参考

ここでは /attendanceって入力するとメンバーの一覧が投稿される機能を作っていきます!

今回の作り方

流れはこのような形になっています

なので、手順としては、

  • google スプレッドシートにメンバーリストを用意する
  • スプレッドシートにメンバーリストを送信するスクリプトを用意する
  • slackからメンバーリストを使ってボタン付き出欠確認メッセージを送信できるようにする

の流れで作っていきます

google スプレッドシートにメンバーリストを用意する

gasを始める前に会議に参加する人の一覧を作っておきましょう

  • 新しいスプレッドシートは http://spreadsheet.new/ で作れます
  • 今回は「ユーザー一覧 (users)」 と 「所属一覧 (groups)」の2つのシートを作ります
  • ユーザー一覧には、slack id, 名前, 所属 (グループ名とか)の3列を用意します
  • 所属一覧には、所属一覧の1列を用意します
  • slack idの調べ方はこちらなどを参考にcsvなどでどかっととってきましょう

シートのイメージ

この時以下の2点に注意してください

  • ユーザー一覧シートは
    • 一行目に 、slack id, 名前, 所属をいれる
    • シートの名前をusersにする
  • 所属一覧シートは
    • 一行目に 、所属一覧をいれる
    • シートの名前をgroupsにする

スプレッドシートにメンバーリストを送信するスクリプトを用意する

まずはgasを開きましょう
先ほどのシートの ツール > スクリプトエディタを選択します

するとこんなコードエディターが開きます

ここに以下をコピペして保存します (コードの詳しい解説はskipします!!)

コード.gs(ここをクリックして展開)
コード.gs
const slackToken = PropertiesService.getScriptProperties().getProperty("slack_token")
const verificationToken = PropertiesService.getScriptProperties().getProperty("verification_token")
const NotVerificateMessage = "許可されていません"
const usersSheetName = "users" // ユーザー一覧シート名
const groupsSheetName = "groups" //所属一覧シート名
const buttonValue = "attend"

// 以下の変数は適宜変更してください
const buttonText = "参加"
const notAttendComment = "まだ参加していない方↓"
const defaultMessage = "参加していますか?"
const responseData = "出欠確認をしました"
const textMessage = "出欠をとっています"

function doPost(e) {
  const params = e.parameter
  const token = params.token
  if (token != verificationToken) {
    return ContentService.createTextOutput(NotVerificateMessage).setMimeType(ContentService.MimeType.JSON);
  }
  const userName = params.user_name
  const channelId = params.channel_id
  let message = params.text
  if (!message) {
    message = defaultMessage
  }
  postAttendaceMessage(channelId, message, userName)
  return ContentService.createTextOutput(responseData).setMimeType(ContentService.MimeType.JSON);
}

function postAttendaceMessage(channelId, message) {
  // userのリストをとってきて
  const usersHash = usersHashByGroupname()
  // blockを作成して
  const blocks = makeBlockFromUsersHash(usersHash, message)
  // 決められたchannelに投稿する
  return postSlackByChannelwithBlocks(channelId, blocks)
}

// return {group_name: [user_id, user_id], group_name: [user_id,...], ..}
function usersHashByGroupname() {
  const rawSheetValues = getSpreadSheetByName(usersSheetName)
  let groupedHash = {}
  const rowGroupSheetValues = getSpreadSheetByName(groupsSheetName)
  rowGroupSheetValues.forEach(function (value) {
    if (value[0]) {
      groupedHash[value[0]] = []
    }
  })
  rawSheetValues.forEach(function (value) {
    if (value[0] && value[1] && value[2]) {
      groupedHash[value[2]].push("<@" + value[0] + ">")
    }
  })
  return groupedHash
}

function getSpreadSheetByName(sheetName) {
  //spreadsheetの値を返す
  const ss = SpreadsheetApp.getActiveSpreadsheet(); //スプレッドシートを取得
  const sheet = ss.getSheetByName(sheetName); //シート名でシートを取得
  const lastRow = sheet.getLastRow(); //最終行取得
  const lastCol = sheet.getLastColumn(); //最終行取得
  const firstRow = 2
  const firstCol = 1
  const range = sheet.getRange(firstRow, firstCol, lastRow, lastCol)
  const values = range.getValues()
  return values
}

function postSlackByChannelwithBlocks(channelId, blocks) {
  const postMessageurl = "https://slack.com/api/chat.postMessage"
  const payload = {
    "token": slackToken,
    "channel": channelId,
    "text": textMessage,
    "blocks": blocks
  }
  const params = {
    methods: 'post',
    payload: payload
  }
  return UrlFetchApp.fetch(postMessageurl, params)
}

function makeBlockFromUsersHash(usersHash, message) {
  const head = {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": message
    },
    "accessory": {
      "type": "button",
      "text": {
        "type": "plain_text",
        "emoji": true,
        "text": buttonText
      },
      "value": buttonValue
    }
  }
  const divider = {
    "type": "divider"
  }
  const notAttendMessage = {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": notAttendComment
    }
  }
  const mainBlocks = makeMainBlocksbyUserHash(usersHash)

  let blocks = []
  blocks.push(head)
  blocks.push(divider)
  blocks.push(notAttendMessage)
  blocks = blocks.concat(mainBlocks)
  blocks.push(divider)
  return JSON.stringify(blocks)
}

function makeMainBlocksbyUserHash(usersHash) {
  let mainBlocks = []
  Object.keys(usersHash).forEach(function (groupName) {
    if (usersHash[groupName].length > 0) {
      const context = {
        "type": "context",
        "elements": [{
          "type": "mrkdwn",
          "text": groupName
        }]
      }
      const usersSection = {
        "type": "section",
        "text": {
          "type": "mrkdwn",
          "text": usersHash[groupName].join(" ")
        }
      }
      mainBlocks.push(context)
      mainBlocks.push(usersSection)
    }
  })
  return mainBlocks
}

保存したら ファイル > プロジェクトのプロパティ を開きます

さらに スクリプトのプロパティのタブに二つの値を設定します

プロパティ
slack_token xoxp-から始まる先ほどメモした OAuth Access Token
verification_token 先ほどメモした Verification Token

設定したら保存を押します

最後にapiとして公開します

公開 > ウェブアプリケーションとして導入を選択します

Deploy as web appというポップアップが出てくるので、以下のように設定します

Project version New (ここをNewにしないと最新の更新が反映されないので注意!!)
Execute the app as Me
Who has access to the app Anyone, even anonymous

そしてDeployを押すと初回は権限を確認されるので許可を確認を押します

アカウント選択画面が出るので、自分のアカウントを選択します

すると警告メッセージが出ますが、詳細 (1) を押すと script名 (安全ではないページに移動) (2) が出てくるのでクリックします

script名 が Google アカウントへのアクセスをリクエストしていますと表示されるので 許可を押します

すると公開が完了します

完了後、Deploy as web appというポップアップと共に今回作成したapiのurlが表示されるのでこれをメモします

これでapiの作成が完了しました!!

slackからメンバーリストを使ってボタン付き出欠確認メッセージを送信できるようにする

次にslackにスラッシュコマンドを用意します

slack apiのFeatures > Slash Commandsより、Create New Commandsを押します

そしたらそれぞれこんな感じで埋めていき、保存します

  • Command : /attendance
  • Request URL : さっき公開したURL
  • Short Description, Usage Hint : いい感じに!!

これでスラッシュコマンドの登録が完了です!

↓この段階でこんな感じのスラッシュコマンドが使えるようになっているはずです
gif

3.参加ボタン用apiの作成

さて残るはボタンを押した時のアクションのみです

参加ボタンを押すとslackのメッセージが変わるアクションを作っていきます

ボタンを押してからメッセージが更新される流れはこんな感じです

flow

apiの作成

ボタンアクション用のapiもgasで作っていきます
先ほどとは別の新しいscriptを用意しましょう => http://script.new/

ここに以下をコピペして保存します (コードの詳しい解説はskipします!!)

コード.gs(ここをクリックして展開)
コード.gs
const slackToken = PropertiesService.getScriptProperties().getProperty("slack_token")
const verificationToken = PropertiesService.getScriptProperties().getProperty("verification_token")

const replaceEmoji = ":white_check_mark:" // 好きな絵文字に置き換える!
const attendButtonValue = "attend"
function doPost(e) {
  const payload = JSON.parse(e["parameter"]["payload"])
  const token = payload.token
  if (token != verificationToken) {
    return
  }
  const action = payload.actions[0]["value"]
  if (action === attendButtonValue) {
    return clickedAttendButton(payload)
  }
}


function clickedAttendButton(payload) {
  const originalBlocks = JSON.stringify(payload.message.blocks)
  const messageTs = payload.container.message_ts
  const channelId = payload.container.channel_id
  const userId = payload.user.id
  const user = "<@" + userId + ">"
  const replacedBlocks = originalBlocks.replace(user, replaceEmoji)
  const chatUpdateUrl = "https://slack.com/api/chat.update"
  const updatePayload = {
    "token": slackToken,
    "channel": channelId,
    "ts": messageTs,
    "blocks": replacedBlocks
  }
  const updateParams = {
    methods: 'post',
    payload: updatePayload
  }
  UrlFetchApp.fetch(chatUpdateUrl, updateParams)
  const params = {
    "replace_original": false,
    "text": ""
  }
  return ContentService.createTextOutput(JSON.stringify(params)).setMimeType(ContentService.MimeType.JSON);
}


保存したら先ほどと同様にtokenの設定を行います
ファイル > プロジェクトのプロパティ を開きます

さらに スクリプトのプロパティのタブに二つの値を設定します

プロパティ
slack_token xoxp-から始まる先ほどメモした OAuth Access Token
verification_token 先ほどメモした Verification Token

完了したらスラッシュコマンド作成時と同様にapiとして公開してください
(この記事の2. スプレッドシートにメンバーリストを送信するスクリプトを用意する を参考にしてください)

slack apiへの登録

早速作成したapiをslackに登録していきます

Features > Interactivity & Shortcutsのページにいきます

このInteractivityOffOn にするといろいろなメニューが展開されます

そしてInteractivityRequest URLに先ほど作成したapiのurlを貼り付けます

そして、右下の Save Changesを押せば完了です!!

これで参加ボタンを押すと自分の名前が ✅ に変更されるようになりました!!

完成!!

長かったですね...
お疲れ様です!!

応用編

毎週月曜日の朝9時に出欠をとりたい!

この記事が参考になるかと思います

今回作成した、/attendance用コードを応用しましょう

例えば、下記のような関数を作成し、上記の記事のようにtriggerを設定してあげれば定例オンライン会議の度に/attendanceをしなくてもよくなります!! (さらなる無駄の解消)

コード.gs
function postAttendaceMessageEveryMonday() {
  const channelId = "channel id"
  const message = "月曜日の定例へ参加お願いします! https://meet.google.com/xxx-xxxx-xxx"
  postAttendaceMessage(channelId, message)
}

出欠確認メッセージをもっとモチベーションがあがる形にしたい!

slackが用意している Block Kit Builderを使えばアウトプットをイメージしながら作ることができます!

ボタンを押したらもっと可愛い絵文字になるようにしたい!

自分のワークスペースでカスタム絵文字を設定して見ましょう

参加ボタン用api のソース内部の replaceEmojiという変数を追加したカスタム絵文字にすることでより愛着が湧きます

参考:
- slackでカスタム絵文字を設定する
- カスタム絵文字ジェネレーター

↓弊社ではなぜか参加ボタンを押すと自分が坊主になります...???

終わりに

書き終わって見ると、この記事が想像の3倍の量になって驚いています...
slack appだったり、google app scriptはリファレンスを読んでいると、「あれ、面倒だったこれもアプリにやらせるんじゃないか??」がいろいろ湧いてきて楽しいですね。
面倒なルーティーンはアプリに任せて、自分は新しいことを勉強する時間に使っていきたいですね!!

参考

115
68
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
115
68