自動化厨のプログラミングメモブログ │ CODE:LIFE

Python/ExcelVBA/JavaScript/Raspberry Piなどで色んなことを自動化

毎朝GASでGoogle Tasks(ToDo)を取得して期限切れタスクをSlackで教えてもらう

f:id:maru0014:20201222211706p:plain

この記事は UG Advent Calendar 2020 23日目の記事です。

カレンダーやGmailの横にある Google Tasks はシンプルかつ使いやすい。

でもここに書いたは良いけど定期的に棚卸ししたりできているかというと...。

ということでGASで取得してスプレッドシートにサマリーを作成、期限超過していた場合は再度期限を設定してSlackに通知するシステムを作ってみる。

リストとタスクを取得してオブジェクト構造を見てみる

ますはTasks APIを利用可能にするため、メニューの「リソース」>「Google の拡張サービス...」にて Tasks API を ON に変更する。

f:id:maru0014:20201222211715p:plain

ひとまずリストを全て取得してJSONを見てみる

// リストを全て取得
const lists = Tasks.Tasklists.list();
Logger.log(JSON.stringify(lists));

なるほど items に配列として各リストが入っていると

{
    "etag": "\"MTU2MjE3Mjc3\"",
    "items": [
        {
            "kind": "tasks#taskList",
            "selfLink": "https://www.googleapis.com/tasks/v1/users/@me/lists/MDMzMDIwNjk4NDI3MzYwNTUwMTc6MDow",
            "title": "ToDo",
            "id": "MDMzMDIwNjk4NDI3MzYwNTUwMTc6MDow",
            "updated": "2020-12-21T12:18:16.187Z",
            "etag": "\"LTIwNTk2NDAxNDg\""
        },
        {
            "id": "WmlWQnBMblJETFQ3Q1RNeQ",
            "title": "CODE:LIFE",
            "updated": "2020-12-21T12:16:59.023Z",
            "etag": "\"LTIwNTk3MTY1ODQ\"",
            "selfLink": "https://www.googleapis.com/tasks/v1/users/@me/lists/WmlWQnBMblJETFQ3Q1RNeQ",
            "kind": "tasks#taskList"
        },
        {
            "etag": "\"LTIwNTk3NjIzMDE\"",
            "id": "RnFzVFBOVXpiTVFHVjhwZQ",
            "updated": "2020-12-21T12:16:13.330Z",
            "kind": "tasks#taskList",
            "title": "UG",
            "selfLink": "https://www.googleapis.com/tasks/v1/users/@me/lists/RnFzVFBOVXpiTVFHVjhwZQ"
        }
    ],
    "kind": "tasks#taskLists"
}

https://developers.google.com/tasks/reference/rest/v1/tasklists

プロパティ
kind string リソースのタイプ。これは常に「tasks#taskList」です。
id string タスクリスト識別子。
etag string リソースのETag。
title string タスクリストのタイトル。
updated string タスクリストの最終変更時刻(RFC 3339タイムスタンプとして)。
selfLink string このタスクリストを指すURL。このタスクリストを取得、更新、または削除するために使用されます。

次にリスト内のタスクを取得してみる

Logger.log(JSON.stringify(Tasks.Tasks.list(lists.items[0].id)));

デフォルトで完了済みタスクは取得しないっぽい。完了済みはどうやって取るんだろう。

{
    "etag": "\"LTIwNTk2NDAxNDg\"",
    "items": [
        {
            "position": "00000000000000000001",
            "due": "2021-01-10T00:00:00.000Z",
            "status": "needsAction",
            "updated": "2020-12-21T12:18:50.000Z",
            "etag": "\"LTIwNTk2MDU4ODk\"",
            "selfLink": "https://www.googleapis.com/tasks/v1/lists/MDMzMDIwNjk4NDI3MzYwNTUwMTc6MDow/tasks/VndOOGh0QVRZSlZoYWJSSw",
            "kind": "tasks#task",
            "id": "VndOOGh0QVRZSlZoYWJSSw",
            "title": "エスプレッソマシン 内部清掃"
        },
        {
            "position": "00000000000000000003",
            "due": "2020-11-30T00:00:00.000Z",
            "id": "bmp4dnhaWDkzUnRhNXViWA",
            "updated": "2020-12-21T12:17:40.000Z",
            "title": "自宅VPN不通調査",
            "kind": "tasks#task",
            "selfLink": "https://www.googleapis.com/tasks/v1/lists/MDMzMDIwNjk4NDI3MzYwNTUwMTc6MDow/tasks/bmp4dnhaWDkzUnRhNXViWA",
            "status": "needsAction",
            "etag": "\"LTIwNTk2NzYxNTc\""
        },
    ],
    "kind": "tasks#tasks"
}

