Skip to content

Commit

Permalink
Merge 54bde78 into a53fff4
Browse files Browse the repository at this point in the history
  • Loading branch information
rekram1-node authored Jan 13, 2023
2 parents a53fff4 + 54bde78 commit 0ae5c59
Show file tree
Hide file tree
Showing 13 changed files with 126 additions and 63 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/pullRequest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
uses: golangci/golangci-lint-action@v3
with:
version: v1.50.1

test:
runs-on: ubuntu-latest
steps:
Expand All @@ -31,4 +32,27 @@ jobs:
- name: Test
run: |
go test -v ./...
deploy-experimental:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19.x
- name: Tag Release
uses: anothrNick/github-tag-action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WITH_V: true
- name: Publish Release
uses: goreleaser/goreleaser-action@v2
with:
version: experimentasl
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}


3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ dist
# vscode artifacts
.vscode

# videos
*.mp4

# used for testing, not necessary for library
main.go

Expand Down
File renamed without changes.
34 changes: 21 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,31 @@ func main() {
email := "[email protected]"
password := "PLEASE_DON'T_PLAINTEXT_REAL_PASSWORDS"

// returns account object with: email, password, uuid
account := blink.NewAccount(email, password)
// returns account object with: email, password, uuid
// this is required for login and once authenticated, used
// for any blink operations
account := blink.NewAccount(user, pass)

// this returns a login response that you can use
// however, it is unneccessary for this example
if _, err := account.Login(); err != nil {
loginResp, err := account.Login()

if err != nil {
log.Fatal(err)
}

fmt.Print("Enter Pin: ")
var pin string
fmt.Scanln(&pin)

// this returns a verify pin response that you can use
// however, it is unneccessary for this example
if _, err = account.VerifyPin(pin); err != nil {
log.Fatal(err)
// if blink wants a 2FA verification you must use the
// verify pin operation
// 2FA is not always required but typically required first
// time on new device
if loginResp.Account.AccountVerificationRequired {
fmt.Print("Enter Pin: ")
var pin string
fmt.Scanln(&pin)

// this returns a verify pin response that you can use
// however, it is unneccessary for this example
if _, err := account.VerifyPin(pin); err != nil {
log.Fatal(err)
}
}
}
```
Expand Down
4 changes: 1 addition & 3 deletions blink/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ type Account struct {

ID int
ClientID int

SyncModules *[]SyncModule
Networks *[]Network
}

// Creates an account object that can be used to login
func NewAccount(email, pass string) *Account {
u := uuid.NewV4() // used for refresh authentication

Expand Down
4 changes: 4 additions & 0 deletions blink/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type LoginResponse struct {
AllowPinResendSeconds int `json:"allow_pin_resend_seconds"`
}

// Completes a login request, might require a verify pin authentication as well
func (account *Account) Login() (*LoginResponse, error) {
body := map[string]interface{}{
"email": account.Email,
Expand Down Expand Up @@ -85,6 +86,7 @@ func (account *Account) updateAccountInfo(resp *LoginResponse) {
account.AuthToken = resp.Auth.Token
}

// Fetches new auth token
func (account *Account) RefreshToken() (*LoginResponse, error) {
// this just for readability of code I created RefreshToken
return account.Login()
Expand All @@ -97,6 +99,7 @@ type VerifyResponse struct {
Code int `json:"code"`
}

// Verifies pin sent via email or sms
func (account *Account) VerifyPin(pin string) (*VerifyResponse, error) {
verifyRes := &VerifyResponse{}
c := client.New(account.AuthToken)
Expand All @@ -119,6 +122,7 @@ func (account *Account) VerifyPin(pin string) (*VerifyResponse, error) {
return verifyRes, nil
}

// Logs user out
func (account *Account) Logout() error {
c := client.New(account.AuthToken)
url := fmt.Sprintf("https://rest-%s.immedia-semi.com/api/v4/account/%d/client/%d/logout", account.Tier, account.ID, account.ClientID)
Expand Down
1 change: 1 addition & 0 deletions blink/camera.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Camera struct {
Color string `json:"color"`
}

// Lists Cameras from manifest
func (account *Account) GetCameras() ([]Camera, error) {
manifest, err := account.GetManifest()

Expand Down
33 changes: 28 additions & 5 deletions blink/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ type SyncModule struct {
Revision string `json:"revision"`
}

// Returns full manifest of all networks, syncmodules, cameras, devices
// This is generally what you would use post login to then use information
// To download or other activities
func (account *Account) GetManifest() (*DeviceManifest, error) {
c := client.New(account.AuthToken)
url := fmt.Sprintf("https://rest-%s.immedia-semi.com/api/v3/accounts/%d/homescreen", account.Tier, account.ID)
Expand All @@ -107,8 +110,6 @@ func (account *Account) GetManifest() (*DeviceManifest, error) {
return nil, fmt.Errorf("failed to get device manifest, status code: %d, response: %s", resp.StatusCode(), resp.String())
}

account.SyncModules = &manifest.SyncModules

return manifest, nil
}

Expand All @@ -117,7 +118,7 @@ type localStorageManifestIDResponse struct {
NetworkID int `json:"network_id"`
}

func (account *Account) GetLocalStorageManifestRequestID(networkID, syncModuleID int) (int, error) {
func (account *Account) getLocalStorageManifestRequestID(networkID, syncModuleID int) (int, error) {
localManifestIDResponse := &localStorageManifestIDResponse{}
c := client.New(account.AuthToken)
url := fmt.Sprintf("https://rest-%s.immedia-semi.com/api/v1/accounts/%d/networks/%d/sync_modules/%d/local_storage/manifest/request", account.Tier, account.ID, networkID, syncModuleID)
Expand All @@ -143,7 +144,15 @@ type LocalStorageManifest struct {
Clips []Clip `json:"clips"`
}

func (account *Account) GetLocalStorageManifest(networkID, syncModuleID, manifestRequestID int) (*LocalStorageManifest, error) {
// Returns the local storage manifest, can take a little while depending on network availability
// Returns Object contaiting version, manifestID, and list of clips for sync module
func (account *Account) GetLocalStorageManifest(networkID, syncModuleID int) (*LocalStorageManifest, error) {
manifestRequestID, err := account.getLocalStorageManifestRequestID(networkID, syncModuleID)

if err != nil {
return nil, err
}

c := client.New(account.AuthToken)
url := fmt.Sprintf("https://rest-%s.immedia-semi.com/api/v1/accounts/%d/networks/%d/sync_modules/%d/local_storage/manifest/request/%d", account.Tier, account.ID, networkID, syncModuleID, manifestRequestID)
localManifest := &LocalStorageManifest{}
Expand All @@ -154,7 +163,21 @@ func (account *Account) GetLocalStorageManifest(networkID, syncModuleID, manifes

if err != nil {
return nil, err
} else if !resp.IsSuccess() {
}

for resp.StatusCode() == 409 {
resp, err = c.R().
SetResult(localManifest).
Get(url)

if err != nil {
return nil, err
}

time.Sleep(time.Second * 1)
}

if !resp.IsSuccess() {
return nil, fmt.Errorf("failed to get local storage manifest, status code: %d, response: %s", resp.StatusCode(), resp.String())
}

Expand Down
11 changes: 5 additions & 6 deletions blink/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ type Network struct {
} `json:"cameras"`
}

func (account *Account) GetListOfNetworks() error {
// Lists networks and cameras
func (account *Account) GetListOfNetworks() (*[]Network, error) {
networkList := &NetworkList{}
c := client.New(account.AuthToken)
url := fmt.Sprintf("https://rest-%s.immedia-semi.com/api/v1/camera/usage", account.Tier)
Expand All @@ -36,12 +37,10 @@ func (account *Account) GetListOfNetworks() error {
Get(url)

if err != nil {
return err
return nil, err
} else if !resp.IsSuccess() {
return fmt.Errorf("failed to get list of networks, status code: %d, response: %s", resp.StatusCode(), resp.String())
return nil, fmt.Errorf("failed to get list of networks, status code: %d, response: %s", resp.StatusCode(), resp.String())
}

account.Networks = &networkList.Networks

return nil
return &networkList.Networks, nil
}
8 changes: 4 additions & 4 deletions blink/syncmodule.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package blink

func (account *Account) GetSyncModules() error {
// Returns sync modules from manifest
func (account *Account) GetSyncModules() (*[]SyncModule, error) {
manifest, err := account.GetManifest()

if err != nil {
return err
return nil, err
}

account.SyncModules = &manifest.SyncModules
return nil
return &manifest.SyncModules, nil
}
55 changes: 31 additions & 24 deletions blink/video.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,46 +43,45 @@ func (account *Account) GetVideoEvents(pages int) (*VideoEvents, error) {
return videoEvents, nil
}

func (account *Account) GetClipIDs(networkID, syncModuleID, requestID int) (*[]Clip, string, error) {
manifest, err := account.GetLocalStorageManifest(networkID, syncModuleID, requestID)
func (account *Account) DownloadVideoByClipID(networkID, syncModuleID int, manifestID, clipID, fileName string) error {
err := account.requestUpload(networkID, syncModuleID, manifestID, clipID)

if err != nil {
return nil, "", err
return err
}

return &manifest.Clips, manifest.ManifestID, nil
}

func (account *Account) DownloadVideoByClipID(networkID, syncModuleID int, manifestID, clipID, fileName string) error {
// filename should have the .mp4 included in it
checkFileName(&fileName)
c := client.New(account.AuthToken)
url := fmt.Sprintf("https://rest-%s.immedia-semi.com/api/v1/accounts/%d/networks/%d/sync_modules/%d/local_storage/manifest/%s/clip/request/%s", account.Tier, account.ID, networkID, syncModuleID, manifestID, clipID)

// wait for upload to complete
time.Sleep(5 * time.Second)

return downloadVideo(url, fileName, c)
}

type UploadResponse struct {
ID int `json:"id"`
NetworkID int `json:"network_id"`
}
// type UploadResponse struct {
// ID int `json:"id"`
// NetworkID int `json:"network_id"`
// }

// For local storage use
func (account *Account) RequestUploadByClipID(networkID, syncModuleID int, manifestID, clipID string) (*UploadResponse, error) {
uploadRes := &UploadResponse{}
// All Local Storage Clips must be uploaded before one can download
func (account *Account) requestUpload(networkID, syncModuleID int, manifestID, clipID string) error {
// uploadRes := &UploadResponse{}
c := client.New(account.AuthToken)
url := fmt.Sprintf("https://rest-%s.immedia-semi.com/api/v1/accounts/%d/networks/%d/sync_modules/%d/local_storage/manifest/%s/clip/request/%s", account.Tier, account.ID, networkID, syncModuleID, manifestID, clipID)

resp, err := c.R().
SetResult(uploadRes).
// SetResult(uploadRes).
Post(url)

if err != nil {
return nil, err
return err
} else if !resp.IsSuccess() {
return nil, fmt.Errorf("failed to get request upload clip by ID: %s, status code: %d, response: %s", clipID, resp.StatusCode(), resp.String())
return fmt.Errorf("failed to get request upload clip by ID: %s, status code: %d, response: %s", clipID, resp.StatusCode(), resp.String())
}

return uploadRes, nil
return nil
}

type AllMedia struct {
Expand Down Expand Up @@ -166,7 +165,7 @@ func (account *Account) GetVideos(sinceTimestamp string, pageNum int) (*[]Video,
return &media.Videos, nil
}

func (account *Account) DownloadVideosByPages(pages int, downloadDir string) error {
func (account *Account) DownloadVideosByPage(pages int, downloadDir string) error {
timeStamp := "1970-01-01T00:00Z"
allMedia, err := account.GetMedia(timeStamp, pages)

Expand All @@ -188,14 +187,15 @@ func (account *Account) DownloadVideosByPages(pages int, downloadDir string) err
return nil
}

func downloadVideo(url, file string, c *resty.Client) error {
out, err := os.Create(file)
func downloadVideo(url, fileName string, c *resty.Client) error {
checkFileName(&fileName)
file, err := os.Create(fileName)

if err != nil {
return err
}

defer out.Close()
defer file.Close()

resp, err := c.R().
SetDoNotParseResponse(true). // necessary to read file
Expand All @@ -208,9 +208,16 @@ func downloadVideo(url, file string, c *resty.Client) error {
}

// copy mp4 video into file
if _, err = io.Copy(out, resp.RawBody()); err != nil {
if _, err = io.Copy(file, resp.RawBody()); err != nil {
return err
}

return resp.RawBody().Close()
}

func checkFileName(name *string) {
fileType := ".mp4"
if !strings.Contains(*name, fileType) {
*name += fileType
}
}
8 changes: 4 additions & 4 deletions docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ if err != nil {
}
```

## Videos Local Storage
## Videos Local From Storage
I did not discover this myself, this is from [blinkpy](https://github.com/fronzbot/blinkpy)

The steps for pulling videos from local storage
Expand All @@ -157,7 +157,7 @@ The steps for pulling videos from local storage

Beware the upload/download sequence, there must be a waiting period between the two as the operation is not instantenous

### List Videos Local Storage
### List Videos From Local Storage
```go
// this manifest will contain your networks and sync modules
manifest, err := account.GetManifest()
Expand All @@ -183,10 +183,10 @@ if err != nil {
}

// extract clipID
clipID := clips[0].ID
clipID := (*clips)[0].ID
```

### Download Videos Local Storage
### Download Videos From Local Storage
```go
// this assumes you already did the list videos from local storage operation and parsed out the clip id you want to download

Expand Down
Loading

0 comments on commit 0ae5c59

Please sign in to comment.