Go で書いたCLIのプログラムから Google Sheets API v4 使ってスプレッドシートを操作したときのメモ。
使用した Go のバージョンは 1.6 です。
サービスアカウント
- Google Developer Console でプロジェクトを作成する
 - API Manager の「ライブラリ」からGoogle Sheets API を有効にする
 - API Manager の「認証情報」からサービスアカウントを作成する。作成すると秘密鍵が書かれたJSONファイルをダウンロードできるので大事に取っておく
 
スプレッドシート
- スプレッドシートを作成する。URLからスプレッドシートIDがあるので、メモしておく。
https://docs.google.com/spreadsheets/d/<スプレッドシートID>/
 - スプレッドシートの共有設定から、サービスアカウントのメールアドレス指定して、サービスアカウントからのアクセスを許可する。
 
Go からスプレッドシートにアクセス
package main
import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/sheets/v4"
)
func httpClient(credentialFilePath string) (*http.Client, error) {
	data, err := ioutil.ReadFile(credentialFilePath)
	if err != nil {
		return nil, err
	}
	conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/spreadsheets")
	if err != nil {
		return nil, err
	}
	return conf.Client(oauth2.NoContext), nil
}
func main() {
	spreadsheetId := "スプレッドシートのURLから抽出したID"
	credentialFilePath := "秘密鍵のJSONファイルのパス"
	client, err := httpClient(credentialFilePath)
	if err != nil {
		log.Fatal(err)
	}
	sheetService, err := sheets.New(client)
	if err != nil {
		log.Fatalf("Unable to retrieve Sheets Client %v", err)
	}
	_, err = sheetService.Spreadsheets.Get(spreadsheetId).Do()
	if err != nil {
		log.Fatalf("Unable to get Spreadsheets. %v", err)
	}
	fmt.Printf("success!\n")
}
go run main.go で実行。ここで403エラーが発生する場合は、スプレッドシートにサービスアカウントからのアクセスが許可されていない。
スプレッドシートを操作
Google Sheets API v4 のリファレンスはこちら。
以下の3つに分類されている。
- spreadsheets
 - spreadsheets.sheets
 - spreadsheets.values
 
Go では以下のように対応している
sheetService.SpreadsheetssheetService.Spreadsheets.SheetssheetService.Spreadsheets.Values
Go のリファレンスはこちら。
GET /v4/spreadsheets/{spreadsheetId}
例えば、 spreadsheets の get (GET /v4/spreadsheets/{spreadsheetId}) を使いたい場合、リファレンス を見てみると、パスパラメータとして spreadsheetId が必要なことが分かる。
Go で書くとこうなる。 sheetService.Spreadsheets.Get(spreadsheetId) で SpreadsheetsGetCall を取得できるので、それの Do() メソッドを呼べば実際にAPIを呼ぶ。
sheetService.Spreadsheets.Get(spreadsheetId).Do()
PUT /v4/spreadsheets/{spreadsheetId}/values/{range}
spreadsheets.values の update ( PUT /v4/spreadsheets/{spreadsheetId}/values/{range} ) を使いたい場合、リファレンスを見ると、パスパラメータとして spreadsheetId と range が必要なことが分かる。クエリパラメータとして valueInputOption があって、リクエストボディとして ValueRange が必要なことも分かる。
Go で書くとこうなる。
sheetService.Spreadsheets.Values.Update(spreadsheetId, targetRange, valueRange).ValueInputOption("USER_ENTERED").Do()
引数の targetRange はただの文字列で、 "シート名!A1:B1" というような書式。 valueRange は struct でドキュメントに詳しく書いてある -> https://godoc.org/google.golang.org/api/sheets/v4#ValueRange
何か操作してみる
行を挿入してから、以下のように書き込んでみる。
| hoge | 1 | 
|---|---|
| fuga | 2 | 
- A2 = hoge
 - B2 = 1
 - A3 = fuga
 - B3 = 2
 
行の挿入はAPIリファレンスを見ているとなさそうに思えるが、 POST /v4/spreadsheets/{spreadsheetId}:batchUpdate で色々できる。
Go のコードはこんな感じ。1行目に挿入することはできなかったので、2行目から2行分挿入して、データを書き込んでいる。
package main
import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/sheets/v4"
)
func httpClient(credentialFilePath string) (*http.Client, error) {
	data, err := ioutil.ReadFile(credentialFilePath)
	if err != nil {
		return nil, err
	}
	conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/spreadsheets")
	if err != nil {
		return nil, err
	}
	return conf.Client(oauth2.NoContext), nil
}
func sheetId(s *sheets.Spreadsheet, sheetName string) (int64, error) {
	for _, sheet := range s.Sheets {
		if sheet.Properties.Title == sheetName {
			return sheet.Properties.SheetId, nil
		}
	}
	return 0, errors.New(sheetName + " is not exists.")
}
func insertRow(sheetService *sheets.Service, spreadsheetId string, sheetId int64) (*sheets.BatchUpdateSpreadsheetResponse, error) {
	req := sheets.Request{
		InsertDimension: &sheets.InsertDimensionRequest{
			InheritFromBefore: false,
			Range: &sheets.DimensionRange{
				Dimension:  "ROWS",
				StartIndex: 1,
				EndIndex:   3,
				SheetId:    sheetId,
			},
		},
	}
	insertRowReq := sheets.BatchUpdateSpreadsheetRequest{
		Requests: []*sheets.Request{&req},
	}
	return sheetService.Spreadsheets.BatchUpdate(spreadsheetId, &insertRowReq).Do()
}
func main() {
	spreadsheetId := "スプレッドシートのURLから抽出したID"
	credentialFilePath := "秘密鍵のJSONファイルのパス"
	sheetName := "シート1"
	client, err := httpClient(credentialFilePath)
	if err != nil {
		log.Fatal(err)
	}
	sheetService, err := sheets.New(client)
	if err != nil {
		log.Fatalf("Unable to retrieve Sheets Client %v", err)
	}
	spreadsheet, err := sheetService.Spreadsheets.Get(spreadsheetId).Do()
	if err != nil {
		log.Fatalf("Unable to get Spreadsheets. %v", err)
	}
	sheetId, err := sheetId(spreadsheet, sheetName)
	if err != nil {
		log.Fatal(err)
	}
	_, err = insertRow(sheetService, spreadsheetId, sheetId)
	if err != nil {
		log.Fatalf("Unable to insert row. %v", err)
	}
	valueRange := &sheets.ValueRange{
		MajorDimension: "ROWS",
		Values: [][]interface{}{
			[]interface{}{"hoge", 1},
			[]interface{}{"fuga", 2},
		},
	}
	_, err = sheetService.Spreadsheets.Values.Update(spreadsheetId, sheetName+"!A2:B3", valueRange).ValueInputOption("USER_ENTERED").Do()
	if err != nil {
		log.Fatalf("Unable to write value. %v", err)
	}
	fmt.Printf("success!\n")
}