本頁面說明如何建立 Chat 應用程式,透過 Cloud Pub/Sub 接收 Chat 的事件。如果您的 Chat 應用程式位於防火牆後方,或是想透過 Google Workspace Events API 傳送或接收有關 Chat 聊天室或使用者的事件,這個架構就非常實用。
下圖顯示以 Pub/Sub 建構的即時通訊應用程式架構:
在上圖中,使用者與 Pub/Sub Chat 應用程式互動時,資訊流程如下:
使用者與 Chat 應用程式互動,例如傳送訊息、發出指令,或在 Chat 聊天室中新增/移除應用程式。
Chat 會將訊息傳送至 Pub/Sub 主題。
應用程式伺服器 (即包含 Chat 應用程式邏輯的雲端或地端系統) 會訂閱 Pub/Sub 主題,以便透過防火牆接收訊息。
Chat 應用程式可以選擇呼叫 Chat API,以非同步方式發布訊息或執行其他作業。
必要條件
Node.js
- 具有 Google Chat 存取權的 Business 或 Enterprise 版 Google Workspace 帳戶。
- 已啟用計費功能的 Google Cloud 專案。如要確認現有專案是否已啟用計費功能,請參閱「確認專案的帳單狀態」。如要建立專案及設定帳單,請參閱「建立 Google Cloud 專案」。
- Node.js 14 以上版本
- npm 套件管理工具
- 已初始化的 Node.js 專案。如要初始化新專案,請建立並切換至新資料夾,然後在指令列介面中執行下列指令:
npm init
Python
- 具有 Google Chat 存取權的 Business 或 Enterprise 版 Google Workspace 帳戶。
- 已啟用計費功能的 Google Cloud 專案。如要確認現有專案是否已啟用計費功能,請參閱「確認專案的帳單狀態」。如要建立專案及設定帳單,請參閱「建立 Google Cloud 專案」。
- Python 3.6 以上版本
- pip 套件管理工具
Java
- 具有 Google Chat 存取權的 Business 或 Enterprise 版 Google Workspace 帳戶。
- 已啟用計費功能的 Google Cloud 專案。如要確認現有專案是否已啟用計費功能,請參閱「確認專案的帳單狀態」。如要建立專案及設定帳單,請參閱「建立 Google Cloud 專案」。
- Java 11 以上版本
- Maven 套件管理工具
啟用 API
使用 Google API 前,您需要在 Google Cloud 專案中啟用這些 API。 您可以在單一 Google Cloud 專案中啟用一或多個 API。在 Google Cloud 控制台中,啟用 Google Chat API 和 Pub/Sub API。
設定 Pub/Sub
建立 Pub/Sub 主題,供 Chat API 傳送訊息。建議每個 Chat 應用程式使用單一主題。
建立服務帳戶,供 Chat 應用程式向 Pub/Sub 和 Chat 授權,並將私密金鑰檔案儲存至工作目錄。
編寫指令碼
在本節中,您將定義 Chat 應用程式的應用程式邏輯,並編寫指令碼,向 Google Cloud 進行驗證,然後訂閱 Pub/Sub 主題,接收來自 Chat 的事件,例如使用者傳送訊息給 Chat 應用程式時。
指令碼收到訊息時,會處理事件資料,並使用 Google Chat API 將回覆傳送給使用者或聊天室。完成這項設定後,您的 Chat 應用程式就能在防火牆後方運作,同時與 Chat 使用者互動。
Node.js
在 CLI 中提供服務帳戶憑證:
export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH在 CLI 中,提供 Google Cloud 專案 ID:
export PROJECT_ID=PROJECT_ID在 CLI 中,提供您先前建立的 Pub/Sub 訂閱項目 ID:
export SUBSCRIPTION_ID=SUBSCRIPTION_ID在工作目錄中,建立名為
package.json的檔案。在
package.json檔案中貼上下列程式碼:{ "name": "pub-sub-app", "version": "1.0.0", "description": "Google Chat App that listens for messages via Cloud Pub/Sub", "main": "index.js", "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "@google-apps/chat": "^0.4.0", "@google-cloud/pubsub": "^4.5.0" }, "license": "Apache-2.0" }在工作目錄中,建立名為
index.js的檔案。在
index.js中貼上下列程式碼:const {ChatServiceClient} = require('@google-apps/chat'); const {MessageReplyOption} = require('@google-apps/chat').protos.google.chat.v1.CreateMessageRequest; const {PubSub} = require('@google-cloud/pubsub'); const {SubscriberClient} = require('@google-cloud/pubsub/build/src/v1'); // Receives messages from a pull subscription. function receiveMessages() { const chat = new ChatServiceClient({ keyFile: process.env.GOOGLE_APPLICATION_CREDENTIALS, scopes: ['https://www.googleapis.com/auth/chat.bot'], }); const subscriptionPath = new SubscriberClient() .subscriptionPath(process.env.PROJECT_ID, process.env.SUBSCRIPTION_ID) const subscription = new PubSub() .subscription(subscriptionPath); // Handle incoming message, then acknowledge the received message const messageHandler = message => { console.log(`Id : ${message.id}`); const event = JSON.parse(message.data); console.log(`Data : ${JSON.stringify(event)}`); // Post the response to Google Chat. const request = formatRequest(event); if (request != null) { chat.createMessage(request); } // Acknowledge the message. message.ack(); } subscription.on('message', messageHandler); console.log(`Listening for messages on ${subscriptionPath}`); // Keep main thread from exiting while waiting for messages setTimeout(() => { subscription.removeListener('message', messageHandler); console.log(`Stopped listening for messages.`); }, 60 * 1000); } // Send message to Google Chat based on the type of event function formatRequest(event) { const chatEvent = event.chat || {}; // If the app was removed, we don't respond. if (chatEvent.removedFromSpacePayload) { console.log(`App removed from space.`); return null; } const payload = chatEvent.messagePayload || chatEvent.addedToSpacePayload; const spaceName = payload?.space?.name; if (!spaceName) { console.log('No space name in event.'); return null; } if (chatEvent.addedToSpacePayload) { // An app can also be added to a space by @mentioning it in a // message. In that case, we fall through to the message case // and let the app respond. If the app was added using the // invite flow, we just post a thank you message in the space. return { parent: spaceName, message: { text: 'Thank you for adding me!' }, }; } else if (chatEvent.messagePayload) { // In case of message, post the response in the same thread. const message = chatEvent.messagePayload.message; return { parent: spaceName, messageReplyOption: MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD, message: { text: 'You said: `' + message.text + '`', thread: { name: message.thread.name }, }, }; } } if (!process.env.PROJECT_ID) { console.log('Missing PROJECT_ID env var.'); process.exit(1); } if (!process.env.SUBSCRIPTION_ID) { console.log('Missing SUBSCRIPTION_ID env var.'); process.exit(1); } if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) { console.log('Missing GOOGLE_APPLICATION_CREDENTIALS env var.'); process.exit(1); } receiveMessages();
Python
在 CLI 中提供服務帳戶憑證:
export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH在 CLI 中,提供 Google Cloud 專案 ID:
export PROJECT_ID=PROJECT_ID在 CLI 中,提供您先前建立的 Pub/Sub 訂閱項目 ID:
export SUBSCRIPTION_ID=SUBSCRIPTION_ID在工作目錄中,建立名為
requirements.txt的檔案。在
requirements.txt檔案中貼上下列程式碼:google-cloud-pubsub>=2.23.0 google-apps-chat==0.1.9在工作目錄中,建立名為
app.py的檔案。在
app.py中貼上下列程式碼:import json import logging import os import sys import time from google.apps import chat_v1 as google_chat from google.cloud import pubsub_v1 from google.oauth2.service_account import Credentials def receive_messages(): """Receives messages from a pull subscription.""" scopes = ['https://www.googleapis.com/auth/chat.bot'] service_account_key_path = os.environ.get('GOOGLE_APPLICATION_CREDENTIALS') creds = Credentials.from_service_account_file(service_account_key_path) chat = google_chat.ChatServiceClient( credentials=creds, client_options={'scopes': scopes} ) project_id = os.environ.get('PROJECT_ID') subscription_id = os.environ.get('SUBSCRIPTION_ID') subscriber = pubsub_v1.SubscriberClient() subscription_path = subscriber.subscription_path(project_id, subscription_id) # Handle incoming message, then acknowledge the received message def callback(message): event = json.loads(message.data) logging.info('Data : %s', event) # Post the response to Google Chat. request = format_request(event) if request is not None: chat.create_message(request) # Acknowledge the message. message.ack() subscriber.subscribe(subscription_path, callback = callback) logging.info('Listening for messages on %s', subscription_path) # Keep main thread from exiting while waiting for messages while True: time.sleep(60) def format_request(event): """Send message to Google Chat based on the type of event. Args: event: A dictionary with the event data. """ chat_event = event.get('chat', {}) # If the app was removed, we don't respond. if 'removedFromSpacePayload' in chat_event: logging.info('App removed from space.') return payload = chat_event.get('messagePayload') or chat_event.get( 'addedToSpacePayload' ) space_name = payload.get('space', {}).get('name') if payload else None if not space_name: logging.warning('No space name in event.') return if 'addedToSpacePayload' in chat_event: # An app can also be added to a space by @mentioning it in a # message. In that case, we fall through to the message case # and let the app respond. If the app was added using the # invite flow, we just post a thank you message in the space. return google_chat.CreateMessageRequest( parent = space_name, message = { 'text': 'Thank you for adding me!' } ) elif 'messagePayload' in chat_event: # In case of message, post the response in the same thread. message = chat_event['messagePayload']['message'] return google_chat.CreateMessageRequest( parent = space_name, message_reply_option = google_chat.CreateMessageRequest.MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD, message = { 'text': 'You said: `' + message['text'] + '`', 'thread': { 'name': message['thread']['name'] } } ) if __name__ == '__main__': if 'PROJECT_ID' not in os.environ: logging.error('Missing PROJECT_ID env var.') sys.exit(1) if 'SUBSCRIPTION_ID' not in os.environ: logging.error('Missing SUBSCRIPTION_ID env var.') sys.exit(1) if 'GOOGLE_APPLICATION_CREDENTIALS' not in os.environ: logging.error('Missing GOOGLE_APPLICATION_CREDENTIALS env var.') sys.exit(1) logging.basicConfig( level=logging.INFO, style='{', format='{levelname:.1}{asctime} {filename}:{lineno}] {message}') receive_messages()
Java
在 CLI 中提供服務帳戶憑證:
export GOOGLE_APPLICATION_CREDENTIALS=SERVICE_ACCOUNT_FILE_PATH在 CLI 中,提供 Google Cloud 專案 ID:
export PROJECT_ID=PROJECT_ID在 CLI 中,提供您先前建立的 Pub/Sub 訂閱項目 ID:
export SUBSCRIPTION_ID=SUBSCRIPTION_ID在工作目錄中,建立名為
pom.xml的檔案。在
pom.xml檔案中貼上下列程式碼:<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.google.chat.addon</groupId> <artifactId>pubsub-addon-chat-app</artifactId> <version>0.1.0</version> <name>pubsub-addon-chat-app-java</name> <properties> <maven.compiler.release>11</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>com.google.cloud</groupId> <artifactId>libraries-bom</artifactId> <version>26.41.0</version> <!-- Use a recent BOM version --> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- Google Chat GAPIC library --> <dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-chat</artifactId> </dependency> <!-- Google Cloud Pub/Sub library --> <dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-pubsub</artifactId> </dependency> <!-- Google Apps Add-ons Event Object --> <dependency> <groupId>com.google.apps.addons.v1</groupId> <artifactId>google-apps-addons-v1-java</artifactId> <version>0.2.0</version> <!-- Check for latest version --> </dependency> <!-- Protobuf JSON utility --> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java-util</artifactId> </dependency> <!-- Google Auth Library --> <dependency> <groupId>com.google.auth</groupId> <artifactId>google-auth-library-oauth2-http</artifactId> </dependency> <dependency> <groupId>com.google.api</groupId> <artifactId>gax</artifactId> </dependency> <!-- JSON utilities for PubSub message (if needed, though protobuf-java-util is primary for EventObject) --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.36</version> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.3.0</version> <configuration> <mainClass>Main</mainClass> </configuration> </plugin> </plugins> </build> </project>在工作目錄中,建立
src/main/java目錄結構。在
src/main/java目錄中,建立名為Main.java的檔案。在
Main.java中貼上下列程式碼:import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.api.gax.core.FixedCredentialsProvider; import com.google.auth.oauth2.GoogleCredentials; import com.google.chat.v1.ChatServiceClient; import com.google.chat.v1.ChatServiceSettings; import com.google.chat.v1.CreateMessageRequest; import com.google.chat.v1.CreateMessageRequest.MessageReplyOption; import com.google.chat.v1.Message; import com.google.chat.v1.Thread; import com.google.cloud.pubsub.v1.AckReplyConsumer; import com.google.cloud.pubsub.v1.MessageReceiver; import com.google.cloud.pubsub.v1.Subscriber; import com.google.pubsub.v1.ProjectSubscriptionName; import com.google.pubsub.v1.PubsubMessage; import java.io.FileInputStream; import java.util.Collections; public class Main { public static final String PROJECT_ID_ENV_PROPERTY = "PROJECT_ID"; public static final String SUBSCRIPTION_ID_ENV_PROPERTY = "SUBSCRIPTION_ID"; public static final String CREDENTIALS_PATH_ENV_PROPERTY = "GOOGLE_APPLICATION_CREDENTIALS"; public static void main(String[] args) throws Exception { ProjectSubscriptionName subscriptionName = ProjectSubscriptionName.of( System.getenv(Main.PROJECT_ID_ENV_PROPERTY), System.getenv(Main.SUBSCRIPTION_ID_ENV_PROPERTY)); // Instantiate app, which implements an asynchronous message receiver. EchoApp echoApp = new EchoApp(); // Create a subscriber for <var>SUBSCRIPTION_ID</var> bound to the message receiver final Subscriber subscriber = Subscriber.newBuilder(subscriptionName, echoApp).build(); System.out.println("Subscriber is listening to events..."); subscriber.startAsync(); // Wait for termination subscriber.awaitTerminated(); } } /** * A demo app which implements {@link MessageReceiver} to receive messages. * It echoes incoming messages. */ class EchoApp implements MessageReceiver { // Path to the private key JSON file of the service account to be used for posting response // messages to Google Chat. // In this demo, we are using the same service account for authorizing with Cloud Pub/Sub to // receive messages and authorizing with Google Chat to post messages. If you are using // different service accounts, set the path to the private key JSON file of the service // account used to post messages to Google Chat here. private static final String SERVICE_ACCOUNT_KEY_PATH = System.getenv(Main.CREDENTIALS_PATH_ENV_PROPERTY); // Developer code for Google Chat API scope. private static final String GOOGLE_CHAT_API_SCOPE = "https://www.googleapis.com/auth/chat.bot"; private static final String ADDED_RESPONSE = "Thank you for adding me!"; ChatServiceClient chatServiceClient; EchoApp() throws Exception { GoogleCredentials credential = GoogleCredentials.fromStream(new FileInputStream(SERVICE_ACCOUNT_KEY_PATH)) .createScoped(Collections.singleton(GOOGLE_CHAT_API_SCOPE)); // Create the ChatServiceSettings with the app credentials ChatServiceSettings chatServiceSettings = ChatServiceSettings.newBuilder() .setCredentialsProvider(FixedCredentialsProvider.create(credential)) .build(); // Set the Chat service client chatServiceClient = ChatServiceClient.create(chatServiceSettings); } // Called when a message is received by the subscriber. @Override public void receiveMessage(PubsubMessage pubsubMessage, AckReplyConsumer consumer) { System.out.println("Id : " + pubsubMessage.getMessageId()); // Handle incoming message, then acknowledge the received message try { ObjectMapper mapper = new ObjectMapper(); JsonNode dataJson = mapper.readTree(pubsubMessage.getData().toStringUtf8()); System.out.println("Data : " + dataJson.toString()); handle(dataJson); consumer.ack(); } catch (Exception e) { System.out.println(e); // Negative acknowledgement makes Pub/Sub redeliver the message. consumer.nack(); } } // Send message to Google Chat based on the type of event. public void handle(JsonNode eventJson) throws Exception { // Google Chat events for add-ons are wrapped in a 'chat' object. if (!eventJson.has("chat")) { System.out.println("Ignored: Not a Chat event (missing 'chat' field)."); return; } JsonNode chatNode = eventJson.get("chat"); CreateMessageRequest createMessageRequest = null; if (chatNode.has("messagePayload")) { // HANDLE MESSAGE JsonNode messagePayload = chatNode.get("messagePayload"); JsonNode message = messagePayload.get("message"); JsonNode space = messagePayload.get("space"); String spaceName = space.get("name").asText(); String userText = message.has("text") ? message.get("text").asText() : ""; String threadName = message.has("thread") ? message.get("thread").get("name").asText() : ""; System.out.println("Received message in " + spaceName + ": " + userText); createMessageRequest = CreateMessageRequest.newBuilder() .setParent(spaceName) .setMessageReplyOption(MessageReplyOption.REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD) .setMessage( Message.newBuilder() .setText("You said: `" + userText + "`") .setThread(Thread.newBuilder().setName(threadName).build()) .build()) .build(); } else if (chatNode.has("addedToSpacePayload")) { // HANDLE ADDED TO SPACE JsonNode addedPayload = chatNode.get("addedToSpacePayload"); JsonNode space = addedPayload.get("space"); String spaceName = space.get("name").asText(); System.out.println("Added to space: " + spaceName); createMessageRequest = CreateMessageRequest.newBuilder() .setParent(spaceName) .setMessage(Message.newBuilder().setText(ADDED_RESPONSE).build()) .build(); } else if (chatNode.has("removedFromSpacePayload")) { System.out.println("Removed from space."); return; } else { System.out.println("Ignored: Unhandled Chat event type."); return; } if (createMessageRequest != null) { // Post the response to Google Chat. chatServiceClient.createMessage(createMessageRequest); System.out.println("Sent reply."); } } }
設定 Chat 應用程式
在 Google Cloud 控制台中設定 Chat 應用程式,提供名稱和虛擬人偶等詳細資料,並設定與 Pub/Sub 主題的連線。
連結至 Pub/Sub 主題後,Chat 就能將事件傳送至應用程式。訂閱該主題的指令碼隨後會收到這些事件,並回應使用者。
在 Google Cloud 控制台中,依序前往「選單」 「>」「API 和服務」 「>」「已啟用的 API 和服務」 「>」「Google Chat API」 「>」「設定」。
設定 Chat 應用程式的 Pub/Sub:
- 在「應用程式名稱」中輸入
Add-on Chat App。 - 在「Avatar URL」中輸入
https://developers.google.com/workspace/add-ons/images/quickstart-app-avatar.png。 - 在「Description」(說明) 中輸入
Quickstart app。 - 在「功能」下方,選取「加入聊天室和群組對話」。
- 在「連線設定」下方,選取「Cloud Pub/Sub」,然後貼上先前建立的 Pub/Sub 主題名稱。
- 在「顯示設定」下方,選取「將這個 Google Chat 擴充應用程式提供給網域中的特定使用者和群組」,然後輸入電子郵件地址。
- 在「記錄」下方,選取「將錯誤記錄至 Logging」。
- 在「應用程式名稱」中輸入
按一下 [儲存]。
設定 Chat 應用程式後,您必須更新 Pub/Sub 設定。
- 在 Chat API 設定頁面的「連線設定」下方,複製「服務帳戶電子郵件」,這是為 Google Cloud 專案產生的專屬電子郵件。
- 授予 Chat 發布至主題的權限:將「Pub/Sub 發布者」角色指派給您先前複製的服務帳戶電子郵件地址。
應用程式已準備好接收及回覆 Chat 訊息。
執行指令碼
在 CLI 中,切換至工作目錄並執行指令碼:
Node.js
npm install
npm start
Python
python -m venv env
source env/bin/activate
pip install -r requirements.txt -U
python app.py
Java
mvn compile exec:java -Dexec.mainClass=Main
執行程式碼時,應用程式會開始監聽發布至 Pub/Sub 主題的訊息。
測試 Chat 應用程式
如要測試 Chat 應用程式,請開啟與該應用程式互傳的即時訊息,然後傳送訊息:
使用您在新增自己為信任測試人員時提供的 Google Workspace 帳戶,開啟 Google Chat。
- 按一下 「發起新即時通訊」。
- 在「新增 1 位以上使用者」欄位中,輸入 Chat 應用程式的名稱。
從結果中選取 Chat 應用程式。系統會開啟即時訊息。
- 在與應用程式互傳的新即時訊息中,輸入
Hello並按下enter。
如要新增信任的測試人員,並進一步瞭解如何測試互動式功能,請參閱「測試 Google Chat 應用程式的互動式功能」。
疑難排解
如果 Google Chat 應用程式或資訊卡傳回錯誤,Chat 介面會顯示「發生錯誤」訊息。或「無法處理您的要求。」有時 Chat UI 不會顯示任何錯誤訊息,但 Chat 應用程式或資訊卡會產生非預期的結果,例如資訊卡訊息可能不會顯示。
即使 Chat 使用者介面未顯示錯誤訊息,只要開啟 Chat 應用程式的錯誤記錄功能,系統就會提供說明性錯誤訊息和記錄資料,協助您修正錯誤。如需查看、偵錯及修正錯誤的相關協助,請參閱「排解及修正 Google Chat 錯誤」。
清除所用資源
為避免系統向您的 Google Cloud 帳戶收取本教學課程中所用資源的相關費用,建議您刪除 Cloud 專案。
- 在 Google Cloud 控制台中前往「管理資源」頁面。依序點選「選單」「IAM 與管理」「管理資源」。
- 在專案清單中選取要刪除的專案,然後按一下「刪除」圖示 。
- 在對話方塊中輸入專案 ID,然後按一下「Shut down」(關閉) 即可刪除專案。
相關主題
如要為 Chat 應用程式新增更多功能,請參閱下列文章: