"html": "<blockquoteclass=\"twitter-tweet\" data-lang=\"en\" data-dnt=\"true\"><plang=\"en\" dir=\"ltr\">Day 100 of <ahref=\"https://twitter.com/hashtag/100binaries?src=hash&ref_src=twsrc%5Etfw\">#100binaries</a><br><br>Today I'm featuring the Rust compiler — the binary that made the previous 99 fast, efficient, user-friendly, easy-to-build, and reliable binaries possible.<br><br>Thanks to all the people that have worked on it past, present, and future. <ahref=\"https://t.co/aBEdLE87eq\">https://t.co/aBEdLE87eq</a><ahref=\"https://t.co/jzyJtIMGn1\">pic.twitter.com/jzyJtIMGn1</a></p>— Wesley Moore (@wezm) <ahref=\"https://twitter.com/wezm/status/1322855912076386304?ref_src=twsrc%5Etfw\">November 1, 2020</a></blockquote>\n",
"width": 550,
"height": null,
"type": "rich",
"cache_age": "3153600000",
"provider_name": "Twitter",
"provider_url": "https://twitter.com",
"version": "1.0"
}
```
The output from `xargs` is lots of these JSON objects all concatenated
together. I needed to turn [tweets.json] into an array of objects to make it
valid JSON. I opened up the file in Neovim and:
* Added commas between the JSON objects: `%s/}{/},\r{/g`.
* This is, substitute `}{` with `},{` and a newline (`\r`), multiple times (`/g`).
* Added `[` and `]` to start and end of the file.
I then reversed the order of the objects and formatted the document with `jq` (from within Neovim): `%!jq '.|reverse' -`.
This filters the whole file though a command (`%!`). The command is `jq` and it
filters the entire document `.`, read from stdin (`-`), through the `reverse`
filter to reverse the order of the array. `jq` automatically pretty prints.
It would have been better to have reversed `tweets.txt` but I didn't
realise they were in reverse chronological ordering until this point and
doing it this way avoided making another 100 HTTP requests.
### Rendering tweets.json
I created a custom [Zola shortcode][shortcode], [tweet_list] that reads
`tweets.json` and renders each item in an ordered list. It evolved over time as
I kept adding more information to the JSON file. It allowed me to see how
the blog post looked as I implemented the following improvements.
### Expanding t.co Links
{% aside(title="You used Rust for this!?", float="right") %}
This is the sort of thing that would be well suited to a scripting language
too. These days I tend to reach for Rust, even for little tasks like this.
It's what I'm most familiar with nowadays and I can mostly write a "script"
like this off the cuff with little need to refer to API docs.
{% end %}
The markup Twitter returns is full of `t.co` redirect links. I wanted to avoid
sending my visitors through the Twitter redirect so I needed to expand these
links to their target. I whipped up a little Rust program to do this:
[expand-t-co]. It finds all `t.co` links with a regex
(`https://t\.co/[a-zA-Z0-9]+`) and replaces each occurrence with the target
of the link.
The target URL is determined by making making a HTTP HEAD request for the
`t.co` URL and noting the value of the `Location` header. The tool
caches the result in a `HashMap` to avoid repeating a request for
the same `t.co` URL if it's encountered again.
I used the [ureq] crate to make the HTTP requests. Arguably it would have been
better to use an async client so that more requests were made in parallel but
that was added complexity I didn't want to deal with for a mostly one-off
program.
### Adding the Media
At this point I did a lot of manual work to find all the screenshots and videos
that I shared in the tweets and [added them to my blog][media-files]. I also
renamed them after the tool they depicted. As part of this process I noted the
source of media files that I didn't create in a `"media_source"` key in
`tweets.json` so that I could attribute them. I also added a `"media"` key with
the name of the media file for each binary.
Some of the externally sourced images were animated GIFs, which lack
playback controls and are very inefficient file size wise. Whenever I encountered an
animated GIF I converted it to an MP4 with `ffmpeg`, resulting in large space savings: