googleapisを利用してGmailをAPIで取得する

## 目的

PlaywrightでGoogle Gmail APIを使用してメールを受信すること。

Playwrightでe2eテストコードを実装する中で、メール認証時の認証コードを取得したかったのでAPIでメールを取得したかった。

## 前提

- Googleアカウントがあること

## 事前準備

まずはgoogleapisでGmailを取得するために必要な認証情報を取得します。

### Google Cloud Consoleでプロジェクトを作成

以下にアクセスしてプロジェクトを作成します。

https://console.developers.google.com/project


以下の手順でプロジェクトを作成

1. Google Cloud Consoleへアクセス
2. プロジェクトを作成ボタンをクリック
3. 適当にプロジェクト名を入力
4. 作成ボタンをクリック

### Gmail APIを有効化

サイドバーを開き「APIとサービス > ライブラリ」をクリックし、APIライブラリからGmail APIを有効化します。

https://console.cloud.google.com/apis/library

以下の手順でGmail APIを有効化

1. APIライブラリへアクセス
2. 検索窓に「Gmail」と入力
3. 検索結果の「Gmail API」をクリック
4. 「有効にする」ボタンをクリック

### OAuth 2.0 クライアントIDを作成

「APIとサービス > 認証情報」から認証情報を作成します。

1. 「認証情報を作成」ボタンをクリック
2. OAuth クライアントIDをクリック
    1. OAuth同意がまだの場合は設定が必要なので、「OAuth同意画面」へアクセスして設定
