A Twitter Bot for Pushups

A few months back I started building a streaming Twitter bot, an idea of mine that had been stewing around for most of the year. As with most projects I got sidetracked, this time by a cross-country move, and it took me to the first day of 2015 to release the prototype. A rough prototype at that.

My thought had been to create a framework that could handle Twitter generic events, whether or not those events came through via a stream or periodic CURL requests. Then you could build different tools and bots that come complete with fallback measures. Plus, with queueing systems a natural extension of the framework, these bots could be more resilient to client-side outages.

I was, and still am, interested in the parallels with micro-framework routing systems. That is, defined callbacks triggered when the HTTP request matches certain conditions. With this bot framework I wanted to attach callbacks with certain Twitter events. This gets a bit more hairy, as the conditions may be more complicated than simple string matches, but the base idea is the same.

Anyways, the pushup bot. As a prototype I wanted to build something that would capture followers as participants, setup unique training profiles, send reminders to do pushups, and track performance over a time period. There would be no queueing or fallbacks if the stream fails. I'm glad I kept it simple. There were plenty of issues to work around with just this criteria.

I chose to use Guzzle 4 again for network communication, which keeps things nice and semantically clean, though it still doesn't have a good way for fetching Twitter streaming data. After many hours of debugging I ended up reading the stream in one character at a time to determine breaks between messages. Guzzle still has a hard time seeking through HTTP/1.1 streams, or so it appears.

  1. use GuzzleHttp\Client;

  2. $streaming_client = new Client([

  3. 'base_url' => 'https://userstream.twitter.com/1.1/',

  4. 'defaults' => ['auth' => 'oauth'],

  5. ]);

  6. // $oauth is already configured with Twitter creds

  7. $streaming_client->getEmitter()->attach($oauth);

  8. $result = $streaming_client->get('user.json', ['stream' => true]);

  9. $line = '';

  10. while (!$stream->eof()) {

  11. $line .= $stream->read(1);

  12. while (strstr($line, "\r\n") !== false) {

  13. list($message, $line) = explode("\r\n", $line, 2);

  14. $message = json_decode($message, true);

  15. }

  16. }

This idea of stepping through the stream a few characters at a time is nothing new - I had to do the same thing when I built a streaming Twitter bot to search the Bigstock API. That was using Guzzle 3, which used CURL and thus had a tendency to assume strings starting with '@' were file transfers.

The other difficulty I'm still dealing with is duplicate tweets. There seems to be some checks in place within the Twitter API stack for duplicate sends, either for spam protection or an extra barrier against poorly written bots, that complicates my idea. Not only does it block basic reminders to users, it also blocks user responses if they are the same. This block seems to be around 24 hours long and only for exact string matches. I can easily get around this by using formatted timestamps in the tweet or random messages, but it does create a subpar user experience, especially if someone consistently does the same number of pushups for each reminder.

Now that the prototype is out and active (you can check it out at @pushuptime) I'm probably going to let it sit for a bit before moving forward. There may be a few kinks to work out and I'm not completely happy with the structure I chose to use. As I start to use the bot (my weak programmer arms could use some pushup motivation) and play around with the code I may come up with a better structure for some of my future projects, like that sweet Twitter-based game that I keep pushing off.