Chatime

Chatworkのメッセージをスプレッドシートに保存しよう その2 – 表示を整え新着メッセージのみを定期的に追加する

Chatworkのフリープランのサービス内容が変更になり、これまで無制限に確認できていた過去メッセージの表示が、直近40日以内に投稿された最新5,000件のメッセージとなりました。

これを機に、APIの勉強をかねてGoogle Apps ScriptでChatworkのメッセージをスプレッドシートに取得するスクリプトを書いてみたいと思います。

尚、過去メッセージ全部をAPIで取得することはそもそもChatwork APIの仕様上の制限でできません。APIで取得できるのは最新の100件のみです。

前回は、「Chatwork APIに接続して最新の100件のメッセージをスプレッドシートに書き出す」までを紹介しました。

スプレッドシートに書き出された日時を変換する

前回の記事でのスクリプトだとsend_time(送信日時)、update_time(更新日時)が一般的な日付の形式ではありません。

これを通常の日時表示にすべく、スクリプト内でDateオブジェクトに変換します。

ベースとなるスクリプトは、再帰関数を利用したスクリプトでなく、前回の記事の最後で紹介したシンプル版のスクリプトです。

function demoMessages2SheetDateFix() {
  
  // トークン・ルームIDはプロパティストアに
  const token = PropertiesService.getUserProperties().getProperty('CHATWORK_TOKEN');
  const roomId = PropertiesService.getScriptProperties().getProperty('CHATWORK_ROOM_ID');
  const url = `https://api.chatwork.com/v2/rooms/${roomId}/messages?force=1`;

  const params = {
    method: 'GET',
    headers: {
      'Accept': 'application/json',
      'x-chatworktoken': token
    }
  };

  const response = UrlFetchApp.fetch(url, params);
  const json = response.getContentText();
  const aryMessage = JSON.parse(json); // メッセージオブジェクトを格納した配列

  // プロパティ名を指定して各値を取り出した新しい二次元配列を生成
  const ary2D = aryMessage.map(message => {

    // 更新時間が0(未更新)の場合は空文字列にそれ以外はUNIX時間をDateオブジェクトに
    let updateTime = new Date(message.update_time * 1000);
    if (message.update_time === 0) { updateTime = '' };

    return [
      message.message_id,
      message.account.account_id,
      message.account.name,
      message.account.avatar_image_url,
      message.body,
      new Date(message.send_time * 1000), // UNIX時間の単位は「秒」Dateオブジェクトは「ミリ秒」単位 *1000
      updateTime
    ];
  });

  // ヘッダー項目を一次元配列で指定して二次元配列の要素の先頭に追加する
  ary2D.unshift([
    'message_id',
    'account_id',
    'name',
    'avatar_image_url',
    'body',
    'send_time',
    'update_time']);

  // アクティブなスプレッドシートのシート名'Log'のシートを取得して二次元配列の値をシートに追加する
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheetLog = ss.getSheetByName('Log');
  const range = sheetLog.getRange(1, 1, ary2D.length, ary2D[0].length);
  range.setValues(ary2D);
}

まずレスポンスで返ってくる謎の数字(例:1643108966)をDateオブジェクトに変換します。答えを言うと、この謎の数字はUNIX時間という日時形式です。

UNIX時間の単位は「秒」、Dateオブジェクトは「ミリ秒」単位と単位の違いがあるため、UNIX時間の数値を1000倍することで、Dateオブジェクトが生成できます。

new Date(message.send_time * 1000)

続いて更新時間を表すupdate_timeプロパティですが、このプロパティの値は、あったりなかったりします。そのため条件分岐が必要になります。

// 更新時間が0(未更新)の場合は空文字列に、それ以外はUNIX時間をDateオブジェクトに
let updateTime = new Date(message.update_time * 1000);
if (message.update_time === 0) { updateTime = '' };

実行すると無事、認識できる日付として出力されました。

新着のメッセージだけ定期取得したい

