A complete Ruby client for the Strava API v3.
Unlike other clients, including strava-api-v3, provides complete OAuth refresh token flow support, webhooks support, a richer first class interface to Strava models, conversion helpers for distance, time and elevation, natively supports pagination, implements more consistent error handling and is built with thorough test coverage using actual Strava data.
- Installation
- Usage
- Configuration
- Errors
- Tools
- Users
- Resources
- Upgrading
- Contributing
- Copyright and License
Add to Gemfile.
gem 'strava-ruby-client'
Run bundle install
.
Use an access token obtained from My API Application in the Strava UI, the strava-oauth-token tool or the OAuth Workflow in your application.
client = Strava::Api::Client.new(
access_token: "12345678987654321"
)
Creates a manual activity for an athlete.
activity = client.create_activity(
name: 'Afternoon Run',
type: 'Run',
start_date_local: Time.now,
elapsed_time: 1234, # in seconds
description: 'Test run.',
distance: 1000 # in meters
)
activity.name # => 'Afternoon Run'
activity.strava_url # => 'https://www.strava.com/activities/1982980795'
See Strava::Models::Activity for all available properties.
Returns the given activity that is owned by the authenticated athlete.
activity = client.activity(1982980795)
activity.name # => 'Afternoon Run'
activity.strava_url # => 'https://www.strava.com/activities/1982980795'
See Strava::Models::Activity for all available properties.
Use map.summary_polyline
and combine with polylines to parse the activity map and to construct a Google maps URL with start and end markers.
map = activity.map # => Strava::Models::Map
decoded_summary_polyline = Polylines::Decoder.decode_polyline(map.summary_polyline)
start_latlng = decoded_summary_polyline[0]
end_latlng = decoded_summary_polyline[-1]
google_maps_api_key = ENV['GOOGLE_STATIC_MAPS_API_KEY']
google_image_url = "https://maps.googleapis.com/maps/api/staticmap?maptype=roadmap&path=enc:#{map.summary_polyline}&key=#{google_maps_api_key}&size=800x800&markers=color:yellow|label:S|#{start_latlng[0]},#{start_latlng[1]}&markers=color:green|label:F|#{end_latlng[0]},#{end_latlng[1]}"
See Strava::Models::Map for all available properties.
Returns the photos on the given activity. This API is undocumented.
photos = client.activity_photos(1982980795) # => Array[Strava::Models::Photo]
photo = photos.first # => Strava::Models::Photo
photo.id # => nil
photo.unique_id # => '65889142-538D-4EE5-96F5-3DC3B773B1E3'
photo.urls # => { '0' => 'https://dgtzuqphqg23d.cloudfront.net/eb4DMJ2hJW3k_g9URZEMfaJ8rZfHagrNlZRuEZz0osU-29x64.jpg' }
photo.athlete_id # => 26_462_176
photo.activity_id # => 1_946_417_534
photo.activity_name # => 'TCS NYC Marathon 2018'
photo.created_at # => Time
photo.uploaded_at # => Time
photo.sizes # => { '0' => [29, 64] }
photo.default_photo # => false
See Strava::Models::Photo for all available properties.
Returns the comments on the given activity.
comments = client.activity_comments(1982980795) # => Array[Strava::Models::Comment]
comment = comments.first # => Strava::Models::Comment
comment.text # => 'Молодчина!'
comment.athlete.username # => 'zolotov'
See Strava::Models::Comment for all available properties.
Returns the athletes who kudoed an activity identified by an identifier.
kudoers = client.activity_kudos(1982980795) # => Array[Strava::Models::Athlete]
kodoer = kudoers.first # => Strava::Models::Athlete
kudoer.username # => 'zolotov'
See Strava::Models::Athlete for all available properties.
Returns the laps of an activity identified by an identifier.
laps = client.activity_laps(1982980795) # => Array[Strava::Models::Lap]
lap = laps.first # => Strava::Models::Lap
lap.name # => 'Lap 1'
See Strava::Models::Lap for all available properties.
Returns the currently logged-in athlete's activities.
activities = client.athlete_activities # => Array[Strava::Models::Activity]
activity = activities.first # => Strava::Models::Activity
activity.name # => 'NYC TCS Marathon 2018'
activity.strava_url # => 'https://www.strava.com/activities/1477353766'
activity.type_emoji # => '🏃'
activity.distance_s # => '42.2km'
activity.moving_time_in_hours_s # => '3h38m5s'
activity.elapsed_time_in_hours_s # => '3h42m13s'
activity.pace_s # => '5m15s/km'
activity.pace_per_mile_s # => '8m28s/mi'
activity.speed_s # => '11.4km/h'
activity.miles_per_hour_s # => '7.1mph'
activity.total_elevation_gain_s # => '270.9m'
activity.total_elevation_gain_in_feet_s # => '888.8ft'
See Strava::Models::Activity, Strava::Models::Mixins::Distance, Strava::Models::Mixins::Elevation and Strava::Models::Mixins::Time for all available properties.
Returns the zones of a given activity.
zones = client.activity_zones(1982980795) # => Array[Strava::Models::ActivityZone]
zone = zones.first # => Strava::Models::ActivityZone
zones.type # => 'heartrate'
distribution_buckets = activity_zone.distribution_buckets # => Array[Strava::Models::TimedZoneRange]
distribution_bucket = distribution_buckets.first # => Strava::Models::TimedZoneRange
distribution_bucket.min # => 0
distribution_bucket.max # => 123
distribution_bucket.time # => 20
See Strava::Models::ActivityZone and Strava::Models::TimedZoneRange for all available properties.
Update an activity.
activity = client.update_activity(
id: 1982980795,
name: 'Afternoon Run (Updated)',
type: 'Run',
description: 'It was cold.'
)
activity.name # => 'Afternoon Run (Updated)'
activity.strava_url # => 'https://www.strava.com/activities/1982980795'
Returns the currently authenticated athlete.
client.athlete # => Strava::Models::Athlete
See Strava::Models::Athlete for all available properties.
Returns the the authenticated athlete's heart rate and power zones.
athlete_zones = client.athlete_zones # => Strava::Models::Zones
heart_rate = athlete_zones.heart_rate # => Strava::Models::HeartRateZoneRanges
heart_rate.custom_zone # => false
zone = heart_rate.zones.first # => Strava::Models::ZoneRange
zone.min # => 0
zone.max # => 123
See Strava::Models::Zones, Strava::Models::HeartRateZoneRanges, Strava::Models::PowerZoneRanges and Strava::Models::ZoneRange for all available properties.
Returns the activity stats of an athlete.
athlete_stats = client.athlete_stats(26462176) # => Strava::Models::ActivityStats
recent_run_totals = athlete_stats.recent_ride_totals # => Strava::Models::ActivityTotal
recent_run_totals.count # => 7
recent_run_totals.distance # => 78049.90087890625
recent_run_totals.distance_s # => '78.05km'
recent_run_totals.moving_time # => 25647
recent_run_totals.moving_time_in_hours_s # => '7h7m27s'
recent_run_totals.elapsed_time # => 26952
recent_run_totals.elapsed_time_in_hours_s # => '7h29m12s'
recent_run_totals.elevation_gain # => 595.4644241333008
recent_run_totals.total_elevation_gain_s # => '595.5m'
recent_run_totals.achievement_count # => 19
See Strava::Models::ActivityStats and Strava::Models::ActivityTotal for all available properties.
Update the currently authenticated athlete.
athlete = client.update_athlete(weight: 90.1) # => Strava::Models::Athlete
See Strava::Models::Athlete for all available returned properties.
Retrieve recent activities from members of a specific club.
activities = client.club_activities(108605) # => Array[Strava::Models::Activity]
activity = activities.first # => Strava::Models::Activity
activity.name # => 'Afternoon Run'
See Strava::Models::Activity for all available properties. Note that Strava does not return activity or athlete ID via this API.
Returns a list of the administrators of a given club.
admins = client.club_admins(108605) # => Array[Strava::Models::ClubAdmin]
admin = admins.first # => Strava::Models::ClubAdmin
admin.name # => 'Peter Ciaccia'
See Strava::Models::ClubAdmin for all available properties.
Returns a given club using its identifier.
club = client.club(108605) # => Strava::Models::Club
club.name # => 'NYRR'
See Strava::Models::Club for all available properties.
Returns a list of the members of a given club.
members = client.club_members(108605) # => Array[Strava::Models::ClubMember]
member = members.first # => Strava::Models::ClubMember
member.name # => 'Peter Ciaccia'
See Strava::Models::ClubMember for all available properties.
Returns a list of the clubs whose membership includes the authenticated athlete.
clubs = client.athlete_clubs # => Array[Strava::Models::Club]
club = clubs.first # => Strava::Models::Club
activity.name # => 'NYRR'
activity.strava_url # => 'https://www.strava.com/clubs/nyrr'
See Strava::Models::Club for all available properties.
Returns an equipment using its identifier.
gear = client.gear('g3844087') # => Strava::Models::Gear
gear.id # => 'g3844087'
gear.name # => 'Adidas Supernova ST'
gear.distance # => 380939.0
gear.distance_s # => '380.94km'
gear.brand_name # => 'Adidas'
gear.model_name # => 'Supernova'
gear.description # => 'My NYC TCS Marathon 2018 shoes.'
See Strava::Models::Gear for all available properties.
Returns GPS Exchange Format (GPX) data of the route. Combine with multi_xml or gpx to parse it.
data = client.export_route_gpx(16341573) # => String
require 'multi_xml'
xml = MultiXml.parse(data) # => parsed GPX
require 'gpx'
gpx = GPX::GPXFile.new(gpx_data: data) # => GPX::GPXFile
gpx.name # => 'Lower Manhattan Loop'
gpx.description # => 'My usual long run when I am too lazy to go to Central Park.'
gpx.tracks # => Array[GPX::Track]
Returns a Training Center XML (TCX) data of the route. Combine with multi_xml to parse it.
data = client.export_route_tcx(16341573) # => String
require 'multi_xml'
xml = MultiXml.parse(data) # => parsed TCX
Returns a route using its identifier.
route = client.route(16341573) # => Strava::Models::Route
route.name # => 'Lower Manhattan Loop'
route.description # => 'My usual long run when I am too lazy to go to Central Park.'
See Strava::Models::Route for all available properties.
Returns a list of the routes by athlete ID.
routes = client.athlete_routes(26462176) # => Array[Strava::Models::Route]
route = routes.first # => Strava::Models::Route
route.name # => 'Lower Manhattan Loop'
route.description # => 'My usual long run when I am too lazy to go to Central Park.'
route.moving_time_in_hours_s # => '1h21m5s'
See Strava::Models::Route for all available properties.
Returns a running race for a given identifier.
running_race = client.running_race(1577) # => Strava::Models::RunningRace
running_race.name # => 'Walt Disney World Marathon 10k'
running_race.distance # => 10_000.0
running_race.distance_s # => '10km'
running_race.city # => 'Orlando'
running_race.state # => 'FL'
running_race.country # => 'United States'
running_race.strava_url # => 'https://www.strava.com/running-races/2018-walt-disney-world-marathon-10k'
running_race.website_url # => 'https://www.rundisney.com/disneyworld-marathon/'
See Strava::Models::RunningRace for all available properties.
Returns a list running races based on a set of search criteria.
running_races = client.running_races
running_race = running_races.first # => Strava::Models::RunningRace
running_race.name # => 'Walt Disney World Half Marathon'
running_race.distance # => 21_097.0
running_race.measurement_preference # => 'feet'
running_race.distance_s # => '13.11mi'
See Strava::Models::RunningRace for all available properties.
Returns a set of the authenticated athlete's segment efforts for a given segment.
segment_efforts = client.segment_efforts(1109718)
segment_effort = segment_efforts.first # => Strava::Models::SegmentEffort
segment_effort.name # => 'E 14th St Climb'
segment_effort.activity # => Strava::Models::Activity
segment_effort.athlete # => Strava::Models::Athlete
segment_effort.elapsed_time # => 116
segment_effort.distance # => 398.6
segment_effort.distance_s # => '0.4km'
segment_effort.average_heartrate # => 152.2
segment_effort.max_heartrate # => 158.0
segment_effort.achievements # => Enumerable
achievement = segment_effort.achievements.first # => Strava::Models::Achievement
achievement.rank # => 1
achievement.type # => 'pr'
achievement.type_id # => 3
See Strava::Models::SegmentEffort and Strava::Models::Achievement for all available properties.
Returns a segment effort from an activity that is owned by the authenticated athlete.
segment_effort = client.segment_effort(41494197089) # => Strava::Models::SegmentEffort
segment_effort.name # => 'E 14th St Climb'
segment_effort.activity # => Strava::Models::Activity
segment_effort.elapsed_time # => 116
segment_stats = segment_effort.athlete_segment_stats # => Strava::Models::SegmentStats
segment_stats.pr_elapsed_time # => 116
segment_stats.elapsed_time_in_hours_s # => '1m56s'
segment_stats.pr_date # => Date
segment_stats.effort_count # => 3
See Strava::Models::SegmentEffort and Strava::Models::SegmentStats for all available properties.
Returns the top 10 segments matching a specified query.
segments = client.explore_segments(bounds: [36.372975, -94.220234, 36.415949, -94.183670], activity_type: 'running')
segment = segments.first # => Strava::Models::ExplorerSegment
segment.name # => 'Compton Gardens hill'
segment.avg_grade # => 4.6
segment.start_latlng # => [36.377702, -94.207242]
segment.end_latlng # => [36.375948, -94.207689]
segment.elev_difference # => 9.6
See Strava::Models::ExplorerSegment for all available properties.
Returns the specified segment leaderboard.
segment_leaderboard = client.segment_leaderboard(1109718) # => Strava::Models::SegmentLeaderboard
segment_leaderboard.effort_count # => 204
segment_leaderboard.entry_count # => 204
segment_leaderboard.kom_type # => 'kom'
segment_leaderboard.entries # => Enumerable
entry = segment_leaderboard.entries.first # => Strava::Models::SegmentLeaderboardEntry
entry.athlete_name # => 'Etan B.'
entry.moving_time_in_hours_s # => '1m32s'
entry.start_date # => Time
entry.start_date_local # => Time
entry.rank # => 1
This API supports pagination through the entire segment leaderboard but wraps entries into a Strava::Models::SegmentLeaderboard object.
client.segment_leaderboard(1109718) do |row|
row # => Strava::Models::SegmentLeaderboard
row.entries # => Array[Strava::Models::SegmentLeaderboardEntry]
end
See Strava::Models::SegmentLeaderboard and Strava::Models::SegmentLeaderboardEntry for all available properties.
List of the authenticated athlete's starred segments.
segments = client.starred_segments
segment = segments.first # => Strava::Models::Segment
segment.pr_time # => 256
segment.elapsed_time_in_hours_s # => '4m16s'
segment.starred_date # => Time
segment.athlete_pr_effort # => Strava::Models::SegmentEffort
See Strava::Models::Segment and Strava::Models::SegmentEffort for all available properties.
Returns the specified segment.
segment = client.segment(1109718) # => Strava::Models::Segment
segment.name # => 'E 14th St Climb'
segment.city # => 'New York'
segment.state # => 'NY'
segment.country # => 'United States'
segment.map # => Strava::Models::Map
segment.effort_count # => 750
segment.athlete_count # => 206
segment.star_count # => 1
segment.athlete_segment_stats # => Strava::Models::SegmentStats
See Strava::Models::Segment for all available properties.
Stars/unstars the given segment for the authenticated athlete.
segment = client.star_segment(50272077110, starred: true) # => Strava::Models::Segment
segment.name # => 'E 14th St Climb'
segment.starred # => true
See Strava::Models::Segment for all available properties.
Stream APIs can return various streams by key(s).
streams = client.segment_streams(1109718, keys: %w[distance latlng altitude]) # => Strava::Models::StrewamSet
streams.distance # => Strava::Models::Stream
streams.latlng # => Strava::Models::Stream
streams.altitude # => Strava::Models::Stream
Returns the given activity's streams.
streams = client.activity_streams(1946417534) # => Strava::Models::StreamSet
distance = streams.distance # => Strava::Models::Stream
distance.original_size # => 13_129
distance.resolution # => 'high'
distance.series_type # => 'distance'
distance.data # => Array[Float]
See Strava::Models::StreamSet and Strava::Models::Stream for all available properties.
Returns a set of streams for a segment effort completed by the authenticated athlete.
streams = client.segment_effort_streams(41494197089)
distance = streams.distance # => Strava::Models::Stream
distance.original_size # => 117
distance.resolution # => 'high'
distance.series_type # => 'distance'
distance.data # => Array[Float]
See Strava::Models::StreamSet and Strava::Models::Stream for all available properties.
Returns the given segment's streams.
streams = client.segment_streams(1109718) # => Strava::Models::StreamSet
distance = streams.distance # => Strava::Models::Stream
distance.original_size # => 32
distance.resolution # => 'high'
distance.series_type # => 'distance'
distance.data # => Array[Float]
See Strava::Models::StreamSet and Strava::Models::Stream for all available properties.
Uploads a new data file to create an activity from.
upload = client.create_upload(
file: Faraday::UploadIO.new('17611540601.tcx', 'application/tcx+xml'),
name: 'Uploaded Activity',
description: 'Uploaded by strava-ruby-client.',
data_type: 'tcx',
external_id: 'strava-ruby-client-upload-1'
) # => Strava::Models::Upload
upload.id # => 2136460097
upload.external_id # => 'strava-ruby-client-upload-1.tcx'
upload.error # => nil
upload.status # => 'Your activity is still being processed.'
upload.activity_id # => nil
See Strava::Models::Upload for all available properties.
Returns an upload for a given identifier.
upload = client.upload(2136460097) # => Strava::Models::Upload
upload.id # => 2_136_460_097
upload.external_id # => 'strava-ruby-client-upload-1.tcx'
upload.error # => nil
upload.status # => 'Your activity is ready.'
upload.activity_id # => 1_998_557_443
See Strava::Models::Upload for all available properties.
Some Strava APIs, including athlete-activities support pagination when supplying an optional page
and per_page
parameter. By default the client retrieves one page of data, which Strava currently defaults to 30 items. You can paginate through more data by supplying a block and an optional per_page
parameter. The underlying implementation makes page-sized calls and increments the page
argument.
client.athlete_activities(per_page: 30) do |activity|
activity # => Strava::Models::Activity
end
Obtain a redirect URL using an instance of Strava::OAuth::Client
.
client = Strava::OAuth::Client.new(
client_id: "12345",
client_secret: "12345678987654321"
)
redirect_url = client.authorize_url(
redirect_uri: 'https://example.com/oauth',
approval_prompt: 'force',
response_type: 'code',
scope: 'activity:read_all',
state: 'magic'
)
Once the user is redirected to your application, perform a token exchange to obtain a refresh and access token.
response = client.oauth_token(code: '1234556789901234567890')
response # => Strava::Models::Token
response.access_token # access token
response.refresh_token # refresh token
response.expires_at # timestamp when the access token expires
response.athlete # => Strava::Models::Athlete
See Strava authentication documentation, Strava::Models::Token and Strava::Models::Athlete for all available properties in the response.
If the access token is expired, refresh it before making any requests. You will get back all new tokens.
response = client.oauth_token(
refresh_token: '...',
grant_type: 'refresh_token'
)
response.access_token # => String, new access token
response.refresh_token # => String, new refresh token
response.expires_at # => Time, new timestamp when the access token expires
Revoke access to an athlete's data using an instance of Strava::API::Client
.
authorization = client.deauthorize
authorization.access_token # => String, access token being revoked
The OAuth process is web-based and you cannot obtain a token from a Strava client ID and secret without user intervention. You can, however, start a local web server to handle the OAuth redirect and open a browser from the command-line.
See strava-oauth-token or strava-ruby-cli for an example.
Strava provides a Webhook Event API.
A complete example that handles subscription creation, deletion and handling can be found in strava-webhooks. Run strava-webhooks
to see current registrations, strava-webhooks handle
to run an HTTP server that handles both challenges and event data, strava-webhooks create [url]
to create a new subscription and strava-webhooks delete [id]
to delete it.
Before creating a webhook subscription you must implement and run an HTTP server that will handle a GET
challenge at the subscription URL.
challenge = Strava::Webhooks::Models::Challenge.new(request.query)
raise 'Bad Request' unless challenge.verify_token == 'token'
response.content_type = 'application/json'
response.body = challenge.response.to_json
See Strava::Webhooks::Models::Challenge for details.
An existing subscription must be handled in the same HTTP server's POST
request to the subscription URL.
event = Strava::Webhooks::Models::Event.new(JSON.parse(request.body))
event # => Strava::Webhooks::Models::Event
event.object_type # => 'activity'
event.object_id # => 1991813808
event.aspect_type # => 'update'
event.updates # => { 'type' => 'Walk' }
event.owner_id # => 29323238
event.subscription_id # => 131302
event.event_time # => DateTime
See Strava::Webhooks::Models::Event for details.
Subscriptions can be created, listed and deleted.
Create a client.
client = Strava::Webhooks::Client.new(
client_id: "12345",
client_secret: "12345678987654321"
)
Create a subscription.
subscription = client.create_push_subscription(callback_url: 'http://example.com/strava', verify_token: 'token')
subscription # => Strava::Webhooks::Models::Subscription
subscription.id # => 131300
subscription.callback_url # => 'http://example.com/strava'
See Strava::Webhooks::Models::Subscription for details.
List an existing subscription. Strava seems to only allow one.
subscriptions = client.push_subscriptions
subscription = subscriptions.first # => Strava::Webhooks::Models::Subscription
subscription.id # => 131300
subscription.callback_url # => 'http://example.com/strava'
Delete an existing subscription.
client.delete_push_subscription(131300) # => nil
You can configure web client options used in the OAuth and API clients, globally.
Strava::Web::Client.configure do |config|
config.user_agent = 'Strava Ruby Client/1.0'
end
The following settings are supported.
setting | description |
---|---|
user_agent | User-agent, defaults to Strava Ruby Client/version. |
proxy | Optional HTTP proxy. |
ca_path | Optional SSL certificates path. |
ca_file | Optional SSL certificates file. |
logger | Optional Logger instance that logs HTTP requests. |
timeout | Optional open/read timeout in seconds. |
open_timeout | Optional connection open timeout in seconds. |
The API client inherits web client options and provides additional application configuration. These can be configured globally or for a client instance.
Strava::API.configure do |config|
config.access_token = "..." # Strava access token
end
client = Strava::API::Client.new(
access_token: "...",
user_agent: "..."
)
The following settings are supported.
setting | description |
---|---|
access_token | Access token to pass in the Authorization header. |
endpoint | Defaults to https://www.strava.com/api/v3 . |
The OAuth client inherits web client options and provides additional application configuration. These can be configured globally or for a client instance.
Strava::OAuth.configure do |config|
config.client_id = "..." # Strava client ID
config.client_secret = "..." # Strava client secret
end
client = Strava::OAuth::Client.new(
client_id: "...",
client_secret: "...",
user_agent: "..."
)
The following settings are supported.
setting | description |
---|---|
client_id | Application client ID. |
client_secret | Application client secret. |
endpoint | Defaults to https://www.strava.com/oauth . |
The Webhooks client inherits web client options and provides additional application configuration. These can be configured globally or for a client instance.
Strava::Webhooks.configure do |config|
config.client_id = "..." # Strava client ID
config.client_secret = "..." # Strava client secret
end
client = Strava::Webhooks::Client.new(
client_id: "...",
client_secret: "...",
user_agent: "..."
)
The following settings are supported.
setting | description |
---|---|
client_id | Application client ID. |
client_secret | Application client secret. |
endpoint | Defaults to https://www.strava.com/api/v3 . |
All errors that return HTTP codes 400-600 result in either Faraday::ResourceNotFound
, Faraday::ConnectionFailed
or Strava::Errors::Fault exceptions.
begin
client.oauth_token(code: 'invalid')
rescue Strava::Errors::Fault => e
e.message # => Bad Request
e.errors # => [{ 'code' => 'invalid', 'field' => 'code', 'resource' => 'RequestToken' }]
e.headers # => { "status" => "403 Bad Request", "x-ratelimit-limit" => "600,30000", "x-ratelimit-usage" => "314,27536" }
end
For a complete set of command-line tools, check out strava-ruby-cli built on top of this gem.
Use strava-oauth-token to obtain a token from the command-line. This will open a new browser window, navigate to Strava, request the appropriate permissions, then handle OAuth in a local redirect. The token type, refresh token, access token and token expiration will be displayed in the browser.
$ STRAVA_CLIENT_ID=... STRAVA_CLIENT_SECRET=... strava-oauth-token
- Slava: Strava integration with Slack, source.
- Jekyll Blog at run.dblock.org, source
- Secret Strava, source
- Strava API Documentation
- Writing a New Strava API Ruby Client
- Dealing with Strava API OAuth Token Migration
- Auto-Publishing Strava Runs to Github Pages
- Strava Command-Line Client
See UPGRADING.
See CONTRIBUTING.
Copyright (c) 2018, Daniel Doubrovkine and Contributors.
This project is licensed under the MIT License.