class FitbitAPI::Client
Constants
- ACTIVITY_INTRADAY_RESOURCES
- ACTIVITY_RESOURCES
- BODY_RESOURCES
- FOOD_RESOURCES
- PERIODS
- SLEEP_RESOURCES
Attributes
Public Class Methods
# File lib/fitbit_api/client.rb, line 20 def initialize(opts={}) validate_args(opts) assign_attrs(opts) set_client establish_token(opts) end
Public Instance Methods
Returns the details of a specific activity in the Fitbit activities database in the format requested.
If activity has levels, also returns a list of activity level details.
# File lib/fitbit_api/activities.rb, line 73 def activity(activity_id) get("activities/#{activity_id}.json") end
# File lib/fitbit_api/activities.rb, line 112 def activity_intraday_time_series(resource, opts={}) date = opts[:date] || Date.today detail_level = opts[:detail_level] start_time = opts[:start_time] end_time = opts[:end_time] unless ACTIVITY_INTRADAY_RESOURCES.include?(resource) raise FitbitAPI::InvalidArgumentError, "Invalid resource: \"#{resource}\". Please provide one of the following: #{ACTIVITY_RESOURCES}." end if [date, detail_level].any?(&:nil?) raise FitbitAPI::InvalidArgumentError, 'A date and detail_level are required.' end unless %(1min 15min).include? detail_level raise FitbitAPI::InvalidArgumentError, "Invalid detail_level: \"#{detail_level}\". Please provide one of the following: \"1min\" or \"15min\"." end if (start_time || end_time) && !(start_time && end_time) raise FitbitAPI::InvalidArgumentError, 'Both start_time and end_time are required if time is being specified.' end if (start_time && end_time) get("user/-/activities/#{resource}/date/#{format_date(date)}/1d/#{detail_level}/time/#{format_time(start_time)}/#{format_time(end_time)}.json") else get("user/-/activities/#{resource}/date/#{format_date(date)}/1d/#{detail_level}.json") end end
URL Parameters¶ ↑
-
:beforeDate
- the date; formatted in yyyy-MM-ddTHH:mm:ss -
:afterDate
- the date; formatted in yyyy-MM-ddTHH:mm:ss -
:sort
- the sort order of entries by date (asc or desc) -
:offset
- the offset number of entries. Must always be 0 -
:limit
- the max of the number of entries returned (max: 20)
# File lib/fitbit_api/activities.rb, line 58 def activity_logs_list(opts={}) opts[:params] = {} param_defaults = { before_date: Date.today, after_date: nil, sort: 'desc', limit: 20, offset: 0 } # move param values from top-level opts into :params sub-hash param_defaults.each do |key, default_val| opts[:params][key] = opts.delete(key) || default_val end get("user/#{user_id}/activities/list.json", opts) end
# File lib/fitbit_api/activities.rb, line 86 def activity_time_series(resource, opts={}) start_date = opts[:start_date] end_date = opts[:end_date] || Date.today period = opts[:period] unless ACTIVITY_RESOURCES.include?(resource) raise FitbitAPI::InvalidArgumentError, "Invalid resource: \"#{resource}\". Please provide one of the following: #{ACTIVITY_RESOURCES}." end if [start_date, period].none? raise FitbitAPI::InvalidArgumentError, 'A start_date or period is required.' end if period && !PERIODS.include?(period) raise FitbitAPI::InvalidArgumentError, "Invalid period: \"#{period}\". Please provide one of the following: #{PERIODS}." end if period result = get("user/#{user_id}/activities/#{resource}/date/#{format_date(end_date)}/#{period}.json", opts) else result = get("user/#{user_id}/activities/#{resource}/date/#{format_date(start_date)}/#{format_date(end_date)}.json", opts) end # remove root key from response result.values[0] end
POST Parameters¶ ↑
-
:time
- time of day that the alarm vibrates with a UTC timezone offset, e.g. 07:15-08:00 -
:enabled
- boolean; if false, alarm does not vibrate until enabled is set to true -
:recurring
- boolean; if false, the alarm is a single event -
:weekDays
- comma separated list of days of the week on which the alarm vibrates (MONDAY,TUESDAY)
# File lib/fitbit_api/alarms.rb, line 23 def add_alarm(tracker_id, opts={}) post("user/#{user_id}/devices/tracker/#{tracker_id}/alarms.json", opts) end
Adds the activity with the given ID to user's list of favorite activities.
# File lib/fitbit_api/activities.rb, line 163 def add_favorite_activity(activity_id) post("user/#{user_id}/activities/favorite/#{activity_id}.json") end
Returns a list of the set alarms connected to a user's account.
# File lib/fitbit_api/alarms.rb, line 8 def alarms(tracker_id, opts={}) get("user/#{user_id}/devices/tracker/#{tracker_id}/alarms.json", opts) end
Gets a tree of all valid Fitbit public activities from
the activities catalog as well as private custom activities the user created.
# File lib/fitbit_api/activities.rb, line 44 def all_activities(opts={}) get('activities.json', opts) end
# File lib/fitbit_api/client.rb, line 51 def auth_header { 'Authorization' => ('Basic ' + Base64.encode64(@client_id + ':' + @client_secret)) } end
# File lib/fitbit_api/client.rb, line 27 def auth_url @client.auth_code.authorize_url(redirect_uri: @redirect_uri, scope: @scope) end
# File lib/fitbit_api/user.rb, line 7 def badges(opts={}) get("user/#{user_id}/badges.json", opts) end
Retrieves a user's current body fat percentage goal.
# File lib/fitbit_api/goals.rb, line 14 def body_fat_goal(opts={}) get("user/-/body/log/fat/goal.json", opts) end
# File lib/fitbit_api/body.rb, line 9 def body_fat_logs(date=Date.today, opts={}) get("user/-/body/log/fat/date/#{format_date(date)}.json", opts) end
# File lib/fitbit_api/body.rb, line 13 def body_time_series(resource, opts={}) start_date = opts[:start_date] end_date = opts[:end_date] || Date.today period = opts[:period] unless BODY_RESOURCES.include?(resource) raise FitbitAPI::InvalidArgumentError, "Invalid resource: \"#{resource}\". Please provide one of the following: #{BODY_RESOURCES}." end if [period, start_date].none? raise FitbitAPI::InvalidArgumentError, 'A start_date or period is required.' end if period && !PERIODS.include?(period) raise FitbitAPI::InvalidArgumentError, "Invalid period: \"#{period}\". Please provide one of the following: #{PERIODS}." end if period result = get("user/#{user_id}/body/#{resource}/date/#{format_date(end_date)}/#{period}.json", opts) else result = get("user/#{user_id}/body/#{resource}/date/#{format_date(start_date)}/#{format_date(end_date)}.json", opts) end # remove root key from response result.values[0] end
POST Parameters¶ ↑
-
:caloriesOut
- calories output goal value; integer -
:activeMinutes
- active minutes goal value; integer -
:floors
- floor goal value; integer -
:distance
- distance goal value; X.XX or integer -
:steps
- steps goal value; integer
# File lib/fitbit_api/goals.rb, line 43 def create_or_update_daily_goals(opts={}) post("user/#{user_id}/activities/goals/daily.json", opts) end
POST Parameters¶ ↑
-
:caloriesOut
- calories output goal value; integer -
:activeMinutes
- active minutes goal value; integer -
:floors
- floor goal value; integer -
:distance
- distance goal value; X.XX or integer -
:steps
- steps goal value; integer
# File lib/fitbit_api/goals.rb, line 57 def create_or_update_weekly_goals(opts={}) post("user/#{user_id}/activities/goals/weekly.json", opts) end
Retrieves a summary and list of a user's activities and activity log entries for a given day
in the format requested using units in the unit system which corresponds to the Accept-Language header provided.
# File lib/fitbit_api/activities.rb, line 20 def daily_activity_summary(date=Date.today, opts={}) get("user/#{user_id}/activities/date/#{format_date(date)}.json", opts) end
Retrieves a user's current daily activity goals.
# File lib/fitbit_api/goals.rb, line 20 def daily_goals(opts={}) get("user/#{user_id}/activities/goals/daily.json", opts) end
# File lib/fitbit_api/helpers/utils.rb, line 42 def deep_keys_to_camel_case!(object) deep_transform_keys!(object) { |key| to_camel_case(key, lower: true) } end
# File lib/fitbit_api/helpers/utils.rb, line 38 def deep_keys_to_snake_case!(object) deep_transform_keys!(object) { |key| to_snake_case(key, replace_dashes: true) } end
# File lib/fitbit_api/helpers/utils.rb, line 46 def deep_symbolize_keys!(object) deep_transform_keys!(object) { |key| key.to_sym rescue key } end
Inspired by ActiveSupport's implementation
# File lib/fitbit_api/helpers/utils.rb, line 51 def deep_transform_keys!(object, &block) case object when Hash object.keys.each do |key| value = object.delete(key) object[yield(key)] = deep_transform_keys!(value) { |key| yield(key) } end object when Array object.map! { |e| deep_transform_keys!(e) { |key| yield(key) } } else object end end
# File lib/fitbit_api/client.rb, line 76 def delete(path, opts={}) response = token.delete(("#{@api_version}/" + path), headers: request_headers).response object = MultiJson.load(response.body) unless response.status == 204 process_keys!(object, opts) end
Deletes a user's activity log entry with the given ID.
# File lib/fitbit_api/activities.rb, line 172 def delete_activity(activity_log_id) delete("user/#{user_id}/activities/#{activity_log_id}.json") end
Deletes the user's device alarm entry with the given ID for a given device.
# File lib/fitbit_api/alarms.rb, line 48 def delete_alarm(tracker_id, alarm_id, opts={}) delete("user/#{user_id}/devices/tracker/#{tracker_id}/alarms/#{alarm_id}.json", opts) end
# File lib/fitbit_api/body.rb, line 51 def delete_body_fat_log(body_fat_log_id, opts={}) delete("user/#{user_id}/body/log/fat/#{body_fat_log_id}.json", opts) end
Removes the activity with the given ID from a user's list of favorite activities.
# File lib/fitbit_api/activities.rb, line 178 def delete_favorite_activity(activity_id) delete("user/#{user_id}/activities/favorite/#{activity_id}.json") end
# File lib/fitbit_api/body.rb, line 43 def delete_weight_log(weight_log_id, opts={}) delete("user/#{user_id}/body/log/weight/#{weight_log_id}.json", opts) end
# File lib/fitbit_api/devices.rb, line 3 def devices(opts={}) get("user/#{user_id}/devices.json", opts) end
Returns a list of a user's favorite activities.
# File lib/fitbit_api/activities.rb, line 37 def favorite_activities(opts={}) get("user/#{user_id}/activities/favorite.json", opts) end
# File lib/fitbit_api/food.rb, line 17 def favorite_foods(opts={}) get("user/#{user_id}/foods/log/favorite.json", opts) end
# File lib/fitbit_api/food.rb, line 21 def food_goals(opts={}) get("user/#{user_id}/foods/log/goal.json", opts) end
# File lib/fitbit_api/food.rb, line 5 def food_logs(date=Date.today, opts={}) get("user/#{user_id}/foods/log/date/#{format_date(date)}.json", opts) end
# File lib/fitbit_api/food.rb, line 25 def food_time_series(resource, opts={}) start_date = opts[:start_date] end_date = opts[:end_date] || Date.today period = opts[:period] unless FOOD_RESOURCES.include?(resource) raise FitbitAPI::InvalidArgumentError, "Invalid resource: \"#{resource}\". Please provide one of the following: #{FOOD_RESOURCES}." end if [period, start_date].none? raise FitbitAPI::InvalidArgumentError, 'A start_date or period is required.' end if period && !PERIODS.include?(period) raise FitbitAPI::InvalidArgumentError, "Invalid period: \"#{period}\". Please provide one of the following: #{PERIODS}." end if period result = get("user/#{user_id}/foods/log/#{resource}/date/#{format_date(end_date)}/#{period}.json", opts) else result = get("user/#{user_id}/foods/log/#{resource}/date/#{format_date(start_date)}/#{format_date(end_date)}.json", opts) end # remove root key from response result.values[0] end
# File lib/fitbit_api/helpers/utils.rb, line 6 def format_date(date) if [Date, Time, DateTime].include?(date.class) date.strftime('%Y-%m-%d') elsif date.is_a? String if date =~ /\d{4}\-\d{2}\-\d{2}/ date else raise FitbitAPI::InvalidArgumentError, "Invalid argument [\"#{date}\"] - string must follow yyyy-MM-dd format." end else raise FitbitAPI::InvalidArgumentError, "Invalid type [#{date.class}] - provide a Date/Time/DateTime or a String(yyyy-MM-dd format)." end end
# File lib/fitbit_api/helpers/utils.rb, line 34 def format_scope(scope) scope.is_a?(Array) ? scope.join(' ') : scope end
# File lib/fitbit_api/helpers/utils.rb, line 20 def format_time(time) if [Time, DateTime].include?(time.class) time.strftime('%H:%M') elsif time.is_a? String if time =~ /\d{2}\:\d{2}/ time else raise FitbitAPI::InvalidArgumentError, "Invalid argument [\"#{time}\"] - string must follow HH:mm format." end else raise FitbitAPI::InvalidArgumentError, "Invalid type [#{time.class}] - provide a Time/DateTime or a String(HH:mm format)." end end
Retrieves a list of a user's frequent activities in the format requested using units
in the unit system which corresponds to the Accept-Language header provided.
# File lib/fitbit_api/activities.rb, line 27 def frequent_activities(opts={}) get("user/#{user_id}/activities/frequent.json", opts) end
# File lib/fitbit_api/food.rb, line 13 def frequent_foods(opts={}) get("user/#{user_id}/foods/log/frequent.json", opts) end
# File lib/fitbit_api/friends.rb, line 3 def friends(opts={}) get("user/#{user_id}/friends.json", opts) end
# File lib/fitbit_api/friends.rb, line 7 def friends_leaderboard(opts={}) get("user/#{user_id}/friends/leaderboard.json", opts) end
# File lib/fitbit_api/client.rb, line 63 def get(path, opts={}) params = opts.delete(:params) || {} response = token.get(("#{@api_version}/" + path), params: deep_keys_to_camel_case!(params), headers: request_headers).response object = MultiJson.load(response.body) unless response.status == 204 process_keys!(object, opts) end
# File lib/fitbit_api/client.rb, line 31 def get_token(auth_code) @token = @client.auth_code.get_token( auth_code, redirect_uri: @redirect_uri, headers: auth_header ) @user_id = @token.params['user_id'] @token end
# File lib/fitbit_api/heart_rate.rb, line 25 def heart_rate_intraday_time_series(opts={}) date = opts[:date] || Date.today detail_level = opts[:detail_level] start_time = opts[:start_time] end_time = opts[:end_time] if [date, detail_level].any?(&:nil?) raise FitbitAPI::InvalidArgumentError, 'A date and detail_level are required.' end unless %(1sec 1min).include? detail_level raise FitbitAPI::InvalidArgumentError, "Invalid detail_level: \"#{detail_level}\". Please provide one of the following: \"1sec\" or \"1min\"." end if (start_time || end_time) && !(start_time && end_time) raise FitbitAPI::InvalidArgumentError, 'Both start_time and end_time are required if time is being specified.' end if (start_time && end_time) get("user/-/activities/heart/date/#{format_date(date)}/1d/#{detail_level}/time/#{format_time(start_time)}/#{format_time(end_time)}.json") else get("user/-/activities/heart/date/#{format_date(date)}/1d/#{detail_level}.json") end end
# File lib/fitbit_api/heart_rate.rb, line 3 def heart_rate_time_series(opts={}) start_date = opts[:start_date] end_date = opts[:end_date] || Date.today period = opts[:period] if [period, start_date].none? raise FitbitAPI::InvalidArgumentError, 'A start_date or period is required.' end if period && !PERIODS.include?(period) raise FitbitAPI::InvalidArgumentError, "Invalid period: \"#{period}\". Please provide one of the following: #{PERIODS}." end if period result = get("user/#{user_id}/activities/heart/date/#{format_date(end_date)}/#{period}.json", opts) else result = get("user/#{user_id}/activities/heart/date/#{format_date(start_date)}/#{format_date(end_date)}.json", opts) end # remove root key from response result.values[0] end
Retrieves the user's activity statistics in the format requested using units
in the unit system which corresponds to the Accept-Language header provided. Activity statistics includes Lifetime and Best achievement values from the My Achievements tile on the website dashboard.
# File lib/fitbit_api/activities.rb, line 82 def lifetime_stats(opts={}) get("user/#{user_id}/activities.json", opts) end
POST Parameters¶ ↑
-
:activityId
- activity id -
:activityName
- custom activity name. Either activity ID or activityName must be provided -
:manualCalories
- calories burned, specified manually. Required with activityName, otherwise optional -
:startTime
- activity start time; formatted in HH:mm:ss -
:durationMillis
- duration in milliseconds -
:date
- log entry date; formatted in yyyy-MM-dd -
:distance
- distance; required for logging directory activity; formatted in X.XX -
:distanceUnit
- distance measurement unit
# File lib/fitbit_api/activities.rb, line 157 def log_activity(opts) post("user/#{user_id}/activities.json", opts) end
# File lib/fitbit_api/body.rb, line 47 def log_body_fat(opts) post("user/#{user_id}/body/log/fat.json", opts) end
# File lib/fitbit_api/body.rb, line 39 def log_weight(opts) post("user/#{user_id}/body/log/weight.json", opts) end
# File lib/fitbit_api/client.rb, line 70 def post(path, opts={}) response = token.post(("#{@api_version}/" + path), body: deep_keys_to_camel_case!(opts), headers: request_headers).response object = MultiJson.load(response.body) unless response.status == 204 process_keys!(object, opts) end
# File lib/fitbit_api/client.rb, line 82 def process_keys!(object, opts={}) deep_keys_to_snake_case!(object) if (opts[:snake_case_keys] || snake_case_keys) deep_symbolize_keys!(object) if (opts[:symbolize_keys] || symbolize_keys) return object end
# File lib/fitbit_api/user.rb, line 3 def profile(opts={}) get("user/#{user_id}/profile.json", opts) end
# File lib/fitbit_api/activities.rb, line 31 def recent_activities(opts={}) get("user/#{user_id}/activities/recent.json", opts) end
# File lib/fitbit_api/food.rb, line 9 def recent_foods(opts={}) get("user/#{user_id}/foods/log/recent.json", opts) end
# File lib/fitbit_api/client.rb, line 45 def refresh_token! @token = @token.refresh!(headers: auth_header) @user_id ||= @token.params['user_id'] @token end
# File lib/fitbit_api/client.rb, line 55 def request_headers { 'User-Agent' => "fitbit_api gem (v#{FitbitAPI::VERSION})", 'Accept-Language' => @unit_system, 'Accept-Locale' => @locale } end
# File lib/fitbit_api/sleep.rb, line 6 def sleep_logs(date=Date.today, opts={}) get("user/#{user_id}/sleep/date/#{format_date(date)}.json", opts) end
# File lib/fitbit_api/sleep.rb, line 10 def sleep_time_series(resource, opts={}) start_date = opts[:start_date] end_date = opts[:end_date] || Date.today period = opts[:period] unless SLEEP_RESOURCES.include?(resource) raise FitbitAPI::InvalidArgumentError, "Invalid resource: \"#{resource}\". Please provide one of the following: #{SLEEP_RESOURCES}." end if [period, start_date].none? raise FitbitAPI::InvalidArgumentError, 'A start_date or period is required.' end if period && !PERIODS.include?(period) raise FitbitAPI::InvalidArgumentError, "Invalid period: \"#{period}\". Please provide one of the following: #{PERIODS}." end if period result = get("user/#{user_id}/sleep/#{resource}/date/#{format_date(end_date)}/#{period}.json", opts) else result = get("user/#{user_id}/sleep/#{resource}/date/#{format_date(start_date)}/#{format_date(end_date)}.json", opts) end # remove root key from response result.values[0] end
# File lib/fitbit_api/helpers/utils.rb, line 75 def to_camel_case(word, opts={}) string = word.to_s 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]*/) string = word.to_s.split('_').collect(&:capitalize).join string.gsub!(/^\w{1}/) { |word| word.downcase } if opts[:lower] return string end
# File lib/fitbit_api/helpers/utils.rb, line 66 def to_snake_case(word, opts={}) string = word.to_s.dup return string.downcase if string.match(/\A[A-Z]+\z/) string.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2') string.gsub!(/([a-z])([A-Z])/, '\1_\2') string.gsub!('-', '_') if opts[:replace_dashes] string.downcase end
# File lib/fitbit_api/client.rb, line 41 def token @token.expired? ? refresh_token! : @token end
POST Parameters¶ ↑
-
:time
- time of day that the alarm vibrates with a UTC timezone offset, e.g. 07:15-08:00 -
:enabled
- boolean; if false, alarm does not vibrate until enabled is set to true -
:recurring
- boolean; if false, the alarm is a single event -
:weekDays
- comma separated list of days of the week on which the alarm vibrates (MONDAY,TUESDAY) -
:snoozeLength
- integer; minutes between alarms -
:snoozeCount
- integer; maximum snooze count -
:label
- string; label for alarm -
:vibe
- vibe pattern; only one value for now (DEFAULT)
# File lib/fitbit_api/alarms.rb, line 39 def update_alarm(tracker_id, alarm_id, opts={}) post("user/#{user_id}/devices/tracker/#{tracker_id}/alarms/#{alarm_id}.json", opts) end
# File lib/fitbit_api/user.rb, line 11 def update_profile(opts) post("user/#{user_id}/profile.json", opts) end
# File lib/fitbit_api/water.rb, line 3 def water_logs(date=Date.today, opts={}) get("user/#{user_id}/foods/log/water/date/#{format_date(date)}.json", opts) end
Retrieves a user's current weekly activity goals.
# File lib/fitbit_api/goals.rb, line 26 def weekly_goals(opts={}) get("user/#{user_id}/activities/goals/weekly.json", opts) end
Retrieves a user's current weight goal.
# File lib/fitbit_api/goals.rb, line 8 def weight_goal(opts={}) get("user/-/body/log/weight/goal.json", opts) end
# File lib/fitbit_api/body.rb, line 5 def weight_logs(date=Date.today, opts={}) get("user/-/body/log/weight/date/#{format_date(date)}.json", opts) end
Private Instance Methods
# File lib/fitbit_api/client.rb, line 103 def assign_attrs(opts) attrs = %i[client_id client_secret redirect_uri site_url authorize_url token_url unit_system locale scope api_version snake_case_keys symbolize_keys].freeze attrs.each do |attr| instance_variable_set("@#{attr}", (opts[attr] || FitbitAPI.send(attr))) end @user_id = opts[:user_id] end
# File lib/fitbit_api/client.rb, line 125 def establish_token(opts) return unless opts[:access_token] || opts[:refresh_token] if opts[:access_token] && !opts[:user_id] raise FitbitAPI::InvalidArgumentError, 'user_id is required if using existing access token' end @token = OAuth2::AccessToken.new( @client, opts[:access_token], refresh_token: opts[:refresh_token], expires_at: opts[:expires_at] ) refresh_token! if @token.token.empty? end
# File lib/fitbit_api/client.rb, line 115 def set_client @client = OAuth2::Client.new( @client_id, @client_secret, site: @site_url, authorize_url: @authorize_url, token_url: @token_url ) end
# File lib/fitbit_api/client.rb, line 90 def validate_args(opts) required_args = %i[client_id client_secret].freeze missing_args = [] required_args.each do |arg| missing_args << arg if (opts[arg] || FitbitAPI.send(arg)).nil? end return if missing_args.empty? raise FitbitAPI::InvalidArgumentError, "Required arguments: #{missing_args.join(', ')}" end