strava-ruby-client 0.1.0 → 0.2.0
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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +176 -15
- data/bin/strava-oauth-token.rb +47 -0
- data/lib/strava-ruby-client.rb +22 -1
- data/lib/strava/api/client.rb +150 -3
- data/lib/strava/api/cursor.rb +29 -0
- data/lib/strava/models/activity.rb +90 -4
- data/lib/strava/models/activity_zone.rb +13 -0
- data/lib/strava/models/athlete.rb +8 -0
- data/lib/strava/models/club.rb +26 -0
- data/lib/strava/models/comment.rb +12 -0
- data/lib/strava/models/gear.rb +11 -0
- data/lib/strava/models/lap.rb +26 -0
- data/lib/strava/models/map.rb +1 -0
- data/lib/strava/models/mixins/distance.rb +73 -0
- data/lib/strava/models/mixins/elevation.rb +46 -0
- data/lib/strava/models/mixins/time.rb +99 -0
- data/lib/strava/models/photo.rb +10 -0
- data/lib/strava/models/photos.rb +9 -0
- data/lib/strava/models/segment.rb +33 -0
- data/lib/strava/models/segment_effort.rb +24 -0
- data/lib/strava/models/similar_activities.rb +17 -0
- data/lib/strava/models/split.rb +29 -0
- data/lib/strava/models/timed_zone_range.rb +9 -0
- data/lib/strava/models/trend.rb +12 -0
- data/lib/strava/version.rb +1 -1
- metadata +35 -47
- data/.gitignore +0 -6
- data/.rspec +0 -2
- data/.rubocop.yml +0 -15
- data/.rubocop_todo.yml +0 -47
- data/.travis.yml +0 -9
- data/CONTRIBUTING.md +0 -125
- data/Dangerfile +0 -2
- data/Gemfile +0 -15
- data/RELEASING.md +0 -61
- data/Rakefile +0 -17
- data/bin/oauth-token.rb +0 -28
- data/spec/fixtures/strava/client_athlete.yml +0 -59
- data/spec/fixtures/strava/client_athlete_activities.yml +0 -121
- data/spec/fixtures/strava/oauth_token_authorization_code.yml +0 -53
- data/spec/fixtures/strava/oauth_token_invalid_client.yml +0 -50
- data/spec/fixtures/strava/oauth_token_invalid_code.yml +0 -50
- data/spec/fixtures/strava/oauth_token_refresh_token.yml +0 -52
- data/spec/spec_helper.rb +0 -10
- data/spec/strava/api/client_spec.rb +0 -36
- data/spec/strava/api/config_spec.rb +0 -22
- data/spec/strava/oauth/client_spec.rb +0 -120
- data/spec/strava/oauth/config_spec.rb +0 -24
- data/spec/strava/version_spec.rb +0 -7
- data/spec/strava/web/client_spec.rb +0 -9
- data/spec/strava/web/config_spec.rb +0 -4
- data/spec/support/shared/it_behaves_like_web_client.rb +0 -131
- data/spec/support/vcr.rb +0 -12
- data/strava-ruby-client.gemspec +0 -21
@@ -0,0 +1,29 @@
|
|
1
|
+
module Strava
|
2
|
+
module Api
|
3
|
+
class Cursor
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :client
|
7
|
+
attr_reader :path
|
8
|
+
attr_reader :params
|
9
|
+
|
10
|
+
def initialize(client, path, params = {})
|
11
|
+
@client = client
|
12
|
+
@path = path
|
13
|
+
@params = params
|
14
|
+
end
|
15
|
+
|
16
|
+
def each
|
17
|
+
next_page = 1
|
18
|
+
loop do
|
19
|
+
query = params.merge(page: next_page)
|
20
|
+
response = client.get(path, query)
|
21
|
+
break unless response.any?
|
22
|
+
|
23
|
+
yield response
|
24
|
+
next_page += 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,14 +1,15 @@
|
|
1
1
|
module Strava
|
2
2
|
module Models
|
3
3
|
class Activity < Model
|
4
|
+
include Mixins::Distance
|
5
|
+
include Mixins::Time
|
6
|
+
include Mixins::Elevation
|
7
|
+
|
4
8
|
property 'id'
|
5
9
|
property 'resource_state'
|
6
10
|
property 'athlete', transform_with: ->(v) { Strava::Models::Athlete.new(v) }
|
7
11
|
property 'name'
|
8
|
-
property '
|
9
|
-
property 'moving_time'
|
10
|
-
property 'elapsed_time'
|
11
|
-
property 'total_elevation_gain'
|
12
|
+
property 'description'
|
12
13
|
property 'type'
|
13
14
|
property 'workout_type'
|
14
15
|
property 'id'
|
@@ -52,6 +53,91 @@ module Strava
|
|
52
53
|
property 'total_photo_count'
|
53
54
|
property 'has_kudoed'
|
54
55
|
property 'suffer_score'
|
56
|
+
property 'calories'
|
57
|
+
property 'segment_efforts', transform_with: ->(v) { v.map { |r| Strava::Models::SegmentEffort.new(r) } }
|
58
|
+
property 'best_efforts', transform_with: ->(v) { v.map { |r| Strava::Models::SegmentEffort.new(r) } }
|
59
|
+
property 'photos', transform_with: ->(v) { Strava::Models::Photos.new(v) }
|
60
|
+
property 'similar_activities', transform_with: ->(v) { Strava::Models::SimilarActivities.new(v) }
|
61
|
+
property 'embed_token'
|
62
|
+
property 'available_zones'
|
63
|
+
property 'splits_metric', transform_with: ->(v) { v.map { |r| Strava::Models::Split::Metric.new(r) } }
|
64
|
+
property 'splits_standard', transform_with: ->(v) { v.map { |r| Strava::Models::Split::Standard.new(r) } }
|
65
|
+
property 'laps', transform_with: ->(v) { v.map { |r| Strava::Models::Lap.new(r) } }
|
66
|
+
property 'gear', transform_with: ->(v) { Strava::Models::Gear.new(v) }
|
67
|
+
property 'device_name'
|
68
|
+
|
69
|
+
def units
|
70
|
+
:metric
|
71
|
+
end
|
72
|
+
|
73
|
+
def distance_s
|
74
|
+
if type == 'Swim'
|
75
|
+
case units
|
76
|
+
when :metric then distance_in_meters_s
|
77
|
+
when :imperial then distance_in_yards_s
|
78
|
+
end
|
79
|
+
else
|
80
|
+
case units
|
81
|
+
when :metric then distance_in_kilometers_s
|
82
|
+
when :imperial then distance_in_miles_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def pace_s
|
88
|
+
case type
|
89
|
+
when 'Swim'
|
90
|
+
case units
|
91
|
+
when :metric then pace_per_100_meters_s
|
92
|
+
when :imperial then pace_per_100_yards_s
|
93
|
+
end
|
94
|
+
else
|
95
|
+
case units
|
96
|
+
when :metric then pace_per_kilometer_s
|
97
|
+
when :imperial then pace_per_mile_s
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def strava_url
|
103
|
+
"https://www.strava.com/activities/#{id}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def type_emoji
|
107
|
+
case type
|
108
|
+
when 'Run' then '🏃'
|
109
|
+
when 'Ride' then '🚴'
|
110
|
+
when 'Swim' then '🏊'
|
111
|
+
when 'Walk' then '🚶'
|
112
|
+
when 'AlpineSki' then '⛷️'
|
113
|
+
when 'BackcountrySki' then '🎿️'
|
114
|
+
# when 'Canoeing' then ''
|
115
|
+
# when 'Crossfit' then ''
|
116
|
+
when 'EBikeRide' then '🚴'
|
117
|
+
# when 'Elliptical' then ''
|
118
|
+
# when 'Hike' then ''
|
119
|
+
when 'IceSkate' then '⛸️'
|
120
|
+
# when 'InlineSkate' then ''
|
121
|
+
# when 'Kayaking' then ''
|
122
|
+
# when 'Kitesurf' then ''
|
123
|
+
# when 'NordicSki' then ''
|
124
|
+
when 'RockClimbing' then '🧗'
|
125
|
+
when 'RollerSki' then ''
|
126
|
+
when 'Rowing' then '🚣'
|
127
|
+
when 'Snowboard' then '🏂'
|
128
|
+
# when 'Snowshoe' then ''
|
129
|
+
# when 'StairStepper' then ''
|
130
|
+
# when 'StandUpPaddling' then ''
|
131
|
+
when 'Surfing' then '🏄'
|
132
|
+
when 'VirtualRide' then '🚴'
|
133
|
+
when 'VirtualRun' then '🏃'
|
134
|
+
when 'WeightTraining' then '🏋️'
|
135
|
+
# when 'Windsurf' then ''
|
136
|
+
when 'Wheelchair' then '♿'
|
137
|
+
# when 'Workout' then ''
|
138
|
+
# when 'Yoga'' then ''
|
139
|
+
end
|
140
|
+
end
|
55
141
|
end
|
56
142
|
end
|
57
143
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Strava
|
2
|
+
module Models
|
3
|
+
class ActivityZone < Model
|
4
|
+
property 'score'
|
5
|
+
property 'distribution_buckets', transform_with: ->(v) { v.map { |r| Strava::Models::TimedZoneRange.new(r) } }
|
6
|
+
property 'type'
|
7
|
+
property 'resource_state'
|
8
|
+
property 'sensor_based'
|
9
|
+
property 'points'
|
10
|
+
property 'custom_zones'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -20,6 +20,14 @@ module Strava
|
|
20
20
|
property 'follower'
|
21
21
|
property 'friend'
|
22
22
|
property 'summit'
|
23
|
+
|
24
|
+
def name
|
25
|
+
[firstname, lastname].compact.join(' ') if firstname || lastname
|
26
|
+
end
|
27
|
+
|
28
|
+
def strava_url
|
29
|
+
"https://www.strava.com/athletes/#{username || id}"
|
30
|
+
end
|
23
31
|
end
|
24
32
|
end
|
25
33
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Strava
|
2
|
+
module Models
|
3
|
+
class Club < Model
|
4
|
+
property 'id'
|
5
|
+
property 'resource_state'
|
6
|
+
property 'name'
|
7
|
+
property 'profile_medium'
|
8
|
+
property 'profile'
|
9
|
+
property 'cover_photo'
|
10
|
+
property 'cover_photo_small'
|
11
|
+
property 'sport_type'
|
12
|
+
property 'city'
|
13
|
+
property 'state'
|
14
|
+
property 'country'
|
15
|
+
property 'private'
|
16
|
+
property 'member_count'
|
17
|
+
property 'featured'
|
18
|
+
property 'verified'
|
19
|
+
property 'url'
|
20
|
+
|
21
|
+
def strava_url
|
22
|
+
"https://www.strava.com/clubs/#{url || id}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Strava
|
2
|
+
module Models
|
3
|
+
class Comment < Model
|
4
|
+
property 'id'
|
5
|
+
property 'activity_id'
|
6
|
+
property 'resource_state'
|
7
|
+
property 'text'
|
8
|
+
property 'created_at', transform_with: ->(v) { Time.parse(v) }
|
9
|
+
property 'athlete', transform_with: ->(v) { Strava::Models::Athlete.new(v) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Strava
|
2
|
+
module Models
|
3
|
+
class Lap < Model
|
4
|
+
include Mixins::Time
|
5
|
+
include Mixins::Distance
|
6
|
+
include Mixins::Elevation
|
7
|
+
|
8
|
+
property 'id'
|
9
|
+
property 'resource_state'
|
10
|
+
property 'name'
|
11
|
+
property 'activity', transform_with: ->(v) { Strava::Models::Activity.new(v) }
|
12
|
+
property 'athlete', transform_with: ->(v) { Strava::Models::Athlete.new(v) }
|
13
|
+
property 'start_date', transform_with: ->(v) { Time.parse(v) }
|
14
|
+
property 'start_date_local', transform_with: ->(v) { Time.parse(v) }
|
15
|
+
property 'start_index'
|
16
|
+
property 'end_index'
|
17
|
+
property 'average_speed'
|
18
|
+
property 'max_speed'
|
19
|
+
property 'average_heartrate'
|
20
|
+
property 'max_heartrate'
|
21
|
+
property 'lap_index'
|
22
|
+
property 'split'
|
23
|
+
property 'pace_zone'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/strava/models/map.rb
CHANGED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Strava
|
2
|
+
module Models
|
3
|
+
module Mixins
|
4
|
+
module Distance
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
property 'distance'
|
9
|
+
end
|
10
|
+
|
11
|
+
def distance_in_meters
|
12
|
+
case units
|
13
|
+
when :metric then distance
|
14
|
+
when :imperial then distance / 3.28084
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def distance_in_feet
|
19
|
+
case units
|
20
|
+
when :metric then distance * 3.28084
|
21
|
+
when :imperial then distance
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def distance_in_miles
|
26
|
+
case units
|
27
|
+
when :metric then distance_in_meters * 0.00062137
|
28
|
+
when :imperial then distance_in_feet / 5280
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def distance_in_miles_s
|
33
|
+
return unless distance && distance.positive?
|
34
|
+
|
35
|
+
format('%gmi', format('%.2f', distance_in_miles))
|
36
|
+
end
|
37
|
+
|
38
|
+
def distance_in_yards
|
39
|
+
distance_in_meters * 1.09361
|
40
|
+
end
|
41
|
+
|
42
|
+
def distance_in_yards_s
|
43
|
+
return unless distance && distance.positive?
|
44
|
+
|
45
|
+
format('%gyd', format('%.1f', distance_in_yards))
|
46
|
+
end
|
47
|
+
|
48
|
+
def distance_in_meters_s
|
49
|
+
return unless distance && distance.positive?
|
50
|
+
|
51
|
+
format('%gm', format('%d', distance_in_meters))
|
52
|
+
end
|
53
|
+
|
54
|
+
def distance_in_kilometers
|
55
|
+
distance_in_meters / 1000
|
56
|
+
end
|
57
|
+
|
58
|
+
def distance_in_kilometers_s
|
59
|
+
return unless distance && distance.positive?
|
60
|
+
|
61
|
+
format('%gkm', format('%.2f', distance_in_kilometers))
|
62
|
+
end
|
63
|
+
|
64
|
+
def distance_s
|
65
|
+
case units
|
66
|
+
when :metric then distance_in_kilometers_s
|
67
|
+
when :imperial then distance_in_miles_s
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Strava
|
2
|
+
module Models
|
3
|
+
module Mixins
|
4
|
+
module Elevation
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
property 'total_elevation_gain'
|
9
|
+
end
|
10
|
+
|
11
|
+
def total_elevation_gain_in_feet
|
12
|
+
case units
|
13
|
+
when :metric then total_elevation_gain * 3.28084
|
14
|
+
when :imperial then total_elevation_gain
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def total_elevation_gain_in_meters
|
19
|
+
case units
|
20
|
+
when :metric then total_elevation_gain
|
21
|
+
when :imperial then total_elevation_gain / 3.28084
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def total_elevation_gain_in_meters_s
|
26
|
+
return unless total_elevation_gain && total_elevation_gain.positive?
|
27
|
+
|
28
|
+
format('%gm', format('%.1f', total_elevation_gain_in_meters))
|
29
|
+
end
|
30
|
+
|
31
|
+
def total_elevation_gain_in_feet_s
|
32
|
+
return unless total_elevation_gain && total_elevation_gain.positive?
|
33
|
+
|
34
|
+
format('%gft', format('%.1f', total_elevation_gain_in_feet))
|
35
|
+
end
|
36
|
+
|
37
|
+
def total_elevation_gain_s
|
38
|
+
case units
|
39
|
+
when :metric then total_elevation_gain_in_meters_s
|
40
|
+
when :imperial then total_elevation_gain_in_feet_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Strava
|
2
|
+
module Models
|
3
|
+
module Mixins
|
4
|
+
module Time
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
property 'moving_time'
|
9
|
+
property 'elapsed_time'
|
10
|
+
property 'average_speed'
|
11
|
+
end
|
12
|
+
|
13
|
+
# always in meters per second, even in imperial splits
|
14
|
+
def average_speed_meters_per_second
|
15
|
+
elapsed_time
|
16
|
+
end
|
17
|
+
|
18
|
+
def moving_time_in_hours_s
|
19
|
+
time_in_hours_s moving_time
|
20
|
+
end
|
21
|
+
|
22
|
+
def elapsed_time_in_hours_s
|
23
|
+
time_in_hours_s elapsed_time
|
24
|
+
end
|
25
|
+
|
26
|
+
def pace_per_mile_s
|
27
|
+
convert_meters_per_second_to_pace average_speed, :mi
|
28
|
+
end
|
29
|
+
|
30
|
+
def pace_per_100_yards_s
|
31
|
+
convert_meters_per_second_to_pace average_speed, :"100yd"
|
32
|
+
end
|
33
|
+
|
34
|
+
def pace_per_100_meters_s
|
35
|
+
convert_meters_per_second_to_pace average_speed, :"100m"
|
36
|
+
end
|
37
|
+
|
38
|
+
def pace_per_kilometer_s
|
39
|
+
convert_meters_per_second_to_pace average_speed, :km
|
40
|
+
end
|
41
|
+
|
42
|
+
def kilometer_per_hour_s
|
43
|
+
return unless average_speed && average_speed.positive?
|
44
|
+
|
45
|
+
format('%.1fkm/h', average_speed * 3.6)
|
46
|
+
end
|
47
|
+
|
48
|
+
def miles_per_hour_s
|
49
|
+
return unless average_speed && average_speed.positive?
|
50
|
+
|
51
|
+
format('%.1fmph', average_speed * 2.23694)
|
52
|
+
end
|
53
|
+
|
54
|
+
def speed_s
|
55
|
+
case units
|
56
|
+
when :metric then kilometer_per_hour_s
|
57
|
+
when :imperial then miles_per_hour_s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def time_in_hours_s(time)
|
64
|
+
return unless time
|
65
|
+
|
66
|
+
hours = time / 3600 % 24
|
67
|
+
minutes = time / 60 % 60
|
68
|
+
seconds = time % 60
|
69
|
+
[
|
70
|
+
hours.to_i.positive? ? format('%dh', hours) : nil,
|
71
|
+
minutes.to_i.positive? ? format('%dm', minutes) : nil,
|
72
|
+
seconds.to_i.positive? ? format('%ds', seconds) : nil
|
73
|
+
].compact.join
|
74
|
+
end
|
75
|
+
|
76
|
+
# Convert speed (m/s) to pace (min/mile or min/km) in the format of 'x:xx'
|
77
|
+
# http://yizeng.me/2017/02/25/convert-speed-to-pace-programmatically-using-ruby
|
78
|
+
def convert_meters_per_second_to_pace(speed, unit = :mi)
|
79
|
+
return unless speed && speed.positive?
|
80
|
+
|
81
|
+
total_seconds = case unit
|
82
|
+
when :mi then 1609.344 / speed
|
83
|
+
when :km then 1000 / speed
|
84
|
+
when :"100yd" then 91.44 / speed
|
85
|
+
when :"100m" then 100 / speed
|
86
|
+
end
|
87
|
+
minutes, seconds = total_seconds.divmod(60)
|
88
|
+
seconds = seconds.round
|
89
|
+
if seconds == 60
|
90
|
+
minutes += 1
|
91
|
+
seconds = 0
|
92
|
+
end
|
93
|
+
seconds = seconds < 10 ? "0#{seconds}" : seconds.to_s
|
94
|
+
"#{minutes}m#{seconds}s/#{unit}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|