weakty

Let’s go on a little whirlwind adventure. Last night I was out for a ride with my pal who mentioned that they were able to export their history with Ride With GPS and had looked into importing it into Strava. Turns out doing that manually would take quite a while because there are some 1200 rides from the Ride With GPS dump. I asked him to send me the data and I said I’d probably find a way to get in into Strava.

Well, I was possessed by something strange tonight and I decided that I would not only try to do this in an hour, but document every step along the way. I clocked in on org mode, and started listing things out.

Buckle up, here we go.

I. Downloading the data and inspecting it

I've never seen GPX data before, but I had a general idea that it would be a data-driven file format that would include latitude and longitude over time. Easy. It's basically a form of XML. I knew, like many projects I have done at work and elsewhere, that I would basically be looping over a folder of files and then making an API request for each file. But why try and do that from the get go? Why not ask ChatGPT?

II. GPT, how me do this?

Here’s what I dumped into the application "Typing Mind", that essentially gives me free access to GPT 4.0 via the SetApp catalogue:

**User**: I have a bunch of GPX files exported from RideWithGPS that I would
like to import to Strava using the Strava API. How would I do this?

**Assistant**: To import GPX files into Strava using the Strava API, you need
to:

1. **Obtain an access token**: You must create an application in your Strava
   profile and obtain an access token with the 'activity:write' scope, which
   allows you to upload to the signed-in user's activities. See [Obtaining
   Access Tokens](https://developers.strava.com/docs/getting-started/#account).

2. **Upload an activity**: After obtaining an access token, use Strava's "Upload
   Activity" endpoint (POST /uploads) to upload your GPX file. Here's a simple
   Python code snippet using the requests library:

`python
import requests

url = "https://www.strava.com/api/v3/uploads"
access_token = 'YOUR_ACCESS_TOKEN'

data = {
    'activity_type': 'ride',  # or another activity type
    'name': 'My ride',  # name your ride
    'description': 'Riding around town',  # describe the activity
    'data_type': 'gpx'  # denotes the data in the file is GPX
}

files = {
    'file': open('/path/to/your/gpxfile.gpx', 'rb')
}

headers = {
    'Authorization': f'Bearer {access_token}'
}

response = requests.post(url, data=data, files=files, headers=headers)

print(response.json())
`

Replace '/path/to/your/gpxfile.gpx' with the real path to your GPX file, and
'YOUR_ACCESS_TOKEN' with the actual access token obtained from Strava.

Be aware that the Uploads endpoint is asynchronous, meaning you make a request
to upload the data and then need to poll another endpoint (GET
/uploads/:upload_id) to check if your upload succeeded or failed.

For more information, please visit the [Strava
Developers](https://developers.strava.com/docs/reference/) site.

III. Why did it have to be snakes?

I don't use Python frequently, and every time I do I'm confused by how its package management ecosystem works. So, I guess I'll ask ChatGPT where they need to get started with that too. Short answer?

python -m venv env
source env/bin/activate
pip install requests

I tried the above, and it fails for two reasons. First, I don't have python installed. I have python3. Second, this shell script doesn't play nice with how fish sets variables. I like using fish and I want to keep using it, so back to GPT. And would you look at that? Turns out there is an activate.fish script right there in the venv folder. Today I learned. And instead of actually looking in that folder, I asked an AI. I feel momentarily depressed.

Well, let's dump in the example python code and poke around. I turn on Mount Shrine; time to get cozy.

IV. A token effort

It's time to go looking for the Strava documentation on how to set up tokens. I poke around, find the API documentation for developers, and discover that they even have an app store for API integrations, including a data importer section (!) However, it doesn't look like RideWithGPS is to be found in this particular catalogue.

To get access to the API, I need to create an app. That means I need to create a name, and it turns out that I even have to come up with an icon. Fine by me, since the running theme here is using AI, instead of actually having to do anything, I'm going to go to DALL-E and demand a beautiful app icon be made for the cause.

It spits out this gem. I’m dying.

Besides now planning on starting a company called Riker Bider, I’ve got myself a nice little app icon, and now I can get back to figuring out this token business.

Moving on, I skim through the terms and conditions, not reading them at all, and hoping that whatever I'm about to end up importing isn't going to get me booted out of the "app store". I land on the API page in my settings, and it says that I get some 2000 daily requests with 200 requests every 15 minutes. It says I have read-only scopes, which doesn't bode well for uploading rides.

V. I dunno, just try it?

I don't even bother testing the URL that chatgpt has given me to see if it's correct. Why not just try it and find out? I've got a hard-coded string for the test gpx file. Here we go! Aaaaand error:

{'message': 'Authorization Error', 'errors': [{'resource': 'AccessToken', 'field': 'activity:write_permission', 'code': 'missing'}]}

I asked GPT what could be going wrong, and it pointed out that I might need to do some kind of in-browser authentication flow because I'm missing a write-permission scope. I guess if I actually read the above error, I would have noted that myself. I google around and I find this gist. It appears useful. Extremely useful. Looks like someone has done basically everything I’m trying to do, but with a different file format.

The gist says I can mock authenticating through oauth by visiting a special link, popping in my Client ID and getting back out a special code that I will use for my requests. I spend about 10 minutes trying to get to the OAuth page before realizing I've been trying to use the Riker Bider client secret and not the client_id in the url. Whoops, must be getting late (and the ambient music is making me sleepy).

VI. A successful request!

I fire the below into my terminal

#+begin_src sh
curl -X POST https://www.strava.com/api/v3/uploads \
    -H "Authorization: Bearer <my_super_secret_code" \
    -F file=@"/Users/tees/Downloads/chad-ridewithgps-rides/2020-02-15T17-08-03Z-44744296.gpx" \
    -F data_type="gpx"
#+end_src

#+RESULTS:
{"id":11113244393,"id_str":"11113244393","external_id":"2020-02-15T17-08-03Z-44744296.gpx","error":null,"status":"Your activity is still being processed.","activity_id":null}⏎

Ta-da! It succeeds, and Strava thinks that I biked 34km outside of Phoenix in 2020.

VII. Departing the Snake Den

So, it appears that I can just run this with a shell command, just like the gist I found pointed out:

 for i in `ls /Users/tees/Downloads/test_rides/*.gpx`
   do curl -X POST https://www.strava.com/api/v3/uploads -H "Authorization: Bearer <my_token>" -F file=@"$i" -F data_type="gpx" -F activity_type="ride"
 done

It looks like I won't have to write any python. Not that I have anything against python. At this point, I have some 200 requests I can make every 15 minutes. It would be easier for me to just portion out the requests into several folders of 200 rides, and then just run the above shell command on that folder. I try it out with 3 and it uploads it with no problem. Well, it takes a little while, actually.

VIII. That’s it?

That’s it. I clock out of org-mode. It took me one hour and 2 minutes. My notes look like this:

I had fun. My pal told me not to kill myself over doing this little endeavour. I certainly didn’t. Undoubtedly, it’s a strange and wonderful time to be a programmer (and also a little scary). Chat GPT is excellent for this kind of thing — I could have easily spent ten minutes just remembering how to set up python environments and figuring out what library to use for making http requests. Instead, it took me 45 seconds. I’m surprised GPT even spat out an example of interacting with the Strava API. It was a real person’s work— a freely available gist that I found through google— however, that did most of the work for me.

My consultancy, Riker Bider, will be available for hire once the paperwork to incorporate is out of the way.