strava 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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