68
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

golangで、 google calendar api v3 を使ってカレンダーを操作する

Last updated at Posted at 2014-07-18

概要

googleカレンダーをgolangで取得、追加、更新をやってみます。
Goolge Calendar API v3を使うのですが、OAuthなので、まず以下の前準備が必要です。

  1. google APIの Client IDの取得
  2. OAuth認証でアクセストークンを取得する

ただし、アクセストークンの取得部分は
goauth2の認証手順をもっと簡単にを参考に、プログラム側で作りました。

環境

  • OS X 10.9 (or Windows7 まだ動作確認していないけど、多少の手直しで動くはず)
  • golang 1.3

google API Client IDの取得

  1. https://console.developers.google.com/project から、
    Create Projectをクリック
    それぞれ適当な名前をつけてCreate
    New Project

  2. Project Dashboardから Enable an API
    Calendar APIのStatusをON
    API status

  3. 左のメニューのCredentialsから Create new Client ID
    Credentials
    Installed applicationOtherを選択しCreate Client ID
    Create Client ID

  4. 「Client ID for native application」のDownload JSONからClient Secretをダウンロード
    Download JSON

  5. OAuth認証でアクセストークンを取得する


goauth2の認証手順をもっと簡単にを参考に、以下の手順を自動処理します。

  1. リダイレクト先となるlocalhostにhttpサーバーを起動
  2. ClientIDとClientSecretを使って、認証ページURLを生成
  3. ブラウザを起動し認証ページのURLを開く
  4. ID/Passの入力(ここは手動)
  5. ブラウザのリダイレクトで、localhostに帰ってきたGETリクエストから認証コードを取得
lib/google-auth.go
package lib

import (
	"fmt"
	"net"
	"net/http"
	"os/exec"
	"strings"
	"time"

	"code.google.com/p/goauth2/oauth"
)

type LocalServerConfig struct {
	Port    int
	Timeout int
	OS      string
}

type RedirectResult struct {
	Code string
	Err  error
}

type Redirect struct {
	Result      chan RedirectResult
	ServerStart chan bool
	ServerStop  chan bool
	Listener    net.Listener
}

// 各種OSでのブラウザ起動コマンドとURLのエスケープコード置換文字列
type OpenBrowser struct {
	EscapeAnd string
	arg       []string
}
var openBrowser = map[string]OpenBrowser{
	"windows": {`&`, []string{"cmd", "/c", "start"}},
	"darwin":  {`&`, []string{"open", "-a", "safari"}},
	"test1":   {`&`, []string{"echo", "", ""}},
	"test2":   {`&`, []string{"fugafuga", "", ""}},
}

func NewRedirect(result chan RedirectResult) *Redirect {
	return &Redirect{result, make(chan bool, 1), make(chan bool, 1), nil}
}

type AuthToken interface {
	GetTokenCache() error
	GetAuthCodeURL() string
	GetAuthToken(string) error
}

type GoogleToken struct {
	Transport *oauth.Transport
}

// テストしやすいようにAuth系APIを隠蔽する
func (this *GoogleToken) GetTokenCache() error {
	_, err := this.Transport.Config.TokenCache.Token()
	return err
}
func (this *GoogleToken) GetAuthCodeURL() string {
	return this.Transport.Config.AuthCodeURL("")
}
func (this *GoogleToken) GetAuthToken(code string) error {
	_, err := this.Transport.Exchange(code)
	return err
}

// アクセストークンを取得
func GoogleOauth(transport AuthToken, localServerConfig LocalServerConfig) (err error) {

	// キャッシュからトークンファイルを取得
	err = transport.GetTokenCache()
	if err == nil {
		return
	}
	url := transport.GetAuthCodeURL()
	code, err := getAuthCode(url, localServerConfig)
	if err != nil {
		err = fmt.Errorf("Error getAuthCode: %#v", err)
		return
	}
	// 認証トークンを取得する。(取得後、キャッシュへ)
	err = transport.GetAuthToken(code)
	if err != nil {
		err = fmt.Errorf("Exchange: %#v", err)
	}
	return
}

// アクセスコード取得
func (this *Redirect) GetCode(w http.ResponseWriter, r *http.Request) {
	//defer this.Listener.Stop()
	code := r.URL.Query().Get("code")

	if code == "" {
		fmt.Fprintf(w, `Erorr`)
		this.Result <- RedirectResult{Err: fmt.Errorf("codeを取得できませんでした。")}
		return
	}

	fmt.Fprintf(w, `<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> </head>
			<body onload="window.open('about:blank','_self').close();">ブラウザが自動で閉じない場合は手動で閉じてください。</body>
			</html> `)
	this.Result <- RedirectResult{Code: code}
}

