runkeeper 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.
- data/.dev +3 -0
- data/.gitignore +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README.markdown +9 -0
- data/Rakefile +1 -0
- data/bin/token_generator +5 -0
- data/lib/runkeeper.rb +22 -0
- data/lib/runkeeper/activity.rb +54 -0
- data/lib/runkeeper/configuration.rb +24 -0
- data/lib/runkeeper/connection.rb +71 -0
- data/lib/runkeeper/token_generator.rb +78 -0
- data/lib/runkeeper/user.rb +47 -0
- data/lib/runkeeper/version.rb +3 -0
- data/runkeeper.gemspec +21 -0
- metadata +103 -0
data/.dev
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2
|
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# Runkeeper API Client
|
2
|
+
## Work in progress - accepting patches
|
3
|
+
|
4
|
+
## Example
|
5
|
+
user = Runkeeper.new("user token")
|
6
|
+
puts user.name
|
7
|
+
activity = user.new_activity(:duration => 60, :type => "Swimming", :distance => 1000
|
8
|
+
activity.save
|
9
|
+
puts user.activity_url(activity)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/token_generator
ADDED
data/lib/runkeeper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class Runkeeper
|
5
|
+
require 'runkeeper/version'
|
6
|
+
|
7
|
+
autoload 'Connection', 'runkeeper/connection'
|
8
|
+
autoload 'User', 'runkeeper/user'
|
9
|
+
autoload 'Activity', 'runkeeper/activity'
|
10
|
+
autoload 'Configuration', 'runkeeper/configuration'
|
11
|
+
|
12
|
+
private :initialize
|
13
|
+
|
14
|
+
def self.new(token)
|
15
|
+
Runkeeper::User.new(token)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.configure
|
19
|
+
yield Configuration
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Runkeeper
|
2
|
+
|
3
|
+
class Activity
|
4
|
+
attr_reader :connection, :url
|
5
|
+
attr_reader :type, :start_time, :total_distance, :duration, :average_heart_rate, :total_calories, :notes, :path, :post_to_facebook, :post_to_twitter
|
6
|
+
|
7
|
+
def initialize(connection, attrs)
|
8
|
+
@connection = connection
|
9
|
+
@attrs = attrs
|
10
|
+
attrs.each do |k,v|
|
11
|
+
instance_variable_set("@#{k}", v)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def save
|
16
|
+
@save_response = connection.save_activity(to_hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
def id
|
20
|
+
@save_response && @save_response.headers['location'][/\d+$/]
|
21
|
+
end
|
22
|
+
|
23
|
+
def formatted_start_time
|
24
|
+
start_time.strftime("%a, %d %b %Y %H:%M:%S")
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
main = {
|
29
|
+
:type => type,
|
30
|
+
:start_time => formatted_start_time,
|
31
|
+
:duration => duration
|
32
|
+
}
|
33
|
+
main.merge! optional_attrs
|
34
|
+
end
|
35
|
+
|
36
|
+
def optional_attrs
|
37
|
+
h = {}
|
38
|
+
[
|
39
|
+
:total_distance,
|
40
|
+
:average_heart_rate,
|
41
|
+
:total_calories,
|
42
|
+
:notes,
|
43
|
+
:path,
|
44
|
+
:post_to_facebook,
|
45
|
+
:post_to_twitter
|
46
|
+
].each do |method_name|
|
47
|
+
v = send(method_name)
|
48
|
+
h[method_name] = v if v
|
49
|
+
end
|
50
|
+
h
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Runkeeper
|
2
|
+
|
3
|
+
module Configuration
|
4
|
+
extend self
|
5
|
+
attr_writer :client_id, :client_secret, :authorization_url, :access_token_url
|
6
|
+
|
7
|
+
def client_id
|
8
|
+
@client_id || ENV['CLIENT_ID']
|
9
|
+
end
|
10
|
+
|
11
|
+
def client_secret
|
12
|
+
@client_secret || ENV['CLIENT_SECRET']
|
13
|
+
end
|
14
|
+
|
15
|
+
def authorization_url
|
16
|
+
@authorization_url || ENV['AUTHORIZATION_URL']
|
17
|
+
end
|
18
|
+
|
19
|
+
def access_token_url
|
20
|
+
@access_token_url || ENV['ACCESS_TOKEN_URL']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Runkeeper
|
2
|
+
|
3
|
+
class Connection
|
4
|
+
include HTTParty
|
5
|
+
base_uri "http://api.runkeeper.com"
|
6
|
+
format :json
|
7
|
+
debug_output
|
8
|
+
|
9
|
+
ACCEPT_HEADERS = {
|
10
|
+
:user => "application/vnd.com.runkeeper.User+json",
|
11
|
+
:fitness_activities => "application/vnd.com.runkeeper.FitnessActivityFeed+json",
|
12
|
+
:past_activities => "application/vnd.com.runkeeper.FitnessActivity+json",
|
13
|
+
:records => "application/vnd.com.runkeeper.Records+json",
|
14
|
+
:profile => "application/vnd.com.runkeeper.Profile+json",
|
15
|
+
:new_activity => "application/vnd.com.runkeeper.NewFitnessActivity+json"
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_reader :token, :user_response
|
19
|
+
|
20
|
+
def initialize(token)
|
21
|
+
@token = token
|
22
|
+
@auth_header = {"Authorization" => "Bearer #{token}"}
|
23
|
+
user
|
24
|
+
end
|
25
|
+
|
26
|
+
def user
|
27
|
+
@user_response = self.class.get('/user', :headers => headers(:user))
|
28
|
+
end
|
29
|
+
|
30
|
+
def profile
|
31
|
+
self.class.get(user_response['profile'], :headers => headers(:profile))
|
32
|
+
end
|
33
|
+
|
34
|
+
def fitness_activities
|
35
|
+
self.class.get(user_response['fitness_activities'], :headers => headers(:fitness_activities))
|
36
|
+
end
|
37
|
+
|
38
|
+
def past_activities
|
39
|
+
self.class.get(user_response['fitness_activities'], :headers => headers(:past_activities))
|
40
|
+
end
|
41
|
+
|
42
|
+
def records
|
43
|
+
self.class.get(user_response['records'], :headers => headers(:records))
|
44
|
+
end
|
45
|
+
|
46
|
+
def save_activity(hash)
|
47
|
+
self.class.post(user_response['fitness_activities'], :headers => headers(:new_activity), :body => hash.to_json)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.post_authorization(code, uri)
|
51
|
+
response = post(Configuration.access_token_url, :query => {
|
52
|
+
:grant_type => 'authorization_code',
|
53
|
+
:code => code,
|
54
|
+
:client_id => Configuration.client_id,
|
55
|
+
:client_secret => Configuration.client_secret,
|
56
|
+
:redirect_uri => uri
|
57
|
+
})
|
58
|
+
response['access_token']
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def headers(resource)
|
64
|
+
@auth_header.merge(
|
65
|
+
"Accept" => ACCEPT_HEADERS[resource],
|
66
|
+
"Content-Type" => ACCEPT_HEADERS[resource]
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
begin
|
2
|
+
require 'rack'
|
3
|
+
require 'launchy'
|
4
|
+
rescue LoadError
|
5
|
+
puts "rack and launchy are required"
|
6
|
+
end
|
7
|
+
|
8
|
+
class Runkeeper
|
9
|
+
class AuthorizationServer
|
10
|
+
class App
|
11
|
+
attr_reader :code, :env, :server
|
12
|
+
|
13
|
+
def initialize(server)
|
14
|
+
@server = server
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
@env = env
|
19
|
+
if params['code']
|
20
|
+
@code = params['code']
|
21
|
+
[200, { 'Content-Type' => 'text/html' }, ['Check the console for your token.']]
|
22
|
+
else
|
23
|
+
[404, { 'Content-Type' => 'text/html' }, ['']]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def params
|
28
|
+
@params ||= begin
|
29
|
+
ary = env['QUERY_STRING'].split('&').map {|s| s.split('=', 2)}
|
30
|
+
Hash[*ary.flatten]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def app
|
37
|
+
@app ||= App.new(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_code?
|
41
|
+
!code.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def code
|
45
|
+
app.code
|
46
|
+
end
|
47
|
+
|
48
|
+
def uri
|
49
|
+
@uri ||= URI.parse('http://localhost:8989')
|
50
|
+
end
|
51
|
+
|
52
|
+
def server
|
53
|
+
@server ||= Rack::Server.new :app => app, :Port => uri.port, :server => 'webrick'
|
54
|
+
end
|
55
|
+
|
56
|
+
def start
|
57
|
+
server.start
|
58
|
+
end
|
59
|
+
|
60
|
+
def stop
|
61
|
+
server.server.shutdown
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Connection
|
66
|
+
|
67
|
+
def self.authorize
|
68
|
+
server = Runkeeper::AuthorizationServer.new
|
69
|
+
uri = URI.parse(Configuration.authorization_url)
|
70
|
+
uri.query = "client_id=#{Configuration.client_id}&response_type=code&redirect_uri=#{server.uri}"
|
71
|
+
Launchy.open(uri.to_s)
|
72
|
+
t1 = Thread.new { server.start }
|
73
|
+
t1.join(0.5) until server.has_code?
|
74
|
+
server.stop
|
75
|
+
puts "Access Token: #{post_authorization(server.code, server.uri)}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Runkeeper
|
2
|
+
|
3
|
+
class User
|
4
|
+
attr_reader :connection, :token
|
5
|
+
|
6
|
+
def initialize(token)
|
7
|
+
@token = token
|
8
|
+
@connection = Connection.new(token)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Profile methods
|
12
|
+
[:name, :location, :athlete_type, :goal, :gender, :birthday, :elite, :profile, :small_picture, :normal_picture, :medium_picture, :large_picture].each do |method_name|
|
13
|
+
define_method method_name do
|
14
|
+
user_profile[method_name.to_s]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
alias elite? elite
|
18
|
+
|
19
|
+
def id
|
20
|
+
connection.user_response['userID']
|
21
|
+
end
|
22
|
+
|
23
|
+
def fitness_activities
|
24
|
+
connection.fitness_activities
|
25
|
+
end
|
26
|
+
|
27
|
+
def past_activities
|
28
|
+
connection.past_activities
|
29
|
+
end
|
30
|
+
|
31
|
+
def new_activity(attrs={})
|
32
|
+
Activity.new(connection, attrs)
|
33
|
+
end
|
34
|
+
|
35
|
+
def activity_url(activity)
|
36
|
+
File.join(profile, "activity", activity.id)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def user_profile
|
42
|
+
@user_profile ||= connection.profile
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/runkeeper.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "runkeeper/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "runkeeper"
|
7
|
+
s.version = Runkeeper::VERSION.dup
|
8
|
+
s.authors = ["Sandro Turriate"]
|
9
|
+
s.email = ["sandro.turriate@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/sandro/runkeeper"
|
11
|
+
s.summary = %q(Runkeeper API client.)
|
12
|
+
s.description = %q(Runkeeper API client.)
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.add_dependency "httparty"
|
19
|
+
s.add_development_dependency 'rack'
|
20
|
+
s.add_development_dependency 'launchy'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: runkeeper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sandro Turriate
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-07-09 00:00:00 -06:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: httparty
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0"
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: launchy
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
description: Runkeeper API client.
|
50
|
+
email:
|
51
|
+
- sandro.turriate@gmail.com
|
52
|
+
executables:
|
53
|
+
- token_generator
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- .dev
|
60
|
+
- .gitignore
|
61
|
+
- .rvmrc
|
62
|
+
- Gemfile
|
63
|
+
- README.markdown
|
64
|
+
- Rakefile
|
65
|
+
- bin/token_generator
|
66
|
+
- lib/runkeeper.rb
|
67
|
+
- lib/runkeeper/activity.rb
|
68
|
+
- lib/runkeeper/configuration.rb
|
69
|
+
- lib/runkeeper/connection.rb
|
70
|
+
- lib/runkeeper/token_generator.rb
|
71
|
+
- lib/runkeeper/user.rb
|
72
|
+
- lib/runkeeper/version.rb
|
73
|
+
- runkeeper.gemspec
|
74
|
+
has_rdoc: true
|
75
|
+
homepage: http://github.com/sandro/runkeeper
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: "0"
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: "0"
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.6.2
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: Runkeeper API client.
|
102
|
+
test_files: []
|
103
|
+
|