はじめに
開発でGmail APIを利用してメールの内容を取得したいということになり、Messageという構造体の取り扱いに苦戦したので、備忘録を兼ねて対処法を解説します。
目次
Message構造体とMessagePart構造体
listメソッドとMessage構造体
Message
構造体は、メールの一覧を取得する
GET https://gmail.googleapis.com/gmail/v1/users/{userId}/messages
というAPIのResponse bodyの主要な構成要素です。
まず、公式のマニュアルによると、Message
構造体は以下の要素からなります。
listメソッドのマニュアルに説明がありますが、このAPIではMessage
構造体のうちid
とthreadId
だけが埋められた状態で返ってくるので注意が必要です。つまり、本文の取得には別のAPIを使うことになります。それが次で説明するgetメソッドです。
getメソッドは
GET https://gmail.googleapis.com/gmail/v1/users/{userId}/messages/{id}
というAPIで利用できます。指定したid
のMessage
構造体がすべて埋められて返ってきます。これで本文を取得することができます。
本文はpayload
というキーで取り出せます。ただし、MessagePart
という別の構造体を扱うことになります。
getメソッドとMessagePart構造体
MessagePart
構造体の構成について、再びマニュアルを参照します。
マニュアルの日本語訳が甘いので注意が必要です。「コンテナMIMEメッセージの部分」や「MIMEメッセージの部分タイプ」は、英語マニュアルの'message part'を指しています。
MIMEタイプというのは、文書や添付ファイルの形式を統一的に表すための記法です。本文と添付ファイルが一緒になって(一つのメールとして)送られるような電子メールを念頭において、MIMEタイプは「マルチパートタイプ」という概念を持っています。つまり、最上位にはMIMEタイプとしてmultipart
というタイプをもつ外枠だけがあり、その内側にtext
(本文)やvideo
(動画ファイル)といったタイプをもつ個別の構成要素(それがこのマニュアルで言うところの'message part'です)があるということです。
以上の知識を前提にすると、body
フィールドの説明にある「コンテナMIMEメッセージ部分」というのが、上で説明した「外枠」にあたるものだとわかります。つまり、本文と添付ファイルがあるようなメールを考えると、getメソッドで取得したMessagePart
構造体のbody
を単にとるだけでは、メールの本文は取得できません。ここでのMessagePart
構造体は「外枠」であり、そのbody
は空になっているからです。この場合、まずparts
フィールドの値(これはMessagePart
構造体のリストになっています)を取得し、そのリストの各MessagePart
構造体についてbody
の中身を調べる必要がある、ということです。
コード例(Dart)
以上の内容を踏まえて、APIを用いてメールの本文を取り出すコードを実装してみます。
Dartにはgoogle_sign_inというパッケージがあるので、それを使います。パッケージの使い方の説明は省きます(適宜リンクを参照してください)。
この実装では、外枠のMessagePart
構造体のparts
フィールドが空であるかどうかを調べて、メールが複数の要素に別れている場合(添付ファイルを持つ場合など)にも適切に本文を取り出せるようにしています。単純にpayload['body']['data]
だけではうまくいきません(理由は上で説明したとおりです)。
Future<List<dynamic>> _fetchEmails() async {
final maxResults = 1; //取得するメールの数
final headers = await _googleSignIn.currentUser!.authHeaders;
final response = await http.get(
Uri.parse(
'https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=$maxResults'),
headers: headers,
);
if (response.statusCode == 200) {
var listRawTexts = [];
final jsonData = jsonDecode(response.body);
final messages = jsonData['messages'];
for (final msg in messages) {
final msgId = msg['id'];
final response = await http.get(
Uri.parse(
'https://gmail.googleapis.com/gmail/v1/users/me/messages/$msgId?format=full'),
headers: headers,
);
final jsonData = jsonDecode(response.body);
final payload = jsonData['payload']; //外枠のMessagePart構造体を取り出す
final parts = payload['parts']; //partsフィールドを取り出す
String? body;
if (parts != null) { //メールは複数の要素に分かれている
for (var part in parts) { //各要素のbodyを調べる
final partData = part['body'];
if (partData['size'] > 0) {
final data = partData['data'];
final decodedData = utf8.decode(base64Url.decode(data));
body = decodedData;
}
}
} else { //メールは単一の要素からなる
final data = payload['body']['data']; //単に外枠のbodyを取り出せば良い
final decodedData = utf8.decode(base64Url.decode(data));
body = decodedData;
}
listRawTexts.add(body);
}
return listRawTexts;
} else {
throw Exception('Failed to fetch emails');
}
参考文献
本文中で直接リンクを示していないもののみ挙げます。