Go言語でGoogle Drive APIとGmail APIを使う方法メモ

# 初めに
会社で業務自動化のためのツールをGo言語で作っており、その中でGoogle Drive APIとGmail APIを使う機会があったのですが、結構はまるところが多く苦労したため、ここに分かったことを記します。

尚、記事が長くならないよう、サンプルコードから例外処理は省いています。
# OAuth2認証
両APIに共通するのが、OAuth2を用いた認証です。
[公式ドキュメントのサンプル](https://developers.google.com/drive/api/v3/quickstart/go)に簡単なサンプルが載っていますが、このやり方だといちいち認証用のurlを出力し、ユーザにそれを手入力でブラウザに表示させ、さらに認証コードをコンソールに打ち込んでもらわなければなりません。

他の言語([Python](https://developers.google.com/drive/api/v3/quickstart/python)など)であれば、自動でブラウザを開き認証をするフローがライブラリにより用意されているのですが、Goではありません。

そこで、以下の記事を参考に、認証用画面を自動で開き、認証後はローカルサーバにリダイレクトをするようにしました。

[コマンドラインアプリで OAuth2 認証を使う際に使えるテクニック](https://mattn.kaoriya.net/software/lang/go/20161231001721.htm)

上の記事を参考に作ったコードが以下です。

<details><summary><b>クリックしてコードを展開</b></summary><div>

```go
func googleDrive(){
	// APIを有効化したときに作成されるファイルにアクセス
	b, _ := ioutil.ReadFile("credentials/credentials.json")
	config, _ := google.ConfigFromJSON(b, drive.DriveScope)
	// 認証が既に済んでいれば、存在するファイル。初めて起動する場合は、存在しない。
	tokFile := "credentials/token.json"
	f, _ := os.Open(tokFile)
	tok := &oauth2.Token{}
	err := json.NewDecoder(f).Decode(tok)
	// 認証がまだ済んでいない場合
	if err != nil {
		authCode := getToken(config)
		tok, err = config.Exchange(context.TODO(), authCode)
		if err != nil{
			fmt.Println(err)
		}
		f, _ := os.OpenFile(tokFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
		json.NewEncoder(f).Encode(tok)
	}
}

func getToken(config *oauth2.Config) string{
	// ローカルサーバの立ち上げ
	l,err := net.Listen("tcp","localhost:8989")
	if err != nil {
		return ""
	}
	defer l.Close()

	err = open.Start(config.AuthCodeURL("state", oauth2.SetAuthURLParam("response_type", "code token")))

	quit := make(chan string)
	go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/" {
			w.Write([]byte(`<script>location.href = "/close?" + location.hash.substring(1);</script>`))
		} else {
			w.Write([]byte(`<script>window.open("about:blank","_self").close()</script>`))
			w.(http.Flusher).Flush()
			fmt.Println(req.URL.Query())
			quit <- req.URL.Query().Get("code")
		}
	}))
	return <-quit
}
```
</div>
</details>

はまりどころは、`getToken`関数の中の

```go
err = open.Start(config.AuthCodeURL("state", oauth2.SetAuthURLParam("response_type", "code token")))
```
の部分です。

上で紹介した記事のサンプルコードでは`response_type`を`token`としていますが、Google Drive API及びGmail APIはそれをサポートしていないため、エラーが出ます。`code token`とすることで認証コードを取得できるようになりました。
# Google Drive API
## 特定フォルダ下のファイル一覧取得
[公式ドキュメントのサンプル](https://developers.google.com/drive/api/v3/quickstart/go)では「マイドライブ」の中の10個を取得するプログラムが紹介されいますが、ある特定の親フォルダの下のすべてのファイル一覧を取得する方法は、以下です。

<details><summary><b>クリックしてコードを展開</b></summary><div>

```go
func googleDrive(){
	// ～　OAuth2認証の部分（上と一緒なので省略）～
	client := config.Client(context.Background(), tok)
	service, _ := drive.New(client)
	r, _ := service.Files.List().Q("'親フォルダのID' in parents").Fields("nextPageToken, files(id, name)").Do()
	fileList := r.Files
}

```
</div></details>

`service.Files.List()`でいったん全ファイルの一覧を取得していますが、そのあと`Q`関数で絞り込みをしています。

この関数の引数ですが、[ドキュメント](https://developers.google.com/drive/api/v3/search-parameters)にある通り、独特な書き方の文字列で絞り込み方法を指定します。

ファイル名、ファイルタイプ、最終閲覧日などでも絞り込みが行えるようです。
## フォルダを新しく作成
アップロードの部分は、ほかの言語は公式サイトにサンプルコードが載っているにもかかわらずGoにはありませんでした。

フォルダを作成するコードは、以下です。

<details><summary><b>クリックしてコードを展開</b></summary><div>

```go
func googleDrive(){
	// ～　OAuth2認証の部分（上と一緒なので省略）～
	newFolder := drive.File{}
	newFolder.Name = "新しいフォルダ名"
	newFolder.MimeType = "application/vnd.google-apps.folder"
	newFolder.Parents = []string{"親フォルダのID"}
	temp, _ := service.Files.Create(&newFolder).Do()
```
</div></details>

MimeTypeを`application/vnd.google-apps.folder`とすると、フォルダとして作成できます。

## ExcelファイルをSpreadSheetとしてアップロード
以下が、ExcelファイルをSpreadSheetとしてアップロードするサンプルです。

<details><summary><b>クリックしてコードを展開</b></summary><div>

```go
func googleDrive(){
	// ～　OAuth2認証の部分（上と一緒なので省略）～
	uploadFile, _ := os.Open("サンプルエクセルファイル.xlsx")
	fileMetaData := &drive.File{Name:"サンプルエクセルファイル.xlsx",MimeType:"application/vnd.google-apps.spreadsheet",Parents:[]string{"親フォルダのID"}}
	res,_ := service.Files.Create(fileMetaData).Media(uploadFile).Do()

```
</div></details>

MimeTypeを`application/vnd.google-apps.spreadsheet`とすることで、SpreadSheetとしてアップロードできます。
# Gmail API
## 件名、本文の文字化け対策
[公式ドキュメント](https://developers.google.com/gmail/api/quickstart/go)のサンプルコードを写経すると、メール件名が日本語だと文字化けします。

[こちらの記事](https://qiita.com/yyoshiki41/items/79882e269ca6af4c2236)を参考にすると、直りました。

<details><summary><b>クリックしてコードを展開</b></summary><div>

```go
func sendMail(itemList items, fileId, meetingDate, fromDate, toDate, mailAddress string){
	// ～Google Drive APIのOAuth2認証と一緒なので、省略～
	client := config.Client(context.Background(), tok)
	service, err := gmail.New(client)
	temp := "From: 'me'\r\n" +
		"To: " + 
		"sample@gmail.com" + 
		"\r\n" +
		"Subject:" + "日本語の見出し" + "\r\n" +
		"\r\n" + "日本語の本文"
	msgISO2022JP, _ := toISO2022JP(temp)
	msg := []byte(msgISO2022JP)
    message := gmail.Message{}
    message.Raw = base64.RawURLEncoding.EncodeToString(msg)

	_, err = service.Users.Messages.Send("me", &message).Do()
	if err != nil {
		fmt.Printf("%v", err)
	}
}

func toISO2022JP(str string) ([]byte, error) {
    reader := strings.NewReader(str)
    transformer := japanese.ISO2022JP.NewEncoder()

    return ioutil.ReadAll(transform.NewReader(reader, transformer))
}
```
</div></details>
## HTMLメールの送信
メールの`Content-Type`を'text/html'にすると、HTMLタグが使えるようになります。

<details><summary><b>クリックしてコードを展開</b></summary><div>

```go
func sendMail(itemList items, fileId, meetingDate, fromDate, toDate, mailAddress string){
	// ～Google Drive APIのOAuth2認証と一緒なので、省略～
    temp := "From: 'me'\r\n" +
		"To: " + 
		"sample@gmail.com" + 
		"\r\n" +
		"Subject:" + "件名" + "\r\n" +
		"MIME-version: 1.0;\r\n" + 
		"Content-Type: text/html; \r\n" +
		"\r\n" + "本文"
	msgISO2022JP, _ := toISO2022JP(temp)
	msg := []byte(msgISO2022JP)
    message := gmail.Message{}
    message.Raw = base64.RawURLEncoding.EncodeToString(msg)}
```
</div></details>
GoでHTMLメールを送信する方法について調べると、`Content-Type: text/html; charset: UTF-8`を指定する、というものが出てきますが、`charset:UTF-8`とすると文字化けが起こりました。

Shift-JISなどを指定しても起こるので、とりあえず何も指定しないことにしたところ、うまくいきました。