SlackからTodoistにタスクを追加する

# 概要
SlackからTodoistにタスクを追加するbotは公式の提供するものが存在する。
本記事では公式の提供する機能の一つと、Scrapboxでページを作成するオプションを持つbotを実装した。実装した機能を以下に示す。

| オプション | 詳細 | 
|:-----------|:-----------|
| . or タスク  | タスクの追加を行う | 
| #(プロジェクト名)  | プロジェクトの下に追加する 未指定ならinbox | 
|!(優先度)|優先度を「(弱)1-4(強)」で設定する 未指定なら1|
| @(期限) | 期限を設定する |
| -s | Scrapboxに同名ページを作成する |

# 下準備
本記事で説明するコードは事前に以下の準備が必要である。本記事での説明は省略する。

- [Incoming WebHooksの設定](https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008)
- [Outgoing Webhooksの設定](https://qiita.com/chike0905/items/58222a99be460f325ab8)
- Todoist tokenの取得
    - Todoistの[設定]>[連携機能]>[APIトークン]
- [GoogleAppsScriptをWebアプリケーションとして導入](https://tonari-it.com/gas-web-application-beginner/#i-2)

# 実装
「Scrapboxのページを作成する」はScrapboxのリンクを作成することで実現している。そのページが存在しない場合は新規作成されるが、存在する場合はページへのリンクとなる。([参考](https://scrapbox.io/help-jp/%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E3%82%8B))
コードを以下に示す。

```javascript:makeTask.gs
function doPost(e) {
  //Incoming WebHooksの設定で入手したWebHookURL
  var postUrl="https://hooks.slack.com/services/*******";

  var userMessage = (e.parameter.text).replace("タスク","");
  userMessage = (userMessage).replace(".","");

  var status = createTask(userMessage);
  var answerText,answerUrl;

  // botの表示名などを変更する。WebHook設定の方が優先されることもあるため気になる場合は確認する、
  var jsonData = {
     "channel" : "#reminder",
     "username" : "Todoist連携さん",
     "icon_emoji"	:":ghost:",
  };
  
  if (status[0] === 200) {
      answerText = "Todoistにタスクを追加しました。";
    if(status[1]){
      answerText += "Scrapboxにページを作成します。";
      answerUrl = createPage(status[2]);
      jsonData["attachments"] = [{
        "fallback": answerText+"（"+answerUrl+"）",
        "color": "#36a64f",
        "pretext": answerText,
        "title": "Scrapbox-"+status[2],
        "title_link": answerUrl,
      }];
    }else{
      jsonData["text"]=answerText;
    }
  } else {
    answerText = "Todoistへのタスク追加に失敗しました。";
    jsonData["text"]=answerText;
  }
  var options =
  {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : JSON.stringify(jsonData)
  };
  UrlFetchApp.fetch(postUrl, options);
}

function createTask(userMessage) {
  var url = "https://todoist.com/API/v7/sync",
      todoistToken = "*******************************",
      uuid = Utilities.getUuid(),
      tempId = Utilities.getUuid(),
      task = strToTask(userMessage),
      projectId = searchProject(task['projectName'],todoistToken),
      formData, response;

  formData = {
    "token": todoistToken,
    "commands": JSON.stringify([{
      "type": "item_add",
      "temp_id": tempId,
      "uuid": uuid,
      "args": {
        "priority" : task['priority'],
        "content": task['taskName'],
        "project_id": projectId, 
      "due": {
        "string": task['limit'],
      },
      }
    }])
  };

  response = UrlFetchApp.fetch(url, {
    "method" : "post",
    "payload" : formData
  });
  
  return [response.getResponseCode(),task['scrapbox'],task['taskName']];
}

function createPage(taskName){
  //  Scrapboxのページを作成する
  //  ******部分にはScrapboxのプロジェクト名を入力しておく
  var url = "https:///scrapbox.io/*******/"+taskName+"";
  url=encodeURI(url);
  Logger.log(url);
  return url;  
}

function strToTask(str){
  var response = {};
  //  !優先度 で設定
  var priority = str.match(/([！!][1-4１-４])/);
  if(priority){
    str = str.replace(priority[0],"");
    response['priority'] = priority[0].slice(1); 
  } else {
    response['priority'] = 1;
  }  
  
  //  -s　でScrapboxのページを作成
  var scrapboxOption = str.match(/(-s)/);
  if(scrapboxOption){
    str = str.replace(scrapboxOption[0],"");
    response['scrapbox'] = true;
  } else {
    response['scrapbox'] = false;
}

  //  #既存のプロジェクト で設定
  var projectName = str.match(/(#|＃)[^(@|＠)]*/);
  if(projectName){
    str = str.replace(projectName[0],"");
    response['projectName'] = projectName[0].slice(1); 
  }
  
  //  @期限 で設定
  var limit = str.match(/(@|＠).*/);
  if(limit){
    str = str.replace(limit[0],"");
    response['limit'] = limit[0].slice(1);
  }
  response['taskName'] = str;
  return response;
}

function searchProject(projectName,token){
  var url = "https://todoist.com/API/v7/sync",
      formData,options,response;
  var headers = {
      "Accept": "application/json",
  };
  formData ={
    "token": token,
    "sync_token":"*",
    "resource_types":'["projects"]',
  };
  response = UrlFetchApp.fetch(url,{
    "contentType": "application/json",
    "headers":headers,
    "method":"post",
    "payload":JSON.stringify(formData)
  });
  
  var array = JSON.parse(response.getContentText());
  for(var i = 0; i < array["projects"].length; i++){
    if(array["projects"][i]["name"]===projectName){
      return array["projects"][i]["id"];
    }
  }
  return 000000;
}
```
# 実行結果
実際に利用しているbotは上記コードからアイコンとコメントを変更したものである。
<img src="https://qiita-image-store.s3.amazonaws.com/0/235454/fc795b1c-5523-87de-4fa8-636086a6c34d.png" width=300px>

Todoist上では以下のように表示される。

<img src="https://qiita-image-store.s3.amazonaws.com/0/235454/f2c8d0b7-7132-8a71-ab74-1d435279abdc.png" >