Created
December 13, 2018 04:59
-
-
Save jkowalski/20736a32a39ba85d9f7e630b6125e2f6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"flag" | |
"fmt" | |
"log" | |
"net/http" | |
"net/url" | |
"os" | |
"strings" | |
"time" | |
"golang.org/x/oauth2" | |
) | |
var scopes = []string{"smartRead"} | |
type tokenResponse struct { | |
AccessToken string `json:"access_token"` | |
RefreshToken string `json:"refresh_token"` | |
ExpiresIn int `json:"expires_in"` // nonstandard | |
TokenType string `json:"token_type"` | |
} | |
func fetchPinChallenge() (string, string, error) { | |
uv := url.Values{ | |
"response_type": {"ecobeePin"}, | |
"client_id": {*appID}, | |
"scope": {strings.Join(scopes, ",")}, | |
} | |
u := url.URL{ | |
Scheme: "https", | |
Host: "api.ecobee.com", | |
Path: "authorize", | |
RawQuery: uv.Encode(), | |
} | |
resp, err := http.Get(u.String()) | |
if err != nil { | |
return "", "", fmt.Errorf("error retrieving response: %s", err) | |
} | |
defer resp.Body.Close() | |
var r struct { // pinResponse | |
EcobeePin string `json:"ecobeePin"` | |
Code string `json:"code"` | |
} | |
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { | |
return "", "", fmt.Errorf("unable to decode response: %v", r) | |
} | |
return r.EcobeePin, r.Code, nil | |
} | |
func fetchInitialToken(key, value string) (*oauth2.Token, error) { | |
uv := url.Values{ | |
"grant_type": {"ecobeePin"}, | |
"client_id": {*appID}, | |
key: {value}, | |
} | |
u := url.URL{ | |
Scheme: "https", | |
Host: "api.ecobee.com", | |
Path: "token", | |
RawQuery: uv.Encode(), | |
} | |
resp, err := http.PostForm(u.String(), nil) | |
if err != nil { | |
return nil, fmt.Errorf("error POSTing request: %s", err) | |
} | |
defer resp.Body.Close() | |
if resp.StatusCode != 200 { | |
return nil, fmt.Errorf("invalid response: %v", resp.Status) | |
} | |
var tr tokenResponse | |
if err := json.NewDecoder(resp.Body).Decode(&tr); err != nil { | |
return nil, fmt.Errorf("error unmarshalling response: %s", err) | |
} | |
return &oauth2.Token{ | |
AccessToken: tr.AccessToken, | |
TokenType: tr.TokenType, | |
RefreshToken: tr.RefreshToken, | |
Expiry: time.Now().Add(time.Duration(tr.ExpiresIn) * time.Second), | |
}, nil | |
} | |
var ( | |
tokenFile = flag.String("token-file", os.ExpandEnv("${HOME}/.ecobee-token"), "Where to store the OAuth tokens.") | |
appID = flag.String("app-id", "", "Ecobee App ID") | |
) | |
func main() { | |
flag.Parse() | |
pin, code, err := fetchPinChallenge() | |
if err != nil { | |
log.Fatalf("unable to get PIN challenge: %v", err) | |
} | |
fmt.Printf("Pin is %q\nPress <enter> after authorizing it on https://www.ecobee.com/consumerportal in the menu under 'My Apps'\n", pin) | |
fmt.Scanln() | |
tok, err := fetchInitialToken("code", code) | |
if err != nil { | |
log.Fatalf("unable to get token: %v\n", err) | |
} | |
f, err := os.Create(*tokenFile) | |
if err != nil { | |
log.Fatalf("unable to open token file: %v", err) | |
} | |
defer f.Close() | |
if err := json.NewEncoder(f).Encode(&tok); err != nil { | |
log.Fatalf("JSON encode error: %v", err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment