blog

作成日 : 2023/09/20
更新日 : 2023/09/19

GASを利用し、RSS FeedをGoogle Chatに通知する実装例

目次

GASを利用し、RSS FeedをGoogle Chatに通知する実装例

   ・概要

   ・Google Chatに通知するための準備

      ■Webhookの作成

      ■Webhookで通知するスレッドの作成 (スキップ可能)

   ・Google SheetsでSpreadsheetを作成し、GASを入力

      ■Spreadsheetを作成し、情報を入力

      ■GAS

      ■トリガーの作成

   ・参考

概要

定期的にGAS (Google Apps Script)でRSS Feedを取得し、Google Chatに更新を通知する実装例を示す。 概要図は以下の通り。

Google Chatに通知するための準備

Webhookの作成

Google Chatに移動し、左上のワークスペース名をクリック、[アプリと統合]をクリックする。

[Webhookを追加]をクリックする。

[名前]、[アバターのURL]に入力し、保存をクリックする。

後で利用するので、右下のコピー用のアイコンをクリックして、WebhookのURLをコピーして、取っておく。

Webhookで通知するスレッドの作成 (スキップ可能)

作成したWebhook URLを利用して、ワークスペースにメッセージを送信する。 RSS Feedの更新を特定のスレッドに送信するため、一意なスレッドキーを生成し、指定する。

# UUIDなど同じスペース内で一意な任意の値を設定
THREAD_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
WEBHOOK_URL='https://chat.googleapis.com/v1/spaces/XXXXXXX_XXX/messages?key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

WEBHOOK_URL_REPLY_THREAD="${WEBHOOK_URL}&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD&threadKey=${THREAD_KEY}"

# textの値は、スレッドに対する最初のメッセージなので、適宜変更
curl -X POST "${WEBHOOK_URL_REPLY_THREAD}" \
  -H "Content-Type: application/json" \
  -d '{"text": "First message for thread"}'

Google SheetsでSpreadsheetを作成し、GASを入力

Spreadsheetを作成し、情報を入力

以下画像のようにGoogle Spreadsheetを作成し、収集するRSS Feedの情報を入力する。 シートタブは、「list」とする。

それぞれの列の意味は以下の表の通り。

列名定義必須備考
#文字列X入力済の場合に、通知対象になる。
そのため、その他の項目を入力後に、入力する。
title文字列プログラムでは利用しないため、自由に入力する。
rssUrlRSS FeedのURLX
lastDateGASのDate.parseで読み込み可能なフォーマット確認した入力は以下の通り。
– 2023-09-02T09:00:00+09:00
– Sun Sep 02 2023 09:00:00 GMT+0900 (Japan Standard Time)
RSS Feedを取得した最新の日付が格納される。
googleChatWebhookUrlWebhookの作成で作成したWebhookのURLX
googleChatThreadKeyWebhookで通知するスレッドの作成で生成したスレッドキーまたは、新規で生成するユニークなキーX

[拡張機能>Apps Script]をクリックし、GASの編集画面を開く。

GAS

Apps Scriptの画面に移動するので、プロジェクト名やファイル名を例えば以下のように変更する。

スクリプトとして、以下を入力する。

function main() {
  // 列名に対するSpreadsheetの列番号を表す
  const columnRowNum = 1;
  const columnRssUrl = 3;
  const columnLastDate = 4;
  const columnGoogleChatWebhookUrl = 5;
  const columnGoogleChatThreadKey = 6;

  // RSSを取得したときに、1回でもエラーが発生したら、true
  let didErrorHappen = false;

  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("list");

  for (let i = 2, rowNum = sheet.getRange(i, columnRowNum).getValue(); rowNum != ""; ++i, rowNum = sheet.getRange(i, columnRowNum).getValue()) {
    let rssUrl = sheet.getRange(i, columnRssUrl).getValue();

    let lastDateRange = sheet.getRange(i, columnLastDate);
    let lastDateStr = lastDateRange.getValue();
    let lastDate = new Date(lastDateStr);

    // 最後に更新した日付(lastDate)が存在しない場合に、lastDateを現在時刻から15分前にする
    if (Number.isNaN(lastDate.getTime())) {
      lastDate = new Date();
      lastDate.setMinutes(lastDate.getMinutes() -15);
      console.log(`lastDateが存在しません。${lastDate}以降のRSS Feedを調べます。`);
    }

    let googleChatWebhookUrl = sheet.getRange(i, columnGoogleChatWebhookUrl).getValue();
    let googleChatThreadKey = sheet.getRange(i, columnGoogleChatThreadKey).getValue();

    let rssInfo;

    try {
      rssInfo = getRssV2(rssUrl, lastDate);
    } catch (e) {
      console.error(e);
      didErrorHappen = true;
      continue;
    }

    rssInfo["messages"].forEach((message) => postGoogleChat(message, googleChatWebhookUrl, googleChatThreadKey));
    lastDateStr = rssInfo["lastDate"].toString();
    console.info(`[#${rowNum}] rssUrl: ${rssUrl}, lastDate: ${lastDateStr}`)
    lastDateRange.setValue(lastDateStr);
  }

  if (didErrorHappen) {
    throw Error("Error happens. Please see logs.");
  }
}


