Skip to content

A unofficial Whistle.com (Whistle Pet Collar) API SDK built with Go.

License

Notifications You must be signed in to change notification settings

amattu2/go-whistle-wrapper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Introduction

This is an unofficial Go API wrapper for the Whistle smart pet collar. This wrapper handles authentication and interacting with all known endpoints.

Currently, the wrapper only focuses on reading from the API endpoints, though this may change in the future. Most of the endpoints do support standard CRUD actions, however.

If you have discovered endpoints not listed here, please open a PR or submit an issue.

Go Report Card CodeQL Analysis Go Tests

Usage

ThunderClient / Postman

If you're only here for the (previously) undocumented API endpoints provided by https://Whistle.com, check out the Thunder Tests folder. This folder contains the HTTP request collection supported by the Whistle V3 API.

Setup

Install the wrapper within your project via Go Get

go get github.com/amattu2/go-whistle-wrapper

Initialization

The wrapper exposes two ways of instantiating a client.

With Bearer Token

If you already have a bearer token, you can instantiate a new wrapper via

whistle := whistle.InitializeBearer("API_TOKEN_HERE")

This is useful for cases where you want to reduce overhead on page reload. You should ideally use this method as often as possible.

With API Key **Note**: I believe this is deprecated and should not be used. The mobile application uses HTTP bearer, and this may be removed unpredictably.

If you already have an API key (X-Whistle-AuthToken), you can instantiate a new wrapper via

whistle := whistle.InitializeToken("API_TOKEN_HERE")

This is useful for cases where you want to reduce overhead on page reload. You should ideally use this method as often as possible.

With Email/Password

If you don't have an active API key, but have credentials that work on the https://Whistle.com mobile app or on https://app.Whistle.com, you can instantiate a new wrapper via

whistle := whistle.Initialize("EMAIL", "PASSWORD")
With Email/Refresh

If you don't have the HTTP bearer token cached, you can reauthenticate using your email and refresh token credentials. This would be preferred over storing a user's password in a cache somewhere. The refresh token is returned during authentication with a email/password.

whistle := whistle.InitializeRefreshToken("EMAIL", "TOKEN")
Manually

In the event that you have an advanced need, you may also initialize the wrapper directly. You only need email/password, email/refresh_token, token, or bearer, but never all 4 options together.

If you provide a email and password or email and refresh_token, a HTTP bearer will automatically be requested and stored on your first API query.

  client := whistle.Client{
    email: "ABC", // Option 1
    password: "XYZ", // Option 1-1
    refreshToken: "XYZ", // Option 1-2
    token: "123", // Option 2
    bearer: "abc12932", // Option 3
    Timeout: 3000,
    Env: whistle.ProdEnv, // Or: whistle.StagingEnv
    UserAgent: "Custom User Agent",
  }

Methods

Important note: The Whistle.com API REQUIRES a Accept: application/vnd.whistle.com.v4+json header to be present in almost ALL REQUESTS otherwise it will return 404. Occasionally a endpoint (usually a deprecated one) will accept application/json.

Users

This section covers all implementations relating to the REST API surrounding users (/api/users).

DEPRECATED: Users()

Get information about the currently authenticated user. This does NOT provide information about all associated users.

// ...
q := client.Users()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {CreatedAt, ..., Username}
// ...
Me()

Returns information about the authenticated user.

// ...
q := client.Me()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.User) // {CreatedAt, ..., Username}
// ...
CheckEmail(email string)

Used to check if an email exists within the database.

HTTP 404 - Non existing

HTTP 204 - User exists

// ...
q := client.CheckEmail("abc@gmail.com")

fmt.Println(q.Response) // true = exists, false = non-existing
// ...
InvitationCodes(code string)

List information about a invitation code. Used during the Whistle App invite process.

// ...
q := client.InvitationCodes("code123")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {pet: ...}
// ...
ApplicationState()

Get information about the current application state. Current usage unknown.

// ...
q := client.ApplicationState()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.ApplicationState) // {...}
// ...
DEPRECATED: CreditCard()

Get information about the current credit card on file. Does not return the actual card number.

// ...
q := client.CreditCard()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response)  // {CardType, ..., ZipCode}
// ...
Subscriptions()

Get a list of subscriptions tied to an account, along with any Partner subscriptions.

// ...
q := client.Subscriptions()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {Subscriptions: ..., PartnerServices: ...}
// ...
CancellationPreview()

Current usage unknown.

// ...
q := client.CancellationPreview()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // TBD
// ...
CancellationReasons()

Returns a list of reasons to cancel a subscription.

// ...
q := client.CancellationReasons()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {cancellation_reasons: [{id: 123, ...}, ...]}
// ...

Devices

This portion of the document outlines the implementations of the smart collar REST api endpoints.

Device(deviceId string)

Provides information about the specified smart collar device.

// ...
q := client.Device("serial_num")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {device: {model_id: ..., ..., has_gps: true, ...}
// ...
DeviceActivationCheck(deviceId string)

Returns HTTP 204 if the device Id is valid, but not registered

Returns HTTP 422 if the device is registered

Returns HTTP 404 if the id is invalid

// ...
q := client.DeviceActivationCheck("serial_num")

q.StatusCode // "204"
q.Error // nil
// ...
DevicePlans(deviceId string)

Provides information about the specified device plans

// ...
q := client.DevicePlans("serial_num")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {paid_through: "", plans: [ ... ] }
// ...
DeviceSubscription(deviceId string)

Provides information about the specified device subscription status

// ...
q := client.DeviceSubscription("serial_num")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {id: 123, ..., plan: {...}}
// ...
DeviceSubscriptionPreview(deviceId string, planId string)

Current usage unknown

// ...
q := client.DeviceSubscriptionPreview("serial_num", "abc")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // TBD
// ...
DeviceUpgradePreview(deviceId string)

Current usage unknown

// ...
q := client.DeviceUpgradePreview("serial_num")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // TBD
// ...
DeviceWifiNetworks(deviceId string)

Provides a listing of all connected networks associated with a device.

// ...
q := client.DeviceWifiNetworks("serial_num")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // [{id: ..., ssid: "xyz"}, ...]
// ...

Breeds

This section related to all of the endpoints (currently only 1) relating to animal breeds.

Breeds(animal string)

Provides a list of breeds given the current animal species. Known options are dogs or cats

// ...
q := client.Breeds("dogs")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Breeds) // [{ID: 123, Name: "German Shepherd", ...}, ...]
// ...

