Go Google+ API
http://developer.mixi.co.jp/connect/mixi_graph_api/mixi_io_spec_top/examples/
mixi Developer Centerでmixi Graph APIを実行するいろんな言語のサンプルが公開されています。されていますっていうか、しましたっていうか。とにかくすばらしいですね。
で、これはmixi Graph APIのサンプルなんですが、ぶっちゃけOAuthで認可するRestfulなAPIはmixiに限らずどれもだいたい同じようなものです。
ということでmixi Graph APIサンプルのGo言語版を元にGoogle+のAPIを実行できるようにしてみました。
変更点はだいたい以下の四点。
- エンドポイントの変更
- 出力フォーマットが違うのでとりあえず生JSON表示
- grant_type=refresh_tokenで新しいrefresh_tokenをもらえないみたいなので、一度取得したらそれをずっと使う
- Google+のエンドポイントはHTTPSなのでnet.Dialの代わりにtls.Dialを使用
あんまり変更してないですね。それでは実行してみます。
$ 6g plus.go $ 6l plus.6 $ ./6.out Please open http://localhost:8008 on a web browser.
結果。
ということで無事に実行できました。ありがとうmixi。ありがとう自分。
// plus.go package main import ( "crypto/tls" "fmt" "http" "io/ioutil" "json" "log" "os" "strings" "template" ) type tokens struct { AccessToken string RefreshToken string } const ( AUTHORIZE_URL_BASE = "https://accounts.google.com/o/oauth2/auth?response_type=code&scope=https%%3A%%2F%%2Fwww.googleapis.com%%2Fauth%%2Fplus.me&client_id=%v&redirect_uri=http%%3A%%2F%%2Flocalhost%%3A8008%%2Foauth2callback" TOKENS_ENDPOINT = "https://accounts.google.com/o/oauth2/token" PEOPLE_ENDPOINT= "https://www.googleapis.com/plus/v1/people/112986010883461255400" CONFIG_FILENAME = "config.json" TOKENS_FILENAME = "tokens.txt" TEMPLATE = ` <html> <head><title>People API</title></head> <body> <pre>{Response}</pre> </body> </html> ` ) var config map[string]string var currentTokens *tokens func NewTokens(accessToken string, refreshToken string) (*tokens) { return &tokens{accessToken, refreshToken} } func RestoreTokens() (*tokens) { if bytes, err := ioutil.ReadFile(TOKENS_FILENAME); err == nil { pair := strings.Split(string(bytes), "\n", -1) return &tokens{pair[0], pair[1]} } return nil } func (t *tokens)store() { ioutil.WriteFile(TOKENS_FILENAME, []byte(t.AccessToken + "\n" + t.RefreshToken), 0666) } func authorizeCode(authCode string) (err os.Error) { return updateTokens(map[string][]string{ "grant_type":{"authorization_code"}, "client_id":{config["client_id"]}, "client_secret":{config["client_secret"]}, "code":{authCode}, "redirect_uri":{"http://localhost:" + config["redirect_port"] + "/oauth2callback"}, }) } func refreshToken() (err os.Error) { return updateTokens(map[string][]string{ "grant_type":{"refresh_token"}, "client_id":{config["client_id"]}, "client_secret":{config["client_secret"]}, "refresh_token":{currentTokens.RefreshToken}, }) } func updateTokens(params map[string][]string) (err os.Error) { println("Call: " + TOKENS_ENDPOINT) response, _ := http.PostForm(TOKENS_ENDPOINT, params) b, _ := ioutil.ReadAll(response.Body) println(string(b)) var responseJson map[string]interface{} json.Unmarshal(b, &responseJson) if errorMessage, ok := responseJson["error"]; ok { return os.ErrorString(errorMessage.(string)) } else { if responseJson["refresh_token"] == nil { currentTokens = &tokens{responseJson["access_token"].(string), currentTokens.RefreshToken} } else { currentTokens = &tokens{responseJson["access_token"].(string), responseJson["refresh_token"].(string)} } currentTokens.store() return nil } return os.ErrorString("access_token is null") } func oauthGet(accessToken string, urlString string) (*http.Response, os.Error) { url, _ := http.ParseURL(urlString) conn, _ := tls.Dial("tcp", url.Host + ":443", nil) clientConn := http.NewClientConn(conn, nil) header := map[string][]string {"Authorization":{"OAuth " + accessToken}} request := http.Request{Method:"GET", URL:url, Header:header} clientConn.Write(&request) return clientConn.Read(&request) } func getPeople() (result string, err os.Error) { println("Call: " + PEOPLE_ENDPOINT) response, _ := oauthGet(currentTokens.AccessToken, PEOPLE_ENDPOINT) if response.StatusCode == 401 { if err = refreshToken(); err == nil { return getPeople() } return "", err } b, _ := ioutil.ReadAll(response.Body) result = string(b) println(result) return result, nil } func redirect(writer http.ResponseWriter, request *http.Request, redirectUrl string) { println("Redirect to: " + redirectUrl) http.Redirect(writer, request, redirectUrl, http.StatusFound) } func handleFriendList(writer http.ResponseWriter, request *http.Request) { if request.RawURL == "/favicon.ico" { return } var ( people string err os.Error ) authorizeUrl := fmt.Sprintf(AUTHORIZE_URL_BASE, config["client_id"]) parts := strings.Split(request.RawURL, "?code=", -1) if 2 <= len(parts) { if err = authorizeCode(parts[1]); err != nil { redirect(writer, request, authorizeUrl) } else { redirect(writer, request, "/") } return } else if currentTokens == nil { redirect(writer, request, authorizeUrl) return } if people, err = getPeople(); err != nil { redirect(writer, request, authorizeUrl) } else { params := new(struct{Response string}) params.Response = people tmpl, _ := template.Parse(TEMPLATE, nil) tmpl.Execute(writer, params) } } func main() { var ( bytes []byte err os.Error ) if bytes, err = ioutil.ReadFile(CONFIG_FILENAME); err != nil { log.Fatal("ioutil.ReadFile:", err) } if err = json.Unmarshal(bytes, &config); err != nil { log.Fatal("json.Unmarshal:", err) } currentTokens = RestoreTokens() http.Handle("/", http.HandlerFunc(handleFriendList)) addr := "localhost:" + config["redirect_port"] println("Please open http://" + addr + " on a web browser.") if err = http.ListenAndServe(addr, nil); err != nil { log.Fatal("http.ListenAndServe:", err) } }
/* config.json */ { "client_id":"YOUR CLIENT ID", "client_secret":"YOUR CLIENT SECRET", "redirect_port":"8008" }
なお、mixi Graph APIサンプルコード集には他にもVBAのサンプルとかあったりするので、その気になればExcelからもGoogle+ APIが実行できるはず。変態ですね。