Skip to content

Instantly share code, notes, and snippets.

@arodland
Forked from markbeee/nest.go
Created September 30, 2017 03:21
Show Gist options
  • Save arodland/71071138f884c947b6b98045bdd575b8 to your computer and use it in GitHub Desktop.
Save arodland/71071138f884c947b6b98045bdd575b8 to your computer and use it in GitHub Desktop.
GoogleNestGo
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
flags "github.com/jessevdk/go-flags"
"io"
"io/ioutil"
"net/http"
"os"
"time"
)
const userAgent string = "Dalvik/1.6.0 (Linux; U; Android 4.2.2; Protonet) Dropcam/5.2.1.13 com.nest.android"
type sessionResponse struct {
User string `json:"user"`
AccessToken string `json:"access_token"`
ExpiresIn string `json:"expires_in"`
Language string `json:"language"`
Email string `json:"email"`
Urls struct {
TransportURL string `json:"transport_url"`
CzfeURL string `json:"czfe_url"`
DirectTransportURL string `json:"direct_transport_url"`
RubyAPIURL string `json:"rubyapi_url"`
WeatherURL string `json:"weather_url"`
LogUploadURL string `json:"log_upload_url"`
SupportURL string `json:"support_url"`
} `json:"urls"`
Limits struct {
ThermostatsPerStructure int `json:"thermostats_per_structure"`
Structures int `json:"structures"`
Thermostats int `json:"thermostats"`
SmokeDetectors int `json:"smoke_detectors"`
SmokeDetectorsPerStructure int `json:"smoke_detectors_per_structure"`
} `json:"limits"`
Weave struct {
ServiceConfig string `json:"service_config"`
PairingToken string `json:"pairing_token"`
} `json:"weave"`
UserID string `json:"userid"`
}
type dropcamLoginResponse struct {
Status int `json:"status"`
Items []struct {
SessionToken string `json:"session_token"`
} `json:"items"`
StatusDescription string `json:"status_description"`
StatusDetail string `json:"status_detail"`
}
type cameraListReponse struct {
Status int `json:"status"`
Items []struct {
TalkbackStreamHost string `json:"talkback_stream_host"`
IsStreamingEnabled bool `json:"is_streaming_enabled"`
LastConnectedTime uint64 `json:"last_connected_time"`
DirectNexustalkHost string `json:"direct_nexustalk_host"`
Timezone string `json:"timezone"`
ID int `json:"id"`
LiveStreamHost string `json:"live_stream_host"`
Description string `json:"description"`
CombinedSoftwareVersion string `json:"combined_software_version"`
UUID string `json:"uuid"`
Title string `json:"title"`
PublicToken string `json:"public_token"`
Capabilities []string `json:"capabilities"`
IsTrialMode bool `json:"is_trial_mode"`
//Location xxx `json:"location"`
MacAddress string `json:"mac_address"`
IsTrialWarning bool `json:"is_trial_warning"`
ActivationTime uint64 `json:"activation_time"`
Type int `json:"type"`
HasBundle bool `json:"has_bundle"`
OwnerID string `json:"owner_id"`
LastLocalIP string `json:"last_local_ip"`
EmbedURL string `json:"embed_url"`
} `json:"items"`
}
type recordingEntry struct {
Duration float32 `json:"duration"`
StartTime float32 `json:"start_time"`
ID int64 `json:"id"`
Type string `json:"string"`
}
func loginToNest(email, password string) (*sessionResponse, error) {
url := "https://home.nest.com/session"
jsonString := fmt.Sprintf(`{"password":"%v", "email":"%v"}`, password, email)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer([]byte(jsonString)))
req.Header.Set("Cookie", "cztoken=")
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
sr := &sessionResponse{}
err = json.Unmarshal(body, sr)
if err != nil {
return nil, err
}
return sr, nil
}
func loginToDropcam(nestToken string) (string, error) {
url := "https://www.dropcam.com/api/v1/login.login_nest"
bodyString := []byte(fmt.Sprintf("access_token=%v&", nestToken))
req, _ := http.NewRequest("POST", url, bytes.NewBuffer([]byte(bodyString)))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Set("User-Agent", userAgent)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
dlr := &dropcamLoginResponse{}
err = json.Unmarshal(body, dlr)
if err != nil {
return "", err
}
return dlr.Items[0].SessionToken, nil
}
func getCameraList(dropcamToken string) ([]string, error) {
url := "https://www.dropcam.com/api/v1/cameras.get_owned_with_properties?status=true"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Cookie", fmt.Sprintf("website_2=%v", dropcamToken))
req.Header.Set("User-Agent", userAgent)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
clr := &cameraListReponse{}
err = json.Unmarshal(body, clr)
if err != nil {
return nil, err
}
var cameras []string
for _, cam := range clr.Items {
cameras = append(cameras, cam.UUID)
}
return cameras, nil
}
func getCameraCuepoints(dropcamToken, camUUID string) ([]int64, error) {
querySetsString := "%5b%7b%22order%22%3a%22desc%22%2c%22limit%22%3a10%2c%22end_time%22%3a%222000000000%22%2c%22start_time%22%3a%220%22%7d%5d"
url := fmt.Sprintf("https://nexusapi.dropcam.com/paginated_cuepoint/%v/2?query_sets=%v", camUUID, querySetsString)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Cookie", fmt.Sprintf("website_2=%v", dropcamToken))
req.Header.Set("User-Agent", userAgent)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var recordings []recordingEntry
var recIDs []int64
err = json.Unmarshal(body, &recordings)
if err != nil {
return nil, err
}
for _, recording := range recordings {
recIDs = append(recIDs, recording.ID)
}
return recIDs, nil
}
func getCuePoint(dropcamToken, cameraUUID string, cuepointID int64) error {
url := fmt.Sprintf("https://nexusapi.dropcam.com/get_event_clip?uuid=%v&cuepoint_id=%v&num_frames=8", cameraUUID, cuepointID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Cookie", fmt.Sprintf("website_2=%v", dropcamToken))
req.Header.Set("User-Agent", userAgent)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
out, err := os.Create(fmt.Sprintf("%v.h264", cuepointID))
defer out.Close()
defer resp.Body.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
return nil
}
func getScreenshot(dropcamToken, cameraUUID, filename string) error {
url := fmt.Sprintf("https://nexusapi.dropcam.com/get_image?uuid=%v&width=1280&heigth=720", cameraUUID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Cookie", fmt.Sprintf("website_2=%v", dropcamToken))
req.Header.Set("User-Agent", userAgent)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
out, err := os.Create(filename)
defer out.Close()
defer resp.Body.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
return nil
}
func getStructureIDs(nestToken, dropcamToken, userID, transportURL string) ([]string, error) {
url := fmt.Sprintf("%v/v6/subscribe", transportURL)
sessionString := fmt.Sprintf("android-%v-%v", userID, time.Now().UTC().UnixNano())
reqBody := fmt.Sprintf(`{ "session": "%v" , "objects": [ { "object_timestamp": "0", "object_key": "user.%v", "object_revision": "0" } ]}`, sessionString, userID)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer([]byte(reqBody)))
req.Header.Set("Authorization", fmt.Sprintf("Basic %v", nestToken))
req.Header.Set("X-nl-send-ww-auth", "true")
req.Header.Set("X-nl-protocol-version", "1")
req.Header.Set("Cookie", fmt.Sprintf("website_2=%v", dropcamToken))
req.Header.Set("User-Agent", userAgent)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
var responseStruct struct {
Objects []struct {
ObjectRevision int `json:"object_revision"`
ObjectTimestamp int64 `json:"object_timestamp"`
ObjectKey string `json:"object_key"`
Value struct {
Email string `json:"email"`
Name string `json:"name"`
Structures []string `json:"structures"`
} `json:"value"`
} `json:"objects"`
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &responseStruct)
if err != nil {
return nil, err
}
if len(responseStruct.Objects) == 0 {
return nil, errors.New("getStructureIDs: 0 user objects received")
}
var structures []string
for _, st := range responseStruct.Objects[0].Value.Structures {
structures = append(structures, st)
}
return structures, nil
}
func getStructureDevices(nestToken, dropcamToken, userID, transportURL, structure string) ([]string, error) {
url := fmt.Sprintf("%v/v6/subscribe", transportURL)
sessionString := fmt.Sprintf("android-%v-%v", userID, time.Now().UTC().UnixNano())
reqBody := fmt.Sprintf(`{ "session": "%v" , "objects": [ { "object_timestamp": "0", "object_key": "%v", "object_revision": "0" } ]}`, sessionString, structure)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer([]byte(reqBody)))
req.Header.Set("Authorization", fmt.Sprintf("Basic %v", nestToken))
req.Header.Set("X-nl-send-ww-auth", "true")
req.Header.Set("X-nl-protocol-version", "1")
req.Header.Set("Cookie", fmt.Sprintf("website_2=%v", dropcamToken))
req.Header.Set("User-Agent", userAgent)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
var responseStruct struct {
Objects []struct {
ObjectRevision int `json:"object_revision"`
ObjectTimestamp int64 `json:"object_timestamp"`
ObjectKey string `json:"object_key"`
Value struct {
Location string `json:"location"`
CountryCode string `json:"country_code"`
Timezone string `json:"time_zone"`
Away bool `json:"away"`
Name string `json:"name"`
Postal string `json:"postal_code"`
Devices []string `json:"devices"`
} `json:"value"`
} `json:"objects"`
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &responseStruct)
if err != nil {
return nil, err
}
if len(responseStruct.Objects) == 0 {
return nil, errors.New("getStructureDevices: 0 structure objects received")
}
var devices []string
for _, dev := range responseStruct.Objects[0].Value.Devices {
devices = append(devices, dev)
}
return devices, nil
}
func getDeviceData(nestToken, dropcamToken, userID, transportURL, device string) ([]string, error) {
url := fmt.Sprintf("%v/v6/subscribe", transportURL)
sessionString := fmt.Sprintf("android-%v-%v", userID, time.Now().UTC().UnixNano())
reqBody := fmt.Sprintf(`{ "session": "%v" , "objects": [ { "object_timestamp": "0", "object_key": "%v", "object_revision": "0" } ]}`, sessionString, device)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer([]byte(reqBody)))
req.Header.Set("Authorization", fmt.Sprintf("Basic %v", nestToken))
req.Header.Set("X-nl-send-ww-auth", "true")
req.Header.Set("X-nl-protocol-version", "1")
req.Header.Set("Cookie", fmt.Sprintf("website_2=%v", dropcamToken))
req.Header.Set("User-Agent", userAgent)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
var responseStruct struct {
Objects []struct {
ObjectRevision int `json:"object_revision"`
ObjectTimestamp int64 `json:"object_timestamp"`
ObjectKey string `json:"object_key"`
Value interface{} `json:"value"`
} `json:"objects"`
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &responseStruct)
if err != nil {
return nil, err
}
if len(responseStruct.Objects) == 0 {
return nil, errors.New("getDeviceData: 0 structure objects received")
}
for _, obj := range responseStruct.Objects {
out, err := json.MarshalIndent(obj, "", " ")
if err != nil {
return nil, err
}
fmt.Println(string(out))
}
return nil, nil
}
func main() {
var opts struct {
Email string `short:"e" long:"email" required:"true"`
Pass string `short:"p" long:"password" required:"true"`
}
parser := flags.NewParser(&opts, flags.Default)
_, err := parser.Parse()
if err != nil {
parser.WriteHelp(os.Stderr)
os.Exit(1)
}
session, err := loginToNest(opts.Email, opts.Pass)
if err != nil {
panic(err)
}
dropcamToken, err := loginToDropcam(session.AccessToken)
if err != nil {
panic(err)
}
fmt.Printf("User ID: %v\n", session.UserID)
cameras, err := getCameraList(dropcamToken)
if err != nil {
panic(err)
}
for _, cameraUUID := range cameras {
points, err := getCameraCuepoints(dropcamToken, cameraUUID)
if err != nil {
panic(err)
}
for _, point := range points {
getCuePoint(dropcamToken, cameraUUID, point)
}
getScreenshot(dropcamToken, cameraUUID, cameraUUID+".jpeg")
}
structures, err := getStructureIDs(session.AccessToken, dropcamToken, session.UserID, session.Urls.TransportURL)
if err != nil {
panic(err)
}
var devices []string
for _, s := range structures {
devs, err := getStructureDevices(session.AccessToken, dropcamToken, session.UserID, session.Urls.TransportURL, s)
if err != nil {
panic(err)
}
devices = append(devices, devs...)
}
for _, dev := range devices {
getDeviceData(session.AccessToken, dropcamToken, session.UserID, session.Urls.TransportURL, dev)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment