strava 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +6 -0
- data/.yardopts +1 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +302 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/strava.rb +79 -0
- data/lib/strava/activity.rb +224 -0
- data/lib/strava/adapters/httparty_adapter.rb +9 -0
- data/lib/strava/athlete.rb +378 -0
- data/lib/strava/base.rb +86 -0
- data/lib/strava/client.rb +65 -0
- data/lib/strava/club.rb +164 -0
- data/lib/strava/club_announcement.rb +28 -0
- data/lib/strava/comment.rb +36 -0
- data/lib/strava/error.rb +15 -0
- data/lib/strava/gear.rb +41 -0
- data/lib/strava/group_event.rb +62 -0
- data/lib/strava/lap.rb +39 -0
- data/lib/strava/leaderboard.rb +57 -0
- data/lib/strava/leaderboard_entry.rb +48 -0
- data/lib/strava/photo.rb +50 -0
- data/lib/strava/route.rb +51 -0
- data/lib/strava/running_race.rb +50 -0
- data/lib/strava/segment.rb +100 -0
- data/lib/strava/segment_effort.rb +45 -0
- data/lib/strava/stream.rb +33 -0
- data/lib/strava/stream_set.rb +62 -0
- data/lib/strava/usage.rb +38 -0
- data/lib/strava/version.rb +4 -0
- data/strava.gemspec +29 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a1efb647025be551ec6e630abf80a97c8e27abbe
|
4
|
+
data.tar.gz: 5606afa633091032f33c3063e58de9192b4c0051
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dae3f482ababb5ce3cc075dd96a3ddae4f900c9cbd73b72abcfe58a26f5bb9508820268ac1be090189a6369ccc9620d9aa53f44e1d90de9fdbae44405f2c9839
|
7
|
+
data.tar.gz: ee1709355ceb9f6b2ddb57fb95ba6c3c910da4b2031ab9e119bc4d24a0071d8a4e79fcd9d31c788e5a17d11ae0c0065a3edef27f69df7e7d4a244d74da507087
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup=markdown
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Paul Hoffer
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,302 @@
|
|
1
|
+
# Strava
|
2
|
+
|
3
|
+
Interact with [Strava's v3 API](https://strava.github.io/api/). This gem is designed to be fully object oriented, so most interaction is with Strava objects. There is an existing gem [strava-api-v3](https://github.com/jaredholdcroft/strava-api-v3), which has been around much longer and has some extra functionality. It is more functional and typically deals with JSON data.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'strava'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install strava
|
20
|
+
|
21
|
+
## API Notes
|
22
|
+
|
23
|
+
### Detail Level
|
24
|
+
|
25
|
+
All Strava resources have a detail level, `{1 => meta, 2 => summary, 3 => detailed}`. Objects will have a `#get_details` method which retrieves the full object details, if supported and not already fetched.
|
26
|
+
|
27
|
+
### Pagination
|
28
|
+
|
29
|
+
Many Strava endpoints support optional pagination. All of these endpoints accept `:page` and `:per_page` options. These can be used like `athlete.activities(page: 3)`. Refer to the [API Coverage](#api-coverage) section for a list of endpoints supporting pagination.
|
30
|
+
|
31
|
+
All method calls including pagination will trigger an API call and return the items from that call. Any calls without pagination will return all previously downloaded items. If no requests have been made, a request without pagination will be made.
|
32
|
+
|
33
|
+
## Functionality
|
34
|
+
|
35
|
+
### Configuration
|
36
|
+
|
37
|
+
This is not necessary, as only the webhooks API requires application information. The values shown below are used by default if not configured manually.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
Strava.client_id = ENV['STRAVA_CLIENT_ID']
|
41
|
+
Strava.secret = ENV['STRAVA_CLIENT_SECRET']
|
42
|
+
```
|
43
|
+
|
44
|
+
### Current Athlete
|
45
|
+
|
46
|
+
Generally, most of the Strava API is based off the authenticated user. Thus, most of the gem's functionality flows from the athlete class (either authenticated user or another).
|
47
|
+
|
48
|
+
The quickest way to get started is with the currently authenticated athlete:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
ca = Strava::Athlete.current_athlete(access_token) # => Strava::Athlete
|
52
|
+
```
|
53
|
+
|
54
|
+
There is also a mixin for existing classes (i.e. models). It is agnostic to DBs/adapters/etc, and only requires a method for the access token.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class Account < ApplicationRecord
|
58
|
+
include Strava.model as: :athlete, via: :token, id: :strava_id
|
59
|
+
end
|
60
|
+
ca = Account.find(1).athlete # => Strava::Athlete
|
61
|
+
```
|
62
|
+
|
63
|
+
This will add an instance method `#athlete` to the `User` class. This returns a `Strava::Athlete`, populating the access token with `User#token` and the user ID with `User#strava_id`. All 3 parameters are optional, and the default parameters are `{ via: :access_token, as: :strava_athlete, id: nil }`.
|
64
|
+
|
65
|
+
It can also be used to go through another method, for instance, if you have separate user and account models:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class User < ApplicationRecord
|
69
|
+
has_one :account
|
70
|
+
include Strava.model as: :athlete, via: 'account.token', id: 'account.strava_id'
|
71
|
+
end
|
72
|
+
ca = User.find(1).athlete # => Strava::Athlete
|
73
|
+
```
|
74
|
+
|
75
|
+
### Classes
|
76
|
+
|
77
|
+
Most use cases won't include manually instantiating classes. Instead, interaction starts with a user and then flows through related objects. If desired, classes can be instantiated, with two requirements:
|
78
|
+
|
79
|
+
1. First argument must be either the object ID (string or integer accepted), or a hash with an `'id'` key.
|
80
|
+
1. Either the `token` or `client` keyword argument must be passed. All API interaction requires an access_token, so instantiation does too.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
act = Strava::Activity.new(321934, token: '83ebeabdec09f6670863766f792ead24d61fe3f9')
|
84
|
+
act = Strava::Activity.new({'id' => 321934}, client: Strava::Client.new('83ebeabdec09f6670863766f792ead24d61fe3f9'))
|
85
|
+
```
|
86
|
+
|
87
|
+
## Usage
|
88
|
+
|
89
|
+
### Athlete
|
90
|
+
|
91
|
+
[Strava Docs](https://strava.github.io/api/v3/athlete/)
|
92
|
+
|
93
|
+
Strava uses the phrase _currently authenticated athlete_ throughout their docs, so we'll use the term _current_ to represent that athlete. With the current athlete from above, we can request data for that athlete. Some APIs can only be made on behalf of the current athlete. See Strava docs for further information.
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
ca.get_details # => retrieves full representation of the athlete
|
97
|
+
ca.email # => "john@applestrava.com"
|
98
|
+
ca.firstname # => "John"
|
99
|
+
ca.lastname # => "Applestrava"
|
100
|
+
ca.stats # => Hash of athlete stats
|
101
|
+
ca.zones # => Array of HR/power zones (hash)
|
102
|
+
ca.koms # => [Strava::SegmentEffort]
|
103
|
+
ca.friends # => [Strava::Athlete]
|
104
|
+
ca.followers # => [Strava::Athlete]
|
105
|
+
ca.both_following(other_id) # => [Strava::Athlete]
|
106
|
+
|
107
|
+
# listed in docs other than athlete docs
|
108
|
+
ca.clubs # => [Strava::Club]
|
109
|
+
ca.routes # => [Strava::Route]
|
110
|
+
ca.starred_segments # => [Strava::Segment]
|
111
|
+
```
|
112
|
+
|
113
|
+
### Activity
|
114
|
+
|
115
|
+
[Strava Docs](https://strava.github.io/api/v3/activities/)
|
116
|
+
|
117
|
+
Strava provides extensive data on activities.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
activity = ca.activities.first # => Strava::Activity
|
121
|
+
activity.get_details # => retrieves full representation of the activity
|
122
|
+
activity.comments # => [Strava::Comment]
|
123
|
+
activity.kudos # => [Strava::Kudo]
|
124
|
+
activity.photos # => [Strava::Photo]
|
125
|
+
activity.related # => [Strava::Activity] activities that were matched as “with this group”
|
126
|
+
activity.zones # => Array of HR/Power zones
|
127
|
+
activity.laps # => [Strava::Lap]
|
128
|
+
activity.streams # => [Strava::StreamSet] Without args, will retrieve time, distance, latlng streams
|
129
|
+
|
130
|
+
# Strava Partner APIs
|
131
|
+
activity.comment(message) # => Create a comment on an activity
|
132
|
+
activity.kudo # => Kudo an activity
|
133
|
+
```
|
134
|
+
|
135
|
+
### Club
|
136
|
+
|
137
|
+
[Strava Docs](https://strava.github.io/api/v3/clubs/)
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
club = ca.clubs.first # => Strava::Club
|
141
|
+
club.get_details # => retrieves full representation of the club
|
142
|
+
club.activities # => [Strava::Activity]
|
143
|
+
club.group_events # => [Strava::GroupEvent]
|
144
|
+
club.announcements # => [Strava::ClubAnnouncment]
|
145
|
+
club.members # => [Strava::Athlete]
|
146
|
+
club.admins # => [Strava::Athlete]
|
147
|
+
club.join # => Join the club as current athlete. Returns hash for success or failure
|
148
|
+
club.leave # => Leave the club as current athlete. Returns hash for success or failure
|
149
|
+
```
|
150
|
+
|
151
|
+
### Group Event
|
152
|
+
|
153
|
+
[Strava Docs](https://strava.github.io/api/v3/club_group_events/)
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
ge = club.group_events.first # => Strava::GroupEvent
|
157
|
+
ge.get_details # => retrieves full representation of the group event
|
158
|
+
ge.athletes # => [Strava::Athlete]
|
159
|
+
ge.delete # => Delete group event. Must have edit permissions. Returns hash of success/failure
|
160
|
+
ge.join # => Join the event as current athlete. Returns hash of success/failure
|
161
|
+
ge.leave # => Leave the event as current athlete. Returns hash of success/failure
|
162
|
+
```
|
163
|
+
|
164
|
+
### Gear
|
165
|
+
|
166
|
+
[Strava Docs](https://strava.github.io/api/v3/gear/)
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
gear = ca.gear.first # => Strava::Gear
|
170
|
+
gear.get_details # => retrieves full representation of the gear
|
171
|
+
```
|
172
|
+
|
173
|
+
### Route
|
174
|
+
|
175
|
+
[Strava Docs](https://strava.github.io/api/v3/routes/)
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
route = ca.routes.first # => Strava::Route
|
179
|
+
route.get_details # => retrieves full representation of the route
|
180
|
+
route.streams # => [Strava::StreamSet] Retrieves distance, altitude, latlng streams (no other streams available).
|
181
|
+
```
|
182
|
+
|
183
|
+
### Running Race
|
184
|
+
|
185
|
+
[Strava Docs](https://strava.github.io/api/v3/running_races/)
|
186
|
+
|
187
|
+
Races are different, as they don't flow from the current athlete. However, the Race APIs still require authentication. As such, there are a couple ways to retrieve races:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
# List races via the current athlete. Probably the simplest way.
|
191
|
+
races = ca.list_races
|
192
|
+
|
193
|
+
# List races via the `RunningRace` class. Must provide an API client.
|
194
|
+
client = Strava::Client.new('83ebeabdec09f6670863766f792ead24d61fe3f9')
|
195
|
+
races = RunningRace.list_races(client)
|
196
|
+
|
197
|
+
# List races via a client. The gem is designed to minimize interaction with the Client class, but it's available if desired.
|
198
|
+
client = Strava::Client.new('83ebeabdec09f6670863766f792ead24d61fe3f9')
|
199
|
+
races = client.list_races
|
200
|
+
|
201
|
+
# All methods accept an optional argument for the year:
|
202
|
+
races = ca.list_races(2016)
|
203
|
+
races = RunningRace.list_races(client, 2016)
|
204
|
+
races = client.list_races(2016)
|
205
|
+
|
206
|
+
# The only API interaction available for a race is to retrieve more details.
|
207
|
+
races.first.get_details # Returns the race, with full representation.
|
208
|
+
```
|
209
|
+
|
210
|
+
### Segment
|
211
|
+
|
212
|
+
[Strava Docs](https://strava.github.io/api/v3/segments/)
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
segment = ca.starred_segments.first # => Strava::Segment
|
216
|
+
segment.get_details # => retrieves full representation of the segment
|
217
|
+
segment.efforts # => [Strava::SegmentEffort] Segment efforts for the current athlete
|
218
|
+
segment.streams # => [Strava::StreamSet] Retrieves distance, altitude, latlng streams (no other streams available).
|
219
|
+
segment.star # => Star the segment, on behalf of current athlete. Returns hash of success/failure.
|
220
|
+
segment.unstar # => Unstar the segment, on behalf of current athlete. Returns hash of success/failure.
|
221
|
+
|
222
|
+
# segment explorer is similar to the running races API. It can be called via a user:
|
223
|
+
bounds = '37.821362,-122.505373,37.842038,-122.465977' # => ‘south,west,north,east
|
224
|
+
ca.segment_explorer(bounds)
|
225
|
+
|
226
|
+
# Or via the `Segment` class. Must provide an API client.
|
227
|
+
segments = Segment.explorer(client, bounds)
|
228
|
+
|
229
|
+
# or via a client (discouraged)
|
230
|
+
client.segment_explorer(bounds)
|
231
|
+
```
|
232
|
+
|
233
|
+
|
234
|
+
### Segment Effort
|
235
|
+
|
236
|
+
[Strava Docs](https://strava.github.io/api/v3/efforts/)
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
effort = segment.efforts.first # => Strava::SegmentEffort
|
240
|
+
effort.get_details # => retrieves full representation of the segment
|
241
|
+
effort.streams # => [Strava::StreamSet] Retrieves distance, altitude, latlng streams (no other streams available).
|
242
|
+
```
|
243
|
+
|
244
|
+
### StreamSet
|
245
|
+
|
246
|
+
[Strava Docs](https://strava.github.io/api/v3/streams/)
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
activity.streams
|
250
|
+
activity.streams.all
|
251
|
+
```
|
252
|
+
|
253
|
+
### Uploads
|
254
|
+
|
255
|
+
[Strava Docs](https://strava.github.io/api/v3/uploads/)
|
256
|
+
|
257
|
+
Uploads haven't been implemented yet :( It will be soon though!
|
258
|
+
|
259
|
+
### Auth
|
260
|
+
|
261
|
+
[Strava Docs](https://strava.github.io/api/v3/oauth/)
|
262
|
+
|
263
|
+
Auth hasn't been implemented yet :( However, most apps are probably using omniauth, so this is a bit lower on the priority list.
|
264
|
+
|
265
|
+
### Webhooks
|
266
|
+
|
267
|
+
[Strava Docs](https://strava.github.io/api/v3/events/)
|
268
|
+
|
269
|
+
Webhooks haven't been implemented yet :( It will be soon though!
|
270
|
+
|
271
|
+
## TODO
|
272
|
+
|
273
|
+
1. Continue adding YARD Documentation
|
274
|
+
1. Add tests
|
275
|
+
1. Submit PRs to existing gem
|
276
|
+
|
277
|
+
## Why
|
278
|
+
|
279
|
+
Comparison to existing gem.
|
280
|
+
|
281
|
+
Q. Why aren't there tests?
|
282
|
+
A. Tests are in progress. Unfortunately, there is no test environment for Strava, and virtually everything is based on hitting their API.
|
283
|
+
|
284
|
+
Q. Why not contribute to the existing gem?
|
285
|
+
A. I'm planning on it! But I also wanted something a bit more OO, and wanted to see what I could come up with.
|
286
|
+
|
287
|
+
|
288
|
+
## Development
|
289
|
+
|
290
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
291
|
+
|
292
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
293
|
+
|
294
|
+
## Contributing
|
295
|
+
|
296
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/phoffer/strava.
|
297
|
+
|
298
|
+
|
299
|
+
## License
|
300
|
+
|
301
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
302
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "strava"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "pry"
|
14
|
+
Pry.start
|
data/bin/setup
ADDED
data/lib/strava.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'strava/version'
|
2
|
+
require 'strava/error'
|
3
|
+
require 'strava/usage'
|
4
|
+
require 'strava/client'
|
5
|
+
require 'strava/base'
|
6
|
+
require 'strava/gear'
|
7
|
+
require 'strava/segment'
|
8
|
+
require 'strava/segment_effort'
|
9
|
+
require 'strava/leaderboard'
|
10
|
+
require 'strava/leaderboard_entry'
|
11
|
+
require 'strava/athlete'
|
12
|
+
require 'strava/route'
|
13
|
+
require 'strava/activity'
|
14
|
+
require 'strava/stream_set'
|
15
|
+
require 'strava/stream'
|
16
|
+
require 'strava/lap'
|
17
|
+
require 'strava/comment'
|
18
|
+
require 'strava/photo'
|
19
|
+
require 'strava/club'
|
20
|
+
require 'strava/group_event'
|
21
|
+
require 'strava/club_announcement'
|
22
|
+
require 'strava/running_race'
|
23
|
+
|
24
|
+
module Strava
|
25
|
+
|
26
|
+
class << self
|
27
|
+
# @return [Integer, String] Strava Application ID
|
28
|
+
attr_writer :client_id
|
29
|
+
# @return [String] Strava Application secret
|
30
|
+
attr_writer :client_secret
|
31
|
+
|
32
|
+
# Helper for model classes.
|
33
|
+
# Allows for convenient instantiation of current athlete.
|
34
|
+
# This is completely agnostic to class type, it can be a DB model, a PORO, etc.
|
35
|
+
#
|
36
|
+
# Usage:
|
37
|
+
#
|
38
|
+
# class Account < ApplicationRecord
|
39
|
+
# include Strava.model as: :athlete, via: :token, id: :strava_id
|
40
|
+
# end
|
41
|
+
# ca = Account.find(1).athlete # => Strava::Athlete
|
42
|
+
#
|
43
|
+
# Can also perform lookup through another method:
|
44
|
+
#
|
45
|
+
# class User < ApplicationRecord
|
46
|
+
# has_one :account
|
47
|
+
# include Strava.model as: :athlete, via: 'account.token', id: 'account.strava_id'
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @param as [Symbol] method to define to return current athlete
|
51
|
+
# @param via [Symbol, String] method to lookup access token
|
52
|
+
# @param id [Symbol, String] method to lookup Strava ID
|
53
|
+
# @return [Module] module to be included in the calling class
|
54
|
+
def model(as: :strava_athlete, via: :access_token, id: nil)
|
55
|
+
Module.new.tap do |mod|
|
56
|
+
str = <<~EOF
|
57
|
+
def self.included(base)
|
58
|
+
base.send(:define_method, :#{as}) { ::Strava::Athlete.new(#{id ? "{'id' => #{id}}" : '{}' }, token: #{via}, current: true) }
|
59
|
+
end
|
60
|
+
EOF
|
61
|
+
mod.class_eval str
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# def adapter(name = :httparty)
|
66
|
+
# require "strava/adapters/#{name}"
|
67
|
+
# Client.include(Adapters.httparty)
|
68
|
+
# end
|
69
|
+
|
70
|
+
# @return [Integer, String] Strava Application ID
|
71
|
+
def client_id
|
72
|
+
@client_id ||= ENV['STRAVA_CLIENT_ID']
|
73
|
+
end
|
74
|
+
# @return [String] Strava Application secret
|
75
|
+
def client_secret
|
76
|
+
@client_secret ||= ENV['STRAVA_CLIENT_SECRET']
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|