ここまでのスクリプトでは、テストのために常に最新の100メッセージを取得してシートに出力していました。しかし、実務上は新着のメッセージだけをトリガーによる定期実行で、シートの最終行に追加していく運用のほうが良いでしょう。

ということで、まずリクエストURL内のクエリパラメータであるforceを0にして、APIで未取得のメッセージのみを取得するようにします。

詳しくは公式ドキュメントを参考にしてください。

const url = `https://api.chatwork.com/v2/rooms/${roomId}/messages?force=0`;

また、定期実行のタイミングで新着メッセージがない場合もあります。これも公式ドキュメントを確認すると、新着メッセージが無い時には、RESPONSE BODYが空文字列になります。

これを利用して新着メッセージがない場合は処理終了させます。

// 新着メッセージがない場合は処理終了
if (json === '') { return };

APIリクエストでエラーが発生した場合も準備しておきます。エラーが発生した時にはerrorsというプロパティにエラー内容の文字列が要素の配列が格納されています。

このerrorsプロパティの有無で条件分岐し、エラーが発生した場合は、エラー履歴シートに記録日時とエラー内容を追加して処理を終了させます。

// エラーの場合はエーラ履歴シートにログを残して終了
if (json.errors) {
  const errors = json.errors.join(); // 配列の要素を連結
  const now = new Date(); // 記録日時
  const sheetErrors = ss.getSheetByName('Errors');
  sheetErrors.appendRow(now, errors); // エラーログを追加 
  return
};

アクティブなSpreadsheetオブジェクトは、後でメッセージをログ記録用シートに追加する際にも使用するので、Spreadsheetオブジェクトの取得スクリプトは、このタイミングでスクリプトの冒頭に移動させます。

const ss = SpreadsheetApp.getActiveSpreadsheet(); // 冒頭に移動

エラー内容の文字列が配列で返ってくるため、これをArrayオブジェクトに対して使用できるjoin()メソッドを利用して単一の文字列に結合します。

join() メソッドは、配列 (または配列風オブジェクト) の全要素を順に連結した文字列を新たに作成して返します。区切り文字はカンマ、または指定された文字列です。配列にアイテムが一つしかない場合は、区切り文字を使用せずにアイテムが返されます。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/join

新着メッセージがあった場合は、過去のメッセージのログは残しつつ、シートの最終行以降に追加したいです。そのためシートの最終行の行番号をgetLastRow()メソッドで取得してデータを追加する範囲を指定します。

const sheetLog = ss.getSheetByName('Log');
const lastRow = sheetLog.getLastRow(); // 最終行番号を取得
const range = sheetLog.getRange(lastRow + 1, 1, ary2D.length, ary2D[0].length);
range.setValues(ary2D);

上記のスクリプトを追加することで、最終行以降に取得したメッセージの情報追加するようになったので、以下のヘッダー項目の定義は不要になりました。この部分は削除します。

  // ヘッダー項目を一次元配列で指定して二次元配列の要素の先頭に追加する
  ary2D.unshift([
    'message_id',
    'account_id',
    'name',
    'avatar_image_url',
    'body',
    'send_time',
    'update_time']);

ということで、一旦完成したコード全体像はこちらです。

