🐄

Notionの変更通知をChatworkに飛ばす機構をGoogle Apps Scriptで作った話

2023/05/19に公開

最近NotionAPIと遊ぶのが好きな、Webエンジニアです。

ある日、上司から
「Notionで何かしら操作したのを外部に通知するwebhookってまだないんですね、、、ステータス切り替えたら勝手にチャットワークに通知するようにしたかった」
って言われました。

確かに、Notionが受け取るやつは結構作ってきたけど、Notionから外に飛ばすのは作ったことないな?
というわけで作りました!まとめておきます。

まずは参考サイトを掘る

https://note.com/mrtn/n/n6772194b0d74
こちらに大変お世話になりました。正直、これの送り先をChatWorkに変えたり、変更を検知したいプロパティをごにょごにょ操作しただけです!ありがとうございます。

ChatWork行きのはまだないなあ

で、Slackのものは見つけたけど、自分たちはSlackはほとんど使わないので、ChatWorkに行くやつがほしい!!って思ってがんばって探して、見つけられず(もしどっかにあったらごめんなさい!!)、もう作るか…と思いました。

仕様

現在、Notionのこういうページでチームタスクを管理しています。

あれよ、あれ。自分のタスクで自分で立てたページなので何も説明書きがないです(だからむしろそのままUPできてる説もある)が、本来はお師匠から仕事の指示がブワァーって書いてあります。

で、「ステータス」欄があって、このうち、お師匠が「終了」に変更したとき、10分以内にChatworkに通知が飛びます。

これが

こうなる!!!

なにがおこっているかというと、
① Notionデータベース検索

  • property「ステータス」が「終了」になっている
  • property「最終更新日時」が過去1週間以内
    の両方を満たすページを取得します(ステータスのところだけ作り変えてます)

② スプレッドシートから過去に通知されたnotion_idを取得して重複チェックします(ほぼまんまお借りしてます)

③ 重複がなければChatWorkに自動メッセージを飛ばします

④ 上記操作を10分に1回発火させるトリガーを設置

という流れでいろいろ起動してます。

各準備

Notionの準備

こちらがわかりやすいです。
https://note.com/mrtn/n/n6772194b0d74#39859537-eaf0-4a67-92c2-753004006924
インテグレーションを作って、データベースをinviteします。
あとはデータベースIDを取得します。データベース(個別ページでなくデータベースのページ)のURLにありますので、そちらを引っ張ってきます。
こちらがわかりやすいです。
https://zenn.dev/kou_pg_0131/articles/notion-api-usage#2.-データベースを作成する

Chatwork

こちらは公式です。APIトークンがあるのでこちらを取得します。
https://help.chatwork.com/hc/ja/articles/115000172402-APIトークンを発行する
あとは、通知を送るルームのidを取得します。
https://help.chatwork.com/hc/ja/articles/360000142942-ルームIDを確認する

Webhookとかは使わないです。もってくるだけ、割とお手軽。

Googleスプレッドシート

これは更新したNotionページのidをためていくのに使います。

GAS (Google Apps Script)

GASをガスガス書きます。ここまでで手に入れたid系も埋め込んでいきます。
ほとんどお借りしたものですが、自分が加工したところだけ書いても意味がわからないと思うのでそのまま転載させていただきます!!


// スプレッドシート選択
const spreadsheet = SpreadsheetApp.getActiveSheet();

// 投稿元のNotionデータベースIDとトークンを入力
const database_id = 'ここにNotionのデータベースidを入力';
const token = 'ここに、Notionのトークンを入力 インテグレーションからもってくるやつ、secret_なんちゃら';

function main() {
  var data = getNotionData();
  var past_data = getSpreadSheet();

  for (i = 0; i < data.results.length; i++) {
    var record = data.results[i];
    //投稿の重複チェック
    var duplicate = 0;
    for (j = 0; j < past_data.length; j++) {
      if (record.id == past_data[j]) {
        duplicate = duplicate + 1;
        continue;
      }
    }

    //重複してなければ、chatworkへ投稿
    if (duplicate == 0) {
      var title = record.properties.Name.title[0].plain_text;
      var url = record.url;
      var name = record.properties.担当者.multi_select[0].name;
      var time = record.properties.最終更新日時.last_edited_time;

      var jsonData = {
        "タイトル": title,
        "URL": url,
        "担当者": name,
        "更新日時": time
      }

      postToChatWork(jsonData);
      setSpreadSheet(record.id);
    }

  }
}

