Sometime around Thanksgiving/November 2020, I formalized the idea that I would actually build a Twitter bot for the sake of novelty and as an experiment/learning exercise. One night at suppertime, we discussed what might be cool as a bot and those ideas are still on the list as possibilities for the future. Ultimately, I settled on a bot idea that built on some things I've used in the past (Google API) so I wouldn't have to learn All The Things in addition to building a bot and figuring out the Twitter API. And so, the idea for a "reverse countdown" (progress bar) bot was formed, based on the idea behind the Year Progress bot.
Because I'm the most familiar with it and I can easily deploy this in an existing production environment without any fiddling, I chose to go with PHP as the bot's language. Using PHP with the Google and Twitter APIs isn't exactly intuitive like many other supported languages, but that was part of the learning process!
During my time away from work at year's end, I decided to dig in and make this be A Thing.
Where To Start?
First was the need to source data to push to Twitter. I'd not really done anything with the Google Calendar API before, but knowing that the API structure and other bits would be similar to other projects I have done in the past (Docs, Sheets, Drive, etc.) I figured it'd be pretty simple. Generally speaking it was, as there is no write access required for this bot code. The Google side consists of:
- Creating a project, service account, and access keys in the Google API Console;
- Creating a "public" Google Calendar as a data source and populating some items/events; and
- Tinkering with a few of the arguments in an API call to ensure the right number and scope of events are returned.
Step 3 can be done with some minor fiddling with the PHP quickstart for Google Calendar API. I wanted to ensure I was getting proper JSON data back at this point.
Twitter Setup Bits
The Twitter side was more or less totally new to me. I'd created a project through the Twitter Developer Portal in the past while I was fiddling with a good way to output tweets (which I later abandoned), so I had already gone through the application process and could easily spin up a new project/app. I also created bot accounts on Twitter, so I was set to dig in.
First Hurdle: OAuth
Since I was using accounts not associated with the Developer Portal project/app account (my own), I had to enable "3-Legged OAuth" for the app. Doing so also requires each (bot) account to go through the OAuth process to authorize the app and obtain appropriate token and secret values.
There is no readily-available PHP library to do this, but lots of pointers to things like Twurl. Twurl is rad, but I wanted to keep the bot's source "light" and easily portable, so I ended up installing Twurl on my local WSL instance to generate the request and thus receive the token and secret for the bot account(s). This way I can locally manage the authorizations, but don't have to keep "other" stuff running or available on the production host.
Time To Tweet!
The first thing to do then was to submit some form of a Tweet to the testing bot account with code. Very little exists in PHP-land to accommodate this so I ended up writing my own
POSTing mechanism. This turned out to be the single most complicated thing in building the entire bot. And it was cause for significant swearing and frustration until it...just worked.
Without going into great detail, each Tweet posted requires a corresponding
oauth_signature to validate the request, and while Twitter has some great documentation on generating such things, very "little" things cause this to barf indisciminately, as it should. I was eventually able to figure out where the problems were (it had to do with which items needed to be encoded, and more specifically when). Several items in the signature are encoded, and several of those items ultimately more than once. Lots of Googling and trial by error.
That being said, I'm pretty proud of those 62 nicely-formatted lines (
TweetPost.php in the GitHub repo). It's worked flawlessly throughout testing and into production.
And so, posting Tweets was accomplished via code which meant it was time to create a generated Tweet.
Building a Generated Tweet
The premise behind the Holiday Progress Bot is basically to tweet the progress increments between "bank holidays" (or any two events on a Google Calendar). At the end of the day, the tweet text itself is simple; it's all the date math that's complex.
Struggles With Date Math
The math itself isn't that complicated, especially if you only ever expect to return all-day events or regular events. Start mixing all-day events with regular events and things start to get...complex. Further complicating this is "how do you want to calculate the next interval?" There's a difference in the math (more the cadence/interval) when you want to immediately roll to the next event versus "respect" the time interval of the event (be that two hours or all day). So there's some modification necessary to calculate the proper "end" date of an event and how one wants the bot to behave while an event is "active."
The TL;DR: of date math: lots of testing with lots of scenarios. I ended up adding some bot options to account for differences of need with how the bot treats such things.
Struggles with Google Calendar API
I didn't expect to encounter as much frustration with the Calendar API as I did. 95% of the API does exactly what I want and expect, but there's a major issue that I'd have expected a behemoth like Google to address: just grabbing the last event (most recent, in the past).
As it turns out the
orderBy parameter only supports two options:
updated. Thus, other parameters such as
timeMax have to be used creatively to look ahead or look behind. I was really hoping for the ability to specify the sorting behavior of
descending. This would have made things far simpler to implement, because I was really just looking for event index -1 as it were. No more, no less.
In the end I worked around this by crafting two API calls: one for upcoming events, and one for past events. The past events call uses both
timeMax parameters to "look back" three months just to make sure there's at least one event returned. Why three months? In skimming some example calendars I was thinking of using as a source, three months seemed an adequate timeframe for the longest duration between events, thus ensuring at least one event would be returned for comparison.
Pulling It Together
I have a status (or state) file written every time the bot code is invoked, done in production via cron. This status file is used to determine if/when it's necessary to tweet. Added in a bunch of debugging options, and it was ready to go.
All told, I spent about two days fiddling with the bot itself, another day fiddling with the Twitter API and
oauth_signature business, and then made a few incremental fixes before I let the bot go live on New Year's Eve/December 31, 2020.
In the end it has been fun to fiddle with, though it's a completely useless bot. I have a much more firm grasp of the Twitter API and a few other ideas for future bots, these more practical or useful in nature. It was nice to work on something totally ridiculous but super creative and "different."
Bot Source and Project Site
I have published the bot's source on GitHub and created a project site for it as well. Feel free to fiddle with it on your own if for no other reason than you can! :)
Headline image via giphy