GASとslackでヘルプデスクフォームを作った話(スラッシュコマンド版 コード編)

GASとslackでヘルプデスクフォームを作った話(スラッシュコマンド版 準備編)の続きです。

処理の流れ

doPostで受けとったデータの内容によって分岐するようにしています。

スラッシュコマンドからのリクエストの場合

  1. トークンのチェック
  2. スラッシュコマンドのチェック
  3. フォームを作成しslackへ返す(表示させる)

フォームからのリクエストの場合

  1. フォームのcallback_idをチェック
  2. 質問者のslackのユーザーIDからメールアドレスを取得
  3. 内容をGSSに書き込む
  4. 質問者をチャンネルへ招待
  5. 内容をチャンネルへポスト

コード全体

コメントアウトで説明を入れています。

function doPost(e) {

  //スクリプトのプロパティからトークンやIDを取得
  var verifiy_token = PropertiesService.getScriptProperties().getProperty('verifiy_token'); //slackアプリ Basic InformationのVerification Token
  var bot_token = PropertiesService.getScriptProperties().getProperty('bot_token'); //slackアプリ OAuth & PermissionsのBot User OAuth Access Token
  var channel_id = PropertiesService.getScriptProperties().getProperty('channel_id'); //#helpdeskのチャンネルID
  var gss_id = PropertiesService.getScriptProperties().getProperty('gss_id'); //スプレッドシートのID
  var sup_mem1 = PropertiesService.getScriptProperties().getProperty('sup_mem1'); //サポートメンバー1のslackUserID
  //メンバー数に応じて、必要があれば追加してください。(スクリプトプロパティも)
  //var sup_mem2 = PropertiesService.getScriptProperties().getProperty('sup_mem2'); //サポートメンバー2のslackUserID
  
  var time = new Date(); //投稿時間作成用に現在時刻を取得
  
  //verification tokenの検証
  var verification_token = e.parameter.token || JSON.parse(e.parameter.payload).token || null;  //リクエストされたトークンを格納
  if (verification_token !== verifiy_token) { // アプリのトークンと一致しない場合終了
    return ContentService.createTextOutput();
  }
  
  //----------------------------------------スラッシュコマンドから投稿された場合の処理
  if (e.parameter.command === "/helpjosys") {
    var created_dialog = createDialog(e,bot_token); //ダイアログを作成する
    var response = UrlFetchApp.fetch("https://slack.com/api/dialog.open", created_dialog); //チャンネルへポストする
    return ContentService.createTextOutput(); //空文字をreturnしないとダイアログが消えない
    
    //----------------------------------------フォーム(Dialog)から投稿された場合の処理
  } else {
    var p = JSON.parse(e.parameter.payload);
    var s = p.submission;
    
    if (p.callback_id === "helpdesk_dialog") { //想定しているフォームからの投稿かチェック
    
      //質問者のslack User IDから、メールアドレスと取得
      var user_email = findUserBySlackID(p.user.id,bot_token)
      
      //スプレッドシートへ記録
      var spread_sheet = SpreadsheetApp.openById(gss_id);
      var sheet_name = "helplist";
      var no = spread_sheet.getSheetByName(sheet_name).getLastRow() //現在の最終行を管理Noにする
      spread_sheet.getSheetByName(sheet_name).appendRow(
        [no,
         time,
         user_email,
         p.user.id,
         s.username, //s.*****の*****はfunction createDialogで作成したelementsのnameです
         s.urgency,
         s.question_content
        ]
      )
      
      //質問者をチャンネルへ招待
      var created_invite = createInvite(p.user.id,bot_token,channel_id)
      var response = UrlFetchApp.fetch("https://slack.com/api/conversations.invite", created_invite);
      
      //チャンネルへポスト
      var message_data = "<@" + sup_mem1 + ">" // サポートメンバー増があれば必要に応じて追加<@" + sup_mem2 + ">"
      + "\n<@" + p.user.id + "> から問い合わせがありました。"
      + "\n\n*No*\n" + no
      + "\n\n*氏名*\n" + s.username
      + "\n\n*緊急度*\n" + s.urgency
      + "\n\n*問い合わせ内容*\n" + s.question_content
      var created_message = createMessage(message_data,bot_token,channel_id)
      var response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", created_message);
    }
    return ContentService.createTextOutput();
  }
  return ContentService.createTextOutput();
}
//----------------------------------------フォーム(Dialog)作成
function createDialog(e,bot_token){
  var trigger_id = e.parameter.trigger_id;
  var dialog = {
    "token": bot_token,
    "trigger_id": trigger_id,
    "dialog": JSON.stringify(
      {
        "callback_id": "helpdesk_dialog",
        "title": "ヘルプデスクフォーム",
        "submit_label": "送信する",
        "elements": [
          {
            "type": "text",
            "label": "氏名",
            "name": "username",
            "placeholder": "佐藤裕也(`ェ´)ピャー" //記入例を表示させたい場合placeholderを使用
          },
          {
            "type": "select",
            "label": "緊急度",
            "name": "urgency",
            "options": [
              {
                "label": "低 (1週間以上)",
                "value": "低 (1週間以上)"
              },
              {
                "label": "中 (1週間以内)",
                "value": "中 (1週間以内)"
              },
              {
                "label": "高 (3日以内)",
                "value": "高 (3日以内)"
              },
              {
                "label": "緊急 (当日)",
                "value": "緊急 (当日)"
              }]
          },
          {
            "type": "textarea",
            "label": "問い合わせ内容",
            "name": "question_content",
            "placeholder": "なにもしてないのにパソコンがこわれた"
            //"optional":"true" ←これを追加することで任意の項目になる(直前にカンマ追加も忘れないよう)
          }
        ]}
    )
  }
  var options = {
    'method' : 'POST',
    'payload' : dialog
  }
  return options;
}
//----------------------------------------slackIDからメールアドレスを取得
function findUserBySlackID(slackID){
  var url="https://slack.com/api/users.info"  
  var payload = {
    "token" : bot_token,
    "user" : slackID
  }
  var options = {
    "method" : "GET",
    "payload" : payload,
    "headers": {
      "contentType": "x-www-form-urlencoded",
    }
  }
  
  var json_data = UrlFetchApp.fetch(url, options);
  var otp = json_data
  json_data = JSON.parse(json_data)
  
  if (json_data["ok"]){
    var user_email = String(json_data["user"]["profile"]["email"])
    } else {
      var user_email = "null" //slackユーザーが居てメールが無いことはないと思いますが念のため
      }
  
  return user_email
}
//----------------------------------------postMessage作成
function createMessage(message_data,bot_token,channel_id){
  var payload = {
    "token" : bot_token,
    "channel" : channel_id,
    "text" : message_data
  }
  var options = {
    "method" : "POST",
    "payload" : payload,
    "headers": {
      "contentType": "x-www-form-urlencoded",
    }
  }
  return options;
}
//----------------------------------------Invite作成
function createInvite(user_id,bot_token,channel_id){
  var payload = {
    "token" : bot_token,
    "channel" : channel_id,
    "users" : user_id
  }
  var options = {
    "method" : "POST",
    "payload" : payload,
    "headers": {
      "contentType": "x-www-form-urlencoded",
    }
  }
  return options;
}

