GoogleAPIをNode.jsから叩いてみた

今更ながら、GoogleAPIをNode.jsから触ってみます。
Google Drive、Gmail、Google Calendar、Googleフォトなど、皆さんgoogleサービスにお世話になっているのではないでしょうか。
ちゃんと、APIも提供されていて、npmモジュールもあるので、今後もいろいろ使えるかもしれません。
本人認証して簡単なリスト表示をするところまでですが、後はリファレンスを見れば拡張できるかと思います。

GitHubに上げておきます。

poruruba/GoogleApiSample
　https://github.com/poruruba/GoogleApiSample

#参考情報

googleapis/google-api-nodes-client
　https://github.com/googleapis/google-api-nodejs-client

Google Cloud Platform Console
　https://console.cloud.google.com/

Google CalendarのQuickStart
　https://developers.google.com/calendar/quickstart/nodejs

Google DriveのQuickStart
　https://developers.google.com/drive/api/v3/quickstart/nodejs

Google PhotoのQuickStart
　https://developers.google.com/photos/library/guides/get-started

GmailのQuickStart
　https://developers.google.com/gmail/api/quickstart/nodejs

Google SheetのQuickStart
　https://developers.google.com/sheets/api/quickstart/nodejs

#GCPのプロジェクトの作成

まずは、Google Cloud Platformのプロジェクトを作成します。すでに作成済みの場合はそれを使ってもよいです。

　https://console.cloud.google.com/projectcreate

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/231c8850-e306-e9cd-5ed2-9b9ce5fb32af.png)

プロジェクト名は適当に「SampleProject」としておきます。
Googleアカウント認証は、GoogleのサーバとOAuth2プロトコルで認証しますので、OAuth2のクライアントIDを作成します。
左上のメニューのAPIとサービスの認証情報を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/3965a8d4-6e67-dd5a-e553-6fee7ade168e.png)

次に、上の方にある「＋認証情報を作成」をクリックし、OAuthクライアントIDを選択します。
アプリケーションの種類には、「ウェブアプリケーション」を選択します。
名前は適当に「GoogleApiSample」とでもしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/cf109c57-c02e-81c2-cdbe-3227fe4b0b93.png)

そして、承認済みのJavascript生成もとには、これから立ち上げるサーバのURLを指定します。
例えば、```https://hogehoge:10443``` という感じです。
承認済みのリダイレクトURIには、これから立ち上げるWebページのURLを指定します。
こんな感じで作る予定です。```https://hogehoge:10443/googleapisample/signin.html```

作成できたら、クライアントIDとクライアントシークレットを覚えておきます。

#利用するGoogleAPIを有効化

これから、先ほど作ったプロジェクトで、Google Drive、Gmail、Google Calendar、Google Photoを使えるように有効化します。
左上のメニューから、APIとサービス→ライブラリ を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/ecdf7795-eabd-a608-a883-278c2de846c0.png)

検索入力のところに、driveと入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/dd11c022-b189-b6e2-da66-6cc1eb854847.png)

そうすると、Google Drive APIが見つかりますので選択し、「有効にする」ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/9ac66611-7b57-24e6-6c85-7373f0189e4d.png)

同様に、Gmail、Google Calendar、Google Photoも有効にします。

#サーバを立ち上げる

とりあえず、GitHubにもろもろを上げておいたのでそれを展開します。

https://github.com/poruruba/GoogleApiSample

```
$ unzip GoogleApiSample-main.zip
$ cd GoogleApiSample-main
$ mkdir cert
$ npm install
```

HTTPSで立ち上げる必要があり、certフォルダにSSL証明書のファイルを配置します。
Let’s Encryptの場合は、cert.pem、chain.pem、privkey.pemです。

起動の前に、各環境に合わせて変更する必要があります。

「public/googleapisample/js/start.js」
以下の2か所を修正します。

```js:/public/googleapisample/js/start.js
const GOOGLE_REDIRECT_URL = 'https://【サーバのホスト名】:10443/googleapisample/signin.html';
const AUTHORISE_URL = 'https://【サーバのホスト名】:10443/gapi/authorize';
```

「api/controllers/gapi/index.js」
以下の3か所です。

```js:/api/controllers/gapi/index.js
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '【クライアントID】';
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || '【クライアントシークレット】';
const GOOGLE_REDIRECT_URL = process.env.GOOGLE_REDIRECT_URL || 'https://【サーバのホスト名】:10443/googleapisample/signin.html';
```

起動は、以下です。

```
$ npm app.js
```

何も指定しなければ、HTTPSが10443ポートで立ち上がるかと思います。

# さっそく動かしてみる

以下のURLをブラウザから開きます。

　https://【サーバのホスト名】:10443/googleapisample/index.html

サーバのホスト名は、GCPプロジェクトのOAuth2クライアントを作成したときのサーバのホスト名と同じである必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/b73f3a8a-2005-044c-9197-4833f0c09eb7.png)

Authorizeボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/feeabc1c-f8f2-91f4-e610-1d595df86055.png)

Googleアカウントのログイン選択画面が表示されます。
アカウントを選択すると以下の画面が表示されます。この画面は、アプリがテスト用であるために表示されています。（本番にしたい場合はhttps://support.google.com/cloud/answer/7454865）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/2b2d5bf9-b928-5f88-34d3-1d1fbc8fb39b.png)