https://developers.google.com/tasks/reference/rest/v1/tasks

プロパティ
kind string リソースのタイプ。これは常に「tasks#task」です。
id string タスク識別子。
etag string リソースのETag。
title string タスクのタイトル。
updated string タスクの最終変更時刻(RFC 3339タイムスタンプとして)。
selfLink string このタスクを指すURL。このタスクを取得、更新、または削除するために使用されます。
parent string 親タスク識別子。トップレベルのタスクの場合、このフィールドは省略されます。このフィールドは読み取り専用です。「move」メソッドを使用して、タスクを別の親の下または最上位に移動します。
position string 同じ親タスクの下または最上位レベルにある兄弟タスク間のタスクの位置を示す文字列。この文字列が辞書式順序に従って別のタスクの対応する位置文字列よりも大きい場合、タスクは同じ親タスクの下(または最上位)の他のタスクの後に配置されます。このフィールドは読み取り専用です。「move」メソッドを使用して、タスクを別の位置に移動します。
notes string タスクを説明するメモ。オプション。
status string タスクのステータス。これは「needsAction」または「completed」のいずれかです。
due string タスクの期日(RFC 3339タイムスタンプとして)。オプション。期日は日付情報のみを記録します。タイムスタンプの時間部分は、期日を設定するときに破棄されます。APIを介してタスクの期限を読み書きすることはできません。
completed string タスクの完了日(RFC 3339タイムスタンプとして)。タスクが完了していない場合、このフィールドは省略されます。
deleted boolean タスクが削除されたかどうかを示すフラグ。デフォルトはFalseです。
hidden boolean タスクが非表示かどうかを示すフラグ。これは、タスクリストが最後にクリアされたときにタスクが完了とマークされていた場合に当てはまります。デフォルトはFalseです。このフィールドは読み取り専用です。
links object リンクのコレクション。このコレクションは読み取り専用です。
links.type string リンクの種類(例:「メール」)。
links.description string 説明。HTMLで話す:の間のすべて。
links.link string URL。

スプレッドシートに一覧で出してみる

とりあえずカレンダー取得のやつを使いまわしてスプレッドシートに出力してみた。

codelife.cafe

まぁこれができたからなんだって話だけどね。

例えばスプレッドシートに今週完了したタスクをまとめて日報や週報的なものを自動生成するとか?

f:id:maru0014:20201222212223p:plain

あー、マイナンバーカードのパスワードリセットやらなきゃ...。

スプレッドシートにタスクを一覧で出力するコード

// シート取得
const spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
const sheetToDo = spreadSheet.getSheetByName("ToDo");

/**
 * メニュー追加
 */
function onOpen() {
  const ui = SpreadsheetApp.getUi(); // UIクラス取得
  const menu = ui.createMenu("GAS"); // メニュー名セット
  menu.addItem("Google Tasks取得", "getToDo"); // 関数セット
  menu.addToUi(); // スプレッドシートに反映
}

/**
 * Google Tasksのリストからタスクを取得してスプレッドシートにセット
 */