スクリプトプロパティの設定

Googleフォーム版と同様に設定してきます。

下記のパラメータを設定します。

sup_mem1
 サポートメンバーのslackユーザーID(メンション通知用)
 ※ユーザーIDの調べ方は別途ググってくださいm(_ _)m
gss_id
 スプレッドシートのID
bot_token
 slackアプリのOAuth & Permissions → Bot User OAuth Access Token
channel_id
 slackのヘルプデスク用チャンネルのID
verify_token
 slackのアプリのBasic Information → Verification Token

Verification Tokenはこちら

f:id:simple-josys:20200502153954p:plain:w500

functionの説明

doPost

メインの処理

createDialog

dialog.openメソッドに必要な中身を作る処理
メイン処理の中に入れるとハイパー見辛いので

findUserBySlackID

slackからユーザーリストを貰い、ユーザーIDを元にメールアドレスを探す処理

createMessage

chat.postMessageメソッドに必要な中身を作る処理

createInvite

channels.inviteメソッドに必要な中身を作る処理

権限のチェック

コードが完成したら、保存して再度公開しましょう。

f:id:simple-josys:20200502140520p:plain:w500


その際に、新たに権限が必要なコードが追加されていた場合、許可を求められるので許可します。

f:id:simple-josys:20200429225326p:plain

実行テスト

スラッシュコマンドの実行

コマンド入力→送信

f:id:simple-josys:20200502161248p:plain:w500


フォームが表示されます(`ェ´)ピャー

f:id:simple-josys:20200502161358p:plain:w500


入力せずに送ろうとすると怒られます。

f:id:simple-josys:20200502161541p:plain:w200

slackの通知

入力して送信します。

f:id:simple-josys:20200502161705p:plain:w500


うまく投稿されました。

f:id:simple-josys:20200502195725p:plain

スプレッドシートの記録

こちらも問題ないようです。

f:id:simple-josys:20200502162120p:plain:w500

終わりに

いかがでしたでしょうか。
色々な記事を参考にさせていただいて、自分で運用しやすいようにいろいろ弄っているのでオリジナリティはそれほどありませんが…。

スプレッドシートへ書き込む部分で、「Googleフォーム経由」とか「slack経由」とか書けば見分けを付けることもできます。
e.parameterを別シートに記録することで実行ログ的なものを記録することもできます。

余力があればチケット管理サービスと連携させてみたいです。

修正

2020/10/16
channelsAPIが廃止予定のため、channels.invite→conversations.inviteへ変更

2021/8/11
function findUserBySlackIDの手法を変更(users.list API → users.info API)