I decided a couple of weeks ago that I'd like to learn C++ as a way to dive as deeply as possible into web browsers and how they work. It'd be a dream if a few years from now I could have an understanding of how nearly anything/everything that happens in a web browser works, whether it's the JavaScript engine, the renderer, or the other components I'm ignorant of right now.

To avoid tutorial hell I'm going to embark on building my first little application, which will be a CLI for posting to Mastodon. I think I shall name her... Mastopo!

Gathering Materials

First, a Google for "post to Mastodon via API" lands me here, which provides me with a sample curl call. Filling in the sample values with some real ones, I get

curl -X POST \
    -F 'client_name=Mastopo' \
    -F 'redirect_uris=urn:ietf:wg:oauth:2.0:oob' \
    -F 'scopes=read write push' \
    -F 'website=https://zev.averba.ch/learn_cpp/1' \
    https://recurse.social/api/v1/apps

This gives me back a nice bit of JSON:

{
  "id": "210",
  "name": "Mastopo",
  "website": "https://zev.averba.ch/learn_cpp/1",
  "redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
  "client_id": "some-private-stuff",
  "client_secret": "some-more-private_stuff",
  "vapid_key": "yep-also-private"
}

Continuing to follow the Mastodon API docs I send another POST request to retrieve an access token with

curl -X POST \
    -F 'client_id=that-client-id' \
    -F 'client_secret=that-secret' \
    -F 'redirect_uri=urn:ietf:wg:oauth:2.0:oob' \
    -F 'grant_type=client_credentials' \
    https://recurse.social/oauth/token

which yields

curl -X POST \
{
  "access_token": "a-useful-token",
  "token_type": "Bearer",
  "scope": "read",
  "created_at": 1672110308
}

Creating the CLI

The two steps above should be executed by the CLI itself, after asking the user for the URL of their Mastodon instance:

#include <iostream>
using namespace std;

int main() {
    char url[100] {};
    cout << "What's your Mastodon instance's URL? " << endl;
    cin >> url;
    cout << "Okay, I got " << url << endl;
    return 0;
}

We need to validate the user's input a bit, but this is a good start. We'll assume perfectly user input for the time being. 😬

It looks like there's some nice documentation for tooting programmatically here. But how to make HTTP requests (including the two above) in C++? At first I tried using libcurl. Here is my non-working code, which segfaults without a helpful error message:

#include <iostream>
#include <cstring>
#include <curl/curl.h>

int main() {
    char url[100];
    std::cout << "What's your Mastodon instance's URL? " << std::endl;
    std::cin >> url;
    std::cout << "Okay, I got " << url << std::endl;

    CURL *curl;
    CURLcode res;
    std::string readBuffer;

    curl = curl_easy_init();

    curl_global_init(CURL_GLOBAL_ALL);
    strcat(url, "/api/v1/apps");
    std::cout << "url: " << url << '
';

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(
        curl, 
        CURLOPT_POSTFIELDS, 
        "client_name=Mastopo&redirect_uris=urn:ietf:wg:oauth:2.0:oob&scopes=read%20write%20push&website=https://zev.averba.ch/learn_cpp/1"
    );
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
    curl_easy_perform(curl);

    if (res != CURLE_OK) {
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
        curl_easy_cleanup(curl);
    }
    curl_global_cleanup();
    std::cout << "readBuffer: " << readBuffer << std::endl;

    return 0;
}

The output of running the compiled app above is sometimes this:

> ./mastopo
What's your Mastodon instance's URL? 
https://recurse.social
Okay, I got https://recurse.social
url: https://recurse.social/api/v1/apps
curl_easy_perform() failed: Unknown error
readBuffer:

and sometimes this:

...
Segmentation fault (core dumped)
readBuffer:

I was going to try debugging and/or use a library next, but I actually want to retreat a bit and switch to learning C. I suspect I'll end up with code similar to the above, and that I'll circle back to C++ in the next 12 months (it's early January 2023 as I write this).

smiley picture of shaven-head Zev

American programmer living in Switzerland. Constant learner, chess improver, speech-to-text. 🌼