/**
 * Ver2.0のRSSからfromTime以降のpubDateのitemを取得する。
 * @param {string} rssUrl - 取得するRSSのURL
 * @param {Date} fromTime - fromTime以降のitemを取得
 * @returns {Object} Jsonデータのオブジェクト
 */
function getRssV2(rssUrl, fromTime) {
  let options = {
    "method" : "get",
    "contentType": 'application/xml; charset="UTF-8"',
  };
  const requestDate = new Date();
  let response = UrlFetchApp.fetch(rssUrl, options);

  let lastDate = new Date(response.getHeaders()["Date"]);

  // 万が一、レスポンスヘッダにDateフィールドが含まれない場合に、リクエスト時の時間を入れる
  if (Number.isNaN(lastDate.getTime())) {
    console.info(`レスポンスヘッダにDateフィールドを含みません。[url: ${rssUrl}]`)
    lastDate = requestDate;
  }
  let xmlText = response.getContentText();

  // XMLを解析
  let document = XmlService.parse(xmlText);
  let root = document.getRootElement();
  let rssVersion = root.getAttribute("version").getValue();

  if(rssVersion != "2.0") {
    throw new Error(`対応していない形式です。[rss version: ${rssVersion}]`);
  }

  // RSSフィード内の記事を取得
  let items = root.getChildren("channel")[0]
                  .getChildren("item");

  let filteredItems = items.filter((item) => {
    let pubDate = new Date(item.getChild("pubDate").getText());
    return pubDate.getTime() >= fromTime.getTime();
  })
  filteredItems.sort((item1, item2) => {
    let item1PubDate = new Date(item1.getChild("pubDate").getText());
    let item2PubDate = new Date(item2.getChild("pubDate").getText());
    return item1PubDate.getTime() - item2PubDate.getTime();
  })

  let messages = filteredItems.map((item) => {
    let pubDate = new Date(item.getChild("pubDate").getText());
    let title = item.getChild("title").getText();
    let link = item.getChild("link").getText();
    return `${pubDate}\ntitle: ${title}\nlink: ${link}`;
  });

  /**
   * @type {Object}
   * @property {Date} lastDate - 取得時の時間。通常、RSS Feedに対するレスポンスヘッダのDateフィールドの時間。Dateフィールドがない場合、リクエスト時の時間。
   * @property {string[]} messages - RSS Feedのitem要素の一部をテキストにしたものの配列。pubDateで昇順ソートしている。
   */
  return {"lastDate": lastDate, "messages": messages};
}


/**
 * Web Hookを利用して、Google Chatのスレッドにmessageを送信する。
 * @param {string} message - Google Chatのスレッドに投げるmessage
 * @param {string} webHookUrl - Google Chatに送信するためのWeb HookのURL
 * @param {string} threadKey - Google ChatのThread Key
 */
function postGoogleChat(message, webHookUrl, threadKey) {
  let url = `${webHookUrl}&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD&threadKey=${threadKey}`;
  let payload = {
    "text": message
  };
  let options = {
    "method" : "post",
    "contentType": "application/json",
    // Convert the JavaScript object to a JSON string.
    "payload" : JSON.stringify(payload)
  };
  let response = UrlFetchApp.fetch(url, options);
  if (response.getResponseCode() != 200) {
    throw Error(`failed to send message to google chat.[response code: ${response.getResponseCode()}][message: ${message}][webHookUrl: ${webHookUrl}][threadKey: ${threadKey}]`);
  }
}

トリガーの作成

左上の時計アイコンにマウスオーバーをし、[トリガー]をクリックする。

右下の[トリガー]をクリックする。

例えば、以下のように設定し、[保存]をクリックする。 [時間ベースのトリガーのタイプを選択]、[時間の間隔を選択]、[エラー通知設定]など、自由に変更する。

参考

着信 Webhook を使用して Google Chat にメッセージを送信する

https://developers.google.com/chat/how-tos/webhooks?hl=ja#start_or_reply_to_a_message_thread

GASの概要

https://developers.google.com/apps-script/overview?hl=ja

GAS (Google Apps Script) のリファレンス

https://developers.google.com/apps-script/reference?hl=en