function demoNewMessages2Sheet() {
  // アクティブなスプレッドシートを取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  // トークン・ルームIDはプロパティストアに
  const token = PropertiesService.getUserProperties().getProperty('CHATWORK_TOKEN');
  const roomId = PropertiesService.getScriptProperties().getProperty('CHATWORK_ROOM_ID');
  const url = `https://api.chatwork.com/v2/rooms/${roomId}/messages?force=0`;

  const params = {
    method: 'GET',
    headers: {
      'Accept': 'application/json',
      'x-chatworktoken': token
    }
  };

  const response = UrlFetchApp.fetch(url, params);
  const json = response.getContentText();

  // 新着メッセージがない場合は処理終了
  if (json === '') { return };

  // エラーの場合はエーラ履歴シートにログを残して終了
  if (json.errors) {
    const errors = json.errors.join(); // 配列の要素を連結
    const now = new Date(); // 記録日時
    const sheetErrors = ss.getSheetByName('Errors');
    sheetErrors.appendRow(now, errors); // エラーログを追加 
    return
  };

  // 新着メッセージがある場合のみ以下の処理が実行される
  const aryMessage = JSON.parse(json); // メッセージオブジェクトを格納した配列

  // プロパティ名を指定して各値を取り出した新しい二次元配列を生成
  const ary2D = aryMessage.map(message => {

    // 更新時間が0(未更新)の場合は空文字列にそれ以外はUNIX時間をDateオブジェクトに
    let updateTime = new Date(message.update_time * 1000);
    if (message.update_time === 0) { updateTime = '' };

    return [
      message.message_id,
      message.account.account_id,
      message.account.name,
      message.account.avatar_image_url,
      message.body,
      new Date(message.send_time * 1000), // UNIX時間の単位は「秒」Dateオブジェクトは「ミリ秒」単位 *1000
      updateTime
    ];
  });

  // アクティブなスプレッドシートのシート名'Log'のシートを取得して二次元配列の値をシートの最終行に追加する
  const sheetLog = ss.getSheetByName('Log');
  const lastRow = sheetLog.getLastRow(); // 最終行番号を取得
  const range = sheetLog.getRange(lastRow + 1, 1, ary2D.length, ary2D[0].length);
  range.setValues(ary2D);
}

このあと少しアレンジを加えていきます。

アイコンのURLは認証無しでアクセスできちゃう

今回Chatwork APIから取得したメッセージに関するデータの中で、ちょっと問題かもと思ったのが、avatar_image_urlプロパティで取得できるアバター(アイコン)画像のURLです。

実はこのURL、ChatworkにログインしていないブラウザからでもURLを直接指定するとアクセスできちゃいます。まあ、このURLを外部の人に知られることはあまりないと思いますし、アバターの画像を見られたところで…ということではありますが。

ということで、せっかく使えるならスプレッドシートのIMAGE関数で表示させちゃえとスクリプトに組み込んでみました。

'=IMAGE("' + message.account.avatar_image_url + '",4,40,40)'

IMAGE関数の引数で調整するアバター画像を表示させるサイズなどはお好みで調整してください。

これで、正真正銘の完成です。

/* Chatworkの新着メッセージをスプレッドシートに書き出す関数 */

function chatworkMessagesLogger() {
  // アクティブなスプレッドシートを取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  // トークン・ルームIDはプロパティストアに
  const token = PropertiesService.getUserProperties().getProperty('CHATWORK_TOKEN');
  const roomId = PropertiesService.getScriptProperties().getProperty('CHATWORK_ROOM_ID');
  const url = `https://api.chatwork.com/v2/rooms/${roomId}/messages?force=0`;

  const params = {
    method: 'GET',
    headers: {
      'Accept': 'application/json',
      'x-chatworktoken': token
    }
  };

  const response = UrlFetchApp.fetch(url, params);
  const json = response.getContentText();

  // 新着メッセージがない場合は処理終了
  if (json === '') { return };

  // エラーの場合はエーラ履歴シートにログを残して終了
  if (json.errors) {
    const errors = json.errors.join(); // 配列の要素を連結
    const now = new Date(); // 記録日時
    const sheetErrors = ss.getSheetByName('Errors');
    sheetErrors.appendRow(now, errors); // エラーログを追加 
    return
  };

  // 新着メッセージがある場合のみ以下の処理が実行される
  const aryMessage = JSON.parse(json); // メッセージオブジェクトを格納した配列

  // プロパティ名を指定して各値を取り出した新しい二次元配列を生成
  const ary2D = aryMessage.map(message => {

    // 更新時間が0(未更新)の場合は空文字列にそれ以外はUNIX時間をDateオブジェクトに
    let updateTime = new Date(message.update_time * 1000);
    if (message.update_time === 0) { updateTime = '' };

    return [
      message.message_id,
      message.account.account_id,
      message.account.name,
      '=IMAGE("' + message.account.avatar_image_url + '",4,40,40)',
      message.body,
      new Date(message.send_time * 1000), // UNIX時間の単位は「秒」Dateオブジェクトは「ミリ秒」単位 *1000
      updateTime
    ];
  });

  // アクティブなスプレッドシートのシート名'Log'のシートを取得して二次元配列の値をシートの最終行に追加する
  const sheetLog = ss.getSheetByName('Log');
  const lastRow = sheetLog.getLastRow(); // 最終行番号を取得
  const range = sheetLog.getRange(lastRow + 1, 1, ary2D.length, ary2D[0].length);
  range.setValues(ary2D);
}

