LoginSignup
69
60

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

2. 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)
    }

}
69
60
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
69
60