見出し画像

Slackのコメントにリアクションをつけたらasanaのタスクを作成してほしい

タイトルの通りなのですが、Slackに特定のリアクションをするとAsanaのタスクを作ってくれるbotを作りました。

挙動としてはユーザーがSlack上で特定のリアクションをつけると、それをつけられたSlackコメントがAsanaのタスクになり、担当者が事前に指定したリアクションと紐づくユーザーになる、という仕様です。

なお、このコードは基本的にスレッドを利用しする想定がないので、もしかしたらスレッド内でついた場合、完了のリアクションか、コメントの取得が想定外の挙動をする可能性があります。

Slack

SlackAPIから以下の項目を設定したものを用意してSlackにインストールします。

  1. EventSubscriptions

    1. Subscribe to bot event

      1. 追加:reaction_added

  2. OAuth & Permissions

    1. Scopes

      1. Bot Token Scopes

        1. 追加:channels:history

        2. 追加:emoji:read

        3. 追加:reactions:read

        4. 追加:reactions:write

そしてTokenを保存しておきます。
また、後ほどEvent SubscriptionsのRequest URLにGASのウェブアプリURLを入れます。

スプシ

絵文字とユーザーを紐づけるためのシートを作成します。
私は以下の構成にしました
■シート1 紐づけ用シート(ユーザーが追加編集)
 A列 絵文字を入れる列
 B列 ユーザー名をAsanaの登録名でプルダウンから選択する列
 C列 Vlookupを使いB列の値をシート2で調べてAsanaのGidを取得する列
■シート2 ユーザー一覧(GASで追加編集)
 A列 Asanaのユーザー名
 B列 AsanaのユーザーGid

ユーザー一覧は以下のようなコードでタスクスケジューラに入れてしまえばユーザーが追加されても勝手に追加されていくので便利です。
※このコード、メンバーは増える一方という前提で書いてるので、書き込み前にシートの値を削除する作業はありません。総数が減ったとき、余計なものは残ります。

function getAsanaUsers() {
  
  var options = {
    'method': 'get',
    'contentType': 'application/json',
    'headers': { 
      'Authorization': 'Bearer ' + 【asanaのアクセストークン】
    }
  };    
  var response = UrlFetchApp.fetch('https://app.asana.com/api/1.0/users', options);
  var result = JSON.parse(response);
  var ss = SpreadsheetApp.getActiveSpreadsheet(); 
  var sheetName = ss.getSheetByName(【ユーザー一覧を入れるシート名】);

  if(result.data){
    for(var i = 0; i < result.data.length; i++){
        sheetName.getRange(i+2,1).setValue('="'+result.data[i]["name"]+'"'); 
        sheetName.getRange(i+2,2).setValue('="'+result.data[i]["gid"]+'"'); 
        sheetName.getRange(i+2,3).setValue('="'+result.data[i]["resource_type"]+'"'); 
    };
  };
  return;
};

Asana

以下を用意しておきます

  • アクセストークン

  • AsanaのワークスペースID

  • AsanaのプロジェクトID

GAS

※時間がなくてコードを整理整頓してないのでいつもよりたぶんちょっとカオスです。もともときれいではない気もするけど、いつも以上にです。転記のタイミングでちょっとだけ直したけど余裕があったら今度直します

長いので分割して書きます。

Slackから呼び出される部分

doPost(e)で値を受け取っています。
さっそくここで問題が1つ。
コードが完了するまでに一定の時間を要する場合、Slackから「もしかして届いてない?また送るね!」というホスピタリティ溢れる追いデータが届きますので、シンプルに書くと完了までの経過時間に応じてAsanaのチケットがいっぱい作成されます。
そのため、その回避が必要になります。
私はキャッシュで回避する以下のページを参考にキャッシュをして回避をしています。

■参考にしたページ
SlackのEvents APIが複数回叩かれてしまうことがあったのでその対応をした

var ss = SpreadsheetApp.getActiveSpreadsheet();
function doPost(e){
  var json = JSON.parse(e.postData.getDataAsString());
  var event = json.event;
  try{
      var channel = event.item.channel;
      var ts = event.item.ts;
      var cache = CacheService.getScriptCache();
      var cacheKey = channel + ':' + ts;
      var cached = cache.get(cacheKey);
      if (cached != null) {
        console.log('do nothing!');
       return ContentService.createTextOutput(params.challenge);
      };
      cache.put(cacheKey, true, 30);      
  var asanaUserGid="";
  if (event.type === "reaction_added") {
  asanaUserGid = reactionList(event.reaction);
    if (asanaUserGid>0){
      asanaTaskCreate(event.item,asanaUserGid);
      addReaction(event.item);;
    };
  };
      return ContentService.createTextOutput(params.challenge);
     }catch(err){
    };
  return ContentService.createTextOutput(json.challenge);
};

