Notionあれやこれや

情報一元化ツールNotion遊泳のTips

【超解説】Notionでランダムな1件をスマホに定期通知する方法❶LINE通知

以下の記事でざっくり紹介した方法❶Google Apps Script (GAS) + LINE Notifyの方法について、詳しくご紹介したいと思います。

aki-toto.hateblo.jp

以前、私もNotion×GAS×LINE Notifyの記事を上げさせていただきましたが、今回はもう少しシンプルな処理になりますので、GASのコーディング内容もご説明出来たらなと思います。 わりとモリモリです。既知の箇所があれば適宜お飛ばしください🎈

まずは実現したい内容のイメージ

通知元のデータベース画面

このデータベースを対象にランダム1件を取得・通知したい

LINE通知画面

LINE Notify通知のイメージ画面

処理の全体

処理の全体イメージ

実現手順

1️⃣ 処理に必要な各種キーを取得しておく

💼 必要なもの

  • Googleアカウント
  • Notionアカウント
  • LINEアカウント
  • 【Notion】インテグレーションシークレット
  • 【Notion】データベースID
  • 【LINE Notify】アクセストーク

今回は、各アカウント(Google, Notion, LINE)の登録方法は割愛しますね。

【Notion】インテグレーションシークレット

❶ 対象データベースがあるNotionアカウントでログインしておく

❷ Notionインテグレーションシークレット作成画面アクセス

 🔗 https://www.notion.so/my-integrations

❸ 新しいインテグレーションシークレットを作成

❹ インテグレーションシークレットを控えておく(メモ帳等に一旦貼付)

【Notion】データベースID

❶ 対象のリストがあるNotionデータベースをブラウザで開く

❷ ブラウザのアドレスバーから、データベースIDを抜き出して控えておく(メモ帳などに一旦貼付)

https://www.notion.so/{workspace_name}/{database_id}?v={view_id}

参照元:公式Developer向けサイト

【LINE Notify】アクセストーク

❶ LINE Notifyのログイン画面へアクセス

 🔗 https://notify-bot.line.me/ja

❷ ログインし、マイページへ遷移する

❸ 「トークンを発行する」ボタンをクリック

トークン発行画面で、必要事項を入力・選択して発行

- トークン名(LINE通知メッセージの1行目に表示されます) - 通知先のトークルーム(今回は自分だけに送るNotifyを選択) ※他グループトークルームも選択可

❺ 発行されるトークンを控えておく(メモ帳などに一旦貼付)

2️⃣ データベースにインテグレーションをコネクト追加

❶ 先ほどの手順 1️⃣ で作成したインテグレーションを、対象データベースにコネクト追加

選択したらコネクト追加完了

接続先の一覧には、インテグレーション作成時に付けた名前で表示されます。表示されない場合は、ブラウザのリロードを何度か試みましょう

3️⃣ GASにてコーディング

Google Apps Scriptのログイン画面へアクセス

 🔗 https://www.google.com/script/

Googleアカウントログイン済みなら、上記リンクでこのページは表示されないかも

❷ 新しいプロジェクトを作成

複数のGoogleアカウントがある場合は、アカウントも希望のものか確認しましょう

❸ コード.gsを開き、初期記述コードを消す

❹ コード.gsに後述のサンプルコードをペースト

サンプルコードおよび説明は結構長いですが…下にございます👇

今回のサンプルコードは、対象のNotionデータベースから、タイトルプロパティに登録された文字(今回はプロパティ種類はタイトル、プロパティ名は「言葉」)をランダムに1件取得→LINE Notifyへ送信するという処理です。

