Google Chat

コミュニケーションGoogle WorkspaceChat APIBot開発Apps ScriptWebhook

コミュニケーションツール

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 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'
                      }
                    }
                  }
                }
              }
            ]
          }
        ]
      }
    }]
  };
}