More cleanup. Added copyright notice.

This commit is contained in:
Jeff Walter 2018-09-22 14:22:42 -05:00
parent 8213d70258
commit 648de10f10
17 changed files with 465 additions and 180 deletions

6
Gopkg.lock generated
View File

@ -2,12 +2,12 @@
[[projects]]
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
branch = "master"
digest = "1:4a28080faacdaabfe54a78ba0bfb41468744cbcda6af15748b917ca725d3749c"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = ""
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
revision = "c059e472caf75dbe73903f6521a20abac245b17f"
[[projects]]
branch = "master"

51
arlo.go
View File

@ -1,6 +1,23 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
import (
"net/http"
"sync"
"github.com/jeffreydwalter/arlo-golang/internal/request"
@ -20,13 +37,14 @@ type Arlo struct {
func newArlo(user string, pass string) (arlo *Arlo) {
c, _ := request.NewClient(BaseUrl)
// Add important headers.
c.BaseHttpHeader.Add("DNT", "1")
c.BaseHttpHeader.Add("schemaVersion", "1")
c.BaseHttpHeader.Add("Host", "arlo.netgear.com")
c.BaseHttpHeader.Add("Referer", "https://arlo.netgear.com/")
baseHeaders := make(http.Header)
baseHeaders.Add("DNT", "1")
baseHeaders.Add("schemaVersion", "1")
baseHeaders.Add("Host", "arlo.netgear.com")
baseHeaders.Add("Referer", "https://arlo.netgear.com/")
c, _ := request.NewClient(BaseUrl, baseHeaders)
return &Arlo{
user: user,
@ -40,8 +58,8 @@ func Login(user string, pass string) (arlo *Arlo, err error) {
body := map[string]string{"email": arlo.user, "password": arlo.pass}
resp, err := arlo.post(LoginUri, "", body, nil)
if err := checkHttpRequest(resp, err, "login request failed"); err != nil {
return nil, err
if err != nil {
return nil, errors.WithMessage(err, "failed to login")
}
defer resp.Body.Close()
@ -52,7 +70,7 @@ func Login(user string, pass string) (arlo *Arlo, err error) {
if loginResponse.Success {
// Cache the auth token.
arlo.client.BaseHttpHeader.Add("Authorization", loginResponse.Data.Token)
arlo.client.AddHeader("Authorization", loginResponse.Data.Token)
// Save the account info with the arlo struct.
arlo.Account = loginResponse.Data
@ -75,10 +93,10 @@ func (a *Arlo) Logout() error {
// GetDevices returns an array of all devices.
// When you call Login, this method is called and all devices are cached in the arlo object.
func (a *Arlo) GetDevices() (devices Devices, err error) {
func (a *Arlo) GetDevices() (devices *Devices, err error) {
resp, err := a.get(DevicesUri, "", nil)
if err := checkHttpRequest(resp, err, "failed to get devices"); err != nil {
return nil, err
if err != nil {
return nil, errors.WithMessage(err, "failed to get devices")
}
defer resp.Body.Close()
@ -95,11 +113,12 @@ func (a *Arlo) GetDevices() (devices Devices, err error) {
return nil, errors.New("no devices found")
}
// Cache a pointer to the arlo object with each device.
for i := range deviceResponse.Data {
deviceResponse.Data[i].arlo = a
}
// disconnect all of the basestations from the EventStream.
// Disconnect all of the basestations from the EventStream.
for i := range a.Basestations {
if err := a.Basestations[i].Disconnect(); err != nil {
return nil, errors.WithMessage(err, "failed to get devices")
@ -108,8 +127,8 @@ func (a *Arlo) GetDevices() (devices Devices, err error) {
a.rwmutex.Lock()
// Cache the devices as their respective types.
a.Cameras = deviceResponse.Data.GetCameras()
a.Basestations = deviceResponse.Data.GetBasestations()
a.Cameras = *deviceResponse.Data.GetCameras()
a.Basestations = *deviceResponse.Data.GetBasestations()
a.rwmutex.Unlock()
// subscribe each basestation to the EventStream.
@ -119,7 +138,7 @@ func (a *Arlo) GetDevices() (devices Devices, err error) {
}
}
return deviceResponse.Data, nil
return &deviceResponse.Data, nil
}
// UpdateDisplayOrder sets the display order according to the order defined in the DeviceOrder given.

View File

@ -1 +1,17 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
import (

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
import (
@ -17,7 +33,7 @@ type Basestation struct {
eventStream *eventStream
}
// Basestations is an array of Basestation objects.
// Basestations is a slice of Basestation objects.
type Basestations []Basestation
// Find returns a basestation with the device id passed in.

173
camera.go
View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
import (
@ -11,7 +27,7 @@ import (
// This type is here just for semantics. Some methods explicitly require a device of a certain type.
type Camera Device
// Cameras is an array of Camera objects.
// Cameras is a slice of Camera objects.
type Cameras []Camera
// Find returns a camera with the device id passed in.
@ -103,83 +119,6 @@ func (c *Camera) SetBrightness(brightness int) (response *EventStreamResponse, e
return b.makeEventStreamRequest(payload, msg)
}
// StartStream returns a json object containing the rtmps url to the requested video stream.
// You will need the to install a library to handle streaming of this protocol: https://pypi.python.org/pypi/python-librtmp
//
// The request to /users/devices/startStream returns:
// NOTE: { "url":"rtsp://vzwow09-z2-prod.vz.netgear.com:80/vzmodulelive?egressToken=b1b4b675_ac03_4182_9844_043e02a44f71&userAgent=web&cameraId=48B4597VD8FF5_1473010750131" }
func (c *Camera) StartStream() (response *StreamResponse, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: map[string]string{
"activityState": "startUserStream",
"cameraId": c.DeviceId,
},
TransId: genTransId(),
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.ParentId,
}
msg := "failed to start stream"
resp, err := c.arlo.post(DeviceStartStreamUri, c.XCloudId, payload, nil)
if err := checkHttpRequest(resp, err, msg); err != nil {
return nil, err
}
defer resp.Body.Close()
if err := resp.Decode(response); err != nil {
return nil, err
}
if !response.Success {
return nil, errors.WithMessage(errors.New("status was false"), msg)
}
response.Data.Url = strings.Replace(response.Data.Url, "rtsp://", "rtsps://", 1)
return response, nil
}
// TakeSnapshot causes the camera to record a snapshot.
func (c *Camera) TakeSnapshot() (response *StreamResponse, err error) {
msg := "failed to take snapshot"
response, err = c.StartStream()
if err != nil {
return nil, errors.WithMessage(err, msg)
}
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
resp, err := c.arlo.post(DeviceTakeSnapshotUri, c.XCloudId, body, nil)
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
return nil, errors.WithMessage(err, msg)
}
return response, nil
}
// StartRecording causes the camera to start recording and returns a url that you must start reading from using ffmpeg
// or something similar.
func (c *Camera) StartRecording() (response *StreamResponse, err error) {
msg := "failed to start recording"
response, err = c.StartStream()
if err != nil {
return nil, errors.WithMessage(err, msg)
}
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
resp, err := c.arlo.post(DeviceStartRecordUri, c.XCloudId, body, nil)
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
return nil, errors.WithMessage(err, msg)
}
return response, nil
}
func (c *Camera) EnableMotionAlerts(sensitivity int, zones []string) (response *EventStreamResponse, err error) {
payload := EventStreamPayload{
Action: "set",
@ -321,3 +260,81 @@ func (c *Camera) SetAlertNotificationMethods(action string, email, push bool) (r
}
return b.makeEventStreamRequest(payload, msg)
}
// StartStream returns a json object containing the rtmps url to the requested video stream.
// You will need the to install a library to handle streaming of this protocol: https://pypi.python.org/pypi/python-librtmp
//
// The request to /users/devices/startStream returns:
// NOTE: { "url":"rtsp://vzwow09-z2-prod.vz.netgear.com:80/vzmodulelive?egressToken=b1b4b675_ac03_4182_9844_043e02a44f71&userAgent=web&cameraId=48B4597VD8FF5_1473010750131" }
func (c *Camera) StartStream() (url string, err error) {
payload := EventStreamPayload{
Action: "set",
Resource: fmt.Sprintf("cameras/%s", c.DeviceId),
PublishResponse: true,
Properties: map[string]string{
"activityState": "startUserStream",
"cameraId": c.DeviceId,
},
TransId: genTransId(),
From: fmt.Sprintf("%s_%s", c.UserId, TransIdPrefix),
To: c.ParentId,
}
msg := "failed to start stream"
resp, err := c.arlo.post(DeviceStartStreamUri, c.XCloudId, payload, nil)
if err != nil {
return "", errors.WithMessage(err, msg)
}
defer resp.Body.Close()
response := new(StreamResponse)
if err := resp.Decode(response); err != nil {
return "", err
}
if !response.Success {
return "", errors.WithMessage(errors.New("status was false"), msg)
}
response.URL = strings.Replace(response.URL, "rtsp://", "rtsps://", 1)
return response.URL, nil
}
// TakeSnapshot causes the camera to record a snapshot.
func (c *Camera) TakeSnapshot() (url string, err error) {
msg := "failed to take snapshot"
url, err = c.StartStream()
if err != nil {
return "", errors.WithMessage(err, msg)
}
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
resp, err := c.arlo.post(DeviceTakeSnapshotUri, c.XCloudId, body, nil)
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
return "", errors.WithMessage(err, msg)
}
return url, nil
}
// StartRecording causes the camera to start recording and returns a url that you must start reading from using ffmpeg
// or something similar.
func (c *Camera) StartRecording() (url string, err error) {
msg := "failed to start recording"
url, err = c.StartStream()
if err != nil {
return "", errors.WithMessage(err, msg)
}
body := map[string]string{"deviceId": c.DeviceId, "parentId": c.ParentId, "xcloudId": c.XCloudId, "olsonTimeZone": c.Properties.OlsonTimeZone}
resp, err := c.arlo.post(DeviceStartRecordUri, c.XCloudId, body, nil)
if err := checkRequest(resp, err, "failed to update device name"); err != nil {
return "", errors.WithMessage(err, msg)
}
return url, nil
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
const (
@ -7,7 +23,6 @@ const (
SubscribeUri = "/client/subscribe?token=%s"
UnsubscribeUri = "/client/unsubscribe"
NotifyUri = "/users/devices/notify/%s"
ResetUri = "/users/library/reset"
ServiceLevelUri = "/users/serviceLevel"
OffersUri = "/users/payment/offers"
UserProfileUri = "/users/profile"
@ -18,8 +33,9 @@ const (
UserLocationsUri = "/users/locations"
UserLocationUri = "/users/locations/%s"
LibraryUri = "/users/library"
LibraryRecycleUri = "/users/library/recycle"
LibraryMetadataUri = "/users/library/metadata"
LibraryRecycleUri = "/users/library/recycle"
LibraryResetUri = "/users/library/reset"
DevicesUri = "/users/devices"
DeviceRenameUri = "/users/devices/renameDevice"
DeviceDisplayOrderUri = "/users/devices/displayOrder"

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
// A Device is the device data, this can be a camera, basestation, arloq, etc.
@ -42,7 +58,7 @@ type Device struct {
XCloudId string `json:"xCloudId"`
}
// Devices is an array of Device objects.
// Devices is a slice of Device objects.
type Devices []Device
// A DeviceOrder holds a map of device ids and a numeric index. The numeric index is the device order.
@ -70,9 +86,9 @@ func (ds *Devices) Find(deviceId string) *Device {
return nil
}
func (ds *Devices) FindCameras(basestationId string) Cameras {
func (ds Devices) FindCameras(basestationId string) Cameras {
cs := new(Cameras)
for _, d := range *ds {
for _, d := range ds {
if d.ParentId == basestationId {
*cs = append(*cs, Camera(d))
}
@ -93,11 +109,11 @@ func (d Device) IsCamera() bool {
// I did this because some device types, like arloq, don't have a basestation.
// So, when interacting with them you must treat them like a basestation and a camera.
// Cameras also includes devices of this type, so you can get the same data there or cast.
func (ds Devices) GetBasestations() Basestations {
var basestations Basestations
func (ds Devices) GetBasestations() *Basestations {
basestations := new(Basestations)
for _, d := range ds {
if d.IsBasestation() || !d.IsCamera() {
basestations = append(basestations, Basestation{Device: d})
*basestations = append(*basestations, Basestation{Device: d})
}
}
return basestations
@ -107,11 +123,11 @@ func (ds Devices) GetBasestations() Basestations {
// I did this because some device types, like arloq, don't have a basestation.
// So, when interacting with them you must treat them like a basestation and a camera.
// Basestations also includes devices of this type, so you can get the same data there or cast.
func (ds Devices) GetCameras() Cameras {
var cameras Cameras
func (ds Devices) GetCameras() *Cameras {
cameras := new(Cameras)
for _, d := range ds {
if d.IsCamera() || !d.IsBasestation() {
cameras = append(cameras, Camera(d))
*cameras = append(*cameras, Camera(d))
}
}
return cameras

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
import (
@ -74,7 +90,7 @@ func (e *eventStream) listen() (connected chan bool) {
/*
fmt.Println("Got event message.")
fmt.Printf("EVENT: %s\n", event.Event)
fmt.Printf("DATA: %s\n", event.Data)
fmt.Printf("DATA: %s\n", event.URL)
*/
if event.Data != nil {

View File

@ -1,12 +1,30 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package request
import (
"bytes"
"encoding/json"
"io"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"sync"
"github.com/pkg/errors"
@ -15,11 +33,12 @@ import (
type Client struct {
BaseURL *url.URL
BaseHttpHeader *http.Header
BaseHeaders *http.Header
HttpClient *http.Client
rwmutex sync.RWMutex
}
func NewClient(baseurl string) (*Client, error) {
func NewClient(baseURL string, baseHeaders http.Header) (*Client, error) {
var err error
var jar *cookiejar.Jar
@ -30,7 +49,7 @@ func NewClient(baseurl string) (*Client, error) {
}
var u *url.URL
if u, err = url.Parse(baseurl); err != nil {
if u, err = url.Parse(baseURL); err != nil {
return nil, errors.Wrap(err, "failed to create client object")
}
@ -41,11 +60,17 @@ func NewClient(baseurl string) (*Client, error) {
return &Client{
BaseURL: u,
BaseHttpHeader: &header,
BaseHeaders: &header,
HttpClient: &http.Client{Jar: jar},
}, nil
}
func (c *Client) AddHeader(key, value string) {
c.rwmutex.Lock()
c.BaseHeaders.Set(key, value)
c.rwmutex.Unlock()
}
func (c *Client) Get(uri string, header http.Header) (*Response, error) {
req, err := c.newRequest("GET", uri, nil, header)
if err != nil {
@ -80,7 +105,7 @@ func (c *Client) newRequest(method string, uri string, body interface{}, header
return nil, errors.Wrap(err, "failed to create request object")
}
}
// log.Printf("\n\nBODY (%s): %s\n\n", uri, buf)
log.Printf("\n\nBODY (%s): %s\n\n", uri, buf)
u := c.BaseURL.String() + uri
req, err := http.NewRequest(method, u, buf)
@ -88,7 +113,11 @@ func (c *Client) newRequest(method string, uri string, body interface{}, header
return nil, errors.Wrap(err, "failed to create request object")
}
for k, v := range *c.BaseHttpHeader {
c.rwmutex.RLock()
baseHeaders := *c.BaseHeaders
c.rwmutex.RUnlock()
for k, v := range baseHeaders {
for _, h := range v {
//log.Printf("Adding header (%s): (%s - %s)\n\n", u, k, h)
req.Header.Set(k, h)
@ -116,8 +145,8 @@ func (c *Client) newResponse(resp *http.Response) (*Response, error) {
func (c *Client) do(req *Request) (*Response, error) {
//log.Printf("\n\nCOOKIES (%s): %v\n\n", req.URL, c.HttpClient.Jar.Cookies(req.URL))
//log.Printf("\n\nHEADERS (%s): %v\n\n", req.URL, req.Header)
log.Printf("\n\nCOOKIES (%s): %v\n\n", req.URL, c.HttpClient.Jar.Cookies(req.URL))
log.Printf("\n\nHEADERS (%s): %v\n\n", req.URL, req.Header)
resp, err := c.HttpClient.Do(&req.Request)
if err != nil {

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package request
import (

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package request
import (

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package util
import (

View File

@ -1,7 +1,26 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
import (
"fmt"
"time"
"github.com/pkg/errors"
)
// LibraryMetaData is the library meta data.
@ -30,38 +49,53 @@ type Recording struct {
UtcCreatedDate int64 `json:"utcCreatedDate"`
CurrentState string `json:"currentState"`
MediaDuration string `json:"mediaDuration"`
UniqueId string `json:"uniqueId"`
}
type Library []Recording
func (a *Arlo) GetLibraryMetaData(fromDate, toDate time.Time) (response *LibraryMetaDataResponse, err error) {
func (a *Arlo) GetLibraryMetaData(fromDate, toDate time.Time) (libraryMetaData *LibraryMetaData, err error) {
msg := "failed to get library metadata"
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
resp, err := a.post(LibraryMetadataUri, "", body, nil)
if err := checkHttpRequest(resp, err, "failed to get library metadata"); err != nil {
if err != nil {
return nil, errors.WithMessage(err, msg)
}
defer resp.Body.Close()
response := new(LibraryMetaDataResponse)
if err := resp.Decode(&response); err != nil {
return nil, err
}
if err := resp.Decode(response); err != nil {
return nil, err
if !response.Success {
return nil, errors.New(msg)
}
return response, nil
return &response.Data, nil
}
func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (response *LibraryResponse, err error) {
func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (library *Library, err error) {
msg := "failed to get library"
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
resp, err := a.post(LibraryUri, "", body, nil)
if err := checkHttpRequest(resp, err, "failed to get library"); err != nil {
if err != nil {
return nil, errors.WithMessage(err, msg)
}
defer resp.Body.Close()
response := new(LibraryResponse)
if err := resp.Decode(&response); err != nil {
return nil, err
}
if err := resp.Decode(response); err != nil {
return nil, err
if !response.Success {
return nil, errors.New(msg)
}
return response, nil
return &response.Data, nil
}
/*
@ -71,9 +105,9 @@ func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (response *LibraryResponse
NOTE: {"data": [{"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}]} is all that's really required.
*/
func (a *Arlo) DeleteRecording(r Recording) error {
func (a *Arlo) DeleteRecording(r *Recording) error {
body := map[string]Library{"data": {r}}
body := map[string]Library{"data": {*r}}
resp, err := a.post(LibraryRecycleUri, "", body, nil)
return checkRequest(resp, err, "failed to delete recording")
}
@ -85,9 +119,11 @@ func (a *Arlo) DeleteRecording(r Recording) error {
NOTE: {"data": [{"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}]} is all that's really required.
*/
func (a *Arlo) BatchDeleteRecordings(l Library) error {
func (a *Arlo) BatchDeleteRecordings(l *Library) error {
body := map[string]Library{"data": l}
resp, err := a.post(LibraryRecycleUri, "", body, nil)
return checkRequest(resp, err, "failed to delete recordings")
body := map[string]Library{"data": *l}
fmt.Printf("%+v\n", body)
return nil
//resp, err := a.post(LibraryRecycleUri, "", body, nil)
//return checkRequest(resp, err, "failed to delete recordings")
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
// LoginResponse is an intermediate struct used when parsing data from the Login() call.
@ -24,12 +40,12 @@ type LibraryResponse struct {
}
type StreamResponse struct {
Data StreamUrl
URL string `json:"url"`
Status
}
type RecordingResponse struct {
Data StreamUrl
URL string `json:"url"`
Status
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
/*
@ -59,7 +75,7 @@ type Connectivity struct {
ActiveNetwork string `json:"activeNetwork,omitempty"`
APN string `json:"apn,omitempty"`
CarrierFw string `json:"carrierFw,omitempty"`
Connected bool `json:"connected"`
Connected bool `json:"connected,omitempty"`
FWVersion string `json:"fwVersion,omitempty"`
ICCID string `json:"iccid,omitempty"`
IMEI string `json:"imei,omitempty"`
@ -115,10 +131,6 @@ type Favorite struct {
Favorite uint8 `json:"Favorite"`
}
type StreamUrl struct {
Url string `json:"url"`
}
type BaseDetectionProperties struct {
Armed bool `json:"armed"`
Sensitivity int `json:"sensitivity"`
@ -240,8 +252,8 @@ type EventStreamPayload struct {
To string `json:"to"`
}
// Data is part of the Status message fragment returned by most calls to the Arlo API.
// Data is only populated when Success is false.
// URL is part of the Status message fragment returned by most calls to the Arlo API.
// URL is only populated when Success is false.
type Data struct {
Message string `json:"message,omitempty"`
Reason string `json:"reason,omitempty"`
@ -250,6 +262,6 @@ type Data struct {
// Status is the message fragment returned from most http calls to the Arlo API.
type Status struct {
Data `json:"Data,omitempty"`
Data `json:"URL,omitempty"`
Success bool `json:"success"`
}

76
util.go
View File

@ -1,10 +1,28 @@
/*
* Copyright (c) 2018 Jeffrey Walter <jeffreydwalter@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package arlo
import (
"fmt"
"io"
"math"
"math/rand"
"net/http"
"os"
"strconv"
"strings"
"time"
@ -15,25 +33,12 @@ import (
"github.com/pkg/errors"
)
func checkHttpRequest(resp *request.Response, err error, msg string) error {
if resp.StatusCode != 200 {
return errors.WithMessage(errors.New(fmt.Sprintf("http request failed: %s (%d)", resp.Status, resp.StatusCode)), msg)
}
func checkRequest(resp *request.Response, err error, msg string) error {
if err != nil {
return errors.WithMessage(err, msg)
}
return nil
}
func checkRequest(resp *request.Response, err error, msg string) error {
defer resp.Body.Close()
if err := checkHttpRequest(resp, err, msg); err != nil {
return err
}
var status Status
if err := resp.Decode(&status); err != nil {
return err
@ -59,31 +64,38 @@ func genTransId() string {
}
func (a *Arlo) get(uri, xCloudId string, header http.Header) (*request.Response, error) {
if len(xCloudId) > 0 {
a.rwmutex.Lock()
a.client.BaseHttpHeader.Set("xcloudId", xCloudId)
a.rwmutex.Unlock()
}
a.client.AddHeader("xcloudId", xCloudId)
return a.client.Get(uri, header)
}
func (a *Arlo) put(uri, xCloudId string, body interface{}, header http.Header) (*request.Response, error) {
if len(xCloudId) > 0 {
a.rwmutex.Lock()
a.client.BaseHttpHeader.Set("xcloudId", xCloudId)
a.rwmutex.Unlock()
}
a.client.AddHeader("xcloudId", xCloudId)
return a.client.Put(uri, body, header)
}
func (a *Arlo) post(uri, xCloudId string, body interface{}, header http.Header) (*request.Response, error) {
if len(xCloudId) > 0 {
a.rwmutex.Lock()
a.client.BaseHttpHeader.Set("xcloudId", xCloudId)
a.rwmutex.Unlock()
}
a.client.AddHeader("xcloudId", xCloudId)
return a.client.Post(uri, body, header)
}
func (a *Arlo) DownloadFile(url, to string) error {
msg := fmt.Sprintf("failed to download file (%s) => (%s)", url, to)
resp, err := a.get(url, "", nil)
if err != nil {
return errors.WithMessage(err, msg)
}
defer resp.Body.Close()
f, err := os.Create(to)
if err != nil {
return errors.WithMessage(err, msg)
}
_, err = io.Copy(f, resp.Body)
defer f.Close()
if err != nil {
return errors.WithMessage(err, msg)
}
return nil
}