サンプルコードについて:少し冗長的ではありますが、説明しやすいよう関数(function~)をかなり切り分けています。各処理内容をコメント(/* ~ *///)で補足していますので、理解の参考になれば幸いです。

※エラーハンドリングは省略しています、悪しからず…🙃

サンプルコードを「コード.gs」にコピペして、定数定義の3項目をご自身の値に書き換えれば(値を囲むシングルクォート '' は消さない)OKです。

/**
 * 定数定義(`const`宣言)
 * 
 * - 各処理で使用する必要な各値をあらかじめ定数に定義
 */
const NOTION_API_KEY = '値をここに入れる'; // Notion APIインテグレーションシークレット
const DATABASE_ID = '値をここに入れる'; // NotionデータベースID
const LINE_NOTIFY_API_TOKEN = '値をここに入れる'; // LINE Norify APIアクセストークン

/**
 * メイン処理
 * 
 * - 後述【1】~【5】の処理を統合した実行関数
 * - コーディング後に、この関数をトリガーとした定期実行の設定を行う
 */
function sendNotification() {
  // 【1】Notion APIを通じ、Notionの指定データベースからデータを取得
  const data = getAllData();

  // 【2】取得データから欲しい情報(タイトル)のみ抽出
  const words = getAllWords(data);
  
  // 【3】【2】のリストから、ランダムに1件抽出
  const randomWord = getRandomWord(words);
  
  // 【4】通知文の設定
  const message = setNotificationMessage(randomWord);

  // 【5】LINE Notify APIを通じ、指定トークルームへ【4】を送信
  sendLineMessage(message);
}

/**
 * 【1】Notion APIを通じ、Notionの指定データベースからデータを取得
 * 
 * - `UrlFetchApp.fetch`メソッドを使用して、指定したURL(`url`)にPOSTリクエストを送信
 * - `url`内の`DATABASE_ID`は冒頭で定義した定数(データベースID)
 * - リクエストの際はNotionインテグレーションシークレット(定数`NOTION_API_KEY`)で認証しデータ取得
 * - `url`や`method`は、操作内容によって異なる(取得、追加、データベースかページかなど)
 * - 取得データ(JSON形式)をパース(GASで使える形に変換)して返す
 */
function getAllData() {
  const url = `https://api.notion.com/v1/databases/${DATABASE_ID}/query`;
  const options = {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${NOTION_API_KEY}`,
      'Content-Type': 'application/json',
      'Notion-Version': '2022-06-28',
    },
    // -- もしフィルターをかけたい場合はここに追加(☆記事にて後述)
  };
  const response = UrlFetchApp.fetch(url, options);
  const data = JSON.parse(response.getContentText());

  return data;
}

/**
 * 【2】取得データから欲しい情報(タイトル)のみ抽出
 * 
 * - 取得したデータを`map`メソッド(配列の繰り返し操作・新たに配列生成)を使って
 *   各結果から「言葉」というタイトルプロパティの文字を抽出・配列へ格納して返す
 *   ★プロパティ名は、ご自身のデータベースに合わせて変更してください
 * - プロパティの種類によって`result.properties['言葉'].`以降の記述は異なる
 */
function getAllWords(data) {
  const pageTitles = data.results.map(
    (result) => result.properties['言葉'].title[0].text.content
  );

  return pageTitles;
}

/**
 * 【3】【2】のリストから、ランダムに1件抽出
 * 
 * - 抽出したタイトルの配列から、`Math.random`メソッドを使用してランダムなインデックスを生成
 * - そのインデックスに位置する1つの単語を選択して返す
 */
function getRandomWord(words) {
  const index = Math.floor(Math.random() * words.length);
  const randomWord = words[index];
  return randomWord;
}

/**
 * 【4】通知文の設定
 * 
 * - 渡されたランダムな言葉を通知文の形式に整えて返す
 * - `\n` はエスケープ文字の一種で、文字列内に挿入するとその位置で改行される
 */
function setNotificationMessage(randomWord) {
  const message = `\n${randomWord}`;
  return message;
}

/**
 * 【5】LINE Notify APIを通じ、指定トークルームへ【4】を送信
 * 
 * - `UrlFetchApp.fetch`メソッドを使用して、指定したURL(`url`)にPOSTリクエストを送信
 * - リクエストの際はLINE Notify APIトークン(定数`LINE_NOTIFY_API_TOKEN`)で認証しデータ送信
 */
function sendLineMessage(message) {
  const notifyUrl = 'https://notify-api.line.me/api/notify';
  const options = {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${LINE_NOTIFY_API_TOKEN}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    payload: {
      message: message,
    },
  };

  UrlFetchApp.fetch(notifyUrl, options);
}

🐍 補足

もしNotionデータベースからデータを取得する際、フィルターを適用したい場合は、上記サンプルコードの // -- もしフィルターをかけたい場合はここに追加(☆記事内にて補足) の箇所に、以下記述を追加してください。

    // -- もしフィルターをかけたい場合はここに追加(☆)
    payload: JSON.stringify({
      filter: {
        property: '対象外', // Notionデータベースのプロパティ名
        checkbox: { // プロパティの種類
            equals: false // 条件(この場合は、チェックボックスに未チェックのデータのみ)
        }      
      },
    }),

フィルターしたいプロパティ種類や、複数のフィルター条件を組み合わせたい場合などによって、記述内容が異なります。 今回は網羅しませんが、詳細は公式Developer向けリファレンスで確認できます🔍

❺ 「実行」をクリックして権限を承認(初回のみ必要)

サンプルコードを貼り付けた後、「保存」クリック(または [Ctrl + S])も忘れずに

❻ 権限の承認

アカウント選択後のモーダルで、左下の「詳細」をクリック

左下の「○○○に移動」をクリックし、確認モーダルで「許可」をクリック

❼ 改めて実行してエラーの有無・LINE通知されたか確認

問題なく実行されれば、エラーメッセージは表示されない

正常にLINE Notifyへ通知されたことを確認

🤢 よくあるエラー

実行時に問題があれば画像のようにエラーメッセージが表示されます

エラーメッセージの内容はほとんど英語ですが、落ち着いて単語を拾っていくと原因の予測が立ちやすいです。以下にありがちエラーをまとめました。

# コード.gsで保存したときのエラーメッセージ:
構文エラー: SyntaxError: Unexpected end of input 行: 115 ファイル: コード.gs

# 原因:
全角スペースが混じっていたり、閉じカッコ`}`や文末コロン`;`が抜けている、誤植など正しい構文になっていない場合
# コードgs.で実行したとき実行ログのエラーメッセージ:
Exception: Request failed for https://api.notion.com returned code 401. Truncated server response: {"object":"error","status":401,"code":"unauthorized","message":"API token is invalid.","request_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"} (use muteHttpExceptions option to examine full response)

# 原因:
インテグレーションシークレット(NOTION_API_KEY)の値に誤りがある
# コードgs.で実行したとき実行ログのエラーメッセージ:
Exception: Request failed for https://api.notion.com returned code 400. Truncated server response: {"object":"error","status":400,"code":"validation_error","message":"path failed validation: path.database_id should be a valid uuid, instead was `\... (use muteHttpExceptions option to examine full response)

# 原因:
データベースID(DATABASE_ID)の値に誤りがある
# コードgs.で実行したとき実行ログのエラーメッセージ:
Exception: Request failed for https://notify-api.line.me returned code 401. Truncated server response: {"status":401,"message":"Invalid access token"} (use muteHttpExceptions option to examine full response)

# 原因:
LINE Notify アクセストークン(LINE_NOTIFY_API_TOKEN)の値に誤りがある
# コードgs.で実行したとき実行ログのエラーメッセージ:
Exception: Request failed for https://api.notion.com returned code 404. Truncated server response: {"object":"error","status":404,"code":"object_not_found","message":"Could not find database with ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Make sur... (use muteHttpExceptions option to examine full response)

# 原因:
コネクト追加できていない

4️⃣ GASにてトリガーの設定

❶ トリガーを追加

左隅の時計マーク(トリガー)メニューをクリックし、「トリガーを追加」ボタンをクリック

❷ トリガーで通知タイミングを設定

画像のように設定したら、保存ボタンをクリック
※「時刻を選択」項目:時間帯指定のため、時間ぴったりの実行ではありません。表示されている時間帯内の実行となります

トリガー一覧に表示されていればOK


おわり

いかがでしたか…画像を多めにしたらかなりの量になってしまった感…。

サンプルコードはご自由にカスタムください。もっと関数のことや、取得データのフィルター詳細も説明できたらと思ったのですが、根尽きてしまったので煩雑になりそうでしたので、今回はこのあたりで留めたいと思います。

参考になったなぁと感じていただけたら、スター🌟やフォローいただけると励みになります!

長文お読みいただきまして、ありがとうございました🥰

次回は Notionでランダムな1件をスマホに定期通知する3つの方法【全4回③iOSリマインダー通知】の記事をまとめたいと思います。がんばろう🏋️‍♀️