詳細リンクをクリックし、【サーバのホスト名】（安全ではないページ）に移動 のリンクをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/d87850cd-bd7c-5b88-3da9-bed2f1da902f.png)

これから触るGoogle Drive、Gmail、Google Calendar、Google Photoへのアクセス権を許可するかの確認が表示されます。許可ボタンを押下します。

そうすると、GoogleアカウントのもろもろのGoogle Drive、Gmail、Google Calendar、Google Photoの情報を取得したのち以下のように各テキストエリアに表示されて終了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/74e47ecb-fc8d-d54d-c07b-3684676c6ed4.png)

#流れを見てみる

まず、ログインから。Authorizeボタン押下から始まりました。
以下の部分が呼ばれます。子ウィンドウを作成し、/public/googleapisample/signin.htmlを開いています。Googleアカウントのログイン処理(の前半部分)は、signin.htmlに実装しています。

```js:/public/googleapisample/js/start.js
        do_authorize: function(){
            this.new_win = open(GOOGLE_REDIRECT_URL, null, 'width=500,height=750');
        },
```

一方、signin.htmlでは、ページが表示されてすぐに、以下を呼び出しています。

```js:/public/googleapisample/signin.html
    window.location = window.opener.vue.make_authorize_url();
```

親ページのmake_authorize_url()を呼び出して戻り値のURLにページ遷移しています。

```js:/public/googleapisample/js/start.js
        make_authorize_url: function(){
            return AUTHORISE_URL + '?state=abcd&prompt=true';
        }
```

Googleアカウントログイン用のURLを生成しています。URLはサーバ側で生成しています。

以下の部分です。
GOOGLE_SCOPEには、取得したい対象サービスをリストアップしています。([OAuth 2.0 Scopes for Google APIs](https://developers.google.com/identity/protocols/oauth2/scopes))

```js:/api/controllers/gapi/index.js
	if( event.path == '/api/authorize' ){
		var params = {
			scope: GOOGLE_SCOPE,
			access_type: 'offline'
		};
		if( event.queryStringParameters.state )
			params.state = event.queryStringParameters.state;
		if( event.queryStringParameters.prompt )
			params.prompt = 'consent';
		const auth = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URL);
		var url = auth.generateAuthUrl(params);
		return new Redirect(url);
	}else
```

実は、URLは固定なので毎回取得する必要はないのですが、今回は手順に沿って生成しています。
見ての通り、リダイレクトを返していまして、やっとGoogleアカウント認証ページに遷移します。

ログインが完了すると、またこのページ(/public/googleapisample/signin.html)に戻ってきます。その時に、codeがパラメータとして戻ってきますのでそれを処理します。

```js:/public/googleapisample/siginin.html
        if( searchs.code ){
            try{
                if( window.opener )
                  window.opener.vue.callback_authorization_code(null, searchs.code, searchs.state);
            }finally{
                window.close();
            }
```

取得されたcodeは認可コードと呼ばれており、以降で利用するアクセストークンの生成に必要なパラメータです。

この認可コードを親ページにcallback_authorization_code()関数を介して戻しています。

```js:/public/googleapisample/js/start.js
        callback_authorization_code: async function(err, code, state){
            if( err ){
                alert(err);
                return;
            }

            this.progress_open();
            try{
                var params = {
                    code: code
                };
                var json = await do_post("/gapi/token", params);
                console.log(json);
```

認可コードの処理は実はサーバ側で実施しますので、サーバ側に転送しています。

```js:/api/controllers/gapi/index.js
	if( event.path == '/gapi/token'){
		try{
			const auth = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URL);
			var token = await auth.getToken(body.code);
			console.log(token);
			auth.setCredentials(token.tokens);
```

これで、token.tokensにもろもろのトークンが取得できました。

後はこれを使って、Google Drive、Gmail、Google Calendar、Google Photoの情報を取得する処理をして、クライアントに返しています。

```js:/api/controllers/gapi/index.js
	const drive = google.drive({version: 'v3', auth: auth});
	var drive_list = await drive.files.list({
		pageSize: 10,
		fields: 'nextPageToken, files(id, name)',
	});
	var image_list = await do_get_image_list({ pageSize: 100 }, token.tokens.access_token);
	const calendar = google.calendar({version: 'v3', auth});
	var calendar_list = await calendar.events.list({
		calendarId: 'primary',
		maxResults: 10,
		singleEvents: true,
		orderBy: 'updated'
	});
	const gmail = google.gmail({version: 'v1', auth});
	var mail_list = await gmail.users.labels.list({
		userId: 'me',
	});

	return new Response({ drive_list, image_list, calendar_list, mail_list });
```

クライアント側では受け取ったレスポンスをテキストエリアに表示しています。

```js:/public/googleapisample/js/start.js
            this.drive_list = JSON.stringify(json.drive_list.data.files, null , "\t");
            this.image_list = JSON.stringify(json.image_list.mediaItems, null , "\t");
            this.mail_list = JSON.stringify(json.mail_list.data.labels, null , "\t");
            this.calendar_list = JSON.stringify(json.calendar_list.data.items, null , "\t");
```