0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Google Chat API】「スペースの過去ログを取りたいだけなのに」403エラーと戦い続けた泥沼の記録と、最終的な正解

Posted at

【Google Chat API】「スペースの過去ログを取りたいだけなのに」403エラーと戦い続けた泥沼の記録と、最終的な正解

はじめに

社内のGoogle Chat(スペース)に蓄積された会話データを取得し、AIで解析したいというニーズがあり、API連携の実装を試みました。
「APIを叩くだけなら簡単だろう」と高を括っていましたが、認証周りの仕様、特に「Botとしての権限」と「ユーザーなりすまし」の違いで泥沼にはまりました。
PHP(レンタルサーバー)での実装から始まり、最終的にPython(VPS)+ドメイン全体の委任で解決に至った経緯と、動くコードを共有します。

結論:何が正解だったか

先に結論を書くと、組織内のチャットログを自由に取得するには、以下の構成が必須でした。

  1. 単純なBot参加ではダメ: Service Accountを作るだけでは、Botが招待されていない過去ログなどは取得できない(403エラーになる)。
  2. 「ドメイン全体の委任」が必要: Google Workspace管理コンソールで、そのService Accountに「ドメイン内のユーザーになりすます権限」を与える必要がある。
  3. Pythonライブラリが強い: PHPでCURLを頑張るより、公式ライブラリが充実しているPython(google-auth, google-api-python-client)を使う方が圧倒的に楽。

苦戦の経緯(ハマりポイント)

1. PHP × Service Account の限界

最初はレンタルサーバー(PHP)で実装を試みました。GCPでService Accountを作成し、JSONキーを発行して認証させましたが、以下のエラーが頻発しました。

{  
  "error": {  
    "code": 403,  
    "message": "Request had insufficient authentication scopes.",  
    "status": "PERMISSION\_DENIED",  
    "details": \[  
      {  
        "reason": "ACCESS\_TOKEN\_SCOPE\_INSUFFICIENT"  
      }  
    \]  
  }  
}

Bot用のスコープ https://www.googleapis.com/auth/chat.bot だけを指定しても、人間用の読み取りスコープ chat.messages.readonly を混ぜてもNG。
Google Chat APIの仕様として、**「Botとして振る舞うなら、人間用の読み取りAPIは使わせない(Botが参加している部屋しか見せない)」**という強い制約があるようでした。

2. ライブラリのバージョン問題

PHPの google/apiclient は更新頻度は高いものの、Chat API特有のクラス(Google\Service\Chat)が環境によっては見つからなかったり、古い HangoutsChat クラスが混在していたりと、環境構築だけで時間を浪費しました。

解決策:Python × ドメイン全体の委任

手順1:GCP側での準備

  1. Google Cloud Consoleでプロジェクトを作成し、「Google Chat API」を有効化。
  2. IAMと管理 > サービスアカウントを作成。
  3. キー(JSON)を作成してダウンロード。
  4. 【重要】 JSONファイル内の client_id (数字の羅列)をコピーしておく。

手順2:Google Workspace 管理コンソールでの設定

ここが最大の山場です。API経由でログを吸い出すには、特権管理者による**「ドメイン全体の委任(Domain-Wide Delegation)」**設定が必須でした。

  1. Google Workspace 管理コンソールへアクセス。
  2. セキュリティ > アクセスとデータ管理 > APIの制御 > ドメイン全体の委任を管理。
  3. 「新しく追加」をクリック。
  4. 承認する。

手順3:Pythonコードの実装

Flaskを使って、指定したスペースのログを取得するAPIサーバーを構築しました。
ポイントは with_subject() メソッドを使って、管理者やメンバーのメールアドレスになりすますことです。

**requirements.txt**

flask  
google-auth  
google-api-python-client
**app.py (抜粋)**

import os  
from datetime import datetime, timedelta, timezone  
from flask import Flask, jsonify, request  
from google.oauth2 import service\_account  
from googleapiclient.discovery import build

app \= Flask(\_\_name\_\_)

\# 設定項目  
KEY\_FILE\_PATH \= '/path/to/service-account-key.json' \# ダウンロードしたJSON  
IMPERSONATE\_USER \= 'admin@example.co.jp' \# 【重要】なりすますユーザーのメアド  
SCOPES \= \['\[https://www.googleapis.com/auth/chat.messages.readonly\](https://www.googleapis.com/auth/chat.messages.readonly)'\]

@app.route('/api/chat\_log', methods=\['GET'\])  
def get\_chat\_log():  
    target\_space \= request.args.get('space') \# 例: spaces/AAAAxxxx  
    days\_param \= request.args.get('days')    \# 例: 3 (過去3日分)  
      
    if not target\_space:  
        return jsonify({"error": "Missing space parameter"}), 400

    try:  
        \# 1\. 認証情報の作成  
        creds \= service\_account.Credentials.from\_service\_account\_file(  
            KEY\_FILE\_PATH, scopes=SCOPES  
        )  
        \# 2\. 【ここが魔法の1行】ユーザーになりすます  
        delegated\_creds \= creds.with\_subject(IMPERSONATE\_USER)

        \# 3\. クライアント構築  
        service \= build('chat', 'v1', credentials=delegated\_creds)

        \# 4\. 取得ロジック(ページネーション対応)  
        all\_messages \= \[\]  
        page\_token \= None  
        cutoff\_time \= None  
          
        if days\_param:  
            cutoff\_time \= datetime.now(timezone.utc) \- timedelta(days=int(days\_param))

        while True:  
            result \= service.spaces().messages().list(  
                parent=target\_space,  
                orderBy='createTime desc',  
                pageSize=100,  
                pageToken=page\_token  
            ).execute()

            messages \= result.get('messages', \[\])  
            if not messages: break

            for msg in messages:  
                \# 日付フィルタなど必要な処理を記述  
                \# ...  
                all\_messages.append(msg)

            page\_token \= result.get('nextPageToken')  
            if not page\_token: break

        return jsonify({"count": len(all\_messages), "messages": all\_messages})

    except Exception as e:  
        return jsonify({"error": str(e)}), 500

if \_\_name\_\_ \== '\_\_main\_\_':  
    app.run(port=5000)

実行結果

このAPIに対して、以下のようにリクエストを送ります。

GET /api/chat_log?space=spaces/AAAAxxxxx&days=3

すると、Botを招待していないスペースであっても、指定したユーザー(IMPERSONATE_USER)が参加しているスペースであれば、無事にJSONでログが返ってきました!

{  
  "count": 18,  
  "messages": \[  
    {  
      "sender": "Unknown",   
      "text": "【日報未提出 通知】...",  
      "time": "2025-12-11T00:00:09.789871Z"  
    },  
    ...  
  \]  
}

※BotやWebhookからのメッセージの場合、sender情報の取得には制限があるようですが、本文(text)は問題なく取得できています。

まとめ

  • Google Chat APIでログ収集などの「管理・分析」をしたいなら、Bot権限で頑張るより「ドメイン全体の委任」を使うのが正解
  • PHPよりもPythonの方が、Google系APIのライブラリサポートが手厚く、AI連携(Gemini/OpenAI)への接続もスムーズ。
  • 403エラーが出たら、コードを疑う前に「管理コンソール」と「IAM」の設定を見直すべき。特にWorkspaceの管理コンソール側は盲点

同じような悩みを持つ方の参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?