実行するとChatworkのルームに新しいメッセージがあった場合にログ記録用のシートにメッセージが追加されます。

これを1週間に1回などの頻度でトリガー実行を設定しておきましょう。

シートの表示を整えます

今回はAPIで取得できる情報全てをスプレッドシートに書き出していますが、message_idやaccount_idなどは特に必要ない情報なので、スプレッドシート上で列を非表示にすると良いでしょう。

また、送信日時などを列の左側に持ってきたいなどの列の順番を入替えたい場合は、追加する配列の要素の順番を調整してください。

// 送信日時, 更新日時, ユーザー名, アバター, メッセージ, メッセージID, アカウントID の順の例
return [
  new Date(message.send_time * 1000),
  updateTime,
  message.account.name,
  '=IMAGE("' + message.account.avatar_image_url + '",4,40,40)',
  message.body,
  message.message_id,
  message.account.account_id
];

交互の背景色やセル内での折返し表示などを使い、表示を整えてみるとこんな感じになります。

アイコンはモザイクかけても生々しいので…

いい感じですね。

おわりに

今回は、「表示を整え新着メッセージのみを定期的に追加する」を紹介しました。

今回のハイライトは、UNIX時間を1000倍してDateオブジェクトに変換するポイントでした。またIMAGE関数を組み込むのもユーザーフレンドリーなシートになってオススメです。

これで「Chatworkのメッセージをスプレッドシートに保存しよう」は完結です。Chatworkのフリープランのサービス内容変更にお困りの方のお役に立てれば嬉しいです。

もし実際に活用された方がいらっしゃったらTwitterなどで感想いただけると嬉しいです。

シリーズ目次

  1. Chatworkのメッセージをスプレッドシートに保存しよう その1 – 最新の100件のメッセージを書き出す
  2. Chatworkのメッセージをスプレッドシートに保存しよう その2 – 表示を整え新着メッセージのみを定期的に追加する
  3. 【まとめ】コピペでOK!?Chatworkのトーク履歴をGoogleスプレッドシートに記録するGAS

Google Apps Scriptを勉強したい方へ

この記事を見て、GASを勉強したいなと思われた方はぜひノンプログラマーのためのスキルアップ研究会(通称 ノンプロ研)にご参加ください。私も未経験からこの学習コミュニティに参加し、講座を受講したことでGASが書けるようになりました。

学習コミュニティ「ノンプログラマーのためのスキルアップ研究会」

挫折しがちなプログラミングの学習も、コミュニティの力で継続できます。ノンプロ研でお待ちしております!

Amazon欲しい物リスト公開しています。

開発者のモチベーションアップのためにAmazon欲しい物リストを公開しております。役に立ったよ!という方の感謝の気持ちで何かいただけるのであれば嬉しいです笑

Amazon欲しい物リスト

タグ: ,
Share on:
Previous Post
eduardo-soares-utWyPB8_FU8-unsplash
GAS活用法

楽天カードの「ご請求予定金額」お知らせメールから、Todoistに「楽天銀行への入金」タスクを作成したい

Next Post
チャット
GAS活用法

Chatworkのメッセージをスプレッドシートに保存しよう その1 – 最新の100件のメッセージを書き出す