ユーザーの代わりとして振る舞うGoogle Chatアプリをステップに分けて開発していきます。題材はAPIの新機能を使ったダイレクトメッセージを送信するアプリです。
開発部門(プロダクト技術本部)の高玉です。
BIGLOBEはオフィスツールとしてGoogle Workspaceを使っています。コミュニケーションの中心となるのがGoogle Chatですが、その機能は日々拡張されています。2023年7月からは、Google Chat APIから利用できる機能が大幅に増えました。
前回の記事では、仮想ユーザーとしてのGoogle Chatアプリ(チャットボット)を開発する方法に焦点を当てました。今回はユーザーの代わりに動作するChatアプリを開発し、Google Chat APIの新機能を使った具体的な事例をご紹介します。
前回の記事と合わせて読んでいただくことで、Chatアプリの開発で特に重要になる概念であるアプリ認証(サービスアカウント認証)とユーザー認証の違いを理解し、解決できることの幅を広げることができます。
- Google Chat APIを利用するのに必要なサービスアカウント認証、ユーザー認証
- Google Apps Scriptとユーザー認証を使ってChatアプリを自作する
- スプレッドシートから複数の人にお知らせを送信する
- まとめ
Google Chat APIを利用するのに必要なサービスアカウント認証、ユーザー認証
2023年7月からGoogle Chat APIで利用できる機能が大幅に増え、Chatアプリがスペースを作成してメンバーを追加したり、ダイレクトメッセージを送信できるようになりました。メッセージにリアクションをつけることも可能だそうです。
workspaceupdates-ja.googleblog.com
前回の記事の記事でもご紹介した通り、Google Chat APIを利用するにはアプリ認証(サービスアカウント認証)、もしくは、ユーザー認証を使ってChatアプリからAPIにアクセスする必要があります。
サービスアカウント認証を使う場合、Chatアプリが人間ではなく、独立したアプリとして振る舞います。一方、ユーザー認証を使うと、Chatアプリは人間の代わりとして振る舞います。Google Chat APIでできることが増えたことに伴い、Chatアプリからユーザー認証を使って送信したメッセージにはChatアプリ名が記載されるようになりました。ユーザーに代わって操作していることが分かるようにするためです。以下の写真では、名前の横にある「お知らせ」がChatアプリ名になります。
Google Apps Scriptとユーザー認証を使ってChatアプリを自作する
この記事では、Google WorkspaceでApps Scriptを使った開発経験がある方を対象に、Chatアプリを自作する方法を解説していきます。
前回の記事ではサービスアカウント認証を使った事例を紹介しました。ここからは、ユーザー認証を使ったChatアプリの作り方についてご紹介していきます。
作るのはspaces.setup とspaces.messages.createを使ってダイレクトメッセージを送信するChatアプリです1。例えば、提出物の未提出者に連絡するのに使います。メールで連絡するよりも、個別チャットで連絡するほうが反応が良いようです。
始める前の準備
Chatアプリを開発しはじめるためにいくつかの設定作業が必要です。以下の項目を予め決めておくことで、作業を一気に進められます。
- Google Cloud のプロジェクト名(英語)
- アルファベット。ハイフン可。
- 今回は
reminder
にします。このプロジェクトを2つのアプリで共用します。
- アプリ名(日本語)
- 「Google」という単語が使えないことに注意してください。 OAuth 同意画面を保存する時に「アプリの保存中にエラーが発生しました」と表示され、保存できなくなります。
- 今回は
お知らせ
にします。
- アプリの説明(日本語)
- 今回は
ダイレクトメッセージでお知らせを送信します。
にします。
- 今回は
- アプリのアバター画像のURL
- 著作権を侵害しないように気をつけましょう。
- 今回はチュートリアルでも使う
https://developers.google.com/static/workspace/chat/images/chat-product-icon.png
にします。
設定作業
前回同様、次の作業をしていきます。なお「作業4. Google Apps Script をテンプレートから作成」はスキップします。テンプレートから作るのはサービスアカウント認証を使うボット型のChatアプリだからです。代わりに「作業6. Google Apps Scriptを新規作成しGoogle Cloudのプロジェクト番号を設定」を実施します。
- 作業1. Google Cloudプロジェクトの作成
- 作業2. Chat APIの有効化
- 作業3. OAuth同意画面の構成
- (作業4. Google Apps Script をテンプレートから作成はスキップ)
- 作業5. Google Chat APIの設定
- 作業6. Google Apps Scriptを新規作成しプロジェクトの設定を変更する
作業1. Google Cloudプロジェクトの作成
Cloud プロジェクトの選択にアクセスし「プロジェクトを作成」を押して新しくプロジェクトを作成します。プロジェクト番号(プロジェクトIDではありません)は後で使うのでメモします。
作業2. Chat APIの有効化
APIの有効化を押して、Google CloudコンソールでGoogle Chat API を有効にします。
作業3. OAuth同意画面の構成
OAuth同意画面に移動を押して、CloudコンソールでOAuth同意画面を開き、以下の情報を入力していきます。
- User Type
- 社内用なので「内部」を選びます。
- アプリ情報
- アプリ名
- 準備しておいた
お知らせ
を入力します。 - Google という単語を含めないでください。アプリの保存に失敗します。
- 準備しておいた
- ユーザーサポートメール
- 自分のメールアドレスを入力してください。自分が所属するメーリングリストのメールアドレスを入力することもできます。
- アプリのロゴ
- 空欄でOKです。
- もし設定するなら、著作権に注意しましょう。サイズは 120 x 120 px の正方形が推奨です。
- アプリのドメイン
- 空欄でOKです。
- 承認済みドメイン
- 空欄でOKです。
- デベロッパーの連絡先情報
- 自分のメールアドレスを入力してください。
- スコープ
- 空欄でOKです。
- アプリ名
以上で OAuth同意画面の設定は終わりです。(2) スコープ、(3) 概要、は何も入力する必要はありません。保存して次へ、を押して、ダッシュボードへ戻る、を押してください。
作業5. Google Chat APIの設定
Google Chat APIの管理画面に戻り、画面中央タブの「構成」に情報を入力していきます。
- アプリケーション情報
- アプリ名
- 準備しておいた
お知らせ
を入力します。
- 準備しておいた
- アバターの URL
- 準備しておいた
https://developers.google.com/static/workspace/chat/images/chat-product-icon.png
を入力します。
- 準備しておいた
- 説明
- 準備しておいた
ダイレクトメッセージでお知らせを通知します。
を入力します。
- 準備しておいた
- アプリ名
- インタラクティブ機能
- 無効にします2。
- ログ
- エラーをLoggingに保存する、をチェックしておきます。
保存ボタンを押します。
作業6. Google Apps Scriptを新規作成しプロジェクトの設定を変更する
Apps Scriptを新規作成してスクリプトエディターが起動したら、左ペイン「ギア」マーク(プロジェクトの設定)を選択し、appsscript.jsonの表示と、Google Cloudプロジェクト番号を設定します。
なお、この後作るアプリは、スプレッドシートの拡張機能からApps Scriptを選択して作るコンテナバインド型のスクリプトです。スプレッドシートを新規作成した後、上部メニュー「拡張機能」から「Apps Script」を選択します。コンテナバインド型ではなく、スタンドアロン型のスクリプトを作る場合も以下の作業が必要です。
スクリプトエディターが起動したら、左ペイン「ギア」マーク(プロジェクトの設定)を選択します。
まず、全般「appsscript.json」マニュフェストファイルをエディタで表示する、にチェックを入れます。Apps ScriptからGoogle Chat APIを利用するためには、appsscript.jsonに必要となるoauthScopes
を記載する必要があるためです。
次に、一番下にある Google Cloud Platform(GCP)プロジェクトで「プロジェクトを変更」を押して、メモしておいたプロジェクト番号を入力し「プロジェクトを設定」ボタンを押します。なお、プロジェクト番号はCloudコンソールにアクセスして確認できます。
ダイレクトメッセージを送信する
ここまでの設定作業が終わったら、実際にダイレクトメッセージを送信してみます。自分宛てにダイレクトメッセージを送信することはできないため、動作試験につきあってくれる人を探してください。
新しくGoogleスプレッドシートを作り、拡張機能からApps Scriptを選んでスクリプトエディターが起動するのを待ち、上記の作業を完了させます。
その上で、スクリプトエディターに次のコードを記述します。
/** * Google APIを呼び出します。 * * @param {string} url - APIのエンドポイント。 * @param {string} oauthToken - OAuthトークン。 * @param {string} [method='get'] - HTTPメソッド(getまたはpost)。 * @param {Object} [payload=undefined] - 送信するデータ。 * @throws ステータスコードが200でない場合、またはJSON形式に変換できない場合にエラーをスローします。 * @returns {Object} APIのレスポンス。 */ function callGoogleApi_(url, oauthToken, method = 'get', payload = undefined) { console.log(`callGoogleApi url=${url}, method=${method}, payload=${JSON.stringify(payload)}`); const options = { method, headers: { 'Authorization': `Bearer ${oauthToken}` }, contentType: 'application/json', } if (payload) options.payload = JSON.stringify(payload); options['muteHttpExceptions'] = true; const res = UrlFetchApp.fetch(url, options); const responseCode = res.getResponseCode(); const contentText = res.getContentText(); if (responseCode !== 200) { const error = new Error(contentText); error.code = responseCode; throw error; } let json; try { json = JSON.parse(contentText); } catch (e) { throw new Error(`JSON形式に変換できませんでした: ${contentText}`); } console.log(`response=${JSON.stringify(json, null, 2)}`); return json; } /** * ユーザー認証を使用してGoogle APIを呼び出します。 * * @param {string} url - APIのエンドポイント。 * @param {string} method - HTTPメソッド(getまたはpost)。 * @param {Object} payload - 送信するデータ。 * @returns {Object} APIのレスポンス。 */ function callGoogleApiWithUserAuth_(url, method, payload) { return callGoogleApi_(url, ScriptApp.getOAuthToken(), method, payload); } /** * 指定された相手とのダイレクトメッセージのスペースを設定します(あれば既存のスペースを、なければ新しいスペースを返却)。 * @param {string} emailOrId - 相手のメールアドレスもしくはID * @returns {Object} APIのレスポンス(スペースの情報)。 */ function setupDirectMessage_(emailOrId) { const url = 'https://chat.googleapis.com/v1/spaces:setup'; const payload = { space: { spaceType: 'DIRECT_MESSAGE', singleUserBotDm: false }, memberships: [{ member: { name: 'users/' + emailOrId, type: 'HUMAN' } }] }; try { return callGoogleApiWithUserAuth_(url, 'post', payload); } catch (error) { if (error.code === 500) { throw new Error(`自分自身にはダイレクトメッセージを送信できません。${error.message}`); } throw error; } } /** * 指定したスレッド宛にメッセージを作成します。 * * @param {string} text - メッセージのテキスト。 * @param {string} spaceName - スペースの名前。 * @param {string} [threadName] - スレッドの名前。 * @returns {Object} APIのレスポンス(送信結果)。 */ function createMessageWithUserAuth_(text, spaceName, threadName) { let url = 'https://chat.googleapis.com/v1/' + spaceName + '/messages'; const payload = { text }; if (threadName) { url += '?messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD'; payload.thread = { name: threadName } } return callGoogleApiWithUserAuth_(url, 'post', payload); } /** * ダイレクトメッセージを送信する * * @param {string} emailOrId - 相手のメールアドレスもしくはID * @param {string} text - メッセージのテキスト。 */ function sendDirectMessage(emailOrId, text) { const space = setupDirectMessage_(emailOrId); createMessageWithUserAuth_(text, space.name); } function testSendDirectMessage() { const email = '<送信相手のメールアドレス>'; sendDirectMessage(email, 'こんにちは。テストです。'); }
callGoogleApiWithUserAuth_()
を見ると分かる通り、ユーザー認証ではOAuthのトークンにScriptApp.getAuthToken()
を利用します3。
次に、スクリプトエディターの左ペイン「エディタ」からappsscript.jsonを選択して、oauthScopes
を定義します。次のコードを記述します。
{ "timeZone": "Asia/Tokyo", "dependencies": { }, "exceptionLogging": "STACKDRIVER", "runtimeVersion": "V8", "oauthScopes": [ "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/chat.spaces", "https://www.googleapis.com/auth/chat.messages.create" ] }
ここまで記述できたら、testSendDirectMessage
を実行します。すると、次のように権限付与を許可するか尋ねられるので、確認の上、許可ボタンを押します。
正しく実行できると、指定した相手にダイレクトメッセージが送信されます(相手とのチャット履歴で確認できます)。送信されたメッセージを見てみると、自分の名前の右隣にアプリ名(ここでは「お知らせ」)が表示されていることが分かります。
もしエラーが出た場合は、設定モレがないか、自分宛にダイレクトメッセージを送信しようとしていないかを確認してみてください。
エラー Request had insufficient authentication scopes が表示されたら?
Error: { "error": { "code": 403, "message": "Request had insufficient authentication scopes.", "status": "PERMISSION_DENIED", "details": [ { "@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT", "domain": "googleapis.com", "metadata": { "method": "google.chat.v1.ChatService.SetUpSpace", "service": "chat.googleapis.com" } } ] } }
スクリプトエディターのプロジェクトの設定でappsscript.jsonを表示させていますか?またappsscript.jsonの中に適切なoauthScopes
を記載しているかご確認ください。
エラー The specified direct message doesn't exist が表示されたら?
Error: 自分自身もしくは組織外の人にダイレクトメッセージを送信できません。{ "error": { "code": 404, "message": "The specified direct message doesn't exist.", "status": "NOT_FOUND" } }
自分宛てにダイレクトメッセージを送信することはできません。組織内で協力者を探して、ダイレクトメッセージを送信させてもらいましょう。
スプレッドシートから複数の人にお知らせを送信する
ここまでの手順で、正しく設定が完了し、指定した相手にダイレクトメッセージを送信できることが確認できました。ここからはその応用編として、提出物の未提出者に連絡するのに便利なアプリを作ってみます。スプレッドシート上に組織内のメンバー全員をリストアップし、チェックボックスをつけた相手に、同じくスプレッドシート上で設定した本文をダイレクトメッセージとして送信するアプリです。乱用すると嫌がられるので注意して使ってください。
メンバー一覧シートを作る
スプレッドシートにメンバー一覧
シートを追加し、Admin SDK APIを使って組織のメンバーをこのシートに表示してみます。また名前の横にはチェックボックスもつけて、ダイレクトメッセージを送信する相手を複数選択できるようにします。
Admin SDK APIを有効にする
Apps Scriptでメンバー一覧を取得するには、Admin Directory を利用します。
APIの有効化を押して、CloudコンソールでGoogle Chat API を有効にします。
スクリプトエディターでライブラリーにAdmin SDK APIを追加する
次に、スクリプトエディターの「エディタ」で左ペイン「サービス」の右隣りにあるプラスアイコンを押し、Admin SDK API
を追加します。この後の説明のため、IDはAdminDirectory
のままにしてください。
追加されると、サービスの下にAdminDirectory
と表示されます。
appsscript.json に Admin SDKと、スプレッドシートのoauthScopesを追加する
appsscript.json の oauthScopes に、https://www.googleapis.com/auth/admin.directory.user.readonly
とhttps://www.googleapis.com/auth/spreadsheets
の2つを追記します。
追記した後の appsscript.json は以下のようになります。
{ "timeZone": "Asia/Tokyo", "dependencies": { "enabledAdvancedServices": [ { "userSymbol": "AdminDirectory", "version": "directory_v1", "serviceId": "admin" } ] }, "exceptionLogging": "STACKDRIVER", "runtimeVersion": "V8", "oauthScopes": [ "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/chat.spaces", "https://www.googleapis.com/auth/chat.messages.create", "https://www.googleapis.com/auth/admin.directory.user.readonly", "https://www.googleapis.com/auth/spreadsheets" ] }
メンバーの情報をシートに表示する
スプレッドシートにメンバー一覧
シートを追加します。
そして、スクリプトエディターに次のコードを追記します。YOUR_DOMAIN
はあなたの組織のGoogle Workspaceのドメインを記載してください。
const YOUR_DOMAIN = '<あなたの組織のドメイン>'; function fetchAllUsers_() { let users = []; let pageToken; let page; do { page = AdminDirectory.Users.list({ domain: YOUR_DOMAIN, orderBy: 'givenName', maxResults: 100, pageToken: pageToken, projection: 'full', viewType: 'domain_public' // 管理者権限がないユーザーが実行するときに必要 }); users = users.concat(page.users); pageToken = page.nextPageToken; } while (pageToken); return users; } function getSheet_(sheetName) { const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName); if (!sheet) throw `シート「${sheetName}」がありません。`; return sheet; } const MEMBERS_SHEET_NAME = 'メンバー一覧'; function updateMembers() { const header = ['送信する', '名前', 'ID']; const names = fetchAllUsers_().map(user => { const { id, name } = user; return [false, name.fullName, id]; }); const rows = [header, ...names]; const sheet = getSheet_(MEMBERS_SHEET_NAME); sheet.getRange(1, 1, rows.length, rows[0].length).setValues(rows); sheet.getRange(2, 1, names.length).insertCheckboxes(); }
fetchAllUsers_()
はAdmin SDK APIを利用して組織内のすべてのメンバー情報を取得します。取得できるメンバー情報はAdmin SDK/APIのusersで確認できます。
updateMembers()
を実行すると、メンバー一覧シートに、id、名前、チェックボックスを追加してくれます。
もしエラーが出た場合は、設定モレや書き換えモレがないかを確認してみてください。
エラー Admin SDK API has not been used が表示されたら?
GoogleJsonResponseException: API call to directory.users.list failed with error: Admin SDK API has not been used in project XXXXXXXXXXXX before or it is disabled.
CloudコンソールでAdmin SDK APIを有効化し忘れていないか確認してください。
エラー AdminDirectory is not defined が表示されたら?
ReferenceError: AdminDirectory is not defined
スクリプトエディターのライブラリーで、AdminDirectory
を追加し忘れていないか確認してください。
appsscript.jsonのoauthScopes追加忘れ
次のエラーが出た場合、appsscript.jsonのoauthScopesに追記し忘れがないか確認してください。
https://www.googleapis.com/auth/admin.directory.user.readonly
を追記し忘れた場合:
GoogleJsonResponseException: API call to directory.users.list failed with error: Request had insufficient authentication scopes.
https://www.googleapis.com/auth/spreadsheets
を追記し忘れた場合:
Exception: You do not have permission to call SpreadsheetApp.getActive. Required permissions: (https://www.googleapis.com/auth/spreadsheets.currentonly || https://www.googleapis.com/auth/spreadsheets)
エラー Domain not found が表示されたら?
GoogleJsonResponseException: API call to directory.users.list failed with error: Domain not found.
YOUR_DOMAINのところは、自分の組織で使うドメインに書き換える必要があります。
本文シートを作りメッセージを送信する
スプレッドシートに「本文」シートを追加します。「本文」シートのセルA1に書いた文字を、「メンバー一覧」シートでチェックした人にダイレクトメッセージとして送信します。
スクリプトエディターで次のコードを追記します。
const CONTENT_SHEET_NAME = '本文'; function sendToCheckedUsers() { const content = getSheet_(CONTENT_SHEET_NAME).getRange(1, 1).getValue().trim(); if (!content) throw `本文が設定されていません。`; const checkedUsers = getSheet_(MEMBERS_SHEET_NAME).getDataRange().getValues().slice(1).filter(row => row[0] === true).map(row => ({ id: row[2], name: row[1] })); if (checkedUsers.length === 0) throw `送信先がチェックされていません。`; const exceptions = []; for (let user of checkedUsers) { try { sendDirectMessage(user.id, content); } catch(e) { // 自分自身に送信した場合にエラーになる console.error(e); exceptions.push(user.name); } } if (exceptions.length > 0) throw `次の相手へのDMがエラーになりました: ${exceptions.join(', ')}`; }
次に「メンバー一覧」シートでメッセージを送信したい相手のチェックボックスをチェックします。念のため、送信する前に、動作試験のためこれからメッセージを送信することを相手に伝えておきましょう。
そして「本文」シートのセルA1に送信するメッセージを記述し、スクリプトエディターでsendToCheckedUsers
を実行します。チェックした相手とのチャット履歴にメッセージが表示されていれば正常に動作しています。おめでとうございます!
メニューからダイレクトメッセージを送信する
操作しやすくするため、スプレッドシートのメニューからダイレクトメッセージを送信できるようにしてみます。
スクリプトエディターで次のコードを追記します。
function clearCheckboxes() { const sheet = getSheet_(MEMBERS_SHEET_NAME); sheet.getRange(2, 1, sheet.getLastRow() - 1).setValue(false); } function doNothing() { // 何もしない // はじめてスクリプトを実行する人に権限を付与するために実行してもらう } function onOpen() { const ui = SpreadsheetApp.getUi(); ui.createMenu('スクリプト実行') .addItem('ダイレクトメッセージを送信', 'sendToCheckedUsers') .addSeparator() .addItem('送付フラグをクリア', 'clearCheckboxes') .addItem('メンバー一覧を更新', 'updateMembers') .addSeparator() .addItem('最初に一度だけ実行する', 'doNothing') .addToUi(); }
追記して保存したら、スプレッドシートを再読み込みします。少し時間が経つと、新しいメニュー「スクリプト実行」が表示されます。なお、スプレッドシートを再読み込みすると、スクリプトエディターは自動的に閉じられてしまいます。再度、スクリプトエディターを表示するには、スプレッドシートの上部メニュー「拡張機能」から「Apps Script」を選んでください。
メニューの一番下にある「最初に一度だけ実行する」は、初めてこのスプレッドシートにアクセスした人向けのメニューです。初めての人がこれを実行すると、ユーザー認証に必要な権限の付与を許可する画面が表示されます。確認の上、許可ボタンを押してもらいます。これはいきなり「ダイレクトメッセージを送信」を選んだ場合には、権限の付与だけで終わり、ダイレクトメッセージが送信されないために追加したメニューです。
まとめ
この記事では、Google Chatの新機能とその上で使えるChatアプリを開発する方法について解説しました。Chatアプリを開発する時に重要になる概念がアプリ認証(サービスアカウント認証)とユーザー認証ですが、前回の記事ではサービスアカウント認証を、今回の記事ではユーザー認証について、それぞれ詳しい使い方を紹介しています。
ユーザー認証を使ってGoogle Chat APIを利用する場合、appsscript.jsonにoauthScopesを正しく設定する必要があります。うっかり設定し忘れてしまうことも多いため、エラーを多めに掲載し、対処法が分かるように工夫しています。Google Apps Scriptを使用してChatアプリを自作する手順でも、早い段階でミスに気がつけるよう、動作確認を含めたステップ・バイ・ステップで解説しています。
複数のメンバーにダイレクトメッセージでお知らせを送るアプリはBIGLOBEの業務の中でも利用しているものです。誰でも使えるよう、スプレッドシートのメニューから利用する方法についても記載しました。
Google Chatは日々進化しています。Chat APIで実現できるようになったことも調べて頂き、より魅力的なChatアプリを作っていただければ幸いです。
※ Google 、Google Chat 、Google Workspace 、GCP は、Google LLC の商標であり、このブログはGoogle によって承認されたり、Google と提携したりするものではありません。
※記載しているシステム名、製品名、サービス名は、それぞれ各社の商標または登録商標です。
- ダイレクトメッセージの送信先を spaces.findDirectMessageで探すやり方だと、相手と過去にやりとりをしていない場合にNOT_FOUNDエラーになります。そこで代わりにspaces.setupを使い、過去にやり取りしていれば既存のスペースを取得し、なければスペースを新しく作ってメッセージを送信します。↩
- サービスアカウント認証をする場合、事前にサービスアカウントを作成し、インタラクティブ機能は有効にする必要があります。↩
- サービスアカウント認証ではOAuth2 for Apps Scriptライブラリーと、サービスアカウントの秘密鍵を使ってトークンを生成します。↩