Skip to content

Instantly share code, notes, and snippets.

@jkowalski
Created December 13, 2018 04:59
Show Gist options
  • Save jkowalski/20736a32a39ba85d9f7e630b6125e2f6 to your computer and use it in GitHub Desktop.
Save jkowalski/20736a32a39ba85d9f7e630b6125e2f6 to your computer and use it in GitHub Desktop.
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