Pets

These are the operations relating to pets (cats/dogs/etc).

Pets()

Returns a populated array of objects describing a Pet belonging to the authenticated user.

// ...
q := client.Pets()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Pets) // {ID: 135, Name: "Baker", ...}
// ...
PetTransfers()

Returns an array of pets that qualify for a transfer. Unsure of the current usage.

// ...
q := client.PetTransfers()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Transfers) // [{ID: 123, Name: "Fido", ...}, ...]
// ...
Pet(petId string)

Returns detailed information about a specific pet.

// ...
q := client.Pet("petid123")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Pet) // {ID: 123, ..., Name: "Fido"}
// ...
PetOwners(petId string)

Returns an array of people that are tied to a pet as owners.

// ...
q := client.PetOwners("pet1233")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Owners) // [{Name: "amattu2", ..., Email: "xyz@gmail.com"}]
// ...
PetWhereabouts(petId string, startDate string, endDate string)

Returns informations about a pet's historical locations. Based on start/end dates. Provides locations and known places.

// ...
q := client.PetWhereabouts("pet321", "2022-03-03", "2024-01-01")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {Locations: [...], Places: [...]}
// ...
PetLocationsRecent(petId string)

Similar to PetWhereabouts, this returns detailed locations about where a pet has been as of recent.

// ...
q := client.PetLocationsRecent("3892821")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Locations) // [{...}]
// ...
PetAchievements(petId string)

Returns a list of achievements that a pet CAN make. The achievements indicate whether or not that goal has been met.

// ...
q := client.PetAchievements("3828111")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Achievements) // [{ID: 8382, Name: "1 Week Streak"}]
// ...
PetStatistics(petId string)

Returns analytical insights about a pet.

// ...
q := client.PetStatistics("12345")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Statistics) // {AverageMinutesActive: 0, ...}
// ...
PetDailies(petId string)

Returns high-level information about a pet's daily activities

// ...
q := client.PetDailies("12345")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Dailies) // [{DayNumber: 93381, ..., UpdatedAt: "..."}]
// ...
PetDaily(petId string, dailyId string)

Returns detailed information about a particular pet's daily activity

// ...
q := client.PetDaily("1234", "938191")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Daily) // [{DayNumber: 938191, ...}]
// ...
PetDailyItems(petId string, dailyId string)

Returns very low-level, and highly-detailed breakdown of a pet's daily activity.

// ...
q := client.PetDailyItems("1234", "938191")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.DailyItems) // [...]
// ...
PetHealthTrends(petId string)

Provides health trend information about the specified pet.

// ...
q := client.PetHealthTrends("12345")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Trends) // {...}
// ...
PetHealthGraphs(petId string, trend string, days int)

Provides data to generate a graph for the specified health trend. Days limits the number of observations to include.

// ...
q := client.PetHealthTrends("1234", "sleeping", 7)

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {Data: [...], PetId: 1234, ...}
// ...
PetNutritionPortions(petId string)

Returns the suggested food portions for the given pet.

// ...
q := client.PetNutritionPortions("1234")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {Treats: ..., SuggestedCalories: 0.0}
// ...
DEPRECATED: PetFoodPortions(petId string)

Returns the suggested food portions for the given pet. Replaced by the above method.

// ...
q := client.PetFoodPortions("1234")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.PetFoodPortions) // ...
// ...
PetTask(petId string, taskId string)

Returns information about the pet's task.

// ...
q := client.PetTask("1234", "35")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // ...
// ...
PetTaskOccurrence(petId string, occurrenceType string)

Current usage unknown.

// ...
q := client.PetTaskOccurrence("1234", "incomplete")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // ...
// ...

Miscellaneous

These are operations not categorized by another API route.

Notifications()

Returns an array of unread notifications for the current user.

// ...
q := client.Notifications()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // {Items: [...]}
// ...
PetFoods(foodType string)

Returns a list of pet foods given the food type. Known options are dog_treat, dog_food. Cat variant does not work.

// ...
q := client.PetFoods("dog_food")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // [{ID: 321, Name: "Purina XXX"}, ...]
// ...
ReverseGeocode(latitude string, longitude string)

Decode latitude and longitude to a physical address.

// ...
q := client.ReverseGeocode("LAT", "LON")

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response.Description) // {address: ..., region: ..., etc}
// ...
Places()

Returns a list of saved places tied to a user account.

// ...
q := client.Places()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // [ {address: "123 ABC Lane", ..., id: 123}, ...]
// ...
AdventureCategories()

Returns a list of adventure categories. Current usage unknown.

// ...
q := client.AdventureCategories()

q.StatusCode // "200"
q.Error // nil

fmt.Println(q.Response) // []
// ...

Requirements

Credits

The following resources were used to compile this API wrapper.

To-Do

These are the remaining action items of this project. Some of them are on hold until I have a device to test with.

  • Add support for the async event pusher service (See realtime_channel)
  • Check into the WiFi adding endpoint

About

A unofficial Whistle.com (Whistle Pet Collar) API SDK built with Go.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages