Refactoring
This commit is contained in:
parent
2589b868a7
commit
75454f08b3
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
145
account.go
145
account.go
@ -1,4 +1,8 @@
|
|||||||
package arloclient
|
package arlo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
// Account is the account data.
|
// Account is the account data.
|
||||||
type Account struct {
|
type Account struct {
|
||||||
@ -16,3 +20,142 @@ type Account struct {
|
|||||||
Arlo bool `json:"arlo"`
|
Arlo bool `json:"arlo"`
|
||||||
DateCreated float64 `json:"dateCreated"`
|
DateCreated float64 `json:"dateCreated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Friend struct {
|
||||||
|
FirstName string `json:"firstName"`
|
||||||
|
LastName string `json:"lastName"`
|
||||||
|
Devices DeviceOrder `json:"devices"`
|
||||||
|
LastModified float64 `json:"lastModified"`
|
||||||
|
AdminUser bool `json:"adminUser"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Login(user string, pass string) (*Arlo, error) {
|
||||||
|
|
||||||
|
a := newArlo(user, pass)
|
||||||
|
|
||||||
|
body := map[string]string{"email": a.user, "password": a.pass}
|
||||||
|
resp, err := a.client.Post(LoginUri, body, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "login request failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginResponse LoginResponse
|
||||||
|
if err := resp.Decode(&loginResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if loginResponse.Success {
|
||||||
|
// Cache the auth token.
|
||||||
|
a.client.BaseHttpHeader.Add("Authorization", loginResponse.Data.Token)
|
||||||
|
|
||||||
|
// Save the account info with the Arlo struct.
|
||||||
|
a.Account = &loginResponse.Data
|
||||||
|
|
||||||
|
if deviceResponse, err := a.GetDevices(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
if !deviceResponse.Success {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the devices as their respective types.
|
||||||
|
a.Basestations = deviceResponse.Data.Basestations()
|
||||||
|
a.Cameras = deviceResponse.Data.Cameras()
|
||||||
|
|
||||||
|
// Set the XCloudId header for future requests. You can override this on a per-request basis if needed.
|
||||||
|
a.client.BaseHttpHeader.Add("xCloudId", deviceResponse.Data[0].XCloudId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("failed to login")
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arlo) Logout() (*Status, error) {
|
||||||
|
|
||||||
|
resp, err := a.client.Put(LogoutUri, nil, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "logout request failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateProfile takes a first and last name, and updates the user profile with that information.
|
||||||
|
func (a *Arlo) UpdateProfile(firstName, lastName string) (*Status, error) {
|
||||||
|
|
||||||
|
body := map[string]string{"firstName": firstName, "lastName": lastName}
|
||||||
|
resp, err := a.client.Put(UserProfileUri, body, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to update profile")
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arlo) UpdatePassword(pass string) (*Status, error) {
|
||||||
|
|
||||||
|
body := map[string]string{"currentPassword": a.pass, "newPassword": pass}
|
||||||
|
resp, err := a.client.Post(UserChangePasswordUri, body, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to update password")
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.Success {
|
||||||
|
a.pass = pass
|
||||||
|
}
|
||||||
|
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is an example of the json you would pass in the body to UpdateFriends():
|
||||||
|
{
|
||||||
|
"firstName":"Some",
|
||||||
|
"lastName":"Body",
|
||||||
|
"devices":{
|
||||||
|
"XXXXXXXXXXXXX":"Camera 1",
|
||||||
|
"XXXXXXXXXXXXX":"Camera 2 ",
|
||||||
|
"XXXXXXXXXXXXX":"Camera 3"
|
||||||
|
},
|
||||||
|
"lastModified":1463977440911,
|
||||||
|
"adminUser":true,
|
||||||
|
"email":"user@example.com",
|
||||||
|
"id":"XXX-XXXXXXX"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
func (a *Arlo) UpdateFriends(f Friend) (*Status, error) {
|
||||||
|
|
||||||
|
resp, err := a.client.Put(UserFriendsUri, f, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to update friends")
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
56
arlo.go
Normal file
56
arlo.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package arlo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jeffreydwalter/arlo/internal/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Arlo struct {
|
||||||
|
user string
|
||||||
|
pass string
|
||||||
|
client *request.Client
|
||||||
|
Account *Account
|
||||||
|
Basestations *Basestations
|
||||||
|
Cameras *Cameras
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArlo(user string, pass string) *Arlo {
|
||||||
|
|
||||||
|
c, _ := request.NewClient(BaseUrl)
|
||||||
|
arlo := &Arlo{
|
||||||
|
user: user,
|
||||||
|
pass: pass,
|
||||||
|
client: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
return arlo
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
##
|
||||||
|
# This function causes the camera to record a snapshot.
|
||||||
|
#
|
||||||
|
# You can get the timezone from GetDevices().
|
||||||
|
##
|
||||||
|
func (a *Arlo) TakeSnapshot(camera):
|
||||||
|
stream_url = self.StartStream(camera)
|
||||||
|
a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/takeSnapshot', {'xcloudId':camera.get('xCloudId'),'parentId':camera.get('parentId'),'deviceId':camera.get('deviceId'),'olsonTimeZone':camera.get('properties', {}).get('olsonTimeZone')}, headers={"xcloudId":camera.get('xCloudId')})
|
||||||
|
return stream_url;
|
||||||
|
|
||||||
|
##
|
||||||
|
# This function causes the camera to start recording.
|
||||||
|
#
|
||||||
|
# You can get the timezone from GetDevices().
|
||||||
|
##
|
||||||
|
func (a *Arlo) StartRecording(camera):
|
||||||
|
stream_url = self.StartStream(camera)
|
||||||
|
a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/startRecord', {'xcloudId':camera.get('xCloudId'),'parentId':camera.get('parentId'),'deviceId':camera.get('deviceId'),'olsonTimeZone':camera.get('properties', {}).get('olsonTimeZone')}, headers={"xcloudId":camera.get('xCloudId')})
|
||||||
|
return stream_url
|
||||||
|
|
||||||
|
##
|
||||||
|
# This function causes the camera to stop recording.
|
||||||
|
#
|
||||||
|
# You can get the timezone from GetDevices().
|
||||||
|
##
|
||||||
|
func (a *Arlo) StopRecording(camera):
|
||||||
|
return a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/stopRecord', {'xcloudId':camera.get('xCloudId'),'parentId':camera.get('parentId'),'deviceId':camera.get('deviceId'),'olsonTimeZone':camera.get('properties', {}).get('olsonTimeZone')}, headers={"xcloudId":camera.get('xCloudId')})
|
||||||
|
*/
|
1
arlo_test.go
Normal file
1
arlo_test.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package arlo
|
338
arloclient.go
338
arloclient.go
@ -1,338 +0,0 @@
|
|||||||
package arloclient
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jeffreydwalter/arloclient/internal/request"
|
|
||||||
"github.com/jeffreydwalter/arloclient/internal/util"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Arlo struct {
|
|
||||||
user string
|
|
||||||
pass string
|
|
||||||
client *request.Client
|
|
||||||
Account *Account
|
|
||||||
Devices *Devices
|
|
||||||
}
|
|
||||||
|
|
||||||
func newArlo(user string, pass string) *Arlo {
|
|
||||||
|
|
||||||
c, _ := request.NewClient(BaseUrl)
|
|
||||||
arlo := &Arlo{
|
|
||||||
user: user,
|
|
||||||
pass: pass,
|
|
||||||
client: c,
|
|
||||||
}
|
|
||||||
|
|
||||||
return arlo
|
|
||||||
}
|
|
||||||
|
|
||||||
func Login(user string, pass string) (*Arlo, error) {
|
|
||||||
|
|
||||||
a := newArlo(user, pass)
|
|
||||||
|
|
||||||
body := map[string]string{"email": a.user, "password": a.pass}
|
|
||||||
resp, err := a.client.Post(LoginUri, body, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "login request failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
var loginResponse LoginResponse
|
|
||||||
if err := util.Decode(resp.ParsedBody, &loginResponse); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if loginResponse.Success {
|
|
||||||
// Cache the auth token.
|
|
||||||
a.client.BaseHttpHeader.Add("Authorization", loginResponse.Data.Token)
|
|
||||||
|
|
||||||
// Save the account info with the Arlo struct.
|
|
||||||
a.Account = &loginResponse.Data
|
|
||||||
|
|
||||||
if deviceResponse, err := a.GetDevices(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
if !deviceResponse.Success {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
a.Devices = &deviceResponse.Data
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("failed to login")
|
|
||||||
}
|
|
||||||
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Arlo) Logout() (*Status, error) {
|
|
||||||
|
|
||||||
resp, err := a.client.Put(LogoutUri, nil, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "logout request failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
var status Status
|
|
||||||
if err := util.Decode(resp.ParsedBody, &status); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Arlo) GetDevices() (*DeviceResponse, error) {
|
|
||||||
|
|
||||||
resp, err := a.client.Get(DevicesUri, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "get devices request failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
var deviceResponse DeviceResponse
|
|
||||||
if err := util.Decode(resp.ParsedBody, &deviceResponse); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &deviceResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Arlo) GetLibraryMetaData(fromDate, toDate time.Time) (*LibraryMetaDataResponse, error) {
|
|
||||||
|
|
||||||
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
|
|
||||||
resp, err := a.client.Post(LibraryMetadataUri, body, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to get library metadata")
|
|
||||||
}
|
|
||||||
|
|
||||||
var libraryMetaDataResponse LibraryMetaDataResponse
|
|
||||||
if err := util.Decode(resp.ParsedBody, &libraryMetaDataResponse); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &libraryMetaDataResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (*LibraryResponse, error) {
|
|
||||||
|
|
||||||
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
|
|
||||||
resp, err := a.client.Post(LibraryUri, body, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to get library")
|
|
||||||
}
|
|
||||||
|
|
||||||
var libraryResponse LibraryResponse
|
|
||||||
if err := util.Decode(resp.ParsedBody, &libraryResponse); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &libraryResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Arlo) UpdateDeviceName(d Device, name string) (*Status, error) {
|
|
||||||
|
|
||||||
body := map[string]string{"deviceId": d.DeviceId, "deviceName": name, "parentId": d.ParentId}
|
|
||||||
resp, err := a.client.Put(DeviceRenameUri, body, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to update device name")
|
|
||||||
}
|
|
||||||
|
|
||||||
var status Status
|
|
||||||
if err := util.Decode(resp.ParsedBody, &status); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &status, nil
|
|
||||||
|
|
||||||
return nil, errors.New("Device not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateProfile takes a first and last name, and updates the user profile with that information.
|
|
||||||
func (a *Arlo) UpdateProfile(firstName, lastName string) (*Status, error) {
|
|
||||||
|
|
||||||
body := map[string]string{"firstName": firstName, "lastName": lastName}
|
|
||||||
resp, err := a.client.Put(UserProfileUri, body, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to update profile")
|
|
||||||
}
|
|
||||||
|
|
||||||
var status Status
|
|
||||||
if err := util.Decode(resp.ParsedBody, &status); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Arlo) UpdatePassword(password string) (*Status, error) {
|
|
||||||
|
|
||||||
body := map[string]string{"currentPassword": a.pass, "newPassword": password}
|
|
||||||
resp, err := a.client.Post(UserChangePasswordUri, body, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to update password")
|
|
||||||
}
|
|
||||||
|
|
||||||
var status Status
|
|
||||||
if err := util.Decode(resp.ParsedBody, &status); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if status.Success {
|
|
||||||
a.pass = password
|
|
||||||
}
|
|
||||||
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This is an example of the json you would pass in the body to UpdateFriends():
|
|
||||||
{
|
|
||||||
"firstName":"Some",
|
|
||||||
"lastName":"Body",
|
|
||||||
"devices":{
|
|
||||||
"XXXXXXXXXXXXX":"Camera 1",
|
|
||||||
"XXXXXXXXXXXXX":"Camera 2 ",
|
|
||||||
"XXXXXXXXXXXXX":"Camera 3"
|
|
||||||
},
|
|
||||||
"lastModified":1463977440911,
|
|
||||||
"adminUser":true,
|
|
||||||
"email":"user@example.com",
|
|
||||||
"id":"XXX-XXXXXXX"
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
func (a *Arlo) UpdateFriends(f Friend) (*Status, error) {
|
|
||||||
|
|
||||||
resp, err := a.client.Put(UserFriendsUri, f, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to update friends")
|
|
||||||
}
|
|
||||||
|
|
||||||
var status Status
|
|
||||||
if err := util.Decode(resp.ParsedBody, &status); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Arlo) UpdateDisplayOrder(d DeviceOrder) (*Status, error) {
|
|
||||||
|
|
||||||
resp, err := a.client.Post(DeviceDisplayOrderUri, d, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to update display order")
|
|
||||||
}
|
|
||||||
|
|
||||||
var status Status
|
|
||||||
if err := util.Decode(resp.ParsedBody, &status); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
##
|
|
||||||
# Delete a single video recording from Arlo.
|
|
||||||
#
|
|
||||||
# All of the date info and device id you need to pass into this method are given in the results of the GetLibrary() call.
|
|
||||||
#
|
|
||||||
##
|
|
||||||
*/
|
|
||||||
func (a *Arlo) DeleteRecording(r *Recording) (*Status, error) {
|
|
||||||
|
|
||||||
body := map[string]map[string]interface{}{"data": {"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}}
|
|
||||||
resp, err := a.client.Post(LibraryRecycleUri, body, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to delete recording")
|
|
||||||
}
|
|
||||||
|
|
||||||
var status Status
|
|
||||||
if err := util.Decode(resp.ParsedBody, &status); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
##
|
|
||||||
# Delete a batch of video recordings from Arlo.
|
|
||||||
#
|
|
||||||
# The GetLibrary() call response json can be passed directly to this method if you'd like to delete the same list of videos you queried for.
|
|
||||||
# If you want to delete some other batch of videos, then you need to send an array of objects representing each video you want to delete.
|
|
||||||
#
|
|
||||||
#[
|
|
||||||
# {
|
|
||||||
# "createdDate":"20160904",
|
|
||||||
# "utcCreatedDate":1473010280395,
|
|
||||||
# "deviceId":"XXXXXXXXXXXXX"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "createdDate":"20160904",
|
|
||||||
# "utcCreatedDate":1473010280395,
|
|
||||||
# "deviceId":"XXXXXXXXXXXXX"
|
|
||||||
# }
|
|
||||||
#]
|
|
||||||
##
|
|
||||||
func (a *Arlo) BatchDeleteRecordings(recording_metadata):
|
|
||||||
return a.client.Post('https://arlo.netgear.com/hmsweb/users/library/recycle', {'data':recording_metadata})
|
|
||||||
|
|
||||||
##
|
|
||||||
# Returns the whole video from the presignedContentUrl.
|
|
||||||
#
|
|
||||||
# Obviously, this function is generic and could be used to download anything. :)
|
|
||||||
##
|
|
||||||
func (a *Arlo) GetRecording(url, chunk_size=4096):
|
|
||||||
video = ''
|
|
||||||
r = requests.get(url, stream=True)
|
|
||||||
r.raise_for_status()
|
|
||||||
|
|
||||||
for chunk in r.iter_content(chunk_size):
|
|
||||||
if chunk: video += chunk
|
|
||||||
return video
|
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# This function 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:
|
|
||||||
#{ "url":"rtmps://vzwow09-z2-prod.vz.netgear.com:80/vzmodulelive?egressToken=b1b4b675_ac03_4182_9844_043e02a44f71&userAgent=web&cameraId=48B4597VD8FF5_1473010750131" }
|
|
||||||
#
|
|
||||||
##
|
|
||||||
func (a *Arlo) StartStream(camera):
|
|
||||||
return a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/startStream', {"to":camera.get('parentId'),"from":self.user_id+"_web","resource":"cameras/"+camera.get('deviceId'),"action":"set","publishResponse":True,"transId":self.genTransId(),"properties":{"activityState":"startUserStream","cameraId":camera.get('deviceId')}}, headers={"xcloudId":camera.get('xCloudId')})
|
|
||||||
|
|
||||||
##
|
|
||||||
# This function causes the camera to record a snapshot.
|
|
||||||
#
|
|
||||||
# You can get the timezone from GetDevices().
|
|
||||||
##
|
|
||||||
func (a *Arlo) TakeSnapshot(camera):
|
|
||||||
stream_url = self.StartStream(camera)
|
|
||||||
a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/takeSnapshot', {'xcloudId':camera.get('xCloudId'),'parentId':camera.get('parentId'),'deviceId':camera.get('deviceId'),'olsonTimeZone':camera.get('properties', {}).get('olsonTimeZone')}, headers={"xcloudId":camera.get('xCloudId')})
|
|
||||||
return stream_url;
|
|
||||||
|
|
||||||
##
|
|
||||||
# This function causes the camera to start recording.
|
|
||||||
#
|
|
||||||
# You can get the timezone from GetDevices().
|
|
||||||
##
|
|
||||||
func (a *Arlo) StartRecording(camera):
|
|
||||||
stream_url = self.StartStream(camera)
|
|
||||||
a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/startRecord', {'xcloudId':camera.get('xCloudId'),'parentId':camera.get('parentId'),'deviceId':camera.get('deviceId'),'olsonTimeZone':camera.get('properties', {}).get('olsonTimeZone')}, headers={"xcloudId":camera.get('xCloudId')})
|
|
||||||
return stream_url
|
|
||||||
|
|
||||||
##
|
|
||||||
# This function causes the camera to stop recording.
|
|
||||||
#
|
|
||||||
# You can get the timezone from GetDevices().
|
|
||||||
##
|
|
||||||
func (a *Arlo) StopRecording(camera):
|
|
||||||
return a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/stopRecord', {'xcloudId':camera.get('xCloudId'),'parentId':camera.get('parentId'),'deviceId':camera.get('deviceId'),'olsonTimeZone':camera.get('properties', {}).get('olsonTimeZone')}, headers={"xcloudId":camera.get('xCloudId')})
|
|
||||||
*/
|
|
@ -1 +0,0 @@
|
|||||||
package arloclient
|
|
2
const.go
2
const.go
@ -1,4 +1,4 @@
|
|||||||
package arloclient
|
package arlo
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BaseUrl = "https://arlo.netgear.com/hmsweb"
|
BaseUrl = "https://arlo.netgear.com/hmsweb"
|
||||||
|
140
devices.go
140
devices.go
@ -1,6 +1,10 @@
|
|||||||
package arloclient
|
package arlo
|
||||||
|
|
||||||
// Device is the device data.
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Device is the device data, this can be a camera, basestation, arloq, etc.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
DeviceType string `json:"deviceType"`
|
DeviceType string `json:"deviceType"`
|
||||||
XCloudId string `json:"xCloudId"`
|
XCloudId string `json:"xCloudId"`
|
||||||
@ -26,7 +30,22 @@ type Device struct {
|
|||||||
// Devices is an array of Device objects.
|
// Devices is an array of Device objects.
|
||||||
type Devices []Device
|
type Devices []Device
|
||||||
|
|
||||||
// DeviceOrder is a hash of # XXXXXXXXXXXXX is the device id of each camera. You can get this from GetDevices().
|
// A Basestation is a Device that's not type "camera" (basestation, arloq, arloqs, etc.).
|
||||||
|
// This type is here just for semantics. Some methods explicitly require a device of a certain type.
|
||||||
|
type Basestation Device
|
||||||
|
|
||||||
|
// Basestations is an array of Basestation objects.
|
||||||
|
type Basestations []Basestation
|
||||||
|
|
||||||
|
// A Camera is a Device of type "camera".
|
||||||
|
// 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.
|
||||||
|
type Cameras []Camera
|
||||||
|
|
||||||
|
// A DeviceOrder holds a map of device ids and a numeric index. The numeric index is the device order.
|
||||||
|
// Device order is mainly used by the UI to determine which order to show the devices.
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
"devices":{
|
"devices":{
|
||||||
@ -36,9 +55,10 @@ type Devices []Device
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
type DeviceOrder struct {
|
type DeviceOrder struct {
|
||||||
Devices map[string]int
|
Devices map[string]int `json:"devices"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find returns a device with the device id passed in.
|
||||||
func (ds *Devices) Find(deviceId string) *Device {
|
func (ds *Devices) Find(deviceId string) *Device {
|
||||||
for _, d := range *ds {
|
for _, d := range *ds {
|
||||||
if d.DeviceId == deviceId {
|
if d.DeviceId == deviceId {
|
||||||
@ -49,22 +69,120 @@ func (ds *Devices) Find(deviceId string) *Device {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *Devices) BaseStations() *Devices {
|
// Basestations returns a Basestations object containing all devices that are NOT type "camera".
|
||||||
var basestations Devices
|
// 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 decvices of this type, so you can get the same data there or cast.
|
||||||
|
func (ds *Devices) Basestations() *Basestations {
|
||||||
|
var basestations Basestations
|
||||||
for _, d := range *ds {
|
for _, d := range *ds {
|
||||||
if d.DeviceType == "basestation" {
|
if d.DeviceType != "camera" {
|
||||||
basestations = append(basestations, d)
|
basestations = append(basestations, Basestation(d))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &basestations
|
return &basestations
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *Devices) Cameras() *Devices {
|
// Cameras returns a Cameras object containing all devices that are of type "camera".
|
||||||
var cameras Devices
|
// 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 decvices of this type, so you can get the same data there or cast.
|
||||||
|
func (ds *Devices) Cameras() *Cameras {
|
||||||
|
var cameras Cameras
|
||||||
for _, d := range *ds {
|
for _, d := range *ds {
|
||||||
if d.DeviceType != "basestation" {
|
if d.DeviceType != "basestation" {
|
||||||
cameras = append(cameras, d)
|
cameras = append(cameras, Camera(d))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &cameras
|
return &cameras
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() (*DeviceResponse, error) {
|
||||||
|
|
||||||
|
resp, err := a.client.Get(DevicesUri, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to get devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
var deviceResponse DeviceResponse
|
||||||
|
if err := resp.Decode(&deviceResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(deviceResponse.Data) == 0 {
|
||||||
|
return nil, errors.New("no devices found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deviceResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDeviceName sets the name of the given device to the name argument.
|
||||||
|
func (a *Arlo) UpdateDeviceName(d Device, name string) (*Status, error) {
|
||||||
|
|
||||||
|
body := map[string]string{"deviceId": d.DeviceId, "deviceName": name, "parentId": d.ParentId}
|
||||||
|
resp, err := a.client.Put(DeviceRenameUri, body, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to update device name")
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &status, nil
|
||||||
|
|
||||||
|
return nil, errors.New("device not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDisplayOrder sets the display order according to the order defined in the DeviceOrder given.
|
||||||
|
func (a *Arlo) UpdateDisplayOrder(d DeviceOrder) (*Status, error) {
|
||||||
|
|
||||||
|
resp, err := a.client.Post(DeviceDisplayOrderUri, d, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to update display order")
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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":"rtmps://vzwow09-z2-prod.vz.netgear.com:80/vzmodulelive?egressToken=b1b4b675_ac03_4182_9844_043e02a44f71&userAgent=web&cameraId=48B4597VD8FF5_1473010750131" }
|
||||||
|
func (a *Arlo) StartStream(c Camera) (*StartStreamResponse, error) {
|
||||||
|
|
||||||
|
var n Notification
|
||||||
|
n.To = c.ParentId
|
||||||
|
n.From = c.UserId
|
||||||
|
n.Resource = "cameras/" + c.DeviceId
|
||||||
|
n.Action = "set"
|
||||||
|
n.PublishResponse = true
|
||||||
|
n.TransId = ""
|
||||||
|
n.Properties.ActivityState = "startUserStream"
|
||||||
|
n.Properties.CameraId = c.DeviceId
|
||||||
|
|
||||||
|
// {"to":camera.get('parentId'),"from":self.user_id+"_web","resource":"cameras/"+camera.get('deviceId'),"action":"set","publishResponse":True,"transId":self.genTransId(),"properties":{"activityState":"startUserStream","cameraId":camera.get('deviceId')}}, headers={"xcloudId":camera.get('xCloudId')}
|
||||||
|
resp, err := a.client.Post(DeviceStartStreamUri, n, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to start stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
var startStreamResponse StartStreamResponse
|
||||||
|
if err := resp.Decode(&startStreamResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &startStreamResponse, nil
|
||||||
|
}
|
||||||
|
131
internal/request/client.go
Normal file
131
internal/request/client.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
BaseURL *url.URL
|
||||||
|
BaseHttpHeader http.Header
|
||||||
|
httpClient http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(baseurl string) (*Client, error) {
|
||||||
|
var err error
|
||||||
|
var jar *cookiejar.Jar
|
||||||
|
|
||||||
|
options := cookiejar.Options{}
|
||||||
|
|
||||||
|
if jar, err = cookiejar.New(&options); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create client object")
|
||||||
|
}
|
||||||
|
|
||||||
|
var u *url.URL
|
||||||
|
if u, err = url.Parse(baseurl); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create client object")
|
||||||
|
}
|
||||||
|
|
||||||
|
header := make(http.Header)
|
||||||
|
header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36")
|
||||||
|
header.Add("Content-Type", "application/json")
|
||||||
|
header.Add("Accept", "application/json")
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
BaseURL: u,
|
||||||
|
BaseHttpHeader: header,
|
||||||
|
httpClient: http.Client{Jar: jar},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Get(uri string, header http.Header) (*Response, error) {
|
||||||
|
req, err := c.newRequest("GET", uri, nil, header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "get request "+uri+" failed")
|
||||||
|
}
|
||||||
|
return c.do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Post(uri string, body interface{}, header http.Header) (*Response, error) {
|
||||||
|
req, err := c.newRequest("POST", uri, body, header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "post request "+uri+" failed")
|
||||||
|
}
|
||||||
|
return c.do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Put(uri string, body interface{}, header http.Header) (*Response, error) {
|
||||||
|
req, err := c.newRequest("PUT", uri, body, header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "put request "+uri+" failed")
|
||||||
|
}
|
||||||
|
return c.do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) newRequest(method string, uri string, body interface{}, header http.Header) (*Request, error) {
|
||||||
|
|
||||||
|
var buf io.ReadWriter
|
||||||
|
if body != nil {
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
err := json.NewEncoder(buf).Encode(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create request object")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("JSON: %v", buf)
|
||||||
|
u := c.BaseURL.String() + uri
|
||||||
|
req, err := http.NewRequest(method, u, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create request object")
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range c.BaseHttpHeader {
|
||||||
|
for _, h := range v {
|
||||||
|
//fmt.Printf("Adding header (%s): (%s - %s)\n\n", u, k, h)
|
||||||
|
req.Header.Add(k, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range header {
|
||||||
|
for _, h := range v {
|
||||||
|
//fmt.Printf("Adding header (%s): (%s - %s)\n\n", u, k, h)
|
||||||
|
req.Header.Add(k, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Request{
|
||||||
|
Request: *req,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) newResponse(resp *http.Response) (*Response, error) {
|
||||||
|
|
||||||
|
return &Response{
|
||||||
|
Response: *resp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) do(req *Request) (*Response, error) {
|
||||||
|
|
||||||
|
//fmt.Printf("\n\nCOOKIES (%s): %v\n\n", req.URL, c.httpClient.Jar.Cookies(req.URL))
|
||||||
|
//fmt.Printf("\n\nHEADERS (%s): %v\n\n", req.URL, req.Header)
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(&req.Request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to execute http request")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
return nil, errors.New("http request failed with status: " + resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.newResponse(resp)
|
||||||
|
}
|
@ -1,168 +1,9 @@
|
|||||||
package request
|
package request
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
BaseURL *url.URL
|
|
||||||
BaseHttpHeader http.Header
|
|
||||||
httpClient http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
type Request struct {
|
type Request struct {
|
||||||
http.Request
|
http.Request
|
||||||
}
|
}
|
||||||
type Response struct {
|
|
||||||
http.Response
|
|
||||||
ParsedBody interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(baseurl string) (*Client, error) {
|
|
||||||
var err error
|
|
||||||
var jar *cookiejar.Jar
|
|
||||||
|
|
||||||
options := cookiejar.Options{}
|
|
||||||
|
|
||||||
if jar, err = cookiejar.New(&options); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to create client object")
|
|
||||||
}
|
|
||||||
|
|
||||||
var u *url.URL
|
|
||||||
if u, err = url.Parse(baseurl); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to create client object")
|
|
||||||
}
|
|
||||||
|
|
||||||
header := make(http.Header)
|
|
||||||
header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36")
|
|
||||||
header.Add("Content-Type", "application/json")
|
|
||||||
header.Add("Accept", "application/json")
|
|
||||||
|
|
||||||
return &Client{
|
|
||||||
BaseURL: u,
|
|
||||||
BaseHttpHeader: header,
|
|
||||||
httpClient: http.Client{Jar: jar},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Get(uri string, header http.Header) (*Response, error) {
|
|
||||||
req, err := c.newRequest("GET", uri, nil, header)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "get request "+uri+" failed")
|
|
||||||
}
|
|
||||||
return c.do(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Post(uri string, body interface{}, header http.Header) (*Response, error) {
|
|
||||||
req, err := c.newRequest("POST", uri, body, header)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "post request "+uri+" failed")
|
|
||||||
}
|
|
||||||
return c.do(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Put(uri string, body interface{}, header http.Header) (*Response, error) {
|
|
||||||
req, err := c.newRequest("PUT", uri, body, header)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "put request "+uri+" failed")
|
|
||||||
}
|
|
||||||
return c.do(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetContentType(ct string) (string, error) {
|
|
||||||
mediaType, _, err := mime.ParseMediaType(ct)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "failed to get content type")
|
|
||||||
}
|
|
||||||
return mediaType, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) newRequest(method string, uri string, body interface{}, header http.Header) (*Request, error) {
|
|
||||||
|
|
||||||
var buf io.ReadWriter
|
|
||||||
if body != nil {
|
|
||||||
buf = new(bytes.Buffer)
|
|
||||||
err := json.NewEncoder(buf).Encode(body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to create request object")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("JSON: %v", buf)
|
|
||||||
u := c.BaseURL.String() + uri
|
|
||||||
req, err := http.NewRequest(method, u, buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to create request object")
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range c.BaseHttpHeader {
|
|
||||||
for _, h := range v {
|
|
||||||
//fmt.Printf("Adding header (%s): (%s - %s)\n\n", u, k, h)
|
|
||||||
req.Header.Add(k, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range header {
|
|
||||||
for _, h := range v {
|
|
||||||
//fmt.Printf("Adding header (%s): (%s - %s)\n\n", u, k, h)
|
|
||||||
req.Header.Add(k, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Request{
|
|
||||||
Request: *req,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) newResponse(resp *http.Response) (*Response, error) {
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// log.Printf("DATA: %v", string(data))
|
|
||||||
|
|
||||||
mediaType, err := GetContentType(resp.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed to create response object")
|
|
||||||
}
|
|
||||||
|
|
||||||
var pb interface{}
|
|
||||||
switch mediaType {
|
|
||||||
case "application/json":
|
|
||||||
err = json.Unmarshal([]byte(body), &pb)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to create response object")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Response{
|
|
||||||
Response: *resp,
|
|
||||||
ParsedBody: pb,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) do(req *Request) (*Response, error) {
|
|
||||||
|
|
||||||
//fmt.Printf("\n\nCOOKIES (%s): %v\n\n", req.URL, c.httpClient.Jar.Cookies(req.URL))
|
|
||||||
//fmt.Printf("\n\nHEADERS (%s): %v\n\n", req.URL, req.Header)
|
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(&req.Request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to execute http request")
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return c.newResponse(resp)
|
|
||||||
}
|
|
||||||
|
72
internal/request/response.go
Normal file
72
internal/request/response.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resp *Response) GetContentType() (string, error) {
|
||||||
|
|
||||||
|
mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to get content type")
|
||||||
|
}
|
||||||
|
return mediaType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resp *Response) Decode(s interface{}) error {
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
mediaType, err := resp.GetContentType()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed to decode response body")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mediaType {
|
||||||
|
case "application/json":
|
||||||
|
err := json.NewDecoder(resp.Body).Decode(&s)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create "+reflect.TypeOf(s).String()+" object")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported content type: " + mediaType)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (resp *Response) Download(to string) (error, int64) {
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Create output file
|
||||||
|
newFile, err := os.Create(to)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer newFile.Close()
|
||||||
|
|
||||||
|
// Write bytes from HTTP response to file.
|
||||||
|
// response.Body satisfies the reader interface.
|
||||||
|
// newFile satisfies the writer interface.
|
||||||
|
// That allows us to use io.Copy which accepts
|
||||||
|
// any type that implements reader and writer interface
|
||||||
|
bytesWritten, err := io.Copy(newFile, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, bytesWritten
|
||||||
|
}
|
@ -3,10 +3,6 @@ package util
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func PrettyPrint(data interface{}) string {
|
func PrettyPrint(data interface{}) string {
|
||||||
@ -17,27 +13,13 @@ func PrettyPrint(data interface{}) string {
|
|||||||
return fmt.Sprint(string(j))
|
return fmt.Sprint(string(j))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
const TransIdPrefix = "web"
|
||||||
type Timestamp time.Time
|
|
||||||
|
|
||||||
func (t *Timestamp) MarshalJSON() ([]byte, error) {
|
func GenTransId(transType string) {
|
||||||
ts := time.Time(*t).Unix()
|
/*
|
||||||
stamp := fmt.Sprint(ts)
|
func divmod(numerator, denominator int64) (quotient, remainder int64) {
|
||||||
return []byte(stamp), nil
|
quotient = numerator / denominator // integer division, decimals are truncated
|
||||||
}
|
remainder = numerator % denominator
|
||||||
func (t *Timestamp) UnmarshalJSON(b []byte) error {
|
return
|
||||||
ts, err := strconv.Atoi(string(b))
|
}*/
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*t = Timestamp(time.Unix(int64(ts), 0))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func Decode(b interface{}, s interface{}) error {
|
|
||||||
if err := mapstructure.Decode(b, s); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to create "+reflect.TypeOf(s).String()+" object")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
87
library.go
87
library.go
@ -1,4 +1,10 @@
|
|||||||
package arloclient
|
package arlo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
// LibraryMetaData is the library meta data.
|
// LibraryMetaData is the library meta data.
|
||||||
type LibraryMetaData struct {
|
type LibraryMetaData struct {
|
||||||
@ -29,3 +35,82 @@ type Recording struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Library []Recording
|
type Library []Recording
|
||||||
|
|
||||||
|
func (a *Arlo) GetLibraryMetaData(fromDate, toDate time.Time) (*LibraryMetaDataResponse, error) {
|
||||||
|
|
||||||
|
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
|
||||||
|
resp, err := a.client.Post(LibraryMetadataUri, body, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to get library metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
var libraryMetaDataResponse LibraryMetaDataResponse
|
||||||
|
if err := resp.Decode(&libraryMetaDataResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &libraryMetaDataResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arlo) GetLibrary(fromDate, toDate time.Time) (*LibraryResponse, error) {
|
||||||
|
|
||||||
|
body := map[string]string{"dateFrom": fromDate.Format("20060102"), "dateTo": toDate.Format("20060102")}
|
||||||
|
resp, err := a.client.Post(LibraryUri, body, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to get library")
|
||||||
|
}
|
||||||
|
|
||||||
|
var libraryResponse LibraryResponse
|
||||||
|
if err := resp.Decode(&libraryResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &libraryResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Delete a single video recording from Arlo.
|
||||||
|
|
||||||
|
All of the date info and device id you need to pass into this method are given in the results of the GetLibrary() call.
|
||||||
|
|
||||||
|
NOTE: {"data": [{"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}]} is all that's really required.
|
||||||
|
*/
|
||||||
|
func (a *Arlo) DeleteRecording(r Recording) (*Status, error) {
|
||||||
|
|
||||||
|
body := map[string]Library{"data": {r}}
|
||||||
|
resp, err := a.client.Post(LibraryRecycleUri, body, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to delete recording")
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Delete a batch of video recordings from Arlo.
|
||||||
|
|
||||||
|
The GetLibrary() call response json can be passed directly to this method if you'd like to delete the same list of videos you queried for.
|
||||||
|
|
||||||
|
NOTE: {"data": [{"createdDate": r.CreatedDate, "utcCreatedDate": r.UtcCreatedDate, "deviceId": r.DeviceId}]} is all that's really required.
|
||||||
|
*/
|
||||||
|
func (a *Arlo) BatchDeleteRecordings(l Library) (*Status, error) {
|
||||||
|
|
||||||
|
body := map[string]Library{"data": l}
|
||||||
|
resp, err := a.client.Post(LibraryRecycleUri, body, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to delete recordings")
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
if err := resp.Decode(&status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
23
responses.go
23
responses.go
@ -1,4 +1,4 @@
|
|||||||
package arloclient
|
package arlo
|
||||||
|
|
||||||
// UpdateResponse is an intermediate struct used when parsing data from the UpdateProfile() call.
|
// UpdateResponse is an intermediate struct used when parsing data from the UpdateProfile() call.
|
||||||
type Status struct {
|
type Status struct {
|
||||||
@ -7,23 +7,28 @@ type Status struct {
|
|||||||
|
|
||||||
// LoginResponse is an intermediate struct used when parsing data from the Login() call.
|
// LoginResponse is an intermediate struct used when parsing data from the Login() call.
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
Data Account
|
Data Account
|
||||||
Success bool `json:"success"`
|
*Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeviceResponse is an intermediate struct used when parsing data from the GetDevices() call.
|
// DeviceResponse is an intermediate struct used when parsing data from the GetDevices() call.
|
||||||
type DeviceResponse struct {
|
type DeviceResponse struct {
|
||||||
Data Devices
|
Data Devices
|
||||||
Success bool `json:"success"`
|
*Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// LibraryMetaDataResponse is an intermediate struct used when parsing data from the GetLibraryMetaData() call.
|
// LibraryMetaDataResponse is an intermediate struct used when parsing data from the GetLibraryMetaData() call.
|
||||||
type LibraryMetaDataResponse struct {
|
type LibraryMetaDataResponse struct {
|
||||||
Data LibraryMetaData
|
Data LibraryMetaData
|
||||||
Success bool `json:"success"`
|
*Status
|
||||||
}
|
}
|
||||||
|
|
||||||
type LibraryResponse struct {
|
type LibraryResponse struct {
|
||||||
Data Library
|
Data Library
|
||||||
Success bool `json:"success"`
|
*Status
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartStreamResponse struct {
|
||||||
|
Data StreamUrl
|
||||||
|
*Status
|
||||||
}
|
}
|
||||||
|
51
types.go
51
types.go
@ -1,4 +1,4 @@
|
|||||||
package arloclient
|
package arlo
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Credentials is the login credential data.
|
// Credentials is the login credential data.
|
||||||
@ -45,12 +45,45 @@ type Favorite struct {
|
|||||||
Favorite uint8 `json:"Favorite"`
|
Favorite uint8 `json:"Favorite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Friend struct {
|
/*
|
||||||
FirstName string `json:"firstName"`
|
type Device struct {
|
||||||
LastName string `json:"lastName"`
|
DeviceType string `json:"deviceType"`
|
||||||
Devices DeviceOrder `json:"devices"`
|
XCloudId string `json:"xCloudId"`
|
||||||
LastModified float64 `json:"lastModified"`
|
DisplayOrder uint8 `json:"displayOrder"`
|
||||||
AdminUser bool `json:"adminUser"`
|
State string `json:"state"`
|
||||||
Email string `json:"email"`
|
ModelId string `json:"modelId"`
|
||||||
Id string `json:"id"`
|
InterfaceVersion string `json:"interfaceVersion"`
|
||||||
|
ParentId string `json:"parentId"`
|
||||||
|
UserId string `json:"userId"`
|
||||||
|
DeviceName string `json:"deviceName"`
|
||||||
|
FirmwareVersion string `json:"firmwareVersion"`
|
||||||
|
MediaObjectCount uint8 `json:"mediaObjectCount"`
|
||||||
|
DateCreated float64 `json:"dateCreated"`
|
||||||
|
Owner Owner `json:"owner"`
|
||||||
|
Properties Properties `json:"properties"`
|
||||||
|
UniqueId string `json:"uniqueId"`
|
||||||
|
LastModified float64 `json:"lastModified"`
|
||||||
|
UserRole string `json:"userRole"`
|
||||||
|
InterfaceSchemaVer string `json:"interfaceSchemaVer"`
|
||||||
|
DeviceId string `json:"deviceId"`
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type StreamUrl struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotificationProperties struct {
|
||||||
|
ActivityState string `json:"activityState"`
|
||||||
|
CameraId string `json:"cameraId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notification struct {
|
||||||
|
To string `json:"to"`
|
||||||
|
From string `json:"from"`
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
PublishResponse bool `json:"publishResourcec"`
|
||||||
|
TransId string `json:"transId"`
|
||||||
|
Properties NotificationProperties `json:"properties"`
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user