3. 設定
    1. アプリケーションの種類：ウェブアプリケーション
    2. 名前：適当でOK（自分は初期値のままにした）
    3. 承認済のリダイレクトURI： [`http://localhost:3000/oauth2callback`](http://localhost:3000/oauth2callback)
        1. 後ほどインストールするライブラリでこのように設定するよう記載があります。

### 認証情報を取得

クライアントIDの作成が完了すると認証情報が作成されます。

後ほど以下の項目を利用します。

- クライアント ID
- クライアントシークレット

## googleapisでGmailを取得する

プロジェクトにgoogleapisをインストール

googleapisはGoogleが提供するAPIや開発者向けツールなどを含む、Google Cloud Platform（GCP）の一部です。今回はこちらを利用してGmailをAPIで取得します。

https://www.npmjs.com/package/googleapis

1.  `pnpm install googleapis` でインストール
    1. 利用しているパッケージマネージャでインストール方法は変更してください

### **認証URLを生成してcodeを取得**

アクセストークンを取得するためにアクセス許可を行います。

先ほど作成した認証情報と、設定したリダイレクトURIを利用します。

```jsx
const oauth2Client = new google.auth.OAuth2(
    'clientID', // クライアントID
    'clientsecret', // クライアントシークレット
    'http://localhost:3000/oauth2callback', // リダイレクトURI
  );

const scopes = ['https://www.googleapis.com/auth/gmail.readonly'];

const url = oauth2Client.generateAuthUrl({
  // 'online' (default) or 'offline' (gets refresh_token)
  access_type: 'offline',
  // If you only need one scope you can pass it as a string
  scope: scopes,
  prompt: 'consent', 
});
// コンソールにURLを表示して、アクセスし認証をしてもらう
console.log('Authorize this app by visiting this url:', url);
```

1. 上記のコードの認証情報の値を先ほど取得した認証情報に変更
2. 上記のコードを実行
3. コンソールに出力されたURLにアクセス
4. 認証
5. リダイレクトされたURLの`code=` 以降を取得

### リフレッシュトークンを取得

先ほど取得したcodeを使い、APIを実行するためのリフレッシュトークンを取得します。

```jsx
const oauth2Client = new google.auth.OAuth2(
    'clientID', // クライアントID
    'clientsecret', // クライアントシークレット
    'http://localhost:3000/oauth2callback', // リダイレクトURI
  );

// URL取得した認証コード
const code =
  'code';
// 認証コードをトークンに交換
const { tokens } = await oauth2Client.getToken(code);
console.log('Tokens:', tokens);
```

1. 上記のコードの認証情報の値を先ほど取得した認証情報に変更
2. 上記のコードの`code='code'`の値を先ほど取得したcodeに変更
3. 上記のコードをを実行
4. コンソールに出力されたURLからリフレッシュトークンを取得

### Gmailを取得

最新のメールを返すgetNewGmailという関数を作成しました。

また、認証情報等の値は.envで管理するよう修正してあります。

```jsx

import dotenv from 'dotenv';
import { google } from 'googleapis';
import type { gmail_v1 } from 'googleapis';

/** Read environment variables from file. https://github.com/motdotla/dotenv */
dotenv.config();

export async function getNewGmail(): Promise<null | gmail_v1.Schema$Message> {
  try {
    // OAuth2 クライアントを設定
    const oauth2Client = new google.auth.OAuth2(
      process.env.GOOGLE_CLIENT_ID, // クライアントID
      process.env.GOOGLE_CLIENT_SECRET, // クライアントシークレット
      'http://localhost:3000/oauth2callback', // リダイレクトURI
    );

    // 保存されたトークンをセット（事前に取得しておく必要があります）
    oauth2Client.setCredentials({
      refresh_token: process.env.GOOGLE_REFRESH_TOKEN,
    });

    // Gmail APIクライアントを初期化
    const gmail = google.gmail({ version: 'v1', auth: oauth2Client });

    // メールを取得
    const res = await gmail.users.messages.list({
      userId: 'me', // 自分自身
      maxResults: 10, // 最大取得数
    });

    // メールのIDを取得
    const messages = res.data.messages;
    if (!messages) return null;

    // 最初のメールの詳細を取得
    const message = await gmail.users.messages
      .get({
        userId: 'me',
        id: messages[0].id!,
      })
      .then((response) => response.data);
    return message;
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}
```

1. 上記のコードの認証情報の値を先ほど取得した認証情報に変更
2. 上記のコードをを実行

自分の場合は特定のエイリアス付きメールアドレス（sample+1@gmail.comのように+付きのメールアドレス）宛に届いた最新のメールを取得したかったので以下のように変更

```jsx
import dotenv from 'dotenv';
import { google } from 'googleapis';
import type { gmail_v1 } from 'googleapis';

/** Read environment variables from file. https://github.com/motdotla/dotenv */
dotenv.config();

/**
 * 特定のメールアドレス宛の最新のGmailのメールを取得する
 *
 * @param   toEmail
 * @returns         null | gmail_v1.Schema$Message
 */
export async function getNewGmail(
  toEmail: string,
): Promise<null | gmail_v1.Schema$Message> {
  try {
    // OAuth2 クライアントを設定
    const oauth2Client = new google.auth.OAuth2(
      process.env.GOOGLE_CLIENT_ID, // クライアントID
      process.env.GOOGLE_CLIENT_SECRET, // クライアントシークレット
      'http://localhost:3000/oauth2callback', // リダイレクトURI
    );

    // 保存されたトークンをセット（事前に取得しておく必要があります）
    oauth2Client.setCredentials({
      refresh_token: process.env.GOOGLE_REFRESH_TOKEN,
    });

    // Gmail APIクライアントを初期化
    const gmail = google.gmail({ version: 'v1', auth: oauth2Client });

    // エイリアスを含むメールを検索するクエリを設定

    const query = `to:${toEmail}`;

    // メールを取得
    const res = await gmail.users.messages.list({
      userId: 'me', // 自分自身
      maxResults: 10, // 最大取得数
      q: query, // 検索クエリ
    });

    // メールのIDを取得
    const messages = res.data.messages;
    if (!messages) return null;

    // 最初のメールの詳細を取得
    const message = await gmail.users.messages
      .get({
        userId: 'me',
        id: messages[0].id!,
      })
      .then((response) => response.data);
    return message;
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}
```

## 最後に

はじめは捨てメールアドレス生成サービスを利用してメールを受診しようとしたが使い方がわからず、Gmailなら方法があるだろうとGmailでのメール取得してみたらいけた。

---

## メールが取得できなくなった

### リフレッシュトークンの期限切れ

昨日までメールの取得ができていたのにできなくなった場合は、リフレッシュトークンの期限が切れている可能性があります。

codeの取得から再度やり直し、リフレッシュトークンを再取得してみてください。

### 迷惑メールフォルダに入っている

メールが取得できない前に、届いていない。

いつの間にか迷惑メールフォルダに振り分けられていてメールの取得ができなくなっていました。

迷惑メールフォルダにメールが届いていないか確認し、振り分けられていた場合は「迷惑メールではありません」ボタンをクリックするなどして迷惑メールフォルダに振り分けられないようにしてください。