Node.jsでGmailの最新のメール内容を取得する


久しぶりにGmail APIのチュートリアルを見たらアップデートされてたので試してみました。

ここでは、 **特定のメールから届いてるメールの最新の値を取得**してみます。

## 認証情報の準備

[Google公式ライブラリを利用してNode.jsからGmailの送受信をしてみよう](https://dotstud.io/blog/gmail-api-from-nodejs/)や[公式チュートリアル](https://developers.google.com/gmail/api/quickstart/nodejs?hl=ja)を参考に、`クレデンシャル`と`トークン`の情報を取得しましょう。

### スコープ

メソッド的には、`gmail.users.messages.list`と`gmail.users.messages.get`を利用します。

> * 参考: https://developers.google.com/gmail/api/reference/rest/v1/users.messages/list
> * 参考: https://developers.google.com/gmail/api/reference/rest/v1/users.messages/list

今回使うトークン取得の際のスコープですが、以下の6種類になります。

```
https://mail.google.com/
https://www.googleapis.com/auth/gmail.modify
https://www.googleapis.com/auth/gmail.readonly
https://www.googleapis.com/auth/gmail.addons.current.message.metadata
https://www.googleapis.com/auth/gmail.addons.current.message.readonly
https://www.googleapis.com/auth/gmail.addons.current.message.action
```

> * 参考: [OAuth 2.0 Scopes for Google APIs](https://developers.google.com/identity/protocols/oauth2/scopes)
> * 参考: [GmailのAPIでのエラー: Metadata scope doesn't allow format FULL](https://qiita.com/n0bisuke/items/f8eb4b3abe5fae7c9a4a)

## コード

環境変数`GOOGLE_CREDENTIALS`と`GOOGLE_TOKEN`にそれぞれ準備した値が入っている前提です。

[dotenv](https://www.npmjs.com/package/dotenv)など使うと便利ですね。

```js
'use strict';

const {google} = require('googleapis');

//認証関数
const authorize = () => {
    const credentials = JSON.parse(process.env.GOOGLE_CREDENTIALS);
    // console.log(credentials);
    const {client_secret, client_id, redirect_uris} = credentials.installed;
    const oAuth2Client = new google.auth.OAuth2(
        client_id,
        client_secret,
        redirect_uris[0]
    );
    oAuth2Client.setCredentials(JSON.parse(process.env.GOOGLE_TOKEN));

    return oAuth2Client;
}

//メッセージ取得関数
const getLastMessage = async (auth) => {
    const gmail = google.gmail({version: 'v1', auth});
    
    //メッセージリスト取得
    const resList = await gmail.users.messages.list({userId: 'me', q: 'from:news@xxxx.com'});
    const lastMessage = resList.data.messages[0]; //最新
    
    //メッセージIDから内容を取得
    const resMes = await gmail.users.messages.get({
      userId: 'me',
      id: lastMessage.id,
      format: 'FULL'
    });
    
    //base64で取得されるのでデコード
    const buf = new Buffer.from(resMes.data.payload.body.data, 'base64');
    const str = buf.toString();

    return str;
}

const main = async () => {
    const auth = authorize();
    const msg = await getLastMessage(auth);
    console.log(msg);
}
main();

```

トークン作成系のコードは削除しているので、トークン作成をしてから実行しましょう。

## 解説1: `q`オプションで検索

`users.messages/list`のAPIを利用する場合に`q`というオプションで検索することができます。

> Only return messages matching the specified query. Supports the same query format as the Gmail search box. For example, "from:someuser@example.com rfc822msgid:<somemsgid@example.com> is:unread". Parameter cannot be used when accessing the api using the gmail.metadata scope.
> https://developers.google.com/gmail/api/reference/rest/v1/users.messages/list

`hogehoge`だと **hogehogeが含まれているメールを検索**、
`from:someuser@example.com`だと **someuser@example.comから送られてきているメールを検索** みたいな感じでGmailで検索する際の指定のように扱えます。

## 解説2: 取得したメッセージはbase64デコード

`gmail.users.message.get()`で取得したメッセージの本文はbase64形式で返ってきます。

ドキュメントを見ると書いてますね。

> string (bytes format)

> The body data of a MIME message part as a base64url encoded string. May be empty for MIME container types that have no message body or when the body data is sent as a separate attachment. An attachment ID is present if the body data is contained in a separate attachment.

> A base64-encoded string.
> [REST Resource: users.messages.attachments](https://developers.google.com/gmail/api/reference/rest/v1/users.messages.attachments#MessagePartBody)

ということで、そのままだと読めないのでデコード処理をしています。

```js
//省略

const buf = new Buffer.from(resMes.data.payload.body.data, 'base64');
const str = buf.toString();

//省略
```

`esMes.data.payload.body.data`にbase64文字列が入ってくるのでBufferでデコードしています。`str`では通常のメール本文が見れます。

## まとめと所感

[トークン作成で少しハマりましたが](https://qiita.com/n0bisuke/items/f8eb4b3abe5fae7c9a4a)、作成後は割となんなく扱えました。

`gmail.users.messages.list()`してから`gmail.users.messages.get()`をするという2ステップで実装してみましたが、もっとスマートなやり方があるような気がするのでもう少しドキュメント眺めてみたい