function getSpreadSheet() {
  var last_row = spreadsheet.getLastRow();
  if (last_row > 0) {
    var value = spreadsheet.getRange(1, 1, last_row, 1).getValues();
  } else {
    var value = "[[dummy_id]]";
  }
  return value;
}


//Notionデータを読み込む
function getNotionData() {
  const url = "https://api.notion.com/v1/databases/" + database_id + "/query";
  let headers = {
    "content-type": "application/json; charset=UTF-8",
    "Authorization": "Bearer " + token,
    "Notion-Version": "2021-08-16",
  }
  //ステータスが終了かつ最終更新日時が1週間以内
  let filter = {
    "filter": {
      "and": [
        {
          "property": "ステータス",
          "select": {
            "equals": "終了"
          }
        },
        {
          "property": "最終更新日時",
          "last_edited_time": {
            "past_week": {}
          }
        },

      ]
    }
  }
  let options = {
    method: "post",
    headers: headers,
    payload: JSON.stringify(filter)
  }
  let data = UrlFetchApp.fetch(url, options);
  data = JSON.parse(data);
  return data;
}

//chatworkへ通知する
function postToChatWork(jsonData) {
  var payload = JSON.stringify(jsonData);

  var obj = JSON.parse(payload);
  // Chatwork APIを入れます。
  var client = ChatWorkClient.factory({ token: "xxxxxxxxxx" });

  client.sendMessage({
    room_id: xxxxxxxxxx, // room IDを入れる intなので、''でくくらないです。

    body: "[info][title]自動メッセージ:タスク終了[/title]" + obj["タイトル"] + "\n\n担当者:" + obj["担当者"] + "\n\n詳細:" + obj["URL"] + "[/info]"

  });
}

//投稿したnotion_idをスプレッドシートへ書き込む
function setSpreadSheet(notion_id) {
  var last_row = spreadsheet.getLastRow();
  spreadsheet.getRange(last_row + 1, 1).setValue(notion_id);
}

これで、main関数にトリガーを設置して完成。

困ったこと

Notionのデータは基本jsonなんですな

そうなんですよ。特徴的なjsonファイルでして、きっちり合わせに行かないといかん。そりゃそうなんだけど。
だからGASで、Notionとスプシとか、今回みたいなNotionとChatWorkやらSlackやらと接続するときは、GAS上で変換操作などなどをしないといけない。

Notionのpropertyはそれぞれに規則があります。それに従って書く、と。
こちら、参考にさせていただきました。
https://www.6666666.jp/productivity/20210617/

今回で言えば、

function getNotionData() {
  const url = "https://api.notion.com/v1/databases/" + database_id + "/query";
  let headers = {
    "content-type": "application/json; charset=UTF-8",
    "Authorization": "Bearer " + token,
    "Notion-Version": "2021-08-16",
  }
  //ステータスが終了かつ最終更新日時が1週間以内
  let filter = {
    "filter": {
      "and": [
        {
          "property": "ステータス",
          "select": {
            "equals": "終了"
          }
        },
        {
          "property": "最終更新日時",
          "last_edited_time": {
            "past_week": {}
          }
        },

      ]
    }
  }
  let options = {
    method: "post",
    headers: headers,
    payload: JSON.stringify(filter)
  }
  let data = UrlFetchApp.fetch(url, options);
  data = JSON.parse(data);
  return data;
}

ここらへんが該当します。今回のメインターゲット「ステータス:終了」はセレクトプロパティなので、このように書いています。

	{
          "property": "ステータス",
          "select": {
            "equals": "終了"
          }
        },

「ステータス」が、selectプロパティである
中身は、 string"終了"と完全一致するものを拾う、という書き方です。
このへんのプロパティを書き換えることによって、Notionでのイベントを検知してChatWorkに飛ばすことは十分に可能だと思います。
かくして、お師匠のお願いを叶えたという、報告でしたー。

Discussion