(WIP) Arlo client written in Go.
This commit is contained in:
parent
2b710eaea2
commit
50bc5bf56e
21
Gopkg.lock
generated
Normal file
21
Gopkg.lock
generated
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mitchellh/mapstructure"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "06020f85339e21b2478f756a78e295255ffa4d6a"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/pkg/errors"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||||
|
version = "v0.8.0"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "f9932cc0e893e6f71176657aa0d2f567ec244ca71e930f3d95235e1d8b349a1c"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
30
Gopkg.toml
Normal file
30
Gopkg.toml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/pkg/errors"
|
||||||
|
version = "0.8.0"
|
288
arloclient.go
Normal file
288
arloclient.go
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
package arloclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jeffreydwalter/arloclient/internal/request"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Arlo struct {
|
||||||
|
user string
|
||||||
|
pass string
|
||||||
|
client *request.Client
|
||||||
|
account Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArlo(user string, pass string) (*Arlo, error) {
|
||||||
|
|
||||||
|
c, _ := request.NewClient(BaseUrl)
|
||||||
|
arlo := &Arlo{
|
||||||
|
user: user,
|
||||||
|
pass: pass,
|
||||||
|
client: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := arlo.Login(); err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to create arlo object")
|
||||||
|
}
|
||||||
|
|
||||||
|
return arlo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arlo) Login() (*Account, error) {
|
||||||
|
|
||||||
|
resp, err := a.client.Post(LoginUri, Credentials{Email: a.user, Password: a.pass}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to login")
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginResponse LoginResponse
|
||||||
|
if err := mapstructure.Decode(resp.Data, &loginResponse); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create loginresponse object")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !loginResponse.Success {
|
||||||
|
return nil, errors.New("request was unsuccessful")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the auth token.
|
||||||
|
a.client.BaseHttpHeader.Add("Authorization", loginResponse.Data.Token)
|
||||||
|
|
||||||
|
// Save the account info with the Arlo struct.
|
||||||
|
a.account = loginResponse.Data
|
||||||
|
|
||||||
|
return &loginResponse.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arlo) Logout() (*request.Response, error) {
|
||||||
|
|
||||||
|
return a.client.Put(LogoutUri, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arlo) GetDevices() (*Devices, 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 := mapstructure.Decode(resp.Data, &deviceResponse); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create deviceresponse object")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !deviceResponse.Success {
|
||||||
|
return nil, errors.New("request was unsuccessful")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deviceResponse.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arlo) GetLibraryMetaData(fromDate, toDate time.Time) (*LibraryMetaData, error) {
|
||||||
|
|
||||||
|
resp, err := a.client.Post(LibraryMetadataUri, Duration{fromDate.Format("20060102"), toDate.Format("20060102")}, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to get library metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("GETLIBRARYMETADATA: %v", resp.Data)
|
||||||
|
|
||||||
|
var libraryMetaDataResponse LibraryMetaDataResponse
|
||||||
|
if err := mapstructure.Decode(resp.Data, &libraryMetaDataResponse); err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to create librarymetadataresponse object")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !libraryMetaDataResponse.Success {
|
||||||
|
return nil, errors.New("request was unsuccessful")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &libraryMetaDataResponse.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arlo) UpdateProfile(firstName, lastName string) (*UserProfile, error) {
|
||||||
|
|
||||||
|
resp, err := a.client.Put(UserProfileUri, FullName{firstName, lastName}, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var userProfileResponse UserProfileResponse
|
||||||
|
if err := mapstructure.Decode(resp.Data, &userProfileResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !userProfileResponse.Success {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userProfileResponse.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arlo) UpdatePassword(password string) error {
|
||||||
|
|
||||||
|
_, err := a.client.Post(UserChangePasswordUri, PasswordPair{a.pass, password}, nil)
|
||||||
|
if err != nil {
|
||||||
|
a.pass = password
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
##
|
||||||
|
# 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(body):
|
||||||
|
return a.client.Put('https://arlo.netgear.com/hmsweb/users/friends', body)
|
||||||
|
|
||||||
|
func (a *Arlo) UpdateDeviceName(device, name):
|
||||||
|
return a.client.Put('https://arlo.netgear.com/hmsweb/users/devices/renameDevice', {'deviceId':device.get('deviceId'), 'deviceName':name, 'parentId':device.get('parentId')})
|
||||||
|
|
||||||
|
##
|
||||||
|
# This is an example of the json you would pass in the body to UpdateDisplayOrder() of your devices in the UI.
|
||||||
|
#
|
||||||
|
# XXXXXXXXXXXXX is the device id of each camera. You can get this from GetDevices().
|
||||||
|
#{
|
||||||
|
# "devices":{
|
||||||
|
# "XXXXXXXXXXXXX":1,
|
||||||
|
# "XXXXXXXXXXXXX":2,
|
||||||
|
# "XXXXXXXXXXXXX":3
|
||||||
|
# }
|
||||||
|
#}
|
||||||
|
##
|
||||||
|
func (a *Arlo) UpdateDisplayOrder(body):
|
||||||
|
return a.client.Post('https://arlo.netgear.com/hmsweb/users/devices/displayOrder', body)
|
||||||
|
|
||||||
|
##
|
||||||
|
# This call returns the following:
|
||||||
|
# presignedContentUrl is a link to the actual video in Amazon AWS.
|
||||||
|
# presignedThumbnailUrl is a link to the thumbnail .jpg of the actual video in Amazon AWS.
|
||||||
|
#
|
||||||
|
#[
|
||||||
|
# {
|
||||||
|
# "mediaDurationSecond": 30,
|
||||||
|
# "contentType": "video/mp4",
|
||||||
|
# "name": "XXXXXXXXXXXXX",
|
||||||
|
# "presignedContentUrl": "https://arlos3-prod-z2.s3.amazonaws.com/XXXXXXX_XXXX_XXXX_XXXX_XXXXXXXXXXXXX/XXX-XXXXXXX/XXXXXXXXXXXXX/recordings/XXXXXXXXXXXXX.mp4?AWSAccessKeyId=XXXXXXXXXXXXXXXXXXXX&Expires=1472968703&Signature=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||||
|
# "lastModified": 1472881430181,
|
||||||
|
# "localCreatedDate": XXXXXXXXXXXXX,
|
||||||
|
# "presignedThumbnailUrl": "https://arlos3-prod-z2.s3.amazonaws.com/XXXXXXX_XXXX_XXXX_XXXX_XXXXXXXXXXXXX/XXX-XXXXXXX/XXXXXXXXXXXXX/recordings/XXXXXXXXXXXXX_thumb.jpg?AWSAccessKeyId=XXXXXXXXXXXXXXXXXXXX&Expires=1472968703&Signature=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||||
|
# "reason": "motionRecord",
|
||||||
|
# "deviceId": "XXXXXXXXXXXXX",
|
||||||
|
# "createdBy": "XXXXXXXXXXXXX",
|
||||||
|
# "createdDate": "20160903",
|
||||||
|
# "timeZone": "America/Chicago",
|
||||||
|
# "ownerId": "XXX-XXXXXXX",
|
||||||
|
# "utcCreatedDate": XXXXXXXXXXXXX,
|
||||||
|
# "currentState": "new",
|
||||||
|
# "mediaDuration": "00:00:30"
|
||||||
|
# }
|
||||||
|
#]
|
||||||
|
##
|
||||||
|
func (a *Arlo) GetLibrary(from_date, to_date):
|
||||||
|
return a.client.Post('https://arlo.netgear.com/hmsweb/users/library', {'dateFrom':from_date, 'dateTo':to_date})
|
||||||
|
|
||||||
|
##
|
||||||
|
# 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(camera, created_date, utc_created_date):
|
||||||
|
return a.client.Post('https://arlo.netgear.com/hmsweb/users/library/recycle', {'data':[{'createdDate':created_date,'utcCreatedDate':utc_created_date,'deviceId':camera.get('deviceId')}]})
|
||||||
|
|
||||||
|
##
|
||||||
|
# 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
arloclient_test.go
Normal file
1
arloclient_test.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package arloclient
|
29
const.go
Normal file
29
const.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package arloclient
|
||||||
|
|
||||||
|
const (
|
||||||
|
BaseUrl = "https://arlo.netgear.com/hmsweb"
|
||||||
|
LoginUri = "/login/v2"
|
||||||
|
LogoutUri = "/logout"
|
||||||
|
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"
|
||||||
|
UserChangePasswordUri = "/users/changePassword"
|
||||||
|
UserSessionUri = "/users/session"
|
||||||
|
UserFriendsUri = "/users/friends"
|
||||||
|
UserLocationsUri = "/users/locations"
|
||||||
|
UserLocationUri = "/users/locations/%s"
|
||||||
|
LibraryUri = "/users/library"
|
||||||
|
LibraryRecycleUri = "/users/library/recycle"
|
||||||
|
LibraryMetadataUri = "/users/library/metadata"
|
||||||
|
DevicesUri = "/users/devices"
|
||||||
|
DeviceRenameUri = "/users/devices/renameDevice"
|
||||||
|
DeviceDisplayOrderUri = "/users/devices/displayOrder"
|
||||||
|
DeviceTakeSnapshotUri = "/users/devices/takeSnapshot"
|
||||||
|
DeviceStartRecordUri = "/users/devices/startRecord"
|
||||||
|
DeviceStopRecordUri = "/users/devices/stopRecord"
|
||||||
|
DeviceStartStreamUri = "/users/devices/startStream"
|
||||||
|
)
|
189
internal/request/request.go
Normal file
189
internal/request/request.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"mime"
|
||||||
|
"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 {
|
||||||
|
http.Request
|
||||||
|
}
|
||||||
|
type Response struct {
|
||||||
|
http.Response
|
||||||
|
Data 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 (resp *Response) Parse(schema interface{}) (interface{}, error){
|
||||||
|
mediatype, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Printf("CONTENT TYPE %s\n", mediatype)
|
||||||
|
|
||||||
|
switch mediatype {
|
||||||
|
case "application/json":
|
||||||
|
log.Println("DECODING JSON: %s", json.Valid(resp.Data))
|
||||||
|
if err := json.Unmarshal(resp.Data, schema); err != nil {
|
||||||
|
log.Println("GOT AN ERROR")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema, 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) {
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Printf("DATA: %v", string(data))
|
||||||
|
|
||||||
|
var d interface{}
|
||||||
|
mediaType, err := GetContentType(resp.Header.Get("Content-Type"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to create response object")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mediaType {
|
||||||
|
case "application/json":
|
||||||
|
err = json.Unmarshal([]byte(data), &d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create response object")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Response{
|
||||||
|
Response: *resp,
|
||||||
|
Data: d,
|
||||||
|
}, 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)
|
||||||
|
}
|
115
types.go
Normal file
115
types.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package arloclient
|
||||||
|
|
||||||
|
// Credentials is the login credential data.
|
||||||
|
type Credentials struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration holds two dates used when you need to specify a date range in the format "20060102".
|
||||||
|
type Duration struct {
|
||||||
|
DateFrom string `json:"dateFrom""`
|
||||||
|
DateTo string `json:"dateTo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordPair is used when updating the account password.
|
||||||
|
type PasswordPair struct {
|
||||||
|
CurrentPassword string `json:"currentPassword"`
|
||||||
|
NewPassword string `json:"newPassword"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullName is used when updating the account username.
|
||||||
|
type FullName struct {
|
||||||
|
FirstName string `json:"firstName"`
|
||||||
|
LastName string `json:"lastName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account is the account data.
|
||||||
|
type Account struct {
|
||||||
|
UserId string `json:"userId"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
PaymentId string `json:"paymentId"`
|
||||||
|
Authenticated uint32 `json:"authenticated"`
|
||||||
|
AccountStatus string `json:"accountStatus"`
|
||||||
|
SerialNumber string `json:"serialNumber"`
|
||||||
|
CountryCode string `json:"countryCode"`
|
||||||
|
TocUpdate bool `json:"tocUpdate"`
|
||||||
|
PolicyUpdate bool `json:"policyUpdate"`
|
||||||
|
ValidEmail bool `json:"validEmail"`
|
||||||
|
Arlo bool `json:"arlo"`
|
||||||
|
DateCreated uint64 `json:"dateCreated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owner is the owner of a Device data.
|
||||||
|
type Owner struct {
|
||||||
|
FirstName string `json:"firstName"`
|
||||||
|
LastName string `json:"lastName"`
|
||||||
|
OwnerId string `json:"ownerId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties is the Device properties data.
|
||||||
|
type Properties struct {
|
||||||
|
ModelId string `json:"modelId"`
|
||||||
|
OlsonTimeZone string `json:"olsonTimeZone"`
|
||||||
|
HwVersion string `json:"hwVersion"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device is the device data.
|
||||||
|
type Device struct {
|
||||||
|
DeviceType string `json:"deviceType"`
|
||||||
|
XCloudId string `json:"xCloudId"`
|
||||||
|
DisplayOrder uint8 `json:"displayOrder"`
|
||||||
|
State string `json:"state"`
|
||||||
|
ModelId string `json:"modelId"`
|
||||||
|
InterfaceVersion string `json:"interfaceVersion"`
|
||||||
|
UserId string `json:"userId"`
|
||||||
|
DeviceName string `json:"deviceName"`
|
||||||
|
FirmwareVersion string `json:"firmwareVersion"`
|
||||||
|
MediaObjectCount uint8 `json:"mediaObjectCount"`
|
||||||
|
DateCreated uint64 `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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devices is an array of Device objects.
|
||||||
|
type Devices []Device
|
||||||
|
|
||||||
|
// LibraryMetaData is the library meta data.
|
||||||
|
type LibraryMetaData struct {
|
||||||
|
// TODO: Fill this out.
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserProfile is the user profile data.
|
||||||
|
type UserProfile struct {
|
||||||
|
// TODO: Fill this out.
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginResponse is an intermediate struct used when parsing data from the Login() call.
|
||||||
|
type LoginResponse struct {
|
||||||
|
Data Account
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceResponse is an intermediate struct used when parsing data from the GetDevices() call.
|
||||||
|
type DeviceResponse struct {
|
||||||
|
Data Devices
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LibraryMetaDataResponse is an intermediate struct used when parsing data from the GetLibraryMetaData() call.
|
||||||
|
type LibraryMetaDataResponse struct {
|
||||||
|
Data LibraryMetaData
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserProfile is an intermediate struct used when parsing data from the UpdateProfile() call.
|
||||||
|
type UserProfileResponse struct {
|
||||||
|
Data UserProfile
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user