fitbit_api 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ - 2.1.6
5
+ - 2.0.0
6
+ - 1.9.3
7
+ before_install: gem install bundler -v 1.10.3
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fitbit_api.gemspec
4
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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,6 @@
1
+ require 'oauth2'
2
+ require 'date'
3
+ require 'fitbit_api/version'
4
+ require 'fitbit_api/client'
5
+
6
+ module FitbitAPI; end
@@ -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,7 @@
1
+ module FitbitAPI
2
+ class Client
3
+ def devices(opts={})
4
+ get("user/#{user_id}/devices.json", opts)
5
+ end
6
+ end
7
+ 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,11 @@
1
+ module FitbitAPI
2
+ class Client
3
+ def friends(opts={})
4
+ get("user/#{user_id}/friends.json", opts)
5
+ end
6
+
7
+ def friends_leaderboard(opts={})
8
+ get("user/#{user_id}/friends/leaderboard.json", opts)
9
+ end
10
+ end
11
+ 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,4 @@
1
+ module FitbitAPI
2
+ class InvalidArgumentError < ArgumentError
3
+ end
4
+ 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
@@ -0,0 +1,4 @@
1
+ module FitbitAPI
2
+ VERSION = '0.8.0'
3
+ REPO_URL = 'https://github.com/zokioki/fitbit_api'
4
+ end
@@ -0,0 +1,7 @@
1
+ module FitbitAPI
2
+ class Client
3
+ def water_logs(date=Date.today, opts={})
4
+ get("user/#{user_id}/foods/log/water/date/#{format_date(date)}.json", opts)
5
+ end
6
+ end
7
+ 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: []