カテゴリー: BackEnd

PHPでGmail APIを利用してメールデータを取得してみる その2

はじめに

前回は、Gmail APIを利用してメールデータ取得してみました。
今回は、ここから件名と本文を取得してみたいと思います。

メールの内容取得

早速、メールの内容取得してみたいと思います。
使用しているAPIについては公式ドキュメントを参照してみてください。
users_messages->get()で取得します。

    public function index(Request $request)
    {
        $client = $this->getClient();
        $service = new Gmail($client);

        $user = 'me';
        $messages = $service->users_messages->listUsersMessages($user);

        foreach ($messages->getMessages() as $message) {
            $message_id = $message->getID();
            $message_contents = $service->users_messages->get($user, $message_id);
        }
    }

取得したレスポンスの中身は、以下の様な構成になっています。
件名や本文は、payloadキーで取得するMessagePartオブジェクトの中に中に入っています。

{
  "id": string,
  "threadId": string,
  "labelIds": [
    string
  ],
  "snippet": string,
  "historyId": string,
  "internalDate": string,
  "payload": {
    object (MessagePart)
  },
  "sizeEstimate": integer,
  "raw": string
}

MessagePartオブジェクト

MessagePartオブジェクトは以下の様に構成されています。

{
  "partId": string,
  "mimeType": string,
  "filename": string,
  "headers": [
    {
      object (Header)
    }
  ],
  "body": {
    object (MessagePartBody)
  },
  "parts": [
    {
      object (MessagePart)
    }
  ]
}

件名

件名はheadersキーで取得するHeaderオブジェクトに含まれています。
様々な値がオブジェクト形式で保管されているので、件名のオブジェクト取得してその値(実際の件名)を取得します。

"headers": [
      {
        "name": "Delivered-To",
        "value": "sample@test.com"
      },
      {
        "name": "Received",
        "value": "by 2002:a05:6a10:c706:b0:2f4:9a11:6104 with SMTP id m6csp843972pxw;        Fri, 23 Sep 2022 20:50:08 -0700 (PDT)"
      },
      {
        "name": "X-Google-Smtp-Source",
        "value": "AMsMyM6bfA8EtKcfhKA16TuTXF6LwqO0APXe8tjgzkYSAEGuZSpJf6djgjkZ8bNwoU0ZYKU1gt+9"
      },
      {
        "name": "Subject",
        "value": "DX\u30bd\u30ea\u30e5\u30fc\u30b7\u30e7\u30f3\u55b6\u696d\u8077\u30fbSES~~"
      }
]
// ヘッダーの取得
$headers = $message_contents['payload']['headers']; // ヘッダーオブジェクトの配列
$subject_key = array_search('Subject', array_column($headers, 'name')); // ヘッダーオブジェクトの配列から件名オブジェクトの連番キーを取得
$subject = $headers[$subject_key]->value; // 件名のオブジェクトからvalueプロパティの値を取得(件名の取得)

本文

本文は、bodyキーで取得するMessagePartBodyオブジェクトの中のdataプロパティに入っています。

{
  "attachmentId": string,
  "size": integer,
  "data": string
}
$url_safe_data = $message_contents['payload']['body']['data']; // 本文の取得
$data = str_replace(array('-', '_'), array('+', '/'), $url_safe_data); // // URL用のBase64エンコーディングされているため、文字置換
$decoded_data = base64_decode($data); // Base64デコードする

multipartの場合

単純な構成のメールであれば上記の方法で本文取得出来ますが、Content-typeがmultipartになっている場合は取得方法が変わります。
multipartの構成については、こちらの解説が分かりやすかったです。
要はメール構成が複雑になると(画像や絵文字や添付ファイルなど)それぞれのデータ取得までの階層が深くなるようです。
MessagePartBodyも同様に、Content-typeがmultipartになっている場合dataは空になっています。
その代わり、MessagePartpartsキーの中に、配列が格納されています。
この配列の中に、更にMessagePartが格納されています。
メール構成によってはこの様に、ひたすらpartsキーの中に配列が含まれ階層が深くなる可能性があります。
通常は多くても3~4階層までいけば本文取得は出来ると思います。