function getToDo(){
  // シート2行名以下をクリア
  const lastRow = sheetToDo.getLastRow();
  const lastColumn = sheetToDo.getLastColumn();
  sheetToDo.getRange(2, 1, lastRow, lastColumn).clearContent();
  
  // 取得
  const table = getLists();
  
  if (table.length) {
    sheetToDo.getRange(2, 1, table.length, table[0].length).setValues(table); // シートに出力
    spreadSheet.toast(`${table.length}件のタスクを取得しました。`, 'Google Tasks 取得完了', 5); // 完了メッセージ表示
  } else {
    spreadSheet.toast('取得結果が0件です。', 'Google Tasks 取得完了', 5); // エラーメッセージ表示
  }
}

/**
 * Google Tasksからリストを取得
 * @return {Array} 取得結果の二次元配列
 */
function getLists(){
  const lists = Tasks.Tasklists.list().getItems();; // リストを全て取得
  
  let table = new Array(); // 配列初期化
  for (let i = 0; i < lists.length; i++) {
    table = table.concat(getTasks(lists[i])); // テーブルにデータを追加
  }
  
  return table
}

/**
 * Google Tasksのリストからタスクを取得
 * @param {TaskList} taskList 取得対象リストのオブジェクト
 * @return {Array} 取得結果の二次元配列
 */
function getTasks(taskList) {
  const tasks = Tasks.Tasks.list(taskList.id).getItems(); // リストからタスクを取得
  const table = new Array(); // 配列初期化
  
  if (tasks) {
    
    for (let i = 0; i < tasks.length; i++) {
      let row = new Array();
      row.push(taskList.title); // リスト名
      row.push(tasks[i].parent); // 親タスク
      row.push(tasks[i].title); // 件名
      row.push(tasks[i].status); // ステータス
      let due = tasks[i].due ? new Date(tasks[i].due) : "";
      row.push(due); // 期限
      let updated = tasks[i].updated ? new Date(tasks[i].updated) : "";
      row.push(updated); // 更新日時
      row.push(tasks[i].notes); // メモ
      
      Logger.log(row);
      
      table.push(row); // 配列に追加
    }
    
  } else {
    Logger.log(`リスト「${taskList.title}」にタスクはありません`);
  }
  
  return table
}

期限切れのタスクの期限を今日にしてSlackに通知してもらう

こうです。重要なメールを見逃しているときも教えてくれる我が家のベイマックスくんが更に強化されました。

f:id:maru0014:20201222212249p:plain

予めプロジェクトのプロパティにSlackのWebhookURLを保存しておきます。

参考: https://tonari-it.com/gas-properties-script-property/

f:id:maru0014:20201222212257p:plain

期限切れタスクの期限を今日に変更しつつSlack通知するコード

// 設定値をPropertiesServiceで取得
const slack_webhook_url = PropertiesService.getScriptProperties().getProperty('slack_webhook_url');

/**
 * Google Tasksのリストからタスクを取得して期限切れのタスクの期限を今日にしてSlack通知
 */
function checkDue(){
  const lists = Tasks.Tasklists.list().getItems();; // リストを全て取得
  
  lists.forEach(function (list){
    let tasks = Tasks.Tasks.list(list.id).getItems(); // リストからタスクを取得    
    for (let i = 0; i < tasks.length; i++) {
      if(new Date(tasks[i].due) < new Date()){
        tasks[i].due = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd') + 'T00:00:00.000Z';
        Tasks.Tasks.update(tasks[i], list.id, tasks[i].id);
        postSlack(`タスク ${tasks[i].title} は期限切れです。早くしてください。`);
      }
    }
  });
}

/**
 * 投稿メッセージを受け取ってSlackに投稿
 * @param {String} message 投稿するメッセージ
 */
function postSlack(message){
  const payload = JSON.stringify({
     "text" : message
  });
  
  const options = {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : payload
  };
  
  UrlFetchApp.fetch(slack_webhook_url, options); //Slackへ投稿
}

あとはこの関数のトリガーを設定して完成

f:id:maru0014:20201223090224p:plain

あとがき

本当は Google カレンダーとTasksから今やっていることを全部取得してGoogle Siteで 社内向けの 動的な自己紹介ページを生成したりとかしたいと思ったけど2時間ではたどり着かず脱線してベイマックスくんの機能追加になりましたw

引き続きGAS研究していきます。やっぱりGASは楽しいね。