初めに
会社で業務自動化のためのツールをGo言語で作っており、その中でGoogle Drive APIとGmail APIを使う機会があったのですが、結構はまるところが多く苦労したため、ここに分かったことを記します。
尚、記事が長くならないよう、サンプルコードから例外処理は省いています。
OAuth2認証
両APIに共通するのが、OAuth2を用いた認証です。
公式ドキュメントのサンプルに簡単なサンプルが載っていますが、このやり方だといちいち認証用のurlを出力し、ユーザにそれを手入力でブラウザに表示させ、さらに認証コードをコンソールに打ち込んでもらわなければなりません。
他の言語(Pythonなど)であれば、自動でブラウザを開き認証をするフローがライブラリにより用意されているのですが、Goではありません。
そこで、以下の記事を参考に、認証用画面を自動で開き、認証後はローカルサーバにリダイレクトをするようにしました。
コマンドラインアプリで OAuth2 認証を使う際に使えるテクニック
上の記事を参考に作ったコードが以下です。
クリックしてコードを展開
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
}
はまりどころは、getToken
関数の中の
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
特定フォルダ下のファイル一覧取得
公式ドキュメントのサンプルでは「マイドライブ」の中の10個を取得するプログラムが紹介されいますが、ある特定の親フォルダの下のすべてのファイル一覧を取得する方法は、以下です。
クリックしてコードを展開
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
}
service.Files.List()
でいったん全ファイルの一覧を取得していますが、そのあとQ
関数で絞り込みをしています。
この関数の引数ですが、ドキュメントにある通り、独特な書き方の文字列で絞り込み方法を指定します。
ファイル名、ファイルタイプ、最終閲覧日などでも絞り込みが行えるようです。
フォルダを新しく作成
アップロードの部分は、ほかの言語は公式サイトにサンプルコードが載っているにもかかわらず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()
MimeTypeをapplication/vnd.google-apps.folder
とすると、フォルダとして作成できます。
ExcelファイルをSpreadSheetとしてアップロード
以下が、ExcelファイルをSpreadSheetとしてアップロードするサンプルです。
クリックしてコードを展開
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()
MimeTypeをapplication/vnd.google-apps.spreadsheet
とすることで、SpreadSheetとしてアップロードできます。
Gmail API
件名、本文の文字化け対策
公式ドキュメントのサンプルコードを写経すると、メール件名が日本語だと文字化けします。
こちらの記事を参考にすると、直りました。
クリックしてコードを展開
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))
}
クリックしてコードを展開
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)}
Shift-JISなどを指定しても起こるので、とりあえず何も指定しないことにしたところ、うまくいきました。