ruby-fitbit 0.2.3 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +4 -3
- data/VERSION +1 -1
- data/bin/ruby-fitbit +15 -8
- data/fixtures/vcr_cassettes/fitbit_get_data.yml +5255 -0
- data/lib/ruby-fitbit.rb +102 -18
- data/ruby-fitbit.gemspec +3 -2
- data/test/test_ruby-fitbit.rb +32 -13
- metadata +4 -3
data/lib/ruby-fitbit.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'mechanize'
|
3
3
|
require 'nokogiri'
|
4
|
+
require 'json'
|
4
5
|
|
5
6
|
class RubyFitbit
|
6
7
|
|
7
|
-
attr_reader :calories, :steps, :miles_walked, :sedentary_active, :lightly_active, :fairly_active, :very_active
|
8
|
-
|
9
8
|
#TODO change tests so reader isn't needed
|
10
9
|
attr_reader :logged_in
|
11
10
|
|
@@ -15,10 +14,8 @@ class RubyFitbit
|
|
15
14
|
@agent = WWW::Mechanize.new #{|a| a.log = Logger.new(STDERR) } #turn on if debugging
|
16
15
|
@logged_in = false
|
17
16
|
@cached_data = {}
|
18
|
-
get_data
|
19
17
|
end
|
20
18
|
|
21
|
-
#todo only login once
|
22
19
|
def login
|
23
20
|
unless @logged_in
|
24
21
|
page = @agent.get 'https://www.fitbit.com/login'
|
@@ -37,6 +34,93 @@ class RubyFitbit
|
|
37
34
|
end
|
38
35
|
end
|
39
36
|
|
37
|
+
def submit_food_log(options = {})
|
38
|
+
login
|
39
|
+
|
40
|
+
date = options.fetch(:date) {Time.now}
|
41
|
+
date = get_fitbit_date_format(date)
|
42
|
+
meal_type = options.fetch(:meal_type){'7'}
|
43
|
+
food_id = options[:food_id]
|
44
|
+
food = options[:food]
|
45
|
+
raise "food_id or food required to submit food log" unless food_id || food
|
46
|
+
unless food_id
|
47
|
+
food_recommendation = get_food_items(food)
|
48
|
+
if food_recommendation.length > 0
|
49
|
+
food_recommendation = food_recommendation.first
|
50
|
+
food = food_recommendation['name']
|
51
|
+
food_id = food_recommendation['id']
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
unit_id = options[:unit_id]
|
56
|
+
unit = options[:unit]
|
57
|
+
raise "unit_id or unit required to submit food log" unless unit_id || unit
|
58
|
+
unless unit_id
|
59
|
+
unit_id = get_unit_id_for_unit(unit)
|
60
|
+
end
|
61
|
+
|
62
|
+
page = @agent.get 'http://www.fitbit.com/foods/log'
|
63
|
+
|
64
|
+
form = page.forms[1]
|
65
|
+
|
66
|
+
form.action="/foods/log/foodLog?apiFormat=htmljson&log=on&date=#{date}"
|
67
|
+
form.foodId = food_id
|
68
|
+
form.foodselectinput = food
|
69
|
+
form.unitId = unit_id
|
70
|
+
form.quantityselectinput = unit
|
71
|
+
form.quantityConsumed = unit
|
72
|
+
form.mealTypeId = meal_type
|
73
|
+
|
74
|
+
result = @agent.submit(form, form.buttons.first)
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_unit_id_for_unit(unit)
|
78
|
+
unit_id = nil
|
79
|
+
unit_type = unit.match(/\d+ (.*)/)[1]
|
80
|
+
|
81
|
+
type_map = {'oz' => '226',
|
82
|
+
'lb' => '180',
|
83
|
+
'gram' => '147',
|
84
|
+
'kilogram' => '389',
|
85
|
+
'roll' => '290',
|
86
|
+
'serving' => '304',
|
87
|
+
'link' => '188',
|
88
|
+
'piece' => '251',
|
89
|
+
'fl oz' => '128',
|
90
|
+
'ml' => '209',
|
91
|
+
'tsp' => '364',
|
92
|
+
'tbsp' => '349',
|
93
|
+
'cup' => '91',
|
94
|
+
'pint' => '256',
|
95
|
+
'slice' => '311',
|
96
|
+
'liter' => '189',
|
97
|
+
'quart' => '279',
|
98
|
+
'entree' => '117',
|
99
|
+
'portion' => '270'
|
100
|
+
}
|
101
|
+
|
102
|
+
unit_id = type_map[unit_type]
|
103
|
+
unit_id
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_food_items(food="Coffe")
|
107
|
+
login
|
108
|
+
result = @agent.get "http://www.fitbit.com/solr/food/select?q=#{food}&wt=foodjson&qt=food"
|
109
|
+
foods = JSON.parse(result.body).first[1]["foods"]
|
110
|
+
foods
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_eaten_calories(date = Time.now)
|
114
|
+
login
|
115
|
+
|
116
|
+
date = get_fitbit_date_format(date).gsub('-','/')
|
117
|
+
page = @agent.get "https://www.fitbit.com/foods/log/#{date}"
|
118
|
+
calories_data = page.search("//div[@id='dailyTotals']").first
|
119
|
+
calories_xml = calories_data.to_xml
|
120
|
+
calories_text = calories_data.text
|
121
|
+
{:calories_xml => calories_xml, :calories_text => calories_text}
|
122
|
+
end
|
123
|
+
|
40
124
|
def get_data(date = Time.now)
|
41
125
|
login
|
42
126
|
|
@@ -45,21 +129,21 @@ class RubyFitbit
|
|
45
129
|
|
46
130
|
page = @agent.get "https://www.fitbit.com/#{date}"
|
47
131
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
132
|
+
calories = page.search("//div[@class='data']").search("span").children[0].text
|
133
|
+
steps = page.search("//div[@class='data']").search("span").children[2].text.strip
|
134
|
+
miles_walked = page.search("//div[@class='data']").search("span").children[3].text.strip
|
135
|
+
sedentary_active = page.search("//div[@class='sedentary caption']/div[@class='number']").text.strip
|
136
|
+
lightly_active = page.search("//div[@class='lightly caption']/div[@class='number']").text.strip
|
137
|
+
fairly_active = page.search("//div[@class='caption fairly']/div[@class='number']").text.strip
|
138
|
+
very_active = page.search("//div[@class='caption very']/div[@class='number']").text.strip
|
55
139
|
data = {}
|
56
|
-
data['calories'] =
|
57
|
-
data['steps'] =
|
58
|
-
data['miles_walked'] =
|
59
|
-
data['sedentary_active'] =
|
60
|
-
data['lightly_active'] =
|
61
|
-
data['fairly_active'] =
|
62
|
-
data['very_active'] =
|
140
|
+
data['calories'] = calories.to_i
|
141
|
+
data['steps'] = steps.to_i
|
142
|
+
data['miles_walked'] = miles_walked.to_f
|
143
|
+
data['sedentary_active'] = sedentary_active
|
144
|
+
data['lightly_active'] = lightly_active
|
145
|
+
data['fairly_active'] = fairly_active
|
146
|
+
data['very_active'] = very_active
|
63
147
|
@cached_data[date] = data
|
64
148
|
data
|
65
149
|
end
|
data/ruby-fitbit.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ruby-fitbit}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Dan Mayer"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-10-14}
|
13
13
|
s.default_executable = %q{ruby-fitbit}
|
14
14
|
s.description = %q{This is a Ruby API for Fitbit.com, currently scraping, but soon will use the officail API (when released)}
|
15
15
|
s.email = %q{Danmayer@gmail.com}
|
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
|
|
26
26
|
"Rakefile",
|
27
27
|
"VERSION",
|
28
28
|
"bin/ruby-fitbit",
|
29
|
+
"fixtures/vcr_cassettes/fitbit_get_data.yml",
|
29
30
|
"lib/ruby-fitbit.rb",
|
30
31
|
"ruby-fitbit.gemspec",
|
31
32
|
"test/helper.rb",
|
data/test/test_ruby-fitbit.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
require File.expand_path('./helper', File.dirname(__FILE__))
|
2
2
|
require 'webmock/test_unit'
|
3
|
-
|
3
|
+
require 'vcr'
|
4
4
|
require 'ruby-fitbit'
|
5
5
|
require 'mocha'
|
6
6
|
|
7
7
|
class TestRubyFitbit < Test::Unit::TestCase
|
8
8
|
include WebMock
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
VCR.config do |c|
|
11
|
+
c.cassette_library_dir = 'fixtures/vcr_cassettes'
|
12
|
+
c.http_stubbing_library = :webmock
|
13
|
+
end
|
14
14
|
|
15
15
|
#TODO this is a mess it took awhile to figure out a way to make the tests work at all clean this up
|
16
16
|
|
@@ -62,7 +62,9 @@ END
|
|
62
62
|
data = File.read("./test/responses/data.txt")
|
63
63
|
response = "#{HEADERS} #{data}"
|
64
64
|
|
65
|
-
|
65
|
+
date = Time.now()
|
66
|
+
date = date.strftime("%Y/%m/%d")
|
67
|
+
stub_request(:get, "https://www.fitbit.com/#{date}").to_return(response)
|
66
68
|
|
67
69
|
RubyFitbit.any_instance.stubs(:login)
|
68
70
|
fitbit = RubyFitbit.new("fake@fake.com","pass")
|
@@ -70,15 +72,32 @@ END
|
|
70
72
|
@logged_in = val
|
71
73
|
end
|
72
74
|
fitbit.set_logged_in(true)
|
75
|
+
data = fitbit.get_data
|
73
76
|
|
74
|
-
assert_equal
|
75
|
-
assert_equal
|
76
|
-
assert_equal
|
77
|
-
assert_equal "16hrs 58min",
|
78
|
-
assert_equal "1hr 42min",
|
79
|
-
assert_equal "1hr 51min",
|
80
|
-
assert_equal "17min",
|
77
|
+
assert_equal 1928, data['calories'], "wrong calories"
|
78
|
+
assert_equal 7821, data['steps'], "wrong steps"
|
79
|
+
assert_equal 3.38, data['miles_walked'], "wrong miles"
|
80
|
+
assert_equal "16hrs 58min", data['sedentary_active'], "wrong sedentary"
|
81
|
+
assert_equal "1hr 42min", data['lightly_active'], "wrong lightly"
|
82
|
+
assert_equal "1hr 51min", data['fairly_active'], "wrong fairly"
|
83
|
+
assert_equal "17min", data['very_active'], "wrong very"
|
84
|
+
end
|
85
|
+
|
86
|
+
should "use VCR successfully" do
|
87
|
+
fitbit = nil
|
88
|
+
data = nil
|
89
|
+
VCR.use_cassette('fitbit_get_data', :record => :new_episodes) do
|
90
|
+
fitbit = RubyFitbit.new("fake@fake.com","fake")
|
91
|
+
data = fitbit.get_data(Time.parse("2010/08/03"))
|
92
|
+
end
|
81
93
|
|
94
|
+
assert_equal 2084, data['calories'], "wrong calories"
|
95
|
+
assert_equal 10018, data['steps'], "wrong steps"
|
96
|
+
assert_equal 4.61, data['miles_walked'], "wrong miles"
|
97
|
+
assert_equal "18hrs 26min", data['sedentary_active'], "wrong sedentary"
|
98
|
+
assert_equal "1hr 17min", data['lightly_active'], "wrong lightly"
|
99
|
+
assert_equal "1hr 29min", data['fairly_active'], "wrong fairly"
|
100
|
+
assert_equal "42min", data['very_active'], "wrong very"
|
82
101
|
end
|
83
102
|
|
84
103
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
- 2
|
8
7
|
- 3
|
9
|
-
|
8
|
+
- 1
|
9
|
+
version: 0.3.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Dan Mayer
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-10-14 00:00:00 -04:00
|
18
18
|
default_executable: ruby-fitbit
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -88,6 +88,7 @@ files:
|
|
88
88
|
- Rakefile
|
89
89
|
- VERSION
|
90
90
|
- bin/ruby-fitbit
|
91
|
+
- fixtures/vcr_cassettes/fitbit_get_data.yml
|
91
92
|
- lib/ruby-fitbit.rb
|
92
93
|
- ruby-fitbit.gemspec
|
93
94
|
- test/helper.rb
|