Google Chat
コミュニケーションツール
Google Chat
概要
Google Chatは、Google Workspaceに統合されたチームコラボレーションプラットフォームです。Gmail、Google Drive、Google Calendarとのシームレスな統合により、統一されたワークフロー体験を提供します。Chat API、Webhookサポート、Apps Script統合を通じて豊富な自動化機能とBot開発が可能で、エンタープライズ向けの高度なセキュリティ機能を提供します。
詳細
Google Chat(グーグルチャット)は、2017年にGoogle Workspaceの一部としてリリースされたビジネスコミュニケーションプラットフォームです。Google Hangouts Chatから発展し、現在は世界中の企業で広く採用されています。特にGoogle Workspaceエコシステム内での統合性の高さが特徴で、Gmail、Google Drive、Google Meetとの連携により統一されたコラボレーション体験を提供します。
2024-2025年には、Quick Commands機能、Accessory Widgets、プライベートメッセージング、Card Builderツール、Dialogflow CX統合、Space Events(Developer Preview)などの大幅な機能強化が行われました。また、Google Apps Scriptとの統合がさらに改善され、Advanced Chat serviceを使用したアプリ開発が推奨されています。
Google Chat APIは包括的なBot開発機能を提供し、Webhook統合、Apps Script統合、カスタム絵文字管理、通知設定API、アプリレベル認証スコープなど、企業向けの高度な機能が利用できます。Google Cloud Platformとの深い統合により、スケーラブルで安全なチャットアプリケーションを構築できます。
メリット・デメリット
メリット
- Google Workspace完全統合: Gmail、Drive、Calendar、Meetとのシームレス連携
- 豊富なAPI機能: Chat API、Advanced Chat service、Space Events API
- Apps Script統合: ノーコード・ローコード開発サポート
- 強力なWebhook機能: 非同期メッセージング、外部システム統合
- エンタープライズセキュリティ: Google Workspaceレベルのセキュリティ
- Card Builder: インタラクティブカードの視覚的設計
- Dialogflow CX統合: 自然言語処理による高度なBot
- 無料Workspace統合: 基本機能は無料で利用可能
デメリット
- Google依存: Google Workspaceエコシステムへの強い依存
- カスタマイズ制限: UI/UXのカスタマイズ範囲の制約
- 学習コスト: Google Cloudプラットフォームの知識が必要
- 他社サービス統合: 非Google系サービスとの統合複雑性
- 機能制限: 無料版での一部機能制限
- レート制限: API呼び出し制限による大規模利用時の制約
主要リンク
- Google Chat公式サイト
- Google Chat Developer Platform
- Chat API Reference
- Apps Script でのChat開発
- Webhook Guide
- Google Workspace
- Card Builder Tool
書き方の例
Google Apps Script を使用したChat App
// Apps Script でのGoogle Chat App実装
function onMessage(event) {
// メッセージイベントの処理
const message = event.message;
const user = event.user;
const space = event.space;
console.log(`メッセージ受信: ${message.text} from ${user.displayName} in ${space.displayName}`);
// 基本的なエコー応答
if (message.text) {
return createTextResponse(`エコー: ${message.text}`);
}
// メンション処理
if (message.argumentText) {
return handleCommand(message.argumentText, user, space);
}
return createTextResponse('こんにちは!何かお手伝いできることはありますか?');
}
// カード付きレスポンスの作成
function createCardResponse(title, description, buttonText, buttonAction) {
return {
cards: [{
header: {
title: title,
subtitle: 'Powered by Apps Script'
},
sections: [{
widgets: [{
textParagraph: {
text: description
}
}, {
buttons: [{
textButton: {
text: buttonText,
onClick: {
action: {
actionMethodName: buttonAction
}
}
}
}]
}]
}]
}]
};
}
// コマンド処理
function handleCommand(command, user, space) {
const [action, ...args] = command.split(' ');
switch (action.toLowerCase()) {
case 'help':
return createHelpCard();
case 'weather':
return getWeatherInfo(args[0] || 'Tokyo');
case 'schedule':
return getCalendarEvents(user);
case 'create':
return createTask(args.join(' '), user);
default:
return createTextResponse(`不明なコマンド: ${action}. 'help'と入力してください。`);
}
}
// ヘルプカードの作成
function createHelpCard() {
return {
cards: [{
header: {
title: '🤖 Bot Commands',
subtitle: 'Available commands'
},
sections: [{
widgets: [{
keyValue: {
topLabel: 'Weather',
content: '@bot weather [city] - 天気情報を取得',
icon: 'DESCRIPTION'
}
}, {
keyValue: {
topLabel: 'Schedule',
content: '@bot schedule - 今日の予定を表示',
icon: 'CLOCK'
}
}, {
keyValue: {
topLabel: 'Task',
content: '@bot create [task] - タスクを作成',
icon: 'BOOKMARK'
}
}]
}]
}]
};
}
// 天気情報取得
function getWeatherInfo(city) {
try {
const apiKey = PropertiesService.getScriptProperties().getProperty('WEATHER_API_KEY');
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric&lang=ja`;
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response.getContentText());
if (data.cod === 200) {
return {
cards: [{
header: {
title: `🌤️ ${data.name}の天気`,
subtitle: '現在の気象情報'
},
sections: [{
widgets: [{
keyValue: {
topLabel: '天気',
content: data.weather[0].description,
icon: 'DESCRIPTION'
}
}, {
keyValue: {
topLabel: '気温',
content: `${data.main.temp}°C`,
icon: 'CLOCK'
}
}, {
keyValue: {
topLabel: '湿度',
content: `${data.main.humidity}%`,
icon: 'DESCRIPTION'
}
}]
}]
}]
};
}
} catch (error) {
console.error('Weather API error:', error);
}
return createTextResponse('❌ 天気情報の取得に失敗しました');
}
// Google Calendar統合
function getCalendarEvents(user) {
try {
const calendar = CalendarApp.getDefaultCalendar();
const today = new Date();
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
const events = calendar.getEvents(today, tomorrow);
if (events.length === 0) {
return createTextResponse('📅 今日は予定がありません');
}
const widgets = events.slice(0, 5).map(event => ({
keyValue: {
topLabel: event.getStartTime().toLocaleTimeString('ja-JP', { hour: '2-digit', minute: '2-digit' }),
content: event.getTitle(),
icon: 'CLOCK'
}
}));
return {
cards: [{
header: {
title: '📅 今日の予定',
subtitle: `${events.length}件のイベント`
},
sections: [{ widgets }]
}]
};
} catch (error) {
console.error('Calendar API error:', error);
return createTextResponse('❌ カレンダー情報の取得に失敗しました');
}
}
// Google Sheets統合でタスク管理
function createTask(taskDescription, user) {
try {
const spreadsheetId = PropertiesService.getScriptProperties().getProperty('TASK_SHEET_ID');
const sheet = SpreadsheetApp.openById(spreadsheetId).getActiveSheet();
const timestamp = new Date();
const taskId = Utilities.getUuid();
sheet.appendRow([taskId, taskDescription, user.displayName, timestamp, 'Open']);
return {
cards: [{
header: {
title: '✅ タスク作成完了',
subtitle: 'New task created'
},
sections: [{
widgets: [{
keyValue: {
topLabel: 'Task ID',
content: taskId,
icon: 'BOOKMARK'
}
}, {
keyValue: {
topLabel: 'Description',
content: taskDescription,
icon: 'DESCRIPTION'
}
}, {
keyValue: {
topLabel: 'Created by',
content: user.displayName,
icon: 'PERSON'
}
}]
}]
}]
};
} catch (error) {
console.error('Task creation error:', error);
return createTextResponse('❌ タスクの作成に失敗しました');
}
}
// ボタンアクション処理
function onCardClick(event) {
const action = event.action.actionMethodName;
const parameters = event.action.parameters;
switch (action) {
case 'markComplete':
return markTaskComplete(parameters[0].value);
case 'deleteTask':
return deleteTask(parameters[0].value);
case 'assignTask':
return assignTask(parameters[0].value, event.user);
default:
return createTextResponse('不明なアクションです');
}
}
// テキストレスポンス作成ヘルパー
function createTextResponse(text) {
return { text: text };
}
Google Chat Webhook統合
// Node.js でのWebhook実装
const express = require('express');
const { IncomingWebhook } = require('@google-cloud/chat');
const app = express();
app.use(express.json());
// Webhook URLの設定
const WEBHOOK_URL = process.env.GOOGLE_CHAT_WEBHOOK_URL;
class GoogleChatNotifier {
constructor(webhookUrl) {
this.webhookUrl = webhookUrl;
}
// 基本メッセージ送信
async sendMessage(text) {
try {
const response = await fetch(this.webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ text })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Webhook送信エラー:', error);
throw error;
}
}
// カード形式メッセージ送信
async sendCard(title, subtitle, widgets) {
const message = {
cards: [{
header: {
title: title,
subtitle: subtitle
},
sections: [{ widgets }]
}]
};
try {
const response = await fetch(this.webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(message)
});
return await response.json();
} catch (error) {
console.error('Card送信エラー:', error);
throw error;
}
}
// GitHubイベント通知
async sendGitHubNotification(eventType, payload) {
switch (eventType) {
case 'push':
return this.handlePushEvent(payload);
case 'pull_request':
return this.handlePullRequestEvent(payload);
case 'issues':
return this.handleIssueEvent(payload);
default:
return this.sendMessage(`🔄 Unknown GitHub event: ${eventType}`);
}
}
async handlePushEvent(payload) {
const { commits, repository, pusher } = payload;
const commitsList = commits.slice(0, 3).map(commit =>
`• [${commit.id.substring(0, 7)}](${commit.url}) ${commit.message}`
).join('\n');
const widgets = [
{
keyValue: {
topLabel: 'Repository',
content: repository.full_name,
contentMultiline: false,
icon: 'BOOKMARK'
}
},
{
keyValue: {
topLabel: 'Pusher',
content: pusher.name,
icon: 'PERSON'
}
},
{
keyValue: {
topLabel: 'Commits',
content: `${commits.length} commits`,
icon: 'DESCRIPTION'
}
},
{
textParagraph: {
text: `<b>Recent commits:</b>\n${commitsList}`
}
},
{
buttons: [{
textButton: {
text: 'View Changes',
onClick: {
openLink: {
url: payload.compare
}
}
}
}]
}
];
return this.sendCard('🚀 Push Event', repository.full_name, widgets);
}
async handlePullRequestEvent(payload) {
const { pull_request: pr, action } = payload;
const emoji = {
opened: '📝',
closed: pr.merged ? '🎉' : '❌',
reopened: '🔄'
};
const widgets = [
{
keyValue: {
topLabel: 'Action',
content: action,
icon: 'DESCRIPTION'
}
},
{
keyValue: {
topLabel: 'Author',
content: pr.user.login,
icon: 'PERSON'
}
},
{
keyValue: {
topLabel: 'Branch',
content: `${pr.head.ref} → ${pr.base.ref}`,
icon: 'BOOKMARK'
}
},
{
textParagraph: {
text: pr.body || '説明なし'
}
},
{
buttons: [{
textButton: {
text: 'View PR',
onClick: {
openLink: {
url: pr.html_url
}
}
}
}]
}
];
return this.sendCard(
`${emoji[action]} Pull Request ${action}`,
`#${pr.number} - ${pr.title}`,
widgets
);
}
// CI/CDパイプライン通知
async sendDeploymentNotification(status, environment, service, version) {
const emoji = status === 'success' ? '✅' : '❌';
const color = status === 'success' ? '#28a745' : '#d73a49';
const widgets = [
{
keyValue: {
topLabel: 'Status',
content: status.toUpperCase(),
icon: status === 'success' ? 'STAR' : 'DESCRIPTION'
}
},
{
keyValue: {
topLabel: 'Environment',
content: environment,
icon: 'BOOKMARK'
}
},
{
keyValue: {
topLabel: 'Service',
content: service,
icon: 'DESCRIPTION'
}
},
{
keyValue: {
topLabel: 'Version',
content: version,
icon: 'CLOCK'
}
}
];
return this.sendCard(
`${emoji} Deployment ${status}`,
`${service} to ${environment}`,
widgets
);
}
// アラート通知
async sendAlert(severity, message, details) {
const emoji = {
'critical': '🚨',
'warning': '⚠️',
'info': 'ℹ️'
};
const widgets = [
{
keyValue: {
topLabel: 'Severity',
content: severity.toUpperCase(),
icon: 'DESCRIPTION'
}
},
{
keyValue: {
topLabel: 'Timestamp',
content: new Date().toLocaleString('ja-JP'),
icon: 'CLOCK'
}
},
{
textParagraph: {
text: `<b>Details:</b>\n${details}`
}
}
];
return this.sendCard(
`${emoji[severity]} Alert: ${message}`,
'System Notification',
widgets
);
}
}
// Express サーバーでの使用例
const notifier = new GoogleChatNotifier(WEBHOOK_URL);
// GitHub Webhook エンドポイント
app.post('/github-webhook', async (req, res) => {
const eventType = req.headers['x-github-event'];
const payload = req.body;
try {
await notifier.sendGitHubNotification(eventType, payload);
res.status(200).send('OK');
} catch (error) {
console.error('GitHub webhook処理エラー:', error);
res.status(500).send('Error');
}
});
// CI/CD通知エンドポイント
app.post('/deployment-notification', async (req, res) => {
const { status, environment, service, version } = req.body;
try {
await notifier.sendDeploymentNotification(status, environment, service, version);
res.status(200).send('OK');
} catch (error) {
console.error('Deployment notification エラー:', error);
res.status(500).send('Error');
}
});
// システムアラートエンドポイント
app.post('/alert', async (req, res) => {
const { severity, message, details } = req.body;
try {
await notifier.sendAlert(severity, message, details);
res.status(200).send('OK');
} catch (error) {
console.error('Alert送信エラー:', error);
res.status(500).send('Error');
}
});
app.listen(3000, () => {
console.log('Google Chat notification server running on port 3000');
});
Chat API を使用したBot開発
// Node.js でのChat API Bot実装
const { GoogleAuth } = require('google-auth-library');
const { google } = require('googleapis');
class GoogleChatBot {
constructor(credentials) {
this.auth = new GoogleAuth({
credentials: credentials,
scopes: ['https://www.googleapis.com/auth/chat.bot']
});
this.chat = google.chat({ version: 'v1', auth: this.auth });
}
// メッセージ送信
async sendMessage(spaceName, text, threadKey = null) {
try {
const request = {
parent: spaceName,
requestBody: {
text: text,
thread: threadKey ? { name: threadKey } : undefined
}
};
const response = await this.chat.spaces.messages.create(request);
return response.data;
} catch (error) {
console.error('メッセージ送信エラー:', error);
throw error;
}
}
// カードメッセージ送信
async sendCardMessage(spaceName, cards, threadKey = null) {
try {
const request = {
parent: spaceName,
requestBody: {
cards: cards,
thread: threadKey ? { name: threadKey } : undefined
}
};
const response = await this.chat.spaces.messages.create(request);
return response.data;
} catch (error) {
console.error('カードメッセージ送信エラー:', error);
throw error;
}
}
// スペース一覧取得
async listSpaces() {
try {
const response = await this.chat.spaces.list();
return response.data.spaces || [];
} catch (error) {
console.error('スペース一覧取得エラー:', error);
throw error;
}
}
// メッセージ履歴取得
async getMessages(spaceName, pageSize = 100) {
try {
const response = await this.chat.spaces.messages.list({
parent: spaceName,
pageSize: pageSize
});
return response.data.messages || [];
} catch (error) {
console.error('メッセージ履歴取得エラー:', error);
throw error;
}
}
// メンバー一覧取得
async getMembers(spaceName) {
try {
const response = await this.chat.spaces.members.list({
parent: spaceName
});
return response.data.memberships || [];
} catch (error) {
console.error('メンバー一覧取得エラー:', error);
throw error;
}
}
// インタラクティブカード作成
createTaskCard(taskId, title, description, assignee) {
return [{
header: {
title: '📋 タスク管理',
subtitle: 'Task Management System'
},
sections: [{
widgets: [
{
keyValue: {
topLabel: 'Task ID',
content: taskId,
icon: 'BOOKMARK'
}
},
{
keyValue: {
topLabel: 'Title',
content: title,
icon: 'DESCRIPTION'
}
},
{
textParagraph: {
text: `<b>Description:</b>\n${description}`
}
},
{
keyValue: {
topLabel: 'Assignee',
content: assignee,
icon: 'PERSON'
}
},
{
buttons: [
{
textButton: {
text: '✅ Complete',
onClick: {
action: {
actionMethodName: 'completeTask',
parameters: [{ key: 'taskId', value: taskId }]
}
}
}
},
{
textButton: {
text: '✏️ Edit',
onClick: {
action: {
actionMethodName: 'editTask',
parameters: [{ key: 'taskId', value: taskId }]
}
}
}
}
]
}
]
}]
}];
}
// プロジェクト管理カード
createProjectCard(projectName, progress, milestones) {
const progressBar = '█'.repeat(Math.floor(progress / 10)) +
'░'.repeat(10 - Math.floor(progress / 10));
const milestoneWidgets = milestones.map(milestone => ({
keyValue: {
topLabel: milestone.name,
content: milestone.completed ? '✅ Complete' : '⏳ In Progress',
icon: milestone.completed ? 'STAR' : 'CLOCK'
}
}));
return [{
header: {
title: `🚀 ${projectName}`,
subtitle: 'Project Status'
},
sections: [{
widgets: [
{
keyValue: {
topLabel: 'Progress',
content: `${progress}% ${progressBar}`,
icon: 'DESCRIPTION'
}
},
...milestoneWidgets
]
}]
}];
}
// レポート作成
async generateDailyReport(spaceName) {
try {
const now = new Date();
const reportData = await this.fetchReportData(); // 外部システムからデータ取得
const widgets = [
{
keyValue: {
topLabel: 'Total Tasks',
content: reportData.totalTasks.toString(),
icon: 'BOOKMARK'
}
},
{
keyValue: {
topLabel: 'Completed',
content: reportData.completedTasks.toString(),
icon: 'STAR'
}
},
{
keyValue: {
topLabel: 'In Progress',
content: reportData.inProgressTasks.toString(),
icon: 'CLOCK'
}
},
{
textParagraph: {
text: `<b>Top Contributors:</b>\n${reportData.topContributors.join('\n')}`
}
}
];
const cards = [{
header: {
title: '📊 Daily Report',
subtitle: now.toLocaleDateString('ja-JP')
},
sections: [{ widgets }]
}];
return this.sendCardMessage(spaceName, cards);
} catch (error) {
console.error('日次レポート生成エラー:', error);
throw error;
}
}
// 外部システムデータ取得(例)
async fetchReportData() {
// 実際の実装では外部API、データベースなどからデータを取得
return {
totalTasks: 45,
completedTasks: 32,
inProgressTasks: 13,
topContributors: [
'👑 田中さん (8 tasks)',
'🥈 佐藤さん (6 tasks)',
'🥉 鈴木さん (5 tasks)'
]
};
}
}
// 使用例
async function main() {
const credentials = {
type: 'service_account',
project_id: process.env.GOOGLE_CLOUD_PROJECT_ID,
private_key_id: process.env.GOOGLE_PRIVATE_KEY_ID,
private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n'),
client_email: process.env.GOOGLE_CLIENT_EMAIL,
client_id: process.env.GOOGLE_CLIENT_ID,
auth_uri: 'https://accounts.google.com/o/oauth2/auth',
token_uri: 'https://oauth2.googleapis.com/token'
};
const bot = new GoogleChatBot(credentials);
// 日次レポートの送信
const spaceName = 'spaces/AAAAGCEIgKY'; // 実際のスペース名に置き換え
try {
await bot.generateDailyReport(spaceName);
console.log('日次レポートを送信しました');
} catch (error) {
console.error('エラー:', error);
}
}
// 定期実行(毎日9時に実行)
const cron = require('node-cron');
cron.schedule('0 9 * * *', () => {
main().catch(console.error);
});
環境変数設定
# .env ファイル
# Google Apps Script設定
SCRIPT_ID=your-apps-script-project-id
WEATHER_API_KEY=your-openweather-api-key
TASK_SHEET_ID=your-google-sheet-id
# Google Chat Webhook
GOOGLE_CHAT_WEBHOOK_URL=https://chat.googleapis.com/v1/spaces/xxx/messages?key=xxx
# Chat API認証情報
GOOGLE_CLOUD_PROJECT_ID=your-project-id
GOOGLE_PRIVATE_KEY_ID=your-private-key-id
GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nxxxx\n-----END PRIVATE KEY-----\n"
GOOGLE_CLIENT_EMAIL=your-service-account@your-project.iam.gserviceaccount.com
GOOGLE_CLIENT_ID=your-client-id
# 外部API連携
JIRA_API_TOKEN=your-jira-api-token
GITHUB_TOKEN=your-github-token
SLACK_WEBHOOK_URL=your-slack-webhook-url
# データベース(任意)
DATABASE_URL=your-database-connection-string
Docker設定例
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# 依存関係のインストール
COPY package*.json ./
RUN npm ci --only=production
# アプリケーションファイルのコピー
COPY . .
# 環境変数
ENV NODE_ENV=production
ENV PORT=3000
# ポート公開
EXPOSE 3000
# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# アプリケーション起動
CMD ["npm", "start"]
Google Cloud Functions デプロイ例
# cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/npm'
args: ['install']
- name: 'gcr.io/cloud-builders/npm'
args: ['run', 'test']
- name: 'gcr.io/cloud-builders/gcloud'
args:
- 'functions'
- 'deploy'
- 'google-chat-bot'
- '--source=.'
- '--trigger-http'
- '--runtime=nodejs18'
- '--entry-point=handleChatEvent'
- '--memory=512MB'
- '--timeout=60s'
- '--set-env-vars=NODE_ENV=production'
- '--allow-unauthenticated'
timeout: '600s'
カード型UI設計ガイド
// Card Builder Tool を使用したUI設計
function createAdvancedCard() {
return {
cardsV2: [{
cardId: 'unique-card-id',
card: {
header: {
title: '📋 Advanced Task Manager',
subtitle: 'Powered by Google Chat API',
imageUrl: 'https://developers.google.com/chat/images/logo.png',
imageType: 'CIRCLE'
},
sections: [
{
header: 'Quick Actions',
widgets: [
{
buttonList: {
buttons: [
{
text: '➕ Create Task',
onClick: {
action: {
function: 'createTaskDialog'
}
}
},
{
text: '📊 View Report',
onClick: {
action: {
function: 'generateReport'
}
}
}
]
}
}
]
},
{
header: 'Recent Tasks',
collapsible: true,
widgets: [
{
decoratedText: {
text: '✅ タスク1が完了しました',
startIcon: {
knownIcon: 'STAR'
},
button: {
text: 'View',
onClick: {
openLink: {
url: 'https://example.com/task/1'
}
}
}
}
}
]
}
]
}
}]
};
}