Chatworkのフリープランのサービス内容が変更になり、これまで無制限に確認できていた過去メッセージの表示が、直近40日以内に投稿された最新5,000件のメッセージとなりました。
昨今の円安でサーバー代がかさむ…みたいな話があるみたいで、まあしょうがないよねという気持ちです。
私としてはSlack派なので、あまり直接的な影響はないのですが、自社で顧問をお願いしている税理士事務所さんとの連絡がChatworkなので、過去メッセージが見れなくなると困るかな…いやあんまり困らないかも…となっています。
40日以内にやりとりするメッセージ数は10件もないので、これを定期バックアップすべきか?すら悩みますが、APIの勉強をかねてGoogle Apps ScriptでChatworkのメッセージをスプレッドシートに取得するスクリプトを書いてみたいと思います。
尚、過去メッセージ全部をAPIで取得することはそもそもChatwork APIの仕様上の制限でできません。APIで取得できるのは最新の100件のみです。
公式ドキュメントで認証方式を確認する
APIの定番、認証方式を公式ドキュメントで確認します。ドキュメントによるとAPIトークンを利用する方式とのことです。
- Chatwork画面右上の「利用者名」をクリック
- メニューの「サービス連携」を選択
- 左側のメニューから「APIトークン」を選択
- APIトークンを控える
の手順です。
簡単ですね。
メッセージ取得のエンドポイントを確認してテストリクエスト送信
続いてメッセージ取得のエンドポイントを確認してリクエスト送信します。
公式ドキュメントのエンドポイント一覧を確認します。左側にずらっとエンドポイントが列挙されていますので、messagesを選択します。
チャットのメッセージ一覧を取得する GETという項目がありますのでそちらをクリックすると右側にリファレンスが表示されます。
メッセージ取得のリクエストのためにはルームIDが必要のようです。ルームIDはChatworkの該当ルームにアクセスした際のURLの末尾に記載があります。
通常であれば、ここでサンプルリクエストを送るGASを書くのですが、Chatworkは必要な情報を入力してTry It!ボタンを押すと、テストリクエストを送信してレスポンスが返してくれます。
ということで無事レスポンスが返ってきました。
GASに書き換える
今度はこのテストリクエストをGASに書き換えます。いつもは、Convert curl commands to codeというサイトでcurlコマンドをJavaScriptに変換していますが、Chatworkのリファレンスにはリクエストサンプルを各言語に変換してくれる機能があります。
LANGUAGEの欄の右側の縦点アイコンを選択し、今回はJavaScriptを選択します。
テストリクエストのコードは以下のようになりました。ルームIDとAPIトークンは書き換えています。
const options = {
method: 'GET',
headers: {
Accept: 'application/json',
'x-chatworktoken': '***API_TOKEN***'
}
};
fetch('https://api.chatwork.com/v2/rooms/**ROOM_ID**/messages?force=1', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
これをGASに読み替えて、リクエストを送信しレスポンスからメッセージ一覧の配列を取得します。トークン類は事前にプロパティストアに格納しました。
追記:APIトークンはユーザープロパティに格納しよう
記事公開時には特に細かく触れていなかったですが、APIトークンは第三者に知られたくないため、必ずユーザープロパティに格納しましょう。
スクリプトプロパティの場合だと、シートを編集権限で共有した場合に自分のAPIトークンを第三者が確認できてしまいます。
function demoGetMessagesFromRoom() {
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);
console.log(aryMessage);
}
注意点としては、ブラウザ上で一度テストリクエストを送信した直後にGASで取得しようとしても、force = 0のままだと取得できる新規のメッセージがありません。初回は、force = 1として強制的に最新の100件のメッセージを取得します。
force integer
https://developer.chatwork.com/reference/get-rooms-room_id-messages
1を指定すると未取得にかかわらず最新の100件を取得します(デフォルトは0)
上記のスクリプトを実行すると無事メッセージオブジェクトが取得できました。
レスポンスで返ってきたメッセージオブジェクトの構成ですが、RESPONSESの200の欄で確認できます。
クリックするとオブジェクトのプロパティ構成がわかります。必要そうな情報は網羅されてそうですね。
複数階層のオブジェクトをフラットにする
取得されたメッセージオブジェクトにはオブジェクトの中にオブジェクトという複数階層になっているプロパティがあります。
スプレッドシートに展開するためには、この値をすべてフラットにしてオブジェクトの値だけの二次元配列を作成したいです。
どんな構造のオブジェクトに対しても機能するようにこのフラット化する関数を作成するために再帰関数を用います。
再帰関数に関しては、下記の記事で少し触れました。
複数階層のオブジェクトをフラットにする関数はこちら
/**
* 複数階層に存在するオブジェクトをフラット化して返す関数
* @params {Object} obj - 元となるオブジェクト
* @params {number} level - ネストの階層のインデックス(デフォルト:0)
* @params {string} prefix - プロパティ名の接頭辞(デフォルト:'')
* @return {Object} flattedObj - フラット化したオブジェクト(オブジェクトを格納するキーは削除)
*/
function flatObj_(obj, level = 0, prefix = '') {
const flattedObj = new Object();
Object.keys(obj).forEach(key => {
const value = obj[key];
// オブジェクトの値が配列を除くオブジェクト(=階層化)の場合、フラット化メソッドをループ
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
const preKey = prefix + key + '__';
Object.assign(flattedObj, flatObj_(value, 0, preKey));
}
// オブジェクトの値が空の配列の場合はスキップ
else if (Array.isArray(value) && value[level] === undefined) {
return; // forEach処理の中でreturnしても、繰り返し処理は継続する
}
// オブジェクトの値がオブジェクトを格納した配列の場合、フラット化メソッドをループ
else if (Array.isArray(value) && typeof value[level] === 'object') {
const preKey = prefix + key + '__';
Object.assign(flattedObj, flatObj_(value[level], 0, preKey));
}
// オブジェクトの値が文字列を格納した配列の場合、配列の各要素を結合
else if (Array.isArray(value) && typeof value[level] === 'string') {
const newKey = prefix + key;
flattedObj[newKey] = value.join();
}
// 上記条件以外の場合
else {
const newKey = prefix + key;
flattedObj[newKey] = value;
}
});
return flattedObj;
};
この関数ではオブジェクトの各プロパティの値が
- オブジェクト
- 空の配列
- オブジェクトを格納した配列
- 文字列を格納した配列
- 上記以外
で条件分岐をし、値がオブジェクトやオブジェクトを要素として持つ配列の場合に再帰処理をして、複数階層に渡っての値を全てフラットにして取得しています。
この関数のポイントは、親階層のプロパティ名と子階層のオブジェクトの各プロパティ名を2つの_(アンダースコア)を使って連結している点です。
通常の命名規則の場合、プロパティ名にアンダースコアを2回連続して使用することは考えづらく、そのため親階層と子階層の境界を後から確認することができます。
これを先程APIで取得したメッセージオブジェクトに適用して、’Log’というシート名のシートに書き出します。
function demoMessages2SheetFlat() {
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 aryFlatMessage = aryMessage.map(message => flatObj_(message));
// シートに書き込むヘッダ項目としてプロパティ名を一次元配列で取得する
const headerKeys = Object.keys(aryFlatMessage[0]);
// プロパティの値のみを抽出した新しい二次元配列を生成する
const ary2D = aryFlatMessage.map(message => Object.values(message));
// 上記の二次元配列にヘッダ項目となる配列を要素の先頭に追加する
ary2D.unshift(headerKeys);
// アクティブなスプレッドシートのシート名'Log'のシートを取得して二次元配列の値をシートに追加する
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheetLog = ss.getSheetByName('Log');
const range = sheetLog.getRange(1, 1, ary2D.length, ary2D[0].length);
range.setValues(ary2D);
}
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
unshift()
メソッドは、配列の最初に 1 つ以上の要素を追加し、新しい配列の長さを返します。
素直にプロパティ名を一つづつ取り出してもOK
flatObj_()関数は、やや複雑な処理をしていますが、今回のレスポンスの構造程度の複雑さであれば、プロパティを1つづつ取り出しても、さほど複雑なスクリプトにはなりません。
function demoMessages2SheetSimple() {
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 => {
return [
message.message_id,
message.account.account_id,
message.account.name,
message.account.avatar_image_url,
message.body,
message.send_time,
message.update_time];
});
// ヘッダー項目を一次元配列で指定して二次元配列の要素の先頭に追加する
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);
}
無事シートに最新の100件のメッセージが書き出されました。
おわりに
今回は、「Chatwork APIに接続して最新の100件のメッセージをスプレッドシートに書き出す」を紹介しました。
今回のハイライトは、オブジェクトの中にオブジェクトという入れ子構造になっているオブジェクトの値をすべてフラットにして二次元配列を生成する再帰関数です。
ただ、記事内でも触れたように今回のChatworkのメッセージオブジェクトの構造はシンプルだったため、そのまま直接指定するほうがわかりやすいと思います。
再帰関数は、他のAPIからの複雑なJSONデータをスプレッドシートに書き出す際に有効だと思います。APIからのデータのスプレッドシートへの書き出しに困ったら、思い出していただけると幸いです。
シリーズ目次
- Chatworkのメッセージをスプレッドシートに保存しよう その1 – 最新の100件のメッセージを書き出す
- Chatworkのメッセージをスプレッドシートに保存しよう その2 – 表示を整え新着メッセージのみを定期的に追加する
- 【まとめ】コピペでOK!?Chatworkのトーク履歴をGoogleスプレッドシートに記録するGAS
Google Apps Scriptを勉強したい方へ
この記事を見て、GASを勉強したいなと思われた方はぜひノンプログラマーのためのスキルアップ研究会(通称 ノンプロ研)にご参加ください。私も未経験からこの学習コミュニティに参加し、講座を受講したことでGASが書けるようになりました。
学習コミュニティ「ノンプログラマーのためのスキルアップ研究会」
挫折しがちなプログラミングの学習も、コミュニティの力で継続できます。ノンプロ研でお待ちしております!