スプシを見に行く部分

この部分で以下を判別して情報を取得しています。

  • 登録済みの絵文字と一致するか=Asana作成対象か

  • Asanaの担当者は誰にしたらよいか

function reactionList(reaction){
  var sheetName = ss.getSheetByName(【事前に用意した絵文字と担当者の紐づけシート】);
  const range = sheetName.getRange(2, 1, sheetName.getLastRow() - 1,3).getValues();
  for (i=0;i<sheetName.getLastRow()-1;i++){
    if (range[i][0].indexOf(reaction) >= 0){
      return  range[i][2];
      };
  };
return 0;
};

Asanaを作成する部分

ここで突然のお知らせです。
私が見間違っていなければリアクションがついたSlackの「コメント部分」は最初の取得時に取れてなさそうでしたので、このタイミングでSlackの該当ポストからコメントを拾う作業を合わせて行っています

コメント部分の取得

function getMessage(ts,channel){
  const url = "https://slack.com/api/conversations.replies"
  const slack_app_token = 【Slackのアクセストークン】;
  const limit =10;
  const options = {
    "method" : "get",
    "contentType": "application/x-www-form-urlencoded",
    "payload" : { 
      "token": slack_app_token,
      "channel": channel,
      "ts":ts
    }
  };
    const response = UrlFetchApp.fetch(url, options);
    const json = JSON.parse(response);
    const text = json.messages[0].text
    const date = new Date();
    return text;
};

Asana作成部分

■参考にした記事
AsanaのAPIに関しては私が書いたほかのnote記事同様にこちらの記事が参考になりました。ありがとうございました。


function asanaTaskCreate(item,usetGidId){
  var today = new Date();
  var todayStr = Utilities.formatDate(today, 'JST', 'yyyy-MM-dd');
  var todayStr2 = Utilities.formatDate(today, 'JST', 'yyyy-MM-dd H:MM:SS');

  const workspaceId = "ワークスペースID"
  const projectId = "プロジェクトID";
  const name = "[Slack作成]"+todayStr2;
  const planText = getMessage(item.ts,item.channel)+"\nhttps://[slack名].slack.com/archives/"+item.channel+"/p"+item.ts
  const userId = usetGidId; //IDで指定


  const objTask = {
    "data": {
      "workspace": workspaceId,
      "projects": [projectId],
      "name": name,
      "notes": planText,
      "assignee": { "gid": userId, "resource_type": "user" },
      "due_on": todayStr
    }
  };
  const asanaManager = new AsanaManager("Asanaアクセストークン");
  asanaManager.createTask(objTask)
}

class AsanaManager {
  constructor(token) {
    this.token = token;
  }
  createTask(objPayload) {
    const url = `https://app.asana.com/api/1.0/tasks`;
    const data = this.postAsanaData(url, objPayload);
    console.log('createTask', data);
    return data;
  }

  postAsanaData(url, objPayload) {
    const token = this.token;
    const options = this.getPostOption(token, objPayload);
    const response = UrlFetchApp.fetch(url, options);
    const jobj = JSON.parse(response);
    if (response.getResponseCode() !== 201) {
      console.log(jobj);
    }
    const data = jobj["data"];
    return data;
  }

  getPostOption(token, objPayload) {
    const headers = {
      "Authorization": "Bearer " + token
    };
    const payload = this.getJson(objPayload);
    const options = {
      "method": "post",
      "contentType": "application/json",
      "Accept": "application/json",
      "headers": headers,
      "payload": payload,
      "muteHttpExceptions": true,
      "number_value":"test"
    }
    return options;
  }

  getJson(object) {
    var json = JSON.stringify(object);
    console.log(json);
    return json;
  }
}

Slackの該当コメントにリアクションをつける部分

function addReaction(item){
 var url = 'https://slack.com/api/reactions.add';

  var payload = {
    "token" : 【Slackのアクセストークン】,
    "channel" : item.channel,
    'timestamp':item.ts,
    'name':'asana_task_create'
    
  };
  var options = {
    "method" : "post",
    "payload" : payload
  };
    var response =  UrlFetchApp.fetch(url, options);
    Logger.log(response);
  return response;
}

接続!


私、最初のほうに「後ほどEvent SubscriptionsのRequest URLにGASのウェブアプリURLを入れます。」と書きましたね?
書いたコードをデプロイして、出てきたURLをSlack側に入れます。


これで、Slackで気になったコメント、やります!と返事したタスクがあったときに、Asanaのチケットがリアクション一つで作れるようになりました!

あとは、稼働させたいチャンネルに作ったBotを招待すればOKです!

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