strava 0.1.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.
@@ -0,0 +1,224 @@
1
+ module Strava
2
+ # Class to represent Strava Activity
3
+ # @see https://strava.github.io/api/v3/activities/ Strava Activity API Docs
4
+ class Activity < Base
5
+ ATTRIBUTES = [:external_id, :upload_id, :athlete, :name, :description, :distance, :moving_time, :elapsed_time, :total_elevation_gain, :elev_high, :elev_low, :type, :start_date, :start_date_local, :timezone, :start_latlng, :end_latlng, :location_city, :location_state, :location_country, :achievement_count, :kudos_count, :comment_count, :athlete_count, :photo_count, :total_photo_count, :map, :trainer, :commute, :manual, :private, :device_name, :embed_token, :flagged, :workout_type, :gear_id, :average_speed, :max_speed, :average_cadence, :average_temp, :average_watts, :max_watts, :weighted_average_watts, :kilojoules, :device_watts, :has_heartrate, :average_heartrate, :max_heartrate, :calories, :suffer_score, :has_kudoed, :splits_metric, :splits_standard, :best_efforts] # activity attributes, all have getter method
6
+ attr_reader *ATTRIBUTES
7
+ attr_reader :photos_info, :gear
8
+
9
+ # Set up instance variables upon instantiation.
10
+ #
11
+ # @abstract
12
+ # @return [void]
13
+ private def set_ivars
14
+ @kudos = {}
15
+ @comments = {}
16
+ @photos = {}
17
+ @laps = {}
18
+ @segment_efforts = {}
19
+ @related = {}
20
+ @streams = StreamSet.new
21
+ end
22
+
23
+ # Updates activity with passed data attributes.
24
+ #
25
+ # @param data [Hash] data hash containing activity data
26
+ # @return [self]
27
+ private def update(data, **opts)
28
+ @response = data
29
+ @id = data["id"]
30
+ @resource_state = data['resource_state']
31
+ @photos_info = data['photos']
32
+ @laps_info = data['laps']
33
+ @gear = Gear.new(data['gear'], client: client) if data['gear']
34
+ ATTRIBUTES.each do |attr|
35
+ instance_variable_set("@#{attr}", data[attr.to_s])
36
+ end
37
+
38
+ parse_data(@segment_efforts, data["segment_efforts"], klass: SegmentEffort, client: @client) if data["segment_efforts"]
39
+ self
40
+ end
41
+
42
+ def segment_efforts
43
+ @segment_efforts.values
44
+ end
45
+
46
+ def kudos(per_page: nil, page: nil)
47
+ if page || per_page
48
+ get_kudos(per_page: per_page, page: page)
49
+ else
50
+ get_kudos if @kudos.empty?
51
+ @kudos.values
52
+ end
53
+ end
54
+
55
+ def comments(per_page: nil, page: nil)
56
+ if page || per_page
57
+ get_comments(per_page: per_page, page: page)
58
+ else
59
+ get_comments if @comments.empty?
60
+ @comments.values
61
+ end
62
+ end
63
+
64
+ def photos(per_page: nil, page: nil)
65
+ if page || per_page
66
+ get_photos(per_page: per_page, page: page)
67
+ else
68
+ get_photos if @photos.empty?
69
+ @photos.values
70
+ end
71
+ end
72
+
73
+ # Activities that were matched as “with this group”. The number equals activity.athlete_count-1. Pagination is supported.
74
+ # @return [Strava::Activity] Related activities
75
+ def related(per_page: nil, page: nil)
76
+ if page || per_page
77
+ get_related(per_page: per_page, page: page)
78
+ else
79
+ get_related if @related.empty?
80
+ @related.values
81
+ end
82
+ end
83
+
84
+ def streams(types = [:time, :distance, :latlng], **params)
85
+ get_streams(types, **params) if @streams.empty?
86
+ @streams
87
+ end
88
+
89
+ def zones
90
+ get_zones unless @zones
91
+ @zones
92
+ end
93
+
94
+ def laps
95
+ get_laps if @laps.empty?
96
+ @laps.values
97
+ end
98
+
99
+ def comment(message)
100
+ res = client.post(path_comments, text: message).to_h
101
+ end
102
+
103
+ def kudo
104
+ res = client.post(path_kudos).to_h
105
+ end
106
+
107
+
108
+ def get_details
109
+ return self if detailed?
110
+ res = client.get(path_base).to_h
111
+ update(res)
112
+ end
113
+ def get_kudos(per_page: nil, page: nil)
114
+ res = client.get(path_kudos, per_page: per_page, page: page).to_a
115
+ parse_data(@kudos, res, klass: Athlete, client: @client)
116
+ end
117
+ def get_comments(per_page: nil, page: nil)
118
+ res = client.get(path_comments, per_page: per_page, page: page).to_a
119
+ parse_data(@comments, res, klass: Comment, client: @client)
120
+ end
121
+ def get_photos(per_page: nil, page: nil)
122
+ res = client.get(path_photos, per_page: per_page, page: page).to_a
123
+ parse_data(@photos, res, klass: Photo, client: @client)
124
+ end
125
+ def get_related(per_page: nil, page: nil)
126
+ res = client.get(path_related, per_page: per_page, page: page).to_a
127
+ parse_data(@related, res, klass: Activity, client: @client)
128
+ end
129
+ def get_streams(types = '', **params)
130
+ res = client.get(path_streams + types.join(','), **params).to_a
131
+ @streams.update(res)
132
+ end
133
+ def get_zones
134
+ res = client.get(path_zones).to_a
135
+ @zones = res
136
+ end
137
+ def get_laps
138
+ res = client.get(path_laps).to_a
139
+ parse_data(@laps, res, klass: Lap, client: @client)
140
+ end
141
+
142
+
143
+ def path_base
144
+ "activities/#{id}"
145
+ end
146
+ def path_kudos
147
+ "#{path_base}/kudos"
148
+ end
149
+ def path_comments
150
+ "#{path_base}/comments"
151
+ end
152
+ def path_photos
153
+ "#{path_base}/photos?photo_sources=true"
154
+ end
155
+ def path_related
156
+ "#{path_base}/related"
157
+ end
158
+ def path_streams
159
+ "#{path_base}/streams/"
160
+ end
161
+ def path_zones
162
+ "#{path_base}/zones"
163
+ end
164
+ def path_laps
165
+ "#{path_base}/laps"
166
+ end
167
+ end
168
+ end
169
+
170
+ activity_types = [
171
+ 'Ride',
172
+ 'Kitesurf',
173
+ 'Run',
174
+ 'NordicSki',
175
+ 'Swim',
176
+ 'RockClimbing',
177
+ 'Hike',
178
+ 'RollerSki',
179
+ 'Walk',
180
+ 'Rowing',
181
+ 'AlpineSki',
182
+ 'Snowboard',
183
+ 'BackcountrySki',
184
+ 'Snowshoe',
185
+ 'Canoeing',
186
+ 'StairStepper',
187
+ 'Crossfit',
188
+ 'StandUpPaddling',
189
+ 'EBikeRide',
190
+ 'Surfing',
191
+ 'Elliptical',
192
+ 'VirtualRide',
193
+ 'IceSkate',
194
+ 'WeightTraining',
195
+ 'InlineSkate',
196
+ 'Windsurf',
197
+ 'Kayaking',
198
+ 'Workout',
199
+ 'Yoga',
200
+ ]
201
+
202
+ __END__
203
+
204
+ ca = Strava::Athlete.current_athlete;
205
+ act = ca.activities.first;
206
+ act.get_details
207
+ act.comments
208
+
209
+
210
+ ca = Strava::Athlete.current_athlete;
211
+ act = ca.activities.detect{|a| a.response['kudos_count'] > 0 }
212
+ act.get_kudos
213
+ ca = Strava::Athlete.current_athlete;
214
+ ca.activities;
215
+ ca.activities(page: 2);
216
+ ca.activities(page: 3);
217
+ ca.activities(page: 4);
218
+ act = ca.activities.detect{|a| a.response['comment_count'] > 0 && a.response['kudos_count'] > 0 }
219
+ act.kudos
220
+ act.photos
221
+ act.comments
222
+ act = ca.activities.detect{|a| a.response['comment_count'] > 0 }
223
+ act.get_comments
224
+
@@ -0,0 +1,9 @@
1
+ require 'httparty'
2
+
3
+ module Strava
4
+ module Adapters
5
+ module HTTPartyAdapter
6
+ #
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,378 @@
1
+ module Strava
2
+ # Strava Athlete class. For the most part, API interaction deals with the currently authenticated athlete.
3
+ #
4
+ # There are mixins available to provide convenient ways to instantiate an athlete, see {Strava.model} for more information.
5
+ #
6
+ # Usage:
7
+ #
8
+ # ca = Strava::Athlete.current_athlete(access_token)
9
+ # ca.firstname # => 'John'
10
+ # ca.lastname # => 'Applestrava'
11
+ # ca.profile # => 'http://pics.com/227615/large.jpg'
12
+ #
13
+ # @see https://strava.github.io/api/v3/athlete/ Strava Docs - Athlete
14
+ class Athlete < Base
15
+
16
+ attr_reader :firstname, :lastname, :profile_medium, :profile, :city, :state, :country, :sex, :friend, :follower, :premium, :created_at, :updated_at, :follower_count, :friend_count, :mutual_friend_count, :athlete_type, :date_preference, :measurement_preference, :email, :ftp, :weight, :bikes, :shoes
17
+
18
+ # Set up instance variables upon instantiation.
19
+ #
20
+ # @abstract
21
+ # @return [void]
22
+ private def set_ivars
23
+ @activities = {}
24
+ @friends_activities = {}
25
+ @routes = {}
26
+ @connections = {}
27
+ @both_following = {}
28
+ @koms = {}
29
+ @gear = {}
30
+ @clubs = {}
31
+ @heatmaps = {}
32
+ @starred_segments = {}
33
+
34
+ @friends = []
35
+ @followers = []
36
+ end
37
+
38
+ def initialize(data, client: nil, token: nil, **opts)
39
+ @current = !!opts[:current]
40
+ super
41
+ end
42
+
43
+ # Update an existing athlete.
44
+ # Used by other methods in the gem.
45
+ # Should not be used directly.
46
+ #
47
+ # @private
48
+ # @param data [Hash] data to update the athlete with
49
+ # @return [self]
50
+ def update(data, **opts)
51
+ @id = data["id"]
52
+ @username = data["username"]
53
+ @resource_state = data["resource_state"]
54
+ @firstname = data["firstname"]
55
+ @lastname = data["lastname"]
56
+ @city = data["city"]
57
+ @state = data["state"]
58
+ @country = data["country"]
59
+ @sex = data["sex"]
60
+ @premium = data["premium"]
61
+ @created_at = data["created_at"]
62
+ @updated_at = data["updated_at"]
63
+ @badge_type_id = data["badge_type_id"]
64
+ @profile_medium = data["profile_medium"]
65
+ @profile = data["profile"]
66
+ @friend = data["friend"]
67
+ @follower = data["follower"]
68
+ @follower_count = data["follower_count"]
69
+ @friend_count = data["friend_count"]
70
+ @mutual_friend_count = data["mutual_friend_count"]
71
+ @athlete_type = data["athlete_type"]
72
+ @date_preference = data["date_preference"]
73
+ @measurement_preference = data["measurement_preference"]
74
+ @email = data["email"]
75
+ @ftp = data["ftp"]
76
+ @weight = data["weight"]
77
+ @bikes = parse_data(@gear, data['bikes'], klass: Gear, client: @client)
78
+ @shoes = parse_data(@gear, data['shoes'], klass: Gear, client: @client)
79
+
80
+ parse_data(@clubs, data['clubs'], klass: Club, client: @client)
81
+
82
+ self
83
+ end
84
+
85
+ # Whether this is the currently authenticated athlete.
86
+ # Strava's API has reduced access for athletes other than the currently authenticated one.
87
+ #
88
+ # @return [Boolean]
89
+ def current?
90
+ @current
91
+ end
92
+
93
+ ## Non athlete specific methods
94
+
95
+ # Retrieve running races.
96
+ # This is not related to the current athlete, but does require an access token.
97
+ #
98
+ # Also available via {RunningRace.list_races}
99
+ #
100
+ # @param year [Integer] Year to retrieve races for
101
+ def list_races(year = Time.now.year)
102
+ client.list_races(year)
103
+ end
104
+
105
+ # Segment Explorer will find popular segments within a given area.
106
+ # Requires a comma separated list of bounding box corners.
107
+ #
108
+ # Also available via {Segment.explorer}
109
+ #
110
+ # @param bounds [String] ‘sw.lat,sw.lng,ne.lat,ne.lng’ or alternatively, ‘south,west,north,east’
111
+ def segment_explorer(bounds = '37.821362,-122.505373,37.842038,-122.465977')
112
+ client.segment_explorer(bounds)
113
+ end
114
+
115
+ # Gear list. Includes shoes and bikes.
116
+ #
117
+ # @return [Array<Gear>] Athlete's gear
118
+ def gear
119
+ @gear.values
120
+ end
121
+
122
+ # Activities belonging to this user.
123
+ # If no activities have been retrieved, an API call will be made.
124
+ # If activities exist, they will be returned.
125
+ # Pagination is supported, and will always trigger an API call.
126
+ # Paged requests will return the activities from that page.
127
+ # Non-paged calls will return all downloaded activities.
128
+
129
+ def activities(per_page: nil, page: nil)
130
+ if page || per_page
131
+ get_activities(per_page: per_page, page: page)
132
+ else
133
+ get_activities if @activities.empty?
134
+ @activities.values
135
+ end
136
+ end
137
+
138
+ # not working, not listed in docs
139
+ # def heatmaps(per_page: nil, page: nil)
140
+ # if page || per_page
141
+ # get_heatmaps(per_page: per_page, page: page)
142
+ # else
143
+ # get_heatmaps if @heatmaps.empty?
144
+ # @heatmaps.values
145
+ # end
146
+ # end
147
+
148
+ def friends_activities(per_page: nil, page: nil, before: nil)
149
+ if page || per_page
150
+ get_friends_activities(per_page: per_page, page: page)
151
+ else
152
+ get_friends_activities if @friends_activities.empty?
153
+ @friends_activities.values
154
+ end
155
+ end
156
+
157
+ def stats
158
+ @stats || get_stats
159
+ end
160
+ alias :totals :stats
161
+
162
+ def zones
163
+ @zones || get_zones
164
+ end
165
+
166
+ def koms(per_page: nil, page: nil)
167
+ if page || per_page
168
+ get_koms(per_page: per_page, page: page)
169
+ else
170
+ get_koms if @koms.empty?
171
+ @koms.values
172
+ end
173
+ end
174
+
175
+ def clubs
176
+ get_clubs if @clubs.empty?
177
+ @clubs.values
178
+ end
179
+
180
+ def routes(per_page: nil, page: nil)
181
+ if page || per_page
182
+ get_routes(per_page: per_page, page: page)
183
+ else
184
+ get_routes if @routes.empty?
185
+ @routes.values
186
+ end
187
+ end
188
+
189
+ def starred_segments(per_page: nil, page: nil)
190
+ if page || per_page
191
+ get_starred_segments(per_page: per_page, page: page)
192
+ else
193
+ get_starred_segments if @starred_segments.empty?
194
+ @starred_segments.values
195
+ end
196
+ end
197
+
198
+ def friends(per_page: nil, page: nil)
199
+ # paginate('friends', struct: Array, per_page: per_page, page: page)
200
+ if page || per_page
201
+ get_friends(per_page: per_page, page: page)
202
+ else
203
+ get_friends if @friends.empty?
204
+ @friends.uniq(&:id)
205
+ end
206
+ end
207
+
208
+ def followers(per_page: nil, page: nil)
209
+ # paginate('followers', struct: Array, per_page: per_page, page: page)
210
+ if page || per_page
211
+ get_followers(per_page: per_page, page: page)
212
+ else
213
+ get_followers if @followers.empty?
214
+ @followers.uniq(&:id)
215
+ end
216
+ end
217
+
218
+ def both_following(other_athlete, per_page: nil, page: nil)
219
+ other_id = other_athlete.is_a?(Athlete) ? other_athlete.id : other_athlete
220
+ if page || per_page
221
+ get_both_following(other_id, per_page: per_page, page: page)
222
+ else
223
+ get_both_following(other_id) if @both_following[other_id].nil?
224
+ @both_following[other_id]
225
+ end
226
+ end
227
+
228
+ ## retrieval methods
229
+ def get_details
230
+ return self if detailed?
231
+ res = client.get(path_base).to_h
232
+ update(res)
233
+ end
234
+
235
+ private def get_activities(per_page: nil, page: nil)
236
+ res = client.get(path_activities, per_page: per_page, page: page).to_a
237
+ parse_data(@activities, res, klass: Activity, client: @client)
238
+ end
239
+
240
+ # not working, not listed in docs
241
+ # private def get_heatmaps(per_page: nil, page: nil)
242
+ # res = client.get(path_heatmaps, per_page: per_page, page: page).to_a
243
+ # parse_data(@heatmaps, res, klass: Base, client: @client)
244
+ # end
245
+
246
+ private def get_friends_activities(per_page: nil, page: nil, before: nil)
247
+ res = client.get(path_friends_activities, per_page: per_page, page: page, before: before).to_a
248
+ parse_data(@friends_activities, res, klass: Activity, client: @client)
249
+ end
250
+
251
+ private def get_stats
252
+ @stats = client.get(path_stats).to_h
253
+ end
254
+
255
+ private def get_zones
256
+ @zones = client.get(path_zones).to_h
257
+ end
258
+
259
+ private def get_koms(per_page: nil, page: nil)
260
+ res = client.get(path_koms, per_page: per_page, page: page).to_a
261
+ parse_data(@koms, res, klass: SegmentEffort, client: @client)
262
+ end
263
+
264
+ private def get_clubs(per_page: nil, page: nil)
265
+ res = client.get(path_clubs, per_page: per_page, page: page).to_a
266
+ parse_data(@clubs, res, klass: Club, client: @client)
267
+ end
268
+
269
+ private def get_routes(per_page: nil, page: nil)
270
+ res = client.get(path_routes, per_page: per_page, page: page).to_a
271
+ parse_data(@routes, res, klass: Route, client: @client)
272
+ end
273
+
274
+ private def get_starred_segments(per_page: nil, page: nil)
275
+ res = client.get(path_starred_segments, per_page: per_page, page: page).to_a
276
+ parse_data(@starred_segments, res, klass: Segment, client: @client)
277
+ end
278
+
279
+ private def get_friends(per_page: nil, page: nil)
280
+ res = client.get(path_friends, per_page: per_page, page: page).to_a
281
+ data = parse_data(@connections, res, klass: self.class, client: @client)
282
+ @friends.concat(data)
283
+ data
284
+ end
285
+
286
+ private def get_followers(per_page: nil, page: nil)
287
+ res = client.get(path_followers, per_page: per_page, page: page).to_a
288
+ data = parse_data(@connections, res, klass: self.class, client: @client)
289
+ @followers.concat(data)
290
+ data
291
+ end
292
+
293
+ private def get_both_following(other_id, per_page: nil, page: nil)
294
+ res = client.get(path_both_following(other_id), per_page: per_page, page: page).to_a
295
+ @both_following[other_id] = parse_data(@connections, res, klass: self.class, client: @client)
296
+ end
297
+
298
+ def path_base
299
+ current? ? 'athlete' : "athletes/#{id}"
300
+ end
301
+
302
+ private def path_activities
303
+ current? ? "athlete/activities" : raise('Need to be current athlete')
304
+ end
305
+
306
+ # not working, not listed in docs
307
+ # http://strava.github.io/api/v3/heatmaps/
308
+ # private def path_heatmaps
309
+ # current? ? "athlete/heatmaps" : raise('Need to be current athlete')
310
+ # end
311
+
312
+ private def path_friends_activities
313
+ current? ? "activities/following" : raise('Need to be current athlete')
314
+ end
315
+
316
+ private def path_stats
317
+ current? ? "athletes/#{id}/stats" : raise('Need to be current athlete')
318
+ end
319
+
320
+ private def path_zones
321
+ current? ? "athlete/zones" : raise('Need to be current athlete')
322
+ end
323
+
324
+ private def path_koms
325
+ "athletes/#{id}/koms"
326
+ end
327
+
328
+ private def path_clubs
329
+ current? ? "athlete/clubs" : raise('Need to be current athlete')
330
+ end
331
+
332
+ private def path_routes
333
+ "athletes/#{id}/routes"
334
+ end
335
+
336
+ private def path_starred_segments
337
+ current? ? "segments/starred" : "athletes/#{id}/segments/starred"
338
+ end
339
+
340
+ private def path_followers
341
+ current? ? "athlete/followers" : "athletes/#{id}/followers"
342
+ end
343
+ private def path_friends
344
+ current? ? "athlete/friends" : "athletes/#{id}/friends"
345
+ end
346
+ private def path_both_following(other_id)
347
+ current? ? "athletes/#{other_id}/friends" : raise('Need to be current athlete')
348
+ end
349
+
350
+ private
351
+
352
+ class << self
353
+ # Retrieve the currently authenticated athlete.
354
+ # Will make request to Strava API.
355
+ #
356
+ # @param token [String] access token for athlete
357
+ # @return [Athlete] currently authenticated athlete
358
+ def current_athlete(token = "ca16caf5b4cb8b57016541f470ae6b3a8aea2252")
359
+ client = Client.new(token)
360
+ res = client.get('athlete').to_h
361
+ new(res, client: client, current: true)
362
+ end
363
+
364
+ # def path(endpoint)
365
+ # paths[endpoint]
366
+ # end
367
+ # def paths
368
+ # @paths ||= {
369
+ # current: 'athlete',
370
+ # }.freeze
371
+ # end
372
+ end
373
+ end
374
+ end
375
+
376
+ __END__
377
+
378
+ ca = Strava::Athlete.current_athlete