"body": {
    "attachmentId": null,
    "data": null,
    "size": 0
  },
  "parts": [
    {
      "filename": "",
      "mimeType": "text\/plain",
      "partId": "0",
      "headers": [
        {
          "name": "Content-Type",
          "value": "text\/plain; charset=UTF-8"
        },
        {
          "name": "Content-Transfer-Encoding",
          "value": "7bit"
        }
      ],
      "body": {
        "attachmentId": null,
        "data": "TWVyZ2VkICM4MTMgaW50byBkZXZlbG9wLg0KDQotLSANClJlcGx5IHRvIHRoaXMgZW1haWwgZGlyZWN0bHkgb3IgdmlldyBpdCBvbiBHaXRIdWI6DQpodHRwczovL2dp~~",
        "size": 283
      }
    },
    {
      "filename": "",
      "mimeType": "text\/html",
      "partId": "1",
      "headers": [
        {
          "name": "Content-Type",
          "value": "text\/html; charset=UTF-8"
        },
        {
          "name": "Content-Transfer-Encoding",
          "value": "7bit"
        }
      ],
      "body": {
        "attachmentId": null,
        "data": "PHA-PC9wPg0KPHAgZGlyPSJhdXRvIj5NZXJnZWQgPGEgY2xhc3M9Imlzc3VlLWxpbms~~",
        "size": 1876
      }
    }
  ]

本文の取得

階層が深くても本文取得出来るように配列の中のMessagePartのmimeTypeを見て本文取得出来るまで再帰処理を行います。
(他にもっと良い方法あるかもしれません)
これでmultipartメールの本文取得が出来ました。

    $body_parts = $message_contents['payload']['parts'];
    $body = $this->get_body($body_parts);

    private function get_body($body_parts)
    {
        foreach ($body_parts as $body_part) {
            // text/htmlなら、本文取得
            if ($body_part->mimeType == 'text/html') {
                $url_safe_data = $body_part->body->data;
                $data = str_replace(array('-', '_'), array('+', '/'), $url_safe_data);
                return base64_decode($data);
            }
        }

        return $this->get_body($body_part->parts);
    }

全文

最後に全文載せておきます。

    public function index(Request $request)
    {
        $client = $this->getClient();
        $service = new Gmail($client);

        $user = 'me';
        $messages = $service->users_messages->listUsersMessages($user);
        
        foreach ($messages->getMessages() as $message) {
            $message_id = $message->getID();
            $message_contents = $service->users_messages->get($user, $message_id); // メッセージ内容の取得

            // ヘッダーの取得
            $headers = $message_contents['payload']['headers']; // ヘッダーオブジェクトの配列
            $subject_key = array_search('Subject', array_column($headers, 'name')); // ヘッダーオブジェクトの配列から件名オブジェクトの連番キーを取得
            $subject = $headers[$subject_key]->value; // 件名のオブジェクトからvalueプロパティの値を取得(件名の取得)

            // Body
            $size = $message_contents['payload']['body']['size'];
            if (!empty($size)) {
                $url_safe_data = $message_contents['payload']['body']['data']; // 本文の取得
                $data = str_replace(array('-', '_'), array('+', '/'), $url_safe_data); // URL用のBase64エンコーディングされているため、文字置換
                $decoded_data = base64_decode($data); // Base64デコードする
            } else {
                $body_parts = $message_contents['payload']['parts'];
                $body = $this->get_body($body_parts);
            }
        }
    }

    private function get_body($body_parts)
    {
        foreach ($body_parts as $body_part) {
            // text/htmlなら、本文取得
            if ($body_part->mimeType == 'text/html') {
                $url_safe_data = $body_part->body->data;
                $data = str_replace(array('-', '_'), array('+', '/'), $url_safe_data);
                return base64_decode($data);
            }
        }

        return $this->get_body($body_part->parts);
    }

さいごに

Gmail API を使う機会はあまりないかもしれませんが、自分が実装した時に困った部分なので、だれかの参考になれば。

おすすめ書籍

Yossy

シェア
執筆者:
Yossy
タグ: php

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

3週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前