fitbit_api 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +88 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +95 -0
- data/Rakefile +6 -0
- data/fitbit_api.gemspec +32 -0
- data/lib/fitbit_api.rb +6 -0
- data/lib/fitbit_api/activities.rb +174 -0
- data/lib/fitbit_api/alarms.rb +52 -0
- data/lib/fitbit_api/base.rb +24 -0
- data/lib/fitbit_api/body.rb +55 -0
- data/lib/fitbit_api/client.rb +94 -0
- data/lib/fitbit_api/devices.rb +7 -0
- data/lib/fitbit_api/food.rb +51 -0
- data/lib/fitbit_api/friends.rb +11 -0
- data/lib/fitbit_api/goals.rb +61 -0
- data/lib/fitbit_api/heart_rate.rb +50 -0
- data/lib/fitbit_api/helpers/configuration.rb +25 -0
- data/lib/fitbit_api/helpers/exceptions.rb +4 -0
- data/lib/fitbit_api/helpers/utils.rb +84 -0
- data/lib/fitbit_api/sleep.rb +36 -0
- data/lib/fitbit_api/user.rb +15 -0
- data/lib/fitbit_api/version.rb +4 -0
- data/lib/fitbit_api/water.rb +7 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 31efa6a0f7a2789cf58d8e7610896d61a62190b6
|
4
|
+
data.tar.gz: 18ca4f3a85c6ef71912eab47ad1b967fad4a70f2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 15cd160837bffa0569b67427559213897620e58512e531ef8f244ea0ac80f23738870cc95ff5e021322223f014014a2df4ec6adac2aac7f7a80c54b4965034e3
|
7
|
+
data.tar.gz: 59fe662dabce55df5f2bfc6163802fcd43a71286b1bd44a0211c7df31c406c494b0af146d636bf3969729b0708ccd4b04d1f5a53cdb51b6670e88a4c65af840c
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
0.7.1
|
2
|
+
-----
|
3
|
+
- Rename `#auth_page_link` to `#auth_url`
|
4
|
+
- Rename `snake_case` option to `snake_case_keys`
|
5
|
+
- Code cleanup
|
6
|
+
|
7
|
+
0.7.0
|
8
|
+
-----
|
9
|
+
- Add support for Activity and Heart Rate Intraday Time Series endpoints.
|
10
|
+
- Add support for weight and fat logging and deletion endpoints.
|
11
|
+
- Renamed `#weight_goals` and `#body_fat_goals` to `#weight_goal` and `#body_fat_goal`.
|
12
|
+
|
13
|
+
0.6.0
|
14
|
+
-----
|
15
|
+
- A `refresh_token` option can be passed in to retrieve an existing access token.
|
16
|
+
- A configuration block can now be passed in to FitbitAPI, to allow for the setting of default instance attributes.
|
17
|
+
- File/code reorganization.
|
18
|
+
|
19
|
+
0.5.0
|
20
|
+
-----
|
21
|
+
- Add Heart Rate endpoint support. Add support for Time Series endpoints.
|
22
|
+
- Minor improvements to some helper functions
|
23
|
+
|
24
|
+
0.4.1
|
25
|
+
-----
|
26
|
+
- Users can now provide either snake_cased or camelCased param attribute keys when POSTing data to Fitbit. Keys are automatically converted to camelCase before a request is sent to Fitbit's API.
|
27
|
+
|
28
|
+
0.4.0
|
29
|
+
-----
|
30
|
+
- Remove FitStruct objects
|
31
|
+
- The response's hash keys can now be transformed using the following options:
|
32
|
+
- `snake_case` - if set to true, all keys are formatted to snake_case
|
33
|
+
- `symbolize_keys` - if set to true, all keys are converted to symbols
|
34
|
+
|
35
|
+
0.3.0
|
36
|
+
-----
|
37
|
+
- API endpoint support for the following actions:
|
38
|
+
- Add alarm
|
39
|
+
- Update alarm
|
40
|
+
- Delete alarm
|
41
|
+
- File reorganization
|
42
|
+
|
43
|
+
0.2.7
|
44
|
+
-----
|
45
|
+
- Set minimum required Ruby version
|
46
|
+
- Minor testing tweaks for Travis CI
|
47
|
+
|
48
|
+
0.2.6
|
49
|
+
-----
|
50
|
+
- Expand API endpoint support for the following actions:
|
51
|
+
- Add activity to favorites
|
52
|
+
- Retrieve list of activity log entries
|
53
|
+
- Create/update daily/weekly goals
|
54
|
+
- Delete activity
|
55
|
+
- Remove activity from favorites
|
56
|
+
|
57
|
+
0.2.5
|
58
|
+
-----
|
59
|
+
- Responses now return FitStruct objects (inherit from OpenStruct).
|
60
|
+
|
61
|
+
- Ability to set default data return format by setting `raw_response` on FitbitAPI::Client instance (defaults to using FitStruct objects). Can be overridden on each API call by specifying the `raw` boolean option.
|
62
|
+
|
63
|
+
0.2.4
|
64
|
+
-----
|
65
|
+
- Default result format for `get` calls now return OpenStruct objects, allowing for more convenient method-like attribute access. Specifying `raw: true` returns original JSON.
|
66
|
+
|
67
|
+
0.2.3
|
68
|
+
-----
|
69
|
+
- Remove `require 'json'`
|
70
|
+
|
71
|
+
0.2.2
|
72
|
+
-----
|
73
|
+
- MultiJson used for parsing JSON (with symbolized keys)
|
74
|
+
|
75
|
+
0.2.1
|
76
|
+
-----
|
77
|
+
- `user_id` is interpolated into resource URLs, removing the hardcoded `-`.
|
78
|
+
|
79
|
+
0.2.0
|
80
|
+
-----
|
81
|
+
- API version in resource URLs is now user settable.
|
82
|
+
- Scope argument takes both String and Array types.
|
83
|
+
- Default init values moved to `defaults` hash.
|
84
|
+
|
85
|
+
0.1.0
|
86
|
+
-----
|
87
|
+
- Initial auth logic setup for Client class.
|
88
|
+
- Endpoint support added for retrieving Food, Sleep, Water, Activities, Alarms, Body, Devices, Friends, and Users.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Zoran Pesic
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# FitbitAPI
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/fitbit_api.svg)](https://badge.fury.io/rb/fitbit_api)
|
4
|
+
[![Build Status](https://travis-ci.org/zokioki/fitbit_api.svg?branch=master)](https://travis-ci.org/zokioki/fitbit_api)
|
5
|
+
|
6
|
+
FitbitAPI provides a Ruby interface to the [Fitbit Web API](https://dev.fitbit.com/reference/web-api/quickstart).
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
To install the latest release:
|
11
|
+
|
12
|
+
$ gem install fitbit_api
|
13
|
+
|
14
|
+
To include in a Rails project, add it to the Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'fitbit_api'
|
18
|
+
```
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
To use the Fitbit API, you must register your application at [dev.fitbit.com](https://dev.fitbit.com/apps). After registering, you should have access to **CLIENT ID**, **CLIENT SECRET**, and **REDIRECT URI (Callback URL)** values for use in instantiating a *FitbitAPI::Client* object.
|
23
|
+
|
24
|
+
### Rails
|
25
|
+
|
26
|
+
Please reference the [fitbit_api_rails repo](https://github.com/zokioki/fitbit_api_rails) as an example of how to use this gem within Rails.
|
27
|
+
|
28
|
+
### Standalone
|
29
|
+
|
30
|
+
- Create a client instance:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
client = FitbitAPI::Client.new(client_id: 'XXXXXX',
|
34
|
+
client_secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
35
|
+
redirect_uri: 'http://example.com/handle/callback')
|
36
|
+
```
|
37
|
+
|
38
|
+
- Generate a link for the Fitbit authorization page:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
client.auth_url
|
42
|
+
# => https://fitbit.com/oauth2/authorize?client_id=123XYZ&redirect_uri=...
|
43
|
+
```
|
44
|
+
|
45
|
+
- Follow the generated link to Fitbit's authorization page. After approving your app, you're sent to the `redirect_uri`, with an appended authorization `code` param, which you'll exchange for an access token:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
client.get_token(auth_code)
|
49
|
+
```
|
50
|
+
|
51
|
+
You're now authenticated and can make calls to Fitbit's API:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
client.food_logs Date.today
|
55
|
+
# => { "foods" => [{ "isFavorite" => true, "logDate" => "2015-06-26", "logId" => 1820, "loggedFood" => { "accessLevel" => "PUBLIC", "amount" => 132.57, "brand" => "", "calories" => 752, ...}] }
|
56
|
+
```
|
57
|
+
|
58
|
+
To make the response more easily suited for attribute-assignment, it can be parsed to return a hash whose keys are in snake_case format. This can be done by setting the client's `snake_case_keys` option to `true`, like so:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
client.snake_case_keys = true
|
62
|
+
client.food_logs Date.today
|
63
|
+
# => { "foods" => [{ "is_favorite" => true, "log_date" => "2015-06-26", "log_id" => 1820, "logged_food" => { "access_level" => "PUBLIC", "amount" => 132.57, "brand" => "", "calories" => 752, ...}] }
|
64
|
+
```
|
65
|
+
|
66
|
+
Similarly, all arguments passed in through a POST request are automatically converted to camelCase before they hit Fitbit's API, making it easy to keep your codebase stylistically consistent. For example, all of the following would result in valid API calls:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
client.log_activity activity_id: 12345, duration_millis: '50000'
|
70
|
+
client.log_activity activityId: 54321, durationMillis: '44100'
|
71
|
+
# If for some reason you had to mix snake and camel case like below,
|
72
|
+
# FitbitAPI would make sure the result is a validly formatted request
|
73
|
+
client.log_activity activity_id: 12345, durationMillis: '683300'
|
74
|
+
```
|
75
|
+
|
76
|
+
### Options
|
77
|
+
|
78
|
+
When initializing a `FitbitAPI::Client` instance, you're given access to a handful of options:
|
79
|
+
|
80
|
+
- `:api_version` - API version to be used when making requests (default: "1")
|
81
|
+
|
82
|
+
- `:unit_system` - The measurement unit system to use for response values (default: "en_US" | available: "en_US", "en_GB", and "any" for metric)
|
83
|
+
|
84
|
+
- `:locale` - The locale to use for response values (default: "en_US" | available: "en_US", "fr_FR", "de_DE", "es_ES", "en_GB", "en_AU", "en_NZ" and "ja_JP")
|
85
|
+
|
86
|
+
- `:scope` - A space-delimited list of the permissions you are requesting (default: "activity nutrition profile settings sleep social weight heartrate" | available: "activity", "heartrate", "location", "nutrition", "profile", "settings" "sleep", "social" and "weight")
|
87
|
+
|
88
|
+
- `:snake_case_keys` - Transform returned object's keys to snake case format (default: false)
|
89
|
+
|
90
|
+
- `:symbolize_keys` - Transform returned object's keys to symbols (default: false)
|
91
|
+
|
92
|
+
|
93
|
+
## License
|
94
|
+
|
95
|
+
This gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/fitbit_api.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fitbit_api/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'fitbit_api'
|
8
|
+
spec.version = FitbitAPI::VERSION
|
9
|
+
spec.authors = ['Zoran']
|
10
|
+
|
11
|
+
spec.summary = %q{A Ruby interface to the Fitbit API, using OAuth2 (renamed to fitbit_api)}
|
12
|
+
spec.homepage = FitbitAPI::REPO_URL
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = 'exe'
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.required_ruby_version = '>= 1.9.3'
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'oauth2', '~> 1.0'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.10'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
spec.add_development_dependency 'rspec'
|
27
|
+
|
28
|
+
spec.post_install_message = %q{
|
29
|
+
The fitbit_api gem has been renamed to fitbit_api and will no longer be supported.
|
30
|
+
Please switch to using fitbit_api for all versions greater than 0.7.1.
|
31
|
+
}
|
32
|
+
end
|
data/lib/fitbit_api.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
module FitbitAPI
|
2
|
+
class Client
|
3
|
+
|
4
|
+
ACTIVITY_RESOURCES = %w(calories caloriesBMR steps distance floors elevation
|
5
|
+
minutesSedentary minutesLightlyActive minutesFairlyActive
|
6
|
+
minutesVeryActive activityCalories tracker/calories
|
7
|
+
tracker/steps tracker/distance tracker/floors
|
8
|
+
tracker/elevation tracker/minutesSedentary
|
9
|
+
tracker/minutesLightlyActive tracker/minutesFairlyActive
|
10
|
+
tracker/minutesVeryActive tracker/activityCalories)
|
11
|
+
|
12
|
+
ACTIVITY_INTRADAY_RESOURCES = %w(calories steps distance floors elevation)
|
13
|
+
|
14
|
+
# GET Activities
|
15
|
+
# ==============
|
16
|
+
|
17
|
+
# Retrieves a summary and list of a user's activities and activity log entries for a given day
|
18
|
+
# in the format requested using units in the unit system which corresponds to the Accept-Language header provided.
|
19
|
+
|
20
|
+
def daily_activity_summary(date=Date.today, opts={})
|
21
|
+
get("user/#{user_id}/activities/date/#{format_date(date)}.json", opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Retrieves a list of a user's frequent activities in the format requested using units
|
25
|
+
# in the unit system which corresponds to the Accept-Language header provided.
|
26
|
+
|
27
|
+
def frequent_activities(opts={})
|
28
|
+
get("user/#{user_id}/activities/frequent.json", opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
def recent_activities(opts={})
|
32
|
+
get("user/#{user_id}/activities/recent.json", opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a list of a user's favorite activities.
|
36
|
+
|
37
|
+
def favorite_activities(opts={})
|
38
|
+
get("user/#{user_id}/activities/favorite.json", opts)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Gets a tree of all valid Fitbit public activities from
|
42
|
+
# the activities catalog as well as private custom activities the user created.
|
43
|
+
|
44
|
+
def all_activities(opts={})
|
45
|
+
get('activities.json', opts)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Retrieves a list of a user's activity log entries before or after a given day with
|
49
|
+
# offset and limit using units in the unit system which corresponds to the Accept-Language header provided.
|
50
|
+
|
51
|
+
# ==== Parameters
|
52
|
+
# * +:beforeDate+ - the date; formatted in yyyy-MM-ddTHH:mm:ss
|
53
|
+
# * +:afterDate+ - the date; formatted in yyyy-MM-ddTHH:mm:ss
|
54
|
+
# * +:sort+ - the sort order of entries by date (asc or desc)
|
55
|
+
# * +:offset+ - the offset number of entries
|
56
|
+
# * +:limit+ - the max of the number of entries returned (max: 100)
|
57
|
+
|
58
|
+
def activity_logs_list(opts={})
|
59
|
+
get("user/#{user_id}/activities/list.json", opts)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the details of a specific activity in the Fitbit activities database in the format requested.
|
63
|
+
# If activity has levels, also returns a list of activity level details.
|
64
|
+
|
65
|
+
def activity(activity_id)
|
66
|
+
get("activities/#{activity_id}.json")
|
67
|
+
end
|
68
|
+
|
69
|
+
# Retrieves the user's activity statistics in the format requested using units
|
70
|
+
# in the unit system which corresponds to the Accept-Language header provided.
|
71
|
+
# Activity statistics includes Lifetime and Best achievement values from the
|
72
|
+
# My Achievements tile on the website dashboard.
|
73
|
+
|
74
|
+
def lifetime_stats(opts={})
|
75
|
+
get("user/#{user_id}/activities.json", opts)
|
76
|
+
end
|
77
|
+
|
78
|
+
def activity_time_series(resource, opts={})
|
79
|
+
start_date = opts[:start_date]
|
80
|
+
end_date = opts[:end_date] || Date.today
|
81
|
+
period = opts[:period]
|
82
|
+
|
83
|
+
unless ACTIVITY_RESOURCES.include?(resource)
|
84
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid resource: \"#{resource}\". Please provide one of the following: #{ACTIVITY_RESOURCES}."
|
85
|
+
end
|
86
|
+
|
87
|
+
if [start_date, period].none?
|
88
|
+
raise FitbitAPI::InvalidArgumentError, 'A start_date or period is required.'
|
89
|
+
end
|
90
|
+
|
91
|
+
if period && !PERIODS.include?(period)
|
92
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid period: \"#{period}\". Please provide one of the following: #{PERIODS}."
|
93
|
+
end
|
94
|
+
|
95
|
+
if period
|
96
|
+
result = get("user/#{user_id}/activities/#{resource}/date/#{format_date(end_date)}/#{period}.json", opts)
|
97
|
+
else
|
98
|
+
result = get("user/#{user_id}/activities/#{resource}/date/#{format_date(start_date)}/#{format_date(end_date)}.json", opts)
|
99
|
+
end
|
100
|
+
# remove root key from response
|
101
|
+
result.values[0]
|
102
|
+
end
|
103
|
+
|
104
|
+
def activity_intraday_time_series(resource, opts={})
|
105
|
+
date = opts[:date] || Date.today
|
106
|
+
detail_level = opts[:detail_level]
|
107
|
+
start_time = opts[:start_time]
|
108
|
+
end_time = opts[:end_time]
|
109
|
+
|
110
|
+
unless ACTIVITY_INTRADAY_RESOURCES.include?(resource)
|
111
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid resource: \"#{resource}\". Please provide one of the following: #{ACTIVITY_RESOURCES}."
|
112
|
+
end
|
113
|
+
|
114
|
+
if [date, detail_level].any?(&:nil?)
|
115
|
+
raise FitbitAPI::InvalidArgumentError, 'A date and detail_level are required.'
|
116
|
+
end
|
117
|
+
|
118
|
+
unless %(1min 15min).include? detail_level
|
119
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid detail_level: \"#{detail_level}\". Please provide one of the following: \"1min\" or \"15min\"."
|
120
|
+
end
|
121
|
+
|
122
|
+
if (start_time || end_time) && !(start_time && end_time)
|
123
|
+
raise FitbitAPI::InvalidArgumentError, 'Both start_time and end_time are required if time is being specified.'
|
124
|
+
end
|
125
|
+
|
126
|
+
if (start_time && end_time)
|
127
|
+
get("user/-/activities/#{resource}/date/#{format_date(date)}/1d/#{detail_level}/time/#{format_time(start_time)}/#{format_time(end_time)}.json")
|
128
|
+
else
|
129
|
+
get("user/-/activities/#{resource}/date/#{format_date(date)}/1d/#{detail_level}.json")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# POST Activities
|
134
|
+
# ===============
|
135
|
+
|
136
|
+
# Creates log entry for an activity or user's private custom activity using units
|
137
|
+
# in the unit system which corresponds to the Accept-Language header provided.
|
138
|
+
|
139
|
+
# ==== POST Parameters
|
140
|
+
# * +:activityId+ - activity id
|
141
|
+
# * +:activityName+ - custom activity name. Either activity ID or activityName must be provided
|
142
|
+
# * +:manualCalories+ - calories burned, specified manually. Required with activityName, otherwise optional
|
143
|
+
# * +:startTime+ - activity start time; formatted in HH:mm:ss
|
144
|
+
# * +:durationMillis+ - duration in milliseconds
|
145
|
+
# * +:date+ - log entry date; formatted in yyyy-MM-dd
|
146
|
+
# * +:distance+ - distance; required for logging directory activity; formatted in X.XX
|
147
|
+
# * +:distanceUnit+ - distance measurement unit
|
148
|
+
|
149
|
+
def log_activity(opts)
|
150
|
+
post("user/#{user_id}/activities.json", opts)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Adds the activity with the given ID to user's list of favorite activities.
|
154
|
+
|
155
|
+
def add_favorite_activity(activity_id)
|
156
|
+
post("user/#{user_id}/activities/favorite/#{activity_id}.json")
|
157
|
+
end
|
158
|
+
|
159
|
+
# DELETE Activities
|
160
|
+
# =================
|
161
|
+
|
162
|
+
# Deletes a user's activity log entry with the given ID.
|
163
|
+
|
164
|
+
def delete_activity(activity_log_id)
|
165
|
+
delete("user/#{user_id}/activities/#{activity_log_id}.json")
|
166
|
+
end
|
167
|
+
|
168
|
+
# Removes the activity with the given ID from a user's list of favorite activities.
|
169
|
+
|
170
|
+
def delete_favorite_activity(activity_id)
|
171
|
+
delete("user/#{user_id}/activities/favorite/#{activity_id}.json")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module FitbitAPI
|
2
|
+
class Client
|
3
|
+
# GET Alarms
|
4
|
+
# ==========
|
5
|
+
|
6
|
+
# Returns a list of the set alarms connected to a user's account.
|
7
|
+
|
8
|
+
def alarms(tracker_id, opts={})
|
9
|
+
get("user/#{user_id}/devices/tracker/#{tracker_id}/alarms.json", opts)
|
10
|
+
end
|
11
|
+
|
12
|
+
# POST Alarms
|
13
|
+
# ===========
|
14
|
+
|
15
|
+
# Adds the alarm settings to a given ID for a given device.
|
16
|
+
|
17
|
+
# ==== POST Parameters
|
18
|
+
# * +:time+ - time of day that the alarm vibrates with a UTC timezone offset, e.g. 07:15-08:00
|
19
|
+
# * +:enabled+ - boolean; if false, alarm does not vibrate until enabled is set to true
|
20
|
+
# * +:recurring+ - boolean; if false, the alarm is a single event
|
21
|
+
# * +:weekDays+ - comma separated list of days of the week on which the alarm vibrates (MONDAY,TUESDAY)
|
22
|
+
|
23
|
+
def add_alarm(tracker_id, opts={})
|
24
|
+
post("user/#{user_id}/devices/tracker/#{tracker_id}/alarms.json", opts)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Updates the alarm entry with a given ID for a given device.
|
28
|
+
|
29
|
+
# ==== POST Parameters
|
30
|
+
# * +:time+ - time of day that the alarm vibrates with a UTC timezone offset, e.g. 07:15-08:00
|
31
|
+
# * +:enabled+ - boolean; if false, alarm does not vibrate until enabled is set to true
|
32
|
+
# * +:recurring+ - boolean; if false, the alarm is a single event
|
33
|
+
# * +:weekDays+ - comma separated list of days of the week on which the alarm vibrates (MONDAY,TUESDAY)
|
34
|
+
# * +:snoozeLength+ - integer; minutes between alarms
|
35
|
+
# * +:snoozeCount+ - integer; maximum snooze count
|
36
|
+
# * +:label+ - string; label for alarm
|
37
|
+
# * +:vibe+ - vibe pattern; only one value for now (DEFAULT)
|
38
|
+
|
39
|
+
def update_alarm(tracker_id, alarm_id, opts={})
|
40
|
+
post("user/#{user_id}/devices/tracker/#{tracker_id}/alarms/#{alarm_id}.json", opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
# DELETE Alarms
|
44
|
+
# =============
|
45
|
+
|
46
|
+
# Deletes the user's device alarm entry with the given ID for a given device.
|
47
|
+
|
48
|
+
def delete_alarm(tracker_id, alarm_id, opts={})
|
49
|
+
delete("user/#{user_id}/devices/tracker/#{tracker_id}/alarms/#{alarm_id}.json", opts)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'fitbit_api/helpers/configuration'
|
2
|
+
require 'fitbit_api/helpers/utils'
|
3
|
+
require 'fitbit_api/helpers/exceptions'
|
4
|
+
|
5
|
+
module FitbitAPI
|
6
|
+
extend Configuration
|
7
|
+
|
8
|
+
define_setting :client_id
|
9
|
+
define_setting :client_secret
|
10
|
+
define_setting :redirect_uri
|
11
|
+
|
12
|
+
define_setting :site_url, 'https://api.fitbit.com'
|
13
|
+
define_setting :authorize_url, 'https://www.fitbit.com/oauth2/authorize'
|
14
|
+
define_setting :token_url, 'https://api.fitbit.com/oauth2/token'
|
15
|
+
|
16
|
+
define_setting :unit_system, 'en_US'
|
17
|
+
define_setting :locale, 'en_US'
|
18
|
+
define_setting :scope, 'activity nutrition profile settings sleep social weight heartrate'
|
19
|
+
|
20
|
+
define_setting :api_version, '1'
|
21
|
+
|
22
|
+
define_setting :snake_case_keys, false
|
23
|
+
define_setting :symbolize_keys, false
|
24
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module FitbitAPI
|
2
|
+
class Client
|
3
|
+
BODY_RESOURCES = %w(bmi fat weight)
|
4
|
+
|
5
|
+
def weight_logs(date=Date.today, opts={})
|
6
|
+
get("user/-/body/log/weight/date/#{format_date(date)}.json", opts)
|
7
|
+
end
|
8
|
+
|
9
|
+
def body_fat_logs(date=Date.today, opts={})
|
10
|
+
get("user/-/body/log/fat/date/#{format_date(date)}.json", opts)
|
11
|
+
end
|
12
|
+
|
13
|
+
def body_time_series(resource, opts={})
|
14
|
+
start_date = opts[:start_date]
|
15
|
+
end_date = opts[:end_date] || Date.today
|
16
|
+
period = opts[:period]
|
17
|
+
|
18
|
+
unless BODY_RESOURCES.include?(resource)
|
19
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid resource: \"#{resource}\". Please provide one of the following: #{BODY_RESOURCES}."
|
20
|
+
end
|
21
|
+
|
22
|
+
if [period, start_date].none?
|
23
|
+
raise FitbitAPI::InvalidArgumentError, 'A start_date or period is required.'
|
24
|
+
end
|
25
|
+
|
26
|
+
if period && !PERIODS.include?(period)
|
27
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid period: \"#{period}\". Please provide one of the following: #{PERIODS}."
|
28
|
+
end
|
29
|
+
|
30
|
+
if period
|
31
|
+
result = get("user/#{user_id}/body/#{resource}/date/#{format_date(end_date)}/#{period}.json", opts)
|
32
|
+
else
|
33
|
+
result = get("user/#{user_id}/body/#{resource}/date/#{format_date(start_date)}/#{format_date(end_date)}.json", opts)
|
34
|
+
end
|
35
|
+
# remove root key from response
|
36
|
+
result.values[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_weight(opts)
|
40
|
+
post("user/#{user_id}/body/log/weight.json", opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete_weight_log(weight_log_id, opts={})
|
44
|
+
delete("user/#{user_id}/body/log/weight/#{weight_log_id}.json", opts)
|
45
|
+
end
|
46
|
+
|
47
|
+
def log_body_fat(opts)
|
48
|
+
post("user/#{user_id}/body/log/fat.json", opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete_body_fat_log(body_fat_log_id, opts={})
|
52
|
+
delete("user/#{user_id}/body/log/fat/#{body_fat_log_id}.json", opts)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'fitbit_api/base'
|
2
|
+
require 'fitbit_api/activities'
|
3
|
+
require 'fitbit_api/heart_rate'
|
4
|
+
require 'fitbit_api/goals'
|
5
|
+
require 'fitbit_api/alarms'
|
6
|
+
require 'fitbit_api/body'
|
7
|
+
require 'fitbit_api/devices'
|
8
|
+
require 'fitbit_api/food'
|
9
|
+
require 'fitbit_api/friends'
|
10
|
+
require 'fitbit_api/sleep'
|
11
|
+
require 'fitbit_api/user'
|
12
|
+
require 'fitbit_api/water'
|
13
|
+
|
14
|
+
module FitbitAPI
|
15
|
+
class Client
|
16
|
+
attr_accessor :api_version, :unit_system, :locale, :scope, :snake_case_keys, :symbolize_keys
|
17
|
+
attr_reader :user_id
|
18
|
+
|
19
|
+
def initialize(opts)
|
20
|
+
missing_args = [:client_id, :client_secret] - opts.keys
|
21
|
+
raise FitbitAPI::InvalidArgumentError, "Required arguments: #{missing_args.join(', ')}" if missing_args.size > 0
|
22
|
+
|
23
|
+
%w(client_id client_secret redirect_uri site_url authorize_url token_url
|
24
|
+
unit_system locale scope api_version snake_case_keys symbolize_keys).each do |attr|
|
25
|
+
instance_variable_set("@#{attr}", (opts[attr.to_sym] || FitbitAPI.send(attr)))
|
26
|
+
end
|
27
|
+
|
28
|
+
@client = OAuth2::Client.new(@client_id, @client_secret, site: @site_url,
|
29
|
+
authorize_url: @authorize_url, token_url: @token_url)
|
30
|
+
|
31
|
+
restore_token(opts[:refresh_token]) if opts[:refresh_token]
|
32
|
+
end
|
33
|
+
|
34
|
+
def auth_url
|
35
|
+
@client.auth_code.authorize_url(redirect_uri: @redirect_uri, scope: @scope)
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_token(auth_code)
|
39
|
+
@token = @client.auth_code.get_token(auth_code, redirect_uri: @redirect_uri, headers: auth_header)
|
40
|
+
@user_id = @token.params['user_id']
|
41
|
+
return @token
|
42
|
+
end
|
43
|
+
|
44
|
+
def restore_token(refresh_token)
|
45
|
+
@token = OAuth2::AccessToken.from_hash(@client, refresh_token: refresh_token).refresh!(headers: auth_header)
|
46
|
+
@user_id = @token.params['user_id']
|
47
|
+
return @token
|
48
|
+
end
|
49
|
+
|
50
|
+
def token
|
51
|
+
@token.expired? ? refresh_token : @token
|
52
|
+
end
|
53
|
+
|
54
|
+
def refresh_token
|
55
|
+
@token = @token.refresh!(headers: auth_header)
|
56
|
+
end
|
57
|
+
|
58
|
+
def auth_header
|
59
|
+
{ 'Authorization' => ('Basic ' + Base64.encode64(@client_id + ':' + @client_secret)) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def request_headers
|
63
|
+
{
|
64
|
+
'User-Agent' => "fitbit_api-#{FitbitAPI::VERSION} gem (#{FitbitAPI::REPO_URL})",
|
65
|
+
'Accept-Language' => @unit_system,
|
66
|
+
'Accept-Locale' => @locale
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def get(path, opts={})
|
71
|
+
response = token.get(("#{@api_version}/" + path), headers: request_headers).response
|
72
|
+
object = MultiJson.load(response.body) unless response.status == 204
|
73
|
+
process_keys!(object, opts)
|
74
|
+
end
|
75
|
+
|
76
|
+
def post(path, opts={})
|
77
|
+
response = token.post(("#{@api_version}/" + path), body: deep_keys_to_camel_case!(opts), headers: request_headers).response
|
78
|
+
object = MultiJson.load(response.body) unless response.status == 204
|
79
|
+
process_keys!(object, opts)
|
80
|
+
end
|
81
|
+
|
82
|
+
def delete(path, opts={})
|
83
|
+
response = token.delete(("#{@api_version}/" + path), headers: request_headers).response
|
84
|
+
object = MultiJson.load(response.body) unless response.status == 204
|
85
|
+
process_keys!(object, opts)
|
86
|
+
end
|
87
|
+
|
88
|
+
def process_keys!(object, opts={})
|
89
|
+
deep_keys_to_snake_case!(object) if (opts[:snake_case_keys] || snake_case_keys)
|
90
|
+
deep_symbolize_keys!(object) if (opts[:symbolize_keys] || symbolize_keys)
|
91
|
+
return object
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module FitbitAPI
|
2
|
+
class Client
|
3
|
+
FOOD_RESOURCES = %w(caloriesIn water)
|
4
|
+
|
5
|
+
def food_logs(date=Date.today, opts={})
|
6
|
+
get("user/#{user_id}/foods/log/date/#{format_date(date)}.json", opts)
|
7
|
+
end
|
8
|
+
|
9
|
+
def recent_foods(opts={})
|
10
|
+
get("user/#{user_id}/foods/log/recent.json", opts)
|
11
|
+
end
|
12
|
+
|
13
|
+
def frequent_foods(opts={})
|
14
|
+
get("user/#{user_id}/foods/log/frequent.json", opts)
|
15
|
+
end
|
16
|
+
|
17
|
+
def favorite_foods(opts={})
|
18
|
+
get("user/#{user_id}/foods/log/favorite.json", opts)
|
19
|
+
end
|
20
|
+
|
21
|
+
def food_goals(opts={})
|
22
|
+
get("user/#{user_id}/foods/log/goal.json", opts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def food_time_series(resource, opts={})
|
26
|
+
start_date = opts[:start_date]
|
27
|
+
end_date = opts[:end_date] || Date.today
|
28
|
+
period = opts[:period]
|
29
|
+
|
30
|
+
unless FOOD_RESOURCES.include?(resource)
|
31
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid resource: \"#{resource}\". Please provide one of the following: #{FOOD_RESOURCES}."
|
32
|
+
end
|
33
|
+
|
34
|
+
if [period, start_date].none?
|
35
|
+
raise FitbitAPI::InvalidArgumentError, 'A start_date or period is required.'
|
36
|
+
end
|
37
|
+
|
38
|
+
if period && !PERIODS.include?(period)
|
39
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid period: \"#{period}\". Please provide one of the following: #{PERIODS}."
|
40
|
+
end
|
41
|
+
|
42
|
+
if period
|
43
|
+
result = get("user/#{user_id}/foods/log/#{resource}/date/#{format_date(end_date)}/#{period}.json", opts)
|
44
|
+
else
|
45
|
+
result = get("user/#{user_id}/foods/log/#{resource}/date/#{format_date(start_date)}/#{format_date(end_date)}.json", opts)
|
46
|
+
end
|
47
|
+
# remove root key from response
|
48
|
+
result.values[0]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module FitbitAPI
|
2
|
+
class Client
|
3
|
+
# GET Goals
|
4
|
+
# =========
|
5
|
+
|
6
|
+
# Retrieves a user's current weight goal.
|
7
|
+
|
8
|
+
def weight_goal(opts={})
|
9
|
+
get("user/-/body/log/weight/goal.json", opts)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Retrieves a user's current body fat percentage goal.
|
13
|
+
|
14
|
+
def body_fat_goal(opts={})
|
15
|
+
get("user/-/body/log/fat/goal.json", opts)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Retrieves a user's current daily activity goals.
|
19
|
+
|
20
|
+
def daily_goals(opts={})
|
21
|
+
get("user/#{user_id}/activities/goals/daily.json", opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Retrieves a user's current weekly activity goals.
|
25
|
+
|
26
|
+
def weekly_goals(opts={})
|
27
|
+
get("user/#{user_id}/activities/goals/weekly.json", opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
# POST Goals
|
31
|
+
# ==========
|
32
|
+
|
33
|
+
# Creates or updates a user's daily activity goals and returns a response using units
|
34
|
+
# in the unit system which corresponds to the Accept-Language header provided.
|
35
|
+
|
36
|
+
# ==== POST Parameters
|
37
|
+
# * +:caloriesOut+ - calories output goal value; integer
|
38
|
+
# * +:activeMinutes+ - active minutes goal value; integer
|
39
|
+
# * +:floors+ - floor goal value; integer
|
40
|
+
# * +:distance+ - distance goal value; X.XX or integer
|
41
|
+
# * +:steps+ - steps goal value; integer
|
42
|
+
|
43
|
+
def create_or_update_daily_goals(opts={})
|
44
|
+
post("user/#{user_id}/activities/goals/daily.json", opts)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Creates or updates a user's weekly activity goals and returns a response using units
|
48
|
+
# in the unit system which corresponds to the Accept-Language header provided.
|
49
|
+
|
50
|
+
# ==== POST Parameters
|
51
|
+
# * +:caloriesOut+ - calories output goal value; integer
|
52
|
+
# * +:activeMinutes+ - active minutes goal value; integer
|
53
|
+
# * +:floors+ - floor goal value; integer
|
54
|
+
# * +:distance+ - distance goal value; X.XX or integer
|
55
|
+
# * +:steps+ - steps goal value; integer
|
56
|
+
|
57
|
+
def create_or_update_weekly_goals(opts={})
|
58
|
+
post("user/#{user_id}/activities/goals/weekly.json", opts)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module FitbitAPI
|
2
|
+
class Client
|
3
|
+
def heart_rate_time_series(opts={})
|
4
|
+
start_date = opts[:start_date]
|
5
|
+
end_date = opts[:end_date] || Date.today
|
6
|
+
period = opts[:period]
|
7
|
+
|
8
|
+
if [period, start_date].none?
|
9
|
+
raise FitbitAPI::InvalidArgumentError, 'A start_date or period is required.'
|
10
|
+
end
|
11
|
+
|
12
|
+
if period && !PERIODS.include?(period)
|
13
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid period: \"#{period}\". Please provide one of the following: #{PERIODS}."
|
14
|
+
end
|
15
|
+
|
16
|
+
if period
|
17
|
+
result = get("user/#{user_id}/activities/heart/date/#{format_date(end_date)}/#{period}.json", opts)
|
18
|
+
else
|
19
|
+
result = get("user/#{user_id}/activities/heart/date/#{format_date(start_date)}/#{format_date(end_date)}.json", opts)
|
20
|
+
end
|
21
|
+
# remove root key from response
|
22
|
+
result.values[0]
|
23
|
+
end
|
24
|
+
|
25
|
+
def heart_rate_intraday_time_series(opts={})
|
26
|
+
date = opts[:date] || Date.today
|
27
|
+
detail_level = opts[:detail_level]
|
28
|
+
start_time = opts[:start_time]
|
29
|
+
end_time = opts[:end_time]
|
30
|
+
|
31
|
+
if [date, detail_level].any?(&:nil?)
|
32
|
+
raise FitbitAPI::InvalidArgumentError, 'A date and detail_level are required.'
|
33
|
+
end
|
34
|
+
|
35
|
+
unless %(1sec 1min).include? detail_level
|
36
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid detail_level: \"#{detail_level}\". Please provide one of the following: \"1sec\" or \"1min\"."
|
37
|
+
end
|
38
|
+
|
39
|
+
if (start_time || end_time) && !(start_time && end_time)
|
40
|
+
raise FitbitAPI::InvalidArgumentError, 'Both start_time and end_time are required if time is being specified.'
|
41
|
+
end
|
42
|
+
|
43
|
+
if (start_time && end_time)
|
44
|
+
get("user/-/activities/heart/date/#{format_date(date)}/1d/#{detail_level}/time/#{format_time(start_time)}/#{format_time(end_time)}.json")
|
45
|
+
else
|
46
|
+
get("user/-/activities/heart/date/#{format_date(date)}/1d/#{detail_level}.json")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Configuration
|
2
|
+
def configure
|
3
|
+
yield self
|
4
|
+
end
|
5
|
+
|
6
|
+
def define_setting(name, default = nil)
|
7
|
+
class_variable_set("@@#{name}", default)
|
8
|
+
|
9
|
+
define_class_method "#{name}=" do |value|
|
10
|
+
class_variable_set("@@#{name}", value)
|
11
|
+
end
|
12
|
+
|
13
|
+
define_class_method name do
|
14
|
+
class_variable_get("@@#{name}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def define_class_method(name, &block)
|
21
|
+
(class << self; self; end).instance_eval do
|
22
|
+
define_method name, &block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module FitbitAPI
|
2
|
+
class Client
|
3
|
+
|
4
|
+
PERIODS = %w(1d 7d 30d 1w 1m 3m 6m 1y max)
|
5
|
+
|
6
|
+
def format_date(date)
|
7
|
+
if [Date, Time, DateTime].include?(date.class)
|
8
|
+
date.strftime("%Y-%m-%d")
|
9
|
+
elsif date.is_a? String
|
10
|
+
if date =~ /\d{4}\-\d{2}\-\d{2}/
|
11
|
+
date
|
12
|
+
else
|
13
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid argument [\"#{date}\"] - string must follow yyyy-MM-dd format."
|
14
|
+
end
|
15
|
+
else
|
16
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid type [#{date.class}] - provide a Date/Time/DateTime or a String(yyyy-MM-dd format)."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def format_time(time)
|
21
|
+
if [Time, DateTime].include?(time.class)
|
22
|
+
time.strftime('%H:%M')
|
23
|
+
elsif time.is_a? String
|
24
|
+
if time =~ /\d{2}\:\d{2}/
|
25
|
+
time
|
26
|
+
else
|
27
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid argument [\"#{time}\"] - string must follow HH:mm format."
|
28
|
+
end
|
29
|
+
else
|
30
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid type [#{time.class}] - provide a Time/DateTime or a String(HH:mm format)."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def format_scope(scope)
|
35
|
+
scope.is_a?(Array) ? scope.join(' ') : scope
|
36
|
+
end
|
37
|
+
|
38
|
+
def deep_keys_to_snake_case!(object)
|
39
|
+
deep_transform_keys!(object) { |key| to_snake_case(key, replace_dashes: true) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def deep_keys_to_camel_case!(object)
|
43
|
+
deep_transform_keys!(object) { |key| to_camel_case(key, lower: true) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def deep_symbolize_keys!(object)
|
47
|
+
deep_transform_keys!(object) { |key| key.to_sym rescue key }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Inspired by ActiveSupport's implementation
|
51
|
+
def deep_transform_keys!(object, &block)
|
52
|
+
case object
|
53
|
+
when Hash
|
54
|
+
object.keys.each do |key|
|
55
|
+
value = object.delete(key)
|
56
|
+
object[yield(key)] = deep_transform_keys!(value) { |key| yield(key) }
|
57
|
+
end
|
58
|
+
object
|
59
|
+
when Array
|
60
|
+
object.map! { |e| deep_transform_keys!(e) { |key| yield(key) } }
|
61
|
+
else
|
62
|
+
object
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_snake_case(word, opts={})
|
67
|
+
string = word.to_s.dup
|
68
|
+
return string.downcase if string.match(/\A[A-Z]+\z/)
|
69
|
+
string.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
70
|
+
string.gsub!(/([a-z])([A-Z])/, '\1_\2')
|
71
|
+
string.gsub!('-', '_') if opts[:replace_dashes]
|
72
|
+
string.downcase
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_camel_case(word, opts={})
|
76
|
+
string = word.to_s
|
77
|
+
return string if string.match(/[A-Z]|[a-z]([A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*/)
|
78
|
+
string = word.to_s.split('_').collect(&:capitalize).join
|
79
|
+
string.gsub!(/^\w{1}/) { |word| word.downcase } if opts[:lower]
|
80
|
+
return string
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module FitbitAPI
|
2
|
+
class Client
|
3
|
+
SLEEP_RESOURCES = %w(startTime timeInBed minutesAsleep awakeningsCount
|
4
|
+
minutesAwake minutesToFallAsleep minutesAfterWakeup efficiency)
|
5
|
+
|
6
|
+
def sleep_logs(date=Date.today, opts={})
|
7
|
+
get("user/#{user_id}/sleep/date/#{format_date(date)}.json", opts)
|
8
|
+
end
|
9
|
+
|
10
|
+
def sleep_time_series(resource, opts={})
|
11
|
+
start_date = opts[:start_date]
|
12
|
+
end_date = opts[:end_date] || Date.today
|
13
|
+
period = opts[:period]
|
14
|
+
|
15
|
+
unless SLEEP_RESOURCES.include?(resource)
|
16
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid resource: \"#{resource}\". Please provide one of the following: #{SLEEP_RESOURCES}."
|
17
|
+
end
|
18
|
+
|
19
|
+
if [period, start_date].none?
|
20
|
+
raise FitbitAPI::InvalidArgumentError, 'A start_date or period is required.'
|
21
|
+
end
|
22
|
+
|
23
|
+
if period && !PERIODS.include?(period)
|
24
|
+
raise FitbitAPI::InvalidArgumentError, "Invalid period: \"#{period}\". Please provide one of the following: #{PERIODS}."
|
25
|
+
end
|
26
|
+
|
27
|
+
if period
|
28
|
+
result = get("user/#{user_id}/activities/#{resource}/date/#{format_date(end_date)}/#{period}.json", opts)
|
29
|
+
else
|
30
|
+
result = get("user/#{user_id}/activities/#{resource}/date/#{format_date(start_date)}/#{format_date(end_date)}.json", opts)
|
31
|
+
end
|
32
|
+
# remove root key from response
|
33
|
+
result.values[0]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module FitbitAPI
|
2
|
+
class Client
|
3
|
+
def profile(opts={})
|
4
|
+
get("user/#{user_id}/profile.json", opts)
|
5
|
+
end
|
6
|
+
|
7
|
+
def badges(opts={})
|
8
|
+
get("user/#{user_id}/badges.json", opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def update_profile(opts)
|
12
|
+
post("user/#{user_id}/profile.json", opts)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fitbit_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Zoran
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: oauth2
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- ".gitignore"
|
76
|
+
- ".rspec"
|
77
|
+
- ".travis.yml"
|
78
|
+
- CHANGELOG.md
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- fitbit_api.gemspec
|
84
|
+
- lib/fitbit_api.rb
|
85
|
+
- lib/fitbit_api/activities.rb
|
86
|
+
- lib/fitbit_api/alarms.rb
|
87
|
+
- lib/fitbit_api/base.rb
|
88
|
+
- lib/fitbit_api/body.rb
|
89
|
+
- lib/fitbit_api/client.rb
|
90
|
+
- lib/fitbit_api/devices.rb
|
91
|
+
- lib/fitbit_api/food.rb
|
92
|
+
- lib/fitbit_api/friends.rb
|
93
|
+
- lib/fitbit_api/goals.rb
|
94
|
+
- lib/fitbit_api/heart_rate.rb
|
95
|
+
- lib/fitbit_api/helpers/configuration.rb
|
96
|
+
- lib/fitbit_api/helpers/exceptions.rb
|
97
|
+
- lib/fitbit_api/helpers/utils.rb
|
98
|
+
- lib/fitbit_api/sleep.rb
|
99
|
+
- lib/fitbit_api/user.rb
|
100
|
+
- lib/fitbit_api/version.rb
|
101
|
+
- lib/fitbit_api/water.rb
|
102
|
+
homepage: https://github.com/zokioki/fitbit_api
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
metadata: {}
|
106
|
+
post_install_message: "\n The fitbit_api gem has been renamed to fitbit_api and
|
107
|
+
will no longer be supported.\n Please switch to using fitbit_api for all versions
|
108
|
+
greater than 0.7.1.\n "
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 1.9.3
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 2.6.13
|
125
|
+
signing_key:
|
126
|
+
specification_version: 4
|
127
|
+
summary: A Ruby interface to the Fitbit API, using OAuth2 (renamed to fitbit_api)
|
128
|
+
test_files: []
|