-
-
Save arodland/71071138f884c947b6b98045bdd575b8 to your computer and use it in GitHub Desktop.
GoogleNestGo
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 ( | |
"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