howami 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +6 -0
- data/CONTRIBUTING.md +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +36 -0
- data/README.rdoc +19 -0
- data/Rakefile +66 -0
- data/bin/howami +60 -0
- data/features/howami.feature +27 -0
- data/features/step_definitions/howami_steps.rb +7 -0
- data/features/support/env.rb +20 -0
- data/howami.gemspec +31 -0
- data/lib/howami.rb +13 -0
- data/lib/howami/authentication.rb +47 -0
- data/lib/howami/configuration.rb +35 -0
- data/lib/howami/credentials.rb +4 -0
- data/lib/howami/fitbit.rb +63 -0
- data/lib/howami/version.rb +3 -0
- data/spec/authentication_spec.rb +24 -0
- data/spec/cassettes/fitbit.yml +229 -0
- data/spec/configuration_spec.rb +60 -0
- data/spec/fitbit_spec.rb +78 -0
- data/spec/spec_helper.rb +24 -0
- metadata +216 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: c8ecc8d9b5061e6e73462466cc9bd93267ad5edd
|
|
4
|
+
data.tar.gz: 382fd3b19a1adc3c60393e04321f5e964f514306
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ea647e2f91f55fecf8c77e88cfe93ac362f849975d599cf33475ebc1222bcdbe53459a0a430ccd4c908b94d0c1d15e2edaf180514307422120c1149ffc99999f
|
|
7
|
+
data.tar.gz: 7051440a383be7d19f956d99aa09635dcb2f2dc5ffafc4144f4054390370cb69bdcf56023d86deeaa731f8f95e54d6d574793a55623dee93714c94b1266ceaa5
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CONTRIBUTING.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Matthew Rothenberg
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# howami 
|
|
2
|
+
|
|
3
|
+
All unix people know the `whoami` command.
|
|
4
|
+
|
|
5
|
+
$ whoami
|
|
6
|
+
mroth
|
|
7
|
+
|
|
8
|
+
But what if you want to keep track of _how_ you are?
|
|
9
|
+
|
|
10
|
+
$ howami
|
|
11
|
+
Current weight: 138.8 pounds (12.97 % body fat)
|
|
12
|
+
Activity level: 2765 steps thus far today
|
|
13
|
+
Sleep last night: 5 hours, 41 min.
|
|
14
|
+
|
|
15
|
+
`howami` uses the Fitbit API to give you a quick CLI glance at your vital stats as synchronized from your nerdy quantified-self tracking devices.
|
|
16
|
+
|
|
17
|
+
[](https://travis-ci.org/mroth/howami)
|
|
18
|
+
[](https://gemnasium.com/mroth/howami)
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Just install it via rubygems:
|
|
23
|
+
|
|
24
|
+
$ gem install howami
|
|
25
|
+
|
|
26
|
+
Done.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
Do `howami --auth` to complete login via the Fitbit API. At any point after that, you can do `howami` to see your stats.
|
|
31
|
+
|
|
32
|
+
## What devices?
|
|
33
|
+
|
|
34
|
+
Since people keep asking, I personally use a [Fitbit Aria](http://www.fitbit.com/aria) scale to weigh myself, and a [Fitbit Flex](http://www.fitbit.com/flex) for activity/sleep tracking.
|
|
35
|
+
|
|
36
|
+
<sub>(And I've lost 22lbs. at time of writing this!)</sub>
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
= howami - DESCRIBE YOUR GEM
|
|
2
|
+
|
|
3
|
+
Author:: YOUR NAME (YOUR EMAIL)
|
|
4
|
+
Copyright:: Copyright (c) 2013 YOUR NAME
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DESCRIBE YOUR GEM HERE
|
|
8
|
+
|
|
9
|
+
== Links
|
|
10
|
+
|
|
11
|
+
* {Source on Github}[LINK TO GITHUB]
|
|
12
|
+
* RDoc[LINK TO RDOC.INFO]
|
|
13
|
+
|
|
14
|
+
== Install
|
|
15
|
+
|
|
16
|
+
== Examples
|
|
17
|
+
|
|
18
|
+
== Contributing
|
|
19
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
def dump_load_path
|
|
2
|
+
puts $LOAD_PATH.join("\n")
|
|
3
|
+
found = nil
|
|
4
|
+
$LOAD_PATH.each do |path|
|
|
5
|
+
if File.exists?(File.join(path,"rspec"))
|
|
6
|
+
puts "Found rspec in #{path}"
|
|
7
|
+
if File.exists?(File.join(path,"rspec","core"))
|
|
8
|
+
puts "Found core"
|
|
9
|
+
if File.exists?(File.join(path,"rspec","core","rake_task"))
|
|
10
|
+
puts "Found rake_task"
|
|
11
|
+
found = path
|
|
12
|
+
else
|
|
13
|
+
puts "!! no rake_task"
|
|
14
|
+
end
|
|
15
|
+
else
|
|
16
|
+
puts "!!! no core"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
if found.nil?
|
|
21
|
+
puts "Didn't find rspec/core/rake_task anywhere"
|
|
22
|
+
else
|
|
23
|
+
puts "Found in #{path}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
require 'bundler'
|
|
27
|
+
require 'rake/clean'
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
require 'rspec/core/rake_task'
|
|
31
|
+
rescue LoadError
|
|
32
|
+
dump_load_path
|
|
33
|
+
raise
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
require 'cucumber'
|
|
37
|
+
require 'cucumber/rake/task'
|
|
38
|
+
gem 'rdoc' # we need the installed RDoc gem, not the system one
|
|
39
|
+
require 'rdoc/task'
|
|
40
|
+
|
|
41
|
+
include Rake::DSL
|
|
42
|
+
|
|
43
|
+
Bundler::GemHelper.install_tasks
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
RSpec::Core::RakeTask.new do |t|
|
|
47
|
+
# Put spec opts in a file named .rspec in root
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
CUKE_RESULTS = 'results.html'
|
|
52
|
+
CLEAN << CUKE_RESULTS
|
|
53
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
|
54
|
+
t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty --no-source -x"
|
|
55
|
+
t.fork = false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Rake::RDocTask.new do |rd|
|
|
59
|
+
|
|
60
|
+
rd.main = "README.rdoc"
|
|
61
|
+
|
|
62
|
+
rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
task :default => [:spec,:features]
|
|
66
|
+
|
data/bin/howami
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'methadone'
|
|
5
|
+
require 'howami.rb'
|
|
6
|
+
|
|
7
|
+
class App
|
|
8
|
+
include Methadone::Main
|
|
9
|
+
include Methadone::CLILogging
|
|
10
|
+
include Howami
|
|
11
|
+
|
|
12
|
+
main do # Add args you want: |like,so|
|
|
13
|
+
# your program code here
|
|
14
|
+
# You can access CLI options via
|
|
15
|
+
# the options Hash
|
|
16
|
+
|
|
17
|
+
if options[:auth]
|
|
18
|
+
Authentication.new.do_interactive_auth
|
|
19
|
+
else
|
|
20
|
+
unless Configuration.has_valid_credentials?
|
|
21
|
+
fatal "You need to authenticate first, please run `howami --auth`"
|
|
22
|
+
exit 1
|
|
23
|
+
end
|
|
24
|
+
fit_obj = Fitbit.new
|
|
25
|
+
Howami::cli_run!(fit_obj)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# supplemental methods here
|
|
31
|
+
# def do_auth
|
|
32
|
+
# Howami::Authentication.new.do_interactive_auth
|
|
33
|
+
# end
|
|
34
|
+
|
|
35
|
+
# Declare command-line interface here
|
|
36
|
+
|
|
37
|
+
# description "one line description of your app"
|
|
38
|
+
#
|
|
39
|
+
# Accept flags via:
|
|
40
|
+
# on("--flag VAL","Some flag")
|
|
41
|
+
# options[flag] will contain VAL
|
|
42
|
+
#
|
|
43
|
+
# Specify switches via:
|
|
44
|
+
# on("--[no-]switch","Some switch")
|
|
45
|
+
on("--auth","Authenticate howami to the Fitbit API")
|
|
46
|
+
#
|
|
47
|
+
# Or, just call OptionParser methods on opts
|
|
48
|
+
#
|
|
49
|
+
# Require an argument
|
|
50
|
+
# arg :some_arg
|
|
51
|
+
#
|
|
52
|
+
# # Make an argument optional
|
|
53
|
+
# arg :optional_arg, :optional
|
|
54
|
+
|
|
55
|
+
version Howami::VERSION
|
|
56
|
+
|
|
57
|
+
use_log_level_option
|
|
58
|
+
|
|
59
|
+
go!
|
|
60
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Feature: My bootstrapped app kinda works
|
|
2
|
+
In order to get going on coding my awesome app
|
|
3
|
+
I want to have aruba and cucumber setup
|
|
4
|
+
So I don't have to do it myself
|
|
5
|
+
|
|
6
|
+
Scenario: displaying help
|
|
7
|
+
When I get help for "howami"
|
|
8
|
+
Then the exit status should be 0
|
|
9
|
+
And the banner should be present
|
|
10
|
+
And the banner should document that this app takes options
|
|
11
|
+
And the following options should be documented:
|
|
12
|
+
|--version|
|
|
13
|
+
|--auth|
|
|
14
|
+
And the banner should document that this app takes no arguments
|
|
15
|
+
|
|
16
|
+
Scenario: graceful error message when not authenticated
|
|
17
|
+
Given howami is not authenticated
|
|
18
|
+
When I run `howami`
|
|
19
|
+
Then the output should contain "You need to authenticate first"
|
|
20
|
+
And the exit status should be 1
|
|
21
|
+
|
|
22
|
+
Scenario: clean configuring the app
|
|
23
|
+
When I run `howami --auth` interactively
|
|
24
|
+
And I type "6uaeuvtk7m5fe3q4o75invkbpf"
|
|
25
|
+
Then the output should contain "Go to http://www.fitbit.com/oauth/authorize"
|
|
26
|
+
|
|
27
|
+
Scenario: overwrites authentication when already authenticated
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
require 'howami'
|
|
2
|
+
|
|
3
|
+
Given(/^howami is not authenticated$/) do
|
|
4
|
+
XDG_CONFIG_HOME = ENV['XDG_CONFIG_HOME'] || File.join(ENV['HOME'], '.config')
|
|
5
|
+
CREDENTIALS_STORE = File.join( XDG_CONFIG_HOME, "howami", "credentials.yml")
|
|
6
|
+
FileUtils.rm CREDENTIALS_STORE if File.exist? CREDENTIALS_STORE
|
|
7
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'aruba/cucumber'
|
|
2
|
+
require 'methadone/cucumber'
|
|
3
|
+
|
|
4
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
|
5
|
+
LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
|
|
6
|
+
|
|
7
|
+
Before do
|
|
8
|
+
# Using "announce" causes massive warnings on 1.9.2
|
|
9
|
+
@puts = true
|
|
10
|
+
@original_rubylib = ENV['RUBYLIB']
|
|
11
|
+
ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
|
|
12
|
+
|
|
13
|
+
@original_home = ENV['HOME']
|
|
14
|
+
ENV['HOME'] = File.expand_path( current_dir )
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
After do
|
|
18
|
+
ENV['RUBYLIB'] = @original_rubylib
|
|
19
|
+
ENV['HOME'] = @original_home
|
|
20
|
+
end
|
data/howami.gemspec
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'howami/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "howami"
|
|
8
|
+
gem.version = Howami::VERSION
|
|
9
|
+
gem.authors = ["Matthew Rothenberg"]
|
|
10
|
+
gem.email = ["mrothenberg@gmail.com"]
|
|
11
|
+
gem.description = %q{CLI tool to show a summary of Fitbit health statistics.}
|
|
12
|
+
gem.summary = %q{CLI tool to show a summary of Fitbit health statistics.}
|
|
13
|
+
gem.homepage = ""
|
|
14
|
+
|
|
15
|
+
gem.files = `git ls-files`.split($/)
|
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
gem.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
gem.add_development_dependency('rdoc')
|
|
21
|
+
gem.add_development_dependency('aruba', '~> 0.5.2')
|
|
22
|
+
gem.add_development_dependency('rake', '~> 10.0.3')
|
|
23
|
+
gem.add_development_dependency('rspec', '~> 2.13.0')
|
|
24
|
+
gem.add_development_dependency('fakefs')
|
|
25
|
+
gem.add_development_dependency('vcr', '~> 2.4.0')
|
|
26
|
+
gem.add_development_dependency('webmock', '~> 1.9.3')
|
|
27
|
+
|
|
28
|
+
gem.add_dependency('methadone', '~> 1.2.6')
|
|
29
|
+
gem.add_dependency('fitgem', '~> 0.6.1')
|
|
30
|
+
gem.add_dependency('colored')
|
|
31
|
+
end
|
data/lib/howami.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require "howami/version"
|
|
2
|
+
require "howami/configuration"
|
|
3
|
+
require "howami/credentials"
|
|
4
|
+
require "howami/authentication"
|
|
5
|
+
require "howami/fitbit"
|
|
6
|
+
|
|
7
|
+
module Howami
|
|
8
|
+
def self.cli_run!(fit_obj)
|
|
9
|
+
puts "Current weight: #{fit_obj.weight_str} (#{fit_obj.fat_str} body fat)"
|
|
10
|
+
puts "Activity level: #{fit_obj.steps_str} thus far today"
|
|
11
|
+
puts "Sleep last night: #{fit_obj.sleep_str}"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require "methadone"
|
|
2
|
+
require "fitgem"
|
|
3
|
+
|
|
4
|
+
module Howami
|
|
5
|
+
class Authentication
|
|
6
|
+
include Methadone::CLILogging
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@client = Fitgem::Client.new({:consumer_key => CONSUMER_KEY, :consumer_secret => CONSUMER_SECRET})
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def fetch_request_token!
|
|
13
|
+
@request_token = @client.request_token
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def do_interactive_auth
|
|
17
|
+
fetch_request_token!
|
|
18
|
+
|
|
19
|
+
auth_url = "http://www.fitbit.com/oauth/authorize?oauth_token=#{@request_token.token}"
|
|
20
|
+
puts "Go to #{auth_url} and then enter the verifier code below and hit Enter"
|
|
21
|
+
verifier = gets.chomp
|
|
22
|
+
debug "Parsed user verifier as: #{verifier}"
|
|
23
|
+
|
|
24
|
+
access_token = self.get_access_token_with_verifier(verifier)
|
|
25
|
+
debug "Received user token is: "+access_token.token
|
|
26
|
+
debug "Received user secret is: "+access_token.secret
|
|
27
|
+
|
|
28
|
+
Configuration.store_credentials(access_token.token, access_token.secret)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get_access_token_with_verifier(verifier)
|
|
32
|
+
@client.authorize(@request_token.token, @request_token.secret, { :oauth_verifier => verifier })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.authenticated_client
|
|
36
|
+
raise "No valid credentials stored!" unless Configuration.has_valid_credentials?
|
|
37
|
+
@auth_client ||= Fitgem::Client.new({
|
|
38
|
+
:consumer_key => CONSUMER_KEY,
|
|
39
|
+
:consumer_secret => CONSUMER_SECRET,
|
|
40
|
+
:token => Configuration.get_credentials[:user_token],
|
|
41
|
+
:secret => Configuration.get_credentials[:user_secret],
|
|
42
|
+
:user_id => "-"
|
|
43
|
+
})
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require "methadone"
|
|
2
|
+
|
|
3
|
+
module Howami
|
|
4
|
+
class Configuration
|
|
5
|
+
include Methadone::CLILogging
|
|
6
|
+
|
|
7
|
+
XDG_CONFIG_HOME = ENV['XDG_CONFIG_HOME'] || File.join(ENV['HOME'], '.config')
|
|
8
|
+
CREDENTIALS_STORE = File.join( XDG_CONFIG_HOME, "howami", "credentials.yml")
|
|
9
|
+
|
|
10
|
+
def self.has_valid_credentials?
|
|
11
|
+
self.has_stored_credentials?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.store_credentials(user_token, user_secret)
|
|
15
|
+
#create proper XDG directory if doesn't exist
|
|
16
|
+
FileUtils.mkdir_p( File.dirname(CREDENTIALS_STORE) )
|
|
17
|
+
|
|
18
|
+
#store
|
|
19
|
+
File.open(CREDENTIALS_STORE, 'w') do |f|
|
|
20
|
+
f.puts({ :user_token => user_token, :user_secret => user_secret }.to_yaml)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.get_credentials
|
|
25
|
+
return nil if not self.has_stored_credentials?
|
|
26
|
+
YAML.load_file(CREDENTIALS_STORE)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
protected
|
|
30
|
+
def self.has_stored_credentials?
|
|
31
|
+
File.exist? CREDENTIALS_STORE
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require "methadone"
|
|
2
|
+
|
|
3
|
+
module Howami
|
|
4
|
+
class Fitbit
|
|
5
|
+
include Methadone::CLILogging
|
|
6
|
+
|
|
7
|
+
attr_reader :client
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@client = Authentication.authenticated_client
|
|
11
|
+
retrieve_values!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def retrieve_values!
|
|
15
|
+
@weights = @client.data_by_time_range('/body/log/weight', {:base_date => Date.today.to_s, :period => '1w'})['weight']
|
|
16
|
+
@fats = @client.data_by_time_range('/body/log/fat', {:base_date => Date.today.to_s, :period => '1w'})['fat']
|
|
17
|
+
@sleeps = @client.sleep_on_date('today')
|
|
18
|
+
@activities = @client.activities_on_date('today')
|
|
19
|
+
@weight_lbl = weight_lbl_value_187_workaround
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# this method breaks the fitgem gem in ruby 1.8.7, so we just work around it and dont
|
|
23
|
+
# offer any i18n support for default units in 1.8.7, just assume AMERICA FUCK YEAH
|
|
24
|
+
def weight_lbl_value_187_workaround
|
|
25
|
+
return "pounds" if RUBY_VERSION =~ /^1\.8/
|
|
26
|
+
@client.label_for_measurement(:weight)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def weight_str
|
|
30
|
+
if @weights.last
|
|
31
|
+
return "#{@weights.last['weight']} #{@weight_lbl}"
|
|
32
|
+
else
|
|
33
|
+
return "* No weight data recorded in past 7 days."
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def fat_str
|
|
38
|
+
if @fats.last
|
|
39
|
+
return "#{@fats.last['fat']} %"
|
|
40
|
+
else
|
|
41
|
+
return "* No body fat data recorded in past 7 days."
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def sleep_str
|
|
46
|
+
return "no sleep data." if @sleeps['sleep'].count == 0
|
|
47
|
+
|
|
48
|
+
sleep_total_min = @sleeps['summary']['totalMinutesAsleep'].to_i
|
|
49
|
+
sleep_hours = sleep_total_min / 60
|
|
50
|
+
sleep_remainder_min = sleep_total_min % 60
|
|
51
|
+
return "#{sleep_hours} hours, #{sleep_remainder_min} min."
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def steps
|
|
55
|
+
@activities['summary']['steps']
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def steps_str
|
|
59
|
+
"#{self.steps} steps"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'fakefs/safe'
|
|
3
|
+
include Howami
|
|
4
|
+
|
|
5
|
+
describe Authentication do
|
|
6
|
+
|
|
7
|
+
describe ".new" do
|
|
8
|
+
it "should be instantiated with any args" do
|
|
9
|
+
lambda { Authentication.new() }.should_not raise_error
|
|
10
|
+
lambda { Authentication.new( "moo ") }.should raise_error(ArgumentError)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe ".authenticated_client" do
|
|
15
|
+
# it "should return a working fitgem client ready for action" √
|
|
16
|
+
it "should throw an error if proper credentials arent stored" do
|
|
17
|
+
FakeFS do
|
|
18
|
+
wipe_credentials!
|
|
19
|
+
lambda { Authentication.authenticated_client }.should raise_error
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
http_interactions:
|
|
3
|
+
- request:
|
|
4
|
+
method: get
|
|
5
|
+
uri: http://api.fitbit.com/1/user/-/body/log/weight/date/2013-05-10/1w.json
|
|
6
|
+
body:
|
|
7
|
+
encoding: US-ASCII
|
|
8
|
+
string: ''
|
|
9
|
+
headers:
|
|
10
|
+
User-Agent:
|
|
11
|
+
- fitgem gem v0.6.1 (OAuth gem v0.4.7)
|
|
12
|
+
Accept-Language:
|
|
13
|
+
- en_US
|
|
14
|
+
Accept-Encoding:
|
|
15
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
|
16
|
+
Accept:
|
|
17
|
+
- '*/*'
|
|
18
|
+
Authorization:
|
|
19
|
+
- <OAUTH_AUTH_HEADER>
|
|
20
|
+
response:
|
|
21
|
+
status:
|
|
22
|
+
code: 200
|
|
23
|
+
message: OK
|
|
24
|
+
headers:
|
|
25
|
+
Server:
|
|
26
|
+
- Apache-Coyote/1.1
|
|
27
|
+
Expires:
|
|
28
|
+
- Thu, 01 Jan 1970 00:00:00 GMT
|
|
29
|
+
Cache-Control:
|
|
30
|
+
- no-store, no-cache, must-revalidate
|
|
31
|
+
Pragma:
|
|
32
|
+
- no-cache
|
|
33
|
+
Set-Cookie:
|
|
34
|
+
- JSESSIONID=5908CBD729C81753C9D9824971D28B92.fitbit1; Path=/
|
|
35
|
+
Content-Type:
|
|
36
|
+
- application/json;charset=UTF-8
|
|
37
|
+
Content-Language:
|
|
38
|
+
- en-US
|
|
39
|
+
Content-Length:
|
|
40
|
+
- '728'
|
|
41
|
+
Date:
|
|
42
|
+
- Fri, 10 May 2013 14:59:45 GMT
|
|
43
|
+
body:
|
|
44
|
+
encoding: UTF-8
|
|
45
|
+
string: '{"weight":[{"bmi":22.01,"date":"2013-05-03","logId":1367578013000,"time":"10:46:53","weight":140.56},{"bmi":21.91,"date":"2013-05-04","logId":1367659147000,"time":"09:19:07","weight":139.9},{"bmi":22.1,"date":"2013-05-05","logId":1367743206000,"time":"08:40:06","weight":141.11},{"bmi":21.81,"date":"2013-05-06","logId":1367829101000,"time":"08:31:41","weight":139.28},{"bmi":21.89,"date":"2013-05-07","logId":1367913082000,"time":"07:51:22","weight":139.79},{"bmi":21.93,"date":"2013-05-08","logId":1368003855000,"time":"09:04:15","weight":140.08},{"bmi":21.84,"date":"2013-05-09","logId":1368089755000,"time":"08:55:55","weight":139.5},{"bmi":21.73,"date":"2013-05-10","logId":1368170956000,"time":"07:29:16","weight":138.8}]}'
|
|
46
|
+
http_version:
|
|
47
|
+
recorded_at: Fri, 10 May 2013 14:59:46 GMT
|
|
48
|
+
- request:
|
|
49
|
+
method: get
|
|
50
|
+
uri: http://api.fitbit.com/1/user/-/body/log/fat/date/2013-05-10/1w.json
|
|
51
|
+
body:
|
|
52
|
+
encoding: US-ASCII
|
|
53
|
+
string: ''
|
|
54
|
+
headers:
|
|
55
|
+
User-Agent:
|
|
56
|
+
- fitgem gem v0.6.1 (OAuth gem v0.4.7)
|
|
57
|
+
Accept-Language:
|
|
58
|
+
- en_US
|
|
59
|
+
Accept-Encoding:
|
|
60
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
|
61
|
+
Accept:
|
|
62
|
+
- '*/*'
|
|
63
|
+
Authorization:
|
|
64
|
+
- <OAUTH_AUTH_HEADER>
|
|
65
|
+
response:
|
|
66
|
+
status:
|
|
67
|
+
code: 200
|
|
68
|
+
message: OK
|
|
69
|
+
headers:
|
|
70
|
+
Server:
|
|
71
|
+
- Apache-Coyote/1.1
|
|
72
|
+
Expires:
|
|
73
|
+
- Thu, 01 Jan 1970 00:00:00 GMT
|
|
74
|
+
Cache-Control:
|
|
75
|
+
- no-store, no-cache, must-revalidate
|
|
76
|
+
Pragma:
|
|
77
|
+
- no-cache
|
|
78
|
+
Set-Cookie:
|
|
79
|
+
- JSESSIONID=6B48DC79806B3131CC24091D8BD4F499.fitbit1; Path=/
|
|
80
|
+
Content-Type:
|
|
81
|
+
- application/json;charset=UTF-8
|
|
82
|
+
Content-Language:
|
|
83
|
+
- en-US
|
|
84
|
+
Content-Length:
|
|
85
|
+
- '600'
|
|
86
|
+
Date:
|
|
87
|
+
- Fri, 10 May 2013 14:59:44 GMT
|
|
88
|
+
body:
|
|
89
|
+
encoding: UTF-8
|
|
90
|
+
string: '{"fat":[{"date":"2013-05-03","fat":13.39,"logId":1367578013000,"time":"10:46:53"},{"date":"2013-05-04","fat":12.5,"logId":1367659147000,"time":"09:19:07"},{"date":"2013-05-05","fat":13.37,"logId":1367743206000,"time":"08:40:06"},{"date":"2013-05-06","fat":13.15,"logId":1367829101000,"time":"08:31:41"},{"date":"2013-05-07","fat":12.81,"logId":1367913082000,"time":"07:51:22"},{"date":"2013-05-08","fat":12.95,"logId":1368003855000,"time":"09:04:15"},{"date":"2013-05-09","fat":12.94,"logId":1368089755000,"time":"08:55:55"},{"date":"2013-05-10","fat":12.97,"logId":1368170956000,"time":"07:29:16"}]}'
|
|
91
|
+
http_version:
|
|
92
|
+
recorded_at: Fri, 10 May 2013 14:59:46 GMT
|
|
93
|
+
- request:
|
|
94
|
+
method: get
|
|
95
|
+
uri: http://api.fitbit.com/1/user/-/sleep/date/2013-05-10.json
|
|
96
|
+
body:
|
|
97
|
+
encoding: US-ASCII
|
|
98
|
+
string: ''
|
|
99
|
+
headers:
|
|
100
|
+
User-Agent:
|
|
101
|
+
- fitgem gem v0.6.1 (OAuth gem v0.4.7)
|
|
102
|
+
Accept-Language:
|
|
103
|
+
- en_US
|
|
104
|
+
Accept-Encoding:
|
|
105
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
|
106
|
+
Accept:
|
|
107
|
+
- '*/*'
|
|
108
|
+
Authorization:
|
|
109
|
+
- <OAUTH_AUTH_HEADER>
|
|
110
|
+
response:
|
|
111
|
+
status:
|
|
112
|
+
code: 200
|
|
113
|
+
message: OK
|
|
114
|
+
headers:
|
|
115
|
+
Server:
|
|
116
|
+
- Apache-Coyote/1.1
|
|
117
|
+
Expires:
|
|
118
|
+
- Thu, 01 Jan 1970 00:00:00 GMT
|
|
119
|
+
Cache-Control:
|
|
120
|
+
- no-store, no-cache, must-revalidate
|
|
121
|
+
Pragma:
|
|
122
|
+
- no-cache
|
|
123
|
+
Set-Cookie:
|
|
124
|
+
- JSESSIONID=D3FEACFED7A7120A8CA8A284EF9918C1.fitbit1; Path=/
|
|
125
|
+
Content-Type:
|
|
126
|
+
- application/json;charset=UTF-8
|
|
127
|
+
Content-Language:
|
|
128
|
+
- en-US
|
|
129
|
+
Content-Length:
|
|
130
|
+
- '6999'
|
|
131
|
+
Date:
|
|
132
|
+
- Fri, 10 May 2013 14:59:44 GMT
|
|
133
|
+
body:
|
|
134
|
+
encoding: UTF-8
|
|
135
|
+
string: '{"sleep":[{"awakeningsCount":7,"duration":11100000,"efficiency":91,"isMainSleep":true,"logId":35846905,"minuteData":[{"dateTime":"03:50:00","value":"3"},{"dateTime":"03:51:00","value":"3"},{"dateTime":"03:52:00","value":"3"},{"dateTime":"03:53:00","value":"3"},{"dateTime":"03:54:00","value":"3"},{"dateTime":"03:55:00","value":"3"},{"dateTime":"03:56:00","value":"2"},{"dateTime":"03:57:00","value":"1"},{"dateTime":"03:58:00","value":"1"},{"dateTime":"03:59:00","value":"1"},{"dateTime":"04:00:00","value":"1"},{"dateTime":"04:01:00","value":"1"},{"dateTime":"04:02:00","value":"1"},{"dateTime":"04:03:00","value":"1"},{"dateTime":"04:04:00","value":"1"},{"dateTime":"04:05:00","value":"1"},{"dateTime":"04:06:00","value":"1"},{"dateTime":"04:07:00","value":"1"},{"dateTime":"04:08:00","value":"1"},{"dateTime":"04:09:00","value":"1"},{"dateTime":"04:10:00","value":"1"},{"dateTime":"04:11:00","value":"1"},{"dateTime":"04:12:00","value":"1"},{"dateTime":"04:13:00","value":"1"},{"dateTime":"04:14:00","value":"1"},{"dateTime":"04:15:00","value":"1"},{"dateTime":"04:16:00","value":"1"},{"dateTime":"04:17:00","value":"1"},{"dateTime":"04:18:00","value":"1"},{"dateTime":"04:19:00","value":"1"},{"dateTime":"04:20:00","value":"1"},{"dateTime":"04:21:00","value":"1"},{"dateTime":"04:22:00","value":"1"},{"dateTime":"04:23:00","value":"1"},{"dateTime":"04:24:00","value":"1"},{"dateTime":"04:25:00","value":"1"},{"dateTime":"04:26:00","value":"1"},{"dateTime":"04:27:00","value":"1"},{"dateTime":"04:28:00","value":"1"},{"dateTime":"04:29:00","value":"1"},{"dateTime":"04:30:00","value":"1"},{"dateTime":"04:31:00","value":"1"},{"dateTime":"04:32:00","value":"1"},{"dateTime":"04:33:00","value":"1"},{"dateTime":"04:34:00","value":"1"},{"dateTime":"04:35:00","value":"1"},{"dateTime":"04:36:00","value":"1"},{"dateTime":"04:37:00","value":"1"},{"dateTime":"04:38:00","value":"1"},{"dateTime":"04:39:00","value":"1"},{"dateTime":"04:40:00","value":"1"},{"dateTime":"04:41:00","value":"1"},{"dateTime":"04:42:00","value":"1"},{"dateTime":"04:43:00","value":"1"},{"dateTime":"04:44:00","value":"2"},{"dateTime":"04:45:00","value":"1"},{"dateTime":"04:46:00","value":"1"},{"dateTime":"04:47:00","value":"1"},{"dateTime":"04:48:00","value":"1"},{"dateTime":"04:49:00","value":"1"},{"dateTime":"04:50:00","value":"1"},{"dateTime":"04:51:00","value":"1"},{"dateTime":"04:52:00","value":"1"},{"dateTime":"04:53:00","value":"1"},{"dateTime":"04:54:00","value":"1"},{"dateTime":"04:55:00","value":"1"},{"dateTime":"04:56:00","value":"1"},{"dateTime":"04:57:00","value":"1"},{"dateTime":"04:58:00","value":"1"},{"dateTime":"04:59:00","value":"1"},{"dateTime":"05:00:00","value":"1"},{"dateTime":"05:01:00","value":"1"},{"dateTime":"05:02:00","value":"1"},{"dateTime":"05:03:00","value":"1"},{"dateTime":"05:04:00","value":"1"},{"dateTime":"05:05:00","value":"1"},{"dateTime":"05:06:00","value":"1"},{"dateTime":"05:07:00","value":"1"},{"dateTime":"05:08:00","value":"1"},{"dateTime":"05:09:00","value":"1"},{"dateTime":"05:10:00","value":"1"},{"dateTime":"05:11:00","value":"1"},{"dateTime":"05:12:00","value":"1"},{"dateTime":"05:13:00","value":"1"},{"dateTime":"05:14:00","value":"1"},{"dateTime":"05:15:00","value":"1"},{"dateTime":"05:16:00","value":"1"},{"dateTime":"05:17:00","value":"1"},{"dateTime":"05:18:00","value":"1"},{"dateTime":"05:19:00","value":"1"},{"dateTime":"05:20:00","value":"1"},{"dateTime":"05:21:00","value":"1"},{"dateTime":"05:22:00","value":"1"},{"dateTime":"05:23:00","value":"1"},{"dateTime":"05:24:00","value":"1"},{"dateTime":"05:25:00","value":"1"},{"dateTime":"05:26:00","value":"1"},{"dateTime":"05:27:00","value":"1"},{"dateTime":"05:28:00","value":"1"},{"dateTime":"05:29:00","value":"1"},{"dateTime":"05:30:00","value":"1"},{"dateTime":"05:31:00","value":"1"},{"dateTime":"05:32:00","value":"1"},{"dateTime":"05:33:00","value":"1"},{"dateTime":"05:34:00","value":"1"},{"dateTime":"05:35:00","value":"1"},{"dateTime":"05:36:00","value":"1"},{"dateTime":"05:37:00","value":"1"},{"dateTime":"05:38:00","value":"1"},{"dateTime":"05:39:00","value":"1"},{"dateTime":"05:40:00","value":"1"},{"dateTime":"05:41:00","value":"1"},{"dateTime":"05:42:00","value":"1"},{"dateTime":"05:43:00","value":"1"},{"dateTime":"05:44:00","value":"1"},{"dateTime":"05:45:00","value":"1"},{"dateTime":"05:46:00","value":"1"},{"dateTime":"05:47:00","value":"1"},{"dateTime":"05:48:00","value":"1"},{"dateTime":"05:49:00","value":"1"},{"dateTime":"05:50:00","value":"1"},{"dateTime":"05:51:00","value":"1"},{"dateTime":"05:52:00","value":"1"},{"dateTime":"05:53:00","value":"1"},{"dateTime":"05:54:00","value":"1"},{"dateTime":"05:55:00","value":"1"},{"dateTime":"05:56:00","value":"1"},{"dateTime":"05:57:00","value":"1"},{"dateTime":"05:58:00","value":"1"},{"dateTime":"05:59:00","value":"1"},{"dateTime":"06:00:00","value":"1"},{"dateTime":"06:01:00","value":"1"},{"dateTime":"06:02:00","value":"1"},{"dateTime":"06:03:00","value":"2"},{"dateTime":"06:04:00","value":"2"},{"dateTime":"06:05:00","value":"2"},{"dateTime":"06:06:00","value":"2"},{"dateTime":"06:07:00","value":"2"},{"dateTime":"06:08:00","value":"2"},{"dateTime":"06:09:00","value":"1"},{"dateTime":"06:10:00","value":"2"},{"dateTime":"06:11:00","value":"2"},{"dateTime":"06:12:00","value":"1"},{"dateTime":"06:13:00","value":"1"},{"dateTime":"06:14:00","value":"1"},{"dateTime":"06:15:00","value":"1"},{"dateTime":"06:16:00","value":"1"},{"dateTime":"06:17:00","value":"1"},{"dateTime":"06:18:00","value":"1"},{"dateTime":"06:19:00","value":"1"},{"dateTime":"06:20:00","value":"1"},{"dateTime":"06:21:00","value":"1"},{"dateTime":"06:22:00","value":"1"},{"dateTime":"06:23:00","value":"1"},{"dateTime":"06:24:00","value":"1"},{"dateTime":"06:25:00","value":"1"},{"dateTime":"06:26:00","value":"1"},{"dateTime":"06:27:00","value":"2"},{"dateTime":"06:28:00","value":"1"},{"dateTime":"06:29:00","value":"1"},{"dateTime":"06:30:00","value":"1"},{"dateTime":"06:31:00","value":"2"},{"dateTime":"06:32:00","value":"1"},{"dateTime":"06:33:00","value":"1"},{"dateTime":"06:34:00","value":"1"},{"dateTime":"06:35:00","value":"1"},{"dateTime":"06:36:00","value":"1"},{"dateTime":"06:37:00","value":"1"},{"dateTime":"06:38:00","value":"1"},{"dateTime":"06:39:00","value":"1"},{"dateTime":"06:40:00","value":"1"},{"dateTime":"06:41:00","value":"1"},{"dateTime":"06:42:00","value":"1"},{"dateTime":"06:43:00","value":"1"},{"dateTime":"06:44:00","value":"1"},{"dateTime":"06:45:00","value":"1"},{"dateTime":"06:46:00","value":"2"},{"dateTime":"06:47:00","value":"2"},{"dateTime":"06:48:00","value":"1"},{"dateTime":"06:49:00","value":"1"},{"dateTime":"06:50:00","value":"2"},{"dateTime":"06:51:00","value":"2"},{"dateTime":"06:52:00","value":"2"},{"dateTime":"06:53:00","value":"1"},{"dateTime":"06:54:00","value":"2"}],"minutesAfterWakeup":1,"minutesAsleep":161,"minutesAwake":16,"minutesToFallAsleep":7,"startTime":"2013-05-10T03:50:00.000","timeInBed":185}],"summary":{"totalMinutesAsleep":161,"totalSleepRecords":1,"totalTimeInBed":185}}'
|
|
136
|
+
http_version:
|
|
137
|
+
recorded_at: Fri, 10 May 2013 14:59:46 GMT
|
|
138
|
+
- request:
|
|
139
|
+
method: get
|
|
140
|
+
uri: http://api.fitbit.com/1/user/-/profile.json
|
|
141
|
+
body:
|
|
142
|
+
encoding: US-ASCII
|
|
143
|
+
string: ''
|
|
144
|
+
headers:
|
|
145
|
+
User-Agent:
|
|
146
|
+
- fitgem gem v0.6.1 (OAuth gem v0.4.7)
|
|
147
|
+
Accept-Language:
|
|
148
|
+
- en_US
|
|
149
|
+
Accept-Encoding:
|
|
150
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
|
151
|
+
Accept:
|
|
152
|
+
- '*/*'
|
|
153
|
+
Authorization:
|
|
154
|
+
- <OAUTH_AUTH_HEADER>
|
|
155
|
+
response:
|
|
156
|
+
status:
|
|
157
|
+
code: 200
|
|
158
|
+
message: OK
|
|
159
|
+
headers:
|
|
160
|
+
Server:
|
|
161
|
+
- Apache-Coyote/1.1
|
|
162
|
+
Expires:
|
|
163
|
+
- Thu, 01 Jan 1970 00:00:00 GMT
|
|
164
|
+
Cache-Control:
|
|
165
|
+
- no-store, no-cache, must-revalidate
|
|
166
|
+
Pragma:
|
|
167
|
+
- no-cache
|
|
168
|
+
Set-Cookie:
|
|
169
|
+
- JSESSIONID=1B679215CCC4D720D8F0D2E0908C189C.fitbit1; Path=/
|
|
170
|
+
Content-Type:
|
|
171
|
+
- application/json;charset=UTF-8
|
|
172
|
+
Content-Language:
|
|
173
|
+
- en-US
|
|
174
|
+
Content-Length:
|
|
175
|
+
- '615'
|
|
176
|
+
Date:
|
|
177
|
+
- Fri, 10 May 2013 14:59:44 GMT
|
|
178
|
+
body:
|
|
179
|
+
encoding: UTF-8
|
|
180
|
+
string: '{"user":{"avatar":"http://cache.fitbit.com/DA2F8E91-BDC1-5BD1-1209-8AA23C6E2AB3_profile_100_square.jpg","city":"Brooklyn","country":"US","dateOfBirth":"1979-08-04","displayName":"Matthew","distanceUnit":"en_US","encodedId":"24DQFP","foodsLocale":"en_US","fullName":"Matthew
|
|
181
|
+
Rothenberg","gender":"MALE","glucoseUnit":"en_US","height":67.00787401574803,"heightUnit":"en_US","locale":"en_US","memberSince":"2013-01-17","nickname":"mroth","offsetFromUTCMillis":-14400000,"state":"NY","strideLengthRunning":0,"strideLengthWalking":0,"timezone":"America/New_York","waterUnit":"en_US","weight":138.8,"weightUnit":"en_US"}}'
|
|
182
|
+
http_version:
|
|
183
|
+
recorded_at: Fri, 10 May 2013 14:59:46 GMT
|
|
184
|
+
- request:
|
|
185
|
+
method: get
|
|
186
|
+
uri: http://api.fitbit.com/1/user/-/activities/date/2013-05-10.json
|
|
187
|
+
body:
|
|
188
|
+
encoding: US-ASCII
|
|
189
|
+
string: ''
|
|
190
|
+
headers:
|
|
191
|
+
User-Agent:
|
|
192
|
+
- fitgem gem v0.6.1 (OAuth gem v0.4.7)
|
|
193
|
+
Accept-Language:
|
|
194
|
+
- en_US
|
|
195
|
+
Accept-Encoding:
|
|
196
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
|
197
|
+
Accept:
|
|
198
|
+
- '*/*'
|
|
199
|
+
Authorization:
|
|
200
|
+
- <OAUTH_AUTH_HEADER>
|
|
201
|
+
response:
|
|
202
|
+
status:
|
|
203
|
+
code: 200
|
|
204
|
+
message: OK
|
|
205
|
+
headers:
|
|
206
|
+
Server:
|
|
207
|
+
- Apache-Coyote/1.1
|
|
208
|
+
Expires:
|
|
209
|
+
- Thu, 01 Jan 1970 00:00:00 GMT
|
|
210
|
+
Cache-Control:
|
|
211
|
+
- no-store, no-cache, must-revalidate
|
|
212
|
+
Pragma:
|
|
213
|
+
- no-cache
|
|
214
|
+
Set-Cookie:
|
|
215
|
+
- JSESSIONID=ECEDFFC89CE6BC7B3B89AF82D8DCEF61.fitbit1; Path=/
|
|
216
|
+
Content-Type:
|
|
217
|
+
- application/json;charset=UTF-8
|
|
218
|
+
Content-Language:
|
|
219
|
+
- en-US
|
|
220
|
+
Content-Length:
|
|
221
|
+
- '624'
|
|
222
|
+
Date:
|
|
223
|
+
- Fri, 10 May 2013 15:54:14 GMT
|
|
224
|
+
body:
|
|
225
|
+
encoding: UTF-8
|
|
226
|
+
string: '{"activities":[],"goals":{"activeScore":1000,"caloriesOut":2817,"distance":5,"steps":10000},"summary":{"activeScore":207,"activityCalories":277,"caloriesBMR":761,"caloriesOut":986,"distances":[{"activity":"total","distance":0.68},{"activity":"tracker","distance":0.68},{"activity":"loggedActivities","distance":0},{"activity":"veryActive","distance":0},{"activity":"moderatelyActive","distance":0.23},{"activity":"lightlyActive","distance":0.44},{"activity":"sedentaryActive","distance":0}],"fairlyActiveMinutes":13,"lightlyActiveMinutes":79,"marginalCalories":154,"sedentaryMinutes":437,"steps":1546,"veryActiveMinutes":0}}'
|
|
227
|
+
http_version:
|
|
228
|
+
recorded_at: Fri, 10 May 2013 15:54:17 GMT
|
|
229
|
+
recorded_with: VCR 2.4.0
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'fakefs/safe'
|
|
3
|
+
include Howami
|
|
4
|
+
|
|
5
|
+
describe Configuration do
|
|
6
|
+
|
|
7
|
+
before(:each) do
|
|
8
|
+
FakeFS.activate!
|
|
9
|
+
end
|
|
10
|
+
after(:each) do
|
|
11
|
+
FakeFS::FileSystem.clear
|
|
12
|
+
FakeFS::deactivate!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe ".has_valid_credentials?" do
|
|
16
|
+
it "should return false when no tokens are stored" do
|
|
17
|
+
wipe_credentials!
|
|
18
|
+
Configuration.has_valid_credentials?.should be_false
|
|
19
|
+
end
|
|
20
|
+
it "should return false if tokens are invalid"
|
|
21
|
+
it "should return true if stored tokens are valid"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe ".get_credentials" do
|
|
25
|
+
it "should return the credentials" do
|
|
26
|
+
Configuration.store_credentials( 'aaa', 'bbb' )
|
|
27
|
+
Configuration.get_credentials().should_not be(nil)
|
|
28
|
+
end
|
|
29
|
+
it "should not throw an exception if no credentials are stored in fs" do
|
|
30
|
+
lambda { Configuration.get_credentials() }.should_not raise_error(Errno::ENOENT)
|
|
31
|
+
end
|
|
32
|
+
it "should return nil if no credentials are stored in fs" do
|
|
33
|
+
Configuration.get_credentials().should be(nil)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe ".store_credentials" do
|
|
38
|
+
it "should require credentials to be passed" do
|
|
39
|
+
lambda { Configuration.store_credentials() }.should raise_error(ArgumentError)
|
|
40
|
+
end
|
|
41
|
+
it "should verify the credentials passed are of proper format"
|
|
42
|
+
it "should store the credentials to a file" do
|
|
43
|
+
File.exist?(Configuration::CREDENTIALS_STORE).should be_false
|
|
44
|
+
Configuration.store_credentials( 'aaa', 'bbb' )
|
|
45
|
+
File.exist?(Configuration::CREDENTIALS_STORE).should be_true
|
|
46
|
+
end
|
|
47
|
+
it "should persist the credentials in a way that they match upon retrieval" do
|
|
48
|
+
Configuration.store_credentials( 'aaa', 'bbb' )
|
|
49
|
+
Configuration.get_credentials()[:user_token].should eq 'aaa'
|
|
50
|
+
Configuration.get_credentials()[:user_secret].should eq 'bbb'
|
|
51
|
+
end
|
|
52
|
+
it "should overwrite credentials if stored a second time" do
|
|
53
|
+
Configuration.store_credentials( 'aaa', 'aaa' )
|
|
54
|
+
Configuration.store_credentials( 'bbb', 'bbb' )
|
|
55
|
+
Configuration.get_credentials()[:user_token].should eq 'bbb'
|
|
56
|
+
Configuration.get_credentials()[:user_secret].should eq 'bbb'
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
data/spec/fitbit_spec.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
include Howami
|
|
3
|
+
|
|
4
|
+
describe Fitbit, :vcr => { :cassette_name => "fitbit" } do
|
|
5
|
+
#use a vcr cassette so we can assume a full populated and authenticated user record
|
|
6
|
+
|
|
7
|
+
# for testing on machines that dont have a valid condif (e.g. travis-ci)
|
|
8
|
+
# generate an invalid configuration and let us rely upon the VCR cassettes
|
|
9
|
+
# we don't use fakefs with this because it seems to screw up VCR
|
|
10
|
+
before(:all) do
|
|
11
|
+
FAKED_CONFIG_LOL = false
|
|
12
|
+
if !File.exist? Howami::Configuration::CREDENTIALS_STORE
|
|
13
|
+
Configuration.store_credentials( 'abc', 'xyz' )
|
|
14
|
+
FAKED_CONFIG_LOL = true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
after(:all) do
|
|
18
|
+
FileUtils.rm Howami::Configuration::CREDENTIALS_STORE if FAKED_CONFIG_LOL
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe ".new" do
|
|
22
|
+
it "should be instantiated without any args" do
|
|
23
|
+
lambda { Fitbit.new() }.should_not raise_error
|
|
24
|
+
lambda { Fitbit.new( "moo ") }.should raise_error(ArgumentError)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "should retrieve all the values it needs on instantiation"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context "output rendering" do
|
|
31
|
+
before(:each) { @fb = Fitbit.new }
|
|
32
|
+
|
|
33
|
+
describe "#weight_str" do
|
|
34
|
+
it "returns a nicely formatted string with the proper weight" do
|
|
35
|
+
@fb.weight_str.should be_kind_of(String)
|
|
36
|
+
@fb.weight_str.should match(/\d\d\d\.\d pounds/)
|
|
37
|
+
end
|
|
38
|
+
it "returns a nice error message if there is no weight from past week" do
|
|
39
|
+
@fb.instance_variable_set("@weights",[])
|
|
40
|
+
@fb.weight_str.should match(/No weight data recorded in past 7 days./)
|
|
41
|
+
end
|
|
42
|
+
it "adds an indicator if measurement is more than 24hrs old"
|
|
43
|
+
end
|
|
44
|
+
describe "#fat_str" do
|
|
45
|
+
it "returns a nicely formatted string with the current body fat" do
|
|
46
|
+
@fb.fat_str.should match(/\d\d\.\d\d %/)
|
|
47
|
+
end
|
|
48
|
+
it "returns a nice error message if there is no body fat measurement from past week" do
|
|
49
|
+
@fb.instance_variable_set("@fats",[])
|
|
50
|
+
@fb.fat_str.should match(/No body fat data recorded in past 7 days./)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "#sleep_str" do
|
|
55
|
+
it "returns a nicely formatted string with the previous nights sleep" do
|
|
56
|
+
@fb.sleep_str.should match(/\d hours, \d+ min./)
|
|
57
|
+
end
|
|
58
|
+
it "returns a nice error message if there is no sleep data for day" do
|
|
59
|
+
@fb.instance_variable_set("@sleeps", {"sleep"=>[], "summary"=>{"totalMinutesAsleep"=>0, "totalSleepRecords"=>0, "totalTimeInBed"=>0}} )
|
|
60
|
+
@fb.sleep_str.should match(/no sleep data./)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "#steps" do
|
|
65
|
+
it "returns the amount of steps that day as an integer" do
|
|
66
|
+
@fb.steps.should be_kind_of(Integer)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "#steps_str" do
|
|
71
|
+
it "returns a nicely formatted string with the current amount of daily steps" do
|
|
72
|
+
@fb.steps_str.should match(/\d+ steps/)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'rspec'
|
|
2
|
+
require 'howami'
|
|
3
|
+
require 'vcr'
|
|
4
|
+
|
|
5
|
+
RSpec.configure do |config|
|
|
6
|
+
config.color_enabled = true
|
|
7
|
+
config.formatter = 'documentation'
|
|
8
|
+
# config.treat_symbols_as_metadata_keys_with_true_values = true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
VCR.configure do |c|
|
|
12
|
+
c.cassette_library_dir = 'spec/cassettes'
|
|
13
|
+
c.hook_into :webmock
|
|
14
|
+
c.configure_rspec_metadata!
|
|
15
|
+
c.default_cassette_options = { :record => :none, :match_requests_on => [:method, :uri] }
|
|
16
|
+
c.filter_sensitive_data('<OAUTH_AUTH_HEADER>') do |interaction|
|
|
17
|
+
interaction.request.headers['Authorization'].first
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def wipe_credentials!
|
|
22
|
+
cred_store = Howami::Configuration::CREDENTIALS_STORE
|
|
23
|
+
FileUtils.rm cred_store if File.exist? cred_store
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: howami
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Matthew Rothenberg
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2013-05-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rdoc
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '>='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '>='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: aruba
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ~>
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 0.5.2
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ~>
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 0.5.2
|
|
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.3
|
|
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.3
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ~>
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 2.13.0
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ~>
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 2.13.0
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: fakefs
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - '>='
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - '>='
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: vcr
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ~>
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 2.4.0
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ~>
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 2.4.0
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: webmock
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ~>
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: 1.9.3
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ~>
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: 1.9.3
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: methadone
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ~>
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: 1.2.6
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ~>
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: 1.2.6
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: fitgem
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ~>
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: 0.6.1
|
|
132
|
+
type: :runtime
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ~>
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: 0.6.1
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: colored
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - '>='
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
type: :runtime
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - '>='
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '0'
|
|
153
|
+
description: CLI tool to show a summary of Fitbit health statistics.
|
|
154
|
+
email:
|
|
155
|
+
- mrothenberg@gmail.com
|
|
156
|
+
executables:
|
|
157
|
+
- howami
|
|
158
|
+
extensions: []
|
|
159
|
+
extra_rdoc_files: []
|
|
160
|
+
files:
|
|
161
|
+
- .gitignore
|
|
162
|
+
- .travis.yml
|
|
163
|
+
- CONTRIBUTING.md
|
|
164
|
+
- Gemfile
|
|
165
|
+
- LICENSE.txt
|
|
166
|
+
- README.md
|
|
167
|
+
- README.rdoc
|
|
168
|
+
- Rakefile
|
|
169
|
+
- bin/howami
|
|
170
|
+
- features/howami.feature
|
|
171
|
+
- features/step_definitions/howami_steps.rb
|
|
172
|
+
- features/support/env.rb
|
|
173
|
+
- howami.gemspec
|
|
174
|
+
- lib/howami.rb
|
|
175
|
+
- lib/howami/authentication.rb
|
|
176
|
+
- lib/howami/configuration.rb
|
|
177
|
+
- lib/howami/credentials.rb
|
|
178
|
+
- lib/howami/fitbit.rb
|
|
179
|
+
- lib/howami/version.rb
|
|
180
|
+
- spec/authentication_spec.rb
|
|
181
|
+
- spec/cassettes/fitbit.yml
|
|
182
|
+
- spec/configuration_spec.rb
|
|
183
|
+
- spec/fitbit_spec.rb
|
|
184
|
+
- spec/spec_helper.rb
|
|
185
|
+
homepage: ''
|
|
186
|
+
licenses: []
|
|
187
|
+
metadata: {}
|
|
188
|
+
post_install_message:
|
|
189
|
+
rdoc_options: []
|
|
190
|
+
require_paths:
|
|
191
|
+
- lib
|
|
192
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
193
|
+
requirements:
|
|
194
|
+
- - '>='
|
|
195
|
+
- !ruby/object:Gem::Version
|
|
196
|
+
version: '0'
|
|
197
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
198
|
+
requirements:
|
|
199
|
+
- - '>='
|
|
200
|
+
- !ruby/object:Gem::Version
|
|
201
|
+
version: '0'
|
|
202
|
+
requirements: []
|
|
203
|
+
rubyforge_project:
|
|
204
|
+
rubygems_version: 2.0.0
|
|
205
|
+
signing_key:
|
|
206
|
+
specification_version: 4
|
|
207
|
+
summary: CLI tool to show a summary of Fitbit health statistics.
|
|
208
|
+
test_files:
|
|
209
|
+
- features/howami.feature
|
|
210
|
+
- features/step_definitions/howami_steps.rb
|
|
211
|
+
- features/support/env.rb
|
|
212
|
+
- spec/authentication_spec.rb
|
|
213
|
+
- spec/cassettes/fitbit.yml
|
|
214
|
+
- spec/configuration_spec.rb
|
|
215
|
+
- spec/fitbit_spec.rb
|
|
216
|
+
- spec/spec_helper.rb
|