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,86 @@
1
+ module Strava
2
+ # Base class for Strava objects.
3
+ # Handles setting up the object, mainly data and a client.
4
+ #
5
+ # @abstract
6
+ class Base
7
+ attr_reader :response, :client, :id
8
+
9
+ def initialize(data, client: nil, token: nil, **opts)
10
+ raise 'missing client or access token' unless (client || token)
11
+ @client = client || Client.new(token)
12
+ if data.is_a?(Hash)
13
+ @id = data['id']
14
+ set_ivars
15
+ update(data, **opts)
16
+ else
17
+ @id = data
18
+ set_ivars
19
+ end
20
+ end
21
+
22
+ # Parse incoming data.
23
+ # Should be defined by subclasses.
24
+ #
25
+ # @abstract
26
+ def update(data, **opts)
27
+ @response = data
28
+ @resource_state = data['resource_state']
29
+ self
30
+ end
31
+
32
+ # Set up instance variables upon instantiation.
33
+ # Should be defined by subclasses.
34
+ # May not always be necessary.
35
+ #
36
+ # @abstract
37
+ # @return [void]
38
+ private def set_ivars
39
+ # this should be defined by subclasses
40
+ end
41
+
42
+ private def parse_data(existing, data, klass: nil, **opts)
43
+ existing ||= {}
44
+ case data
45
+ when [], {}
46
+ []
47
+ when Array
48
+ data.map do |hash|
49
+ current = existing[hash['id']]
50
+ if current
51
+ current.send(:update, hash, **opts)
52
+ else
53
+ current = klass.new(hash, **opts)
54
+ existing[current.id] = current
55
+ end
56
+ existing[current.id]
57
+ end
58
+ when Hash
59
+ existing[data['id']] = klass.new(data, **opts)
60
+ else
61
+ # raise
62
+ end
63
+ end
64
+
65
+ def resource_state
66
+ self.class.resource_states[@resource_state]
67
+ end
68
+
69
+ def summary?
70
+ @resource_state == 2
71
+ end
72
+
73
+ def detailed?
74
+ @resource_state == 3
75
+ end
76
+
77
+ def self.resource_states
78
+ @resource_states ||= {
79
+ 1 => 'meta',
80
+ 2 => 'summary',
81
+ 3 => 'detailed',
82
+ }
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,65 @@
1
+ require 'httparty'
2
+ module Strava
3
+ class Client
4
+ attr_reader :token
5
+ # @return [Usage] Information on API quota usage
6
+ attr_reader :usage
7
+ BASE_URL = 'https://www.strava.com/api/v3/' # can be overridden for individual requests
8
+
9
+ def initialize(token)
10
+ @token = token
11
+ end
12
+
13
+ def get(path, **params)
14
+ make_request(:get, path, **params)
15
+ end
16
+
17
+ def post(path, **params)
18
+ make_request(:post, path, **params)
19
+ end
20
+
21
+ def put(path, **params)
22
+ make_request(:put, path, **params)
23
+ end
24
+
25
+ def delete(path, **params)
26
+ make_request(:delete, path, **params)
27
+ end
28
+
29
+ def make_request(verb, path, **params)
30
+ puts (params[:host] || BASE_URL) + path
31
+ handle_params(params)
32
+ res = HTTParty.send(verb, (params.delete(:host) || BASE_URL) + path, query: params)
33
+ check_for_error(res)
34
+ res
35
+ end
36
+
37
+ def handle_params(params)
38
+ if @token
39
+ params.merge!(access_token: @token)
40
+ else
41
+ params.merge!(client_id: Strava.client_id, client_secret: Strava.secret)
42
+ end
43
+ params.reverse_each { |k, v| params.delete(k) if v.nil? }
44
+ end
45
+
46
+ def check_for_error(response)
47
+ @usage = Usage.new(response.headers['X-Ratelimit-Limit'], response.headers['X-Ratelimit-Usage'])
48
+ case response.code
49
+ when 401, 403
50
+ raise Strava::AccessError.new(response.to_h)
51
+ end
52
+ end
53
+
54
+
55
+ ## non athlete calls
56
+ def list_races(year = Time.now.year)
57
+ RunningRace.list_races(self, year)
58
+ end
59
+
60
+ def segment_explorer(bounds = '37.821362,-122.505373,37.842038,-122.465977')
61
+ Segment.explorer(self, bounds)
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,164 @@
1
+ module Strava
2
+ # Clubs represent groups of athletes on Strava. They can be public or private.
3
+ # Clubs have both summary and detailed representations.
4
+ #
5
+ # @see https://strava.github.io/api/v3/clubs/ Strava Docs - Clubs
6
+ class Club < Base
7
+
8
+ # Set up instance variables upon instantiation.
9
+ #
10
+ # @abstract
11
+ # @return [void]
12
+ private def set_ivars
13
+ @activities = {}
14
+ @group_events = {}
15
+ @announcements = []
16
+ @members = {}
17
+ @admins = []
18
+ @segment_efforts = {}
19
+ end
20
+
21
+ # Update an existing club.
22
+ # Used by other methods in the gem.
23
+ # Should not be used directly.
24
+ #
25
+ # @param data [Hash] data to update the club with
26
+ # @return [self]
27
+ def update(data, **opts)
28
+ @response = data
29
+ @id = data["id"]
30
+ @resource_state = data['resource_state']
31
+
32
+ self
33
+ end
34
+
35
+ def activities(per_page: nil, page: nil, before: nil)
36
+ if page || per_page || before
37
+ get_activities(per_page: per_page, page: page, before: before)
38
+ else
39
+ get_activities if @activities.empty?
40
+ @activities.values
41
+ end
42
+ end
43
+
44
+ def group_events(per_page: nil, page: nil, before: nil)
45
+ if page || per_page || before
46
+ get_group_events(per_page: per_page, page: page, before: before)
47
+ else
48
+ get_group_events if @group_events.empty?
49
+ @group_events.values
50
+ end
51
+ end
52
+
53
+ def announcements
54
+ get_announcements if @announcements.empty?
55
+ @announcements
56
+ end
57
+
58
+ def members(per_page: nil, page: nil)
59
+ if page || per_page
60
+ get_members(per_page: per_page, page: page)
61
+ else
62
+ get_members if @members.empty? || !@members_fetched
63
+ @members_fetched = true
64
+ @members.values
65
+ end
66
+ end
67
+
68
+ def admins(per_page: nil, page: nil)
69
+ if page || per_page
70
+ get_admins(per_page: per_page, page: page)
71
+ else
72
+ get_admins if @admins.empty?
73
+ @admins
74
+ end
75
+ end
76
+
77
+ # {"success"=>true, "active"=>false}
78
+ def join
79
+ res = client.post(path_join).to_h
80
+ end
81
+
82
+ # {"success"=>true, "active"=>true, "membership"=>"member"}
83
+ def leave
84
+ res = client.post(path_leave).to_h
85
+ end
86
+
87
+ def get_details
88
+ return self if detailed?
89
+ res = client.get(path_base).to_h
90
+ update(res)
91
+ end
92
+
93
+ private def get_activities(per_page: nil, page: nil, before: nil)
94
+ res = client.get(path_activities, per_page: per_page, page: page, before: before).to_a
95
+ parse_data(@activities, res, klass: Activity, client: @client)
96
+ end
97
+
98
+ private def get_group_events(per_page: nil, page: nil, before: nil)
99
+ res = client.get(path_group_events, per_page: per_page, page: page, before: before).to_a
100
+ parse_data(@group_events, res, klass: Activity, client: @client)
101
+ end
102
+
103
+ private def get_announcements
104
+ res = client.get(path_announcements).to_a
105
+ @announcements = parse_data({}, res, klass: ClubAnnouncement, client: @client)
106
+ end
107
+
108
+ private def get_members
109
+ res = client.get(path_members).to_a
110
+ parse_data(@members, res, klass: Athlete, client: @client)
111
+ end
112
+
113
+ private def get_admins
114
+ res = client.get(path_admins).to_a
115
+ @admins = parse_data(@members, res, klass: Athlete, client: @client)
116
+ end
117
+
118
+ private def path_base
119
+ "clubs/#{id}"
120
+ end
121
+
122
+ private def path_activities
123
+ "#{path_base}/activities"
124
+ end
125
+
126
+ private def path_group_events
127
+ "#{path_base}/group_events"
128
+ end
129
+
130
+ private def path_announcements
131
+ "#{path_base}/announcements"
132
+ end
133
+
134
+ private def path_members
135
+ "#{path_base}/members"
136
+ end
137
+
138
+ private def path_admins
139
+ "#{path_base}/admins"
140
+ end
141
+
142
+ private def path_join
143
+ "#{path_base}/join"
144
+ end
145
+
146
+ private def path_leave
147
+ "#{path_base}/leave"
148
+ end
149
+
150
+ end
151
+ end
152
+
153
+ __END__
154
+
155
+ ca = Strava::Athlete.current_athlete;
156
+ club = ca.clubs.last
157
+ club.admins
158
+ club.members
159
+ club.get_details
160
+ club.activities
161
+ club.group_events
162
+ club.announcements
163
+ club.leave
164
+ club.join
@@ -0,0 +1,28 @@
1
+ module Strava
2
+ # Class to represent Strava Club Announcement
3
+ # https://strava.github.io/api/v3/activities/
4
+ class ClubAnnouncement < Base
5
+
6
+ def update(data, **opts)
7
+ @response = data
8
+ @id = data['id']
9
+ @resource_state = data['resource_state']
10
+
11
+ @message = data['message']
12
+ @created_at = data['created_at']
13
+ @club_id = data['club_id']
14
+ @athlete = Athlete.new(data['athlete'], client: @client)
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ __END__
21
+
22
+ ca = Strava::Athlete.current_athlete;
23
+ ca.activities;
24
+ ca.activities(page: 2);
25
+ ca.activities(page: 3);
26
+ ca.activities(page: 4);
27
+ act = ca.activities.detect{|act| act.response['comment_count'] > 0 && act.response['kudos_count'] > 0 }
28
+ act.comments
@@ -0,0 +1,36 @@
1
+ module Strava
2
+ # Class to represent Strava Activity
3
+ # https://strava.github.io/api/v3/activities/
4
+ class Comment < Base
5
+
6
+ attr_reader :activity_id
7
+
8
+ def update(data, **opts)
9
+ @response = data
10
+ @id = data['id']
11
+ @resource_state = data['resource_state']
12
+
13
+ @text = data['text']
14
+ @activity_id = data['activity_id']
15
+ @athlete = Athlete.new(data['athlete'], client: @client)
16
+ end
17
+
18
+ def delete
19
+ res = client.delete(path_base).to_h
20
+ end
21
+
22
+ def path_base
23
+ "activities/#{activity_id}/comments/#{id}"
24
+ end
25
+ end
26
+ end
27
+
28
+ __END__
29
+
30
+ ca = Strava::Athlete.current_athlete;
31
+ ca.activities;
32
+ ca.activities(page: 2);
33
+ ca.activities(page: 3);
34
+ ca.activities(page: 4);
35
+ act = ca.activities.detect{|act| act.response['comment_count'] > 0 && act.response['kudos_count'] > 0 }
36
+ act.comments
@@ -0,0 +1,15 @@
1
+ {"message"=>"Authorization Error", "errors"=>[{"resource"=>"AccessToken", "field"=>"write_permission", "code"=>"missing"}]}
2
+
3
+ module Strava
4
+ class Error < StandardError
5
+ attr_accessor :response, :strava_errors
6
+ end
7
+
8
+ class AccessError < Error
9
+ def initialize(response)
10
+ message = response['message']
11
+ strava_errors = response['errors']
12
+ super(message)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ module Strava
2
+ # Gear represents both shoes and bikes.
3
+ # These are returned as part of the athlete summary.
4
+ #
5
+ # @see https://strava.github.io/api/v3/gear/ Strava Gear API Docs
6
+ class Gear < Base
7
+
8
+ # Updates gear with passed data attributes.
9
+ #
10
+ # @param data [Hash] data hash containing gear data
11
+ # @return [self]
12
+ def update(data, **opts)
13
+ @response = data
14
+ @id = data['id']
15
+ @resource_state = data['resource_state']
16
+ self
17
+ end
18
+
19
+ # Retrieve full details for Gear object.
20
+ # Sets all data attributes on self.
21
+ #
22
+ # @return [Hash] raw API response
23
+ def get_details
24
+ return self if detailed?
25
+ res = client.get(path_base).to_h
26
+ update(res)
27
+ res
28
+ end
29
+
30
+ # URL path for Gear object.
31
+ #
32
+ # @return [String] URL path
33
+ private def path_base
34
+ "gear/#{id}"
35
+ end
36
+ end
37
+ end
38
+
39
+ __END__
40
+
41
+ ca = Strava::Athlete.current_athlete;
@@ -0,0 +1,62 @@
1
+ module Strava
2
+ # Group events for Strava Clubs
3
+ #
4
+ # @see http://strava.github.io/api/v3/club_group_events/ Strava Docs - Group Events
5
+ class GroupEvent < Base
6
+
7
+ def update(data, **opts)
8
+ @response = data
9
+ @id = data["id"]
10
+ @resource_state = data['resource_state']
11
+ end
12
+
13
+ def get_details
14
+ return self if detailed?
15
+ res = client.get(path_base).to_h
16
+ update(res)
17
+ res
18
+ end
19
+
20
+ def athletes(per_page: nil, page: nil)
21
+ if page || per_page
22
+ get_athletes(per_page: per_page, page: page)
23
+ else
24
+ get_athletes if @athletes.empty?
25
+ @athletes.values
26
+ end
27
+ end
28
+
29
+ def delete
30
+ res = client.delete(path_base).to_h
31
+ end
32
+
33
+ # {"success"=>true, "active"=>false}
34
+ def join
35
+ res = client.post(path_rsvp).to_h
36
+ end
37
+
38
+ # {"success"=>true, "active"=>true, "membership"=>"member"}
39
+ def leave
40
+ res = client.delete(path_rsvp).to_h
41
+ end
42
+
43
+ private def path_base
44
+ "group_events/#{id}"
45
+ end
46
+
47
+ private def path_rsvp
48
+ "#{path_base}/rsvps"
49
+ end
50
+
51
+ private def path_athletes
52
+ "#{path_base}/athletes"
53
+ end
54
+
55
+ end
56
+ end
57
+
58
+ __END__
59
+
60
+ ca = Strava::Athlete.current_athlete;
61
+ miz = ca.clubs.last;
62
+ miz.group_events