// localhostのhttpサーバー
func (this *Redirect) Server(port int) {
	mux := http.NewServeMux()
	mux.HandleFunc("/", this.GetCode)
	host := fmt.Sprintf("localhost:%d", port)
	fmt.Printf("Start Listen: %s\n", host)
	var err error
	this.Listener, err = net.Listen("tcp", host)
	if err != nil {
		this.Result <- RedirectResult{Err: err}
		return
	}
	server := http.Server{}
	server.Handler = mux
	go server.Serve(this.Listener)
	this.ServerStart <- true
	<-this.ServerStop
	this.Listener.Close()
	this.Result <- RedirectResult{Err: err}
	return
}
func (this *Redirect) Stop() {
	this.ServerStop <- true
}

// サーバー起動 -> ブラウザ起動 -> コード取得
func getAuthCode(url string, localServerConfig LocalServerConfig) (string, error) {

	var cmd *exec.Cmd

	//os := runtime.GOOS
	os := localServerConfig.OS
	var browser *OpenBrowser
	for key, value := range openBrowser {
		if os == key {
			browser = &value
			break
		}
	}
	if browser == nil {
		return "", fmt.Errorf("まだ未対応です・・・\n%s\n", url)
	}

	redirect := NewRedirect(make(chan RedirectResult, 1))
	go redirect.Server(localServerConfig.Port)

	// set redirect timeout
	redirectTimeout := time.After(time.Duration(localServerConfig.Timeout) * time.Second)
	<-redirect.ServerStart

	url = strings.Replace(url, "&", browser.EscapeAnd, -1)
	// ブラウザ起動

	//fmt.Printf("%v %v %v %v", browser.arg[0], browser.arg[1], browser.arg[2], url)
	cmd = exec.Command(browser.arg[0], browser.arg[1], browser.arg[2], url)
	if err := cmd.Start(); err != nil {
		return "", fmt.Errorf("Error:  start browser: %v, browser: %v\n", err, browser)
	}

	defer redirect.Stop()
	var result RedirectResult

	select {
	case result = <-redirect.Result:
		//ブラウザ側の応答があればなにもしない
	case <-redirectTimeout:
		// タイムアウト
		return "", fmt.Errorf("リダイレクト待ち時間がタイムアウトしました")
	}

	if result.Err != nil {
		return "", fmt.Errorf("Error: リダイレクト: %v\n", result.Err)
	}

	fmt.Printf("code: %v\n", result.Code)

	return result.Code, nil
}

Calendar APIを叩く

事前に console.developers.google.comからDownload JSONで保存したファイルをgoogle.json としてカレントディレクトリに保存しておく。

main.go
package main

import (
	"flag"
	"fmt"
	"log"
	"runtime"

	"code.google.com/p/goauth2/oauth"
	"github.com/masahide/get-cybozu-schedule/lib"
)

func main() {

	flag.Usage = lib.Usage
	flag.Parse()

	if *lib.Version {
		fmt.Printf("%s\n", lib.ShowVersion())
		return
	}

	// ClientID等を読み込む
	config, err := lib.Parse("google.json")
	if err != nil {
		log.Fatalf("Error Server: %v", err)
		return
	}
	

	port := 3000
	transport := oauth.Transport{
		Config: &oauth.Config{
			ClientId:     config.Installed.ClientID,
			ClientSecret: config.Installed.ClientSecret,
			RedirectURL:  fmt.Sprintf("%s:%d", "http://localhost", port),
			Scope:        "https://www.googleapis.com/auth/calendar",
			AuthURL:      config.Installed.AuthURL,
			TokenURL:     config.Installed.TokenURL,
			TokenCache:   oauth.CacheFile("cache.json"),
		},
	}
	
	// OAuthを実行
	err = lib.GoogleOauth(&lib.GoogleToken{&transport}, lib.LocalServerConfig{port, 30, runtime.GOOS})
	if err != nil {
		log.Fatalf("Error Server: %v", err)
		return
	}

	// ここからやっとカレンダーAPIを使い始める
	svc, err := calendar.New(transport.Client())
	if err != nil {
		log.Fatalf("Error calendar.New: %v", err)
		return
	}

	// カレンダー一覧を取得
	cl, err := svc.CalendarList.List().Do()
	if err != nil {
		log.Fatalf("Error CalendarList.List(): %v", err)
		return
	}

	fmt.Printf("--- Your calendars ---\n")
	for _, item := range cl.Items {
		fmt.Printf("%# v\n", item)
	}

}
68
59
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
68
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?